[incfs] Space trimming for IncFS v1

Run a manual timed job that trims all files one by one on the
old version of IncFS, where it didn't do it automatically.

Bug: 183435580
Fixes: 183436717
Test: atest libincfs-test service.incremental_test
Change-Id: I57885b2826e383814822c767802f837135fd8464
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index 9bb2f04..94f8e59 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -1086,17 +1086,14 @@
         return err;
     }
     if (params.size > 0) {
-        // Only v2+ incfs supports automatically trimming file over-reserved sizes
-        if (mIncFs->features() & incfs::Features::v2) {
-            if (auto err = mIncFs->reserveSpace(ifs->control, normPath, params.size)) {
-                if (err != -EOPNOTSUPP) {
-                    LOG(ERROR) << "Failed to reserve space for a new file: " << err;
-                    (void)mIncFs->unlink(ifs->control, normPath);
-                    return err;
-                } else {
-                    LOG(WARNING) << "Reserving space for backing file isn't supported, "
-                                    "may run out of disk later";
-                }
+        if (auto err = mIncFs->reserveSpace(ifs->control, id, params.size)) {
+            if (err != -EOPNOTSUPP) {
+                LOG(ERROR) << "Failed to reserve space for a new file: " << err;
+                (void)mIncFs->unlink(ifs->control, normPath);
+                return err;
+            } else {
+                LOG(WARNING) << "Reserving space for backing file isn't supported, "
+                                "may run out of disk later";
             }
         }
         if (!data.empty()) {
@@ -1680,6 +1677,15 @@
     }
 }
 
+void IncrementalService::trimReservedSpaceV1(const IncFsMount& ifs) {
+    mIncFs->forEachFile(ifs.control, [this](auto&& control, auto&& fileId) {
+        if (mIncFs->isFileFullyLoaded(control, fileId) == incfs::LoadingState::Full) {
+            mIncFs->reserveSpace(control, fileId, -1);
+        }
+        return true;
+    });
+}
+
 void IncrementalService::prepareDataLoaderLocked(IncFsMount& ifs, DataLoaderParamsParcel&& params,
                                                  DataLoaderStatusListener&& statusListener,
                                                  const StorageHealthCheckParams& healthCheckParams,
@@ -1699,6 +1705,22 @@
                                std::move(statusListener), healthCheckParams,
                                std::move(healthListener), path::join(ifs.root, constants().mount));
 
+    // pre-v2 IncFS doesn't do automatic reserved space trimming - need to run it manually
+    if (!(mIncFs->features() & incfs::Features::v2)) {
+        addIfsStateCallback(ifs.mountId, [this](StorageId storageId, IfsState state) -> bool {
+            if (!state.fullyLoaded) {
+                return true;
+            }
+
+            const auto ifs = getIfs(storageId);
+            if (!ifs) {
+                return false;
+            }
+            trimReservedSpaceV1(*ifs);
+            return false;
+        });
+    }
+
     addIfsStateCallback(ifs.mountId, [this](StorageId storageId, IfsState state) -> bool {
         if (!state.fullyLoaded || state.readLogsEnabled) {
             return true;
diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h
index a697305..fb6f56c 100644
--- a/services/incremental/IncrementalService.h
+++ b/services/incremental/IncrementalService.h
@@ -452,6 +452,8 @@
                                StorageLoadingProgressListener&& progressListener);
     long getMillsSinceOldestPendingRead(StorageId storage);
 
+    void trimReservedSpaceV1(const IncFsMount& ifs);
+
 private:
     const std::unique_ptr<VoldServiceWrapper> mVold;
     const std::unique_ptr<DataLoaderManagerWrapper> mDataLoaderManager;
diff --git a/services/incremental/ServiceWrappers.cpp b/services/incremental/ServiceWrappers.cpp
index 3465499..8e416f3 100644
--- a/services/incremental/ServiceWrappers.cpp
+++ b/services/incremental/ServiceWrappers.cpp
@@ -212,6 +212,9 @@
                                           std::string_view path) const final {
         return incfs::isFullyLoaded(control, path);
     }
+    incfs::LoadingState isFileFullyLoaded(const Control& control, FileId id) const final {
+        return incfs::isFullyLoaded(control, id);
+    }
     incfs::LoadingState isEverythingFullyLoaded(const Control& control) const final {
         return incfs::isEverythingFullyLoaded(control);
     }
@@ -227,9 +230,8 @@
     ErrorCode writeBlocks(std::span<const incfs::DataBlock> blocks) const final {
         return incfs::writeBlocks({blocks.data(), size_t(blocks.size())});
     }
-    ErrorCode reserveSpace(const Control& control, std::string_view path,
-                           IncFsSize size) const final {
-        return incfs::reserveSpace(control, path, size);
+    ErrorCode reserveSpace(const Control& control, FileId id, IncFsSize size) const final {
+        return incfs::reserveSpace(control, id, size);
     }
     WaitResult waitForPendingReads(const Control& control, std::chrono::milliseconds timeout,
                                    std::vector<incfs::ReadInfo>* pendingReadsBuffer) const final {
@@ -238,19 +240,26 @@
     ErrorCode setUidReadTimeouts(const Control& control,
                                  const std::vector<android::os::incremental::PerUidReadTimeouts>&
                                          perUidReadTimeouts) const final {
-        std::vector<incfs::UidReadTimeouts> timeouts;
-        timeouts.resize(perUidReadTimeouts.size());
+        std::vector<incfs::UidReadTimeouts> timeouts(perUidReadTimeouts.size());
         for (int i = 0, size = perUidReadTimeouts.size(); i < size; ++i) {
-            auto&& timeout = timeouts[i];
+            auto& timeout = timeouts[i];
             const auto& perUidTimeout = perUidReadTimeouts[i];
             timeout.uid = perUidTimeout.uid;
             timeout.minTimeUs = perUidTimeout.minTimeUs;
             timeout.minPendingTimeUs = perUidTimeout.minPendingTimeUs;
             timeout.maxPendingTimeUs = perUidTimeout.maxPendingTimeUs;
         }
-
         return incfs::setUidReadTimeouts(control, timeouts);
     }
+    ErrorCode forEachFile(const Control& control, FileCallback cb) const final {
+        return incfs::forEachFile(control,
+                                  [&](auto& control, FileId id) { return cb(control, id); });
+    }
+    ErrorCode forEachIncompleteFile(const Control& control, FileCallback cb) const final {
+        return incfs::forEachIncompleteFile(control, [&](auto& control, FileId id) {
+            return cb(control, id);
+        });
+    }
 };
 
 static JNIEnv* getOrAttachJniEnv(JavaVM* jvm);
diff --git a/services/incremental/ServiceWrappers.h b/services/incremental/ServiceWrappers.h
index a787db5..d4cdcbe 100644
--- a/services/incremental/ServiceWrappers.h
+++ b/services/incremental/ServiceWrappers.h
@@ -84,6 +84,8 @@
             void(std::string_view root, std::string_view backingDir,
                  std::span<std::pair<std::string_view, std::string_view>> binds)>;
 
+    using FileCallback = android::base::function_ref<bool(const Control& control, FileId fileId)>;
+
     static std::string toString(FileId fileId);
 
     virtual ~IncFsWrapper() = default;
@@ -105,14 +107,14 @@
             const Control& control, std::string_view path) const = 0;
     virtual incfs::LoadingState isFileFullyLoaded(const Control& control,
                                                   std::string_view path) const = 0;
+    virtual incfs::LoadingState isFileFullyLoaded(const Control& control, FileId id) const = 0;
     virtual incfs::LoadingState isEverythingFullyLoaded(const Control& control) const = 0;
     virtual ErrorCode link(const Control& control, std::string_view from,
                            std::string_view to) const = 0;
     virtual ErrorCode unlink(const Control& control, std::string_view path) const = 0;
     virtual UniqueFd openForSpecialOps(const Control& control, FileId id) const = 0;
     virtual ErrorCode writeBlocks(std::span<const incfs::DataBlock> blocks) const = 0;
-    virtual ErrorCode reserveSpace(const Control& control, std::string_view path,
-                                   IncFsSize size) const = 0;
+    virtual ErrorCode reserveSpace(const Control& control, FileId id, IncFsSize size) const = 0;
     virtual WaitResult waitForPendingReads(
             const Control& control, std::chrono::milliseconds timeout,
             std::vector<incfs::ReadInfo>* pendingReadsBuffer) const = 0;
@@ -120,6 +122,8 @@
             const Control& control,
             const std::vector<::android::os::incremental::PerUidReadTimeouts>& perUidReadTimeouts)
             const = 0;
+    virtual ErrorCode forEachFile(const Control& control, FileCallback cb) const = 0;
+    virtual ErrorCode forEachIncompleteFile(const Control& control, FileCallback cb) const = 0;
 };
 
 class AppOpsManagerWrapper {
diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp
index 8ba7c86..ddb7784 100644
--- a/services/incremental/test/IncrementalServiceTest.cpp
+++ b/services/incremental/test/IncrementalServiceTest.cpp
@@ -379,6 +379,7 @@
                                                                    std::string_view path));
     MOCK_CONST_METHOD2(isFileFullyLoaded,
                        incfs::LoadingState(const Control& control, std::string_view path));
