// Copyright 2018 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <errno.h>
#include <fcntl.h>
#include <linux/capability.h>
#include <linux/magic.h>
#include <sched.h>
#include <stdio.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/eventfd.h>
#include <sys/mman.h>
#include <sys/mount.h>
#include <sys/resource.h>
#include <sys/signalfd.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/statfs.h>
#include <sys/sysmacros.h>
#include <sys/un.h>
#include <sys/vfs.h>
#include <sys/xattr.h>
#include <unistd.h>

#include <array>
#include <cerrno>
#include <cmath>
#include <cstdint>
#include <cstdlib>
#include <functional>
#include <iostream>
#include <memory>
#include <ostream>
#include <string>
#include <tuple>
#include <utility>
#include <vector>

#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"
#include "absl/strings/str_format.h"
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"
#include "absl/strings/substitute.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "test/util/capability_util.h"
#include "test/util/cleanup.h"
#include "test/util/eventfd_util.h"
#include "test/util/file_descriptor.h"
#include "test/util/fs_util.h"
#include "test/util/linux_capability_util.h"
#include "test/util/logging.h"
#include "test/util/memory_util.h"
#include "test/util/mount_util.h"
#include "test/util/multiprocess_util.h"
#include "test/util/posix_error.h"
#include "test/util/save_util.h"
#include "test/util/temp_path.h"
#include "test/util/test_util.h"
#include "test/util/thread_util.h"

namespace gvisor {
namespace testing {

namespace {

using ::testing::AnyOf;
using ::testing::Contains;
using ::testing::Pair;

constexpr char kTmpfs[] = "tmpfs";

TEST(MountTest, MountBadFilesystem) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  // Linux expects a valid target before it checks the file system name.
  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  EXPECT_THAT(mount("", dir.path().c_str(), "foobar", 0, ""),
              SyscallFailsWithErrno(ENODEV));
}

TEST(MountTest, MountInvalidTarget) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto const dir = NewTempAbsPath();
  EXPECT_THAT(mount("", dir.c_str(), kTmpfs, 0, ""),
              SyscallFailsWithErrno(ENOENT));
}

TEST(MountTest, MountPermDenied) {
  // Clear CAP_SYS_ADMIN.
  AutoCapability cap(CAP_SYS_ADMIN, false);

  // Linux expects a valid target before checking capability.
  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  EXPECT_THAT(mount("", dir.path().c_str(), "", 0, ""),
              SyscallFailsWithErrno(EPERM));
}

TEST(MountTest, UmountPermDenied) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const mount =
      ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), kTmpfs, 0, "", 0));

  // Drop privileges in another thread, so we can still unmount the mounted
  // directory.
  ScopedThread([&]() {
    EXPECT_NO_ERRNO(SetCapability(CAP_SYS_ADMIN, false));
    EXPECT_THAT(umount(dir.path().c_str()), SyscallFailsWithErrno(EPERM));
  });
}

TEST(MountTest, MountOverBusy) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const fd = ASSERT_NO_ERRNO_AND_VALUE(
      Open(JoinPath(dir.path(), "foo"), O_CREAT | O_RDWR, 0777));

  // Should be able to mount over a busy directory.
  ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), kTmpfs, 0, "", 0));
}

TEST(MountTest, OpenFileBusy) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const mount = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", dir.path(), kTmpfs, 0, "mode=0700", 0));
  auto const fd = ASSERT_NO_ERRNO_AND_VALUE(
      Open(JoinPath(dir.path(), "foo"), O_CREAT | O_RDWR, 0777));

  // An open file should prevent unmounting.
  EXPECT_THAT(umount(dir.path().c_str()), SyscallFailsWithErrno(EBUSY));
}

TEST(MountTest, UmountNoFollow) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());

  auto const mountPoint = NewTempAbsPathInDir(dir.path());
  ASSERT_THAT(mkdir(mountPoint.c_str(), 0777), SyscallSucceeds());

  // Create a symlink in dir which will point to the actual mountpoint.
  const std::string symlinkInDir = NewTempAbsPathInDir(dir.path());
  EXPECT_THAT(symlink(mountPoint.c_str(), symlinkInDir.c_str()),
              SyscallSucceeds());

  // Create a symlink to the dir.
  const std::string symlinkToDir = NewTempAbsPath();
  EXPECT_THAT(symlink(dir.path().c_str(), symlinkToDir.c_str()),
              SyscallSucceeds());

  // Should fail with ELOOP when UMOUNT_NOFOLLOW is specified and the last
  // component is a symlink.
  auto mount = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", mountPoint, kTmpfs, 0, "mode=0700", 0));
  EXPECT_THAT(umount2(symlinkInDir.c_str(), UMOUNT_NOFOLLOW),
              SyscallFailsWithErrno(EINVAL));
  EXPECT_THAT(unlink(symlinkInDir.c_str()), SyscallSucceeds());

  // UMOUNT_NOFOLLOW should only apply to the last path component. A symlink in
  // non-last path component should be just fine.
  EXPECT_THAT(umount2(JoinPath(symlinkToDir, Basename(mountPoint)).c_str(),
                      UMOUNT_NOFOLLOW),
              SyscallSucceeds());
  mount.Release();
}

TEST(MountTest, UmountDetach) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  // structure:
  //
  // dir (mount point)
  //   subdir
  //   file
  //
  // We show that we can walk around in the mount after detach-unmount dir.
  //
  // We show that even though dir is unreachable from outside the mount, we can
  // still reach dir's (former) parent!
  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());

  const struct stat before = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path()));
  auto mount =
      ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), kTmpfs, 0, "mode=0700",
                                      /* umountflags= */ MNT_DETACH));
  const struct stat after = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path()));
  EXPECT_FALSE(before.st_dev == after.st_dev && before.st_ino == after.st_ino)
      << "mount point has device number " << before.st_dev
      << " and inode number " << before.st_ino << " before and after mount";

  // Create files in the new mount.
  constexpr char kContents[] = "no no no";
  auto const subdir =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir.path()));
  auto const file = ASSERT_NO_ERRNO_AND_VALUE(
      TempPath::CreateFileWith(dir.path(), kContents, 0777));

  auto const dir_fd =
      ASSERT_NO_ERRNO_AND_VALUE(Open(subdir.path(), O_RDONLY | O_DIRECTORY));
  auto const fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY));

  // Unmount the tmpfs.
  mount.Release()();

  // Inode numbers for gofer-accessed files may change across save/restore.
  //
  // For overlayfs, if xino option is not enabled and if all overlayfs layers do
  // not belong to the same filesystem then "the value of st_ino for directory
  // objects may not be persistent and could change even while the overlay
  // filesystem is mounted."  -- Documentation/filesystems/overlayfs.txt
  if (!IsRunningWithSaveRestore() &&
      !ASSERT_NO_ERRNO_AND_VALUE(IsOverlayfs(dir.path()))) {
    const struct stat after2 = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path()));
    EXPECT_EQ(before.st_ino, after2.st_ino);
  }

  // Can still read file after unmounting.
  std::vector<char> buf(sizeof(kContents));
  EXPECT_THAT(ReadFd(fd.get(), buf.data(), buf.size()), SyscallSucceeds());

  // Walk to dir.
  auto const mounted_dir = ASSERT_NO_ERRNO_AND_VALUE(
      OpenAt(dir_fd.get(), "..", O_DIRECTORY | O_RDONLY));
  // Walk to dir/file.
  auto const fd_again = ASSERT_NO_ERRNO_AND_VALUE(
      OpenAt(mounted_dir.get(), std::string(Basename(file.path())), O_RDONLY));

  std::vector<char> buf2(sizeof(kContents));
  EXPECT_THAT(ReadFd(fd_again.get(), buf2.data(), buf2.size()),
              SyscallSucceeds());
  EXPECT_EQ(buf, buf2);

  // Walking outside the unmounted realm should still work, too!
  auto const dir_parent = ASSERT_NO_ERRNO_AND_VALUE(
      OpenAt(mounted_dir.get(), "..", O_DIRECTORY | O_RDONLY));
}

TEST(MountTest, MMapWithExecProtFailsOnNoExecFile) {
  // Skips the test if test does not have needed capability to create the volume
  // mount.
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto ret = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", dir.path(), kTmpfs, MS_NOEXEC, "", 0));
  auto file = ASSERT_NO_ERRNO_AND_VALUE(
      TempPath::CreateFileWith(dir.path(), "random1", 0777));

  FileDescriptor fd =
      ASSERT_NO_ERRNO_AND_VALUE(Open(file.path().c_str(), O_RDWR));
  ASSERT_THAT(reinterpret_cast<uintptr_t>(
                  mmap(0, kPageSize, PROT_EXEC, MAP_PRIVATE, fd.get(), 0)),
              SyscallFailsWithErrno(EPERM));
}

TEST(MountTest, MMapWithExecProtSucceedsOnExecutableVolumeFile) {
  // Capability is needed to create tmpfs.
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto ret = ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), kTmpfs, 0, "", 0));
  auto file = ASSERT_NO_ERRNO_AND_VALUE(
      TempPath::CreateFileWith(dir.path(), "random1", 0777));

  FileDescriptor fd =
      ASSERT_NO_ERRNO_AND_VALUE(Open(file.path().c_str(), O_RDWR));

  void* address = mmap(0, kPageSize, PROT_EXEC, MAP_PRIVATE, fd.get(), 0);
  EXPECT_NE(address, MAP_FAILED);

  MunmapSafe(address, kPageSize);
}

TEST(MountTest, MMapWithoutNoExecProtSucceedsOnNoExecFile) {
  // Capability is needed to create tmpfs.
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto ret = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", dir.path(), kTmpfs, MS_NOEXEC, "", 0));
  auto file = ASSERT_NO_ERRNO_AND_VALUE(
      TempPath::CreateFileWith(dir.path(), "random1", 0777));
  FileDescriptor fd =
      ASSERT_NO_ERRNO_AND_VALUE(Open(file.path().c_str(), O_RDWR));

  void* address =
      mmap(0, kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd.get(), 0);
  EXPECT_NE(address, MAP_FAILED);

  MunmapSafe(address, kPageSize);
}

TEST(MountTest, MProtectWithNoExecProtFailsOnNoExecFile) {
  // Capability is needed to create tmpfs.
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto ret = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", dir.path(), kTmpfs, MS_NOEXEC, "", 0));
  auto file = ASSERT_NO_ERRNO_AND_VALUE(
      TempPath::CreateFileWith(dir.path(), "random1", 0777));
  FileDescriptor fd =
      ASSERT_NO_ERRNO_AND_VALUE(Open(file.path().c_str(), O_RDWR));

  void* address =
      mmap(0, kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd.get(), 0);
  EXPECT_NE(address, MAP_FAILED);

  ASSERT_THAT(mprotect(address, kPageSize, PROT_EXEC),
              SyscallFailsWithErrno(EACCES));

  MunmapSafe(address, kPageSize);
}

TEST(MountTest, UmountMountsStackedOnDot) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  // Verify that unmounting at "." properly unmounts the mount at the top of
  // mount stack.
  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  TEST_CHECK_SUCCESS(chdir(dir.path().c_str()));
  const struct stat before = ASSERT_NO_ERRNO_AND_VALUE(Stat("."));

  TEST_CHECK_SUCCESS(mount("", dir.path().c_str(), kTmpfs, 0, "mode=0700"));
  TEST_CHECK_SUCCESS(mount("", dir.path().c_str(), kTmpfs, 0, "mode=0700"));

  // Unmount the second mount at "."
  TEST_CHECK_SUCCESS(umount2(".", MNT_DETACH));

  // Unmount the first mount at "."; this will fail if umount does not resolve
  // "." to the topmost mount.
  TEST_CHECK_SUCCESS(umount2(".", MNT_DETACH));
  const struct stat after2 = ASSERT_NO_ERRNO_AND_VALUE(Stat("."));
  EXPECT_TRUE(before.st_dev == after2.st_dev && before.st_ino == after2.st_ino);
}

TEST(MountTest, ActiveSubmountBusy) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const mount1 = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", dir.path(), kTmpfs, 0, "mode=0700", 0));

  auto const dir2 =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir.path()));
  auto const mount2 =
      ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir2.path(), kTmpfs, 0, "", 0));

  // Since dir now has an active submount, should not be able to unmount.
  EXPECT_THAT(umount(dir.path().c_str()), SyscallFailsWithErrno(EBUSY));
}

TEST(MountTest, MountTmpfs) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());

  // NOTE(b/129868551): Inode IDs are only stable across S/R if we have an open
  // FD for that inode. Since we are going to compare inode IDs below, get a
  // FileDescriptor for this directory here, which will be closed automatically
  // at the end of the test.
  auto const fd =
      ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_DIRECTORY, O_RDONLY));

  const struct stat before = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path()));

  {
    auto const mount = ASSERT_NO_ERRNO_AND_VALUE(
        Mount("", dir.path(), kTmpfs, 0, "mode=0700", 0));

    const struct stat s = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path()));
    EXPECT_EQ(s.st_mode, S_IFDIR | 0700);
    EXPECT_FALSE(before.st_dev == s.st_dev && before.st_ino == s.st_ino)
        << "mount point has device number " << before.st_dev
        << " and inode number " << before.st_ino << " before and after mount";

    EXPECT_NO_ERRNO(Open(JoinPath(dir.path(), "foo"), O_CREAT | O_RDWR, 0777));
  }

  // Now that dir is unmounted again, we should have the old inode back.
  //
  // Inode numbers for gofer-accessed files may change across save/restore.
  //
  // For overlayfs, if xino option is not enabled and if all overlayfs layers do
  // not belong to the same filesystem then "the value of st_ino for directory
  // objects may not be persistent and could change even while the overlay
  // filesystem is mounted."  -- Documentation/filesystems/overlayfs.txt
  if (!IsRunningWithSaveRestore() &&
      !ASSERT_NO_ERRNO_AND_VALUE(IsOverlayfs(dir.path()))) {
    const struct stat after = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path()));
    EXPECT_EQ(before.st_ino, after.st_ino);
  }
}

TEST(MountTest, MountTmpfsMagicValIgnored) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());

  auto const mount = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", dir.path(), kTmpfs, MS_MGC_VAL, "mode=0700", 0));
}

// Passing nullptr to data is equivalent to "".
TEST(MountTest, NullData) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());

  EXPECT_THAT(mount("", dir.path().c_str(), kTmpfs, 0, nullptr),
              SyscallSucceeds());
  EXPECT_THAT(umount2(dir.path().c_str(), 0), SyscallSucceeds());
}

TEST(MountTest, MountReadonly) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const mount = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", dir.path(), kTmpfs, MS_RDONLY, "mode=0777", 0));

  const struct stat s = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path()));
  EXPECT_EQ(s.st_mode, S_IFDIR | 0777);

  EXPECT_THAT(access(dir.path().c_str(), W_OK), SyscallFailsWithErrno(EROFS));

  std::string const filename = JoinPath(dir.path(), "foo");
  EXPECT_THAT(open(filename.c_str(), O_RDWR | O_CREAT, 0777),
              SyscallFailsWithErrno(EROFS));
}

TEST(MountTest, BindMountReadonly) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const bindDir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const mnt = ASSERT_NO_ERRNO_AND_VALUE(
      Mount(dir.path(), bindDir.path(), "", MS_BIND, "", 0));

  std::string const filename = JoinPath(bindDir.path(), "foo");
  FileDescriptor fd =
      ASSERT_NO_ERRNO_AND_VALUE(Open(filename, O_RDWR | O_CREAT, 0644));
  char msg[] = "hello world";
  EXPECT_THAT(pwrite64(fd.get(), msg, strlen(msg), 0),
              SyscallSucceedsWithValue(strlen(msg)));
  fd.reset();
  EXPECT_THAT(mount(dir.path().c_str(), bindDir.path().c_str(), NULL,
                    MS_BIND | MS_RDONLY | MS_REMOUNT, NULL),
              SyscallSucceeds());

  EXPECT_THAT(access(bindDir.path().c_str(), W_OK),
              SyscallFailsWithErrno(EROFS));

  EXPECT_THAT(open(filename.c_str(), O_RDWR), SyscallFailsWithErrno(EROFS));
  EXPECT_THAT(open(filename.c_str(), O_RDWR | O_TRUNC),
              SyscallFailsWithErrno(EROFS));
  EXPECT_THAT(open(filename.c_str(), O_RDONLY | O_TRUNC),
              SyscallFailsWithErrno(EROFS));
  const struct stat s = ASSERT_NO_ERRNO_AND_VALUE(Stat(filename));
  EXPECT_EQ(s.st_size, strlen(msg));
}

