1 /*
2  * Copyright (C) 2010 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 
17 package com.android.defcontainer;
18 
19 import static com.android.internal.content.NativeLibraryHelper.LIB_DIR_NAME;
20 
21 import android.app.IntentService;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.pm.IPackageManager;
25 import android.content.pm.PackageCleanItem;
26 import android.content.pm.PackageInfoLite;
27 import android.content.pm.PackageManager;
28 import android.content.pm.PackageParser;
29 import android.content.pm.PackageParser.PackageLite;
30 import android.content.pm.PackageParser.PackageParserException;
31 import android.content.res.ObbInfo;
32 import android.content.res.ObbScanner;
33 import android.os.Environment;
34 import android.os.Environment.UserEnvironment;
35 import android.os.FileUtils;
36 import android.os.IBinder;
37 import android.os.ParcelFileDescriptor;
38 import android.os.Process;
39 import android.os.RemoteException;
40 import android.os.ServiceManager;
41 import android.system.ErrnoException;
42 import android.system.Os;
43 import android.system.StructStatVfs;
44 import android.util.Slog;
45 
46 import com.android.internal.app.IMediaContainerService;
47 import com.android.internal.content.NativeLibraryHelper;
48 import com.android.internal.content.PackageHelper;
49 import com.android.internal.os.IParcelFileDescriptorFactory;
50 import com.android.internal.util.ArrayUtils;
51 
52 import libcore.io.IoUtils;
53 import libcore.io.Streams;
54 
55 import java.io.File;
56 import java.io.FileInputStream;
57 import java.io.IOException;
58 import java.io.InputStream;
59 import java.io.OutputStream;
60 
61 /**
62  * Service that offers to inspect and copy files that may reside on removable
63  * storage. This is designed to prevent the system process from holding onto
64  * open files that cause the kernel to kill it when the underlying device is
65  * removed.
66  */
67 public class DefaultContainerService extends IntentService {
68     private static final String TAG = "DefContainer";
69 
70     // TODO: migrate native code unpacking to always be a derivative work
71 
72     private IMediaContainerService.Stub mBinder = new IMediaContainerService.Stub() {
73         /**
74          * Creates a new container and copies package there.
75          *
76          * @param packagePath absolute path to the package to be copied. Can be
77          *            a single monolithic APK file or a cluster directory
78          *            containing one or more APKs.
79          * @param containerId the id of the secure container that should be used
80          *            for creating a secure container into which the resource
81          *            will be copied.
82          * @param key Refers to key used for encrypting the secure container
83          * @return Returns the new cache path where the resource has been copied
84          *         into
85          */
86         @Override
87         public String copyPackageToContainer(String packagePath, String containerId, String key,
88                 boolean isExternal, boolean isForwardLocked, String abiOverride) {
89             if (packagePath == null || containerId == null) {
90                 return null;
91             }
92 
93             if (isExternal) {
94                 // Make sure the sdcard is mounted.
95                 String status = Environment.getExternalStorageState();
96                 if (!status.equals(Environment.MEDIA_MOUNTED)) {
97                     Slog.w(TAG, "Make sure sdcard is mounted.");
98                     return null;
99                 }
100             }
101 
102             PackageLite pkg = null;
103             NativeLibraryHelper.Handle handle = null;
104             try {
105                 final File packageFile = new File(packagePath);
106                 pkg = PackageParser.parsePackageLite(packageFile, 0);
107                 handle = NativeLibraryHelper.Handle.create(pkg);
108                 return copyPackageToContainerInner(pkg, handle, containerId, key, isExternal,
109                         isForwardLocked, abiOverride);
110             } catch (PackageParserException | IOException e) {
111                 Slog.w(TAG, "Failed to copy package at " + packagePath, e);
112                 return null;
113             } finally {
114                 IoUtils.closeQuietly(handle);
115             }
116         }
117 
118         /**
119          * Copy package to the target location.
120          *
121          * @param packagePath absolute path to the package to be copied. Can be
122          *            a single monolithic APK file or a cluster directory
123          *            containing one or more APKs.
124          * @return returns status code according to those in
125          *         {@link PackageManager}
126          */
127         @Override
128         public int copyPackage(String packagePath, IParcelFileDescriptorFactory target) {
129             if (packagePath == null || target == null) {
130                 return PackageManager.INSTALL_FAILED_INVALID_URI;
131             }
132 
133             PackageLite pkg = null;
134             try {
135                 final File packageFile = new File(packagePath);
136                 pkg = PackageParser.parsePackageLite(packageFile, 0);
137                 return copyPackageInner(pkg, target);
138             } catch (PackageParserException | IOException | RemoteException e) {
139                 Slog.w(TAG, "Failed to copy package at " + packagePath + ": " + e);
140                 return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
141             }
142         }
143 
144         /**
145          * Parse given package and return minimal details.
146          *
147          * @param packagePath absolute path to the package to be copied. Can be
148          *            a single monolithic APK file or a cluster directory
149          *            containing one or more APKs.
150          */
151         @Override
152         public PackageInfoLite getMinimalPackageInfo(String packagePath, int flags,
153                 String abiOverride) {
154             final Context context = DefaultContainerService.this;
155             final boolean isForwardLocked = (flags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
156 
157             PackageInfoLite ret = new PackageInfoLite();
158             if (packagePath == null) {
159                 Slog.i(TAG, "Invalid package file " + packagePath);
160                 ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
161                 return ret;
162             }
163 
164             final File packageFile = new File(packagePath);
165             final PackageParser.PackageLite pkg;
166             final long sizeBytes;
167             try {
168                 pkg = PackageParser.parsePackageLite(packageFile, 0);
169                 sizeBytes = PackageHelper.calculateInstalledSize(pkg, isForwardLocked, abiOverride);
170             } catch (PackageParserException | IOException e) {
171                 Slog.w(TAG, "Failed to parse package at " + packagePath + ": " + e);
172 
173                 if (!packageFile.exists()) {
174                     ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_URI;
175                 } else {
176                     ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
177                 }
178 
179                 return ret;
180             }
181 
182             ret.packageName = pkg.packageName;
183             ret.splitNames = pkg.splitNames;
184             ret.versionCode = pkg.versionCode;
185             ret.baseRevisionCode = pkg.baseRevisionCode;
186             ret.splitRevisionCodes = pkg.splitRevisionCodes;
187             ret.installLocation = pkg.installLocation;
188             ret.verifiers = pkg.verifiers;
189             ret.recommendedInstallLocation = PackageHelper.resolveInstallLocation(context,
190                     pkg.packageName, pkg.installLocation, sizeBytes, flags);
191             ret.multiArch = pkg.multiArch;
192 
193             return ret;
194         }
195 
196         @Override
197         public ObbInfo getObbInfo(String filename) {
198             try {
199                 return ObbScanner.getObbInfo(filename);
200             } catch (IOException e) {
201                 Slog.d(TAG, "Couldn't get OBB info for " + filename);
202                 return null;
203             }
204         }
205 
206         @Override
207         public long calculateDirectorySize(String path) throws RemoteException {
208             Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
209 
210             final File dir = Environment.maybeTranslateEmulatedPathToInternal(new File(path));
211             if (dir.exists() && dir.isDirectory()) {
212                 final String targetPath = dir.getAbsolutePath();
213                 return MeasurementUtils.measureDirectory(targetPath);
214             } else {
215                 return 0L;
216             }
217         }
218 
219         @Override
220         public long[] getFileSystemStats(String path) {
221             Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
222 
223             try {
224                 final StructStatVfs stat = Os.statvfs(path);
225                 final long totalSize = stat.f_blocks * stat.f_bsize;
226                 final long availSize = stat.f_bavail * stat.f_bsize;
227                 return new long[] { totalSize, availSize };
228             } catch (ErrnoException e) {
229                 throw new IllegalStateException(e);
230             }
231         }
232 
233         @Override
234         public void clearDirectory(String path) throws RemoteException {
235             Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
236 
237             final File directory = new File(path);
238             if (directory.exists() && directory.isDirectory()) {
239                 eraseFiles(directory);
240             }
241         }
242 
243         /**
244          * Calculate estimated footprint of given package post-installation.
245          *
246          * @param packagePath absolute path to the package to be copied. Can be
247          *            a single monolithic APK file or a cluster directory
248          *            containing one or more APKs.
249          */
250         @Override
251         public long calculateInstalledSize(String packagePath, boolean isForwardLocked,
252                 String abiOverride) throws RemoteException {
253             final File packageFile = new File(packagePath);
254             final PackageParser.PackageLite pkg;
255             try {
256                 pkg = PackageParser.parsePackageLite(packageFile, 0);
257                 return PackageHelper.calculateInstalledSize(pkg, isForwardLocked, abiOverride);
258             } catch (PackageParserException | IOException e) {
259                 Slog.w(TAG, "Failed to calculate installed size: " + e);
260                 return Long.MAX_VALUE;
261             }
262         }
263     };
264 
DefaultContainerService()265     public DefaultContainerService() {
266         super("DefaultContainerService");
267         setIntentRedelivery(true);
268     }
269 
270     @Override
onHandleIntent(Intent intent)271     protected void onHandleIntent(Intent intent) {
272         if (PackageManager.ACTION_CLEAN_EXTERNAL_STORAGE.equals(intent.getAction())) {
273             final IPackageManager pm = IPackageManager.Stub.asInterface(
274                     ServiceManager.getService("package"));
275             PackageCleanItem item = null;
276             try {
277                 while ((item = pm.nextPackageToClean(item)) != null) {
278                     final UserEnvironment userEnv = new UserEnvironment(item.userId);
279                     eraseFiles(userEnv.buildExternalStorageAppDataDirs(item.packageName));
280                     eraseFiles(userEnv.buildExternalStorageAppMediaDirs(item.packageName));
281                     if (item.andCode) {
282                         eraseFiles(userEnv.buildExternalStorageAppObbDirs(item.packageName));
283                     }
284                 }
285             } catch (RemoteException e) {
286             }
287         }
288     }
289 
eraseFiles(File[] paths)290     void eraseFiles(File[] paths) {
291         for (File path : paths) {
292             eraseFiles(path);
293         }
294     }
295 
eraseFiles(File path)296     void eraseFiles(File path) {
297         if (path.isDirectory()) {
298             String[] files = path.list();
299             if (files != null) {
300                 for (String file : files) {
301                     eraseFiles(new File(path, file));
302                 }
303             }
304         }
305         path.delete();
306     }
307 
308     @Override
onBind(Intent intent)309     public IBinder onBind(Intent intent) {
310         return mBinder;
311     }
312 
copyPackageToContainerInner(PackageLite pkg, NativeLibraryHelper.Handle handle, String newCid, String key, boolean isExternal, boolean isForwardLocked, String abiOverride)313     private String copyPackageToContainerInner(PackageLite pkg, NativeLibraryHelper.Handle handle,
314             String newCid, String key, boolean isExternal, boolean isForwardLocked,
315             String abiOverride) throws IOException {
316 
317         // Calculate container size, rounding up to nearest MB and adding an
318         // extra MB for filesystem overhead
319         final long sizeBytes = PackageHelper.calculateInstalledSize(pkg, handle,
320                 isForwardLocked, abiOverride);
321 
322         // Create new container
323         final String newMountPath = PackageHelper.createSdDir(sizeBytes, newCid, key,
324                 Process.myUid(), isExternal);
325         if (newMountPath == null) {
326             throw new IOException("Failed to create container " + newCid);
327         }
328         final File targetDir = new File(newMountPath);
329 
330         try {
331             // Copy all APKs
332             copyFile(pkg.baseCodePath, targetDir, "base.apk", isForwardLocked);
333             if (!ArrayUtils.isEmpty(pkg.splitNames)) {
334                 for (int i = 0; i < pkg.splitNames.length; i++) {
335                     copyFile(pkg.splitCodePaths[i], targetDir,
336                             "split_" + pkg.splitNames[i] + ".apk", isForwardLocked);
337                 }
338             }
339 
340             // Extract native code
341             final File libraryRoot = new File(targetDir, LIB_DIR_NAME);
342             final int res = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libraryRoot,
343                     abiOverride);
344             if (res != PackageManager.INSTALL_SUCCEEDED) {
345                 throw new IOException("Failed to extract native code, res=" + res);
346             }
347 
348             if (!PackageHelper.finalizeSdDir(newCid)) {
349                 throw new IOException("Failed to finalize " + newCid);
350             }
351 
352             if (PackageHelper.isContainerMounted(newCid)) {
353                 PackageHelper.unMountSdDir(newCid);
354             }
355 
356         } catch (ErrnoException e) {
357             PackageHelper.destroySdDir(newCid);
358             throw e.rethrowAsIOException();
359         } catch (IOException e) {
360             PackageHelper.destroySdDir(newCid);
361             throw e;
362         }
363 
364         return newMountPath;
365     }
366 
copyPackageInner(PackageLite pkg, IParcelFileDescriptorFactory target)367     private int copyPackageInner(PackageLite pkg, IParcelFileDescriptorFactory target)
368             throws IOException, RemoteException {
369         copyFile(pkg.baseCodePath, target, "base.apk");
370         if (!ArrayUtils.isEmpty(pkg.splitNames)) {
371             for (int i = 0; i < pkg.splitNames.length; i++) {
372                 copyFile(pkg.splitCodePaths[i], target, "split_" + pkg.splitNames[i] + ".apk");
373             }
374         }
375 
376         return PackageManager.INSTALL_SUCCEEDED;
377     }
378 
copyFile(String sourcePath, IParcelFileDescriptorFactory target, String targetName)379     private void copyFile(String sourcePath, IParcelFileDescriptorFactory target, String targetName)
380             throws IOException, RemoteException {
381         Slog.d(TAG, "Copying " + sourcePath + " to " + targetName);
382         InputStream in = null;
383         OutputStream out = null;
384         try {
385             in = new FileInputStream(sourcePath);
386             out = new ParcelFileDescriptor.AutoCloseOutputStream(
387                     target.open(targetName, ParcelFileDescriptor.MODE_READ_WRITE));
388             Streams.copy(in, out);
389         } finally {
390             IoUtils.closeQuietly(out);
391             IoUtils.closeQuietly(in);
392         }
393     }
394 
copyFile(String sourcePath, File targetDir, String targetName, boolean isForwardLocked)395     private void copyFile(String sourcePath, File targetDir, String targetName,
396             boolean isForwardLocked) throws IOException, ErrnoException {
397         final File sourceFile = new File(sourcePath);
398         final File targetFile = new File(targetDir, targetName);
399 
400         Slog.d(TAG, "Copying " + sourceFile + " to " + targetFile);
401         if (!FileUtils.copyFile(sourceFile, targetFile)) {
402             throw new IOException("Failed to copy " + sourceFile + " to " + targetFile);
403         }
404 
405         if (isForwardLocked) {
406             final String publicTargetName = PackageHelper.replaceEnd(targetName,
407                     ".apk", ".zip");
408             final File publicTargetFile = new File(targetDir, publicTargetName);
409 
410             PackageHelper.extractPublicFiles(sourceFile, publicTargetFile);
411 
412             Os.chmod(targetFile.getAbsolutePath(), 0640);
413             Os.chmod(publicTargetFile.getAbsolutePath(), 0644);
414         } else {
415             Os.chmod(targetFile.getAbsolutePath(), 0644);
416         }
417     }
418 }
419