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
.imgfile - Mount the partitions in an
.imgfile usinglosetup,kpartxandmount - Create your own
.imgfiles 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 ?