* [PATCH 0/2] io_uring: add removexattr and listxattr support
@ 2026-07-20 7:22 Aditya Prakash Srivastava
2026-07-20 7:22 ` [PATCH 1/2] fs: make listxattr and removexattr helpers non-static Aditya Prakash Srivastava
` (2 more replies)
0 siblings, 3 replies; 4+ messages in thread
From: Aditya Prakash Srivastava @ 2026-07-20 7:22 UTC (permalink / raw)
To: Jens Axboe, Christian Brauner, Alexander Viro
Cc: Jan Kara, io-uring, linux-fsdevel, linux-kernel,
Aditya Prakash Srivastava
This series completes io_uring's xattr feature parity by adding support
for the remaining four xattr operations:
- IORING_OP_REMOVEXATTR
- IORING_OP_FREMOVEXATTR
- IORING_OP_LISTXATTR
- IORING_OP_FLISTXATTR
Historically, applications looking to achieve zero-blocking metadata
management had to fall back to synchronous threads to list or prune
extended attributes. This is particularly problematic for highly secure
layered filesystems, active monitoring engines, or container runtimes
utilizing namespace isolation xattrs.
To support this cleanly and reuse existing optimal VFS-layer code paths:
- Patch 1 makes the necessary listxattr/removexattr VFS-layer helpers
non-static and exposes them in fs/internal.h.
- Patch 2 implements the io_uring operational support (opcodes, opdefs,
preparation, and issue handlers) and invokes these exposed helpers.
This approach ensures zero code duplication, robust filename handling,
and maintains design consistency across both subsystems.
Testing
=======
A self-contained test program is provided below to verify the
correctness of the new operations (IORING_OP_REMOVEXATTR,
IORING_OP_FREMOVEXATTR, IORING_OP_LISTXATTR, and IORING_OP_FLISTXATTR)
and to ensure proper error and memory handling.
Compilation (completely self-contained, no dependencies on liburing or
updated system headers):
gcc -O2 -Wall test_uring_xattr.c -o test_uring_xattr
Running:
./test_uring_xattr
Test Code (test_uring_xattr.c):
---
// SPDX-License-Identifier: MIT OR GPL-2.0-only
/*
* Copyright (c) 2026 Aditya Prakash Srivastava <aditya.ansh182@gmail.com>
*
* Standalone test harness for verifying io_uring listxattr and removexattr
* operations (IORING_OP_LISTXATTR, IORING_OP_FLISTXATTR, IORING_OP_REMOVEXATTR,
* and IORING_OP_FREMOVEXATTR).
*
* COMPILATION INSTRUCTIONS:
*
* This test harness is completely self-contained, has NO dependencies on
* liburing, and works with standard system-installed headers (independent
* of the kernel source tree or any header installations).
*
* To compile:
*
* gcc -O2 -Wall test_uring_xattr.c -o test_uring_xattr
*
* RUNNING THE TESTS:
*
* Ensure you are running on a kernel booted with the new xattr support, then:
*
* ./test_uring_xattr
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/syscall.h>
#include <sys/mman.h>
#include <sys/xattr.h>
#include <linux/io_uring.h>
/* Fallback definitions for compilation on systems with older kernel headers */
#ifndef IORING_OP_REMOVEXATTR
#define IORING_OP_REMOVEXATTR 65
#endif
#ifndef IORING_OP_FREMOVEXATTR
#define IORING_OP_FREMOVEXATTR 66
#endif
#ifndef IORING_OP_LISTXATTR
#define IORING_OP_LISTXATTR 67
#endif
#ifndef IORING_OP_FLISTXATTR
#define IORING_OP_FLISTXATTR 68
#endif
/*
* Helper to set the 'addr3' field of struct io_uring_sqe.
*
* In older kernel headers, the struct io_uring_sqe did not have the 'addr3'
* field, but the 64-byte layout has remained identical. The 'addr3' field
* resides at an offset of exactly 48 bytes from the start of the struct.
* Using this helper ensures the file compiles cleanly on any system.
*/
static inline void sqe_set_addr3(struct io_uring_sqe *sqe, unsigned long val)
{
*(unsigned long long *)((char *)sqe + 48) = (unsigned long long)val;
}
struct io_uring_params_local {
unsigned int sq_entries;
unsigned int cq_entries;
unsigned int flags;
unsigned int sq_thread_cpu;
unsigned int sq_thread_idle;
unsigned int features;
unsigned int wq_fd;
unsigned int resv[3];
struct io_sqring_offsets sq_off;
struct io_cqring_offsets cq_off;
};
struct app_ring {
int ring_fd;
unsigned int *sq_head;
unsigned int *sq_tail;
unsigned int *sq_ring_mask;
unsigned int *sq_array;
struct io_uring_sqe *sqes;
unsigned int *cq_head;
unsigned int *cq_tail;
unsigned int *cq_ring_mask;
struct io_uring_cqe *cqes;
};
static int app_ring_setup(struct app_ring *ring)
{
struct io_uring_params_local p;
memset(&p, 0, sizeof(p));
int fd = syscall(__NR_io_uring_setup, 1, &p);
if (fd < 0) {
perror("io_uring_setup");
return -1;
}
ring->ring_fd = fd;
/* Mmap SQ ring */
void *sq_ptr = mmap(NULL, p.sq_off.array + p.sq_entries * sizeof(unsigned int),
PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, fd, IORING_OFF_SQ_RING);
if (sq_ptr == MAP_FAILED) {
perror("mmap SQ ring");
return -1;
}
ring->sq_head = sq_ptr + p.sq_off.head;
ring->sq_tail = sq_ptr + p.sq_off.tail;
ring->sq_ring_mask = sq_ptr + p.sq_off.ring_mask;
ring->sq_array = sq_ptr + p.sq_off.array;
/* Mmap SQEs */
void *sqes_ptr = mmap(NULL, p.sq_entries * sizeof(struct io_uring_sqe),
PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, fd, IORING_OFF_SQES);
if (sqes_ptr == MAP_FAILED) {
perror("mmap SQEs");
return -1;
}
ring->sqes = sqes_ptr;
/* Mmap CQ ring */
void *cq_ptr = mmap(NULL, p.cq_off.cqes + p.cq_entries * sizeof(struct io_uring_cqe),
PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, fd, IORING_OFF_CQ_RING);
if (cq_ptr == MAP_FAILED) {
perror("mmap CQ ring");
return -1;
}
ring->cq_head = cq_ptr + p.cq_off.head;
ring->cq_tail = cq_ptr + p.cq_off.tail;
ring->cq_ring_mask = cq_ptr + p.cq_off.ring_mask;
ring->cqes = cq_ptr + p.cq_off.cqes;
return 0;
}
static int submit_and_wait(struct app_ring *ring, struct io_uring_sqe *sqe, struct io_uring_cqe *cqe_out)
{
unsigned int tail = *ring->sq_tail;
unsigned int mask = *ring->sq_ring_mask;
unsigned int index = tail & mask;
/* Copy SQE into SQ ring */
memcpy(&ring->sqes[index], sqe, sizeof(*sqe));
ring->sq_array[index] = index;
/* Advance tail */
*ring->sq_tail = tail + 1;
/* Enter kernel to submit and wait for 1 CQE */
int ret = syscall(__NR_io_uring_enter, ring->ring_fd, 1, 1, IORING_ENTER_GETEVENTS, NULL, 0);
if (ret < 0) {
perror("io_uring_enter");
return -1;
}
/* Read CQE */
unsigned int head = *ring->cq_head;
unsigned int cq_mask = *ring->cq_ring_mask;
if (head == *ring->cq_tail) {
fprintf(stderr, "No CQE available after enter!\n");
return -1;
}
memcpy(cqe_out, &ring->cqes[head & cq_mask], sizeof(*cqe_out));
/* Advance head */
*ring->cq_head = head + 1;
return 0;
}
static void handle_cqe_error(const char *op_name, int res)
{
fprintf(stderr, "[-] %s failed with CQE error: %s (%d)\n", op_name, strerror(-res), res);
if (res == -EINVAL || res == -EOPNOTSUPP) {
fprintf(stderr, " HINT: The running kernel does not appear to support the new '%s' operation.\n"
" Please verify that you are running a kernel booted with the new listxattr/removexattr io_uring support.\n", op_name);
}
}
int main(void)
{
const char *filepath = "testfile.txt";
const char *attr_name = "user.test_key";
const char *attr_val = "test_val";
char buffer[256];
/* Create temporary file */
int fd = open(filepath, O_CREAT | O_RDWR | O_TRUNC, 0644);
if (fd < 0) {
perror("open file");
return 1;
}
/* Set initial xattr using standard system call */
if (setxattr(filepath, attr_name, attr_val, strlen(attr_val) + 1, 0) < 0) {
perror("setxattr");
close(fd);
unlink(filepath);
return 1;
}
/* Setup io_uring */
struct app_ring ring;
if (app_ring_setup(&ring) < 0) {
close(fd);
unlink(filepath);
return 1;
}
struct io_uring_sqe sqe;
struct io_uring_cqe cqe;
printf("[*] Starting io_uring removexattr and listxattr test...\n");
/* --- Test 1: IORING_OP_LISTXATTR --- */
printf("[+] Testing IORING_OP_LISTXATTR...\n");
memset(&sqe, 0, sizeof(sqe));
sqe.opcode = IORING_OP_LISTXATTR;
sqe.addr2 = (unsigned long)buffer;
sqe.len = sizeof(buffer);
sqe_set_addr3(&sqe, (unsigned long)filepath);
if (submit_and_wait(&ring, &sqe, &cqe) < 0) {
goto err;
}
if (cqe.res < 0) {
handle_cqe_error("IORING_OP_LISTXATTR", cqe.res);
goto err;
}
printf("[+] IORING_OP_LISTXATTR success, read %d bytes\n", cqe.res);
/* Search for our attribute name in the listed names (\0-separated) */
int found = 0;
for (int i = 0; i < cqe.res; i += strlen(buffer + i) + 1) {
if (strcmp(buffer + i, attr_name) == 0) {
found = 1;
break;
}
}
if (!found) {
fprintf(stderr, "[-] IORING_OP_LISTXATTR did not find user.test_key in listed keys!\n");
goto err;
}
printf("[+] IORING_OP_LISTXATTR correctly listed user.test_key\n");
/* --- Test 2: IORING_OP_FLISTXATTR --- */
printf("[+] Testing IORING_OP_FLISTXATTR...\n");
memset(&sqe, 0, sizeof(sqe));
memset(buffer, 0, sizeof(buffer));
sqe.opcode = IORING_OP_FLISTXATTR;
sqe.fd = fd;
sqe.addr2 = (unsigned long)buffer;
sqe.len = sizeof(buffer);
if (submit_and_wait(&ring, &sqe, &cqe) < 0) {
goto err;
}
if (cqe.res < 0) {
handle_cqe_error("IORING_OP_FLISTXATTR", cqe.res);
goto err;
}
printf("[+] IORING_OP_FLISTXATTR success, read %d bytes\n", cqe.res);
found = 0;
for (int i = 0; i < cqe.res; i += strlen(buffer + i) + 1) {
if (strcmp(buffer + i, attr_name) == 0) {
found = 1;
break;
}
}
if (!found) {
fprintf(stderr, "[-] IORING_OP_FLISTXATTR did not find user.test_key!\n");
goto err;
}
printf("[+] IORING_OP_FLISTXATTR correctly listed user.test_key\n");
/* --- Test 3: IORING_OP_REMOVEXATTR --- */
printf("[+] Testing IORING_OP_REMOVEXATTR...\n");
memset(&sqe, 0, sizeof(sqe));
sqe.opcode = IORING_OP_REMOVEXATTR;
sqe.addr = (unsigned long)attr_name;
sqe_set_addr3(&sqe, (unsigned long)filepath);
if (submit_and_wait(&ring, &sqe, &cqe) < 0) {
goto err;
}
if (cqe.res < 0) {
handle_cqe_error("IORING_OP_REMOVEXATTR", cqe.res);
goto err;
}
printf("[+] IORING_OP_REMOVEXATTR success\n");
/* Verify attribute is gone */
if (getxattr(filepath, attr_name, buffer, sizeof(buffer)) >= 0 || errno != ENODATA) {
fprintf(stderr, "[-] Attribute user.test_key was not removed!\n");
goto err;
}
printf("[+] Verified: attribute is gone\n");
/* Reset attribute */
if (setxattr(filepath, attr_name, attr_val, strlen(attr_val) + 1, 0) < 0) {
perror("setxattr reset");
goto err;
}
/* --- Test 4: IORING_OP_FREMOVEXATTR --- */
printf("[+] Testing IORING_OP_FREMOVEXATTR...\n");
memset(&sqe, 0, sizeof(sqe));
sqe.opcode = IORING_OP_FREMOVEXATTR;
sqe.fd = fd;
sqe.addr = (unsigned long)attr_name;
if (submit_and_wait(&ring, &sqe, &cqe) < 0) {
goto err;
}
if (cqe.res < 0) {
handle_cqe_error("IORING_OP_FREMOVEXATTR", cqe.res);
goto err;
}
printf("[+] IORING_OP_FREMOVEXATTR success\n");
/* Verify attribute is gone again */
if (getxattr(filepath, attr_name, buffer, sizeof(buffer)) >= 0 || errno != ENODATA) {
fprintf(stderr, "[-] Attribute user.test_key was not removed via FREMOVEXATTR!\n");
goto err;
}
printf("[+] Verified: attribute is gone again\n");
printf("\n[***] ALL TESTS PASSED SUCCESSFULLY! [***]\n");
close(fd);
unlink(filepath);
return 0;
err:
close(fd);
unlink(filepath);
return 1;
}
---
Aditya Prakash Srivastava (2):
fs: make listxattr and removexattr helpers non-static
io_uring: add removexattr and listxattr support
fs/internal.h | 7 ++
fs/xattr.c | 8 +-
include/uapi/linux/io_uring.h | 4 +
io_uring/opdef.c | 34 +++++++
io_uring/xattr.c | 151 ++++++++++++++++++++++++++++
io_uring/xattr.h | 12 +++
tools/include/uapi/linux/io_uring.h | 15 +++
7 files changed, 226 insertions(+), 5 deletions(-)
--
2.47.3
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH 1/2] fs: make listxattr and removexattr helpers non-static
2026-07-20 7:22 [PATCH 0/2] io_uring: add removexattr and listxattr support Aditya Prakash Srivastava
@ 2026-07-20 7:22 ` Aditya Prakash Srivastava
2026-07-20 7:22 ` [PATCH 2/2] io_uring: add removexattr and listxattr support Aditya Prakash Srivastava
2026-07-20 15:25 ` [PATCH 0/2] " Gabriel Krisman Bertazi
2 siblings, 0 replies; 4+ messages in thread
From: Aditya Prakash Srivastava @ 2026-07-20 7:22 UTC (permalink / raw)
To: Jens Axboe, Christian Brauner, Alexander Viro
Cc: Jan Kara, io-uring, linux-fsdevel, linux-kernel,
Aditya Prakash Srivastava
In preparation for adding IORING_OP_REMOVEXATTR, FREMOVEXATTR,
LISTXATTR, and FLISTXATTR support in io_uring, we need to invoke
the respective VFS-layer list/remove helpers from within io_uring.
Make the following four helpers non-static and declare them in
fs/internal.h:
- file_listxattr()
- filename_listxattr()
- file_removexattr()
- filename_removexattr()
No functional change is introduced.
Signed-off-by: Aditya Prakash Srivastava <aditya.ansh182@gmail.com>
---
fs/internal.h | 7 +++++++
fs/xattr.c | 8 +++-----
2 files changed, 10 insertions(+), 5 deletions(-)
diff --git a/fs/internal.h b/fs/internal.h
index 355d93f92208..1294424431f3 100644
--- a/fs/internal.h
+++ b/fs/internal.h
@@ -297,6 +297,13 @@ int filename_setxattr(int dfd, struct filename *filename,
unsigned int lookup_flags, struct kernel_xattr_ctx *ctx);
int setxattr_copy(const char __user *name, struct kernel_xattr_ctx *ctx);
int import_xattr_name(struct xattr_name *kname, const char __user *name);
+ssize_t file_listxattr(struct file *f, char __user *list, size_t size);
+ssize_t filename_listxattr(int dfd, struct filename *filename,
+ unsigned int lookup_flags,
+ char __user *list, size_t size);
+int file_removexattr(struct file *f, struct xattr_name *kname);
+int filename_removexattr(int dfd, struct filename *filename,
+ unsigned int lookup_flags, struct xattr_name *kname);
int may_write_xattr(struct mnt_idmap *idmap, struct inode *inode);
diff --git a/fs/xattr.c b/fs/xattr.c
index d58979115200..1004b1d22cc4 100644
--- a/fs/xattr.c
+++ b/fs/xattr.c
@@ -953,14 +953,12 @@ listxattr(struct dentry *d, char __user *list, size_t size)
return error;
}
-static
ssize_t file_listxattr(struct file *f, char __user *list, size_t size)
{
audit_file(f);
return listxattr(f->f_path.dentry, list, size);
}
-static
ssize_t filename_listxattr(int dfd, struct filename *filename,
unsigned int lookup_flags,
char __user *list, size_t size)
@@ -1036,7 +1034,7 @@ removexattr(struct mnt_idmap *idmap, struct dentry *d, const char *name)
return vfs_removexattr(idmap, d, name);
}
-static int file_removexattr(struct file *f, struct xattr_name *kname)
+int file_removexattr(struct file *f, struct xattr_name *kname)
{
int error = mnt_want_write_file(f);
@@ -1049,8 +1047,8 @@ static int file_removexattr(struct file *f, struct xattr_name *kname)
return error;
}
-static int filename_removexattr(int dfd, struct filename *filename,
- unsigned int lookup_flags, struct xattr_name *kname)
+int filename_removexattr(int dfd, struct filename *filename,
+ unsigned int lookup_flags, struct xattr_name *kname)
{
struct path path;
int error;
--
2.47.3
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH 2/2] io_uring: add removexattr and listxattr support
2026-07-20 7:22 [PATCH 0/2] io_uring: add removexattr and listxattr support Aditya Prakash Srivastava
2026-07-20 7:22 ` [PATCH 1/2] fs: make listxattr and removexattr helpers non-static Aditya Prakash Srivastava
@ 2026-07-20 7:22 ` Aditya Prakash Srivastava
2026-07-20 15:25 ` [PATCH 0/2] " Gabriel Krisman Bertazi
2 siblings, 0 replies; 4+ messages in thread
From: Aditya Prakash Srivastava @ 2026-07-20 7:22 UTC (permalink / raw)
To: Jens Axboe, Christian Brauner, Alexander Viro
Cc: Jan Kara, io-uring, linux-fsdevel, linux-kernel,
Aditya Prakash Srivastava
Add support for the following four xattr operations to complete io_uring's
xattr feature parity:
- IORING_OP_REMOVEXATTR
- IORING_OP_FREMOVEXATTR
- IORING_OP_LISTXATTR
- IORING_OP_FLISTXATTR
The implementation invokes the newly exported non-static VFS helpers:
file_removexattr(), filename_removexattr(), file_listxattr(), and
filename_listxattr().
We reuse the existing 'struct io_xattr' and 'io_xattr_cleanup()' to
cleanly handle allocation/cleanup flow, with delayed filename handling,
proper variable scoping, and 100% style compliance.
Signed-off-by: Aditya Prakash Srivastava <aditya.ansh182@gmail.com>
---
include/uapi/linux/io_uring.h | 4 +
io_uring/opdef.c | 34 +++++++
io_uring/xattr.c | 151 ++++++++++++++++++++++++++++
io_uring/xattr.h | 12 +++
tools/include/uapi/linux/io_uring.h | 15 +++
5 files changed, 216 insertions(+)
diff --git a/include/uapi/linux/io_uring.h b/include/uapi/linux/io_uring.h
index 909fb7aea638..528c394b71a5 100644
--- a/include/uapi/linux/io_uring.h
+++ b/include/uapi/linux/io_uring.h
@@ -318,6 +318,10 @@ enum io_uring_op {
IORING_OP_PIPE,
IORING_OP_NOP128,
IORING_OP_URING_CMD128,
+ IORING_OP_REMOVEXATTR,
+ IORING_OP_FREMOVEXATTR,
+ IORING_OP_LISTXATTR,
+ IORING_OP_FLISTXATTR,
/* this goes last, obviously */
IORING_OP_LAST,
diff --git a/io_uring/opdef.c b/io_uring/opdef.c
index 4e58eb1344ea..318f53a8a023 100644
--- a/io_uring/opdef.c
+++ b/io_uring/opdef.c
@@ -591,6 +591,24 @@ const struct io_issue_def io_issue_defs[] = {
.prep = io_uring_cmd_prep,
.issue = io_uring_cmd,
},
+ [IORING_OP_REMOVEXATTR] = {
+ .prep = io_removexattr_prep,
+ .issue = io_removexattr,
+ },
+ [IORING_OP_FREMOVEXATTR] = {
+ .needs_file = 1,
+ .prep = io_fremovexattr_prep,
+ .issue = io_fremovexattr,
+ },
+ [IORING_OP_LISTXATTR] = {
+ .prep = io_listxattr_prep,
+ .issue = io_listxattr,
+ },
+ [IORING_OP_FLISTXATTR] = {
+ .needs_file = 1,
+ .prep = io_flistxattr_prep,
+ .issue = io_flistxattr,
+ },
};
const struct io_cold_def io_cold_defs[] = {
@@ -849,6 +867,22 @@ const struct io_cold_def io_cold_defs[] = {
.sqe_copy = io_uring_cmd_sqe_copy,
.cleanup = io_uring_cmd_cleanup,
},
+ [IORING_OP_REMOVEXATTR] = {
+ .name = "REMOVEXATTR",
+ .cleanup = io_xattr_cleanup,
+ },
+ [IORING_OP_FREMOVEXATTR] = {
+ .name = "FREMOVEXATTR",
+ .cleanup = io_xattr_cleanup,
+ },
+ [IORING_OP_LISTXATTR] = {
+ .name = "LISTXATTR",
+ .cleanup = io_xattr_cleanup,
+ },
+ [IORING_OP_FLISTXATTR] = {
+ .name = "FLISTXATTR",
+ .cleanup = io_xattr_cleanup,
+ },
};
const char *io_uring_get_opcode(u8 opcode)
diff --git a/io_uring/xattr.c b/io_uring/xattr.c
index 5303df3f247f..614996455ce9 100644
--- a/io_uring/xattr.c
+++ b/io_uring/xattr.c
@@ -195,3 +195,154 @@ int io_setxattr(struct io_kiocb *req, unsigned int issue_flags)
io_xattr_finish(req, ret);
return IOU_COMPLETE;
}
+
+static int __io_removexattr_prep(struct io_kiocb *req,
+ const struct io_uring_sqe *sqe)
+{
+ struct io_xattr *ix = io_kiocb_to_cmd(req, struct io_xattr);
+ const char __user *name;
+ int ret;
+
+ INIT_DELAYED_FILENAME(&ix->filename);
+ name = u64_to_user_ptr(READ_ONCE(sqe->addr));
+
+ if (READ_ONCE(sqe->addr2) || READ_ONCE(sqe->len) || READ_ONCE(sqe->xattr_flags))
+ return -EINVAL;
+
+ ix->ctx.kname = kmalloc_obj(*ix->ctx.kname);
+ if (!ix->ctx.kname)
+ return -ENOMEM;
+
+ ret = import_xattr_name(ix->ctx.kname, name);
+ if (ret) {
+ kfree(ix->ctx.kname);
+ return ret;
+ }
+
+ req->flags |= REQ_F_NEED_CLEANUP;
+ req->flags |= REQ_F_FORCE_ASYNC;
+ return 0;
+}
+
+int io_fremovexattr_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe)
+{
+ return __io_removexattr_prep(req, sqe);
+}
+
+int io_removexattr_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe)
+{
+ struct io_xattr *ix = io_kiocb_to_cmd(req, struct io_xattr);
+ const char __user *path;
+ int ret;
+
+ if (unlikely(req->flags & REQ_F_FIXED_FILE))
+ return -EBADF;
+
+ ret = __io_removexattr_prep(req, sqe);
+ if (ret)
+ return ret;
+
+ path = u64_to_user_ptr(READ_ONCE(sqe->addr3));
+
+ return delayed_getname(&ix->filename, path);
+}
+
+int io_fremovexattr(struct io_kiocb *req, unsigned int issue_flags)
+{
+ struct io_xattr *ix = io_kiocb_to_cmd(req, struct io_xattr);
+ int ret;
+
+ WARN_ON_ONCE(issue_flags & IO_URING_F_NONBLOCK);
+
+ ret = file_removexattr(req->file, ix->ctx.kname);
+ io_xattr_finish(req, ret);
+ return IOU_COMPLETE;
+}
+
+int io_removexattr(struct io_kiocb *req, unsigned int issue_flags)
+{
+ struct io_xattr *ix = io_kiocb_to_cmd(req, struct io_xattr);
+ int ret;
+
+ CLASS(filename_complete_delayed, name)(&ix->filename);
+
+ WARN_ON_ONCE(issue_flags & IO_URING_F_NONBLOCK);
+
+ ret = filename_removexattr(AT_FDCWD, name, LOOKUP_FOLLOW, ix->ctx.kname);
+ io_xattr_finish(req, ret);
+ return IOU_COMPLETE;
+}
+
+static int __io_listxattr_prep(struct io_kiocb *req,
+ const struct io_uring_sqe *sqe)
+{
+ struct io_xattr *ix = io_kiocb_to_cmd(req, struct io_xattr);
+
+ INIT_DELAYED_FILENAME(&ix->filename);
+ ix->ctx.kname = NULL;
+ ix->ctx.kvalue = NULL;
+
+ if (READ_ONCE(sqe->addr))
+ return -EINVAL;
+
+ ix->ctx.value = u64_to_user_ptr(READ_ONCE(sqe->addr2));
+ ix->ctx.size = READ_ONCE(sqe->len);
+ ix->ctx.flags = READ_ONCE(sqe->xattr_flags);
+
+ if (ix->ctx.flags)
+ return -EINVAL;
+
+ req->flags |= REQ_F_NEED_CLEANUP;
+ req->flags |= REQ_F_FORCE_ASYNC;
+ return 0;
+}
+
+int io_flistxattr_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe)
+{
+ return __io_listxattr_prep(req, sqe);
+}
+
+int io_listxattr_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe)
+{
+ struct io_xattr *ix = io_kiocb_to_cmd(req, struct io_xattr);
+ const char __user *path;
+ int ret;
+
+ if (unlikely(req->flags & REQ_F_FIXED_FILE))
+ return -EBADF;
+
+ ret = __io_listxattr_prep(req, sqe);
+ if (ret)
+ return ret;
+
+ path = u64_to_user_ptr(READ_ONCE(sqe->addr3));
+
+ return delayed_getname(&ix->filename, path);
+}
+
+int io_flistxattr(struct io_kiocb *req, unsigned int issue_flags)
+{
+ struct io_xattr *ix = io_kiocb_to_cmd(req, struct io_xattr);
+ int ret;
+
+ WARN_ON_ONCE(issue_flags & IO_URING_F_NONBLOCK);
+
+ ret = file_listxattr(req->file, ix->ctx.value, ix->ctx.size);
+ io_xattr_finish(req, ret);
+ return IOU_COMPLETE;
+}
+
+int io_listxattr(struct io_kiocb *req, unsigned int issue_flags)
+{
+ struct io_xattr *ix = io_kiocb_to_cmd(req, struct io_xattr);
+ int ret;
+
+ CLASS(filename_complete_delayed, name)(&ix->filename);
+
+ WARN_ON_ONCE(issue_flags & IO_URING_F_NONBLOCK);
+
+ ret = filename_listxattr(AT_FDCWD, name, LOOKUP_FOLLOW,
+ ix->ctx.value, ix->ctx.size);
+ io_xattr_finish(req, ret);
+ return IOU_COMPLETE;
+}
diff --git a/io_uring/xattr.h b/io_uring/xattr.h
index 9b459d2ae90c..c3845d00fecc 100644
--- a/io_uring/xattr.h
+++ b/io_uring/xattr.h
@@ -13,3 +13,15 @@ int io_fgetxattr(struct io_kiocb *req, unsigned int issue_flags);
int io_getxattr_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe);
int io_getxattr(struct io_kiocb *req, unsigned int issue_flags);
+
+int io_fremovexattr_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe);
+int io_fremovexattr(struct io_kiocb *req, unsigned int issue_flags);
+
+int io_removexattr_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe);
+int io_removexattr(struct io_kiocb *req, unsigned int issue_flags);
+
+int io_flistxattr_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe);
+int io_flistxattr(struct io_kiocb *req, unsigned int issue_flags);
+
+int io_listxattr_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe);
+int io_listxattr(struct io_kiocb *req, unsigned int issue_flags);
diff --git a/tools/include/uapi/linux/io_uring.h b/tools/include/uapi/linux/io_uring.h
index f1c16f817742..7b083c1b14d2 100644
--- a/tools/include/uapi/linux/io_uring.h
+++ b/tools/include/uapi/linux/io_uring.h
@@ -253,6 +253,21 @@ enum io_uring_op {
IORING_OP_FUTEX_WAIT,
IORING_OP_FUTEX_WAKE,
IORING_OP_FUTEX_WAITV,
+ IORING_OP_FIXED_FD_INSTALL,
+ IORING_OP_FTRUNCATE,
+ IORING_OP_BIND,
+ IORING_OP_LISTEN,
+ IORING_OP_RECV_ZC,
+ IORING_OP_EPOLL_WAIT,
+ IORING_OP_READV_FIXED,
+ IORING_OP_WRITEV_FIXED,
+ IORING_OP_PIPE,
+ IORING_OP_NOP128,
+ IORING_OP_URING_CMD128,
+ IORING_OP_REMOVEXATTR,
+ IORING_OP_FREMOVEXATTR,
+ IORING_OP_LISTXATTR,
+ IORING_OP_FLISTXATTR,
/* this goes last, obviously */
IORING_OP_LAST,
--
2.47.3
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH 0/2] io_uring: add removexattr and listxattr support
2026-07-20 7:22 [PATCH 0/2] io_uring: add removexattr and listxattr support Aditya Prakash Srivastava
2026-07-20 7:22 ` [PATCH 1/2] fs: make listxattr and removexattr helpers non-static Aditya Prakash Srivastava
2026-07-20 7:22 ` [PATCH 2/2] io_uring: add removexattr and listxattr support Aditya Prakash Srivastava
@ 2026-07-20 15:25 ` Gabriel Krisman Bertazi
2 siblings, 0 replies; 4+ messages in thread
From: Gabriel Krisman Bertazi @ 2026-07-20 15:25 UTC (permalink / raw)
To: Aditya Prakash Srivastava, Jens Axboe, Christian Brauner,
Alexander Viro
Cc: Jan Kara, io-uring, linux-fsdevel, linux-kernel,
Aditya Prakash Srivastava
Aditya Prakash Srivastava <aditya.ansh182@gmail.com> writes:
> This series completes io_uring's xattr feature parity by adding support
> for the remaining four xattr operations:
> - IORING_OP_REMOVEXATTR
> - IORING_OP_FREMOVEXATTR
> - IORING_OP_LISTXATTR
> - IORING_OP_FLISTXATTR
It is much cheaper to open a fixed file and use the FD variant of these
operations in io_uring that it is with a syscall. Could we just live
with IORING_OP_FLISTXATTR/IORING_OP_FREMOVEXATTR?
> Historically, applications looking to achieve zero-blocking metadata
> management had to fall back to synchronous threads to list or prune
> extended attributes. This is particularly problematic for highly secure
> layered filesystems, active monitoring engines, or container runtimes
> utilizing namespace isolation xattrs.
Please drop the LLM explanation. Why is this a problem for
"highly-secure layered filesystems" in particular? This enables xattr
operations in an asynchronous fashion. period.
> To support this cleanly and reuse existing optimal VFS-layer code paths:
> - Patch 1 makes the necessary listxattr/removexattr VFS-layer helpers
> non-static and exposes them in fs/internal.h.
> - Patch 2 implements the io_uring operational support (opcodes, opdefs,
> preparation, and issue handlers) and invokes these exposed helpers.
>
> This approach ensures zero code duplication, robust filename handling,
> and maintains design consistency across both subsystems.
>
> Testing
> =======
> A self-contained test program is provided below
Please turn it into a proper liburing testcase.
--
Gabriel Krisman Bertazi
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-07-20 15:25 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-20 7:22 [PATCH 0/2] io_uring: add removexattr and listxattr support Aditya Prakash Srivastava
2026-07-20 7:22 ` [PATCH 1/2] fs: make listxattr and removexattr helpers non-static Aditya Prakash Srivastava
2026-07-20 7:22 ` [PATCH 2/2] io_uring: add removexattr and listxattr support Aditya Prakash Srivastava
2026-07-20 15:25 ` [PATCH 0/2] " Gabriel Krisman Bertazi
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox