PackageInstallerSession.java revision fdeeeea6cfdebdb98dd70a7dd48965743af01750
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_CONTAINER_ERROR;
21import static android.content.pm.PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
22import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
23import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
24import static android.system.OsConstants.O_CREAT;
25import static android.system.OsConstants.O_RDONLY;
26import static android.system.OsConstants.O_WRONLY;
27import static com.android.server.pm.PackageInstallerService.prepareExternalStageCid;
28import static com.android.server.pm.PackageInstallerService.prepareStageDir;
29
30import android.app.admin.DevicePolicyManager;
31import android.content.Context;
32import android.content.Intent;
33import android.content.IntentSender;
34import android.content.pm.ApplicationInfo;
35import android.content.pm.IPackageInstallObserver2;
36import android.content.pm.IPackageInstallerSession;
37import android.content.pm.PackageInstaller;
38import android.content.pm.PackageInstaller.SessionInfo;
39import android.content.pm.PackageInstaller.SessionParams;
40import android.content.pm.PackageManager;
41import android.content.pm.PackageParser;
42import android.content.pm.PackageParser.ApkLite;
43import android.content.pm.PackageParser.PackageLite;
44import android.content.pm.PackageParser.PackageParserException;
45import android.content.pm.Signature;
46import android.os.Bundle;
47import android.os.FileBridge;
48import android.os.FileUtils;
49import android.os.Handler;
50import android.os.Looper;
51import android.os.Message;
52import android.os.ParcelFileDescriptor;
53import android.os.Process;
54import android.os.RemoteException;
55import android.os.SELinux;
56import android.os.UserHandle;
57import android.system.ErrnoException;
58import android.system.Os;
59import android.system.OsConstants;
60import android.system.StructStat;
61import android.util.ArraySet;
62import android.util.ExceptionUtils;
63import android.util.MathUtils;
64import android.util.Slog;
65
66import libcore.io.IoUtils;
67import libcore.io.Libcore;
68
69import com.android.internal.annotations.GuardedBy;
70import com.android.internal.content.NativeLibraryHelper;
71import com.android.internal.content.PackageHelper;
72import com.android.internal.os.InstallerConnection.InstallerException;
73import com.android.internal.util.ArrayUtils;
74import com.android.internal.util.IndentingPrintWriter;
75import com.android.internal.util.Preconditions;
76import com.android.server.pm.PackageInstallerService.PackageInstallObserverAdapter;
77
78import java.io.File;
79import java.io.FileDescriptor;
80import java.io.IOException;
81import java.util.ArrayList;
82import java.util.Arrays;
83import java.util.List;
84import java.util.concurrent.atomic.AtomicInteger;
85
86public class PackageInstallerSession extends IPackageInstallerSession.Stub {
87    private static final String TAG = "PackageInstaller";
88    private static final boolean LOGD = true;
89
90    private static final int MSG_COMMIT = 0;
91
92    // TODO: enforce INSTALL_ALLOW_TEST
93    // TODO: enforce INSTALL_ALLOW_DOWNGRADE
94
95    private final PackageInstallerService.InternalCallback mCallback;
96    private final Context mContext;
97    private final PackageManagerService mPm;
98    private final Handler mHandler;
99    private final boolean mIsInstallerDeviceOwner;
100
101    final int sessionId;
102    final int userId;
103    final String installerPackageName;
104    final int installerUid;
105    final SessionParams params;
106    final long createdMillis;
107
108    /** Staging location where client data is written. */
109    final File stageDir;
110    final String stageCid;
111
112    private final AtomicInteger mActiveCount = new AtomicInteger();
113
114    private final Object mLock = new Object();
115
116    @GuardedBy("mLock")
117    private float mClientProgress = 0;
118    @GuardedBy("mLock")
119    private float mInternalProgress = 0;
120
121    @GuardedBy("mLock")
122    private float mProgress = 0;
123    @GuardedBy("mLock")
124    private float mReportedProgress = -1;
125
126    @GuardedBy("mLock")
127    private boolean mPrepared = false;
128    @GuardedBy("mLock")
129    private boolean mSealed = false;
130    @GuardedBy("mLock")
131    private boolean mPermissionsAccepted = false;
132    @GuardedBy("mLock")
133    private boolean mRelinquished = false;
134    @GuardedBy("mLock")
135    private boolean mDestroyed = false;
136
137    private int mFinalStatus;
138    private String mFinalMessage;
139
140    @GuardedBy("mLock")
141    private ArrayList<FileBridge> mBridges = new ArrayList<>();
142
143    @GuardedBy("mLock")
144    private IPackageInstallObserver2 mRemoteObserver;
145
146    /** Fields derived from commit parsing */
147    private String mPackageName;
148    private int mVersionCode;
149    private Signature[] mSignatures;
150
151    /**
152     * Path to the validated base APK for this session, which may point at an
153     * APK inside the session (when the session defines the base), or it may
154     * point at the existing base APK (when adding splits to an existing app).
155     * <p>
156     * This is used when confirming permissions, since we can't fully stage the
157     * session inside an ASEC before confirming with user.
158     */
159    @GuardedBy("mLock")
160    private File mResolvedBaseFile;
161
162    @GuardedBy("mLock")
163    private File mResolvedStageDir;
164
165    @GuardedBy("mLock")
166    private final List<File> mResolvedStagedFiles = new ArrayList<>();
167    @GuardedBy("mLock")
168    private final List<File> mResolvedInheritedFiles = new ArrayList<>();
169    @GuardedBy("mLock")
170    private final List<String> mResolvedInstructionSets = new ArrayList<>();
171    @GuardedBy("mLock")
172    private File mInheritedFilesBase;
173
174    private final Handler.Callback mHandlerCallback = new Handler.Callback() {
175        @Override
176        public boolean handleMessage(Message msg) {
177            synchronized (mLock) {
178                if (msg.obj != null) {
179                    mRemoteObserver = (IPackageInstallObserver2) msg.obj;
180                }
181
182                try {
183                    commitLocked();
184                } catch (PackageManagerException e) {
185                    final String completeMsg = ExceptionUtils.getCompleteMessage(e);
186                    Slog.e(TAG, "Commit of session " + sessionId + " failed: " + completeMsg);
187                    destroyInternal();
188                    dispatchSessionFinished(e.error, completeMsg, null);
189                }
190
191                return true;
192            }
193        }
194    };
195
196    public PackageInstallerSession(PackageInstallerService.InternalCallback callback,
197            Context context, PackageManagerService pm, Looper looper, int sessionId, int userId,
198            String installerPackageName, int installerUid, SessionParams params, long createdMillis,
199            File stageDir, String stageCid, boolean prepared, boolean sealed) {
200        mCallback = callback;
201        mContext = context;
202        mPm = pm;
203        mHandler = new Handler(looper, mHandlerCallback);
204
205        this.sessionId = sessionId;
206        this.userId = userId;
207        this.installerPackageName = installerPackageName;
208        this.installerUid = installerUid;
209        this.params = params;
210        this.createdMillis = createdMillis;
211        this.stageDir = stageDir;
212        this.stageCid = stageCid;
213
214        if ((stageDir == null) == (stageCid == null)) {
215            throw new IllegalArgumentException(
216                    "Exactly one of stageDir or stageCid stage must be set");
217        }
218
219        mPrepared = prepared;
220        mSealed = sealed;
221
222        // Device owners are allowed to silently install packages, so the permission check is
223        // waived if the installer is the device owner.
224        DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService(
225                Context.DEVICE_POLICY_SERVICE);
226        final boolean isPermissionGranted =
227                (mPm.checkUidPermission(android.Manifest.permission.INSTALL_PACKAGES, installerUid)
228                        == PackageManager.PERMISSION_GRANTED);
229        final boolean isInstallerRoot = (installerUid == Process.ROOT_UID);
230        final boolean forcePermissionPrompt =
231                (params.installFlags & PackageManager.INSTALL_FORCE_PERMISSION_PROMPT) != 0;
232        mIsInstallerDeviceOwner = (dpm != null) && dpm.isDeviceOwnerAppOnCallingUser(
233                installerPackageName);
234        if ((isPermissionGranted
235                        || isInstallerRoot
236                        || mIsInstallerDeviceOwner)
237                && !forcePermissionPrompt) {
238            mPermissionsAccepted = true;
239        } else {
240            mPermissionsAccepted = false;
241        }
242    }
243
244    public SessionInfo generateInfo() {
245        final SessionInfo info = new SessionInfo();
246        synchronized (mLock) {
247            info.sessionId = sessionId;
248            info.installerPackageName = installerPackageName;
249            info.resolvedBaseCodePath = (mResolvedBaseFile != null) ?
250                    mResolvedBaseFile.getAbsolutePath() : null;
251            info.progress = mProgress;
252            info.sealed = mSealed;
253            info.active = mActiveCount.get() > 0;
254
255            info.mode = params.mode;
256            info.sizeBytes = params.sizeBytes;
257            info.appPackageName = params.appPackageName;
258            info.appIcon = params.appIcon;
259            info.appLabel = params.appLabel;
260        }
261        return info;
262    }
263
264    public boolean isPrepared() {
265        synchronized (mLock) {
266            return mPrepared;
267        }
268    }
269
270    public boolean isSealed() {
271        synchronized (mLock) {
272            return mSealed;
273        }
274    }
275
276    private void assertPreparedAndNotSealed(String cookie) {
277        synchronized (mLock) {
278            if (!mPrepared) {
279                throw new IllegalStateException(cookie + " before prepared");
280            }
281            if (mSealed) {
282                throw new SecurityException(cookie + " not allowed after commit");
283            }
284        }
285    }
286
287    /**
288     * Resolve the actual location where staged data should be written. This
289     * might point at an ASEC mount point, which is why we delay path resolution
290     * until someone actively works with the session.
291     */
292    private File resolveStageDir() throws IOException {
293        synchronized (mLock) {
294            if (mResolvedStageDir == null) {
295                if (stageDir != null) {
296                    mResolvedStageDir = stageDir;
297                } else {
298                    final String path = PackageHelper.getSdDir(stageCid);
299                    if (path != null) {
300                        mResolvedStageDir = new File(path);
301                    } else {
302                        throw new IOException("Failed to resolve path to container " + stageCid);
303                    }
304                }
305            }
306            return mResolvedStageDir;
307        }
308    }
309
310    @Override
311    public void setClientProgress(float progress) {
312        synchronized (mLock) {
313            // Always publish first staging movement
314            final boolean forcePublish = (mClientProgress == 0);
315            mClientProgress = progress;
316            computeProgressLocked(forcePublish);
317        }
318    }
319
320    @Override
321    public void addClientProgress(float progress) {
322        synchronized (mLock) {
323            setClientProgress(mClientProgress + progress);
324        }
325    }
326
327    private void computeProgressLocked(boolean forcePublish) {
328        mProgress = MathUtils.constrain(mClientProgress * 0.8f, 0f, 0.8f)
329                + MathUtils.constrain(mInternalProgress * 0.2f, 0f, 0.2f);
330
331        // Only publish when meaningful change
332        if (forcePublish || Math.abs(mProgress - mReportedProgress) >= 0.01) {
333            mReportedProgress = mProgress;
334            mCallback.onSessionProgressChanged(this, mProgress);
335        }
336    }
337
338    @Override
339    public String[] getNames() {
340        assertPreparedAndNotSealed("getNames");
341        try {
342            return resolveStageDir().list();
343        } catch (IOException e) {
344            throw ExceptionUtils.wrap(e);
345        }
346    }
347
348    @Override
349    public ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes) {
350        try {
351            return openWriteInternal(name, offsetBytes, lengthBytes);
352        } catch (IOException e) {
353            throw ExceptionUtils.wrap(e);
354        }
355    }
356
357    private ParcelFileDescriptor openWriteInternal(String name, long offsetBytes, long lengthBytes)
358            throws IOException {
359        // Quick sanity check of state, and allocate a pipe for ourselves. We
360        // then do heavy disk allocation outside the lock, but this open pipe
361        // will block any attempted install transitions.
362        final FileBridge bridge;
363        synchronized (mLock) {
364            assertPreparedAndNotSealed("openWrite");
365
366            bridge = new FileBridge();
367            mBridges.add(bridge);
368        }
369
370        try {
371            // Use installer provided name for now; we always rename later
372            if (!FileUtils.isValidExtFilename(name)) {
373                throw new IllegalArgumentException("Invalid name: " + name);
374            }
375            final File target = new File(resolveStageDir(), name);
376
377            // TODO: this should delegate to DCS so the system process avoids
378            // holding open FDs into containers.
379            final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(),
380                    O_CREAT | O_WRONLY, 0644);
381            Os.chmod(target.getAbsolutePath(), 0644);
382
383            // If caller specified a total length, allocate it for them. Free up
384            // cache space to grow, if needed.
385            if (lengthBytes > 0) {
386                final StructStat stat = Libcore.os.fstat(targetFd);
387                final long deltaBytes = lengthBytes - stat.st_size;
388                // Only need to free up space when writing to internal stage
389                if (stageDir != null && deltaBytes > 0) {
390                    mPm.freeStorage(params.volumeUuid, deltaBytes);
391                }
392                Libcore.os.posix_fallocate(targetFd, 0, lengthBytes);
393            }
394
395            if (offsetBytes > 0) {
396                Libcore.os.lseek(targetFd, offsetBytes, OsConstants.SEEK_SET);
397            }
398
399            bridge.setTargetFile(targetFd);
400            bridge.start();
401            return new ParcelFileDescriptor(bridge.getClientSocket());
402
403        } catch (ErrnoException e) {
404            throw e.rethrowAsIOException();
405        }
406    }
407
408    @Override
409    public ParcelFileDescriptor openRead(String name) {
410        try {
411            return openReadInternal(name);
412        } catch (IOException e) {
413            throw ExceptionUtils.wrap(e);
414        }
415    }
416
417    private ParcelFileDescriptor openReadInternal(String name) throws IOException {
418        assertPreparedAndNotSealed("openRead");
419
420        try {
421            if (!FileUtils.isValidExtFilename(name)) {
422                throw new IllegalArgumentException("Invalid name: " + name);
423            }
424            final File target = new File(resolveStageDir(), name);
425
426            final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(), O_RDONLY, 0);
427            return new ParcelFileDescriptor(targetFd);
428
429        } catch (ErrnoException e) {
430            throw e.rethrowAsIOException();
431        }
432    }
433
434    @Override
435    public void commit(IntentSender statusReceiver) {
436        Preconditions.checkNotNull(statusReceiver);
437
438        final boolean wasSealed;
439        synchronized (mLock) {
440            wasSealed = mSealed;
441            if (!mSealed) {
442                // Verify that all writers are hands-off
443                for (FileBridge bridge : mBridges) {
444                    if (!bridge.isClosed()) {
445                        throw new SecurityException("Files still open");
446                    }
447                }
448                mSealed = true;
449            }
450
451            // Client staging is fully done at this point
452            mClientProgress = 1f;
453            computeProgressLocked(true);
454        }
455
456        if (!wasSealed) {
457            // Persist the fact that we've sealed ourselves to prevent
458            // mutations of any hard links we create. We do this without holding
459            // the session lock, since otherwise it's a lock inversion.
460            mCallback.onSessionSealedBlocking(this);
461        }
462
463        // This ongoing commit should keep session active, even though client
464        // will probably close their end.
465        mActiveCount.incrementAndGet();
466
467        final PackageInstallObserverAdapter adapter = new PackageInstallObserverAdapter(mContext,
468                statusReceiver, sessionId, mIsInstallerDeviceOwner, userId);
469        mHandler.obtainMessage(MSG_COMMIT, adapter.getBinder()).sendToTarget();
470    }
471
472    private void commitLocked() throws PackageManagerException {
473        if (mDestroyed) {
474            throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session destroyed");
475        }
476        if (!mSealed) {
477            throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session not sealed");
478        }
479
480        try {
481            resolveStageDir();
482        } catch (IOException e) {
483            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
484                    "Failed to resolve stage location", e);
485        }
486
487        // Verify that stage looks sane with respect to existing application.
488        // This currently only ensures packageName, versionCode, and certificate
489        // consistency.
490        validateInstallLocked();
491
492        Preconditions.checkNotNull(mPackageName);
493        Preconditions.checkNotNull(mSignatures);
494        Preconditions.checkNotNull(mResolvedBaseFile);
495
496        if (!mPermissionsAccepted) {
497            // User needs to accept permissions; give installer an intent they
498            // can use to involve user.
499            final Intent intent = new Intent(PackageInstaller.ACTION_CONFIRM_PERMISSIONS);
500            intent.setPackage(mContext.getPackageManager().getPermissionControllerPackageName());
501            intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
502            try {
503                mRemoteObserver.onUserActionRequired(intent);
504            } catch (RemoteException ignored) {
505            }
506
507            // Commit was keeping session marked as active until now; release
508            // that extra refcount so session appears idle.
509            close();
510            return;
511        }
512
513        if (stageCid != null) {
514            // Figure out the final installed size and resize the container once
515            // and for all. Internally the parser handles straddling between two
516            // locations when inheriting.
517            final long finalSize = calculateInstalledSize();
518            resizeContainer(stageCid, finalSize);
519        }
520
521        // Inherit any packages and native libraries from existing install that
522        // haven't been overridden.
523        if (params.mode == SessionParams.MODE_INHERIT_EXISTING) {
524            try {
525                final List<File> fromFiles = mResolvedInheritedFiles;
526                final File toDir = resolveStageDir();
527
528                if (LOGD) Slog.d(TAG, "Inherited files: " + mResolvedInheritedFiles);
529                if (!mResolvedInheritedFiles.isEmpty() && mInheritedFilesBase == null) {
530                    throw new IllegalStateException("mInheritedFilesBase == null");
531                }
532
533                if (isLinkPossible(fromFiles, toDir)) {
534                    if (!mResolvedInstructionSets.isEmpty()) {
535                        final File oatDir = new File(toDir, "oat");
536                        createOatDirs(mResolvedInstructionSets, oatDir);
537                    }
538                    linkFiles(fromFiles, toDir, mInheritedFilesBase);
539                } else {
540                    // TODO: this should delegate to DCS so the system process
541                    // avoids holding open FDs into containers.
542                    copyFiles(fromFiles, toDir);
543                }
544            } catch (IOException e) {
545                throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
546                        "Failed to inherit existing install", e);
547            }
548        }
549
550        // TODO: surface more granular state from dexopt
551        mInternalProgress = 0.5f;
552        computeProgressLocked(true);
553
554        // Unpack native libraries
555        extractNativeLibraries(mResolvedStageDir, params.abiOverride);
556
557        // Container is ready to go, let's seal it up!
558        if (stageCid != null) {
559            finalizeAndFixContainer(stageCid);
560        }
561
562        // We've reached point of no return; call into PMS to install the stage.
563        // Regardless of success or failure we always destroy session.
564        final IPackageInstallObserver2 localObserver = new IPackageInstallObserver2.Stub() {
565            @Override
566            public void onUserActionRequired(Intent intent) {
567                throw new IllegalStateException();
568            }
569
570            @Override
571            public void onPackageInstalled(String basePackageName, int returnCode, String msg,
572                    Bundle extras) {
573                destroyInternal();
574                dispatchSessionFinished(returnCode, msg, extras);
575            }
576        };
577
578        final UserHandle user;
579        if ((params.installFlags & PackageManager.INSTALL_ALL_USERS) != 0) {
580            user = UserHandle.ALL;
581        } else {
582            user = new UserHandle(userId);
583        }
584
585        mRelinquished = true;
586        mPm.installStage(mPackageName, stageDir, stageCid, localObserver, params,
587                installerPackageName, installerUid, user);
588    }
589
590    /**
591     * Validate install by confirming that all application packages are have
592     * consistent package name, version code, and signing certificates.
593     * <p>
594     * Clears and populates {@link #mResolvedBaseFile},
595     * {@link #mResolvedStagedFiles}, and {@link #mResolvedInheritedFiles}.
596     * <p>
597     * Renames package files in stage to match split names defined inside.
598     * <p>
599     * Note that upgrade compatibility is still performed by
600     * {@link PackageManagerService}.
601     */
602    private void validateInstallLocked() throws PackageManagerException {
603        mPackageName = null;
604        mVersionCode = -1;
605        mSignatures = null;
606
607        mResolvedBaseFile = null;
608        mResolvedStagedFiles.clear();
609        mResolvedInheritedFiles.clear();
610
611        final File[] files = mResolvedStageDir.listFiles();
612        if (ArrayUtils.isEmpty(files)) {
613            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "No packages staged");
614        }
615
616        // Verify that all staged packages are internally consistent
617        final ArraySet<String> stagedSplits = new ArraySet<>();
618        for (File file : files) {
619
620            // Installers can't stage directories, so it's fine to ignore
621            // entries like "lost+found".
622            if (file.isDirectory()) continue;
623
624            final ApkLite apk;
625            try {
626                apk = PackageParser.parseApkLite(file, PackageParser.PARSE_COLLECT_CERTIFICATES);
627            } catch (PackageParserException e) {
628                throw PackageManagerException.from(e);
629            }
630
631            if (!stagedSplits.add(apk.splitName)) {
632                throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
633                        "Split " + apk.splitName + " was defined multiple times");
634            }
635
636            // Use first package to define unknown values
637            if (mPackageName == null) {
638                mPackageName = apk.packageName;
639                mVersionCode = apk.versionCode;
640            }
641            if (mSignatures == null) {
642                mSignatures = apk.signatures;
643            }
644
645            assertApkConsistent(String.valueOf(file), apk);
646
647            // Take this opportunity to enforce uniform naming
648            final String targetName;
649            if (apk.splitName == null) {
650                targetName = "base.apk";
651            } else {
652                targetName = "split_" + apk.splitName + ".apk";
653            }
654            if (!FileUtils.isValidExtFilename(targetName)) {
655                throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
656                        "Invalid filename: " + targetName);
657            }
658
659            final File targetFile = new File(mResolvedStageDir, targetName);
660            if (!file.equals(targetFile)) {
661                file.renameTo(targetFile);
662            }
663
664            // Base is coming from session
665            if (apk.splitName == null) {
666                mResolvedBaseFile = targetFile;
667            }
668
669            mResolvedStagedFiles.add(targetFile);
670        }
671
672        if (params.mode == SessionParams.MODE_FULL_INSTALL) {
673            // Full installs must include a base package
674            if (!stagedSplits.contains(null)) {
675                throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
676                        "Full install must include a base package");
677            }
678
679        } else {
680            // Partial installs must be consistent with existing install
681            final ApplicationInfo app = mPm.getApplicationInfo(mPackageName, 0, userId);
682            if (app == null) {
683                throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
684                        "Missing existing base package for " + mPackageName);
685            }
686
687            final PackageLite existing;
688            final ApkLite existingBase;
689            try {
690                existing = PackageParser.parsePackageLite(new File(app.getCodePath()), 0);
691                existingBase = PackageParser.parseApkLite(new File(app.getBaseCodePath()),
692                        PackageParser.PARSE_COLLECT_CERTIFICATES);
693            } catch (PackageParserException e) {
694                throw PackageManagerException.from(e);
695            }
696
697            assertApkConsistent("Existing base", existingBase);
698
699            // Inherit base if not overridden
700            if (mResolvedBaseFile == null) {
701                mResolvedBaseFile = new File(app.getBaseCodePath());
702                mResolvedInheritedFiles.add(mResolvedBaseFile);
703            }
704
705            // Inherit splits if not overridden
706            if (!ArrayUtils.isEmpty(existing.splitNames)) {
707                for (int i = 0; i < existing.splitNames.length; i++) {
708                    final String splitName = existing.splitNames[i];
709                    final File splitFile = new File(existing.splitCodePaths[i]);
710
711                    if (!stagedSplits.contains(splitName)) {
712                        mResolvedInheritedFiles.add(splitFile);
713                    }
714                }
715            }
716
717            // Inherit compiled oat directory.
718            final File packageInstallDir = (new File(app.getBaseCodePath())).getParentFile();
719            mInheritedFilesBase = packageInstallDir;
720            final File oatDir = new File(packageInstallDir, "oat");
721            if (oatDir.exists()) {
722                final File[] archSubdirs = oatDir.listFiles();
723
724                // Keep track of all instruction sets we've seen compiled output for.
725                // If we're linking (and not copying) inherited files, we can recreate the
726                // instruction set hierarchy and link compiled output.
727                if (archSubdirs != null && archSubdirs.length > 0) {
728                    final String[] instructionSets = InstructionSets.getAllDexCodeInstructionSets();
729                    for (File archSubDir : archSubdirs) {
730                        // Skip any directory that isn't an ISA subdir.
731                        if (!ArrayUtils.contains(instructionSets, archSubDir.getName())) {
732                            continue;
733                        }
734
735                        mResolvedInstructionSets.add(archSubDir.getName());
736                        List<File> oatFiles = Arrays.asList(archSubDir.listFiles());
737                        if (!oatFiles.isEmpty()) {
738                            mResolvedInheritedFiles.addAll(oatFiles);
739                        }
740                    }
741                }
742            }
743        }
744    }
745
746    private void assertApkConsistent(String tag, ApkLite apk) throws PackageManagerException {
747        if (!mPackageName.equals(apk.packageName)) {
748            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag + " package "
749                    + apk.packageName + " inconsistent with " + mPackageName);
750        }
751        if (mVersionCode != apk.versionCode) {
752            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag
753                    + " version code " + apk.versionCode + " inconsistent with "
754                    + mVersionCode);
755        }
756        if (!Signature.areExactMatch(mSignatures, apk.signatures)) {
757            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
758                    tag + " signatures are inconsistent");
759        }
760    }
761
762    /**
763     * Calculate the final install footprint size, combining both staged and
764     * existing APKs together and including unpacked native code from both.
765     */
766    private long calculateInstalledSize() throws PackageManagerException {
767        Preconditions.checkNotNull(mResolvedBaseFile);
768
769        final ApkLite baseApk;
770        try {
771            baseApk = PackageParser.parseApkLite(mResolvedBaseFile, 0);
772        } catch (PackageParserException e) {
773            throw PackageManagerException.from(e);
774        }
775
776        final List<String> splitPaths = new ArrayList<>();
777        for (File file : mResolvedStagedFiles) {
778            if (mResolvedBaseFile.equals(file)) continue;
779            splitPaths.add(file.getAbsolutePath());
780        }
781        for (File file : mResolvedInheritedFiles) {
782            if (mResolvedBaseFile.equals(file)) continue;
783            splitPaths.add(file.getAbsolutePath());
784        }
785
786        // This is kind of hacky; we're creating a half-parsed package that is
787        // straddled between the inherited and staged APKs.
788        final PackageLite pkg = new PackageLite(null, baseApk, null,
789                splitPaths.toArray(new String[splitPaths.size()]), null);
790        final boolean isForwardLocked =
791                (params.installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
792
793        try {
794            return PackageHelper.calculateInstalledSize(pkg, isForwardLocked, params.abiOverride);
795        } catch (IOException e) {
796            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
797                    "Failed to calculate install size", e);
798        }
799    }
800
801    /**
802     * Determine if creating hard links between source and destination is
803     * possible. That is, do they all live on the same underlying device.
804     */
805    private boolean isLinkPossible(List<File> fromFiles, File toDir) {
806        try {
807            final StructStat toStat = Os.stat(toDir.getAbsolutePath());
808            for (File fromFile : fromFiles) {
809                final StructStat fromStat = Os.stat(fromFile.getAbsolutePath());
810                if (fromStat.st_dev != toStat.st_dev) {
811                    return false;
812                }
813            }
814        } catch (ErrnoException e) {
815            Slog.w(TAG, "Failed to detect if linking possible: " + e);
816            return false;
817        }
818        return true;
819    }
820
821    private static String getRelativePath(File file, File base) throws IOException {
822        final String pathStr = file.getAbsolutePath();
823        final String baseStr = base.getAbsolutePath();
824        // Don't allow relative paths.
825        if (pathStr.contains("/.") ) {
826            throw new IOException("Invalid path (was relative) : " + pathStr);
827        }
828
829        if (pathStr.startsWith(baseStr)) {
830            return pathStr.substring(baseStr.length());
831        }
832
833        throw new IOException("File: " + pathStr + " outside base: " + baseStr);
834    }
835
836    private void createOatDirs(List<String> instructionSets, File fromDir)
837            throws PackageManagerException {
838        for (String instructionSet : instructionSets) {
839            try {
840                mPm.mInstaller.createOatDir(fromDir.getAbsolutePath(), instructionSet);
841            } catch (InstallerException e) {
842                throw PackageManagerException.from(e);
843            }
844        }
845    }
846
847    private void linkFiles(List<File> fromFiles, File toDir, File fromDir)
848            throws IOException {
849        for (File fromFile : fromFiles) {
850            final String relativePath = getRelativePath(fromFile, fromDir);
851            try {
852                mPm.mInstaller.linkFile(relativePath, fromDir.getAbsolutePath(),
853                        toDir.getAbsolutePath());
854            } catch (InstallerException e) {
855                throw new IOException("failed linkOrCreateDir(" + relativePath + ", "
856                        + fromDir + ", " + toDir + ")", e);
857            }
858        }
859
860        Slog.d(TAG, "Linked " + fromFiles.size() + " files into " + toDir);
861    }
862
863    private static void copyFiles(List<File> fromFiles, File toDir) throws IOException {
864        // Remove any partial files from previous attempt
865        for (File file : toDir.listFiles()) {
866            if (file.getName().endsWith(".tmp")) {
867                file.delete();
868            }
869        }
870
871        for (File fromFile : fromFiles) {
872            final File tmpFile = File.createTempFile("inherit", ".tmp", toDir);
873            if (LOGD) Slog.d(TAG, "Copying " + fromFile + " to " + tmpFile);
874            if (!FileUtils.copyFile(fromFile, tmpFile)) {
875                throw new IOException("Failed to copy " + fromFile + " to " + tmpFile);
876            }
877            try {
878                Os.chmod(tmpFile.getAbsolutePath(), 0644);
879            } catch (ErrnoException e) {
880                throw new IOException("Failed to chmod " + tmpFile);
881            }
882            final File toFile = new File(toDir, fromFile.getName());
883            if (LOGD) Slog.d(TAG, "Renaming " + tmpFile + " to " + toFile);
884            if (!tmpFile.renameTo(toFile)) {
885                throw new IOException("Failed to rename " + tmpFile + " to " + toFile);
886            }
887        }
888        Slog.d(TAG, "Copied " + fromFiles.size() + " files into " + toDir);
889    }
890
891    private static void extractNativeLibraries(File packageDir, String abiOverride)
892            throws PackageManagerException {
893        // Always start from a clean slate
894        final File libDir = new File(packageDir, NativeLibraryHelper.LIB_DIR_NAME);
895        NativeLibraryHelper.removeNativeBinariesFromDirLI(libDir, true);
896
897        NativeLibraryHelper.Handle handle = null;
898        try {
899            handle = NativeLibraryHelper.Handle.create(packageDir);
900            final int res = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libDir,
901                    abiOverride);
902            if (res != PackageManager.INSTALL_SUCCEEDED) {
903                throw new PackageManagerException(res,
904                        "Failed to extract native libraries, res=" + res);
905            }
906        } catch (IOException e) {
907            throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
908                    "Failed to extract native libraries", e);
909        } finally {
910            IoUtils.closeQuietly(handle);
911        }
912    }
913
914    private static void resizeContainer(String cid, long targetSize)
915            throws PackageManagerException {
916        String path = PackageHelper.getSdDir(cid);
917        if (path == null) {
918            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
919                    "Failed to find mounted " + cid);
920        }
921
922        final long currentSize = new File(path).getTotalSpace();
923        if (currentSize > targetSize) {
924            Slog.w(TAG, "Current size " + currentSize + " is larger than target size "
925                    + targetSize + "; skipping resize");
926            return;
927        }
928
929        if (!PackageHelper.unMountSdDir(cid)) {
930            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
931                    "Failed to unmount " + cid + " before resize");
932        }
933
934        if (!PackageHelper.resizeSdDir(targetSize, cid,
935                PackageManagerService.getEncryptKey())) {
936            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
937                    "Failed to resize " + cid + " to " + targetSize + " bytes");
938        }
939
940        path = PackageHelper.mountSdDir(cid, PackageManagerService.getEncryptKey(),
941                Process.SYSTEM_UID, false);
942        if (path == null) {
943            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
944                    "Failed to mount " + cid + " after resize");
945        }
946    }
947
948    private void finalizeAndFixContainer(String cid) throws PackageManagerException {
949        if (!PackageHelper.finalizeSdDir(cid)) {
950            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
951                    "Failed to finalize container " + cid);
952        }
953
954        final int uid = mPm.getPackageUid(PackageManagerService.DEFAULT_CONTAINER_PACKAGE,
955                PackageManager.MATCH_SYSTEM_ONLY, UserHandle.USER_SYSTEM);
956        final int gid = UserHandle.getSharedAppGid(uid);
957        if (!PackageHelper.fixSdPermissions(cid, gid, null)) {
958            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
959                    "Failed to fix permissions on container " + cid);
960        }
961    }
962
963    void setPermissionsResult(boolean accepted) {
964        if (!mSealed) {
965            throw new SecurityException("Must be sealed to accept permissions");
966        }
967
968        if (accepted) {
969            // Mark and kick off another install pass
970            synchronized (mLock) {
971                mPermissionsAccepted = true;
972            }
973            mHandler.obtainMessage(MSG_COMMIT).sendToTarget();
974        } else {
975            destroyInternal();
976            dispatchSessionFinished(INSTALL_FAILED_ABORTED, "User rejected permissions", null);
977        }
978    }
979
980    public void open() throws IOException {
981        if (mActiveCount.getAndIncrement() == 0) {
982            mCallback.onSessionActiveChanged(this, true);
983        }
984
985        synchronized (mLock) {
986            if (!mPrepared) {
987                if (stageDir != null) {
988                    prepareStageDir(stageDir);
989                } else if (stageCid != null) {
990                    prepareExternalStageCid(stageCid, params.sizeBytes);
991
992                    // TODO: deliver more granular progress for ASEC allocation
993                    mInternalProgress = 0.25f;
994                    computeProgressLocked(true);
995                } else {
996                    throw new IllegalArgumentException(
997                            "Exactly one of stageDir or stageCid stage must be set");
998                }
999
1000                mPrepared = true;
1001                mCallback.onSessionPrepared(this);
1002            }
1003        }
1004    }
1005
1006    @Override
1007    public void close() {
1008        if (mActiveCount.decrementAndGet() == 0) {
1009            mCallback.onSessionActiveChanged(this, false);
1010        }
1011    }
1012
1013    @Override
1014    public void abandon() {
1015        if (mRelinquished) {
1016            Slog.d(TAG, "Ignoring abandon after commit relinquished control");
1017            return;
1018        }
1019        destroyInternal();
1020        dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null);
1021    }
1022
1023    private void dispatchSessionFinished(int returnCode, String msg, Bundle extras) {
1024        mFinalStatus = returnCode;
1025        mFinalMessage = msg;
1026
1027        if (mRemoteObserver != null) {
1028            try {
1029                mRemoteObserver.onPackageInstalled(mPackageName, returnCode, msg, extras);
1030            } catch (RemoteException ignored) {
1031            }
1032        }
1033
1034        final boolean success = (returnCode == PackageManager.INSTALL_SUCCEEDED);
1035        mCallback.onSessionFinished(this, success);
1036    }
1037
1038    private void destroyInternal() {
1039        synchronized (mLock) {
1040            mSealed = true;
1041            mDestroyed = true;
1042
1043            // Force shut down all bridges
1044            for (FileBridge bridge : mBridges) {
1045                bridge.forceClose();
1046            }
1047        }
1048        if (stageDir != null) {
1049            try {
1050                mPm.mInstaller.rmPackageDir(stageDir.getAbsolutePath());
1051            } catch (InstallerException ignored) {
1052            }
1053        }
1054        if (stageCid != null) {
1055            PackageHelper.destroySdDir(stageCid);
1056        }
1057    }
1058
1059    void dump(IndentingPrintWriter pw) {
1060        synchronized (mLock) {
1061            dumpLocked(pw);
1062        }
1063    }
1064
1065    private void dumpLocked(IndentingPrintWriter pw) {
1066        pw.println("Session " + sessionId + ":");
1067        pw.increaseIndent();
1068
1069        pw.printPair("userId", userId);
1070        pw.printPair("installerPackageName", installerPackageName);
1071        pw.printPair("installerUid", installerUid);
1072        pw.printPair("createdMillis", createdMillis);
1073        pw.printPair("stageDir", stageDir);
1074        pw.printPair("stageCid", stageCid);
1075        pw.println();
1076
1077        params.dump(pw);
1078
1079        pw.printPair("mClientProgress", mClientProgress);
1080        pw.printPair("mProgress", mProgress);
1081        pw.printPair("mSealed", mSealed);
1082        pw.printPair("mPermissionsAccepted", mPermissionsAccepted);
1083        pw.printPair("mRelinquished", mRelinquished);
1084        pw.printPair("mDestroyed", mDestroyed);
1085        pw.printPair("mBridges", mBridges.size());
1086        pw.printPair("mFinalStatus", mFinalStatus);
1087        pw.printPair("mFinalMessage", mFinalMessage);
1088        pw.println();
1089
1090        pw.decreaseIndent();
1091    }
1092}
1093