Bug 683127 part 8 - Allow to load Elf files from a Zip archive. r=tglek,r=sewardj
authorMike Hommey <mh+mozilla@glandium.org>
Fri, 20 Jan 2012 09:48:44 +0100
changeset 84970 bd752f4935d978c6e81f04c632ea054701f64a08
parent 84969 54a8b1b25477d04ecfae8f13775ec5b25a7c27de
child 84971 229140e62d7b49f34cb140a639d21f92aaa39d97
push idunknown
push userunknown
push dateunknown
reviewerstglek, sewardj
bugs683127
milestone12.0a1
Bug 683127 part 8 - Allow to load Elf files from a Zip archive. r=tglek,r=sewardj
browser/app/Makefile.in
configure.in
mozglue/linker/CustomElf.cpp
mozglue/linker/CustomElf.h
mozglue/linker/ElfLoader.cpp
mozglue/linker/ElfLoader.h
mozglue/linker/Makefile.in
mozglue/linker/Mappable.cpp
mozglue/linker/Mappable.h
mozglue/linker/Utils.h
mozglue/tests/Makefile.in
--- a/browser/app/Makefile.in
+++ b/browser/app/Makefile.in
@@ -78,16 +78,20 @@ LOCAL_INCLUDES += -I$(DEPTH)/build
 DEFINES += -DXPCOM_GLUE
 STL_FLAGS=
 
 LIBS += \
 	$(EXTRA_DSO_LIBS) \
 	$(XPCOM_STANDALONE_GLUE_LDOPTS) \
 	$(NULL)
 
+ifdef MOZ_LINKER
+LIBS += $(ZLIB_LIBS)
+endif
+
 ifndef MOZ_WINCONSOLE
 ifdef MOZ_DEBUG
 MOZ_WINCONSOLE = 1
 else
 MOZ_WINCONSOLE = 0
 endif
 endif
 
--- a/configure.in
+++ b/configure.in
@@ -7154,16 +7154,19 @@ elif test "${OS_TARGET}" = "WINNT" -o "$
   MOZ_GLUE_LDFLAGS='$(call EXPAND_LIBNAME_PATH,mozglue,$(LIBXUL_DIST)/lib)'
 else
   dnl On other Unix systems, we only want to link executables against mozglue
   MOZ_GLUE_PROGRAM_LDFLAGS='$(MKSHLIB_FORCE_ALL) $(call EXPAND_LIBNAME_PATH,mozglue,$(LIBXUL_DIST)/lib) $(MKSHLIB_UNFORCE_ALL)'
   if test -n "$GNU_CC"; then
     dnl And we need mozglue symbols to be exported.
     MOZ_GLUE_PROGRAM_LDFLAGS="$MOZ_GLUE_PROGRAM_LDFLAGS -rdynamic"
   fi
+  if test "$MOZ_LINKER" = 1; then
+    MOZ_GLUE_PROGRAM_LDFLAGS="$MOZ_GLUE_PROGRAM_LDFLAGS $ZLIB_LIBS"
+  fi
 fi
 
 if test -z "$MOZ_MEMORY"; then
   case "${target}" in
     *-mingw*)
       if test -z "$WIN32_REDIST_DIR" -a -z "$MOZ_DEBUG"; then
         AC_MSG_WARN([When not building jemalloc, you need to set WIN32_REDIST_DIR to the path to the Visual C++ Redist (usually VCINSTALLDIR\redist\x86\Microsoft.VC80.CRT, for VC++ v8) if you intend to distribute your build.])
       fi
--- a/mozglue/linker/CustomElf.cpp
+++ b/mozglue/linker/CustomElf.cpp
@@ -2,16 +2,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include <cstring>
 #include <sys/mman.h>
 #include <vector>
 #include <dlfcn.h>
 #include "CustomElf.h"
+#include "Mappable.h"
 #include "Logging.h"
 
 using namespace Elf;
 using namespace mozilla;
 
 #ifndef PAGE_SIZE
 #define PAGE_SIZE 4096
 #endif
