xref: /aosp_15_r20/external/boringssl/src/util/bot/extract.py (revision 8fb009dc861624b67b6cdb62ea21f0f22d0c584b)
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