| /* | |
| * 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. | |
| */ | |
| #pragma once | |
| #include <android/content/pm/BnDataLoaderStatusListener.h> | |
| #include <android/content/pm/DataLoaderParamsParcel.h> | |
| #include <android/content/pm/FileSystemControlParcel.h> | |
| #include <android/content/pm/IDataLoaderStatusListener.h> | |
| #include <android/os/incremental/BnIncrementalService.h> | |
| #include <android/os/incremental/BnIncrementalServiceConnector.h> | |
| #include <android/os/incremental/BnStorageHealthListener.h> | |
| #include <android/os/incremental/BnStorageLoadingProgressListener.h> | |
| #include <android/os/incremental/PerUidReadTimeouts.h> | |
| #include <android/os/incremental/StorageHealthCheckParams.h> | |
| #include <binder/AppOpsManager.h> | |
| #include <binder/PersistableBundle.h> | |
| #include <utils/String16.h> | |
| #include <utils/StrongPointer.h> | |
| #include <ziparchive/zip_archive.h> | |
| #include <atomic> | |
| #include <chrono> | |
| #include <condition_variable> | |
| #include <functional> | |
| #include <limits> | |
| #include <map> | |
| #include <mutex> | |
| #include <set> | |
| #include <span> | |
| #include <string> | |
| #include <string_view> | |
| #include <thread> | |
| #include <unordered_map> | |
| #include <unordered_set> | |
| #include <utility> | |
| #include <vector> | |
| #include "ServiceWrappers.h" | |
| #include "incfs.h" | |
| #include "path.h" | |
| namespace android::incremental { | |
| using MountId = int; | |
| using StorageId = int; | |
| using FileId = incfs::FileId; | |
| using BlockIndex = incfs::BlockIndex; | |
| using RawMetadata = incfs::RawMetadata; | |
| using Seconds = std::chrono::seconds; | |
| using BootClockTsUs = uint64_t; | |
| using IDataLoaderStatusListener = ::android::content::pm::IDataLoaderStatusListener; | |
| using DataLoaderStatusListener = ::android::sp<IDataLoaderStatusListener>; | |
| using StorageHealthCheckParams = ::android::os::incremental::StorageHealthCheckParams; | |
| using IStorageHealthListener = ::android::os::incremental::IStorageHealthListener; | |
| using StorageHealthListener = ::android::sp<IStorageHealthListener>; | |
| using IStorageLoadingProgressListener = ::android::os::incremental::IStorageLoadingProgressListener; | |
| using StorageLoadingProgressListener = ::android::sp<IStorageLoadingProgressListener>; | |
| using PerUidReadTimeouts = ::android::os::incremental::PerUidReadTimeouts; | |
| struct IfsState { | |
| // If mount is fully loaded. | |
| bool fullyLoaded = false; | |
| // If read logs are enabled on this mount. Populated only if fullyLoaded == true. | |
| bool readLogsEnabled = false; | |
| // If there was an error fetching any of the above. | |
| bool error = false; | |
| }; | |
| // Returns true if wants to be called again. | |
| using IfsStateCallback = std::function<bool(StorageId, IfsState)>; | |
| class IncrementalService final { | |
| public: | |
| explicit IncrementalService(ServiceManagerWrapper&& sm, std::string_view rootDir); | |
| #pragma GCC diagnostic push | |
| #pragma GCC diagnostic ignored "-Wnon-virtual-dtor" | |
| ~IncrementalService(); | |
| #pragma GCC diagnostic pop | |
| static constexpr StorageId kInvalidStorageId = -1; | |
| static constexpr StorageId kMaxStorageId = std::numeric_limits<int>::max() - 1; | |
| static constexpr StorageId kAllStoragesId = kMaxStorageId + 1; | |
| static constexpr BootClockTsUs kMaxBootClockTsUs = std::numeric_limits<BootClockTsUs>::max(); | |
| enum CreateOptions { | |
| TemporaryBind = 1, | |
| PermanentBind = 2, | |
| CreateNew = 4, | |
| OpenExisting = 8, | |
| Default = TemporaryBind | CreateNew | |
| }; | |
| enum class BindKind { | |
| Temporary = 0, | |
| Permanent = 1, | |
| }; | |
| enum StorageFlags { | |
| ReadLogsAllowed = 1 << 0, | |
| ReadLogsEnabled = 1 << 1, | |
| ReadLogsRequested = 1 << 2, | |
| ReadTimeoutsEnabled = 1 << 3, | |
| ReadTimeoutsRequested = 1 << 4, | |
| }; | |
| struct LoadingProgress { | |
| ssize_t filledBlocks; | |
| ssize_t totalBlocks; | |
| bool isError() const { return totalBlocks < 0; } | |
| bool started() const { return totalBlocks > 0; } | |
| bool fullyLoaded() const { return !isError() && (totalBlocks == filledBlocks); } | |
| int blocksRemainingOrError() const { | |
| return totalBlocks <= 0 ? totalBlocks : totalBlocks - filledBlocks; | |
| } | |
| float getProgress() const { | |
| return totalBlocks < 0 | |
| ? totalBlocks | |
| : totalBlocks > 0 ? double(filledBlocks) / double(totalBlocks) : 1.f; | |
| } | |
| }; | |
| static FileId idFromMetadata(std::span<const uint8_t> metadata); | |
| static inline FileId idFromMetadata(std::span<const char> metadata) { | |
| return idFromMetadata({(const uint8_t*)metadata.data(), metadata.size()}); | |
| } | |
| void onDump(int fd); | |
| void onSystemReady(); | |
| StorageId createStorage(std::string_view mountPoint, | |
| content::pm::DataLoaderParamsParcel dataLoaderParams, | |
| CreateOptions options); | |
| StorageId createLinkedStorage(std::string_view mountPoint, StorageId linkedStorage, | |
| CreateOptions options = CreateOptions::Default); | |
| StorageId openStorage(std::string_view path); | |
| bool startLoading(StorageId storage, content::pm::DataLoaderParamsParcel dataLoaderParams, | |
| DataLoaderStatusListener statusListener, | |
| const StorageHealthCheckParams& healthCheckParams, | |
| StorageHealthListener healthListener, | |
| std::vector<PerUidReadTimeouts> perUidReadTimeouts); | |
| void onInstallationComplete(StorageId storage); | |
| int bind(StorageId storage, std::string_view source, std::string_view target, BindKind kind); | |
| int unbind(StorageId storage, std::string_view target); | |
| void deleteStorage(StorageId storage); | |
| void disallowReadLogs(StorageId storage); | |
| int setStorageParams(StorageId storage, bool enableReadLogs); | |
| int makeFile(StorageId storage, std::string_view path, int mode, FileId id, | |
| incfs::NewFileParams params, std::span<const uint8_t> data); | |
| int makeDir(StorageId storage, std::string_view path, int mode = 0755); | |
| int makeDirs(StorageId storage, std::string_view path, int mode = 0755); | |
| int link(StorageId sourceStorageId, std::string_view oldPath, StorageId destStorageId, | |
| std::string_view newPath); | |
| int unlink(StorageId storage, std::string_view path); | |
| incfs::LoadingState isFileFullyLoaded(StorageId storage, std::string_view filePath) const; | |
| incfs::LoadingState isMountFullyLoaded(StorageId storage) const; | |
| LoadingProgress getLoadingProgress(StorageId storage) const; | |
| bool registerLoadingProgressListener(StorageId storage, | |
| StorageLoadingProgressListener progressListener); | |
| bool unregisterLoadingProgressListener(StorageId storage); | |
| RawMetadata getMetadata(StorageId storage, std::string_view path) const; | |
| RawMetadata getMetadata(StorageId storage, FileId node) const; | |
| bool configureNativeBinaries(StorageId storage, std::string_view apkFullPath, | |
| std::string_view libDirRelativePath, std::string_view abi, | |
| bool extractNativeLibs); | |
| bool waitForNativeBinariesExtraction(StorageId storage); | |
| void getMetrics(int32_t storageId, android::os::PersistableBundle* _aidl_return); | |
| class AppOpsListener : public com::android::internal::app::BnAppOpsCallback { | |
| public: | |
| AppOpsListener(IncrementalService& incrementalService, std::string packageName, int32_t op) | |
| : incrementalService(incrementalService), | |
| packageName(std::move(packageName)), | |
| op(op) {} | |
| binder::Status opChanged(int32_t op, int32_t uid, const String16& packageName, | |
| const String16& persistentDeviceId) final; | |
| private: | |
| IncrementalService& incrementalService; | |
| const std::string packageName; | |
| const int32_t op; | |
| }; | |
| class IncrementalServiceConnector : public os::incremental::BnIncrementalServiceConnector { | |
| public: | |
| IncrementalServiceConnector(IncrementalService& incrementalService, int32_t storage) | |
| : incrementalService(incrementalService), storage(storage) {} | |
| binder::Status setStorageParams(bool enableReadLogs, int32_t* _aidl_return) final; | |
| private: | |
| IncrementalService& incrementalService; | |
| int32_t const storage; | |
| }; | |
| private: | |
| struct IncFsMount; | |
| class DataLoaderStub : public content::pm::BnDataLoaderStatusListener { | |
| public: | |
| DataLoaderStub(IncrementalService& service, MountId id, | |
| content::pm::DataLoaderParamsParcel&& params, | |
| content::pm::FileSystemControlParcel&& control, | |
| DataLoaderStatusListener&& statusListener, | |
| const StorageHealthCheckParams& healthCheckParams, | |
| StorageHealthListener&& healthListener, std::string&& healthPath); | |
| ~DataLoaderStub(); | |
| // Cleans up the internal state and invalidates DataLoaderStub. Any subsequent calls will | |
| // result in an error. | |
| void cleanupResources(); | |
| bool requestCreate(); | |
| bool requestStart(); | |
| bool requestDestroy(); | |
| void onDump(int fd); | |
| MountId id() const { return mId.load(std::memory_order_relaxed); } | |
| const content::pm::DataLoaderParamsParcel& params() const { return mParams; } | |
| bool isSystemDataLoader() const; | |
| void setHealthListener(const StorageHealthCheckParams& healthCheckParams, | |
| StorageHealthListener&& healthListener); | |
| void getMetrics(android::os::PersistableBundle* _aidl_return); | |
| private: | |
| binder::Status onStatusChanged(MountId mount, int newStatus) final; | |
| void setCurrentStatus(int newStatus); | |
| void compareAndSetCurrentStatus(int expectedStatus, int newStatus); | |
| sp<content::pm::IDataLoader> getDataLoader(); | |
| bool bind(); | |
| bool create(); | |
| bool start(); | |
| bool destroy(); | |
| bool setTargetStatus(int status); | |
| void setTargetStatusLocked(int status); | |
| bool fsmStep(); | |
| void onHealthStatus(const StorageHealthListener& healthListener, int healthStatus); | |
| void updateHealthStatus(bool baseline = false); | |
| bool isValid() const { return id() != kInvalidStorageId; } | |
| bool isHealthParamsValid() const; | |
| const incfs::UniqueControl& initializeHealthControl(); | |
| void resetHealthControl(); | |
| BootClockTsUs getOldestPendingReadTs(); | |
| BootClockTsUs getOldestTsFromLastPendingReads(); | |
| Milliseconds elapsedMsSinceKernelTs(TimePoint now, BootClockTsUs kernelTsUs); | |
| long elapsedMsSinceOldestPendingRead(); | |
| // If the stub has to bind to the DL. | |
| // Returns {} if bind operation is already in progress. | |
| // Or bind delay in ms. | |
| std::optional<Milliseconds> needToBind(); | |
| void registerForPendingReads(); | |
| void unregisterFromPendingReads(); | |
| IncrementalService& mService; | |
| std::mutex mMutex; | |
| std::atomic<MountId> mId = kInvalidStorageId; | |
| content::pm::DataLoaderParamsParcel mParams; | |
| content::pm::FileSystemControlParcel mControl; | |
| DataLoaderStatusListener mStatusListener; | |
| StorageHealthListener mHealthListener; | |
| std::atomic<int> mHealthStatus = IStorageHealthListener::HEALTH_STATUS_OK; | |
| std::condition_variable mStatusCondition; | |
| int mCurrentStatus = content::pm::IDataLoaderStatusListener::DATA_LOADER_DESTROYED; | |
| TimePoint mCurrentStatusTs = {}; | |
| int mTargetStatus = content::pm::IDataLoaderStatusListener::DATA_LOADER_DESTROYED; | |
| TimePoint mTargetStatusTs = {}; | |
| TimePoint mPreviousBindTs = {}; | |
| Milliseconds mPreviousBindDelay = {}; | |
| std::string mHealthPath; | |
| incfs::UniqueControl mHealthControl; | |
| struct { | |
| TimePoint userTs; | |
| BootClockTsUs kernelTsUs; | |
| } mHealthBase = {TimePoint::max(), kMaxBootClockTsUs}; | |
| StorageHealthCheckParams mHealthCheckParams; | |
| std::vector<incfs::ReadInfoWithUid> mLastPendingReads; | |
| }; | |
| using DataLoaderStubPtr = sp<DataLoaderStub>; | |
| struct IncFsMount { | |
| struct Bind { | |
| StorageId storage; | |
| std::string savedFilename; | |
| std::string sourceDir; | |
| BindKind kind; | |
| }; | |
| struct Storage { | |
| std::string name; | |
| }; | |
| using Control = incfs::UniqueControl; | |
| using BindMap = std::map<std::string, Bind, path::PathLess>; | |
| using StorageMap = std::unordered_map<StorageId, Storage>; | |
| mutable std::mutex lock; | |
| const std::string root; | |
| const std::string metricsKey; | |
| Control control; | |
| /*const*/ MountId mountId; | |
| int32_t flags = StorageFlags::ReadLogsAllowed; | |
| StorageMap storages; | |
| BindMap bindPoints; | |
| DataLoaderStubPtr dataLoaderStub; | |
| TimePoint startLoadingTs = {}; | |
| std::atomic<int> nextStorageDirNo{0}; | |
| const IncrementalService& incrementalService; | |
| IncFsMount(std::string root, std::string metricsKey, MountId mountId, Control control, | |
| const IncrementalService& incrementalService) | |
| : root(std::move(root)), | |
| metricsKey(std::move(metricsKey)), | |
| control(std::move(control)), | |
| mountId(mountId), | |
| incrementalService(incrementalService) {} | |
| IncFsMount(IncFsMount&&) = delete; | |
| IncFsMount& operator=(IncFsMount&&) = delete; | |
| ~IncFsMount(); | |
| StorageMap::iterator makeStorage(StorageId id); | |
| void disallowReadLogs() { flags &= ~StorageFlags::ReadLogsAllowed; } | |
| int32_t readLogsAllowed() const { return (flags & StorageFlags::ReadLogsAllowed); } | |
| void setReadLogsEnabled(bool value) { | |
| return setFlag(StorageFlags::ReadLogsEnabled, value); | |
| } | |
| int32_t readLogsEnabled() const { return (flags & StorageFlags::ReadLogsEnabled); } | |
| void setReadLogsRequested(bool value) { | |
| return setFlag(StorageFlags::ReadLogsRequested, value); | |
| } | |
| int32_t readLogsRequested() const { return (flags & StorageFlags::ReadLogsRequested); } | |
| void setReadTimeoutsEnabled(bool value) { | |
| return setFlag(StorageFlags::ReadTimeoutsEnabled, value); | |
| } | |
| int32_t readTimeoutsEnabled() const { return (flags & StorageFlags::ReadTimeoutsEnabled); } | |
| void setReadTimeoutsRequested(bool value) { | |
| return setFlag(StorageFlags::ReadTimeoutsRequested, value); | |
| } | |
| int32_t readTimeoutsRequested() const { | |
| return (flags & StorageFlags::ReadTimeoutsRequested); | |
| } | |
| static void cleanupFilesystem(std::string_view root); | |
| private: | |
| void setFlag(StorageFlags flag, bool value); | |
| }; | |
| using IfsMountPtr = std::shared_ptr<IncFsMount>; | |
| using MountMap = std::unordered_map<MountId, IfsMountPtr>; | |
| using BindPathMap = std::map<std::string, IncFsMount::BindMap::iterator, path::PathLess>; | |
| static bool perfLoggingEnabled(); | |
| void setUidReadTimeouts(StorageId storage, | |
| std::vector<PerUidReadTimeouts>&& perUidReadTimeouts); | |
| void clearUidReadTimeouts(StorageId storage); | |
| bool checkUidReadTimeouts(StorageId storage, IfsState state, Clock::time_point timeLimit); | |
| std::unordered_set<std::string_view> adoptMountedInstances(); | |
| void mountExistingImages(const std::unordered_set<std::string_view>& mountedRootNames); | |
| bool mountExistingImage(std::string_view root); | |
| IfsMountPtr getIfs(StorageId storage) const; | |
| const IfsMountPtr& getIfsLocked(StorageId storage) const; | |
| int addBindMount(IncFsMount& ifs, StorageId storage, std::string_view storageRoot, | |
| std::string&& source, std::string&& target, BindKind kind, | |
| std::unique_lock<std::mutex>& mainLock); | |
| int addBindMountWithMd(IncFsMount& ifs, StorageId storage, std::string&& metadataName, | |
| std::string&& source, std::string&& target, BindKind kind, | |
| std::unique_lock<std::mutex>& mainLock); | |
| void addBindMountRecordLocked(IncFsMount& ifs, StorageId storage, std::string&& metadataName, | |
| std::string&& source, std::string&& target, BindKind kind); | |
| bool needStartDataLoaderLocked(IncFsMount& ifs); | |
| void prepareDataLoaderLocked(IncFsMount& ifs, content::pm::DataLoaderParamsParcel&& params, | |
| DataLoaderStatusListener&& statusListener = {}, | |
| const StorageHealthCheckParams& healthCheckParams = {}, | |
| StorageHealthListener&& healthListener = {}); | |
| BindPathMap::const_iterator findStorageLocked(std::string_view path) const; | |
| StorageId findStorageId(std::string_view path) const; | |
| void deleteStorage(IncFsMount& ifs); | |
| void deleteStorageLocked(IncFsMount& ifs, std::unique_lock<std::mutex>&& ifsLock); | |
| MountMap::iterator getStorageSlotLocked(); | |
| std::string normalizePathToStorage(const IncFsMount& incfs, StorageId storage, | |
| std::string_view path) const; | |
| std::string normalizePathToStorageLocked(const IncFsMount& incfs, | |
| IncFsMount::StorageMap::const_iterator storageIt, | |
| std::string_view path) const; | |
| int makeDirs(const IncFsMount& ifs, StorageId storageId, std::string_view path, int mode); | |
| int disableReadLogsLocked(IncFsMount& ifs); | |
| int applyStorageParamsLocked(IncFsMount& ifs); | |
| LoadingProgress getLoadingProgressFromPath(const IncFsMount& ifs, std::string_view path) const; | |
| int setFileContent(const IfsMountPtr& ifs, const incfs::FileId& fileId, | |
| std::string_view debugFilePath, std::span<const uint8_t> data) const; | |
| void registerAppOpsCallback(const std::string& packageName); | |
| bool unregisterAppOpsCallback(const std::string& packageName); | |
| void onAppOpChanged(const std::string& packageName); | |
| void runJobProcessing(); | |
| void extractZipFile(const IfsMountPtr& ifs, ZipArchiveHandle zipFile, ZipEntry& entry, | |
| const incfs::FileId& libFileId, std::string_view debugLibPath, | |
| Clock::time_point scheduledTs); | |
| void runCmdLooper(); | |
| bool addTimedJob(TimedQueueWrapper& timedQueue, MountId id, Milliseconds after, Job what); | |
| bool removeTimedJobs(TimedQueueWrapper& timedQueue, MountId id); | |
| void addIfsStateCallback(StorageId storageId, IfsStateCallback callback); | |
| void removeIfsStateCallbacks(StorageId storageId); | |
| void processIfsStateCallbacks(); | |
| void processIfsStateCallbacks(StorageId storageId, std::vector<IfsStateCallback>& callbacks); | |
| bool updateLoadingProgress(int32_t storageId, | |
| StorageLoadingProgressListener&& progressListener); | |
| void trimReservedSpaceV1(const IncFsMount& ifs); | |
| int64_t elapsedUsSinceMonoTs(uint64_t monoTsUs); | |
| private: | |
| const std::unique_ptr<VoldServiceWrapper> mVold; | |
| const std::unique_ptr<DataLoaderManagerWrapper> mDataLoaderManager; | |
| const std::unique_ptr<IncFsWrapper> mIncFs; | |
| const std::unique_ptr<AppOpsManagerWrapper> mAppOpsManager; | |
| const std::unique_ptr<JniWrapper> mJni; | |
| const std::unique_ptr<LooperWrapper> mLooper; | |
| const std::unique_ptr<TimedQueueWrapper> mTimedQueue; | |
| const std::unique_ptr<TimedQueueWrapper> mProgressUpdateJobQueue; | |
| const std::unique_ptr<FsWrapper> mFs; | |
| const std::unique_ptr<ClockWrapper> mClock; | |
| const std::string mIncrementalDir; | |
| mutable std::mutex mLock; | |
| mutable std::mutex mMountOperationLock; | |
| MountMap mMounts; | |
| BindPathMap mBindsByPath; | |
| std::mutex mCallbacksLock; | |
| std::unordered_map<std::string, sp<AppOpsListener>> mCallbackRegistered; | |
| using IfsStateCallbacks = std::map<StorageId, std::vector<IfsStateCallback>>; | |
| std::mutex mIfsStateCallbacksLock; | |
| IfsStateCallbacks mIfsStateCallbacks; | |
| std::atomic_bool mSystemReady = false; | |
| StorageId mNextId = 0; | |
| std::atomic_bool mRunning{true}; | |
| std::unordered_map<MountId, std::vector<Job>> mJobQueue; | |
| MountId mPendingJobsMount = kInvalidStorageId; | |
| std::condition_variable mJobCondition; | |
| std::mutex mJobMutex; | |
| std::thread mJobProcessor; | |
| std::thread mCmdLooperThread; | |
| }; | |
| } // namespace android::incremental |