Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5730d95892 |
@@ -2,97 +2,81 @@
|
||||
|
||||
Observer-only file access notifications for [ScoutFS](https://github.com/versity/scoutfs),
|
||||
distributed as a rebasable `git format-patch` series plus a small Go
|
||||
userspace relay daemon. Maintained as one long-lived branch per
|
||||
currently-supported upstream scoutfs version.
|
||||
userspace relay daemon. Layers onto each upstream release with minimal
|
||||
maintenance.
|
||||
|
||||
## What the series adds
|
||||
|
||||
Three patches against each supported scoutfs release:
|
||||
Three patches against the scoutfs source tree:
|
||||
|
||||
1. **Kmod core** — a per-mount 64 KiB ring of 64-byte notification records and
|
||||
a single-reader drain ioctl (`SCOUTFS_IOC_READ_NOTIFY`, nr 26). Emit is
|
||||
a single-reader drain ioctl (`SCOUTFS_IOC_READ_NOTIFY`, nr 25). Emit is
|
||||
non-blocking, drop-on-full; the monotonic `seq` field exposes drops to
|
||||
consumers. Three percpu counters (`notify_emitted`,
|
||||
`notify_dropped_ring_full`, `notify_reader_attached`). No mount option,
|
||||
no sysfs toggle.
|
||||
2. **Kmod hooks** — `->open` wrapper and READ emit in
|
||||
`scoutfs_file_read_iter`. Every hook is a single predicted-false
|
||||
branch when no reader is attached. Nothing in the data-waiter state
|
||||
machine is touched. (Branches up to `v1.32` also hooked
|
||||
`scoutfs_file_aio_read`; scoutfs v1.33 removed the
|
||||
`KC_LINUX_HAVE_FOP_AIO_READ` compat paths, so `read_iter` is now the
|
||||
only read path.)
|
||||
`scoutfs_file_aio_read` / `scoutfs_file_read_iter`. Every hook is a
|
||||
single predicted-false branch when no reader is attached. Nothing in
|
||||
the data-waiter state machine is touched.
|
||||
3. **scoutfs-notifyd (Go)** — userspace daemon that binds
|
||||
`/run/scoutfs/<fsid>/notify.sock` (AF_UNIX SOCK_SEQPACKET, mode 0600,
|
||||
root-only), drains the ring, and broadcasts each record to connected
|
||||
clients. Pure-stdlib Go; no external module dependencies. Shipped
|
||||
with a systemd template unit `scoutfs-notifyd@<mountpoint>.service`.
|
||||
|
||||
The 64-byte event record layout is identical on every branch, so
|
||||
consumers and the daemon do not need per-version branching in their own
|
||||
code. The ioctl number is not: it is nr 25 on `v1.28`-`v1.30` and nr 26
|
||||
from `v1.32` on, because upstream v1.32 took nr 25 for
|
||||
`SCOUTFS_IOC_INJECT_TOTL_DELTA`. scoutfs v1.33 did not renumber again.
|
||||
## Base
|
||||
|
||||
## Repository layout
|
||||
Currently rebased against:
|
||||
|
||||
Each supported scoutfs version lives on its own long-lived branch. The
|
||||
patches in each branch are tuned for that specific scoutfs tree; they do
|
||||
NOT apply cross-version without rebasing.
|
||||
scoutfs v1.30
|
||||
|
||||
| Branch | Targets scoutfs | Tag |
|
||||
|----------------|-----------------|------------------|
|
||||
| `v1.28` | `v1.28` | `v1.28-notify-1` |
|
||||
| `v1.29` | `v1.29` | `v1.29-notify-1` |
|
||||
| `v1.30` | `v1.30` | `v1.30-notify-1` |
|
||||
| `v1.32` | `v1.32` | `v1.32-notify-1` |
|
||||
| `v1.33` | `v1.33` | `v1.33-notify-1` |
|
||||
See [base.txt](./base.txt).
|
||||
|
||||
`main` tracks the newest supported scoutfs version. When a new scoutfs
|
||||
release ships, a new branch is created and the patches are rebased onto
|
||||
it; when a scoutfs version ages out of support, its branch stays in the
|
||||
repo for historical builds but is no longer maintained.
|
||||
## Build dependencies added
|
||||
|
||||
On top of the stock scoutfs build requirements, patch 3 adds:
|
||||
|
||||
* `golang >= 1.26` on the build host (fetched from go.dev by the CI; no distro currently packages 1.26).
|
||||
|
||||
|
||||
The Go build is offline (`GOPROXY=off`) — no network access required at
|
||||
build time. `CGO_ENABLED=0` so the produced binary is a pure-Go static
|
||||
ELF.
|
||||
|
||||
## Applying
|
||||
|
||||
Against a scoutfs git working tree at the matching upstream tag:
|
||||
|
||||
```sh
|
||||
git clone -b v1.29 https://git.anomalous.dev/alphacentri/scoutfs-notify.git /tmp/notify
|
||||
/tmp/notify/apply.sh /path/to/scoutfs-v1.29-checkout
|
||||
./apply.sh /path/to/scoutfs
|
||||
```
|
||||
|
||||
The script runs `git am --3way` on each patch in order.
|
||||
Runs `git am --3way` on each patch. For a tarball instead of a git tree,
|
||||
loop `patch -p1 < patches/*.patch`.
|
||||
|
||||
## Rebasing onto a new upstream release
|
||||
|
||||
```sh
|
||||
# In a fresh scoutfs checkout:
|
||||
git checkout v1.31
|
||||
git checkout -b notify-v1.31
|
||||
|
||||
# Apply the previous branch's patches (usually clean; resolve any
|
||||
# conflicts, git am --continue).
|
||||
git am --3way /path/to/scoutfs-notify-v1.30/patches/*.patch
|
||||
git format-patch v1.31..notify-v1.31 -o /tmp/new-patches/
|
||||
|
||||
# Push a new branch + tag in this repo:
|
||||
cd /path/to/scoutfs-notify
|
||||
git checkout -b v1.31 main
|
||||
rm patches/*.patch
|
||||
cp /tmp/new-patches/*.patch patches/
|
||||
echo v1.31 > base.txt
|
||||
git commit -am 'v1.31-notify-1: rebase onto scoutfs v1.31'
|
||||
git push -u origin v1.31
|
||||
git tag v1.31-notify-1 && git push origin v1.31-notify-1
|
||||
# Fast-forward main to the new tip once you're ready to promote it.
|
||||
git fetch --tags
|
||||
git checkout -B notify v1.30
|
||||
git am --3way patches/*.patch
|
||||
# resolve any conflicts, git am --continue
|
||||
git format-patch v1.30..notify -o patches/
|
||||
# update base.txt and commit
|
||||
```
|
||||
|
||||
## Tag history
|
||||
|
||||
v1.30-notify-1 (current — rebased onto scoutfs v1.30, no code changes)
|
||||
v1.29-notify-4 (retired — last v1.29-base release)
|
||||
v1.29-notify-3 (retired — Go 1.21 toolchain)
|
||||
v1.29-notify-2 (retired — C daemon; had latent ring-size compile bug)
|
||||
v1.29-notify-1 (retired — shipped mount options)
|
||||
|
||||
## Quick smoke test after installation
|
||||
|
||||
```sh
|
||||
systemctl enable --now scoutfs-notifyd@mnt-scoutfs.service
|
||||
FSID=$(scoutfs stat /mnt/scoutfs | awk '/fsid/ { print $2 }')
|
||||
FSID=$(stat -f -c %i /mnt/scoutfs) # or use your scoutfs cli
|
||||
socat - UNIX-CONNECT:/run/scoutfs/${FSID}/notify.sock | xxd | head
|
||||
# touch some files in /mnt/scoutfs in another terminal
|
||||
```
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
From 0e703364da5efe4a3b2e96e5513204dd8a1a79ac Mon Sep 17 00:00:00 2001
|
||||
From 01ff9e270c2ef3818abdf6034d2bd090aec4f4a7 Mon Sep 17 00:00:00 2001
|
||||
From: William Gill <claude@williamgill.net>
|
||||
Date: Wed, 22 Apr 2026 14:38:51 -0500
|
||||
Subject: [PATCH 1/3] notify: core file-access notification infrastructure
|
||||
@@ -70,10 +70,10 @@ index fa632aa..de14c74 100644
|
||||
options.o \
|
||||
per_task.o \
|
||||
diff --git a/kmod/src/counters.h b/kmod/src/counters.h
|
||||
index cb7a2e1..83c0976 100644
|
||||
index 9088496..94e4f44 100644
|
||||
--- a/kmod/src/counters.h
|
||||
+++ b/kmod/src/counters.h
|
||||
@@ -157,6 +157,9 @@
|
||||
@@ -156,6 +156,9 @@
|
||||
EXPAND_COUNTER(net_recv_invalid_message) \
|
||||
EXPAND_COUNTER(net_recv_messages) \
|
||||
EXPAND_COUNTER(net_unknown_request) \
|
||||
@@ -84,7 +84,7 @@ index cb7a2e1..83c0976 100644
|
||||
EXPAND_COUNTER(orphan_scan_attempts) \
|
||||
EXPAND_COUNTER(orphan_scan_cached) \
|
||||
diff --git a/kmod/src/ioctl.c b/kmod/src/ioctl.c
|
||||
index 903033b..3a09e2e 100644
|
||||
index 0a5fc4c..b3a4d9a 100644
|
||||
--- a/kmod/src/ioctl.c
|
||||
+++ b/kmod/src/ioctl.c
|
||||
@@ -47,6 +47,7 @@
|
||||
@@ -95,22 +95,22 @@ index 903033b..3a09e2e 100644
|
||||
#include "scoutfs_trace.h"
|
||||
#include "util.h"
|
||||
|
||||
@@ -1827,6 +1828,8 @@ long scoutfs_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
@@ -1790,6 +1791,8 @@ long scoutfs_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
return scoutfs_ioc_read_xattr_index(file, arg);
|
||||
case SCOUTFS_IOC_PUNCH_OFFLINE:
|
||||
return scoutfs_ioc_punch_offline(file, arg);
|
||||
case SCOUTFS_IOC_INJECT_TOTL_DELTA:
|
||||
return scoutfs_ioc_inject_totl_delta(file, arg);
|
||||
+ case SCOUTFS_IOC_READ_NOTIFY:
|
||||
+ return scoutfs_ioc_read_notify(file, arg);
|
||||
}
|
||||
|
||||
return -ENOTTY;
|
||||
diff --git a/kmod/src/ioctl.h b/kmod/src/ioctl.h
|
||||
index 1bb466c..0ac8d24 100644
|
||||
index c0d2285..d468d4c 100644
|
||||
--- a/kmod/src/ioctl.h
|
||||
+++ b/kmod/src/ioctl.h
|
||||
@@ -889,4 +889,88 @@ struct scoutfs_ioctl_inject_totl_delta {
|
||||
#define SCOUTFS_IOC_INJECT_TOTL_DELTA \
|
||||
_IOW(SCOUTFS_IOCTL_MAGIC, 25, struct scoutfs_ioctl_inject_totl_delta)
|
||||
@@ -876,4 +876,88 @@ struct scoutfs_ioctl_punch_offline {
|
||||
#define SCOUTFS_IOC_PUNCH_OFFLINE \
|
||||
_IOW(SCOUTFS_IOCTL_MAGIC, 24, struct scoutfs_ioctl_punch_offline)
|
||||
|
||||
+/*
|
||||
+ * File access notification stream (observer-only).
|
||||
@@ -194,7 +194,7 @@ index 1bb466c..0ac8d24 100644
|
||||
+};
|
||||
+
|
||||
+#define SCOUTFS_IOC_READ_NOTIFY \
|
||||
+ _IOWR(SCOUTFS_IOCTL_MAGIC, 26, struct scoutfs_ioctl_read_notify)
|
||||
+ _IOWR(SCOUTFS_IOCTL_MAGIC, 25, struct scoutfs_ioctl_read_notify)
|
||||
+
|
||||
#endif
|
||||
diff --git a/kmod/src/notify.c b/kmod/src/notify.c
|
||||
@@ -622,7 +622,7 @@ index 0000000..fe6a2d9
|
||||
+
|
||||
+#endif /* _SCOUTFS_NOTIFY_H_ */
|
||||
diff --git a/kmod/src/super.c b/kmod/src/super.c
|
||||
index f2e1420..94922be 100644
|
||||
index 3c83716..3c028b0 100644
|
||||
--- a/kmod/src/super.c
|
||||
+++ b/kmod/src/super.c
|
||||
@@ -51,6 +51,7 @@
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
From 9eb38690d14d631a0dd96f464caa588bee86278d Mon Sep 17 00:00:00 2001
|
||||
From f4abcb49aa3477fc654a435798e5e9efeea44e12 Mon Sep 17 00:00:00 2001
|
||||
From: William Gill <claude@williamgill.net>
|
||||
Date: Wed, 22 Apr 2026 14:40:04 -0500
|
||||
Subject: [PATCH 2/3] notify: file open/read hook sites
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
Wires the notification emit path into the two scoutfs file
|
||||
operations that carry user-visible activity we want to observe.
|
||||
@@ -10,22 +13,18 @@ OPEN (data.c):
|
||||
A new scoutfs_file_open() wrapper is installed as ->open in
|
||||
scoutfs_file_fops. The wrapper calls generic_file_open() to
|
||||
preserve existing VFS default semantics for regular files,
|
||||
then - only on success and only when notifications are enabled
|
||||
- emits a SCOUTFS_NOTIFY_TYPE_OPEN record. File mode is
|
||||
then — only on success and only when notifications are enabled
|
||||
— emits a SCOUTFS_NOTIFY_TYPE_OPEN record. File mode is
|
||||
inspected for FMODE_WRITE to set SCOUTFS_NOTIFY_F_WRITE_OPEN.
|
||||
|
||||
READ (file.c):
|
||||
scoutfs_file_read_iter() gets an emit placed past the existing
|
||||
data-waiter retry check, guarded on (ret > 0) so only successful
|
||||
reads are reported and retries never double-count. start_pos is
|
||||
captured at function entry before generic_file_read_iter advances
|
||||
Both the aio_read (KC_LINUX_HAVE_FOP_AIO_READ) and read_iter
|
||||
paths get an emit placed past the existing data-waiter retry
|
||||
check, guarded on (ret > 0) so only successful reads are
|
||||
reported and retries never double-count. start_pos is captured
|
||||
at entry of read_iter before generic_file_read_iter advances
|
||||
iocb->ki_pos.
|
||||
|
||||
Rebased onto v1.33: upstream removed the KC_LINUX_HAVE_FOP_AIO_READ
|
||||
compat paths, so scoutfs_file_aio_read/aio_write no longer exist and
|
||||
the second READ hook and the fops table's #ifdef arm went with them.
|
||||
read_iter is now the only read path. The hook body is unchanged.
|
||||
|
||||
Every hook is behind unlikely(READ_ONCE(sbi->notify_enabled)), so
|
||||
when no userspace reader is attached the hook reduces to a single
|
||||
predicted-false branch. No scoutfs state is mutated, no error is
|
||||
@@ -34,14 +33,14 @@ propagated, and no existing control flow is altered.
|
||||
Nothing in the data-waiter state machine is touched.
|
||||
---
|
||||
kmod/src/data.c | 28 ++++++++++++++++++++++++++++
|
||||
kmod/src/file.c | 7 +++++++
|
||||
2 files changed, 35 insertions(+)
|
||||
kmod/src/file.c | 11 +++++++++++
|
||||
2 files changed, 39 insertions(+)
|
||||
|
||||
diff --git a/kmod/src/data.c b/kmod/src/data.c
|
||||
index 1d3168f..5611dd8 100644
|
||||
index 0abb48c..33a61af 100644
|
||||
--- a/kmod/src/data.c
|
||||
+++ b/kmod/src/data.c
|
||||
@@ -42,6 +42,7 @@
|
||||
@@ -41,6 +41,7 @@
|
||||
#include "msg.h"
|
||||
#include "ext.h"
|
||||
#include "util.h"
|
||||
@@ -49,7 +48,7 @@ index 1d3168f..5611dd8 100644
|
||||
|
||||
/*
|
||||
* We want to amortize work done after dirtying the shared transaction
|
||||
@@ -2242,11 +2243,38 @@ const struct address_space_operations scoutfs_file_aops = {
|
||||
@@ -2304,6 +2305,32 @@ const struct address_space_operations scoutfs_file_aops = {
|
||||
.write_end = scoutfs_write_end,
|
||||
};
|
||||
|
||||
@@ -80,16 +79,18 @@ index 1d3168f..5611dd8 100644
|
||||
+}
|
||||
+
|
||||
const struct file_operations scoutfs_file_fops = {
|
||||
.read_iter = scoutfs_file_read_iter,
|
||||
.write_iter = scoutfs_file_write_iter,
|
||||
#ifdef KC_LINUX_HAVE_FOP_AIO_READ
|
||||
.read = do_sync_read,
|
||||
@@ -2316,6 +2343,7 @@ const struct file_operations scoutfs_file_fops = {
|
||||
.splice_read = generic_file_splice_read,
|
||||
.splice_write = iter_file_splice_write,
|
||||
#endif
|
||||
+ .open = scoutfs_file_open,
|
||||
.mmap = scoutfs_file_mmap,
|
||||
.unlocked_ioctl = scoutfs_ioctl,
|
||||
.fsync = scoutfs_file_fsync,
|
||||
diff --git a/kmod/src/file.c b/kmod/src/file.c
|
||||
index 0f2e7b8..fb7eace 100644
|
||||
index 15158a2..44f1bc4 100644
|
||||
--- a/kmod/src/file.c
|
||||
+++ b/kmod/src/file.c
|
||||
@@ -29,6 +29,7 @@
|
||||
@@ -98,9 +99,20 @@ index 0f2e7b8..fb7eace 100644
|
||||
#include "quota.h"
|
||||
+#include "notify.h"
|
||||
|
||||
ssize_t scoutfs_file_read_iter(struct kiocb *iocb, struct iov_iter *to)
|
||||
{
|
||||
@@ -39,6 +40,7 @@ ssize_t scoutfs_file_read_iter(struct kiocb *iocb, struct iov_iter *to)
|
||||
#ifdef KC_LINUX_HAVE_FOP_AIO_READ
|
||||
/*
|
||||
@@ -84,6 +85,10 @@ out:
|
||||
goto retry;
|
||||
}
|
||||
|
||||
+ if (ret > 0 && unlikely(READ_ONCE(SCOUTFS_SB(sb)->notify_enabled)))
|
||||
+ scoutfs_notify_emit(sb, SCOUTFS_NOTIFY_TYPE_READ,
|
||||
+ scoutfs_ino(inode), pos, ret, 0);
|
||||
+
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -166,6 +171,7 @@ ssize_t scoutfs_file_read_iter(struct kiocb *iocb, struct iov_iter *to)
|
||||
struct scoutfs_lock *scoutfs_inode_lock = NULL;
|
||||
SCOUTFS_DECLARE_PER_TASK_ENTRY(pt_ent);
|
||||
DECLARE_DATA_WAIT(dw);
|
||||
@@ -108,7 +120,7 @@ index 0f2e7b8..fb7eace 100644
|
||||
int ret;
|
||||
|
||||
retry:
|
||||
@@ -73,6 +75,11 @@ out:
|
||||
@@ -200,6 +206,11 @@ out:
|
||||
if (ret == 0)
|
||||
goto retry;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
From e7d50bb7b4e2fa0a88e7b4b8f4be75bb07e37351 Mon Sep 17 00:00:00 2001
|
||||
From c2793743cdc635f54edb1ed2a8020a9f1ca47315 Mon Sep 17 00:00:00 2001
|
||||
From: William Gill <claude@williamgill.net>
|
||||
Date: Wed, 22 Apr 2026 14:40:34 -0500
|
||||
Subject: [PATCH 3/3] notify: scoutfs-notifyd userspace relay daemon (Go 1.26)
|
||||
@@ -72,10 +72,10 @@ recv() events.
|
||||
utils/Makefile | 28 +-
|
||||
utils/man/scoutfs-notifyd.8 | 120 +++++++
|
||||
utils/notifyd/go.mod | 3 +
|
||||
utils/notifyd/main.go | 437 +++++++++++++++++++++++++
|
||||
utils/notifyd/main.go | 434 +++++++++++++++++++++++++
|
||||
utils/notifyd/scoutfs-notifyd@.service | 40 +++
|
||||
utils/scoutfs-utils.spec.in | 8 +
|
||||
6 files changed, 633 insertions(+), 3 deletions(-)
|
||||
6 files changed, 630 insertions(+), 3 deletions(-)
|
||||
create mode 100644 utils/man/scoutfs-notifyd.8
|
||||
create mode 100644 utils/notifyd/go.mod
|
||||
create mode 100644 utils/notifyd/main.go
|
||||
@@ -274,10 +274,10 @@ index 0000000..d99f395
|
||||
+go 1.26
|
||||
diff --git a/utils/notifyd/main.go b/utils/notifyd/main.go
|
||||
new file mode 100644
|
||||
index 0000000..41b98ab
|
||||
index 0000000..9478bae
|
||||
--- /dev/null
|
||||
+++ b/utils/notifyd/main.go
|
||||
@@ -0,0 +1,437 @@
|
||||
@@ -0,0 +1,434 @@
|
||||
+// Copyright (C) 2026 Versity Software, Inc. All rights reserved.
|
||||
+//
|
||||
+// This program is free software; you can redistribute it and/or
|
||||
@@ -396,11 +396,8 @@ index 0000000..41b98ab
|
||||
+var (
|
||||
+ scoutfsIocStatfsMore = iocEncode(iocDirRead,
|
||||
+ scoutfsIoctlMagic, 10, statfsMoreSize)
|
||||
+ // nr must match SCOUTFS_IOC_READ_NOTIFY in kmod/src/ioctl.h. On the
|
||||
+ // v1.32 base this is 26: upstream v1.32 took nr 25 for
|
||||
+ // SCOUTFS_IOC_INJECT_TOTL_DELTA, so the notify ioctl moved to 26.
|
||||
+ scoutfsIocReadNotify = iocEncode(iocDirRead|iocDirWrite,
|
||||
+ scoutfsIoctlMagic, 26, readNotifySize)
|
||||
+ scoutfsIoctlMagic, 25, readNotifySize)
|
||||
+)
|
||||
+
|
||||
+func init() {
|
||||
|
||||
Reference in New Issue
Block a user