Если нужно понять, как файл в ZFS ложится на физический диск, лучший способ — намеренно сломать один байт и посмотреть, что произойдёт. Важное правило: всё делать только на тестовых пулах из файлов, а не на реальных дисках.
Самый простой способ — утилита zinject. Она эмулирует повреждение данных в живом пуле и выдаёт ошибку контрольной суммы. Но это не показывает, где именно на диске живут байты. Для понимания всё надо делать руками.
Автор создаёт два пула из обычных файлов в /tmp. Первый — single-vdev, вообще без избыточности. Второй — RAIDZ2 из четырёх файлов, с двумя дисками чётности. В каждом пуле он отключает сжатие (compression=off), иначе данные будут упакованы и их не найти в сыром виде.
Для single-пула он находит блок файла через zdb -O, получает DVA — адрес блока на виртуальном устройстве. Расчёт физического смещения: к DVA-смещению надо добавить 4 МБ (0x400000), которые ZFS резервирует под метки и загрузчик. Попытка прочитать блок по этому адресу натыкается на сжатие — данные не совпадают с ожидаемыми. После отключения сжатия всё сходится: паттерн SINGLE-ZFS-CORRUPTION-DEMO-BLOCK находится точно.
Коррупция — одна команда printf 'BAD-SINGLE\n' | dd с ключом conv=notrunc поверх нужного сектора файла. После импорта пула и scrub ZFS находит ошибку, но восстановить не может — копия данных одна.
На RAIDZ2 всё сложнее. DVA указывает на топологический vdev, а не на конкретный дочерний файл. Приходится вручную разматывать раскладку: определить номер начальной колонки (f), смещение внутри каждой колонки (o), учесть, что колонки чётности идут первыми, а данные оборачиваются при выходе за границу. Для нашего блока 128 КБ данных разбивается на две части по 64 КБ на файлах r1.img и r2.img. После экспорта пула автор портит сектор на r1.img, импортирует пул и запускает scrub. ZFS находит ошибку только на r1.img и исправляет её из чётности.
Разница очевидна: на single-пуле файл остаётся битым, на RAIDZ2 — восстанавливается.
Мораль: zinject удобен, но не учит геометрии диска. Ручной путь — через inode, dnode, block pointer, DVA, учёт 4 МБ резерва и сжатия — превращает абстракцию в понятную карту. Именно это пригождается, когда баг сидит на стыке абстракций и железа.