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