• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  */
openAlternateSuffix(const char * fileName,const char * suffix,int flags,char ** pCachedName)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  */
dvmDexCacheStatus(const char * fileName)86 DexCacheStatus dvmDexCacheStatus(const char *fileName)
87 {
88     ZipArchive archive;
89     char* cachedName = NULL;
90     int fd;
91     DexCacheStatus result = DEX_CACHE_ERROR;
92     ZipEntry entry;
93 
94     /* Always treat elements of the bootclasspath as up-to-date.
95      * The fact that interpreted code is running at all means that this
96      * should be true.
97      */
98     if (dvmClassPathContains(gDvm.bootClassPath, fileName)) {
99         return DEX_CACHE_OK;
100     }
101 
102     //TODO: match dvmJarFileOpen()'s logic.  Not super-important
103     //      (the odex-first logic is only necessary for dexpreopt)
104     //      but it would be nice to be consistent.
105 
106     /* Try to find the dex file inside of the archive.
107      */
108     if (dexZipOpenArchive(fileName, &archive) != 0) {
109         return DEX_CACHE_BAD_ARCHIVE;
110     }
111     entry = dexZipFindEntry(&archive, kDexInJarName);
112     if (entry != NULL) {
113         bool newFile = false;
114 
115         /*
116          * See if there's an up-to-date copy of the optimized dex
117          * in the cache, but don't create one if there isn't.
118          */
119         ALOGV("dvmDexCacheStatus: Checking cache for %s", fileName);
120         cachedName = dexOptGenerateCacheFileName(fileName, kDexInJarName);
121         if (cachedName == NULL)
122             return DEX_CACHE_BAD_ARCHIVE;
123 
124         fd = dvmOpenCachedDexFile(fileName, cachedName,
125                 dexGetZipEntryModTime(&archive, entry),
126                 dexGetZipEntryCrc32(&archive, entry),
127                 /*isBootstrap=*/false, &newFile, /*createIfMissing=*/false);
128         ALOGV("dvmOpenCachedDexFile returned fd %d", fd);
129         if (fd < 0) {
130             result = DEX_CACHE_STALE;
131             goto bail;
132         }
133 
134         /* dvmOpenCachedDexFile locks the file as a side-effect.
135          * Unlock and close it.
136          */
137         if (!dvmUnlockCachedDexFile(fd)) {
138             /* uh oh -- this process needs to exit or we'll wedge the system */
139             ALOGE("Unable to unlock DEX file");
140             goto bail;
141         }
142 
143         /* When createIfMissing is false, dvmOpenCachedDexFile() only
144          * returns a valid fd if the cache file is up-to-date.
145          */
146     } else {
147         /*
148          * There's no dex file in the jar file.  See if there's an
149          * optimized dex file living alongside the jar.
150          */
151         fd = openAlternateSuffix(fileName, "odex", O_RDONLY, &cachedName);
152         if (fd < 0) {
153             ALOGI("Zip is good, but no %s inside, and no .odex "
154                     "file in the same directory", kDexInJarName);
155             result = DEX_CACHE_BAD_ARCHIVE;
156             goto bail;
157         }
158 
159         ALOGV("Using alternate file (odex) for %s ...", fileName);
160         if (!dvmCheckOptHeaderAndDependencies(fd, false, 0, 0, true, true)) {
161             ALOGE("%s odex has stale dependencies", fileName);
162             ALOGE("odex source not available -- failing");
163             result = DEX_CACHE_STALE_ODEX;
164             goto bail;
165         } else {
166             ALOGV("%s odex has good dependencies", fileName);
167         }
168     }
169     result = DEX_CACHE_OK;
170 
171 bail:
172     dexZipCloseArchive(&archive);
173     free(cachedName);
174     if (fd >= 0) {
175         close(fd);
176     }
177     return result;
178 }
179 
180 /*
181  * Open a Jar file.  It's okay if it's just a Zip archive without all of
182  * the Jar trimmings, but we do insist on finding "classes.dex" inside
183  * or an appropriately-named ".odex" file alongside.
184  *
185  * If "isBootstrap" is not set, the optimizer/verifier regards this DEX as
186  * being part of a different class loader.
187  */
dvmJarFileOpen(const char * fileName,const char * odexOutputName,JarFile ** ppJarFile,bool isBootstrap)188 int dvmJarFileOpen(const char* fileName, const char* odexOutputName,
189     JarFile** ppJarFile, bool isBootstrap)
190 {
191     /*
192      * TODO: This function has been duplicated and modified to become
193      * dvmRawDexFileOpen() in RawDexFile.c. This should be refactored.
194      */
195 
196     ZipArchive archive;
197     DvmDex* pDvmDex = NULL;
198     char* cachedName = NULL;
199     bool archiveOpen = false;
200     bool locked = false;
201     int fd = -1;
202     int result = -1;
203 
204     /* Even if we're not going to look at the archive, we need to
205      * open it so we can stuff it into ppJarFile.
206      */
207     if (dexZipOpenArchive(fileName, &archive) != 0)
208         goto bail;
209     archiveOpen = true;
210 
211     /* If we fork/exec into dexopt, don't let it inherit the archive's fd.
212      */
213     dvmSetCloseOnExec(dexZipGetArchiveFd(&archive));
214 
215     /* First, look for a ".odex" alongside the jar file.  It will
216      * have the same name/path except for the extension.
217      */
218     fd = openAlternateSuffix(fileName, "odex", O_RDONLY, &cachedName);
219     if (fd >= 0) {
220         ALOGV("Using alternate file (odex) for %s ...", fileName);
221         if (!dvmCheckOptHeaderAndDependencies(fd, false, 0, 0, true, true)) {
222             ALOGE("%s odex has stale dependencies", fileName);
223             free(cachedName);
224             cachedName = NULL;
225             close(fd);
226             fd = -1;
227             goto tryArchive;
228         } else {
229             ALOGV("%s odex has good dependencies", fileName);
230             //TODO: make sure that the .odex actually corresponds
231             //      to the classes.dex inside the archive (if present).
232             //      For typical use there will be no classes.dex.
233         }
234     } else {
235         ZipEntry entry;
236 
237 tryArchive:
238         /*
239          * Pre-created .odex absent or stale.  Look inside the jar for a
240          * "classes.dex".
241          */
242         entry = dexZipFindEntry(&archive, kDexInJarName);
243         if (entry != NULL) {
244             bool newFile = false;
245 
246             /*
247              * We've found the one we want.  See if there's an up-to-date copy
248              * in the cache.
249              *
250              * On return, "fd" will be seeked just past the "opt" header.
251              *
252              * If a stale .odex file is present and classes.dex exists in
253              * the archive, this will *not* return an fd pointing to the
254              * .odex file; the fd will point into dalvik-cache like any
255              * other jar.
256              */
257             if (odexOutputName == NULL) {
258                 cachedName = dexOptGenerateCacheFileName(fileName,
259                                 kDexInJarName);
260                 if (cachedName == NULL)
261                     goto bail;
262             } else {
263                 cachedName = strdup(odexOutputName);
264             }
265             ALOGV("dvmJarFileOpen: Checking cache for %s (%s)",
266                 fileName, cachedName);
267             fd = dvmOpenCachedDexFile(fileName, cachedName,
268                     dexGetZipEntryModTime(&archive, entry),
269                     dexGetZipEntryCrc32(&archive, entry),
270                     isBootstrap, &newFile, /*createIfMissing=*/true);
271             if (fd < 0) {
272                 ALOGI("Unable to open or create cache for %s (%s)",
273                     fileName, cachedName);
274                 goto bail;
275             }
276             locked = true;
277 
278             /*
279              * If fd points to a new file (because there was no cached version,
280              * or the cached version was stale), generate the optimized DEX.
281              * The file descriptor returned is still locked, and is positioned
282              * just past the optimization header.
283              */
284             if (newFile) {
285                 u8 startWhen, extractWhen, endWhen;
286                 bool result;
287                 off_t dexOffset;
288 
289                 dexOffset = lseek(fd, 0, SEEK_CUR);
290                 result = (dexOffset > 0);
291 
292                 if (result) {
293                     startWhen = dvmGetRelativeTimeUsec();
294                     result = dexZipExtractEntryToFile(&archive, entry, fd) == 0;
295                     extractWhen = dvmGetRelativeTimeUsec();
296                 }
297                 if (result) {
298                     result = dvmOptimizeDexFile(fd, dexOffset,
299                                 dexGetZipEntryUncompLen(&archive, entry),
300                                 fileName,
301                                 dexGetZipEntryModTime(&archive, entry),
302                                 dexGetZipEntryCrc32(&archive, entry),
303                                 isBootstrap);
304                 }
305 
306                 if (!result) {
307                     ALOGE("Unable to extract+optimize DEX from '%s'",
308                         fileName);
309                     goto bail;
310                 }
311 
312                 endWhen = dvmGetRelativeTimeUsec();
313                 ALOGD("DEX prep '%s': unzip in %dms, rewrite %dms",
314                     fileName,
315                     (int) (extractWhen - startWhen) / 1000,
316                     (int) (endWhen - extractWhen) / 1000);
317             }
318         } else {
319             ALOGI("Zip is good, but no %s inside, and no valid .odex "
320                     "file in the same directory", kDexInJarName);
321             goto bail;
322         }
323     }
324 
325     /*
326      * Map the cached version.  This immediately rewinds the fd, so it
327      * doesn't have to be seeked anywhere in particular.
328      */
329     if (dvmDexFileOpenFromFd(fd, &pDvmDex) != 0) {
330         ALOGI("Unable to map %s in %s", kDexInJarName, fileName);
331         goto bail;
332     }
333 
334     if (locked) {
335         /* unlock the fd */
336         if (!dvmUnlockCachedDexFile(fd)) {
337             /* uh oh -- this process needs to exit or we'll wedge the system */
338             ALOGE("Unable to unlock DEX file");
339             goto bail;
340         }
341         locked = false;
342     }
343 
344     ALOGV("Successfully opened '%s' in '%s'", kDexInJarName, fileName);
345 
346     *ppJarFile = (JarFile*) calloc(1, sizeof(JarFile));
347     (*ppJarFile)->archive = archive;
348     (*ppJarFile)->cacheFileName = cachedName;
349     (*ppJarFile)->pDvmDex = pDvmDex;
350     cachedName = NULL;      // don't free it below
351     result = 0;
352 
353 bail:
354     /* clean up, closing the open file */
355     if (archiveOpen && result != 0)
356         dexZipCloseArchive(&archive);
357     free(cachedName);
358     if (fd >= 0) {
359         if (locked)
360             (void) dvmUnlockCachedDexFile(fd);
361         close(fd);
362     }
363     return result;
364 }
365 
366 /*
367  * Close a Jar file and free the struct.
368  */
dvmJarFileFree(JarFile * pJarFile)369 void dvmJarFileFree(JarFile* pJarFile)
370 {
371     if (pJarFile == NULL)
372         return;
373 
374     dvmDexFileFree(pJarFile->pDvmDex);
375     dexZipCloseArchive(&pJarFile->archive);
376     free(pJarFile->cacheFileName);
377     free(pJarFile);
378 }
379