I am creating an empty file...

dd if=/dev/zero of=${SDCARD} bs=1 count=0 seek=$(expr 1024 \* ${SDCARD_SIZE})

...then turning it into an drive image...

parted -s ${SDCARD} mklabel msdos

...and creating partitions on it

parted -s ${SDCARD} unit KiB mkpart primary fat32 ${IMAGE_ROOTFS_ALIGNMENT} $(expr ${IMAGE_ROOTFS_ALIGNMENT} \+ ${BOOT_SPACE_ALIGNED})

How do I use mkfs.ext and mkfs.vfat without mounting this image?

4 Answers 4


To create an image with multiple partitions, a solution that doesn't require any fancy tools or root access is to first create the filesystems, then concatenate them.

truncate -s $IMAGE_ROOTFS_ALIGNMENT disk
truncate -s $BOOT_SPACE_ALIGNED part1
mkfs.fat part1
cat part1 >>disk
truncate -s $ROOTFS_SIZE part2
mkfs.ext4 part2
cat part2 >>disk

Then run parted or fdisk to create the partitions.

This approach has the downside that the resulting image won't be sparse.


To expand on the answer provided by @gilles, here's one way to create a disk image containing a formatted filesystem by first creating a filesystem (of type ESP in this example) within a file and then assembling that to a valid disk image; no root, mounts or loop devices required:

diskimg=diskimg    # filename of resulting disk image
size=$((260*(1<<20))) # desired size in bytes, 260MB in this case
alignment=1048576  # align to next MB (https://www.thomas-krenn.com/en/wiki/Partition_Alignment)
size=$(( (size + alignment - 1)/alignment * alignment ))  # ceil(size, 1MB)

# mkfs.fat requires size as an (undefined) block-count; seem to be units of 1k
mkfs.fat -C -F32 -n "volname" "${diskimg}".fat $((size >> 10))

# insert the filesystem to a new file at offset 1MB
dd if="${diskimg}".fat of="${diskimg}" conv=sparse obs=512 seek=$((alignment/512))
# extend the file by 1MB
truncate -s "+${alignment}" "${diskimg}"

# apply partitioning
parted --align optimal "${diskimg}" mklabel gpt mkpart ESP "${offset}B" '100%' set 1 boot on

The above approach has the side-benefit of being sparse when used on a filesystem that supports sparse files; the resulting "262MB" file occupies less than 200kB on disk:

du -h --apparent diskimg; du -h diskimg
262M    diskimg
196K    diskimg

For FAT filesystems the Mtools utilities support operating on an offset into a file (ext2/4/etc probably do as well?). This makes it easier, you just create the partitioned image and work on that directly:

size=$((260*(1<<20))) # desired size in bytes, 260MB in this case
# align to next MB (https://www.thomas-krenn.com/en/wiki/Partition_Alignment)

size=$(( (size + alignment - 1)/alignment * alignment ))

# image size is gpt + filesystem size + gpt backup
truncate -s $((size + 2*alignment)) "${diskimg}"

parted --machine --script --align optimal "${diskimg}" mklabel gpt mkpart ESP "${alignment}B" '100%' set 1 boot on

mformat -i "${diskimg}"@@"${alignment}" -t $((size>>20)) -h 64 -s 32 -v "volname"

Here's a diagram of the resulting image file:

partitioned image file

  • This is great, its just missing the part of how to add another (root) partition to the image, probably ext4. How would I extend the code? Aug 29, 2023 at 22:21

You want to format a partition in a disk-image file, rather than the entire image file. In that case, you need to use losetup to tell linux to use the image file as a loopback device.

NOTE: losetup requires root privileges, so must be run as root or with sudo. The /dev/loop* devices it uses/creates also require root privs to access and use.

e.g (as root)

# losetup /dev/loop0 ./sdcard.img

# fdisk -l /dev/loop0
Disk /dev/loop0: 1 MiB, 1048576 bytes, 2048 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x54c246ab

Device       Boot Start   End Sectors   Size Id Type
/dev/loop0p1          1  1023    1023 511.5K  c W95 FAT32 (LBA)
/dev/loop0p2       1024  2047    1024   512K 83 Linux

