diff options
7 files changed, 921 insertions, 805 deletions
diff --git a/services/core/java/com/android/server/pm/ResilientAtomicFile.java b/services/core/java/com/android/server/pm/ResilientAtomicFile.java new file mode 100644 index 000000000000..19aa4f8e8d0b --- /dev/null +++ b/services/core/java/com/android/server/pm/ResilientAtomicFile.java @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2023 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. + */ + +package com.android.server.pm; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.FileUtils; +import android.os.ParcelFileDescriptor; +import android.util.Log; +import android.util.Slog; + +import com.android.server.security.FileIntegrity; + +import libcore.io.IoUtils; + +import java.io.Closeable; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; + +final class ResilientAtomicFile implements Closeable { + private static final String LOG_TAG = "ResilientAtomicFile"; + + private final File mFile; + + private final File mTemporaryBackup; + + private final File mReserveCopy; + + private final int mFileMode; + + private final String mDebugName; + + private final ReadEventLogger mReadEventLogger; + + // Write state. + private FileOutputStream mMainOutStream = null; + private FileInputStream mMainInStream = null; + private FileOutputStream mReserveOutStream = null; + private FileInputStream mReserveInStream = null; + + // Read state. + private File mCurrentFile = null; + private FileInputStream mCurrentInStream = null; + + private void finalizeOutStream(FileOutputStream str) throws IOException { + // Flash/sync + set permissions. + str.flush(); + FileUtils.sync(str); + FileUtils.setPermissions(str.getFD(), mFileMode, -1, -1); + } + + ResilientAtomicFile(@NonNull File file, @NonNull File temporaryBackup, + @NonNull File reserveCopy, int fileMode, String debugName, + @Nullable ReadEventLogger readEventLogger) { + mFile = file; + mTemporaryBackup = temporaryBackup; + mReserveCopy = reserveCopy; + mFileMode = fileMode; + mDebugName = debugName; + mReadEventLogger = readEventLogger; + } + + public File getBaseFile() { + return mFile; + } + + public FileOutputStream startWrite() throws IOException { + if (mMainOutStream != null) { + throw new IllegalStateException("Duplicate startWrite call?"); + } + + new File(mFile.getParent()).mkdirs(); + + if (mFile.exists()) { + // Presence of backup settings file indicates that we failed + // to persist packages earlier. So preserve the older + // backup for future reference since the current packages + // might have been corrupted. + if (!mTemporaryBackup.exists()) { + if (!mFile.renameTo(mTemporaryBackup)) { + throw new IOException("Unable to backup " + mDebugName + + " file, current changes will be lost at reboot"); + } + } else { + mFile.delete(); + Slog.w(LOG_TAG, "Preserving older " + mDebugName + " backup"); + } + } + // Reserve copy is not valid anymore. + mReserveCopy.delete(); + + // In case of MT access, it's possible the files get overwritten during write. + // Let's open all FDs we need now. + mMainOutStream = new FileOutputStream(mFile); + mMainInStream = new FileInputStream(mFile); + mReserveOutStream = new FileOutputStream(mReserveCopy); + mReserveInStream = new FileInputStream(mReserveCopy); + + return mMainOutStream; + } + + public void finishWrite(FileOutputStream str) throws IOException { + if (mMainOutStream != str) { + throw new IllegalStateException("Invalid incoming stream."); + } + + // Flush and set permissions. + try (FileOutputStream mainOutStream = mMainOutStream) { + mMainOutStream = null; + finalizeOutStream(mainOutStream); + } + // New file successfully written, old one are no longer needed. + mTemporaryBackup.delete(); + + try (FileInputStream mainInStream = mMainInStream; + FileInputStream reserveInStream = mReserveInStream) { + mMainInStream = null; + mReserveInStream = null; + + // Copy main file to reserve. + try (FileOutputStream reserveOutStream = mReserveOutStream) { + mReserveOutStream = null; + FileUtils.copy(mainInStream, reserveOutStream); + finalizeOutStream(reserveOutStream); + } + + // Protect both main and reserve using fs-verity. + try (ParcelFileDescriptor mainPfd = ParcelFileDescriptor.dup(mainInStream.getFD()); + ParcelFileDescriptor copyPfd = ParcelFileDescriptor.dup(reserveInStream.getFD())) { + FileIntegrity.setUpFsVerity(mainPfd); + FileIntegrity.setUpFsVerity(copyPfd); + } catch (IOException e) { + Slog.e(LOG_TAG, "Failed to verity-protect " + mDebugName, e); + } + } catch (IOException e) { + Slog.e(LOG_TAG, "Failed to write reserve copy " + mDebugName + ": " + mReserveCopy, e); + } + } + + public void failWrite(FileOutputStream str) { + if (mMainOutStream != str) { + throw new IllegalStateException("Invalid incoming stream."); + } + + // Close all FDs. + close(); + + // Clean up partially written files + if (mFile.exists()) { + if (!mFile.delete()) { + Slog.i(LOG_TAG, "Failed to clean up mangled file: " + mFile); + } + } + } + + public FileInputStream openRead() throws IOException { + if (mTemporaryBackup.exists()) { + try { + mCurrentFile = mTemporaryBackup; + mCurrentInStream = new FileInputStream(mCurrentFile); + if (mReadEventLogger != null) { + mReadEventLogger.logEvent(Log.INFO, + "Need to read from backup " + mDebugName + " file"); + } + if (mFile.exists()) { + // If both the backup and normal file exist, we + // ignore the normal one since it might have been + // corrupted. + Slog.w(LOG_TAG, "Cleaning up " + mDebugName + " file " + mFile); + mFile.delete(); + } + // Ignore reserve copy as well. + mReserveCopy.delete(); + } catch (java.io.IOException e) { + // We'll try for the normal settings file. + } + } + + if (mCurrentInStream != null) { + return mCurrentInStream; + } + + if (mFile.exists()) { + mCurrentFile = mFile; + mCurrentInStream = new FileInputStream(mCurrentFile); + } else if (mReserveCopy.exists()) { + mCurrentFile = mReserveCopy; + mCurrentInStream = new FileInputStream(mCurrentFile); + if (mReadEventLogger != null) { + mReadEventLogger.logEvent(Log.INFO, + "Need to read from reserve copy " + mDebugName + " file"); + } + } + + if (mCurrentInStream == null) { + if (mReadEventLogger != null) { + mReadEventLogger.logEvent(Log.INFO, "No " + mDebugName + " file"); + } + } + + return mCurrentInStream; + } + + public void failRead(FileInputStream str, Exception e) { + if (mCurrentInStream != str) { + throw new IllegalStateException("Invalid incoming stream."); + } + mCurrentInStream = null; + IoUtils.closeQuietly(str); + + if (mReadEventLogger != null) { + mReadEventLogger.logEvent(Log.ERROR, + "Error reading " + mDebugName + ", removing " + mCurrentFile + '\n' + + Log.getStackTraceString(e)); + } + + mCurrentFile.delete(); + mCurrentFile = null; + } + + public void delete() { + mFile.delete(); + mTemporaryBackup.delete(); + mReserveCopy.delete(); + } + + @Override + public void close() { + IoUtils.closeQuietly(mMainOutStream); + IoUtils.closeQuietly(mMainInStream); + IoUtils.closeQuietly(mReserveOutStream); + IoUtils.closeQuietly(mReserveInStream); + IoUtils.closeQuietly(mCurrentInStream); + mMainOutStream = null; + mMainInStream = null; + mReserveOutStream = null; + mReserveInStream = null; + mCurrentInStream = null; + mCurrentFile = null; + } + + public String toString() { + return mFile.getPath(); + } + + interface ReadEventLogger { + void logEvent(int priority, String msg); + } +} diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 2658d31ed880..b6557d000463 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -119,7 +119,6 @@ import com.android.server.pm.resolution.ComponentResolver; import com.android.server.pm.verify.domain.DomainVerificationLegacySettings; import com.android.server.pm.verify.domain.DomainVerificationManagerInternal; import com.android.server.pm.verify.domain.DomainVerificationPersistence; -import com.android.server.security.FileIntegrity; import com.android.server.utils.Slogf; import com.android.server.utils.Snappable; import com.android.server.utils.SnapshotCache; @@ -172,7 +171,7 @@ import java.util.function.Consumer; /** * Holds information about dynamic settings. */ -public final class Settings implements Watchable, Snappable { +public final class Settings implements Watchable, Snappable, ResilientAtomicFile.ReadEventLogger { private static final String TAG = "PackageSettings"; /** @@ -344,7 +343,7 @@ public final class Settings implements Watchable, Snappable { private static final String ATTR_BLOCK_UNINSTALL = "blockUninstall"; private static final String ATTR_ENABLED = "enabled"; private static final String ATTR_ENABLED_CALLER = "enabledCaller"; - private static final String ATTR_DOMAIN_VERIFICATON_STATE = "domainVerificationStatus"; + private static final String ATTR_DOMAIN_VERIFICATION_STATE = "domainVerificationStatus"; private static final String ATTR_APP_LINK_GENERATION = "app-link-generation"; private static final String ATTR_INSTALL_REASON = "install-reason"; private static final String ATTR_UNINSTALL_REASON = "uninstall-reason"; @@ -1511,16 +1510,22 @@ public final class Settings implements Watchable, Snappable { return new File(new File(mSystemDir, "users"), Integer.toString(userId)); } - // The method itself does not have to be guarded, but the file does. - @GuardedBy("mPackageRestrictionsLock") - private File getUserPackagesStateFile(int userId) { - return new File(getUserSystemDirectory(userId), "package-restrictions.xml"); + private ResilientAtomicFile getUserPackagesStateFile(int userId) { + File mainFile = new File(getUserSystemDirectory(userId), "package-restrictions.xml"); + File temporaryBackup = new File(getUserSystemDirectory(userId), + "package-restrictions-backup.xml"); + File reserveCopy = new File(getUserSystemDirectory(userId), + "package-restrictions.xml.reservecopy"); + return new ResilientAtomicFile(mainFile, temporaryBackup, reserveCopy, + FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP | FileUtils.S_IWGRP, + "package restrictions", this); } - // The method itself does not have to be guarded, but the file does. - @GuardedBy("mPackageRestrictionsLock") - private File getUserPackagesStateBackupFile(int userId) { - return new File(getUserSystemDirectory(userId), "package-restrictions-backup.xml"); + private ResilientAtomicFile getSettingsFile() { + return new ResilientAtomicFile(mSettingsFilename, mPreviousSettingsFilename, + mSettingsReserveCopyFilename, + FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP | FileUtils.S_IWGRP, + "package manager settings", this); } private File getUserRuntimePermissionsFile(int userId) { @@ -1730,272 +1735,243 @@ public final class Settings implements Watchable, Snappable { } } + @Override + public void logEvent(int priority, String msg) { + mReadMessages.append(msg + "\n"); + PackageManagerService.reportSettingsProblem(priority, msg); + } + + void readPackageRestrictionsLPr(int userId, @NonNull ArrayMap<String, Long> origFirstInstallTimes) { if (DEBUG_MU) { Log.i(TAG, "Reading package restrictions for user=" + userId); } - FileInputStream str = null; - synchronized (mPackageRestrictionsLock) { - File userPackagesStateFile = getUserPackagesStateFile(userId); - File backupFile = getUserPackagesStateBackupFile(userId); - if (backupFile.exists()) { - try { - str = new FileInputStream(backupFile); - mReadMessages.append("Reading from backup stopped packages file\n"); - PackageManagerService.reportSettingsProblem(Log.INFO, - "Need to read from backup stopped packages file"); - if (userPackagesStateFile.exists()) { - // If both the backup and normal file exist, we - // ignore the normal one since it might have been - // corrupted. - Slog.w(PackageManagerService.TAG, "Cleaning up stopped packages file " - + userPackagesStateFile); - userPackagesStateFile.delete(); + try (ResilientAtomicFile atomicFile = getUserPackagesStateFile(userId)) { + FileInputStream str = null; + try { + synchronized (mPackageRestrictionsLock) { + str = atomicFile.openRead(); + if (str == null) { + // At first boot, make sure no packages are stopped. + // We usually want to have third party apps initialize + // in the stopped state, but not at first boot. Also + // consider all applications to be installed. + for (PackageSetting pkg : mPackages.values()) { + pkg.setUserState(userId, 0, COMPONENT_ENABLED_STATE_DEFAULT, + true /*installed*/, + false /*stopped*/, + false /*notLaunched*/, + false /*hidden*/, + 0 /*distractionFlags*/, + null /*suspendParams*/, + false /*instantApp*/, + false /*virtualPreload*/, + null /*lastDisableAppCaller*/, + null /*enabledComponents*/, + null /*disabledComponents*/, + PackageManager.INSTALL_REASON_UNKNOWN, + PackageManager.UNINSTALL_REASON_UNKNOWN, + null /*harmfulAppWarning*/, + null /* splashScreenTheme*/, + 0 /*firstInstallTime*/ + ); + } + return; } - } catch (java.io.IOException e) { - // We'll try for the normal settings file. } - } - if (str == null && userPackagesStateFile.exists()) { - try { - str = new FileInputStream(userPackagesStateFile); - if (DEBUG_MU) Log.i(TAG, "Reading " + userPackagesStateFile); - } catch (java.io.IOException e) { - mReadMessages.append("Error reading: " + e.toString()); - PackageManagerService.reportSettingsProblem(Log.ERROR, - "Error reading settings: " + e); - Slog.wtf(TAG, "Error reading package manager stopped packages", e); - } - } - } - - if (str == null) { - mReadMessages.append("No stopped packages file found\n"); - PackageManagerService.reportSettingsProblem(Log.INFO, - "No stopped packages file; " - + "assuming all started"); - // At first boot, make sure no packages are stopped. - // We usually want to have third party apps initialize - // in the stopped state, but not at first boot. Also - // consider all applications to be installed. - for (PackageSetting pkg : mPackages.values()) { - pkg.setUserState(userId, 0, COMPONENT_ENABLED_STATE_DEFAULT, - true /*installed*/, - false /*stopped*/, - false /*notLaunched*/, - false /*hidden*/, - 0 /*distractionFlags*/, - null /*suspendParams*/, - false /*instantApp*/, - false /*virtualPreload*/, - null /*lastDisableAppCaller*/, - null /*enabledComponents*/, - null /*disabledComponents*/, - PackageManager.INSTALL_REASON_UNKNOWN, - PackageManager.UNINSTALL_REASON_UNKNOWN, - null /*harmfulAppWarning*/, - null /* splashScreenTheme*/, - 0 /*firstInstallTime*/ - ); - } - return; - } + final TypedXmlPullParser parser = Xml.resolvePullParser(str); - try { - final TypedXmlPullParser parser = Xml.resolvePullParser(str); - - int type; - while ((type=parser.next()) != XmlPullParser.START_TAG - && type != XmlPullParser.END_DOCUMENT) { - ; - } - - if (type != XmlPullParser.START_TAG) { - mReadMessages.append("No start tag found in package restrictions file\n"); - PackageManagerService.reportSettingsProblem(Log.WARN, - "No start tag found in package manager stopped packages"); - return; - } + int type; + while ((type = parser.next()) != XmlPullParser.START_TAG + && type != XmlPullParser.END_DOCUMENT) { + // nothing + } - int outerDepth = parser.getDepth(); - PackageSetting ps = null; - while ((type=parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG - || parser.getDepth() > outerDepth)) { - if (type == XmlPullParser.END_TAG - || type == XmlPullParser.TEXT) { - continue; + if (type != XmlPullParser.START_TAG) { + mReadMessages.append("No start tag found in package restrictions file\n"); + PackageManagerService.reportSettingsProblem(Log.WARN, + "No start tag found in package manager package restrictions file"); + return; } - String tagName = parser.getName(); - if (tagName.equals(TAG_PACKAGE)) { - String name = parser.getAttributeValue(null, ATTR_NAME); - ps = mPackages.get(name); - if (ps == null) { - Slog.w(PackageManagerService.TAG, "No package known for stopped package " - + name); - XmlUtils.skipCurrentTag(parser); + int outerDepth = parser.getDepth(); + PackageSetting ps = null; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG + || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG + || type == XmlPullParser.TEXT) { continue; } - final long ceDataInode = - parser.getAttributeLong(null, ATTR_CE_DATA_INODE, 0); - final boolean installed = - parser.getAttributeBoolean(null, ATTR_INSTALLED, true); - final boolean stopped = - parser.getAttributeBoolean(null, ATTR_STOPPED, false); - final boolean notLaunched = - parser.getAttributeBoolean(null, ATTR_NOT_LAUNCHED, false); - - // For backwards compatibility with the previous name of "blocked", which - // now means hidden, read the old attribute as well. - boolean hidden = parser.getAttributeBoolean(null, ATTR_HIDDEN, false); - if (!hidden) { - hidden = parser.getAttributeBoolean(null, ATTR_BLOCKED, false); - } + String tagName = parser.getName(); + if (tagName.equals(TAG_PACKAGE)) { + String name = parser.getAttributeValue(null, ATTR_NAME); + ps = mPackages.get(name); + if (ps == null) { + Slog.w(PackageManagerService.TAG, + "No package known for package restrictions " + name); + XmlUtils.skipCurrentTag(parser); + continue; + } - final int distractionFlags = parser.getAttributeInt(null, ATTR_DISTRACTION_FLAGS, 0); - final boolean suspended = parser.getAttributeBoolean(null, ATTR_SUSPENDED, false); - String oldSuspendingPackage = parser.getAttributeValue(null, - ATTR_SUSPENDING_PACKAGE); - final String dialogMessage = parser.getAttributeValue(null, - ATTR_SUSPEND_DIALOG_MESSAGE); - if (suspended && oldSuspendingPackage == null) { - oldSuspendingPackage = PLATFORM_PACKAGE_NAME; - } + final long ceDataInode = + parser.getAttributeLong(null, ATTR_CE_DATA_INODE, 0); + final boolean installed = + parser.getAttributeBoolean(null, ATTR_INSTALLED, true); + final boolean stopped = + parser.getAttributeBoolean(null, ATTR_STOPPED, false); + final boolean notLaunched = + parser.getAttributeBoolean(null, ATTR_NOT_LAUNCHED, false); + + // For backwards compatibility with the previous name of "blocked", which + // now means hidden, read the old attribute as well. + boolean hidden = parser.getAttributeBoolean(null, ATTR_HIDDEN, false); + if (!hidden) { + hidden = parser.getAttributeBoolean(null, ATTR_BLOCKED, false); + } - final boolean blockUninstall = - parser.getAttributeBoolean(null, ATTR_BLOCK_UNINSTALL, false); - final boolean instantApp = - parser.getAttributeBoolean(null, ATTR_INSTANT_APP, false); - final boolean virtualPreload = - parser.getAttributeBoolean(null, ATTR_VIRTUAL_PRELOAD, false); - final int enabled = parser.getAttributeInt(null, ATTR_ENABLED, - COMPONENT_ENABLED_STATE_DEFAULT); - final String enabledCaller = parser.getAttributeValue(null, - ATTR_ENABLED_CALLER); - final String harmfulAppWarning = - parser.getAttributeValue(null, ATTR_HARMFUL_APP_WARNING); - final int verifState = parser.getAttributeInt(null, - ATTR_DOMAIN_VERIFICATON_STATE, - PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED); - final int installReason = parser.getAttributeInt(null, ATTR_INSTALL_REASON, - PackageManager.INSTALL_REASON_UNKNOWN); - final int uninstallReason = parser.getAttributeInt(null, ATTR_UNINSTALL_REASON, - PackageManager.UNINSTALL_REASON_UNKNOWN); - final String splashScreenTheme = parser.getAttributeValue(null, - ATTR_SPLASH_SCREEN_THEME); - final long firstInstallTime = parser.getAttributeLongHex(null, - ATTR_FIRST_INSTALL_TIME, 0); - - ArraySet<String> enabledComponents = null; - ArraySet<String> disabledComponents = null; - PersistableBundle suspendedAppExtras = null; - PersistableBundle suspendedLauncherExtras = null; - SuspendDialogInfo oldSuspendDialogInfo = null; - - int packageDepth = parser.getDepth(); - ArrayMap<String, SuspendParams> suspendParamsMap = null; - while ((type=parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG - || parser.getDepth() > packageDepth)) { - if (type == XmlPullParser.END_TAG - || type == XmlPullParser.TEXT) { - continue; + final int distractionFlags = parser.getAttributeInt(null, + ATTR_DISTRACTION_FLAGS, 0); + final boolean suspended = parser.getAttributeBoolean(null, ATTR_SUSPENDED, + false); + String oldSuspendingPackage = parser.getAttributeValue(null, + ATTR_SUSPENDING_PACKAGE); + final String dialogMessage = parser.getAttributeValue(null, + ATTR_SUSPEND_DIALOG_MESSAGE); + if (suspended && oldSuspendingPackage == null) { + oldSuspendingPackage = PLATFORM_PACKAGE_NAME; } - switch (parser.getName()) { - case TAG_ENABLED_COMPONENTS: - enabledComponents = readComponentsLPr(parser); - break; - case TAG_DISABLED_COMPONENTS: - disabledComponents = readComponentsLPr(parser); - break; - case TAG_SUSPENDED_APP_EXTRAS: - suspendedAppExtras = PersistableBundle.restoreFromXml(parser); - break; - case TAG_SUSPENDED_LAUNCHER_EXTRAS: - suspendedLauncherExtras = PersistableBundle.restoreFromXml(parser); - break; - case TAG_SUSPENDED_DIALOG_INFO: - oldSuspendDialogInfo = SuspendDialogInfo.restoreFromXml(parser); - break; - case TAG_SUSPEND_PARAMS: - final String suspendingPackage = parser.getAttributeValue(null, - ATTR_SUSPENDING_PACKAGE); - if (suspendingPackage == null) { - Slog.wtf(TAG, "No suspendingPackage found inside tag " - + TAG_SUSPEND_PARAMS); - continue; - } - if (suspendParamsMap == null) { - suspendParamsMap = new ArrayMap<>(); - } - suspendParamsMap.put(suspendingPackage, - SuspendParams.restoreFromXml(parser)); - break; - default: - Slog.wtf(TAG, "Unknown tag " + parser.getName() + " under tag " - + TAG_PACKAGE); + + final boolean blockUninstall = + parser.getAttributeBoolean(null, ATTR_BLOCK_UNINSTALL, false); + final boolean instantApp = + parser.getAttributeBoolean(null, ATTR_INSTANT_APP, false); + final boolean virtualPreload = + parser.getAttributeBoolean(null, ATTR_VIRTUAL_PRELOAD, false); + final int enabled = parser.getAttributeInt(null, ATTR_ENABLED, + COMPONENT_ENABLED_STATE_DEFAULT); + final String enabledCaller = parser.getAttributeValue(null, + ATTR_ENABLED_CALLER); + final String harmfulAppWarning = + parser.getAttributeValue(null, ATTR_HARMFUL_APP_WARNING); + final int verifState = parser.getAttributeInt(null, + ATTR_DOMAIN_VERIFICATION_STATE, + PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED); + final int installReason = parser.getAttributeInt(null, ATTR_INSTALL_REASON, + PackageManager.INSTALL_REASON_UNKNOWN); + final int uninstallReason = parser.getAttributeInt(null, + ATTR_UNINSTALL_REASON, + PackageManager.UNINSTALL_REASON_UNKNOWN); + final String splashScreenTheme = parser.getAttributeValue(null, + ATTR_SPLASH_SCREEN_THEME); + final long firstInstallTime = parser.getAttributeLongHex(null, + ATTR_FIRST_INSTALL_TIME, 0); + + ArraySet<String> enabledComponents = null; + ArraySet<String> disabledComponents = null; + PersistableBundle suspendedAppExtras = null; + PersistableBundle suspendedLauncherExtras = null; + SuspendDialogInfo oldSuspendDialogInfo = null; + + int packageDepth = parser.getDepth(); + ArrayMap<String, SuspendParams> suspendParamsMap = null; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG + || parser.getDepth() > packageDepth)) { + if (type == XmlPullParser.END_TAG + || type == XmlPullParser.TEXT) { + continue; + } + switch (parser.getName()) { + case TAG_ENABLED_COMPONENTS: + enabledComponents = readComponentsLPr(parser); + break; + case TAG_DISABLED_COMPONENTS: + disabledComponents = readComponentsLPr(parser); + break; + case TAG_SUSPENDED_APP_EXTRAS: + suspendedAppExtras = PersistableBundle.restoreFromXml(parser); + break; + case TAG_SUSPENDED_LAUNCHER_EXTRAS: + suspendedLauncherExtras = PersistableBundle.restoreFromXml( + parser); + break; + case TAG_SUSPENDED_DIALOG_INFO: + oldSuspendDialogInfo = SuspendDialogInfo.restoreFromXml(parser); + break; + case TAG_SUSPEND_PARAMS: + final String suspendingPackage = parser.getAttributeValue(null, + ATTR_SUSPENDING_PACKAGE); + if (suspendingPackage == null) { + Slog.wtf(TAG, "No suspendingPackage found inside tag " + + TAG_SUSPEND_PARAMS); + continue; + } + if (suspendParamsMap == null) { + suspendParamsMap = new ArrayMap<>(); + } + suspendParamsMap.put(suspendingPackage, + SuspendParams.restoreFromXml(parser)); + break; + default: + Slog.wtf(TAG, "Unknown tag " + parser.getName() + " under tag " + + TAG_PACKAGE); + } + } + if (oldSuspendDialogInfo == null && !TextUtils.isEmpty(dialogMessage)) { + oldSuspendDialogInfo = new SuspendDialogInfo.Builder() + .setMessage(dialogMessage) + .build(); + } + if (suspended && suspendParamsMap == null) { + final SuspendParams suspendParams = new SuspendParams( + oldSuspendDialogInfo, + suspendedAppExtras, + suspendedLauncherExtras); + suspendParamsMap = new ArrayMap<>(); + suspendParamsMap.put(oldSuspendingPackage, suspendParams); } - } - if (oldSuspendDialogInfo == null && !TextUtils.isEmpty(dialogMessage)) { - oldSuspendDialogInfo = new SuspendDialogInfo.Builder() - .setMessage(dialogMessage) - .build(); - } - if (suspended && suspendParamsMap == null) { - final SuspendParams suspendParams = new SuspendParams( - oldSuspendDialogInfo, - suspendedAppExtras, - suspendedLauncherExtras); - suspendParamsMap = new ArrayMap<>(); - suspendParamsMap.put(oldSuspendingPackage, suspendParams); - } - if (blockUninstall) { - setBlockUninstallLPw(userId, name, true); + if (blockUninstall) { + setBlockUninstallLPw(userId, name, true); + } + ps.setUserState(userId, ceDataInode, enabled, installed, stopped, + notLaunched, + hidden, distractionFlags, suspendParamsMap, instantApp, + virtualPreload, + enabledCaller, enabledComponents, disabledComponents, installReason, + uninstallReason, harmfulAppWarning, splashScreenTheme, + firstInstallTime != 0 ? firstInstallTime : + origFirstInstallTimes.getOrDefault(name, 0L)); + + mDomainVerificationManager.setLegacyUserState(name, userId, verifState); + } else if (tagName.equals("preferred-activities")) { + readPreferredActivitiesLPw(parser, userId); + } else if (tagName.equals(TAG_PERSISTENT_PREFERRED_ACTIVITIES)) { + readPersistentPreferredActivitiesLPw(parser, userId); + } else if (tagName.equals(TAG_CROSS_PROFILE_INTENT_FILTERS)) { + readCrossProfileIntentFiltersLPw(parser, userId); + } else if (tagName.equals(TAG_DEFAULT_APPS)) { + readDefaultAppsLPw(parser, userId); + } else if (tagName.equals(TAG_BLOCK_UNINSTALL_PACKAGES)) { + readBlockUninstallPackagesLPw(parser, userId); + } else { + Slog.w(PackageManagerService.TAG, + "Unknown element under <stopped-packages>: " + + parser.getName()); + XmlUtils.skipCurrentTag(parser); } - ps.setUserState(userId, ceDataInode, enabled, installed, stopped, notLaunched, - hidden, distractionFlags, suspendParamsMap, instantApp, virtualPreload, - enabledCaller, enabledComponents, disabledComponents, installReason, - uninstallReason, harmfulAppWarning, splashScreenTheme, - firstInstallTime != 0 ? firstInstallTime : - origFirstInstallTimes.getOrDefault(name, 0L)); - - mDomainVerificationManager.setLegacyUserState(name, userId, verifState); - } else if (tagName.equals("preferred-activities")) { - readPreferredActivitiesLPw(parser, userId); - } else if (tagName.equals(TAG_PERSISTENT_PREFERRED_ACTIVITIES)) { - readPersistentPreferredActivitiesLPw(parser, userId); - } else if (tagName.equals(TAG_CROSS_PROFILE_INTENT_FILTERS)) { - readCrossProfileIntentFiltersLPw(parser, userId); - } else if (tagName.equals(TAG_DEFAULT_APPS)) { - readDefaultAppsLPw(parser, userId); - } else if (tagName.equals(TAG_BLOCK_UNINSTALL_PACKAGES)) { - readBlockUninstallPackagesLPw(parser, userId); - } else { - Slog.w(PackageManagerService.TAG, "Unknown element under <stopped-packages>: " - + parser.getName()); - XmlUtils.skipCurrentTag(parser); } - } - - str.close(); - } catch (XmlPullParserException e) { - mReadMessages.append("Error reading: " + e.toString()); - PackageManagerService.reportSettingsProblem(Log.ERROR, - "Error reading stopped packages: " + e); - Slog.wtf(PackageManagerService.TAG, "Error reading package manager stopped packages", - e); + } catch (IOException | XmlPullParserException e) { + // Remove corrupted file and retry. + atomicFile.failRead(str, e); - } catch (java.io.IOException e) { - mReadMessages.append("Error reading: " + e.toString()); - PackageManagerService.reportSettingsProblem(Log.ERROR, "Error reading settings: " + e); - Slog.wtf(PackageManagerService.TAG, "Error reading package manager stopped packages", - e); + readPackageRestrictionsLPr(userId, origFirstInstallTimes); + } } } @@ -2165,219 +2141,176 @@ public final class Settings implements Watchable, Snappable { Log.i(TAG, "Writing package restrictions for user=" + userId); } - final File userPackagesStateFile; - final File backupFile; - final FileOutputStream fstr; - - synchronized (mPackageRestrictionsLock) { - if (!sync) { - int pending = mPendingAsyncPackageRestrictionsWrites.get(userId, 0) - 1; - if (pending < 0) { - Log.i(TAG, "Cancel writing package restrictions for user=" + userId); - return; - } - mPendingAsyncPackageRestrictionsWrites.put(userId, pending); - } + FileOutputStream str = null; + try (ResilientAtomicFile atomicFile = getUserPackagesStateFile(userId)) { + try { + synchronized (mPackageRestrictionsLock) { + if (!sync) { + int pending = mPendingAsyncPackageRestrictionsWrites.get(userId, 0) - 1; + if (pending < 0) { + Log.i(TAG, "Cancel writing package restrictions for user=" + userId); + return; + } + mPendingAsyncPackageRestrictionsWrites.put(userId, pending); + } - // Keep the old stopped packages around until we know the new ones have - // been successfully written. - userPackagesStateFile = getUserPackagesStateFile(userId); - backupFile = getUserPackagesStateBackupFile(userId); - new File(userPackagesStateFile.getParent()).mkdirs(); - if (userPackagesStateFile.exists()) { - // Presence of backup settings file indicates that we failed - // to persist packages earlier. So preserve the older - // backup for future reference since the current packages - // might have been corrupted. - if (!backupFile.exists()) { - if (!userPackagesStateFile.renameTo(backupFile)) { + try { + str = atomicFile.startWrite(); + } catch (java.io.IOException e) { Slog.wtf(PackageManagerService.TAG, - "Unable to backup user packages state file, " - + "current changes will be lost at reboot"); + "Unable to write package manager package restrictions, " + + " current changes will be lost at reboot", e); return; } - } else { - userPackagesStateFile.delete(); - Slog.w(PackageManagerService.TAG, "Preserving older stopped packages backup"); } - } - try { - fstr = new FileOutputStream(userPackagesStateFile); - // File is created, set permissions. - FileUtils.setPermissions(userPackagesStateFile.toString(), - FileUtils.S_IRUSR | FileUtils.S_IWUSR - | FileUtils.S_IRGRP | FileUtils.S_IWGRP, - -1, -1); - } catch (java.io.IOException e) { - Slog.wtf(PackageManagerService.TAG, - "Unable to write package manager user packages state, " - + " current changes will be lost at reboot", e); - return; - } - } - - try { - synchronized (mLock) { - final TypedXmlSerializer serializer = Xml.resolveSerializer(fstr); - serializer.startDocument(null, true); - serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", - true); + synchronized (mLock) { + final TypedXmlSerializer serializer = Xml.resolveSerializer(str); + serializer.startDocument(null, true); + serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", + true); - serializer.startTag(null, TAG_PACKAGE_RESTRICTIONS); + serializer.startTag(null, TAG_PACKAGE_RESTRICTIONS); - if (DEBUG_MU) { - Slogf.i(TAG, "Writing %s (%d packages)", userPackagesStateFile, - mPackages.values().size()); - } - for (final PackageSetting pkg : mPackages.values()) { - final PackageUserStateInternal ustate = pkg.readUserState(userId); if (DEBUG_MU) { - Log.v(TAG, " pkg=" + pkg.getPackageName() - + ", installed=" + ustate.isInstalled() - + ", state=" + ustate.getEnabledState()); + Slogf.i(TAG, "Writing %s (%d packages)", atomicFile, + mPackages.values().size()); } + for (final PackageSetting pkg : mPackages.values()) { + final PackageUserStateInternal ustate = pkg.readUserState(userId); + if (DEBUG_MU) { + Log.v(TAG, " pkg=" + pkg.getPackageName() + + ", installed=" + ustate.isInstalled() + + ", state=" + ustate.getEnabledState()); + } - serializer.startTag(null, TAG_PACKAGE); - serializer.attribute(null, ATTR_NAME, pkg.getPackageName()); - if (ustate.getCeDataInode() != 0) { - serializer.attributeLong(null, ATTR_CE_DATA_INODE, ustate.getCeDataInode()); - } - if (!ustate.isInstalled()) { - serializer.attributeBoolean(null, ATTR_INSTALLED, false); - } - if (ustate.isStopped()) { - serializer.attributeBoolean(null, ATTR_STOPPED, true); - } - if (ustate.isNotLaunched()) { - serializer.attributeBoolean(null, ATTR_NOT_LAUNCHED, true); - } - if (ustate.isHidden()) { - serializer.attributeBoolean(null, ATTR_HIDDEN, true); - } - if (ustate.getDistractionFlags() != 0) { - serializer.attributeInt(null, ATTR_DISTRACTION_FLAGS, - ustate.getDistractionFlags()); - } - if (ustate.isSuspended()) { - serializer.attributeBoolean(null, ATTR_SUSPENDED, true); - } - if (ustate.isInstantApp()) { - serializer.attributeBoolean(null, ATTR_INSTANT_APP, true); - } - if (ustate.isVirtualPreload()) { - serializer.attributeBoolean(null, ATTR_VIRTUAL_PRELOAD, true); - } - if (ustate.getEnabledState() != COMPONENT_ENABLED_STATE_DEFAULT) { - serializer.attributeInt(null, ATTR_ENABLED, ustate.getEnabledState()); - if (ustate.getLastDisableAppCaller() != null) { - serializer.attribute(null, ATTR_ENABLED_CALLER, - ustate.getLastDisableAppCaller()); + serializer.startTag(null, TAG_PACKAGE); + serializer.attribute(null, ATTR_NAME, pkg.getPackageName()); + if (ustate.getCeDataInode() != 0) { + serializer.attributeLong(null, ATTR_CE_DATA_INODE, + ustate.getCeDataInode()); } - } - if (ustate.getInstallReason() != PackageManager.INSTALL_REASON_UNKNOWN) { - serializer.attributeInt(null, ATTR_INSTALL_REASON, - ustate.getInstallReason()); - } - serializer.attributeLongHex(null, ATTR_FIRST_INSTALL_TIME, - ustate.getFirstInstallTimeMillis()); - if (ustate.getUninstallReason() != PackageManager.UNINSTALL_REASON_UNKNOWN) { - serializer.attributeInt(null, ATTR_UNINSTALL_REASON, - ustate.getUninstallReason()); - } - if (ustate.getHarmfulAppWarning() != null) { - serializer.attribute(null, ATTR_HARMFUL_APP_WARNING, - ustate.getHarmfulAppWarning()); - } - if (ustate.getSplashScreenTheme() != null) { - serializer.attribute(null, ATTR_SPLASH_SCREEN_THEME, - ustate.getSplashScreenTheme()); - } - if (ustate.isSuspended()) { - for (int i = 0; i < ustate.getSuspendParams().size(); i++) { - final String suspendingPackage = ustate.getSuspendParams().keyAt(i); - serializer.startTag(null, TAG_SUSPEND_PARAMS); - serializer.attribute(null, ATTR_SUSPENDING_PACKAGE, suspendingPackage); - final SuspendParams params = - ustate.getSuspendParams().valueAt(i); - if (params != null) { - params.saveToXml(serializer); + if (!ustate.isInstalled()) { + serializer.attributeBoolean(null, ATTR_INSTALLED, false); + } + if (ustate.isStopped()) { + serializer.attributeBoolean(null, ATTR_STOPPED, true); + } + if (ustate.isNotLaunched()) { + serializer.attributeBoolean(null, ATTR_NOT_LAUNCHED, true); + } + if (ustate.isHidden()) { + serializer.attributeBoolean(null, ATTR_HIDDEN, true); + } + if (ustate.getDistractionFlags() != 0) { + serializer.attributeInt(null, ATTR_DISTRACTION_FLAGS, + ustate.getDistractionFlags()); + } + if (ustate.isSuspended()) { + serializer.attributeBoolean(null, ATTR_SUSPENDED, true); + } + if (ustate.isInstantApp()) { + serializer.attributeBoolean(null, ATTR_INSTANT_APP, true); + } + if (ustate.isVirtualPreload()) { + serializer.attributeBoolean(null, ATTR_VIRTUAL_PRELOAD, true); + } + if (ustate.getEnabledState() != COMPONENT_ENABLED_STATE_DEFAULT) { + serializer.attributeInt(null, ATTR_ENABLED, ustate.getEnabledState()); + if (ustate.getLastDisableAppCaller() != null) { + serializer.attribute(null, ATTR_ENABLED_CALLER, + ustate.getLastDisableAppCaller()); } - serializer.endTag(null, TAG_SUSPEND_PARAMS); } - } - final ArraySet<String> enabledComponents = ustate.getEnabledComponents(); - if (enabledComponents != null && enabledComponents.size() > 0) { - serializer.startTag(null, TAG_ENABLED_COMPONENTS); - for (int i = 0; i < enabledComponents.size(); i++) { - serializer.startTag(null, TAG_ITEM); - serializer.attribute(null, ATTR_NAME, - enabledComponents.valueAt(i)); - serializer.endTag(null, TAG_ITEM); + if (ustate.getInstallReason() != PackageManager.INSTALL_REASON_UNKNOWN) { + serializer.attributeInt(null, ATTR_INSTALL_REASON, + ustate.getInstallReason()); } - serializer.endTag(null, TAG_ENABLED_COMPONENTS); - } - final ArraySet<String> disabledComponents = ustate.getDisabledComponents(); - if (disabledComponents != null && disabledComponents.size() > 0) { - serializer.startTag(null, TAG_DISABLED_COMPONENTS); - for (int i = 0; i < disabledComponents.size(); i++) { - serializer.startTag(null, TAG_ITEM); - serializer.attribute(null, ATTR_NAME, - disabledComponents.valueAt(i)); - serializer.endTag(null, TAG_ITEM); + serializer.attributeLongHex(null, ATTR_FIRST_INSTALL_TIME, + ustate.getFirstInstallTimeMillis()); + if (ustate.getUninstallReason() + != PackageManager.UNINSTALL_REASON_UNKNOWN) { + serializer.attributeInt(null, ATTR_UNINSTALL_REASON, + ustate.getUninstallReason()); + } + if (ustate.getHarmfulAppWarning() != null) { + serializer.attribute(null, ATTR_HARMFUL_APP_WARNING, + ustate.getHarmfulAppWarning()); + } + if (ustate.getSplashScreenTheme() != null) { + serializer.attribute(null, ATTR_SPLASH_SCREEN_THEME, + ustate.getSplashScreenTheme()); + } + if (ustate.isSuspended()) { + for (int i = 0; i < ustate.getSuspendParams().size(); i++) { + final String suspendingPackage = ustate.getSuspendParams().keyAt(i); + serializer.startTag(null, TAG_SUSPEND_PARAMS); + serializer.attribute(null, ATTR_SUSPENDING_PACKAGE, + suspendingPackage); + final SuspendParams params = + ustate.getSuspendParams().valueAt(i); + if (params != null) { + params.saveToXml(serializer); + } + serializer.endTag(null, TAG_SUSPEND_PARAMS); + } + } + final ArraySet<String> enabledComponents = ustate.getEnabledComponents(); + if (enabledComponents != null && enabledComponents.size() > 0) { + serializer.startTag(null, TAG_ENABLED_COMPONENTS); + for (int i = 0; i < enabledComponents.size(); i++) { + serializer.startTag(null, TAG_ITEM); + serializer.attribute(null, ATTR_NAME, + enabledComponents.valueAt(i)); + serializer.endTag(null, TAG_ITEM); + } + serializer.endTag(null, TAG_ENABLED_COMPONENTS); + } + final ArraySet<String> disabledComponents = ustate.getDisabledComponents(); + if (disabledComponents != null && disabledComponents.size() > 0) { + serializer.startTag(null, TAG_DISABLED_COMPONENTS); + for (int i = 0; i < disabledComponents.size(); i++) { + serializer.startTag(null, TAG_ITEM); + serializer.attribute(null, ATTR_NAME, + disabledComponents.valueAt(i)); + serializer.endTag(null, TAG_ITEM); + } + serializer.endTag(null, TAG_DISABLED_COMPONENTS); } - serializer.endTag(null, TAG_DISABLED_COMPONENTS); - } - - serializer.endTag(null, TAG_PACKAGE); - } - - writePreferredActivitiesLPr(serializer, userId, true); - writePersistentPreferredActivitiesLPr(serializer, userId); - writeCrossProfileIntentFiltersLPr(serializer, userId); - writeDefaultAppsLPr(serializer, userId); - writeBlockUninstallPackagesLPr(serializer, userId); - serializer.endTag(null, TAG_PACKAGE_RESTRICTIONS); + serializer.endTag(null, TAG_PACKAGE); + } - serializer.endDocument(); - } + writePreferredActivitiesLPr(serializer, userId, true); + writePersistentPreferredActivitiesLPr(serializer, userId); + writeCrossProfileIntentFiltersLPr(serializer, userId); + writeDefaultAppsLPr(serializer, userId); + writeBlockUninstallPackagesLPr(serializer, userId); - fstr.flush(); - FileUtils.sync(fstr); - IoUtils.closeQuietly(fstr); + serializer.endTag(null, TAG_PACKAGE_RESTRICTIONS); - synchronized (mPackageRestrictionsLock) { - // File is created, set permissions. - FileUtils.setPermissions(userPackagesStateFile.toString(), - FileUtils.S_IRUSR | FileUtils.S_IWUSR - | FileUtils.S_IRGRP | FileUtils.S_IWGRP, - -1, -1); - // New settings successfully written, old ones are no longer needed. - backupFile.delete(); - } + serializer.endDocument(); + } - if (DEBUG_MU) { - Log.i(TAG, "New settings successfully written for user=" + userId + ": " - + userPackagesStateFile); - } + atomicFile.finishWrite(str); - com.android.internal.logging.EventLogTags.writeCommitSysConfigFile( - "package-user-" + userId, SystemClock.uptimeMillis() - startTime); + if (DEBUG_MU) { + Log.i(TAG, "New package restrictions successfully written for user=" + userId + + ": " + atomicFile); + } - // Done, all is good! - return; - } catch (java.io.IOException e) { - Slog.wtf(PackageManagerService.TAG, - "Unable to write package manager user packages state, " - + " current changes will be lost at reboot", e); - } + com.android.internal.logging.EventLogTags.writeCommitSysConfigFile( + "package-user-" + userId, SystemClock.uptimeMillis() - startTime); - // Clean up partially written files - if (userPackagesStateFile.exists()) { - if (!userPackagesStateFile.delete()) { - Log.i(PackageManagerService.TAG, "Failed to clean up mangled file: " - + mStoppedPackagesFilename); + // Done, all is good! + return; + } catch (java.io.IOException e) { + Slog.wtf(PackageManagerService.TAG, + "Unable to write package manager package restrictions, " + + " current changes will be lost at reboot", e); + if (str != null) { + atomicFile.failWrite(str); + } } } } @@ -2589,153 +2522,108 @@ public final class Settings implements Watchable, Snappable { // right time. invalidatePackageCache(); - // Keep the old settings around until we know the new ones have - // been successfully written. - if (mSettingsFilename.exists()) { - // Presence of backup settings file indicates that we failed - // to persist settings earlier. So preserve the older - // backup for future reference since the current settings - // might have been corrupted. - if (!mPreviousSettingsFilename.exists()) { - if (!mSettingsFilename.renameTo(mPreviousSettingsFilename)) { - Slog.wtf(PackageManagerService.TAG, - "Unable to store older package manager settings, " - + " current changes will be lost at reboot"); - return; - } - } else { - mSettingsFilename.delete(); - Slog.w(PackageManagerService.TAG, "Preserving older settings backup"); - } - } - mPastSignatures.clear(); - try { - final FileOutputStream fstr = new FileOutputStream(mSettingsFilename); - final TypedXmlSerializer serializer = Xml.resolveSerializer(fstr); - serializer.startDocument(null, true); - serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); - - serializer.startTag(null, "packages"); - - for (int i = 0; i < mVersion.size(); i++) { - final String volumeUuid = mVersion.keyAt(i); - final VersionInfo ver = mVersion.valueAt(i); - - serializer.startTag(null, TAG_VERSION); - XmlUtils.writeStringAttribute(serializer, ATTR_VOLUME_UUID, volumeUuid); - serializer.attributeInt(null, ATTR_SDK_VERSION, ver.sdkVersion); - serializer.attributeInt(null, ATTR_DATABASE_VERSION, ver.databaseVersion); - XmlUtils.writeStringAttribute(serializer, ATTR_BUILD_FINGERPRINT, - ver.buildFingerprint); - XmlUtils.writeStringAttribute(serializer, ATTR_FINGERPRINT, ver.fingerprint); - serializer.endTag(null, TAG_VERSION); - } + try (ResilientAtomicFile atomicFile = getSettingsFile()) { + FileOutputStream str = null; + try { + str = atomicFile.startWrite(); - if (mVerifierDeviceIdentity != null) { - serializer.startTag(null, "verifier"); - serializer.attribute(null, "device", mVerifierDeviceIdentity.toString()); - serializer.endTag(null, "verifier"); - } + final TypedXmlSerializer serializer = Xml.resolveSerializer(str); + serializer.startDocument(null, true); + serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", + true); - serializer.startTag(null, "permission-trees"); - mPermissions.writePermissionTrees(serializer); - serializer.endTag(null, "permission-trees"); + serializer.startTag(null, "packages"); - serializer.startTag(null, "permissions"); - mPermissions.writePermissions(serializer); - serializer.endTag(null, "permissions"); + for (int i = 0; i < mVersion.size(); i++) { + final String volumeUuid = mVersion.keyAt(i); + final VersionInfo ver = mVersion.valueAt(i); - for (final PackageSetting pkg : mPackages.values()) { - if (pkg.getPkg() != null && pkg.getPkg().isApex()) { - // Don't persist APEX which doesn't have a valid app id and will fail to load - continue; + serializer.startTag(null, TAG_VERSION); + XmlUtils.writeStringAttribute(serializer, ATTR_VOLUME_UUID, volumeUuid); + serializer.attributeInt(null, ATTR_SDK_VERSION, ver.sdkVersion); + serializer.attributeInt(null, ATTR_DATABASE_VERSION, ver.databaseVersion); + XmlUtils.writeStringAttribute(serializer, ATTR_BUILD_FINGERPRINT, + ver.buildFingerprint); + XmlUtils.writeStringAttribute(serializer, ATTR_FINGERPRINT, ver.fingerprint); + serializer.endTag(null, TAG_VERSION); } - writePackageLPr(serializer, pkg); - } - for (final PackageSetting pkg : mDisabledSysPackages.values()) { - if (pkg.getPkg() != null && pkg.getPkg().isApex()) { - // Don't persist APEX which doesn't have a valid app id and will fail to load - continue; + if (mVerifierDeviceIdentity != null) { + serializer.startTag(null, "verifier"); + serializer.attribute(null, "device", mVerifierDeviceIdentity.toString()); + serializer.endTag(null, "verifier"); } - writeDisabledSysPackageLPr(serializer, pkg); - } - for (final SharedUserSetting usr : mSharedUsers.values()) { - serializer.startTag(null, "shared-user"); - serializer.attribute(null, ATTR_NAME, usr.name); - serializer.attributeInt(null, "userId", usr.mAppId); - usr.signatures.writeXml(serializer, "sigs", mPastSignatures.untrackedStorage()); - serializer.endTag(null, "shared-user"); - } + serializer.startTag(null, "permission-trees"); + mPermissions.writePermissionTrees(serializer); + serializer.endTag(null, "permission-trees"); - if (mRenamedPackages.size() > 0) { - for (Map.Entry<String, String> e : mRenamedPackages.entrySet()) { - serializer.startTag(null, "renamed-package"); - serializer.attribute(null, "new", e.getKey()); - serializer.attribute(null, "old", e.getValue()); - serializer.endTag(null, "renamed-package"); + serializer.startTag(null, "permissions"); + mPermissions.writePermissions(serializer); + serializer.endTag(null, "permissions"); + + for (final PackageSetting pkg : mPackages.values()) { + if (pkg.getPkg() != null && pkg.getPkg().isApex()) { + // Don't persist APEX which doesn't have a valid app id and will fail to + // load + continue; + } + writePackageLPr(serializer, pkg); } - } - mDomainVerificationManager.writeSettings(computer, serializer, - false /* includeSignatures */, UserHandle.USER_ALL); + for (final PackageSetting pkg : mDisabledSysPackages.values()) { + if (pkg.getPkg() != null && pkg.getPkg().isApex()) { + // Don't persist APEX which doesn't have a valid app id and will fail to + // load + continue; + } + writeDisabledSysPackageLPr(serializer, pkg); + } - mKeySetManagerService.writeKeySetManagerServiceLPr(serializer); + for (final SharedUserSetting usr : mSharedUsers.values()) { + serializer.startTag(null, "shared-user"); + serializer.attribute(null, ATTR_NAME, usr.name); + serializer.attributeInt(null, "userId", usr.mAppId); + usr.signatures.writeXml(serializer, "sigs", mPastSignatures.untrackedStorage()); + serializer.endTag(null, "shared-user"); + } - serializer.endTag(null, "packages"); + if (mRenamedPackages.size() > 0) { + for (Map.Entry<String, String> e : mRenamedPackages.entrySet()) { + serializer.startTag(null, "renamed-package"); + serializer.attribute(null, "new", e.getKey()); + serializer.attribute(null, "old", e.getValue()); + serializer.endTag(null, "renamed-package"); + } + } - serializer.endDocument(); + mDomainVerificationManager.writeSettings(computer, serializer, + false /* includeSignatures */, UserHandle.USER_ALL); - fstr.flush(); - FileUtils.sync(fstr); - fstr.close(); + mKeySetManagerService.writeKeySetManagerServiceLPr(serializer); - // New settings successfully written, old ones are no longer needed. - mPreviousSettingsFilename.delete(); - mSettingsReserveCopyFilename.delete(); + serializer.endTag(null, "packages"); - FileUtils.setPermissions(mSettingsFilename.toString(), - FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP | FileUtils.S_IWGRP, - -1, -1); + serializer.endDocument(); - try (FileInputStream in = new FileInputStream(mSettingsFilename); - FileOutputStream out = new FileOutputStream(mSettingsReserveCopyFilename)) { - FileUtils.copy(in, out); - out.flush(); - FileUtils.sync(out); - } catch (IOException e) { - Slog.e(TAG, - "Failed to write reserve copy of settings: " + mSettingsReserveCopyFilename, - e); - } + atomicFile.finishWrite(str); - try { - FileIntegrity.setUpFsVerity(mSettingsFilename); - FileIntegrity.setUpFsVerity(mSettingsReserveCopyFilename); - } catch (IOException e) { - Slog.e(TAG, "Failed to verity-protect settings", e); - } - - writeKernelMappingLPr(); - writePackageListLPr(); - writeAllUsersPackageRestrictionsLPr(sync); - writeAllRuntimePermissionsLPr(); - com.android.internal.logging.EventLogTags.writeCommitSysConfigFile( - "package", SystemClock.uptimeMillis() - startTime); - return; + writeKernelMappingLPr(); + writePackageListLPr(); + writeAllUsersPackageRestrictionsLPr(sync); + writeAllRuntimePermissionsLPr(); + com.android.internal.logging.EventLogTags.writeCommitSysConfigFile( + "package", SystemClock.uptimeMillis() - startTime); + return; - } catch(java.io.IOException e) { - Slog.wtf(PackageManagerService.TAG, "Unable to write package manager settings, " - + "current changes will be lost at reboot", e); - } - // Clean up partially written files - if (mSettingsFilename.exists()) { - if (!mSettingsFilename.delete()) { - Slog.wtf(PackageManagerService.TAG, "Failed to clean up mangled file: " - + mSettingsFilename); + } catch (java.io.IOException e) { + Slog.wtf(PackageManagerService.TAG, "Unable to write package manager settings, " + + "current changes will be lost at reboot", e); + if (str != null) { + atomicFile.failWrite(str); + } } } //Debug.stopMethodTracing(); @@ -3160,183 +3048,140 @@ public final class Settings implements Watchable, Snappable { mInstallerPackages.clear(); originalFirstInstallTimes.clear(); - File file = null; - FileInputStream str = null; - - try { - // Check if the previous write was incomplete. - if (mPreviousSettingsFilename.exists()) { - try { - file = mPreviousSettingsFilename; - str = new FileInputStream(file); - mReadMessages.append("Reading from backup settings file\n"); - PackageManagerService.reportSettingsProblem(Log.INFO, - "Need to read from backup settings file"); - if (mSettingsFilename.exists()) { - // If both the previous and current settings files exist, - // we ignore the current since it might have been corrupted. - Slog.w(PackageManagerService.TAG, "Cleaning up settings file " - + mSettingsFilename); - mSettingsFilename.delete(); - } - // Ignore reserve copy as well. - mSettingsReserveCopyFilename.delete(); - } catch (java.io.IOException e) { - // We'll try for the normal settings file. - } - } - if (str == null) { - if (mSettingsFilename.exists()) { - // Using packages.xml. - file = mSettingsFilename; - str = new FileInputStream(file); - } else if (mSettingsReserveCopyFilename.exists()) { - // Using reserve copy. - file = mSettingsReserveCopyFilename; - str = new FileInputStream(file); - mReadMessages.append("Reading from reserve copy settings file\n"); - PackageManagerService.reportSettingsProblem(Log.INFO, - "Need to read from reserve copy settings file"); + try (ResilientAtomicFile atomicFile = getSettingsFile()) { + FileInputStream str = null; + try { + str = atomicFile.openRead(); + if (str == null) { + // Not necessary, but will avoid wtf-s in the "finally" section. + findOrCreateVersion(StorageManager.UUID_PRIVATE_INTERNAL).forceCurrent(); + findOrCreateVersion(StorageManager.UUID_PRIMARY_PHYSICAL).forceCurrent(); + return false; } - } - if (str == null) { - // No available data sources. - mReadMessages.append("No settings file found\n"); - PackageManagerService.reportSettingsProblem(Log.INFO, - "No settings file; creating initial state"); - // Not necessary, but will avoid wtf-s in the "finally" section. - findOrCreateVersion(StorageManager.UUID_PRIVATE_INTERNAL).forceCurrent(); - findOrCreateVersion(StorageManager.UUID_PRIMARY_PHYSICAL).forceCurrent(); - return false; - } - final TypedXmlPullParser parser = Xml.resolvePullParser(str); - - int type; - while ((type = parser.next()) != XmlPullParser.START_TAG - && type != XmlPullParser.END_DOCUMENT) { - ; - } + final TypedXmlPullParser parser = Xml.resolvePullParser(str); - if (type != XmlPullParser.START_TAG) { - mReadMessages.append("No start tag found in settings file\n"); - PackageManagerService.reportSettingsProblem(Log.WARN, - "No start tag found in package manager settings"); - Slog.wtf(PackageManagerService.TAG, - "No start tag found in package manager settings"); - return false; - } + int type; + while ((type = parser.next()) != XmlPullParser.START_TAG + && type != XmlPullParser.END_DOCUMENT) { + // nothing + } - int outerDepth = parser.getDepth(); - while ((type = parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { - if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { - continue; + if (type != XmlPullParser.START_TAG) { + mReadMessages.append("No start tag found in settings file\n"); + PackageManagerService.reportSettingsProblem(Log.WARN, + "No start tag found in package manager settings"); + Slog.wtf(PackageManagerService.TAG, + "No start tag found in package manager settings"); + return false; } - String tagName = parser.getName(); - if (tagName.equals("package")) { - readPackageLPw(parser, users, originalFirstInstallTimes); - } else if (tagName.equals("permissions")) { - mPermissions.readPermissions(parser); - } else if (tagName.equals("permission-trees")) { - mPermissions.readPermissionTrees(parser); - } else if (tagName.equals("shared-user")) { - readSharedUserLPw(parser, users); - } else if (tagName.equals("preferred-packages")) { - // no longer used. - } else if (tagName.equals("preferred-activities")) { - // Upgrading from old single-user implementation; - // these are the preferred activities for user 0. - readPreferredActivitiesLPw(parser, 0); - } else if (tagName.equals(TAG_PERSISTENT_PREFERRED_ACTIVITIES)) { - // TODO: check whether this is okay! as it is very - // similar to how preferred-activities are treated - readPersistentPreferredActivitiesLPw(parser, 0); - } else if (tagName.equals(TAG_CROSS_PROFILE_INTENT_FILTERS)) { - // TODO: check whether this is okay! as it is very - // similar to how preferred-activities are treated - readCrossProfileIntentFiltersLPw(parser, 0); - } else if (tagName.equals(TAG_DEFAULT_BROWSER)) { - readDefaultAppsLPw(parser, 0); - } else if (tagName.equals("updated-package")) { - readDisabledSysPackageLPw(parser, users); - } else if (tagName.equals("renamed-package")) { - String nname = parser.getAttributeValue(null, "new"); - String oname = parser.getAttributeValue(null, "old"); - if (nname != null && oname != null) { - mRenamedPackages.put(nname, oname); + int outerDepth = parser.getDepth(); + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; } - } else if (tagName.equals("last-platform-version")) { - // Upgrade from older XML schema - final VersionInfo internal = findOrCreateVersion( - StorageManager.UUID_PRIVATE_INTERNAL); - final VersionInfo external = findOrCreateVersion( - StorageManager.UUID_PRIMARY_PHYSICAL); - - internal.sdkVersion = parser.getAttributeInt(null, "internal", 0); - external.sdkVersion = parser.getAttributeInt(null, "external", 0); - internal.buildFingerprint = external.buildFingerprint = - XmlUtils.readStringAttribute(parser, "buildFingerprint"); - internal.fingerprint = external.fingerprint = - XmlUtils.readStringAttribute(parser, "fingerprint"); - - } else if (tagName.equals("database-version")) { - // Upgrade from older XML schema - final VersionInfo internal = findOrCreateVersion( - StorageManager.UUID_PRIVATE_INTERNAL); - final VersionInfo external = findOrCreateVersion( - StorageManager.UUID_PRIMARY_PHYSICAL); - - internal.databaseVersion = parser.getAttributeInt(null, "internal", 0); - external.databaseVersion = parser.getAttributeInt(null, "external", 0); - - } else if (tagName.equals("verifier")) { - final String deviceIdentity = parser.getAttributeValue(null, "device"); - try { - mVerifierDeviceIdentity = VerifierDeviceIdentity.parse(deviceIdentity); - } catch (IllegalArgumentException e) { - Slog.w(PackageManagerService.TAG, "Discard invalid verifier device id: " - + e.getMessage()); + + String tagName = parser.getName(); + if (tagName.equals("package")) { + readPackageLPw(parser, users, originalFirstInstallTimes); + } else if (tagName.equals("permissions")) { + mPermissions.readPermissions(parser); + } else if (tagName.equals("permission-trees")) { + mPermissions.readPermissionTrees(parser); + } else if (tagName.equals("shared-user")) { + readSharedUserLPw(parser, users); + } else if (tagName.equals("preferred-packages")) { + // no longer used. + } else if (tagName.equals("preferred-activities")) { + // Upgrading from old single-user implementation; + // these are the preferred activities for user 0. + readPreferredActivitiesLPw(parser, 0); + } else if (tagName.equals(TAG_PERSISTENT_PREFERRED_ACTIVITIES)) { + // TODO: check whether this is okay! as it is very + // similar to how preferred-activities are treated + readPersistentPreferredActivitiesLPw(parser, 0); + } else if (tagName.equals(TAG_CROSS_PROFILE_INTENT_FILTERS)) { + // TODO: check whether this is okay! as it is very + // similar to how preferred-activities are treated + readCrossProfileIntentFiltersLPw(parser, 0); + } else if (tagName.equals(TAG_DEFAULT_BROWSER)) { + readDefaultAppsLPw(parser, 0); + } else if (tagName.equals("updated-package")) { + readDisabledSysPackageLPw(parser, users); + } else if (tagName.equals("renamed-package")) { + String nname = parser.getAttributeValue(null, "new"); + String oname = parser.getAttributeValue(null, "old"); + if (nname != null && oname != null) { + mRenamedPackages.put(nname, oname); + } + } else if (tagName.equals("last-platform-version")) { + // Upgrade from older XML schema + final VersionInfo internal = findOrCreateVersion( + StorageManager.UUID_PRIVATE_INTERNAL); + final VersionInfo external = findOrCreateVersion( + StorageManager.UUID_PRIMARY_PHYSICAL); + + internal.sdkVersion = parser.getAttributeInt(null, "internal", 0); + external.sdkVersion = parser.getAttributeInt(null, "external", 0); + internal.buildFingerprint = external.buildFingerprint = + XmlUtils.readStringAttribute(parser, "buildFingerprint"); + internal.fingerprint = external.fingerprint = + XmlUtils.readStringAttribute(parser, "fingerprint"); + + } else if (tagName.equals("database-version")) { + // Upgrade from older XML schema + final VersionInfo internal = findOrCreateVersion( + StorageManager.UUID_PRIVATE_INTERNAL); + final VersionInfo external = findOrCreateVersion( + StorageManager.UUID_PRIMARY_PHYSICAL); + + internal.databaseVersion = parser.getAttributeInt(null, "internal", 0); + external.databaseVersion = parser.getAttributeInt(null, "external", 0); + + } else if (tagName.equals("verifier")) { + final String deviceIdentity = parser.getAttributeValue(null, "device"); + try { + mVerifierDeviceIdentity = VerifierDeviceIdentity.parse(deviceIdentity); + } catch (IllegalArgumentException e) { + Slog.w(PackageManagerService.TAG, "Discard invalid verifier device id: " + + e.getMessage()); + } + } else if (TAG_READ_EXTERNAL_STORAGE.equals(tagName)) { + // No longer used. + } else if (tagName.equals("keyset-settings")) { + mKeySetManagerService.readKeySetsLPw(parser, + mKeySetRefs.untrackedStorage()); + } else if (TAG_VERSION.equals(tagName)) { + final String volumeUuid = XmlUtils.readStringAttribute(parser, + ATTR_VOLUME_UUID); + final VersionInfo ver = findOrCreateVersion(volumeUuid); + ver.sdkVersion = parser.getAttributeInt(null, ATTR_SDK_VERSION); + ver.databaseVersion = parser.getAttributeInt(null, ATTR_DATABASE_VERSION); + ver.buildFingerprint = XmlUtils.readStringAttribute(parser, + ATTR_BUILD_FINGERPRINT); + ver.fingerprint = XmlUtils.readStringAttribute(parser, ATTR_FINGERPRINT); + } else if (tagName.equals( + DomainVerificationPersistence.TAG_DOMAIN_VERIFICATIONS)) { + mDomainVerificationManager.readSettings(computer, parser); + } else if (tagName.equals( + DomainVerificationLegacySettings.TAG_DOMAIN_VERIFICATIONS_LEGACY)) { + mDomainVerificationManager.readLegacySettings(parser); + } else { + Slog.w(PackageManagerService.TAG, "Unknown element under <packages>: " + + parser.getName()); + XmlUtils.skipCurrentTag(parser); } - } else if (TAG_READ_EXTERNAL_STORAGE.equals(tagName)) { - // No longer used. - } else if (tagName.equals("keyset-settings")) { - mKeySetManagerService.readKeySetsLPw(parser, mKeySetRefs.untrackedStorage()); - } else if (TAG_VERSION.equals(tagName)) { - final String volumeUuid = XmlUtils.readStringAttribute(parser, - ATTR_VOLUME_UUID); - final VersionInfo ver = findOrCreateVersion(volumeUuid); - ver.sdkVersion = parser.getAttributeInt(null, ATTR_SDK_VERSION); - ver.databaseVersion = parser.getAttributeInt(null, ATTR_DATABASE_VERSION); - ver.buildFingerprint = XmlUtils.readStringAttribute(parser, - ATTR_BUILD_FINGERPRINT); - ver.fingerprint = XmlUtils.readStringAttribute(parser, ATTR_FINGERPRINT); - } else if (tagName.equals(DomainVerificationPersistence.TAG_DOMAIN_VERIFICATIONS)) { - mDomainVerificationManager.readSettings(computer, parser); - } else if (tagName.equals( - DomainVerificationLegacySettings.TAG_DOMAIN_VERIFICATIONS_LEGACY)) { - mDomainVerificationManager.readLegacySettings(parser); - } else { - Slog.w(PackageManagerService.TAG, "Unknown element under <packages>: " - + parser.getName()); - XmlUtils.skipCurrentTag(parser); } - } - - str.close(); - } catch (IOException | XmlPullParserException e) { - mReadMessages.append("Error reading: " + e.toString()); - PackageManagerService.reportSettingsProblem(Log.ERROR, "Error reading settings: " + e); - Slog.wtf(PackageManagerService.TAG, "Error reading package manager settings", e); - // Remove corrupted file and retry. - Slog.e(TAG, - "Error reading package manager settings, removing " + file + " and retrying.", - e); - file.delete(); + str.close(); + } catch (IOException | XmlPullParserException e) { + // Remove corrupted file and retry. + atomicFile.failRead(str, e); - // Ignore the result to not mark this as a "first boot". - readSettingsLPw(computer, users, originalFirstInstallTimes); + // Ignore the result to not mark this as a "first boot". + readSettingsLPw(computer, users, originalFirstInstallTimes); + } } return true; @@ -4488,10 +4333,7 @@ public final class Settings implements Watchable, Snappable { mPreferredActivities.remove(userId); synchronized (mPackageRestrictionsLock) { - File file = getUserPackagesStateFile(userId); - file.delete(); - file = getUserPackagesStateBackupFile(userId); - file.delete(); + getUserPackagesStateFile(userId).delete(); mPendingAsyncPackageRestrictionsWrites.delete(userId); } diff --git a/services/core/java/com/android/server/pm/ShortcutPackageItem.java b/services/core/java/com/android/server/pm/ShortcutPackageItem.java index e20330d562f6..8b118da1cdbe 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackageItem.java +++ b/services/core/java/com/android/server/pm/ShortcutPackageItem.java @@ -27,6 +27,7 @@ import android.util.Xml; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; import com.android.modules.utils.TypedXmlSerializer; +import com.android.server.security.FileIntegrity; import org.json.JSONException; import org.json.JSONObject; @@ -180,6 +181,12 @@ abstract class ShortcutPackageItem { os.flush(); file.finishWrite(os); + + try { + FileIntegrity.setUpFsVerity(path); + } catch (IOException e) { + Slog.e(TAG, "Failed to verity-protect " + path, e); + } } catch (XmlPullParserException | IOException e) { Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e); file.failWrite(os); diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 372b3bb8681b..20cb485434b0 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -119,7 +119,6 @@ import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; import com.android.server.LocalServices; import com.android.server.SystemService; -import com.android.server.security.FileIntegrity; import com.android.server.uri.UriGrantsManagerInternal; import org.json.JSONArray; @@ -1070,57 +1069,38 @@ public class ShortcutService extends IShortcutService.Stub { } @VisibleForTesting - final File getUserFile(@UserIdInt int userId) { - return new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES); - } - - @VisibleForTesting - final File getReserveCopyUserFile(@UserIdInt int userId) { - return new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES_RESERVE_COPY); + final ResilientAtomicFile getUserFile(@UserIdInt int userId) { + File mainFile = new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES); + File temporaryBackup = new File(injectUserDataPath(userId), + FILENAME_USER_PACKAGES + ".backup"); + File reserveCopy = new File(injectUserDataPath(userId), + FILENAME_USER_PACKAGES_RESERVE_COPY); + int fileMode = FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IXOTH; + return new ResilientAtomicFile(mainFile, temporaryBackup, reserveCopy, fileMode, + "user shortcut", null); } @GuardedBy("mLock") private void saveUserLocked(@UserIdInt int userId) { - final File path = getUserFile(userId); - if (DEBUG || DEBUG_REBOOT) { - Slog.d(TAG, "Saving to " + path); - } - - final File reservePath = getReserveCopyUserFile(userId); - reservePath.delete(); - - path.getParentFile().mkdirs(); - final AtomicFile file = new AtomicFile(path); - FileOutputStream os = null; - try { - os = file.startWrite(); - - saveUserInternalLocked(userId, os, /* forBackup= */ false); + try (ResilientAtomicFile file = getUserFile(userId)) { + FileOutputStream os = null; + try { + if (DEBUG || DEBUG_REBOOT) { + Slog.d(TAG, "Saving to " + file); + } - file.finishWrite(os); + os = file.startWrite(); - // Remove all dangling bitmap files. - cleanupDanglingBitmapDirectoriesLocked(userId); - } catch (XmlPullParserException | IOException e) { - Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e); - file.failWrite(os); - } + saveUserInternalLocked(userId, os, /* forBackup= */ false); - // Store the reserve copy of the file. - try (FileInputStream in = new FileInputStream(path); - FileOutputStream out = new FileOutputStream(reservePath)) { - FileUtils.copy(in, out); - FileUtils.sync(out); - } catch (IOException e) { - Slog.e(TAG, "Failed to write reserve copy: " + path, e); - } + file.finishWrite(os); - // Protect both primary and reserve copy with fs-verity. - try { - FileIntegrity.setUpFsVerity(path); - FileIntegrity.setUpFsVerity(reservePath); - } catch (IOException e) { - Slog.e(TAG, "Failed to verity-protect", e); + // Remove all dangling bitmap files. + cleanupDanglingBitmapDirectoriesLocked(userId); + } catch (XmlPullParserException | IOException e) { + Slog.e(TAG, "Failed to write to file " + file, e); + file.failWrite(os); + } } getUserShortcutsLocked(userId).logSharingShortcutStats(mMetricsLogger); @@ -1157,29 +1137,26 @@ public class ShortcutService extends IShortcutService.Stub { @Nullable private ShortcutUser loadUserLocked(@UserIdInt int userId) { - final File path = getUserFile(userId); - if (DEBUG || DEBUG_REBOOT) { - Slog.d(TAG, "Loading from " + path); - } - - try (FileInputStream in = new AtomicFile(path).openRead()) { - return loadUserInternal(userId, in, /* forBackup= */ false); - } catch (FileNotFoundException e) { - if (DEBUG || DEBUG_REBOOT) { - Slog.d(TAG, "Not found " + path); - } - } catch (Exception e) { - final File reservePath = getReserveCopyUserFile(userId); - Slog.e(TAG, "Reading from reserve copy: " + reservePath, e); - try (FileInputStream in = new AtomicFile(reservePath).openRead()) { + try (ResilientAtomicFile file = getUserFile(userId)) { + FileInputStream in = null; + try { + if (DEBUG || DEBUG_REBOOT) { + Slog.d(TAG, "Loading from " + file); + } + in = file.openRead(); + if (in == null) { + if (DEBUG || DEBUG_REBOOT) { + Slog.d(TAG, "Not found " + file); + } + return null; + } return loadUserInternal(userId, in, /* forBackup= */ false); - } catch (Exception exceptionReadingReserveFile) { - Slog.e(TAG, "Failed to read reserve copy: " + reservePath, - exceptionReadingReserveFile); + } catch (Exception e) { + // Remove corrupted file and retry. + file.failRead(in, e); + return loadUserLocked(userId); } - Slog.e(TAG, "Failed to read file " + path, e); } - return null; } private ShortcutUser loadUserInternal(@UserIdInt int userId, InputStream is, diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java index a39e0216f4e5..836f8581e8eb 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java @@ -566,6 +566,22 @@ public class PackageManagerSettingsTests { } @Test + public void testWriteCorruptReadPackageRestrictions() { + final Settings settingsUnderTest = makeSettings(); + + populateDistractionFlags(settingsUnderTest); + settingsUnderTest.writePackageRestrictionsLPr(0, /*sync=*/true); + + // Corrupt primary file. + writeCorruptedPackageRestrictions(0); + + // now read and verify + populateDefaultSettings(settingsUnderTest); + settingsUnderTest.readPackageRestrictionsLPr(0, mOrigFirstInstallTimes); + verifyDistractionFlags(settingsUnderTest); + } + + @Test public void testReadWritePackageRestrictionsAsync() { final Settings settingsWrite = makeSettings(); final Settings settingsRead = makeSettings(); @@ -1811,6 +1827,14 @@ public class PackageManagerSettingsTests { .getBytes()); } + private void writeCorruptedPackageRestrictions(final int userId) { + writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(), "system/users/" + + userId + "/package-restrictions.xml"), + ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n" + + "<package-restrictions>\n" + + " <pkg name=\"" + PACKAGE_NAME_1 + "\" ").getBytes()); + } + private static void writeStoppedPackagesXml() { writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(), "system/packages-stopped.xml"), ( "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>" diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java index 0a718e329498..861087a5ab54 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java @@ -4096,11 +4096,12 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { // Save and corrupt the primary files. mService.saveDirtyInfo(); - try (Writer os = new FileWriter(mService.getUserFile(UserHandle.USER_SYSTEM))) { + try (Writer os = new FileWriter( + mService.getUserFile(UserHandle.USER_SYSTEM).getBaseFile())) { os.write("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n" + "<user locales=\"en\" last-app-scan-time2=\"14400000"); } - try (Writer os = new FileWriter(mService.getUserFile(USER_10))) { + try (Writer os = new FileWriter(mService.getUserFile(USER_10).getBaseFile())) { os.write("<?xml version='1.0' encoding='utf"); } diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java index 15fd73cf4674..01e56a0ddae6 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java @@ -2349,7 +2349,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { * can still be read. */ public void testLoadLegacySavedFile() throws Exception { - final File path = mService.getUserFile(USER_0); + final File path = mService.getUserFile(USER_0).getBaseFile(); path.getParentFile().mkdirs(); try (Writer w = new FileWriter(path)) { w.write(readTestAsset("shortcut/shortcut_legacy_file.xml")); |