blob: 7e6f79f0d407382e20dbd0c4ecebc28c0a23ec9e [file] [log] [blame]
/*
* Copyright (C) 2020 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 ATRACE_TAG ATRACE_TAG_ADB
#define LOG_TAG "PackageManagerShellCommandDataLoader-jni"
#include <android-base/logging.h>
#include <android-base/unique_fd.h>
#include <nativehelper/JNIHelp.h>
#include "android-base/file.h"
#include <endian.h>
#include <core_jni_helpers.h>
#include "dataloader.h"
#include <chrono>
#include <thread>
namespace android {
namespace {
using android::base::borrowed_fd;
using android::base::ReadFully;
using android::base::unique_fd;
static constexpr int BUFFER_SIZE = 256 * 1024;
static constexpr int BLOCKS_COUNT = BUFFER_SIZE / INCFS_DATA_FILE_BLOCK_SIZE;
struct JniIds {
jclass packageManagerShellCommandDataLoader;
jmethodID pmscdLookupShellCommand;
jmethodID pmscdGetStdInPFD;
jmethodID pmscdGetLocalFile;
jmethodID parcelFileDescriptorGetFileDescriptor;
jclass ioUtils;
jmethodID ioUtilsCloseQuietly;
JniIds(JNIEnv* env) {
packageManagerShellCommandDataLoader = (jclass)env->NewGlobalRef(
FindClassOrDie(env, "com/android/server/pm/PackageManagerShellCommandDataLoader"));
pmscdLookupShellCommand =
GetStaticMethodIDOrDie(env, packageManagerShellCommandDataLoader,
"lookupShellCommand",
"(Ljava/lang/String;)Landroid/os/ShellCommand;");
pmscdGetStdInPFD =
GetStaticMethodIDOrDie(env, packageManagerShellCommandDataLoader, "getStdInPFD",
"(Landroid/os/ShellCommand;)Landroid/os/"
"ParcelFileDescriptor;");
pmscdGetLocalFile =
GetStaticMethodIDOrDie(env, packageManagerShellCommandDataLoader, "getLocalFile",
"(Landroid/os/ShellCommand;Ljava/lang/String;)Landroid/os/"
"ParcelFileDescriptor;");
auto parcelFileDescriptor = FindClassOrDie(env, "android/os/ParcelFileDescriptor");
parcelFileDescriptorGetFileDescriptor =
GetMethodIDOrDie(env, parcelFileDescriptor, "getFileDescriptor",
"()Ljava/io/FileDescriptor;");
ioUtils = (jclass)env->NewGlobalRef(FindClassOrDie(env, "libcore/io/IoUtils"));
ioUtilsCloseQuietly = GetStaticMethodIDOrDie(env, ioUtils, "closeQuietly",
"(Ljava/lang/AutoCloseable;)V");
}
};
const JniIds& jniIds(JNIEnv* env) {
static const JniIds ids(env);
return ids;
}
static inline int32_t readBEInt32(borrowed_fd fd) {
int32_t result;
ReadFully(fd, &result, sizeof(result));
result = int32_t(be32toh(result));
return result;
}
static inline std::vector<char> readBytes(borrowed_fd fd) {
int32_t size = readBEInt32(fd);
std::vector<char> result(size);
ReadFully(fd, result.data(), size);
return result;
}
static inline int32_t skipIdSigHeaders(borrowed_fd fd) {
readBytes(fd); // verityRootHash
readBytes(fd); // v3Digest
readBytes(fd); // pkcs7SignatureBlock
return readBEInt32(fd); // size of the verity tree
}
static inline unique_fd convertPfdToFdAndDup(JNIEnv* env, const JniIds& jni, jobject pfd) {
if (!pfd) {
ALOGE("Missing In ParcelFileDescriptor.");
return {};
}
auto managedFd = env->CallObjectMethod(pfd, jni.parcelFileDescriptorGetFileDescriptor);
if (!pfd) {
ALOGE("Missing In FileDescriptor.");
return {};
}
unique_fd result{dup(jniGetFDFromFileDescriptor(env, managedFd))};
// Can be closed after dup.
env->CallStaticVoidMethod(jni.ioUtils, jni.ioUtilsCloseQuietly, pfd);
return result;
}
struct InputDesc {
unique_fd fd;
IncFsSize size;
IncFsBlockKind kind;
bool waitOnEof;
};
using InputDescs = std::vector<InputDesc>;
static inline InputDescs openInputs(JNIEnv* env, const JniIds& jni, jobject shellCommand,
IncFsSize size, IncFsSpan metadata) {
InputDescs result;
result.reserve(2);
const bool fromStdin = (metadata.size == 0 || *metadata.data == '-');
if (fromStdin) {
// stdin
auto fd = convertPfdToFdAndDup(
env, jni,
env->CallStaticObjectMethod(jni.packageManagerShellCommandDataLoader,
jni.pmscdGetStdInPFD, shellCommand));
if (fd.ok()) {
result.push_back(InputDesc{
.fd = std::move(fd),
.size = size,
.kind = INCFS_BLOCK_KIND_DATA,
.waitOnEof = true,
});
}
return result;
}
// local file and possibly signature
const std::string filePath(metadata.data, metadata.size);
const std::string idsigPath = filePath + ".idsig";
auto idsigFd = convertPfdToFdAndDup(
env, jni,
env->CallStaticObjectMethod(jni.packageManagerShellCommandDataLoader,
jni.pmscdGetLocalFile, shellCommand,
env->NewStringUTF(idsigPath.c_str())));
if (idsigFd.ok()) {
ALOGE("idsig found, skipping to the tree");
auto treeSize = skipIdSigHeaders(idsigFd);
result.push_back(InputDesc{
.fd = std::move(idsigFd),
.size = treeSize,
.kind = INCFS_BLOCK_KIND_HASH,
.waitOnEof = false,
});
}
auto fileFd = convertPfdToFdAndDup(
env, jni,
env->CallStaticObjectMethod(jni.packageManagerShellCommandDataLoader,
jni.pmscdGetLocalFile, shellCommand,
env->NewStringUTF(filePath.c_str())));
if (fileFd.ok()) {
result.push_back(InputDesc{
.fd = std::move(fileFd),
.size = size,
.kind = INCFS_BLOCK_KIND_DATA,
.waitOnEof = false,
});
}
return result;
}
static inline JNIEnv* GetJNIEnvironment(JavaVM* vm) {
JNIEnv* env;
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
return 0;
}
return env;
}
static inline JNIEnv* GetOrAttachJNIEnvironment(JavaVM* jvm) {
JNIEnv* env = GetJNIEnvironment(jvm);
if (!env) {
int result = jvm->AttachCurrentThread(&env, nullptr);
CHECK_EQ(result, JNI_OK) << "thread attach failed";
struct VmDetacher {
VmDetacher(JavaVM* vm) : mVm(vm) {}
~VmDetacher() { mVm->DetachCurrentThread(); }
private:
JavaVM* const mVm;
};
static thread_local VmDetacher detacher(jvm);
}
return env;
}
class PackageManagerShellCommandDataLoaderDataLoader : public android::dataloader::DataLoader {
public:
PackageManagerShellCommandDataLoaderDataLoader(JavaVM* jvm) : mJvm(jvm) { CHECK(mJvm); }
private:
// Lifecycle.
bool onCreate(const android::dataloader::DataLoaderParams& params,
android::dataloader::FilesystemConnectorPtr ifs,
android::dataloader::StatusListenerPtr statusListener,
android::dataloader::ServiceConnectorPtr,
android::dataloader::ServiceParamsPtr) final {
mArgs = params.arguments();
mIfs = ifs;
return true;
}
bool onStart() final { return true; }
void onStop() final {}
void onDestroy() final {}
// IFS callbacks.
void onPendingReads(const dataloader::PendingReads& pendingReads) final {}
void onPageReads(const dataloader::PageReads& pageReads) final {}
// FS callbacks.
bool onPrepareImage(const dataloader::DataLoaderInstallationFiles& addedFiles) final {
JNIEnv* env = GetOrAttachJNIEnvironment(mJvm);
const auto& jni = jniIds(env);
jobject shellCommand = env->CallStaticObjectMethod(jni.packageManagerShellCommandDataLoader,
jni.pmscdLookupShellCommand,
env->NewStringUTF(mArgs.c_str()));
if (!shellCommand) {
ALOGE("Missing shell command.");
return false;
}
std::vector<char> buffer;
buffer.reserve(BUFFER_SIZE);
std::vector<IncFsDataBlock> blocks;
blocks.reserve(BLOCKS_COUNT);
for (auto&& file : addedFiles) {
auto inputs = openInputs(env, jni, shellCommand, file.size, file.metadata);
if (inputs.empty()) {
ALOGE("Failed to open an input file for metadata: %.*s, final file name is: %s. "
"Error %d",
int(file.metadata.size), file.metadata.data, file.name, errno);
return false;
}
const auto fileId = IncFs_FileIdFromMetadata(file.metadata);
const auto incfsFd(mIfs->openWrite(fileId));
if (incfsFd < 0) {
ALOGE("Failed to open an IncFS file for metadata: %.*s, final file name is: %s. "
"Error %d",
int(file.metadata.size), file.metadata.data, file.name, errno);
return false;
}
for (auto&& input : inputs) {
if (!copyToIncFs(incfsFd, input.size, input.kind, input.fd, input.waitOnEof,
&buffer, &blocks)) {
ALOGE("Failed to copy data to IncFS file for metadata: %.*s, final file name "
"is: %s. "
"Error %d",
int(file.metadata.size), file.metadata.data, file.name, errno);
return false;
}
}
}
ALOGE("All done.");
return true;
}
bool copyToIncFs(borrowed_fd incfsFd, IncFsSize size, IncFsBlockKind kind,
borrowed_fd incomingFd, bool waitOnEof, std::vector<char>* buffer,
std::vector<IncFsDataBlock>* blocks) {
IncFsSize remaining = size;
IncFsSize totalSize = 0;
IncFsBlockIndex blockIdx = 0;
while (remaining > 0) {
constexpr auto capacity = BUFFER_SIZE;
auto size = buffer->size();
if (capacity - size < INCFS_DATA_FILE_BLOCK_SIZE) {
if (!flashToIncFs(incfsFd, kind, false, &blockIdx, buffer, blocks)) {
return false;
}
continue;
}
auto toRead = std::min<IncFsSize>(remaining, capacity - size);
buffer->resize(size + toRead);
auto read = ::read(incomingFd.get(), buffer->data() + size, toRead);
if (read == 0) {
if (waitOnEof) {
// eof of stdin, waiting...
ALOGE("eof of stdin, waiting...: %d, remaining: %d, block: %d, read: %d",
int(totalSize), int(remaining), int(blockIdx), int(read));
using namespace std::chrono_literals;
std::this_thread::sleep_for(10ms);
continue;
}
break;
}
if (read < 0) {
return false;
}
buffer->resize(size + read);
remaining -= read;
totalSize += read;
}
if (!buffer->empty() && !flashToIncFs(incfsFd, kind, true, &blockIdx, buffer, blocks)) {
return false;
}
return true;
}
bool flashToIncFs(borrowed_fd incfsFd, IncFsBlockKind kind, bool eof, IncFsBlockIndex* blockIdx,
std::vector<char>* buffer, std::vector<IncFsDataBlock>* blocks) {
int consumed = 0;
const auto fullBlocks = buffer->size() / INCFS_DATA_FILE_BLOCK_SIZE;
for (int i = 0; i < fullBlocks; ++i) {
const auto inst = IncFsDataBlock{
.fileFd = incfsFd.get(),
.pageIndex = (*blockIdx)++,
.compression = INCFS_COMPRESSION_KIND_NONE,
.kind = kind,
.dataSize = INCFS_DATA_FILE_BLOCK_SIZE,
.data = buffer->data() + consumed,
};
blocks->push_back(inst);
consumed += INCFS_DATA_FILE_BLOCK_SIZE;
}
const auto remain = buffer->size() - fullBlocks * INCFS_DATA_FILE_BLOCK_SIZE;
if (remain && eof) {
const auto inst = IncFsDataBlock{
.fileFd = incfsFd.get(),
.pageIndex = (*blockIdx)++,
.compression = INCFS_COMPRESSION_KIND_NONE,
.kind = kind,
.dataSize = static_cast<uint16_t>(remain),
.data = buffer->data() + consumed,
};
blocks->push_back(inst);
consumed += remain;
}
auto res = mIfs->writeBlocks({blocks->data(), blocks->data() + blocks->size()});
blocks->clear();
buffer->erase(buffer->begin(), buffer->begin() + consumed);
if (res < 0) {
ALOGE("Failed to write block to IncFS: %d", int(res));
return false;
}
return true;
}
JavaVM* const mJvm;
std::string mArgs;
android::dataloader::FilesystemConnectorPtr mIfs;
};
static void nativeInitialize(JNIEnv* env, jclass klass) {
jniIds(env);
}
static const JNINativeMethod method_table[] = {
{"nativeInitialize", "()V", (void*)nativeInitialize},
};
} // namespace
int register_android_server_com_android_server_pm_PackageManagerShellCommandDataLoader(
JNIEnv* env) {
android::dataloader::DataLoader::initialize(
[](auto jvm, const auto& params) -> android::dataloader::DataLoaderPtr {
if (params.type() == DATA_LOADER_TYPE_INCREMENTAL) {
// This DataLoader only supports incremental installations.
return std::make_unique<PackageManagerShellCommandDataLoaderDataLoader>(jvm);
}
return {};
});
return jniRegisterNativeMethods(env,
"com/android/server/pm/PackageManagerShellCommandDataLoader",
method_table, NELEM(method_table));
}
} // namespace android