#!/usr/bin/env python3 """ Restore ext4 metadata (permissions, ownership, timestamps) to a recovered tree. Run after dump_tree.py has extracted files. Usage: python3 restore_meta.py """ import struct, os, sys, stat, ctypes, ctypes.util DEV = '/dev/dm-0' BLOCK = 4096 BACKUP_SB_BLOCK = 32768 # ── reuse same low-level helpers from dump_tree.py ─────────────────────────── # (paste parse_superblock, parse_gdt_entry, read_at, read_inode, # read_extent_tree_blocks, read_dir_entries here) # or factor them into a shared ext4lib.py and import from both scripts import ext4lib libc = ctypes.CDLL(ctypes.util.find_library('c'), use_errno=True) class Timeval(ctypes.Structure): _fields_ = [('tv_sec', ctypes.c_long), ('tv_usec', ctypes.c_long)] def lutimes(path, atime, mtime): times = (Timeval * 2)((atime, 0), (mtime, 0)) libc.lutimes(path.encode(), ctypes.byref(times)) def get_inode_meta(idata, slot, sb): mode = struct.unpack_from('= 256: atime_extra = struct.unpack_from(' ") sys.exit(1) root_inum = int(sys.argv[1]) dest_dir = sys.argv[2] with open(DEV, 'rb') as f: sb_data = ext4lib.read_at(f, BACKUP_SB_BLOCK * BLOCK, 1024) sb = ext4lib.parse_superblock(sb_data) assert sb['magic'] == 0xef53 num_groups = (sb['blocks_count'] + sb['blocks_per_group'] - 1) \ // sb['blocks_per_group'] gdt_data = ext4lib.read_at(f, (BACKUP_SB_BLOCK + 1) * BLOCK, num_groups * sb['desc_size']) print(f"Restoring metadata: inode {root_inum} -> {dest_dir}") walk_and_restore(f, sb, gdt_data, root_inum, dest_dir) print("Done") if __name__ == '__main__': main()