mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Implement MS_REC.
PiperOrigin-RevId: 571091808
This commit is contained in:
committed by
gVisor bot
parent
9aa05f01e0
commit
598fbe6503
@@ -46,7 +46,7 @@ func Mount(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr,
|
||||
|
||||
// Silently allow MS_NOSUID, since we don't implement set-id bits anyway.
|
||||
const unsupported = linux.MS_REMOUNT | linux.MS_UNBINDABLE | linux.MS_MOVE |
|
||||
linux.MS_REC | linux.MS_NODIRATIME
|
||||
linux.MS_NODIRATIME
|
||||
|
||||
// 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
|
||||
@@ -67,7 +67,7 @@ func Mount(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr,
|
||||
}
|
||||
defer target.Release(t)
|
||||
|
||||
if flags&linux.MS_BIND == linux.MS_BIND {
|
||||
if flags&linux.MS_BIND != 0 {
|
||||
var sourcePath fspath.Path
|
||||
sourcePath, err = copyInPath(t, sourceAddr)
|
||||
if err != nil {
|
||||
@@ -79,11 +79,10 @@ func Mount(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr,
|
||||
return 0, nil, err
|
||||
}
|
||||
defer sourceTpop.Release(t)
|
||||
return 0, nil, t.Kernel().VFS().BindAt(t, creds, &sourceTpop.pop, &target.pop)
|
||||
return 0, nil, t.Kernel().VFS().BindAt(t, creds, &sourceTpop.pop, &target.pop, flags&linux.MS_REC != 0)
|
||||
}
|
||||
const propagationFlags = linux.MS_SHARED | linux.MS_PRIVATE | linux.MS_SLAVE | linux.MS_UNBINDABLE
|
||||
if propFlag := flags & propagationFlags; propFlag != 0 {
|
||||
return 0, nil, t.Kernel().VFS().SetMountPropagationAt(t, creds, &target.pop, uint32(propFlag))
|
||||
if flags&(linux.MS_SHARED|linux.MS_PRIVATE|linux.MS_SLAVE|linux.MS_UNBINDABLE) != 0 {
|
||||
return 0, nil, t.Kernel().VFS().SetMountPropagationAt(t, creds, &target.pop, uint32(flags))
|
||||
}
|
||||
|
||||
// Only copy in source, fstype, and data if we are doing a normal mount.
|
||||
|
||||
+46
-20
@@ -118,10 +118,6 @@ type Mount struct {
|
||||
// Mount.EndWrite(). The MSB of writers is set if MS_RDONLY is in effect.
|
||||
// writers is accessed using atomic memory operations.
|
||||
writers atomicbitops.Int64
|
||||
|
||||
// pendingChildren is a list of new child mounts that have not yet been
|
||||
// connected to this mount as the parent.
|
||||
pendingChildren []*Mount
|
||||
}
|
||||
|
||||
func newMount(vfs *VirtualFilesystem, fs *Filesystem, root *Dentry, mntns *MountNamespace, opts *MountOptions) *Mount {
|
||||
@@ -241,12 +237,12 @@ func (vfs *VirtualFilesystem) MountDisconnected(ctx context.Context, creds *auth
|
||||
return newMount(vfs, fs, root, nil /* mntns */, opts), nil
|
||||
}
|
||||
|
||||
// attachMountLocked attaches mnt to vd and propagates the mount to vd.mount's
|
||||
// peers and followers. This method is analogous to
|
||||
// attachTreeLocked attaches the mount tree at mnt to vd and propagates the
|
||||
// mount to vd.mount's peers and followers. This method is analogous to
|
||||
// fs/namespace.c:attach_recursive_mnt() in Linux.
|
||||
//
|
||||
// +checklocks:vfs.mountMu
|
||||
func (vfs *VirtualFilesystem) attachMountLocked(ctx context.Context, mnt *Mount, vd VirtualDentry) error {
|
||||
func (vfs *VirtualFilesystem) attachTreeLocked(ctx context.Context, mnt *Mount, vd VirtualDentry) error {
|
||||
vdCleanup := cleanup.Make(func() {
|
||||
vd.DecRef(ctx)
|
||||
})
|
||||
@@ -256,8 +252,11 @@ func (vfs *VirtualFilesystem) attachMountLocked(ctx context.Context, mnt *Mount,
|
||||
if vd.mount.neverConnected() {
|
||||
return linuxerr.EINVAL
|
||||
}
|
||||
if vd.mount.ns.mounts+1 > MountMax {
|
||||
return linuxerr.ENOSPC
|
||||
defer func() {
|
||||
vd.mount.ns.pending = 0
|
||||
}()
|
||||
if err := vd.mount.ns.checkMountCount(ctx, mnt); err != nil {
|
||||
return err
|
||||
}
|
||||
if vd.mount.isShared {
|
||||
if err := vfs.allocMountGroupIDs(mnt, true); err != nil {
|
||||
@@ -270,7 +269,10 @@ func (vfs *VirtualFilesystem) attachMountLocked(ctx context.Context, mnt *Mount,
|
||||
// force.
|
||||
vfs.freeMountGroupIDs(mnt.submountsLocked()) // +checklocksforce
|
||||
for pmnt := range propMnts {
|
||||
vfs.abortTree(ctx, pmnt) // +checklocksforce
|
||||
if !pmnt.parent().neverConnected() {
|
||||
pmnt.parent().ns.pending -= pmnt.countSubmountsLocked() // +checklocksforce
|
||||
}
|
||||
vfs.abortUncommitedMount(ctx, pmnt) // +checklocksforce
|
||||
}
|
||||
})
|
||||
defer cleanup.Clean()
|
||||
@@ -287,8 +289,9 @@ func (vfs *VirtualFilesystem) attachMountLocked(ctx context.Context, mnt *Mount,
|
||||
return err
|
||||
}
|
||||
cleanup.Release()
|
||||
vfs.commitChildren(ctx, mnt)
|
||||
for pmnt := range propMnts {
|
||||
vfs.commitTree(ctx, pmnt)
|
||||
vfs.commitMount(ctx, pmnt)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -305,7 +308,7 @@ func (vfs *VirtualFilesystem) ConnectMountAt(ctx context.Context, creds *auth.Cr
|
||||
}
|
||||
vfs.lockMounts()
|
||||
defer vfs.unlockMounts(ctx)
|
||||
return vfs.attachMountLocked(ctx, mnt, vd)
|
||||
return vfs.attachTreeLocked(ctx, mnt, vd)
|
||||
}
|
||||
|
||||
// connectMountAtLocked attaches mnt at vd. This method consumes a reference on
|
||||
@@ -443,7 +446,8 @@ type cloneTreeNode struct {
|
||||
}
|
||||
|
||||
// cloneMountTree creates a copy of mnt's tree with the specified root
|
||||
// dentry at root. The new descendants are added to mnt's pending mount list.
|
||||
// dentry at root. The new descendants are added to mnt's children list but are
|
||||
// not connected with call to connectLocked.
|
||||
// `cloneFunc` is a callback that is executed for each cloned mount.
|
||||
// This method is analogous to fs/namespace.c:copy_tree() in Linux.
|
||||
//
|
||||
@@ -466,7 +470,7 @@ func (vfs *VirtualFilesystem) cloneMountTree(ctx context.Context, mnt *Mount, ro
|
||||
}
|
||||
m, err := vfs.cloneMount(c, c.root, nil, cloneType)
|
||||
if err != nil {
|
||||
vfs.abortTree(ctx, clone)
|
||||
vfs.abortUncommitedMount(ctx, clone)
|
||||
return nil, err
|
||||
}
|
||||
mp := VirtualDentry{
|
||||
@@ -475,7 +479,10 @@ func (vfs *VirtualFilesystem) cloneMountTree(ctx context.Context, mnt *Mount, ro
|
||||
}
|
||||
mp.IncRef()
|
||||
m.setKey(mp)
|
||||
p.parentMount.pendingChildren = append(p.parentMount.pendingChildren, m)
|
||||
if p.parentMount.children == nil {
|
||||
p.parentMount.children = make(map[*Mount]struct{})
|
||||
}
|
||||
p.parentMount.children[m] = struct{}{}
|
||||
if len(c.children) != 0 {
|
||||
queue = append(queue, cloneTreeNode{c, m})
|
||||
}
|
||||
@@ -490,9 +497,7 @@ func (vfs *VirtualFilesystem) cloneMountTree(ctx context.Context, mnt *Mount, ro
|
||||
// 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) error {
|
||||
func (vfs *VirtualFilesystem) BindAt(ctx context.Context, creds *auth.Credentials, source, target *PathOperation, recursive bool) error {
|
||||
sourceVd, err := vfs.GetDentryAt(ctx, creds, source, &GetDentryOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -505,13 +510,22 @@ func (vfs *VirtualFilesystem) BindAt(ctx context.Context, creds *auth.Credential
|
||||
|
||||
vfs.lockMounts()
|
||||
defer vfs.unlockMounts(ctx)
|
||||
clone, err := vfs.cloneMount(sourceVd.mount, sourceVd.dentry, nil, 0)
|
||||
var clone *Mount
|
||||
if recursive {
|
||||
clone, err = vfs.cloneMountTree(ctx, sourceVd.mount, sourceVd.dentry, 0, nil)
|
||||
} else {
|
||||
clone, err = vfs.cloneMount(sourceVd.mount, sourceVd.dentry, nil, 0)
|
||||
}
|
||||
if err != nil {
|
||||
vfs.delayDecRef(targetVd)
|
||||
return err
|
||||
}
|
||||
vfs.delayDecRef(clone)
|
||||
return vfs.attachMountLocked(ctx, clone, targetVd)
|
||||
if err := vfs.attachTreeLocked(ctx, clone, targetVd); err != nil {
|
||||
vfs.abortUncomittedChildren(ctx, clone)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MountAt creates and mounts a Filesystem configured by the given arguments.
|
||||
@@ -1134,6 +1148,18 @@ func (mnt *Mount) submountsLocked() []*Mount {
|
||||
return mounts
|
||||
}
|
||||
|
||||
// countSubmountsLocked returns mnt's total number of descendants including
|
||||
// uncommitted descendants.
|
||||
//
|
||||
// Precondition: mnt.vfs.mountMu must be held.
|
||||
func (mnt *Mount) countSubmountsLocked() uint32 {
|
||||
mounts := uint32(1)
|
||||
for m := range mnt.children {
|
||||
mounts += m.countSubmountsLocked()
|
||||
}
|
||||
return mounts
|
||||
}
|
||||
|
||||
// Root returns the mount's root. It does not take a reference on the returned
|
||||
// Dentry.
|
||||
func (mnt *Mount) Root() *Dentry {
|
||||
|
||||
@@ -54,6 +54,9 @@ type MountNamespace struct {
|
||||
|
||||
// mounts is the total number of mounts in this mount namespace.
|
||||
mounts uint32
|
||||
|
||||
// pending is the total number of pending mounts in this mount namespace.
|
||||
pending uint32
|
||||
}
|
||||
|
||||
// Namespace is the namespace interface.
|
||||
@@ -184,7 +187,7 @@ func (vfs *VirtualFilesystem) CloneMountNamespace(
|
||||
}
|
||||
newns.root = newRoot
|
||||
newns.root.ns = newns
|
||||
vfs.commitPendingTree(ctx, newRoot)
|
||||
vfs.commitChildren(ctx, newRoot)
|
||||
return newns, nil
|
||||
}
|
||||
|
||||
@@ -242,3 +245,18 @@ func (mntns *MountNamespace) Root(ctx context.Context) VirtualDentry {
|
||||
vd.dentry.IncRef()
|
||||
return vd
|
||||
}
|
||||
|
||||
func (mntns *MountNamespace) checkMountCount(ctx context.Context, mnt *Mount) error {
|
||||
if mntns.mounts > MountMax {
|
||||
return linuxerr.ENOSPC
|
||||
}
|
||||
if mntns.mounts+mntns.pending > MountMax {
|
||||
return linuxerr.ENOSPC
|
||||
}
|
||||
mnts := mnt.countSubmountsLocked()
|
||||
if mntns.mounts+mntns.pending+mnts > MountMax {
|
||||
return linuxerr.ENOSPC
|
||||
}
|
||||
mntns.pending += mnts
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -35,18 +35,24 @@ const (
|
||||
makePrivateClone
|
||||
// Analogous to CL_SHARED_TO_SLAVE in Linux.
|
||||
sharedToFollowerClone
|
||||
|
||||
propagationFlags = linux.MS_SHARED | linux.MS_PRIVATE | linux.MS_SLAVE | linux.MS_UNBINDABLE
|
||||
)
|
||||
|
||||
// +checklocks:vfs.mountMu
|
||||
func (vfs *VirtualFilesystem) commitPendingTree(ctx context.Context, mnt *Mount) {
|
||||
for _, c := range mnt.pendingChildren {
|
||||
vfs.commitTree(ctx, c)
|
||||
func (vfs *VirtualFilesystem) commitChildren(ctx context.Context, mnt *Mount) {
|
||||
for c := range mnt.children {
|
||||
if c.neverConnected() {
|
||||
vfs.commitMount(ctx, c)
|
||||
}
|
||||
}
|
||||
mnt.pendingChildren = nil
|
||||
}
|
||||
|
||||
// commitMount attaches mnt to the parent and mountpoint specified by its
|
||||
// mountKey and recursively does the same for all of mnt's descendants.
|
||||
//
|
||||
// +checklocks:vfs.mountMu
|
||||
func (vfs *VirtualFilesystem) commitTree(ctx context.Context, mnt *Mount) {
|
||||
func (vfs *VirtualFilesystem) commitMount(ctx context.Context, mnt *Mount) {
|
||||
mp := mnt.getKey()
|
||||
|
||||
// If there is already a mount at this (parent, point), disconnect it from its
|
||||
@@ -66,29 +72,38 @@ func (vfs *VirtualFilesystem) commitTree(ctx context.Context, mnt *Mount) {
|
||||
vfs.delayDecRef(child)
|
||||
}
|
||||
vfs.mounts.seq.EndWrite()
|
||||
vfs.commitPendingTree(ctx, mnt)
|
||||
vfs.commitChildren(ctx, mnt)
|
||||
}
|
||||
|
||||
// abortTree releases references on a pending mount and all its pending
|
||||
// descendants.
|
||||
//
|
||||
// +checklocks:vfs.mountMu
|
||||
func (vfs *VirtualFilesystem) abortTree(ctx context.Context, mnt *Mount) {
|
||||
func (vfs *VirtualFilesystem) abortUncomittedChildren(ctx context.Context, mnt *Mount) {
|
||||
for c := range mnt.children {
|
||||
if c.neverConnected() {
|
||||
vfs.abortUncommitedMount(ctx, c)
|
||||
delete(mnt.children, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// abortUncommitedMount releases references on mnt and all its descendants.
|
||||
//
|
||||
// Prerequisite: mnt is not connected, i.e. mnt.ns == nil.
|
||||
// +checklocks:vfs.mountMu
|
||||
func (vfs *VirtualFilesystem) abortUncommitedMount(ctx context.Context, mnt *Mount) {
|
||||
vfs.delayDecRef(mnt)
|
||||
vfs.delayDecRef(mnt.getKey())
|
||||
mnt.setKey(VirtualDentry{})
|
||||
vfs.setPropagation(mnt, linux.MS_PRIVATE)
|
||||
for _, c := range mnt.pendingChildren {
|
||||
vfs.abortTree(ctx, c)
|
||||
}
|
||||
mnt.pendingChildren = nil
|
||||
vfs.abortUncomittedChildren(ctx, mnt)
|
||||
}
|
||||
|
||||
// SetMountPropagationAt changes the propagation type of the mount pointed to by
|
||||
// pop.
|
||||
func (vfs *VirtualFilesystem) SetMountPropagationAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation, propFlags uint32) error {
|
||||
func (vfs *VirtualFilesystem) SetMountPropagationAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation, propFlag uint32) error {
|
||||
recursive := propFlag&linux.MS_REC != 0
|
||||
propFlag &= propagationFlags
|
||||
// Check if flags is a power of 2. If not then more than one flag is set.
|
||||
if !bits.IsPowerOfTwo32(propFlags) {
|
||||
if !bits.IsPowerOfTwo32(propFlag) {
|
||||
return linuxerr.EINVAL
|
||||
}
|
||||
vd, err := vfs.GetDentryAt(ctx, creds, pop, &GetDentryOptions{})
|
||||
@@ -107,20 +122,27 @@ func (vfs *VirtualFilesystem) SetMountPropagationAt(ctx context.Context, creds *
|
||||
} else if vd.dentry != vd.mount.root {
|
||||
return linuxerr.EINVAL
|
||||
}
|
||||
vfs.SetMountPropagation(vd.mount, propFlags)
|
||||
vfs.SetMountPropagation(vd.mount, propFlag, recursive)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetMountPropagation changes the propagation type of the mount.
|
||||
func (vfs *VirtualFilesystem) SetMountPropagation(mnt *Mount, propFlags uint32) error {
|
||||
func (vfs *VirtualFilesystem) SetMountPropagation(mnt *Mount, propFlags uint32, recursive bool) error {
|
||||
vfs.lockMounts()
|
||||
defer vfs.unlockMounts(context.Background())
|
||||
if propFlags == linux.MS_SHARED {
|
||||
if err := vfs.allocMountGroupIDs(mnt, false); err != nil {
|
||||
if err := vfs.allocMountGroupIDs(mnt, recursive); err != nil {
|
||||
return fmt.Errorf("allocMountGroupIDs: %v", err)
|
||||
}
|
||||
}
|
||||
vfs.setPropagation(mnt, propFlags)
|
||||
|
||||
if !recursive {
|
||||
vfs.setPropagation(mnt, propFlags)
|
||||
return nil
|
||||
}
|
||||
for _, m := range mnt.submountsLocked() {
|
||||
vfs.setPropagation(m, propFlags)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -303,7 +325,7 @@ func (vfs *VirtualFilesystem) propagateMount(ctx context.Context, dstMnt *Mount,
|
||||
cloneType |= makeSharedClone
|
||||
}
|
||||
}
|
||||
clone, err := vfs.cloneMount(state.prevSrc, state.prevSrc.root, nil, cloneType)
|
||||
clone, err := vfs.cloneMountTree(ctx, state.prevSrc, state.prevSrc.root, cloneType, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -315,10 +337,7 @@ func (vfs *VirtualFilesystem) propagateMount(ctx context.Context, dstMnt *Mount,
|
||||
}
|
||||
state.prevDst = dstMnt
|
||||
state.prevSrc = clone
|
||||
if uint32(len(state.propList))+dstMnt.ns.mounts > MountMax {
|
||||
return linuxerr.ENOSPC
|
||||
}
|
||||
return nil
|
||||
return dstMnt.ns.checkMountCount(ctx, clone)
|
||||
}
|
||||
|
||||
// nextFollowerPeerGroup iterates through the propagation tree and returns the
|
||||
|
||||
@@ -1419,6 +1419,7 @@ cc_binary(
|
||||
"//test/util:test_main",
|
||||
"//test/util:test_util",
|
||||
"//test/util:thread_util",
|
||||
"@com_google_absl//absl/container:flat_hash_map",
|
||||
"@com_google_absl//absl/strings:str_format",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "absl/strings/match.h"
|
||||
#include "absl/strings/numbers.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
@@ -820,8 +821,8 @@ TEST(MountTest, SimpleBind) {
|
||||
// Write to child1 in dir1.
|
||||
const std::string filename = "foo.txt";
|
||||
const std::string contents = "barbaz";
|
||||
ASSERT_NO_ERRNO(CreateWithContents(JoinPath(child1.path(), filename),
|
||||
contents, O_WRONLY));
|
||||
ASSERT_NO_ERRNO(
|
||||
CreateWithContents(JoinPath(child1.path(), filename), contents, 0666));
|
||||
// Verify both directories have the same nodes.
|
||||
std::vector<std::string> child_names = {std::string(Basename(child1.path())),
|
||||
std::string(Basename(child2.path()))};
|
||||
@@ -1851,6 +1852,163 @@ TEST(MountTest, MaxMountsWithSlave) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(MountTest, SetPropagationRecursive) {
|
||||
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
|
||||
|
||||
const TempPath a = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
|
||||
auto const a_mnt = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
Mount("test", a.path(), "tmpfs", 0, "mode=0123", MNT_DETACH));
|
||||
const auto b = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(a.path()));
|
||||
auto const b_mnt = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
Mount("test", b.path(), "tmpfs", 0, "mode=0123", MNT_DETACH));
|
||||
const auto c = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(b.path()));
|
||||
auto const c_mnt = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
Mount("test", c.path(), "tmpfs", 0, "mode=0123", MNT_DETACH));
|
||||
const auto d = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(c.path()));
|
||||
auto const d_mnt = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
Mount("test", d.path(), "tmpfs", 0, "mode=0123", MNT_DETACH));
|
||||
|
||||
ASSERT_THAT(mount("", a.path().c_str(), "", MS_SHARED | MS_REC, 0),
|
||||
SyscallSucceeds());
|
||||
absl::flat_hash_map<std::string, std::vector<MountOptional>> optionals =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(MountOptionals());
|
||||
EXPECT_NE(optionals[a.path()][0].shared, 0);
|
||||
EXPECT_NE(optionals[b.path()][0].shared, 0);
|
||||
EXPECT_NE(optionals[c.path()][0].shared, 0);
|
||||
EXPECT_NE(optionals[d.path()][0].shared, 0);
|
||||
}
|
||||
|
||||
TEST(MountTest, SetSlaveRecursive) {
|
||||
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
|
||||
|
||||
const auto a = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
|
||||
auto const a_mnt = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
Mount(a.path(), a.path(), "", MS_BIND, "", MNT_DETACH));
|
||||
ASSERT_THAT(mount("", a.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds());
|
||||
const auto a_master = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
|
||||
auto const a_master_mnt = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
Mount(a.path(), a_master.path(), "", MS_BIND, "", MNT_DETACH));
|
||||
|
||||
const auto b = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(a.path()));
|
||||
auto const b_mnt = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
Mount(a.path(), b.path(), "", MS_BIND, "", MNT_DETACH));
|
||||
const auto b_master = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
|
||||
auto const b_master_mnt = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
Mount(b.path(), b_master.path(), "", MS_BIND, "", MNT_DETACH));
|
||||
|
||||
ASSERT_THAT(mount("", a.path().c_str(), "", MS_SLAVE | MS_REC, 0),
|
||||
SyscallSucceeds());
|
||||
|
||||
absl::flat_hash_map<std::string, std::vector<MountOptional>> optionals =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(MountOptionals());
|
||||
EXPECT_EQ(optionals[a.path()][0].master,
|
||||
optionals[a_master.path()][0].shared);
|
||||
EXPECT_EQ(optionals[b.path()][0].master,
|
||||
optionals[b_master.path()][0].shared);
|
||||
}
|
||||
|
||||
TEST(MountTest, RecursiveBind) {
|
||||
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
|
||||
|
||||
const auto a = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
|
||||
auto const a_mnt = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
Mount(a.path(), a.path(), "", MS_BIND, "", MNT_DETACH));
|
||||
const auto b = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(a.path()));
|
||||
auto const b_mnt = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
Mount(b.path(), b.path(), "", MS_BIND, "", MNT_DETACH));
|
||||
const auto c = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(b.path()));
|
||||
const auto d = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
|
||||
auto const d_mnt = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
Mount(a.path(), d.path(), "", MS_BIND | MS_REC, "", MNT_DETACH));
|
||||
|
||||
// Write to child1 in dir1.
|
||||
const std::string filename = "foo.txt";
|
||||
const std::string contents = "barbaz";
|
||||
ASSERT_NO_ERRNO(
|
||||
CreateWithContents(JoinPath(c.path(), filename), contents, 0666));
|
||||
// Verify both directories have the same nodes.
|
||||
const std::string path =
|
||||
JoinPath(d.path(), Basename(b.path()), Basename(c.path()), filename);
|
||||
|
||||
std::string output;
|
||||
ASSERT_NO_ERRNO(GetContents(path, &output));
|
||||
EXPECT_EQ(output, contents);
|
||||
}
|
||||
|
||||
TEST(MountTest, MaxRecursiveBind) {
|
||||
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
|
||||
|
||||
const auto a = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
|
||||
auto const a_mnt = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
Mount(a.path(), a.path(), "", MS_BIND, "", MNT_DETACH));
|
||||
const auto b = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(a.path()));
|
||||
|
||||
int mount_max = 10000;
|
||||
bool mount_max_exists =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(Exists("/proc/sys/fs/mount-max"));
|
||||
if (mount_max_exists) {
|
||||
std::string mount_max_string;
|
||||
ASSERT_NO_ERRNO(GetContents("/proc/sys/fs/mount-max", &mount_max_string));
|
||||
ASSERT_TRUE(absl::SimpleAtoi(mount_max_string, &mount_max));
|
||||
}
|
||||
|
||||
const std::vector<ProcMountInfoEntry> mounts =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries());
|
||||
int num_binds = static_cast<int>(std::log2(mount_max - mounts.size()));
|
||||
|
||||
for (int i = 0; i < num_binds; i++) {
|
||||
ASSERT_THAT(mount(a.path().c_str(), b.path().c_str(), nullptr,
|
||||
MS_BIND | MS_REC, nullptr),
|
||||
SyscallSucceeds());
|
||||
}
|
||||
ASSERT_THAT(mount(a.path().c_str(), b.path().c_str(), nullptr,
|
||||
MS_BIND | MS_REC, nullptr),
|
||||
SyscallFailsWithErrno(ENOSPC));
|
||||
}
|
||||
|
||||
TEST(MountTest, RecursiveBindPropagation) {
|
||||
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
|
||||
|
||||
const auto parent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
|
||||
auto const parent_mnt = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
Mount(parent.path(), parent.path(), "", MS_BIND, "", MNT_DETACH));
|
||||
const auto a =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(parent.path()));
|
||||
const auto b =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(parent.path()));
|
||||
const auto c =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(parent.path()));
|
||||
|
||||
auto const a_mnt = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
Mount("test", a.path(), "tmpfs", 0, "", MNT_DETACH));
|
||||
ASSERT_THAT(mount("", a.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds());
|
||||
|
||||
auto const b_mnt = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
Mount(a.path(), b.path(), "", MS_BIND, "", MNT_DETACH));
|
||||
auto const c_mnt = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
Mount(a.path(), c.path(), "", MS_BIND, "", MNT_DETACH));
|
||||
|
||||
const auto d =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(parent.path()));
|
||||
const auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(a.path()));
|
||||
auto const d_mnt = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
Mount(d.path(), d.path(), "", MS_BIND, "", MNT_DETACH));
|
||||
|
||||
const auto e = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(d.path()));
|
||||
auto const e_mnt = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
Mount(e.path(), e.path(), "", MS_BIND, "", MNT_DETACH));
|
||||
|
||||
auto const f_mnt = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
Mount(d.path(), f.path(), "", MS_BIND | MS_REC, "", MNT_DETACH));
|
||||
|
||||
absl::flat_hash_map<std::string, std::vector<MountOptional>> optionals =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(MountOptionals());
|
||||
auto b_e_path = JoinPath(b.path(), Basename(f.path()), Basename(e.path()));
|
||||
ASSERT_FALSE(optionals[b_e_path].empty());
|
||||
auto c_e_path = JoinPath(c.path(), Basename(f.path()), Basename(e.path()));
|
||||
ASSERT_FALSE(optionals[c_e_path].empty());
|
||||
}
|
||||
|
||||
TEST(MountTest, MountNamespace) {
|
||||
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user