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 cmd[0] = FindProgram(cmd[0]) 39 40 logging.info("Env: %s", env) 41 logging.info("Running: " + " ".join(cmd)) 42 43 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, 44 env=env_copy) 45 output, _ = p.communicate() 46 47 return output, p.returncode 48 49def FindProgram(prog_name): 50 """Finds the path to prog_name. 51 52 Args: 53 prog_name: the program name to find. 54 Returns: 55 path to the progName if found. The program is searched in the same directory 56 where this script is located at. If not found, progName is returned. 57 """ 58 exec_dir = os.path.dirname(os.path.realpath(sys.argv[0])) 59 prog_path = os.path.join(exec_dir, prog_name) 60 if os.path.exists(prog_path): 61 return prog_path 62 else: 63 return prog_name 64 65def ParseArguments(argv): 66 """Parses the input arguments to the program.""" 67 68 parser = argparse.ArgumentParser( 69 description=__doc__, 70 formatter_class=argparse.RawDescriptionHelpFormatter) 71 72 parser.add_argument("src_dir", help="The source directory for user image.") 73 parser.add_argument("output_file", help="The path of the output image file.") 74 parser.add_argument("ext_variant", choices=["ext2", "ext4"], 75 help="Variant of the extended filesystem.") 76 parser.add_argument("mount_point", help="The mount point for user image.") 77 parser.add_argument("fs_size", help="Size of the file system.") 78 parser.add_argument("file_contexts", nargs='?', 79 help="The selinux file context.") 80 81 parser.add_argument("--android_sparse", "-s", action="store_true", 82 help="Outputs an android sparse image (mke2fs).") 83 parser.add_argument("--journal_size", "-j", 84 help="Journal size (mke2fs).") 85 parser.add_argument("--timestamp", "-T", 86 help="Fake timetamp for the output image.") 87 parser.add_argument("--fs_config", "-C", 88 help="Path to the fs config file (e2fsdroid).") 89 parser.add_argument("--product_out", "-D", 90 help="Path to the directory with device specific fs" 91 " config files (e2fsdroid).") 92 parser.add_argument("--block_list_file", "-B", 93 help="Path to the block list file (e2fsdroid).") 94 parser.add_argument("--base_alloc_file_in", "-d", 95 help="Path to the input base fs file (e2fsdroid).") 96 parser.add_argument("--base_alloc_file_out", "-A", 97 help="Path to the output base fs file (e2fsdroid).") 98 parser.add_argument("--label", "-L", 99 help="The mount point (mke2fs).") 100 parser.add_argument("--inodes", "-i", 101 help="The extfs inodes count (mke2fs).") 102 parser.add_argument("--inode_size", "-I", 103 help="The extfs inode size (mke2fs).") 104 parser.add_argument("--reserved_percent", "-M", 105 help="The reserved blocks percentage (mke2fs).") 106 parser.add_argument("--flash_erase_block_size", "-e", 107 help="The flash erase block size (mke2fs).") 108 parser.add_argument("--flash_logical_block_size", "-o", 109 help="The flash logical block size (mke2fs).") 110 parser.add_argument("--mke2fs_uuid", "-U", 111 help="The mke2fs uuid (mke2fs) .") 112 parser.add_argument("--mke2fs_hash_seed", "-S", 113 help="The mke2fs hash seed (mke2fs).") 114 parser.add_argument("--share_dup_blocks", "-c", action="store_true", 115 help="ext4 share dup blocks (e2fsdroid).") 116 117 args, remainder = parser.parse_known_args(argv) 118 # The current argparse doesn't handle intermixed arguments well. Checks 119 # manually whether the file_contexts exists as the last argument. 120 # TODO(xunchang) use parse_intermixed_args() when we switch to python 3.7. 121 if len(remainder) == 1 and remainder[0] == argv[-1]: 122 args.file_contexts = remainder[0] 123 elif remainder: 124 parser.print_usage() 125 sys.exit(1) 126 127 return args 128 129 130def ConstructE2fsCommands(args): 131 """Builds the mke2fs & e2fsdroid command based on the input arguments. 132 133 Args: 134 args: The result of ArgumentParser after parsing the command line arguments. 135 Returns: 136 A tuple of two lists that serve as the command for mke2fs and e2fsdroid. 137 """ 138 139 BLOCKSIZE = 4096 140 141 e2fsdroid_opts = [] 142 mke2fs_extended_opts = [] 143 mke2fs_opts = [] 144 145 if args.android_sparse: 146 mke2fs_extended_opts.append("android_sparse") 147 else: 148 e2fsdroid_opts.append("-e") 149 if args.timestamp: 150 e2fsdroid_opts += ["-T", args.timestamp] 151 if args.fs_config: 152 e2fsdroid_opts += ["-C", args.fs_config] 153 if args.product_out: 154 e2fsdroid_opts += ["-p", args.product_out] 155 if args.block_list_file: 156 e2fsdroid_opts += ["-B", args.block_list_file] 157 if args.base_alloc_file_in: 158 e2fsdroid_opts += ["-d", args.base_alloc_file_in] 159 if args.base_alloc_file_out: 160 e2fsdroid_opts += ["-D", args.base_alloc_file_out] 161 if args.share_dup_blocks: 162 e2fsdroid_opts.append("-s") 163 if args.file_contexts: 164 e2fsdroid_opts += ["-S", args.file_contexts] 165 166 if args.flash_erase_block_size: 167 mke2fs_extended_opts.append("stripe_width={}".format( 168 int(args.flash_erase_block_size) / BLOCKSIZE)) 169 if args.flash_logical_block_size: 170 # stride should be the max of 8kb and the logical block size 171 stride = max(int(args.flash_logical_block_size), 8192) 172 mke2fs_extended_opts.append("stride={}".format(stride / BLOCKSIZE)) 173 if args.mke2fs_hash_seed: 174 mke2fs_extended_opts.append("hash_seed=" + args.mke2fs_hash_seed) 175 176 if args.journal_size: 177 if args.journal_size == "0": 178 mke2fs_opts += ["-O", "^has_journal"] 179 else: 180 mke2fs_opts += ["-J", "size=" + args.journal_size] 181 if args.label: 182 mke2fs_opts += ["-L", args.label] 183 if args.inodes: 184 mke2fs_opts += ["-N", args.inodes] 185 if args.inode_size: 186 mke2fs_opts += ["-I", args.inode_size] 187 if args.mount_point: 188 mke2fs_opts += ["-M", args.mount_point] 189 if args.reserved_percent: 190 mke2fs_opts += ["-m", args.reserved_percent] 191 if args.mke2fs_uuid: 192 mke2fs_opts += ["-U", args.mke2fs_uuid] 193 if mke2fs_extended_opts: 194 mke2fs_opts += ["-E", ','.join(mke2fs_extended_opts)] 195 196 # Round down the filesystem length to be a multiple of the block size 197 blocks = int(args.fs_size) / BLOCKSIZE 198 mke2fs_cmd = (["mke2fs"] + mke2fs_opts + 199 ["-t", args.ext_variant, "-b", str(BLOCKSIZE), args.output_file, 200 str(blocks)]) 201 202 e2fsdroid_cmd = (["e2fsdroid"] + e2fsdroid_opts + 203 ["-f", args.src_dir, "-a", args.mount_point, 204 args.output_file]) 205 206 return mke2fs_cmd, e2fsdroid_cmd 207 208 209def main(argv): 210 logging_format = '%(asctime)s %(filename)s %(levelname)s: %(message)s' 211 logging.basicConfig(level=logging.INFO, format=logging_format, 212 datefmt='%H:%M:%S') 213 214 args = ParseArguments(argv) 215 if not os.path.isdir(args.src_dir): 216 logging.error("Can not find directory %s", args.src_dir) 217 sys.exit(2) 218 if not args.mount_point: 219 logging.error("Mount point is required") 220 sys.exit(2) 221 if args.mount_point[0] != '/': 222 args.mount_point = '/' + args.mount_point 223 if not args.fs_size: 224 logging.error("Size of the filesystem is required") 225 sys.exit(2) 226 227 mke2fs_cmd, e2fsdroid_cmd = ConstructE2fsCommands(args) 228 229 # truncate output file since mke2fs will keep verity section in existing file 230 with open(args.output_file, 'w') as output: 231 output.truncate() 232 233 # run mke2fs 234 with tempfile.NamedTemporaryFile() as conf_file: 235 conf_data = pkgutil.get_data('mkuserimg_mke2fs', 'mke2fs.conf') 236 conf_file.write(conf_data) 237 conf_file.flush() 238 mke2fs_env = {"MKE2FS_CONFIG" : conf_file.name} 239 240 if args.timestamp: 241 mke2fs_env["E2FSPROGS_FAKE_TIME"] = args.timestamp 242 243 output, ret = RunCommand(mke2fs_cmd, mke2fs_env) 244 print(output) 245 if ret != 0: 246 logging.error("Failed to run mke2fs: " + output) 247 sys.exit(4) 248 249 # run e2fsdroid 250 e2fsdroid_env = {} 251 if args.timestamp: 252 e2fsdroid_env["E2FSPROGS_FAKE_TIME"] = args.timestamp 253 254 output, ret = RunCommand(e2fsdroid_cmd, e2fsdroid_env) 255 # The build script is parsing the raw output of e2fsdroid; keep the pattern 256 # unchanged for now. 257 print(output) 258 if ret != 0: 259 logging.error("Failed to run e2fsdroid_cmd: " + output) 260 os.remove(args.output_file) 261 sys.exit(4) 262 263 264if __name__ == '__main__': 265 main(sys.argv[1:]) 266