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