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