libcryfs/test/cryfs/impl/filesystem/CryNodeTest_RenameNested.cpp
2022-12-19 22:38:55 +01:00

138 lines
4.8 KiB
C++

// This test tests various ways of renaming files in nested directory structures.
// It tests both that the rename operation succeeds, and that it doesn't deadlock
// This is important because our CryNode implementation accesses multiple blobs
// (source, source_parent, target_parent, target_grandparent) and if any of those
// overlap, we need to make sure that we don't deadlock by trying to load them
// at the same time. This is also why these tests nest quite deeply.
#include <gtest/gtest.h>
#include "testutils/CryTestBase.h"
#include <cryfs/impl/filesystem/CryDir.h>
#include <cryfs/impl/filesystem/CryFile.h>
#include <cryfs/impl/filesystem/CryOpenFile.h>
#include <fspp/fs_interface/FuseErrnoException.h>
#include <boost/algorithm/string/predicate.hpp>
using ::testing::Combine;
using ::testing::ValuesIn;
using ::testing::TestWithParam;
using std::vector;
namespace bf = boost::filesystem;
using fspp::fuse::FuseErrnoException;
namespace {
vector<bf::path> SourceDirs() {
return {
"/",
"/a1",
"/a1/b1",
"/a1/b1/c1",
"/a1/b1/c1/d1",
"/a1/b1/c1/d1/e1",
"/a1/b1/c1/d1/e1/f1",
};
}
vector<bf::path> DestDirs() {
auto result = SourceDirs();
result.push_back("/a2");
result.push_back("/a2/b");
result.push_back("/a2/b/c");
result.push_back("/a2/b/c/d");
result.push_back("/a2/b/c/d/e");
result.push_back("/a2/b/c/d/e/f");
result.push_back("/a1/b2");
result.push_back("/a1/b2/c");
result.push_back("/a1/b2/c/d");
result.push_back("/a1/b2/c/d/e");
result.push_back("/a1/b2/c/d/e/f");
result.push_back("/a1/b1/c2");
result.push_back("/a1/b1/c2/d");
result.push_back("/a1/b1/c2/d/e");
result.push_back("/a1/b1/c2/d/e/f");
result.push_back("/a1/b1/c1/d2");
result.push_back("/a1/b1/c1/d2/e");
result.push_back("/a1/b1/c1/d2/e/f");
result.push_back("/a1/b1/c1/d1/e2");
result.push_back("/a1/b1/c1/d1/e2/f");
result.push_back("/a1/b1/c1/d1/e1/f2");
return result;
}
}
class CryNodeTest_RenameNested : public TestWithParam<std::tuple<bf::path, bf::path>>, public CryTestBase {
public:
void CreateDirs() {
for (const auto& dir : SourceDirs()) {
if (dir != "/") {
CreateDir(dir);
}
}
}
// NOLINTNEXTLINE(misc-no-recursion)
void create_path_if_not_exists(const bf::path& path) {
if (!Exists(path)) {
if (path.has_parent_path()) {
create_path_if_not_exists(path.parent_path());
}
CreateDir(path);
}
}
void expect_rename_succeeds(const bf::path& source_path, const bf::path& dest_path) {
auto source = device().Load(source_path).value();
source->rename(dest_path);
// Test that rename was successful
if (source_path != dest_path && source_path != "/") {
ASSERT_FALSE(Exists(source_path));
}
ASSERT_TRUE(Exists(dest_path));
}
void expect_rename_fails(const bf::path& source_path, const bf::path& dest_path, int expected_errno) {
auto source = device().Load(source_path).value();
try {
source->rename(dest_path);
ASSERT(false, "Expected throw FuseErrnoException(" + std::to_string(expected_errno) + " but didn't throw");
} catch (const FuseErrnoException& e) {
ASSERT_EQ(expected_errno, e.getErrno());
}
// Test rename wasn't successful
if (dest_path != "/") {
ASSERT_TRUE(Exists(source_path));
}
auto source_dirs = SourceDirs();
if (source_path != dest_path && source_path != "/" && std::find(source_dirs.begin(), source_dirs.end(), dest_path) == source_dirs.end()) {
ASSERT_FALSE(Exists(dest_path));
}
}
};
INSTANTIATE_TEST_SUITE_P(All, CryNodeTest_RenameNested, Combine(ValuesIn(SourceDirs()), ValuesIn(DestDirs())));
TEST_P(CryNodeTest_RenameNested, Rename) {
CreateDirs();
auto source_path = std::get<0>(GetParam());
auto dest_path = std::get<1>(GetParam());
if (dest_path.has_parent_path()) {
create_path_if_not_exists(dest_path.parent_path());
}
if (source_path == "/" || dest_path == "/") {
expect_rename_fails(source_path, dest_path, EBUSY);
} else if (source_path == dest_path) { // NOLINT(bugprone-branch-clone)
expect_rename_succeeds(source_path, dest_path);
} else if (boost::starts_with(source_path, dest_path)) {
expect_rename_fails(source_path, dest_path, ENOTEMPTY);
} else if (boost::starts_with(dest_path, source_path)) {
expect_rename_fails(source_path, dest_path, EINVAL);
} else {
expect_rename_succeeds(source_path, dest_path);
}
}