PackageInstallerSession.java revision 78cc340c2de873d6995c283b777476f7237d690f
1/*
2 * Copyright (C) 2014 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.server.pm;
18
19import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
20import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
21import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
22import static android.content.pm.PackageManager.INSTALL_FAILED_PACKAGE_CHANGED;
23import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
24
25import android.content.pm.ApplicationInfo;
26import android.content.pm.IPackageInstallObserver2;
27import android.content.pm.IPackageInstallerSession;
28import android.content.pm.PackageInstallerParams;
29import android.content.pm.PackageManager;
30import android.content.pm.PackageParser;
31import android.content.pm.PackageParser.PackageLite;
32import android.content.pm.Signature;
33import android.os.Build;
34import android.os.Bundle;
35import android.os.FileBridge;
36import android.os.FileUtils;
37import android.os.Handler;
38import android.os.Looper;
39import android.os.Message;
40import android.os.ParcelFileDescriptor;
41import android.os.RemoteException;
42import android.os.SELinux;
43import android.system.ErrnoException;
44import android.system.OsConstants;
45import android.system.StructStat;
46import android.util.ArraySet;
47import android.util.Slog;
48
49import com.android.internal.content.NativeLibraryHelper;
50import com.android.internal.util.ArrayUtils;
51import com.android.internal.util.Preconditions;
52
53import libcore.io.IoUtils;
54import libcore.io.Libcore;
55import libcore.io.Streams;
56
57import java.io.File;
58import java.io.FileDescriptor;
59import java.io.FileInputStream;
60import java.io.FileOutputStream;
61import java.io.IOException;
62import java.util.ArrayList;
63
64public class PackageInstallerSession extends IPackageInstallerSession.Stub {
65    private static final String TAG = "PackageInstaller";
66
67    private final PackageInstallerService.Callback mCallback;
68    private final PackageManagerService mPm;
69    private final Handler mHandler;
70
71    public final int sessionId;
72    public final int userId;
73    public final String installerPackageName;
74    /** UID not persisted */
75    public final int installerUid;
76    public final PackageInstallerParams params;
77    public final long createdMillis;
78    public final File sessionDir;
79
80    private static final int MSG_INSTALL = 0;
81
82    private Handler.Callback mHandlerCallback = new Handler.Callback() {
83        @Override
84        public boolean handleMessage(Message msg) {
85            synchronized (mLock) {
86                if (msg.obj != null) {
87                    mRemoteObserver = (IPackageInstallObserver2) msg.obj;
88                }
89
90                try {
91                    installLocked();
92                } catch (InstallFailedException e) {
93                    Slog.e(TAG, "Install failed: " + e);
94                    try {
95                        mRemoteObserver.packageInstalled(mPackageName, null, e.error);
96                    } catch (RemoteException ignored) {
97                    }
98                }
99
100                return true;
101            }
102        }
103    };
104
105    private final Object mLock = new Object();
106
107    private int mProgress;
108
109    private String mPackageName;
110    private int mVersionCode;
111    private Signature[] mSignatures;
112
113    private boolean mMutationsAllowed;
114    private boolean mVerifierConfirmed;
115    private boolean mPermissionsConfirmed;
116    private boolean mInvalid;
117
118    private ArrayList<FileBridge> mBridges = new ArrayList<>();
119
120    private IPackageInstallObserver2 mRemoteObserver;
121
122    public PackageInstallerSession(PackageInstallerService.Callback callback,
123            PackageManagerService pm, int sessionId, int userId, String installerPackageName,
124            int installerUid, PackageInstallerParams params, long createdMillis, File sessionDir,
125            Looper looper) {
126        mCallback = callback;
127        mPm = pm;
128        mHandler = new Handler(looper, mHandlerCallback);
129
130        this.sessionId = sessionId;
131        this.userId = userId;
132        this.installerPackageName = installerPackageName;
133        this.installerUid = installerUid;
134        this.params = params;
135        this.createdMillis = createdMillis;
136        this.sessionDir = sessionDir;
137
138        // Check against any explicitly provided signatures
139        mSignatures = params.signatures;
140
141        // TODO: splice in flag when restoring persisted session
142        mMutationsAllowed = true;
143
144        if (pm.checkPermission(android.Manifest.permission.INSTALL_PACKAGES, installerPackageName)
145                == PackageManager.PERMISSION_GRANTED) {
146            mPermissionsConfirmed = true;
147        }
148    }
149
150    @Override
151    public void updateProgress(int progress) {
152        mProgress = progress;
153        mCallback.onProgressChanged(this);
154    }
155
156    @Override
157    public ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes) {
158        // TODO: relay over to DCS when installing to ASEC
159
160        // Quick sanity check of state, and allocate a pipe for ourselves. We
161        // then do heavy disk allocation outside the lock, but this open pipe
162        // will block any attempted install transitions.
163        final FileBridge bridge;
164        synchronized (mLock) {
165            if (!mMutationsAllowed) {
166                throw new IllegalStateException("Mutations not allowed");
167            }
168
169            bridge = new FileBridge();
170            mBridges.add(bridge);
171        }
172
173        try {
174            // Use installer provided name for now; we always rename later
175            if (!FileUtils.isValidExtFilename(name)) {
176                throw new IllegalArgumentException("Invalid name: " + name);
177            }
178            final File target = new File(sessionDir, name);
179
180            final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(),
181                    OsConstants.O_CREAT | OsConstants.O_WRONLY, 00700);
182
183            // If caller specified a total length, allocate it for them. Free up
184            // cache space to grow, if needed.
185            if (lengthBytes > 0) {
186                final StructStat stat = Libcore.os.fstat(targetFd);
187                final long deltaBytes = lengthBytes - stat.st_size;
188                if (deltaBytes > 0) {
189                    mPm.freeStorage(deltaBytes);
190                }
191                Libcore.os.posix_fallocate(targetFd, 0, lengthBytes);
192            }
193
194            if (offsetBytes > 0) {
195                Libcore.os.lseek(targetFd, offsetBytes, OsConstants.SEEK_SET);
196            }
197
198            bridge.setTargetFile(targetFd);
199            bridge.start();
200            return new ParcelFileDescriptor(bridge.getClientSocket());
201
202        } catch (ErrnoException e) {
203            throw new IllegalStateException("Failed to write", e);
204        } catch (IOException e) {
205            throw new IllegalStateException("Failed to write", e);
206        }
207    }
208
209    @Override
210    public void install(IPackageInstallObserver2 observer) {
211        Preconditions.checkNotNull(observer);
212        mHandler.obtainMessage(MSG_INSTALL, observer).sendToTarget();
213    }
214
215    private void installLocked() throws InstallFailedException {
216        if (mInvalid) {
217            throw new InstallFailedException(INSTALL_FAILED_ALREADY_EXISTS, "Invalid session");
218        }
219
220        // Verify that all writers are hands-off
221        if (mMutationsAllowed) {
222            for (FileBridge bridge : mBridges) {
223                if (!bridge.isClosed()) {
224                    throw new InstallFailedException(INSTALL_FAILED_PACKAGE_CHANGED,
225                            "Files still open");
226                }
227            }
228            mMutationsAllowed = false;
229
230            // TODO: persist disabled mutations before going forward, since
231            // beyond this point we may have hardlinks to the valid install
232        }
233
234        // Verify that stage looks sane with respect to existing application.
235        // This currently only ensures packageName, versionCode, and certificate
236        // consistency.
237        validateInstallLocked();
238
239        Preconditions.checkNotNull(mPackageName);
240        Preconditions.checkNotNull(mSignatures);
241
242        if (!mVerifierConfirmed) {
243            // TODO: async communication with verifier
244            // when they confirm, we'll kick off another install() pass
245            mVerifierConfirmed = true;
246        }
247
248        if (!mPermissionsConfirmed) {
249            // TODO: async confirm permissions with user
250            // when they confirm, we'll kick off another install() pass
251            mPermissionsConfirmed = true;
252        }
253
254        // Unpack any native libraries contained in this session
255        unpackNativeLibraries();
256
257        // Inherit any packages and native libraries from existing install that
258        // haven't been overridden.
259        if (!params.fullInstall) {
260            spliceExistingFilesIntoStage();
261        }
262
263        // TODO: for ASEC based applications, grow and stream in packages
264
265        // We've reached point of no return; call into PMS to install the stage.
266        // Regardless of success or failure we always destroy session.
267        final IPackageInstallObserver2 remoteObserver = mRemoteObserver;
268        final IPackageInstallObserver2 localObserver = new IPackageInstallObserver2.Stub() {
269            @Override
270            public void packageInstalled(String basePackageName, Bundle extras, int returnCode)
271                    throws RemoteException {
272                destroy();
273                remoteObserver.packageInstalled(basePackageName, extras, returnCode);
274            }
275        };
276
277        mPm.installStage(mPackageName, this.sessionDir, localObserver, params.installFlags);
278    }
279
280    /**
281     * Validate install by confirming that all application packages are have
282     * consistent package name, version code, and signing certificates.
283     * <p>
284     * Renames package files in stage to match split names defined inside.
285     */
286    private void validateInstallLocked() throws InstallFailedException {
287        mPackageName = null;
288        mVersionCode = -1;
289        mSignatures = null;
290
291        final File[] files = sessionDir.listFiles();
292        if (ArrayUtils.isEmpty(files)) {
293            throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, "No packages staged");
294        }
295
296        final ArraySet<String> seenSplits = new ArraySet<>();
297
298        // Verify that all staged packages are internally consistent
299        for (File file : files) {
300            final PackageLite info = PackageParser.parsePackageLite(file.getAbsolutePath(),
301                    PackageParser.PARSE_GET_SIGNATURES);
302            if (info == null) {
303                throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
304                        "Failed to parse " + file);
305            }
306
307            if (!seenSplits.add(info.splitName)) {
308                throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
309                        "Split " + info.splitName + " was defined multiple times");
310            }
311
312            // Use first package to define unknown values
313            if (mPackageName != null) {
314                mPackageName = info.packageName;
315                mVersionCode = info.versionCode;
316            }
317            if (mSignatures != null) {
318                mSignatures = info.signatures;
319            }
320
321            assertPackageConsistent(String.valueOf(file), info.packageName, info.versionCode,
322                    info.signatures);
323
324            // Take this opportunity to enforce uniform naming
325            final String name;
326            if (info.splitName == null) {
327                name = info.packageName + ".apk";
328            } else {
329                name = info.packageName + "-" + info.splitName + ".apk";
330            }
331            if (!FileUtils.isValidExtFilename(name)) {
332                throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
333                        "Invalid filename: " + name);
334            }
335            if (!file.getName().equals(name)) {
336                file.renameTo(new File(file.getParentFile(), name));
337            }
338        }
339
340        // TODO: shift package signature verification to installer; we're
341        // currently relying on PMS to do this.
342        // TODO: teach about compatible upgrade keysets.
343
344        if (params.fullInstall) {
345            // Full installs must include a base package
346            if (!seenSplits.contains(null)) {
347                throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
348                        "Full install must include a base package");
349            }
350
351        } else {
352            // Partial installs must be consistent with existing install.
353            final ApplicationInfo app = mPm.getApplicationInfo(mPackageName, 0, userId);
354            if (app == null) {
355                throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
356                        "Missing existing base package for " + mPackageName);
357            }
358
359            final PackageLite info = PackageParser.parsePackageLite(app.sourceDir,
360                    PackageParser.PARSE_GET_SIGNATURES);
361            if (info == null) {
362                throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
363                        "Failed to parse existing base " + app.sourceDir);
364            }
365
366            assertPackageConsistent("Existing base", info.packageName, info.versionCode,
367                    info.signatures);
368        }
369    }
370
371    private void assertPackageConsistent(String tag, String packageName, int versionCode,
372            Signature[] signatures) throws InstallFailedException {
373        if (!mPackageName.equals(packageName)) {
374            throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, tag + " package "
375                    + packageName + " inconsistent with " + mPackageName);
376        }
377        if (mVersionCode != versionCode) {
378            throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, tag
379                    + " version code " + versionCode + " inconsistent with "
380                    + mVersionCode);
381        }
382        if (!Signature.areExactMatch(mSignatures, signatures)) {
383            throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
384                    tag + " signatures are inconsistent");
385        }
386    }
387
388    /**
389     * Application is already installed; splice existing files that haven't been
390     * overridden into our stage.
391     */
392    private void spliceExistingFilesIntoStage() throws InstallFailedException {
393        final ApplicationInfo app = mPm.getApplicationInfo(mPackageName, 0, userId);
394        final File existingDir = new File(app.sourceDir).getParentFile();
395
396        try {
397            linkTreeIgnoringExisting(existingDir, sessionDir);
398        } catch (ErrnoException e) {
399            throw new InstallFailedException(INSTALL_FAILED_INTERNAL_ERROR,
400                    "Failed to splice into stage");
401        }
402    }
403
404    /**
405     * Recursively hard link all files from source directory tree to target.
406     * When a file already exists in the target tree, it leaves that file
407     * intact.
408     */
409    private void linkTreeIgnoringExisting(File sourceDir, File targetDir) throws ErrnoException {
410        final File[] sourceContents = sourceDir.listFiles();
411        if (ArrayUtils.isEmpty(sourceContents)) return;
412
413        for (File sourceFile : sourceContents) {
414            final File targetFile = new File(targetDir, sourceFile.getName());
415
416            if (sourceFile.isDirectory()) {
417                targetFile.mkdir();
418                linkTreeIgnoringExisting(sourceFile, targetFile);
419            } else {
420                Libcore.os.link(sourceFile.getAbsolutePath(), targetFile.getAbsolutePath());
421            }
422        }
423    }
424
425    private void unpackNativeLibraries() throws InstallFailedException {
426        final File libDir = new File(sessionDir, "lib");
427
428        if (!libDir.mkdir()) {
429            throw new InstallFailedException(INSTALL_FAILED_INTERNAL_ERROR,
430                    "Failed to create " + libDir);
431        }
432
433        try {
434            Libcore.os.chmod(libDir.getAbsolutePath(), 0755);
435        } catch (ErrnoException e) {
436            throw new InstallFailedException(INSTALL_FAILED_INTERNAL_ERROR,
437                    "Failed to prepare " + libDir + ": " + e);
438        }
439
440        if (!SELinux.restorecon(libDir)) {
441            throw new InstallFailedException(INSTALL_FAILED_INTERNAL_ERROR,
442                    "Failed to set context on " + libDir);
443        }
444
445        // Unpack all native libraries under stage
446        final File[] files = sessionDir.listFiles();
447        if (ArrayUtils.isEmpty(files)) {
448            throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, "No packages staged");
449        }
450
451        for (File file : files) {
452            final NativeLibraryHelper.ApkHandle handle = new NativeLibraryHelper.ApkHandle(file);
453            try {
454                final int abiIndex = NativeLibraryHelper.findSupportedAbi(handle,
455                        Build.SUPPORTED_ABIS);
456                if (abiIndex >= 0) {
457                    int copyRet = NativeLibraryHelper.copyNativeBinariesIfNeededLI(handle, libDir,
458                            Build.SUPPORTED_ABIS[abiIndex]);
459                    if (copyRet != INSTALL_SUCCEEDED) {
460                        throw new InstallFailedException(copyRet,
461                                "Failed to copy native libraries for " + file);
462                    }
463                } else if (abiIndex != PackageManager.NO_NATIVE_LIBRARIES) {
464                    throw new InstallFailedException(abiIndex,
465                            "Failed to copy native libraries for " + file);
466                }
467            } finally {
468                handle.close();
469            }
470        }
471    }
472
473    @Override
474    public void destroy() {
475        try {
476            synchronized (mLock) {
477                mInvalid = true;
478            }
479            FileUtils.deleteContents(sessionDir);
480            sessionDir.delete();
481        } finally {
482            mCallback.onSessionInvalid(this);
483        }
484    }
485
486    private class InstallFailedException extends Exception {
487        private final int error;
488
489        public InstallFailedException(int error, String detailMessage) {
490            super(detailMessage);
491            this.error = error;
492        }
493    }
494}
495