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