Merge "Make apexd consider decompressed APEX as factory packages" am: 7e4bd60724
Original change: https://android-review.googlesource.com/c/platform/system/apex/+/1581783
MUST ONLY BE SUBMITTED BY AUTOMERGER
Change-Id: I6a5dfec7266df80361398449a76a7d8d7ece3a64
diff --git a/apexd/apex_preinstalled_data.cpp b/apexd/apex_preinstalled_data.cpp
index ef42f35..401437e 100644
--- a/apexd/apex_preinstalled_data.cpp
+++ b/apexd/apex_preinstalled_data.cpp
@@ -23,6 +23,7 @@
#include <android-base/file.h>
#include <android-base/properties.h>
#include <android-base/result.h>
+#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include "apex_constants.h"
@@ -32,6 +33,7 @@
using android::base::Error;
using android::base::GetProperty;
using android::base::Result;
+using android::base::StringPrintf;
namespace android {
namespace apex {
@@ -121,12 +123,25 @@
return data_.find(name) != data_.end();
}
+// ApexFile is considered a decompressed APEX if it is a hard link of file in
+// |decompression_dir_| with same filename
+bool ApexPreinstalledData::IsDecompressedApex(const ApexFile& apex) const {
+ namespace fs = std::filesystem;
+ const std::string filename = fs::path(apex.GetPath()).filename();
+ const std::string decompressed_path =
+ StringPrintf("%s/%s", decompression_dir_.c_str(), filename.c_str());
+
+ std::error_code ec;
+ bool hard_link_exists = fs::equivalent(decompressed_path, apex.GetPath(), ec);
+ return !ec && hard_link_exists;
+}
+
bool ApexPreinstalledData::IsPreInstalledApex(const ApexFile& apex) const {
auto it = data_.find(apex.GetManifest().name());
if (it == data_.end()) {
return false;
}
- return it->second.path == apex.GetPath();
+ return it->second.path == apex.GetPath() || IsDecompressedApex(apex);
}
} // namespace apex
diff --git a/apexd/apex_preinstalled_data.h b/apexd/apex_preinstalled_data.h
index 44ac32b..4edf0f7 100644
--- a/apexd/apex_preinstalled_data.h
+++ b/apexd/apex_preinstalled_data.h
@@ -19,6 +19,7 @@
#include <string>
#include <unordered_map>
#include <vector>
+#include "apex_constants.h"
#include "apex_file.h"
#include <android-base/result.h>
@@ -35,7 +36,9 @@
class ApexPreinstalledData final {
public:
// c-tor and d-tor are exposed for testing.
- ApexPreinstalledData(){};
+ ApexPreinstalledData(
+ const std::string& decompression_dir = kApexDecompressedDir)
+ : decompression_dir_(decompression_dir){};
~ApexPreinstalledData() { data_.clear(); };
@@ -64,6 +67,9 @@
// Checks if given |apex| is pre-installed.
bool IsPreInstalledApex(const ApexFile& apex) const;
+ // Checks if given |apex| is decompressed from a pre-installed APEX
+ bool IsDecompressedApex(const ApexFile& apex) const;
+
private:
// Non-copyable && non-moveable.
ApexPreinstalledData(const ApexPreinstalledData&) = delete;
@@ -83,6 +89,9 @@
};
std::unordered_map<std::string, ApexData> data_;
+ // Decompression directory which will be used to determine if apex is
+ // decompressed or not
+ const std::string decompression_dir_;
};
} // namespace apex
diff --git a/apexd/apex_preinstalled_data_test.cpp b/apexd/apex_preinstalled_data_test.cpp
index 65d72be..93faf2b 100644
--- a/apexd/apex_preinstalled_data_test.cpp
+++ b/apexd/apex_preinstalled_data_test.cpp
@@ -40,6 +40,7 @@
using android::apex::testing::IsOk;
using android::base::GetExecutableDirectory;
+using android::base::RemoveFileIfExists;
using android::base::StringPrintf;
static std::string GetTestDataDir() { return GetExecutableDirectory(); }
@@ -240,5 +241,45 @@
ASSERT_FALSE(instance.IsPreInstalledApex(*apex3));
}
+TEST(ApexPreinstalledDataTest, IsDecompressedApex) {
+ // Prepare instance
+ TemporaryDir decompression_dir;
+ ApexPreinstalledData instance(decompression_dir.path);
+
+ // Prepare decompressed apex
+ std::string filename = "com.android.apex.compressed.v1_original.apex";
+ fs::copy(GetTestFile(filename), decompression_dir.path);
+ auto decompressed_path =
+ StringPrintf("%s/%s", decompression_dir.path, filename.c_str());
+ auto decompressed_apex = ApexFile::Open(decompressed_path);
+
+ // Any file which is already located in |decompression_dir| should be
+ // considered decompressed
+ ASSERT_TRUE(instance.IsDecompressedApex(*decompressed_apex));
+
+ // Hard links with same file name is considered decompressed
+ TemporaryDir active_dir;
+ auto active_path = StringPrintf("%s/%s", active_dir.path, filename.c_str());
+ std::error_code ec;
+ fs::create_hard_link(decompressed_path, active_path, ec);
+ ASSERT_FALSE(ec) << "Failed to create hardlink";
+ auto active_apex = ApexFile::Open(active_path);
+ ASSERT_TRUE(instance.IsDecompressedApex(*active_apex));
+
+ // Hard links with different filename is not considered decompressed
+ auto different_name_path =
+ StringPrintf("%s/different.name.apex", active_dir.path);
+ fs::create_hard_link(decompressed_path, different_name_path, ec);
+ ASSERT_FALSE(ec) << "Failed to create hardlink";
+ auto different_name_apex = ApexFile::Open(different_name_path);
+ ASSERT_FALSE(instance.IsDecompressedApex(*different_name_apex));
+
+ // Same file name but not hard link -> not considered decompressed
+ RemoveFileIfExists(active_path);
+ fs::copy(GetTestFile(filename), active_dir.path);
+ active_apex = ApexFile::Open(active_path);
+ ASSERT_FALSE(instance.IsDecompressedApex(*active_apex));
+}
+
} // namespace apex
} // namespace android
diff --git a/apexd/apexd.cpp b/apexd/apexd.cpp
index 4489382..4d608d8 100644
--- a/apexd/apexd.cpp
+++ b/apexd/apexd.cpp
@@ -1387,6 +1387,17 @@
std::vector<ApexFile> GetFactoryPackages() {
std::vector<ApexFile> ret;
+
+ // Decompressed APEX is considered factory package
+ std::vector<std::string> decompressed_pkg_names;
+ auto active_pkgs = GetActivePackages();
+ for (ApexFile& apex : active_pkgs) {
+ if (ApexPreinstalledData::GetInstance().IsDecompressedApex(apex)) {
+ decompressed_pkg_names.push_back(apex.GetManifest().name());
+ ret.emplace_back(std::move(apex));
+ }
+ }
+
for (const auto& dir : kApexPackageBuiltinDirs) {
auto all_apex_files = FindFilesBySuffix(
dir, {kApexPackageSuffix, kCompressedApexPackageSuffix});
@@ -1399,9 +1410,18 @@
Result<ApexFile> apex_file = ApexFile::Open(path);
if (!apex_file.ok()) {
LOG(ERROR) << apex_file.error();
- } else {
- ret.emplace_back(std::move(*apex_file));
+ continue;
}
+ // Ignore compressed APEX if it has been decompressed already
+ if (apex_file->IsCompressed() &&
+ std::find(decompressed_pkg_names.begin(),
+ decompressed_pkg_names.end(),
+ apex_file->GetManifest().name()) !=
+ decompressed_pkg_names.end()) {
+ continue;
+ }
+
+ ret.emplace_back(std::move(*apex_file));
}
}
return ret;
@@ -2343,6 +2363,8 @@
}
if (apex_files.size() == 1) {
+ LOG(DEBUG) << "Selecting the only APEX: " << package_name << " "
+ << apex_files[0].GetPath();
activation_list.emplace_back(std::move(apex_files[0]));
continue;
}
@@ -2360,8 +2382,13 @@
const bool same_version_priority_to_data =
a.GetManifest().version() == version_b &&
!instance.IsPreInstalledApex(a);
+ // If A has same version as B and they are both pre-installed,
+ // then it means one of them is compressed. Choose decompressed copy.
+ const bool decompressed = instance.IsDecompressedApex(a);
if (provides_shared_apex_libs || higher_version ||
- same_version_priority_to_data) {
+ same_version_priority_to_data || decompressed) {
+ LOG(DEBUG) << "Selecting between two APEX: " << a.GetManifest().name()
+ << " " << a.GetPath();
activation_list.emplace_back(std::move(a));
}
};
@@ -2381,6 +2408,7 @@
std::vector<ApexFile>&& compressed_apex,
const std::string& decompression_dir = kApexDecompressedDir,
const std::string& active_apex_dir = kActiveApexPackagesDataDir) {
+ LOG(INFO) << "Processing compressed APEX";
std::vector<ApexFile> decompressed_apex_list;
for (const ApexFile& apex_file : compressed_apex) {
if (!apex_file.IsCompressed()) {
@@ -2691,7 +2719,7 @@
}
for (const auto& path : *data_apexes) {
if (!apexd_private::IsMounted(path)) {
- LOG(DEBUG) << "Removing inactive data APEX " << path;
+ LOG(INFO) << "Removing inactive data APEX " << path;
if (unlink(path.c_str()) != 0) {
PLOG(ERROR) << "Failed to unlink inactive data APEX " << path;
}
diff --git a/tests/app/src/com/android/tests/apex/app/ApexCompressionTests.java b/tests/app/src/com/android/tests/apex/app/ApexCompressionTests.java
index 62b8c4d..aea8ca4 100644
--- a/tests/app/src/com/android/tests/apex/app/ApexCompressionTests.java
+++ b/tests/app/src/com/android/tests/apex/app/ApexCompressionTests.java
@@ -20,6 +20,7 @@
import android.Manifest;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -66,7 +67,7 @@
}
@Test
- public void testCompressedApexCanBeQueried() throws Exception {
+ public void testDecompressedApexIsConsideredFactory() throws Exception {
// Only retrieve active apex package
PackageInfo pi = mPm.getPackageInfo(
COMPRESSED_APEX_PACKAGE_NAME, PackageManager.MATCH_APEX);
@@ -74,6 +75,8 @@
assertThat(pi.isApex).isTrue();
assertThat(pi.packageName).isEqualTo(COMPRESSED_APEX_PACKAGE_NAME);
assertThat(pi.getLongVersionCode()).isEqualTo(1);
+ boolean isFactoryPackage = (pi.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+ assertThat(isFactoryPackage).isTrue();
}
@Test
diff --git a/tests/src/com/android/tests/apex/host/ApexCompressionTests.java b/tests/src/com/android/tests/apex/host/ApexCompressionTests.java
index 72e26b6..e36c3e6 100644
--- a/tests/src/com/android/tests/apex/host/ApexCompressionTests.java
+++ b/tests/src/com/android/tests/apex/host/ApexCompressionTests.java
@@ -126,9 +126,9 @@
@Test
@LargeTest
- public void testCompressedApexCanBeQueried() throws Exception {
+ public void testDecompressedApexIsConsideredFactory() throws Exception {
pushTestApex(COMPRESSED_APEX_PACKAGE_NAME + ".v1.capex");
- runPhase("testCompressedApexCanBeQueried");
+ runPhase("testDecompressedApexIsConsideredFactory");
}
@Test
@@ -161,6 +161,22 @@
@Test
@LargeTest
+ public void testDecompressedApexSurvivesReboot() throws Exception {
+ pushTestApex(COMPRESSED_APEX_PACKAGE_NAME + ".v1.capex");
+
+ // Ensure that compressed APEX was activated in APEX_ACTIVE_DIR
+ List<String> files = getFilesInDir(APEX_ACTIVE_DIR);
+ assertThat(files).contains(COMPRESSED_APEX_PACKAGE_NAME + "@1.apex");
+
+ getDevice().reboot();
+
+ // Ensure it gets activated again on reboot
+ files = getFilesInDir(APEX_ACTIVE_DIR);
+ assertThat(files).contains(COMPRESSED_APEX_PACKAGE_NAME + "@1.apex");
+ }
+
+ @Test
+ @LargeTest
public void testDecompressionDoesNotHappenOnEveryReboot() throws Exception {
pushTestApex(COMPRESSED_APEX_PACKAGE_NAME + ".v1.capex");