User:I2Oc9/Btrfs subvolumes
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.
- Only subvolumes support snapshoting
- Copies are equal, we can trash the source subvolume and replace it with the snapshot
- 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.
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
- Create a config file at /etc/snapper/configs/user_home, which defines the frequency of taking snapshots and cleaning up old ones
- 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
- Create a regular folder at /home/user/.snapshots/1
- Create a file /home/user/.snapshots/1/info.xml, containing metadata
- 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
- copy the current system to a new read-only snapshot/subvolume
- copy the desired snapshot to a new writable snapshot/subvolume
- 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 |
---|---|---|
@var | no mountpoint | |
@usr | no mountpoint |
Root filesystem
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 |
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, ... ...