add_img_to_target_files.py revision cfa86223d62a6afa0eb8f5a1a215e985bb0a8c89
1#!/usr/bin/env python
2#
3# Copyright (C) 2014 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"""
18Given a target-files zipfile that does not contain images (ie, does
19not have an IMAGES/ top-level subdirectory), produce the images and
20add them to the zipfile.
21
22Usage:  add_img_to_target_files target_files
23"""
24
25import sys
26
27if sys.hexversion < 0x02070000:
28  print >> sys.stderr, "Python 2.7 or newer is required."
29  sys.exit(1)
30
31import datetime
32import errno
33import os
34import shutil
35import tempfile
36import zipfile
37
38import build_image
39import common
40import sparse_img
41
42OPTIONS = common.OPTIONS
43
44OPTIONS.add_missing = False
45OPTIONS.rebuild_recovery = False
46OPTIONS.replace_verity_public_key = False
47OPTIONS.replace_verity_private_key = False
48OPTIONS.verity_signer_path = None
49
50def GetCareMap(which, imgname):
51  """Generate care_map of system (or vendor) partition"""
52
53  assert which in ("system", "vendor")
54  _, blk_device = common.GetTypeAndDevice("/" + which, OPTIONS.info_dict)
55
56  simg = sparse_img.SparseImage(imgname)
57  care_map_list = []
58  care_map_list.append(blk_device)
59  care_map_list.append(simg.care_map.to_string_raw())
60  return care_map_list
61
62
63def AddSystem(output_zip, prefix="IMAGES/", recovery_img=None, boot_img=None):
64  """Turn the contents of SYSTEM into a system image and store it in
65  output_zip."""
66
67  prebuilt_path = os.path.join(OPTIONS.input_tmp, prefix, "system.img")
68  if os.path.exists(prebuilt_path):
69    print "system.img already exists in %s, no need to rebuild..." % (prefix,)
70    return prebuilt_path
71
72  def output_sink(fn, data):
73    ofile = open(os.path.join(OPTIONS.input_tmp, "SYSTEM", fn), "w")
74    ofile.write(data)
75    ofile.close()
76
77  if OPTIONS.rebuild_recovery:
78    print "Building new recovery patch"
79    common.MakeRecoveryPatch(OPTIONS.input_tmp, output_sink, recovery_img,
80                             boot_img, info_dict=OPTIONS.info_dict)
81
82  block_list = common.MakeTempFile(prefix="system-blocklist-", suffix=".map")
83  imgname = BuildSystem(OPTIONS.input_tmp, OPTIONS.info_dict,
84                        block_list=block_list)
85  common.ZipWrite(output_zip, imgname, prefix + "system.img")
86  common.ZipWrite(output_zip, block_list, prefix + "system.map")
87  return imgname
88
89
90def BuildSystem(input_dir, info_dict, block_list=None):
91  """Build the (sparse) system image and return the name of a temp
92  file containing it."""
93  return CreateImage(input_dir, info_dict, "system", block_list=block_list)
94
95
96def AddSystemOther(output_zip, prefix="IMAGES/"):
97  """Turn the contents of SYSTEM_OTHER into a system_other image
98  and store it in output_zip."""
99
100  prebuilt_path = os.path.join(OPTIONS.input_tmp, prefix, "system_other.img")
101  if os.path.exists(prebuilt_path):
102    print "system_other.img already exists in %s, no need to rebuild..." % (prefix,)
103    return
104
105  imgname = BuildSystemOther(OPTIONS.input_tmp, OPTIONS.info_dict)
106  common.ZipWrite(output_zip, imgname, prefix + "system_other.img")
107
108def BuildSystemOther(input_dir, info_dict):
109  """Build the (sparse) system_other image and return the name of a temp
110  file containing it."""
111  return CreateImage(input_dir, info_dict, "system_other", block_list=None)
112
113
114def AddVendor(output_zip, prefix="IMAGES/"):
115  """Turn the contents of VENDOR into a vendor image and store in it
116  output_zip."""
117
118  prebuilt_path = os.path.join(OPTIONS.input_tmp, prefix, "vendor.img")
119  if os.path.exists(prebuilt_path):
120    print "vendor.img already exists in %s, no need to rebuild..." % (prefix,)
121    return prebuilt_path
122
123  block_list = common.MakeTempFile(prefix="vendor-blocklist-", suffix=".map")
124  imgname = BuildVendor(OPTIONS.input_tmp, OPTIONS.info_dict,
125                        block_list=block_list)
126  common.ZipWrite(output_zip, imgname, prefix + "vendor.img")
127  common.ZipWrite(output_zip, block_list, prefix + "vendor.map")
128  return imgname
129
130
131def BuildVendor(input_dir, info_dict, block_list=None):
132  """Build the (sparse) vendor image and return the name of a temp
133  file containing it."""
134  return CreateImage(input_dir, info_dict, "vendor", block_list=block_list)
135
136
137def CreateImage(input_dir, info_dict, what, block_list=None):
138  print "creating " + what + ".img..."
139
140  img = common.MakeTempFile(prefix=what + "-", suffix=".img")
141
142  # The name of the directory it is making an image out of matters to
143  # mkyaffs2image.  It wants "system" but we have a directory named
144  # "SYSTEM", so create a symlink.
145  try:
146    os.symlink(os.path.join(input_dir, what.upper()),
147               os.path.join(input_dir, what))
148  except OSError as e:
149    # bogus error on my mac version?
150    #   File "./build/tools/releasetools/img_from_target_files"
151    #     os.path.join(OPTIONS.input_tmp, "system"))
152    # OSError: [Errno 17] File exists
153    if e.errno == errno.EEXIST:
154      pass
155
156  image_props = build_image.ImagePropFromGlobalDict(info_dict, what)
157  fstab = info_dict["fstab"]
158  mount_point = "/" + what
159  if fstab and mount_point in fstab:
160    image_props["fs_type"] = fstab[mount_point].fs_type
161
162  # Use a fixed timestamp (01/01/2009) when packaging the image.
163  # Bug: 24377993
164  epoch = datetime.datetime.fromtimestamp(0)
165  timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
166  image_props["timestamp"] = int(timestamp)
167
168  if what == "system":
169    fs_config_prefix = ""
170  else:
171    fs_config_prefix = what + "_"
172
173  fs_config = os.path.join(
174      input_dir, "META/" + fs_config_prefix + "filesystem_config.txt")
175  if not os.path.exists(fs_config):
176    fs_config = None
177
178  # Override values loaded from info_dict.
179  if fs_config:
180    image_props["fs_config"] = fs_config
181  if block_list:
182    image_props["block_list"] = block_list
183
184  succ = build_image.BuildImage(os.path.join(input_dir, what),
185                                image_props, img)
186  assert succ, "build " + what + ".img image failed"
187
188  return img
189
190
191def AddUserdata(output_zip, prefix="IMAGES/"):
192  """Create a userdata image and store it in output_zip.
193
194  In most case we just create and store an empty userdata.img;
195  But the invoker can also request to create userdata.img with real
196  data from the target files, by setting "userdata_img_with_data=true"
197  in OPTIONS.info_dict.
198  """
199
200  prebuilt_path = os.path.join(OPTIONS.input_tmp, prefix, "userdata.img")
201  if os.path.exists(prebuilt_path):
202    print "userdata.img already exists in %s, no need to rebuild..." % (prefix,)
203    return
204
205  image_props = build_image.ImagePropFromGlobalDict(OPTIONS.info_dict, "data")
206  # We only allow yaffs to have a 0/missing partition_size.
207  # Extfs, f2fs must have a size. Skip userdata.img if no size.
208  if (not image_props.get("fs_type", "").startswith("yaffs") and
209      not image_props.get("partition_size")):
210    return
211
212  print "creating userdata.img..."
213
214  # Use a fixed timestamp (01/01/2009) when packaging the image.
215  # Bug: 24377993
216  epoch = datetime.datetime.fromtimestamp(0)
217  timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
218  image_props["timestamp"] = int(timestamp)
219
220  # The name of the directory it is making an image out of matters to
221  # mkyaffs2image.  So we create a temp dir, and within it we create an
222  # empty dir named "data", or a symlink to the DATA dir,
223  # and build the image from that.
224  temp_dir = tempfile.mkdtemp()
225  user_dir = os.path.join(temp_dir, "data")
226  empty = (OPTIONS.info_dict.get("userdata_img_with_data") != "true")
227  if empty:
228    # Create an empty dir.
229    os.mkdir(user_dir)
230  else:
231    # Symlink to the DATA dir.
232    os.symlink(os.path.join(OPTIONS.input_tmp, "DATA"),
233               user_dir)
234
235  img = tempfile.NamedTemporaryFile()
236
237  fstab = OPTIONS.info_dict["fstab"]
238  if fstab:
239    image_props["fs_type"] = fstab["/data"].fs_type
240  succ = build_image.BuildImage(user_dir, image_props, img.name)
241  assert succ, "build userdata.img image failed"
242
243  common.CheckSize(img.name, "userdata.img", OPTIONS.info_dict)
244  common.ZipWrite(output_zip, img.name, prefix + "userdata.img")
245  img.close()
246  shutil.rmtree(temp_dir)
247
248
249def AddCache(output_zip, prefix="IMAGES/"):
250  """Create an empty cache image and store it in output_zip."""
251
252  prebuilt_path = os.path.join(OPTIONS.input_tmp, prefix, "cache.img")
253  if os.path.exists(prebuilt_path):
254    print "cache.img already exists in %s, no need to rebuild..." % (prefix,)
255    return
256
257  image_props = build_image.ImagePropFromGlobalDict(OPTIONS.info_dict, "cache")
258  # The build system has to explicitly request for cache.img.
259  if "fs_type" not in image_props:
260    return
261
262  print "creating cache.img..."
263
264  # Use a fixed timestamp (01/01/2009) when packaging the image.
265  # Bug: 24377993
266  epoch = datetime.datetime.fromtimestamp(0)
267  timestamp = (datetime.datetime(2009, 1, 1) - epoch).total_seconds()
268  image_props["timestamp"] = int(timestamp)
269
270  # The name of the directory it is making an image out of matters to
271  # mkyaffs2image.  So we create a temp dir, and within it we create an
272  # empty dir named "cache", and build the image from that.
273  temp_dir = tempfile.mkdtemp()
274  user_dir = os.path.join(temp_dir, "cache")
275  os.mkdir(user_dir)
276  img = tempfile.NamedTemporaryFile()
277
278  fstab = OPTIONS.info_dict["fstab"]
279  if fstab:
280    image_props["fs_type"] = fstab["/cache"].fs_type
281  succ = build_image.BuildImage(user_dir, image_props, img.name)
282  assert succ, "build cache.img image failed"
283
284  common.CheckSize(img.name, "cache.img", OPTIONS.info_dict)
285  common.ZipWrite(output_zip, img.name, prefix + "cache.img")
286  img.close()
287  os.rmdir(user_dir)
288  os.rmdir(temp_dir)
289
290
291def AddImagesToTargetFiles(filename):
292  OPTIONS.input_tmp, input_zip = common.UnzipTemp(filename)
293
294  if not OPTIONS.add_missing:
295    for n in input_zip.namelist():
296      if n.startswith("IMAGES/"):
297        print "target_files appears to already contain images."
298        sys.exit(1)
299
300  try:
301    input_zip.getinfo("VENDOR/")
302    has_vendor = True
303  except KeyError:
304    has_vendor = False
305
306  has_system_other = "SYSTEM_OTHER/" in input_zip.namelist()
307
308  OPTIONS.info_dict = common.LoadInfoDict(input_zip, OPTIONS.input_tmp)
309
310  common.ZipClose(input_zip)
311  output_zip = zipfile.ZipFile(filename, "a",
312                               compression=zipfile.ZIP_DEFLATED)
313
314  has_recovery = (OPTIONS.info_dict.get("no_recovery") != "true")
315
316  def banner(s):
317    print "\n\n++++ " + s + " ++++\n\n"
318
319  banner("boot")
320  prebuilt_path = os.path.join(OPTIONS.input_tmp, "IMAGES", "boot.img")
321  boot_image = None
322  if os.path.exists(prebuilt_path):
323    print "boot.img already exists in IMAGES/, no need to rebuild..."
324    if OPTIONS.rebuild_recovery:
325      boot_image = common.GetBootableImage(
326          "IMAGES/boot.img", "boot.img", OPTIONS.input_tmp, "BOOT")
327  else:
328    boot_image = common.GetBootableImage(
329        "IMAGES/boot.img", "boot.img", OPTIONS.input_tmp, "BOOT")
330    if boot_image:
331      boot_image.AddToZip(output_zip)
332
333  recovery_image = None
334  if has_recovery:
335    banner("recovery")
336    prebuilt_path = os.path.join(OPTIONS.input_tmp, "IMAGES", "recovery.img")
337    if os.path.exists(prebuilt_path):
338      print "recovery.img already exists in IMAGES/, no need to rebuild..."
339      if OPTIONS.rebuild_recovery:
340        recovery_image = common.GetBootableImage(
341            "IMAGES/recovery.img", "recovery.img", OPTIONS.input_tmp,
342            "RECOVERY")
343    else:
344      recovery_image = common.GetBootableImage(
345          "IMAGES/recovery.img", "recovery.img", OPTIONS.input_tmp, "RECOVERY")
346      if recovery_image:
347        recovery_image.AddToZip(output_zip)
348
349  banner("system")
350  system_imgname = AddSystem(output_zip, recovery_img=recovery_image,
351                             boot_img=boot_image)
352  vendor_imgname = None
353  if has_vendor:
354    banner("vendor")
355    vendor_imgname = AddVendor(output_zip)
356  if has_system_other:
357    banner("system_other")
358    AddSystemOther(output_zip)
359  banner("userdata")
360  AddUserdata(output_zip)
361  banner("cache")
362  AddCache(output_zip)
363
364  # For devices using A/B update, copy over images from RADIO/ to IMAGES/ and
365  # make sure we have all the needed images ready under IMAGES/.
366  ab_partitions = os.path.join(OPTIONS.input_tmp, "META", "ab_partitions.txt")
367  if os.path.exists(ab_partitions):
368    with open(ab_partitions, 'r') as f:
369      lines = f.readlines()
370    # For devices using A/B update, generate care_map for system and vendor
371    # partitions (if present), then write this file to target_files package.
372    care_map_list = []
373    for line in lines:
374      if line.strip() == "system" and OPTIONS.info_dict.get(
375          "system_verity_block_device", None) is not None:
376        assert os.path.exists(system_imgname)
377        care_map_list += GetCareMap("system", system_imgname)
378      if line.strip() == "vendor" and OPTIONS.info_dict.get(
379          "vendor_verity_block_device", None) is not None:
380        assert os.path.exists(vendor_imgname)
381        care_map_list += GetCareMap("vendor", vendor_imgname)
382
383      img_name = line.strip() + ".img"
384      img_radio_path = os.path.join(OPTIONS.input_tmp, "RADIO", img_name)
385      if os.path.exists(img_radio_path):
386        common.ZipWrite(output_zip, img_radio_path,
387                        os.path.join("IMAGES", img_name))
388
389      # Zip spec says: All slashes MUST be forward slashes.
390      img_path = 'IMAGES/' + img_name
391      assert img_path in output_zip.namelist(), "cannot find " + img_name
392
393    if care_map_list:
394      file_path = "META/care_map.txt"
395      common.ZipWriteStr(output_zip, file_path, '\n'.join(care_map_list))
396
397  common.ZipClose(output_zip)
398
399def main(argv):
400  def option_handler(o, a):
401    if o in ("-a", "--add_missing"):
402      OPTIONS.add_missing = True
403    elif o in ("-r", "--rebuild_recovery",):
404      OPTIONS.rebuild_recovery = True
405    elif o == "--replace_verity_private_key":
406      OPTIONS.replace_verity_private_key = (True, a)
407    elif o == "--replace_verity_public_key":
408      OPTIONS.replace_verity_public_key = (True, a)
409    elif o == "--verity_signer_path":
410      OPTIONS.verity_signer_path = a
411    else:
412      return False
413    return True
414
415  args = common.ParseOptions(
416      argv, __doc__, extra_opts="ar",
417      extra_long_opts=["add_missing", "rebuild_recovery",
418                       "replace_verity_public_key=",
419                       "replace_verity_private_key=",
420                       "verity_signer_path="],
421      extra_option_handler=option_handler)
422
423
424  if len(args) != 1:
425    common.Usage(__doc__)
426    sys.exit(1)
427
428  AddImagesToTargetFiles(args[0])
429  print "done."
430
431if __name__ == '__main__':
432  try:
433    common.CloseInheritedPipes()
434    main(sys.argv[1:])
435  except common.ExternalError as e:
436    print
437    print "   ERROR: %s" % (e,)
438    print
439    sys.exit(1)
440  finally:
441    common.Cleanup()
442