NativeLibraryHelper.java revision 43a17654cf4bfe7f1ec22bd8b7b32daccdf27c09
1package com.android.internal.content;
2
3import android.content.pm.PackageManager;
4import android.os.Build;
5import android.os.FileUtils;
6import android.os.SystemProperties;
7import android.util.Log;
8import android.util.Pair;
9import android.util.Slog;
10
11import java.io.File;
12import java.io.IOException;
13import java.io.InputStream;
14import java.util.Enumeration;
15import java.util.LinkedList;
16import java.util.List;
17import java.util.zip.ZipEntry;
18import java.util.zip.ZipException;
19import java.util.zip.ZipFile;
20
21/**
22 * Native libraries helper.
23 *
24 * @hide
25 */
26public class NativeLibraryHelper {
27    private static final String TAG = "NativeHelper";
28
29    private static final boolean DEBUG_NATIVE = false;
30
31    /*
32     * The following constants are returned by listPackageSharedLibsForAbiLI
33     * to indicate if native shared libraries were found in the package.
34     * Values are:
35     *    PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES => native libraries found and installed
36     *    PACKAGE_INSTALL_NATIVE_NO_LIBRARIES     => no native libraries in package
37     *    PACKAGE_INSTALL_NATIVE_ABI_MISMATCH     => native libraries for another ABI found
38     *                                        in package (and not installed)
39     *
40     */
41    private static final int PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES = 0;
42    private static final int PACKAGE_INSTALL_NATIVE_NO_LIBRARIES = 1;
43    private static final int PACKAGE_INSTALL_NATIVE_ABI_MISMATCH = 2;
44
45    // Directory in the APK that holds all the native shared libraries.
46    private static final String APK_LIB = "lib/";
47    private static final int APK_LIB_LENGTH = APK_LIB.length();
48
49    // Prefix that native shared libraries must have.
50    private static final String LIB_PREFIX = "lib";
51    private static final int LIB_PREFIX_LENGTH = LIB_PREFIX.length();
52
53    // Suffix that the native shared libraries must have.
54    private static final String LIB_SUFFIX = ".so";
55    private static final int LIB_SUFFIX_LENGTH = LIB_SUFFIX.length();
56
57    // Name of the GDB binary.
58    private static final String GDBSERVER = "gdbserver";
59
60    // the minimum length of a valid native shared library of the form
61    // lib/<something>/lib<name>.so.
62    private static final int MIN_ENTRY_LENGTH = APK_LIB_LENGTH + 2 + LIB_PREFIX_LENGTH + 1
63            + LIB_SUFFIX_LENGTH;
64
65    /*
66     * Find all files of the form lib/<cpuAbi>/lib<name>.so in the .apk
67     * and add them to a list to be installed later.
68     *
69     * NOTE: this method may throw an IOException if the library cannot
70     * be copied to its final destination, e.g. if there isn't enough
71     * room left on the data partition, or a ZipException if the package
72     * file is malformed.
73     */
74    private static int listPackageSharedLibsForAbiLI(ZipFile zipFile,
75            String cpuAbi, List<Pair<ZipEntry, String>> libEntries) throws IOException,
76            ZipException {
77        final int cpuAbiLen = cpuAbi.length();
78        boolean hasNativeLibraries = false;
79        boolean installedNativeLibraries = false;
80
81        if (DEBUG_NATIVE) {
82            Slog.d(TAG, "Checking " + zipFile.getName() + " for shared libraries of CPU ABI type "
83                    + cpuAbi);
84        }
85
86        Enumeration<? extends ZipEntry> entries = zipFile.entries();
87
88        while (entries.hasMoreElements()) {
89            ZipEntry entry = entries.nextElement();
90
91            // skip directories
92            if (entry.isDirectory()) {
93                continue;
94            }
95            String entryName = entry.getName();
96
97            /*
98             * Check that the entry looks like lib/<something>/lib<name>.so
99             * here, but don't check the ABI just yet.
100             *
101             * - must be sufficiently long
102             * - must end with LIB_SUFFIX, i.e. ".so"
103             * - must start with APK_LIB, i.e. "lib/"
104             */
105            if (entryName.length() < MIN_ENTRY_LENGTH || !entryName.endsWith(LIB_SUFFIX)
106                    || !entryName.startsWith(APK_LIB)) {
107                continue;
108            }
109
110            // file name must start with LIB_PREFIX, i.e. "lib"
111            int lastSlash = entryName.lastIndexOf('/');
112
113            if (lastSlash < 0
114                    || !entryName.regionMatches(lastSlash + 1, LIB_PREFIX, 0, LIB_PREFIX_LENGTH)) {
115                continue;
116            }
117
118            hasNativeLibraries = true;
119
120            // check the cpuAbi now, between lib/ and /lib<name>.so
121            if (lastSlash != APK_LIB_LENGTH + cpuAbiLen
122                    || !entryName.regionMatches(APK_LIB_LENGTH, cpuAbi, 0, cpuAbiLen))
123                continue;
124
125            /*
126             * Extract the library file name, ensure it doesn't contain
127             * weird characters. we're guaranteed here that it doesn't contain
128             * a directory separator though.
129             */
130            String libFileName = entryName.substring(lastSlash+1);
131            if (!FileUtils.isFilenameSafe(new File(libFileName))) {
132                continue;
133            }
134
135            installedNativeLibraries = true;
136
137            if (DEBUG_NATIVE) {
138                Log.d(TAG, "Caching shared lib " + entry.getName());
139            }
140
141            libEntries.add(Pair.create(entry, libFileName));
142        }
143        if (!hasNativeLibraries)
144            return PACKAGE_INSTALL_NATIVE_NO_LIBRARIES;
145
146        if (!installedNativeLibraries)
147            return PACKAGE_INSTALL_NATIVE_ABI_MISMATCH;
148
149        return PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES;
150    }
151
152    /*
153     * Find the gdbserver executable program in a package at
154     * lib/<cpuAbi>/gdbserver and add it to the list of binaries
155     * to be copied out later.
156     *
157     * Returns PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES on success,
158     * or PACKAGE_INSTALL_NATIVE_NO_LIBRARIES otherwise.
159     */
160    private static int listPackageGdbServerLI(ZipFile zipFile, String cpuAbi,
161            List<Pair<ZipEntry, String>> nativeFiles) throws IOException, ZipException {
162        final String apkGdbServerPath = "lib/" + cpuAbi + "/" + GDBSERVER;
163
164        Enumeration<? extends ZipEntry> entries = zipFile.entries();
165
166        while (entries.hasMoreElements()) {
167            ZipEntry entry = entries.nextElement();
168            // skip directories
169            if (entry.isDirectory()) {
170                continue;
171            }
172            String entryName = entry.getName();
173
174            if (!entryName.equals(apkGdbServerPath)) {
175                continue;
176            }
177
178            if (false) {
179                Log.d(TAG, "Found gdbserver: " + entry.getName());
180            }
181
182            final String installGdbServerPath = GDBSERVER;
183            nativeFiles.add(Pair.create(entry, installGdbServerPath));
184
185            return PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES;
186        }
187        return PACKAGE_INSTALL_NATIVE_NO_LIBRARIES;
188    }
189
190    /*
191     * Examine shared libraries stored in the APK as
192     * lib/<cpuAbi>/lib<name>.so and add them to a list to be copied
193     * later.
194     *
195     * This function will first try the main CPU ABI defined by Build.CPU_ABI
196     * (which corresponds to ro.product.cpu.abi), and also try an alternate
197     * one if ro.product.cpu.abi2 is defined.
198     */
199    public static int listPackageNativeBinariesLI(ZipFile zipFile,
200            List<Pair<ZipEntry, String>> nativeFiles) throws ZipException, IOException {
201        String cpuAbi = Build.CPU_ABI;
202
203        int result = listPackageSharedLibsForAbiLI(zipFile, cpuAbi, nativeFiles);
204
205        /*
206         * Some architectures are capable of supporting several CPU ABIs
207         * for example, 'armeabi-v7a' also supports 'armeabi' native code
208         * this is indicated by the definition of the ro.product.cpu.abi2
209         * system property.
210         *
211         * only scan the package twice in case of ABI mismatch
212         */
213        if (result == PACKAGE_INSTALL_NATIVE_ABI_MISMATCH) {
214            final String cpuAbi2 = SystemProperties.get("ro.product.cpu.abi2", null);
215            if (cpuAbi2 != null) {
216                result = listPackageSharedLibsForAbiLI(zipFile, cpuAbi2, nativeFiles);
217            }
218
219            if (result == PACKAGE_INSTALL_NATIVE_ABI_MISMATCH) {
220                Slog.w(TAG, "Native ABI mismatch from package file");
221                return PackageManager.INSTALL_FAILED_INVALID_APK;
222            }
223
224            if (result == PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES) {
225                cpuAbi = cpuAbi2;
226            }
227        }
228
229        /*
230         * Debuggable packages may have gdbserver embedded, so add it to
231         * the list to the list of items to be extracted (as lib/gdbserver)
232         * into the application's native library directory later.
233         */
234        if (result == PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES) {
235            listPackageGdbServerLI(zipFile, cpuAbi, nativeFiles);
236        }
237        return PackageManager.INSTALL_SUCCEEDED;
238    }
239
240    public static int copyNativeBinariesLI(File scanFile, File sharedLibraryDir) {
241        /*
242         * Check all the native files that need to be copied and add
243         * that to the container size.
244         */
245        ZipFile zipFile;
246        try {
247            zipFile = new ZipFile(scanFile);
248
249            List<Pair<ZipEntry, String>> nativeFiles = new LinkedList<Pair<ZipEntry, String>>();
250
251            NativeLibraryHelper.listPackageNativeBinariesLI(zipFile, nativeFiles);
252
253            final int N = nativeFiles.size();
254
255            for (int i = 0; i < N; i++) {
256                final Pair<ZipEntry, String> entry = nativeFiles.get(i);
257
258                File destFile = new File(sharedLibraryDir, entry.second);
259                copyNativeBinaryLI(zipFile, entry.first, sharedLibraryDir, destFile);
260            }
261            zipFile.close();
262        } catch (ZipException e) {
263            Slog.w(TAG, "Failed to extract data from package file", e);
264            return PackageManager.INSTALL_FAILED_INVALID_APK;
265        } catch (IOException e) {
266            Slog.w(TAG, "Failed to cache package shared libs", e);
267            return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
268        }
269
270        return PackageManager.INSTALL_SUCCEEDED;
271    }
272
273    private static void copyNativeBinaryLI(ZipFile zipFile, ZipEntry entry,
274            File binaryDir, File binaryFile) throws IOException {
275        InputStream inputStream = zipFile.getInputStream(entry);
276        try {
277            File tempFile = File.createTempFile("tmp", "tmp", binaryDir);
278            String tempFilePath = tempFile.getPath();
279            // XXX package manager can't change owner, so the executable files for
280            // now need to be left as world readable and owned by the system.
281            if (!FileUtils.copyToFile(inputStream, tempFile)
282                    || !tempFile.setLastModified(entry.getTime())
283                    || FileUtils.setPermissions(tempFilePath, FileUtils.S_IRUSR | FileUtils.S_IWUSR
284                            | FileUtils.S_IRGRP | FileUtils.S_IXUSR | FileUtils.S_IXGRP
285                            | FileUtils.S_IXOTH | FileUtils.S_IROTH, -1, -1) != 0
286                    || !tempFile.renameTo(binaryFile)) {
287                // Failed to properly write file.
288                tempFile.delete();
289                throw new IOException("Couldn't create cached binary " + binaryFile + " in "
290                        + binaryDir);
291            }
292        } finally {
293            inputStream.close();
294        }
295    }
296
297    // Convenience method to call removeNativeBinariesFromDirLI(File)
298    public static boolean removeNativeBinariesLI(String nativeLibraryPath) {
299        return removeNativeBinariesFromDirLI(new File(nativeLibraryPath));
300    }
301
302    // Remove the native binaries of a given package. This simply
303    // gets rid of the files in the 'lib' sub-directory.
304    public static boolean removeNativeBinariesFromDirLI(File nativeLibraryDir) {
305        if (DEBUG_NATIVE) {
306            Slog.w(TAG, "Deleting native binaries from: " + nativeLibraryDir.getPath());
307        }
308
309        boolean deletedFiles = false;
310
311        /*
312         * Just remove any file in the directory. Since the directory is owned
313         * by the 'system' UID, the application is not supposed to have written
314         * anything there.
315         */
316        if (nativeLibraryDir.exists()) {
317            final File[] binaries = nativeLibraryDir.listFiles();
318            if (binaries != null) {
319                for (int nn = 0; nn < binaries.length; nn++) {
320                    if (DEBUG_NATIVE) {
321                        Slog.d(TAG, "    Deleting " + binaries[nn].getName());
322                    }
323
324                    if (!binaries[nn].delete()) {
325                        Slog.w(TAG, "Could not delete native binary: " + binaries[nn].getPath());
326                    } else {
327                        deletedFiles = true;
328                    }
329                }
330            }
331            // Do not delete 'lib' directory itself, or this will prevent
332            // installation of future updates.
333        }
334
335        return deletedFiles;
336    }
337}
338