Merge branch 'Pr1' of https://github.com/romange/liburing

* 'Pr1' of https://github.com/romange/liburing:
  Add const modifier to functions that do not change the state of the ring
diff --git a/.gitignore b/.gitignore
index 8f7f369..d9fa2c2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -80,11 +80,15 @@
 /test/poll-cancel-ton
 /test/poll-link
 /test/poll-many
+/test/poll-ring
 /test/poll-v-poll
 /test/probe
 /test/read-write
 /test/register-restrictions
+/test/rename
 /test/ring-leak
+/test/ring-leak2
+/test/self
 /test/send_recv
 /test/send_recvmsg
 /test/shared-wq
@@ -96,15 +100,21 @@
 /test/splice
 /test/sq-full
 /test/sq-full-cpp
+/test/sq-poll-dup
 /test/sq-poll-kthread
 /test/sq-poll-share
+/test/sqpoll-exit-hang
+/test/sqpoll-sleep
 /test/sq-space_left
 /test/statx
 /test/stdout
 /test/submit-reuse
 /test/teardowns
+/test/thread-exit
 /test/timeout
+/test/timeout-new
 /test/timeout-overflow
+/test/unlink
 /test/wakeup-hang
 /test/*.dmesg
 
diff --git a/Makefile b/Makefile
index c761604..5d9c4dc 100644
--- a/Makefile
+++ b/Makefile
@@ -56,6 +56,8 @@
 	$(INSTALL) -m 644 man/*.2 $(DESTDIR)$(mandir)/man2
 	$(INSTALL) -m 755 -d $(DESTDIR)$(mandir)/man3
 	$(INSTALL) -m 644 man/*.3 $(DESTDIR)$(mandir)/man3
+	$(INSTALL) -m 755 -d $(DESTDIR)$(mandir)/man7
+	$(INSTALL) -m 644 man/*.7 $(DESTDIR)$(mandir)/man7
 
 install-tests:
 	@$(MAKE) -C test install prefix=$(DESTDIR)$(prefix) datadir=$(DESTDIR)$(datadir)
diff --git a/configure b/configure
index 518a5b0..3b96cde 100755
--- a/configure
+++ b/configure
@@ -1,13 +1,4 @@
 #!/bin/sh
-#
-# set temporary file name
-if test ! -z "$TMPDIR" ; then
-    TMPDIR1="${TMPDIR}"
-elif test ! -z "$TEMPDIR" ; then
-    TMPDIR1="${TEMPDIR}"
-else
-    TMPDIR1="/tmp"
-fi
 
 cc=${CC:-gcc}
 cxx=${CXX:-g++}
@@ -82,10 +73,10 @@
 exit 0
 fi
 
-TMPC="${TMPDIR1}/fio-conf-${RANDOM}-$$-${RANDOM}.c"
-TMPC2="${TMPDIR1}/fio-conf-${RANDOM}-$$-${RANDOM}-2.c"
-TMPO="${TMPDIR1}/fio-conf-${RANDOM}-$$-${RANDOM}.o"
-TMPE="${TMPDIR1}/fio-conf-${RANDOM}-$$-${RANDOM}.exe"
+TMPC="$(mktemp --tmpdir fio-conf-XXXXXXXXXX.c)"
+TMPC2="$(mktemp --tmpdir fio-conf-XXXXXXXXXX-2.c)"
+TMPO="$(mktemp --tmpdir fio-conf-XXXXXXXXXX.o)"
+TMPE="$(mktemp --tmpdir fio-conf-XXXXXXXXXX.exe)"
 
 # NB: do not call "exit" in the trap handler; this is buggy with some shells;
 # see <1285349658-3122-1-git-send-email-loic.minier@linaro.org>
@@ -296,6 +287,24 @@
 fi
 print_config "C++" "$has_cxx"
 
+##########################################
+# check for ucontext support
+has_ucontext="no"
+cat > $TMPC << EOF
+#include <ucontext.h>
+int main(int argc, char **argv)
+{
+  ucontext_t ctx;
+  getcontext(&ctx);
+  return 0;
+}
+EOF
+if compile_prog "" "" "has_ucontext"; then
+  has_ucontext="yes"
+fi
+print_config "has_ucontext" "$has_ucontext"
+
+
 #############################################################################
 
 if test "$__kernel_rwf_t" = "yes"; then
@@ -313,6 +322,9 @@
 if test "$has_cxx" = "yes"; then
   output_sym "CONFIG_HAVE_CXX"
 fi
+if test "$has_ucontext" = "yes"; then
+  output_sym "CONFIG_HAVE_UCONTEXT"
+fi
 
 echo "CC=$cc" >> $config_host_mak
 print_config "CC" "$cc"
diff --git a/debian/liburing-dev.manpages b/debian/liburing-dev.manpages
index a3d21be..fbbee23 100644
--- a/debian/liburing-dev.manpages
+++ b/debian/liburing-dev.manpages
@@ -3,3 +3,4 @@
 man/io_uring_register.2
 man/io_uring_queue_exit.3
 man/io_uring_queue_init.3
+man/io_uring_get_sqe.3
diff --git a/examples/Makefile b/examples/Makefile
index 28456a9..60c1b71 100644
--- a/examples/Makefile
+++ b/examples/Makefile
@@ -1,6 +1,8 @@
+CPPFLAGS ?=
+override CPPFLAGS += -D_GNU_SOURCE -I../src/include/
 CFLAGS ?= -g -O2
 XCFLAGS =
-override CFLAGS += -Wall -D_GNU_SOURCE -L../src/ -I../src/include/
+override CFLAGS += -Wall -L../src/
 
 include ../Makefile.quiet
 
@@ -8,7 +10,11 @@
 include ../config-host.mak
 endif
 
-all_targets += io_uring-test io_uring-cp link-cp ucontext-cp
+all_targets += io_uring-test io_uring-cp link-cp
+
+ifdef CONFIG_HAVE_UCONTEXT
+all_targets += ucontext-cp
+endif
 
 all: $(all_targets)
 
@@ -17,7 +23,7 @@
 test_objs := $(patsubst %.c,%.ol,$(test_srcs))
 
 %: %.c
-	$(QUIET_CC)$(CC) $(CFLAGS) -o $@ $< -luring $(XCFLAGS)
+	$(QUIET_CC)$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $< -luring $(XCFLAGS)
 
 clean:
 	@rm -f $(all_targets) $(test_objs)
diff --git a/liburing.spec b/liburing.spec
index 9cbbc61..fa4d970 100644
--- a/liburing.spec
+++ b/liburing.spec
@@ -45,6 +45,7 @@
 %{_libdir}/pkgconfig/*
 %{_mandir}/man2/*
 %{_mandir}/man3/*
+%{_mandir}/man7/*
 
 %changelog
 * Thu Oct 31 2019 Jeff Moyer <jmoyer@redhat.com> - 0.2-1
diff --git a/man/io_uring.7 b/man/io_uring.7
index d371afb..a63b3e9 100644
--- a/man/io_uring.7
+++ b/man/io_uring.7
@@ -637,7 +637,7 @@
     /* Get the entry */
     cqe = &cqes[head & (*cring_mask)];
     if (cqe->res < 0)
-        fprintf(stderr, "Error: %s\n", strerror(abs(cqe->res)));
+        fprintf(stderr, "Error: %s\\n", strerror(abs(cqe->res)));
 
     head++;
 
@@ -698,7 +698,7 @@
 
     /* Setup io_uring for use */
     if(app_setup_uring()) {
-        fprintf(stderr, "Unable to setup uring!\n");
+        fprintf(stderr, "Unable to setup uring!\\n");
         return 1;
     }
 
@@ -721,7 +721,7 @@
         }
         else if (res < 0) {
             /* Error reading file */
-            fprintf(stderr, "Error: %s\n", strerror(abs(res)));
+            fprintf(stderr, "Error: %s\\n", strerror(abs(res)));
             break;
         }
         offset += res;
diff --git a/man/io_uring_enter.2 b/man/io_uring_enter.2
index 15a5a4a..086207d 100644
--- a/man/io_uring_enter.2
+++ b/man/io_uring_enter.2
@@ -29,15 +29,35 @@
 is the file descriptor returned by
 .BR io_uring_setup (2).
 .I to_submit
-specifies the number of I/Os to submit from the submission queue.  If
-the
+specifies the number of I/Os to submit from the submission queue.
+.I flags
+is a bitmask of the following values:
+.TP
 .B IORING_ENTER_GETEVENTS
-bit is set in
-.IR flags ,
-then the system call will attempt to wait for
+If this flag is set, then the system call will wait for the specificied
+number of events in
 .I min_complete
-event completions before returning.  If the io_uring instance was
-configured for polling, by specifying
+before returning. This flag can be set along with
+.I to_submit
+to both submit and complete events in a single system call.
+.TP
+.B IORING_ENTER_SQ_WAKEUP
+If the ring has been created with
+.B IORING_SETUP_SQPOLL,
+then this flag asks the kernel to wakeup the SQ kernel thread to submit IO.
+.TP
+.B IORING_ENTER_SQ_WAIT
+If the ring has been created with
+.B IORING_SETUP_SQPOLL,
+then the application has no real insight into when the SQ kernel thread has
+consumed entries from the SQ ring. This can lead to a situation where the
+application can no longer get a free SQE entry to submit, without knowing
+when it one becomes available as the SQ kernel thread consumes them. If
+the system call is used with this flag set, then it will wait until at least
+one entry is free in the SQ ring.
+.PP
+.PP
+If the io_uring instance was configured for polling, by specifying
 .B IORING_SETUP_IOPOLL
 in the call to
 .BR io_uring_setup (2),
@@ -206,7 +226,10 @@
 field.  Unlike poll or epoll without
 .BR EPOLLONESHOT ,
 this interface always works in one shot mode.  That is, once the poll
-operation is completed, it will have to be resubmitted.
+operation is completed, it will have to be resubmitted. This command works like
+an async
+.BR poll(2)
+and the completion event result is the returned mask of events.
 
 .TP
 .B IORING_OP_POLL_REMOVE
@@ -322,7 +345,9 @@
 
 .TP
 .B IORING_OP_TIMEOUT_REMOVE
-Attempt to remove an existing timeout operation.
+If
+.I timeout_flags are zero, then it attempts to remove an existing timeout
+operation.
 .I addr
 must contain the
 .I user_data
@@ -338,6 +363,19 @@
 .I -ENOENT
 Available since 5.5.
 
+If
+.I timeout_flags
+contain
+.I IORING_TIMEOUT_UPDATE,
+instead of removing an existing operation it updates it.
+.I addr
+and return values are same as before.
+.I addr2
+field must contain a pointer to a struct timespec64 structure.
+.I timeout_flags
+may also contain IORING_TIMEOUT_ABS.
+Available since 5.11.
+
 .TP
 .B IORING_OP_ACCEPT
 Issue the equivalent of an
@@ -415,9 +453,11 @@
 system call.
 .I fd
 must be set to the file descriptor,
+.I len
+must contain the mode associated with the operation,
 .I off
 must contain the offset on which to operate, and
-.I len
+.I addr
 must contain the length. See also
 .BR fallocate(2)
 for the general description of the related system call. Available since 5.6.
@@ -663,6 +703,62 @@
 must contain the buffer group ID from which to remove the buffers. Available
 since 5.7.
 
+.TP
+.B IORING_OP_SHUTDOWN
+Issue the equivalent of a
+.BR shutdown(2)
+system call.
+.I fd
+is the file descriptor to the socket being shutdown, no other fields should
+be set. Available since 5.11.
+
+.TP
+.B IORING_OP_RENAMEAT
+Issue the equivalent of a
+.BR renameat2(2)
+system call.
+.I fd
+should be set to the
+.I olddirfd,
+.I addr
+should be set to the
+.I oldpath,
+.I len
+should be set to the
+.I newdirfd,
+.I addr
+should be set to the
+.I oldpath,
+.I addr2
+should be set to the
+.I newpath,
+and finally
+.I rename_flags
+should be set to the
+.I flags
+passed in to
+.BR renameat2(2).
+Available since 5.11.
+
+.TP
+.B IORING_OP_UNLINKAT
+Issue the equivalent of a
+.BR unlinkat2(2)
+system call.
+.I fd
+should be set to the
+.I dirfd,
+.I addr
+should be set to the
+.I pathname,
+and
+.I unlink_flags
+should be set to the
+.I flags
+being passed in to
+.BR unlinkat(2).
+Available since 5.11.
+
 .PP
 The
 .I flags
diff --git a/man/io_uring_get_sqe.3 b/man/io_uring_get_sqe.3
new file mode 100644
index 0000000..24834f3
--- /dev/null
+++ b/man/io_uring_get_sqe.3
@@ -0,0 +1,37 @@
+.\" Copyright (C) 2020 Jens Axboe <axboe@kernel.dk>
+.\" Copyright (C) 2020 Red Hat, Inc.
+.\"
+.\" SPDX-License-Identifier: LGPL-2.0-or-later
+.\"
+.TH io_uring_get_sqe 3 "July 10, 2020" "liburing-0.7" "liburing Manual"
+.SH NAME
+io_uring_get_sqe - get the next vacant event from the submission queue
+.SH SYNOPSIS
+.nf
+.BR "#include <liburing.h>"
+.PP
+.BI "struct io_uring_sqe *io_uring_get_sqe(struct io_uring " *ring );
+.fi
+.PP
+.SH DESCRIPTION
+.PP
+The io_uring_get_sqe() function gets the next vacant event from the submission
+queue belonging to the
+.I ring
+param.
+
+On success io_uring_get_sqe() returns a pointer to the submission queue event.
+On failure NULL is returned.
+
+If a submission queue event is returned, it should be filled out via one of the
+prep functions such as
+.BR io_uring_prep_read (3)
+and submitted via
+.BR io_uring_submit (3).
+
+.SH RETURN VALUE
+.BR io_uring_get_sqe (3)
+returns a pointer to the next submission queue event on success and NULL on
+failure.
+.SH SEE ALSO
+.BR io_uring_submit (3)
diff --git a/man/io_uring_queue_exit.3 b/man/io_uring_queue_exit.3
index 835eb5d..8bdda6a 100644
--- a/man/io_uring_queue_exit.3
+++ b/man/io_uring_queue_exit.3
@@ -3,8 +3,7 @@
 .\"
 .\" SPDX-License-Identifier: LGPL-2.0-or-later
 .\"
-.\" TODO: Fix the version number/date here
-.TH io_uring_queue_exit 3 "September 11, 2020" "liburing 1.0.0" "liburing Manual"
+.TH io_uring_queue_exit 3 "July 10, 2020" "liburing-0.7" "liburing Manual"
 .SH NAME
 io_uring_queue_exit - tear down io_uring submission and completion queues
 .SH SYNOPSIS
diff --git a/man/io_uring_queue_init.3 b/man/io_uring_queue_init.3
index 1a62899..1980fa4 100644
--- a/man/io_uring_queue_init.3
+++ b/man/io_uring_queue_init.3
@@ -3,8 +3,7 @@
 .\"
 .\" SPDX-License-Identifier: LGPL-2.0-or-later
 .\"
-.\" TODO: Fix the version number/date here
-.TH io_uring_queue_init 3 "September 11, 2020" "liburing 1.0.0" "liburing Manual"
+.TH io_uring_queue_init 3 "July 10, 2020" "liburing-0.7" "liburing Manual"
 .SH NAME
 io_uring_queue_init - setup io_uring submission and completion queues
 .SH SYNOPSIS
diff --git a/man/io_uring_register.2 b/man/io_uring_register.2
index 225e461..5326a87 100644
--- a/man/io_uring_register.2
+++ b/man/io_uring_register.2
@@ -144,6 +144,7 @@
 ones, either turning a sparse entry (one where fd is equal to -1) into a
 real one, removing an existing entry (new one is set to -1), or replacing
 an existing entry with a new existing entry.
+
 .I arg
 must contain a pointer to a struct io_uring_files_update, which contains
 an offset on which to start the update, and an array of file descriptors to
@@ -152,6 +153,12 @@
 must contain the number of descriptors in the passed in array. Available
 since 5.5.
 
+File descriptors can be skipped if they are set to
+.B IORING_REGISTER_FILES_SKIP.
+Skipping an fd will not touch the file associated with the previous
+fd at that index. Available since 5.12.
+
+
 .TP
 .B IORING_UNREGISTER_FILES
 This operation requires no argument, and
diff --git a/man/io_uring_setup.2 b/man/io_uring_setup.2
index a903b04..3122313 100644
--- a/man/io_uring_setup.2
+++ b/man/io_uring_setup.2
@@ -116,7 +116,7 @@
 described below.
 .TP
 .BR
-Before version 5.10 of the Linux kernel, to successfully use this feature, the
+Before version 5.11 of the Linux kernel, to successfully use this feature, the
 application must register a set of files to be used for IO through
 .BR io_uring_register (2)
 using the
@@ -126,8 +126,8 @@
 The presence of this feature can be detected by the
 .B IORING_FEAT_SQPOLL_NONFIXED
 feature flag.
-In version 5.10 and later, it is no longer necessary to register files to use
-this feature. 5.10 also allows using this as non-root, if the user has the
+In version 5.11 and later, it is no longer necessary to register files to use
+this feature. 5.11 also allows using this as non-root, if the user has the
 .B CAP_SYS_NICE
 capability.
 .TP
@@ -256,7 +256,7 @@
 If this flag is set, then io_uring supports using an internal poll mechanism
 to drive data/space readiness. This means that requests that cannot read or
 write data to a file no longer need to be punted to an async thread for
-handling, instead they will being operation when the file is ready. This is
+handling, instead they will begin operation when the file is ready. This is
 similar to doing poll + read/write in userspace, but eliminates the need to do
 so. If this flag is set, requests waiting on space/data consume a lot less
 resources doing so as they are not blocking a thread.
diff --git a/src/Makefile b/src/Makefile
index 4a28d25..dfca826 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -3,9 +3,10 @@
 libdir ?= $(prefix)/lib
 libdevdir ?= $(prefix)/lib
 
+CPPFLAGS ?=
+override CPPFLAGS += -Iinclude/ -include ../config-host.h
 CFLAGS ?= -g -fomit-frame-pointer -O2
-override CFLAGS += -Wall -Wextra -Wno-unused-parameter -Wno-sign-compare\
-	-Iinclude/ -include ../config-host.h
+override CFLAGS += -Wall -Wextra -Wno-unused-parameter -Wno-sign-compare
 SO_CFLAGS=-fPIC $(CFLAGS)
 L_CFLAGS=$(CFLAGS)
 LINK_FLAGS=
@@ -38,10 +39,10 @@
 $(liburing_objs) $(liburing_sobjs): include/liburing/io_uring.h
 
 %.os: %.c
-	$(QUIET_CC)$(CC) $(SO_CFLAGS) -c -o $@ $<
+	$(QUIET_CC)$(CC) $(CPPFLAGS) $(SO_CFLAGS) -c -o $@ $<
 
 %.ol: %.c
-	$(QUIET_CC)$(CC) $(L_CFLAGS) -c -o $@ $<
+	$(QUIET_CC)$(CC) $(CPPFLAGS) $(L_CFLAGS) -c -o $@ $<
 
 AR ?= ar
 RANLIB ?= ranlib
diff --git a/src/include/liburing.h b/src/include/liburing.h
index 014d504..6b2cfa1 100644
--- a/src/include/liburing.h
+++ b/src/include/liburing.h
@@ -2,6 +2,10 @@
 #ifndef LIB_URING_H
 #define LIB_URING_H
 
+#ifndef _XOPEN_SOURCE
+#define _XOPEN_SOURCE 500 /* Required for glibc to expose sigset_t */
+#endif
+
 #include <sys/socket.h>
 #include <sys/uio.h>
 #include <sys/stat.h>
@@ -15,6 +19,14 @@
 #include "liburing/io_uring.h"
 #include "liburing/barrier.h"
 
+#ifndef uring_unlikely
+#  define uring_unlikely(cond)      __builtin_expect(!!(cond), 0)
+#endif
+
+#ifndef uring_likely
+#  define uring_likely(cond)        __builtin_expect(!!(cond), 1)
+#endif
+
 #ifdef __cplusplus
 extern "C" {
 #endif
@@ -62,7 +74,8 @@
 	unsigned flags;
 	int ring_fd;
 
-	unsigned pad[4];
+	unsigned features;
+	unsigned pad[3];
 };
 
 /*
@@ -214,6 +227,25 @@
 	sqe->__pad2[0] = sqe->__pad2[1] = sqe->__pad2[2] = 0;
 }
 
+/**
+ * @pre Either fd_in or fd_out must be a pipe.
+ * @param off_in If fd_in refers to a pipe, off_in must be (int64_t) -1;
+ *               If fd_in does not refer to a pipe and off_in is (int64_t) -1, then bytes are read
+ *               from fd_in starting from the file offset and it is adjust appropriately;
+ *               If fd_in does not refer to a pipe and off_in is not (int64_t) -1, then the
+ *               starting offset of fd_in will be off_in.
+ * @param off_out The description of off_in also applied to off_out.
+ * @param splice_flags see man splice(2) for description of flags.
+ *
+ * This splice operation can be used to implement sendfile by splicing to an intermediate pipe
+ * first, then splice to the final destination.
+ * In fact, the implementation of sendfile in kernel uses splice internally.
+ *
+ * NOTE that even if fd_in or fd_out refers to a pipe, the splice operation can still failed with
+ * EINVAL if one of the fd doesn't explicitly support splice operation, e.g. reading from terminal
+ * is unsupported from kernel 5.7 to 5.11.
+ * Check issue #291 for more information.
+ */
 static inline void io_uring_prep_splice(struct io_uring_sqe *sqe,
 					int fd_in, int64_t off_in,
 					int fd_out, int64_t off_out,
@@ -325,6 +357,16 @@
 	sqe->timeout_flags = flags;
 }
 
+static inline void io_uring_prep_timeout_update(struct io_uring_sqe *sqe,
+						struct __kernel_timespec *ts,
+						__u64 user_data, unsigned flags)
+{
+	io_uring_prep_rw(IORING_OP_TIMEOUT_REMOVE, sqe, -1,
+				(void *)(unsigned long)user_data, 0,
+				(uintptr_t)ts);
+	sqe->timeout_flags = flags | IORING_TIMEOUT_UPDATE;
+}
+
 static inline void io_uring_prep_accept(struct io_uring_sqe *sqe, int fd,
 					struct sockaddr *addr,
 					socklen_t *addrlen, int flags)
@@ -469,6 +511,22 @@
 	io_uring_prep_rw(IORING_OP_SHUTDOWN, sqe, fd, NULL, how, 0);
 }
 
