btrfs Magic

Posted by Nate Bargmann on Fri, Mar 10, 2023

A few years back I purchased a FreedomBox Pioneer Edition HSK (HSK) to use as a dedicated server for various functions. The HSK is built around the Olimex A20-OLinuXino_LIME2 (LIME2) Single Board Computer (SBC) and features a SATA port with an associated power port to support the use of an external disk drive—typically a 2.5" laptop sized drive, either a Hard Disk Drive (HDD) or Solid State Disk (SSD).

The FreedomBox is a software stack built on top of Debian and is intended to be a network appliance for a number of installable apps. From the Web page:

FreedomBox is a private server for non-experts: it lets you install and configure server applications with only a few clicks. It runs on cheap hardware of your choice, uses your internet connection and power, and is under your control.

The “cheap hardware” is always the attractive part as is the size of the Pioneer HSK which is small enough to be tucked away and forgotten until…

Yes, the inevitable occurred—crashes with alarming frequency that led me to finally investigate the issue. As the preferred way to boot and often times run such a system is with a micro-SD card, I found it giving a large number of errors.

Sigh…

Saving and writing the micro-SD image

Recovery seemed a remote possibility so I opted to try and salvage a copy with dd as (the mmcblk0 device name is what the kernel assigns to cards inserted in the internal SD card slot of my laptop. A card inserted into a USB card reader adapter will be assigned sdx where x is the next available letter, at least this is the case with my hardware):

dd if=/dev/mmcblk0 of=freedombox.img status=progress bs=8192

See man dd for an explanation of the syntax.

And hoped for the best. I did end up with a 128GB file that likely contains the errors as well but if the card died completely, at least I have that to copy whatever files weren’t corrupted.

The next step was to download a fresh image from the Pioneer Edition FreedomBox Web page and write it to a new micro-SD card which was more or less a reversal of the command above:

dd if=freedombox-stable-free_pioneer_a20-olinuxino-lime2-armhf.img of=/dev/mmcblk0 status=progress bs=8192

Note: dd is a simple but very powerful program. It won’t warn you if you’re about to overwrite existing data, either a file or whatever is on the target device. Take a moment to examine the command line again before hitting the Enter key. Writing directly to a device file will require root privileges.

Root partition on a SATA drive

With that done, I booted the HSK with the new image and proceeded through the initial setup. At this point I contemplated my options for using a SATA attached drive as the root partition. One option was to “ghost” the root partition of the micro-SD image to the SATA drive, but anyone who has tried wrestling with uboot knows that pointing it to a different root partition can be easy or impossible depending on how it was setup by the distribution at hand. This led me to search the FreedomBox Pioneer Support forum for any discussion of using a SATA drive as a root partition on the LIME2 hardware. The following message gave a clue in the form of the btrfs replace command (see the linked bug/issue for more details). Perhaps this would be the way to get the root partition onto the SATA drive.

At this point it should be noted that the FreedomBox image contains two partitions. The first is rather small and formatted in the ext2 file system format. The second is quite a bit larger and is formatted in the btrfs file system format. These will be noted as p1 and p2 going forward.

Once the system is booted and running from the micro-SD card, p1 is mounted under the boot directory of the root (/) file system. This is an advantage for moving the root file system to the SATA drive. According to the linked post above, the btrfs replace command held the promise of moving the root file system from the micro-SD card to the HDD while the system was live and running. Incredible! This I just had to try.

Left over from some previous project a few years ago. I had two 1 TB HDDs mounted on a metal tray that would serve as the base for this experiment. With one of the HDDs attached to the SATA port and powered, I booted the system from the micro-SD card and logged in from an attached USB keyboard as I had the HSK connected to it and an HDMI display but was denied running administrative commands with sudo.

To back up slightly, the initial FreedomBox setup is done through a Web page interface (WUI) and the user account that is setup becomes the administrator of the system. However, I found that logging in through the console (locally attached keyboard and monitor) does not grant that user access to administrative privileges through the sudo command, a bit of a surprise (Note: later reboots did allow for running sudo from the console. YMMV). Yet, logging in as the same user through Secure SHell (SSH) over the network from another computer does grant those privileges. As the system is most often run “headless”, i.e. no attached keyboard or monitor, this is not a big deal but is something to be aware of.