// Special files (such as FIFO) should be openable for writing even on a
// read-only mount. Regular files should not.
TEST(MountTest, FifoWritableOnReadonlyMount) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const mnt =
      ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), kTmpfs, 0, "", 0));

  // Populate the still-writable mount with a FIFO and a regular file.
  std::string const fifo_path = JoinPath(dir.path(), "fifo");
  std::string const reg_path = JoinPath(dir.path(), "reg");
  ASSERT_THAT(mkfifo(fifo_path.c_str(), 0666), SyscallSucceeds());
  FileDescriptor reg_fd =
      ASSERT_NO_ERRNO_AND_VALUE(Open(reg_path, O_WRONLY | O_CREAT, 0644));
  reg_fd.reset();

  // Remount read-only.
  ASSERT_THAT(
      mount("", dir.path().c_str(), nullptr, MS_REMOUNT | MS_RDONLY, nullptr),
      SyscallSucceeds());
  ASSERT_THAT(access(dir.path().c_str(), W_OK), SyscallFailsWithErrno(EROFS));

  // The FIFO opens writably despite the RO mount.
  FileDescriptor fifo_fd = ASSERT_NO_ERRNO_AND_VALUE(Open(fifo_path, O_RDWR));
  EXPECT_THAT(write(fifo_fd.get(), "x", 1), SyscallSucceedsWithValue(1));

  // The regular file still fails a writable open with EROFS, but a read-only
  // open succeeds.
  EXPECT_THAT(open(reg_path.c_str(), O_WRONLY), SyscallFailsWithErrno(EROFS));
  ASSERT_NO_ERRNO_AND_VALUE(Open(reg_path, O_RDONLY));
}

// Same test as above, but for a character device.
TEST(MountTest, DeviceFileWritableOnReadonlyMount) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const mnt =
      ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), kTmpfs, 0, "", 0));

  // Create a /dev/null-style character device while the mount is writable.
  std::string const dev_path = JoinPath(dir.path(), "null");
  int ret = mknod(dev_path.c_str(), S_IFCHR | 0666, makedev(1, 3));
  const bool was_eperm = Value(ret, SyscallFailsWithErrno(EPERM));

  // In our test infrastructure, this may not always be possible to test on
  // native since CAP_MKNOD is required in the *initial* user ns, and native
  // tests are sometimes run in a user ns.
  SKIP_IF(was_eperm);

  // (But otherwise, mknod() should succeed.)
  ASSERT_THAT(ret, SyscallSucceeds());

  // Remount read-only.
  ASSERT_THAT(
      mount("", dir.path().c_str(), nullptr, MS_REMOUNT | MS_RDONLY, nullptr),
      SyscallSucceeds());

  // The device node opens writably and accepts writes despite the RO mount.
  FileDescriptor dev_fd = ASSERT_NO_ERRNO_AND_VALUE(Open(dev_path, O_WRONLY));
  EXPECT_THAT(write(dev_fd.get(), "x", 1), SyscallSucceedsWithValue(1));
}

TEST(MountTest, ReadOnlyBindMountAllowsWriteOpenOfCharDevice) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  std::string const nullPath = JoinPath(dir.path(), "null");
  FileDescriptor create_fd =
      ASSERT_NO_ERRNO_AND_VALUE(Open(nullPath, O_CREAT | O_RDWR, 0666));
  create_fd.reset();

  auto const mnt = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("/dev/null", nullPath, "", MS_BIND, "", 0));
  ASSERT_THAT(mount("/dev/null", nullPath.c_str(), nullptr,
                    MS_BIND | MS_REMOUNT | MS_RDONLY, nullptr),
              SyscallSucceeds());

  FileDescriptor wfd = ASSERT_NO_ERRNO_AND_VALUE(Open(nullPath, O_WRONLY));
  char msg[] = "hello";
  EXPECT_THAT(write(wfd.get(), msg, sizeof(msg)),
              SyscallSucceedsWithValue(sizeof(msg)));

  FileDescriptor rwfd = ASSERT_NO_ERRNO_AND_VALUE(Open(nullPath, O_RDWR));
  EXPECT_THAT(write(rwfd.get(), msg, sizeof(msg)),
              SyscallSucceedsWithValue(sizeof(msg)));
}

TEST(MountTest, ReadOnlyBindMountAllowsWriteOpenOfFIFO) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  std::string const fifoPath = JoinPath(dir.path(), "fifo");
  ASSERT_THAT(mkfifo(fifoPath.c_str(), 0666), SyscallSucceeds());

  auto const bindDir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const mnt = ASSERT_NO_ERRNO_AND_VALUE(
      Mount(dir.path(), bindDir.path(), "", MS_BIND, "", 0));
  ASSERT_THAT(mount(dir.path().c_str(), bindDir.path().c_str(), nullptr,
                    MS_BIND | MS_REMOUNT | MS_RDONLY, nullptr),
              SyscallSucceeds());

  std::string const boundFifoPath = JoinPath(bindDir.path(), "fifo");
  FileDescriptor rfd =
      ASSERT_NO_ERRNO_AND_VALUE(Open(boundFifoPath, O_RDONLY | O_NONBLOCK));
  FileDescriptor wfd =
      ASSERT_NO_ERRNO_AND_VALUE(Open(boundFifoPath, O_WRONLY | O_NONBLOCK));
  char msg[] = "hello";
  EXPECT_THAT(write(wfd.get(), msg, sizeof(msg)),
              SyscallSucceedsWithValue(sizeof(msg)));
}

// Test that bind mounting a directory onto a regular file fails with ENOTDIR.
TEST(MountTest, BindMountDirectoryOntoFileFails) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const source_dir =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir.path()));
  auto const target_file =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path()));

  // Bind mounting a directory onto a file should fail with ENOTDIR.
  EXPECT_THAT(mount(source_dir.path().c_str(), target_file.path().c_str(),
                    nullptr, MS_BIND, nullptr),
              SyscallFailsWithErrno(ENOTDIR));
}

// Test that bind mounting a regular file onto a directory fails with ENOTDIR.
TEST(MountTest, BindMountFileOntoDirectoryFails) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const source_file =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path()));
  auto const target_dir =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir.path()));

  // Bind mounting a file onto a directory should fail with ENOTDIR.
  EXPECT_THAT(mount(source_file.path().c_str(), target_dir.path().c_str(),
                    nullptr, MS_BIND, nullptr),
              SyscallFailsWithErrno(ENOTDIR));
}

PosixErrorOr<absl::Time> ATime(absl::string_view file) {
  struct stat s = {};
  if (stat(std::string(file).c_str(), &s) == -1) {
    return PosixError(errno, "stat failed");
  }
  return absl::TimeFromTimespec(s.st_atim);
}

TEST(MountTest, MountNoAtime) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const mount = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", dir.path(), kTmpfs, MS_NOATIME, "mode=0777", 0));

  std::string const contents = "No no no, don't follow the instructions!";
  auto const file = ASSERT_NO_ERRNO_AND_VALUE(
      TempPath::CreateFileWith(dir.path(), contents, 0777));

  absl::Time const before = ASSERT_NO_ERRNO_AND_VALUE(ATime(file.path()));

  // Reading from the file should change the atime, but the MS_NOATIME flag
  // should prevent that.
  auto const fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR));
  char buf[100];
  int read_n;
  ASSERT_THAT(read_n = read(fd.get(), buf, sizeof(buf)), SyscallSucceeds());
  EXPECT_EQ(std::string(buf, read_n), contents);

  absl::Time const after = ASSERT_NO_ERRNO_AND_VALUE(ATime(file.path()));

  // Expect that atime hasn't changed.
  EXPECT_EQ(before, after);
}

TEST(MountTest, MountWithStrictAtime) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const mount = ASSERT_NO_ERRNO_AND_VALUE(Mount(
      "", dir.path(), kTmpfs, MS_NOATIME | MS_STRICTATIME, "mode=0777", 0));

  std::string const contents = "No no no, don't follow the instructions!";
  auto const file = ASSERT_NO_ERRNO_AND_VALUE(
      TempPath::CreateFileWith(dir.path(), contents, 0777));

  absl::Time const before = ASSERT_NO_ERRNO_AND_VALUE(ATime(file.path()));

  absl::SleepFor(absl::Milliseconds(100));

  // MS_STRICTATIME should override MS_NOATIME and update the file's atime.
  auto const fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR));
  char buf[100];
  int read_n;
  ASSERT_THAT(read_n = read(fd.get(), buf, sizeof(buf)), SyscallSucceeds());
  EXPECT_EQ(std::string(buf, read_n), contents);

  absl::Time const after = ASSERT_NO_ERRNO_AND_VALUE(ATime(file.path()));

  // The after atime is expected to be larger than the before atime.
  EXPECT_LT(before, after);
}

TEST(MountTest, MountNoExec) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const mount = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", dir.path(), kTmpfs, MS_NOEXEC, "mode=0777", 0));

  std::string const contents = "No no no, don't follow the instructions!";
  auto const file = ASSERT_NO_ERRNO_AND_VALUE(
      TempPath::CreateFileWith(dir.path(), contents, 0777));

  int execve_errno;
  ASSERT_NO_ERRNO_AND_VALUE(
      ForkAndExec(file.path(), {}, {}, nullptr, &execve_errno));
  EXPECT_EQ(execve_errno, EACCES);
}

TEST(MountTest, RenameRemoveMountPoint) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto const dir_parent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const dir =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir_parent.path()));
  auto const new_dir = NewTempAbsPath();

  auto const mount =
      ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), kTmpfs, 0, "", 0));

  ASSERT_THAT(rename(dir.path().c_str(), new_dir.c_str()),
              SyscallFailsWithErrno(EBUSY));

  ASSERT_THAT(rmdir(dir.path().c_str()), SyscallFailsWithErrno(EBUSY));
}

TEST(MountTest, MountMoveSuccess) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto const privateParent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto privateMount = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("source", privateParent.path(), kTmpfs, 0, "", 0));
  EXPECT_THAT(mount("", privateParent.path().c_str(), "", MS_PRIVATE, ""),
              SyscallSucceeds());

  auto const dir1 =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(privateParent.path()));
  auto const dir2 =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(privateParent.path()));
  auto mount1 =
      ASSERT_NO_ERRNO_AND_VALUE(Mount("source", dir1.path(), kTmpfs, 0, "", 0));

  auto fd = ASSERT_NO_ERRNO_AND_VALUE(
      Open(JoinPath(dir1.path(), "foo").c_str(), O_CREAT | O_RDWR, 0777));
  EXPECT_THAT(close(fd.release()), SyscallSucceeds());

  auto const mount2 = ASSERT_NO_ERRNO_AND_VALUE(
      Mount(dir1.path(), dir2.path(), "", MS_MOVE, "", 0));
  mount1.Release();

  EXPECT_THAT(Exists(JoinPath(dir1.path(), "foo")),
              IsPosixErrorOkAndHolds(false));
  EXPECT_THAT(Exists(JoinPath(dir2.path(), "foo")),
              IsPosixErrorOkAndHolds(true));
}

// TODO(b/305893463): When support for MS_UNBINDABLE is added, moving a mount
// tree with unbindable children to an MS_SHARED destination should EINVAL.

TEST(MountTest, MountMoveSharedParent) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto const parentDir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());

  auto parentMount = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("source", parentDir1.path(), kTmpfs, 0, "", 0));
  EXPECT_THAT(mount("", parentDir1.path().c_str(), "", MS_SHARED, ""),
              SyscallSucceeds());

  auto const subDir =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(parentDir1.path()));
  auto subMount = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("source", subDir.path(), kTmpfs, 0, "", 0));

  EXPECT_THAT(
      mount(subDir.path().c_str(), dir2.path().c_str(), "", MS_MOVE, nullptr),
      SyscallFailsWithErrno(EINVAL));
}

TEST(MountTest, MountMoveSourceNotMounted) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto const dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());

  auto fd = ASSERT_NO_ERRNO_AND_VALUE(
      Open(JoinPath(dir1.path(), "foo").c_str(), O_CREAT | O_RDWR, 0777));
  EXPECT_THAT(close(fd.release()), SyscallSucceeds());

  EXPECT_THAT(mount(dir1.path().c_str(), dir2.path().c_str(), "", MS_MOVE, ""),
              SyscallFailsWithErrno(EINVAL));
}

TEST(MountTest, MountInfo) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const mount = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", dir.path(), kTmpfs, MS_NOEXEC, "mode=0123", 0));
  const std::vector<ProcMountsEntry> mounts =
      ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountsEntries());
  for (const auto& e : mounts) {
    if (e.mount_point == dir.path()) {
      EXPECT_EQ(e.fstype, kTmpfs);
      auto mopts = ParseMountOptions(e.mount_opts);
      EXPECT_THAT(mopts, AnyOf(Contains(Pair("mode", "0123")),
                               Contains(Pair("mode", "123"))));
    }
  }

  const std::vector<ProcMountInfoEntry> mountinfo =
      ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries());

  for (auto const& e : mountinfo) {
    if (e.mount_point == dir.path()) {
      EXPECT_EQ(e.fstype, kTmpfs);
      auto mopts = ParseMountOptions(e.super_opts);
      EXPECT_THAT(mopts, AnyOf(Contains(Pair("mode", "0123")),
                               Contains(Pair("mode", "123"))));
    }
  }
}

TEST(MountTest, TmpfsSizeRoundUpSinglePageSize) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto tmpfs_size_opt = absl::StrCat("size=", kPageSize / 2);
  auto const mount = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", dir.path(), kTmpfs, 0, tmpfs_size_opt, 0));
  auto fd = ASSERT_NO_ERRNO_AND_VALUE(
      Open(JoinPath(dir.path(), "foo"), O_CREAT | O_RDWR, 0777));

  // Check that it starts at size zero.
  struct stat buf;
  ASSERT_THAT(fstat(fd.get(), &buf), SyscallSucceeds());
  EXPECT_EQ(buf.st_size, 0);

  // Grow to 1 Page Size.
  ASSERT_THAT(fallocate(fd.get(), 0, 0, kPageSize), SyscallSucceeds());
  ASSERT_THAT(fstat(fd.get(), &buf), SyscallSucceeds());
  EXPECT_EQ(buf.st_size, kPageSize);

  // Grow to size beyond tmpfs allocated bytes.
  ASSERT_THAT(fallocate(fd.get(), 0, 0, kPageSize + 1),
              SyscallFailsWithErrno(ENOSPC));
  ASSERT_THAT(fstat(fd.get(), &buf), SyscallSucceeds());
  EXPECT_EQ(buf.st_size, kPageSize);
}

TEST(MountTest, TmpfsNrBlocksAllocation) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const mount = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", dir.path(), kTmpfs, 0, "nr_blocks=1", 0));
  auto fd = ASSERT_NO_ERRNO_AND_VALUE(
      Open(JoinPath(dir.path(), "foo"), O_CREAT | O_RDWR, 0777));

  // Check that it starts at size zero.
  struct stat buf;
  ASSERT_THAT(fstat(fd.get(), &buf), SyscallSucceeds());
  EXPECT_EQ(buf.st_size, 0);

  // Grow to 1 Page Size.
  ASSERT_THAT(fallocate(fd.get(), 0, 0, kPageSize), SyscallSucceeds());
  ASSERT_THAT(fstat(fd.get(), &buf), SyscallSucceeds());
  EXPECT_EQ(buf.st_size, kPageSize);

  // Grow to size beyond the block limit.
  ASSERT_THAT(fallocate(fd.get(), 0, 0, kPageSize + 1),
              SyscallFailsWithErrno(ENOSPC));
  ASSERT_THAT(fstat(fd.get(), &buf), SyscallSucceeds());
  EXPECT_EQ(buf.st_size, kPageSize);
}