+static inline void io_uring_prep_unlinkat(struct io_uring_sqe *sqe, int dfd,
+					  const char *path, int flags)
+{
+	io_uring_prep_rw(IORING_OP_UNLINKAT, sqe, dfd, path, 0, 0);
+	sqe->unlink_flags = flags;
+}
+
+static inline void io_uring_prep_renameat(struct io_uring_sqe *sqe, int olddfd,
+					  const char *oldpath, int newdfd,
+					  const char *newpath, int flags)
+{
+	io_uring_prep_rw(IORING_OP_RENAMEAT, sqe, olddfd, oldpath, newdfd,
+				(uint64_t) (uintptr_t) newpath);
+	sqe->rename_flags = flags;
+}
+
 /*
  * Returns number of unconsumed (if SQPOLL) or unsubmitted entries exist in
  * the SQ ring
diff --git a/src/include/liburing/barrier.h b/src/include/liburing/barrier.h
index a4a59fb..89ac682 100644
--- a/src/include/liburing/barrier.h
+++ b/src/include/liburing/barrier.h
@@ -56,17 +56,17 @@
 #include <stdatomic.h>
 
 #define IO_URING_WRITE_ONCE(var, val)				\
-	atomic_store_explicit((_Atomic typeof(var) *)&(var),	\
+	atomic_store_explicit((_Atomic __typeof__(var) *)&(var),	\
 			      (val), memory_order_relaxed)
 #define IO_URING_READ_ONCE(var)					\
-	atomic_load_explicit((_Atomic typeof(var) *)&(var),	\
+	atomic_load_explicit((_Atomic __typeof__(var) *)&(var),	\
 			     memory_order_relaxed)
 
 #define io_uring_smp_store_release(p, v)			\
-	atomic_store_explicit((_Atomic typeof(*(p)) *)(p), (v), \
+	atomic_store_explicit((_Atomic __typeof__(*(p)) *)(p), (v), \
 			      memory_order_release)
 #define io_uring_smp_load_acquire(p)				\
-	atomic_load_explicit((_Atomic typeof(*(p)) *)(p),	\
+	atomic_load_explicit((_Atomic __typeof__(*(p)) *)(p),	\
 			     memory_order_acquire)
 #endif
 
diff --git a/src/include/liburing/io_uring.h b/src/include/liburing/io_uring.h
index d23718f..1d47389 100644
--- a/src/include/liburing/io_uring.h
+++ b/src/include/liburing/io_uring.h
@@ -46,6 +46,8 @@
 		__u32		statx_flags;
 		__u32		fadvise_advice;
 		__u32		splice_flags;
+		__u32		rename_flags;
+		__u32		unlink_flags;
 	};
 	__u64	user_data;	/* data to be passed back at completion time */
 	union {
@@ -137,6 +139,9 @@
 	IORING_OP_REMOVE_BUFFERS,
 	IORING_OP_TEE,
 	IORING_OP_SHUTDOWN,
+	IORING_OP_RENAMEAT,
+	IORING_OP_UNLINKAT,
+	IORING_OP_MKDIRAT,
 
 	/* this goes last, obviously */
 	IORING_OP_LAST,
@@ -151,6 +156,7 @@
  * sqe->timeout_flags
  */
 #define IORING_TIMEOUT_ABS	(1U << 0)
+#define IORING_TIMEOUT_UPDATE	(1U << 1)
 
 /*
  * sqe->splice_flags
@@ -231,6 +237,7 @@
 #define IORING_ENTER_GETEVENTS	(1U << 0)
 #define IORING_ENTER_SQ_WAKEUP	(1U << 1)
 #define IORING_ENTER_SQ_WAIT	(1U << 2)
+#define IORING_ENTER_EXT_ARG	(1U << 3)
 
 /*
  * Passed in for io_uring_setup(2). Copied back with updated info on success
@@ -259,6 +266,7 @@
 #define IORING_FEAT_FAST_POLL		(1U << 5)
 #define IORING_FEAT_POLL_32BITS 	(1U << 6)
 #define IORING_FEAT_SQPOLL_NONFIXED	(1U << 7)
+#define IORING_FEAT_EXT_ARG		(1U << 8)
 
 /*
  * io_uring_register(2) opcodes and arguments
@@ -288,6 +296,9 @@
 	__aligned_u64 /* __s32 * */ fds;
 };
 
+/* Skip updating fd indexes set to this value in the fd table */
+#define IORING_REGISTER_FILES_SKIP	(-2)
+
 #define IO_URING_OP_SUPPORTED	(1U << 0)
 
 struct io_uring_probe_op {
@@ -302,7 +313,7 @@
 	__u8 ops_len;	/* length of ops[] array below */
 	__u16 resv;
 	__u32 resv2[3];
-	struct io_uring_probe_op ops[0];
+	struct io_uring_probe_op ops[];
 };
 
 struct io_uring_restriction {
@@ -335,6 +346,12 @@
 	IORING_RESTRICTION_LAST
 };
 
+struct io_uring_getevents_arg {
+	__u64	sigmask;
+	__u32	sigmask_sz;
+	__u32	pad;
+	__u64	ts;
+};
 
 #ifdef __cplusplus
 }
diff --git a/src/queue.c b/src/queue.c
index 24fde2d..ce5d237 100644
--- a/src/queue.c
+++ b/src/queue.c
@@ -1,4 +1,6 @@
 /* SPDX-License-Identifier: MIT */
+#define _POSIX_C_SOURCE 200112L
+
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <sys/mman.h>
@@ -23,7 +25,9 @@
 {
 	if (!(ring->flags & IORING_SETUP_SQPOLL))
 		return true;
-	if (IO_URING_READ_ONCE(*ring->sq.kflags) & IORING_SQ_NEED_WAKEUP) {
+
+	if (uring_unlikely(IO_URING_READ_ONCE(*ring->sq.kflags) &
+			   IORING_SQ_NEED_WAKEUP)) {
 		*flags |= IORING_ENTER_SQ_WAKEUP;
 		return true;
 	}
@@ -37,83 +41,113 @@
 }
 
 static int __io_uring_peek_cqe(struct io_uring *ring,
-			       struct io_uring_cqe **cqe_ptr)
+			       struct io_uring_cqe **cqe_ptr,
+			       unsigned *nr_available)
 {
 	struct io_uring_cqe *cqe;
-	unsigned head;
 	int err = 0;
+	unsigned available;
+	unsigned mask = *ring->cq.kring_mask;
 
 	do {
-		io_uring_for_each_cqe(ring, head, cqe)
+		unsigned tail = io_uring_smp_load_acquire(ring->cq.ktail);
+		unsigned head = *ring->cq.khead;
+
+		cqe = NULL;
+		available = tail - head;
+		if (!available)
 			break;
-		if (cqe) {
-			if (cqe->user_data == LIBURING_UDATA_TIMEOUT) {
-				if (cqe->res < 0)
-					err = cqe->res;
-				io_uring_cq_advance(ring, 1);
-				if (!err)
-					continue;
-				cqe = NULL;
-			}
+
+		cqe = &ring->cq.cqes[head & mask];
+		if (!(ring->features & IORING_FEAT_EXT_ARG) &&
+				cqe->user_data == LIBURING_UDATA_TIMEOUT) {
+			if (cqe->res < 0)
+				err = cqe->res;
+			io_uring_cq_advance(ring, 1);
+			if (!err)
+				continue;
+			cqe = NULL;
 		}
+
 		break;
 	} while (1);
 
 	*cqe_ptr = cqe;
+	*nr_available = available;
+	return err;
+}
+
+struct get_data {
+	unsigned submit;
+	unsigned wait_nr;
+	unsigned get_flags;
+	int sz;
+	void *arg;
+};
+
+static int _io_uring_get_cqe(struct io_uring *ring, struct io_uring_cqe **cqe_ptr,
+			     struct get_data *data)
+{
+	struct io_uring_cqe *cqe = NULL;
+	int err;
+
+	do {
+		bool need_enter = false;
+		bool cq_overflow_flush = false;
+		unsigned flags = 0;
+		unsigned nr_available;
+		int ret;
+
+		err = __io_uring_peek_cqe(ring, &cqe, &nr_available);
+		if (err)
+			break;
+		if (!cqe && !data->wait_nr && !data->submit) {
+			if (!cq_ring_needs_flush(ring)) {
+				err = -EAGAIN;
+				break;
+			}
+			cq_overflow_flush = true;
+		}
+		if (data->wait_nr > nr_available || cq_overflow_flush) {
+			flags = IORING_ENTER_GETEVENTS | data->get_flags;
+			need_enter = true;
+		}
+		if (data->submit) {
+			sq_ring_needs_enter(ring, &flags);
+			need_enter = true;
+		}
+		if (!need_enter)
+			break;
+
+		ret = __sys_io_uring_enter2(ring->ring_fd, data->submit,
+				data->wait_nr, flags, data->arg,
+				data->sz);
+		if (ret < 0) {
+			err = -errno;
+			break;
+		}
+
+		data->submit -= ret;
+		if (cqe)
+			break;
+	} while (1);
+
+	*cqe_ptr = cqe;
 	return err;
 }
 
 int __io_uring_get_cqe(struct io_uring *ring, struct io_uring_cqe **cqe_ptr,
 		       unsigned submit, unsigned wait_nr, sigset_t *sigmask)
 {
-	struct io_uring_cqe *cqe = NULL;
-	const int to_wait = wait_nr;
-	int ret = 0, err;
+	struct get_data data = {
+		.submit		= submit,
+		.wait_nr 	= wait_nr,
+		.get_flags	= 0,
+		.sz		= _NSIG / 8,
+		.arg		= sigmask,
+	};
 
-	do {
-		bool cq_overflow_flush = false;
-		unsigned flags = 0;
-
-		err = __io_uring_peek_cqe(ring, &cqe);
-		if (err)
-			break;
-		if (!cqe && !to_wait && !submit) {
-			if (!cq_ring_needs_flush(ring)) {
-				err = -EAGAIN;
-				break;
-			}
-			cq_overflow_flush = true;
-		}
-		if (wait_nr && cqe)
-			wait_nr--;
-		if (wait_nr || cq_overflow_flush)
-			flags = IORING_ENTER_GETEVENTS;
-		if (submit)
-			sq_ring_needs_enter(ring, &flags);
-		if (wait_nr || submit || cq_overflow_flush)
-			ret = __sys_io_uring_enter(ring->ring_fd, submit,
-						   wait_nr, flags, sigmask);
-		if (ret < 0) {
-			err = -errno;
-		} else if (ret == (int)submit) {
-			submit = 0;
-			/*
-			 * When SETUP_IOPOLL is set, __sys_io_uring enter()
-			 * must be called to reap new completions but the call
-			 * won't be made if both wait_nr and submit are zero
-			 * so preserve wait_nr.
-			 */
-			if (!(ring->flags & IORING_SETUP_IOPOLL))
-				wait_nr = 0;
-		} else {
-			submit -= ret;
-		}
-		if (cqe)
-			break;
-	} while (!err);
-
-	*cqe_ptr = cqe;
-	return err;
+	return _io_uring_get_cqe(ring, cqe_ptr, &data);
 }
 
 /*
@@ -188,19 +222,61 @@
 	 */
 	io_uring_smp_store_release(sq->ktail, ktail);
 out:
+	/*
+	 * This _may_ look problematic, as we're not supposed to be reading
+	 * SQ->head without acquire semantics. When we're in SQPOLL mode, the
+	 * kernel submitter could be updating this right now. For non-SQPOLL,
+	 * task itself does it, and there's no potential race. But even for
+	 * SQPOLL, the load is going to be potentially out-of-date the very
+	 * instant it's done, regardless or whether or not it's done
+	 * atomically. Worst case, we're going to be over-estimating what
+	 * we can submit. The point is, we need to be able to deal with this
+	 * situation regardless of any perceived atomicity.
+	 */
 	return ktail - *sq->khead;
 }
 
 /*
+ * If we have kernel support for IORING_ENTER_EXT_ARG, then we can use that
+ * more efficiently than queueing an internal timeout command.
+ */
+static int io_uring_wait_cqes_new(struct io_uring *ring,
+				  struct io_uring_cqe **cqe_ptr,
+				  unsigned wait_nr, struct __kernel_timespec *ts,
+				  sigset_t *sigmask)
+{
+	struct io_uring_getevents_arg arg = {
+		.sigmask	= (unsigned long) sigmask,
+		.sigmask_sz	= _NSIG / 8,
+		.ts		= (unsigned long) ts
+	};
+	struct get_data data = {
+		.submit		= __io_uring_flush_sq(ring),
+		.wait_nr	= wait_nr,
+		.get_flags	= IORING_ENTER_EXT_ARG,
+		.sz		= sizeof(arg),
+		.arg		= &arg
+	};
+
+	return _io_uring_get_cqe(ring, cqe_ptr, &data);
+}
+
+/*
  * Like io_uring_wait_cqe(), except it accepts a timeout value as well. Note
- * that an sqe is used internally to handle the timeout. Applications using
- * this function must never set sqe->user_data to LIBURING_UDATA_TIMEOUT!
+ * that an sqe is used internally to handle the timeout. For kernel doesn't
+ * support IORING_FEAT_EXT_ARG, applications using this function must never
+ * set sqe->user_data to LIBURING_UDATA_TIMEOUT!
  *
- * If 'ts' is specified, the application need not call io_uring_submit() before
+ * For kernels without IORING_FEAT_EXT_ARG (5.10 and older), if 'ts' is
+ * specified, the application need not call io_uring_submit() before
  * calling this function, as we will do that on its behalf. From this it also
  * follows that this function isn't safe to use for applications that split SQ
  * and CQ handling between two threads and expect that to work without
  * synchronization, as this function manipulates both the SQ and CQ side.
+ *
+ * For kernels with IORING_FEAT_EXT_ARG, no implicit submission is done and
+ * hence this function is safe to use for applications that split SQ and CQ
+ * handling between two threads.
  */
 int io_uring_wait_cqes(struct io_uring *ring, struct io_uring_cqe **cqe_ptr,
 		       unsigned wait_nr, struct __kernel_timespec *ts,
@@ -212,6 +288,10 @@
 		struct io_uring_sqe *sqe;
 		int ret;
 
+		if (ring->features & IORING_FEAT_EXT_ARG)
+			return io_uring_wait_cqes_new(ring, cqe_ptr, wait_nr,
+							ts, sigmask);
+
 		/*
 		 * If the SQ ring is full, we may need to submit IO first
 		 */
@@ -294,19 +374,6 @@
 	return __io_uring_submit_and_wait(ring, wait_nr);
 }
 
-static inline struct io_uring_sqe *
-__io_uring_get_sqe(struct io_uring_sq *sq, unsigned int __head)
-{
-	unsigned int __next = (sq)->sqe_tail + 1;
-	struct io_uring_sqe *__sqe = NULL;
-
-	if (__next - __head <= *(sq)->kring_entries) {
-		__sqe = &(sq)->sqes[(sq)->sqe_tail & *(sq)->kring_mask];
-		(sq)->sqe_tail = __next;
-	}
-	return __sqe;
-}
-
 /*
  * Return an sqe to fill. Application must later call io_uring_submit()
  * when it's ready to tell the kernel about it. The caller may call this
@@ -317,8 +384,15 @@
 struct io_uring_sqe *io_uring_get_sqe(struct io_uring *ring)
 {
 	struct io_uring_sq *sq = &ring->sq;
+	unsigned int head = io_uring_smp_load_acquire(sq->khead);
+	unsigned int next = sq->sqe_tail + 1;
+	struct io_uring_sqe *sqe = NULL;
 
-	return __io_uring_get_sqe(sq, io_uring_smp_load_acquire(sq->khead));
+	if (next - head <= *sq->kring_entries) {
+		sqe = &sq->sqes[sq->sqe_tail & *sq->kring_mask];
+		sq->sqe_tail = next;
+	}
+	return sqe;
 }
 
 int __io_uring_sqring_wait(struct io_uring *ring)
diff --git a/src/register.c b/src/register.c
index f3787c0..994aaff 100644
--- a/src/register.c
+++ b/src/register.c
@@ -1,4 +1,6 @@
 /* SPDX-License-Identifier: MIT */
+#define _POSIX_C_SOURCE 200112L
+
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <sys/mman.h>
diff --git a/src/setup.c b/src/setup.c
index 8e14085..062eaa0 100644
--- a/src/setup.c
+++ b/src/setup.c
@@ -1,4 +1,6 @@
 /* SPDX-License-Identifier: MIT */
+#define _DEFAULT_SOURCE
+
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <sys/mman.h>
@@ -6,6 +8,7 @@
 #include <errno.h>
 #include <string.h>
 #include <stdlib.h>
+#include <signal.h>
 
 #include "liburing/compat.h"
 #include "liburing/io_uring.h"
@@ -142,10 +145,13 @@
 		return -errno;
 
 	ret = io_uring_queue_mmap(fd, p, ring);
-	if (ret)
+	if (ret) {
 		close(fd);
+		return ret;
+	}
 
-	return ret;
+	ring->features = p->features;
+	return 0;
 }
 
 /*
diff --git a/src/syscall.c b/src/syscall.c
index 598b531..2fd3dd4 100644
--- a/src/syscall.c
+++ b/src/syscall.c
@@ -1,4 +1,6 @@
 /* SPDX-License-Identifier: MIT */
+#define _DEFAULT_SOURCE
+
 /*
  * Will go away once libc support is there
  */
@@ -46,9 +48,16 @@
 	return syscall(__NR_io_uring_setup, entries, p);
 }
 
+int __sys_io_uring_enter2(int fd, unsigned to_submit, unsigned min_complete,
+			 unsigned flags, sigset_t *sig, int sz)
+{
+	return syscall(__NR_io_uring_enter, fd, to_submit, min_complete,
+			flags, sig, sz);
+}
+
 int __sys_io_uring_enter(int fd, unsigned to_submit, unsigned min_complete,
 			 unsigned flags, sigset_t *sig)
 {
-	return syscall(__NR_io_uring_enter, fd, to_submit, min_complete,
-			flags, sig, _NSIG / 8);
+	return __sys_io_uring_enter2(fd, to_submit, min_complete, flags, sig,
+					_NSIG / 8);
 }
diff --git a/src/syscall.h b/src/syscall.h
index 0b00f70..3b94efc 100644
--- a/src/syscall.h
+++ b/src/syscall.h
@@ -12,6 +12,8 @@
 extern int __sys_io_uring_setup(unsigned entries, struct io_uring_params *p);
 extern int __sys_io_uring_enter(int fd, unsigned to_submit,
 	unsigned min_complete, unsigned flags, sigset_t *sig);
+extern int __sys_io_uring_enter2(int fd, unsigned to_submit,
+	unsigned min_complete, unsigned flags, sigset_t *sig, int sz);
 extern int __sys_io_uring_register(int fd, unsigned int opcode, const void *arg,
 	unsigned int nr_args);
 
diff --git a/test/232c93d07b74-test.c b/test/232c93d07b74-test.c
index a0da3fd..cd194cb 100644
--- a/test/232c93d07b74-test.c
+++ b/test/232c93d07b74-test.c
@@ -60,20 +60,25 @@
 {
 	struct params *p = arg;
 	int s0;
+	int res;
 
 	if (p->tcp) {
 		int val = 1;
+                
 
 		s0 = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, IPPROTO_TCP);
-		assert(setsockopt(s0, SOL_SOCKET, SO_REUSEPORT, &val, sizeof(val)) != -1);
-		assert(setsockopt(s0, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) != -1);
+		res = setsockopt(s0, SOL_SOCKET, SO_REUSEPORT, &val, sizeof(val));
+		assert(res != -1);
+		res = setsockopt(s0, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
+		assert(res != -1);
 
 		struct sockaddr_in addr;
 
 		addr.sin_family = AF_INET;
 		addr.sin_port = PORT;
 		addr.sin_addr.s_addr = 0x0100007fU;
-		assert(bind(s0, (struct sockaddr *) &addr, sizeof(addr)) != -1);
+		res = bind(s0, (struct sockaddr *) &addr, sizeof(addr));
+		assert(res != -1);
 	} else {
 		s0 = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
 		assert(s0 != -1);
@@ -83,10 +88,11 @@
 
 		addr.sun_family = AF_UNIX;
 		memcpy(addr.sun_path, "\0sock", 6);
-		assert(bind(s0, (struct sockaddr *) &addr, sizeof(addr)) != -1);
+		res = bind(s0, (struct sockaddr *) &addr, sizeof(addr));
+		assert(res != -1);
 	}
-
-	assert(listen(s0, 128) != -1);
+	res = listen(s0, 128);
+	assert(res != -1);
 
 	set_rcv_ready();
 
@@ -98,13 +104,15 @@
 		assert(flags != -1);
 
 		flags |= O_NONBLOCK;
-		assert(fcntl(s1, F_SETFL, flags) != -1);
+		res = fcntl(s1, F_SETFL, flags);
+		assert(res != -1);
 	}
 
 	struct io_uring m_io_uring;
 	void *ret = NULL;
 
-	assert(io_uring_queue_init(32, &m_io_uring, 0) >= 0);
+	res = io_uring_queue_init(32, &m_io_uring, 0);
+	assert(res >= 0);
 
 	int bytes_read = 0;
 	int expected_byte = 0;
@@ -122,7 +130,8 @@
 
 		io_uring_prep_readv(sqe, s1, &iov, 1, 0);
 
-		assert(io_uring_submit(&m_io_uring) != -1);
+		res = io_uring_submit(&m_io_uring);
+		assert(res != -1);
 
 		struct io_uring_cqe *cqe;
 		unsigned head;
@@ -167,6 +176,7 @@
 {
 	struct params *p = arg;
 	int s0;
+	int ret;
 
 	wait_for_rcv_ready();
 
@@ -174,14 +184,16 @@
 		int val = 1;
 
 		s0 = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, IPPROTO_TCP);
-		assert(setsockopt(s0, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val)) != -1);
+		ret = setsockopt(s0, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val));
+		assert(ret != -1);
 
 		struct sockaddr_in addr;
 
 		addr.sin_family = AF_INET;
 		addr.sin_port = PORT;
 		addr.sin_addr.s_addr = 0x0100007fU;
-		assert(connect(s0, (struct sockaddr*) &addr, sizeof(addr)) != -1);
+		ret = connect(s0, (struct sockaddr*) &addr, sizeof(addr));
+		assert(ret != -1);
 	} else {
 		s0 = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
 		assert(s0 != -1);
@@ -191,7 +203,8 @@
 
 		addr.sun_family = AF_UNIX;
 		memcpy(addr.sun_path, "\0sock", 6);
-		assert(connect(s0, (struct sockaddr*) &addr, sizeof(addr)) != -1);
+		ret = connect(s0, (struct sockaddr*) &addr, sizeof(addr));
+		assert(ret != -1);
 	}
 
 	if (p->non_blocking) {
@@ -199,12 +212,14 @@
 		assert(flags != -1);
 
 		flags |= O_NONBLOCK;
-		assert(fcntl(s0, F_SETFL, flags) != -1);
+		ret = fcntl(s0, F_SETFL, flags);
+		assert(ret != -1);
 	}
 
 	struct io_uring m_io_uring;
 
-	assert(io_uring_queue_init(32, &m_io_uring, 0) >= 0);
+	ret = io_uring_queue_init(32, &m_io_uring, 0);
+	assert(ret >= 0);
 
 	int bytes_written = 0;
 	int done = 0;
