Support loading shared libraries from zip files

Add code to support loading shared libraries directly from within
APK files.

Extends the linker's handling of LD_LIBRARY_PATH, DT_RUNPATH, etc
to allow elements to be either directories as normal, or ZIP
format files.  For ZIP, the ZIP subdirectory string is separated
from the path to file by '!'.

For example, if DT_NEEDED is libchrome.so and Chrome.apk is the
Android ARM APK then the path element

  /system/app/Chrome.apk!lib/armeabi-v7a

would cause the linker to load lib/armeabi-v7a/libchrome.so
directly from inside Chrome.apk.  For loading to succeed,
libchrome.so must be 'stored' and not compressed in Chrome.apk,
and must be page aligned within the file.

Motivation:
  Chromium tracking issue:
  https://code.google.com/p/chromium/issues/detail?id=390618

Bug: 8076853
Change-Id: Ic49046600b1417eae3ee8f37ee98c8ac1ecc19e7
diff --git a/linker/Android.mk b/linker/Android.mk
index f78a025..81c007f 100644
--- a/linker/Android.mk
+++ b/linker/Android.mk
@@ -57,7 +57,7 @@
 
 LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
 
-LOCAL_STATIC_LIBRARIES := libc_nomalloc
+LOCAL_STATIC_LIBRARIES := libc_nomalloc libziparchive libutils libz liblog
 
 LOCAL_FORCE_STATIC_EXECUTABLE := true
 
diff --git a/linker/linker.cpp b/linker/linker.cpp
index 18f8cdc..15ea423 100644
--- a/linker/linker.cpp
+++ b/linker/linker.cpp
@@ -57,6 +57,7 @@
 #include "linker_phdr.h"
 #include "linker_relocs.h"
 #include "linker_reloc_iterators.h"
