PackageInstallerSession.java revision 9a445771f57dd15b06db0dbefd66c368d84eec2d
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_ALREADY_EXISTS;
20import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
21import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
22import static android.content.pm.PackageManager.INSTALL_FAILED_PACKAGE_CHANGED;
23import static android.system.OsConstants.O_CREAT;
24import static android.system.OsConstants.O_WRONLY;
25
26import android.content.pm.ApplicationInfo;
27import android.content.pm.IPackageInstallObserver2;
28import android.content.pm.IPackageInstallerSession;
29import android.content.pm.InstallSessionInfo;
30import android.content.pm.InstallSessionParams;
31import android.content.pm.PackageManager;
32import android.content.pm.PackageParser;
33import android.content.pm.PackageParser.ApkLite;
34import android.content.pm.PackageParser.PackageParserException;
35import android.content.pm.Signature;
36import android.os.Bundle;
37import android.os.FileBridge;
38import android.os.FileUtils;
39import android.os.Handler;
40import android.os.Looper;
41import android.os.Message;
42import android.os.ParcelFileDescriptor;
43import android.os.Process;
44import android.os.RemoteException;
45import android.os.UserHandle;
46import android.system.ErrnoException;
47import android.system.Os;
48import android.system.OsConstants;
49import android.system.StructStat;
50import android.util.ArraySet;
51import android.util.ExceptionUtils;
52import android.util.MathUtils;
53import android.util.Slog;
54
55import com.android.internal.util.ArrayUtils;
56import com.android.internal.util.IndentingPrintWriter;
57import com.android.internal.util.Preconditions;
58
59import libcore.io.Libcore;
60
61import java.io.File;
62import java.io.FileDescriptor;
63import java.io.IOException;
64import java.util.ArrayList;
65
66public class PackageInstallerSession extends IPackageInstallerSession.Stub {
67    private static final String TAG = "PackageInstaller";
68    private static final boolean LOGD = true;
69
70    // TODO: enforce INSTALL_ALLOW_TEST
71    // TODO: enforce INSTALL_ALLOW_DOWNGRADE
72    // TODO: handle INSTALL_EXTERNAL, INSTALL_INTERNAL
73
74    private final PackageInstallerService.Callback mCallback;
75    private final PackageManagerService mPm;
76    private final Handler mHandler;
77
78    public final int sessionId;
79    public final int userId;
80    public final String installerPackageName;
81    /** UID not persisted */
82    public final int installerUid;
83    public final InstallSessionParams params;
84    public final long createdMillis;
85    public final File sessionStageDir;
86
87    private static final int MSG_INSTALL = 0;
88
89    private Handler.Callback mHandlerCallback = new Handler.Callback() {
90        @Override
91        public boolean handleMessage(Message msg) {
92            synchronized (mLock) {
93                if (msg.obj != null) {
94                    mRemoteObserver = (IPackageInstallObserver2) msg.obj;
95                }
96
97                try {
98                    installLocked();
99                } catch (PackageManagerException e) {
100                    Slog.e(TAG, "Install failed: " + e);
101                    destroyInternal();
102                    try {
103                        mRemoteObserver.packageInstalled(mPackageName, null, e.error,
104                                e.getMessage());
105                    } catch (RemoteException ignored) {
106                    }
107                    mCallback.onSessionFinished(PackageInstallerSession.this, false);
108                }
109
110                return true;
111            }
112        }
113    };
114
115    private final Object mLock = new Object();
116
117    private int mClientProgress;
118    private int mProgress = 0;
119
120    private String mPackageName;
121    private int mVersionCode;
122    private Signature[] mSignatures;
123
124    private boolean mMutationsAllowed;
125    private boolean mPermissionsConfirmed;
126    private boolean mInvalid;
127
128    private ArrayList<FileBridge> mBridges = new ArrayList<>();
129
130    private IPackageInstallObserver2 mRemoteObserver;
131
132    public PackageInstallerSession(PackageInstallerService.Callback callback,
133            PackageManagerService pm, int sessionId, int userId, String installerPackageName,
134            int installerUid, InstallSessionParams params, long createdMillis, File sessionStageDir,
135            Looper looper) {
136        mCallback = callback;
137        mPm = pm;
138        mHandler = new Handler(looper, mHandlerCallback);
139
140        this.sessionId = sessionId;
141        this.userId = userId;
142        this.installerPackageName = installerPackageName;
143        this.installerUid = installerUid;
144        this.params = params;
145        this.createdMillis = createdMillis;
146        this.sessionStageDir = sessionStageDir;
147
148        // Check against any explicitly provided signatures
149        mSignatures = params.signatures;
150
151        // TODO: splice in flag when restoring persisted session
152        mMutationsAllowed = true;
153
154        if (pm.checkPermission(android.Manifest.permission.INSTALL_PACKAGES, installerPackageName)
155                == PackageManager.PERMISSION_GRANTED) {
156            mPermissionsConfirmed = true;
157        }
158        if (installerUid == Process.SHELL_UID || installerUid == 0) {
159            mPermissionsConfirmed = true;
160        }
161    }
162
163    public InstallSessionInfo generateInfo() {
164        final InstallSessionInfo info = new InstallSessionInfo();
165
166        info.sessionId = sessionId;
167        info.installerPackageName = installerPackageName;
168        info.progress = mProgress;
169
170        info.mode = params.mode;
171        info.packageName = params.packageName;
172        info.icon = params.icon;
173        info.title = params.title;
174
175        return info;
176    }
177
178    @Override
179    public void setClientProgress(int progress) {
180        mClientProgress = progress;
181        mProgress = MathUtils.constrain(
182                (int) (((float) mClientProgress) / ((float) params.progressMax)) * 80, 0, 80);
183        mCallback.onSessionProgress(this, mProgress);
184    }
185
186    @Override
187    public void addClientProgress(int progress) {
188        setClientProgress(mClientProgress + progress);
189    }
190
191    @Override
192    public ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes) {
193        try {
194            return openWriteInternal(name, offsetBytes, lengthBytes);
195        } catch (IOException e) {
196            throw ExceptionUtils.wrap(e);
197        }
198    }
199
200    private ParcelFileDescriptor openWriteInternal(String name, long offsetBytes, long lengthBytes)
201            throws IOException {
202        // TODO: relay over to DCS when installing to ASEC
203
204        // Quick sanity check of state, and allocate a pipe for ourselves. We
205        // then do heavy disk allocation outside the lock, but this open pipe
206        // will block any attempted install transitions.
207        final FileBridge bridge;
208        synchronized (mLock) {
209            if (!mMutationsAllowed) {
210                throw new IllegalStateException("Mutations not allowed");
211            }
212
213            bridge = new FileBridge();
214            mBridges.add(bridge);
215        }
216
217        try {
218            // Use installer provided name for now; we always rename later
219            if (!FileUtils.isValidExtFilename(name)) {
220                throw new IllegalArgumentException("Invalid name: " + name);
221            }
222            final File target = new File(sessionStageDir, name);
223
224            final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(),
225                    O_CREAT | O_WRONLY, 0644);
226            Os.chmod(target.getAbsolutePath(), 0644);
227
228            // If caller specified a total length, allocate it for them. Free up
229            // cache space to grow, if needed.
230            if (lengthBytes > 0) {
231                final StructStat stat = Libcore.os.fstat(targetFd);
232                final long deltaBytes = lengthBytes - stat.st_size;
233                if (deltaBytes > 0) {
234                    mPm.freeStorage(deltaBytes);
235                }
236                Libcore.os.posix_fallocate(targetFd, 0, lengthBytes);
237            }
238
239            if (offsetBytes > 0) {
240                Libcore.os.lseek(targetFd, offsetBytes, OsConstants.SEEK_SET);
241            }
242
243            bridge.setTargetFile(targetFd);
244            bridge.start();
245            return new ParcelFileDescriptor(bridge.getClientSocket());
246
247        } catch (ErrnoException e) {
248            throw e.rethrowAsIOException();
249        }
250    }
251
252    @Override
253    public void install(IPackageInstallObserver2 observer) {
254        Preconditions.checkNotNull(observer);
255        mHandler.obtainMessage(MSG_INSTALL, observer).sendToTarget();
256    }
257
258    private void installLocked() throws PackageManagerException {
259        if (mInvalid) {
260            throw new PackageManagerException(INSTALL_FAILED_ALREADY_EXISTS, "Invalid session");
261        }
262
263        // Verify that all writers are hands-off
264        if (mMutationsAllowed) {
265            for (FileBridge bridge : mBridges) {
266                if (!bridge.isClosed()) {
267                    throw new PackageManagerException(INSTALL_FAILED_PACKAGE_CHANGED,
268                            "Files still open");
269                }
270            }
271            mMutationsAllowed = false;
272
273            // TODO: persist disabled mutations before going forward, since
274            // beyond this point we may have hardlinks to the valid install
275        }
276
277        // Verify that stage looks sane with respect to existing application.
278        // This currently only ensures packageName, versionCode, and certificate
279        // consistency.
280        validateInstallLocked();
281
282        Preconditions.checkNotNull(mPackageName);
283        Preconditions.checkNotNull(mSignatures);
284
285        if (!mPermissionsConfirmed) {
286            // TODO: async confirm permissions with user
287            // when they confirm, we'll kick off another install() pass
288            throw new SecurityException("Caller must hold INSTALL permission");
289        }
290
291        // Inherit any packages and native libraries from existing install that
292        // haven't been overridden.
293        if (params.mode == InstallSessionParams.MODE_INHERIT_EXISTING) {
294            spliceExistingFilesIntoStage();
295        }
296
297        // TODO: surface more granular state from dexopt
298        mCallback.onSessionProgress(this, 90);
299
300        // TODO: for ASEC based applications, grow and stream in packages
301
302        // We've reached point of no return; call into PMS to install the stage.
303        // Regardless of success or failure we always destroy session.
304        final IPackageInstallObserver2 remoteObserver = mRemoteObserver;
305        final IPackageInstallObserver2 localObserver = new IPackageInstallObserver2.Stub() {
306            @Override
307            public void packageInstalled(String basePackageName, Bundle extras, int returnCode,
308                    String msg) {
309                destroyInternal();
310                try {
311                    remoteObserver.packageInstalled(basePackageName, extras, returnCode, msg);
312                } catch (RemoteException ignored) {
313                }
314                final boolean success = (returnCode == PackageManager.INSTALL_SUCCEEDED);
315                mCallback.onSessionFinished(PackageInstallerSession.this, success);
316            }
317        };
318
319        mPm.installStage(mPackageName, this.sessionStageDir, localObserver, params,
320                installerPackageName, installerUid, new UserHandle(userId));
321    }
322
323    /**
324     * Validate install by confirming that all application packages are have
325     * consistent package name, version code, and signing certificates.
326     * <p>
327     * Renames package files in stage to match split names defined inside.
328     */
329    private void validateInstallLocked() throws PackageManagerException {
330        mPackageName = null;
331        mVersionCode = -1;
332
333        final File[] files = sessionStageDir.listFiles();
334        if (ArrayUtils.isEmpty(files)) {
335            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "No packages staged");
336        }
337
338        final ArraySet<String> seenSplits = new ArraySet<>();
339
340        // Verify that all staged packages are internally consistent
341        for (File file : files) {
342            final ApkLite info;
343            try {
344                info = PackageParser.parseApkLite(file, PackageParser.PARSE_GET_SIGNATURES);
345            } catch (PackageParserException e) {
346                throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
347                        "Failed to parse " + file + ": " + e);
348            }
349
350            if (!seenSplits.add(info.splitName)) {
351                throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
352                        "Split " + info.splitName + " was defined multiple times");
353            }
354
355            // Use first package to define unknown values
356            if (mPackageName == null) {
357                mPackageName = info.packageName;
358                mVersionCode = info.versionCode;
359            }
360            if (mSignatures == null) {
361                mSignatures = info.signatures;
362            }
363
364            assertPackageConsistent(String.valueOf(file), info.packageName, info.versionCode,
365                    info.signatures);
366
367            // Take this opportunity to enforce uniform naming
368            final String name;
369            if (info.splitName == null) {
370                name = "base.apk";
371            } else {
372                name = "split_" + info.splitName + ".apk";
373            }
374            if (!FileUtils.isValidExtFilename(name)) {
375                throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
376                        "Invalid filename: " + name);
377            }
378            if (!file.getName().equals(name)) {
379                file.renameTo(new File(file.getParentFile(), name));
380            }
381        }
382
383        // TODO: shift package signature verification to installer; we're
384        // currently relying on PMS to do this.
385        // TODO: teach about compatible upgrade keysets.
386
387        if (params.mode == InstallSessionParams.MODE_FULL_INSTALL) {
388            // Full installs must include a base package
389            if (!seenSplits.contains(null)) {
390                throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
391                        "Full install must include a base package");
392            }
393
394        } else {
395            // Partial installs must be consistent with existing install.
396            final ApplicationInfo app = mPm.getApplicationInfo(mPackageName, 0, userId);
397            if (app == null) {
398                throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
399                        "Missing existing base package for " + mPackageName);
400            }
401
402            final ApkLite info;
403            try {
404                info = PackageParser.parseApkLite(new File(app.getBaseCodePath()),
405                        PackageParser.PARSE_GET_SIGNATURES);
406            } catch (PackageParserException e) {
407                throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
408                        "Failed to parse existing base " + app.getBaseCodePath() + ": " + e);
409            }
410
411            assertPackageConsistent("Existing base", info.packageName, info.versionCode,
412                    info.signatures);
413        }
414    }
415
416    private void assertPackageConsistent(String tag, String packageName, int versionCode,
417            Signature[] signatures) throws PackageManagerException {
418        if (!mPackageName.equals(packageName)) {
419            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag + " package "
420                    + packageName + " inconsistent with " + mPackageName);
421        }
422        if (mVersionCode != versionCode) {
423            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag
424                    + " version code " + versionCode + " inconsistent with "
425                    + mVersionCode);
426        }
427        if (!Signature.areExactMatch(mSignatures, signatures)) {
428            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
429                    tag + " signatures are inconsistent");
430        }
431    }
432
433    /**
434     * Application is already installed; splice existing files that haven't been
435     * overridden into our stage.
436     */
437    private void spliceExistingFilesIntoStage() throws PackageManagerException {
438        final ApplicationInfo app = mPm.getApplicationInfo(mPackageName, 0, userId);
439
440        int n = 0;
441        final File[] oldFiles = new File(app.getCodePath()).listFiles();
442        if (!ArrayUtils.isEmpty(oldFiles)) {
443            for (File oldFile : oldFiles) {
444                if (!PackageParser.isApkFile(oldFile)) continue;
445
446                final File newFile = new File(sessionStageDir, oldFile.getName());
447                try {
448                    Os.link(oldFile.getAbsolutePath(), newFile.getAbsolutePath());
449                    n++;
450                } catch (ErrnoException e) {
451                    throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
452                            "Failed to splice into stage", e);
453                }
454            }
455        }
456
457        if (LOGD) Slog.d(TAG, "Spliced " + n + " existing APKs into stage");
458    }
459
460    @Override
461    public void destroy() {
462        try {
463            destroyInternal();
464        } finally {
465            mCallback.onSessionFinished(this, false);
466        }
467    }
468
469    private void destroyInternal() {
470        synchronized (mLock) {
471            mInvalid = true;
472        }
473        FileUtils.deleteContents(sessionStageDir);
474        sessionStageDir.delete();
475    }
476
477    void dump(IndentingPrintWriter pw) {
478        pw.println("Session " + sessionId + ":");
479        pw.increaseIndent();
480
481        pw.printPair("userId", userId);
482        pw.printPair("installerPackageName", installerPackageName);
483        pw.printPair("installerUid", installerUid);
484        pw.printPair("createdMillis", createdMillis);
485        pw.printPair("sessionStageDir", sessionStageDir);
486        pw.println();
487
488        params.dump(pw);
489
490        pw.printPair("mClientProgress", mClientProgress);
491        pw.printPair("mProgress", mProgress);
492        pw.printPair("mMutationsAllowed", mMutationsAllowed);
493        pw.printPair("mPermissionsConfirmed", mPermissionsConfirmed);
494        pw.printPair("mBridges", mBridges.size());
495        pw.println();
496
497        pw.decreaseIndent();
498    }
499}
500