@@ -226,7 +241,8 @@
 
 		io_uring_prep_writev(sqe, s0, &iov, 1, 0);
 
-		assert(io_uring_submit(&m_io_uring) != -1);
+		ret = io_uring_submit(&m_io_uring);
+		assert(ret != -1);
 
 		struct io_uring_cqe *cqe;
 		unsigned head;
diff --git a/test/35fa71a030ca-test.c b/test/35fa71a030ca-test.c
index 4ecf211..7f2124b 100644
--- a/test/35fa71a030ca-test.c
+++ b/test/35fa71a030ca-test.c
@@ -24,6 +24,10 @@
 
 #include <linux/futex.h>
 
+#if !defined(SYS_futex) && defined(SYS_futex_time64)
+# define SYS_futex SYS_futex_time64
+#endif
+
 static void sleep_ms(uint64_t ms)
 {
   usleep(ms * 1000);
diff --git a/test/Makefile b/test/Makefile
index 8d76c1b..7751eff 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -3,12 +3,15 @@
 
 INSTALL=install
 
+CPPFLAGS ?=
+override CPPFLAGS += -D_GNU_SOURCE -D__SANE_USERSPACE_TYPES__ \
+	-I../src/include/ -include ../config-host.h
 CFLAGS ?= -g -O2
 XCFLAGS =
-override CFLAGS += -Wall -Wextra -Wno-unused-parameter -Wno-sign-compare\
-	-D_GNU_SOURCE -D__SANE_USERSPACE_TYPES__ -L../src/ \
-	-I../src/include/ -include ../config-host.h
-CXXFLAGS += $(CFLAGS) -std=c++11
+override CFLAGS += -Wall -Wextra -Wno-unused-parameter -Wno-sign-compare \
+	-L../src/
+CXXFLAGS ?=
+override CXXFLAGS += $(CFLAGS) -std=c++11
 
 test_targets += \
 	232c93d07b74-test \
@@ -75,11 +78,15 @@
 	poll-cancel-ton \
 	poll-link \
 	poll-many \
+	poll-ring \
 	poll-v-poll \
 	probe \
 	read-write \
 	register-restrictions \
+	rename \
 	ring-leak \
+	ring-leak2 \
+	self \
 	send_recv \
 	send_recvmsg \
 	shared-wq \
@@ -92,13 +99,19 @@
 	sq-poll-dup \
 	sq-poll-kthread \
 	sq-poll-share \
+	sqpoll-exit-hang \
+	sqpoll-sleep \
 	sq-space_left \
 	stdout \
 	submit-reuse \
 	teardowns \
+	thread-exit \
 	timeout \
+	timeout-new \
 	timeout-overflow \
+	unlink \
 	wakeup-hang \
+	sendmsg_fs_cve \
 	# EOL
 
 all_targets += $(test_targets)
@@ -122,10 +135,10 @@
 all: $(test_targets)
 
 %: %.c
-	$(QUIET_CC)$(CC) $(CFLAGS) -o $@ $< -luring $(XCFLAGS)
+	$(QUIET_CC)$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $< -luring $(XCFLAGS)
 
 %: %.cc
-	$(QUIET_CXX)$(CXX) $(CXXFLAGS) -o $@ $< -luring $(XCFLAGS)
+	$(QUIET_CXX)$(CXX) $(CPPFLAGS) $(CXXFLAGS) -o $@ $< -luring $(XCFLAGS)
 
 test_srcs := \
 	232c93d07b74-test.c \
@@ -190,13 +203,16 @@
 	poll-cancel-ton.c \
 	poll-cancel.c \
 	poll-link.c \
-	poll-many.c \
+	poll-ring.c \
 	poll-v-poll.c \
 	poll.c \
 	probe.c \
 	read-write.c \
 	register-restrictions.c \
+	rename.c \
 	ring-leak.c \
+	ring-leak2.c \
+	self.c \
 	send_recvmsg.c \
 	shared-wq.c \
 	short-read.c \
@@ -210,14 +226,20 @@
 	sq-poll-dup.c \
 	sq-poll-kthread.c \
 	sq-poll-share.c \
+	sqpoll-exit-hang.c \
+	sqpoll-sleep.c \
 	sq-space_left.c \
 	statx.c \
 	stdout.c \
 	submit-reuse.c \
 	teardowns.c \
+	thread-exit.c \
+	timeout-new.c \
 	timeout-overflow.c \
 	timeout.c \
+	unlink.c \
 	wakeup-hang.c \
+	sendmsg_fs_cve.c \
 	# EOL
 
 test_objs := $(patsubst %.c,%.ol,$(patsubst %.cc,%.ol,$(test_srcs)))
@@ -234,6 +256,9 @@
 ce593a6c480a-test: XCFLAGS = -lpthread
 wakeup-hang: XCFLAGS = -lpthread
 pipe-eof: XCFLAGS = -lpthread
+timeout-new: XCFLAGS = -lpthread
+thread-exit: XCFLAGS = -lpthread
+ring-leak2: XCFLAGS = -lpthread
 
 install: $(test_targets) runtests.sh runtests-loop.sh
 	$(INSTALL) -D -d -m 755 $(datadir)/liburing-test/
diff --git a/test/accept-link.c b/test/accept-link.c
index 7e4df48..605e0ec 100644
--- a/test/accept-link.c
+++ b/test/accept-link.c
@@ -49,6 +49,7 @@
 static void *send_thread(void *arg)
 {
 	struct data *data = arg;
+	int ret;
 
 	wait_for_var(&recv_thread_ready);
 
@@ -64,7 +65,8 @@
 	addr.sin_port = data->port;
 	addr.sin_addr.s_addr = 0x0100007fU;
 
-        assert(connect(s0, (struct sockaddr*)&addr, sizeof(addr)) != -1);
+	ret = connect(s0, (struct sockaddr*)&addr, sizeof(addr));
+	assert(ret != -1);
 
 	wait_for_var(&recv_thread_done);
 
@@ -76,21 +78,24 @@
 {
 	struct data *data = arg;
 	struct io_uring ring;
-	int i;
+	int i, ret;
 
-	assert(io_uring_queue_init(8, &ring, 0) == 0);
+	ret = io_uring_queue_init(8, &ring, 0);
+	assert(ret == 0);
 
 	int s0 = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
 	assert(s0 != -1);
 
 	int32_t val = 1;
-        assert(setsockopt(s0, SOL_SOCKET, SO_REUSEPORT, &val, sizeof(val)) != -1);
-        assert(setsockopt(s0, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) != -1);
+	ret = setsockopt(s0, SOL_SOCKET, SO_REUSEPORT, &val, sizeof(val));
+	assert(ret != -1);
+	ret = setsockopt(s0, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
+	assert(ret != -1);
 
 	struct sockaddr_in addr;
 
-        addr.sin_family = AF_INET;
-        addr.sin_addr.s_addr = 0x0100007fU;
+	addr.sin_family = AF_INET;
+	addr.sin_addr.s_addr = 0x0100007fU;
 
 	i = 0;
 	do {
@@ -108,7 +113,8 @@
 		goto out;
 	}
 
-        assert(listen(s0, 128) != -1);
+	ret = listen(s0, 128);
+	assert(ret != -1);
 
 	signal_var(&recv_thread_ready);
 
@@ -130,7 +136,8 @@
 	io_uring_prep_link_timeout(sqe, &ts, 0);
 	sqe->user_data = 2;
 
-	assert(io_uring_submit(&ring) == 2);
+	ret = io_uring_submit(&ring);
+	assert(ret == 2);
 
 	for (i = 0; i < 2; i++) {
 		struct io_uring_cqe *cqe;
@@ -144,8 +151,13 @@
 		if (cqe->res != data->expected[idx]) {
 			if (cqe->res > 0 && data->just_positive[idx])
 				goto ok;
-			fprintf(stderr, "cqe %llu got %d, wanted %d\n",
-					cqe->user_data, cqe->res,
+			if (cqe->res == -EBADF) {
+				fprintf(stdout, "Accept not supported, skipping\n");
+				data->stop = 1;
+				goto out;
+			}
+			fprintf(stderr, "cqe %" PRIu64 " got %d, wanted %d\n",
+					(uint64_t) cqe->user_data, cqe->res,
 					data->expected[idx]);
 			goto err;
 		}
diff --git a/test/accept-reuse.c b/test/accept-reuse.c
index 59a2f79..0062729 100644
--- a/test/accept-reuse.c
+++ b/test/accept-reuse.c
@@ -1,5 +1,4 @@
 /* SPDX-License-Identifier: MIT */
-#include <assert.h>
 #include <liburing.h>
 #include <netdb.h>
 #include <string.h>
@@ -48,7 +47,7 @@
 		return 0;
 
 	memset(&params, 0, sizeof(params));
-	ret = io_uring_queue_init_params(1024, &io_uring, &params);
+	ret = io_uring_queue_init_params(4, &io_uring, &params);
 	if (ret) {
 		fprintf(stderr, "io_uring_init_failed: %d\n", ret);
 		return 1;
diff --git a/test/accept-test.c b/test/accept-test.c
index 52b4395..71d9d80 100644
--- a/test/accept-test.c
+++ b/test/accept-test.c
@@ -37,8 +37,10 @@
 	addr.sun_family = AF_UNIX;
 	memcpy(addr.sun_path, "\0sock", 6);
 
-	assert(bind(fd, (struct sockaddr *)&addr, addrlen) != -1);
-	assert(listen(fd, 128) != -1);
+	ret = bind(fd, (struct sockaddr *)&addr, addrlen);
+	assert(ret != -1);
+	ret = listen(fd, 128);
+	assert(ret != -1);
 
 	sqe = io_uring_get_sqe(&ring);
 	if (!sqe) {
@@ -55,11 +57,23 @@
 	}
 
 	ret = io_uring_wait_cqe_timeout(&ring, &cqe, &ts);
-	if (ret != -ETIME) {
+	if (!ret) {
+		if (cqe->res == -EBADF || cqe->res == -EINVAL) {
+			fprintf(stdout, "Accept not supported, skipping\n");
+			goto out;
+		} else if (cqe->res < 0) {
+			fprintf(stderr, "cqe error %d\n", cqe->res);
+			goto err;
+		}
+	} else if (ret != -ETIME) {
 		fprintf(stderr, "accept() failed to use addr & addrlen parameters!\n");
 		return 1;
 	}
 
+out:
 	io_uring_queue_exit(&ring);
 	return 0;
+err:
+	io_uring_queue_exit(&ring);
+	return 1;
 }
diff --git a/test/accept.c b/test/accept.c
index faf81d6..89e4c59 100644
--- a/test/accept.c
+++ b/test/accept.c
@@ -62,9 +62,11 @@
 	sqe = io_uring_get_sqe(ring);
 	io_uring_prep_accept(sqe, fd, NULL, NULL, 0);
 
-	assert(io_uring_submit(ring) != -1);
+	ret = io_uring_submit(ring);
+	assert(ret != -1);
 
-	assert(!io_uring_wait_cqe(ring, &cqe));
+	ret = io_uring_wait_cqe(ring, &cqe);
+	assert(!ret);
 	ret = cqe->res;
 	io_uring_cqe_seen(ring, cqe);
 	return ret;
@@ -72,13 +74,15 @@
 
 static int start_accept_listen(struct sockaddr_in *addr, int port_off)
 {
-	int fd;
+	int fd, ret;
 
 	fd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, IPPROTO_TCP);
 
 	int32_t val = 1;
-	assert(setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &val, sizeof(val)) != -1);
-	assert(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) != -1);
+	ret = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &val, sizeof(val));
+	assert(ret != -1);
+	ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
+	assert(ret != -1);
 
 	struct sockaddr_in laddr;
 
@@ -89,8 +93,10 @@
 	addr->sin_port = 0x1235 + port_off;
 	addr->sin_addr.s_addr = 0x0100007fU;
 
-	assert(bind(fd, (struct sockaddr*)addr, sizeof(*addr)) != -1);
-	assert(listen(fd, 128) != -1);
+	ret = bind(fd, (struct sockaddr*)addr, sizeof(*addr));
+	assert(ret != -1);
+	ret = listen(fd, 128);
+	assert(ret != -1);
 
 	return fd;
 }
@@ -103,27 +109,32 @@
 	uint32_t count = 0;
 	int done = 0;
 	int p_fd[2];
+        int ret;
 
 	int32_t val, recv_s0 = start_accept_listen(&addr, 0);
 
 	p_fd[1] = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, IPPROTO_TCP);
 
 	val = 1;
-	assert(setsockopt(p_fd[1], IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val)) != -1);
+	ret = setsockopt(p_fd[1], IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val));
+	assert(ret != -1);
 
 	int32_t flags = fcntl(p_fd[1], F_GETFL, 0);
 	assert(flags != -1);
 
 	flags |= O_NONBLOCK;
-	assert(fcntl(p_fd[1], F_SETFL, flags) != -1);
+	ret = fcntl(p_fd[1], F_SETFL, flags);
+	assert(ret != -1);
 
-	assert(connect(p_fd[1], (struct sockaddr*)&addr, sizeof(addr)) == -1);
+	ret = connect(p_fd[1], (struct sockaddr*)&addr, sizeof(addr));
+	assert(ret == -1);
 
 	flags = fcntl(p_fd[1], F_GETFL, 0);
 	assert(flags != -1);
 
 	flags &= ~O_NONBLOCK;
