Allow for creation of shared mounts.

This is the first in a series of changes that will enabled shared mount
subtrees. This will emulate the Linux kernel implementation described here:
https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt

Mounts can now a be part of a mount groups. These groups are
identified by ids assigned by the VFS. Group IDs can be reused after all
mounts that are a member of that group are destroyed.

Newly created mounts inherit the propagation type of their parent mount. A
mount created on a shared mount will also be a shared mount, though it will
be part of a different group. A shared mount 'A' bound to a shared mount 'B'
will replicate that bind to all of 'B's peers. These new mounts will all be
part of a new shared peer group.

Unmounting a mount 'A' that is a direct child of a shared mount 'B' mounted
at dentry 'b' will propagate that event to all its peers. So peers B1, B2,
B3, etc will all unmount the mounts (A1, A2...) located at 'b'. However, if
any of peers of A have children, they are skipped. If the original mount A
has children, the mount is failed entirely.

The initial root mount has propagation type MS_PRIVATE.

This change only implements a basic version of mount groups. Notably it does
not implement MS_SLAVE, MS_UNBINDABLE, or MS_REC.

PiperOrigin-RevId: 483792757
This commit is contained in:
Lucas Manning
2022-10-25 15:35:10 -07:00
committed by gVisor bot
parent 38d2b048fd
commit 0b983ff832
8 changed files with 1009 additions and 82 deletions
+16
View File
@@ -50,6 +50,22 @@ func (b *Bitmap) IsEmpty() bool {
return b.numOnes == 0
}
// Size returns the total number of bits in the bitmap.
func (b *Bitmap) Size() int {
return len(b.bitBlock) * 64
}
// Grow grows the bitmap by at least toGrow bits.
func (b *Bitmap) Grow(toGrow uint32) error {
newbitBlockSize := uint32(len(b.bitBlock)) + ((toGrow + 63) / 64)
if newbitBlockSize > MaxBitEntryLimit/8 {
return fmt.Errorf("requested bitmap size %d too large", newbitBlockSize*64)
}
bits := make([]uint64, (toGrow+63)/64)
b.bitBlock = append(b.bitBlock, bits...)
return nil
}
// Minimum return the smallest value in the Bitmap.
func (b *Bitmap) Minimum() uint32 {
for i := 0; i < len(b.bitBlock); i++ {
+16
View File
@@ -340,3 +340,19 @@ func TestFirstOne(t *testing.T) {
}
}
}
func TestGrow(t *testing.T) {
bitmap := New(uint32(64))
bitmap.FlipRange(0, 64)
bitmap.Grow(64)
want := make([]uint32, 64)
for i := 0; i < 128; i++ {
if i < 64 {
want[i] = uint32(i)
}
}
if !reflect.DeepEqual(bitmap.ToSlice(), want) {
t.Errorf("Grow() got: %v, want: %v", bitmap.ToSlice(), want)
}
}
+64 -58
View File
@@ -16,6 +16,7 @@ package vfs2
import (
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/bits"
"gvisor.dev/gvisor/pkg/errors/linuxerr"
"gvisor.dev/gvisor/pkg/fspath"
"gvisor.dev/gvisor/pkg/hostarch"
@@ -32,39 +33,6 @@ func Mount(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall
flags := args[3].Uint64()
dataAddr := args[4].Pointer()
// For null-terminated strings related to mount(2), Linux copies in at most
// a page worth of data. See fs/namespace.c:copy_mount_string().
fsType, err := t.CopyInString(typeAddr, hostarch.PageSize)
if err != nil {
return 0, nil, err
}
source, err := t.CopyInString(sourceAddr, hostarch.PageSize)
if err != nil {
return 0, nil, err
}
targetPath, err := copyInPath(t, targetAddr)
if err != nil {
return 0, nil, err
}
data := ""
if dataAddr != 0 {
// In Linux, a full page is always copied in regardless of null
// character placement, and the address is passed to each file system.
// Most file systems always treat this data as a string, though, and so
// do all of the ones we implement.
data, err = t.CopyInString(dataAddr, hostarch.PageSize)
if err != nil {
return 0, nil, err
}
}
// Ignore magic value that was required before Linux 2.4.
if flags&linux.MS_MGC_MSK == linux.MS_MGC_VAL {
flags = flags &^ linux.MS_MGC_MSK
}
// Must have CAP_SYS_ADMIN in the current mount namespace's associated user
// namespace.
creds := t.Credentials()
@@ -72,38 +40,29 @@ func Mount(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall
return 0, nil, linuxerr.EPERM
}
const unsupportedOps = linux.MS_REMOUNT | linux.MS_SHARED | linux.MS_PRIVATE |
linux.MS_SLAVE | linux.MS_UNBINDABLE | linux.MS_MOVE
// Ignore magic value that was required before Linux 2.4.
if flags&linux.MS_MGC_MSK == linux.MS_MGC_VAL {
flags = flags &^ linux.MS_MGC_MSK
}
// Silently allow MS_NOSUID, since we don't implement set-id bits
// anyway.
const unsupportedFlags = linux.MS_NODIRATIME | linux.MS_STRICTATIME
// Silently allow MS_NOSUID, since we don't implement set-id bits anyway.
const unsupported = linux.MS_REMOUNT | linux.MS_SLAVE |
linux.MS_UNBINDABLE | linux.MS_MOVE | linux.MS_REC | linux.MS_NODIRATIME |
linux.MS_STRICTATIME
// Linux just allows passing any flags to mount(2) - it won't fail when
// unknown or unsupported flags are passed. Since we don't implement
// everything, we fail explicitly on flags that are unimplemented.
if flags&(unsupportedOps|unsupportedFlags) != 0 {
if flags&(unsupported) != 0 {
return 0, nil, linuxerr.EINVAL
}
var opts vfs.MountOptions
if flags&linux.MS_NOATIME == linux.MS_NOATIME {
opts.Flags.NoATime = true
// For null-terminated strings related to mount(2), Linux copies in at most
// a page worth of data. See fs/namespace.c:copy_mount_string().
targetPath, err := copyInPath(t, targetAddr)
if err != nil {
return 0, nil, err
}
if flags&linux.MS_NOEXEC == linux.MS_NOEXEC {
opts.Flags.NoExec = true
}
if flags&linux.MS_NODEV == linux.MS_NODEV {
opts.Flags.NoDev = true
}
if flags&linux.MS_NOSUID == linux.MS_NOSUID {
opts.Flags.NoSUID = true
}
if flags&linux.MS_RDONLY == linux.MS_RDONLY {
opts.ReadOnly = true
}
opts.GetFilesystemOptions.Data = data
target, err := getTaskPathOperation(t, linux.AT_FDCWD, targetPath, disallowEmptyPath, nofollowFinalSymlink)
if err != nil {
return 0, nil, err
@@ -123,9 +82,56 @@ func Mount(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscall
}
defer sourceTpop.Release(t)
_, err = t.Kernel().VFS().BindAt(t, creds, &sourceTpop.pop, &target.pop)
} else {
_, err = t.Kernel().VFS().MountAt(t, creds, source, &target.pop, fsType, &opts)
return 0, nil, err
}
const propagationFlags = linux.MS_SHARED | linux.MS_PRIVATE | linux.MS_SLAVE | linux.MS_UNBINDABLE
if propFlag := flags & propagationFlags; propFlag != 0 {
// Check if flags is a power of 2. If not then more than one flag is set.
if !bits.IsPowerOfTwo64(propFlag) {
return 0, nil, linuxerr.EINVAL
}
propType := vfs.PropagationTypeFromLinux(propFlag)
return 0, nil, t.Kernel().VFS().SetMountPropagation(t, creds, &target.pop, propType)
}
// Only copy in source, fstype, and data if we are doing a normal mount.
source, err := t.CopyInString(sourceAddr, hostarch.PageSize)
if err != nil {
return 0, nil, err
}
fsType, err := t.CopyInString(typeAddr, hostarch.PageSize)
if err != nil {
return 0, nil, err
}
data := ""
if dataAddr != 0 {
// In Linux, a full page is always copied in regardless of null
// character placement, and the address is passed to each file system.
// Most file systems always treat this data as a string, though, and so
// do all of the ones we implement.
data, err = t.CopyInString(dataAddr, hostarch.PageSize)
if err != nil {
return 0, nil, err
}
}
var opts vfs.MountOptions
if flags&linux.MS_NOATIME == linux.MS_NOATIME {
opts.Flags.NoATime = true
}
if flags&linux.MS_NOEXEC == linux.MS_NOEXEC {
opts.Flags.NoExec = true
}
if flags&linux.MS_NODEV == linux.MS_NODEV {
opts.Flags.NoDev = true
}
if flags&linux.MS_NOSUID == linux.MS_NOSUID {
opts.Flags.NoSUID = true
}
if flags&linux.MS_RDONLY == linux.MS_RDONLY {
opts.ReadOnly = true
}
opts.GetFilesystemOptions.Data = data
_, err = t.Kernel().VFS().MountAt(t, creds, source, &target.pop, fsType, &opts)
return 0, nil, err
}
+15
View File
@@ -51,6 +51,19 @@ go_template_instance(
},
)
go_template_instance(
name = "shared_list",
out = "shared_list.go",
package = "vfs",
prefix = "shared",
template = "//pkg/ilist:generic_list",
types = {
"Element": "*Mount",
"Linker": "*sharedEntry",
"ElementMapper": "sharedMapper",
},
)
go_template_instance(
name = "event_list",
out = "event_list.go",
@@ -129,6 +142,7 @@ go_library(
"permissions.go",
"resolving_path.go",
"save_restore.go",
"shared_list.go",
"vfs.go",
"virtual_filesystem_mutex.go",
],
@@ -136,6 +150,7 @@ go_library(
deps = [
"//pkg/abi/linux",
"//pkg/atomicbitops",
"//pkg/bitmap",
"//pkg/context",
"//pkg/errors/linuxerr",
"//pkg/fd",
+3 -1
View File
@@ -336,7 +336,9 @@ func (vfs *VirtualFilesystem) forgetDeadMountpoint(ctx context.Context, d *Dentr
vfs.mountMu.Lock()
vfs.mounts.seq.BeginWrite()
for mnt := range vfs.mountpoints[d] {
vdsToDecRef, mountsToDecRef = vfs.umountRecursiveLocked(mnt, &umountRecursiveOptions{}, vdsToDecRef, mountsToDecRef)
vds, mounts := vfs.umountAtRecursiveLocked(ctx, VirtualDentry{mnt, d}, &umountRecursiveOptions{})
vdsToDecRef = append(vdsToDecRef, vds...)
mountsToDecRef = append(mountsToDecRef, mounts...)
}
vfs.mounts.seq.EndWrite()
vfs.mountMu.Unlock()
+349 -20
View File
@@ -29,6 +29,42 @@ import (
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
)
// PropagationType is a propagation flavor as described in
// https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt. Child
// and Unbindable are currently unimplemented.
// TODO(b/249777195): Support MS_SLAVE and MS_UNBINDABLE propagation types.
type PropagationType int
const (
// Unknown represents an invalid/unknown propagation type.
Unknown PropagationType = iota
// Shared represents the shared propagation type.
Shared
// Private represents the private propagation type.
Private
// Child represents the child propagation type (MS_SLAVE).
Child
// Unbindable represents the unbindable propagation type.
Unbindable
)
// PropagationTypeFromLinux returns the PropagationType corresponding to a
// linux mount flag, aka MS_SHARED.
func PropagationTypeFromLinux(propFlag uint64) PropagationType {
switch propFlag {
case linux.MS_SHARED:
return Shared
case linux.MS_PRIVATE:
return Private
case linux.MS_SLAVE:
return Child
case linux.MS_UNBINDABLE:
return Unbindable
default:
return Unknown
}
}
// A Mount is a replacement of a Dentry (Mount.key.point) from one Filesystem
// (Mount.key.parent.fs) with a Dentry (Mount.root) from another Filesystem
// (Mount.fs), which applies to path resolution in the context of a particular
@@ -82,6 +118,23 @@ type Mount struct {
// Mount. children is protected by VirtualFilesystem.mountMu.
children map[*Mount]struct{}
// propagationType is propagation type of this mount. It can be shared or
// private.
propType PropagationType
// sharedList is a list of mounts in the shared peer group. It is nil if
// propType is not Shared. All mounts in a shared peer group hold the same
// sharedList. The mounts in sharedList do not need an extra reference taken
// because it would be redundant with the taken for being attached to a
// parent mount. If a mount is in a shared list if and only if it is attached
// and has the shared propagation type.
sharedList *sharedList
sharedEntry sharedEntry
// groupID is the ID for this mount's shared peer group. If the mount is not
// in a peer group, this is 0.
groupID uint32
// umounted is true if VFS.umountRecursiveLocked() has been called on this
// Mount. VirtualFilesystem does not hold a reference on Mounts for which
// umounted is true. umounted is protected by VirtualFilesystem.mountMu.
@@ -94,15 +147,20 @@ type Mount struct {
writers atomicbitops.Int64
}
type sharedMapper struct{}
func (sharedMapper) linkerFor(mnt *Mount) *sharedEntry { return &mnt.sharedEntry }
func newMount(vfs *VirtualFilesystem, fs *Filesystem, root *Dentry, mntns *MountNamespace, opts *MountOptions) *Mount {
mnt := &Mount{
ID: vfs.lastMountID.Add(1),
Flags: opts.Flags,
vfs: vfs,
fs: fs,
root: root,
ns: mntns,
refs: atomicbitops.FromInt64(1),
ID: vfs.lastMountID.Add(1),
Flags: opts.Flags,
vfs: vfs,
fs: fs,
root: root,
ns: mntns,
propType: Private,
refs: atomicbitops.FromInt64(1),
}
if opts.ReadOnly {
mnt.setReadOnlyLocked(true)
@@ -121,6 +179,60 @@ func (mnt *Mount) Options() MountOptions {
}
}
// addPeer adds oth to mnt's peer group. Both will have the same groupID
// and sharedList. vfs.mountMu must be locked.
//
// +checklocks:vfs.mountMu
func (vfs *VirtualFilesystem) addPeer(mnt *Mount, oth *Mount) {
mnt.sharedList.PushBack(oth)
oth.sharedList = mnt.sharedList
oth.propType = mnt.propType
oth.groupID = mnt.groupID
}
// mergePeerGroup merges oth and all its peers into mnt's peer group. Oth
// must have propagation type shared and vfs.mountMu must be locked.
//
// +checklocks:vfs.mountMu
func (vfs *VirtualFilesystem) mergePeerGroup(mnt *Mount, oth *Mount) {
peer := oth.sharedList.Front()
for peer != nil {
next := peer.sharedEntry.Next()
vfs.setPropagation(peer, Private)
vfs.addPeer(mnt, peer)
peer = next
}
}
// setPropagation sets the propagation on mnt for a propagation type.
//
// +checklocks:vfs.mountMu
func (vfs *VirtualFilesystem) setPropagation(mnt *Mount, ptype PropagationType) error {
switch ptype {
case Shared:
id, err := vfs.allocateGroupID()
if err != nil {
return err
}
mnt.groupID = id
mnt.sharedList = &sharedList{}
mnt.sharedList.PushBack(mnt)
case Private:
if mnt.propType == Shared {
mnt.sharedList.Remove(mnt)
if mnt.sharedList.Empty() {
vfs.freeGroupID(mnt.groupID)
}
mnt.sharedList = nil
mnt.groupID = 0
}
default:
panic(fmt.Sprintf("unsupported propagation type: %v", ptype))
}
mnt.propType = ptype
return nil
}
// A MountNamespace is a collection of Mounts.//
// MountNamespaces are reference-counted. Unless otherwise specified, all
// MountNamespace methods require that a reference is held.
@@ -224,12 +336,95 @@ func (vfs *VirtualFilesystem) ConnectMountAt(ctx context.Context, creds *auth.Cr
return err
}
vfs.mountMu.Lock()
defer vfs.mountMu.Unlock()
tree := vfs.preparePropagationTree(mnt, vd)
if err := vfs.connectMountAt(ctx, mnt, vd); err != nil {
vfs.abortPropagationTree(ctx, tree)
return err
}
vfs.commitPropagationTree(ctx, tree)
return nil
}
// preparePropagationTree returns a mapping of propagated mounts to their future
// mountpoints. The new mounts are clones of mnt and are added to mnt's peer
// group if vd.mount and mnt are shared. All the cloned mounts and new
// mountpoints in the tree have an extra reference taken.
//
// +checklocks:vfs.mountMu
// +checklocksalias:mnt.vfs.mountMu=vfs.mountMu
func (vfs *VirtualFilesystem) preparePropagationTree(mnt *Mount, vd VirtualDentry) map[*Mount]VirtualDentry {
tree := map[*Mount]VirtualDentry{}
if vd.mount.propType == Private {
return tree
}
if mnt.propType == Private {
vfs.setPropagation(mnt, Shared)
}
var newPeerGroup []*Mount
for peer := vd.mount.sharedList.Front(); peer != nil; peer = peer.sharedEntry.Next() {
if peer == vd.mount {
continue
}
peerVd := VirtualDentry{
mount: peer,
dentry: vd.dentry,
}
peerVd.IncRef()
clone := vfs.cloneMount(mnt, mnt.root)
tree[clone] = peerVd
newPeerGroup = append(newPeerGroup, clone)
}
for _, newPeer := range newPeerGroup {
vfs.addPeer(mnt, newPeer)
}
return tree
}
// commitPropagationTree attaches to mounts in tree to the mountpoints they
// are mapped to. If there is an error attaching a mount, the method panics.
//
// +checklocks:vfs.mountMu
func (vfs *VirtualFilesystem) commitPropagationTree(ctx context.Context, tree map[*Mount]VirtualDentry) {
// The peer mounts should have no way of being dead if we've reached this
// point so its safe to connect without checks.
vfs.mounts.seq.BeginWrite()
for mnt, vd := range tree {
vd.dentry.mu.Lock()
mntns := vd.mount.ns
vfs.connectLocked(mnt, vd, mntns)
vd.dentry.mu.Unlock()
mnt.DecRef(ctx)
}
vfs.mounts.seq.EndWrite()
}
// abortPropagationTree releases any references held by the mounts and
// mountpoints in the tree and removes the mounts from their peer groups.
//
// +checklocks:vfs.mountMu
func (vfs *VirtualFilesystem) abortPropagationTree(ctx context.Context, tree map[*Mount]VirtualDentry) {
for mnt, vd := range tree {
vd.DecRef(ctx)
vfs.setPropagation(mnt, Private)
mnt.DecRef(ctx)
}
}
// connectMountAtLocked attaches mnt at vd. It returns the new mountpoint of mnt
// if no error occurred.
//
// Preconditions:
// - mnt must be disconnected.
// - vfs.mountMu must be locked.
//
// +checklocks:vfs.mountMu
func (vfs *VirtualFilesystem) connectMountAt(ctx context.Context, mnt *Mount, vd VirtualDentry) error {
vdDentry := vd.dentry
vdDentry.mu.Lock()
for {
if vd.mount.umounted || vdDentry.dead {
vdDentry.mu.Unlock()
vfs.mountMu.Unlock()
vd.DecRef(ctx)
return linuxerr.ENOENT
}
@@ -267,28 +462,92 @@ func (vfs *VirtualFilesystem) ConnectMountAt(ctx context.Context, creds *auth.Cr
vfs.connectLocked(mnt, vd, mntns)
vfs.mounts.seq.EndWrite()
vdDentry.mu.Unlock()
vfs.mountMu.Unlock()
return nil
}
// SetMountPropagation changes the propagation type of the mount pointed to by
// pop.
func (vfs *VirtualFilesystem) SetMountPropagation(ctx context.Context, creds *auth.Credentials, pop *PathOperation, propType PropagationType) error {
vd, err := vfs.GetDentryAt(ctx, creds, pop, &GetDentryOptions{})
if err != nil {
return err
}
// See the similar defer in UmountAt for why this is in a closure.
defer func() {
vd.DecRef(ctx)
}()
if vd.dentry.isMounted() {
if realmnt := vfs.getMountAt(ctx, vd.mount, vd.dentry); realmnt != nil {
vd.mount.DecRef(ctx)
vd.mount = realmnt
}
} else if vd.dentry != vd.mount.root {
return linuxerr.EINVAL
}
vfs.mountMu.Lock()
defer vfs.mountMu.Unlock()
mnt := vd.mount
if propType != mnt.propType {
switch propType {
case Shared, Private:
vfs.setPropagation(mnt, propType)
default:
panic(fmt.Sprintf("unsupported propagation type: %v", propType))
}
}
mnt.propType = propType
return nil
}
// cloneMount returns a new mount with mnt.fs as the filesystem and root as the
// root. The returned mount has an extra reference.
//
// +checklocks:vfs.mountMu
// +checklocksalias:mnt.vfs.mountMu=vfs.mountMu
func (vfs *VirtualFilesystem) cloneMount(mnt *Mount, root *Dentry) *Mount {
opts := MountOptions{
Flags: mnt.Flags,
ReadOnly: mnt.ReadOnly(),
}
return vfs.NewDisconnectedMount(mnt.fs, root, &opts)
}
// BindAt creates a clone of the source path's parent mount and mounts it at
// the target path. The new mount's root dentry is one pointed to by the source
// path.
//
// TODO(b/249121230): Support recursive bind mounting.
func (vfs *VirtualFilesystem) BindAt(ctx context.Context, creds *auth.Credentials, source, target *PathOperation) (*Mount, error) {
vd, err := vfs.GetDentryAt(ctx, creds, source, &GetDentryOptions{})
sourceVd, err := vfs.GetDentryAt(ctx, creds, source, &GetDentryOptions{})
if err != nil {
return nil, err
}
defer vd.DecRef(ctx)
opts := vd.mount.Options()
mnt := vfs.NewDisconnectedMount(vd.mount.fs, vd.dentry, &opts)
defer mnt.DecRef(ctx)
if err := vfs.ConnectMountAt(ctx, creds, mnt, target); err != nil {
defer sourceVd.DecRef(ctx)
targetVd, err := vfs.GetDentryAt(ctx, creds, target, &GetDentryOptions{})
if err != nil {
return nil, err
}
return mnt, nil
vfs.mountMu.Lock()
defer vfs.mountMu.Unlock()
clone := vfs.cloneMount(sourceVd.mount, sourceVd.dentry)
defer clone.DecRef(ctx)
tree := vfs.preparePropagationTree(clone, targetVd)
if sourceVd.mount.propType == Shared {
if clone.propType == Private {
vfs.addPeer(sourceVd.mount, clone)
} else {
vfs.mergePeerGroup(sourceVd.mount, clone)
}
}
if err := vfs.connectMountAt(ctx, clone, targetVd); err != nil {
vfs.setPropagation(clone, Private)
vfs.abortPropagationTree(ctx, tree)
return nil, err
}
vfs.commitPropagationTree(ctx, tree)
return clone, nil
}
// MountAt creates and mounts a Filesystem configured by the given arguments.
@@ -384,10 +643,10 @@ func (vfs *VirtualFilesystem) UmountAt(ctx context.Context, creds *auth.Credenti
return linuxerr.EBUSY
}
}
vdsToDecRef, mountsToDecRef := vfs.umountRecursiveLocked(vd.mount, &umountRecursiveOptions{
vdsToDecRef, mountsToDecRef := vfs.umountAtRecursiveLocked(ctx, vd, &umountRecursiveOptions{
eager: opts.Flags&linux.MNT_DETACH == 0,
disconnectHierarchy: true,
}, nil, nil)
})
vfs.mounts.seq.EndWrite()
vfs.mountMu.Unlock()
for _, vd := range vdsToDecRef {
@@ -399,6 +658,45 @@ func (vfs *VirtualFilesystem) UmountAt(ctx context.Context, creds *auth.Credenti
return nil
}
// umountAtRecursiveLocked marks the mount located at vd and its decendendents
// as umounted and does the same for mounts at the same dentry in peers of
// vd.mount's parent.
//
// Preconditions:
// - vd is a mountpoint.
// - vfs.mountMu must be locked.
// - vfs.mounts.seq must be in a writer critical section.
//
// +checklocks:vfs.mountMu
func (vfs *VirtualFilesystem) umountAtRecursiveLocked(ctx context.Context, vd VirtualDentry, opts *umountRecursiveOptions) ([]VirtualDentry, []*Mount) {
parent, mountpoint := vd.mount.parent(), vd.mount.point()
vdsToDecRef, mountsToDecRef := vfs.umountRecursiveLocked(vd.mount, opts, nil, nil)
if parent != nil && parent.propType == Shared {
for peer := parent.sharedList.Front(); peer != nil; peer = peer.sharedEntry.Next() {
if peer == parent {
continue
}
vfs.mounts.seq.EndWrite()
mnt := vfs.mounts.Lookup(peer, mountpoint)
vfs.mounts.seq.BeginWrite()
if mnt == nil {
continue
}
// From https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt:
// If any peer has some child mounts, then that mount is not unmounted,
// but all other mounts are unmounted.
if len(mnt.children) != 0 {
continue
}
vdsToDecRef, mountsToDecRef = vfs.umountRecursiveLocked(mnt, opts, vdsToDecRef, mountsToDecRef)
}
}
return vdsToDecRef, mountsToDecRef
}
// +stateify savable
type umountRecursiveOptions struct {
// If eager is true, ensure that future calls to Mount.tryIncMountedRef()
@@ -429,6 +727,8 @@ type umountRecursiveOptions struct {
// Preconditions:
// - vfs.mountMu must be locked.
// - vfs.mounts.seq must be in a writer critical section.
//
// +checklocks:vfs.mountMu
func (vfs *VirtualFilesystem) umountRecursiveLocked(mnt *Mount, opts *umountRecursiveOptions, vdsToDecRef []VirtualDentry, mountsToDecRef []*Mount) ([]VirtualDentry, []*Mount) {
if !mnt.umounted {
mnt.umounted = true
@@ -436,6 +736,9 @@ func (vfs *VirtualFilesystem) umountRecursiveLocked(mnt *Mount, opts *umountRecu
if parent := mnt.parent(); parent != nil && (opts.disconnectHierarchy || !parent.umounted) {
vdsToDecRef = append(vdsToDecRef, vfs.disconnectLocked(mnt))
}
if mnt.propType == Shared {
vfs.setPropagation(mnt, Private)
}
}
if opts.eager {
for {
@@ -600,9 +903,9 @@ func (mntns *MountNamespace) DecRef(ctx context.Context) {
mntns.MountNamespaceRefs.DecRef(func() {
vfs.mountMu.Lock()
vfs.mounts.seq.BeginWrite()
vdsToDecRef, mountsToDecRef := vfs.umountRecursiveLocked(mntns.root, &umountRecursiveOptions{
vdsToDecRef, mountsToDecRef := vfs.umountAtRecursiveLocked(ctx, mntns.Root(), &umountRecursiveOptions{
disconnectHierarchy: true,
}, nil, nil)
})
vfs.mounts.seq.EndWrite()
vfs.mountMu.Unlock()
for _, vd := range vdsToDecRef {
@@ -1062,6 +1365,9 @@ func (vfs *VirtualFilesystem) GenerateProcMountInfo(ctx context.Context, taskRoo
fmt.Fprintf(buf, "%s ", opts)
// (7) Optional fields: zero or more fields of the form "tag[:value]".
if mnt.propType == Shared {
fmt.Fprintf(buf, "shared:%d ", mnt.groupID)
}
// (8) Separator: the end of the optional fields is marked by a single hyphen.
fmt.Fprintf(buf, "- ")
@@ -1116,3 +1422,26 @@ func superBlockOpts(mountPath string, mnt *Mount) string {
return opts
}
// allocateGroupID returns a new mount group id if one is available, and
// error otherwise. If the group ID bitmap is full, double the size of the
// bitmap before allocating the new group id.
//
// +checklocks:vfs.mountMu
func (vfs *VirtualFilesystem) allocateGroupID() (uint32, error) {
groupID, err := vfs.groupIDBitmap.FirstZero(1)
if err != nil {
if err := vfs.groupIDBitmap.Grow(uint32(vfs.groupIDBitmap.Size())); err != nil {
return 0, err
}
}
vfs.groupIDBitmap.Add(groupID)
return groupID, nil
}
// freeGroupID marks a groupID as available for reuse.
//
// +checklocks:vfs.mountMu
func (vfs *VirtualFilesystem) freeGroupID(id uint32) {
vfs.groupIDBitmap.Remove(id)
}
+8
View File
@@ -41,6 +41,7 @@ import (
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/atomicbitops"
"gvisor.dev/gvisor/pkg/bitmap"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/errors/linuxerr"
"gvisor.dev/gvisor/pkg/fspath"
@@ -123,6 +124,9 @@ type VirtualFilesystem struct {
// filesystemsMu.
filesystemsMu sync.Mutex `state:"nosave"`
filesystems map[*Filesystem]struct{}
// groupIDBitmap tracks which mount group IDs are available for allocation.
groupIDBitmap bitmap.Bitmap
}
// Init initializes a new VirtualFilesystem with no mounts or FilesystemTypes.
@@ -138,6 +142,10 @@ func (vfs *VirtualFilesystem) Init(ctx context.Context) error {
vfs.filesystems = make(map[*Filesystem]struct{})
vfs.mounts.Init()
vfs.mountMu.Lock()
vfs.groupIDBitmap = bitmap.New(1024)
vfs.mountMu.Unlock()
// Construct vfs.anonMount.
anonfsDevMinor, err := vfs.GetAnonBlockDevMinor()
if err != nil {
File diff suppressed because it is too large Load Diff