Add support for opening classes.dex file from zip, jar, apk
Adding new ZipArchive class and test
src/zip_archive.h
src/zip_archive.cc
src/zip_archive_test.cc
build/Android.common.mk
Change from host only use of build dex file for libcore to using host
and target core.jar containing classes.dex files. This requires
setting up an ANDROID_DATA directory to containing an art-cache file
for the extracted dex files, similar to the dalvik-cache for odex
files. A unique ANDROID_DATA and art-cache is created and cleaned up
for each test run (similar to vogar).
src/common_test.h
Add dependency for libcore jar files to test targets to support
RuntimeTest use of core.jar
Android.mk
Extract common includes to ART_C_INCLUDES when adding zlib dependency
build/Android.common.mk
build/Android.libart.mk
build/Android.test.mk
Adding TODO regarding unordered map for ClassLinker::classes_ table.
src/class_linker.h
Adding DexFile::OpenZip (also changed OpenFile to take
src/dex_file.cc
src/dex_file.h
Adding kPageSize of 4096, validated by Runtime::Init
src/globals.h
src/runtime.cc
Updated to use kPageSize where it seemed appropriate.
src/jni_compiler.cc
src/jni_compiler_test.cc
src/space.cc
src/thread.cc
src/thread_x86.cc
Changed thread_list_ and class_linker_ to be declared in Runtime::Init
initialization order.
src/runtime.h
Change-Id: Id626abe5b6c1990e4f93598256ee0fae000818f6
diff --git a/src/dex_file.cc b/src/dex_file.cc
index a5ac01b..b61babf 100644
--- a/src/dex_file.cc
+++ b/src/dex_file.cc
@@ -3,17 +3,22 @@
#include "dex_file.h"
#include <fcntl.h>
+#include <map>
+#include <stdio.h>
#include <string.h>
+#include <sys/file.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
-#include <map>
#include "globals.h"
#include "logging.h"
#include "object.h"
#include "scoped_ptr.h"
+#include "stringprintf.h"
+#include "thread.h"
#include "utils.h"
+#include "zip_archive.h"
namespace art {
@@ -34,9 +39,8 @@
DexFile::PtrCloser::PtrCloser(byte* addr) : addr_(addr) {}
DexFile::PtrCloser::~PtrCloser() { delete[] addr_; }
-DexFile* DexFile::OpenFile(const char* filename) {
- CHECK(filename != NULL);
- int fd = open(filename, O_RDONLY); // TODO: scoped_fd
+DexFile* DexFile::OpenFile(const std::string& filename) {
+ int fd = open(filename.c_str(), O_RDONLY); // TODO: scoped_fd
if (fd == -1) {
PLOG(ERROR) << "open(\"" << filename << "\", O_RDONLY) failed";
return NULL;
@@ -61,6 +65,199 @@
return Open(dex_file, length, closer);
}
+static const char* kClassesDex = "classes.dex";
+
+class LockedFd {
+ public:
+ static LockedFd* CreateAndLock(std::string& name, mode_t mode) {
+ int fd = open(name.c_str(), O_CREAT | O_RDWR, mode);
+ if (fd == -1) {
+ PLOG(ERROR) << "Can't open file '" << name;
+ return NULL;
+ }
+ fchmod(fd, mode);
+
+ LOG(INFO) << "locking file " << name << " (fd=" << fd << ")";
+ int result = flock(fd, LOCK_EX | LOCK_NB);
+ if (result == -1) {
+ LOG(WARNING) << "sleeping while locking file " << name;
+ result = flock(fd, LOCK_EX);
+ }
+ if (result == -1 ) {
+ close(fd);
+ PLOG(ERROR) << "Can't lock file '" << name;
+ return NULL;
+ }
+ return new LockedFd(fd);
+ }
+
+ int GetFd() const {
+ return fd_;
+ }
+
+ ~LockedFd() {
+ if (fd_ != -1) {
+ int result = flock(fd_, LOCK_UN);
+ if (result == -1) {
+ PLOG(WARNING) << "flock(" << fd_ << ", LOCK_UN) failed";
+ }
+ close(fd_);
+ }
+ }
+
+ private:
+ LockedFd(int fd) : fd_(fd) {}
+
+ int fd_;
+};
+
+class TmpFile {
+ public:
+ TmpFile(const std::string name) : name_(name) {}
+ ~TmpFile() {
+ unlink(name_.c_str());
+ }
+ private:
+ const std::string name_;
+};
+
+// Open classes.dex from within a .zip, .jar, .apk, ...
+DexFile* DexFile::OpenZip(const std::string& filename) {
+
+ // First, look for a ".dex" alongside the jar file. It will have
+ // the same name/path except for the extension.
+
+ // Example filename = dir/foo.jar
+ std::string adjacent_dex_filename(filename);
+ size_t found = adjacent_dex_filename.find_last_of(".");
+ if (found == std::string::npos) {
+ LOG(WARNING) << "No . in filename" << filename;
+ }
+ adjacent_dex_filename.replace(adjacent_dex_filename.begin() + found,
+ adjacent_dex_filename.end(),
+ ".dex");
+ // Example adjacent_dex_filename = dir/foo.dex
+ DexFile* adjacent_dex_file = DexFile::OpenFile(adjacent_dex_filename);
+ if (adjacent_dex_file != NULL) {
+ // We don't verify anything in this case, because we aren't in
+ // the cache and typically the file is in the readonly /system
+ // area, so if something is wrong, there is nothing we can do.
+ return adjacent_dex_file;
+ }
+
+ char resolved[PATH_MAX];
+ char* absolute_path = realpath(filename.c_str(), resolved);
+ if (absolute_path == NULL) {
+ LOG(WARNING) << "Could not create absolute path for " << filename
+ << " when looking for classes.dex";
+ return NULL;
+ }
+ std::string cache_file(absolute_path+1); // skip leading slash
+ std::replace(cache_file.begin(), cache_file.end(), '/', '@');
+ cache_file.push_back('@');
+ cache_file.append(kClassesDex);
+ // Example cache_file = parent@dir@foo.jar@classes.dex
+
+ const char* data_root = getenv("ANDROID_DATA");
+ if (data_root == NULL) {
+ data_root = "/data";
+ }
+
+ std::string cache_path_tmp = StringPrintf("%s/art-cache/%s", data_root, cache_file.c_str());
+ // Example cache_path_tmp = /data/art-cache/parent@dir@foo.jar@classes.dex
+
+ scoped_ptr<ZipArchive> zip_archive(ZipArchive::Open(filename));
+ if (zip_archive == NULL) {
+ LOG(WARNING) << "Could not open " << filename << " when looking for classes.dex";
+ return NULL;
+ }
+ scoped_ptr<ZipEntry> zip_entry(zip_archive->Find(kClassesDex));
+ if (zip_entry == NULL) {
+ LOG(WARNING) << "Could not find classes.dex within " << filename;
+ return NULL;
+ }
+
+ std::string cache_path = StringPrintf("%s.%08x", cache_path_tmp.c_str(), zip_entry->GetCrc32());
+ // Example cache_path = /data/art-cache/parent@dir@foo.jar@classes.dex.1a2b3c4d
+
+ while (true) {
+ DexFile* cached_dex_file = DexFile::OpenFile(cache_path);
+ if (cached_dex_file != NULL) {
+ return cached_dex_file;
+ }
+
+ // Try to open the temporary cache file, grabbing an exclusive
+ // lock. If somebody else is working on it, we'll block here until
+ // they complete. Because we're waiting on an external resource,
+ // we go into native mode.
+ Thread* current_thread = Thread::Current();
+ Thread::State old = current_thread->GetState();
+ current_thread->SetState(Thread::kNative);
+ scoped_ptr<LockedFd> fd(LockedFd::CreateAndLock(cache_path_tmp, 0644));
+ current_thread->SetState(old);
+ if (fd == NULL) {
+ return NULL;
+ }
+
+ // Check to see if the fd we opened and locked matches the file in
+ // the filesystem. If they don't, then somebody else unlinked
+ // ours and created a new file, and we need to use that one
+ // instead. (If we caught them between the unlink and the create,
+ // we'll get an ENOENT from the file stat.)
+ struct stat fd_stat;
+ int fd_stat_result = fstat(fd->GetFd(), &fd_stat);
+ if (fd_stat_result == -1) {
+ PLOG(ERROR) << "Can't stat open file '" << cache_path_tmp << "'";
+ return NULL;
+ }
+ struct stat file_stat;
+ int file_stat_result = stat(cache_path_tmp.c_str(), &file_stat);
+ if (file_stat_result == -1 ||
+ fd_stat.st_dev != file_stat.st_dev || fd_stat.st_ino != file_stat.st_ino) {
+ LOG(WARNING) << "our open cache file is stale; sleeping and retrying";
+ usleep(250 * 1000); // if something is hosed, don't peg machine
+ continue;
+ }
+
+ // We have the correct file open and locked. Extract classes.dex
+ TmpFile tmp_file(cache_path_tmp);
+ bool success = zip_entry->Extract(fd->GetFd());
+ if (!success) {
+ return NULL;
+ }
+
+ // TODO restat and check length against zip_entry->GetUncompressedLength()?
+
+ // Compute checksum and compare to zip. If things look okay, rename from tmp.
+ off_t lseek_result = lseek(fd->GetFd(), 0, SEEK_SET);
+ if (lseek_result == -1) {
+ return NULL;
+ }
+ const size_t kBufSize = 32768;
+ scoped_ptr<uint8_t> buf(new uint8_t[kBufSize]);
+ if (buf == NULL) {
+ return NULL;
+ }
+ uint32_t computed_crc = crc32(0L, Z_NULL, 0);
+ while (true) {
+ ssize_t bytes_read = TEMP_FAILURE_RETRY(read(fd->GetFd(), buf.get(), kBufSize));
+ if (bytes_read == 0) {
+ break;
+ }
+ computed_crc = crc32(computed_crc, buf.get(), bytes_read);
+ }
+ if (computed_crc != zip_entry->GetCrc32()) {
+ return NULL;
+ }
+ int rename_result = rename(cache_path_tmp.c_str(), cache_path.c_str());
+ if (rename_result == -1) {
+ PLOG(ERROR) << "Can't install dex cache file '" << cache_path << "' from '" << cache_path_tmp;
+ unlink(cache_path.c_str());
+ }
+ }
+ // NOTREACHED
+}
+
DexFile* DexFile::OpenPtr(byte* ptr, size_t length) {
CHECK(ptr != NULL);
DexFile::Closer* closer = new PtrCloser(ptr);