#!/usr/bin/env python3 import struct, os DEVICE = '/dev/nbd0' BSIZE = 4096 IPG = 8192 INODE_SZ = 256 NUM_GROUPS = 35728 MIN_GROUP = 13 TARGETS = [ b'pterodactyl', b'mysql', b'www', b'log', b'docker', b'nginx', b'apache2', b'archives', b'wings', b'grub2-efi.cfg', b'commons-codec', ] def is_valid_dirent(block, off, name): if off + 8 + len(name) > BSIZE: return False inode = struct.unpack_from(' BSIZE or rec_len%4 != 0: return False if ftype not in (1,2,7): return False if block[off+8:off+8+name_len] != name: return False pad = off+8+name_len if pad < BSIZE and block[pad] != 0: return False return True def parse_extents(inode_data): blocks = [] magic = struct.unpack_from(' 1024: continue for b in range(min(ee_len, 8)): blocks.append(ee_start + b) return blocks results = {} print(f'Device: {DEVICE}') print(f'Scanning groups {MIN_GROUP} to {NUM_GROUPS-1}...') print() with open(DEVICE, 'rb', buffering=0) as f: for group in range(MIN_GROUP, NUM_GROUPS): it_block = 1070 + group * 512 try: f.seek(it_block * BSIZE) inode_table = f.read(IPG * INODE_SZ) except OSError: continue for idx in range(IPG): inode_data = inode_table[idx*INODE_SZ:(idx+1)*INODE_SZ] if not any(inode_data): continue mode = struct.unpack_from('=13 else 'LOST' tname = {1:'file',2:'dir',7:'link'}.get(ftype,'?') print(f'[{status}] {target.decode()!r:15s} ' f'child={child_ino:10d} ' f'parent={inode_num:10d} ' f'type={tname}', flush=True) if group % 1000 == 0: print(f' Group {group}/{NUM_GROUPS}...', flush=True) print() print('=== SUMMARY ===') for (name, child_ino), (parent_ino, ftype, grp) in sorted(results.items()): status = 'INTACT' if grp >= 13 else 'LOST' tname = {1:'file',2:'dir',7:'link'}.get(ftype,'?') print(f'[{status}] {name!r:15s} child={child_ino} ' f'parent={parent_ino} type={tname}')