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