Sawyer Shepherd's Blog

Hibernating to an Encrypted Swapfile on BTRFS with NixOS

Note that BTRFS swapfiles across multiple devices (pools, RAIDs) are not supported1.

BTRFS on Linux kernels before version 5.0 can result in filesystem corruption2. From the Arch Wiki:

Btrfs on Linux kernel before version 5.0 does not support swap files. Failure to heed this warning may result in file system corruption. While a swap file may be used on Btrfs when mounted through a loop device, this will result in severely degraded swap performance.

You must make a BTRFS subvolume and disable copy-on-write:

sudo btrfs subvolume create /swap
sudo chattr +C /swap

Create a swapfile of your chosen size. I chose the size of my memory +4kB for swap headers and such

sudo fallocate --length 15698384KiB /swap/swapfile

Format the swapfile

sudo mkswap /swap/swapfile

Enable the swapfile

sudo swapon /swap/swapfile

Add the following to your NixOS hardware configuration file3:

fileSystems."/swap" = {
  device = "/dev/disk/by-uuid/[uuid-of-your-btrfs-partition]";
  fsType = "btrfs";
  options = [ "subvol=swap" "noatime" ];
};

swapDevices = [ { device = "/swap/swapfile"; } ];

The UUID for the swap subvolme is the exact same as the UUID for the whole BTRFS partition. You don’t need to specify a subvol option to mount the root BTRFS partition.

Next, we need to find some values to add to our kernel boot parameters. Specifically, we need the UUID of the swap file and the swap file offest. Find the UUID of the swap device with:

findmnt -no UUID -T /swap/swapfile

Note that this UUID should point to your mounted, mapped BTRFS filesystem. In /dev/disk/by-uuid/[...], it’s probably a symlink to /dev/dm-X.

Now we need to find the physical swap file offset. In filesystems other than BTRFS, this is relatively easy, as we can use filefrag. Unfortunately, filefrag does not report the real physical offset of files on BTRFS systems (this is intended)4. Fortunately, a user called Osandov has recognized this problem and published a program to find the physical offset of a file on BTRFS here. Download the program and compile it:

wget https://raw.githubusercontent.com/osandov/osandov-linux/master/scripts/btrfs_map_physical.c
gcc btrfs_map_physical.c -o btrfs_map_physical

Now use the program to find the offset:

sudo ./btrfs_map_physical /swap/swapfile

Find the first physical offset provided and divide it by the page size of your system. Usually this is 4096, but you can also find it with:

getconf PAGESIZE

Add this to your NixOS hardware configuration:

boot.resumeDevice = "/dev/disk/by-uuid/[swap_device_uuid]"
boot.kernelParams = [ "resume_offset=[offset]" ];

And finally, rebuild and switch: sudo nixos-rebuild switch

If you have any issues with your device not shutting off after hibernating, add this to your NixOS config:

systemd.sleep.extraConfig = ''
    [Sleep]
    HibernateMode=shutdown
'';