summaryrefslogtreecommitdiffhomepage
path: root/tunnel/src/main/java/com/wireguard/android/util/SharedLibraryLoader.java
blob: 1bb3659fa3b9025f84fe4f77a36f0c10c00eea98 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
/*
 * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package com.wireguard.android.util;

import android.content.Context;
import android.os.Build;
import android.util.Log;

import com.wireguard.util.NonNullForAll;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import androidx.annotation.RestrictTo;
import androidx.annotation.RestrictTo.Scope;

@NonNullForAll
@RestrictTo(Scope.LIBRARY_GROUP)
public final class SharedLibraryLoader {
    private static final String TAG = "WireGuard/" + SharedLibraryLoader.class.getSimpleName();

    private SharedLibraryLoader() {
    }

    public static boolean extractLibrary(final Context context, final String libName, final File destination) throws IOException {
        final Collection<String> apks = new HashSet<>();
        if (context.getApplicationInfo().sourceDir != null)
            apks.add(context.getApplicationInfo().sourceDir);
        if (context.getApplicationInfo().splitSourceDirs != null)
            apks.addAll(Arrays.asList(context.getApplicationInfo().splitSourceDirs));

        for (final String abi : Build.SUPPORTED_ABIS) {
            for (final String apk : apks) {
                try (final ZipFile zipFile = new ZipFile(new File(apk), ZipFile.OPEN_READ)) {
                    final String mappedLibName = System.mapLibraryName(libName);
                    final String libZipPath = "lib" + File.separatorChar + abi + File.separatorChar + mappedLibName;
                    final ZipEntry zipEntry = zipFile.getEntry(libZipPath);
                    if (zipEntry == null)
                        continue;
                    Log.d(TAG, "Extracting apk:/" + libZipPath + " to " + destination.getAbsolutePath());
                    try (final FileOutputStream out = new FileOutputStream(destination);
                         final InputStream in = zipFile.getInputStream(zipEntry)) {
                        int len;
                        final byte[] buffer = new byte[1024 * 32];
                        while ((len = in.read(buffer)) != -1) {
                            out.write(buffer, 0, len);
                        }
                        out.getFD().sync();
                    }
                }
                return true;
            }
        }
        return false;
    }

    public static void loadSharedLibrary(final Context context, final String libName) {
        Throwable noAbiException;
        try {
            System.loadLibrary(libName);
            return;
        } catch (final UnsatisfiedLinkError e) {
            Log.d(TAG, "Failed to load library normally, so attempting to extract from apk", e);
            noAbiException = e;
        }
        File f = null;
        try {
            f = File.createTempFile("lib", ".so", context.getCodeCacheDir());
            if (extractLibrary(context, libName, f)) {
                System.load(f.getAbsolutePath());
                return;
            }
        } catch (final Exception e) {
            Log.d(TAG, "Failed to load library apk:/" + libName, e);
            noAbiException = e;
        } finally {
            if (f != null)
                // noinspection ResultOfMethodCallIgnored
                f.delete();
        }
        if (noAbiException instanceof RuntimeException)
            throw (RuntimeException) noAbiException;
        throw new RuntimeException(noAbiException);
    }
}