-	assert(fcntl(p_fd[1], F_SETFL, flags) != -1);
+	ret = fcntl(p_fd[1], F_SETFL, flags);
+	assert(ret != -1);
 
 	p_fd[0] = accept_conn(ring, recv_s0);
 	if (p_fd[0] == -EINVAL) {
@@ -143,7 +154,8 @@
 	queue_send(ring, p_fd[1]);
 	queue_recv(ring, p_fd[0]);
 
-	assert(io_uring_submit_and_wait(ring, 2) != -1);
+	ret = io_uring_submit_and_wait(ring, 2);
+	assert(ret != -1);
 
 	while (count < 2) {
 		io_uring_for_each_cqe(ring, head, cqe) {
@@ -184,19 +196,22 @@
 	struct io_uring m_io_uring;
 	struct io_uring_cqe *cqe;
 	struct io_uring_sqe *sqe;
-	int fd;
+	int fd, ret;
 
-	assert(io_uring_queue_init(32, &m_io_uring, 0) >= 0);
+	ret = io_uring_queue_init(32, &m_io_uring, 0);
+	assert(ret >= 0);
 
 	fd = start_accept_listen(NULL, 0);
 
 	sqe = io_uring_get_sqe(&m_io_uring);
 	io_uring_prep_accept(sqe, fd, NULL, NULL, 0);
-	assert(io_uring_submit(&m_io_uring) != -1);
+	ret = io_uring_submit(&m_io_uring);
+	assert(ret != -1);
 
 	signal(SIGALRM, sig_alrm);
 	alarm(1);
-	assert(!io_uring_wait_cqe(&m_io_uring, &cqe));
+	ret = io_uring_wait_cqe(&m_io_uring, &cqe);
+	assert(!ret);
 	io_uring_cqe_seen(&m_io_uring, cqe);
 
 	io_uring_queue_exit(&m_io_uring);
@@ -213,7 +228,7 @@
 	struct io_uring_sqe *sqe;
 	unsigned long cur_lim;
 	struct rlimit rlim;
-	int *fds, i, ret = 0;
+	int *fds, i, ret;
 
 	if (getrlimit(RLIMIT_NPROC, &rlim) < 0) {
 		perror("getrlimit");
@@ -228,7 +243,8 @@
 		return 1;
 	}
 
-	assert(io_uring_queue_init(2 * nr, &m_io_uring, 0) >= 0);
+	ret = io_uring_queue_init(2 * nr, &m_io_uring, 0);
+	assert(ret >= 0);
 
 	fds = calloc(nr, sizeof(int));
 
@@ -239,7 +255,8 @@
 		sqe = io_uring_get_sqe(&m_io_uring);
 		io_uring_prep_accept(sqe, fds[i], NULL, NULL, 0);
 		sqe->user_data = 1 + i;
-		assert(io_uring_submit(&m_io_uring) == 1);
+		ret = io_uring_submit(&m_io_uring);
+		assert(ret == 1);
 	}
 
 	if (usecs)
@@ -263,7 +280,7 @@
 
 	free(fds);
 	io_uring_queue_exit(&m_io_uring);
-	return ret;
+	return 0;
 err:
 	ret = 1;
 	goto out;
@@ -274,16 +291,18 @@
 	struct io_uring m_io_uring;
 	struct io_uring_cqe *cqe;
 	struct io_uring_sqe *sqe;
-	int fd, i;
+	int fd, i, ret;
 
-	assert(io_uring_queue_init(32, &m_io_uring, 0) >= 0);
+	ret = io_uring_queue_init(32, &m_io_uring, 0);
+	assert(ret >= 0);
 
 	fd = start_accept_listen(NULL, 0);
 
 	sqe = io_uring_get_sqe(&m_io_uring);
 	io_uring_prep_accept(sqe, fd, NULL, NULL, 0);
 	sqe->user_data = 1;
-	assert(io_uring_submit(&m_io_uring) == 1);
+        ret = io_uring_submit(&m_io_uring);
+	assert(ret == 1);
 
 	if (usecs)
 		usleep(usecs);
@@ -291,10 +310,12 @@
 	sqe = io_uring_get_sqe(&m_io_uring);
 	io_uring_prep_cancel(sqe, (void *) 1, 0);
 	sqe->user_data = 2;
-	assert(io_uring_submit(&m_io_uring) == 1);
+	ret = io_uring_submit(&m_io_uring);
+	assert(ret == 1);
 
 	for (i = 0; i < 2; i++) {
-		assert(!io_uring_wait_cqe(&m_io_uring, &cqe));
+		ret = io_uring_wait_cqe(&m_io_uring, &cqe);
+		assert(!ret);
 		/*
 		 * Two cases here:
 		 *
@@ -331,7 +352,8 @@
 	struct io_uring m_io_uring;
 	int ret;
 
-	assert(io_uring_queue_init(32, &m_io_uring, 0) >= 0);
+	ret = io_uring_queue_init(32, &m_io_uring, 0);
+	assert(ret >= 0);
 	ret = test(&m_io_uring, 0);
 	io_uring_queue_exit(&m_io_uring);
 	return ret;
diff --git a/test/close-opath.c b/test/close-opath.c
index 884bda9..f267dad 100644
--- a/test/close-opath.c
+++ b/test/close-opath.c
@@ -58,7 +58,7 @@
 	ret = cqe->res;
 	io_uring_cqe_seen(ring, cqe);
 
-	if (ret < 0 && ret != -EOPNOTSUPP) {
+	if (ret < 0 && ret != -EOPNOTSUPP && ret != -EINVAL && ret != -EBADF) {
 		fprintf(stderr, "io_uring close() failed, errno %d: %s\n",
 			-ret, strerror(-ret));
 		return ret;
diff --git a/test/config b/test/config
index cab2703..6c0925a 100644
--- a/test/config
+++ b/test/config
@@ -4,4 +4,7 @@
 # TEST_EXCLUDE=""
 #
 # Define raw test devices (or files) for test cases, if any
-# TEST_FILES="/dev/nvme0n1p2 /data/file"
+# declare -A TEST_MAP=()
+#
+# If no TEST_MAP entry exists for a test, use the ones given in TEST_FILES
+# TEST_FILES="/dev/somedevice /data/somefile"
diff --git a/test/connect.c b/test/connect.c
index 668997f..34e3954 100644
--- a/test/connect.c
+++ b/test/connect.c
@@ -17,6 +17,8 @@
 
 #include "liburing.h"
 
+static int no_connect;
+
 static int create_socket(void)
 {
 	int fd;
@@ -103,12 +105,9 @@
 	return 0;
 }
 
-static int connect_socket(struct io_uring *ring, int fd, int *code)
+static int configure_connect(int fd, struct sockaddr_in* addr)
 {
-	struct io_uring_sqe *sqe;
-	struct sockaddr_in addr;
-	int ret, res, val = 1;
-	socklen_t code_len = sizeof(*code);
+	int ret, val = 1;
 
 	ret = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &val, sizeof(val));
 	if (ret == -1) {
@@ -122,10 +121,23 @@
 		return -1;
 	}
 
-	memset(&addr, 0, sizeof(addr));
-	addr.sin_family = AF_INET;
-	addr.sin_port = 0x1234;
-	addr.sin_addr.s_addr = 0x0100007fU;
+	memset(addr, 0, sizeof(*addr));
+	addr->sin_family = AF_INET;
+	addr->sin_port = 0x1234;
+	addr->sin_addr.s_addr = 0x0100007fU;
+
+	return 0;
+}
+
+static int connect_socket(struct io_uring *ring, int fd, int *code)
+{
+	struct sockaddr_in addr;
+	int ret, res;
+	socklen_t code_len = sizeof(*code);
+	struct io_uring_sqe *sqe;
+
+	if (configure_connect(fd, &addr) == -1)
+		return -1;
 
 	sqe = io_uring_get_sqe(ring);
 	if (!sqe) {
@@ -175,10 +187,16 @@
 		goto err;
 
 	if (code != -ECONNREFUSED) {
+		if (code == -EINVAL || code == -EBADF || code == -EOPNOTSUPP) {
+			fprintf(stdout, "No connect support, skipping\n");
+			no_connect = 1;
+			goto out;
+		}
 		fprintf(stderr, "connect failed with %d\n", code);
 		goto err;
 	}
 
+out:
 	close(connect_fd);
 	return 0;
 
@@ -227,6 +245,102 @@
 	return -1;
 }
 
+static int test_connect_timeout(struct io_uring *ring)
+{
+	int fd = -1, connect_fd = -1, accept_fd = -1;
+	int ret;
+	struct sockaddr_in addr;
+	struct io_uring_sqe *sqe;
+	struct __kernel_timespec ts = {.tv_sec = 0, .tv_nsec = 100000};
+
+	connect_fd = create_socket();
+	if (connect_fd == -1)
+		return -1;
+
+	accept_fd = create_socket();
+	if (accept_fd == -1)
+		goto err;
+
+	if (configure_connect(connect_fd, &addr) == -1)
+		goto err;
+
+	ret = bind(accept_fd, (struct sockaddr*)&addr, sizeof(addr));
+	if (ret == -1) {
+		perror("bind()");
+		goto err;
+	}
+
+	ret = listen(accept_fd, 0);  // no backlog in order to block connect_fd
+	if (ret == -1) {
+		perror("listen()");
+		goto err;
+	}
+
+	// Fill up available place in the accept queue (backlog)
+	fd = create_socket();
+	if (connect(fd, &addr, sizeof(addr)) == -1) {
+			fprintf(stderr, "unable to connect %d\n", errno);
+			goto err;
+	}
+
+	sqe = io_uring_get_sqe(ring);
+	if (!sqe) {
+		fprintf(stderr, "unable to get sqe\n");
+		goto err;
+	}
+
+	io_uring_prep_connect(sqe, connect_fd, (struct sockaddr*)&addr, sizeof(addr));
+	sqe->user_data = 1;
+	sqe->flags |= IOSQE_IO_LINK;
+
+	sqe = io_uring_get_sqe(ring);
+	if (!sqe) {
+		fprintf(stderr, "unable to get sqe\n");
+		goto err;
+	}
+	sqe->user_data = 2;
+
+	io_uring_prep_link_timeout(sqe, &ts, 0);
+	ret = io_uring_submit(ring);
+	if (ret != 2) {
+		fprintf(stderr, "submitted %d\n", ret);
+		return -1;
+	}
+
+	for (int i = 0; i < 2; i++) {
+		int expected;
+		struct io_uring_cqe *cqe;
+
+		ret = io_uring_wait_cqe(ring, &cqe);
+		if (ret) {
+			fprintf(stderr, "wait_cqe=%d\n", ret);
+			return -1;
+		}
+
+		expected = (cqe->user_data == 1) ? -ECANCELED : -ETIME;
+		if (expected != cqe->res) {
+			fprintf(stderr, "cqe %d, res %d, wanted %d\n", 
+					(int)cqe->user_data, cqe->res, expected);
+			goto err;
+		}
+		io_uring_cqe_seen(ring, cqe);
+	}
+
+	close(connect_fd);
+	close(accept_fd);
+	close(fd);
+
+	return 0;
+
+err:
+	close(connect_fd);
+	if (accept_fd != -1)
+		close(accept_fd);
+	if (fd != -1)
+		close(fd);
+	return -1;
+}
+
 int main(int argc, char *argv[])
 {
 	struct io_uring ring;
@@ -246,6 +360,8 @@
 		fprintf(stderr, "test_connect_with_no_peer(): failed\n");
 		return 1;
 	}
+	if (no_connect)
+		return 0;
 
 	ret = test_connect(&ring);
 	if (ret == -1) {
@@ -253,6 +369,12 @@
 		return 1;
 	}
 
+	ret = test_connect_timeout(&ring);
+	if (ret == -1) {
+		fprintf(stderr, "test_connect_timeout(): failed\n");
+		return 1;
+	}
+
 	io_uring_queue_exit(&ring);
 	return 0;
 }
diff --git a/test/cq-overflow-peek.c b/test/cq-overflow-peek.c
index 72b6768..353c6f3 100644
--- a/test/cq-overflow-peek.c
+++ b/test/cq-overflow-peek.c
@@ -62,16 +62,22 @@
 {
 	int ret;
 	struct io_uring ring;
+	struct io_uring_params p = { };
 
 	if (argc > 1)
 		return 0;
 
-	ret = io_uring_queue_init(16, &ring, 0);
+	ret = io_uring_queue_init_params(16, &ring, &p);
 	if (ret) {
 		fprintf(stderr, "ring setup failed: %d\n", ret);
 		return 1;
 	}
 
+	if (!(p.features & IORING_FEAT_NODROP)) {
+		fprintf(stdout, "No overflow protection, skipped\n");
+		return 0;
+	}
+
 	ret = test_cq_overflow(&ring);
 	if (ret) {
 		fprintf(stderr, "test_cq_overflow failed\n");
diff --git a/test/cq-peek-batch.c b/test/cq-peek-batch.c
index ee7537c..6c47bec 100644
--- a/test/cq-peek-batch.c
+++ b/test/cq-peek-batch.c
@@ -74,7 +74,8 @@
 	CHECK_BATCH(&ring, got, cqes, 4, 4);
 	for (i=0;i<4;i++) {
 		if (i != cqes[i]->user_data) {
-			printf("Got user_data %lld, expected %d\n", cqes[i]->user_data, i);
+			printf("Got user_data %" PRIu64 ", expected %d\n",
+				(uint64_t) cqes[i]->user_data, i);
 			goto err;
 		}
 	}
@@ -86,7 +87,8 @@
 	CHECK_BATCH(&ring, got, cqes, 4, 4);
 	for (i=0;i<4;i++) {
 		if (i + 4 != cqes[i]->user_data) {
-			printf("Got user_data %lld, expected %d\n", cqes[i]->user_data, i + 4);
+			printf("Got user_data %" PRIu64 ", expected %d\n",
+				(uint64_t) cqes[i]->user_data, i + 4);
 			goto err;
 		}
 	}
diff --git a/test/defer.c b/test/defer.c
index 05833d4..98abfba 100644
--- a/test/defer.c
+++ b/test/defer.c
@@ -148,6 +148,38 @@
 	return 1;
 }
 
+static int test_drain_with_linked_timeout(struct io_uring *ring)
+{
+	const int nr = 3;
+	struct __kernel_timespec ts = { .tv_sec = 1, .tv_nsec = 0, };
+	struct test_context ctx;
+	int ret, i;
+
+	if (init_context(&ctx, ring, nr * 2))
+		return 1;
+
+	for (i = 0; i < nr; i++) {
+		io_uring_prep_timeout(ctx.sqes[2 * i], &ts, 0, 0);
+		ctx.sqes[2 * i]->flags |= IOSQE_IO_LINK | IOSQE_IO_DRAIN;
+		io_uring_prep_link_timeout(ctx.sqes[2 * i + 1], &ts, 0);
+	}
+
+	ret = io_uring_submit(ring);
+	if (ret <= 0) {
+		printf("sqe submit failed: %d\n", ret);
+		goto err;
+	}
+
+	if (wait_cqes(&ctx))
+		goto err;
+
+	free_context(&ctx);
+	return 0;
+err:
+	free_context(&ctx);
+	return 1;
+}
+
 static int run_drained(struct io_uring *ring, int nr)
 {
 	struct test_context ctx;
@@ -269,5 +301,11 @@
 		return ret;
 	}
 
+	ret = test_drain_with_linked_timeout(&ring);
+	if (ret) {
+		printf("test_drain_with_linked_timeout failed\n");
+		return ret;
+	}
+
 	return 0;
 }
diff --git a/test/fallocate.c b/test/fallocate.c
index e662a6a..da90be8 100644
--- a/test/fallocate.c
+++ b/test/fallocate.c
@@ -191,8 +191,8 @@
 			goto err;
 		}
 		if (cqe->res) {
-			fprintf(stderr, "cqe->res=%d,data=%llu\n", cqe->res,
-							cqe->user_data);
+			fprintf(stderr, "cqe->res=%d,data=%" PRIu64 "\n", cqe->res,
+							(uint64_t) cqe->user_data);
 			goto err;
 		}
 		io_uring_cqe_seen(ring, cqe);
diff --git a/test/file-register.c b/test/file-register.c
index 7400b3a..b86ee59 100644
--- a/test/file-register.c
+++ b/test/file-register.c
@@ -346,17 +346,25 @@
 	return 1;
 }
 
-static int test_basic(struct io_uring *ring)
+static int test_basic(struct io_uring *ring, int fail)
 {
 	int *files;
 	int ret;
 
-	files = open_files(100, 0, 0);
+	files = open_files(fail ? 10 : 100, 0, 0);
 	ret = io_uring_register_files(ring, files, 100);
 	if (ret) {
+		if (fail) {
+			if (ret == -EBADF || ret == -EFAULT)
+				return 0;
+		}
 		fprintf(stderr, "%s: register %d\n", __FUNCTION__, ret);
 		goto err;
 	}
+	if (fail) {
+		fprintf(stderr, "Registration succeeded, but expected fail\n");
+		goto err;
+	}
 	ret = io_uring_unregister_files(ring);
 	if (ret) {
 		fprintf(stderr, "%s: unregister %d\n", __FUNCTION__, ret);
@@ -542,6 +550,47 @@
 	return 1;
 }
 
+static int test_skip(struct io_uring *ring)
+{
+	int *files;
+	int ret;
+
+	files = open_files(100, 0, 0);
+	ret = io_uring_register_files(ring, files, 100);
+	if (ret) {
+		fprintf(stderr, "%s: register ret=%d\n", __FUNCTION__, ret);
+		goto err;
+	}
+
+	files[90] = IORING_REGISTER_FILES_SKIP;
+	ret = io_uring_register_files_update(ring, 90, &files[90], 1);
+	if (ret != 1) {
+		if (ret == -EBADF) {
+			fprintf(stdout, "Skipping files not supported\n");
+			goto done;
+		}
+		fprintf(stderr, "%s: update ret=%d\n", __FUNCTION__, ret);
+		goto err;
+	}
+
+	/* verify can still use file index 90 */
+	if (test_fixed_read_write(ring, 90))
+		goto err;
+
+	ret = io_uring_unregister_files(ring);
+	if (ret) {
+		fprintf(stderr, "%s: unregister ret=%d\n", __FUNCTION__, ret);
+		goto err;
+	}
+
+done:
+	close_files(files, 100, 0);
+	return 0;
+err:
+	close_files(files, 100, 0);
+	return 1;
+}
+
 static int test_sparse_updates(void)
 {
 	struct io_uring ring;
@@ -596,6 +645,91 @@
 	return 0;
 }
 
+static int test_fixed_removal_ordering(void)
+{
+	char buffer[128];
+	struct io_uring ring;
+	struct io_uring_sqe *sqe;
+	struct io_uring_cqe *cqe;
+	struct __kernel_timespec ts;
+	int ret, fd, i, fds[2];
+
+	ret = io_uring_queue_init(8, &ring, 0);
+	if (ret < 0) {
+		fprintf(stderr, "failed to init io_uring: %s\n", strerror(-ret));
+		return ret;
+	}
+	if (pipe(fds)) {
+		perror("pipe");
+		return -1;
+	}
+	ret = io_uring_register_files(&ring, fds, 2);
+	if (ret) {
+		fprintf(stderr, "file_register: %d\n", ret);
+		return ret;
+	}
+	/* ring should have fds referenced, can close them */
+	close(fds[0]);
+	close(fds[1]);
+
+	sqe = io_uring_get_sqe(&ring);
+	if (!sqe) {
+		fprintf(stderr, "%s: get sqe failed\n", __FUNCTION__);
+		return 1;
+	}
+	/* outwait file recycling delay */
+	ts.tv_sec = 3;
+	ts.tv_nsec = 0;
+	io_uring_prep_timeout(sqe, &ts, 0, 0);
+	sqe->flags |= IOSQE_IO_LINK | IOSQE_IO_HARDLINK;
+	sqe->user_data = 1;
+
+	sqe = io_uring_get_sqe(&ring);
+	if (!sqe) {
+		printf("get sqe failed\n");
+		return -1;
+	}
+	io_uring_prep_write(sqe, 1, buffer, sizeof(buffer), 0);
+	sqe->flags |= IOSQE_FIXED_FILE;
+	sqe->user_data = 2;
+
+	ret = io_uring_submit(&ring);
+	if (ret != 2) {
+		fprintf(stderr, "%s: got %d, wanted 2\n", __FUNCTION__, ret);
+		return -1;
+	}
+
+	/* remove unused pipe end */
+	fd = -1;
+	ret = io_uring_register_files_update(&ring, 0, &fd, 1);
+	if (ret != 1) {
+		fprintf(stderr, "update off=0 failed\n");
+		return -1;
+	}
+
+	/* remove used pipe end */
+	fd = -1;
+	ret = io_uring_register_files_update(&ring, 1, &fd, 1);
+	if (ret != 1) {
+		fprintf(stderr, "update off=1 failed\n");
+		return -1;
+	}
+
+	for (i = 0; i < 2; ++i) {
+		ret = io_uring_wait_cqe(&ring, &cqe);
+		if (ret < 0) {
+			fprintf(stderr, "%s: io_uring_wait_cqe=%d\n", __FUNCTION__, ret);
+			return 1;
+		}
+		io_uring_cqe_seen(&ring, cqe);
+	}
+
+	io_uring_queue_exit(&ring);
+	return 0;
+}
+
+
+
 int main(int argc, char *argv[])
 {
 	struct io_uring ring;
@@ -610,7 +744,13 @@
 		return 1;
 	}
 
-	ret = test_basic(&ring);
+	ret = test_basic(&ring, 0);
+	if (ret) {
+		printf("test_basic failed\n");
+		return ret;
+	}
+
+	ret = test_basic(&ring, 1);
 	if (ret) {
 		printf("test_basic failed\n");
 		return ret;
@@ -679,11 +819,23 @@
 		return ret;
 	}
 
+	ret = test_skip(&ring);
+	if (ret) {
+		printf("test_skip failed\n");
+		return 1;
+	}
+
 	ret = test_sparse_updates();
 	if (ret) {
 		printf("test_sparse_updates failed\n");
 		return ret;
 	}
 
+	ret = test_fixed_removal_ordering();
+	if (ret) {
+		printf("test_fixed_removal_ordering failed\n");
+		return 1;
+	}
+
 	return 0;
 }
diff --git a/test/fixed-link.c b/test/fixed-link.c
index 14def83..be8e9c8 100644
--- a/test/fixed-link.c
+++ b/test/fixed-link.c
@@ -4,7 +4,6 @@
 #include <unistd.h>
 #include <stdlib.h>
 #include <string.h>
-#include <assert.h>
 #include <fcntl.h>
 #include <sys/types.h>
 
diff --git a/test/lfs-openat.c b/test/lfs-openat.c
index 921e2a1..b14238a 100644
--- a/test/lfs-openat.c
+++ b/test/lfs-openat.c
@@ -133,10 +133,9 @@
 {
 	struct io_uring ring;
 	struct io_uring_sqe *sqe;
-	struct io_uring_cqe *cqe;
 	char buffer[128];
 	struct iovec iov = {.iov_base = buffer, .iov_len = sizeof(buffer), };
-	int i, ret, fd, fds[2], to_cancel = 0;
+	int ret, fd, fds[2], to_cancel = 0;
 
 	ret = io_uring_queue_init(10, &ring, 0);
 	if (ret < 0)
@@ -202,18 +201,11 @@
 		return 1;
 	}
 
-	/* io_uring->flush() */
+	/*
+	 * close(), which triggers ->flush(), and io_uring_queue_exit()
+	 * should successfully return and not hang.
+	 */
 	close(fd);
-
-	for (i = 0; i < to_cancel; i++) {
-		ret = io_uring_wait_cqe(&ring, &cqe);
-		if (cqe->res != -ECANCELED) {
-			fprintf(stderr, "fail cqe->res=%d\n", cqe->res);
-			return 1;
-		}
-		io_uring_cqe_seen(&ring, cqe);
-	}
-
 	io_uring_queue_exit(&ring);
 	return 0;
 }
diff --git a/test/link-timeout.c b/test/link-timeout.c
index c9aff11..5d8417f 100644
--- a/test/link-timeout.c
+++ b/test/link-timeout.c
@@ -528,6 +528,8 @@
 		io_uring_cqe_seen(ring, cqe);
 	}
 
+	close(fds[0]);
+	close(fds[1]);
 	return 0;
 err:
 	return 1;
