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