Benjamin Gordon | 1f4537f | 2019-12-06 09:10:56 -0700 | [diff] [blame] | 1 | # Copyright 2020 The Chromium OS Authors. All rights reserved. |
| 2 | # Use of this source code is governed by a BSD-style license that can be |
| 3 | # found in the LICENSE file. |
| 4 | |
| 5 | """Run xz from PATH with a thread for each core in the system.""" |
| 6 | |
Ben Pastene | c6bf549 | 2020-08-28 17:35:01 -0700 | [diff] [blame] | 7 | from __future__ import division |
Benjamin Gordon | 1f4537f | 2019-12-06 09:10:56 -0700 | [diff] [blame] | 8 | |
George Burgess IV | 757887f | 2021-09-22 15:58:35 -0700 | [diff] [blame^] | 9 | import getopt |
Benjamin Gordon | 1f4537f | 2019-12-06 09:10:56 -0700 | [diff] [blame] | 10 | import os |
George Burgess IV | 757887f | 2021-09-22 15:58:35 -0700 | [diff] [blame^] | 11 | import subprocess |
| 12 | import sys |
Mike Frysinger | 687ab9d | 2020-02-06 00:35:15 -0500 | [diff] [blame] | 13 | |
Ben Pastene | c6bf549 | 2020-08-28 17:35:01 -0700 | [diff] [blame] | 14 | from chromite.lib import commandline |
| 15 | from chromite.lib import osutils |
| 16 | from chromite.utils import memoize |
| 17 | |
Benjamin Gordon | 1f4537f | 2019-12-06 09:10:56 -0700 | [diff] [blame] | 18 | |
George Burgess IV | 757887f | 2021-09-22 15:58:35 -0700 | [diff] [blame^] | 19 | PIXZ_DISABLE_VAR = 'FOR_TEST_XZ_AUTO_NO_PIXZ' |
| 20 | |
| 21 | |
Ben Pastene | c6bf549 | 2020-08-28 17:35:01 -0700 | [diff] [blame] | 22 | @memoize.Memoize |
| 23 | def HasPixz(): |
| 24 | """Returns path to pixz if it's on PATH or None otherwise.""" |
George Burgess IV | 757887f | 2021-09-22 15:58:35 -0700 | [diff] [blame^] | 25 | return PIXZ_DISABLE_VAR not in os.environ and osutils.Which('pixz') |
Ben Pastene | c6bf549 | 2020-08-28 17:35:01 -0700 | [diff] [blame] | 26 | |
| 27 | |
George Burgess IV | 757887f | 2021-09-22 15:58:35 -0700 | [diff] [blame^] | 28 | def ParsePixzArgs(argv): |
| 29 | """Determines flags to pass to pixz, per argv. |
Denis Nikitin | 091a7c4 | 2021-10-27 18:55:27 +0000 | [diff] [blame] | 30 | |
George Burgess IV | 757887f | 2021-09-22 15:58:35 -0700 | [diff] [blame^] | 31 | Returns: |
| 32 | A tuple containing: |
| 33 | - A raw list of flags to pass to pixz. |
| 34 | - An optional input file. |
| 35 | - An optional output file (only exists if the input file is present). |
Denis Nikitin | 091a7c4 | 2021-10-27 18:55:27 +0000 | [diff] [blame] | 36 | """ |
George Burgess IV | 757887f | 2021-09-22 15:58:35 -0700 | [diff] [blame^] | 37 | # Glancing at docs, the following opts are supported. -i and -o are ignored, |
| 38 | # since we assert in `main` that they're not present, but include parsing for |
| 39 | # them anyway. |
| 40 | flags, args = getopt.gnu_getopt( |
| 41 | args=argv, |
| 42 | shortopts='dlxi:o:0123456789p:tkch', |
| 43 | ) |
| 44 | if not args: |
| 45 | file_to_compress = None |
| 46 | target = None |
| 47 | elif len(args) == 1: |
| 48 | file_to_compress = args[0] |
| 49 | target = None |
| 50 | else: |
| 51 | file_to_compress = args[0] |
| 52 | target = args[1] |
| 53 | |
| 54 | raw_flag_list = [] |
| 55 | for key, val in flags: |
| 56 | raw_flag_list.append(key) |
| 57 | if val: |
| 58 | raw_flag_list.append(val) |
| 59 | |
| 60 | return raw_flag_list, file_to_compress, target |
Ben Pastene | c6bf549 | 2020-08-28 17:35:01 -0700 | [diff] [blame] | 61 | |
| 62 | |
George Burgess IV | 757887f | 2021-09-22 15:58:35 -0700 | [diff] [blame^] | 63 | def Execvp(argv): |
| 64 | """Execs the given argv.""" |
| 65 | os.execvp(argv[0], argv) |
| 66 | |
| 67 | |
| 68 | def ExecCompressCommand(stdout, argv): |
| 69 | """Execs compression command.""" |
| 70 | # It appears that in order for pixz to do parallel decompression, compression |
| 71 | # needs to be done with pixz. xz itself is only capable of parallel |
| 72 | # compression. |
| 73 | if not HasPixz(): |
| 74 | cmd = ['xz'] |
| 75 | |
| 76 | if stdout: |
| 77 | cmd.append('-zc') |
| 78 | else: |
| 79 | cmd.append('-z') |
| 80 | cmd += argv |
| 81 | Execvp(cmd) |
| 82 | |
| 83 | cmd = ['pixz'] |
| 84 | raw_flag_list, compressed_file_name, output_file = ParsePixzArgs(argv) |
| 85 | |
| 86 | autodelete_input_file = False |
| 87 | if not compressed_file_name: |
| 88 | assert not output_file |
| 89 | compressed_file_name = '/dev/stdin' |
| 90 | output_file = '/dev/stdout' |
| 91 | elif stdout: |
| 92 | output_file = '/dev/stdout' |
| 93 | elif not output_file: |
| 94 | # Pixz defaults to a `.pxz` suffix (or `.tpxz` if it's compressing a |
| 95 | # tar file). We need the suffix to be consistent, so force it here. |
| 96 | output_file = f'{compressed_file_name}.xz' |
| 97 | autodelete_input_file = True |
| 98 | |
| 99 | cmd += raw_flag_list |
| 100 | cmd.append(compressed_file_name) |
| 101 | if output_file: |
| 102 | cmd.append(output_file) |
| 103 | |
| 104 | if not autodelete_input_file: |
| 105 | Execvp(cmd) |
| 106 | |
| 107 | return_code = subprocess.call(cmd) |
| 108 | if not return_code: |
| 109 | os.unlink(compressed_file_name) |
| 110 | sys.exit(return_code) |
| 111 | |
| 112 | |
| 113 | def ExecDecompressCommand(stdout, argv): |
| 114 | """Execs decompression command.""" |
Ben Pastene | c6bf549 | 2020-08-28 17:35:01 -0700 | [diff] [blame] | 115 | if HasPixz(): |
George Burgess IV | 757887f | 2021-09-22 15:58:35 -0700 | [diff] [blame^] | 116 | cmd = ['pixz'] |
| 117 | cmd.append('-d') |
| 118 | |
| 119 | _, compressed_file_name, _ = ParsePixzArgs(argv) |
Tiancong Wang | ac3fc4a | 2020-09-11 10:44:03 -0700 | [diff] [blame] | 120 | if stdout: |
| 121 | # Explicitly tell pixz the file is the input, so it will dump the output |
| 122 | # to stdout, instead of automatically choosing an output name. |
| 123 | cmd.append('-i') |
George Burgess IV | 757887f | 2021-09-22 15:58:35 -0700 | [diff] [blame^] | 124 | if not compressed_file_name: |
| 125 | cmd.append('/dev/stdin') |
| 126 | elif not compressed_file_name: |
| 127 | cmd += ['-i', '/dev/stdin'] |
| 128 | cmd += argv |
| 129 | Execvp(cmd) |
| 130 | |
| 131 | cmd = ['xz'] |
Tiancong Wang | ac3fc4a | 2020-09-11 10:44:03 -0700 | [diff] [blame] | 132 | if stdout: |
George Burgess IV | 757887f | 2021-09-22 15:58:35 -0700 | [diff] [blame^] | 133 | cmd.append('-dc') |
| 134 | else: |
| 135 | cmd.append('-d') |
| 136 | cmd += argv |
| 137 | Execvp(cmd) |
Ben Pastene | c6bf549 | 2020-08-28 17:35:01 -0700 | [diff] [blame] | 138 | |
| 139 | |
| 140 | def GetParser(): |
| 141 | """Return a command line parser.""" |
| 142 | parser = commandline.ArgumentParser(description=__doc__) |
| 143 | parser.add_argument( |
Tiancong Wang | ac3fc4a | 2020-09-11 10:44:03 -0700 | [diff] [blame] | 144 | '-d', |
| 145 | '--decompress', |
| 146 | '--uncompress', |
Ben Pastene | c6bf549 | 2020-08-28 17:35:01 -0700 | [diff] [blame] | 147 | help='Decompress rather than compress.', |
| 148 | action='store_true') |
Tiancong Wang | ac3fc4a | 2020-09-11 10:44:03 -0700 | [diff] [blame] | 149 | parser.add_argument( |
| 150 | '-c', |
| 151 | dest='stdout', |
| 152 | action='store_true', |
| 153 | help="Write to standard output and don't delete input files.") |
Ben Pastene | c6bf549 | 2020-08-28 17:35:01 -0700 | [diff] [blame] | 154 | return parser |
| 155 | |
| 156 | |
Benjamin Gordon | 1f4537f | 2019-12-06 09:10:56 -0700 | [diff] [blame] | 157 | def main(argv): |
Ben Pastene | c6bf549 | 2020-08-28 17:35:01 -0700 | [diff] [blame] | 158 | parser = GetParser() |
| 159 | known_args, argv = parser.parse_known_args() |
Tiancong Wang | ac3fc4a | 2020-09-11 10:44:03 -0700 | [diff] [blame] | 160 | if '-i' in argv or '-o' in argv: |
| 161 | parser.error('It is invalid to use -i or -o with xz_auto') |
| 162 | |
Ben Pastene | c6bf549 | 2020-08-28 17:35:01 -0700 | [diff] [blame] | 163 | if known_args.decompress: |
George Burgess IV | 757887f | 2021-09-22 15:58:35 -0700 | [diff] [blame^] | 164 | ExecDecompressCommand(known_args.stdout, argv) |
Tiancong Wang | ac3fc4a | 2020-09-11 10:44:03 -0700 | [diff] [blame] | 165 | else: |
George Burgess IV | 757887f | 2021-09-22 15:58:35 -0700 | [diff] [blame^] | 166 | ExecCompressCommand(known_args.stdout, argv) |