Add decompression capability to apexd for compressed APEX

Apexd can now decompress a compressed APEX. We still haven't integrated
this new ability with the boot flow yet. It will be done in next CL.

Bug: 172911820
Test: atest ApexFileTest#Decompress
Test: atest ApexFileTest#DecompressWithoutProperSuffix

Change-Id: If8e6f7de86cbefecdb1e1ed955b39fc3f58521e2
diff --git a/apexd/apex_file.cpp b/apexd/apex_file.cpp
index d40e367..6169ad5 100644
--- a/apexd/apex_file.cpp
+++ b/apexd/apex_file.cpp
@@ -386,5 +386,55 @@
   return verity_data;
 }
 
+Result<void> ApexFile::Decompress(const std::string& dest_path) const {
+  const std::string& src_path = GetPath();
+
+  // We should decompress compressed APEX files only
+  if (!IsCompressed()) {
+    return ErrnoError() << "Cannot decompress an uncompressed APEX";
+  }
+
+  // Get file descriptor of the compressed apex file
+  unique_fd src_fd(open(src_path.c_str(), O_RDONLY | O_CLOEXEC));
+  if (src_fd.get() == -1) {
+    return ErrnoError() << "Failed to open compressed APEX " << GetPath();
+  }
+
+  // Open it as a zip file
+  ZipArchiveHandle handle;
+  int ret = OpenArchiveFd(src_fd.get(), src_path.c_str(), &handle, false);
+  if (ret < 0) {
+    return Error() << "Failed to open package " << src_path << ": "
+                   << ErrorCodeString(ret);
+  }
+  auto handle_guard =
+      android::base::make_scope_guard([&handle] { CloseArchive(handle); });
+
+  // Find the original apex file inside the zip and extract to dest
+  ZipEntry entry;
+  ret = FindEntry(handle, kCompressedApexFilename, &entry);
+  if (ret < 0) {
+    return Error() << "Could not find entry \"" << kCompressedApexFilename
+                   << "\" in package " << src_path << ": "
+                   << ErrorCodeString(ret);
+  }
+
+  // Open destination file descriptor
+  unique_fd dest_fd(
+      open(dest_path.c_str(), O_WRONLY | O_CLOEXEC | O_CREAT, 0644));
+  if (dest_fd.get() == -1) {
+    return ErrnoError() << "Failed to open decompression destination "
+                        << GetPath();
+  }
+  ret = ExtractEntryToFile(handle, &entry, dest_fd.get());
+  if (ret < 0) {
+    return Error() << "Could not decompress to file " << dest_path
+                   << ErrorCodeString(ret);
+  }
+
+  LOG(VERBOSE) << "Decompressed " << src_path << " to " << dest_path;
+  return {};
+}
+
 }  // namespace apex
 }  // namespace android