TEST(MountTest, TmpfsSizeAllocationMultiplePages) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto page_multiple = 2;
  auto size = kPageSize * page_multiple;
  auto tmpfs_size_opt = absl::StrCat("size=", size);
  auto const mount = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", dir.path(), kTmpfs, 0, tmpfs_size_opt, 0));
  auto fd = ASSERT_NO_ERRNO_AND_VALUE(
      Open(JoinPath(dir.path(), "foo"), O_CREAT | O_RDWR, 0777));

  // Check that it starts at size zero.
  struct stat buf;
  ASSERT_THAT(fstat(fd.get(), &buf), SyscallSucceeds());
  EXPECT_EQ(buf.st_size, 0);

  // Ensure fallocate does not allow partial allocations.
  ASSERT_THAT(fallocate(fd.get(), 0, 0, size + 1),
              SyscallFailsWithErrno(ENOSPC));
  ASSERT_THAT(fstat(fd.get(), &buf), SyscallSucceeds());
  EXPECT_EQ(buf.st_size, 0);

  // Grow to multiple of page size.
  ASSERT_THAT(fallocate(fd.get(), 0, 0, size), SyscallSucceeds());
  ASSERT_THAT(fstat(fd.get(), &buf), SyscallSucceeds());
  EXPECT_EQ(buf.st_size, size);

  // Grow to beyond tmpfs size bytes.
  ASSERT_THAT(fallocate(fd.get(), 0, 0, size + 1),
              SyscallFailsWithErrno(ENOSPC));
  ASSERT_THAT(fstat(fd.get(), &buf), SyscallSucceeds());
  EXPECT_EQ(buf.st_size, size);
}

TEST(MountTest, TmpfsSizeMoreThanSinglePgSZMultipleFiles) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const page_multiple = 10;
  auto const size = kPageSize * page_multiple;
  auto tmpfs_size_opt = absl::StrCat("size=", size);
  auto const mount = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", dir.path(), kTmpfs, 0, tmpfs_size_opt, 0));
  for (int i = 0; i < page_multiple; i++) {
    auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(
        JoinPath(dir.path(), absl::StrCat("foo_", i)), O_CREAT | O_RDWR, 0777));
    // Create buffer & Grow to 100 bytes.
    struct stat buf;
    ASSERT_THAT(fstat(fd.get(), &buf), SyscallSucceeds());
    ASSERT_THAT(fallocate(fd.get(), 0, 0, 100), SyscallSucceeds());
    ASSERT_THAT(fstat(fd.get(), &buf), SyscallSucceeds());
    EXPECT_EQ(buf.st_size, 100);
  }
  auto fd = ASSERT_NO_ERRNO_AND_VALUE(
      Open(JoinPath(dir.path(), absl::StrCat("foo_", page_multiple + 1)),
           O_CREAT | O_RDWR, 0777));
  // Grow to beyond tmpfs size bytes after exhausting the size.
  ASSERT_THAT(fallocate(fd.get(), 0, 0, kPageSize),
              SyscallFailsWithErrno(ENOSPC));
}

TEST(MountTest, TmpfsSizeFtruncate) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto tmpfs_size_opt = absl::StrCat("size=", kPageSize);
  auto const mount = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", dir.path(), kTmpfs, 0, tmpfs_size_opt, 0));
  auto fd = ASSERT_NO_ERRNO_AND_VALUE(
      Open(JoinPath(dir.path(), "foo"), O_CREAT | O_RDWR, 0777));
  ASSERT_THAT(fallocate(fd.get(), 0, 0, kPageSize), SyscallSucceeds());
  struct stat status;
  ASSERT_THAT(fstat(fd.get(), &status), SyscallSucceeds());
  EXPECT_EQ(status.st_size, kPageSize);

  ASSERT_THAT(ftruncate(fd.get(), kPageSize + 1), SyscallSucceeds());
  ASSERT_THAT(fstat(fd.get(), &status), SyscallSucceeds());
  EXPECT_EQ(status.st_size, kPageSize + 1);

  ASSERT_THAT(ftruncate(fd.get(), 0), SyscallSucceeds());
  ASSERT_THAT(fstat(fd.get(), &status), SyscallSucceeds());
  EXPECT_EQ(status.st_size, 0);

  ASSERT_THAT(fallocate(fd.get(), 0, 0, kPageSize), SyscallSucceeds());
  ASSERT_THAT(fstat(fd.get(), &status), SyscallSucceeds());
  EXPECT_EQ(status.st_size, kPageSize);
}

// Test shows directory does not take up any pages.
TEST(MountTest, TmpfsDirectoryAllocCheck) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  auto const dir_parent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());

  auto tmpfs_size_opt = absl::StrCat("size=", kPageSize);
  auto const mount = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", dir_parent.path(), kTmpfs, 0, tmpfs_size_opt, 0));

  auto const dir_tmp =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir_parent.path()));

  // Creating only 1 regular file allocates 1 page size.
  auto fd = ASSERT_NO_ERRNO_AND_VALUE(
      Open(JoinPath(dir_parent.path(), "foo"), O_CREAT | O_RDWR, 0777));

  // Check that it starts at size zero.
  struct stat buf;
  ASSERT_THAT(fstat(fd.get(), &buf), SyscallSucceeds());
  EXPECT_EQ(buf.st_size, 0);

  // Grow to 1 Page Size.
  ASSERT_THAT(fallocate(fd.get(), 0, 0, kPageSize), SyscallSucceeds());
  ASSERT_THAT(fstat(fd.get(), &buf), SyscallSucceeds());
  EXPECT_EQ(buf.st_size, kPageSize);

  // Grow to beyond 1 Page Size.
  ASSERT_THAT(fallocate(fd.get(), 0, 0, kPageSize + 1),
              SyscallFailsWithErrno(ENOSPC));
}

// Tests memory allocation for symlinks.
TEST(MountTest, TmpfsSymlinkAllocCheck) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  auto const dir_parent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());

  auto tmpfs_size_opt = absl::StrCat("size=", kPageSize);
  auto const mount = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", dir_parent.path(), kTmpfs, 0, tmpfs_size_opt, 0));

  const int target_size = 128;
  auto target = std::string(target_size - 1, 'a');
  auto pathname = JoinPath(dir_parent.path(), "foo1");
  EXPECT_THAT(symlink(target.c_str(), pathname.c_str()), SyscallSucceeds());

  target = std::string(target_size, 'a');
  pathname = absl::StrCat(dir_parent.path(), "/foo2");
  EXPECT_THAT(symlink(target.c_str(), pathname.c_str()), SyscallSucceeds());

  target = std::string(target_size, 'a');
  pathname = absl::StrCat(dir_parent.path(), "/foo3");
  EXPECT_THAT(symlink(target.c_str(), pathname.c_str()),
              SyscallFailsWithErrno(ENOSPC));

  target = std::string(target_size - 1, 'a');
  pathname = absl::StrCat(dir_parent.path(), "/foo4");
  EXPECT_THAT(symlink(target.c_str(), pathname.c_str()), SyscallSucceeds());
  EXPECT_THAT(unlink(pathname.c_str()), SyscallSucceeds());
}

// Tests memory unallocation for symlinks.
TEST(MountTest, TmpfsSymlinkUnallocCheck) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  auto const dir_parent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());

  auto tmpfs_size_opt = absl::StrCat("size=", kPageSize);
  auto const mount = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", dir_parent.path(), kTmpfs, 0, tmpfs_size_opt, 0));

  const int target_size = 128;
  auto pathname = JoinPath(dir_parent.path(), "foo1");
  auto target = std::string(target_size, 'a');
  EXPECT_THAT(symlink(target.c_str(), pathname.c_str()), SyscallSucceeds());
  auto const fd =
      ASSERT_NO_ERRNO_AND_VALUE(Open(pathname, O_CREAT | O_RDWR, 0777));
  ASSERT_THAT(fallocate(fd.get(), 0, 0, kPageSize),
              SyscallFailsWithErrno(ENOSPC));
  EXPECT_THAT(unlink(pathname.c_str()), SyscallSucceeds());
  ASSERT_THAT(fallocate(fd.get(), 0, 0, kPageSize), SyscallSucceeds());
}

// Tests memory allocation for Hard Links is not double allocated.
TEST(MountTest, TmpfsHardLinkAllocCheck) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto tmpfs_size_opt = absl::StrCat("size=", kPageSize);
  auto const mount = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", dir.path(), kTmpfs, 0, tmpfs_size_opt, 0));
  const std::string fileOne = JoinPath(dir.path(), "foo1");
  const std::string fileTwo = JoinPath(dir.path(), "foo2");
  auto const fd =
      ASSERT_NO_ERRNO_AND_VALUE(Open(fileOne, O_CREAT | O_RDWR, 0777));
  EXPECT_THAT(link(fileOne.c_str(), fileTwo.c_str()), SyscallSucceeds());

  // Check that it starts at size zero.
  struct stat buf;
  ASSERT_THAT(fstat(fd.get(), &buf), SyscallSucceeds());
  EXPECT_EQ(buf.st_size, 0);

  // Grow to 1 Page Size.
  ASSERT_THAT(fallocate(fd.get(), 0, 0, kPageSize), SyscallSucceeds());
  ASSERT_THAT(fstat(fd.get(), &buf), SyscallSucceeds());
  EXPECT_EQ(buf.st_size, kPageSize);

  // Grow to size beyond tmpfs allocated bytes.
  ASSERT_THAT(fallocate(fd.get(), 0, 0, kPageSize + 1),
              SyscallFailsWithErrno(ENOSPC));
  ASSERT_THAT(fstat(fd.get(), &buf), SyscallSucceeds());
  EXPECT_EQ(buf.st_size, kPageSize);
  EXPECT_THAT(unlink(fileTwo.c_str()), SyscallSucceeds());
  EXPECT_THAT(unlink(fileOne.c_str()), SyscallSucceeds());
}

// Tests memory allocation for empty size.
TEST(MountTest, TmpfsEmptySizeAllocCheck) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  ASSERT_THAT(mount("", dir.path().c_str(), kTmpfs, 0, "size"),
              SyscallFailsWithErrno(EINVAL));
}

TEST(MountTest, TmpfsUnlinkRegularFileAllocCheck) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto tmpfs_size_opt = absl::StrCat("size=", kPageSize);
  const int kTruncateSize = 2 * kPageSize;
  auto const mount = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", dir.path(), kTmpfs, 0, tmpfs_size_opt, 0));
  const std::string fileOne = JoinPath(dir.path(), "foo1");
  auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(fileOne, O_CREAT | O_RDWR, 0777));
  EXPECT_THAT(unlink(fileOne.c_str()), SyscallSucceeds());
  EXPECT_THAT(ftruncate(fd.get(), kTruncateSize), SyscallSucceeds());
}

TEST(MountTest, TmpfsSizePartialWriteSinglePage) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto tmpfs_size_opt = absl::StrCat("size=", kPageSize);
  auto const mount = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", dir.path(), kTmpfs, 0, tmpfs_size_opt, 0));

  const std::string fileOne = JoinPath(dir.path(), "foo1");
  auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(fileOne, O_CREAT | O_RDWR, 0777));
  lseek(fd.get(), kPageSize - 2, SEEK_SET);
  char buf[4];
  EXPECT_THAT(write(fd.get(), buf, 4), SyscallSucceedsWithValue(2));
  EXPECT_THAT(write(fd.get(), buf, 4), SyscallFailsWithErrno(ENOSPC));
}

TEST(MountTest, TmpfsSizePartialWriteMultiplePages) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto tmpfs_size_opt = absl::StrCat("size=", 3 * kPageSize);
  auto const mount = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", dir.path(), kTmpfs, 0, tmpfs_size_opt, 0));

  const std::string fileOne = JoinPath(dir.path(), "foo1");
  auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(fileOne, O_CREAT | O_RDWR, 0777));
  lseek(fd.get(), kPageSize, SEEK_SET);
  std::vector<char> buf(kPageSize + 2);
  EXPECT_THAT(write(fd.get(), buf.data(), 4), SyscallSucceedsWithValue(4));
  struct stat status;
  ASSERT_THAT(fstat(fd.get(), &status), SyscallSucceeds());
  EXPECT_EQ(status.st_size, kPageSize + 4);
  EXPECT_THAT(write(fd.get(), buf.data(), 1), SyscallSucceedsWithValue(1));

  // Writing with size exactly until the end of page boundary.
  EXPECT_THAT(write(fd.get(), buf.data(), kPageSize - 5),
              SyscallSucceedsWithValue(kPageSize - 5));

  EXPECT_THAT(write(fd.get(), buf.data(), 1), SyscallSucceedsWithValue(1));
  // Writing with size more than page end & having extra page available as well.
  EXPECT_THAT(write(fd.get(), buf.data(), kPageSize + 1),
              SyscallSucceedsWithValue(kPageSize + 1));

  // Writing with size more than page end & having no page available.
  EXPECT_THAT(write(fd.get(), buf.data(), kPageSize + 1),
              SyscallSucceedsWithValue(kPageSize - 2));
  EXPECT_THAT(write(fd.get(), buf.data(), 1), SyscallFailsWithErrno(ENOSPC));
}

TEST(MountTest, TmpfsSizeMmap) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto tmpfs_size_opt = absl::StrCat("size=", kPageSize);
  auto const mount = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", dir.path(), kTmpfs, 0, tmpfs_size_opt, 0));
  const std::string fileOne = JoinPath(dir.path(), "foo");
  auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(fileOne, O_CREAT | O_RDWR, 0777));
  EXPECT_THAT(ftruncate(fd.get(), 2 * kPageSize), SyscallSucceeds());
  void* addr = mmap(NULL, 2 * kPageSize, PROT_READ, MAP_PRIVATE, fd.get(), 0);
  EXPECT_NE(addr, MAP_FAILED);
  // Access memory so that the first page to page fault occurs and is allocated.
  char data = ((char*)addr)[kPageSize - 2];
  EXPECT_EQ(data, 0);
  std::vector<char> in(kPageSize + 2);
  // Access memory such that it causes the second page to page fault. The page
  // fault should fail due to hitting tmpfs size limit which should cause
  // SIGBUS signal.
  EXPECT_EXIT(memcpy(in.data(), reinterpret_cast<char*>(addr), kPageSize + 2),
              ::testing::KilledBySignal(SIGBUS), "");
  EXPECT_THAT(munmap(addr, 2 * kPageSize), SyscallSucceeds());
}

TEST(MountTest, SimpleBind) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto const dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const mount = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", dir1.path(), kTmpfs, 0, "mode=0123", 0));
  auto const child1 =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir1.path()));
  auto const child2 =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir1.path()));
  auto const bind_mount = Mount(dir1.path(), dir2.path(), "", MS_BIND, "", 0);

  // 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, 0666));
  // Verify both directories have the same nodes.
  std::vector<std::string> child_names = {std::string(Basename(child1.path())),
                                          std::string(Basename(child2.path()))};
  ASSERT_NO_ERRNO(DirContains(dir1.path(), child_names, {}));
  ASSERT_NO_ERRNO(DirContains(dir2.path(), child_names, {}));

  const std::string dir1_filepath =
      JoinPath(dir1.path(), Basename(child1.path()), filename);
  const std::string dir2_filepath =
      JoinPath(dir2.path(), Basename(child1.path()), filename);

  std::string output;
  ASSERT_NO_ERRNO(GetContents(dir1_filepath, &output));
  EXPECT_EQ(output, contents);
  ASSERT_NO_ERRNO(GetContents(dir2_filepath, &output));
  EXPECT_EQ(output, contents);
}

TEST(MountTest, BindToSelf) {
  // Test that we can turn a normal directory into a mount with MS_BIND.
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());

  const std::vector<ProcMountsEntry> mounts_before =
      ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountsEntries());
  for (const auto& e : mounts_before) {
    ASSERT_NE(e.mount_point, dir.path());
  }

  auto const mount = ASSERT_NO_ERRNO_AND_VALUE(
      Mount(dir.path(), dir.path(), "", MS_BIND, "", 0));

  const std::vector<ProcMountsEntry> mounts_after =
      ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountsEntries());
  bool found = false;
  for (const auto& e : mounts_after) {
    if (e.mount_point == dir.path()) {
      found = true;
    }
  }
  ASSERT_TRUE(found);
}

TEST(MountTest, MaxMounts) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto const parent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  ASSERT_THAT(mount("", parent.path().c_str(), kTmpfs, 0, ""),
              SyscallSucceeds());
  auto const dir =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(parent.path()));
  ASSERT_THAT(
      mount(dir.path().c_str(), dir.path().c_str(), nullptr, MS_BIND, nullptr),
      SyscallSucceeds());
  ASSERT_THAT(mount("", dir.path().c_str(), "", MS_SHARED, ""),
              SyscallSucceeds());

  // Each bind mount doubles the number of mounts in the peer group. The number
  // of binds we can do before failing is log2(max_mounts-num_current_mounts).
  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(dir.path().c_str(), dir.path().c_str(), nullptr, MS_BIND,
                      nullptr),
                SyscallSucceeds());
  }
  ASSERT_THAT(
      mount(dir.path().c_str(), dir.path().c_str(), nullptr, MS_BIND, nullptr),
      SyscallFailsWithErrno(ENOSPC));
  umount2(parent.path().c_str(), MNT_DETACH);
}

