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");