#!/usr/bin/env dub
/+ dub.sdl:
name "io_uring_direct_descriptors"
dependency "during" version="~>0.5.0"
platforms "linux"
targetPath "build"
+/
/**
* `io_uring` — direct (registered) descriptors + `FIXED_FD_INSTALL`
* (`IORING_OP_OPENAT` direct-open, Linux 5.15; `FIXED_FD_INSTALL`, Linux 6.8).
*
* Direct descriptors live in the ring's registered-files table instead of the
* process fd table: a direct `openat` deposits the kernel file object straight
* into a table slot and never allocates a userspace fd. Ops then address that
* slot *by index* with the `IOSQE_FIXED_FILE` flag, which skips the per-op fd
* lookup/refcount and removes the open-file from `/proc/<pid>/fd`. This example
* walks the full life cycle:
*
* 1. `registerFiles([-1])` reserves one sparse (empty) slot in the table.
* 2. `OPENAT_DIRECT` opens a temp file straight into slot 0 (no process fd).
* 3. A `READ` addresses slot 0 via index 0 + `FIXED_FILE` — proving the file
* is usable purely by table index.
* 4. `FIXED_FD_INSTALL` (6.8) materializes slot 0 back into a *real* process
* fd, returned in the CQE `res`; a plain libc `read()` through it confirms.
*
* Companion to the io_uring chronology:
* see docs/research/async-io/io-uring/timeline.md
* § "5.15 — More filesystem ops, worker caps (October 2021)" (direct descriptors)
* and § "6.8 — Fixed-fd install, pbuf status (March 2024)" (FIXED_FD_INSTALL).
*
* Run with: `dub run --single direct-descriptors.d`
*
* Portability: if `io_uring` is unavailable, registered files / direct open are
* unsupported, or `FIXED_FD_INSTALL` is missing (pre-6.8), the program prints a
* `SKIP:` line and exits 0 so it stays green in CI regardless of host kernel.
*/
module (module) io_uring_direct_descriptorsio_uring — direct (registered) descriptors + FIXED_FD_INSTALL
(IORING_OP_OPENAT direct-open, Linux 5.15; FIXED_FD_INSTALL, Linux 6.8).
Direct descriptors live in the ring's registered-files table instead of the
process fd table: a direct openat deposits the kernel file object straight
into a table slot and never allocates a userspace fd. Ops then address that
slot by index with the IOSQE_FIXED_FILE flag, which skips the per-op fd
lookup/refcount and removes the open-file from /proc/<pid>/fd. This example
walks the full life cycle:
registerFiles([-1]) reserves one sparse (empty) slot in the table.
OPENAT_DIRECT opens a temp file straight into slot 0 (no process fd).
A READ addresses slot 0 via index 0 + FIXED_FILE — proving the file
is usable purely by table index.
FIXED_FD_INSTALL (6.8) materializes slot 0 back into a real process
fd, returned in the CQE res; a plain libc read() through it confirms.
Companion to the io_uring chronology:
see docs/research/async-io/io-uring/timeline.md
§ "5.15 — More filesystem ops, worker caps (October 2021)" (direct descriptors)
and § "6.8 — Fixed-fd install, pbuf status (March 2024)" (FIXED_FD_INSTALL).
Run with: dub run --single direct-descriptors.d
Portability
if io_uring is unavailable, registered files / direct open are
unsupported, or FIXED_FD_INSTALL is missing (pre-6.8), the program prints a
SKIP: line and exits 0 so it stays green in CI regardless of host kernel.
io_uring_direct_descriptors;
import (module) duringSimple idiomatic dlang wrapper around linux io_uring
(see: https://kernel.dk/io_uring.pdf) asynchronous API.
during;
import (package) stdstd.(module) std.stdioCategory Symbols File handles _popen File isFileHandle openNetwork stderr stdin stdout Reading chunks lines readf readfln readln Writing toFile write writef writefln writeln Misc KeepTerminator LockType StdioException
Standard I/O functions that extend core.stdc.stdio. core.stdc.stdio
is publically imported when importing std.stdio.
There are three layers of I/O:
The lowest layer is the operating system layer. The two main schemes are Windows and Posix.
C's stdio.h which unifies the two operating system schemes.
std.stdio, this module, unifies the various stdio.h implementations into
a high level package for D programs.
Source
std/stdio.d
stdio : (alias template) io_uring_direct_descriptors.writefln = std.stdio.writefln(alias fmt, A...)(A args) if (isSomeString!(typeof(fmt)))Equivalent to writef(fmt, args, '\n').
writefln, stderr;
import (package) corecore.(package) core.syssys.(package) core.sys.linuxlinux.(module) core.sys.linux.errnoD header file for GNU/Linux
errno : (alias constant) io_uring_direct_descriptors.EINVAL = int core.stdc.errno.EINVAL = 22EINVAL, (alias constant) io_uring_direct_descriptors.EOPNOTSUPP = int core.stdc.errno.EOPNOTSUPP = 95EOPNOTSUPP, (alias constant) io_uring_direct_descriptors.ENOSYS = int core.stdc.errno.ENOSYS = 38ENOSYS, (alias constant) io_uring_direct_descriptors.EPERM = int core.stdc.errno.EPERM = 1EPERM;
import (package) corecore.(package) core.syssys.(package) core.sys.linuxlinux.(module) core.sys.linux.fcntlfcntl : (alias constant) io_uring_direct_descriptors.AT_FDCWD = int core.sys.posix.fcntl.AT_FDCWD = -100AT_FDCWD, (alias constant) io_uring_direct_descriptors.O_CREAT = int core.sys.posix.fcntl.O_CREAT = 64O_CREAT, (alias constant) io_uring_direct_descriptors.O_RDONLY = int core.sys.posix.fcntl.O_RDONLY = 0O_RDONLY, (alias constant) io_uring_direct_descriptors.O_WRONLY = int core.sys.posix.fcntl.O_WRONLY = 1O_WRONLY;
import (package) corecore.(package) core.syssys.(package) core.sys.posixposix.(module) core.sys.posix.unistdD header file for POSIX.
unistd : (alias) io_uring_direct_descriptors.close = int core.sys.posix.unistd.close(int) nothrow @nogc @trustedclose, (alias) io_uring_direct_descriptors.read = long core.sys.posix.unistd.read(int, void*, ulong) nothrow @nogcread, (alias) io_uring_direct_descriptors.unlink = int core.sys.posix.unistd.unlink(scope const(char*)) nothrow @nogcunlink, (alias) io_uring_direct_descriptors.write = long core.sys.posix.unistd.write(int, scope const(void*), ulong) nothrow @nogcwrite;
// The eight payload bytes we write to the temp file and expect to read back —
// twice: once through the registered slot, once through the installed real fd.
private static immutable ubyte[8] (immutable global) immutable(ubyte[8]) io_uring_direct_descriptors.payloadpayload = [10, 20, 30, 40, 50, 60, 70, 80];
// `true` for the transient "feature not available on this kernel" errnos, so a
// missing capability becomes a clean SKIP rather than a hard failure.
private bool bool io_uring_direct_descriptors.unsupported(int res) pure nothrow @nogc @safeunsupported(int (parameter) int resres) @safe pure nothrow @nogc
{
return (parameter) int resres == -(constant) int core.stdc.errno.EINVAL = 22EINVAL || (parameter) int resres == -(constant) int core.stdc.errno.EOPNOTSUPP = 95EOPNOTSUPP || (parameter) int resres == -(constant) int core.stdc.errno.ENOSYS = 38ENOSYS || (parameter) int resres == -(constant) int core.stdc.errno.EPERM = 1EPERM;
}
int int D main()main()
{
(struct) during.UringMain entry point to work with io_uring.
It hides SubmissionQueue and CompletionQueue behind standard range interface.
We put in SubmissionEntry entries and take out CompletionEntry entries.
Use predefined prepXX methods to fill required fields of SubmissionEntry before put or during putWith.
Note
prepXX functions doesn't touch previous entry state, just fills in operation properties. This is because for
less error prone interface it is cleared automatically when prepared using putWith. So when using on own SubmissionEntry
(outside submission queue), that would be added to the submission queue using put, be sure its cleared if it's
reused for multiple operations.
Uring (local variable) during.Uring ioio;
const (local variable) const(int) setupRetsetupRet = (local variable) during.Uring ioio.int during.setup(ref during.Uring uring, uint entries = 128u, during.io_uring.SetupFlags flags = SetupFlags.NONE) nothrow @nogc @safeSetup new instance of io_uring into provided Uring structure.
setup(8);
if ((local variable) const(int) setupRetsetupRet < 0)
{
void std.stdio.writefln!(char, const(int))(in char[] fmt, const(int) __param_1) @safeEquivalent to writef(fmt, args, '\n').
writefln("SKIP: io_uring_setup failed (errno %d) — io_uring unavailable on this host", -(local variable) const(int) setupRetsetupRet);
return 0;
}
// Reserve a single *sparse* slot: -1 means "empty", ready to receive a
// direct-opened file. The table must exist before OPENAT_DIRECT can target it.
int[1] (local variable) int[1] sparsesparse = -1;
const (local variable) const(int) regRetregRet = (local variable) during.Uring ioio.int during.Uring.registerFiles(const(int)[] fds) nothrow @nogcRegister files for I/O.
To make use of the registered files, the IOSQE_FIXED_FILE flag must be set in the flags
member of the SubmissionEntry, and the fd member is set to the index of the file in the
file descriptor array.
Files are automatically unregistered when the io_uring instance is torn down. An application
need only unregister if it wishes to register a new set of fds.
Use -1 as a file descriptor to mark it as reserved in the array.*
registerFiles((local variable) int[1] sparsesparse[]);
if ((local variable) const(int) regRetregRet != 0)
{
if (bool io_uring_direct_descriptors.unsupported(int res) pure nothrow @nogc @safeunsupported((local variable) const(int) regRetregRet))
{
void std.stdio.writefln!(char, const(int))(in char[] fmt, const(int) __param_1) @safeEquivalent to writef(fmt, args, '\n').
writefln("SKIP: registerFiles unsupported (errno %d)", -(local variable) const(int) regRetregRet);
return 0;
}
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("registerFiles failed: errno %d", -(local variable) const(int) regRetregRet);
return 1;
}
scope (exit) (local variable) during.Uring ioio.int during.Uring.unregisterFiles() nothrow @nogc @trustedAll previously registered files associated with the io_uring instance will be unregistered.
Files are automatically unregistered when the io_uring instance is torn down. An application
need only unregister if it wishes to register a new set of fds.
unregisterFiles();
// Create a temp file with the known payload using ordinary libc I/O, then
// close it — io_uring will reopen it directly into the table.
enum (alias) object.string = stringstring (constant) string io_uring_direct_descriptors.main.path = "/tmp/io_uring_direct_descriptors_demo\0"path = "/tmp/io_uring_direct_descriptors_demo\0";
{
const (local variable) const(int) fdfd = int io_uring_direct_descriptors.openFile(const(char)* path) nothrow @nogc @trustedopenFile((constant) string io_uring_direct_descriptors.main.path = "/tmp/io_uring_direct_descriptors_demo\0"path.(constant) immutable(char)* "/tmp/io_uring_direct_descriptors_demo\0".ptr = "/tmp/io_uring_direct_descriptors_demo\0"ptr);
if ((local variable) const(int) fdfd < 0)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("could not create temp file");
return 1;
}
const (local variable) const(long) ww = long core.sys.posix.unistd.write(int, scope const(void*), ulong) nothrow @nogcwrite((local variable) const(int) fdfd, &(immutable global) immutable(ubyte[8]) io_uring_direct_descriptors.payloadpayload[0], (immutable global) immutable(ubyte[8]) io_uring_direct_descriptors.payloadpayload.(constant) ulong immutable(ubyte[8]).length = 8LUlength);
int core.sys.posix.unistd.close(int) nothrow @nogc @trustedclose((local variable) const(int) fdfd);
if ((local variable) const(long) ww != (immutable global) immutable(ubyte[8]) io_uring_direct_descriptors.payloadpayload.(constant) ulong immutable(ubyte[8]).length = 8LUlength)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("short write to temp file (%d)", (local variable) const(long) ww);
return 1;
}
}
scope (exit) int core.sys.posix.unistd.unlink(scope const(char*)) nothrow @nogcunlink((constant) string io_uring_direct_descriptors.main.path = "/tmp/io_uring_direct_descriptors_demo\0"path.(constant) immutable(char)* "/tmp/io_uring_direct_descriptors_demo\0".ptr = "/tmp/io_uring_direct_descriptors_demo\0"ptr);
// --- Step 1: OPENAT_DIRECT — open straight into registered slot 0. ---------
// No process fd is allocated; the file object lands in the files table only.
(local variable) during.Uring ioio.putWith!((ref SubmissionEntry e, const(char)* p) {
e.prepOpenatDirect(AT_FDCWD, p, O_RDONLY, 0, /*fileIndex=*/ 0);
e.user_data = 1;
})(during.Uring during.Uring.putWith!(function (ref during.io_uring.SubmissionEntry e, const(char)* p) nothrow @nogc @system
{
prepOpenatDirect(e, -100, p, 0, 0u, 0u);
e.user_data = 1LU;
}
, immutable(char)*)(immutable(char)* __param_0) nothrow @nogc return ref @safeAdds new entry to the SubmissionQueue.
Note that this just adds entry to the queue and doesn't advance the tail
marker kernel sees. For that finishSq() is needed to be called next.
Also note that to actually enter new entries to kernel,
it's needed to call submit().
path.during.Uring during.Uring.putWith!(function (ref during.io_uring.SubmissionEntry e, const(char)* p) nothrow @nogc @system
{
prepOpenatDirect(e, -100, p, 0, 0u, 0u);
e.user_data = 1LU;
}
, immutable(char)*)(immutable(char)* __param_0) nothrow @nogc return ref @safeAdds new entry to the SubmissionQueue.
Note that this just adds entry to the queue and doesn't advance the tail
marker kernel sees. For that finishSq() is needed to be called next.
Also note that to actually enter new entries to kernel,
it's needed to call submit().
ptr);
if ((local variable) during.Uring ioio.int during.Uring.submit(uint want) nothrow @nogc @safeSubmits qued SubmissionEntry to be processed by kernel.
submit(1) != 1) { stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("submit (open) failed"); return 1; }
(local variable) during.Uring ioio.int during.Uring.wait(uint want = 1u) nothrow @nogcSimmilar to submit but with this method we just wait for required number
of CompletionEntries.
wait(1);
{
const (local variable) const(int) resres = (local variable) during.Uring ioio.during.io_uring.CompletionEntry during.Uring.front() pure nothrow @nogc return ref @safeGet first CompletionEntry from cq ring
front.(field) int during.io_uring.CompletionEntry.resresult code for this event
res;
(local variable) during.Uring ioio.void during.Uring.popFront() pure nothrow @nogc @safeMove to next CompletionEntry
popFront();
if (bool io_uring_direct_descriptors.unsupported(int res) pure nothrow @nogc @safeunsupported((local variable) const(int) resres))
{
void std.stdio.writefln!(char, const(int))(in char[] fmt, const(int) __param_1) @safeEquivalent to writef(fmt, args, '\n').
writefln("SKIP: OPENAT_DIRECT unsupported (errno %d)", -(local variable) const(int) resres);
return 0;
}
if ((local variable) const(int) resres != 0)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("OPENAT_DIRECT failed: errno %d", -(local variable) const(int) resres);
return 1;
}
}
// --- Step 2: READ addressing slot 0 by index, with the FIXED_FILE flag. ----
// The "fd" we pass is the *table index* (0); FIXED_FILE tells the kernel to
// resolve it through the registered-files table instead of the process table.
ubyte[8] (local variable) ubyte[8] viaIndexviaIndex;
(local variable) during.Uring ioio.putWith!((ref SubmissionEntry e, ubyte[] buf) {
e.prepRead(/*index=*/ 0, buf, /*offset=*/ 0);
e.flags |= SubmissionEntryFlags.FIXED_FILE;
e.user_data = 2;
})(during.Uring during.Uring.putWith!(function (ref during.io_uring.SubmissionEntry e, ubyte[] buf) nothrow @nogc @safe
{
prepRead(e, 0, buf, 0L);
cast(int)e.flags |= 1;
e.user_data = 2LU;
}
, ubyte[])(ubyte[] __param_0) nothrow @nogc return ref @safeAdds new entry to the SubmissionQueue.
Note that this just adds entry to the queue and doesn't advance the tail
marker kernel sees. For that finishSq() is needed to be called next.
Also note that to actually enter new entries to kernel,
it's needed to call submit().
viaIndex[]);
if ((local variable) during.Uring ioio.int during.Uring.submit(uint want) nothrow @nogc @safeSubmits qued SubmissionEntry to be processed by kernel.
submit(1) != 1) { stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("submit (read-by-index) failed"); return 1; }
(local variable) during.Uring ioio.int during.Uring.wait(uint want = 1u) nothrow @nogcSimmilar to submit but with this method we just wait for required number
of CompletionEntries.
wait(1);
{
const (local variable) const(int) resres = (local variable) during.Uring ioio.during.io_uring.CompletionEntry during.Uring.front() pure nothrow @nogc return ref @safeGet first CompletionEntry from cq ring
front.(field) int during.io_uring.CompletionEntry.resresult code for this event
res;
(local variable) during.Uring ioio.void during.Uring.popFront() pure nothrow @nogc @safeMove to next CompletionEntry
popFront();
if ((local variable) const(int) resres < 0)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("FIXED_FILE READ failed: errno %d", -(local variable) const(int) resres);
return 1;
}
if ((local variable) const(int) resres != (immutable global) immutable(ubyte[8]) io_uring_direct_descriptors.payloadpayload.(constant) ulong immutable(ubyte[8]).length = 8LUlength || (local variable) ubyte[8] viaIndexviaIndex != (immutable global) immutable(ubyte[8]) io_uring_direct_descriptors.payloadpayload)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("read-by-index payload mismatch (res=%d)", (local variable) const(int) resres);
return 1;
}
}
// --- Step 3: FIXED_FD_INSTALL — promote slot 0 to a real process fd. -------
// This op (6.8) is itself issued against the table index; prepFixedFdInstall
// sets FIXED_FILE for us. The new, ordinary fd is returned in the CQE res.
(local variable) during.Uring ioio.putWith!((ref SubmissionEntry e) {
e.prepFixedFdInstall(/*fixedFd=*/ 0, /*flags=*/ 0);
e.user_data = 3;
})();
if ((local variable) during.Uring ioio.int during.Uring.submit(uint want) nothrow @nogc @safeSubmits qued SubmissionEntry to be processed by kernel.
submit(1) != 1) { stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("submit (install) failed"); return 1; }
(local variable) during.Uring ioio.int during.Uring.wait(uint want = 1u) nothrow @nogcSimmilar to submit but with this method we just wait for required number
of CompletionEntries.
wait(1);
int (local variable) int newFdnewFd;
{
const (local variable) const(int) resres = (local variable) during.Uring ioio.during.io_uring.CompletionEntry during.Uring.front() pure nothrow @nogc return ref @safeGet first CompletionEntry from cq ring
front.(field) int during.io_uring.CompletionEntry.resresult code for this event
res;
(local variable) during.Uring ioio.void during.Uring.popFront() pure nothrow @nogc @safeMove to next CompletionEntry
popFront();
if (bool io_uring_direct_descriptors.unsupported(int res) pure nothrow @nogc @safeunsupported((local variable) const(int) resres))
{
void std.stdio.writefln!(char, const(int))(in char[] fmt, const(int) __param_1) @safeEquivalent to writef(fmt, args, '\n').
writefln("SKIP: FIXED_FD_INSTALL unsupported (errno %d) — needs Linux 6.8+", -(local variable) const(int) resres);
return 0;
}
if ((local variable) const(int) resres < 0)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("FIXED_FD_INSTALL failed: errno %d", -(local variable) const(int) resres);
return 1;
}
(local variable) int newFdnewFd = (local variable) const(int) resres;
}
scope (exit) int core.sys.posix.unistd.close(int) nothrow @nogc @trustedclose((local variable) int newFdnewFd);
// --- Step 4: confirm the installed fd is a normal, readable process fd. ----
ubyte[8] (local variable) ubyte[8] viaRealFdviaRealFd;
const (local variable) const(long) rdrd = long core.sys.posix.unistd.read(int, void*, ulong) nothrow @nogcread((local variable) int newFdnewFd, &(local variable) ubyte[8] viaRealFdviaRealFd[0], (local variable) ubyte[8] viaRealFdviaRealFd.(constant) ulong ubyte[8].length = 8LUlength);
if ((local variable) const(long) rdrd != (immutable global) immutable(ubyte[8]) io_uring_direct_descriptors.payloadpayload.(constant) ulong immutable(ubyte[8]).length = 8LUlength || (local variable) ubyte[8] viaRealFdviaRealFd != (immutable global) immutable(ubyte[8]) io_uring_direct_descriptors.payloadpayload)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("installed real fd read mismatch (rd=%d)", (local variable) const(long) rdrd);
return 1;
}
void std.stdio.writefln!(char, int)(in char[] fmt, int __param_1) @safeEquivalent to writef(fmt, args, '\n').
writefln("ok: direct fd opened into table slot 0, read by index, then "
~ "FIXED_FD_INSTALL → real fd %d (both reads returned the same 8 bytes)", (local variable) int newFdnewFd);
return 0;
}
// Thin libc `open(path, O_CREAT|O_WRONLY, 0600)` wrapper — `core.sys.posix`'s
// `open` is variadic, which is awkward to call from a `@trusted` context, so we
// bind the C symbol directly.
private int int io_uring_direct_descriptors.openFile(const(char)* path) nothrow @nogc @trustedopenFile(const(char)* (parameter) const(char)* pathpath) @trusted nothrow @nogc
{
import (package) stdstd.(module) std.convA one-stop shop for converting values from one type to another.
Category Functions Generic asOriginalType castFrom parse to toChars bitCast Strings text wtext dtext writeText writeWText writeDText hexString Numeric octal roundTo signed unsigned Exceptions ConvException ConvOverflowException
Source
std/conv.d
conv : (alias template) octal = std.conv.octal(string num) if (isOctalLiteral(num))The octal facility provides a means to declare a number in base 8.
Using octal!177 or octal!"177" for 127 represented in octal
(same as 0177 in C).
The rules for strings are the usual for literals: If it can fit in an
int, it is an int. Otherwise, it is a long. But, if the
user specifically asks for a long with the L suffix, always
give the long. Give an unsigned iff it is asked for with the $(D
U) or u suffix. _Octals created from integers preserve the type
of the passed-in integral.
See_Also:
$(LREF parse) for parsing octal strings at runtime.
octal;
return int io_uring_direct_descriptors.open(const(char)* path, int flags, ...) nothrow @nogc @trustedc_open((parameter) const(char)* pathpath, (constant) int core.sys.posix.fcntl.O_CREAT = 64O_CREAT | (constant) int core.sys.posix.fcntl.O_WRONLY = 1O_WRONLY, (template instance) std.conv.octal!600octal!600);
}
extern (C) private int int io_uring_direct_descriptors.open(const(char)* path, int flags, ...) nothrow @nogc @trustedopen(const(char)* (parameter) const(char)* pathpath, int (parameter) int flagsflags, ...) @trusted nothrow @nogc;
private alias (alias) io_uring_direct_descriptors.c_open = int io_uring_direct_descriptors.open(const(char)* path, int flags, ...) nothrow @nogc @trustedc_open = int io_uring_direct_descriptors.open(const(char)* path, int flags, ...) nothrow @nogc @trustedopen;