Skip to contentSkip to conversation

io_uring: add support for open/close

PATCH In review Jens Axboe 5 participants 37 messages Jan 7, 2020 – Jan 25, 2020

Subsystems (3)
lore
Share & tools
lore

Jens Axboe

author maintainer
#0 lore raw

v2 cover io_uring: add support for open/close

Sending this out separately, as I rebased it on top of the work.openat2 branch from Al to resolve some of the conflicts with the differences in how open flags are built.

Al, you had objections on patch 1 in this series. Are you fine with this version?

Signature · 1 line
Jens Axboe
author maintainer
#1 lore raw

PATCH v2 1/6 fs: add namei support for doing a non-blocking path lookup

If the fast lookup fails, then return -EAGAIN to have the caller retry the path lookup. Assume that a dentry having any of:

->d_revalidate()
->d_automount()
->d_manage()

could block in those callbacks. Preemptively return -EAGAIN if any of these are present.

This is in preparation for supporting non-blocking open.

---
 fs/namei.c            | 21 ++++++++++++++++++++-
 include/linux/namei.h |  2 ++
 2 files changed, 22 insertions(+), 1 deletion(-)
fs/namei.c +20 -1
index b367fdb91682..ed108a41634f 100644
@@ -1641,6 +1641,17 @@ static struct dentry *__lookup_hash(const struct qstr *name,
 	return dentry;
 }
 
+static inline bool lookup_could_block(struct dentry *dentry, unsigned int flags)
+{
+	const struct dentry_operations *ops = dentry->d_op;
+
+	if (!ops || !(flags & LOOKUP_NONBLOCK))
+		return 0;
+
+	/* assume these dentry ops may block */
+	return ops->d_revalidate || ops->d_automount || ops->d_manage;
+}
+
 static int lookup_fast(struct nameidata *nd,
 		       struct path *path, struct inode **inode,
 		       unsigned *seqp)
@@ -1665,6 +1676,9 @@ static int lookup_fast(struct nameidata *nd,
 			return 0;
 		}
 
+		if (unlikely(lookup_could_block(dentry, nd->flags)))
+			return -EAGAIN;
+
 		/*
 		 * This sequence count validates that the inode matches
 		 * the dentry name information from lookup.
@@ -1707,7 +1721,10 @@ static int lookup_fast(struct nameidata *nd,
 		dentry = __d_lookup(parent, &nd->last);
 		if (unlikely(!dentry))
 			return 0;
-		status = d_revalidate(dentry, nd->flags);
+		if (unlikely(lookup_could_block(dentry, nd->flags)))
+			status = -EAGAIN;
+		else
+			status = d_revalidate(dentry, nd->flags);
 	}
 	if (unlikely(status <= 0)) {
 		if (!status)
@@ -1912,6 +1929,8 @@ static int walk_component(struct nameidata *nd, int flags)
 	if (unlikely(err <= 0)) {
 		if (err < 0)
 			return err;
+		if (nd->flags & LOOKUP_NONBLOCK)
+			return -EAGAIN;
 		path.dentry = lookup_slow(&nd->last, nd->path.dentry,
 					  nd->flags);
 		if (IS_ERR(path.dentry))
include/linux/namei.h +2 -0
index 4e77068f7a1a..392eb439f88b 100644
@@ -49,6 +49,8 @@ enum {LAST_NORM, LAST_ROOT, LAST_DOT, LAST_DOTDOT, LAST_BIND};
 /* LOOKUP_* flags which do scope-related checks based on the dirfd. */
 #define LOOKUP_IS_SCOPED (LOOKUP_BENEATH | LOOKUP_IN_ROOT)
 
+#define LOOKUP_NONBLOCK		0x200000 /* don't block for lookup */
+
 extern int path_pts(struct path *path);
 
 extern int user_path_at_empty(int, const char __user *, unsigned, struct path *, int *empty);
Signature · 1 line
2.24.1
#2 lore raw

Re: [PATCH 1/6] fs: add namei support for doing a non-blocking path lookup

Quoted 27 lines · Jens Axboe

If the fast lookup fails, then return -EAGAIN to have the caller retry the path lookup. Assume that a dentry having any of:

->d_revalidate()
->d_automount()
->d_manage()

could block in those callbacks. Preemptively return -EAGAIN if any of these are present.

This is in preparation for supporting non-blocking open.

---
 fs/namei.c            | 21 ++++++++++++++++++++-
 include/linux/namei.h |  2 ++
 2 files changed, 22 insertions(+), 1 deletion(-)
fs/namei.c +1 -0
index b367fdb91682..ed108a41634f 100644
@@ -1641,6 +1641,17 @@ static struct dentry *__lookup_hash(const struct qstr *name,
 	return dentry;
 }
 
+static inline bool lookup_could_block(struct dentry *dentry, unsigned int flags)

