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