// Copyright 2018 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 fs import ( "bytes" "fmt" "sync/atomic" "gvisor.dev/gvisor/pkg/context" "gvisor.dev/gvisor/pkg/refs" ) // DirentOperations provide file systems greater control over how long a Dirent // stays pinned in core. Implementations must not take Dirent.mu. type DirentOperations interface { // Revalidate is called during lookup each time we encounter a Dirent // in the cache. Implementations may update stale properties of the // child Inode. If Revalidate returns true, then the entire Inode will // be reloaded. // // Revalidate will never be called on a Inode that is mounted. Revalidate(ctx context.Context, name string, parent, child *Inode) bool // Keep returns true if the Dirent should be kept in memory for as long // as possible beyond any active references. Keep(dirent *Dirent) bool // CacheReaddir returns true if directory entries returned by // FileOperations.Readdir may be cached for future use. // // Postconditions: This method must always return the same value. CacheReaddir() bool } // MountSourceOperations contains filesystem specific operations. type MountSourceOperations interface { // DirentOperations provide optional extra management of Dirents. DirentOperations // Destroy destroys the MountSource. Destroy(ctx context.Context) // Below are MountSourceOperations that do not conform to Linux. // ResetInodeMappings clears all mappings of Inodes before SaveInodeMapping // is called. ResetInodeMappings() // SaveInodeMappings is called during saving to store, for each reachable // Inode in the mounted filesystem, a mapping of Inode.StableAttr.InodeID // to the Inode's path relative to its mount point. If an Inode is // reachable at more than one path due to hard links, it is unspecified // which path is mapped. Filesystems that do not use this information to // restore inodes can make SaveInodeMappings a no-op. SaveInodeMapping(inode *Inode, path string) } // InodeMappings defines a fmt.Stringer MountSource Inode mappings. type InodeMappings map[uint64]string // String implements fmt.Stringer.String. func (i InodeMappings) String() string { var mappingsBuf bytes.Buffer mappingsBuf.WriteString("\n") for ino, name := range i { mappingsBuf.WriteString(fmt.Sprintf("\t%q\t\tinode number %d\n", name, ino)) } return mappingsBuf.String() } // MountSource represents a source of file objects. // // MountSource corresponds to struct super_block in Linux. // // A mount source may represent a physical device (or a partition of a physical // device) or a virtual source of files such as procfs for a specific PID // namespace. There should be only one mount source per logical device. E.g. // there should be only procfs mount source for a given PID namespace. // // A mount source represents files as inodes. Every inode belongs to exactly // one mount source. Each file object may only be represented using one inode // object in a sentry instance. // // TODO(b/63601033): Move Flags out of MountSource to Mount. // // +stateify savable type MountSource struct { refs.AtomicRefCount // MountSourceOperations defines filesystem specific behavior. MountSourceOperations // FilesystemType is the type of the filesystem backing this mount. FilesystemType string // Flags are the flags that this filesystem was mounted with. Flags MountSourceFlags // fscache keeps Dirents pinned beyond application references to them. // It must be flushed before kernel.SaveTo. fscache *DirentCache // direntRefs is the sum of references on all Dirents in this MountSource. // // direntRefs is increased when a Dirent in MountSource is IncRef'd, and // decreased when a Dirent in MountSource is DecRef'd. // // To cleanly unmount a MountSource, one must check that no direntRefs are // held anymore. To check, one must hold root.parent.dirMu of the // MountSource's root Dirent before reading direntRefs to prevent further // walks to Dirents in this MountSource. // // direntRefs must be atomically changed. direntRefs uint64 } // DefaultDirentCacheSize is the number of Dirents that the VFS can hold an // extra reference on. const DefaultDirentCacheSize uint64 = 1000 // NewMountSource returns a new MountSource. Filesystem may be nil if there is no // filesystem backing the mount. func NewMountSource(ctx context.Context, mops MountSourceOperations, filesystem Filesystem, flags MountSourceFlags) *MountSource { fsType := "none" if filesystem != nil { fsType = filesystem.Name() } msrc := MountSource{ MountSourceOperations: mops, Flags: flags, FilesystemType: fsType, fscache: NewDirentCache(DefaultDirentCacheSize), } msrc.EnableLeakCheck("fs.MountSource") return &msrc } // DirentRefs returns the current mount direntRefs. func (msrc *MountSource) DirentRefs() uint64 { return atomic.LoadUint64(&msrc.direntRefs) } // IncDirentRefs increases direntRefs. func (msrc *MountSource) IncDirentRefs() { atomic.AddUint64(&msrc.direntRefs, 1) } // DecDirentRefs decrements direntRefs. func (msrc *MountSource) DecDirentRefs() { if atomic.AddUint64(&msrc.direntRefs, ^uint64(0)) == ^uint64(0) { panic("Decremented zero mount reference direntRefs") } } func (msrc *MountSource) destroy(ctx context.Context) { if c := msrc.DirentRefs(); c != 0 { panic(fmt.Sprintf("MountSource with non-zero direntRefs is being destroyed: %d", c)) } msrc.MountSourceOperations.Destroy(ctx) } // DecRef drops a reference on the MountSource. func (msrc *MountSource) DecRef(ctx context.Context) { msrc.DecRefWithDestructor(ctx, msrc.destroy) } // FlushDirentRefs drops all references held by the MountSource on Dirents. func (msrc *MountSource) FlushDirentRefs() { msrc.fscache.Invalidate() } // SetDirentCacheMaxSize sets the max size to the dirent cache associated with // this mount source. func (msrc *MountSource) SetDirentCacheMaxSize(max uint64) { msrc.fscache.setMaxSize(max) } // SetDirentCacheLimiter sets the limiter objcet to the dirent cache associated // with this mount source. func (msrc *MountSource) SetDirentCacheLimiter(l *DirentCacheLimiter) { msrc.fscache.limit = l } // NewCachingMountSource returns a generic mount that will cache dirents // aggressively. func NewCachingMountSource(ctx context.Context, filesystem Filesystem, flags MountSourceFlags) *MountSource { return NewMountSource(ctx, &SimpleMountSourceOperations{ keep: true, revalidate: false, cacheReaddir: true, }, filesystem, flags) } // NewNonCachingMountSource returns a generic mount that will never cache dirents. func NewNonCachingMountSource(ctx context.Context, filesystem Filesystem, flags MountSourceFlags) *MountSource { return NewMountSource(ctx, &SimpleMountSourceOperations{ keep: false, revalidate: false, cacheReaddir: false, }, filesystem, flags) } // NewRevalidatingMountSource returns a generic mount that will cache dirents, // but will revalidate them on each lookup and always perform uncached readdir. func NewRevalidatingMountSource(ctx context.Context, filesystem Filesystem, flags MountSourceFlags) *MountSource { return NewMountSource(ctx, &SimpleMountSourceOperations{ keep: true, revalidate: true, cacheReaddir: false, }, filesystem, flags) } // NewPseudoMountSource returns a "pseudo" mount source that is not backed by // an actual filesystem. It is always non-caching. func NewPseudoMountSource(ctx context.Context) *MountSource { return NewMountSource(ctx, &SimpleMountSourceOperations{ keep: false, revalidate: false, cacheReaddir: false, }, nil, MountSourceFlags{}) } // SimpleMountSourceOperations implements MountSourceOperations. // // +stateify savable type SimpleMountSourceOperations struct { keep bool revalidate bool cacheReaddir bool } // Revalidate implements MountSourceOperations.Revalidate. func (smo *SimpleMountSourceOperations) Revalidate(context.Context, string, *Inode, *Inode) bool { return smo.revalidate } // Keep implements MountSourceOperations.Keep. func (smo *SimpleMountSourceOperations) Keep(*Dirent) bool { return smo.keep } // CacheReaddir implements MountSourceOperations.CacheReaddir. func (smo *SimpleMountSourceOperations) CacheReaddir() bool { return smo.cacheReaddir } // ResetInodeMappings implements MountSourceOperations.ResetInodeMappings. func (*SimpleMountSourceOperations) ResetInodeMappings() {} // SaveInodeMapping implements MountSourceOperations.SaveInodeMapping. func (*SimpleMountSourceOperations) SaveInodeMapping(*Inode, string) {} // Destroy implements MountSourceOperations.Destroy. func (*SimpleMountSourceOperations) Destroy(context.Context) {} // Info defines attributes of a filesystem. type Info struct { // Type is the filesystem type magic value. Type uint64 // TotalBlocks is the total data blocks in the filesystem. TotalBlocks uint64 // FreeBlocks is the number of free blocks available. FreeBlocks uint64 // TotalFiles is the total file nodes in the filesystem. TotalFiles uint64 // FreeFiles is the number of free file nodes. FreeFiles uint64 }