PackageInstallerSession.java revision 941a8ba1a6043cf84a7bf622e44a0b4f7abd0178
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.open = 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        mProgress = MathUtils.constrain(mClientProgress * 0.8f, 0f, 0.8f);
298    }
299
300    private void maybePublishProgress() {
301        // Only publish when meaningful change
302        if (Math.abs(mProgress - mReportedProgress) > 0.01) {
303            mReportedProgress = mProgress;
304            mCallback.onSessionProgressChanged(this, mProgress);
305        }
306    }
307
308    @Override
309    public String[] getNames() {
310        assertNotSealed("getNames");
311        try {
312            return resolveStageDir().list();
313        } catch (IOException e) {
314            throw ExceptionUtils.wrap(e);
315        }
316    }
317
318    @Override
319    public ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes) {
320        try {
321            return openWriteInternal(name, offsetBytes, lengthBytes);
322        } catch (IOException e) {
323            throw ExceptionUtils.wrap(e);
324        }
325    }
326
327    private ParcelFileDescriptor openWriteInternal(String name, long offsetBytes, long lengthBytes)
328            throws IOException {
329        // Quick sanity check of state, and allocate a pipe for ourselves. We
330        // then do heavy disk allocation outside the lock, but this open pipe
331        // will block any attempted install transitions.
332        final FileBridge bridge;
333        synchronized (mLock) {
334            assertNotSealed("openWrite");
335
336            bridge = new FileBridge();
337            mBridges.add(bridge);
338        }
339
340        try {
341            // Use installer provided name for now; we always rename later
342            if (!FileUtils.isValidExtFilename(name)) {
343                throw new IllegalArgumentException("Invalid name: " + name);
344            }
345            final File target = new File(resolveStageDir(), name);
346
347            // TODO: this should delegate to DCS so the system process avoids
348            // holding open FDs into containers.
349            final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(),
350                    O_CREAT | O_WRONLY, 0644);
351            Os.chmod(target.getAbsolutePath(), 0644);
352
353            // If caller specified a total length, allocate it for them. Free up
354            // cache space to grow, if needed.
355            if (lengthBytes > 0) {
356                final StructStat stat = Libcore.os.fstat(targetFd);
357                final long deltaBytes = lengthBytes - stat.st_size;
358                // Only need to free up space when writing to internal stage
359                if (stageDir != null && deltaBytes > 0) {
360                    mPm.freeStorage(deltaBytes);
361                }
362                Libcore.os.posix_fallocate(targetFd, 0, lengthBytes);
363            }
364
365            if (offsetBytes > 0) {
366                Libcore.os.lseek(targetFd, offsetBytes, OsConstants.SEEK_SET);
367            }
368
369            bridge.setTargetFile(targetFd);
370            bridge.start();
371            return new ParcelFileDescriptor(bridge.getClientSocket());
372
373        } catch (ErrnoException e) {
374            throw e.rethrowAsIOException();
375        }
376    }
377
378    @Override
379    public ParcelFileDescriptor openRead(String name) {
380        try {
381            return openReadInternal(name);
382        } catch (IOException e) {
383            throw ExceptionUtils.wrap(e);
384        }
385    }
386
387    private ParcelFileDescriptor openReadInternal(String name) throws IOException {
388        assertNotSealed("openRead");
389
390        try {
391            if (!FileUtils.isValidExtFilename(name)) {
392                throw new IllegalArgumentException("Invalid name: " + name);
393            }
394            final File target = new File(resolveStageDir(), name);
395
396            final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(), O_RDONLY, 0);
397            return new ParcelFileDescriptor(targetFd);
398
399        } catch (ErrnoException e) {
400            throw e.rethrowAsIOException();
401        }
402    }
403
404    @Override
405    public void commit(IntentSender statusReceiver) {
406        Preconditions.checkNotNull(statusReceiver);
407
408        final PackageInstallObserverAdapter adapter = new PackageInstallObserverAdapter(mContext,
409                statusReceiver, sessionId);
410        mHandler.obtainMessage(MSG_COMMIT, adapter.getBinder()).sendToTarget();
411    }
412
413    private void commitLocked() throws PackageManagerException {
414        if (mDestroyed) {
415            throw new PackageManagerException(INSTALL_FAILED_ALREADY_EXISTS, "Invalid session");
416        }
417
418        // Verify that all writers are hands-off
419        if (!mSealed) {
420            for (FileBridge bridge : mBridges) {
421                if (!bridge.isClosed()) {
422                    throw new PackageManagerException(INSTALL_FAILED_PACKAGE_CHANGED,
423                            "Files still open");
424                }
425            }
426            mSealed = true;
427
428            // Persist the fact that we've sealed ourselves to prevent mutations
429            // of any hard links we create below.
430            mCallback.onSessionSealed(this);
431        }
432
433        try {
434            resolveStageDir();
435        } catch (IOException e) {
436            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
437                    "Failed to resolve stage location", e);
438        }
439
440        // Verify that stage looks sane with respect to existing application.
441        // This currently only ensures packageName, versionCode, and certificate
442        // consistency.
443        validateInstallLocked();
444
445        Preconditions.checkNotNull(mPackageName);
446        Preconditions.checkNotNull(mSignatures);
447        Preconditions.checkNotNull(mResolvedBaseFile);
448
449        if (!mPermissionsAccepted) {
450            // User needs to accept permissions; give installer an intent they
451            // can use to involve user.
452            final Intent intent = new Intent(PackageInstaller.ACTION_CONFIRM_PERMISSIONS);
453            intent.setPackage("com.android.packageinstaller");
454            intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
455            try {
456                mRemoteObserver.onUserActionRequired(intent);
457            } catch (RemoteException ignored) {
458            }
459            return;
460        }
461
462        if (stageCid != null) {
463            // Figure out the final installed size and resize the container once
464            // and for all. Internally the parser handles straddling between two
465            // locations when inheriting.
466            final long finalSize = calculateInstalledSize();
467            resizeContainer(stageCid, finalSize);
468        }
469
470        // Inherit any packages and native libraries from existing install that
471        // haven't been overridden.
472        if (params.mode == SessionParams.MODE_INHERIT_EXISTING) {
473            try {
474                if (stageCid != null) {
475                    // TODO: this should delegate to DCS so the system process
476                    // avoids holding open FDs into containers.
477                    copyFiles(mResolvedInheritedFiles, resolveStageDir());
478                } else {
479                    linkFiles(mResolvedInheritedFiles, resolveStageDir());
480                }
481            } catch (IOException e) {
482                throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
483                        "Failed to inherit existing install", e);
484            }
485        }
486
487        // TODO: surface more granular state from dexopt
488        mCallback.onSessionProgressChanged(this, 0.9f);
489
490        // Unpack native libraries
491        extractNativeLibraries(mResolvedStageDir, params.abiOverride);
492
493        // Container is ready to go, let's seal it up!
494        if (stageCid != null) {
495            finalizeAndFixContainer(stageCid);
496        }
497
498        // We've reached point of no return; call into PMS to install the stage.
499        // Regardless of success or failure we always destroy session.
500        final IPackageInstallObserver2 localObserver = new IPackageInstallObserver2.Stub() {
501            @Override
502            public void onUserActionRequired(Intent intent) {
503                throw new IllegalStateException();
504            }
505
506            @Override
507            public void onPackageInstalled(String basePackageName, int returnCode, String msg,
508                    Bundle extras) {
509                destroyInternal();
510                dispatchSessionFinished(returnCode, msg, extras);
511            }
512        };
513
514        mPm.installStage(mPackageName, stageDir, stageCid, localObserver,
515                params, installerPackageName, installerUid, new UserHandle(userId));
516    }
517
518    /**
519     * Validate install by confirming that all application packages are have
520     * consistent package name, version code, and signing certificates.
521     * <p>
522     * Clears and populates {@link #mResolvedBaseFile},
523     * {@link #mResolvedStagedFiles}, and {@link #mResolvedInheritedFiles}.
524     * <p>
525     * Renames package files in stage to match split names defined inside.
526     * <p>
527     * Note that upgrade compatibility is still performed by
528     * {@link PackageManagerService}.
529     */
530    private void validateInstallLocked() throws PackageManagerException {
531        mPackageName = null;
532        mVersionCode = -1;
533        mSignatures = null;
534
535        mResolvedBaseFile = null;
536        mResolvedStagedFiles.clear();
537        mResolvedInheritedFiles.clear();
538
539        final File[] files = mResolvedStageDir.listFiles();
540        if (ArrayUtils.isEmpty(files)) {
541            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "No packages staged");
542        }
543
544        // Verify that all staged packages are internally consistent
545        final ArraySet<String> stagedSplits = new ArraySet<>();
546        for (File file : files) {
547
548            // Installers can't stage directories, so it's fine to ignore
549            // entries like "lost+found".
550            if (file.isDirectory()) continue;
551
552            final ApkLite apk;
553            try {
554                apk = PackageParser.parseApkLite(file, PackageParser.PARSE_COLLECT_CERTIFICATES);
555            } catch (PackageParserException e) {
556                throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
557                        "Failed to parse " + file + ": " + e);
558            }
559
560            if (!stagedSplits.add(apk.splitName)) {
561                throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
562                        "Split " + apk.splitName + " was defined multiple times");
563            }
564
565            // Use first package to define unknown values
566            if (mPackageName == null) {
567                mPackageName = apk.packageName;
568                mVersionCode = apk.versionCode;
569            }
570            if (mSignatures == null) {
571                mSignatures = apk.signatures;
572            }
573
574            assertApkConsistent(String.valueOf(file), apk);
575
576            // Take this opportunity to enforce uniform naming
577            final String targetName;
578            if (apk.splitName == null) {
579                targetName = "base.apk";
580            } else {
581                targetName = "split_" + apk.splitName + ".apk";
582            }
583            if (!FileUtils.isValidExtFilename(targetName)) {
584                throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
585                        "Invalid filename: " + targetName);
586            }
587
588            final File targetFile = new File(mResolvedStageDir, targetName);
589            if (!file.equals(targetFile)) {
590                file.renameTo(targetFile);
591            }
592
593            // Base is coming from session
594            if (apk.splitName == null) {
595                mResolvedBaseFile = targetFile;
596            }
597
598            mResolvedStagedFiles.add(targetFile);
599        }
600
601        if (params.mode == SessionParams.MODE_FULL_INSTALL) {
602            // Full installs must include a base package
603            if (!stagedSplits.contains(null)) {
604                throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
605                        "Full install must include a base package");
606            }
607
608        } else {
609            // Partial installs must be consistent with existing install
610            final ApplicationInfo app = mPm.getApplicationInfo(mPackageName, 0, userId);
611            if (app == null) {
612                throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
613                        "Missing existing base package for " + mPackageName);
614            }
615
616            final PackageLite existing;
617            final ApkLite existingBase;
618            try {
619                existing = PackageParser.parsePackageLite(new File(app.getCodePath()), 0);
620                existingBase = PackageParser.parseApkLite(new File(app.getBaseCodePath()),
621                        PackageParser.PARSE_COLLECT_CERTIFICATES);
622            } catch (PackageParserException e) {
623                throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
624                        "Failed to parse existing package " + app.getCodePath() + ": " + e);
625            }
626
627            assertApkConsistent("Existing base", existingBase);
628
629            // Inherit base if not overridden
630            if (mResolvedBaseFile == null) {
631                mResolvedBaseFile = new File(app.getBaseCodePath());
632                mResolvedInheritedFiles.add(mResolvedBaseFile);
633            }
634
635            // Inherit splits if not overridden
636            if (!ArrayUtils.isEmpty(existing.splitNames)) {
637                for (int i = 0; i < existing.splitNames.length; i++) {
638                    final String splitName = existing.splitNames[i];
639                    final File splitFile = new File(existing.splitCodePaths[i]);
640
641                    if (!stagedSplits.contains(splitName)) {
642                        mResolvedInheritedFiles.add(splitFile);
643                    }
644                }
645            }
646        }
647    }
648
649    private void assertApkConsistent(String tag, ApkLite apk) throws PackageManagerException {
650        if (!mPackageName.equals(apk.packageName)) {
651            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag + " package "
652                    + apk.packageName + " inconsistent with " + mPackageName);
653        }
654        if (mVersionCode != apk.versionCode) {
655            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag
656                    + " version code " + apk.versionCode + " inconsistent with "
657                    + mVersionCode);
658        }
659        if (!Signature.areExactMatch(mSignatures, apk.signatures)) {
660            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
661                    tag + " signatures are inconsistent");
662        }
663    }
664
665    /**
666     * Calculate the final install footprint size, combining both staged and
667     * existing APKs together and including unpacked native code from both.
668     */
669    private long calculateInstalledSize() throws PackageManagerException {
670        Preconditions.checkNotNull(mResolvedBaseFile);
671
672        final ApkLite baseApk;
673        try {
674            baseApk = PackageParser.parseApkLite(mResolvedBaseFile, 0);
675        } catch (PackageParserException e) {
676            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
677                    "Failed to parse base package " + mResolvedBaseFile + ": " + e);
678        }
679
680        final List<String> splitPaths = new ArrayList<>();
681        for (File file : mResolvedStagedFiles) {
682            if (mResolvedBaseFile.equals(file)) continue;
683            splitPaths.add(file.getAbsolutePath());
684        }
685        for (File file : mResolvedInheritedFiles) {
686            if (mResolvedBaseFile.equals(file)) continue;
687            splitPaths.add(file.getAbsolutePath());
688        }
689
690        // This is kind of hacky; we're creating a half-parsed package that is
691        // straddled between the inherited and staged APKs.
692        final PackageLite pkg = new PackageLite(null, baseApk, null,
693                splitPaths.toArray(new String[splitPaths.size()]));
694        final boolean isForwardLocked =
695                (params.installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
696
697        try {
698            return PackageHelper.calculateInstalledSize(pkg, isForwardLocked, params.abiOverride);
699        } catch (IOException e) {
700            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
701                    "Failed to calculate install size", e);
702        }
703    }
704
705    private static void linkFiles(List<File> fromFiles, File toDir) throws IOException {
706        for (File fromFile : fromFiles) {
707            final File toFile = new File(toDir, fromFile.getName());
708            try {
709                if (LOGD) Slog.d(TAG, "Linking " + fromFile + " to " + toFile);
710                Os.link(fromFile.getAbsolutePath(), toFile.getAbsolutePath());
711            } catch (ErrnoException e) {
712                throw new IOException("Failed to link " + fromFile + " to " + toFile, e);
713            }
714        }
715        Slog.d(TAG, "Linked " + fromFiles.size() + " files into " + toDir);
716    }
717
718    private static void copyFiles(List<File> fromFiles, File toDir) throws IOException {
719        // Remove any partial files from previous attempt
720        for (File file : toDir.listFiles()) {
721            if (file.getName().endsWith(".tmp")) {
722                file.delete();
723            }
724        }
725
726        for (File fromFile : fromFiles) {
727            final File tmpFile = File.createTempFile("inherit", ".tmp", toDir);
728            if (LOGD) Slog.d(TAG, "Copying " + fromFile + " to " + tmpFile);
729            if (!FileUtils.copyFile(fromFile, tmpFile)) {
730                throw new IOException("Failed to copy " + fromFile + " to " + tmpFile);
731            }
732
733            final File toFile = new File(toDir, fromFile.getName());
734            if (LOGD) Slog.d(TAG, "Renaming " + tmpFile + " to " + toFile);
735            if (!tmpFile.renameTo(toFile)) {
736                throw new IOException("Failed to rename " + tmpFile + " to " + toFile);
737            }
738        }
739        Slog.d(TAG, "Copied " + fromFiles.size() + " files into " + toDir);
740    }
741
742    private static void extractNativeLibraries(File packageDir, String abiOverride)
743            throws PackageManagerException {
744        if (LOGD) Slog.v(TAG, "extractNativeLibraries()");
745
746        // Always start from a clean slate
747        final File libDir = new File(packageDir, NativeLibraryHelper.LIB_DIR_NAME);
748        NativeLibraryHelper.removeNativeBinariesFromDirLI(libDir, true);
749
750        NativeLibraryHelper.Handle handle = null;
751        try {
752            handle = NativeLibraryHelper.Handle.create(packageDir);
753            final int res = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libDir,
754                    abiOverride);
755            if (res != PackageManager.INSTALL_SUCCEEDED) {
756                throw new PackageManagerException(res,
757                        "Failed to extract native libraries, res=" + res);
758            }
759        } catch (IOException e) {
760            throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
761                    "Failed to extract native libraries", e);
762        } finally {
763            IoUtils.closeQuietly(handle);
764        }
765    }
766
767    private static void resizeContainer(String cid, long targetSize)
768            throws PackageManagerException {
769        String path = PackageHelper.getSdDir(cid);
770        if (path == null) {
771            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
772                    "Failed to find mounted " + cid);
773        }
774
775        final long currentSize = new File(path).getTotalSpace();
776        if (currentSize > targetSize) {
777            Slog.w(TAG, "Current size " + currentSize + " is larger than target size "
778                    + targetSize + "; skipping resize");
779            return;
780        }
781
782        if (!PackageHelper.unMountSdDir(cid)) {
783            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
784                    "Failed to unmount " + cid + " before resize");
785        }
786
787        if (!PackageHelper.resizeSdDir(targetSize, cid,
788                PackageManagerService.getEncryptKey())) {
789            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
790                    "Failed to resize " + cid + " to " + targetSize + " bytes");
791        }
792
793        path = PackageHelper.mountSdDir(cid, PackageManagerService.getEncryptKey(),
794                Process.SYSTEM_UID, false);
795        if (path == null) {
796            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
797                    "Failed to mount " + cid + " after resize");
798        }
799    }
800
801    private void finalizeAndFixContainer(String cid) throws PackageManagerException {
802        if (!PackageHelper.finalizeSdDir(cid)) {
803            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
804                    "Failed to finalize container " + cid);
805        }
806
807        final int uid = mPm.getPackageUid(PackageManagerService.DEFAULT_CONTAINER_PACKAGE,
808                UserHandle.USER_OWNER);
809        final int gid = UserHandle.getSharedAppGid(uid);
810        if (!PackageHelper.fixSdPermissions(cid, gid, null)) {
811            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
812                    "Failed to fix permissions on container " + cid);
813        }
814    }
815
816    void setPermissionsResult(boolean accepted) {
817        if (!mSealed) {
818            throw new SecurityException("Must be sealed to accept permissions");
819        }
820
821        if (accepted) {
822            // Mark and kick off another install pass
823            mPermissionsAccepted = true;
824            mHandler.obtainMessage(MSG_COMMIT).sendToTarget();
825        } else {
826            destroyInternal();
827            dispatchSessionFinished(INSTALL_FAILED_ABORTED, "User rejected permissions", null);
828        }
829    }
830
831    public void open() {
832        if (mOpenCount.getAndIncrement() == 0) {
833            mCallback.onSessionOpened(this);
834        }
835    }
836
837    @Override
838    public void close() {
839        if (mOpenCount.decrementAndGet() == 0) {
840            mCallback.onSessionClosed(this);
841        }
842    }
843
844    @Override
845    public void abandon() {
846        destroyInternal();
847        dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null);
848    }
849
850    private void dispatchSessionFinished(int returnCode, String msg, Bundle extras) {
851        mFinalStatus = returnCode;
852        mFinalMessage = msg;
853
854        if (mRemoteObserver != null) {
855            try {
856                mRemoteObserver.onPackageInstalled(mPackageName, returnCode, msg, extras);
857            } catch (RemoteException ignored) {
858            }
859        }
860
861        final boolean success = (returnCode == PackageManager.INSTALL_SUCCEEDED);
862        mCallback.onSessionFinished(this, success);
863    }
864
865    private void destroyInternal() {
866        synchronized (mLock) {
867            mSealed = true;
868            mDestroyed = true;
869        }
870        if (stageDir != null) {
871            FileUtils.deleteContents(stageDir);
872            stageDir.delete();
873        }
874        if (stageCid != null) {
875            PackageHelper.destroySdDir(stageCid);
876        }
877    }
878
879    void dump(IndentingPrintWriter pw) {
880        synchronized (mLock) {
881            dumpLocked(pw);
882        }
883    }
884
885    private void dumpLocked(IndentingPrintWriter pw) {
886        pw.println("Session " + sessionId + ":");
887        pw.increaseIndent();
888
889        pw.printPair("userId", userId);
890        pw.printPair("installerPackageName", installerPackageName);
891        pw.printPair("installerUid", installerUid);
892        pw.printPair("createdMillis", createdMillis);
893        pw.printPair("stageDir", stageDir);
894        pw.printPair("stageCid", stageCid);
895        pw.println();
896
897        params.dump(pw);
898
899        pw.printPair("mClientProgress", mClientProgress);
900        pw.printPair("mProgress", mProgress);
901        pw.printPair("mSealed", mSealed);
902        pw.printPair("mPermissionsAccepted", mPermissionsAccepted);
903        pw.printPair("mDestroyed", mDestroyed);
904        pw.printPair("mBridges", mBridges.size());
905        pw.printPair("mFinalStatus", mFinalStatus);
906        pw.printPair("mFinalMessage", mFinalMessage);
907        pw.println();
908
909        pw.decreaseIndent();
910    }
911}
912