blob: 21ffd31bdb0dd2aa140162616df1d6e8b6bd90da [file] [log] [blame]
// Copyright 2023 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <dirent.h>
#include <fcntl.h>
#include <lib/fit/defer.h>
#include <stdio.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fstream>
#include <gtest/gtest.h>
#include "src/lib/files/directory.h"
#include "src/lib/files/file.h"
#include "src/lib/fxl/strings/string_printf.h"
#include "src/starnix/tests/syscalls/cpp/syscall_matchers.h"
#include "src/starnix/tests/syscalls/cpp/test_helper.h"
using testing::ElementsAre;
using testing::IsEmpty;
namespace {
struct DirEntry {
std::string name;
uint8_t type;
ino_t inode_num;
off_t offset;
static DirEntry Dir(const std::string& name) {
return DirEntry{
.name = name,
.type = DT_DIR,
};
}
static DirEntry CharDev(const std::string& name) {
return DirEntry{
.name = name,
.type = DT_CHR,
};
}
static DirEntry File(const std::string& name) {
return DirEntry{
.name = name,
.type = DT_REG,
};
}
bool operator<(const DirEntry& rhs) const { return name < rhs.name; }
bool operator==(const DirEntry& rhs) const { return name == rhs.name && type == rhs.type; }
};
std::ostream& operator<<(std::ostream& stream, DirEntry const& value) {
return stream << "DirEntry(\"" << value.name << "\", type=" << static_cast<int>(value.type)
<< ")";
}
std::vector<DirEntry> ReadDir(const std::string& path) {
std::vector<DirEntry> result;
DIR* dir = opendir(path.c_str());
if (!dir) {
ADD_FAILURE() << "opendir() failed for " << path << " errno=" << errno;
return result;
}
struct dirent* de;
errno = 0;
bool found_dot = false;
bool found_dot_dot = false;
while ((de = readdir(dir)) != nullptr) {
if (std::string(de->d_name) == ".") {
EXPECT_FALSE(found_dot);
EXPECT_EQ(de->d_type, DT_DIR);
found_dot = true;
} else if (std::string(de->d_name) == "..") {
EXPECT_FALSE(found_dot_dot);
EXPECT_EQ(de->d_type, DT_DIR);
found_dot_dot = true;
} else {
result.push_back(DirEntry{
.name = de->d_name,
.type = de->d_type,
.inode_num = de->d_ino,
.offset = de->d_off,
});
}
}
closedir(dir);
if (errno != 0) {
ADD_FAILURE() << "readdir() failed with errno=" << errno;
return result;
}
EXPECT_TRUE(found_dot);
EXPECT_TRUE(found_dot_dot);
std::sort(result.begin(), result.end());
return result;
}
std::string ReadFileContent(const std::string& path) {
std::string content;
EXPECT_TRUE(files::ReadFileToString(path.c_str(), &content));
return content;
}
bool IsWhiteout(const std::string& path) {
struct stat s;
return stat(path.c_str(), &s) == 0 && (s.st_mode & S_IFMT) == S_IFCHR && s.st_rdev == 0;
}
std::string Readlink(const std::string& path) {
std::string result;
result.resize(256);
ssize_t r = readlink(path.c_str(), result.data(), static_cast<int>(result.size()));
if (r < 0) {
ADD_FAILURE() << "readlink(" << path << ") failed with errno=" << errno;
return "";
}
result.resize(r);
return result;
}
class OverlayFsTest : public ::testing::Test {
protected:
void SetUp() override {
// TODO(https://fxbug.dev/317285180) don't skip on baseline
if (!test_helper::HasSysAdmin()) {
GTEST_SKIP() << "Not running with sysadmin capabilities, skipping suite.";
}
ASSERT_FALSE(temp_dir_.path().empty());
overlay_ = temp_dir_.path() + "/overlay";
ASSERT_THAT(mkdir(overlay_.c_str(), 0700), SyscallSucceeds());
lower_ = temp_dir_.path() + "/lower";
ASSERT_THAT(mkdir(lower_.c_str(), 0700), SyscallSucceeds());
upper_base_ = temp_dir_.path() + "/upper_base";
ASSERT_THAT(mkdir(upper_base_.c_str(), 0700), SyscallSucceeds());
ASSERT_NO_FATAL_FAILURE(InitUpperDirs());
}
void InitUpperDirs() {
upper_ = upper_base_ + "/upper";
ASSERT_THAT(mkdir(upper_.c_str(), 0700), SyscallSucceeds());
work_ = upper_base_ + "/work";
ASSERT_THAT(mkdir(work_.c_str(), 0700), SyscallSucceeds());
}
void Mount() {
std::string options = fxl::StringPrintf("lowerdir=%s,upperdir=%s,workdir=%s", lower_.c_str(),
upper_.c_str(), work_.c_str());
ASSERT_THAT(mount(nullptr, overlay_.c_str(), "overlay", 0, options.c_str()), SyscallSucceeds());
}
std::string ReadOverlayFileContent(const std::string& file) {
return ReadFileContent(overlay_ + file);
}
test_helper::ScopedTempDir temp_dir_;
std::string lower_;
std::string upper_;
std::string upper_base_;
std::string work_;
std::string overlay_;
};
TEST_F(OverlayFsTest, ListRoot) {
ASSERT_TRUE(files::WriteFile(lower_ + "/a", "lower/a"));
ASSERT_TRUE(files::WriteFile(lower_ + "/c", "lower/c"));
ASSERT_TRUE(files::WriteFile(upper_ + "/b", "upper/b"));
ASSERT_TRUE(files::WriteFile(upper_ + "/c", "upper/c"));
ASSERT_NO_FATAL_FAILURE(Mount());
std::vector<DirEntry> list = ReadDir(overlay_);
EXPECT_THAT(list, ElementsAre(DirEntry::File("a"), DirEntry::File("b"), DirEntry::File("c")));
std::vector<DirEntry> lower_list = ReadDir(lower_);
std::vector<DirEntry> upper_list = ReadDir(upper_);
// Inode number copied from the source file systems.
EXPECT_EQ(list[0].inode_num, lower_list[0].inode_num);
EXPECT_EQ(list[1].inode_num, upper_list[0].inode_num);
EXPECT_EQ(list[2].inode_num, upper_list[1].inode_num);
}
TEST_F(OverlayFsTest, ListSubdir) {
ASSERT_THAT(mkdir((lower_ + "/sub").c_str(), 0700), SyscallSucceeds());
ASSERT_TRUE(files::WriteFile(lower_ + "/sub/a", "a"));
ASSERT_THAT(mkdir((upper_ + "/sub").c_str(), 0700), SyscallSucceeds());
ASSERT_TRUE(files::WriteFile(upper_ + "/sub/b", "b"));
ASSERT_NO_FATAL_FAILURE(Mount());
EXPECT_EQ(ReadDir(overlay_ + "/sub"), (std::vector{DirEntry::File("a"), DirEntry::File("b")}));
}
TEST_F(OverlayFsTest, DirAndFile) {
ASSERT_TRUE(files::WriteFile(lower_ + "/sub", "a"));
ASSERT_THAT(mkdir((upper_ + "/sub").c_str(), 0700), SyscallSucceeds());
ASSERT_TRUE(files::WriteFile(upper_ + "/sub/b", "b"));
ASSERT_NO_FATAL_FAILURE(Mount());
EXPECT_EQ(ReadDir(overlay_), (std::vector{DirEntry::Dir("sub")}));
EXPECT_EQ(ReadDir(overlay_ + "/sub"), (std::vector{DirEntry::File("b")}));
EXPECT_EQ(ReadOverlayFileContent("/sub/b"), "b");
}
TEST_F(OverlayFsTest, ReadFileLower) {
ASSERT_TRUE(files::WriteFile(lower_ + "/a", "1"));
ASSERT_NO_FATAL_FAILURE(Mount());
EXPECT_EQ(ReadOverlayFileContent("/a"), "1");
}
TEST_F(OverlayFsTest, ReadFileUpper) {
ASSERT_TRUE(files::WriteFile(upper_ + "/a", "2"));
ASSERT_NO_FATAL_FAILURE(Mount());
EXPECT_EQ(ReadOverlayFileContent("/a"), "2");
}
TEST_F(OverlayFsTest, ReadFileBoth) {
ASSERT_TRUE(files::WriteFile(lower_ + "/a", "1"));
ASSERT_TRUE(files::WriteFile(upper_ + "/a", "2"));
ASSERT_NO_FATAL_FAILURE(Mount());
EXPECT_EQ(ReadOverlayFileContent("/a"), "2");
}
TEST_F(OverlayFsTest, UnmountBase) {
ASSERT_THAT(mount(nullptr, lower_.c_str(), "tmpfs", 0, nullptr), SyscallSucceeds());
ASSERT_THAT(mount(nullptr, upper_base_.c_str(), "tmpfs", 0, nullptr), SyscallSucceeds());
ASSERT_NO_FATAL_FAILURE(InitUpperDirs());
ASSERT_TRUE(files::WriteFile(lower_ + "/a", "1"));
ASSERT_TRUE(files::WriteFile(upper_ + "/b", "2"));
ASSERT_NO_FATAL_FAILURE(Mount());
// We should be able to unmount base file systems. Overlay should still be accessible.
ASSERT_THAT(umount(lower_.c_str()), SyscallSucceeds());
ASSERT_THAT(umount(upper_base_.c_str()), SyscallSucceeds());
EXPECT_EQ(ReadOverlayFileContent("/a"), "1");
EXPECT_EQ(ReadOverlayFileContent("/b"), "2");
}
TEST_F(OverlayFsTest, MountSubdir) {
ASSERT_THAT(mkdir((lower_ + "/sub").c_str(), 0700), SyscallSucceeds());
ASSERT_TRUE(files::WriteFile(lower_ + "/sub/a", "1"));
ASSERT_THAT(mkdir((upper_ + "/sub").c_str(), 0700), SyscallSucceeds());
ASSERT_TRUE(files::WriteFile(upper_ + "/sub/b", "2"));
// Mount new FS over the subdirs. That shouldn't affect the files visible to overlayfs.
ASSERT_THAT(mount(nullptr, (lower_ + "/sub").c_str(), "tmpfs", 0, nullptr), SyscallSucceeds());
ASSERT_THAT(mount(nullptr, (upper_ + "/sub").c_str(), "tmpfs", 0, nullptr), SyscallSucceeds());
ASSERT_TRUE(files::WriteFile(upper_ + "/sub/c", "3"));
ASSERT_NO_FATAL_FAILURE(Mount());
EXPECT_EQ(ReadOverlayFileContent("/sub/a"), "1");
EXPECT_EQ(ReadOverlayFileContent("/sub/b"), "2");
EXPECT_EQ(ReadDir(overlay_ + "/sub"), (std::vector{DirEntry::File("a"), DirEntry::File("b")}));
}
TEST_F(OverlayFsTest, RemovedFiles) {
// Char device with `type=0` is a removed file marker. It should not be listed in the
// overlay FS.
ASSERT_THAT(mknod((lower_ + "/a").c_str(), S_IFCHR, 0), SyscallSucceeds());
ASSERT_THAT(mknod((lower_ + "/b").c_str(), S_IFCHR, 1), SyscallSucceeds());
ASSERT_THAT(mknod((upper_ + "/c").c_str(), S_IFCHR, 0), SyscallSucceeds());
ASSERT_THAT(mknod((upper_ + "/d").c_str(), S_IFCHR, 1), SyscallSucceeds());
ASSERT_NO_FATAL_FAILURE(Mount());
EXPECT_EQ(ReadDir(overlay_), (std::vector{DirEntry::CharDev("b"), DirEntry::CharDev("d")}));
}
TEST_F(OverlayFsTest, UnlinkLower) {
ASSERT_THAT(mkdir((lower_ + "/s").c_str(), 0700), SyscallSucceeds());
ASSERT_TRUE(files::WriteFile(lower_ + "/s/a", "a"));
ASSERT_NO_FATAL_FAILURE(Mount());
// Remove the file.
ASSERT_THAT(unlink((overlay_ + "/s/a").c_str()), SyscallSucceeds());
EXPECT_THAT(ReadDir(overlay_ + "/s"), IsEmpty());
EXPECT_THAT(open((overlay_ + "/s/a").c_str(), O_RDONLY), SyscallFailsWithErrno(ENOENT));
// Whiteout should be created in the upper dir.
EXPECT_TRUE(IsWhiteout(upper_ + "/s/a"));
// Remove the directory.
ASSERT_THAT(rmdir((overlay_ + "/s").c_str()), SyscallSucceeds());
EXPECT_THAT(ReadDir(overlay_), IsEmpty());
EXPECT_THAT(open((overlay_ + "/s").c_str(), O_RDONLY), SyscallFailsWithErrno(ENOENT));
// Whiteout should be created in the upper dir.
EXPECT_TRUE(IsWhiteout(upper_ + "/s"));
// Lower file should not be touched.
EXPECT_EQ(ReadFileContent(lower_ + "/s/a"), "a");
}
TEST_F(OverlayFsTest, UnlinkNonEmptyDir) {
ASSERT_THAT(mkdir((upper_ + "/d").c_str(), 0700), SyscallSucceeds());
ASSERT_THAT(mkdir((lower_ + "/d").c_str(), 0700), SyscallSucceeds());
ASSERT_TRUE(files::WriteFile(lower_ + "/d/a", "a"));
ASSERT_NO_FATAL_FAILURE(Mount());
// Try removing non-empty dir. This is expected to fail.
ASSERT_THAT(rmdir((overlay_ + "/d").c_str()), SyscallFailsWithErrno(ENOTEMPTY));
// Directory should still exist.
EXPECT_EQ(ReadDir(overlay_), (std::vector{DirEntry::Dir("d")}));
EXPECT_EQ(ReadDir(overlay_ + "/d"), (std::vector{DirEntry::File("a")}));
// No changes to the lower and upper FS.
EXPECT_THAT(ReadDir(upper_ + "/d"), IsEmpty());
EXPECT_EQ(ReadDir(lower_ + "/d"), (std::vector{DirEntry::File("a")}));
}
TEST_F(OverlayFsTest, UnlinkUpper) {
ASSERT_THAT(mkdir((upper_ + "/s").c_str(), 0700), SyscallSucceeds());
ASSERT_TRUE(files::WriteFile(upper_ + "/s/a", "a"));
ASSERT_NO_FATAL_FAILURE(Mount());
ASSERT_THAT(unlink((overlay_ + "/s/a").c_str()), SyscallSucceeds());
EXPECT_THAT(ReadDir(overlay_ + "/s"), IsEmpty());
EXPECT_THAT(open((overlay_ + "/s/a").c_str(), O_RDONLY), SyscallFailsWithErrno(ENOENT));
EXPECT_THAT(ReadDir(lower_), IsEmpty());
EXPECT_THAT(ReadDir(upper_ + "/s"), IsEmpty());
// Remove the directory.
ASSERT_THAT(rmdir((overlay_ + "/s").c_str()), SyscallSucceeds());
EXPECT_THAT(open((overlay_ + "/s").c_str(), O_RDONLY), SyscallFailsWithErrno(ENOENT));
EXPECT_THAT(ReadDir(overlay_), IsEmpty());
EXPECT_THAT(ReadDir(lower_), IsEmpty());
EXPECT_THAT(ReadDir(upper_), IsEmpty());
}
TEST_F(OverlayFsTest, UnlinkBoth) {
ASSERT_THAT(mkdir((lower_ + "/s").c_str(), 0700), SyscallSucceeds());
ASSERT_TRUE(files::WriteFile(lower_ + "/s/a", "a"));
ASSERT_THAT(mkdir((upper_ + "/s").c_str(), 0700), SyscallSucceeds());
ASSERT_TRUE(files::WriteFile(upper_ + "/s/a", "a"));
ASSERT_NO_FATAL_FAILURE(Mount());
// Remove the file.
ASSERT_THAT(unlink((overlay_ + "/s/a").c_str()), SyscallSucceeds());
EXPECT_THAT(ReadDir(overlay_ + "/s"), IsEmpty());
EXPECT_THAT(open((overlay_ + "/s/a").c_str(), O_RDONLY), SyscallFailsWithErrno(ENOENT));
// Whiteout should be created in the upper dir.
EXPECT_TRUE(IsWhiteout(upper_ + "/s/a"));
// Remove the directory.
ASSERT_THAT(rmdir((overlay_ + "/s").c_str()), SyscallSucceeds());
EXPECT_THAT(ReadDir(overlay_), IsEmpty());
EXPECT_THAT(open((overlay_ + "/s").c_str(), O_RDONLY), SyscallFailsWithErrno(ENOENT));
// Whiteout should be created in the upper dir.
EXPECT_TRUE(IsWhiteout(upper_ + "/s"));
// Lower file should not be touched.
EXPECT_EQ(ReadFileContent(lower_ + "/s/a"), "a");
}
TEST_F(OverlayFsTest, RecreateDir) {
ASSERT_THAT(mkdir((lower_ + "/s").c_str(), 0700), SyscallSucceeds());
ASSERT_TRUE(files::WriteFile(lower_ + "/s/a", "a"));
ASSERT_NO_FATAL_FAILURE(Mount());
// Remove the file.
ASSERT_THAT(unlink((overlay_ + "/s/a").c_str()), SyscallSucceeds());
EXPECT_THAT(ReadDir(overlay_ + "/s"), IsEmpty());
// Whiteout should be created in the upper dir.
EXPECT_TRUE(IsWhiteout(upper_ + "/s/a"));
// Remove the directory.
ASSERT_THAT(rmdir((overlay_ + "/s").c_str()), SyscallSucceeds());
EXPECT_THAT(ReadDir(overlay_), IsEmpty());
// Whiteout should be created in the upper dir.
EXPECT_TRUE(IsWhiteout(upper_ + "/s"));
// Create the dir again.
ASSERT_THAT(mkdir((overlay_ + "/s").c_str(), 0700), SyscallSucceeds());
// The new dir should be empty.
EXPECT_THAT(ReadDir(overlay_ + "/s"), IsEmpty());
EXPECT_THAT(open((overlay_ + "/s/a").c_str(), O_RDONLY), SyscallFailsWithErrno(ENOENT));
// Lower file should not be touched.
EXPECT_EQ(ReadFileContent(lower_ + "/s/a"), "a");
}
TEST_F(OverlayFsTest, RecreateFile) {
ASSERT_THAT(mkdir((lower_ + "/s").c_str(), 0700), SyscallSucceeds());
ASSERT_TRUE(files::WriteFile(lower_ + "/s/a", "a"));
ASSERT_NO_FATAL_FAILURE(Mount());
// Remove the file.
ASSERT_THAT(unlink((overlay_ + "/s/a").c_str()), SyscallSucceeds());
EXPECT_THAT(ReadDir(overlay_ + "/s"), IsEmpty());
EXPECT_THAT(open((overlay_ + "/s/a").c_str(), O_RDONLY), SyscallFailsWithErrno(ENOENT));
// Try creating the file again
ASSERT_TRUE(files::WriteFile(overlay_ + "/s/a", "b"));
EXPECT_EQ(ReadFileContent(overlay_ + "/s/a"), "b");
// It should be written to the upper dir.
EXPECT_EQ(ReadFileContent(upper_ + "/s/a"), "b");
// Lower file should not be touched.
EXPECT_EQ(ReadFileContent(lower_ + "/s/a"), "a");
}
TEST_F(OverlayFsTest, CreateDir) {
ASSERT_THAT(mkdir((lower_ + "/d1").c_str(), 0700), SyscallSucceeds());
ASSERT_THAT(mkdir((lower_ + "/d2").c_str(), 0700), SyscallSucceeds());
ASSERT_THAT(mkdir((upper_ + "/d1").c_str(), 0700), SyscallSucceeds());
ASSERT_THAT(mkdir((upper_ + "/d3").c_str(), 0700), SyscallSucceeds());
ASSERT_NO_FATAL_FAILURE(Mount());
// Make new dirs.
ASSERT_THAT(mkdir((overlay_ + "/d1/1").c_str(), 0700), SyscallSucceeds());
ASSERT_THAT(mkdir((overlay_ + "/d2/2").c_str(), 0700), SyscallSucceeds());
ASSERT_THAT(mkdir((overlay_ + "/d3/3").c_str(), 0700), SyscallSucceeds());
// Verify that the dirs were created.
EXPECT_EQ(ReadDir(overlay_ + "/d1"), (std::vector{DirEntry::Dir("1")}));
EXPECT_EQ(ReadDir(overlay_ + "/d2"), (std::vector{DirEntry::Dir("2")}));
EXPECT_EQ(ReadDir(overlay_ + "/d3"), (std::vector{DirEntry::Dir("3")}));
// New dirs are empty.
EXPECT_THAT(ReadDir(overlay_ + "/d1/1"), IsEmpty());
EXPECT_THAT(ReadDir(overlay_ + "/d2/2"), IsEmpty());
EXPECT_THAT(ReadDir(overlay_ + "/d3/3"), IsEmpty());
// Lower FS should not change.
EXPECT_EQ(ReadDir(lower_), (std::vector{DirEntry::Dir("d1"), DirEntry::Dir("d2")}));
EXPECT_THAT(ReadDir(lower_ + "/d1"), IsEmpty());
EXPECT_THAT(ReadDir(lower_ + "/d2"), IsEmpty());
// New parent dir created in the upper FS.
EXPECT_EQ(ReadDir(upper_),
(std::vector{DirEntry::Dir("d1"), DirEntry::Dir("d2"), DirEntry::Dir("d3")}));
}
TEST_F(OverlayFsTest, CreateFile) {
ASSERT_THAT(mkdir((lower_ + "/d").c_str(), 0700), SyscallSucceeds());
ASSERT_NO_FATAL_FAILURE(Mount());
ASSERT_TRUE(files::WriteFile(overlay_ + "/d/a", "foo"));
EXPECT_EQ(ReadFileContent(overlay_ + "/d/a"), "foo");
// New file should be created in the upper dir.
EXPECT_EQ(ReadDir(upper_), (std::vector{DirEntry::Dir("d")}));
EXPECT_EQ(ReadFileContent(upper_ + "/d/a"), "foo");
// Lower FS should not change.
EXPECT_THAT(ReadDir(lower_ + "/d"), IsEmpty());
}
TEST_F(OverlayFsTest, CreateSymlink) {
ASSERT_THAT(mkdir((lower_ + "/d").c_str(), 0700), SyscallSucceeds());
ASSERT_NO_FATAL_FAILURE(Mount());
ASSERT_THAT(symlink("symlink_target", (overlay_ + "/d/a").c_str()), SyscallSucceeds());
EXPECT_EQ(Readlink(overlay_ + "/d/a"), "symlink_target");
// New file should be created in the upper dir.
EXPECT_EQ(ReadDir(upper_), (std::vector{DirEntry::Dir("d")}));
EXPECT_EQ(Readlink(upper_ + "/d/a"), "symlink_target");
// Lower FS should not change.
EXPECT_THAT(ReadDir(lower_ + "/d"), IsEmpty());
}
TEST_F(OverlayFsTest, RewriteFile) {
ASSERT_THAT(mkdir((lower_ + "/d").c_str(), 0700), SyscallSucceeds());
ASSERT_TRUE(files::WriteFile(lower_ + "/d/a", "a"));
ASSERT_NO_FATAL_FAILURE(Mount());
ASSERT_TRUE(files::WriteFile(overlay_ + "/d/a", "foo"));
EXPECT_EQ(ReadFileContent(overlay_ + "/d/a"), "foo");
// New file should be created in the upper dir.
EXPECT_EQ(ReadDir(upper_), (std::vector{DirEntry::Dir("d")}));
EXPECT_EQ(ReadFileContent(upper_ + "/d/a"), "foo");
// Lower FS should not change.
EXPECT_EQ(ReadFileContent(lower_ + "/d/a"), "a");
}
TEST_F(OverlayFsTest, AppendFile) {
ASSERT_THAT(mkdir((lower_ + "/d").c_str(), 0700), SyscallSucceeds());
ASSERT_TRUE(files::WriteFile(lower_ + "/d/a", "a-"));
ASSERT_NO_FATAL_FAILURE(Mount());
{
std::ofstream stream;
stream.open(overlay_ + "/d/a", std::ios_base::app);
stream << "foo";
ASSERT_FALSE(stream.fail());
}
EXPECT_EQ(ReadFileContent(overlay_ + "/d/a"), "a-foo");
// New file should be created in the upper dir.
EXPECT_EQ(ReadDir(upper_), (std::vector{DirEntry::Dir("d")}));
EXPECT_EQ(ReadFileContent(upper_ + "/d/a"), "a-foo");
// Lower FS should not change.
EXPECT_EQ(ReadFileContent(lower_ + "/d/a"), "a-");
}
TEST_F(OverlayFsTest, Link) {
ASSERT_THAT(mkdir((lower_ + "/d").c_str(), 0700), SyscallSucceeds());
ASSERT_TRUE(files::WriteFile(lower_ + "/d/a", "a"));
ASSERT_NO_FATAL_FAILURE(Mount());
ASSERT_THAT(link((overlay_ + "/d/a").c_str(), (overlay_ + "/d/b").c_str()), SyscallSucceeds());
// New file should be created in the upper dir.
auto list = ReadDir(upper_ + "/d");
EXPECT_EQ(list, (std::vector{DirEntry::File("a"), DirEntry::File("b")}));
EXPECT_EQ(list[0].inode_num, list[1].inode_num);
// Write new content. Both files should be updated.
ASSERT_TRUE(files::WriteFile(overlay_ + "/d/a", "new"));
EXPECT_EQ(ReadFileContent(upper_ + "/d/a"), "new");
EXPECT_EQ(ReadFileContent(upper_ + "/d/b"), "new");
// Lower FS should not change.
EXPECT_EQ(ReadDir(lower_ + "/d"), (std::vector{DirEntry::File("a")}));
EXPECT_EQ(ReadFileContent(lower_ + "/d/a"), "a");
}
TEST_F(OverlayFsTest, UpdateAfterOpen) {
ASSERT_TRUE(files::WriteFile(lower_ + "/a", "a"));
ASSERT_NO_FATAL_FAILURE(Mount());
std::ifstream file(overlay_ + "/a", std::ios::in);
ASSERT_TRUE(files::WriteFile(overlay_ + "/a", "updated"));
// Start reading from `file`. We should get new content, written after the file was open.
std::string data;
file >> data;
EXPECT_EQ(data, "updated");
}
TEST_F(OverlayFsTest, RenameFile) {
ASSERT_THAT(mkdir((lower_ + "/d1").c_str(), 0700), SyscallSucceeds());
ASSERT_TRUE(files::WriteFile(lower_ + "/d1/a", "a"));
ASSERT_NO_FATAL_FAILURE(Mount());
ASSERT_THAT(mkdir((overlay_ + "/d2").c_str(), 0700), SyscallSucceeds());
ASSERT_THAT(rename((overlay_ + "/d1/a").c_str(), (overlay_ + "/d2/b").c_str()),
SyscallSucceeds());
EXPECT_EQ(ReadDir(overlay_), (std::vector{DirEntry::Dir("d1"), DirEntry::Dir("d2")}));
EXPECT_EQ(ReadFileContent(overlay_ + "/d2/b"), "a");
// New file should be created in the upper FS.
EXPECT_EQ(ReadFileContent(upper_ + "/d2/b"), "a");
// Lower FS should not change.
EXPECT_EQ(ReadDir(lower_), (std::vector{DirEntry::Dir("d1")}));
EXPECT_EQ(ReadFileContent(lower_ + "/d1/a"), "a");
}
TEST_F(OverlayFsTest, RenameNonexistent) {
ASSERT_THAT(mkdir((lower_ + "/d1").c_str(), 0700), SyscallSucceeds());
ASSERT_TRUE(files::WriteFile(lower_ + "/d1/a", "a"));
ASSERT_NO_FATAL_FAILURE(Mount());
ASSERT_THAT(rename((overlay_ + "/d1/b").c_str(), (overlay_ + "/d1/c").c_str()),
SyscallFailsWithErrno(ENOENT));
}
TEST_F(OverlayFsTest, RenameDir) {
ASSERT_THAT(mkdir((lower_ + "/d1").c_str(), 0700), SyscallSucceeds());
ASSERT_TRUE(files::WriteFile(lower_ + "/d1/a", "a"));
ASSERT_THAT(mkdir((upper_ + "/d1").c_str(), 0700), SyscallSucceeds());
ASSERT_TRUE(files::WriteFile(upper_ + "/d1/a", "a"));
ASSERT_NO_FATAL_FAILURE(Mount());
ASSERT_THAT(rename((overlay_ + "/d1").c_str(), (overlay_ + "/d2").c_str()),
SyscallFailsWithErrno(EXDEV));
// Lower FS should not change.
EXPECT_EQ(ReadDir(lower_), (std::vector{DirEntry::Dir("d1")}));
EXPECT_EQ(ReadFileContent(lower_ + "/d1/a"), "a");
}
} // namespace