blob: 63335a08f00354cfd394656f232a4822c0a75de8 [file] [log] [blame]
Songchun Fan4e758692019-11-29 15:43:27 -08001/*
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 android.os.incremental;
18
19/**
20 * Set up files and directories used in an installation session.
21 * Currently only used by Incremental Installation.
22 * For Incremental installation, the expected outcome of this function is:
23 * 0) All the files are in defaultStorage
24 * 1) All APK files are in the same directory, bound to mApkStorage, and bound to the
25 * InstallerSession's stage dir. The files are linked from mApkStorage to defaultStorage.
26 * 2) All lib files are in the sub directories as their names suggest, and in the same parent
27 * directory as the APK files. The files are linked from mApkStorage to defaultStorage.
28 * 3) OBB files are in another directory that is different from APK files and lib files, bound
29 * to mObbStorage. The files are linked from mObbStorage to defaultStorage.
30 *
31 * @throws IllegalStateException the session is not an Incremental installation session.
32 */
33
34import static dalvik.system.VMRuntime.getInstructionSet;
35
36import android.annotation.NonNull;
37import android.annotation.Nullable;
Alex Buynytskyyea14d192019-12-13 15:42:18 -080038import android.content.pm.DataLoaderParams;
Songchun Fan4e758692019-11-29 15:43:27 -080039import android.content.pm.InstallationFile;
40import android.os.IVold;
Martijn Coenen9fd2b642019-12-24 13:04:36 +010041import android.os.Process;
Songchun Fan4e758692019-11-29 15:43:27 -080042import android.os.RemoteException;
43import android.os.ServiceManager;
44import android.util.ArraySet;
45import android.util.Slog;
46
47import java.io.File;
48import java.io.IOException;
49import java.nio.file.Files;
50import java.nio.file.Path;
51import java.nio.file.Paths;
52import java.util.Random;
53
54/**
55 * This class manages storage instances used during a package installation session.
56 * @hide
57 */
58public final class IncrementalFileStorages {
59 private static final String TAG = "IncrementalFileStorages";
60 private @Nullable IncrementalStorage mDefaultStorage;
61 private @Nullable IncrementalStorage mApkStorage;
62 private @Nullable IncrementalStorage mObbStorage;
63 private @Nullable String mDefaultDir;
64 private @Nullable String mObbDir;
65 private @NonNull IncrementalManager mIncrementalManager;
66 private @Nullable ArraySet<String> mLibDirs;
67 private @NonNull String mPackageName;
68 private @NonNull File mStageDir;
69
70 /**
71 * Set up files and directories used in an installation session.
72 * Currently only used by Incremental Installation.
73 * For Incremental installation, the expected outcome of this function is:
74 * 0) All the files are in defaultStorage
75 * 1) All APK files are in the same directory, bound to mApkStorage, and bound to the
76 * InstallerSession's stage dir. The files are linked from mApkStorage to defaultStorage.
77 * 2) All lib files are in the sub directories as their names suggest, and in the same parent
78 * directory as the APK files. The files are linked from mApkStorage to defaultStorage.
79 * 3) OBB files are in another directory that is different from APK files and lib files, bound
80 * to mObbStorage. The files are linked from mObbStorage to defaultStorage.
81 *
82 * @throws IllegalStateException the session is not an Incremental installation session.
83 */
84 public IncrementalFileStorages(@NonNull String packageName,
85 @NonNull File stageDir,
86 @NonNull IncrementalManager incrementalManager,
Alex Buynytskyyea14d192019-12-13 15:42:18 -080087 @NonNull DataLoaderParams dataLoaderParams) {
Songchun Fan4e758692019-11-29 15:43:27 -080088 mPackageName = packageName;
89 mStageDir = stageDir;
90 mIncrementalManager = incrementalManager;
Alex Buynytskyy1ecfcec2019-12-17 12:10:41 -080091 if (dataLoaderParams.getComponentName().getPackageName().equals("local")) {
92 final String incrementalPath = dataLoaderParams.getArguments();
Songchun Fan4e758692019-11-29 15:43:27 -080093 mDefaultStorage = mIncrementalManager.openStorage(incrementalPath);
94 mDefaultDir = incrementalPath;
95 return;
96 }
97 mDefaultDir = getTempDir();
98 if (mDefaultDir == null) {
99 return;
100 }
101 mDefaultStorage = mIncrementalManager.createStorage(mDefaultDir,
Alex Buynytskyyea14d192019-12-13 15:42:18 -0800102 dataLoaderParams,
Songchun Fan4e758692019-11-29 15:43:27 -0800103 IncrementalManager.CREATE_MODE_CREATE
104 | IncrementalManager.CREATE_MODE_TEMPORARY_BIND, false);
105 }
106
107 /**
108 * Adds a file into the installation session. Makes sure it will be placed inside
109 * a proper storage instance, based on its file type.
110 */
111 public void addFile(@NonNull InstallationFile file) throws IOException {
112 if (mDefaultStorage == null) {
113 throw new IOException("Cannot add file because default storage does not exist");
114 }
115 if (file.getFileType() == InstallationFile.FILE_TYPE_APK) {
116 addApkFile(file);
117 } else if (file.getFileType() == InstallationFile.FILE_TYPE_OBB) {
118 addObbFile(file);
119 } else if (file.getFileType() == InstallationFile.FILE_TYPE_LIB) {
120 addLibFile(file);
121 } else {
122 throw new IOException("Unknown file type: " + file.getFileType());
123 }
124 }
125
126 private void addApkFile(@NonNull InstallationFile apk) throws IOException {
127 // Create a storage for APK files and lib files
128 final String stageDirPath = mStageDir.getAbsolutePath();
129 if (mApkStorage == null) {
130 mApkStorage = mIncrementalManager.createStorage(stageDirPath, mDefaultStorage,
131 IncrementalManager.CREATE_MODE_CREATE
132 | IncrementalManager.CREATE_MODE_TEMPORARY_BIND);
133 mApkStorage.bind(stageDirPath);
134 }
135
136 if (!new File(mDefaultDir, apk.getName()).exists()) {
137 mDefaultStorage.makeFile(apk.getName(), apk.getSize(),
138 apk.getMetadata());
139 }
140 // Assuming APK files are already named properly, e.g., "base.apk"
141 mDefaultStorage.makeLink(apk.getName(), mApkStorage, apk.getName());
142 }
143
144 private void addLibFile(@NonNull InstallationFile lib) throws IOException {
145 // TODO(b/136132412): remove this after we have incfs support for lib file mapping
146 if (mApkStorage == null) {
147 throw new IOException("Cannot add lib file without adding an apk file first");
148 }
149 if (mLibDirs == null) {
150 mLibDirs = new ArraySet<>();
151 }
152 String current = "";
153 final Path libDirPath = Paths.get(lib.getName()).getParent();
154 final int numDirComponents = libDirPath.getNameCount();
155 for (int i = 0; i < numDirComponents; i++) {
156 String dirName = libDirPath.getName(i).toString();
157 try {
158 dirName = getInstructionSet(dirName);
159 } catch (IllegalArgumentException ignored) {
160 }
161 current += dirName;
162 if (!mLibDirs.contains(current)) {
163 mDefaultStorage.makeDirectory(current);
164 mApkStorage.makeDirectory(current);
165 mLibDirs.add(current);
166 }
167 current += '/';
168 }
169 String libFilePath = current + Paths.get(lib.getName()).getFileName();
170 mDefaultStorage.makeFile(libFilePath, lib.getSize(), lib.getMetadata());
171 mDefaultStorage.makeLink(libFilePath, mApkStorage, libFilePath);
172 }
173
174 private void addObbFile(@NonNull InstallationFile obb) throws IOException {
175 if (mObbStorage == null) {
176 // Create a storage for OBB files
177 mObbDir = getTempDir();
178 if (mObbDir == null) {
179 throw new IOException("Failed to create obb storage directory.");
180 }
181 mObbStorage = mIncrementalManager.createStorage(
182 mObbDir, mDefaultStorage,
183 IncrementalManager.CREATE_MODE_CREATE
184 | IncrementalManager.CREATE_MODE_TEMPORARY_BIND);
185 }
186 mDefaultStorage.makeFile(obb.getName(), obb.getSize(), obb.getMetadata());
187 mDefaultStorage.makeLink(obb.getName(), mObbStorage, obb.getName());
188 }
189
190 private boolean hasObb() {
191 return (mObbStorage != null && mObbDir != null);
192 }
193
194 /**
195 * Starts loading data for default storage.
196 * TODO(b/136132412): update the implementation with latest API design.
197 */
198 public boolean startLoading() {
199 if (mDefaultStorage == null) {
200 return false;
201 }
202 return mDefaultStorage.startLoading();
203 }
204
205 /**
206 * Sets up obb storage directory and create bindings.
207 */
208 public void finishSetUp() {
209 if (!hasObb()) {
210 return;
211 }
Martijn Coenen9fd2b642019-12-24 13:04:36 +0100212 final String obbDir = "/storage/emulated/0/Android/obb";
213 final String packageObbDir = String.format("%s/%s", obbDir, mPackageName);
Songchun Fan4e758692019-11-29 15:43:27 -0800214 final String packageObbDirRoot =
215 String.format("/mnt/runtime/%s/emulated/0/Android/obb/", mPackageName);
216 final String[] obbDirs = {
217 packageObbDirRoot + "read",
218 packageObbDirRoot + "write",
219 packageObbDirRoot + "full",
220 packageObbDirRoot + "default",
221 String.format("/data/media/0/Android/obb/%s", mPackageName),
Martijn Coenen9fd2b642019-12-24 13:04:36 +0100222 packageObbDir,
Songchun Fan4e758692019-11-29 15:43:27 -0800223 };
224 try {
Martijn Coenen9fd2b642019-12-24 13:04:36 +0100225 Slog.i(TAG, "Creating obb directory '" + packageObbDir + "'");
Songchun Fan4e758692019-11-29 15:43:27 -0800226 final IVold vold = IVold.Stub.asInterface(ServiceManager.getServiceOrThrow("vold"));
Martijn Coenen9fd2b642019-12-24 13:04:36 +0100227 vold.setupAppDir(packageObbDir, obbDir, Process.ROOT_UID);
Songchun Fan4e758692019-11-29 15:43:27 -0800228 for (String d : obbDirs) {
229 mObbStorage.bindPermanent(d);
230 }
231 } catch (ServiceManager.ServiceNotFoundException ex) {
232 Slog.e(TAG, "vold service is not found.");
233 cleanUp();
234 } catch (IOException | RemoteException ex) {
Martijn Coenen9fd2b642019-12-24 13:04:36 +0100235 Slog.e(TAG, "Failed to create obb dir at: " + packageObbDir, ex);
Songchun Fan4e758692019-11-29 15:43:27 -0800236 cleanUp();
237 }
238 }
239
240 /**
241 * Resets the states and unbinds storage instances for an installation session.
242 * TODO(b/136132412): make sure unnecessary binds are removed but useful storages are kept
243 */
244 public void cleanUp() {
245 if (mDefaultStorage != null && mDefaultDir != null) {
246 try {
247 mDefaultStorage.unBind(mDefaultDir);
248 } catch (IOException ignored) {
249 }
250 mDefaultDir = null;
251 mDefaultStorage = null;
252 }
253 if (mApkStorage != null && mStageDir != null) {
254 try {
255 mApkStorage.unBind(mStageDir.getAbsolutePath());
256 } catch (IOException ignored) {
257 }
258 mApkStorage = null;
259 }
260 if (mObbStorage != null && mObbDir != null) {
261 try {
262 mObbStorage.unBind(mObbDir);
263 } catch (IOException ignored) {
264 }
265 mObbDir = null;
266 mObbStorage = null;
267 }
268 }
269
270 private String getTempDir() {
Songchun Fan720a7812019-12-17 19:31:18 -0800271 final String tmpDirRoot = "/data/incremental/tmp";
Songchun Fan4e758692019-11-29 15:43:27 -0800272 final Random random = new Random();
273 final Path tmpDir =
274 Paths.get(tmpDirRoot, String.valueOf(random.nextInt(Integer.MAX_VALUE - 1)));
275 try {
276 Files.createDirectories(tmpDir);
277 } catch (Exception ex) {
278 Slog.e(TAG, "Failed to create dir", ex);
279 return null;
280 }
281 return tmpDir.toAbsolutePath().toString();
282 }
283}