You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
266 lines
9.2 KiB
266 lines
9.2 KiB
#!/usr/bin/env python
|
|
#
|
|
# Copyright (C) 2018 The Android Open Source Project
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
import argparse
|
|
import logging
|
|
import os
|
|
import pkgutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
|
|
|
|
def RunCommand(cmd, env):
|
|
"""Runs the given command.
|
|
|
|
Args:
|
|
cmd: the command represented as a list of strings.
|
|
env: a dictionary of additional environment variables.
|
|
Returns:
|
|
A tuple of the output and the exit code.
|
|
"""
|
|
env_copy = os.environ.copy()
|
|
env_copy.update(env)
|
|
|
|
cmd[0] = FindProgram(cmd[0])
|
|
|
|
logging.info("Env: %s", env)
|
|
logging.info("Running: " + " ".join(cmd))
|
|
|
|
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
|
env=env_copy)
|
|
output, _ = p.communicate()
|
|
|
|
return output, p.returncode
|
|
|
|
def FindProgram(prog_name):
|
|
"""Finds the path to prog_name.
|
|
|
|
Args:
|
|
prog_name: the program name to find.
|
|
Returns:
|
|
path to the progName if found. The program is searched in the same directory
|
|
where this script is located at. If not found, progName is returned.
|
|
"""
|
|
exec_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
|
|
prog_path = os.path.join(exec_dir, prog_name)
|
|
if os.path.exists(prog_path):
|
|
return prog_path
|
|
else:
|
|
return prog_name
|
|
|
|
def ParseArguments(argv):
|
|
"""Parses the input arguments to the program."""
|
|
|
|
parser = argparse.ArgumentParser(
|
|
description=__doc__,
|
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
|
|
parser.add_argument("src_dir", help="The source directory for user image.")
|
|
parser.add_argument("output_file", help="The path of the output image file.")
|
|
parser.add_argument("ext_variant", choices=["ext2", "ext4"],
|
|
help="Variant of the extended filesystem.")
|
|
parser.add_argument("mount_point", help="The mount point for user image.")
|
|
parser.add_argument("fs_size", help="Size of the file system.")
|
|
parser.add_argument("file_contexts", nargs='?',
|
|
help="The selinux file context.")
|
|
|
|
parser.add_argument("--android_sparse", "-s", action="store_true",
|
|
help="Outputs an android sparse image (mke2fs).")
|
|
parser.add_argument("--journal_size", "-j",
|
|
help="Journal size (mke2fs).")
|
|
parser.add_argument("--timestamp", "-T",
|
|
help="Fake timetamp for the output image.")
|
|
parser.add_argument("--fs_config", "-C",
|
|
help="Path to the fs config file (e2fsdroid).")
|
|
parser.add_argument("--product_out", "-D",
|
|
help="Path to the directory with device specific fs"
|
|
" config files (e2fsdroid).")
|
|
parser.add_argument("--block_list_file", "-B",
|
|
help="Path to the block list file (e2fsdroid).")
|
|
parser.add_argument("--base_alloc_file_in", "-d",
|
|
help="Path to the input base fs file (e2fsdroid).")
|
|
parser.add_argument("--base_alloc_file_out", "-A",
|
|
help="Path to the output base fs file (e2fsdroid).")
|
|
parser.add_argument("--label", "-L",
|
|
help="The mount point (mke2fs).")
|
|
parser.add_argument("--inodes", "-i",
|
|
help="The extfs inodes count (mke2fs).")
|
|
parser.add_argument("--inode_size", "-I",
|
|
help="The extfs inode size (mke2fs).")
|
|
parser.add_argument("--reserved_percent", "-M",
|
|
help="The reserved blocks percentage (mke2fs).")
|
|
parser.add_argument("--flash_erase_block_size", "-e",
|
|
help="The flash erase block size (mke2fs).")
|
|
parser.add_argument("--flash_logical_block_size", "-o",
|
|
help="The flash logical block size (mke2fs).")
|
|
parser.add_argument("--mke2fs_uuid", "-U",
|
|
help="The mke2fs uuid (mke2fs) .")
|
|
parser.add_argument("--mke2fs_hash_seed", "-S",
|
|
help="The mke2fs hash seed (mke2fs).")
|
|
parser.add_argument("--share_dup_blocks", "-c", action="store_true",
|
|
help="ext4 share dup blocks (e2fsdroid).")
|
|
|
|
args, remainder = parser.parse_known_args(argv)
|
|
# The current argparse doesn't handle intermixed arguments well. Checks
|
|
# manually whether the file_contexts exists as the last argument.
|
|
# TODO(xunchang) use parse_intermixed_args() when we switch to python 3.7.
|
|
if len(remainder) == 1 and remainder[0] == argv[-1]:
|
|
args.file_contexts = remainder[0]
|
|
elif remainder:
|
|
parser.print_usage()
|
|
sys.exit(1)
|
|
|
|
return args
|
|
|
|
|
|
def ConstructE2fsCommands(args):
|
|
"""Builds the mke2fs & e2fsdroid command based on the input arguments.
|
|
|
|
Args:
|
|
args: The result of ArgumentParser after parsing the command line arguments.
|
|
Returns:
|
|
A tuple of two lists that serve as the command for mke2fs and e2fsdroid.
|
|
"""
|
|
|
|
BLOCKSIZE = 4096
|
|
|
|
e2fsdroid_opts = []
|
|
mke2fs_extended_opts = []
|
|
mke2fs_opts = []
|
|
|
|
if args.android_sparse:
|
|
mke2fs_extended_opts.append("android_sparse")
|
|
else:
|
|
e2fsdroid_opts.append("-e")
|
|
if args.timestamp:
|
|
e2fsdroid_opts += ["-T", args.timestamp]
|
|
if args.fs_config:
|
|
e2fsdroid_opts += ["-C", args.fs_config]
|
|
if args.product_out:
|
|
e2fsdroid_opts += ["-p", args.product_out]
|
|
if args.block_list_file:
|
|
e2fsdroid_opts += ["-B", args.block_list_file]
|
|
if args.base_alloc_file_in:
|
|
e2fsdroid_opts += ["-d", args.base_alloc_file_in]
|
|
if args.base_alloc_file_out:
|
|
e2fsdroid_opts += ["-D", args.base_alloc_file_out]
|
|
if args.share_dup_blocks:
|
|
e2fsdroid_opts.append("-s")
|
|
if args.file_contexts:
|
|
e2fsdroid_opts += ["-S", args.file_contexts]
|
|
|
|
if args.flash_erase_block_size:
|
|
mke2fs_extended_opts.append("stripe_width={}".format(
|
|
int(args.flash_erase_block_size) / BLOCKSIZE))
|
|
if args.flash_logical_block_size:
|
|
# stride should be the max of 8kb and the logical block size
|
|
stride = max(int(args.flash_logical_block_size), 8192)
|
|
mke2fs_extended_opts.append("stride={}".format(stride / BLOCKSIZE))
|
|
if args.mke2fs_hash_seed:
|
|
mke2fs_extended_opts.append("hash_seed=" + args.mke2fs_hash_seed)
|
|
|
|
if args.journal_size:
|
|
if args.journal_size == "0":
|
|
mke2fs_opts += ["-O", "^has_journal"]
|
|
else:
|
|
mke2fs_opts += ["-J", "size=" + args.journal_size]
|
|
if args.label:
|
|
mke2fs_opts += ["-L", args.label]
|
|
if args.inodes:
|
|
mke2fs_opts += ["-N", args.inodes]
|
|
if args.inode_size:
|
|
mke2fs_opts += ["-I", args.inode_size]
|
|
if args.mount_point:
|
|
mke2fs_opts += ["-M", args.mount_point]
|
|
if args.reserved_percent:
|
|
mke2fs_opts += ["-m", args.reserved_percent]
|
|
if args.mke2fs_uuid:
|
|
mke2fs_opts += ["-U", args.mke2fs_uuid]
|
|
if mke2fs_extended_opts:
|
|
mke2fs_opts += ["-E", ','.join(mke2fs_extended_opts)]
|
|
|
|
# Round down the filesystem length to be a multiple of the block size
|
|
blocks = int(args.fs_size) / BLOCKSIZE
|
|
mke2fs_cmd = (["mke2fs"] + mke2fs_opts +
|
|
["-t", args.ext_variant, "-b", str(BLOCKSIZE), args.output_file,
|
|
str(blocks)])
|
|
|
|
e2fsdroid_cmd = (["e2fsdroid"] + e2fsdroid_opts +
|
|
["-f", args.src_dir, "-a", args.mount_point,
|
|
args.output_file])
|
|
|
|
return mke2fs_cmd, e2fsdroid_cmd
|
|
|
|
|
|
def main(argv):
|
|
logging_format = '%(asctime)s %(filename)s %(levelname)s: %(message)s'
|
|
logging.basicConfig(level=logging.INFO, format=logging_format,
|
|
datefmt='%H:%M:%S')
|
|
|
|
args = ParseArguments(argv)
|
|
if not os.path.isdir(args.src_dir):
|
|
logging.error("Can not find directory %s", args.src_dir)
|
|
sys.exit(2)
|
|
if not args.mount_point:
|
|
logging.error("Mount point is required")
|
|
sys.exit(2)
|
|
if args.mount_point[0] != '/':
|
|
args.mount_point = '/' + args.mount_point
|
|
if not args.fs_size:
|
|
logging.error("Size of the filesystem is required")
|
|
sys.exit(2)
|
|
|
|
mke2fs_cmd, e2fsdroid_cmd = ConstructE2fsCommands(args)
|
|
|
|
# truncate output file since mke2fs will keep verity section in existing file
|
|
with open(args.output_file, 'w') as output:
|
|
output.truncate()
|
|
|
|
# run mke2fs
|
|
with tempfile.NamedTemporaryFile() as conf_file:
|
|
conf_data = pkgutil.get_data('mkuserimg_mke2fs', 'mke2fs.conf')
|
|
conf_file.write(conf_data)
|
|
conf_file.flush()
|
|
mke2fs_env = {"MKE2FS_CONFIG" : conf_file.name}
|
|
|
|
if args.timestamp:
|
|
mke2fs_env["E2FSPROGS_FAKE_TIME"] = args.timestamp
|
|
|
|
output, ret = RunCommand(mke2fs_cmd, mke2fs_env)
|
|
print(output)
|
|
if ret != 0:
|
|
logging.error("Failed to run mke2fs: " + output)
|
|
sys.exit(4)
|
|
|
|
# run e2fsdroid
|
|
e2fsdroid_env = {}
|
|
if args.timestamp:
|
|
e2fsdroid_env["E2FSPROGS_FAKE_TIME"] = args.timestamp
|
|
|
|
output, ret = RunCommand(e2fsdroid_cmd, e2fsdroid_env)
|
|
# The build script is parsing the raw output of e2fsdroid; keep the pattern
|
|
# unchanged for now.
|
|
print(output)
|
|
if ret != 0:
|
|
logging.error("Failed to run e2fsdroid_cmd: " + output)
|
|
os.remove(args.output_file)
|
|
sys.exit(4)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main(sys.argv[1:])
|