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 }