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