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");' % 169 (p.fs_type, common.PARTITION_TYPES[p.fs_type], 170 p.device, p.length)) 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 RetouchBinaries(self, file_list): 232 """Execute the retouch instructions in files listed.""" 233 cmd = ('retouch_binaries(' + 234 ', '.join(['"' + i[0] + '", "' + i[1] + '"' for i in file_list]) + 235 ');') 236 self.script.append(self._WordWrap(cmd)) 237 238 def UndoRetouchBinaries(self, file_list): 239 """Undo the retouching (retouch to zero offset).""" 240 cmd = ('undo_retouch_binaries(' + 241 ', '.join(['"' + i[0] + '", "' + i[1] + '"' for i in file_list]) + 242 ');') 243 self.script.append(self._WordWrap(cmd)) 244 245 def AppendExtra(self, extra): 246 """Append text verbatim to the output script.""" 247 self.script.append(extra) 248 249 def UnmountAll(self): 250 for p in sorted(self.mounts): 251 self.script.append('unmount("%s");' % (p,)) 252 self.mounts = set() 253 254 def AddToZip(self, input_zip, output_zip, input_path=None): 255 """Write the accumulated script to the output_zip file. input_zip 256 is used as the source for the 'updater' binary needed to run 257 script. If input_path is not None, it will be used as a local 258 path for the binary instead of input_zip.""" 259 260 self.UnmountAll() 261 262 common.ZipWriteStr(output_zip, "META-INF/com/google/android/updater-script", 263 "\n".join(self.script) + "\n") 264 265 if input_path is None: 266 data = input_zip.read("OTA/bin/updater") 267 else: 268 data = open(os.path.join(input_path, "updater")).read() 269 common.ZipWriteStr(output_zip, "META-INF/com/google/android/update-binary", 270 data, perms=0755) 271