| /* | |
| * 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; | |
| auto conditionPredicate = [this, oldNextJobTs = nextJobTs]() { | |
| const auto now = Clock::now(); | |
| const auto newFirstJobTs = !mJobs.empty() ? mJobs.begin()->when : kInfinityTs; | |
| return newFirstJobTs <= now || newFirstJobTs < oldNextJobTs || !mRunning; | |
| }; | |
| // libcxx's implementation of wait_until() recalculates the 'until' time into | |
| // the wait duration and then goes back to the absolute timestamp when calling | |
| // pthread_cond_timedwait(); this back-and-forth calculation sometimes loses | |
| // the 'infinity' value because enough time passes in between, and instead | |
| // passes incorrect timestamp into the syscall, causing a crash. | |
| // Mitigating it by explicitly calling the non-timed wait here. | |
| if (mJobs.empty()) { | |
| mCondition.wait(lock, conditionPredicate); | |
| } else { | |
| mCondition.wait_until(lock, nextJobTs, conditionPredicate); | |
| } | |
| 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 |