1*8fb009dcSAndroid Build Coastguard Worker# Copyright (c) 2015, Google Inc. 2*8fb009dcSAndroid Build Coastguard Worker# 3*8fb009dcSAndroid Build Coastguard Worker# Permission to use, copy, modify, and/or distribute this software for any 4*8fb009dcSAndroid Build Coastguard Worker# purpose with or without fee is hereby granted, provided that the above 5*8fb009dcSAndroid Build Coastguard Worker# copyright notice and this permission notice appear in all copies. 6*8fb009dcSAndroid Build Coastguard Worker# 7*8fb009dcSAndroid Build Coastguard Worker# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8*8fb009dcSAndroid Build Coastguard Worker# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9*8fb009dcSAndroid Build Coastguard Worker# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10*8fb009dcSAndroid Build Coastguard Worker# SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11*8fb009dcSAndroid Build Coastguard Worker# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12*8fb009dcSAndroid Build Coastguard Worker# OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13*8fb009dcSAndroid Build Coastguard Worker# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14*8fb009dcSAndroid Build Coastguard Worker 15*8fb009dcSAndroid Build Coastguard Worker"""Extracts archives.""" 16*8fb009dcSAndroid Build Coastguard Worker 17*8fb009dcSAndroid Build Coastguard Worker 18*8fb009dcSAndroid Build Coastguard Workerimport hashlib 19*8fb009dcSAndroid Build Coastguard Workerimport optparse 20*8fb009dcSAndroid Build Coastguard Workerimport os 21*8fb009dcSAndroid Build Coastguard Workerimport os.path 22*8fb009dcSAndroid Build Coastguard Workerimport tarfile 23*8fb009dcSAndroid Build Coastguard Workerimport shutil 24*8fb009dcSAndroid Build Coastguard Workerimport sys 25*8fb009dcSAndroid Build Coastguard Workerimport zipfile 26*8fb009dcSAndroid Build Coastguard Worker 27*8fb009dcSAndroid Build Coastguard Worker 28*8fb009dcSAndroid Build Coastguard Workerdef CheckedJoin(output, path): 29*8fb009dcSAndroid Build Coastguard Worker """ 30*8fb009dcSAndroid Build Coastguard Worker CheckedJoin returns os.path.join(output, path). It does sanity checks to 31*8fb009dcSAndroid Build Coastguard Worker ensure the resulting path is under output, but shouldn't be used on untrusted 32*8fb009dcSAndroid Build Coastguard Worker input. 33*8fb009dcSAndroid Build Coastguard Worker """ 34*8fb009dcSAndroid Build Coastguard Worker path = os.path.normpath(path) 35*8fb009dcSAndroid Build Coastguard Worker if os.path.isabs(path) or path.startswith('.'): 36*8fb009dcSAndroid Build Coastguard Worker raise ValueError(path) 37*8fb009dcSAndroid Build Coastguard Worker return os.path.join(output, path) 38*8fb009dcSAndroid Build Coastguard Worker 39*8fb009dcSAndroid Build Coastguard Worker 40*8fb009dcSAndroid Build Coastguard Workerclass FileEntry(object): 41*8fb009dcSAndroid Build Coastguard Worker def __init__(self, path, mode, fileobj): 42*8fb009dcSAndroid Build Coastguard Worker self.path = path 43*8fb009dcSAndroid Build Coastguard Worker self.mode = mode 44*8fb009dcSAndroid Build Coastguard Worker self.fileobj = fileobj 45*8fb009dcSAndroid Build Coastguard Worker 46*8fb009dcSAndroid Build Coastguard Worker 47*8fb009dcSAndroid Build Coastguard Workerclass SymlinkEntry(object): 48*8fb009dcSAndroid Build Coastguard Worker def __init__(self, path, mode, target): 49*8fb009dcSAndroid Build Coastguard Worker self.path = path 50*8fb009dcSAndroid Build Coastguard Worker self.mode = mode 51*8fb009dcSAndroid Build Coastguard Worker self.target = target 52*8fb009dcSAndroid Build Coastguard Worker 53*8fb009dcSAndroid Build Coastguard Worker 54*8fb009dcSAndroid Build Coastguard Workerdef IterateZip(path): 55*8fb009dcSAndroid Build Coastguard Worker """ 56*8fb009dcSAndroid Build Coastguard Worker IterateZip opens the zip file at path and returns a generator of entry objects 57*8fb009dcSAndroid Build Coastguard Worker for each file in it. 58*8fb009dcSAndroid Build Coastguard Worker """ 59*8fb009dcSAndroid Build Coastguard Worker with zipfile.ZipFile(path, 'r') as zip_file: 60*8fb009dcSAndroid Build Coastguard Worker for info in zip_file.infolist(): 61*8fb009dcSAndroid Build Coastguard Worker if info.filename.endswith('/'): 62*8fb009dcSAndroid Build Coastguard Worker continue 63*8fb009dcSAndroid Build Coastguard Worker yield FileEntry(info.filename, None, zip_file.open(info)) 64*8fb009dcSAndroid Build Coastguard Worker 65*8fb009dcSAndroid Build Coastguard Worker 66*8fb009dcSAndroid Build Coastguard Workerdef IterateTar(path, compression): 67*8fb009dcSAndroid Build Coastguard Worker """ 68*8fb009dcSAndroid Build Coastguard Worker IterateTar opens the tar.gz or tar.bz2 file at path and returns a generator of 69*8fb009dcSAndroid Build Coastguard Worker entry objects for each file in it. 70*8fb009dcSAndroid Build Coastguard Worker """ 71*8fb009dcSAndroid Build Coastguard Worker with tarfile.open(path, 'r:' + compression) as tar_file: 72*8fb009dcSAndroid Build Coastguard Worker for info in tar_file: 73*8fb009dcSAndroid Build Coastguard Worker if info.isdir(): 74*8fb009dcSAndroid Build Coastguard Worker pass 75*8fb009dcSAndroid Build Coastguard Worker elif info.issym(): 76*8fb009dcSAndroid Build Coastguard Worker yield SymlinkEntry(info.name, None, info.linkname) 77*8fb009dcSAndroid Build Coastguard Worker elif info.isfile(): 78*8fb009dcSAndroid Build Coastguard Worker yield FileEntry(info.name, info.mode, tar_file.extractfile(info)) 79*8fb009dcSAndroid Build Coastguard Worker else: 80*8fb009dcSAndroid Build Coastguard Worker raise ValueError('Unknown entry type "%s"' % (info.name, )) 81*8fb009dcSAndroid Build Coastguard Worker 82*8fb009dcSAndroid Build Coastguard Worker 83*8fb009dcSAndroid Build Coastguard Workerdef main(args): 84*8fb009dcSAndroid Build Coastguard Worker parser = optparse.OptionParser(usage='Usage: %prog ARCHIVE OUTPUT') 85*8fb009dcSAndroid Build Coastguard Worker parser.add_option('--no-prefix', dest='no_prefix', action='store_true', 86*8fb009dcSAndroid Build Coastguard Worker help='Do not remove a prefix from paths in the archive.') 87*8fb009dcSAndroid Build Coastguard Worker options, args = parser.parse_args(args) 88*8fb009dcSAndroid Build Coastguard Worker 89*8fb009dcSAndroid Build Coastguard Worker if len(args) != 2: 90*8fb009dcSAndroid Build Coastguard Worker parser.print_help() 91*8fb009dcSAndroid Build Coastguard Worker return 1 92*8fb009dcSAndroid Build Coastguard Worker 93*8fb009dcSAndroid Build Coastguard Worker archive, output = args 94*8fb009dcSAndroid Build Coastguard Worker 95*8fb009dcSAndroid Build Coastguard Worker if not os.path.exists(archive): 96*8fb009dcSAndroid Build Coastguard Worker # Skip archives that weren't downloaded. 97*8fb009dcSAndroid Build Coastguard Worker return 0 98*8fb009dcSAndroid Build Coastguard Worker 99*8fb009dcSAndroid Build Coastguard Worker with open(archive, 'rb') as f: 100*8fb009dcSAndroid Build Coastguard Worker sha256 = hashlib.sha256() 101*8fb009dcSAndroid Build Coastguard Worker while True: 102*8fb009dcSAndroid Build Coastguard Worker chunk = f.read(1024 * 1024) 103*8fb009dcSAndroid Build Coastguard Worker if not chunk: 104*8fb009dcSAndroid Build Coastguard Worker break 105*8fb009dcSAndroid Build Coastguard Worker sha256.update(chunk) 106*8fb009dcSAndroid Build Coastguard Worker digest = sha256.hexdigest() 107*8fb009dcSAndroid Build Coastguard Worker 108*8fb009dcSAndroid Build Coastguard Worker stamp_path = os.path.join(output, ".boringssl_archive_digest") 109*8fb009dcSAndroid Build Coastguard Worker if os.path.exists(stamp_path): 110*8fb009dcSAndroid Build Coastguard Worker with open(stamp_path) as f: 111*8fb009dcSAndroid Build Coastguard Worker if f.read().strip() == digest: 112*8fb009dcSAndroid Build Coastguard Worker print("Already up-to-date.") 113*8fb009dcSAndroid Build Coastguard Worker return 0 114*8fb009dcSAndroid Build Coastguard Worker 115*8fb009dcSAndroid Build Coastguard Worker if archive.endswith('.zip'): 116*8fb009dcSAndroid Build Coastguard Worker entries = IterateZip(archive) 117*8fb009dcSAndroid Build Coastguard Worker elif archive.endswith('.tar.gz'): 118*8fb009dcSAndroid Build Coastguard Worker entries = IterateTar(archive, 'gz') 119*8fb009dcSAndroid Build Coastguard Worker elif archive.endswith('.tar.bz2'): 120*8fb009dcSAndroid Build Coastguard Worker entries = IterateTar(archive, 'bz2') 121*8fb009dcSAndroid Build Coastguard Worker elif archive.endswith('.tar.xz'): 122*8fb009dcSAndroid Build Coastguard Worker entries = IterateTar(archive, 'xz') 123*8fb009dcSAndroid Build Coastguard Worker else: 124*8fb009dcSAndroid Build Coastguard Worker raise ValueError(archive) 125*8fb009dcSAndroid Build Coastguard Worker 126*8fb009dcSAndroid Build Coastguard Worker try: 127*8fb009dcSAndroid Build Coastguard Worker if os.path.exists(output): 128*8fb009dcSAndroid Build Coastguard Worker print("Removing %s" % (output, )) 129*8fb009dcSAndroid Build Coastguard Worker shutil.rmtree(output) 130*8fb009dcSAndroid Build Coastguard Worker 131*8fb009dcSAndroid Build Coastguard Worker print("Extracting %s to %s" % (archive, output)) 132*8fb009dcSAndroid Build Coastguard Worker prefix = None 133*8fb009dcSAndroid Build Coastguard Worker num_extracted = 0 134*8fb009dcSAndroid Build Coastguard Worker for entry in entries: 135*8fb009dcSAndroid Build Coastguard Worker # Even on Windows, zip files must always use forward slashes. 136*8fb009dcSAndroid Build Coastguard Worker if '\\' in entry.path or entry.path.startswith('/'): 137*8fb009dcSAndroid Build Coastguard Worker raise ValueError(entry.path) 138*8fb009dcSAndroid Build Coastguard Worker 139*8fb009dcSAndroid Build Coastguard Worker if not options.no_prefix: 140*8fb009dcSAndroid Build Coastguard Worker new_prefix, rest = entry.path.split('/', 1) 141*8fb009dcSAndroid Build Coastguard Worker 142*8fb009dcSAndroid Build Coastguard Worker # Ensure the archive is consistent. 143*8fb009dcSAndroid Build Coastguard Worker if prefix is None: 144*8fb009dcSAndroid Build Coastguard Worker prefix = new_prefix 145*8fb009dcSAndroid Build Coastguard Worker if prefix != new_prefix: 146*8fb009dcSAndroid Build Coastguard Worker raise ValueError((prefix, new_prefix)) 147*8fb009dcSAndroid Build Coastguard Worker else: 148*8fb009dcSAndroid Build Coastguard Worker rest = entry.path 149*8fb009dcSAndroid Build Coastguard Worker 150*8fb009dcSAndroid Build Coastguard Worker # Extract the file into the output directory. 151*8fb009dcSAndroid Build Coastguard Worker fixed_path = CheckedJoin(output, rest) 152*8fb009dcSAndroid Build Coastguard Worker if not os.path.isdir(os.path.dirname(fixed_path)): 153*8fb009dcSAndroid Build Coastguard Worker os.makedirs(os.path.dirname(fixed_path)) 154*8fb009dcSAndroid Build Coastguard Worker if isinstance(entry, FileEntry): 155*8fb009dcSAndroid Build Coastguard Worker with open(fixed_path, 'wb') as out: 156*8fb009dcSAndroid Build Coastguard Worker shutil.copyfileobj(entry.fileobj, out) 157*8fb009dcSAndroid Build Coastguard Worker elif isinstance(entry, SymlinkEntry): 158*8fb009dcSAndroid Build Coastguard Worker os.symlink(entry.target, fixed_path) 159*8fb009dcSAndroid Build Coastguard Worker else: 160*8fb009dcSAndroid Build Coastguard Worker raise TypeError('unknown entry type') 161*8fb009dcSAndroid Build Coastguard Worker 162*8fb009dcSAndroid Build Coastguard Worker # Fix up permissions if needbe. 163*8fb009dcSAndroid Build Coastguard Worker # TODO(davidben): To be extra tidy, this should only track the execute bit 164*8fb009dcSAndroid Build Coastguard Worker # as in git. 165*8fb009dcSAndroid Build Coastguard Worker if entry.mode is not None: 166*8fb009dcSAndroid Build Coastguard Worker os.chmod(fixed_path, entry.mode) 167*8fb009dcSAndroid Build Coastguard Worker 168*8fb009dcSAndroid Build Coastguard Worker # Print every 100 files, so bots do not time out on large archives. 169*8fb009dcSAndroid Build Coastguard Worker num_extracted += 1 170*8fb009dcSAndroid Build Coastguard Worker if num_extracted % 100 == 0: 171*8fb009dcSAndroid Build Coastguard Worker print("Extracted %d files..." % (num_extracted,)) 172*8fb009dcSAndroid Build Coastguard Worker finally: 173*8fb009dcSAndroid Build Coastguard Worker entries.close() 174*8fb009dcSAndroid Build Coastguard Worker 175*8fb009dcSAndroid Build Coastguard Worker with open(stamp_path, 'w') as f: 176*8fb009dcSAndroid Build Coastguard Worker f.write(digest) 177*8fb009dcSAndroid Build Coastguard Worker 178*8fb009dcSAndroid Build Coastguard Worker print("Done. Extracted %d files." % (num_extracted,)) 179*8fb009dcSAndroid Build Coastguard Worker return 0 180*8fb009dcSAndroid Build Coastguard Worker 181*8fb009dcSAndroid Build Coastguard Worker 182*8fb009dcSAndroid Build Coastguard Workerif __name__ == '__main__': 183*8fb009dcSAndroid Build Coastguard Worker sys.exit(main(sys.argv[1:])) 184