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