NativeLibraryHelper.java revision bb7b7bea19223c1eba74f525c7fe87ca3911813b
1/*
2 * Copyright (C) 2010 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.internal.content;
18
19import static android.content.pm.PackageManager.INSTALL_FAILED_NO_MATCHING_ABIS;
20import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
21import static android.content.pm.PackageManager.NO_NATIVE_LIBRARIES;
22import static android.system.OsConstants.S_IRGRP;
23import static android.system.OsConstants.S_IROTH;
24import static android.system.OsConstants.S_IRWXU;
25import static android.system.OsConstants.S_IXGRP;
26import static android.system.OsConstants.S_IXOTH;
27
28import android.content.pm.PackageManager;
29import android.content.pm.PackageParser;
30import android.content.pm.PackageParser.Package;
31import android.content.pm.PackageParser.PackageLite;
32import android.content.pm.PackageParser.PackageParserException;
33import android.os.Build;
34import android.os.SELinux;
35import android.system.ErrnoException;
36import android.system.Os;
37import android.util.Slog;
38
39import dalvik.system.CloseGuard;
40import dalvik.system.VMRuntime;
41
42import java.io.Closeable;
43import java.io.File;
44import java.io.IOException;
45import java.util.List;
46
47/**
48 * Native libraries helper.
49 *
50 * @hide
51 */
52public class NativeLibraryHelper {
53    private static final String TAG = "NativeHelper";
54
55    private static final boolean DEBUG_NATIVE = false;
56
57    // Special value for {@code PackageParser.Package#cpuAbiOverride} to indicate
58    // that the cpuAbiOverride must be clear.
59    public static final String CLEAR_ABI_OVERRIDE = "-";
60
61    /**
62     * A handle to an opened package, consisting of one or more APKs. Used as
63     * input to the various NativeLibraryHelper methods. Allows us to scan and
64     * parse the APKs exactly once instead of doing it multiple times.
65     *
66     * @hide
67     */
68    public static class Handle implements Closeable {
69        private final CloseGuard mGuard = CloseGuard.get();
70        private volatile boolean mClosed;
71
72        final long[] apkHandles;
73
74        public static Handle create(File packageFile) throws IOException {
75            try {
76                final PackageLite lite = PackageParser.parsePackageLite(packageFile, 0);
77                return create(lite);
78            } catch (PackageParserException e) {
79                throw new IOException("Failed to parse package: " + packageFile, e);
80            }
81        }
82
83        public static Handle create(Package pkg) throws IOException {
84            return create(pkg.getAllCodePaths());
85        }
86
87        public static Handle create(PackageLite lite) throws IOException {
88            return create(lite.getAllCodePaths());
89        }
90
91        private static Handle create(List<String> codePaths) throws IOException {
92            final int size = codePaths.size();
93            final long[] apkHandles = new long[size];
94            for (int i = 0; i < size; i++) {
95                final String path = codePaths.get(i);
96                apkHandles[i] = nativeOpenApk(path);
97                if (apkHandles[i] == 0) {
98                    // Unwind everything we've opened so far
99                    for (int j = 0; j < i; j++) {
100                        nativeClose(apkHandles[j]);
101                    }
102                    throw new IOException("Unable to open APK: " + path);
103                }
104            }
105
106            return new Handle(apkHandles);
107        }
108
109        Handle(long[] apkHandles) {
110            this.apkHandles = apkHandles;
111            mGuard.open("close");
112        }
113
114        @Override
115        public void close() {
116            for (long apkHandle : apkHandles) {
117                nativeClose(apkHandle);
118            }
119            mGuard.close();
120            mClosed = true;
121        }
122
123        @Override
124        protected void finalize() throws Throwable {
125            if (mGuard != null) {
126                mGuard.warnIfOpen();
127            }
128            try {
129                if (!mClosed) {
130                    close();
131                }
132            } finally {
133                super.finalize();
134            }
135        }
136    }
137
138    private static native long nativeOpenApk(String path);
139    private static native void nativeClose(long handle);
140
141    private static native long nativeSumNativeBinaries(long handle, String cpuAbi);
142
143    private native static int nativeCopyNativeBinaries(long handle,
144            String sharedLibraryPath, String abiToCopy);
145
146    private static long sumNativeBinaries(Handle handle, String abi) {
147        long sum = 0;
148        for (long apkHandle : handle.apkHandles) {
149            sum += nativeSumNativeBinaries(apkHandle, abi);
150        }
151        return sum;
152    }
153
154    /**
155     * Copies native binaries to a shared library directory.
156     *
157     * @param handle APK file to scan for native libraries
158     * @param sharedLibraryDir directory for libraries to be copied to
159     * @return {@link PackageManager#INSTALL_SUCCEEDED} if successful or another
160     *         error code from that class if not
161     */
162    public static int copyNativeBinariesIfNeededLI(Handle handle, File sharedLibraryDir,
163            String abi) {
164        for (long apkHandle : handle.apkHandles) {
165            int res = nativeCopyNativeBinaries(apkHandle, sharedLibraryDir.getPath(), abi);
166            if (res != INSTALL_SUCCEEDED) {
167                return res;
168            }
169        }
170        return INSTALL_SUCCEEDED;
171    }
172
173    /**
174     * Checks if a given APK contains native code for any of the provided
175     * {@code supportedAbis}. Returns an index into {@code supportedAbis} if a matching
176     * ABI is found, {@link PackageManager#NO_NATIVE_LIBRARIES} if the
177     * APK doesn't contain any native code, and
178     * {@link PackageManager#INSTALL_FAILED_NO_MATCHING_ABIS} if none of the ABIs match.
179     */
180    public static int findSupportedAbi(Handle handle, String[] supportedAbis) {
181        int finalRes = NO_NATIVE_LIBRARIES;
182        for (long apkHandle : handle.apkHandles) {
183            final int res = nativeFindSupportedAbi(apkHandle, supportedAbis);
184            if (res == NO_NATIVE_LIBRARIES) {
185                // No native code, keep looking through all APKs.
186            } else if (res == INSTALL_FAILED_NO_MATCHING_ABIS) {
187                // Found some native code, but no ABI match; update our final
188                // result if we haven't found other valid code.
189                if (finalRes < 0) {
190                    finalRes = INSTALL_FAILED_NO_MATCHING_ABIS;
191                }
192            } else if (res >= 0) {
193                // Found valid native code, track the best ABI match
194                if (finalRes < 0 || res < finalRes) {
195                    finalRes = res;
196                }
197            } else {
198                // Unexpected error; bail
199                return res;
200            }
201        }
202        return finalRes;
203    }
204
205    private native static int nativeFindSupportedAbi(long handle, String[] supportedAbis);
206
207    // Convenience method to call removeNativeBinariesFromDirLI(File)
208    public static void removeNativeBinariesLI(String nativeLibraryPath) {
209        if (nativeLibraryPath == null) return;
210        removeNativeBinariesFromDirLI(new File(nativeLibraryPath), false /* delete root dir */);
211    }
212
213    /**
214     * Remove the native binaries of a given package. This deletes the files
215     */
216    public static void removeNativeBinariesFromDirLI(File nativeLibraryRoot, boolean deleteRootDir) {
217        if (DEBUG_NATIVE) {
218            Slog.w(TAG, "Deleting native binaries from: " + nativeLibraryRoot.getPath());
219        }
220
221        /*
222         * Just remove any file in the directory. Since the directory is owned
223         * by the 'system' UID, the application is not supposed to have written
224         * anything there.
225         */
226        if (nativeLibraryRoot.exists()) {
227            final File[] files = nativeLibraryRoot.listFiles();
228            if (files != null) {
229                for (int nn = 0; nn < files.length; nn++) {
230                    if (DEBUG_NATIVE) {
231                        Slog.d(TAG, "    Deleting " + files[nn].getName());
232                    }
233
234                    if (files[nn].isDirectory()) {
235                        removeNativeBinariesFromDirLI(files[nn], true /* delete root dir */);
236                    } else if (!files[nn].delete()) {
237                        Slog.w(TAG, "Could not delete native binary: " + files[nn].getPath());
238                    }
239                }
240            }
241            // Do not delete 'lib' directory itself, unless we're specifically
242            // asked to or this will prevent installation of future updates.
243            if (deleteRootDir) {
244                if (!nativeLibraryRoot.delete()) {
245                    Slog.w(TAG, "Could not delete native binary directory: " + nativeLibraryRoot.getPath());
246                }
247            }
248        }
249    }
250
251    private static void createNativeLibrarySubdir(File path) throws IOException {
252        if (!path.isDirectory()) {
253            path.delete();
254
255            if (!path.mkdir()) {
256                throw new IOException("Cannot create " + path.getPath());
257            }
258
259            try {
260                Os.chmod(path.getPath(), S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
261            } catch (ErrnoException e) {
262                throw new IOException("Cannot chmod native library directory "
263                        + path.getPath(), e);
264            }
265        } else if (!SELinux.restorecon(path)) {
266            throw new IOException("Cannot set SELinux context for " + path.getPath());
267        }
268    }
269
270    private static long sumNativeBinaries(Handle handle, String[] abiList) {
271        int abi = findSupportedAbi(handle, abiList);
272        if (abi >= 0) {
273            return sumNativeBinaries(handle, abiList[abi]);
274        } else {
275            return 0;
276        }
277    }
278
279    public static int copyNativeBinariesIfNeededLI(Handle handle, File libraryRoot,
280            String[] abiList, boolean useIsaSubdir) throws IOException {
281        createNativeLibrarySubdir(libraryRoot);
282
283        /*
284         * If this is an internal application or our nativeLibraryPath points to
285         * the app-lib directory, unpack the libraries if necessary.
286         */
287        int abi = findSupportedAbi(handle, abiList);
288        if (abi >= 0) {
289            /*
290             * If we have a matching instruction set, construct a subdir under the native
291             * library root that corresponds to this instruction set.
292             */
293            final String instructionSet = VMRuntime.getInstructionSet(abiList[abi]);
294            final File subDir;
295            if (useIsaSubdir) {
296                final File isaSubdir = new File(libraryRoot, instructionSet);
297                createNativeLibrarySubdir(isaSubdir);
298                subDir = isaSubdir;
299            } else {
300                subDir = libraryRoot;
301            }
302
303            int copyRet = copyNativeBinariesIfNeededLI(handle, subDir, abiList[abi]);
304            if (copyRet != PackageManager.INSTALL_SUCCEEDED) {
305                return copyRet;
306            }
307        }
308
309        return abi;
310    }
311
312    public static int copyNativeBinariesIfNeededLI(Handle handle, File libraryRoot,
313            String abiOverride, boolean multiArch) {
314        try {
315            if (multiArch) {
316                // Warn if we've set an abiOverride for multi-lib packages..
317                // By definition, we need to copy both 32 and 64 bit libraries for
318                // such packages.
319                if (abiOverride != null && !CLEAR_ABI_OVERRIDE.equals(abiOverride)) {
320                    Slog.w(TAG, "Ignoring abiOverride for multi arch application.");
321                }
322
323                int copyRet = PackageManager.NO_NATIVE_LIBRARIES;
324                if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
325                    copyRet = copyNativeBinariesIfNeededLI(handle, libraryRoot,
326                            Build.SUPPORTED_32_BIT_ABIS, true /* use isa specific subdirs */);
327                    if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES &&
328                            copyRet != PackageManager.INSTALL_FAILED_NO_MATCHING_ABIS) {
329                        Slog.w(TAG, "Failure copying 32 bit native libraries; copyRet=" +copyRet);
330                        return copyRet;
331                    }
332                }
333
334                if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
335                    copyRet = copyNativeBinariesIfNeededLI(handle, libraryRoot,
336                            Build.SUPPORTED_64_BIT_ABIS, true /* use isa specific subdirs */);
337                    if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES &&
338                            copyRet != PackageManager.INSTALL_FAILED_NO_MATCHING_ABIS) {
339                        Slog.w(TAG, "Failure copying 64 bit native libraries; copyRet=" +copyRet);
340                        return copyRet;
341                    }
342                }
343            } else {
344                String cpuAbiOverride = null;
345                if (CLEAR_ABI_OVERRIDE.equals(abiOverride)) {
346                    cpuAbiOverride = null;
347                } else if (abiOverride != null) {
348                    cpuAbiOverride = abiOverride;
349                }
350
351                String[] abiList = (cpuAbiOverride != null) ?
352                        new String[] { cpuAbiOverride } : Build.SUPPORTED_ABIS;
353                if (Build.SUPPORTED_64_BIT_ABIS.length > 0 && cpuAbiOverride == null &&
354                        hasRenderscriptBitcode(handle)) {
355                    abiList = Build.SUPPORTED_32_BIT_ABIS;
356                }
357
358                int copyRet = copyNativeBinariesIfNeededLI(handle, libraryRoot, abiList,
359                        true /* use isa specific subdirs */);
360                if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES) {
361                    Slog.w(TAG, "Failure copying native libraries [errorCode=" + copyRet + "]");
362                    return copyRet;
363                }
364            }
365
366            return PackageManager.INSTALL_SUCCEEDED;
367        } catch (IOException e) {
368            Slog.e(TAG, "Copying native libraries failed", e);
369            return PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
370        }
371    }
372
373    public static long sumNativeBinaries(Handle handle, String abiOverride, boolean multiArch)
374            throws IOException {
375        long sum = 0;
376        if (multiArch) {
377            // Warn if we've set an abiOverride for multi-lib packages..
378            // By definition, we need to copy both 32 and 64 bit libraries for
379            // such packages.
380            if (abiOverride != null && !CLEAR_ABI_OVERRIDE.equals(abiOverride)) {
381                Slog.w(TAG, "Ignoring abiOverride for multi arch application.");
382            }
383
384            if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
385                sum += sumNativeBinaries(handle, Build.SUPPORTED_32_BIT_ABIS);
386            }
387
388            if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
389                sum += sumNativeBinaries(handle, Build.SUPPORTED_64_BIT_ABIS);
390            }
391        } else {
392            String cpuAbiOverride = null;
393            if (CLEAR_ABI_OVERRIDE.equals(abiOverride)) {
394                cpuAbiOverride = null;
395            } else if (abiOverride != null) {
396                cpuAbiOverride = abiOverride;
397            }
398
399            String[] abiList = (cpuAbiOverride != null) ?
400                    new String[] { cpuAbiOverride } : Build.SUPPORTED_ABIS;
401            if (Build.SUPPORTED_64_BIT_ABIS.length > 0 && cpuAbiOverride == null &&
402                    hasRenderscriptBitcode(handle)) {
403                abiList = Build.SUPPORTED_32_BIT_ABIS;
404            }
405
406            sum += sumNativeBinaries(handle, abiList);
407        }
408        return sum;
409    }
410
411    // We don't care about the other return values for now.
412    private static final int BITCODE_PRESENT = 1;
413
414    private static native int hasRenderscriptBitcode(long apkHandle);
415
416    public static boolean hasRenderscriptBitcode(Handle handle) throws IOException {
417        for (long apkHandle : handle.apkHandles) {
418            final int res = hasRenderscriptBitcode(apkHandle);
419            if (res < 0) {
420                throw new IOException("Error scanning APK, code: " + res);
421            } else if (res == BITCODE_PRESENT) {
422                return true;
423            }
424        }
425        return false;
426    }
427}
428