TEST(MountTest, PropagateToSameMountpointStacksMounts) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const mnt = ASSERT_NO_ERRNO_AND_VALUE(Mount(
      dir.path().c_str(), dir.path().c_str(), "", MS_BIND, "", MNT_DETACH));
  auto const child =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir.path()));
  ASSERT_THAT(mount(child.path().c_str(), child.path().c_str(), "", MS_BIND, 0),
              SyscallSucceeds());
  ASSERT_THAT(mount("", dir.path().c_str(), "", MS_SHARED, 0),
              SyscallSucceeds());
  auto const mnt2 = ASSERT_NO_ERRNO_AND_VALUE(Mount(
      dir.path().c_str(), dir2.path().c_str(), "", MS_BIND, "", MNT_DETACH));

  std::string dir2_child_path = JoinPath(dir2.path(), Basename(child.path()));
  ASSERT_THAT(
      mount(dir2_child_path.c_str(), dir2_child_path.c_str(), "", MS_BIND, 0),
      SyscallSucceeds());

  // Check that mounts at the child mount point have distinct parents.
  std::vector<ProcMountInfoEntry> mounts =
      ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries());
  uint64_t parent_id = 0;
  for (auto& minfo : mounts) {
    if (minfo.mount_point == child.path()) {
      if (parent_id == 0) {
        parent_id = minfo.parent_id;
      } else {
        EXPECT_NE(parent_id, minfo.parent_id);
      }
    }
  }
}

TEST(MountTest, UmountReparentsCoveredMounts) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const mnt = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", dir.path().c_str(), kTmpfs, 0, "", MNT_DETACH));
  ASSERT_THAT(mount("", dir.path().c_str(), "", MS_SHARED, 0),
              SyscallSucceeds());
  auto const child =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir.path()));
  ASSERT_THAT(mount("", child.path().c_str(), kTmpfs, 0, 0), SyscallSucceeds());
  auto const mnt2 = ASSERT_NO_ERRNO_AND_VALUE(Mount(
      dir.path().c_str(), dir2.path().c_str(), "", MS_BIND, "", MNT_DETACH));

  std::string dir2_child_path = JoinPath(dir2.path(), Basename(child.path()));
  ASSERT_THAT(mount("", dir2_child_path.c_str(), kTmpfs, 0, 0),
              SyscallSucceeds());

  umount2(dir2_child_path.c_str(), MNT_DETACH);

  auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals());
  ASSERT_FALSE(optionals[child.path()].empty());
  EXPECT_NE(optionals[child.path()][0].shared, 0);
  EXPECT_TRUE(optionals[dir2_child_path].empty());
}

// Tests that it is possible to make a shared mount.
TEST(MountTest, MakeShared) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const mnt = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", dir.path().c_str(), kTmpfs, 0, "", 0));
  ASSERT_THAT(mount("", dir.path().c_str(), "", MS_SHARED, 0),
              SyscallSucceeds());

  auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals());
  ASSERT_FALSE(optionals[dir.path()].empty());
  EXPECT_NE(optionals[dir.path()][0].shared, 0);
}

// Tests that shared mounts have different group IDs.
TEST(MountTest, MakeMultipleShared) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto const dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const mount1 =
      ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir1.path(), kTmpfs, 0, "", 0));
  ASSERT_THAT(mount("", dir1.path().c_str(), "", MS_SHARED, 0),
              SyscallSucceeds());

  auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const mount2 =
      ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir2.path(), kTmpfs, 0, "", 0));
  ASSERT_THAT(mount("", dir2.path().c_str(), "", MS_SHARED, 0),
              SyscallSucceeds());

  auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals());
  ASSERT_FALSE(optionals[dir1.path()].empty());
  ASSERT_FALSE(optionals[dir2.path()].empty());
  EXPECT_NE(optionals[dir1.path()][0].shared, optionals[dir2.path()][0].shared);
}

// Tests that shared mounts reused group IDs from deleted groups.
TEST(MountTest, ReuseGroupIDs) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto const dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const mount1 =
      ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir1.path(), kTmpfs, 0, "", 0));
  ASSERT_THAT(mount("", dir1.path().c_str(), "", MS_SHARED, 0),
              SyscallSucceeds());

  auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  int reused_group_id;
  {
    auto const mount2 =
        ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir2.path(), kTmpfs, 0, "", 0));
    ASSERT_THAT(mount("", dir2.path().c_str(), "", MS_SHARED, 0),
                SyscallSucceeds());
    auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals());
    ASSERT_FALSE(optionals[dir2.path()].empty());
    reused_group_id = optionals[dir2.path()][0].shared;
  }

  // Check that created a new shared mount reuses the ID 2.
  auto const mount2 =
      ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir2.path(), kTmpfs, 0, "", 0));
  ASSERT_THAT(mount("", dir2.path().c_str(), "", MS_SHARED, 0),
              SyscallSucceeds());
  auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals());
  ASSERT_FALSE(optionals[dir2.path()].empty());
  EXPECT_EQ(reused_group_id, optionals[dir2.path()][0].shared);
}

// Tests that a child mount inherits the propagation type of its parent.
TEST(MountTest, InerheritPropagation) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto const dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const mount1 =
      ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir1.path(), kTmpfs, 0, "", 0));
  ASSERT_THAT(mount("", dir1.path().c_str(), "", MS_SHARED, 0),
              SyscallSucceeds());

  auto const dir2 =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir1.path()));
  auto const mount2 =
      ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir2.path(), kTmpfs, 0, "", 0));

  auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals());
  ASSERT_FALSE(optionals[dir2.path()].empty());
  EXPECT_NE(optionals[dir2.path()][0].shared, 0);
}

// Tests that it is possible to make a mount private again after it is shared.
TEST(MountTest, MakePrivate) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const mnt =
      ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), kTmpfs, 0, "", 0));
  ASSERT_THAT(mount("", dir.path().c_str(), "", MS_SHARED, 0),
              SyscallSucceeds());
  ASSERT_THAT(mount("", dir.path().c_str(), "", MS_PRIVATE, 0),
              SyscallSucceeds());

  auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals());
  ASSERT_FALSE(optionals[dir.path()].empty());
  EXPECT_EQ(optionals[dir.path()][0].shared, 0);
}

TEST(MountTest, ArgumentsAreIgnored) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  // These mounts should not fail even though string arguments are passed as
  // NULL.
  auto const mnt = ASSERT_NO_ERRNO_AND_VALUE(
      Mount(dir.path(), dir.path(), "", MS_BIND, "", MNT_DETACH));
  ASSERT_THAT(mount(NULL, dir.path().c_str(), NULL, MS_SHARED, NULL),
              SyscallSucceeds());
  auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals());
  ASSERT_FALSE(optionals[dir.path()].empty());
  EXPECT_NE(optionals[dir.path()][0].shared, 0);
}

TEST(MountTest, MultiplePropagationFlagsFails) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const mnt =
      ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), kTmpfs, 0, "", 0));
  EXPECT_THAT(mount("", dir.path().c_str(), "", MS_SHARED | MS_PRIVATE, 0),
              SyscallFailsWithErrno(EINVAL));
}

TEST(MountTest, SetMountPropagationOfStackedMounts) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const mnt1 = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", dir.path().c_str(), kTmpfs, 0, "", 0));
  // Only the topmost mount on the stack should be shared.
  auto const mnt2 = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", dir.path().c_str(), kTmpfs, 0, "", 0));
  ASSERT_THAT(mount("", dir.path().c_str(), "", MS_SHARED, 0),
              SyscallSucceeds());

  auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals());
  ASSERT_FALSE(optionals[dir.path()].empty());
  EXPECT_EQ(optionals[dir.path()][0].shared, 0);
  EXPECT_NE(optionals[dir.path()][1].shared, 0);
}

TEST(MountTest, MakePeer) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  auto const dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const mnt = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", dir1.path().c_str(), kTmpfs, 0, "", 0));
  ASSERT_THAT(mount("", dir1.path().c_str(), "", MS_SHARED, 0),
              SyscallSucceeds());

  auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const mnt2 = ASSERT_NO_ERRNO_AND_VALUE(
      Mount(dir1.path(), dir2.path(), "", MS_BIND, "", MNT_DETACH));

  auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals());
  ASSERT_FALSE(optionals[dir1.path()].empty());
  ASSERT_FALSE(optionals[dir2.path()].empty());
  EXPECT_EQ(optionals[dir1.path()][0].shared, optionals[dir2.path()][0].shared);
  EXPECT_NE(optionals[dir1.path()][0].shared, 0);
}

TEST(MountTest, PropagateMountEvent) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  auto const dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const mnt = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", dir1.path().c_str(), kTmpfs, 0, "", MNT_DETACH));
  auto const child_dir =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir1.path()));
  ASSERT_THAT(mount("", dir1.path().c_str(), "", MS_SHARED, 0),
              SyscallSucceeds());
  auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const mnt2 = ASSERT_NO_ERRNO_AND_VALUE(
      Mount(dir1.path(), dir2.path(), "", MS_BIND, "", MNT_DETACH));
  // This mount should propagate to dir2.
  auto const child_mnt = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", child_dir.path().c_str(), kTmpfs, 0, "", MNT_DETACH));

  const std::string child_path1 =
      JoinPath(dir1.path(), Basename(child_dir.path()));
  const std::string child_path2 =
      JoinPath(dir2.path(), Basename(child_dir.path()));

  auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals());
  ASSERT_FALSE(optionals[dir1.path()].empty());
  ASSERT_FALSE(optionals[dir2.path()].empty());
  ASSERT_FALSE(optionals[child_path1].empty());
  ASSERT_FALSE(optionals[child_path2].empty());
  EXPECT_EQ(optionals[dir1.path()][0].shared, optionals[dir2.path()][0].shared);
  EXPECT_NE(optionals[dir1.path()][0].shared, 0);
  EXPECT_EQ(optionals[child_path1][0].shared, optionals[child_path2][0].shared);
  EXPECT_NE(optionals[child_path1][0].shared, 0);
}

TEST(MountTest, PropagateUmountEvent) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  auto const dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const mnt = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", dir1.path().c_str(), kTmpfs, 0, "", MNT_DETACH));
  auto const child_dir =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir1.path()));
  ASSERT_THAT(mount("", dir1.path().c_str(), "", MS_SHARED, 0),
              SyscallSucceeds());
  auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const mnt2 = ASSERT_NO_ERRNO_AND_VALUE(
      Mount(dir1.path(), dir2.path(), "", MS_BIND, "", MNT_DETACH));
  // This mount will propagate to dir2. Once the block ends it will be
  // unmounted, which should also propagate to dir2.
  {
    auto const child_mnt = ASSERT_NO_ERRNO_AND_VALUE(
        Mount("", child_dir.path().c_str(), kTmpfs, 0, "", MNT_DETACH));
  }

  const std::string child_path1 =
      JoinPath(dir1.path(), Basename(child_dir.path()));
  const std::string child_path2 =
      JoinPath(dir2.path(), Basename(child_dir.path()));

  std::vector<ProcMountInfoEntry> mounts =
      ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries());
  for (const auto& e : mounts) {
    EXPECT_NE(e.mount_point, child_path1);
    EXPECT_NE(e.mount_point, child_path2);
  }
}

TEST(MountTest, PropagateChildUmountEvent) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  TempPath const dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  ASSERT_THAT(mount("", dir1.path().c_str(), kTmpfs, 0, ""), SyscallSucceeds());

  TempPath const child =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir1.path()));
  ASSERT_THAT(mount("", child.path().c_str(), kTmpfs, 0, ""),
              SyscallSucceeds());
  ASSERT_THAT(mount("", child.path().c_str(), "", MS_SHARED, 0),
              SyscallSucceeds());

  TempPath const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  ASSERT_THAT(mount(child.path().c_str(), dir2.path().c_str(), "", MS_BIND, 0),
              SyscallSucceeds());
  ASSERT_THAT(mount("", child.path().c_str(), kTmpfs, 0, ""),
              SyscallSucceeds());
  ASSERT_THAT(umount2(dir1.path().c_str(), MNT_DETACH), SyscallSucceeds());

  auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals());
  EXPECT_EQ(optionals[dir2.path()].size(), 1);

  ASSERT_EQ(umount2(dir2.path().c_str(), MNT_DETACH), 0);
}

TEST(MountTest, UmountIgnoresPeersWithChildren) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  auto const dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  ASSERT_THAT(mount("", dir1.path().c_str(), kTmpfs, 0, ""), SyscallSucceeds());
  ASSERT_THAT(mount("", dir1.path().c_str(), "", MS_SHARED, 0),
              SyscallSucceeds());
  auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  ASSERT_THAT(mount(dir1.path().c_str(), dir2.path().c_str(), "", MS_BIND, 0),
              SyscallSucceeds());

  auto const child_dir =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir1.path()));
  ASSERT_THAT(mount("", child_dir.path().c_str(), kTmpfs, 0, ""),
              SyscallSucceeds());
  auto const grandchild_dir =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(child_dir.path()));
  ASSERT_THAT(mount("", grandchild_dir.path().c_str(), kTmpfs, 0, ""),
              SyscallSucceeds());

  const std::string child_path1 =
      JoinPath(dir1.path(), Basename(child_dir.path()));
  const std::string child_path2 =
      JoinPath(dir2.path(), Basename(child_dir.path()));
  ASSERT_THAT(mount("", child_path2.c_str(), "", MS_PRIVATE, 0),
              SyscallSucceeds());
  const std::string grandchild_path2 =
      JoinPath(child_path2, Basename(grandchild_dir.path()));
  ASSERT_THAT(umount2(grandchild_path2.c_str(), MNT_DETACH), SyscallSucceeds());

  // This umount event should not propagate to the peer at dir1 because its
  // child mount still has its own child mount.
  ASSERT_THAT(umount2(child_path2.c_str(), MNT_DETACH), SyscallSucceeds());
  auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals());
  EXPECT_EQ(optionals[child_path2].size(), 0);
  EXPECT_EQ(optionals[child_path1].size(), 1);

  ASSERT_THAT(umount2(dir1.path().c_str(), MNT_DETACH), SyscallSucceeds());
  ASSERT_THAT(umount2(dir2.path().c_str(), MNT_DETACH), SyscallSucceeds());
}

TEST(MountTest, BindSharedOnShared) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  auto const dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const dir3 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const dir4 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  // Dir 1 and 2 are part of peer group 'A', dir 3 and 4 are part of peer group
  // 'B'.
  auto const mnt1 = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", dir1.path().c_str(), kTmpfs, 0, "", MNT_DETACH));
  auto const dir5 =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir1.path()));
  ASSERT_THAT(mount("", dir1.path().c_str(), "", MS_SHARED, 0),
              SyscallSucceeds());
  auto const mnt2 = ASSERT_NO_ERRNO_AND_VALUE(Mount(
      dir1.path().c_str(), dir2.path().c_str(), "", MS_BIND, "", MNT_DETACH));
  auto const mnt3 = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", dir3.path().c_str(), kTmpfs, 0, "", MNT_DETACH));
  ASSERT_THAT(mount("", dir3.path().c_str(), "", MS_SHARED, 0),
              SyscallSucceeds());
  auto const mnt4 = ASSERT_NO_ERRNO_AND_VALUE(Mount(
      dir3.path().c_str(), dir4.path().c_str(), "", MS_BIND, "", MNT_DETACH));

  const std::string dir5_path2 = JoinPath(dir2.path(), Basename(dir5.path()));

  // Bind peer group 'A' to peer group 'B'.
  auto const mnt5 = ASSERT_NO_ERRNO_AND_VALUE(Mount(
      dir4.path().c_str(), dir5.path().c_str(), "", MS_BIND, "", MNT_DETACH));

  auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals());
  ASSERT_FALSE(optionals[dir1.path()].empty());
  ASSERT_FALSE(optionals[dir2.path()].empty());
  ASSERT_FALSE(optionals[dir3.path()].empty());
  ASSERT_FALSE(optionals[dir4.path()].empty());
  ASSERT_FALSE(optionals[dir5.path()].empty());
  ASSERT_FALSE(optionals[dir5_path2].empty());
  EXPECT_EQ(optionals[dir3.path()][0].shared, optionals[dir4.path()][0].shared);
  EXPECT_EQ(optionals[dir4.path()][0].shared, optionals[dir5.path()][0].shared);
  EXPECT_EQ(optionals[dir5.path()][0].shared, optionals[dir5_path2][0].shared);
  EXPECT_NE(optionals[dir3.path()][0].shared, 0);
}

