owned this note
owned this note
Published
Linked with GitHub
Raspbian Btrfs root filesystem guide (2017-06)
==
<center>
![RaspberryPi](https://i.imgur.com/5zQC4Kf.png =210x253) ![Btrfs](https://i.imgur.com/wVEjDJ4.png =397x253)
</center>
# Introduction
This document will guide you through the process of configuring a boot-time mountable Btrfs root filesystem on Raspbian Jessie. We'll step through configuration of a new install of Raspbian lite on a freshly written SD card and discuss at a higher level the steps needed to convert an existing install. Additionally we'll explore leveraging Btrfs snapshots to make inexpensive weak backups, send those backups remotely as differentials and demonstrate how to revert to a prior operating system state quickly and easily.
Most of this can be done over SSH once you've prepped and installed your SD card, but you'll need physical access to plug/unplug storage and diagnose any misconfigurations during boot. I've done my best to thoroughly cover any gotchas relating to boot configuration.
## Scope
This is a (relatively) quick and dirty guide, if you're interested in using Btrfs I assume you're a reasonably savvy user so I've glossed over a number of things. Most "How do I ...?" or "What is ...?" questions should be easily Googleable and are beyond the scope of this document. I'll attempt to explain my reasons for various choices as we proceed.
I'll be using a lot of linux commands to make things easier to read, if you'd like to learn about them you can use `man {command}` to do so.
I've tested everything contained in this guide on a Pi 2B and a Pi 3, YMMV on other models.
## What you'll need
* A Pi
* Internet connectivity to/from your Pi
* Physical access to your Pi for some steps
* An SD card that you can erase then write a fresh Raspbian image onto.
* ~1.5GB extra space on the SD card to keep a backup bootable ext4 partition.
:::info
_Note: A 4GB SD card will work but your available space will be very limited. I'd recommend at least 8GB capacity._
:::
### Why two filesystems?
We want to keep a bootable ext4 partition available in case of damage to our btrfs filesystem. Having this partition enables you to easily modify `/boot/cmdline.txt` and boot an operable system with btrfs tools available to attempt recovery. Unlike booting a recovery ramdisk image you'll be able to install or update any packages you might need and restore remote backups if needed.
Strictly speaking this isn't necessary, but not everyone following along has a linux machine they can plug their SD card into and attempt repair if things go wrong.
## Caveats
:::warning
* **Btrfs does not support swap files**
If you need swap you'll need to create a dedicated swap partition or locate the swap file on a non-Btrfs partition. Most of us can simply disable swap.
_Do not try to be cute and set the swapfile +NOCOW ~that~ ~won't~ ~help.~_
:::
:::danger
* **Btrfs write amplification risk**
Btrfs is a copy on write (CoW) filesystem. Due to this design certain workloads and use-cases can produce large write amplification on the underlying storage media[^writeamplification].
When combined with inexpensive flash storage devices (SD cards, USB flash drives) these workloads can, and probably will, shorten the life of the storage device.
**In particular I would not locate storage backing a heavily updated database or an email server working directory on Btrfs on cheap flash.** These are the worst possible cases for write amplification. If you're running one of these services make another partition using a different filesystem to hold their working data.
:::
[^writeamplification]: [See: 'Recursive Updates in Copy-on-write File Systems - Modeling and Analysis'](http://www.jcomputers.us/vol9/jcp0910-11.pdf)
### Mitigation
If you're concerned about flash longevity but still want to use Btrfs consider using an SSD rather than an SD card or USB flash drive to hold your filesystem. Even an inexpensive SSD in a USB housing will have drastically better performance and longevity than either and will allow you to monitor device health for early warning signs before failure.
At the time of writing 16GB mSATA SSDs are [available on eBay](http://www.ebay.com/sch/i.html?_from=R40&_nkw=sandisk+16gb+msata&_sacat=0) for less than the cost of a good quality 16GB Class 10 SD card from Amazon and an [external mSATA to USB3 enclosure](https://www.amazon.com/Apricorn-mSATA-Enclosure-Upgrade-AMSW-USB3/dp/B00NTQGZK6) will cost you about $20. I use this particular enclosure on both of my Pis and it's suitable for 24/7 use without overheating.
## Let's do this anyway
For most of us the risk of excessive write amplification is relatively low in practice and the convenience of filesystem snapshots and differential backups are good enough reasons to go ahead anyway. The sorts of workloads that generate enough write amplification to kill media don't really apply to what most of us are doing with our devices.
In my (limited) testing I haven't seen more than a 10% write overhead compared to ext4 with journaling during normal use.
If, on the other hand, you routinely kill flash storage devices with your Pi you should probably stick with ext4 and/or switch to more durable storage.
Anyway, you've been warned.
Let's get started.
----
# New Install
## Pre Boot Preparation
Write a recent Raspbian image to your SD card using your favorite software (Etcher, win32DiskImager, dd, etc.)
I'll be using 2017-04-10 Raspbian lite for this walkthrough.
https://www.raspberrypi.org/downloads/raspbian/
### Modify boot partition files on your SD card before booting
We're going to stop raspbian from automatically growing the initial ext4 partition to fill all space on your SD card during first boot so we don't have to deal with shrinking the ext4 partiton later.
We'll also enable SSH as this tutorial uses a headless machine attached to the network.
* add an empty file named `ssh` (no extension) to the root of the SD card's boot partition
* edit `cmdline.txt`:
* remove parameter `init=/usr/lib/raspi-config/init_resize.sh`
* remove parameter `quiet` - this will help with debugging boot problems later
* optional edit: `config.txt`
* optional: on a headless server disable sound
* optional: overclock SD card reader on rPi 3, this speeds things up noticeably when we start rsyncing a couple gigabytes around.
## First boot
### Initial setup
Install the SD card in the pi, plug in ethernet, then power everything on. The Pi should be booted in ~30s or so.
* find the pi on your network - your DHCP server should show "raspberrypi" as a new client.
* ssh in as the default user `pi` with passwd: `raspberry`
* switch to root so we save keystrokes typing sudo before every command `sudo -i bash`
* add a new user: `useradd {youruser} -s /bin/bash -m -G adm,sudo`
* set a password: `passwd {youruser}`
* lock the default account: `passwd -l pi`
* revoke default account group membership: `usermod -G pi,users pi`
**log out completely, and ssh back in as our newly added user**
* switch to root again `sudo -i bash`
* grab a couple of tools we'll need: `apt-get update && apt-get install -y vim tree rsync`
* fix our keyboard layout in case we ever have to use the console:
`vim /etc/default/keyboard`
* change layout from "gb" to "us"
_Feel free to use nano, rather than vim, if that works for you._
* setup localization, gpu memory split, wifi country:
`raspi-config`
* Localization options:
* locale to en_US.UTF-8
* timezone to America/Los Angeles
* wifi country to US
* Advanced options:
* GPU memory split = 16
Set your localization appropriately.
You can setup wireless now if you like.
I'm intentionally skipping hostname changes so that we can take an image of the SD card once we've finished updating the system, this way we won't have name collisions if we re-use this image on another pi later.
### Manually resizing /
We need to make a bit of space before we update our system so we're going to grow the ext4 fs to ~1.5GB. This should give us enough wiggle room to go ahead and update everything and install needed tools.
We're going to pop open our SD card device in fdisk, print the partition table and locate the beginning sector of our ext4 root fs partition, delete the partition, recreate it at the same sector with our desired size then run resize2fs to grow our ext4 filesystem to fill our newly allocated space. This is non-destructive and can be done while the filesystem is online without losing data.
```
root@raspberrypi:~# fdisk /dev/mmcblk0
Welcome to fdisk (util-linux 2.25.2).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.
Command (m for help): p
Disk /dev/mmcblk0: 15 GiB, 16088301568 bytes, 31422464 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: 0x84fa8189
Device Boot Start End Sectors Size Id Type
/dev/mmcblk0p1 8192 92159 83968 41M c W95 FAT32 (LBA)
/dev/mmcblk0p2 92160 2534887 2442728 1.2G 83 Linux
Command (m for help):
```
:::danger
Note the start sector of your ext4 filesystem partition, in this case it's `92160`, then delete the partition.
:::
```
Command (m for help): d
Partition number (1,2, default 2): 2
Partition 2 has been deleted.
```
Now create a larger partition in its place using the same number and starting sector as before.
```
Command (m for help): n
Partition type
p primary (1 primary, 0 extended, 3 free)
e extended (container for logical partitions)
Select (default p):
Using default response p.
Partition number (2-4, default 2):
First sector (2048-31422463, default 2048): 92160
Last sector, +sectors or +size{K,M,G,T,P} (92160-31422463, default 31422463): +1.5G
Created a new partition 2 of type 'Linux' and of size 1.5 GiB.
```
:::danger
Double check that the partition begins on exactly the same sector as before, then write our changes to disk.
:::
```
Command (m for help): p
Disk /dev/mmcblk0: 15 GiB, 16088301568 bytes, 31422464 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: 0x84fa8189
Device Boot Start End Sectors Size Id Type
/dev/mmcblk0p1 8192 92159 83968 41M c W95 FAT32 (LBA)
/dev/mmcblk0p2 92160 3213311 3121152 1.5G 83 Linux
Command (m for help): w
The partition table has been altered.
Calling ioctl() to re-read partition table.
Re-reading the partition table failed.: Device or resource busy
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).
```
Done, let's tell the kernel that partitions on the SD card have changed and then grow the filesystem.
```
root@raspberrypi:~# partprobe /dev/mmcblk0
root@raspberrypi:~# resize2fs /dev/mmcblk0p2
resize2fs 1.42.12 (29-Aug-2014)
Filesystem at /dev/mmcblk0p2 is mounted on /; on-line resizing required
old_desc_blocks = 1, new_desc_blocks = 1
The filesystem on /dev/mmcblk0p2 is now 390144 (4k) blocks long.
```
Do we have free space?
```
root@raspberrypi:~# df -h
Filesystem Size Used Avail Use% Mounted on
/dev/root 1.5G 929M 456M 68% /
devtmpfs 459M 0 459M 0% /dev
tmpfs 463M 0 463M 0% /dev/shm
tmpfs 463M 12M 451M 3% /run
tmpfs 5.0M 0 5.0M 0% /run/lock
tmpfs 463M 0 463M 0% /sys/fs/cgroup
/dev/mmcblk0p1 41M 21M 21M 51% /boot
```
We do!
Verify that our PARTUUID is still there, and that it still matches the root PARTUUID in `/boot/cmdline.txt`:
```
root@raspberrypi:~# tree /dev/disk
/dev/disk
|-- by-id
| |-- mmc-USDU1_0x41019842 -> ../../mmcblk0
| |-- mmc-USDU1_0x41019842-part1 -> ../../mmcblk0p1
| `-- mmc-USDU1_0x41019842-part2 -> ../../mmcblk0p2
|-- by-label
| `-- boot -> ../../mmcblk0p1
|-- by-partuuid
| |-- 84fa8189-01 -> ../../mmcblk0p1
| `-- 84fa8189-02 -> ../../mmcblk0p2
|-- by-path
| |-- platform-3f202000.sdhost -> ../../mmcblk0
| |-- platform-3f202000.sdhost-part1 -> ../../mmcblk0p1
| `-- platform-3f202000.sdhost-part2 -> ../../mmcblk0p2
`-- by-uuid
|-- 70CE-EB76 -> ../../mmcblk0p1
`-- f2100b2f-ed84-4647-b5ae-089280112716 -> ../../mmcblk0p2
5 directories, 11 files
root@raspberrypi:~# cat /boot/cmdline.txt
dwc_otg.lpm_enable=0 console=serial0,115200 console=tty1 root=PARTUUID=84fa8189-02 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait
```
Looking good, PARTUUID is intact and still matches so we shouldn't have problems booting.
### Updates
Let's go ahead and update our system, we'll then reboot to load any kernel/firmware updates and make sure everything is working. Updating could take some time, find something to do for a few minutes after it starts.
`apt-get -y dist-upgrade`
Once apt-get finishes go ahead and...
### Reboot!
Pop open a terminal window and start pinging the pi, on windows start>run and `ping -t raspberrypi` (or by ip address,) so we know when it's back up and available again.
And reboot:
`root@raspberrypi:~# reboot 0`
Once the Pi is back online login and verify that everything still works.
## Recommended: Backup your SD card now
:::info
This would be a good time to halt the machine and take an image of your SD card. We haven't done a ton of work so far but this saves time configuring a new image and downloading updates if something breaks or you want to image another SD card in the future.
`root@raspberrypi:~# reboot -H 0` and wait for the machine to shut down.
If you're using Win32DiskImager to read from the card make sure 'Read Only Allocated Partitions' is ticked to avoid reading unallocated space on the SD card and reduce image file size.
The image should be small, less than 2GB, and should only take a minute or two to create.
:::
## Machine specific configuration
Boot the Pi and make any machine specific configuration changes that you'd like now. We skipped setting a hostname earlier in case we want to use this image to setup another Pi on the same network in the future.
# Creating an Initramfs
We'll need an initramfs containing the Btrfs kernel module to mount our filesystem during boot. We'll also need to make some changes to how we're referencing our root filesystem to work around a problem booting using PARTUUIDs with an initramfs on Raspbian.
## Initramfs boot configuration
### First, backups
:::info
Before we make any changes to boot first make backups of `cmdline.txt` and `config.txt`. If anything goes wrong you can use another machine to revert your changes and debug.
:::
```
root@raspberrypi:~# cd /boot
root@raspberrypi:/boot# cp config.txt config.bak
root@raspberrypi:/boot# cp cmdline.txt cmdline.bak
```
Now let's tackle a couple of problems.
### The first problem
:::danger
**Using an initramfs with the current release of Raspbian (2017-04-10) breaks booting by PARTUUID. We'll need to reference our root filesystem by UUID in `cmdline.txt` from now on.**
:::
Let's look at `cmdline.txt`, this is passed directly to the kernel at boot and tells it where to find the root filesystem among other things. We'll break parameters out to separate lines so we can see what's going on:
`sed 's/ /\n/g' cmdline.txt | tee temp`
```=
dwc_otg.lpm_enable=0
console=serial0,115200
console=tty1
root=PARTUUID=84fa8189-02
rootfstype=ext4
elevator=deadline
fsck.repair=yes
rootwait
```
So we're booting our root ext4 filesystem using `root=PARTUUID=84fa8189-02`. This is going to break when we load the initramfs that we need for our kernel module. I'm not sure why it breaks, it shouldn't, but it does so we need to work around the problem.
Our workaround is pretty easy, we'll just change the `root=PARTUUID=` parameter in `cmdline.txt` to reference our root filesystem by UUID instead.
Here are our UUIDs and labels:
```
root@raspberrypi:~# tree /dev/disk
/dev/disk
|-- by-id
| |-- mmc-USDU1_0x41019842 -> ../../mmcblk0
| |-- mmc-USDU1_0x41019842-part1 -> ../../mmcblk0p1
| `-- mmc-USDU1_0x41019842-part2 -> ../../mmcblk0p2
|-- by-label
| `-- boot -> ../../mmcblk0p1
|-- by-partuuid
| |-- 84fa8189-01 -> ../../mmcblk0p1
| `-- 84fa8189-02 -> ../../mmcblk0p2
|-- by-path
| |-- platform-3f202000.sdhost -> ../../mmcblk0
| |-- platform-3f202000.sdhost-part1 -> ../../mmcblk0p1
| `-- platform-3f202000.sdhost-part2 -> ../../mmcblk0p2
`-- by-uuid
|-- 70CE-EB76 -> ../../mmcblk0p1
`-- f2100b2f-ed84-4647-b5ae-089280112716 -> ../../mmcblk0p2
5 directories, 11 files
```
Our PARTUUID corresponds to `/dev/mmcblk0p2` and we can see the UUID for the same partition at the bottom of the list.
We split our `cmdline.txt` into separate lines and dumped it into a file named `temp` earlier, edit this temp file to reference the partition by UUID now:
```=
dwc_otg.lpm_enable=0
console=serial0,115200
console=tty1
root=UUID=f2100b2f-ed84-4647-b5ae-089280112716
rootfstype=ext4
elevator=deadline
fsck.repair=yes
rootwait
```
Verify that your UUID is correct and then put `cmdline.txt` back together:
`paste -d" " -s temp | tee cmdline.txt`
```
dwc_otg.lpm_enable=0 console=serial0,115200 console=tty1 root=UUID=f2100b2f-ed84-4647-b5ae-089280112716 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait
```
:::warning
Verify that your `cmdline.txt` is one single line with no line breaks.
:::
Reboot to make sure everything still works. If something has gone wrong use another machine to restore your `cmdline.txt` from `cmdline.bak`, reboot and try again.
### Another problem
:::danger
Raspbian currently has no way to automatically update `/boot/config.txt` with new initrd.img file names. Every time you apply a kernel version update the install script hooks will generate a new initrd.img with a new file name.
This will break our boot sequence.
:::
This is easy enough to solve if you remember to edit `/boot/config.txt` manually every time you install a kernel update or if you can pull the SD card and edit your config.txt on another machine when your next boot fails.
It's also really easy to forget to do this, so we're going to cheat and make sure that doesn't happen by holding updates to the kernel and bootloader. You'll have to manually remove these holds before updating these packages. Make yourself a note to update `/boot/config.txt` to reference the new initrd.img file when you update your kernel or firmware.
Log back in and switch to root: `sudo -i bash`
Then flag the packages for hold:
```
root@raspberrypi:~# apt-mark hold raspberrypi-kernel raspberrypi-bootloader
raspberrypi-kernel set on hold.
raspberrypi-bootloader set on hold.
```
## Creating our initrd.img
Now that we've solved Raspbian's boot problems let's create and actually use our initrd.img.
Let's specify what we need to include, then create the image:
```
root@raspberrypi:~# echo "btrfs" >> /etc/initramfs-tools/modules
root@raspberrypi:~# cat /etc/initramfs-tools/modules
# List of modules that you want to include in your initramfs.
# They will be loaded at boot time in the order below.
#
# Syntax: module_name [args ...]
#
# You must run update-initramfs(8) to effect this change.
#
# Examples:
#
# raid1
# sd_mod
btrfs
root@raspberrypi:~# update-initramfs -c -k `uname -r`
update-initramfs: Generating /boot/initrd.img-4.9.24-v7+
```
Now let's find it and configure Raspbian to use it:
```
root@raspberrypi:~# cd /boot
root@raspberrypi:/boot# ls -b initrd*
initrd.img-4.9.24-v7+
```
To load the image at boot we'll need to edit `config.txt` to include the following line:
```=
initramfs initrd.img-4.9.24-v7+ followkernel
```
Do this now, make sure the initrd.img file name matches yours.
:::warning
_note: `ramfsfile=initrd.img-4.9.24-v7+` syntax in `config.txt` should work as well, but for some reason boot hangs when I use it._
:::
Your config.txt should now look something like this, though some of your parameters may be different:
`root@raspberrypi:/boot# cat config.txt`
```=
# For more options and information see
# http://rpf.io/configtxtreadme
# Some settings may impact device functionality. See link above for details
# uncomment if you get no picture on HDMI for a default "safe" mode
#hdmi_safe=1
# uncomment this if your display has a black border of unused pixels visible
# and your display can output without overscan
#disable_overscan=1
# uncomment the following to adjust overscan. Use positive numbers if console
# goes off screen, and negative if there is too much border
#overscan_left=16
#overscan_right=16
#overscan_top=16
#overscan_bottom=16
# uncomment to force a console size. By default it will be display's size minus
# overscan.
#framebuffer_width=1280
#framebuffer_height=720
# uncomment if hdmi display is not detected and composite is being output
#hdmi_force_hotplug=1
# uncomment to force a specific HDMI mode (this will force VGA)
#hdmi_group=1
#hdmi_mode=1
# uncomment to force a HDMI mode rather than DVI. This can make audio work in
# DMT (computer monitor) modes
#hdmi_drive=2
# uncomment to increase signal to HDMI, if you have interference, blanking, or
# no display
#config_hdmi_boost=4
# uncomment for composite PAL
#sdtv_mode=2
#uncomment to overclock the arm. 700 MHz is the default.
#arm_freq=800
# Uncomment some or all of these to enable the optional hardware interfaces
#dtparam=i2c_arm=on
#dtparam=i2s=on
#dtparam=spi=on
# Uncomment this to enable the lirc-rpi module
#dtoverlay=lirc-rpi
# Additional overlays and parameters are documented /boot/overlays/README
# Enable audio (loads snd_bcm2835)
dtparam=audio=on
gpu_mem=16
initramfs initrd.img-4.9.24-v7+ followkernel
```
Now reboot and make sure everything still works. If boot hangs unplug the pi and use another machine to revert your `config.txt` and try again.
Once the Pi is back online login and verify that the btrfs module is loading at boot:
```
root@raspberrypi:~# dmesg | grep -i btrfs
[ 5.344837] Btrfs loaded, crc32c=crc32c-generic
```
If `dmesg` doesn't show the module loading something is going wrong. Go back to the beginning of this section, recreate your initramfs and verify that you've followed every step.
## Recap
At this point you should be successfully loading an initramfs containing the Btrfs module during boot and booting from your ext4 partition using `root=UUID= ` in `/boot/cmdline.txt`.
Take off your fireman's hat, I think we're done putting out boot sequence fires for now.
# Btrfs Preparation
Boot the Pi if it isn't already and log in, then switch to root:
`sudo -i bash`
## Disable swap
We need to shut off swap before we go any further, locating a swap file on a copy on write filesystem is a bad idea.
```
root@raspberrypi:~# dphys-swapfile swapoff
root@raspberrypi:~# dphys-swapfile uninstall
root@raspberrypi:~# systemctl stop dphys-swapfile
root@raspberrypi:~# systemctl disable dphys-swapfile
```
## Install Btrfs-tools
```
root@raspberrypi:~# apt-get install -y btrfs-tools
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following extra packages will be installed:
liblzo2-2
The following NEW packages will be installed:
btrfs-tools liblzo2-2
0 upgraded, 2 newly installed, 0 to remove and 0 not upgraded.
Need to get 462 kB of archives.
After this operation, 3,521 kB of additional disk space will be used.
...
Setting up liblzo2-2:armhf (2.08-1.2) ...
Setting up btrfs-tools (3.17-1.1) ...
update-initramfs: deferring update (trigger activated)
Processing triggers for libc-bin (2.19-18+deb8u9) ...
Processing triggers for initramfs-tools (0.120+deb8u3) ...
root@raspberrypi:~# btrfs --version
Btrfs v3.17
```
### A note on versions
You might have noticed that our Btrfs tools are [about two and a half years out of date](https://btrfs.wiki.kernel.org/index.php/Changelog#btrfs-progs_3.17_.28Oct_2014.29). This is bad; Btrfs development is moving fast and there are a huge number of fixes and feature improvements that we'd like to take advantage of. We're not going to do anything about this yet as grabbing new versions requires us to make changes to our APT configuration which could have unintended consequences.
We'll solve this later and in the process demonstrate how Btrfs filesystem snapshots can be used to to save state before making important system configuration changes.
## Creating the filesystem
Let's go ahead and create our new Btrfs filesystem.
```
root@raspberrypi:~# fdisk -l /dev/mmcblk0
Disk /dev/mmcblk0: 15 GiB, 16088301568 bytes, 31422464 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: 0x84fa8189
Device Boot Start End Sectors Size Id Type
/dev/mmcblk0p1 8192 92159 83968 41M c W95 FAT32 (LBA)
/dev/mmcblk0p2 92160 3213311 3121152 1.5G 83 Linux
root@raspberrypi:~#
```
We'll create our new partition on 1MB boundaries (which conveniently start 1 sector after the end of the prior partition) and just assume that it's aligned "well enough" with the page and block sizes of the nand.
```
root@raspberrypi:~# fdisk /dev/mmcblk0
Welcome to fdisk (util-linux 2.25.2).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.
Command (m for help): p
Disk /dev/mmcblk0: 15 GiB, 16088301568 bytes, 31422464 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: 0x84fa8189
Device Boot Start End Sectors Size Id Type
/dev/mmcblk0p1 8192 92159 83968 41M c W95 FAT32 (LBA)
/dev/mmcblk0p2 92160 3213311 3121152 1.5G 83 Linux
Command (m for help): n
Partition type
p primary (2 primary, 0 extended, 2 free)
e extended (container for logical partitions)
Select (default p):
Using default response p.
Partition number (3,4, default 3):
First sector (2048-31422463, default 2048): 3213312
Last sector, +sectors or +size{K,M,G,T,P} (3213312-31422463, default 31422463): +13G
Created a new partition 3 of type 'Linux' and of size 13 GiB.
Command (m for help): p
Disk /dev/mmcblk0: 15 GiB, 16088301568 bytes, 31422464 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: 0x84fa8189
Device Boot Start End Sectors Size Id Type
/dev/mmcblk0p1 8192 92159 83968 41M c W95 FAT32 (LBA)
/dev/mmcblk0p2 92160 3213311 3121152 1.5G 83 Linux
/dev/mmcblk0p3 3213312 30476287 27262976 13G 83 Linux
Command (m for help): w
The partition table has been altered.
Calling ioctl() to re-read partition table.
Re-reading the partition table failed.: Device or resource busy
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).
```
```
root@raspberrypi:~# partprobe /dev/mmcblk0
root@raspberrypi:~# tree /dev/disk
/dev/disk
├── by-id
│ ├── mmc-USDU1_0x41019842 -> ../../mmcblk0
│ ├── mmc-USDU1_0x41019842-part1 -> ../../mmcblk0p1
│ ├── mmc-USDU1_0x41019842-part2 -> ../../mmcblk0p2
│ └── mmc-USDU1_0x41019842-part3 -> ../../mmcblk0p3
├── by-label
│ └── boot -> ../../mmcblk0p1
├── by-partuuid
│ ├── 84fa8189-01 -> ../../mmcblk0p1
│ ├── 84fa8189-02 -> ../../mmcblk0p2
│ └── 84fa8189-03 -> ../../mmcblk0p3
├── by-path
│ ├── platform-3f202000.sdhost -> ../../mmcblk0
│ ├── platform-3f202000.sdhost-part1 -> ../../mmcblk0p1
│ ├── platform-3f202000.sdhost-part2 -> ../../mmcblk0p2
│ └── platform-3f202000.sdhost-part3 -> ../../mmcblk0p3
└── by-uuid
├── 70CE-EB76 -> ../../mmcblk0p1
└── f2100b2f-ed84-4647-b5ae-089280112716 -> ../../mmcblk0p2
5 directories, 14 files
```
Jackpot, time to make our filesystem.
The default node/leaf size (16KB) for btrfs should be fine for most devices, so we'll go with the defaults.
We'll also assign a label to our filesystem to make it easier to mount.
```
root@raspberrypi:~# mkfs.btrfs -L sd_btrfs /dev/mmcblk0p3
Detected a SSD, turning off metadata duplication. Mkfs with -m dup if you want to force metadata duplication.
Btrfs v3.17
See http://btrfs.wiki.kernel.org for more information.
Performing full device TRIM (13.00GiB) ...
Turning ON incompat feature 'extref': increased hardlink limit per file to 65536
fs created label sd_btrfs on /dev/mmcblk0p3
nodesize 16384 leafsize 16384 sectorsize 4096 size 13.00GiB
```
#### Updating fstab
Let's add our new partition to `/etc/fstab` so we can mount it without typing our mount options every time.
The btrfs mount option 'ssd_spread' is intended to be used with lower quality SSDs whose controllers perform better with large extents of free space, I think the naive controller in our SD card qualifies as "low quality" so we'll go with ssd_spread as an option. We'll also use noatime to avoid excessive access time metadata updates.
```
root@raspberrypi:~# mkdir -p /mnt/btrfs
root@raspberrypi:~# echo "LABEL=sd_btrfs /mnt/btrfs btrfs defaults,ssd_spread,noatime,noauto 0 0" >> /etc/fstab
root@raspberrypi:~# mount /mnt/btrfs
```
Let's have a look at what we've done:
```
root@raspberrypi:~# df -h
Filesystem Size Used Avail Use% Mounted on
/dev/root 1.5G 936M 449M 68% /
devtmpfs 481M 0 481M 0% /dev
tmpfs 486M 0 486M 0% /dev/shm
tmpfs 486M 6.5M 480M 2% /run
tmpfs 5.0M 0 5.0M 0% /run/lock
tmpfs 486M 0 486M 0% /sys/fs/cgroup
/dev/mmcblk0p1 41M 21M 20M 52% /boot
/dev/mmcblk0p3 13G 17M 13G 1% /mnt/btrfs
root@raspberrypi:~# column -t /etc/fstab
proc /proc proc defaults 0 0
PARTUUID=84fa8189-01 /boot vfat defaults 0 2
PARTUUID=84fa8189-02 / ext4 defaults,noatime 0 1
LABEL=sd_btrfs /mnt/btrfs btrfs defaults,ssd_spread,noatime,noauto 0 0
root@raspberrypi:~#
```
Perfect.
:::info
Note:
You can also assign a label to your ext4 partition using `e2label /dev/mmcblk0p2 <label here>` if you like. I've set my ext4 partition to `sd_ext4`.
:::
## Cloning / onto our new Btrfs filesystem
### Creating a subvolume to hold our root filesystem
We're going to be a little bit clever and use a subvolume to make snapshot management easier in the future.
First, let's create an empty subvolume on our Btrfs filesystem and make a directory to hold future snapshots:
```
root@raspberrypi:~# btrfs subv create /mnt/btrfs/\@root
Create subvolume '/mnt/btrfs/@root'
root@raspberrypi:~# mkdir -p /mnt/btrfs/snapshots
root@raspberrypi:~# tree /mnt/btrfs/
/mnt/btrfs/
├── @root
└── snapshots
2 directories, 0 files
```
There's our subvolume. Subvolumes are automatically mounted by their parent volume so we've isolated our root into it's own subvolume which we can mount directly without exposing the rest of the filesystem tree. This way any snapshots we take in the future won't be visible unless we mount the entire Btrfs filesystem tree explicitly.
This isn't entirely necessary but it keeps things partitioned nicely and makes sure that our snapshots aren't vulnerable to accidental changes.
### Cloning the filesystem
Now let's clone the filesystem, this shouldn't take too long on a fresh install with around ~1GB data.
`root@raspberrypi:~# rsync -avxHW / /mnt/btrfs/\@root/`
```
...
var/spool/
var/spool/mail -> ../mail
var/spool/cron/
var/spool/cron/crontabs/
var/spool/rsyslog/
var/tmp/
sent 896,499,356 bytes received 601,810 bytes 6,208,312.57 bytes/sec
total size is 902,927,704 speedup is 1.01
```
## Preparing to boot our new filesystem
### Updating our target fstab
We need to update `/mnt/btrfs/@root/etc/fstab` to mount things properly when we boot from our new filesystem.
Let's have another look at our UUIDs and labels:
```
root@raspberrypi:~# tree /dev/disk
/dev/disk
├── by-id
│ ├── mmc-USDU1_0x41019842 -> ../../mmcblk0
│ ├── mmc-USDU1_0x41019842-part1 -> ../../mmcblk0p1
│ ├── mmc-USDU1_0x41019842-part2 -> ../../mmcblk0p2
│ └── mmc-USDU1_0x41019842-part3 -> ../../mmcblk0p3
├── by-label
│ ├── boot -> ../../mmcblk0p1
│ └── sd_btrfs -> ../../mmcblk0p3
├── by-partuuid
│ ├── 84fa8189-01 -> ../../mmcblk0p1
│ ├── 84fa8189-02 -> ../../mmcblk0p2
│ └── 84fa8189-03 -> ../../mmcblk0p3
├── by-path
│ ├── platform-3f202000.sdhost -> ../../mmcblk0
│ ├── platform-3f202000.sdhost-part1 -> ../../mmcblk0p1
│ ├── platform-3f202000.sdhost-part2 -> ../../mmcblk0p2
│ └── platform-3f202000.sdhost-part3 -> ../../mmcblk0p3
└── by-uuid
├── 70CE-EB76 -> ../../mmcblk0p1
├── f2100b2f-ed84-4647-b5ae-089280112716 -> ../../mmcblk0p2
└── f4f24622-b848-494b-b89e-853bd9a0c37c -> ../../mmcblk0p3
5 directories, 16 files
```
Here's our current fstab on our ext4 filesystem:
`root@raspberrypi:~# column -t /etc/fstab`
```
proc /proc proc defaults 0 0
PARTUUID=84fa8189-01 /boot vfat defaults 0 2
PARTUUID=84fa8189-02 / ext4 defaults,noatime 0 1
LABEL=sd_btrfs /mnt/btrfs btrfs defaults,ssd_spread,noatime,noauto 0 0
```
And here's what we want in our new fstab:
`root@raspberrypi:~# column -t /mnt/btrfs/\@root/etc/fstab`
```
proc /proc proc defaults 0 0
PARTUUID=84fa8189-01 /boot vfat defaults 0 2
PARTUUID=84fa8189-02 /mnt/ext4_root ext4 defaults,noatime,noauto 0 2
LABEL=sd_btrfs /mnt/btrfs btrfs defaults,ssd_spread,noatime,noauto 0 0
LABEL=sd_btrfs / btrfs defaults,ssd_spread,noatime,subvol=@root 0 0
```
Go ahead and edit yours to match. Pay special attention to the fsck flags at the end (the last column of numbers) as we do /not/ want anything trying to btrfs.fsck our filesystem in an emergency. Make sure your PARTUUIDs correspond to the PARTUUIDs on your system.
_Note: `/mnt/ext4_root` is included purely for convenience and `/mnt/btrfs` will be useful when we start managing snapshots._
Don't forget to create the mount point for our ext4 partition:
`mkdir -p /mnt/btrfs/\@root/mnt/ext4_root`
### Updating `/boot/cmdline.txt`
First let's make a copy of `/boot/cmdline.txt` so it's easy to boot from the ext4 partition in the future:
`cd /boot && cp cmdline.txt cmdline.ext4`
Let's look at our UUIDs again:
```
root@raspberrypi:/boot# tree /dev/disk
/dev/disk
├── by-id
│ ├── mmc-USDU1_0x41019842 -> ../../mmcblk0
│ ├── mmc-USDU1_0x41019842-part1 -> ../../mmcblk0p1
│ ├── mmc-USDU1_0x41019842-part2 -> ../../mmcblk0p2
│ └── mmc-USDU1_0x41019842-part3 -> ../../mmcblk0p3
├── by-label
│ ├── boot -> ../../mmcblk0p1
│ └── sd_btrfs -> ../../mmcblk0p3
├── by-partuuid
│ ├── 84fa8189-01 -> ../../mmcblk0p1
│ ├── 84fa8189-02 -> ../../mmcblk0p2
│ └── 84fa8189-03 -> ../../mmcblk0p3
├── by-path
│ ├── platform-3f202000.sdhost -> ../../mmcblk0
│ ├── platform-3f202000.sdhost-part1 -> ../../mmcblk0p1
│ ├── platform-3f202000.sdhost-part2 -> ../../mmcblk0p2
│ └── platform-3f202000.sdhost-part3 -> ../../mmcblk0p3
└── by-uuid
├── 70CE-EB76 -> ../../mmcblk0p1
├── f2100b2f-ed84-4647-b5ae-089280112716 -> ../../mmcblk0p2
└── f4f24622-b848-494b-b89e-853bd9a0c37c -> ../../mmcblk0p3
5 directories, 16 files
```
Take a look at the `by-uuid` branch at the bottom:
`/dev/mmcblk0p2` is the partition holding our ext4 filesystem
`/dev/mmcblk0p3` is the partition holding our new Btrfs filesystem
Find the UUID that points to your Btrfs partition.
Now let's edit `cmdline.txt` to boot from our Btrfs partition and root subvolume. We need to change our `root=UUID=` parameter to point to our Btrfs partition, `rootfstype=ext4` to `rootfstype=btrfs`, add `rootflags=subvol=@root` and remove `fsck.repair=yes`.
Either edit the `temp` file we created earlier and put it back together with `paste -d" " -s temp > cmdline.txt` or edit your `cmdline.txt` directly.
:::warning
Make sure your final `cmdline.txt` is one single line with no line breaks.
:::
Your new `cmdline.txt` should look something like this and should use your Btrfs partition's UUID:
`root@raspberrypi:/boot# cat cmdline.txt`
```
dwc_otg.lpm_enable=0 console=serial0,115200 console=tty1 root=UUID=f4f24622-b848-494b-b89e-853bd9a0c37c rootfstype=btrfs rootflags=subvol=@root elevator=deadline rootwait
```
Here it is broken out to show our changes.
`root@raspberrypi:/boot# sed 's/ /\n/g' cmdline.txt`
```=
dwc_otg.lpm_enable=0
console=serial0,115200
console=tty1
root=UUID=f4f24622-b848-494b-b89e-853bd9a0c37c
rootfstype=btrfs
rootflags=subvol=@root
elevator=deadline
rootwait
```
### Reboot
Everything should be in place, go ahead and reboot: `reboot 0`
If something goes wrong halt the pi, pull the SD card and copy `cmdline.ext4` over `cmdline.txt` and try again.
Log back in and switch to root: `sudo -i bash`
Let's verify that everything is mounted where it should be:
```
root@raspberrypi:~# lsblk -f
NAME FSTYPE LABEL UUID MOUNTPOINT
mmcblk0
├─mmcblk0p2 ext4 f2100b2f-ed84-4647-b5ae-089280112716
├─mmcblk0p3 btrfs sd_btrfs f4f24622-b848-494b-b89e-853bd9a0c37c /
└─mmcblk0p1 vfat boot 70CE-EB76 /boot
```
Success!
Let's make a copy of our `cmdline.txt` so we don't lose our changes if we ever have to boot from our ext4 partition in the future:
`cd /boot && cp cmdline.txt cmdline.btrfs`
```
root@raspberrypi:/boot# ls cmdline*
cmdline.bak cmdline.btrfs cmdline.ext4 cmdline.txt
```
Now we've got backup copies we can restore in case we need to boot from the ext4 partition without losing our current configuration.
# Your new Btrfs filesystem
OK. We've setup a bootable system using Btrfs. Let's start doing things with it.
We'll take advantage of our old version of Btrfs-tools and use updating the package from Raspbian unstable as an opportunity to demonstrate protecting ourselves with snapshots. We'll configure APT to pull some packages from the unstable branch of Raspbian (stretch) while using a snapshot to ensure that even if we misconfigure APT and something goes horribly wrong we can simply change our boot target and roll back.
Snapshots make potentially dangerous changes like this ridiculously convenient to explore. We don't have to worry about breaking our install or manually undoing any of a dozen things we forgot to document while testing. As long as we don't crash the machine or destroy our entire filesystem we can simply rebase our root filesystem target to a known good snapshot and reboot if things go wrong.
First, a quick look at the state of our system:
```
root@raspberrypi:~# lsblk -f
NAME FSTYPE LABEL UUID MOUNTPOINT
mmcblk0
├─mmcblk0p2 ext4 f2100b2f-ed84-4647-b5ae-089280112716
├─mmcblk0p3 btrfs sd_btrfs f4f24622-b848-494b-b89e-853bd9a0c37c /
└─mmcblk0p1 vfat boot 70CE-EB76 /boot
root@raspberrypi:~# btrfs fi show /
Label: 'sd_btrfs' uuid: f4f24622-b848-494b-b89e-853bd9a0c37c
Total devices 1 FS bytes used 913.56MiB
devid 1 size 13.00GiB used 1.30GiB path /dev/mmcblk0p3
Btrfs v3.17
root@raspberrypi:~# btrfs subv show /
/
Name: @root
uuid: 4cdb3079-d1bf-7b48-96f9-ae21e6338715
Parent uuid: -
Creation time: 2017-06-03 20:21:55
Object ID: 258
Generation (Gen): 168
Gen at creation: 12
Parent: 5
Top Level: 5
Flags: -
Snapshot(s):
root@raspberrypi:~#
```
## Our first snapshot
Log back in and switch to root: `sudo -i bash`
Mount the full Btrfs filesystem that we added to `/etc/fstab` earlier and let's take a snapshot of the current system state.
```
root@raspberrypi:~# mount /mnt/btrfs
root@raspberrypi:~# btrfs subv snapshot / /mnt/btrfs/snapshots/\@root-pre-APT-changes
Create a snapshot of '/' in '/mnt/btrfs/snapshots/@root-pre-APT-changes'
```
And here's our snapshot:
```
root@raspberrypi:~# btrfs subv show /
/
Name: @root
uuid: 4cdb3079-d1bf-7b48-96f9-ae21e6338715
Parent uuid: -
Creation time: 2017-06-03 20:21:55
Object ID: 258
Generation (Gen): 169
Gen at creation: 12
Parent: 5
Top Level: 5
Flags: -
Snapshot(s):
snapshots/@root-pre-APT-changes
root@raspberrypi:~# tree /mnt/btrfs/snapshots/ -L 2
/mnt/btrfs/snapshots/
└── @root-pre-APT-changes
├── bin
├── boot
├── dev
├── etc
├── home
├── lib
├── lost+found
├── media
├── mnt
├── opt
├── proc
├── root
├── run
├── sbin
├── srv
├── sys
├── tmp
├── usr
└── var
20 directories, 0 files
```
There we are, a safe point to restore from if we break anything.
## Updating `btrfs-tools`
Now we're ready to configure APT to search the stretch repository for the packages we want.
Here's our current APT configuration:
```
root@raspberrypi:~# cd /etc/apt
root@raspberrypi:/etc/apt# tree
.
├── apt.conf.d
│ ├── 01autoremove
│ ├── 01autoremove-kernels
│ ├── 20listchanges
│ ├── 50raspi
│ └── 70debconf
├── listchanges.conf
├── preferences.d
├── sources.list
├── sources.list.d
│ └── raspi.list
├── trusted.gpg
└── trusted.gpg.d
4 directories, 9 files
```
We're going to add the stretch repo to `sources.list` and add a couple new files to the `preferences.d/` subdirectory to tell APT how to handle packages from Raspbian unstable.
Here are our changed files:
`root@raspberrypi:/etc/apt# cat sources.list`
```=
deb http://mirrordirector.raspbian.org/raspbian/ jessie main contrib non-free rpi
deb http://mirrordirector.raspbian.org/raspbian/ stretch main contrib non-free rpi
# Uncomment line below then 'apt-get update' to enable 'apt-get source'
#deb-src http://archive.raspbian.org/raspbian/ jessie main contrib non-free rpi
```
`root@raspberrypi:/etc/apt# cat preferences.d/stretch-btrfs-tools.pref`
```=
Package: btrfs*
Pin: release o=raspbian, n=stretch
Pin-Priority: 999
```
`root@raspberrypi:/etc/apt# cat preferences.d/stretch-low-priority.pref`
```=
Package: *
Pin: release o=raspbian, n=stretch
Pin-Priority: 400
```
We've told APT to use the stretch repo as well as jessie but we've set a priority for all stretch packages that's lower than the default of 500 for our current release. We've also specified a very high priority for Btrfs related packages from stretch. This will allow `apt-get` to continue tracking packages from our current release and also track updates to the Btrfs packages from stretch as they're updated. This shouldn't get us into any trouble but if it does we'll just revert to our prior snapshot.
And here's our new APT configuration:
```
root@raspberrypi:/etc/apt# tree
.
├── apt.conf.d
│ ├── 01autoremove
│ ├── 01autoremove-kernels
│ ├── 20listchanges
│ ├── 50raspi
│ └── 70debconf
├── listchanges.conf
├── preferences.d
│ ├── stretch-btrfs-tools.pref
│ └── stretch-low-priority.pref
├── sources.list
├── sources.list.d
│ └── raspi.list
├── trusted.gpg
└── trusted.gpg.d
4 directories, 11 files
```
Let's see what happens:
```
root@raspberrypi:/etc/apt# apt-get update
Hit http://archive.raspberrypi.org jessie InRelease
Get:1 http://mirrordirector.raspbian.org jessie InRelease [14.9 kB]
Get:2 http://mirrordirector.raspbian.org stretch InRelease [15.0 kB]
...
Fetched 21.5 MB in 37s (579 kB/s)
Reading package lists... Done
root@raspberrypi:/etc/apt# apt-cache policy libc6
libc6:
Installed: 2.19-18+deb8u9
Candidate: 2.19-18+deb8u9
Version table:
2.24-11 0
400 http://mirrordirector.raspbian.org/raspbian/ stretch/main armhf Packages
*** 2.19-18+deb8u9 0
500 http://mirrordirector.raspbian.org/raspbian/ jessie/main armhf Packages
100 /var/lib/dpkg/status
root@raspberrypi:/etc/apt# apt-cache policy btrfs-tools
btrfs-tools:
Installed: 3.17-1.1
Candidate: 4.7.3-1
Package pin: 4.7.3-1
Version table:
4.7.3-1 999
400 http://mirrordirector.raspbian.org/raspbian/ stretch/main armhf Packages
*** 3.17-1.1 999
500 http://mirrordirector.raspbian.org/raspbian/ jessie/main armhf Packages
100 /var/lib/dpkg/status
```
Perfect, asking APT about an important package (libc6) shows that we're still tracking jessie but `btrfs-tools` has an update candidate from stretch. Let's upgrade `btrfs-tools`.
```
root@raspberrypi:/etc/apt# apt-get upgrade btrfs-tools
Reading package lists... Done
Building dependency tree
Reading state information... Done
Calculating upgrade... Done
The following NEW packages will be installed:
btrfs-progs
The following packages will be upgraded:
btrfs-tools
1 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.
Need to get 584 kB of archives.
After this operation, 1,297 kB of additional disk space will be used.
Do you want to continue? [Y/n]
```
It looks like `btrfs-tools` has a new dependency in stretch, so we'll go ahead and install it.
```
Do you want to continue? [Y/n]
Get:1 http://mirrordirector.raspbian.org/raspbian/ stretch/main btrfs-tools armhf 4.7.3-1 [16.9 kB]
Get:2 http://mirrordirector.raspbian.org/raspbian/ stretch/main btrfs-progs armhf 4.7.3-1 [567 kB]
Fetched 584 kB in 1s (561 kB/s)
Reading changelogs... Done
(Reading database ... 33550 files and directories currently installed.)
Preparing to unpack .../btrfs-tools_4.7.3-1_armhf.deb ...
Unpacking btrfs-tools (4.7.3-1) over (3.17-1.1) ...
Selecting previously unselected package btrfs-progs.
Preparing to unpack .../btrfs-progs_4.7.3-1_armhf.deb ...
Unpacking btrfs-progs (4.7.3-1) ...
Processing triggers for man-db (2.7.0.2-5) ...
Processing triggers for initramfs-tools (0.120+deb8u3) ...
update-initramfs: /boot/initrd.img-4.9.24-v7+ has been altered.
update-initramfs: Cannot update. Override with -t option.
Processing triggers for libc-bin (2.19-18+deb8u9) ...
Setting up btrfs-progs (4.7.3-1) ...
Processing triggers for initramfs-tools (0.120+deb8u3) ...
update-initramfs: /boot/initrd.img-4.9.24-v7+ has been altered.
update-initramfs: Cannot update. Override with -t option.
Setting up btrfs-tools (4.7.3-1) ...
Processing triggers for libc-bin (2.19-18+deb8u9) ...
root@raspberrypi:/etc/apt# btrfs --version
bash: /sbin/btrfs: No such file or directory
```
Oops, looks like file paths have changed. Let's tell bash to rescan executable paths.
```
root@raspberrypi:/etc/apt# hash -r
root@raspberrypi:/etc/apt# btrfs --version
btrfs-progs v4.7.3
```
And there we are.
Let's run `apt-get upgrade` again and make sure APT isn't trying to install a pile of packages from stretch by accident.
```
root@raspberrypi:~# apt-get upgrade
Reading package lists... Done
Building dependency tree
Reading state information... Done
Calculating upgrade... Done
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
```
Perfect.
You can check to see which version of a package APT is going to install at any time using `apt-cache policy <<package>>` the same way we did earlier for `libc6`.
## Managing snapshots
Let's take a look at working with snapshots.
First we'll take a snapshot of our current state:
```
root@raspberrypi:~# btrfs subv snapshot / /mnt/btrfs/snapshots/\@root-`date +'%F-%T'`
Create a snapshot of '/' in '/mnt/btrfs/snapshots/@root-2017-06-05-13:41:17'
root@raspberrypi:~# btrfs subv list /
ID 258 gen 541 top level 5 path @root
ID 264 gen 426 top level 5 path snapshots/@root-pre-APT-changes
ID 268 gen 541 top level 5 path snapshots/@root-2017-06-05-13:41:17
```
Now we have snapshots before, and after, making our changes to APT and updating our Btrfs tools.
### Rolling back to a prior snapshot
Let's pretend for a minute that something went horribly wrong, or maybe you just don't like the idea of mixing packages on your system. Rather than undoing all of our changes we'll rebase our boot mount target and reboot.
:::warning
The following is a demonstration, you don't need to actually do this. There's really no risk though as we just took a snapshot of our system with our changes intact.
:::
```
root@raspberrypi:~# cd /mnt/btrfs/
root@raspberrypi:/mnt/btrfs# tree -L 2
.
├── @root
│ ├── bin
│ ├── boot
│ ├── dev
│ ├── etc
│ ├── home
│ ├── lib
│ ├── lost+found
│ ├── media
│ ├── mnt
│ ├── opt
│ ├── proc
│ ├── root
│ ├── run
│ ├── sbin
│ ├── srv
│ ├── sys
│ ├── tmp
│ ├── usr
│ └── var
└── snapshots
├── @root-2017-06-05-13:41:17
└── @root-pre-APT-changes
23 directories, 0 files
```
Btrfs snapshots are treated like any other subvolume and are available in the full filesystem tree so this is easy. We can either edit our `/etc/fstab` and `/boot/cmdline.txt` to point our root filesystem directly at a snapshot by setting `subvol=/snapshots/@root-pre-APT-changes` or we can be lazy and just move `@root` and replace it. I'm going to be lazy.
```
root@raspberrypi:/mnt/btrfs# mv \@root/ \@root-bad
root@raspberrypi:/mnt/btrfs# mv snapshots/\@root-pre-APT-changes/ \@root
root@raspberrypi:/mnt/btrfs# tree -L 2
.
├── @root
│ ├── bin
│ ├── boot
│ ├── dev
│ ├── etc
│ ├── home
│ ├── lib
│ ├── lost+found
│ ├── media
│ ├── mnt
│ ├── opt
│ ├── proc
│ ├── root
│ ├── run
│ ├── sbin
│ ├── srv
│ ├── sys
│ ├── tmp
│ ├── usr
│ └── var
├── @root-bad
│ ├── bin
│ ├── boot
│ ├── dev
│ ├── etc
│ ├── home
│ ├── lib
│ ├── lost+found
│ ├── media
│ ├── mnt
│ ├── opt
│ ├── proc
│ ├── root
│ ├── run
│ ├── sbin
│ ├── srv
│ ├── sys
│ ├── tmp
│ ├── usr
│ └── var
└── snapshots
└── @root-2017-06-05-13:41:17
42 directories, 0 files
```
And let's reboot: `reboot 0`
Login again and switch to root: `sudo -i bash`
And let's verify that we've rolled back.
```
root@raspberrypi:~# tree /etc/apt
/etc/apt
├── apt.conf.d
│ ├── 01autoremove
│ ├── 01autoremove-kernels
│ ├── 20listchanges
│ ├── 50raspi
│ └── 70debconf
├── listchanges.conf
├── preferences.d
├── sources.list
├── sources.list.d
│ └── raspi.list
├── trusted.gpg
└── trusted.gpg.d
4 directories, 9 files
root@raspberrypi:~# btrfs --version
Btrfs v3.17
```
Magical.
OK, I've changed my mind and I _do_ want to keep those changes.
```
root@raspberrypi:~# mount /mnt/btrfs && cd /mnt/btrfs/
root@raspberrypi:/mnt/btrfs# mv \@root snapshots/\@root-pre-APT-changes
root@raspberrypi:/mnt/btrfs# mv \@root-bad/ \@root
root@raspberrypi:/mnt/btrfs# tree -L 2
.
├── @root
│ ├── bin
│ ├── boot
│ ├── dev
│ ├── etc
│ ├── home
│ ├── lib
│ ├── lost+found
│ ├── media
│ ├── mnt
│ ├── opt
│ ├── proc
│ ├── root
│ ├── run
│ ├── sbin
│ ├── srv
│ ├── sys
│ ├── tmp
│ ├── usr
│ └── var
└── snapshots
├── @root-2017-06-05-13:41:17
└── @root-pre-APT-changes
23 directories, 0 files
root@raspberrypi:/mnt/btrfs#
```
`reboot 0` and log in then switch to root: `sudo -i bash`
```
root@raspberrypi:~# btrfs --version
btrfs-progs v4.7.3
root@raspberrypi:~# tree /etc/apt
/etc/apt
├── apt.conf.d
│ ├── 01autoremove
│ ├── 01autoremove-kernels
│ ├── 20listchanges
│ ├── 50raspi
│ └── 70debconf
├── listchanges.conf
├── preferences.d
│ ├── stretch-btrfs-tools.pref
│ └── stretch-low-priority.pref
├── sources.list
├── sources.list.d
│ └── raspi.list
├── trusted.gpg
└── trusted.gpg.d
4 directories, 11 files
```
And there we are, back up and running with our updated Btrfs tools and our changes to APT intact.
### Deleting snapshots
Let's make another snapshot and then delete it:
```
root@raspberrypi:~# mount /mnt/btrfs
root@raspberrypi:~# btrfs subv snapshot / /mnt/btrfs/temp
Create a snapshot of '/' in '/mnt/btrfs/temp'
root@raspberrypi:~# btrfs subv list /
ID 258 gen 555 top level 5 path @root
ID 264 gen 550 top level 5 path snapshots/@root-pre-APT-changes
ID 268 gen 541 top level 5 path snapshots/@root-2017-06-05-13:41:17
ID 269 gen 555 top level 5 path temp
root@raspberrypi:~# btrfs subv delete /mnt/btrfs/temp/
Delete subvolume (no-commit): '/mnt/btrfs/temp'
root@raspberrypi:~# btrfs subv list /
ID 258 gen 555 top level 5 path @root
ID 264 gen 550 top level 5 path snapshots/@root-pre-APT-changes
ID 268 gen 541 top level 5 path snapshots/@root-2017-06-05-13:41:17
```
Easy.
### Disk usage
Snapshots are cheap, the storage cost is only the difference between the current state of the filesystem and the state at the time of snapshot creation.
Let's look at our disk usage compared to our prior ext4 filesystem:
```
root@raspberrypi:~# btrfs fi df /
Data, single: total=2.01GiB, used=1.09GiB
System, single: total=32.00MiB, used=16.00KiB
Metadata, single: total=264.00MiB, used=40.12MiB
GlobalReserve, single: total=16.00MiB, used=0.00B
root@raspberrypi:~# mount /mnt/ext4_root/ && df -h /mnt/ext4_root/
Filesystem Size Used Avail Use% Mounted on
/dev/mmcblk0p2 1.5G 941M 444M 68% /mnt/ext4_root
```
So we're using a bit more space than we were with ext4, not much though and the bulk of that is probably stuff APT has cached since we started making changes. Keep in mind also that we're keeping two snapshots of the past state of our filesystem.
## A Note on snapshots and backups
Now that we've demonstrated the convenience of snapshots I need to stress this point:
:::danger
<center><b>
Snapshots are not backups.
</b></center>
:::
Snapshots are nothing more than a feature of the Btrfs filesystem. If the filesystem becomes damaged there's no guarantee that your snapshots will be intact or even available. If you need backups, keep full backups on other media.
# Backups: Full and Differential
First a note: `btrfs send` can only operate on read-only subvolumes so we'll need to make snapshots for this that are flagged read-only or we'll need to set the read-only flag on an existing snapshot.
Let's look at the state of our filesystem:
```
root@raspberrypi:/mnt/btrfs# btrfs sub show /
/
Name: @root
UUID: 4cdb3079-d1bf-7b48-96f9-ae21e6338715
Parent UUID: -
Received UUID: -
Creation time: 2017-06-03 20:21:55 -0700
Subvolume ID: 258
Generation: 839
Gen at creation: 12
Parent ID: 5
Top level ID: 5
Flags: -
Snapshot(s):
snapshots/@root-pre-APT-changes
snapshots/@root-2017-06-05-13:41:17
```
We have two existing snapshots. The first we took before making changes to APT, the second after. Unfortunately in a prior example I mounted the pre-APT-changes snapshot and booted from it, so there are some file changes in that snapshot that differ from our current root subvolume. We'll ignore that snapshot and work with the later, post-APT-change, snapshot.
First we'll need to set our snapshot RO:
```
root@raspberrypi:/mnt/btrfs# btrfs prop set -ts snapshots/\@root-2017-06-05-13\:41\:17/ ro true
```
Now we'll take another, read-only, snapshot of our current state:
```
root@raspberrypi:/mnt/btrfs# btrfs sub snap -r / snapshots/\@root-backup-example
Create a readonly snapshot of '/' in 'snapshots/@root-backup-example'
```
For this demonstration I've plugged in a 16GB SSD with an empty ext4 filesystem and mounted it at `/mnt/ssd_ext4`:
```
root@raspberrypi:/mnt/btrfs# lsblk -f
NAME FSTYPE LABEL UUID MOUNTPOINT
sda
└─sda1 ext4 ssd_ext4 94170e21-7a6d-4e90-b83d-70a2a4b46bc8 /mnt/ssd_ext4
mmcblk0
├─mmcblk0p2 ext4 sd_ext4 f2100b2f-ed84-4647-b5ae-089280112716
├─mmcblk0p3 btrfs sd_btrfs f4f24622-b848-494b-b89e-853bd9a0c37c /mnt/btrfs
└─mmcblk0p1 vfat boot 70CE-EB76 /boot
```
Let's send a full backup of our first snapshot to that filesystem as a file:
```
root@raspberrypi:/mnt/btrfs# btrfs send -v snapshots/\@root-2017-06-05-13\:41\:17/ -f /mnt/ssd_ext4/btrfs_full_backup
At subvol snapshots/@root-2017-06-05-13:41:17/
BTRFS_IOC_SEND returned 0
joining genl thread
root@raspberrypi:/mnt/btrfs# ls -lah /mnt/ssd_ext4/
total 978M
drwxr-xr-x 3 root root 4.0K Jun 6 18:39 .
drwxr-xr-x 1 root root 44 Jun 6 17:52 ..
-rw------- 1 root root 978M Jun 6 18:40 btrfs_full_backup
drwx------ 2 root root 16K Jun 6 17:51 lost+found
```
Now let's look at sending a differential of our changes between that snapshot and our newly created `@root-backup-example`.
```
root@raspberrypi:/mnt/btrfs# btrfs send -v -p snapshots/\@root-2017-06-05-13\:41\:17/ snapshots/\@root-backup-example/ -f /mnt/ssd_ext4/btrfs_incremental
At subvol snapshots/@root-backup-example/
BTRFS_IOC_SEND returned 0
joining genl thread
root@raspberrypi:/mnt/btrfs# ls -lah /mnt/ssd_ext4/
total 1.2G
drwxr-xr-x 3 root root 4.0K Jun 6 20:49 .
drwxr-xr-x 1 root root 44 Jun 6 17:52 ..
-rw------- 1 root root 978M Jun 6 18:40 btrfs_full_backup
-rw------- 1 root root 196M Jun 6 20:49 btrfs_incremental
drwx------ 2 root root 16K Jun 6 17:51 lost+found
```
And there we are, our changes between the time we created the two snapshots total about 200MB. Note that I've done a bunch of experimenting on the demo pi in the last day, you probably won't have such a large delta.
There's something else important going on here that isn't obvious. File data and metadata are stored separately on our filesystem, so if the metadata for a file changes only this metadata change is stored in the differential backup. Contrast this with rsync which has no method to transfer just the metadata and transfers a new copy of the file if the metadata has changed. This means that btrfs send/receive can, under the right circumstances, create drastically smaller differentials than rsync would.
We can also play some neat tricks. For example, if we pipe btrfs send over ssh to a remote machine we can compress our backups remotely, rather than using the pi, on a potentially much more capable host.
## A Remote Backup Comparison
:::info
Note: For the following demonstration I've upgraded the `xz-utils` package on our demo machine to the stretch release. The version available on jessie doesn't feature multi-threading.
`apt-get upgrade xz-utils/stretch` if you'd like to perform these tests yourself.
I've also installed the python program `glances` so I can easily monitor IO performance and CPU load in another terminal window.
:::
### Local Backup, with Compression
Let's try something interesting as a demonstration.
First we'll send a full backup of our initial timestamped snapshot locally but we'll pipe it through `xz` utilizing 4 threads before writing it to disk. We'll time the entire operation.
```
root@raspberrypi:/mnt/btrfs# time btrfs send -v snapshots/\@root-2017-06-05-13\:41\:17/ | xz -c -T 4 > /mnt/ssd_ext4/btrfs_full.xz
At subvol snapshots/@root-2017-06-05-13:41:17/
```
This is going to take some time.
![](https://i.imgur.com/dhh3L2x.png)
Assuming the output of `/opt/vc/bin/vcgencmd measure_temp` is correct the SoC is hovering around ~75-80c during this operation. Oof, the rPi 3 isn't exactly a powerhouse for tasks like this.
```
root@raspberrypi:/mnt/btrfs# time btrfs send -v snapshots/\@root-2017-06-05-13\:41\:17/ | xz -c -T 4 > /mnt/ssd_ext4/btrfs_full.xz
At subvol snapshots/@root-2017-06-05-13:41:17/
BTRFS_IOC_SEND returned 0
joining genl thread
real 13m1.580s
user 47m29.010s
sys 0m50.810s
root@raspberrypi:/mnt/btrfs# ls -lah /mnt/ssd_ext4/
total 350M
drwxr-xr-x 2 root root 4.0K Jun 6 22:55 .
drwxr-xr-x 1 root root 44 Jun 6 17:52 ..
-rw-r--r-- 1 root root 350M Jun 6 22:54 btrfs_full.xz
root@raspberrypi:/mnt/btrfs# xz -l /mnt/ssd_ext4/btrfs_full.xz
Strms Blocks Compressed Uncompressed Ratio Check Filename
1 41 349.1 MiB 977.6 MiB 0.357 CRC64 /mnt/ssd_ext4/btrfs_full.xz
```
OK, 13 minutes isn't bad for compressing a ~1GB backup on the Pi.
### Remote Backup, with Compression on the remote host
Now let's have some real fun: Let's send our full backup to a remote server and compress it on the receiving end so we don't tie up resources on the Pi.
```
root@raspberrypi:/mnt/btrfs# time btrfs send -v snapshots/\@root-2017-06-05-13\:41\:17/ | ssh root@meta "xz -c -T 4 > /alpha/btrfs_remote_full.xz"
At subvol snapshots/@root-2017-06-05-13:41:17/
root@meta's password:
```
Here's the system state during the transfer:
![](https://i.imgur.com/W71cgz6.png)
Good, we're leaving our local resources available and temps (not shown) are around ~55C.
```
root@raspberrypi:/mnt/btrfs# time btrfs send -v snapshots/\@root-2017-06-05-13\:41\:17/ | ssh root@meta "xz -c -T 4 > /alpha/btrfs_remote_full.xz"
At subvol snapshots/@root-2017-06-05-13:41:17/
root@meta's password:
BTRFS_IOC_SEND returned 0
joining genl thread
real 3m54.947s
user 1m2.290s
sys 1m17.010s
root@raspberrypi:/mnt/btrfs# ssh root@meta "ls -lah /alpha/*.xz"
root@meta's password:
-rw-r--r--. 1 root root 350M Jun 6 23:03 /alpha/btrfs_remote_full.xz
```
Not bad. My NAS isn't much of a powerhouse either with a low clock dual core Pentium but it still outperforms the pi on tasks like this.
Let's quickly look at sending our differential in the same way.
```
root@raspberrypi:/mnt/btrfs# time btrfs send -v -p snapshots/\@root-2017-06-05-13\:41\:17/ snapshots/\@root-backup-example/ | ssh root@meta "xz -c -T 4 > /alpha/btrfs_remote_diff.xz"
At subvol snapshots/@root-backup-example/
root@meta's password:
BTRFS_IOC_SEND returned 0
joining genl thread
real 0m54.670s
user 0m10.850s
sys 0m11.930s
root@raspberrypi:/mnt/btrfs# ssh root@meta "ls -lah /alpha/*.xz"
root@meta's password:
-rw-r--r--. 1 root root 42M Jun 6 23:12 /alpha/btrfs_remote_diff.xz
-rw-r--r--. 1 root root 350M Jun 6 23:03 /alpha/btrfs_remote_full.xz
```
So there we are, secure remote differential backup with compression performed on the receiving host rather than the Pi.
Assuming a ~4GB data set size our compressed backup would take about 15 minutes to send to the remote host versus an hour to compress locally on the pi.
# TODO
# Existing system conversion
Let's talk a bit about converting an existing Raspbian install to Btrfs. There are too many possible scenarios for me to cover every case and write a full walkthrough but I can give you a rough overview of the steps that you'll need to follow.
First, decide whether you want to convert a portion of your SD card to a Btrfs partition.
If you're interested in trying Btrfs but don't want to commit to blowing away your existing install I suggest you temporarily use a USB flash drive to try things out. You'll need to install btrfs-tools/btrfs-progs with APT, create a partition + Btrfs filesystem on the flash drive and clone your root fs over with rsync. Next: setup your initramfs to include the btrfs module and make the relevant changes in /boot/config.txt to load the initrd.img, verify that the image loads at boot and then boot from the USB partition. You should be able to follow the general flow of the guide above and modify your partition locations accordingly. There are well-written guides available online re: mounting a USB drive partition as your root filesystem, if you're unsure about the process it would be a good idea to read one. Pay attention to the gotchas in the initramfs section above as they can make things difficult.
If you want to run Btrfs on your SD card you'll probably need to shrink your existing ext4 partition. Pull the SD card and use GParted or Paragon Partition Manager (there's a free version available) on another machine to shrink your partition. This should be non-destructive, but make a backup of the SD card first anyway. Once that's done go ahead and follow the the outline of the guide for a new install while making any needed changes for your setup. If you don't want to waste space keeping a full copy of your current install on the ext4 partition you can clone your install to a flash drive temporarily, install a fresh copy of Raspbian lite on the SD card, follow the guide above, and instead of cloning the new install to your Btrfs partition rsync your backup from the flash drive instead.
TL;DR: Read the guide for a new install so you understand what's going on, then dive in and modify it to suite your needs. Pay attention to the gotchas re: initramfs as the problems booting PARTUUIDs in Raspbian jessie with an initramfs can throw the process off.
# Final notes
It's probably worth booting your ext4 recovery partition and making changes to APT to install `btrfs-tools/stretch` and, if you're making remote backups using xz, `xz-utils/stretch` to take advantage of multithreading.
Hopefully this document is useful to you, it took a lot longer to write than I planned.
###### tags: `raspbian` `btrfs` `raspberry pi` `linux`