Skip to content

Commit 74e2c7b

Browse files
committed
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.
1 parent 863bb2d commit 74e2c7b

2 files changed

Lines changed: 73 additions & 18 deletions

File tree

ubireader/scripts/ubireader_list_files.py

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@
1818
# along with this program. If not, see <http://www.gnu.org/licenses/>.
1919
#############################################################
2020

21+
from __future__ import annotations
2122
import os
2223
import sys
2324
import time
2425
import argparse
26+
from typing import Protocol, cast
2527

2628
from ubireader import settings
2729
from ubireader.ubi import ubi
@@ -33,6 +35,23 @@
3335
from ubireader.debug import error, log
3436
from ubireader.utils import guess_filetype, guess_start_offset, guess_leb_size, guess_peb_size
3537

38+
class _Args(Protocol):
39+
log: bool
40+
verbose: bool
41+
block_size: int | None
42+
start_offset: int | None
43+
end_offset: int | None
44+
guess_offset: int | None
45+
warn_only_block_read_errors: bool
46+
ignore_block_header_errors: bool
47+
uboot_fix: bool
48+
listpath: str | None
49+
copyfile: str | None
50+
copyfiledest: str | None
51+
master_key: str | None
52+
recursive: bool
53+
filepath: str
54+
3655
def main():
3756
start = time.time()
3857
description = 'List and Extract files of a UBI or UBIFS image.'
@@ -81,13 +100,16 @@ def main():
81100
parser.add_argument('-K', '--master-key', dest='master_key',
82101
help='Master key file, given with fscryptctl e.g. to encrypt the UBIFS (support limited to fscrypt v1 policies)')
83102

103+
parser.add_argument('-r', '--recursive', action='store_true',
104+
help='List files recursively and show absolute paths.')
105+
84106
parser.add_argument('filepath', help='UBI/UBIFS image file.')
85107

86108
if len(sys.argv) == 1:
87109
parser.print_help()
88110
sys.exit(1)
89111

90-
args = parser.parse_args()
112+
args = cast(_Args, parser.parse_args())
91113

92114
settings.logging_on = args.log
93115

@@ -99,6 +121,9 @@ def main():
99121

100122
settings.uboot_fix = args.uboot_fix
101123

124+
if args.recursive and not args.listpath:
125+
parser.error("Recursive option needs a path to start with.")
126+
102127
if args.master_key:
103128
path = args.master_key
104129
if not os.path.exists(path):
@@ -173,7 +198,7 @@ def main():
173198
ubifs_obj = ubifs(lebv_file, master_key=master_key)
174199

175200
if args.listpath:
176-
list_files(ubifs_obj, args.listpath)
201+
list_files(ubifs_obj, args.listpath, recursive=args.recursive)
177202
if args.copyfile and args.copyfiledest:
178203
copy_file(ubifs_obj, args.copyfile, args.copyfiledest)
179204

@@ -182,7 +207,7 @@ def main():
182207
ubifs_obj = ubifs(ufile_obj, master_key=master_key)
183208

184209
if args.listpath:
185-
list_files(ubifs_obj, args.listpath)
210+
list_files(ubifs_obj, args.listpath, recursive=args.recursive)
186211
if args.copyfile and args.copyfiledest:
187212
copy_file(ubifs_obj, args.copyfile, args.copyfiledest)
188213

ubireader/ubifs/list.py

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
from __future__ import annotations
2121
import os
22+
from pathlib import PurePath
2223
import time
2324
from typing import TYPE_CHECKING
2425
from ubireader.ubifs.decrypt import decrypt_symlink_target
@@ -28,16 +29,13 @@
2829
from ubireader.debug import error
2930

3031
if TYPE_CHECKING:
31-
from collections.abc import Mapping
32+
from collections.abc import Mapping, Sequence
3233
from ubireader.ubifs import ubifs as Ubifs, nodes
3334
from ubireader.ubifs.walk import _Inode
3435

