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