László Csomor | 5f99fda | 2017-08-11 09:28:12 +0200 | [diff] [blame] | 1 | # 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 | # |
Googler | bd7a6b9 | 2022-02-24 07:38:58 -0800 | [diff] [blame] | 8 | # http://www.apache.org/licenses/LICENSE-2.0 |
László Csomor | 5f99fda | 2017-08-11 09:28:12 +0200 | [diff] [blame] | 9 | # |
| 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 | |
| 17 | import contextlib |
| 18 | import os.path |
| 19 | import stat |
| 20 | import tarfile |
| 21 | import zipfile |
| 22 | |
| 23 | |
| 24 | def 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 | |
| 29 | def 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 | |
| 34 | def 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 | |
| 71 | def 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)) |