DefaultContainerService.java revision bb7b7bea19223c1eba74f525c7fe87ca3911813b
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
17package com.android.defcontainer;
18
19import static android.net.TrafficStats.MB_IN_BYTES;
20
21import android.app.IntentService;
22import android.content.Context;
23import android.content.Intent;
24import android.content.pm.IPackageManager;
25import android.content.pm.PackageCleanItem;
26import android.content.pm.PackageInfoLite;
27import android.content.pm.PackageManager;
28import android.content.pm.PackageParser;
29import android.content.pm.PackageParser.PackageLite;
30import android.content.pm.PackageParser.PackageParserException;
31import android.content.res.ObbInfo;
32import android.content.res.ObbScanner;
33import android.os.Environment;
34import android.os.Environment.UserEnvironment;
35import android.os.FileUtils;
36import android.os.IBinder;
37import android.os.ParcelFileDescriptor;
38import android.os.Process;
39import android.os.RemoteException;
40import android.os.ServiceManager;
41import android.system.ErrnoException;
42import android.system.Os;
43import android.system.StructStatVfs;
44import android.util.Slog;
45
46import com.android.internal.app.IMediaContainerService;
47import com.android.internal.content.NativeLibraryHelper;
48import com.android.internal.content.PackageHelper;
49import com.android.internal.os.IParcelFileDescriptorFactory;
50import com.android.internal.util.ArrayUtils;
51
52import libcore.io.IoUtils;
53import libcore.io.Streams;
54
55import java.io.File;
56import java.io.FileInputStream;
57import java.io.IOException;
58import java.io.InputStream;
59import 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 */
67public class DefaultContainerService extends IntentService {
68    private static final String TAG = "DefContainer";
69
70    private static final String LIB_DIR_NAME = "lib";
71
72    // TODO: migrate native code unpacking to always be a derivative work
73
74    private IMediaContainerService.Stub mBinder = new IMediaContainerService.Stub() {
75        /**
76         * Creates a new container and copies package there.
77         *
78         * @param packagePath absolute path to the package to be copied. Can be
79         *            a single monolithic APK file or a cluster directory
80         *            containing one or more APKs.
81         * @param containerId the id of the secure container that should be used
82         *            for creating a secure container into which the resource
83         *            will be copied.
84         * @param key Refers to key used for encrypting the secure container
85         * @return Returns the new cache path where the resource has been copied
86         *         into
87         */
88        @Override
89        public String copyPackageToContainer(String packagePath, String containerId, String key,
90                boolean isExternal, boolean isForwardLocked, String abiOverride) {
91            if (packagePath == null || containerId == null) {
92                return null;
93            }
94
95            if (isExternal) {
96                // Make sure the sdcard is mounted.
97                String status = Environment.getExternalStorageState();
98                if (!status.equals(Environment.MEDIA_MOUNTED)) {
99                    Slog.w(TAG, "Make sure sdcard is mounted.");
100                    return null;
101                }
102            }
103
104            PackageLite pkg = null;
105            NativeLibraryHelper.Handle handle = null;
106            try {
107                final File packageFile = new File(packagePath);
108                pkg = PackageParser.parsePackageLite(packageFile, 0);
109                handle = NativeLibraryHelper.Handle.create(pkg);
110                return copyPackageToContainerInner(pkg, handle, containerId, key, isExternal,
111                        isForwardLocked, abiOverride);
112            } catch (PackageParserException | IOException e) {
113                Slog.w(TAG, "Failed to copy package at " + packagePath, e);
114                return null;
115            } finally {
116                IoUtils.closeQuietly(handle);
117            }
118        }
119
120        /**
121         * Copy package to the target location.
122         *
123         * @param packagePath absolute path to the package to be copied. Can be
124         *            a single monolithic APK file or a cluster directory
125         *            containing one or more APKs.
126         * @return returns status code according to those in
127         *         {@link PackageManager}
128         */
129        @Override
130        public int copyPackage(String packagePath, IParcelFileDescriptorFactory target) {
131            if (packagePath == null || target == null) {
132                return PackageManager.INSTALL_FAILED_INVALID_URI;
133            }
134
135            PackageLite pkg = null;
136            try {
137                final File packageFile = new File(packagePath);
138                pkg = PackageParser.parsePackageLite(packageFile, 0);
139                return copyPackageInner(pkg, target);
140            } catch (PackageParserException | IOException | RemoteException e) {
141                Slog.w(TAG, "Failed to copy package at " + packagePath + ": " + e);
142                return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
143            }
144        }
145
146        /**
147         * Parse given package and return minimal details.
148         *
149         * @param packagePath absolute path to the package to be copied. Can be
150         *            a single monolithic APK file or a cluster directory
151         *            containing one or more APKs.
152         */
153        @Override
154        public PackageInfoLite getMinimalPackageInfo(String packagePath, int flags,
155                String abiOverride) {
156            final Context context = DefaultContainerService.this;
157            final boolean isForwardLocked = (flags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
158
159            PackageInfoLite ret = new PackageInfoLite();
160            if (packagePath == null) {
161                Slog.i(TAG, "Invalid package file " + packagePath);
162                ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
163                return ret;
164            }
165
166            final File packageFile = new File(packagePath);
167            final PackageParser.PackageLite pkg;
168            final long sizeBytes;
169            try {
170                pkg = PackageParser.parsePackageLite(packageFile, 0);
171                sizeBytes = calculateInstalledSizeInner(pkg, isForwardLocked, abiOverride);
172            } catch (PackageParserException | IOException e) {
173                Slog.w(TAG, "Failed to parse package at " + packagePath + ": " + e);
174
175                if (!packageFile.exists()) {
176                    ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_URI;
177                } else {
178                    ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
179                }
180
181                return ret;
182            }
183
184            ret.packageName = pkg.packageName;
185            ret.versionCode = pkg.versionCode;
186            ret.installLocation = pkg.installLocation;
187            ret.verifiers = pkg.verifiers;
188            ret.recommendedInstallLocation = PackageHelper.resolveInstallLocation(context,
189                    pkg.packageName, pkg.installLocation, sizeBytes, flags);
190            ret.multiArch = pkg.multiArch;
191
192            return ret;
193        }
194
195        @Override
196        public ObbInfo getObbInfo(String filename) {
197            try {
198                return ObbScanner.getObbInfo(filename);
199            } catch (IOException e) {
200                Slog.d(TAG, "Couldn't get OBB info for " + filename);
201                return null;
202            }
203        }
204
205        @Override
206        public long calculateDirectorySize(String path) throws RemoteException {
207            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
208
209            final File dir = Environment.maybeTranslateEmulatedPathToInternal(new File(path));
210            if (dir.exists() && dir.isDirectory()) {
211                final String targetPath = dir.getAbsolutePath();
212                return MeasurementUtils.measureDirectory(targetPath);
213            } else {
214                return 0L;
215            }
216        }
217
218        @Override
219        public long[] getFileSystemStats(String path) {
220            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
221
222            try {
223                final StructStatVfs stat = Os.statvfs(path);
224                final long totalSize = stat.f_blocks * stat.f_bsize;
225                final long availSize = stat.f_bavail * stat.f_bsize;
226                return new long[] { totalSize, availSize };
227            } catch (ErrnoException e) {
228                throw new IllegalStateException(e);
229            }
230        }
231
232        @Override
233        public void clearDirectory(String path) throws RemoteException {
234            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
235
236            final File directory = new File(path);
237            if (directory.exists() && directory.isDirectory()) {
238                eraseFiles(directory);
239            }
240        }
241
242        /**
243         * Calculate estimated footprint of given package post-installation.
244         *
245         * @param packagePath absolute path to the package to be copied. Can be
246         *            a single monolithic APK file or a cluster directory
247         *            containing one or more APKs.
248         */
249        @Override
250        public long calculateInstalledSize(String packagePath, boolean isForwardLocked,
251                String abiOverride) throws RemoteException {
252            final File packageFile = new File(packagePath);
253            final PackageParser.PackageLite pkg;
254            try {
255                pkg = PackageParser.parsePackageLite(packageFile, 0);
256                return calculateInstalledSizeInner(pkg, isForwardLocked, abiOverride);
257            } catch (PackageParserException | IOException e) {
258                Slog.w(TAG, "Failed to calculate installed size: " + e);
259                return Long.MAX_VALUE;
260            }
261        }
262    };
263
264    public DefaultContainerService() {
265        super("DefaultContainerService");
266        setIntentRedelivery(true);
267    }
268
269    @Override
270    protected void onHandleIntent(Intent intent) {
271        if (PackageManager.ACTION_CLEAN_EXTERNAL_STORAGE.equals(intent.getAction())) {
272            final IPackageManager pm = IPackageManager.Stub.asInterface(
273                    ServiceManager.getService("package"));
274            PackageCleanItem item = null;
275            try {
276                while ((item = pm.nextPackageToClean(item)) != null) {
277                    final UserEnvironment userEnv = new UserEnvironment(item.userId);
278                    eraseFiles(userEnv.buildExternalStorageAppDataDirs(item.packageName));
279                    eraseFiles(userEnv.buildExternalStorageAppMediaDirs(item.packageName));
280                    if (item.andCode) {
281                        eraseFiles(userEnv.buildExternalStorageAppObbDirs(item.packageName));
282                    }
283                }
284            } catch (RemoteException e) {
285            }
286        }
287    }
288
289    void eraseFiles(File[] paths) {
290        for (File path : paths) {
291            eraseFiles(path);
292        }
293    }
294
295    void eraseFiles(File path) {
296        if (path.isDirectory()) {
297            String[] files = path.list();
298            if (files != null) {
299                for (String file : files) {
300                    eraseFiles(new File(path, file));
301                }
302            }
303        }
304        path.delete();
305    }
306
307    @Override
308    public IBinder onBind(Intent intent) {
309        return mBinder;
310    }
311
312    private String copyPackageToContainerInner(PackageLite pkg, NativeLibraryHelper.Handle handle,
313            String newCid, String key, boolean isExternal, boolean isForwardLocked,
314            String abiOverride) throws IOException {
315
316        // Calculate container size, rounding up to nearest MB and adding an
317        // extra MB for filesystem overhead
318        final long sizeBytes = calculateInstalledSizeInner(pkg, handle, isForwardLocked,
319                abiOverride);
320        final int sizeMb = ((int) ((sizeBytes + MB_IN_BYTES) / MB_IN_BYTES)) + 1;
321
322        // Create new container
323        final String newMountPath = PackageHelper.createSdDir(sizeMb, newCid, key, Process.myUid(),
324                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.copyNativeBinariesIfNeededLI(handle, libraryRoot,
343                    abiOverride, pkg.multiArch);
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    private long calculateInstalledSizeInner(PackageLite pkg, boolean isForwardLocked,
420            String abiOverride) throws IOException {
421        NativeLibraryHelper.Handle handle = null;
422        try {
423            handle = NativeLibraryHelper.Handle.create(pkg);
424            return calculateInstalledSizeInner(pkg, handle, isForwardLocked, abiOverride);
425        } finally {
426            IoUtils.closeQuietly(handle);
427        }
428    }
429
430    private long calculateInstalledSizeInner(PackageLite pkg, NativeLibraryHelper.Handle handle,
431            boolean isForwardLocked, String abiOverride) throws IOException {
432        long sizeBytes = 0;
433
434        // Include raw APKs, and possibly unpacked resources
435        for (String codePath : pkg.getAllCodePaths()) {
436            final File codeFile = new File(codePath);
437            sizeBytes += codeFile.length();
438
439            if (isForwardLocked) {
440                sizeBytes += PackageHelper.extractPublicFiles(codeFile, null);
441            }
442        }
443
444        // Include all relevant native code
445        sizeBytes += NativeLibraryHelper.sumNativeBinaries(handle, abiOverride, pkg.multiArch);
446
447        return sizeBytes;
448    }
449}
450