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
17package dalvik.system;
18
19import android.system.ErrnoException;
20import android.system.StructStat;
21import java.io.File;
22import java.io.FileNotFoundException;
23import java.io.IOException;
24import java.util.ArrayList;
25import java.util.Enumeration;
26import java.util.List;
27import 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 */
36public final class DexFile {
37  /**
38   * If close is called, mCookie becomes null but the internal cookie is preserved if the close
39   * failed so that we can free resources in the finalizer.
40   */
41    private Object mCookie;
42    private Object mInternalCookie;
43    private final String mFileName;
44
45    /**
46     * Opens a DEX file from a given File object. This will usually be a ZIP/JAR
47     * file with a "classes.dex" inside.
48     *
49     * The VM will generate the name of the corresponding file in
50     * /data/dalvik-cache and open it, possibly creating or updating
51     * it first if system permissions allow.  Don't pass in the name of
52     * a file in /data/dalvik-cache, as the named file is expected to be
53     * in its original (pre-dexopt) state.
54     *
55     * @param file
56     *            the File object referencing the actual DEX file
57     *
58     * @throws IOException
59     *             if an I/O error occurs, such as the file not being found or
60     *             access rights missing for opening it
61     */
62    public DexFile(File file) throws IOException {
63        this(file.getPath());
64    }
65    /*
66     * Private version with class loader argument.
67     *
68     * @param file
69     *            the File object referencing the actual DEX file
70     * @param loader
71     *            the class loader object creating the DEX file object
72     * @param elements
73     *            the temporary dex path list elements from DexPathList.makeElements
74     */
75    DexFile(File file, ClassLoader loader, DexPathList.Element[] elements)
76            throws IOException {
77        this(file.getPath(), loader, elements);
78    }
79
80    /**
81     * Opens a DEX file from a given filename. This will usually be a ZIP/JAR
82     * file with a "classes.dex" inside.
83     *
84     * The VM will generate the name of the corresponding file in
85     * /data/dalvik-cache and open it, possibly creating or updating
86     * it first if system permissions allow.  Don't pass in the name of
87     * a file in /data/dalvik-cache, as the named file is expected to be
88     * in its original (pre-dexopt) state.
89     *
90     * @param fileName
91     *            the filename of the DEX file
92     *
93     * @throws IOException
94     *             if an I/O error occurs, such as the file not being found or
95     *             access rights missing for opening it
96     */
97    public DexFile(String fileName) throws IOException {
98        this(fileName, null, null);
99    }
100
101    /*
102     * Private version with class loader argument.
103     *
104     * @param fileName
105     *            the filename of the DEX file
106     * @param loader
107     *            the class loader creating the DEX file object
108     * @param elements
109     *            the temporary dex path list elements from DexPathList.makeElements
110     */
111    DexFile(String fileName, ClassLoader loader, DexPathList.Element[] elements) throws IOException {
112        mCookie = openDexFile(fileName, null, 0, loader, elements);
113        mInternalCookie = mCookie;
114        mFileName = fileName;
115        //System.out.println("DEX FILE cookie is " + mCookie + " fileName=" + fileName);
116    }
117
118    /**
119     * Opens a DEX file from a given filename, using a specified file
120     * to hold the optimized data.
121     *
122     * @param sourceName
123     *  Jar or APK file with "classes.dex".
124     * @param outputName
125     *  File that will hold the optimized form of the DEX data.
126     * @param flags
127     *  Enable optional features.
128     * @param loader
129     *  The class loader creating the DEX file object.
130     * @param elements
131     *  The temporary dex path list elements from DexPathList.makeElements
132     */
133    private DexFile(String sourceName, String outputName, int flags, ClassLoader loader,
134            DexPathList.Element[] elements) throws IOException {
135        if (outputName != null) {
136            try {
137                String parent = new File(outputName).getParent();
138                if (Libcore.os.getuid() != Libcore.os.stat(parent).st_uid) {
139                    throw new IllegalArgumentException("Optimized data directory " + parent
140                            + " is not owned by the current user. Shared storage cannot protect"
141                            + " your application from code injection attacks.");
142                }
143            } catch (ErrnoException ignored) {
144                // assume we'll fail with a more contextual error later
145            }
146        }
147
148        mCookie = openDexFile(sourceName, outputName, flags, loader, elements);
149        mFileName = sourceName;
150        //System.out.println("DEX FILE cookie is " + mCookie + " sourceName=" + sourceName + " outputName=" + outputName);
151    }
152
153    /**
154     * Open a DEX file, specifying the file in which the optimized DEX
155     * data should be written.  If the optimized form exists and appears
156     * to be current, it will be used; if not, the VM will attempt to
157     * regenerate it.
158     *
159     * This is intended for use by applications that wish to download
160     * and execute DEX files outside the usual application installation
161     * mechanism.  This function should not be called directly by an
162     * application; instead, use a class loader such as
163     * dalvik.system.DexClassLoader.
164     *
165     * @param sourcePathName
166     *  Jar or APK file with "classes.dex".  (May expand this to include
167     *  "raw DEX" in the future.)
168     * @param outputPathName
169     *  File that will hold the optimized form of the DEX data.
170     * @param flags
171     *  Enable optional features.  (Currently none defined.)
172     * @return
173     *  A new or previously-opened DexFile.
174     * @throws IOException
175     *  If unable to open the source or output file.
176     */
177    static public DexFile loadDex(String sourcePathName, String outputPathName,
178        int flags) throws IOException {
179
180        /*
181         * TODO: we may want to cache previously-opened DexFile objects.
182         * The cache would be synchronized with close().  This would help
183         * us avoid mapping the same DEX more than once when an app
184         * decided to open it multiple times.  In practice this may not
185         * be a real issue.
186         */
187        return loadDex(sourcePathName, outputPathName, flags, null, null);
188    }
189
190    /*
191     * Private version of loadDex that also takes a class loader.
192     *
193     * @param sourcePathName
194     *  Jar or APK file with "classes.dex".  (May expand this to include
195     *  "raw DEX" in the future.)
196     * @param outputPathName
197     *  File that will hold the optimized form of the DEX data.
198     * @param flags
199     *  Enable optional features.  (Currently none defined.)
200     * @param loader
201     *  Class loader that is aloading the DEX file.
202     * @param elements
203     *  The temporary dex path list elements from DexPathList.makeElements
204     * @return
205     *  A new or previously-opened DexFile.
206     * @throws IOException
207     *  If unable to open the source or output file.
208     */
209    static DexFile loadDex(String sourcePathName, String outputPathName,
210        int flags, ClassLoader loader, DexPathList.Element[] elements) throws IOException {
211
212        /*
213         * TODO: we may want to cache previously-opened DexFile objects.
214         * The cache would be synchronized with close().  This would help
215         * us avoid mapping the same DEX more than once when an app
216         * decided to open it multiple times.  In practice this may not
217         * be a real issue.
218         */
219        return new DexFile(sourcePathName, outputPathName, flags, loader, elements);
220    }
221
222    /**
223     * Gets the name of the (already opened) DEX file.
224     *
225     * @return the file name
226     */
227    public String getName() {
228        return mFileName;
229    }
230
231    @Override public String toString() {
232        return getName();
233    }
234
235    /**
236     * Closes the DEX file.
237     * <p>
238     * This may not be able to release all of the resources. If classes from this DEX file are
239     * still resident, the DEX file can't be unmapped. In the case where we do not release all
240     * the resources, close is called again in the finalizer.
241     *
242     * @throws IOException
243     *             if an I/O error occurs during closing the file, which
244     *             normally should not happen
245     */
246    public void close() throws IOException {
247        if (mInternalCookie != null) {
248            if (closeDexFile(mInternalCookie)) {
249                mInternalCookie = null;
250            }
251            mCookie = null;
252        }
253    }
254
255    /**
256     * Loads a class. Returns the class on success, or a {@code null} reference
257     * on failure.
258     * <p>
259     * If you are not calling this from a class loader, this is most likely not
260     * going to do what you want. Use {@link Class#forName(String)} instead.
261     * <p>
262     * The method does not throw {@link ClassNotFoundException} if the class
263     * isn't found because it isn't reasonable to throw exceptions wildly every
264     * time a class is not found in the first DEX file we look at.
265     *
266     * @param name
267     *            the class name, which should look like "java/lang/String"
268     *
269     * @param loader
270     *            the class loader that tries to load the class (in most cases
271     *            the caller of the method
272     *
273     * @return the {@link Class} object representing the class, or {@code null}
274     *         if the class cannot be loaded
275     */
276    public Class loadClass(String name, ClassLoader loader) {
277        String slashName = name.replace('.', '/');
278        return loadClassBinaryName(slashName, loader, null);
279    }
280
281    /**
282     * See {@link #loadClass(String, ClassLoader)}.
283     *
284     * This takes a "binary" class name to better match ClassLoader semantics.
285     *
286     * @hide
287     */
288    public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
289        return defineClass(name, loader, mCookie, this, suppressed);
290    }
291
292    private static Class defineClass(String name, ClassLoader loader, Object cookie,
293                                     DexFile dexFile, List<Throwable> suppressed) {
294        Class result = null;
295        try {
296            result = defineClassNative(name, loader, cookie, dexFile);
297        } catch (NoClassDefFoundError e) {
298            if (suppressed != null) {
299                suppressed.add(e);
300            }
301        } catch (ClassNotFoundException e) {
302            if (suppressed != null) {
303                suppressed.add(e);
304            }
305        }
306        return result;
307    }
308
309    /**
310     * Enumerate the names of the classes in this DEX file.
311     *
312     * @return an enumeration of names of classes contained in the DEX file, in
313     *         the usual internal form (like "java/lang/String").
314     */
315    public Enumeration<String> entries() {
316        return new DFEnum(this);
317    }
318
319    /*
320     * Helper class.
321     */
322    private class DFEnum implements Enumeration<String> {
323        private int mIndex;
324        private String[] mNameList;
325
326        DFEnum(DexFile df) {
327            mIndex = 0;
328            mNameList = getClassNameList(mCookie);
329        }
330
331        public boolean hasMoreElements() {
332            return (mIndex < mNameList.length);
333        }
334
335        public String nextElement() {
336            return mNameList[mIndex++];
337        }
338    }
339
340    /**
341     * Called when the class is finalized. Makes sure the DEX file is closed.
342     *
343     * @throws IOException
344     *             if an I/O error occurs during closing the file, which
345     *             normally should not happen
346     */
347    @Override protected void finalize() throws Throwable {
348        try {
349            if (mInternalCookie != null && !closeDexFile(mInternalCookie)) {
350                throw new AssertionError("Failed to close dex file in finalizer.");
351            }
352            mInternalCookie = null;
353            mCookie = null;
354        } finally {
355            super.finalize();
356        }
357    }
358
359
360    /*
361     * Open a DEX file.  The value returned is a magic VM cookie.  On
362     * failure, an IOException is thrown.
363     */
364    private static Object openDexFile(String sourceName, String outputName, int flags,
365            ClassLoader loader, DexPathList.Element[] elements) throws IOException {
366        // Use absolute paths to enable the use of relative paths when testing on host.
367        return openDexFileNative(new File(sourceName).getAbsolutePath(),
368                                 (outputName == null)
369                                     ? null
370                                     : new File(outputName).getAbsolutePath(),
371                                 flags,
372                                 loader,
373                                 elements);
374    }
375
376    /*
377     * Returns true if the dex file is backed by a valid oat file.
378     */
379    /*package*/ boolean isBackedByOatFile() {
380        return isBackedByOatFile(mCookie);
381    }
382
383    /*
384     * Returns true if we managed to close the dex file.
385     */
386    private static native boolean closeDexFile(Object cookie);
387    private static native Class defineClassNative(String name, ClassLoader loader, Object cookie,
388                                                  DexFile dexFile)
389            throws ClassNotFoundException, NoClassDefFoundError;
390    private static native String[] getClassNameList(Object cookie);
391    private static native boolean isBackedByOatFile(Object cookie);
392    /*
393     * Open a DEX file.  The value returned is a magic VM cookie.  On
394     * failure, an IOException is thrown.
395     */
396    private static native Object openDexFileNative(String sourceName, String outputName, int flags,
397            ClassLoader loader, DexPathList.Element[] elements);
398
399    /**
400     * Returns true if the VM believes that the apk/jar file is out of date
401     * and should be passed through "dexopt" again.
402     *
403     * @param fileName the absolute path to the apk/jar file to examine.
404     * @return true if dexopt should be called on the file, false otherwise.
405     * @throws java.io.FileNotFoundException if fileName is not readable,
406     *         not a file, or not present.
407     * @throws java.io.IOException if fileName is not a valid apk/jar file or
408     *         if problems occur while parsing it.
409     * @throws java.lang.NullPointerException if fileName is null.
410     */
411    public static native boolean isDexOptNeeded(String fileName)
412            throws FileNotFoundException, IOException;
413
414    /**
415     * See {@link #getDexOptNeeded(String, String, int)}.
416     *
417     * @hide
418     */
419    public static final int NO_DEXOPT_NEEDED = 0;
420
421    /**
422     * See {@link #getDexOptNeeded(String, String, int)}.
423     *
424     * @hide
425     */
426    public static final int DEX2OAT_NEEDED = 1;
427
428    /**
429     * See {@link #getDexOptNeeded(String, String, int)}.
430     *
431     * @hide
432     */
433    public static final int PATCHOAT_NEEDED = 2;
434
435    /**
436     * See {@link #getDexOptNeeded(String, String, int)}.
437     *
438     * @hide
439     */
440    public static final int SELF_PATCHOAT_NEEDED = 3;
441
442    /**
443     * Returns whether the given filter is a valid filter.
444     *
445     * @hide
446     */
447    public native static boolean isValidCompilerFilter(String filter);
448
449    /**
450     * Returns whether the given filter is based on profiles.
451     *
452     * @hide
453     */
454    public native static boolean isProfileGuidedCompilerFilter(String filter);
455
456    /**
457     * Returns the version of the compiler filter that is not based on profiles.
458     * If the input is not a valid filter, or the filter is already not based on
459     * profiles, this returns the input.
460     *
461     * @hide
462     */
463    public native static String getNonProfileGuidedCompilerFilter(String filter);
464
465    /**
466     * Returns the VM's opinion of what kind of dexopt is needed to make the
467     * apk/jar file up to date, where {@code targetMode} is used to indicate what
468     * type of compilation the caller considers up-to-date, and {@code newProfile}
469     * is used to indicate whether profile information has changed recently.
470     *
471     * @param fileName the absolute path to the apk/jar file to examine.
472     * @param compilerFilter a compiler filter to use for what a caller considers up-to-date.
473     * @param newProfile flag that describes whether a profile corresponding
474     *        to the dex file has been recently updated and should be considered
475     *        in the state of the file.
476     * @return NO_DEXOPT_NEEDED if the apk/jar is already up to date.
477     *         DEX2OAT_NEEDED if dex2oat should be called on the apk/jar file.
478     *         PATCHOAT_NEEDED if patchoat should be called on the apk/jar
479     *         file to patch the odex file along side the apk/jar.
480     *         SELF_PATCHOAT_NEEDED if selfpatchoat should be called on the
481     *         apk/jar file to patch the oat file in the dalvik cache.
482     * @throws java.io.FileNotFoundException if fileName is not readable,
483     *         not a file, or not present.
484     * @throws java.io.IOException if fileName is not a valid apk/jar file or
485     *         if problems occur while parsing it.
486     * @throws java.lang.NullPointerException if fileName is null.
487     *
488     * @hide
489     */
490    public static native int getDexOptNeeded(String fileName,
491            String instructionSet, String compilerFilter, boolean newProfile)
492            throws FileNotFoundException, IOException;
493
494    /**
495     * Returns the status of the dex file {@code fileName}. The returned string is
496     * an opaque, human readable representation of the current status. The output
497     * is only meant for debugging and is not guaranteed to be stable across
498     * releases and/or devices.
499     *
500     * @hide
501     */
502    public static native String getDexFileStatus(String fileName, String instructionSet)
503        throws FileNotFoundException;
504
505    /**
506     * Returns the full file path of the optimized dex file {@code fileName}.  The returned string
507     * is the full file name including path of optimized dex file, if it exists.
508     * @hide
509     */
510    public static native String getDexFileOutputPath(String fileName, String instructionSet)
511        throws FileNotFoundException;
512}
513