mirror of
https://github.com/hanzoai/visor.git
synced 2026-09-24 17:04:14 +00:00
958 lines
35 KiB
C++
958 lines
35 KiB
C++
// Copyright 2026 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.
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <linux/capability.h>
|
|
#include <sched.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <sys/mount.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/sysmacros.h>
|
|
#include <sys/vfs.h>
|
|
#include <unistd.h>
|
|
|
|
#include <string>
|
|
|
|
#include "gmock/gmock.h"
|
|
#include "gtest/gtest.h"
|
|
#include "test/util/cleanup.h"
|
|
#include "test/util/fs_util.h"
|
|
#include "test/util/linux_capability_util.h"
|
|
#include "test/util/posix_error.h"
|
|
#include "test/util/temp_path.h"
|
|
#include "test/util/test_util.h"
|
|
#include "test/util/thread_util.h"
|
|
|
|
// Syscall Definitions/Constants
|
|
|
|
#ifndef FSOPEN_CLOEXEC
|
|
#define FSOPEN_CLOEXEC 0x1
|
|
#endif
|
|
|
|
#ifndef FSCONFIG_SET_FLAG
|
|
#define FSCONFIG_SET_FLAG 0x0
|
|
#define FSCONFIG_SET_STRING 0x1
|
|
#define FSCONFIG_SET_BINARY 0x2
|
|
#define FSCONFIG_SET_PATH 0x3
|
|
#define FSCONFIG_SET_PATH_EMPTY 0x4
|
|
#define FSCONFIG_SET_FD 0x5
|
|
#define FSCONFIG_CMD_CREATE 0x6
|
|
#define FSCONFIG_CMD_RECONFIGURE 0x7
|
|
#define FSCONFIG_CMD_CREATE_EXCL 0x8
|
|
#endif
|
|
|
|
#ifndef FSMOUNT_CLOEXEC
|
|
#define FSMOUNT_CLOEXEC 0x1
|
|
#endif
|
|
|
|
#ifndef MOUNT_ATTR_RDONLY
|
|
#define MOUNT_ATTR_RDONLY 0x00000001
|
|
#define MOUNT_ATTR_NOSUID 0x00000002
|
|
#define MOUNT_ATTR_NODEV 0x00000004
|
|
#define MOUNT_ATTR_NOEXEC 0x00000008
|
|
#define MOUNT_ATTR__ATIME 0x00000070
|
|
#define MOUNT_ATTR_RELATIME 0x00000000
|
|
#define MOUNT_ATTR_NOATIME 0x00000010
|
|
#define MOUNT_ATTR_STRICTATIME 0x00000020
|
|
#define MOUNT_ATTR_NODIRATIME 0x00000080
|
|
#endif
|
|
|
|
#ifndef MOVE_MOUNT_F_SYMLINKS
|
|
#define MOVE_MOUNT_F_SYMLINKS 0x00000001
|
|
#define MOVE_MOUNT_F_AUTOMOUNTS 0x00000002
|
|
#define MOVE_MOUNT_F_EMPTY_PATH 0x00000004
|
|
#define MOVE_MOUNT_T_SYMLINKS 0x00000010
|
|
#define MOVE_MOUNT_T_AUTOMOUNTS 0x00000020
|
|
#define MOVE_MOUNT_T_EMPTY_PATH 0x00000040
|
|
#define MOVE_MOUNT__MASK 0x00000077
|
|
#endif
|
|
|
|
#ifndef ST_RDONLY
|
|
#define ST_RDONLY 0x1
|
|
#endif
|
|
#ifndef ST_NODEV
|
|
#define ST_NODEV 0x4
|
|
#endif
|
|
#ifndef ST_NOEXEC
|
|
#define ST_NOEXEC 0x8
|
|
#endif
|
|
|
|
#ifndef SYS_fsopen
|
|
#if defined(__x86_64__)
|
|
#define SYS_open_tree 428
|
|
#define SYS_move_mount 429
|
|
#define SYS_fsopen 430
|
|
#define SYS_fsconfig 431
|
|
#define SYS_fsmount 432
|
|
#define SYS_fspick 433
|
|
#elif defined(__aarch64__)
|
|
#define SYS_open_tree 428
|
|
#define SYS_move_mount 429
|
|
#define SYS_fsopen 430
|
|
#define SYS_fsconfig 431
|
|
#define SYS_fsmount 432
|
|
#define SYS_fspick 433
|
|
#else
|
|
#error "Unknown architecture"
|
|
#endif
|
|
#endif
|
|
|
|
#ifndef OPEN_TREE_CLONE
|
|
#define OPEN_TREE_CLONE 1
|
|
#define OPEN_TREE_CLOEXEC O_CLOEXEC
|
|
#endif
|
|
|
|
#ifndef AT_RECURSIVE
|
|
#define AT_RECURSIVE 0x8000
|
|
#endif
|
|
|
|
inline int fsopen(const char* fsname, unsigned int flags) {
|
|
return syscall(SYS_fsopen, fsname, flags);
|
|
}
|
|
inline int fsconfig(int fd, unsigned int cmd, const char* key,
|
|
const void* value, int aux) {
|
|
return syscall(SYS_fsconfig, fd, cmd, key, value, aux);
|
|
}
|
|
inline int fsmount(int fsfd, unsigned int flags, unsigned int attr_flags) {
|
|
return syscall(SYS_fsmount, fsfd, flags, attr_flags);
|
|
}
|
|
inline int move_mount(int from_dirfd, const char* from_pathname, int to_dirfd,
|
|
const char* to_pathname, unsigned int flags) {
|
|
return syscall(SYS_move_mount, from_dirfd, from_pathname, to_dirfd,
|
|
to_pathname, flags);
|
|
}
|
|
inline int open_tree(int dirfd, const char* pathname, unsigned int flags) {
|
|
return syscall(SYS_open_tree, dirfd, pathname, flags);
|
|
}
|
|
|
|
namespace gvisor {
|
|
namespace testing {
|
|
|
|
namespace {
|
|
|
|
constexpr char kTmpfs[] = "tmpfs";
|
|
|
|
// fsopen(2) tests
|
|
|
|
TEST(FsOpenTest, FsOpenSuccess) {
|
|
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
|
|
|
|
int fd1 = fsopen(kTmpfs, 0);
|
|
ASSERT_THAT(fd1, SyscallSucceeds());
|
|
EXPECT_THAT(close(fd1), SyscallSucceeds());
|
|
|
|
int fd2 = fsopen(kTmpfs, FSOPEN_CLOEXEC);
|
|
ASSERT_THAT(fd2, SyscallSucceeds());
|
|
EXPECT_THAT(close(fd2), SyscallSucceeds());
|
|
}
|
|
|
|
TEST(FsOpenTest, FsOpenInvalidFlags) {
|
|
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
|
|
|
|
EXPECT_THAT(fsopen(kTmpfs, -1), SyscallFailsWithErrno(EINVAL));
|
|
}
|
|
|
|
TEST(FsOpenTest, FsOpenPermDenied) {
|
|
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
|
|
|
|
// Drop privileges in another thread to verify CAP_SYS_ADMIN check.
|
|
ScopedThread([&]() {
|
|
EXPECT_NO_ERRNO(SetCapability(CAP_SYS_ADMIN, false));
|
|
EXPECT_THAT(fsopen(kTmpfs, 0), SyscallFailsWithErrno(EPERM));
|
|
});
|
|
}
|
|
|
|
// fsconfig(2) tests
|
|
|
|
TEST(FsConfigTest, FsConfigSetSource) {
|
|
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
|
|
|
|
int fsfd = fsopen(kTmpfs, 0);
|
|
ASSERT_THAT(fsfd, SyscallSucceeds());
|
|
auto cleanup = Cleanup([&]() { close(fsfd); });
|
|
|
|
EXPECT_THAT(fsconfig(fsfd, FSCONFIG_SET_STRING, "source", "my_source", 0),
|
|
SyscallSucceeds());
|
|
}
|
|
|
|
TEST(FsConfigTest, FsConfigSetParams) {
|
|
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
|
|
|
|
int fsfd = fsopen(kTmpfs, 0);
|
|
ASSERT_THAT(fsfd, SyscallSucceeds());
|
|
auto cleanup = Cleanup([&]() { close(fsfd); });
|
|
|
|
EXPECT_THAT(fsconfig(fsfd, FSCONFIG_SET_FLAG, "ro", NULL, 0),
|
|
SyscallSucceeds());
|
|
EXPECT_THAT(fsconfig(fsfd, FSCONFIG_SET_STRING, "mode", "755", 0),
|
|
SyscallSucceeds());
|
|
}
|
|
|
|
TEST(FsConfigTest, FsConfigSetSourceTwice) {
|
|
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
|
|
|
|
int fsfd = fsopen(kTmpfs, 0);
|
|
ASSERT_THAT(fsfd, SyscallSucceeds());
|
|
auto cleanup = Cleanup([&]() { close(fsfd); });
|
|
|
|
EXPECT_THAT(fsconfig(fsfd, FSCONFIG_SET_STRING, "source", "src1", 0),
|
|
SyscallSucceeds());
|
|
EXPECT_THAT(fsconfig(fsfd, FSCONFIG_SET_STRING, "source", "src2", 0),
|
|
SyscallFailsWithErrno(EINVAL));
|
|
}
|
|
|
|
TEST(FsConfigTest, FsConfigCreateWithoutSource) {
|
|
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
|
|
|
|
int fsfd = fsopen(kTmpfs, 0);
|
|
ASSERT_THAT(fsfd, SyscallSucceeds());
|
|
auto cleanup = Cleanup([&]() { close(fsfd); });
|
|
|
|
EXPECT_THAT(fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0),
|
|
SyscallSucceeds());
|
|
}
|
|
|
|
TEST(FsConfigTest, FsConfigBadFilesystem) {
|
|
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
|
|
// Note: Skipped on Linux because Linux fails immediately at fsopen() time
|
|
// with ENODEV. Currently on gVisor, fsopen() succeeds and the ENODEV failure
|
|
// occurs during fsconfig(FSCONFIG_CMD_CREATE).
|
|
SKIP_IF(!IsRunningOnGvisor());
|
|
|
|
int fsfd = fsopen("invalid_fs_name", 0);
|
|
ASSERT_THAT(fsfd, SyscallSucceeds());
|
|
auto cleanup = Cleanup([&]() { close(fsfd); });
|
|
|
|
EXPECT_THAT(fsconfig(fsfd, FSCONFIG_SET_STRING, "source", "src", 0),
|
|
SyscallSucceeds());
|
|
EXPECT_THAT(fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0),
|
|
SyscallFailsWithErrno(ENODEV));
|
|
}
|
|
|
|
TEST(FsConfigTest, FsConfigCreateSuccess) {
|
|
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
|
|
|
|
int fsfd = fsopen(kTmpfs, 0);
|
|
ASSERT_THAT(fsfd, SyscallSucceeds());
|
|
auto cleanup = Cleanup([&]() { close(fsfd); });
|
|
|
|
EXPECT_THAT(fsconfig(fsfd, FSCONFIG_SET_STRING, "source", "my_source", 0),
|
|
SyscallSucceeds());
|
|
EXPECT_THAT(fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0),
|
|
SyscallSucceeds());
|
|
}
|
|
|
|
TEST(FsConfigTest, FsConfigCreateTwice) {
|
|
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
|
|
|
|
int fsfd = fsopen(kTmpfs, 0);
|
|
ASSERT_THAT(fsfd, SyscallSucceeds());
|
|
auto cleanup = Cleanup([&]() { close(fsfd); });
|
|
|
|
EXPECT_THAT(fsconfig(fsfd, FSCONFIG_SET_STRING, "source", "my_source", 0),
|
|
SyscallSucceeds());
|
|
EXPECT_THAT(fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0),
|
|
SyscallSucceeds());
|
|
EXPECT_THAT(fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0),
|
|
SyscallFailsWithErrno(EBUSY));
|
|
}
|
|
|
|
TEST(FsConfigTest, FsConfigUnsupportedCmds) {
|
|
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
|
|
// Note: Skipped on Linux because gVisor currently does not support
|
|
// FSCONFIG_CMD_CREATE_EXCL (returns EINVAL).
|
|
SKIP_IF(!IsRunningOnGvisor());
|
|
|
|
int fsfd = fsopen(kTmpfs, 0);
|
|
ASSERT_THAT(fsfd, SyscallSucceeds());
|
|
auto cleanup = Cleanup([&]() { close(fsfd); });
|
|
|
|
EXPECT_THAT(fsconfig(fsfd, FSCONFIG_CMD_CREATE_EXCL, NULL, NULL, 0),
|
|
SyscallFailsWithErrno(EINVAL));
|
|
}
|
|
|
|
TEST(FsConfigTest, FsConfigReconfigureSucceeds) {
|
|
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
|
|
|
|
int fsfd = fsopen(kTmpfs, 0);
|
|
ASSERT_THAT(fsfd, SyscallSucceeds());
|
|
auto cleanup_fs = Cleanup([&]() { close(fsfd); });
|
|
|
|
EXPECT_THAT(fsconfig(fsfd, FSCONFIG_SET_STRING, "source", "my_source", 0),
|
|
SyscallSucceeds());
|
|
EXPECT_THAT(fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0),
|
|
SyscallSucceeds());
|
|
|
|
int mntfd = fsmount(fsfd, FSMOUNT_CLOEXEC, 0);
|
|
ASSERT_THAT(mntfd, SyscallSucceeds());
|
|
auto cleanup_mnt = Cleanup([&]() { close(mntfd); });
|
|
|
|
EXPECT_THAT(fsconfig(fsfd, FSCONFIG_SET_FLAG, "ro", NULL, 0),
|
|
SyscallSucceeds());
|
|
EXPECT_THAT(fsconfig(fsfd, FSCONFIG_SET_STRING, "nr_inodes", "12345", 0),
|
|
SyscallSucceeds());
|
|
EXPECT_THAT(fsconfig(fsfd, FSCONFIG_CMD_RECONFIGURE, NULL, NULL, 0),
|
|
SyscallSucceeds());
|
|
|
|
// TODO(gvisor.dev/issues/13450): once reconfiguration is properly supported
|
|
// on underlying filesystems, this test should be updated.
|
|
struct statfs st;
|
|
ASSERT_THAT(fstatfs(mntfd, &st), SyscallSucceeds());
|
|
EXPECT_NE(st.f_files, 12345);
|
|
EXPECT_NE(st.f_flags & ST_RDONLY, ST_RDONLY);
|
|
}
|
|
|
|
TEST(FsConfigTest, FsConfigUnsupportedTypes) {
|
|
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
|
|
// Note: Skipped on Linux because gVisor currently does not support
|
|
// BINARY, FD, or PATH parameter types (returns EINVAL).
|
|
SKIP_IF(!IsRunningOnGvisor());
|
|
|
|
int fsfd = fsopen(kTmpfs, 0);
|
|
ASSERT_THAT(fsfd, SyscallSucceeds());
|
|
auto cleanup = Cleanup([&]() { close(fsfd); });
|
|
|
|
EXPECT_THAT(fsconfig(fsfd, FSCONFIG_SET_BINARY, "key", "val", 0),
|
|
SyscallFailsWithErrno(EINVAL));
|
|
EXPECT_THAT(fsconfig(fsfd, FSCONFIG_SET_FD, "key", NULL, 0),
|
|
SyscallFailsWithErrno(EINVAL));
|
|
EXPECT_THAT(fsconfig(fsfd, FSCONFIG_SET_PATH, "key", "val", 0),
|
|
SyscallFailsWithErrno(EINVAL));
|
|
}
|
|
|
|
// fsmount(2) tests
|
|
|
|
TEST(FsMountTest, FsMountSuccess) {
|
|
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
|
|
|
|
int fsfd = fsopen(kTmpfs, 0);
|
|
ASSERT_THAT(fsfd, SyscallSucceeds());
|
|
auto cleanup_fs = Cleanup([&]() { close(fsfd); });
|
|
|
|
EXPECT_THAT(fsconfig(fsfd, FSCONFIG_SET_STRING, "source", "my_source", 0),
|
|
SyscallSucceeds());
|
|
EXPECT_THAT(fsconfig(fsfd, FSCONFIG_SET_STRING, "nr_inodes", "12345", 0),
|
|
SyscallSucceeds());
|
|
EXPECT_THAT(fsconfig(fsfd, FSCONFIG_SET_FLAG, "ro", NULL, 0),
|
|
SyscallSucceeds());
|
|
EXPECT_THAT(fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0),
|
|
SyscallSucceeds());
|
|
|
|
int mntfd =
|
|
fsmount(fsfd, FSMOUNT_CLOEXEC, MOUNT_ATTR_NODEV | MOUNT_ATTR_NOEXEC);
|
|
ASSERT_THAT(mntfd, SyscallSucceeds());
|
|
auto cleanup_mnt = Cleanup([&]() { close(mntfd); });
|
|
|
|
struct statfs st;
|
|
ASSERT_THAT(fstatfs(mntfd, &st), SyscallSucceeds());
|
|
EXPECT_EQ(st.f_files, 12345);
|
|
EXPECT_EQ(st.f_flags & (ST_NODEV | ST_NOEXEC | ST_RDONLY),
|
|
ST_NODEV | ST_NOEXEC | ST_RDONLY);
|
|
}
|
|
|
|
TEST(FsMountTest, FsMountBeforeCreate) {
|
|
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
|
|
|
|
int fsfd = fsopen(kTmpfs, 0);
|
|
ASSERT_THAT(fsfd, SyscallSucceeds());
|
|
auto cleanup = Cleanup([&]() { close(fsfd); });
|
|
|
|
EXPECT_THAT(fsconfig(fsfd, FSCONFIG_SET_STRING, "source", "my_source", 0),
|
|
SyscallSucceeds());
|
|
EXPECT_THAT(fsmount(fsfd, 0, 0), SyscallFailsWithErrno(EINVAL));
|
|
}
|
|
|
|
TEST(FsMountTest, FsMountTwice) {
|
|
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
|
|
|
|
int fsfd = fsopen(kTmpfs, 0);
|
|
ASSERT_THAT(fsfd, SyscallSucceeds());
|
|
auto cleanup_fs = Cleanup([&]() { close(fsfd); });
|
|
|
|
EXPECT_THAT(fsconfig(fsfd, FSCONFIG_SET_STRING, "source", "my_source", 0),
|
|
SyscallSucceeds());
|
|
EXPECT_THAT(fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0),
|
|
SyscallSucceeds());
|
|
|
|
int mntfd = fsmount(fsfd, 0, 0);
|
|
ASSERT_THAT(mntfd, SyscallSucceeds());
|
|
auto cleanup_mnt = Cleanup([&]() { close(mntfd); });
|
|
|
|
EXPECT_THAT(fsmount(fsfd, 0, 0), SyscallFailsWithErrno(EBUSY));
|
|
}
|
|
|
|
TEST(FsMountTest, FsMountInvalidFlags) {
|
|
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
|
|
|
|
int fsfd = fsopen(kTmpfs, 0);
|
|
ASSERT_THAT(fsfd, SyscallSucceeds());
|
|
auto cleanup_fs = Cleanup([&]() { close(fsfd); });
|
|
|
|
EXPECT_THAT(fsconfig(fsfd, FSCONFIG_SET_STRING, "source", "my_source", 0),
|
|
SyscallSucceeds());
|
|
EXPECT_THAT(fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0),
|
|
SyscallSucceeds());
|
|
|
|
EXPECT_THAT(fsmount(fsfd, -1, 0), SyscallFailsWithErrno(EINVAL));
|
|
EXPECT_THAT(fsmount(fsfd, 0, -1), SyscallFailsWithErrno(EINVAL));
|
|
}
|
|
|
|
TEST(FsMountTest, FsMountDetachedUsage) {
|
|
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
|
|
|
|
int fsfd = fsopen(kTmpfs, 0);
|
|
ASSERT_THAT(fsfd, SyscallSucceeds());
|
|
auto cleanup_fs = Cleanup([&]() { close(fsfd); });
|
|
|
|
EXPECT_THAT(fsconfig(fsfd, FSCONFIG_SET_STRING, "source", "my_source", 0),
|
|
SyscallSucceeds());
|
|
EXPECT_THAT(fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0),
|
|
SyscallSucceeds());
|
|
|
|
int mntfd = fsmount(fsfd, 0, 0);
|
|
ASSERT_THAT(mntfd, SyscallSucceeds());
|
|
auto cleanup_mnt = Cleanup([&]() { close(mntfd); });
|
|
|
|
// Verify that detached mount FD acts as a valid root dirfd before attachment.
|
|
EXPECT_THAT(mkdirat(mntfd, "subdir", 0755), SyscallSucceeds());
|
|
|
|
int filefd = openat(mntfd, "file", O_CREAT | O_RDWR, 0644);
|
|
ASSERT_THAT(filefd, SyscallSucceeds());
|
|
EXPECT_THAT(close(filefd), SyscallSucceeds());
|
|
}
|
|
|
|
// move_mount(2) tests
|
|
|
|
TEST(MoveMountTest, MoveMountAttachSuccess) {
|
|
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
|
|
|
|
int fsfd = fsopen(kTmpfs, 0);
|
|
ASSERT_THAT(fsfd, SyscallSucceeds());
|
|
auto cleanup_fs = Cleanup([&]() { close(fsfd); });
|
|
|
|
EXPECT_THAT(fsconfig(fsfd, FSCONFIG_SET_STRING, "source", "my_source", 0),
|
|
SyscallSucceeds());
|
|
EXPECT_THAT(fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0),
|
|
SyscallSucceeds());
|
|
|
|
int mntfd = fsmount(fsfd, 0, 0);
|
|
ASSERT_THAT(mntfd, SyscallSucceeds());
|
|
auto cleanup_mnt = Cleanup([&]() { close(mntfd); });
|
|
|
|
// Pre-move creation: Create files and directories in detached mntfd.
|
|
EXPECT_THAT(mkdirat(mntfd, "pre_move_dir", 0755), SyscallSucceeds());
|
|
int filefd = openat(mntfd, "pre_move_file", O_CREAT | O_RDWR, 0644);
|
|
ASSERT_THAT(filefd, SyscallSucceeds());
|
|
EXPECT_THAT(close(filefd), SyscallSucceeds());
|
|
|
|
// Create a mountpoint on the existing root filesystem
|
|
auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
|
|
|
|
// Move the detached mount onto the new mount directory.
|
|
EXPECT_THAT(move_mount(mntfd, "", AT_FDCWD, dir.path().c_str(),
|
|
MOVE_MOUNT_F_EMPTY_PATH),
|
|
SyscallSucceeds());
|
|
cleanup_mnt.Release()();
|
|
|
|
// Verify that pre-move contents are fully preserved and accessible at target.
|
|
EXPECT_THAT(access(JoinPath(dir.path(), "pre_move_dir").c_str(), F_OK),
|
|
SyscallSucceeds());
|
|
EXPECT_THAT(access(JoinPath(dir.path(), "pre_move_file").c_str(), F_OK),
|
|
SyscallSucceeds());
|
|
|
|
EXPECT_THAT(umount(dir.path().c_str()), SyscallSucceeds());
|
|
}
|
|
|
|
TEST(MoveMountTest, MoveMountFromAttached) {
|
|
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
|
|
|
|
int fsfd = fsopen(kTmpfs, 0);
|
|
ASSERT_THAT(fsfd, SyscallSucceeds());
|
|
auto cleanup_fs = Cleanup([&]() { close(fsfd); });
|
|
|
|
EXPECT_THAT(fsconfig(fsfd, FSCONFIG_SET_STRING, "source", "my_source", 0),
|
|
SyscallSucceeds());
|
|
EXPECT_THAT(fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0),
|
|
SyscallSucceeds());
|
|
|
|
int mntfd = fsmount(fsfd, 0, 0);
|
|
ASSERT_THAT(mntfd, SyscallSucceeds());
|
|
auto cleanup_mnt = Cleanup([&]() { close(mntfd); });
|
|
|
|
auto const dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
|
|
auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
|
|
|
|
// First attach to dir1.
|
|
EXPECT_THAT(move_mount(mntfd, "", AT_FDCWD, dir1.path().c_str(),
|
|
MOVE_MOUNT_F_EMPTY_PATH),
|
|
SyscallSucceeds());
|
|
cleanup_mnt.Release()();
|
|
|
|
// Now move from dir1 to dir2.
|
|
EXPECT_THAT(move_mount(AT_FDCWD, dir1.path().c_str(), AT_FDCWD,
|
|
dir2.path().c_str(), 0),
|
|
SyscallSucceeds());
|
|
|
|
EXPECT_THAT(umount(dir2.path().c_str()), SyscallSucceeds());
|
|
}
|
|
|
|
TEST(MoveMountTest, MoveMountOntoDetached) {
|
|
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
|
|
|
|
// Create first detached mount mntfd1.
|
|
int fsfd1 = fsopen(kTmpfs, 0);
|
|
ASSERT_THAT(fsfd1, SyscallSucceeds());
|
|
auto cleanup_fs1 = Cleanup([&]() { close(fsfd1); });
|
|
EXPECT_THAT(fsconfig(fsfd1, FSCONFIG_SET_STRING, "source", "src1", 0),
|
|
SyscallSucceeds());
|
|
EXPECT_THAT(fsconfig(fsfd1, FSCONFIG_CMD_CREATE, NULL, NULL, 0),
|
|
SyscallSucceeds());
|
|
int mntfd1 = fsmount(fsfd1, 0, 0);
|
|
ASSERT_THAT(mntfd1, SyscallSucceeds());
|
|
auto cleanup_mnt1 = Cleanup([&]() { close(mntfd1); });
|
|
|
|
EXPECT_THAT(mkdirat(mntfd1, "submnt", 0755), SyscallSucceeds());
|
|
|
|
// Create second detached mount mntfd2.
|
|
int fsfd2 = fsopen(kTmpfs, 0);
|
|
ASSERT_THAT(fsfd2, SyscallSucceeds());
|
|
auto cleanup_fs2 = Cleanup([&]() { close(fsfd2); });
|
|
EXPECT_THAT(fsconfig(fsfd2, FSCONFIG_SET_STRING, "source", "src2", 0),
|
|
SyscallSucceeds());
|
|
EXPECT_THAT(fsconfig(fsfd2, FSCONFIG_CMD_CREATE, NULL, NULL, 0),
|
|
SyscallSucceeds());
|
|
int mntfd2 = fsmount(fsfd2, 0, 0);
|
|
ASSERT_THAT(mntfd2, SyscallSucceeds());
|
|
auto cleanup_mnt2 = Cleanup([&]() { close(mntfd2); });
|
|
|
|
// Move mntfd2 onto submnt of mntfd1.
|
|
EXPECT_THAT(move_mount(mntfd2, "", mntfd1, "submnt", MOVE_MOUNT_F_EMPTY_PATH),
|
|
SyscallSucceeds());
|
|
cleanup_mnt2.Release()();
|
|
|
|
auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
|
|
EXPECT_THAT(move_mount(mntfd1, "", AT_FDCWD, dir.path().c_str(),
|
|
MOVE_MOUNT_F_EMPTY_PATH),
|
|
SyscallSucceeds());
|
|
cleanup_mnt1.Release()();
|
|
|
|
EXPECT_THAT(umount(JoinPath(dir.path(), "submnt").c_str()),
|
|
SyscallSucceeds());
|
|
EXPECT_THAT(umount(dir.path().c_str()), SyscallSucceeds());
|
|
}
|
|
|
|
TEST(MoveMountTest, MoveMountSubmountToFs) {
|
|
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
|
|
|
|
// To move a mount out of an anonymous mount namespace, it must be
|
|
// the *root* of that namespace.
|
|
|
|
// Create first detached mount mntfd1.
|
|
int fsfd1 = fsopen(kTmpfs, 0);
|
|
ASSERT_THAT(fsfd1, SyscallSucceeds());
|
|
auto cleanup_fs1 = Cleanup([&]() { close(fsfd1); });
|
|
EXPECT_THAT(fsconfig(fsfd1, FSCONFIG_SET_STRING, "source", "src1", 0),
|
|
SyscallSucceeds());
|
|
EXPECT_THAT(fsconfig(fsfd1, FSCONFIG_CMD_CREATE, NULL, NULL, 0),
|
|
SyscallSucceeds());
|
|
int mntfd1 = fsmount(fsfd1, 0, 0);
|
|
ASSERT_THAT(mntfd1, SyscallSucceeds());
|
|
auto cleanup_mnt1 = Cleanup([&]() { close(mntfd1); });
|
|
|
|
EXPECT_THAT(mkdirat(mntfd1, "submnt", 0755), SyscallSucceeds());
|
|
|
|
// Create second detached mount mntfd2.
|
|
int fsfd2 = fsopen(kTmpfs, 0);
|
|
ASSERT_THAT(fsfd2, SyscallSucceeds());
|
|
auto cleanup_fs2 = Cleanup([&]() { close(fsfd2); });
|
|
EXPECT_THAT(fsconfig(fsfd2, FSCONFIG_SET_STRING, "source", "src2", 0),
|
|
SyscallSucceeds());
|
|
EXPECT_THAT(fsconfig(fsfd2, FSCONFIG_CMD_CREATE, NULL, NULL, 0),
|
|
SyscallSucceeds());
|
|
int mntfd2 = fsmount(fsfd2, 0, 0);
|
|
ASSERT_THAT(mntfd2, SyscallSucceeds());
|
|
auto cleanup_mnt2 = Cleanup([&]() { close(mntfd2); });
|
|
|
|
// Move mntfd2 onto submnt of mntfd1.
|
|
EXPECT_THAT(move_mount(mntfd2, "", mntfd1, "submnt", MOVE_MOUNT_F_EMPTY_PATH),
|
|
SyscallSucceeds());
|
|
cleanup_mnt2.Release()();
|
|
|
|
auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
|
|
|
|
// Attempt to move *only* the submount (submnt of mntfd1) onto the real tree
|
|
// (which should fail).
|
|
EXPECT_THAT(move_mount(mntfd1, "submnt", AT_FDCWD, dir.path().c_str(), 0),
|
|
SyscallFailsWithErrno(EINVAL));
|
|
}
|
|
|
|
TEST(MoveMountTest, MoveMountWithoutEmptyPath) {
|
|
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
|
|
|
|
int fsfd = fsopen(kTmpfs, 0);
|
|
ASSERT_THAT(fsfd, SyscallSucceeds());
|
|
auto cleanup_fs = Cleanup([&]() { close(fsfd); });
|
|
EXPECT_THAT(fsconfig(fsfd, FSCONFIG_SET_STRING, "source", "src", 0),
|
|
SyscallSucceeds());
|
|
EXPECT_THAT(fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0),
|
|
SyscallSucceeds());
|
|
int mntfd = fsmount(fsfd, 0, 0);
|
|
ASSERT_THAT(mntfd, SyscallSucceeds());
|
|
auto cleanup_mnt = Cleanup([&]() { close(mntfd); });
|
|
|
|
auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
|
|
|
|
// Omit MOVE_MOUNT_F_EMPTY_PATH when passing mntfd and "".
|
|
EXPECT_THAT(move_mount(mntfd, "", AT_FDCWD, dir.path().c_str(), 0),
|
|
SyscallFailsWithErrno(ENOENT));
|
|
}
|
|
|
|
TEST(MoveMountTest, MoveMountLoop) {
|
|
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
|
|
|
|
int fsfd = fsopen(kTmpfs, 0);
|
|
ASSERT_THAT(fsfd, SyscallSucceeds());
|
|
auto cleanup_fs = Cleanup([&]() { close(fsfd); });
|
|
EXPECT_THAT(fsconfig(fsfd, FSCONFIG_SET_STRING, "source", "src", 0),
|
|
SyscallSucceeds());
|
|
EXPECT_THAT(fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0),
|
|
SyscallSucceeds());
|
|
int mntfd = fsmount(fsfd, 0, 0);
|
|
ASSERT_THAT(mntfd, SyscallSucceeds());
|
|
auto cleanup_mnt = Cleanup([&]() { close(mntfd); });
|
|
|
|
auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
|
|
|
|
EXPECT_THAT(move_mount(mntfd, "", AT_FDCWD, dir.path().c_str(),
|
|
MOVE_MOUNT_F_EMPTY_PATH),
|
|
SyscallSucceeds());
|
|
cleanup_mnt.Release();
|
|
close(mntfd);
|
|
|
|
EXPECT_THAT(mkdir(JoinPath(dir.path(), "child").c_str(), 0755),
|
|
SyscallSucceeds());
|
|
|
|
// Attempt to move parent mount under its own child directory.
|
|
EXPECT_THAT(move_mount(AT_FDCWD, dir.path().c_str(), AT_FDCWD,
|
|
JoinPath(dir.path(), "child").c_str(), 0),
|
|
SyscallFailsWithErrno(ELOOP));
|
|
|
|
EXPECT_THAT(umount(dir.path().c_str()), SyscallSucceeds());
|
|
}
|
|
|
|
// open_tree(2) tests
|
|
|
|
TEST(OpenTreeTest, OpenTreeNoCloneSuccess) {
|
|
// No capability needed without OPEN_TREE_CLONE.
|
|
auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
|
|
|
|
int fd = open_tree(AT_FDCWD, dir.path().c_str(), 0);
|
|
ASSERT_THAT(fd, SyscallSucceeds());
|
|
auto cleanup = Cleanup([&]() { close(fd); });
|
|
|
|
// Returned fd is a path fd usable as a dirfd.
|
|
struct stat st;
|
|
EXPECT_THAT(fstat(fd, &st), SyscallSucceeds());
|
|
EXPECT_TRUE(S_ISDIR(st.st_mode));
|
|
}
|
|
|
|
TEST(OpenTreeTest, OpenTreeCloneRequiresCapSysAdmin) {
|
|
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
|
|
|
|
auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
|
|
|
|
ScopedThread([&]() {
|
|
EXPECT_NO_ERRNO(SetCapability(CAP_SYS_ADMIN, false));
|
|
EXPECT_THAT(open_tree(AT_FDCWD, dir.path().c_str(), OPEN_TREE_CLONE),
|
|
SyscallFailsWithErrno(EPERM));
|
|
});
|
|
}
|
|
|
|
TEST(OpenTreeTest, OpenTreeRecursiveRequiresClone) {
|
|
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
|
|
|
|
auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
|
|
|
|
EXPECT_THAT(open_tree(AT_FDCWD, dir.path().c_str(), AT_RECURSIVE),
|
|
SyscallFailsWithErrno(EINVAL));
|
|
}
|
|
|
|
TEST(OpenTreeTest, OpenTreeInvalidFlags) {
|
|
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
|
|
|
|
auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
|
|
|
|
// A high bit that isn't a known open_tree flag on Linux or gVisor.
|
|
EXPECT_THAT(open_tree(AT_FDCWD, dir.path().c_str(), 1u << 30),
|
|
SyscallFailsWithErrno(EINVAL));
|
|
}
|
|
|
|
TEST(OpenTreeTest, OpenTreeCloneNonRecursiveOnAttached) {
|
|
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
|
|
|
|
// Create and attach the parent tmpfs via the new mount API.
|
|
int parent_fsfd = fsopen(kTmpfs, 0);
|
|
ASSERT_THAT(parent_fsfd, SyscallSucceeds());
|
|
auto cleanup_parent_fs = Cleanup([&]() { close(parent_fsfd); });
|
|
EXPECT_THAT(fsconfig(parent_fsfd, FSCONFIG_SET_STRING, "source", "parent", 0),
|
|
SyscallSucceeds());
|
|
EXPECT_THAT(fsconfig(parent_fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0),
|
|
SyscallSucceeds());
|
|
int parent_mntfd = fsmount(parent_fsfd, 0, 0);
|
|
ASSERT_THAT(parent_mntfd, SyscallSucceeds());
|
|
auto cleanup_parent_mnt = Cleanup([&]() { close(parent_mntfd); });
|
|
|
|
auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
|
|
EXPECT_THAT(move_mount(parent_mntfd, "", AT_FDCWD, dir.path().c_str(),
|
|
MOVE_MOUNT_F_EMPTY_PATH),
|
|
SyscallSucceeds());
|
|
cleanup_parent_mnt.Release()();
|
|
|
|
// Create the submount mountpoint, then attach a child tmpfs onto it.
|
|
std::string sub_path = JoinPath(dir.path(), "sub");
|
|
ASSERT_THAT(mkdir(sub_path.c_str(), 0755), SyscallSucceeds());
|
|
|
|
int child_fsfd = fsopen(kTmpfs, 0);
|
|
ASSERT_THAT(child_fsfd, SyscallSucceeds());
|
|
auto cleanup_child_fs = Cleanup([&]() { close(child_fsfd); });
|
|
EXPECT_THAT(fsconfig(child_fsfd, FSCONFIG_SET_STRING, "source", "child", 0),
|
|
SyscallSucceeds());
|
|
EXPECT_THAT(fsconfig(child_fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0),
|
|
SyscallSucceeds());
|
|
int child_mntfd = fsmount(child_fsfd, 0, 0);
|
|
ASSERT_THAT(child_mntfd, SyscallSucceeds());
|
|
auto cleanup_child_mnt = Cleanup([&]() { close(child_mntfd); });
|
|
|
|
EXPECT_THAT(move_mount(child_mntfd, "", AT_FDCWD, sub_path.c_str(),
|
|
MOVE_MOUNT_F_EMPTY_PATH),
|
|
SyscallSucceeds());
|
|
cleanup_child_mnt.Release()();
|
|
auto cleanup_child_umount = Cleanup([&]() { umount(sub_path.c_str()); });
|
|
auto cleanup_parent_umount = Cleanup([&]() { umount(dir.path().c_str()); });
|
|
|
|
int markerfd =
|
|
open(JoinPath(sub_path, "marker_sub").c_str(), O_CREAT | O_RDWR, 0644);
|
|
ASSERT_THAT(markerfd, SyscallSucceeds());
|
|
EXPECT_THAT(close(markerfd), SyscallSucceeds());
|
|
|
|
// Clone parent without AT_RECURSIVE: the submount and its contents should
|
|
// not be visible in the clone, though the (now-empty) mountpoint dir is.
|
|
int treefd = open_tree(AT_FDCWD, dir.path().c_str(), OPEN_TREE_CLONE);
|
|
ASSERT_THAT(treefd, SyscallSucceeds());
|
|
auto cleanup_tree = Cleanup([&]() { close(treefd); });
|
|
|
|
EXPECT_THAT(faccessat(treefd, "sub", F_OK, 0), SyscallSucceeds());
|
|
EXPECT_THAT(faccessat(treefd, "sub/marker_sub", F_OK, 0),
|
|
SyscallFailsWithErrno(ENOENT));
|
|
}
|
|
|
|
TEST(OpenTreeTest, OpenTreeCloneFailsOnDetachedMount) {
|
|
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
|
|
|
|
// Create a detached tmpfs mount fd via fsopen/fsmount (no path attachment).
|
|
int fsfd = fsopen(kTmpfs, 0);
|
|
ASSERT_THAT(fsfd, SyscallSucceeds());
|
|
auto cleanup_fs = Cleanup([&]() { close(fsfd); });
|
|
ASSERT_THAT(fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0),
|
|
SyscallSucceeds());
|
|
int mntfd = fsmount(fsfd, 0, 0);
|
|
ASSERT_THAT(mntfd, SyscallSucceeds());
|
|
auto cleanup_mnt = Cleanup([&]() { close(mntfd); });
|
|
|
|
// Attach the mount to a path and then detach it via MNT_DETACH while
|
|
// keeping the mount fd alive.
|
|
const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
|
|
ASSERT_THAT(move_mount(mntfd, "", AT_FDCWD, dir.path().c_str(),
|
|
MOVE_MOUNT_F_EMPTY_PATH),
|
|
SyscallSucceeds());
|
|
ASSERT_THAT(umount2(dir.path().c_str(), MNT_DETACH), SyscallSucceeds());
|
|
|
|
// open_tree(OPEN_TREE_CLONE) on a mount that has already been detached via
|
|
// MNT_DETACH must fail with EINVAL, matching Linux. Regression test for
|
|
// CloneTreeToAnonNS() not checking Mount.umounted (gvisor.dev/issue/13481).
|
|
EXPECT_THAT(open_tree(mntfd, "", AT_EMPTY_PATH | OPEN_TREE_CLONE),
|
|
SyscallFailsWithErrno(EINVAL));
|
|
}
|
|
|
|
TEST(OpenTreeTest, OpenTreeCloneNonRecursiveOnDetached) {
|
|
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
|
|
|
|
// Create the parent tmpfs as a detached mount.
|
|
int parent_fsfd = fsopen(kTmpfs, 0);
|
|
ASSERT_THAT(parent_fsfd, SyscallSucceeds());
|
|
auto cleanup_parent_fs = Cleanup([&]() { close(parent_fsfd); });
|
|
EXPECT_THAT(fsconfig(parent_fsfd, FSCONFIG_SET_STRING, "source", "parent", 0),
|
|
SyscallSucceeds());
|
|
EXPECT_THAT(fsconfig(parent_fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0),
|
|
SyscallSucceeds());
|
|
int parent_mntfd = fsmount(parent_fsfd, 0, 0);
|
|
ASSERT_THAT(parent_mntfd, SyscallSucceeds());
|
|
auto cleanup_parent_mnt = Cleanup([&]() { close(parent_mntfd); });
|
|
|
|
// Create the submount mountpoint inside the detached parent and attach a
|
|
// child tmpfs there (still all detached).
|
|
ASSERT_THAT(mkdirat(parent_mntfd, "sub", 0755), SyscallSucceeds());
|
|
|
|
int child_fsfd = fsopen(kTmpfs, 0);
|
|
ASSERT_THAT(child_fsfd, SyscallSucceeds());
|
|
auto cleanup_child_fs = Cleanup([&]() { close(child_fsfd); });
|
|
EXPECT_THAT(fsconfig(child_fsfd, FSCONFIG_SET_STRING, "source", "child", 0),
|
|
SyscallSucceeds());
|
|
EXPECT_THAT(fsconfig(child_fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0),
|
|
SyscallSucceeds());
|
|
int child_mntfd = fsmount(child_fsfd, 0, 0);
|
|
ASSERT_THAT(child_mntfd, SyscallSucceeds());
|
|
auto cleanup_child_mnt = Cleanup([&]() { close(child_mntfd); });
|
|
|
|
EXPECT_THAT(
|
|
move_mount(child_mntfd, "", parent_mntfd, "sub", MOVE_MOUNT_F_EMPTY_PATH),
|
|
SyscallSucceeds());
|
|
cleanup_child_mnt.Release()();
|
|
|
|
int markerfd = openat(parent_mntfd, "sub/marker_sub", O_CREAT | O_RDWR, 0644);
|
|
ASSERT_THAT(markerfd, SyscallSucceeds());
|
|
EXPECT_THAT(close(markerfd), SyscallSucceeds());
|
|
|
|
// Clone parent without AT_RECURSIVE.
|
|
int treefd = open_tree(parent_mntfd, "", AT_EMPTY_PATH | OPEN_TREE_CLONE);
|
|
ASSERT_THAT(treefd, SyscallSucceeds());
|
|
auto cleanup_tree = Cleanup([&]() { close(treefd); });
|
|
|
|
EXPECT_THAT(faccessat(treefd, "sub", F_OK, 0), SyscallSucceeds());
|
|
EXPECT_THAT(faccessat(treefd, "sub/marker_sub", F_OK, 0),
|
|
SyscallFailsWithErrno(ENOENT));
|
|
}
|
|
|
|
TEST(OpenTreeTest, OpenTreeCloneRecursiveOnAttached) {
|
|
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
|
|
|
|
// Create and attach the parent tmpfs.
|
|
int parent_fsfd = fsopen(kTmpfs, 0);
|
|
ASSERT_THAT(parent_fsfd, SyscallSucceeds());
|
|
auto cleanup_parent_fs = Cleanup([&]() { close(parent_fsfd); });
|
|
EXPECT_THAT(fsconfig(parent_fsfd, FSCONFIG_SET_STRING, "source", "parent", 0),
|
|
SyscallSucceeds());
|
|
EXPECT_THAT(fsconfig(parent_fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0),
|
|
SyscallSucceeds());
|
|
int parent_mntfd = fsmount(parent_fsfd, 0, 0);
|
|
ASSERT_THAT(parent_mntfd, SyscallSucceeds());
|
|
auto cleanup_parent_mnt = Cleanup([&]() { close(parent_mntfd); });
|
|
|
|
auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
|
|
EXPECT_THAT(move_mount(parent_mntfd, "", AT_FDCWD, dir.path().c_str(),
|
|
MOVE_MOUNT_F_EMPTY_PATH),
|
|
SyscallSucceeds());
|
|
cleanup_parent_mnt.Release()();
|
|
|
|
// Mount a child tmpfs at parent/sub.
|
|
std::string sub_path = JoinPath(dir.path(), "sub");
|
|
ASSERT_THAT(mkdir(sub_path.c_str(), 0755), SyscallSucceeds());
|
|
|
|
int child_fsfd = fsopen(kTmpfs, 0);
|
|
ASSERT_THAT(child_fsfd, SyscallSucceeds());
|
|
auto cleanup_child_fs = Cleanup([&]() { close(child_fsfd); });
|
|
EXPECT_THAT(fsconfig(child_fsfd, FSCONFIG_SET_STRING, "source", "child", 0),
|
|
SyscallSucceeds());
|
|
EXPECT_THAT(fsconfig(child_fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0),
|
|
SyscallSucceeds());
|
|
int child_mntfd = fsmount(child_fsfd, 0, 0);
|
|
ASSERT_THAT(child_mntfd, SyscallSucceeds());
|
|
auto cleanup_child_mnt = Cleanup([&]() { close(child_mntfd); });
|
|
|
|
EXPECT_THAT(move_mount(child_mntfd, "", AT_FDCWD, sub_path.c_str(),
|
|
MOVE_MOUNT_F_EMPTY_PATH),
|
|
SyscallSucceeds());
|
|
cleanup_child_mnt.Release()();
|
|
auto cleanup_child_umount = Cleanup([&]() { umount(sub_path.c_str()); });
|
|
auto cleanup_parent_umount = Cleanup([&]() { umount(dir.path().c_str()); });
|
|
|
|
int markerfd =
|
|
open(JoinPath(sub_path, "marker_sub").c_str(), O_CREAT | O_RDWR, 0644);
|
|
ASSERT_THAT(markerfd, SyscallSucceeds());
|
|
EXPECT_THAT(close(markerfd), SyscallSucceeds());
|
|
|
|
// Clone parent with AT_RECURSIVE: the submount is carried along.
|
|
int treefd =
|
|
open_tree(AT_FDCWD, dir.path().c_str(), OPEN_TREE_CLONE | AT_RECURSIVE);
|
|
ASSERT_THAT(treefd, SyscallSucceeds());
|
|
auto cleanup_tree = Cleanup([&]() { close(treefd); });
|
|
|
|
EXPECT_THAT(faccessat(treefd, "sub/marker_sub", F_OK, 0), SyscallSucceeds());
|
|
|
|
// The cloned tree is itself a detached mount; verify it can be attached
|
|
// via move_mount, with the submount still present at the new location.
|
|
auto const dst_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
|
|
EXPECT_THAT(move_mount(treefd, "", AT_FDCWD, dst_dir.path().c_str(),
|
|
MOVE_MOUNT_F_EMPTY_PATH),
|
|
SyscallSucceeds());
|
|
cleanup_tree.Release()();
|
|
EXPECT_THAT(access(JoinPath(dst_dir.path(), "sub/marker_sub").c_str(), F_OK),
|
|
SyscallSucceeds());
|
|
EXPECT_THAT(umount(JoinPath(dst_dir.path(), "sub").c_str()),
|
|
SyscallSucceeds());
|
|
EXPECT_THAT(umount(dst_dir.path().c_str()), SyscallSucceeds());
|
|
}
|
|
|
|
TEST(OpenTreeTest, OpenTreeCloneRecursiveOnDetached) {
|
|
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
|
|
|
|
// Create the parent tmpfs as a detached mount.
|
|
int parent_fsfd = fsopen(kTmpfs, 0);
|
|
ASSERT_THAT(parent_fsfd, SyscallSucceeds());
|
|
auto cleanup_parent_fs = Cleanup([&]() { close(parent_fsfd); });
|
|
EXPECT_THAT(fsconfig(parent_fsfd, FSCONFIG_SET_STRING, "source", "parent", 0),
|
|
SyscallSucceeds());
|
|
EXPECT_THAT(fsconfig(parent_fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0),
|
|
SyscallSucceeds());
|
|
int parent_mntfd = fsmount(parent_fsfd, 0, 0);
|
|
ASSERT_THAT(parent_mntfd, SyscallSucceeds());
|
|
auto cleanup_parent_mnt = Cleanup([&]() { close(parent_mntfd); });
|
|
|
|
ASSERT_THAT(mkdirat(parent_mntfd, "sub", 0755), SyscallSucceeds());
|
|
|
|
int child_fsfd = fsopen(kTmpfs, 0);
|
|
ASSERT_THAT(child_fsfd, SyscallSucceeds());
|
|
auto cleanup_child_fs = Cleanup([&]() { close(child_fsfd); });
|
|
EXPECT_THAT(fsconfig(child_fsfd, FSCONFIG_SET_STRING, "source", "child", 0),
|
|
SyscallSucceeds());
|
|
EXPECT_THAT(fsconfig(child_fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0),
|
|
SyscallSucceeds());
|
|
int child_mntfd = fsmount(child_fsfd, 0, 0);
|
|
ASSERT_THAT(child_mntfd, SyscallSucceeds());
|
|
auto cleanup_child_mnt = Cleanup([&]() { close(child_mntfd); });
|
|
|
|
EXPECT_THAT(
|
|
move_mount(child_mntfd, "", parent_mntfd, "sub", MOVE_MOUNT_F_EMPTY_PATH),
|
|
SyscallSucceeds());
|
|
cleanup_child_mnt.Release()();
|
|
|
|
int markerfd = openat(parent_mntfd, "sub/marker_sub", O_CREAT | O_RDWR, 0644);
|
|
ASSERT_THAT(markerfd, SyscallSucceeds());
|
|
EXPECT_THAT(close(markerfd), SyscallSucceeds());
|
|
|
|
// Clone parent with AT_RECURSIVE on the detached mountfd.
|
|
int treefd = open_tree(parent_mntfd, "",
|
|
AT_EMPTY_PATH | OPEN_TREE_CLONE | AT_RECURSIVE);
|
|
ASSERT_THAT(treefd, SyscallSucceeds());
|
|
auto cleanup_tree = Cleanup([&]() { close(treefd); });
|
|
|
|
EXPECT_THAT(faccessat(treefd, "sub/marker_sub", F_OK, 0), SyscallSucceeds());
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace testing
|
|
} // namespace gvisor
|