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