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.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 
    265     public DefaultContainerService() {
    266         super("DefaultContainerService");
    267         setIntentRedelivery(true);
    268     }
    269 
    270     @Override
    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 
    290     void eraseFiles(File[] paths) {
    291         for (File path : paths) {
    292             eraseFiles(path);
    293         }
    294     }
    295 
    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
    309     public IBinder onBind(Intent intent) {
    310         return mBinder;
    311     }
    312 
    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 
    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 
    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 
    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