User:M0p/Btrfs subvolumes

From ArchWiki

An intro to subvolumes

Basics

Btrfs has a basic concept called 'subvolumes'. Subvolumes are identified by a numeric ID, or its location relative to the top-level subvolume. The top-level subvolume has an ID of 5, or path /.

mkfs.btrfs /dev/sda1
mount /dev/sda1 /mnt
mount | grep mnt

Output

/dev/sda1 on /mnt type btrfs (rw,relatime,subvolid=5,subvol=/)

We then create a new subvolume

cd /mnt
btrfs subvolume create subvol_1

Now it should appear as a normal folder, let's see what does Btrfs think of it

btrfs subvolume list /mnt -t

Output

ID	gen	top level	path	
--	---	---------	----	
256	15	5		subvol_1

It has an ID of 256, its path is "subvol_1" and the ID of its top-level subvolume is 5.

Subvolumes can be mounted

A subvolume can be mounted anywhere, just like an independent filesystem. subvol= and subvolid= are redundant.

mkdir /mnt/mountpoint_1
mount /dev/sda1 /mnt/mountpoint_1 -o subvol=subvol_1,subvolid=256
mount | grep subvol_1

Output

/dev/sda1 on /mnt/mountpoint_1 type btrfs (rw,relatime,subvol=subvol_1,subvolid=256)

Now /mnt/mountpoint_1 and /mnt/subvol_1 are the same folder.

A Btrfs filesystem has an attribute called 'default subvolume', which specifies the subvolume to be mounted by default when no other options has been supplied. To test this, we will find the current default subvolume, set the default subvolume to subvol_1, unmount and remount the filesystem.

btrfs subvolume get-default /mnt

Output

ID 5 (FS_TREE)

Next step

btrfs subvolume set-default 256 /mnt # numeric ID only
cd /
umount /mnt
mount /dev/sda1 /mnt
mount | grep mnt

Output

/dev/sda1 on /mnt/ type btrfs (rw,relatime,subvol=subvol_1,subvolid=256)

Subvolumes can be everywhere

Subvolumes can be created inside subvolumes, inside normal folders, or inside a nested subvolume/folder.

cd /mnt
btrfs subvolume create subvol_1/subvol_2
mkdir subvol_1/dir_1
btrfs subvolume create subvol_1/dir_1/subvol_3
btrfs subvolume list /mnt -t

Output

ID	gen	top level	path	
--	---	---------	----	
257	15	256		subvol_1/subvol_2
258	15	256		subvol_1/dir_1/subvol_3

They can be moved freely with normal commands

mv subvol_1/subvol_2 subvol_1/dir_1/
btrfs subvolume list /mnt -t

Output

ID	gen	top level	path	
--	---	---------	----	
257	15	256		subvol_1/dir_1/subvol_2
258	15	256		subvol_1/dir_1/subvol_3

Subvolumes can also be deleted with rm -r or rmdir. They are functionally identical with btrfs subvolume delete

Set a subvolume as read-only

btrfs property set /mnt/subvol_1 ro true

To unset, simply run

btrfs property set /mnt/subvol_1 ro false

Snapshots

There isn't any kind of special file named "snapshot"; there's only a process, referred to as "snapshoting", which is nothing but making copies of existing subvolumes.

  1. Only subvolumes support snapshoting
  2. Copies are equal, we can trash the source subvolume and replace it with the snapshot
  3. Snapshots are subvolumes

In this step, we will create snapshots of subvol_1.

cd /mnt
btrfs snapshot subvol_1 subvol_1_snapshot_1
mkdir dir_2
btrfs snapshot -r subvol_1 dir_2/subvol_1_snapshot_2

The result is two subvolumes, subvol_1_snapshot_1 and subvol_1_snapshot_2, identical to subvol_1. subvol_1_snapshot_2 subvolume is read-only because of -r.

Snapshoting is not recursive

btrfs subvolume create subvol_1/subvol_demo
touch subvol_1/subvol_demo/test
btrfs snapshot subvol_1 subvol_1_snapshot_1

subvol_1_snapshot_1/subvol_demo will be an empty folder. Subvolume snapshot is not recursive.

The implication is that we will need to explicitly configure every subvolume we want to take snapshot of with snapper.

Note: If a subvolume contains a Copy-on-Write-disabled file, such as swapfile, a snapshot of this subvolume can not be taken.

An intro to Snapper

What does snapper do when taking a snapshot

The package snapper also operates on the same basis described in the above section.

Now let's get real and see snapper in action.

First, there's a Btrfs subvolume /home/user. We then create a snapper config for it with

snapper create-config user_home /home/user

This will

  1. Create a config file at /etc/snapper/configs/user_home, which defines the frequency of taking snapshots and cleaning up old ones
  2. Create a regular folder at /home/user/.snapshots, not subvolume.

We then take a snapshot with the following command

snapper -c home --description 'user_home'

This will

  1. Create a regular folder at /home/user/.snapshots/1
  2. Create a file /home/user/.snapshots/1/info.xml, containing metadata
  3. Create read-only snapshot /home/user to /home/user/.snapshots/1/snapshot, btrfs snapshot -r /home/user /home/user/.snapshots/1/snapshot

snapper rollback

'snapper rollback' is only intended to be used on the subvolume that serves as the system root /, since it will attempt to set the default subvolume to the new writeable subvolume (or snapshot, they are the same thing).

'snapper rollback' will

  1. copy the current system to a new read-only snapshot/subvolume
  2. copy the desired snapshot to a new writable snapshot/subvolume
  3. set the default subvolume to the writable snapshot/subvolume

