From 34889a98949208fbc930efd0b9c2e77c0d1e6fd4 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 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. Co-authored-by: KrisztiƔn Fekete <1246751+e3krisztian@users.noreply.github.com> --- ubireader/scripts/ubireader_list_files.py | 31 ++++++++++++++++-- ubireader/ubifs/list.py | 39 ++++++++++++++++------- 2 files changed, 55 insertions(+), 15 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..36977e4 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,8 +54,11 @@ def list_files(ubifs: Ubifs, list_path: str) -> None: return for dent in inodes[inum]['dent']: - print_dent(ubifs, inodes, dent, longts=False) - + if recursive: + print_dent_recursive(ubifs, inodes, dent, longts=False, dent_path=list_path / dent.name) + else: + print_dent(ubifs, inodes, dent, longts=False) + if len(bad_blocks): error(list_files, 'Warn', 'Data may be missing or corrupted, bad blocks, LEB [%s]' % ','.join(map(str, bad_blocks))) @@ -113,9 +114,11 @@ def find_dir(inodes: Mapping[int, Inode], inum: int, names: list[str], idx: int) return find_dir(inodes, dent.inum, names, idx+1) 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, *, 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,9 +131,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) + + +def print_dent_recursive(ubifs: Ubifs, inodes: Mapping[int, Inode], dent_node: nodes.dent_node, long: bool = True, longts: bool = False, *, dent_path: PurePath) -> None: + inode = inodes[dent_node.inum] + + print_dent(ubifs, inodes, dent_node, long=long, longts=longts, dent_path=dent_path) + + if dent_node.type != UBIFS_ITYPE_DIR: + return + + for dnode in inode.get('dent', []): + print_dent_recursive(ubifs, inodes, dnode, long=long, longts=longts, dent_path=dent_path / dnode.name) def file_leng(ubifs: Ubifs, inode: Inode) -> int: