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