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    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     */
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     */
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     */
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     */
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     */
159    public String getName() {
160        return mFileName;
161    }
162
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     */
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     */
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     */
218    public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
219        return defineClass(name, loader, mCookie, suppressed);
220    }
221
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     */
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
256        DFEnum(DexFile df) {
257            mIndex = 0;
258            mNameList = getClassNameList(mCookie);
259        }
260
261        public boolean hasMoreElements() {
262            return (mIndex < mNameList.length);
263        }
264
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     */
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     */
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
300    private static native void closeDexFile(long cookie);
301    private static native Class defineClassNative(String name, ClassLoader loader, long cookie)
302            throws ClassNotFoundException, NoClassDefFoundError;
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     */
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     */
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     */
369    public static native byte isDexOptNeededInternal(String fileName, String pkgname,
370            String instructionSet, boolean defer)
371            throws FileNotFoundException, IOException;
372}
373