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 <android-base/file.h>
18 #include <android-base/stringprintf.h>
19 #include <sys/select.h>
20 
21 #include <unistd.h>
22 
23 #include <optional>
24 #include <thread>
25 
26 #include "IncFsTestBase.h"
27 
28 using namespace android::incfs;
29 using namespace std::literals;
30 namespace ab = android::base;
31 
32 struct ScopedUnmount {
33     std::string path_;
ScopedUnmountScopedUnmount34     explicit ScopedUnmount(std::string&& path) : path_(std::move(path)) {}
~ScopedUnmountScopedUnmount35     ~ScopedUnmount() { unmount(path_); }
36 };
37 
38 class IncFsTest : public IncFsTestBase {
39 protected:
getReadTimeout()40     virtual int32_t getReadTimeout() {
41         return std::chrono::duration_cast<std::chrono::milliseconds>(kDefaultReadTimeout).count();
42     }
43 
metadata(std::string_view sv)44     static IncFsSpan metadata(std::string_view sv) {
45         return {.data = sv.data(), .size = IncFsSize(sv.size())};
46     }
47 
sizeToPages(int size)48     static int sizeToPages(int size) {
49         return (size + INCFS_DATA_FILE_BLOCK_SIZE - 1) / INCFS_DATA_FILE_BLOCK_SIZE;
50     }
51 
writeTestRanges(int id,int size)52     void writeTestRanges(int id, int size) {
53         auto wfd = openForSpecialOps(control_, fileId(id));
54         ASSERT_GE(wfd.get(), 0);
55 
56         auto lastPage = sizeToPages(size) - 1;
57 
58         std::vector<char> data(INCFS_DATA_FILE_BLOCK_SIZE);
59         DataBlock blocks[] = {{
60                                       .fileFd = wfd.get(),
61                                       .pageIndex = 1,
62                                       .compression = INCFS_COMPRESSION_KIND_NONE,
63                                       .dataSize = (uint32_t)data.size(),
64                                       .data = data.data(),
65                               },
66                               {
67                                       .fileFd = wfd.get(),
68                                       .pageIndex = 2,
69                                       .compression = INCFS_COMPRESSION_KIND_NONE,
70                                       .dataSize = (uint32_t)data.size(),
71                                       .data = data.data(),
72                               },
73                               {
74                                       .fileFd = wfd.get(),
75                                       .pageIndex = 10,
76                                       .compression = INCFS_COMPRESSION_KIND_NONE,
77                                       .dataSize = (uint32_t)data.size(),
78                                       .data = data.data(),
79                               },
80                               {
81                                       .fileFd = wfd.get(),
82                                       // last data page
83                                       .pageIndex = lastPage,
84                                       .compression = INCFS_COMPRESSION_KIND_NONE,
85                                       .dataSize = (uint32_t)data.size(),
86                                       .data = data.data(),
87                               },
88                               {
89                                       .fileFd = wfd.get(),
90                                       // first hash page
91                                       .pageIndex = 0,
92                                       .compression = INCFS_COMPRESSION_KIND_NONE,
93                                       .dataSize = (uint32_t)data.size(),
94                                       .kind = INCFS_BLOCK_KIND_HASH,
95                                       .data = data.data(),
96                               },
97                               {
98                                       .fileFd = wfd.get(),
99                                       .pageIndex = 2,
100                                       .compression = INCFS_COMPRESSION_KIND_NONE,
101                                       .dataSize = (uint32_t)data.size(),
102                                       .kind = INCFS_BLOCK_KIND_HASH,
103                                       .data = data.data(),
104                               }};
105         ASSERT_EQ((int)std::size(blocks), writeBlocks({blocks, std::size(blocks)}));
106     }
107 
writeBlock(int pageIndex)108     void writeBlock(int pageIndex) {
109         auto fd = openForSpecialOps(control_, fileId(1));
110         ASSERT_GE(fd.get(), 0);
111 
112         std::vector<char> data(INCFS_DATA_FILE_BLOCK_SIZE);
113         auto block = DataBlock{
114                 .fileFd = fd.get(),
115                 .pageIndex = pageIndex,
116                 .compression = INCFS_COMPRESSION_KIND_NONE,
117                 .dataSize = (uint32_t)data.size(),
118                 .data = data.data(),
119         };
120         ASSERT_EQ(1, writeBlocks({&block, 1}));
121     }
122 
123     template <class ReadStruct>
testWriteBlockAndPageRead()124     void testWriteBlockAndPageRead() {
125         const auto id = fileId(1);
126         ASSERT_TRUE(control_.logs() >= 0);
127         ASSERT_EQ(0,
128                   makeFile(control_, mountPath(test_file_name_), 0555, id,
129                            {.size = test_file_size_}));
130         writeBlock(/*pageIndex=*/0);
131 
132         std::thread wait_page_read_thread([&]() {
133             std::vector<ReadStruct> reads;
134             auto res = waitForPageReads(control_, std::chrono::seconds(5), &reads);
135             ASSERT_EQ(WaitResult::HaveData, res) << (int)res;
136             ASSERT_FALSE(reads.empty());
137             EXPECT_EQ(0, memcmp(&id, &reads[0].id, sizeof(id)));
138             EXPECT_EQ(0, int(reads[0].block));
139             if constexpr (std::is_same_v<ReadStruct, ReadInfoWithUid>) {
140                 if (features() & Features::v2) {
141                     EXPECT_NE(kIncFsNoUid, int(reads[0].uid));
142                 } else {
143                     EXPECT_EQ(kIncFsNoUid, int(reads[0].uid));
144                 }
145             }
146         });
147 
148         const auto file_path = mountPath(test_file_name_);
149         const android::base::unique_fd readFd(
150                 open(file_path.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY));
151         ASSERT_TRUE(readFd >= 0);
152         char buf[INCFS_DATA_FILE_BLOCK_SIZE];
153         ASSERT_TRUE(android::base::ReadFully(readFd, buf, sizeof(buf)));
154         wait_page_read_thread.join();
155     }
156 
157     template <class PendingRead>
testWaitForPendingReads()158     void testWaitForPendingReads() {
159         const auto id = fileId(1);
160         ASSERT_EQ(0,
161                   makeFile(control_, mountPath(test_file_name_), 0555, id,
162                            {.size = test_file_size_}));
163 
164         std::thread wait_pending_read_thread([&]() {
165             std::vector<PendingRead> pending_reads;
166             ASSERT_EQ(WaitResult::HaveData,
167                       waitForPendingReads(control_, std::chrono::seconds(10), &pending_reads));
168             ASSERT_GT(pending_reads.size(), 0u);
169             EXPECT_EQ(0, memcmp(&id, &pending_reads[0].id, sizeof(id)));
170             EXPECT_EQ(0, (int)pending_reads[0].block);
171             if constexpr (std::is_same_v<PendingRead, ReadInfoWithUid>) {
172                 if (features() & Features::v2) {
173                     EXPECT_NE(kIncFsNoUid, int(pending_reads[0].uid));
174                 } else {
175                     EXPECT_EQ(kIncFsNoUid, int(pending_reads[0].uid));
176                 }
177             }
178 
179             auto fd = openForSpecialOps(control_, fileId(1));
180             ASSERT_GE(fd.get(), 0);
181 
182             std::vector<char> data(INCFS_DATA_FILE_BLOCK_SIZE);
183             auto block = DataBlock{
184                     .fileFd = fd.get(),
185                     .pageIndex = 0,
186                     .compression = INCFS_COMPRESSION_KIND_NONE,
187                     .dataSize = (uint32_t)data.size(),
188                     .data = data.data(),
189             };
190             ASSERT_EQ(1, writeBlocks({&block, 1}));
191         });
192 
193         const auto file_path = mountPath(test_file_name_);
194         const android::base::unique_fd fd(open(file_path.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY));
195         ASSERT_GE(fd.get(), 0);
196         char buf[INCFS_DATA_FILE_BLOCK_SIZE];
197         ASSERT_TRUE(android::base::ReadFully(fd, buf, sizeof(buf)));
198         wait_pending_read_thread.join();
199     }
200 
201     inline static const int test_file_size_ = INCFS_DATA_FILE_BLOCK_SIZE;
202 };
203 
TEST_F(IncFsTest,GetIncfsFeatures)204 TEST_F(IncFsTest, GetIncfsFeatures) {
205     ASSERT_NE(features(), none);
206 }
207 
TEST_F(IncFsTest,FalseIncfsPath)208 TEST_F(IncFsTest, FalseIncfsPath) {
209     TemporaryDir test_dir;
210     ASSERT_FALSE(isIncFsPath(test_dir.path));
211 }
212 
TEST_F(IncFsTest,TrueIncfsPath)213 TEST_F(IncFsTest, TrueIncfsPath) {
214     ASSERT_TRUE(isIncFsPath(mount_dir_path_));
215 }
216 
TEST_F(IncFsTest,TrueIncfsPathForBindMount)217 TEST_F(IncFsTest, TrueIncfsPathForBindMount) {
218     TemporaryDir tmp_dir_to_bind;
219     ASSERT_EQ(0, makeDir(control_, mountPath(test_dir_name_)));
220     ASSERT_EQ(0, bindMount(mountPath(test_dir_name_), tmp_dir_to_bind.path));
221     ScopedUnmount su(tmp_dir_to_bind.path);
222     ASSERT_TRUE(isIncFsPath(tmp_dir_to_bind.path));
223 }
224 
TEST_F(IncFsTest,FalseIncfsPathFile)225 TEST_F(IncFsTest, FalseIncfsPathFile) {
226     TemporaryFile test_file;
227     ASSERT_FALSE(isIncFsFd(test_file.fd));
228     ASSERT_FALSE(isIncFsPath(test_file.path));
229 }
230 
TEST_F(IncFsTest,TrueIncfsPathForBindMountFile)231 TEST_F(IncFsTest, TrueIncfsPathForBindMountFile) {
232     ASSERT_EQ(0,
233               makeFile(control_, mountPath(test_file_name_), 0555, fileId(1),
234                        {.size = test_file_size_}));
235     const auto file_path = mountPath(test_file_name_);
236     const android::base::unique_fd fd(open(file_path.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY));
237     ASSERT_GE(fd.get(), 0);
238     ASSERT_TRUE(isIncFsFd(fd.get()));
239     ASSERT_TRUE(isIncFsPath(file_path));
240 }
241 
TEST_F(IncFsTest,Control)242 TEST_F(IncFsTest, Control) {
243     ASSERT_TRUE(control_);
244     EXPECT_GE(IncFs_GetControlFd(control_, CMD), 0);
245     EXPECT_GE(IncFs_GetControlFd(control_, PENDING_READS), 0);
246     EXPECT_GE(IncFs_GetControlFd(control_, LOGS), 0);
247     EXPECT_EQ((features() & Features::v2) != 0, IncFs_GetControlFd(control_, BLOCKS_WRITTEN) >= 0);
248 
249     auto fds = control_.releaseFds();
250     EXPECT_GE(fds.size(), size_t(4));
251     EXPECT_GE(fds[0].get(), 0);
252     EXPECT_GE(fds[1].get(), 0);
253     EXPECT_GE(fds[2].get(), 0);
254     ASSERT_TRUE(control_);
255     EXPECT_LT(IncFs_GetControlFd(control_, CMD), 0);
256     EXPECT_LT(IncFs_GetControlFd(control_, PENDING_READS), 0);
257     EXPECT_LT(IncFs_GetControlFd(control_, LOGS), 0);
258     EXPECT_LT(IncFs_GetControlFd(control_, BLOCKS_WRITTEN), 0);
259 
260     control_.close();
261     EXPECT_FALSE(control_);
262 
263     auto control = IncFs_CreateControl(fds[0].release(), fds[1].release(), fds[2].release(), -1);
264     ASSERT_TRUE(control);
265     EXPECT_GE(IncFs_GetControlFd(control, CMD), 0);
266     EXPECT_GE(IncFs_GetControlFd(control, PENDING_READS), 0);
267     EXPECT_GE(IncFs_GetControlFd(control, LOGS), 0);
268     IncFsFd rawFds[4];
269     EXPECT_EQ(-EINVAL, IncFs_ReleaseControlFds(nullptr, rawFds, 3));
270     EXPECT_EQ(-EINVAL, IncFs_ReleaseControlFds(control, nullptr, 3));
271     EXPECT_EQ(-ERANGE, IncFs_ReleaseControlFds(control, rawFds, 2));
272     EXPECT_EQ(4, IncFs_ReleaseControlFds(control, rawFds, 4));
273     EXPECT_GE(rawFds[0], 0);
274     EXPECT_GE(rawFds[1], 0);
275     EXPECT_GE(rawFds[2], 0);
276     ::close(rawFds[0]);
277     ::close(rawFds[1]);
278     ::close(rawFds[2]);
279     if (rawFds[3] >= 0) ::close(rawFds[3]);
280     IncFs_DeleteControl(control);
281 }
282 
TEST_F(IncFsTest,MakeDir)283 TEST_F(IncFsTest, MakeDir) {
284     const auto dir_path = mountPath(test_dir_name_);
285     ASSERT_FALSE(exists(dir_path));
286     ASSERT_EQ(makeDir(control_, dir_path), 0);
287     ASSERT_TRUE(exists(dir_path));
288 }
289 
TEST_F(IncFsTest,MakeDirs)290 TEST_F(IncFsTest, MakeDirs) {
291     const auto dir_path = mountPath(test_dir_name_);
292     ASSERT_FALSE(exists(dir_path));
293     ASSERT_EQ(makeDirs(control_, dir_path), 0);
294     ASSERT_TRUE(exists(dir_path));
295     ASSERT_EQ(makeDirs(control_, dir_path), 0);
296     auto nested = dir_path + "/couple/more/nested/levels";
297     ASSERT_EQ(makeDirs(control_, nested), 0);
298     ASSERT_TRUE(exists(nested));
299     ASSERT_NE(makeDirs(control_, "/"), 0);
300 }
301 
TEST_F(IncFsTest,BindMount)302 TEST_F(IncFsTest, BindMount) {
303     {
304         TemporaryDir tmp_dir_to_bind;
305         ASSERT_EQ(0, makeDir(control_, mountPath(test_dir_name_)));
306         ASSERT_EQ(0, bindMount(mountPath(test_dir_name_), tmp_dir_to_bind.path));
307         ScopedUnmount su(tmp_dir_to_bind.path);
308         const auto test_file = mountPath(test_dir_name_, test_file_name_);
309         ASSERT_FALSE(exists(test_file.c_str())) << "Present: " << test_file;
310         ASSERT_EQ(0,
311                   makeFile(control_, test_file, 0555, fileId(1),
312                            {.size = test_file_size_, .metadata = metadata("md")}));
313         ASSERT_TRUE(exists(test_file.c_str())) << "Missing: " << test_file;
314         const auto file_binded_path = path::join(tmp_dir_to_bind.path, test_file_name_);
315         ASSERT_TRUE(exists(file_binded_path.c_str())) << "Missing: " << file_binded_path;
316     }
317 
318     {
319         // Don't allow binding the root
320         TemporaryDir tmp_dir_to_bind;
321         ASSERT_EQ(-EINVAL, bindMount(mount_dir_path_, tmp_dir_to_bind.path));
322     }
323 }
324 
TEST_F(IncFsTest,Root)325 TEST_F(IncFsTest, Root) {
326     ASSERT_EQ(mount_dir_path_, root(control_)) << "Error: " << errno;
327 }
328 
TEST_F(IncFsTest,RootInvalidControl)329 TEST_F(IncFsTest, RootInvalidControl) {
330     const TemporaryFile tmp_file;
331     auto control{createControl(tmp_file.fd, -1, -1, -1)};
332     ASSERT_EQ("", root(control)) << "Error: " << errno;
333 }
334 
TEST_F(IncFsTest,Open)335 TEST_F(IncFsTest, Open) {
336     Control control = open(mount_dir_path_);
337     ASSERT_TRUE(control.cmd() >= 0);
338     ASSERT_TRUE(control.pendingReads() >= 0);
339     ASSERT_TRUE(control.logs() >= 0);
340 }
341 
TEST_F(IncFsTest,OpenFail)342 TEST_F(IncFsTest, OpenFail) {
343     TemporaryDir tmp_dir_to_bind;
344     Control control = open(tmp_dir_to_bind.path);
345     ASSERT_TRUE(control.cmd() < 0);
346     ASSERT_TRUE(control.pendingReads() < 0);
347     ASSERT_TRUE(control.logs() < 0);
348 }
349 
TEST_F(IncFsTest,MakeFile)350 TEST_F(IncFsTest, MakeFile) {
351     ASSERT_EQ(0, makeDir(control_, mountPath(test_dir_name_)));
352     const auto file_path = mountPath(test_dir_name_, test_file_name_);
353     ASSERT_FALSE(exists(file_path));
354     ASSERT_EQ(0,
355               makeFile(control_, file_path, 0111, fileId(1),
356                        {.size = test_file_size_, .metadata = metadata("md")}));
357     struct stat s;
358     ASSERT_EQ(0, stat(file_path.c_str(), &s));
359     ASSERT_EQ(test_file_size_, (int)s.st_size);
360 }
361 
TEST_F(IncFsTest,MakeFile0)362 TEST_F(IncFsTest, MakeFile0) {
363     ASSERT_EQ(0, makeDir(control_, mountPath(test_dir_name_)));
364     const auto file_path = mountPath(test_dir_name_, ".info");
365     ASSERT_FALSE(exists(file_path));
366     ASSERT_EQ(0,
367               makeFile(control_, file_path, 0555, fileId(1),
368                        {.size = 0, .metadata = metadata("mdsdfhjasdkfas l;jflaskdjf")}));
369     struct stat s;
370     ASSERT_EQ(0, stat(file_path.c_str(), &s));
371     ASSERT_EQ(0, (int)s.st_size);
372 }
373 
TEST_F(IncFsTest,MakeMappedFile)374 TEST_F(IncFsTest, MakeMappedFile) {
375     ASSERT_EQ(0, makeDir(control_, mountPath(test_dir_name_)));
376 
377     constexpr auto file_size = INCFS_DATA_FILE_BLOCK_SIZE * 2;
378     constexpr auto mapped_file_offset = file_size / 2;
379     constexpr auto mapped_file_size = file_size / 3;
380 
381     const auto file_path = mountPath(test_dir_name_, test_file_name_);
382     ASSERT_FALSE(exists(file_path));
383     ASSERT_EQ(0,
384               makeFile(control_, file_path, 0111, fileId(1),
385                        {.size = file_size, .metadata = metadata("md")}));
386     struct stat s = {};
387     ASSERT_EQ(0, stat(file_path.c_str(), &s));
388     ASSERT_EQ(file_size, (int)s.st_size);
389 
390     const auto mapped_file_path = mountPath(test_dir_name_, test_mapped_file_name_);
391     ASSERT_FALSE(exists(mapped_file_path));
392     ASSERT_EQ(0,
393               makeMappedFile(control_, mapped_file_path, 0111,
394                              {.sourceId = fileId(1),
395                               .sourceOffset = mapped_file_offset,
396                               .size = mapped_file_size}));
397     s = {};
398     ASSERT_EQ(0, stat(mapped_file_path.c_str(), &s));
399     ASSERT_EQ(mapped_file_size, (int)s.st_size);
400 
401     // Check fileId for the source file.
402     ASSERT_EQ(fileId(1), getFileId(control_, file_path));
403     // Check that there is no fileId for the mapped file.
404     ASSERT_EQ(kIncFsInvalidFileId, getFileId(control_, mapped_file_path));
405 }
406 
TEST_F(IncFsTest,GetFileId)407 TEST_F(IncFsTest, GetFileId) {
408     auto id = fileId(1);
409     ASSERT_EQ(0,
410               makeFile(control_, mountPath(test_file_name_), 0555, id,
411                        {.size = test_file_size_, .metadata = metadata("md")}));
412     EXPECT_EQ(id, getFileId(control_, mountPath(test_file_name_))) << "errno = " << errno;
413     EXPECT_EQ(kIncFsInvalidFileId, getFileId(control_, test_file_name_));
414     EXPECT_EQ(kIncFsInvalidFileId, getFileId(control_, "asdf"));
415     EXPECT_EQ(kIncFsInvalidFileId, getFileId({}, mountPath(test_file_name_)));
416 }
417 
TEST_F(IncFsTest,GetMetaData)418 TEST_F(IncFsTest, GetMetaData) {
419     const std::string_view md = "abc"sv;
420     ASSERT_EQ(0,
421               makeFile(control_, mountPath(test_file_name_), 0555, fileId(1),
422                        {.size = test_file_size_, .metadata = metadata(md)}));
423     {
424         const auto raw_metadata = getMetadata(control_, mountPath(test_file_name_));
425         ASSERT_NE(0u, raw_metadata.size()) << errno;
426         const std::string result(raw_metadata.begin(), raw_metadata.end());
427         ASSERT_EQ(md, result);
428     }
429     {
430         const auto raw_metadata = getMetadata(control_, fileId(1));
431         ASSERT_NE(0u, raw_metadata.size()) << errno;
432         const std::string result(raw_metadata.begin(), raw_metadata.end());
433         ASSERT_EQ(md, result);
434     }
435 }
436 
TEST_F(IncFsTest,LinkAndUnlink)437 TEST_F(IncFsTest, LinkAndUnlink) {
438     ASSERT_EQ(0, makeFile(control_, mountPath(test_file_name_), 0555, fileId(1), {.size = 0}));
439     ASSERT_EQ(0, makeDir(control_, mountPath(test_dir_name_)));
440     const std::string_view test_file = "test1.txt"sv;
441     const auto linked_file_path = mountPath(test_dir_name_, test_file);
442     ASSERT_FALSE(exists(linked_file_path));
443     ASSERT_EQ(0, link(control_, mountPath(test_file_name_), linked_file_path));
444     ASSERT_TRUE(exists(linked_file_path));
445     ASSERT_EQ(0, unlink(control_, linked_file_path));
446     ASSERT_FALSE(exists(linked_file_path));
447 }
448 
TEST_F(IncFsTest,WriteBlocksAndPageRead)449 TEST_F(IncFsTest, WriteBlocksAndPageRead) {
450     ASSERT_NO_FATAL_FAILURE(testWriteBlockAndPageRead<ReadInfo>());
451 }
452 
TEST_F(IncFsTest,WriteBlocksAndPageReadWithUid)453 TEST_F(IncFsTest, WriteBlocksAndPageReadWithUid) {
454     ASSERT_NO_FATAL_FAILURE(testWriteBlockAndPageRead<ReadInfoWithUid>());
455 }
456 
TEST_F(IncFsTest,WaitForPendingReads)457 TEST_F(IncFsTest, WaitForPendingReads) {
458     ASSERT_NO_FATAL_FAILURE(testWaitForPendingReads<ReadInfo>());
459 }
460 
TEST_F(IncFsTest,WaitForPendingReadsWithUid)461 TEST_F(IncFsTest, WaitForPendingReadsWithUid) {
462     ASSERT_NO_FATAL_FAILURE(testWaitForPendingReads<ReadInfoWithUid>());
463 }
464 
TEST_F(IncFsTest,GetFilledRangesBad)465 TEST_F(IncFsTest, GetFilledRangesBad) {
466     EXPECT_EQ(-EBADF, IncFs_GetFilledRanges(-1, {}, nullptr));
467     EXPECT_EQ(-EINVAL, IncFs_GetFilledRanges(0, {}, nullptr));
468     EXPECT_EQ(-EINVAL, IncFs_GetFilledRangesStartingFrom(0, -1, {}, nullptr));
469 
470     makeFileWithHash(1);
471     const android::base::unique_fd readFd(
472             open(mountPath(test_file_name_).c_str(), O_RDONLY | O_CLOEXEC | O_BINARY));
473     ASSERT_GE(readFd.get(), 0);
474 
475     char buffer[1024];
476     IncFsFilledRanges res;
477     EXPECT_EQ(-EPERM, IncFs_GetFilledRanges(readFd.get(), {buffer, std::size(buffer)}, &res));
478 }
479 
TEST_F(IncFsTest,GetFilledRanges)480 TEST_F(IncFsTest, GetFilledRanges) {
481     ASSERT_EQ(0,
482               makeFile(control_, mountPath(test_file_name_), 0555, fileId(1),
483                        {.size = 4 * INCFS_DATA_FILE_BLOCK_SIZE}));
484     char buffer[1024];
485     const auto bufferSpan = IncFsSpan{.data = buffer, .size = std::size(buffer)};
486 
487     auto fd = openForSpecialOps(control_, fileId(1));
488     ASSERT_GE(fd.get(), 0);
489 
490     IncFsFilledRanges filledRanges;
491     EXPECT_EQ(0, IncFs_GetFilledRanges(fd.get(), IncFsSpan{}, &filledRanges));
492     EXPECT_EQ(0, filledRanges.dataRangesCount);
493     EXPECT_EQ(0, filledRanges.hashRangesCount);
494 
495     EXPECT_EQ(0, IncFs_GetFilledRanges(fd.get(), bufferSpan, &filledRanges));
496     EXPECT_EQ(0, filledRanges.dataRangesCount);
497     EXPECT_EQ(0, filledRanges.hashRangesCount);
498 
499     EXPECT_EQ(0, IncFs_GetFilledRangesStartingFrom(fd.get(), 0, bufferSpan, &filledRanges));
500     EXPECT_EQ(0, filledRanges.dataRangesCount);
501     EXPECT_EQ(0, filledRanges.hashRangesCount);
502 
503     EXPECT_EQ(0, IncFs_GetFilledRangesStartingFrom(fd.get(), 1, bufferSpan, &filledRanges));
504     EXPECT_EQ(0, filledRanges.dataRangesCount);
505     EXPECT_EQ(0, filledRanges.hashRangesCount);
506 
507     EXPECT_EQ(0, IncFs_GetFilledRangesStartingFrom(fd.get(), 30, bufferSpan, &filledRanges));
508     EXPECT_EQ(0, filledRanges.dataRangesCount);
509     EXPECT_EQ(0, filledRanges.hashRangesCount);
510 
511     EXPECT_EQ(-ENODATA, IncFs_IsFullyLoaded(fd.get()));
512     EXPECT_EQ(-ENODATA, IncFs_IsEverythingFullyLoaded(control_));
513 
514     // write one block
515     std::vector<char> data(INCFS_DATA_FILE_BLOCK_SIZE);
516     auto block = DataBlock{
517             .fileFd = fd.get(),
518             .pageIndex = 0,
519             .compression = INCFS_COMPRESSION_KIND_NONE,
520             .dataSize = (uint32_t)data.size(),
521             .data = data.data(),
522     };
523     ASSERT_EQ(1, writeBlocks({&block, 1}));
524 
525     EXPECT_EQ(0, IncFs_GetFilledRanges(fd.get(), bufferSpan, &filledRanges));
526     ASSERT_EQ(1, filledRanges.dataRangesCount);
527     EXPECT_EQ(0, filledRanges.dataRanges[0].begin);
528     EXPECT_EQ(1, filledRanges.dataRanges[0].end);
529     EXPECT_EQ(0, filledRanges.hashRangesCount);
530 
531     EXPECT_EQ(0, IncFs_GetFilledRangesStartingFrom(fd.get(), 0, bufferSpan, &filledRanges));
532     ASSERT_EQ(1, filledRanges.dataRangesCount);
533     EXPECT_EQ(0, filledRanges.dataRanges[0].begin);
534     EXPECT_EQ(1, filledRanges.dataRanges[0].end);
535     EXPECT_EQ(0, filledRanges.hashRangesCount);
536 
537     EXPECT_EQ(0, IncFs_GetFilledRangesStartingFrom(fd.get(), 1, bufferSpan, &filledRanges));
538     EXPECT_EQ(0, filledRanges.dataRangesCount);
539     EXPECT_EQ(0, filledRanges.hashRangesCount);
540 
541     EXPECT_EQ(0, IncFs_GetFilledRangesStartingFrom(fd.get(), 30, bufferSpan, &filledRanges));
542     EXPECT_EQ(0, filledRanges.dataRangesCount);
543     EXPECT_EQ(0, filledRanges.hashRangesCount);
544 
545     EXPECT_EQ(-ENODATA, IncFs_IsFullyLoaded(fd.get()));
546     EXPECT_EQ(-ENODATA, IncFs_IsEverythingFullyLoaded(control_));
547 
548     // append one more block next to the first one
549     block.pageIndex = 1;
550     ASSERT_EQ(1, writeBlocks({&block, 1}));
551 
552     EXPECT_EQ(0, IncFs_GetFilledRanges(fd.get(), 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(), 0, bufferSpan, &filledRanges));
559     ASSERT_EQ(1, filledRanges.dataRangesCount);
560     EXPECT_EQ(0, 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(), 1, bufferSpan, &filledRanges));
565     ASSERT_EQ(1, filledRanges.dataRangesCount);
566     EXPECT_EQ(1, filledRanges.dataRanges[0].begin);
567     EXPECT_EQ(2, filledRanges.dataRanges[0].end);
568     EXPECT_EQ(0, filledRanges.hashRangesCount);
569 
570     EXPECT_EQ(0, IncFs_GetFilledRangesStartingFrom(fd.get(), 30, bufferSpan, &filledRanges));
571     EXPECT_EQ(0, filledRanges.dataRangesCount);
572     EXPECT_EQ(0, filledRanges.hashRangesCount);
573 
574     EXPECT_EQ(-ENODATA, IncFs_IsFullyLoaded(fd.get()));
575     EXPECT_EQ(-ENODATA, IncFs_IsEverythingFullyLoaded(control_));
576 
577     // now create a gap between filled blocks
578     block.pageIndex = 3;
579     ASSERT_EQ(1, writeBlocks({&block, 1}));
580 
581     EXPECT_EQ(0, IncFs_GetFilledRanges(fd.get(), bufferSpan, &filledRanges));
582     ASSERT_EQ(2, filledRanges.dataRangesCount);
583     EXPECT_EQ(0, filledRanges.dataRanges[0].begin);
584     EXPECT_EQ(2, filledRanges.dataRanges[0].end);
585     EXPECT_EQ(3, filledRanges.dataRanges[1].begin);
586     EXPECT_EQ(4, filledRanges.dataRanges[1].end);
587     EXPECT_EQ(0, filledRanges.hashRangesCount);
588 
589     EXPECT_EQ(0, IncFs_GetFilledRangesStartingFrom(fd.get(), 0, bufferSpan, &filledRanges));
590     ASSERT_EQ(2, filledRanges.dataRangesCount);
591     EXPECT_EQ(0, filledRanges.dataRanges[0].begin);
592     EXPECT_EQ(2, filledRanges.dataRanges[0].end);
593     EXPECT_EQ(3, filledRanges.dataRanges[1].begin);
594     EXPECT_EQ(4, filledRanges.dataRanges[1].end);
595     EXPECT_EQ(0, filledRanges.hashRangesCount);
596 
597     EXPECT_EQ(0, IncFs_GetFilledRangesStartingFrom(fd.get(), 1, bufferSpan, &filledRanges));
598     ASSERT_EQ(2, filledRanges.dataRangesCount);
599     EXPECT_EQ(1, filledRanges.dataRanges[0].begin);
600     EXPECT_EQ(2, filledRanges.dataRanges[0].end);
601     EXPECT_EQ(3, filledRanges.dataRanges[1].begin);
602     EXPECT_EQ(4, filledRanges.dataRanges[1].end);
603     EXPECT_EQ(0, filledRanges.hashRangesCount);
604 
605     EXPECT_EQ(0, IncFs_GetFilledRangesStartingFrom(fd.get(), 2, bufferSpan, &filledRanges));
606     ASSERT_EQ(1, filledRanges.dataRangesCount);
607     EXPECT_EQ(3, filledRanges.dataRanges[0].begin);
608     EXPECT_EQ(4, filledRanges.dataRanges[0].end);
609     EXPECT_EQ(0, filledRanges.hashRangesCount);
610 
611     EXPECT_EQ(0, IncFs_GetFilledRangesStartingFrom(fd.get(), 30, bufferSpan, &filledRanges));
612     EXPECT_EQ(0, filledRanges.dataRangesCount);
613     EXPECT_EQ(0, filledRanges.hashRangesCount);
614 
615     EXPECT_EQ(-ENODATA, IncFs_IsFullyLoaded(fd.get()));
616     EXPECT_EQ(-ENODATA, IncFs_IsEverythingFullyLoaded(control_));
617 
618     // at last fill the whole file and make sure we report it as having a single range
619     block.pageIndex = 2;
620     ASSERT_EQ(1, writeBlocks({&block, 1}));
621 
622     EXPECT_EQ(0, IncFs_GetFilledRanges(fd.get(), bufferSpan, &filledRanges));
623     ASSERT_EQ(1, filledRanges.dataRangesCount);
624     EXPECT_EQ(0, filledRanges.dataRanges[0].begin);
625     EXPECT_EQ(4, filledRanges.dataRanges[0].end);
626     EXPECT_EQ(0, filledRanges.hashRangesCount);
627 
628     EXPECT_EQ(0, IncFs_GetFilledRangesStartingFrom(fd.get(), 0, bufferSpan, &filledRanges));
629     ASSERT_EQ(1, filledRanges.dataRangesCount);
630     EXPECT_EQ(0, filledRanges.dataRanges[0].begin);
631     EXPECT_EQ(4, filledRanges.dataRanges[0].end);
632     EXPECT_EQ(0, filledRanges.hashRangesCount);
633 
634     EXPECT_EQ(0, IncFs_GetFilledRangesStartingFrom(fd.get(), 1, bufferSpan, &filledRanges));
635     ASSERT_EQ(1, filledRanges.dataRangesCount);
636     EXPECT_EQ(1, filledRanges.dataRanges[0].begin);
637     EXPECT_EQ(4, filledRanges.dataRanges[0].end);
638 
639     EXPECT_EQ(0, IncFs_GetFilledRangesStartingFrom(fd.get(), 30, bufferSpan, &filledRanges));
640     EXPECT_EQ(0, filledRanges.dataRangesCount);
641     EXPECT_EQ(0, filledRanges.hashRangesCount);
642 
643     EXPECT_EQ(0, IncFs_IsFullyLoaded(fd.get()));
644     EXPECT_EQ(0, IncFs_IsEverythingFullyLoaded(control_));
645 }
646 
TEST_F(IncFsTest,GetFilledRangesSmallBuffer)647 TEST_F(IncFsTest, GetFilledRangesSmallBuffer) {
648     ASSERT_EQ(0,
649               makeFile(control_, mountPath(test_file_name_), 0555, fileId(1),
650                        {.size = 5 * INCFS_DATA_FILE_BLOCK_SIZE}));
651     char buffer[1024];
652 
653     auto fd = openForSpecialOps(control_, fileId(1));
654     ASSERT_GE(fd.get(), 0);
655 
656     std::vector<char> data(INCFS_DATA_FILE_BLOCK_SIZE);
657     DataBlock blocks[] = {DataBlock{
658                                   .fileFd = fd.get(),
659                                   .pageIndex = 0,
660                                   .compression = INCFS_COMPRESSION_KIND_NONE,
661                                   .dataSize = (uint32_t)data.size(),
662                                   .data = data.data(),
663                           },
664                           DataBlock{
665                                   .fileFd = fd.get(),
666                                   .pageIndex = 2,
667                                   .compression = INCFS_COMPRESSION_KIND_NONE,
668                                   .dataSize = (uint32_t)data.size(),
669                                   .data = data.data(),
670                           },
671                           DataBlock{
672                                   .fileFd = fd.get(),
673                                   .pageIndex = 4,
674                                   .compression = INCFS_COMPRESSION_KIND_NONE,
675                                   .dataSize = (uint32_t)data.size(),
676                                   .data = data.data(),
677                           }};
678     ASSERT_EQ(3, writeBlocks({blocks, 3}));
679 
680     IncFsSpan bufferSpan = {.data = buffer, .size = sizeof(IncFsBlockRange)};
681     IncFsFilledRanges filledRanges;
682     EXPECT_EQ(-ERANGE, IncFs_GetFilledRanges(fd.get(), bufferSpan, &filledRanges));
683     ASSERT_EQ(1, filledRanges.dataRangesCount);
684     EXPECT_EQ(0, filledRanges.dataRanges[0].begin);
685     EXPECT_EQ(1, filledRanges.dataRanges[0].end);
686     EXPECT_EQ(0, filledRanges.hashRangesCount);
687     EXPECT_EQ(2, filledRanges.endIndex);
688 
689     EXPECT_EQ(-ERANGE,
690               IncFs_GetFilledRangesStartingFrom(fd.get(), filledRanges.endIndex, bufferSpan,
691                                                 &filledRanges));
692     ASSERT_EQ(1, filledRanges.dataRangesCount);
693     EXPECT_EQ(2, filledRanges.dataRanges[0].begin);
694     EXPECT_EQ(3, filledRanges.dataRanges[0].end);
695     EXPECT_EQ(0, filledRanges.hashRangesCount);
696     EXPECT_EQ(4, filledRanges.endIndex);
697 
698     EXPECT_EQ(0,
699               IncFs_GetFilledRangesStartingFrom(fd.get(), filledRanges.endIndex, bufferSpan,
700                                                 &filledRanges));
701     ASSERT_EQ(1, filledRanges.dataRangesCount);
702     EXPECT_EQ(4, filledRanges.dataRanges[0].begin);
703     EXPECT_EQ(5, filledRanges.dataRanges[0].end);
704     EXPECT_EQ(0, filledRanges.hashRangesCount);
705     EXPECT_EQ(5, filledRanges.endIndex);
706 }
707 
TEST_F(IncFsTest,GetFilledRangesWithHashes)708 TEST_F(IncFsTest, GetFilledRangesWithHashes) {
709     auto size = makeFileWithHash(1);
710     ASSERT_GT(size, 0);
711     ASSERT_NO_FATAL_FAILURE(writeTestRanges(1, size));
712 
713     auto fd = openForSpecialOps(control_, fileId(1));
714     ASSERT_GE(fd.get(), 0);
715 
716     char buffer[1024];
717     IncFsSpan bufferSpan = {.data = buffer, .size = sizeof(buffer)};
718     IncFsFilledRanges filledRanges;
719     EXPECT_EQ(0, IncFs_GetFilledRanges(fd.get(), bufferSpan, &filledRanges));
720     ASSERT_EQ(3, filledRanges.dataRangesCount);
721     auto lastPage = sizeToPages(size) - 1;
722     EXPECT_EQ(lastPage, filledRanges.dataRanges[2].begin);
723     EXPECT_EQ(lastPage + 1, filledRanges.dataRanges[2].end);
724     EXPECT_EQ(2, filledRanges.hashRangesCount);
725     EXPECT_EQ(0, filledRanges.hashRanges[0].begin);
726     EXPECT_EQ(1, filledRanges.hashRanges[0].end);
727     EXPECT_EQ(2, filledRanges.hashRanges[1].begin);
728     EXPECT_EQ(3, filledRanges.hashRanges[1].end);
729     EXPECT_EQ(sizeToPages(size) + 3, filledRanges.endIndex);
730 }
731 
TEST_F(IncFsTest,GetFilledRangesCpp)732 TEST_F(IncFsTest, GetFilledRangesCpp) {
733     auto size = makeFileWithHash(1);
734     ASSERT_GT(size, 0);
735     ASSERT_NO_FATAL_FAILURE(writeTestRanges(1, size));
736 
737     auto fd = openForSpecialOps(control_, fileId(1));
738     ASSERT_GE(fd.get(), 0);
739 
740     // simply get all ranges
741     auto [res, ranges] = getFilledRanges(fd.get());
742     EXPECT_EQ(res, 0);
743     EXPECT_EQ(size_t(5), ranges.totalSize());
744     ASSERT_EQ(size_t(3), ranges.dataRanges().size());
745     auto lastPage = sizeToPages(size) - 1;
746     EXPECT_EQ(lastPage, ranges.dataRanges()[2].begin);
747     EXPECT_EQ(size_t(1), ranges.dataRanges()[2].size());
748     ASSERT_EQ(size_t(2), ranges.hashRanges().size());
749     EXPECT_EQ(0, ranges.hashRanges()[0].begin);
750     EXPECT_EQ(size_t(1), ranges.hashRanges()[0].size());
751     EXPECT_EQ(2, ranges.hashRanges()[1].begin);
752     EXPECT_EQ(size_t(1), ranges.hashRanges()[1].size());
753 
754     // now check how buffer size limiting works.
755     FilledRanges::RangeBuffer buf(ranges.totalSize() - 1);
756     auto [res2, ranges2] = getFilledRanges(fd.get(), std::move(buf));
757     ASSERT_EQ(-ERANGE, res2);
758     EXPECT_EQ(ranges.totalSize() - 1, ranges2.totalSize());
759     ASSERT_EQ(size_t(3), ranges2.dataRanges().size());
760     ASSERT_EQ(size_t(1), ranges2.hashRanges().size());
761     EXPECT_EQ(0, ranges2.hashRanges()[0].begin);
762     EXPECT_EQ(size_t(1), ranges2.hashRanges()[0].size());
763 
764     // and now check the resumption from the previous result
765     auto [res3, ranges3] = getFilledRanges(fd.get(), std::move(ranges2));
766     ASSERT_EQ(0, res3);
767     EXPECT_EQ(ranges.totalSize(), ranges3.totalSize());
768     ASSERT_EQ(size_t(3), ranges3.dataRanges().size());
769     ASSERT_EQ(size_t(2), ranges3.hashRanges().size());
770     EXPECT_EQ(0, ranges3.hashRanges()[0].begin);
771     EXPECT_EQ(size_t(1), ranges3.hashRanges()[0].size());
772     EXPECT_EQ(2, ranges3.hashRanges()[1].begin);
773     EXPECT_EQ(size_t(1), ranges3.hashRanges()[1].size());
774 
775     EXPECT_EQ(LoadingState::MissingBlocks, isFullyLoaded(fd.get()));
776     EXPECT_EQ(LoadingState::MissingBlocks, isEverythingFullyLoaded(control_));
777 
778     {
779         std::vector<char> data(INCFS_DATA_FILE_BLOCK_SIZE);
780         DataBlock block = {.fileFd = fd.get(),
781                            .pageIndex = 1,
782                            .compression = INCFS_COMPRESSION_KIND_NONE,
783                            .dataSize = (uint32_t)data.size(),
784                            .data = data.data()};
785         for (auto i = 0; i != sizeToPages(size); ++i) {
786             block.pageIndex = i;
787             ASSERT_EQ(1, writeBlocks({&block, 1}));
788         }
789         block.kind = INCFS_BLOCK_KIND_HASH;
790         for (auto i = 0; i != 3; ++i) {
791             block.pageIndex = i;
792             ASSERT_EQ(1, writeBlocks({&block, 1}));
793         }
794     }
795     EXPECT_EQ(LoadingState::Full, isFullyLoaded(fd.get()));
796     EXPECT_EQ(LoadingState::Full, isEverythingFullyLoaded(control_));
797 }
798 
TEST_F(IncFsTest,BlocksWritten)799 TEST_F(IncFsTest, BlocksWritten) {
800     if (!(features() & Features::v2)) {
801         GTEST_SKIP() << "test not supported: IncFS is too old";
802         return;
803     }
804     const auto id = fileId(1);
805     ASSERT_EQ(0,
806               makeFile(control_, mountPath(test_file_name_), 0555, id, {.size = test_file_size_}));
807 
808     IncFsSize blocksWritten = 0;
809     ASSERT_EQ(0, IncFs_WaitForFsWrittenBlocksChange(control_, 0, &blocksWritten));
810     EXPECT_EQ(0, blocksWritten);
811 
812     auto fd = openForSpecialOps(control_, fileId(1));
813     ASSERT_GE(fd.get(), 0);
814 
815     std::vector<char> data(INCFS_DATA_FILE_BLOCK_SIZE);
816     auto block = DataBlock{
817             .fileFd = fd.get(),
818             .pageIndex = 0,
819             .compression = INCFS_COMPRESSION_KIND_NONE,
820             .dataSize = (uint32_t)data.size(),
821             .data = data.data(),
822     };
823     ASSERT_EQ(1, writeBlocks({&block, 1}));
824 
825     ASSERT_EQ(0, IncFs_WaitForFsWrittenBlocksChange(control_, 0, &blocksWritten));
826     EXPECT_EQ(1, blocksWritten);
827 }
828 
TEST_F(IncFsTest,Timeouts)829 TEST_F(IncFsTest, Timeouts) {
830     if (!(features() & Features::v2)) {
831         GTEST_SKIP() << "test not supported: IncFS is too old";
832         return;
833     }
834 
835     IncFsUidReadTimeouts timeouts[2] = {{1, 1000, 2000, 3000}, {2, 1000, 3000, 4000}};
836 
837     EXPECT_EQ(0, IncFs_SetUidReadTimeouts(control_, timeouts, std::size(timeouts)));
838 
839     IncFsUidReadTimeouts outTimeouts[3];
840 
841     size_t outSize = 1;
842     EXPECT_EQ(-E2BIG, IncFs_GetUidReadTimeouts(control_, outTimeouts, &outSize));
843     EXPECT_EQ(size_t(2), outSize);
844 
845     outSize = 3;
846     EXPECT_EQ(0, IncFs_GetUidReadTimeouts(control_, outTimeouts, &outSize));
847     EXPECT_EQ(size_t(2), outSize);
848 
849     EXPECT_EQ(0, memcmp(timeouts, outTimeouts, 2 * sizeof(timeouts[0])));
850 }
851 
TEST_F(IncFsTest,CompletionNoFiles)852 TEST_F(IncFsTest, CompletionNoFiles) {
853     if (!(features() & Features::v2)) {
854         GTEST_SKIP() << "test not supported: IncFS is too old";
855         return;
856     }
857 
858     size_t count = 0;
859     EXPECT_EQ(0, IncFs_ListIncompleteFiles(control_, nullptr, &count));
860     EXPECT_EQ(size_t(0), count);
861     EXPECT_EQ(0, IncFs_WaitForLoadingComplete(control_, 0));
862 }
863 
TEST_F(IncFsTest,CompletionOneFile)864 TEST_F(IncFsTest, CompletionOneFile) {
865     if (!(features() & Features::v2)) {
866         GTEST_SKIP() << "test not supported: IncFS is too old";
867         return;
868     }
869 
870     const auto id = fileId(1);
871     ASSERT_EQ(0,
872               makeFile(control_, mountPath(test_file_name_), 0555, id, {.size = test_file_size_}));
873 
874     size_t count = 0;
875     EXPECT_EQ(-E2BIG, IncFs_ListIncompleteFiles(control_, nullptr, &count));
876     EXPECT_EQ(size_t(1), count);
877     EXPECT_EQ(-ETIMEDOUT, IncFs_WaitForLoadingComplete(control_, 0));
878 
879     IncFsFileId ids[2];
880     count = 2;
881     EXPECT_EQ(0, IncFs_ListIncompleteFiles(control_, ids, &count));
882     EXPECT_EQ(size_t(1), count);
883     EXPECT_EQ(id, ids[0]);
884 
885     auto fd = openForSpecialOps(control_, id);
886     ASSERT_GE(fd.get(), 0);
887     std::vector<char> data(INCFS_DATA_FILE_BLOCK_SIZE);
888     auto block = DataBlock{
889             .fileFd = fd.get(),
890             .pageIndex = 0,
891             .compression = INCFS_COMPRESSION_KIND_NONE,
892             .dataSize = (uint32_t)data.size(),
893             .data = data.data(),
894     };
895     ASSERT_EQ(1, writeBlocks({&block, 1}));
896 
897     count = 2;
898     EXPECT_EQ(0, IncFs_ListIncompleteFiles(control_, ids, &count));
899     EXPECT_EQ(size_t(0), count);
900     EXPECT_EQ(0, IncFs_WaitForLoadingComplete(control_, 0));
901 }
902 
TEST_F(IncFsTest,CompletionMultiple)903 TEST_F(IncFsTest, CompletionMultiple) {
904     if (!(features() & Features::v2)) {
905         GTEST_SKIP() << "test not supported: IncFS is too old";
906         return;
907     }
908 
909     const auto id = fileId(1);
910     ASSERT_EQ(0,
911               makeFile(control_, mountPath(test_file_name_), 0555, id, {.size = test_file_size_}));
912 
913     size_t count = 0;
914     EXPECT_EQ(-E2BIG, IncFs_ListIncompleteFiles(control_, nullptr, &count));
915     EXPECT_EQ(size_t(1), count);
916     EXPECT_EQ(-ETIMEDOUT, IncFs_WaitForLoadingComplete(control_, 0));
917 
918     // fill the existing file but add another one
919     const auto id2 = fileId(2);
920     ASSERT_EQ(0, makeFile(control_, mountPath("test2"), 0555, id2, {.size = test_file_size_}));
921 
922     IncFsFileId ids[2];
923     count = 2;
924     EXPECT_EQ(0, IncFs_ListIncompleteFiles(control_, ids, &count));
925     EXPECT_EQ(size_t(2), count);
926     EXPECT_EQ(id, ids[0]);
927     EXPECT_EQ(id2, ids[1]);
928 
929     auto fd = openForSpecialOps(control_, id);
930     ASSERT_GE(fd.get(), 0);
931     std::vector<char> data(INCFS_DATA_FILE_BLOCK_SIZE);
932     auto block = DataBlock{
933             .fileFd = fd.get(),
934             .pageIndex = 0,
935             .compression = INCFS_COMPRESSION_KIND_NONE,
936             .dataSize = (uint32_t)data.size(),
937             .data = data.data(),
938     };
939     ASSERT_EQ(1, writeBlocks({&block, 1}));
940 
941     count = 2;
942     EXPECT_EQ(0, IncFs_ListIncompleteFiles(control_, ids, &count));
943     EXPECT_EQ(size_t(1), count);
944     EXPECT_EQ(id2, ids[0]);
945     EXPECT_EQ(-ETIMEDOUT, IncFs_WaitForLoadingComplete(control_, 0));
946 }
947 
TEST_F(IncFsTest,CompletionWait)948 TEST_F(IncFsTest, CompletionWait) {
949     if (!(features() & Features::v2)) {
950         GTEST_SKIP() << "test not supported: IncFS is too old";
951         return;
952     }
953 
954     ASSERT_EQ(0,
955               makeFile(control_, mountPath("test1"), 0555, fileId(1),
956                        {.size = INCFS_DATA_FILE_BLOCK_SIZE}));
957     ASSERT_EQ(0,
958               makeFile(control_, mountPath("test2"), 0555, fileId(2),
959                        {.size = INCFS_DATA_FILE_BLOCK_SIZE}));
960     ASSERT_EQ(0,
961               makeFile(control_, mountPath("test3"), 0555, fileId(3),
962                        {.size = INCFS_DATA_FILE_BLOCK_SIZE}));
963 
964     std::atomic<int> res = -1;
965     auto waiter = std::thread([&] { res = IncFs_WaitForLoadingComplete(control_, 5 * 1000); });
966 
967     std::vector<char> data(INCFS_DATA_FILE_BLOCK_SIZE);
968 
969     {
970         auto fd = openForSpecialOps(control_, fileId(1));
971         ASSERT_GE(fd.get(), 0);
972         auto block = DataBlock{
973                 .fileFd = fd.get(),
974                 .pageIndex = 0,
975                 .compression = INCFS_COMPRESSION_KIND_NONE,
976                 .dataSize = (uint32_t)data.size(),
977                 .data = data.data(),
978         };
979         ASSERT_EQ(1, writeBlocks({&block, 1}));
980     }
981     ASSERT_TRUE(res == -1);
982 
983     {
984         auto fd = openForSpecialOps(control_, fileId(3));
985         ASSERT_GE(fd.get(), 0);
986         auto block = DataBlock{
987                 .fileFd = fd.get(),
988                 .pageIndex = 0,
989                 .compression = INCFS_COMPRESSION_KIND_NONE,
990                 .dataSize = (uint32_t)data.size(),
991                 .data = data.data(),
992         };
993         ASSERT_EQ(1, writeBlocks({&block, 1}));
994     }
995     ASSERT_TRUE(res == -1);
996 
997     {
998         auto fd = openForSpecialOps(control_, fileId(2));
999         ASSERT_GE(fd.get(), 0);
1000         auto block = DataBlock{
1001                 .fileFd = fd.get(),
1002                 .pageIndex = 0,
1003                 .compression = INCFS_COMPRESSION_KIND_NONE,
1004                 .dataSize = (uint32_t)data.size(),
1005                 .data = data.data(),
1006         };
1007         ASSERT_EQ(1, writeBlocks({&block, 1}));
1008     }
1009 
1010     waiter.join();
1011 
1012     auto listIncomplete = [&] {
1013         IncFsFileId ids[3];
1014         size_t count = 3;
1015         if (IncFs_ListIncompleteFiles(control_, ids, &count) != 0) {
1016             return "error listing incomplete files"s;
1017         }
1018         auto res = ab::StringPrintf("[%d]", int(count));
1019         for (size_t i = 0; i < count; ++i) {
1020             ab::StringAppendF(&res, " %s", toString(ids[i]).c_str());
1021         }
1022         return res;
1023     };
1024     EXPECT_EQ(0, res) << "Incomplete files: " << listIncomplete();
1025 }
1026 
TEST_F(IncFsTest,GetBlockCounts)1027 TEST_F(IncFsTest, GetBlockCounts) {
1028     if (!(features() & Features::v2)) {
1029         GTEST_SKIP() << "test not supported: IncFS is too old";
1030         return;
1031     }
1032 
1033     const auto id = fileId(1);
1034     ASSERT_EQ(0,
1035               makeFile(control_, mountPath(test_file_name_), 0555, id,
1036                        {.size = 20 * INCFS_DATA_FILE_BLOCK_SIZE + 3}));
1037 
1038     IncFsBlockCounts counts = {};
1039     EXPECT_EQ(0,
1040               IncFs_GetFileBlockCountByPath(control_, mountPath(test_file_name_).c_str(), &counts));
1041     EXPECT_EQ(21, counts.totalDataBlocks);
1042     EXPECT_EQ(0, counts.filledDataBlocks);
1043     EXPECT_EQ(0, counts.totalHashBlocks);
1044     EXPECT_EQ(0, counts.filledHashBlocks);
1045 
1046     EXPECT_EQ(0, IncFs_GetFileBlockCountById(control_, id, &counts));
1047     EXPECT_EQ(21, counts.totalDataBlocks);
1048     EXPECT_EQ(0, counts.filledDataBlocks);
1049     EXPECT_EQ(0, counts.totalHashBlocks);
1050     EXPECT_EQ(0, counts.filledHashBlocks);
1051 
1052     auto fd = openForSpecialOps(control_, id);
1053     ASSERT_GE(fd.get(), 0);
1054     std::vector<char> data(INCFS_DATA_FILE_BLOCK_SIZE);
1055     auto block = DataBlock{
1056             .fileFd = fd.get(),
1057             .pageIndex = 3,
1058             .compression = INCFS_COMPRESSION_KIND_NONE,
1059             .dataSize = (uint32_t)data.size(),
1060             .data = data.data(),
1061     };
1062     ASSERT_EQ(1, writeBlocks({&block, 1}));
1063 
1064     EXPECT_EQ(0,
1065               IncFs_GetFileBlockCountByPath(control_, mountPath(test_file_name_).c_str(), &counts));
1066     EXPECT_EQ(21, counts.totalDataBlocks);
1067     EXPECT_EQ(1, counts.filledDataBlocks);
1068     EXPECT_EQ(0, counts.totalHashBlocks);
1069     EXPECT_EQ(0, counts.filledHashBlocks);
1070 
1071     EXPECT_EQ(0, IncFs_GetFileBlockCountById(control_, id, &counts));
1072     EXPECT_EQ(21, counts.totalDataBlocks);
1073     EXPECT_EQ(1, counts.filledDataBlocks);
1074     EXPECT_EQ(0, counts.totalHashBlocks);
1075     EXPECT_EQ(0, counts.filledHashBlocks);
1076 }
1077 
TEST_F(IncFsTest,GetBlockCountsHash)1078 TEST_F(IncFsTest, GetBlockCountsHash) {
1079     if (!(features() & Features::v2)) {
1080         GTEST_SKIP() << "test not supported: IncFS is too old";
1081         return;
1082     }
1083 
1084     auto size = makeFileWithHash(1);
1085     ASSERT_GT(size, 0);
1086 
1087     IncFsBlockCounts counts = {};
1088     EXPECT_EQ(0,
1089               IncFs_GetFileBlockCountByPath(control_, mountPath(test_file_name_).c_str(), &counts));
1090     EXPECT_EQ(sizeToPages(size), counts.totalDataBlocks);
1091     EXPECT_EQ(0, counts.filledDataBlocks);
1092     EXPECT_EQ(3, counts.totalHashBlocks);
1093     EXPECT_EQ(0, counts.filledHashBlocks);
1094 
1095     ASSERT_NO_FATAL_FAILURE(writeTestRanges(1, size));
1096 
1097     EXPECT_EQ(0,
1098               IncFs_GetFileBlockCountByPath(control_, mountPath(test_file_name_).c_str(), &counts));
1099     EXPECT_EQ(sizeToPages(size), counts.totalDataBlocks);
1100     EXPECT_EQ(4, counts.filledDataBlocks);
1101     EXPECT_EQ(3, counts.totalHashBlocks);
1102     EXPECT_EQ(2, counts.filledHashBlocks);
1103 }
1104 
TEST_F(IncFsTest,ReserveSpace)1105 TEST_F(IncFsTest, ReserveSpace) {
1106     auto size = makeFileWithHash(1);
1107     ASSERT_GT(size, 0);
1108 
1109     EXPECT_EQ(-ENOENT,
1110               IncFs_ReserveSpaceByPath(control_, mountPath("1"s += test_file_name_).c_str(), size));
1111     EXPECT_EQ(0, IncFs_ReserveSpaceByPath(control_, mountPath(test_file_name_).c_str(), size));
1112     EXPECT_EQ(0, IncFs_ReserveSpaceByPath(control_, mountPath(test_file_name_).c_str(), 2 * size));
1113     EXPECT_EQ(0, IncFs_ReserveSpaceByPath(control_, mountPath(test_file_name_).c_str(), 2 * size));
1114     EXPECT_EQ(0,
1115               IncFs_ReserveSpaceByPath(control_, mountPath(test_file_name_).c_str(),
1116                                        kTrimReservedSpace));
1117     EXPECT_EQ(0,
1118               IncFs_ReserveSpaceByPath(control_, mountPath(test_file_name_).c_str(),
1119                                        kTrimReservedSpace));
1120 
1121     EXPECT_EQ(-ENOENT, IncFs_ReserveSpaceById(control_, fileId(2), size));
1122     EXPECT_EQ(0, IncFs_ReserveSpaceById(control_, fileId(1), size));
1123     EXPECT_EQ(0, IncFs_ReserveSpaceById(control_, fileId(1), 2 * size));
1124     EXPECT_EQ(0, IncFs_ReserveSpaceById(control_, fileId(1), 2 * size));
1125     EXPECT_EQ(0, IncFs_ReserveSpaceById(control_, fileId(1), kTrimReservedSpace));
1126     EXPECT_EQ(0, IncFs_ReserveSpaceById(control_, fileId(1), kTrimReservedSpace));
1127 }
1128 
TEST_F(IncFsTest,ForEachFile)1129 TEST_F(IncFsTest, ForEachFile) {
1130     const auto incompleteSupported = (features() & Features::v2) != 0;
1131     EXPECT_EQ(-EINVAL, IncFs_ForEachFile(nullptr, nullptr, nullptr));
1132     EXPECT_EQ(-EINVAL, IncFs_ForEachIncompleteFile(nullptr, nullptr, nullptr));
1133     EXPECT_EQ(-EINVAL, IncFs_ForEachFile(control_, nullptr, nullptr));
1134     EXPECT_EQ(-EINVAL, IncFs_ForEachIncompleteFile(control_, nullptr, nullptr));
1135     EXPECT_EQ(0, IncFs_ForEachFile(control_, nullptr, [](auto, auto, auto) { return true; }));
1136     EXPECT_EQ(incompleteSupported ? 0 : -ENOTSUP,
1137               IncFs_ForEachIncompleteFile(control_, nullptr,
1138                                           [](auto, auto, auto) { return true; }));
1139     EXPECT_EQ(0, IncFs_ForEachFile(control_, this, [](auto, auto, auto) { return true; }));
1140     EXPECT_EQ(incompleteSupported ? 0 : -ENOTSUP,
1141               IncFs_ForEachIncompleteFile(control_, this, [](auto, auto, auto) { return true; }));
1142 
1143     int res = makeFile(control_, mountPath("incomplete.txt"), 0555, fileId(1),
1144                        {.metadata = metadata("md")});
1145     ASSERT_EQ(res, 0);
1146 
1147     EXPECT_EQ(1, IncFs_ForEachFile(control_, this, [](auto, auto context, auto id) {
1148                   auto self = (IncFsTest*)context;
1149                   EXPECT_EQ(self->fileId(1), id);
1150                   return true;
1151               }));
1152     EXPECT_EQ(incompleteSupported ? 0 : -ENOTSUP,
1153               IncFs_ForEachIncompleteFile(control_, this, [](auto, auto, auto) { return true; }));
1154 
1155     auto size = makeFileWithHash(2);
1156     ASSERT_GT(size, 0);
1157 
1158     EXPECT_EQ(1, IncFs_ForEachFile(control_, this, [](auto, auto context, auto id) {
1159                   auto self = (IncFsTest*)context;
1160                   EXPECT_TRUE(id == self->fileId(1) || id == self->fileId(2));
1161                   return false;
1162               }));
1163     EXPECT_EQ(2, IncFs_ForEachFile(control_, this, [](auto, auto context, auto id) {
1164                   auto self = (IncFsTest*)context;
1165                   EXPECT_TRUE(id == self->fileId(1) || id == self->fileId(2));
1166                   return true;
1167               }));
1168     EXPECT_EQ(incompleteSupported ? 1 : -ENOTSUP,
1169               IncFs_ForEachIncompleteFile(control_, this, [](auto, auto context, auto id) {
1170                   auto self = (IncFsTest*)context;
1171                   EXPECT_EQ(self->fileId(2), id);
1172                   return true;
1173               }));
1174 }
1175 
TEST(CStrWrapperTest,EmptyStringView)1176 TEST(CStrWrapperTest, EmptyStringView) {
1177     ASSERT_STREQ("", details::c_str({}).get());
1178     ASSERT_STREQ("", details::c_str({nullptr, 0}).get());
1179 }
1180 
1181 class IncFsGetMetricsTest : public IncFsTestBase {
1182 protected:
getReadTimeout()1183     int32_t getReadTimeout() override { return 100 /* 0.1 second */; }
1184 };
1185 
TEST_F(IncFsGetMetricsTest,MetricsWithNoEvents)1186 TEST_F(IncFsGetMetricsTest, MetricsWithNoEvents) {
1187     if (!(features() & Features::v2)) {
1188         GTEST_SKIP() << "test not supported: IncFS is too old";
1189         return;
1190     }
1191     IncFsLastReadError lastReadError = {.id = fileId(-1),
1192                                         .timestampUs = static_cast<uint64_t>(-1),
1193                                         .block = static_cast<IncFsBlockIndex>(-1),
1194                                         .errorNo = static_cast<uint32_t>(-1),
1195                                         .uid = static_cast<IncFsUid>(-1)};
1196     EXPECT_EQ(0, IncFs_GetLastReadError(control_, &lastReadError));
1197     // All fields should be zero
1198     EXPECT_EQ(FileId{}, lastReadError.id);
1199     EXPECT_EQ(0, (int)lastReadError.timestampUs);
1200     EXPECT_EQ(0, (int)lastReadError.block);
1201     EXPECT_EQ(0, (int)lastReadError.errorNo);
1202     EXPECT_EQ(0, (int)lastReadError.uid);
1203 
1204     IncFsMetrics incfsMetrics = {10, 10, 10, 10, 10, 10, 10, 10, 10};
1205     EXPECT_EQ(0, IncFs_GetMetrics(metrics_key_.c_str(), &incfsMetrics));
1206     EXPECT_EQ(0, (int)incfsMetrics.readsDelayedMin);
1207     EXPECT_EQ(0, (int)incfsMetrics.readsDelayedMinUs);
1208     EXPECT_EQ(0, (int)incfsMetrics.readsDelayedPending);
1209     EXPECT_EQ(0, (int)incfsMetrics.readsDelayedPendingUs);
1210     EXPECT_EQ(0, (int)incfsMetrics.readsFailedHashVerification);
1211     EXPECT_EQ(0, (int)incfsMetrics.readsFailedOther);
1212     EXPECT_EQ(0, (int)incfsMetrics.readsFailedTimedOut);
1213 }
1214 
TEST_F(IncFsGetMetricsTest,MetricsWithReadsTimeOut)1215 TEST_F(IncFsGetMetricsTest, MetricsWithReadsTimeOut) {
1216     if (!(features() & Features::v2)) {
1217         GTEST_SKIP() << "test not supported: IncFS is too old";
1218         return;
1219     }
1220     const auto id = fileId(1);
1221     ASSERT_EQ(0,
1222               makeFile(control_, mountPath(test_file_name_), 0555, id,
1223                        {.size = INCFS_DATA_FILE_BLOCK_SIZE}));
1224 
1225     const auto file_path = mountPath(test_file_name_);
1226     const android::base::unique_fd fd(open(file_path.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY));
1227     ASSERT_GE(fd.get(), 0);
1228     // Read should timeout immediately
1229     char buf[INCFS_DATA_FILE_BLOCK_SIZE];
1230     EXPECT_FALSE(android::base::ReadFully(fd, buf, sizeof(buf)));
1231     IncFsLastReadError lastReadError = {.id = fileId(-1),
1232                                         .timestampUs = static_cast<uint64_t>(-1),
1233                                         .block = static_cast<IncFsBlockIndex>(-1),
1234                                         .errorNo = static_cast<uint32_t>(-1),
1235                                         .uid = static_cast<IncFsUid>(-1)};
1236     EXPECT_EQ(0, IncFs_GetLastReadError(control_, &lastReadError));
1237     EXPECT_EQ(id, lastReadError.id);
1238     EXPECT_TRUE(lastReadError.timestampUs > 0);
1239     EXPECT_EQ(0, (int)lastReadError.block);
1240     EXPECT_EQ(-ETIME, (int)lastReadError.errorNo);
1241     EXPECT_EQ((int)getuid(), (int)lastReadError.uid);
1242 
1243     IncFsMetrics incfsMetrics = {10, 10, 10, 10, 10, 10, 10, 10, 10};
1244     EXPECT_EQ(0, IncFs_GetMetrics(metrics_key_.c_str(), &incfsMetrics));
1245     EXPECT_EQ(0, (int)incfsMetrics.readsDelayedMin);
1246     EXPECT_EQ(0, (int)incfsMetrics.readsDelayedMinUs);
1247     EXPECT_EQ(0, (int)incfsMetrics.readsDelayedPending);
1248     EXPECT_EQ(0, (int)incfsMetrics.readsDelayedPendingUs);
1249     EXPECT_EQ(0, (int)incfsMetrics.readsFailedHashVerification);
1250     EXPECT_EQ(0, (int)incfsMetrics.readsFailedOther);
1251     EXPECT_EQ(1, (int)incfsMetrics.readsFailedTimedOut);
1252 }
1253 
TEST_F(IncFsGetMetricsTest,MetricsWithHashFailure)1254 TEST_F(IncFsGetMetricsTest, MetricsWithHashFailure) {
1255     if (!(features() & Features::v2)) {
1256         GTEST_SKIP() << "test not supported: IncFS is too old";
1257         return;
1258     }
1259     auto size = makeFileWithHash(1);
1260     ASSERT_GT(size, 0);
1261     // Make data and hash mismatch
1262     const auto id = fileId(1);
1263     char data[INCFS_DATA_FILE_BLOCK_SIZE]{static_cast<char>(-1)};
1264     char hashData[INCFS_DATA_FILE_BLOCK_SIZE]{};
1265     auto wfd = openForSpecialOps(control_, id);
1266     ASSERT_GE(wfd.get(), 0);
1267     DataBlock blocks[] = {{
1268                                   .fileFd = wfd.get(),
1269                                   .pageIndex = 0,
1270                                   .compression = INCFS_COMPRESSION_KIND_NONE,
1271                                   .dataSize = INCFS_DATA_FILE_BLOCK_SIZE,
1272                                   .data = data,
1273                           },
1274                           {
1275                                   .fileFd = wfd.get(),
1276                                   // first hash page
1277                                   .pageIndex = 0,
1278                                   .compression = INCFS_COMPRESSION_KIND_NONE,
1279                                   .dataSize = INCFS_DATA_FILE_BLOCK_SIZE,
1280                                   .kind = INCFS_BLOCK_KIND_HASH,
1281                                   .data = hashData,
1282                           },
1283                           {
1284                                   .fileFd = wfd.get(),
1285                                   .pageIndex = 2,
1286                                   .compression = INCFS_COMPRESSION_KIND_NONE,
1287                                   .dataSize = INCFS_DATA_FILE_BLOCK_SIZE,
1288                                   .kind = INCFS_BLOCK_KIND_HASH,
1289                                   .data = hashData,
1290                           }};
1291     ASSERT_EQ((int)std::size(blocks), writeBlocks({blocks, std::size(blocks)}));
1292     const auto file_path = mountPath(test_file_name_);
1293     const android::base::unique_fd fd(open(file_path.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY));
1294     ASSERT_GE(fd.get(), 0);
1295     // Read should fail at reading the first block due to hash failure
1296     char buf[INCFS_DATA_FILE_BLOCK_SIZE];
1297     EXPECT_FALSE(android::base::ReadFully(fd, buf, sizeof(buf)));
1298     IncFsLastReadError lastReadError = {.id = fileId(-1),
1299                                         .timestampUs = static_cast<uint64_t>(-1),
1300                                         .block = static_cast<IncFsBlockIndex>(-1),
1301                                         .errorNo = static_cast<uint32_t>(-1),
1302                                         .uid = static_cast<IncFsUid>(-1)};
1303     EXPECT_EQ(0, IncFs_GetLastReadError(control_, &lastReadError));
1304     EXPECT_EQ(0, std::strcmp(lastReadError.id.data, id.data));
1305     EXPECT_TRUE(lastReadError.timestampUs > 0);
1306     EXPECT_EQ(0, (int)lastReadError.block);
1307     EXPECT_EQ(-EBADMSG, (int)lastReadError.errorNo);
1308     EXPECT_EQ((int)getuid(), (int)lastReadError.uid);
1309 
1310     IncFsMetrics incfsMetrics = {10, 10, 10, 10, 10, 10, 10, 10, 10};
1311     EXPECT_EQ(0, IncFs_GetMetrics(metrics_key_.c_str(), &incfsMetrics));
1312     EXPECT_EQ(0, (int)incfsMetrics.readsDelayedMin);
1313     EXPECT_EQ(0, (int)incfsMetrics.readsDelayedMinUs);
1314     EXPECT_EQ(0, (int)incfsMetrics.readsDelayedPending);
1315     EXPECT_EQ(0, (int)incfsMetrics.readsDelayedPendingUs);
1316     EXPECT_EQ(1, (int)incfsMetrics.readsFailedHashVerification);
1317     EXPECT_EQ(0, (int)incfsMetrics.readsFailedOther);
1318     EXPECT_EQ(0, (int)incfsMetrics.readsFailedTimedOut);
1319 }
1320 
TEST_F(IncFsGetMetricsTest,MetricsWithReadsDelayed)1321 TEST_F(IncFsGetMetricsTest, MetricsWithReadsDelayed) {
1322     if (!(features() & Features::v2)) {
1323         GTEST_SKIP() << "test not supported: IncFS is too old";
1324         return;
1325     }
1326     const auto id = fileId(1);
1327     int testFileSize = INCFS_DATA_FILE_BLOCK_SIZE;
1328     int waitBeforeWriteUs = 10000;
1329     ASSERT_EQ(0, makeFile(control_, mountPath(test_file_name_), 0555, id, {.size = testFileSize}));
1330     std::thread wait_before_write_thread([&]() {
1331         std::vector<ReadInfoWithUid> pending_reads;
1332         ASSERT_EQ(WaitResult::HaveData,
1333                   waitForPendingReads(control_, std::chrono::seconds(1), &pending_reads));
1334         // Additional wait is needed for the kernel jiffies counter to increment
1335         usleep(waitBeforeWriteUs);
1336         auto fd = openForSpecialOps(control_, fileId(1));
1337         ASSERT_GE(fd.get(), 0);
1338         std::vector<char> data(INCFS_DATA_FILE_BLOCK_SIZE);
1339         auto block = DataBlock{
1340                 .fileFd = fd.get(),
1341                 .pageIndex = 0,
1342                 .compression = INCFS_COMPRESSION_KIND_NONE,
1343                 .dataSize = (uint32_t)data.size(),
1344                 .data = data.data(),
1345         };
1346         ASSERT_EQ(1, writeBlocks({&block, 1}));
1347     });
1348 
1349     const auto file_path = mountPath(test_file_name_);
1350     const android::base::unique_fd fd(open(file_path.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY));
1351     ASSERT_GE(fd.get(), 0);
1352     char buf[testFileSize];
1353     EXPECT_TRUE(android::base::ReadFully(fd, buf, sizeof(buf)));
1354     wait_before_write_thread.join();
1355 
1356     IncFsLastReadError lastReadError = {.id = fileId(-1), 1, 1, 1, 1};
1357     EXPECT_EQ(0, IncFs_GetLastReadError(control_, &lastReadError));
1358     EXPECT_EQ(FileId{}, lastReadError.id);
1359     EXPECT_EQ(0, (int)lastReadError.timestampUs);
1360     EXPECT_EQ(0, (int)lastReadError.block);
1361     EXPECT_EQ(0, (int)lastReadError.errorNo);
1362     EXPECT_EQ(0, (int)lastReadError.uid);
1363 
1364     IncFsMetrics incfsMetrics = {10, 10, 10, 10, 10, 10, 10, 10, 10};
1365     EXPECT_EQ(0, IncFs_GetMetrics(metrics_key_.c_str(), &incfsMetrics));
1366     EXPECT_EQ(0, (int)incfsMetrics.readsDelayedMin);
1367     EXPECT_EQ(0, (int)incfsMetrics.readsDelayedMinUs);
1368     EXPECT_EQ(1, (int)incfsMetrics.readsDelayedPending);
1369     EXPECT_TRUE((int)incfsMetrics.readsDelayedPendingUs > 0);
1370     EXPECT_EQ(0, (int)incfsMetrics.readsFailedHashVerification);
1371     EXPECT_EQ(0, (int)incfsMetrics.readsFailedOther);
1372     EXPECT_EQ(0, (int)incfsMetrics.readsFailedTimedOut);
1373 }
1374 
TEST_F(IncFsGetMetricsTest,MetricsWithReadsDelayedPerUidTimeout)1375 TEST_F(IncFsGetMetricsTest, MetricsWithReadsDelayedPerUidTimeout) {
1376     if (!(features() & Features::v2)) {
1377         GTEST_SKIP() << "test not supported: IncFS is too old";
1378         return;
1379     }
1380     const auto id = fileId(1);
1381     int testFileSize = INCFS_DATA_FILE_BLOCK_SIZE;
1382     ASSERT_EQ(0, makeFile(control_, mountPath(test_file_name_), 0555, id, {.size = testFileSize}));
1383 
1384     auto fdToFill = openForSpecialOps(control_, fileId(1));
1385     ASSERT_GE(fdToFill.get(), 0);
1386     std::vector<char> data(INCFS_DATA_FILE_BLOCK_SIZE);
1387     auto block = DataBlock{
1388             .fileFd = fdToFill.get(),
1389             .pageIndex = 0,
1390             .compression = INCFS_COMPRESSION_KIND_NONE,
1391             .dataSize = (uint32_t)data.size(),
1392             .data = data.data(),
1393     };
1394     ASSERT_EQ(1, writeBlocks({&block, 1}));
1395 
1396     // Set per-uid read timeout then read
1397     uint32_t readTimeoutUs = 1000000;
1398     IncFsUidReadTimeouts timeouts[1] = {
1399             {static_cast<IncFsUid>(getuid()), readTimeoutUs, readTimeoutUs, readTimeoutUs}};
1400     ASSERT_EQ(0, IncFs_SetUidReadTimeouts(control_, timeouts, std::size(timeouts)));
1401     const auto file_path = mountPath(test_file_name_);
1402     const android::base::unique_fd fd(open(file_path.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY));
1403     char buf[testFileSize];
1404     ASSERT_GE(fd.get(), 0);
1405     ASSERT_TRUE(android::base::ReadFully(fd, buf, sizeof(buf)));
1406 
1407     IncFsLastReadError lastReadError = {.id = fileId(-1), 1, 1, 1, 1};
1408     EXPECT_EQ(0, IncFs_GetLastReadError(control_, &lastReadError));
1409     EXPECT_EQ(FileId{}, lastReadError.id);
1410     EXPECT_EQ(0, (int)lastReadError.timestampUs);
1411     EXPECT_EQ(0, (int)lastReadError.block);
1412     EXPECT_EQ(0, (int)lastReadError.errorNo);
1413     EXPECT_EQ(0, (int)lastReadError.uid);
1414 
1415     IncFsMetrics incfsMetrics = {10, 10, 10, 10, 10, 10, 10, 10, 10};
1416     EXPECT_EQ(0, IncFs_GetMetrics(metrics_key_.c_str(), &incfsMetrics));
1417     EXPECT_EQ(1, (int)incfsMetrics.readsDelayedMin);
1418     EXPECT_EQ(readTimeoutUs, (uint32_t)incfsMetrics.readsDelayedMinUs);
1419     EXPECT_EQ(0, (int)incfsMetrics.readsDelayedPending);
1420     EXPECT_EQ(0, (int)incfsMetrics.readsDelayedPendingUs);
1421     EXPECT_EQ(0, (int)incfsMetrics.readsFailedHashVerification);
1422     EXPECT_EQ(0, (int)incfsMetrics.readsFailedOther);
1423     EXPECT_EQ(0, (int)incfsMetrics.readsFailedTimedOut);
1424 }
1425 
operator ==(const BlockCounts & lhs,const BlockCounts & rhs)1426 inline bool operator==(const BlockCounts& lhs, const BlockCounts& rhs) {
1427     return lhs.totalDataBlocks == rhs.totalDataBlocks &&
1428             lhs.filledDataBlocks == rhs.filledDataBlocks &&
1429             lhs.totalHashBlocks == rhs.totalHashBlocks &&
1430             lhs.filledHashBlocks == rhs.filledHashBlocks;
1431 }
1432 
TEST_F(IncFsTest,LoadingProgress)1433 TEST_F(IncFsTest, LoadingProgress) {
1434     ASSERT_EQ(0, makeDir(control_, mountPath(test_dir_name_)));
1435 
1436     constexpr auto file_size = INCFS_DATA_FILE_BLOCK_SIZE * 2;
1437     constexpr auto mapped_file_offset = file_size / 2;
1438     constexpr auto mapped_file_size = file_size / 3;
1439 
1440     const auto file_id = fileId(1);
1441 
1442     const auto file_path = mountPath(test_dir_name_, test_file_name_);
1443     ASSERT_FALSE(exists(file_path));
1444     ASSERT_EQ(0,
1445               makeFile(control_, file_path, 0111, file_id,
1446                        {.size = file_size, .metadata = metadata("md")}));
1447     struct stat s = {};
1448     ASSERT_EQ(0, stat(file_path.c_str(), &s));
1449     ASSERT_EQ(file_size, (int)s.st_size);
1450 
1451     const auto mapped_file_path = mountPath(test_dir_name_, test_mapped_file_name_);
1452     ASSERT_FALSE(exists(mapped_file_path));
1453     ASSERT_EQ(0,
1454               makeMappedFile(control_, mapped_file_path, 0111,
1455                              {.sourceId = file_id,
1456                               .sourceOffset = mapped_file_offset,
1457                               .size = mapped_file_size}));
1458     s = {};
1459     ASSERT_EQ(0, stat(mapped_file_path.c_str(), &s));
1460     ASSERT_EQ(mapped_file_size, (int)s.st_size);
1461 
1462     // Check fully loaded first.
1463     ASSERT_EQ(LoadingState::MissingBlocks, isFullyLoaded(control_, file_path));
1464     ASSERT_EQ(LoadingState::MissingBlocks, isFullyLoaded(control_, file_id));
1465     ASSERT_EQ((LoadingState)-ENOTSUP, isFullyLoaded(control_, mapped_file_path));
1466 
1467     // Next is loading progress.
1468     ASSERT_EQ(BlockCounts{.totalDataBlocks = 2}, *getBlockCount(control_, file_path));
1469     ASSERT_EQ(BlockCounts{.totalDataBlocks = 2}, *getBlockCount(control_, file_id));
1470     ASSERT_FALSE(getBlockCount(control_, mapped_file_path));
1471 
1472     // Now write a page #0.
1473     ASSERT_NO_FATAL_FAILURE(writeBlock(0));
1474 
1475     // Recheck everything.
1476     ASSERT_EQ(LoadingState::MissingBlocks, isFullyLoaded(control_, file_path));
1477     ASSERT_EQ(LoadingState::MissingBlocks, isFullyLoaded(control_, file_id));
1478     ASSERT_EQ((LoadingState)-ENOTSUP, isFullyLoaded(control_, mapped_file_path));
1479 
1480     BlockCounts onePage{.totalDataBlocks = 2, .filledDataBlocks = 1};
1481     ASSERT_EQ(onePage, *getBlockCount(control_, file_path));
1482     ASSERT_EQ(onePage, *getBlockCount(control_, file_id));
1483     ASSERT_FALSE(getBlockCount(control_, mapped_file_path));
1484 
1485     // Now write a page #1.
1486     ASSERT_NO_FATAL_FAILURE(writeBlock(1));
1487 
1488     // Check for fully loaded.
1489     ASSERT_EQ(LoadingState::Full, isFullyLoaded(control_, file_path));
1490     ASSERT_EQ(LoadingState::Full, isFullyLoaded(control_, file_id));
1491     ASSERT_EQ((LoadingState)-ENOTSUP, isFullyLoaded(control_, mapped_file_path));
1492 
1493     BlockCounts twoPages{.totalDataBlocks = 2, .filledDataBlocks = 2};
1494     ASSERT_EQ(twoPages, *getBlockCount(control_, file_path));
1495     ASSERT_EQ(twoPages, *getBlockCount(control_, file_id));
1496     ASSERT_FALSE(getBlockCount(control_, mapped_file_path));
1497 }
1498