summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/releasetools/blockimgdiff.py1
-rw-r--r--tools/releasetools/common.py82
-rw-r--r--tools/releasetools/edify_generator.py118
-rwxr-xr-xtools/releasetools/ota_from_target_files.py49
4 files changed, 199 insertions, 51 deletions
diff --git a/tools/releasetools/blockimgdiff.py b/tools/releasetools/blockimgdiff.py
index 72f065d19d..8b6a6900d5 100644
--- a/tools/releasetools/blockimgdiff.py
+++ b/tools/releasetools/blockimgdiff.py
@@ -1558,6 +1558,7 @@ class BlockImageDiff(object):
split_large_apks = []
cache_size = common.OPTIONS.cache_size
split_threshold = 0.125
+ assert cache_size is not None
max_blocks_per_transfer = int(cache_size * split_threshold /
self.tgt.blocksize)
empty = RangeSet()
diff --git a/tools/releasetools/common.py b/tools/releasetools/common.py
index 42c29c01c9..96f93a82c7 100644
--- a/tools/releasetools/common.py
+++ b/tools/releasetools/common.py
@@ -677,9 +677,14 @@ def LoadInfoDict(input_file, repacking=False):
makeint("userdata_size")
makeint("cache_size")
makeint("recovery_size")
- makeint("boot_size")
makeint("fstab_version")
+ boot_images = "boot.img"
+ if "boot_images" in d:
+ boot_images = d["boot_images"]
+ for b in boot_images.split():
+ makeint(b.replace(".img","_size"))
+
# Load recovery fstab if applicable.
d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
@@ -843,12 +848,13 @@ class PartitionBuildProps(object):
def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
system_root_image=False):
class Partition(object):
- def __init__(self, mount_point, fs_type, device, length, context):
+ def __init__(self, mount_point, fs_type, device, length, context, slotselect):
self.mount_point = mount_point
self.fs_type = fs_type
self.device = device
self.length = length
self.context = context
+ self.slotselect = slotselect
try:
data = read_helper(recovery_fstab_path)
@@ -876,10 +882,13 @@ def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
# It's a good line, parse it.
length = 0
+ slotselect = False
options = options.split(",")
for i in options:
if i.startswith("length="):
length = int(i[7:])
+ elif i == "slotselect":
+ slotselect = True
else:
# Ignore all unknown options in the unified fstab.
continue
@@ -893,7 +902,8 @@ def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
mount_point = pieces[1]
d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
- device=pieces[0], length=length, context=context)
+ device=pieces[0], length=length, context=context,
+ slotselect=slotselect)
# / is used for the system mount point when the root directory is included in
# system. Other areas assume system is always at "/system" so point /system
@@ -908,7 +918,8 @@ def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
"""Finds the path to recovery fstab and loads its contents."""
# recovery fstab is only meaningful when installing an update via recovery
# (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
- if info_dict.get('ab_update') == 'true':
+ if info_dict.get('ab_update') == 'true' and \
+ info_dict.get("allow_non_ab") != "true":
return None
# We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
@@ -1334,7 +1345,10 @@ def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None,
# AVB: if enabled, calculate and add hash to boot.img or recovery.img.
if info_dict.get("avb_enable") == "true":
avbtool = info_dict["avb_avbtool"]
- part_size = info_dict[partition_name + "_size"]
+ if partition_name == "recovery":
+ part_size = info_dict["recovery_size"]
+ else:
+ part_size = info_dict[image_name.replace(".img","_size")]
cmd = [avbtool, "add_hash_footer", "--image", img.name,
"--partition_size", str(part_size), "--partition_name",
partition_name]
@@ -2672,11 +2686,12 @@ class BlockDifference(object):
self.device = 'map_partition("%s")' % partition
else:
if OPTIONS.source_info_dict is None:
- _, device_path = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
+ _, device_expr = GetTypeAndDeviceExpr("/" + partition,
+ OPTIONS.info_dict)
else:
- _, device_path = GetTypeAndDevice("/" + partition,
- OPTIONS.source_info_dict)
- self.device = '"%s"' % device_path
+ _, device_expr = GetTypeAndDeviceExpr("/" + partition,
+ OPTIONS.source_info_dict)
+ self.device = device_expr
@property
def required_cache(self):
@@ -2908,16 +2923,51 @@ PARTITION_TYPES = {
"squashfs": "EMMC"
}
-
-def GetTypeAndDevice(mount_point, info):
+def GetTypeAndDevice(mount_point, info, check_no_slot=True):
+ """
+ Use GetTypeAndDeviceExpr whenever possible. This function is kept for
+ backwards compatibility. It aborts if the fstab entry has slotselect option
+ (unless check_no_slot is explicitly set to False).
+ """
fstab = info["fstab"]
if fstab:
+ if check_no_slot:
+ assert not fstab[mount_point].slotselect, \
+ "Use GetTypeAndDeviceExpr instead"
return (PARTITION_TYPES[fstab[mount_point].fs_type],
fstab[mount_point].device)
else:
raise KeyError
+def GetTypeAndDeviceExpr(mount_point, info):
+ """
+ Return the filesystem of the partition, and an edify expression that evaluates
+ to the device at runtime.
+ """
+ fstab = info["fstab"]
+ if fstab:
+ p = fstab[mount_point]
+ device_expr = '"%s"' % fstab[mount_point].device
+ if p.slotselect:
+ device_expr = 'add_slot_suffix(%s)' % device_expr
+ return (PARTITION_TYPES[fstab[mount_point].fs_type], device_expr)
+ else:
+ raise KeyError
+
+
+def GetEntryForDevice(fstab, device):
+ """
+ Returns:
+ The first entry in fstab whose device is the given value.
+ """
+ if not fstab:
+ return None
+ for mount_point in fstab:
+ if fstab[mount_point].device == device:
+ return fstab[mount_point]
+ return None
+
def ParseCertificate(data):
"""Parses and converts a PEM-encoded certificate into DER-encoded.
@@ -3042,8 +3092,10 @@ def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
try:
# The following GetTypeAndDevice()s need to use the path in the target
# info_dict instead of source_info_dict.
- boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
- recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
+ boot_type, boot_device = GetTypeAndDevice("/boot", info_dict,
+ check_no_slot=False)
+ recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict,
+ check_no_slot=False)
except KeyError:
return
@@ -3085,8 +3137,8 @@ fi
'recovery_size': recovery_img.size,
'recovery_sha1': recovery_img.sha1,
'boot_type': boot_type,
- 'boot_device': boot_device,
- 'recovery_type': recovery_type,
+ 'boot_device': boot_device + '$(getprop ro.boot.slot_suffix)',
+ 'recovery_type': recovery_type + '$(getprop ro.boot.slot_suffix)',
'recovery_device': recovery_device,
'bonus_args': bonus_args}
diff --git a/tools/releasetools/edify_generator.py b/tools/releasetools/edify_generator.py
index 7ed85fecc9..99e21f1c04 100644
--- a/tools/releasetools/edify_generator.py
+++ b/tools/releasetools/edify_generator.py
@@ -183,11 +183,30 @@ class EdifyGenerator(object):
It checks the checksums of the given partitions. If none of them matches the
expected checksum, updater will additionally look for a backup on /cache.
"""
+ self._CheckSecondTokenNotSlotSuffixed(target, "PatchPartitionExprCheck")
+ self._CheckSecondTokenNotSlotSuffixed(source, "PatchPartitionExprCheck")
+ self.PatchPartitionExprCheck('"%s"' % target, '"%s"' % source)
+
+ def PatchPartitionExprCheck(self, target_expr, source_expr):
+ """Checks whether updater can patch the given partitions.
+
+ It checks the checksums of the given partitions. If none of them matches the
+ expected checksum, updater will additionally look for a backup on /cache.
+
+ Args:
+ target_expr: an Edify expression that serves as the target arg to
+ patch_partition. Must be evaluated to a string in the form of
+ foo:bar:baz:quux
+ source_expr: an Edify expression that serves as the source arg to
+ patch_partition. Must be evaluated to a string in the form of
+ foo:bar:baz:quux
+ """
self.script.append(self.WordWrap((
- 'patch_partition_check("{target}",\0"{source}") ||\n abort('
- '"E{code}: \\"{target}\\" or \\"{source}\\" has unexpected '
- 'contents.");').format(
- target=target, source=source,
+ 'patch_partition_check({target},\0{source}) ||\n abort('
+ 'concat("E{code}: \\"",{target},"\\" or \\"",{source},"\\" has '
+ 'unexpected contents."));').format(
+ target=target_expr,
+ source=source_expr,
code=common.ErrorCode.BAD_PATCH_FILE)))
def CacheFreeSpaceCheck(self, amount):
@@ -218,8 +237,9 @@ class EdifyGenerator(object):
mount_flags = mount_dict.get(p.fs_type, "")
if p.context is not None:
mount_flags = p.context + ("," + mount_flags if mount_flags else "")
- self.script.append('mount("%s", "%s", "%s", "%s", "%s");' % (
- p.fs_type, common.PARTITION_TYPES[p.fs_type], p.device,
+ self.script.append('mount("%s", "%s", %s, "%s", "%s");' % (
+ p.fs_type, common.PARTITION_TYPES[p.fs_type],
+ self._GetSlotSuffixDeviceForEntry(p),
p.mount_point, mount_flags))
self.mounts.add(p.mount_point)
@@ -242,8 +262,9 @@ class EdifyGenerator(object):
raise ValueError("Partition %s cannot be tuned\n" % (partition,))
self.script.append(
'tune2fs(' + "".join(['"%s", ' % (i,) for i in options]) +
- '"%s") || abort("E%d: Failed to tune partition %s");' % (
- p.device, common.ErrorCode.TUNE_PARTITION_FAILURE, partition))
+ '%s) || abort("E%d: Failed to tune partition %s");' % (
+ self._GetSlotSuffixDeviceForEntry(p),
+ common.ErrorCode.TUNE_PARTITION_FAILURE, partition))
def FormatPartition(self, partition):
"""Format the given partition, specified by its mount point (eg,
@@ -252,18 +273,19 @@ class EdifyGenerator(object):
fstab = self.fstab
if fstab:
p = fstab[partition]
- self.script.append('format("%s", "%s", "%s", "%s", "%s");' %
+ self.script.append('format("%s", "%s", %s, "%s", "%s");' %
(p.fs_type, common.PARTITION_TYPES[p.fs_type],
- p.device, p.length, p.mount_point))
+ self._GetSlotSuffixDeviceForEntry(p),
+ p.length, p.mount_point))
def WipeBlockDevice(self, partition):
if partition not in ("/system", "/vendor"):
raise ValueError(("WipeBlockDevice doesn't work on %s\n") % (partition,))
fstab = self.fstab
size = self.info.get(partition.lstrip("/") + "_size", None)
- device = fstab[partition].device
+ device = self._GetSlotSuffixDeviceForEntry(fstab[partition])
- self.script.append('wipe_block_device("%s", %s);' % (device, size))
+ self.script.append('wipe_block_device(%s, %s);' % (device, size))
def ApplyPatch(self, srcfile, tgtfile, tgtsize, tgtsha1, *patchpairs):
"""Apply binary patches (in *patchpairs) to the given srcfile to
@@ -296,14 +318,69 @@ class EdifyGenerator(object):
self.PatchPartition(target, source, patch)
def PatchPartition(self, target, source, patch):
- """Applies the patch to the source partition and writes it to target."""
+ """
+ Applies the patch to the source partition and writes it to target.
+
+ Args:
+ target: the target arg to patch_partition. Must be in the form of
+ foo:bar:baz:quux
+ source: the source arg to patch_partition. Must be in the form of
+ foo:bar:baz:quux
+ patch: the patch arg to patch_partition. Must be an unquoted string.
+ """
+ self._CheckSecondTokenNotSlotSuffixed(target, "PatchPartitionExpr")
+ self._CheckSecondTokenNotSlotSuffixed(source, "PatchPartitionExpr")
+ self.PatchPartitionExpr('"%s"' % target, '"%s"' % source, '"%s"' % patch)
+
+ def PatchPartitionExpr(self, target_expr, source_expr, patch_expr):
+ """
+ Applies the patch to the source partition and writes it to target.
+
+ Args:
+ target_expr: an Edify expression that serves as the target arg to
+ patch_partition. Must be evaluated to a string in the form of
+ foo:bar:baz:quux
+ source_expr: an Edify expression that serves as the source arg to
+ patch_partition. Must be evaluated to a string in the form of
+ foo:bar:baz:quux
+ patch_expr: an Edify expression that serves as the patch arg to
+ patch_partition. Must be evaluated to a string.
+ """
self.script.append(self.WordWrap((
- 'patch_partition("{target}",\0"{source}",\0'
- 'package_extract_file("{patch}")) ||\n'
- ' abort("E{code}: Failed to apply patch to {source}");').format(
- target=target, source=source, patch=patch,
+ 'patch_partition({target},\0{source},\0'
+ 'package_extract_file({patch})) ||\n'
+ ' abort(concat('
+ ' "E{code}: Failed to apply patch to ",{source}));').format(
+ target=target_expr,
+ source=source_expr,
+ patch=patch_expr,
code=common.ErrorCode.APPLY_PATCH_FAILURE)))
+ def _GetSlotSuffixDeviceForEntry(self, entry=None):
+ """
+ Args:
+ entry: the fstab entry of device "foo"
+ Returns:
+ An edify expression. Caller must not quote result.
+ If foo is slot suffixed, it returns
+ 'add_slot_suffix("foo")'
+ Otherwise it returns
+ '"foo"' (quoted)
+ """
+ assert entry is not None
+ if entry.slotselect:
+ return 'add_slot_suffix("%s")' % entry.device
+ return '"%s"' % entry.device
+
+ def _CheckSecondTokenNotSlotSuffixed(self, s, fn):
+ lst = s.split(':')
+ assert(len(s) == 4), "{} does not contain 4 tokens".format(s)
+ if self.fstab:
+ entry = common.GetEntryForDevice(s[1])
+ if entry is not None:
+ assert not entry.slotselect, \
+ "Use %s because %s is slot suffixed" % (fn, s[1])
+
def WriteRawImage(self, mount_point, fn, mapfn=None):
"""Write the given package file into the partition for the given
mount point."""
@@ -312,15 +389,16 @@ class EdifyGenerator(object):
if fstab:
p = fstab[mount_point]
partition_type = common.PARTITION_TYPES[p.fs_type]
- args = {'device': p.device, 'fn': fn}
+ device = self._GetSlotSuffixDeviceForEntry(p)
+ args = {'device': device, 'fn': fn}
if partition_type == "EMMC":
if mapfn:
args["map"] = mapfn
self.script.append(
- 'package_extract_file("%(fn)s", "%(device)s", "%(map)s");' % args)
+ 'package_extract_file("%(fn)s", %(device)s, "%(map)s");' % args)
else:
self.script.append(
- 'package_extract_file("%(fn)s", "%(device)s");' % args)
+ 'package_extract_file("%(fn)s", %(device)s);' % args)
else:
raise ValueError(
"don't know how to write \"%s\" partitions" % p.fs_type)
diff --git a/tools/releasetools/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py
index ad001d13c7..16b278a519 100755
--- a/tools/releasetools/ota_from_target_files.py
+++ b/tools/releasetools/ota_from_target_files.py
@@ -78,6 +78,13 @@ Common options that apply to both of non-A/B and A/B OTAs
Write a copy of the metadata to a separate file. Therefore, users can
read the post build fingerprint without extracting the OTA package.
+ --force_non_ab
+ This flag can only be set on an A/B device that also supports non-A/B
+ updates. Implies --two_step.
+ If set, generate that non-A/B update package.
+ If not set, generates A/B package for A/B device and non-A/B package for
+ non-A/B device.
+
Non-A/B OTA specific options
-b (--binary) <file>
@@ -251,6 +258,7 @@ OPTIONS.skip_compatibility_check = False
OPTIONS.output_metadata_path = None
OPTIONS.disable_fec_computation = False
OPTIONS.boot_variable_values = None
+OPTIONS.force_non_ab = False
METADATA_NAME = 'META-INF/com/android/metadata'
@@ -933,7 +941,7 @@ def GetPackageMetadata(target_info, source_info=None):
'ro.build.version.security_patch'),
}
- if target_info.is_ab:
+ if target_info.is_ab and not OPTIONS.force_non_ab:
metadata['ota-type'] = 'AB'
metadata['ota-required-cache'] = '0'
else:
@@ -1455,7 +1463,8 @@ else if get_stage("%(bcb_dev)s") != "3/3" then
required_cache_sizes = [diff.required_cache for diff in
block_diff_dict.values()]
if updating_boot:
- boot_type, boot_device = common.GetTypeAndDevice("/boot", source_info)
+ boot_type, boot_device_expr = common.GetTypeAndDeviceExpr("/boot",
+ source_info)
d = common.Difference(target_boot, source_boot)
_, _, d = d.ComputePatch()
if d is None:
@@ -1470,11 +1479,11 @@ else if get_stage("%(bcb_dev)s") != "3/3" then
common.ZipWriteStr(output_zip, "boot.img.p", d)
- script.PatchPartitionCheck(
- "{}:{}:{}:{}".format(
- boot_type, boot_device, target_boot.size, target_boot.sha1),
- "{}:{}:{}:{}".format(
- boot_type, boot_device, source_boot.size, source_boot.sha1))
+ target_expr = 'concat("{}:",{},":{}:{}")'.format(
+ boot_type, boot_device_expr, target_boot.size, target_boot.sha1)
+ source_expr = 'concat("{}:",{},":{}:{}")'.format(
+ boot_type, boot_device_expr, source_boot.size, source_boot.sha1)
+ script.PatchPartitionExprCheck(target_expr, source_expr)
required_cache_sizes.append(target_boot.size)
@@ -1542,12 +1551,11 @@ else
logger.info("boot image changed; including patch.")
script.Print("Patching boot image...")
script.ShowProgress(0.1, 10)
- script.PatchPartition(
- '{}:{}:{}:{}'.format(
- boot_type, boot_device, target_boot.size, target_boot.sha1),
- '{}:{}:{}:{}'.format(
- boot_type, boot_device, source_boot.size, source_boot.sha1),
- 'boot.img.p')
+ target_expr = 'concat("{}:",{},":{}:{}")'.format(
+ boot_type, boot_device_expr, target_boot.size, target_boot.sha1)
+ source_expr = 'concat("{}:",{},":{}:{}")'.format(
+ boot_type, boot_device_expr, source_boot.size, source_boot.sha1)
+ script.PatchPartitionExpr(target_expr, source_expr, '"boot.img.p"')
else:
logger.info("boot image unchanged; skipping.")
@@ -2067,6 +2075,8 @@ def main(argv):
OPTIONS.output_metadata_path = a
elif o == "--disable_fec_computation":
OPTIONS.disable_fec_computation = True
+ elif o == "--force_non_ab":
+ OPTIONS.force_non_ab = True
else:
return False
return True
@@ -2103,6 +2113,7 @@ def main(argv):
"skip_compatibility_check",
"output_metadata_path=",
"disable_fec_computation",
+ "force_non_ab",
], extra_option_handler=option_handler)
if len(args) != 2:
@@ -2164,11 +2175,17 @@ def main(argv):
OPTIONS.skip_postinstall = True
ab_update = OPTIONS.info_dict.get("ab_update") == "true"
+ allow_non_ab = OPTIONS.info_dict.get("allow_non_ab") == "true"
+ if OPTIONS.force_non_ab:
+ assert allow_non_ab, "--force_non_ab only allowed on devices that supports non-A/B"
+ assert ab_update, "--force_non_ab only allowed on A/B devices"
+
+ generate_ab = not OPTIONS.force_non_ab and ab_update
# Use the default key to sign the package if not specified with package_key.
# package_keys are needed on ab_updates, so always define them if an
- # ab_update is getting created.
- if not OPTIONS.no_signing or ab_update:
+ # A/B update is getting created.
+ if not OPTIONS.no_signing or generate_ab:
if OPTIONS.package_key is None:
OPTIONS.package_key = OPTIONS.info_dict.get(
"default_system_dev_certificate",
@@ -2176,7 +2193,7 @@ def main(argv):
# Get signing keys
OPTIONS.key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
- if ab_update:
+ if generate_ab:
GenerateAbOtaPackage(
target_file=args[0],
output_file=args[1],