• 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 
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