diff options
author | 2020-08-25 12:45:22 -0700 | |
---|---|---|
committer | 2020-08-27 10:33:46 -0700 | |
commit | bc0a7e6cbf4a56c1451e8e03040974b1cc0a782d (patch) | |
tree | eddfd43620f7c3aadb6ff7428b46f61fe9c0ce25 | |
parent | edbf3411d2c91f38a1a5d93b9e5b3380524b3ed8 (diff) |
Wait for APK to be fully downloaded for full APK digests.
Bug: 160605420
Test: atest ChecksumsTest
Change-Id: Ib9fd591c67290786268b6dcdc57c7db153612e01
12 files changed, 365 insertions, 51 deletions
diff --git a/core/java/android/os/incremental/IIncrementalService.aidl b/core/java/android/os/incremental/IIncrementalService.aidl index be8b9297ea6a..f351c7df8d5d 100644 --- a/core/java/android/os/incremental/IIncrementalService.aidl +++ b/core/java/android/os/incremental/IIncrementalService.aidl @@ -90,6 +90,14 @@ interface IIncrementalService { int unlink(int storageId, in @utf8InCpp String path); /** + * Checks if a file is fully loaded. File is specified by its path. + * 0 - fully loaded + * >0 - certain pages missing + * <0 - -errcode + */ + int isFileFullyLoaded(int storageId, in @utf8InCpp String path); + + /** * Returns overall loading progress of all the files on a storage, progress value between [0,1]. * Returns a negative value on error. */ diff --git a/core/java/android/os/incremental/IncrementalStorage.java b/core/java/android/os/incremental/IncrementalStorage.java index b8dbfbb9d109..ed386f79882f 100644 --- a/core/java/android/os/incremental/IncrementalStorage.java +++ b/core/java/android/os/incremental/IncrementalStorage.java @@ -304,6 +304,25 @@ public final class IncrementalStorage { } /** + * Checks whether a file under the current storage directory is fully loaded. + * + * @param path The relative path of the file. + * @return True if the file is fully loaded. + */ + public boolean isFileFullyLoaded(@NonNull String path) throws IOException { + try { + int res = mService.isFileFullyLoaded(mId, path); + if (res < 0) { + throw new IOException("isFileFullyLoaded() failed, errno " + -res); + } + return res == 0; + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + return false; + } + } + + /** * Returns the loading progress of a storage * * @return progress value between [0, 1]. diff --git a/services/core/java/com/android/server/pm/ApkChecksums.java b/services/core/java/com/android/server/pm/ApkChecksums.java index d8745abb4a07..0338ed802436 100644 --- a/services/core/java/com/android/server/pm/ApkChecksums.java +++ b/services/core/java/com/android/server/pm/ApkChecksums.java @@ -16,6 +16,7 @@ package com.android.server.pm; +import static android.content.pm.PackageManager.EXTRA_CHECKSUMS; import static android.content.pm.PackageManager.PARTIAL_MERKLE_ROOT_1M_SHA256; import static android.content.pm.PackageManager.PARTIAL_MERKLE_ROOT_1M_SHA512; import static android.content.pm.PackageManager.WHOLE_MD5; @@ -27,11 +28,20 @@ import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA25 import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512; import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256; +import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.Context; +import android.content.Intent; +import android.content.IntentSender; import android.content.pm.FileChecksum; import android.content.pm.PackageManager; import android.content.pm.PackageParser; +import android.os.Handler; +import android.os.SystemClock; +import android.os.incremental.IncrementalManager; +import android.os.incremental.IncrementalStorage; import android.util.ArrayMap; +import android.util.Pair; import android.util.Slog; import android.util.apk.ApkSignatureSchemeV2Verifier; import android.util.apk.ApkSignatureSchemeV3Verifier; @@ -43,6 +53,7 @@ import android.util.apk.SignatureInfo; import android.util.apk.SignatureNotFoundException; import android.util.apk.VerityBuilder; +import com.android.internal.annotations.VisibleForTesting; import com.android.server.security.VerityUtils; import java.io.BufferedInputStream; @@ -72,32 +83,163 @@ public class ApkChecksums { static final String ALGO_SHA512 = "SHA512"; /** - * Fetch or calculate checksums for the specific file. + * Check back in 1 second after we detected we needed to wait for the APK to be fully available. + */ + private static final long PROCESS_REQUIRED_CHECKSUMS_DELAY_MILLIS = 1000; + + /** + * 24 hours timeout to wait till all files are loaded. + */ + private static final long PROCESS_REQUIRED_CHECKSUMS_TIMEOUT_MILLIS = 1000 * 3600 * 24; + + /** + * Unit tests will instantiate, extend and/or mock to mock dependencies / behaviors. * - * @param split split name, null for base - * @param file to fetch checksums for + * NOTE: All getters should return the same instance for every call. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + static class Injector { + + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + interface Producer<T> { + /** Produce an instance of type {@link T} */ + T produce(); + } + + private final Producer<Context> mContext; + private final Producer<Handler> mHandlerProducer; + private final Producer<IncrementalManager> mIncrementalManagerProducer; + + Injector(Producer<Context> context, Producer<Handler> handlerProducer, + Producer<IncrementalManager> incrementalManagerProducer) { + mContext = context; + mHandlerProducer = handlerProducer; + mIncrementalManagerProducer = incrementalManagerProducer; + } + + public Context getContext() { + return mContext.produce(); + } + + public Handler getHandler() { + return mHandlerProducer.produce(); + } + + public IncrementalManager getIncrementalManager() { + return mIncrementalManagerProducer.produce(); + } + } + + /** + * Fetch or calculate checksums for the collection of files. + * + * @param filesToChecksum split name, null for base and File to fetch checksums for * @param optional mask to fetch readily available checksums * @param required mask to forcefully calculate if not available * @param trustedInstallers array of certificate to trust, two specific cases: * null - trust anybody, * [] - trust nobody. + * @param statusReceiver to receive the resulting checksums */ - public static List<FileChecksum> getFileChecksums(String split, File file, + public static void getChecksums(List<Pair<String, File>> filesToChecksum, @PackageManager.FileChecksumKind int optional, @PackageManager.FileChecksumKind int required, - @Nullable Certificate[] trustedInstallers) { + @Nullable Certificate[] trustedInstallers, + @NonNull IntentSender statusReceiver, + @NonNull Injector injector) { + List<Map<Integer, FileChecksum>> result = new ArrayList<>(filesToChecksum.size()); + for (int i = 0, size = filesToChecksum.size(); i < size; ++i) { + final String split = filesToChecksum.get(i).first; + final File file = filesToChecksum.get(i).second; + Map<Integer, FileChecksum> checksums = new ArrayMap<>(); + result.add(checksums); + + try { + getAvailableFileChecksums(split, file, optional | required, trustedInstallers, + checksums); + } catch (Throwable e) { + Slog.e(TAG, "Preferred checksum calculation error", e); + } + } + + long startTime = SystemClock.uptimeMillis(); + processRequiredChecksums(filesToChecksum, result, required, statusReceiver, injector, + startTime); + } + + private static void processRequiredChecksums(List<Pair<String, File>> filesToChecksum, + List<Map<Integer, FileChecksum>> result, + @PackageManager.FileChecksumKind int required, + @NonNull IntentSender statusReceiver, + @NonNull Injector injector, + long startTime) { + final boolean timeout = + SystemClock.uptimeMillis() - startTime >= PROCESS_REQUIRED_CHECKSUMS_TIMEOUT_MILLIS; + List<FileChecksum> allChecksums = new ArrayList<>(); + for (int i = 0, size = filesToChecksum.size(); i < size; ++i) { + final String split = filesToChecksum.get(i).first; + final File file = filesToChecksum.get(i).second; + Map<Integer, FileChecksum> checksums = result.get(i); + + try { + if (!timeout || required != 0) { + if (needToWait(file, required, checksums, injector)) { + // Not ready, come back later. + injector.getHandler().postDelayed(() -> { + processRequiredChecksums(filesToChecksum, result, required, + statusReceiver, injector, startTime); + }, PROCESS_REQUIRED_CHECKSUMS_DELAY_MILLIS); + return; + } + + getRequiredFileChecksums(split, file, required, checksums); + } + allChecksums.addAll(checksums.values()); + } catch (Throwable e) { + Slog.e(TAG, "Required checksum calculation error", e); + } + } + + final Intent intent = new Intent(); + intent.putExtra(EXTRA_CHECKSUMS, + allChecksums.toArray(new FileChecksum[allChecksums.size()])); + + try { + statusReceiver.sendIntent(injector.getContext(), 1, intent, null, null); + } catch (IntentSender.SendIntentException e) { + Slog.w(TAG, e); + } + } + + /** + * Fetch readily available checksums - enforced by kernel or provided by Installer. + * + * @param split split name, null for base + * @param file to fetch checksums for + * @param kinds mask to fetch checksums + * @param trustedInstallers array of certificate to trust, two specific cases: + * null - trust anybody, + * [] - trust nobody. + * @param checksums resulting checksums + */ + private static void getAvailableFileChecksums(String split, File file, + @PackageManager.FileChecksumKind int kinds, + @Nullable Certificate[] trustedInstallers, + Map<Integer, FileChecksum> checksums) { final String filePath = file.getAbsolutePath(); - Map<Integer, FileChecksum> checksums = new ArrayMap<>(); - final int kinds = (optional | required); - // System enforced: FSI or v2/v3/v4 signatures. - if ((kinds & WHOLE_MERKLE_ROOT_4K_SHA256) != 0) { + + // Always available: FSI or IncFs. + if (isRequired(WHOLE_MERKLE_ROOT_4K_SHA256, kinds, checksums)) { // Hashes in fs-verity and IncFS are always verified. FileChecksum checksum = extractHashFromFS(split, filePath); if (checksum != null) { checksums.put(checksum.getKind(), checksum); } } - if ((kinds & (PARTIAL_MERKLE_ROOT_1M_SHA256 | PARTIAL_MERKLE_ROOT_1M_SHA512)) != 0) { + + // System enforced: v2/v3. + if (isRequired(PARTIAL_MERKLE_ROOT_1M_SHA256, kinds, checksums) || isRequired( + PARTIAL_MERKLE_ROOT_1M_SHA512, kinds, checksums)) { Map<Integer, FileChecksum> v2v3checksums = extractHashFromV2V3Signature( split, filePath, kinds); if (v2v3checksums != null) { @@ -106,11 +248,58 @@ public class ApkChecksums { } // TODO(b/160605420): Installer provided. - // TODO(b/160605420): Wait for Incremental to be fully loaded. + } + + /** + * Whether the file is available for checksumming or we need to wait. + */ + private static boolean needToWait(File file, + @PackageManager.FileChecksumKind int kinds, + Map<Integer, FileChecksum> checksums, + @NonNull Injector injector) throws IOException { + if (!isRequired(WHOLE_MERKLE_ROOT_4K_SHA256, kinds, checksums) + && !isRequired(WHOLE_MD5, kinds, checksums) + && !isRequired(WHOLE_SHA1, kinds, checksums) + && !isRequired(WHOLE_SHA256, kinds, checksums) + && !isRequired(WHOLE_SHA512, kinds, checksums) + && !isRequired(PARTIAL_MERKLE_ROOT_1M_SHA256, kinds, checksums) + && !isRequired(PARTIAL_MERKLE_ROOT_1M_SHA512, kinds, checksums)) { + return false; + } + + final String filePath = file.getAbsolutePath(); + if (!IncrementalManager.isIncrementalPath(filePath)) { + return false; + } + + IncrementalManager manager = injector.getIncrementalManager(); + if (manager == null) { + throw new IllegalStateException("IncrementalManager is missing."); + } + IncrementalStorage storage = manager.openStorage(filePath); + if (storage == null) { + throw new IllegalStateException( + "IncrementalStorage is missing for a path on IncFs: " + filePath); + } + + return !storage.isFileFullyLoaded(filePath); + } + + /** + * Fetch or calculate checksums for the specific file. + * + * @param split split name, null for base + * @param file to fetch checksums for + * @param kinds mask to forcefully calculate if not available + * @param checksums resulting checksums + */ + private static void getRequiredFileChecksums(String split, File file, + @PackageManager.FileChecksumKind int kinds, + Map<Integer, FileChecksum> checksums) { + final String filePath = file.getAbsolutePath(); // Manually calculating required checksums if not readily available. - if ((required & WHOLE_MERKLE_ROOT_4K_SHA256) != 0 && !checksums.containsKey( - WHOLE_MERKLE_ROOT_4K_SHA256)) { + if (isRequired(WHOLE_MERKLE_ROOT_4K_SHA256, kinds, checksums)) { try { byte[] generatedRootHash = VerityBuilder.generateFsVerityRootHash( filePath, /*salt=*/null, @@ -127,14 +316,23 @@ public class ApkChecksums { } } - calculateChecksumIfRequested(checksums, split, file, required, WHOLE_MD5); - calculateChecksumIfRequested(checksums, split, file, required, WHOLE_SHA1); - calculateChecksumIfRequested(checksums, split, file, required, WHOLE_SHA256); - calculateChecksumIfRequested(checksums, split, file, required, WHOLE_SHA512); + calculateChecksumIfRequested(checksums, split, file, kinds, WHOLE_MD5); + calculateChecksumIfRequested(checksums, split, file, kinds, WHOLE_SHA1); + calculateChecksumIfRequested(checksums, split, file, kinds, WHOLE_SHA256); + calculateChecksumIfRequested(checksums, split, file, kinds, WHOLE_SHA512); - calculatePartialChecksumsIfRequested(checksums, split, file, required); + calculatePartialChecksumsIfRequested(checksums, split, file, kinds); + } - return new ArrayList<>(checksums.values()); + private static boolean isRequired(@PackageManager.FileChecksumKind int kind, + @PackageManager.FileChecksumKind int kinds, Map<Integer, FileChecksum> checksums) { + if ((kinds & kind) == 0) { + return false; + } + if (checksums.containsKey(kind)) { + return false; + } + return true; } private static FileChecksum extractHashFromFS(String split, String filePath) { @@ -170,7 +368,9 @@ public class ApkChecksums { PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2, false).contentDigests; } catch (PackageParser.PackageParserException e) { - Slog.e(TAG, "Signature verification error", e); + if (!(e.getCause() instanceof SignatureNotFoundException)) { + Slog.e(TAG, "Signature verification error", e); + } } if (contentDigests == null) { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 344f9cfdbc96..ca97f31e75ab 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -40,7 +40,6 @@ import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; -import static android.content.pm.PackageManager.EXTRA_CHECKSUMS; import static android.content.pm.PackageManager.EXTRA_VERIFICATION_ID; import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT; import static android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED; @@ -169,7 +168,6 @@ import android.content.pm.ComponentInfo; import android.content.pm.DataLoaderType; import android.content.pm.FallbackCategoryProvider; import android.content.pm.FeatureInfo; -import android.content.pm.FileChecksum; import android.content.pm.IDexModuleRegisterCallback; import android.content.pm.IPackageChangeObserver; import android.content.pm.IPackageDataObserver; @@ -933,6 +931,7 @@ public class PackageManagerService extends IPackageManager.Stub private final Object mLock; private final Installer mInstaller; private final Object mInstallLock; + private final Handler mBackgroundHandler; private final Executor mBackgroundExecutor; // ----- producers ----- @@ -955,7 +954,7 @@ public class PackageManagerService extends IPackageManager.Stub Injector(Context context, Object lock, Installer installer, Object installLock, PackageAbiHelper abiHelper, - Executor backgroundExecutor, + Handler backgroundHandler, Producer<ComponentResolver> componentResolverProducer, Producer<PermissionManagerServiceInternal> permissionManagerProducer, Producer<UserManagerService> userManagerProducer, @@ -977,7 +976,8 @@ public class PackageManagerService extends IPackageManager.Stub mInstaller = installer; mAbiHelper = abiHelper; mInstallLock = installLock; - mBackgroundExecutor = backgroundExecutor; + mBackgroundHandler = backgroundHandler; + mBackgroundExecutor = new HandlerExecutor(backgroundHandler); mComponentResolverProducer = new Singleton<>(componentResolverProducer); mPermissionManagerProducer = new Singleton<>(permissionManagerProducer); mUserManagerProducer = new Singleton<>(userManagerProducer); @@ -1092,6 +1092,10 @@ public class PackageManagerService extends IPackageManager.Stub return mPlatformCompatProducer.get(this, mPackageManager); } + public Handler getBackgroundHandler() { + return mBackgroundHandler; + } + public Executor getBackgroundExecutor() { return mBackgroundExecutor; } @@ -2489,29 +2493,14 @@ public class PackageManagerService extends IPackageManager.Stub final Certificate[] trustedCerts = (trustedInstallers != null) ? decodeCertificates( trustedInstallers) : null; - final Context context = mContext; mInjector.getBackgroundExecutor().execute(() -> { - final Intent intent = new Intent(); - List<FileChecksum> result = new ArrayList<>(); - for (int i = 0, size = filesToChecksum.size(); i < size; ++i) { - final String split = filesToChecksum.get(i).first; - final File file = filesToChecksum.get(i).second; - try { - result.addAll(ApkChecksums.getFileChecksums(split, file, optional, required, - trustedCerts)); - } catch (Throwable e) { - Slog.e(TAG, "Checksum calculation error", e); - } - } - intent.putExtra(EXTRA_CHECKSUMS, - result.toArray(new FileChecksum[result.size()])); - - try { - statusReceiver.sendIntent(context, 1, intent, null, null); - } catch (SendIntentException e) { - Slog.w(TAG, e); - } + ApkChecksums.Injector injector = new ApkChecksums.Injector( + () -> mContext, + () -> mInjector.getBackgroundHandler(), + () -> mContext.getSystemService(IncrementalManager.class)); + ApkChecksums.getChecksums(filesToChecksum, optional, required, trustedCerts, + statusReceiver, injector); }); } @@ -2684,7 +2673,7 @@ public class PackageManagerService extends IPackageManager.Stub Injector injector = new Injector( context, lock, installer, installLock, new PackageAbiHelperImpl(), - new HandlerExecutor(backgroundHandler), + backgroundHandler, (i, pm) -> new ComponentResolver(i.getUserManagerService(), pm.mPmInternal, lock), (i, pm) -> diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING index dbe96e63d978..485868237895 100644 --- a/services/core/java/com/android/server/pm/TEST_MAPPING +++ b/services/core/java/com/android/server/pm/TEST_MAPPING @@ -33,6 +33,9 @@ "name": "CtsContentTestCases", "options": [ { + "include-filter": "android.content.pm.cts.ChecksumsTest" + }, + { "include-filter": "android.content.pm.cts.PackageManagerShellCommandTest" }, { diff --git a/services/incremental/BinderIncrementalService.cpp b/services/incremental/BinderIncrementalService.cpp index 41945a276fde..87ae4d719d11 100644 --- a/services/incremental/BinderIncrementalService.cpp +++ b/services/incremental/BinderIncrementalService.cpp @@ -237,6 +237,13 @@ binder::Status BinderIncrementalService::unlink(int32_t storageId, const std::st return ok(); } +binder::Status BinderIncrementalService::isFileFullyLoaded(int32_t storageId, + const std::string& path, + int32_t* _aidl_return) { + *_aidl_return = mImpl.isFileFullyLoaded(storageId, path); + return ok(); +} + binder::Status BinderIncrementalService::getLoadingProgress(int32_t storageId, float* _aidl_return) { *_aidl_return = mImpl.getLoadingProgress(storageId); diff --git a/services/incremental/BinderIncrementalService.h b/services/incremental/BinderIncrementalService.h index 8b40350468ce..8478142b2d95 100644 --- a/services/incremental/BinderIncrementalService.h +++ b/services/incremental/BinderIncrementalService.h @@ -66,6 +66,8 @@ public: int32_t destStorageId, const std::string& destPath, int32_t* _aidl_return) final; binder::Status unlink(int32_t storageId, const std::string& path, int32_t* _aidl_return) final; + binder::Status isFileFullyLoaded(int32_t storageId, const std::string& path, + int32_t* _aidl_return) final; binder::Status getLoadingProgress(int32_t storageId, float* _aidl_return) final; binder::Status getMetadataByPath(int32_t storageId, const std::string& path, std::vector<uint8_t>* _aidl_return) final; diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp index 9836262ec2b0..447ee552a335 100644 --- a/services/incremental/IncrementalService.cpp +++ b/services/incremental/IncrementalService.cpp @@ -1603,7 +1603,8 @@ void IncrementalService::extractZipFile(const IfsMountPtr& ifs, ZipArchiveHandle const auto writeFd = mIncFs->openForSpecialOps(ifs->control, libFileId); if (!writeFd.ok()) { - LOG(ERROR) << "Failed to open write fd for: " << targetLibPath << " errno: " << writeFd; + LOG(ERROR) << "Failed to open write fd for: " << targetLibPath + << " errno: " << writeFd.get(); return; } @@ -1673,6 +1674,37 @@ bool IncrementalService::waitForNativeBinariesExtraction(StorageId storage) { return mRunning; } +int IncrementalService::isFileFullyLoaded(StorageId storage, const std::string& path) const { + std::unique_lock l(mLock); + const auto ifs = getIfsLocked(storage); + if (!ifs) { + LOG(ERROR) << "isFileFullyLoaded failed, invalid storageId: " << storage; + return -EINVAL; + } + const auto storageInfo = ifs->storages.find(storage); + if (storageInfo == ifs->storages.end()) { + LOG(ERROR) << "isFileFullyLoaded failed, no storage: " << storage; + return -EINVAL; + } + l.unlock(); + return isFileFullyLoadedFromPath(*ifs, path); +} + +int IncrementalService::isFileFullyLoadedFromPath(const IncFsMount& ifs, + std::string_view filePath) const { + const auto [filledBlocks, totalBlocks] = mIncFs->countFilledBlocks(ifs.control, filePath); + if (filledBlocks < 0) { + LOG(ERROR) << "isFileFullyLoadedFromPath failed to get filled blocks count for: " + << filePath << " errno: " << filledBlocks; + return filledBlocks; + } + if (totalBlocks < filledBlocks) { + LOG(ERROR) << "isFileFullyLoadedFromPath failed to get total num of blocks"; + return -EINVAL; + } + return totalBlocks - filledBlocks; +} + float IncrementalService::getLoadingProgress(StorageId storage) const { std::unique_lock l(mLock); const auto ifs = getIfsLocked(storage); diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h index cd6bfedb8a9e..267458d8769c 100644 --- a/services/incremental/IncrementalService.h +++ b/services/incremental/IncrementalService.h @@ -132,6 +132,7 @@ public: std::string_view newPath); int unlink(StorageId storage, std::string_view path); + int isFileFullyLoaded(StorageId storage, const std::string& path) const; float getLoadingProgress(StorageId storage) const; RawMetadata getMetadata(StorageId storage, std::string_view path) const; @@ -339,6 +340,7 @@ private: int makeDirs(const IncFsMount& ifs, StorageId storageId, std::string_view path, int mode); binder::Status applyStorageParams(IncFsMount& ifs, bool enableReadLogs); + int isFileFullyLoadedFromPath(const IncFsMount& ifs, std::string_view filePath) const; float getLoadingProgressFromPath(const IncFsMount& ifs, std::string_view path) const; void registerAppOpsCallback(const std::string& packageName); diff --git a/services/incremental/ServiceWrappers.cpp b/services/incremental/ServiceWrappers.cpp index 1ed46c49c5e1..f6d89c53be32 100644 --- a/services/incremental/ServiceWrappers.cpp +++ b/services/incremental/ServiceWrappers.cpp @@ -195,8 +195,8 @@ public: ErrorCode unlink(const Control& control, std::string_view path) const final { return incfs::unlink(control, path); } - base::unique_fd openForSpecialOps(const Control& control, FileId id) const final { - return base::unique_fd{incfs::openForSpecialOps(control, id).release()}; + 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())}); diff --git a/services/incremental/ServiceWrappers.h b/services/incremental/ServiceWrappers.h index 82a170470fee..6376d86543f8 100644 --- a/services/incremental/ServiceWrappers.h +++ b/services/incremental/ServiceWrappers.h @@ -74,6 +74,7 @@ public: using Control = incfs::Control; using FileId = incfs::FileId; using ErrorCode = incfs::ErrorCode; + using UniqueFd = incfs::UniqueFd; using WaitResult = incfs::WaitResult; using ExistingMountCallback = @@ -96,7 +97,7 @@ public: virtual ErrorCode link(const Control& control, std::string_view from, std::string_view to) const = 0; virtual ErrorCode unlink(const Control& control, std::string_view path) const = 0; - virtual base::unique_fd openForSpecialOps(const Control& control, FileId id) const = 0; + virtual UniqueFd openForSpecialOps(const Control& control, FileId id) const = 0; virtual ErrorCode writeBlocks(std::span<const incfs::DataBlock> blocks) const = 0; virtual WaitResult waitForPendingReads( const Control& control, std::chrono::milliseconds timeout, diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp index d1000e56e5ee..a290a1791481 100644 --- a/services/incremental/test/IncrementalServiceTest.cpp +++ b/services/incremental/test/IncrementalServiceTest.cpp @@ -289,7 +289,7 @@ public: ErrorCode(const Control& control, std::string_view from, std::string_view to)); MOCK_CONST_METHOD2(unlink, ErrorCode(const Control& control, std::string_view path)); - MOCK_CONST_METHOD2(openForSpecialOps, base::unique_fd(const Control& control, FileId id)); + MOCK_CONST_METHOD2(openForSpecialOps, UniqueFd(const Control& control, FileId id)); MOCK_CONST_METHOD1(writeBlocks, ErrorCode(std::span<const DataBlock> blocks)); MOCK_CONST_METHOD3(waitForPendingReads, WaitResult(const Control& control, std::chrono::milliseconds timeout, @@ -304,6 +304,10 @@ public: ON_CALL(*this, countFilledBlocks(_, _)).WillByDefault(Return(std::make_pair(1, 2))); } + void countFilledBlocksFullyLoaded() { + ON_CALL(*this, countFilledBlocks(_, _)).WillByDefault(Return(std::make_pair(10000, 10000))); + } + void countFilledBlocksFails() { ON_CALL(*this, countFilledBlocks(_, _)).WillByDefault(Return(std::make_pair(-1, -1))); } @@ -1069,6 +1073,53 @@ TEST_F(IncrementalServiceTest, testMakeDirectories) { ASSERT_EQ(res, 0); } +TEST_F(IncrementalServiceTest, testIsFileFullyLoadedFailsWithNoFile) { + mIncFs->countFilledBlocksFails(); + mFs->hasNoFile(); + + TemporaryDir tempDir; + int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), + IncrementalService::CreateOptions::CreateNew, + {}, {}, {}); + ASSERT_EQ(-1, mIncrementalService->isFileFullyLoaded(storageId, "base.apk")); +} + +TEST_F(IncrementalServiceTest, testIsFileFullyLoadedFailsWithFailedRanges) { + mIncFs->countFilledBlocksFails(); + mFs->hasFiles(); + + TemporaryDir tempDir; + int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), + IncrementalService::CreateOptions::CreateNew, + {}, {}, {}); + EXPECT_CALL(*mIncFs, countFilledBlocks(_, _)).Times(1); + ASSERT_EQ(-1, mIncrementalService->isFileFullyLoaded(storageId, "base.apk")); +} + +TEST_F(IncrementalServiceTest, testIsFileFullyLoadedSuccessWithEmptyRanges) { + mIncFs->countFilledBlocksEmpty(); + mFs->hasFiles(); + + TemporaryDir tempDir; + int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), + IncrementalService::CreateOptions::CreateNew, + {}, {}, {}); + EXPECT_CALL(*mIncFs, countFilledBlocks(_, _)).Times(1); + ASSERT_EQ(0, mIncrementalService->isFileFullyLoaded(storageId, "base.apk")); +} + +TEST_F(IncrementalServiceTest, testIsFileFullyLoadedSuccess) { + mIncFs->countFilledBlocksFullyLoaded(); + mFs->hasFiles(); + + TemporaryDir tempDir; + int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), + IncrementalService::CreateOptions::CreateNew, + {}, {}, {}); + EXPECT_CALL(*mIncFs, countFilledBlocks(_, _)).Times(1); + ASSERT_EQ(0, mIncrementalService->isFileFullyLoaded(storageId, "base.apk")); +} + TEST_F(IncrementalServiceTest, testGetLoadingProgressSuccessWithNoFile) { mIncFs->countFilledBlocksSuccess(); mFs->hasNoFile(); |