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