4 Commits
Author SHA1 Message Date
57_WolveandClaude Opus 5 49af0c0baa v1.34-notify-1: rebase onto scoutfs v1.34
Upstream v1.34 added iomap-based buffered and direct I/O, splitting
kmod/src/file.c into a KC_USE_IOMAP_FOR_IO arm and an #else arm, each
with its own scoutfs_file_read_iter.  Patch 0002 now hooks both.  EL8
and EL9 kernels lack copy_page_to_iter_nofault (v6.4+) and compile the
#else arm, so the shipped kmods are unaffected either way, but hooking
only that arm would make READ notifications a silent no-op on any
newer kernel that later enters the build matrix.

Patch 0001 and 0003 are unchanged apart from context offsets; the
ioctl ABI is untouched (SCOUTFS_IOC_READ_NOTIFY stays nr 26 -- v1.34
did not renumber).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 19:12:33 -05:00
57_Wolve 168f608649 v1.33-notify-1: rebase onto scoutfs v1.33 2026-07-31 08:53:10 -05:00
57_Wolve b22524091e v1.32: rebase notify series onto scoutfs v1.32
ioctl number collision: upstream v1.32 claimed _IOC nr 25 for
SCOUTFS_IOC_INJECT_TOTL_DELTA, which the v1.30-based series used for
SCOUTFS_IOC_READ_NOTIFY.  Moved the notify ioctl to nr 26 in both
kmod/src/ioctl.h and the notifyd Go daemon (utils/notifyd/main.go).
Patches 0002 (hook sites) and 0003 (daemon) otherwise unchanged.

