mirror of
https://github.com/nestriness/cdc-file-transfer.git
synced 2026-01-30 12:25:35 +02:00
The tools allow efficient and fast synchronization of large directory trees from a Windows workstation to a Linux target machine. cdc_rsync* support efficient copy of files by using content-defined chunking (CDC) to identify chunks within files that can be reused. asset_stream_manager + cdc_fuse_fs support efficient streaming of a local directory to a remote virtual file system based on FUSE. It also employs CDC to identify and reuse unchanged data chunks.
1147 lines
41 KiB
C++
1147 lines
41 KiB
C++
// Copyright 2022 Google LLC
|
|
//
|
|
// 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 "cdc_fuse_fs/cdc_fuse_fs.h"
|
|
|
|
#include <memory>
|
|
#include <vector>
|
|
|
|
#include "cdc_fuse_fs/mock_libfuse.h"
|
|
#include "common/log.h"
|
|
#include "common/path.h"
|
|
#include "common/status_test_macros.h"
|
|
#include "data_store/mem_data_store.h"
|
|
#include "gtest/gtest.h"
|
|
#include "manifest/fake_manifest_builder.h"
|
|
|
|
namespace cdc_ft {
|
|
|
|
// FUSE callback methods. Declared here since they depend on Fuse types that
|
|
// should not be exposed in cdc_fuse_fs.h.
|
|
void CdcFuseForget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup);
|
|
void CdcFuseForgetMulti(fuse_req_t req, size_t count,
|
|
struct fuse_forget_data* forgets);
|
|
void CdcFuseGetAttr(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi);
|
|
void CdcFuseLookup(fuse_req_t req, fuse_ino_t parent_ino, const char* name);
|
|
void CdcFuseOpen(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi);
|
|
void CdcFuseOpenDir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi);
|
|
void CdcFuseRead(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
|
|
struct fuse_file_info* fi);
|
|
void CdcFuseReadDir(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
|
|
fuse_file_info* fi);
|
|
void CdcFuseRelease(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi);
|
|
void CdcFuseReleaseDir(fuse_req_t req, fuse_ino_t ino,
|
|
struct fuse_file_info* fi);
|
|
size_t CdcFuseGetInodeCountForTesting();
|
|
size_t CdcFuseGetInvalidInodeCountForTesting();
|
|
void CdcFuseAccess(fuse_req_t req, fuse_ino_t ino, int mask);
|
|
|
|
namespace {
|
|
|
|
class FuseLog : public ConsoleLog {
|
|
public:
|
|
explicit FuseLog(LogLevel log_level) : ConsoleLog(log_level) {}
|
|
|
|
const std::string& LastMessage() const { return last_message_; }
|
|
|
|
protected:
|
|
void WriteLogMessage(LogLevel level, const char* file, int line,
|
|
const char* func, const char* message) override {
|
|
ConsoleLog::WriteLogMessage(level, file, line, func, message);
|
|
last_message_ = message;
|
|
}
|
|
|
|
private:
|
|
std::string last_message_;
|
|
};
|
|
|
|
class CdcFuseFsTest : public ::testing::Test {
|
|
protected:
|
|
static constexpr char kFile1Name[] = "file1.txt";
|
|
static constexpr uint32_t kFile1Perm = path::MODE_IRUSR;
|
|
static constexpr int64_t kFile1Mtime = 1ull << 40;
|
|
const std::vector<char> kFile1Data = {'f', 'i', 'l', 'e', '1'};
|
|
|
|
static constexpr char kFile2Name[] = "file2.txt";
|
|
static constexpr uint32_t kFile2Perm = path::MODE_IRWXU;
|
|
static constexpr int64_t kFile2Mtime = -kFile1Mtime;
|
|
const std::vector<char> kFile2Data = {'H', 'e', 'l', 'l', 'o', ' ',
|
|
'W', 'o', 'r', 'l', 'd', '!'};
|
|
|
|
static constexpr char kSubdirName[] = "subdir";
|
|
static constexpr uint32_t kSubdirPerm = path::MODE_IRUSR | path::MODE_IXUSR;
|
|
static constexpr int64_t kSubdirMtime = 0;
|
|
|
|
static constexpr char kWorldFile[] = "world_file.txt";
|
|
static constexpr char kGroupFile[] = "group_file.txt";
|
|
static constexpr char kUserFile[] = "user_file.txt";
|
|
|
|
public:
|
|
CdcFuseFsTest() : builder_(&cache_) {
|
|
cdc_fuse_fs::Initialize(0, nullptr).IgnoreError();
|
|
Log::Initialize(std::make_unique<FuseLog>(LogLevel::kInfo));
|
|
}
|
|
~CdcFuseFsTest() {
|
|
Log::Shutdown();
|
|
cdc_fuse_fs::Shutdown();
|
|
}
|
|
|
|
void SetUp() override {
|
|
// Set up an in-memory directory structure for testing.
|
|
// - file1.txt
|
|
// - subdir
|
|
// |
|
|
// - file2.txt
|
|
builder_.AddFile(builder_.Root(), kFile1Name, kFile1Mtime, kFile1Perm,
|
|
kFile1Data);
|
|
AssetProto* subdir = builder_.AddDirectory(builder_.Root(), kSubdirName,
|
|
kSubdirMtime, kSubdirPerm);
|
|
builder_.AddFile(subdir, kFile2Name, kFile2Mtime, kFile2Perm, kFile2Data);
|
|
|
|
manifest_id_ = cache_.AddProto(*builder_.Manifest());
|
|
fuse_.SetUid(internal::kCdcFuseCloudcastUid);
|
|
fuse_.SetGid(internal::kCdcFuseCloudcastGid);
|
|
|
|
// Note: Run(&cache_) immediately exits after setting the provider/id on
|
|
// Windows.
|
|
EXPECT_OK(cdc_fuse_fs::Run(&cache_, true));
|
|
EXPECT_OK(cdc_fuse_fs::SetManifest(manifest_id_));
|
|
EXPECT_EQ("FUSE consistency check succeeded", Log()->LastMessage());
|
|
}
|
|
|
|
void TearDown() override {}
|
|
|
|
protected:
|
|
void ExpectAttr(const struct stat& attr, uint32_t mode, uint64_t size,
|
|
int64_t mtime) {
|
|
EXPECT_NE(attr.st_ino, 0);
|
|
EXPECT_EQ(attr.st_mode, mode);
|
|
EXPECT_EQ(attr.st_size, size);
|
|
EXPECT_EQ(attr.st_nlink, internal::kCdcFuseDefaultNLink);
|
|
EXPECT_EQ(attr.st_mtime, mtime);
|
|
EXPECT_EQ(attr.st_uid, internal::kCdcFuseCloudcastUid);
|
|
EXPECT_EQ(attr.st_gid, internal::kCdcFuseCloudcastGid);
|
|
}
|
|
|
|
void ExpectAccessError(int mask, int exp_error) {
|
|
size_t num_errors = fuse_.errors.size();
|
|
for (size_t it = 0; it < fuse_.entries.size(); ++it, ++num_errors) {
|
|
CdcFuseAccess(req_, fuse_.entries[it].ino, mask);
|
|
ASSERT_EQ(fuse_.errors.size(), num_errors + 1);
|
|
EXPECT_EQ(fuse_.errors[num_errors], exp_error);
|
|
}
|
|
}
|
|
|
|
void ExpectAccessSucceeds() {
|
|
// Each file should allow read access.
|
|
ExpectAccessError(R_OK, 0 /*error*/);
|
|
|
|
// Each file should allow write access.
|
|
ExpectAccessError(W_OK, 0 /*error*/);
|
|
|
|
// Each file should allow exec access.
|
|
ExpectAccessError(X_OK, 0 /*error*/);
|
|
|
|
// All files should provide all types of access.
|
|
ExpectAccessError(R_OK | W_OK | X_OK, 0 /*error*/);
|
|
}
|
|
|
|
// Wipes chunks for |kFile1Name| to simulate an intermediate manifest, i.e.
|
|
// the manifest that contains all assets, but misses file chunks.
|
|
// Returns the intermediate manifest id.
|
|
ContentIdProto CreateIntermediateManifestId() {
|
|
ManifestProto manifest;
|
|
EXPECT_OK(cache_.GetProto(manifest_id_, &manifest));
|
|
EXPECT_GT(manifest.root_dir().dir_assets_size(), 0);
|
|
if (manifest.root_dir().dir_assets_size() == 0) return ContentIdProto();
|
|
AssetProto* file1 = manifest.mutable_root_dir()->mutable_dir_assets(0);
|
|
EXPECT_EQ(file1->name(), kFile1Name);
|
|
file1->clear_file_chunks();
|
|
return cache_.AddProto(manifest);
|
|
}
|
|
|
|
MemDataStore cache_;
|
|
MockLibFuse fuse_;
|
|
fuse_req_t req_ = nullptr;
|
|
ContentIdProto manifest_id_;
|
|
FakeManifestBuilder builder_;
|
|
FuseLog* Log() const { return static_cast<FuseLog*>(Log::Instance()); }
|
|
};
|
|
|
|
TEST_F(CdcFuseFsTest, LookupSucceeds) {
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kFile1Name);
|
|
ASSERT_EQ(fuse_.entries.size(), 1);
|
|
EXPECT_NE(fuse_.entries[0].ino, 0);
|
|
ExpectAttr(fuse_.entries[0].attr, kFile1Perm | path::MODE_IFREG,
|
|
kFile1Data.size(), kFile1Mtime);
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, LookupFailsNotADirectory) {
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kFile1Name);
|
|
ASSERT_EQ(fuse_.entries.size(), 1);
|
|
EXPECT_EQ(fuse_.errors.size(), 0);
|
|
|
|
// The ino refers to a file, but Lookup() wants a directory.
|
|
CdcFuseLookup(req_, fuse_.entries[0].ino, kFile1Name);
|
|
EXPECT_EQ(fuse_.entries.size(), 1);
|
|
ASSERT_EQ(fuse_.errors.size(), 1);
|
|
EXPECT_EQ(fuse_.errors[0], ENOENT);
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, LookupFailsDoesNotExist) {
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, "does_not_exist");
|
|
ASSERT_EQ(fuse_.errors.size(), 1);
|
|
EXPECT_EQ(fuse_.errors[0], ENOENT);
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, GetAttrSucceedsRootDir) {
|
|
fuse_file_info fi;
|
|
CdcFuseGetAttr(req_, FUSE_ROOT_ID, &fi);
|
|
ASSERT_EQ(fuse_.attrs.size(), 1);
|
|
EXPECT_EQ(fuse_.attrs[0].timeout, internal::kCdcFuseInodeTimeoutSec);
|
|
ExpectAttr(fuse_.attrs[0].value,
|
|
FakeManifestBuilder::kRootDirPerms | path::MODE_IFDIR, 0, 0);
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, GetAttrSucceedsFile) {
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kFile1Name);
|
|
ASSERT_EQ(fuse_.entries.size(), 1);
|
|
|
|
fuse_file_info fi;
|
|
CdcFuseGetAttr(req_, fuse_.entries[0].ino, &fi);
|
|
ASSERT_EQ(fuse_.attrs.size(), 1);
|
|
EXPECT_EQ(fuse_.attrs[0].timeout, internal::kCdcFuseInodeTimeoutSec);
|
|
ExpectAttr(fuse_.attrs[0].value, kFile1Perm | path::MODE_IFREG,
|
|
kFile1Data.size(), kFile1Mtime);
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, OpenSucceeds) {
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kFile1Name);
|
|
ASSERT_EQ(fuse_.entries.size(), 1);
|
|
|
|
fuse_file_info fi;
|
|
CdcFuseOpen(req_, fuse_.entries[0].ino, &fi);
|
|
ASSERT_EQ(fuse_.open_files.size(), 1);
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, OpenRespectsODirect) {
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kFile1Name);
|
|
ASSERT_EQ(fuse_.entries.size(), 1);
|
|
|
|
fuse_file_info fi;
|
|
CdcFuseOpen(req_, fuse_.entries[0].ino, &fi);
|
|
fi.flags = O_DIRECT;
|
|
CdcFuseOpen(req_, fuse_.entries[0].ino, &fi);
|
|
|
|
ASSERT_EQ(fuse_.open_files.size(), 2);
|
|
|
|
ASSERT_FALSE(fuse_.open_files[0].direct_io);
|
|
ASSERT_TRUE(fuse_.open_files[1].direct_io);
|
|
|
|
ASSERT_TRUE(fuse_.open_files[0].keep_cache);
|
|
ASSERT_FALSE(fuse_.open_files[1].keep_cache);
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, OpenFailsDirectory) {
|
|
fuse_file_info fi;
|
|
CdcFuseOpen(req_, FUSE_ROOT_ID, &fi);
|
|
ASSERT_EQ(fuse_.errors.size(), 1);
|
|
EXPECT_EQ(fuse_.errors[0], EISDIR);
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, OpenFailsWriteAccess) {
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kFile1Name);
|
|
ASSERT_EQ(fuse_.entries.size(), 1);
|
|
|
|
fuse_file_info fi(O_RDWR);
|
|
CdcFuseOpen(req_, fuse_.entries[0].ino, &fi);
|
|
ASSERT_EQ(fuse_.errors.size(), 1);
|
|
EXPECT_EQ(fuse_.errors[0], EACCES);
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, OpenQueuedForIntermediateManifest) {
|
|
ContentIdProto intermediate_manifest_id = CreateIntermediateManifestId();
|
|
EXPECT_OK(cdc_fuse_fs::SetManifest(intermediate_manifest_id));
|
|
|
|
// Opening file1 should be queued as it contains no chunks.
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kFile1Name);
|
|
ASSERT_EQ(fuse_.entries.size(), 1);
|
|
fuse_file_info fi;
|
|
CdcFuseOpen(req_, fuse_.entries[0].ino, &fi);
|
|
ASSERT_EQ(fuse_.open_files.size(), 0);
|
|
|
|
// Setting the final manifest should fulfill queued open requests.
|
|
EXPECT_OK(cdc_fuse_fs::SetManifest(manifest_id_));
|
|
ASSERT_EQ(fuse_.open_files.size(), 1);
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, OpenQueuedRequestsRequeue) {
|
|
ContentIdProto intermediate_manifest_id = CreateIntermediateManifestId();
|
|
EXPECT_OK(cdc_fuse_fs::SetManifest(intermediate_manifest_id));
|
|
|
|
// Opening file1 should be queued as it contains no chunks.
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kFile1Name);
|
|
ASSERT_EQ(fuse_.entries.size(), 1);
|
|
fuse_file_info fi;
|
|
CdcFuseOpen(req_, fuse_.entries[0].ino, &fi);
|
|
ASSERT_EQ(fuse_.open_files.size(), 0);
|
|
|
|
// Setting the same incomplete manifest again should requeue the request.
|
|
EXPECT_OK(cdc_fuse_fs::SetManifest(intermediate_manifest_id));
|
|
ASSERT_EQ(fuse_.open_files.size(), 0);
|
|
|
|
// Setting the final manifest should fulfill queued open requests.
|
|
EXPECT_OK(cdc_fuse_fs::SetManifest(manifest_id_));
|
|
ASSERT_EQ(fuse_.open_files.size(), 1);
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, ReadSucceeds) {
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kFile1Name);
|
|
ASSERT_EQ(fuse_.entries.size(), 1);
|
|
|
|
// Read everything from file1 except the first and the last byte.
|
|
fuse_file_info fi;
|
|
CdcFuseRead(req_, fuse_.entries[0].ino, kFile1Data.size() - 2, 1, &fi);
|
|
ASSERT_EQ(fuse_.buffers.size(), 1);
|
|
std::vector<char> data(kFile1Data.begin() + 1, kFile1Data.end() - 1);
|
|
EXPECT_EQ(fuse_.buffers[0], data);
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, ReadFailsNotAFile) {
|
|
fuse_file_info fi;
|
|
CdcFuseRead(req_, FUSE_ROOT_ID, kFile1Data.size(), 0, &fi);
|
|
ASSERT_EQ(fuse_.errors.size(), 1);
|
|
EXPECT_EQ(fuse_.errors[0], EIO);
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, ReadDirSucceeds) {
|
|
const size_t kEntrySize = sizeof(MockLibFuse::DirEntry);
|
|
fuse_file_info fi;
|
|
CdcFuseReadDir(req_, FUSE_ROOT_ID, kEntrySize * 10, 0, &fi);
|
|
ASSERT_EQ(fuse_.buffers.size(), 1);
|
|
ASSERT_EQ(fuse_.buffers[0].size(), kEntrySize * 4); // ., .., file1, subdir
|
|
MockLibFuse::DirEntry* entries =
|
|
reinterpret_cast<MockLibFuse::DirEntry*>(fuse_.buffers[0].data());
|
|
|
|
// Get inos for "file1.txt" and "subdir".
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kFile1Name);
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kSubdirName);
|
|
ASSERT_EQ(fuse_.entries.size(), 2);
|
|
|
|
std::unordered_map<std::string, MockLibFuse::DirEntry> expected;
|
|
expected["."] = {FUSE_ROOT_ID,
|
|
FakeManifestBuilder::kRootDirPerms | path::MODE_IFDIR,
|
|
{0},
|
|
0};
|
|
expected[".."] = {FUSE_ROOT_ID,
|
|
FakeManifestBuilder::kRootDirPerms | path::MODE_IFDIR,
|
|
{0},
|
|
0};
|
|
expected[kFile1Name] = {
|
|
fuse_.entries[0].ino & 0xFFFF, kFile1Perm | path::MODE_IFREG, {0}, 0};
|
|
expected[kSubdirName] = {
|
|
fuse_.entries[1].ino & 0xFFFF, kSubdirPerm | path::MODE_IFDIR, {0}, 0};
|
|
|
|
// A couple of things to note:
|
|
// - ReadDir() only fills the ino and mode attr entries. This is expected
|
|
// and matches what libfuse uses. The filesystem actually GetAttr() on every
|
|
// entry to get the full attributes. This has been fixed by ReadDirPlus()
|
|
// in LibFuse 3.
|
|
// - ".." is assigned the |FUSE_ROOT_ID| (this works fine, trust me!).
|
|
// - Unfortunately, on Windows stat::st_ino is 16 bit and there seems to be
|
|
// no good way to make it 64 bit. On Linux, we assert 64 bits, though.
|
|
|
|
std::unordered_set<std::string> unique_assets;
|
|
for (size_t i = 0; i < 4; ++i) {
|
|
auto it = expected.find(entries[i].name);
|
|
ASSERT_NE(it, expected.end());
|
|
EXPECT_EQ(entries[i].ino & 0xFFFF, it->second.ino);
|
|
EXPECT_EQ(entries[i].mode, it->second.mode);
|
|
unique_assets.insert(entries[i].name);
|
|
}
|
|
EXPECT_EQ(unique_assets.size(), expected.size());
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, ReadDirWithOffsetSizeSucceeds) {
|
|
const size_t kEntrySize = sizeof(MockLibFuse::DirEntry);
|
|
fuse_file_info fi;
|
|
CdcFuseReadDir(req_, FUSE_ROOT_ID, kEntrySize * 2, kEntrySize, &fi);
|
|
ASSERT_EQ(fuse_.buffers.size(), 1);
|
|
ASSERT_EQ(fuse_.buffers[0].size(), kEntrySize * 2);
|
|
MockLibFuse::DirEntry* entries =
|
|
reinterpret_cast<MockLibFuse::DirEntry*>(fuse_.buffers[0].data());
|
|
|
|
std::unordered_set<std::string> known_assets;
|
|
known_assets.insert(".");
|
|
known_assets.insert("..");
|
|
known_assets.insert(kFile1Name);
|
|
known_assets.insert(kSubdirName);
|
|
for (size_t i = 0; i < 2; ++i)
|
|
EXPECT_NE(known_assets.find(entries[i].name), known_assets.end())
|
|
<< "Could not find " << entries[i].name;
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, ReadDirBeyondEofSucceeds) {
|
|
// Start reading at entry 7, but there are only 6 entries.
|
|
const size_t kEntrySize = sizeof(MockLibFuse::DirEntry);
|
|
fuse_file_info fi;
|
|
CdcFuseReadDir(req_, FUSE_ROOT_ID, kEntrySize * 14, kEntrySize * 7, &fi);
|
|
ASSERT_EQ(fuse_.buffers.size(), 1);
|
|
EXPECT_TRUE(fuse_.buffers[0].empty());
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, ReadDirFailsNotADirectory) {
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kFile1Name);
|
|
ASSERT_EQ(fuse_.entries.size(), 1);
|
|
|
|
fuse_file_info fi;
|
|
CdcFuseReadDir(req_, fuse_.entries[0].ino, 1, 0, &fi);
|
|
ASSERT_EQ(fuse_.errors.size(), 1);
|
|
EXPECT_EQ(fuse_.errors[0], ENOTDIR);
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, ReadDirFailsInvalidIndirectAssetList) {
|
|
FakeManifestBuilder builder(&cache_);
|
|
ContentIdProto invalid_id;
|
|
*builder.Root()->add_dir_indirect_assets() = invalid_id;
|
|
ContentIdProto root_id = cache_.AddProto(*builder.Manifest());
|
|
EXPECT_OK(cdc_fuse_fs::SetManifest(root_id));
|
|
|
|
fuse_file_info fi;
|
|
CdcFuseReadDir(req_, FUSE_ROOT_ID, 1, 0, &fi);
|
|
ASSERT_EQ(fuse_.errors.size(), 1);
|
|
EXPECT_EQ(fuse_.errors[0], EBADF);
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, Forget) {
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kFile1Name);
|
|
EXPECT_EQ(fuse_.entries.size(), 1u);
|
|
EXPECT_EQ(CdcFuseGetInodeCountForTesting(), 1u);
|
|
|
|
CdcFuseForget(req_, fuse_.entries[0].ino, 1u);
|
|
// No new entry should be created as forget() finishes with
|
|
// fuse_reply_none().
|
|
EXPECT_EQ(fuse_.entries.size(), 1u);
|
|
EXPECT_TRUE(fuse_.errors.empty());
|
|
EXPECT_EQ(CdcFuseGetInodeCountForTesting(), 0u);
|
|
EXPECT_EQ(fuse_.none_counter, 1u);
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, ForgetMulti) {
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kFile1Name);
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kSubdirName);
|
|
ASSERT_EQ(fuse_.entries.size(), 2);
|
|
EXPECT_EQ(CdcFuseGetInodeCountForTesting(), 2u);
|
|
|
|
fuse_forget_data nodes_to_forget[2];
|
|
nodes_to_forget[0].ino = fuse_.entries[0].ino;
|
|
nodes_to_forget[0].nlookup = 1;
|
|
nodes_to_forget[1].ino = fuse_.entries[1].ino;
|
|
nodes_to_forget[1].nlookup = 1;
|
|
CdcFuseForgetMulti(req_, 2u, &nodes_to_forget[0]);
|
|
|
|
EXPECT_EQ(CdcFuseGetInodeCountForTesting(), 0u);
|
|
EXPECT_EQ(fuse_.none_counter, 1u);
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, DoNotForgetRoot) {
|
|
CdcFuseForget(req_, FUSE_ROOT_ID, 1u);
|
|
EXPECT_EQ(fuse_.none_counter, 1u);
|
|
EXPECT_EQ(CdcFuseGetInodeCountForTesting(), 0u);
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, DoNotForgetParent) {
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kSubdirName);
|
|
EXPECT_EQ(CdcFuseGetInodeCountForTesting(), 1u);
|
|
|
|
CdcFuseLookup(req_, fuse_.entries[0].ino, kFile2Name);
|
|
EXPECT_EQ(CdcFuseGetInodeCountForTesting(), 2u);
|
|
|
|
ASSERT_EQ(fuse_.entries.size(), 2);
|
|
|
|
CdcFuseForget(req_, fuse_.entries[0].ino, 1u);
|
|
EXPECT_EQ(fuse_.none_counter, 1u);
|
|
|
|
// The inode for kFile2Name still holds a reference to its parent.
|
|
EXPECT_EQ(CdcFuseGetInodeCountForTesting(), 2u);
|
|
|
|
// Unreal scenario: second forget for parent.
|
|
CdcFuseForget(req_, fuse_.entries[0].ino, 1u);
|
|
EXPECT_EQ(fuse_.none_counter, 2u);
|
|
|
|
// The inode for kFile2Name still holds a reference to its parent.
|
|
EXPECT_EQ(CdcFuseGetInodeCountForTesting(), 2u);
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, ForgetAllParentsExceptRoot) {
|
|
FakeManifestBuilder builder(&cache_);
|
|
builder.AddDirectory(builder.Root(), kFile1Name, kFile1Mtime, kFile1Perm);
|
|
AssetProto* subdir1 = builder.AddDirectory(builder.Root(), kSubdirName,
|
|
kSubdirMtime, kSubdirPerm);
|
|
AssetProto* subdir2 =
|
|
builder.AddDirectory(subdir1, kSubdirName, kSubdirMtime, kSubdirPerm);
|
|
AssetProto* subdir3 =
|
|
builder.AddDirectory(subdir2, kSubdirName, kSubdirMtime, kSubdirPerm);
|
|
builder.AddFile(subdir3, kFile2Name, kFile2Mtime, kFile2Perm, kFile2Data);
|
|
EXPECT_OK(cdc_fuse_fs::SetManifest(cache_.AddProto(*builder.Manifest())));
|
|
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kSubdirName);
|
|
ASSERT_EQ(fuse_.entries.size(), 1);
|
|
CdcFuseLookup(req_, fuse_.entries[0].ino, kSubdirName);
|
|
ASSERT_EQ(fuse_.entries.size(), 2);
|
|
CdcFuseLookup(req_, fuse_.entries[1].ino, kSubdirName);
|
|
ASSERT_EQ(fuse_.entries.size(), 3);
|
|
CdcFuseLookup(req_, fuse_.entries[2].ino, kFile2Name);
|
|
ASSERT_EQ(fuse_.entries.size(), 4);
|
|
EXPECT_EQ(CdcFuseGetInodeCountForTesting(), 4u);
|
|
|
|
CdcFuseForget(req_, fuse_.entries[0].ino, 1u);
|
|
EXPECT_EQ(fuse_.none_counter, 1u);
|
|
EXPECT_EQ(CdcFuseGetInodeCountForTesting(), 4u);
|
|
|
|
CdcFuseForget(req_, fuse_.entries[1].ino, 1u);
|
|
EXPECT_EQ(fuse_.none_counter, 2u);
|
|
EXPECT_EQ(CdcFuseGetInodeCountForTesting(), 4u);
|
|
|
|
CdcFuseForget(req_, fuse_.entries[2].ino, 1u);
|
|
EXPECT_EQ(fuse_.none_counter, 3u);
|
|
EXPECT_EQ(CdcFuseGetInodeCountForTesting(), 4u);
|
|
|
|
CdcFuseForget(req_, fuse_.entries[3].ino, 1u);
|
|
EXPECT_EQ(fuse_.none_counter, 4u);
|
|
EXPECT_EQ(CdcFuseGetInodeCountForTesting(), 0u);
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, AccessToReadSucceeds) {
|
|
FakeManifestBuilder builder(&cache_);
|
|
|
|
// Only read access for a specific rights collection.
|
|
builder.AddFile(builder.Root(), kUserFile, kFile1Mtime, path::MODE_IRUSR,
|
|
kFile1Data);
|
|
builder.AddFile(builder.Root(), kGroupFile, kFile1Mtime, path::MODE_IRGRP,
|
|
kFile1Data);
|
|
builder.AddFile(builder.Root(), kWorldFile, kFile1Mtime, path::MODE_IROTH,
|
|
kFile1Data);
|
|
manifest_id_ = cache_.AddProto(*builder.Manifest());
|
|
|
|
EXPECT_OK(cdc_fuse_fs::SetManifest(manifest_id_));
|
|
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kUserFile);
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kGroupFile);
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kWorldFile);
|
|
EXPECT_EQ(fuse_.entries.size(), 3u);
|
|
|
|
// Each file should allow read access.
|
|
ExpectAccessError(R_OK, 0 /*error*/);
|
|
|
|
// No file should provide write access.
|
|
ExpectAccessError(W_OK, EACCES /*error*/);
|
|
|
|
// No file should provide exec access.
|
|
ExpectAccessError(X_OK, EACCES /*error*/);
|
|
|
|
// No file should provide all types of access.
|
|
ExpectAccessError(R_OK | W_OK | X_OK, EACCES /*error*/);
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, AccessToAllRightsSucceeds) {
|
|
FakeManifestBuilder builder(&cache_);
|
|
|
|
// All access rights for a specific rights collection.
|
|
builder.AddFile(builder.Root(), kUserFile, kFile1Mtime, path::MODE_IRWXU,
|
|
kFile1Data);
|
|
builder.AddFile(builder.Root(), kGroupFile, kFile1Mtime, path::MODE_IRWXG,
|
|
kFile1Data);
|
|
builder.AddFile(builder.Root(), kWorldFile, kFile1Mtime, path::MODE_IRWXO,
|
|
kFile1Data);
|
|
manifest_id_ = cache_.AddProto(*builder.Manifest());
|
|
|
|
EXPECT_OK(cdc_fuse_fs::SetManifest(manifest_id_));
|
|
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kUserFile);
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kGroupFile);
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kWorldFile);
|
|
EXPECT_EQ(fuse_.entries.size(), 3u);
|
|
|
|
ExpectAccessSucceeds();
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, AccessFailsWrongUser) {
|
|
FakeManifestBuilder builder(&cache_);
|
|
|
|
// Only the default user has all rights.
|
|
builder.AddFile(builder.Root(), kUserFile, kFile1Mtime, path::MODE_IRWXU,
|
|
kFile1Data);
|
|
manifest_id_ = cache_.AddProto(*builder.Manifest());
|
|
|
|
EXPECT_OK(cdc_fuse_fs::SetManifest(manifest_id_));
|
|
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kUserFile);
|
|
EXPECT_EQ(fuse_.entries.size(), 1u);
|
|
|
|
// The user does not have rights to access the file.
|
|
// Only root and default users have access.
|
|
fuse_.SetUid(100);
|
|
|
|
ExpectAccessError(R_OK, EACCES /*error*/);
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, AccessFailsWrongGroup) {
|
|
FakeManifestBuilder builder(&cache_);
|
|
|
|
// Only the users of the default group have all rights.
|
|
builder.AddFile(builder.Root(), kUserFile, kFile1Mtime, path::MODE_IRWXG,
|
|
kFile1Data);
|
|
manifest_id_ = cache_.AddProto(*builder.Manifest());
|
|
|
|
EXPECT_OK(cdc_fuse_fs::SetManifest(manifest_id_));
|
|
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kUserFile);
|
|
EXPECT_EQ(fuse_.entries.size(), 1u);
|
|
|
|
fuse_.SetGid(100);
|
|
|
|
// The user does not have rights to access the file.
|
|
ExpectAccessError(R_OK, EACCES /*error*/);
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, AccessAsRootUserSucceeds) {
|
|
FakeManifestBuilder builder(&cache_);
|
|
|
|
// No rights are set.
|
|
builder.AddFile(builder.Root(), kUserFile, kFile1Mtime, 0, kFile1Data);
|
|
manifest_id_ = cache_.AddProto(*builder.Manifest());
|
|
|
|
EXPECT_OK(cdc_fuse_fs::SetManifest(manifest_id_));
|
|
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kUserFile);
|
|
EXPECT_EQ(fuse_.entries.size(), 1u);
|
|
|
|
fuse_.SetUid(internal::kCdcFuseRootUid);
|
|
|
|
ExpectAccessSucceeds();
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, AccessAsRootGroupSucceeds) {
|
|
FakeManifestBuilder builder(&cache_);
|
|
|
|
// No rights are set.
|
|
builder.AddFile(builder.Root(), kUserFile, kFile1Mtime, 0, kFile1Data);
|
|
manifest_id_ = cache_.AddProto(*builder.Manifest());
|
|
|
|
EXPECT_OK(cdc_fuse_fs::SetManifest(manifest_id_));
|
|
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kUserFile);
|
|
EXPECT_EQ(fuse_.entries.size(), 1u);
|
|
|
|
fuse_.SetGid(internal::kCdcFuseRootGid);
|
|
|
|
ExpectAccessSucceeds();
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, SetInvalidManifestFails) {
|
|
ContentIdProto invalid_manifest_id;
|
|
absl::Status status = cdc_fuse_fs::SetManifest(invalid_manifest_id);
|
|
EXPECT_NOT_OK(status);
|
|
|
|
// The old manifest still should be valid: its files should be requestable.
|
|
ExpectAccessSucceeds();
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, AddFileUpdateManifestSucceeds) {
|
|
FakeManifestBuilder builder(&cache_);
|
|
|
|
// All access rights for a specific rights collection.
|
|
builder.AddFile(builder.Root(), kUserFile, kFile1Mtime, path::MODE_IRWXU,
|
|
kFile1Data);
|
|
manifest_id_ = cache_.AddProto(*builder.Manifest());
|
|
EXPECT_OK(cdc_fuse_fs::SetManifest(manifest_id_));
|
|
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kUserFile);
|
|
EXPECT_EQ(fuse_.entries.size(), 1u);
|
|
|
|
// The file does not exists -> error.
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kGroupFile);
|
|
ASSERT_EQ(fuse_.errors.size(), 1);
|
|
EXPECT_EQ(fuse_.errors[0], ENOENT);
|
|
|
|
// Add the missing file and update the manifest id.
|
|
builder.AddFile(builder.Root(), kGroupFile, kFile1Mtime, path::MODE_IRWXG,
|
|
kFile1Data);
|
|
manifest_id_ = cache_.AddProto(*builder.Manifest());
|
|
EXPECT_OK(cdc_fuse_fs::SetManifest(manifest_id_));
|
|
|
|
// Can get access to the first file.
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kUserFile);
|
|
EXPECT_EQ(fuse_.entries.size(), 2u);
|
|
|
|
// Can get access to the new file.
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kGroupFile);
|
|
EXPECT_EQ(fuse_.entries.size(), 3u);
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, CompletelyUpdatedManifestSucceeds) {
|
|
FakeManifestBuilder builder(&cache_);
|
|
|
|
// All access rights for a specific rights collection.
|
|
builder.AddFile(builder.Root(), kUserFile, kFile1Mtime, path::MODE_IRWXU,
|
|
kFile1Data);
|
|
manifest_id_ = cache_.AddProto(*builder.Manifest());
|
|
EXPECT_OK(cdc_fuse_fs::SetManifest(manifest_id_));
|
|
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kUserFile);
|
|
EXPECT_EQ(fuse_.entries.size(), 1u);
|
|
|
|
FakeManifestBuilder builder2(&cache_);
|
|
// Add the missing file and update the manifest id.
|
|
builder2.AddFile(builder2.Root(), kGroupFile, kFile1Mtime, path::MODE_IRWXG,
|
|
kFile1Data);
|
|
manifest_id_ = cache_.AddProto(*builder2.Manifest());
|
|
EXPECT_OK(cdc_fuse_fs::SetManifest(manifest_id_));
|
|
|
|
// Cannot get access to the old file.
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kUserFile);
|
|
ASSERT_EQ(fuse_.errors.size(), 1);
|
|
EXPECT_EQ(fuse_.errors[0], ENOENT);
|
|
|
|
// Can get access to the new file.
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kGroupFile);
|
|
EXPECT_EQ(fuse_.entries.size(), 2u);
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, CompletelyUpdatedManifestForgetOldFileSucceeds) {
|
|
FakeManifestBuilder builder(&cache_);
|
|
|
|
builder.AddFile(builder.Root(), kUserFile, kFile1Mtime, path::MODE_IRWXU,
|
|
kFile1Data);
|
|
manifest_id_ = cache_.AddProto(*builder.Manifest());
|
|
EXPECT_OK(cdc_fuse_fs::SetManifest(manifest_id_));
|
|
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kUserFile);
|
|
EXPECT_EQ(fuse_.entries.size(), 1u);
|
|
|
|
FakeManifestBuilder builder2(&cache_);
|
|
// Add file and update the manifest id.
|
|
builder2.AddFile(builder2.Root(), kGroupFile, kFile1Mtime, path::MODE_IRWXG,
|
|
kFile1Data);
|
|
manifest_id_ = cache_.AddProto(*builder2.Manifest());
|
|
EXPECT_OK(cdc_fuse_fs::SetManifest(manifest_id_));
|
|
EXPECT_EQ("FUSE consistency check succeeded", Log()->LastMessage());
|
|
EXPECT_EQ(CdcFuseGetInodeCountForTesting(), 0u);
|
|
EXPECT_EQ(CdcFuseGetInvalidInodeCountForTesting(), 1u);
|
|
|
|
// Cannot get access to the old file.
|
|
CdcFuseForget(req_, fuse_.entries[0].ino, 1u);
|
|
EXPECT_EQ(fuse_.none_counter, 1u);
|
|
EXPECT_EQ(CdcFuseGetInodeCountForTesting(), 0u);
|
|
EXPECT_EQ(CdcFuseGetInvalidInodeCountForTesting(), 0u);
|
|
|
|
// Can get access to the new file.
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kGroupFile);
|
|
EXPECT_EQ(CdcFuseGetInodeCountForTesting(), 1u);
|
|
EXPECT_EQ(fuse_.entries.size(), 2u);
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, AddFileUpdateManifestOldInodesValid) {
|
|
// Get inode.
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kFile1Name);
|
|
ASSERT_EQ(fuse_.entries.size(), 1u);
|
|
|
|
// Update manifest while adding a new file.
|
|
builder_.AddFile(builder_.Root(), kUserFile, kFile1Mtime, path::MODE_IRWXU,
|
|
kFile1Data);
|
|
manifest_id_ = cache_.AddProto(*builder_.Manifest());
|
|
EXPECT_OK(cdc_fuse_fs::SetManifest(manifest_id_));
|
|
EXPECT_EQ("FUSE consistency check succeeded", Log()->LastMessage());
|
|
|
|
// New file should be accessible.
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kUserFile);
|
|
EXPECT_EQ(fuse_.entries.size(), 2u);
|
|
|
|
// inode for kFile1Name should be valid.
|
|
fuse_file_info fi;
|
|
CdcFuseRead(req_, fuse_.entries[0].ino, kFile1Data.size(), 0, &fi);
|
|
ASSERT_EQ(fuse_.buffers.size(), 1);
|
|
EXPECT_EQ(fuse_.buffers[0], kFile1Data);
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, ModifyFileUpdateManifestOldInodesValid) {
|
|
// Get inode.
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kFile1Name);
|
|
ASSERT_EQ(fuse_.entries.size(), 1u);
|
|
|
|
// Update manifest by modifying a file.
|
|
builder_.ModifyFile(builder_.Root(), kFile1Name, kFile2Mtime, kFile2Perm,
|
|
kFile2Data);
|
|
manifest_id_ = cache_.AddProto(*builder_.Manifest());
|
|
EXPECT_OK(cdc_fuse_fs::SetManifest(manifest_id_));
|
|
EXPECT_EQ("FUSE consistency check succeeded", Log()->LastMessage());
|
|
EXPECT_EQ(CdcFuseGetInvalidInodeCountForTesting(), 0u);
|
|
|
|
// inode for kFile1Name should be valid, but the content should be new ->
|
|
// reload is required.
|
|
fuse_file_info fi;
|
|
CdcFuseRead(req_, fuse_.entries[0].ino, kFile2Data.size(), 0, &fi);
|
|
EXPECT_TRUE(fuse_.buffers.empty());
|
|
ASSERT_EQ(fuse_.errors.size(), 1);
|
|
EXPECT_EQ(fuse_.errors[0], EIO);
|
|
|
|
// Forget the inode, lookup + read it again -> should succeed.
|
|
CdcFuseForget(req_, fuse_.entries[0].ino, 1u);
|
|
EXPECT_EQ(CdcFuseGetInodeCountForTesting(), 0u);
|
|
EXPECT_EQ(fuse_.none_counter, 1u);
|
|
|
|
// Read it again.
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kFile1Name);
|
|
ExpectAttr(fuse_.entries[1].attr, kFile2Perm | path::MODE_IFREG,
|
|
kFile2Data.size(), kFile2Mtime);
|
|
CdcFuseRead(req_, fuse_.entries[1].ino, kFile2Data.size(), 0, &fi);
|
|
ASSERT_EQ(fuse_.buffers.size(), 1);
|
|
EXPECT_EQ(fuse_.buffers[0], kFile2Data);
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest,
|
|
LookupFileInSubfolderRemoveSubfolderUpdateManifestReadFile) {
|
|
// Get inode.
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kFile1Name);
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kSubdirName);
|
|
CdcFuseLookup(req_, fuse_.entries[1].ino, kFile2Name);
|
|
EXPECT_EQ(CdcFuseGetInodeCountForTesting(), 3u);
|
|
ASSERT_EQ(fuse_.entries.size(), 3);
|
|
|
|
// Update manifest: it has only 1 file.
|
|
FakeManifestBuilder builder(&cache_);
|
|
builder.AddFile(builder.Root(), kFile1Name, kFile1Mtime, kFile1Perm,
|
|
kFile1Data);
|
|
|
|
manifest_id_ = cache_.AddProto(*builder.Manifest());
|
|
EXPECT_OK(cdc_fuse_fs::SetManifest(manifest_id_));
|
|
EXPECT_EQ("FUSE consistency check succeeded", Log()->LastMessage());
|
|
|
|
// The total amount of valid and invalid inodes should stay the same.
|
|
// As the old inodes could be potentially accessed by the system and should be
|
|
// forgotten with forget() or forget_multi().
|
|
EXPECT_EQ(CdcFuseGetInodeCountForTesting(), 1u);
|
|
EXPECT_EQ(CdcFuseGetInvalidInodeCountForTesting(), 2u);
|
|
|
|
fuse_file_info fi;
|
|
CdcFuseRead(req_, fuse_.entries[2].ino, kFile2Data.size(), 0, &fi);
|
|
EXPECT_TRUE(fuse_.buffers.empty());
|
|
ASSERT_EQ(fuse_.errors.size(), 1u);
|
|
EXPECT_EQ(fuse_.errors[0], ENOENT);
|
|
|
|
const size_t kEntrySize = sizeof(MockLibFuse::DirEntry);
|
|
CdcFuseReadDir(req_, fuse_.entries[1].ino, kEntrySize * 10, 0, &fi);
|
|
ASSERT_EQ(fuse_.errors.size(), 2u);
|
|
EXPECT_EQ(fuse_.errors[1], ENOENT);
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, FileToFolderUpdateManifest) {
|
|
// Get inode.
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kFile1Name);
|
|
ASSERT_EQ(fuse_.entries.size(), 1);
|
|
|
|
// Read everything from file1 -> fill internal asset's structures.
|
|
fuse_file_info file_info;
|
|
CdcFuseRead(req_, fuse_.entries[0].ino, kFile1Data.size(), 0, &file_info);
|
|
ASSERT_EQ(fuse_.buffers.size(), 1);
|
|
EXPECT_EQ(fuse_.buffers[0], kFile1Data);
|
|
|
|
// Change file1.txt to folder.
|
|
FakeManifestBuilder builder(&cache_);
|
|
builder.AddDirectory(builder.Root(), kFile1Name, kFile1Mtime, kFile1Perm);
|
|
AssetProto* subdir = builder.AddDirectory(builder.Root(), kSubdirName,
|
|
kSubdirMtime, kSubdirPerm);
|
|
builder.AddFile(subdir, kFile2Name, kFile2Mtime, kFile2Perm, kFile2Data);
|
|
manifest_id_ = cache_.AddProto(*builder.Manifest());
|
|
EXPECT_OK(cdc_fuse_fs::SetManifest(manifest_id_));
|
|
EXPECT_EQ("FUSE consistency check succeeded", Log()->LastMessage());
|
|
|
|
// Read directory should succeed.
|
|
const size_t kEntrySize = sizeof(MockLibFuse::DirEntry);
|
|
fuse_file_info dir_info;
|
|
CdcFuseReadDir(req_, fuse_.entries[0].ino, kEntrySize * 10, 0, &dir_info);
|
|
ASSERT_EQ(fuse_.buffers.size(), 2);
|
|
ASSERT_EQ(fuse_.buffers[1].size(), kEntrySize * 2); // ., ..
|
|
MockLibFuse::DirEntry* entries =
|
|
reinterpret_cast<MockLibFuse::DirEntry*>(fuse_.buffers[1].data());
|
|
EXPECT_STREQ(entries[0].name, ".");
|
|
EXPECT_STREQ(entries[1].name, "..");
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, FolderToFileUpdateManifest) {
|
|
// Get inode.
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kSubdirName);
|
|
EXPECT_EQ(CdcFuseGetInodeCountForTesting(), 1u);
|
|
|
|
// Read directory to fill internal asset's structures.
|
|
const size_t kEntrySize = sizeof(MockLibFuse::DirEntry);
|
|
fuse_file_info dir_info;
|
|
CdcFuseReadDir(req_, fuse_.entries[0].ino, kEntrySize * 10, 0, &dir_info);
|
|
ASSERT_EQ(fuse_.buffers.size(), 1);
|
|
ASSERT_EQ(fuse_.buffers[0].size(), kEntrySize * 3); // ., .., file2.txt
|
|
EXPECT_EQ(CdcFuseGetInodeCountForTesting(), 2u);
|
|
|
|
// Get inos for "file2.txt".
|
|
CdcFuseLookup(req_, fuse_.entries[0].ino, kFile2Name);
|
|
ASSERT_EQ(fuse_.entries.size(), 2);
|
|
|
|
// Change subfolder to file.
|
|
FakeManifestBuilder builder(&cache_);
|
|
builder.AddFile(builder.Root(), kSubdirName, kSubdirMtime, kSubdirPerm,
|
|
kFile1Data);
|
|
manifest_id_ = cache_.AddProto(*builder.Manifest());
|
|
EXPECT_OK(cdc_fuse_fs::SetManifest(manifest_id_));
|
|
EXPECT_EQ("FUSE consistency check succeeded", Log()->LastMessage());
|
|
EXPECT_EQ(CdcFuseGetInodeCountForTesting(),
|
|
1u); // the number of inodes should not change.
|
|
|
|
// Reading file should fail as the proto has been changed.
|
|
fuse_file_info fi;
|
|
CdcFuseRead(req_, fuse_.entries[0].ino, kFile1Data.size(), 0, &fi);
|
|
EXPECT_EQ(fuse_.buffers.size(), 1); // should not change.
|
|
ASSERT_EQ(fuse_.errors.size(), 1);
|
|
EXPECT_EQ(fuse_.errors[0], EIO);
|
|
|
|
// Forget the inode, lookup + read it again -> should succeed.
|
|
CdcFuseForget(req_, fuse_.entries[0].ino, 1u);
|
|
CdcFuseForget(req_, fuse_.entries[1].ino, 2u);
|
|
EXPECT_EQ(CdcFuseGetInodeCountForTesting(), 0u);
|
|
EXPECT_EQ(fuse_.none_counter, 2u);
|
|
|
|
// Read it again.
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kSubdirName);
|
|
CdcFuseRead(req_, fuse_.entries[2].ino, kFile1Data.size(), 0, &fi);
|
|
ASSERT_EQ(fuse_.buffers.size(), 2);
|
|
EXPECT_EQ(fuse_.buffers[1], kFile1Data);
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, ModifyFileUpdateManifestTwiceOldInodesValid) {
|
|
// Get inode.
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kFile1Name);
|
|
ASSERT_EQ(fuse_.entries.size(), 1u);
|
|
|
|
// Update manifest by modifying a file.
|
|
builder_.ModifyFile(builder_.Root(), kFile1Name, kFile2Mtime, kFile2Perm,
|
|
kFile2Data);
|
|
manifest_id_ = cache_.AddProto(*builder_.Manifest());
|
|
EXPECT_OK(cdc_fuse_fs::SetManifest(manifest_id_));
|
|
EXPECT_EQ("FUSE consistency check succeeded", Log()->LastMessage());
|
|
|
|
// Lookup should return the same ino, but different attributes.
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kFile1Name);
|
|
ASSERT_EQ(fuse_.entries.size(), 2u);
|
|
EXPECT_EQ(fuse_.entries[1].ino, fuse_.entries[0].ino);
|
|
ExpectAttr(fuse_.entries[1].attr, kFile2Perm | path::MODE_IFREG,
|
|
kFile2Data.size(), kFile2Mtime);
|
|
|
|
// Update the file and manifest second time.
|
|
builder_.ModifyFile(builder_.Root(), kFile1Name, kFile1Mtime, kFile1Perm,
|
|
kFile1Data);
|
|
manifest_id_ = cache_.AddProto(*builder_.Manifest());
|
|
EXPECT_OK(cdc_fuse_fs::SetManifest(manifest_id_));
|
|
EXPECT_EQ("FUSE consistency check succeeded", Log()->LastMessage());
|
|
|
|
// Lookup should return the same ino, but different attributes.
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kFile1Name);
|
|
ASSERT_EQ(fuse_.entries.size(), 3u);
|
|
EXPECT_EQ(fuse_.entries[2].ino, fuse_.entries[1].ino);
|
|
ExpectAttr(fuse_.entries[2].attr, kFile1Perm | path::MODE_IFREG,
|
|
kFile1Data.size(), kFile1Mtime);
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, RemoveFolderUpdateManifest) {
|
|
// Get inode.
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kSubdirName);
|
|
CdcFuseLookup(req_, fuse_.entries[0].ino, kFile2Name);
|
|
EXPECT_EQ(CdcFuseGetInodeCountForTesting(), 2u);
|
|
|
|
// Remove subfolder.
|
|
FakeManifestBuilder builder(&cache_);
|
|
manifest_id_ = cache_.AddProto(*builder.Manifest());
|
|
EXPECT_OK(cdc_fuse_fs::SetManifest(manifest_id_));
|
|
EXPECT_EQ("FUSE consistency check succeeded", Log()->LastMessage());
|
|
EXPECT_EQ(CdcFuseGetInodeCountForTesting(), 0u);
|
|
EXPECT_EQ(CdcFuseGetInvalidInodeCountForTesting(), 2u);
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, InvalidateSubSubDirInvalidateSubDirSucceeds) {
|
|
// Create a subdir in a subdir of the root.
|
|
FakeManifestBuilder builder(&cache_);
|
|
AssetProto* subdir = builder.AddDirectory(builder.Root(), kSubdirName,
|
|
kSubdirMtime, kSubdirPerm);
|
|
builder.AddDirectory(subdir, kSubdirName, kSubdirMtime, kSubdirPerm);
|
|
manifest_id_ = cache_.AddProto(*builder.Manifest());
|
|
EXPECT_OK(cdc_fuse_fs::SetManifest(manifest_id_));
|
|
EXPECT_EQ("FUSE consistency check succeeded", Log()->LastMessage());
|
|
|
|
// Both subdirs are in inodes.
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kSubdirName);
|
|
CdcFuseLookup(req_, fuse_.entries[0].ino, kSubdirName);
|
|
EXPECT_EQ(CdcFuseGetInodeCountForTesting(), 2u);
|
|
|
|
// Update manifest while removing subsubdir.
|
|
FakeManifestBuilder builder1(&cache_);
|
|
builder1.AddDirectory(builder1.Root(), kSubdirName, kSubdirMtime,
|
|
kSubdirPerm);
|
|
manifest_id_ = cache_.AddProto(*builder1.Manifest());
|
|
EXPECT_OK(cdc_fuse_fs::SetManifest(manifest_id_));
|
|
EXPECT_EQ("FUSE consistency check succeeded", Log()->LastMessage());
|
|
EXPECT_EQ(CdcFuseGetInodeCountForTesting(), 1u);
|
|
EXPECT_EQ(CdcFuseGetInvalidInodeCountForTesting(), 1u);
|
|
|
|
// Update manifest while removing subdir.
|
|
FakeManifestBuilder builder2(&cache_);
|
|
manifest_id_ = cache_.AddProto(*builder2.Manifest());
|
|
EXPECT_OK(cdc_fuse_fs::SetManifest(manifest_id_));
|
|
EXPECT_EQ("FUSE consistency check succeeded", Log()->LastMessage());
|
|
EXPECT_EQ(CdcFuseGetInodeCountForTesting(), 0u);
|
|
EXPECT_EQ(CdcFuseGetInvalidInodeCountForTesting(), 2u);
|
|
|
|
fuse_file_info fi;
|
|
CdcFuseReadDir(req_, fuse_.entries[0].ino, 1, 0, &fi);
|
|
ASSERT_EQ(fuse_.errors.size(), 1);
|
|
EXPECT_EQ(fuse_.errors[0], ENOENT);
|
|
|
|
CdcFuseReadDir(req_, fuse_.entries[1].ino, 1, 0, &fi);
|
|
ASSERT_EQ(fuse_.errors.size(), 2);
|
|
EXPECT_EQ(fuse_.errors[1], ENOENT);
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, OpenDirSucceeds) {
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kSubdirName);
|
|
ASSERT_EQ(fuse_.entries.size(), 1u);
|
|
|
|
fuse_file_info fi;
|
|
CdcFuseOpenDir(req_, fuse_.entries[0].ino, &fi);
|
|
EXPECT_TRUE(fuse_.errors.empty());
|
|
ASSERT_EQ(fuse_.open_files.size(), 1u);
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, OpenDirFailsNotADirectory) {
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kFile1Name);
|
|
ASSERT_EQ(fuse_.entries.size(), 1u);
|
|
|
|
fuse_file_info fi;
|
|
CdcFuseOpenDir(req_, fuse_.entries[0].ino, &fi);
|
|
ASSERT_EQ(fuse_.errors.size(), 1u);
|
|
EXPECT_EQ(fuse_.errors[0], ENOTDIR);
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, OpenDirFailsInvalidDir) {
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kSubdirName);
|
|
ASSERT_EQ(fuse_.entries.size(), 1u);
|
|
|
|
// Remove subdir.
|
|
FakeManifestBuilder builder(&cache_);
|
|
manifest_id_ = cache_.AddProto(*builder.Manifest());
|
|
EXPECT_OK(cdc_fuse_fs::SetManifest(manifest_id_));
|
|
EXPECT_EQ("FUSE consistency check succeeded", Log()->LastMessage());
|
|
|
|
fuse_file_info fi;
|
|
CdcFuseOpenDir(req_, fuse_.entries[0].ino, &fi);
|
|
ASSERT_EQ(fuse_.errors.size(), 1u);
|
|
EXPECT_EQ(fuse_.errors[0], ENOENT);
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, ReleaseSucceeds) {
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kFile1Name);
|
|
ASSERT_EQ(fuse_.entries.size(), 1u);
|
|
|
|
fuse_file_info fi;
|
|
CdcFuseRelease(req_, fuse_.entries[0].ino, &fi);
|
|
ASSERT_EQ(fuse_.errors.size(), 1u);
|
|
EXPECT_EQ(fuse_.errors[0], 0u);
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, ReleaseFailsForDirectory) {
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kSubdirName);
|
|
ASSERT_EQ(fuse_.entries.size(), 1u);
|
|
|
|
fuse_file_info fi;
|
|
CdcFuseRelease(req_, fuse_.entries[0].ino, &fi);
|
|
ASSERT_EQ(fuse_.errors.size(), 1u);
|
|
EXPECT_EQ(fuse_.errors[0], EISDIR);
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, ReleaseFailsInvalidFile) {
|
|
// Get inode.
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kFile1Name);
|
|
|
|
// Remove file.
|
|
FakeManifestBuilder builder(&cache_);
|
|
manifest_id_ = cache_.AddProto(*builder.Manifest());
|
|
EXPECT_OK(cdc_fuse_fs::SetManifest(manifest_id_));
|
|
EXPECT_EQ("FUSE consistency check succeeded", Log()->LastMessage());
|
|
|
|
fuse_file_info fi;
|
|
CdcFuseRelease(req_, fuse_.entries[0].ino, &fi);
|
|
ASSERT_EQ(fuse_.errors.size(), 1u);
|
|
EXPECT_EQ(fuse_.errors[0], ENOENT);
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, ReleaseDirSucceeds) {
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kFile1Name);
|
|
ASSERT_EQ(fuse_.entries.size(), 1u);
|
|
|
|
fuse_file_info fi;
|
|
CdcFuseRelease(req_, fuse_.entries[0].ino, &fi);
|
|
ASSERT_EQ(fuse_.errors.size(), 1u);
|
|
EXPECT_EQ(fuse_.errors[0], 0u);
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, ReleaseDirFailsNotADirectory) {
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kFile1Name);
|
|
ASSERT_EQ(fuse_.entries.size(), 1u);
|
|
|
|
fuse_file_info fi;
|
|
CdcFuseReleaseDir(req_, fuse_.entries[0].ino, &fi);
|
|
ASSERT_EQ(fuse_.errors.size(), 1u);
|
|
EXPECT_EQ(fuse_.errors[0], ENOTDIR);
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, ReleaseDirFailsInvalidDirectory) {
|
|
// Get inode.
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kSubdirName);
|
|
|
|
// Remove subdir.
|
|
FakeManifestBuilder builder(&cache_);
|
|
manifest_id_ = cache_.AddProto(*builder.Manifest());
|
|
EXPECT_OK(cdc_fuse_fs::SetManifest(manifest_id_));
|
|
EXPECT_EQ("FUSE consistency check succeeded", Log()->LastMessage());
|
|
|
|
fuse_file_info fi;
|
|
CdcFuseReleaseDir(req_, fuse_.entries[0].ino, &fi);
|
|
ASSERT_EQ(fuse_.errors.size(), 1u);
|
|
EXPECT_EQ(fuse_.errors[0], ENOENT);
|
|
}
|
|
|
|
TEST_F(CdcFuseFsTest, UpdateManifestEmptyFile) {
|
|
CdcFuseLookup(req_, FUSE_ROOT_ID, kFile1Name);
|
|
EXPECT_EQ(fuse_.entries.size(), 1u);
|
|
FakeManifestBuilder builder(&cache_);
|
|
|
|
builder.AddFile(builder.Root(), kFile1Name, kFile1Mtime, path::MODE_IRWXU,
|
|
{});
|
|
manifest_id_ = cache_.AddProto(*builder.Manifest());
|
|
EXPECT_OK(cdc_fuse_fs::SetManifest(manifest_id_));
|
|
|
|
EXPECT_EQ("FUSE consistency check succeeded", Log()->LastMessage());
|
|
EXPECT_EQ(CdcFuseGetInodeCountForTesting(), 1u);
|
|
EXPECT_EQ(CdcFuseGetInvalidInodeCountForTesting(), 0u);
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace cdc_ft
|