@@ -592,7 +594,7 @@
 		switch (cqe->user_data) {
 		case 1:
 			if (cqe->res != -EINTR && cqe->res != -ECANCELED) {
-				fprintf(stderr, "Req %llu got %d\n", cqe->user_data,
+				fprintf(stderr, "Req %" PRIu64 " got %d\n", (uint64_t) cqe->user_data,
 						cqe->res);
 				goto err;
 			}
@@ -600,14 +602,14 @@
 		case 2:
 			/* FASTPOLL kernels can cancel successfully */
 			if (cqe->res != -EALREADY && cqe->res != -ETIME) {
-				fprintf(stderr, "Req %llu got %d\n", cqe->user_data,
+				fprintf(stderr, "Req %" PRIu64 " got %d\n", (uint64_t) cqe->user_data,
 						cqe->res);
 				goto err;
 			}
 			break;
 		case 3:
 			if (cqe->res != -ECANCELED) {
-				fprintf(stderr, "Req %llu got %d\n", cqe->user_data,
+				fprintf(stderr, "Req %" PRIu64 " got %d\n", (uint64_t) cqe->user_data,
 						cqe->res);
 				goto err;
 			}
@@ -687,14 +689,14 @@
 		/* poll cancel really should return -ECANCEL... */
 		case 1:
 			if (cqe->res != -ECANCELED) {
-				fprintf(stderr, "Req %llu got %d\n", cqe->user_data,
+				fprintf(stderr, "Req %" PRIu64 " got %d\n", (uint64_t) cqe->user_data,
 						cqe->res);
 				goto err;
 			}
 			break;
 		case 2:
 			if (cqe->res != -ETIME) {
-				fprintf(stderr, "Req %llu got %d\n", cqe->user_data,
+				fprintf(stderr, "Req %" PRIu64 " got %d\n", (uint64_t) cqe->user_data,
 						cqe->res);
 				goto err;
 			}
@@ -702,7 +704,7 @@
 		case 3:
 		case 4:
 			if (cqe->res != -ECANCELED) {
-				fprintf(stderr, "Req %llu got %d\n", cqe->user_data,
+				fprintf(stderr, "Req %" PRIu64 " got %d\n", (uint64_t) cqe->user_data,
 						cqe->res);
 				goto err;
 			}
@@ -805,7 +807,7 @@
 		switch (cqe->user_data) {
 		case 2:
 			if (cqe->res != -ETIME) {
-				fprintf(stderr, "Req %llu got %d\n", cqe->user_data,
+				fprintf(stderr, "Req %" PRIu64 " got %d\n", (uint64_t) cqe->user_data,
 						cqe->res);
 				goto err;
 			}
@@ -815,14 +817,14 @@
 		case 4:
 		case 5:
 			if (cqe->res != -ECANCELED) {
-				fprintf(stderr, "Req %llu got %d\n", cqe->user_data,
+				fprintf(stderr, "Req %" PRIu64 " got %d\n", (uint64_t) cqe->user_data,
 						cqe->res);
 				goto err;
 			}
 			break;
 		case 6:
 			if (cqe->res) {
-				fprintf(stderr, "Req %llu got %d\n", cqe->user_data,
+				fprintf(stderr, "Req %" PRIu64 " got %d\n", (uint64_t) cqe->user_data,
 						cqe->res);
 				goto err;
 			}
@@ -892,21 +894,21 @@
 		/* poll cancel really should return -ECANCEL... */
 		case 1:
 			if (cqe->res) {
-				fprintf(stderr, "Req %llu got %d\n", cqe->user_data,
+				fprintf(stderr, "Req %" PRIu64 " got %d\n", (uint64_t) cqe->user_data,
 						cqe->res);
 				goto err;
 			}
 			break;
 		case 2:
 			if (cqe->res != -ECANCELED) {
-				fprintf(stderr, "Req %llu got %d\n", cqe->user_data,
+				fprintf(stderr, "Req %" PRIu64 " got %d\n", (uint64_t) cqe->user_data,
 						cqe->res);
 				goto err;
 			}
 			break;
 		case 3:
 			if (cqe->res != -ETIME) {
-				fprintf(stderr, "Req %llu got %d\n", cqe->user_data,
+				fprintf(stderr, "Req %" PRIu64 " got %d\n", (uint64_t) cqe->user_data,
 						cqe->res);
 				goto err;
 			}
diff --git a/test/pipe-reuse.c b/test/pipe-reuse.c
index 1d5200a..9dc60ee 100644
--- a/test/pipe-reuse.c
+++ b/test/pipe-reuse.c
@@ -17,6 +17,7 @@
 {
 	char buf[BUFSIZE], wbuf[BUFSIZE];
 	struct iovec iov[BUFFERS];
+	struct io_uring_params p = { };
 	struct io_uring ring;
 	struct io_uring_sqe *sqe;
 	struct io_uring_cqe *cqe;
@@ -37,7 +38,15 @@
 		ptr += bsize;
 	}
 
-	io_uring_queue_init(8, &ring, 0);
+	ret = io_uring_queue_init_params(8, &ring, &p);
+	if (ret) {
+		fprintf(stderr, "queue_init: %d\n", ret);
+		return 1;
+	}
+	if (!(p.features & IORING_FEAT_SUBMIT_STABLE)) {
+		fprintf(stdout, "FEAT_SUBMIT_STABLE not there, skipping\n");
+		return 0;
+	}
 
 	ptr = wbuf;
 	memset(ptr, 0x11, sizeof(wbuf) / 2);
diff --git a/test/poll-cancel-ton.c b/test/poll-cancel-ton.c
index 1a75463..e9d612e 100644
--- a/test/poll-cancel-ton.c
+++ b/test/poll-cancel-ton.c
@@ -102,6 +102,7 @@
 int main(int argc, char *argv[])
 {
 	struct io_uring ring;
+	struct io_uring_params p = { };
 	int pipe1[2];
 	int ret;
 
@@ -113,10 +114,18 @@
 		return 1;
 	}
 
-	ret = io_uring_queue_init(1024, &ring, 0);
+	p.flags = IORING_SETUP_CQSIZE;
+	p.cq_entries = 16384;
+	ret = io_uring_queue_init_params(1024, &ring, &p);
 	if (ret) {
-		fprintf(stderr, "ring setup failed: %d\n", ret);
-		return 1;
+		if (ret == -EINVAL) {
+			fprintf(stdout, "No CQSIZE, trying without\n");
+			ret = io_uring_queue_init(1024, &ring, 0);
+			if (ret) {
+				fprintf(stderr, "ring setup failed: %d\n", ret);
+				return 1;
+			}
+		}
 	}
 
 	add_polls(&ring, pipe1[0], 30000);
diff --git a/test/poll-link.c b/test/poll-link.c
index d0786d4..4b4f9aa 100644
--- a/test/poll-link.c
+++ b/test/poll-link.c
@@ -73,16 +73,19 @@
 	struct data *data = arg;
 	struct io_uring_sqe *sqe;
 	struct io_uring ring;
-	int i;
+	int i, ret;
 
-	assert(io_uring_queue_init(8, &ring, 0) == 0);
+	ret = io_uring_queue_init(8, &ring, 0);
+	assert(ret == 0);
 
 	int s0 = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
 	assert(s0 != -1);
 
 	int32_t val = 1;
-        assert(setsockopt(s0, SOL_SOCKET, SO_REUSEPORT, &val, sizeof(val)) != -1);
-        assert(setsockopt(s0, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) != -1);
+	ret = setsockopt(s0, SOL_SOCKET, SO_REUSEPORT, &val, sizeof(val));
+	assert(ret != -1);
+	ret = setsockopt(s0, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
+	assert(ret != -1);
 
 	struct sockaddr_in addr;
 
@@ -105,7 +108,8 @@
 		goto out;
 	}
 
-        assert(listen(s0, 128) != -1);
+	ret = listen(s0, 128);
+	assert(ret != -1);
 
 	signal_var(&recv_thread_ready);
 
@@ -125,7 +129,8 @@
 	io_uring_prep_link_timeout(sqe, &ts, 0);
 	sqe->user_data = 2;
 
-	assert(io_uring_submit(&ring) == 2);
+	ret = io_uring_submit(&ring);
+	assert(ret == 2);
 
 	for (i = 0; i < 2; i++) {
 		struct io_uring_cqe *cqe;
@@ -137,13 +142,13 @@
 		}
 		idx = cqe->user_data - 1;
 		if (data->is_mask[idx] && !(data->expected[idx] & cqe->res)) {
-			fprintf(stderr, "cqe %llu got %x, wanted mask %x\n",
-					cqe->user_data, cqe->res,
+			fprintf(stderr, "cqe %" PRIu64 " got %x, wanted mask %x\n",
+					(uint64_t) cqe->user_data, cqe->res,
 					data->expected[idx]);
 			goto err;
 		} else if (!data->is_mask[idx] && cqe->res != data->expected[idx]) {
-			fprintf(stderr, "cqe %llu got %d, wanted %d\n",
-					cqe->user_data, cqe->res,
+			fprintf(stderr, "cqe %" PRIu64 " got %d, wanted %d\n",
+					(uint64_t) cqe->user_data, cqe->res,
 					data->expected[idx]);
 			goto err;
 		}
diff --git a/test/poll-many.c b/test/poll-many.c
index 723a353..3f8d08d 100644
--- a/test/poll-many.c
+++ b/test/poll-many.c
@@ -140,6 +140,7 @@
 int main(int argc, char *argv[])
 {
 	struct io_uring ring;
+	struct io_uring_params params = { };
 	struct rlimit rlim;
 	int i, ret;
 
@@ -169,9 +170,18 @@
 		}
 	}
 
-	if (io_uring_queue_init(RING_SIZE, &ring, 0)) {
-		fprintf(stderr, "failed ring init\n");
-		goto err_noring;
+	params.flags = IORING_SETUP_CQSIZE;
+	params.cq_entries = 4096;
+	ret = io_uring_queue_init_params(RING_SIZE, &ring, &params);
+	if (ret) {
+		if (ret == -EINVAL) {
+			fprintf(stdout, "No CQSIZE, trying without\n");
+			ret = io_uring_queue_init(RING_SIZE, &ring, 0);
+			if (ret) {
+				fprintf(stderr, "ring setup failed: %d\n", ret);
+				return 1;
+			}
+		}
 	}
 
 	if (arm_polls(&ring))
diff --git a/test/poll-ring.c b/test/poll-ring.c
new file mode 100644
index 0000000..1f69e20
--- /dev/null
+++ b/test/poll-ring.c
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Description: Test poll against ring itself. A buggy kernel will end up
+ * 		having io_wq_* workers pending, as the circular reference
+ * 		will prevent full exit.
+ *
+ */
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/poll.h>
+
+#include "liburing.h"
+
+int main(int argc, char *argv[])
+{
+	struct io_uring_sqe *sqe;
+	struct io_uring ring;
+	int ret;
+
+	if (argc > 1)
+		return 0;
+
+	ret = io_uring_queue_init(1, &ring, 0);
+	if (ret) {
+		fprintf(stderr, "child: ring setup failed: %d\n", ret);
+		return 1;
+	}
+
+	sqe = io_uring_get_sqe(&ring);
+	if (!sqe) {
+		fprintf(stderr, "get sqe failed\n");
+		return 1;
+	}
+
+	io_uring_prep_poll_add(sqe, ring.ring_fd, POLLIN);
+	io_uring_sqe_set_data(sqe, sqe);
+
+	ret = io_uring_submit(&ring);
+	if (ret <= 0) {
+		fprintf(stderr, "child: sqe submit failed: %d\n", ret);
+		return 1;
+	}
+
+	return 0;
+}
diff --git a/test/read-write.c b/test/read-write.c
index 3bea26f..7f33ad4 100644
--- a/test/read-write.c
+++ b/test/read-write.c
@@ -37,6 +37,23 @@
 	return 0;
 }
 
+static int create_nonaligned_buffers(void)
+{
+	int i;
+
+	vecs = malloc(BUFFERS * sizeof(struct iovec));
+	for (i = 0; i < BUFFERS; i++) {
+		char *p = malloc(3 * BS);
+
+		if (!p)
+			return 1;
+		vecs[i].iov_base = p + (rand() % BS);
+		vecs[i].iov_len = 1 + (rand() % BS);
+	}
+
+	return 0;
+}
+
 static int create_file(const char *file)
 {
 	ssize_t ret;
@@ -56,8 +73,8 @@
 	return ret != FILE_SIZE;
 }
 
-static int __test_io(const char *file, struct io_uring *ring, int write, int buffered,
-		     int sqthread, int fixed, int mixed_fixed, int nonvec,
+static int __test_io(const char *file, struct io_uring *ring, int write,
+		     int buffered, int sqthread, int fixed, int nonvec,
 		     int buf_select, int seq, int exp_len)
 {
 	struct io_uring_sqe *sqe;
@@ -67,10 +84,9 @@
 	off_t offset;
 
 #ifdef VERBOSE
-	fprintf(stdout, "%s: start %d/%d/%d/%d/%d/%d: ", __FUNCTION__, write,
+	fprintf(stdout, "%s: start %d/%d/%d/%d/%d: ", __FUNCTION__, write,
 							buffered, sqthread,
-							fixed, mixed_fixed,
-							nonvec);
+							fixed, nonvec);
 #endif
 	if (sqthread && geteuid()) {
 #ifdef VERBOSE
@@ -156,6 +172,7 @@
 			}
 
 		}
+		sqe->user_data = i;
 		if (sqthread)
 			sqe->flags |= IOSQE_FIXED_FILE;
 		if (buf_select) {
@@ -163,7 +180,6 @@
 				sqe->addr = 0;
 			sqe->flags |= IOSQE_BUFFER_SELECT;
 			sqe->buf_group = buf_select;
-			sqe->user_data = i;
 		}
 		if (seq)
 			offset += BS;
@@ -188,6 +204,14 @@
 				warned = 1;
 				no_read = 1;
 			}
+		} else if (exp_len == -1) {
+			int iov_len = vecs[cqe->user_data].iov_len;
+
+			if (cqe->res != iov_len) {
+				fprintf(stderr, "cqe res %d, wanted %d\n",
+					cqe->res, iov_len);
+				goto err;
+			}
 		} else if (cqe->res != exp_len) {
 			fprintf(stderr, "cqe res %d, wanted %d\n", cqe->res, exp_len);
 			goto err;
@@ -239,7 +263,7 @@
 	return 1;
 }
 static int test_io(const char *file, int write, int buffered, int sqthread,
-		   int fixed, int mixed_fixed, int nonvec)
+		   int fixed, int nonvec, int exp_len)
 {
 	struct io_uring ring;
 	int ret, ring_flags;
@@ -263,8 +287,8 @@
 		return 1;
 	}
 
-	ret = __test_io(file, &ring, write, buffered, sqthread, fixed,
-			mixed_fixed, nonvec, 0, 0, BS);
+	ret = __test_io(file, &ring, write, buffered, sqthread, fixed, nonvec,
+			0, 0, exp_len);
 
 	io_uring_queue_exit(&ring);
 	return ret;
@@ -442,16 +466,48 @@
 		io_uring_cqe_seen(&ring, cqe);
 	}
 
-	ret = __test_io(filename, &ring, 0, 0, 0, 0, 0, nonvec, 1, 1, exp_len);
+	ret = __test_io(filename, &ring, 0, 0, 0, 0, nonvec, 1, 1, exp_len);
 
 	io_uring_queue_exit(&ring);
 	return ret;
 }
 
-static int test_buf_select(const char *filename, int nonvec)
+static int provide_buffers_iovec(struct io_uring *ring, int bgid)
 {
 	struct io_uring_sqe *sqe;
 	struct io_uring_cqe *cqe;
+	int i, ret;
+
+	for (i = 0; i < BUFFERS; i++) {
+		sqe = io_uring_get_sqe(ring);
+		io_uring_prep_provide_buffers(sqe, vecs[i].iov_base,
+						vecs[i].iov_len, 1, bgid, i);
+	}
+
+	ret = io_uring_submit(ring);
+	if (ret != BUFFERS) {
+		fprintf(stderr, "submit: %d\n", ret);
+		return -1;
+	}
+
+	for (i = 0; i < BUFFERS; i++) {
+		ret = io_uring_wait_cqe(ring, &cqe);
+		if (ret) {
+			fprintf(stderr, "wait_cqe=%d\n", ret);
+			return 1;
+		}
+		if (cqe->res < 0) {
+			fprintf(stderr, "cqe->res=%d\n", cqe->res);
+			return 1;
+		}
+		io_uring_cqe_seen(ring, cqe);
+	}
+
+	return 0;
+}
+
+static int test_buf_select(const char *filename, int nonvec)
+{
 	struct io_uring_probe *p;
 	struct io_uring ring;
 	int ret, i;
@@ -476,7 +532,7 @@
 	for (i = 0; i < BUFFERS; i++)
 		memset(vecs[i].iov_base, i, vecs[i].iov_len);
 
-	ret = __test_io(filename, &ring, 1, 0, 0, 0, 0, 0, 0, 1, BS);
+	ret = __test_io(filename, &ring, 1, 0, 0, 0, 0, 0, 1, BS);
 	if (ret) {
 		fprintf(stderr, "failed writing data\n");
 		return 1;
@@ -485,29 +541,67 @@
 	for (i = 0; i < BUFFERS; i++)
 		memset(vecs[i].iov_base, 0x55, vecs[i].iov_len);
 
-	for (i = 0; i < BUFFERS; i++) {
+	ret = provide_buffers_iovec(&ring, 1);
+	if (ret)
+		return ret;
+
+	ret = __test_io(filename, &ring, 0, 0, 0, 0, nonvec, 1, 1, BS);
+	io_uring_queue_exit(&ring);
+	return ret;
+}
+
+static int test_rem_buf(int batch, int sqe_flags)
+{
+	struct io_uring_sqe *sqe;
+	struct io_uring_cqe *cqe;
+	struct io_uring ring;
+	int left, ret, nr = 0;
+	int bgid = 1;
+
+	if (no_buf_select)
+		return 0;
+
+	ret = io_uring_queue_init(64, &ring, 0);
+	if (ret) {
+		fprintf(stderr, "ring create failed: %d\n", ret);
+		return 1;
+	}
+
+	ret = provide_buffers_iovec(&ring, bgid);
+	if (ret)
+		return ret;
+
+	left = BUFFERS;
+	while (left) {
+		int to_rem = (left < batch) ? left : batch;
+
+		left -= to_rem;
 		sqe = io_uring_get_sqe(&ring);
-		io_uring_prep_provide_buffers(sqe, vecs[i].iov_base,
-						vecs[i].iov_len, 1, 1, i);
+		io_uring_prep_remove_buffers(sqe, to_rem, bgid);
+		sqe->user_data = to_rem;
+		sqe->flags |= sqe_flags;
+		++nr;
 	}
 
 	ret = io_uring_submit(&ring);
-	if (ret != BUFFERS) {
+	if (ret != nr) {
 		fprintf(stderr, "submit: %d\n", ret);
 		return -1;
 	}
 
-	for (i = 0; i < BUFFERS; i++) {
+	for (; nr > 0; nr--) {
 		ret = io_uring_wait_cqe(&ring, &cqe);
-		if (cqe->res < 0) {
+		if (ret) {
+			fprintf(stderr, "wait_cqe=%d\n", ret);
+			return 1;
+		}
+		if (cqe->res != cqe->user_data) {
 			fprintf(stderr, "cqe->res=%d\n", cqe->res);
 			return 1;
 		}
 		io_uring_cqe_seen(&ring, cqe);
 	}
 
-	ret = __test_io(filename, &ring, 0, 0, 0, 0, 0, nonvec, 1, 1, BS);
-
 	io_uring_queue_exit(&ring);
 	return ret;
 }
@@ -594,14 +688,15 @@
 	struct io_uring_sqe *sqe;
 	struct io_uring_cqe *cqe;
 	struct io_uring ring;
-	struct rlimit rlim;
+	struct rlimit rlim, old_rlim;
 	int i, fd, ret;
 	loff_t off;
 
-	if (getrlimit(RLIMIT_FSIZE, &rlim) < 0) {
+	if (getrlimit(RLIMIT_FSIZE, &old_rlim) < 0) {
 		perror("getrlimit");
 		return 1;
 	}
+	rlim = old_rlim;
 	rlim.rlim_cur = 64 * 1024;
 	rlim.rlim_max = 64 * 1024;
 	if (setrlimit(RLIMIT_FSIZE, &rlim) < 0) {
@@ -661,6 +756,11 @@
 	io_uring_queue_exit(&ring);
 	close(fd);
 	unlink(".efbig");
+
+	if (setrlimit(RLIMIT_FSIZE, &old_rlim) < 0) {
+		perror("setrlimit");
+		return 1;
+	}
 	return 0;
 err:
 	if (fd != -1)
@@ -690,24 +790,20 @@
 	}
 
 	/* if we don't have nonvec read, skip testing that */
-	if (has_nonvec_read())
-		nr = 64;
-	else
-		nr = 32;
+	nr = has_nonvec_read() ? 32 : 16;
 
 	for (i = 0; i < nr; i++) {
-		int v1, v2, v3, v4, v5, v6;
+		int write = (i & 1) != 0;
+		int buffered = (i & 2) != 0;
+		int sqthread = (i & 4) != 0;
+		int fixed = (i & 8) != 0;
+		int nonvec = (i & 16) != 0;
 
-		v1 = (i & 1) != 0;
-		v2 = (i & 2) != 0;
-		v3 = (i & 4) != 0;
-		v4 = (i & 8) != 0;
-		v5 = (i & 16) != 0;
-		v6 = (i & 32) != 0;
-		ret = test_io(fname, v1, v2, v3, v4, v5, v6);
+		ret = test_io(fname, write, buffered, sqthread, fixed, nonvec,
+			      BS);
 		if (ret) {
-			fprintf(stderr, "test_io failed %d/%d/%d/%d/%d/%d\n",
-					v1, v2, v3, v4, v5, v6);
+			fprintf(stderr, "test_io failed %d/%d/%d/%d/%d\n",
+				write, buffered, sqthread, fixed, nonvec);
 			goto err;
 		}
 	}
@@ -760,6 +856,57 @@
 		goto err;
 	}
 
+	ret = test_rem_buf(1, 0);
+	if (ret) {
+		fprintf(stderr, "test_rem_buf by 1 failed\n");
+		goto err;
+	}
+
+	ret = test_rem_buf(10, 0);
+	if (ret) {
+		fprintf(stderr, "test_rem_buf by 10 failed\n");
+		goto err;
+	}
+
+	ret = test_rem_buf(2, IOSQE_IO_LINK);
+	if (ret) {
+		fprintf(stderr, "test_rem_buf link failed\n");
+		goto err;
+	}
+
+	ret = test_rem_buf(2, IOSQE_ASYNC);
+	if (ret) {
+		fprintf(stderr, "test_rem_buf async failed\n");
+		goto err;
+	}
+
+	srand((unsigned)time(NULL));
+	if (create_nonaligned_buffers()) {
+		fprintf(stderr, "file creation failed\n");
+		goto err;
+	}
+
+	/* test fixed bufs with non-aligned len/offset */
+	for (i = 0; i < nr; i++) {
+		int write = (i & 1) != 0;
+		int buffered = (i & 2) != 0;
+		int sqthread = (i & 4) != 0;
+		int fixed = (i & 8) != 0;
+		int nonvec = (i & 16) != 0;
+
+		/* direct IO requires alignment, skip it */
+		if (!buffered || !fixed || nonvec)
+			continue;
+
+		ret = test_io(fname, write, buffered, sqthread, fixed, nonvec,
+			      -1);
+		if (ret) {
+			fprintf(stderr, "test_io failed %d/%d/%d/%d/%d\n",
+				write, buffered, sqthread, fixed, nonvec);
+			goto err;
+		}
+	}
+
 	if (fname != argv[1])
 		unlink(fname);
 	return 0;
diff --git a/test/register-restrictions.c b/test/register-restrictions.c
index 4f64c41..04a0ed9 100644
--- a/test/register-restrictions.c
+++ b/test/register-restrictions.c
@@ -406,8 +406,8 @@
 		case 2: /* writev - flags = IOSQE_FIXED_FILE | IOSQE_ASYNC */
 		case 3: /* writev - flags = IOSQE_FIXED_FILE | IOSQE_IO_LINK */
 			if (cqe->res != sizeof(ptr)) {
-				fprintf(stderr, "write res: %d user_data %lld \n",
-					cqe->res, cqe->user_data);
+				fprintf(stderr, "write res: %d user_data %" PRIu64 "\n",
+					cqe->res, (uint64_t) cqe->user_data);
 				return TEST_FAILED;
 			}
 
@@ -417,8 +417,8 @@
 		case 6: /* writev - flags = IOSQE_ASYNC */
 		case 7: /* writev - flags = 0 */
 			if (cqe->res != -EACCES) {
-				fprintf(stderr, "write res: %d user_data %lld \n",
-					cqe->res, cqe->user_data);
+				fprintf(stderr, "write res: %d user_data %" PRIu64 "\n",
+					cqe->res, (uint64_t) cqe->user_data);
 				return TEST_FAILED;
 			}
 			break;
diff --git a/test/rename.c b/test/rename.c
new file mode 100644
index 0000000..af09d65
--- /dev/null
+++ b/test/rename.c
@@ -0,0 +1,134 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Description: run various nop tests
+ *
+ */
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+
+#include "liburing.h"
+
+static int test_rename(struct io_uring *ring, const char *old, const char *new)
+{
+	struct io_uring_cqe *cqe;
+	struct io_uring_sqe *sqe;
+	int ret;
+
+	sqe = io_uring_get_sqe(ring);
+	if (!sqe) {
+		fprintf(stderr, "get sqe failed\n");
+		goto err;
+	}
+
+	memset(sqe, 0, sizeof(*sqe));
+	sqe->opcode = IORING_OP_RENAMEAT;
+	sqe->fd = AT_FDCWD;
+	sqe->addr2 = (unsigned long) new;
+	sqe->addr = (unsigned long) old;
+	sqe->len = AT_FDCWD;
+	
+	ret = io_uring_submit(ring);
+	if (ret <= 0) {
+		fprintf(stderr, "sqe submit failed: %d\n", ret);
+		goto err;
+	}
+
+	ret = io_uring_wait_cqe(ring, &cqe);
+	if (ret < 0) {
+		fprintf(stderr, "wait completion %d\n", ret);
+		goto err;
+	}
+	ret = cqe->res;
+	io_uring_cqe_seen(ring, cqe);
+	return ret;
+err:
+	return 1;
+}
+
+static int stat_file(const char *buf)
+{
+	struct stat sb;
+
+	if (!stat(buf, &sb))
+		return 0;
+
+	return errno;
+}
+
+int main(int argc, char *argv[])
+{
+	struct io_uring ring;
+	char src[32] = "./XXXXXX";
+	char dst[32] = "./XXXXXX";
+	int ret;
+
+	if (argc > 1)
+		return 0;
+
+	ret = io_uring_queue_init(1, &ring, 0);
+	if (ret) {
+		fprintf(stderr, "ring setup failed: %d\n", ret);
+		return 1;
+	}
+
+	ret = mkstemp(src);
+	if (ret < 0) {
+		perror("mkstemp");
+		return 1;
+	}
+	close(ret);
+
+	ret = mkstemp(dst);
+	if (ret < 0) {
+		perror("mkstemp");
+		return 1;
+	}
+	close(ret);
+
+	if (stat_file(src) != 0) {
+		perror("stat");
+		return 1;
+	}
+	if (stat_file(dst) != 0) {
+		perror("stat");
+		return 1;
+	}
+
+	ret = test_rename(&ring, src, dst);
+	if (ret < 0) {
+		if (ret == -EBADF || ret == -EINVAL) {
+			fprintf(stdout, "Rename not supported, skipping\n");
+			goto out;
+		}
+		fprintf(stderr, "rename: %s\n", strerror(-ret));
+		goto err;
+	} else if (ret)
+		goto err;
+
+	if (stat_file(src) != ENOENT) {
+		fprintf(stderr, "stat got %s\n", strerror(ret));
+		return 1;
+	}
+
+	if (stat_file(dst) != 0) {
+		perror("stat");
+		return 1;
+	}
+
+	ret = test_rename(&ring, "/x/y/1/2", "/2/1/y/x");
+	if (ret != -ENOENT) {
+		fprintf(stderr, "test_rename invalid failed: %d\n", ret);
+		return ret;
+	}
+out:
+	unlink(dst);
+	return 0;
+err:
+	unlink(src);
+	unlink(dst);
+	return 1;
+}
diff --git a/test/ring-leak2.c b/test/ring-leak2.c
new file mode 100644
index 0000000..d9bfe0f
--- /dev/null
+++ b/test/ring-leak2.c
@@ -0,0 +1,248 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Description: Test two ring deadlock. A buggy kernel will end up
+ * 		having io_wq_* workers pending, as the circular reference
+ * 		will prevent full exit.
+ *
+ * Based on a test case from Josef <josef.grieb@gmail.com>
+ *
+ */
+#include <errno.h>
+#include <fcntl.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <sys/poll.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <sys/eventfd.h>
+#include <pthread.h>
+
+#include "liburing.h"
+#include "../src/syscall.h"
+
+enum {
+	ACCEPT,
+	READ,
+	WRITE,
+	POLLING_IN,
+	POLLING_RDHUP,
+	CLOSE,
+	EVENTFD_READ,
+};
+
+typedef struct conn_info {
+	__u32 fd;
+	__u16 type;
+	__u16 bid;
+} conn_info;
+
+static char read_eventfd_buffer[8];
+
+static pthread_mutex_t lock;
+static struct io_uring *client_ring;
+
+static int client_eventfd = -1;
+
+int setup_io_uring(struct io_uring *ring)
+{
+	struct io_uring_params p = { };
+	int ret;
+
+	ret = io_uring_queue_init_params(8, ring, &p);
+	if (ret) {
+		fprintf(stderr, "Unable to setup io_uring: %s\n",
+			strerror(-ret));
+		return 1;
+	}
+	return 0;
+}
+
+static void add_socket_eventfd_read(struct io_uring *ring, int fd)
+{
+	struct io_uring_sqe *sqe;
+	conn_info conn_i = {
+		.fd = fd,
+		.type = EVENTFD_READ,
+	};
+
+	sqe = io_uring_get_sqe(ring);
+	io_uring_prep_read(sqe, fd, &read_eventfd_buffer, 8, 0);
+	io_uring_sqe_set_flags(sqe, IOSQE_ASYNC);
+
+	memcpy(&sqe->user_data, &conn_i, sizeof(conn_i));
+}
+
+static void add_socket_pollin(struct io_uring *ring, int fd)
+{
+	struct io_uring_sqe *sqe;
+	conn_info conn_i = {
+		.fd = fd,
+		.type = POLLING_IN,
+	};
+
+	sqe = io_uring_get_sqe(ring);
+	io_uring_prep_poll_add(sqe, fd, POLL_IN);
+
+	memcpy(&sqe->user_data, &conn_i, sizeof(conn_i));
+}
+
+static void *server_thread(void *arg)
+{
+	struct sockaddr_in serv_addr;
+	int port = 0;
+	int sock_listen_fd, evfd;
+	const int val = 1;
+	struct io_uring ring;
+       
+	sock_listen_fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
+	setsockopt(sock_listen_fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
+
+	memset(&serv_addr, 0, sizeof(serv_addr));
+	serv_addr.sin_family = AF_INET;
+	serv_addr.sin_port = htons(port);
+	serv_addr.sin_addr.s_addr = INADDR_ANY;
+
+	evfd = eventfd(0, EFD_CLOEXEC);
+
+	// bind and listen
+	if (bind(sock_listen_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
+		perror("Error binding socket...\n");
+		exit(1);
+	}
+	if (listen(sock_listen_fd, 1) < 0) {
+		perror("Error listening on socket...\n");
+		exit(1);
+	}
+
+	setup_io_uring(&ring);
+	add_socket_eventfd_read(&ring, evfd);
+	add_socket_pollin(&ring, sock_listen_fd);
+
+	while (1) {
+		struct io_uring_cqe *cqe;
+		unsigned head;
+		unsigned count = 0;
+
+		io_uring_submit_and_wait(&ring, 1);
+
+		io_uring_for_each_cqe(&ring, head, cqe) {
+			struct conn_info conn_i;
+
+			count++;
+			memcpy(&conn_i, &cqe->user_data, sizeof(conn_i));
+
+			if (conn_i.type == ACCEPT) {
+				int sock_conn_fd = cqe->res;
+				// only read when there is no error, >= 0
+				if (sock_conn_fd > 0) {
+					add_socket_pollin(&ring, sock_listen_fd);
+
+					pthread_mutex_lock(&lock);
+					io_uring_submit(client_ring);
+					pthread_mutex_unlock(&lock);
+
+				}
+			} else if (conn_i.type == POLLING_IN) {
+				break;
+			}
+		}
+		io_uring_cq_advance(&ring, count);
+	}
+}
+
+static void *client_thread(void *arg)
+{
+	struct io_uring ring;
+	int ret;
+
+	setup_io_uring(&ring);
+	client_ring = &ring;
+
+	client_eventfd = eventfd(0, EFD_CLOEXEC);
+	pthread_mutex_lock(&lock);
+	add_socket_eventfd_read(&ring, client_eventfd);
+	pthread_mutex_unlock(&lock);
+
+	while (1) {
+		struct io_uring_cqe *cqe;
+		unsigned head;
+		unsigned count = 0;
+
+		pthread_mutex_lock(&lock);
+		io_uring_submit(&ring);
+		pthread_mutex_unlock(&lock);
+
+		ret = __sys_io_uring_enter(ring.ring_fd, 0, 1, IORING_ENTER_GETEVENTS, NULL);
+		if (ret < 0) {
+			perror("Error io_uring_enter...\n");
+			exit(1);
+		}
+
+		// go through all CQEs
+		io_uring_for_each_cqe(&ring, head, cqe) {
+			struct conn_info conn_i;
+			int type;
+
+			count++;
+			memcpy(&conn_i, &cqe->user_data, sizeof(conn_i));
+
+			type = conn_i.type;
+			if (type == READ) {
+				pthread_mutex_lock(&lock);
+
+				if (cqe->res <= 0) {
+					// connection closed or error
+					shutdown(conn_i.fd, SHUT_RDWR);
+				} else {
+					break;
+				}
+				add_socket_pollin(&ring, conn_i.fd);
+				pthread_mutex_unlock(&lock);
+			} else if (type == WRITE) {
+			} else if (type == POLLING_IN) {
+				break;
+			} else if (type == POLLING_RDHUP) {
+				break;
+			} else if (type == CLOSE) {
+			} else if (type == EVENTFD_READ) {
+				add_socket_eventfd_read(&ring, client_eventfd);
+			}
+		}
+
+		io_uring_cq_advance(&ring, count);
+	}
+}
+
+static void sig_alrm(int sig)
+{
+	exit(0);
+}
+
+int main(int argc, char *argv[])
+{
+	pthread_t server_thread_t, client_thread_t;
+	struct sigaction act;
+
+	if (argc > 1)
+		return 0;
+
+	if (pthread_mutex_init(&lock, NULL) != 0) {
+		printf("\n mutex init failed\n");
+		return 1;
+	}
+
+	pthread_create(&server_thread_t, NULL, &server_thread, NULL);
+	pthread_create(&client_thread_t, NULL, &client_thread, NULL);
+
+	memset(&act, 0, sizeof(act));
+	act.sa_handler = sig_alrm;
+	act.sa_flags = SA_RESTART;
+	sigaction(SIGALRM, &act, NULL);
+	alarm(1);
+
+	pthread_join(server_thread_t, NULL);
+	return 0;
+}
diff --git a/test/runtests.sh b/test/runtests.sh
index fa240f2..e8f4ae5 100755
--- a/test/runtests.sh
+++ b/test/runtests.sh
@@ -5,10 +5,11 @@
 TIMEOUT=60
 DMESG_FILTER="cat"
 TEST_DIR=$(dirname $0)
-TEST_FILES=""
 FAILED=""
 SKIPPED=""
 MAYBE_FAILED=""
+TEST_FILES=""
+declare -A TEST_MAP
 
 # Only use /dev/kmsg if running as root
 DO_KMSG="1"
@@ -23,6 +24,12 @@
 			exit 1
 		fi
 	done
+	for dev in ${TEST_MAP[@]}; do
+		if [ ! -e "$dev" ]; then
+			echo "Test file in map $dev not valid"
+			exit 1
+		fi
+	done
 fi
 
 _check_dmesg()
@@ -84,7 +91,7 @@
 	fi
 
 	# Run the test
-	timeout --preserve-status -s INT -k $TIMEOUT $TIMEOUT ./$test_name $dev
+	timeout -s INT -k $TIMEOUT $TIMEOUT ./$test_name $dev
 	local status=$?
 
 	# Check test status
@@ -109,11 +116,15 @@
 
 # Run all specified tests
 for tst in $TESTS; do
-	run_test $tst
-	if [ ! -z "$TEST_FILES" ]; then
-		for dev in $TEST_FILES; do
-			run_test $tst $dev
-		done
+	if [ ! -n "${TEST_MAP[$tst]}" ]; then
+		run_test $tst
+		if [ ! -z "$TEST_FILES" ]; then
+			for dev in $TEST_FILES; do
+				run_test $tst $dev
+			done
+		fi
+	else
+		run_test $tst ${TEST_MAP[$tst]}
 	fi
 done
 
diff --git a/test/self.c b/test/self.c
new file mode 100644
index 0000000..422c9e3
--- /dev/null
+++ b/test/self.c
@@ -0,0 +1,91 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Description: test that pathname resolution works from async context when
+ * using /proc/self/ which should be the original submitting task, not the
+ * async worker.
+ *
+ */
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+
+#include "liburing.h"
+
+static int io_openat2(struct io_uring *ring, const char *path, int dfd)
+{
+	struct io_uring_cqe *cqe;
+	struct io_uring_sqe *sqe;
+	struct open_how how;
+	int ret;
+
+	sqe = io_uring_get_sqe(ring);
+	if (!sqe) {
+		fprintf(stderr, "get sqe failed\n");
+		goto err;
+	}
+	memset(&how, 0, sizeof(how));
+	how.flags = O_RDONLY;
+	io_uring_prep_openat2(sqe, dfd, path, &how);
+
+	ret = io_uring_submit(ring);
+	if (ret <= 0) {
+		fprintf(stderr, "sqe submit failed: %d\n", ret);
+		goto err;
+	}
+
+	ret = io_uring_wait_cqe(ring, &cqe);
+	if (ret < 0) {
+		fprintf(stderr, "wait completion %d\n", ret);
+		goto err;
+	}
+	ret = cqe->res;
+	io_uring_cqe_seen(ring, cqe);
+	return ret;
+err:
+	return -1;
+}
+
+int main(int argc, char *argv[])
+{
+	struct io_uring ring;
+	char buf[64];
+	int ret;
+
+	if (argc > 1)
+		return 0;
+
+	ret = io_uring_queue_init(1, &ring, 0);
+	if (ret) {
+		fprintf(stderr, "ring setup failed\n");
+		return 1;
+	}
+
+	ret = io_openat2(&ring, "/proc/self/comm", -1);
+	if (ret < 0) {
+		if (ret == -EOPNOTSUPP)
+			return 0;
+		if (ret == -EINVAL) {
+			fprintf(stdout, "openat2 not supported, skipping\n");
+			return 0;
+		}
+		fprintf(stderr, "openat2 failed: %s\n", strerror(-ret));
+		return 1;
+	}
+
+	memset(buf, 0, sizeof(buf));
+	ret = read(ret, buf, sizeof(buf));
+	if (ret < 0) {
+		perror("read");
+		return 1;
+	}
+
+	if (strncmp(buf, "self", 4)) {
+		fprintf(stderr, "got comm=<%s>, wanted <self>\n", buf);
+		return 1;
+	}
+
+	return 0;
+}
diff --git a/test/send_recvmsg.c b/test/send_recvmsg.c
index 50c8e94..6b513bc 100644
--- a/test/send_recvmsg.c
+++ b/test/send_recvmsg.c
@@ -11,6 +11,7 @@
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <pthread.h>
+#include <assert.h>
 
 #include "liburing.h"
 
@@ -24,7 +25,10 @@
 #define BUF_BGID	10
 #define BUF_BID		89
 
-static int recv_prep(struct io_uring *ring, struct iovec *iov, int bgid)
+#define MAX_IOV_COUNT	10
+
+static int recv_prep(struct io_uring *ring, struct iovec iov[], int iov_count,
+		     int bgid)
 {
 	struct sockaddr_in saddr;
 	struct msghdr msg;
@@ -53,11 +57,6 @@
 		goto err;
 	}
 
-	memset(&msg, 0, sizeof(msg));
-        msg.msg_namelen = sizeof(struct sockaddr_in);
-	msg.msg_iov = iov;
-	msg.msg_iovlen = 1;
-
 	sqe = io_uring_get_sqe(ring);
 	if (!sqe) {
 		fprintf(stderr, "io_uring_get_sqe failed\n");
@@ -66,11 +65,15 @@
 
 	io_uring_prep_recvmsg(sqe, sockfd, &msg, 0);
 	if (bgid) {
-		sqe->user_data = (unsigned long) iov->iov_base;
 		iov->iov_base = NULL;
 		sqe->flags |= IOSQE_BUFFER_SELECT;
 		sqe->buf_group = bgid;
+		iov_count = 1;
 	}
+	memset(&msg, 0, sizeof(msg));
+	msg.msg_namelen = sizeof(struct sockaddr_in);
+	msg.msg_iov = iov;
+	msg.msg_iovlen = iov_count;
 
 	ret = io_uring_submit(ring);
 	if (ret <= 0) {
@@ -89,9 +92,10 @@
 	pthread_mutex_t *mutex;
 	int buf_select;
 	int no_buf_add;
+	int iov_count;
 };
 
-static int do_recvmsg(struct io_uring *ring, struct iovec *iov,
+static int do_recvmsg(struct io_uring *ring, char buf[MAX_MSG + 1],
 		      struct recv_data *rd)
 {
 	struct io_uring_cqe *cqe;
@@ -112,8 +116,6 @@
 		int bid = cqe->flags >> 16;
 		if (bid != BUF_BID)
 			fprintf(stderr, "Buffer ID mismatch %d\n", bid);
-		/* just for passing the pointer to str */
-		iov->iov_base = (void *) (uintptr_t) cqe->user_data;
 	}
 
 	if (rd->no_buf_add && rd->buf_select) {
@@ -127,7 +129,7 @@
 		goto err;
 	}
 
-	if (strcmp(str, iov->iov_base)) {
+	if (strncmp(str, buf, MAX_MSG + 1)) {
 		fprintf(stderr, "string mismatch\n");
 		goto err;
 	}
@@ -137,20 +139,34 @@
 	return 1;
 }
 
+static void init_iov(struct iovec iov[MAX_IOV_COUNT], int iov_to_use,
+		     char buf[MAX_MSG + 1])
+{
+	int i, last_idx = iov_to_use - 1;
+
+	assert(0 < iov_to_use && iov_to_use <= MAX_IOV_COUNT);
+	for (i = 0; i < last_idx; ++i) {
+		iov[i].iov_base = buf + i;
+		iov[i].iov_len = 1;
+	}
+
+	iov[last_idx].iov_base = buf + last_idx;
+	iov[last_idx].iov_len = MAX_MSG - last_idx;
+}
+
 static void *recv_fn(void *data)
 {
 	struct recv_data *rd = data;
 	pthread_mutex_t *mutex = rd->mutex;
 	char buf[MAX_MSG + 1];
-	struct iovec iov = {
-		.iov_base = buf,
-		.iov_len = sizeof(buf) - 1,
-	};
+	struct iovec iov[MAX_IOV_COUNT];
 	struct io_uring_sqe *sqe;
 	struct io_uring_cqe *cqe;
 	struct io_uring ring;
 	int ret;
 
+	init_iov(iov, rd->iov_count, buf);
+
 	ret = io_uring_queue_init(1, &ring, 0);
 	if (ret) {
 		fprintf(stderr, "queue init failed: %d\n", ret);
@@ -184,14 +200,14 @@
 		}
 	}
 
-	ret = recv_prep(&ring, &iov, rd->buf_select ? BUF_BGID : 0);
+	ret = recv_prep(&ring, iov, rd->iov_count, rd->buf_select ? BUF_BGID : 0);
 	if (ret) {
 		fprintf(stderr, "recv_prep failed: %d\n", ret);
 		goto err;
 	}
 
 	pthread_mutex_unlock(mutex);
-	ret = do_recvmsg(&ring, &iov, rd);
+	ret = do_recvmsg(&ring, buf, rd);
 
 	io_uring_queue_exit(&ring);
 
@@ -261,7 +277,7 @@
 	return 1;
 }
 
-static int test(int buf_select, int no_buf_add)
+static int test(int buf_select, int no_buf_add, int iov_count)
 {
 	struct recv_data rd;
 	pthread_mutexattr_t attr;
@@ -278,6 +294,7 @@
 	rd.mutex = &mutex;
 	rd.buf_select = buf_select;
 	rd.no_buf_add = no_buf_add;
+	rd.iov_count = iov_count;
 	ret = pthread_create(&recv_thread, NULL, recv_fn, &rd);
 	if (ret) {
 		fprintf(stderr, "Thread create failed\n");
@@ -299,19 +316,25 @@
 	if (argc > 1)
 		return 0;
 
-	ret = test(0, 0);
+	ret = test(0, 0, 1);
 	if (ret) {
 		fprintf(stderr, "send_recvmsg 0 failed\n");
 		return 1;
 	}
 
-	ret = test(1, 0);
+	ret = test(0, 0, 10);
+	if (ret) {
+		fprintf(stderr, "send_recvmsg multi iov failed\n");
+		return 1;
+	}
+
+	ret = test(1, 0, 1);
 	if (ret) {
 		fprintf(stderr, "send_recvmsg 1 0 failed\n");
 		return 1;
 	}
 
-	ret = test(1, 1);
+	ret = test(1, 1, 1);
 	if (ret) {
 		fprintf(stderr, "send_recvmsg 1 1 failed\n");
 		return 1;
diff --git a/test/sendmsg_fs_cve.c b/test/sendmsg_fs_cve.c
new file mode 100644
index 0000000..85f271b
--- /dev/null
+++ b/test/sendmsg_fs_cve.c
@@ -0,0 +1,193 @@
+/*
+ * repro-CVE-2020-29373 -- Reproducer for CVE-2020-29373.
+ *
+ * Copyright (c) 2021 SUSE
+ * Author: Nicolai Stange <nstange@suse.de>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <unistd.h>
+#include <syscall.h>
+#include <stdio.h>
+#include <sys/mman.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include "liburing.h"
+
+/*
+ * This attempts to make the kernel issue a sendmsg() to
+ * path from io_uring's async io_sq_wq_submit_work().
+ *
+ * Unfortunately, IOSQE_ASYNC is available only from kernel version
+ * 5.6 onwards. To still force io_uring to process the request
+ * asynchronously from io_sq_wq_submit_work(), queue a couple of
+ * auxiliary requests all failing with EAGAIN before. This is
+ * implemented by writing repeatedly to an auxiliary O_NONBLOCK
+ * AF_UNIX socketpair with a small SO_SNDBUF.
+ */
+static int try_sendmsg_async(const char * const path)
+{
+	int snd_sock, r;
+	struct io_uring ring;
+	char sbuf[16] = {};
+	struct iovec siov = { .iov_base = &sbuf, .iov_len = sizeof(sbuf) };
+	struct sockaddr_un addr = {};
+	struct msghdr msg = {
+		.msg_name = &addr,
+		.msg_namelen = sizeof(addr),
+		.msg_iov = &siov,
+		.msg_iovlen = 1,
+	};
+	struct io_uring_cqe *cqe;
+	struct io_uring_sqe *sqe;
+
+	snd_sock = socket(AF_UNIX, SOCK_DGRAM, 0);
+	if (snd_sock < 0) {
+		perror("socket(AF_UNIX)");
+		return -1;
+	}
+
+	addr.sun_family = AF_UNIX;
+	strcpy(addr.sun_path, path);
+
+	r = io_uring_queue_init(512, &ring, 0);
+	if (r < 0) {
+		fprintf(stderr, "ring setup failed: %d\n", r);
+		goto close_iour;
+	}
+
+	sqe = io_uring_get_sqe(&ring);
+	if (!sqe) {
+		fprintf(stderr, "get sqe failed\n");
+		r = -EFAULT;
+		goto close_iour;
+	}
+
+	/* the actual one supposed to fail with -ENOENT. */
+	io_uring_prep_sendmsg(sqe, snd_sock, &msg, 0);
+	sqe->flags = IOSQE_ASYNC;
+	sqe->user_data = 255;
+
+	r = io_uring_submit(&ring);
+	if (r != 1) {
+		fprintf(stderr, "sqe submit failed: %d\n", r);
+		r = -EFAULT;
+		goto close_iour;
+	}
+
+	r = io_uring_wait_cqe(&ring, &cqe);
+	if (r < 0) {
+		fprintf(stderr, "wait completion %d\n", r);
+		r = -EFAULT;
+		goto close_iour;
+	}
+	if (cqe->user_data != 255) {
+		fprintf(stderr, "user data %d\n", r);
+		r = -EFAULT;
+		goto close_iour;
+	}
+	if (cqe->res != -ENOENT) {
+		r = 3;
+		fprintf(stderr,
+			"error: cqe %i: res=%i, but expected -ENOENT\n",
+			(int)cqe->user_data, (int)cqe->res);
+	}
+	io_uring_cqe_seen(&ring, cqe);
+
+close_iour:
+	io_uring_queue_exit(&ring);
+	close(snd_sock);
+	return r;
+}
+
+int main(int argc, char *argv[])
+{
+	int r;
+	char tmpdir[] = "/tmp/tmp.XXXXXX";
+	int rcv_sock;
+	struct sockaddr_un addr = {};
+	pid_t c;
+	int wstatus;
+
+	if (!mkdtemp(tmpdir)) {
+		perror("mkdtemp()");
+		return 1;
+	}
+
+	rcv_sock = socket(AF_UNIX, SOCK_DGRAM, 0);
+	if (rcv_sock < 0) {
+		perror("socket(AF_UNIX)");
+		r = 1;
+		goto rmtmpdir;
+	}
+
+	addr.sun_family = AF_UNIX;
+	snprintf(addr.sun_path, sizeof(addr.sun_path), "%s/sock", tmpdir);
+
+	r = bind(rcv_sock, (struct sockaddr *)&addr,
+		 sizeof(addr));
+	if (r < 0) {
+		perror("bind()");
+		close(rcv_sock);
+		r = 1;
+		goto rmtmpdir;
+	}
+
+	c = fork();
+	if (!c) {
+		close(rcv_sock);
+
+		if (chroot(tmpdir)) {
+			perror("chroot()");
+			return 1;
+		}
+
+		r = try_sendmsg_async(addr.sun_path);
+		if (r < 0) {
+			/* system call failure */
+			r = 1;
+		} else if (r) {
+			/* test case failure */
+			r += 1;
+		}
+		return r;
+	}
+
+	if (waitpid(c, &wstatus, 0) == (pid_t)-1) {
+		perror("waitpid()");
+		r = 1;
+		goto rmsock;
+	}
+	if (!WIFEXITED(wstatus)) {
+		fprintf(stderr, "child got terminated\n");
+		r = 1;
+		goto rmsock;
+	}
+	r = WEXITSTATUS(wstatus);
+	if (r)
+		fprintf(stderr, "error: Test failed\n");
+rmsock:
+	close(rcv_sock);
+	unlink(addr.sun_path);
+rmtmpdir:
+	rmdir(tmpdir);
+	return r;
+}
diff --git a/test/shutdown.c b/test/shutdown.c
index eb66ded..5f4e9cc 100644
--- a/test/shutdown.c
+++ b/test/shutdown.c
@@ -24,7 +24,7 @@
 
 int main(int argc, char *argv[])
 {
-	int p_fd[2];
+	int p_fd[2], ret;
 	int32_t recv_s0;
 	int32_t val = 1;
 	struct sockaddr_in addr;
@@ -34,34 +34,42 @@
 
 	recv_s0 = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, IPPROTO_TCP);
 
-	assert(setsockopt(recv_s0, SOL_SOCKET, SO_REUSEPORT, &val, sizeof(val)) != -1);
-	assert(setsockopt(recv_s0, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) != -1);
+	ret = setsockopt(recv_s0, SOL_SOCKET, SO_REUSEPORT, &val, sizeof(val));
+	assert(ret != -1);
+	ret = setsockopt(recv_s0, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
+	assert(ret != -1);
 
 	addr.sin_family = AF_INET;
 	addr.sin_port = 0x1235;
 	addr.sin_addr.s_addr = 0x0100007fU;
 
-	assert(bind(recv_s0, (struct sockaddr*)&addr, sizeof(addr)) != -1);
-	assert(listen(recv_s0, 128) != -1);
+	ret = bind(recv_s0, (struct sockaddr*)&addr, sizeof(addr));
+	assert(ret != -1);
+	ret = listen(recv_s0, 128);
+	assert(ret != -1);
 
 	p_fd[1] = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, IPPROTO_TCP);
 
 	val = 1;
-	assert(setsockopt(p_fd[1], IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val)) != -1);
+	ret = setsockopt(p_fd[1], IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val));
+	assert(ret != -1);
 
 	int32_t flags = fcntl(p_fd[1], F_GETFL, 0);
 	assert(flags != -1);
 
 	flags |= O_NONBLOCK;
-	assert(fcntl(p_fd[1], F_SETFL, flags) != -1);
+	ret = fcntl(p_fd[1], F_SETFL, flags);
+	assert(ret != -1);
 
-	assert(connect(p_fd[1], (struct sockaddr*)&addr, sizeof(addr)) == -1);
+	ret = connect(p_fd[1], (struct sockaddr*)&addr, sizeof(addr));
+	assert(ret == -1);
 
 	flags = fcntl(p_fd[1], F_GETFL, 0);
 	assert(flags != -1);
 
 	flags &= ~O_NONBLOCK;
-	assert(fcntl(p_fd[1], F_SETFL, flags) != -1);
+	ret = fcntl(p_fd[1], F_SETFL, flags);
+	assert(ret != -1);
 
 	p_fd[0] = accept(recv_s0, NULL, NULL);
 	assert(p_fd[0] != -1);
@@ -72,7 +80,8 @@
 		int32_t code;
 		socklen_t code_len = sizeof(code);
 
-		assert(getsockopt(p_fd[1], SOL_SOCKET, SO_ERROR, &code, &code_len) != -1);
+		ret = getsockopt(p_fd[1], SOL_SOCKET, SO_ERROR, &code, &code_len);
+		assert(ret != -1);
 
 		if (!code)
 			break;
@@ -80,21 +89,23 @@
 
 	struct io_uring m_io_uring;
 
-	assert(io_uring_queue_init(32, &m_io_uring, 0) >= 0);
+	ret = io_uring_queue_init(32, &m_io_uring, 0);
+	assert(ret >= 0);
 
 	{
 		struct io_uring_cqe *cqe;
 		struct io_uring_sqe *sqe;
-		int ret;
+		int res;
 
 		sqe = io_uring_get_sqe(&m_io_uring);
 		io_uring_prep_shutdown(sqe, p_fd[1], SHUT_WR);
 		sqe->user_data = 1;
 
-		assert(io_uring_submit_and_wait(&m_io_uring, 1) != -1);
+		res = io_uring_submit_and_wait(&m_io_uring, 1);
+		assert(res != -1);
 
-		ret = io_uring_wait_cqe(&m_io_uring, &cqe);
-		if (ret < 0) {
+		res = io_uring_wait_cqe(&m_io_uring, &cqe);
+		if (res < 0) {
 			fprintf(stderr, "wait: %s\n", strerror(-ret));
 			goto err;
 		}
@@ -116,7 +127,7 @@
 		struct io_uring_sqe *sqe;
 		struct iovec iov[1];
 		char send_buff[128];
-		int ret;
+		int res;
 
 		iov[0].iov_base = send_buff;
 		iov[0].iov_len = sizeof(send_buff);
@@ -125,10 +136,11 @@
 		assert(sqe != NULL);
 
 		io_uring_prep_writev(sqe, p_fd[1], iov, 1, 0);
-		assert(io_uring_submit_and_wait(&m_io_uring, 1) != -1);
+		res = io_uring_submit_and_wait(&m_io_uring, 1);
+		assert(res != -1);
 
-		ret = io_uring_wait_cqe(&m_io_uring, &cqe);
-		if (ret < 0) {
+		res = io_uring_wait_cqe(&m_io_uring, &cqe);
+		if (res < 0) {
 			fprintf(stderr, "wait: %s\n", strerror(-ret));
 			goto err;
 		}
diff --git a/test/socket-rw-eagain.c b/test/socket-rw-eagain.c
index a6782cc..e08f984 100644
--- a/test/socket-rw-eagain.c
+++ b/test/socket-rw-eagain.c
@@ -20,7 +20,7 @@
 
 int main(int argc, char *argv[])
 {
-	int p_fd[2];
+	int p_fd[2], ret;
 	int32_t recv_s0;
 	int32_t val = 1;
 	struct sockaddr_in addr;
@@ -30,28 +30,35 @@
 
 	recv_s0 = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, IPPROTO_TCP);
 
-	assert(setsockopt(recv_s0, SOL_SOCKET, SO_REUSEPORT, &val, sizeof(val)) != -1);
-	assert(setsockopt(recv_s0, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) != -1);
+	ret = setsockopt(recv_s0, SOL_SOCKET, SO_REUSEPORT, &val, sizeof(val));
+	assert(ret != -1);
+	ret = setsockopt(recv_s0, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
+	assert(ret != -1);
 
 	addr.sin_family = AF_INET;
 	addr.sin_port = 0x1235;
 	addr.sin_addr.s_addr = 0x0100007fU;
 
-	assert(bind(recv_s0, (struct sockaddr*)&addr, sizeof(addr)) != -1);
-	assert(listen(recv_s0, 128) != -1);
+	ret = bind(recv_s0, (struct sockaddr*)&addr, sizeof(addr));
+	assert(ret != -1);
+	ret = listen(recv_s0, 128);
+	assert(ret != -1);
 
 	p_fd[1] = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, IPPROTO_TCP);
 
 	val = 1;
-	assert(setsockopt(p_fd[1], IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val)) != -1);
+	ret = setsockopt(p_fd[1], IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val));
+	assert(ret != -1);
 
 	int32_t flags = fcntl(p_fd[1], F_GETFL, 0);
 	assert(flags != -1);
 
 	flags |= O_NONBLOCK;
-	assert(fcntl(p_fd[1], F_SETFL, flags) != -1);
+	ret = fcntl(p_fd[1], F_SETFL, flags);
+	assert(ret != -1);
 
-	assert(connect(p_fd[1], (struct sockaddr*)&addr, sizeof(addr)) == -1);
+	ret = connect(p_fd[1], (struct sockaddr*)&addr, sizeof(addr));
+	assert(ret == -1);
 
 	p_fd[0] = accept(recv_s0, NULL, NULL);
 	assert(p_fd[0] != -1);
@@ -60,13 +67,15 @@
 	assert(flags != -1);
 
 	flags |= O_NONBLOCK;
-	assert(fcntl(p_fd[0], F_SETFL, flags) != -1);
+        ret = fcntl(p_fd[0], F_SETFL, flags);
+	assert(ret != -1);
 
 	while (1) {
 		int32_t code;
 		socklen_t code_len = sizeof(code);
 
-		assert(getsockopt(p_fd[1], SOL_SOCKET, SO_ERROR, &code, &code_len) != -1);
+		ret = getsockopt(p_fd[1], SOL_SOCKET, SO_ERROR, &code, &code_len);
+		assert(ret != -1);
 
 		if (!code)
 			break;
@@ -74,7 +83,8 @@
 
 	struct io_uring m_io_uring;
 
-	assert(io_uring_queue_init(32, &m_io_uring, 0) >= 0);
+	ret = io_uring_queue_init(32, &m_io_uring, 0);
+	assert(ret >= 0);
 
 	char recv_buff[128];
 	char send_buff[128];
@@ -105,7 +115,8 @@
 		sqe->user_data = 2;
 	}
 
-	assert(io_uring_submit_and_wait(&m_io_uring, 2) != -1);
+	ret = io_uring_submit_and_wait(&m_io_uring, 2);
+	assert(ret != -1);
 
 	struct io_uring_cqe* cqe;
 	uint32_t head;
diff --git a/test/socket-rw.c b/test/socket-rw.c
index 45daf57..77fae59 100644
--- a/test/socket-rw.c
+++ b/test/socket-rw.c
@@ -22,7 +22,7 @@
 
 int main(int argc, char *argv[])
 {
-	int p_fd[2];
+	int p_fd[2], ret;
 	int32_t recv_s0;
 	int32_t val = 1;
 	struct sockaddr_in addr;
@@ -32,35 +32,43 @@
 
 	recv_s0 = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, IPPROTO_TCP);
 
-	assert(setsockopt(recv_s0, SOL_SOCKET, SO_REUSEPORT, &val, sizeof(val)) != -1);
-	assert(setsockopt(recv_s0, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) != -1);
+	ret = setsockopt(recv_s0, SOL_SOCKET, SO_REUSEPORT, &val, sizeof(val));
+	assert(ret != -1);
+	ret = setsockopt(recv_s0, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
+	assert(ret != -1);
 
 	addr.sin_family = AF_INET;
 	addr.sin_port = 0x1235;
 	addr.sin_addr.s_addr = 0x0100007fU;
 
-	assert(bind(recv_s0, (struct sockaddr*)&addr, sizeof(addr)) != -1);
-	assert(listen(recv_s0, 128) != -1);
+	ret = bind(recv_s0, (struct sockaddr*)&addr, sizeof(addr));
+	assert(ret != -1);
+	ret = listen(recv_s0, 128);
+	assert(ret != -1);
 
 
 	p_fd[1] = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, IPPROTO_TCP);
 
 	val = 1;
-	assert(setsockopt(p_fd[1], IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val)) != -1);
+	ret = setsockopt(p_fd[1], IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val));
+	assert(ret != -1);
 
 	int32_t flags = fcntl(p_fd[1], F_GETFL, 0);
 	assert(flags != -1);
 
 	flags |= O_NONBLOCK;
-	assert(fcntl(p_fd[1], F_SETFL, flags) != -1);
+	ret = fcntl(p_fd[1], F_SETFL, flags);
+	assert(ret != -1);
 
-	assert(connect(p_fd[1], (struct sockaddr*)&addr, sizeof(addr)) == -1);
+	ret = connect(p_fd[1], (struct sockaddr*)&addr, sizeof(addr));
+	assert(ret == -1);
 
 	flags = fcntl(p_fd[1], F_GETFL, 0);
 	assert(flags != -1);
 
 	flags &= ~O_NONBLOCK;
-	assert(fcntl(p_fd[1], F_SETFL, flags) != -1);
+	ret = fcntl(p_fd[1], F_SETFL, flags);
+	assert(ret != -1);
 
 	p_fd[0] = accept(recv_s0, NULL, NULL);
 	assert(p_fd[0] != -1);
@@ -69,7 +77,8 @@
 		int32_t code;
 		socklen_t code_len = sizeof(code);
 
-		assert(getsockopt(p_fd[1], SOL_SOCKET, SO_ERROR, &code, &code_len) != -1);
+		ret = getsockopt(p_fd[1], SOL_SOCKET, SO_ERROR, &code, &code_len);
+		assert(ret != -1);
 
 		if (!code)
 			break;
@@ -77,7 +86,8 @@
 
 	struct io_uring m_io_uring;
 
-	assert(io_uring_queue_init(32, &m_io_uring, 0) >= 0);
+	ret = io_uring_queue_init(32, &m_io_uring, 0);
+	assert(ret >= 0);
 
 	char recv_buff[128];
 	char send_buff[128];
@@ -106,7 +116,8 @@
 		io_uring_prep_writev(sqe, p_fd[1], iov, 1, 0);
 	}
 
-	assert(io_uring_submit_and_wait(&m_io_uring, 2) != -1);
+	ret = io_uring_submit_and_wait(&m_io_uring, 2);
+	assert(ret != -1);
 
 	struct io_uring_cqe* cqe;
 	uint32_t head;
diff --git a/test/splice.c b/test/splice.c
index e67bb10..6442caf 100644
--- a/test/splice.c
+++ b/test/splice.c
@@ -446,6 +446,7 @@
 int main(int argc, char *argv[])
 {
 	struct io_uring ring;
+	struct io_uring_params p = { };
 	struct test_ctx ctx;
 	int ret;
 	int reg_fds[6];
@@ -453,11 +454,15 @@
 	if (argc > 1)
 		return 0;
 
-	ret = io_uring_queue_init(8, &ring, 0);
+	ret = io_uring_queue_init_params(8, &ring, &p);
 	if (ret) {
 		fprintf(stderr, "ring setup failed\n");
 		return 1;
 	}
+	if (!(p.features & IORING_FEAT_FAST_POLL)) {
+		fprintf(stdout, "No splice support, skipping\n");
+		return 0;
+	}
 
 	ret = init_splice_ctx(&ctx);
 	if (ret) {
diff --git a/test/sq-poll-share.c b/test/sq-poll-share.c
index 0f25389..02b008e 100644
--- a/test/sq-poll-share.c
+++ b/test/sq-poll-share.c
@@ -60,7 +60,14 @@
 	struct io_uring_cqe *cqe;
 
 	while (nr_ios) {
-		io_uring_wait_cqe(ring, &cqe);
+		int ret = io_uring_wait_cqe(ring, &cqe);
+
+		if (ret == -EAGAIN) {
+			continue;
+		} else if (ret) {
+			fprintf(stderr, "io_uring_wait_cqe failed %i\n", ret);
+			return 1;
+		}
 		if (cqe->res != BS) {
 			fprintf(stderr, "Unexpected ret %d\n", cqe->res);
 			return 1;
diff --git a/test/sqpoll-exit-hang.c b/test/sqpoll-exit-hang.c
new file mode 100644
index 0000000..43385ce
--- /dev/null
+++ b/test/sqpoll-exit-hang.c
@@ -0,0 +1,77 @@
+/*
+ * Test that we exit properly with SQPOLL and having a request that
+ * adds a circular reference to the ring itself.
+ */
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <sys/poll.h>
+#include "liburing.h"
+
+static unsigned long long mtime_since(const struct timeval *s,
+				      const struct timeval *e)
+{
+	long long sec, usec;
+
+	sec = e->tv_sec - s->tv_sec;
+	usec = (e->tv_usec - s->tv_usec);
+	if (sec > 0 && usec < 0) {
+		sec--;
+		usec += 1000000;
+	}
+
+	sec *= 1000;
+	usec /= 1000;
+	return sec + usec;
+}
+
+static unsigned long long mtime_since_now(struct timeval *tv)
+{
+	struct timeval end;
+
+	gettimeofday(&end, NULL);
+	return mtime_since(tv, &end);
+}
+
+int main(int argc, char *argv[])
+{
+	struct io_uring_params p = {};
+	struct timeval tv;
+	struct io_uring ring;
+	struct io_uring_sqe *sqe;
+	int ret;
+
+	if (argc > 1)
+		return 0;
+
+	p.flags = IORING_SETUP_SQPOLL;
+	p.sq_thread_idle = 100;
+
+	ret = io_uring_queue_init_params(1, &ring, &p);
+	if (ret) {
+		if (geteuid()) {
+			printf("%s: skipped, not root\n", argv[0]);
+			return 0;
+		}
+		fprintf(stderr, "queue_init=%d\n", ret);
+		return 1;
+	}
+
+	if (!(p.features & IORING_FEAT_SQPOLL_NONFIXED)) {
+		fprintf(stdout, "Skipping\n");
+		return 0;
+	}
+
+	sqe = io_uring_get_sqe(&ring);
+	io_uring_prep_poll_add(sqe, ring.ring_fd, POLLIN);
+	io_uring_submit(&ring);
+
+	gettimeofday(&tv, NULL);
+	do {
+		usleep(1000);
+	} while (mtime_since_now(&tv) < 1000);
+
+	return 0;
+}
diff --git a/test/sqpoll-sleep.c b/test/sqpoll-sleep.c
new file mode 100644
index 0000000..7ffd0e5
--- /dev/null
+++ b/test/sqpoll-sleep.c
@@ -0,0 +1,68 @@
+/*
+ * Test that the sqthread goes to sleep around the specified time, and that
+ * the NEED_WAKEUP flag is then set.
+ */
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include "liburing.h"
+
+static unsigned long long mtime_since(const struct timeval *s,
+				      const struct timeval *e)
+{
+	long long sec, usec;
+
+	sec = e->tv_sec - s->tv_sec;
+	usec = (e->tv_usec - s->tv_usec);
+	if (sec > 0 && usec < 0) {
+		sec--;
+		usec += 1000000;
+	}
+
+	sec *= 1000;
+	usec /= 1000;
+	return sec + usec;
+}
+
+static unsigned long long mtime_since_now(struct timeval *tv)
+{
+	struct timeval end;
+
+	gettimeofday(&end, NULL);
+	return mtime_since(tv, &end);
+}
+
+int main(int argc, char *argv[])
+{
+	struct io_uring_params p = {};
+	struct timeval tv;
+	struct io_uring ring;
+	int ret;
+
+	if (argc > 1)
+		return 0;
+
+	p.flags = IORING_SETUP_SQPOLL;
+	p.sq_thread_idle = 100;
+
+	ret = io_uring_queue_init_params(1, &ring, &p);
+	if (ret) {
+		if (geteuid()) {
+			printf("%s: skipped, not root\n", argv[0]);
+			return 0;
+		}
+		fprintf(stderr, "queue_init=%d\n", ret);
+		return 1;
+	}
+
+	gettimeofday(&tv, NULL);
+	do {
+		usleep(1000);
+		if ((*ring.sq.kflags) & IORING_SQ_NEED_WAKEUP)
+			return 0;
+	} while (mtime_since_now(&tv) < 1000);
+
+	return 1;
+}
diff --git a/test/submit-reuse.c b/test/submit-reuse.c
index 5491253..f14cc22 100644
--- a/test/submit-reuse.c
+++ b/test/submit-reuse.c
@@ -62,6 +62,8 @@
 
 static struct io_uring ring;
 
+static int no_stable;
+
 static int prep(int fd, char *str, int split, int async)
 {
 	struct io_uring_sqe *sqe;
@@ -149,6 +151,7 @@
 static int test_reuse(int argc, char *argv[], int split, int async)
 {
 	struct thread_data data;
+	struct io_uring_params p = { };
 	int fd1, fd2, ret, i;
 	struct timeval tv;
 	pthread_t thread;
@@ -161,12 +164,18 @@
 		do_unlink = 0;
 	}
 
-	ret = io_uring_queue_init(32, &ring, 0);
+	ret = io_uring_queue_init_params(32, &ring, &p);
 	if (ret) {
 		fprintf(stderr, "io_uring_queue_init: %d\n", ret);
 		return 1;
 	}
 
+	if (!(p.features & IORING_FEAT_SUBMIT_STABLE)) {
+		fprintf(stdout, "FEAT_SUBMIT_STABLE not there, skipping\n");
+		no_stable = 1;
+		return 0;
+	}
+
 	if (do_unlink && create_file(fname1)) {
 		fprintf(stderr, "file creation failed\n");
 		goto err;
@@ -177,7 +186,15 @@
 	}
 
 	fd1 = open(fname1, O_RDONLY);
+	if (fd1 < 0) {
+		perror("open fname1");
+		goto err;
+	}
 	fd2 = open(".reuse.2", O_RDONLY);
+	if (fd2 < 0) {
+		perror("open .reuse.2");
+		goto err;
+	}
 
 	data.fd1 = fd1;
 	data.fd2 = fd2;
@@ -240,6 +257,8 @@
 			fprintf(stderr, "test_reuse %d %d failed\n", split, async);
 			return ret;
 		}
+		if (no_stable)
+			break;
 	}
 
 	return 0;
diff --git a/test/thread-exit.c b/test/thread-exit.c
new file mode 100644
index 0000000..722edbc
--- /dev/null
+++ b/test/thread-exit.c
@@ -0,0 +1,156 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Description: test that thread pool issued requests don't cancel on thread
+ *		exit, but do get canceled once the parent exits. Do both
+ *		writes that finish and a poll request that sticks around.
+ *
+ */
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/poll.h>
+#include <pthread.h>
+
+#include "liburing.h"
+
+#define NR_IOS	8
+#define WSIZE	512
+
+static int create_file(const char *file, size_t size)
+{
+	ssize_t ret;
+	char *buf;
+	int fd;
+
+	buf = malloc(size);
+	memset(buf, 0xaa, size);
+
+	fd = open(file, O_WRONLY | O_CREAT, 0644);
+	if (fd < 0) {
+		perror("open file");
+		return 1;
+	}
+	ret = write(fd, buf, size);
+	close(fd);
+	free(buf);
+	return ret != size;
+}
+
+struct d {
+	int fd;
+	struct io_uring *ring;
+	unsigned long off;
+	int pipe_fd;
+	int err;
+};
+
+static void *do_io(void *data)
+{
+	struct d *d = data;
+	struct io_uring_sqe *sqe;
+	char *buffer;
+	int ret;
+
+	buffer = malloc(WSIZE);
+	memset(buffer, 0x5a, WSIZE);
+	sqe = io_uring_get_sqe(d->ring);
+	if (!sqe) {
+		d->err++;
+		return NULL;
+	}
+	io_uring_prep_write(sqe, d->fd, buffer, WSIZE, d->off);
+	sqe->user_data = d->off;
+
+	sqe = io_uring_get_sqe(d->ring);
+	if (!sqe) {
+		d->err++;
+		return NULL;
+	}
+	io_uring_prep_poll_add(sqe, d->pipe_fd, POLLIN);
+
+	ret = io_uring_submit(d->ring);
+	if (ret != 2)
+		d->err++;
+
+	free(buffer);
+	return NULL;
+}
+
+int main(int argc, char *argv[])
+{
+	struct io_uring ring;
+	const char *fname;
+	pthread_t thread;
+	int ret, do_unlink, i, fd;
+	struct d d;
+	int fds[2];
+
+	if (pipe(fds) < 0) {
+		perror("pipe");
+		return 1;
+	}
+
+	ret = io_uring_queue_init(32, &ring, 0);
+	if (ret) {
+		fprintf(stderr, "ring setup failed\n");
+		return 1;
+	}
+
+	if (argc > 1) {
+		fname = argv[1];
+		do_unlink = 0;
+	} else {
+		fname = ".thread.exit";
+		do_unlink = 1;
+	}
+
+	if (do_unlink && create_file(fname, 4096)) {
+		fprintf(stderr, "file create failed\n");
+		return 1;
+	}
+
+	fd = open(fname, O_WRONLY);
+	if (fd < 0) {
+		perror("open");
+		return 1;
+	}
+
+	d.fd = fd;
+	d.ring = &ring;
+	d.off = 0;
+	d.pipe_fd = fds[0];
+	d.err = 0;
+	for (i = 0; i < NR_IOS; i++) {
+		memset(&thread, 0, sizeof(thread));
+		pthread_create(&thread, NULL, do_io, &d);
+		pthread_join(thread, NULL);
+		d.off += WSIZE;
+	}
+
+	for (i = 0; i < NR_IOS; i++) {
+		struct io_uring_cqe *cqe;
+
+		ret = io_uring_wait_cqe(&ring, &cqe);
+		if (ret) {
+			fprintf(stderr, "io_uring_wait_cqe=%d\n", ret);
+			goto err;
+		}
+		if (cqe->res != WSIZE) {
+			fprintf(stderr, "cqe->res=%d, Expected %d\n", cqe->res,
+								WSIZE);
+			goto err;
+		}
+		io_uring_cqe_seen(&ring, cqe);
+	}
+
+	if (do_unlink)
+		unlink(fname);
+	return d.err;
+err:
+	if (do_unlink)
+		unlink(fname);
+	return 1;
+}
diff --git a/test/timeout-new.c b/test/timeout-new.c
new file mode 100644
index 0000000..45b9a14
--- /dev/null
+++ b/test/timeout-new.c
@@ -0,0 +1,246 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Description: tests for getevents timeout
+ *
+ */
+#include <stdio.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <pthread.h>
+#include "liburing.h"
+
+#define TIMEOUT_MSEC	200
+#define TIMEOUT_SEC	10
+
+int thread_ret0, thread_ret1;
+int cnt = 0;
+pthread_mutex_t mutex;
+
+static void msec_to_ts(struct __kernel_timespec *ts, unsigned int msec)
+{
+	ts->tv_sec = msec / 1000;
+	ts->tv_nsec = (msec % 1000) * 1000000;
+}
+
+static unsigned long long mtime_since(const struct timeval *s,
+				      const struct timeval *e)
+{
+	long long sec, usec;
+
+	sec = e->tv_sec - s->tv_sec;
+	usec = (e->tv_usec - s->tv_usec);
+	if (sec > 0 && usec < 0) {
+		sec--;
+		usec += 1000000;
+	}
+
+	sec *= 1000;
+	usec /= 1000;
+	return sec + usec;
+}
+
+static unsigned long long mtime_since_now(struct timeval *tv)
+{
+	struct timeval end;
+
+	gettimeofday(&end, NULL);
+	return mtime_since(tv, &end);
+}
+
+
+static int test_return_before_timeout(struct io_uring *ring)
+{
+	struct io_uring_cqe *cqe;
+	struct io_uring_sqe *sqe;
+	int ret;
+	struct __kernel_timespec ts;
+
+	sqe = io_uring_get_sqe(ring);
+	if (!sqe) {
+		fprintf(stderr, "%s: get sqe failed\n", __FUNCTION__);
+		return 1;
+	}
+
+	io_uring_prep_nop(sqe);
+
+	ret = io_uring_submit(ring);
+	if (ret <= 0) {
+		fprintf(stderr, "%s: sqe submit failed: %d\n", __FUNCTION__, ret);
+		return 1;
+	}
+
+	msec_to_ts(&ts, TIMEOUT_MSEC);
+	ret = io_uring_wait_cqe_timeout(ring, &cqe, &ts);
+	if (ret < 0) {
+		fprintf(stderr, "%s: timeout error: %d\n", __FUNCTION__, ret);
+		return 1;
+	}
+
+	io_uring_cqe_seen(ring, cqe);
+	return 0;
+}
+
+static int test_return_after_timeout(struct io_uring *ring)
+{
+	struct io_uring_cqe *cqe;
+	int ret;
+	struct __kernel_timespec ts;
+	struct timeval tv;
+	unsigned long long exp;
+
+	msec_to_ts(&ts, TIMEOUT_MSEC);
+	gettimeofday(&tv, NULL);
+	ret = io_uring_wait_cqe_timeout(ring, &cqe, &ts);
+	exp = mtime_since_now(&tv);
+	if (ret != -ETIME) {
+		fprintf(stderr, "%s: timeout error: %d\n", __FUNCTION__, ret);
+		return 1;
+	}
+
+	if (exp < TIMEOUT_MSEC / 2 || exp > (TIMEOUT_MSEC  * 3) / 2) {
+		fprintf(stderr, "%s: Timeout seems wonky (got %llu)\n", __FUNCTION__, exp);
+		return 1;
+	}
+
+	return 0;
+}
+
+int __reap_thread_fn(void *data) {
+	struct io_uring *ring = (struct io_uring *)data;
+	struct io_uring_cqe *cqe;
+	struct __kernel_timespec ts;
+
+	msec_to_ts(&ts, TIMEOUT_SEC);
+	pthread_mutex_lock(&mutex);
+	cnt++;
+	pthread_mutex_unlock(&mutex);
+	return io_uring_wait_cqe_timeout(ring, &cqe, &ts);
+}
+
+void *reap_thread_fn0(void *data) {
+	thread_ret0 = __reap_thread_fn(data);
+	return NULL;
+}
+
+void *reap_thread_fn1(void *data) {
+	thread_ret1 = __reap_thread_fn(data);
+	return NULL;
+}
+
+/*
+ * This is to test issuing a sqe in main thread and reaping it in two child-thread
+ * at the same time. To see if timeout feature works or not.
+ */
+int test_multi_threads_timeout() {
+	struct io_uring ring;
+	int ret;
+	bool both_wait = false;
+	pthread_t reap_thread0, reap_thread1;
+	struct io_uring_sqe *sqe;
+
+	ret = io_uring_queue_init(8, &ring, 0);
+	if (ret) {
+		fprintf(stderr, "%s: ring setup failed: %d\n", __FUNCTION__, ret);
+		return 1;
+	}
+
+	pthread_create(&reap_thread0, NULL, reap_thread_fn0, &ring);
+	pthread_create(&reap_thread1, NULL, reap_thread_fn1, &ring);
+
+	/*
+	 * make two threads both enter io_uring_wait_cqe_timeout() before issuing the sqe
+	 * as possible as we can. So that there are two threads in the ctx->wait queue.
+	 * In this way, we can test if a cqe wakes up two threads at the same time.
+	 */
+	while(!both_wait) {
+		pthread_mutex_lock(&mutex);
+		if (cnt == 2)
+			both_wait = true;
+		pthread_mutex_unlock(&mutex);
+		sleep(1);
+	}
+
+	sqe = io_uring_get_sqe(&ring);
+	if (!sqe) {
+		fprintf(stderr, "%s: get sqe failed\n", __FUNCTION__);
+		goto err;
+	}
+
+	io_uring_prep_nop(sqe);
+
+	ret = io_uring_submit(&ring);
+	if (ret <= 0) {
+		fprintf(stderr, "%s: sqe submit failed: %d\n", __FUNCTION__, ret);
+		goto err;
+	}
+
+	pthread_join(reap_thread0, NULL);
+	pthread_join(reap_thread1, NULL);
+
+	if ((thread_ret0 && thread_ret0 != -ETIME) || (thread_ret1 && thread_ret1 != -ETIME)) {
+		fprintf(stderr, "%s: thread wait cqe timeout failed: %d %d\n",
+				__FUNCTION__, thread_ret0, thread_ret1);
+		goto err;
+	}
+
+	return 0;
+err:
+	return 1;
+}
+
+int main(int argc, char *argv[])
+{
+	struct io_uring ring_normal, ring_sq;
+	int ret;
+
+	if (argc > 1)
+		return 0;
+
+	ret = io_uring_queue_init(8, &ring_normal, 0);
+	if (ret) {
+		fprintf(stderr, "ring_normal setup failed: %d\n", ret);
+		return 1;
+	}
+	if (!(ring_normal.features & IORING_FEAT_EXT_ARG)) {
+		fprintf(stderr, "feature IORING_FEAT_EXT_ARG not supported.\n");
+		return 1;
+	}
+
+	ret = test_return_before_timeout(&ring_normal);
+	if (ret) {
+		fprintf(stderr, "ring_normal: test_return_before_timeout failed\n");
+		return ret;
+	}
+
+	ret = test_return_after_timeout(&ring_normal);
+	if (ret) {
+		fprintf(stderr, "ring_normal: test_return_after_timeout failed\n");
+		return ret;
+	}
+
+	ret = io_uring_queue_init(8, &ring_sq, IORING_SETUP_SQPOLL);
+	if (ret) {
+		fprintf(stderr, "ring_sq setup failed: %d\n", ret);
+		return 1;
+	}
+
+	ret = test_return_before_timeout(&ring_sq);
+	if (ret) {
+		fprintf(stderr, "ring_sq: test_return_before_timeout failed\n");
+		return ret;
+	}
+
+	ret = test_return_after_timeout(&ring_sq);
+	if (ret) {
+		fprintf(stderr, "ring_sq: test_return_after_timeout failed\n");
+		return ret;
+	}
+
+	ret = test_multi_threads_timeout();
+	if (ret) {
+		fprintf(stderr, "test_multi_threads_timeout failed\n");
+		return ret;
+	}
+
+	return 0;
+}
diff --git a/test/timeout.c b/test/timeout.c
index 7e9f11d..a28d599 100644
--- a/test/timeout.c
+++ b/test/timeout.c
@@ -112,7 +112,7 @@
 /*
  * Test numbered trigger of timeout
  */
-static int test_single_timeout_nr(struct io_uring *ring)
+static int test_single_timeout_nr(struct io_uring *ring, int nr)
 {
 	struct io_uring_cqe *cqe;
 	struct io_uring_sqe *sqe;
@@ -126,7 +126,7 @@
 	}
 
 	msec_to_ts(&ts, TIMEOUT_MSEC);
-	io_uring_prep_timeout(sqe, &ts, 2, 0);
+	io_uring_prep_timeout(sqe, &ts, nr, 0);
 
 	sqe = io_uring_get_sqe(ring);
 	io_uring_prep_nop(sqe);
@@ -149,33 +149,26 @@
 			goto err;
 		}
 
+		ret = cqe->res;
+
 		/*
 		 * NOP commands have user_data as 1. Check that we get the
-		 * two NOPs first, then the successfully removed timout as
-		 * the last one.
+		 * at least 'nr' NOPs first, then the successfully removed timout.
 		 */
-		switch (i) {
-		case 0:
-		case 1:
-			if (io_uring_cqe_get_data(cqe) != (void *) 1) {
-				fprintf(stderr, "%s: nop not seen as 1 or 2\n", __FUNCTION__);
+		if (io_uring_cqe_get_data(cqe) == NULL) {
+			if (i < nr) {
+				fprintf(stderr, "%s: timeout received too early\n", __FUNCTION__);
 				goto err;
 			}
-			break;
-		case 2:
-			if (io_uring_cqe_get_data(cqe) != NULL) {
-				fprintf(stderr, "%s: timeout not last\n", __FUNCTION__);
+			if (ret) {
+				fprintf(stderr, "%s: timeout triggered by passage of"
+					" time, not by events completed\n", __FUNCTION__);
 				goto err;
 			}
-			break;
 		}
 
-		ret = cqe->res;
 		io_uring_cqe_seen(ring, cqe);
-		if (ret < 0) {
-			fprintf(stderr, "Timeout: %s\n", strerror(-ret));
-			goto err;
-		} else if (ret) {
+		if (ret) {
 			fprintf(stderr, "res: %d\n", ret);
 			goto err;
 		}
@@ -965,10 +958,213 @@
 	return 1;
 }
 
+static int test_update_timeout(struct io_uring *ring, unsigned long ms,
+				bool abs, bool async, bool linked)
+{
+	struct io_uring_sqe *sqe;
+	struct io_uring_cqe *cqe;
+	struct __kernel_timespec ts, ts_upd;
+	unsigned long long exp_ms, base_ms = 10000;
+	struct timeval tv;
+	int ret, i, nr = 2;
+	__u32 mode = abs ? IORING_TIMEOUT_ABS : 0;
+
+	msec_to_ts(&ts_upd, ms);
+	gettimeofday(&tv, NULL);
+
+	sqe = io_uring_get_sqe(ring);
+	if (!sqe) {
+		fprintf(stderr, "%s: get sqe failed\n", __FUNCTION__);
+		goto err;
+	}
+	msec_to_ts(&ts, base_ms);
+	io_uring_prep_timeout(sqe, &ts, 0, 0);
+	sqe->user_data = 1;
+
+	if (linked) {
+		sqe = io_uring_get_sqe(ring);
+		if (!sqe) {
+			fprintf(stderr, "%s: get sqe failed\n", __FUNCTION__);
+			goto err;
+		}
+		io_uring_prep_nop(sqe);
+		sqe->user_data = 3;
+		sqe->flags = IOSQE_IO_LINK;
+		if (async)
+			sqe->flags |= IOSQE_ASYNC;
+		nr++;
+	}
+
+	sqe = io_uring_get_sqe(ring);
+	if (!sqe) {
+		fprintf(stderr, "%s: get sqe failed\n", __FUNCTION__);
+		goto err;
+	}
+	io_uring_prep_timeout_update(sqe, &ts_upd, 1, mode);
+	sqe->user_data = 2;
+	if (async)
+		sqe->flags |= IOSQE_ASYNC;
+
+	ret = io_uring_submit(ring);
+	if (ret != nr) {
+		fprintf(stderr, "%s: sqe submit failed: %d\n", __FUNCTION__, ret);
+		goto err;
+	}
+
+	for (i = 0; i < nr; i++) {
+		ret = io_uring_wait_cqe(ring, &cqe);
+		if (ret < 0) {
+			fprintf(stderr, "%s: wait completion %d\n", __FUNCTION__, ret);
+			goto err;
+		}
+
+		switch (cqe->user_data) {
+		case 1:
+			if (cqe->res != -ETIME) {
+				fprintf(stderr, "%s: got %d, wanted %d\n",
+						__FUNCTION__, cqe->res, -ETIME);
+				goto err;
+			}
+			break;
+		case 2:
+			if (cqe->res != 0) {
+				fprintf(stderr, "%s: got %d, wanted %d\n",
+						__FUNCTION__, cqe->res,
+						0);
+				goto err;
+			}
+			break;
+		case 3:
+			if (cqe->res != 0) {
+				fprintf(stderr, "nop failed\n");
+				goto err;
+			}
+			break;
+		default:
+			goto err;
+		}
+		io_uring_cqe_seen(ring, cqe);
+	}
+
+	exp_ms = mtime_since_now(&tv);
+	if (exp_ms >= base_ms / 2) {
+		fprintf(stderr, "too long, timeout wasn't updated\n");
+		goto err;
+	}
+	if (ms >= 1000 && !abs && exp_ms < ms / 2) {
+		fprintf(stderr, "fired too early, potentially updated to 0 ms"
+					"instead of %lu\n", ms);
+		goto err;
+	}
+	return 0;
+err:
+	return 1;
+}
+
+static int test_update_nonexistent_timeout(struct io_uring *ring)
+{
+	struct io_uring_sqe *sqe;
+	struct io_uring_cqe *cqe;
+	struct __kernel_timespec ts;
+	int ret;
+
+	sqe = io_uring_get_sqe(ring);
+	if (!sqe) {
+		fprintf(stderr, "%s: get sqe failed\n", __FUNCTION__);
+		goto err;
+	}
+	msec_to_ts(&ts, 0);
+	io_uring_prep_timeout_update(sqe, &ts, 42, 0);
+
+	ret = io_uring_submit(ring);
+	if (ret != 1) {
+		fprintf(stderr, "%s: sqe submit failed: %d\n", __FUNCTION__, ret);
+		goto err;
+	}
+
+	ret = io_uring_wait_cqe(ring, &cqe);
+	if (ret < 0) {
+		fprintf(stderr, "%s: wait completion %d\n", __FUNCTION__, ret);
+		goto err;
+	}
+
+	ret = cqe->res;
+	if (ret == -ENOENT)
+		ret = 0;
+	io_uring_cqe_seen(ring, cqe);
+	return ret;
+err:
+	return 1;
+}
+
+static int test_update_invalid_flags(struct io_uring *ring)
+{
+	struct io_uring_sqe *sqe;
+	struct io_uring_cqe *cqe;
+	struct __kernel_timespec ts;
+	int ret;
+
+	sqe = io_uring_get_sqe(ring);
+	if (!sqe) {
+		fprintf(stderr, "%s: get sqe failed\n", __FUNCTION__);
+		goto err;
+	}
+	io_uring_prep_timeout_remove(sqe, 0, IORING_TIMEOUT_ABS);
+
+	ret = io_uring_submit(ring);
+	if (ret != 1) {
+		fprintf(stderr, "%s: sqe submit failed: %d\n", __FUNCTION__, ret);
+		goto err;
+	}
+
+	ret = io_uring_wait_cqe(ring, &cqe);
+	if (ret < 0) {
+		fprintf(stderr, "%s: wait completion %d\n", __FUNCTION__, ret);
+		goto err;
+	}
+	if (cqe->res != -EINVAL) {
+		fprintf(stderr, "%s: got %d, wanted %d\n",
+				__FUNCTION__, cqe->res, -EINVAL);
+		goto err;
+	}
+	io_uring_cqe_seen(ring, cqe);
+
+
+	sqe = io_uring_get_sqe(ring);
+	if (!sqe) {
+		fprintf(stderr, "%s: get sqe failed\n", __FUNCTION__);
+		goto err;
+	}
+	msec_to_ts(&ts, 0);
+	io_uring_prep_timeout_update(sqe, &ts, 0, -1);
+
+	ret = io_uring_submit(ring);
+	if (ret != 1) {
+		fprintf(stderr, "%s: sqe submit failed: %d\n", __FUNCTION__, ret);
+		goto err;
+	}
+
+	ret = io_uring_wait_cqe(ring, &cqe);
+	if (ret < 0) {
+		fprintf(stderr, "%s: wait completion %d\n", __FUNCTION__, ret);
+		goto err;
+	}
+	if (cqe->res != -EINVAL) {
+		fprintf(stderr, "%s: got %d, wanted %d\n",
+				__FUNCTION__, cqe->res, -EINVAL);
+		goto err;
+	}
+	io_uring_cqe_seen(ring, cqe);
+
+	return 0;
+err:
+	return 1;
+}
 
 int main(int argc, char *argv[])
 {
-	struct io_uring ring;
+	struct io_uring ring, sqpoll_ring;
+	bool has_timeout_update, sqpoll;
 	int ret;
 
 	if (argc > 1)
@@ -980,6 +1176,9 @@
 		return 1;
 	}
 
+	ret = io_uring_queue_init(8, &sqpoll_ring, IORING_SETUP_SQPOLL);
+	sqpoll = !ret;
+
 	ret = test_single_timeout(&ring);
 	if (ret) {
 		fprintf(stderr, "test_single_timeout failed\n");
@@ -1018,9 +1217,14 @@
 		return ret;
 	}
 
-	ret = test_single_timeout_nr(&ring);
+	ret = test_single_timeout_nr(&ring, 1);
 	if (ret) {
-		fprintf(stderr, "test_single_timeout_nr failed\n");
+		fprintf(stderr, "test_single_timeout_nr(1) failed\n");
+		return ret;
+	}
+	ret = test_single_timeout_nr(&ring, 2);
+	if (ret) {
+		fprintf(stderr, "test_single_timeout_nr(2) failed\n");
 		return ret;
 	}
 
@@ -1054,6 +1258,76 @@
 		return ret;
 	}
 
+	/* io_uring_wait_cqes() may have left a timeout, reinit ring */
+	io_uring_queue_exit(&ring);
+	ret = io_uring_queue_init(8, &ring, 0);
+	if (ret) {
+		fprintf(stderr, "ring setup failed\n");
+		return 1;
+	}
+
+	ret = test_update_nonexistent_timeout(&ring);
+	has_timeout_update = (ret != -EINVAL);
+	if (has_timeout_update) {
+		if (ret) {
+			fprintf(stderr, "test_update_nonexistent_timeout failed\n");
+			return ret;
+		}
+
+		ret = test_update_invalid_flags(&ring);
+		if (ret) {
+			fprintf(stderr, "test_update_invalid_flags failed\n");
+			return ret;
+		}
+
+		ret = test_update_timeout(&ring, 0, false, false, false);
+		if (ret) {
+			fprintf(stderr, "test_update_timeout failed\n");
+			return ret;
+		}
+
+		ret = test_update_timeout(&ring, 1, false, false, false);
+		if (ret) {
+			fprintf(stderr, "test_update_timeout 1ms failed\n");
+			return ret;
+		}
+
+		ret = test_update_timeout(&ring, 1000, false, false, false);
+		if (ret) {
+			fprintf(stderr, "test_update_timeout 1s failed\n");
+			return ret;
+		}
+
+		ret = test_update_timeout(&ring, 0, true, true, false);
+		if (ret) {
+			fprintf(stderr, "test_update_timeout abs failed\n");
+			return ret;
+		}
+
+
+		ret = test_update_timeout(&ring, 0, false, true, false);
+		if (ret) {
+			fprintf(stderr, "test_update_timeout async failed\n");
+			return ret;
+		}
+
+		ret = test_update_timeout(&ring, 0, false, false, true);
+		if (ret) {
+			fprintf(stderr, "test_update_timeout linked failed\n");
+			return ret;
+		}
+
+		if (sqpoll) {
+			ret = test_update_timeout(&sqpoll_ring, 0, false, false,
+						  false);
+			if (ret) {
+				fprintf(stderr, "test_update_timeout sqpoll"
+						"failed\n");
+				return ret;
+			}
+		}
+	}
+
 	/*
 	 * this test must go last, it kills the ring
 	 */
@@ -1063,5 +1337,7 @@
 		return ret;
 	}
 
+	if (sqpoll)
+		io_uring_queue_exit(&sqpoll_ring);
 	return 0;
 }
diff --git a/test/unlink.c b/test/unlink.c
new file mode 100644
index 0000000..f8c7639
--- /dev/null
+++ b/test/unlink.c
@@ -0,0 +1,111 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Description: run various nop tests
+ *
+ */
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+
+#include "liburing.h"
+
+static int test_unlink(struct io_uring *ring, const char *old)
+{
+	struct io_uring_cqe *cqe;
+	struct io_uring_sqe *sqe;
+	int ret;
+
+	sqe = io_uring_get_sqe(ring);
+	if (!sqe) {
+		fprintf(stderr, "get sqe failed\n");
+		goto err;
+	}
+	io_uring_prep_unlinkat(sqe, AT_FDCWD, old, 0);
+	
+	ret = io_uring_submit(ring);
+	if (ret <= 0) {
+		fprintf(stderr, "sqe submit failed: %d\n", ret);
+		goto err;
+	}
+
+	ret = io_uring_wait_cqe(ring, &cqe);
+	if (ret < 0) {
+		fprintf(stderr, "wait completion %d\n", ret);
+		goto err;
+	}
+	ret = cqe->res;
+	io_uring_cqe_seen(ring, cqe);
+	return ret;
+err:
+	return 1;
+}
+
+static int stat_file(const char *buf)
+{
+	struct stat sb;
+
+	if (!stat(buf, &sb))
+		return 0;
+
+	return errno;
+}
+
+int main(int argc, char *argv[])
+{
+	struct io_uring ring;
+	char buf[32] = "./XXXXXX";
+	int ret;
+
+	if (argc > 1)
+		return 0;
+
+	ret = io_uring_queue_init(1, &ring, 0);
+	if (ret) {
+		fprintf(stderr, "ring setup failed: %d\n", ret);
+		return 1;
+	}
+
+	ret = mkstemp(buf);
+	if (ret < 0) {
+		perror("mkstemp");
+		return 1;
+	}
+	close(ret);
+
+	if (stat_file(buf) != 0) {
+		perror("stat");
+		return 1;
+	}
+
+	ret = test_unlink(&ring, buf);
+	if (ret < 0) {
+		if (ret == -EBADF || ret == -EINVAL) {
+			fprintf(stdout, "Unlink not supported, skipping\n");
+			unlink(buf);
+			return 0;
+		}
+		fprintf(stderr, "rename: %s\n", strerror(-ret));
+		goto err;
+	} else if (ret)
+		goto err;
+
+	ret = stat_file(buf);
+	if (ret != ENOENT) {
+		fprintf(stderr, "stat got %s\n", strerror(ret));
+		return 1;
+	}
+
+	ret = test_unlink(&ring, "/3/2/3/1/z/y");
+	if (ret != -ENOENT) {
+		fprintf(stderr, "invalid unlink got %s\n", strerror(-ret));
+		return 1;
+	}
+
+	return 0;
+err:
+	unlink(buf);
+	return 1;
+}