1#!/usr/bin/env python 2# 3# Copyright (C) 2018 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17import argparse 18import logging 19import os 20import pkgutil 21import subprocess 22import sys 23import tempfile 24 25 26def RunCommand(cmd, env): 27 """Runs the given command. 28 29 Args: 30 cmd: the command represented as a list of strings. 31 env: a dictionary of additional environment variables. 32 Returns: 33 A tuple of the output and the exit code. 34 """ 35 env_copy = os.environ.copy() 36 env_copy.update(env) 37 38 logging.info("Env: %s", env) 39 logging.info("Running: " + " ".join(cmd)) 40 41 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, 42 env=env_copy) 43 output, _ = p.communicate() 44 45 return output, p.returncode 46 47 48def ParseArguments(argv): 49 """Parses the input arguments to the program.""" 50 51 parser = argparse.ArgumentParser( 52 description=__doc__, 53 formatter_class=argparse.RawDescriptionHelpFormatter) 54 55 parser.add_argument("src_dir", help="The source directory for user image.") 56 parser.add_argument("output_file", help="The path of the output image file.") 57 parser.add_argument("ext_variant", choices=["ext2", "ext4"], 58 help="Variant of the extended filesystem.") 59 parser.add_argument("mount_point", help="The mount point for user image.") 60 parser.add_argument("fs_size", help="Size of the file system.") 61 parser.add_argument("file_contexts", nargs='?', 62 help="The selinux file context.") 63 64 parser.add_argument("--android_sparse", "-s", action="store_true", 65 help="Outputs an android sparse image (mke2fs).") 66 parser.add_argument("--journal_size", "-j", 67 help="Journal size (mke2fs).") 68 parser.add_argument("--timestamp", "-T", 69 help="Fake timetamp for the output image.") 70 parser.add_argument("--fs_config", "-C", 71 help="Path to the fs config file (e2fsdroid).") 72 parser.add_argument("--product_out", "-D", 73 help="Path to the directory with device specific fs" 74 " config files (e2fsdroid).") 75 parser.add_argument("--block_list_file", "-B", 76 help="Path to the block list file (e2fsdroid).") 77 parser.add_argument("--base_alloc_file_in", "-d", 78 help="Path to the input base fs file (e2fsdroid).") 79 parser.add_argument("--base_alloc_file_out", "-A", 80 help="Path to the output base fs file (e2fsdroid).") 81 parser.add_argument("--label", "-L", 82 help="The mount point (mke2fs).") 83 parser.add_argument("--inodes", "-i", 84 help="The extfs inodes count (mke2fs).") 85 parser.add_argument("--inode_size", "-I", 86 help="The extfs inode size (mke2fs).") 87 parser.add_argument("--reserved_percent", "-M", 88 help="The reserved blocks percentage (mke2fs).") 89 parser.add_argument("--flash_erase_block_size", "-e", 90 help="The flash erase block size (mke2fs).") 91 parser.add_argument("--flash_logical_block_size", "-o", 92 help="The flash logical block size (mke2fs).") 93 parser.add_argument("--mke2fs_uuid", "-U", 94 help="The mke2fs uuid (mke2fs) .") 95 parser.add_argument("--mke2fs_hash_seed", "-S", 96 help="The mke2fs hash seed (mke2fs).") 97 parser.add_argument("--share_dup_blocks", "-c", action="store_true", 98 help="ext4 share dup blocks (e2fsdroid).") 99 100 args, remainder = parser.parse_known_args(argv) 101 # The current argparse doesn't handle intermixed arguments well. Checks 102 # manually whether the file_contexts exists as the last argument. 103 # TODO(xunchang) use parse_intermixed_args() when we switch to python 3.7. 104 if len(remainder) == 1 and remainder[0] == argv[-1]: 105 args.file_contexts = remainder[0] 106 elif remainder: 107 parser.print_usage() 108 sys.exit(1) 109 110 return args 111 112 113def ConstructE2fsCommands(args): 114 """Builds the mke2fs & e2fsdroid command based on the input arguments. 115 116 Args: 117 args: The result of ArgumentParser after parsing the command line arguments. 118 Returns: 119 A tuple of two lists that serve as the command for mke2fs and e2fsdroid. 120 """ 121 122 BLOCKSIZE = 4096 123 124 e2fsdroid_opts = [] 125 mke2fs_extended_opts = [] 126 mke2fs_opts = [] 127 128 if args.android_sparse: 129 mke2fs_extended_opts.append("android_sparse") 130 else: 131 e2fsdroid_opts.append("-e") 132 if args.timestamp: 133 e2fsdroid_opts += ["-T", args.timestamp] 134 if args.fs_config: 135 e2fsdroid_opts += ["-C", args.fs_config] 136 if args.product_out: 137 e2fsdroid_opts += ["-p", args.product_out] 138 if args.block_list_file: 139 e2fsdroid_opts += ["-B", args.block_list_file] 140 if args.base_alloc_file_in: 141 e2fsdroid_opts += ["-d", args.base_alloc_file_in] 142 if args.base_alloc_file_out: 143 e2fsdroid_opts += ["-D", args.base_alloc_file_out] 144 if args.share_dup_blocks: 145 e2fsdroid_opts.append("-s") 146 if args.file_contexts: 147 e2fsdroid_opts += ["-S", args.file_contexts] 148 149 if args.flash_erase_block_size: 150 mke2fs_extended_opts.append("stripe_width={}".format( 151 int(args.flash_erase_block_size) / BLOCKSIZE)) 152 if args.flash_logical_block_size: 153 # stride should be the max of 8kb and the logical block size 154 stride = max(int(args.flash_logical_block_size), 8192) 155 mke2fs_extended_opts.append("stride={}".format(stride / BLOCKSIZE)) 156 if args.mke2fs_hash_seed: 157 mke2fs_extended_opts.append("hash_seed=" + args.mke2fs_hash_seed) 158 159 if args.journal_size: 160 if args.journal_size == "0": 161 mke2fs_opts += ["-O", "^has_journal"] 162 else: 163 mke2fs_opts += ["-J", "size=" + args.journal_size] 164 if args.label: 165 mke2fs_opts += ["-L", args.label] 166 if args.inodes: 167 mke2fs_opts += ["-N", args.inodes] 168 if args.inode_size: 169 mke2fs_opts += ["-I", args.inode_size] 170 if args.mount_point: 171 mke2fs_opts += ["-M", args.mount_point] 172 if args.reserved_percent: 173 mke2fs_opts += ["-m", args.reserved_percent] 174 if args.mke2fs_uuid: 175 mke2fs_opts += ["-U", args.mke2fs_uuid] 176 if mke2fs_extended_opts: 177 mke2fs_opts += ["-E", ','.join(mke2fs_extended_opts)] 178 179 # Round down the filesystem length to be a multiple of the block size 180 blocks = int(args.fs_size) / BLOCKSIZE 181 mke2fs_cmd = (["mke2fs"] + mke2fs_opts + 182 ["-t", args.ext_variant, "-b", str(BLOCKSIZE), args.output_file, 183 str(blocks)]) 184 185 e2fsdroid_cmd = (["e2fsdroid"] + e2fsdroid_opts + 186 ["-f", args.src_dir, "-a", args.mount_point, 187 args.output_file]) 188 189 return mke2fs_cmd, e2fsdroid_cmd 190 191 192def main(argv): 193 logging_format = '%(asctime)s %(filename)s %(levelname)s: %(message)s' 194 logging.basicConfig(level=logging.INFO, format=logging_format, 195 datefmt='%H:%M:%S') 196 197 args = ParseArguments(argv) 198 if not os.path.isdir(args.src_dir): 199 logging.error("Can not find directory %s", args.src_dir) 200 sys.exit(2) 201 if not args.mount_point: 202 logging.error("Mount point is required") 203 sys.exit(2) 204 if args.mount_point[0] != '/': 205 args.mount_point = '/' + args.mount_point 206 if not args.fs_size: 207 logging.error("Size of the filesystem is required") 208 sys.exit(2) 209 210 mke2fs_cmd, e2fsdroid_cmd = ConstructE2fsCommands(args) 211 212 # truncate output file since mke2fs will keep verity section in existing file 213 with open(args.output_file, 'w') as output: 214 output.truncate() 215 216 # run mke2fs 217 with tempfile.NamedTemporaryFile() as conf_file: 218 conf_data = pkgutil.get_data('mkuserimg_mke2fs', 'mke2fs.conf') 219 conf_file.write(conf_data) 220 conf_file.flush() 221 mke2fs_env = {"MKE2FS_CONFIG" : conf_file.name} 222 223 if args.timestamp: 224 mke2fs_env["E2FSPROGS_FAKE_TIME"] = args.timestamp 225 226 output, ret = RunCommand(mke2fs_cmd, mke2fs_env) 227 print(output) 228 if ret != 0: 229 logging.error("Failed to run mke2fs: " + output) 230 sys.exit(4) 231 232 # run e2fsdroid 233 e2fsdroid_env = {} 234 if args.timestamp: 235 e2fsdroid_env["E2FSPROGS_FAKE_TIME"] = args.timestamp 236 237 output, ret = RunCommand(e2fsdroid_cmd, e2fsdroid_env) 238 # The build script is parsing the raw output of e2fsdroid; keep the pattern 239 # unchanged for now. 240 print(output) 241 if ret != 0: 242 logging.error("Failed to run e2fsdroid_cmd: " + output) 243 os.remove(args.output_file) 244 sys.exit(4) 245 246 247if __name__ == '__main__': 248 main(sys.argv[1:]) 249