fs/namei.c static struct dentry *__lookup_hash(const struct qstr *name, · line 1654

Quoted 10 lines
fs/namei.c +10 -0
+{
+	const struct dentry_operations *ops = dentry->d_op;
+
+	if (!ops || !(flags & LOOKUP_NONBLOCK))
+		return 0;
+
+	/* assume these dentry ops may block */
+	return ops->d_revalidate || ops->d_automount || ops->d_manage;
+}
+

d_revalidate shouldn't block if LOOKUP_RCU is set.

Quoted 47 lines
fs/namei.c +9 -1
 static int lookup_fast(struct nameidata *nd,
 		       struct path *path, struct inode **inode,
 		       unsigned *seqp)
@@ -1665,6 +1676,9 @@ static int lookup_fast(struct nameidata *nd,
 			return 0;
 		}
 
+		if (unlikely(lookup_could_block(dentry, nd->flags)))
+			return -EAGAIN;
+
 		/*
 		 * This sequence count validates that the inode matches
 		 * the dentry name information from lookup.
@@ -1707,7 +1721,10 @@ static int lookup_fast(struct nameidata *nd,
 		dentry = __d_lookup(parent, &nd->last);
 		if (unlikely(!dentry))
 			return 0;
-		status = d_revalidate(dentry, nd->flags);
+		if (unlikely(lookup_could_block(dentry, nd->flags)))
+			status = -EAGAIN;
+		else
+			status = d_revalidate(dentry, nd->flags);
 	}
 	if (unlikely(status <= 0)) {
 		if (!status)
@@ -1912,6 +1929,8 @@ static int walk_component(struct nameidata *nd, int flags)
 	if (unlikely(err <= 0)) {
 		if (err < 0)
 			return err;
+		if (nd->flags & LOOKUP_NONBLOCK)
+			return -EAGAIN;
 		path.dentry = lookup_slow(&nd->last, nd->path.dentry,
 					  nd->flags);
 		if (IS_ERR(path.dentry))
include/linux/namei.h +2 -0
index 4e77068f7a1a..392eb439f88b 100644
@@ -49,6 +49,8 @@ enum {LAST_NORM, LAST_ROOT, LAST_DOT, LAST_DOTDOT, LAST_BIND};
 /* LOOKUP_* flags which do scope-related checks based on the dirfd. */
 #define LOOKUP_IS_SCOPED (LOOKUP_BENEATH | LOOKUP_IN_ROOT)
 
+#define LOOKUP_NONBLOCK		0x200000 /* don't block for lookup */
+
 extern int path_pts(struct path *path);
 
 extern int user_path_at_empty(int, const char __user *, unsigned, struct path *, int *empty);
Signature · 1 line
Jeff Layton <[email protected]>
author maintainer
#3 lore raw

PATCH v2 2/6 fs: make build_open_flags() available internally

In reply to Jens Axboe

This is a prep patch for supporting non-blocking open from io_uring.

---
 fs/internal.h | 2 ++
 fs/open.c     | 5 ++---
 2 files changed, 4 insertions(+), 3 deletions(-)
fs/internal.h +2 -0
index 4a7da1df573d..d6929425365d 100644
@@ -124,6 +124,8 @@ extern struct file *do_filp_open(int dfd, struct filename *pathname,
 		const struct open_flags *op);
 extern struct file *do_file_open_root(struct dentry *, struct vfsmount *,
 		const char *, const struct open_flags *);
+extern struct open_how build_open_how(int flags, umode_t mode);
+extern int build_open_flags(const struct open_how *how, struct open_flags *op);
 
 long do_sys_ftruncate(unsigned int fd, loff_t length, int small);
 long do_faccessat(int dfd, const char __user *filename, int mode);
fs/open.c +2 -3
index 50a46501bcc9..c103623d28ca 100644
@@ -958,7 +958,7 @@ EXPORT_SYMBOL(open_with_fake_path);
 #define WILL_CREATE(flags)	(flags & (O_CREAT | __O_TMPFILE))
 #define O_PATH_FLAGS		(O_DIRECTORY | O_NOFOLLOW | O_PATH | O_CLOEXEC)
 
-static inline struct open_how build_open_how(int flags, umode_t mode)
+inline struct open_how build_open_how(int flags, umode_t mode)
 {
 	struct open_how how = {
 		.flags = flags & VALID_OPEN_FLAGS,
@@ -974,8 +974,7 @@ static inline struct open_how build_open_how(int flags, umode_t mode)
 	return how;
 }
 
-static inline int build_open_flags(const struct open_how *how,
-				   struct open_flags *op)
+inline int build_open_flags(const struct open_how *how, struct open_flags *op)
 {
 	int flags = how->flags;
 	int lookup_flags = 0;
Signature · 1 line
2.24.1
author maintainer
#4 lore raw

PATCH v2 3/6 io_uring: add support for IORING_OP_OPENAT

In reply to Jens Axboe

This works just like openat(2), except it can be performed async. For the normal case of a non-blocking path lookup this will complete inline. If we have to do IO to perform the open, it'll be done from async context.

---
 fs/io_uring.c                 | 107 +++++++++++++++++++++++++++++++++-
 include/uapi/linux/io_uring.h |   2 +
 2 files changed, 107 insertions(+), 2 deletions(-)
fs/io_uring.c +105 -2
index 1822bf9aba12..53ff67ab5c4b 100644
@@ -70,6 +70,8 @@
 #include <linux/sizes.h>
 #include <linux/hugetlb.h>
 #include <linux/highmem.h>
+#include <linux/namei.h>
+#include <linux/fsnotify.h>
 
 #define CREATE_TRACE_POINTS
 #include <trace/events/io_uring.h>
@@ -353,6 +355,15 @@ struct io_sr_msg {
 	int				msg_flags;
 };
 
+struct io_open {
+	struct file			*file;
+	int				dfd;
+	umode_t				mode;
+	const char __user		*fname;
+	struct filename			*filename;
+	int				flags;
+};
+
 struct io_async_connect {
 	struct sockaddr_storage		address;
 };
@@ -371,12 +382,17 @@ struct io_async_rw {
 	ssize_t				size;
 };
 
+struct io_async_open {
+	struct filename			*filename;
+};
+
 struct io_async_ctx {
 	union {
 		struct io_async_rw	rw;
 		struct io_async_msghdr	msg;
 		struct io_async_connect	connect;
 		struct io_timeout_data	timeout;
+		struct io_async_open	open;
 	};
 };
 
@@ -397,6 +413,7 @@ struct io_kiocb {
 		struct io_timeout	timeout;
 		struct io_connect	connect;
 		struct io_sr_msg	sr_msg;
+		struct io_open		open;
 	};
 
 	struct io_async_ctx		*io;
@@ -2135,6 +2152,79 @@ static int io_fallocate(struct io_kiocb *req, struct io_kiocb **nxt,
 	return 0;
 }
 
+static int io_openat_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe)
+{
+	int ret;
+
+	if (sqe->ioprio || sqe->buf_index)
+		return -EINVAL;
+
+	req->open.dfd = READ_ONCE(sqe->fd);
+	req->open.mode = READ_ONCE(sqe->len);
+	req->open.fname = u64_to_user_ptr(READ_ONCE(sqe->addr));
+	req->open.flags = READ_ONCE(sqe->open_flags);
+
+	req->open.filename = getname(req->open.fname);
+	if (IS_ERR(req->open.filename)) {
+		ret = PTR_ERR(req->open.filename);
+		req->open.filename = NULL;
+		return ret;
+	}
+
+	return 0;
+}
+
+static void io_openat_async(struct io_wq_work **workptr)
+{
+	struct io_kiocb *req = container_of(*workptr, struct io_kiocb, work);
+	struct filename *filename = req->open.filename;
+
+	io_wq_submit_work(workptr);
+	putname(filename);
+}
+
+static int io_openat(struct io_kiocb *req, struct io_kiocb **nxt,
+		     bool force_nonblock)
+{
+	struct open_flags op;
+	struct open_how how;
+	struct file *file;
+	int ret;
+
+	how = build_open_how(req->open.flags, req->open.mode);
+	ret = build_open_flags(&how, &op);
+	if (ret)
+		goto err;
+	if (force_nonblock)
+		op.lookup_flags |= LOOKUP_NONBLOCK;
+
+	ret = get_unused_fd_flags(how.flags);
+	if (ret < 0)
+		goto err;
+
+	file = do_filp_open(req->open.dfd, req->open.filename, &op);
+	if (IS_ERR(file)) {
+		put_unused_fd(ret);
+		ret = PTR_ERR(file);
+		if (ret == -EAGAIN) {
+			req->work.flags |= IO_WQ_WORK_NEEDS_FILES;
+			req->work.func = io_openat_async;
+			return -EAGAIN;
+		}
+	} else {
+		fsnotify_open(file);
+		fd_install(ret, file);
+	}
+err:
+	if (!io_wq_current_is_worker())
+		putname(req->open.filename);
+	if (ret < 0)
+		req_set_fail_links(req);
+	io_cqring_add_event(req, ret);
+	io_put_req_find_next(req, nxt);
+	return 0;
+}
+
 static int io_prep_sfr(struct io_kiocb *req, const struct io_uring_sqe *sqe)
 {
 	struct io_ring_ctx *ctx = req->ctx;
@@ -3160,6 +3250,9 @@ static int io_req_defer_prep(struct io_kiocb *req,
 	case IORING_OP_FALLOCATE:
 		ret = io_fallocate_prep(req, sqe);
 		break;
+	case IORING_OP_OPENAT:
+		ret = io_openat_prep(req, sqe);
+		break;
 	default:
 		printk_once(KERN_WARNING "io_uring: unhandled opcode %d\n",
 				req->opcode);
@@ -3322,6 +3415,14 @@ static int io_issue_sqe(struct io_kiocb *req, const struct io_uring_sqe *sqe,
 		}
 		ret = io_fallocate(req, nxt, force_nonblock);
 		break;
+	case IORING_OP_OPENAT:
+		if (sqe) {
+			ret = io_openat_prep(req, sqe);
+			if (ret)
+				break;
+		}
+		ret = io_openat(req, nxt, force_nonblock);
+		break;
 	default:
 		ret = -EINVAL;
 		break;
@@ -3403,7 +3504,7 @@ static bool io_req_op_valid(int op)
 	return op >= IORING_OP_NOP && op < IORING_OP_LAST;
 }
 
-static int io_req_needs_file(struct io_kiocb *req)
+static int io_req_needs_file(struct io_kiocb *req, int fd)
 {
 	switch (req->opcode) {
 	case IORING_OP_NOP:
@@ -3413,6 +3514,8 @@ static int io_req_needs_file(struct io_kiocb *req)
 	case IORING_OP_ASYNC_CANCEL:
 	case IORING_OP_LINK_TIMEOUT:
 		return 0;
+	case IORING_OP_OPENAT:
+		return fd != -1;
 	default:
 		if (io_req_op_valid(req->opcode))
 			return 1;
@@ -3442,7 +3545,7 @@ static int io_req_set_file(struct io_submit_state *state, struct io_kiocb *req,
 	if (flags & IOSQE_IO_DRAIN)
 		req->flags |= REQ_F_IO_DRAIN;
 
-	ret = io_req_needs_file(req);
+	ret = io_req_needs_file(req, fd);
 	if (ret <= 0)
 		return ret;
 
include/uapi/linux/io_uring.h +2 -0
index bdbe2b130179..02af580754ce 100644
@@ -34,6 +34,7 @@ struct io_uring_sqe {
 		__u32		timeout_flags;
 		__u32		accept_flags;
 		__u32		cancel_flags;
+		__u32		open_flags;
 	};
 	__u64	user_data;	/* data to be passed back at completion time */
 	union {
@@ -77,6 +78,7 @@ enum {
 	IORING_OP_LINK_TIMEOUT,
 	IORING_OP_CONNECT,
 	IORING_OP_FALLOCATE,
+	IORING_OP_OPENAT,
 
 	/* this goes last, obviously */
 	IORING_OP_LAST,
Signature · 1 line
2.24.1
#5 lore raw

Re: [PATCH 3/6] io_uring: add support for IORING_OP_OPENAT

Hi Jens,

Quoted 4 lines

This works just like openat(2), except it can be performed async. For the normal case of a non-blocking path lookup this will complete inline. If we have to do IO to perform the open, it'll be done from async context.

Did you already thought about the credentials being used for the async open? The application could call setuid() and similar calls to change the credentials of the userspace process/threads. In order for applications like samba to use this async openat, it would be required to specify the credentials for each open, as we have to multiplex requests from multiple user sessions in one process.

This applies to non-fd based syscall. Also for an async connect to a unix domain socket.

Do you have comments on this?

Thanks!
metze

author maintainer
#6 lore raw

Re: [PATCH 3/6] io_uring: add support for IORING_OP_OPENAT

Quoted 14 lines · Stefan Metzmacher

Hi Jens,

Quoted 4 lines

This works just like openat(2), except it can be performed async. For the normal case of a non-blocking path lookup this will complete inline. If we have to do IO to perform the open, it'll be done from async context.

Did you already thought about the credentials being used for the async open? The application could call setuid() and similar calls to change the credentials of the userspace process/threads. In order for applications like samba to use this async openat, it would be required to specify the credentials for each open, as we have to multiplex requests from multiple user sessions in one process.

Quoted 4 lines

This applies to non-fd based syscall. Also for an async connect to a unix domain socket.

Do you have comments on this?

The open works like any of the other commands, it inherits the credentials that the ring was setup with. Same with the memory context, file table, etc. There's currently no way to have multiple personalities within a single ring.

Sounds like you'd like an option for having multiple personalities within a single ring? I think it would be better to have a ring per personality instead. One thing we could do to make this more lightweight is to have rings that are associated, so that we can share a lot of the backend processing between them.

Signature · 1 line
Jens Axboe
#7 lore raw

Re: [PATCH 3/6] io_uring: add support for IORING_OP_OPENAT

Am 08.01.20 um 17:20 schrieb Jens Axboe:

Quoted 18 lines · Stefan Metzmacher
Quoted 17 lines

Hi Jens,

Quoted 4 lines

This works just like openat(2), except it can be performed async. For the normal case of a non-blocking path lookup this will complete inline. If we have to do IO to perform the open, it'll be done from async context.

Did you already thought about the credentials being used for the async open? The application could call setuid() and similar calls to change the credentials of the userspace process/threads. In order for applications like samba to use this async openat, it would be required to specify the credentials for each open, as we have to multiplex requests from multiple user sessions in one process.

This applies to non-fd based syscall. Also for an async connect to a unix domain socket.

Quoted 6 lines
Quoted 1 line

Do you have comments on this?

The open works like any of the other commands, it inherits the credentials that the ring was setup with. Same with the memory context, file table, etc. There's currently no way to have multiple personalities within a single ring.

Ah, it's user = get_uid(current_user()); and ctx->user = user in io_uring_create(), right?

Quoted 2 lines

Sounds like you'd like an option for having multiple personalities within a single ring?

I'm not sure anymore, I wasn't aware of the above.

Quoted 1 line

I think it would be better to have a ring per personality instead.

We could do that. I guess we could use per user rings for path based operations and a single ring for fd based operations.

Quoted 3 lines

One thing we could do to make this more lightweight
is to have rings that are associated, so that we can share a lot of the backend processing between them.

My current idea is to use the ring fd and pass it to our main epoll loop.

Can you be more specific about how an api for associated rings could look like?

Thanks!
metze

author maintainer
#8 lore raw

Re: [PATCH 3/6] io_uring: add support for IORING_OP_OPENAT

Quoted 21 lines · Stefan Metzmacher

Am 08.01.20 um 17:20 schrieb Jens Axboe:

Quoted 20 lines · Stefan Metzmacher
Quoted 18 lines

Hi Jens,

Quoted 4 lines

This works just like openat(2), except it can be performed async. For the normal case of a non-blocking path lookup this will complete inline. If we have to do IO to perform the open, it'll be done from async context.

Did you already thought about the credentials being used for the async open? The application could call setuid() and similar calls to change the credentials of the userspace process/threads. In order for applications like samba to use this async openat, it would be required to specify the credentials for each open, as we have to multiplex requests from multiple user sessions in one process.

This applies to non-fd based syscall. Also for an async connect to a unix domain socket.

Do you have comments on this?

Quoted 7 lines
Quoted 4 lines

The open works like any of the other commands, it inherits the credentials that the ring was setup with. Same with the memory context, file table, etc. There's currently no way to have multiple personalities within a single ring.

Ah, it's user = get_uid(current_user()); and ctx->user = user in io_uring_create(), right?

That's just for the accounting, it's the:

ctx->creds = get_current_cred();

Quoted 10 lines
Quoted 2 lines

Sounds like you'd like an option for having multiple personalities within a single ring?

I'm not sure anymore, I wasn't aware of the above.

Quoted 1 line

I think it would be better to have a ring per personality instead.

We could do that. I guess we could use per user rings for path based operations and a single ring for fd based operations.

Quoted 8 lines
Quoted 3 lines

One thing we could do to make this more lightweight
is to have rings that are associated, so that we can share a lot of the backend processing between them.

My current idea is to use the ring fd and pass it to our main epoll loop.

Can you be more specific about how an api for associated rings could look like?

The API would be the exact same, there would just be some way to associate rings when you create them. Probably a new field in struct io_uring_params (and an associated flag), which would tell io_uring that two separate rings are really the same "user". This would allow io_uring to use the same io-wq workqueues, for example, etc.

This depends on the fact that you can setup the rings with the right personalities, that they would be known upfront. From your description, I'm not so sure that's the case? If not, then we would indeed need something that can pass in the credentials on a per-command basis. Not sure what that would look like.

Signature · 1 line
Jens Axboe
#9 lore raw

Re: [PATCH 3/6] io_uring: add support for IORING_OP_OPENAT

Am 08.01.20 um 17:40 schrieb Jens Axboe:

Quoted 27 lines · Stefan Metzmacher
Quoted 26 lines

Am 08.01.20 um 17:20 schrieb Jens Axboe:

Quoted 24 lines · Stefan Metzmacher
Quoted 14 lines

Hi Jens,

Quoted 4 lines

This works just like openat(2), except it can be performed async. For the normal case of a non-blocking path lookup this will complete inline. If we have to do IO to perform the open, it'll be done from async context.

Did you already thought about the credentials being used for the async open? The application could call setuid() and similar calls to change the credentials of the userspace process/threads. In order for applications like samba to use this async openat, it would be required to specify the credentials for each open, as we have to multiplex requests from multiple user sessions in one process.

Quoted 4 lines

This applies to non-fd based syscall. Also for an async connect to a unix domain socket.

Do you have comments on this?

The open works like any of the other commands, it inherits the credentials that the ring was setup with. Same with the memory context, file table, etc. There's currently no way to have multiple personalities within a single ring.

Quoted 6 lines
Quoted 2 lines

Ah, it's user = get_uid(current_user()); and ctx->user = user in io_uring_create(), right?

That's just for the accounting, it's the:

ctx->creds = get_current_cred();

Ok, I just looked at an old checkout.

In kernel-dk-block/for-5.6/io_uring-vfs I see this only used in the async processing. Does a non-blocking openat also use ctx->creds?

Quoted 16 lines
Quoted 16 lines
Quoted 2 lines

Sounds like you'd like an option for having multiple personalities within a single ring?

I'm not sure anymore, I wasn't aware of the above.

Quoted 1 line

I think it would be better to have a ring per personality instead.

We could do that. I guess we could use per user rings for path based operations and a single ring for fd based operations.

Quoted 3 lines

One thing we could do to make this more lightweight
is to have rings that are associated, so that we can share a lot of the backend processing between them.

My current idea is to use the ring fd and pass it to our main epoll loop.

Quoted 8 lines
Quoted 2 lines

Can you be more specific about how an api for associated rings could look like?

The API would be the exact same, there would just be some way to associate rings when you create them. Probably a new field in struct io_uring_params (and an associated flag), which would tell io_uring that two separate rings are really the same "user". This would allow io_uring to use the same io-wq workqueues, for example, etc.

Ok, this would be just for better performance / better usage of resources, right?

Quoted 5 lines

This depends on the fact that you can setup the rings with the right personalities, that they would be known upfront. From your description, I'm not so sure that's the case? If not, then we would indeed need something that can pass in the credentials on a per-command basis. Not sure what that would look like.

We know the credentials and using a ring per user should be ok.

metze

author maintainer
#10 lore raw

Re: [PATCH 3/6] io_uring: add support for IORING_OP_OPENAT

Quoted 31 lines · Stefan Metzmacher

Am 08.01.20 um 17:40 schrieb Jens Axboe:

Quoted 30 lines · Stefan Metzmacher
Quoted 28 lines

Am 08.01.20 um 17:20 schrieb Jens Axboe:

Quoted 18 lines · Stefan Metzmacher
Quoted 17 lines

Hi Jens,

Quoted 4 lines

This works just like openat(2), except it can be performed async. For the normal case of a non-blocking path lookup this will complete inline. If we have to do IO to perform the open, it'll be done from async context.

Did you already thought about the credentials being used for the async open? The application could call setuid() and similar calls to change the credentials of the userspace process/threads. In order for applications like samba to use this async openat, it would be required to specify the credentials for each open, as we have to multiplex requests from multiple user sessions in one process.

This applies to non-fd based syscall. Also for an async connect to a unix domain socket.

Quoted 6 lines
Quoted 1 line

Do you have comments on this?

The open works like any of the other commands, it inherits the credentials that the ring was setup with. Same with the memory context, file table, etc. There's currently no way to have multiple personalities within a single ring.

Ah, it's user = get_uid(current_user()); and ctx->user = user in io_uring_create(), right?

Quoted 8 lines
Quoted 3 lines

That's just for the accounting, it's the:

ctx->creds = get_current_cred();

Ok, I just looked at an old checkout.

In kernel-dk-block/for-5.6/io_uring-vfs I see this only used in the async processing. Does a non-blocking openat also use ctx->creds?

There's basically two sets here - one set is in the ring, and the other is the identity that the async thread (briefly) assumes if we have to go async. Right now they are the same thing, and hence we don't need to play any tricks off the system call submitting SQEs to assume any other identity than the one we have.

Quoted 19 lines
Quoted 19 lines
Quoted 18 lines
Quoted 2 lines

Sounds like you'd like an option for having multiple personalities within a single ring?

I'm not sure anymore, I wasn't aware of the above.

Quoted 1 line

I think it would be better to have a ring per personality instead.

We could do that. I guess we could use per user rings for path based operations and a single ring for fd based operations.

Quoted 3 lines

One thing we could do to make this more lightweight
is to have rings that are associated, so that we can share a lot of the backend processing between them.

My current idea is to use the ring fd and pass it to our main epoll loop.

Can you be more specific about how an api for associated rings could look like?

Quoted 8 lines
Quoted 5 lines

The API would be the exact same, there would just be some way to associate rings when you create them. Probably a new field in struct io_uring_params (and an associated flag), which would tell io_uring that two separate rings are really the same "user". This would allow io_uring to use the same io-wq workqueues, for example, etc.

Ok, this would be just for better performance / better usage of resources, right?

Exactly

Quoted 7 lines
Quoted 5 lines

This depends on the fact that you can setup the rings with the right personalities, that they would be known upfront. From your description, I'm not so sure that's the case? If not, then we would indeed need something that can pass in the credentials on a per-command basis. Not sure what that would look like.

We know the credentials and using a ring per user should be ok.

Sounds good!

Signature · 1 line
Jens Axboe
#11 lore raw

Re: [PATCH 3/6] io_uring: add support for IORING_OP_OPENAT

Am 08.01.20 um 23:53 schrieb Jens Axboe:

Quoted 38 lines · Stefan Metzmacher
Quoted 37 lines

Am 08.01.20 um 17:40 schrieb Jens Axboe:

Quoted 27 lines · Stefan Metzmacher
Quoted 26 lines

Am 08.01.20 um 17:20 schrieb Jens Axboe:

Quoted 24 lines · Stefan Metzmacher
Quoted 14 lines

Hi Jens,

Quoted 4 lines

This works just like openat(2), except it can be performed async. For the normal case of a non-blocking path lookup this will complete inline. If we have to do IO to perform the open, it'll be done from async context.

Did you already thought about the credentials being used for the async open? The application could call setuid() and similar calls to change the credentials of the userspace process/threads. In order for applications like samba to use this async openat, it would be required to specify the credentials for each open, as we have to multiplex requests from multiple user sessions in one process.

Quoted 4 lines

This applies to non-fd based syscall. Also for an async connect to a unix domain socket.

Do you have comments on this?

The open works like any of the other commands, it inherits the credentials that the ring was setup with. Same with the memory context, file table, etc. There's currently no way to have multiple personalities within a single ring.

Quoted 6 lines
Quoted 2 lines

Ah, it's user = get_uid(current_user()); and ctx->user = user in io_uring_create(), right?

That's just for the accounting, it's the:

ctx->creds = get_current_cred();

Ok, I just looked at an old checkout.

Quoted 8 lines
Quoted 2 lines

In kernel-dk-block/for-5.6/io_uring-vfs I see this only used in the async processing. Does a non-blocking openat also use ctx->creds?

There's basically two sets here - one set is in the ring, and the other is the identity that the async thread (briefly) assumes if we have to go async. Right now they are the same thing, and hence we don't need to play any tricks off the system call submitting SQEs to assume any other identity than the one we have.

I see two cases using it io_sq_thread() and io_wq_create()->io_worker_handle_work() call override_creds().

But aren't non-blocking syscall executed in the context of the thread calling io_uring_enter()->io_submit_sqes()?
In only see some magic around ctx->sqo_mm for that case, but ctx->creds doesn't seem to be used in that case. And my design would require that.

metze

author maintainer
#12 lore raw

Re: [PATCH 3/6] io_uring: add support for IORING_OP_OPENAT

Quoted 48 lines · Stefan Metzmacher

Am 08.01.20 um 23:53 schrieb Jens Axboe:

Quoted 38 lines · Stefan Metzmacher
Quoted 37 lines

Am 08.01.20 um 17:40 schrieb Jens Axboe:

Quoted 27 lines · Stefan Metzmacher
Quoted 26 lines

Am 08.01.20 um 17:20 schrieb Jens Axboe:

Quoted 24 lines · Stefan Metzmacher
Quoted 14 lines

Hi Jens,

Quoted 4 lines

This works just like openat(2), except it can be performed async. For the normal case of a non-blocking path lookup this will complete inline. If we have to do IO to perform the open, it'll be done from async context.

Did you already thought about the credentials being used for the async open? The application could call setuid() and similar calls to change the credentials of the userspace process/threads. In order for applications like samba to use this async openat, it would be required to specify the credentials for each open, as we have to multiplex requests from multiple user sessions in one process.

Quoted 4 lines

This applies to non-fd based syscall. Also for an async connect to a unix domain socket.

Do you have comments on this?

The open works like any of the other commands, it inherits the credentials that the ring was setup with. Same with the memory context, file table, etc. There's currently no way to have multiple personalities within a single ring.

Quoted 6 lines
Quoted 2 lines

Ah, it's user = get_uid(current_user()); and ctx->user = user in io_uring_create(), right?

That's just for the accounting, it's the:

ctx->creds = get_current_cred();

Ok, I just looked at an old checkout.

Quoted 8 lines
Quoted 2 lines

In kernel-dk-block/for-5.6/io_uring-vfs I see this only used in the async processing. Does a non-blocking openat also use ctx->creds?

There's basically two sets here - one set is in the ring, and the other is the identity that the async thread (briefly) assumes if we have to go async. Right now they are the same thing, and hence we don't need to play any tricks off the system call submitting SQEs to assume any other identity than the one we have.

Quoted 7 lines

I see two cases using it io_sq_thread() and io_wq_create()->io_worker_handle_work() call override_creds().

But aren't non-blocking syscall executed in the context of the thread calling io_uring_enter()->io_submit_sqes()?
In only see some magic around ctx->sqo_mm for that case, but ctx->creds doesn't seem to be used in that case. And my design would require that.

For now, the sq thread (which is used if you use IORING_SETUP_SQPOLL) currently requires fixed files, so it can't be used with open at the moment anyway. But if/when enabled, it'll assume the same credentials as the async context and syscall path.

Signature · 1 line
Jens Axboe
#13 lore raw

Re: [PATCH 3/6] io_uring: add support for IORING_OP_OPENAT

Am 09.01.20 um 00:05 schrieb Jens Axboe:

Quoted 57 lines · Stefan Metzmacher
Quoted 48 lines

Am 08.01.20 um 23:53 schrieb Jens Axboe:

Quoted 38 lines · Stefan Metzmacher
Quoted 37 lines

Am 08.01.20 um 17:40 schrieb Jens Axboe:

Quoted 27 lines · Stefan Metzmacher
Quoted 26 lines

Am 08.01.20 um 17:20 schrieb Jens Axboe:

Quoted 24 lines · Stefan Metzmacher
Quoted 14 lines

Hi Jens,

Quoted 4 lines

This works just like openat(2), except it can be performed async. For the normal case of a non-blocking path lookup this will complete inline. If we have to do IO to perform the open, it'll be done from async context.

Did you already thought about the credentials being used for the async open? The application could call setuid() and similar calls to change the credentials of the userspace process/threads. In order for applications like samba to use this async openat, it would be required to specify the credentials for each open, as we have to multiplex requests from multiple user sessions in one process.

Quoted 4 lines

This applies to non-fd based syscall. Also for an async connect to a unix domain socket.

Do you have comments on this?

The open works like any of the other commands, it inherits the credentials that the ring was setup with. Same with the memory context, file table, etc. There's currently no way to have multiple personalities within a single ring.

Quoted 6 lines
Quoted 2 lines

Ah, it's user = get_uid(current_user()); and ctx->user = user in io_uring_create(), right?

That's just for the accounting, it's the:

ctx->creds = get_current_cred();

Ok, I just looked at an old checkout.

Quoted 8 lines
Quoted 2 lines

In kernel-dk-block/for-5.6/io_uring-vfs I see this only used in the async processing. Does a non-blocking openat also use ctx->creds?

There's basically two sets here - one set is in the ring, and the other is the identity that the async thread (briefly) assumes if we have to go async. Right now they are the same thing, and hence we don't need to play any tricks off the system call submitting SQEs to assume any other identity than the one we have.

Quoted 7 lines

I see two cases using it io_sq_thread() and io_wq_create()->io_worker_handle_work() call override_creds().

But aren't non-blocking syscall executed in the context of the thread calling io_uring_enter()->io_submit_sqes()?
In only see some magic around ctx->sqo_mm for that case, but ctx->creds doesn't seem to be used in that case. And my design would require that.

Quoted 4 lines

For now, the sq thread (which is used if you use IORING_SETUP_SQPOLL) currently requires fixed files, so it can't be used with open at the moment anyway. But if/when enabled, it'll assume the same credentials as the async context and syscall path.

I'm sorry, but I'm still unsure we're talking about the same thing (or maybe I'm missing some basics here).

My understanding of the io_uring_enter() is that it will execute as much non-blocking calls as it can without switching to any other kernel thread.

And my fear is that openat will use get_current_cred() instead of ctx->creds.

I'm I missing something?

Thanks!
metze

author maintainer
#14 lore raw

Re: [PATCH 3/6] io_uring: add support for IORING_OP_OPENAT

Quoted 63 lines · Stefan Metzmacher

Am 09.01.20 um 00:05 schrieb Jens Axboe:

Quoted 57 lines · Stefan Metzmacher
Quoted 48 lines

Am 08.01.20 um 23:53 schrieb Jens Axboe:

Quoted 38 lines · Stefan Metzmacher
Quoted 37 lines

Am 08.01.20 um 17:40 schrieb Jens Axboe:

Quoted 27 lines · Stefan Metzmacher
Quoted 26 lines

Am 08.01.20 um 17:20 schrieb Jens Axboe:

Quoted 24 lines · Stefan Metzmacher
Quoted 14 lines

Hi Jens,

Quoted 4 lines

This works just like openat(2), except it can be performed async. For the normal case of a non-blocking path lookup this will complete inline. If we have to do IO to perform the open, it'll be done from async context.

Did you already thought about the credentials being used for the async open? The application could call setuid() and similar calls to change the credentials of the userspace process/threads. In order for applications like samba to use this async openat, it would be required to specify the credentials for each open, as we have to multiplex requests from multiple user sessions in one process.

Quoted 4 lines

This applies to non-fd based syscall. Also for an async connect to a unix domain socket.

Do you have comments on this?

The open works like any of the other commands, it inherits the credentials that the ring was setup with. Same with the memory context, file table, etc. There's currently no way to have multiple personalities within a single ring.

Quoted 6 lines
Quoted 2 lines

Ah, it's user = get_uid(current_user()); and ctx->user = user in io_uring_create(), right?

That's just for the accounting, it's the:

ctx->creds = get_current_cred();

Ok, I just looked at an old checkout.

Quoted 8 lines
Quoted 2 lines

In kernel-dk-block/for-5.6/io_uring-vfs I see this only used in the async processing. Does a non-blocking openat also use ctx->creds?

There's basically two sets here - one set is in the ring, and the other is the identity that the async thread (briefly) assumes if we have to go async. Right now they are the same thing, and hence we don't need to play any tricks off the system call submitting SQEs to assume any other identity than the one we have.

Quoted 7 lines

I see two cases using it io_sq_thread() and io_wq_create()->io_worker_handle_work() call override_creds().

But aren't non-blocking syscall executed in the context of the thread calling io_uring_enter()->io_submit_sqes()?
In only see some magic around ctx->sqo_mm for that case, but ctx->creds doesn't seem to be used in that case. And my design would require that.

Quoted 4 lines

For now, the sq thread (which is used if you use IORING_SETUP_SQPOLL) currently requires fixed files, so it can't be used with open at the moment anyway. But if/when enabled, it'll assume the same credentials as the async context and syscall path.

Quoted 5 lines

I'm sorry, but I'm still unsure we're talking about the same thing (or maybe I'm missing some basics here).

My understanding of the io_uring_enter() is that it will execute as much non-blocking calls as it can without switching to any other kernel thread.

Correct, any SQE that we can do without switching, we will.

Quoted 2 lines

And my fear is that openat will use get_current_cred() instead of ctx->creds.

OK, I think I follow your concern. So you'd like to setup the rings from a _different_ user, and then later on use it for submission for SQEs that a specific user. So sort of the same as our initial discussion, except the mapping would be static. The difference being that you might setup the ring from a different user than the user that would be submitting IO on it?

If so, then we do need something to support that, probably an IORING_REGISTER_CREDS or similar. This would allow you to replace the creds you currently have in ctx->creds with whatever new one.

Quoted 1 line

I'm I missing something?

I think we're talking about the same thing, just different views of it :-)

Signature · 1 line
Jens Axboe
#15 lore raw

Re: [PATCH 3/6] io_uring: add support for IORING_OP_OPENAT

Quoted 11 lines
Quoted 5 lines

I'm sorry, but I'm still unsure we're talking about the same thing (or maybe I'm missing some basics here).

My understanding of the io_uring_enter() is that it will execute as much non-blocking calls as it can without switching to any other kernel thread.

Correct, any SQE that we can do without switching, we will.

Quoted 2 lines

And my fear is that openat will use get_current_cred() instead of ctx->creds.

Quoted 6 lines

OK, I think I follow your concern. So you'd like to setup the rings from a _different_ user, and then later on use it for submission for SQEs that a specific user. So sort of the same as our initial discussion, except the mapping would be static. The difference being that you might setup the ring from a different user than the user that would be submitting IO on it?

Our current (much simplified here) flow is this:

  # we start as root
  seteuid(0);setegid(0);setgroups()...
  ...
  # we become the user555 and
  # create our desired credential token
  seteuid(555); seteguid(555); setgroups()...
  # Start an openat2 on behalf of user555
  openat2()
  # we unbecome the user again and run as root
  seteuid(0);setegid(0); setgroups()...
  ...
  # we become the user444 and
  # create our desired credential token
  seteuid(444); seteguid(444); setgroups()...
  # Start an openat2 on behalf of user444
  openat2()
  # we unbecome the user again and run as root
  seteuid(0);setegid(0); setgroups()...
  ...
  # we become the user555 and
  # create our desired credential token
  seteuid(555); seteguid(555); setgroups()...
  # Start an openat2 on behalf of user555
  openat2()
  # we unbecome the user again and run as root
  seteuid(0);setegid(0); setgroups()...

It means we have to do about 7 syscalls in order to open a file on behalf of a user.
(In reality we cache things and avoid set*id() calls most of the time, but I want to demonstrate the simplified design here)

With io_uring I'd like to use a flow like this:

  # we start as root
  seteuid(0);setegid(0);setgroups()...
  ...
  # we become the user444 and
  # create our desired credential token
  seteuid(444); seteguid(444); setgroups()...
  # we snapshot the credentials to the new ring for user444
  ring444 = io_uring_setup()
  # we unbecome the user again and run as root
  seteuid(0);setegid(0);setgroups()...
  ...
  # we become the user555 and
  # create our desired credential token
  seteuid(555); seteguid(555); setgroups()...
  # we snapshot the credentials to the new ring for user555
  ring555 = io_uring_setup()
  # we unbecome the user again and run as root
  seteuid(0);setegid(0);setgroups()...
  ...
  # Start an openat2 on behalf of user555
  io_uring_enter(ring555, OP_OPENAT2...)
  ...
  # Start an openat2 on behalf of user444
  io_uring_enter(ring444, OP_OPENAT2...)
  ...
  # Start an openat2 on behalf of user555
  io_uring_enter(ring555, OP_OPENAT2...)

So instead of constantly doing 7 syscalls per open, we would be down to just at most one. And I would assume that io_uring_enter() would do the temporary credential switch for me also in the non-blocking case.

Quoted 3 lines

If so, then we do need something to support that, probably an IORING_REGISTER_CREDS or similar. This would allow you to replace the creds you currently have in ctx->creds with whatever new one.

I don't want to change ctx->creds, but I want it to be used consistently.

What I think is missing is something like this:

fs/io_uring.c +0 -0
index 32aee149f652..55dbb154915a 100644
@@ -6359,10 +6359,27 @@ SYSCALL_DEFINE6(io_uring_enter, unsigned int,

fd, u32, to_submit,

                struct mm_struct *cur_mm;

                mutex_lock(&ctx->uring_lock);
+               if (current->mm != ctx->sqo_mm) {
+                       // TODO: somthing like this...
+                       restore_mm = current->mm;
+                       use_mm(ctx->sqo_mm);
+               }
                /* already have mm, so io_submit_sqes() won't try to
grab it */
                cur_mm = ctx->sqo_mm;

+ if (current_cred() != ctx->creds) {
+ // TODO: somthing like this...
+ restore_cred = override_creds(ctx->creds);
+ }
submitted = io_submit_sqes(ctx, to_submit, f.file, fd,
&cur_mm, false);
+ if (restore_cred != NULL) {
+ revert_creds(restore_cred);
+ }
+ if (restore_mm != NULL) {

+                       // TODO: something like this...
+                       unuse_mm(ctx->sqo_mm);
+                       use_mm(restore_mm);
+               }
                mutex_unlock(&ctx->uring_lock);

                if (submitted != to_submit)

I'm not sure if current->mm is needed, I just added it for completeness and as hint that io_op_defs[req->opcode].needs_mm is there and a needs_creds could also be added (if it helps with performance)

Is it possible to trigger a change of current->mm from userspace?

An IORING_REGISTER_CREDS would only be useful if it's possible to register a set of credentials and then use per io_uring_sqe credentials. That would also be fine for me, but I'm not sure it's needed for now.

Apart from IORING_REGISTER_CREDS I think a change like the one above is needed in order to avoid potential security problems.

Quoted 3 lines
Quoted 1 line

I'm I missing something?

I think we're talking about the same thing, just different views of it :-)

I hope it's clear from my side now :-)

Thanks!
metze

author maintainer
#16 lore raw

Re: [PATCH 3/6] io_uring: add support for IORING_OP_OPENAT

Quoted 83 lines · Stefan Metzmacher
Quoted 11 lines
Quoted 5 lines

I'm sorry, but I'm still unsure we're talking about the same thing (or maybe I'm missing some basics here).

My understanding of the io_uring_enter() is that it will execute as much non-blocking calls as it can without switching to any other kernel thread.

Correct, any SQE that we can do without switching, we will.

Quoted 2 lines

And my fear is that openat will use get_current_cred() instead of ctx->creds.

Quoted 6 lines

OK, I think I follow your concern. So you'd like to setup the rings from a _different_ user, and then later on use it for submission for SQEs that a specific user. So sort of the same as our initial discussion, except the mapping would be static. The difference being that you might setup the ring from a different user than the user that would be submitting IO on it?

Our current (much simplified here) flow is this:

  # we start as root
  seteuid(0);setegid(0);setgroups()...
  ...
  # we become the user555 and
  # create our desired credential token
  seteuid(555); seteguid(555); setgroups()...
  # Start an openat2 on behalf of user555
  openat2()
  # we unbecome the user again and run as root
  seteuid(0);setegid(0); setgroups()...
  ...
  # we become the user444 and
  # create our desired credential token
  seteuid(444); seteguid(444); setgroups()...
  # Start an openat2 on behalf of user444
  openat2()
  # we unbecome the user again and run as root
  seteuid(0);setegid(0); setgroups()...
  ...
  # we become the user555 and
  # create our desired credential token
  seteuid(555); seteguid(555); setgroups()...
  # Start an openat2 on behalf of user555
  openat2()
  # we unbecome the user again and run as root
  seteuid(0);setegid(0); setgroups()...

It means we have to do about 7 syscalls in order to open a file on behalf of a user.
(In reality we cache things and avoid set*id() calls most of the time, but I want to demonstrate the simplified design here)

With io_uring I'd like to use a flow like this:

  # we start as root
  seteuid(0);setegid(0);setgroups()...
  ...
  # we become the user444 and
  # create our desired credential token
  seteuid(444); seteguid(444); setgroups()...
  # we snapshot the credentials to the new ring for user444
  ring444 = io_uring_setup()
  # we unbecome the user again and run as root
  seteuid(0);setegid(0);setgroups()...
  ...
  # we become the user555 and
  # create our desired credential token
  seteuid(555); seteguid(555); setgroups()...
  # we snapshot the credentials to the new ring for user555
  ring555 = io_uring_setup()
  # we unbecome the user again and run as root
  seteuid(0);setegid(0);setgroups()...
  ...
  # Start an openat2 on behalf of user555
  io_uring_enter(ring555, OP_OPENAT2...)
  ...
  # Start an openat2 on behalf of user444
  io_uring_enter(ring444, OP_OPENAT2...)
  ...
  # Start an openat2 on behalf of user555
  io_uring_enter(ring555, OP_OPENAT2...)
Quoted 4 lines

So instead of constantly doing 7 syscalls per open, we would be down to just at most one. And I would assume that io_uring_enter() would do the temporary credential switch for me also in the non-blocking case.

OK, thanks for spelling the use case out, makes it easier to understand what you need in terms of what we currently can't do.

Quoted 47 lines
Quoted 3 lines

If so, then we do need something to support that, probably an IORING_REGISTER_CREDS or similar. This would allow you to replace the creds you currently have in ctx->creds with whatever new one.

I don't want to change ctx->creds, but I want it to be used consistently.

What I think is missing is something like this:

fs/io_uring.c +5 -0
index 32aee149f652..55dbb154915a 100644
@@ -6359,10 +6359,27 @@ SYSCALL_DEFINE6(io_uring_enter, unsigned int,
 fd, u32, to_submit,
                struct mm_struct *cur_mm;
 
                mutex_lock(&ctx->uring_lock);
+               if (current->mm != ctx->sqo_mm) {
+                       // TODO: somthing like this...
+                       restore_mm = current->mm;
+                       use_mm(ctx->sqo_mm);
+               }
                /* already have mm, so io_submit_sqes() won't try to

grab it */

                cur_mm = ctx->sqo_mm;
fs/io_uring.c +12 -0
+               if (current_cred() != ctx->creds) {
+                       // TODO: somthing like this...
+                       restore_cred = override_creds(ctx->creds);
+               }
                submitted = io_submit_sqes(ctx, to_submit, f.file, fd,
                                           &cur_mm, false);
+               if (restore_cred != NULL) {
+                       revert_creds(restore_cred);
+               }
+               if (restore_mm != NULL) {
+                       // TODO: something like this...
+                       unuse_mm(ctx->sqo_mm);
+                       use_mm(restore_mm);
+               }
                mutex_unlock(&ctx->uring_lock);
 
                if (submitted != to_submit)

I'm not sure if current->mm is needed, I just added it for completeness and as hint that io_op_defs[req->opcode].needs_mm is there and a needs_creds could also be added (if it helps with performance)

Quoted 5 lines

Is it possible to trigger a change of current->mm from userspace?

An IORING_REGISTER_CREDS would only be useful if it's possible to register a set of credentials and then use per io_uring_sqe credentials. That would also be fine for me, but I'm not sure it's needed for now.

I think it'd be a cleaner way of doing the same thing as your patch does. It seems a little odd to do this by default (having the ring change personalities depending on who's using it), but from an opt-in point of view, I think it makes more sense.

That would make the IORING_REGISTER_ call something like IORING_REGISTER_ADOPT_OWNER or something like that, meaning that the ring would just assume the identify of the task that's calling io_uring_enter().

Note that this also has to be passed through to the io-wq handler, as the mappings there are currently static as well.

Signature · 1 line
Jens Axboe
#17 lore raw

Re: [PATCH 3/6] io_uring: add support for IORING_OP_OPENAT

Am 09.01.20 um 22:31 schrieb Jens Axboe:

Quoted 150 lines · Stefan Metzmacher
Quoted 83 lines
Quoted 11 lines
Quoted 5 lines

I'm sorry, but I'm still unsure we're talking about the same thing (or maybe I'm missing some basics here).

My understanding of the io_uring_enter() is that it will execute as much non-blocking calls as it can without switching to any other kernel thread.

Correct, any SQE that we can do without switching, we will.

Quoted 2 lines

And my fear is that openat will use get_current_cred() instead of ctx->creds.

Quoted 6 lines

OK, I think I follow your concern. So you'd like to setup the rings from a _different_ user, and then later on use it for submission for SQEs that a specific user. So sort of the same as our initial discussion, except the mapping would be static. The difference being that you might setup the ring from a different user than the user that would be submitting IO on it?

Our current (much simplified here) flow is this:

  # we start as root
  seteuid(0);setegid(0);setgroups()...
  ...
  # we become the user555 and
  # create our desired credential token
  seteuid(555); seteguid(555); setgroups()...
  # Start an openat2 on behalf of user555
  openat2()
  # we unbecome the user again and run as root
  seteuid(0);setegid(0); setgroups()...
  ...
  # we become the user444 and
  # create our desired credential token
  seteuid(444); seteguid(444); setgroups()...
  # Start an openat2 on behalf of user444
  openat2()
  # we unbecome the user again and run as root
  seteuid(0);setegid(0); setgroups()...
  ...
  # we become the user555 and
  # create our desired credential token
  seteuid(555); seteguid(555); setgroups()...
  # Start an openat2 on behalf of user555
  openat2()
  # we unbecome the user again and run as root
  seteuid(0);setegid(0); setgroups()...

It means we have to do about 7 syscalls in order to open a file on behalf of a user.
(In reality we cache things and avoid set*id() calls most of the time, but I want to demonstrate the simplified design here)

With io_uring I'd like to use a flow like this:

  # we start as root
  seteuid(0);setegid(0);setgroups()...
  ...
  # we become the user444 and
  # create our desired credential token
  seteuid(444); seteguid(444); setgroups()...
  # we snapshot the credentials to the new ring for user444
  ring444 = io_uring_setup()
  # we unbecome the user again and run as root
  seteuid(0);setegid(0);setgroups()...
  ...
  # we become the user555 and
  # create our desired credential token
  seteuid(555); seteguid(555); setgroups()...
  # we snapshot the credentials to the new ring for user555
  ring555 = io_uring_setup()
  # we unbecome the user again and run as root
  seteuid(0);setegid(0);setgroups()...
  ...
  # Start an openat2 on behalf of user555
  io_uring_enter(ring555, OP_OPENAT2...)
  ...
  # Start an openat2 on behalf of user444
  io_uring_enter(ring444, OP_OPENAT2...)
  ...
  # Start an openat2 on behalf of user555
  io_uring_enter(ring555, OP_OPENAT2...)
Quoted 4 lines

So instead of constantly doing 7 syscalls per open, we would be down to just at most one. And I would assume that io_uring_enter() would do the temporary credential switch for me also in the non-blocking case.

OK, thanks for spelling the use case out, makes it easier to understand what you need in terms of what we currently can't do.

Quoted 47 lines
Quoted 3 lines

If so, then we do need something to support that, probably an IORING_REGISTER_CREDS or similar. This would allow you to replace the creds you currently have in ctx->creds with whatever new one.

I don't want to change ctx->creds, but I want it to be used consistently.

What I think is missing is something like this:

fs/io_uring.c +5 -0
index 32aee149f652..55dbb154915a 100644
@@ -6359,10 +6359,27 @@ SYSCALL_DEFINE6(io_uring_enter, unsigned int,
 fd, u32, to_submit,
                struct mm_struct *cur_mm;
 
                mutex_lock(&ctx->uring_lock);
+               if (current->mm != ctx->sqo_mm) {
+                       // TODO: somthing like this...
+                       restore_mm = current->mm;
+                       use_mm(ctx->sqo_mm);
+               }
                /* already have mm, so io_submit_sqes() won't try to

grab it */

                cur_mm = ctx->sqo_mm;
fs/io_uring.c +12 -0
+               if (current_cred() != ctx->creds) {
+                       // TODO: somthing like this...
+                       restore_cred = override_creds(ctx->creds);
+               }
                submitted = io_submit_sqes(ctx, to_submit, f.file, fd,
                                           &cur_mm, false);
+               if (restore_cred != NULL) {
+                       revert_creds(restore_cred);
+               }
+               if (restore_mm != NULL) {
+                       // TODO: something like this...
+                       unuse_mm(ctx->sqo_mm);
+                       use_mm(restore_mm);
+               }
                mutex_unlock(&ctx->uring_lock);
 
                if (submitted != to_submit)

I'm not sure if current->mm is needed, I just added it for completeness and as hint that io_op_defs[req->opcode].needs_mm is there and a needs_creds could also be added (if it helps with performance)

Quoted 5 lines

Is it possible to trigger a change of current->mm from userspace?

An IORING_REGISTER_CREDS would only be useful if it's possible to register a set of credentials and then use per io_uring_sqe credentials. That would also be fine for me, but I'm not sure it's needed for now.

I think it'd be a cleaner way of doing the same thing as your patch does. It seems a little odd to do this by default (having the ring change personalities depending on who's using it), but from an opt-in point of view, I think it makes more sense.

Quoted 7 lines

That would make the IORING_REGISTER_ call something like IORING_REGISTER_ADOPT_OWNER or something like that, meaning that the ring would just assume the identify of the task that's calling io_uring_enter().

Note that this also has to be passed through to the io-wq handler, as the mappings there are currently static as well.

What's the next step here?

I think the current state is a security problem!

The inline execution either needs to change the creds temporary or io_uring_enter() needs a general check that the current creds match the creds of the ring and return -EPERM or something similar.

Thanks!
metze

author maintainer
#18 lore raw

Re: [PATCH 3/6] io_uring: add support for IORING_OP_OPENAT

Quoted 20 lines · Stefan Metzmacher
Quoted 20 lines
Quoted 9 lines

I'm not sure if current->mm is needed, I just added it for completeness and as hint that io_op_defs[req->opcode].needs_mm is there and a needs_creds could also be added (if it helps with performance)

Is it possible to trigger a change of current->mm from userspace?

An IORING_REGISTER_CREDS would only be useful if it's possible to register a set of credentials and then use per io_uring_sqe credentials. That would also be fine for me, but I'm not sure it's needed for now.

I think it'd be a cleaner way of doing the same thing as your patch does. It seems a little odd to do this by default (having the ring change personalities depending on who's using it), but from an opt-in point of view, I think it makes more sense.

That would make the IORING_REGISTER_ call something like IORING_REGISTER_ADOPT_OWNER or something like that, meaning that the ring would just assume the identify of the task that's calling io_uring_enter().

Quoted 4 lines
Quoted 2 lines

Note that this also has to be passed through to the io-wq handler, as the mappings there are currently static as well.

What's the next step here?

Not sure, need to find some time to work on this!

Quoted 5 lines

I think the current state is a security problem!

The inline execution either needs to change the creds temporary or io_uring_enter() needs a general check that the current creds match the creds of the ring and return -EPERM or something similar.

Hmm, if you transfer the fd to someone else, you also give them access to your credentials etc. We could make that -EPERM, if the owner of the ring isn't the one invoking the submit. But that doesn't really help the SQPOLL case, which simply consumes SQE entries. There can be no checking there.

Signature · 1 line
Jens Axboe
author maintainer
#19 lore raw

PATCH v2 4/6 fs: move filp_close() outside of __close_fd_get_file()

In reply to Jens Axboe

Just one caller of this, and just use filp_close() there manually. This is important to allow async close/removal of the fd.

---
 drivers/android/binder.c | 6 ++++--
 fs/file.c                | 6 ++++--
 2 files changed, 8 insertions(+), 4 deletions(-)
drivers/android/binder.c +4 -2
index b2dad43dbf82..cf72ca250a08 100644
@@ -2249,10 +2249,12 @@ static void binder_deferred_fd_close(int fd)
 		return;
 	init_task_work(&twcb->twork, binder_do_fd_close);
 	__close_fd_get_file(fd, &twcb->file);
-	if (twcb->file)
+	if (twcb->file) {
+		filp_close(twcb->file, current->files);
 		task_work_add(current, &twcb->twork, true);
-	else
+	} else {
 		kfree(twcb);
+	}
 }
 
 static void binder_transaction_buffer_release(struct binder_proc *proc,
fs/file.c +4 -2
index 3da91a112bab..fb7081bfac2b 100644
@@ -642,7 +642,9 @@ int __close_fd(struct files_struct *files, unsigned fd)
 EXPORT_SYMBOL(__close_fd); /* for ksys_close() */
 
 /*
- * variant of __close_fd that gets a ref on the file for later fput
+ * variant of __close_fd that gets a ref on the file for later fput.
+ * The caller must ensure that filp_close() called on the file, and then
+ * an fput().
  */
 int __close_fd_get_file(unsigned int fd, struct file **res)
 {
@@ -662,7 +664,7 @@ int __close_fd_get_file(unsigned int fd, struct file **res)
 	spin_unlock(&files->file_lock);
 	get_file(file);
 	*res = file;
-	return filp_close(file, files);
+	return 0;
 
 out_unlock:
 	spin_unlock(&files->file_lock);
Signature · 1 line
2.24.1
author maintainer
#20 lore raw

PATCH v2 5/6 io-wq: add support for uncancellable work

In reply to Jens Axboe

Not all work can be cancelled, some of it we may need to guarantee that it runs to completion. Allow the caller to set IO_WQ_WORK_NO_CANCEL on work that must not be cancelled. Note that the caller work function must also check for IO_WQ_WORK_NO_CANCEL on work that is marked IO_WQ_WORK_CANCEL.

---
 fs/io-wq.c    | 8 +++++++-
 fs/io-wq.h    | 1 +
 fs/io_uring.c | 5 ++++-
 3 files changed, 12 insertions(+), 2 deletions(-)
fs/io-wq.c +7 -1
index 541c8a3e0bbb..2f94170eeb89 100644
@@ -452,6 +452,10 @@ static void io_worker_handle_work(struct io_worker *worker)
 		}
 		if (!worker->creds)
 			worker->creds = override_creds(wq->creds);
+		/*
+		 * OK to set IO_WQ_WORK_CANCEL even for uncancellable work,
+		 * the worker function will do the right thing.
+		 */
 		if (test_bit(IO_WQ_BIT_CANCEL, &wq->state))
 			work->flags |= IO_WQ_WORK_CANCEL;
 		if (worker->mm)
@@ -824,6 +828,7 @@ static bool io_work_cancel(struct io_worker *worker, void *cancel_data)
 	 */
 	spin_lock_irqsave(&worker->lock, flags);
 	if (worker->cur_work &&
+	    !(worker->cur_work->flags & IO_WQ_WORK_NO_CANCEL) &&
 	    data->cancel(worker->cur_work, data->caller_data)) {
 		send_sig(SIGINT, worker->task, 1);
 		ret = true;
@@ -898,7 +903,8 @@ static bool io_wq_worker_cancel(struct io_worker *worker, void *data)
 		return false;
 
 	spin_lock_irqsave(&worker->lock, flags);
-	if (worker->cur_work == work) {
+	if (worker->cur_work == work &&
+	    !(worker->cur_work->flags & IO_WQ_WORK_NO_CANCEL)) {
 		send_sig(SIGINT, worker->task, 1);
 		ret = true;
 	}
fs/io-wq.h +1 -0
index 3f5e356de980..04d60ad38dfc 100644
@@ -12,6 +12,7 @@ enum {
 	IO_WQ_WORK_UNBOUND	= 32,
 	IO_WQ_WORK_INTERNAL	= 64,
 	IO_WQ_WORK_CB		= 128,
+	IO_WQ_WORK_NO_CANCEL	= 256,
 
 	IO_WQ_HASH_SHIFT	= 24,	/* upper 8 bits are used for hash key */
 };
fs/io_uring.c +4 -1
index 53ff67ab5c4b..e12b1545468d 100644
@@ -3457,8 +3457,11 @@ static void io_wq_submit_work(struct io_wq_work **workptr)
 	struct io_kiocb *nxt = NULL;
 	int ret = 0;
 
-	if (work->flags & IO_WQ_WORK_CANCEL)
+	/* if NO_CANCEL is set, we must still run the work */
+	if ((work->flags & (IO_WQ_WORK_CANCEL|IO_WQ_WORK_NO_CANCEL)) ==
+				IO_WQ_WORK_CANCEL) {
 		ret = -ECANCELED;
+	}
 
 	if (!ret) {
 		req->has_user = (work->flags & IO_WQ_WORK_HAS_MM) != 0;
Signature · 1 line
2.24.1
author maintainer
#21 lore raw

PATCH v2 6/6 io_uring: add support for IORING_OP_CLOSE

In reply to Jens Axboe

This works just like close(2), unsurprisingly. We remove the file descriptor and post the completion inline, then offload the actual (potential) last file put to async context.

Mark the async part of this work as uncancellable, as we really must guarantee that the latter part of the close is run.

---
 fs/io_uring.c                 | 109 ++++++++++++++++++++++++++++++++++
 include/uapi/linux/io_uring.h |   1 +
 2 files changed, 110 insertions(+)
fs/io_uring.c +109 -0
index e12b1545468d..39abe20220d0 100644
@@ -301,6 +301,12 @@ struct io_poll_iocb {
 	struct wait_queue_entry		wait;
 };
 
+struct io_close {
+	struct file			*file;
+	struct file			*put_file;
+	int				fd;
+};
+
 struct io_timeout_data {
 	struct io_kiocb			*req;
 	struct hrtimer			timer;
@@ -414,6 +420,7 @@ struct io_kiocb {
 		struct io_connect	connect;
 		struct io_sr_msg	sr_msg;
 		struct io_open		open;
+		struct io_close		close;
 	};
 
 	struct io_async_ctx		*io;
@@ -2225,6 +2232,94 @@ static int io_openat(struct io_kiocb *req, struct io_kiocb **nxt,
 	return 0;
 }
 
+static int io_close_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe)
+{
+	/*
+	 * If we queue this for async, it must not be cancellable. That would
+	 * leave the 'file' in an undeterminate state.
+	 */
+	req->work.flags |= IO_WQ_WORK_NO_CANCEL;
+
+	if (sqe->ioprio || sqe->off || sqe->addr || sqe->len ||
+	    sqe->rw_flags || sqe->buf_index)
+		return -EINVAL;
+	if (sqe->flags & IOSQE_FIXED_FILE)
+		return -EINVAL;
+
+	req->close.fd = READ_ONCE(sqe->fd);
+	if (req->file->f_op == &io_uring_fops ||
+	    req->close.fd == req->ring_fd)
+		return -EBADF;
+
+	return 0;
+}
+
+static void io_close_finish(struct io_wq_work **workptr)
+{
+	struct io_kiocb *req = container_of(*workptr, struct io_kiocb, work);
+	struct io_kiocb *nxt = NULL;
+
+	/* Invoked with files, we need to do the close */
+	if (req->work.files) {
+		int ret;
+
+		ret = filp_close(req->close.put_file, req->work.files);
+		if (ret < 0) {
+			req_set_fail_links(req);
+		}
+		io_cqring_add_event(req, ret);
+	}
+
+	fput(req->close.put_file);
+
+	/* we bypassed the re-issue, drop the submission reference */
+	io_put_req(req);
+	io_put_req_find_next(req, &nxt);
+	if (nxt)
+		*workptr = &nxt->work;
+}
+
+static int io_close(struct io_kiocb *req, struct io_kiocb **nxt,
+		    bool force_nonblock)
+{
+	int ret;
+
+	req->close.put_file = NULL;
+	ret = __close_fd_get_file(req->close.fd, &req->close.put_file);
+	if (ret < 0)
+		return ret;
+
+	/* if the file has a flush method, be safe and punt to async */
+	if (req->close.put_file->f_op->flush && !io_wq_current_is_worker()) {
+		req->work.flags |= IO_WQ_WORK_NEEDS_FILES;
+		goto eagain;
+	}
+
+	/*
+	 * No ->flush(), safely close from here and just punt the
+	 * fput() to async context.
+	 */
+	ret = filp_close(req->close.put_file, current->files);
+
+	if (ret < 0)
+		req_set_fail_links(req);
+	io_cqring_add_event(req, ret);
+
+	if (io_wq_current_is_worker()) {
+		struct io_wq_work *old_work, *work;
+
+		old_work = work = &req->work;
+		io_close_finish(&work);
+		if (work && work != old_work)
+			*nxt = container_of(work, struct io_kiocb, work);
+		return 0;
+	}
+
+eagain:
+	req->work.func = io_close_finish;
+	return -EAGAIN;
+}
+
 static int io_prep_sfr(struct io_kiocb *req, const struct io_uring_sqe *sqe)
 {
 	struct io_ring_ctx *ctx = req->ctx;
@@ -3253,6 +3348,9 @@ static int io_req_defer_prep(struct io_kiocb *req,
 	case IORING_OP_OPENAT:
 		ret = io_openat_prep(req, sqe);
 		break;
+	case IORING_OP_CLOSE:
+		ret = io_close_prep(req, sqe);
+		break;
 	default:
 		printk_once(KERN_WARNING "io_uring: unhandled opcode %d\n",
 				req->opcode);
@@ -3423,6 +3521,14 @@ static int io_issue_sqe(struct io_kiocb *req, const struct io_uring_sqe *sqe,
 		}
 		ret = io_openat(req, nxt, force_nonblock);
 		break;
+	case IORING_OP_CLOSE:
+		if (sqe) {
+			ret = io_close_prep(req, sqe);
+			if (ret)
+				break;
+		}
+		ret = io_close(req, nxt, force_nonblock);
+		break;
 	default:
 		ret = -EINVAL;
 		break;
@@ -3578,6 +3684,9 @@ static int io_grab_files(struct io_kiocb *req)
 	int ret = -EBADF;
 	struct io_ring_ctx *ctx = req->ctx;
 
+	if (!req->ring_file)
+		return -EBADF;
+
 	rcu_read_lock();
 	spin_lock_irq(&ctx->inflight_lock);
 	/*
include/uapi/linux/io_uring.h +1 -0
index 02af580754ce..42a7f0e8dee3 100644
@@ -79,6 +79,7 @@ enum {
 	IORING_OP_CONNECT,
 	IORING_OP_FALLOCATE,
 	IORING_OP_OPENAT,
+	IORING_OP_CLOSE,
 
 	/* this goes last, obviously */
 	IORING_OP_LAST,
Signature · 1 line
2.24.1
#22 lore raw
In reply to Jens Axboe

Am 07.01.20 um 18:00 schrieb Jens Axboe:

Quoted 3 lines

Sending this out separately, as I rebased it on top of the work.openat2 branch from Al to resolve some of the conflicts with the differences in how open flags are built.

Now that you rebased on top of openat2, wouldn't it be better to add openat2 that to io_uring instead of the old openat call?

metze

author maintainer
#23 lore raw
Quoted 7 lines · Stefan Metzmacher

Am 07.01.20 um 18:00 schrieb Jens Axboe:

Quoted 3 lines

Sending this out separately, as I rebased it on top of the work.openat2 branch from Al to resolve some of the conflicts with the differences in how open flags are built.

Now that you rebased on top of openat2, wouldn't it be better to add openat2 that to io_uring instead of the old openat call?

The IORING_OP_OPENAT already exists, so it would probably make more sense to add IORING_OP_OPENAT2 alongside that. Or I could just change it. Don't really feel that strongly about it, I'll probably just add openat2 and leave openat alone, openat will just be a wrapper around openat2 anyway.

Signature · 1 line
Jens Axboe
#24 lore raw

Am 08.01.20 um 23:57 schrieb Jens Axboe:

Quoted 6 lines · Stefan Metzmacher
Quoted 5 lines

Am 07.01.20 um 18:00 schrieb Jens Axboe:

Quoted 3 lines

Sending this out separately, as I rebased it on top of the work.openat2 branch from Al to resolve some of the conflicts with the differences in how open flags are built.

Quoted 7 lines
Quoted 2 lines

Now that you rebased on top of openat2, wouldn't it be better to add openat2 that to io_uring instead of the old openat call?

The IORING_OP_OPENAT already exists, so it would probably make more sense to add IORING_OP_OPENAT2 alongside that. Or I could just change it. Don't really feel that strongly about it, I'll probably just add openat2 and leave openat alone, openat will just be a wrapper around openat2 anyway.

Great, thanks!
metze

author maintainer
#25 lore raw
Quoted 10 lines · Stefan Metzmacher

Am 08.01.20 um 23:57 schrieb Jens Axboe:

Quoted 9 lines · Stefan Metzmacher
Quoted 7 lines

Am 07.01.20 um 18:00 schrieb Jens Axboe:

Quoted 3 lines

Sending this out separately, as I rebased it on top of the work.openat2 branch from Al to resolve some of the conflicts with the differences in how open flags are built.

Now that you rebased on top of openat2, wouldn't it be better to add openat2 that to io_uring instead of the old openat call?

Quoted 6 lines
Quoted 4 lines

The IORING_OP_OPENAT already exists, so it would probably make more sense to add IORING_OP_OPENAT2 alongside that. Or I could just change it. Don't really feel that strongly about it, I'll probably just add openat2 and leave openat alone, openat will just be a wrapper around openat2 anyway.

Great, thanks!

Here:

https://git.kernel.dk/cgit/linux-block/log/?h=for-5.6/io_uring-vfs

Not tested yet, will wire this up in liburing and write a test case as well.

Signature · 1 line
Jens Axboe
author maintainer
#26 lore raw
Quoted 16 lines · Jens Axboe
Quoted 15 lines · Stefan Metzmacher

Am 08.01.20 um 23:57 schrieb Jens Axboe:

Quoted 13 lines · Stefan Metzmacher
Quoted 7 lines

Am 07.01.20 um 18:00 schrieb Jens Axboe:

Quoted 3 lines

Sending this out separately, as I rebased it on top of the work.openat2 branch from Al to resolve some of the conflicts with the differences in how open flags are built.

Now that you rebased on top of openat2, wouldn't it be better to add openat2 that to io_uring instead of the old openat call?

The IORING_OP_OPENAT already exists, so it would probably make more sense to add IORING_OP_OPENAT2 alongside that. Or I could just change it. Don't really feel that strongly about it, I'll probably just add openat2 and leave openat alone, openat will just be a wrapper around openat2 anyway.

Quoted 8 lines
Quoted 1 line

Great, thanks!

Here:

https://git.kernel.dk/cgit/linux-block/log/?h=for-5.6/io_uring-vfs

Not tested yet, will wire this up in liburing and write a test case as well.

Wrote a basic test case, and used my openbench as well. Seems to work fine for me. Pushed prep etc support to liburing.

Signature · 1 line
Jens Axboe
#27 lore raw

Am 09.01.20 um 03:03 schrieb Jens Axboe:

Quoted 21 lines · Jens Axboe
Quoted 20 lines · Stefan Metzmacher
Quoted 10 lines

Am 08.01.20 um 23:57 schrieb Jens Axboe:

Quoted 9 lines · Stefan Metzmacher
Quoted 7 lines

Am 07.01.20 um 18:00 schrieb Jens Axboe:

Quoted 3 lines

Sending this out separately, as I rebased it on top of the work.openat2 branch from Al to resolve some of the conflicts with the differences in how open flags are built.

Now that you rebased on top of openat2, wouldn't it be better to add openat2 that to io_uring instead of the old openat call?

Quoted 6 lines
Quoted 4 lines

The IORING_OP_OPENAT already exists, so it would probably make more sense to add IORING_OP_OPENAT2 alongside that. Or I could just change it. Don't really feel that strongly about it, I'll probably just add openat2 and leave openat alone, openat will just be a wrapper around openat2 anyway.

Great, thanks!

Here:

Quoted 7 lines
Quoted 4 lines

https://git.kernel.dk/cgit/linux-block/log/?h=for-5.6/io_uring-vfs

Not tested yet, will wire this up in liburing and write a test case as well.

Wrote a basic test case, and used my openbench as well. Seems to work fine for me. Pushed prep etc support to liburing.

Thanks!

Another great feature would the possibility to make use of the generated fd in the following request.

This is a feature that's also available in the SMB3 protocol called compound related requests.

The client can compound a chain with open, getinfo, read, close getinfo, read and close get an file handle of -1 and implicitly get the fd generated/used in the previous request.

metze

author maintainer
#28 lore raw
Quoted 35 lines · Stefan Metzmacher

Am 09.01.20 um 03:03 schrieb Jens Axboe:

Quoted 21 lines · Jens Axboe
Quoted 20 lines · Stefan Metzmacher
Quoted 10 lines

Am 08.01.20 um 23:57 schrieb Jens Axboe:

Quoted 9 lines · Stefan Metzmacher
Quoted 7 lines

Am 07.01.20 um 18:00 schrieb Jens Axboe:

Quoted 3 lines

Sending this out separately, as I rebased it on top of the work.openat2 branch from Al to resolve some of the conflicts with the differences in how open flags are built.

Now that you rebased on top of openat2, wouldn't it be better to add openat2 that to io_uring instead of the old openat call?

Quoted 6 lines
Quoted 4 lines

The IORING_OP_OPENAT already exists, so it would probably make more sense to add IORING_OP_OPENAT2 alongside that. Or I could just change it. Don't really feel that strongly about it, I'll probably just add openat2 and leave openat alone, openat will just be a wrapper around openat2 anyway.

Great, thanks!

Here:

Quoted 7 lines
Quoted 4 lines

https://git.kernel.dk/cgit/linux-block/log/?h=for-5.6/io_uring-vfs

Not tested yet, will wire this up in liburing and write a test case as well.

Wrote a basic test case, and used my openbench as well. Seems to work fine for me. Pushed prep etc support to liburing.

Thanks!

Another great feature would the possibility to make use of the generated fd in the following request.

Quoted 6 lines

This is a feature that's also available in the SMB3 protocol called compound related requests.

The client can compound a chain with open, getinfo, read, close getinfo, read and close get an file handle of -1 and implicitly get the fd generated/used in the previous request.

Right, the "plan" there is to utilize BPF to make this programmable. We really need something more expressive to be able to pass information between SQEs that are linked, or even to decide which link to run depending on the outcome of the parent.

There's a lot of potential there!

Signature · 1 line
Jens Axboe
#29 lore raw

Hi Jens,

Quoted 12 lines
Quoted 11 lines

Thanks!

Another great feature would the possibility to make use of the generated fd in the following request.

This is a feature that's also available in the SMB3 protocol called compound related requests.

The client can compound a chain with open, getinfo, read, close getinfo, read and close get an file handle of -1 and implicitly get the fd generated/used in the previous request.

Quoted 6 lines

Right, the "plan" there is to utilize BPF to make this programmable. We really need something more expressive to be able to pass information between SQEs that are linked, or even to decide which link to run depending on the outcome of the parent.

There's a lot of potential there!

I guess so, but I don't yet understand how BPF works in real life.

Is it possible to do that as normal user without special privileges?

My naive way would be using some flags and get res and pass fd by reference.

metze

#30 lore raw
Quoted 19 lines · Stefan Metzmacher

Hi Jens,

Quoted 17 lines
Quoted 11 lines

Thanks!

Another great feature would the possibility to make use of the generated fd in the following request.

This is a feature that's also available in the SMB3 protocol called compound related requests.

The client can compound a chain with open, getinfo, read, close getinfo, read and close get an file handle of -1 and implicitly get the fd generated/used in the previous request.

Right, the "plan" there is to utilize BPF to make this programmable. We really need something more expressive to be able to pass information between SQEs that are linked, or even to decide which link to run depending on the outcome of the parent.

Quoted 8 lines
Quoted 1 line

There's a lot of potential there!

I guess so, but I don't yet understand how BPF works in real life.

Is it possible to do that as normal user without special privileges?

My naive way would be using some flags and get res and pass fd by reference.

Just have been discussing related stuff. See the link if curious
https://github.com/axboe/liburing/issues/58

To summarise, there won't be enough flags to cover all use-cases and it will slow down the common path. There should be something with zero-overhead if the feature is not used, and that's not the case with flags. That's why it'd be great to have a custom eBPF program (in-kernel) controlling what and how to do next.

I don't much about eBPF internals, but probably we will be able to attach an eBPF program to io_uring instance. Though, not sure whether it could be done without privileges.

Signature · 1 line
Pavel Begunkov
#31 lore raw
In reply to Stefan Metzmacher
Quoted 4 lines · Stefan Metzmacher

The client can compound a chain with open, getinfo, read, close getinfo, read and close get an file handle of -1 and implicitly get the fd generated/used in the previous request.

Sounds similar to https://capnproto.org/rpc.html too.

But that seems most valuable in a situation with nontrivial latency.

author maintainer
#32 lore raw
Quoted 9 lines · Colin Walters
Quoted 4 lines · Stefan Metzmacher

The client can compound a chain with open, getinfo, read, close getinfo, read and close get an file handle of -1 and implicitly get the fd generated/used in the previous request.

Sounds similar to https://capnproto.org/rpc.html too.

Never heard of it, but I don't see how you can do any of this in an efficient manner without kernel support. Unless it's just wrapping the communication in its own protocol and using that as the basis for the on-wire part. It has "infinitely faster" in a yellow sticker, so it must work :-)

Quoted 1 line

But that seems most valuable in a situation with nontrivial latency.

Which is basically what Stefan is looking at, over the network open, read, close etc is pretty nondeterministic. But even for local storage, being able to setup a bundle like that and have it automagically work makes for easier programming and more efficient communication with the kernel.

Signature · 1 line
Jens Axboe
#33 lore raw
In reply to Colin Walters
Quoted 7 lines · Colin Walters
Quoted 3 lines · Stefan Metzmacher

The client can compound a chain with open, getinfo, read, close getinfo, read and close get an file handle of -1 and implicitly get the fd generated/used in the previous request.

Sounds similar to https://capnproto.org/rpc.html too.

Looks like just grouping a pack of operations for RPC.
With io_uring we could implement more interesting stuff. I've been thinking about eBPF in io_uring for a while as well, and apparently it could be _really_ powerful, and would allow almost zero-context-switches for some usecases.

1. full flow control with eBPF
- dropping requests (links)
- emitting reqs/links (e.g. after completions of another req)
- chaining/redirecting
of course, all of that with fast intermediate computations in between

2. do long eBPF programs by introducing a new opcode (punted to async). (though, there would be problems with that)

Could even allow to dynamically register new opcodes within the kernel and extend it to eBPF, if there will be demand for such things.

Signature · 1 line
Pavel Begunkov
author maintainer
#34 lore raw
Quoted 20 lines · Pavel Begunkov
Quoted 7 lines · Colin Walters
Quoted 3 lines · Stefan Metzmacher

The client can compound a chain with open, getinfo, read, close getinfo, read and close get an file handle of -1 and implicitly get the fd generated/used in the previous request.

Sounds similar to https://capnproto.org/rpc.html too.

Looks like just grouping a pack of operations for RPC.
With io_uring we could implement more interesting stuff. I've been thinking about eBPF in io_uring for a while as well, and apparently it could be _really_ powerful, and would allow almost zero-context-switches for some usecases.

1. full flow control with eBPF
- dropping requests (links)
- emitting reqs/links (e.g. after completions of another req)
- chaining/redirecting
of course, all of that with fast intermediate computations in between

Quoted 5 lines

2. do long eBPF programs by introducing a new opcode (punted to async). (though, there would be problems with that)

Could even allow to dynamically register new opcodes within the kernel and extend it to eBPF, if there will be demand for such things.

We're also looking into exactly that at Facebook, nothing concrete yet though. But it's clear we need it to take full advantage of links at least, and it's also clear that it would unlock a lot of really cool functionality once we do.

Pavel, I'd strongly urge you to submit a talk to LSF/MM/BPF about this. It's the perfect venue to have some concrete planning around this topic and get things rolling.

https://lore.kernel.org/bpf/20191122172502.vffyfxlqejthjib6@macbook-pro-91.dhcp.thefacebook.com/

Signature · 1 line
Jens Axboe
#35 lore raw
Quoted 27 lines · Jens Axboe
Quoted 20 lines · Pavel Begunkov
Quoted 7 lines · Colin Walters
Quoted 3 lines · Stefan Metzmacher

The client can compound a chain with open, getinfo, read, close getinfo, read and close get an file handle of -1 and implicitly get the fd generated/used in the previous request.

Sounds similar to https://capnproto.org/rpc.html too.

Looks like just grouping a pack of operations for RPC.
With io_uring we could implement more interesting stuff. I've been thinking about eBPF in io_uring for a while as well, and apparently it could be _really_ powerful, and would allow almost zero-context-switches for some usecases.

1. full flow control with eBPF
- dropping requests (links)
- emitting reqs/links (e.g. after completions of another req)
- chaining/redirecting
of course, all of that with fast intermediate computations in between

Quoted 5 lines

2. do long eBPF programs by introducing a new opcode (punted to async). (though, there would be problems with that)

Could even allow to dynamically register new opcodes within the kernel and extend it to eBPF, if there will be demand for such things.

Quoted 8 lines

We're also looking into exactly that at Facebook, nothing concrete yet though. But it's clear we need it to take full advantage of links at least, and it's also clear that it would unlock a lot of really cool functionality once we do.

Pavel, I'd strongly urge you to submit a talk to LSF/MM/BPF about this. It's the perfect venue to have some concrete planning around this topic and get things rolling.

Sounds interesting, I'll try this, but didn't you intend to do it yourself? And thanks for the tip!

Quoted 3 lines

https://lore.kernel.org/bpf/20191122172502.vffyfxlqejthjib6@macbook-pro-91.dhcp.thefacebook.com/

Signature · 1 line
Pavel Begunkov
author maintainer
#36 lore raw
Quoted 33 lines · Pavel Begunkov
Quoted 32 lines · Jens Axboe
Quoted 20 lines · Pavel Begunkov
Quoted 7 lines · Colin Walters
Quoted 3 lines · Stefan Metzmacher

The client can compound a chain with open, getinfo, read, close getinfo, read and close get an file handle of -1 and implicitly get the fd generated/used in the previous request.

Sounds similar to https://capnproto.org/rpc.html too.

Looks like just grouping a pack of operations for RPC.
With io_uring we could implement more interesting stuff. I've been thinking about eBPF in io_uring for a while as well, and apparently it could be _really_ powerful, and would allow almost zero-context-switches for some usecases.

1. full flow control with eBPF
- dropping requests (links)
- emitting reqs/links (e.g. after completions of another req)
- chaining/redirecting
of course, all of that with fast intermediate computations in between

Quoted 5 lines

2. do long eBPF programs by introducing a new opcode (punted to async). (though, there would be problems with that)

Could even allow to dynamically register new opcodes within the kernel and extend it to eBPF, if there will be demand for such things.

We're also looking into exactly that at Facebook, nothing concrete yet though. But it's clear we need it to take full advantage of links at least, and it's also clear that it would unlock a lot of really cool functionality once we do.

Quoted 6 lines
Quoted 3 lines

Pavel, I'd strongly urge you to submit a talk to LSF/MM/BPF about this. It's the perfect venue to have some concrete planning around this topic and get things rolling.

Sounds interesting, I'll try this, but didn't you intend to do it yourself? And thanks for the tip!

Just trying to delegate a bit, and I think you'd be a great candidate to drive this. I'll likely do some other io_uring related topic there.

Signature · 1 line
Jens Axboe