35-
def list_files(ubifs: Ubifs, list_path: str) -> None:
36-
pathnames = list_path.split("/")
37-
pnames: list[str] = []
38-
for i in pathnames:
39-
if len(i) > 0:
40-
pnames.append(i)
36+
def list_files(ubifs: Ubifs, list_path: PurePath | str, *, recursive: bool = False) -> None:
37+
list_path = PurePath(list_path)
38+
pnames = [part for part in list_path.parts if part != '/']
4139
try:
4240
inodes: dict[int, _Inode] = {}
4341
bad_blocks: list[int] = []
@@ -55,8 +53,16 @@ def list_files(ubifs: Ubifs, list_path: str) -> None:
5553
if not 'dent' in inodes[inum]:
5654
return
5755

58-
for dent in inodes[inum]['dent']:
59-
print_dent(ubifs, inodes, dent, longts=False)
56+
for dent in inodes[inum].get('dent', []):
57+
print_dent(
58+
ubifs,
59+
inodes,
60+
dent,
61+
longts=False,
62+
recursive=recursive,
63+
# Only show absolute paths if recursive
64+
dent_path=(list_path / dent.name) if recursive else None,
65+
)
6066

6167
if len(bad_blocks):
6268
error(list_files, 'Warn', 'Data may be missing or corrupted, bad blocks, LEB [%s]' % ','.join(map(str, bad_blocks)))
@@ -102,10 +108,10 @@ def copy_file(ubifs: Ubifs, filepath: str, destpath: str) -> bool:
102108
return False
103109

104110

105-
def find_dir(inodes: Mapping[int, _Inode], inum: int, names: list[str], idx: int) -> int | None:
111+
def find_dir(inodes: Mapping[int, _Inode], inum: int, names: Sequence[str], idx: int) -> int | None:
106112
if len(names) == 0:
107113
return 1
108-
for dent in inodes[inum]['dent']:
114+
for dent in inodes[inum].get('dent', []):
109115
if dent.name == names[idx]:
110116
if len(names) == idx+1:
111117
return dent.inum
@@ -114,8 +120,21 @@ def find_dir(inodes: Mapping[int, _Inode], inum: int, names: list[str], idx: int
114120
return None
115121

116122

117-
def print_dent(ubifs: Ubifs, inodes: Mapping[int, _Inode], dent_node: nodes.dent_node, long: bool = True, longts: bool = False) -> None:
123+
def print_dent(
124+
ubifs: Ubifs,
125+
inodes: Mapping[int, _Inode],
126+
dent_node: nodes.dent_node,
127+
long: bool = True,
128+
longts: bool = False,
129+
*,
130+
recursive: bool = False,
131+
# Path of the directory entry
132+
dent_path: PurePath | None = None,
133+
) -> None:
118134
inode = inodes[dent_node.inum]
135+
# Display the full path if path is set, otherwise just the name.
136+
display_path = str(dent_path) if dent_path is not None else dent_node.name
137+
119138
if long:
120139
fl = file_leng(ubifs, inode)
121140

@@ -128,10 +147,21 @@ def print_dent(ubifs: Ubifs, inodes: Mapping[int, _Inode], dent_node: nodes.dent
128147
else:
129148
mtime = time.strftime("%b %d %H:%M", time.gmtime(inode['ino'].mtime_sec))
130149

131-
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))
150+
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))
132151
else:
133-
print(dent_node.name)
134-
152+
print(display_path)
153+
154+
if recursive and dent_node.type == UBIFS_ITYPE_DIR:
155+
for dnode in inode.get('dent', []):
156+
print_dent(
157+
ubifs,
158+
inodes,
159+
dnode,
160+
long=long,
161+
longts=longts,
162+
recursive=recursive,
163+
dent_path=dent_path / dnode.name if dent_path is not None else None,
164+
)
135165

136166
def file_leng(ubifs: Ubifs, inode: _Inode) -> int:
137167
fl = 0

0 commit comments

Comments
 (0)