gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/sentry/hostfd/hostfd.go (about)

     1  // Copyright 2020 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package hostfd provides efficient I/O with host file descriptors.
    16  package hostfd
    17  
    18  import (
    19  	"io"
    20  
    21  	"golang.org/x/sys/unix"
    22  	"gvisor.dev/gvisor/pkg/safemem"
    23  	"gvisor.dev/gvisor/pkg/sync"
    24  )
    25  
    26  // ReadWriterAt implements safemem.Reader and safemem.Writer by reading from
    27  // and writing to a host file descriptor respectively. ReadWriterAts should be
    28  // obtained by calling GetReadWriterAt.
    29  //
    30  // Clients should usually prefer to use Preadv2 and Pwritev2 directly.
    31  type ReadWriterAt struct {
    32  	fd     int32
    33  	offset int64
    34  	flags  uint32
    35  }
    36  
    37  var rwpool = sync.Pool{
    38  	New: func() any {
    39  		return &ReadWriterAt{}
    40  	},
    41  }
    42  
    43  // GetReadWriterAt returns a ReadWriterAt that reads from / writes to the given
    44  // host file descriptor, starting at the given offset and using the given
    45  // preadv2(2)/pwritev2(2) flags. If offset is -1, the host file descriptor's
    46  // offset is used instead. Users are responsible for ensuring that fd remains
    47  // valid for the lifetime of the returned ReadWriterAt, and must call
    48  // PutReadWriterAt when it is no longer needed.
    49  func GetReadWriterAt(fd int32, offset int64, flags uint32) *ReadWriterAt {
    50  	rw := rwpool.Get().(*ReadWriterAt)
    51  	*rw = ReadWriterAt{
    52  		fd:     fd,
    53  		offset: offset,
    54  		flags:  flags,
    55  	}
    56  	return rw
    57  }
    58  
    59  // PutReadWriterAt releases a ReadWriterAt returned by a previous call to
    60  // GetReadWriterAt that is no longer in use.
    61  func PutReadWriterAt(rw *ReadWriterAt) {
    62  	rwpool.Put(rw)
    63  }
    64  
    65  // ReadToBlocks implements safemem.Reader.ReadToBlocks.
    66  func (rw *ReadWriterAt) ReadToBlocks(dsts safemem.BlockSeq) (uint64, error) {
    67  	if dsts.IsEmpty() {
    68  		return 0, nil
    69  	}
    70  	n, err := Preadv2(rw.fd, dsts, rw.offset, rw.flags)
    71  	if rw.offset >= 0 {
    72  		rw.offset += int64(n)
    73  	}
    74  	return n, err
    75  }
    76  
    77  // WriteFromBlocks implements safemem.Writer.WriteFromBlocks.
    78  func (rw *ReadWriterAt) WriteFromBlocks(srcs safemem.BlockSeq) (uint64, error) {
    79  	if srcs.IsEmpty() {
    80  		return 0, nil
    81  	}
    82  	n, err := Pwritev2(rw.fd, srcs, rw.offset, rw.flags)
    83  	if rw.offset >= 0 {
    84  		rw.offset += int64(n)
    85  	}
    86  	return n, err
    87  }
    88  
    89  // Preadv2 reads up to dsts.NumBytes() bytes from host file descriptor fd into
    90  // dsts. offset and flags are interpreted as for preadv2(2).
    91  //
    92  // Preconditions: !dsts.IsEmpty().
    93  func Preadv2(fd int32, dsts safemem.BlockSeq, offset int64, flags uint32) (uint64, error) {
    94  	// No buffering is necessary regardless of safecopy; host syscalls will
    95  	// return EFAULT if appropriate, instead of raising SIGBUS.
    96  	var (
    97  		n uintptr
    98  		e unix.Errno
    99  	)
   100  	if flags == 0 && dsts.NumBlocks() == 1 {
   101  		// Use read() or pread() to avoid iovec allocation and copying.
   102  		dst := dsts.Head()
   103  		if offset == -1 {
   104  			n, _, e = unix.Syscall(unix.SYS_READ, uintptr(fd), dst.Addr(), uintptr(dst.Len()))
   105  		} else {
   106  			n, _, e = unix.Syscall6(unix.SYS_PREAD64, uintptr(fd), dst.Addr(), uintptr(dst.Len()), uintptr(offset), 0 /* pos_h */, 0 /* unused */)
   107  		}
   108  	} else {
   109  		n, e = iovecsReadWrite(unix.SYS_PREADV2, fd, safemem.IovecsFromBlockSeq(dsts), offset, flags)
   110  	}
   111  	if e != 0 {
   112  		return 0, e
   113  	}
   114  	if n == 0 {
   115  		return 0, io.EOF
   116  	}
   117  	return uint64(n), nil
   118  }
   119  
   120  // Pwritev2 writes up to srcs.NumBytes() from srcs into host file descriptor
   121  // fd. offset and flags are interpreted as for pwritev2(2).
   122  //
   123  // Preconditions: !srcs.IsEmpty().
   124  func Pwritev2(fd int32, srcs safemem.BlockSeq, offset int64, flags uint32) (uint64, error) {
   125  	// No buffering is necessary regardless of safecopy; host syscalls will
   126  	// return EFAULT if appropriate, instead of raising SIGBUS.
   127  	var (
   128  		n uintptr
   129  		e unix.Errno
   130  	)
   131  	if flags == 0 && srcs.NumBlocks() == 1 {
   132  		// Use write() or pwrite() to avoid iovec allocation and copying.
   133  		src := srcs.Head()
   134  		if offset == -1 {
   135  			n, _, e = unix.Syscall(unix.SYS_WRITE, uintptr(fd), src.Addr(), uintptr(src.Len()))
   136  		} else {
   137  			n, _, e = unix.Syscall6(unix.SYS_PWRITE64, uintptr(fd), src.Addr(), uintptr(src.Len()), uintptr(offset), 0 /* pos_h */, 0 /* unused */)
   138  		}
   139  	} else {
   140  		n, e = iovecsReadWrite(unix.SYS_PWRITEV2, fd, safemem.IovecsFromBlockSeq(srcs), offset, flags)
   141  	}
   142  	if e != 0 {
   143  		return 0, e
   144  	}
   145  	return uint64(n), nil
   146  }