Add F2FS support for apexer
The new flag --payload_fs_type enables the user to choose between ext4
and f2fs for apex_payload.img
Test: ./runtests.sh
Bug: 158453869
Change-Id: I2858d834924c5cec0f809c4f5c8b2e622b8fdf47
(cherry picked from commit 856d59d5dace3179b836e3b4bfb1f4c9bf4d653e)
diff --git a/apexer/Android.bp b/apexer/Android.bp
index 603ea46..b41a632 100644
--- a/apexer/Android.bp
+++ b/apexer/Android.bp
@@ -21,6 +21,8 @@
"resize2fs",
"sefcontext_compile",
"zipalign",
+ "make_f2fs",
+ "sload_f2fs",
// 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 8bd01bf..3f34fe9 100644
--- a/apexer/apexer.py
+++ b/apexer/apexer.py
@@ -102,6 +102,13 @@
choices=['zip', 'image'],
help='type of APEX payload being built "zip" or "image"')
parser.add_argument(
+ '--payload_fs_type',
+ metavar='FS_TYPE',
+ required=False,
+ default='ext4',
+ choices=['ext4', 'f2fs'],
+ help='type of filesystem being used for payload image "ext4" or "f2fs"')
+ parser.add_argument(
'--override_apk_package_name',
required=False,
help='package name of the APK container. Default is the apex name in --manifest.'
@@ -163,7 +170,7 @@
parser.add_argument(
'--unsigned_payload_only',
action='store_true',
- help="""Outputs the unsigned payload image/zip only. Also, setting this flag implies
+ help="""Outputs the unsigned payload image/zip only. Also, setting this flag implies
--payload_only is set too."""
)
parser.add_argument(
@@ -183,7 +190,7 @@
':'.join(tool_path_list))
-def RunCommand(cmd, verbose=False, env=None):
+def RunCommand(cmd, verbose=False, env=None, expected_return_values={0}):
env = env or {}
env.update(os.environ.copy())
@@ -195,10 +202,10 @@
cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env)
output, _ = p.communicate()
- if verbose or p.returncode is not 0:
+ if verbose or p.returncode not in expected_return_values:
print(output.rstrip())
- assert p.returncode is 0, 'Failed to execute: ' + ' '.join(cmd)
+ assert p.returncode in expected_return_values, 'Failed to execute: ' + ' '.join(cmd)
return (output, p.returncode)
@@ -379,6 +386,9 @@
if args.logging_parent:
build_info.logging_parent = args.logging_parent
+ if args.payload_type == 'image':
+ build_info.payload_fs_type = args.payload_fs_type
+
return build_info
def AddLoggingParent(android_manifest, logging_parent_value):
@@ -451,9 +461,8 @@
print("Cannot read manifest file: '" + args.manifest + "'")
return False
- # create an empty ext4 image that is sufficiently big
- # sufficiently big = size + 16MB margin
- size_in_mb = (GetDirSize(args.input_dir) / (1024 * 1024)) + 16
+ # create an empty image that is sufficiently big
+ size_in_mb = (GetDirSize(args.input_dir) / (1024 * 1024))
content_dir = os.path.join(work_dir, 'content')
os.mkdir(content_dir)
@@ -480,66 +489,110 @@
return False
img_file = os.path.join(content_dir, 'apex_payload.img')
- # margin is for files that are not under args.input_dir. this consists of
- # n inodes for apex_manifest files and 11 reserved inodes for ext4.
- # TOBO(b/122991714) eliminate these details. use build_image.py which
- # determines the optimal inode count by first building an image and then
- # count the inodes actually used.
- inode_num_margin = GetFilesAndDirsCount(manifests_dir) + 11
- inode_num = GetFilesAndDirsCount(args.input_dir) + inode_num_margin
+ if args.payload_fs_type == 'ext4':
+ # sufficiently big = size + 16MB margin
+ size_in_mb += 16
- cmd = ['mke2fs']
- cmd.extend(['-O', '^has_journal']) # because image is read-only
- cmd.extend(['-b', str(BLOCK_SIZE)])
- cmd.extend(['-m', '0']) # reserved block percentage
- cmd.extend(['-t', 'ext4'])
- cmd.extend(['-I', '256']) # inode size
- cmd.extend(['-N', str(inode_num)])
- uu = str(uuid.uuid5(uuid.NAMESPACE_URL, 'www.android.com'))
- cmd.extend(['-U', uu])
- cmd.extend(['-E', 'hash_seed=' + uu])
- cmd.append(img_file)
- cmd.append(str(size_in_mb) + 'M')
- with tempfile.NamedTemporaryFile(dir=work_dir, suffix="mke2fs.conf") as conf_file:
- conf_data = pkgutil.get_data('apexer', 'mke2fs.conf')
- conf_file.write(conf_data)
- conf_file.flush()
- RunCommand(cmd, args.verbose,
- {"MKE2FS_CONFIG": conf_file.name, 'E2FSPROGS_FAKE_TIME': '1'})
+ # margin is for files that are not under args.input_dir. this consists of
+ # n inodes for apex_manifest files and 11 reserved inodes for ext4.
+ # TOBO(b/122991714) eliminate these details. use build_image.py which
+ # determines the optimal inode count by first building an image and then
+ # count the inodes actually used.
+ inode_num_margin = GetFilesAndDirsCount(manifests_dir) + 11
+ inode_num = GetFilesAndDirsCount(args.input_dir) + inode_num_margin
- # Compile the file context into the binary form
- compiled_file_contexts = os.path.join(work_dir, 'file_contexts.bin')
- cmd = ['sefcontext_compile']
- cmd.extend(['-o', compiled_file_contexts])
- cmd.append(args.file_contexts)
- RunCommand(cmd, args.verbose)
+ cmd = ['mke2fs']
+ cmd.extend(['-O', '^has_journal']) # because image is read-only
+ cmd.extend(['-b', str(BLOCK_SIZE)])
+ cmd.extend(['-m', '0']) # reserved block percentage
+ cmd.extend(['-t', 'ext4'])
+ cmd.extend(['-I', '256']) # inode size
+ cmd.extend(['-N', str(inode_num)])
+ uu = str(uuid.uuid5(uuid.NAMESPACE_URL, 'www.android.com'))
+ cmd.extend(['-U', uu])
+ cmd.extend(['-E', 'hash_seed=' + uu])
+ cmd.append(img_file)
+ cmd.append(str(size_in_mb) + 'M')
+ with tempfile.NamedTemporaryFile(dir=work_dir, suffix="mke2fs.conf") as conf_file:
+ conf_data = pkgutil.get_data('apexer', 'mke2fs.conf')
+ conf_file.write(conf_data)
+ conf_file.flush()
+ RunCommand(cmd, args.verbose,
+ {"MKE2FS_CONFIG": conf_file.name, 'E2FSPROGS_FAKE_TIME': '1'})
- # Add files to the image file
- cmd = ['e2fsdroid']
- cmd.append('-e') # input is not android_sparse_file
- cmd.extend(['-f', args.input_dir])
- cmd.extend(['-T', '0']) # time is set to epoch
- cmd.extend(['-S', compiled_file_contexts])
- cmd.extend(['-C', args.canned_fs_config])
- cmd.append('-s') # share dup blocks
- cmd.append(img_file)
- RunCommand(cmd, args.verbose, {'E2FSPROGS_FAKE_TIME': '1'})
+ # Compile the file context into the binary form
+ compiled_file_contexts = os.path.join(work_dir, 'file_contexts.bin')
+ cmd = ['sefcontext_compile']
+ cmd.extend(['-o', compiled_file_contexts])
+ cmd.append(args.file_contexts)
+ RunCommand(cmd, args.verbose)
- cmd = ['e2fsdroid']
- cmd.append('-e') # input is not android_sparse_file
- cmd.extend(['-f', manifests_dir])
- cmd.extend(['-T', '0']) # time is set to epoch
- cmd.extend(['-S', compiled_file_contexts])
- cmd.extend(['-C', args.canned_fs_config])
- cmd.append('-s') # share dup blocks
- cmd.append(img_file)
- RunCommand(cmd, args.verbose, {'E2FSPROGS_FAKE_TIME': '1'})
+ # Add files to the image file
+ cmd = ['e2fsdroid']
+ cmd.append('-e') # input is not android_sparse_file
+ cmd.extend(['-f', args.input_dir])
+ cmd.extend(['-T', '0']) # time is set to epoch
+ cmd.extend(['-S', compiled_file_contexts])
+ cmd.extend(['-C', args.canned_fs_config])
+ cmd.append('-s') # share dup blocks
+ cmd.append(img_file)
+ RunCommand(cmd, args.verbose, {'E2FSPROGS_FAKE_TIME': '1'})
- # Resize the image file to save space
- cmd = ['resize2fs']
- cmd.append('-M') # shrink as small as possible
- cmd.append(img_file)
- RunCommand(cmd, args.verbose, {'E2FSPROGS_FAKE_TIME': '1'})
+ cmd = ['e2fsdroid']
+ cmd.append('-e') # input is not android_sparse_file
+ cmd.extend(['-f', manifests_dir])
+ cmd.extend(['-T', '0']) # time is set to epoch
+ cmd.extend(['-S', compiled_file_contexts])
+ cmd.extend(['-C', args.canned_fs_config])
+ cmd.append('-s') # share dup blocks
+ cmd.append(img_file)
+ RunCommand(cmd, args.verbose, {'E2FSPROGS_FAKE_TIME': '1'})
+
+ # Resize the image file to save space
+ cmd = ['resize2fs']
+ cmd.append('-M') # shrink as small as possible
+ cmd.append(img_file)
+ RunCommand(cmd, args.verbose, {'E2FSPROGS_FAKE_TIME': '1'})
+
+ elif args.payload_fs_type == 'f2fs':
+ # F2FS requires a ~100M minimum size (necessary for ART, could be reduced a bit for other)
+ # TODO(b/158453869): relax these requirements for readonly devices
+ size_in_mb += 100
+
+ # Create an empty image
+ cmd = ['/usr/bin/fallocate']
+ cmd.extend(['-l', str(size_in_mb)+'M'])
+ cmd.append(img_file)
+ RunCommand(cmd, args.verbose)
+
+ # Format the image to F2FS
+ cmd = ['make_f2fs']
+ cmd.extend(['-g', 'android'])
+ uu = str(uuid.uuid5(uuid.NAMESPACE_URL, 'www.android.com'))
+ cmd.extend(['-U', uu])
+ cmd.extend(['-T', '0'])
+ cmd.append('-r') # sets checkpointing seed to 0 to remove random bits
+ cmd.append(img_file)
+ RunCommand(cmd, args.verbose)
+
+ # Add files to the image
+ cmd = ['sload_f2fs']
+ cmd.extend(['-C', args.canned_fs_config])
+ cmd.extend(['-f', manifests_dir])
+ cmd.extend(['-s', args.file_contexts])
+ cmd.extend(['-T', '0'])
+ cmd.append(img_file)
+ RunCommand(cmd, args.verbose, expected_return_values={0,1})
+
+ cmd = ['sload_f2fs']
+ cmd.extend(['-C', args.canned_fs_config])
+ cmd.extend(['-f', args.input_dir])
+ cmd.extend(['-s', args.file_contexts])
+ cmd.extend(['-T', '0'])
+ cmd.append(img_file)
+ RunCommand(cmd, args.verbose, expected_return_values={0,1})
+
+ # TODO(b/158453869): resize the image file to save space
if args.unsigned_payload_only:
shutil.copyfile(img_file, args.output)
diff --git a/apexer/runtests.sh b/apexer/runtests.sh
index bc634c0..e2eacc9 100755
--- a/apexer/runtests.sh
+++ b/apexer/runtests.sh
@@ -28,10 +28,12 @@
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
+do
input_dir=$(mktemp -d)
output_dir=$(mktemp -d)
-function finish {
+function cleanup {
sudo umount /dev/loop10
sudo losetup --detach /dev/loop10
@@ -39,7 +41,7 @@
rm -rf ${output_dir}
}
-trap finish EXIT
+trap cleanup ERR
#############################################
# prepare the inputs
#############################################
@@ -82,6 +84,7 @@
${ANDROID_HOST_OUT}/bin/apexer --verbose --manifest ${manifest_file} \
--file_contexts ${file_contexts_file} \
--canned_fs_config ${canned_fs_config_file} \
+ --payload_fs_type ${fs_type} \
--key ${ANDROID_BUILD_TOP}/system/apex/apexer/testdata/com.android.example.apex.pem \
--android_jar_path ${ANDROID_BUILD_TOP}/prebuilts/sdk/current/public/android.jar \
${input_dir} ${output_file}
@@ -132,4 +135,8 @@
# check the android manifest
aapt dump xmltree ${output_file} AndroidManifest.xml
-echo Passed
+echo "Passed for ${fs_type}"
+cleanup
+done
+
+echo "Passed for all fs types"
diff --git a/proto/apex_build_info.proto b/proto/apex_build_info.proto
index e25baa2..fd8a349 100644
--- a/proto/apex_build_info.proto
+++ b/proto/apex_build_info.proto
@@ -48,4 +48,7 @@
// Value of --logging_parent passed at build time.
string logging_parent = 9;
+
+ // Value of --payload_fs_type passed at build time.
+ string payload_fs_type = 10;
}