Home | History | Annotate | Download | only in defcontainer
      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 void clearDirectory(String path) throws RemoteException {
    217             Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
    218 
    219             final File directory = new File(path);
    220             if (directory.exists() && directory.isDirectory()) {
    221                 eraseFiles(directory);
    222             }
    223         }
    224 
    225         /**
    226          * Calculate estimated footprint of given package post-installation.
    227          *
    228          * @param packagePath absolute path to the package to be copied. Can be
    229          *            a single monolithic APK file or a cluster directory
    230          *            containing one or more APKs.
    231          */
    232         @Override
    233         public long calculateInstalledSize(String packagePath, boolean isForwardLocked,
    234                 String abiOverride) throws RemoteException {
    235             final File packageFile = new File(packagePath);
    236             final PackageParser.PackageLite pkg;
    237             try {
    238                 pkg = PackageParser.parsePackageLite(packageFile, 0);
    239                 return PackageHelper.calculateInstalledSize(pkg, isForwardLocked, abiOverride);
    240             } catch (PackageParserException | IOException e) {
    241                 Slog.w(TAG, "Failed to calculate installed size: " + e);
    242                 return Long.MAX_VALUE;
    243             }
    244         }
    245     };
    246 
    247     public DefaultContainerService() {
    248         super("DefaultContainerService");
    249         setIntentRedelivery(true);
    250     }
    251 
    252     @Override
    253     protected void onHandleIntent(Intent intent) {
    254         if (PackageManager.ACTION_CLEAN_EXTERNAL_STORAGE.equals(intent.getAction())) {
    255             final IPackageManager pm = IPackageManager.Stub.asInterface(
    256                     ServiceManager.getService("package"));
    257             PackageCleanItem item = null;
    258             try {
    259                 while ((item = pm.nextPackageToClean(item)) != null) {
    260                     final UserEnvironment userEnv = new UserEnvironment(item.userId);
    261                     eraseFiles(userEnv.buildExternalStorageAppDataDirs(item.packageName));
    262                     eraseFiles(userEnv.buildExternalStorageAppMediaDirs(item.packageName));
    263                     if (item.andCode) {
    264                         eraseFiles(userEnv.buildExternalStorageAppObbDirs(item.packageName));
    265                     }
    266                 }
    267             } catch (RemoteException e) {
    268             }
    269         }
    270     }
    271 
    272     void eraseFiles(File[] paths) {
    273         for (File path : paths) {
    274             eraseFiles(path);
    275         }
    276     }
    277 
    278     void eraseFiles(File path) {
    279         if (path.isDirectory()) {
    280             String[] files = path.list();
    281             if (files != null) {
    282                 for (String file : files) {
    283                     eraseFiles(new File(path, file));
    284                 }
    285             }
    286         }
    287         path.delete();
    288     }
    289 
    290     @Override
    291     public IBinder onBind(Intent intent) {
    292         return mBinder;
    293     }
    294 
    295     private String copyPackageToContainerInner(PackageLite pkg, NativeLibraryHelper.Handle handle,
    296             String newCid, String key, boolean isExternal, boolean isForwardLocked,
    297             String abiOverride) throws IOException {
    298 
    299         // Calculate container size, rounding up to nearest MB and adding an
    300         // extra MB for filesystem overhead
    301         final long sizeBytes = PackageHelper.calculateInstalledSize(pkg, handle,
    302                 isForwardLocked, abiOverride);
    303 
    304         // Create new container
    305         final String newMountPath = PackageHelper.createSdDir(sizeBytes, newCid, key,
    306                 Process.myUid(), isExternal);
    307         if (newMountPath == null) {
    308             throw new IOException("Failed to create container " + newCid);
    309         }
    310         final File targetDir = new File(newMountPath);
    311 
    312         try {
    313             // Copy all APKs
    314             copyFile(pkg.baseCodePath, targetDir, "base.apk", isForwardLocked);
    315             if (!ArrayUtils.isEmpty(pkg.splitNames)) {
    316                 for (int i = 0; i < pkg.splitNames.length; i++) {
    317                     copyFile(pkg.splitCodePaths[i], targetDir,
    318                             "split_" + pkg.splitNames[i] + ".apk", isForwardLocked);
    319                 }
    320             }
    321 
    322             // Extract native code
    323             final File libraryRoot = new File(targetDir, LIB_DIR_NAME);
    324             final int res = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libraryRoot,
    325                     abiOverride);
    326             if (res != PackageManager.INSTALL_SUCCEEDED) {
    327                 throw new IOException("Failed to extract native code, res=" + res);
    328             }
    329 
    330             if (!PackageHelper.finalizeSdDir(newCid)) {
    331                 throw new IOException("Failed to finalize " + newCid);
    332             }
    333 
    334             if (PackageHelper.isContainerMounted(newCid)) {
    335                 PackageHelper.unMountSdDir(newCid);
    336             }
    337 
    338         } catch (ErrnoException e) {
    339             PackageHelper.destroySdDir(newCid);
    340             throw e.rethrowAsIOException();
    341         } catch (IOException e) {
    342             PackageHelper.destroySdDir(newCid);
    343             throw e;
    344         }
    345 
    346         return newMountPath;
    347     }
    348 
    349     private int copyPackageInner(PackageLite pkg, IParcelFileDescriptorFactory target)
    350             throws IOException, RemoteException {
    351         copyFile(pkg.baseCodePath, target, "base.apk");
    352         if (!ArrayUtils.isEmpty(pkg.splitNames)) {
    353             for (int i = 0; i < pkg.splitNames.length; i++) {
    354                 copyFile(pkg.splitCodePaths[i], target, "split_" + pkg.splitNames[i] + ".apk");
    355             }
    356         }
    357 
    358         return PackageManager.INSTALL_SUCCEEDED;
    359     }
    360 
    361     private void copyFile(String sourcePath, IParcelFileDescriptorFactory target, String targetName)
    362             throws IOException, RemoteException {
    363         Slog.d(TAG, "Copying " + sourcePath + " to " + targetName);
    364         InputStream in = null;
    365         OutputStream out = null;
    366         try {
    367             in = new FileInputStream(sourcePath);
    368             out = new ParcelFileDescriptor.AutoCloseOutputStream(
    369                     target.open(targetName, ParcelFileDescriptor.MODE_READ_WRITE));
    370             Streams.copy(in, out);
    371         } finally {
    372             IoUtils.closeQuietly(out);
    373             IoUtils.closeQuietly(in);
    374         }
    375     }
    376 
    377     private void copyFile(String sourcePath, File targetDir, String targetName,
    378             boolean isForwardLocked) throws IOException, ErrnoException {
    379         final File sourceFile = new File(sourcePath);
    380         final File targetFile = new File(targetDir, targetName);
    381 
    382         Slog.d(TAG, "Copying " + sourceFile + " to " + targetFile);
    383         if (!FileUtils.copyFile(sourceFile, targetFile)) {
    384             throw new IOException("Failed to copy " + sourceFile + " to " + targetFile);
    385         }
    386 
    387         if (isForwardLocked) {
    388             final String publicTargetName = PackageHelper.replaceEnd(targetName,
    389                     ".apk", ".zip");
    390             final File publicTargetFile = new File(targetDir, publicTargetName);
    391 
    392             PackageHelper.extractPublicFiles(sourceFile, publicTargetFile);
    393 
    394             Os.chmod(targetFile.getAbsolutePath(), 0640);
    395             Os.chmod(publicTargetFile.getAbsolutePath(), 0644);
    396         } else {
    397             Os.chmod(targetFile.getAbsolutePath(), 0644);
    398         }
    399     }
    400 }
    401