+    MOCK_CONST_METHOD2(isFileFullyLoaded, incfs::LoadingState(const Control& control, FileId id));
     MOCK_CONST_METHOD1(isEverythingFullyLoaded, incfs::LoadingState(const Control& control));
     MOCK_CONST_METHOD3(link,
                        ErrorCode(const Control& control, std::string_view from,
@@ -386,14 +387,15 @@
     MOCK_CONST_METHOD2(unlink, ErrorCode(const Control& control, std::string_view path));
     MOCK_CONST_METHOD2(openForSpecialOps, UniqueFd(const Control& control, FileId id));
     MOCK_CONST_METHOD1(writeBlocks, ErrorCode(std::span<const DataBlock> blocks));
-    MOCK_CONST_METHOD3(reserveSpace,
-                       ErrorCode(const Control& control, std::string_view path, IncFsSize size));
+    MOCK_CONST_METHOD3(reserveSpace, ErrorCode(const Control& control, FileId id, IncFsSize size));
     MOCK_CONST_METHOD3(waitForPendingReads,
                        WaitResult(const Control& control, std::chrono::milliseconds timeout,
                                   std::vector<incfs::ReadInfo>* pendingReadsBuffer));
     MOCK_CONST_METHOD2(setUidReadTimeouts,
                        ErrorCode(const Control& control,
                                  const std::vector<PerUidReadTimeouts>& perUidReadTimeouts));
+    MOCK_CONST_METHOD2(forEachFile, ErrorCode(const Control& control, FileCallback cb));
+    MOCK_CONST_METHOD2(forEachIncompleteFile, ErrorCode(const Control& control, FileCallback cb));
 
     MockIncFs() {
         ON_CALL(*this, listExistingMounts(_)).WillByDefault(Return());
@@ -1594,7 +1596,7 @@
     int storageId =
             mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
                                                IncrementalService::CreateOptions::CreateNew);
-    EXPECT_CALL(*mIncFs, isFileFullyLoaded(_, _))
+    EXPECT_CALL(*mIncFs, isFileFullyLoaded(_, An<std::string_view>()))
             .Times(1)
             .WillOnce(Return(incfs::LoadingState::MissingBlocks));
     ASSERT_GT((int)mIncrementalService->isFileFullyLoaded(storageId, "base.apk"), 0);
@@ -1605,7 +1607,7 @@
     int storageId =
             mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
                                                IncrementalService::CreateOptions::CreateNew);
-    EXPECT_CALL(*mIncFs, isFileFullyLoaded(_, _))
+    EXPECT_CALL(*mIncFs, isFileFullyLoaded(_, An<std::string_view>()))
             .Times(1)
             .WillOnce(Return(incfs::LoadingState(-1)));
     ASSERT_LT((int)mIncrementalService->isFileFullyLoaded(storageId, "base.apk"), 0);
@@ -1616,7 +1618,7 @@
     int storageId =
             mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
                                                IncrementalService::CreateOptions::CreateNew);
-    EXPECT_CALL(*mIncFs, isFileFullyLoaded(_, _))
+    EXPECT_CALL(*mIncFs, isFileFullyLoaded(_, An<std::string_view>()))
             .Times(1)
             .WillOnce(Return(incfs::LoadingState::Full));
     ASSERT_EQ(0, (int)mIncrementalService->isFileFullyLoaded(storageId, "base.apk"));