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