Editor’s note: This tutorial is written using Ubuntu Linux. If you are on a different platform you may need to replace commands such as apt-get
with your distributions equivalent.
What’s In Those .img Files?
Have you ever found yourself with an .img
file blindly following the tutorial on “flashing” it onto an SD card? Did you know you can create your own disk image files for distributions on thumb drives, SD cards, etc. with minimal effort? This tutorial aims to show you how to:
- Discover the hidden secrets of a monolithic
.img
file - Mount the partitions in an
.img
file usinglosetup
,kpartx
andmount
- Create your own
.img
files and use them as virtual disks - Write out your virtual disk image to a thumb drive (or any drive for that matter) for use later
Let’s take a look at the BeagleBone Black Debian distribution file. You can download it with this link.
Once its downloaded you will want to uncompress it with xz --decompress
:
1 |
$ xz --decompress bone-debian-7.8-lxde-4gb-armhf-2015-03-01-4gb.img.xz |
If you don’t have xz
installed you can obtain it through apt-get install xz-utils
.
Once the image is decompressed many tutorials would have you stop there and go straight to using dd
to do a byte-by-byte copy onto an SD card, insert into the BeagleBone Black, and boot. We want to examine it a bit further first.
First, we’re going to attach the image file to what is known as a loopback device. The Wikipedia entry introduction is succinct: “In Unix-like operating systems, a loop device is a pseudo-device that makes a file accessible as a block device.” In short, a loop device allows us to present the image file to the operating system as if it were an actual “disk”. To set up the loop device:
1 |
$ sudo losetup /dev/loop0 bone-debian-7.8-lxde-4gb-armhf-2015-03-01-4gb.img |
We used /dev/loop0
in this example. If /dev/loop0
wasn’t available to us (that is, it was already in use), we could have chosen /dev/loop1
, etc. To get a list of the loopback devices that are already in use, one can use losetup -a
:
1 2 |
$ sudo losetup -a /dev/loop0: [0801]:10976989 (/home/user/BBB/bone-debian-7.8-lxde-4gb-armhf-2015-03-01-4gb.img) |
Now /dev/loop0
is attached. What can we do? How about look at the partition table with fdisk
?
1 2 3 4 5 6 7 8 9 10 11 12 |
$ sudo fdisk -l /dev/loop0 Disk /dev/loop0: 3879 MB, 3879731200 bytes 255 heads, 63 sectors/track, 471 cylinders, total 7577600 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 Disk identifier: 0x00000000 Device Boot Start End Blocks Id System /dev/loop0p1 * 2048 198655 98304 e W95 FAT16 (LBA) /dev/loop0p2 198656 7577599 3689472 83 Linux |
Whoa! From the output of fdisk
we can infer a few things, namely that:
- There is a partition table present
- There are two partitions: one with a FAT16 filesystem and another with a “Linux” filesystem
We want some more information about the filesystems on the partitions, so we’re going to utilize the utility kpartx
, which according to the man
page will Create device maps from partition tables. If you don’t have kpartx
on your system, it can be installed with apt-get install kpartx
.
To see what kpartx
would map, run it with the -l
option:
1 2 3 |
$ sudo kpartx -l /dev/loop0 loop0p1 : 0 196608 /dev/loop0 2048 loop0p2 : 0 7378944 /dev/loop0 198656 |
Let’s go ahead and run it and add the maps:
1 |
$ sudo kpartx -a /dev/loop0 |
Don’t go looking for loop0p1
in /dev
, but rather you should look in /dev/mapper
. kpartx
will assign the partitions it found and enumerate the partitions in /dev/mapper
with the pattern loop0pX
where X is the partition number assigned.
Now that the partitions are mapped, let’s examine the filesystems on each partition with file
and the --special-files
and --dereference
options.
1 2 |
$ sudo file -sL /dev/mapper/loop0p1 /dev/mapper/loop0p1: x86 boot sector, mkdosfs boot message display, code offset 0x3c, OEM-ID "mkfs.fat", sectors/cluster 4, root entries 512, Media descriptor 0xf8, sectors/FAT 192, heads 255, sectors 196608 (volumes > 32 MB) , serial number 0xed080652, label: "BEAGLEBONE ", FAT (16 bit) |
From the file
manpage: Specifying the -s option causes file to also read argument files which are block or character special files. This is useful for determining the filesystem types of the data in raw disk partitions, which are block special files.
Let’s look at the second partition, which the only thing we know thus far is that it is a “Linux filesystem”.
1 2 |
sudo file -sL /dev/mapper/loop0p2 /dev/mapper/loop0p2: Linux rev 1.0 ext4 filesystem data, UUID=8e249d34-9f95-4aa2-928f-584551296f5e, volume name "rootfs" (extents) (large files) (huge files) |
The second partition is clearly formatted with an ext4
filesystem.
Now that we have our partitions mapped, we can mount them. Create two directories to serve as mountpoints:
1 2 |
$ mkdir BEAGLEBONE # We will mount the FAT partition here $ mkdir rootfs # We will mount the ext4 partition here |
Once they are created, mount
the filesystems.
1 2 |
$ sudo mount /dev/mapper/loop0p1 BEAGLEBONE $ sudo mount /dev/mapper/loop0p2 rootfs |
It should be quite clear as to why we chose these commands to mount the filesystems. Once they are mounted you can cd
into them and look around, change things, etc.
Once you are done and want to “let go” of the .img
file, reverse the process with:
1 2 3 4 |
$ sudo umount BEAGLEBONE $ sudo umount rootfs $ sudo kpartx -d /dev/loop0 $ sudo losetup -d /dev/loop0 |
Creating Your Own .img Files
Creating your own .img
files is quite easy. Let’s say we want to distribute a 2G USB stick that is partitioned with two filesystems: one an ext4 filesystem and the other a btrfs filesystem. On each we’ll store the latest (as of this writing) kernel source code: linux-4.2-rc5.
First, we’ll use the dd
utility and the /dev/zero
device to create a monolithic 2G file. We’ll use the SI definition for 2 gigabytes, which is 2000000000. This command instructs dd
to use /dev/zero
as the input file and linuxsource.img
as the output file. bs
is block size and count
is the number of blocks to write. We could have used a block size of 1 but dd
is much slower (an inordinate number of minutes compared to 10 seconds with a blocksize of 1000).
Warning: It is a time honored tradition to remind you to treat dd
with respect, akin to issuing commands as root
. Misused or supplied the wrong arguments dd
can seriously ruin your day.
1 2 3 4 |
$ sudo dd if=/dev/zero of=linuxsource.img bs=1000 count=2000000 2000000+0 records in 2000000+0 records out 2000000000 bytes (2.0 GB) copied, 11.6933 s, 171 MB/s |
Now we have a raw file, 2G in length, full of zeroes. What do we do with it?
The answer is to present it to the OS as a loop device, and then use fdisk
to create a partition table. We effectively created a blank disk drive with nothing on it, so the first step is to put a partition table on the disk.
1 2 |
# Present the file as a loop device $ sudo losetup /dev/loop0 linuxsource.img |
Now we use fdisk
:
1 2 3 4 5 6 7 8 9 |
$ sudo fdisk /dev/loop0 Device contains neither a valid DOS partition table, nor Sun, SGI or OSF disklabel Building a new DOS disklabel with disk identifier 0xed4eb569. Changes will remain in memory only, until you decide to write them. After that, of course, the previous content won't be recoverable. Warning: invalid flag 0x0000 of partition table 4 will be corrected by w(rite) Command (m for help): |
Surprise! There’s no partition table on the “disk”. We have to create one and create our partitions. Type n
at the Command prompt. n
is for new partition.
1 2 3 4 5 |
Command (m for help): n Partition type: p primary (0 primary, 0 extended, 4 free) e extended Select (default p): |
Hit ENTER
to accept the default of a primary partition. Hit ENTER
again to accept the default of partition number 1
. Hit ENTER
again to accept the default of 2048 as the first sector.
Now we will enter +900M as our “last sector”, which is actually applying a size of 900MB for the partition. Here is what the full sequence looks like for creating our first partition:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Command (m for help): n Partition type: p primary (0 primary, 0 extended, 4 free) e extended Select (default p): Using default response p Partition number (1-4, default 1): Using default value 1 First sector (2048-3906249, default 2048): Using default value 2048 Last sector, +sectors or +size{K,M,G} (2048-3906249, default 3906249): +900M Command (m for help): |
Let’s create a second partition while we are at it, but if you are curious, you can press p
here to print out what the partition will look like when its written.
1 2 3 4 5 6 7 8 9 10 11 |
Command (m for help): p Disk /dev/loop0: 2000 MB, 2000000000 bytes 255 heads, 63 sectors/track, 243 cylinders, total 3906250 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 Disk identifier: 0xed4eb569 Device Boot Start End Blocks Id System /dev/loop0p1 2048 1845247 921600 83 Linux |
Create the second partition by issuing n
and following the same steps. Choose defaults for the partition type, partition number, first sector, and use +990M for the last sector. To write out the partition table type w
.
1 2 3 4 5 6 7 8 9 |
Command (m for help): w The partition table has been altered! Calling ioctl() to re-read partition table. WARNING: Re-reading the partition table failed with error 22: Invalid argument. The kernel still uses the old table. The new table will be used at the next reboot or after you run partprobe(8) or kpartx(8) Syncing disks. |
Don’t ignore the warning here. You’ve changed the partition table of a disk but the kernel is still referencing the old (i.e., non-existent) partition table. Run partprobe
and the kernel will reread all of its attached devices for the partition tables. If you run fdisk -l /dev/loop0
now, you won’t get a nasty comment about no partition table!
1 2 3 4 5 6 7 8 9 10 11 12 |
$ sudo fdisk -l /dev/loop0 Disk /dev/loop0: 2000 MB, 2000000000 bytes 255 heads, 63 sectors/track, 243 cylinders, total 3906250 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 Disk identifier: 0xed4eb569 Device Boot Start End Blocks Id System /dev/loop0p1 2048 1845247 921600 83 Linux /dev/loop0p2 1845248 3872767 1013760 83 Linux |
Okay, we have our partition table written, but there are no actual filesystems present. We will have to make them with mkfs (make filesystem) utilities. Using kpartx
once more we need to map the partitions.
1 2 3 |
$ sudo kpartx -a /dev/loop0 $ ls /dev/mapper/loop0p* /dev/mapper/loop0p1 /dev/mapper/loop0p2 |
We’ll start with our first partition and create an ext4 filesystem, followed by a btrfs filesystem on the second partition.
1 2 |
$ sudo mkfs.ext4 /dev/mapper/loop0p1 $ sudo mkfs.btrfs /dev/mapper/loop0p2 |
Note: If mkfs.btrfs
is not available you can install it with apt-get install btrfs-tools
.
After these commands you should be able to use file -SL
to examine the filesystems present.
1 2 3 4 |
$ sudo file -sL /dev/mapper/loop0p1 /dev/mapper/loop0p1: Linux rev 1.0 ext4 filesystem data, UUID=7ed2c156-dc38-4626-a10f-ed6fdb1c30b5 (extents) (large files) (huge files) $ sudo file -sL /dev/mapper/loop0p2 /dev/mapper/loop0p2: BTRFS Filesystem sectorsize 4096, nodesize 4096, leafsize 4096) |
Now let’s mount our two filesystems:
1 2 3 4 |
$ sudo mkdir ext4 $ sudo mkdir btrfs $ sudo mount /dev/mapper/loop0p1 ext4 $ sudo mount /dev/mapper/loop0p2 btrfs |
If you look in the ext4
directory you should find a directory called lost+found
. This is a special directory present on ext
-based filesystems where fsck
puts recovered files. You will not see this directory present in the btrfs
directory.
We’re going to unpack the Linux source tree into both filesystems. Download the source from Kernel.org. We are using 4.2 Release Candidate 5.
1 2 3 4 |
$ cd ext4 # Go into our ext4 filesystem $ sudo tar -xJf ~/Downloads/linux-4.2-rc5.tar.xz $ cd ../btrfs # Go into our btrfs filesystem $ sudo tar -xJf ~/Downloads/linux-4.2-rc5.tar.xz |
Using df
in the respective directory you can see how much space was taken up.
1 2 3 4 5 6 7 8 |
$ cd ext4 $ df -h . Filesystem Size Used Avail Use% Mounted on /dev/mapper/loop0p1 870M 707M 103M 88% /home/user/BBB/ext4 $ cd ../btrfs/ $ df -h . Filesystem Size Used Avail Use% Mounted on /dev/mapper/loop0p2 990M 766M 101M 89% /home/user/BBB/btrfs |
Now that the source has been exploded into the two filesystems, unmount the partitions.
1 |
$ sudo umount btrfs/ ext4/ |
Go ahead and instruct kpartx
to unmap the partitions as well and remind yourself that /dev/loop0
is still presenting our original image file. You can release it as well.
1 2 3 4 |
$ kpartx -d /dev/loop0 $ sudo losetup -a /dev/loop0: [0801]:10976993 (/home/user/BBB/linuxsource.img) $ sudo losetup -d /dev/loop0 |
Writing Out the .img File
Now we come to writing the entire linuxsource.img
out to a USB thumbdrive. Once more we will be using dd
and once more we’ll warn you: ensure you are writing out to the USB device. Writing to the wrong device could render your system inoperable and destroy data!. I suggest watching /var/log/syslog
with tail -f
to see where the USB drive was attached. In this case it was attached to /dev/sdh
.
1 2 3 |
Aug 8 14:36:34 darthvader kernel: [70798.027860] scsi 19:0:0:0: Direct-Access USB 2.0 USB Flash Drive 0.00 PQ: 0 ANSI: 2 Aug 8 14:36:34 darthvader kernel: [70798.028505] sd 19:0:0:0: Attached scsi generic sg8 type 0 Aug 8 14:36:34 darthvader kernel: [70798.030276] sd 19:0:0:0: [sdh] 3947016 512-byte logical blocks: (2.02 GB/1.88 GiB) |
Warning: Replace /dev/sdX
below with the device your system mounted the USB drive.
1 2 3 4 |
$ sudo dd if=linuxsource.img of=/dev/sdX bs=1M 1907+1 records in 1907+1 records out 2000000000 bytes (2.0 GB) copied, 234.545 s, 8.5 MB/s |
dd
will happily write out every byte in linuxsource.img
onto the device presented at /dev/sdh
. Once it is done we’re going to have a USB stick with a partition table and two partitions. Once the command completes run fdisk -l /dev/sdX
, where X is where your system has the USB drive available.
1 2 3 4 5 6 7 8 9 10 11 12 |
$ sudo fdisk -l /dev/sdh Disk /dev/sdh: 2020 MB, 2020872192 bytes 63 heads, 62 sectors/track, 1010 cylinders, total 3947016 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 Disk identifier: 0xed4eb569 Device Boot Start End Blocks Id System /dev/sdh1 2048 1845247 921600 83 Linux /dev/sdh2 1845248 3872767 1013760 83 Linux |
We can take a look at our partitions and see our filesystems are there!
1 2 3 4 |
$ sudo file -sL /dev/sdh1 /dev/sdh1: Linux rev 1.0 ext4 filesystem data, UUID=7ed2c156-dc38-4626-a10f-ed6fdb1c30b5 (extents) (large files) (huge files) $ sudo file -sL /dev/sdh2 /dev/sdh2: BTRFS Filesystem sectorsize 4096, nodesize 4096, leafsize 4096) |
Mount a partition and verify that our Linux kernel source is intact!
Closing Remarks
Linux provides a variety of powerful tools for creating and manipulating disk images and disks. Although a little daunting at first the concepts of devices, partitions, and filesystems are quite simple to grasp. Hopefully this tutorial was helpful in exploring how to leverage these tools to create your own disk images!
Hi,
What if xxxx.img file is a huge file and placed on different partition ?
How to do it ?