LINUX.ORG.RU

Создание образа GPT-диска с несколькими разделами без loop-устройств

 , , ,


0

1

Всем привет.

Возникла следующая задача: создать образ диска с GPT и двумя разделами (EFI/FAT32 и ext4), чтобы его можно было сразу заливать на блочное устройство.

Я быстро родил тривиальное решение:

  • создать болванку образа нужного размера с помощью truncate
  • sgdisk’ом разметить образ в GPT с двумя разделами нужных типов
  • создать loop-ноды для образа диска через losetup
  • примонтировать loopNpM, залить данные, отмонтировать и убрать loop-ноды

Тем не менее, я хотел бы допилить это решение так, чтобы оно работало и на системах без возможности создания loop-нод (в ограниченных контейнерах, например).

В голову пришла мысль создать болванку с GPT-таблицей и cat’ом или dd’шкой присобачить в конец этой болванки образы разделов (создаваемые условными mkfs.XXX part_XXX.img). Попытался реализовать - не вышло, образ диска превращается в кашу, хоть я вроде и указывал отступ на 2048 байт от начала. Скорее всего я просто не осилил что-то в используемых утилитах, либо не вкурил матчасть.

Итак, вопрос: как в болванку образа GPT-диска добавить разделы из своих образов без использования loop-устройств?

UPD: вот так.



Последнее исправление: mradermaxlol (всего исправлений: 1)
Ответ на: комментарий от Flotsky

И правда. Насколько я вижу, у mkfs.vfat есть аналогичная опция с оффсетом в секторах, возьму на заметку.

mradermaxlol
() автор топика

А смысл так заморачиваться? Возьми просто, реальный винт, разметь его как тебе нужно, залей туда что тебе нужно, а потом сделай образ. Так ведь проще будет, не?

cadaber ★★
()

Зачем тебе создавать образ, не пользуясь loop’ами, если ты собираешься его только заливать там, где нет loop’ов? (И насчет заливать, кстати, тоже непонятно, почему надо это делать из контейнера).

thesis ★★★★★
()

указывал отступ на 2048 байт от начала

Это где такой? Обычно 2048 секторов (=1048576 байт) отступ до первого раздела.

firkax ★★★★★
()

У GPT две таблицы: одна в начале диска, другая в конце. Т.е. ты не можешь просто скопировать начало диска и дописать данные разделов в конец. Про 2048 байт уже написали.

d ★★★★
()
Ответ на: комментарий от cadaber

Так и делается сейчас, но от меня хотят автоматизацию создания образа диска (как я понял, для условного cp disk.img /dev/sda, но, возможно, и для чего-то ещё).

Требования пишу не я, увы.

mradermaxlol
() автор топика
Ответ на: комментарий от shpinog

Не совсем понял вопрос.

Насколько я знаю, есть mtools и e2tools, которые формально позволяют что-то делать внутри ФС без монтирования, в т.ч. если эта ФС лежит в отдельном файле-образе.

Ну и никто не запрещает делать mount partition.img /mnt без всяких плясок с /dev.

TL;DR: можно, разве нет?

mradermaxlol
() автор топика
Ответ на: комментарий от thesis

В контейнерах (или виртуальных машинах, тут кто во что горазд) лежит сборочное окружение, которое никто то ли не хочет, то ли не может обновить ещё с мезозоя.

Соответственно, всё нужное для образа диска создаётся сборочной системой в контейнере, и было бы логично для плясок с образом сделать хук постобработки для сборочной системы.

Касаемо заливки: scp disk.img root@device:/dev/sda, что вполне выполняемо из контейнера сразу после сборки.

mradermaxlol
() автор топика
Ответ на: комментарий от mradermaxlol

С помощью sgdisk сохраняешь в отдельный файл gpt таблицу. И в отдельные файлы снимаешь разделы.

Для снятия разделов можешь использовать dd.

В этом случае посредством sgdisk на целевом устройстве будешь восстанавливать таблицу разделов gpt и потом на файлы блочных устройств разделов лить дампы разделов.

Вместо dd можешь снимать дампы файловых систем, но в этом случае тебе придется на целевом диске после выполнения sgdisk на разделах создавать целевую файловую системы и восстанавливать дамп файловой системы.

Но в этом случае будет меняться uuid (идентификатор) файловых систем.

kostik87 ★★★★★
()
Ответ на: комментарий от firkax

Ошибку понял. Видимо, запутался в единицах измерения в процессе гуглежа.

mradermaxlol
() автор топика
Ответ на: комментарий от d

Читал про это, но тоже скатился в путаницу. Разве один из подвидов GPT (тот, что ещё с не то Hybrid, не то Protective MBR) не пишет прямо до конца устройства, игнорируя там кусок таблицы?

Anyway: можно ли как-то выделить эту часть таблицы и после добавления разделов присобачить обратно?

mradermaxlol
() автор топика
Ответ на: комментарий от kostik87

Ковырялся в создании дампа GPT у sgdisk’а, только не понял, что с этим потом делать, чтобы было хорошо.

Представим, что разделы уже сняты - девственно чистые образы mkfs.XXX part_XXX.img тоже считаются. Или я что-то путаю между понятиями «раздел» и «дамп ФС» в этом контексте?

Перечитав пару раз, я откровенно не понимаю, как тут происходит избегание loop-устройств. Или я упускаю какой-то очевидный способ отформатировать и примонтировать раздел прямо из образа диска, без озаloopливания?

mradermaxlol
() автор топика
Ответ на: комментарий от mradermaxlol

sgdisk - утилита для создания резервной копии таблицы разделов.

https://www.cyberciti.biz/faq/linux-backup-restore-a-partition-table-with-sfdisk-command/

sgdisk --backup=/tmp/sda.gpt /dev/sda

Сохранение GPT таблицы разделов диска /dev/sda в файл /tmp/sda.gpt

sgdisk --load-backup=sda.gpt /dev/sdb

Восстановление GPT таблицы разделов на диск /dev/sdb.

После этой операции будут созданы файлы блочных устройств диска /dev/sdb.

Т.е. если на /dev/sda было три раздела, /dev/sda1, /dev/sda2, /dev/sda3, то после восстановления таблицы разделов на /dev/sdb так же будут созданы 3 файла блочных устройств /dev/sdb1, /dev/sdb2, /dev/sdb3.

А дальше работаешь с разделами /dev/sdb1, /dev/sdb2, /dev/sdb3.

Или я что-то путаю между понятиями «раздел» и «дамп ФС» в этом контексте?

Дамп фс - это дамп фс, т.е. копия структур файловой системы записанных в специальном формате. Смонтировать его нельзя и развернуть dd тоже нельзя, но он занимает ровно столько места сколько было на файловой системе.

Вот пример работы с dump / restore файловой системы ext4: https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/storage_administration_guide/ext4backup

Снятие дампа файловой системы ext4 с раздела /dev/sda1:

dump -0uf /sda1.dump /dev/sda1

Восстановление дампа на раздел /dev/sdb1:

mkfs.ext4 /dev/sdb1
mkdir /mnt/sdb1
mount -t ext4 /dev/sdb1 /mnt/sdb1
cd /mnt/sdb1
restore -rf /sda1.dump

Как видишь в случае работы с дампами файловой системы ext4 для его восстановления на целевом диске нужно создать файловую систему ext4, смонтировать её, перейти в точку монтирования и восстановить дамп. В этом случае меняется UUID и LABEL файловой системы.

Для избежания этого нужно передавать параметры с указанием UUID и LABEL для утилиты mkfs.ext4.

mkfs.ext4 -U UUID -L LABEL /dev/sdb1

Вот пример снятия дампа XFS: https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/storage_administration_guide/xfsbackuprestore

Снятие дампа:

mkdir /mnt/sda1
mount /dev/sda1 /mnt/sda1
xfsdump -l 0 -f /sda1.xfsdump /mnt/sda1

