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