blob: f5f82ea729b09bc820115561cd851dbac322ef29 [file] [log] [blame]
/*
* 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 <android-base/logging.h>
#include <fcntl.h>
#include <nativehelper/JNIPlatformHelp.h>
#include <nativehelper/scoped_local_ref.h>
#include <nativehelper/scoped_utf_chars.h>
#include <sys/stat.h>
#include <utils/Looper.h>
#include <thread>
#include <unordered_map>
#include "JNIHelpers.h"
#include "ManagedDataLoader.h"
#include "dataloader.h"
#include "incfs.h"
#include "path.h"
namespace {
using namespace std::literals;
using ReadInfo = android::dataloader::ReadInfo;
using ReadInfoWithUid = android::dataloader::ReadInfoWithUid;
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 controlBlocksWritten;
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");
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;");
controlBlocksWritten = GetFieldIDOrDie(env, incControl, "blocksWritten",
"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 checkAndClearJavaException(JNIEnv* env, std::string_view method) {
if (!env->ExceptionCheck()) {
return false;
}
LOG(ERROR) << "Java exception during DataLoader::" << method;
env->ExceptionDescribe();
env->ExceptionClear();
return true;
}
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<DataLoaderConnector>;
using DataLoaderConnectorsMap = std::unordered_map<int, DataLoaderConnectorPtr>;
struct Globals {
Globals() { managedDataLoaderFactory = new android::dataloader::ManagedDataLoaderFactory(); }
DataLoaderFactory* managedDataLoaderFactory = nullptr;
DataLoaderFactory* legacyDataLoaderFactory = 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<ReadInfo> pendingReads;
std::vector<ReadInfo> pageReads;
std::vector<ReadInfoWithUid> pendingReadsWithUid;
std::vector<ReadInfoWithUid> pageReadsWithUid;
};
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 android::dataloader::FilesystemConnector,
public android::dataloader::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() {
if (mDataLoader && mDataLoader->onDestroy) {
mDataLoader->onDestroy(mDataLoader);
checkAndClearJavaException(__func__);
}
mDataLoader = nullptr;
JNIEnv* env = GetOrAttachJNIEnvironment(mJvm);
const auto& jni = jniIds(env);
reportStatusViaCallback(env, mListener, mStorageId, jni.constants.DATA_LOADER_DESTROYED);
env->DeleteGlobalRef(mService);
env->DeleteGlobalRef(mServiceConnector);
env->DeleteGlobalRef(mCallbackControl);
env->DeleteGlobalRef(mListener);
} // to avoid delete-non-virtual-dtor
bool tryFactory(DataLoaderFactory* factory, bool withFeatures,
const DataLoaderParamsPair& params, jobject managedParams) {
if (!factory) {
return true;
}
// Let's try the non-default first.
mDataLoader = factory->onCreate(factory, &params.ndkDataLoaderParams(), this, this, mJvm,
mService, managedParams);
if (checkAndClearJavaException(__func__)) {
return false;
}
if (!mDataLoader) {
return true;
}
mDataLoaderFeatures = withFeatures && mDataLoader->getFeatures
? mDataLoader->getFeatures(mDataLoader)
: DATA_LOADER_FEATURE_NONE;
if (mDataLoaderFeatures & DATA_LOADER_FEATURE_UID) {
ALOGE("DataLoader supports UID");
CHECK(mDataLoader->onPageReadsWithUid);
CHECK(mDataLoader->onPendingReadsWithUid);
}
return true;
}
bool onCreate(const DataLoaderParamsPair& params, jobject managedParams) {
CHECK(mDataLoader == nullptr);
if (!mDataLoader &&
!tryFactory(globals().dataLoaderFactory, /*withFeatures=*/true, params,
managedParams)) {
return false;
}
if (!mDataLoader &&
!tryFactory(globals().legacyDataLoaderFactory, /*withFeatures=*/false, params,
managedParams)) {
return false;
}
if (!mDataLoader &&
!tryFactory(globals().managedDataLoaderFactory, /*withFeatures=*/false, params,
managedParams)) {
return false;
}
if (!mDataLoader) {
return false;
}
return true;
}
bool onStart() {
CHECK(mDataLoader);
bool result = !mDataLoader->onStart || 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
if (mDataLoader->onStop) {
mDataLoader->onStop(mDataLoader);
}
checkAndClearJavaException(__func__);
}
bool onPrepareImage(const android::dataloader::DataLoaderInstallationFiles& addedFiles) {
CHECK(mDataLoader);
bool result = !mDataLoader->onPrepareImage ||
mDataLoader->onPrepareImage(mDataLoader, addedFiles.data(), addedFiles.size());
return result;
}
template <class ReadInfoType>
int onPendingReadsLooperEvent(std::vector<ReadInfoType>& 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;
}
if constexpr (std::is_same_v<ReadInfoType, ReadInfo>) {
if (mDataLoader->onPendingReads) {
mDataLoader->onPendingReads(mDataLoader, pendingReads.data(),
pendingReads.size());
}
} else {
if (mDataLoader->onPendingReadsWithUid) {
mDataLoader->onPendingReadsWithUid(mDataLoader, pendingReads.data(),
pendingReads.size());
}
}
}
return 1;
}
template <class ReadInfoType>
int onLogLooperEvent(std::vector<ReadInfoType>& 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;
}
if constexpr (std::is_same_v<ReadInfoType, ReadInfo>) {
if (mDataLoader->onPageReads) {
mDataLoader->onPageReads(mDataLoader, pageReads.data(), pageReads.size());
}
} else {
if (mDataLoader->onPageReadsWithUid) {
mDataLoader->onPageReadsWithUid(mDataLoader, pageReads.data(),
pageReads.size());
}
}
}
return 1;
}
int onPendingReadsLooperEvent(std::vector<ReadInfo>& pendingReads,
std::vector<ReadInfoWithUid>& pendingReadsWithUid) {
CHECK(mDataLoader);
if (mDataLoaderFeatures & DATA_LOADER_FEATURE_UID) {
return this->onPendingReadsLooperEvent(pendingReadsWithUid);
} else {
return this->onPendingReadsLooperEvent(pendingReads);
}
}
int onLogLooperEvent(std::vector<ReadInfo>& pageReads,
std::vector<ReadInfoWithUid>& pageReadsWithUid) {
CHECK(mDataLoader);
if (mDataLoaderFeatures & DATA_LOADER_FEATURE_UID) {
return this->onLogLooperEvent(pageReadsWithUid);
} else {
return this->onLogLooperEvent(pageReads);
}
}
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(android::dataloader::Span<const IncFsDataBlock> 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) {
JNIEnv* env = GetOrAttachJNIEnvironment(mJvm);
const auto& jni = jniIds(env);
jint osStatus;
switch (status) {
case DATA_LOADER_UNAVAILABLE:
osStatus = jni.constants.DATA_LOADER_UNAVAILABLE;
break;
case DATA_LOADER_UNRECOVERABLE:
osStatus = jni.constants.DATA_LOADER_UNRECOVERABLE;
break;
default: {
ALOGE("Unable to report invalid status. status=%d", status);
return false;
}
}
return reportStatusViaCallback(env, mListener, mStorageId, osStatus);
}
bool checkAndClearJavaException(std::string_view method) const {
JNIEnv* env = GetOrAttachJNIEnvironment(mJvm);
return ::checkAndClearJavaException(env, method);
}
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;
DataLoaderFeatures mDataLoaderFeatures = DATA_LOADER_FEATURE_NONE;
std::mutex mPendingReadsLooperBusy;
std::mutex mLogLooperBusy;
std::atomic<bool> 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,
globals().pendingReadsWithUid);
}
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, globals().pageReadsWithUid);
}
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));
auto blocksWritten =
createFdFromManaged(env,
env->GetObjectField(managedIncControl, jni.controlBlocksWritten));
return android::incfs::createControl(cmd, pr, log, blocksWritten);
}
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<jstring> paramsPackageName(env,
GetStringField(env, managedParams,
jni.paramsPackageName));
ScopedLocalRef<jstring> paramsClassName(env,
GetStringField(env, managedParams,
jni.paramsClassName));
ScopedLocalRef<jstring> 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 = -1;
while (!globals().stopped) {
pendingReadsLooper().pollAll(kTimeoutMsecs);
}
}
static void logLooperThread() {
constexpr auto kTimeoutMsecs = -1;
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().legacyDataLoaderFactory = factory;
}
void DataLoader_Initialize_WithFeatures(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<DataLoaderConnector*>(ifs);
return connector->writeData(name, offsetBytes, lengthBytes, incomingFd);
}
int DataLoader_FilesystemConnector_openForSpecialOps(DataLoaderFilesystemConnectorPtr ifs,
IncFsFileId fid) {
auto connector = static_cast<DataLoaderConnector*>(ifs);
return connector->openForSpecialOps(fid).release();
}
int DataLoader_FilesystemConnector_writeBlocks(DataLoaderFilesystemConnectorPtr ifs,
const IncFsDataBlock blocks[], int blocksCount) {
auto connector = static_cast<DataLoaderConnector*>(ifs);
return connector->writeBlocks({blocks, static_cast<size_t>(blocksCount)});
}
int DataLoader_FilesystemConnector_getRawMetadata(DataLoaderFilesystemConnectorPtr ifs,
IncFsFileId fid, char buffer[],
size_t* bufferSize) {
auto connector = static_cast<DataLoaderConnector*>(ifs);
return connector->getRawMetadata(fid, buffer, bufferSize);
}
bool DataLoader_FilesystemConnector_setParams(DataLoaderFilesystemConnectorPtr ifs,
DataLoaderFilesystemParams params) {
auto connector = static_cast<DataLoaderConnector*>(ifs);
return connector->setParams(params);
}
int DataLoader_StatusListener_reportStatus(DataLoaderStatusListenerPtr listener,
DataLoaderStatus status) {
auto connector = static_cast<DataLoaderConnector*>(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);
if (nativeControl) {
using namespace android::incfs;
ALOGI("DataLoader::create incremental fds: %d/%d/%d/%d", nativeControl.cmd(),
nativeControl.pendingReads(), nativeControl.logs(), nativeControl.blocksWritten());
auto cmdPath = pathFromFd(nativeControl.cmd());
auto dir = path::dirName(cmdPath);
ALOGI("DataLoader::create incremental dir: %s, files: %s/%s/%s/%s",
details::c_str(dir).get(), details::c_str(path::baseName(cmdPath)).get(),
details::c_str(path::baseName(pathFromFd(nativeControl.pendingReads()))).get(),
details::c_str(path::baseName(pathFromFd(nativeControl.logs()))).get(),
details::c_str(path::baseName(pathFromFd(nativeControl.blocksWritten()))).get());
} else {
ALOGI("DataLoader::create no incremental control");
}
auto nativeParams = DataLoaderParamsPair::createFromManaged(env, params);
ALOGI("DataLoader::create params: %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<DataLoaderConnector>(env, service, storageId, std::move(nativeControl),
serviceConnector, callbackControl, listener);
bool created = dataLoaderConnector->onCreate(nativeParams, params);
{
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 (!created) {
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);
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;
}
dataLoaderConnector = dlIt->second;
}
const UniqueControl* control = &(dataLoaderConnector->control());
jobject listener = dataLoaderConnector->getListenerLocalRef(env);
if (!dataLoaderConnector->onStart()) {
ALOGE("Failed to start id(%d): onStart returned false", storageId);
destroyAndReportUnavailableOnExit.reset(listener);
return false;
}
if (control->pendingReads() >= 0) {
auto&& looper = pendingReadsLooper();
if (!globals().pendingReadsLooperThread.joinable()) {
std::lock_guard lock{globals().dataLoaderConnectorsLock};
if (!globals().pendingReadsLooperThread.joinable()) {
globals().pendingReadsLooperThread = std::thread(&pendingReadsLooperThread);
}
}
looper.addFd(control->pendingReads(), android::Looper::POLL_CALLBACK,
android::Looper::EVENT_INPUT, &onPendingReadsLooperEvent,
dataLoaderConnector.get());
looper.wake();
}
if (control->logs() >= 0) {
auto&& looper = logLooper();
if (!globals().logLooperThread.joinable()) {
std::lock_guard lock{globals().dataLoaderConnectorsLock};
if (!globals().logLooperThread.joinable()) {
globals().logLooperThread = std::thread(&logLooperThread);
}
}
looper.addFd(control->logs(), android::Looper::POLL_CALLBACK, android::Looper::EVENT_INPUT,
&onLogLooperEvent, dataLoaderConnector.get());
looper.wake();
}
const auto& jni = jniIds(env);
reportStatusViaCallback(env, listener, storageId, jni.constants.DATA_LOADER_STARTED);
return true;
}
static void DataLoaderService_OnStop_NoStatus(const UniqueControl* control,
const DataLoaderConnectorPtr& dataLoaderConnector) {
if (control->pendingReads() >= 0) {
pendingReadsLooper().removeFd(control->pendingReads());
pendingReadsLooper().wake();
}
if (control->logs() >= 0) {
logLooper().removeFd(control->logs());
logLooper().wake();
}
dataLoaderConnector->onStop();
}
bool DataLoaderService_OnStop(JNIEnv* env, jint storageId) {
DataLoaderConnectorPtr dataLoaderConnector;
{
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 true;
}
dataLoaderConnector = dlIt->second;
}
const UniqueControl* control = &(dataLoaderConnector->control());
jobject listener = dataLoaderConnector->getListenerLocalRef(env);
// Just stop.
DataLoaderService_OnStop_NoStatus(control, dataLoaderConnector);
const auto& jni = jniIds(env);
reportStatusViaCallback(env, listener, storageId, jni.constants.DATA_LOADER_STOPPED);
return true;
}
bool DataLoaderService_OnDestroy(JNIEnv* env, jint storageId) {
DataLoaderConnectorPtr dataLoaderConnector;
{
std::lock_guard lock{globals().dataLoaderConnectorsLock};
auto dlIt = globals().dataLoaderConnectors.find(storageId);
if (dlIt == globals().dataLoaderConnectors.end()) {
ALOGI("Failed to destroy id(%d): not found", storageId);
return true;
}
dataLoaderConnector = std::move(dlIt->second);
globals().dataLoaderConnectors.erase(dlIt);
}
const UniqueControl* control = &(dataLoaderConnector->control());
// Stop/destroy.
DataLoaderService_OnStop_NoStatus(control, dataLoaderConnector);
// This will destroy the last instance of the DataLoaderConnectorPtr and should trigger the
// destruction of the DataLoader. However if there are any hanging instances, the destruction
// will be postponed. E.g. OnPrepareImage in progress at the same time we call OnDestroy.
dataLoaderConnector = {};
return true;
}
struct DataLoaderInstallationFilesPair {
static DataLoaderInstallationFilesPair createFromManaged(JNIEnv* env, jobjectArray jfiles);
using Files = std::vector<android::dataloader::DataLoaderInstallationFile>;
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<jobject> 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<jbyteArray> 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) {
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;
}
dataLoaderConnector = dlIt->second;
}
jobject listener = dataLoaderConnector->getListenerLocalRef(env);
auto addedFilesPair = DataLoaderInstallationFilesPair::createFromManaged(env, addedFiles);
bool result = dataLoaderConnector->onPrepareImage(addedFilesPair.ndkFiles());
const auto& jni = jniIds(env);
if (checkAndClearJavaException(env, "onPrepareImage")) {
reportStatusViaCallback(env, listener, storageId, jni.constants.DATA_LOADER_UNAVAILABLE);
return false;
}
reportStatusViaCallback(env, listener, storageId,
result ? jni.constants.DATA_LOADER_IMAGE_READY
: jni.constants.DATA_LOADER_IMAGE_NOT_READY);
return result;
}