/* * 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 "incfs-dataloaderconnector" #include #include #include #include #include #include #include #include #include #include "JNIHelpers.h" #include "ManagedDataLoader.h" #include "dataloader.h" #include "incfs.h" namespace { using namespace android::dataloader; using namespace std::literals; using FileId = android::incfs::FileId; using RawMetadata = android::incfs::RawMetadata; using UniqueControl = android::incfs::UniqueControl; struct JniIds { struct { jint DATA_LOADER_CREATED; jint DATA_LOADER_DESTROYED; jint DATA_LOADER_STARTED; jint DATA_LOADER_STOPPED; jint DATA_LOADER_IMAGE_READY; jint DATA_LOADER_IMAGE_NOT_READY; jint DATA_LOADER_UNAVAILABLE; jint DATA_LOADER_UNRECOVERABLE; jint DATA_LOADER_TYPE_NONE; jint DATA_LOADER_TYPE_STREAMING; jint DATA_LOADER_TYPE_INCREMENTAL; jint DATA_LOADER_LOCATION_DATA_APP; jint DATA_LOADER_LOCATION_MEDIA_OBB; jint DATA_LOADER_LOCATION_MEDIA_DATA; } constants; jmethodID parcelFileDescriptorGetFileDescriptor; jfieldID incremental; jfieldID service; jfieldID callback; jfieldID controlCmd; jfieldID controlPendingReads; jfieldID controlLog; jfieldID paramsType; jfieldID paramsPackageName; jfieldID paramsClassName; jfieldID paramsArguments; jclass listener; jmethodID listenerOnStatusChanged; jmethodID callbackControlWriteData; jmethodID listGet; jmethodID listSize; jfieldID installationFileLocation; jfieldID installationFileName; jfieldID installationFileLengthBytes; jfieldID installationFileMetadata; jmethodID incrementalServiceConnectorSetStorageParams; JniIds(JNIEnv* env) { listener = (jclass)env->NewGlobalRef( FindClassOrDie(env, "android/content/pm/IDataLoaderStatusListener")); listenerOnStatusChanged = GetMethodIDOrDie(env, listener, "onStatusChanged", "(II)V"); constants.DATA_LOADER_CREATED = GetStaticIntFieldValueOrDie(env, listener, "DATA_LOADER_CREATED"); constants.DATA_LOADER_DESTROYED = GetStaticIntFieldValueOrDie(env, listener, "DATA_LOADER_DESTROYED"); constants.DATA_LOADER_STARTED = GetStaticIntFieldValueOrDie(env, listener, "DATA_LOADER_STARTED"); constants.DATA_LOADER_STOPPED = GetStaticIntFieldValueOrDie(env, listener, "DATA_LOADER_STOPPED"); constants.DATA_LOADER_IMAGE_READY = GetStaticIntFieldValueOrDie(env, listener, "DATA_LOADER_IMAGE_READY"); constants.DATA_LOADER_IMAGE_NOT_READY = GetStaticIntFieldValueOrDie(env, listener, "DATA_LOADER_IMAGE_NOT_READY"); constants.DATA_LOADER_UNAVAILABLE = GetStaticIntFieldValueOrDie(env, listener, "DATA_LOADER_UNAVAILABLE"); constants.DATA_LOADER_UNRECOVERABLE = GetStaticIntFieldValueOrDie(env, listener, "DATA_LOADER_UNRECOVERABLE"); CHECK(constants.DATA_LOADER_UNRECOVERABLE == DATA_LOADER_UNRECOVERABLE); auto packageInstaller = (jclass)FindClassOrDie(env, "android/content/pm/PackageInstaller"); constants.DATA_LOADER_TYPE_NONE = GetStaticIntFieldValueOrDie(env, packageInstaller, "DATA_LOADER_TYPE_NONE"); constants.DATA_LOADER_TYPE_STREAMING = GetStaticIntFieldValueOrDie(env, packageInstaller, "DATA_LOADER_TYPE_STREAMING"); constants.DATA_LOADER_TYPE_INCREMENTAL = GetStaticIntFieldValueOrDie(env, packageInstaller, "DATA_LOADER_TYPE_INCREMENTAL"); CHECK(constants.DATA_LOADER_TYPE_NONE == DATA_LOADER_TYPE_NONE); CHECK(constants.DATA_LOADER_TYPE_STREAMING == DATA_LOADER_TYPE_STREAMING); CHECK(constants.DATA_LOADER_TYPE_INCREMENTAL == DATA_LOADER_TYPE_INCREMENTAL); constants.DATA_LOADER_LOCATION_DATA_APP = GetStaticIntFieldValueOrDie(env, packageInstaller, "LOCATION_DATA_APP"); constants.DATA_LOADER_LOCATION_MEDIA_OBB = GetStaticIntFieldValueOrDie(env, packageInstaller, "LOCATION_MEDIA_OBB"); constants.DATA_LOADER_LOCATION_MEDIA_DATA = GetStaticIntFieldValueOrDie(env, packageInstaller, "LOCATION_MEDIA_DATA"); CHECK(constants.DATA_LOADER_LOCATION_DATA_APP == DATA_LOADER_LOCATION_DATA_APP); CHECK(constants.DATA_LOADER_LOCATION_MEDIA_OBB == DATA_LOADER_LOCATION_MEDIA_OBB); CHECK(constants.DATA_LOADER_LOCATION_MEDIA_DATA == DATA_LOADER_LOCATION_MEDIA_DATA); auto parcelFileDescriptor = FindClassOrDie(env, "android/os/ParcelFileDescriptor"); parcelFileDescriptorGetFileDescriptor = GetMethodIDOrDie(env, parcelFileDescriptor, "getFileDescriptor", "()Ljava/io/FileDescriptor;"); auto control = FindClassOrDie(env, "android/content/pm/FileSystemControlParcel"); incremental = GetFieldIDOrDie(env, control, "incremental", "Landroid/os/incremental/IncrementalFileSystemControlParcel;"); service = GetFieldIDOrDie(env, control, "service", "Landroid/os/incremental/IIncrementalServiceConnector;"); callback = GetFieldIDOrDie(env, control, "callback", "Landroid/content/pm/IPackageInstallerSessionFileSystemConnector;"); auto incControl = FindClassOrDie(env, "android/os/incremental/IncrementalFileSystemControlParcel"); controlCmd = GetFieldIDOrDie(env, incControl, "cmd", "Landroid/os/ParcelFileDescriptor;"); controlPendingReads = GetFieldIDOrDie(env, incControl, "pendingReads", "Landroid/os/ParcelFileDescriptor;"); controlLog = GetFieldIDOrDie(env, incControl, "log", "Landroid/os/ParcelFileDescriptor;"); auto params = FindClassOrDie(env, "android/content/pm/DataLoaderParamsParcel"); paramsType = GetFieldIDOrDie(env, params, "type", "I"); paramsPackageName = GetFieldIDOrDie(env, params, "packageName", "Ljava/lang/String;"); paramsClassName = GetFieldIDOrDie(env, params, "className", "Ljava/lang/String;"); paramsArguments = GetFieldIDOrDie(env, params, "arguments", "Ljava/lang/String;"); auto callbackControl = FindClassOrDie(env, "android/content/pm/IPackageInstallerSessionFileSystemConnector"); callbackControlWriteData = GetMethodIDOrDie(env, callbackControl, "writeData", "(Ljava/lang/String;JJLandroid/os/ParcelFileDescriptor;)V"); auto list = (jclass)FindClassOrDie(env, "java/util/List"); listGet = GetMethodIDOrDie(env, list, "get", "(I)Ljava/lang/Object;"); listSize = GetMethodIDOrDie(env, list, "size", "()I"); auto installationFileParcel = (jclass)FindClassOrDie(env, "android/content/pm/InstallationFileParcel"); installationFileLocation = GetFieldIDOrDie(env, installationFileParcel, "location", "I"); installationFileName = GetFieldIDOrDie(env, installationFileParcel, "name", "Ljava/lang/String;"); installationFileLengthBytes = GetFieldIDOrDie(env, installationFileParcel, "size", "J"); installationFileMetadata = GetFieldIDOrDie(env, installationFileParcel, "metadata", "[B"); auto incrementalServiceConnector = FindClassOrDie(env, "android/os/incremental/IIncrementalServiceConnector"); incrementalServiceConnectorSetStorageParams = GetMethodIDOrDie(env, incrementalServiceConnector, "setStorageParams", "(Z)I"); } }; JavaVM* getJavaVM(JNIEnv* env) { CHECK(env); JavaVM* jvm = nullptr; env->GetJavaVM(&jvm); CHECK(jvm); return jvm; } const JniIds& jniIds(JNIEnv* env) { static const JniIds ids(env); return ids; } bool reportStatusViaCallback(JNIEnv* env, jobject listener, jint storageId, jint status) { if (listener == nullptr) { ALOGE("No listener object to talk to IncrementalService. " "DataLoaderId=%d, " "status=%d", storageId, status); return false; } const auto& jni = jniIds(env); env->CallVoidMethod(listener, jni.listenerOnStatusChanged, storageId, status); ALOGI("Reported status back to IncrementalService. DataLoaderId=%d, " "status=%d", storageId, status); return true; } class DataLoaderConnector; using DataLoaderConnectorPtr = std::shared_ptr; using DataLoaderConnectorsMap = std::unordered_map; struct Globals { Globals() { managedDataLoaderFactory = new details::DataLoaderFactoryImpl( [](auto jvm, auto) { return std::make_unique(jvm); }); } DataLoaderFactory* managedDataLoaderFactory = nullptr; DataLoaderFactory* dataLoaderFactory = nullptr; std::mutex dataLoaderConnectorsLock; // id->DataLoader map DataLoaderConnectorsMap dataLoaderConnectors GUARDED_BY(dataLoaderConnectorsLock); std::atomic_bool stopped; std::thread pendingReadsLooperThread; std::thread logLooperThread; std::vector pendingReads; std::vector pageReads; }; static Globals& globals() { static Globals globals; return globals; } struct IncFsLooper : public android::Looper { IncFsLooper() : Looper(/*allowNonCallbacks=*/false) {} ~IncFsLooper() {} }; static android::Looper& pendingReadsLooper() { static IncFsLooper pendingReadsLooper; return pendingReadsLooper; } static android::Looper& logLooper() { static IncFsLooper logLooper; return logLooper; } struct DataLoaderParamsPair { static DataLoaderParamsPair createFromManaged(JNIEnv* env, jobject params); const android::dataloader::DataLoaderParams& dataLoaderParams() const { return mDataLoaderParams; } const ::DataLoaderParams& ndkDataLoaderParams() const { return mNDKDataLoaderParams; } private: DataLoaderParamsPair(android::dataloader::DataLoaderParams&& dataLoaderParams); android::dataloader::DataLoaderParams mDataLoaderParams; ::DataLoaderParams mNDKDataLoaderParams; }; static constexpr auto kPendingReadsBufferSize = 256; class DataLoaderConnector : public FilesystemConnector, public StatusListener { public: DataLoaderConnector(JNIEnv* env, jobject service, jint storageId, UniqueControl control, jobject serviceConnector, jobject callbackControl, jobject listener) : mJvm(getJavaVM(env)), mService(env->NewGlobalRef(service)), mServiceConnector(env->NewGlobalRef(serviceConnector)), mCallbackControl(env->NewGlobalRef(callbackControl)), mListener(env->NewGlobalRef(listener)), mStorageId(storageId), mControl(std::move(control)) { CHECK(mJvm != nullptr); } DataLoaderConnector(const DataLoaderConnector&) = delete; DataLoaderConnector(const DataLoaderConnector&&) = delete; virtual ~DataLoaderConnector() { JNIEnv* env = GetOrAttachJNIEnvironment(mJvm); env->DeleteGlobalRef(mService); env->DeleteGlobalRef(mServiceConnector); env->DeleteGlobalRef(mCallbackControl); env->DeleteGlobalRef(mListener); } // to avoid delete-non-virtual-dtor bool onCreate(const DataLoaderParamsPair& params, jobject managedParams) { CHECK(mDataLoader == nullptr); if (auto factory = globals().dataLoaderFactory) { // Let's try the non-default first. mDataLoader = factory->onCreate(factory, ¶ms.ndkDataLoaderParams(), this, this, mJvm, mService, managedParams); if (checkAndClearJavaException(__func__)) { return false; } } if (!mDataLoader) { // Didn't work, let's try the default. auto factory = globals().managedDataLoaderFactory; mDataLoader = factory->onCreate(factory, ¶ms.ndkDataLoaderParams(), this, this, mJvm, mService, managedParams); if (checkAndClearJavaException(__func__)) { return false; } } if (!mDataLoader) { return false; } return true; } bool onStart() { CHECK(mDataLoader); bool result = mDataLoader->onStart(mDataLoader); if (checkAndClearJavaException(__func__)) { result = false; } mRunning = result; return result; } void onStop() { CHECK(mDataLoader); // Stopping both loopers and waiting for them to exit - we should be able to acquire/release // both mutexes. mRunning = false; std::lock_guard{mPendingReadsLooperBusy}; // NOLINT std::lock_guard{mLogLooperBusy}; // NOLINT mDataLoader->onStop(mDataLoader); checkAndClearJavaException(__func__); } void onDestroy() { CHECK(mDataLoader); mDataLoader->onDestroy(mDataLoader); checkAndClearJavaException(__func__); } bool onPrepareImage(const DataLoaderInstallationFiles& addedFiles) { CHECK(mDataLoader); bool result = mDataLoader->onPrepareImage(mDataLoader, addedFiles.data(), addedFiles.size()); if (checkAndClearJavaException(__func__)) { result = false; } return result; } int onPendingReadsLooperEvent(std::vector& pendingReads) { CHECK(mDataLoader); std::lock_guard lock{mPendingReadsLooperBusy}; while (mRunning.load(std::memory_order_relaxed)) { pendingReads.resize(kPendingReadsBufferSize); if (android::incfs::waitForPendingReads(mControl, 0ms, &pendingReads) != android::incfs::WaitResult::HaveData || pendingReads.empty()) { return 1; } mDataLoader->onPendingReads(mDataLoader, pendingReads.data(), pendingReads.size()); } return 1; } int onLogLooperEvent(std::vector& pageReads) { CHECK(mDataLoader); std::lock_guard lock{mLogLooperBusy}; while (mRunning.load(std::memory_order_relaxed)) { pageReads.clear(); if (android::incfs::waitForPageReads(mControl, 0ms, &pageReads) != android::incfs::WaitResult::HaveData || pageReads.empty()) { return 1; } mDataLoader->onPageReads(mDataLoader, pageReads.data(), pageReads.size()); } return 1; } void writeData(jstring name, jlong offsetBytes, jlong lengthBytes, jobject incomingFd) const { CHECK(mCallbackControl); JNIEnv* env = GetOrAttachJNIEnvironment(mJvm); const auto& jni = jniIds(env); env->CallVoidMethod(mCallbackControl, jni.callbackControlWriteData, name, offsetBytes, lengthBytes, incomingFd); } android::incfs::UniqueFd openForSpecialOps(FileId fid) const { return android::incfs::openForSpecialOps(mControl, fid); } int writeBlocks(Span blocks) const { return android::incfs::writeBlocks(blocks); } int getRawMetadata(FileId fid, char buffer[], size_t* bufferSize) const { return IncFs_GetMetadataById(mControl, fid, buffer, bufferSize); } bool setParams(DataLoaderFilesystemParams params) const { CHECK(mServiceConnector); JNIEnv* env = GetOrAttachJNIEnvironment(mJvm); const auto& jni = jniIds(env); int result = env->CallIntMethod(mServiceConnector, jni.incrementalServiceConnectorSetStorageParams, params.readLogsEnabled); if (result != 0) { LOG(ERROR) << "setStorageParams failed with error: " << result; } if (checkAndClearJavaException(__func__)) { return false; } return (result == 0); } bool reportStatus(DataLoaderStatus status) { if (status < DATA_LOADER_FIRST_STATUS || DATA_LOADER_LAST_STATUS < status) { ALOGE("Unable to report invalid status. status=%d", status); return false; } JNIEnv* env = GetOrAttachJNIEnvironment(mJvm); return reportStatusViaCallback(env, mListener, mStorageId, status); } bool checkAndClearJavaException(std::string_view method) const { JNIEnv* env = GetOrAttachJNIEnvironment(mJvm); if (!env->ExceptionCheck()) { return false; } LOG(ERROR) << "Java exception during DataLoader::" << method; env->ExceptionDescribe(); env->ExceptionClear(); return true; } const UniqueControl& control() const { return mControl; } jobject getListenerLocalRef(JNIEnv* env) const { return env->NewLocalRef(mListener); } private: JavaVM* const mJvm; jobject const mService; jobject const mServiceConnector; jobject const mCallbackControl; jobject const mListener; jint const mStorageId; UniqueControl const mControl; ::DataLoader* mDataLoader = nullptr; std::mutex mPendingReadsLooperBusy; std::mutex mLogLooperBusy; std::atomic mRunning{false}; }; static int onPendingReadsLooperEvent(int fd, int events, void* data) { if (globals().stopped) { // No more listeners. return 0; } auto&& dataLoaderConnector = (DataLoaderConnector*)data; return dataLoaderConnector->onPendingReadsLooperEvent(globals().pendingReads); } static int onLogLooperEvent(int fd, int events, void* data) { if (globals().stopped) { // No more listeners. return 0; } auto&& dataLoaderConnector = (DataLoaderConnector*)data; return dataLoaderConnector->onLogLooperEvent(globals().pageReads); } static int createFdFromManaged(JNIEnv* env, jobject pfd) { if (!pfd) { return -1; } const auto& jni = jniIds(env); auto managedFd = env->CallObjectMethod(pfd, jni.parcelFileDescriptorGetFileDescriptor); return fcntl(jniGetFDFromFileDescriptor(env, managedFd), F_DUPFD_CLOEXEC, 0); } static jobject createServiceConnector(JNIEnv* env, jobject managedControl) { const auto& jni = jniIds(env); return env->GetObjectField(managedControl, jni.service); } static jobject createCallbackControl(JNIEnv* env, jobject managedControl) { const auto& jni = jniIds(env); return env->GetObjectField(managedControl, jni.callback); } static UniqueControl createIncFsControlFromManaged(JNIEnv* env, jobject managedControl) { const auto& jni = jniIds(env); auto managedIncControl = env->GetObjectField(managedControl, jni.incremental); if (!managedIncControl) { return UniqueControl(); } auto cmd = createFdFromManaged(env, env->GetObjectField(managedIncControl, jni.controlCmd)); auto pr = createFdFromManaged(env, env->GetObjectField(managedIncControl, jni.controlPendingReads)); auto log = createFdFromManaged(env, env->GetObjectField(managedIncControl, jni.controlLog)); return android::incfs::createControl(cmd, pr, log); } DataLoaderParamsPair::DataLoaderParamsPair(android::dataloader::DataLoaderParams&& dataLoaderParams) : mDataLoaderParams(std::move(dataLoaderParams)) { mNDKDataLoaderParams.type = mDataLoaderParams.type(); mNDKDataLoaderParams.packageName = mDataLoaderParams.packageName().c_str(); mNDKDataLoaderParams.className = mDataLoaderParams.className().c_str(); mNDKDataLoaderParams.arguments = mDataLoaderParams.arguments().c_str(); } DataLoaderParamsPair DataLoaderParamsPair::createFromManaged(JNIEnv* env, jobject managedParams) { const auto& jni = jniIds(env); const DataLoaderType type = (DataLoaderType)env->GetIntField(managedParams, jni.paramsType); ScopedLocalRef paramsPackageName(env, GetStringField(env, managedParams, jni.paramsPackageName)); ScopedLocalRef paramsClassName(env, GetStringField(env, managedParams, jni.paramsClassName)); ScopedLocalRef paramsArguments(env, GetStringField(env, managedParams, jni.paramsArguments)); ScopedUtfChars package(env, paramsPackageName.get()); ScopedUtfChars className(env, paramsClassName.get()); ScopedUtfChars arguments(env, paramsArguments.get()); return DataLoaderParamsPair(android::dataloader::DataLoaderParams(type, package.c_str(), className.c_str(), arguments.c_str())); } static void pendingReadsLooperThread() { constexpr auto kTimeoutMsecs = 60 * 1000; while (!globals().stopped) { pendingReadsLooper().pollAll(kTimeoutMsecs); } } static void logLooperThread() { constexpr auto kTimeoutMsecs = 60 * 1000; while (!globals().stopped) { logLooper().pollAll(kTimeoutMsecs); } } static std::string pathFromFd(int fd) { static constexpr char fdNameFormat[] = "/proc/self/fd/%d"; char fdNameBuffer[NELEM(fdNameFormat) + 11 + 1]; // max int length + '\0' snprintf(fdNameBuffer, NELEM(fdNameBuffer), fdNameFormat, fd); std::string res; // lstat() is supposed to return us exactly the needed buffer size, but // somehow it may also return a smaller (but still >0) st_size field. // That's why let's only use it for the initial estimate. struct stat st = {}; if (::lstat(fdNameBuffer, &st) || st.st_size == 0) { st.st_size = PATH_MAX; } auto bufSize = st.st_size; for (;;) { res.resize(bufSize + 1, '\0'); auto size = ::readlink(fdNameBuffer, &res[0], res.size()); if (size < 0) { return {}; } if (size > bufSize) { // File got renamed in between lstat() and readlink() calls? Retry. bufSize *= 2; continue; } res.resize(size); return res; } } } // namespace void DataLoader_Initialize(struct ::DataLoaderFactory* factory) { CHECK(factory) << "DataLoader factory is invalid."; globals().dataLoaderFactory = factory; } void DataLoader_FilesystemConnector_writeData(DataLoaderFilesystemConnectorPtr ifs, jstring name, jlong offsetBytes, jlong lengthBytes, jobject incomingFd) { auto connector = static_cast(ifs); return connector->writeData(name, offsetBytes, lengthBytes, incomingFd); } int DataLoader_FilesystemConnector_openForSpecialOps(DataLoaderFilesystemConnectorPtr ifs, IncFsFileId fid) { auto connector = static_cast(ifs); return connector->openForSpecialOps(fid).release(); } int DataLoader_FilesystemConnector_writeBlocks(DataLoaderFilesystemConnectorPtr ifs, const IncFsDataBlock blocks[], int blocksCount) { auto connector = static_cast(ifs); return connector->writeBlocks({blocks, static_cast(blocksCount)}); } int DataLoader_FilesystemConnector_getRawMetadata(DataLoaderFilesystemConnectorPtr ifs, IncFsFileId fid, char buffer[], size_t* bufferSize) { auto connector = static_cast(ifs); return connector->getRawMetadata(fid, buffer, bufferSize); } bool DataLoader_FilesystemConnector_setParams(DataLoaderFilesystemConnectorPtr ifs, DataLoaderFilesystemParams params) { auto connector = static_cast(ifs); return connector->setParams(params); } int DataLoader_StatusListener_reportStatus(DataLoaderStatusListenerPtr listener, DataLoaderStatus status) { auto connector = static_cast(listener); return connector->reportStatus(status); } bool DataLoaderService_OnCreate(JNIEnv* env, jobject service, jint storageId, jobject control, jobject params, jobject listener) { { std::lock_guard lock{globals().dataLoaderConnectorsLock}; auto dlIt = globals().dataLoaderConnectors.find(storageId); if (dlIt != globals().dataLoaderConnectors.end()) { ALOGI("id(%d): already exist, skipping creation.", storageId); return true; } } auto nativeControl = createIncFsControlFromManaged(env, control); ALOGI("DataLoader::create1 cmd: %d|%s", nativeControl.cmd(), pathFromFd(nativeControl.cmd()).c_str()); ALOGI("DataLoader::create1 pendingReads: %d|%s", nativeControl.pendingReads(), pathFromFd(nativeControl.pendingReads()).c_str()); ALOGI("DataLoader::create1 log: %d|%s", nativeControl.logs(), pathFromFd(nativeControl.logs()).c_str()); auto nativeParams = DataLoaderParamsPair::createFromManaged(env, params); ALOGI("DataLoader::create2: %d|%s|%s|%s", nativeParams.dataLoaderParams().type(), nativeParams.dataLoaderParams().packageName().c_str(), nativeParams.dataLoaderParams().className().c_str(), nativeParams.dataLoaderParams().arguments().c_str()); auto serviceConnector = createServiceConnector(env, control); auto callbackControl = createCallbackControl(env, control); auto reportUnavailable = [env, storageId](jobject listener) { const auto& jni = jniIds(env); reportStatusViaCallback(env, listener, storageId, jni.constants.DATA_LOADER_UNAVAILABLE); }; // By default, it's disabled. Need to assign listener to enable. std::unique_ptr<_jobject, decltype(reportUnavailable)> reportUnavailableOnExit(nullptr, reportUnavailable); auto dataLoaderConnector = std::make_unique(env, service, storageId, std::move(nativeControl), serviceConnector, callbackControl, listener); { std::lock_guard lock{globals().dataLoaderConnectorsLock}; auto [dlIt, dlInserted] = globals().dataLoaderConnectors.try_emplace(storageId, std::move(dataLoaderConnector)); if (!dlInserted) { ALOGE("id(%d): already exist, skipping creation.", storageId); return false; } if (!dlIt->second->onCreate(nativeParams, params)) { globals().dataLoaderConnectors.erase(dlIt); // Enable the reporter. reportUnavailableOnExit.reset(listener); return false; } } const auto& jni = jniIds(env); reportStatusViaCallback(env, listener, storageId, jni.constants.DATA_LOADER_CREATED); return true; } bool DataLoaderService_OnStart(JNIEnv* env, jint storageId) { auto destroyAndReportUnavailable = [env, storageId](jobject listener) { // Because of the MT the installer can call commit and recreate/restart dataLoader before // system server has a change to destroy it. DataLoaderService_OnDestroy(env, storageId); const auto& jni = jniIds(env); reportStatusViaCallback(env, listener, storageId, jni.constants.DATA_LOADER_UNAVAILABLE); }; // By default, it's disabled. Need to assign listener to enable. std::unique_ptr<_jobject, decltype(destroyAndReportUnavailable)> destroyAndReportUnavailableOnExit(nullptr, destroyAndReportUnavailable); const UniqueControl* control; jobject listener; DataLoaderConnectorPtr dataLoaderConnector; { std::lock_guard lock{globals().dataLoaderConnectorsLock}; auto dlIt = globals().dataLoaderConnectors.find(storageId); if (dlIt == globals().dataLoaderConnectors.end()) { ALOGE("Failed to start id(%d): not found", storageId); return false; } listener = dlIt->second->getListenerLocalRef(env); dataLoaderConnector = dlIt->second; if (!dataLoaderConnector->onStart()) { ALOGE("Failed to start id(%d): onStart returned false", storageId); destroyAndReportUnavailableOnExit.reset(listener); return false; } control = &(dataLoaderConnector->control()); // Create loopers while we are under lock. if (control->pendingReads() >= 0 && !globals().pendingReadsLooperThread.joinable()) { pendingReadsLooper(); globals().pendingReadsLooperThread = std::thread(&pendingReadsLooperThread); } if (control->logs() >= 0 && !globals().logLooperThread.joinable()) { logLooper(); globals().logLooperThread = std::thread(&logLooperThread); } } if (control->pendingReads() >= 0) { pendingReadsLooper().addFd(control->pendingReads(), android::Looper::POLL_CALLBACK, android::Looper::EVENT_INPUT, &onPendingReadsLooperEvent, dataLoaderConnector.get()); pendingReadsLooper().wake(); } if (control->logs() >= 0) { logLooper().addFd(control->logs(), android::Looper::POLL_CALLBACK, android::Looper::EVENT_INPUT, &onLogLooperEvent, dataLoaderConnector.get()); logLooper().wake(); } const auto& jni = jniIds(env); reportStatusViaCallback(env, listener, storageId, jni.constants.DATA_LOADER_STARTED); return true; } jobject DataLoaderService_OnStop_NoStatus(JNIEnv* env, jint storageId) { const UniqueControl* control; { std::lock_guard lock{globals().dataLoaderConnectorsLock}; auto dlIt = globals().dataLoaderConnectors.find(storageId); if (dlIt == globals().dataLoaderConnectors.end()) { return nullptr; } control = &(dlIt->second->control()); } if (control->pendingReads() >= 0) { pendingReadsLooper().removeFd(control->pendingReads()); pendingReadsLooper().wake(); } if (control->logs() >= 0) { logLooper().removeFd(control->logs()); logLooper().wake(); } jobject listener = nullptr; { std::lock_guard lock{globals().dataLoaderConnectorsLock}; auto dlIt = globals().dataLoaderConnectors.find(storageId); if (dlIt == globals().dataLoaderConnectors.end()) { ALOGI("Failed to stop id(%d): not found", storageId); return nullptr; } listener = dlIt->second->getListenerLocalRef(env); auto&& dataLoaderConnector = dlIt->second; dataLoaderConnector->onStop(); } return listener; } bool DataLoaderService_OnStop(JNIEnv* env, jint storageId) { auto listener = DataLoaderService_OnStop_NoStatus(env, storageId); if (listener == nullptr) { ALOGI("Failed to stop id(%d): not found", storageId); return true; } const auto& jni = jniIds(env); reportStatusViaCallback(env, listener, storageId, jni.constants.DATA_LOADER_STOPPED); return true; } jobject DataLoaderService_OnDestroy_NoStatus(JNIEnv* env, jint storageId) { jobject listener = DataLoaderService_OnStop_NoStatus(env, storageId); if (!listener) { return nullptr; } { std::lock_guard lock{globals().dataLoaderConnectorsLock}; auto dlIt = globals().dataLoaderConnectors.find(storageId); if (dlIt == globals().dataLoaderConnectors.end()) { return nullptr; } auto&& dataLoaderConnector = dlIt->second; dataLoaderConnector->onDestroy(); globals().dataLoaderConnectors.erase(dlIt); } return listener; } bool DataLoaderService_OnDestroy(JNIEnv* env, jint storageId) { jobject listener = DataLoaderService_OnDestroy_NoStatus(env, storageId); if (!listener) { ALOGI("Failed to remove id(%d): not found", storageId); return true; } const auto& jni = jniIds(env); reportStatusViaCallback(env, listener, storageId, jni.constants.DATA_LOADER_DESTROYED); return true; } struct DataLoaderInstallationFilesPair { static DataLoaderInstallationFilesPair createFromManaged(JNIEnv* env, jobjectArray jfiles); using Files = std::vector; const Files& files() const { return mFiles; } using NDKFiles = std::vector<::DataLoaderInstallationFile>; const NDKFiles& ndkFiles() const { return mNDKFiles; } private: DataLoaderInstallationFilesPair(Files&& files); Files mFiles; NDKFiles mNDKFiles; }; DataLoaderInstallationFilesPair DataLoaderInstallationFilesPair::createFromManaged( JNIEnv* env, jobjectArray jfiles) { const auto& jni = jniIds(env); // jfiles is a Java array of InstallationFileParcel auto size = env->GetArrayLength(jfiles); DataLoaderInstallationFilesPair::Files files; files.reserve(size); for (int i = 0; i < size; ++i) { ScopedLocalRef jfile(env, env->GetObjectArrayElement(jfiles, i)); DataLoaderLocation location = (DataLoaderLocation)env->GetIntField(jfile.get(), jni.installationFileLocation); ScopedUtfChars name(env, GetStringField(env, jfile.get(), jni.installationFileName)); IncFsSize size = env->GetLongField(jfile.get(), jni.installationFileLengthBytes); ScopedLocalRef jmetadataBytes(env, GetByteArrayField(env, jfile.get(), jni.installationFileMetadata)); auto metadataElements = env->GetByteArrayElements(jmetadataBytes.get(), nullptr); auto metadataLength = env->GetArrayLength(jmetadataBytes.get()); RawMetadata metadata(metadataElements, metadataElements + metadataLength); env->ReleaseByteArrayElements(jmetadataBytes.get(), metadataElements, 0); files.emplace_back(location, name.c_str(), size, std::move(metadata)); } return DataLoaderInstallationFilesPair(std::move(files)); } DataLoaderInstallationFilesPair::DataLoaderInstallationFilesPair(Files&& files) : mFiles(std::move(files)) { const auto size = mFiles.size(); mNDKFiles.resize(size); for (size_t i = 0; i < size; ++i) { const auto& file = mFiles[i]; auto&& ndkFile = mNDKFiles[i]; ndkFile.location = file.location(); ndkFile.name = file.name().c_str(); ndkFile.size = file.size(); ndkFile.metadata.data = file.metadata().data(); ndkFile.metadata.size = file.metadata().size(); } } bool DataLoaderService_OnPrepareImage(JNIEnv* env, jint storageId, jobjectArray addedFiles, jobjectArray removedFiles) { jobject listener; DataLoaderConnectorPtr dataLoaderConnector; { std::lock_guard lock{globals().dataLoaderConnectorsLock}; auto dlIt = globals().dataLoaderConnectors.find(storageId); if (dlIt == globals().dataLoaderConnectors.end()) { ALOGE("Failed to handle onPrepareImage for id(%d): not found", storageId); return false; } listener = dlIt->second->getListenerLocalRef(env); dataLoaderConnector = dlIt->second; } auto addedFilesPair = DataLoaderInstallationFilesPair::createFromManaged(env, addedFiles); bool result = dataLoaderConnector->onPrepareImage(addedFilesPair.ndkFiles()); const auto& jni = jniIds(env); reportStatusViaCallback(env, listener, storageId, result ? jni.constants.DATA_LOADER_IMAGE_READY : jni.constants.DATA_LOADER_IMAGE_NOT_READY); return result; }