With the system up and running (the fresh image, not my long running one with errors at this point) and logged into the root account with sudo -i (easier as one doesn’t have to remember to prefix every command with ‘sudo’, I started the process with the following command:

# btrfs replace start -Bf /dev/mmcblk0p2 /dev/sda /

When complete, the utility closed without printing anything to the console simply returning the shell prompt. There is a way to monitor the progress which will be shown below.

To verify that the root has really been changed, use the df command:

# df -h
Filesystem      Size  Used Avail Use% Mounted on
udev            435M     0  435M   0% /dev
tmpfs           100M  3.5M   96M   4% /run
/dev/sda        118G   19G  100G  16% /
tmpfs           496M   24K  496M   1% /dev/shm
tmpfs           5.0M     0  5.0M   0% /run/lock
/dev/sda        118G   19G  100G  16% /.snapshots
/dev/mmcblk0p1  2.1G   56M  1.9G   3% /boot
tmpfs           100M  4.0K  100M   1% /run/user/10000
tmpfs           100M  4.0K  100M   1% /run/user/10001

Indeed, / now resides on /dev/sda (Note: btrfs can use the raw disk device without need of partitioning!)

The next step is to expand the root file system to use all of the available disk space:

# btrfs filesystem resize max /
Resize '/' of 'max'

Not much output so use the df command again to see the change:

# df -h
Filesystem      Size  Used Avail Use% Mounted on
udev            435M     0  435M   0% /dev
tmpfs           100M  3.5M   96M   4% /run
/dev/sda        932G   19G  914G   2% /
tmpfs           496M   24K  496M   1% /dev/shm
tmpfs           5.0M     0  5.0M   0% /run/lock
/dev/sda        932G   19G  914G   2% /.snapshots
/dev/mmcblk0p1  2.1G   56M  1.9G   3% /boot
tmpfs           100M  4.0K  100M   1% /run/user/10000
tmpfs           100M  4.0K  100M   1% /run/user/10001

In a lack of foresight I performed the previous steps with the micro-SD card with the errors, but once complete I realized that I was in a bit of a pickle. I had the root file system running from the SATA HDD but the boot partition was still on the micro-SD card with the errors. What to do?

As I had already resized the most recently copied file system to the max of the drive, I needed to get it to fit into the 29 GB or so available on the fresh micro-SD card. Well, btrfs can do that too:

# btrfs filesystem resize 25G /
Resize '/' of '25G'

And confirmed the new size:

# df -h
Filesystem      Size  Used Avail Use% Mounted on
udev            435M     0  435M   0% /dev
tmpfs           100M  3.5M   96M   4% /run
/dev/sda         25G   19G  6.5G  74% /
tmpfs           496M   24K  496M   1% /dev/shm
tmpfs           5.0M     0  5.0M   0% /run/lock
/dev/sda         25G   19G  6.5G  74% /.snapshots
/dev/mmcblk0p1  2.1G   56M  1.9G   3% /boot
tmpfs           100M  4.0K  100M   1% /run/user/10001
tmpfs           100M  4.0K  100M   1% /run/user/10000

Cool, eh?

The next steps I took were to:

  • Unmount the boot partition and remove the bad micro-SD card.
  • Insert the fresh micro-SD card.
  • Use the blkid utility to get the UUID of /dev/mmcblk0p1 and edit /etc/fstab to reflect that value.
  • Mount the new boot partition.

Now the problem I faced was figuring out how to make the fresh micro-SD card point to the second root partition instead of the first one. While I am sure there is a way to modify this value, I was impatient and opted to run the btrfs replace command to move the root file system to the fresh micro-sd card:

# btrfs replace start -Bf /dev/sda /dev/mmcblk0p2 /
ERROR: target device smaller than source device (required 1000204886016 bytes)

Crud. Except I didn’t say, “Crud!” (oblique reference to the movie A Christmas Story).

In my estimation, even though the resize was successful as shown by the df -h command, the system still had a reference to the partition being 1 TB, which clearly won’t fit into 29GB no matter what (or a LOT of data compression). A reboot should correct this little problem. Except that the next problem reared its ugly head, the reboot failed.

To cut a long story short, the root filesystem from the micro-SD card with errors carried its UUID throughout the process and the fresh micro-SD card was looking for a different UUID (the one native to the downloaded image). I learned this by connecting a serial adapter to the proper pins on the LIME2 board and watching the messages printed to the serial port as the system was booting. Then I used another btrfs command (that I have forgotten to note) to write the UUID the fresh micro-SD was expecting to find for the root partition into the file system I wanted to use as /, get the system to start from the fresh micro-SD card, run the btrfs replace command to get the root file system onto the SATA HDD, resize it, and finally make sure it all booted again. Whew!

Other than my boneheaded mistakes, the btrfs replace command is a very clever way to (almost) painlessly get the FreedomBox micro-SD card image’s root file system onto a SATA HDD.