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