Tuesday 28 August 2018

Backing up an RPi SD and shrinking the disk image

There are numerous tutorials on how to resize/shrinking media but they typically advise using gparted for the task; whilst gparted is a great tool, it can fail with obscure error messages when trying to shrink partitions. This is how we can do this the old fashioned way.

The process for shrinking a SD care image can be summarised as:
  • pull SD image onto local disk and clean up
  • use resize2fs -M to shrink the underlying filesystem
  • use fdisk to delete/recreate the partition to reflect the smaller filesystem
  • truncate the disk image

Pull SD image and clean

We assume the SD image has been put onto the disk (via dd if=/dev/sdb of=rpi.img bs=2M status=progress or similar) and the next steps are to clean up the disk image ahead of making it in the archive.

# find out about what we've pulled off the SD card
$ fdisk -l ./rpi.img Disk rpi.img: 3.7 GiB, 3965190144 bytes, 7744512 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: 0xe611ddec Device Boot Start End Sectors Size Id Type rpi.img1 8192 137215 129024 63M c W95 FAT32 (LBA) rpi.img2 137216 7607296 3.6G 83 Linux
# mount it through an explicit loop device and tidyup # .. could also: # mount -o loop,offset=$((137216*512)) rpi.img backup/ # but need to be aware of the start sector and sector size from the fdisk output above
$ losetup /dev/loop0 rpi.img # make sure the kernel is aware of the partitions on the loop0 device
$ partprobe /dev/loop0 $ fsck -f /dev/loop0p1 $ fsck -f /dev/loop0p2
# mount the pi partition and clean up
$ mkdir /tmp/rpi
$ mount /dev/loop0p2 /tmp/pi $ rm -fr /tmp/pi/var/logs/* \ /tmp/pi/var/cache/apt/archives/*.deb \ /tmp/pi/usr/share/doc/*
# change default hostname/ip so no clashes on network
$ vi /etc/network/interfaces /etc/hostname

$ umount /tmp/pi $ fsck -f /dev/loop0p2

Shrink filesystem

At this point we're ready to shrink the filesystem and for archive purposes, we'll just shrink to its minimum size using resize2fs (yes, its safe on the ext4 filesystem) and this is where we overcome the problem with the gparted method
# make sure its unmounted!! $ umount /dev/loop0p2
$ resize2fs -p -M /dev/loop0p2
resize2fs 1.43.4 (31-Jan-2017) Resizing the filesystem on /dev/loop0p2 to 534714 (4k) blocks. Begin pass 2 (max = 120494) Relocating blocks XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX Begin pass 3 (max = 30) Scanning inode table XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX Begin pass 4 (max = 3746) Updating inode references XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX The filesystem on /dev/loop0p2 is now 534714 (4k) blocks long.
# the new filesystem size in K for above is: $ echo "(534714 * 4096) / 1024" | bc
2138856
It is imperative that you take note of the last line output from resize2fs - this info can be obtained again via tune2fs -l. This is now how large your filesystem is and we will need to use these values to truncate the original disk image.

Now for the surprise: if you mount the partition again and run df -h you will find that the filesystem is not 100% full. For my image, I found that there was ~200Mb free on the filesystem even though we asked resize2fs to reduce the filesystem to its minimum .. and this is why gparted can fail with obscure message about "new size is smaller than minimum".

At this point the filesystem inside the disk image is smaller than when we started but the disk image (and the corresponding 2nd partition) is still the same size

Reduce the SD image size

To truncate the underlying disk image we have a few final steps:
  • find the size of the filesystem in K:
  • recreate the partition to contain the smaller filesystem
  • and then truncate
$ fdisk /dev/loop0
d 2
p
...
Device Boot Start End Sectors Size Id Type /dev/loop0p1 8192 137215 129024 63M c W95 FAT32 (LBA)
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 (2048-8191, default 2048): 137216
Last sector....: +2138856K
When creating new partition fdisk may to tell you to use a start sector that is not correct: use the End + 1 of the first partition. In the example above, we'd used 137216. For the last sector specific the size in Kilobytes with "+<value>K" notation using the value we found above. In this example +2138856K.

Now save exit. Next we verify that we've done things correct
$ losetup -D $ losetup /dev/loop0 rpi.img $ partprobe /dev/loop0 $ fsck -f /dev/loop0p2 $ mount /dev/loop0p2 /tmp/pi $ umount /tmp/pi
If at this point there were no errors during the mount, the file system looks good and we're ready to physically reduce the size of the image on disk.
$ fdisk -l /dev/loop0 .. 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: 0xe611ddec Device Boot Start End Sectors Size Id Type /dev/loop0p1 8192 137215 129024 63M c W95 FAT32 (LBA) /dev/loop0p2 137216 3282943 3145728 1.5G 83 Linux $ losetup -D # PAY ATTENTION - you want the value in the End column PLUS ONE
$ truncate -s $(( (1 + 3282943) * 512)) rpi.img
# final validation $ losetup /dev/loop0 rpi.img $ partprobe /dev/loop0 $ mount /dev/loop0p2 /tmp/pi -o ro $ umount /tmp/pi $ losetup -D

Now if you mount the device again you should see no errors, particularly at the partprobe - if you've used the wrong values it will complain and you've just been informed that you've lost that data forever.

Be careful, but its not too difficult to create the smallest RPi SD image for backup.

No comments:

Post a Comment