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