Add EROFS support for APEX

Bug: 195274797
Test: ./apxer/runtests.sh
Test: atest ApexTestCases
Change-Id: I19019d2809496bfc37eca1964e58a4e04d8bbbe7
Signed-off-by: Huang Jianan <[email protected]>
diff --git a/apexd/Android.bp b/apexd/Android.bp
index 1bf6dda..e6c3047 100644
--- a/apexd/Android.bp
+++ b/apexd/Android.bp
@@ -336,6 +336,17 @@
 }
 
 genrule {
+  // Extract the root digest with avbtool
+  name: "apex.apexd_test_erofs_digest",
+  out: ["apex.apexd_test_erofs_digest.txt"],
+  srcs: [":apex.apexd_test_erofs"],
+  tools: ["avbtool"],
+  cmd: "unzip -q $(in) -d $(genDir) apex_payload.img && " +
+       "$(location avbtool) print_partition_digests --image $(genDir)/apex_payload.img " +
+       "| cut -c 3-| tee $(out)"
+}
+
+genrule {
   // Generates an apex which has same module name as apex.apexd_test.apex, but
   // is actually signed with a different key.
   name: "gen_key_mismatch_apex",
@@ -414,8 +425,10 @@
   ],
   data: [
     ":apex.apexd_test",
+    ":apex.apexd_test_erofs",
     ":apex.apexd_test_f2fs",
     ":apex.apexd_test_digest",
+    ":apex.apexd_test_erofs_digest",
     ":apex.apexd_test_f2fs_digest",
     ":apex.apexd_test_different_app",
     ":apex.apexd_test_no_hashtree",
diff --git a/apexd/apex_file.cpp b/apexd/apex_file.cpp
index 0ffd106..9ed48aa 100644
--- a/apexd/apex_file.cpp
+++ b/apexd/apex_file.cpp
@@ -60,7 +60,8 @@
   const char* magic;
 };
 constexpr const FsMagic kFsType[] = {{"f2fs", 1024, 4, "\x10\x20\xf5\xf2"},
-                                     {"ext4", 1024 + 0x38, 2, "\123\357"}};
+                                     {"ext4", 1024 + 0x38, 2, "\123\357"},
+                                     {"erofs", 1024, 4, "\xe2\xe1\xf5\xe0"}};
 
 Result<std::string> RetrieveFsType(borrowed_fd fd, uint32_t image_offset) {
   for (const auto& fs : kFsType) {
diff --git a/apexd/apex_file_test.cpp b/apexd/apex_file_test.cpp
index d42af09..d6d9cf2 100644
--- a/apexd/apex_file_test.cpp
+++ b/apexd/apex_file_test.cpp
@@ -45,7 +45,9 @@
 };
 
 constexpr const ApexFileTestParam kParameters[] = {
-    {"ext4", "apex.apexd_test"}, {"f2fs", "apex.apexd_test_f2fs"}};
+    {"ext4", "apex.apexd_test"},
+    {"f2fs", "apex.apexd_test_f2fs"},
+    {"erofs", "apex.apexd_test_erofs"}};
 
 class ApexFileTest : public ::testing::TestWithParam<ApexFileTestParam> {};
 
diff --git a/apexd/apexd_testdata/Android.bp b/apexd/apexd_testdata/Android.bp
index 1d92d49..5e1250a 100644
--- a/apexd/apexd_testdata/Android.bp
+++ b/apexd/apexd_testdata/Android.bp
@@ -204,6 +204,17 @@
 }
 
 apex {
+    name: "apex.apexd_test_erofs",
+    manifest: "manifest.json",
+    file_contexts: ":apex.test-file_contexts",
+    prebuilts: ["sample_prebuilt_file"],
+    key: "com.android.apex.test_package.key",
+    installable: false,
+    min_sdk_version: "current",
+    payload_fs_type: "erofs",
+}
+
+apex {
     name: "apex.apexd_test_no_hashtree",
     manifest: "manifest.json",
     file_contexts: ":apex.test-file_contexts",
@@ -297,6 +308,17 @@
     updatable: false,
 }
 