Восстановление дампа:

mkfs.xfs /dev/sdb1
mkdir /mnt/sdb1
mount -t xfs /dev/sdb1 /mnt/sdb1
xfsrestore -f /sda1.xfsdump /mnt/sda1

Для изменения UUID и LABEL на файловой системе xfs используй утилиту xfs_admin:

xfs_admin -U ... /dev/sdb1
xfs_admin -L ... /dev/sdb1

В случае снятия дампа раздела с помощью утилиты dd устанавливать UUID и LABEL заново не нужно. Но размеры дампа dd будут соответствовать размеру раздела, а не занятому пространству на файловой системе.

kostik87 ★★★★★
()
Ответ на: комментарий от mradermaxlol

никто не запрещает делать mount partition.img /mnt без всяких плясок с /dev.

Ты не поверишь - пляска с /dev там всё равно под капотом есть :-)

mini-router [~]# dd if=/dev/zero of=test bs=1M count=32
32+0 records in
32+0 records out
mini-router [~]# mkfs.ext4 test
mke2fs 1.46.4 (18-Aug-2021)
Discarding device blocks: done
Creating filesystem with 32768 1k blocks and 8192 inodes
Filesystem UUID: 50af660b-cf57-47e1-bd95-8ac2a8918b41
Superblock backups stored on blocks:
        8193, 24577

Allocating group tables: done
Writing inode tables: done
Creating journal (4096 blocks): done
Writing superblocks and filesystem accounting information: done

mini-router [~]# mkdir /mnt/test
mini-router [~]# mount test /mnt/test/
mini-router [~]# losetup -a
/dev/loop0: 0 /image.squashfs
/dev/loop1: 0 /root/test

Обрати внимание на loop1.

Pinkbyte ★★★★★
()
Ответ на: комментарий от Pinkbyte

Ага, уже потом сообразил, что я ошибся. В любом случае я, судя по всему, накатал решение, доведу до ума и выложу сюда.

mradermaxlol
() автор топика
Ответ на: комментарий от aol

Спасибо за наводку, но я уже собрал свой велосипед.

mradermaxlol
() автор топика

На данный момент выглядит вот так. Требуемое решение реализовано в функции mode_noloop.

#!/bin/sh -Eeu


opt_help() {
	echo "createimg - tool for creating bootable disk images."
	echo "Usage: $(basename "$0") [OPTIONS]"
	echo
	echo "Options:"
	echo "	-h	Show this help text."
	echo "	-l	Create image with losetup and mount (sudo required)."
	echo "	-n	Create image with dd and mtools/e2tools."
	echo "	-e	EFI partition size in megabytes."
	echo "	-d	Data partition size in megabytes."
	echo "	-k	Kernel image filename."
	echo "	-i	Initrd filename."
}


prepare_image() {
	# exit with error if rootfs OR kernel image (OR both) is not present
	# useful if default filenames are used
	if [ ! -e "${KERNEL_IMAGE}" ] || [ ! -e "${INITRD_IMAGE}" ]; then
		echo "Bootable files (kernel and/or initrd) not found!"
		exit 1;
	fi


	# remove leftover images;
	# create sparse disk image and write GPT to it; create partition images;
	# 2 megabytes are reserved for GPT data
	for img_file in img_full.img img_p1_EFI.img img_p2_data.img; do rm $img_file; done
	truncate -s $((IMG_EFI_SIZE + IMG_DATA_SIZE + 2))M img_full.img
	truncate -s "${IMG_EFI_SIZE}"M img_p1_EFI.img
	truncate -s "${IMG_DATA_SIZE}"M img_p2_data.img
	sgdisk -og -n 0:0:+"${IMG_EFI_SIZE}"M -t 1:ef00 -n 0:0:+"${IMG_DATA_SIZE}"M -t 2:8300 -s img_full.img
}


