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