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