PackageInstallerSession.java revision 3a44f3f1b446315ef894e01d2ab9b5388c2bd8c4
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.PackageLite;
32import android.content.pm.Signature;
33import android.os.Build;
34import android.os.Bundle;
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.SELinux;
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.content.NativeLibraryHelper;
49import com.android.internal.util.ArrayUtils;
50import com.android.internal.util.Preconditions;
51
52import libcore.io.IoUtils;
53import libcore.io.Libcore;
54import libcore.io.Streams;
55
56import java.io.File;
57import java.io.FileDescriptor;
58import java.io.FileInputStream;
59import java.io.FileOutputStream;
60import java.io.IOException;
61import java.util.ArrayList;
62
63public class PackageInstallerSession extends IPackageInstallerSession.Stub {
64    private static final String TAG = "PackageInstaller";
65
66    private final PackageInstallerService.Callback mCallback;
67    private final PackageManagerService mPm;
68    private final Handler mHandler;
69
70    public final int sessionId;
71    public final int userId;
72    public final String installerPackageName;
73    /** UID not persisted */
74    public final int installerUid;
75    public final PackageInstallerParams params;
76    public final long createdMillis;
77    public final File sessionDir;
78
79    private static final int MSG_INSTALL = 0;
80
81    private Handler.Callback mHandlerCallback = new Handler.Callback() {
82        @Override
83        public boolean handleMessage(Message msg) {
84            synchronized (mLock) {
85                if (msg.obj != null) {
86                    mRemoteObserver = (IPackageInstallObserver2) msg.obj;
87                }
88
89                try {
90                    installLocked();
91                } catch (InstallFailedException e) {
92                    Slog.e(TAG, "Install failed: " + e);
93                    try {
94                        mRemoteObserver.packageInstalled(mPackageName, null, e.error);
95                    } catch (RemoteException ignored) {
96                    }
97                }
98
99                return true;
100            }
101        }
102    };
103
104    private final Object mLock = new Object();
105
106    private int mProgress;
107
108    private String mPackageName;
109    private int mVersionCode;
110    private Signature[] mSignatures;
111
112    private boolean mMutationsAllowed;
113    private boolean mVerifierConfirmed;
114    private boolean mPermissionsConfirmed;
115    private boolean mInvalid;
116
117    private ArrayList<WritePipe> mPipes = new ArrayList<>();
118
119    private IPackageInstallObserver2 mRemoteObserver;
120
121    public PackageInstallerSession(PackageInstallerService.Callback callback,
122            PackageManagerService pm, int sessionId, int userId, String installerPackageName,
123            int installerUid, PackageInstallerParams params, long createdMillis, File sessionDir,
124            Looper looper) {
125        mCallback = callback;
126        mPm = pm;
127        mHandler = new Handler(looper, mHandlerCallback);
128
129        this.sessionId = sessionId;
130        this.userId = userId;
131        this.installerPackageName = installerPackageName;
132        this.installerUid = installerUid;
133        this.params = params;
134        this.createdMillis = createdMillis;
135        this.sessionDir = sessionDir;
136
137        // Check against any explicitly provided signatures
138        mSignatures = params.signatures;
139
140        // TODO: splice in flag when restoring persisted session
141        mMutationsAllowed = true;
142
143        if (pm.checkPermission(android.Manifest.permission.INSTALL_PACKAGES, installerPackageName)
144                == PackageManager.PERMISSION_GRANTED) {
145            mPermissionsConfirmed = true;
146        }
147    }
148
149    @Override
150    public void updateProgress(int progress) {
151        mProgress = progress;
152        mCallback.onProgressChanged(this);
153    }
154
155    @Override
156    public ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes) {
157        // TODO: relay over to DCS when installing to ASEC
158
159        // Quick sanity check of state, and allocate a pipe for ourselves. We
160        // then do heavy disk allocation outside the lock, but this open pipe
161        // will block any attempted install transitions.
162        final WritePipe pipe;
163        synchronized (mLock) {
164            if (!mMutationsAllowed) {
165                throw new IllegalStateException("Mutations not allowed");
166            }
167
168            pipe = new WritePipe();
169            mPipes.add(pipe);
170        }
171
172        try {
173            // Use installer provided name for now; we always rename later
174            if (!FileUtils.isValidExtFilename(name)) {
175                throw new IllegalArgumentException("Invalid name: " + name);
176            }
177            final File target = new File(sessionDir, name);
178
179            final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(),
180                    OsConstants.O_CREAT | OsConstants.O_WRONLY, 00700);
181
182            // If caller specified a total length, allocate it for them. Free up
183            // cache space to grow, if needed.
184            if (lengthBytes > 0) {
185                final StructStat stat = Libcore.os.fstat(targetFd);
186                final long deltaBytes = lengthBytes - stat.st_size;
187                if (deltaBytes > 0) {
188                    mPm.freeStorage(deltaBytes);
189                }
190                Libcore.os.posix_fallocate(targetFd, 0, lengthBytes);
191            }
192
193            if (offsetBytes > 0) {
194                Libcore.os.lseek(targetFd, offsetBytes, OsConstants.SEEK_SET);
195            }
196
197            pipe.setTargetFd(targetFd);
198            pipe.start();
199            return pipe.getWriteFd();
200
201        } catch (ErrnoException e) {
202            throw new IllegalStateException("Failed to write", e);
203        } catch (IOException e) {
204            throw new IllegalStateException("Failed to write", e);
205        }
206    }
207
208    @Override
209    public void install(IPackageInstallObserver2 observer) {
210        Preconditions.checkNotNull(observer);
211        mHandler.obtainMessage(MSG_INSTALL, observer).sendToTarget();
212    }
213
214    private void installLocked() throws InstallFailedException {
215        if (mInvalid) {
216            throw new InstallFailedException(INSTALL_FAILED_ALREADY_EXISTS, "Invalid session");
217        }
218
219        // Verify that all writers are hands-off
220        if (mMutationsAllowed) {
221            for (WritePipe pipe : mPipes) {
222                if (!pipe.isClosed()) {
223                    throw new InstallFailedException(INSTALL_FAILED_PACKAGE_CHANGED,
224                            "Files still open");
225                }
226            }
227            mMutationsAllowed = false;
228
229            // TODO: persist disabled mutations before going forward, since
230            // beyond this point we may have hardlinks to the valid install
231        }
232
233        // Verify that stage looks sane with respect to existing application.
234        // This currently only ensures packageName, versionCode, and certificate
235        // consistency.
236        validateInstallLocked();
237
238        Preconditions.checkNotNull(mPackageName);
239        Preconditions.checkNotNull(mSignatures);
240
241        if (!mVerifierConfirmed) {
242            // TODO: async communication with verifier
243            // when they confirm, we'll kick off another install() pass
244            mVerifierConfirmed = true;
245        }
246
247        if (!mPermissionsConfirmed) {
248            // TODO: async confirm permissions with user
249            // when they confirm, we'll kick off another install() pass
250            mPermissionsConfirmed = true;
251        }
252
253        // Unpack any native libraries contained in this session
254        unpackNativeLibraries();
255
256        // Inherit any packages and native libraries from existing install that
257        // haven't been overridden.
258        if (!params.fullInstall) {
259            spliceExistingFilesIntoStage();
260        }
261
262        // TODO: for ASEC based applications, grow and stream in packages
263
264        // We've reached point of no return; call into PMS to install the stage.
265        // Regardless of success or failure we always destroy session.
266        final IPackageInstallObserver2 remoteObserver = mRemoteObserver;
267        final IPackageInstallObserver2 localObserver = new IPackageInstallObserver2.Stub() {
268            @Override
269            public void packageInstalled(String basePackageName, Bundle extras, int returnCode)
270                    throws RemoteException {
271                destroy();
272                remoteObserver.packageInstalled(basePackageName, extras, returnCode);
273            }
274        };
275
276        mPm.installStage(mPackageName, this.sessionDir, localObserver, params.installFlags);
277    }
278
279    /**
280     * Validate install by confirming that all application packages are have
281     * consistent package name, version code, and signing certificates.
282     * <p>
283     * Renames package files in stage to match split names defined inside.
284     */
285    private void validateInstallLocked() throws InstallFailedException {
286        mPackageName = null;
287        mVersionCode = -1;
288        mSignatures = null;
289
290        final File[] files = sessionDir.listFiles();
291        if (ArrayUtils.isEmpty(files)) {
292            throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, "No packages staged");
293        }
294
295        final ArraySet<String> seenSplits = new ArraySet<>();
296
297        // Verify that all staged packages are internally consistent
298        for (File file : files) {
299            final PackageLite info = PackageParser.parsePackageLite(file.getAbsolutePath(),
300                    PackageParser.PARSE_GET_SIGNATURES);
301            if (info == null) {
302                throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
303                        "Failed to parse " + file);
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 PackageLite info = PackageParser.parsePackageLite(app.sourceDir,
359                    PackageParser.PARSE_GET_SIGNATURES);
360            if (info == null) {
361                throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
362                        "Failed to parse existing base " + app.sourceDir);
363            }
364
365            assertPackageConsistent("Existing base", info.packageName, info.versionCode,
366                    info.signatures);
367        }
368    }
369
370    private void assertPackageConsistent(String tag, String packageName, int versionCode,
371            Signature[] signatures) throws InstallFailedException {
372        if (!mPackageName.equals(packageName)) {
373            throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, tag + " package "
374                    + packageName + " inconsistent with " + mPackageName);
375        }
376        if (mVersionCode != versionCode) {
377            throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, tag
378                    + " version code " + versionCode + " inconsistent with "
379                    + mVersionCode);
380        }
381        if (!Signature.areExactMatch(mSignatures, signatures)) {
382            throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
383                    tag + " signatures are inconsistent");
384        }
385    }
386
387    /**
388     * Application is already installed; splice existing files that haven't been
389     * overridden into our stage.
390     */
391    private void spliceExistingFilesIntoStage() throws InstallFailedException {
392        final ApplicationInfo app = mPm.getApplicationInfo(mPackageName, 0, userId);
393        final File existingDir = new File(app.sourceDir).getParentFile();
394
395        try {
396            linkTreeIgnoringExisting(existingDir, sessionDir);
397        } catch (ErrnoException e) {
398            throw new InstallFailedException(INSTALL_FAILED_INTERNAL_ERROR,
399                    "Failed to splice into stage");
400        }
401    }
402
403    /**
404     * Recursively hard link all files from source directory tree to target.
405     * When a file already exists in the target tree, it leaves that file
406     * intact.
407     */
408    private void linkTreeIgnoringExisting(File sourceDir, File targetDir) throws ErrnoException {
409        final File[] sourceContents = sourceDir.listFiles();
410        if (ArrayUtils.isEmpty(sourceContents)) return;
411
412        for (File sourceFile : sourceContents) {
413            final File targetFile = new File(targetDir, sourceFile.getName());
414
415            if (sourceFile.isDirectory()) {
416                targetFile.mkdir();
417                linkTreeIgnoringExisting(sourceFile, targetFile);
418            } else {
419                Libcore.os.link(sourceFile.getAbsolutePath(), targetFile.getAbsolutePath());
420            }
421        }
422    }
423
424    private void unpackNativeLibraries() throws InstallFailedException {
425        final File libDir = new File(sessionDir, "lib");
426
427        if (!libDir.mkdir()) {
428            throw new InstallFailedException(INSTALL_FAILED_INTERNAL_ERROR,
429                    "Failed to create " + libDir);
430        }
431
432        try {
433            Libcore.os.chmod(libDir.getAbsolutePath(), 0755);
434        } catch (ErrnoException e) {
435            throw new InstallFailedException(INSTALL_FAILED_INTERNAL_ERROR,
436                    "Failed to prepare " + libDir + ": " + e);
437        }
438
439        if (!SELinux.restorecon(libDir)) {
440            throw new InstallFailedException(INSTALL_FAILED_INTERNAL_ERROR,
441                    "Failed to set context on " + libDir);
442        }
443
444        // Unpack all native libraries under stage
445        final File[] files = sessionDir.listFiles();
446        if (ArrayUtils.isEmpty(files)) {
447            throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, "No packages staged");
448        }
449
450        for (File file : files) {
451            final NativeLibraryHelper.ApkHandle handle = new NativeLibraryHelper.ApkHandle(file);
452            try {
453                final int abiIndex = NativeLibraryHelper.findSupportedAbi(handle,
454                        Build.SUPPORTED_ABIS);
455                if (abiIndex >= 0) {
456                    int copyRet = NativeLibraryHelper.copyNativeBinariesIfNeededLI(handle, libDir,
457                            Build.SUPPORTED_ABIS[abiIndex]);
458                    if (copyRet != INSTALL_SUCCEEDED) {
459                        throw new InstallFailedException(copyRet,
460                                "Failed to copy native libraries for " + file);
461                    }
462                } else if (abiIndex != PackageManager.NO_NATIVE_LIBRARIES) {
463                    throw new InstallFailedException(abiIndex,
464                            "Failed to copy native libraries for " + file);
465                }
466            } finally {
467                handle.close();
468            }
469        }
470    }
471
472    @Override
473    public void destroy() {
474        try {
475            synchronized (mLock) {
476                mInvalid = true;
477            }
478            FileUtils.deleteContents(sessionDir);
479            sessionDir.delete();
480        } finally {
481            mCallback.onSessionInvalid(this);
482        }
483    }
484
485    private static class WritePipe extends Thread {
486        private final ParcelFileDescriptor[] mPipe;
487
488        private FileDescriptor mTargetFd;
489
490        private volatile boolean mClosed;
491
492        public WritePipe() {
493            try {
494                mPipe = ParcelFileDescriptor.createPipe();
495            } catch (IOException e) {
496                throw new IllegalStateException("Failed to create pipe");
497            }
498        }
499
500        public boolean isClosed() {
501            return mClosed;
502        }
503
504        public void setTargetFd(FileDescriptor targetFd) {
505            mTargetFd = targetFd;
506        }
507
508        public ParcelFileDescriptor getWriteFd() {
509            return mPipe[1];
510        }
511
512        @Override
513        public void run() {
514            FileInputStream in = null;
515            FileOutputStream out = null;
516            try {
517                // TODO: look at switching to sendfile(2) to speed up
518                in = new FileInputStream(mPipe[0].getFileDescriptor());
519                out = new FileOutputStream(mTargetFd);
520                Streams.copy(in, out);
521            } catch (IOException e) {
522                Slog.w(TAG, "Failed to stream data: " + e);
523            } finally {
524                IoUtils.closeQuietly(mPipe[0]);
525                IoUtils.closeQuietly(mTargetFd);
526                mClosed = true;
527            }
528        }
529    }
530
531    private class InstallFailedException extends Exception {
532        private final int error;
533
534        public InstallFailedException(int error, String detailMessage) {
535            super(detailMessage);
536            this.error = error;
537        }
538    }
539}
540