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