[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/core/java/com/android/server/incremental/IncrementalManagerService.java b/services/core/java/com/android/server/incremental/IncrementalManagerService.java
index 789551b..3049522 100644
--- a/services/core/java/com/android/server/incremental/IncrementalManagerService.java
+++ b/services/core/java/com/android/server/incremental/IncrementalManagerService.java
@@ -67,13 +67,14 @@
         mDataLoaderManager = mContext.getSystemService(DataLoaderManager.class);
         ServiceManager.addService(BINDER_SERVICE_NAME, this);
         // Starts and register IIncrementalManagerNative service
-        // TODO(b/136132412): add jni implementation
+        mNativeInstance = nativeStartService();
     }
+
     /**
      * Notifies native IIncrementalManager service that system is ready.
      */
     public void systemReady() {
-        // TODO(b/136132412): add jni implementation
+        nativeSystemReady(mNativeInstance);
     }
 
     /**
@@ -152,4 +153,8 @@
         (new IncrementalManagerShellCommand(mContext)).exec(
                 this, in, out, err, args, callback, resultReceiver);
     }
+
+    private static native long nativeStartService();
+
+    private static native void nativeSystemReady(long nativeInstance);
 }
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index a34b7fd..fee29db 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -52,6 +52,7 @@
         "com_android_server_GraphicsStatsService.cpp",
         "com_android_server_am_AppCompactor.cpp",
         "com_android_server_am_LowMemDetector.cpp",
+        "com_android_server_incremental_IncrementalManagerService.cpp",
         "onload.cpp",
         ":lib_networkStatsFactory_native",
     ],
@@ -145,6 +146,7 @@
         "[email protected]",
         "[email protected]",
         "[email protected]",
+        "service.incremental",
         "suspend_control_aidl_interface-cpp",
         "vintf-vibrator-cpp",
     ],
diff --git a/services/core/jni/com_android_server_incremental_IncrementalManagerService.cpp b/services/core/jni/com_android_server_incremental_IncrementalManagerService.cpp
new file mode 100644
index 0000000..5e255f4
--- /dev/null
+++ b/services/core/jni/com_android_server_incremental_IncrementalManagerService.cpp
@@ -0,0 +1,47 @@
+/*
+ * 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 "incremental_manager_service-jni"
+
+#include "incremental_service.h"
+#include "jni.h"
+
+#include <memory>
+#include <nativehelper/JNIHelp.h>
+
+
+namespace android {
+
+static jlong nativeStartService(JNIEnv* env, jclass klass, jobject self) {
+    return Incremental_IncrementalService_Start();
+}
+
+static void nativeSystemReady(JNIEnv* env, jclass klass, jlong self) {
+    Incremental_IncrementalService_OnSystemReady(self);
+}
+
+static const JNINativeMethod method_table[] = {
+        {"nativeStartService", "()J", (void*)nativeStartService},
+        {"nativeSystemReady", "(J)V", (void*)nativeSystemReady},
+};
+
+int register_android_server_incremental_IncrementalManagerService(JNIEnv* env) {
+    return jniRegisterNativeMethods(env,
+        "com/android/server/incremental/IncrementalManagerService",
+        method_table, std::size(method_table));
+}
+
+} // namespace android
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 165edf1..c0a6e4e 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -58,6 +58,7 @@
 int register_android_server_am_LowMemDetector(JNIEnv* env);
 int register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(
         JNIEnv* env);
+int register_android_server_incremental_IncrementalManagerService(JNIEnv* env);
 };
 
 using namespace android;
@@ -109,5 +110,6 @@
     register_android_server_am_LowMemDetector(env);
     register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(
             env);
+    register_android_server_incremental_IncrementalManagerService(env);
     return JNI_VERSION_1_4;
 }
diff --git a/services/incremental/Android.bp b/services/incremental/Android.bp
new file mode 100644
index 0000000..2661925
--- /dev/null
+++ b/services/incremental/Android.bp
@@ -0,0 +1,110 @@
+// Copyright 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.
+
+cc_defaults {
+    name: "service.incremental-proto-defaults",
+
+    cpp_std: "c++2a",
+    proto: {
+        type: "lite",
+    },
+}
+
+cc_defaults {
+    name: "service.incremental-defaults",
+    defaults: ["service.incremental-proto-defaults"],
+    local_include_dirs: ["include/"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wextra",
+        "-Wno-unused-parameter",
+    ],
+
+    static_libs: [
+        "libbase",
+        "libext2_uuid",
+        "libdataloader_aidl-cpp",
+        "libincremental_aidl-cpp",
+        "libincremental_manager_aidl-cpp",
+        "libnativehelper",
+        "libprotobuf-cpp-lite",
+        "service.incremental.proto",
+        "libutils",
+        "libvold_binder",
+    ],
+    shared_libs: [
+        "libandroidfw",
+        "libbinder",
+        "libincfs",
+        "liblog",
+        "libz",
+        "libziparchive",
+    ],
+}
+
+filegroup {
+    name: "service.incremental_srcs",
+    srcs: [
+        "incremental_service.c",
+        "IncrementalService.cpp",
+        "BinderIncrementalService.cpp",
+        "path.cpp",
+        "ServiceWrappers.cpp",
+    ],
+}
+
+cc_library {
+    name: "service.incremental",
+    defaults: [
+        "service.incremental-defaults",
+        "linux_bionic_supported",
+    ],
+
+    export_include_dirs: ["include/",],
+    srcs: [
+        ":service.incremental_srcs",
+    ],
+}
+
+cc_library_headers {
+    name: "service.incremental_headers",
+    export_include_dirs: ["include/",],
+}
+
+cc_library_static {
+    name: "service.incremental.proto",
+    defaults: ["service.incremental-proto-defaults"],
+    proto: {
+        export_proto_headers: true,
+    },
+
+    srcs: [
+        "Metadata.proto",
+    ],
+}
+
+cc_test {
+    name: "service.incremental_test",
+    defaults: ["service.incremental-defaults"],
+    test_suites: ["device-tests"],
+    srcs: [
+        ":service.incremental_srcs",
+        "test/IncrementalServiceTest.cpp",
+        "test/path_test.cpp",
+    ],
+    static_libs: [
+        "libgmock",
+    ]
+}
diff --git a/services/incremental/BinderIncrementalService.cpp b/services/incremental/BinderIncrementalService.cpp
new file mode 100644
index 0000000..bb26c1f
--- /dev/null
+++ b/services/incremental/BinderIncrementalService.cpp
@@ -0,0 +1,230 @@
+/*
+ * 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.
+ */
+
+#include "BinderIncrementalService.h"
+
+#include <binder/IResultReceiver.h>
+#include <incfs.h>
+
+#include "ServiceWrappers.h"
+#include "jni.h"
+#include "nativehelper/JNIHelp.h"
+#include "path.h"
+
+using namespace std::literals;
+using namespace android::incremental;
+
+namespace android::os::incremental {
+
+static constexpr auto kAndroidDataEnv = "ANDROID_DATA"sv;
+static constexpr auto kDataDir = "/data"sv;
+static constexpr auto kIncrementalSubDir = "incremental"sv;
+
+static std::string getIncrementalDir() {
+    const char* dataDir = getenv(kAndroidDataEnv.data());
+    if (!dataDir || !*dataDir) {
+        dataDir = kDataDir.data();
+    }
+    return path::normalize(path::join(dataDir, kIncrementalSubDir));
+}
+
+static bool incFsEnabled() {
+    // TODO(b/136132412): use vold to check /sys/fs/incfs/version (per selinux compliance)
+    return incfs::enabled();
+}
+
+static bool incFsVersionValid(const sp<IVold>& vold) {
+    int version = -1;
+    auto status = vold->incFsVersion(&version);
+    if (!status.isOk() || version <= 0) {
+        return false;
+    }
+    return true;
+}
+
+BinderIncrementalService::BinderIncrementalService(const sp<IServiceManager>& sm)
+      : mImpl(RealServiceManager(sm), getIncrementalDir()) {}
+
+BinderIncrementalService* BinderIncrementalService::start() {
+    if (!incFsEnabled()) {
+        return nullptr;
+    }
+
+    IPCThreadState::self()->disableBackgroundScheduling(true);
+    sp<IServiceManager> sm(defaultServiceManager());
+    if (!sm) {
+        return nullptr;
+    }
+
+    sp<IBinder> voldBinder(sm->getService(String16("vold")));
+    if (voldBinder == nullptr) {
+        return nullptr;
+    }
+    sp<IVold> vold = interface_cast<IVold>(voldBinder);
+    if (!incFsVersionValid(vold)) {
+        return nullptr;
+    }
+
+    sp<BinderIncrementalService> self(new BinderIncrementalService(sm));
+    status_t ret = sm->addService(String16{getServiceName()}, self);
+    if (ret != android::OK) {
+        return nullptr;
+    }
+    sp<ProcessState> ps(ProcessState::self());
+    ps->startThreadPool();
+    ps->giveThreadPoolName();
+    return self.get();
+}
+
+status_t BinderIncrementalService::dump(int fd, const Vector<String16>& args) {
+    return OK;
+}
+
+void BinderIncrementalService::onSystemReady() {
+    mImpl.onSystemReady();
+}
+
+static binder::Status ok() {
+    return binder::Status::ok();
+}
+
+binder::Status BinderIncrementalService::openStorage(const std::string& path,
+                                                     int32_t* _aidl_return) {
+    *_aidl_return = mImpl.openStorage(path);
+    return ok();
+}
+
+binder::Status BinderIncrementalService::createStorage(
+        const std::string& path, const DataLoaderParamsParcel& params,
+        int32_t createMode, int32_t* _aidl_return) {
+    *_aidl_return =
+            mImpl.createStorage(path, const_cast<DataLoaderParamsParcel&&>(params),
+                                android::incremental::IncrementalService::CreateOptions(
+                                        createMode));
+    return ok();
+}
+
+binder::Status BinderIncrementalService::createLinkedStorage(const std::string& path,
+                                                             int32_t otherStorageId,
+                                                             int32_t createMode,
+                                                             int32_t* _aidl_return) {
+    *_aidl_return =
+            mImpl.createLinkedStorage(path, otherStorageId,
+                                      android::incremental::IncrementalService::CreateOptions(
+                                              createMode));
+    return ok();
+}
+
+binder::Status BinderIncrementalService::makeBindMount(int32_t storageId,
+                                                       const std::string& pathUnderStorage,
+                                                       const std::string& targetFullPath,
+                                                       int32_t bindType, int32_t* _aidl_return) {
+    *_aidl_return = mImpl.bind(storageId, pathUnderStorage, targetFullPath,
+                               android::incremental::IncrementalService::BindKind(bindType));
+    return ok();
+}
+
+binder::Status BinderIncrementalService::deleteBindMount(int32_t storageId,
+                                                         const std::string& targetFullPath,
+                                                         int32_t* _aidl_return) {
+    *_aidl_return = mImpl.unbind(storageId, targetFullPath);
+    return ok();
+}
+
+binder::Status BinderIncrementalService::deleteStorage(int32_t storageId) {
+    mImpl.deleteStorage(storageId);
+    return ok();
+}
+
+binder::Status BinderIncrementalService::makeDirectory(int32_t storageId,
+                                                       const std::string& pathUnderStorage,
+                                                       int32_t* _aidl_return) {
+    auto inode = mImpl.makeDir(storageId, pathUnderStorage);
+    *_aidl_return = inode < 0 ? inode : 0;
+    return ok();
+}
+
+binder::Status BinderIncrementalService::makeDirectories(int32_t storageId,
+                                                         const std::string& pathUnderStorage,
+                                                         int32_t* _aidl_return) {
+    auto inode = mImpl.makeDirs(storageId, pathUnderStorage);
+    *_aidl_return = inode < 0 ? inode : 0;
+    return ok();
+}
+
+binder::Status BinderIncrementalService::makeFile(int32_t storageId,
+                                                  const std::string& pathUnderStorage, int64_t size,
+                                                  const std::vector<uint8_t>& metadata,
+                                                  int32_t* _aidl_return) {
+    auto inode = mImpl.makeFile(storageId, pathUnderStorage, size,
+                                {(const char*)metadata.data(), metadata.size()}, {});
+    *_aidl_return = inode < 0 ? inode : 0;
+    return ok();
+}
+binder::Status BinderIncrementalService::makeFileFromRange(
+        int32_t storageId, const std::string& pathUnderStorage,
+        const std::string& sourcePathUnderStorage, int64_t start, int64_t end,
+        int32_t* _aidl_return) {
+    // TODO(b/136132412): implement this
+    *_aidl_return = -1;
+    return ok();
+}
+binder::Status BinderIncrementalService::makeLink(int32_t sourceStorageId,
+                                                  const std::string& relativeSourcePath,
+                                                  int32_t destStorageId,
+                                                  const std::string& relativeDestPath,
+                                                  int32_t* _aidl_return) {
+    auto sourceInode = mImpl.nodeFor(sourceStorageId, relativeSourcePath);
+    auto [targetParentInode, name] = mImpl.parentAndNameFor(destStorageId, relativeDestPath);
+    *_aidl_return = mImpl.link(sourceStorageId, sourceInode, targetParentInode, name);
+    return ok();
+}
+binder::Status BinderIncrementalService::unlink(int32_t storageId,
+                                                const std::string& pathUnderStorage,
+                                                int32_t* _aidl_return) {
+    auto [parentNode, name] = mImpl.parentAndNameFor(storageId, pathUnderStorage);
+    *_aidl_return = mImpl.unlink(storageId, parentNode, name);
+    return ok();
+}
+binder::Status BinderIncrementalService::isFileRangeLoaded(int32_t storageId,
+                                                           const std::string& relativePath,
+                                                           int64_t start, int64_t end,
+                                                           bool* _aidl_return) {
+    *_aidl_return = false;
+    return ok();
+}
+binder::Status BinderIncrementalService::getFileMetadata(int32_t storageId,
+                                                         const std::string& relativePath,
+                                                         std::vector<uint8_t>* _aidl_return) {
+    auto inode = mImpl.nodeFor(storageId, relativePath);
+    auto metadata = mImpl.getMetadata(storageId, inode);
+    _aidl_return->assign(metadata.begin(), metadata.end());
+    return ok();
+}
+binder::Status BinderIncrementalService::startLoading(int32_t storageId, bool* _aidl_return) {
+    *_aidl_return = mImpl.startLoading(storageId);
+    return ok();
+}
+} // namespace android::os::incremental
+
+jlong Incremental_IncrementalService_Start() {
+    return (jlong)android::os::incremental::BinderIncrementalService::start();
+}
+void Incremental_IncrementalService_OnSystemReady(jlong self) {
+    if (self) {
+        ((android::os::incremental::BinderIncrementalService*)self)->onSystemReady();
+    }
+}
diff --git a/services/incremental/BinderIncrementalService.h b/services/incremental/BinderIncrementalService.h
new file mode 100644
index 0000000..37c9661d
--- /dev/null
+++ b/services/incremental/BinderIncrementalService.h
@@ -0,0 +1,77 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <binder/BinderService.h>
+#include <binder/IServiceManager.h>
+
+#include "IncrementalService.h"
+#include "android/os/incremental/BnIncrementalManagerNative.h"
+#include "incremental_service.h"
+
+namespace android::os::incremental {
+
+class BinderIncrementalService : public BnIncrementalManagerNative,
+                                 public BinderService<BinderIncrementalService> {
+public:
+    BinderIncrementalService(const sp<IServiceManager> &sm);
+
+    static BinderIncrementalService *start();
+    static const char16_t *getServiceName() { return u"incremental_service"; }
+    status_t dump(int fd, const Vector<String16> &args) final;
+
+    void onSystemReady();
+    void onInvalidStorage(int mountId);
+
+    binder::Status openStorage(const std::string &path, int32_t *_aidl_return) final;
+    binder::Status createStorage(
+            const std::string &path,
+            const ::android::content::pm::DataLoaderParamsParcel &params,
+            int32_t createMode, int32_t *_aidl_return) final;
+    binder::Status createLinkedStorage(const std::string &path, int32_t otherStorageId,
+                                       int32_t createMode, int32_t *_aidl_return) final;
+    binder::Status makeBindMount(int32_t storageId, const std::string &pathUnderStorage,
+                                 const std::string &targetFullPath, int32_t bindType,
+                                 int32_t *_aidl_return) final;
+    binder::Status deleteBindMount(int32_t storageId, const std::string &targetFullPath,
+                                   int32_t *_aidl_return) final;
+    binder::Status deleteStorage(int32_t storageId) final;
+    binder::Status makeDirectory(int32_t storageId, const std::string &pathUnderStorage,
+                                 int32_t *_aidl_return) final;
+    binder::Status makeDirectories(int32_t storageId, const std::string &pathUnderStorage,
+                                   int32_t *_aidl_return) final;
+    binder::Status makeFile(int32_t storageId, const std::string &pathUnderStorage, int64_t size,
+                            const std::vector<uint8_t> &metadata, int32_t *_aidl_return) final;
+    binder::Status makeFileFromRange(int32_t storageId, const std::string &pathUnderStorage,
+                                     const std::string &sourcePathUnderStorage, int64_t start,
+                                     int64_t end, int32_t *_aidl_return);
+    binder::Status makeLink(int32_t sourceStorageId, const std::string &relativeSourcePath,
+                            int32_t destStorageId, const std::string &relativeDestPath,
+                            int32_t *_aidl_return) final;
+    binder::Status unlink(int32_t storageId, const std::string &pathUnderStorage,
+                          int32_t *_aidl_return) final;
+    binder::Status isFileRangeLoaded(int32_t storageId, const std::string &relativePath,
+                                     int64_t start, int64_t end, bool *_aidl_return) final;
+    binder::Status getFileMetadata(int32_t storageId, const std::string &relativePath,
+                                   std::vector<uint8_t> *_aidl_return) final;
+    binder::Status startLoading(int32_t storageId, bool *_aidl_return) final;
+
+private:
+    android::incremental::IncrementalService mImpl;
+};
+
+} // namespace android::os::incremental
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
diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h
new file mode 100644
index 0000000..a03ffa0
--- /dev/null
+++ b/services/incremental/IncrementalService.h
@@ -0,0 +1,231 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <android/os/incremental/IIncrementalManager.h>
+#include <android/content/pm/DataLoaderParamsParcel.h>
+#include <binder/IServiceManager.h>
+#include <utils/String16.h>
+#include <utils/StrongPointer.h>
+#include <utils/Vector.h>
+
+#include <atomic>
+#include <chrono>
+#include <future>
+#include <limits>
+#include <map>
+#include <mutex>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include "ServiceWrappers.h"
+#include "android/content/pm/BnDataLoaderStatusListener.h"
+#include "incfs.h"
+#include "path.h"
+
+using namespace android::os::incremental;
+
+namespace android::os {
+class IVold;
+}
+
+namespace android::incremental {
+
+using MountId = int;
+using StorageId = int;
+using Inode = incfs::Inode;
+using BlockIndex = incfs::BlockIndex;
+using RawMetadata = incfs::RawMetadata;
+using Clock = std::chrono::steady_clock;
+using TimePoint = std::chrono::time_point<Clock>;
+using Seconds = std::chrono::seconds;
+
+class IncrementalService {
+public:
+    explicit IncrementalService(const ServiceManagerWrapper& sm, std::string_view rootDir);
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
+    ~IncrementalService();
+#pragma GCC diagnostic pop
+
+    static constexpr StorageId kInvalidStorageId = -1;
+    static constexpr StorageId kMaxStorageId = std::numeric_limits<int>::max();
+
+    enum CreateOptions {
+        TemporaryBind = 1,
+        PermanentBind = 2,
+        CreateNew = 4,
+        OpenExisting = 8,
+
+        Default = TemporaryBind | CreateNew
+    };
+
+    enum class BindKind {
+        Temporary = 0,
+        Permanent = 1,
+    };
+
+    std::optional<std::future<void>> onSystemReady();
+
+    StorageId createStorage(std::string_view mountPoint,
+                            DataLoaderParamsParcel&& dataLoaderParams,
+                            CreateOptions options = CreateOptions::Default);
+    StorageId createLinkedStorage(std::string_view mountPoint, StorageId linkedStorage,
+                                  CreateOptions options = CreateOptions::Default);
+    StorageId openStorage(std::string_view pathInMount);
+
+    Inode nodeFor(StorageId storage, std::string_view subpath) const;
+    std::pair<Inode, std::string_view> parentAndNameFor(StorageId storage,
+                                                        std::string_view subpath) const;
+
+    int bind(StorageId storage, std::string_view subdir, std::string_view target, BindKind kind);
+    int unbind(StorageId storage, std::string_view target);
+    void deleteStorage(StorageId storage);
+
+    Inode makeFile(StorageId storage, std::string_view name, long size, std::string_view metadata,
+                   std::string_view signature);
+    Inode makeDir(StorageId storage, std::string_view name, std::string_view metadata = {});
+    Inode makeDirs(StorageId storage, std::string_view name, std::string_view metadata = {});
+
+    int link(StorageId storage, Inode item, Inode newParent, std::string_view newName);
+    int unlink(StorageId storage, Inode parent, std::string_view name);
+
+    bool isRangeLoaded(StorageId storage, Inode file, std::pair<BlockIndex, BlockIndex> range) {
+        return false;
+    }
+
+    RawMetadata getMetadata(StorageId storage, Inode node) const;
+    std::string getSigngatureData(StorageId storage, Inode node) const { return {}; }
+
+    std::vector<std::string> listFiles(StorageId storage) const;
+    bool startLoading(StorageId storage) const;
+
+    class IncrementalDataLoaderListener : public android::content::pm::BnDataLoaderStatusListener {
+    public:
+        IncrementalDataLoaderListener(IncrementalService& incrementalService)
+              : incrementalService(incrementalService) {}
+        // Callbacks interface
+        binder::Status onStatusChanged(MountId mount, int newStatus) override;
+
+    private:
+        IncrementalService& incrementalService;
+    };
+
+private:
+    struct IncFsMount {
+        struct Bind {
+            StorageId storage;
+            std::string savedFilename;
+            std::string sourceDir;
+            BindKind kind;
+        };
+
+        struct Storage {
+            std::string name;
+            Inode node;
+        };
+
+        struct Control {
+            operator IncFsControl() const { return {cmdFd, logFd}; }
+            void reset() {
+                cmdFd.reset();
+                logFd.reset();
+            }
+
+            base::unique_fd cmdFd;
+            base::unique_fd logFd;
+        };
+
+        using BindMap = std::map<std::string, Bind>;
+        using StorageMap = std::unordered_map<StorageId, Storage>;
+
+        mutable std::mutex lock;
+        const std::string root;
+        Control control;
+        /*const*/ MountId mountId;
+        StorageMap storages;
+        BindMap bindPoints;
+        std::optional<DataLoaderParamsParcel> savedDataLoaderParams;
+        std::atomic<int> nextStorageDirNo{0};
+        std::atomic<int> dataLoaderStatus = -1;
+        std::condition_variable dataLoaderReady;
+        TimePoint connectionLostTime = TimePoint();
+        const IncrementalService& incrementalService;
+
+        IncFsMount(std::string root, MountId mountId, Control control,
+                   const IncrementalService& incrementalService)
+              : root(std::move(root)),
+                control(std::move(control)),
+                mountId(mountId),
+                incrementalService(incrementalService) {}
+        IncFsMount(IncFsMount&&) = delete;
+        IncFsMount& operator=(IncFsMount&&) = delete;
+        ~IncFsMount();
+
+        StorageMap::iterator makeStorage(StorageId id);
+
+        static void cleanupFilesystem(std::string_view root);
+    };
+
+    using IfsMountPtr = std::shared_ptr<IncFsMount>;
+    using MountMap = std::unordered_map<MountId, IfsMountPtr>;
+    using BindPathMap = std::map<std::string, IncFsMount::BindMap::iterator, path::PathLess>;
+
+    void mountExistingImages();
+    bool mountExistingImage(std::string_view root, std::string_view key);
+
+    IfsMountPtr getIfs(StorageId storage) const;
+    const IfsMountPtr& getIfsLocked(StorageId storage) const;
+    int addBindMount(IncFsMount& ifs, StorageId storage, std::string&& sourceSubdir,
+                     std::string&& target, BindKind kind, std::unique_lock<std::mutex>& mainLock);
+
+    int addBindMountWithMd(IncFsMount& ifs, StorageId storage, std::string&& metadataName,
+                           std::string&& sourceSubdir, std::string&& target, BindKind kind,
+                           std::unique_lock<std::mutex>& mainLock);
+
+    bool prepareDataLoader(IncFsMount& ifs, DataLoaderParamsParcel* params);
+    BindPathMap::const_iterator findStorageLocked(std::string_view path) const;
+    StorageId findStorageId(std::string_view path) const;
+
+    void deleteStorage(IncFsMount& ifs);
+    void deleteStorageLocked(IncFsMount& ifs, std::unique_lock<std::mutex>&& ifsLock);
+    MountMap::iterator getStorageSlotLocked();
+
+    // Member variables
+    // These are shared pointers for the sake of unit testing
+    std::shared_ptr<VoldServiceWrapper> mVold;
+    std::shared_ptr<IncrementalManagerWrapper> mIncrementalManager;
+    std::shared_ptr<IncFsWrapper> mIncFs;
+    const std::string mIncrementalDir;
+
+    mutable std::mutex mLock;
+    mutable std::mutex mMountOperationLock;
+    MountMap mMounts;
+    BindPathMap mBindsByPath;
+
+    std::atomic_bool mSystemReady = false;
+    StorageId mNextId = 0;
+    std::promise<void> mPrepareDataLoaders;
+};
+
+} // namespace android::incremental
diff --git a/services/incremental/Metadata.proto b/services/incremental/Metadata.proto
new file mode 100644
index 0000000..0ff3c32
--- /dev/null
+++ b/services/incremental/Metadata.proto
@@ -0,0 +1,23 @@
+syntax = "proto3";
+
+package android.incremental.metadata;
+
+message BindPoint {
+    int32 storage_id = 1;
+    string source_subdir = 2;
+    string dest_path = 3;
+}
+
+message DataLoader {
+    string package_name = 1;
+    string arguments = 2;
+}
+
+message Storage {
+    int32 id = 1;
+}
+
+message Mount {
+    Storage storage = 1;
+    DataLoader loader = 2;
+}
diff --git a/services/incremental/ServiceWrappers.cpp b/services/incremental/ServiceWrappers.cpp
new file mode 100644
index 0000000..a79b26a
--- /dev/null
+++ b/services/incremental/ServiceWrappers.cpp
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+
+#include "ServiceWrappers.h"
+
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <binder/IServiceManager.h>
+#include <utils/String16.h>
+
+#include <string>
+#include <string_view>
+
+using namespace std::literals;
+
+namespace android::os::incremental {
+
+static constexpr auto kVoldServiceName = "vold"sv;
+static constexpr auto kIncrementalManagerName = "incremental"sv;
+
+RealServiceManager::RealServiceManager(const sp<IServiceManager>& serviceManager)
+      : mServiceManager(serviceManager) {}
+
+template <class INTERFACE>
+sp<INTERFACE> RealServiceManager::getRealService(std::string_view serviceName) const {
+    sp<IBinder> binder = mServiceManager->getService(String16(serviceName.data()));
+    if (binder == 0) {
+        return 0;
+    }
+    return interface_cast<INTERFACE>(binder);
+}
+
+std::shared_ptr<VoldServiceWrapper> RealServiceManager::getVoldService() const {
+    sp<os::IVold> vold = RealServiceManager::getRealService<os::IVold>(kVoldServiceName);
+    if (vold != 0) {
+        return std::make_shared<RealVoldService>(vold);
+    }
+    return nullptr;
+}
+
+std::shared_ptr<IncrementalManagerWrapper> RealServiceManager::getIncrementalManager() const {
+    sp<IIncrementalManager> manager =
+            RealServiceManager::getRealService<IIncrementalManager>(kIncrementalManagerName);
+    if (manager != 0) {
+        return std::make_shared<RealIncrementalManager>(manager);
+    }
+    return nullptr;
+}
+
+std::shared_ptr<IncFsWrapper> RealServiceManager::getIncFs() const {
+    return std::make_shared<RealIncFs>();
+}
+
+} // namespace android::os::incremental
diff --git a/services/incremental/ServiceWrappers.h b/services/incremental/ServiceWrappers.h
new file mode 100644
index 0000000..5704582
--- /dev/null
+++ b/services/incremental/ServiceWrappers.h
@@ -0,0 +1,183 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <android/content/pm/DataLoaderParamsParcel.h>
+#include <android/content/pm/FileSystemControlParcel.h>
+#include <android/content/pm/IDataLoaderStatusListener.h>
+#include <android/os/IVold.h>
+#include <android/os/incremental/IIncrementalManager.h>
+#include <binder/IServiceManager.h>
+#include <incfs.h>
+
+#include <string>
+#include <string_view>
+
+using namespace android::incfs;
+using namespace android::content::pm;
+
+namespace android::os::incremental {
+
+// --- Wrapper interfaces ---
+
+class VoldServiceWrapper {
+public:
+    virtual ~VoldServiceWrapper(){};
+    virtual binder::Status mountIncFs(const std::string& imagePath, const std::string& targetDir,
+                                      int32_t flags,
+                                      IncrementalFileSystemControlParcel* _aidl_return) const = 0;
+    virtual binder::Status unmountIncFs(const std::string& dir) const = 0;
+    virtual binder::Status bindMount(const std::string& sourceDir,
+                                     const std::string& targetDir) const = 0;
+};
+
+class IncrementalManagerWrapper {
+public:
+    virtual ~IncrementalManagerWrapper() {}
+    virtual binder::Status prepareDataLoader(
+            int32_t mountId, const FileSystemControlParcel& control,
+            const DataLoaderParamsParcel& params,
+            const sp<IDataLoaderStatusListener>& listener,
+            bool* _aidl_return) const = 0;
+    virtual binder::Status startDataLoader(int32_t mountId, bool* _aidl_return) const = 0;
+    virtual binder::Status destroyDataLoader(int32_t mountId) const = 0;
+    virtual binder::Status newFileForDataLoader(int32_t mountId, int64_t inode,
+                                                const ::std::vector<uint8_t>& metadata) const = 0;
+    virtual binder::Status showHealthBlockedUI(int32_t mountId) const = 0;
+};
+
+class IncFsWrapper {
+public:
+    virtual ~IncFsWrapper() {}
+    virtual Inode makeFile(Control control, std::string_view name, Inode parent, Size size,
+                           std::string_view metadata) const = 0;
+    virtual Inode makeDir(Control control, std::string_view name, Inode parent,
+                          std::string_view metadata, int mode = 0555) const = 0;
+    virtual RawMetadata getMetadata(Control control, Inode inode) const = 0;
+    virtual ErrorCode link(Control control, Inode item, Inode targetParent,
+                           std::string_view name) const = 0;
+    virtual ErrorCode unlink(Control control, Inode parent, std::string_view name) const = 0;
+    virtual ErrorCode writeBlocks(Control control, const incfs_new_data_block blocks[],
+                                  int blocksCount) const = 0;
+};
+
+class ServiceManagerWrapper {
+public:
+    virtual ~ServiceManagerWrapper() {}
+    virtual std::shared_ptr<VoldServiceWrapper> getVoldService() const = 0;
+    virtual std::shared_ptr<IncrementalManagerWrapper> getIncrementalManager() const = 0;
+    virtual std::shared_ptr<IncFsWrapper> getIncFs() const = 0;
+};
+
+// --- Real stuff ---
+
+class RealVoldService : public VoldServiceWrapper {
+public:
+    RealVoldService(const sp<os::IVold> vold) : mInterface(vold) {}
+    ~RealVoldService() = default;
+    binder::Status mountIncFs(const std::string& imagePath, const std::string& targetDir,
+                              int32_t flags,
+                              IncrementalFileSystemControlParcel* _aidl_return) const override {
+        return mInterface->mountIncFs(imagePath, targetDir, flags, _aidl_return);
+    }
+    binder::Status unmountIncFs(const std::string& dir) const override {
+        return mInterface->unmountIncFs(dir);
+    }
+    binder::Status bindMount(const std::string& sourceDir,
+                             const std::string& targetDir) const override {
+        return mInterface->bindMount(sourceDir, targetDir);
+    }
+
+private:
+    sp<os::IVold> mInterface;
+};
+
+class RealIncrementalManager : public IncrementalManagerWrapper {
+public:
+    RealIncrementalManager(const sp<os::incremental::IIncrementalManager> manager)
+          : mInterface(manager) {}
+    ~RealIncrementalManager() = default;
+    binder::Status prepareDataLoader(
+            int32_t mountId, const FileSystemControlParcel& control,
+            const DataLoaderParamsParcel& params,
+            const sp<IDataLoaderStatusListener>& listener,
+            bool* _aidl_return) const override {
+        return mInterface->prepareDataLoader(mountId, control, params, listener, _aidl_return);
+    }
+    binder::Status startDataLoader(int32_t mountId, bool* _aidl_return) const override {
+        return mInterface->startDataLoader(mountId, _aidl_return);
+    }
+    binder::Status destroyDataLoader(int32_t mountId) const override {
+        return mInterface->destroyDataLoader(mountId);
+    }
+    binder::Status newFileForDataLoader(int32_t mountId, int64_t inode,
+                                        const ::std::vector<uint8_t>& metadata) const override {
+        return mInterface->newFileForDataLoader(mountId, inode, metadata);
+    }
+    binder::Status showHealthBlockedUI(int32_t mountId) const override {
+        return mInterface->showHealthBlockedUI(mountId);
+    }
+
+private:
+    sp<os::incremental::IIncrementalManager> mInterface;
+};
+
+class RealServiceManager : public ServiceManagerWrapper {
+public:
+    RealServiceManager(const sp<IServiceManager>& serviceManager);
+    ~RealServiceManager() = default;
+    std::shared_ptr<VoldServiceWrapper> getVoldService() const override;
+    std::shared_ptr<IncrementalManagerWrapper> getIncrementalManager() const override;
+    std::shared_ptr<IncFsWrapper> getIncFs() const override;
+
+private:
+    template <class INTERFACE>
+    sp<INTERFACE> getRealService(std::string_view serviceName) const;
+    sp<android::IServiceManager> mServiceManager;
+};
+
+class RealIncFs : public IncFsWrapper {
+public:
+    RealIncFs() = default;
+    ~RealIncFs() = default;
+    Inode makeFile(Control control, std::string_view name, Inode parent, Size size,
+                   std::string_view metadata) const override {
+        return incfs::makeFile(control, name, parent, size, metadata);
+    }
+    Inode makeDir(Control control, std::string_view name, Inode parent, std::string_view metadata,
+                  int mode) const override {
+        return incfs::makeDir(control, name, parent, metadata, mode);
+    }
+    RawMetadata getMetadata(Control control, Inode inode) const override {
+        return incfs::getMetadata(control, inode);
+    }
+    ErrorCode link(Control control, Inode item, Inode targetParent,
+                   std::string_view name) const override {
+        return incfs::link(control, item, targetParent, name);
+    }
+    ErrorCode unlink(Control control, Inode parent, std::string_view name) const override {
+        return incfs::unlink(control, parent, name);
+    }
+    ErrorCode writeBlocks(Control control, const incfs_new_data_block blocks[],
+                          int blocksCount) const override {
+        return incfs::writeBlocks(control, blocks, blocksCount);
+    }
+};
+
+} // namespace android::os::incremental
diff --git a/services/incremental/include/incremental_service.h b/services/incremental/include/incremental_service.h
new file mode 100644
index 0000000..7109d95
--- /dev/null
+++ b/services/incremental/include/incremental_service.h
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_INCREMENTAL_SERVICE_H
+#define ANDROID_INCREMENTAL_SERVICE_H
+
+#include <sys/cdefs.h>
+#include <jni.h>
+
+__BEGIN_DECLS
+
+#define INCREMENTAL_LIBRARY_NAME "service.incremental.so"
+
+jlong Incremental_IncrementalService_Start();
+void Incremental_IncrementalService_OnSystemReady(jlong self);
+
+__END_DECLS
+
+#endif  // ANDROID_INCREMENTAL_SERVICE_H
diff --git a/services/incremental/incremental_service.c b/services/incremental/incremental_service.c
new file mode 100644
index 0000000..f6ea59e
--- /dev/null
+++ b/services/incremental/incremental_service.c
@@ -0,0 +1,17 @@
+/*
+ * 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.
+ */
+
+#include "incremental_service.h"
diff --git a/services/incremental/path.cpp b/services/incremental/path.cpp
new file mode 100644
index 0000000..c529d61
--- /dev/null
+++ b/services/incremental/path.cpp
@@ -0,0 +1,178 @@
+/*
+ * 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.
+ */
+
+#include "path.h"
+
+#include <android-base/strings.h>
+#include <android-base/logging.h>
+
+#include <algorithm>
+#include <iterator>
+#include <limits>
+#include <memory>
+
+#include <dirent.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+using namespace std::literals;
+
+namespace android::incremental::path {
+
+bool PathCharsLess::operator()(char l, char r) const {
+    int ll = l == '/' ? std::numeric_limits<char>::min() - 1 : l;
+    int rr = r == '/' ? std::numeric_limits<char>::min() - 1 : r;
+    return ll < rr;
+}
+
+bool PathLess::operator()(std::string_view l, std::string_view r) const {
+    return std::lexicographical_compare(std::begin(l), std::end(l), std::begin(r), std::end(r),
+                                        PathCharsLess());
+}
+
+void details::append_next_path(std::string& target, std::string_view path) {
+    if (path.empty()) {
+        return;
+    }
+    if (!target.empty()) {
+        target.push_back('/');
+    }
+    target += path;
+}
+
+bool isAbsolute(std::string_view path) {
+    return !path.empty() && path[0] == '/';
+}
+
+std::string normalize(std::string_view path) {
+    if (path.empty()) {
+        return {};
+    }
+    if (path.starts_with("../"sv)) {
+        return {};
+    }
+
+    std::string result;
+    if (isAbsolute(path)) {
+        path.remove_prefix(1);
+    } else {
+        char buffer[PATH_MAX];
+        if (!::getcwd(buffer, sizeof(buffer))) {
+            return {};
+        }
+        result += buffer;
+    }
+
+    size_t start = 0;
+    size_t end = 0;
+    for (; end != path.npos; start = end + 1) {
+        end = path.find('/', start);
+        // Next component, excluding the separator
+        auto part = path.substr(start, end - start);
+        if (part.empty() || part == "."sv) {
+            continue;
+        }
+        if (part == ".."sv) {
+            if (result.empty()) {
+                return {};
+            }
+            auto lastPos = result.rfind('/');
+            if (lastPos == result.npos) {
+                result.clear();
+            } else {
+                result.resize(lastPos);
+            }
+            continue;
+        }
+        result += '/';
+        result += part;
+    }
+
+    return result;
+}
+
+std::string_view basename(std::string_view path) {
+    if (path.empty()) {
+        return {};
+    }
+    if (path == "/"sv) {
+        return "/"sv;
+    }
+    auto pos = path.rfind('/');
+    while (!path.empty() && pos == path.size() - 1) {
+        path.remove_suffix(1);
+        pos = path.rfind('/');
+    }
+    if (pos == path.npos) {
+        return path.empty() ? "/"sv : path;
+    }
+    return path.substr(pos + 1);
+}
+
+std::string_view dirname(std::string_view path) {
+    if (path.empty()) {
+        return {};
+    }
+    if (path == "/"sv) {
+        return "/"sv;
+    }
+    const auto pos = path.rfind('/');
+    if (pos == 0) {
+        return "/"sv;
+    }
+    if (pos == path.npos) {
+        return "."sv;
+    }
+    return path.substr(0, pos);
+}
+
+details::CStrWrapper::CStrWrapper(std::string_view sv) {
+    if (sv[sv.size()] == '\0') {
+        mCstr = sv.data();
+    } else {
+        mCopy.emplace(sv);
+        mCstr = mCopy->c_str();
+    }
+}
+
+std::optional<bool> isEmptyDir(std::string_view dir) {
+    const auto d = std::unique_ptr<DIR, decltype(&::closedir)>{::opendir(c_str(dir)), ::closedir};
+    if (!d) {
+        if (errno == EPERM || errno == EACCES) {
+            return std::nullopt;
+        }
+        return false;
+    }
+    while (auto entry = ::readdir(d.get())) {
+        if (entry->d_type != DT_DIR) {
+            return false;
+        }
+        if (entry->d_name != "."sv && entry->d_name != ".."sv) {
+            return false;
+        }
+    }
+    return true;
+}
+
+bool startsWith(std::string_view path, std::string_view prefix) {
+    if (!base::StartsWith(path, prefix)) {
+        return false;
+    }
+    return path.size() == prefix.size() || path[prefix.size()] == '/';
+}
+
+} // namespace android::incremental::path
diff --git a/services/incremental/path.h b/services/incremental/path.h
new file mode 100644
index 0000000..a1f4b8e
--- /dev/null
+++ b/services/incremental/path.h
@@ -0,0 +1,89 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <optional>
+#include <string>
+#include <string_view>
+
+namespace android::incremental::path {
+
+namespace details {
+
+class CStrWrapper {
+public:
+    CStrWrapper(std::string_view sv);
+
+    CStrWrapper(const CStrWrapper&) = delete;
+    void operator=(const CStrWrapper&) = delete;
+    CStrWrapper(CStrWrapper&&) = delete;
+    void operator=(CStrWrapper&&) = delete;
+
+    const char* get() const { return mCstr; }
+    operator const char*() const { return get(); }
+
+private:
+    const char* mCstr;
+    std::optional<std::string> mCopy;
+};
+
+void append_next_path(std::string& res, std::string_view c);
+
+} // namespace details
+
+//
+// An std::map<> comparator that makes all nested paths to be ordered before the parents.
+//
+
+struct PathCharsLess {
+    bool operator()(char l, char r) const;
+};
+
+struct PathLess {
+    using is_transparent = void;
+    bool operator()(std::string_view l, std::string_view r) const;
+};
+
+//
+// Returns a zero-terminated version of a passed string view
+// Only makes a copy if it wasn't zero-terminated already
+// Useful for passing string view parameters to system functions.
+//
+inline details::CStrWrapper c_str(std::string_view sv) {
+    return {sv};
+}
+
+bool isAbsolute(std::string_view path);
+std::string normalize(std::string_view path);
+std::string_view dirname(std::string_view path);
+std::string_view basename(std::string_view path);
+std::optional<bool> isEmptyDir(std::string_view dir);
+bool startsWith(std::string_view path, std::string_view prefix);
+
+template <class... Paths>
+std::string join(std::string_view first, std::string_view second, Paths&&... paths) {
+    std::string result;
+    {
+        using std::size;
+        result.reserve(first.size() + second.size() + 1 + (sizeof...(paths) + ... + size(paths)));
+    }
+    result.assign(first);
+    (details::append_next_path(result, second), ..., details::append_next_path(result, paths));
+    return result;
+}
+
+} // namespace android::incremental::path
diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp
new file mode 100644
index 0000000..f6b123d
--- /dev/null
+++ b/services/incremental/test/IncrementalServiceTest.cpp
@@ -0,0 +1,461 @@
+/*
+ * 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.
+ */
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/unique_fd.h>
+#include <binder/ParcelFileDescriptor.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <utils/Log.h>
+
+#include <future>
+
+#include "IncrementalService.h"
+#include "Metadata.pb.h"
+#include "ServiceWrappers.h"
+
+using namespace testing;
+using namespace android::incremental;
+using namespace std::literals;
+using testing::_;
+using testing::Invoke;
+using testing::NiceMock;
+
+#undef LOG_TAG
+#define LOG_TAG "IncrementalServiceTest"
+
+using namespace android::incfs;
+using namespace android::content::pm;
+
+namespace android::os::incremental {
+
+class MockVoldService : public VoldServiceWrapper {
+public:
+    MOCK_CONST_METHOD4(mountIncFs,
+                       binder::Status(const std::string& imagePath, const std::string& targetDir,
+                                      int32_t flags,
+                                      IncrementalFileSystemControlParcel* _aidl_return));
+    MOCK_CONST_METHOD1(unmountIncFs, binder::Status(const std::string& dir));
+    MOCK_CONST_METHOD2(bindMount,
+                       binder::Status(const std::string& sourceDir, const std::string& argetDir));
+
+    void mountIncFsFails() {
+        ON_CALL(*this, mountIncFs(_, _, _, _))
+                .WillByDefault(
+                        Return(binder::Status::fromExceptionCode(1, String8("failed to mount"))));
+    }
+    void mountIncFsInvalidControlParcel() {
+        ON_CALL(*this, mountIncFs(_, _, _, _))
+                .WillByDefault(Invoke(this, &MockVoldService::getInvalidControlParcel));
+    }
+    void mountIncFsSuccess() {
+        ON_CALL(*this, mountIncFs(_, _, _, _))
+                .WillByDefault(Invoke(this, &MockVoldService::incFsSuccess));
+    }
+    void bindMountFails() {
+        ON_CALL(*this, bindMount(_, _))
+                .WillByDefault(Return(
+                        binder::Status::fromExceptionCode(1, String8("failed to bind-mount"))));
+    }
+    void bindMountSuccess() {
+        ON_CALL(*this, bindMount(_, _)).WillByDefault(Return(binder::Status::ok()));
+    }
+    binder::Status getInvalidControlParcel(const std::string& imagePath,
+                                           const std::string& targetDir, int32_t flags,
+                                           IncrementalFileSystemControlParcel* _aidl_return) {
+        _aidl_return->cmd = nullptr;
+        _aidl_return->log = nullptr;
+        return binder::Status::ok();
+    }
+    binder::Status incFsSuccess(const std::string& imagePath, const std::string& targetDir,
+                                int32_t flags, IncrementalFileSystemControlParcel* _aidl_return) {
+        _aidl_return->cmd = std::make_unique<os::ParcelFileDescriptor>(std::move(cmdFd));
+        _aidl_return->log = std::make_unique<os::ParcelFileDescriptor>(std::move(logFd));
+        return binder::Status::ok();
+    }
+
+private:
+    TemporaryFile cmdFile;
+    TemporaryFile logFile;
+    base::unique_fd cmdFd;
+    base::unique_fd logFd;
+};
+
+class MockIncrementalManager : public IncrementalManagerWrapper {
+public:
+    MOCK_CONST_METHOD5(prepareDataLoader,
+                       binder::Status(int32_t mountId, const FileSystemControlParcel& control,
+                                      const DataLoaderParamsParcel& params,
+                                      const sp<IDataLoaderStatusListener>& listener,
+                                      bool* _aidl_return));
+    MOCK_CONST_METHOD2(startDataLoader, binder::Status(int32_t mountId, bool* _aidl_return));
+    MOCK_CONST_METHOD1(destroyDataLoader, binder::Status(int32_t mountId));
+    MOCK_CONST_METHOD3(newFileForDataLoader,
+                       binder::Status(int32_t mountId, int64_t inode,
+                                      const ::std::vector<uint8_t>& metadata));
+    MOCK_CONST_METHOD1(showHealthBlockedUI, binder::Status(int32_t mountId));
+
+    binder::Status prepareDataLoaderOk(int32_t mountId, const FileSystemControlParcel& control,
+                                       const DataLoaderParamsParcel& params,
+                                       const sp<IDataLoaderStatusListener>& listener,
+                                       bool* _aidl_return) {
+        mId = mountId;
+        mListener = listener;
+        *_aidl_return = true;
+        return binder::Status::ok();
+    }
+
+    binder::Status startDataLoaderOk(int32_t mountId, bool* _aidl_return) {
+        *_aidl_return = true;
+        return binder::Status::ok();
+    }
+
+    void prepareDataLoaderFails() {
+        ON_CALL(*this, prepareDataLoader(_, _, _, _, _))
+                .WillByDefault(Return(
+                        (binder::Status::fromExceptionCode(1, String8("failed to prepare")))));
+    }
+    void prepareDataLoaderSuccess() {
+        ON_CALL(*this, prepareDataLoader(_, _, _, _, _))
+                .WillByDefault(Invoke(this, &MockIncrementalManager::prepareDataLoaderOk));
+    }
+    void startDataLoaderSuccess() {
+        ON_CALL(*this, startDataLoader(_, _))
+                .WillByDefault(Invoke(this, &MockIncrementalManager::startDataLoaderOk));
+    }
+    void setDataLoaderStatusNotReady() {
+        mListener->onStatusChanged(mId, IDataLoaderStatusListener::DATA_LOADER_NOT_READY);
+    }
+    void setDataLoaderStatusReady() {
+        mListener->onStatusChanged(mId, IDataLoaderStatusListener::DATA_LOADER_READY);
+    }
+
+private:
+    int mId;
+    sp<IDataLoaderStatusListener> mListener;
+};
+
+class MockIncFs : public IncFsWrapper {
+public:
+    MOCK_CONST_METHOD5(makeFile,
+                       Inode(Control control, std::string_view name, Inode parent, Size size,
+                             std::string_view metadata));
+    MOCK_CONST_METHOD5(makeDir,
+                       Inode(Control control, std::string_view name, Inode parent,
+                             std::string_view metadata, int mode));
+    MOCK_CONST_METHOD2(getMetadata, RawMetadata(Control control, Inode inode));
+    MOCK_CONST_METHOD4(link,
+                       ErrorCode(Control control, Inode item, Inode targetParent,
+                                 std::string_view name));
+    MOCK_CONST_METHOD3(unlink, ErrorCode(Control control, Inode parent, std::string_view name));
+    MOCK_CONST_METHOD3(writeBlocks,
+                       ErrorCode(Control control, const incfs_new_data_block blocks[],
+                                 int blocksCount));
+
+    void makeFileFails() { ON_CALL(*this, makeFile(_, _, _, _, _)).WillByDefault(Return(-1)); }
+    void makeFileSuccess() { ON_CALL(*this, makeFile(_, _, _, _, _)).WillByDefault(Return(0)); }
+    RawMetadata getMountInfoMetadata(Control control, Inode inode) {
+        metadata::Mount m;
+        m.mutable_storage()->set_id(100);
+        m.mutable_loader()->set_package_name("com.test");
+        m.mutable_loader()->set_arguments("com.uri");
+        const auto metadata = m.SerializeAsString();
+        m.mutable_loader()->release_arguments();
+        m.mutable_loader()->release_package_name();
+        return std::vector<char>(metadata.begin(), metadata.end());
+    }
+    RawMetadata getStorageMetadata(Control control, Inode inode) {
+        metadata::Storage st;
+        st.set_id(100);
+        auto metadata = st.SerializeAsString();
+        return std::vector<char>(metadata.begin(), metadata.end());
+    }
+    RawMetadata getBindPointMetadata(Control control, Inode inode) {
+        metadata::BindPoint bp;
+        std::string destPath = "dest";
+        std::string srcPath = "src";
+        bp.set_storage_id(100);
+        bp.set_allocated_dest_path(&destPath);
+        bp.set_allocated_source_subdir(&srcPath);
+        const auto metadata = bp.SerializeAsString();
+        bp.release_source_subdir();
+        bp.release_dest_path();
+        return std::vector<char>(metadata.begin(), metadata.end());
+    }
+};
+
+class MockServiceManager : public ServiceManagerWrapper {
+public:
+    MockServiceManager(std::shared_ptr<MockVoldService> vold,
+                       std::shared_ptr<MockIncrementalManager> manager,
+                       std::shared_ptr<MockIncFs> incfs)
+          : mVold(vold), mIncrementalManager(manager), mIncFs(incfs) {}
+    std::shared_ptr<VoldServiceWrapper> getVoldService() const override { return mVold; }
+    std::shared_ptr<IncrementalManagerWrapper> getIncrementalManager() const override {
+        return mIncrementalManager;
+    }
+    std::shared_ptr<IncFsWrapper> getIncFs() const override { return mIncFs; }
+
+private:
+    std::shared_ptr<MockVoldService> mVold;
+    std::shared_ptr<MockIncrementalManager> mIncrementalManager;
+    std::shared_ptr<MockIncFs> mIncFs;
+};
+
+// --- IncrementalServiceTest ---
+
+static Inode inode(std::string_view path) {
+    struct stat st;
+    if (::stat(path::c_str(path), &st)) {
+        return -1;
+    }
+    return st.st_ino;
+}
+
+class IncrementalServiceTest : public testing::Test {
+public:
+    void SetUp() override {
+        mVold = std::make_shared<NiceMock<MockVoldService>>();
+        mIncrementalManager = std::make_shared<NiceMock<MockIncrementalManager>>();
+        mIncFs = std::make_shared<NiceMock<MockIncFs>>();
+        MockServiceManager serviceManager = MockServiceManager(mVold, mIncrementalManager, mIncFs);
+        mIncrementalService = std::make_unique<IncrementalService>(serviceManager, mRootDir.path);
+        mDataLoaderParcel.packageName = "com.test";
+        mDataLoaderParcel.staticArgs = "uri";
+        mIncrementalService->onSystemReady();
+    }
+
+    void setUpExistingMountDir(const std::string& rootDir) {
+        const auto dir = rootDir + "/dir1";
+        const auto mountDir = dir + "/mount";
+        const auto backingDir = dir + "/backing_store";
+        const auto storageDir = mountDir + "/st0";
+        ASSERT_EQ(0, mkdir(dir.c_str(), 0755));
+        ASSERT_EQ(0, mkdir(mountDir.c_str(), 0755));
+        ASSERT_EQ(0, mkdir(backingDir.c_str(), 0755));
+        ASSERT_EQ(0, mkdir(storageDir.c_str(), 0755));
+        const auto mountInfoFile = rootDir + "/dir1/mount/.info";
+        const auto mountPointsFile = rootDir + "/dir1/mount/.mountpoint.abcd";
+        ASSERT_TRUE(base::WriteStringToFile("info", mountInfoFile));
+        ASSERT_TRUE(base::WriteStringToFile("mounts", mountPointsFile));
+        ASSERT_GE(inode(mountInfoFile), 0);
+        ASSERT_GE(inode(mountPointsFile), 0);
+        ON_CALL(*mIncFs, getMetadata(_, inode(mountInfoFile)))
+                .WillByDefault(Invoke(mIncFs.get(), &MockIncFs::getMountInfoMetadata));
+        ON_CALL(*mIncFs, getMetadata(_, inode(mountPointsFile)))
+                .WillByDefault(Invoke(mIncFs.get(), &MockIncFs::getBindPointMetadata));
+        ON_CALL(*mIncFs, getMetadata(_, inode(rootDir + "/dir1/mount/st0")))
+                .WillByDefault(Invoke(mIncFs.get(), &MockIncFs::getStorageMetadata));
+    }
+
+protected:
+    std::shared_ptr<NiceMock<MockVoldService>> mVold;
+    std::shared_ptr<NiceMock<MockIncFs>> mIncFs;
+    std::shared_ptr<NiceMock<MockIncrementalManager>> mIncrementalManager;
+    std::unique_ptr<IncrementalService> mIncrementalService;
+    TemporaryDir mRootDir;
+    DataLoaderParamsParcel mDataLoaderParcel;
+};
+
+/*
+TEST_F(IncrementalServiceTest, testBootMountExistingImagesSuccess) {
+    TemporaryDir tempDir;
+    setUpExistingMountDir(tempDir.path);
+    mVold->mountIncFsSuccess();
+    mVold->bindMountSuccess();
+    mIncrementalManager->prepareDataLoaderSuccess();
+    ON_CALL(*mIncrementalManager, destroyDataLoader(_)).WillByDefault(Return(binder::Status::ok()));
+
+    EXPECT_CALL(*mVold, mountIncFs(_, _, _, _)).Times(1);
+    EXPECT_CALL(*mIncrementalManager, prepareDataLoader(_, _, _, _, _)).Times(1);
+
+    MockServiceManager serviceManager = MockServiceManager(mVold, mIncrementalManager, mIncFs);
+    std::unique_ptr<IncrementalService> incrementalService =
+            std::make_unique<IncrementalService>(serviceManager, tempDir.path);
+    auto finished = incrementalService->onSystemReady();
+    if (finished) {
+        finished->wait();
+    }
+}
+*/
+
+TEST_F(IncrementalServiceTest, testCreateStorageMountIncFsFails) {
+    mVold->mountIncFsFails();
+    EXPECT_CALL(*mIncrementalManager, prepareDataLoader(_, _, _, _, _)).Times(0);
+    TemporaryDir tempDir;
+    int storageId =
+            mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+                                               IncrementalService::CreateOptions::CreateNew);
+    ASSERT_LT(storageId, 0);
+}
+
+TEST_F(IncrementalServiceTest, testCreateStorageMountIncFsInvalidControlParcel) {
+    mVold->mountIncFsInvalidControlParcel();
+    EXPECT_CALL(*mIncrementalManager, prepareDataLoader(_, _, _, _, _)).Times(0);
+    TemporaryDir tempDir;
+    int storageId =
+            mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+                                               IncrementalService::CreateOptions::CreateNew);
+    ASSERT_LT(storageId, 0);
+}
+
+TEST_F(IncrementalServiceTest, testCreateStorageMakeFileFails) {
+    mVold->mountIncFsSuccess();
+    mIncFs->makeFileFails();
+    EXPECT_CALL(*mIncrementalManager, prepareDataLoader(_, _, _, _, _)).Times(0);
+    EXPECT_CALL(*mIncrementalManager, destroyDataLoader(_));
+    EXPECT_CALL(*mVold, unmountIncFs(_));
+    TemporaryDir tempDir;
+    int storageId =
+            mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+                                               IncrementalService::CreateOptions::CreateNew);
+    ASSERT_LT(storageId, 0);
+}
+
+TEST_F(IncrementalServiceTest, testCreateStorageBindMountFails) {
+    mVold->mountIncFsSuccess();
+    mIncFs->makeFileSuccess();
+    mVold->bindMountFails();
+    EXPECT_CALL(*mIncrementalManager, prepareDataLoader(_, _, _, _, _)).Times(0);
+    EXPECT_CALL(*mIncrementalManager, destroyDataLoader(_));
+    EXPECT_CALL(*mVold, unmountIncFs(_));
+    TemporaryDir tempDir;
+    int storageId =
+            mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+                                               IncrementalService::CreateOptions::CreateNew);
+    ASSERT_LT(storageId, 0);
+}
+
+TEST_F(IncrementalServiceTest, testCreateStoragePrepareDataLoaderFails) {
+    mVold->mountIncFsSuccess();
+    mIncFs->makeFileSuccess();
+    mVold->bindMountSuccess();
+    mIncrementalManager->prepareDataLoaderFails();
+    EXPECT_CALL(*mIncrementalManager, destroyDataLoader(_));
+    EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
+    TemporaryDir tempDir;
+    int storageId =
+            mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+                                               IncrementalService::CreateOptions::CreateNew);
+    ASSERT_LT(storageId, 0);
+}
+
+TEST_F(IncrementalServiceTest, testDeleteStorageSuccess) {
+    mVold->mountIncFsSuccess();
+    mIncFs->makeFileSuccess();
+    mVold->bindMountSuccess();
+    mIncrementalManager->prepareDataLoaderSuccess();
+    EXPECT_CALL(*mIncrementalManager, destroyDataLoader(_));
+    EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
+    TemporaryDir tempDir;
+    int storageId =
+            mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+                                               IncrementalService::CreateOptions::CreateNew);
+    ASSERT_GE(storageId, 0);
+    mIncrementalService->deleteStorage(storageId);
+}
+
+TEST_F(IncrementalServiceTest, testOnStatusNotReady) {
+    mVold->mountIncFsSuccess();
+    mIncFs->makeFileSuccess();
+    mVold->bindMountSuccess();
+    mIncrementalManager->prepareDataLoaderSuccess();
+    EXPECT_CALL(*mIncrementalManager, destroyDataLoader(_));
+    EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
+    TemporaryDir tempDir;
+    int storageId =
+            mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+                                               IncrementalService::CreateOptions::CreateNew);
+    ASSERT_GE(storageId, 0);
+    mIncrementalManager->setDataLoaderStatusNotReady();
+}
+
+TEST_F(IncrementalServiceTest, testStartDataLoaderSuccess) {
+    mVold->mountIncFsSuccess();
+    mIncFs->makeFileSuccess();
+    mVold->bindMountSuccess();
+    mIncrementalManager->prepareDataLoaderSuccess();
+    mIncrementalManager->startDataLoaderSuccess();
+    EXPECT_CALL(*mIncrementalManager, destroyDataLoader(_));
+    EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
+    TemporaryDir tempDir;
+    int storageId =
+            mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+                                               IncrementalService::CreateOptions::CreateNew);
+    ASSERT_GE(storageId, 0);
+    mIncrementalManager->setDataLoaderStatusReady();
+    ASSERT_TRUE(mIncrementalService->startLoading(storageId));
+}
+
+TEST_F(IncrementalServiceTest, testMakeDirectory) {
+    mVold->mountIncFsSuccess();
+    mIncFs->makeFileSuccess();
+    mVold->bindMountSuccess();
+    mIncrementalManager->prepareDataLoaderSuccess();
+    mIncrementalManager->startDataLoaderSuccess();
+    TemporaryDir tempDir;
+    int storageId =
+            mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+                                               IncrementalService::CreateOptions::CreateNew);
+    std::string_view dir_path("test");
+    EXPECT_CALL(*mIncFs, makeDir(_, dir_path, _, _, _));
+    int fileIno = mIncrementalService->makeDir(storageId, dir_path, "");
+    ASSERT_GE(fileIno, 0);
+}
+
+TEST_F(IncrementalServiceTest, testMakeDirectoryNoParent) {
+    mVold->mountIncFsSuccess();
+    mIncFs->makeFileSuccess();
+    mVold->bindMountSuccess();
+    mIncrementalManager->prepareDataLoaderSuccess();
+    mIncrementalManager->startDataLoaderSuccess();
+    TemporaryDir tempDir;
+    int storageId =
+            mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+                                               IncrementalService::CreateOptions::CreateNew);
+    std::string_view first("first");
+    std::string_view second("second");
+    std::string dir_path = std::string(first) + "/" + std::string(second);
+    EXPECT_CALL(*mIncFs, makeDir(_, first, _, _, _)).Times(0);
+    EXPECT_CALL(*mIncFs, makeDir(_, second, _, _, _)).Times(0);
+    int fileIno = mIncrementalService->makeDir(storageId, dir_path, "");
+    ASSERT_LT(fileIno, 0);
+}
+
+TEST_F(IncrementalServiceTest, testMakeDirectories) {
+    mVold->mountIncFsSuccess();
+    mIncFs->makeFileSuccess();
+    mVold->bindMountSuccess();
+    mIncrementalManager->prepareDataLoaderSuccess();
+    mIncrementalManager->startDataLoaderSuccess();
+    TemporaryDir tempDir;
+    int storageId =
+            mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+                                               IncrementalService::CreateOptions::CreateNew);
+    std::string_view first("first");
+    std::string_view second("second");
+    std::string_view third("third");
+    InSequence seq;
+    EXPECT_CALL(*mIncFs, makeDir(_, first, _, _, _));
+    EXPECT_CALL(*mIncFs, makeDir(_, second, _, _, _));
+    EXPECT_CALL(*mIncFs, makeDir(_, third, _, _, _));
+    std::string dir_path =
+            std::string(first) + "/" + std::string(second) + "/" + std::string(third);
+    int fileIno = mIncrementalService->makeDirs(storageId, dir_path, "");
+    ASSERT_GE(fileIno, 0);
+}
+} // namespace android::os::incremental
diff --git a/services/incremental/test/path_test.cpp b/services/incremental/test/path_test.cpp
new file mode 100644
index 0000000..cbe479e1
--- /dev/null
+++ b/services/incremental/test/path_test.cpp
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+#include "../path.h"
+
+#include <gtest/gtest.h>
+
+using namespace std::literals;
+
+namespace android::incremental::path {
+
+TEST(Path, Normalize) {
+    EXPECT_STREQ("", normalize("").c_str());
+    EXPECT_STREQ("/data/app/com.snapchat.android-evzhnJDgPOq8VcxwEkSY5g==/base.apk",
+                 normalize("/data/app/com.snapchat.android-evzhnJDgPOq8VcxwEkSY5g==/base.apk")
+                         .c_str());
+    EXPECT_STREQ("/a/b", normalize("/a/c/../b").c_str());
+}
+
+TEST(Path, Comparator) {
+    EXPECT_TRUE(PathLess()("/a", "/aa"));
+    EXPECT_TRUE(PathLess()("/a/b", "/aa/b"));
+    EXPECT_TRUE(PathLess()("/a", "/a/b"));
+    EXPECT_TRUE(PathLess()("/a/b"sv, "/a\0"sv));
+    EXPECT_TRUE(!PathLess()("/aa/b", "/a/b"));
+    EXPECT_TRUE(!PathLess()("/a/b", "/a/b"));
+    EXPECT_TRUE(!PathLess()("/a/b", "/a"));
+}
+
+} // namespace android::incremental::path