ota_from_target_files revision 91a99c28e0d15a753bc303982c970c96d6dfe0f5
1#!/usr/bin/env python
2#
3# Copyright (C) 2008 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, produces an OTA package that installs
19that build.  An incremental OTA is produced if -i is given, otherwise
20a full OTA is produced.
21
22Usage:  ota_from_target_files [flags] input_target_files output_ota_package
23
24  --board_config  <file>
25      Deprecated.
26
27  -k (--package_key) <key> Key to use to sign the package (default is
28      the value of default_system_dev_certificate from the input
29      target-files's META/misc_info.txt, or
30      "build/target/product/security/testkey" if that value is not
31      specified).
32
33      For incremental OTAs, the default value is based on the source
34      target-file, not the target build.
35
36  -i  (--incremental_from)  <file>
37      Generate an incremental OTA using the given target-files zip as
38      the starting build.
39
40  -o  (--oem_settings)  <file>
41      Use the file to specify the expected OEM-specific properties
42      on the OEM partition of the intended device.
43
44  -w  (--wipe_user_data)
45      Generate an OTA package that will wipe the user data partition
46      when installed.
47
48  -n  (--no_prereq)
49      Omit the timestamp prereq check normally included at the top of
50      the build scripts (used for developer OTA packages which
51      legitimately need to go back and forth).
52
53  -e  (--extra_script)  <file>
54      Insert the contents of file at the end of the update script.
55
56  -a  (--aslr_mode)  <on|off>
57      Specify whether to turn on ASLR for the package (on by default).
58
59  -2  (--two_step)
60      Generate a 'two-step' OTA package, where recovery is updated
61      first, so that any changes made to the system partition are done
62      using the new recovery (new kernel, etc.).
63
64  --block
65      Generate a block-based OTA if possible.  Will fall back to a
66      file-based OTA if the target_files is older and doesn't support
67      block-based OTAs.
68
69  -b  (--binary)  <file>
70      Use the given binary as the update-binary in the output package,
71      instead of the binary in the build's target_files.  Use for
72      development only.
73
74"""
75
76import sys
77
78if sys.hexversion < 0x02070000:
79  print >> sys.stderr, "Python 2.7 or newer is required."
80  sys.exit(1)
81
82import copy
83import errno
84import os
85import re
86import subprocess
87import tempfile
88import time
89import zipfile
90
91try:
92  from hashlib import sha1 as sha1
93except ImportError:
94  from sha import sha as sha1
95
96import common
97import img_from_target_files
98import edify_generator
99import build_image
100
101OPTIONS = common.OPTIONS
102OPTIONS.package_key = None
103OPTIONS.incremental_source = None
104OPTIONS.require_verbatim = set()
105OPTIONS.prohibit_verbatim = set(("system/build.prop",))
106OPTIONS.patch_threshold = 0.95
107OPTIONS.wipe_user_data = False
108OPTIONS.omit_prereq = False
109OPTIONS.extra_script = None
110OPTIONS.aslr_mode = True
111OPTIONS.worker_threads = 3
112OPTIONS.two_step = False
113OPTIONS.no_signing = False
114OPTIONS.block_based = False
115OPTIONS.updater_binary = None
116OPTIONS.oem_source = None
117
118def MostPopularKey(d, default):
119  """Given a dict, return the key corresponding to the largest
120  value.  Returns 'default' if the dict is empty."""
121  x = [(v, k) for (k, v) in d.iteritems()]
122  if not x: return default
123  x.sort()
124  return x[-1][1]
125
126
127def IsSymlink(info):
128  """Return true if the zipfile.ZipInfo object passed in represents a
129  symlink."""
130  return (info.external_attr >> 16) == 0120777
131
132def IsRegular(info):
133  """Return true if the zipfile.ZipInfo object passed in represents a
134  symlink."""
135  return (info.external_attr >> 28) == 010
136
137def ClosestFileMatch(src, tgtfiles, existing):
138  """Returns the closest file match between a source file and list
139     of potential matches.  The exact filename match is preferred,
140     then the sha1 is searched for, and finally a file with the same
141     basename is evaluated.  Rename support in the updater-binary is
142     required for the latter checks to be used."""
143
144  result = tgtfiles.get("path:" + src.name)
145  if result is not None:
146    return result
147
148  if not OPTIONS.target_info_dict.get("update_rename_support", False):
149    return None
150
151  if src.size < 1000:
152    return None
153
154  result = tgtfiles.get("sha1:" + src.sha1)
155  if result is not None and existing.get(result.name) is None:
156    return result
157  result = tgtfiles.get("file:" + src.name.split("/")[-1])
158  if result is not None and existing.get(result.name) is None:
159    return result
160  return None
161
162class Item:
163  """Items represent the metadata (user, group, mode) of files and
164  directories in the system image."""
165  ITEMS = {}
166  def __init__(self, name, dir=False):
167    self.name = name
168    self.uid = None
169    self.gid = None
170    self.mode = None
171    self.selabel = None
172    self.capabilities = None
173    self.dir = dir
174
175    if name:
176      self.parent = Item.Get(os.path.dirname(name), dir=True)
177      self.parent.children.append(self)
178    else:
179      self.parent = None
180    if dir:
181      self.children = []
182
183  def Dump(self, indent=0):
184    if self.uid is not None:
185      print "%s%s %d %d %o" % ("  "*indent, self.name, self.uid, self.gid, self.mode)
186    else:
187      print "%s%s %s %s %s" % ("  "*indent, self.name, self.uid, self.gid, self.mode)
188    if self.dir:
189      print "%s%s" % ("  "*indent, self.descendants)
190      print "%s%s" % ("  "*indent, self.best_subtree)
191      for i in self.children:
192        i.Dump(indent=indent+1)
193
194  @classmethod
195  def Get(cls, name, dir=False):
196    if name not in cls.ITEMS:
197      cls.ITEMS[name] = Item(name, dir=dir)
198    return cls.ITEMS[name]
199
200  @classmethod
201  def GetMetadata(cls, input_zip):
202
203    # The target_files contains a record of what the uid,
204    # gid, and mode are supposed to be.
205    output = input_zip.read("META/filesystem_config.txt")
206
207    for line in output.split("\n"):
208      if not line: continue
209      columns = line.split()
210      name, uid, gid, mode = columns[:4]
211      selabel = None
212      capabilities = None
213
214      # After the first 4 columns, there are a series of key=value
215      # pairs. Extract out the fields we care about.
216      for element in columns[4:]:
217        key, value = element.split("=")
218        if key == "selabel":
219          selabel = value
220        if key == "capabilities":
221          capabilities = value
222
223      i = cls.ITEMS.get(name, None)
224      if i is not None:
225        i.uid = int(uid)
226        i.gid = int(gid)
227        i.mode = int(mode, 8)
228        i.selabel = selabel
229        i.capabilities = capabilities
230        if i.dir:
231          i.children.sort(key=lambda i: i.name)
232
233    # set metadata for the files generated by this script.
234    i = cls.ITEMS.get("system/recovery-from-boot.p", None)
235    if i: i.uid, i.gid, i.mode, i.selabel, i.capabilities = 0, 0, 0644, None, None
236    i = cls.ITEMS.get("system/etc/install-recovery.sh", None)
237    if i: i.uid, i.gid, i.mode, i.selabel, i.capabilities = 0, 0, 0544, None, None
238
239  def CountChildMetadata(self):
240    """Count up the (uid, gid, mode, selabel, capabilities) tuples for
241    all children and determine the best strategy for using set_perm_recursive and
242    set_perm to correctly chown/chmod all the files to their desired
243    values.  Recursively calls itself for all descendants.
244
245    Returns a dict of {(uid, gid, dmode, fmode, selabel, capabilities): count} counting up
246    all descendants of this node.  (dmode or fmode may be None.)  Also
247    sets the best_subtree of each directory Item to the (uid, gid,
248    dmode, fmode, selabel, capabilities) tuple that will match the most
249    descendants of that Item.
250    """
251
252    assert self.dir
253    d = self.descendants = {(self.uid, self.gid, self.mode, None, self.selabel, self.capabilities): 1}
254    for i in self.children:
255      if i.dir:
256        for k, v in i.CountChildMetadata().iteritems():
257          d[k] = d.get(k, 0) + v
258      else:
259        k = (i.uid, i.gid, None, i.mode, i.selabel, i.capabilities)
260        d[k] = d.get(k, 0) + 1
261
262    # Find the (uid, gid, dmode, fmode, selabel, capabilities)
263    # tuple that matches the most descendants.
264
265    # First, find the (uid, gid) pair that matches the most
266    # descendants.
267    ug = {}
268    for (uid, gid, _, _, _, _), count in d.iteritems():
269      ug[(uid, gid)] = ug.get((uid, gid), 0) + count
270    ug = MostPopularKey(ug, (0, 0))
271
272    # Now find the dmode, fmode, selabel, and capabilities that match
273    # the most descendants with that (uid, gid), and choose those.
274    best_dmode = (0, 0755)
275    best_fmode = (0, 0644)
276    best_selabel = (0, None)
277    best_capabilities = (0, None)
278    for k, count in d.iteritems():
279      if k[:2] != ug: continue
280      if k[2] is not None and count >= best_dmode[0]: best_dmode = (count, k[2])
281      if k[3] is not None and count >= best_fmode[0]: best_fmode = (count, k[3])
282      if k[4] is not None and count >= best_selabel[0]: best_selabel = (count, k[4])
283      if k[5] is not None and count >= best_capabilities[0]: best_capabilities = (count, k[5])
284    self.best_subtree = ug + (best_dmode[1], best_fmode[1], best_selabel[1], best_capabilities[1])
285
286    return d
287
288  def SetPermissions(self, script):
289    """Append set_perm/set_perm_recursive commands to 'script' to
290    set all permissions, users, and groups for the tree of files
291    rooted at 'self'."""
292
293    self.CountChildMetadata()
294
295    def recurse(item, current):
296      # current is the (uid, gid, dmode, fmode, selabel, capabilities) tuple that the current
297      # item (and all its children) have already been set to.  We only
298      # need to issue set_perm/set_perm_recursive commands if we're
299      # supposed to be something different.
300      if item.dir:
301        if current != item.best_subtree:
302          script.SetPermissionsRecursive("/"+item.name, *item.best_subtree)
303          current = item.best_subtree
304
305        if item.uid != current[0] or item.gid != current[1] or \
306           item.mode != current[2] or item.selabel != current[4] or \
307           item.capabilities != current[5]:
308          script.SetPermissions("/"+item.name, item.uid, item.gid,
309                                item.mode, item.selabel, item.capabilities)
310
311        for i in item.children:
312          recurse(i, current)
313      else:
314        if item.uid != current[0] or item.gid != current[1] or \
315               item.mode != current[3] or item.selabel != current[4] or \
316               item.capabilities != current[5]:
317          script.SetPermissions("/"+item.name, item.uid, item.gid,
318                                item.mode, item.selabel, item.capabilities)
319
320    recurse(self, (-1, -1, -1, -1, None, None))
321
322
323def CopySystemFiles(input_zip, output_zip=None,
324                    substitute=None):
325  """Copies files underneath system/ in the input zip to the output
326  zip.  Populates the Item class with their metadata, and returns a
327  list of symlinks.  output_zip may be None, in which case the copy is
328  skipped (but the other side effects still happen).  substitute is an
329  optional dict of {output filename: contents} to be output instead of
330  certain input files.
331  """
332
333  symlinks = []
334
335  for info in input_zip.infolist():
336    if info.filename.startswith("SYSTEM/"):
337      basefilename = info.filename[7:]
338      if IsSymlink(info):
339        symlinks.append((input_zip.read(info.filename),
340                         "/system/" + basefilename))
341      else:
342        info2 = copy.copy(info)
343        fn = info2.filename = "system/" + basefilename
344        if substitute and fn in substitute and substitute[fn] is None:
345          continue
346        if output_zip is not None:
347          if substitute and fn in substitute:
348            data = substitute[fn]
349          else:
350            data = input_zip.read(info.filename)
351          output_zip.writestr(info2, data)
352        if fn.endswith("/"):
353          Item.Get(fn[:-1], dir=True)
354        else:
355          Item.Get(fn, dir=False)
356
357  symlinks.sort()
358  return symlinks
359
360
361def SignOutput(temp_zip_name, output_zip_name):
362  key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
363  pw = key_passwords[OPTIONS.package_key]
364
365  common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw,
366                  whole_file=True)
367
368
369def AppendAssertions(script, info_dict, oem_dict = None):
370  oem_props = info_dict.get("oem_fingerprint_properties")
371  if oem_props is None:
372    device = GetBuildProp("ro.product.device", info_dict)
373    script.AssertDevice(device)
374  else:
375    if oem_dict is None:
376      raise common.ExternalError("No OEM file provided to answer expected assertions")
377    for prop in oem_props.split():
378      if oem_dict.get(prop) is None:
379        raise common.ExternalError("The OEM file is missing the property %s" % prop)
380      script.AssertOemProperty(prop, oem_dict.get(prop))
381
382
383def HasRecoveryPatch(target_files_zip):
384  try:
385    target_files_zip.getinfo("SYSTEM/recovery-from-boot.p")
386    return True
387  except KeyError:
388    return False
389
390def GetOemProperty(name, oem_props, oem_dict, info_dict):
391  if oem_props is not None and name in oem_props:
392    return oem_dict[name]
393  return GetBuildProp(name, info_dict)
394
395
396def CalculateFingerprint(oem_props, oem_dict, info_dict):
397  if oem_props is None:
398    return GetBuildProp("ro.build.fingerprint", info_dict)
399  return "%s/%s/%s:%s" % (
400    GetOemProperty("ro.product.brand", oem_props, oem_dict, info_dict),
401    GetOemProperty("ro.product.name", oem_props, oem_dict, info_dict),
402    GetOemProperty("ro.product.device", oem_props, oem_dict, info_dict),
403    GetBuildProp("ro.build.thumbprint", info_dict))
404
405def WriteFullOTAPackage(input_zip, output_zip):
406  # TODO: how to determine this?  We don't know what version it will
407  # be installed on top of.  For now, we expect the API just won't
408  # change very often.
409  script = edify_generator.EdifyGenerator(3, OPTIONS.info_dict)
410
411  oem_props = OPTIONS.info_dict.get("oem_fingerprint_properties")
412  oem_dict = None
413  if oem_props is not None:
414    if OPTIONS.oem_source is None:
415      raise common.ExternalError("OEM source required for this build")
416    script.Mount("/oem")
417    oem_dict = common.LoadDictionaryFromLines(open(OPTIONS.oem_source).readlines())
418
419  metadata = {"post-build": CalculateFingerprint(
420                               oem_props, oem_dict, OPTIONS.info_dict),
421              "pre-device": GetOemProperty("ro.product.device", oem_props, oem_dict,
422                                         OPTIONS.info_dict),
423              "post-timestamp": GetBuildProp("ro.build.date.utc",
424                                             OPTIONS.info_dict),
425              }
426
427  device_specific = common.DeviceSpecificParams(
428      input_zip=input_zip,
429      input_version=OPTIONS.info_dict["recovery_api_version"],
430      output_zip=output_zip,
431      script=script,
432      input_tmp=OPTIONS.input_tmp,
433      metadata=metadata,
434      info_dict=OPTIONS.info_dict)
435
436  has_recovery_patch = HasRecoveryPatch(input_zip)
437  block_based = OPTIONS.block_based and has_recovery_patch
438
439  if not OPTIONS.omit_prereq:
440    ts = GetBuildProp("ro.build.date.utc", OPTIONS.info_dict)
441    ts_text = GetBuildProp("ro.build.date", OPTIONS.info_dict)
442    script.AssertOlderBuild(ts, ts_text)
443
444  AppendAssertions(script, OPTIONS.info_dict, oem_dict)
445  device_specific.FullOTA_Assertions()
446
447  # Two-step package strategy (in chronological order, which is *not*
448  # the order in which the generated script has things):
449  #
450  # if stage is not "2/3" or "3/3":
451  #    write recovery image to boot partition
452  #    set stage to "2/3"
453  #    reboot to boot partition and restart recovery
454  # else if stage is "2/3":
455  #    write recovery image to recovery partition
456  #    set stage to "3/3"
457  #    reboot to recovery partition and restart recovery
458  # else:
459  #    (stage must be "3/3")
460  #    set stage to ""
461  #    do normal full package installation:
462  #       wipe and install system, boot image, etc.
463  #       set up system to update recovery partition on first boot
464  #    complete script normally (allow recovery to mark itself finished and reboot)
465
466  recovery_img = common.GetBootableImage("recovery.img", "recovery.img",
467                                         OPTIONS.input_tmp, "RECOVERY")
468  if OPTIONS.two_step:
469    if not OPTIONS.info_dict.get("multistage_support", None):
470      assert False, "two-step packages not supported by this build"
471    fs = OPTIONS.info_dict["fstab"]["/misc"]
472    assert fs.fs_type.upper() == "EMMC", \
473        "two-step packages only supported on devices with EMMC /misc partitions"
474    bcb_dev = {"bcb_dev": fs.device}
475    common.ZipWriteStr(output_zip, "recovery.img", recovery_img.data)
476    script.AppendExtra("""
477if get_stage("%(bcb_dev)s", "stage") == "2/3" then
478""" % bcb_dev)
479    script.WriteRawImage("/recovery", "recovery.img")
480    script.AppendExtra("""
481set_stage("%(bcb_dev)s", "3/3");
482reboot_now("%(bcb_dev)s", "recovery");
483else if get_stage("%(bcb_dev)s", "stage") == "3/3" then
484""" % bcb_dev)
485
486  device_specific.FullOTA_InstallBegin()
487
488  system_progress = 0.75
489
490  if OPTIONS.wipe_user_data:
491    system_progress -= 0.1
492
493  if "selinux_fc" in OPTIONS.info_dict:
494    WritePolicyConfig(OPTIONS.info_dict["selinux_fc"], output_zip)
495
496  script.ShowProgress(system_progress, 30)
497  if block_based:
498    mapdata, data = img_from_target_files.BuildSystem(
499        OPTIONS.input_tmp, OPTIONS.info_dict,
500        sparse=False, map_file=True)
501
502    common.ZipWriteStr(output_zip, "system.map", mapdata)
503    common.ZipWriteStr(output_zip, "system.muimg", data)
504    script.WipeBlockDevice("/system")
505    script.WriteRawImage("/system", "system.muimg", mapfn="system.map")
506  else:
507    script.FormatPartition("/system")
508    script.Mount("/system")
509    if not has_recovery_patch:
510      script.UnpackPackageDir("recovery", "/system")
511    script.UnpackPackageDir("system", "/system")
512
513    symlinks = CopySystemFiles(input_zip, output_zip)
514    script.MakeSymlinks(symlinks)
515
516  boot_img = common.GetBootableImage("boot.img", "boot.img",
517                                     OPTIONS.input_tmp, "BOOT")
518
519  if not block_based:
520    def output_sink(fn, data):
521      common.ZipWriteStr(output_zip, "recovery/" + fn, data)
522      Item.Get("system/" + fn, dir=False)
523
524    common.MakeRecoveryPatch(OPTIONS.input_tmp, output_sink,
525                             recovery_img, boot_img)
526
527    Item.GetMetadata(input_zip)
528    Item.Get("system").SetPermissions(script)
529
530  common.CheckSize(boot_img.data, "boot.img", OPTIONS.info_dict)
531  common.ZipWriteStr(output_zip, "boot.img", boot_img.data)
532
533  script.ShowProgress(0.05, 5)
534  script.WriteRawImage("/boot", "boot.img")
535
536  script.ShowProgress(0.2, 10)
537  device_specific.FullOTA_InstallEnd()
538
539  if OPTIONS.extra_script is not None:
540    script.AppendExtra(OPTIONS.extra_script)
541
542  script.UnmountAll()
543
544  if OPTIONS.wipe_user_data:
545    script.ShowProgress(0.1, 10)
546    script.FormatPartition("/data")
547    
548  if OPTIONS.two_step:
549    script.AppendExtra("""
550set_stage("%(bcb_dev)s", "");
551""" % bcb_dev)
552    script.AppendExtra("else\n")
553    script.WriteRawImage("/boot", "recovery.img")
554    script.AppendExtra("""
555set_stage("%(bcb_dev)s", "2/3");
556reboot_now("%(bcb_dev)s", "");
557endif;
558endif;
559""" % bcb_dev)
560  script.AddToZip(input_zip, output_zip, input_path=OPTIONS.updater_binary)
561  WriteMetadata(metadata, output_zip)
562
563def WritePolicyConfig(file_context, output_zip):
564  f = open(file_context, 'r');
565  basename = os.path.basename(file_context)
566  common.ZipWriteStr(output_zip, basename, f.read())
567
568
569def WriteMetadata(metadata, output_zip):
570  common.ZipWriteStr(output_zip, "META-INF/com/android/metadata",
571                     "".join(["%s=%s\n" % kv
572                              for kv in sorted(metadata.iteritems())]))
573
574def LoadSystemFiles(z):
575  """Load all the files from SYSTEM/... in a given target-files
576  ZipFile, and return a dict of {filename: File object}."""
577  out = {}
578  for info in z.infolist():
579    if info.filename.startswith("SYSTEM/") and not IsSymlink(info):
580      basefilename = info.filename[7:]
581      fn = "system/" + basefilename
582      data = z.read(info.filename)
583      out[fn] = common.File(fn, data)
584  return out
585
586
587def GetBuildProp(prop, info_dict):
588  """Return the fingerprint of the build of a given target-files info_dict."""
589  try:
590    return info_dict.get("build.prop", {})[prop]
591  except KeyError:
592    raise common.ExternalError("couldn't find %s in build.prop" % (prop,))
593
594def AddToKnownPaths(filename, known_paths):
595  if filename[-1] == "/":
596    return
597  dirs = filename.split("/")[:-1]
598  while len(dirs) > 0:
599    path = "/".join(dirs)
600    if path in known_paths:
601      break;
602    known_paths.add(path)
603    dirs.pop()
604
605def WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_zip):
606  source_version = OPTIONS.source_info_dict["recovery_api_version"]
607  target_version = OPTIONS.target_info_dict["recovery_api_version"]
608
609  if source_version == 0:
610    print ("WARNING: generating edify script for a source that "
611           "can't install it.")
612  script = edify_generator.EdifyGenerator(source_version,
613                                          OPTIONS.target_info_dict)
614
615  metadata = {"pre-device": GetBuildProp("ro.product.device",
616                                         OPTIONS.source_info_dict),
617              "post-timestamp": GetBuildProp("ro.build.date.utc",
618                                             OPTIONS.target_info_dict),
619              }
620
621  device_specific = common.DeviceSpecificParams(
622      source_zip=source_zip,
623      source_version=source_version,
624      target_zip=target_zip,
625      target_version=target_version,
626      output_zip=output_zip,
627      script=script,
628      metadata=metadata,
629      info_dict=OPTIONS.info_dict)
630
631  source_fp = GetBuildProp("ro.build.fingerprint", OPTIONS.source_info_dict)
632  target_fp = GetBuildProp("ro.build.fingerprint", OPTIONS.target_info_dict)
633  metadata["pre-build"] = source_fp
634  metadata["post-build"] = target_fp
635
636  source_boot = common.GetBootableImage(
637      "/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT",
638      OPTIONS.source_info_dict)
639  target_boot = common.GetBootableImage(
640      "/tmp/boot.img", "boot.img", OPTIONS.target_tmp, "BOOT")
641  updating_boot = (not OPTIONS.two_step and
642                   (source_boot.data != target_boot.data))
643
644  source_recovery = common.GetBootableImage(
645      "/tmp/recovery.img", "recovery.img", OPTIONS.source_tmp, "RECOVERY",
646      OPTIONS.source_info_dict)
647  target_recovery = common.GetBootableImage(
648      "/tmp/recovery.img", "recovery.img", OPTIONS.target_tmp, "RECOVERY")
649  updating_recovery = (source_recovery.data != target_recovery.data)
650
651  with tempfile.NamedTemporaryFile() as src_file:
652    with tempfile.NamedTemporaryFile() as tgt_file:
653      print "building source system image..."
654      src_file = tempfile.NamedTemporaryFile()
655      src_mapdata, src_data = img_from_target_files.BuildSystem(
656          OPTIONS.source_tmp, OPTIONS.source_info_dict,
657          sparse=False, map_file=True)
658
659      src_sys_sha1 = sha1(src_data).hexdigest()
660      print "source system sha1:", src_sys_sha1
661      src_file.write(src_data)
662
663      print "building target system image..."
664      tgt_file = tempfile.NamedTemporaryFile()
665      tgt_mapdata, tgt_data = img_from_target_files.BuildSystem(
666          OPTIONS.target_tmp, OPTIONS.target_info_dict,
667          sparse=False, map_file=True)
668      tgt_sys_sha1 = sha1(tgt_data).hexdigest()
669      print "target system sha1:", tgt_sys_sha1
670      tgt_sys_len = len(tgt_data)
671      tgt_file.write(tgt_data)
672
673      system_type, system_device = common.GetTypeAndDevice("/system", OPTIONS.info_dict)
674      system_patch = common.MakeSystemPatch(src_file, tgt_file)
675
676      TestBlockPatch(src_data, src_mapdata, system_patch.data, tgt_mapdata, tgt_sys_sha1)
677      src_data = None
678      tgt_data = None
679
680      system_patch.AddToZip(output_zip, compression=zipfile.ZIP_STORED)
681      src_mapfilename = system_patch.name + ".src.map"
682      common.ZipWriteStr(output_zip, src_mapfilename, src_mapdata)
683      tgt_mapfilename = system_patch.name + ".tgt.map"
684      common.ZipWriteStr(output_zip, tgt_mapfilename, tgt_mapdata)
685
686  oem_props = OPTIONS.target_info_dict.get("oem_fingerprint_properties")
687  oem_dict = None
688  if oem_props is not None:
689    if OPTIONS.oem_source is None:
690      raise common.ExternalError("OEM source required for this build")
691    script.Mount("/oem")
692    oem_dict = common.LoadDictionaryFromLines(open(OPTIONS.oem_source).readlines())
693
694  AppendAssertions(script, OPTIONS.target_info_dict, oem_dict)
695  device_specific.IncrementalOTA_Assertions()
696
697  # Two-step incremental package strategy (in chronological order,
698  # which is *not* the order in which the generated script has
699  # things):
700  #
701  # if stage is not "2/3" or "3/3":
702  #    do verification on current system
703  #    write recovery image to boot partition
704  #    set stage to "2/3"
705  #    reboot to boot partition and restart recovery
706  # else if stage is "2/3":
707  #    write recovery image to recovery partition
708  #    set stage to "3/3"
709  #    reboot to recovery partition and restart recovery
710  # else:
711  #    (stage must be "3/3")
712  #    perform update:
713  #       patch system files, etc.
714  #       force full install of new boot image
715  #       set up system to update recovery partition on first boot
716  #    complete script normally (allow recovery to mark itself finished and reboot)
717
718  if OPTIONS.two_step:
719    if not OPTIONS.info_dict.get("multistage_support", None):
720      assert False, "two-step packages not supported by this build"
721    fs = OPTIONS.info_dict["fstab"]["/misc"]
722    assert fs.fs_type.upper() == "EMMC", \
723        "two-step packages only supported on devices with EMMC /misc partitions"
724    bcb_dev = {"bcb_dev": fs.device}
725    common.ZipWriteStr(output_zip, "recovery.img", target_recovery.data)
726    script.AppendExtra("""
727if get_stage("%(bcb_dev)s", "stage") == "2/3" then
728""" % bcb_dev)
729    script.AppendExtra("sleep(20);\n");
730    script.WriteRawImage("/recovery", "recovery.img")
731    script.AppendExtra("""
732set_stage("%(bcb_dev)s", "3/3");
733reboot_now("%(bcb_dev)s", "recovery");
734else if get_stage("%(bcb_dev)s", "stage") != "3/3" then
735""" % bcb_dev)
736
737  script.Print("Verifying current system...")
738
739  device_specific.IncrementalOTA_VerifyBegin()
740
741  if oem_props is None:
742    script.AssertSomeFingerprint(source_fp, target_fp)
743  else:
744    script.AssertSomeThumbprint(
745        GetBuildProp("ro.build.thumbprint", OPTIONS.target_info_dict),
746        GetBuildProp("ro.build.thumbprint", OPTIONS.source_info_dict))
747
748  if updating_boot:
749    d = common.Difference(target_boot, source_boot)
750    _, _, d = d.ComputePatch()
751    print "boot      target: %d  source: %d  diff: %d" % (
752        target_boot.size, source_boot.size, len(d))
753
754    common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
755
756    boot_type, boot_device = common.GetTypeAndDevice("/boot", OPTIONS.info_dict)
757
758    script.PatchCheck("%s:%s:%d:%s:%d:%s" %
759                      (boot_type, boot_device,
760                       source_boot.size, source_boot.sha1,
761                       target_boot.size, target_boot.sha1))
762
763  device_specific.IncrementalOTA_VerifyEnd()
764
765  if OPTIONS.two_step:
766    script.WriteRawImage("/boot", "recovery.img")
767    script.AppendExtra("""
768set_stage("%(bcb_dev)s", "2/3");
769reboot_now("%(bcb_dev)s", "");
770else
771""" % bcb_dev)
772
773  script.Comment("---- start making changes here ----")
774
775  device_specific.IncrementalOTA_InstallBegin()
776
777  script.Print("Patching system image...")
778  script.Syspatch(system_device,
779                  tgt_mapfilename, tgt_sys_sha1,
780                  src_mapfilename, src_sys_sha1,
781                  system_patch.name)
782
783  if OPTIONS.two_step:
784    common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
785    script.WriteRawImage("/boot", "boot.img")
786    print "writing full boot image (forced by two-step mode)"
787
788  if not OPTIONS.two_step:
789    if updating_boot:
790      # Produce the boot image by applying a patch to the current
791      # contents of the boot partition, and write it back to the
792      # partition.
793      script.Print("Patching boot image...")
794      script.ApplyPatch("%s:%s:%d:%s:%d:%s"
795                        % (boot_type, boot_device,
796                           source_boot.size, source_boot.sha1,
797                           target_boot.size, target_boot.sha1),
798                        "-",
799                        target_boot.size, target_boot.sha1,
800                        source_boot.sha1, "patch/boot.img.p")
801      print "boot image changed; including."
802    else:
803      print "boot image unchanged; skipping."
804
805  # Do device-specific installation (eg, write radio image).
806  device_specific.IncrementalOTA_InstallEnd()
807
808  if OPTIONS.extra_script is not None:
809    script.AppendExtra(OPTIONS.extra_script)
810
811  if OPTIONS.wipe_user_data:
812    script.Print("Erasing user data...")
813    script.FormatPartition("/data")
814
815  if OPTIONS.two_step:
816    script.AppendExtra("""
817set_stage("%(bcb_dev)s", "");
818endif;
819endif;
820""" % bcb_dev)
821
822  script.SetProgress(1)
823  script.AddToZip(target_zip, output_zip, input_path=OPTIONS.updater_binary)
824  WriteMetadata(metadata, output_zip)
825
826def ParseMap(map_str):
827  x = map_str.split()
828  assert int(x[0]) == 4096
829  assert int(x[1]) == len(x)-2
830  return int(x[0]), [int(i) for i in x[2:]]
831
832def TestBlockPatch(src_muimg, src_map, patch_data, tgt_map, tgt_sha1):
833  src_blksize, src_regions = ParseMap(src_map)
834  tgt_blksize, tgt_regions = ParseMap(tgt_map)
835
836  with tempfile.NamedTemporaryFile() as src_file,\
837       tempfile.NamedTemporaryFile() as patch_file,\
838       tempfile.NamedTemporaryFile() as tgt_file,\
839       tempfile.NamedTemporaryFile() as src_map_file,\
840       tempfile.NamedTemporaryFile() as tgt_map_file:
841
842    src_total = sum(src_regions) * src_blksize
843    src_file.truncate(src_total)
844    p = 0
845    for i in range(0, len(src_regions), 2):
846      c, dc = src_regions[i:i+2]
847      src_file.write(src_muimg[p:(p+c*src_blksize)])
848      p += c*src_blksize
849      src_file.seek(dc*src_blksize, 1)
850    assert src_file.tell() == src_total
851
852    patch_file.write(patch_data)
853
854    tgt_total = sum(tgt_regions) * tgt_blksize
855    tgt_file.truncate(tgt_total)
856
857    src_map_file.write(src_map)
858    tgt_map_file.write(tgt_map)
859
860    src_file.flush()
861    src_map_file.flush()
862    patch_file.flush()
863    tgt_file.flush()
864    tgt_map_file.flush()
865
866    p = common.Run(["syspatch_host", src_file.name, src_map_file.name,
867                    patch_file.name, tgt_file.name, tgt_map_file.name],
868                   stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
869    stdoutdata, _ = p.communicate()
870    if p.returncode != 0:
871      print stdoutdata
872      raise ValueError("failed to reconstruct target system image from patch")
873
874    h = sha1()
875    for i in range(0, len(tgt_regions), 2):
876      c, dc = tgt_regions[i:i+2]
877      h.update(tgt_file.read(c*tgt_blksize))
878      tgt_file.seek(dc*tgt_blksize, 1)
879
880    if h.hexdigest() != tgt_sha1:
881      raise ValueError("patch reconstructed incorrect target system image")
882
883  print "test of system image patch succeeded"
884
885
886def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
887  target_has_recovery_patch = HasRecoveryPatch(target_zip)
888  source_has_recovery_patch = HasRecoveryPatch(source_zip)
889
890  if (OPTIONS.block_based and
891      target_has_recovery_patch and
892      source_has_recovery_patch):
893    return WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_zip)
894
895  source_version = OPTIONS.source_info_dict["recovery_api_version"]
896  target_version = OPTIONS.target_info_dict["recovery_api_version"]
897
898  if source_version == 0:
899    print ("WARNING: generating edify script for a source that "
900           "can't install it.")
901  script = edify_generator.EdifyGenerator(source_version,
902                                          OPTIONS.target_info_dict)
903
904  oem_props = OPTIONS.info_dict.get("oem_fingerprint_properties")
905  oem_dict = None
906  if oem_props is not None:
907    if OPTIONS.oem_source is None:
908      raise common.ExternalError("OEM source required for this build")
909    script.Mount("/oem")
910    oem_dict = common.LoadDictionaryFromLines(open(OPTIONS.oem_source).readlines())
911
912  metadata = {"pre-device": GetOemProperty("ro.product.device", oem_props, oem_dict,
913                                         OPTIONS.source_info_dict),
914              "post-timestamp": GetBuildProp("ro.build.date.utc",
915                                             OPTIONS.target_info_dict),
916              }
917
918  device_specific = common.DeviceSpecificParams(
919      source_zip=source_zip,
920      source_version=source_version,
921      target_zip=target_zip,
922      target_version=target_version,
923      output_zip=output_zip,
924      script=script,
925      metadata=metadata,
926      info_dict=OPTIONS.info_dict)
927
928  print "Loading target..."
929  target_data = LoadSystemFiles(target_zip)
930  print "Loading source..."
931  source_data = LoadSystemFiles(source_zip)
932
933  verbatim_targets = []
934  patch_list = []
935  diffs = []
936  renames = {}
937  known_paths = set()
938  largest_source_size = 0
939
940  matching_file_cache = {}
941  for fn, sf in source_data.items():
942    assert fn == sf.name
943    matching_file_cache["path:" + fn] = sf
944    if fn in target_data.keys():
945      AddToKnownPaths(fn, known_paths)
946    # Only allow eligibility for filename/sha matching
947    # if there isn't a perfect path match.
948    if target_data.get(sf.name) is None:
949      matching_file_cache["file:" + fn.split("/")[-1]] = sf
950      matching_file_cache["sha:" + sf.sha1] = sf
951
952  for fn in sorted(target_data.keys()):
953    tf = target_data[fn]
954    assert fn == tf.name
955    sf = ClosestFileMatch(tf, matching_file_cache, renames)
956    if sf is not None and sf.name != tf.name:
957      print "File has moved from " + sf.name + " to " + tf.name
958      renames[sf.name] = tf
959
960    if sf is None or fn in OPTIONS.require_verbatim:
961      # This file should be included verbatim
962      if fn in OPTIONS.prohibit_verbatim:
963        raise common.ExternalError("\"%s\" must be sent verbatim" % (fn,))
964      print "send", fn, "verbatim"
965      tf.AddToZip(output_zip)
966      verbatim_targets.append((fn, tf.size))
967      if fn in target_data.keys():
968        AddToKnownPaths(fn, known_paths)
969    elif tf.sha1 != sf.sha1:
970      # File is different; consider sending as a patch
971      diffs.append(common.Difference(tf, sf))
972    else:
973      # Target file data identical to source (may still be renamed)
974      pass
975
976  common.ComputeDifferences(diffs)
977
978  for diff in diffs:
979    tf, sf, d = diff.GetPatch()
980    path = "/".join(tf.name.split("/")[:-1])
981    if d is None or len(d) > tf.size * OPTIONS.patch_threshold or \
982        path not in known_paths:
983      # patch is almost as big as the file; don't bother patching
984      # or a patch + rename cannot take place due to the target
985      # directory not existing
986      tf.AddToZip(output_zip)
987      verbatim_targets.append((tf.name, tf.size))
988      if sf.name in renames:
989        del renames[sf.name]
990      AddToKnownPaths(tf.name, known_paths)
991    else:
992      common.ZipWriteStr(output_zip, "patch/" + sf.name + ".p", d)
993      patch_list.append((tf, sf, tf.size, common.sha1(d).hexdigest()))
994      largest_source_size = max(largest_source_size, sf.size)
995
996  script.Mount("/system")
997
998  target_fp = CalculateFingerprint(oem_props, oem_dict, OPTIONS.target_info_dict)
999  source_fp = CalculateFingerprint(oem_props, oem_dict, OPTIONS.source_info_dict)
1000
1001  if oem_props is None:
1002    script.AssertSomeFingerprint(source_fp, target_fp)
1003  else:
1004    script.AssertSomeThumbprint(
1005        GetBuildProp("ro.build.thumbprint", OPTIONS.target_info_dict),
1006        GetBuildProp("ro.build.thumbprint", OPTIONS.source_info_dict))
1007
1008  metadata["pre-build"] = source_fp
1009  metadata["post-build"] = target_fp
1010
1011  source_boot = common.GetBootableImage(
1012      "/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT",
1013      OPTIONS.source_info_dict)
1014  target_boot = common.GetBootableImage(
1015      "/tmp/boot.img", "boot.img", OPTIONS.target_tmp, "BOOT")
1016  updating_boot = (not OPTIONS.two_step and
1017                   (source_boot.data != target_boot.data))
1018
1019  source_recovery = common.GetBootableImage(
1020      "/tmp/recovery.img", "recovery.img", OPTIONS.source_tmp, "RECOVERY",
1021      OPTIONS.source_info_dict)
1022  target_recovery = common.GetBootableImage(
1023      "/tmp/recovery.img", "recovery.img", OPTIONS.target_tmp, "RECOVERY")
1024  updating_recovery = (source_recovery.data != target_recovery.data)
1025
1026  # Here's how we divide up the progress bar:
1027  #  0.1 for verifying the start state (PatchCheck calls)
1028  #  0.8 for applying patches (ApplyPatch calls)
1029  #  0.1 for unpacking verbatim files, symlinking, and doing the
1030  #      device-specific commands.
1031
1032  AppendAssertions(script, OPTIONS.target_info_dict, oem_dict)
1033  device_specific.IncrementalOTA_Assertions()
1034
1035  # Two-step incremental package strategy (in chronological order,
1036  # which is *not* the order in which the generated script has
1037  # things):
1038  #
1039  # if stage is not "2/3" or "3/3":
1040  #    do verification on current system
1041  #    write recovery image to boot partition
1042  #    set stage to "2/3"
1043  #    reboot to boot partition and restart recovery
1044  # else if stage is "2/3":
1045  #    write recovery image to recovery partition
1046  #    set stage to "3/3"
1047  #    reboot to recovery partition and restart recovery
1048  # else:
1049  #    (stage must be "3/3")
1050  #    perform update:
1051  #       patch system files, etc.
1052  #       force full install of new boot image
1053  #       set up system to update recovery partition on first boot
1054  #    complete script normally (allow recovery to mark itself finished and reboot)
1055
1056  if OPTIONS.two_step:
1057    if not OPTIONS.info_dict.get("multistage_support", None):
1058      assert False, "two-step packages not supported by this build"
1059    fs = OPTIONS.info_dict["fstab"]["/misc"]
1060    assert fs.fs_type.upper() == "EMMC", \
1061        "two-step packages only supported on devices with EMMC /misc partitions"
1062    bcb_dev = {"bcb_dev": fs.device}
1063    common.ZipWriteStr(output_zip, "recovery.img", target_recovery.data)
1064    script.AppendExtra("""
1065if get_stage("%(bcb_dev)s", "stage") == "2/3" then
1066""" % bcb_dev)
1067    script.AppendExtra("sleep(20);\n");
1068    script.WriteRawImage("/recovery", "recovery.img")
1069    script.AppendExtra("""
1070set_stage("%(bcb_dev)s", "3/3");
1071reboot_now("%(bcb_dev)s", "recovery");
1072else if get_stage("%(bcb_dev)s", "stage") != "3/3" then
1073""" % bcb_dev)
1074
1075  script.Print("Verifying current system...")
1076
1077  device_specific.IncrementalOTA_VerifyBegin()
1078
1079  script.ShowProgress(0.1, 0)
1080  so_far = 0
1081
1082  for tf, sf, size, patch_sha in patch_list:
1083    if tf.name != sf.name:
1084      script.SkipNextActionIfTargetExists(tf.name, tf.sha1)
1085    script.PatchCheck("/"+sf.name, tf.sha1, sf.sha1)
1086    so_far += sf.size
1087
1088  if updating_boot:
1089    d = common.Difference(target_boot, source_boot)
1090    _, _, d = d.ComputePatch()
1091    print "boot      target: %d  source: %d  diff: %d" % (
1092        target_boot.size, source_boot.size, len(d))
1093
1094    common.ZipWriteStr(output_zip, "patch/boot.img.p", d)
1095
1096    boot_type, boot_device = common.GetTypeAndDevice("/boot", OPTIONS.info_dict)
1097
1098    script.PatchCheck("%s:%s:%d:%s:%d:%s" %
1099                      (boot_type, boot_device,
1100                       source_boot.size, source_boot.sha1,
1101                       target_boot.size, target_boot.sha1))
1102    so_far += source_boot.size
1103
1104  if patch_list or updating_recovery or updating_boot:
1105    script.CacheFreeSpaceCheck(largest_source_size)
1106
1107  device_specific.IncrementalOTA_VerifyEnd()
1108
1109  if OPTIONS.two_step:
1110    script.WriteRawImage("/boot", "recovery.img")
1111    script.AppendExtra("""
1112set_stage("%(bcb_dev)s", "2/3");
1113reboot_now("%(bcb_dev)s", "");
1114else
1115""" % bcb_dev)
1116
1117  script.Comment("---- start making changes here ----")
1118
1119  device_specific.IncrementalOTA_InstallBegin()
1120
1121  if OPTIONS.two_step:
1122    common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
1123    script.WriteRawImage("/boot", "boot.img")
1124    print "writing full boot image (forced by two-step mode)"
1125
1126  script.Print("Removing unneeded files...")
1127  script.DeleteFiles(["/"+i[0] for i in verbatim_targets] +
1128                     ["/"+i for i in sorted(source_data)
1129                            if i not in target_data and
1130                            i not in renames] +
1131                     ["/system/recovery.img"])
1132
1133  script.ShowProgress(0.8, 0)
1134  total_patch_size = float(sum([i[1].size for i in patch_list]) + 1)
1135  if updating_boot:
1136    total_patch_size += target_boot.size
1137  so_far = 0
1138
1139  script.Print("Patching system files...")
1140  deferred_patch_list = []
1141  for item in patch_list:
1142    tf, sf, size, _ = item
1143    if tf.name == "system/build.prop":
1144      deferred_patch_list.append(item)
1145      continue
1146    if (sf.name != tf.name):
1147      script.SkipNextActionIfTargetExists(tf.name, tf.sha1)
1148    script.ApplyPatch("/"+sf.name, "-", tf.size, tf.sha1, sf.sha1, "patch/"+sf.name+".p")
1149    so_far += tf.size
1150    script.SetProgress(so_far / total_patch_size)
1151
1152  if not OPTIONS.two_step:
1153    if updating_boot:
1154      # Produce the boot image by applying a patch to the current
1155      # contents of the boot partition, and write it back to the
1156      # partition.
1157      script.Print("Patching boot image...")
1158      script.ApplyPatch("%s:%s:%d:%s:%d:%s"
1159                        % (boot_type, boot_device,
1160                           source_boot.size, source_boot.sha1,
1161                           target_boot.size, target_boot.sha1),
1162                        "-",
1163                        target_boot.size, target_boot.sha1,
1164                        source_boot.sha1, "patch/boot.img.p")
1165      so_far += target_boot.size
1166      script.SetProgress(so_far / total_patch_size)
1167      print "boot image changed; including."
1168    else:
1169      print "boot image unchanged; skipping."
1170
1171  if updating_recovery:
1172    # Recovery is generated as a patch using both the boot image
1173    # (which contains the same linux kernel as recovery) and the file
1174    # /system/etc/recovery-resource.dat (which contains all the images
1175    # used in the recovery UI) as sources.  This lets us minimize the
1176    # size of the patch, which must be included in every OTA package.
1177    #
1178    # For older builds where recovery-resource.dat is not present, we
1179    # use only the boot image as the source.
1180
1181    if not target_has_recovery_patch:
1182      def output_sink(fn, data):
1183        common.ZipWriteStr(output_zip, "recovery/" + fn, data)
1184        Item.Get("system/" + fn, dir=False)
1185
1186      common.MakeRecoveryPatch(OPTIONS.target_tmp, output_sink,
1187                               target_recovery, target_boot)
1188      script.DeleteFiles(["/system/recovery-from-boot.p",
1189                          "/system/etc/install-recovery.sh"])
1190    print "recovery image changed; including as patch from boot."
1191  else:
1192    print "recovery image unchanged; skipping."
1193
1194  script.ShowProgress(0.1, 10)
1195
1196  target_symlinks = CopySystemFiles(target_zip, None)
1197
1198  target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
1199  temp_script = script.MakeTemporary()
1200  Item.GetMetadata(target_zip)
1201  Item.Get("system").SetPermissions(temp_script)
1202
1203  # Note that this call will mess up the tree of Items, so make sure
1204  # we're done with it.
1205  source_symlinks = CopySystemFiles(source_zip, None)
1206  source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks])
1207
1208  # Delete all the symlinks in source that aren't in target.  This
1209  # needs to happen before verbatim files are unpacked, in case a
1210  # symlink in the source is replaced by a real file in the target.
1211  to_delete = []
1212  for dest, link in source_symlinks:
1213    if link not in target_symlinks_d:
1214      to_delete.append(link)
1215  script.DeleteFiles(to_delete)
1216
1217  if verbatim_targets:
1218    script.Print("Unpacking new files...")
1219    script.UnpackPackageDir("system", "/system")
1220
1221  if updating_recovery and not target_has_recovery_patch:
1222    script.Print("Unpacking new recovery...")
1223    script.UnpackPackageDir("recovery", "/system")
1224
1225  if len(renames) > 0:
1226    script.Print("Renaming files...")
1227
1228  for src in renames:
1229    print "Renaming " + src + " to " + renames[src].name
1230    script.RenameFile(src, renames[src].name)
1231
1232  script.Print("Symlinks and permissions...")
1233
1234  # Create all the symlinks that don't already exist, or point to
1235  # somewhere different than what we want.  Delete each symlink before
1236  # creating it, since the 'symlink' command won't overwrite.
1237  to_create = []
1238  for dest, link in target_symlinks:
1239    if link in source_symlinks_d:
1240      if dest != source_symlinks_d[link]:
1241        to_create.append((dest, link))
1242    else:
1243      to_create.append((dest, link))
1244  script.DeleteFiles([i[1] for i in to_create])
1245  script.MakeSymlinks(to_create)
1246
1247  # Now that the symlinks are created, we can set all the
1248  # permissions.
1249  script.AppendScript(temp_script)
1250
1251  # Do device-specific installation (eg, write radio image).
1252  device_specific.IncrementalOTA_InstallEnd()
1253
1254  if OPTIONS.extra_script is not None:
1255    script.AppendExtra(OPTIONS.extra_script)
1256
1257  # Patch the build.prop file last, so if something fails but the
1258  # device can still come up, it appears to be the old build and will
1259  # get set the OTA package again to retry.
1260  script.Print("Patching remaining system files...")
1261  for item in deferred_patch_list:
1262    tf, sf, size, _ = item
1263    script.ApplyPatch("/"+sf.name, "-", tf.size, tf.sha1, sf.sha1, "patch/"+sf.name+".p")
1264  script.SetPermissions("/system/build.prop", 0, 0, 0644, None, None)
1265
1266  if OPTIONS.wipe_user_data:
1267    script.Print("Erasing user data...")
1268    script.FormatPartition("/data")
1269
1270  if OPTIONS.two_step:
1271    script.AppendExtra("""
1272set_stage("%(bcb_dev)s", "");
1273endif;
1274endif;
1275""" % bcb_dev)
1276
1277  script.AddToZip(target_zip, output_zip, input_path=OPTIONS.updater_binary)
1278  WriteMetadata(metadata, output_zip)
1279
1280
1281def main(argv):
1282
1283  def option_handler(o, a):
1284    if o == "--board_config":
1285      pass   # deprecated
1286    elif o in ("-k", "--package_key"):
1287      OPTIONS.package_key = a
1288    elif o in ("-i", "--incremental_from"):
1289      OPTIONS.incremental_source = a
1290    elif o in ("-w", "--wipe_user_data"):
1291      OPTIONS.wipe_user_data = True
1292    elif o in ("-n", "--no_prereq"):
1293      OPTIONS.omit_prereq = True
1294    elif o in ("-o", "--oem_settings"):
1295      OPTIONS.oem_source = a
1296    elif o in ("-e", "--extra_script"):
1297      OPTIONS.extra_script = a
1298    elif o in ("-a", "--aslr_mode"):
1299      if a in ("on", "On", "true", "True", "yes", "Yes"):
1300        OPTIONS.aslr_mode = True
1301      else:
1302        OPTIONS.aslr_mode = False
1303    elif o in ("--worker_threads"):
1304      OPTIONS.worker_threads = int(a)
1305    elif o in ("-2", "--two_step"):
1306      OPTIONS.two_step = True
1307    elif o == "--no_signing":
1308      OPTIONS.no_signing = True
1309    elif o == "--block":
1310      OPTIONS.block_based = True
1311    elif o in ("-b", "--binary"):
1312      OPTIONS.updater_binary = a
1313    else:
1314      return False
1315    return True
1316
1317  args = common.ParseOptions(argv, __doc__,
1318                             extra_opts="b:k:i:d:wne:a:2o:",
1319                             extra_long_opts=["board_config=",
1320                                              "package_key=",
1321                                              "incremental_from=",
1322                                              "wipe_user_data",
1323                                              "no_prereq",
1324                                              "extra_script=",
1325                                              "worker_threads=",
1326                                              "aslr_mode=",
1327                                              "two_step",
1328                                              "no_signing",
1329                                              "block",
1330                                              "binary=",
1331                                              "oem_settings=",
1332                                              ],
1333                             extra_option_handler=option_handler)
1334
1335  if len(args) != 2:
1336    common.Usage(__doc__)
1337    sys.exit(1)
1338
1339  if OPTIONS.extra_script is not None:
1340    OPTIONS.extra_script = open(OPTIONS.extra_script).read()
1341
1342  print "unzipping target target-files..."
1343  OPTIONS.input_tmp, input_zip = common.UnzipTemp(args[0])
1344
1345  OPTIONS.target_tmp = OPTIONS.input_tmp
1346  OPTIONS.info_dict = common.LoadInfoDict(input_zip)
1347
1348  # If this image was originally labelled with SELinux contexts, make sure we
1349  # also apply the labels in our new image. During building, the "file_contexts"
1350  # is in the out/ directory tree, but for repacking from target-files.zip it's
1351  # in the root directory of the ramdisk.
1352  if "selinux_fc" in OPTIONS.info_dict:
1353    OPTIONS.info_dict["selinux_fc"] = os.path.join(OPTIONS.input_tmp, "BOOT", "RAMDISK",
1354        "file_contexts")
1355
1356  if OPTIONS.verbose:
1357    print "--- target info ---"
1358    common.DumpInfoDict(OPTIONS.info_dict)
1359
1360  # If the caller explicitly specified the device-specific extensions
1361  # path via -s/--device_specific, use that.  Otherwise, use
1362  # META/releasetools.py if it is present in the target target_files.
1363  # Otherwise, take the path of the file from 'tool_extensions' in the
1364  # info dict and look for that in the local filesystem, relative to
1365  # the current directory.
1366
1367  if OPTIONS.device_specific is None:
1368    from_input = os.path.join(OPTIONS.input_tmp, "META", "releasetools.py")
1369    if os.path.exists(from_input):
1370      print "(using device-specific extensions from target_files)"
1371      OPTIONS.device_specific = from_input
1372    else:
1373      OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions", None)
1374
1375  if OPTIONS.device_specific is not None:
1376    OPTIONS.device_specific = os.path.abspath(OPTIONS.device_specific)
1377
1378  if OPTIONS.no_signing:
1379    output_zip = zipfile.ZipFile(args[1], "w", compression=zipfile.ZIP_DEFLATED)
1380  else:
1381    temp_zip_file = tempfile.NamedTemporaryFile()
1382    output_zip = zipfile.ZipFile(temp_zip_file, "w",
1383                                 compression=zipfile.ZIP_DEFLATED)
1384
1385  if OPTIONS.incremental_source is None:
1386    WriteFullOTAPackage(input_zip, output_zip)
1387    if OPTIONS.package_key is None:
1388      OPTIONS.package_key = OPTIONS.info_dict.get(
1389          "default_system_dev_certificate",
1390          "build/target/product/security/testkey")
1391  else:
1392    print "unzipping source target-files..."
1393    OPTIONS.source_tmp, source_zip = common.UnzipTemp(OPTIONS.incremental_source)
1394    OPTIONS.target_info_dict = OPTIONS.info_dict
1395    OPTIONS.source_info_dict = common.LoadInfoDict(source_zip)
1396    if "selinux_fc" in OPTIONS.source_info_dict:
1397      OPTIONS.source_info_dict["selinux_fc"] = os.path.join(OPTIONS.source_tmp, "BOOT", "RAMDISK",
1398                                                            "file_contexts")
1399    if OPTIONS.package_key is None:
1400      OPTIONS.package_key = OPTIONS.source_info_dict.get(
1401          "default_system_dev_certificate",
1402          "build/target/product/security/testkey")
1403    if OPTIONS.verbose:
1404      print "--- source info ---"
1405      common.DumpInfoDict(OPTIONS.source_info_dict)
1406    WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)
1407
1408  output_zip.close()
1409
1410  if not OPTIONS.no_signing:
1411    SignOutput(temp_zip_file.name, args[1])
1412    temp_zip_file.close()
1413
1414  common.Cleanup()
1415
1416  print "done."
1417
1418
1419if __name__ == '__main__':
1420  try:
1421    common.CloseInheritedPipes()
1422    main(sys.argv[1:])
1423  except common.ExternalError, e:
1424    print
1425    print "   ERROR: %s" % (e,)
1426    print
1427    sys.exit(1)
1428