Home | History | Annotate | Download | only in releasetools
      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 """
     18 Given a target-files zipfile that does not contain images (ie, does
     19 not have an IMAGES/ top-level subdirectory), produce the images and
     20 add them to the zipfile.
     21 
     22 Usage:  add_img_to_target_files target_files
     23 """
     24 
     25 import sys
     26 
     27 if sys.hexversion < 0x02070000:
     28   print >> sys.stderr, "Python 2.7 or newer is required."
     29   sys.exit(1)
     30 
     31 import datetime
     32 import errno
     33 import os
     34 import shutil
     35 import tempfile
     36 import zipfile
     37 
     38 import build_image
     39 import common
     40 import sparse_img
     41 
     42 OPTIONS = common.OPTIONS
     43 
     44 OPTIONS.add_missing = False
     45 OPTIONS.rebuild_recovery = False
     46 OPTIONS.replace_verity_public_key = False
     47 OPTIONS.replace_verity_private_key = False
     48 OPTIONS.verity_signer_path = None
     49 
     50 def 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 
     63 def 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 
     90 def 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 
     96 def 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 
    108 def 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 
    114 def 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 
    131 def 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 
    137 def 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 
    191 def 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 
    249 def 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 
    291 def 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 
    399 def 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 
    431 if __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