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