diff options
author | 2019-11-29 14:23:45 -0800 | |
---|---|---|
committer | 2019-12-19 11:27:42 -0800 | |
commit | 3c82a306cd8d670103face9a60150e1cdb8e5a81 (patch) | |
tree | 2bd3b4c13a31d9abd6522c9974dbb8f9f68eaa53 /services | |
parent | 73b4b64be87b4fc1f1538e5f78eafd0c66e0bbc8 (diff) |
[incremental] native implementation of Incremental Service
The implementation of IIncrementalManager.aidl. TODO to refactor this.
Test: atest service.incremental_test
Change-Id: Ib8c8a9c0e7f0289b4bcd8961fa39746ed12b4310
Diffstat (limited to 'services')
18 files changed, 2839 insertions, 2 deletions
diff --git a/services/core/java/com/android/server/incremental/IncrementalManagerService.java b/services/core/java/com/android/server/incremental/IncrementalManagerService.java index 789551bb2263..3049522c2231 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 @@ public class IncrementalManagerService extends IIncrementalManager.Stub { 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 @@ public class IncrementalManagerService extends IIncrementalManager.Stub { (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 a34b7fdb911c..fee29db8edf2 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -52,6 +52,7 @@ cc_library_static { "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 @@ cc_defaults { "android.frameworks.schedulerservice@1.0", "android.frameworks.sensorservice@1.0", "android.system.suspend@1.0", + "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 000000000000..5e255f46fa05 --- /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 165edf15ca23..c0a6e4e30f3a 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -58,6 +58,7 @@ int register_android_server_am_AppCompactor(JNIEnv* env); 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 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) 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 000000000000..2661925c1d06 --- /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 000000000000..bb26c1f93159 --- /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 000000000000..37c9661db28d --- /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 000000000000..c43328fcdf9d --- /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 000000000000..a03ffa00d035 --- /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 000000000000..0ff3c3234ffa --- /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 000000000000..a79b26ae4fb3 --- /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 000000000000..570458283ac9 --- /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 000000000000..7109d953ba4d --- /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 000000000000..f6ea59ea0880 --- /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 000000000000..c529d61abd15 --- /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 000000000000..a1f4b8ec3093 --- /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 000000000000..f6b123d68212 --- /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 000000000000..cbe479e10087 --- /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 |