edify_generator.py revision 5a48209541d5eed602bfb8e2c4ff51e31443daf2
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): 25 self.script = [] 26 self.mounts = set() 27 self.version = version 28 29 def MakeTemporary(self): 30 """Make a temporary script object whose commands can latter be 31 appended to the parent script with AppendScript(). Used when the 32 caller wants to generate script commands out-of-order.""" 33 x = EdifyGenerator(self.version) 34 x.mounts = self.mounts 35 return x 36 37 @staticmethod 38 def _WordWrap(cmd, linelen=80): 39 """'cmd' should be a function call with null characters after each 40 parameter (eg, "somefun(foo,\0bar,\0baz)"). This function wraps cmd 41 to a given line length, replacing nulls with spaces and/or newlines 42 to format it nicely.""" 43 indent = cmd.index("(")+1 44 out = [] 45 first = True 46 x = re.compile("^(.{,%d})\0" % (linelen-indent,)) 47 while True: 48 if not first: 49 out.append(" " * indent) 50 first = False 51 m = x.search(cmd) 52 if not m: 53 parts = cmd.split("\0", 1) 54 out.append(parts[0]+"\n") 55 if len(parts) == 1: 56 break 57 else: 58 cmd = parts[1] 59 continue 60 out.append(m.group(1)+"\n") 61 cmd = cmd[m.end():] 62 63 return "".join(out).replace("\0", " ").rstrip("\n") 64 65 def AppendScript(self, other): 66 """Append the contents of another script (which should be created 67 with temporary=True) to this one.""" 68 self.script.extend(other.script) 69 70 def AssertSomeFingerprint(self, *fp): 71 """Assert that the current system build fingerprint is one of *fp.""" 72 if not fp: 73 raise ValueError("must specify some fingerprints") 74 cmd = ('assert(' + 75 ' ||\0'.join([('file_getprop("/system/build.prop", ' 76 '"ro.build.fingerprint") == "%s"') 77 % i for i in fp]) + 78 ');') 79 self.script.append(self._WordWrap(cmd)) 80 81 def AssertOlderBuild(self, timestamp): 82 """Assert that the build on the device is older (or the same as) 83 the given timestamp.""" 84 self.script.append(('assert(!less_than_int(%s, ' 85 'getprop("ro.build.date.utc")));') % (timestamp,)) 86 87 def AssertDevice(self, device): 88 """Assert that the device identifier is the given string.""" 89 cmd = ('assert(getprop("ro.product.device") == "%s" ||\0' 90 'getprop("ro.build.product") == "%s");' % (device, device)) 91 self.script.append(self._WordWrap(cmd)) 92 93 def AssertSomeBootloader(self, *bootloaders): 94 """Asert that the bootloader version is one of *bootloaders.""" 95 cmd = ("assert(" + 96 " ||\0".join(['getprop("ro.bootloader") == "%s"' % (b,) 97 for b in bootloaders]) + 98 ");") 99 self.script.append(self._WordWrap(cmd)) 100 101 def ShowProgress(self, frac, dur): 102 """Update the progress bar, advancing it over 'frac' over the next 103 'dur' seconds. 'dur' may be zero to advance it via SetProgress 104 commands instead of by time.""" 105 self.script.append("show_progress(%f, %d);" % (frac, int(dur))) 106 107 def SetProgress(self, frac): 108 """Set the position of the progress bar within the chunk defined 109 by the most recent ShowProgress call. 'frac' should be in 110 [0,1].""" 111 self.script.append("set_progress(%f);" % (frac,)) 112 113 def PatchCheck(self, filename, *sha1): 114 """Check that the given file (or MTD reference) has one of the 115 given *sha1 hashes.""" 116 self.script.append('assert(sha1_check(read_file("%s")' % (filename,) + 117 "".join([', "%s"' % (i,) for i in sha1]) + 118 '));') 119 120 def CacheFreeSpaceCheck(self, amount): 121 """Check that there's at least 'amount' space that can be made 122 available on /cache.""" 123 self.script.append("assert(apply_patch_space(%d));" % (amount,)) 124 125 def Mount(self, kind, what, path): 126 """Mount the given 'what' at the given path. 'what' should be a 127 partition name if kind is "MTD", or a block device if kind is 128 "vfat". No other values of 'kind' are supported.""" 129 self.script.append('mount("%s", "%s", "%s");' % (kind, what, path)) 130 self.mounts.add(path) 131 132 def UnpackPackageDir(self, src, dst): 133 """Unpack a given directory from the OTA package into the given 134 destination directory.""" 135 self.script.append('package_extract_dir("%s", "%s");' % (src, dst)) 136 137 def Comment(self, comment): 138 """Write a comment into the update script.""" 139 self.script.append("") 140 for i in comment.split("\n"): 141 self.script.append("# " + i) 142 self.script.append("") 143 144 def Print(self, message): 145 """Log a message to the screen (if the logs are visible).""" 146 self.script.append('ui_print("%s");' % (message,)) 147 148 def FormatPartition(self, partition): 149 """Format the given MTD partition.""" 150 self.script.append('format("MTD", "%s");' % (partition,)) 151 152 def DeleteFiles(self, file_list): 153 """Delete all files in file_list.""" 154 if not file_list: return 155 cmd = "delete(" + ",\0".join(['"%s"' % (i,) for i in file_list]) + ");" 156 self.script.append(self._WordWrap(cmd)) 157 158 def ApplyPatch(self, srcfile, tgtfile, tgtsize, tgtsha1, *patchpairs): 159 """Apply binary patches (in *patchpairs) to the given srcfile to 160 produce tgtfile (which may be "-" to indicate overwriting the 161 source file.""" 162 if len(patchpairs) % 2 != 0 or len(patchpairs) == 0: 163 raise ValueError("bad patches given to ApplyPatch") 164 cmd = ['apply_patch("%s",\0"%s",\0%s,\0%d' 165 % (srcfile, tgtfile, tgtsha1, tgtsize)] 166 for i in range(0, len(patchpairs), 2): 167 cmd.append(',\0"%s:%s"' % patchpairs[i:i+2]) 168 cmd.append(');') 169 cmd = "".join(cmd) 170 self.script.append(self._WordWrap(cmd)) 171 172 def WriteFirmwareImage(self, kind, fn): 173 """Arrange to update the given firmware image (kind must be 174 "hboot" or "radio") when recovery finishes.""" 175 if self.version == 1: 176 self.script.append( 177 ('assert(package_extract_file("%(fn)s", "/tmp/%(kind)s.img"),\n' 178 ' write_firmware_image("/tmp/%(kind)s.img", "%(kind)s"));') 179 % {'kind': kind, 'fn': fn}) 180 else: 181 self.script.append( 182 'write_firmware_image("PACKAGE:%s", "%s");' % (fn, kind)) 183 184 def WriteRawImage(self, partition, fn): 185 """Write the given package file into the given MTD partition.""" 186 self.script.append( 187 ('assert(package_extract_file("%(fn)s", "/tmp/%(partition)s.img"),\n' 188 ' write_raw_image("/tmp/%(partition)s.img", "%(partition)s"),\n' 189 ' delete("/tmp/%(partition)s.img"));') 190 % {'partition': partition, 'fn': fn}) 191 192 def SetPermissions(self, fn, uid, gid, mode): 193 """Set file ownership and permissions.""" 194 self.script.append('set_perm(%d, %d, 0%o, "%s");' % (uid, gid, mode, fn)) 195 196 def SetPermissionsRecursive(self, fn, uid, gid, dmode, fmode): 197 """Recursively set path ownership and permissions.""" 198 self.script.append('set_perm_recursive(%d, %d, 0%o, 0%o, "%s");' 199 % (uid, gid, dmode, fmode, fn)) 200 201 def MakeSymlinks(self, symlink_list): 202 """Create symlinks, given a list of (dest, link) pairs.""" 203 by_dest = {} 204 for d, l in symlink_list: 205 by_dest.setdefault(d, []).append(l) 206 207 for dest, links in sorted(by_dest.iteritems()): 208 cmd = ('symlink("%s", ' % (dest,) + 209 ",\0".join(['"' + i + '"' for i in sorted(links)]) + ");") 210 self.script.append(self._WordWrap(cmd)) 211 212 def AppendExtra(self, extra): 213 """Append text verbatim to the output script.""" 214 self.script.append(extra) 215 216 def UnmountAll(self): 217 for p in sorted(self.mounts): 218 self.script.append('unmount("%s");' % (p,)) 219 self.mounts = set() 220 221 def AddToZip(self, input_zip, output_zip, input_path=None): 222 """Write the accumulated script to the output_zip file. input_zip 223 is used as the source for the 'updater' binary needed to run 224 script. If input_path is not None, it will be used as a local 225 path for the binary instead of input_zip.""" 226 227 self.UnmountAll() 228 229 common.ZipWriteStr(output_zip, "META-INF/com/google/android/updater-script", 230 "\n".join(self.script) + "\n") 231 232 if input_path is None: 233 data = input_zip.read("OTA/bin/updater") 234 else: 235 data = open(os.path.join(input_path, "updater")).read() 236 common.ZipWriteStr(output_zip, "META-INF/com/google/android/update-binary", 237 data, perms=0755) 238