diff options
Diffstat (limited to 'fs/btrfs')
| -rw-r--r-- | fs/btrfs/shutdown/Makefile | 56 | ||||
| -rw-r--r-- | fs/btrfs/shutdown/README.md | 93 | ||||
| -rw-r--r-- | fs/btrfs/shutdown/fserror.c | 77 | ||||
| -rw-r--r-- | fs/btrfs/shutdown/shutdown.c | 68 |
4 files changed, 294 insertions, 0 deletions
diff --git a/fs/btrfs/shutdown/Makefile b/fs/btrfs/shutdown/Makefile new file mode 100644 index 0000000..8871afd --- /dev/null +++ b/fs/btrfs/shutdown/Makefile @@ -0,0 +1,56 @@ +## +# By default everything is silent. If you want to change this behavior, simply +# assign V=1 when calling make. + +V = +ifeq ($(strip $(V)),) + E = @echo + Q = @ +else + E = @\# + Q = +endif + +## +# Compile options. You can use CROSS_COMPILE just like on the Linux Kernel. + +CC = $(CROSS_COMPILE)gcc +LD = $(CROSS_COMPILE)ld +CCFLAGS = -Werror -Wpedantic -Wall -Wextra -Wcast-qual -Winit-self \ + -Wmissing-include-dirs -Wredundant-decls -Wshadow -Wsign-conversion \ + -Wswitch-default -Wundef -Wunreachable-code -Wmissing-noreturn \ + -D_FORTIFY_SOURCE=3 -fstrict-flex-arrays=3 -fsanitize=bounds-strict + # NOTE: last line: special flags for the examples :) +LDFLAGS = + + +# You can pass an optional `DEBUG` variable to manipulate the build type. +DEBUG = +ifeq ($(strip $(DEBUG)),) + CCFLAGS += -O3 +else + CCFLAGS += -g +endif + +## +# Paths. + +SRC = $(wildcard *.c) +EXES = $(SRC:.c=) + +## +# Targets + +.PHONY: all +all: clean $(EXES) + +.PHONY: build +build: $(EXES) + +.c: + $(E) " CC " $(*F) + $(Q) $(CC) $(CCFLAGS) $< -o $@ + +.PHONY: clean +clean: + $(Q) rm -f $(EXES) diff --git a/fs/btrfs/shutdown/README.md b/fs/btrfs/shutdown/README.md new file mode 100644 index 0000000..332eaf4 --- /dev/null +++ b/fs/btrfs/shutdown/README.md @@ -0,0 +1,93 @@ +This example contains a set of files which showcase how the shutdown ioctl works +on BTRFS. This ioctl was introduced in Linux 6.19 and is marked as an +experimental feature. + +BTRFS has been able to freeze the filesystem for a long time, which might sound +similar to shutting it down. The main difference is that shutdown is meant for a +complete, persistent power-off; while freezing can be undone, it's temporary. As +it stands in Linux kernel 6.19, BTRFS shutdown comes in two flavors: + +1. _With_ log flushing (default): commits the current transaction, leaving the + file system into a consistent state, and shuts down the filesystem right + after freezing it. Then it's marked as thawed, but the shutdown state is + preserved. This is a bit of trickery to get all logs flushed, the filesystem + consistent, but with the proper flag set to shutdown. +2. _Without_ log flushing: discards any current transaction to keep the + filesystem into a consistent state and shuts down the filesystem right + away. This is probably the fastest and most aggresive route. + +## Requirements + +In order to be able to run this example as intended you need: + +- Linux kernel 6.19+ with BTRFS experimental features enabled. +- A Linux kernel built with the following patch applied: (["btrfs: report filesystem shutdown via + fserror"](https://lore.kernel.org/linux-btrfs/20260216002806.3831884-1-mssola@mssola.com)). + +## Listening for filesystem errors + +BTRFS will notify user-space whenever a shutdown happens. This way user-space +can mark this device as not available and hence can avoid writing into it. The +`fserror.c` file contains a small program that does just that. It receives a +mount point as an argument, and then waits for events on that filesystem, +filtering for the shutdown error. Run this program and then go onto the next +section. Whenever the shutdown happens, the terminal from where you executed +`fserror.c` will print the following: + +``` +Shutdown detected! +``` + +If you don't see it, either the kernel you have does not support BTRFS shutdown +yet, or some requirements are missing. + +## Actually shutting down + +The `shutdown.c` file contains a small program which accepts a mount point as an +argument. It then checks that it's a BTRFS mount point, and afterwards it calls +the shutdown ioctl with the default flags (i.e. with log flushing). Note that it +does not do any validation on the kernel you are using. Hence, if you are using +a Linux kernel version which has no support for this (e.g. previous than 6.19 or +6.19 without BTRFS experimental features), you will get the following message: + +``` +Shutdown failed: Inappropriate ioctl for device +``` + +This is the `ETTY` errno message, which just means that the ioctl support for +this filesystem does not implement this function. This will also happen in +virtualized environments, as the virtualized filesystem (e.g. overlayfs) will be +on the receiving end of this ioctl and, hence, they will return the same errno. + +But if you are running on a Linux kernel which does support this, then running +this program will give you the following message: + +``` +Success: filesystem shut down. +``` + +Then you will see the following message on `dmesg`: + +``` +[ 201.048599][ T995] BTRFS critical (device sda state E): emergency shutdown +``` + +From now on you will receive `EROFS` errors whenever you try to write into this +device. For example: + +``` +# echo "hello" >> mnt/lala.txt +bash: mnt/lala.txt: Read-only file system +``` + +## Patches sent to the Linux kernel as a result + +As a result of looking into this I have sent two patches to the Linux kernel: + +1. ["btrfs: report filesystem shutdown via + fserror"](https://lore.kernel.org/linux-btrfs/20260216002806.3831884-1-mssola@mssola.com), + which adds the 'fanotify' event for filesystem shutdown in BTRFS. +2. ["btrfs: don't commit the super block when unmounting a shutdown + filesystem"](https://lore.kernel.org/linux-btrfs/20260216002252.3831277-1-mssola@mssola.com), + which prevents BTRFS from trying to commit the super block on filesystem + unmount after a filesystem was shutdown. diff --git a/fs/btrfs/shutdown/fserror.c b/fs/btrfs/shutdown/fserror.c new file mode 100644 index 0000000..f06521c --- /dev/null +++ b/fs/btrfs/shutdown/fserror.c @@ -0,0 +1,77 @@ +#define _GNU_SOURCE + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/fanotify.h> + +int main(int argc, char *argv[]) +{ + int fd; + char buf[4096]; + ssize_t len; + struct fanotify_event_metadata *metadata; + struct fanotify_event_info_error *error_info; + + if (argc < 2) { + fprintf(stderr, "Usage: %s <mount point>\n", argv[0]); + exit(EXIT_FAILURE); + } + + /* + * Initialize 'fanotify' with the default value and `FAN_REPORT_FID`, which + * will allow use to filter by `FAN_FS_ERROR` down the line. + */ + fd = fanotify_init(FAN_CLASS_NOTIF | FAN_REPORT_FID, O_RDONLY); + if (fd < 0) { + perror("fanotify_init"); + exit(EXIT_FAILURE); + } + + /* + * Add a 'fanotify' mark to monitor all files from the filesystem pointed by + * 'fd'. Note that this general monitoring via 'FAN_MARK_FILESYSTEM' is the + * thing that makes this program to need root permission as documented in + * the manpage: "Use of this flag requires the CAP_SYS_ADMIN capability". + */ + if (fanotify_mark(fd, FAN_MARK_ADD | FAN_MARK_FILESYSTEM, + FAN_FS_ERROR, AT_FDCWD, argv[1]) < 0) { + perror("fanotify_mark"); + exit(EXIT_FAILURE); + } + + printf("Listening for filesystem errors on '%s'\n", argv[1]); + + // Event Loop + while ((len = read(fd, buf, sizeof(buf))) > 0) { + metadata = (struct fanotify_event_metadata *)buf; + + while (FAN_EVENT_OK(metadata, len)) { + /* + * We only care about filesystem errors, which is the only thing we + * passed to 'fanotify_mark'. That being said, just to be sure, + * let's filter this out. + */ + if (metadata->mask & FAN_FS_ERROR) { + // fancy... + error_info = (struct fanotify_event_info_error *) + ((char *)metadata + metadata->event_len - sizeof(*error_info)); + + if (error_info->error == ESHUTDOWN) { + printf("Shutdown detected!\n"); + goto done; + } else { + printf("Filesystem error detected with code: %d\n", error_info->error); + } + } + + metadata = FAN_EVENT_NEXT(metadata, len); + } + } + + done: + close(fd); + return 0; +} diff --git a/fs/btrfs/shutdown/shutdown.c b/fs/btrfs/shutdown/shutdown.c new file mode 100644 index 0000000..46a5731 --- /dev/null +++ b/fs/btrfs/shutdown/shutdown.c @@ -0,0 +1,68 @@ +#define _GNU_SOURCE + +#include <stdlib.h> +#include <fcntl.h> +#include <stdio.h> +#include <unistd.h> +#include <linux/btrfs.h> +#include <linux/magic.h> +#include <linux/types.h> +#include <sys/ioctl.h> +#include <sys/statfs.h> + +/* + * Constants defined in BTRFS' experimental shutdown support + * starting from Linux 6.19. + */ + +#ifndef BTRFS_SHUTDOWN_FLAGS_DEFAULT +#define BTRFS_SHUTDOWN_FLAGS_DEFAULT 0x0 +#endif + +#ifndef BTRFS_IOC_SHUTDOWN +#define BTRFS_IOC_SHUTDOWN _IOR('X', 125, __u32) +#endif + +int main(int argc, char *argv[]) +{ + int fd; + __u32 flags; + struct statfs info; + + if (argc < 2) { + fprintf(stderr, "usage: %s <mount point>\n", argv[0]); + exit(EXIT_FAILURE); + } + + if (statfs(argv[1], &info) == 0) { + /* + * We could certainly use this ioctl against EXT4 or XFS, but that's not + * the point we want to illustrate here. + */ + if (info.f_type != BTRFS_SUPER_MAGIC) { + printf("error: you are supposed to be running this against BTRFS;" + " %lx magic detected instead\n", info.f_type); + exit(EXIT_FAILURE); + } + } else { + perror("statfs failed"); + exit(EXIT_FAILURE); + } + + fd = open(argv[1], O_RDONLY | O_DIRECTORY); + if (fd < 0) { + perror("open"); + exit(EXIT_FAILURE); + } + + flags = BTRFS_SHUTDOWN_FLAGS_DEFAULT; + if (ioctl(fd, BTRFS_IOC_SHUTDOWN, &flags) == 0) { + printf("Success: filesystem shut down.\n"); + } else { + perror("Shutdown failed"); + exit(EXIT_FAILURE); + } + + close(fd); + return 0; +} |
