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/Android.bp b/apexd/Android.bp
index b8542be..66b546f 100644
--- a/apexd/Android.bp
+++ b/apexd/Android.bp
@@ -386,6 +386,7 @@
":com.android.apex.cts.shim.v2_with_pre_install_hook_prebuilt",
":com.android.apex.cts.shim.v2_with_post_install_hook_prebuilt",
":com.android.apex.compressed.v1",
+ ":com.android.apex.compressed.v1_original",
"apexd_testdata/com.android.apex.test_package.avbpubkey",
"apexd_testdata/com.android.apex.compressed.avbpubkey",
],
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
diff --git a/apexd/apex_file.h b/apexd/apex_file.h
index fcc51c6..2b3494e 100644
--- a/apexd/apex_file.h
+++ b/apexd/apex_file.h
@@ -54,6 +54,7 @@
android::base::Result<ApexVerityData> VerifyApexVerity(
const std::string& public_key) const;
bool IsCompressed() const { return is_compressed_; }
+ android::base::Result<void> Decompress(const std::string& output_path) const;
private:
ApexFile(const std::string& apex_path,
diff --git a/apexd/apex_file_test.cpp b/apexd/apex_file_test.cpp
index cbf8051..98f5a1b 100644
--- a/apexd/apex_file_test.cpp
+++ b/apexd/apex_file_test.cpp
@@ -26,6 +26,8 @@
#include <ziparchive/zip_archive.h>
#include "apex_file.h"
+#include "apexd_test_utils.h"
+#include "apexd_utils.h"
using android::base::GetExecutableDirectory;
using android::base::Result;
@@ -46,7 +48,7 @@
class ApexFileTest : public ::testing::TestWithParam<ApexFileTestParam> {};
-INSTANTIATE_TEST_SUITE_P(Apex, ApexFileTest, testing::ValuesIn(kParameters));
+INSTANTIATE_TEST_SUITE_P(Apex, ApexFileTest, ::testing::ValuesIn(kParameters));
TEST_P(ApexFileTest, GetOffsetOfSimplePackage) {
const std::string file_path = kTestDataDir + GetParam().prefix + ".apex";
@@ -81,7 +83,7 @@
Result<ApexFile> apex_file = ApexFile::Open(file_path);
ASSERT_FALSE(apex_file.ok());
ASSERT_THAT(apex_file.error().message(),
- testing::HasSubstr("Failed to open package"));
+ ::testing::HasSubstr("Failed to open package"));
}
TEST_P(ApexFileTest, GetApexManifest) {
@@ -161,7 +163,7 @@
Result<ApexFile> apex_file = ApexFile::Open(file_path);
ASSERT_FALSE(apex_file.ok());
ASSERT_THAT(apex_file.error().message(),
- testing::HasSubstr("Failed to retrieve filesystem type"));
+ ::testing::HasSubstr("Failed to retrieve filesystem type"));
}
TEST(ApexFileTest, OpenCompressedApexFile) {
@@ -182,7 +184,7 @@
Result<ApexFile> apex_file = ApexFile::Open(file_path);
ASSERT_FALSE(apex_file.ok());
ASSERT_THAT(apex_file.error().message(),
- testing::HasSubstr("Could not find entry"));
+ ::testing::HasSubstr("Could not find entry"));
}
TEST(ApexFileTest, GetCompressedApexManifest) {
@@ -221,6 +223,50 @@
::testing::HasSubstr("Cannot verify ApexVerity of compressed APEX"));
}
+TEST(ApexFileTest, DecompressCompressedApex) {
+ const std::string file_path =
+ kTestDataDir + "com.android.apex.compressed.v1.capex";
+ Result<ApexFile> apex_file = ApexFile::Open(file_path);
+ ASSERT_RESULT_OK(apex_file);
+
+ // Create a temp dir for decompression
+ TemporaryDir tmp_dir;
+
+ const std::string package_name = apex_file->GetManifest().name();
+ const std::string decompression_file_path =
+ tmp_dir.path + package_name + ".capex";
+
+ auto result = apex_file->Decompress(decompression_file_path);
+ ASSERT_RESULT_OK(result);
+
+ // Assert output path is not empty
+ auto exists = PathExists(decompression_file_path);
+ ASSERT_RESULT_OK(exists);
+ ASSERT_TRUE(*exists) << decompression_file_path << " does not exist";
+
+ // Assert that decompressed apex is same as original apex
+ const std::string original_apex_file_path =
+ kTestDataDir + "com.android.apex.compressed.v1_original.apex";
+ auto comparison_result =
+ CompareFiles(original_apex_file_path, decompression_file_path);
+ ASSERT_RESULT_OK(comparison_result);
+ ASSERT_TRUE(*comparison_result);
+}
+
+TEST(ApexFileTest, DecompressFailForNormalApex) {
+ const std::string file_path =
+ kTestDataDir + "com.android.apex.compressed.v1_original.apex";
+ Result<ApexFile> apex_file = ApexFile::Open(file_path);
+ ASSERT_RESULT_OK(apex_file);
+
+ TemporaryFile decompression_file_path;
+
+ auto result = apex_file->Decompress(decompression_file_path.path);
+ ASSERT_FALSE(result.ok());
+ ASSERT_THAT(result.error().message(),
+ ::testing::HasSubstr("Cannot decompress an uncompressed APEX"));
+}
+
} // namespace
} // namespace apex
} // namespace android
diff --git a/apexd/apexd_test_utils.h b/apexd/apexd_test_utils.h
index 0e43477..6ee1927 100644
--- a/apexd/apexd_test_utils.h
+++ b/apexd/apexd_test_utils.h
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+#include <fstream>
+
#include <android/apex/ApexInfo.h>
#include <android/apex/ApexSessionInfo.h>
#include <binder/IServiceManager.h>
@@ -22,6 +24,8 @@
#include "session_state.pb.h"
+using android::base::Error;
+using android::base::Result;
using apex::proto::SessionState;
namespace android {
@@ -128,5 +132,20 @@
*os << "}";
}
+inline Result<bool> CompareFiles(const std::string& filename1,
+ const std::string& filename2) {
+ std::ifstream file1(filename1, std::ios::binary);
+ std::ifstream file2(filename2, std::ios::binary);
+
+ if (file1.bad() || file2.bad()) {
+ return Error() << "Could not open one of the file";
+ }
+
+ std::istreambuf_iterator<char> begin1(file1);
+ std::istreambuf_iterator<char> begin2(file2);
+
+ return std::equal(begin1, std::istreambuf_iterator<char>(), begin2);
+}
+
} // namespace apex
} // namespace android