2017-04-27 07:42:07 +03:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2004-present, Facebook, Inc.
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* This source code is licensed under the BSD-style license found in the
|
|
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
|
|
*
|
|
|
|
*/
|
2017-11-15 23:48:58 +03:00
|
|
|
#include "eden/fs/inodes/FileInode.h"
|
|
|
|
|
2017-04-27 07:42:07 +03:00
|
|
|
#include <folly/Format.h>
|
2018-03-22 08:21:16 +03:00
|
|
|
#include <folly/Range.h>
|
2017-11-15 23:48:58 +03:00
|
|
|
#include <folly/test/TestUtils.h>
|
2017-04-27 07:42:07 +03:00
|
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <chrono>
|
|
|
|
|
2018-03-19 22:51:10 +03:00
|
|
|
#include "eden/fs/fuse/FileHandle.h"
|
2017-04-27 07:42:07 +03:00
|
|
|
#include "eden/fs/inodes/TreeInode.h"
|
2017-12-05 20:52:56 +03:00
|
|
|
#include "eden/fs/testharness/FakeBackingStore.h"
|
2017-04-27 07:42:07 +03:00
|
|
|
#include "eden/fs/testharness/FakeTreeBuilder.h"
|
|
|
|
#include "eden/fs/testharness/TestChecks.h"
|
|
|
|
#include "eden/fs/testharness/TestMount.h"
|
|
|
|
|
|
|
|
using namespace facebook::eden;
|
|
|
|
using folly::StringPiece;
|
2018-03-22 08:21:16 +03:00
|
|
|
using folly::literals::string_piece_literals::operator""_sp;
|
2017-04-27 07:42:07 +03:00
|
|
|
using std::chrono::duration_cast;
|
2018-05-19 02:48:58 +03:00
|
|
|
using namespace std::chrono_literals;
|
2017-04-27 07:42:07 +03:00
|
|
|
|
|
|
|
std::ostream& operator<<(std::ostream& os, const timespec& ts) {
|
|
|
|
os << folly::sformat("{}.{:09d}", ts.tv_sec, ts.tv_nsec);
|
|
|
|
return os;
|
|
|
|
}
|
|
|
|
|
|
|
|
namespace std {
|
|
|
|
namespace chrono {
|
|
|
|
std::ostream& operator<<(
|
|
|
|
std::ostream& os,
|
|
|
|
const std::chrono::system_clock::time_point& tp) {
|
|
|
|
auto duration = tp.time_since_epoch();
|
|
|
|
auto secs = duration_cast<std::chrono::seconds>(duration);
|
|
|
|
auto nsecs = duration_cast<std::chrono::nanoseconds>(duration - secs);
|
|
|
|
os << folly::sformat("{}.{:09d}", secs.count(), nsecs.count());
|
|
|
|
return os;
|
|
|
|
}
|
2017-11-04 01:58:04 +03:00
|
|
|
} // namespace chrono
|
|
|
|
} // namespace std
|
2017-04-27 07:42:07 +03:00
|
|
|
|
|
|
|
template <typename Clock = std::chrono::system_clock>
|
|
|
|
typename Clock::time_point timespecToTimePoint(const timespec& ts) {
|
|
|
|
auto duration =
|
|
|
|
std::chrono::seconds{ts.tv_sec} + std::chrono::nanoseconds{ts.tv_nsec};
|
|
|
|
return typename Clock::time_point{duration};
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Helper functions for comparing timespec structs from file attributes
|
|
|
|
* against C++11-style time_point objects.
|
|
|
|
*/
|
|
|
|
bool operator<(const timespec& ts, std::chrono::system_clock::time_point tp) {
|
|
|
|
return timespecToTimePoint(ts) < tp;
|
|
|
|
}
|
|
|
|
bool operator<=(const timespec& ts, std::chrono::system_clock::time_point tp) {
|
|
|
|
return timespecToTimePoint(ts) <= tp;
|
|
|
|
}
|
|
|
|
bool operator>(const timespec& ts, std::chrono::system_clock::time_point tp) {
|
|
|
|
return timespecToTimePoint(ts) > tp;
|
|
|
|
}
|
|
|
|
bool operator>=(const timespec& ts, std::chrono::system_clock::time_point tp) {
|
|
|
|
return timespecToTimePoint(ts) >= tp;
|
|
|
|
}
|
|
|
|
bool operator!=(const timespec& ts, std::chrono::system_clock::time_point tp) {
|
|
|
|
return timespecToTimePoint(ts) != tp;
|
|
|
|
}
|
|
|
|
bool operator==(const timespec& ts, std::chrono::system_clock::time_point tp) {
|
|
|
|
return timespecToTimePoint(ts) == tp;
|
|
|
|
}
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
2018-03-20 03:01:15 +03:00
|
|
|
Dispatcher::Attr getFileAttr(const FileInodePtr& inode) {
|
2017-04-27 07:42:07 +03:00
|
|
|
auto attrFuture = inode->getattr();
|
|
|
|
// We unfortunately can't use an ASSERT_* check here, since it tries
|
|
|
|
// to return from the function normally, rather than throwing.
|
|
|
|
if (!attrFuture.isReady()) {
|
|
|
|
// Use ADD_FAILURE() so that any SCOPED_TRACE() data will be reported,
|
|
|
|
// then throw an exception.
|
|
|
|
ADD_FAILURE() << "getattr() future is not ready";
|
|
|
|
throw std::runtime_error("getattr future is not ready");
|
|
|
|
}
|
|
|
|
return attrFuture.get();
|
|
|
|
}
|
|
|
|
|
2018-03-20 03:01:15 +03:00
|
|
|
Dispatcher::Attr setFileAttr(
|
2017-04-27 07:42:07 +03:00
|
|
|
const FileInodePtr& inode,
|
2018-01-03 03:25:03 +03:00
|
|
|
const fuse_setattr_in& desired) {
|
|
|
|
auto attrFuture = inode->setattr(desired);
|
2017-04-27 07:42:07 +03:00
|
|
|
if (!attrFuture.isReady()) {
|
|
|
|
ADD_FAILURE() << "setattr() future is not ready";
|
|
|
|
throw std::runtime_error("setattr future is not ready");
|
|
|
|
}
|
|
|
|
return attrFuture.get();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper function used by BASIC_ATTR_CHECKS()
|
|
|
|
*/
|
2018-03-20 03:01:15 +03:00
|
|
|
void basicAttrChecks(const FileInodePtr& inode, const Dispatcher::Attr& attr) {
|
2018-02-27 23:40:30 +03:00
|
|
|
EXPECT_EQ(inode->getNodeId().getRawValue(), attr.st.st_ino);
|
2017-04-27 07:42:07 +03:00
|
|
|
EXPECT_EQ(1, attr.st.st_nlink);
|
2017-09-09 05:15:20 +03:00
|
|
|
EXPECT_EQ(inode->getMount()->getUid(), attr.st.st_uid);
|
|
|
|
EXPECT_EQ(inode->getMount()->getGid(), attr.st.st_gid);
|
2017-04-27 07:42:07 +03:00
|
|
|
EXPECT_EQ(0, attr.st.st_rdev);
|
|
|
|
EXPECT_GT(attr.st.st_atime, 0);
|
|
|
|
EXPECT_GT(attr.st.st_mtime, 0);
|
|
|
|
EXPECT_GT(attr.st.st_ctime, 0);
|
2018-02-09 06:32:04 +03:00
|
|
|
EXPECT_GT(attr.st.st_blksize, 0);
|
|
|
|
|
|
|
|
// Note that st_blocks always refers to 512B blocks, and is not related to
|
|
|
|
// the block size reported in st_blksize.
|
|
|
|
//
|
|
|
|
// Eden doesn't really store data in blocks internally, and instead simply
|
|
|
|
// computes the value in st_blocks based on st_size. This is mainly so that
|
|
|
|
// applications like "du" will report mostly sane results.
|
|
|
|
if (attr.st.st_size == 0) {
|
|
|
|
EXPECT_EQ(0, attr.st.st_blocks);
|
|
|
|
} else {
|
|
|
|
EXPECT_GE(512 * attr.st.st_blocks, attr.st.st_size);
|
|
|
|
EXPECT_LT(512 * (attr.st.st_blocks - 1), attr.st.st_size);
|
|
|
|
}
|
2017-04-27 07:42:07 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Run some basic sanity checks on an inode's attributes.
|
|
|
|
*
|
|
|
|
* This can be invoked with either a two arguments (an inode and attributes),
|
|
|
|
* or with just a single argument (just the inode). If only one argument is
|
|
|
|
* supplied the attributes will be retrieved by calling getattr() on the inode.
|
|
|
|
*
|
|
|
|
* This checks several fixed invariants:
|
|
|
|
* - The inode number reported in the attributes should match the input inode's
|
|
|
|
* number.
|
|
|
|
* - The UID and GID should match the EdenMount's user and group IDs.
|
|
|
|
* - The link count should always be 1.
|
|
|
|
* - The timestamps should be greater than 0.
|
|
|
|
*/
|
|
|
|
#define BASIC_ATTR_CHECKS(inode, ...) \
|
|
|
|
({ \
|
|
|
|
SCOPED_TRACE( \
|
|
|
|
folly::to<std::string>("Originally from ", __FILE__, ":", __LINE__)); \
|
|
|
|
basicAttrChecks(inode, ##__VA_ARGS__); \
|
|
|
|
})
|
2017-11-04 01:58:04 +03:00
|
|
|
} // namespace
|
2017-04-27 07:42:07 +03:00
|
|
|
|
|
|
|
class FileInodeTest : public ::testing::Test {
|
|
|
|
protected:
|
|
|
|
void SetUp() override {
|
2017-12-05 20:55:31 +03:00
|
|
|
// Default to a nonzero time.
|
|
|
|
mount_.getClock().advance(9876min);
|
|
|
|
|
2017-04-27 07:42:07 +03:00
|
|
|
// Set up a directory structure that we will use for most
|
|
|
|
// of the tests below
|
|
|
|
FakeTreeBuilder builder;
|
2017-12-05 20:52:56 +03:00
|
|
|
builder.setFiles({{"dir/a.txt", "This is a.txt.\n"},
|
|
|
|
{"dir/sub/b.txt", "This is b.txt.\n"}});
|
2017-04-27 07:42:07 +03:00
|
|
|
mount_.initialize(builder);
|
|
|
|
}
|
|
|
|
|
|
|
|
TestMount mount_;
|
|
|
|
};
|
|
|
|
|
2017-12-15 03:36:38 +03:00
|
|
|
TEST_F(FileInodeTest, getType) {
|
|
|
|
auto dir = mount_.getTreeInode("dir/sub");
|
|
|
|
auto regularFile = mount_.getFileInode("dir/a.txt");
|
|
|
|
EXPECT_EQ(dtype_t::Dir, dir->getType());
|
|
|
|
EXPECT_EQ(dtype_t::Regular, regularFile->getType());
|
|
|
|
}
|
|
|
|
|
2017-04-27 07:42:07 +03:00
|
|
|
TEST_F(FileInodeTest, getattrFromBlob) {
|
|
|
|
auto inode = mount_.getFileInode("dir/a.txt");
|
|
|
|
auto attr = getFileAttr(inode);
|
|
|
|
|
|
|
|
BASIC_ATTR_CHECKS(inode, attr);
|
|
|
|
EXPECT_EQ((S_IFREG | 0644), attr.st.st_mode);
|
|
|
|
EXPECT_EQ(15, attr.st.st_size);
|
2018-02-09 06:32:04 +03:00
|
|
|
EXPECT_EQ(1, attr.st.st_blocks);
|
2017-04-27 07:42:07 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(FileInodeTest, getattrFromOverlay) {
|
2017-12-05 20:55:31 +03:00
|
|
|
auto start = mount_.getClock().getTimePoint();
|
|
|
|
|
2017-04-27 07:42:07 +03:00
|
|
|
mount_.addFile("dir/new_file.c", "hello\nworld\n");
|
|
|
|
auto inode = mount_.getFileInode("dir/new_file.c");
|
|
|
|
|
|
|
|
auto attr = getFileAttr(inode);
|
|
|
|
BASIC_ATTR_CHECKS(inode, attr);
|
|
|
|
EXPECT_EQ((S_IFREG | 0644), attr.st.st_mode);
|
|
|
|
EXPECT_EQ(12, attr.st.st_size);
|
2018-02-09 06:32:04 +03:00
|
|
|
EXPECT_EQ(1, attr.st.st_blocks);
|
2017-12-05 20:55:31 +03:00
|
|
|
EXPECT_EQ(folly::to<FakeClock::time_point>(attr.st.st_atim), start);
|
|
|
|
EXPECT_EQ(folly::to<FakeClock::time_point>(attr.st.st_mtim), start);
|
|
|
|
EXPECT_EQ(folly::to<FakeClock::time_point>(attr.st.st_ctim), start);
|
2017-04-27 07:42:07 +03:00
|
|
|
}
|
|
|
|
|
2018-03-23 22:34:59 +03:00
|
|
|
void testSetattrTruncateAll(TestMount& mount) {
|
|
|
|
auto inode = mount.getFileInode("dir/a.txt");
|
2018-01-03 03:25:03 +03:00
|
|
|
fuse_setattr_in desired = {};
|
|
|
|
desired.valid = FATTR_SIZE;
|
|
|
|
auto attr = setFileAttr(inode, desired);
|
2017-04-27 07:42:07 +03:00
|
|
|
|
|
|
|
BASIC_ATTR_CHECKS(inode, attr);
|
|
|
|
EXPECT_EQ((S_IFREG | 0644), attr.st.st_mode);
|
|
|
|
EXPECT_EQ(0, attr.st.st_size);
|
2018-02-09 06:32:04 +03:00
|
|
|
EXPECT_EQ(0, attr.st.st_blocks);
|
2017-04-27 07:42:07 +03:00
|
|
|
|
|
|
|
EXPECT_FILE_INODE(inode, "", 0644);
|
|
|
|
}
|
|
|
|
|
2018-03-23 22:34:59 +03:00
|
|
|
TEST_F(FileInodeTest, setattrTruncateAll) {
|
|
|
|
testSetattrTruncateAll(mount_);
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(FileInodeTest, setattrTruncateAllMaterialized) {
|
|
|
|
// Modify the inode before running the test, so that
|
|
|
|
// it will be materialized in the overlay.
|
|
|
|
auto inode = mount_.getFileInode("dir/a.txt");
|
|
|
|
inode->write("THIS IS A.TXT.\n", 0);
|
|
|
|
inode.reset();
|
|
|
|
|
|
|
|
testSetattrTruncateAll(mount_);
|
|
|
|
}
|
|
|
|
|
2017-04-27 07:42:07 +03:00
|
|
|
TEST_F(FileInodeTest, setattrTruncatePartial) {
|
|
|
|
auto inode = mount_.getFileInode("dir/a.txt");
|
2018-01-03 03:25:03 +03:00
|
|
|
fuse_setattr_in desired = {};
|
|
|
|
desired.size = 4;
|
|
|
|
desired.valid = FATTR_SIZE;
|
|
|
|
auto attr = setFileAttr(inode, desired);
|
2017-04-27 07:42:07 +03:00
|
|
|
|
|
|
|
BASIC_ATTR_CHECKS(inode, attr);
|
|
|
|
EXPECT_EQ((S_IFREG | 0644), attr.st.st_mode);
|
|
|
|
EXPECT_EQ(4, attr.st.st_size);
|
|
|
|
|
|
|
|
EXPECT_FILE_INODE(inode, "This", 0644);
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(FileInodeTest, setattrBiggerSize) {
|
|
|
|
auto inode = mount_.getFileInode("dir/a.txt");
|
2018-01-03 03:25:03 +03:00
|
|
|
fuse_setattr_in desired = {};
|
|
|
|
desired.size = 30;
|
|
|
|
desired.valid = FATTR_SIZE;
|
|
|
|
auto attr = setFileAttr(inode, desired);
|
2017-04-27 07:42:07 +03:00
|
|
|
|
|
|
|
BASIC_ATTR_CHECKS(inode, attr);
|
|
|
|
EXPECT_EQ((S_IFREG | 0644), attr.st.st_mode);
|
|
|
|
EXPECT_EQ(30, attr.st.st_size);
|
|
|
|
|
|
|
|
StringPiece expectedContents(
|
|
|
|
"This is a.txt.\n"
|
|
|
|
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0",
|
|
|
|
30);
|
|
|
|
EXPECT_FILE_INODE(inode, expectedContents, 0644);
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(FileInodeTest, setattrPermissions) {
|
|
|
|
auto inode = mount_.getFileInode("dir/a.txt");
|
2018-01-03 03:25:03 +03:00
|
|
|
fuse_setattr_in desired = {};
|
2017-04-27 07:42:07 +03:00
|
|
|
|
|
|
|
for (int n = 0; n <= 0777; ++n) {
|
2018-01-03 03:25:03 +03:00
|
|
|
desired.mode = n;
|
|
|
|
desired.valid = FATTR_MODE;
|
|
|
|
auto attr = setFileAttr(inode, desired);
|
2017-04-27 07:42:07 +03:00
|
|
|
|
|
|
|
BASIC_ATTR_CHECKS(inode, attr);
|
|
|
|
EXPECT_EQ((S_IFREG | n), attr.st.st_mode);
|
|
|
|
EXPECT_EQ(15, attr.st.st_size);
|
|
|
|
EXPECT_FILE_INODE(inode, "This is a.txt.\n", n);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(FileInodeTest, setattrFileType) {
|
|
|
|
auto inode = mount_.getFileInode("dir/a.txt");
|
2018-01-03 03:25:03 +03:00
|
|
|
fuse_setattr_in desired = {};
|
2017-04-27 07:42:07 +03:00
|
|
|
|
|
|
|
// File type bits in the mode should be ignored.
|
2018-01-03 03:25:03 +03:00
|
|
|
desired.mode = S_IFLNK | 0755;
|
|
|
|
desired.valid = FATTR_MODE;
|
|
|
|
auto attr = setFileAttr(inode, desired);
|
2017-04-27 07:42:07 +03:00
|
|
|
|
|
|
|
BASIC_ATTR_CHECKS(inode, attr);
|
|
|
|
EXPECT_EQ((S_IFREG | 0755), attr.st.st_mode)
|
|
|
|
<< "File type bits in the mode should be ignored by setattr()";
|
|
|
|
EXPECT_EQ(15, attr.st.st_size);
|
|
|
|
EXPECT_FILE_INODE(inode, "This is a.txt.\n", 0755);
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(FileInodeTest, setattrAtime) {
|
|
|
|
auto inode = mount_.getFileInode("dir/a.txt");
|
2018-01-03 03:25:03 +03:00
|
|
|
fuse_setattr_in desired = {};
|
|
|
|
desired.valid = FATTR_ATIME;
|
2017-04-27 07:42:07 +03:00
|
|
|
|
|
|
|
// Set the atime to a specific value
|
2018-01-03 03:25:03 +03:00
|
|
|
desired.atime = 1234;
|
|
|
|
desired.atimensec = 5678;
|
|
|
|
auto attr = setFileAttr(inode, desired);
|
2017-04-27 07:42:07 +03:00
|
|
|
|
|
|
|
BASIC_ATTR_CHECKS(inode, attr);
|
|
|
|
EXPECT_EQ(1234, attr.st.st_atime);
|
|
|
|
EXPECT_EQ(1234, attr.st.st_atim.tv_sec);
|
|
|
|
EXPECT_EQ(5678, attr.st.st_atim.tv_nsec);
|
|
|
|
|
2017-12-05 20:55:31 +03:00
|
|
|
mount_.getClock().advance(10min);
|
|
|
|
|
2017-04-27 07:42:07 +03:00
|
|
|
// Ask to set the atime to the current time
|
2018-01-03 03:25:03 +03:00
|
|
|
desired.atime = 8765;
|
|
|
|
desired.atimensec = 4321;
|
|
|
|
desired.valid = FATTR_ATIME_NOW;
|
|
|
|
attr = setFileAttr(inode, desired);
|
2017-04-27 07:42:07 +03:00
|
|
|
|
|
|
|
BASIC_ATTR_CHECKS(inode, attr);
|
2017-12-05 20:55:31 +03:00
|
|
|
EXPECT_EQ(
|
|
|
|
mount_.getClock().getTimePoint(),
|
|
|
|
folly::to<FakeClock::time_point>(attr.st.st_atim));
|
2017-04-27 07:42:07 +03:00
|
|
|
}
|
|
|
|
|
2018-03-23 22:34:59 +03:00
|
|
|
void testSetattrMtime(TestMount& mount) {
|
|
|
|
auto inode = mount.getFileInode("dir/a.txt");
|
2018-01-03 03:25:03 +03:00
|
|
|
fuse_setattr_in desired = {};
|
2017-04-27 07:42:07 +03:00
|
|
|
|
|
|
|
// Set the mtime to a specific value
|
2018-01-03 03:25:03 +03:00
|
|
|
desired.mtime = 1234;
|
|
|
|
desired.mtimensec = 5678;
|
|
|
|
desired.valid = FATTR_MTIME;
|
|
|
|
auto attr = setFileAttr(inode, desired);
|
2017-04-27 07:42:07 +03:00
|
|
|
|
|
|
|
BASIC_ATTR_CHECKS(inode, attr);
|
|
|
|
EXPECT_EQ(1234, attr.st.st_mtime);
|
|
|
|
EXPECT_EQ(1234, attr.st.st_mtim.tv_sec);
|
|
|
|
EXPECT_EQ(5678, attr.st.st_mtim.tv_nsec);
|
|
|
|
|
|
|
|
// Ask to set the mtime to the current time
|
2018-03-23 22:34:59 +03:00
|
|
|
mount.getClock().advance(1234min);
|
|
|
|
auto start = mount.getClock().getTimePoint();
|
2018-01-03 03:25:03 +03:00
|
|
|
desired.mtime = 8765;
|
|
|
|
desired.mtimensec = 4321;
|
|
|
|
desired.valid = FATTR_MTIME_NOW;
|
|
|
|
attr = setFileAttr(inode, desired);
|
2017-04-27 07:42:07 +03:00
|
|
|
|
|
|
|
BASIC_ATTR_CHECKS(inode, attr);
|
2017-12-05 20:55:31 +03:00
|
|
|
EXPECT_EQ(start, folly::to<FakeClock::time_point>(attr.st.st_mtim));
|
2017-04-27 07:42:07 +03:00
|
|
|
}
|
|
|
|
|
2018-03-23 22:34:59 +03:00
|
|
|
TEST_F(FileInodeTest, setattrMtime) {
|
|
|
|
testSetattrMtime(mount_);
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(FileInodeTest, setattrMtimeMaterialized) {
|
|
|
|
// Modify the inode before running the test, so that
|
|
|
|
// it will be materialized in the overlay.
|
|
|
|
auto inode = mount_.getFileInode("dir/a.txt");
|
|
|
|
inode->write("THIS IS A.TXT.\n", 0);
|
|
|
|
inode.reset();
|
|
|
|
|
|
|
|
testSetattrMtime(mount_);
|
|
|
|
}
|
|
|
|
|
2017-12-05 20:52:56 +03:00
|
|
|
namespace {
|
|
|
|
bool isInodeMaterialized(const TreeInodePtr& inode) {
|
|
|
|
return inode->getContents().wlock()->isMaterialized();
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
TEST_F(FileInodeTest, writingMaterializesParent) {
|
|
|
|
auto inode = mount_.getFileInode("dir/sub/b.txt");
|
|
|
|
auto parent = mount_.getTreeInode("dir/sub");
|
|
|
|
auto grandparent = mount_.getTreeInode("dir");
|
|
|
|
|
|
|
|
EXPECT_EQ(false, isInodeMaterialized(grandparent));
|
|
|
|
EXPECT_EQ(false, isInodeMaterialized(parent));
|
|
|
|
|
2018-01-03 03:25:03 +03:00
|
|
|
auto handle = inode->open(O_WRONLY).get();
|
2017-12-05 20:52:56 +03:00
|
|
|
auto written = handle->write("abcd", 0).get();
|
|
|
|
EXPECT_EQ(4, written);
|
|
|
|
|
|
|
|
EXPECT_EQ(true, isInodeMaterialized(grandparent));
|
|
|
|
EXPECT_EQ(true, isInodeMaterialized(parent));
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(FileInodeTest, truncatingMaterializesParent) {
|
|
|
|
auto inode = mount_.getFileInode("dir/sub/b.txt");
|
|
|
|
auto parent = mount_.getTreeInode("dir/sub");
|
|
|
|
auto grandparent = mount_.getTreeInode("dir");
|
|
|
|
|
|
|
|
EXPECT_EQ(false, isInodeMaterialized(grandparent));
|
|
|
|
EXPECT_EQ(false, isInodeMaterialized(parent));
|
|
|
|
|
2018-01-03 03:25:03 +03:00
|
|
|
(void)inode->open(O_WRONLY | O_TRUNC).get();
|
2017-12-05 20:52:56 +03:00
|
|
|
|
|
|
|
EXPECT_EQ(true, isInodeMaterialized(grandparent));
|
|
|
|
EXPECT_EQ(true, isInodeMaterialized(parent));
|
|
|
|
}
|
|
|
|
|
2018-03-22 08:21:16 +03:00
|
|
|
TEST(FileInode, truncatingDuringLoad) {
|
2018-01-04 04:18:32 +03:00
|
|
|
FakeTreeBuilder builder;
|
|
|
|
builder.setFiles({{"notready.txt", "Contents not ready.\n"}});
|
|
|
|
|
|
|
|
TestMount mount_;
|
|
|
|
mount_.initialize(builder, false);
|
|
|
|
|
|
|
|
auto inode = mount_.getFileInode("notready.txt");
|
|
|
|
|
|
|
|
auto backingStore = mount_.getBackingStore();
|
|
|
|
auto storedBlob = backingStore->getStoredBlob(*inode->getBlobHash());
|
|
|
|
|
|
|
|
auto readAllFuture = inode->readAll();
|
|
|
|
EXPECT_EQ(false, readAllFuture.isReady());
|
|
|
|
|
|
|
|
{
|
|
|
|
// Synchronously truncate the file while the load is in progress.
|
|
|
|
auto handleFuture = inode->open(O_TRUNC);
|
|
|
|
EXPECT_EQ(true, handleFuture.isReady());
|
|
|
|
EXPECT_EQ(true, handleFuture.hasValue());
|
|
|
|
// Deallocate the handle here, closing the open file.
|
|
|
|
}
|
|
|
|
|
|
|
|
// Verify, from the caller's perspective, the load is complete (but empty).
|
|
|
|
EXPECT_EQ(true, readAllFuture.isReady());
|
|
|
|
EXPECT_EQ("", readAllFuture.value());
|
|
|
|
|
|
|
|
// Now finish the ObjectStore load request to make sure the FileInode
|
|
|
|
// handles the state correctly.
|
|
|
|
storedBlob->setReady();
|
|
|
|
}
|
|
|
|
|
2018-03-22 08:21:16 +03:00
|
|
|
TEST(FileInode, readDuringLoad) {
|
|
|
|
// Build a tree to test against, but do not mark the state ready yet
|
|
|
|
FakeTreeBuilder builder;
|
|
|
|
auto contents = "Contents not ready.\n"_sp;
|
|
|
|
builder.setFiles({{"notready.txt", contents}});
|
|
|
|
TestMount mount_;
|
|
|
|
mount_.initialize(builder, false);
|
|
|
|
|
|
|
|
// Load the inode and start reading the contents
|
|
|
|
auto inode = mount_.getFileInode("notready.txt");
|
|
|
|
auto dataFuture = inode->open(O_RDONLY).then(
|
|
|
|
[](std::shared_ptr<FileHandle> handle) { return handle->read(4096, 0); });
|
|
|
|
EXPECT_FALSE(dataFuture.isReady());
|
|
|
|
|
|
|
|
// Make the backing store data ready now.
|
|
|
|
builder.setAllReady();
|
|
|
|
|
|
|
|
// The read() operation should have completed now.
|
|
|
|
ASSERT_TRUE(dataFuture.isReady());
|
|
|
|
EXPECT_EQ(contents, dataFuture.get().copyData());
|
|
|
|
}
|
|
|
|
|
2018-03-23 22:34:59 +03:00
|
|
|
TEST(FileInode, writeDuringLoad) {
|
|
|
|
// Build a tree to test against, but do not mark the state ready yet
|
|
|
|
FakeTreeBuilder builder;
|
|
|
|
builder.setFiles({{"notready.txt", "Contents not ready.\n"}});
|
|
|
|
TestMount mount_;
|
|
|
|
mount_.initialize(builder, false);
|
|
|
|
|
|
|
|
// Load the inode and start reading the contents
|
|
|
|
auto inode = mount_.getFileInode("notready.txt");
|
|
|
|
auto handleFuture = inode->open(O_WRONLY);
|
|
|
|
ASSERT_TRUE(handleFuture.isReady());
|
|
|
|
auto handle = handleFuture.get();
|
|
|
|
|
|
|
|
auto newContents = "TENTS"_sp;
|
|
|
|
auto writeFuture = handle->write(newContents, 3);
|
|
|
|
EXPECT_FALSE(writeFuture.isReady());
|
|
|
|
|
|
|
|
// Make the backing store data ready now.
|
|
|
|
builder.setAllReady();
|
|
|
|
|
|
|
|
// The write() operation should have completed now.
|
|
|
|
ASSERT_TRUE(writeFuture.isReady());
|
|
|
|
EXPECT_EQ(newContents.size(), writeFuture.get());
|
|
|
|
|
|
|
|
// We should be able to read back our modified data now.
|
|
|
|
EXPECT_FILE_INODE(inode, "ConTENTS not ready.\n", 0644);
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(FileInode, truncateDuringLoad) {
|
|
|
|
// Build a tree to test against, but do not mark the state ready yet
|
|
|
|
FakeTreeBuilder builder;
|
|
|
|
builder.setFiles({{"notready.txt", "Contents not ready.\n"}});
|
|
|
|
TestMount mount_;
|
|
|
|
mount_.initialize(builder, false);
|
|
|
|
|
|
|
|
auto inode = mount_.getFileInode("notready.txt");
|
|
|
|
|
|
|
|
// Open the file and start reading the contents
|
|
|
|
auto handleFuture = inode->open(O_RDWR);
|
|
|
|
ASSERT_TRUE(handleFuture.isReady());
|
|
|
|
auto handle = handleFuture.get();
|
|
|
|
auto dataFuture = handle->read(4096, 0);
|
|
|
|
EXPECT_FALSE(dataFuture.isReady());
|
|
|
|
|
|
|
|
// Open the file again with O_TRUNC while the initial read is in progress.
|
|
|
|
// This should immediately truncate the file even without needing to wait for
|
|
|
|
// the data from the object store.
|
|
|
|
auto truncHandleFuture = inode->open(O_WRONLY | O_TRUNC);
|
|
|
|
ASSERT_TRUE(truncHandleFuture.isReady());
|
|
|
|
auto truncHandle = truncHandleFuture.get();
|
|
|
|
|
|
|
|
// The read should complete now too.
|
|
|
|
ASSERT_TRUE(dataFuture.isReady());
|
|
|
|
EXPECT_EQ("", dataFuture.get().copyData());
|
|
|
|
|
|
|
|
// For good measure, test reading and writing some more.
|
2018-04-27 06:41:40 +03:00
|
|
|
truncHandle->write("foobar\n"_sp, 5).get(0ms);
|
2018-03-23 22:34:59 +03:00
|
|
|
|
|
|
|
dataFuture = handle->read(4096, 0);
|
|
|
|
ASSERT_TRUE(dataFuture.isReady());
|
|
|
|
EXPECT_EQ("\0\0\0\0\0foobar\n"_sp, dataFuture.get().copyData());
|
|
|
|
|
|
|
|
EXPECT_FILE_INODE(inode, "\0\0\0\0\0foobar\n"_sp, 0644);
|
|
|
|
}
|
|
|
|
|
2017-04-27 07:42:07 +03:00
|
|
|
// TODO: test multiple flags together
|
|
|
|
// TODO: ensure ctime is updated after every call to setattr()
|
|
|
|
// TODO: ensure mtime is updated after opening a file, writing to it, then
|
|
|
|
// closing it.
|