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     reserve_size = 0
    165     fstab = self.info.get("fstab", None)
    166     if fstab:
    167       p = fstab[partition]
    168       self.script.append('format("%s", "%s", "%s", "%s", "%s");' %
    169                          (p.fs_type, common.PARTITION_TYPES[p.fs_type],
    170                           p.device, p.length, p.mount_point))
    171 
    172   def DeleteFiles(self, file_list):
    173     """Delete all files in file_list."""
    174     if not file_list: return
    175     cmd = "delete(" + ",\0".join(['"%s"' % (i,) for i in file_list]) + ");"
    176     self.script.append(self._WordWrap(cmd))
    177 
    178   def ApplyPatch(self, srcfile, tgtfile, tgtsize, tgtsha1, *patchpairs):
    179     """Apply binary patches (in *patchpairs) to the given srcfile to
    180     produce tgtfile (which may be "-" to indicate overwriting the
    181     source file."""
    182     if len(patchpairs) % 2 != 0 or len(patchpairs) == 0:
    183       raise ValueError("bad patches given to ApplyPatch")
    184     cmd = ['apply_patch("%s",\0"%s",\0%s,\0%d'
    185            % (srcfile, tgtfile, tgtsha1, tgtsize)]
    186     for i in range(0, len(patchpairs), 2):
    187       cmd.append(',\0%s, package_extract_file("%s")' % patchpairs[i:i+2])
    188     cmd.append(');')
    189     cmd = "".join(cmd)
    190     self.script.append(self._WordWrap(cmd))
    191 
    192   def WriteRawImage(self, mount_point, fn):
    193     """Write the given package file into the partition for the given
    194     mount point."""
    195 
    196     fstab = self.info["fstab"]
    197     if fstab:
    198       p = fstab[mount_point]
    199       partition_type = common.PARTITION_TYPES[p.fs_type]
    200       args = {'device': p.device, 'fn': fn}
    201       if partition_type == "MTD":
    202         self.script.append(
    203             'write_raw_image(package_extract_file("%(fn)s"), "%(device)s");'
    204             % args)
    205       elif partition_type == "EMMC":
    206         self.script.append(
    207             'package_extract_file("%(fn)s", "%(device)s");' % args)
    208       else:
    209         raise ValueError("don't know how to write \"%s\" partitions" % (p.fs_type,))
    210 
    211   def SetPermissions(self, fn, uid, gid, mode):
    212     """Set file ownership and permissions."""
    213     self.script.append('set_perm(%d, %d, 0%o, "%s");' % (uid, gid, mode, fn))
    214 
    215   def SetPermissionsRecursive(self, fn, uid, gid, dmode, fmode):
    216     """Recursively set path ownership and permissions."""
    217     self.script.append('set_perm_recursive(%d, %d, 0%o, 0%o, "%s");'
    218                        % (uid, gid, dmode, fmode, fn))
    219 
    220   def MakeSymlinks(self, symlink_list):
    221     """Create symlinks, given a list of (dest, link) pairs."""
    222     by_dest = {}
    223     for d, l in symlink_list:
    224       by_dest.setdefault(d, []).append(l)
    225 
    226     for dest, links in sorted(by_dest.iteritems()):
    227       cmd = ('symlink("%s", ' % (dest,) +
    228              ",\0".join(['"' + i + '"' for i in sorted(links)]) + ");")
    229       self.script.append(self._WordWrap(cmd))
    230 
    231   def AppendExtra(self, extra):
    232     """Append text verbatim to the output script."""
    233     self.script.append(extra)
    234 
    235   def UnmountAll(self):
    236     for p in sorted(self.mounts):
    237       self.script.append('unmount("%s");' % (p,))
    238     self.mounts = set()
    239 
    240   def AddToZip(self, input_zip, output_zip, input_path=None):
    241     """Write the accumulated script to the output_zip file.  input_zip
    242     is used as the source for the 'updater' binary needed to run
    243     script.  If input_path is not None, it will be used as a local
    244     path for the binary instead of input_zip."""
    245 
    246     self.UnmountAll()
    247 
    248     common.ZipWriteStr(output_zip, "META-INF/com/google/android/updater-script",
    249                        "\n".join(self.script) + "\n")
    250 
    251     if input_path is None:
    252       data = input_zip.read("OTA/bin/updater")
    253     else:
    254       data = open(os.path.join(input_path, "updater")).read()
    255     common.ZipWriteStr(output_zip, "META-INF/com/google/android/update-binary",
    256                        data, perms=0755)
    257