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