Skip to content

Instantly share code, notes, and snippets.

@Jotschi
Last active April 3, 2026 09:27
Show Gist options
  • Select an option

  • Save Jotschi/3aed062a47d14e0fc96f6c730f70f6f6 to your computer and use it in GitHub Desktop.

Select an option

Save Jotschi/3aed062a47d14e0fc96f6c730f70f6f6 to your computer and use it in GitHub Desktop.

Problem chain

  1. When an NFS client mounts a FUSE-backed export, the NFS server tells the client whether the filesystem supports user xattrs.
  2. The server does this by calling xattr_supports_user_prefix() in xattr.c, which loops over the superblock's xattr handlers looking for one whose prefix starts with "user.".
  3. FUSE registers a single catch-all handler with .prefix = "" (empty string) in xattr.c. This handler delegates everything to userspace — it handles all namespaces, including user.*.
  4. But strncmp("", "user.", 5) doesn't match, so xattr_supports_user_prefix() returns -EOPNOTSUPP.
  5. The NFS server encodes Xattr_support = 0 in nfsd4_encode_fattr4_xattr_support() (nfs4xdr.c), and the NFS client disables all xattr operations.

The fix (fs/xattr.c)

In xattr_supports_user_prefix(), after the existing strncmp check, we add: if the handler's prefix is empty (!*prefix), it's a catch-all that covers all namespaces including user.* — return success.

Why you can't just add a "user." handler in xattr.c

xattr_resolve_name() in xattr.c strips the matched prefix from the xattr name before passing it to the handler's .get/.set callbacks. With the existing catch-all (.prefix = ""), nothing is stripped — "user.myattr" reaches the FUSE daemon as "user.myattr".

If you add a handler with .prefix = "user.", xattr_resolve_name() strips "user." and passes just "myattr" to fuse_xattr_get/fuse_xattr_set, which then sends "myattr" to the FUSE daemon. The daemon expects the full name "user.myattr" — so the operation silently breaks or fails.

Methods and files involved

File Function Role
xattr.c xattr_supports_user_prefix() Fixed here. Checks if a filesystem supports user.* xattrs
xattr.c xattr_resolve_name() Matches an xattr name to a handler and strips the prefix — why adding a FUSE handler breaks things
xattr.c fuse_xattr_handler (.prefix = "") The catch-all handler that wasn't being recognized
nfs4xdr.c nfsd4_encode_fattr4_xattr_support() NFS server calls xattr_supports_user_prefix() and reports result to client
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment