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