PackageInstallerSession.java revision 1cb2d0d4bba387665128c62c342e59103ea4be26
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.system.OsConstants.O_CREAT;
24import static android.system.OsConstants.O_RDONLY;
25import static android.system.OsConstants.O_WRONLY;
26
27import android.content.pm.ApplicationInfo;
28import android.content.pm.IPackageInstallObserver2;
29import android.content.pm.IPackageInstallerSession;
30import android.content.pm.InstallSessionInfo;
31import android.content.pm.InstallSessionParams;
32import android.content.pm.PackageManager;
33import android.content.pm.PackageParser;
34import android.content.pm.PackageParser.ApkLite;
35import android.content.pm.PackageParser.PackageParserException;
36import android.content.pm.Signature;
37import android.os.Bundle;
38import android.os.FileBridge;
39import android.os.FileUtils;
40import android.os.Handler;
41import android.os.Looper;
42import android.os.Message;
43import android.os.ParcelFileDescriptor;
44import android.os.RemoteException;
45import android.os.UserHandle;
46import android.system.ErrnoException;
47import android.system.Os;
48import android.system.OsConstants;
49import android.system.StructStat;
50import android.util.ArraySet;
51import android.util.ExceptionUtils;
52import android.util.MathUtils;
53import android.util.Slog;
54
55import com.android.internal.annotations.GuardedBy;
56import com.android.internal.util.ArrayUtils;
57import com.android.internal.util.IndentingPrintWriter;
58import com.android.internal.util.Preconditions;
59
60import libcore.io.Libcore;
61
62import java.io.File;
63import java.io.FileDescriptor;
64import java.io.IOException;
65import java.util.ArrayList;
66import java.util.concurrent.atomic.AtomicInteger;
67
68public class PackageInstallerSession extends IPackageInstallerSession.Stub {
69    private static final String TAG = "PackageInstaller";
70    private static final boolean LOGD = true;
71
72    private static final int MSG_COMMIT = 0;
73
74    // TODO: enforce INSTALL_ALLOW_TEST
75    // TODO: enforce INSTALL_ALLOW_DOWNGRADE
76    // TODO: handle INSTALL_EXTERNAL, INSTALL_INTERNAL
77
78    // TODO: treat INHERIT_EXISTING as installExistingPackage()
79
80    private final PackageInstallerService.InternalCallback mCallback;
81    private final PackageManagerService mPm;
82    private final Handler mHandler;
83
84    final int sessionId;
85    final int userId;
86    final String installerPackageName;
87    final InstallSessionParams params;
88    final long createdMillis;
89    final File sessionStageDir;
90
91    /** Note that UID is not persisted; it's always derived at runtime. */
92    final int installerUid;
93
94    AtomicInteger openCount = new AtomicInteger();
95
96    private final Object mLock = new Object();
97
98    @GuardedBy("mLock")
99    private float mClientProgress = 0;
100    @GuardedBy("mLock")
101    private float mProgress = 0;
102    @GuardedBy("mLock")
103    private float mReportedProgress = -1;
104
105    @GuardedBy("mLock")
106    private boolean mSealed = false;
107    @GuardedBy("mLock")
108    private boolean mPermissionsConfirmed = false;
109    @GuardedBy("mLock")
110    private boolean mDestroyed = false;
111
112    @GuardedBy("mLock")
113    private ArrayList<FileBridge> mBridges = new ArrayList<>();
114
115    @GuardedBy("mLock")
116    private IPackageInstallObserver2 mRemoteObserver;
117
118    /** Fields derived from commit parsing */
119    private String mPackageName;
120    private int mVersionCode;
121    private Signature[] mSignatures;
122
123    private final Handler.Callback mHandlerCallback = new Handler.Callback() {
124        @Override
125        public boolean handleMessage(Message msg) {
126            synchronized (mLock) {
127                if (msg.obj != null) {
128                    mRemoteObserver = (IPackageInstallObserver2) msg.obj;
129                }
130
131                try {
132                    commitLocked();
133                } catch (PackageManagerException e) {
134                    Slog.e(TAG, "Install failed: " + e);
135                    destroyInternal();
136                    try {
137                        mRemoteObserver.packageInstalled(mPackageName, null, e.error,
138                                e.getMessage());
139                    } catch (RemoteException ignored) {
140                    }
141                    mCallback.onSessionFinished(PackageInstallerSession.this, false);
142                }
143
144                return true;
145            }
146        }
147    };
148
149    public PackageInstallerSession(PackageInstallerService.InternalCallback callback,
150            PackageManagerService pm, Looper looper, int sessionId, int userId,
151            String installerPackageName, InstallSessionParams params, long createdMillis,
152            File sessionStageDir, boolean sealed) {
153        mCallback = callback;
154        mPm = pm;
155        mHandler = new Handler(looper, mHandlerCallback);
156
157        this.sessionId = sessionId;
158        this.userId = userId;
159        this.installerPackageName = installerPackageName;
160        this.params = params;
161        this.createdMillis = createdMillis;
162        this.sessionStageDir = sessionStageDir;
163
164        mSealed = sealed;
165
166        // Always derived at runtime
167        installerUid = mPm.getPackageUid(installerPackageName, userId);
168
169        if (mPm.checkPermission(android.Manifest.permission.INSTALL_PACKAGES,
170                installerPackageName) == PackageManager.PERMISSION_GRANTED) {
171            mPermissionsConfirmed = true;
172        } else {
173            mPermissionsConfirmed = false;
174        }
175
176        computeProgressLocked();
177    }
178
179    public InstallSessionInfo generateInfo() {
180        final InstallSessionInfo info = new InstallSessionInfo();
181
182        info.sessionId = sessionId;
183        info.installerPackageName = installerPackageName;
184        info.progress = mProgress;
185        info.open = openCount.get() > 0;
186
187        info.mode = params.mode;
188        info.sizeBytes = params.sizeBytes;
189        info.appPackageName = params.appPackageName;
190        info.appIcon = params.appIcon;
191        info.appLabel = params.appLabel;
192
193        return info;
194    }
195
196    private void assertNotSealed(String cookie) {
197        synchronized (mLock) {
198            if (mSealed) {
199                throw new SecurityException(cookie + " not allowed after commit");
200            }
201        }
202    }
203
204    @Override
205    public void setClientProgress(float progress) {
206        synchronized (mLock) {
207            mClientProgress = progress;
208            computeProgressLocked();
209        }
210        maybePublishProgress();
211    }
212
213    @Override
214    public void addClientProgress(float progress) {
215        synchronized (mLock) {
216            mClientProgress += progress;
217            computeProgressLocked();
218        }
219        maybePublishProgress();
220    }
221
222    private void computeProgressLocked() {
223        mProgress = MathUtils.constrain(mClientProgress * 0.8f, 0f, 0.8f);
224    }
225
226    private void maybePublishProgress() {
227        // Only publish when meaningful change
228        if (Math.abs(mProgress - mReportedProgress) > 0.01) {
229            mReportedProgress = mProgress;
230            mCallback.onSessionProgressChanged(this, mProgress);
231        }
232    }
233
234    @Override
235    public String[] list() {
236        assertNotSealed("list");
237        return sessionStageDir.list();
238    }
239
240    @Override
241    public ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes) {
242        try {
243            return openWriteInternal(name, offsetBytes, lengthBytes);
244        } catch (IOException e) {
245            throw ExceptionUtils.wrap(e);
246        }
247    }
248
249    private ParcelFileDescriptor openWriteInternal(String name, long offsetBytes, long lengthBytes)
250            throws IOException {
251        // TODO: relay over to DCS when installing to ASEC
252
253        // Quick sanity check of state, and allocate a pipe for ourselves. We
254        // then do heavy disk allocation outside the lock, but this open pipe
255        // will block any attempted install transitions.
256        final FileBridge bridge;
257        synchronized (mLock) {
258            assertNotSealed("openWrite");
259
260            bridge = new FileBridge();
261            mBridges.add(bridge);
262        }
263
264        try {
265            // Use installer provided name for now; we always rename later
266            if (!FileUtils.isValidExtFilename(name)) {
267                throw new IllegalArgumentException("Invalid name: " + name);
268            }
269            final File target = new File(sessionStageDir, name);
270
271            final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(),
272                    O_CREAT | O_WRONLY, 0644);
273            Os.chmod(target.getAbsolutePath(), 0644);
274
275            // If caller specified a total length, allocate it for them. Free up
276            // cache space to grow, if needed.
277            if (lengthBytes > 0) {
278                final StructStat stat = Libcore.os.fstat(targetFd);
279                final long deltaBytes = lengthBytes - stat.st_size;
280                if (deltaBytes > 0) {
281                    mPm.freeStorage(deltaBytes);
282                }
283                Libcore.os.posix_fallocate(targetFd, 0, lengthBytes);
284            }
285
286            if (offsetBytes > 0) {
287                Libcore.os.lseek(targetFd, offsetBytes, OsConstants.SEEK_SET);
288            }
289
290            bridge.setTargetFile(targetFd);
291            bridge.start();
292            return new ParcelFileDescriptor(bridge.getClientSocket());
293
294        } catch (ErrnoException e) {
295            throw e.rethrowAsIOException();
296        }
297    }
298
299    @Override
300    public ParcelFileDescriptor openRead(String name) {
301        try {
302            return openReadInternal(name);
303        } catch (IOException e) {
304            throw ExceptionUtils.wrap(e);
305        }
306    }
307
308    private ParcelFileDescriptor openReadInternal(String name) throws IOException {
309        assertNotSealed("openRead");
310
311        try {
312            if (!FileUtils.isValidExtFilename(name)) {
313                throw new IllegalArgumentException("Invalid name: " + name);
314            }
315            final File target = new File(sessionStageDir, name);
316
317            final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(), O_RDONLY, 0);
318            return new ParcelFileDescriptor(targetFd);
319
320        } catch (ErrnoException e) {
321            throw e.rethrowAsIOException();
322        }
323    }
324
325    @Override
326    public void commit(IPackageInstallObserver2 observer) {
327        Preconditions.checkNotNull(observer);
328        mHandler.obtainMessage(MSG_COMMIT, observer).sendToTarget();
329    }
330
331    private void commitLocked() throws PackageManagerException {
332        if (mDestroyed) {
333            throw new PackageManagerException(INSTALL_FAILED_ALREADY_EXISTS, "Invalid session");
334        }
335
336        // Verify that all writers are hands-off
337        if (!mSealed) {
338            for (FileBridge bridge : mBridges) {
339                if (!bridge.isClosed()) {
340                    throw new PackageManagerException(INSTALL_FAILED_PACKAGE_CHANGED,
341                            "Files still open");
342                }
343            }
344            mSealed = true;
345
346            // TODO: persist disabled mutations before going forward, since
347            // beyond this point we may have hardlinks to the valid install
348        }
349
350        // Verify that stage looks sane with respect to existing application.
351        // This currently only ensures packageName, versionCode, and certificate
352        // consistency.
353        validateInstallLocked();
354
355        Preconditions.checkNotNull(mPackageName);
356        Preconditions.checkNotNull(mSignatures);
357
358        if (!mPermissionsConfirmed) {
359            // TODO: async confirm permissions with user
360            // when they confirm, we'll kick off another install() pass
361            throw new SecurityException("Caller must hold INSTALL permission");
362        }
363
364        // Inherit any packages and native libraries from existing install that
365        // haven't been overridden.
366        if (params.mode == InstallSessionParams.MODE_INHERIT_EXISTING) {
367            spliceExistingFilesIntoStage();
368        }
369
370        // TODO: surface more granular state from dexopt
371        mCallback.onSessionProgressChanged(this, 0.9f);
372
373        // TODO: for ASEC based applications, grow and stream in packages
374
375        // We've reached point of no return; call into PMS to install the stage.
376        // Regardless of success or failure we always destroy session.
377        final IPackageInstallObserver2 remoteObserver = mRemoteObserver;
378        final IPackageInstallObserver2 localObserver = new IPackageInstallObserver2.Stub() {
379            @Override
380            public void packageInstalled(String basePackageName, Bundle extras, int returnCode,
381                    String msg) {
382                destroyInternal();
383                try {
384                    remoteObserver.packageInstalled(basePackageName, extras, returnCode, msg);
385                } catch (RemoteException ignored) {
386                }
387                final boolean success = (returnCode == PackageManager.INSTALL_SUCCEEDED);
388                mCallback.onSessionFinished(PackageInstallerSession.this, success);
389            }
390        };
391
392        mPm.installStage(mPackageName, this.sessionStageDir, localObserver, params,
393                installerPackageName, installerUid, new UserHandle(userId));
394    }
395
396    /**
397     * Validate install by confirming that all application packages are have
398     * consistent package name, version code, and signing certificates.
399     * <p>
400     * Renames package files in stage to match split names defined inside.
401     */
402    private void validateInstallLocked() throws PackageManagerException {
403        mPackageName = null;
404        mVersionCode = -1;
405        mSignatures = null;
406
407        final File[] files = sessionStageDir.listFiles();
408        if (ArrayUtils.isEmpty(files)) {
409            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "No packages staged");
410        }
411
412        final ArraySet<String> seenSplits = new ArraySet<>();
413
414        // Verify that all staged packages are internally consistent
415        for (File file : files) {
416            final ApkLite info;
417            try {
418                info = PackageParser.parseApkLite(file, PackageParser.PARSE_GET_SIGNATURES);
419            } catch (PackageParserException e) {
420                throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
421                        "Failed to parse " + file + ": " + e);
422            }
423
424            if (!seenSplits.add(info.splitName)) {
425                throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
426                        "Split " + info.splitName + " was defined multiple times");
427            }
428
429            // Use first package to define unknown values
430            if (mPackageName == null) {
431                mPackageName = info.packageName;
432                mVersionCode = info.versionCode;
433            }
434            if (mSignatures == null) {
435                mSignatures = info.signatures;
436            }
437
438            assertPackageConsistent(String.valueOf(file), info.packageName, info.versionCode,
439                    info.signatures);
440
441            // Take this opportunity to enforce uniform naming
442            final String name;
443            if (info.splitName == null) {
444                name = "base.apk";
445            } else {
446                name = "split_" + info.splitName + ".apk";
447            }
448            if (!FileUtils.isValidExtFilename(name)) {
449                throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
450                        "Invalid filename: " + name);
451            }
452            if (!file.getName().equals(name)) {
453                file.renameTo(new File(file.getParentFile(), name));
454            }
455        }
456
457        // TODO: shift package signature verification to installer; we're
458        // currently relying on PMS to do this.
459        // TODO: teach about compatible upgrade keysets.
460
461        if (params.mode == InstallSessionParams.MODE_FULL_INSTALL) {
462            // Full installs must include a base package
463            if (!seenSplits.contains(null)) {
464                throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
465                        "Full install must include a base package");
466            }
467
468        } else {
469            // Partial installs must be consistent with existing install.
470            final ApplicationInfo app = mPm.getApplicationInfo(mPackageName, 0, userId);
471            if (app == null) {
472                throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
473                        "Missing existing base package for " + mPackageName);
474            }
475
476            final ApkLite info;
477            try {
478                info = PackageParser.parseApkLite(new File(app.getBaseCodePath()),
479                        PackageParser.PARSE_GET_SIGNATURES);
480            } catch (PackageParserException e) {
481                throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
482                        "Failed to parse existing base " + app.getBaseCodePath() + ": " + e);
483            }
484
485            assertPackageConsistent("Existing base", info.packageName, info.versionCode,
486                    info.signatures);
487        }
488    }
489
490    private void assertPackageConsistent(String tag, String packageName, int versionCode,
491            Signature[] signatures) throws PackageManagerException {
492        if (!mPackageName.equals(packageName)) {
493            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag + " package "
494                    + packageName + " inconsistent with " + mPackageName);
495        }
496        if (mVersionCode != versionCode) {
497            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag
498                    + " version code " + versionCode + " inconsistent with "
499                    + mVersionCode);
500        }
501        if (!Signature.areExactMatch(mSignatures, signatures)) {
502            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
503                    tag + " signatures are inconsistent");
504        }
505    }
506
507    /**
508     * Application is already installed; splice existing files that haven't been
509     * overridden into our stage.
510     */
511    private void spliceExistingFilesIntoStage() throws PackageManagerException {
512        final ApplicationInfo app = mPm.getApplicationInfo(mPackageName, 0, userId);
513
514        int n = 0;
515        final File[] oldFiles = new File(app.getCodePath()).listFiles();
516        if (!ArrayUtils.isEmpty(oldFiles)) {
517            for (File oldFile : oldFiles) {
518                if (!PackageParser.isApkFile(oldFile)) continue;
519
520                final File newFile = new File(sessionStageDir, oldFile.getName());
521                try {
522                    Os.link(oldFile.getAbsolutePath(), newFile.getAbsolutePath());
523                    n++;
524                } catch (ErrnoException e) {
525                    throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
526                            "Failed to splice into stage", e);
527                }
528            }
529        }
530
531        if (LOGD) Slog.d(TAG, "Spliced " + n + " existing APKs into stage");
532    }
533
534    @Override
535    public void close() {
536        if (openCount.decrementAndGet() == 0) {
537            mCallback.onSessionClosed(this);
538        }
539    }
540
541    @Override
542    public void abandon() {
543        try {
544            destroyInternal();
545        } finally {
546            mCallback.onSessionFinished(this, false);
547        }
548    }
549
550    private void destroyInternal() {
551        synchronized (mLock) {
552            mSealed = true;
553            mDestroyed = true;
554        }
555        FileUtils.deleteContents(sessionStageDir);
556        sessionStageDir.delete();
557    }
558
559    void dump(IndentingPrintWriter pw) {
560        pw.println("Session " + sessionId + ":");
561        pw.increaseIndent();
562
563        pw.printPair("userId", userId);
564        pw.printPair("installerPackageName", installerPackageName);
565        pw.printPair("installerUid", installerUid);
566        pw.printPair("createdMillis", createdMillis);
567        pw.printPair("sessionStageDir", sessionStageDir);
568        pw.println();
569
570        params.dump(pw);
571
572        pw.printPair("mClientProgress", mClientProgress);
573        pw.printPair("mProgress", mProgress);
574        pw.printPair("mSealed", mSealed);
575        pw.printPair("mPermissionsConfirmed", mPermissionsConfirmed);
576        pw.printPair("mDestroyed", mDestroyed);
577        pw.printPair("mBridges", mBridges.size());
578        pw.println();
579
580        pw.decreaseIndent();
581    }
582
583    Snapshot snapshot() {
584        return new Snapshot(this);
585    }
586
587    static class Snapshot {
588        final float clientProgress;
589        final boolean sealed;
590
591        public Snapshot(PackageInstallerSession session) {
592            clientProgress = session.mClientProgress;
593            sealed = session.mSealed;
594        }
595    }
596}
597