xref: /aosp_15_r20/tools/external_updater/archive_utils.py (revision 3c875a214f382db1236d28570d1304ce57138f32)
1*3c875a21SAndroid Build Coastguard Worker# Copyright (C) 2018 The Android Open Source Project
2*3c875a21SAndroid Build Coastguard Worker#
3*3c875a21SAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
4*3c875a21SAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
5*3c875a21SAndroid Build Coastguard Worker# You may obtain a copy of the License at
6*3c875a21SAndroid Build Coastguard Worker#
7*3c875a21SAndroid Build Coastguard Worker#      http://www.apache.org/licenses/LICENSE-2.0
8*3c875a21SAndroid Build Coastguard Worker#
9*3c875a21SAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
10*3c875a21SAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
11*3c875a21SAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*3c875a21SAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
13*3c875a21SAndroid Build Coastguard Worker# limitations under the License.
14*3c875a21SAndroid Build Coastguard Worker"""Functions to process archive files."""
15*3c875a21SAndroid Build Coastguard Worker
16*3c875a21SAndroid Build Coastguard Workerimport os
17*3c875a21SAndroid Build Coastguard Workerimport tempfile
18*3c875a21SAndroid Build Coastguard Workerimport tarfile
19*3c875a21SAndroid Build Coastguard Workerimport urllib.parse
20*3c875a21SAndroid Build Coastguard Workerimport zipfile
21*3c875a21SAndroid Build Coastguard Worker
22*3c875a21SAndroid Build Coastguard Worker
23*3c875a21SAndroid Build Coastguard Workerclass ZipFileWithPermission(zipfile.ZipFile):
24*3c875a21SAndroid Build Coastguard Worker    """Subclassing Zipfile to preserve file permission.
25*3c875a21SAndroid Build Coastguard Worker
26*3c875a21SAndroid Build Coastguard Worker    See https://bugs.python.org/issue15795
27*3c875a21SAndroid Build Coastguard Worker    """
28*3c875a21SAndroid Build Coastguard Worker    def _extract_member(self, member, targetpath, pwd):
29*3c875a21SAndroid Build Coastguard Worker        ret_val = super()._extract_member(member, targetpath, pwd)
30*3c875a21SAndroid Build Coastguard Worker
31*3c875a21SAndroid Build Coastguard Worker        if not isinstance(member, zipfile.ZipInfo):
32*3c875a21SAndroid Build Coastguard Worker            member = self.getinfo(member)
33*3c875a21SAndroid Build Coastguard Worker        attr = member.external_attr >> 16
34*3c875a21SAndroid Build Coastguard Worker        if attr != 0:
35*3c875a21SAndroid Build Coastguard Worker            os.chmod(ret_val, attr)
36*3c875a21SAndroid Build Coastguard Worker        return ret_val
37*3c875a21SAndroid Build Coastguard Worker
38*3c875a21SAndroid Build Coastguard Worker
39*3c875a21SAndroid Build Coastguard Workerdef unzip(archive_path, target_path):
40*3c875a21SAndroid Build Coastguard Worker    """Extracts zip file to a path.
41*3c875a21SAndroid Build Coastguard Worker
42*3c875a21SAndroid Build Coastguard Worker    Args:
43*3c875a21SAndroid Build Coastguard Worker        archive_path: Path to the zip file.
44*3c875a21SAndroid Build Coastguard Worker        target_path: Path to extract files to.
45*3c875a21SAndroid Build Coastguard Worker    """
46*3c875a21SAndroid Build Coastguard Worker
47*3c875a21SAndroid Build Coastguard Worker    with ZipFileWithPermission(archive_path) as zfile:
48*3c875a21SAndroid Build Coastguard Worker        zfile.extractall(target_path)
49*3c875a21SAndroid Build Coastguard Worker
50*3c875a21SAndroid Build Coastguard Worker
51*3c875a21SAndroid Build Coastguard Workerdef untar(archive_path, target_path):
52*3c875a21SAndroid Build Coastguard Worker    """Extracts tar file to a path.
53*3c875a21SAndroid Build Coastguard Worker
54*3c875a21SAndroid Build Coastguard Worker    Args:
55*3c875a21SAndroid Build Coastguard Worker        archive_path: Path to the tar file.
56*3c875a21SAndroid Build Coastguard Worker        target_path: Path to extract files to.
57*3c875a21SAndroid Build Coastguard Worker    """
58*3c875a21SAndroid Build Coastguard Worker
59*3c875a21SAndroid Build Coastguard Worker    with tarfile.open(archive_path, mode='r') as tfile:
60*3c875a21SAndroid Build Coastguard Worker        tfile.extractall(target_path)
61*3c875a21SAndroid Build Coastguard Worker
62*3c875a21SAndroid Build Coastguard Worker
63*3c875a21SAndroid Build Coastguard WorkerARCHIVE_TYPES = {
64*3c875a21SAndroid Build Coastguard Worker    '.zip': unzip,
65*3c875a21SAndroid Build Coastguard Worker    '.tar.gz': untar,
66*3c875a21SAndroid Build Coastguard Worker    '.tar.bz2': untar,
67*3c875a21SAndroid Build Coastguard Worker    '.tar.xz': untar,
68*3c875a21SAndroid Build Coastguard Worker}
69*3c875a21SAndroid Build Coastguard Worker
70*3c875a21SAndroid Build Coastguard Worker
71*3c875a21SAndroid Build Coastguard Workerdef is_supported_archive(url):
72*3c875a21SAndroid Build Coastguard Worker    """Checks whether the url points to a supported archive."""
73*3c875a21SAndroid Build Coastguard Worker    return get_extract_func(url) is not None
74*3c875a21SAndroid Build Coastguard Worker
75*3c875a21SAndroid Build Coastguard Worker
76*3c875a21SAndroid Build Coastguard Workerdef get_extract_func(url):
77*3c875a21SAndroid Build Coastguard Worker    """Gets the function to extract an archive.
78*3c875a21SAndroid Build Coastguard Worker
79*3c875a21SAndroid Build Coastguard Worker    Args:
80*3c875a21SAndroid Build Coastguard Worker        url: The url to the archive file.
81*3c875a21SAndroid Build Coastguard Worker
82*3c875a21SAndroid Build Coastguard Worker    Returns:
83*3c875a21SAndroid Build Coastguard Worker        A function to extract the archive. None if not found.
84*3c875a21SAndroid Build Coastguard Worker    """
85*3c875a21SAndroid Build Coastguard Worker
86*3c875a21SAndroid Build Coastguard Worker    parsed_url = urllib.parse.urlparse(url)
87*3c875a21SAndroid Build Coastguard Worker    filename = os.path.basename(parsed_url.path)
88*3c875a21SAndroid Build Coastguard Worker    for ext, func in ARCHIVE_TYPES.items():
89*3c875a21SAndroid Build Coastguard Worker        if filename.endswith(ext):
90*3c875a21SAndroid Build Coastguard Worker            return func
91*3c875a21SAndroid Build Coastguard Worker    # crates.io download url does not have file suffix
92*3c875a21SAndroid Build Coastguard Worker    # e.g., https://crates.io/api/v1/crates/syn/1.0.16/download
93*3c875a21SAndroid Build Coastguard Worker    if url.find('/crates.io/api/') > 0 or url.find('/static.crates.io/crates/'):
94*3c875a21SAndroid Build Coastguard Worker        return untar
95*3c875a21SAndroid Build Coastguard Worker    return None
96*3c875a21SAndroid Build Coastguard Worker
97*3c875a21SAndroid Build Coastguard Worker
98*3c875a21SAndroid Build Coastguard Workerdef download_and_extract(url):
99*3c875a21SAndroid Build Coastguard Worker    """Downloads and extracts an archive file to a temporary directory.
100*3c875a21SAndroid Build Coastguard Worker
101*3c875a21SAndroid Build Coastguard Worker    Args:
102*3c875a21SAndroid Build Coastguard Worker        url: Url to download.
103*3c875a21SAndroid Build Coastguard Worker
104*3c875a21SAndroid Build Coastguard Worker    Returns:
105*3c875a21SAndroid Build Coastguard Worker        Path to the temporary directory.
106*3c875a21SAndroid Build Coastguard Worker    """
107*3c875a21SAndroid Build Coastguard Worker
108*3c875a21SAndroid Build Coastguard Worker    print(f'Downloading {url}')
109*3c875a21SAndroid Build Coastguard Worker    archive_file, _headers = urllib.request.urlretrieve(url)
110*3c875a21SAndroid Build Coastguard Worker
111*3c875a21SAndroid Build Coastguard Worker    temporary_dir = tempfile.mkdtemp()
112*3c875a21SAndroid Build Coastguard Worker    print(f'Extracting {archive_file} to {temporary_dir}')
113*3c875a21SAndroid Build Coastguard Worker    get_extract_func(url)(archive_file, temporary_dir)
114*3c875a21SAndroid Build Coastguard Worker
115*3c875a21SAndroid Build Coastguard Worker    return temporary_dir
116*3c875a21SAndroid Build Coastguard Worker
117*3c875a21SAndroid Build Coastguard Worker
118*3c875a21SAndroid Build Coastguard Workerdef find_archive_root(path):
119*3c875a21SAndroid Build Coastguard Worker    """Finds the real root of an extracted archive.
120*3c875a21SAndroid Build Coastguard Worker
121*3c875a21SAndroid Build Coastguard Worker    Sometimes archives has additional layers of directories. This function tries
122*3c875a21SAndroid Build Coastguard Worker    to guess the right 'root' path by entering all single subdirectories.
123*3c875a21SAndroid Build Coastguard Worker
124*3c875a21SAndroid Build Coastguard Worker    Args:
125*3c875a21SAndroid Build Coastguard Worker        path: Path to the extracted archive.
126*3c875a21SAndroid Build Coastguard Worker
127*3c875a21SAndroid Build Coastguard Worker    Returns:
128*3c875a21SAndroid Build Coastguard Worker        The root path we found.
129*3c875a21SAndroid Build Coastguard Worker    """
130*3c875a21SAndroid Build Coastguard Worker    for root, dirs, files in os.walk(path):
131*3c875a21SAndroid Build Coastguard Worker        if files or len(dirs) > 1:
132*3c875a21SAndroid Build Coastguard Worker            return root
133*3c875a21SAndroid Build Coastguard Worker    return path
134