Shrinking Multi Partition Linux Image Files

Recently I purchased a Samsung PRO+ “128GB” micro SD card for use with a RetroPie setup. I put 128GB in quotes here as after dropping it on a box and inspecting the true size, I find that Samsung pulled the old bait and switch on this card and it’s true size is 119GB. Unfortunately for me, I had a multi partition 128GB .img file already to go.

Power of *nix to the rescue!

TL;DR:

  1. Temporarily mount partition within imaged as a loop and remove enough files for desired target size
  2. Mount as a loop device and shrink the partition – leaving unallocated space at the end of the image file
  3. Discover the new ending byte offset of the partition and truncate the file at that point
  4. Viola! A smaller (and still bootable) image file!

Example Session

First, let’s discover some information about the image file itself:

$ parted image.img
(parted) unit
Unit? [compact]? B  
(parted) print
Model:  (file)  
Disk /home/bryan/Downloads/image.img: 128094044160B  
Sector size (logical/physical): 512B/512B  
Partition Table: msdos  
Disk Flags: 

Number  Start      End            Size           Type     File system  Flags  
 1      4194304B   63963135B      59768832B      primary  fat16        boot, lba
 2      63963136B  127865454591B  127801491456B  primary  ext4

(parted) quit

Note the start offset of 63963136. We’ll use that in the next step:

$ mkdir mnt
$ sudo mount -o loop,offset=63963136 ./mnt/  

OK, we have a mounted image file! Browse and remove any unwanted/unnecessary files. The goal here is to free enough space for our target device…

With that out of the way, unmount the file:

$ sudo umount ./mnt/

Now, we want to mount the entire image file as a loopback device. This will allow us to muck around with it in GParted (or whatever you fancy):

$ sudo modprobe loop
$ sudo losetup -f
/dev/loop0
$ sudo losetup /dev/loop0 image.img
$ sudo gparted /dev/loop0

From here you can use GParted to resize the main ext4 (or whatever) partition. Note that you may very likely need to still leave a small amount of space at the end of the allocated area (ie: don’t fully shrink) else the resize may fail. Once you’re happy here, make sure to apply and exit GParted.

Now, unmount the loopback device:

$ sudo losetup -d /dev/loop0

Let’s also find out the new ending position of the partition:

$ sudo fdisk -l image.img
Disk image.img: 119.3 GiB, 128094044160 bytes, 250183680 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: 0x81c0ff4b

Device                           Boot  Start       End   Sectors   Size Id Type  
image.img1 *      8192    124927    116736    57M  e W95 FAT16 (LBA)  
image.img2      124928 222177279 222052352 105.9G 83 Linux  

Good deal! We see here the ending position of the partition we just resized is 222177279. Our byte offset is (end + 1) * 512. In this case (222177279 + 1) * 512 = 113754767360. Let’s now truncate the image file down to size:

$ truncate --size=113754767360 image.img

…and that’s it!