PackageInstallerSession.java revision bc7bce38b2e4733a14f6296c75f983bd50f996d1
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_ALREADY_EXISTS;
21import static android.content.pm.PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
22import static android.content.pm.PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
23import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
24import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
25import static android.content.pm.PackageManager.INSTALL_FAILED_PACKAGE_CHANGED;
26import static android.system.OsConstants.O_CREAT;
27import static android.system.OsConstants.O_RDONLY;
28import static android.system.OsConstants.O_WRONLY;
29
30import android.content.Context;
31import android.content.Intent;
32import android.content.IntentSender;
33import android.content.pm.ApplicationInfo;
34import android.content.pm.IPackageInstallObserver2;
35import android.content.pm.IPackageInstallerSession;
36import android.content.pm.PackageInstaller;
37import android.content.pm.PackageInstaller.SessionInfo;
38import android.content.pm.PackageInstaller.SessionParams;
39import android.content.pm.PackageManager;
40import android.content.pm.PackageParser;
41import android.content.pm.PackageParser.ApkLite;
42import android.content.pm.PackageParser.PackageLite;
43import android.content.pm.PackageParser.PackageParserException;
44import android.content.pm.Signature;
45import android.os.Bundle;
46import android.os.FileBridge;
47import android.os.FileUtils;
48import android.os.Handler;
49import android.os.Looper;
50import android.os.Message;
51import android.os.ParcelFileDescriptor;
52import android.os.Process;
53import android.os.RemoteException;
54import android.os.UserHandle;
55import android.system.ErrnoException;
56import android.system.Os;
57import android.system.OsConstants;
58import android.system.StructStat;
59import android.util.ArraySet;
60import android.util.ExceptionUtils;
61import android.util.MathUtils;
62import android.util.Slog;
63
64import com.android.internal.annotations.GuardedBy;
65import com.android.internal.content.NativeLibraryHelper;
66import com.android.internal.content.PackageHelper;
67import com.android.internal.util.ArrayUtils;
68import com.android.internal.util.IndentingPrintWriter;
69import com.android.internal.util.Preconditions;
70import com.android.server.pm.PackageInstallerService.PackageInstallObserverAdapter;
71
72import libcore.io.IoUtils;
73import libcore.io.Libcore;
74
75import java.io.File;
76import java.io.FileDescriptor;
77import java.io.IOException;
78import java.util.ArrayList;
79import java.util.List;
80import java.util.concurrent.atomic.AtomicInteger;
81
82public class PackageInstallerSession extends IPackageInstallerSession.Stub {
83    private static final String TAG = "PackageInstaller";
84    private static final boolean LOGD = true;
85
86    private static final int MSG_COMMIT = 0;
87
88    // TODO: enforce INSTALL_ALLOW_TEST
89    // TODO: enforce INSTALL_ALLOW_DOWNGRADE
90
91    // TODO: treat INHERIT_EXISTING as installExistingPackage()
92
93    private final PackageInstallerService.InternalCallback mCallback;
94    private final Context mContext;
95    private final PackageManagerService mPm;
96    private final Handler mHandler;
97
98    final int sessionId;
99    final int userId;
100    final String installerPackageName;
101    final SessionParams params;
102    final long createdMillis;
103
104    /** Staging location where client data is written. */
105    final File stageDir;
106    final String stageCid;
107
108    /** Note that UID is not persisted; it's always derived at runtime. */
109    final int installerUid;
110
111    private final AtomicInteger mOpenCount = 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 mProgress = 0;
119    @GuardedBy("mLock")
120    private float mReportedProgress = -1;
121
122    @GuardedBy("mLock")
123    private boolean mSealed = false;
124    @GuardedBy("mLock")
125    private boolean mPermissionsAccepted = false;
126    @GuardedBy("mLock")
127    private boolean mDestroyed = false;
128
129    private int mFinalStatus;
130    private String mFinalMessage;
131
132    @GuardedBy("mLock")
133    private ArrayList<FileBridge> mBridges = new ArrayList<>();
134
135    @GuardedBy("mLock")
136    private IPackageInstallObserver2 mRemoteObserver;
137
138    /** Fields derived from commit parsing */
139    private String mPackageName;
140    private int mVersionCode;
141    private Signature[] mSignatures;
142
143    /**
144     * Path to the validated base APK for this session, which may point at an
145     * APK inside the session (when the session defines the base), or it may
146     * point at the existing base APK (when adding splits to an existing app).
147     * <p>
148     * This is used when confirming permissions, since we can't fully stage the
149     * session inside an ASEC before confirming with user.
150     */
151    @GuardedBy("mLock")
152    private File mResolvedBaseFile;
153
154    @GuardedBy("mLock")
155    private File mResolvedStageDir;
156
157    @GuardedBy("mLock")
158    private final List<File> mResolvedStagedFiles = new ArrayList<>();
159    @GuardedBy("mLock")
160    private final List<File> mResolvedInheritedFiles = new ArrayList<>();
161
162    private final Handler.Callback mHandlerCallback = new Handler.Callback() {
163        @Override
164        public boolean handleMessage(Message msg) {
165            synchronized (mLock) {
166                if (msg.obj != null) {
167                    mRemoteObserver = (IPackageInstallObserver2) msg.obj;
168                }
169
170                try {
171                    commitLocked();
172                } catch (PackageManagerException e) {
173                    final String completeMsg = ExceptionUtils.getCompleteMessage(e);
174                    Slog.e(TAG, "Commit of session " + sessionId + " failed: " + completeMsg);
175                    destroyInternal();
176                    dispatchSessionFinished(e.error, completeMsg, null);
177                }
178
179                return true;
180            }
181        }
182    };
183
184    public PackageInstallerSession(PackageInstallerService.InternalCallback callback,
185            Context context, PackageManagerService pm, Looper looper, int sessionId, int userId,
186            String installerPackageName, SessionParams params, long createdMillis,
187            File stageDir, String stageCid, boolean sealed) {
188        mCallback = callback;
189        mContext = context;
190        mPm = pm;
191        mHandler = new Handler(looper, mHandlerCallback);
192
193        this.sessionId = sessionId;
194        this.userId = userId;
195        this.installerPackageName = installerPackageName;
196        this.params = params;
197        this.createdMillis = createdMillis;
198        this.stageDir = stageDir;
199        this.stageCid = stageCid;
200
201        if ((stageDir == null) == (stageCid == null)) {
202            throw new IllegalArgumentException(
203                    "Exactly one of stageDir or stageCid stage must be set");
204        }
205
206        mSealed = sealed;
207
208        // Always derived at runtime
209        installerUid = mPm.getPackageUid(installerPackageName, userId);
210
211        if (mPm.checkPermission(android.Manifest.permission.INSTALL_PACKAGES,
212                installerPackageName) == PackageManager.PERMISSION_GRANTED) {
213            mPermissionsAccepted = true;
214        } else {
215            mPermissionsAccepted = false;
216        }
217
218        computeProgressLocked();
219    }
220
221    public SessionInfo generateInfo() {
222        final SessionInfo info = new SessionInfo();
223        synchronized (mLock) {
224            info.sessionId = sessionId;
225            info.installerPackageName = installerPackageName;
226            info.resolvedBaseCodePath = (mResolvedBaseFile != null) ?
227                    mResolvedBaseFile.getAbsolutePath() : null;
228            info.progress = mProgress;
229            info.sealed = mSealed;
230            info.active = mOpenCount.get() > 0;
231
232            info.mode = params.mode;
233            info.sizeBytes = params.sizeBytes;
234            info.appPackageName = params.appPackageName;
235            info.appIcon = params.appIcon;
236            info.appLabel = params.appLabel;
237        }
238        return info;
239    }
240
241    public boolean isSealed() {
242        synchronized (mLock) {
243            return mSealed;
244        }
245    }
246
247    private void assertNotSealed(String cookie) {
248        synchronized (mLock) {
249            if (mSealed) {
250                throw new SecurityException(cookie + " not allowed after commit");
251            }
252        }
253    }
254
255    /**
256     * Resolve the actual location where staged data should be written. This
257     * might point at an ASEC mount point, which is why we delay path resolution
258     * until someone actively works with the session.
259     */
260    private File resolveStageDir() throws IOException {
261        synchronized (mLock) {
262            if (mResolvedStageDir == null) {
263                if (stageDir != null) {
264                    mResolvedStageDir = stageDir;
265                } else {
266                    final String path = PackageHelper.getSdDir(stageCid);
267                    if (path != null) {
268                        mResolvedStageDir = new File(path);
269                    } else {
270                        throw new IOException("Failed to resolve path to container " + stageCid);
271                    }
272                }
273            }
274            return mResolvedStageDir;
275        }
276    }
277
278    @Override
279    public void setClientProgress(float progress) {
280        synchronized (mLock) {
281            mClientProgress = progress;
282            computeProgressLocked();
283        }
284        maybePublishProgress();
285    }
286
287    @Override
288    public void addClientProgress(float progress) {
289        synchronized (mLock) {
290            mClientProgress += progress;
291            computeProgressLocked();
292        }
293        maybePublishProgress();
294    }
295
296    private void computeProgressLocked() {
297        if (mProgress <= 0.8f) {
298            mProgress = MathUtils.constrain(mClientProgress * 0.8f, 0f, 0.8f);
299        }
300    }
301
302    private void maybePublishProgress() {
303        // Only publish when meaningful change
304        if (Math.abs(mProgress - mReportedProgress) > 0.01) {
305            mReportedProgress = mProgress;
306            mCallback.onSessionProgressChanged(this, mProgress);
307        }
308    }
309
310    @Override
311    public String[] getNames() {
312        assertNotSealed("getNames");
313        try {
314            return resolveStageDir().list();
315        } catch (IOException e) {
316            throw ExceptionUtils.wrap(e);
317        }
318    }
319
320    @Override
321    public ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes) {
322        try {
323            return openWriteInternal(name, offsetBytes, lengthBytes);
324        } catch (IOException e) {
325            throw ExceptionUtils.wrap(e);
326        }
327    }
328
329    private ParcelFileDescriptor openWriteInternal(String name, long offsetBytes, long lengthBytes)
330            throws IOException {
331        // Quick sanity check of state, and allocate a pipe for ourselves. We
332        // then do heavy disk allocation outside the lock, but this open pipe
333        // will block any attempted install transitions.
334        final FileBridge bridge;
335        synchronized (mLock) {
336            assertNotSealed("openWrite");
337
338            bridge = new FileBridge();
339            mBridges.add(bridge);
340        }
341
342        try {
343            // Use installer provided name for now; we always rename later
344            if (!FileUtils.isValidExtFilename(name)) {
345                throw new IllegalArgumentException("Invalid name: " + name);
346            }
347            final File target = new File(resolveStageDir(), name);
348
349            // TODO: this should delegate to DCS so the system process avoids
350            // holding open FDs into containers.
351            final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(),
352                    O_CREAT | O_WRONLY, 0644);
353            Os.chmod(target.getAbsolutePath(), 0644);
354
355            // If caller specified a total length, allocate it for them. Free up
356            // cache space to grow, if needed.
357            if (lengthBytes > 0) {
358                final StructStat stat = Libcore.os.fstat(targetFd);
359                final long deltaBytes = lengthBytes - stat.st_size;
360                // Only need to free up space when writing to internal stage
361                if (stageDir != null && deltaBytes > 0) {
362                    mPm.freeStorage(deltaBytes);
363                }
364                Libcore.os.posix_fallocate(targetFd, 0, lengthBytes);
365            }
366
367            if (offsetBytes > 0) {
368                Libcore.os.lseek(targetFd, offsetBytes, OsConstants.SEEK_SET);
369            }
370
371            bridge.setTargetFile(targetFd);
372            bridge.start();
373            return new ParcelFileDescriptor(bridge.getClientSocket());
374
375        } catch (ErrnoException e) {
376            throw e.rethrowAsIOException();
377        }
378    }
379
380    @Override
381    public ParcelFileDescriptor openRead(String name) {
382        try {
383            return openReadInternal(name);
384        } catch (IOException e) {
385            throw ExceptionUtils.wrap(e);
386        }
387    }
388
389    private ParcelFileDescriptor openReadInternal(String name) throws IOException {
390        assertNotSealed("openRead");
391
392        try {
393            if (!FileUtils.isValidExtFilename(name)) {
394                throw new IllegalArgumentException("Invalid name: " + name);
395            }
396            final File target = new File(resolveStageDir(), name);
397
398            final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(), O_RDONLY, 0);
399            return new ParcelFileDescriptor(targetFd);
400
401        } catch (ErrnoException e) {
402            throw e.rethrowAsIOException();
403        }
404    }
405
406    @Override
407    public void commit(IntentSender statusReceiver) {
408        Preconditions.checkNotNull(statusReceiver);
409
410        final PackageInstallObserverAdapter adapter = new PackageInstallObserverAdapter(mContext,
411                statusReceiver, sessionId);
412        mHandler.obtainMessage(MSG_COMMIT, adapter.getBinder()).sendToTarget();
413    }
414
415    private void commitLocked() throws PackageManagerException {
416        if (mDestroyed) {
417            throw new PackageManagerException(INSTALL_FAILED_ALREADY_EXISTS, "Invalid session");
418        }
419
420        // Verify that all writers are hands-off
421        if (!mSealed) {
422            for (FileBridge bridge : mBridges) {
423                if (!bridge.isClosed()) {
424                    throw new PackageManagerException(INSTALL_FAILED_PACKAGE_CHANGED,
425                            "Files still open");
426                }
427            }
428            mSealed = true;
429
430            // Persist the fact that we've sealed ourselves to prevent mutations
431            // of any hard links we create below.
432            mCallback.onSessionSealed(this);
433        }
434
435        try {
436            resolveStageDir();
437        } catch (IOException e) {
438            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
439                    "Failed to resolve stage location", e);
440        }
441
442        // Verify that stage looks sane with respect to existing application.
443        // This currently only ensures packageName, versionCode, and certificate
444        // consistency.
445        validateInstallLocked();
446
447        Preconditions.checkNotNull(mPackageName);
448        Preconditions.checkNotNull(mSignatures);
449        Preconditions.checkNotNull(mResolvedBaseFile);
450
451        if (!mPermissionsAccepted) {
452            // User needs to accept permissions; give installer an intent they
453            // can use to involve user.
454            final Intent intent = new Intent(PackageInstaller.ACTION_CONFIRM_PERMISSIONS);
455            intent.setPackage("com.android.packageinstaller");
456            intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
457            try {
458                mRemoteObserver.onUserActionRequired(intent);
459            } catch (RemoteException ignored) {
460            }
461            return;
462        }
463
464        if (stageCid != null) {
465            // Figure out the final installed size and resize the container once
466            // and for all. Internally the parser handles straddling between two
467            // locations when inheriting.
468            final long finalSize = calculateInstalledSize();
469            resizeContainer(stageCid, finalSize);
470        }
471
472        // Inherit any packages and native libraries from existing install that
473        // haven't been overridden.
474        if (params.mode == SessionParams.MODE_INHERIT_EXISTING) {
475            try {
476                if (stageCid != null) {
477                    // TODO: this should delegate to DCS so the system process
478                    // avoids holding open FDs into containers.
479                    copyFiles(mResolvedInheritedFiles, resolveStageDir());
480                } else {
481                    linkFiles(mResolvedInheritedFiles, resolveStageDir());
482                }
483            } catch (IOException e) {
484                throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
485                        "Failed to inherit existing install", e);
486            }
487        }
488
489        // TODO: surface more granular state from dexopt
490        mProgress = 0.9f;
491        maybePublishProgress();
492
493        // Unpack native libraries
494        extractNativeLibraries(mResolvedStageDir, params.abiOverride);
495
496        // Container is ready to go, let's seal it up!
497        if (stageCid != null) {
498            finalizeAndFixContainer(stageCid);
499        }
500
501        // We've reached point of no return; call into PMS to install the stage.
502        // Regardless of success or failure we always destroy session.
503        final IPackageInstallObserver2 localObserver = new IPackageInstallObserver2.Stub() {
504            @Override
505            public void onUserActionRequired(Intent intent) {
506                throw new IllegalStateException();
507            }
508
509            @Override
510            public void onPackageInstalled(String basePackageName, int returnCode, String msg,
511                    Bundle extras) {
512                destroyInternal();
513                dispatchSessionFinished(returnCode, msg, extras);
514            }
515        };
516
517        mPm.installStage(mPackageName, stageDir, stageCid, localObserver,
518                params, installerPackageName, installerUid, new UserHandle(userId));
519    }
520
521    /**
522     * Validate install by confirming that all application packages are have
523     * consistent package name, version code, and signing certificates.
524     * <p>
525     * Clears and populates {@link #mResolvedBaseFile},
526     * {@link #mResolvedStagedFiles}, and {@link #mResolvedInheritedFiles}.
527     * <p>
528     * Renames package files in stage to match split names defined inside.
529     * <p>
530     * Note that upgrade compatibility is still performed by
531     * {@link PackageManagerService}.
532     */
533    private void validateInstallLocked() throws PackageManagerException {
534        mPackageName = null;
535        mVersionCode = -1;
536        mSignatures = null;
537
538        mResolvedBaseFile = null;
539        mResolvedStagedFiles.clear();
540        mResolvedInheritedFiles.clear();
541
542        final File[] files = mResolvedStageDir.listFiles();
543        if (ArrayUtils.isEmpty(files)) {
544            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "No packages staged");
545        }
546
547        // Verify that all staged packages are internally consistent
548        final ArraySet<String> stagedSplits = new ArraySet<>();
549        for (File file : files) {
550
551            // Installers can't stage directories, so it's fine to ignore
552            // entries like "lost+found".
553            if (file.isDirectory()) continue;
554
555            final ApkLite apk;
556            try {
557                apk = PackageParser.parseApkLite(file, PackageParser.PARSE_COLLECT_CERTIFICATES);
558            } catch (PackageParserException e) {
559                throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
560                        "Failed to parse " + file + ": " + e);
561            }
562
563            if (!stagedSplits.add(apk.splitName)) {
564                throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
565                        "Split " + apk.splitName + " was defined multiple times");
566            }
567
568            // Use first package to define unknown values
569            if (mPackageName == null) {
570                mPackageName = apk.packageName;
571                mVersionCode = apk.versionCode;
572            }
573            if (mSignatures == null) {
574                mSignatures = apk.signatures;
575            }
576
577            assertApkConsistent(String.valueOf(file), apk);
578
579            // Take this opportunity to enforce uniform naming
580            final String targetName;
581            if (apk.splitName == null) {
582                targetName = "base.apk";
583            } else {
584                targetName = "split_" + apk.splitName + ".apk";
585            }
586            if (!FileUtils.isValidExtFilename(targetName)) {
587                throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
588                        "Invalid filename: " + targetName);
589            }
590
591            final File targetFile = new File(mResolvedStageDir, targetName);
592            if (!file.equals(targetFile)) {
593                file.renameTo(targetFile);
594            }
595
596            // Base is coming from session
597            if (apk.splitName == null) {
598                mResolvedBaseFile = targetFile;
599            }
600
601            mResolvedStagedFiles.add(targetFile);
602        }
603
604        if (params.mode == SessionParams.MODE_FULL_INSTALL) {
605            // Full installs must include a base package
606            if (!stagedSplits.contains(null)) {
607                throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
608                        "Full install must include a base package");
609            }
610
611        } else {
612            // Partial installs must be consistent with existing install
613            final ApplicationInfo app = mPm.getApplicationInfo(mPackageName, 0, userId);
614            if (app == null) {
615                throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
616                        "Missing existing base package for " + mPackageName);
617            }
618
619            final PackageLite existing;
620            final ApkLite existingBase;
621            try {
622                existing = PackageParser.parsePackageLite(new File(app.getCodePath()), 0);
623                existingBase = PackageParser.parseApkLite(new File(app.getBaseCodePath()),
624                        PackageParser.PARSE_COLLECT_CERTIFICATES);
625            } catch (PackageParserException e) {
626                throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
627                        "Failed to parse existing package " + app.getCodePath() + ": " + e);
628            }
629
630            assertApkConsistent("Existing base", existingBase);
631
632            // Inherit base if not overridden
633            if (mResolvedBaseFile == null) {
634                mResolvedBaseFile = new File(app.getBaseCodePath());
635                mResolvedInheritedFiles.add(mResolvedBaseFile);
636            }
637
638            // Inherit splits if not overridden
639            if (!ArrayUtils.isEmpty(existing.splitNames)) {
640                for (int i = 0; i < existing.splitNames.length; i++) {
641                    final String splitName = existing.splitNames[i];
642                    final File splitFile = new File(existing.splitCodePaths[i]);
643
644                    if (!stagedSplits.contains(splitName)) {
645                        mResolvedInheritedFiles.add(splitFile);
646                    }
647                }
648            }
649        }
650    }
651
652    private void assertApkConsistent(String tag, ApkLite apk) throws PackageManagerException {
653        if (!mPackageName.equals(apk.packageName)) {
654            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag + " package "
655                    + apk.packageName + " inconsistent with " + mPackageName);
656        }
657        if (mVersionCode != apk.versionCode) {
658            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag
659                    + " version code " + apk.versionCode + " inconsistent with "
660                    + mVersionCode);
661        }
662        if (!Signature.areExactMatch(mSignatures, apk.signatures)) {
663            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
664                    tag + " signatures are inconsistent");
665        }
666    }
667
668    /**
669     * Calculate the final install footprint size, combining both staged and
670     * existing APKs together and including unpacked native code from both.
671     */
672    private long calculateInstalledSize() throws PackageManagerException {
673        Preconditions.checkNotNull(mResolvedBaseFile);
674
675        final ApkLite baseApk;
676        try {
677            baseApk = PackageParser.parseApkLite(mResolvedBaseFile, 0);
678        } catch (PackageParserException e) {
679            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
680                    "Failed to parse base package " + mResolvedBaseFile + ": " + e);
681        }
682
683        final List<String> splitPaths = new ArrayList<>();
684        for (File file : mResolvedStagedFiles) {
685            if (mResolvedBaseFile.equals(file)) continue;
686            splitPaths.add(file.getAbsolutePath());
687        }
688        for (File file : mResolvedInheritedFiles) {
689            if (mResolvedBaseFile.equals(file)) continue;
690            splitPaths.add(file.getAbsolutePath());
691        }
692
693        // This is kind of hacky; we're creating a half-parsed package that is
694        // straddled between the inherited and staged APKs.
695        final PackageLite pkg = new PackageLite(null, baseApk, null,
696                splitPaths.toArray(new String[splitPaths.size()]));
697        final boolean isForwardLocked =
698                (params.installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
699
700        try {
701            return PackageHelper.calculateInstalledSize(pkg, isForwardLocked, params.abiOverride);
702        } catch (IOException e) {
703            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
704                    "Failed to calculate install size", e);
705        }
706    }
707
708    private static void linkFiles(List<File> fromFiles, File toDir) throws IOException {
709        for (File fromFile : fromFiles) {
710            final File toFile = new File(toDir, fromFile.getName());
711            try {
712                if (LOGD) Slog.d(TAG, "Linking " + fromFile + " to " + toFile);
713                Os.link(fromFile.getAbsolutePath(), toFile.getAbsolutePath());
714            } catch (ErrnoException e) {
715                throw new IOException("Failed to link " + fromFile + " to " + toFile, e);
716            }
717        }
718        Slog.d(TAG, "Linked " + fromFiles.size() + " files into " + toDir);
719    }
720
721    private static void copyFiles(List<File> fromFiles, File toDir) throws IOException {
722        // Remove any partial files from previous attempt
723        for (File file : toDir.listFiles()) {
724            if (file.getName().endsWith(".tmp")) {
725                file.delete();
726            }
727        }
728
729        for (File fromFile : fromFiles) {
730            final File tmpFile = File.createTempFile("inherit", ".tmp", toDir);
731            if (LOGD) Slog.d(TAG, "Copying " + fromFile + " to " + tmpFile);
732            if (!FileUtils.copyFile(fromFile, tmpFile)) {
733                throw new IOException("Failed to copy " + fromFile + " to " + tmpFile);
734            }
735
736            final File toFile = new File(toDir, fromFile.getName());
737            if (LOGD) Slog.d(TAG, "Renaming " + tmpFile + " to " + toFile);
738            if (!tmpFile.renameTo(toFile)) {
739                throw new IOException("Failed to rename " + tmpFile + " to " + toFile);
740            }
741        }
742        Slog.d(TAG, "Copied " + fromFiles.size() + " files into " + toDir);
743    }
744
745    private static void extractNativeLibraries(File packageDir, String abiOverride)
746            throws PackageManagerException {
747        if (LOGD) Slog.v(TAG, "extractNativeLibraries()");
748
749        // Always start from a clean slate
750        final File libDir = new File(packageDir, NativeLibraryHelper.LIB_DIR_NAME);
751        NativeLibraryHelper.removeNativeBinariesFromDirLI(libDir, true);
752
753        NativeLibraryHelper.Handle handle = null;
754        try {
755            handle = NativeLibraryHelper.Handle.create(packageDir);
756            final int res = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libDir,
757                    abiOverride);
758            if (res != PackageManager.INSTALL_SUCCEEDED) {
759                throw new PackageManagerException(res,
760                        "Failed to extract native libraries, res=" + res);
761            }
762        } catch (IOException e) {
763            throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
764                    "Failed to extract native libraries", e);
765        } finally {
766            IoUtils.closeQuietly(handle);
767        }
768    }
769
770    private static void resizeContainer(String cid, long targetSize)
771            throws PackageManagerException {
772        String path = PackageHelper.getSdDir(cid);
773        if (path == null) {
774            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
775                    "Failed to find mounted " + cid);
776        }
777
778        final long currentSize = new File(path).getTotalSpace();
779        if (currentSize > targetSize) {
780            Slog.w(TAG, "Current size " + currentSize + " is larger than target size "
781                    + targetSize + "; skipping resize");
782            return;
783        }
784
785        if (!PackageHelper.unMountSdDir(cid)) {
786            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
787                    "Failed to unmount " + cid + " before resize");
788        }
789
790        if (!PackageHelper.resizeSdDir(targetSize, cid,
791                PackageManagerService.getEncryptKey())) {
792            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
793                    "Failed to resize " + cid + " to " + targetSize + " bytes");
794        }
795
796        path = PackageHelper.mountSdDir(cid, PackageManagerService.getEncryptKey(),
797                Process.SYSTEM_UID, false);
798        if (path == null) {
799            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
800                    "Failed to mount " + cid + " after resize");
801        }
802    }
803
804    private void finalizeAndFixContainer(String cid) throws PackageManagerException {
805        if (!PackageHelper.finalizeSdDir(cid)) {
806            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
807                    "Failed to finalize container " + cid);
808        }
809
810        final int uid = mPm.getPackageUid(PackageManagerService.DEFAULT_CONTAINER_PACKAGE,
811                UserHandle.USER_OWNER);
812        final int gid = UserHandle.getSharedAppGid(uid);
813        if (!PackageHelper.fixSdPermissions(cid, gid, null)) {
814            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
815                    "Failed to fix permissions on container " + cid);
816        }
817    }
818
819    void setPermissionsResult(boolean accepted) {
820        if (!mSealed) {
821            throw new SecurityException("Must be sealed to accept permissions");
822        }
823
824        if (accepted) {
825            // Mark and kick off another install pass
826            mPermissionsAccepted = true;
827            mHandler.obtainMessage(MSG_COMMIT).sendToTarget();
828        } else {
829            destroyInternal();
830            dispatchSessionFinished(INSTALL_FAILED_ABORTED, "User rejected permissions", null);
831        }
832    }
833
834    public void open() {
835        if (mOpenCount.getAndIncrement() == 0) {
836            mCallback.onSessionActiveChanged(this, true);
837        }
838    }
839
840    @Override
841    public void close() {
842        if (mOpenCount.decrementAndGet() == 0) {
843            mCallback.onSessionActiveChanged(this, false);
844        }
845    }
846
847    @Override
848    public void abandon() {
849        destroyInternal();
850        dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null);
851    }
852
853    private void dispatchSessionFinished(int returnCode, String msg, Bundle extras) {
854        mFinalStatus = returnCode;
855        mFinalMessage = msg;
856
857        if (mRemoteObserver != null) {
858            try {
859                mRemoteObserver.onPackageInstalled(mPackageName, returnCode, msg, extras);
860            } catch (RemoteException ignored) {
861            }
862        }
863
864        final boolean success = (returnCode == PackageManager.INSTALL_SUCCEEDED);
865        mCallback.onSessionFinished(this, success);
866    }
867
868    private void destroyInternal() {
869        synchronized (mLock) {
870            mSealed = true;
871            mDestroyed = true;
872        }
873        if (stageDir != null) {
874            FileUtils.deleteContents(stageDir);
875            stageDir.delete();
876        }
877        if (stageCid != null) {
878            PackageHelper.destroySdDir(stageCid);
879        }
880    }
881
882    void dump(IndentingPrintWriter pw) {
883        synchronized (mLock) {
884            dumpLocked(pw);
885        }
886    }
887
888    private void dumpLocked(IndentingPrintWriter pw) {
889        pw.println("Session " + sessionId + ":");
890        pw.increaseIndent();
891
892        pw.printPair("userId", userId);
893        pw.printPair("installerPackageName", installerPackageName);
894        pw.printPair("installerUid", installerUid);
895        pw.printPair("createdMillis", createdMillis);
896        pw.printPair("stageDir", stageDir);
897        pw.printPair("stageCid", stageCid);
898        pw.println();
899
900        params.dump(pw);
901
902        pw.printPair("mClientProgress", mClientProgress);
903        pw.printPair("mProgress", mProgress);
904        pw.printPair("mSealed", mSealed);
905        pw.printPair("mPermissionsAccepted", mPermissionsAccepted);
906        pw.printPair("mDestroyed", mDestroyed);
907        pw.printPair("mBridges", mBridges.size());
908        pw.printPair("mFinalStatus", mFinalStatus);
909        pw.printPair("mFinalMessage", mFinalMessage);
910        pw.println();
911
912        pw.decreaseIndent();
913    }
914}
915