+#include "ziparchive/zip_archive.h"
 
 /* >>> IMPORTANT NOTE - READ ME BEFORE MODIFYING <<<
  *
@@ -838,29 +839,109 @@
   return nullptr;
 }
 
-static int open_library_on_path(const char* name, const char* const paths[]) {
-  char buf[512];
-  for (size_t i = 0; paths[i] != nullptr; ++i) {
-    int n = __libc_format_buffer(buf, sizeof(buf), "%s/%s", paths[i], name);
-    if (n < 0 || n >= static_cast<int>(sizeof(buf))) {
-      PRINT("Warning: ignoring very long library path: %s/%s", paths[i], name);
-      continue;
-    }
-    int fd = TEMP_FAILURE_RETRY(open(buf, O_RDONLY | O_CLOEXEC));
-    if (fd != -1) {
-      return fd;
-    }
+static int open_library_in_zipfile(const char* const path,
+                                   off64_t* file_offset) {
+  TRACE("Trying zip file open from path '%s'", path);
+
+  // Treat an '!' character inside a path as the separator between the name
+  // of the zip file on disk and the subdirectory to search within it.
+  // For example, if path is "foo.zip!bar/bas/x.so", then we search for
+  // "bar/bas/x.so" within "foo.zip".
+  const char* separator = strchr(path, '!');
+  if (separator == nullptr) {
+    return -1;
   }
-  return -1;
+
+  char buf[512];
+  if (strlcpy(buf, path, sizeof(buf)) >= sizeof(buf)) {
+    PRINT("Warning: ignoring very long library path: %s", path);
+    return -1;
+  }
+
+  buf[separator - path] = '\0';
+
+  const char* zip_path = buf;
+  const char* file_path = &buf[separator - path + 1];
+  int fd = TEMP_FAILURE_RETRY(open(zip_path, O_RDONLY | O_CLOEXEC));
+  if (fd == -1) {
+    return -1;
+  }
+
+  ZipArchiveHandle handle;
+  if (OpenArchiveFd(fd, "", &handle, false) != 0) {
+    // invalid zip-file (?)
+    close(fd);
+    return -1;
+  }
+
+  auto archive_guard = make_scope_guard([&]() {
+    CloseArchive(handle);
+  });
+
+  ZipEntry entry;
+
+  if (FindEntry(handle, ZipEntryName(file_path), &entry) != 0) {
+    // Entry was not found.
+    close(fd);
+    return -1;
+  }
+
+  // Check if it is properly stored
+  if (entry.method != kCompressStored || (entry.offset % PAGE_SIZE) != 0) {
+    close(fd);
+    return -1;
+  }
+
+  *file_offset = entry.offset;
+  return fd;
 }
 
-static int open_library(const char* name) {
+static int open_library_on_path(const char* name,
+                                const char* const paths[],
+                                off64_t* file_offset) {
+  char buf[512];
+  int fd = -1;
+
+  for (size_t i = 0; paths[i] != nullptr && fd == -1; ++i) {
+    const char* const path = paths[i];
+    int n = __libc_format_buffer(buf, sizeof(buf), "%s/%s", path, name);
+    if (n < 0 || n >= static_cast<int>(sizeof(buf))) {
+      PRINT("Warning: ignoring very long library path: %s/%s", path, name);
+      return -1;
+    }
+
+    const char* separator = strchr(path, '!');
+
+    if (separator != nullptr) {
+      fd = open_library_in_zipfile(buf, file_offset);
+    }
+
+    if (fd == -1) {
+      fd = TEMP_FAILURE_RETRY(open(buf, O_RDONLY | O_CLOEXEC));
+      if (fd != -1) {
+        *file_offset = 0;
+      }
+    }
+  }
+
+  return fd;
+}
+
+static int open_library(const char* name, off64_t* file_offset) {
   TRACE("[ opening %s ]", name);
 
   // If the name contains a slash, we should attempt to open it directly and not search the paths.
   if (strchr(name, '/') != nullptr) {
+    if (strchr(name, '!') != nullptr) {
+      int fd = open_library_in_zipfile(name, file_offset);
+      if (fd != -1) {
+        return fd;
+      }
+    }
+
     int fd = TEMP_FAILURE_RETRY(open(name, O_RDONLY | O_CLOEXEC));
     if (fd != -1) {
+      *file_offset = 0;
       return fd;
     }
     // ...but nvidia binary blobs (at least) rely on this behavior, so fall through for now.
@@ -870,9 +951,9 @@
   }
 
   // Otherwise we try LD_LIBRARY_PATH first, and fall back to the built-in well known paths.
-  int fd = open_library_on_path(name, g_ld_library_paths);
+  int fd = open_library_on_path(name, g_ld_library_paths, file_offset);
   if (fd == -1) {
-    fd = open_library_on_path(name, kDefaultLdPaths);
+    fd = open_library_on_path(name, kDefaultLdPaths, file_offset);
   }
   return fd;
 }
@@ -886,7 +967,9 @@
   }
 }
 
-static soinfo* load_library(LoadTaskList& load_tasks, const char* name, int rtld_flags, const android_dlextinfo* extinfo) {
+static soinfo* load_library(LoadTaskList& load_tasks,
+                            const char* name, int rtld_flags,
+                            const android_dlextinfo* extinfo) {
   int fd = -1;
   off64_t file_offset = 0;
   ScopedFd file_guard(-1);
@@ -898,7 +981,7 @@
     }
   } else {
     // Open the file.
-    fd = open_library(name);
+    fd = open_library(name, &file_offset);
     if (fd == -1) {
       DL_ERR("library \"%s\" not found", name);
       return nullptr;
diff --git a/tests/dlfcn_test.cpp b/tests/dlfcn_test.cpp
index dfb5e54..ef4ff20 100644
--- a/tests/dlfcn_test.cpp
+++ b/tests/dlfcn_test.cpp
@@ -29,6 +29,14 @@
 #define ASSERT_SUBSTR(needle, haystack) \
     ASSERT_PRED_FORMAT2(::testing::IsSubstring, needle, haystack)
 
+#if defined(__LP64__)
+#define LIBPATH_PREFIX "/nativetest64/libdlext_test_fd/"
+#else
+#define LIBPATH_PREFIX "/nativetest/libdlext_test_fd/"
+#endif
+
+#define LIBZIPPATH LIBPATH_PREFIX "libdlext_test_fd_zipaligned.zip"
+
 static bool g_called = false;
 extern "C" void DlSymTestFunction() {
   g_called = true;
@@ -844,6 +852,46 @@
   dlclose(handle2);
 }
 
+TEST(dlfcn, dlopen_from_zip_absolute_path) {
+  const std::string lib_path = std::string(getenv("ANDROID_DATA")) + LIBZIPPATH;
+
+  void* handle = dlopen((lib_path + "!libdir/libdlext_test_fd.so").c_str(), RTLD_NOW);
+  ASSERT_TRUE(handle != nullptr) << dlerror();
+
+  int (*fn)(void);
+  fn = reinterpret_cast<int (*)(void)>(dlsym(handle, "getRandomNumber"));
+  ASSERT_TRUE(fn != nullptr);
+  EXPECT_EQ(4, fn());
+
+  dlclose(handle);
+}
+
+TEST(dlfcn, dlopen_from_zip_ld_library_path) {
+  const std::string lib_path = std::string(getenv("ANDROID_DATA")) + LIBZIPPATH + "!libdir";
+
+  typedef void (*fn_t)(const char*);
+  fn_t android_update_LD_LIBRARY_PATH =
+      reinterpret_cast<fn_t>(dlsym(RTLD_DEFAULT, "android_update_LD_LIBRARY_PATH"));
+
+  ASSERT_TRUE(android_update_LD_LIBRARY_PATH != nullptr) << dlerror();
+
+  void* handle = dlopen("libdlext_test_fd.so", RTLD_NOW);
+  ASSERT_TRUE(handle == nullptr);
+
+  android_update_LD_LIBRARY_PATH(lib_path.c_str());
+
+  handle = dlopen("libdlext_test_fd.so", RTLD_NOW);
+  ASSERT_TRUE(handle != nullptr) << dlerror();
+
+  int (*fn)(void);
+  fn = reinterpret_cast<int (*)(void)>(dlsym(handle, "getRandomNumber"));
+  ASSERT_TRUE(fn != nullptr);
+  EXPECT_EQ(4, fn());
+
+  dlclose(handle);
+}
+
+
 // libtest_dlopen_from_ctor_main.so depends on
 // libtest_dlopen_from_ctor.so which has a constructor
 // that calls dlopen(libc...). This is to test the situation
diff --git a/tests/libs/Android.build.dlext_testzip.mk b/tests/libs/Android.build.dlext_testzip.mk
index d05927e..7cc0dae 100644
--- a/tests/libs/Android.build.dlext_testzip.mk
+++ b/tests/libs/Android.build.dlext_testzip.mk
@@ -35,7 +35,7 @@
 $(LOCAL_BUILT_MODULE): PRIVATE_ALIGNMENT := 4096 # PAGE_SIZE
 $(LOCAL_BUILT_MODULE) : $(my_shared_libs) | $(ZIPALIGN)
 	@echo "Zipalign $(PRIVATE_ALIGNMENT): $@"
-	$(hide) rm -rf $(dir $@) && mkdir -p $(dir $@)
-	$(hide) cp $^ $(dir $@)
-	$(hide) (cd $(dir $@) && touch empty_file.txt && zip -rD0 $(notdir $@).unaligned empty_file.txt *.so)
+	$(hide) rm -rf $(dir $@) && mkdir -p $(dir $@)/libdir
+	$(hide) cp $^ $(dir $@)/libdir
+	$(hide) (cd $(dir $@) && touch empty_file.txt && zip -rD0 $(notdir $@).unaligned empty_file.txt libdir/*.so)
 	$(hide) $(ZIPALIGN) $(PRIVATE_ALIGNMENT) $@.unaligned $@