blob: cff0836c4d42b1df0120b07b9f9767718f9b802d [file] [log] [blame]
László Csomor5f99fda2017-08-11 09:28:12 +02001# pylint: disable=g-bad-file-header
2# Copyright 2017 The Bazel Authors. All rights reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
Googlerbd7a6b92022-02-24 07:38:58 -08008# http://www.apache.org/licenses/LICENSE-2.0
László Csomor5f99fda2017-08-11 09:28:12 +02009#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15"""Utils to the contents of a tar or zip file into another zip file."""
16
17import contextlib
18import os.path
19import stat
20import tarfile
21import zipfile
22
23
24def is_mode_executable(mode):
25 """Returns true if `mode` has any of the executable bits set."""
26 return mode & (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) > 0
27
28
29def is_executable(path):
30 """Returns true if `path` is an executable file/directory."""
31 return is_mode_executable(os.stat(path)[stat.ST_MODE])
32
33
34def copy_tar_to_zip(output_zip, input_file, process_filename=None):
35 """Copy a tar file's contents into a zip file.
36
37 This function unpacks every file from `input_file` and puts them into
38 `output_zip`. The unpacking is performed in-memory.
39
40 Args:
41 output_zip: zipfile.ZipFile; the destination archive
42 input_file: string; path to the source tar file
43 process_filename: function(str) -> str; optional; for a packed file entry in
44 `input_file` it computes the path in `output_zip`
45 """
46 with tarfile.open(input_file, 'r', errorlevel=2) as tar_file:
47 while True:
48 tar_entry = tar_file.next()
49 if tar_entry is None:
50 break
51 filename = (process_filename(tar_entry.name)
52 if process_filename else tar_entry.name)
53 zipinfo = zipfile.ZipInfo(filename, (1980, 1, 1, 0, 0, 0))
54 if tar_entry.isreg():
55 if is_mode_executable(tar_entry.mode):
56 zipinfo.external_attr = 0o755 << 16
57 else:
58 zipinfo.external_attr = 0o644 << 16
59 zipinfo.compress_type = zipfile.ZIP_DEFLATED
60 output_zip.writestr(zipinfo, tar_file.extractfile(tar_entry).read())
61 elif tar_entry.issym():
62 # 0120000 originally comes from the definition of S_IFLNK and
63 # marks a symbolic link in the Zip file format.
64 zipinfo.external_attr = 0o120000 << 16
65 output_zip.writestr(zipinfo, tar_entry.linkname)
66 else:
67 # Ignore directories, hard links, special files, ...
68 pass
69
70
71def copy_zip_to_zip(output_zip, input_file, process_filename=None):
72 """Copy a zip file's contents into another zip file.
73
74 This function unpacks every file from `input_file` and puts them into
75 `output_zip`. The unpacking is performed in-memory.
76
77 Args:
78 output_zip: zipfile.ZipFile; the destination archive
79 input_file: string; path to the source tar file
80 process_filename: function(str) -> str; optional; for a packed file entry in
81 `input_file` it computes the path in `output_zip`
82 """
83 # Adding contextlib.closing to be python 2.6 (for centos 6.7) compatible
84 with contextlib.closing(zipfile.ZipFile(input_file, 'r')) as zip_file:
85 for zip_entry in zip_file.infolist():
86 filename = (process_filename(zip_entry.filename)
87 if process_filename else zip_entry.filename)
88 zipinfo = zipfile.ZipInfo(filename, (1980, 1, 1, 0, 0, 0))
89 if is_mode_executable(zip_entry.external_attr >> 16 & 0xFFFF):
90 zipinfo.external_attr = 0o755 << 16
91 else:
92 zipinfo.external_attr = 0o644 << 16
93 zipinfo.compress_type = zip_entry.compress_type
94 output_zip.writestr(zipinfo, zip_file.read(zip_entry))