Initial remote commit
This commit is contained in:
133
test/build_tree.py
Normal file
133
test/build_tree.py
Normal file
@@ -0,0 +1,133 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Build directory tree from ils output using fls parent pointers.
|
||||
For each directory inode:
|
||||
1. Run fls to get its contents and parent (..)
|
||||
2. Record parent->child relationships
|
||||
3. Walk parent chain to resolve full path
|
||||
4. Place unreachable dirs in /orphans/
|
||||
"""
|
||||
import subprocess, sys, os, collections
|
||||
|
||||
DEVICE = '/dev/nbd0'
|
||||
OUTDIR = '/mnt/recovered'
|
||||
MIN_INODE = 106497 # first intact group
|
||||
|
||||
def fls(inode):
|
||||
try:
|
||||
r = subprocess.run(
|
||||
['fls', DEVICE, str(inode)],
|
||||
capture_output=True, text=True, timeout=30
|
||||
)
|
||||
entries = []
|
||||
for line in r.stdout.splitlines():
|
||||
try:
|
||||
parts = line.split(None, 2)
|
||||
if len(parts) < 3: continue
|
||||
type_str = parts[0]
|
||||
ino_str = parts[1].rstrip(':').lstrip('*')
|
||||
name = parts[2].strip()
|
||||
ino = int(ino_str)
|
||||
etype = type_str[0]
|
||||
entries.append((etype, ino, name))
|
||||
except: continue
|
||||
return entries
|
||||
except:
|
||||
return []
|
||||
|
||||
# Load directory inodes from ils output
|
||||
print('Loading directory inodes...')
|
||||
dir_inodes = []
|
||||
with open('/tmp/dir_inodes.txt') as f:
|
||||
for line in f:
|
||||
parts = line.strip().split('|')
|
||||
if len(parts) < 2: continue
|
||||
try:
|
||||
ino = int(parts[0])
|
||||
if ino >= MIN_INODE:
|
||||
dir_inodes.append(ino)
|
||||
except: continue
|
||||
|
||||
print(f'Found {len(dir_inodes)} directory inodes to process')
|
||||
|
||||
# For each directory, get its parent and children
|
||||
# parent_of[inode] = parent_inode
|
||||
# children_of[inode] = [(child_inode, name, type)]
|
||||
parent_of = {}
|
||||
children_of = collections.defaultdict(list)
|
||||
names = {} # inode -> name (as seen from parent)
|
||||
|
||||
print('Running fls on each directory...')
|
||||
for idx, ino in enumerate(dir_inodes):
|
||||
entries = fls(ino)
|
||||
for etype, eino, ename in entries:
|
||||
if ename == '..':
|
||||
parent_of[ino] = eino
|
||||
elif ename == '.':
|
||||
continue
|
||||
else:
|
||||
children_of[ino].append((eino, ename, etype))
|
||||
names[eino] = ename
|
||||
|
||||
if idx % 1000 == 0:
|
||||
print(f' {idx}/{len(dir_inodes)} processed...', flush=True)
|
||||
|
||||
print(f'Built tree: {len(parent_of)} dirs with known parents')
|
||||
|
||||
# Resolve full paths by walking parent chain
|
||||
resolved = {} # inode -> full path
|
||||
|
||||
def resolve(ino, depth=0):
|
||||
if ino in resolved:
|
||||
return resolved[ino]
|
||||
if depth > 50:
|
||||
return None
|
||||
|
||||
parent = parent_of.get(ino)
|
||||
if parent is None:
|
||||
path = f'orphans/{ino}'
|
||||
resolved[ino] = path
|
||||
return path
|
||||
|
||||
# Check if parent is in intact groups
|
||||
if parent < MIN_INODE:
|
||||
# Parent is in zeroed groups — this is a root-level orphan
|
||||
# Use the name if we know it
|
||||
name = names.get(ino, str(ino))
|
||||
path = f'orphans/{name}_{ino}'
|
||||
resolved[ino] = path
|
||||
return path
|
||||
|
||||
parent_path = resolve(parent, depth + 1)
|
||||
if parent_path is None:
|
||||
path = f'orphans/{ino}'
|
||||
else:
|
||||
name = names.get(ino, str(ino))
|
||||
path = os.path.join(parent_path, name)
|
||||
|
||||
resolved[ino] = path
|
||||
return path
|
||||
|
||||
print('Resolving paths...')
|
||||
for ino in dir_inodes:
|
||||
resolve(ino)
|
||||
|
||||
# Print summary
|
||||
orphans = sum(1 for p in resolved.values() if p.startswith('orphans'))
|
||||
resolved_count = len(resolved) - orphans
|
||||
print(f'Resolved paths: {resolved_count}')
|
||||
print(f'Orphaned dirs: {orphans}')
|
||||
print()
|
||||
|
||||
# Show interesting paths
|
||||
print('Sample resolved paths:')
|
||||
for ino, path in sorted(resolved.items(), key=lambda x: x[1]):
|
||||
if any(x in path for x in ['var','pterodactyl','docker','mysql',
|
||||
'www','log','lib']):
|
||||
print(f' inode {ino:10d}: {path}')
|
||||
|
||||
# Save full tree
|
||||
with open('/tmp/dir_tree.txt','w') as f:
|
||||
for ino, path in sorted(resolved.items(), key=lambda x: x[1]):
|
||||
f.write(f'{ino}\t{path}\n')
|
||||
print(f'Saved {len(resolved)} paths to /tmp/dir_tree.txt')
|
||||
Reference in New Issue
Block a user