Add functions in recovery/minzip to libziparchive

Add two functions libziparchive that libminzip has. And create
corresponding unit tests.
1. Open a zip archive from a memory mapped region.
2. A new writer that takes a call back function pointer.
(Used by the OTA updater to stream the data.)

Test: Unit tests passed
Bug: 19472796
Change-Id: I2b2daec71174afe221030357e39bff5faea51e72
diff --git a/include/ziparchive/zip_archive.h b/include/ziparchive/zip_archive.h
index 4f68c3b..fc845a4 100644
--- a/include/ziparchive/zip_archive.h
+++ b/include/ziparchive/zip_archive.h
@@ -130,6 +130,8 @@
 int32_t OpenArchiveFd(const int fd, const char* debugFileName,
                       ZipArchiveHandle *handle, bool assume_ownership = true);
 
+int32_t OpenArchiveFromMemory(void* address, size_t length, const char* debugFileName,
+                              ZipArchiveHandle *handle);
 /*
  * Close archive, releasing resources associated with it. This will
  * unmap the central directory of the zipfile and free all internal
@@ -214,6 +216,17 @@
 
 const char* ErrorCodeString(int32_t error_code);
 
+#if !defined(_WIN32)
+typedef bool (*ProcessZipEntryFunction)(const uint8_t* buf, size_t buf_size, void* cookie);
+
+/*
+ * Stream the uncompressed data through the supplied function,
+ * passing cookie to it each time it gets called.
+*/
+int32_t ProcessZipEntryContents(ZipArchiveHandle handle, ZipEntry* entry,
+        ProcessZipEntryFunction func, void* cookie);
+#endif
+
 __END_DECLS
 
 #endif  // LIBZIPARCHIVE_ZIPARCHIVE_H_
diff --git a/libziparchive/testdata/dummy-update.zip b/libziparchive/testdata/dummy-update.zip
new file mode 100644
index 0000000..6976bf1
--- /dev/null
+++ b/libziparchive/testdata/dummy-update.zip
Binary files differ
diff --git a/libziparchive/zip_archive.cc b/libziparchive/zip_archive.cc
index cc3f0e4..b00557c 100644
--- a/libziparchive/zip_archive.cc
+++ b/libziparchive/zip_archive.cc
@@ -215,19 +215,14 @@
   return 0;
 }
 