TEST(MountTest, BindSharedOnPrivate) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  auto const dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const mnt1 = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", dir1.path().c_str(), kTmpfs, 0, "", MNT_DETACH));
  auto const dir2 =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir1.path()));
  auto const dir3 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const dir4 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const mnt3 = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", dir3.path().c_str(), kTmpfs, 0, "", MNT_DETACH));
  ASSERT_THAT(mount("", dir3.path().c_str(), "", MS_SHARED, 0),
              SyscallSucceeds());
  auto const mnt4 = ASSERT_NO_ERRNO_AND_VALUE(Mount(
      dir3.path().c_str(), dir4.path().c_str(), "", MS_BIND, "", MNT_DETACH));

  // bind to private mount.
  auto const mnt2 = ASSERT_NO_ERRNO_AND_VALUE(Mount(
      dir3.path().c_str(), dir2.path().c_str(), "", MS_BIND, "", MNT_DETACH));

  auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals());
  ASSERT_FALSE(optionals[dir1.path()].empty());
  ASSERT_FALSE(optionals[dir2.path()].empty());
  ASSERT_FALSE(optionals[dir3.path()].empty());
  ASSERT_FALSE(optionals[dir4.path()].empty());
  EXPECT_EQ(optionals[dir1.path()][0].shared, 0);
  EXPECT_EQ(optionals[dir2.path()][0].shared, optionals[dir3.path()][0].shared);
  EXPECT_EQ(optionals[dir3.path()][0].shared, optionals[dir4.path()][0].shared);
}

TEST(MountTest, BindPeerGroupsWithChildren) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  auto const dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const mnt1 = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", dir1.path().c_str(), kTmpfs, 0, "", MNT_DETACH));
  ASSERT_THAT(mount("", dir1.path().c_str(), "", MS_SHARED, 0),
              SyscallSucceeds());
  auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const mnt2 = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", dir2.path().c_str(), kTmpfs, 0, "", MNT_DETACH));
  ASSERT_THAT(mount("", dir2.path().c_str(), "", MS_SHARED, 0),
              SyscallSucceeds());
  // dir3 and dir4 are child mounts of dir1.
  auto const dir3 =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir1.path()));
  auto const mnt3 = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", dir3.path().c_str(), kTmpfs, 0, "", MNT_DETACH));
  auto const dir4 =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir1.path()));
  auto const mnt4 = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", dir4.path().c_str(), kTmpfs, 0, "", MNT_DETACH));
  auto const mnt5 = ASSERT_NO_ERRNO_AND_VALUE(Mount(
      dir1.path().c_str(), dir2.path().c_str(), "", MS_BIND, "", MNT_DETACH));

  const std::string dir3_path2 = JoinPath(dir2.path(), Basename(dir3.path()));
  const std::string dir4_path2 = JoinPath(dir2.path(), Basename(dir4.path()));

  auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals());
  ASSERT_FALSE(optionals[dir1.path()].empty());
  ASSERT_FALSE(optionals[dir2.path()].empty());
  ASSERT_FALSE(optionals[dir3.path()].empty());

  EXPECT_NE(optionals[dir1.path()][0].shared, optionals[dir3.path()][0].shared);
  EXPECT_NE(optionals[dir3.path()][0].shared, optionals[dir4.path()][0].shared);
  EXPECT_NE(optionals[dir4.path()][0].shared, optionals[dir1.path()][0].shared);
  EXPECT_EQ(optionals[dir3_path2].size(), 0);
  EXPECT_EQ(optionals[dir4_path2].size(), 0);
}

TEST(MountTest, BindParentToChild) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  auto const dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const mnt1 = ASSERT_NO_ERRNO_AND_VALUE(Mount(
      dir1.path().c_str(), dir1.path().c_str(), "", MS_BIND, "", MNT_DETACH));
  ASSERT_THAT(mount("", dir1.path().c_str(), "", MS_SHARED, 0),
              SyscallSucceeds());
  auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const mnt2 = ASSERT_NO_ERRNO_AND_VALUE(Mount(
      dir1.path().c_str(), dir2.path().c_str(), "", MS_BIND, "", MNT_DETACH));
  auto const child_dir =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir1.path()));
  auto const mnt3 = ASSERT_NO_ERRNO_AND_VALUE(
      Mount(dir1.path().c_str(), child_dir.path().c_str(), "", MS_BIND, "",
            MNT_DETACH));

  auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals());
  ASSERT_FALSE(optionals[dir1.path()].empty());
  ASSERT_FALSE(optionals[dir2.path()].empty());
  ASSERT_FALSE(optionals[child_dir.path()].empty());

  EXPECT_EQ(optionals[dir1.path()][0].shared, optionals[dir2.path()][0].shared);
  EXPECT_EQ(optionals[dir2.path()][0].shared,
            optionals[child_dir.path()][0].shared);
  EXPECT_NE(optionals[dir1.path()][0].shared, 0);
}

TEST(MountTest, MountInfoHasRoot) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  auto const parent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const mount = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", parent.path(), kTmpfs, 0, "mode=0123", 0));
  auto const child =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(parent.path()));
  auto const bind_mount = Mount(child.path(), child.path(), "", MS_BIND, "", 0);
  std::vector<ProcMountInfoEntry> mounts =
      ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries());
  for (const auto& e : mounts) {
    if (e.mount_point == child.path()) {
      EXPECT_EQ(e.root, JoinPath("/", Basename(child.path())))
          << "Offending mount ID is: " << e.id;
    }
    if (e.mount_point == parent.path()) {
      EXPECT_EQ(e.root, "/") << "Offending mount ID is: " << e.id;
    }
  }
}

TEST(MountTest, DeadMountsAreDecRefd) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  DisableSave ds;
  std::string home = NewTempAbsPath();
  ASSERT_NO_ERRNO(Mkdir(home));
  ASSERT_THAT(chdir(home.c_str()), SyscallSucceeds());
  constexpr char dirpath[] = "./file";

  for (int i = 0; i < 10; ++i) {
    const auto rest = [&] {
      mkdir(dirpath, 0);
      mount(dirpath, ".", 0, MS_BIND, 0);
      rmdir(dirpath);
      mkdir(dirpath, 0);
      mount(dirpath, ".", 0, MS_BIND, 0);
    };
    EXPECT_THAT(InForkedProcess(rest), IsPosixErrorOkAndHolds(0));
  }
}

TEST(MountTest, UmountSharedBind) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  std::string home = NewTempAbsPath();
  ASSERT_NO_ERRNO(Mkdir(home));
  ASSERT_THAT(chdir(home.c_str()), SyscallSucceeds());
  constexpr char dirpath[] = "./file";

  ASSERT_THAT(mkdir(dirpath, 0), SyscallSucceeds());
  ASSERT_THAT(mount(dirpath, dirpath, 0, MS_BIND, 0), SyscallSucceeds());
  ASSERT_THAT(mount(0, dirpath, 0, MS_SHARED, 0), SyscallSucceeds());
  ASSERT_THAT(mount(dirpath, dirpath, 0, MS_BIND, 0), SyscallSucceeds());
  ASSERT_THAT(mount(0, dirpath, 0, MS_SHARED, 0), SyscallSucceeds());
  ASSERT_THAT(umount2(dirpath, MNT_DETACH), SyscallSucceeds());
  ASSERT_THAT(umount2(dirpath, MNT_DETACH), SyscallSucceeds());
}

TEST(MountTest, MakeSlave) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  auto const dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const mnt1 = ASSERT_NO_ERRNO_AND_VALUE(Mount(
      dir1.path().c_str(), dir1.path().c_str(), "", MS_BIND, "", MNT_DETACH));
  ASSERT_THAT(mount("", dir1.path().c_str(), "", MS_SHARED, 0),
              SyscallSucceeds());
  auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const mnt2 = ASSERT_NO_ERRNO_AND_VALUE(Mount(
      dir1.path().c_str(), dir2.path().c_str(), "", MS_BIND, "", MNT_DETACH));

  ASSERT_THAT(mount(0, dir2.path().c_str(), 0, MS_SLAVE, 0), SyscallSucceeds());
  auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals());
  ASSERT_NE(optionals[dir1.path()][0].shared, 0);
  ASSERT_NE(optionals[dir2.path()][0].master, 0);
}

TEST(MountTest, MakeSharedSlave) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto const dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const mnt1 = ASSERT_NO_ERRNO_AND_VALUE(Mount(
      dir1.path().c_str(), dir1.path().c_str(), "", MS_BIND, "", MNT_DETACH));
  ASSERT_THAT(mount("", dir1.path().c_str(), "", MS_SHARED, 0),
              SyscallSucceeds());
  auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const mnt2 = ASSERT_NO_ERRNO_AND_VALUE(Mount(
      dir1.path().c_str(), dir2.path().c_str(), "", MS_BIND, "", MNT_DETACH));

  ASSERT_THAT(mount(0, dir2.path().c_str(), 0, MS_SLAVE, 0), SyscallSucceeds());
  ASSERT_THAT(mount(0, dir2.path().c_str(), 0, MS_SHARED, 0),
              SyscallSucceeds());

  auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals());
  ASSERT_NE(optionals[dir1.path()][0].shared, 0);
  ASSERT_NE(optionals[dir2.path()][0].shared, 0);
  ASSERT_NE(optionals[dir2.path()][0].master, 0);
}

TEST(MountTest, PrivateMasterUnslaves) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  auto const base = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const base_mnt = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", base.path().c_str(), kTmpfs, 0, "", MNT_DETACH));
  ASSERT_THAT(mount("", base.path().c_str(), "", MS_PRIVATE, 0),
              SyscallSucceeds());

  auto const dir1 =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(base.path()));
  auto const mnt1 = ASSERT_NO_ERRNO_AND_VALUE(Mount(
      dir1.path().c_str(), dir1.path().c_str(), "", MS_BIND, "", MNT_DETACH));
  ASSERT_THAT(mount("", dir1.path().c_str(), "", MS_SHARED, 0),
              SyscallSucceeds());
  auto const dir2 =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(base.path()));
  auto const mnt2 = ASSERT_NO_ERRNO_AND_VALUE(Mount(
      dir1.path().c_str(), dir2.path().c_str(), "", MS_BIND, "", MNT_DETACH));
  ASSERT_THAT(mount(0, dir2.path().c_str(), 0, MS_SLAVE, 0), SyscallSucceeds());
  ASSERT_THAT(mount(0, dir2.path().c_str(), 0, MS_SHARED, 0),
              SyscallSucceeds());

  ASSERT_THAT(mount(0, dir1.path().c_str(), 0, MS_PRIVATE, 0),
              SyscallSucceeds());

  auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals());
  ASSERT_EQ(optionals[dir1.path()][0].shared, 0);
  ASSERT_EQ(optionals[dir2.path()][0].master, 0);
  ASSERT_NE(optionals[dir2.path()][0].shared, 0);
}

TEST(MountTest, SlaveMaster) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto const dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const mnt1 = ASSERT_NO_ERRNO_AND_VALUE(Mount(
      dir1.path().c_str(), dir1.path().c_str(), "", MS_BIND, "", MNT_DETACH));
  ASSERT_THAT(mount("", dir1.path().c_str(), "", MS_SHARED, 0),
              SyscallSucceeds());
  auto const dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const mnt2 = ASSERT_NO_ERRNO_AND_VALUE(Mount(
      dir1.path().c_str(), dir2.path().c_str(), "", MS_BIND, "", MNT_DETACH));
  ASSERT_THAT(mount(0, dir2.path().c_str(), 0, MS_SLAVE, 0), SyscallSucceeds());
  ASSERT_THAT(mount(0, dir2.path().c_str(), 0, MS_SHARED, 0),
              SyscallSucceeds());
  auto const dir3 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const mnt3 = ASSERT_NO_ERRNO_AND_VALUE(Mount(
      dir2.path().c_str(), dir3.path().c_str(), "", MS_BIND, "", MNT_DETACH));
  ASSERT_THAT(mount(0, dir3.path().c_str(), 0, MS_SLAVE, 0), SyscallSucceeds());

  ASSERT_THAT(mount(0, dir2.path().c_str(), 0, MS_PRIVATE, 0),
              SyscallSucceeds());

  auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals());
  ASSERT_NE(optionals[dir1.path()][0].shared, 0);
  ASSERT_EQ(optionals[dir2.path()][0].shared, 0);
  ASSERT_EQ(optionals[dir2.path()][0].master, 0);
  ASSERT_EQ(optionals[dir3.path()][0].master, optionals[dir1.path()][0].shared);
}

TEST(MountTest, BindSharedToSlave) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto const src = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const src_mnt = ASSERT_NO_ERRNO_AND_VALUE(Mount(
      src.path().c_str(), src.path().c_str(), "", MS_BIND, "", MNT_DETACH));
  ASSERT_THAT(mount("", src.path().c_str(), "", MS_SHARED, 0),
              SyscallSucceeds());
  auto const dst_master = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const dst_master_mnt = ASSERT_NO_ERRNO_AND_VALUE(
      Mount(dst_master.path().c_str(), dst_master.path().c_str(), "", MS_BIND,
            "", MNT_DETACH));
  ASSERT_THAT(mount("", dst_master.path().c_str(), "", MS_SHARED, 0),
              SyscallSucceeds());
  auto const dst = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const dst_mnt = ASSERT_NO_ERRNO_AND_VALUE(
      Mount(dst_master.path().c_str(), dst.path().c_str(), "", MS_BIND, "",
            MNT_DETACH));
  ASSERT_THAT(mount("", dst.path().c_str(), "", MS_SLAVE, 0),
              SyscallSucceeds());

  auto const dst_mnt2 = ASSERT_NO_ERRNO_AND_VALUE(Mount(
      src.path().c_str(), dst.path().c_str(), "", MS_BIND, "", MNT_DETACH));
  auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals());

  ASSERT_EQ(optionals[src.path()][0].shared, optionals[dst.path()][1].shared);
  ASSERT_EQ(optionals[dst.path()][0].master,
            optionals[dst_master.path()][0].shared);
}

TEST(MountTest, BindSlaveToShared) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto const src = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const src_mnt = ASSERT_NO_ERRNO_AND_VALUE(Mount(
      src.path().c_str(), src.path().c_str(), "", MS_BIND, "", MNT_DETACH));
  ASSERT_THAT(mount("", src.path().c_str(), "", MS_SHARED, 0),
              SyscallSucceeds());
  auto const src_master = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const src_master_mnt = ASSERT_NO_ERRNO_AND_VALUE(
      Mount(src.path().c_str(), src_master.path().c_str(), "", MS_BIND, "",
            MNT_DETACH));
  ASSERT_THAT(mount("", src.path().c_str(), "", MS_SLAVE, 0),
              SyscallSucceeds());

  auto const dst = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const dst_mnt = ASSERT_NO_ERRNO_AND_VALUE(Mount(
      dst.path().c_str(), dst.path().c_str(), "", MS_BIND, "", MNT_DETACH));
  ASSERT_THAT(mount("", dst.path().c_str(), "", MS_SHARED, 0),
              SyscallSucceeds());
  auto const dst_peer = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const dst_peer_mnt = ASSERT_NO_ERRNO_AND_VALUE(
      Mount(dst.path().c_str(), dst_peer.path().c_str(), "", MS_BIND, "",
            MNT_DETACH));

  auto const dst_mnt2 = ASSERT_NO_ERRNO_AND_VALUE(Mount(
      src.path().c_str(), dst.path().c_str(), "", MS_BIND, "", MNT_DETACH));

  auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals());
  ASSERT_EQ(optionals[dst.path()][1].shared,
            optionals[dst_peer.path()][1].shared);
  ASSERT_EQ(optionals[dst.path()][1].master,
            optionals[dst_peer.path()][1].master);
  ASSERT_EQ(optionals[dst.path()][1].master,
            optionals[src_master.path()][0].shared);
}

