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