Home | History | Annotate | Download | only in system
      1 /*
      2  * Copyright (C) 2011 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 
     17 package dalvik.system;
     18 
     19 import android.system.ErrnoException;
     20 import android.system.StructStat;
     21 import java.io.File;
     22 import java.io.IOException;
     23 import java.net.MalformedURLException;
     24 import java.net.URL;
     25 import java.util.ArrayList;
     26 import java.util.Arrays;
     27 import java.util.Collections;
     28 import java.util.Enumeration;
     29 import java.util.List;
     30 import java.util.zip.ZipEntry;
     31 import libcore.io.IoUtils;
     32 import libcore.io.Libcore;
     33 import libcore.io.ClassPathURLStreamHandler;
     34 
     35 import static android.system.OsConstants.S_ISDIR;
     36 
     37 /**
     38  * A pair of lists of entries, associated with a {@code ClassLoader}.
     39  * One of the lists is a dex/resource path — typically referred
     40  * to as a "class path" — list, and the other names directories
     41  * containing native code libraries. Class path entries may be any of:
     42  * a {@code .jar} or {@code .zip} file containing an optional
     43  * top-level {@code classes.dex} file as well as arbitrary resources,
     44  * or a plain {@code .dex} file (with no possibility of associated
     45  * resources).
     46  *
     47  * <p>This class also contains methods to use these lists to look up
     48  * classes and resources.</p>
     49  */
     50 /*package*/ final class DexPathList {
     51     private static final String DEX_SUFFIX = ".dex";
     52     private static final String zipSeparator = "!/";
     53 
     54     /** class definition context */
     55     private final ClassLoader definingContext;
     56 
     57     /**
     58      * List of dex/resource (class path) elements.
     59      * Should be called pathElements, but the Facebook app uses reflection
     60      * to modify 'dexElements' (http://b/7726934).
     61      */
     62     private Element[] dexElements;
     63 
     64     /** List of native library path elements. */
     65     private final Element[] nativeLibraryPathElements;
     66 
     67     /** List of application native library directories. */
     68     private final List<File> nativeLibraryDirectories;
     69 
     70     /** List of system native library directories. */
     71     private final List<File> systemNativeLibraryDirectories;
     72 
     73     /**
     74      * Exceptions thrown during creation of the dexElements list.
     75      */
     76     private IOException[] dexElementsSuppressedExceptions;
     77 
     78     /**
     79      * Constructs an instance.
     80      *
     81      * @param definingContext the context in which any as-yet unresolved
     82      * classes should be defined
     83      * @param dexPath list of dex/resource path elements, separated by
     84      * {@code File.pathSeparator}
     85      * @param librarySearchPath list of native library directory path elements,
     86      * separated by {@code File.pathSeparator}
     87      * @param libraryPermittedPath is path containing permitted directories for
     88      * linker isolated namespaces (in addition to librarySearchPath which is allowed
     89      * implicitly). Note that this path does not affect the search order for the library
     90      * and intended for white-listing additional paths when loading native libraries
     91      * by absolute path.
     92      * @param optimizedDirectory directory where optimized {@code .dex} files
     93      * should be found and written to, or {@code null} to use the default
     94      * system directory for same
     95      */
     96     public DexPathList(ClassLoader definingContext, String dexPath,
     97             String librarySearchPath, File optimizedDirectory) {
     98 
     99         if (definingContext == null) {
    100             throw new NullPointerException("definingContext == null");
    101         }
    102 
    103         if (dexPath == null) {
    104             throw new NullPointerException("dexPath == null");
    105         }
    106 
    107         if (optimizedDirectory != null) {
    108             if (!optimizedDirectory.exists())  {
    109                 throw new IllegalArgumentException(
    110                         "optimizedDirectory doesn't exist: "
    111                         + optimizedDirectory);
    112             }
    113 
    114             if (!(optimizedDirectory.canRead()
    115                             && optimizedDirectory.canWrite())) {
    116                 throw new IllegalArgumentException(
    117                         "optimizedDirectory not readable/writable: "
    118                         + optimizedDirectory);
    119             }
    120         }
    121 
    122         this.definingContext = definingContext;
    123 
    124         ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
    125         // save dexPath for BaseDexClassLoader
    126         this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
    127                                            suppressedExceptions, definingContext);
    128 
    129         // Native libraries may exist in both the system and
    130         // application library paths, and we use this search order:
    131         //
    132         //   1. This class loader's library path for application libraries (librarySearchPath):
    133         //   1.1. Native library directories
    134         //   1.2. Path to libraries in apk-files
    135         //   2. The VM's library path from the system property for system libraries
    136         //      also known as java.library.path
    137         //
    138         // This order was reversed prior to Gingerbread; see http://b/2933456.
    139         this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);
    140         this.systemNativeLibraryDirectories =
    141                 splitPaths(System.getProperty("java.library.path"), true);
    142         List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
    143         allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
    144 
    145         this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories,
    146                                                           suppressedExceptions,
    147                                                           definingContext);
    148 
    149         if (suppressedExceptions.size() > 0) {
    150             this.dexElementsSuppressedExceptions =
    151                 suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
    152         } else {
    153             dexElementsSuppressedExceptions = null;
    154         }
    155     }
    156 
    157     @Override public String toString() {
    158         List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
    159         allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
    160 
    161         File[] nativeLibraryDirectoriesArray =
    162                 allNativeLibraryDirectories.toArray(
    163                     new File[allNativeLibraryDirectories.size()]);
    164 
    165         return "DexPathList[" + Arrays.toString(dexElements) +
    166             ",nativeLibraryDirectories=" + Arrays.toString(nativeLibraryDirectoriesArray) + "]";
    167     }
    168 
    169     /**
    170      * For BaseDexClassLoader.getLdLibraryPath.
    171      */
    172     public List<File> getNativeLibraryDirectories() {
    173         return nativeLibraryDirectories;
    174     }
    175 
    176     /**
    177      * Adds a new path to this instance
    178      * @param dexPath list of dex/resource path element, separated by
    179      * {@code File.pathSeparator}
    180      * @param optimizedDirectory directory where optimized {@code .dex} files
    181      * should be found and written to, or {@code null} to use the default
    182      * system directory for same
    183      */
    184     public void addDexPath(String dexPath, File optimizedDirectory) {
    185         final List<IOException> suppressedExceptionList = new ArrayList<IOException>();
    186         final Element[] newElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
    187                 suppressedExceptionList, definingContext);
    188 
    189         if (newElements != null && newElements.length > 0) {
    190             final Element[] oldElements = dexElements;
    191             dexElements = new Element[oldElements.length + newElements.length];
    192             System.arraycopy(
    193                     oldElements, 0, dexElements, 0, oldElements.length);
    194             System.arraycopy(
    195                     newElements, 0, dexElements, oldElements.length, newElements.length);
    196         }
    197 
    198         if (suppressedExceptionList.size() > 0) {
    199             final IOException[] newSuppressedExceptions = suppressedExceptionList.toArray(
    200                     new IOException[suppressedExceptionList.size()]);
    201             if (dexElementsSuppressedExceptions != null) {
    202                 final IOException[] oldSuppressedExceptions = dexElementsSuppressedExceptions;
    203                 final int suppressedExceptionsLength = oldSuppressedExceptions.length +
    204                         newSuppressedExceptions.length;
    205                 dexElementsSuppressedExceptions = new IOException[suppressedExceptionsLength];
    206                 System.arraycopy(oldSuppressedExceptions, 0, dexElementsSuppressedExceptions,
    207                         0, oldSuppressedExceptions.length);
    208                 System.arraycopy(newSuppressedExceptions, 0, dexElementsSuppressedExceptions,
    209                         oldSuppressedExceptions.length, newSuppressedExceptions.length);
    210             } else {
    211                 dexElementsSuppressedExceptions = newSuppressedExceptions;
    212             }
    213         }
    214     }
    215 
    216     /**
    217      * Splits the given dex path string into elements using the path
    218      * separator, pruning out any elements that do not refer to existing
    219      * and readable files.
    220      */
    221     private static List<File> splitDexPath(String path) {
    222         return splitPaths(path, false);
    223     }
    224 
    225     /**
    226      * Splits the given path strings into file elements using the path
    227      * separator, combining the results and filtering out elements
    228      * that don't exist, aren't readable, or aren't either a regular
    229      * file or a directory (as specified). Either string may be empty
    230      * or {@code null}, in which case it is ignored. If both strings
    231      * are empty or {@code null}, or all elements get pruned out, then
    232      * this returns a zero-element list.
    233      */
    234     private static List<File> splitPaths(String searchPath, boolean directoriesOnly) {
    235         List<File> result = new ArrayList<>();
    236 
    237         if (searchPath != null) {
    238             for (String path : searchPath.split(File.pathSeparator)) {
    239                 if (directoriesOnly) {
    240                     try {
    241                         StructStat sb = Libcore.os.stat(path);
    242                         if (!S_ISDIR(sb.st_mode)) {
    243                             continue;
    244                         }
    245                     } catch (ErrnoException ignored) {
    246                         continue;
    247                     }
    248                 }
    249                 result.add(new File(path));
    250             }
    251         }
    252 
    253         return result;
    254     }
    255 
    256     /**
    257      * Makes an array of dex/resource path elements, one per element of
    258      * the given array.
    259      */
    260     private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
    261                                              List<IOException> suppressedExceptions,
    262                                              ClassLoader loader) {
    263         return makeElements(files, optimizedDirectory, suppressedExceptions, false, loader);
    264     }
    265 
    266     /**
    267      * Makes an array of directory/zip path elements, one per element of the given array.
    268      */
    269     private static Element[] makePathElements(List<File> files,
    270                                               List<IOException> suppressedExceptions,
    271                                               ClassLoader loader) {
    272         return makeElements(files, null, suppressedExceptions, true, loader);
    273     }
    274 
    275     /*
    276      * TODO (dimitry): Revert after apps stops relying on the existence of this
    277      * method (see http://b/21957414 and http://b/26317852 for details)
    278      */
    279     private static Element[] makePathElements(List<File> files, File optimizedDirectory,
    280                                               List<IOException> suppressedExceptions) {
    281         return makeElements(files, optimizedDirectory, suppressedExceptions, false, null);
    282     }
    283 
    284     private static Element[] makeElements(List<File> files, File optimizedDirectory,
    285                                           List<IOException> suppressedExceptions,
    286                                           boolean ignoreDexFiles,
    287                                           ClassLoader loader) {
    288         Element[] elements = new Element[files.size()];
    289         int elementsPos = 0;
    290         /*
    291          * Open all files and load the (direct or contained) dex files
    292          * up front.
    293          */
    294         for (File file : files) {
    295             File zip = null;
    296             File dir = new File("");
    297             DexFile dex = null;
    298             String path = file.getPath();
    299             String name = file.getName();
    300 
    301             if (path.contains(zipSeparator)) {
    302                 String split[] = path.split(zipSeparator, 2);
    303                 zip = new File(split[0]);
    304                 dir = new File(split[1]);
    305             } else if (file.isDirectory()) {
    306                 // We support directories for looking up resources and native libraries.
    307                 // Looking up resources in directories is useful for running libcore tests.
    308                 elements[elementsPos++] = new Element(file, true, null, null);
    309             } else if (file.isFile()) {
    310                 if (!ignoreDexFiles && name.endsWith(DEX_SUFFIX)) {
    311                     // Raw dex file (not inside a zip/jar).
    312                     try {
    313                         dex = loadDexFile(file, optimizedDirectory, loader, elements);
    314                     } catch (IOException suppressed) {
    315                         System.logE("Unable to load dex file: " + file, suppressed);
    316                         suppressedExceptions.add(suppressed);
    317                     }
    318                 } else {
    319                     zip = file;
    320 
    321                     if (!ignoreDexFiles) {
    322                         try {
    323                             dex = loadDexFile(file, optimizedDirectory, loader, elements);
    324                         } catch (IOException suppressed) {
    325                             /*
    326                              * IOException might get thrown "legitimately" by the DexFile constructor if
    327                              * the zip file turns out to be resource-only (that is, no classes.dex file
    328                              * in it).
    329                              * Let dex == null and hang on to the exception to add to the tea-leaves for
    330                              * when findClass returns null.
    331                              */
    332                             suppressedExceptions.add(suppressed);
    333                         }
    334                     }
    335                 }
    336             } else {
    337                 System.logW("ClassLoader referenced unknown path: " + file);
    338             }
    339 
    340             if ((zip != null) || (dex != null)) {
    341                 elements[elementsPos++] = new Element(dir, false, zip, dex);
    342             }
    343         }
    344         if (elementsPos != elements.length) {
    345             elements = Arrays.copyOf(elements, elementsPos);
    346         }
    347         return elements;
    348     }
    349 
    350     /**
    351      * Constructs a {@code DexFile} instance, as appropriate depending on whether
    352      * {@code optimizedDirectory} is {@code null}. An application image file may be associated with
    353      * the {@code loader} if it is not null.
    354      */
    355     private static DexFile loadDexFile(File file, File optimizedDirectory, ClassLoader loader,
    356                                        Element[] elements)
    357             throws IOException {
    358         if (optimizedDirectory == null) {
    359             return new DexFile(file, loader, elements);
    360         } else {
    361             String optimizedPath = optimizedPathFor(file, optimizedDirectory);
    362             return DexFile.loadDex(file.getPath(), optimizedPath, 0, loader, elements);
    363         }
    364     }
    365 
    366     /**
    367      * Converts a dex/jar file path and an output directory to an
    368      * output file path for an associated optimized dex file.
    369      */
    370     private static String optimizedPathFor(File path,
    371             File optimizedDirectory) {
    372         /*
    373          * Get the filename component of the path, and replace the
    374          * suffix with ".dex" if that's not already the suffix.
    375          *
    376          * We don't want to use ".odex", because the build system uses
    377          * that for files that are paired with resource-only jar
    378          * files. If the VM can assume that there's no classes.dex in
    379          * the matching jar, it doesn't need to open the jar to check
    380          * for updated dependencies, providing a slight performance
    381          * boost at startup. The use of ".dex" here matches the use on
    382          * files in /data/dalvik-cache.
    383          */
    384         String fileName = path.getName();
    385         if (!fileName.endsWith(DEX_SUFFIX)) {
    386             int lastDot = fileName.lastIndexOf(".");
    387             if (lastDot < 0) {
    388                 fileName += DEX_SUFFIX;
    389             } else {
    390                 StringBuilder sb = new StringBuilder(lastDot + 4);
    391                 sb.append(fileName, 0, lastDot);
    392                 sb.append(DEX_SUFFIX);
    393                 fileName = sb.toString();
    394             }
    395         }
    396 
    397         File result = new File(optimizedDirectory, fileName);
    398         return result.getPath();
    399     }
    400 
    401     /**
    402      * Finds the named class in one of the dex files pointed at by
    403      * this instance. This will find the one in the earliest listed
    404      * path element. If the class is found but has not yet been
    405      * defined, then this method will define it in the defining
    406      * context that this instance was constructed with.
    407      *
    408      * @param name of class to find
    409      * @param suppressed exceptions encountered whilst finding the class
    410      * @return the named class or {@code null} if the class is not
    411      * found in any of the dex files
    412      */
    413     public Class findClass(String name, List<Throwable> suppressed) {
    414         for (Element element : dexElements) {
    415             DexFile dex = element.dexFile;
    416 
    417             if (dex != null) {
    418                 Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
    419                 if (clazz != null) {
    420                     return clazz;
    421                 }
    422             }
    423         }
    424         if (dexElementsSuppressedExceptions != null) {
    425             suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
    426         }
    427         return null;
    428     }
    429 
    430     /**
    431      * Finds the named resource in one of the zip/jar files pointed at
    432      * by this instance. This will find the one in the earliest listed
    433      * path element.
    434      *
    435      * @return a URL to the named resource or {@code null} if the
    436      * resource is not found in any of the zip/jar files
    437      */
    438     public URL findResource(String name) {
    439         for (Element element : dexElements) {
    440             URL url = element.findResource(name);
    441             if (url != null) {
    442                 return url;
    443             }
    444         }
    445 
    446         return null;
    447     }
    448 
    449     /**
    450      * Finds all the resources with the given name, returning an
    451      * enumeration of them. If there are no resources with the given
    452      * name, then this method returns an empty enumeration.
    453      */
    454     public Enumeration<URL> findResources(String name) {
    455         ArrayList<URL> result = new ArrayList<URL>();
    456 
    457         for (Element element : dexElements) {
    458             URL url = element.findResource(name);
    459             if (url != null) {
    460                 result.add(url);
    461             }
    462         }
    463 
    464         return Collections.enumeration(result);
    465     }
    466 
    467     /**
    468      * Finds the named native code library on any of the library
    469      * directories pointed at by this instance. This will find the
    470      * one in the earliest listed directory, ignoring any that are not
    471      * readable regular files.
    472      *
    473      * @return the complete path to the library or {@code null} if no
    474      * library was found
    475      */
    476     public String findLibrary(String libraryName) {
    477         String fileName = System.mapLibraryName(libraryName);
    478 
    479         for (Element element : nativeLibraryPathElements) {
    480             String path = element.findNativeLibrary(fileName);
    481 
    482             if (path != null) {
    483                 return path;
    484             }
    485         }
    486 
    487         return null;
    488     }
    489 
    490     /**
    491      * Element of the dex/resource/native library path
    492      */
    493     /*package*/ static class Element {
    494         private final File dir;
    495         private final boolean isDirectory;
    496         private final File zip;
    497         private final DexFile dexFile;
    498 
    499         private ClassPathURLStreamHandler urlHandler;
    500         private boolean initialized;
    501 
    502         public Element(File dir, boolean isDirectory, File zip, DexFile dexFile) {
    503             this.dir = dir;
    504             this.isDirectory = isDirectory;
    505             this.zip = zip;
    506             this.dexFile = dexFile;
    507         }
    508 
    509         @Override public String toString() {
    510             if (isDirectory) {
    511                 return "directory \"" + dir + "\"";
    512             } else if (zip != null) {
    513                 return "zip file \"" + zip + "\"" +
    514                        (dir != null && !dir.getPath().isEmpty() ? ", dir \"" + dir + "\"" : "");
    515             } else {
    516                 return "dex file \"" + dexFile + "\"";
    517             }
    518         }
    519 
    520         public synchronized void maybeInit() {
    521             if (initialized) {
    522                 return;
    523             }
    524 
    525             initialized = true;
    526 
    527             if (isDirectory || zip == null) {
    528                 return;
    529             }
    530 
    531             try {
    532                 urlHandler = new ClassPathURLStreamHandler(zip.getPath());
    533             } catch (IOException ioe) {
    534                 /*
    535                  * Note: ZipException (a subclass of IOException)
    536                  * might get thrown by the ZipFile constructor
    537                  * (e.g. if the file isn't actually a zip/jar
    538                  * file).
    539                  */
    540                 System.logE("Unable to open zip file: " + zip, ioe);
    541                 urlHandler = null;
    542             }
    543         }
    544 
    545         public String findNativeLibrary(String name) {
    546             maybeInit();
    547 
    548             if (isDirectory) {
    549                 String path = new File(dir, name).getPath();
    550                 if (IoUtils.canOpenReadOnly(path)) {
    551                     return path;
    552                 }
    553             } else if (urlHandler != null) {
    554                 // Having a urlHandler means the element has a zip file.
    555                 // In this case Android supports loading the library iff
    556                 // it is stored in the zip uncompressed.
    557 
    558                 String entryName = new File(dir, name).getPath();
    559                 if (urlHandler.isEntryStored(entryName)) {
    560                   return zip.getPath() + zipSeparator + entryName;
    561                 }
    562             }
    563 
    564             return null;
    565         }
    566 
    567         public URL findResource(String name) {
    568             maybeInit();
    569 
    570             // We support directories so we can run tests and/or legacy code
    571             // that uses Class.getResource.
    572             if (isDirectory) {
    573                 File resourceFile = new File(dir, name);
    574                 if (resourceFile.exists()) {
    575                     try {
    576                         return resourceFile.toURI().toURL();
    577                     } catch (MalformedURLException ex) {
    578                         throw new RuntimeException(ex);
    579                     }
    580                 }
    581             }
    582 
    583             if (urlHandler == null) {
    584                 /* This element has no zip/jar file.
    585                  */
    586                 return null;
    587             }
    588             return urlHandler.getEntryUrlOrNull(name);
    589         }
    590     }
    591 }
    592