From e2f68456598a0b9bd34a29f4989b9110a8d56198 Mon Sep 17 00:00:00 2001 From: Simon Berger Date: Sun, 7 Sep 2025 11:57:53 +0000 Subject: [PATCH] feat(ubireader_list_files): support recursive listing The default output of ubireader_list_files remains the same, but when you add '--recursive' it will recursively list all the inodes under the listpath. It then also displays the absolute path to the files instead of just the name. --- ubireader/scripts/ubireader_list_files.py | 31 ++++++++++++-- ubireader/ubifs/list.py | 52 ++++++++++++++++++----- 2 files changed, 69 insertions(+), 14 deletions(-) diff --git a/ubireader/scripts/ubireader_list_files.py b/ubireader/scripts/ubireader_list_files.py index ed65023..041e33a 100755 --- a/ubireader/scripts/ubireader_list_files.py +++ b/ubireader/scripts/ubireader_list_files.py @@ -18,10 +18,12 @@ # along with this program. If not, see . ############################################################# +from __future__ import annotations import os import sys import time import argparse +from typing import Protocol, cast from ubireader import settings from ubireader.ubi import ubi @@ -33,6 +35,23 @@ from ubireader.debug import error, log from ubireader.utils import guess_filetype, guess_start_offset, guess_leb_size, guess_peb_size +class _Args(Protocol): + log: bool + verbose: bool + block_size: int | None + start_offset: int | None + end_offset: int | None + guess_offset: int | None + warn_only_block_read_errors: bool + ignore_block_header_errors: bool + uboot_fix: bool + listpath: str | None + copyfile: str | None + copyfiledest: str | None + master_key: str | None + recursive: bool + filepath: str + def main(): start = time.time() description = 'List and Extract files of a UBI or UBIFS image.' @@ -81,13 +100,16 @@ def main(): parser.add_argument('-K', '--master-key', dest='master_key', help='Master key file, given with fscryptctl e.g. to encrypt the UBIFS (support limited to fscrypt v1 policies)') + parser.add_argument('-r', '--recursive', action='store_true', + help='List files recursively and show absolute paths.') + parser.add_argument('filepath', help='UBI/UBIFS image file.') if len(sys.argv) == 1: parser.print_help() sys.exit(1) - args = parser.parse_args() + args = cast(_Args, parser.parse_args()) settings.logging_on = args.log @@ -99,6 +121,9 @@ def main(): settings.uboot_fix = args.uboot_fix + if args.recursive and not args.listpath: + parser.error("Recursive option needs a path to start with.") + if args.master_key: path = args.master_key if not os.path.exists(path): @@ -173,7 +198,7 @@ def main(): ubifs_obj = ubifs(lebv_file, master_key=master_key) if args.listpath: - list_files(ubifs_obj, args.listpath) + list_files(ubifs_obj, args.listpath, recursive=args.recursive) if args.copyfile and args.copyfiledest: copy_file(ubifs_obj, args.copyfile, args.copyfiledest) @@ -182,7 +207,7 @@ def main(): ubifs_obj = ubifs(ufile_obj, master_key=master_key) if args.listpath: - list_files(ubifs_obj, args.listpath) + list_files(ubifs_obj, args.listpath, recursive=args.recursive) if args.copyfile and args.copyfiledest: copy_file(ubifs_obj, args.copyfile, args.copyfiledest) diff --git a/ubireader/ubifs/list.py b/ubireader/ubifs/list.py index c858d05..12336f9 100755 --- a/ubireader/ubifs/list.py +++ b/ubireader/ubifs/list.py @@ -19,6 +19,7 @@ from __future__ import annotations import os +from pathlib import PurePath import time from typing import TYPE_CHECKING from ubireader.ubifs.decrypt import decrypt_symlink_target @@ -32,12 +33,9 @@ from ubireader.ubifs import ubifs as Ubifs, nodes from ubireader.ubifs.walk import Inode -def list_files(ubifs: Ubifs, list_path: str) -> None: - pathnames = list_path.split("/") - pnames: list[str] = [] - for i in pathnames: - if len(i) > 0: - pnames.append(i) +def list_files(ubifs: Ubifs, list_path: PurePath | str, *, recursive: bool = False) -> None: + list_path = PurePath(list_path) + pnames = [part for part in list_path.parts if part != '/'] try: inodes: dict[int, Inode] = {} bad_blocks: list[int] = [] @@ -56,7 +54,15 @@ def list_files(ubifs: Ubifs, list_path: str) -> None: return for dent in inodes[inum]['dent']: - print_dent(ubifs, inodes, dent, longts=False) + print_dent( + ubifs, + inodes, + dent, + longts=False, + recursive=recursive, + # Only show absolute paths if recursive + dent_path=(list_path / dent.name) if recursive else None, + ) if len(bad_blocks): error(list_files, 'Warn', 'Data may be missing or corrupted, bad blocks, LEB [%s]' % ','.join(map(str, bad_blocks))) @@ -114,8 +120,21 @@ def find_dir(inodes: Mapping[int, Inode], inum: int, names: list[str], idx: int) return None -def print_dent(ubifs: Ubifs, inodes: Mapping[int, Inode], dent_node: nodes.dent_node, long: bool = True, longts: bool = False) -> None: +def print_dent( + ubifs: Ubifs, + inodes: Mapping[int, Inode], + dent_node: nodes.dent_node, + long: bool = True, + longts: bool = False, + *, + recursive: bool = False, + # Path of the directory entry + dent_path: PurePath | None = None, +) -> None: inode = inodes[dent_node.inum] + # Display the full path if path is set, otherwise just the name. + display_path = str(dent_path) if dent_path is not None else dent_node.name + if long: fl = file_leng(ubifs, inode) @@ -128,10 +147,21 @@ def print_dent(ubifs: Ubifs, inodes: Mapping[int, Inode], dent_node: nodes.dent_ else: mtime = time.strftime("%b %d %H:%M", time.gmtime(inode['ino'].mtime_sec)) - print('%6o %2d %s %s %7d %s %s%s' % (inode['ino'].mode, inode['ino'].nlink, inode['ino'].uid, inode['ino'].gid, fl, mtime, dent_node.name, lnk)) + print('%6o %2d %s %s %7d %s %s%s' % (inode['ino'].mode, inode['ino'].nlink, inode['ino'].uid, inode['ino'].gid, fl, mtime, display_path, lnk)) else: - print(dent_node.name) - + print(display_path) + + if recursive and dent_node.type == UBIFS_ITYPE_DIR: + for dnode in inode.get('dent', []): + print_dent( + ubifs, + inodes, + dnode, + long=long, + longts=longts, + recursive=recursive, + dent_path=dent_path / dnode.name if dent_path is not None else None, + ) def file_leng(ubifs: Ubifs, inode: Inode) -> int: fl = 0