1#!/usr/bin/env python3 2# 3# Copyright (C) 2018 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17""" 18Given a OTA package file, produces update config JSON file. 19 20Example: 21 $ PYTHONPATH=$ANDROID_BUILD_TOP/build/make/tools/releasetools:$PYTHONPATH \\ 22 bootable/recovery/updater_sample/tools/gen_update_config.py \\ 23 --ab_install_type=STREAMING \\ 24 ota-build-001.zip \\ 25 my-config-001.json \\ 26 http://foo.bar/ota-builds/ota-build-001.zip 27""" 28 29import argparse 30import json 31import os.path 32import sys 33import zipfile 34 35import ota_from_target_files # pylint: disable=import-error 36 37 38class GenUpdateConfig(object): 39 """ 40 A class that generates update configuration file from an OTA package. 41 42 Currently supports only A/B (seamless) OTA packages. 43 TODO: add non-A/B packages support. 44 """ 45 46 AB_INSTALL_TYPE_STREAMING = 'STREAMING' 47 AB_INSTALL_TYPE_NON_STREAMING = 'NON_STREAMING' 48 49 def __init__(self, 50 package, 51 url, 52 ab_install_type, 53 ab_force_switch_slot, 54 ab_verify_payload_metadata): 55 self.package = package 56 self.url = url 57 self.ab_install_type = ab_install_type 58 self.ab_force_switch_slot = ab_force_switch_slot 59 self.ab_verify_payload_metadata = ab_verify_payload_metadata 60 self.streaming_required = ( 61 # payload.bin and payload_properties.txt must exist. 62 'payload.bin', 63 'payload_properties.txt', 64 ) 65 self.streaming_optional = ( 66 # care_map.txt is available only if dm-verity is enabled. 67 'care_map.txt', 68 # compatibility.zip is available only if target supports Treble. 69 'compatibility.zip', 70 ) 71 self._config = None 72 73 @property 74 def config(self): 75 """Returns generated config object.""" 76 return self._config 77 78 def run(self): 79 """Generates config.""" 80 self._config = { 81 '__': '*** Generated using tools/gen_update_config.py ***', 82 'name': self.ab_install_type[0] + ' ' + os.path.basename(self.package)[:-4], 83 'url': self.url, 84 'ab_config': self._gen_ab_config(), 85 'ab_install_type': self.ab_install_type, 86 } 87 88 def _gen_ab_config(self): 89 """Builds config required for A/B update.""" 90 with zipfile.ZipFile(self.package, 'r') as package_zip: 91 config = { 92 'property_files': self._get_property_files(package_zip), 93 'verify_payload_metadata': self.ab_verify_payload_metadata, 94 'force_switch_slot': self.ab_force_switch_slot, 95 } 96 97 return config 98 99 @staticmethod 100 def _get_property_files(package_zip): 101 """Constructs the property-files list for A/B streaming metadata.""" 102 103 ab_ota = ota_from_target_files.AbOtaPropertyFiles() 104 property_str = ab_ota.GetPropertyFilesString(package_zip, False) 105 property_files = [] 106 for file in property_str.split(','): 107 filename, offset, size = file.split(':') 108 inner_file = { 109 'filename': filename, 110 'offset': int(offset), 111 'size': int(size) 112 } 113 property_files.append(inner_file) 114 115 return property_files 116 117 def write(self, out): 118 """Writes config to the output file.""" 119 with open(out, 'w') as out_file: 120 json.dump(self.config, out_file, indent=4, separators=(',', ': '), sort_keys=True) 121 122 123def main(): # pylint: disable=missing-docstring 124 ab_install_type_choices = [ 125 GenUpdateConfig.AB_INSTALL_TYPE_STREAMING, 126 GenUpdateConfig.AB_INSTALL_TYPE_NON_STREAMING] 127 parser = argparse.ArgumentParser(description=__doc__, 128 formatter_class=argparse.RawDescriptionHelpFormatter) 129 parser.add_argument('--ab_install_type', 130 type=str, 131 default=GenUpdateConfig.AB_INSTALL_TYPE_NON_STREAMING, 132 choices=ab_install_type_choices, 133 help='A/B update installation type') 134 parser.add_argument('--ab_force_switch_slot', 135 default=False, 136 action='store_true', 137 help='if set device will boot to a new slot, otherwise user ' 138 'manually switches slot on the screen') 139 parser.add_argument('--ab_verify_payload_metadata', 140 default=False, 141 action='store_true', 142 help='if set the app will verify the update payload metadata using ' 143 'update_engine before downloading the whole package.') 144 parser.add_argument('package', 145 type=str, 146 help='OTA package zip file') 147 parser.add_argument('out', 148 type=str, 149 help='Update configuration JSON file') 150 parser.add_argument('url', 151 type=str, 152 help='OTA package download url') 153 args = parser.parse_args() 154 155 if not args.out.endswith('.json'): 156 print('out must be a json file') 157 sys.exit(1) 158 159 gen = GenUpdateConfig( 160 package=args.package, 161 url=args.url, 162 ab_install_type=args.ab_install_type, 163 ab_force_switch_slot=args.ab_force_switch_slot, 164 ab_verify_payload_metadata=args.ab_verify_payload_metadata) 165 gen.run() 166 gen.write(args.out) 167 print('Config is written to ' + args.out) 168 169 170if __name__ == '__main__': 171 main() 172