Skip to content

Instantly share code, notes, and snippets.

@gopalkildoliya
Created March 7, 2019 12:44
Show Gist options
  • Save gopalkildoliya/58004e96c04224485ac82598be6c1253 to your computer and use it in GitHub Desktop.
Save gopalkildoliya/58004e96c04224485ac82598be6c1253 to your computer and use it in GitHub Desktop.
Shrink SD card image

Copy SD Card image

First we will create a .img file of your SD card image.

Linux users can just use dd (the same command you probably used to get the image onto the SD card in the first place) or the dcfldd command (which shows the progress of the operation unlike dd). For example, to copy all contents off the device that represents your SD card into an .img file in your home directory:

You should find out the block size of your sd card first (using an improper block size can lead to issues later on... TRUST)

$ sudo fdisk -l /dev/mmcblk0

Please check the SD Card device, it might be something like /dev/mmcblk0 or /dev/sdX. In the following example we are using /dev/mmcblk0.

Now, look at this line from the output of the previous command. It will determine what we should use as the bs (block size, not bullsh!t):

Units: sectors of 1 * 512 = 512 bytes

So, bs=512 would be the proper syntax in this instance. Other examples could be: bs=2MB (for block size = 2 Megabytes), or bs=4MB (for block size = 4 Megabytes), without specification, dd/dcfldd default to the bytes value.

And finally, to copy all contents off the device that represents your SD card into an .img file in your home directory:

$ sudo dd if=/dev/mmcblk0 of=$HOME/sd_image.img bs=512

The above command may take some time depending on the size of your SD card.

Creating loopback device

Now to resize the SD card we will use resize2fs. resize2fs operates on devices, not simple files like images. This is why we first need to create a device for the image. We do this using the loopback-functionality of Linux.

First we will enable loopback if it wasn't already enabled:

$ sudo modprobe loop

Now we can request a new (free) loopback device:

$ sudo losetup -f

This will return the path to a free loopback device. In this example this is /dev/loop0.

Next we create a device of the image:

$ sudo losetup /dev/loop0 sd_image.img

Now we have a device /dev/loop0 that represents sd_image.img. We want to access the partitions that are on the image, so we need to ask the kernel to load those too:

$ sudo partprobe /dev/loop0

This should give us the device /dev/loop0p1, which represents the first partition in sd_image.img.

Here we will resize the second part loop0p2

If the partition the file system is on is currently mounted, unmount it.

For example

$ sudo umount /dev/loop0p2

Run fsck on the unmounted file system.

For example

$ sudo e2fsck -f /dev/loop0p2
e2fsck 1.42.13 (17-May-2015)
Pass 1: Checking inodes, blocks, and sizes
Pass 2: Checking directory structure
Pass 3: Checking directory connectivity
Pass 4: Checking reference counts
Pass 5: Checking group summary information
/dev/loop0p2: 6990/242880 files (0.0% non-contiguous), 78511/971008 blocks

Now we resize our file system with resize2fs. Shrink the file system with the resize2fs /dev/device size command.

For example

$ sudo resize2fs /dev/loop0p2 500M
resize2fs 1.42.13 (17-May-2015)
Resizing the filesystem on /dev/loop0p2 to 128000 (4k) blocks.
The filesystem on /dev/loop0p2 is now 128000 (4k) blocks long.

Accepted size units for file system block sizes are:

S - 512 byte sectors

K - kilobytes

M - megabytes

G - gigabytes

Please take note of the amount of blocks (128000) and their size (4k). We need that soon.

Now we delete our /dev/loop0p2 partition (don't be afraid, no data will be lost) and create a new, smaller one (but still big enough to hold our resized file system!). We can do this with fdisk:

$ sudo fdisk /dev/loop0

Its loop0 not loop0p2

Type m to get a list of all commands or p to print the partition table.

Command (m for help): p
Disk /dev/loop0: 3.7 GiB, 3995074560 bytes, 7802880 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: 0x4474fba8

Device       Boot Start     End Sectors  Size Id Type
/dev/loop0p1       2048   34815   32768   16M  c W95 FAT32 (LBA)
/dev/loop0p2      34816 7802879 7768064  3.7G 83 Linux

Now use d to delete the partition. Here we will delete the second partition.

Command (m for help): d
Partition number (1,2, default 2): 2

Partition 2 has been deleted.

Now we will create a new partition with n command. Give it a partition number 2.

For first sector user default. For the last sector we have to calculate it. Fortunately, we can specify the size in kilobytes (K), so we calculate the size like this:

We multiply the amount of blocks from the resize2fs output (128000) by the size of a block (4k), and to go sure the partition is big enough, we add 3 to 5% to it (3% was enough for me, but if you want to go sure take 5%):

128000 * 4k * 1.03 = 527360k

So we prepend that value with a + sign and replace the small k with a capital one (K) and enter it:

Command (m for help): n
Partition type
   p   primary (1 primary, 0 extended, 3 free)
   e   extended (container for logical partitions)
Select (default p): p
Partition number (2-4, default 2): 2
First sector (34816-7802879, default 34816): 
Last sector, +sectors or +size{K,M,G,T,P} (34816-7802879, default 7802879): +527360K                               

Created a new partition 2 of type 'Linux' and of size 515 MiB.

Now let's write our new partition table and exit fdisk:

Command (m for help): w
The partition table has been altered.
Calling ioctl() to re-read partition table.
Re-reading the partition table failed.: 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).

Now we don't need the loopback-device anymore, so unload it:

$ sudo losetup -d /dev/loop0

Shaving the image

Now that we have all the important data at the beginning of the image it is time to shave of that unallocated part. We will first need to know where our partition ends and where the unallocated part begins. We do this using fdisk:

$ fdisk -l sd_image.img

Here we will see an output similar to the following:

Disk sd_image.img: 3.7 GiB, 3995074560 bytes, 7802880 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: 0x4474fba8

Device        Boot Start     End Sectors  Size Id Type
sd_image.img1       2048   34815   32768   16M  c W95 FAT32 (LBA)
sd_image.img2      34816 1089535 1054720  515M 83 Linux

Note two things in the output:

  • The partition ends on block 1089535 (shown under End)
  • The block-size is 512 bytes (shown as sectors of 1 * 512)

We will use these numbers in the rest of the example. The block-size (512) is often the same, but the ending block (1089535) will differ for you. The numbers mean that the parition ends on byte 1089535*512 of the file. After that byte comes the unallocated-part. Only the first 1089535*512 bytes will be useful for our image.

Next we shrink the image-file to a size that can just contain the partition. For this we will use the truncate command (thanks uggla!). With the truncate command need to supply the size of the file in bytes. The last block was 1089535 and block-numbers start at 0. That means we need (1089535+1)*512 bytes. This is important, else the partition will not fit the image. So now we use truncate with the calculations:

$ sudo truncate --size=$[(1089535+1)*512] sd_image.img

Now you can use this image.

More Links:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment