DF-3159 / craft_fat32big.py
#!/usr/bin/env python3 """ DF-3159 -- FAT32 volume with 64 KiB clusters and > 4 GiB capacity, so msdosfs_setattr -> detrunc -> deextend (msdosfs_denode.c:509-511/618-647) can push de_FileSize past 0xFFFFFFFF, which DE_EXTERNALIZE truncates into the 32-bit dirent size field on next update (silent shrink after remount). Also emits <out>.head (the dense prefix: boot sector + FSInfo + FAT + root dir cluster) plus a dd recipe, because the full image is a 4.5 GiB sparse file that is cheaper to materialize inside the guest. Geometry: 512B sectors, SPC=128 (64 KiB clusters), RESV=32, 1 FAT of 1840 sectors (covers 943,718 clusters), data starts at sector 1872, total sectors 9,437,184 (4.5 GiB) -> 73,584 clusters. Root dir = cluster 2 with one regular file BIG . (0 bytes, cluster 0). """ import struct, sys BPS = 512 SPC = 128 RESV = 32 NFATS = 1 FATSEC = 1840 TOTSEC = 9437184 # 4.5 GiB FIRSTCL = RESV + NFATS * FATSEC # 1872 FAT32_EOC = 0x0FFFFFF8 def direntry(name, ext, attr, cluster, size): e = bytearray(32) e[0:8] = (name + ' ' * 8)[:8].encode() e[8:11] = (ext + ' ' * 3)[:3].encode() e[11] = attr struct.pack_into('<H', e, 20, 0) struct.pack_into('<H', e, 26, cluster) struct.pack_into('<I', e, 28, size) return bytes(e) def build(outpath): img = bytearray(FIRSTCL * BPS + SPC * BPS) # dense prefix through root dir cluster img[0] = 0xEB; img[1] = 0x58; img[2] = 0x90 img[3:11] = b'MSDOS5.0' struct.pack_into('<H', img, 11, BPS) img[13] = SPC struct.pack_into('<H', img, 14, RESV) img[16] = NFATS struct.pack_into('<H', img, 17, 0) struct.pack_into('<H', img, 19, 0) img[21] = 0xF8 struct.pack_into('<H', img, 22, 0) struct.pack_into('<H', img, 24, 63) struct.pack_into('<H', img, 26, 255) struct.pack_into('<I', img, 28, 0) struct.pack_into('<I', img, 32, TOTSEC) struct.pack_into('<I', img, 36, FATSEC) struct.pack_into('<H', img, 40, 0) # ExtFlags: mirrored (harmless here) struct.pack_into('<H', img, 42, 0) struct.pack_into('<I', img, 44, 2) # RootClust struct.pack_into('<H', img, 48, 1) # FSInfo sector struct.pack_into('<H', img, 50, 6) img[510] = 0x55; img[511] = 0xAA fsi = BPS struct.pack_into('<I', img, fsi + 0, 0x41615252) struct.pack_into('<I', img, fsi + 484, 0x61417272) struct.pack_into('<I', img, fsi + 488, 0xFFFFFFFF) struct.pack_into('<I', img, fsi + 492, 0xFFFFFFFF) fat = bytearray(FATSEC * BPS) fat[0] = 0xF8; fat[1] = 0xFF; fat[2] = 0xFF; fat[3] = 0x0F struct.pack_into('<I', fat, 8, FAT32_EOC) # cluster 2 = root dir, EOC img[RESV * BPS:RESV * BPS + len(fat)] = fat root_off = FIRSTCL * BPS img[root_off:root_off + 32] = direntry('. ', ' ', 0x10, 2, 0) img[root_off + 32:root_off + 64] = direntry('.. ', ' ', 0x10, 2, 0) img[root_off + 64:root_off + 96] = direntry('BIG ', ' ', 0x20, 0, 0) img[root_off + 96] = 0x00 with open(outpath, 'wb') as f: f.write(img) print("wrote %s (%d bytes dense prefix); guest: truncate -s %d then dd this at offset 0" % (outpath, len(img), TOTSEC * BPS)) if __name__ == '__main__': build(sys.argv[1]) |