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