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 = ('assert(' +
     76            ' ||\0'.join([('file_getprop("/system/build.prop", '
     77                          '"ro.build.fingerprint") == "%s"')
     78                         % i for i in fp]) +
     79            ');')
     80     self.script.append(self._WordWrap(cmd))
     81 
     82   def AssertOlderBuild(self, timestamp):
     83     """Assert that the build on the device is older (or the same as)
     84     the given timestamp."""
     85     self.script.append(('assert(!less_than_int(%s, '
     86                         'getprop("ro.build.date.utc")));') % (timestamp,))
     87 
     88   def AssertDevice(self, device):
     89     """Assert that the device identifier is the given string."""
     90     cmd = ('assert(getprop("ro.product.device") == "%s" ||\0'
     91            'getprop("ro.build.product") == "%s");' % (device, device))
     92     self.script.append(self._WordWrap(cmd))
     93 
     94   def AssertSomeBootloader(self, *bootloaders):
     95     """Asert that the bootloader version is one of *bootloaders."""
     96     cmd = ("assert(" +
     97            " ||\0".join(['getprop("ro.bootloader") == "%s"' % (b,)
     98                          for b in bootloaders]) +
     99            ");")
    100     self.script.append(self._WordWrap(cmd))
    101 
    102   def ShowProgress(self, frac, dur):
    103     """Update the progress bar, advancing it over 'frac' over the next
    104     'dur' seconds.  'dur' may be zero to advance it via SetProgress
    105     commands instead of by time."""
    106     self.script.append("show_progress(%f, %d);" % (frac, int(dur)))
    107 
    108   def SetProgress(self, frac):
    109     """Set the position of the progress bar within the chunk defined
    110     by the most recent ShowProgress call.  'frac' should be in
    111     [0,1]."""
    112     self.script.append("set_progress(%f);" % (frac,))
    113 
    114   def PatchCheck(self, filename, *sha1):
    115     """Check that the given file (or MTD reference) has one of the
    116     given *sha1 hashes, checking the version saved in cache if the
    117     file does not match."""
    118     self.script.append('assert(apply_patch_check("%s"' % (filename,) +
    119                        "".join([', "%s"' % (i,) for i in sha1]) +
    120                        '));')
    121 
    122   def FileCheck(self, filename, *sha1):
    123     """Check that the given file (or MTD reference) has one of the
    124     given *sha1 hashes."""
    125     self.script.append('assert(sha1_check(read_file("%s")' % (filename,) +
    126                        "".join([', "%s"' % (i,) for i in sha1]) +
    127                        '));')
    128 
    129   def CacheFreeSpaceCheck(self, amount):
    130     """Check that there's at least 'amount' space that can be made
    131     available on /cache."""
    132     self.script.append("assert(apply_patch_space(%d));" % (amount,))
    133 
    134   def Mount(self, mount_point):
    135     """Mount the partition with the given mount_point."""
    136     fstab = self.info.get("fstab", None)
    137     if fstab:
    138       p = fstab[mount_point]
    139       self.script.append('mount("%s", "%s", "%s", "%s");' %
    140                          (p.fs_type, common.PARTITION_TYPES[p.fs_type],
    141                           p.device, p.mount_point))
    142       self.mounts.add(p.mount_point)
    143 
    144   def UnpackPackageDir(self, src, dst):
    145     """Unpack a given directory from the OTA package into the given
    146     destination directory."""
    147     self.script.append('package_extract_dir("%s", "%s");' % (src, dst))
    148 
    149   def Comment(self, comment):
    150     """Write a comment into the update script."""
    151     self.script.append("")
    152     for i in comment.split("\n"):
    153       self.script.append("# " + i)
    154     self.script.append("")
    155 
    156   def Print(self, message):
    157     """Log a message to the screen (if the logs are visible)."""
    158     self.script.append('ui_print("%s");' % (message,))
    159 
    160   def FormatPartition(self, partition):
    161     """Format the given partition, specified by its mount point (eg,
    162     "/system")."""
    163 
    164     fstab = self.info.get("fstab", None)
    165     if fstab:
    166       p = fstab[partition]
    167       self.script.append('format("%s", "%s", "%s");' %
    168                          (p.fs_type, common.PARTITION_TYPES[p.fs_type], p.device))
    169 
    170   def DeleteFiles(self, file_list):
    171     """Delete all files in file_list."""
    172     if not file_list: return
    173     cmd = "delete(" + ",\0".join(['"%s"' % (i,) for i in file_list]) + ");"
    174     self.script.append(self._WordWrap(cmd))
    175 
    176   def ApplyPatch(self, srcfile, tgtfile, tgtsize, tgtsha1, *patchpairs):
    177     """Apply binary patches (in *patchpairs) to the given srcfile to
    178     produce tgtfile (which may be "-" to indicate overwriting the
    179     source file."""
    180     if len(patchpairs) % 2 != 0 or len(patchpairs) == 0:
    181       raise ValueError("bad patches given to ApplyPatch")
    182     cmd = ['apply_patch("%s",\0"%s",\0%s,\0%d'
    183            % (srcfile, tgtfile, tgtsha1, tgtsize)]
    184     for i in range(0, len(patchpairs), 2):
    185       cmd.append(',\0%s, package_extract_file("%s")' % patchpairs[i:i+2])
    186     cmd.append(');')
    187     cmd = "".join(cmd)
    188     self.script.append(self._WordWrap(cmd))
    189 
    190   def WriteFirmwareImage(self, kind, fn):
    191     """Arrange to update the given firmware image (kind must be
    192     "hboot" or "radio") when recovery finishes."""
    193     if self.version == 1:
    194       self.script.append(
    195           ('assert(package_extract_file("%(fn)s", "/tmp/%(kind)s.img"),\n'
    196            '       write_firmware_image("/tmp/%(kind)s.img", "%(kind)s"));')
    197           % {'kind': kind, 'fn': fn})
    198     else:
    199       self.script.append(
    200           'write_firmware_image("PACKAGE:%s", "%s");' % (fn, kind))
    201 
    202   def WriteRawImage(self, mount_point, fn):
    203     """Write the given package file into the partition for the given
    204     mount point."""
    205 
    206     fstab = self.info["fstab"]
    207     if fstab:
    208       p = fstab[mount_point]
    209       partition_type = common.PARTITION_TYPES[p.fs_type]
    210       args = {'device': p.device, 'fn': fn}
    211       if partition_type == "MTD":
    212         self.script.append(
    213             ('assert(package_extract_file("%(fn)s", "/tmp/%(device)s.img"),\n'
    214              '       write_raw_image("/tmp/%(device)s.img", "%(device)s"),\n'
    215              '       delete("/tmp/%(device)s.img"));') % args)
    216       elif partition_type == "EMMC":
    217         self.script.append(
    218             'package_extract_file("%(fn)s", "%(device)s");' % args)
    219       else:
    220         raise ValueError("don't know how to write \"%s\" partitions" % (p.fs_type,))
    221 
    222   def SetPermissions(self, fn, uid, gid, mode):
    223     """Set file ownership and permissions."""
    224     self.script.append('set_perm(%d, %d, 0%o, "%s");' % (uid, gid, mode, fn))
    225 
    226   def SetPermissionsRecursive(self, fn, uid, gid, dmode, fmode):
    227     """Recursively set path ownership and permissions."""
    228     self.script.append('set_perm_recursive(%d, %d, 0%o, 0%o, "%s");'
    229                        % (uid, gid, dmode, fmode, fn))
    230 
    231   def MakeSymlinks(self, symlink_list):
    232     """Create symlinks, given a list of (dest, link) pairs."""
    233     by_dest = {}
    234     for d, l in symlink_list:
    235       by_dest.setdefault(d, []).append(l)
    236 
    237     for dest, links in sorted(by_dest.iteritems()):
    238       cmd = ('symlink("%s", ' % (dest,) +
    239              ",\0".join(['"' + i + '"' for i in sorted(links)]) + ");")
    240       self.script.append(self._WordWrap(cmd))
    241 
    242   def AppendExtra(self, extra):
    243     """Append text verbatim to the output script."""
    244     self.script.append(extra)
    245 
    246   def UnmountAll(self):
    247     for p in sorted(self.mounts):
    248       self.script.append('unmount("%s");' % (p,))
    249     self.mounts = set()
    250 
    251   def AddToZip(self, input_zip, output_zip, input_path=None):
    252     """Write the accumulated script to the output_zip file.  input_zip
    253     is used as the source for the 'updater' binary needed to run
    254     script.  If input_path is not None, it will be used as a local
    255     path for the binary instead of input_zip."""
    256 
    257     self.UnmountAll()
    258 
    259     common.ZipWriteStr(output_zip, "META-INF/com/google/android/updater-script",
    260                        "\n".join(self.script) + "\n")
    261 
    262     if input_path is None:
    263       data = input_zip.read("OTA/bin/updater")
    264     else:
    265       data = open(os.path.join(input_path, "updater")).read()
    266     common.ZipWriteStr(output_zip, "META-INF/com/google/android/update-binary",
    267                        data, perms=0755)
    268