[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 ¶ms,
+ 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