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 RenameFile(self, srcfile, tgtfile):
    188     """Moves a file from one location to another."""
    189     if self.info.get("update_rename_support", False):
    190       self.script.append('rename("%s", "%s");' % (srcfile, tgtfile))
    191 
    192   def ApplyPatch(self, srcfile, tgtfile, tgtsize, tgtsha1, *patchpairs):
    193     """Apply binary patches (in *patchpairs) to the given srcfile to
    194     produce tgtfile (which may be "-" to indicate overwriting the
    195     source file."""
    196     if len(patchpairs) % 2 != 0 or len(patchpairs) == 0:
    197       raise ValueError("bad patches given to ApplyPatch")
    198     cmd = ['apply_patch("%s",\0"%s",\0%s,\0%d'
    199            % (srcfile, tgtfile, tgtsha1, tgtsize)]
    200     for i in range(0, len(patchpairs), 2):
    201       cmd.append(',\0%s, package_extract_file("%s")' % patchpairs[i:i+2])
    202     cmd.append(');')
    203     cmd = "".join(cmd)
    204     self.script.append(self._WordWrap(cmd))
    205 
    206   def WriteRawImage(self, mount_point, fn):
    207     """Write the given package file into the partition for the given
    208     mount point."""
    209 
    210     fstab = self.info["fstab"]
    211     if fstab:
    212       p = fstab[mount_point]
    213       partition_type = common.PARTITION_TYPES[p.fs_type]
    214       args = {'device': p.device, 'fn': fn}
    215       if partition_type == "MTD":
    216         self.script.append(
    217             'write_raw_image(package_extract_file("%(fn)s"), "%(device)s");'
    218             % args)
    219       elif partition_type == "EMMC":
    220         self.script.append(
    221             'package_extract_file("%(fn)s", "%(device)s");' % args)
    222       else:
    223         raise ValueError("don't know how to write \"%s\" partitions" % (p.fs_type,))
    224 
    225   def SetPermissions(self, fn, uid, gid, mode, selabel, capabilities):
    226     """Set file ownership and permissions."""
    227     if not self.info.get("use_set_metadata", False):
    228       self.script.append('set_perm(%d, %d, 0%o, "%s");' % (uid, gid, mode, fn))
    229     else:
    230       if capabilities is None: capabilities = "0x0"
    231       cmd = 'set_metadata("%s", "uid", %d, "gid", %d, "mode", 0%o, ' \
    232           '"capabilities", %s' % (fn, uid, gid, mode, capabilities)
    233       if selabel is not None:
    234         cmd += ', "selabel", "%s"' % ( selabel )
    235       cmd += ');'
    236       self.script.append(cmd)
    237 
    238   def SetPermissionsRecursive(self, fn, uid, gid, dmode, fmode, selabel, capabilities):
    239     """Recursively set path ownership and permissions."""
    240     if not self.info.get("use_set_metadata", False):
    241       self.script.append('set_perm_recursive(%d, %d, 0%o, 0%o, "%s");'
    242                          % (uid, gid, dmode, fmode, fn))
    243     else:
    244       if capabilities is None: capabilities = "0x0"
    245       cmd = 'set_metadata_recursive("%s", "uid", %d, "gid", %d, ' \
    246           '"dmode", 0%o, "fmode", 0%o, "capabilities", %s' \
    247           % (fn, uid, gid, dmode, fmode, capabilities)
    248       if selabel is not None:
    249         cmd += ', "selabel", "%s"' % ( selabel )
    250       cmd += ');'
    251       self.script.append(cmd)
    252 
    253   def MakeSymlinks(self, symlink_list):
    254     """Create symlinks, given a list of (dest, link) pairs."""
    255     by_dest = {}
    256     for d, l in symlink_list:
    257       by_dest.setdefault(d, []).append(l)
    258 
    259     for dest, links in sorted(by_dest.iteritems()):
    260       cmd = ('symlink("%s", ' % (dest,) +
    261              ",\0".join(['"' + i + '"' for i in sorted(links)]) + ");")
    262       self.script.append(self._WordWrap(cmd))
    263 
    264   def AppendExtra(self, extra):
    265     """Append text verbatim to the output script."""
    266     self.script.append(extra)
    267 
    268   def UnmountAll(self):
    269     for p in sorted(self.mounts):
    270       self.script.append('unmount("%s");' % (p,))
    271     self.mounts = set()
    272 
    273   def AddToZip(self, input_zip, output_zip, input_path=None):
    274     """Write the accumulated script to the output_zip file.  input_zip
    275     is used as the source for the 'updater' binary needed to run
    276     script.  If input_path is not None, it will be used as a local
    277     path for the binary instead of input_zip."""
    278 
    279     self.UnmountAll()
    280 
    281     common.ZipWriteStr(output_zip, "META-INF/com/google/android/updater-script",
    282                        "\n".join(self.script) + "\n")
    283 
    284     if input_path is None:
    285       data = input_zip.read("OTA/bin/updater")
    286     else:
    287       data = open(os.path.join(input_path, "updater")).read()
    288     common.ZipWriteStr(output_zip, "META-INF/com/google/android/update-binary",
    289                        data, perms=0755)
    290