You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
498 lines
18 KiB
498 lines
18 KiB
/*
|
|
* 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 "ServiceWrappers.h"
|
|
|
|
#include <MountRegistry.h>
|
|
#include <android-base/logging.h>
|
|
#include <android/content/pm/IDataLoaderManager.h>
|
|
#include <android/os/IVold.h>
|
|
#include <binder/AppOpsManager.h>
|
|
#include <utils/String16.h>
|
|
|
|
#include <filesystem>
|
|
#include <thread>
|
|
|
|
#include "IncrementalServiceValidation.h"
|
|
|
|
using namespace std::literals;
|
|
|
|
namespace android::incremental {
|
|
|
|
static constexpr auto kVoldServiceName = "vold"sv;
|
|
static constexpr auto kDataLoaderManagerName = "dataloader_manager"sv;
|
|
|
|
class RealVoldService : public VoldServiceWrapper {
|
|
public:
|
|
RealVoldService(sp<os::IVold> vold) : mInterface(std::move(vold)) {}
|
|
~RealVoldService() = default;
|
|
binder::Status mountIncFs(
|
|
const std::string& backingPath, const std::string& targetDir, int32_t flags,
|
|
const std::string& sysfsName,
|
|
os::incremental::IncrementalFileSystemControlParcel* _aidl_return) const final {
|
|
return mInterface->mountIncFs(backingPath, targetDir, flags, sysfsName, _aidl_return);
|
|
}
|
|
binder::Status unmountIncFs(const std::string& dir) const final {
|
|
return mInterface->unmountIncFs(dir);
|
|
}
|
|
binder::Status bindMount(const std::string& sourceDir,
|
|
const std::string& targetDir) const final {
|
|
return mInterface->bindMount(sourceDir, targetDir);
|
|
}
|
|
binder::Status setIncFsMountOptions(
|
|
const ::android::os::incremental::IncrementalFileSystemControlParcel& control,
|
|
bool enableReadLogs, bool enableReadTimeouts,
|
|
const std::string& sysfsName) const final {
|
|
return mInterface->setIncFsMountOptions(control, enableReadLogs, enableReadTimeouts,
|
|
sysfsName);
|
|
}
|
|
|
|
private:
|
|
sp<os::IVold> mInterface;
|
|
};
|
|
|
|
class RealDataLoaderManager : public DataLoaderManagerWrapper {
|
|
public:
|
|
RealDataLoaderManager(sp<content::pm::IDataLoaderManager> manager)
|
|
: mInterface(std::move(manager)) {}
|
|
~RealDataLoaderManager() = default;
|
|
binder::Status bindToDataLoader(MountId mountId,
|
|
const content::pm::DataLoaderParamsParcel& params,
|
|
int bindDelayMs,
|
|
const sp<content::pm::IDataLoaderStatusListener>& listener,
|
|
bool* _aidl_return) const final {
|
|
return mInterface->bindToDataLoader(mountId, params, bindDelayMs, listener, _aidl_return);
|
|
}
|
|
binder::Status getDataLoader(MountId mountId,
|
|
sp<content::pm::IDataLoader>* _aidl_return) const final {
|
|
return mInterface->getDataLoader(mountId, _aidl_return);
|
|
}
|
|
binder::Status unbindFromDataLoader(MountId mountId) const final {
|
|
return mInterface->unbindFromDataLoader(mountId);
|
|
}
|
|
|
|
private:
|
|
sp<content::pm::IDataLoaderManager> mInterface;
|
|
};
|
|
|
|
class RealAppOpsManager : public AppOpsManagerWrapper {
|
|
public:
|
|
~RealAppOpsManager() = default;
|
|
binder::Status checkPermission(const char* permission, const char* operation,
|
|
const char* package) const final {
|
|
return android::incremental::CheckPermissionForDataDelivery(permission, operation, package);
|
|
}
|
|
void startWatchingMode(int32_t op, const String16& packageName,
|
|
const sp<IAppOpsCallback>& callback) final {
|
|
mAppOpsManager.startWatchingMode(op, packageName, callback);
|
|
}
|
|
void stopWatchingMode(const sp<IAppOpsCallback>& callback) final {
|
|
mAppOpsManager.stopWatchingMode(callback);
|
|
}
|
|
|
|
private:
|
|
android::AppOpsManager mAppOpsManager;
|
|
};
|
|
|
|
class RealJniWrapper final : public JniWrapper {
|
|
public:
|
|
RealJniWrapper(JavaVM* jvm);
|
|
void initializeForCurrentThread() const final;
|
|
|
|
static JavaVM* getJvm(JNIEnv* env);
|
|
|
|
private:
|
|
JavaVM* const mJvm;
|
|
};
|
|
|
|
class RealLooperWrapper final : public LooperWrapper {
|
|
public:
|
|
int addFd(int fd, int ident, int events, android::Looper_callbackFunc callback,
|
|
void* data) final {
|
|
return mLooper.addFd(fd, ident, events, callback, data);
|
|
}
|
|
int removeFd(int fd) final { return mLooper.removeFd(fd); }
|
|
void wake() final { return mLooper.wake(); }
|
|
int pollAll(int timeoutMillis) final { return mLooper.pollAll(timeoutMillis); }
|
|
|
|
private:
|
|
struct Looper : public android::Looper {
|
|
Looper() : android::Looper(/*allowNonCallbacks=*/false) {}
|
|
~Looper() {}
|
|
} mLooper;
|
|
};
|
|
|
|
std::string IncFsWrapper::toString(FileId fileId) {
|
|
return incfs::toString(fileId);
|
|
}
|
|
|
|
class RealIncFs final : public IncFsWrapper {
|
|
public:
|
|
RealIncFs() = default;
|
|
~RealIncFs() final = default;
|
|
Features features() const final { return incfs::features(); }
|
|
void listExistingMounts(const ExistingMountCallback& cb) const final {
|
|
for (auto mount : incfs::defaultMountRegistry().copyMounts()) {
|
|
auto binds = mount.binds(); // span() doesn't like rvalue containers, needs to save it.
|
|
cb(mount.root(), mount.backingDir(), binds);
|
|
}
|
|
}
|
|
Control openMount(std::string_view path) const final { return incfs::open(path); }
|
|
Control createControl(IncFsFd cmd, IncFsFd pendingReads, IncFsFd logs,
|
|
IncFsFd blocksWritten) const final {
|
|
return incfs::createControl(cmd, pendingReads, logs, blocksWritten);
|
|
}
|
|
ErrorCode makeFile(const Control& control, std::string_view path, int mode, FileId id,
|
|
incfs::NewFileParams params) const final {
|
|
return incfs::makeFile(control, path, mode, id, params);
|
|
}
|
|
ErrorCode makeMappedFile(const Control& control, std::string_view path, int mode,
|
|
incfs::NewMappedFileParams params) const final {
|
|
return incfs::makeMappedFile(control, path, mode, params);
|
|
}
|
|
ErrorCode makeDir(const Control& control, std::string_view path, int mode) const final {
|
|
return incfs::makeDir(control, path, mode);
|
|
}
|
|
ErrorCode makeDirs(const Control& control, std::string_view path, int mode) const final {
|
|
return incfs::makeDirs(control, path, mode);
|
|
}
|
|
incfs::RawMetadata getMetadata(const Control& control, FileId fileid) const final {
|
|
return incfs::getMetadata(control, fileid);
|
|
}
|
|
incfs::RawMetadata getMetadata(const Control& control, std::string_view path) const final {
|
|
return incfs::getMetadata(control, path);
|
|
}
|
|
FileId getFileId(const Control& control, std::string_view path) const final {
|
|
return incfs::getFileId(control, path);
|
|
}
|
|
std::pair<IncFsBlockIndex, IncFsBlockIndex> countFilledBlocks(
|
|
const Control& control, std::string_view path) const final {
|
|
if (incfs::features() & Features::v2) {
|
|
const auto counts = incfs::getBlockCount(control, path);
|
|
if (!counts) {
|
|
return {-errno, -errno};
|
|
}
|
|
return {counts->filledDataBlocks + counts->filledHashBlocks,
|
|
counts->totalDataBlocks + counts->totalHashBlocks};
|
|
}
|
|
const auto fileId = incfs::getFileId(control, path);
|
|
const auto fd = incfs::openForSpecialOps(control, fileId);
|
|
int res = fd.get();
|
|
if (!fd.ok()) {
|
|
return {res, res};
|
|
}
|
|
const auto ranges = incfs::getFilledRanges(res);
|
|
res = ranges.first;
|
|
if (res) {
|
|
return {res, res};
|
|
}
|
|
const auto totalBlocksCount = ranges.second.internalRawRanges().endIndex;
|
|
int filledBlockCount = 0;
|
|
for (const auto& dataRange : ranges.second.dataRanges()) {
|
|
filledBlockCount += dataRange.size();
|
|
}
|
|
for (const auto& hashRange : ranges.second.hashRanges()) {
|
|
filledBlockCount += hashRange.size();
|
|
}
|
|
return {filledBlockCount, totalBlocksCount};
|
|
}
|
|
incfs::LoadingState isFileFullyLoaded(const Control& control,
|
|
std::string_view path) const final {
|
|
return incfs::isFullyLoaded(control, path);
|
|
}
|
|
incfs::LoadingState isFileFullyLoaded(const Control& control, FileId id) const final {
|
|
return incfs::isFullyLoaded(control, id);
|
|
}
|
|
incfs::LoadingState isEverythingFullyLoaded(const Control& control) const final {
|
|
return incfs::isEverythingFullyLoaded(control);
|
|
}
|
|
ErrorCode link(const Control& control, std::string_view from, std::string_view to) const final {
|
|
return incfs::link(control, from, to);
|
|
}
|
|
ErrorCode unlink(const Control& control, std::string_view path) const final {
|
|
return incfs::unlink(control, path);
|
|
}
|
|
incfs::UniqueFd openForSpecialOps(const Control& control, FileId id) const final {
|
|
return incfs::openForSpecialOps(control, id);
|
|
}
|
|
ErrorCode writeBlocks(std::span<const incfs::DataBlock> blocks) const final {
|
|
return incfs::writeBlocks({blocks.data(), size_t(blocks.size())});
|
|
}
|
|
ErrorCode reserveSpace(const Control& control, FileId id, IncFsSize size) const final {
|
|
return incfs::reserveSpace(control, id, size);
|
|
}
|
|
WaitResult waitForPendingReads(
|
|
const Control& control, std::chrono::milliseconds timeout,
|
|
std::vector<incfs::ReadInfoWithUid>* pendingReadsBuffer) const final {
|
|
return incfs::waitForPendingReads(control, timeout, pendingReadsBuffer);
|
|
}
|
|
ErrorCode setUidReadTimeouts(const Control& control,
|
|
const std::vector<android::os::incremental::PerUidReadTimeouts>&
|
|
perUidReadTimeouts) const final {
|
|
std::vector<incfs::UidReadTimeouts> timeouts(perUidReadTimeouts.size());
|
|
for (int i = 0, size = perUidReadTimeouts.size(); i < size; ++i) {
|
|
auto& timeout = timeouts[i];
|
|
const auto& perUidTimeout = perUidReadTimeouts[i];
|
|
timeout.uid = perUidTimeout.uid;
|
|
timeout.minTimeUs = perUidTimeout.minTimeUs;
|
|
timeout.minPendingTimeUs = perUidTimeout.minPendingTimeUs;
|
|
timeout.maxPendingTimeUs = perUidTimeout.maxPendingTimeUs;
|
|
}
|
|
return incfs::setUidReadTimeouts(control, timeouts);
|
|
}
|
|
ErrorCode forEachFile(const Control& control, FileCallback cb) const final {
|
|
return incfs::forEachFile(control,
|
|
[&](auto& control, FileId id) { return cb(control, id); });
|
|
}
|
|
ErrorCode forEachIncompleteFile(const Control& control, FileCallback cb) const final {
|
|
return incfs::forEachIncompleteFile(control, [&](auto& control, FileId id) {
|
|
return cb(control, id);
|
|
});
|
|
}
|
|
std::optional<Metrics> getMetrics(std::string_view sysfsName) const final {
|
|
return incfs::getMetrics(sysfsName);
|
|
}
|
|
std::optional<LastReadError> getLastReadError(const Control& control) const final {
|
|
return incfs::getLastReadError(control);
|
|
}
|
|
};
|
|
|
|
static JNIEnv* getOrAttachJniEnv(JavaVM* jvm);
|
|
|
|
class RealTimedQueueWrapper final : public TimedQueueWrapper {
|
|
public:
|
|
RealTimedQueueWrapper(JavaVM* jvm) {
|
|
mThread = std::thread([this, jvm]() {
|
|
(void)getOrAttachJniEnv(jvm);
|
|
runTimers();
|
|
});
|
|
}
|
|
~RealTimedQueueWrapper() final {
|
|
CHECK(!mRunning) << "call stop first";
|
|
CHECK(!mThread.joinable()) << "call stop first";
|
|
}
|
|
|
|
void addJob(MountId id, Milliseconds timeout, Job what) final {
|
|
const auto now = Clock::now();
|
|
{
|
|
std::unique_lock lock(mMutex);
|
|
mJobs.insert(TimedJob{id, now + timeout, std::move(what)});
|
|
}
|
|
mCondition.notify_all();
|
|
}
|
|
void removeJobs(MountId id) final {
|
|
std::unique_lock lock(mMutex);
|
|
std::erase_if(mJobs, [id](auto&& item) { return item.id == id; });
|
|
}
|
|
void stop() final {
|
|
{
|
|
std::unique_lock lock(mMutex);
|
|
mRunning = false;
|
|
}
|
|
mCondition.notify_all();
|
|
mThread.join();
|
|
mJobs.clear();
|
|
}
|
|
|
|
private:
|
|
void runTimers() {
|
|
static constexpr TimePoint kInfinityTs{Clock::duration::max()};
|
|
std::unique_lock lock(mMutex);
|
|
for (;;) {
|
|
const TimePoint nextJobTs = mJobs.empty() ? kInfinityTs : mJobs.begin()->when;
|
|
mCondition.wait_until(lock, nextJobTs, [this, oldNextJobTs = nextJobTs]() {
|
|
const auto now = Clock::now();
|
|
const auto newFirstJobTs = !mJobs.empty() ? mJobs.begin()->when : kInfinityTs;
|
|
return newFirstJobTs <= now || newFirstJobTs < oldNextJobTs || !mRunning;
|
|
});
|
|
if (!mRunning) {
|
|
return;
|
|
}
|
|
|
|
const auto now = Clock::now();
|
|
// Always re-acquire begin(). We can't use it after unlock as mTimedJobs can change.
|
|
for (auto it = mJobs.begin(); it != mJobs.end() && it->when <= now;
|
|
it = mJobs.begin()) {
|
|
auto jobNode = mJobs.extract(it);
|
|
|
|
lock.unlock();
|
|
jobNode.value().what();
|
|
lock.lock();
|
|
}
|
|
}
|
|
}
|
|
|
|
struct TimedJob {
|
|
MountId id;
|
|
TimePoint when;
|
|
Job what;
|
|
friend bool operator<(const TimedJob& lhs, const TimedJob& rhs) {
|
|
return lhs.when < rhs.when;
|
|
}
|
|
};
|
|
bool mRunning = true;
|
|
std::multiset<TimedJob> mJobs;
|
|
std::condition_variable mCondition;
|
|
std::mutex mMutex;
|
|
std::thread mThread;
|
|
};
|
|
|
|
class RealFsWrapper final : public FsWrapper {
|
|
public:
|
|
RealFsWrapper() = default;
|
|
~RealFsWrapper() = default;
|
|
|
|
void listFilesRecursive(std::string_view directoryPath, FileCallback onFile) const final {
|
|
for (const auto& entry : std::filesystem::recursive_directory_iterator(directoryPath)) {
|
|
if (!entry.is_regular_file()) {
|
|
continue;
|
|
}
|
|
if (!onFile(entry.path().native())) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
class RealClockWrapper final : public ClockWrapper {
|
|
public:
|
|
RealClockWrapper() = default;
|
|
~RealClockWrapper() = default;
|
|
|
|
TimePoint now() const final { return Clock::now(); }
|
|
};
|
|
|
|
RealServiceManager::RealServiceManager(sp<IServiceManager> serviceManager, JNIEnv* env)
|
|
: mServiceManager(std::move(serviceManager)), mJvm(RealJniWrapper::getJvm(env)) {}
|
|
|
|
template <class INTERFACE>
|
|
sp<INTERFACE> RealServiceManager::getRealService(std::string_view serviceName) const {
|
|
sp<IBinder> binder =
|
|
mServiceManager->getService(String16(serviceName.data(), serviceName.size()));
|
|
if (!binder) {
|
|
return nullptr;
|
|
}
|
|
return interface_cast<INTERFACE>(binder);
|
|
}
|
|
|
|
std::unique_ptr<VoldServiceWrapper> RealServiceManager::getVoldService() {
|
|
sp<os::IVold> vold = RealServiceManager::getRealService<os::IVold>(kVoldServiceName);
|
|
if (vold != 0) {
|
|
return std::make_unique<RealVoldService>(vold);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
std::unique_ptr<DataLoaderManagerWrapper> RealServiceManager::getDataLoaderManager() {
|
|
sp<content::pm::IDataLoaderManager> manager =
|
|
RealServiceManager::getRealService<content::pm::IDataLoaderManager>(
|
|
kDataLoaderManagerName);
|
|
if (manager) {
|
|
return std::make_unique<RealDataLoaderManager>(manager);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
std::unique_ptr<IncFsWrapper> RealServiceManager::getIncFs() {
|
|
return std::make_unique<RealIncFs>();
|
|
}
|
|
|
|
std::unique_ptr<AppOpsManagerWrapper> RealServiceManager::getAppOpsManager() {
|
|
return std::make_unique<RealAppOpsManager>();
|
|
}
|
|
|
|
std::unique_ptr<JniWrapper> RealServiceManager::getJni() {
|
|
return std::make_unique<RealJniWrapper>(mJvm);
|
|
}
|
|
|
|
std::unique_ptr<LooperWrapper> RealServiceManager::getLooper() {
|
|
return std::make_unique<RealLooperWrapper>();
|
|
}
|
|
|
|
std::unique_ptr<TimedQueueWrapper> RealServiceManager::getTimedQueue() {
|
|
return std::make_unique<RealTimedQueueWrapper>(mJvm);
|
|
}
|
|
|
|
std::unique_ptr<TimedQueueWrapper> RealServiceManager::getProgressUpdateJobQueue() {
|
|
return std::make_unique<RealTimedQueueWrapper>(mJvm);
|
|
}
|
|
|
|
std::unique_ptr<FsWrapper> RealServiceManager::getFs() {
|
|
return std::make_unique<RealFsWrapper>();
|
|
}
|
|
|
|
std::unique_ptr<ClockWrapper> RealServiceManager::getClock() {
|
|
return std::make_unique<RealClockWrapper>();
|
|
}
|
|
|
|
static JavaVM* getJavaVm(JNIEnv* env) {
|
|
CHECK(env);
|
|
JavaVM* jvm = nullptr;
|
|
env->GetJavaVM(&jvm);
|
|
CHECK(jvm);
|
|
return jvm;
|
|
}
|
|
|
|
static JNIEnv* getJniEnv(JavaVM* vm) {
|
|
JNIEnv* env;
|
|
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
|
|
return nullptr;
|
|
}
|
|
return env;
|
|
}
|
|
|
|
static JNIEnv* getOrAttachJniEnv(JavaVM* jvm) {
|
|
if (!jvm) {
|
|
LOG(ERROR) << "No JVM instance";
|
|
return nullptr;
|
|
}
|
|
|
|
JNIEnv* env = getJniEnv(jvm);
|
|
if (!env) {
|
|
int result = jvm->AttachCurrentThread(&env, nullptr);
|
|
if (result != JNI_OK) {
|
|
LOG(ERROR) << "JVM thread attach failed: " << result;
|
|
return nullptr;
|
|
}
|
|
struct VmDetacher {
|
|
VmDetacher(JavaVM* vm) : mVm(vm) {}
|
|
~VmDetacher() { mVm->DetachCurrentThread(); }
|
|
|
|
private:
|
|
JavaVM* const mVm;
|
|
};
|
|
static thread_local VmDetacher detacher(jvm);
|
|
}
|
|
|
|
return env;
|
|
}
|
|
|
|
RealJniWrapper::RealJniWrapper(JavaVM* jvm) : mJvm(jvm) {
|
|
CHECK(!!mJvm) << "JVM is unavailable";
|
|
}
|
|
|
|
void RealJniWrapper::initializeForCurrentThread() const {
|
|
(void)getOrAttachJniEnv(mJvm);
|
|
}
|
|
|
|
JavaVM* RealJniWrapper::getJvm(JNIEnv* env) {
|
|
return getJavaVm(env);
|
|
}
|
|
|
|
} // namespace android::incremental
|