diff options
-rw-r--r-- | incfs/incfs.cpp | 20 | ||||
-rw-r--r-- | incfs/include/incfs.h | 1 | ||||
-rw-r--r-- | incfs/include/incfs_ndk.h | 6 | ||||
-rw-r--r-- | incfs/tests/incfs_test.cpp | 127 | ||||
-rw-r--r-- | incfs/tests/include/IncFsTestBase.h | 1 |
5 files changed, 142 insertions, 13 deletions
diff --git a/incfs/incfs.cpp b/incfs/incfs.cpp index 490b907..842e381 100644 --- a/incfs/incfs.cpp +++ b/incfs/incfs.cpp @@ -252,14 +252,15 @@ bool IncFs_IsEnabled() { static Features readIncFsFeatures() { init().enabledAndReady(); + int res = Features::none | Features::mappingFilesProgressFixed; + static const char kSysfsFeaturesDir[] = "/sys/fs/" INCFS_NAME "/features"; const auto dir = path::openDir(kSysfsFeaturesDir); if (!dir) { PLOG(ERROR) << "IncFs_Features: failed to open features dir, assuming v1/none."; - return Features::none; + return Features(res); } - int res = Features::none; while (auto entry = ::readdir(dir.get())) { if (entry->d_type != DT_REG) { continue; @@ -1555,7 +1556,7 @@ IncFsErrorCode IncFs_IsFullyLoadedByPath(const IncFsControl* control, const char if (features() & Features::v2) { const auto id = getId(::getxattr, path); if (id == kIncFsInvalidFileId) { - return -errno; + return -ENOTSUP; } return isFullyLoadedV2(root, id); } @@ -1692,7 +1693,20 @@ IncFsErrorCode IncFs_GetUidReadTimeouts(const IncFsControl* control, return 0; } +// Trying to detect if this is a mapped file. +// Not the best way as it might return true for other system files. +// TODO: remove after IncFS returns ENOTSUP for such files. +static bool isMapped(int fd) { + char buffer[kIncFsFileIdStringLength]; + const auto res = ::fgetxattr(fd, kIdAttrName, buffer, sizeof(buffer)); + return res != sizeof(buffer); +} + static IncFsErrorCode getFileBlockCount(int fd, IncFsBlockCounts* blockCount) { + if (isMapped(fd)) { + return -ENOTSUP; + } + incfs_get_block_count_args args = {}; auto res = ::ioctl(fd, INCFS_IOC_GET_BLOCK_COUNT, &args); if (res < 0) { diff --git a/incfs/include/incfs.h b/incfs/include/incfs.h index 76619c2..5dfe060 100644 --- a/incfs/include/incfs.h +++ b/incfs/include/incfs.h @@ -41,6 +41,7 @@ enum Features { none = INCFS_FEATURE_NONE, core = INCFS_FEATURE_CORE, v2 = INCFS_FEATURE_V2, + mappingFilesProgressFixed = INCFS_FEATURE_MAPPING_FILES_PROGRESS_FIXED, }; enum class HashAlgorithm { diff --git a/incfs/include/incfs_ndk.h b/incfs/include/incfs_ndk.h index 2a538bc..4862f39 100644 --- a/incfs/include/incfs_ndk.h +++ b/incfs/include/incfs_ndk.h @@ -43,8 +43,10 @@ static const int kIncFsFileIdStringLength = sizeof(IncFsFileId) * 2; typedef enum { INCFS_FEATURE_NONE = 0, - INCFS_FEATURE_CORE = 1, - INCFS_FEATURE_V2 = 2, + INCFS_FEATURE_CORE = 1 << 0, + INCFS_FEATURE_V2 = 1 << 1, + INCFS_FEATURE_MAPPING_FILES_PROGRESS_FIXED = 1 << 2, + } IncFsFeatures; typedef int IncFsErrorCode; diff --git a/incfs/tests/incfs_test.cpp b/incfs/tests/incfs_test.cpp index e651f2e..94bbdcd 100644 --- a/incfs/tests/incfs_test.cpp +++ b/incfs/tests/incfs_test.cpp @@ -105,25 +105,29 @@ protected: ASSERT_EQ((int)std::size(blocks), writeBlocks({blocks, std::size(blocks)})); } - template <class ReadStruct> - void testWriteBlockAndPageRead() { - const auto id = fileId(1); - ASSERT_TRUE(control_.logs() >= 0); - ASSERT_EQ(0, - makeFile(control_, mountPath(test_file_name_), 0555, id, - {.size = test_file_size_})); + void writeBlock(int pageIndex) { auto fd = openForSpecialOps(control_, fileId(1)); ASSERT_GE(fd.get(), 0); std::vector<char> data(INCFS_DATA_FILE_BLOCK_SIZE); auto block = DataBlock{ .fileFd = fd.get(), - .pageIndex = 0, + .pageIndex = pageIndex, .compression = INCFS_COMPRESSION_KIND_NONE, .dataSize = (uint32_t)data.size(), .data = data.data(), }; ASSERT_EQ(1, writeBlocks({&block, 1})); + } + + template <class ReadStruct> + void testWriteBlockAndPageRead() { + const auto id = fileId(1); + ASSERT_TRUE(control_.logs() >= 0); + ASSERT_EQ(0, + makeFile(control_, mountPath(test_file_name_), 0555, id, + {.size = test_file_size_})); + writeBlock(/*pageIndex=*/0); std::thread wait_page_read_thread([&]() { std::vector<ReadStruct> reads; @@ -149,6 +153,7 @@ protected: ASSERT_TRUE(android::base::ReadFully(readFd, buf, sizeof(buf))); wait_page_read_thread.join(); } + template <class PendingRead> void testWaitForPendingReads() { const auto id = fileId(1); @@ -366,6 +371,39 @@ TEST_F(IncFsTest, MakeFile0) { ASSERT_EQ(0, (int)s.st_size); } +TEST_F(IncFsTest, MakeMappedFile) { + ASSERT_EQ(0, makeDir(control_, mountPath(test_dir_name_))); + + constexpr auto file_size = INCFS_DATA_FILE_BLOCK_SIZE * 2; + constexpr auto mapped_file_offset = file_size / 2; + constexpr auto mapped_file_size = file_size / 3; + + const auto file_path = mountPath(test_dir_name_, test_file_name_); + ASSERT_FALSE(exists(file_path)); + ASSERT_EQ(0, + makeFile(control_, file_path, 0111, fileId(1), + {.size = file_size, .metadata = metadata("md")})); + struct stat s = {}; + ASSERT_EQ(0, stat(file_path.c_str(), &s)); + ASSERT_EQ(file_size, (int)s.st_size); + + const auto mapped_file_path = mountPath(test_dir_name_, test_mapped_file_name_); + ASSERT_FALSE(exists(mapped_file_path)); + ASSERT_EQ(0, + makeMappedFile(control_, mapped_file_path, 0111, + {.sourceId = fileId(1), + .sourceOffset = mapped_file_offset, + .size = mapped_file_size})); + s = {}; + ASSERT_EQ(0, stat(mapped_file_path.c_str(), &s)); + ASSERT_EQ(mapped_file_size, (int)s.st_size); + + // Check fileId for the source file. + ASSERT_EQ(fileId(1), getFileId(control_, file_path)); + // Check that there is no fileId for the mapped file. + ASSERT_EQ(kIncFsInvalidFileId, getFileId(control_, mapped_file_path)); +} + TEST_F(IncFsTest, GetFileId) { auto id = fileId(1); ASSERT_EQ(0, @@ -1384,3 +1422,76 @@ TEST_F(IncFsGetMetricsTest, MetricsWithReadsDelayedPerUidTimeout) { EXPECT_EQ(0, (int)incfsMetrics.readsFailedOther); EXPECT_EQ(0, (int)incfsMetrics.readsFailedTimedOut); } + +inline bool operator==(const BlockCounts& lhs, const BlockCounts& rhs) { + return lhs.totalDataBlocks == rhs.totalDataBlocks && + lhs.filledDataBlocks == rhs.filledDataBlocks && + lhs.totalHashBlocks == rhs.totalHashBlocks && + lhs.filledHashBlocks == rhs.filledHashBlocks; +} + +TEST_F(IncFsTest, LoadingProgress) { + ASSERT_EQ(0, makeDir(control_, mountPath(test_dir_name_))); + + constexpr auto file_size = INCFS_DATA_FILE_BLOCK_SIZE * 2; + constexpr auto mapped_file_offset = file_size / 2; + constexpr auto mapped_file_size = file_size / 3; + + const auto file_id = fileId(1); + + const auto file_path = mountPath(test_dir_name_, test_file_name_); + ASSERT_FALSE(exists(file_path)); + ASSERT_EQ(0, + makeFile(control_, file_path, 0111, file_id, + {.size = file_size, .metadata = metadata("md")})); + struct stat s = {}; + ASSERT_EQ(0, stat(file_path.c_str(), &s)); + ASSERT_EQ(file_size, (int)s.st_size); + + const auto mapped_file_path = mountPath(test_dir_name_, test_mapped_file_name_); + ASSERT_FALSE(exists(mapped_file_path)); + ASSERT_EQ(0, + makeMappedFile(control_, mapped_file_path, 0111, + {.sourceId = file_id, + .sourceOffset = mapped_file_offset, + .size = mapped_file_size})); + s = {}; + ASSERT_EQ(0, stat(mapped_file_path.c_str(), &s)); + ASSERT_EQ(mapped_file_size, (int)s.st_size); + + // Check fully loaded first. + ASSERT_EQ(LoadingState::MissingBlocks, isFullyLoaded(control_, file_path)); + ASSERT_EQ(LoadingState::MissingBlocks, isFullyLoaded(control_, file_id)); + ASSERT_EQ((LoadingState)-ENOTSUP, isFullyLoaded(control_, mapped_file_path)); + + // Next is loading progress. + ASSERT_EQ(BlockCounts{.totalDataBlocks = 2}, *getBlockCount(control_, file_path)); + ASSERT_EQ(BlockCounts{.totalDataBlocks = 2}, *getBlockCount(control_, file_id)); + ASSERT_FALSE(getBlockCount(control_, mapped_file_path)); + + // Now write a page #0. + ASSERT_NO_FATAL_FAILURE(writeBlock(0)); + + // Recheck everything. + ASSERT_EQ(LoadingState::MissingBlocks, isFullyLoaded(control_, file_path)); + ASSERT_EQ(LoadingState::MissingBlocks, isFullyLoaded(control_, file_id)); + ASSERT_EQ((LoadingState)-ENOTSUP, isFullyLoaded(control_, mapped_file_path)); + + BlockCounts onePage{.totalDataBlocks = 2, .filledDataBlocks = 1}; + ASSERT_EQ(onePage, *getBlockCount(control_, file_path)); + ASSERT_EQ(onePage, *getBlockCount(control_, file_id)); + ASSERT_FALSE(getBlockCount(control_, mapped_file_path)); + + // Now write a page #1. + ASSERT_NO_FATAL_FAILURE(writeBlock(1)); + + // Check for fully loaded. + ASSERT_EQ(LoadingState::Full, isFullyLoaded(control_, file_path)); + ASSERT_EQ(LoadingState::Full, isFullyLoaded(control_, file_id)); + ASSERT_EQ((LoadingState)-ENOTSUP, isFullyLoaded(control_, mapped_file_path)); + + BlockCounts twoPages{.totalDataBlocks = 2, .filledDataBlocks = 2}; + ASSERT_EQ(twoPages, *getBlockCount(control_, file_path)); + ASSERT_EQ(twoPages, *getBlockCount(control_, file_id)); + ASSERT_FALSE(getBlockCount(control_, mapped_file_path)); +} diff --git a/incfs/tests/include/IncFsTestBase.h b/incfs/tests/include/IncFsTestBase.h index 5f8c311..8c61d64 100644 --- a/incfs/tests/include/IncFsTestBase.h +++ b/incfs/tests/include/IncFsTestBase.h @@ -117,6 +117,7 @@ protected: std::string image_dir_path_; std::optional<TemporaryDir> tmp_dir_for_image_; inline static const std::string_view test_file_name_ = "test.txt"; + inline static const std::string_view test_mapped_file_name_ = "mapped.txt"; inline static const std::string_view test_dir_name_ = "test_dir"; std::string metrics_key_; Control control_; |