blob: 7ed85fecc95d8f86a797c0a4ac041527cf23eb36 [file] [log] [blame]
Doug Zongkerc494d7c2009-06-18 08:43:44 -07001# 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
Doug Zongkerc494d7c2009-06-18 08:43:44 -070015import re
16
17import common
18
19class EdifyGenerator(object):
20 """Class to generate scripts in the 'edify' recovery script language
21 used from donut onwards."""
22
Tao Bao34b47bf2015-06-22 19:17:41 -070023 def __init__(self, version, info, fstab=None):
Doug Zongkerc494d7c2009-06-18 08:43:44 -070024 self.script = []
25 self.mounts = set()
Tao Baod8d14be2016-02-04 14:26:02 -080026 self._required_cache = 0
Doug Zongkerc494d7c2009-06-18 08:43:44 -070027 self.version = version
Doug Zongkerb4c7d322010-07-01 15:30:11 -070028 self.info = info
Tao Bao34b47bf2015-06-22 19:17:41 -070029 if fstab is None:
30 self.fstab = self.info.get("fstab", None)
31 else:
32 self.fstab = fstab
Doug Zongkerc494d7c2009-06-18 08:43:44 -070033
Tao Baod8d14be2016-02-04 14:26:02 -080034 @property
35 def required_cache(self):
36 """Return the minimum cache size to apply the update."""
37 return self._required_cache
38
Doug Zongkerc494d7c2009-06-18 08:43:44 -070039 @staticmethod
Dan Albert8b72aef2015-03-23 19:13:21 -070040 def WordWrap(cmd, linelen=80):
Doug Zongkerc494d7c2009-06-18 08:43:44 -070041 """'cmd' should be a function call with null characters after each
42 parameter (eg, "somefun(foo,\0bar,\0baz)"). This function wraps cmd
43 to a given line length, replacing nulls with spaces and/or newlines
44 to format it nicely."""
45 indent = cmd.index("(")+1
46 out = []
47 first = True
48 x = re.compile("^(.{,%d})\0" % (linelen-indent,))
49 while True:
50 if not first:
51 out.append(" " * indent)
52 first = False
53 m = x.search(cmd)
54 if not m:
55 parts = cmd.split("\0", 1)
56 out.append(parts[0]+"\n")
57 if len(parts) == 1:
58 break
59 else:
60 cmd = parts[1]
61 continue
62 out.append(m.group(1)+"\n")
63 cmd = cmd[m.end():]
64
65 return "".join(out).replace("\0", " ").rstrip("\n")
66
67 def AppendScript(self, other):
68 """Append the contents of another script (which should be created
69 with temporary=True) to this one."""
70 self.script.extend(other.script)
71
Tao Bao481bab82017-12-21 11:23:09 -080072 def AssertOemProperty(self, name, values, oem_no_mount):
Alain Vongsouvanh7f804ba2017-02-16 13:06:55 -080073 """Assert that a property on the OEM paritition matches allowed values."""
Michael Runge6e836112014-04-15 17:40:21 -070074 if not name:
75 raise ValueError("must specify an OEM property")
Alain Vongsouvanh7f804ba2017-02-16 13:06:55 -080076 if not values:
Michael Runge6e836112014-04-15 17:40:21 -070077 raise ValueError("must specify the OEM value")
Tao Bao481bab82017-12-21 11:23:09 -080078
79 if oem_no_mount:
Alain Vongsouvanh7f804ba2017-02-16 13:06:55 -080080 get_prop_command = 'getprop("%s")' % name
Tao Bao8608cde2016-02-25 19:49:55 -080081 else:
Alain Vongsouvanh7f804ba2017-02-16 13:06:55 -080082 get_prop_command = 'file_getprop("/oem/oem.prop", "%s")' % name
83
84 cmd = ''
85 for value in values:
86 cmd += '%s == "%s" || ' % (get_prop_command, value)
87 cmd += (
88 'abort("E{code}: This package expects the value \\"{values}\\" for '
89 '\\"{name}\\"; this has value \\"" + '
90 '{get_prop_command} + "\\".");').format(
91 code=common.ErrorCode.OEM_PROP_MISMATCH,
92 get_prop_command=get_prop_command, name=name,
93 values='\\" or \\"'.join(values))
Michael Runge6e836112014-04-15 17:40:21 -070094 self.script.append(cmd)
95
Doug Zongkerc494d7c2009-06-18 08:43:44 -070096 def AssertSomeFingerprint(self, *fp):
Doug Zongkeraf845252014-05-09 08:29:05 -070097 """Assert that the current recovery build fingerprint is one of *fp."""
Doug Zongkerc494d7c2009-06-18 08:43:44 -070098 if not fp:
99 raise ValueError("must specify some fingerprints")
Dan Albert8b72aef2015-03-23 19:13:21 -0700100 cmd = (' ||\n '.join([('getprop("ro.build.fingerprint") == "%s"') % i
101 for i in fp]) +
Tianjie Xu209db462016-05-24 17:34:52 -0700102 ' ||\n abort("E%d: Package expects build fingerprint of %s; '
103 'this device has " + getprop("ro.build.fingerprint") + ".");') % (
104 common.ErrorCode.FINGERPRINT_MISMATCH, " or ".join(fp))
Doug Zongker0d92f1f2013-06-03 12:07:12 -0700105 self.script.append(cmd)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700106
Michael Runge6e836112014-04-15 17:40:21 -0700107 def AssertSomeThumbprint(self, *fp):
Doug Zongkeraf845252014-05-09 08:29:05 -0700108 """Assert that the current recovery build thumbprint is one of *fp."""
Geremy Condra36bd3652014-02-06 19:45:10 -0800109 if not fp:
Michael Runge6e836112014-04-15 17:40:21 -0700110 raise ValueError("must specify some thumbprints")
Dan Albert8b72aef2015-03-23 19:13:21 -0700111 cmd = (' ||\n '.join([('getprop("ro.build.thumbprint") == "%s"') % i
112 for i in fp]) +
Tianjie Xu209db462016-05-24 17:34:52 -0700113 ' ||\n abort("E%d: Package expects build thumbprint of %s; this '
Dan Albert8b72aef2015-03-23 19:13:21 -0700114 'device has " + getprop("ro.build.thumbprint") + ".");') % (
Tianjie Xu209db462016-05-24 17:34:52 -0700115 common.ErrorCode.THUMBPRINT_MISMATCH, " or ".join(fp))
Geremy Condra36bd3652014-02-06 19:45:10 -0800116 self.script.append(cmd)
117
Tao Bao3e30d972016-03-15 13:20:19 -0700118 def AssertFingerprintOrThumbprint(self, fp, tp):
119 """Assert that the current recovery build fingerprint is fp, or thumbprint
120 is tp."""
121 cmd = ('getprop("ro.build.fingerprint") == "{fp}" ||\n'
122 ' getprop("ro.build.thumbprint") == "{tp}" ||\n'
123 ' abort("Package expects build fingerprint of {fp} or '
124 'thumbprint of {tp}; this device has a fingerprint of " '
Tao Baod2d01e52017-06-05 12:25:46 -0700125 '+ getprop("ro.build.fingerprint") + " and a thumbprint of " '
Tao Bao3e30d972016-03-15 13:20:19 -0700126 '+ getprop("ro.build.thumbprint") + ".");').format(fp=fp, tp=tp)
127 self.script.append(cmd)
128
Doug Zongker0d92f1f2013-06-03 12:07:12 -0700129 def AssertOlderBuild(self, timestamp, timestamp_text):
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700130 """Assert that the build on the device is older (or the same as)
131 the given timestamp."""
Doug Zongker0d92f1f2013-06-03 12:07:12 -0700132 self.script.append(
133 ('(!less_than_int(%s, getprop("ro.build.date.utc"))) || '
Tianjie Xu209db462016-05-24 17:34:52 -0700134 'abort("E%d: Can\'t install this package (%s) over newer '
Tao Bao76def242017-11-21 09:25:31 -0800135 'build (" + getprop("ro.build.date") + ").");') % (
136 timestamp, common.ErrorCode.OLDER_BUILD, timestamp_text))
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700137
138 def AssertDevice(self, device):
139 """Assert that the device identifier is the given string."""
Doug Zongker0d92f1f2013-06-03 12:07:12 -0700140 cmd = ('getprop("ro.product.device") == "%s" || '
Tianjie Xu209db462016-05-24 17:34:52 -0700141 'abort("E%d: This package is for \\"%s\\" devices; '
Dan Albert8b72aef2015-03-23 19:13:21 -0700142 'this is a \\"" + getprop("ro.product.device") + "\\".");') % (
Tianjie Xu209db462016-05-24 17:34:52 -0700143 device, common.ErrorCode.DEVICE_MISMATCH, device)
Doug Zongker0d92f1f2013-06-03 12:07:12 -0700144 self.script.append(cmd)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700145
146 def AssertSomeBootloader(self, *bootloaders):
147 """Asert that the bootloader version is one of *bootloaders."""
148 cmd = ("assert(" +
149 " ||\0".join(['getprop("ro.bootloader") == "%s"' % (b,)
150 for b in bootloaders]) +
151 ");")
Dan Albert8b72aef2015-03-23 19:13:21 -0700152 self.script.append(self.WordWrap(cmd))
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700153
154 def ShowProgress(self, frac, dur):
155 """Update the progress bar, advancing it over 'frac' over the next
Doug Zongker881dd402009-09-20 14:03:55 -0700156 'dur' seconds. 'dur' may be zero to advance it via SetProgress
157 commands instead of by time."""
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700158 self.script.append("show_progress(%f, %d);" % (frac, int(dur)))
159
Doug Zongker881dd402009-09-20 14:03:55 -0700160 def SetProgress(self, frac):
161 """Set the position of the progress bar within the chunk defined
162 by the most recent ShowProgress call. 'frac' should be in
163 [0,1]."""
164 self.script.append("set_progress(%f);" % (frac,))
165
Tao Bao51216552018-08-26 11:53:15 -0700166 def PatchCheck(self, filename, *sha1): # pylint: disable=unused-argument
167 """Checks that the given partition has the desired checksum.
168
169 The call to this function is being deprecated in favor of
170 PatchPartitionCheck(). It will try to parse and handle the old format,
171 unless the format is unknown.
172 """
173 tokens = filename.split(':')
174 assert len(tokens) == 6 and tokens[0] == 'EMMC', \
175 "Failed to handle unknown format. Use PatchPartitionCheck() instead."
176 source = '{}:{}:{}:{}'.format(tokens[0], tokens[1], tokens[2], tokens[3])
177 target = '{}:{}:{}:{}'.format(tokens[0], tokens[1], tokens[4], tokens[5])
178 self.PatchPartitionCheck(target, source)
179
180 def PatchPartitionCheck(self, target, source):
181 """Checks whether updater can patch the given partitions.
182
183 It checks the checksums of the given partitions. If none of them matches the
184 expected checksum, updater will additionally look for a backup on /cache.
185 """
186 self.script.append(self.WordWrap((
187 'patch_partition_check("{target}",\0"{source}") ||\n abort('
188 '"E{code}: \\"{target}\\" or \\"{source}\\" has unexpected '
189 'contents.");').format(
190 target=target, source=source,
191 code=common.ErrorCode.BAD_PATCH_FILE)))
Doug Zongkerc8d446b2010-02-22 15:41:53 -0800192
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700193 def CacheFreeSpaceCheck(self, amount):
194 """Check that there's at least 'amount' space that can be made
195 available on /cache."""
Tao Baod8d14be2016-02-04 14:26:02 -0800196 self._required_cache = max(self._required_cache, amount)
Tianjie Xu209db462016-05-24 17:34:52 -0700197 self.script.append(('apply_patch_space(%d) || abort("E%d: Not enough free '
198 'space on /cache to apply patches.");') % (
199 amount,
200 common.ErrorCode.INSUFFICIENT_CACHE_SPACE))
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700201
Michael Runge7cd99ba2014-10-22 17:21:48 -0700202 def Mount(self, mount_point, mount_options_by_format=""):
203 """Mount the partition with the given mount_point.
204 mount_options_by_format:
205 [fs_type=option[,option]...[|fs_type=option[,option]...]...]
206 where option is optname[=optvalue]
207 E.g. ext4=barrier=1,nodelalloc,errors=panic|f2fs=errors=recover
208 """
Tao Bao34b47bf2015-06-22 19:17:41 -0700209 fstab = self.fstab
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700210 if fstab:
211 p = fstab[mount_point]
Michael Runge7cd99ba2014-10-22 17:21:48 -0700212 mount_dict = {}
213 if mount_options_by_format is not None:
214 for option in mount_options_by_format.split("|"):
215 if "=" in option:
216 key, value = option.split("=", 1)
217 mount_dict[key] = value
Tao Baodf06e962015-06-10 12:32:41 -0700218 mount_flags = mount_dict.get(p.fs_type, "")
219 if p.context is not None:
220 mount_flags = p.context + ("," + mount_flags if mount_flags else "")
Dan Albert8b72aef2015-03-23 19:13:21 -0700221 self.script.append('mount("%s", "%s", "%s", "%s", "%s");' % (
222 p.fs_type, common.PARTITION_TYPES[p.fs_type], p.device,
Tao Baodf06e962015-06-10 12:32:41 -0700223 p.mount_point, mount_flags))
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700224 self.mounts.add(p.mount_point)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700225
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700226 def Comment(self, comment):
227 """Write a comment into the update script."""
228 self.script.append("")
229 for i in comment.split("\n"):
230 self.script.append("# " + i)
231 self.script.append("")
232
233 def Print(self, message):
234 """Log a message to the screen (if the logs are visible)."""
235 self.script.append('ui_print("%s");' % (message,))
236
Michael Runge3e286642014-11-21 00:46:03 -0800237 def TunePartition(self, partition, *options):
Tao Bao34b47bf2015-06-22 19:17:41 -0700238 fstab = self.fstab
Michael Runge3e286642014-11-21 00:46:03 -0800239 if fstab:
240 p = fstab[partition]
Dan Albert8b72aef2015-03-23 19:13:21 -0700241 if p.fs_type not in ("ext2", "ext3", "ext4"):
Michael Runge3e286642014-11-21 00:46:03 -0800242 raise ValueError("Partition %s cannot be tuned\n" % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -0700243 self.script.append(
244 'tune2fs(' + "".join(['"%s", ' % (i,) for i in options]) +
Tianjie Xu209db462016-05-24 17:34:52 -0700245 '"%s") || abort("E%d: Failed to tune partition %s");' % (
246 p.device, common.ErrorCode.TUNE_PARTITION_FAILURE, partition))
Michael Runge3e286642014-11-21 00:46:03 -0800247
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700248 def FormatPartition(self, partition):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700249 """Format the given partition, specified by its mount point (eg,
250 "/system")."""
251
Tao Bao34b47bf2015-06-22 19:17:41 -0700252 fstab = self.fstab
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700253 if fstab:
254 p = fstab[partition]
Doug Zongkerdf2056e2012-04-09 12:27:43 -0700255 self.script.append('format("%s", "%s", "%s", "%s", "%s");' %
Doug Zongker086cbb02011-02-17 15:54:20 -0800256 (p.fs_type, common.PARTITION_TYPES[p.fs_type],
Doug Zongkerdf2056e2012-04-09 12:27:43 -0700257 p.device, p.length, p.mount_point))
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700258
Doug Zongker5fad2032014-02-24 08:13:45 -0800259 def WipeBlockDevice(self, partition):
Doug Zongkerc8b4e842014-06-16 15:16:31 -0700260 if partition not in ("/system", "/vendor"):
261 raise ValueError(("WipeBlockDevice doesn't work on %s\n") % (partition,))
Tao Bao34b47bf2015-06-22 19:17:41 -0700262 fstab = self.fstab
Doug Zongkerc8b4e842014-06-16 15:16:31 -0700263 size = self.info.get(partition.lstrip("/") + "_size", None)
Doug Zongker5fad2032014-02-24 08:13:45 -0800264 device = fstab[partition].device
265
266 self.script.append('wipe_block_device("%s", %s);' % (device, size))
267
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700268 def ApplyPatch(self, srcfile, tgtfile, tgtsize, tgtsha1, *patchpairs):
269 """Apply binary patches (in *patchpairs) to the given srcfile to
270 produce tgtfile (which may be "-" to indicate overwriting the
Tao Bao51216552018-08-26 11:53:15 -0700271 source file.
272
273 This edify function is being deprecated in favor of PatchPartition(). It
274 will try to redirect calls to PatchPartition() if possible. On unknown /
275 invalid inputs, raises an exception.
276 """
277 tokens = srcfile.split(':')
278 assert (len(tokens) == 6 and tokens[0] == 'EMMC' and tgtfile == '-' and
279 len(patchpairs) == 2), \
280 "Failed to handle unknown format. Use PatchPartition() instead."
281
282 # Also sanity check the args.
283 assert tokens[3] == patchpairs[0], \
284 "Found mismatching values for source SHA-1: {} vs {}".format(
285 tokens[3], patchpairs[0])
286 assert int(tokens[4]) == tgtsize, \
287 "Found mismatching values for target size: {} vs {}".format(
288 tokens[4], tgtsize)
289 assert tokens[5] == tgtsha1, \
290 "Found mismatching values for target SHA-1: {} vs {}".format(
291 tokens[5], tgtsha1)
292
293 source = '{}:{}:{}:{}'.format(tokens[0], tokens[1], tokens[2], tokens[3])
294 target = '{}:{}:{}:{}'.format(tokens[0], tokens[1], tokens[4], tokens[5])
295 patch = patchpairs[1]
296 self.PatchPartition(target, source, patch)
297
298 def PatchPartition(self, target, source, patch):
299 """Applies the patch to the source partition and writes it to target."""
300 self.script.append(self.WordWrap((
301 'patch_partition("{target}",\0"{source}",\0'
302 'package_extract_file("{patch}")) ||\n'
303 ' abort("E{code}: Failed to apply patch to {source}");').format(
304 target=target, source=source, patch=patch,
305 code=common.ErrorCode.APPLY_PATCH_FAILURE)))
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700306
Doug Zongker5fad2032014-02-24 08:13:45 -0800307 def WriteRawImage(self, mount_point, fn, mapfn=None):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700308 """Write the given package file into the partition for the given
309 mount point."""
Doug Zongkerb4c7d322010-07-01 15:30:11 -0700310
Tao Bao34b47bf2015-06-22 19:17:41 -0700311 fstab = self.fstab
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700312 if fstab:
313 p = fstab[mount_point]
Doug Zongker96a57e72010-09-26 14:57:41 -0700314 partition_type = common.PARTITION_TYPES[p.fs_type]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700315 args = {'device': p.device, 'fn': fn}
Elliott Hughes305b0882016-06-15 17:04:54 -0700316 if partition_type == "EMMC":
Doug Zongker5fad2032014-02-24 08:13:45 -0800317 if mapfn:
318 args["map"] = mapfn
319 self.script.append(
320 'package_extract_file("%(fn)s", "%(device)s", "%(map)s");' % args)
321 else:
322 self.script.append(
323 'package_extract_file("%(fn)s", "%(device)s");' % args)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700324 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700325 raise ValueError(
326 "don't know how to write \"%s\" partitions" % p.fs_type)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700327
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700328 def AppendExtra(self, extra):
329 """Append text verbatim to the output script."""
330 self.script.append(extra)
331
Michael Runge63f01de2014-10-28 19:24:19 -0700332 def Unmount(self, mount_point):
Dan Albert8b72aef2015-03-23 19:13:21 -0700333 self.script.append('unmount("%s");' % mount_point)
334 self.mounts.remove(mount_point)
Michael Runge63f01de2014-10-28 19:24:19 -0700335
Doug Zongker14833602010-02-02 13:12:04 -0800336 def UnmountAll(self):
337 for p in sorted(self.mounts):
338 self.script.append('unmount("%s");' % (p,))
339 self.mounts = set()
340
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700341 def AddToZip(self, input_zip, output_zip, input_path=None):
342 """Write the accumulated script to the output_zip file. input_zip
343 is used as the source for the 'updater' binary needed to run
344 script. If input_path is not None, it will be used as a local
345 path for the binary instead of input_zip."""
346
Doug Zongker14833602010-02-02 13:12:04 -0800347 self.UnmountAll()
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700348
349 common.ZipWriteStr(output_zip, "META-INF/com/google/android/updater-script",
350 "\n".join(self.script) + "\n")
351
352 if input_path is None:
353 data = input_zip.read("OTA/bin/updater")
354 else:
Doug Zongker25568482014-03-03 10:21:27 -0800355 data = open(input_path, "rb").read()
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700356 common.ZipWriteStr(output_zip, "META-INF/com/google/android/update-binary",
Dan Albert8b72aef2015-03-23 19:13:21 -0700357 data, perms=0o755)