Nikita Ioffe | 96385f7 | 2019-08-25 17:56:30 +0100 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | # |
| 3 | # Copyright (C) 2019 The Android Open Source Project |
| 4 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | # you may not use this file except in compliance with the License. |
| 7 | # You may obtain a copy of the License at |
| 8 | # |
| 9 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | # |
| 11 | # Unless required by applicable law or agreed to in writing, software |
| 12 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | # See the License for the specific language governing permissions and |
| 15 | # limitations under the License. |
| 16 | """deapexer is a tool that prints out content of an APEX. |
| 17 | |
| 18 | To print content of an APEX to stdout: |
Nikita Ioffe | 62ae36a | 2019-10-30 00:16:03 +0000 | [diff] [blame] | 19 | deapexer list foo.apex |
Nikita Ioffe | 96385f7 | 2019-08-25 17:56:30 +0100 | [diff] [blame] | 20 | |
Nikita Ioffe | 62ae36a | 2019-10-30 00:16:03 +0000 | [diff] [blame] | 21 | To extract content of an APEX to the given directory: |
| 22 | deapexer extract foo.apex dest |
Nikita Ioffe | 96385f7 | 2019-08-25 17:56:30 +0100 | [diff] [blame] | 23 | """ |
Dario Freni | da006cf | 2020-01-02 10:36:59 +0000 | [diff] [blame] | 24 | from __future__ import print_function |
Nikita Ioffe | 96385f7 | 2019-08-25 17:56:30 +0100 | [diff] [blame] | 25 | |
Nikita Ioffe | 62ae36a | 2019-10-30 00:16:03 +0000 | [diff] [blame] | 26 | import argparse |
| 27 | import os |
Nikita Ioffe | 96385f7 | 2019-08-25 17:56:30 +0100 | [diff] [blame] | 28 | import shutil |
Nikita Ioffe | 62ae36a | 2019-10-30 00:16:03 +0000 | [diff] [blame] | 29 | import sys |
Nikita Ioffe | 96385f7 | 2019-08-25 17:56:30 +0100 | [diff] [blame] | 30 | import subprocess |
| 31 | import tempfile |
Nikita Ioffe | 96385f7 | 2019-08-25 17:56:30 +0100 | [diff] [blame] | 32 | import zipfile |
Jooyung Han | 4ebf219 | 2019-12-03 19:28:12 +0900 | [diff] [blame] | 33 | import apex_manifest |
Nikita Ioffe | 96385f7 | 2019-08-25 17:56:30 +0100 | [diff] [blame] | 34 | |
| 35 | class ApexImageEntry(object): |
| 36 | |
Jiyong Park | 109cb6c | 2020-01-14 14:08:31 +0900 | [diff] [blame] | 37 | def __init__(self, name, base_dir, permissions, size, is_directory=False, is_symlink=False): |
Nikita Ioffe | 96385f7 | 2019-08-25 17:56:30 +0100 | [diff] [blame] | 38 | self._name = name |
| 39 | self._base_dir = base_dir |
| 40 | self._permissions = permissions |
Jiyong Park | 109cb6c | 2020-01-14 14:08:31 +0900 | [diff] [blame] | 41 | self._size = size |
Nikita Ioffe | 96385f7 | 2019-08-25 17:56:30 +0100 | [diff] [blame] | 42 | self._is_directory = is_directory |
| 43 | self._is_symlink = is_symlink |
| 44 | |
| 45 | @property |
| 46 | def name(self): |
| 47 | return self._name |
| 48 | |
| 49 | @property |
| 50 | def full_path(self): |
| 51 | return os.path.join(self._base_dir, self._name) |
| 52 | |
| 53 | @property |
| 54 | def is_directory(self): |
| 55 | return self._is_directory |
| 56 | |
| 57 | @property |
| 58 | def is_symlink(self): |
| 59 | return self._is_symlink |
| 60 | |
| 61 | @property |
| 62 | def is_regular_file(self): |
| 63 | return not self.is_directory and not self.is_symlink |
| 64 | |
| 65 | @property |
| 66 | def permissions(self): |
| 67 | return self._permissions |
| 68 | |
Jiyong Park | 109cb6c | 2020-01-14 14:08:31 +0900 | [diff] [blame] | 69 | @property |
| 70 | def size(self): |
| 71 | return self._size |
| 72 | |
Nikita Ioffe | 96385f7 | 2019-08-25 17:56:30 +0100 | [diff] [blame] | 73 | def __str__(self): |
| 74 | ret = '' |
| 75 | if self._is_directory: |
| 76 | ret += 'd' |
| 77 | elif self._is_symlink: |
| 78 | ret += 'l' |
| 79 | else: |
| 80 | ret += '-' |
| 81 | |
| 82 | def mask_as_string(m): |
| 83 | ret = 'r' if m & 4 == 4 else '-' |
| 84 | ret += 'w' if m & 2 == 2 else '-' |
| 85 | ret += 'x' if m & 1 == 1 else '-' |
| 86 | return ret |
| 87 | |
| 88 | ret += mask_as_string(self._permissions >> 6) |
| 89 | ret += mask_as_string((self._permissions >> 3) & 7) |
| 90 | ret += mask_as_string(self._permissions & 7) |
| 91 | |
Jiyong Park | 109cb6c | 2020-01-14 14:08:31 +0900 | [diff] [blame] | 92 | return ret + ' ' + self._size + ' ' + self._name |
Nikita Ioffe | 96385f7 | 2019-08-25 17:56:30 +0100 | [diff] [blame] | 93 | |
| 94 | |
| 95 | class ApexImageDirectory(object): |
| 96 | |
| 97 | def __init__(self, path, entries, apex): |
| 98 | self._path = path |
| 99 | self._entries = sorted(entries, key=lambda e: e.name) |
| 100 | self._apex = apex |
| 101 | |
| 102 | def list(self, is_recursive=False): |
| 103 | for e in self._entries: |
| 104 | yield e |
| 105 | if e.is_directory and e.name != '.' and e.name != '..': |
| 106 | for ce in self.enter_subdir(e).list(is_recursive): |
| 107 | yield ce |
| 108 | |
| 109 | def enter_subdir(self, entry): |
Nikita Ioffe | 62ae36a | 2019-10-30 00:16:03 +0000 | [diff] [blame] | 110 | return self._apex._list(self._path + entry.name + '/') |
| 111 | |
| 112 | def extract(self, dest): |
| 113 | path = self._path |
| 114 | self._apex._extract(self._path, dest) |
Nikita Ioffe | 96385f7 | 2019-08-25 17:56:30 +0100 | [diff] [blame] | 115 | |
| 116 | |
| 117 | class Apex(object): |
| 118 | |
Dario Freni | da006cf | 2020-01-02 10:36:59 +0000 | [diff] [blame] | 119 | def __init__(self, args): |
| 120 | self._debugfs = args.debugfs_path |
| 121 | self._apex = args.apex |
Nikita Ioffe | 96385f7 | 2019-08-25 17:56:30 +0100 | [diff] [blame] | 122 | self._tempdir = tempfile.mkdtemp() |
| 123 | # TODO(b/139125405): support flattened APEXes. |
| 124 | with zipfile.ZipFile(self._apex, 'r') as zip_ref: |
| 125 | self._payload = zip_ref.extract('apex_payload.img', path=self._tempdir) |
| 126 | self._cache = {} |
| 127 | |
| 128 | def __del__(self): |
| 129 | shutil.rmtree(self._tempdir) |
| 130 | |
| 131 | def __enter__(self): |
Nikita Ioffe | 62ae36a | 2019-10-30 00:16:03 +0000 | [diff] [blame] | 132 | return self._list('./') |
Nikita Ioffe | 96385f7 | 2019-08-25 17:56:30 +0100 | [diff] [blame] | 133 | |
| 134 | def __exit__(self, type, value, traceback): |
| 135 | pass |
| 136 | |
| 137 | def _list(self, path): |
| 138 | if path in self._cache: |
| 139 | return self._cache[path] |
| 140 | process = subprocess.Popen([self._debugfs, '-R', 'ls -l -p %s' % path, self._payload], |
| 141 | stdout=subprocess.PIPE, stderr=subprocess.PIPE, |
| 142 | universal_newlines=True) |
| 143 | stdout, _ = process.communicate() |
| 144 | res = str(stdout) |
| 145 | entries = [] |
| 146 | for line in res.split('\n'): |
| 147 | if not line: |
| 148 | continue |
| 149 | parts = line.split('/') |
| 150 | if len(parts) != 8: |
| 151 | continue |
| 152 | name = parts[5] |
| 153 | if not name: |
| 154 | continue |
| 155 | bits = parts[2] |
Jiyong Park | 109cb6c | 2020-01-14 14:08:31 +0900 | [diff] [blame] | 156 | size = parts[6] |
| 157 | entries.append(ApexImageEntry(name, base_dir=path, permissions=int(bits[3:], 8), size=size, |
Nikita Ioffe | 96385f7 | 2019-08-25 17:56:30 +0100 | [diff] [blame] | 158 | is_directory=bits[1]=='4', is_symlink=bits[1]=='2')) |
| 159 | return ApexImageDirectory(path, entries, self) |
| 160 | |
Nikita Ioffe | 62ae36a | 2019-10-30 00:16:03 +0000 | [diff] [blame] | 161 | def _extract(self, path, dest): |
| 162 | process = subprocess.Popen([self._debugfs, '-R', 'rdump %s %s' % (path, dest), self._payload], |
| 163 | stdout=subprocess.PIPE, stderr=subprocess.PIPE, |
| 164 | universal_newlines=True) |
| 165 | _, stderr = process.communicate() |
Nikita Ioffe | 16574ac | 2019-12-30 21:55:08 +0000 | [diff] [blame] | 166 | if process.returncode != 0: |
| 167 | print(stderr, file=sys.stderr) |
Nikita Ioffe | 62ae36a | 2019-10-30 00:16:03 +0000 | [diff] [blame] | 168 | |
| 169 | |
| 170 | def RunList(args): |
Dario Freni | da006cf | 2020-01-02 10:36:59 +0000 | [diff] [blame] | 171 | with Apex(args) as apex: |
Nikita Ioffe | 62ae36a | 2019-10-30 00:16:03 +0000 | [diff] [blame] | 172 | for e in apex.list(is_recursive=True): |
Nikita Ioffe | cbe898e | 2020-03-13 22:08:47 +0000 | [diff] [blame] | 173 | if e.is_directory: |
| 174 | continue |
| 175 | if args.size: |
Jiyong Park | 109cb6c | 2020-01-14 14:08:31 +0900 | [diff] [blame] | 176 | print(e.size, e.full_path) |
Nikita Ioffe | cbe898e | 2020-03-13 22:08:47 +0000 | [diff] [blame] | 177 | else: |
Nikita Ioffe | 62ae36a | 2019-10-30 00:16:03 +0000 | [diff] [blame] | 178 | print(e.full_path) |
| 179 | |
| 180 | |
| 181 | def RunExtract(args): |
Dario Freni | da006cf | 2020-01-02 10:36:59 +0000 | [diff] [blame] | 182 | with Apex(args) as apex: |
| 183 | if not os.path.exists(args.dest): |
| 184 | os.makedirs(args.dest, mode=0o755) |
| 185 | apex.extract(args.dest) |
Jooyung Han | 08c223c | 2020-06-30 05:28:00 +0900 | [diff] [blame] | 186 | shutil.rmtree(os.path.join(args.dest, "lost+found")) |
Nikita Ioffe | 62ae36a | 2019-10-30 00:16:03 +0000 | [diff] [blame] | 187 | |
Nikita Ioffe | 96385f7 | 2019-08-25 17:56:30 +0100 | [diff] [blame] | 188 | |
Jooyung Han | 4ebf219 | 2019-12-03 19:28:12 +0900 | [diff] [blame] | 189 | def RunInfo(args): |
| 190 | manifest = apex_manifest.fromApex(args.apex) |
| 191 | print(apex_manifest.toJsonString(manifest)) |
| 192 | |
| 193 | |
Nikita Ioffe | 96385f7 | 2019-08-25 17:56:30 +0100 | [diff] [blame] | 194 | def main(argv): |
Nikita Ioffe | 62ae36a | 2019-10-30 00:16:03 +0000 | [diff] [blame] | 195 | parser = argparse.ArgumentParser() |
Dario Freni | da006cf | 2020-01-02 10:36:59 +0000 | [diff] [blame] | 196 | |
| 197 | debugfs_default = 'debugfs' # assume in PATH by default |
| 198 | if 'ANDROID_HOST_OUT' in os.environ: |
| 199 | debugfs_default = '%s/bin/debugfs_static' % os.environ['ANDROID_HOST_OUT'] |
| 200 | parser.add_argument('--debugfs_path', help='The path to debugfs binary', default=debugfs_default) |
| 201 | |
Theotime Combes | c9f9810 | 2020-04-08 12:54:03 +0000 | [diff] [blame] | 202 | subparsers = parser.add_subparsers(required=True, dest='cmd') |
Nikita Ioffe | 62ae36a | 2019-10-30 00:16:03 +0000 | [diff] [blame] | 203 | |
| 204 | parser_list = subparsers.add_parser('list', help='prints content of an APEX to stdout') |
| 205 | parser_list.add_argument('apex', type=str, help='APEX file') |
Jiyong Park | 109cb6c | 2020-01-14 14:08:31 +0900 | [diff] [blame] | 206 | parser_list.add_argument('--size', help='also show the size of the files', action="store_true") |
Nikita Ioffe | 62ae36a | 2019-10-30 00:16:03 +0000 | [diff] [blame] | 207 | parser_list.set_defaults(func=RunList) |
| 208 | |
| 209 | parser_extract = subparsers.add_parser('extract', help='extracts content of an APEX to the given ' |
| 210 | 'directory') |
| 211 | parser_extract.add_argument('apex', type=str, help='APEX file') |
| 212 | parser_extract.add_argument('dest', type=str, help='Directory to extract content of APEX to') |
| 213 | parser_extract.set_defaults(func=RunExtract) |
| 214 | |
Jooyung Han | 4ebf219 | 2019-12-03 19:28:12 +0900 | [diff] [blame] | 215 | parser_info = subparsers.add_parser('info', help='prints APEX manifest') |
| 216 | parser_info.add_argument('apex', type=str, help='APEX file') |
| 217 | parser_info.set_defaults(func=RunInfo) |
| 218 | |
Nikita Ioffe | 62ae36a | 2019-10-30 00:16:03 +0000 | [diff] [blame] | 219 | args = parser.parse_args(argv) |
| 220 | |
| 221 | args.func(args) |
Nikita Ioffe | 96385f7 | 2019-08-25 17:56:30 +0100 | [diff] [blame] | 222 | |
| 223 | |
| 224 | if __name__ == '__main__': |
| 225 | main(sys.argv[1:]) |