build_image.py revision 3e92fd0fbaf0a7366bd9b60e700948d6be2b08e4
1#!/usr/bin/env python
2#
3# Copyright (C) 2011 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
17"""
18Build image output_image_file from input_directory and properties_file.
19
20Usage:  build_image input_directory properties_file output_image_file
21
22"""
23import os
24import os.path
25import subprocess
26import sys
27import commands
28import shutil
29
30import simg_map
31
32def RunCommand(cmd):
33  """ Echo and run the given command
34
35  Args:
36    cmd: the command represented as a list of strings.
37  Returns:
38    The exit code.
39  """
40  print "Running: ", " ".join(cmd)
41  p = subprocess.Popen(cmd)
42  p.communicate()
43  return p.returncode
44
45def GetVerityTreeSize(partition_size):
46  cmd = "build_verity_tree -s %d"
47  cmd %= partition_size
48  status, output = commands.getstatusoutput(cmd)
49  if status:
50    print output
51    return False, 0
52  return True, int(output)
53
54def GetVerityMetadataSize(partition_size):
55  cmd = "system/extras/verity/build_verity_metadata.py -s %d"
56  cmd %= partition_size
57  status, output = commands.getstatusoutput(cmd)
58  if status:
59    print output
60    return False, 0
61  return True, int(output)
62
63def AdjustPartitionSizeForVerity(partition_size):
64  """Modifies the provided partition size to account for the verity metadata.
65
66  This information is used to size the created image appropriately.
67  Args:
68    partition_size: the size of the partition to be verified.
69  Returns:
70    The size of the partition adjusted for verity metadata.
71  """
72  success, verity_tree_size = GetVerityTreeSize(partition_size)
73  if not success:
74    return 0;
75  success, verity_metadata_size = GetVerityMetadataSize(partition_size)
76  if not success:
77    return 0
78  return partition_size - verity_tree_size - verity_metadata_size
79
80def BuildVerityTree(sparse_image_path, verity_image_path, prop_dict):
81  cmd = ("build_verity_tree %s %s" % (sparse_image_path, verity_image_path))
82  print cmd
83  status, output = commands.getstatusoutput(cmd)
84  if status:
85    print "Could not build verity tree! Error: %s" % output
86    return False
87  root, salt = output.split()
88  prop_dict["verity_root_hash"] = root
89  prop_dict["verity_salt"] = salt
90  return True
91
92def BuildVerityMetadata(image_size, verity_metadata_path, root_hash, salt,
93                        block_device, signer_path, key):
94  cmd = ("system/extras/verity/build_verity_metadata.py %s %s %s %s %s %s %s" %
95              (image_size,
96              verity_metadata_path,
97              root_hash,
98              salt,
99              block_device,
100              signer_path,
101              key))
102  print cmd
103  status, output = commands.getstatusoutput(cmd)
104  if status:
105    print "Could not build verity metadata! Error: %s" % output
106    return False
107  return True
108
109def Append2Simg(sparse_image_path, unsparse_image_path, error_message):
110  """Appends the unsparse image to the given sparse image.
111
112  Args:
113    sparse_image_path: the path to the (sparse) image
114    unsparse_image_path: the path to the (unsparse) image
115  Returns:
116    True on success, False on failure.
117  """
118  cmd = "append2simg %s %s"
119  cmd %= (sparse_image_path, unsparse_image_path)
120  print cmd
121  status, output = commands.getstatusoutput(cmd)
122  if status:
123    print "%s: %s" % (error_message, output)
124    return False
125  return True
126
127def BuildVerifiedImage(data_image_path, verity_image_path, verity_metadata_path):
128  if not Append2Simg(data_image_path, verity_metadata_path, "Could not append verity metadata!"):
129    return False
130  if not Append2Simg(data_image_path, verity_image_path, "Could not append verity tree!"):
131    return False
132  return True
133
134def UnsparseImage(sparse_image_path, replace=True):
135  img_dir = os.path.dirname(sparse_image_path)
136  unsparse_image_path = "unsparse_" + os.path.basename(sparse_image_path)
137  unsparse_image_path = os.path.join(img_dir, unsparse_image_path)
138  if os.path.exists(unsparse_image_path):
139    if replace:
140      os.unlink(unsparse_image_path)
141    else:
142      return True, unsparse_image_path
143  inflate_command = ["simg2img", sparse_image_path, unsparse_image_path]
144  exit_code = RunCommand(inflate_command)
145  if exit_code != 0:
146    os.remove(unsparse_image_path)
147    return False, None
148  return True, unsparse_image_path
149
150def MappedUnsparseImage(sparse_image_path, unsparse_image_path,
151                        map_path, mapped_unsparse_image_path):
152  if simg_map.ComputeMap(sparse_image_path, unsparse_image_path,
153                         map_path, mapped_unsparse_image_path):
154    return False
155  return True
156
157def MakeVerityEnabledImage(out_file, prop_dict):
158  """Creates an image that is verifiable using dm-verity.
159
160  Args:
161    out_file: the location to write the verifiable image at
162    prop_dict: a dictionary of properties required for image creation and verification
163  Returns:
164    True on success, False otherwise.
165  """
166  # get properties
167  image_size = prop_dict["partition_size"]
168  block_dev = prop_dict["verity_block_device"]
169  signer_key = prop_dict["verity_key"]
170  signer_path = prop_dict["verity_signer_cmd"]
171
172  # make a tempdir
173  tempdir_name = os.path.join(os.path.dirname(out_file), "verity_images")
174  if os.path.exists(tempdir_name):
175    shutil.rmtree(tempdir_name)
176  os.mkdir(tempdir_name)
177
178  # get partial image paths
179  verity_image_path = os.path.join(tempdir_name, "verity.img")
180  verity_metadata_path = os.path.join(tempdir_name, "verity_metadata.img")
181
182  # build the verity tree and get the root hash and salt
183  if not BuildVerityTree(out_file, verity_image_path, prop_dict):
184    shutil.rmtree(tempdir_name)
185    return False
186
187  # build the metadata blocks
188  root_hash = prop_dict["verity_root_hash"]
189  salt = prop_dict["verity_salt"]
190  if not BuildVerityMetadata(image_size,
191                              verity_metadata_path,
192                              root_hash,
193                              salt,
194                              block_dev,
195                              signer_path,
196                              signer_key):
197    shutil.rmtree(tempdir_name)
198    return False
199
200  # build the full verified image
201  if not BuildVerifiedImage(out_file,
202                            verity_image_path,
203                            verity_metadata_path):
204    shutil.rmtree(tempdir_name)
205    return False
206
207  shutil.rmtree(tempdir_name)
208  return True
209
210def BuildImage(in_dir, prop_dict, out_file):
211  """Build an image to out_file from in_dir with property prop_dict.
212
213  Args:
214    in_dir: path of input directory.
215    prop_dict: property dictionary.
216    out_file: path of the output image file.
217
218  Returns:
219    True iff the image is built successfully.
220  """
221  build_command = []
222  fs_type = prop_dict.get("fs_type", "")
223  run_fsck = False
224
225  # adjust the partition size to make room for the hashes if this is to be verified
226  if prop_dict.get("verity") == "true":
227    partition_size = int(prop_dict.get("partition_size"))
228    adjusted_size = AdjustPartitionSizeForVerity(partition_size)
229    if not adjusted_size:
230      return False
231    prop_dict["partition_size"] = str(adjusted_size)
232    prop_dict["original_partition_size"] = str(partition_size)
233
234  if fs_type.startswith("ext"):
235    build_command = ["mkuserimg.sh"]
236    if "extfs_sparse_flag" in prop_dict:
237      build_command.append(prop_dict["extfs_sparse_flag"])
238      run_fsck = True
239    build_command.extend([in_dir, out_file, fs_type,
240                          prop_dict["mount_point"]])
241    build_command.append(prop_dict["partition_size"])
242    if "timestamp" in prop_dict:
243      build_command.extend(["-T", str(prop_dict["timestamp"])])
244    if "selinux_fc" in prop_dict:
245      build_command.append(prop_dict["selinux_fc"])
246  else:
247    build_command = ["mkyaffs2image", "-f"]
248    if prop_dict.get("mkyaffs2_extra_flags", None):
249      build_command.extend(prop_dict["mkyaffs2_extra_flags"].split())
250    build_command.append(in_dir)
251    build_command.append(out_file)
252    if "selinux_fc" in prop_dict:
253      build_command.append(prop_dict["selinux_fc"])
254      build_command.append(prop_dict["mount_point"])
255
256  exit_code = RunCommand(build_command)
257  if exit_code != 0:
258    return False
259
260  # create the verified image if this is to be verified
261  if prop_dict.get("verity") == "true":
262    if not MakeVerityEnabledImage(out_file, prop_dict):
263      return False
264
265  if run_fsck and prop_dict.get("skip_fsck") != "true":
266    success, unsparse_image = UnsparseImage(out_file, replace=False)
267    if not success:
268      return False
269
270    # Run e2fsck on the inflated image file
271    e2fsck_command = ["e2fsck", "-f", "-n", unsparse_image]
272    exit_code = RunCommand(e2fsck_command)
273
274    os.remove(unsparse_image)
275
276  return exit_code == 0
277
278
279def ImagePropFromGlobalDict(glob_dict, mount_point):
280  """Build an image property dictionary from the global dictionary.
281
282  Args:
283    glob_dict: the global dictionary from the build system.
284    mount_point: such as "system", "data" etc.
285  """
286  d = {}
287  if "build.prop" in glob_dict:
288    bp = glob_dict["build.prop"]
289    if "ro.build.date.utc" in bp:
290      d["timestamp"] = bp["ro.build.date.utc"]
291
292  def copy_prop(src_p, dest_p):
293    if src_p in glob_dict:
294      d[dest_p] = str(glob_dict[src_p])
295
296  common_props = (
297      "extfs_sparse_flag",
298      "mkyaffs2_extra_flags",
299      "selinux_fc",
300      "skip_fsck",
301      "verity",
302      "verity_block_device",
303      "verity_key",
304      "verity_signer_cmd"
305      )
306  for p in common_props:
307    copy_prop(p, p)
308
309  d["mount_point"] = mount_point
310  if mount_point == "system":
311    copy_prop("fs_type", "fs_type")
312    copy_prop("system_size", "partition_size")
313  elif mount_point == "data":
314    copy_prop("fs_type", "fs_type")
315    copy_prop("userdata_size", "partition_size")
316  elif mount_point == "cache":
317    copy_prop("cache_fs_type", "fs_type")
318    copy_prop("cache_size", "partition_size")
319  elif mount_point == "vendor":
320    copy_prop("vendor_fs_type", "fs_type")
321    copy_prop("vendor_size", "partition_size")
322  elif mount_point == "oem":
323    copy_prop("fs_type", "fs_type")
324    copy_prop("oem_size", "partition_size")
325
326  return d
327
328
329def LoadGlobalDict(filename):
330  """Load "name=value" pairs from filename"""
331  d = {}
332  f = open(filename)
333  for line in f:
334    line = line.strip()
335    if not line or line.startswith("#"):
336      continue
337    k, v = line.split("=", 1)
338    d[k] = v
339  f.close()
340  return d
341
342
343def main(argv):
344  if len(argv) != 3:
345    print __doc__
346    sys.exit(1)
347
348  in_dir = argv[0]
349  glob_dict_file = argv[1]
350  out_file = argv[2]
351
352  glob_dict = LoadGlobalDict(glob_dict_file)
353  image_filename = os.path.basename(out_file)
354  mount_point = ""
355  if image_filename == "system.img":
356    mount_point = "system"
357  elif image_filename == "userdata.img":
358    mount_point = "data"
359  elif image_filename == "cache.img":
360    mount_point = "cache"
361  elif image_filename == "vendor.img":
362    mount_point = "vendor"
363  elif image_filename == "oem.img":
364    mount_point = "oem"
365  else:
366    print >> sys.stderr, "error: unknown image file name ", image_filename
367    exit(1)
368
369  image_properties = ImagePropFromGlobalDict(glob_dict, mount_point)
370  if not BuildImage(in_dir, image_properties, out_file):
371    print >> sys.stderr, "error: failed to build %s from %s" % (out_file, in_dir)
372    exit(1)
373
374
375if __name__ == '__main__':
376  main(sys.argv[1:])
377