• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 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.FileNotFoundException;
23 import java.io.IOException;
24 import java.util.ArrayList;
25 import java.util.Enumeration;
26 import java.util.List;
27 import libcore.io.Libcore;
28 
29 /**
30  * Manipulates DEX files. The class is similar in principle to
31  * {@link java.util.zip.ZipFile}. It is used primarily by class loaders.
32  * <p>
33  * Note we don't directly open and read the DEX file here. They're memory-mapped
34  * read-only by the VM.
35  */
36 public final class DexFile {
37     private long mCookie;
38     private final String mFileName;
39     private final CloseGuard guard = CloseGuard.get();
40 
41     /**
42      * Opens a DEX file from a given File object. This will usually be a ZIP/JAR
43      * file with a "classes.dex" inside.
44      *
45      * The VM will generate the name of the corresponding file in
46      * /data/dalvik-cache and open it, possibly creating or updating
47      * it first if system permissions allow.  Don't pass in the name of
48      * a file in /data/dalvik-cache, as the named file is expected to be
49      * in its original (pre-dexopt) state.
50      *
51      * @param file
52      *            the File object referencing the actual DEX file
53      *
54      * @throws IOException
55      *             if an I/O error occurs, such as the file not being found or
56      *             access rights missing for opening it
57      */
DexFile(File file)58     public DexFile(File file) throws IOException {
59         this(file.getPath());
60     }
61 
62     /**
63      * Opens a DEX file from a given filename. This will usually be a ZIP/JAR
64      * file with a "classes.dex" inside.
65      *
66      * The VM will generate the name of the corresponding file in
67      * /data/dalvik-cache and open it, possibly creating or updating
68      * it first if system permissions allow.  Don't pass in the name of
69      * a file in /data/dalvik-cache, as the named file is expected to be
70      * in its original (pre-dexopt) state.
71      *
72      * @param fileName
73      *            the filename of the DEX file
74      *
75      * @throws IOException
76      *             if an I/O error occurs, such as the file not being found or
77      *             access rights missing for opening it
78      */
DexFile(String fileName)79     public DexFile(String fileName) throws IOException {
80         mCookie = openDexFile(fileName, null, 0);
81         mFileName = fileName;
82         guard.open("close");
83         //System.out.println("DEX FILE cookie is " + mCookie + " fileName=" + fileName);
84     }
85 
86     /**
87      * Opens a DEX file from a given filename, using a specified file
88      * to hold the optimized data.
89      *
90      * @param sourceName
91      *  Jar or APK file with "classes.dex".
92      * @param outputName
93      *  File that will hold the optimized form of the DEX data.
94      * @param flags
95      *  Enable optional features.
96      */
DexFile(String sourceName, String outputName, int flags)97     private DexFile(String sourceName, String outputName, int flags) throws IOException {
98         if (outputName != null) {
99             try {
100                 String parent = new File(outputName).getParent();
101                 if (Libcore.os.getuid() != Libcore.os.stat(parent).st_uid) {
102                     throw new IllegalArgumentException("Optimized data directory " + parent
103                             + " is not owned by the current user. Shared storage cannot protect"
104                             + " your application from code injection attacks.");
105                 }
106             } catch (ErrnoException ignored) {
107                 // assume we'll fail with a more contextual error later
108             }
109         }
110 
111         mCookie = openDexFile(sourceName, outputName, flags);
112         mFileName = sourceName;
113         guard.open("close");
114         //System.out.println("DEX FILE cookie is " + mCookie + " sourceName=" + sourceName + " outputName=" + outputName);
115     }
116 
117     /**
118      * Open a DEX file, specifying the file in which the optimized DEX
119      * data should be written.  If the optimized form exists and appears
120      * to be current, it will be used; if not, the VM will attempt to
121      * regenerate it.
122      *
123      * This is intended for use by applications that wish to download
124      * and execute DEX files outside the usual application installation
125      * mechanism.  This function should not be called directly by an
126      * application; instead, use a class loader such as
127      * dalvik.system.DexClassLoader.
128      *
129      * @param sourcePathName
130      *  Jar or APK file with "classes.dex".  (May expand this to include
131      *  "raw DEX" in the future.)
132      * @param outputPathName
133      *  File that will hold the optimized form of the DEX data.
134      * @param flags
135      *  Enable optional features.  (Currently none defined.)
136      * @return
137      *  A new or previously-opened DexFile.
138      * @throws IOException
139      *  If unable to open the source or output file.
140      */
loadDex(String sourcePathName, String outputPathName, int flags)141     static public DexFile loadDex(String sourcePathName, String outputPathName,
142         int flags) throws IOException {
143 
144         /*
145          * TODO: we may want to cache previously-opened DexFile objects.
146          * The cache would be synchronized with close().  This would help
147          * us avoid mapping the same DEX more than once when an app
148          * decided to open it multiple times.  In practice this may not
149          * be a real issue.
150          */
151         return new DexFile(sourcePathName, outputPathName, flags);
152     }
153 
154     /**
155      * Gets the name of the (already opened) DEX file.
156      *
157      * @return the file name
158      */
getName()159     public String getName() {
160         return mFileName;
161     }
162 
toString()163     @Override public String toString() {
164         return getName();
165     }
166 
167     /**
168      * Closes the DEX file.
169      * <p>
170      * This may not be able to release any resources. If classes from this
171      * DEX file are still resident, the DEX file can't be unmapped.
172      *
173      * @throws IOException
174      *             if an I/O error occurs during closing the file, which
175      *             normally should not happen
176      */
close()177     public void close() throws IOException {
178         if (mCookie != 0) {
179             guard.close();
180             closeDexFile(mCookie);
181             mCookie = 0;
182         }
183     }
184 
185     /**
186      * Loads a class. Returns the class on success, or a {@code null} reference
187      * on failure.
188      * <p>
189      * If you are not calling this from a class loader, this is most likely not
190      * going to do what you want. Use {@link Class#forName(String)} instead.
191      * <p>
192      * The method does not throw {@link ClassNotFoundException} if the class
193      * isn't found because it isn't reasonable to throw exceptions wildly every
194      * time a class is not found in the first DEX file we look at.
195      *
196      * @param name
197      *            the class name, which should look like "java/lang/String"
198      *
199      * @param loader
200      *            the class loader that tries to load the class (in most cases
201      *            the caller of the method
202      *
203      * @return the {@link Class} object representing the class, or {@code null}
204      *         if the class cannot be loaded
205      */
loadClass(String name, ClassLoader loader)206     public Class loadClass(String name, ClassLoader loader) {
207         String slashName = name.replace('.', '/');
208         return loadClassBinaryName(slashName, loader, null);
209     }
210 
211     /**
212      * See {@link #loadClass(String, ClassLoader)}.
213      *
214      * This takes a "binary" class name to better match ClassLoader semantics.
215      *
216      * @hide
217      */
loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed)218     public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
219         return defineClass(name, loader, mCookie, suppressed);
220     }
221 
defineClass(String name, ClassLoader loader, long cookie, List<Throwable> suppressed)222     private static Class defineClass(String name, ClassLoader loader, long cookie,
223                                      List<Throwable> suppressed) {
224         Class result = null;
225         try {
226             result = defineClassNative(name, loader, cookie);
227         } catch (NoClassDefFoundError e) {
228             if (suppressed != null) {
229                 suppressed.add(e);
230             }
231         } catch (ClassNotFoundException e) {
232             if (suppressed != null) {
233                 suppressed.add(e);
234             }
235         }
236         return result;
237     }
238 
239     /**
240      * Enumerate the names of the classes in this DEX file.
241      *
242      * @return an enumeration of names of classes contained in the DEX file, in
243      *         the usual internal form (like "java/lang/String").
244      */
entries()245     public Enumeration<String> entries() {
246         return new DFEnum(this);
247     }
248 
249     /*
250      * Helper class.
251      */
252     private class DFEnum implements Enumeration<String> {
253         private int mIndex;
254         private String[] mNameList;
255 
DFEnum(DexFile df)256         DFEnum(DexFile df) {
257             mIndex = 0;
258             mNameList = getClassNameList(mCookie);
259         }
260 
hasMoreElements()261         public boolean hasMoreElements() {
262             return (mIndex < mNameList.length);
263         }
264 
nextElement()265         public String nextElement() {
266             return mNameList[mIndex++];
267         }
268     }
269 
270     /**
271      * Called when the class is finalized. Makes sure the DEX file is closed.
272      *
273      * @throws IOException
274      *             if an I/O error occurs during closing the file, which
275      *             normally should not happen
276      */
finalize()277     @Override protected void finalize() throws Throwable {
278         try {
279             if (guard != null) {
280                 guard.warnIfOpen();
281             }
282             close();
283         } finally {
284             super.finalize();
285         }
286     }
287 
288 
289     /*
290      * Open a DEX file.  The value returned is a magic VM cookie.  On
291      * failure, an IOException is thrown.
292      */
openDexFile(String sourceName, String outputName, int flags)293     private static long openDexFile(String sourceName, String outputName, int flags) throws IOException {
294         // Use absolute paths to enable the use of relative paths when testing on host.
295         return openDexFileNative(new File(sourceName).getAbsolutePath(),
296                                  (outputName == null) ? null : new File(outputName).getAbsolutePath(),
297                                  flags);
298     }
299 
closeDexFile(long cookie)300     private static native void closeDexFile(long cookie);
defineClassNative(String name, ClassLoader loader, long cookie)301     private static native Class defineClassNative(String name, ClassLoader loader, long cookie)
302             throws ClassNotFoundException, NoClassDefFoundError;
getClassNameList(long cookie)303     private static native String[] getClassNameList(long cookie);
304     /*
305      * Open a DEX file.  The value returned is a magic VM cookie.  On
306      * failure, an IOException is thrown.
307      */
openDexFileNative(String sourceName, String outputName, int flags)308     private static native long openDexFileNative(String sourceName, String outputName, int flags);
309 
310     /**
311      * Returns true if the VM believes that the apk/jar file is out of date
312      * and should be passed through "dexopt" again.
313      *
314      * @param fileName the absolute path to the apk/jar file to examine.
315      * @return true if dexopt should be called on the file, false otherwise.
316      * @throws java.io.FileNotFoundException if fileName is not readable,
317      *         not a file, or not present.
318      * @throws java.io.IOException if fileName is not a valid apk/jar file or
319      *         if problems occur while parsing it.
320      * @throws java.lang.NullPointerException if fileName is null.
321      * @throws dalvik.system.StaleDexCacheError if the optimized dex file
322      *         is stale but exists on a read-only partition.
323      */
isDexOptNeeded(String fileName)324     public static native boolean isDexOptNeeded(String fileName)
325             throws FileNotFoundException, IOException;
326 
327     /**
328      * See {@link #isDexOptNeededInternal(String, String, String, boolean)}.
329      *
330      * @hide
331      */
332     public static final byte UP_TO_DATE = 0;
333 
334     /**
335      * See {@link #isDexOptNeededInternal(String, String, String, boolean)}.
336      *
337      * @hide
338      */
339     public static final byte PATCHOAT_NEEDED = 1;
340 
341     /**
342      * See {@link #isDexOptNeededInternal(String, String, String, boolean)}.
343      *
344      * @hide
345      */
346     public static final byte DEXOPT_NEEDED = 2;
347 
348     /**
349      * Returns UP_TO_DATE if the VM believes that the apk/jar file
350      * is up to date, PATCHOAT_NEEDED if it believes that the file is up
351      * to date but it must be relocated to match the base address offset,
352      * and DEXOPT_NEEDED if it believes that it is out of date and should
353      * be passed through "dexopt" again.
354      *
355      * @param fileName the absolute path to the apk/jar file to examine.
356      * @return DEXOPT_NEEDED if dexopt should be called on the file,
357      *         PATCHOAT_NEEDED if we need to run "patchoat" on it and
358      *         UP_TO_DATE otherwise.
359      * @throws java.io.FileNotFoundException if fileName is not readable,
360      *         not a file, or not present.
361      * @throws java.io.IOException if fileName is not a valid apk/jar file or
362      *         if problems occur while parsing it.
363      * @throws java.lang.NullPointerException if fileName is null.
364      * @throws dalvik.system.StaleDexCacheError if the optimized dex file
365      *         is stale but exists on a read-only partition.
366      *
367      * @hide
368      */
isDexOptNeededInternal(String fileName, String pkgname, String instructionSet, boolean defer)369     public static native byte isDexOptNeededInternal(String fileName, String pkgname,
370             String instructionSet, boolean defer)
371             throws FileNotFoundException, IOException;
372 }
373