blob: b02363cde67cc02508ca70ab2bab8d66187834b2 [file] [log] [blame]
Bill Peckhame9eb5f92019-02-01 15:52:10 -08001#!/usr/bin/env python
2#
3# Copyright (C) 2019 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License"); you may not
6# use this file except in compliance with the License. You may obtain a copy of
7# 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, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14# License for the specific language governing permissions and limitations under
15# the License.
16
17"""
18This script merges two partial target files packages (one of which contains
19system files, and the other contains non-system files) together, producing a
20complete target files package that can be used to generate an OTA package.
21
22Usage: merge_target_files.py [args]
23
24 --system-target-files system-target-files-zip-archive
25 The input target files package containing system bits. This is a zip
26 archive.
27
28 --other-target-files other-target-files-zip-archive
29 The input target files package containing other bits. This is a zip
30 archive.
31
32 --output-target-files output-target-files-package
33 The output merged target files package. Also a zip archive.
34"""
35
36from __future__ import print_function
37
38import argparse
39import fnmatch
40import logging
41import os
42import sys
43import zipfile
44
45import common
46import add_img_to_target_files
47
48logger = logging.getLogger(__name__)
49OPTIONS = common.OPTIONS
50OPTIONS.verbose = True
51
52# system_extract_as_is_item_list is a list of items to extract from the partial
53# system target files package as is, meaning these items will land in the
54# output target files package exactly as they appear in the input partial
55# system target files package.
56
57system_extract_as_is_item_list = [
58 'META/apkcerts.txt',
59 'META/filesystem_config.txt',
60 'META/root_filesystem_config.txt',
61 'META/system_manifest.xml',
62 'META/system_matrix.xml',
63 'META/update_engine_config.txt',
64 'PRODUCT/*',
65 'ROOT/*',
66 'SYSTEM/*',
67]
68
69# system_extract_special_item_list is a list of items to extract from the
70# partial system target files package that need some special processing, such
71# as some sort of combination with items from the partial other target files
72# package.
73
74system_extract_special_item_list = [
75 'META/*',
76]
77
78# system_misc_info_keys is a list of keys to obtain from the system instance of
79# META/misc_info.txt. The remaining keys from the other instance.
80
81system_misc_info_keys = [
82 'avb_system_hashtree_enable',
83 'avb_system_add_hashtree_footer_args',
84 'avb_system_key_path',
85 'avb_system_algorithm',
86 'avb_system_rollback_index_location',
87 'avb_product_hashtree_enable',
88 'avb_product_add_hashtree_footer_args',
89 'avb_product_services_hashtree_enable',
90 'avb_product_services_add_hashtree_footer_args',
91 'system_root_image',
92 'root_dir',
93 'ab_update',
94 'default_system_dev_certificate',
95 'system_size',
96]
97
98# other_extract_as_is_item_list is a list of items to extract from the partial
99# other target files package as is, meaning these items will land in the output
100# target files package exactly as they appear in the input partial other target
101# files package.
102
103other_extract_as_is_item_list = [
104 'META/boot_filesystem_config.txt',
105 'META/otakeys.txt',
106 'META/releasetools.py',
107 'META/vendor_filesystem_config.txt',
108 'META/vendor_manifest.xml',
109 'META/vendor_matrix.xml',
110 'BOOT/*',
111 'DATA/*',
112 'ODM/*',
113 'OTA/android-info.txt',
114 'PREBUILT_IMAGES/*',
115 'RADIO/*',
116 'VENDOR/*',
117]
118
119# other_extract_for_merge_item_list is a list of items to extract from the
120# partial other target files package that need some special processing, such as
121# some sort of combination with items from the partial system target files
122# package.
123
124other_extract_special_item_list = [
125 'META/*',
126]
127
128
129def extract_items(target_files, target_files_temp_dir, extract_item_list):
130 """Extract items from target files to temporary directory.
131
132 This function extracts from the specified target files zip archive into the
133 specified temporary directory, the items specified in the extract item list.
134
135 Args:
136 target_files: The target files zip archive from which to extract items.
137
138 target_files_temp_dir: The temporary directory where the extracted items
139 will land.
140
141 extract_item_list: A list of items to extract.
142
143 Returns:
144 On success, 0. Otherwise, a non-zero exit code from unzip.
145 """
146
147 logger.info('extracting from %s', target_files)
148
149 # Filter the extract_item_list to remove any items that do not exist in the
150 # zip file. Otherwise, the extraction step will fail.
151
152 with zipfile.ZipFile(
153 target_files,
154 'r',
155 allowZip64=True) as target_files_zipfile:
156 target_files_namelist = target_files_zipfile.namelist()
157
158 filtered_extract_item_list = []
159 for pattern in extract_item_list:
160 matching_namelist = fnmatch.filter(target_files_namelist, pattern)
161 if not matching_namelist:
162 logger.warning('no match for %s', pattern)
163 else:
164 filtered_extract_item_list.append(pattern)
165
166 # Extract the filtered_extract_item_list from target_files into
167 # target_files_temp_dir.
168
169 # TODO(b/124464492): Extend common.UnzipTemp() to handle this use case.
170 command = [
171 'unzip',
172 '-n',
173 '-q',
174 '-d', target_files_temp_dir,
175 target_files
176 ] + filtered_extract_item_list
177
178 result = common.RunAndWait(command, verbose=True)
179
180 if result != 0:
181 logger.error('extract_items command %s failed %d', str(command), result)
182 return result
183
184 return 0
185
186
187def process_ab_partitions_txt(
188 system_target_files_temp_dir,
189 other_target_files_temp_dir,
190 output_target_files_temp_dir):
191 """Perform special processing for META/ab_partitions.txt
192
193 This function merges the contents of the META/ab_partitions.txt files from
194 the system directory and the other directory, placing the merged result in
195 the output directory. The precondition in that the files are already
196 extracted. The post condition is that the output META/ab_partitions.txt
197 contains the merged content. The format for each ab_partitions.txt a one
198 partition name per line. The output file contains the union of the parition
199 names.
200
201 Args:
202 system_target_files_temp_dir: The name of a directory containing the
203 special items extracted from the system target files package.
204
205 other_target_files_temp_dir: The name of a directory containing the
206 special items extracted from the other target files package.
207
208 output_target_files_temp_dir: The name of a directory that will be used
209 to create the output target files package after all the special cases
210 are processed.
211 """
212
213 system_ab_partitions_txt = os.path.join(
214 system_target_files_temp_dir, 'META', 'ab_partitions.txt')
215
216 other_ab_partitions_txt = os.path.join(
217 other_target_files_temp_dir, 'META', 'ab_partitions.txt')
218
219 with open(system_ab_partitions_txt) as f:
220 system_ab_partitions = f.read().splitlines()
221
222 with open(other_ab_partitions_txt) as f:
223 other_ab_partitions = f.read().splitlines()
224
225 output_ab_partitions = set(system_ab_partitions + other_ab_partitions)
226
227 output_ab_partitions_txt = os.path.join(
228 output_target_files_temp_dir, 'META', 'ab_partitions.txt')
229
230 with open(output_ab_partitions_txt, 'w') as output:
231 for partition in sorted(output_ab_partitions):
232 output.write('%s\n' % partition)
233
234
235def process_misc_info_txt(
236 system_target_files_temp_dir,
237 other_target_files_temp_dir,
238 output_target_files_temp_dir):
239 """Perform special processing for META/misc_info.txt
240
241 This function merges the contents of the META/misc_info.txt files from the
242 system directory and the other directory, placing the merged result in the
243 output directory. The precondition in that the files are already extracted.
244 The post condition is that the output META/misc_info.txt contains the merged
245 content.
246
247 Args:
248 system_target_files_temp_dir: The name of a directory containing the
249 special items extracted from the system target files package.
250
251 other_target_files_temp_dir: The name of a directory containing the
252 special items extracted from the other target files package.
253
254 output_target_files_temp_dir: The name of a directory that will be used
255 to create the output target files package after all the special cases
256 are processed.
257 """
258
259 def read_helper(d):
260 misc_info_txt = os.path.join(d, 'META', 'misc_info.txt')
261 with open(misc_info_txt) as f:
262 return list(f.read().splitlines())
263
264 system_info_dict = common.LoadDictionaryFromLines(
265 read_helper(system_target_files_temp_dir))
266
267 # We take most of the misc info from the other target files.
268
269 merged_info_dict = common.LoadDictionaryFromLines(
270 read_helper(other_target_files_temp_dir))
271
272 # Replace certain values in merged_info_dict with values from
273 # system_info_dict. TODO(b/124467065): This should be more flexible than
274 # using the hard-coded system_misc_info_keys.
275
276 for key in system_misc_info_keys:
277 merged_info_dict[key] = system_info_dict[key]
278
279 output_misc_info_txt = os.path.join(
280 output_target_files_temp_dir,
281 'META', 'misc_info.txt')
282
283 sorted_keys = sorted(merged_info_dict.keys())
284
285 with open(output_misc_info_txt, 'w') as output:
286 for key in sorted_keys:
287 output.write('{}={}\n'.format(key, merged_info_dict[key]))
288
289
290def process_file_contexts_bin(temp_dir, output_target_files_temp_dir):
291 """Perform special processing for META/file_contexts.bin.
292
293 This function combines plat_file_contexts and vendor_file_contexts, which are
294 expected to already be extracted in temp_dir, to produce a merged
295 file_contexts.bin that will land in temp_dir at META/file_contexts.bin.
296
297 Args:
298 temp_dir: The name of a scratch directory that this function can use for
299 intermediate files generated during processing.
300
301 output_target_files_temp_dir: The name of the working directory that must
302 already contain plat_file_contexts and vendor_file_contexts (in the
303 appropriate sub directories), and to which META/file_contexts.bin will be
304 written.
305
306 Returns:
307 On success, 0. Otherwise, a non-zero exit code.
308 """
309
310 # To create a merged file_contexts.bin file, we use the system and vendor
311 # file contexts files as input, the m4 tool to combine them, the sorting tool
312 # to sort, and finally the sefcontext_compile tool to generate the final
313 # output. We currently omit a checkfc step since the files had been checked
314 # as part of the build.
315
316 # The m4 step concatenates the two input files contexts files. Since m4
317 # writes to stdout, we receive that into an array of bytes, and then write it
318 # to a file.
319
320 # Collect the file contexts that we're going to combine from SYSTEM, VENDOR,
321 # PRODUCT, and ODM. We require SYSTEM and VENDOR, but others are optional.
322
323 file_contexts_list = []
324
325 for partition in ['SYSTEM', 'VENDOR', 'PRODUCT', 'ODM']:
326 prefix = 'plat' if partition == 'SYSTEM' else partition.lower()
327
328 file_contexts = os.path.join(
329 output_target_files_temp_dir,
330 partition, 'etc', 'selinux', prefix + '_file_contexts')
331
332 mandatory = partition in ['SYSTEM', 'VENDOR']
333
334 if mandatory or os.path.isfile(file_contexts):
335 file_contexts_list.append(file_contexts)
336 else:
337 logger.warning('file not found: %s', file_contexts)
338
339 command = ['m4', '--fatal-warnings', '-s'] + file_contexts_list
340
341 merged_content = common.RunAndCheckOutput(command, verbose=False)
342
343 merged_file_contexts_txt = os.path.join(temp_dir, 'merged_file_contexts.txt')
344
345 with open(merged_file_contexts_txt, 'wb') as f:
346 f.write(merged_content)
347
348 # The sort step sorts the concatenated file.
349
350 sorted_file_contexts_txt = os.path.join(temp_dir, 'sorted_file_contexts.txt')
351 command = ['fc_sort', merged_file_contexts_txt, sorted_file_contexts_txt]
352
353 # TODO(b/124521133): Refector RunAndWait to raise exception on failure.
354 result = common.RunAndWait(command, verbose=True)
355
356 if result != 0:
357 return result
358
359 # Finally, the compile step creates the final META/file_contexts.bin.
360
361 file_contexts_bin = os.path.join(
362 output_target_files_temp_dir,
363 'META', 'file_contexts.bin')
364
365 command = [
366 'sefcontext_compile',
367 '-o', file_contexts_bin,
368 sorted_file_contexts_txt,
369 ]
370
371 result = common.RunAndWait(command, verbose=True)
372
373 if result != 0:
374 return result
375
376 return 0
377
378
379def process_special_cases(
380 temp_dir,
381 system_target_files_temp_dir,
382 other_target_files_temp_dir,
383 output_target_files_temp_dir):
384 """Perform special-case processing for certain target files items.
385
386 Certain files in the output target files package require special-case
387 processing. This function performs all that special-case processing.
388
389 Args:
390 temp_dir: The name of a scratch directory that this function can use for
391 intermediate files generated during processing.
392
393 system_target_files_temp_dir: The name of a directory containing the
394 special items extracted from the system target files package.
395
396 other_target_files_temp_dir: The name of a directory containing the
397 special items extracted from the other target files package.
398
399 output_target_files_temp_dir: The name of a directory that will be used
400 to create the output target files package after all the special cases
401 are processed.
402
403 Returns:
404 On success, 0. Otherwise, a non-zero exit code.
405 """
406
407 process_ab_partitions_txt(
408 system_target_files_temp_dir=system_target_files_temp_dir,
409 other_target_files_temp_dir=other_target_files_temp_dir,
410 output_target_files_temp_dir=output_target_files_temp_dir)
411
412 process_misc_info_txt(
413 system_target_files_temp_dir=system_target_files_temp_dir,
414 other_target_files_temp_dir=other_target_files_temp_dir,
415 output_target_files_temp_dir=output_target_files_temp_dir)
416
417 result = process_file_contexts_bin(
418 temp_dir=temp_dir,
419 output_target_files_temp_dir=output_target_files_temp_dir)
420
421 if result != 0:
422 return result
423
424 return 0
425
426
427def merge_target_files(
428 temp_dir,
429 system_target_files,
430 other_target_files,
431 output_target_files):
432 """Merge two target files packages together.
433
434 This function takes system and other target files packages as input, performs
435 various file extractions, special case processing, and finally creates a
436 merged zip archive as output.
437
438 Args:
439 temp_dir: The name of a directory we use when we extract items from the
440 input target files packages, and also a scratch directory that we use for
441 temporary files.
442
443 system_target_files: The name of the zip archive containing the system
444 partial target files package.
445
446 other_target_files: The name of the zip archive containing the other
447 partial target files package.
448
449 output_target_files: The name of the output zip archive target files
450 package created by merging system and other.
451
452 Returns:
453 On success, 0. Otherwise, a non-zero exit code.
454 """
455
456 # Create directory names that we'll use when we extract files from system,
457 # and other, and for zipping the final output.
458
459 system_target_files_temp_dir = os.path.join(temp_dir, 'system')
460 other_target_files_temp_dir = os.path.join(temp_dir, 'other')
461 output_target_files_temp_dir = os.path.join(temp_dir, 'output')
462
463 # Extract "as is" items from the input system partial target files package.
464 # We extract them directly into the output temporary directory since the
465 # items do not need special case processing.
466
467 result = extract_items(
468 target_files=system_target_files,
469 target_files_temp_dir=output_target_files_temp_dir,
470 extract_item_list=system_extract_as_is_item_list)
471
472 if result != 0:
473 return result
474
475 # Extract "as is" items from the input other partial target files package. We
476 # extract them directly into the output temporary directory since the items
477 # do not need special case processing.
478
479 result = extract_items(
480 target_files=other_target_files,
481 target_files_temp_dir=output_target_files_temp_dir,
482 extract_item_list=other_extract_as_is_item_list)
483
484 if result != 0:
485 return result
486
487 # Extract "special" items from the input system partial target files package.
488 # We extract these items to different directory since they require special
489 # processing before they will end up in the output directory.
490
491 result = extract_items(
492 target_files=system_target_files,
493 target_files_temp_dir=system_target_files_temp_dir,
494 extract_item_list=system_extract_special_item_list)
495
496 if result != 0:
497 return result
498
499 # Extract "special" items from the input other partial target files package.
500 # We extract these items to different directory since they require special
501 # processing before they will end up in the output directory.
502
503 result = extract_items(
504 target_files=other_target_files,
505 target_files_temp_dir=other_target_files_temp_dir,
506 extract_item_list=other_extract_special_item_list)
507
508 if result != 0:
509 return result
510
511 # Now that the temporary directories contain all the extracted files, perform
512 # special case processing on any items that need it. After this function
513 # completes successfully, all the files we need to create the output target
514 # files package are in place.
515
516 result = process_special_cases(
517 temp_dir=temp_dir,
518 system_target_files_temp_dir=system_target_files_temp_dir,
519 other_target_files_temp_dir=other_target_files_temp_dir,
520 output_target_files_temp_dir=output_target_files_temp_dir)
521
522 if result != 0:
523 return result
524
525 # Regenerate IMAGES in the temporary directory.
526
527 add_img_args = [
528 '--verbose',
529 output_target_files_temp_dir,
530 ]
531
532 add_img_to_target_files.main(add_img_args)
533
534 # Finally, create the output target files zip archive.
535
536 output_zip = os.path.abspath(output_target_files)
537 output_target_files_list = os.path.join(temp_dir, 'output.list')
538 output_target_files_meta_dir = os.path.join(
539 output_target_files_temp_dir, 'META')
540
541 command = [
542 'find',
543 output_target_files_meta_dir,
544 ]
545 # TODO(bpeckham): sort this to be more like build.
546 meta_content = common.RunAndCheckOutput(command, verbose=False)
547 command = [
548 'find',
549 output_target_files_temp_dir,
550 '-path',
551 output_target_files_meta_dir,
552 '-prune',
553 '-o',
554 '-print'
555 ]
556 # TODO(bpeckham): sort this to be more like build.
557 other_content = common.RunAndCheckOutput(command, verbose=False)
558
559 with open(output_target_files_list, 'wb') as f:
560 f.write(meta_content)
561 f.write(other_content)
562
563 command = [
564 # TODO(124468071): Use soong_zip from otatools.zip
565 'prebuilts/build-tools/linux-x86/bin/soong_zip',
566 '-d',
567 '-o', output_zip,
568 '-C', output_target_files_temp_dir,
569 '-l', output_target_files_list,
570 ]
571 logger.info('creating %s', output_target_files)
572 result = common.RunAndWait(command, verbose=True)
573
574 if result != 0:
575 logger.error('zip command %s failed %d', str(command), result)
576 return result
577
578 return 0
579
580
581def merge_target_files_with_temp_dir(
582 system_target_files,
583 other_target_files,
584 output_target_files,
585 keep_tmp):
586 """Manage the creation and cleanup of the temporary directory.
587
588 This function wraps merge_target_files after first creating a temporary
589 directory. It also cleans up the temporary directory.
590
591 Args:
592 system_target_files: The name of the zip archive containing the system
593 partial target files package.
594
595 other_target_files: The name of the zip archive containing the other
596 partial target files package.
597
598 output_target_files: The name of the output zip archive target files
599 package created by merging system and other.
600
601 keep_tmp: Keep the temporary directory after processing is complete.
602
603 Returns:
604 On success, 0. Otherwise, a non-zero exit code.
605 """
606
607 # Create a temporary directory. This will serve as the parent of directories
608 # we use when we extract items from the input target files packages, and also
609 # a scratch directory that we use for temporary files.
610
611 logger.info(
612 'starting: merge system %s and other %s into output %s',
613 system_target_files,
614 other_target_files,
615 output_target_files)
616
617 temp_dir = common.MakeTempDir(prefix='merge_target_files_')
618
619 try:
620 return merge_target_files(
621 temp_dir=temp_dir,
622 system_target_files=system_target_files,
623 other_target_files=other_target_files,
624 output_target_files=output_target_files)
625 except:
626 raise
627 finally:
628 if keep_tmp:
629 logger.info('keeping %s', temp_dir)
630 else:
631 common.Cleanup()
632
633
634def main():
635 """The main function.
636
637 Process command line arguments, then call merge_target_files_with_temp_dir to
638 perform the heavy lifting.
639
640 Returns:
641 On success, 0. Otherwise, a non-zero exit code.
642 """
643
644 common.InitLogging()
645
646 parser = argparse.ArgumentParser()
647
648 parser.add_argument(
649 '--system-target-files',
650 required=True,
651 help='The input target files package containing system bits.')
652
653 parser.add_argument(
654 '--other-target-files',
655 required=True,
656 help='The input target files package containing other bits.')
657
658 parser.add_argument(
659 '--output-target-files',
660 required=True,
661 help='The output merged target files package.')
662
663 parser.add_argument(
664 '--keep-tmp',
665 required=False,
666 action='store_true',
667 help='Keep the temporary directories after execution.')
668
669 args = parser.parse_args()
670
671 return merge_target_files_with_temp_dir(
672 system_target_files=args.system_target_files,
673 other_target_files=args.other_target_files,
674 output_target_files=args.output_target_files,
675 keep_tmp=args.keep_tmp)
676
677
678if __name__ == '__main__':
679 sys.exit(main())