However, neither GRUB or /etc/fstab generated by genfstab respects this 'default subvolume' setting. They have ideas of their own, namely, both of them hard-codes the root subvolume.

This can be easily averted in /etc/fstab by removing subvol= and subvolid= from the / line. But for GRUB, we will need grub-btrfs to detect other snapshots and make them available for selection in GRUB menu.

Snapper on flatly-installed system subvolume

The above works very well with non-system subvolumes. Every file can be recovered by visiting /.snapshots. Let's consider a system installed in Btrfs root.

 subvolid=5,subvol=/
 └── /usr, /bin, /sbin, /.snapshots, ...

After taking a snapshot, it will be something like this

 subvolid=5,subvol=/
 └── /usr, /bin, /sbin, /.snapshots, ...
                            └── 1
                                └── snapshot (ro)
                                     └── /usr, /bin, /.snapshots, ....

Suppose the system is now broken and contains a file 'broken' in / and you'd like to rollback to snapshot 1. After issuing 'snapper rollback 1', the filesystem should be something like this

 subvolid=5,subvol=/
 └── /usr, /bin, /sbin, /.snapshots, 'broken', ...
                            ├── 1
                            │   └── snapshot (ro)
                            │        └── /usr, /bin, /.snapshots, ....
                            ├── 2
                            │   └── snapshot (ro)
                            │        └── /usr, /bin,  /.snapshots, 'broken', ....
                            └── 3
                                └── snapshot (rw, identical with snapshot 1)
                                     └── /usr, /bin, /.snapshots, ....

After tons of configfile editing, you booted from snapshot 3, and snapper automatically starts to take snapshots again, the filesystem will be something like this

 subvolid=5,subvol=/
 └── /usr, /bin, /sbin, /.snapshots, 'broken', ...
                            ├── 1
                            │   └── snapshot (ro)
                            │        └── /usr, /bin, /.snapshots, ....
                            ├── 2
                            │   └── snapshot (ro)
                            │        └── /usr, /bin,  /.snapshots, 'broken', ....
                            └── 3
                                └── snapshot (rw, identical with snapshot 1, will boot from here)
                                     └── /usr, /bin, /.snapshots, ....
                                                       └── 4
                                                           └── snapshot (ro)
                                                               └── /usr, /bin, /.snapshots, ....

Now 3 is broken with file 'broken3', Rollback again to 4

 subvolid=5,subvol=/
 └── /usr, /bin, /sbin, /.snapshots, 'broken', ...
                            ├── 1
                            │   └── snapshot (ro)
                            │        └── /usr, /bin, /.snapshots, ....
                            ├── 2
                            │   └── snapshot (ro)
                            │        └── /usr, /bin,  /.snapshots, 'broken', ....
                            └── 3
                                └── snapshot
                                     └── /usr, /bin, /.snapshots, 'broken3', ....
                                                      ├── 4
                                                      │   └── snapshot (ro)
                                                      │        └── /usr, /bin, /.snapshots, ....
                                                      ├── 5
                                                      │   └── snapshot (ro)
                                                      │        └── /usr, /bin,  /.snapshots, 'broken3', ....
                                                      └── 6
                                                          └── snapshot (rw, identical with snapshot 4, will boot from here)
                                                               └── /usr, /bin, /.snapshots, ....
                                                                                  └── 7
                                                                                      └── snapshot
                                                                                               └── /usr, /bin, /.snapshots, ....

We can see that this kind of mess does not end. So we need to separate /.snapshots from /.

A new kind of layout

Btrfs root file system layout as recommended by openSUSE [1]. Important dirs are excluded. Other Linux distributions can also be installed.

Containers

Subvolume Mountpoint Notes
Filesystem layout
@var no mountpoint
@usr no mountpoint

Root filesystem

Filesystem layout
Subvolume Mountpoint Notes
@/0/snapshot /
@/ /.snapshots
@home /home
@root /root
@srv /srv
@usr/local /usr/local
@swap /swap nodatacow, for swapfile
@var/{tmp,spool,log,cache,games,www,lib,lib/libvirt,lib/docker,lib/AccountsService,lib/NetworkManager} ... Install_Arch_Linux_on_ZFS#System_datasets
@var/mail /var/mail doesn't work with pacstrap
subvol=/
├── archlinux
│    ├── @ (mounted at /.snapshots)
│    │   └── 0 (normal folder)
│    │       └── snapshot (mounted at /)
│    │            └── /usr, /bin, /.snapshots (mountpoint), ...
│    ├── @home (mounted at /home)
│    ├── @opt (same as above)
│    ├── @root (same)
│    ├── @srv (same)
│    ...
├── debian
...
subvol=/archlinux/@/0/snapshot
  ├── bin
  ├── home
  ├── opt
  ├── root
  ├── srv
  ...

Suppose it's broken again with a 'broken' file, rollback to 1 with '--ambit classic' (snapper will fail to detect default snapshot without this flag)

subvol=/
└── archlinux
     ├── @ (mounted at /.snapshots)
     │   ├── 0
     │   │   └── snapshot (rw)
     │   │        └── /usr, /bin, /.snapshots, 'broken', ...
     │   ├── 1
     │   │   └── snapshot (ro)
     │   │        └── /usr, /bin, /.snapshots, ...
     │   ├── 2
     │   │   └── snapshot (ro)
     │   │        └── /usr, /bin, /.snapshots, 'broken', ...
     │   └── 3
     │       └── snapshot (rw, identical to 1, will boot from here)
     │            └── /usr, /bin, /.snapshots, ...
     ...