OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 package org.chromium.incrementalinstall; |
| 6 |
| 7 import android.content.Context; |
| 8 import android.os.Build; |
| 9 import android.util.Log; |
| 10 |
| 11 import java.io.File; |
| 12 import java.io.FileInputStream; |
| 13 import java.io.FileNotFoundException; |
| 14 import java.io.FileOutputStream; |
| 15 import java.io.IOException; |
| 16 import java.util.List; |
| 17 |
| 18 /** |
| 19 * Provides the ability to add native libraries and .dex files to an existing cl
ass loader. |
| 20 * Tested with Jellybean MR2 - Marshmellow. |
| 21 */ |
| 22 final class ClassLoaderPatcher { |
| 23 private static final String TAG = "cr.incrementalinstall"; |
| 24 private final File mAppFilesSubDir; |
| 25 private final ClassLoader mClassLoader; |
| 26 private final Object mLibcoreOs; |
| 27 private final int mProcessUid; |
| 28 final boolean mIsPrimaryProcess; |
| 29 |
| 30 ClassLoaderPatcher(Context context) throws ReflectiveOperationException { |
| 31 mAppFilesSubDir = |
| 32 new File(context.getApplicationInfo().dataDir, "incremental-inst
all-files"); |
| 33 mClassLoader = context.getClassLoader(); |
| 34 mLibcoreOs = Reflect.getField(Class.forName("libcore.io.Libcore"), "os")
; |
| 35 mProcessUid = (Integer) Reflect.invokeMethod(mLibcoreOs, "getuid"); |
| 36 mIsPrimaryProcess = context.getApplicationInfo().uid == mProcessUid; |
| 37 Log.i(TAG, "uid=" + mProcessUid + " (isPrimary=" + mIsPrimaryProcess + "
)"); |
| 38 } |
| 39 |
| 40 /** |
| 41 * Loads all dex files within |dexDir| into the app's ClassLoader. |
| 42 */ |
| 43 void loadDexFiles(File dexDir) throws ReflectiveOperationException, FileNotF
oundException { |
| 44 Log.i(TAG, "Installing dex files from: " + dexDir); |
| 45 File[] dexFilesArr = dexDir.listFiles(); |
| 46 if (dexFilesArr == null) { |
| 47 throw new FileNotFoundException("Dex dir does not exist: " + dexDir)
; |
| 48 } |
| 49 // The optimized dex files will be owned by this process' user. |
| 50 // Store them within the app's data dir rather than on /data/local/tmp |
| 51 // so that they are still deleted (by the OS) when we uninstall |
| 52 // (even on a non-rooted device). |
| 53 File incrementalDexesDir = new File(mAppFilesSubDir, "optimized-dexes"); |
| 54 File isolatedDexesDir = new File(mAppFilesSubDir, "isolated-dexes"); |
| 55 File optimizedDir; |
| 56 |
| 57 if (mIsPrimaryProcess) { |
| 58 ensureAppFilesSubDirExists(); |
| 59 // Allows isolated processes to access the same files. |
| 60 incrementalDexesDir.mkdir(); |
| 61 incrementalDexesDir.setReadable(true, false); |
| 62 incrementalDexesDir.setExecutable(true, false); |
| 63 // Create a directory for isolated processes to create directories i
n. |
| 64 isolatedDexesDir.mkdir(); |
| 65 isolatedDexesDir.setWritable(true, false); |
| 66 isolatedDexesDir.setExecutable(true, false); |
| 67 |
| 68 optimizedDir = incrementalDexesDir; |
| 69 } else { |
| 70 // There is a UID check of the directory in dalvik.system.DexFile(): |
| 71 // https://android.googlesource.com/platform/libcore/+/45e0260/dalvi
k/src/main/java/dalvik/system/DexFile.java#101 |
| 72 // Rather than have each isolated process run DexOpt though, we use |
| 73 // symlinks within the directory to point at the browser process' |
| 74 // optimized dex files. |
| 75 optimizedDir = new File(isolatedDexesDir, "isolated-" + mProcessUid)
; |
| 76 optimizedDir.mkdir(); |
| 77 // Always wipe it out and re-create for simplicity. |
| 78 Log.i(TAG, "Creating dex file symlinks for isolated process"); |
| 79 for (File f : optimizedDir.listFiles()) { |
| 80 f.delete(); |
| 81 } |
| 82 for (File f : incrementalDexesDir.listFiles()) { |
| 83 String to = "../../" + incrementalDexesDir.getName() + "/" + f.g
etName(); |
| 84 File from = new File(optimizedDir, f.getName()); |
| 85 createSymlink(to, from); |
| 86 } |
| 87 } |
| 88 |
| 89 Log.i(TAG, "Code cache dir: " + optimizedDir); |
| 90 // TODO(agrieve): Might need to record classpath ordering if we ever hav
e duplicate |
| 91 // class names (since then order will matter here). |
| 92 Log.i(TAG, "Loading " + dexFilesArr.length + " dex files"); |
| 93 |
| 94 Object dexPathList = Reflect.getField(mClassLoader, "pathList"); |
| 95 Object[] dexElements = (Object[]) Reflect.getField(dexPathList, "dexElem
ents"); |
| 96 Object[] additionalElements = makeDexElements(dexFilesArr, optimizedDir)
; |
| 97 Reflect.setField( |
| 98 dexPathList, "dexElements", Reflect.concatArrays(dexElements, ad
ditionalElements)); |
| 99 } |
| 100 |
| 101 /** |
| 102 * Sets up all libraries within |libDir| to be loadable by System.loadLibrar
y(). |
| 103 */ |
| 104 void importNativeLibs(File libDir) throws ReflectiveOperationException, IOEx
ception { |
| 105 Log.i(TAG, "Importing native libraries from: " + libDir); |
| 106 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
| 107 libDir = prepareNativeLibsAndroidM(libDir); |
| 108 } |
| 109 addNativeLibrarySearchPath(libDir); |
| 110 } |
| 111 |
| 112 /** |
| 113 * Primary process: Copies native libraries into the app's data directory |
| 114 * Other processes: Waits for primary process to finish copying. |
| 115 */ |
| 116 private File prepareNativeLibsAndroidM(File libDir) throws IOException { |
| 117 File localLibsDir = new File(mAppFilesSubDir, "lib"); |
| 118 File copyLibsLockFile = new File(mAppFilesSubDir, "libcopy.lock"); |
| 119 // Due to a new SELinux policy, all libs must be copied into the app's |
| 120 // data directory first. |
| 121 // https://code.google.com/p/android/issues/detail?id=79480 |
| 122 if (mIsPrimaryProcess) { |
| 123 LockFile lockFile = LockFile.acquireRuntimeLock(copyLibsLockFile); |
| 124 if (lockFile == null) { |
| 125 LockFile.waitForRuntimeLock(copyLibsLockFile, 10 * 1000); |
| 126 } else { |
| 127 try { |
| 128 ensureAppFilesSubDirExists(); |
| 129 localLibsDir.mkdir(); |
| 130 localLibsDir.setReadable(true, false); |
| 131 localLibsDir.setExecutable(true, false); |
| 132 copyChangedFiles(libDir, localLibsDir); |
| 133 } finally { |
| 134 lockFile.release(); |
| 135 } |
| 136 } |
| 137 } else { |
| 138 // TODO: Work around this issue by using APK splits to install each
dex / lib. |
| 139 throw new RuntimeException("Incremental install does not work on And
roid M+ " |
| 140 + "with isolated processes. Use the gn arg:\n" |
| 141 + " disable_incremental_isolated_processes=true\n" |
| 142 + "and try again."); |
| 143 } |
| 144 return localLibsDir; |
| 145 } |
| 146 |
| 147 @SuppressWarnings("unchecked") |
| 148 private void addNativeLibrarySearchPath(File nativeLibDir) throws Reflective
OperationException { |
| 149 Object dexPathList = Reflect.getField(mClassLoader, "pathList"); |
| 150 Object currentDirs = Reflect.getField(dexPathList, "nativeLibraryDirecto
ries"); |
| 151 File[] newDirs = new File[] { nativeLibDir }; |
| 152 // Switched from an array to an ArrayList in Lollipop. |
| 153 if (currentDirs instanceof List) { |
| 154 List<File> dirsAsList = (List<File>) currentDirs; |
| 155 dirsAsList.add(nativeLibDir); |
| 156 } else { |
| 157 File[] dirsAsArray = (File[]) currentDirs; |
| 158 Reflect.setField(dexPathList, "nativeLibraryDirectories", |
| 159 Reflect.concatArrays(dirsAsArray, newDirs)); |
| 160 } |
| 161 |
| 162 Object[] nativeLibraryPathElements; |
| 163 try { |
| 164 nativeLibraryPathElements = |
| 165 (Object[]) Reflect.getField(dexPathList, "nativeLibraryPathE
lements"); |
| 166 } catch (NoSuchFieldException e) { |
| 167 // This field doesn't exist pre-M. |
| 168 return; |
| 169 } |
| 170 Object[] additionalElements = makeNativePathElements(newDirs); |
| 171 Reflect.setField( |
| 172 dexPathList, "nativeLibraryPathElements", |
| 173 Reflect.concatArrays(nativeLibraryPathElements, additionalElemen
ts)); |
| 174 } |
| 175 |
| 176 private static void copyChangedFiles(File srcDir, File dstDir) throws IOExce
ption { |
| 177 // No need to delete stale libs since libraries are loaded explicitly. |
| 178 for (File f : srcDir.listFiles()) { |
| 179 // Note: Tried using hardlinks, but resulted in EACCES exceptions. |
| 180 File dest = new File(dstDir, f.getName()); |
| 181 copyIfModified(f, dest); |
| 182 } |
| 183 } |
| 184 |
| 185 private static void copyIfModified(File src, File dest) throws IOException { |
| 186 long lastModified = src.lastModified(); |
| 187 if (!dest.exists() || dest.lastModified() != lastModified) { |
| 188 Log.i(TAG, "Copying " + src + " -> " + dest); |
| 189 FileInputStream istream = new FileInputStream(src); |
| 190 FileOutputStream ostream = new FileOutputStream(dest); |
| 191 ostream.getChannel().transferFrom(istream.getChannel(), 0, istream.g
etChannel().size()); |
| 192 istream.close(); |
| 193 ostream.close(); |
| 194 dest.setReadable(true, false); |
| 195 dest.setExecutable(true, false); |
| 196 dest.setLastModified(lastModified); |
| 197 } |
| 198 } |
| 199 |
| 200 private void ensureAppFilesSubDirExists() { |
| 201 mAppFilesSubDir.mkdir(); |
| 202 mAppFilesSubDir.setExecutable(true, false); |
| 203 } |
| 204 |
| 205 private void createSymlink(String to, File from) throws ReflectiveOperationE
xception { |
| 206 Reflect.invokeMethod(mLibcoreOs, "symlink", to, from.getAbsolutePath()); |
| 207 } |
| 208 |
| 209 private static Object[] makeNativePathElements(File[] paths) |
| 210 throws ReflectiveOperationException { |
| 211 Class<?> entryClazz = Class.forName("dalvik.system.DexPathList$Element")
; |
| 212 Object[] entries = new Object[paths.length]; |
| 213 for (int i = 0; i < paths.length; ++i) { |
| 214 entries[i] = Reflect.newInstance(entryClazz, paths[i], true, null, n
ull); |
| 215 } |
| 216 return entries; |
| 217 } |
| 218 |
| 219 private static Object[] makeDexElements(File[] files, File optimizedDirector
y) |
| 220 throws ReflectiveOperationException { |
| 221 Class<?> entryClazz = Class.forName("dalvik.system.DexPathList$Element")
; |
| 222 Class<?> clazz = Class.forName("dalvik.system.DexPathList"); |
| 223 Object[] entries = new Object[files.length]; |
| 224 File emptyDir = new File(""); |
| 225 for (int i = 0; i < files.length; ++i) { |
| 226 File file = files[i]; |
| 227 Object dexFile = Reflect.invokeMethod(clazz, "loadDexFile", file, op
timizedDirectory); |
| 228 entries[i] = Reflect.newInstance(entryClazz, emptyDir, false, file,
dexFile); |
| 229 } |
| 230 return entries; |
| 231 } |
| 232 } |
OLD | NEW |