PackageManagerServiceUtils.java revision 77029c5b16351775cb2333369ef9a4bc1d9acf58
1/*
2 * Copyright (C) 2016 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_SHARED_USER_INCOMPATIBLE;
20import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
21import static com.android.server.pm.PackageManagerService.COMPRESSED_EXTENSION;
22import static com.android.server.pm.PackageManagerService.DEBUG_COMPRESSION;
23import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT;
24import static com.android.server.pm.PackageManagerService.STUB_SUFFIX;
25import static com.android.server.pm.PackageManagerService.TAG;
26import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
27
28import com.android.internal.content.NativeLibraryHelper;
29import com.android.internal.util.ArrayUtils;
30import com.android.internal.util.FastPrintWriter;
31import com.android.server.EventLogTags;
32import com.android.server.pm.dex.DexManager;
33import com.android.server.pm.dex.PackageDexUsage;
34
35import android.annotation.NonNull;
36import android.annotation.Nullable;
37import android.app.AppGlobals;
38import android.content.Intent;
39import android.content.pm.PackageManager;
40import android.content.pm.PackageParser;
41import android.content.pm.PackageParser.PackageParserException;
42import android.content.pm.ResolveInfo;
43import android.content.pm.Signature;
44import android.os.Build;
45import android.os.Debug;
46import android.os.Environment;
47import android.os.FileUtils;
48import android.os.Process;
49import android.os.RemoteException;
50import android.os.SystemProperties;
51import android.os.UserHandle;
52import android.service.pm.PackageServiceDumpProto;
53import android.system.ErrnoException;
54import android.system.Os;
55import android.util.ArraySet;
56import android.util.Log;
57import android.util.Slog;
58import android.util.jar.StrictJarFile;
59import android.util.proto.ProtoOutputStream;
60
61import dalvik.system.VMRuntime;
62
63import libcore.io.IoUtils;
64import libcore.io.Libcore;
65import libcore.io.Streams;
66
67import java.io.BufferedReader;
68import java.io.File;
69import java.io.FileInputStream;
70import java.io.FileOutputStream;
71import java.io.FileReader;
72import java.io.FilenameFilter;
73import java.io.IOException;
74import java.io.InputStream;
75import java.io.OutputStream;
76import java.io.PrintWriter;
77import java.security.cert.CertificateEncodingException;
78import java.security.cert.CertificateException;
79import java.text.SimpleDateFormat;
80import java.util.ArrayList;
81import java.util.Arrays;
82import java.util.Collection;
83import java.util.Collections;
84import java.util.Date;
85import java.util.Iterator;
86import java.util.LinkedList;
87import java.util.List;
88import java.util.function.Predicate;
89import java.util.zip.GZIPInputStream;
90import java.util.zip.ZipEntry;
91
92/**
93 * Class containing helper methods for the PackageManagerService.
94 *
95 * {@hide}
96 */
97public class PackageManagerServiceUtils {
98    private final static long SEVEN_DAYS_IN_MILLISECONDS = 7 * 24 * 60 * 60 * 1000;
99
100    private static ArraySet<String> getPackageNamesForIntent(Intent intent, int userId) {
101        List<ResolveInfo> ris = null;
102        try {
103            ris = AppGlobals.getPackageManager().queryIntentReceivers(intent, null, 0, userId)
104                    .getList();
105        } catch (RemoteException e) {
106        }
107        ArraySet<String> pkgNames = new ArraySet<String>();
108        if (ris != null) {
109            for (ResolveInfo ri : ris) {
110                pkgNames.add(ri.activityInfo.packageName);
111            }
112        }
113        return pkgNames;
114    }
115
116    // Sort a list of apps by their last usage, most recently used apps first. The order of
117    // packages without usage data is undefined (but they will be sorted after the packages
118    // that do have usage data).
119    public static void sortPackagesByUsageDate(List<PackageParser.Package> pkgs,
120            PackageManagerService packageManagerService) {
121        if (!packageManagerService.isHistoricalPackageUsageAvailable()) {
122            return;
123        }
124
125        Collections.sort(pkgs, (pkg1, pkg2) ->
126                Long.compare(pkg2.getLatestForegroundPackageUseTimeInMills(),
127                        pkg1.getLatestForegroundPackageUseTimeInMills()));
128    }
129
130    // Apply the given {@code filter} to all packages in {@code packages}. If tested positive, the
131    // package will be removed from {@code packages} and added to {@code result} with its
132    // dependencies. If usage data is available, the positive packages will be sorted by usage
133    // data (with {@code sortTemp} as temporary storage).
134    private static void applyPackageFilter(Predicate<PackageParser.Package> filter,
135            Collection<PackageParser.Package> result,
136            Collection<PackageParser.Package> packages,
137            @NonNull List<PackageParser.Package> sortTemp,
138            PackageManagerService packageManagerService) {
139        for (PackageParser.Package pkg : packages) {
140            if (filter.test(pkg)) {
141                sortTemp.add(pkg);
142            }
143        }
144
145        sortPackagesByUsageDate(sortTemp, packageManagerService);
146        packages.removeAll(sortTemp);
147
148        for (PackageParser.Package pkg : sortTemp) {
149            result.add(pkg);
150
151            Collection<PackageParser.Package> deps =
152                    packageManagerService.findSharedNonSystemLibraries(pkg);
153            if (!deps.isEmpty()) {
154                deps.removeAll(result);
155                result.addAll(deps);
156                packages.removeAll(deps);
157            }
158        }
159
160        sortTemp.clear();
161    }
162
163    // Sort apps by importance for dexopt ordering. Important apps are given
164    // more priority in case the device runs out of space.
165    public static List<PackageParser.Package> getPackagesForDexopt(
166            Collection<PackageParser.Package> packages,
167            PackageManagerService packageManagerService) {
168        ArrayList<PackageParser.Package> remainingPkgs = new ArrayList<>(packages);
169        LinkedList<PackageParser.Package> result = new LinkedList<>();
170        ArrayList<PackageParser.Package> sortTemp = new ArrayList<>(remainingPkgs.size());
171
172        // Give priority to core apps.
173        applyPackageFilter((pkg) -> pkg.coreApp, result, remainingPkgs, sortTemp,
174                packageManagerService);
175
176        // Give priority to system apps that listen for pre boot complete.
177        Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
178        final ArraySet<String> pkgNames = getPackageNamesForIntent(intent, UserHandle.USER_SYSTEM);
179        applyPackageFilter((pkg) -> pkgNames.contains(pkg.packageName), result, remainingPkgs,
180                sortTemp, packageManagerService);
181
182        // Give priority to apps used by other apps.
183        DexManager dexManager = packageManagerService.getDexManager();
184        applyPackageFilter((pkg) ->
185                dexManager.getPackageUseInfoOrDefault(pkg.packageName)
186                        .isAnyCodePathUsedByOtherApps(),
187                result, remainingPkgs, sortTemp, packageManagerService);
188
189        // Filter out packages that aren't recently used, add all remaining apps.
190        // TODO: add a property to control this?
191        Predicate<PackageParser.Package> remainingPredicate;
192        if (!remainingPkgs.isEmpty() && packageManagerService.isHistoricalPackageUsageAvailable()) {
193            if (DEBUG_DEXOPT) {
194                Log.i(TAG, "Looking at historical package use");
195            }
196            // Get the package that was used last.
197            PackageParser.Package lastUsed = Collections.max(remainingPkgs, (pkg1, pkg2) ->
198                    Long.compare(pkg1.getLatestForegroundPackageUseTimeInMills(),
199                            pkg2.getLatestForegroundPackageUseTimeInMills()));
200            if (DEBUG_DEXOPT) {
201                Log.i(TAG, "Taking package " + lastUsed.packageName + " as reference in time use");
202            }
203            long estimatedPreviousSystemUseTime =
204                    lastUsed.getLatestForegroundPackageUseTimeInMills();
205            // Be defensive if for some reason package usage has bogus data.
206            if (estimatedPreviousSystemUseTime != 0) {
207                final long cutoffTime = estimatedPreviousSystemUseTime - SEVEN_DAYS_IN_MILLISECONDS;
208                remainingPredicate =
209                        (pkg) -> pkg.getLatestForegroundPackageUseTimeInMills() >= cutoffTime;
210            } else {
211                // No meaningful historical info. Take all.
212                remainingPredicate = (pkg) -> true;
213            }
214            sortPackagesByUsageDate(remainingPkgs, packageManagerService);
215        } else {
216            // No historical info. Take all.
217            remainingPredicate = (pkg) -> true;
218        }
219        applyPackageFilter(remainingPredicate, result, remainingPkgs, sortTemp,
220                packageManagerService);
221
222        if (DEBUG_DEXOPT) {
223            Log.i(TAG, "Packages to be dexopted: " + packagesToString(result));
224            Log.i(TAG, "Packages skipped from dexopt: " + packagesToString(remainingPkgs));
225        }
226
227        return result;
228    }
229
230    /**
231     * Checks if the package was inactive during since <code>thresholdTimeinMillis</code>.
232     * Package is considered active, if:
233     * 1) It was active in foreground.
234     * 2) It was active in background and also used by other apps.
235     *
236     * If it doesn't have sufficient information about the package, it return <code>false</code>.
237     */
238    public static boolean isUnusedSinceTimeInMillis(long firstInstallTime, long currentTimeInMillis,
239            long thresholdTimeinMillis, PackageDexUsage.PackageUseInfo packageUseInfo,
240            long latestPackageUseTimeInMillis, long latestForegroundPackageUseTimeInMillis) {
241
242        if (currentTimeInMillis - firstInstallTime < thresholdTimeinMillis) {
243            return false;
244        }
245
246        // If the app was active in foreground during the threshold period.
247        boolean isActiveInForeground = (currentTimeInMillis
248                - latestForegroundPackageUseTimeInMillis)
249                < thresholdTimeinMillis;
250
251        if (isActiveInForeground) {
252            return false;
253        }
254
255        // If the app was active in background during the threshold period and was used
256        // by other packages.
257        boolean isActiveInBackgroundAndUsedByOtherPackages = ((currentTimeInMillis
258                - latestPackageUseTimeInMillis)
259                < thresholdTimeinMillis)
260                && packageUseInfo.isAnyCodePathUsedByOtherApps();
261
262        return !isActiveInBackgroundAndUsedByOtherPackages;
263    }
264
265    /**
266     * Returns the canonicalized path of {@code path} as per {@code realpath(3)}
267     * semantics.
268     */
269    public static String realpath(File path) throws IOException {
270        try {
271            return Os.realpath(path.getAbsolutePath());
272        } catch (ErrnoException ee) {
273            throw ee.rethrowAsIOException();
274        }
275    }
276
277    public static String packagesToString(Collection<PackageParser.Package> c) {
278        StringBuilder sb = new StringBuilder();
279        for (PackageParser.Package pkg : c) {
280            if (sb.length() > 0) {
281                sb.append(", ");
282            }
283            sb.append(pkg.packageName);
284        }
285        return sb.toString();
286    }
287
288    /**
289     * Verifies that the given string {@code isa} is a valid supported isa on
290     * the running device.
291     */
292    public static boolean checkISA(String isa) {
293        for (String abi : Build.SUPPORTED_ABIS) {
294            if (VMRuntime.getInstructionSet(abi).equals(isa)) {
295                return true;
296            }
297        }
298        return false;
299    }
300
301    public static long getLastModifiedTime(PackageParser.Package pkg) {
302        final File srcFile = new File(pkg.codePath);
303        if (!srcFile.isDirectory()) {
304            return srcFile.lastModified();
305        }
306        final File baseFile = new File(pkg.baseCodePath);
307        long maxModifiedTime = baseFile.lastModified();
308        if (pkg.splitCodePaths != null) {
309            for (int i = pkg.splitCodePaths.length - 1; i >=0; --i) {
310                final File splitFile = new File(pkg.splitCodePaths[i]);
311                maxModifiedTime = Math.max(maxModifiedTime, splitFile.lastModified());
312            }
313        }
314        return maxModifiedTime;
315    }
316
317    /**
318     * Checks that the archive located at {@code fileName} has uncompressed dex file and so
319     * files that can be direclty mapped.
320     */
321    public static void logApkHasUncompressedCode(String fileName) {
322        StrictJarFile jarFile = null;
323        try {
324            jarFile = new StrictJarFile(fileName,
325                    false /*verify*/, false /*signatureSchemeRollbackProtectionsEnforced*/);
326            Iterator<ZipEntry> it = jarFile.iterator();
327            while (it.hasNext()) {
328                ZipEntry entry = it.next();
329                if (entry.getName().endsWith(".dex")) {
330                    if (entry.getMethod() != ZipEntry.STORED) {
331                        Slog.wtf(TAG, "APK " + fileName + " has compressed dex code " +
332                                entry.getName());
333                    } else if ((entry.getDataOffset() & 0x3) != 0) {
334                        Slog.wtf(TAG, "APK " + fileName + " has unaligned dex code " +
335                                entry.getName());
336                    }
337                } else if (entry.getName().endsWith(".so")) {
338                    if (entry.getMethod() != ZipEntry.STORED) {
339                        Slog.wtf(TAG, "APK " + fileName + " has compressed native code " +
340                                entry.getName());
341                    } else if ((entry.getDataOffset() & (0x1000 - 1)) != 0) {
342                        Slog.wtf(TAG, "APK " + fileName + " has unaligned native code " +
343                                entry.getName());
344                    }
345                }
346            }
347        } catch (IOException ignore) {
348            Slog.wtf(TAG, "Error when parsing APK " + fileName);
349        } finally {
350            try {
351                if (jarFile != null) {
352                    jarFile.close();
353                }
354            } catch (IOException ignore) {}
355        }
356        return;
357    }
358
359    /**
360     * Checks that the APKs in the given package have uncompressed dex file and so
361     * files that can be direclty mapped.
362     */
363    public static void logPackageHasUncompressedCode(PackageParser.Package pkg) {
364        logApkHasUncompressedCode(pkg.baseCodePath);
365        if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) {
366            for (int i = 0; i < pkg.splitCodePaths.length; i++) {
367                logApkHasUncompressedCode(pkg.splitCodePaths[i]);
368            }
369        }
370    }
371
372    private static File getSettingsProblemFile() {
373        File dataDir = Environment.getDataDirectory();
374        File systemDir = new File(dataDir, "system");
375        File fname = new File(systemDir, "uiderrors.txt");
376        return fname;
377    }
378
379    public static void dumpCriticalInfo(ProtoOutputStream proto) {
380        try (BufferedReader in = new BufferedReader(new FileReader(getSettingsProblemFile()))) {
381            String line = null;
382            while ((line = in.readLine()) != null) {
383                if (line.contains("ignored: updated version")) continue;
384                proto.write(PackageServiceDumpProto.MESSAGES, line);
385            }
386        } catch (IOException ignored) {
387        }
388    }
389
390    public static void dumpCriticalInfo(PrintWriter pw, String msg) {
391        try (BufferedReader in = new BufferedReader(new FileReader(getSettingsProblemFile()))) {
392            String line = null;
393            while ((line = in.readLine()) != null) {
394                if (line.contains("ignored: updated version")) continue;
395                if (msg != null) {
396                    pw.print(msg);
397                }
398                pw.println(line);
399            }
400        } catch (IOException ignored) {
401        }
402    }
403
404    public static void logCriticalInfo(int priority, String msg) {
405        Slog.println(priority, TAG, msg);
406        EventLogTags.writePmCriticalInfo(msg);
407        try {
408            File fname = getSettingsProblemFile();
409            FileOutputStream out = new FileOutputStream(fname, true);
410            PrintWriter pw = new FastPrintWriter(out);
411            SimpleDateFormat formatter = new SimpleDateFormat();
412            String dateString = formatter.format(new Date(System.currentTimeMillis()));
413            pw.println(dateString + ": " + msg);
414            pw.close();
415            FileUtils.setPermissions(
416                    fname.toString(),
417                    FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IROTH,
418                    -1, -1);
419        } catch (java.io.IOException e) {
420        }
421    }
422
423    public static void enforceShellRestriction(String restriction, int callingUid, int userHandle) {
424        if (callingUid == Process.SHELL_UID) {
425            if (userHandle >= 0
426                    && PackageManagerService.sUserManager.hasUserRestriction(
427                            restriction, userHandle)) {
428                throw new SecurityException("Shell does not have permission to access user "
429                        + userHandle);
430            } else if (userHandle < 0) {
431                Slog.e(PackageManagerService.TAG, "Unable to check shell permission for user "
432                        + userHandle + "\n\t" + Debug.getCallers(3));
433            }
434        }
435    }
436
437    /**
438     * Derive the value of the {@code cpuAbiOverride} based on the provided
439     * value and an optional stored value from the package settings.
440     */
441    public static String deriveAbiOverride(String abiOverride, PackageSetting settings) {
442        String cpuAbiOverride = null;
443        if (NativeLibraryHelper.CLEAR_ABI_OVERRIDE.equals(abiOverride)) {
444            cpuAbiOverride = null;
445        } else if (abiOverride != null) {
446            cpuAbiOverride = abiOverride;
447        } else if (settings != null) {
448            cpuAbiOverride = settings.cpuAbiOverrideString;
449        }
450        return cpuAbiOverride;
451    }
452
453    /**
454     * Compares two sets of signatures. Returns:
455     * <br />
456     * {@link PackageManager#SIGNATURE_NEITHER_SIGNED}: if both signature sets are null,
457     * <br />
458     * {@link PackageManager#SIGNATURE_FIRST_NOT_SIGNED}: if the first signature set is null,
459     * <br />
460     * {@link PackageManager#SIGNATURE_SECOND_NOT_SIGNED}: if the second signature set is null,
461     * <br />
462     * {@link PackageManager#SIGNATURE_MATCH}: if the two signature sets are identical,
463     * <br />
464     * {@link PackageManager#SIGNATURE_NO_MATCH}: if the two signature sets differ.
465     */
466    public static int compareSignatures(Signature[] s1, Signature[] s2) {
467        if (s1 == null) {
468            return s2 == null
469                    ? PackageManager.SIGNATURE_NEITHER_SIGNED
470                    : PackageManager.SIGNATURE_FIRST_NOT_SIGNED;
471        }
472
473        if (s2 == null) {
474            return PackageManager.SIGNATURE_SECOND_NOT_SIGNED;
475        }
476
477        if (s1.length != s2.length) {
478            return PackageManager.SIGNATURE_NO_MATCH;
479        }
480
481        // Since both signature sets are of size 1, we can compare without HashSets.
482        if (s1.length == 1) {
483            return s1[0].equals(s2[0]) ?
484                    PackageManager.SIGNATURE_MATCH :
485                    PackageManager.SIGNATURE_NO_MATCH;
486        }
487
488        ArraySet<Signature> set1 = new ArraySet<Signature>();
489        for (Signature sig : s1) {
490            set1.add(sig);
491        }
492        ArraySet<Signature> set2 = new ArraySet<Signature>();
493        for (Signature sig : s2) {
494            set2.add(sig);
495        }
496        // Make sure s2 contains all signatures in s1.
497        if (set1.equals(set2)) {
498            return PackageManager.SIGNATURE_MATCH;
499        }
500        return PackageManager.SIGNATURE_NO_MATCH;
501    }
502
503    /**
504     * Used for backward compatibility to make sure any packages with
505     * certificate chains get upgraded to the new style. {@code existingSigs}
506     * will be in the old format (since they were stored on disk from before the
507     * system upgrade) and {@code scannedSigs} will be in the newer format.
508     */
509    private static boolean matchSignaturesCompat(String packageName,
510            PackageSignatures packageSignatures, PackageParser.SigningDetails parsedSignatures) {
511        ArraySet<Signature> existingSet = new ArraySet<Signature>();
512        for (Signature sig : packageSignatures.mSigningDetails.signatures) {
513            existingSet.add(sig);
514        }
515        ArraySet<Signature> scannedCompatSet = new ArraySet<Signature>();
516        for (Signature sig : parsedSignatures.signatures) {
517            try {
518                Signature[] chainSignatures = sig.getChainSignatures();
519                for (Signature chainSig : chainSignatures) {
520                    scannedCompatSet.add(chainSig);
521                }
522            } catch (CertificateEncodingException e) {
523                scannedCompatSet.add(sig);
524            }
525        }
526        // make sure the expanded scanned set contains all signatures in the existing one
527        if (scannedCompatSet.equals(existingSet)) {
528            // migrate the old signatures to the new scheme
529            packageSignatures.mSigningDetails = parsedSignatures;
530            return true;
531        }
532        return false;
533    }
534
535    private static boolean matchSignaturesRecover(String packageName,
536            Signature[] existingSignatures, Signature[] parsedSignatures) {
537        String msg = null;
538        try {
539            if (Signature.areEffectiveMatch(existingSignatures, parsedSignatures)) {
540                logCriticalInfo(Log.INFO,
541                        "Recovered effectively matching certificates for " + packageName);
542                return true;
543            }
544        } catch (CertificateException e) {
545            msg = e.getMessage();
546        }
547        logCriticalInfo(Log.INFO,
548                "Failed to recover certificates for " + packageName + ": " + msg);
549        return false;
550    }
551
552    /**
553     * Make sure the updated priv app is signed with the same key as the original APK file on the
554     * /system partition.
555     *
556     * <p>The rationale is that {@code disabledPkg} is a PackageSetting backed by xml files in /data
557     * and is not tamperproof.
558     */
559    private static boolean matchSignatureInSystem(PackageSetting pkgSetting,
560            PackageSetting disabledPkgSetting) {
561        try {
562            PackageParser.collectCertificates(disabledPkgSetting.pkg,
563                    PackageParser.PARSE_IS_SYSTEM_DIR);
564            if (compareSignatures(pkgSetting.signatures.mSigningDetails.signatures,
565                        disabledPkgSetting.signatures.mSigningDetails.signatures)
566                    != PackageManager.SIGNATURE_MATCH) {
567                logCriticalInfo(Log.ERROR, "Updated system app mismatches cert on /system: " +
568                        pkgSetting.name);
569                return false;
570            }
571        } catch (PackageParserException e) {
572            logCriticalInfo(Log.ERROR, "Failed to collect cert for " + pkgSetting.name + ": " +
573                    e.getMessage());
574            return false;
575        }
576        return true;
577    }
578
579    /** Returns true to force apk verification if the updated package (in /data) is a priv app. */
580    static boolean isApkVerificationForced(@Nullable PackageSetting disabledPs) {
581        return disabledPs != null && disabledPs.isPrivileged() &&
582                SystemProperties.getInt("ro.apk_verity.mode", 0) != 0;
583    }
584
585    /**
586     * Verifies that signatures match.
587     * @returns {@code true} if the compat signatures were matched; otherwise, {@code false}.
588     * @throws PackageManagerException if the signatures did not match.
589     */
590    public static boolean verifySignatures(PackageSetting pkgSetting,
591            PackageSetting disabledPkgSetting, PackageParser.SigningDetails parsedSignatures,
592            boolean compareCompat, boolean compareRecover)
593            throws PackageManagerException {
594        final String packageName = pkgSetting.name;
595        boolean compatMatch = false;
596        if (pkgSetting.signatures.mSigningDetails.signatures != null) {
597            // Already existing package. Make sure signatures match
598            boolean match = compareSignatures(pkgSetting.signatures.mSigningDetails.signatures,
599                    parsedSignatures.signatures)
600                    == PackageManager.SIGNATURE_MATCH;
601            if (!match && compareCompat) {
602                match = matchSignaturesCompat(packageName, pkgSetting.signatures,
603                        parsedSignatures);
604                compatMatch = match;
605            }
606            if (!match && compareRecover) {
607                match = matchSignaturesRecover(
608                        packageName, pkgSetting.signatures.mSigningDetails.signatures,
609                        parsedSignatures.signatures);
610            }
611
612            if (!match && isApkVerificationForced(disabledPkgSetting)) {
613                match = matchSignatureInSystem(pkgSetting, disabledPkgSetting);
614            }
615
616            if (!match) {
617                throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
618                        "Package " + packageName +
619                        " signatures do not match previously installed version; ignoring!");
620            }
621        }
622        // Check for shared user signatures
623        if (pkgSetting.sharedUser != null
624                && pkgSetting.sharedUser.signatures.mSigningDetails.signatures != null) {
625            // Already existing package. Make sure signatures match
626            boolean match =
627                    compareSignatures(
628                            pkgSetting.sharedUser.signatures.mSigningDetails.signatures,
629                            parsedSignatures.signatures) == PackageManager.SIGNATURE_MATCH;
630            if (!match && compareCompat) {
631                match = matchSignaturesCompat(
632                        packageName, pkgSetting.sharedUser.signatures, parsedSignatures);
633            }
634            if (!match && compareRecover) {
635                match = matchSignaturesRecover(packageName,
636                        pkgSetting.sharedUser.signatures.mSigningDetails.signatures,
637                        parsedSignatures.signatures);
638                compatMatch |= match;
639            }
640            if (!match) {
641                throw new PackageManagerException(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE,
642                        "Package " + packageName
643                        + " has no signatures that match those in shared user "
644                        + pkgSetting.sharedUser.name + "; ignoring!");
645            }
646        }
647        return compatMatch;
648    }
649
650    public static int decompressFile(File srcFile, File dstFile) throws ErrnoException {
651        if (DEBUG_COMPRESSION) {
652            Slog.i(TAG, "Decompress file"
653                    + "; src: " + srcFile.getAbsolutePath()
654                    + ", dst: " + dstFile.getAbsolutePath());
655        }
656        try (
657                InputStream fileIn = new GZIPInputStream(new FileInputStream(srcFile));
658                OutputStream fileOut = new FileOutputStream(dstFile, false /*append*/);
659        ) {
660            Streams.copy(fileIn, fileOut);
661            Os.chmod(dstFile.getAbsolutePath(), 0644);
662            return PackageManager.INSTALL_SUCCEEDED;
663        } catch (IOException e) {
664            logCriticalInfo(Log.ERROR, "Failed to decompress file"
665                    + "; src: " + srcFile.getAbsolutePath()
666                    + ", dst: " + dstFile.getAbsolutePath());
667        }
668        return PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
669    }
670
671    public static File[] getCompressedFiles(String codePath) {
672        final File stubCodePath = new File(codePath);
673        final String stubName = stubCodePath.getName();
674
675        // The layout of a compressed package on a given partition is as follows :
676        //
677        // Compressed artifacts:
678        //
679        // /partition/ModuleName/foo.gz
680        // /partation/ModuleName/bar.gz
681        //
682        // Stub artifact:
683        //
684        // /partition/ModuleName-Stub/ModuleName-Stub.apk
685        //
686        // In other words, stub is on the same partition as the compressed artifacts
687        // and in a directory that's suffixed with "-Stub".
688        int idx = stubName.lastIndexOf(STUB_SUFFIX);
689        if (idx < 0 || (stubName.length() != (idx + STUB_SUFFIX.length()))) {
690            return null;
691        }
692
693        final File stubParentDir = stubCodePath.getParentFile();
694        if (stubParentDir == null) {
695            Slog.e(TAG, "Unable to determine stub parent dir for codePath: " + codePath);
696            return null;
697        }
698
699        final File compressedPath = new File(stubParentDir, stubName.substring(0, idx));
700        final File[] files = compressedPath.listFiles(new FilenameFilter() {
701            @Override
702            public boolean accept(File dir, String name) {
703                return name.toLowerCase().endsWith(COMPRESSED_EXTENSION);
704            }
705        });
706
707        if (DEBUG_COMPRESSION && files != null && files.length > 0) {
708            Slog.i(TAG, "getCompressedFiles[" + codePath + "]: " + Arrays.toString(files));
709        }
710
711        return files;
712    }
713
714    public static boolean compressedFileExists(String codePath) {
715        final File[] compressedFiles = getCompressedFiles(codePath);
716        return compressedFiles != null && compressedFiles.length > 0;
717    }
718}
719