/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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; using StorageHealthCheckParams = ::android::os::incremental::StorageHealthCheckParams; using IStorageHealthListener = ::android::os::incremental::IStorageHealthListener; using StorageHealthListener = ::android::sp; using IStorageLoadingProgressListener = ::android::os::incremental::IStorageLoadingProgressListener; using StorageLoadingProgressListener = ::android::sp; 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; 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::max() - 1; static constexpr StorageId kAllStoragesId = kMaxStorageId + 1; static constexpr BootClockTsUs kMaxBootClockTsUs = std::numeric_limits::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 metadata); static inline FileId idFromMetadata(std::span 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); 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 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 android::BnAppOpsCallback { public: AppOpsListener(IncrementalService& incrementalService, std::string packageName) : incrementalService(incrementalService), packageName(std::move(packageName)) {} void opChanged(int32_t op, const String16& packageName) final; private: IncrementalService& incrementalService; const std::string packageName; }; 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 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 needToBind(); void registerForPendingReads(); void unregisterFromPendingReads(); IncrementalService& mService; std::mutex mMutex; std::atomic mId = kInvalidStorageId; content::pm::DataLoaderParamsParcel mParams; content::pm::FileSystemControlParcel mControl; DataLoaderStatusListener mStatusListener; StorageHealthListener mHealthListener; std::atomic 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 mLastPendingReads; }; using DataLoaderStubPtr = sp; 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; using StorageMap = std::unordered_map; 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 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; using MountMap = std::unordered_map; using BindPathMap = std::map; static bool perfLoggingEnabled(); void setUidReadTimeouts(StorageId storage, std::vector&& perUidReadTimeouts); void clearUidReadTimeouts(StorageId storage); bool checkUidReadTimeouts(StorageId storage, IfsState state, Clock::time_point timeLimit); std::unordered_set adoptMountedInstances(); void mountExistingImages(const std::unordered_set& 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& mainLock); int addBindMountWithMd(IncFsMount& ifs, StorageId storage, std::string&& metadataName, std::string&& source, std::string&& target, BindKind kind, std::unique_lock& 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&& 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 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& callbacks); bool updateLoadingProgress(int32_t storageId, StorageLoadingProgressListener&& progressListener); void trimReservedSpaceV1(const IncFsMount& ifs); int64_t elapsedUsSinceMonoTs(uint64_t monoTsUs); private: const std::unique_ptr mVold; const std::unique_ptr mDataLoaderManager; const std::unique_ptr mIncFs; const std::unique_ptr mAppOpsManager; const std::unique_ptr mJni; const std::unique_ptr mLooper; const std::unique_ptr mTimedQueue; const std::unique_ptr mProgressUpdateJobQueue; const std::unique_ptr mFs; const std::unique_ptr mClock; const std::string mIncrementalDir; mutable std::mutex mLock; mutable std::mutex mMountOperationLock; MountMap mMounts; BindPathMap mBindsByPath; std::mutex mCallbacksLock; std::unordered_map> mCallbackRegistered; using IfsStateCallbacks = std::map>; std::mutex mIfsStateCallbacksLock; IfsStateCallbacks mIfsStateCallbacks; std::atomic_bool mSystemReady = false; StorageId mNextId = 0; std::atomic_bool mRunning{true}; std::unordered_map> mJobQueue; MountId mPendingJobsMount = kInvalidStorageId; std::condition_variable mJobCondition; std::mutex mJobMutex; std::thread mJobProcessor; std::thread mCmdLooperThread; }; } // namespace android::incremental