Implement MS_REC.

PiperOrigin-RevId: 571091808
This commit is contained in:
Lucas Manning
2023-10-05 12:08:15 -07:00
committed by gVisor bot
parent 9aa05f01e0
commit 598fbe6503
6 changed files with 275 additions and 54 deletions
+5 -6
View File
@@ -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
View File
@@ -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 {
+19 -1
View File
@@ -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
}
+44 -25
View File
@@ -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
+1
View File
@@ -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",
],
)
+160 -2
View File
@@ -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)));