xref: /aosp_15_r20/build/make/tools/generate-self-extracting-archive.py (revision 9e94795a3d4ef5c1d47486f9a02bb378756cea8a)
1*9e94795aSAndroid Build Coastguard Worker#!/usr/bin/env python3
2*9e94795aSAndroid Build Coastguard Worker#
3*9e94795aSAndroid Build Coastguard Worker# Copyright (C) 2019 The Android Open Source Project
4*9e94795aSAndroid Build Coastguard Worker#
5*9e94795aSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
6*9e94795aSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
7*9e94795aSAndroid Build Coastguard Worker# You may obtain a copy of the License at
8*9e94795aSAndroid Build Coastguard Worker#
9*9e94795aSAndroid Build Coastguard Worker#      http://www.apache.org/licenses/LICENSE-2.0
10*9e94795aSAndroid Build Coastguard Worker#
11*9e94795aSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
12*9e94795aSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
13*9e94795aSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14*9e94795aSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
15*9e94795aSAndroid Build Coastguard Worker# limitations under the License.
16*9e94795aSAndroid Build Coastguard Worker
17*9e94795aSAndroid Build Coastguard Worker"""
18*9e94795aSAndroid Build Coastguard WorkerGenerates a self extracting archive with a license click through.
19*9e94795aSAndroid Build Coastguard Worker
20*9e94795aSAndroid Build Coastguard WorkerUsage:
21*9e94795aSAndroid Build Coastguard Worker  generate-self-extracting-archive.py $OUTPUT_FILE $INPUT_ARCHIVE $COMMENT $LICENSE_FILE
22*9e94795aSAndroid Build Coastguard Worker
23*9e94795aSAndroid Build Coastguard Worker  The comment will be included at the beginning of the output archive file.
24*9e94795aSAndroid Build Coastguard Worker
25*9e94795aSAndroid Build Coastguard WorkerOutput:
26*9e94795aSAndroid Build Coastguard Worker  The output of the script is a single executable file that when run will
27*9e94795aSAndroid Build Coastguard Worker  display the provided license and if the user accepts extract the wrapped
28*9e94795aSAndroid Build Coastguard Worker  archive.
29*9e94795aSAndroid Build Coastguard Worker
30*9e94795aSAndroid Build Coastguard Worker  The layout of the output file is roughly:
31*9e94795aSAndroid Build Coastguard Worker   * Executable shell script that extracts the archive
32*9e94795aSAndroid Build Coastguard Worker   * Actual archive contents
33*9e94795aSAndroid Build Coastguard Worker   * Zip file containing the license
34*9e94795aSAndroid Build Coastguard Worker"""
35*9e94795aSAndroid Build Coastguard Worker
36*9e94795aSAndroid Build Coastguard Workerimport tempfile
37*9e94795aSAndroid Build Coastguard Workerimport sys
38*9e94795aSAndroid Build Coastguard Workerimport os
39*9e94795aSAndroid Build Coastguard Workerimport zipfile
40*9e94795aSAndroid Build Coastguard Worker
41*9e94795aSAndroid Build Coastguard Worker_HEADER_TEMPLATE = """#!/bin/bash
42*9e94795aSAndroid Build Coastguard Worker#
43*9e94795aSAndroid Build Coastguard Worker{comment_line}
44*9e94795aSAndroid Build Coastguard Worker#
45*9e94795aSAndroid Build Coastguard Worker# Usage is subject to the enclosed license agreement
46*9e94795aSAndroid Build Coastguard Worker
47*9e94795aSAndroid Build Coastguard Workerecho
48*9e94795aSAndroid Build Coastguard Workerecho The license for this software will now be displayed.
49*9e94795aSAndroid Build Coastguard Workerecho You must agree to this license before using this software.
50*9e94795aSAndroid Build Coastguard Workerecho
51*9e94795aSAndroid Build Coastguard Workerecho -n Press Enter to view the license
52*9e94795aSAndroid Build Coastguard Workerread dummy
53*9e94795aSAndroid Build Coastguard Workerecho
54*9e94795aSAndroid Build Coastguard Workermore << EndOfLicense
55*9e94795aSAndroid Build Coastguard Worker{license}
56*9e94795aSAndroid Build Coastguard WorkerEndOfLicense
57*9e94795aSAndroid Build Coastguard Worker
58*9e94795aSAndroid Build Coastguard Workerif test $? != 0
59*9e94795aSAndroid Build Coastguard Workerthen
60*9e94795aSAndroid Build Coastguard Worker  echo "ERROR: Couldn't display license file" 1>&2
61*9e94795aSAndroid Build Coastguard Worker  exit 1
62*9e94795aSAndroid Build Coastguard Workerfi
63*9e94795aSAndroid Build Coastguard Workerecho
64*9e94795aSAndroid Build Coastguard Workerecho -n 'Type "I ACCEPT" if you agree to the terms of the license: '
65*9e94795aSAndroid Build Coastguard Workerread typed
66*9e94795aSAndroid Build Coastguard Workerif test "$typed" != "I ACCEPT"
67*9e94795aSAndroid Build Coastguard Workerthen
68*9e94795aSAndroid Build Coastguard Worker  echo
69*9e94795aSAndroid Build Coastguard Worker  echo "You didn't accept the license. Extraction aborted."
70*9e94795aSAndroid Build Coastguard Worker  exit 2
71*9e94795aSAndroid Build Coastguard Workerfi
72*9e94795aSAndroid Build Coastguard Workerecho
73*9e94795aSAndroid Build Coastguard Worker{extract_command}
74*9e94795aSAndroid Build Coastguard Workerif test $? != 0
75*9e94795aSAndroid Build Coastguard Workerthen
76*9e94795aSAndroid Build Coastguard Worker  echo
77*9e94795aSAndroid Build Coastguard Worker  echo "ERROR: Couldn't extract files." 1>&2
78*9e94795aSAndroid Build Coastguard Worker  exit 3
79*9e94795aSAndroid Build Coastguard Workerelse
80*9e94795aSAndroid Build Coastguard Worker  echo
81*9e94795aSAndroid Build Coastguard Worker  echo "Files extracted successfully."
82*9e94795aSAndroid Build Coastguard Workerfi
83*9e94795aSAndroid Build Coastguard Workerexit 0
84*9e94795aSAndroid Build Coastguard Worker"""
85*9e94795aSAndroid Build Coastguard Worker
86*9e94795aSAndroid Build Coastguard Worker_PIPE_CHUNK_SIZE = 1048576
87*9e94795aSAndroid Build Coastguard Workerdef _pipe_bytes(src, dst):
88*9e94795aSAndroid Build Coastguard Worker  while True:
89*9e94795aSAndroid Build Coastguard Worker    b = src.read(_PIPE_CHUNK_SIZE)
90*9e94795aSAndroid Build Coastguard Worker    if not b:
91*9e94795aSAndroid Build Coastguard Worker      break
92*9e94795aSAndroid Build Coastguard Worker    dst.write(b)
93*9e94795aSAndroid Build Coastguard Worker
94*9e94795aSAndroid Build Coastguard Worker_MAX_OFFSET_WIDTH = 20
95*9e94795aSAndroid Build Coastguard Workerdef _generate_extract_command(start, size, extract_name):
96*9e94795aSAndroid Build Coastguard Worker  """Generate the extract command.
97*9e94795aSAndroid Build Coastguard Worker
98*9e94795aSAndroid Build Coastguard Worker  The length of this string must be constant no matter what the start and end
99*9e94795aSAndroid Build Coastguard Worker  offsets are so that its length can be computed before the actual command is
100*9e94795aSAndroid Build Coastguard Worker  generated.
101*9e94795aSAndroid Build Coastguard Worker
102*9e94795aSAndroid Build Coastguard Worker  Args:
103*9e94795aSAndroid Build Coastguard Worker    start: offset in bytes of the start of the wrapped file
104*9e94795aSAndroid Build Coastguard Worker    size: size in bytes of the wrapped file
105*9e94795aSAndroid Build Coastguard Worker    extract_name: of the file to create when extracted
106*9e94795aSAndroid Build Coastguard Worker
107*9e94795aSAndroid Build Coastguard Worker  """
108*9e94795aSAndroid Build Coastguard Worker  # start gets an extra character for the '+'
109*9e94795aSAndroid Build Coastguard Worker  # for tail +1 is the start of the file, not +0
110*9e94795aSAndroid Build Coastguard Worker  start_str = ('+%d' % (start + 1)).rjust(_MAX_OFFSET_WIDTH + 1)
111*9e94795aSAndroid Build Coastguard Worker  if len(start_str) != _MAX_OFFSET_WIDTH + 1:
112*9e94795aSAndroid Build Coastguard Worker    raise Exception('Start offset too large (%d)' % start)
113*9e94795aSAndroid Build Coastguard Worker
114*9e94795aSAndroid Build Coastguard Worker  size_str = ('%d' % size).rjust(_MAX_OFFSET_WIDTH)
115*9e94795aSAndroid Build Coastguard Worker  if len(size_str) != _MAX_OFFSET_WIDTH:
116*9e94795aSAndroid Build Coastguard Worker    raise Exception('Size too large (%d)' % size)
117*9e94795aSAndroid Build Coastguard Worker
118*9e94795aSAndroid Build Coastguard Worker  return "tail -c %s $0 | head -c %s > %s\n" % (start_str, size_str, extract_name)
119*9e94795aSAndroid Build Coastguard Worker
120*9e94795aSAndroid Build Coastguard Worker
121*9e94795aSAndroid Build Coastguard Workerdef main(argv):
122*9e94795aSAndroid Build Coastguard Worker  if len(argv) != 5:
123*9e94795aSAndroid Build Coastguard Worker    print('generate-self-extracting-archive.py expects exactly 4 arguments')
124*9e94795aSAndroid Build Coastguard Worker    sys.exit(1)
125*9e94795aSAndroid Build Coastguard Worker
126*9e94795aSAndroid Build Coastguard Worker  output_filename = argv[1]
127*9e94795aSAndroid Build Coastguard Worker  input_archive_filename = argv[2]
128*9e94795aSAndroid Build Coastguard Worker  comment = argv[3]
129*9e94795aSAndroid Build Coastguard Worker  license_filename = argv[4]
130*9e94795aSAndroid Build Coastguard Worker
131*9e94795aSAndroid Build Coastguard Worker  input_archive_size = os.stat(input_archive_filename).st_size
132*9e94795aSAndroid Build Coastguard Worker
133*9e94795aSAndroid Build Coastguard Worker  with open(license_filename, 'r') as license_file:
134*9e94795aSAndroid Build Coastguard Worker    license = license_file.read()
135*9e94795aSAndroid Build Coastguard Worker
136*9e94795aSAndroid Build Coastguard Worker  if not license:
137*9e94795aSAndroid Build Coastguard Worker    print('License file was empty')
138*9e94795aSAndroid Build Coastguard Worker    sys.exit(1)
139*9e94795aSAndroid Build Coastguard Worker
140*9e94795aSAndroid Build Coastguard Worker  if 'SOFTWARE LICENSE AGREEMENT' not in license:
141*9e94795aSAndroid Build Coastguard Worker    print('License does not look like a license')
142*9e94795aSAndroid Build Coastguard Worker    sys.exit(1)
143*9e94795aSAndroid Build Coastguard Worker
144*9e94795aSAndroid Build Coastguard Worker  comment_line = '# %s\n' % comment
145*9e94795aSAndroid Build Coastguard Worker  extract_name = os.path.basename(input_archive_filename)
146*9e94795aSAndroid Build Coastguard Worker
147*9e94795aSAndroid Build Coastguard Worker  # Compute the size of the header before writing the file out. This is required
148*9e94795aSAndroid Build Coastguard Worker  # so that the extract command, which uses the contents offset, can be created
149*9e94795aSAndroid Build Coastguard Worker  # and included inside the header.
150*9e94795aSAndroid Build Coastguard Worker  header_for_size = _HEADER_TEMPLATE.format(
151*9e94795aSAndroid Build Coastguard Worker      comment_line=comment_line,
152*9e94795aSAndroid Build Coastguard Worker      license=license,
153*9e94795aSAndroid Build Coastguard Worker      extract_command=_generate_extract_command(0, 0, extract_name),
154*9e94795aSAndroid Build Coastguard Worker  )
155*9e94795aSAndroid Build Coastguard Worker  header_size = len(header_for_size.encode('utf-8'))
156*9e94795aSAndroid Build Coastguard Worker
157*9e94795aSAndroid Build Coastguard Worker  # write the final output
158*9e94795aSAndroid Build Coastguard Worker  with open(output_filename, 'wb') as output:
159*9e94795aSAndroid Build Coastguard Worker    output.write(_HEADER_TEMPLATE.format(
160*9e94795aSAndroid Build Coastguard Worker        comment_line=comment_line,
161*9e94795aSAndroid Build Coastguard Worker        license=license,
162*9e94795aSAndroid Build Coastguard Worker        extract_command=_generate_extract_command(header_size, input_archive_size, extract_name),
163*9e94795aSAndroid Build Coastguard Worker    ).encode('utf-8'))
164*9e94795aSAndroid Build Coastguard Worker
165*9e94795aSAndroid Build Coastguard Worker    with open(input_archive_filename, 'rb') as input_file:
166*9e94795aSAndroid Build Coastguard Worker      _pipe_bytes(input_file, output)
167*9e94795aSAndroid Build Coastguard Worker
168*9e94795aSAndroid Build Coastguard Worker    with tempfile.TemporaryFile() as trailing_zip:
169*9e94795aSAndroid Build Coastguard Worker      with zipfile.ZipFile(trailing_zip, 'w') as myzip:
170*9e94795aSAndroid Build Coastguard Worker        myzip.writestr('license.txt', license, compress_type=zipfile.ZIP_STORED)
171*9e94795aSAndroid Build Coastguard Worker
172*9e94795aSAndroid Build Coastguard Worker      # append the trailing zip to the end of the file
173*9e94795aSAndroid Build Coastguard Worker      trailing_zip.seek(0)
174*9e94795aSAndroid Build Coastguard Worker      _pipe_bytes(trailing_zip, output)
175*9e94795aSAndroid Build Coastguard Worker
176*9e94795aSAndroid Build Coastguard Worker  umask = os.umask(0)
177*9e94795aSAndroid Build Coastguard Worker  os.umask(umask)
178*9e94795aSAndroid Build Coastguard Worker  os.chmod(output_filename, 0o777 & ~umask)
179*9e94795aSAndroid Build Coastguard Worker
180*9e94795aSAndroid Build Coastguard Workerif __name__ == "__main__":
181*9e94795aSAndroid Build Coastguard Worker  main(sys.argv)
182