github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/pkg/sentry/fsimpl/fuse/regular_file.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 fuse
    16  
    17  import (
    18  	"io"
    19  	"math"
    20  	"sync"
    21  
    22  	"github.com/nicocha30/gvisor-ligolo/pkg/abi/linux"
    23  	"github.com/nicocha30/gvisor-ligolo/pkg/context"
    24  	"github.com/nicocha30/gvisor-ligolo/pkg/errors/linuxerr"
    25  	"github.com/nicocha30/gvisor-ligolo/pkg/hostarch"
    26  	"github.com/nicocha30/gvisor-ligolo/pkg/sentry/fsutil"
    27  	"github.com/nicocha30/gvisor-ligolo/pkg/sentry/kernel/auth"
    28  	"github.com/nicocha30/gvisor-ligolo/pkg/sentry/memmap"
    29  	"github.com/nicocha30/gvisor-ligolo/pkg/sentry/vfs"
    30  	"github.com/nicocha30/gvisor-ligolo/pkg/usermem"
    31  )
    32  
    33  type regularFileFD struct {
    34  	fileDescription
    35  
    36  	// offMu protects off.
    37  	offMu sync.Mutex
    38  
    39  	// off is the file offset.
    40  	// +checklocks:offMu
    41  	off int64
    42  
    43  	// mapsMu protects mappings.
    44  	mapsMu sync.Mutex `state:"nosave"`
    45  
    46  	// mappings tracks mappings of the file into memmap.MappingSpaces.
    47  	//
    48  	// Protected by mapsMu.
    49  	mappings memmap.MappingSet
    50  
    51  	// dataMu protects the fields below.
    52  	dataMu sync.RWMutex `state:"nosave"`
    53  
    54  	// data maps offsets into the file to offsets into memFile that store
    55  	// the file's data.
    56  	//
    57  	// Protected by dataMu.
    58  	data fsutil.FileRangeSet
    59  }
    60  
    61  // Seek implements vfs.FileDescriptionImpl.Allocate.
    62  func (fd *regularFileFD) Allocate(ctx context.Context, mode, offset, length uint64) error {
    63  	if mode & ^uint64(linux.FALLOC_FL_KEEP_SIZE|linux.FALLOC_FL_PUNCH_HOLE|linux.FALLOC_FL_ZERO_RANGE) != 0 {
    64  		return linuxerr.EOPNOTSUPP
    65  	}
    66  	in := linux.FUSEFallocateIn{
    67  		Fh:     fd.Fh,
    68  		Offset: uint64(offset),
    69  		Length: uint64(length),
    70  		Mode:   uint32(mode),
    71  	}
    72  	i := fd.inode()
    73  	req := i.fs.conn.NewRequest(auth.CredentialsFromContext(ctx), pidFromContext(ctx), i.nodeID, linux.FUSE_FALLOCATE, &in)
    74  	res, err := i.fs.conn.Call(ctx, req)
    75  	if err != nil {
    76  		return err
    77  	}
    78  	if err := res.Error(); err != nil {
    79  		return err
    80  	}
    81  	i.attrMu.Lock()
    82  	defer i.attrMu.Unlock()
    83  	if uint64(offset+length) > i.size.Load() {
    84  		if err := i.reviseAttr(ctx, linux.FUSE_GETATTR_FH, fd.Fh); err != nil {
    85  			return err
    86  		}
    87  		// If the offset after update is still too large, return error.
    88  		if uint64(offset) >= i.size.Load() {
    89  			return io.EOF
    90  		}
    91  	}
    92  	return nil
    93  }
    94  
    95  // Seek implements vfs.FileDescriptionImpl.Seek.
    96  func (fd *regularFileFD) Seek(ctx context.Context, offset int64, whence int32) (int64, error) {
    97  	fd.offMu.Lock()
    98  	defer fd.offMu.Unlock()
    99  	inode := fd.inode()
   100  	inode.attrMu.Lock()
   101  	defer inode.attrMu.Unlock()
   102  	switch whence {
   103  	case linux.SEEK_SET:
   104  		// use offset as specified
   105  	case linux.SEEK_CUR:
   106  		offset += fd.off
   107  	case linux.SEEK_END:
   108  		offset += int64(inode.size.Load())
   109  	default:
   110  		return 0, linuxerr.EINVAL
   111  	}
   112  	if offset < 0 {
   113  		return 0, linuxerr.EINVAL
   114  	}
   115  	fd.off = offset
   116  	return offset, nil
   117  }
   118  
   119  // PRead implements vfs.FileDescriptionImpl.PRead.
   120  func (fd *regularFileFD) PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts vfs.ReadOptions) (int64, error) {
   121  	if offset < 0 {
   122  		return 0, linuxerr.EINVAL
   123  	}
   124  
   125  	// Check that flags are supported.
   126  	//
   127  	// TODO(gvisor.dev/issue/2601): Support select preadv2 flags.
   128  	if opts.Flags&^linux.RWF_HIPRI != 0 {
   129  		return 0, linuxerr.EOPNOTSUPP
   130  	}
   131  
   132  	size := dst.NumBytes()
   133  	if size == 0 {
   134  		// Early return if count is 0.
   135  		return 0, nil
   136  	} else if size > math.MaxUint32 {
   137  		// FUSE only supports uint32 for size.
   138  		// Overflow.
   139  		return 0, linuxerr.EINVAL
   140  	}
   141  
   142  	// TODO(gvisor.dev/issue/3678): Add direct IO support.
   143  
   144  	inode := fd.inode()
   145  	inode.attrMu.Lock()
   146  	defer inode.attrMu.Unlock()
   147  
   148  	// Reading beyond EOF, update file size if outdated.
   149  	if uint64(offset+size) > inode.size.Load() {
   150  		if err := inode.reviseAttr(ctx, linux.FUSE_GETATTR_FH, fd.Fh); err != nil {
   151  			return 0, err
   152  		}
   153  		// If the offset after update is still too large, return error.
   154  		if uint64(offset) >= inode.size.Load() {
   155  			return 0, io.EOF
   156  		}
   157  	}
   158  
   159  	// Truncate the read with updated file size.
   160  	fileSize := inode.size.Load()
   161  	if uint64(offset+size) > fileSize {
   162  		size = int64(fileSize) - offset
   163  	}
   164  
   165  	buffers, n, err := inode.fs.ReadInPages(ctx, fd, uint64(offset), uint32(size))
   166  	if err != nil {
   167  		return 0, err
   168  	}
   169  
   170  	// TODO(gvisor.dev/issue/3237): support indirect IO (e.g. caching),
   171  	// store the bytes that were read ahead.
   172  
   173  	// Update the number of bytes to copy for short read.
   174  	if n < uint32(size) {
   175  		size = int64(n)
   176  	}
   177  
   178  	// Copy the bytes read to the dst.
   179  	// This loop is intended for fragmented reads.
   180  	// For the majority of reads, this loop only execute once.
   181  	var copied int64
   182  	for _, buffer := range buffers {
   183  		toCopy := int64(len(buffer))
   184  		if copied+toCopy > size {
   185  			toCopy = size - copied
   186  		}
   187  		cp, err := dst.DropFirst64(copied).CopyOut(ctx, buffer[:toCopy])
   188  		if err != nil {
   189  			return 0, err
   190  		}
   191  		if int64(cp) != toCopy {
   192  			return 0, linuxerr.EIO
   193  		}
   194  		copied += toCopy
   195  	}
   196  
   197  	return copied, nil
   198  }
   199  
   200  // Read implements vfs.FileDescriptionImpl.Read.
   201  func (fd *regularFileFD) Read(ctx context.Context, dst usermem.IOSequence, opts vfs.ReadOptions) (int64, error) {
   202  	fd.offMu.Lock()
   203  	n, err := fd.PRead(ctx, dst, fd.off, opts)
   204  	fd.off += n
   205  	fd.offMu.Unlock()
   206  	return n, err
   207  }
   208  
   209  // PWrite implements vfs.FileDescriptionImpl.PWrite.
   210  func (fd *regularFileFD) PWrite(ctx context.Context, src usermem.IOSequence, offset int64, opts vfs.WriteOptions) (int64, error) {
   211  	n, _, err := fd.pwrite(ctx, src, offset, opts)
   212  	return n, err
   213  }
   214  
   215  // Write implements vfs.FileDescriptionImpl.Write.
   216  func (fd *regularFileFD) Write(ctx context.Context, src usermem.IOSequence, opts vfs.WriteOptions) (int64, error) {
   217  	fd.offMu.Lock()
   218  	n, off, err := fd.pwrite(ctx, src, fd.off, opts)
   219  	fd.off = off
   220  	fd.offMu.Unlock()
   221  	return n, err
   222  }
   223  
   224  // pwrite returns the number of bytes written, final offset and error. The
   225  // final offset should be ignored by PWrite.
   226  func (fd *regularFileFD) pwrite(ctx context.Context, src usermem.IOSequence, offset int64, opts vfs.WriteOptions) (int64, int64, error) {
   227  	if offset < 0 {
   228  		return 0, offset, linuxerr.EINVAL
   229  	}
   230  
   231  	// Check that flags are supported.
   232  	//
   233  	// TODO(gvisor.dev/issue/2601): Support select preadv2 flags.
   234  	if opts.Flags&^linux.RWF_HIPRI != 0 {
   235  		return 0, offset, linuxerr.EOPNOTSUPP
   236  	}
   237  
   238  	inode := fd.inode()
   239  	inode.attrMu.Lock()
   240  	defer inode.attrMu.Unlock()
   241  
   242  	// If the file is opened with O_APPEND, update offset to file size.
   243  	// Note: since our Open() implements the interface of kernfs,
   244  	// and kernfs currently does not support O_APPEND, this will never
   245  	// be true before we switch out from kernfs.
   246  	if fd.vfsfd.StatusFlags()&linux.O_APPEND != 0 {
   247  		// Locking inode.metadataMu is sufficient for reading size
   248  		offset = int64(inode.size.Load())
   249  	}
   250  
   251  	srclen := src.NumBytes()
   252  	if srclen > math.MaxUint32 {
   253  		// FUSE only supports uint32 for size.
   254  		// Overflow.
   255  		return 0, offset, linuxerr.EINVAL
   256  	}
   257  	if end := offset + srclen; end < offset {
   258  		// Overflow.
   259  		return 0, offset, linuxerr.EINVAL
   260  	}
   261  
   262  	limit, err := vfs.CheckLimit(ctx, offset, srclen)
   263  	if err != nil {
   264  		return 0, offset, err
   265  	}
   266  	if limit == 0 {
   267  		// Return before causing any side effects.
   268  		return 0, offset, nil
   269  	}
   270  	src = src.TakeFirst64(limit)
   271  
   272  	n, offset, err := inode.fs.Write(ctx, fd, offset, src)
   273  	if n == 0 {
   274  		// We have checked srclen != 0 previously.
   275  		// If err == nil, then it's a short write and we return EIO.
   276  		return 0, offset, linuxerr.EIO
   277  	}
   278  
   279  	if offset > int64(inode.size.Load()) {
   280  		inode.size.Store(uint64(offset))
   281  		inode.fs.conn.attributeVersion.Add(1)
   282  	}
   283  	inode.touchCMtime()
   284  	return n, offset, err
   285  }
   286  
   287  // ConfigureMMap implements vfs.FileDescriptionImpl.ConfigureMMap.
   288  func (fd *regularFileFD) ConfigureMMap(ctx context.Context, opts *memmap.MMapOpts) error {
   289  	return linuxerr.ENOSYS
   290  }
   291  
   292  // AddMapping implements memmap.Mappable.AddMapping.
   293  func (fd *regularFileFD) AddMapping(ctx context.Context, ms memmap.MappingSpace, ar hostarch.AddrRange, offset uint64, writable bool) error {
   294  	return linuxerr.ENOSYS
   295  }
   296  
   297  // RemoveMapping implements memmap.Mappable.RemoveMapping.
   298  func (fd *regularFileFD) RemoveMapping(ctx context.Context, ms memmap.MappingSpace, ar hostarch.AddrRange, offset uint64, writable bool) {
   299  }
   300  
   301  // CopyMapping implements memmap.Mappable.CopyMapping.
   302  func (fd *regularFileFD) CopyMapping(ctx context.Context, ms memmap.MappingSpace, srcAR, dstAR hostarch.AddrRange, offset uint64, writable bool) error {
   303  	return linuxerr.ENOSYS
   304  }
   305  
   306  // Translate implements memmap.Mappable.Translate.
   307  func (fd *regularFileFD) Translate(ctx context.Context, required, optional memmap.MappableRange, at hostarch.AccessType) ([]memmap.Translation, error) {
   308  	return nil, linuxerr.ENOSYS
   309  }
   310  
   311  // InvalidateUnsavable implements memmap.Mappable.InvalidateUnsavable.
   312  func (fd *regularFileFD) InvalidateUnsavable(ctx context.Context) error {
   313  	return linuxerr.ENOSYS
   314  }