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                final List<File> fromFiles = mResolvedInheritedFiles;
501                final File toDir = resolveStageDir();
502
503                if (isLinkPossible(fromFiles, toDir)) {
504                    linkFiles(fromFiles, toDir);
505                } else {
506                    // TODO: this should delegate to DCS so the system process
507                    // avoids holding open FDs into containers.
508                    copyFiles(fromFiles, toDir);
509                }
510            } catch (IOException e) {
511                throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
512                        "Failed to inherit existing install", e);
513            }
514        }
515
516        // TODO: surface more granular state from dexopt
517        mInternalProgress = 0.5f;
518        computeProgressLocked(true);
519
520        // Unpack native libraries
521        extractNativeLibraries(mResolvedStageDir, params.abiOverride);
522
523        // Container is ready to go, let's seal it up!
524        if (stageCid != null) {
525            finalizeAndFixContainer(stageCid);
526        }
527
528        // We've reached point of no return; call into PMS to install the stage.
529        // Regardless of success or failure we always destroy session.
530        final IPackageInstallObserver2 localObserver = new IPackageInstallObserver2.Stub() {
531            @Override
532            public void onUserActionRequired(Intent intent) {
533                throw new IllegalStateException();
534            }
535
536            @Override
537            public void onPackageInstalled(String basePackageName, int returnCode, String msg,
538                    Bundle extras) {
539                destroyInternal();
540                dispatchSessionFinished(returnCode, msg, extras);
541            }
542        };
543
544        final UserHandle user;
545        if ((params.installFlags & PackageManager.INSTALL_ALL_USERS) != 0) {
546            user = UserHandle.ALL;
547        } else {
548            user = new UserHandle(userId);
549        }
550
551        mPm.installStage(mPackageName, stageDir, stageCid, localObserver, params,
552                installerPackageName, installerUid, user);
553    }
554
555    /**
556     * Validate install by confirming that all application packages are have
557     * consistent package name, version code, and signing certificates.
558     * <p>
559     * Clears and populates {@link #mResolvedBaseFile},
560     * {@link #mResolvedStagedFiles}, and {@link #mResolvedInheritedFiles}.
561     * <p>
562     * Renames package files in stage to match split names defined inside.
563     * <p>
564     * Note that upgrade compatibility is still performed by
565     * {@link PackageManagerService}.
566     */
567    private void validateInstallLocked() throws PackageManagerException {
568        mPackageName = null;
569        mVersionCode = -1;
570        mSignatures = null;
571
572        mResolvedBaseFile = null;
573        mResolvedStagedFiles.clear();
574        mResolvedInheritedFiles.clear();
575
576        final File[] files = mResolvedStageDir.listFiles();
577        if (ArrayUtils.isEmpty(files)) {
578            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "No packages staged");
579        }
580
581        // Verify that all staged packages are internally consistent
582        final ArraySet<String> stagedSplits = new ArraySet<>();
583        for (File file : files) {
584
585            // Installers can't stage directories, so it's fine to ignore
586            // entries like "lost+found".
587            if (file.isDirectory()) continue;
588
589            final ApkLite apk;
590            try {
591                apk = PackageParser.parseApkLite(file, PackageParser.PARSE_COLLECT_CERTIFICATES);
592            } catch (PackageParserException e) {
593                throw PackageManagerException.from(e);
594            }
595
596            if (!stagedSplits.add(apk.splitName)) {
597                throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
598                        "Split " + apk.splitName + " was defined multiple times");
599            }
600
601            // Use first package to define unknown values
602            if (mPackageName == null) {
603                mPackageName = apk.packageName;
604                mVersionCode = apk.versionCode;
605            }
606            if (mSignatures == null) {
607                mSignatures = apk.signatures;
608            }
609
610            assertApkConsistent(String.valueOf(file), apk);
611
612            // Take this opportunity to enforce uniform naming
613            final String targetName;
614            if (apk.splitName == null) {
615                targetName = "base.apk";
616            } else {
617                targetName = "split_" + apk.splitName + ".apk";
618            }
619            if (!FileUtils.isValidExtFilename(targetName)) {
620                throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
621                        "Invalid filename: " + targetName);
622            }
623
624            final File targetFile = new File(mResolvedStageDir, targetName);
625            if (!file.equals(targetFile)) {
626                file.renameTo(targetFile);
627            }
628
629            // Base is coming from session
630            if (apk.splitName == null) {
631                mResolvedBaseFile = targetFile;
632            }
633
634            mResolvedStagedFiles.add(targetFile);
635        }
636
637        if (params.mode == SessionParams.MODE_FULL_INSTALL) {
638            // Full installs must include a base package
639            if (!stagedSplits.contains(null)) {
640                throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
641                        "Full install must include a base package");
642            }
643
644        } else {
645            // Partial installs must be consistent with existing install
646            final ApplicationInfo app = mPm.getApplicationInfo(mPackageName, 0, userId);
647            if (app == null) {
648                throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
649                        "Missing existing base package for " + mPackageName);
650            }
651
652            final PackageLite existing;
653            final ApkLite existingBase;
654            try {
655                existing = PackageParser.parsePackageLite(new File(app.getCodePath()), 0);
656                existingBase = PackageParser.parseApkLite(new File(app.getBaseCodePath()),
657                        PackageParser.PARSE_COLLECT_CERTIFICATES);
658            } catch (PackageParserException e) {
659                throw PackageManagerException.from(e);
660            }
661
662            assertApkConsistent("Existing base", existingBase);
663
664            // Inherit base if not overridden
665            if (mResolvedBaseFile == null) {
666                mResolvedBaseFile = new File(app.getBaseCodePath());
667                mResolvedInheritedFiles.add(mResolvedBaseFile);
668            }
669
670            // Inherit splits if not overridden
671            if (!ArrayUtils.isEmpty(existing.splitNames)) {
672                for (int i = 0; i < existing.splitNames.length; i++) {
673                    final String splitName = existing.splitNames[i];
674                    final File splitFile = new File(existing.splitCodePaths[i]);
675
676                    if (!stagedSplits.contains(splitName)) {
677                        mResolvedInheritedFiles.add(splitFile);
678                    }
679                }
680            }
681        }
682    }
683
684    private void assertApkConsistent(String tag, ApkLite apk) throws PackageManagerException {
685        if (!mPackageName.equals(apk.packageName)) {
686            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag + " package "
687                    + apk.packageName + " inconsistent with " + mPackageName);
688        }
689        if (mVersionCode != apk.versionCode) {
690            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag
691                    + " version code " + apk.versionCode + " inconsistent with "
692                    + mVersionCode);
693        }
694        if (!Signature.areExactMatch(mSignatures, apk.signatures)) {
695            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
696                    tag + " signatures are inconsistent");
697        }
698    }
699
700    /**
701     * Calculate the final install footprint size, combining both staged and
702     * existing APKs together and including unpacked native code from both.
703     */
704    private long calculateInstalledSize() throws PackageManagerException {
705        Preconditions.checkNotNull(mResolvedBaseFile);
706
707        final ApkLite baseApk;
708        try {
709            baseApk = PackageParser.parseApkLite(mResolvedBaseFile, 0);
710        } catch (PackageParserException e) {
711            throw PackageManagerException.from(e);
712        }
713
714        final List<String> splitPaths = new ArrayList<>();
715        for (File file : mResolvedStagedFiles) {
716            if (mResolvedBaseFile.equals(file)) continue;
717            splitPaths.add(file.getAbsolutePath());
718        }
719        for (File file : mResolvedInheritedFiles) {
720            if (mResolvedBaseFile.equals(file)) continue;
721            splitPaths.add(file.getAbsolutePath());
722        }
723
724        // This is kind of hacky; we're creating a half-parsed package that is
725        // straddled between the inherited and staged APKs.
726        final PackageLite pkg = new PackageLite(null, baseApk, null,
727                splitPaths.toArray(new String[splitPaths.size()]), null);
728        final boolean isForwardLocked =
729                (params.installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
730
731        try {
732            return PackageHelper.calculateInstalledSize(pkg, isForwardLocked, params.abiOverride);
733        } catch (IOException e) {
734            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
735                    "Failed to calculate install size", e);
736        }
737    }
738
739    /**
740     * Determine if creating hard links between source and destination is
741     * possible. That is, do they all live on the same underlying device.
742     */
743    private boolean isLinkPossible(List<File> fromFiles, File toDir) {
744        try {
745            final StructStat toStat = Os.stat(toDir.getAbsolutePath());
746            for (File fromFile : fromFiles) {
747                final StructStat fromStat = Os.stat(fromFile.getAbsolutePath());
748                if (fromStat.st_dev != toStat.st_dev) {
749                    return false;
750                }
751            }
752        } catch (ErrnoException e) {
753            Slog.w(TAG, "Failed to detect if linking possible: " + e);
754            return false;
755        }
756        return true;
757    }
758
759    private static void linkFiles(List<File> fromFiles, File toDir) throws IOException {
760        for (File fromFile : fromFiles) {
761            final File toFile = new File(toDir, fromFile.getName());
762            try {
763                if (LOGD) Slog.d(TAG, "Linking " + fromFile + " to " + toFile);
764                Os.link(fromFile.getAbsolutePath(), toFile.getAbsolutePath());
765            } catch (ErrnoException e) {
766                throw new IOException("Failed to link " + fromFile + " to " + toFile, e);
767            }
768        }
769        Slog.d(TAG, "Linked " + fromFiles.size() + " files into " + toDir);
770    }
771
772    private static void copyFiles(List<File> fromFiles, File toDir) throws IOException {
773        // Remove any partial files from previous attempt
774        for (File file : toDir.listFiles()) {
775            if (file.getName().endsWith(".tmp")) {
776                file.delete();
777            }
778        }
779
780        for (File fromFile : fromFiles) {
781            final File tmpFile = File.createTempFile("inherit", ".tmp", toDir);
782            if (LOGD) Slog.d(TAG, "Copying " + fromFile + " to " + tmpFile);
783            if (!FileUtils.copyFile(fromFile, tmpFile)) {
784                throw new IOException("Failed to copy " + fromFile + " to " + tmpFile);
785            }
786            try {
787                Os.chmod(tmpFile.getAbsolutePath(), 0644);
788            } catch (ErrnoException e) {
789                throw new IOException("Failed to chmod " + tmpFile);
790            }
791            final File toFile = new File(toDir, fromFile.getName());
792            if (LOGD) Slog.d(TAG, "Renaming " + tmpFile + " to " + toFile);
793            if (!tmpFile.renameTo(toFile)) {
794                throw new IOException("Failed to rename " + tmpFile + " to " + toFile);
795            }
796        }
797        Slog.d(TAG, "Copied " + fromFiles.size() + " files into " + toDir);
798    }
799
800    private static void extractNativeLibraries(File packageDir, String abiOverride)
801            throws PackageManagerException {
802        // Always start from a clean slate
803        final File libDir = new File(packageDir, NativeLibraryHelper.LIB_DIR_NAME);
804        NativeLibraryHelper.removeNativeBinariesFromDirLI(libDir, true);
805
806        NativeLibraryHelper.Handle handle = null;
807        try {
808            handle = NativeLibraryHelper.Handle.create(packageDir);
809            final int res = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libDir,
810                    abiOverride);
811            if (res != PackageManager.INSTALL_SUCCEEDED) {
812                throw new PackageManagerException(res,
813                        "Failed to extract native libraries, res=" + res);
814            }
815        } catch (IOException e) {
816            throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
817                    "Failed to extract native libraries", e);
818        } finally {
819            IoUtils.closeQuietly(handle);
820        }
821    }
822
823    private static void resizeContainer(String cid, long targetSize)
824            throws PackageManagerException {
825        String path = PackageHelper.getSdDir(cid);
826        if (path == null) {
827            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
828                    "Failed to find mounted " + cid);
829        }
830
831        final long currentSize = new File(path).getTotalSpace();
832        if (currentSize > targetSize) {
833            Slog.w(TAG, "Current size " + currentSize + " is larger than target size "
834                    + targetSize + "; skipping resize");
835            return;
836        }
837
838        if (!PackageHelper.unMountSdDir(cid)) {
839            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
840                    "Failed to unmount " + cid + " before resize");
841        }
842
843        if (!PackageHelper.resizeSdDir(targetSize, cid,
844                PackageManagerService.getEncryptKey())) {
845            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
846                    "Failed to resize " + cid + " to " + targetSize + " bytes");
847        }
848
849        path = PackageHelper.mountSdDir(cid, PackageManagerService.getEncryptKey(),
850                Process.SYSTEM_UID, false);
851        if (path == null) {
852            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
853                    "Failed to mount " + cid + " after resize");
854        }
855    }
856
857    private void finalizeAndFixContainer(String cid) throws PackageManagerException {
858        if (!PackageHelper.finalizeSdDir(cid)) {
859            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
860                    "Failed to finalize container " + cid);
861        }
862
863        final int uid = mPm.getPackageUid(PackageManagerService.DEFAULT_CONTAINER_PACKAGE,
864                UserHandle.USER_OWNER);
865        final int gid = UserHandle.getSharedAppGid(uid);
866        if (!PackageHelper.fixSdPermissions(cid, gid, null)) {
867            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
868                    "Failed to fix permissions on container " + cid);
869        }
870    }
871
872    void setPermissionsResult(boolean accepted) {
873        if (!mSealed) {
874            throw new SecurityException("Must be sealed to accept permissions");
875        }
876
877        if (accepted) {
878            // Mark and kick off another install pass
879            mPermissionsAccepted = true;
880            mHandler.obtainMessage(MSG_COMMIT).sendToTarget();
881        } else {
882            destroyInternal();
883            dispatchSessionFinished(INSTALL_FAILED_ABORTED, "User rejected permissions", null);
884        }
885    }
886
887    public void open() throws IOException {
888        if (mActiveCount.getAndIncrement() == 0) {
889            mCallback.onSessionActiveChanged(this, true);
890        }
891
892        synchronized (mLock) {
893            if (!mPrepared) {
894                if (stageDir != null) {
895                    prepareInternalStageDir(stageDir);
896                } else if (stageCid != null) {
897                    prepareExternalStageCid(stageCid, params.sizeBytes);
898
899                    // TODO: deliver more granular progress for ASEC allocation
900                    mInternalProgress = 0.25f;
901                    computeProgressLocked(true);
902                } else {
903                    throw new IllegalArgumentException(
904                            "Exactly one of stageDir or stageCid stage must be set");
905                }
906
907                mPrepared = true;
908                mCallback.onSessionPrepared(this);
909            }
910        }
911    }
912
913    @Override
914    public void close() {
915        if (mActiveCount.decrementAndGet() == 0) {
916            mCallback.onSessionActiveChanged(this, false);
917        }
918    }
919
920    @Override
921    public void abandon() {
922        destroyInternal();
923        dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null);
924    }
925
926    private void dispatchSessionFinished(int returnCode, String msg, Bundle extras) {
927        mFinalStatus = returnCode;
928        mFinalMessage = msg;
929
930        if (mRemoteObserver != null) {
931            try {
932                mRemoteObserver.onPackageInstalled(mPackageName, returnCode, msg, extras);
933            } catch (RemoteException ignored) {
934            }
935        }
936
937        final boolean success = (returnCode == PackageManager.INSTALL_SUCCEEDED);
938        mCallback.onSessionFinished(this, success);
939    }
940
941    private void destroyInternal() {
942        synchronized (mLock) {
943            mSealed = true;
944            mDestroyed = true;
945
946            // Force shut down all bridges
947            for (FileBridge bridge : mBridges) {
948                bridge.forceClose();
949            }
950        }
951        if (stageDir != null) {
952            FileUtils.deleteContents(stageDir);
953            stageDir.delete();
954        }
955        if (stageCid != null) {
956            PackageHelper.destroySdDir(stageCid);
957        }
958    }
959
960    void dump(IndentingPrintWriter pw) {
961        synchronized (mLock) {
962            dumpLocked(pw);
963        }
964    }
965
966    private void dumpLocked(IndentingPrintWriter pw) {
967        pw.println("Session " + sessionId + ":");
968        pw.increaseIndent();
969
970        pw.printPair("userId", userId);
971        pw.printPair("installerPackageName", installerPackageName);
972        pw.printPair("installerUid", installerUid);
973        pw.printPair("createdMillis", createdMillis);
974        pw.printPair("stageDir", stageDir);
975        pw.printPair("stageCid", stageCid);
976        pw.println();
977
978        params.dump(pw);
979
980        pw.printPair("mClientProgress", mClientProgress);
981        pw.printPair("mProgress", mProgress);
982        pw.printPair("mSealed", mSealed);
983        pw.printPair("mPermissionsAccepted", mPermissionsAccepted);
984        pw.printPair("mDestroyed", mDestroyed);
985        pw.printPair("mBridges", mBridges.size());
986        pw.printPair("mFinalStatus", mFinalStatus);
987        pw.printPair("mFinalMessage", mFinalMessage);
988        pw.println();
989
990        pw.decreaseIndent();
991    }
992}
993