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