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