[incremental] native implementation of Incremental Service

The implementation of IIncrementalManager.aidl. TODO to refactor this.

Test: atest service.incremental_test
Change-Id: Ib8c8a9c0e7f0289b4bcd8961fa39746ed12b4310
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
new file mode 100644
index 0000000..c43328f
--- /dev/null
+++ b/services/incremental/IncrementalService.cpp
@@ -0,0 +1,1040 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "IncrementalService"
+
+#include "IncrementalService.h"
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <android/content/pm/IDataLoaderStatusListener.h>
+#include <android/os/IVold.h>
+#include <androidfw/ZipFileRO.h>
+#include <androidfw/ZipUtils.h>
+#include <binder/BinderService.h>
+#include <binder/ParcelFileDescriptor.h>
+#include <binder/Status.h>
+#include <sys/stat.h>
+#include <uuid/uuid.h>
+#include <zlib.h>
+
+#include <iterator>
+#include <span>
+#include <stack>
+#include <thread>
+#include <type_traits>
+
+#include "Metadata.pb.h"
+
+using namespace std::literals;
+using namespace android::content::pm;
+
+namespace android::incremental {
+
+namespace {
+
+using IncrementalFileSystemControlParcel =
+        ::android::os::incremental::IncrementalFileSystemControlParcel;
+
+struct Constants {
+    static constexpr auto backing = "backing_store"sv;
+    static constexpr auto mount = "mount"sv;
+    static constexpr auto image = "incfs.img"sv;
+    static constexpr auto storagePrefix = "st"sv;
+    static constexpr auto mountpointMdPrefix = ".mountpoint."sv;
+    static constexpr auto infoMdName = ".info"sv;
+};
+
+static const Constants& constants() {
+    static Constants c;
+    return c;
+}
+
+template <base::LogSeverity level = base::ERROR>
+bool mkdirOrLog(std::string_view name, int mode = 0770, bool allowExisting = true) {
+    auto cstr = path::c_str(name);
+    if (::mkdir(cstr, mode)) {
+        if (errno != EEXIST) {
+            PLOG(level) << "Can't create directory '" << name << '\'';
+            return false;
+        }
+        struct stat st;
+        if (::stat(cstr, &st) || !S_ISDIR(st.st_mode)) {
+            PLOG(level) << "Path exists but is not a directory: '" << name << '\'';
+            return false;
+        }
+    }
+    return true;
+}
+
+static std::string toMountKey(std::string_view path) {
+    if (path.empty()) {
+        return "@none";
+    }
+    if (path == "/"sv) {
+        return "@root";
+    }
+    if (path::isAbsolute(path)) {
+        path.remove_prefix(1);
+    }
+    std::string res(path);
+    std::replace(res.begin(), res.end(), '/', '_');
+    std::replace(res.begin(), res.end(), '@', '_');
+    return res;
+}
+
+static std::pair<std::string, std::string> makeMountDir(std::string_view incrementalDir,
+                                                        std::string_view path) {
+    auto mountKey = toMountKey(path);
+    const auto prefixSize = mountKey.size();
+    for (int counter = 0; counter < 1000;
+         mountKey.resize(prefixSize), base::StringAppendF(&mountKey, "%d", counter++)) {
+        auto mountRoot = path::join(incrementalDir, mountKey);
+        if (mkdirOrLog(mountRoot, 0770, false)) {
+            return {mountKey, mountRoot};
+        }
+    }
+    return {};
+}
+
+template <class ProtoMessage, class Control>
+static ProtoMessage parseFromIncfs(const IncFsWrapper* incfs, Control&& control,
+                                   std::string_view path) {
+    struct stat st;
+    if (::stat(path::c_str(path), &st)) {
+        return {};
+    }
+    auto md = incfs->getMetadata(control, st.st_ino);
+    ProtoMessage message;
+    return message.ParseFromArray(md.data(), md.size()) ? message : ProtoMessage{};
+}
+
+static bool isValidMountTarget(std::string_view path) {
+    return path::isAbsolute(path) && path::isEmptyDir(path).value_or(true);
+}
+
+std::string makeBindMdName() {
+    static constexpr auto uuidStringSize = 36;
+
+    uuid_t guid;
+    uuid_generate(guid);
+
+    std::string name;
+    const auto prefixSize = constants().mountpointMdPrefix.size();
+    name.reserve(prefixSize + uuidStringSize);
+
+    name = constants().mountpointMdPrefix;
+    name.resize(prefixSize + uuidStringSize);
+    uuid_unparse(guid, name.data() + prefixSize);
+
+    return name;
+}
+} // namespace
+
+IncrementalService::IncFsMount::~IncFsMount() {
+    incrementalService.mIncrementalManager->destroyDataLoader(mountId);
+    control.reset();
+    LOG(INFO) << "Unmounting and cleaning up mount " << mountId << " with root '" << root << '\'';
+    for (auto&& [target, _] : bindPoints) {
+        LOG(INFO) << "\tbind: " << target;
+        incrementalService.mVold->unmountIncFs(target);
+    }
+    LOG(INFO) << "\troot: " << root;
+    incrementalService.mVold->unmountIncFs(path::join(root, constants().mount));
+    cleanupFilesystem(root);
+}
+
+auto IncrementalService::IncFsMount::makeStorage(StorageId id) -> StorageMap::iterator {
+    metadata::Storage st;
+    st.set_id(id);
+    auto metadata = st.SerializeAsString();
+
+    std::string name;
+    for (int no = nextStorageDirNo.fetch_add(1, std::memory_order_relaxed), i = 0;
+         i < 1024 && no >= 0; no = nextStorageDirNo.fetch_add(1, std::memory_order_relaxed), ++i) {
+        name.clear();
+        base::StringAppendF(&name, "%.*s%d", int(constants().storagePrefix.size()),
+                            constants().storagePrefix.data(), no);
+        if (auto node =
+                    incrementalService.mIncFs->makeDir(control, name, INCFS_ROOT_INODE, metadata);
+            node >= 0) {
+            std::lock_guard l(lock);
+            return storages.insert_or_assign(id, Storage{std::move(name), node}).first;
+        }
+    }
+    nextStorageDirNo = 0;
+    return storages.end();
+}
+
+void IncrementalService::IncFsMount::cleanupFilesystem(std::string_view root) {
+    ::unlink(path::join(root, constants().backing, constants().image).c_str());
+    ::rmdir(path::join(root, constants().backing).c_str());
+    ::rmdir(path::join(root, constants().mount).c_str());
+    ::rmdir(path::c_str(root));
+}
+
+IncrementalService::IncrementalService(const ServiceManagerWrapper& sm, std::string_view rootDir)
+      : mVold(sm.getVoldService()),
+        mIncrementalManager(sm.getIncrementalManager()),
+        mIncFs(sm.getIncFs()),
+        mIncrementalDir(rootDir) {
+    if (!mVold) {
+        LOG(FATAL) << "Vold service is unavailable";
+    }
+    if (!mIncrementalManager) {
+        LOG(FATAL) << "IncrementalManager service is unavailable";
+    }
+    // TODO(b/136132412): check that root dir should already exist
+    // TODO(b/136132412): enable mount existing dirs after SELinux rules are merged
+    // mountExistingImages();
+}
+
+IncrementalService::~IncrementalService() = default;
+
+std::optional<std::future<void>> IncrementalService::onSystemReady() {
+    std::promise<void> threadFinished;
+    if (mSystemReady.exchange(true)) {
+        return {};
+    }
+
+    std::vector<IfsMountPtr> mounts;
+    {
+        std::lock_guard l(mLock);
+        mounts.reserve(mMounts.size());
+        for (auto&& [id, ifs] : mMounts) {
+            if (ifs->mountId == id) {
+                mounts.push_back(ifs);
+            }
+        }
+    }
+
+    std::thread([this, mounts = std::move(mounts)]() {
+        std::vector<IfsMountPtr> failedLoaderMounts;
+        for (auto&& ifs : mounts) {
+            if (prepareDataLoader(*ifs, nullptr)) {
+                LOG(INFO) << "Successfully started data loader for mount " << ifs->mountId;
+            } else {
+                LOG(WARNING) << "Failed to start data loader for mount " << ifs->mountId;
+                failedLoaderMounts.push_back(std::move(ifs));
+            }
+        }
+
+        while (!failedLoaderMounts.empty()) {
+            LOG(WARNING) << "Deleting failed mount " << failedLoaderMounts.back()->mountId;
+            deleteStorage(*failedLoaderMounts.back());
+            failedLoaderMounts.pop_back();
+        }
+        mPrepareDataLoaders.set_value_at_thread_exit();
+    }).detach();
+    return mPrepareDataLoaders.get_future();
+}
+
+auto IncrementalService::getStorageSlotLocked() -> MountMap::iterator {
+    for (;;) {
+        if (mNextId == kMaxStorageId) {
+            mNextId = 0;
+        }
+        auto id = ++mNextId;
+        auto [it, inserted] = mMounts.try_emplace(id, nullptr);
+        if (inserted) {
+            return it;
+        }
+    }
+}
+
+StorageId IncrementalService::createStorage(std::string_view mountPoint,
+                                            DataLoaderParamsParcel&& dataLoaderParams,
+                                            CreateOptions options) {
+    LOG(INFO) << "createStorage: " << mountPoint << " | " << int(options);
+    if (!path::isAbsolute(mountPoint)) {
+        LOG(ERROR) << "path is not absolute: " << mountPoint;
+        return kInvalidStorageId;
+    }
+
+    auto mountNorm = path::normalize(mountPoint);
+    {
+        const auto id = findStorageId(mountNorm);
+        if (id != kInvalidStorageId) {
+            if (options & CreateOptions::OpenExisting) {
+                LOG(INFO) << "Opened existing storage " << id;
+                return id;
+            }
+            LOG(ERROR) << "Directory " << mountPoint << " is already mounted at storage " << id;
+            return kInvalidStorageId;
+        }
+    }
+
+    if (!(options & CreateOptions::CreateNew)) {
+        LOG(ERROR) << "not requirested create new storage, and it doesn't exist: " << mountPoint;
+        return kInvalidStorageId;
+    }
+
+    if (!path::isEmptyDir(mountNorm)) {
+        LOG(ERROR) << "Mounting over existing non-empty directory is not supported: " << mountNorm;
+        return kInvalidStorageId;
+    }
+    auto [mountKey, mountRoot] = makeMountDir(mIncrementalDir, mountNorm);
+    if (mountRoot.empty()) {
+        LOG(ERROR) << "Bad mount point";
+        return kInvalidStorageId;
+    }
+    // Make sure the code removes all crap it may create while still failing.
+    auto firstCleanup = [](const std::string* ptr) { IncFsMount::cleanupFilesystem(*ptr); };
+    auto firstCleanupOnFailure =
+            std::unique_ptr<std::string, decltype(firstCleanup)>(&mountRoot, firstCleanup);
+
+    auto mountTarget = path::join(mountRoot, constants().mount);
+    if (!mkdirOrLog(path::join(mountRoot, constants().backing)) || !mkdirOrLog(mountTarget)) {
+        return kInvalidStorageId;
+    }
+
+    const auto image = path::join(mountRoot, constants().backing, constants().image);
+    IncFsMount::Control control;
+    {
+        std::lock_guard l(mMountOperationLock);
+        IncrementalFileSystemControlParcel controlParcel;
+        auto status = mVold->mountIncFs(image, mountTarget, incfs::truncate, &controlParcel);
+        if (!status.isOk()) {
+            LOG(ERROR) << "Vold::mountIncFs() failed: " << status.toString8();
+            return kInvalidStorageId;
+        }
+        if (!controlParcel.cmd || !controlParcel.log) {
+            LOG(ERROR) << "Vold::mountIncFs() returned invalid control parcel.";
+            return kInvalidStorageId;
+        }
+        control.cmdFd = controlParcel.cmd->release();
+        control.logFd = controlParcel.log->release();
+    }
+
+    std::unique_lock l(mLock);
+    const auto mountIt = getStorageSlotLocked();
+    const auto mountId = mountIt->first;
+    l.unlock();
+
+    auto ifs =
+            std::make_shared<IncFsMount>(std::move(mountRoot), mountId, std::move(control), *this);
+    // Now it's the |ifs|'s responsibility to clean up after itself, and the only cleanup we need
+    // is the removal of the |ifs|.
+    firstCleanupOnFailure.release();
+
+    auto secondCleanup = [this, &l](auto itPtr) {
+        if (!l.owns_lock()) {
+            l.lock();
+        }
+        mMounts.erase(*itPtr);
+    };
+    auto secondCleanupOnFailure =
+            std::unique_ptr<decltype(mountIt), decltype(secondCleanup)>(&mountIt, secondCleanup);
+
+    const auto storageIt = ifs->makeStorage(ifs->mountId);
+    if (storageIt == ifs->storages.end()) {
+        LOG(ERROR) << "Can't create default storage directory";
+        return kInvalidStorageId;
+    }
+
+    {
+        metadata::Mount m;
+        m.mutable_storage()->set_id(ifs->mountId);
+        m.mutable_loader()->set_package_name(dataLoaderParams.packageName);
+        m.mutable_loader()->set_arguments(dataLoaderParams.staticArgs);
+        const auto metadata = m.SerializeAsString();
+        m.mutable_loader()->release_arguments();
+        m.mutable_loader()->release_package_name();
+        if (auto err = mIncFs->makeFile(ifs->control, constants().infoMdName, INCFS_ROOT_INODE, 0,
+                                        metadata);
+            err < 0) {
+            LOG(ERROR) << "Saving mount metadata failed: " << -err;
+            return kInvalidStorageId;
+        }
+    }
+
+    const auto bk =
+            (options & CreateOptions::PermanentBind) ? BindKind::Permanent : BindKind::Temporary;
+    if (auto err = addBindMount(*ifs, storageIt->first, std::string(storageIt->second.name),
+                                std::move(mountNorm), bk, l);
+        err < 0) {
+        LOG(ERROR) << "adding bind mount failed: " << -err;
+        return kInvalidStorageId;
+    }
+
+    // Done here as well, all data structures are in good state.
+    secondCleanupOnFailure.release();
+
+    if (!prepareDataLoader(*ifs, &dataLoaderParams)) {
+        LOG(ERROR) << "prepareDataLoader() failed";
+        deleteStorageLocked(*ifs, std::move(l));
+        return kInvalidStorageId;
+    }
+
+    mountIt->second = std::move(ifs);
+    l.unlock();
+    LOG(INFO) << "created storage " << mountId;
+    return mountId;
+}
+
+StorageId IncrementalService::createLinkedStorage(std::string_view mountPoint,
+                                                  StorageId linkedStorage,
+                                                  IncrementalService::CreateOptions options) {
+    if (!isValidMountTarget(mountPoint)) {
+        LOG(ERROR) << "Mount point is invalid or missing";
+        return kInvalidStorageId;
+    }
+
+    std::unique_lock l(mLock);
+    const auto& ifs = getIfsLocked(linkedStorage);
+    if (!ifs) {
+        LOG(ERROR) << "Ifs unavailable";
+        return kInvalidStorageId;
+    }
+
+    const auto mountIt = getStorageSlotLocked();
+    const auto storageId = mountIt->first;
+    const auto storageIt = ifs->makeStorage(storageId);
+    if (storageIt == ifs->storages.end()) {
+        LOG(ERROR) << "Can't create a new storage";
+        mMounts.erase(mountIt);
+        return kInvalidStorageId;
+    }
+
+    l.unlock();
+
+    const auto bk =
+            (options & CreateOptions::PermanentBind) ? BindKind::Permanent : BindKind::Temporary;
+    if (auto err = addBindMount(*ifs, storageIt->first, std::string(storageIt->second.name),
+                                path::normalize(mountPoint), bk, l);
+        err < 0) {
+        LOG(ERROR) << "bindMount failed with error: " << err;
+        return kInvalidStorageId;
+    }
+
+    mountIt->second = ifs;
+    return storageId;
+}
+
+IncrementalService::BindPathMap::const_iterator IncrementalService::findStorageLocked(
+        std::string_view path) const {
+    auto bindPointIt = mBindsByPath.upper_bound(path);
+    if (bindPointIt == mBindsByPath.begin()) {
+        return mBindsByPath.end();
+    }
+    --bindPointIt;
+    if (!path::startsWith(path, bindPointIt->first)) {
+        return mBindsByPath.end();
+    }
+    return bindPointIt;
+}
+
+StorageId IncrementalService::findStorageId(std::string_view path) const {
+    std::lock_guard l(mLock);
+    auto it = findStorageLocked(path);
+    if (it == mBindsByPath.end()) {
+        return kInvalidStorageId;
+    }
+    return it->second->second.storage;
+}
+
+void IncrementalService::deleteStorage(StorageId storageId) {
+    const auto ifs = getIfs(storageId);
+    if (!ifs) {
+        return;
+    }
+    deleteStorage(*ifs);
+}
+
+void IncrementalService::deleteStorage(IncrementalService::IncFsMount& ifs) {
+    std::unique_lock l(ifs.lock);
+    deleteStorageLocked(ifs, std::move(l));
+}
+
+void IncrementalService::deleteStorageLocked(IncrementalService::IncFsMount& ifs,
+                                             std::unique_lock<std::mutex>&& ifsLock) {
+    const auto storages = std::move(ifs.storages);
+    // Don't move the bind points out: Ifs's dtor will use them to unmount everything.
+    const auto bindPoints = ifs.bindPoints;
+    ifsLock.unlock();
+
+    std::lock_guard l(mLock);
+    for (auto&& [id, _] : storages) {
+        if (id != ifs.mountId) {
+            mMounts.erase(id);
+        }
+    }
+    for (auto&& [path, _] : bindPoints) {
+        mBindsByPath.erase(path);
+    }
+    mMounts.erase(ifs.mountId);
+}
+
+StorageId IncrementalService::openStorage(std::string_view pathInMount) {
+    if (!path::isAbsolute(pathInMount)) {
+        return kInvalidStorageId;
+    }
+
+    return findStorageId(path::normalize(pathInMount));
+}
+
+Inode IncrementalService::nodeFor(StorageId storage, std::string_view subpath) const {
+    const auto ifs = getIfs(storage);
+    if (!ifs) {
+        return -1;
+    }
+    std::unique_lock l(ifs->lock);
+    auto storageIt = ifs->storages.find(storage);
+    if (storageIt == ifs->storages.end()) {
+        return -1;
+    }
+    if (subpath.empty() || subpath == "."sv) {
+        return storageIt->second.node;
+    }
+    auto path = path::join(ifs->root, constants().mount, storageIt->second.name, subpath);
+    l.unlock();
+    struct stat st;
+    if (::stat(path.c_str(), &st)) {
+        return -1;
+    }
+    return st.st_ino;
+}
+
+std::pair<Inode, std::string_view> IncrementalService::parentAndNameFor(
+        StorageId storage, std::string_view subpath) const {
+    auto name = path::basename(subpath);
+    if (name.empty()) {
+        return {-1, {}};
+    }
+    auto dir = path::dirname(subpath);
+    if (dir.empty() || dir == "/"sv) {
+        return {-1, {}};
+    }
+    auto inode = nodeFor(storage, dir);
+    return {inode, name};
+}
+
+IncrementalService::IfsMountPtr IncrementalService::getIfs(StorageId storage) const {
+    std::lock_guard l(mLock);
+    return getIfsLocked(storage);
+}
+
+const IncrementalService::IfsMountPtr& IncrementalService::getIfsLocked(StorageId storage) const {
+    auto it = mMounts.find(storage);
+    if (it == mMounts.end()) {
+        static const IfsMountPtr kEmpty = {};
+        return kEmpty;
+    }
+    return it->second;
+}
+
+int IncrementalService::bind(StorageId storage, std::string_view sourceSubdir,
+                             std::string_view target, BindKind kind) {
+    if (!isValidMountTarget(target)) {
+        return -EINVAL;
+    }
+
+    const auto ifs = getIfs(storage);
+    if (!ifs) {
+        return -EINVAL;
+    }
+    std::unique_lock l(ifs->lock);
+    const auto storageInfo = ifs->storages.find(storage);
+    if (storageInfo == ifs->storages.end()) {
+        return -EINVAL;
+    }
+    auto source = path::join(storageInfo->second.name, sourceSubdir);
+    l.unlock();
+    std::unique_lock l2(mLock, std::defer_lock);
+    return addBindMount(*ifs, storage, std::move(source), path::normalize(target), kind, l2);
+}
+
+int IncrementalService::unbind(StorageId storage, std::string_view target) {
+    if (!path::isAbsolute(target)) {
+        return -EINVAL;
+    }
+
+    LOG(INFO) << "Removing bind point " << target;
+
+    // Here we should only look up by the exact target, not by a subdirectory of any existing mount,
+    // otherwise there's a chance to unmount something completely unrelated
+    const auto norm = path::normalize(target);
+    std::unique_lock l(mLock);
+    const auto storageIt = mBindsByPath.find(norm);
+    if (storageIt == mBindsByPath.end() || storageIt->second->second.storage != storage) {
+        return -EINVAL;
+    }
+    const auto bindIt = storageIt->second;
+    const auto storageId = bindIt->second.storage;
+    const auto ifs = getIfsLocked(storageId);
+    if (!ifs) {
+        LOG(ERROR) << "Internal error: storageId " << storageId << " for bound path " << target
+                   << " is missing";
+        return -EFAULT;
+    }
+    mBindsByPath.erase(storageIt);
+    l.unlock();
+
+    mVold->unmountIncFs(bindIt->first);
+    std::unique_lock l2(ifs->lock);
+    if (ifs->bindPoints.size() <= 1) {
+        ifs->bindPoints.clear();
+        deleteStorageLocked(*ifs, std::move(l2));
+    } else {
+        const std::string savedFile = std::move(bindIt->second.savedFilename);
+        ifs->bindPoints.erase(bindIt);
+        l2.unlock();
+        if (!savedFile.empty()) {
+            mIncFs->unlink(ifs->control, INCFS_ROOT_INODE, savedFile);
+        }
+    }
+    return 0;
+}
+
+Inode IncrementalService::makeFile(StorageId storageId, std::string_view pathUnderStorage,
+                                   long size, std::string_view metadata,
+                                   std::string_view signature) {
+    (void)signature;
+    auto [parentInode, name] = parentAndNameFor(storageId, pathUnderStorage);
+    if (parentInode < 0) {
+        return -EINVAL;
+    }
+    if (auto ifs = getIfs(storageId)) {
+        auto inode = mIncFs->makeFile(ifs->control, name, parentInode, size, metadata);
+        if (inode < 0) {
+            return inode;
+        }
+        auto metadataBytes = std::vector<uint8_t>();
+        if (metadata.data() != nullptr && metadata.size() > 0) {
+            metadataBytes.insert(metadataBytes.end(), &metadata.data()[0],
+                                 &metadata.data()[metadata.size()]);
+        }
+        mIncrementalManager->newFileForDataLoader(ifs->mountId, inode, metadataBytes);
+        return inode;
+    }
+    return -EINVAL;
+}
+
+Inode IncrementalService::makeDir(StorageId storageId, std::string_view pathUnderStorage,
+                                  std::string_view metadata) {
+    auto [parentInode, name] = parentAndNameFor(storageId, pathUnderStorage);
+    if (parentInode < 0) {
+        return -EINVAL;
+    }
+    if (auto ifs = getIfs(storageId)) {
+        return mIncFs->makeDir(ifs->control, name, parentInode, metadata);
+    }
+    return -EINVAL;
+}
+
+Inode IncrementalService::makeDirs(StorageId storageId, std::string_view pathUnderStorage,
+                                   std::string_view metadata) {
+    const auto ifs = getIfs(storageId);
+    if (!ifs) {
+        return -EINVAL;
+    }
+    std::string_view parentDir(pathUnderStorage);
+    auto p = parentAndNameFor(storageId, pathUnderStorage);
+    std::stack<std::string> pathsToCreate;
+    while (p.first < 0) {
+        parentDir = path::dirname(parentDir);
+        pathsToCreate.emplace(parentDir);
+        p = parentAndNameFor(storageId, parentDir);
+    }
+    Inode inode;
+    while (!pathsToCreate.empty()) {
+        p = parentAndNameFor(storageId, pathsToCreate.top());
+        pathsToCreate.pop();
+        inode = mIncFs->makeDir(ifs->control, p.second, p.first, metadata);
+        if (inode < 0) {
+            return inode;
+        }
+    }
+    return mIncFs->makeDir(ifs->control, path::basename(pathUnderStorage), inode, metadata);
+}
+
+int IncrementalService::link(StorageId storage, Inode item, Inode newParent,
+                             std::string_view newName) {
+    if (auto ifs = getIfs(storage)) {
+        return mIncFs->link(ifs->control, item, newParent, newName);
+    }
+    return -EINVAL;
+}
+
+int IncrementalService::unlink(StorageId storage, Inode parent, std::string_view name) {
+    if (auto ifs = getIfs(storage)) {
+        return mIncFs->unlink(ifs->control, parent, name);
+    }
+    return -EINVAL;
+}
+
+int IncrementalService::addBindMount(IncFsMount& ifs, StorageId storage, std::string&& sourceSubdir,
+                                     std::string&& target, BindKind kind,
+                                     std::unique_lock<std::mutex>& mainLock) {
+    if (!isValidMountTarget(target)) {
+        return -EINVAL;
+    }
+
+    std::string mdFileName;
+    if (kind != BindKind::Temporary) {
+        metadata::BindPoint bp;
+        bp.set_storage_id(storage);
+        bp.set_allocated_dest_path(&target);
+        bp.set_allocated_source_subdir(&sourceSubdir);
+        const auto metadata = bp.SerializeAsString();
+        bp.release_source_subdir();
+        bp.release_dest_path();
+        mdFileName = makeBindMdName();
+        auto node = mIncFs->makeFile(ifs.control, mdFileName, INCFS_ROOT_INODE, 0, metadata);
+        if (node < 0) {
+            return int(node);
+        }
+    }
+
+    return addBindMountWithMd(ifs, storage, std::move(mdFileName), std::move(sourceSubdir),
+                              std::move(target), kind, mainLock);
+}
+
+int IncrementalService::addBindMountWithMd(IncrementalService::IncFsMount& ifs, StorageId storage,
+                                           std::string&& metadataName, std::string&& sourceSubdir,
+                                           std::string&& target, BindKind kind,
+                                           std::unique_lock<std::mutex>& mainLock) {
+    LOG(INFO) << "Adding bind mount: " << sourceSubdir << " -> " << target;
+    {
+        auto path = path::join(ifs.root, constants().mount, sourceSubdir);
+        std::lock_guard l(mMountOperationLock);
+        const auto status = mVold->bindMount(path, target);
+        if (!status.isOk()) {
+            LOG(ERROR) << "Calling Vold::bindMount() failed: " << status.toString8();
+            return status.exceptionCode() == binder::Status::EX_SERVICE_SPECIFIC
+                    ? status.serviceSpecificErrorCode() > 0 ? -status.serviceSpecificErrorCode()
+                                                            : status.serviceSpecificErrorCode() == 0
+                                    ? -EFAULT
+                                    : status.serviceSpecificErrorCode()
+                    : -EIO;
+        }
+    }
+
+    if (!mainLock.owns_lock()) {
+        mainLock.lock();
+    }
+    std::lock_guard l(ifs.lock);
+    const auto [it, _] =
+            ifs.bindPoints.insert_or_assign(target,
+                                            IncFsMount::Bind{storage, std::move(metadataName),
+                                                             std::move(sourceSubdir), kind});
+    mBindsByPath[std::move(target)] = it;
+    return 0;
+}
+
+RawMetadata IncrementalService::getMetadata(StorageId storage, Inode node) const {
+    const auto ifs = getIfs(storage);
+    if (!ifs) {
+        return {};
+    }
+    return mIncFs->getMetadata(ifs->control, node);
+}
+
+std::vector<std::string> IncrementalService::listFiles(StorageId storage) const {
+    const auto ifs = getIfs(storage);
+    if (!ifs) {
+        return {};
+    }
+
+    std::unique_lock l(ifs->lock);
+    auto subdirIt = ifs->storages.find(storage);
+    if (subdirIt == ifs->storages.end()) {
+        return {};
+    }
+    auto dir = path::join(ifs->root, constants().mount, subdirIt->second.name);
+    l.unlock();
+
+    const auto prefixSize = dir.size() + 1;
+    std::vector<std::string> todoDirs{std::move(dir)};
+    std::vector<std::string> result;
+    do {
+        auto currDir = std::move(todoDirs.back());
+        todoDirs.pop_back();
+
+        auto d =
+                std::unique_ptr<DIR, decltype(&::closedir)>(::opendir(currDir.c_str()), ::closedir);
+        while (auto e = ::readdir(d.get())) {
+            if (e->d_type == DT_REG) {
+                result.emplace_back(
+                        path::join(std::string_view(currDir).substr(prefixSize), e->d_name));
+                continue;
+            }
+            if (e->d_type == DT_DIR) {
+                if (e->d_name == "."sv || e->d_name == ".."sv) {
+                    continue;
+                }
+                todoDirs.emplace_back(path::join(currDir, e->d_name));
+                continue;
+            }
+        }
+    } while (!todoDirs.empty());
+    return result;
+}
+
+bool IncrementalService::startLoading(StorageId storage) const {
+    const auto ifs = getIfs(storage);
+    if (!ifs) {
+        return false;
+    }
+    bool started = false;
+    std::unique_lock l(ifs->lock);
+    if (ifs->dataLoaderStatus != IDataLoaderStatusListener::DATA_LOADER_READY) {
+        if (ifs->dataLoaderReady.wait_for(l, Seconds(5)) == std::cv_status::timeout) {
+            LOG(ERROR) << "Timeout waiting for data loader to be ready";
+            return false;
+        }
+    }
+    auto status = mIncrementalManager->startDataLoader(ifs->mountId, &started);
+    if (!status.isOk()) {
+        return false;
+    }
+    return started;
+}
+
+void IncrementalService::mountExistingImages() {
+    auto d = std::unique_ptr<DIR, decltype(&::closedir)>(::opendir(mIncrementalDir.c_str()),
+                                                         ::closedir);
+    while (auto e = ::readdir(d.get())) {
+        if (e->d_type != DT_DIR) {
+            continue;
+        }
+        if (e->d_name == "."sv || e->d_name == ".."sv) {
+            continue;
+        }
+        auto root = path::join(mIncrementalDir, e->d_name);
+        if (!mountExistingImage(root, e->d_name)) {
+            IncFsMount::cleanupFilesystem(root);
+        }
+    }
+}
+
+bool IncrementalService::mountExistingImage(std::string_view root, std::string_view key) {
+    LOG(INFO) << "Trying to mount: " << key;
+
+    auto mountTarget = path::join(root, constants().mount);
+    const auto image = path::join(root, constants().backing, constants().image);
+
+    IncFsMount::Control control;
+    IncrementalFileSystemControlParcel controlParcel;
+    auto status = mVold->mountIncFs(image, mountTarget, 0, &controlParcel);
+    if (!status.isOk()) {
+        LOG(ERROR) << "Vold::mountIncFs() failed: " << status.toString8();
+        return false;
+    }
+    if (controlParcel.cmd) {
+        control.cmdFd = controlParcel.cmd->release();
+    }
+    if (controlParcel.log) {
+        control.logFd = controlParcel.log->release();
+    }
+
+    auto ifs = std::make_shared<IncFsMount>(std::string(root), -1, std::move(control), *this);
+
+    auto m = parseFromIncfs<metadata::Mount>(mIncFs.get(), ifs->control,
+                                             path::join(mountTarget, constants().infoMdName));
+    if (!m.has_loader() || !m.has_storage()) {
+        LOG(ERROR) << "Bad mount metadata in mount at " << root;
+        return false;
+    }
+
+    ifs->mountId = m.storage().id();
+    mNextId = std::max(mNextId, ifs->mountId + 1);
+
+    std::vector<std::pair<std::string, metadata::BindPoint>> bindPoints;
+    auto d = std::unique_ptr<DIR, decltype(&::closedir)>(::opendir(path::c_str(mountTarget)),
+                                                         ::closedir);
+    while (auto e = ::readdir(d.get())) {
+        if (e->d_type == DT_REG) {
+            auto name = std::string_view(e->d_name);
+            if (name.starts_with(constants().mountpointMdPrefix)) {
+                bindPoints.emplace_back(name,
+                                        parseFromIncfs<metadata::BindPoint>(mIncFs.get(),
+                                                                            ifs->control,
+                                                                            path::join(mountTarget,
+                                                                                       name)));
+                if (bindPoints.back().second.dest_path().empty() ||
+                    bindPoints.back().second.source_subdir().empty()) {
+                    bindPoints.pop_back();
+                    mIncFs->unlink(ifs->control, INCFS_ROOT_INODE, name);
+                }
+            }
+        } else if (e->d_type == DT_DIR) {
+            if (e->d_name == "."sv || e->d_name == ".."sv) {
+                continue;
+            }
+            auto name = std::string_view(e->d_name);
+            if (name.starts_with(constants().storagePrefix)) {
+                auto md = parseFromIncfs<metadata::Storage>(mIncFs.get(), ifs->control,
+                                                            path::join(mountTarget, name));
+                auto [_, inserted] = mMounts.try_emplace(md.id(), ifs);
+                if (!inserted) {
+                    LOG(WARNING) << "Ignoring storage with duplicate id " << md.id()
+                                 << " for mount " << root;
+                    continue;
+                }
+                ifs->storages.insert_or_assign(md.id(),
+                                               IncFsMount::Storage{std::string(name),
+                                                                   Inode(e->d_ino)});
+                mNextId = std::max(mNextId, md.id() + 1);
+            }
+        }
+    }
+
+    if (ifs->storages.empty()) {
+        LOG(WARNING) << "No valid storages in mount " << root;
+        return false;
+    }
+
+    int bindCount = 0;
+    for (auto&& bp : bindPoints) {
+        std::unique_lock l(mLock, std::defer_lock);
+        bindCount += !addBindMountWithMd(*ifs, bp.second.storage_id(), std::move(bp.first),
+                                         std::move(*bp.second.mutable_source_subdir()),
+                                         std::move(*bp.second.mutable_dest_path()),
+                                         BindKind::Permanent, l);
+    }
+
+    if (bindCount == 0) {
+        LOG(WARNING) << "No valid bind points for mount " << root;
+        deleteStorage(*ifs);
+        return false;
+    }
+
+    DataLoaderParamsParcel dlParams;
+    dlParams.packageName = std::move(*m.mutable_loader()->mutable_package_name());
+    dlParams.staticArgs = std::move(*m.mutable_loader()->mutable_arguments());
+    if (!prepareDataLoader(*ifs, &dlParams)) {
+        deleteStorage(*ifs);
+        return false;
+    }
+
+    mMounts[ifs->mountId] = std::move(ifs);
+    return true;
+}
+
+bool IncrementalService::prepareDataLoader(IncrementalService::IncFsMount& ifs,
+                                           DataLoaderParamsParcel* params) {
+    if (!mSystemReady.load(std::memory_order_relaxed)) {
+        std::unique_lock l(ifs.lock);
+        if (params) {
+            if (ifs.savedDataLoaderParams) {
+                LOG(WARNING) << "Trying to pass second set of data loader parameters, ignored it";
+            } else {
+                ifs.savedDataLoaderParams = std::move(*params);
+            }
+        } else {
+            if (!ifs.savedDataLoaderParams) {
+                LOG(ERROR) << "Mount " << ifs.mountId
+                           << " is broken: no data loader params (system is not ready yet)";
+                return false;
+            }
+        }
+        return true; // eventually...
+    }
+    if (base::GetBoolProperty("incremental.skip_loader", false)) {
+        LOG(INFO) << "Skipped data loader because of incremental.skip_loader property";
+        std::unique_lock l(ifs.lock);
+        ifs.savedDataLoaderParams.reset();
+        return true;
+    }
+
+    std::unique_lock l(ifs.lock);
+    if (ifs.dataLoaderStatus == IDataLoaderStatusListener::DATA_LOADER_READY) {
+        LOG(INFO) << "Skipped data loader preparation because it already exists";
+        return true;
+    }
+
+    auto* dlp = params ? params
+                       : ifs.savedDataLoaderParams ? &ifs.savedDataLoaderParams.value() : nullptr;
+    if (!dlp) {
+        LOG(ERROR) << "Mount " << ifs.mountId << " is broken: no data loader params";
+        return false;
+    }
+    FileSystemControlParcel fsControlParcel;
+    fsControlParcel.incremental = std::make_unique<IncrementalFileSystemControlParcel>();
+    fsControlParcel.incremental->cmd =
+            std::make_unique<os::ParcelFileDescriptor>(base::unique_fd(::dup(ifs.control.cmdFd)));
+    fsControlParcel.incremental->log =
+            std::make_unique<os::ParcelFileDescriptor>(base::unique_fd(::dup(ifs.control.logFd)));
+    sp<IncrementalDataLoaderListener> listener = new IncrementalDataLoaderListener(*this);
+    bool created = false;
+    auto status = mIncrementalManager->prepareDataLoader(ifs.mountId, fsControlParcel, *dlp,
+                                                         listener, &created);
+    if (!status.isOk() || !created) {
+        LOG(ERROR) << "Failed to create a data loader for mount " << ifs.mountId;
+        return false;
+    }
+    ifs.savedDataLoaderParams.reset();
+    return true;
+}
+
+binder::Status IncrementalService::IncrementalDataLoaderListener::onStatusChanged(MountId mountId,
+                                                                                  int newStatus) {
+    std::unique_lock l(incrementalService.mLock);
+    const auto& ifs = incrementalService.getIfsLocked(mountId);
+    if (!ifs) {
+        LOG(WARNING) << "Received data loader status " << int(newStatus) << " for unknown mount "
+                     << mountId;
+        return binder::Status::ok();
+    }
+    ifs->dataLoaderStatus = newStatus;
+    switch (newStatus) {
+        case IDataLoaderStatusListener::DATA_LOADER_NO_CONNECTION: {
+            auto now = Clock::now();
+            if (ifs->connectionLostTime.time_since_epoch().count() == 0) {
+                ifs->connectionLostTime = now;
+                break;
+            }
+            auto duration =
+                    std::chrono::duration_cast<Seconds>(now - ifs->connectionLostTime).count();
+            if (duration >= 10) {
+                incrementalService.mIncrementalManager->showHealthBlockedUI(mountId);
+            }
+            break;
+        }
+        case IDataLoaderStatusListener::DATA_LOADER_READY: {
+            ifs->dataLoaderReady.notify_one();
+            break;
+        }
+        case IDataLoaderStatusListener::DATA_LOADER_NOT_READY: {
+            ifs->dataLoaderStatus = IDataLoaderStatusListener::DATA_LOADER_STOPPED;
+            incrementalService.deleteStorageLocked(*ifs, std::move(l));
+            break;
+        }
+        case IDataLoaderStatusListener::DATA_LOADER_RUNNING: {
+            break;
+        }
+        case IDataLoaderStatusListener::DATA_LOADER_CONNECTION_OK: {
+            ifs->dataLoaderStatus = IDataLoaderStatusListener::DATA_LOADER_RUNNING;
+            break;
+        }
+        case IDataLoaderStatusListener::DATA_LOADER_STOPPED: {
+            break;
+        }
+        default: {
+            LOG(WARNING) << "Unknown data loader status: " << newStatus
+                         << " for mount: " << mountId;
+            break;
+        }
+    }
+
+    return binder::Status::ok();
+}
+
+} // namespace android::incremental