| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 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 |  | 
| Doug Zongker | b4c7d32 | 2010-07-01 15:30:11 -0700 | [diff] [blame] | 24 | def __init__(self, version, info): | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 25 | self.script = [] | 
|  | 26 | self.mounts = set() | 
|  | 27 | self.version = version | 
| Doug Zongker | b4c7d32 | 2010-07-01 15:30:11 -0700 | [diff] [blame] | 28 | self.info = info | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 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.""" | 
| Doug Zongker | 6736998 | 2010-07-07 13:53:32 -0700 | [diff] [blame] | 34 | x = EdifyGenerator(self.version, self.info) | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 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 | 
| Doug Zongker | 881dd40 | 2009-09-20 14:03:55 -0700 | [diff] [blame] | 104 | 'dur' seconds.  'dur' may be zero to advance it via SetProgress | 
|  | 105 | commands instead of by time.""" | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 106 | self.script.append("show_progress(%f, %d);" % (frac, int(dur))) | 
|  | 107 |  | 
| Doug Zongker | 881dd40 | 2009-09-20 14:03:55 -0700 | [diff] [blame] | 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 |  | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 114 | def PatchCheck(self, filename, *sha1): | 
|  | 115 | """Check that the given file (or MTD reference) has one of the | 
| Doug Zongker | c8d446b | 2010-02-22 15:41:53 -0800 | [diff] [blame] | 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 | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 124 | given *sha1 hashes.""" | 
| Doug Zongker | 5a48209 | 2010-02-17 16:09:18 -0800 | [diff] [blame] | 125 | self.script.append('assert(sha1_check(read_file("%s")' % (filename,) + | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 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 |  | 
| Doug Zongker | 9ce0fb6 | 2010-09-20 18:04:41 -0700 | [diff] [blame] | 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");' % | 
| Doug Zongker | 96a57e7 | 2010-09-26 14:57:41 -0700 | [diff] [blame] | 140 | (p.fs_type, common.PARTITION_TYPES[p.fs_type], | 
| Doug Zongker | 9ce0fb6 | 2010-09-20 18:04:41 -0700 | [diff] [blame] | 141 | p.device, p.mount_point)) | 
|  | 142 | self.mounts.add(p.mount_point) | 
|  | 143 | else: | 
|  | 144 | what = mount_point.lstrip("/") | 
|  | 145 | what = self.info.get("partition_path", "") + what | 
|  | 146 | self.script.append('mount("%s", "%s", "%s", "%s");' % | 
|  | 147 | (self.info["fs_type"], self.info["partition_type"], | 
|  | 148 | what, mount_point)) | 
|  | 149 | self.mounts.add(mount_point) | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 150 |  | 
|  | 151 | def UnpackPackageDir(self, src, dst): | 
|  | 152 | """Unpack a given directory from the OTA package into the given | 
|  | 153 | destination directory.""" | 
|  | 154 | self.script.append('package_extract_dir("%s", "%s");' % (src, dst)) | 
|  | 155 |  | 
|  | 156 | def Comment(self, comment): | 
|  | 157 | """Write a comment into the update script.""" | 
|  | 158 | self.script.append("") | 
|  | 159 | for i in comment.split("\n"): | 
|  | 160 | self.script.append("# " + i) | 
|  | 161 | self.script.append("") | 
|  | 162 |  | 
|  | 163 | def Print(self, message): | 
|  | 164 | """Log a message to the screen (if the logs are visible).""" | 
|  | 165 | self.script.append('ui_print("%s");' % (message,)) | 
|  | 166 |  | 
|  | 167 | def FormatPartition(self, partition): | 
| Doug Zongker | 9ce0fb6 | 2010-09-20 18:04:41 -0700 | [diff] [blame] | 168 | """Format the given partition, specified by its mount point (eg, | 
|  | 169 | "/system").""" | 
|  | 170 |  | 
| Ken Sumrall | a67616a | 2011-01-19 17:08:54 -0800 | [diff] [blame] | 171 | reserve_size = 0 | 
| Doug Zongker | 9ce0fb6 | 2010-09-20 18:04:41 -0700 | [diff] [blame] | 172 | fstab = self.info.get("fstab", None) | 
|  | 173 | if fstab: | 
|  | 174 | p = fstab[partition] | 
| Ken Sumrall | a67616a | 2011-01-19 17:08:54 -0800 | [diff] [blame] | 175 | self.script.append('format("%s", "%s", "%s", "%s");' % | 
| Doug Zongker | 086cbb0 | 2011-02-17 15:54:20 -0800 | [diff] [blame] | 176 | (p.fs_type, common.PARTITION_TYPES[p.fs_type], | 
|  | 177 | p.device, p.length)) | 
| Doug Zongker | 9ce0fb6 | 2010-09-20 18:04:41 -0700 | [diff] [blame] | 178 | else: | 
|  | 179 | # older target-files without per-partition types | 
|  | 180 | partition = self.info.get("partition_path", "") + partition | 
| Ken Sumrall | a67616a | 2011-01-19 17:08:54 -0800 | [diff] [blame] | 181 | self.script.append('format("%s", "%s", "%s", "%s");' % | 
| Doug Zongker | 9ce0fb6 | 2010-09-20 18:04:41 -0700 | [diff] [blame] | 182 | (self.info["fs_type"], self.info["partition_type"], | 
| Ken Sumrall | a67616a | 2011-01-19 17:08:54 -0800 | [diff] [blame] | 183 | partition, reserve_size)) | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 184 |  | 
|  | 185 | def DeleteFiles(self, file_list): | 
|  | 186 | """Delete all files in file_list.""" | 
|  | 187 | if not file_list: return | 
|  | 188 | cmd = "delete(" + ",\0".join(['"%s"' % (i,) for i in file_list]) + ");" | 
|  | 189 | self.script.append(self._WordWrap(cmd)) | 
|  | 190 |  | 
|  | 191 | def ApplyPatch(self, srcfile, tgtfile, tgtsize, tgtsha1, *patchpairs): | 
|  | 192 | """Apply binary patches (in *patchpairs) to the given srcfile to | 
|  | 193 | produce tgtfile (which may be "-" to indicate overwriting the | 
|  | 194 | source file.""" | 
|  | 195 | if len(patchpairs) % 2 != 0 or len(patchpairs) == 0: | 
|  | 196 | raise ValueError("bad patches given to ApplyPatch") | 
|  | 197 | cmd = ['apply_patch("%s",\0"%s",\0%s,\0%d' | 
|  | 198 | % (srcfile, tgtfile, tgtsha1, tgtsize)] | 
|  | 199 | for i in range(0, len(patchpairs), 2): | 
| Doug Zongker | c8d446b | 2010-02-22 15:41:53 -0800 | [diff] [blame] | 200 | cmd.append(',\0%s, package_extract_file("%s")' % patchpairs[i:i+2]) | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 201 | cmd.append(');') | 
|  | 202 | cmd = "".join(cmd) | 
|  | 203 | self.script.append(self._WordWrap(cmd)) | 
|  | 204 |  | 
|  | 205 | def WriteFirmwareImage(self, kind, fn): | 
|  | 206 | """Arrange to update the given firmware image (kind must be | 
|  | 207 | "hboot" or "radio") when recovery finishes.""" | 
|  | 208 | if self.version == 1: | 
|  | 209 | self.script.append( | 
|  | 210 | ('assert(package_extract_file("%(fn)s", "/tmp/%(kind)s.img"),\n' | 
|  | 211 | '       write_firmware_image("/tmp/%(kind)s.img", "%(kind)s"));') | 
|  | 212 | % {'kind': kind, 'fn': fn}) | 
|  | 213 | else: | 
|  | 214 | self.script.append( | 
|  | 215 | 'write_firmware_image("PACKAGE:%s", "%s");' % (fn, kind)) | 
|  | 216 |  | 
| Doug Zongker | 9ce0fb6 | 2010-09-20 18:04:41 -0700 | [diff] [blame] | 217 | def WriteRawImage(self, mount_point, fn): | 
|  | 218 | """Write the given package file into the partition for the given | 
|  | 219 | mount point.""" | 
| Doug Zongker | b4c7d32 | 2010-07-01 15:30:11 -0700 | [diff] [blame] | 220 |  | 
| Doug Zongker | 9ce0fb6 | 2010-09-20 18:04:41 -0700 | [diff] [blame] | 221 | fstab = self.info["fstab"] | 
|  | 222 | if fstab: | 
|  | 223 | p = fstab[mount_point] | 
| Doug Zongker | 96a57e7 | 2010-09-26 14:57:41 -0700 | [diff] [blame] | 224 | partition_type = common.PARTITION_TYPES[p.fs_type] | 
| Doug Zongker | 9ce0fb6 | 2010-09-20 18:04:41 -0700 | [diff] [blame] | 225 | args = {'device': p.device, 'fn': fn} | 
|  | 226 | if partition_type == "MTD": | 
|  | 227 | self.script.append( | 
|  | 228 | ('assert(package_extract_file("%(fn)s", "/tmp/%(device)s.img"),\n' | 
|  | 229 | '       write_raw_image("/tmp/%(device)s.img", "%(device)s"),\n' | 
|  | 230 | '       delete("/tmp/%(device)s.img"));') % args) | 
|  | 231 | elif partition_type == "EMMC": | 
|  | 232 | self.script.append( | 
|  | 233 | 'package_extract_file("%(fn)s", "%(device)s");' % args) | 
|  | 234 | else: | 
|  | 235 | raise ValueError("don't know how to write \"%s\" partitions" % (p.fs_type,)) | 
| Doug Zongker | b4c7d32 | 2010-07-01 15:30:11 -0700 | [diff] [blame] | 236 | else: | 
| Doug Zongker | 9ce0fb6 | 2010-09-20 18:04:41 -0700 | [diff] [blame] | 237 | # backward compatibility with older target-files that lack recovery.fstab | 
|  | 238 | if self.info["partition_type"] == "MTD": | 
|  | 239 | self.script.append( | 
|  | 240 | ('assert(package_extract_file("%(fn)s", "/tmp/%(partition)s.img"),\n' | 
|  | 241 | '       write_raw_image("/tmp/%(partition)s.img", "%(partition)s"),\n' | 
|  | 242 | '       delete("/tmp/%(partition)s.img"));') | 
|  | 243 | % {'partition': partition, 'fn': fn}) | 
|  | 244 | elif self.info["partition_type"] == "EMMC": | 
|  | 245 | self.script.append( | 
|  | 246 | ('package_extract_file("%(fn)s", "%(dir)s%(partition)s");') | 
|  | 247 | % {'partition': partition, 'fn': fn, | 
|  | 248 | 'dir': self.info.get("partition_path", ""), | 
|  | 249 | }) | 
|  | 250 | else: | 
|  | 251 | raise ValueError("don't know how to write \"%s\" partitions" % | 
|  | 252 | (self.info["partition_type"],)) | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 253 |  | 
|  | 254 | def SetPermissions(self, fn, uid, gid, mode): | 
|  | 255 | """Set file ownership and permissions.""" | 
|  | 256 | self.script.append('set_perm(%d, %d, 0%o, "%s");' % (uid, gid, mode, fn)) | 
|  | 257 |  | 
|  | 258 | def SetPermissionsRecursive(self, fn, uid, gid, dmode, fmode): | 
|  | 259 | """Recursively set path ownership and permissions.""" | 
|  | 260 | self.script.append('set_perm_recursive(%d, %d, 0%o, 0%o, "%s");' | 
|  | 261 | % (uid, gid, dmode, fmode, fn)) | 
|  | 262 |  | 
|  | 263 | def MakeSymlinks(self, symlink_list): | 
|  | 264 | """Create symlinks, given a list of (dest, link) pairs.""" | 
|  | 265 | by_dest = {} | 
|  | 266 | for d, l in symlink_list: | 
|  | 267 | by_dest.setdefault(d, []).append(l) | 
|  | 268 |  | 
|  | 269 | for dest, links in sorted(by_dest.iteritems()): | 
|  | 270 | cmd = ('symlink("%s", ' % (dest,) + | 
|  | 271 | ",\0".join(['"' + i + '"' for i in sorted(links)]) + ");") | 
|  | 272 | self.script.append(self._WordWrap(cmd)) | 
|  | 273 |  | 
| Hristo Bojinov | 96be720 | 2010-08-02 10:26:17 -0700 | [diff] [blame] | 274 | def RetouchBinaries(self, file_list): | 
|  | 275 | """Execute the retouch instructions in files listed.""" | 
|  | 276 | cmd = ('retouch_binaries(' + | 
|  | 277 | ', '.join(['"' + i[0] + '", "' + i[1] + '"' for i in file_list]) + | 
|  | 278 | ');') | 
|  | 279 | self.script.append(self._WordWrap(cmd)) | 
|  | 280 |  | 
|  | 281 | def UndoRetouchBinaries(self, file_list): | 
|  | 282 | """Undo the retouching (retouch to zero offset).""" | 
|  | 283 | cmd = ('undo_retouch_binaries(' + | 
|  | 284 | ', '.join(['"' + i[0] + '", "' + i[1] + '"' for i in file_list]) + | 
|  | 285 | ');') | 
|  | 286 | self.script.append(self._WordWrap(cmd)) | 
|  | 287 |  | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 288 | def AppendExtra(self, extra): | 
|  | 289 | """Append text verbatim to the output script.""" | 
|  | 290 | self.script.append(extra) | 
|  | 291 |  | 
| Doug Zongker | 1483360 | 2010-02-02 13:12:04 -0800 | [diff] [blame] | 292 | def UnmountAll(self): | 
|  | 293 | for p in sorted(self.mounts): | 
|  | 294 | self.script.append('unmount("%s");' % (p,)) | 
|  | 295 | self.mounts = set() | 
|  | 296 |  | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 297 | def AddToZip(self, input_zip, output_zip, input_path=None): | 
|  | 298 | """Write the accumulated script to the output_zip file.  input_zip | 
|  | 299 | is used as the source for the 'updater' binary needed to run | 
|  | 300 | script.  If input_path is not None, it will be used as a local | 
|  | 301 | path for the binary instead of input_zip.""" | 
|  | 302 |  | 
| Doug Zongker | 1483360 | 2010-02-02 13:12:04 -0800 | [diff] [blame] | 303 | self.UnmountAll() | 
| Doug Zongker | c494d7c | 2009-06-18 08:43:44 -0700 | [diff] [blame] | 304 |  | 
|  | 305 | common.ZipWriteStr(output_zip, "META-INF/com/google/android/updater-script", | 
|  | 306 | "\n".join(self.script) + "\n") | 
|  | 307 |  | 
|  | 308 | if input_path is None: | 
|  | 309 | data = input_zip.read("OTA/bin/updater") | 
|  | 310 | else: | 
|  | 311 | data = open(os.path.join(input_path, "updater")).read() | 
|  | 312 | common.ZipWriteStr(output_zip, "META-INF/com/google/android/update-binary", | 
|  | 313 | data, perms=0755) |