blob: b170d90f5fce27724ceb01e1fce659cfcb198f96 [file] [log] [blame]
Nikita Ioffe96385f72019-08-25 17:56:30 +01001#!/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
18To print content of an APEX to stdout:
Nikita Ioffe62ae36a2019-10-30 00:16:03 +000019 deapexer list foo.apex
Nikita Ioffe96385f72019-08-25 17:56:30 +010020
Nikita Ioffe62ae36a2019-10-30 00:16:03 +000021To extract content of an APEX to the given directory:
22 deapexer extract foo.apex dest
Nikita Ioffe96385f72019-08-25 17:56:30 +010023"""
Dario Frenida006cf2020-01-02 10:36:59 +000024from __future__ import print_function
Nikita Ioffe96385f72019-08-25 17:56:30 +010025
Nikita Ioffe62ae36a2019-10-30 00:16:03 +000026import argparse
27import os
Nikita Ioffe96385f72019-08-25 17:56:30 +010028import shutil
Nikita Ioffe62ae36a2019-10-30 00:16:03 +000029import sys
Nikita Ioffe96385f72019-08-25 17:56:30 +010030import subprocess
31import tempfile
Nikita Ioffe96385f72019-08-25 17:56:30 +010032import zipfile
Jooyung Han4ebf2192019-12-03 19:28:12 +090033import apex_manifest
Nikita Ioffe96385f72019-08-25 17:56:30 +010034
35class ApexImageEntry(object):
36
Jiyong Park109cb6c2020-01-14 14:08:31 +090037 def __init__(self, name, base_dir, permissions, size, is_directory=False, is_symlink=False):
Nikita Ioffe96385f72019-08-25 17:56:30 +010038 self._name = name
39 self._base_dir = base_dir
40 self._permissions = permissions
Jiyong Park109cb6c2020-01-14 14:08:31 +090041 self._size = size
Nikita Ioffe96385f72019-08-25 17:56:30 +010042 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 Park109cb6c2020-01-14 14:08:31 +090069 @property
70 def size(self):
71 return self._size
72
Nikita Ioffe96385f72019-08-25 17:56:30 +010073 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 Park109cb6c2020-01-14 14:08:31 +090092 return ret + ' ' + self._size + ' ' + self._name
Nikita Ioffe96385f72019-08-25 17:56:30 +010093
94
95class 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 Ioffe62ae36a2019-10-30 00:16:03 +0000110 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 Ioffe96385f72019-08-25 17:56:30 +0100115
116
117class Apex(object):
118
Dario Frenida006cf2020-01-02 10:36:59 +0000119 def __init__(self, args):
120 self._debugfs = args.debugfs_path
121 self._apex = args.apex
Nikita Ioffe96385f72019-08-25 17:56:30 +0100122 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 Ioffe62ae36a2019-10-30 00:16:03 +0000132 return self._list('./')
Nikita Ioffe96385f72019-08-25 17:56:30 +0100133
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 Park109cb6c2020-01-14 14:08:31 +0900156 size = parts[6]
157 entries.append(ApexImageEntry(name, base_dir=path, permissions=int(bits[3:], 8), size=size,
Nikita Ioffe96385f72019-08-25 17:56:30 +0100158 is_directory=bits[1]=='4', is_symlink=bits[1]=='2'))
159 return ApexImageDirectory(path, entries, self)
160
Nikita Ioffe62ae36a2019-10-30 00:16:03 +0000161 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 Ioffe16574ac2019-12-30 21:55:08 +0000166 if process.returncode != 0:
167 print(stderr, file=sys.stderr)
Nikita Ioffe62ae36a2019-10-30 00:16:03 +0000168
169
170def RunList(args):
Dario Frenida006cf2020-01-02 10:36:59 +0000171 with Apex(args) as apex:
Nikita Ioffe62ae36a2019-10-30 00:16:03 +0000172 for e in apex.list(is_recursive=True):
Nikita Ioffecbe898e2020-03-13 22:08:47 +0000173 if e.is_directory:
174 continue
175 if args.size:
Jiyong Park109cb6c2020-01-14 14:08:31 +0900176 print(e.size, e.full_path)
Nikita Ioffecbe898e2020-03-13 22:08:47 +0000177 else:
Nikita Ioffe62ae36a2019-10-30 00:16:03 +0000178 print(e.full_path)
179
180
181def RunExtract(args):
Dario Frenida006cf2020-01-02 10:36:59 +0000182 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 Han08c223c2020-06-30 05:28:00 +0900186 shutil.rmtree(os.path.join(args.dest, "lost+found"))
Nikita Ioffe62ae36a2019-10-30 00:16:03 +0000187
Nikita Ioffe96385f72019-08-25 17:56:30 +0100188
Jooyung Han4ebf2192019-12-03 19:28:12 +0900189def RunInfo(args):
190 manifest = apex_manifest.fromApex(args.apex)
191 print(apex_manifest.toJsonString(manifest))
192
193
Nikita Ioffe96385f72019-08-25 17:56:30 +0100194def main(argv):
Nikita Ioffe62ae36a2019-10-30 00:16:03 +0000195 parser = argparse.ArgumentParser()
Dario Frenida006cf2020-01-02 10:36:59 +0000196
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 Combesc9f98102020-04-08 12:54:03 +0000202 subparsers = parser.add_subparsers(required=True, dest='cmd')
Nikita Ioffe62ae36a2019-10-30 00:16:03 +0000203
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 Park109cb6c2020-01-14 14:08:31 +0900206 parser_list.add_argument('--size', help='also show the size of the files', action="store_true")
Nikita Ioffe62ae36a2019-10-30 00:16:03 +0000207 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 Han4ebf2192019-12-03 19:28:12 +0900215 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 Ioffe62ae36a2019-10-30 00:16:03 +0000219 args = parser.parse_args(argv)
220
221 args.func(args)
Nikita Ioffe96385f72019-08-25 17:56:30 +0100222
223
224if __name__ == '__main__':
225 main(sys.argv[1:])