base: v1.32
2026-06-11 09:23:30 -05:00
William Gill f54bf88e50 Document per-version branch model on main README
With the scoutfs-build multi-version pipeline landing, each
supported scoutfs version (v1.28, v1.29, v1.30) has its own
long-lived branch here.  The tag history section is replaced by a
table showing which branch targets which scoutfs version, and the
rebase-for-new-upstream walkthrough is updated to describe creating
a new branch rather than overwriting main.
2026-04-23 09:02:44 -05:00
5 changed files with 128 additions and 95 deletions
+59 -39
View File
@@ -2,81 +2,101 @@
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. Layers onto each upstream release with minimal
maintenance.
userspace relay daemon. Maintained as one long-lived branch per
currently-supported upstream scoutfs version.
## What the series adds
Three patches against the scoutfs source tree:
Three patches against each supported scoutfs release:
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 25). Emit is
a single-reader drain ioctl (`SCOUTFS_IOC_READ_NOTIFY`, nr 26). 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_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.
`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 v1.34 added iomap buffered/direct I/O, which
splits `scoutfs_file_read_iter` into a `KC_USE_IOMAP_FOR_IO` arm and
an `#else` arm; from `v1.34` on both arms carry the hook.)
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`.
## Base
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`. Neither scoutfs v1.33 nor v1.34
renumbered again.
Currently rebased against:
## Repository layout
scoutfs v1.30
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.
See [base.txt](./base.txt).
| 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` |
| `v1.34` | `v1.34` | `v1.34-notify-1` |
## 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.
`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.
## Applying
Against a scoutfs git working tree at the matching upstream tag:
```sh
./apply.sh /path/to/scoutfs
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
```
Runs `git am --3way` on each patch. For a tarball instead of a git tree,
loop `patch -p1 < patches/*.patch`.
The script runs `git am --3way` on each patch in order.
## Rebasing onto a new upstream release
```sh
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
# 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.
```
## 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=$(stat -f -c %i /mnt/scoutfs) # or use your scoutfs cli
FSID=$(scoutfs stat /mnt/scoutfs | awk '/fsid/ { print $2 }')
socat - UNIX-CONNECT:/run/scoutfs/${FSID}/notify.sock | xxd | head
# touch some files in /mnt/scoutfs in another terminal
```
+1 -1
View File
@@ -1 +1 @@
v1.28
v1.34
@@ -1,4 +1,4 @@
From 1be639b8a4fdfaac37fc5801a314d080200105cb Mon Sep 17 00:00:00 2001
From e01e070f486cf0f525cf4300464201e092b62e4d 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
@@ -58,10 +58,10 @@ follow-up patch.
create mode 100644 kmod/src/notify.h
diff --git a/kmod/src/Makefile b/kmod/src/Makefile
index fa632aa..de14c74 100644
index 7fd7319..c54b47e 100644
--- a/kmod/src/Makefile
+++ b/kmod/src/Makefile
@@ -31,6 +31,7 @@ scoutfs-y += \
@@ -32,6 +32,7 @@ scoutfs-y += \
lock_server.o \
msg.o \
net.o \
@@ -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 9088496..94e4f44 100644
index cb7a2e1..83c0976 100644
--- a/kmod/src/counters.h
+++ b/kmod/src/counters.h
@@ -156,6 +156,9 @@
@@ -157,6 +157,9 @@
EXPAND_COUNTER(net_recv_invalid_message) \
EXPAND_COUNTER(net_recv_messages) \
EXPAND_COUNTER(net_unknown_request) \
@@ -84,7 +84,7 @@ index 9088496..94e4f44 100644
EXPAND_COUNTER(orphan_scan_attempts) \
EXPAND_COUNTER(orphan_scan_cached) \
diff --git a/kmod/src/ioctl.c b/kmod/src/ioctl.c
index 7a73d4d..9893559 100644
index 6da71d9..5703c6a 100644
--- a/kmod/src/ioctl.c
+++ b/kmod/src/ioctl.c
@@ -47,6 +47,7 @@
@@ -94,23 +94,23 @@ index 7a73d4d..9893559 100644
+#include "notify.h"
#include "scoutfs_trace.h"
#include "util.h"
@@ -1718,6 +1719,8 @@ long scoutfs_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
return scoutfs_ioc_mod_quota_rule(file, arg, false);
case SCOUTFS_IOC_READ_XATTR_INDEX:
return scoutfs_ioc_read_xattr_index(file, arg);
#include "msg.h"
@@ -1829,6 +1830,8 @@ long scoutfs_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
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 cefecd4..23fda13 100644
index 1bb466c..0ac8d24 100644
--- a/kmod/src/ioctl.h
+++ b/kmod/src/ioctl.h
@@ -848,4 +848,88 @@ struct scoutfs_ioctl_read_xattr_index {
#define SCOUTFS_IOC_READ_XATTR_INDEX \
_IOR(SCOUTFS_IOCTL_MAGIC, 23, struct scoutfs_ioctl_read_xattr_index)
@@ -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)
+/*
+ * File access notification stream (observer-only).
@@ -194,7 +194,7 @@ index cefecd4..23fda13 100644
+};
+
+#define SCOUTFS_IOC_READ_NOTIFY \
+ _IOWR(SCOUTFS_IOCTL_MAGIC, 25, struct scoutfs_ioctl_read_notify)
+ _IOWR(SCOUTFS_IOCTL_MAGIC, 26, 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 3c83716..3c028b0 100644
index f2e1420..94922be 100644
--- a/kmod/src/super.c
+++ b/kmod/src/super.c
@@ -51,6 +51,7 @@
@@ -1,10 +1,7 @@
From e970d4d8c6ab1f13cc7e2e51a830ba2f387b62d3 Mon Sep 17 00:00:00 2001
From cf9d03234f1424b95d08e3a3492479f550aee455 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.
@@ -13,18 +10,25 @@ 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):
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
Each 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 the read advances
iocb->ki_pos.
Rebased onto v1.34: upstream added iomap-based buffered and direct
I/O, so file.c now carries two scoutfs_file_read_iter definitions -
one under KC_USE_IOMAP_FOR_IO and the pre-v1.34 one in the #else
arm. Both get the same hook in the same position. EL8 and EL9
kernels lack copy_page_to_iter_nofault (v6.4+) and so compile the
#else arm, but hooking only that arm would leave notifications a
silent no-op on any newer kernel that later enters the build matrix.
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
@@ -33,22 +37,22 @@ 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 | 11 +++++++++++
2 files changed, 39 insertions(+)
kmod/src/file.c | 12 ++++++++++++
2 files changed, 40 insertions(+)
diff --git a/kmod/src/data.c b/kmod/src/data.c
index 7903e8d..6e3b133 100644
index 44ca4c9..3fea851 100644
--- a/kmod/src/data.c
+++ b/kmod/src/data.c
@@ -41,6 +41,7 @@
#include "msg.h"
@@ -45,6 +45,7 @@
#include "ext.h"
#include "util.h"
#include "iomap.h"
+#include "notify.h"
/*
* We want to amortize work done after dirtying the shared transaction
@@ -2207,6 +2208,32 @@ const struct address_space_operations scoutfs_file_aops = {
@@ -2254,11 +2255,38 @@ const struct address_space_operations scoutfs_file_aops = {
.write_end = scoutfs_write_end,
};
@@ -79,40 +83,46 @@ index 7903e8d..6e3b133 100644
+}
+
const struct file_operations scoutfs_file_fops = {
#ifdef KC_LINUX_HAVE_FOP_AIO_READ
.read = do_sync_read,
@@ -2219,6 +2246,7 @@ const struct file_operations scoutfs_file_fops = {
.read_iter = scoutfs_file_read_iter,
.write_iter = scoutfs_file_write_iter,
.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 15158a2..44f1bc4 100644
index 730a662..cf54871 100644
--- a/kmod/src/file.c
+++ b/kmod/src/file.c
@@ -29,6 +29,7 @@
#include "per_task.h"
#include "omap.h"
#include "quota.h"
@@ -33,6 +33,7 @@
#include "iomap.h"
#include "trans.h"
#include "msg.h"
+#include "notify.h"
#ifdef KC_LINUX_HAVE_FOP_AIO_READ
/*
@@ -84,6 +85,10 @@ out:
#ifdef KC_USE_IOMAP_FOR_IO
@@ -187,6 +188,7 @@ ssize_t scoutfs_file_read_iter(struct kiocb *iocb, struct iov_iter *to)
struct scoutfs_lock *scoutfs_inode_lock;
SCOUTFS_DECLARE_PER_TASK_ENTRY(pt_data_ent);
DECLARE_DATA_WAIT(dw);
+ loff_t start_pos = iocb->ki_pos;
int lock_flags = SCOUTFS_LKF_REFRESH_INODE;
bool is_dio = (iocb->ki_flags & IOCB_DIRECT);
bool nowait = (iocb->ki_flags & IOCB_NOWAIT);
@@ -258,6 +260,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);
+ scoutfs_ino(inode), start_pos, ret, 0);
+
return ret;
}
@@ -166,6 +171,7 @@ ssize_t scoutfs_file_read_iter(struct kiocb *iocb, struct iov_iter *to)
@@ -578,6 +584,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);
@@ -120,7 +130,7 @@ index 15158a2..44f1bc4 100644
int ret;
retry:
@@ -200,6 +206,11 @@ out:
@@ -612,6 +619,11 @@ out:
if (ret == 0)
goto retry;
}
@@ -1,4 +1,4 @@
From a227eecabc78f331fcb8c483e6051021cb44a94b Mon Sep 17 00:00:00 2001
From f95104cb0b1a46cfc2dbd7a9573eb80ff9176445 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 | 434 +++++++++++++++++++++++++
utils/notifyd/main.go | 437 +++++++++++++++++++++++++
utils/notifyd/scoutfs-notifyd@.service | 40 +++
utils/scoutfs-utils.spec.in | 8 +
6 files changed, 630 insertions(+), 3 deletions(-)
6 files changed, 633 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..9478bae
index 0000000..41b98ab
--- /dev/null
+++ b/utils/notifyd/main.go
@@ -0,0 +1,434 @@
@@ -0,0 +1,437 @@
+// Copyright (C) 2026 Versity Software, Inc. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or
@@ -396,8 +396,11 @@ index 0000000..9478bae
+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, 25, readNotifySize)
+ scoutfsIoctlMagic, 26, readNotifySize)
+)
+
+func init() {