# file -s /dev/loop0p1
/dev/loop0p1: data

# mkfs.vfat /dev/loop0p1 
mkfs.fat 3.0.28 (2015-05-16)
Loop device does not match a floppy size, using default hd params

# file -s /dev/loop0p1
/dev/loop0p1: DOS/MBR boot sector, code offset 0x3c+2, OEM-ID "mkfs.fat", sectors/cluster 4, root entries 512, sectors 1023 (volumes <=32 MB) , Media descriptor 0xf8, sectors/FAT 1, sectors/track 32, heads 64, serial number 0xfa9e3726, unlabeled, FAT (12 bit)

and, finally, detach the image from the loopback device:

# losetup -d /dev/loop0

See man losetup for more details.

  • Note that mounting / partitioning loopback devices does not always cause kernel to re-read partition table, therefore one should either run partprobe afterwards or initially invoke losetup with -P, --partscan option. See this answer for details: unix.stackexchange.com/a/425641/30230
    – PF4Public
    Aug 30, 2019 at 18:47
  • 1
    This should not be the accepted answer. The question is how to do this "without mounting", and losetup is the same thing as doing a loop mount of the file: askubuntu.com/questions/1073467/…
    – Étienne
    Apr 10, 2020 at 15:43
  • @Étienne you are mistaken, and you have misunderstood the Q you linked to - it is asking if one command (mount -o loop ...) is basically the same as two (losetup ...; mount ...). losetup is not the same as mounting a filesystem. It tells the kernel to assign one of the loopback devices to a file so it can be treated as if it were a block device, including partitioning it and formatting one or more of the partitions with mkfs. The partitioning can be done without losetup, but the formatting can not because mkfs has no option to use an offset.
    – cas
    Apr 11, 2020 at 2:31
  • @cas what I meant is that a typical use-case for wanting to avoid a mount-loop is to perform parallel builds which don't interfere with each others when using loop devices, and also to avoid running as root. The reply below with mformat allows both things. Using losetup allows neither, so while it is working I wonder what the advantage of losetup vs loop mount is.
    – Étienne
    Apr 12, 2020 at 9:18
  • @Étienne you're missing the obvious. again. The advantage of using losetup rather than mount is that it doesn't mount anything, which is exactly what the OP asked for. mount -o loop is effectively just a short-cut for losetup ...; mount ....
    – cas
    Apr 12, 2020 at 14:25

Since @user310346's answer proved particularly tricky and fragile to adapt for producing FAT16 filesystems for use in Box86, I spent a while wrestling with it and came up with three useful things to say:

First, most of that boilerplate in user310346's answer is for allowing you to specify an arbitrary partition size in bytes and then calculate the drive size from it, with appropriate rounding. If you're willing to specify an image/drive size (which I need to do because I'm trying to match a CHS preset from Box86), then parted can automate deriving the rest from it.

Second, the Parted manual from 2002 lists mkfs and mkpartfs commands that should theoretically be able to do this sort of thing, so it'd be worth looking through the changelogs to see what became of them. Maybe they just morphed into something more obscure but still not requiring root permissions.

Third, thanks to the Bochs manual, I discovered that, if you're going to use mtools, it's easier to get a working filesystem if you have it do all the work.

cd "$(dirname "$(readlink -f "$0")")" || exit


# Size for the hard drive image
# (Here's one that's in 86Box's CHS presets)

# Work around partition=1 not being possible on the command-line AFAIK
mtools_tmp="$(mktemp -d --tmpdir make_disk.XXXXXXXXXX)"
echo "drive c: file=\"${IMG_NAME}\" partition=1" >"$mtools_conf"
export MTOOLSRC="${mtools_conf}"
cleanup() {
    rm -rf "${mtools_tmp}"
trap cleanup EXIT

rm -f "${IMG_NAME}.img"
truncate -s "$((CYLINDERS * HEADS * SECTORS * 512))" "$IMG_NAME"

mpartition -I -t "$CYLINDERS" -h "$HEADS" -s "$SECTORS" c: 2>&1
mpartition -c -T 6 -t "$CYLINDERS" -h "$HEADS" -s "$SECTORS" c:
mformat c:
mcopy -s "$SRC_DIR"/* c:

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .