Dynamically creating an IMG file with a FAT32 partition
I recently had a need to dynamically create an img
file containing a FAT32 partition so its contents would be bootable on a PC. I had a heck of a time trying to put all the pieces together from different articles online. Alas, I came up with a working solution, so here it is. Enjoy!
This solution is for Linux. It does not work on Windows, but will work on Windows Subsystem for Linux (WSL).
This is a multi-step process outlined below:
- Create the
disk.img
image file- Create empty
disk.img
file - Create partition table
- Create partition
- Format partition as FAT32 file system
- Create empty
- Create the
partition.img
image file- Create empty
partition.img
file - Format the partition image file as FAT32 file system
- Create empty
- Copy files into
partition.img
- Copy
partition.img
into the partition indisk.img
Create the disk.img
image file
First, we want to create a blank img
file.
The dd
command is used to create a file of a specified size. The bs
parameter is the block size, and count
is the number of blocks. So if we want a 1GB file, we would do bs=1M count=1024
. The if
parameter is the input file, which in this case is /dev/zero
. /dev/zero
is a special file that provides as many null characters as you want. So we can use this to create a blank file. The of
parameter is the output file, which in this case is disk.img
.
$ sudo dd if=/dev/zero of=disk.img bs=1M count=1024
user@example:~/img_test$ sudo dd if=/dev/zero of=disk.img bs=1M count=1024
1024+0 records in
1024+0 records out
1073741824 bytes (1.1 GB, 1.0 GiB) copied, 0.707222 s, 1.5 GB/s
Now we need to create a GPT partition table in the img
file.
We are using the parted tool to manage the disk partitions. The --script
parameter tells parted
to not prompt for user input and that we are running in a script for automation. The disk.img
parameter is the path to the img
file we created. The mktable
command creates a new partition table. The gpt
parameter specifies the type of partition table to create.
sudo parted --script disk.img mktable gpt
Create a FAT32 partition that spans the entire disk.
--align optimal
ensures the partition is aligned to the optimal sector size for the disk. mkpart
creates a partition. primary
is the partition type. fat32
is the file system type. 2048s
is the starting sector. 100%
is the ending sector.
sudo parted --script disk.img --align optimal mkpart primary fat32 2048s 100%
We can name the partition if we want. This is optional.
name
sets the name of the partition. 1
is the partition number. BOOT
is the name.
sudo parted --script disk.img name 1 BOOT
We can print the partition table to verify everything is correct.
$ sudo parted --script disk.img print
Model: (file)
Disk /home/user/disk.img: 1074MB
Sector size (logical/physical): 512B/512B
Partition Table: gpt
Disk Flags:
Number Start End Size File system Name Flags
1 1049kB 1073MB 1072MB BOOT msftdata
Create the partition.img
image file
We will use dd
to create another img
file to represent the FAT32 partition. Make it the same size as disk.img
minus a couple megabytes. This is to make up for the fact that the partition stars 2048 sectors in, and not at the beginning of the disk. If you don’t, you will get out of space errors when copying this partition into disk.img
$ sudo dd if=/dev/zero of=partition.img bs=1M count=1022
1022+0 records in
1022+0 records out
1071644672 bytes (1.1 GB, 1022 MiB) copied, 1.00757 s, 1.1 GB/s
Now we need to create a FAT32 file system on the partition image file.
We will use the mkfs.vfat
command to do this. -F 32
specifies the FAT32 file system. -v
is verbose output.
$ sudo mkfs.vfat partition.img -F 32 -v
mkfs.fat 4.2 (2021-01-31)
partition.img has 64 heads and 63 sectors per track,
hidden sectors 0x0000;
logical sector size is 512,
using 0xf8 media descriptor, with 2093049 sectors;
drive number 0x80;
filesystem has 2 32-bit FATs and 8 sectors per cluster.
FAT size is 2040 sectors, and provides 261117 clusters.
There are 32 reserved sectors.
Volume ID is 986efae3, no volume label.
Let us print the partition info to verify everything is correct.
$ sudo parted --script partition.img print
Model: (file)
Disk /home/user/img_test/partition.img: 1072MB
Sector size (logical/physical): 512B/512B
Partition Table: loop
Disk Flags:
Number Start End Size File system Flags
1 0.00B 1072MB 1072MB fat32
Copy files into partition.img
We can use the mcopy tool to copy files into the partition.img
file.
To test this, I created the directory files
, with 3 text files, and a directory inner_dir
, that contains another text file.
```bash
$ sudo mcopy -i partition.img /home/user/files/* -spQmv ::/
Copying inner_dir
Copying text4.txt
Copying text1.txt
Copying text2.txt
Copying text3.txt
s
indicates a recursive copy and will copy all directories and their contents. p
preserves the file attributes. Q
is valid when copying multiple files and will quit as soon as one copy fails. m
preserves the file modification time. v
is verbose output.
There is a b
switch to use batch copy mode for large recursive copies. For me, it kept erroring with Internal error, size too big
.
Copy partition.img
into the partition in disk.img
Lastly, we need to copy the contents of the partition.img
file into the partition in disk.img
. We can do this with the dd
command after mounting the partitions of the image file.
We will use kpartx
to attach the partition in disk.img
to a loop device. kpartx
will create a loop device for each partition in the image file. We will use the -a
switch to add the partition to the loop device. -v
is verbose output.
$ sudo kpartx -av disk.img
add map loop5p1 (253:0): 0 2093056 linear 7:5 2048
We can verify the attachment by listing out block devices.
$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
loop0 7:0 0 59.2M 1 loop /snap/core20/1977
loop1 7:1 0 59.3M 1 loop /snap/core20/2019
loop2 7:2 0 109.6M 1 loop /snap/lxd/24326
loop3 7:3 0 35.5M 1 loop /snap/snapd/20102
loop4 7:4 0 35.5M 1 loop /snap/snapd/20298
loop5 7:5 0 1G 0 loop
└─loop5p1 253:0 0 1022M 0 part
sda 8:0 0 1T 0 disk
└─sda1 8:1 0 1024G 0 part /mnt/example
sdb 8:16 0 30G 0 disk
├─sdb1 8:17 0 29.9G 0 part /
└─sdb15 8:31 0 99M 0 part /boot/efi
We can see that loop5
represents the loop device for disk.img
and loop5p1
is the partition we created. Now lets copy the contents of partition.img
into the loop5p1
partition. Notice the mapper
location. This is where kpartx
attached the partition.
$ sudo dd if=partition.img of=/dev/mapper/loop5p1 bs=1M
1022+0 records in
1022+0 records out
1071644672 bytes (1.1 GB, 1022 MiB) copied, 2.15913 s, 496 MB/s
Now lets unmount the partition.
$ sudo kpartx -dv disk.img
del devmap : loop5p1
loop deleted : /dev/loop5
We can delete partition.img
.
Lets print the partition info for disk.img
once more to verify we’re done.
$ sudo parted --script disk.img print
Model: (file)
Disk /home/sync-user/img_test/disk.img: 1074MB
Sector size (logical/physical): 512B/512B
Partition Table: gpt
Disk Flags:
Number Start End Size File system Name Flags
1 1049kB 1073MB 1072MB fat32 BOOT msftdata
We have a gpt
type partition table and 1 partition labeled BOOT
formatted with the FAT32 file system! disk.img
is ready to go!