blob: 56e1d080a988ce6941c13e5e57e4fcfbe6bb5a50 [file] [log] [blame]
Narayan Kamathc034fe92019-01-23 10:48:17 +00001/*
2 * Copyright (C) 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.rollback;
18
19import android.content.rollback.PackageRollbackInfo;
20import android.content.rollback.PackageRollbackInfo.RestoreInfo;
Narayan Kamathc034fe92019-01-23 10:48:17 +000021import android.os.storage.StorageManager;
22import android.util.IntArray;
23import android.util.Log;
Nikita Ioffe952aa7b2019-01-28 19:49:56 +000024import android.util.SparseLongArray;
Narayan Kamathc034fe92019-01-23 10:48:17 +000025
26import com.android.internal.annotations.VisibleForTesting;
27import com.android.server.pm.Installer;
28import com.android.server.pm.Installer.InstallerException;
29
30import java.util.ArrayList;
Nikita Ioffe5dcd17972019-02-04 11:08:13 +000031import java.util.HashMap;
Richard Uhlere9aaf632019-03-01 16:03:01 +000032import java.util.HashSet;
Nikita Ioffe5dcd17972019-02-04 11:08:13 +000033import java.util.Iterator;
Narayan Kamathc034fe92019-01-23 10:48:17 +000034import java.util.List;
35import java.util.Map;
Richard Uhlere9aaf632019-03-01 16:03:01 +000036import java.util.Set;
Narayan Kamathc034fe92019-01-23 10:48:17 +000037
38/**
39 * Encapsulates the logic for initiating userdata snapshots and rollbacks via installd.
40 */
41@VisibleForTesting
42// TODO(narayan): Reason about the failure scenarios that involve one or more IPCs to installd
43// failing. We need to decide what course of action to take if calls to snapshotAppData or
44// restoreAppDataSnapshot fail.
45public class AppDataRollbackHelper {
46 private static final String TAG = "RollbackManager";
47
48 private final Installer mInstaller;
49
50 public AppDataRollbackHelper(Installer installer) {
51 mInstaller = installer;
52 }
53
54 /**
Nikita Ioffe5dcd17972019-02-04 11:08:13 +000055 * Creates an app data snapshot for a specified {@code packageRollbackInfo}. Updates said {@code
56 * packageRollbackInfo} with the inodes of the CE user data snapshot folders.
Narayan Kamathc034fe92019-01-23 10:48:17 +000057 */
Nikita Ioffe5dcd17972019-02-04 11:08:13 +000058 public void snapshotAppData(int snapshotId, PackageRollbackInfo packageRollbackInfo) {
59 final int[] installedUsers = packageRollbackInfo.getInstalledUsers().toArray();
Narayan Kamathc034fe92019-01-23 10:48:17 +000060 for (int user : installedUsers) {
61 final int storageFlags;
62 if (isUserCredentialLocked(user)) {
63 // We've encountered a user that hasn't unlocked on a FBE device, so we can't copy
64 // across app user data until the user unlocks their device.
65 Log.v(TAG, "User: " + user + " isn't unlocked, skipping CE userdata backup.");
66 storageFlags = Installer.FLAG_STORAGE_DE;
Nikita Ioffe5dcd17972019-02-04 11:08:13 +000067 packageRollbackInfo.addPendingBackup(user);
Narayan Kamathc034fe92019-01-23 10:48:17 +000068 } else {
69 storageFlags = Installer.FLAG_STORAGE_CE | Installer.FLAG_STORAGE_DE;
70 }
71
72 try {
Nikita Ioffe5dcd17972019-02-04 11:08:13 +000073 long ceSnapshotInode = mInstaller.snapshotAppData(
74 packageRollbackInfo.getPackageName(), user, snapshotId, storageFlags);
Nikita Ioffe952aa7b2019-01-28 19:49:56 +000075 if ((storageFlags & Installer.FLAG_STORAGE_CE) != 0) {
Nikita Ioffe5dcd17972019-02-04 11:08:13 +000076 packageRollbackInfo.putCeSnapshotInode(user, ceSnapshotInode);
Nikita Ioffe952aa7b2019-01-28 19:49:56 +000077 }
Narayan Kamathc034fe92019-01-23 10:48:17 +000078 } catch (InstallerException ie) {
Nikita Ioffe5dcd17972019-02-04 11:08:13 +000079 Log.e(TAG, "Unable to create app data snapshot for: "
80 + packageRollbackInfo.getPackageName() + ", userId: " + user, ie);
Narayan Kamathc034fe92019-01-23 10:48:17 +000081 }
82 }
Narayan Kamathc034fe92019-01-23 10:48:17 +000083 }
84
85 /**
Nikita Ioffe5dcd17972019-02-04 11:08:13 +000086 * Restores an app data snapshot for a specified {@code packageRollbackInfo}, for a specified
87 * {@code userId}.
Narayan Kamathc034fe92019-01-23 10:48:17 +000088 *
Nikita Ioffe5dcd17972019-02-04 11:08:13 +000089 * @return {@code true} iff. a change to the {@code packageRollbackInfo} has been made. Changes
90 * to {@code packageRollbackInfo} are restricted to the removal or addition of {@code
91 * userId} to the list of pending backups or restores.
Narayan Kamathc034fe92019-01-23 10:48:17 +000092 */
Nikita Ioffe5dcd17972019-02-04 11:08:13 +000093 public boolean restoreAppData(int rollbackId, PackageRollbackInfo packageRollbackInfo,
94 int userId, int appId, String seInfo) {
Narayan Kamathc034fe92019-01-23 10:48:17 +000095 int storageFlags = Installer.FLAG_STORAGE_DE;
96
Nikita Ioffe5dcd17972019-02-04 11:08:13 +000097 final IntArray pendingBackups = packageRollbackInfo.getPendingBackups();
98 final List<RestoreInfo> pendingRestores = packageRollbackInfo.getPendingRestores();
Narayan Kamathc034fe92019-01-23 10:48:17 +000099 boolean changedRollbackData = false;
100
101 // If we still have a userdata backup pending for this user, it implies that the user
102 // hasn't unlocked their device between the point of backup and the point of restore,
103 // so the data cannot have changed. We simply skip restoring CE data in this case.
104 if (pendingBackups != null && pendingBackups.indexOf(userId) != -1) {
105 pendingBackups.remove(pendingBackups.indexOf(userId));
106 changedRollbackData = true;
107 } else {
108 // There's no pending CE backup for this user, which means that we successfully
109 // managed to backup data for the user, which means we seek to restore it
110 if (isUserCredentialLocked(userId)) {
111 // We've encountered a user that hasn't unlocked on a FBE device, so we can't
112 // copy across app user data until the user unlocks their device.
113 pendingRestores.add(new RestoreInfo(userId, appId, seInfo));
114 changedRollbackData = true;
115 } else {
116 // This user has unlocked, we can proceed to restore both CE and DE data.
117 storageFlags = storageFlags | Installer.FLAG_STORAGE_CE;
118 }
119 }
120
121 try {
Nikita Ioffe5dcd17972019-02-04 11:08:13 +0000122 mInstaller.restoreAppDataSnapshot(packageRollbackInfo.getPackageName(), appId, seInfo,
123 userId, rollbackId, storageFlags);
Narayan Kamathc034fe92019-01-23 10:48:17 +0000124 } catch (InstallerException ie) {
Nikita Ioffe5dcd17972019-02-04 11:08:13 +0000125 Log.e(TAG, "Unable to restore app data snapshot: "
126 + packageRollbackInfo.getPackageName(), ie);
Narayan Kamathc034fe92019-01-23 10:48:17 +0000127 }
128
129 return changedRollbackData;
130 }
131
132 /**
Nikita Ioffe5dcd17972019-02-04 11:08:13 +0000133 * Deletes an app data snapshot with a given {@code rollbackId} for a specified package
134 * {@code packageName} for a given {@code user}.
Nikita Ioffe952aa7b2019-01-28 19:49:56 +0000135 */
Nikita Ioffe5dcd17972019-02-04 11:08:13 +0000136 public void destroyAppDataSnapshot(int rollbackId, PackageRollbackInfo packageRollbackInfo,
137 int user) {
Nikita Ioffe952aa7b2019-01-28 19:49:56 +0000138 int storageFlags = Installer.FLAG_STORAGE_DE;
Nikita Ioffe5dcd17972019-02-04 11:08:13 +0000139 final SparseLongArray ceSnapshotInodes = packageRollbackInfo.getCeSnapshotInodes();
140 long ceSnapshotInode = ceSnapshotInodes.get(user);
Nikita Ioffe952aa7b2019-01-28 19:49:56 +0000141 if (ceSnapshotInode > 0) {
142 storageFlags |= Installer.FLAG_STORAGE_CE;
143 }
144 try {
Nikita Ioffe5dcd17972019-02-04 11:08:13 +0000145 mInstaller.destroyAppDataSnapshot(packageRollbackInfo.getPackageName(), user,
146 ceSnapshotInode, rollbackId, storageFlags);
147 if ((storageFlags & Installer.FLAG_STORAGE_CE) != 0) {
148 ceSnapshotInodes.delete(user);
149 }
Nikita Ioffe952aa7b2019-01-28 19:49:56 +0000150 } catch (InstallerException ie) {
Nikita Ioffe5dcd17972019-02-04 11:08:13 +0000151 Log.e(TAG, "Unable to delete app data snapshot for "
152 + packageRollbackInfo.getPackageName(), ie);
Nikita Ioffe952aa7b2019-01-28 19:49:56 +0000153 }
154 }
155
156 /**
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000157 * Computes the list of pending backups for {@code userId} given lists of rollbacks.
Nikita Ioffe5dcd17972019-02-04 11:08:13 +0000158 * Packages pending backup for the given user are added to {@code pendingBackupPackages} along
159 * with their corresponding {@code PackageRollbackInfo}.
Narayan Kamathc034fe92019-01-23 10:48:17 +0000160 *
Nikita Ioffe5dcd17972019-02-04 11:08:13 +0000161 * @return the list of {@code RollbackData} that has pending backups. Note that some of the
162 * backups won't be performed, because they might be counteracted by pending restores.
Narayan Kamathc034fe92019-01-23 10:48:17 +0000163 */
Nikita Ioffe5dcd17972019-02-04 11:08:13 +0000164 private static List<RollbackData> computePendingBackups(int userId,
165 Map<String, PackageRollbackInfo> pendingBackupPackages,
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000166 List<RollbackData> rollbacks) {
Narayan Kamathc034fe92019-01-23 10:48:17 +0000167 List<RollbackData> rd = new ArrayList<>();
Nikita Ioffe5dcd17972019-02-04 11:08:13 +0000168
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000169 for (RollbackData data : rollbacks) {
Richard Uhlercca637a2019-02-27 11:50:48 +0000170 for (PackageRollbackInfo info : data.info.getPackages()) {
Narayan Kamathc034fe92019-01-23 10:48:17 +0000171 final IntArray pendingBackupUsers = info.getPendingBackups();
172 if (pendingBackupUsers != null) {
173 final int idx = pendingBackupUsers.indexOf(userId);
174 if (idx != -1) {
Nikita Ioffe5dcd17972019-02-04 11:08:13 +0000175 pendingBackupPackages.put(info.getPackageName(), info);
Narayan Kamathc034fe92019-01-23 10:48:17 +0000176 if (rd.indexOf(data) == -1) {
177 rd.add(data);
178 }
179 }
180 }
181 }
182 }
Nikita Ioffe5dcd17972019-02-04 11:08:13 +0000183 return rd;
184 }
Narayan Kamathc034fe92019-01-23 10:48:17 +0000185
Nikita Ioffe5dcd17972019-02-04 11:08:13 +0000186 /**
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000187 * Computes the list of pending restores for {@code userId} given lists of rollbacks.
Nikita Ioffe5dcd17972019-02-04 11:08:13 +0000188 * Packages pending restore are added to {@code pendingRestores} along with their corresponding
189 * {@code PackageRollbackInfo}.
190 *
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000191 * @return the list of {@code RollbackData} that has pending restores. Note that some of the
Nikita Ioffe5dcd17972019-02-04 11:08:13 +0000192 * restores won't be performed, because they might be counteracted by pending backups.
193 */
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000194 private static List<RollbackData> computePendingRestores(int userId,
Nikita Ioffe5dcd17972019-02-04 11:08:13 +0000195 Map<String, PackageRollbackInfo> pendingRestorePackages,
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000196 List<RollbackData> rollbacks) {
197 List<RollbackData> rd = new ArrayList<>();
Nikita Ioffe5dcd17972019-02-04 11:08:13 +0000198
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000199 for (RollbackData data : rollbacks) {
200 for (PackageRollbackInfo info : data.info.getPackages()) {
Narayan Kamathc034fe92019-01-23 10:48:17 +0000201 final RestoreInfo ri = info.getRestoreInfo(userId);
202 if (ri != null) {
Nikita Ioffe5dcd17972019-02-04 11:08:13 +0000203 pendingRestorePackages.put(info.getPackageName(), info);
204 if (rd.indexOf(data) == -1) {
205 rd.add(data);
Narayan Kamathc034fe92019-01-23 10:48:17 +0000206 }
Narayan Kamathc034fe92019-01-23 10:48:17 +0000207 }
208 }
209 }
210
211 return rd;
212 }
213
214 /**
Nikita Ioffe952aa7b2019-01-28 19:49:56 +0000215 * Commits the list of pending backups and restores for a given {@code userId}. For the pending
216 * backups updates corresponding {@code changedRollbackData} with a mapping from {@code userId}
217 * to a inode of theirs CE user data snapshot.
Nikita Ioffe5dcd17972019-02-04 11:08:13 +0000218 *
Richard Uhlere9aaf632019-03-01 16:03:01 +0000219 * @return the set of {@code RollbackData} that have been changed and should be stored on disk.
Narayan Kamathc034fe92019-01-23 10:48:17 +0000220 */
Richard Uhlere9aaf632019-03-01 16:03:01 +0000221 public Set<RollbackData> commitPendingBackupAndRestoreForUser(int userId,
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000222 List<RollbackData> rollbacks) {
Nikita Ioffe5dcd17972019-02-04 11:08:13 +0000223
224 final Map<String, PackageRollbackInfo> pendingBackupPackages = new HashMap<>();
225 final List<RollbackData> pendingBackups = computePendingBackups(userId,
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000226 pendingBackupPackages, rollbacks);
Nikita Ioffe5dcd17972019-02-04 11:08:13 +0000227
228 final Map<String, PackageRollbackInfo> pendingRestorePackages = new HashMap<>();
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000229 final List<RollbackData> pendingRestores = computePendingRestores(userId,
230 pendingRestorePackages, rollbacks);
Nikita Ioffe5dcd17972019-02-04 11:08:13 +0000231
232 // First remove unnecessary backups, i.e. when user did not unlock their phone between the
233 // request to backup data and the request to restore it.
234 Iterator<Map.Entry<String, PackageRollbackInfo>> iter =
235 pendingBackupPackages.entrySet().iterator();
236 while (iter.hasNext()) {
237 PackageRollbackInfo backupPackage = iter.next().getValue();
238 PackageRollbackInfo restorePackage =
239 pendingRestorePackages.get(backupPackage.getPackageName());
240 if (restorePackage != null) {
241 backupPackage.removePendingBackup(userId);
242 backupPackage.removePendingRestoreInfo(userId);
243 iter.remove();
244 pendingRestorePackages.remove(backupPackage.getPackageName());
245 }
246 }
247
248 if (!pendingBackupPackages.isEmpty()) {
249 for (RollbackData data : pendingBackups) {
Richard Uhlercca637a2019-02-27 11:50:48 +0000250 for (PackageRollbackInfo info : data.info.getPackages()) {
Nikita Ioffe5dcd17972019-02-04 11:08:13 +0000251 final IntArray pendingBackupUsers = info.getPendingBackups();
252 final int idx = pendingBackupUsers.indexOf(userId);
253 if (idx != -1) {
254 try {
255 long ceSnapshotInode = mInstaller.snapshotAppData(info.getPackageName(),
Richard Uhlercca637a2019-02-27 11:50:48 +0000256 userId, data.info.getRollbackId(), Installer.FLAG_STORAGE_CE);
Nikita Ioffe5dcd17972019-02-04 11:08:13 +0000257 info.putCeSnapshotInode(userId, ceSnapshotInode);
258 pendingBackupUsers.remove(idx);
259 } catch (InstallerException ie) {
260 Log.e(TAG,
261 "Unable to create app data snapshot for: "
262 + info.getPackageName() + ", userId: " + userId, ie);
Nikita Ioffe952aa7b2019-01-28 19:49:56 +0000263 }
264 }
Narayan Kamathc034fe92019-01-23 10:48:17 +0000265 }
266 }
267 }
268
Nikita Ioffe5dcd17972019-02-04 11:08:13 +0000269 if (!pendingRestorePackages.isEmpty()) {
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000270 for (RollbackData data : pendingRestores) {
271 for (PackageRollbackInfo info : data.info.getPackages()) {
Nikita Ioffe5dcd17972019-02-04 11:08:13 +0000272 final RestoreInfo ri = info.getRestoreInfo(userId);
273 if (ri != null) {
274 try {
275 mInstaller.restoreAppDataSnapshot(info.getPackageName(), ri.appId,
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000276 ri.seInfo, userId, data.info.getRollbackId(),
Nikita Ioffe5dcd17972019-02-04 11:08:13 +0000277 Installer.FLAG_STORAGE_CE);
278 info.removeRestoreInfo(ri);
279 } catch (InstallerException ie) {
280 Log.e(TAG, "Unable to restore app data snapshot for: "
281 + info.getPackageName(), ie);
282 }
283 }
Narayan Kamathc034fe92019-01-23 10:48:17 +0000284 }
285 }
286 }
Nikita Ioffe5dcd17972019-02-04 11:08:13 +0000287
Richard Uhlere9aaf632019-03-01 16:03:01 +0000288 final Set<RollbackData> changed = new HashSet<>(pendingBackups);
Richard Uhler6f8a33b2019-02-26 10:40:36 +0000289 changed.addAll(pendingRestores);
290 return changed;
Narayan Kamathc034fe92019-01-23 10:48:17 +0000291 }
292
293 /**
294 * @return {@code true} iff. {@code userId} is locked on an FBE device.
295 */
296 @VisibleForTesting
297 public boolean isUserCredentialLocked(int userId) {
298 return StorageManager.isFileEncryptedNativeOrEmulated()
299 && !StorageManager.isUserKeyUnlocked(userId);
300 }
Narayan Kamathc034fe92019-01-23 10:48:17 +0000301}