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 java.io.File;
20import java.io.FileNotFoundException;
21import java.io.IOException;
22import java.util.ArrayList;
23import java.util.Enumeration;
24import java.util.List;
25import libcore.io.ErrnoException;
26import libcore.io.Libcore;
27import libcore.io.StructStat;
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 int 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);
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);
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    /**
164     * Closes the DEX file.
165     * <p>
166     * This may not be able to release any resources. If classes from this
167     * DEX file are still resident, the DEX file can't be unmapped.
168     *
169     * @throws IOException
170     *             if an I/O error occurs during closing the file, which
171     *             normally should not happen
172     */
173    public void close() throws IOException {
174        if (mCookie != 0) {
175            guard.close();
176            closeDexFile(mCookie);
177            mCookie = 0;
178        }
179    }
180
181    /**
182     * Loads a class. Returns the class on success, or a {@code null} reference
183     * on failure.
184     * <p>
185     * If you are not calling this from a class loader, this is most likely not
186     * going to do what you want. Use {@link Class#forName(String)} instead.
187     * <p>
188     * The method does not throw {@link ClassNotFoundException} if the class
189     * isn't found because it isn't reasonable to throw exceptions wildly every
190     * time a class is not found in the first DEX file we look at.
191     *
192     * @param name
193     *            the class name, which should look like "java/lang/String"
194     *
195     * @param loader
196     *            the class loader that tries to load the class (in most cases
197     *            the caller of the method
198     *
199     * @return the {@link Class} object representing the class, or {@code null}
200     *         if the class cannot be loaded
201     */
202    public Class loadClass(String name, ClassLoader loader) {
203        String slashName = name.replace('.', '/');
204        return loadClassBinaryName(slashName, loader, null);
205    }
206
207    /**
208     * See {@link #loadClass(String, ClassLoader)}.
209     *
210     * This takes a "binary" class name to better match ClassLoader semantics.
211     *
212     * @hide
213     */
214    public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
215        return defineClass(name, loader, mCookie, suppressed);
216    }
217
218    private static Class defineClass(String name, ClassLoader loader, int cookie,
219                                     List<Throwable> suppressed) {
220        Class result = null;
221        try {
222            result = defineClassNative(name, loader, cookie);
223        } catch (NoClassDefFoundError e) {
224            if (suppressed != null) {
225                suppressed.add(e);
226            }
227        } catch (ClassNotFoundException e) {
228            if (suppressed != null) {
229                suppressed.add(e);
230            }
231        }
232        return result;
233    }
234
235    private static native Class defineClassNative(String name, ClassLoader loader, int cookie)
236        throws ClassNotFoundException, NoClassDefFoundError;
237
238    /**
239     * Enumerate the names of the classes in this DEX file.
240     *
241     * @return an enumeration of names of classes contained in the DEX file, in
242     *         the usual internal form (like "java/lang/String").
243     */
244    public Enumeration<String> entries() {
245        return new DFEnum(this);
246    }
247
248    /*
249     * Helper class.
250     */
251    private class DFEnum implements Enumeration<String> {
252        private int mIndex;
253        private String[] mNameList;
254
255        DFEnum(DexFile df) {
256            mIndex = 0;
257            mNameList = getClassNameList(mCookie);
258        }
259
260        public boolean hasMoreElements() {
261            return (mIndex < mNameList.length);
262        }
263
264        public String nextElement() {
265            return mNameList[mIndex++];
266        }
267    }
268
269    /* return a String array with class names */
270    native private static String[] getClassNameList(int cookie);
271
272    /**
273     * Called when the class is finalized. Makes sure the DEX file is closed.
274     *
275     * @throws IOException
276     *             if an I/O error occurs during closing the file, which
277     *             normally should not happen
278     */
279    @Override protected void finalize() throws Throwable {
280        try {
281            if (guard != null) {
282                guard.warnIfOpen();
283            }
284            close();
285        } finally {
286            super.finalize();
287        }
288    }
289
290    /*
291     * Open a DEX file.  The value returned is a magic VM cookie.  On
292     * failure, an IOException is thrown.
293     */
294    private static int openDexFile(String sourceName, String outputName,
295        int flags) throws IOException {
296        return openDexFileNative(new File(sourceName).getCanonicalPath(),
297                                 (outputName == null) ? null : new File(outputName).getCanonicalPath(),
298                                 flags);
299    }
300
301    native private static int openDexFileNative(String sourceName, String outputName,
302        int flags) throws IOException;
303
304    /*
305     * Close DEX file.
306     */
307    native private static void closeDexFile(int cookie);
308
309    /**
310     * Returns true if the VM believes that the apk/jar file is out of date
311     * and should be passed through "dexopt" again.
312     *
313     * @param fileName the absolute path to the apk/jar file to examine.
314     * @return true if dexopt should be called on the file, false otherwise.
315     * @throws java.io.FileNotFoundException if fileName is not readable,
316     *         not a file, or not present.
317     * @throws java.io.IOException if fileName is not a valid apk/jar file or
318     *         if problems occur while parsing it.
319     * @throws java.lang.NullPointerException if fileName is null.
320     * @throws dalvik.system.StaleDexCacheError if the optimized dex file
321     *         is stale but exists on a read-only partition.
322     */
323    native public static boolean isDexOptNeeded(String fileName)
324            throws FileNotFoundException, IOException;
325}
326