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