github.com/westcoastroms/westcoastroms-build@v0.0.0-20190928114312-2350e5a73030/build/make/tools/releasetools/edify_generator.py (about)

     1  # Copyright (C) 2009 The Android Open Source Project
     2  #
     3  # Licensed under the Apache License, Version 2.0 (the "License");
     4  # you may not use this file except in compliance with the License.
     5  # You may obtain a copy of the License at
     6  #
     7  #      http://www.apache.org/licenses/LICENSE-2.0
     8  #
     9  # Unless required by applicable law or agreed to in writing, software
    10  # distributed under the License is distributed on an "AS IS" BASIS,
    11  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  # See the License for the specific language governing permissions and
    13  # limitations under the License.
    14  
    15  import re
    16  
    17  import common
    18  
    19  class EdifyGenerator(object):
    20    """Class to generate scripts in the 'edify' recovery script language
    21    used from donut onwards."""
    22  
    23    def __init__(self, version, info, fstab=None):
    24      self.script = []
    25      self.mounts = set()
    26      self._required_cache = 0
    27      self.version = version
    28      self.info = info
    29      if fstab is None:
    30        self.fstab = self.info.get("fstab", None)
    31      else:
    32        self.fstab = fstab
    33  
    34    def MakeTemporary(self):
    35      """Make a temporary script object whose commands can latter be
    36      appended to the parent script with AppendScript().  Used when the
    37      caller wants to generate script commands out-of-order."""
    38      x = EdifyGenerator(self.version, self.info)
    39      x.mounts = self.mounts
    40      return x
    41  
    42    @property
    43    def required_cache(self):
    44      """Return the minimum cache size to apply the update."""
    45      return self._required_cache
    46  
    47    @staticmethod
    48    def WordWrap(cmd, linelen=80):
    49      """'cmd' should be a function call with null characters after each
    50      parameter (eg, "somefun(foo,\0bar,\0baz)").  This function wraps cmd
    51      to a given line length, replacing nulls with spaces and/or newlines
    52      to format it nicely."""
    53      indent = cmd.index("(")+1
    54      out = []
    55      first = True
    56      x = re.compile("^(.{,%d})\0" % (linelen-indent,))
    57      while True:
    58        if not first:
    59          out.append(" " * indent)
    60        first = False
    61        m = x.search(cmd)
    62        if not m:
    63          parts = cmd.split("\0", 1)
    64          out.append(parts[0]+"\n")
    65          if len(parts) == 1:
    66            break
    67          else:
    68            cmd = parts[1]
    69            continue
    70        out.append(m.group(1)+"\n")
    71        cmd = cmd[m.end():]
    72  
    73      return "".join(out).replace("\0", " ").rstrip("\n")
    74  
    75    def AppendScript(self, other):
    76      """Append the contents of another script (which should be created
    77      with temporary=True) to this one."""
    78      self.script.extend(other.script)
    79  
    80    def AssertOemProperty(self, name, values, oem_no_mount):
    81      """Assert that a property on the OEM paritition matches allowed values."""
    82      if not name:
    83        raise ValueError("must specify an OEM property")
    84      if not values:
    85        raise ValueError("must specify the OEM value")
    86  
    87      if oem_no_mount:
    88        get_prop_command = 'getprop("%s")' % name
    89      else:
    90        get_prop_command = 'file_getprop("/oem/oem.prop", "%s")' % name
    91  
    92      cmd = ''
    93      for value in values:
    94        cmd += '%s == "%s" || ' % (get_prop_command, value)
    95      cmd += (
    96          'abort("E{code}: This package expects the value \\"{values}\\" for '
    97          '\\"{name}\\"; this has value \\"" + '
    98          '{get_prop_command} + "\\".");').format(
    99              code=common.ErrorCode.OEM_PROP_MISMATCH,
   100              get_prop_command=get_prop_command, name=name,
   101              values='\\" or \\"'.join(values))
   102      self.script.append(cmd)
   103  
   104    def AssertSomeFingerprint(self, *fp):
   105      """Assert that the current recovery build fingerprint is one of *fp."""
   106      if not fp:
   107        raise ValueError("must specify some fingerprints")
   108      cmd = (' ||\n    '.join([('getprop("ro.build.fingerprint") == "%s"') % i
   109                               for i in fp]) +
   110             ' ||\n    abort("E%d: Package expects build fingerprint of %s; '
   111             'this device has " + getprop("ro.build.fingerprint") + ".");') % (
   112                 common.ErrorCode.FINGERPRINT_MISMATCH, " or ".join(fp))
   113      self.script.append(cmd)
   114  
   115    def AssertSomeThumbprint(self, *fp):
   116      """Assert that the current recovery build thumbprint is one of *fp."""
   117      if not fp:
   118        raise ValueError("must specify some thumbprints")
   119      cmd = (' ||\n    '.join([('getprop("ro.build.thumbprint") == "%s"') % i
   120                               for i in fp]) +
   121             ' ||\n    abort("E%d: Package expects build thumbprint of %s; this '
   122             'device has " + getprop("ro.build.thumbprint") + ".");') % (
   123                 common.ErrorCode.THUMBPRINT_MISMATCH, " or ".join(fp))
   124      self.script.append(cmd)
   125  
   126    def AssertFingerprintOrThumbprint(self, fp, tp):
   127      """Assert that the current recovery build fingerprint is fp, or thumbprint
   128         is tp."""
   129      cmd = ('getprop("ro.build.fingerprint") == "{fp}" ||\n'
   130             '    getprop("ro.build.thumbprint") == "{tp}" ||\n'
   131             '    abort("Package expects build fingerprint of {fp} or '
   132             'thumbprint of {tp}; this device has a fingerprint of " '
   133             '+ getprop("ro.build.fingerprint") + " and a thumbprint of " '
   134             '+ getprop("ro.build.thumbprint") + ".");').format(fp=fp, tp=tp)
   135      self.script.append(cmd)
   136  
   137    def AssertOlderBuild(self, timestamp, timestamp_text):
   138      """Assert that the build on the device is older (or the same as)
   139      the given timestamp."""
   140      self.script.append(
   141          ('(!less_than_int(%s, getprop("ro.build.date.utc"))) || '
   142           'abort("E%d: Can\'t install this package (%s) over newer '
   143           'build (" + getprop("ro.build.date") + ").");') % (timestamp,
   144               common.ErrorCode.OLDER_BUILD, timestamp_text))
   145  
   146    def AssertDevice(self, device):
   147      """Assert that the device identifier is the given string."""
   148      cmd = ('getprop("ro.product.device") == "%s" || '
   149             'abort("E%d: This package is for \\"%s\\" devices; '
   150             'this is a \\"" + getprop("ro.product.device") + "\\".");') % (
   151                 device, common.ErrorCode.DEVICE_MISMATCH, device)
   152      self.script.append(cmd)
   153  
   154    def AssertSomeBootloader(self, *bootloaders):
   155      """Asert that the bootloader version is one of *bootloaders."""
   156      cmd = ("assert(" +
   157             " ||\0".join(['getprop("ro.bootloader") == "%s"' % (b,)
   158                           for b in bootloaders]) +
   159             ");")
   160      self.script.append(self.WordWrap(cmd))
   161  
   162    def ShowProgress(self, frac, dur):
   163      """Update the progress bar, advancing it over 'frac' over the next
   164      'dur' seconds.  'dur' may be zero to advance it via SetProgress
   165      commands instead of by time."""
   166      self.script.append("show_progress(%f, %d);" % (frac, int(dur)))
   167  
   168    def SetProgress(self, frac):
   169      """Set the position of the progress bar within the chunk defined
   170      by the most recent ShowProgress call.  'frac' should be in
   171      [0,1]."""
   172      self.script.append("set_progress(%f);" % (frac,))
   173  
   174    def PatchCheck(self, filename, *sha1):
   175      """Check that the given file has one of the
   176      given *sha1 hashes, checking the version saved in cache if the
   177      file does not match."""
   178      self.script.append(
   179          'apply_patch_check("%s"' % (filename,) +
   180          "".join([', "%s"' % (i,) for i in sha1]) +
   181          ') || abort("E%d: \\"%s\\" has unexpected contents.");' % (
   182              common.ErrorCode.BAD_PATCH_FILE, filename))
   183  
   184    def Verify(self, filename):
   185      """Check that the given file has one of the
   186      given hashes (encoded in the filename)."""
   187      self.script.append(
   188          'apply_patch_check("{filename}") && '
   189          'ui_print("    Verified.") || '
   190          'ui_print("\\"{filename}\\" has unexpected contents.");'.format(
   191              filename=filename))
   192  
   193    def FileCheck(self, filename, *sha1):
   194      """Check that the given file has one of the
   195      given *sha1 hashes."""
   196      self.script.append('assert(sha1_check(read_file("%s")' % (filename,) +
   197                         "".join([', "%s"' % (i,) for i in sha1]) +
   198                         '));')
   199  
   200    def CacheFreeSpaceCheck(self, amount):
   201      """Check that there's at least 'amount' space that can be made
   202      available on /cache."""
   203      self._required_cache = max(self._required_cache, amount)
   204      self.script.append(('apply_patch_space(%d) || abort("E%d: Not enough free '
   205                          'space on /cache to apply patches.");') % (
   206                              amount,
   207                              common.ErrorCode.INSUFFICIENT_CACHE_SPACE))
   208  
   209    def Mount(self, mount_point, mount_options_by_format=""):
   210      """Mount the partition with the given mount_point.
   211        mount_options_by_format:
   212        [fs_type=option[,option]...[|fs_type=option[,option]...]...]
   213        where option is optname[=optvalue]
   214        E.g. ext4=barrier=1,nodelalloc,errors=panic|f2fs=errors=recover
   215      """
   216      fstab = self.fstab
   217      if fstab:
   218        p = fstab[mount_point]
   219        mount_dict = {}
   220        if mount_options_by_format is not None:
   221          for option in mount_options_by_format.split("|"):
   222            if "=" in option:
   223              key, value = option.split("=", 1)
   224              mount_dict[key] = value
   225        mount_flags = mount_dict.get(p.fs_type, "")
   226        if p.context is not None:
   227          mount_flags = p.context + ("," + mount_flags if mount_flags else "")
   228        self.script.append('mount("%s", "%s", "%s", "%s", "%s");' % (
   229            p.fs_type, common.PARTITION_TYPES[p.fs_type], p.device,
   230            p.mount_point, mount_flags))
   231        self.mounts.add(p.mount_point)
   232  
   233    def Comment(self, comment):
   234      """Write a comment into the update script."""
   235      self.script.append("")
   236      for i in comment.split("\n"):
   237        self.script.append("# " + i)
   238      self.script.append("")
   239  
   240    def Print(self, message):
   241      """Log a message to the screen (if the logs are visible)."""
   242      self.script.append('ui_print("%s");' % (message,))
   243  
   244    def TunePartition(self, partition, *options):
   245      fstab = self.fstab
   246      if fstab:
   247        p = fstab[partition]
   248        if p.fs_type not in ("ext2", "ext3", "ext4"):
   249          raise ValueError("Partition %s cannot be tuned\n" % (partition,))
   250      self.script.append(
   251          'tune2fs(' + "".join(['"%s", ' % (i,) for i in options]) +
   252          '"%s") || abort("E%d: Failed to tune partition %s");' % (
   253              p.device, common.ErrorCode.TUNE_PARTITION_FAILURE, partition))
   254  
   255    def FormatPartition(self, partition):
   256      """Format the given partition, specified by its mount point (eg,
   257      "/system")."""
   258  
   259      fstab = self.fstab
   260      if fstab:
   261        p = fstab[partition]
   262        self.script.append('format("%s", "%s", "%s", "%s", "%s");' %
   263                           (p.fs_type, common.PARTITION_TYPES[p.fs_type],
   264                            p.device, p.length, p.mount_point))
   265  
   266    def WipeBlockDevice(self, partition):
   267      if partition not in ("/system", "/vendor"):
   268        raise ValueError(("WipeBlockDevice doesn't work on %s\n") % (partition,))
   269      fstab = self.fstab
   270      size = self.info.get(partition.lstrip("/") + "_size", None)
   271      device = fstab[partition].device
   272  
   273      self.script.append('wipe_block_device("%s", %s);' % (device, size))
   274  
   275    def ApplyPatch(self, srcfile, tgtfile, tgtsize, tgtsha1, *patchpairs):
   276      """Apply binary patches (in *patchpairs) to the given srcfile to
   277      produce tgtfile (which may be "-" to indicate overwriting the
   278      source file."""
   279      if len(patchpairs) % 2 != 0 or len(patchpairs) == 0:
   280        raise ValueError("bad patches given to ApplyPatch")
   281      cmd = ['apply_patch("%s",\0"%s",\0%s,\0%d'
   282             % (srcfile, tgtfile, tgtsha1, tgtsize)]
   283      for i in range(0, len(patchpairs), 2):
   284        cmd.append(',\0%s,\0package_extract_file("%s")' % patchpairs[i:i+2])
   285      cmd.append(') ||\n    abort("E%d: Failed to apply patch to %s");' % (
   286          common.ErrorCode.APPLY_PATCH_FAILURE, srcfile))
   287      cmd = "".join(cmd)
   288      self.script.append(self.WordWrap(cmd))
   289  
   290    def WriteRawImage(self, mount_point, fn, mapfn=None):
   291      """Write the given package file into the partition for the given
   292      mount point."""
   293  
   294      fstab = self.fstab
   295      if fstab:
   296        p = fstab[mount_point]
   297        partition_type = common.PARTITION_TYPES[p.fs_type]
   298        args = {'device': p.device, 'fn': fn}
   299        if partition_type == "EMMC":
   300          if mapfn:
   301            args["map"] = mapfn
   302            self.script.append(
   303                'package_extract_file("%(fn)s", "%(device)s", "%(map)s");' % args)
   304          else:
   305            self.script.append(
   306                'package_extract_file("%(fn)s", "%(device)s");' % args)
   307        else:
   308          raise ValueError(
   309              "don't know how to write \"%s\" partitions" % p.fs_type)
   310  
   311    def AppendExtra(self, extra):
   312      """Append text verbatim to the output script."""
   313      self.script.append(extra)
   314  
   315    def Unmount(self, mount_point):
   316      self.script.append('unmount("%s");' % mount_point)
   317      self.mounts.remove(mount_point)
   318  
   319    def UnmountAll(self):
   320      for p in sorted(self.mounts):
   321        self.script.append('unmount("%s");' % (p,))
   322      self.mounts = set()
   323  
   324    def AddToZip(self, input_zip, output_zip, input_path=None):
   325      """Write the accumulated script to the output_zip file.  input_zip
   326      is used as the source for the 'updater' binary needed to run
   327      script.  If input_path is not None, it will be used as a local
   328      path for the binary instead of input_zip."""
   329  
   330      self.UnmountAll()
   331  
   332      common.ZipWriteStr(output_zip, "META-INF/com/google/android/updater-script",
   333                         "\n".join(self.script) + "\n")
   334  
   335      if input_path is None:
   336        data = input_zip.read("OTA/bin/updater")
   337      else:
   338        data = open(input_path, "rb").read()
   339      common.ZipWriteStr(output_zip, "META-INF/com/google/android/update-binary",
   340                         data, perms=0o755)