OSDN Git Service

Changed dvmDexCacheStatus to check for odex file first.
[android-x86/dalvik.git] / vm / JarFile.cpp
1 /*
2  * Copyright (C) 2008 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 /*
18  * Access the contents of a Jar file.
19  *
20  * This isn't actually concerned with any of the Jar-like elements; it
21  * just wants a zip archive with "classes.dex" inside.  In Android the
22  * most common example is ".apk".
23  */
24
25 #include "Dalvik.h"
26 #include "libdex/OptInvocation.h"
27
28 #include <stdlib.h>
29 #include <string.h>
30 #include <zlib.h>
31 #include <fcntl.h>
32 #include <errno.h>
33
34 static const char* kDexInJarName = "classes.dex";
35
36 /*
37  * Attempt to open a file whose name is similar to <fileName>,
38  * but with the supplied suffix.  E.g.,
39  * openAlternateSuffix("Home.apk", "dex", O_RDONLY) will attempt
40  * to open "Home.dex".  If the open succeeds, a pointer to a
41  * malloc()ed copy of the opened file name will be put in <*pCachedName>.
42  *
43  * <flags> is passed directly to open(). O_CREAT is not supported.
44  */
45 static int openAlternateSuffix(const char *fileName, const char *suffix,
46     int flags, char **pCachedName)
47 {
48     char *buf, *c;
49     size_t fileNameLen = strlen(fileName);
50     size_t suffixLen = strlen(suffix);
51     size_t bufLen = fileNameLen + suffixLen + 1;
52     int fd = -1;
53
54     buf = (char*)malloc(bufLen);
55     if (buf == NULL) {
56         errno = ENOMEM;
57         return -1;
58     }
59
60     /* Copy the original filename into the buffer, find
61      * the last dot, and copy the suffix to just after it.
62      */
63     memcpy(buf, fileName, fileNameLen + 1);
64     c = strrchr(buf, '.');
65     if (c == NULL) {
66         errno = ENOENT;
67         goto bail;
68     }
69     memcpy(c + 1, suffix, suffixLen + 1);
70
71     fd = open(buf, flags);
72     if (fd >= 0) {
73         *pCachedName = buf;
74         return fd;
75     }
76     ALOGV("Couldn't open %s: %s", buf, strerror(errno));
77 bail:
78     free(buf);
79     return -1;
80 }
81
82 /*
83  * Checks the dependencies of the dex cache file corresponding
84  * to the jar file at the absolute path "fileName".
85  *
86  * Note: This should parallel the logic of dvmJarFileOpen.
87  */
88 DexCacheStatus dvmDexCacheStatus(const char *fileName)
89 {
90     ZipArchive archive;
91     char* cachedName = NULL;
92     int fd;
93     DexCacheStatus result = DEX_CACHE_ERROR;
94     ZipEntry entry;
95
96     /* Always treat elements of the bootclasspath as up-to-date.
97      * The fact that interpreted code is running at all means that this
98      * should be true.
99      */
100     if (dvmClassPathContains(gDvm.bootClassPath, fileName)) {
101         return DEX_CACHE_OK;
102     }
103
104     /* Try to find the dex file inside of the archive.
105      */
106     if (dexZipOpenArchive(fileName, &archive) != 0) {
107         return DEX_CACHE_BAD_ARCHIVE;
108     }
109
110     /* First, look for a ".odex" alongside the jar file.  It will
111      * have the same name/path except for the extension.
112      */
113     fd = openAlternateSuffix(fileName, "odex", O_RDONLY, &cachedName);
114     if (fd >= 0) {
115         ALOGV("Using alternate file (odex) for %s ...", fileName);
116         if (!dvmCheckOptHeaderAndDependencies(fd, false, 0, 0, true, true)) {
117             ALOGE("%s odex has stale dependencies", fileName);
118             free(cachedName);
119             cachedName = NULL;
120             close(fd);
121             fd = -1;
122             goto tryArchive;
123         } else {
124             ALOGV("%s odex has good dependencies", fileName);
125         }
126     } else {
127
128 tryArchive:
129         /*
130          * Pre-created .odex absent or stale.  Look inside the jar for a
131          * "classes.dex".
132          */
133         if ((entry = dexZipFindEntry(&archive, kDexInJarName)) != NULL) {
134             bool newFile = false;
135
136             /*
137              * See if there's an up-to-date copy of the optimized dex
138              * in the cache, but don't create one if there isn't.
139              */
140             ALOGV("dvmDexCacheStatus: Checking cache for %s", fileName);
141             cachedName = dexOptGenerateCacheFileName(fileName, kDexInJarName);
142             if (cachedName == NULL)
143                 return DEX_CACHE_BAD_ARCHIVE;
144
145             fd = dvmOpenCachedDexFile(fileName, cachedName,
146                     dexGetZipEntryModTime(&archive, entry),
147                     dexGetZipEntryCrc32(&archive, entry),
148                     /*isBootstrap=*/false, &newFile, /*createIfMissing=*/false);
149             ALOGV("dvmOpenCachedDexFile returned fd %d", fd);
150             if (fd < 0) {
151                 result = DEX_CACHE_STALE;
152                 goto bail;
153             }
154
155             /* dvmOpenCachedDexFile locks the file as a side-effect.
156              * Unlock and close it.
157              */
158             if (!dvmUnlockCachedDexFile(fd)) {
159                 /* uh oh -- this process needs to exit or we'll wedge the system */
160                 ALOGE("Unable to unlock DEX file");
161                 goto bail;
162             }
163         } else {
164             ALOGI("Zip is good, but no %s inside, and no .odex "
165                     "file in the same directory", kDexInJarName);
166             result = DEX_CACHE_BAD_ARCHIVE;
167             goto bail;
168         }
169     }
170     result = DEX_CACHE_OK;
171
172 bail:
173     dexZipCloseArchive(&archive);
174     free(cachedName);
175     if (fd >= 0) {
176         close(fd);
177     }
178     return result;
179 }
180
181 /*
182  * Open a Jar file.  It's okay if it's just a Zip archive without all of
183  * the Jar trimmings, but we do insist on finding "classes.dex" inside
184  * or an appropriately-named ".odex" file alongside.
185  *
186  * If "isBootstrap" is not set, the optimizer/verifier regards this DEX as
187  * being part of a different class loader.
188  *
189  * Note: This should parallel the logic of dvmDexCacheStatus.
190  */
191 int dvmJarFileOpen(const char* fileName, const char* odexOutputName,
192     JarFile** ppJarFile, bool isBootstrap)
193 {
194     /*
195      * TODO: This function has been duplicated and modified to become
196      * dvmRawDexFileOpen() in RawDexFile.c. This should be refactored.
197      */
198
199     ZipArchive archive;
200     DvmDex* pDvmDex = NULL;
201     char* cachedName = NULL;
202     bool archiveOpen = false;
203     bool locked = false;
204     int fd = -1;
205     int result = -1;
206
207     /* Even if we're not going to look at the archive, we need to
208      * open it so we can stuff it into ppJarFile.
209      */
210     if (dexZipOpenArchive(fileName, &archive) != 0)
211         goto bail;
212     archiveOpen = true;
213
214     /* If we fork/exec into dexopt, don't let it inherit the archive's fd.
215      */
216     dvmSetCloseOnExec(dexZipGetArchiveFd(&archive));
217
218     /* First, look for a ".odex" alongside the jar file.  It will
219      * have the same name/path except for the extension.
220      */
221     fd = openAlternateSuffix(fileName, "odex", O_RDONLY, &cachedName);
222     if (fd >= 0) {
223         ALOGV("Using alternate file (odex) for %s ...", fileName);
224         if (!dvmCheckOptHeaderAndDependencies(fd, false, 0, 0, true, true)) {
225             ALOGE("%s odex has stale dependencies", fileName);
226             free(cachedName);
227             cachedName = NULL;
228             close(fd);
229             fd = -1;
230             goto tryArchive;
231         } else {
232             ALOGV("%s odex has good dependencies", fileName);
233             //TODO: make sure that the .odex actually corresponds
234             //      to the classes.dex inside the archive (if present).
235             //      For typical use there will be no classes.dex.
236         }
237     } else {
238         ZipEntry entry;
239
240 tryArchive:
241         /*
242          * Pre-created .odex absent or stale.  Look inside the jar for a
243          * "classes.dex".
244          */
245         entry = dexZipFindEntry(&archive, kDexInJarName);
246         if (entry != NULL) {
247             bool newFile = false;
248
249             /*
250              * We've found the one we want.  See if there's an up-to-date copy
251              * in the cache.
252              *
253              * On return, "fd" will be seeked just past the "opt" header.
254              *
255              * If a stale .odex file is present and classes.dex exists in
256              * the archive, this will *not* return an fd pointing to the
257              * .odex file; the fd will point into dalvik-cache like any
258              * other jar.
259              */
260             if (odexOutputName == NULL) {
261                 cachedName = dexOptGenerateCacheFileName(fileName,
262                                 kDexInJarName);
263                 if (cachedName == NULL)
264                     goto bail;
265             } else {
266                 cachedName = strdup(odexOutputName);
267             }
268             ALOGV("dvmJarFileOpen: Checking cache for %s (%s)",
269                 fileName, cachedName);
270             fd = dvmOpenCachedDexFile(fileName, cachedName,
271                     dexGetZipEntryModTime(&archive, entry),
272                     dexGetZipEntryCrc32(&archive, entry),
273                     isBootstrap, &newFile, /*createIfMissing=*/true);
274             if (fd < 0) {
275                 ALOGI("Unable to open or create cache for %s (%s)",
276                     fileName, cachedName);
277                 goto bail;
278             }
279             locked = true;
280
281             /*
282              * If fd points to a new file (because there was no cached version,
283              * or the cached version was stale), generate the optimized DEX.
284              * The file descriptor returned is still locked, and is positioned
285              * just past the optimization header.
286              */
287             if (newFile) {
288                 u8 startWhen, extractWhen, endWhen;
289                 bool result;
290                 off_t dexOffset;
291
292                 dexOffset = lseek(fd, 0, SEEK_CUR);
293                 result = (dexOffset > 0);
294
295                 if (result) {
296                     startWhen = dvmGetRelativeTimeUsec();
297                     result = dexZipExtractEntryToFile(&archive, entry, fd) == 0;
298                     extractWhen = dvmGetRelativeTimeUsec();
299                 }
300                 if (result) {
301                     result = dvmOptimizeDexFile(fd, dexOffset,
302                                 dexGetZipEntryUncompLen(&archive, entry),
303                                 fileName,
304                                 dexGetZipEntryModTime(&archive, entry),
305                                 dexGetZipEntryCrc32(&archive, entry),
306                                 isBootstrap);
307                 }
308
309                 if (!result) {
310                     ALOGE("Unable to extract+optimize DEX from '%s'",
311                         fileName);
312                     goto bail;
313                 }
314
315                 endWhen = dvmGetRelativeTimeUsec();
316                 ALOGD("DEX prep '%s': unzip in %dms, rewrite %dms",
317                     fileName,
318                     (int) (extractWhen - startWhen) / 1000,
319                     (int) (endWhen - extractWhen) / 1000);
320             }
321         } else {
322             ALOGI("Zip is good, but no %s inside, and no valid .odex "
323                     "file in the same directory", kDexInJarName);
324             goto bail;
325         }
326     }
327
328     /*
329      * Map the cached version.  This immediately rewinds the fd, so it
330      * doesn't have to be seeked anywhere in particular.
331      */
332     if (dvmDexFileOpenFromFd(fd, &pDvmDex) != 0) {
333         ALOGI("Unable to map %s in %s", kDexInJarName, fileName);
334         goto bail;
335     }
336
337     if (locked) {
338         /* unlock the fd */
339         if (!dvmUnlockCachedDexFile(fd)) {
340             /* uh oh -- this process needs to exit or we'll wedge the system */
341             ALOGE("Unable to unlock DEX file");
342             goto bail;
343         }
344         locked = false;
345     }
346
347     ALOGV("Successfully opened '%s' in '%s'", kDexInJarName, fileName);
348
349     *ppJarFile = (JarFile*) calloc(1, sizeof(JarFile));
350     (*ppJarFile)->archive = archive;
351     (*ppJarFile)->cacheFileName = cachedName;
352     (*ppJarFile)->pDvmDex = pDvmDex;
353     cachedName = NULL;      // don't free it below
354     result = 0;
355
356 bail:
357     /* clean up, closing the open file */
358     if (archiveOpen && result != 0)
359         dexZipCloseArchive(&archive);
360     free(cachedName);
361     if (fd >= 0) {
362         if (locked)
363             (void) dvmUnlockCachedDexFile(fd);
364         close(fd);
365     }
366     return result;
367 }
368
369 /*
370  * Close a Jar file and free the struct.
371  */
372 void dvmJarFileFree(JarFile* pJarFile)
373 {
374     if (pJarFile == NULL)
375         return;
376
377     dvmDexFileFree(pJarFile->pDvmDex);
378     dexZipCloseArchive(&pJarFile->archive);
379     free(pJarFile->cacheFileName);
380     free(pJarFile);
381 }