/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Open an unoptimized DEX file.
*/
#include "Dalvik.h"
#include "libdex/OptInvocation.h"
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
/*
* Copy the given number of bytes from one fd to another, first
* seeking the source fd to the start of the file.
*/
static int copyFileToFile(int destFd, int srcFd, size_t size)
{
if (lseek(srcFd, 0, SEEK_SET) != 0) {
ALOGE("lseek failure: %s", strerror(errno));
return -1;
}
return sysCopyFileToFile(destFd, srcFd, size);
}
/*
* Get the modification time and size in bytes for the given fd.
*/
static int getModTimeAndSize(int fd, u4* modTime, size_t* size)
{
struct stat buf;
int result = fstat(fd, &buf);
if (result < 0) {
ALOGE("Unable to determine mod time: %s", strerror(errno));
return -1;
}
*modTime = (u4) buf.st_mtime;
*size = (size_t) buf.st_size;
assert((size_t) buf.st_size == buf.st_size);
return 0;
}
/*
* Verify the dex file magic number, and get the adler32 checksum out
* of the given fd, which is presumed to be a reference to a dex file
* with the cursor at the start of the file. The fd's cursor is
* modified by this operation.
*/
static int verifyMagicAndGetAdler32(int fd, u4 *adler32)
{
/*
* The start of a dex file is eight bytes of magic followed by
* four bytes of checksum.
*/
u1 headerStart[12];
ssize_t amt = read(fd, headerStart, sizeof(headerStart));
if (amt < 0) {
ALOGE("Unable to read header: %s", strerror(errno));
return -1;
}
if (amt != sizeof(headerStart)) {
ALOGE("Unable to read full header (only got %d bytes)", (int) amt);
return -1;
}
if (!dexHasValidMagic((DexHeader*) (void*) headerStart)) {
return -1;
}
/*
* We can't just cast the data to a u4 and read it, since the
* platform might be big-endian (also, because that would make the
* compiler complain about type-punned pointers). We assume here
* that the dex file is in the standard little-endian format; if
* that assumption turns out to be invalid, code that runs later
* will notice and complain.
*/
*adler32 = (u4) headerStart[8]
| (((u4) headerStart[9]) << 8)
| (((u4) headerStart[10]) << 16)
| (((u4) headerStart[11]) << 24);
return 0;
}
/* See documentation comment in header. */
int dvmRawDexFileOpen(const char* fileName, const char* odexOutputName,
RawDexFile** ppRawDexFile, bool isBootstrap)
{
/*
* TODO: This duplicates a lot of code from dvmJarFileOpen() in
* JarFile.c. This should be refactored.
*/
DvmDex* pDvmDex = NULL;
char* cachedName = NULL;
int result = -1;
int dexFd = -1;
int optFd = -1;
u4 modTime = 0;
u4 adler32 = 0;
size_t fileSize = 0;
bool newFile = false;
bool locked = false;
dexFd = open(fileName, O_RDONLY);
if (dexFd < 0) goto bail;
/* If we fork/exec into dexopt, don't let it inherit the open fd. */
dvmSetCloseOnExec(dexFd);
if (verifyMagicAndGetAdler32(dexFd, &adler32) < 0) {
ALOGE("Error with header for %s", fileName);
goto bail;
}
if (getModTimeAndSize(dexFd, &modTime, &fileSize) < 0) {
ALOGE("Error with stat for %s", fileName);
goto bail;
}
/*
* See if the cached file matches. If so, optFd will become a reference
* to the cached file and will have been seeked to just past the "opt"
* header.
*/
if (odexOutputName == NULL) {
cachedName = dexOptGenerateCacheFileName(fileName, NULL);
if (cachedName == NULL)
goto bail;
} else {
cachedName = strdup(odexOutputName);
}
ALOGV("dvmRawDexFileOpen: Checking cache for %s (%s)",
fileName, cachedName);
optFd = dvmOpenCachedDexFile(fileName, cachedName, modTime,
adler32, isBootstrap, &newFile, /*createIfMissing=*/true);
if (optFd < 0) {
ALOGI("Unable to open or create cache for %s (%s)",
fileName, cachedName);
goto bail;
}
locked = true;
/*
* If optFd points to a new file (because there was no cached
* version, or the cached version was stale), generate the
* optimized DEX. The file descriptor returned is still locked,
* and is positioned just past the optimization header.
*/
if (newFile) {
u8 startWhen, copyWhen, endWhen;
bool result;
off_t dexOffset;
dexOffset = lseek(optFd, 0, SEEK_CUR);
result = (dexOffset > 0);
if (result) {
startWhen = dvmGetRelativeTimeUsec();
result = copyFileToFile(optFd, dexFd, fileSize) == 0;
copyWhen = dvmGetRelativeTimeUsec();
}
if (result) {
result = dvmOptimizeDexFile(optFd, dexOffset, fileSize,
fileName, modTime, adler32, isBootstrap);
}
if (!result) {
ALOGE("Unable to extract+optimize DEX from '%s'", fileName);
goto bail;
}
endWhen = dvmGetRelativeTimeUsec();
ALOGD("DEX prep '%s': copy in %dms, rewrite %dms",
fileName,
(int) (copyWhen - startWhen) / 1000,
(int) (endWhen - copyWhen) / 1000);
}
/*
* Map the cached version. This immediately rewinds the fd, so it
* doesn't have to be seeked anywhere in particular.
*/
if (dvmDexFileOpenFromFd(optFd, &pDvmDex) != 0) {
ALOGI("Unable to map cached %s", fileName);
goto bail;
}
if (locked) {
/* unlock the fd */
if (!dvmUnlockCachedDexFile(optFd)) {
/* uh oh -- this process needs to exit or we'll wedge the system */
ALOGE("Unable to unlock DEX file");
goto bail;
}
locked = false;
}
ALOGV("Successfully opened '%s'", fileName);
*ppRawDexFile = (RawDexFile*) calloc(1, sizeof(RawDexFile));
(*ppRawDexFile)->cacheFileName = cachedName;
(*ppRawDexFile)->pDvmDex = pDvmDex;
cachedName = NULL; // don't free it below
result = 0;
bail:
free(cachedName);
if (dexFd >= 0) {
close(dexFd);
}
if (optFd >= 0) {
if (locked)
(void) dvmUnlockCachedDexFile(optFd);
close(optFd);
}
return result;
}
/* See documentation comment in header. */
int dvmRawDexFileOpenArray(u1* pBytes, u4 length, RawDexFile** ppRawDexFile)
{
DvmDex* pDvmDex = NULL;
if (!dvmPrepareDexInMemory(pBytes, length, &pDvmDex)) {
ALOGD("Unable to open raw DEX from array");
return -1;
}
assert(pDvmDex != NULL);
*ppRawDexFile = (RawDexFile*) calloc(1, sizeof(RawDexFile));
(*ppRawDexFile)->pDvmDex = pDvmDex;
return 0;
}
/*
* Close a RawDexFile and free the struct.
*/
void dvmRawDexFileFree(RawDexFile* pRawDexFile)
{
if (pRawDexFile == NULL)
return;
dvmDexFileFree(pRawDexFile->pDvmDex);
free(pRawDexFile->cacheFileName);
free(pRawDexFile);
}