2 * Copyright (C) 2014 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.server.pm;
19 import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
20 import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
21 import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
22 import static android.content.pm.PackageManager.INSTALL_FAILED_PACKAGE_CHANGED;
23 import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
25 import android.content.pm.ApplicationInfo;
26 import android.content.pm.IPackageInstallObserver2;
27 import android.content.pm.IPackageInstallerSession;
28 import android.content.pm.PackageInstallerParams;
29 import android.content.pm.PackageManager;
30 import android.content.pm.PackageParser;
31 import android.content.pm.PackageParser.PackageLite;
32 import android.content.pm.Signature;
33 import android.os.Build;
34 import android.os.Bundle;
35 import android.os.FileBridge;
36 import android.os.FileUtils;
37 import android.os.Handler;
38 import android.os.Looper;
39 import android.os.Message;
40 import android.os.ParcelFileDescriptor;
41 import android.os.RemoteException;
42 import android.os.SELinux;
43 import android.system.ErrnoException;
44 import android.system.OsConstants;
45 import android.system.StructStat;
46 import android.util.ArraySet;
47 import android.util.Slog;
49 import com.android.internal.content.NativeLibraryHelper;
50 import com.android.internal.util.ArrayUtils;
51 import com.android.internal.util.Preconditions;
53 import libcore.io.IoUtils;
54 import libcore.io.Libcore;
55 import libcore.io.Streams;
58 import java.io.FileDescriptor;
59 import java.io.FileInputStream;
60 import java.io.FileOutputStream;
61 import java.io.IOException;
62 import java.util.ArrayList;
64 public class PackageInstallerSession extends IPackageInstallerSession.Stub {
65 private static final String TAG = "PackageInstaller";
67 private final PackageInstallerService.Callback mCallback;
68 private final PackageManagerService mPm;
69 private final Handler mHandler;
71 public final int sessionId;
72 public final int userId;
73 public final String installerPackageName;
74 /** UID not persisted */
75 public final int installerUid;
76 public final PackageInstallerParams params;
77 public final long createdMillis;
78 public final File sessionDir;
80 private static final int MSG_INSTALL = 0;
82 private Handler.Callback mHandlerCallback = new Handler.Callback() {
84 public boolean handleMessage(Message msg) {
85 synchronized (mLock) {
86 if (msg.obj != null) {
87 mRemoteObserver = (IPackageInstallObserver2) msg.obj;
92 } catch (InstallFailedException e) {
93 Slog.e(TAG, "Install failed: " + e);
95 mRemoteObserver.packageInstalled(mPackageName, null, e.error);
96 } catch (RemoteException ignored) {
105 private final Object mLock = new Object();
107 private int mProgress;
109 private String mPackageName;
110 private int mVersionCode;
111 private Signature[] mSignatures;
113 private boolean mMutationsAllowed;
114 private boolean mVerifierConfirmed;
115 private boolean mPermissionsConfirmed;
116 private boolean mInvalid;
118 private ArrayList<FileBridge> mBridges = new ArrayList<>();
120 private IPackageInstallObserver2 mRemoteObserver;
122 public PackageInstallerSession(PackageInstallerService.Callback callback,
123 PackageManagerService pm, int sessionId, int userId, String installerPackageName,
124 int installerUid, PackageInstallerParams params, long createdMillis, File sessionDir,
126 mCallback = callback;
128 mHandler = new Handler(looper, mHandlerCallback);
130 this.sessionId = sessionId;
131 this.userId = userId;
132 this.installerPackageName = installerPackageName;
133 this.installerUid = installerUid;
134 this.params = params;
135 this.createdMillis = createdMillis;
136 this.sessionDir = sessionDir;
138 // Check against any explicitly provided signatures
139 mSignatures = params.signatures;
141 // TODO: splice in flag when restoring persisted session
142 mMutationsAllowed = true;
144 if (pm.checkPermission(android.Manifest.permission.INSTALL_PACKAGES, installerPackageName)
145 == PackageManager.PERMISSION_GRANTED) {
146 mPermissionsConfirmed = true;
151 public void updateProgress(int progress) {
152 mProgress = progress;
153 mCallback.onProgressChanged(this);
157 public ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes) {
158 // TODO: relay over to DCS when installing to ASEC
160 // Quick sanity check of state, and allocate a pipe for ourselves. We
161 // then do heavy disk allocation outside the lock, but this open pipe
162 // will block any attempted install transitions.
163 final FileBridge bridge;
164 synchronized (mLock) {
165 if (!mMutationsAllowed) {
166 throw new IllegalStateException("Mutations not allowed");
169 bridge = new FileBridge();
170 mBridges.add(bridge);
174 // Use installer provided name for now; we always rename later
175 if (!FileUtils.isValidExtFilename(name)) {
176 throw new IllegalArgumentException("Invalid name: " + name);
178 final File target = new File(sessionDir, name);
180 final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(),
181 OsConstants.O_CREAT | OsConstants.O_WRONLY, 00700);
183 // If caller specified a total length, allocate it for them. Free up
184 // cache space to grow, if needed.
185 if (lengthBytes > 0) {
186 final StructStat stat = Libcore.os.fstat(targetFd);
187 final long deltaBytes = lengthBytes - stat.st_size;
188 if (deltaBytes > 0) {
189 mPm.freeStorage(deltaBytes);
191 Libcore.os.posix_fallocate(targetFd, 0, lengthBytes);
194 if (offsetBytes > 0) {
195 Libcore.os.lseek(targetFd, offsetBytes, OsConstants.SEEK_SET);
198 bridge.setTargetFile(targetFd);
200 return new ParcelFileDescriptor(bridge.getClientSocket());
202 } catch (ErrnoException e) {
203 throw new IllegalStateException("Failed to write", e);
204 } catch (IOException e) {
205 throw new IllegalStateException("Failed to write", e);
210 public void install(IPackageInstallObserver2 observer) {
211 Preconditions.checkNotNull(observer);
212 mHandler.obtainMessage(MSG_INSTALL, observer).sendToTarget();
215 private void installLocked() throws InstallFailedException {
217 throw new InstallFailedException(INSTALL_FAILED_ALREADY_EXISTS, "Invalid session");
220 // Verify that all writers are hands-off
221 if (mMutationsAllowed) {
222 for (FileBridge bridge : mBridges) {
223 if (!bridge.isClosed()) {
224 throw new InstallFailedException(INSTALL_FAILED_PACKAGE_CHANGED,
228 mMutationsAllowed = false;
230 // TODO: persist disabled mutations before going forward, since
231 // beyond this point we may have hardlinks to the valid install
234 // Verify that stage looks sane with respect to existing application.
235 // This currently only ensures packageName, versionCode, and certificate
237 validateInstallLocked();
239 Preconditions.checkNotNull(mPackageName);
240 Preconditions.checkNotNull(mSignatures);
242 if (!mVerifierConfirmed) {
243 // TODO: async communication with verifier
244 // when they confirm, we'll kick off another install() pass
245 mVerifierConfirmed = true;
248 if (!mPermissionsConfirmed) {
249 // TODO: async confirm permissions with user
250 // when they confirm, we'll kick off another install() pass
251 mPermissionsConfirmed = true;
254 // Unpack any native libraries contained in this session
255 unpackNativeLibraries();
257 // Inherit any packages and native libraries from existing install that
258 // haven't been overridden.
259 if (!params.fullInstall) {
260 spliceExistingFilesIntoStage();
263 // TODO: for ASEC based applications, grow and stream in packages
265 // We've reached point of no return; call into PMS to install the stage.
266 // Regardless of success or failure we always destroy session.
267 final IPackageInstallObserver2 remoteObserver = mRemoteObserver;
268 final IPackageInstallObserver2 localObserver = new IPackageInstallObserver2.Stub() {
270 public void packageInstalled(String basePackageName, Bundle extras, int returnCode)
271 throws RemoteException {
273 remoteObserver.packageInstalled(basePackageName, extras, returnCode);
277 mPm.installStage(mPackageName, this.sessionDir, localObserver, params.installFlags);
281 * Validate install by confirming that all application packages are have
282 * consistent package name, version code, and signing certificates.
284 * Renames package files in stage to match split names defined inside.
286 private void validateInstallLocked() throws InstallFailedException {
291 final File[] files = sessionDir.listFiles();
292 if (ArrayUtils.isEmpty(files)) {
293 throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, "No packages staged");
296 final ArraySet<String> seenSplits = new ArraySet<>();
298 // Verify that all staged packages are internally consistent
299 for (File file : files) {
300 final PackageLite info = PackageParser.parsePackageLite(file.getAbsolutePath(),
301 PackageParser.PARSE_GET_SIGNATURES);
303 throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
304 "Failed to parse " + file);
307 if (!seenSplits.add(info.splitName)) {
308 throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
309 "Split " + info.splitName + " was defined multiple times");
312 // Use first package to define unknown values
313 if (mPackageName != null) {
314 mPackageName = info.packageName;
315 mVersionCode = info.versionCode;
317 if (mSignatures != null) {
318 mSignatures = info.signatures;
321 assertPackageConsistent(String.valueOf(file), info.packageName, info.versionCode,
324 // Take this opportunity to enforce uniform naming
326 if (info.splitName == null) {
327 name = info.packageName + ".apk";
329 name = info.packageName + "-" + info.splitName + ".apk";
331 if (!FileUtils.isValidExtFilename(name)) {
332 throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
333 "Invalid filename: " + name);
335 if (!file.getName().equals(name)) {
336 file.renameTo(new File(file.getParentFile(), name));
340 // TODO: shift package signature verification to installer; we're
341 // currently relying on PMS to do this.
342 // TODO: teach about compatible upgrade keysets.
344 if (params.fullInstall) {
345 // Full installs must include a base package
346 if (!seenSplits.contains(null)) {
347 throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
348 "Full install must include a base package");
352 // Partial installs must be consistent with existing install.
353 final ApplicationInfo app = mPm.getApplicationInfo(mPackageName, 0, userId);
355 throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
356 "Missing existing base package for " + mPackageName);
359 final PackageLite info = PackageParser.parsePackageLite(app.sourceDir,
360 PackageParser.PARSE_GET_SIGNATURES);
362 throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
363 "Failed to parse existing base " + app.sourceDir);
366 assertPackageConsistent("Existing base", info.packageName, info.versionCode,
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);
377 if (mVersionCode != versionCode) {
378 throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, tag
379 + " version code " + versionCode + " inconsistent with "
382 if (!Signature.areExactMatch(mSignatures, signatures)) {
383 throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
384 tag + " signatures are inconsistent");
389 * Application is already installed; splice existing files that haven't been
390 * overridden into our stage.
392 private void spliceExistingFilesIntoStage() throws InstallFailedException {
393 final ApplicationInfo app = mPm.getApplicationInfo(mPackageName, 0, userId);
394 final File existingDir = new File(app.sourceDir).getParentFile();
397 linkTreeIgnoringExisting(existingDir, sessionDir);
398 } catch (ErrnoException e) {
399 throw new InstallFailedException(INSTALL_FAILED_INTERNAL_ERROR,
400 "Failed to splice into stage");
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
409 private void linkTreeIgnoringExisting(File sourceDir, File targetDir) throws ErrnoException {
410 final File[] sourceContents = sourceDir.listFiles();
411 if (ArrayUtils.isEmpty(sourceContents)) return;
413 for (File sourceFile : sourceContents) {
414 final File targetFile = new File(targetDir, sourceFile.getName());
416 if (sourceFile.isDirectory()) {
418 linkTreeIgnoringExisting(sourceFile, targetFile);
420 Libcore.os.link(sourceFile.getAbsolutePath(), targetFile.getAbsolutePath());
425 private void unpackNativeLibraries() throws InstallFailedException {
426 final File libDir = new File(sessionDir, "lib");
428 if (!libDir.mkdir()) {
429 throw new InstallFailedException(INSTALL_FAILED_INTERNAL_ERROR,
430 "Failed to create " + libDir);
434 Libcore.os.chmod(libDir.getAbsolutePath(), 0755);
435 } catch (ErrnoException e) {
436 throw new InstallFailedException(INSTALL_FAILED_INTERNAL_ERROR,
437 "Failed to prepare " + libDir + ": " + e);
440 if (!SELinux.restorecon(libDir)) {
441 throw new InstallFailedException(INSTALL_FAILED_INTERNAL_ERROR,
442 "Failed to set context on " + libDir);
445 // Unpack all native libraries under stage
446 final File[] files = sessionDir.listFiles();
447 if (ArrayUtils.isEmpty(files)) {
448 throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, "No packages staged");
451 for (File file : files) {
452 final NativeLibraryHelper.ApkHandle handle = new NativeLibraryHelper.ApkHandle(file);
454 final int abiIndex = NativeLibraryHelper.findSupportedAbi(handle,
455 Build.SUPPORTED_ABIS);
457 int copyRet = NativeLibraryHelper.copyNativeBinariesIfNeededLI(handle, libDir,
458 Build.SUPPORTED_ABIS[abiIndex]);
459 if (copyRet != INSTALL_SUCCEEDED) {
460 throw new InstallFailedException(copyRet,
461 "Failed to copy native libraries for " + file);
463 } else if (abiIndex != PackageManager.NO_NATIVE_LIBRARIES) {
464 throw new InstallFailedException(abiIndex,
465 "Failed to copy native libraries for " + file);
474 public void destroy() {
476 synchronized (mLock) {
479 FileUtils.deleteContents(sessionDir);
482 mCallback.onSessionInvalid(this);
486 private class InstallFailedException extends Exception {
487 private final int error;
489 public InstallFailedException(int error, String detailMessage) {
490 super(detailMessage);