Home | History | Annotate | Download | only in releasetools
      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 os
     16 import re
     17 
     18 import common
     19 
     20 class EdifyGenerator(object):
     21   """Class to generate scripts in the 'edify' recovery script language
     22   used from donut onwards."""
     23 
     24   def __init__(self, version, info):
     25     self.script = []
     26     self.mounts = set()
     27     self.version = version
     28     self.info = info
     29 
     30   def MakeTemporary(self):
     31     """Make a temporary script object whose commands can latter be
     32     appended to the parent script with AppendScript().  Used when the
     33     caller wants to generate script commands out-of-order."""
     34     x = EdifyGenerator(self.version, self.info)
     35     x.mounts = self.mounts
     36     return x
     37 
     38   @staticmethod
     39   def _WordWrap(cmd, linelen=80):
     40     """'cmd' should be a function call with null characters after each
     41     parameter (eg, "somefun(foo,\0bar,\0baz)").  This function wraps cmd
     42     to a given line length, replacing nulls with spaces and/or newlines
     43     to format it nicely."""
     44     indent = cmd.index("(")+1
     45     out = []
     46     first = True
     47     x = re.compile("^(.{,%d})\0" % (linelen-indent,))
     48     while True:
     49       if not first:
     50         out.append(" " * indent)
     51       first = False
     52       m = x.search(cmd)
     53       if not m:
     54         parts = cmd.split("\0", 1)
     55         out.append(parts[0]+"\n")
     56         if len(parts) == 1:
     57           break
     58         else:
     59           cmd = parts[1]
     60           continue
     61       out.append(m.group(1)+"\n")
     62       cmd = cmd[m.end():]
     63 
     64     return "".join(out).replace("\0", " ").rstrip("\n")
     65 
     66   def AppendScript(self, other):
     67     """Append the contents of another script (which should be created
     68     with temporary=True) to this one."""
     69     self.script.extend(other.script)
     70 
     71   def AssertSomeFingerprint(self, *fp):
     72     """Assert that the current system build fingerprint is one of *fp."""
     73     if not fp:
     74       raise ValueError("must specify some fingerprints")
     75     cmd = (
     76            ' ||\n    '.join([('file_getprop("/system/build.prop", '
     77                          '"ro.build.fingerprint") == "%s"')
     78                         % i for i in fp]) +
     79            ' ||\n    abort("Package expects build fingerprint of %s; this '
     80            'device has " + getprop("ro.build.fingerprint") + ".");'
     81            ) % (" or ".join(fp),)
     82     self.script.append(cmd)
     83 
     84   def AssertOlderBuild(self, timestamp, timestamp_text):
     85     """Assert that the build on the device is older (or the same as)
     86     the given timestamp."""
     87     self.script.append(
     88         ('(!less_than_int(%s, getprop("ro.build.date.utc"))) || '
     89          'abort("Can\'t install this package (%s) over newer '
     90          'build (" + getprop("ro.build.date") + ").");'
     91          ) % (timestamp, timestamp_text))
     92 
     93   def AssertDevice(self, device):
     94     """Assert that the device identifier is the given string."""
     95     cmd = ('getprop("ro.product.device") == "%s" || '
     96            'abort("This package is for \\"%s\\" devices; '
     97            'this is a \\"" + getprop("ro.product.device") + "\\".");'
     98            ) % (device, device)
     99     self.script.append(cmd)
    100 
    101   def AssertSomeBootloader(self, *bootloaders):
    102     """Asert that the bootloader version is one of *bootloaders."""
    103     cmd = ("assert(" +
    104            " ||\0".join(['getprop("ro.bootloader") == "%s"' % (b,)
    105                          for b in bootloaders]) +
    106            ");")
    107     self.script.append(self._WordWrap(cmd))
    108 
    109   def ShowProgress(self, frac, dur):
    110     """Update the progress bar, advancing it over 'frac' over the next
    111     'dur' seconds.  'dur' may be zero to advance it via SetProgress
    112     commands instead of by time."""
    113     self.script.append("show_progress(%f, %d);" % (frac, int(dur)))
    114 
    115   def SetProgress(self, frac):
    116     """Set the position of the progress bar within the chunk defined
    117     by the most recent ShowProgress call.  'frac' should be in
    118     [0,1]."""
    119     self.script.append("set_progress(%f);" % (frac,))
    120 
    121   def PatchCheck(self, filename, *sha1):
    122     """Check that the given file (or MTD reference) has one of the
    123     given *sha1 hashes, checking the version saved in cache if the
    124     file does not match."""
    125     self.script.append(
    126         'apply_patch_check("%s"' % (filename,) +
    127         "".join([', "%s"' % (i,) for i in sha1]) +
    128         ') || abort("\\"%s\\" has unexpected contents.");' % (filename,))
    129 
    130   def FileCheck(self, filename, *sha1):
    131     """Check that the given file (or MTD reference) has one of the
    132     given *sha1 hashes."""
    133     self.script.append('assert(sha1_check(read_file("%s")' % (filename,) +
    134                        "".join([', "%s"' % (i,) for i in sha1]) +
    135                        '));')
    136 
    137   def CacheFreeSpaceCheck(self, amount):
    138     """Check that there's at least 'amount' space that can be made
    139     available on /cache."""
    140     self.script.append(('apply_patch_space(%d) || abort("Not enough free space '
    141                         'on /system to apply patches.");') % (amount,))
    142 
    143   def Mount(self, mount_point):
    144     """Mount the partition with the given mount_point."""
    145     fstab = self.info.get("fstab", None)
    146     if fstab:
    147       p = fstab[mount_point]
    148       self.script.append('mount("%s", "%s", "%s", "%s");' %
    149                          (p.fs_type, common.PARTITION_TYPES[p.fs_type],
    150                           p.device, p.mount_point))
    151       self.mounts.add(p.mount_point)
    152 
    153   def UnpackPackageDir(self, src, dst):
    154     """Unpack a given directory from the OTA package into the given
    155     destination directory."""
    156     self.script.append('package_extract_dir("%s", "%s");' % (src, dst))
    157 
    158   def Comment(self, comment):
    159     """Write a comment into the update script."""
    160     self.script.append("")
    161     for i in comment.split("\n"):
    162       self.script.append("# " + i)
    163     self.script.append("")
    164 
    165   def Print(self, message):
    166     """Log a message to the screen (if the logs are visible)."""
    167     self.script.append('ui_print("%s");' % (message,))
    168 
    169   def FormatPartition(self, partition):
    170     """Format the given partition, specified by its mount point (eg,
    171     "/system")."""
    172 
    173     reserve_size = 0
    174     fstab = self.info.get("fstab", None)
    175     if fstab:
    176       p = fstab[partition]
    177       self.script.append('format("%s", "%s", "%s", "%s", "%s");' %
    178                          (p.fs_type, common.PARTITION_TYPES[p.fs_type],
    179                           p.device, p.length, p.mount_point))
    180 
    181   def DeleteFiles(self, file_list):
    182     """Delete all files in file_list."""
    183     if not file_list: return
    184     cmd = "delete(" + ",\0".join(['"%s"' % (i,) for i in file_list]) + ");"
    185     self.script.append(self._WordWrap(cmd))
    186 
    187   def ApplyPatch(self, srcfile, tgtfile, tgtsize, tgtsha1, *patchpairs):
    188     """Apply binary patches (in *patchpairs) to the given srcfile to
    189     produce tgtfile (which may be "-" to indicate overwriting the
    190     source file."""
    191     if len(patchpairs) % 2 != 0 or len(patchpairs) == 0:
    192       raise ValueError("bad patches given to ApplyPatch")
    193     cmd = ['apply_patch("%s",\0"%s",\0%s,\0%d'
    194            % (srcfile, tgtfile, tgtsha1, tgtsize)]
    195     for i in range(0, len(patchpairs), 2):
    196       cmd.append(',\0%s, package_extract_file("%s")' % patchpairs[i:i+2])
    197     cmd.append(');')
    198     cmd = "".join(cmd)
    199     self.script.append(self._WordWrap(cmd))
    200 
    201   def WriteRawImage(self, mount_point, fn):
    202     """Write the given package file into the partition for the given
    203     mount point."""
    204 
    205     fstab = self.info["fstab"]
    206     if fstab:
    207       p = fstab[mount_point]
    208       partition_type = common.PARTITION_TYPES[p.fs_type]
    209       args = {'device': p.device, 'fn': fn}
    210       if partition_type == "MTD":
    211         self.script.append(
    212             'write_raw_image(package_extract_file("%(fn)s"), "%(device)s");'
    213             % args)
    214       elif partition_type == "EMMC":
    215         self.script.append(
    216             'package_extract_file("%(fn)s", "%(device)s");' % args)
    217       else:
    218         raise ValueError("don't know how to write \"%s\" partitions" % (p.fs_type,))
    219 
    220   def SetPermissions(self, fn, uid, gid, mode, selabel, capabilities):
    221     """Set file ownership and permissions."""
    222     if not self.info.get("use_set_metadata", False):
    223       self.script.append('set_perm(%d, %d, 0%o, "%s");' % (uid, gid, mode, fn))
    224     else:
    225       if capabilities is None: capabilities = "0x0"
    226       cmd = 'set_metadata("%s", "uid", %d, "gid", %d, "mode", 0%o, ' \
    227           '"capabilities", %s' % (fn, uid, gid, mode, capabilities)
    228       if selabel is not None:
    229         cmd += ', "selabel", "%s"' % ( selabel )
    230       cmd += ');'
    231       self.script.append(cmd)
    232 
    233   def SetPermissionsRecursive(self, fn, uid, gid, dmode, fmode, selabel, capabilities):
    234     """Recursively set path ownership and permissions."""
    235     if not self.info.get("use_set_metadata", False):
    236       self.script.append('set_perm_recursive(%d, %d, 0%o, 0%o, "%s");'
    237                          % (uid, gid, dmode, fmode, fn))
    238     else:
    239       if capabilities is None: capabilities = "0x0"
    240       cmd = 'set_metadata_recursive("%s", "uid", %d, "gid", %d, ' \
    241           '"dmode", 0%o, "fmode", 0%o, "capabilities", %s' \
    242           % (fn, uid, gid, dmode, fmode, capabilities)
    243       if selabel is not None:
    244         cmd += ', "selabel", "%s"' % ( selabel )
    245       cmd += ');'
    246       self.script.append(cmd)
    247 
    248   def MakeSymlinks(self, symlink_list):
    249     """Create symlinks, given a list of (dest, link) pairs."""
    250     by_dest = {}
    251     for d, l in symlink_list:
    252       by_dest.setdefault(d, []).append(l)
    253 
    254     for dest, links in sorted(by_dest.iteritems()):
    255       cmd = ('symlink("%s", ' % (dest,) +
    256              ",\0".join(['"' + i + '"' for i in sorted(links)]) + ");")
    257       self.script.append(self._WordWrap(cmd))
    258 
    259   def AppendExtra(self, extra):
    260     """Append text verbatim to the output script."""
    261     self.script.append(extra)
    262 
    263   def UnmountAll(self):
    264     for p in sorted(self.mounts):
    265       self.script.append('unmount("%s");' % (p,))
    266     self.mounts = set()
    267 
    268   def AddToZip(self, input_zip, output_zip, input_path=None):
    269     """Write the accumulated script to the output_zip file.  input_zip
    270     is used as the source for the 'updater' binary needed to run
    271     script.  If input_path is not None, it will be used as a local
    272     path for the binary instead of input_zip."""
    273 
    274     self.UnmountAll()
    275 
    276     common.ZipWriteStr(output_zip, "META-INF/com/google/android/updater-script",
    277                        "\n".join(self.script) + "\n")
    278 
    279     if input_path is None:
    280       data = input_zip.read("OTA/bin/updater")
    281     else:
    282       data = open(os.path.join(input_path, "updater")).read()
    283     common.ZipWriteStr(output_zip, "META-INF/com/google/android/update-binary",
    284                        data, perms=0755)
    285