@@ -68,28 +69,47 @@ void debug_phdr(const char *type, const 
         "flags: %c%c%c)",
         type, phdr->p_vaddr, phdr->p_filesz, phdr->p_memsz,
         phdr->p_offset, phdr->p_flags & PF_R ? 'r' : '-',
         phdr->p_flags & PF_W ? 'w' : '-', phdr->p_flags & PF_X ? 'x' : '-');
 }
 
 } /* anonymous namespace */
 
+/**
+ * RAII wrapper for a mapping of the first page off a Mappable object.
+ * This calls Mappable::munmap instead of system munmap.
+ */
+class Mappable1stPagePtr: public GenericMappedPtr<Mappable1stPagePtr> {
+public:
+  Mappable1stPagePtr(Mappable *mappable)
+  : GenericMappedPtr<Mappable1stPagePtr>(
+      mappable->mmap(NULL, PAGE_SIZE, PROT_READ, MAP_PRIVATE, 0), PAGE_SIZE)
+  , mappable(mappable)
+  { }
+
+  void munmap(void *buf, size_t length) {
+    mappable->munmap(buf, length);
+  }
+private:
+  Mappable *mappable;
+};
+
+
 TemporaryRef<LibHandle>
-CustomElf::Load(int fd, const char *path, int flags)
+CustomElf::Load(Mappable *mappable, const char *path, int flags)
 {
   debug("CustomElf::Load(\"%s\", %x) = ...", path, flags);
-  if (fd == -1)
+  if (!mappable)
     return NULL;
   /* Keeping a RefPtr of the CustomElf is going to free the appropriate
    * resources when returning NULL */
-  RefPtr<CustomElf> elf = new CustomElf(fd, path);
+  RefPtr<CustomElf> elf = new CustomElf(mappable, path);
   /* Map the first page of the Elf object to access Elf and program headers */
-  MappedPtr ehdr_raw(mmap(NULL, PAGE_SIZE, PROT_READ, MAP_PRIVATE, fd, 0),
-                      PAGE_SIZE);
+  Mappable1stPagePtr ehdr_raw(mappable);
   if (ehdr_raw == MAP_FAILED)
     return NULL;
 
   const Ehdr *ehdr = Ehdr::validate(ehdr_raw);
   if (!ehdr)
     return NULL;
 
   /* Scan Elf Program Headers and gather some information about them */
@@ -164,16 +184,19 @@ CustomElf::Load(int fd, const char *path
   }
 
   /* Load and initialize library */
   for (std::vector<const Phdr *>::iterator it = pt_loads.begin();
        it < pt_loads.end(); ++it)
     if (!elf->LoadSegment(*it))
       return NULL;
 
+  /* We're not going to mmap anymore */
+  mappable->finalize();
+
   report_mapping(const_cast<char *>(elf->GetName()), elf->base,
                  (max_vaddr + PAGE_SIZE - 1) & PAGE_MASK, 0);
 
   if (!elf->InitDyn(dyn))
     return NULL;
 
   debug("CustomElf::Load(\"%s\", %x) = %p", path, flags,
         static_cast<void *>(elf));
@@ -184,16 +207,17 @@ CustomElf::~CustomElf()
 {
   debug("CustomElf::~CustomElf(%p [\"%s\"])",
         reinterpret_cast<void *>(this), GetPath());
   CallFini();
   /* Normally, __cxa_finalize is called by the .fini function. However,
    * Android NDK before r6b doesn't do that. Our wrapped cxa_finalize only
    * calls destructors once, so call it in all cases. */
   ElfLoader::__wrap_cxa_finalize(this);
+  delete mappable;
 }
 
 namespace {
 
 /**
  * Hash function for symbol lookup, as defined in ELF standard for System V
  */
 unsigned long
@@ -334,39 +358,34 @@ CustomElf::LoadSegment(const Phdr *pt_lo
 
   /* Mmap at page boundary */
   Addr page_offset = pt_load->p_vaddr & ~PAGE_MASK;
   void *where = GetPtr(pt_load->p_vaddr - page_offset);
   debug("%s: Loading segment @%p %c%c%c", GetPath(), where,
                                           prot & PROT_READ ? 'r' : '-',
                                           prot & PROT_WRITE ? 'w' : '-',
                                           prot & PROT_EXEC ? 'x' : '-');
-  void *mapped = mmap(where, pt_load->p_filesz + page_offset,
-                      prot, MAP_PRIVATE | MAP_FIXED, fd,
-                      pt_load->p_offset - page_offset);
+  void *mapped = mappable->mmap(where, pt_load->p_filesz + page_offset,
+                                prot, MAP_PRIVATE | MAP_FIXED,
+                                pt_load->p_offset - page_offset);
   if (mapped != where) {
     if (mapped == MAP_FAILED) {
       log("%s: Failed to mmap", GetPath());
     } else {
       log("%s: Didn't map at the expected location (wanted: %p, got: %p)",
           GetPath(), where, mapped);
     }
     return false;
   }
 
   /* When p_memsz is greater than p_filesz, we need to have nulled out memory
    * after p_filesz and before p_memsz.
-   * We first null out bytes after p_filesz and up to the end of the page
-   * p_filesz is in. */
-  Addr end_offset = pt_load->p_filesz + page_offset;
-  if ((prot & PROT_WRITE) && (end_offset & ~PAGE_MASK)) {
-    memset(reinterpret_cast<char *>(mapped) + end_offset,
-           0, PAGE_SIZE - (end_offset & ~PAGE_MASK));
-  }
-  /* Above the end of that page, and up to p_memsz, we already have nulled out
+   * Mappable::mmap already guarantees that after p_filesz and up to the end
+   * of the page p_filesz is in, memory is nulled out.
+   * Above the end of that page, and up to p_memsz, we already have nulled out
    * memory because we mapped anonymous memory on the whole library virtual
    * address space. We just need to adjust this anonymous memory protection
    * flags. */
   if (pt_load->p_memsz > pt_load->p_filesz) {
     Addr file_end = pt_load->p_vaddr + pt_load->p_filesz;
     Addr mem_end = pt_load->p_vaddr + pt_load->p_memsz;
     Addr next_page = (file_end & ~(PAGE_SIZE - 1)) + PAGE_SIZE;
     if (mem_end > next_page) {
--- a/mozglue/linker/CustomElf.h
+++ b/mozglue/linker/CustomElf.h
@@ -197,33 +197,35 @@ struct Rela: public Elf_(Rela)
   Addr GetAddend(void *base) const
   {
     return r_addend;
   }
 };
 
 } /* namespace Elf */
 
+class Mappable;
+
 /**
  * Library Handle class for ELF libraries we don't let the system linker
  * handle.
  */
 class CustomElf: public LibHandle
 {
 public:
   /**
    * Returns a new CustomElf using the given file descriptor to map ELF
    * content. The file descriptor ownership is stolen, and it will be closed
    * in CustomElf's destructor if an instance is created, or by the Load
    * method otherwise. The path corresponds to the file descriptor, and flags
    * are the same kind of flags that would be given to dlopen(), though
    * currently, none are supported and the behaviour is more or less that of
    * RTLD_GLOBAL | RTLD_BIND_NOW.
    */
-  static mozilla::TemporaryRef<LibHandle> Load(int fd,
+  static mozilla::TemporaryRef<LibHandle> Load(Mappable *mappable,
                                                const char *path, int flags);
 
   /**
    * Inherited from LibHandle
    */
   virtual ~CustomElf();
   virtual void *GetSymbolPtr(const char *symbol) const;
   virtual bool Contains(void *addr) const;
@@ -246,18 +248,18 @@ private:
    * given symbol name. This is used to find symbols that are undefined
    * in the Elf object.
    */
   void *GetSymbolPtrInDeps(const char *symbol) const;
 
   /**
    * Private constructor
    */
-  CustomElf(int fd, const char *path)
-  : LibHandle(path), fd(fd), init(0), fini(0), initialized(false)
+  CustomElf(Mappable *mappable, const char *path)
+  : LibHandle(path), mappable(mappable), init(0), fini(0), initialized(false)
   { }
 
   /**
    * Returns a pointer relative to the base address where the library is
    * loaded.
    */
   void *GetPtr(const Elf::Addr offset) const
   {
@@ -328,18 +330,18 @@ private:
   /**
    * Call a function given a an address relative to the library base
    */
   void CallFunction(Elf::Addr addr) const
   {
     return CallFunction(GetPtr(addr));
   }
 
-  /* Appropriated file descriptor */
-  AutoCloseFD fd;
+  /* Appropriated Mappable */
+  Mappable *mappable;
 
   /* Base address where the library is loaded */
   MappedPtr base;
 
   /* String table */
   Elf::Strtab strtab;
 
   /* Symbol table */
--- a/mozglue/linker/ElfLoader.cpp
+++ b/mozglue/linker/ElfLoader.cpp
@@ -5,16 +5,17 @@
 #include <cstring>
 #include <cstdlib>
 #include <dlfcn.h>
 #include <unistd.h>
 #include <algorithm>
 #include <fcntl.h>
 #include "ElfLoader.h"
 #include "CustomElf.h"
+#include "Mappable.h"
 #include "Logging.h"
 
 using namespace mozilla;
 
 /**
  * dlfcn.h replacements functions
  */
 
@@ -184,23 +185,42 @@ ElfLoader::Load(const char *path, int fl
     const char *parentPath = parent->GetPath();
     abs_path = new char[strlen(parentPath) + strlen(path)];
     strcpy(abs_path, parentPath);
     char *slash = strrchr(abs_path, '/');
     strcpy(slash + 1, path);
     path = abs_path;
   }
 
-  /* Try loading the file with the custom linker, and fall back to the
-   * system linker if that fails */
-  AutoCloseFD fd = open(path, O_RDONLY);
-  if (fd != -1) {
-    handle = CustomElf::Load(fd, path, flags);
-    fd.forget();
+  /* Create a mappable object for the given path. Paths in the form
+   *   /foo/bar/baz/archive!/directory/lib.so
+   * try to load the directory/lib.so in /foo/bar/baz/archive, provided
+   * that file is a Zip archive. */
+  Mappable *mappable = NULL;
+  RefPtr<Zip> zip;
+  const char *subpath;
+  if ((subpath = strchr(path, '!'))) {
+    char *zip_path = strndup(path, subpath - path);
+    while (*(++subpath) == '/') { }
+    zip = zips.GetZip(zip_path);
+    Zip::Stream s;
+    if (zip && zip->GetStream(subpath, &s)) {
+      if (s.GetType() == Zip::Stream::DEFLATE)
+        mappable = MappableDeflate::Create(name, zip, &s);
+    }
   }
+  /* If we couldn't load above, try with a MappableFile */
+  if (!mappable && !zip)
+    mappable = MappableFile::Create(path);
+
+  /* Try loading with the custom linker if we have a Mappable */
+  if (mappable)
+    handle = CustomElf::Load(mappable, path, flags);
+
+  /* Try loading with the system linker if everything above failed */
   if (!handle)
     handle = SystemElf::Load(path, flags);
 
   /* If we didn't have an absolute path and haven't been able to load
    * a library yet, try in the system search path */
   if (!handle && abs_path)
     handle = SystemElf::Load(name, flags);
 
--- a/mozglue/linker/ElfLoader.h
+++ b/mozglue/linker/ElfLoader.h
@@ -283,11 +283,14 @@ protected:
     Destructor destructor;
     void *object;
     void *dso_handle;
   };
 
 private:
   /* Keep track of all registered destructors */
   std::vector<DestructorCaller> destructors;
+
+  /* Keep track of Zips used for library loading */
+  ZipCollection zips;
 };
 
 #endif /* ElfLoader_h */
--- a/mozglue/linker/Makefile.in
+++ b/mozglue/linker/Makefile.in
@@ -17,12 +17,13 @@ STL_FLAGS =
 CPPSRCS = \
   Zip.cpp \
   $(NULL)
 
 ifndef MOZ_OLD_LINKER
 CPPSRCS += \
   ElfLoader.cpp \
   CustomElf.cpp \
+  Mappable.cpp \
   $(NULL)
 endif
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/mozglue/linker/Mappable.cpp
@@ -0,0 +1,247 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <cstring>
+#include <cstdlib>
+#include "Mappable.h"
+#ifdef ANDROID
+#include <linux/ashmem.h>
+#endif
+#include "Logging.h"
+
+#ifndef PAGE_SIZE
+#define PAGE_SIZE 4096
+#endif
+
+#ifndef PAGE_MASK
+#define PAGE_MASK (~ (PAGE_SIZE - 1))
+#endif
+
+MappableFile *MappableFile::Create(const char *path)
+{
+  int fd = open(path, O_RDONLY);
+  if (fd != -1)
+    return new MappableFile(fd);
+  return NULL;
+}
+
+void *
+MappableFile::mmap(const void *addr, size_t length, int prot, int flags,
+                   off_t offset)
+{
+  // ASSERT(fd != -1)
+  // ASSERT(! flags & MAP_SHARED)
+  flags |= MAP_PRIVATE;
+
+  void *mapped = ::mmap(const_cast<void *>(addr), length, prot, flags,
+                        fd, offset);
+  if (mapped == MAP_FAILED)
+    return mapped;
+
+  /* Fill the remainder of the last page with zeroes when the requested
+   * protection has write bits. */
+  if ((mapped != MAP_FAILED) && (prot & PROT_WRITE) &&
+      (length & (PAGE_SIZE - 1))) {
+    memset(reinterpret_cast<char *>(mapped) + length, 0,
+           PAGE_SIZE - (length & ~(PAGE_MASK)));
+  }
+  return mapped;
+}
+
+void
+MappableFile::finalize()
+{
+  /* Close file ; equivalent to close(fd.forget()) */
+  fd = -1;
+}
+
+/**
+ * _MappableBuffer is a buffer which content can be mapped at different
+ * locations in the virtual address space.
+ * On Linux, uses a (deleted) temporary file on a tmpfs for sharable content.
+ * On Android, uses ashmem.
+ */
+class _MappableBuffer: public MappedPtr
+{
+public:
+  /**
+   * Returns a _MappableBuffer instance with the given name and the given
+   * length.
+   */
+  static _MappableBuffer *Create(const char *name, size_t length)
+  {
+    AutoCloseFD fd;
+#ifdef ANDROID
+    /* On Android, initialize an ashmem region with the given length */
+    fd = open("/" ASHMEM_NAME_DEF, O_RDWR, 0600);
+    if (fd == -1)
+      return NULL;
+    char str[ASHMEM_NAME_LEN];
+    strlcpy(str, name, sizeof(str));
+    ioctl(fd, ASHMEM_SET_NAME, str);
+    if (ioctl(fd, ASHMEM_SET_SIZE, length))
+      return NULL;
+
+    /* The Gecko crash reporter is confused by adjacent memory mappings of
+     * the same file. On Android, subsequent mappings are growing in memory
+     * address, and chances are we're going to map from the same file
+     * descriptor right away. Allocate one page more than requested so that
+     * there is a gap between this mapping and the subsequent one. */
+    void *buf = ::mmap(NULL, length + PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+    if (buf != MAP_FAILED) {
+      /* Actually create the gap with anonymous memory */
+      ::mmap(reinterpret_cast<char *>(buf) + ((length + PAGE_SIZE) & PAGE_MASK),
+             PAGE_SIZE, PROT_NONE, MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS,
+             -1, 0);
+      debug("Decompression buffer of size %d in ashmem \"%s\", mapped @%p",
+            length, str, buf);
+      return new _MappableBuffer(fd.forget(), buf, length);
+    }
+#else
+    /* On Linux, use /dev/shm as base directory for temporary files, assuming
+     * it's on tmpfs */
+    /* TODO: check that /dev/shm is tmpfs */
+    char path[256];
+    sprintf(path, "/dev/shm/%s.XXXXXX", name);
+    fd = mkstemp(path);
+    if (fd == -1)
+      return NULL;
+    unlink(path);
+    ftruncate(fd, length);
+
+    void *buf = ::mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+    if (buf != MAP_FAILED) {
+      debug("Decompression buffer of size %ld in \"%s\", mapped @%p",
+            length, path, buf);
+      return new _MappableBuffer(fd.forget(), buf, length);
+    }
+#endif
+    return NULL;
+  }
+
+  void *mmap(const void *addr, size_t length, int prot, int flags, off_t offset)
+  {
+    // ASSERT(fd != -1)
+#ifdef ANDROID
+    /* Mapping ashmem MAP_PRIVATE is like mapping anonymous memory, even when
+     * there is content in the ashmem */
+    if (flags & MAP_PRIVATE) {
+      flags &= ~MAP_PRIVATE;
+      flags |= MAP_SHARED;
+    }
+#endif
+    return ::mmap(const_cast<void *>(addr), length, prot, flags, fd, offset);
+  }
+
+#ifdef ANDROID
+  ~_MappableBuffer() {
+    /* Free the additional page we allocated. See _MappableBuffer::Create */
+    munmap(this + ((GetLength() + PAGE_SIZE) & ~(PAGE_SIZE - 1)), PAGE_SIZE);
+  }
+#endif
+
+private:
+  _MappableBuffer(int fd, void *buf, size_t length)
+  : MappedPtr(buf, length), fd(fd) { }
+
+  /* File descriptor for the temporary file or ashmem */
+  AutoCloseFD fd;
+};
+
+
+MappableDeflate *
+MappableDeflate::Create(const char *name, Zip *zip, Zip::Stream *stream)
+{
+  // ASSERT(stream->GetType() == Zip::Stream::DEFLATE)
+  _MappableBuffer *buf = _MappableBuffer::Create(name, stream->GetUncompressedSize());
+  if (buf)
+    return new MappableDeflate(buf, zip, stream);
+  return NULL;
+}
+
+MappableDeflate::MappableDeflate(_MappableBuffer *buf, Zip *zip,
+                                 Zip::Stream *stream)
+: zip(zip), buffer(buf)
+{
+  /* Initialize Zlib data with zip stream info and decompression buffer */
+  memset(&zStream, 0, sizeof(zStream));
+  zStream.avail_in = stream->GetSize();
+  zStream.next_in = const_cast<Bytef *>(
+                    reinterpret_cast<const Bytef *>(stream->GetBuffer()));
+  zStream.total_in = 0;
+  zStream.avail_out = stream->GetUncompressedSize();
+  zStream.next_out = static_cast<Bytef*>(*buffer);
+  zStream.total_out = 0;
+}
+
+MappableDeflate::~MappableDeflate()
+{
+  delete buffer;
+}
+
+void *
+MappableDeflate::mmap(const void *addr, size_t length, int prot, int flags, off_t offset)
+{
+  // ASSERT(buffer)
+  // ASSERT(! flags & MAP_SHARED)
+  flags |= MAP_PRIVATE;
+
+  /* The deflate stream is uncompressed up to the required offset + length, if
+   * it hasn't previously been uncompressed */
+  ssize_t missing = offset + length + zStream.avail_out - buffer->GetLength();
+  if (missing > 0) {
+    uInt avail_out = zStream.avail_out;
+    zStream.avail_out = missing;
+    if ((*buffer == zStream.next_out) &&
+        (inflateInit2(&zStream, -MAX_WBITS) != Z_OK)) {
+      log("inflateInit failed: %s", zStream.msg);
+      return MAP_FAILED;
+    }
+    int ret = inflate(&zStream, Z_SYNC_FLUSH);
+    if (ret < 0) {
+      log("inflate failed: %s", zStream.msg);
+      return MAP_FAILED;
+    }
+    if (ret == Z_NEED_DICT) {
+      log("zstream requires a dictionary. %s", zStream.msg);
+      return MAP_FAILED;
+    }
+    zStream.avail_out = avail_out - missing + zStream.avail_out;
+    if (ret == Z_STREAM_END) {
+      if (inflateEnd(&zStream) != Z_OK) {
+        log("inflateEnd failed: %s", zStream.msg);
+        return MAP_FAILED;
+      }
+      if (zStream.total_out != buffer->GetLength()) {
+        log("File not fully uncompressed! %ld / %d", zStream.total_out,
+            static_cast<unsigned int>(buffer->GetLength()));
+        return MAP_FAILED;
+      }
+    }
+  }
+#if defined(ANDROID) && defined(__arm__)
+  if (prot & PROT_EXEC) {
+    /* We just extracted data that may be executed in the future.
+     * We thus need to ensure Instruction and Data cache coherency. */
+    debug("cacheflush(%p, %p)", *buffer + offset, *buffer + (offset + length));
+    cacheflush(reinterpret_cast<uintptr_t>(*buffer + offset),
+               reinterpret_cast<uintptr_t>(*buffer + (offset + length)), 0);
+  }
+#endif
+
+  return buffer->mmap(addr, length, prot, flags, offset);
+}
+
+void
+MappableDeflate::finalize()
+{
+  /* Free decompression buffer */
+  delete buffer;
+  buffer = NULL;
+  /* Remove reference to Zip archive */
+  zip = NULL;
+}
new file mode 100644
--- /dev/null
+++ b/mozglue/linker/Mappable.h
@@ -0,0 +1,96 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef Mappable_h
+#define Mappable_h
+
+#include "Zip.h"
+#include "mozilla/RefPtr.h"
+#include "zlib.h"
+
+/**
+ * Abstract class to handle mmap()ing from various kind of entities, such as
+ * plain files or Zip entries. The virtual members are meant to act as the
+ * equivalent system functions, with a few differences:
+ * - mapped memory is always MAP_PRIVATE, even though a given implementation
+ *   may use something different internally.
+ * - memory after length and up to the end of the corresponding page is nulled
+ *   out.
+ */
+class Mappable
+{
+public:
+  virtual ~Mappable() { }
+
+  virtual void *mmap(const void *addr, size_t length, int prot, int flags,
+                     off_t offset) = 0;
+  virtual void munmap(void *addr, size_t length) {
+    ::munmap(addr, length);
+  }
+
+  /**
+   * Indicate to a Mappable instance that no further mmap is going to happen.
+   */
+  virtual void finalize() = 0;
+};
+
+/**
+ * Mappable implementation for plain files
+ */
+class MappableFile: public Mappable
+{
+public:
+  ~MappableFile() { }
+
+  /**
+   * Create a MappableFile instance for the given file path.
+   */
+  static MappableFile *Create(const char *path);
+
+  /* Inherited from Mappable */
+  virtual void *mmap(const void *addr, size_t length, int prot, int flags, off_t offset);
+  virtual void finalize();
+
+private:
+  MappableFile(int fd): fd(fd) { }
+
+  /* File descriptor */
+  AutoCloseFD fd;
+};
+
+class _MappableBuffer;
+
+/**
+ * Mappable implementation for deflated stream in a Zip archive
+ */
+class MappableDeflate: public Mappable
+{
+public:
+  ~MappableDeflate();
+
+  /**
+   * Create a MappableDeflate instance for the given Zip stream. The name
+   * argument is used for an appropriately named temporary file, and the Zip
+   * instance is given for the MappableDeflate to keep a reference of it.
+   */
+  static MappableDeflate *Create(const char *name, Zip *zip, Zip::Stream *stream);
+
+  /* Inherited from Mappable */
+  virtual void *mmap(const void *addr, size_t length, int prot, int flags, off_t offset);
+  virtual void finalize();
+
+private:
+  MappableDeflate(_MappableBuffer *buf, Zip *zip, Zip::Stream *stream);
+
+  /* Zip reference */
+  mozilla::RefPtr<Zip> zip;
+
+  /* Decompression buffer */
+  _MappableBuffer *buffer;
+
+  /* Zlib data */
+  z_stream zStream;
+};
+
+#endif /* Mappable_h */
--- a/mozglue/linker/Utils.h
+++ b/mozglue/linker/Utils.h
@@ -91,32 +91,36 @@ public:
 
 private:
   int fd;
 };
 
 /**
  * MappedPtr is a RAII wrapper for mmap()ed memory. It can be used as
  * a simple void * or unsigned char *.
+ *
+ * It is defined as a derivative of a template that allows to use a
+ * different unmapping strategy.
  */
-class MappedPtr
+template <typename T>
+class GenericMappedPtr
 {
 public:
-  MappedPtr(void *buf, size_t length): buf(buf), length(length) { }
-  MappedPtr(): buf(MAP_FAILED), length(0) { }
+  GenericMappedPtr(void *buf, size_t length): buf(buf), length(length) { }
+  GenericMappedPtr(): buf(MAP_FAILED), length(0) { }
 
   void Init(void *b, size_t len) {
     buf = b;
     length = len;
   }
 
-  ~MappedPtr()
+  ~GenericMappedPtr()
   {
     if (buf != MAP_FAILED)
-      munmap(buf, length);
+      static_cast<T *>(this)->munmap(buf, length);
   }
 
   operator void *() const
   {
     return buf;
   }
 
   operator unsigned char *() const
@@ -140,21 +144,41 @@ public:
   /**
    * Returns whether the given address is within the mapped range
    */
   bool Contains(void *ptr) const
   {
     return (ptr >= buf) && (ptr < reinterpret_cast<char *>(buf) + length);
   }
 
+  /**
+   * Returns the length of the mapped range
+   */
+  size_t GetLength() const
+  {
+    return length;
+  }
+
 private:
   void *buf;
   size_t length;
 };
 
+struct MappedPtr: public GenericMappedPtr<MappedPtr>
+{
+  MappedPtr(void *buf, size_t length)
+  : GenericMappedPtr<MappedPtr>(buf, length) { }
+  MappedPtr(): GenericMappedPtr<MappedPtr>() { }
+
+  void munmap(void *buf, size_t length)
+  {
+    ::munmap(buf, length);
+  }
+};
+
 /**
  * UnsizedArray is a way to access raw arrays of data in memory.
  *
  *   struct S { ... };
  *   UnsizedArray<S> a(buf);
  *   UnsizedArray<S> b; b.Init(buf);
  *
  * This is roughly equivalent to
--- a/mozglue/tests/Makefile.in
+++ b/mozglue/tests/Makefile.in
@@ -18,16 +18,18 @@ SIMPLE_PROGRAMS := $(CPPSRCS:.cpp=$(BIN_
 NO_DIST_INSTALL = 1
 STL_FLAGS =
 
 LOCAL_INCLUDES += -I$(srcdir)/../linker
 # Only link against the linker, not mozglue
 MOZ_GLUE_PROGRAM_LDFLAGS =
 MOZ_GLUE_LDFLAGS =
 LIBS += $(call EXPAND_LIBNAME_PATH,linker,../linker)
+
+EXTRA_LIBS = $(ZLIB_LIBS)
 endif
 
 include $(topsrcdir)/config/rules.mk
 
 ifdef MOZ_LINKER
 check::
 	@$(EXIT_ON_ERROR) ./TestZip$(BIN_SUFFIX) $(srcdir)
 endif