Initial remote commit

This commit is contained in:
2026-04-30 11:04:05 +00:00
commit b86e4f9a98
103 changed files with 262770 additions and 0 deletions

133
test/build_tree.py Normal file
View 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')