TEST(MountTest, BindSlaveToSlave) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto const src = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const src_mnt = ASSERT_NO_ERRNO_AND_VALUE(Mount(
      src.path().c_str(), src.path().c_str(), "", MS_BIND, "", MNT_DETACH));
  ASSERT_THAT(mount("", src.path().c_str(), "", MS_SHARED, 0),
              SyscallSucceeds());
  auto const src_master = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const src_master_mnt = ASSERT_NO_ERRNO_AND_VALUE(
      Mount(src.path().c_str(), src_master.path().c_str(), "", MS_BIND, "",
            MNT_DETACH));
  ASSERT_THAT(mount("", src.path().c_str(), "", MS_SLAVE, 0),
              SyscallSucceeds());

  auto const dst = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const dst_mnt = ASSERT_NO_ERRNO_AND_VALUE(Mount(
      dst.path().c_str(), dst.path().c_str(), "", MS_BIND, "", MNT_DETACH));
  ASSERT_THAT(mount("", dst.path().c_str(), "", MS_SHARED, 0),
              SyscallSucceeds());
  auto const dst_master = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const dst_master_mnt = ASSERT_NO_ERRNO_AND_VALUE(
      Mount(dst.path().c_str(), dst_master.path().c_str(), "", MS_BIND, "",
            MNT_DETACH));
  ASSERT_THAT(mount("", dst.path().c_str(), "", MS_SLAVE, 0),
              SyscallSucceeds());

  auto const dst_mnt2 = ASSERT_NO_ERRNO_AND_VALUE(Mount(
      src.path().c_str(), dst.path().c_str(), "", MS_BIND, "", MNT_DETACH));

  auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals());
  ASSERT_EQ(optionals[dst.path()][1].master, optionals[src.path()][0].master);
}

// Test that mounting on a slave mount does not propagate to the master.
TEST(MountTest, SlavePropagationEvent) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto const dst_master = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const dst_master_mnt = ASSERT_NO_ERRNO_AND_VALUE(
      Mount(dst_master.path().c_str(), dst_master.path().c_str(), "", MS_BIND,
            "", MNT_DETACH));
  ASSERT_THAT(mount("", dst_master.path().c_str(), "", MS_SHARED, 0),
              SyscallSucceeds());
  auto const dst = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const dst_mnt = ASSERT_NO_ERRNO_AND_VALUE(
      Mount(dst_master.path().c_str(), dst.path().c_str(), "", MS_BIND, "",
            MNT_DETACH));
  ASSERT_THAT(mount("", dst.path().c_str(), "", MS_SLAVE, 0),
              SyscallSucceeds());
  auto const child =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dst_master.path()));

  const std::string master_child_path =
      JoinPath(dst_master.path(), Basename(child.path()));
  const std::string slave_child_path =
      JoinPath(dst.path(), Basename(child.path()));
  auto const slave_child_mnt = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", slave_child_path.c_str(), kTmpfs, 0, "", MNT_DETACH));

  auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals());
  ASSERT_EQ(optionals[child.path()].size(), 0);
}

// We are building a propagation tree that looks like this:
/*
    A <--> B <--> C <---> D
   /|\           /|       |\
  / F G         J K       H I
 /
E<-->O
    /|\
   M L N
*/
// Propagating mount events across this tree should cover most propagation
// cases.
TEST(MountTest, LargeTreePropagationEvent) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  // 15 total mounts that should all get propagated to if we mount on A.
  auto const a = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const b = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const c = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const d = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const e = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const g = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const h = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const i = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const j = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const k = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const l = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const m = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const n = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const o = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());

  auto const a_mnt = ASSERT_NO_ERRNO_AND_VALUE(
      Mount(a.path().c_str(), a.path().c_str(), "", MS_BIND, "", MNT_DETACH));
  ASSERT_THAT(mount("", a.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds());

  // Place E, F, and G in A's peer group, then make them slaves.
  auto const e_mnt = ASSERT_NO_ERRNO_AND_VALUE(
      Mount(a.path().c_str(), e.path().c_str(), "", MS_BIND, "", MNT_DETACH));
  auto const f_mnt = ASSERT_NO_ERRNO_AND_VALUE(
      Mount(a.path().c_str(), f.path().c_str(), "", MS_BIND, "", MNT_DETACH));
  auto const g_mnt = ASSERT_NO_ERRNO_AND_VALUE(
      Mount(a.path().c_str(), g.path().c_str(), "", MS_BIND, "", MNT_DETACH));
  ASSERT_THAT(mount("", e.path().c_str(), "", MS_SLAVE, 0), SyscallSucceeds());
  ASSERT_THAT(mount("", f.path().c_str(), "", MS_SLAVE, 0), SyscallSucceeds());
  ASSERT_THAT(mount("", g.path().c_str(), "", MS_SLAVE, 0), SyscallSucceeds());

  // Add B to A's shared group.
  auto const b_mnt = ASSERT_NO_ERRNO_AND_VALUE(
      Mount(a.path().c_str(), b.path().c_str(), "", MS_BIND, "", MNT_DETACH));

  // Add C to A's shared group and place J and K in C's peer group, then make
  // them slaves.
  auto const c_mnt = ASSERT_NO_ERRNO_AND_VALUE(
      Mount(a.path().c_str(), c.path().c_str(), "", MS_BIND, "", MNT_DETACH));
  auto const j_mnt = ASSERT_NO_ERRNO_AND_VALUE(
      Mount(c.path().c_str(), j.path().c_str(), "", MS_BIND, "", MNT_DETACH));
  auto const k_mnt = ASSERT_NO_ERRNO_AND_VALUE(
      Mount(c.path().c_str(), k.path().c_str(), "", MS_BIND, "", MNT_DETACH));
  ASSERT_THAT(mount("", j.path().c_str(), "", MS_SLAVE, 0), SyscallSucceeds());
  ASSERT_THAT(mount("", k.path().c_str(), "", MS_SLAVE, 0), SyscallSucceeds());

  // Add D to A's shared group and place H and I in Ds peer group, then make
  // them slaves.
  auto const d_mnt = ASSERT_NO_ERRNO_AND_VALUE(
      Mount(a.path().c_str(), d.path().c_str(), "", MS_BIND, "", MNT_DETACH));
  auto const h_mnt = ASSERT_NO_ERRNO_AND_VALUE(
      Mount(d.path().c_str(), h.path().c_str(), "", MS_BIND, "", MNT_DETACH));
  auto const i_mnt = ASSERT_NO_ERRNO_AND_VALUE(
      Mount(d.path().c_str(), i.path().c_str(), "", MS_BIND, "", MNT_DETACH));
  ASSERT_THAT(mount("", h.path().c_str(), "", MS_SLAVE, 0), SyscallSucceeds());
  ASSERT_THAT(mount("", i.path().c_str(), "", MS_SLAVE, 0), SyscallSucceeds());

  // Make E shared and create a group with O.
  ASSERT_THAT(mount("", e.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds());
  auto const o_mnt = ASSERT_NO_ERRNO_AND_VALUE(
      Mount(e.path().c_str(), o.path().c_str(), "", MS_BIND, "", MNT_DETACH));

  // Add M, L and N to K's shared group and make them slaves of O.
  auto const m_mnt = ASSERT_NO_ERRNO_AND_VALUE(
      Mount(o.path().c_str(), m.path().c_str(), "", MS_BIND, "", MNT_DETACH));
  auto const l_mnt = ASSERT_NO_ERRNO_AND_VALUE(
      Mount(o.path().c_str(), l.path().c_str(), "", MS_BIND, "", MNT_DETACH));
  auto const n_mnt = ASSERT_NO_ERRNO_AND_VALUE(
      Mount(o.path().c_str(), n.path().c_str(), "", MS_BIND, "", MNT_DETACH));
  ASSERT_THAT(mount("", m.path().c_str(), "", MS_SLAVE, 0), SyscallSucceeds());
  ASSERT_THAT(mount("", l.path().c_str(), "", MS_SLAVE, 0), SyscallSucceeds());
  ASSERT_THAT(mount("", n.path().c_str(), "", MS_SLAVE, 0), SyscallSucceeds());

  std::vector<ProcMountInfoEntry> mounts =
      ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries());

  ASSERT_THAT(mount("", a.path().c_str(), kTmpfs, 0, ""), SyscallSucceeds());

  std::vector<ProcMountInfoEntry> mounts_after_mount =
      ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries());
  ASSERT_EQ(mounts_after_mount.size(), mounts.size() + 15);

  auto optionals = ASSERT_NO_ERRNO_AND_VALUE(MountOptionals());

  // A, B, C, and D are all mounted over and in a peer group.
  ASSERT_NE(optionals[a.path()][1].shared, 0);
  ASSERT_EQ(optionals[a.path()][1].shared, optionals[b.path()][1].shared);
  ASSERT_EQ(optionals[a.path()][1].shared, optionals[c.path()][1].shared);
  ASSERT_EQ(optionals[a.path()][1].shared, optionals[d.path()][1].shared);

  // E, F, G, H, I, J, K are all mounted over and slave to A's peer group.
  ASSERT_EQ(optionals[a.path()][1].shared, optionals[e.path()][1].master);
  ASSERT_EQ(optionals[a.path()][1].shared, optionals[f.path()][1].master);
  ASSERT_EQ(optionals[a.path()][1].shared, optionals[g.path()][1].master);
  ASSERT_EQ(optionals[a.path()][1].shared, optionals[h.path()][1].master);
  ASSERT_EQ(optionals[a.path()][1].shared, optionals[i.path()][1].master);
  ASSERT_EQ(optionals[a.path()][1].shared, optionals[j.path()][1].master);
  ASSERT_EQ(optionals[a.path()][1].shared, optionals[k.path()][1].master);

  // E and O are all mounted over and in a peer group.
  ASSERT_EQ(optionals[e.path()][1].shared, optionals[o.path()][1].shared);

  // L, M, and N are all mounted over and slaves to E's peer group.
  ASSERT_EQ(optionals[e.path()][1].shared, optionals[l.path()][1].master);
  ASSERT_EQ(optionals[e.path()][1].shared, optionals[m.path()][1].master);
  ASSERT_EQ(optionals[e.path()][1].shared, optionals[n.path()][1].master);

  ASSERT_THAT(umount2(a.path().c_str(), MNT_DETACH), SyscallSucceeds());
  std::vector<ProcMountInfoEntry> mounts_after_umount =
      ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries());
  ASSERT_EQ(mounts_after_umount.size(), mounts.size());
}

TEST(MountTest, MaxMountsWithSlave) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto const parent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const parent_mnt = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("test", parent.path(), kTmpfs, 0, "mode=0123", MNT_DETACH));
  auto const a =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(parent.path()));
  auto const b =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(parent.path()));
  auto const c =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(parent.path()));

  auto const a_mnt = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("test", a.path(), kTmpfs, 0, "mode=0123", 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));
  ASSERT_THAT(mount("", b.path().c_str(), "", MS_SLAVE, 0), SyscallSucceeds());
  ASSERT_THAT(mount("", b.path().c_str(), "", MS_SHARED, 0), SyscallSucceeds());

  auto const c_mnt = ASSERT_NO_ERRNO_AND_VALUE(
      Mount(b.path(), c.path(), "", MS_BIND, "", MNT_DETACH));
  ASSERT_THAT(mount("", c.path().c_str(), "", MS_SLAVE, 0), SyscallSucceeds());

  auto const d = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(a.path()));
  auto const e = 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));
  }

  // Each bind mount doubles the number of mounts in the propagation tree
  // starting with 3. The number of binds we can do before failing is
  // log2((max_mounts-num_current_mounts)/3).
  std::vector<ProcMountInfoEntry> mounts =
      ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries());
  int num_binds = static_cast<int>(std::log2((mount_max - mounts.size()) / 3));

  for (int i = 0; i < num_binds; i++) {
    ASSERT_THAT(
        mount(d.path().c_str(), d.path().c_str(), nullptr, MS_BIND, nullptr),
        SyscallSucceeds());
    ASSERT_THAT(mount("", d.path().c_str(), "", MS_SHARED, 0),
                SyscallSucceeds());
  }
  for (int i = 0; i < 2; i++) {
    EXPECT_THAT(
        mount(d.path().c_str(), d.path().c_str(), nullptr, MS_BIND, nullptr),
        SyscallFailsWithErrno(ENOSPC));
  }
}

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(), kTmpfs, 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(), kTmpfs, 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(), kTmpfs, 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(), kTmpfs, 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(), kTmpfs, 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)));

  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const mount = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", dir.path(), kTmpfs, 0, "mode=0700", 0));
  auto const file_path = JoinPath(dir.path(), "foo");
  EXPECT_NO_ERRNO(Open(file_path, O_CREAT | O_RDWR, 0777));

  pid_t child = fork();
  if (child == 0) {
    // Create a new mount namespace and umount the test mount from it.
    TEST_PCHECK(unshare(CLONE_NEWNS) == 0);
    TEST_PCHECK(access(file_path.c_str(), F_OK) == 0);
    TEST_PCHECK(umount2(dir.path().c_str(), MNT_DETACH) == 0);
    _exit(0);
  }
  ASSERT_THAT(child, SyscallSucceeds());
  int status;
  ASSERT_THAT(waitpid(child, &status, 0), SyscallSucceedsWithValue(child));
  ASSERT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0);

  // Check that the test mount is still here.
  EXPECT_NO_ERRNO(Open(file_path, O_RDWR));
}

TEST(MountTest, MountNamespaceSetns) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const mnt = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", dir.path(), kTmpfs, 0, "mode=0700", MNT_DETACH));
  auto const file_path = JoinPath(dir.path(), "foo");
  EXPECT_NO_ERRNO(Open(file_path, O_CREAT | O_RDWR, 0777));
  const FileDescriptor nsfd =
      ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/thread-self/ns/mnt", O_RDONLY));

  pid_t child = fork();
  if (child == 0) {
    TEST_PCHECK(unshare(CLONE_NEWNS) == 0);
    TEST_PCHECK(umount2(dir.path().c_str(), MNT_DETACH) == 0);
    TEST_PCHECK(setns(nsfd.get(), CLONE_NEWNS) == 0);
    TEST_PCHECK(access(file_path.c_str(), F_OK) == 0);
    _exit(0);
  }
  ASSERT_THAT(child, SyscallSucceeds());
  int status;
  ASSERT_THAT(waitpid(child, &status, 0), SyscallSucceedsWithValue(child));
  ASSERT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0);
}

TEST(MountTest, MountNamespacePropagation) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  auto const mnt = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", dir.path(), kTmpfs, 0, "mode=0700", MNT_DETACH));
  auto child_dir = JoinPath(dir.path(), "test");
  auto const file_path = JoinPath(child_dir, "foo");
  auto const file2_path = JoinPath(child_dir, "boo");

  ASSERT_THAT(mount(NULL, dir.path().c_str(), NULL, MS_SHARED, NULL),
              SyscallSucceeds());
  ASSERT_THAT(mkdir(child_dir.c_str(), 0700), SyscallSucceeds());
  ASSERT_THAT(mount("child", child_dir.c_str(), kTmpfs, 0, NULL),
              SyscallSucceeds());
  EXPECT_NO_ERRNO(Open(file_path, O_CREAT | O_RDWR, 0777));

  pid_t child = fork();
  if (child == 0) {
    TEST_PCHECK(unshare(CLONE_NEWNS) == 0);
    TEST_PCHECK(access(file_path.c_str(), F_OK) == 0);
    // The test mount has to be umounted from the second mount namespace too.
    TEST_PCHECK(umount2(child_dir.c_str(), MNT_DETACH) == 0);
    // The new mount has to be propagated to the second mount namespace.
    TEST_PCHECK(mount("test2", child_dir.c_str(), kTmpfs, 0, NULL) == 0);
    TEST_PCHECK(mknod(file2_path.c_str(), 0777 | S_IFREG, 0) == 0);
    _exit(0);
  }
  ASSERT_THAT(child, SyscallSucceeds());
  int status;
  ASSERT_THAT(waitpid(child, &status, 0), SyscallSucceedsWithValue(child));
  ASSERT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0);

  // Check that the test mount is still here.
  EXPECT_NO_ERRNO(Open(file2_path, O_RDWR));
  EXPECT_THAT(umount2(child_dir.c_str(), MNT_DETACH), SyscallSucceeds());
}

