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