1#!/usr/bin/env python3
2# Copyright (C) 2023 The Android Open Source Project
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#
8#     http://www.apache.org/licenses/LICENSE-2.0
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
16'''
17Partition inspector creates a text file that describes a partition, for diffing it against another
18partition. Currently it just lists the files in the partition, but should be expanded to include
19selinux information and avb information.
20'''
21
22import argparse
23import difflib
24import hashlib
25import os
26import subprocess
27import sys
28
29def run_debugfs(command):
30    p = subprocess.run(command, check=True, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
31    if len(p.stderr.splitlines()) > 1:
32        # debugfs unfortunately doesn't exit with a nonzero status code on most errors.
33        # Instead, check if it had more than 1 line of stderr output.
34        # It always outputs its version number as the first line.
35        sys.exit(p.stderr)
36    return p.stdout
37
38def tree(debugfs, image, path='/', depth=0):
39    result = ''
40    for line in run_debugfs([debugfs, '-R', 'ls -p '+path, image]).splitlines():
41        line = line.strip()
42        if not line:
43            continue
44        _, _ino, mode, uid, gid, name, size, _ = line.split('/')
45        if name == '.' or name == '..':
46            continue
47        is_dir = not size
48
49        child = os.path.join(path, name)
50
51        # The selinux contexts are stored in the extended attributes
52        extended_attributes = ' '.join(run_debugfs([debugfs, '-R' 'ea_list '+child, image]).strip().split())
53
54        result += '  '*depth
55        result += f'{mode} {uid} {gid} {name} ({extended_attributes}){":" if is_dir else ""}\n'
56        if not size:
57            result += tree(debugfs, image, child, depth+1)
58    return result
59
60
61def main():
62    parser = argparse.ArgumentParser()
63    parser.add_argument('--debugfs-path', default='debugfs')
64    parser.add_argument('image')
65    args = parser.parse_args()
66
67    # debugfs doesn't exit with an error if the image doesn't exist
68    if not os.path.isfile(args.image):
69        sys.exit(f"{args.image} was not found or was a directory")
70
71    print(tree(args.debugfs_path, args.image))
72
73
74
75if __name__ == "__main__":
76    main()
77