-static int32_t MapCentralDirectory0(int fd, const char* debug_file_name,
-                                    ZipArchive* archive, off64_t file_length,
-                                    off64_t read_amount, uint8_t* scan_buffer) {
+static int32_t MapCentralDirectory0(const char* debug_file_name, ZipArchive* archive,
+                                    off64_t file_length, off64_t read_amount,
+                                    uint8_t* scan_buffer) {
   const off64_t search_start = file_length - read_amount;
 
-  if (lseek64(fd, search_start, SEEK_SET) != search_start) {
-    ALOGW("Zip: seek %" PRId64 " failed: %s", static_cast<int64_t>(search_start),
-          strerror(errno));
-    return kIoError;
-  }
-  if (!android::base::ReadFully(fd, scan_buffer, static_cast<size_t>(read_amount))) {
-    ALOGW("Zip: read %" PRId64 " failed: %s", static_cast<int64_t>(read_amount),
-          strerror(errno));
+  if(!archive->mapped_zip.ReadAtOffset(scan_buffer, read_amount, search_start)) {
+    ALOGE("Zip: read %" PRId64 " from offset %" PRId64 " failed",
+          static_cast<int64_t>(read_amount), static_cast<int64_t>(search_start));
     return kIoError;
   }
 
@@ -287,9 +282,11 @@
    * It all looks good.  Create a mapping for the CD, and set the fields
    * in archive.
    */
-  if (!archive->directory_map.create(debug_file_name, fd,
-          static_cast<off64_t>(eocd->cd_start_offset),
-          static_cast<size_t>(eocd->cd_size), true /* read only */) ) {
+
+  if (!archive->InitializeCentralDirectory(debug_file_name,
+                                           static_cast<off64_t>(eocd->cd_start_offset),
+                                           static_cast<size_t>(eocd->cd_size))) {
+    ALOGE("Zip: failed to intialize central directory.\n");
     return kMmapFailed;
   }
 
@@ -304,18 +301,16 @@
  *
  * On success, returns 0 after populating fields from the EOCD area:
  *   directory_offset
- *   directory_map
+ *   directory_ptr
  *   num_entries
  */
-static int32_t MapCentralDirectory(int fd, const char* debug_file_name,
-                                   ZipArchive* archive) {
+static int32_t MapCentralDirectory(const char* debug_file_name, ZipArchive* archive) {
 
   // Test file length. We use lseek64 to make sure the file
   // is small enough to be a zip file (Its size must be less than
   // 0xffffffff bytes).
-  off64_t file_length = lseek64(fd, 0, SEEK_END);
+  off64_t file_length = archive->mapped_zip.GetFileLength();
   if (file_length == -1) {
-    ALOGV("Zip: lseek on fd %d failed", fd);
     return kInvalidFile;
   }
 
@@ -346,11 +341,9 @@
     read_amount = file_length;
   }
 
-  uint8_t* scan_buffer = reinterpret_cast<uint8_t*>(malloc(read_amount));
-  int32_t result = MapCentralDirectory0(fd, debug_file_name, archive,
-                                        file_length, read_amount, scan_buffer);
-
-  free(scan_buffer);
+  std::vector<uint8_t> scan_buffer(read_amount);
+  int32_t result = MapCentralDirectory0(debug_file_name, archive, file_length, read_amount,
+                                        scan_buffer.data());
   return result;
 }
 
@@ -361,9 +354,8 @@
  * Returns 0 on success.
  */
 static int32_t ParseZipArchive(ZipArchive* archive) {
-  const uint8_t* const cd_ptr =
-      reinterpret_cast<const uint8_t*>(archive->directory_map.getDataPtr());
-  const size_t cd_length = archive->directory_map.getDataLength();
+  const uint8_t* const cd_ptr = archive->central_directory.GetBasePtr();
+  const size_t cd_length = archive->central_directory.GetMapLength();
   const uint16_t num_entries = archive->num_entries;
 
   /*
@@ -437,7 +429,7 @@
 static int32_t OpenArchiveInternal(ZipArchive* archive,
                                    const char* debug_file_name) {
   int32_t result = -1;
-  if ((result = MapCentralDirectory(archive->fd, debug_file_name, archive))) {
+  if ((result = MapCentralDirectory(debug_file_name, archive)) != 0) {
     return result;
   }
 
@@ -468,6 +460,13 @@
   return OpenArchiveInternal(archive, fileName);
 }
 
+int32_t OpenArchiveFromMemory(void* address, size_t length, const char* debug_file_name,
+                              ZipArchiveHandle *handle) {
+  ZipArchive* archive = new ZipArchive(address, length);
+  *handle = archive;
+  return OpenArchiveInternal(archive, debug_file_name);
+}
+
 /*
  * Close a ZipArchive, closing the file and freeing the contents.
  */
@@ -477,10 +476,10 @@
   delete archive;
 }
 
-static int32_t UpdateEntryFromDataDescriptor(int fd,
+static int32_t UpdateEntryFromDataDescriptor(MappedZipFile& mapped_zip,
                                              ZipEntry *entry) {
   uint8_t ddBuf[sizeof(DataDescriptor) + sizeof(DataDescriptor::kOptSignature)];
-  if (!android::base::ReadFully(fd, ddBuf, sizeof(ddBuf))) {
+  if (!mapped_zip.ReadData(ddBuf, sizeof(ddBuf))) {
     return kIoError;
   }
 
@@ -495,23 +494,6 @@
   return 0;
 }
 
-// Attempts to read |len| bytes into |buf| at offset |off|.
-// On non-Windows platforms, callers are guaranteed that the |fd|
-// offset is unchanged and there is no side effect to this call.
-//
-// On Windows platforms this is not thread-safe.
-static inline bool ReadAtOffset(int fd, uint8_t* buf, size_t len, off64_t off) {
-#if !defined(_WIN32)
-  return TEMP_FAILURE_RETRY(pread64(fd, buf, len, off));
-#else
-  if (lseek64(fd, off, SEEK_SET) != off) {
-    ALOGW("Zip: failed seek to offset %" PRId64, off);
-    return false;
-  }
-  return android::base::ReadFully(fd, buf, len);
-#endif
-}
-
 static int32_t FindEntry(const ZipArchive* archive, const int ent,
                          ZipEntry* data) {
   const uint16_t nameLen = archive->hash_table[ent].name_length;
@@ -525,9 +507,8 @@
   // This is the base of our mmapped region, we have to sanity check that
   // the name that's in the hash table is a pointer to a location within
   // this mapped region.
-  const uint8_t* base_ptr = reinterpret_cast<const uint8_t*>(
-    archive->directory_map.getDataPtr());
-  if (ptr < base_ptr || ptr > base_ptr + archive->directory_map.getDataLength()) {
+  const uint8_t* base_ptr = archive->central_directory.GetBasePtr();
+  if (ptr < base_ptr || ptr > base_ptr + archive->central_directory.GetMapLength()) {
     ALOGW("Zip: Invalid entry pointer");
     return kInvalidOffset;
   }
@@ -559,7 +540,7 @@
   }
 
   uint8_t lfh_buf[sizeof(LocalFileHeader)];
-  if (!ReadAtOffset(archive->fd, lfh_buf, sizeof(lfh_buf), local_header_offset)) {
+  if (!archive->mapped_zip.ReadAtOffset(lfh_buf, sizeof(lfh_buf), local_header_offset)) {
     ALOGW("Zip: failed reading lfh name from offset %" PRId64,
         static_cast<int64_t>(local_header_offset));
     return kIoError;
@@ -599,19 +580,16 @@
       return kInvalidOffset;
     }
 
-    uint8_t* name_buf = reinterpret_cast<uint8_t*>(malloc(nameLen));
-    if (!ReadAtOffset(archive->fd, name_buf, nameLen, name_offset)) {
+    std::vector<uint8_t> name_buf(nameLen);
+    if (!archive->mapped_zip.ReadAtOffset(name_buf.data(), nameLen, name_offset)) {
       ALOGW("Zip: failed reading lfh name from offset %" PRId64, static_cast<int64_t>(name_offset));
-      free(name_buf);
       return kIoError;
     }
 
-    if (memcmp(archive->hash_table[ent].name, name_buf, nameLen)) {
-      free(name_buf);
+    if (memcmp(archive->hash_table[ent].name, name_buf.data(), nameLen)) {
       return kInconsistentInformation;
     }
 
-    free(name_buf);
   } else {
     ALOGW("Zip: lfh name did not match central directory.");
     return kInconsistentInformation;
@@ -881,7 +859,7 @@
 }
 #pragma GCC diagnostic pop
 
-static int32_t InflateEntryToWriter(int fd, const ZipEntry* entry,
+static int32_t InflateEntryToWriter(MappedZipFile& mapped_zip, const ZipEntry* entry,
                                     Writer* writer, uint64_t* crc_out) {
   const size_t kBufSize = 32768;
   std::vector<uint8_t> read_buf(kBufSize);
@@ -931,7 +909,7 @@
     /* read as much as we can */
     if (zstream.avail_in == 0) {
       const size_t getSize = (compressed_length > kBufSize) ? kBufSize : compressed_length;
-      if (!android::base::ReadFully(fd, read_buf.data(), getSize)) {
+      if (!mapped_zip.ReadData(read_buf.data(), getSize)) {
         ALOGW("Zip: inflate read failed, getSize = %zu: %s", getSize, strerror(errno));
         return kIoError;
       }
@@ -979,7 +957,7 @@
   return 0;
 }
 
-static int32_t CopyEntryToWriter(int fd, const ZipEntry* entry, Writer* writer,
+static int32_t CopyEntryToWriter(MappedZipFile& mapped_zip, const ZipEntry* entry, Writer* writer,
                                  uint64_t *crc_out) {
   static const uint32_t kBufSize = 32768;
   std::vector<uint8_t> buf(kBufSize);
@@ -993,7 +971,7 @@
     // Safe conversion because kBufSize is narrow enough for a 32 bit signed
     // value.
     const size_t block_size = (remaining > kBufSize) ? kBufSize : remaining;
-    if (!android::base::ReadFully(fd, buf.data(), block_size)) {
+    if (!mapped_zip.ReadData(buf.data(), block_size)) {
       ALOGW("CopyFileToFile: copy read failed, block_size = %zu: %s", block_size, strerror(errno));
       return kIoError;
     }
@@ -1016,7 +994,7 @@
   const uint16_t method = entry->method;
   off64_t data_offset = entry->offset;
 
-  if (lseek64(archive->fd, data_offset, SEEK_SET) != data_offset) {
+  if (!archive->mapped_zip.SeekToOffset(data_offset)) {
     ALOGW("Zip: lseek to data at %" PRId64 " failed", static_cast<int64_t>(data_offset));
     return kIoError;
   }
@@ -1025,13 +1003,13 @@
   int32_t return_value = -1;
   uint64_t crc = 0;
   if (method == kCompressStored) {
-    return_value = CopyEntryToWriter(archive->fd, entry, writer, &crc);
+    return_value = CopyEntryToWriter(archive->mapped_zip, entry, writer, &crc);
   } else if (method == kCompressDeflated) {
-    return_value = InflateEntryToWriter(archive->fd, entry, writer, &crc);
+    return_value = InflateEntryToWriter(archive->mapped_zip, entry, writer, &crc);
   }
 
   if (!return_value && entry->has_data_descriptor) {
-    return_value = UpdateEntryFromDataDescriptor(archive->fd, entry);
+    return_value = UpdateEntryFromDataDescriptor(archive->mapped_zip, entry);
     if (return_value) {
       return return_value;
     }
@@ -1072,7 +1050,7 @@
 }
 
 int GetFileDescriptor(const ZipArchiveHandle handle) {
-  return reinterpret_cast<ZipArchive*>(handle)->fd;
+  return reinterpret_cast<ZipArchive*>(handle)->mapped_zip.GetFileDescriptor();
 }
 
 ZipString::ZipString(const char* entry_name)
@@ -1081,3 +1059,143 @@
   CHECK_LE(len, static_cast<size_t>(UINT16_MAX));
   name_length = static_cast<uint16_t>(len);
 }
+
+#if !defined(_WIN32)
+class ProcessWriter : public Writer {
+ public:
+  ProcessWriter(ProcessZipEntryFunction func, void* cookie) : Writer(),
+    proc_function_(func),
+    cookie_(cookie) {
+  }
+
+  virtual bool Append(uint8_t* buf, size_t buf_size) override {
+    return proc_function_(buf, buf_size, cookie_);
+  }
+
+ private:
+  ProcessZipEntryFunction proc_function_;
+  void* cookie_;
+};
+
+int32_t ProcessZipEntryContents(ZipArchiveHandle handle, ZipEntry* entry,
+                                ProcessZipEntryFunction func, void* cookie) {
+  ProcessWriter writer(func, cookie);
+  return ExtractToWriter(handle, entry, &writer);
+}
+
+#endif //!defined(_WIN32)
+
+int MappedZipFile::GetFileDescriptor() const {
+  if (!has_fd_) {
+    ALOGW("Zip: MappedZipFile doesn't have a file descriptor.");
+    return -1;
+  }
+  return fd_;
+}
+
+void* MappedZipFile::GetBasePtr() const {
+  if (has_fd_) {
+    ALOGW("Zip: MappedZipFile doesn't have a base pointer.");
+    return nullptr;
+  }
+  return base_ptr_;
+}
+
+off64_t MappedZipFile::GetFileLength() const {
+  if (has_fd_) {
+    off64_t result = lseek64(fd_, 0, SEEK_END);
+    if (result == -1) {
+      ALOGE("Zip: lseek on fd %d failed: %s", fd_, strerror(errno));
+    }
+    return result;
+  } else {
+    if (base_ptr_ == nullptr) {
+      ALOGE("Zip: invalid file map\n");
+      return -1;
+    }
+    return static_cast<off64_t>(data_length_);
+  }
+}
+
+bool MappedZipFile::SeekToOffset(off64_t offset) {
+  if (has_fd_) {
+    if (lseek64(fd_, offset, SEEK_SET) != offset) {
+      ALOGE("Zip: lseek to %" PRId64 " failed: %s\n", offset, strerror(errno));
+      return false;
+    }
+    return true;
+  } else {
+    if (offset < 0 || offset > static_cast<off64_t>(data_length_)) {
+      ALOGE("Zip: invalid offset: %" PRId64 ", data length: %" PRId64 "\n" , offset,
+            data_length_);
+      return false;
+    }
+
+    read_pos_ = offset;
+    return true;
+  }
+}
+
+bool MappedZipFile::ReadData(uint8_t* buffer, size_t read_amount) {
+  if (has_fd_) {
+    if(!android::base::ReadFully(fd_, buffer, read_amount)) {
+      ALOGE("Zip: read from %d failed\n", fd_);
+      return false;
+    }
+  } else {
+    memcpy(buffer, static_cast<uint8_t*>(base_ptr_) + read_pos_, read_amount);
+    read_pos_ += read_amount;
+  }
+  return true;
+}
+
+// Attempts to read |len| bytes into |buf| at offset |off|.
+bool MappedZipFile::ReadAtOffset(uint8_t* buf, size_t len, off64_t off) {
+#if !defined(_WIN32)
+  if (has_fd_) {
+    if (static_cast<size_t>(TEMP_FAILURE_RETRY(pread64(fd_, buf, len, off))) != len) {
+      ALOGE("Zip: failed to read at offset %" PRId64 "\n", off);
+      return false;
+    }
+    return true;
+  }
+#endif
+  if (!SeekToOffset(off)) {
+    return false;
+  }
+  return ReadData(buf, len);
+
+}
+
+void CentralDirectory::Initialize(void* map_base_ptr, off64_t cd_start_offset, size_t cd_size) {
+  base_ptr_ = static_cast<uint8_t*>(map_base_ptr) + cd_start_offset;
+  length_ = cd_size;
+}
+
+bool ZipArchive::InitializeCentralDirectory(const char* debug_file_name, off64_t cd_start_offset,
+                                            size_t cd_size) {
+  if (mapped_zip.HasFd()) {
+    if (!directory_map->create(debug_file_name, mapped_zip.GetFileDescriptor(),
+                               cd_start_offset, cd_size, true /* read only */)) {
+      return false;
+    }
+
+    CHECK_EQ(directory_map->getDataLength(), cd_size);
+    central_directory.Initialize(directory_map->getDataPtr(), 0/*offset*/, cd_size);
+  } else {
+    if (mapped_zip.GetBasePtr() == nullptr) {
+      ALOGE("Zip: Failed to map central directory, bad mapped_zip base pointer\n");
+      return false;
+    }
+    if (static_cast<off64_t>(cd_start_offset) + static_cast<off64_t>(cd_size) >
+        mapped_zip.GetFileLength()) {
+      ALOGE("Zip: Failed to map central directory, offset exceeds mapped memory region ("
+            "start_offset %"  PRId64 ", cd_size %zu, mapped_region_size %" PRId64 ")",
+            static_cast<int64_t>(cd_start_offset), cd_size, mapped_zip.GetFileLength());
+      return false;
+    }
+
+    central_directory.Initialize(mapped_zip.GetBasePtr(), cd_start_offset, cd_size);
+  }
+  return true;
+}
diff --git a/libziparchive/zip_archive_private.h b/libziparchive/zip_archive_private.h
index ab52368..971db4f 100644
--- a/libziparchive/zip_archive_private.h
+++ b/libziparchive/zip_archive_private.h
@@ -21,17 +21,83 @@
 #include <stdlib.h>
 #include <unistd.h>
 
+#include <memory>
+#include <vector>
+
 #include <utils/FileMap.h>
 #include <ziparchive/zip_archive.h>
 
+class MappedZipFile {
+ public:
+  explicit MappedZipFile(const int fd) :
+    has_fd_(true),
+    fd_(fd),
+    base_ptr_(nullptr),
+    data_length_(0),
+    read_pos_(0) {}
+
+  explicit MappedZipFile(void* address, size_t length) :
+    has_fd_(false),
+    fd_(-1),
+    base_ptr_(address),
+    data_length_(static_cast<off64_t>(length)),
+    read_pos_(0) {}
+
+  bool HasFd() const {return has_fd_;}
+
+  int GetFileDescriptor() const;
+
+  void* GetBasePtr() const;
+
+  off64_t GetFileLength() const;
+
+  bool SeekToOffset(off64_t offset);
+
+  bool ReadData(uint8_t* buffer, size_t read_amount);
+
+  bool ReadAtOffset(uint8_t* buf, size_t len, off64_t off);
+
+ private:
+  // If has_fd_ is true, fd is valid and we'll read contents of a zip archive
+  // from the file. Otherwise, we're opening the archive from a memory mapped
+  // file. In that case, base_ptr_ points to the start of the memory region and
+  // data_length_ defines the file length.
+  const bool has_fd_;
+
+  const int fd_;
+
+  void* const base_ptr_;
+  const off64_t data_length_;
+  // read_pos_ is the offset to the base_ptr_ where we read data from.
+  size_t read_pos_;
+};
+
+class CentralDirectory {
+ public:
+  CentralDirectory(void) :
+    base_ptr_(nullptr),
+    length_(0) {}
+
+  const uint8_t* GetBasePtr() const {return base_ptr_;}
+
+  size_t GetMapLength() const {return length_;}
+
+  void Initialize(void* map_base_ptr, off64_t cd_start_offset, size_t cd_size);
+
+ private:
+  const uint8_t* base_ptr_;
+  size_t length_;
+};
+
 struct ZipArchive {
   // open Zip archive
-  const int fd;
+  mutable MappedZipFile mapped_zip;
   const bool close_file;
 
   // mapped central directory area
   off64_t directory_offset;
-  android::FileMap directory_map;
+  CentralDirectory central_directory;
+  std::unique_ptr<android::FileMap> directory_map;
 
   // number of entries in the Zip archive
   uint16_t num_entries;
@@ -44,20 +110,36 @@
   ZipString* hash_table;
 
   ZipArchive(const int fd, bool assume_ownership) :
-      fd(fd),
-      close_file(assume_ownership),
-      directory_offset(0),
-      num_entries(0),
-      hash_table_size(0),
-      hash_table(NULL) {}
+    mapped_zip(fd),
+    close_file(assume_ownership),
+    directory_offset(0),
+    central_directory(),
+    directory_map(new android::FileMap()),
+    num_entries(0),
+    hash_table_size(0),
+    hash_table(nullptr) {}
+
+  ZipArchive(void* address, size_t length) :
+    mapped_zip(address, length),
+    close_file(false),
+    directory_offset(0),
+    central_directory(),
+    directory_map(new android::FileMap()),
+    num_entries(0),
+    hash_table_size(0),
+    hash_table(nullptr) {}
 
   ~ZipArchive() {
-    if (close_file && fd >= 0) {
-      close(fd);
+    if (close_file && mapped_zip.GetFileDescriptor() >= 0) {
+      close(mapped_zip.GetFileDescriptor());
     }
 
     free(hash_table);
   }
+
+  bool InitializeCentralDirectory(const char* debug_file_name, off64_t cd_start_offset,
+                                  size_t cd_size);
+
 };
 
 #endif  // LIBZIPARCHIVE_ZIPARCHIVE_PRIVATE_H_
diff --git a/libziparchive/zip_archive_stream_entry.cc b/libziparchive/zip_archive_stream_entry.cc
index 41988bc..64b24c3 100644
--- a/libziparchive/zip_archive_stream_entry.cc
+++ b/libziparchive/zip_archive_stream_entry.cc
@@ -39,7 +39,7 @@
 bool ZipArchiveStreamEntry::Init(const ZipEntry& entry) {
   ZipArchive* archive = reinterpret_cast<ZipArchive*>(handle_);
   off64_t data_offset = entry.offset;
-  if (lseek64(archive->fd, data_offset, SEEK_SET) != data_offset) {
+  if (!archive->mapped_zip.SeekToOffset(data_offset)) {
     ALOGW("lseek to data at %" PRId64 " failed: %s", data_offset, strerror(errno));
     return false;
   }
@@ -88,7 +88,7 @@
   size_t bytes = (length_ > data_.size()) ? data_.size() : length_;
   ZipArchive* archive = reinterpret_cast<ZipArchive*>(handle_);
   errno = 0;
-  if (!android::base::ReadFully(archive->fd, data_.data(), bytes)) {
+  if (!archive->mapped_zip.ReadData(data_.data(), bytes)) {
     if (errno != 0) {
       ALOGE("Error reading from archive fd: %s", strerror(errno));
     } else {
@@ -209,7 +209,7 @@
       size_t bytes = (compressed_length_ > in_.size()) ? in_.size() : compressed_length_;
       ZipArchive* archive = reinterpret_cast<ZipArchive*>(handle_);
       errno = 0;
-      if (!android::base::ReadFully(archive->fd, in_.data(), bytes)) {
+      if (!archive->mapped_zip.ReadData(in_.data(), bytes)) {
         if (errno != 0) {
           ALOGE("Error reading from archive fd: %s", strerror(errno));
         } else {
diff --git a/libziparchive/zip_archive_test.cc b/libziparchive/zip_archive_test.cc
index 6aee1bb..9dd6cc0 100644
--- a/libziparchive/zip_archive_test.cc
+++ b/libziparchive/zip_archive_test.cc
@@ -26,7 +26,9 @@
 
 #include <android-base/file.h>
 #include <android-base/test_utils.h>
+#include <android-base/unique_fd.h>
 #include <gtest/gtest.h>
+#include <utils/FileMap.h>
 #include <ziparchive/zip_archive.h>
 #include <ziparchive/zip_archive_stream_entry.h>
 
@@ -36,6 +38,7 @@
 static const std::string kValidZip = "valid.zip";
 static const std::string kLargeZip = "large.zip";
 static const std::string kBadCrcZip = "bad_crc.zip";
+static const std::string kUpdateZip = "dummy-update.zip";
 
 static const std::vector<uint8_t> kATxtContents {
   'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
@@ -494,6 +497,32 @@
             lseek64(tmp_file.fd, 0, SEEK_END));
 }
 
+#if !defined(_WIN32)
+TEST(ziparchive, OpenFromMemory) {
+  const std::string zip_path = test_data_dir + "/" + kUpdateZip;
+  android::base::unique_fd fd(open(zip_path.c_str(), O_RDONLY | O_BINARY));
+  ASSERT_NE(-1, fd);
+  struct stat sb;
+  ASSERT_EQ(0, fstat(fd, &sb));
+
+  // Memory map the file first and open the archive from the memory region.
+  android::FileMap file_map;
+  file_map.create(zip_path.c_str(), fd, 0/*offset*/, sb.st_size, true);
+  ZipArchiveHandle handle;
+  ASSERT_EQ(0, OpenArchiveFromMemory(file_map.getDataPtr(), file_map.getDataLength(),
+                                     zip_path.c_str(), &handle));
+
+  // Assert one entry can be found and extracted correctly.
+  std::string BINARY_PATH("META-INF/com/google/android/update-binary");
+  ZipString binary_path(BINARY_PATH.c_str());
+  ZipEntry binary_entry;
+  ASSERT_EQ(0, FindEntry(handle, binary_path, &binary_entry));
+  TemporaryFile tmp_binary;
+  ASSERT_NE(-1, tmp_binary.fd);
+  ASSERT_EQ(0, ExtractEntryToFile(handle, &binary_entry, tmp_binary.fd));
+}
+#endif
+
 static void ZipArchiveStreamTest(
     ZipArchiveHandle& handle, const std::string& entry_name, bool raw,
     bool verified, ZipEntry* entry, std::vector<uint8_t>* read_data) {