• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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