+apex {
+    name: "apex.apexd_test_erofs_no_inst_key",
+    manifest: "manifest_no_inst_key.json",
+    file_contexts: ":apex.test-file_contexts",
+    prebuilts: ["sample_prebuilt_file"],
+    key: "com.android.apex.test_package.no_inst_key.key",
+    installable: false,
+    payload_fs_type: "erofs",
+    updatable: false,
+}
+
 apex_key {
     name: "com.android.apex.test_package_2.key",
     public_key: "com.android.apex.test_package_2.avbpubkey",
diff --git a/apexer/Android.bp b/apexer/Android.bp
index 29be7d1..9de4d0b 100644
--- a/apexer/Android.bp
+++ b/apexer/Android.bp
@@ -27,6 +27,7 @@
       "zipalign",
       "make_f2fs",
       "sload_f2fs",
+      "make_erofs",
       // TODO(b/124476339) apex doesn't follow 'required' dependencies so we need to include this
       // manually for 'avbtool'.
       "fec",
diff --git a/apexer/apexer.py b/apexer/apexer.py
index 2dc9d8f..bb1c4b2 100644
--- a/apexer/apexer.py
+++ b/apexer/apexer.py
@@ -33,6 +33,7 @@
 import uuid
 import xml.etree.ElementTree as ET
 import zipfile
+import glob
 from apex_manifest import ValidateApexManifest
 from apex_manifest import ApexManifestError
 from manifest import android_ns
@@ -107,8 +108,8 @@
       metavar='FS_TYPE',
       required=False,
       default='ext4',
-      choices=['ext4', 'f2fs'],
-      help='type of filesystem being used for payload image "ext4" or "f2fs"')
+      choices=['ext4', 'f2fs', 'erofs'],
+      help='type of filesystem being used for payload image "ext4", "f2fs" or "erofs"')
   parser.add_argument(
       '--override_apk_package_name',
       required=False,
@@ -593,6 +594,36 @@
 
       # TODO(b/158453869): resize the image file to save space
 
+    elif args.payload_fs_type == 'erofs':
+      # mkfs.erofs doesn't support multiple input
+      tmp_input_dir = os.path.join(work_dir, 'tmp_input_dir')
+      os.mkdir(tmp_input_dir)
+      cmd = ['/bin/cp', '-ra']
+      cmd.extend(glob.glob(manifests_dir + '/*'))
+      cmd.extend(glob.glob(args.input_dir + '/*'))
+      cmd.append(tmp_input_dir)
+      RunCommand(cmd, args.verbose)
+
+      cmd = ['make_erofs']
+      cmd.extend(['-z', 'lz4hc'])
+      cmd.extend(['--fs-config-file', args.canned_fs_config])
+      cmd.extend(['--file-contexts', args.file_contexts])
+      uu = str(uuid.uuid5(uuid.NAMESPACE_URL, 'www.android.com'))
+      cmd.extend(['-U', uu])
+      cmd.extend(['-T', '0'])
+      cmd.extend([img_file, tmp_input_dir])
+      RunCommand(cmd, args.verbose)
+      shutil.rmtree(tmp_input_dir)
+
+      # The minimum image size of erofs is 4k, which will cause an error
+      # when execute generate_hash_tree in avbtool
+      cmd = ["/bin/ls", "-lgG", img_file]
+      output, _ = RunCommand(cmd, verbose=False)
+      image_size = int(output.split()[2])
+      if image_size == 4096:
+        cmd = ["/usr/bin/fallocate", "-l", "8k", img_file]
+        RunCommand(cmd, verbose=False)
+
     if args.unsigned_payload_only:
       shutil.copyfile(img_file, args.output)
       if (args.verbose):
diff --git a/apexer/runtests.sh b/apexer/runtests.sh
index e2eacc9..c9d3f26 100755
--- a/apexer/runtests.sh
+++ b/apexer/runtests.sh
@@ -28,7 +28,7 @@
 export APEXER_TOOL_PATH="${ANDROID_BUILD_TOP}/out/soong/host/linux-x86/bin:${ANDROID_BUILD_TOP}/prebuilts/sdk/tools/linux/bin"
 PATH+=":${ANDROID_BUILD_TOP}/prebuilts/sdk/tools/linux/bin"
 
-for fs_type in ext4 f2fs
+for fs_type in ext4 f2fs erofs
 do
 input_dir=$(mktemp -d)
 output_dir=$(mktemp -d)