TEST(MountTest, MountNamespaceSlavesNewUserNamespace) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());

  const Cleanup dir_mount = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", dir.path(), kTmpfs, 0, "", MNT_DETACH));
  ASSERT_THAT(mount(NULL, dir.path().c_str(), NULL, MS_SHARED, NULL),
              SyscallSucceeds());
  const TempPath child_dir =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir.path()));
  const std::string file_path = JoinPath(child_dir.path(), "foo");
  const std::string file2_path = JoinPath(child_dir.path(), "boo");

  const std::function<void()> parent = [&] {
    TEST_CHECK_SUCCESS(
        mount("child", child_dir.path().c_str(), kTmpfs, 0, NULL));
    TEST_CHECK_SUCCESS(open(file_path.c_str(), O_CREAT | O_RDWR, 0777));
  };
  const std::function<void()> child = [&] {
    TEST_CHECK_SUCCESS(access(JoinPath(child_dir.path(), "foo").c_str(), F_OK));

    // These mount operations will not propagate to the other namespace
    // because it is a slave mount.
    TEST_CHECK_SUCCESS(umount2(child_dir.path().c_str(), MNT_DETACH));
    TEST_CHECK_SUCCESS(
        mount("test2", child_dir.path().c_str(), kTmpfs, 0, NULL));
    TEST_CHECK_SUCCESS(mknod(file2_path.c_str(), 0777 | S_IFREG, 0));

    TEST_CHECK_SUCCESS(umount2(child_dir.path().c_str(), MNT_DETACH));
    // This should fail because the mount is locked.
    TEST_CHECK_ERRNO(umount2(child_dir.path().c_str(), MNT_DETACH), EINVAL);

    // Check that there is a master entry in mountinfo.
    int fd = open("/proc/self/mountinfo", O_RDONLY);
    TEST_CHECK(fd >= 0);
    char child_mountinfo[0x8000];
    int size = 0;
    while (true) {
      int ret =
          read(fd, child_mountinfo + size, sizeof(child_mountinfo) - size);
      TEST_CHECK(ret != -1);
      size += ret;
      if (ret == 0) {
        break;
      }
    }
    TEST_CHECK(absl::StrContains(child_mountinfo, "master:"));
  };
  EXPECT_THAT(InForkedUserMountNamespace(parent, child),
              IsPosixErrorOkAndHolds(0));

  // Check that the test mount is still here.
  EXPECT_EQ(Open(file2_path, O_RDWR).error().errno_value(), ENOENT);
  EXPECT_THAT(umount2(child_dir.path().c_str(), MNT_DETACH), SyscallSucceeds());
}

TEST(MountTest, LockedMountStopsNonRecBind) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  const Cleanup dir_mount = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", dir.path(), kTmpfs, 0, "", MNT_DETACH));
  const TempPath child_dir =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir.path()));
  const Cleanup child_mount = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", child_dir.path().c_str(), kTmpfs, 0, "", MNT_DETACH));
  const std::string foo_dir = JoinPath(dir.path(), "foo");

  const std::function<void()> child_fn = [&] {
    TEST_CHECK_SUCCESS(mkdir(foo_dir.c_str(), 0700));
    TEST_CHECK_ERRNO(
        mount(dir.path().c_str(), foo_dir.c_str(), "", MS_BIND, ""), EINVAL);
    TEST_CHECK_SUCCESS(
        mount(dir.path().c_str(), foo_dir.c_str(), "", MS_BIND | MS_REC, ""));
  };
  EXPECT_THAT(InForkedUserMountNamespace([] {}, child_fn),
              IsPosixErrorOkAndHolds(0));
}

// This test checks that a mount tree that propagates from a more privileged
// mount namespace cannot be partially unmounted. It must be unmounted as a
// single unit as described in point 4 of the notes in
// https://man7.org/linux/man-pages/man7/mount_namespaces.7.html. This test
// also checks that unmounting a propagated mount tree does not reveal the
// contents of overmounted filesystems from the more privileged mount namespace.
TEST(MountTest, UmountPropagatedSubtreeFromPrivilegedNS) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  const Cleanup dir_mount = ASSERT_NO_ERRNO_AND_VALUE(
      Mount(dir.path(), dir.path(), "", MS_BIND, "", MNT_DETACH));
  ASSERT_THAT(mount(NULL, dir.path().c_str(), NULL, MS_SHARED, NULL),
              SyscallSucceeds());

  const TempPath child_dir =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir.path()));
  const TempPath sibling_dir =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir.path()));
  const Cleanup child_mount = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", child_dir.path(), kTmpfs, 0, "", MNT_DETACH));
  ASSERT_THAT(mount(NULL, child_dir.path().c_str(), NULL, MS_PRIVATE, NULL),
              SyscallSucceeds());

  const TempPath grandchild_dir =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(child_dir.path()));
  ASSERT_THAT(open(JoinPath(grandchild_dir.path(), "foo").c_str(),
                   O_CREAT | O_RDWR, 0777),
              SyscallSucceeds());
  const Cleanup grandchild_mnt = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", grandchild_dir.path(), kTmpfs, 0, "", MNT_DETACH));
  ASSERT_THAT(
      mount(NULL, grandchild_dir.path().c_str(), NULL, MS_PRIVATE, NULL),
      SyscallSucceeds());
  const std::string grandsibling_dir =
      JoinPath(sibling_dir.path(), Basename(grandchild_dir.path()));

  const std::function<void()> parent = [&] {
    TEST_CHECK_SUCCESS(mount(child_dir.path().c_str(),
                             sibling_dir.path().c_str(), "", MS_BIND | MS_REC,
                             ""));
    TEST_CHECK_SUCCESS(
        mount("", sibling_dir.path().c_str(), "", MS_PRIVATE | MS_REC, ""));
  };
  // You can umount an entire subtree that propagated from a more privileged
  // mount namespace, but can't umount only part of the subtree.
  const std::string file_path =
      JoinPath(Basename(grandchild_dir.path()), "foo");
  const std::function<void()> child = [&] {
    TEST_CHECK_ERRNO(umount2(grandsibling_dir.c_str(), MNT_DETACH), EINVAL);
    int dirfd = open(sibling_dir.path().c_str(), O_RDONLY | O_DIRECTORY);
    TEST_PCHECK(dirfd >= 0);
    TEST_CHECK_SUCCESS(umount2(sibling_dir.path().c_str(), MNT_DETACH));
    // Check to ensure you cannot access an overmounted file with openat after
    // the mount unit has been destroyed.
    TEST_CHECK_ERRNO(openat(dirfd, file_path.c_str(), O_RDONLY), ENOENT);
  };
  EXPECT_THAT(InForkedUserMountNamespace(parent, child),
              IsPosixErrorOkAndHolds(0));
}

TEST(MountTest, MountFailsOnPseudoFilesystemMountpoint) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
  auto const fd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, 0));
  std::string path = absl::StrCat("/proc/self/fd/", fd.get());
  // After 0d039eac6e595 ("fix a couple of races in MNT_TREE_BENEATH handling
  // by do_move_mount()"), this hits fs/namespace.c:do_lock_mount() =>
  // !is_mounted() (eventfds use fs/anon_inodes.c:anon_inode_mnt, which has
  // real_mount(mnt)->mnt_ns == fs/mount.h:MNT_NS_INTERNAL == ERR_PTR(-EINVAL)
  // from kern_mount()) and returns ENOENT.
  EXPECT_THAT(
      mount("test", path.c_str(), kTmpfs, 0, 0),
      AnyOf(SyscallFailsWithErrno(EINVAL), SyscallFailsWithErrno(ENOENT)));
}

TEST(MountTest, ChangeMountFlags) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  const auto flag = MS_NODEV;
  const auto dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  const auto dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  const auto mount_cleanup = ASSERT_NO_ERRNO_AND_VALUE(
      Mount(dir1.path(), dir2.path(), kTmpfs, MS_BIND, "", 0));

  struct statfs st;
  EXPECT_THAT(mount(dir1.path().c_str(), dir2.path().c_str(), kTmpfs,
                    MS_REMOUNT | MS_BIND | flag, ""),
              SyscallSucceeds());
  EXPECT_THAT(statfs(dir2.path().c_str(), &st), SyscallSucceeds());
  EXPECT_EQ(st.f_flags & flag, flag);
  // Resets mount flags.
  EXPECT_THAT(mount(dir1.path().c_str(), dir2.path().c_str(), kTmpfs,
                    MS_REMOUNT | MS_BIND, ""),
              SyscallSucceeds());
  ASSERT_THAT(statfs(dir2.path().c_str(), &st), SyscallSucceeds());
  ASSERT_EQ(st.f_flags & flag, 0);
}

constexpr int kLockableFlags = MS_RDONLY | MS_NOEXEC | MS_NODEV | MS_NOSUID;

// Sets kLockableFlags on the mount at `path`.
void SetLockableFlags(const std::string& path) {
  ASSERT_THAT(mount("", path.c_str(), nullptr,
                    MS_REMOUNT | MS_BIND | kLockableFlags, ""),
              SyscallSucceeds());
}

TEST(MountTest, ChangeMountFlagsOfLockedMount) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  const Cleanup mount_cleanup =
      ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), kTmpfs, 0, "", 0));
  ASSERT_NO_FATAL_FAILURE(SetLockableFlags(dir.path()));
  const std::string path = dir.path();
  const std::string file_path = JoinPath(path, "foo");

  const std::function<void()> child = [&] {
    // The mount was copied into a new user namespace, so none of its flags may
    // be cleared.
    for (const int flag : {MS_RDONLY, MS_NOEXEC, MS_NODEV, MS_NOSUID}) {
      TEST_CHECK_ERRNO(
          mount("", path.c_str(), nullptr,
                MS_REMOUNT | MS_BIND | (kLockableFlags & ~flag), ""),
          EPERM);
    }
    TEST_CHECK_ERRNO(mount("", path.c_str(), nullptr, MS_REMOUNT | MS_BIND, ""),
                     EPERM);
    // The mount is still read-only.
    TEST_CHECK_ERRNO(open(file_path.c_str(), O_CREAT | O_RDWR, 0666), EROFS);
    // A remount that keeps all of the locked flags set is allowed.
    TEST_CHECK_SUCCESS(mount("", path.c_str(), nullptr,
                             MS_REMOUNT | MS_BIND | kLockableFlags, ""));
  };
  EXPECT_THAT(InForkedUserMountNamespace([] {}, child),
              IsPosixErrorOkAndHolds(0));

  // The flags are not locked in the namespace that owns the mount.
  EXPECT_THAT(mount("", path.c_str(), nullptr, MS_REMOUNT | MS_BIND, ""),
              SyscallSucceeds());
  struct statfs st;
  ASSERT_THAT(statfs(path.c_str(), &st), SyscallSucceeds());
  EXPECT_EQ(st.f_flags & kLockableFlags, 0);
}

TEST(MountTest, LockedMountFlagsAreInheritedByBind) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  const Cleanup mount_cleanup =
      ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), kTmpfs, 0, "", 0));
  ASSERT_NO_FATAL_FAILURE(SetLockableFlags(dir.path()));
  const TempPath target = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  const std::string path = dir.path();
  const std::string target_path = target.path();

  const std::function<void()> child = [&] {
    // Binding the locked mount elsewhere must not result in a mount for which
    // flags can be cleared.
    TEST_CHECK_SUCCESS(
        mount(path.c_str(), target_path.c_str(), "", MS_BIND, ""));
    for (const int flag : {MS_RDONLY, MS_NOEXEC, MS_NODEV, MS_NOSUID}) {
      TEST_CHECK_ERRNO(
          mount("", target_path.c_str(), nullptr,
                MS_REMOUNT | MS_BIND | (kLockableFlags & ~flag), ""),
          EPERM);
    }
    TEST_CHECK_SUCCESS(mount("", target_path.c_str(), nullptr,
                             MS_REMOUNT | MS_BIND | kLockableFlags, ""));
    // The bind mount itself was created in this namespace, so unlike the mount
    // it was cloned from, it can be unmounted.
    TEST_CHECK_SUCCESS(umount2(target_path.c_str(), MNT_DETACH));
  };
  EXPECT_THAT(InForkedUserMountNamespace([] {}, child),
              IsPosixErrorOkAndHolds(0));
}

TEST(MountTest, RemountUnmounted) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());

  EXPECT_THAT(mount("", dir.path().c_str(), kTmpfs, MS_REMOUNT, ""),
              SyscallFailsWithErrno(EINVAL));
}

TEST(MountTest, DetachedMountBindFails) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  const TempPath path1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  Cleanup mount_cleanup = ASSERT_NO_ERRNO_AND_VALUE(
      Mount("", path1.path().c_str(), kTmpfs, 0, "", MNT_DETACH));
  mount_cleanup.Release();
  const FileDescriptor fd =
      ASSERT_NO_ERRNO_AND_VALUE(Open(path1.path().c_str(), O_RDONLY));
  std::string fd_path = absl::Substitute("/proc/self/fd/$0", fd.get());
  ASSERT_THAT(umount2(path1.path().c_str(), MNT_DETACH), SyscallSucceeds());
  const TempPath path2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());

  EXPECT_THAT(mount(fd_path.c_str(), path2.path().c_str(), "", MS_BIND, 0),
              SyscallFailsWithErrno(EINVAL));
}

TEST(MountTest, MountProc) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  // Mount procfs with a NULL source to a temporary directory.
  const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  ASSERT_THAT(mount(NULL, dir.path().c_str(), "proc", 0, NULL),
              SyscallSucceeds());
  auto cleanup = Cleanup([&dir] {
    EXPECT_THAT(umount2(dir.path().c_str(), 0), SyscallSucceeds());
  });

  // Verify that /proc/self/mountinfo describes the device as "none".
  const std::vector<ProcMountInfoEntry> mountinfo =
      ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries());
  for (auto const& e : mountinfo) {
    if (e.mount_point == dir.path()) {
      EXPECT_EQ(e.fstype, "proc");
      EXPECT_EQ(e.mount_source, "none");
    }
  }
}

TEST(MountTest, OverlayfsSgidBitIsCopiedUp) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto base_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  // Overlayfs can not be used as upper layer for another overlayfs mount. If
  // running in overlayfs, create a tmpfs mount to use as the upper layer.
  bool in_overlayfs = ASSERT_NO_ERRNO_AND_VALUE(IsOverlayfs(base_dir.path()));
  if (in_overlayfs) {
    ASSERT_THAT(
        mount("tmpfs", base_dir.path().c_str(), "tmpfs", 0, "mode=1777"),
        SyscallSucceeds());
  }
  auto tmpfs_cleanup = Cleanup([&base_dir, &in_overlayfs] {
    if (in_overlayfs) {
      ASSERT_THAT(umount2(base_dir.path().c_str(), MNT_DETACH),
                  SyscallSucceeds());
    }
  });

  // Create the files under test in `lower` and set the S_ISGID bit on them.
  auto lower =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(base_dir.path()));
  struct FileUT {
    absl::string_view name;
    void (*create)(const std::string& path);
  };
  static constexpr std::array kFilesUnderTest = {
      FileUT{"setgid_dir",
             [](const std::string& path) {
               ASSERT_THAT(mkdir(path.c_str(), 0755), SyscallSucceeds());
             }},
      FileUT{"setgid_empty_file",
             [](const std::string& path) {
               ASSERT_NO_ERRNO_AND_VALUE(Open(path, O_CREAT | O_RDWR, 0755));
             }},
      FileUT{"setgid_nonempty_file",
             [](const std::string& path) {
               ASSERT_NO_ERRNO(CreateWithContents(path, "junk", 0755));
             }},
  };
  for (const auto& file_ut : kFilesUnderTest) {
    std::string lower_target = lower.path() + "/" + std::string(file_ut.name);
    file_ut.create(lower_target);

    struct stat st_lower;
    ASSERT_THAT(stat(lower_target.c_str(), &st_lower), SyscallSucceeds());
    ASSERT_THAT(chmod(lower_target.c_str(), st_lower.st_mode | S_ISGID),
                SyscallSucceeds());
    ASSERT_THAT(stat(lower_target.c_str(), &st_lower), SyscallSucceeds());
    ASSERT_TRUE(st_lower.st_mode & S_ISGID);
  }

  // Mount the overlayfs.
  auto upper =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(base_dir.path()));
  auto work = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(base_dir.path()));
  auto merged =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(base_dir.path()));
  std::string opts = "lowerdir=" + lower.path() + ",upperdir=" + upper.path() +
                     ",workdir=" + work.path();
  ASSERT_THAT(
      mount("overlay", merged.path().c_str(), "overlay", 0, opts.c_str()),
      SyscallSucceeds());
  auto overlayfs_cleanup =
      Cleanup([&merged] { umount2(merged.path().c_str(), MNT_DETACH); });

  for (const auto& file_ut : kFilesUnderTest) {
    SCOPED_TRACE(file_ut.name);

    // The S_ISGID bit is visible before any copy-up took place.
    std::string merged_target = merged.path() + "/" + std::string(file_ut.name);
    struct stat st_merged;
    ASSERT_THAT(stat(merged_target.c_str(), &st_merged), SyscallSucceeds());
    ASSERT_TRUE(st_merged.st_mode & S_ISGID);

    // Touch the target to force a copy-up to `upper`.
    ASSERT_THAT(utimensat(AT_FDCWD, merged_target.c_str(), nullptr, 0),
                SyscallSucceeds());

    // The S_ISGID bit must also be copied up.
    std::string upper_target = upper.path() + "/" + std::string(file_ut.name);
    struct stat upper_st_after_touch;
    ASSERT_THAT(stat(upper_target.c_str(), &upper_st_after_touch),
                SyscallSucceeds());
    EXPECT_TRUE(upper_st_after_touch.st_mode & S_ISGID);
    struct stat merged_st_after_touch;
    ASSERT_THAT(stat(merged_target.c_str(), &merged_st_after_touch),
                SyscallSucceeds());
    EXPECT_TRUE(merged_st_after_touch.st_mode & S_ISGID);
  }
}

