diff options
Diffstat (limited to 'pkg/sentry/fsimpl/gofer/regular_file.go')
-rw-r--r-- | pkg/sentry/fsimpl/gofer/regular_file.go | 872 |
1 files changed, 0 insertions, 872 deletions
diff --git a/pkg/sentry/fsimpl/gofer/regular_file.go b/pkg/sentry/fsimpl/gofer/regular_file.go deleted file mode 100644 index e95209661..000000000 --- a/pkg/sentry/fsimpl/gofer/regular_file.go +++ /dev/null @@ -1,872 +0,0 @@ -// Copyright 2019 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package gofer - -import ( - "fmt" - "io" - "math" - "sync" - "sync/atomic" - - "gvisor.dev/gvisor/pkg/abi/linux" - "gvisor.dev/gvisor/pkg/context" - "gvisor.dev/gvisor/pkg/log" - "gvisor.dev/gvisor/pkg/safemem" - "gvisor.dev/gvisor/pkg/sentry/fs/fsutil" - "gvisor.dev/gvisor/pkg/sentry/memmap" - "gvisor.dev/gvisor/pkg/sentry/pgalloc" - "gvisor.dev/gvisor/pkg/sentry/platform" - "gvisor.dev/gvisor/pkg/sentry/usage" - "gvisor.dev/gvisor/pkg/sentry/vfs" - "gvisor.dev/gvisor/pkg/syserror" - "gvisor.dev/gvisor/pkg/usermem" -) - -func (d *dentry) isRegularFile() bool { - return d.fileType() == linux.S_IFREG -} - -type regularFileFD struct { - fileDescription - - // off is the file offset. off is protected by mu. - mu sync.Mutex - off int64 -} - -// Release implements vfs.FileDescriptionImpl.Release. -func (fd *regularFileFD) Release() { -} - -// OnClose implements vfs.FileDescriptionImpl.OnClose. -func (fd *regularFileFD) OnClose(ctx context.Context) error { - if !fd.vfsfd.IsWritable() { - return nil - } - // Skip flushing if writes may be buffered by the client, since (as with - // the VFS1 client) we don't flush buffered writes on close anyway. - d := fd.dentry() - if d.fs.opts.interop == InteropModeExclusive { - return nil - } - d.handleMu.RLock() - defer d.handleMu.RUnlock() - return d.handle.file.flush(ctx) -} - -// PRead implements vfs.FileDescriptionImpl.PRead. -func (fd *regularFileFD) PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts vfs.ReadOptions) (int64, error) { - if offset < 0 { - return 0, syserror.EINVAL - } - if opts.Flags != 0 { - return 0, syserror.EOPNOTSUPP - } - - // Check for reading at EOF before calling into MM (but not under - // InteropModeShared, which makes d.size unreliable). - d := fd.dentry() - if d.fs.opts.interop != InteropModeShared && uint64(offset) >= atomic.LoadUint64(&d.size) { - return 0, io.EOF - } - - if fd.vfsfd.StatusFlags()&linux.O_DIRECT != 0 { - // Lock d.metadataMu for the rest of the read to prevent d.size from - // changing. - d.metadataMu.Lock() - defer d.metadataMu.Unlock() - // Write dirty cached pages that will be touched by the read back to - // the remote file. - if err := d.writeback(ctx, offset, dst.NumBytes()); err != nil { - return 0, err - } - } - - rw := getDentryReadWriter(ctx, d, offset) - if fd.vfsfd.StatusFlags()&linux.O_DIRECT != 0 { - // Require the read to go to the remote file. - rw.direct = true - } - n, err := dst.CopyOutFrom(ctx, rw) - putDentryReadWriter(rw) - if d.fs.opts.interop != InteropModeShared { - // Compare Linux's mm/filemap.c:do_generic_file_read() => file_accessed(). - d.touchAtime(ctx, fd.vfsfd.Mount()) - } - return n, err -} - -// Read implements vfs.FileDescriptionImpl.Read. -func (fd *regularFileFD) Read(ctx context.Context, dst usermem.IOSequence, opts vfs.ReadOptions) (int64, error) { - fd.mu.Lock() - n, err := fd.PRead(ctx, dst, fd.off, opts) - fd.off += n - fd.mu.Unlock() - return n, err -} - -// PWrite implements vfs.FileDescriptionImpl.PWrite. -func (fd *regularFileFD) PWrite(ctx context.Context, src usermem.IOSequence, offset int64, opts vfs.WriteOptions) (int64, error) { - if offset < 0 { - return 0, syserror.EINVAL - } - if opts.Flags != 0 { - return 0, syserror.EOPNOTSUPP - } - - d := fd.dentry() - d.metadataMu.Lock() - defer d.metadataMu.Unlock() - if d.fs.opts.interop != InteropModeShared { - // Compare Linux's mm/filemap.c:__generic_file_write_iter() => - // file_update_time(). This is d.touchCMtime(), but without locking - // d.metadataMu (recursively). - if now, ok := nowFromContext(ctx); ok { - atomic.StoreInt64(&d.mtime, now) - atomic.StoreInt64(&d.ctime, now) - } - } - if fd.vfsfd.StatusFlags()&linux.O_DIRECT != 0 { - // Write dirty cached pages that will be touched by the write back to - // the remote file. - if err := d.writeback(ctx, offset, src.NumBytes()); err != nil { - return 0, err - } - // Remove touched pages from the cache. - pgstart := pageRoundDown(uint64(offset)) - pgend := pageRoundUp(uint64(offset + src.NumBytes())) - if pgend < pgstart { - return 0, syserror.EINVAL - } - mr := memmap.MappableRange{pgstart, pgend} - var freed []platform.FileRange - d.dataMu.Lock() - cseg := d.cache.LowerBoundSegment(mr.Start) - for cseg.Ok() && cseg.Start() < mr.End { - cseg = d.cache.Isolate(cseg, mr) - freed = append(freed, platform.FileRange{cseg.Value(), cseg.Value() + cseg.Range().Length()}) - cseg = d.cache.Remove(cseg).NextSegment() - } - d.dataMu.Unlock() - // Invalidate mappings of removed pages. - d.mapsMu.Lock() - d.mappings.Invalidate(mr, memmap.InvalidateOpts{}) - d.mapsMu.Unlock() - // Finally free pages removed from the cache. - mf := d.fs.mfp.MemoryFile() - for _, freedFR := range freed { - mf.DecRef(freedFR) - } - } - rw := getDentryReadWriter(ctx, d, offset) - if fd.vfsfd.StatusFlags()&linux.O_DIRECT != 0 { - // Require the write to go to the remote file. - rw.direct = true - } - n, err := src.CopyInTo(ctx, rw) - putDentryReadWriter(rw) - if n != 0 && fd.vfsfd.StatusFlags()&(linux.O_DSYNC|linux.O_SYNC) != 0 { - // Write dirty cached pages touched by the write back to the remote - // file. - if err := d.writeback(ctx, offset, src.NumBytes()); err != nil { - return 0, err - } - // Request the remote filesystem to sync the remote file. - if err := d.handle.file.fsync(ctx); err != nil { - return 0, err - } - } - return n, err -} - -// Write implements vfs.FileDescriptionImpl.Write. -func (fd *regularFileFD) Write(ctx context.Context, src usermem.IOSequence, opts vfs.WriteOptions) (int64, error) { - fd.mu.Lock() - n, err := fd.PWrite(ctx, src, fd.off, opts) - fd.off += n - fd.mu.Unlock() - return n, err -} - -type dentryReadWriter struct { - ctx context.Context - d *dentry - off uint64 - direct bool -} - -var dentryReadWriterPool = sync.Pool{ - New: func() interface{} { - return &dentryReadWriter{} - }, -} - -func getDentryReadWriter(ctx context.Context, d *dentry, offset int64) *dentryReadWriter { - rw := dentryReadWriterPool.Get().(*dentryReadWriter) - rw.ctx = ctx - rw.d = d - rw.off = uint64(offset) - rw.direct = false - return rw -} - -func putDentryReadWriter(rw *dentryReadWriter) { - rw.ctx = nil - rw.d = nil - dentryReadWriterPool.Put(rw) -} - -// ReadToBlocks implements safemem.Reader.ReadToBlocks. -func (rw *dentryReadWriter) ReadToBlocks(dsts safemem.BlockSeq) (uint64, error) { - if dsts.IsEmpty() { - return 0, nil - } - - // If we have a mmappable host FD (which must be used here to ensure - // coherence with memory-mapped I/O), or if InteropModeShared is in effect - // (which prevents us from caching file contents and makes dentry.size - // unreliable), or if the file was opened O_DIRECT, read directly from - // dentry.handle without locking dentry.dataMu. - rw.d.handleMu.RLock() - if (rw.d.handle.fd >= 0 && !rw.d.fs.opts.forcePageCache) || rw.d.fs.opts.interop == InteropModeShared || rw.direct { - n, err := rw.d.handle.readToBlocksAt(rw.ctx, dsts, rw.off) - rw.d.handleMu.RUnlock() - rw.off += n - return n, err - } - - // Otherwise read from/through the cache. - mf := rw.d.fs.mfp.MemoryFile() - fillCache := mf.ShouldCacheEvictable() - var dataMuUnlock func() - if fillCache { - rw.d.dataMu.Lock() - dataMuUnlock = rw.d.dataMu.Unlock - } else { - rw.d.dataMu.RLock() - dataMuUnlock = rw.d.dataMu.RUnlock - } - - // Compute the range to read (limited by file size and overflow-checked). - if rw.off >= rw.d.size { - dataMuUnlock() - rw.d.handleMu.RUnlock() - return 0, io.EOF - } - end := rw.d.size - if rend := rw.off + dsts.NumBytes(); rend > rw.off && rend < end { - end = rend - } - - var done uint64 - seg, gap := rw.d.cache.Find(rw.off) - for rw.off < end { - mr := memmap.MappableRange{rw.off, end} - switch { - case seg.Ok(): - // Get internal mappings from the cache. - ims, err := mf.MapInternal(seg.FileRangeOf(seg.Range().Intersect(mr)), usermem.Read) - if err != nil { - dataMuUnlock() - rw.d.handleMu.RUnlock() - return done, err - } - - // Copy from internal mappings. - n, err := safemem.CopySeq(dsts, ims) - done += n - rw.off += n - dsts = dsts.DropFirst64(n) - if err != nil { - dataMuUnlock() - rw.d.handleMu.RUnlock() - return done, err - } - - // Continue. - seg, gap = seg.NextNonEmpty() - - case gap.Ok(): - gapMR := gap.Range().Intersect(mr) - if fillCache { - // Read into the cache, then re-enter the loop to read from the - // cache. - reqMR := memmap.MappableRange{ - Start: pageRoundDown(gapMR.Start), - End: pageRoundUp(gapMR.End), - } - optMR := gap.Range() - err := rw.d.cache.Fill(rw.ctx, reqMR, maxFillRange(reqMR, optMR), mf, usage.PageCache, rw.d.handle.readToBlocksAt) - mf.MarkEvictable(rw.d, pgalloc.EvictableRange{optMR.Start, optMR.End}) - seg, gap = rw.d.cache.Find(rw.off) - if !seg.Ok() { - dataMuUnlock() - rw.d.handleMu.RUnlock() - return done, err - } - // err might have occurred in part of gap.Range() outside - // gapMR. Forget about it for now; if the error matters and - // persists, we'll run into it again in a later iteration of - // this loop. - } else { - // Read directly from the file. - gapDsts := dsts.TakeFirst64(gapMR.Length()) - n, err := rw.d.handle.readToBlocksAt(rw.ctx, gapDsts, gapMR.Start) - done += n - rw.off += n - dsts = dsts.DropFirst64(n) - // Partial reads are fine. But we must stop reading. - if n != gapDsts.NumBytes() || err != nil { - dataMuUnlock() - rw.d.handleMu.RUnlock() - return done, err - } - - // Continue. - seg, gap = gap.NextSegment(), fsutil.FileRangeGapIterator{} - } - } - } - dataMuUnlock() - rw.d.handleMu.RUnlock() - return done, nil -} - -// WriteFromBlocks implements safemem.Writer.WriteFromBlocks. -// -// Preconditions: rw.d.metadataMu must be locked. -func (rw *dentryReadWriter) WriteFromBlocks(srcs safemem.BlockSeq) (uint64, error) { - if srcs.IsEmpty() { - return 0, nil - } - - // If we have a mmappable host FD (which must be used here to ensure - // coherence with memory-mapped I/O), or if InteropModeShared is in effect - // (which prevents us from caching file contents), or if the file was - // opened with O_DIRECT, write directly to dentry.handle without locking - // dentry.dataMu. - rw.d.handleMu.RLock() - if (rw.d.handle.fd >= 0 && !rw.d.fs.opts.forcePageCache) || rw.d.fs.opts.interop == InteropModeShared || rw.direct { - n, err := rw.d.handle.writeFromBlocksAt(rw.ctx, srcs, rw.off) - rw.off += n - rw.d.dataMu.Lock() - if rw.off > rw.d.size { - atomic.StoreUint64(&rw.d.size, rw.off) - // The remote file's size will implicitly be extended to the correct - // value when we write back to it. - } - rw.d.dataMu.Unlock() - rw.d.handleMu.RUnlock() - return n, err - } - - // Otherwise write to/through the cache. - mf := rw.d.fs.mfp.MemoryFile() - rw.d.dataMu.Lock() - - // Compute the range to write (overflow-checked). - start := rw.off - end := rw.off + srcs.NumBytes() - if end <= rw.off { - end = math.MaxInt64 - } - - var ( - done uint64 - retErr error - ) - seg, gap := rw.d.cache.Find(rw.off) - for rw.off < end { - mr := memmap.MappableRange{rw.off, end} - switch { - case seg.Ok(): - // Get internal mappings from the cache. - segMR := seg.Range().Intersect(mr) - ims, err := mf.MapInternal(seg.FileRangeOf(segMR), usermem.Write) - if err != nil { - retErr = err - goto exitLoop - } - - // Copy to internal mappings. - n, err := safemem.CopySeq(ims, srcs) - done += n - rw.off += n - srcs = srcs.DropFirst64(n) - rw.d.dirty.MarkDirty(segMR) - if err != nil { - retErr = err - goto exitLoop - } - - // Continue. - seg, gap = seg.NextNonEmpty() - - case gap.Ok(): - // Write directly to the file. At present, we never fill the cache - // when writing, since doing so can convert small writes into - // inefficient read-modify-write cycles, and we have no mechanism - // for detecting or avoiding this. - gapMR := gap.Range().Intersect(mr) - gapSrcs := srcs.TakeFirst64(gapMR.Length()) - n, err := rw.d.handle.writeFromBlocksAt(rw.ctx, gapSrcs, gapMR.Start) - done += n - rw.off += n - srcs = srcs.DropFirst64(n) - // Partial writes are fine. But we must stop writing. - if n != gapSrcs.NumBytes() || err != nil { - retErr = err - goto exitLoop - } - - // Continue. - seg, gap = gap.NextSegment(), fsutil.FileRangeGapIterator{} - } - } -exitLoop: - if rw.off > rw.d.size { - atomic.StoreUint64(&rw.d.size, rw.off) - // The remote file's size will implicitly be extended to the correct - // value when we write back to it. - } - // If InteropModeWritethrough is in effect, flush written data back to the - // remote filesystem. - if rw.d.fs.opts.interop == InteropModeWritethrough && done != 0 { - if err := fsutil.SyncDirty(rw.ctx, memmap.MappableRange{ - Start: start, - End: rw.off, - }, &rw.d.cache, &rw.d.dirty, rw.d.size, mf, rw.d.handle.writeFromBlocksAt); err != nil { - // We have no idea how many bytes were actually flushed. - rw.off = start - done = 0 - retErr = err - } - } - rw.d.dataMu.Unlock() - rw.d.handleMu.RUnlock() - return done, retErr -} - -func (d *dentry) writeback(ctx context.Context, offset, size int64) error { - if size == 0 { - return nil - } - d.handleMu.RLock() - defer d.handleMu.RUnlock() - d.dataMu.Lock() - defer d.dataMu.Unlock() - // Compute the range of valid bytes (overflow-checked). - if uint64(offset) >= d.size { - return nil - } - end := int64(d.size) - if rend := offset + size; rend > offset && rend < end { - end = rend - } - return fsutil.SyncDirty(ctx, memmap.MappableRange{ - Start: uint64(offset), - End: uint64(end), - }, &d.cache, &d.dirty, d.size, d.fs.mfp.MemoryFile(), d.handle.writeFromBlocksAt) -} - -// Seek implements vfs.FileDescriptionImpl.Seek. -func (fd *regularFileFD) Seek(ctx context.Context, offset int64, whence int32) (int64, error) { - fd.mu.Lock() - defer fd.mu.Unlock() - switch whence { - case linux.SEEK_SET: - // Use offset as specified. - case linux.SEEK_CUR: - offset += fd.off - case linux.SEEK_END, linux.SEEK_DATA, linux.SEEK_HOLE: - // Ensure file size is up to date. - d := fd.dentry() - if fd.filesystem().opts.interop == InteropModeShared { - if err := d.updateFromGetattr(ctx); err != nil { - return 0, err - } - } - size := int64(atomic.LoadUint64(&d.size)) - // For SEEK_DATA and SEEK_HOLE, treat the file as a single contiguous - // block of data. - switch whence { - case linux.SEEK_END: - offset += size - case linux.SEEK_DATA: - if offset > size { - return 0, syserror.ENXIO - } - // Use offset as specified. - case linux.SEEK_HOLE: - if offset > size { - return 0, syserror.ENXIO - } - offset = size - } - default: - return 0, syserror.EINVAL - } - if offset < 0 { - return 0, syserror.EINVAL - } - fd.off = offset - return offset, nil -} - -// Sync implements vfs.FileDescriptionImpl.Sync. -func (fd *regularFileFD) Sync(ctx context.Context) error { - return fd.dentry().syncSharedHandle(ctx) -} - -func (d *dentry) syncSharedHandle(ctx context.Context) error { - d.handleMu.RLock() - if !d.handleWritable { - d.handleMu.RUnlock() - return nil - } - d.dataMu.Lock() - // Write dirty cached data to the remote file. - err := fsutil.SyncDirtyAll(ctx, &d.cache, &d.dirty, d.size, d.fs.mfp.MemoryFile(), d.handle.writeFromBlocksAt) - d.dataMu.Unlock() - if err == nil { - // Sync the remote file. - err = d.handle.sync(ctx) - } - d.handleMu.RUnlock() - return err -} - -// ConfigureMMap implements vfs.FileDescriptionImpl.ConfigureMMap. -func (fd *regularFileFD) ConfigureMMap(ctx context.Context, opts *memmap.MMapOpts) error { - d := fd.dentry() - switch d.fs.opts.interop { - case InteropModeExclusive: - // Any mapping is fine. - case InteropModeWritethrough: - // Shared writable mappings require a host FD, since otherwise we can't - // synchronously flush memory-mapped writes to the remote file. - if opts.Private || !opts.MaxPerms.Write { - break - } - fallthrough - case InteropModeShared: - // All mappings require a host FD to be coherent with other filesystem - // users. - if d.fs.opts.forcePageCache { - // Whether or not we have a host FD, we're not allowed to use it. - return syserror.ENODEV - } - d.handleMu.RLock() - haveFD := d.handle.fd >= 0 - d.handleMu.RUnlock() - if !haveFD { - return syserror.ENODEV - } - default: - panic(fmt.Sprintf("unknown InteropMode %v", d.fs.opts.interop)) - } - // After this point, d may be used as a memmap.Mappable. - d.pf.hostFileMapperInitOnce.Do(d.pf.hostFileMapper.Init) - return vfs.GenericConfigureMMap(&fd.vfsfd, d, opts) -} - -func (d *dentry) mayCachePages() bool { - if d.fs.opts.interop == InteropModeShared { - return false - } - if d.fs.opts.forcePageCache { - return true - } - d.handleMu.RLock() - haveFD := d.handle.fd >= 0 - d.handleMu.RUnlock() - return haveFD -} - -// AddMapping implements memmap.Mappable.AddMapping. -func (d *dentry) AddMapping(ctx context.Context, ms memmap.MappingSpace, ar usermem.AddrRange, offset uint64, writable bool) error { - d.mapsMu.Lock() - mapped := d.mappings.AddMapping(ms, ar, offset, writable) - // Do this unconditionally since whether we have a host FD can change - // across save/restore. - for _, r := range mapped { - d.pf.hostFileMapper.IncRefOn(r) - } - if d.mayCachePages() { - // d.Evict() will refuse to evict memory-mapped pages, so tell the - // MemoryFile to not bother trying. - mf := d.fs.mfp.MemoryFile() - for _, r := range mapped { - mf.MarkUnevictable(d, pgalloc.EvictableRange{r.Start, r.End}) - } - } - d.mapsMu.Unlock() - return nil -} - -// RemoveMapping implements memmap.Mappable.RemoveMapping. -func (d *dentry) RemoveMapping(ctx context.Context, ms memmap.MappingSpace, ar usermem.AddrRange, offset uint64, writable bool) { - d.mapsMu.Lock() - unmapped := d.mappings.RemoveMapping(ms, ar, offset, writable) - for _, r := range unmapped { - d.pf.hostFileMapper.DecRefOn(r) - } - if d.mayCachePages() { - // Pages that are no longer referenced by any application memory - // mappings are now considered unused; allow MemoryFile to evict them - // when necessary. - mf := d.fs.mfp.MemoryFile() - d.dataMu.Lock() - for _, r := range unmapped { - // Since these pages are no longer mapped, they are no longer - // concurrently dirtyable by a writable memory mapping. - d.dirty.AllowClean(r) - mf.MarkEvictable(d, pgalloc.EvictableRange{r.Start, r.End}) - } - d.dataMu.Unlock() - } - d.mapsMu.Unlock() -} - -// CopyMapping implements memmap.Mappable.CopyMapping. -func (d *dentry) CopyMapping(ctx context.Context, ms memmap.MappingSpace, srcAR, dstAR usermem.AddrRange, offset uint64, writable bool) error { - return d.AddMapping(ctx, ms, dstAR, offset, writable) -} - -// Translate implements memmap.Mappable.Translate. -func (d *dentry) Translate(ctx context.Context, required, optional memmap.MappableRange, at usermem.AccessType) ([]memmap.Translation, error) { - d.handleMu.RLock() - if d.handle.fd >= 0 && !d.fs.opts.forcePageCache { - d.handleMu.RUnlock() - mr := optional - if d.fs.opts.limitHostFDTranslation { - mr = maxFillRange(required, optional) - } - return []memmap.Translation{ - { - Source: mr, - File: &d.pf, - Offset: mr.Start, - Perms: usermem.AnyAccess, - }, - }, nil - } - - d.dataMu.Lock() - - // Constrain translations to d.size (rounded up) to prevent translation to - // pages that may be concurrently truncated. - pgend := pageRoundUp(d.size) - var beyondEOF bool - if required.End > pgend { - if required.Start >= pgend { - d.dataMu.Unlock() - d.handleMu.RUnlock() - return nil, &memmap.BusError{io.EOF} - } - beyondEOF = true - required.End = pgend - } - if optional.End > pgend { - optional.End = pgend - } - - mf := d.fs.mfp.MemoryFile() - cerr := d.cache.Fill(ctx, required, maxFillRange(required, optional), mf, usage.PageCache, d.handle.readToBlocksAt) - - var ts []memmap.Translation - var translatedEnd uint64 - for seg := d.cache.FindSegment(required.Start); seg.Ok() && seg.Start() < required.End; seg, _ = seg.NextNonEmpty() { - segMR := seg.Range().Intersect(optional) - // TODO(jamieliu): Make Translations writable even if writability is - // not required if already kept-dirty by another writable translation. - perms := usermem.AccessType{ - Read: true, - Execute: true, - } - if at.Write { - // From this point forward, this memory can be dirtied through the - // mapping at any time. - d.dirty.KeepDirty(segMR) - perms.Write = true - } - ts = append(ts, memmap.Translation{ - Source: segMR, - File: mf, - Offset: seg.FileRangeOf(segMR).Start, - Perms: perms, - }) - translatedEnd = segMR.End - } - - d.dataMu.Unlock() - d.handleMu.RUnlock() - - // Don't return the error returned by c.cache.Fill if it occurred outside - // of required. - if translatedEnd < required.End && cerr != nil { - return ts, &memmap.BusError{cerr} - } - if beyondEOF { - return ts, &memmap.BusError{io.EOF} - } - return ts, nil -} - -func maxFillRange(required, optional memmap.MappableRange) memmap.MappableRange { - const maxReadahead = 64 << 10 // 64 KB, chosen arbitrarily - if required.Length() >= maxReadahead { - return required - } - if optional.Length() <= maxReadahead { - return optional - } - optional.Start = required.Start - if optional.Length() <= maxReadahead { - return optional - } - optional.End = optional.Start + maxReadahead - return optional -} - -// InvalidateUnsavable implements memmap.Mappable.InvalidateUnsavable. -func (d *dentry) InvalidateUnsavable(ctx context.Context) error { - // Whether we have a host fd (and consequently what platform.File is - // mapped) can change across save/restore, so invalidate all translations - // unconditionally. - d.mapsMu.Lock() - defer d.mapsMu.Unlock() - d.mappings.InvalidateAll(memmap.InvalidateOpts{}) - - // Write the cache's contents back to the remote file so that if we have a - // host fd after restore, the remote file's contents are coherent. - mf := d.fs.mfp.MemoryFile() - d.dataMu.Lock() - defer d.dataMu.Unlock() - if err := fsutil.SyncDirtyAll(ctx, &d.cache, &d.dirty, d.size, mf, d.handle.writeFromBlocksAt); err != nil { - return err - } - - // Discard the cache so that it's not stored in saved state. This is safe - // because per InvalidateUnsavable invariants, no new translations can have - // been returned after we invalidated all existing translations above. - d.cache.DropAll(mf) - d.dirty.RemoveAll() - - return nil -} - -// Evict implements pgalloc.EvictableMemoryUser.Evict. -func (d *dentry) Evict(ctx context.Context, er pgalloc.EvictableRange) { - d.mapsMu.Lock() - defer d.mapsMu.Unlock() - d.dataMu.Lock() - defer d.dataMu.Unlock() - - mr := memmap.MappableRange{er.Start, er.End} - mf := d.fs.mfp.MemoryFile() - // Only allow pages that are no longer memory-mapped to be evicted. - for mgap := d.mappings.LowerBoundGap(mr.Start); mgap.Ok() && mgap.Start() < mr.End; mgap = mgap.NextGap() { - mgapMR := mgap.Range().Intersect(mr) - if mgapMR.Length() == 0 { - continue - } - if err := fsutil.SyncDirty(ctx, mgapMR, &d.cache, &d.dirty, d.size, mf, d.handle.writeFromBlocksAt); err != nil { - log.Warningf("Failed to writeback cached data %v: %v", mgapMR, err) - } - d.cache.Drop(mgapMR, mf) - d.dirty.KeepClean(mgapMR) - } -} - -// dentryPlatformFile implements platform.File. It exists solely because dentry -// cannot implement both vfs.DentryImpl.IncRef and platform.File.IncRef. -// -// dentryPlatformFile is only used when a host FD representing the remote file -// is available (i.e. dentry.handle.fd >= 0), and that FD is used for -// application memory mappings (i.e. !filesystem.opts.forcePageCache). -type dentryPlatformFile struct { - *dentry - - // fdRefs counts references on platform.File offsets. fdRefs is protected - // by dentry.dataMu. - fdRefs fsutil.FrameRefSet - - // If this dentry represents a regular file, and handle.fd >= 0, - // hostFileMapper caches mappings of handle.fd. - hostFileMapper fsutil.HostFileMapper - - // hostFileMapperInitOnce is used to lazily initialize hostFileMapper. - hostFileMapperInitOnce sync.Once -} - -// IncRef implements platform.File.IncRef. -func (d *dentryPlatformFile) IncRef(fr platform.FileRange) { - d.dataMu.Lock() - seg, gap := d.fdRefs.Find(fr.Start) - for { - switch { - case seg.Ok() && seg.Start() < fr.End: - seg = d.fdRefs.Isolate(seg, fr) - seg.SetValue(seg.Value() + 1) - seg, gap = seg.NextNonEmpty() - case gap.Ok() && gap.Start() < fr.End: - newRange := gap.Range().Intersect(fr) - usage.MemoryAccounting.Inc(newRange.Length(), usage.Mapped) - seg, gap = d.fdRefs.InsertWithoutMerging(gap, newRange, 1).NextNonEmpty() - default: - d.fdRefs.MergeAdjacent(fr) - d.dataMu.Unlock() - return - } - } -} - -// DecRef implements platform.File.DecRef. -func (d *dentryPlatformFile) DecRef(fr platform.FileRange) { - d.dataMu.Lock() - seg := d.fdRefs.FindSegment(fr.Start) - - for seg.Ok() && seg.Start() < fr.End { - seg = d.fdRefs.Isolate(seg, fr) - if old := seg.Value(); old == 1 { - usage.MemoryAccounting.Dec(seg.Range().Length(), usage.Mapped) - seg = d.fdRefs.Remove(seg).NextSegment() - } else { - seg.SetValue(old - 1) - seg = seg.NextSegment() - } - } - d.fdRefs.MergeAdjacent(fr) - d.dataMu.Unlock() - -} - -// MapInternal implements platform.File.MapInternal. -func (d *dentryPlatformFile) MapInternal(fr platform.FileRange, at usermem.AccessType) (safemem.BlockSeq, error) { - d.handleMu.RLock() - bs, err := d.hostFileMapper.MapInternal(fr, int(d.handle.fd), at.Write) - d.handleMu.RUnlock() - return bs, err -} - -// FD implements platform.File.FD. -func (d *dentryPlatformFile) FD() int { - d.handleMu.RLock() - fd := d.handle.fd - d.handleMu.RUnlock() - return int(fd) -} |