1 /*
2 * Copyright (C) 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "incfs.h"
18
19 #include <android-base/file.h>
20 #include <android-base/logging.h>
21 #include <android-base/unique_fd.h>
22 #include <gtest/gtest.h>
23 #include <selinux/selinux.h>
24 #include <sys/select.h>
25 #include <unistd.h>
26
27 #include <optional>
28 #include <thread>
29
30 #include "path.h"
31
32 using namespace android::incfs;
33 using namespace std::literals;
34
exists(std::string_view path)35 static bool exists(std::string_view path) {
36 return access(path.data(), F_OK) == 0;
37 }
38
39 struct ScopedUnmount {
40 std::string path_;
ScopedUnmountScopedUnmount41 explicit ScopedUnmount(std::string&& path) : path_(std::move(path)) {}
~ScopedUnmountScopedUnmount42 ~ScopedUnmount() { unmount(path_); }
43 };
44
45 class IncFsTest : public ::testing::Test {
46 protected:
SetUp()47 virtual void SetUp() {
48 tmp_dir_for_mount_.emplace();
49 mount_dir_path_ = tmp_dir_for_mount_->path;
50 tmp_dir_for_image_.emplace();
51 image_dir_path_ = tmp_dir_for_image_->path;
52 ASSERT_TRUE(exists(image_dir_path_));
53 ASSERT_TRUE(exists(mount_dir_path_));
54 if (!enabled()) {
55 GTEST_SKIP() << "test not supported: IncFS is not enabled";
56 } else {
57 control_ =
58 mount(image_dir_path_, mount_dir_path_,
59 MountOptions{.readLogBufferPages = 4,
60 .defaultReadTimeoutMs = std::chrono::duration_cast<
61 std::chrono::milliseconds>(
62 kDefaultReadTimeout)
63 .count()});
64 ASSERT_TRUE(control_.cmd() >= 0) << "Expected >= 0 got " << control_.cmd();
65 ASSERT_TRUE(control_.pendingReads() >= 0);
66 ASSERT_TRUE(control_.logs() >= 0);
67 checkRestoreconResult(mountPath(INCFS_PENDING_READS_FILENAME));
68 checkRestoreconResult(mountPath(INCFS_LOG_FILENAME));
69 }
70 }
71
checkRestoreconResult(std::string_view path)72 static void checkRestoreconResult(std::string_view path) {
73 char* ctx = nullptr;
74 ASSERT_NE(-1, getfilecon(path.data(), &ctx));
75 ASSERT_EQ("u:object_r:shell_data_file:s0", std::string(ctx));
76 freecon(ctx);
77 }
78
TearDown()79 virtual void TearDown() {
80 unmount(mount_dir_path_);
81 tmp_dir_for_image_.reset();
82 tmp_dir_for_mount_.reset();
83 EXPECT_FALSE(exists(image_dir_path_));
84 EXPECT_FALSE(exists(mount_dir_path_));
85 }
86
87 template <class... Paths>
mountPath(Paths &&...paths) const88 std::string mountPath(Paths&&... paths) const {
89 return path::join(mount_dir_path_, std::forward<Paths>(paths)...);
90 }
91
fileId(uint64_t i)92 static IncFsFileId fileId(uint64_t i) {
93 IncFsFileId id = {};
94 static_assert(sizeof(id) >= sizeof(i));
95 memcpy(&id, &i, sizeof(i));
96 return id;
97 }
98
metadata(std::string_view sv)99 static IncFsSpan metadata(std::string_view sv) {
100 return {.data = sv.data(), .size = IncFsSize(sv.size())};
101 }
102
makeFileWithHash(int id)103 int makeFileWithHash(int id) {
104 // calculate the required size for two leaf hash blocks
105 constexpr auto size =
106 (INCFS_DATA_FILE_BLOCK_SIZE / INCFS_MAX_HASH_SIZE + 1) * INCFS_DATA_FILE_BLOCK_SIZE;
107
108 // assemble a signature/hashing data for it
109 struct __attribute__((packed)) Signature {
110 uint32_t version = INCFS_SIGNATURE_VERSION;
111 uint32_t hashingSize = sizeof(hashing);
112 struct __attribute__((packed)) Hashing {
113 uint32_t algo = INCFS_HASH_TREE_SHA256;
114 uint8_t log2Blocksize = 12;
115 uint32_t saltSize = 0;
116 uint32_t rootHashSize = INCFS_MAX_HASH_SIZE;
117 char rootHash[INCFS_MAX_HASH_SIZE] = {};
118 } hashing;
119 uint32_t signingSize = 0;
120 } signature;
121
122 int res = makeFile(control_, mountPath(test_file_name_), 0555, fileId(id),
123 {.size = size,
124 .signature = {.data = (char*)&signature, .size = sizeof(signature)}});
125 EXPECT_EQ(0, res);
126 return res ? -1 : size;
127 }
128
sizeToPages(int size)129 static int sizeToPages(int size) {
130 return (size + INCFS_DATA_FILE_BLOCK_SIZE - 1) / INCFS_DATA_FILE_BLOCK_SIZE;
131 }
132
writeTestRanges(int id,int size)133 void writeTestRanges(int id, int size) {
134 auto wfd = openForSpecialOps(control_, fileId(id));
135 ASSERT_GE(wfd.get(), 0);
136
137 auto lastPage = sizeToPages(size) - 1;
138
139 std::vector<char> data(INCFS_DATA_FILE_BLOCK_SIZE);
140 DataBlock blocks[] = {{
141 .fileFd = wfd.get(),
142 .pageIndex = 1,
143 .compression = INCFS_COMPRESSION_KIND_NONE,
144 .dataSize = (uint32_t)data.size(),
145 .data = data.data(),
146 },
147 {
148 .fileFd = wfd.get(),
149 .pageIndex = 2,
150 .compression = INCFS_COMPRESSION_KIND_NONE,
151 .dataSize = (uint32_t)data.size(),
152 .data = data.data(),
153 },
154 {
155 .fileFd = wfd.get(),
156 .pageIndex = 10,
157 .compression = INCFS_COMPRESSION_KIND_NONE,
158 .dataSize = (uint32_t)data.size(),
159 .data = data.data(),
160 },
161 {
162 .fileFd = wfd.get(),
163 // last data page
164 .pageIndex = lastPage,
165 .compression = INCFS_COMPRESSION_KIND_NONE,
166 .dataSize = (uint32_t)data.size(),
167 .data = data.data(),
168 },
169 {
170 .fileFd = wfd.get(),
171 // first hash page
172 .pageIndex = 0,
173 .compression = INCFS_COMPRESSION_KIND_NONE,
174 .dataSize = (uint32_t)data.size(),
175 .kind = INCFS_BLOCK_KIND_HASH,
176 .data = data.data(),
177 },
178 {
179 .fileFd = wfd.get(),
180 .pageIndex = 2,
181 .compression = INCFS_COMPRESSION_KIND_NONE,
182 .dataSize = (uint32_t)data.size(),
183 .kind = INCFS_BLOCK_KIND_HASH,
184 .data = data.data(),
185 }};
186 ASSERT_EQ((int)std::size(blocks), writeBlocks({blocks, std::size(blocks)}));
187 }
188
189 std::string mount_dir_path_;
190 std::optional<TemporaryDir> tmp_dir_for_mount_;
191 std::string image_dir_path_;
192 std::optional<TemporaryDir> tmp_dir_for_image_;
193 inline static const std::string_view test_file_name_ = "test.txt"sv;
194 inline static const std::string_view test_dir_name_ = "test_dir"sv;
195 inline static const int test_file_size_ = INCFS_DATA_FILE_BLOCK_SIZE;
196 Control control_;
197 };
198
TEST_F(IncFsTest,GetIncfsFeatures)199 TEST_F(IncFsTest, GetIncfsFeatures) {
200 ASSERT_NE(features(), none);
201 }
202
TEST_F(IncFsTest,FalseIncfsPath)203 TEST_F(IncFsTest, FalseIncfsPath) {
204 TemporaryDir test_dir;
205 ASSERT_FALSE(isIncFsPath(test_dir.path));
206 }
207
TEST_F(IncFsTest,TrueIncfsPath)208 TEST_F(IncFsTest, TrueIncfsPath) {
209 ASSERT_TRUE(isIncFsPath(mount_dir_path_));
210 }
211
TEST_F(IncFsTest,TrueIncfsPathForBindMount)212 TEST_F(IncFsTest, TrueIncfsPathForBindMount) {
213 TemporaryDir tmp_dir_to_bind;
214 ASSERT_EQ(0, makeDir(control_, mountPath(test_dir_name_)));
215 ASSERT_EQ(0, bindMount(mountPath(test_dir_name_), tmp_dir_to_bind.path));
216 ScopedUnmount su(tmp_dir_to_bind.path);
217 ASSERT_TRUE(isIncFsPath(tmp_dir_to_bind.path));
218 }
219
TEST_F(IncFsTest,Control)220 TEST_F(IncFsTest, Control) {
221 ASSERT_TRUE(control_);
222 EXPECT_GE(IncFs_GetControlFd(control_, CMD), 0);
223 EXPECT_GE(IncFs_GetControlFd(control_, PENDING_READS), 0);
224 EXPECT_GE(IncFs_GetControlFd(control_, LOGS), 0);
225
226 auto fds = control_.releaseFds();
227 EXPECT_GE(fds.size(), size_t(3));
228 EXPECT_GE(fds[0].get(), 0);
229 EXPECT_GE(fds[1].get(), 0);
230 EXPECT_GE(fds[2].get(), 0);
231 ASSERT_TRUE(control_);
232 EXPECT_LT(IncFs_GetControlFd(control_, CMD), 0);
233 EXPECT_LT(IncFs_GetControlFd(control_, PENDING_READS), 0);
234 EXPECT_LT(IncFs_GetControlFd(control_, LOGS), 0);
235
236 control_.close();
237 EXPECT_FALSE(control_);
238
239 auto control = IncFs_CreateControl(fds[0].release(), fds[1].release(), fds[2].release());
240 ASSERT_TRUE(control);
241 EXPECT_GE(IncFs_GetControlFd(control, CMD), 0);
242 EXPECT_GE(IncFs_GetControlFd(control, PENDING_READS), 0);
243 EXPECT_GE(IncFs_GetControlFd(control, LOGS), 0);
244 IncFsFd rawFds[3];
245 EXPECT_EQ(-EINVAL, IncFs_ReleaseControlFds(nullptr, rawFds, 3));
246 EXPECT_EQ(-EINVAL, IncFs_ReleaseControlFds(control, nullptr, 3));
247 EXPECT_EQ(-ERANGE, IncFs_ReleaseControlFds(control, rawFds, 2));
248 EXPECT_EQ(3, IncFs_ReleaseControlFds(control, rawFds, 3));
249 EXPECT_GE(rawFds[0], 0);
250 EXPECT_GE(rawFds[1], 0);
251 EXPECT_GE(rawFds[2], 0);
252 ::close(rawFds[0]);
253 ::close(rawFds[1]);
254 ::close(rawFds[2]);
255 IncFs_DeleteControl(control);
256 }
257
TEST_F(IncFsTest,MakeDir)258 TEST_F(IncFsTest, MakeDir) {
259 const auto dir_path = mountPath(test_dir_name_);
260 ASSERT_FALSE(exists(dir_path));
261 ASSERT_EQ(makeDir(control_, dir_path), 0);
262 ASSERT_TRUE(exists(dir_path));
263 }
264
TEST_F(IncFsTest,MakeDirs)265 TEST_F(IncFsTest, MakeDirs) {
266 const auto dir_path = mountPath(test_dir_name_);
267 ASSERT_FALSE(exists(dir_path));
268 ASSERT_EQ(makeDirs(control_, dir_path), 0);
269 ASSERT_TRUE(exists(dir_path));
270 ASSERT_EQ(makeDirs(control_, dir_path), 0);
271 auto nested = dir_path + "/couple/more/nested/levels";
272 ASSERT_EQ(makeDirs(control_, nested), 0);
273 ASSERT_TRUE(exists(nested));
274 ASSERT_NE(makeDirs(control_, "/"), 0);
275 }
276
TEST_F(IncFsTest,BindMount)277 TEST_F(IncFsTest, BindMount) {
278 {
279 TemporaryDir tmp_dir_to_bind;
280 ASSERT_EQ(0, makeDir(control_, mountPath(test_dir_name_)));
281 ASSERT_EQ(0, bindMount(mountPath(test_dir_name_), tmp_dir_to_bind.path));
282 ScopedUnmount su(tmp_dir_to_bind.path);
283 const auto test_file = mountPath(test_dir_name_, test_file_name_);
284 ASSERT_FALSE(exists(test_file.c_str())) << "Present: " << test_file;
285 ASSERT_EQ(0,
286 makeFile(control_, test_file, 0555, fileId(1),
287 {.size = test_file_size_, .metadata = metadata("md")}));
288 ASSERT_TRUE(exists(test_file.c_str())) << "Missing: " << test_file;
289 const auto file_binded_path = path::join(tmp_dir_to_bind.path, test_file_name_);
290 ASSERT_TRUE(exists(file_binded_path.c_str())) << "Missing: " << file_binded_path;
291 }
292
293 {
294 // Don't allow binding the root
295 TemporaryDir tmp_dir_to_bind;
296 ASSERT_EQ(-EINVAL, bindMount(mount_dir_path_, tmp_dir_to_bind.path));
297 }
298 }
299
TEST_F(IncFsTest,Root)300 TEST_F(IncFsTest, Root) {
301 ASSERT_EQ(mount_dir_path_, root(control_)) << "Error: " << errno;
302 }
303
TEST_F(IncFsTest,RootInvalidControl)304 TEST_F(IncFsTest, RootInvalidControl) {
305 const TemporaryFile tmp_file;
306 auto control{createControl(tmp_file.fd, -1, -1)};
307 ASSERT_EQ("", root(control)) << "Error: " << errno;
308 }
309
TEST_F(IncFsTest,Open)310 TEST_F(IncFsTest, Open) {
311 Control control = open(mount_dir_path_);
312 ASSERT_TRUE(control.cmd() >= 0);
313 ASSERT_TRUE(control.pendingReads() >= 0);
314 ASSERT_TRUE(control.logs() >= 0);
315 }
316
TEST_F(IncFsTest,OpenFail)317 TEST_F(IncFsTest, OpenFail) {
318 TemporaryDir tmp_dir_to_bind;
319 Control control = open(tmp_dir_to_bind.path);
320 ASSERT_TRUE(control.cmd() < 0);
321 ASSERT_TRUE(control.pendingReads() < 0);
322 ASSERT_TRUE(control.logs() < 0);
323 }
324
TEST_F(IncFsTest,MakeFile)325 TEST_F(IncFsTest, MakeFile) {
326 ASSERT_EQ(0, makeDir(control_, mountPath(test_dir_name_)));
327 const auto file_path = mountPath(test_dir_name_, test_file_name_);
328 ASSERT_FALSE(exists(file_path));
329 ASSERT_EQ(0,
330 makeFile(control_, file_path, 0111, fileId(1),
331 {.size = test_file_size_, .metadata = metadata("md")}));
332 struct stat s;
333 ASSERT_EQ(0, stat(file_path.c_str(), &s));
334 ASSERT_EQ(test_file_size_, (int)s.st_size);
335 }
336
TEST_F(IncFsTest,MakeFile0)337 TEST_F(IncFsTest, MakeFile0) {
338 ASSERT_EQ(0, makeDir(control_, mountPath(test_dir_name_)));
339 const auto file_path = mountPath(test_dir_name_, ".info");
340 ASSERT_FALSE(exists(file_path));
341 ASSERT_EQ(0,
342 makeFile(control_, file_path, 0555, fileId(1),
343 {.size = 0, .metadata = metadata("mdsdfhjasdkfas l;jflaskdjf")}));
344 struct stat s;
345 ASSERT_EQ(0, stat(file_path.c_str(), &s));
346 ASSERT_EQ(0, (int)s.st_size);
347 }
348
TEST_F(IncFsTest,GetFileId)349 TEST_F(IncFsTest, GetFileId) {
350 auto id = fileId(1);
351 ASSERT_EQ(0,
352 makeFile(control_, mountPath(test_file_name_), 0555, id,
353 {.size = test_file_size_, .metadata = metadata("md")}));
354 EXPECT_EQ(id, getFileId(control_, mountPath(test_file_name_))) << "errno = " << errno;
355 EXPECT_EQ(kIncFsInvalidFileId, getFileId(control_, test_file_name_));
356 EXPECT_EQ(kIncFsInvalidFileId, getFileId(control_, "asdf"));
357 EXPECT_EQ(kIncFsInvalidFileId, getFileId({}, mountPath(test_file_name_)));
358 }
359
TEST_F(IncFsTest,GetMetaData)360 TEST_F(IncFsTest, GetMetaData) {
361 const std::string_view md = "abc"sv;
362 ASSERT_EQ(0,
363 makeFile(control_, mountPath(test_file_name_), 0555, fileId(1),
364 {.size = test_file_size_, .metadata = metadata(md)}));
365 {
366 const auto raw_metadata = getMetadata(control_, mountPath(test_file_name_));
367 ASSERT_NE(0u, raw_metadata.size()) << errno;
368 const std::string result(raw_metadata.begin(), raw_metadata.end());
369 ASSERT_EQ(md, result);
370 }
371 {
372 const auto raw_metadata = getMetadata(control_, fileId(1));
373 ASSERT_NE(0u, raw_metadata.size()) << errno;
374 const std::string result(raw_metadata.begin(), raw_metadata.end());
375 ASSERT_EQ(md, result);
376 }
377 }
378
TEST_F(IncFsTest,LinkAndUnlink)379 TEST_F(IncFsTest, LinkAndUnlink) {
380 ASSERT_EQ(0, makeFile(control_, mountPath(test_file_name_), 0555, fileId(1), {.size = 0}));
381 ASSERT_EQ(0, makeDir(control_, mountPath(test_dir_name_)));
382 const std::string_view test_file = "test1.txt"sv;
383 const auto linked_file_path = mountPath(test_dir_name_, test_file);
384 ASSERT_FALSE(exists(linked_file_path));
385 ASSERT_EQ(0, link(control_, mountPath(test_file_name_), linked_file_path));
386 ASSERT_TRUE(exists(linked_file_path));
387 ASSERT_EQ(0, unlink(control_, linked_file_path));
388 ASSERT_FALSE(exists(linked_file_path));
389 }
390
TEST_F(IncFsTest,WriteBlocksAndPageRead)391 TEST_F(IncFsTest, WriteBlocksAndPageRead) {
392 const auto id = fileId(1);
393 ASSERT_TRUE(control_.logs() >= 0);
394 ASSERT_EQ(0,
395 makeFile(control_, mountPath(test_file_name_), 0555, id, {.size = test_file_size_}));
396 auto fd = openForSpecialOps(control_, fileId(1));
397 ASSERT_GE(fd.get(), 0);
398
399 std::vector<char> data(INCFS_DATA_FILE_BLOCK_SIZE);
400 auto block = DataBlock{
401 .fileFd = fd.get(),
402 .pageIndex = 0,
403 .compression = INCFS_COMPRESSION_KIND_NONE,
404 .dataSize = (uint32_t)data.size(),
405 .data = data.data(),
406 };
407 ASSERT_EQ(1, writeBlocks({&block, 1}));
408
409 std::thread wait_page_read_thread([&]() {
410 std::vector<ReadInfo> reads;
411 ASSERT_EQ(WaitResult::HaveData,
412 waitForPageReads(control_, std::chrono::seconds(5), &reads));
413 ASSERT_FALSE(reads.empty());
414 ASSERT_EQ(0, memcmp(&id, &reads[0].id, sizeof(id)));
415 ASSERT_EQ(0, int(reads[0].block));
416 });
417
418 const auto file_path = mountPath(test_file_name_);
419 const android::base::unique_fd readFd(open(file_path.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY));
420 ASSERT_TRUE(readFd >= 0);
421 char buf[INCFS_DATA_FILE_BLOCK_SIZE];
422 ASSERT_TRUE(android::base::ReadFully(readFd, buf, sizeof(buf)));
423 wait_page_read_thread.join();
424 }
425
TEST_F(IncFsTest,WaitForPendingReads)426 TEST_F(IncFsTest, WaitForPendingReads) {
427 const auto id = fileId(1);
428 ASSERT_EQ(0,
429 makeFile(control_, mountPath(test_file_name_), 0555, id, {.size = test_file_size_}));
430
431 std::thread wait_pending_read_thread([&]() {
432 std::vector<ReadInfo> pending_reads;
433 ASSERT_EQ(WaitResult::HaveData,
434 waitForPendingReads(control_, std::chrono::seconds(10), &pending_reads));
435 ASSERT_GT(pending_reads.size(), 0u);
436 ASSERT_EQ(0, memcmp(&id, &pending_reads[0].id, sizeof(id)));
437 ASSERT_EQ(0, (int)pending_reads[0].block);
438
439 auto fd = openForSpecialOps(control_, fileId(1));
440 ASSERT_GE(fd.get(), 0);
441
442 std::vector<char> data(INCFS_DATA_FILE_BLOCK_SIZE);
443 auto block = DataBlock{
444 .fileFd = fd.get(),
445 .pageIndex = 0,
446 .compression = INCFS_COMPRESSION_KIND_NONE,
447 .dataSize = (uint32_t)data.size(),
448 .data = data.data(),
449 };
450 ASSERT_EQ(1, writeBlocks({&block, 1}));
451 });
452
453 const auto file_path = mountPath(test_file_name_);
454 const android::base::unique_fd fd(open(file_path.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY));
455 ASSERT_GE(fd.get(), 0);
456 char buf[INCFS_DATA_FILE_BLOCK_SIZE];
457 ASSERT_TRUE(android::base::ReadFully(fd, buf, sizeof(buf)));
458 wait_pending_read_thread.join();
459 }
460
TEST_F(IncFsTest,GetFilledRangesBad)461 TEST_F(IncFsTest, GetFilledRangesBad) {
462 EXPECT_EQ(-EBADF, IncFs_GetFilledRanges(-1, {}, nullptr));
463 EXPECT_EQ(-EINVAL, IncFs_GetFilledRanges(0, {}, nullptr));
464 EXPECT_EQ(-EINVAL, IncFs_GetFilledRangesStartingFrom(0, -1, {}, nullptr));
465
466 makeFileWithHash(1);
467 const android::base::unique_fd readFd(
468 open(mountPath(test_file_name_).c_str(), O_RDONLY | O_CLOEXEC | O_BINARY));
469 ASSERT_GE(readFd.get(), 0);
470
471 char buffer[1024];
472 IncFsFilledRanges res;
473 EXPECT_EQ(-EPERM, IncFs_GetFilledRanges(readFd.get(), {buffer, std::size(buffer)}, &res));
474 }
475
TEST_F(IncFsTest,GetFilledRanges)476 TEST_F(IncFsTest, GetFilledRanges) {
477 ASSERT_EQ(0,
478 makeFile(control_, mountPath(test_file_name_), 0555, fileId(1),
479 {.size = 4 * INCFS_DATA_FILE_BLOCK_SIZE}));
480 char buffer[1024];
481 const auto bufferSpan = IncFsSpan{.data = buffer, .size = std::size(buffer)};
482
483 auto fd = openForSpecialOps(control_, fileId(1));
484 ASSERT_GE(fd.get(), 0);
485
486 IncFsFilledRanges filledRanges;
487 EXPECT_EQ(0, IncFs_GetFilledRanges(fd.get(), IncFsSpan{}, &filledRanges));
488 EXPECT_EQ(0, filledRanges.dataRangesCount);
489 EXPECT_EQ(0, filledRanges.hashRangesCount);
490
491 EXPECT_EQ(0, IncFs_GetFilledRanges(fd.get(), bufferSpan, &filledRanges));
492 EXPECT_EQ(0, filledRanges.dataRangesCount);
493 EXPECT_EQ(0, filledRanges.hashRangesCount);
494
495 EXPECT_EQ(0, IncFs_GetFilledRangesStartingFrom(fd.get(), 0, bufferSpan, &filledRanges));
496 EXPECT_EQ(0, filledRanges.dataRangesCount);
497 EXPECT_EQ(0, filledRanges.hashRangesCount);
498
499 EXPECT_EQ(0, IncFs_GetFilledRangesStartingFrom(fd.get(), 1, bufferSpan, &filledRanges));
500 EXPECT_EQ(0, filledRanges.dataRangesCount);
501 EXPECT_EQ(0, filledRanges.hashRangesCount);
502
503 EXPECT_EQ(0, IncFs_GetFilledRangesStartingFrom(fd.get(), 30, bufferSpan, &filledRanges));
504 EXPECT_EQ(0, filledRanges.dataRangesCount);
505 EXPECT_EQ(0, filledRanges.hashRangesCount);
506
507 EXPECT_EQ(-ENODATA, IncFs_IsFullyLoaded(fd.get()));
508
509 // write one block
510 std::vector<char> data(INCFS_DATA_FILE_BLOCK_SIZE);
511 auto block = DataBlock{
512 .fileFd = fd.get(),
513 .pageIndex = 0,
514 .compression = INCFS_COMPRESSION_KIND_NONE,
515 .dataSize = (uint32_t)data.size(),
516 .data = data.data(),
517 };
518 ASSERT_EQ(1, writeBlocks({&block, 1}));
519
520 EXPECT_EQ(0, IncFs_GetFilledRanges(fd.get(), bufferSpan, &filledRanges));
521 ASSERT_EQ(1, filledRanges.dataRangesCount);
522 EXPECT_EQ(0, filledRanges.dataRanges[0].begin);
523 EXPECT_EQ(1, filledRanges.dataRanges[0].end);
524 EXPECT_EQ(0, filledRanges.hashRangesCount);
525
526 EXPECT_EQ(0, IncFs_GetFilledRangesStartingFrom(fd.get(), 0, bufferSpan, &filledRanges));
527 ASSERT_EQ(1, filledRanges.dataRangesCount);
528 EXPECT_EQ(0, filledRanges.dataRanges[0].begin);
529 EXPECT_EQ(1, filledRanges.dataRanges[0].end);
530 EXPECT_EQ(0, filledRanges.hashRangesCount);
531
532 EXPECT_EQ(0, IncFs_GetFilledRangesStartingFrom(fd.get(), 1, bufferSpan, &filledRanges));
533 EXPECT_EQ(0, filledRanges.dataRangesCount);
534 EXPECT_EQ(0, filledRanges.hashRangesCount);
535
536 EXPECT_EQ(0, IncFs_GetFilledRangesStartingFrom(fd.get(), 30, bufferSpan, &filledRanges));
537 EXPECT_EQ(0, filledRanges.dataRangesCount);
538 EXPECT_EQ(0, filledRanges.hashRangesCount);
539
540 EXPECT_EQ(-ENODATA, IncFs_IsFullyLoaded(fd.get()));
541
542 // append one more block next to the first one
543 block.pageIndex = 1;
544 ASSERT_EQ(1, writeBlocks({&block, 1}));
545
546 EXPECT_EQ(0, IncFs_GetFilledRanges(fd.get(), bufferSpan, &filledRanges));
547 ASSERT_EQ(1, filledRanges.dataRangesCount);
548 EXPECT_EQ(0, filledRanges.dataRanges[0].begin);
549 EXPECT_EQ(2, filledRanges.dataRanges[0].end);
550 EXPECT_EQ(0, filledRanges.hashRangesCount);
551
552 EXPECT_EQ(0, IncFs_GetFilledRangesStartingFrom(fd.get(), 0, bufferSpan, &filledRanges));
553 ASSERT_EQ(1, filledRanges.dataRangesCount);
554 EXPECT_EQ(0, filledRanges.dataRanges[0].begin);
555 EXPECT_EQ(2, filledRanges.dataRanges[0].end);
556 EXPECT_EQ(0, filledRanges.hashRangesCount);
557
558 EXPECT_EQ(0, IncFs_GetFilledRangesStartingFrom(fd.get(), 1, bufferSpan, &filledRanges));
559 ASSERT_EQ(1, filledRanges.dataRangesCount);
560 EXPECT_EQ(1, filledRanges.dataRanges[0].begin);
561 EXPECT_EQ(2, filledRanges.dataRanges[0].end);
562 EXPECT_EQ(0, filledRanges.hashRangesCount);
563
564 EXPECT_EQ(0, IncFs_GetFilledRangesStartingFrom(fd.get(), 30, bufferSpan, &filledRanges));
565 EXPECT_EQ(0, filledRanges.dataRangesCount);
566 EXPECT_EQ(0, filledRanges.hashRangesCount);
567
568 EXPECT_EQ(-ENODATA, IncFs_IsFullyLoaded(fd.get()));
569
570 // now create a gap between filled blocks
571 block.pageIndex = 3;
572 ASSERT_EQ(1, writeBlocks({&block, 1}));
573
574 EXPECT_EQ(0, IncFs_GetFilledRanges(fd.get(), bufferSpan, &filledRanges));
575 ASSERT_EQ(2, filledRanges.dataRangesCount);
576 EXPECT_EQ(0, filledRanges.dataRanges[0].begin);
577 EXPECT_EQ(2, filledRanges.dataRanges[0].end);
578 EXPECT_EQ(3, filledRanges.dataRanges[1].begin);
579 EXPECT_EQ(4, filledRanges.dataRanges[1].end);
580 EXPECT_EQ(0, filledRanges.hashRangesCount);
581
582 EXPECT_EQ(0, IncFs_GetFilledRangesStartingFrom(fd.get(), 0, bufferSpan, &filledRanges));
583 ASSERT_EQ(2, filledRanges.dataRangesCount);
584 EXPECT_EQ(0, filledRanges.dataRanges[0].begin);
585 EXPECT_EQ(2, filledRanges.dataRanges[0].end);
586 EXPECT_EQ(3, filledRanges.dataRanges[1].begin);
587 EXPECT_EQ(4, filledRanges.dataRanges[1].end);
588 EXPECT_EQ(0, filledRanges.hashRangesCount);
589
590 EXPECT_EQ(0, IncFs_GetFilledRangesStartingFrom(fd.get(), 1, bufferSpan, &filledRanges));
591 ASSERT_EQ(2, filledRanges.dataRangesCount);
592 EXPECT_EQ(1, filledRanges.dataRanges[0].begin);
593 EXPECT_EQ(2, filledRanges.dataRanges[0].end);
594 EXPECT_EQ(3, filledRanges.dataRanges[1].begin);
595 EXPECT_EQ(4, filledRanges.dataRanges[1].end);
596 EXPECT_EQ(0, filledRanges.hashRangesCount);
597
598 EXPECT_EQ(0, IncFs_GetFilledRangesStartingFrom(fd.get(), 2, bufferSpan, &filledRanges));
599 ASSERT_EQ(1, filledRanges.dataRangesCount);
600 EXPECT_EQ(3, filledRanges.dataRanges[0].begin);
601 EXPECT_EQ(4, filledRanges.dataRanges[0].end);
602 EXPECT_EQ(0, filledRanges.hashRangesCount);
603
604 EXPECT_EQ(0, IncFs_GetFilledRangesStartingFrom(fd.get(), 30, bufferSpan, &filledRanges));
605 EXPECT_EQ(0, filledRanges.dataRangesCount);
606 EXPECT_EQ(0, filledRanges.hashRangesCount);
607
608 EXPECT_EQ(-ENODATA, IncFs_IsFullyLoaded(fd.get()));
609
610 // at last fill the whole file and make sure we report it as having a single range
611 block.pageIndex = 2;
612 ASSERT_EQ(1, writeBlocks({&block, 1}));
613
614 EXPECT_EQ(0, IncFs_GetFilledRanges(fd.get(), bufferSpan, &filledRanges));
615 ASSERT_EQ(1, filledRanges.dataRangesCount);
616 EXPECT_EQ(0, filledRanges.dataRanges[0].begin);
617 EXPECT_EQ(4, filledRanges.dataRanges[0].end);
618 EXPECT_EQ(0, filledRanges.hashRangesCount);
619
620 EXPECT_EQ(0, IncFs_GetFilledRangesStartingFrom(fd.get(), 0, bufferSpan, &filledRanges));
621 ASSERT_EQ(1, filledRanges.dataRangesCount);
622 EXPECT_EQ(0, filledRanges.dataRanges[0].begin);
623 EXPECT_EQ(4, filledRanges.dataRanges[0].end);
624 EXPECT_EQ(0, filledRanges.hashRangesCount);
625
626 EXPECT_EQ(0, IncFs_GetFilledRangesStartingFrom(fd.get(), 1, bufferSpan, &filledRanges));
627 ASSERT_EQ(1, filledRanges.dataRangesCount);
628 EXPECT_EQ(1, filledRanges.dataRanges[0].begin);
629 EXPECT_EQ(4, filledRanges.dataRanges[0].end);
630
631 EXPECT_EQ(0, IncFs_GetFilledRangesStartingFrom(fd.get(), 30, bufferSpan, &filledRanges));
632 EXPECT_EQ(0, filledRanges.dataRangesCount);
633 EXPECT_EQ(0, filledRanges.hashRangesCount);
634
635 EXPECT_EQ(0, IncFs_IsFullyLoaded(fd.get()));
636 }
637
TEST_F(IncFsTest,GetFilledRangesSmallBuffer)638 TEST_F(IncFsTest, GetFilledRangesSmallBuffer) {
639 ASSERT_EQ(0,
640 makeFile(control_, mountPath(test_file_name_), 0555, fileId(1),
641 {.size = 5 * INCFS_DATA_FILE_BLOCK_SIZE}));
642 char buffer[1024];
643
644 auto fd = openForSpecialOps(control_, fileId(1));
645 ASSERT_GE(fd.get(), 0);
646
647 std::vector<char> data(INCFS_DATA_FILE_BLOCK_SIZE);
648 DataBlock blocks[] = {DataBlock{
649 .fileFd = fd.get(),
650 .pageIndex = 0,
651 .compression = INCFS_COMPRESSION_KIND_NONE,
652 .dataSize = (uint32_t)data.size(),
653 .data = data.data(),
654 },
655 DataBlock{
656 .fileFd = fd.get(),
657 .pageIndex = 2,
658 .compression = INCFS_COMPRESSION_KIND_NONE,
659 .dataSize = (uint32_t)data.size(),
660 .data = data.data(),
661 },
662 DataBlock{
663 .fileFd = fd.get(),
664 .pageIndex = 4,
665 .compression = INCFS_COMPRESSION_KIND_NONE,
666 .dataSize = (uint32_t)data.size(),
667 .data = data.data(),
668 }};
669 ASSERT_EQ(3, writeBlocks({blocks, 3}));
670
671 IncFsSpan bufferSpan = {.data = buffer, .size = sizeof(IncFsBlockRange)};
672 IncFsFilledRanges filledRanges;
673 EXPECT_EQ(-ERANGE, IncFs_GetFilledRanges(fd.get(), bufferSpan, &filledRanges));
674 ASSERT_EQ(1, filledRanges.dataRangesCount);
675 EXPECT_EQ(0, filledRanges.dataRanges[0].begin);
676 EXPECT_EQ(1, filledRanges.dataRanges[0].end);
677 EXPECT_EQ(0, filledRanges.hashRangesCount);
678 EXPECT_EQ(2, filledRanges.endIndex);
679
680 EXPECT_EQ(-ERANGE,
681 IncFs_GetFilledRangesStartingFrom(fd.get(), filledRanges.endIndex, bufferSpan,
682 &filledRanges));
683 ASSERT_EQ(1, filledRanges.dataRangesCount);
684 EXPECT_EQ(2, filledRanges.dataRanges[0].begin);
685 EXPECT_EQ(3, filledRanges.dataRanges[0].end);
686 EXPECT_EQ(0, filledRanges.hashRangesCount);
687 EXPECT_EQ(4, filledRanges.endIndex);
688
689 EXPECT_EQ(0,
690 IncFs_GetFilledRangesStartingFrom(fd.get(), filledRanges.endIndex, bufferSpan,
691 &filledRanges));
692 ASSERT_EQ(1, filledRanges.dataRangesCount);
693 EXPECT_EQ(4, filledRanges.dataRanges[0].begin);
694 EXPECT_EQ(5, filledRanges.dataRanges[0].end);
695 EXPECT_EQ(0, filledRanges.hashRangesCount);
696 EXPECT_EQ(5, filledRanges.endIndex);
697 }
698
TEST_F(IncFsTest,GetFilledRangesWithHashes)699 TEST_F(IncFsTest, GetFilledRangesWithHashes) {
700 auto size = makeFileWithHash(1);
701 ASSERT_GT(size, 0);
702 ASSERT_NO_FATAL_FAILURE(writeTestRanges(1, size));
703
704 auto fd = openForSpecialOps(control_, fileId(1));
705 ASSERT_GE(fd.get(), 0);
706
707 char buffer[1024];
708 IncFsSpan bufferSpan = {.data = buffer, .size = sizeof(buffer)};
709 IncFsFilledRanges filledRanges;
710 EXPECT_EQ(0, IncFs_GetFilledRanges(fd.get(), bufferSpan, &filledRanges));
711 ASSERT_EQ(3, filledRanges.dataRangesCount);
712 auto lastPage = sizeToPages(size) - 1;
713 EXPECT_EQ(lastPage, filledRanges.dataRanges[2].begin);
714 EXPECT_EQ(lastPage + 1, filledRanges.dataRanges[2].end);
715 EXPECT_EQ(2, filledRanges.hashRangesCount);
716 EXPECT_EQ(0, filledRanges.hashRanges[0].begin);
717 EXPECT_EQ(1, filledRanges.hashRanges[0].end);
718 EXPECT_EQ(2, filledRanges.hashRanges[1].begin);
719 EXPECT_EQ(3, filledRanges.hashRanges[1].end);
720 EXPECT_EQ(sizeToPages(size) + 3, filledRanges.endIndex);
721 }
722
TEST_F(IncFsTest,GetFilledRangesCpp)723 TEST_F(IncFsTest, GetFilledRangesCpp) {
724 auto size = makeFileWithHash(1);
725 ASSERT_GT(size, 0);
726 ASSERT_NO_FATAL_FAILURE(writeTestRanges(1, size));
727
728 auto fd = openForSpecialOps(control_, fileId(1));
729 ASSERT_GE(fd.get(), 0);
730
731 // simply get all ranges
732 auto [res, ranges] = getFilledRanges(fd.get());
733 EXPECT_EQ(res, 0);
734 EXPECT_EQ(size_t(5), ranges.totalSize());
735 ASSERT_EQ(size_t(3), ranges.dataRanges().size());
736 auto lastPage = sizeToPages(size) - 1;
737 EXPECT_EQ(lastPage, ranges.dataRanges()[2].begin);
738 EXPECT_EQ(size_t(1), ranges.dataRanges()[2].size());
739 ASSERT_EQ(size_t(2), ranges.hashRanges().size());
740 EXPECT_EQ(0, ranges.hashRanges()[0].begin);
741 EXPECT_EQ(size_t(1), ranges.hashRanges()[0].size());
742 EXPECT_EQ(2, ranges.hashRanges()[1].begin);
743 EXPECT_EQ(size_t(1), ranges.hashRanges()[1].size());
744
745 // now check how buffer size limiting works.
746 FilledRanges::RangeBuffer buf(ranges.totalSize() - 1);
747 auto [res2, ranges2] = getFilledRanges(fd.get(), std::move(buf));
748 ASSERT_EQ(-ERANGE, res2);
749 EXPECT_EQ(ranges.totalSize() - 1, ranges2.totalSize());
750 ASSERT_EQ(size_t(3), ranges2.dataRanges().size());
751 ASSERT_EQ(size_t(1), ranges2.hashRanges().size());
752 EXPECT_EQ(0, ranges2.hashRanges()[0].begin);
753 EXPECT_EQ(size_t(1), ranges2.hashRanges()[0].size());
754
755 // and now check the resumption from the previous result
756 auto [res3, ranges3] = getFilledRanges(fd.get(), std::move(ranges2));
757 ASSERT_EQ(0, res3);
758 EXPECT_EQ(ranges.totalSize(), ranges3.totalSize());
759 ASSERT_EQ(size_t(3), ranges3.dataRanges().size());
760 ASSERT_EQ(size_t(2), ranges3.hashRanges().size());
761 EXPECT_EQ(0, ranges3.hashRanges()[0].begin);
762 EXPECT_EQ(size_t(1), ranges3.hashRanges()[0].size());
763 EXPECT_EQ(2, ranges3.hashRanges()[1].begin);
764 EXPECT_EQ(size_t(1), ranges3.hashRanges()[1].size());
765
766 EXPECT_EQ(LoadingState::MissingBlocks, isFullyLoaded(fd.get()));
767
768 {
769 std::vector<char> data(INCFS_DATA_FILE_BLOCK_SIZE);
770 DataBlock block = {.fileFd = fd.get(),
771 .pageIndex = 1,
772 .compression = INCFS_COMPRESSION_KIND_NONE,
773 .dataSize = (uint32_t)data.size(),
774 .data = data.data()};
775 for (auto i = 0; i != sizeToPages(size); ++i) {
776 block.pageIndex = i;
777 ASSERT_EQ(1, writeBlocks({&block, 1}));
778 }
779 block.kind = INCFS_BLOCK_KIND_HASH;
780 for (auto i = 0; i != 3; ++i) {
781 block.pageIndex = i;
782 ASSERT_EQ(1, writeBlocks({&block, 1}));
783 }
784 }
785 EXPECT_EQ(LoadingState::Full, isFullyLoaded(fd.get()));
786 }
787