mode_loop() {
	# create loopdev & exit with error if loopdev node could not be created
	LOOPDEV=$(sudo losetup --show -Pf img_full.img)
	if [ ! -e "${LOOPDEV}" ]; then exit 1; fi
	
	
	# format disk image partitions
	sudo mkfs.fat -F32 "${LOOPDEV}p1"
	sudo mkfs.ext4 -qF "${LOOPDEV}p2"
	
	
	# create tmp directories & mount disk image partitions
	IMG_TMP=/tmp/br_img
	mkdir -p ${IMG_TMP}/boot ${IMG_TMP}/root ${IMG_TMP}/efi/boot
	sudo mount "${LOOPDEV}p1" ${IMG_TMP}/boot
	
	
	# populate disk image boot partition
	cp -R efi-part/* ${IMG_TMP}/efi/
	cp "${KERNEL_IMAGE}" ${IMG_TMP}/efi/boot/
	cp "${INITRD_IMAGE}" ${IMG_TMP}/efi/boot/initrd
	sudo cp -R ${IMG_TMP}/efi/* ${IMG_TMP}/boot/
	
	
	# unmount disk image & cleanup
	sudo umount ${IMG_TMP}/boot
	sudo rm -rf ${IMG_TMP}
	
	
	# create partition images using cat to inherit user permissions
	# "sudo cp" creates files as root
	sudo cat "${LOOPDEV}p1" > img_p1_EFI.img
	sudo cat "${LOOPDEV}p2" > img_p2_data.img
	
	
	# release disk image loopdev
	sudo losetup -d "${LOOPDEV}"
}


mode_noloop() {
	# format partition images
	mkfs.fat -F32 img_p1_EFI.img
	mkfs.ext4 -qF img_p2_data.img


	# populate disk image boot partition
	mcopy -si img_p1_EFI.img efi-part/* ::
	mmd -i img_p1_EFI.img ::boot
	mcopy -i img_p1_EFI.img "${KERNEL_IMAGE}" ::boot/bzImage
	mcopy -i img_p1_EFI.img "${INITRD_IMAGE}" ::boot/initrd


	# add partition images to the disk image
	# calculating offsets:
	# 1) paritions start at sector 2048 (everything before that is GPT data)
	# 2) sector N's offset is (N * 2^9) bytes away from the beginning
	#    this means that sector 2048 is
	#    2048 * 2^9 = 2^20 = 1048576 bytes away
	#    IT IS ASSUMED THAT 1 SECTOR = 512 BYTES!
	# 3) amount of sectors is (size(P) * 2^11)
	#    where size(P) is the partition P size in megabytes
	# 4) taking that into account, first sector of the next partition P + 1
	#    is calculated as ((size(P) + 1) * 2^11)
	# 5) offset (taking GPT data into account) for dd'ing partiiton P + 1
	#    is calculated as ((size(P) + 1) * 2^20)
	# 6) for compatibility with POSIX shells, ** operator can't be used in
	#    arithmetic expressions; however, as we only use powers of 2, we can
	#    replace $((2 ** N)) with $((2 << (N - 1)))
	dd if=img_p1_EFI.img of=img_full.img oflag=seek_bytes \
		seek=$((2 << 19)) conv=notrunc status=none
	dd if=img_p2_data.img of=img_full.img oflag=seek_bytes \
		seek=$(((IMG_EFI_SIZE + 1) * (2 << 19))) conv=notrunc status=none
}


# set default creation mode, partition sizes & filenames
IMG_MODE=none
IMG_EFI_SIZE=256
IMG_DATA_SIZE=32
KERNEL_IMAGE=bzImage
INITRD_IMAGE=rootfs.cpio


# handle commandline arguments
while getopts ":hlne:d:k:i:" arg; do
	case $arg in
		h)
			opt_help
			;;
		l)
			IMG_MODE=loop
			;;
		n)
			IMG_MODE=noloop
			;;
		e)
			# check if arg is an integer
			if [ -n "${OPTARG##*[!0-9]*}" ]; then
				IMG_EFI_SIZE=${OPTARG}
			else
				echo "EFI partition size must be integer!"
			fi
			;;
		d)
			# check if arg is an integer
			if [ -n "${OPTARG##*[!0-9]*}" ]; then
				IMG_DATA_SIZE=${OPTARG}
			else
				echo "Data partition size must be integer!"
			fi
			;;
		k)
			# check path existece
			if [ -e "${OPTARG}" ]; then
				KERNEL_IMAGE=${OPTARG}
			else
				echo "Kernel image not found!"
				exit 1;
			fi
			;;
		i)
			# check path existence
			if [ -e "${OPTARG}" ]; then
				INITRD_IMAGE=${OPTARG}
			else
				echo "Initrd image not found!"
				exit 1;
			fi
			;;
		?)
			echo "Unknown option: -${OPTARG}"
			exit 1
			;;
	esac
done


# create image in selected mode
case $IMG_MODE in
	loop)
		prepare_image
		mode_loop
		;;
	noloop)
		prepare_image
		mode_noloop
		;;
	?)
		echo "No proper image creation mode specified: ${IMG_MODE}"
		exit 1
		;;
esac

mradermaxlol
() автор топика
Последнее исправление: mradermaxlol (всего исправлений: 1)

образ раздела сделать в виде sparse-файла ?? тогда пустое место не будет занимать место на носителе.
внутри все пространство разметить и отформатировать и залить нужные данные.
готовый образ-файл сжать чтобы пустое место не занимало. распаковывать на готовое напрямую из сжатого образа.

pfg ★★★★★
()
Ответ на: комментарий от cadaber

Што? Ты смешал в одну кучу разметку диска и структуру ФС. Разметка диска, будь то MBR или GPT, ничего не знает и не беспокоится о внутренностях ФС. Только offset+length и type.

d ★★★★
()
Ответ на: комментарий от pfg

Мне как раз sparse-образ и нужен. Им является лишь болванка, которая потом наполняется не очень-sparse-разделами, так что в итоге (насколько я понимаю) пустот в готовом образе минимум (либо их нет вообще). Далее образ кладётся на реальное блочное устройство соответствующего размера. А размер образа (для соответствия размерам устройства, если это необходимо) регулируется параметрами скрипта.

mradermaxlol
() автор топика
Последнее исправление: mradermaxlol (всего исправлений: 1)
Ответ на: комментарий от mradermaxlol

sparse-пустот в готовом образе будет столько, сколько осталось незаписанных блоков (размера той фс, на которой находится образ).
читающий файл sparse-пустот не увидит (без спец.едрёных функций), для него они будут подставляться нулями.
размер образа стационарен. «резиновых» файловых систем емнип нет (хотя и вполне вероятно ошибаюсь). размер фс прописан у ней внутре.
после распаковывания образа на на носитель, надобно будет еще скриптануть под размер имеющегося носителя - обычно последний раздел увеличивается до конца свободного места, а потом растягивается фс на нем.
правильно понимаю что целевые носители произвольнного размера ??

pfg ★★★★★
()
Ответ на: комментарий от pfg

правильно понимаю что целевые носители произвольнного размера ??

Совершенно верно.

mradermaxlol
() автор топика
Ответ на: комментарий от tz4678_2

В оригинальном посте явно указано, что с loop устройствами у меня и так всё было хорошо. Интересовала сборка рабочего образа GPT-диска без loop-устройств вообще.

mradermaxlol
() автор топика
Ответ на: комментарий от tz4678_2

Мы, кажется, не понимаем друг друга.

Сборочное окружение собирает образ initrd и ядро, а также содержимое раздела EFI. Мне нужно (после сборки) автоматически создать

  • образ раздела EFI
  • образ раздела с данными (чистая ФС)
  • общий образ диска с GPT и вышеуказанными разделами

Это нужно для того, чтобы разработчик после сборки системы мог выполнить cp img_full.img /dev/sda (или аналог с scp) и у него всё работало и запускалось. Образы разделов я оставляю на случай, если разработчик захочет перезаписать конкретный раздел на целевом устройстве.

Сделать все указанные образы - тривиальная задача, если пользоваться loop-устройствами. Однако мне нужно было также реализовать создание этих образов без loop-устройств ВООБЩЕ, чтобы образы можно было собрать даже в кастрированных ограниченных контейнерах.

Моё итоговое решение успешно с задачей справляется. Бонусом на тестовой системе в noloop-режиме срезается ~секунда времени выполнения из-за меньшего количества операций. Мелочь, а приятно.

mradermaxlol
() автор топика
Последнее исправление: mradermaxlol (всего исправлений: 1)
Ответ на: комментарий от mradermaxlol

Это нужно для того, чтобы разработчик после сборки системы мог выполнить cp img_full.img /dev/sda (или аналог с scp) и у него всё работало и запускалось.

да, вполне можно сделать такой образ.

но ! загвоздка в том, что если целевой носитель будет меньшего размер чем образ начнутся проблеммы в той фс, размер которой «обрежется». вполне вероятно не сначала, а прошествию времени и заполнению данной фс до упора.

в случае большего места на носителе. часть места просто не будет использоваться.

както так

зачем упиратся в отсутствие loop не понял. это всего лишь эффективный подручный инструмент получения блочного устройства из рабочей файловой системы.
в контейнерах вполне возможно воспользоваться средствами виртуализации по подсовыванию файлов хоста как блочных устройств гостю. будет без лупов, но смысл практически тот же.

pfg ★★★★★
()
Последнее исправление: pfg (всего исправлений: 1)
Ответ на: комментарий от pfg

Это я осознаю и это предполагаемое поведение, да. Финальный образ будет подгоняться под размер финального устройства.

mradermaxlol
() автор топика
Ответ на: комментарий от pfg

в контейнерах вполне возможно воспользоваться средствами виртуализации по подсовыванию файлов хоста как блочных устройств гостю. будет без лупов, но смысл практически тот же.

Увы, некоторые контейнеры настроены через /dev/ass, а у некоторых их пользователей на это же устройство является симлинком и /dev/brain.

mradermaxlol
() автор топика
Ответ на: комментарий от mradermaxlol

сделать контейнер на «правильной» системе или использовать систему виртуализацию, которая умеет подсовывать файлы хоста как блочные устройства.

образ, после заполнения нужными файлами, обрезать по использованному месту. и при разворачивании на цели, после распаковывания растягивать его по наличию места на разделе.

pfg ★★★★★
()
Ответ на: комментарий от mradermaxlol

Насколько я знаю, есть mtools и e2tools, которые формально позволяют что-то делать внутри ФС без монтирования, в т.ч. если эта ФС лежит в отдельном файле-образе.

Но оно ничего из современного не поддерживает.

Ну и никто не запрещает делать mount partition.img /mnt без всяких плясок с /dev.

В конечном счёте, разве это не будет монтированием блочного устройства ? Оно как-то прозрачно зайдёт в gpt образ, и будет писать в раздел напрямую в фс?

shpinog ★★★★
()
Ответ на: комментарий от shpinog

Но оно ничего из современного не поддерживает.

Мои тесты говорят об обратном. Возможно, есть что-то, чего я не знаю?

В конечном счёте, разве это не будет монтированием блочного устройства ?

Создание образа GPT-диска с несколькими разделами без loop-устройств (комментарий)

mradermaxlol
() автор топика
Ответ на: комментарий от mradermaxlol

Создание образа GPT-диска с несколькими разделами без loop-устройств (комментарий)

Ну так ты просто создал файл, и сделал внутри разметку gpt. Да, для этого loop не нужен. Но как ты потом в этом файле без loop будешь создавать fs? Ты же сначала должен его примонтировать как блочное устройство. Что и происходит в твоём скрипте.

shpinog ★★★★
()
Последнее исправление: shpinog (всего исправлений: 1)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.