// Renaming a directory on an overlay inside a user namespace requires
// user.overlay.* xattrs to mark the directory opaque.
TEST(MountTest, OverlayfsDirectoryRenameInUserNamespace) {
  // Test fails in gVisor running on <6.0 due to fsgofer getting permission
  // errors on `mknod`.
  SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(GetHostKernelVersion()).major < 6);
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(CanCreateUserNamespace()));
  auto base_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  bool in_overlayfs = ASSERT_NO_ERRNO_AND_VALUE(IsOverlayfs(base_dir.path()));

  const std::function<void()> parent = [] {};
  const std::function<void()> child = [&base_dir, &in_overlayfs] {
    // Overlayfs can not be used as upper layer for another overlayfs mount. If
    // running in overlayfs, create a tmpfs mount to use as the upper layer.
    if (in_overlayfs) {
      TEST_CHECK_SUCCESS(mount("tmpfs", base_dir.path().c_str(), "tmpfs", 0,
                               "mode=1777,size=10m"));
    }
    auto tmpfs_cleanup = Cleanup([&base_dir, &in_overlayfs] {
      if (in_overlayfs) {
        TEST_CHECK_SUCCESS(umount2(base_dir.path().c_str(), 0));
      }
    });

    auto lower = JoinPath(base_dir.path(), "lower");
    auto upper = JoinPath(base_dir.path(), "upper");
    auto work = JoinPath(base_dir.path(), "work");
    auto merged = JoinPath(base_dir.path(), "merged");
    TEST_CHECK_SUCCESS(mkdir(lower.c_str(), 0755));
    TEST_CHECK_SUCCESS(mkdir(upper.c_str(), 0755));
    TEST_CHECK_SUCCESS(mkdir(work.c_str(), 0755));
    TEST_CHECK_SUCCESS(mkdir(merged.c_str(), 0755));

    // 1. Create the DESTINATION directory in lower.
    // (It must be empty so standard POSIX rename allows overwriting it).
    {
      auto lower_renamed = JoinPath(lower, "renamed");
      TEST_CHECK_SUCCESS(mkdir(lower_renamed.c_str(), 0755));
    }

    std::string opts = "lowerdir=" + lower + ",upperdir=" + upper +
                       ",workdir=" + work + ",userxattr";
    TEST_CHECK_SUCCESS(
        mount("overlay", merged.c_str(), "overlay", 0, opts.c_str()));
    auto overlayfs_cleanup =
        Cleanup([&merged] { TEST_CHECK_SUCCESS(umount2(merged.c_str(), 0)); });

    // 2. Create the SOURCE directory directly in merged.
    // Because it doesn't exist in lower, it is a "pure upper" directory.
    auto mydir = JoinPath(merged, "mydir");
    TEST_CHECK_SUCCESS(mkdir(mydir.c_str(), 0755));

    int fd =
        open(JoinPath(mydir, "file.txt").c_str(), O_WRONLY | O_CREAT, 0644);
    TEST_CHECK(fd >= 0);
    TEST_CHECK_SUCCESS(close(fd));

    // 3. Rename the pure upper directory over the existing lower directory.
    // - This avoids EXDEV on Linux because 'mydir' is pure upper.
    // - This forces the kernel to apply user.overlay.opaque="y" to 'renamed'
    //   so that 'lower/renamed' is hidden.
    auto renamed = JoinPath(merged, "renamed");
    TEST_CHECK_SUCCESS(rename(mydir.c_str(), renamed.c_str()));

    // 4. Verify the rename succeeded and contents are correct
    struct stat st;
    TEST_CHECK_SUCCESS(stat(renamed.c_str(), &st));
    TEST_CHECK(S_ISDIR(st.st_mode));

    // Original directory should be gone
    TEST_CHECK(stat(mydir.c_str(), &st) == -1 && errno == ENOENT);

    // File inside should have moved with the directory
    TEST_CHECK_SUCCESS(stat(JoinPath(renamed, "file.txt").c_str(), &st));
    TEST_CHECK(S_ISREG(st.st_mode));
  };

  EXPECT_THAT(InForkedUserMountNamespace(parent, child),
              IsPosixErrorOkAndHolds(0));
}

// Test that overlay can be mounted with a gofer upper layer. Runs some basic
// overlayfs tests to confirm basic functionality.
TEST(MountTest, OverlayfsOnGoferBehavior) {
  // Test fails in gVisor running on <6.0 due to fsgofer getting permission
  // errors on `mknod`.
  SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(GetHostKernelVersion()).major < 6);

  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto base_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
  // We only want to test overlayfs on gofer.
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsGoferfs(base_dir.path())));

  // Create directories for overlayfs mount.
  auto lower =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(base_dir.path()));
  auto upper =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(base_dir.path()));
  auto work = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(base_dir.path()));
  auto merged =
      ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(base_dir.path()));

  // Set up lower layer with some content.
  const std::string kTestFile = "testfile";
  const std::string kTestFileContent = "lower_content";
  std::string lower_file = JoinPath(lower.path(), kTestFile);
  ASSERT_NO_ERRNO(CreateWithContents(lower_file, kTestFileContent, 0644));
  const std::string kTestDirName = "testdir";
  std::string lower_dir = JoinPath(lower.path(), kTestDirName);
  ASSERT_THAT(mkdir(lower_dir.c_str(), 0755), SyscallSucceeds());
  const std::string kCopyUpFile = "copyupfile";
  std::string lower_copyup_file = JoinPath(lower.path(), kCopyUpFile);
  ASSERT_NO_ERRNO(CreateWithContents(lower_copyup_file, "initial", 0644));
  bool xattr_supported = true;
  if (setxattr(lower_copyup_file.c_str(), "user.test", "value", 5, 0) < 0) {
    if (errno == EOPNOTSUPP) {
      xattr_supported = false;
    } else {
      ASSERT_NO_ERRNO(PosixError(errno, "setxattr failed"));
    }
  }

  // Mount the overlay.
  std::string opts = "lowerdir=" + lower.path() + ",upperdir=" + upper.path() +
                     ",workdir=" + work.path() + ",userxattr";
  ASSERT_THAT(
      mount("overlay", merged.path().c_str(), "overlay", 0, opts.c_str()),
      SyscallSucceeds());
  auto cleanup = Cleanup([&merged] { umount2(merged.path().c_str(), 0); });

  // Read the lower layer file through the overlay.
  std::string merged_file = JoinPath(merged.path(), kTestFile);
  std::string merged_content;
  ASSERT_NO_ERRNO(GetContents(merged_file, &merged_content));
  EXPECT_EQ(merged_content, kTestFileContent);

  // Write a new file through the overlay and confirm it appears in the upper.
  const std::string kNewFile = "newfile";
  const std::string kNewFileContent = "new";
  std::string new_file = JoinPath(merged.path(), kNewFile);
  ASSERT_NO_ERRNO(CreateWithContents(new_file, kNewFileContent, 0644));
  std::string upper_new_file = JoinPath(upper.path(), kNewFile);
  std::string upper_new_content;
  ASSERT_NO_ERRNO(GetContents(upper_new_file, &upper_new_content));
  EXPECT_EQ(upper_new_content, kNewFileContent);

  // Delete the lower layer file through the overlay and confirm it creates a
  // whiteout in the upper.
  ASSERT_THAT(unlink(merged_file.c_str()), SyscallSucceeds());
  std::string whiteout = JoinPath(upper.path(), kTestFile);
  struct stat st;
  ASSERT_THAT(stat(whiteout.c_str(), &st), SyscallSucceeds());
  EXPECT_TRUE(S_ISCHR(st.st_mode));
  EXPECT_EQ(major(st.st_rdev), 0);
  EXPECT_EQ(minor(st.st_rdev), 0);

  // Delete the lower layer directory through the overlay and recreate it. This
  // should mark it opaque in the upper layer.
  std::string merged_dir = JoinPath(merged.path(), kTestDirName);
  ASSERT_THAT(rmdir(merged_dir.c_str()), SyscallSucceeds());
  ASSERT_THAT(mkdir(merged_dir.c_str(), 0755), SyscallSucceeds());

  std::string upper_dir = JoinPath(upper.path(), kTestDirName);
  char opaque_buf[16] = {};
  ASSERT_THAT(getxattr(upper_dir.c_str(), "user.overlay.opaque", opaque_buf,
                       sizeof(opaque_buf)),
              SyscallSucceeds());
  EXPECT_EQ(opaque_buf[0], 'y');

  // Test copy up on regular file.
  std::string merged_copyup_file = JoinPath(merged.path(), kCopyUpFile);
  ASSERT_NO_ERRNO(SetContents(merged_copyup_file, "modified"));
  if (xattr_supported) {
    // Confirm that the xattr is copied up with the file.
    char xattr_buf[16] = {};
    ASSERT_THAT(getxattr(merged_copyup_file.c_str(), "user.test", xattr_buf,
                         sizeof(xattr_buf)),
                SyscallSucceeds());
    EXPECT_STREQ(xattr_buf, "value");
  }
}

TEST(MountTest, PollMountInfo) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  for (const char* path : {"/proc/self/mounts", "/proc/self/mountinfo"}) {
    FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path, O_RDONLY));
    int epfd_raw = epoll_create1(0);
    ASSERT_GE(epfd_raw, 0) << "epoll_create1 failed: " << strerror(errno);
    FileDescriptor epfd(epfd_raw);

    struct epoll_event ev = {};
    ev.events = EPOLLPRI | EPOLLERR;
    ev.data.fd = fd.get();
    ASSERT_THAT(epoll_ctl(epfd.get(), EPOLL_CTL_ADD, fd.get(), &ev),
                SyscallSucceeds());

    // Drains one event and assert it has EPOLLPRI or EPOLLERR.
    auto expectEvent = [&](absl::string_view label) {
      struct epoll_event events[1] = {};
      int nfds = epoll_wait(epfd.get(), events, 1, 0);
      ASSERT_GE(nfds, 0) << label << ": epoll_wait failed: " << strerror(errno);
      ASSERT_EQ(nfds, 1) << label << ": expected 1 event";
      EXPECT_TRUE(events[0].events & (EPOLLPRI | EPOLLERR)) << label;
    };

    // Asserts no events pending.
    auto expectNoEvent = [&](absl::string_view label) {
      struct epoll_event events[1] = {};
      int nfds = epoll_wait(epfd.get(), events, 1, 0);
      ASSERT_GE(nfds, 0) << label << ": epoll_wait failed: " << strerror(errno);
      EXPECT_EQ(nfds, 0) << label;
    };

    // Initially, there should be no events.
    expectNoEvent("initial");

    // mount() triggers event.
    auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
    auto mnt =
        ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), kTmpfs, 0, "", 0));
    expectEvent("mount");

    // Event is consumed by poll itself, no read needed.
    expectNoEvent("after mount consumed");

    // remount triggers event.
    ASSERT_THAT(
        mount("", dir.path().c_str(), nullptr, MS_REMOUNT | MS_RDONLY, nullptr),
        SyscallSucceeds());
    expectEvent("remount");
    expectNoEvent("after remount consumed");

    // Undo the read-only remount so umount works cleanly.
    ASSERT_THAT(mount("", dir.path().c_str(), nullptr, MS_REMOUNT, nullptr),
                SyscallSucceeds());
    // Drain the remount-undo event.
    expectEvent("remount undo");

    // Umount triggers event.
    mnt.Release();
    ASSERT_THAT(umount2(dir.path().c_str(), 0), SyscallSucceeds());
    expectEvent("umount");
    expectNoEvent("after umount consumed");

    // Move mount triggers event.
    //
    // MS_MOVE is not allowed under a MS_SHARED parent mount, so we create a
    // private parent mount containing both src and dst.
    auto const privateParent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
    auto privateMount = ASSERT_NO_ERRNO_AND_VALUE(
        Mount("", privateParent.path(), kTmpfs, 0, "", 0));
    expectEvent("move setup private parent mount");

    ASSERT_THAT(mount("", privateParent.path().c_str(), "", MS_PRIVATE, ""),
                SyscallSucceeds());

    auto const src =
        ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(privateParent.path()));
    auto const dst =
        ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(privateParent.path()));
    auto srcMnt =
        ASSERT_NO_ERRNO_AND_VALUE(Mount("", src.path(), kTmpfs, 0, "", 0));
    expectEvent("move setup src mount");

    auto dstMnt = ASSERT_NO_ERRNO_AND_VALUE(
        Mount(src.path(), dst.path(), "", MS_MOVE, "", 0));
    srcMnt.Release();
    expectEvent("move mount");
    expectNoEvent("after move consumed");
  }
}

TEST(MountTest, MountInfoAfterUnshare) {
  SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));

  auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());

  for (const char* path : {"/proc/self/mounts", "/proc/self/mountinfo"}) {
    int pipefd[2];
    ASSERT_THAT(pipe(pipefd), SyscallSucceeds());

    FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(path, O_RDONLY));
    int epfd_raw = epoll_create1(0);
    ASSERT_GE(epfd_raw, 0) << "epoll_create1 failed: " << strerror(errno);
    FileDescriptor epfd(epfd_raw);

    struct epoll_event ev = {};
    ev.events = EPOLLPRI | EPOLLERR;
    ev.data.fd = fd.get();
    ASSERT_THAT(epoll_ctl(epfd.get(), EPOLL_CTL_ADD, fd.get(), &ev),
                SyscallSucceeds());

    // Initially, no events on fd.
    auto expectNoEvent = [](int epfd_raw) {
      struct epoll_event events[1] = {};
      int nfds = epoll_wait(epfd_raw, events, 1, 0);
      return nfds == 0;
    };
    ASSERT_TRUE(expectNoEvent(epfd.get()));

    pid_t child = fork();
    if (child == 0) {
      close(pipefd[1]);

      auto childExpectEvent = [](int epfd_raw) {
        struct epoll_event events[1] = {};
        int nfds = epoll_wait(epfd_raw, events, 1, 0);
        TEST_PCHECK(nfds == 1);
        TEST_CHECK(events[0].events & (EPOLLPRI | EPOLLERR));
      };

      // Unshare into a new mount namespace.
      TEST_PCHECK(unshare(CLONE_NEWNS) == 0);

      // Wait for the parent to perform a mount in the parent's mountns.
      char c;
      TEST_PCHECK(read(pipefd[0], &c, 1) == 1);
      close(pipefd[0]);

      // The inherited fd was opened before unshare(CLONE_NEWNS), so it should
      // see the mount event from the parent's mount namespace.
      childExpectEvent(epfd.get());

      // Reading from the inherited fd should still give info about the old
      // mount namespace (viz., where the parent mounted dir.path()).
      lseek(fd.get(), 0, SEEK_SET);
      char buf[65536];
      size_t total = 0;
      int n;
      while ((total < sizeof(buf) - 1) &&
             (n = read(fd.get(), buf + total, sizeof(buf) - 1 - total)) > 0) {
        total += n;
      }
      TEST_PCHECK(n >= 0);
      TEST_CHECK(absl::StrContains(absl::string_view(buf, total), dir.path()));

      _exit(0);
    }
    close(pipefd[0]);
    ASSERT_THAT(child, SyscallSucceeds());

    // Perform a mount in the parent's mount namespace.
    auto mnt =
        ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), kTmpfs, 0, "", 0));

    // Notify the child that the mount is complete.
    char c = 'a';
    ASSERT_THAT(write(pipefd[1], &c, 1), SyscallSucceedsWithValue(1));
    close(pipefd[1]);

    int status;
    ASSERT_THAT(waitpid(child, &status, 0), SyscallSucceedsWithValue(child));
    ASSERT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0);
  }
}

}  // namespace

}  // namespace testing
}  // namespace gvisor
