Однажды глубокой ночью мне позарез понадобилось вытащить из остановленной виртуальной машины Virtualbox, находящейся на моем рабочем компьютере, один файлик. Удаленно запустить виртуальную машину и тем более подключиться к ней не получилось, поэтому я решил попробовать вытащить нужный раздел из vdi и смонтировать его. Ситуация немного осложнялась тем, что виртуальный жесткий диск хоть и содержал всего один корневой раздел, но был динамически расширяющимся, а это значит, что блоки файловой системы располагались в хронологическом порядке, а не в том, который ожидает драйвер файловой системы.
К счастью, можно легко преобразовать vdi из динамического формата в статический с помощью многоцелевой утилиты vboxmanage, входящей в состав пакета virtualbox:
vboxmanage clonehd --variant static debian.vdi temp.vdi
Теперь у нас есть файл temp.vdi, внутри которого где-то присутствует образ искомой файловой системы; для ее корректного извлечения нужно вычислить смещение образа относительно начала файла. В половине найденных по запросу «mount vdi linux» статей рекомендуется использовать для этих целей утилиту vditool, предназначенную на самом деле для внутреннего тестирования функциональности виртуальных дисков в VirtualBox. Раньше бинарник vditool можно было отдельно скачать с сайта virtualbox.org, но теперь его там по понятным причинам нет (желающие могут скомпилировать его самостоятельно: )
Мы, как всегда, пойдем другим путем и воспользуемся подручными средствами. Первые 512 байт нашего vdi-файла — его заголовок, в котором можно разобраться, воспользовавшись, например, или непосредственно описанием в исходниках VirtualBox. Из этих источников следует, что образ размечен следующим образом:
512 байт: заголовок
4 * N + X: карта мегабайтных блоков, здесь N — количество мегабайт в виртуальном жестком диске, а X — выравнивание получившегося числа до ближайшей верхней 512-байтной границы
512 + 4*N + выравнивание: смещение данных
Зная точный размер виртуального диска, можно легко вычислить смещение вручную:
$ vboxmanage showhdinfo f8e0de05-1419-405d-92d9-8358dc1e6bec
VirtualBox Command Line Management Interface Version 3.0.8_OSE
(C) 2005-2009 Sun Microsystems, Inc.
All rights reserved.
UUID: f8e0de05-1419-405d-92d9-8358dc1e6bec
Accessible: yes
Description:
Logical size: 4095 MBytes
Current size on disk: 2637 MBytes
Type: normal (base)
Storage format: VDI
In use by VMs: debian (UUID: f1f8221a-6542-4dfe-9062-5397f429da4b)
Location: /home/bvk/.VirtualBox/HardDisks/debian.vdi
(UUID нужного диска можно узнать из вывода команды vboxmanage list hhds). Здесь выравненное до 512 значение размера карты блоков равно 16384, а смещение данных — 16896.
А можно и вытащить смещение непосредственно из заголовка vdi. Например, вот начало моего четырёхгигового виртуального диска:
$ head -c 512 temp.vdi |hexdump -C
00000000 3c 3c 3c 20 53 75 6e 20 56 69 72 74 75 61 6c 42 |< << Sun VirtualB|
00000010 6f 78 20 44 69 73 6b 20 49 6d 61 67 65 20 3e 3e |ox Disk Image >>|
00000020 3e 0a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |>...............|
00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000040 7f 10 da be 01 00 01 00 80 01 00 00 02 00 00 00 |................|
00000050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00000150 00 00 00 00 00 02 00 00 00 42 00 00 00 00 00 00 |.........B......|
00000160 00 00 00 00 00 00 00 00 00 02 00 00 00 00 00 00 |................|
00000170 00 00 f0 ff 00 00 00 00 00 00 10 00 00 00 00 00 |................|
00000180 ff 0f 00 00 ff 0f 00 00 db fa c4 c9 fd 13 ea 49 |...............I|
00000190 9e ae 4e 47 43 3f a0 3c 46 37 6c be e6 fd 02 40 |..NGC?.<f7l ....@|
000001a0 a7 63 44 3f 6e 3f 26 d8 00 00 00 00 00 00 00 00 |.cD?n?&.........|
000001b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000001c0 00 00 00 00 00 00 00 00 0a 02 00 00 ff 00 00 00 |................|
000001d0 3f 00 00 00 00 02 00 00 00 00 00 00 00 00 00 00 |?...............|
000001e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00000200
Жирным шрифтом выделены необходимые четыре байта offsetData по смещению 0×0158. Можно искать их визуально, помня о little-endian-порядке байт (в этом случае искомое значение равно 0×00004200), а можно воспользоваться простенькой командой:
$ head -c 348 temp.vdi |tail -c 4|perl -ne 'print unpack("L",$_),"\n"'
16896
В любом случае, лишний раз перепроверить себя никогда не помешает.
Посмотрим, что находится по этому смещению. Сразу можно предположить, что там располагается начало образа жесткого диска, из этого следует, что первые 512 байт содержат mbr. Проверим:
$ dd if=temp.vdi bs=512 count=1 skip=$((16896/512))|file -
1+0 records in
1+0 records out
512 bytes (512 B) copied, 3.9397e-05 s, 13.0 MB/s
/dev/stdin: x86 boot sector; partition 1: ID=0x83, active, starthead 1,
startsector 63, 7903917 sectors; partition 2: ID=0x5, starthead 0, startsector
7903980, 481950 sectors, code offset 0x4c
Жесткий диск содержит 2 раздела: основной размером 3859 мегабайт, начинающийся с 63 сектора (1 сектор — 512 байт), и расширенный 235 мегабайт, отведенный установщиком Debian под swap (вот это сюрприз на виртуалке). Для очистки совести проверяем заголовок первого раздела:
$ dd if=temp.vdi bs=512 count=3 skip=$((16896/512+63))|file -
3+0 records in
3+0 records out
1536 bytes (1.5 kB) copied, 4.7716e-05 s, 32.2 MB/s
/dev/stdin: Linux rev 1.0 ext3 filesystem data,
UUID=5ac94027-bf6e-43bc-9c3e-8a7830fe4ff8 (needs journal recovery) (large files)
То, что надо! Осталось смонтировать. Несколько лет назад пришлось бы дополнительно извлекать все 7903917 секторов в отдельный файл и монтировать его, но сейчас можно просто указать смещение от начала файла как опцию mount (оставшиеся расширенный раздел и swap внутри него будут проигнорированы):
$ sudo mount -o loop,offset=$((16896+63*512)) temp.vdi mnt
$ ls mnt/
bin cdrom etc initrd.img lib media opt root selinux sys usr vmlinuz
boot dev home initrd.img.old lost+found mnt proc sbin srv tmp var vmlinuz.old
Задача решена, все свободны.