#!/usr/bin/env dub
/+ dub.sdl:
name "io_uring_fs_mutations"
dependency "during" version="~>0.5.0"
platforms "linux"
targetPath "build"
+/
/**
* `io_uring` — asynchronous filesystem mutation ops (Linux 5.11 / 5.15).
*
* Before 5.11, path-based metadata operations (create, rename, unlink, …) had
* no `io_uring` opcode and had to run on a worker thread or out-of-band. Linux
* 5.11 added `RENAMEAT` and `UNLINKAT`; 5.15 rounded out the set with `MKDIRAT`,
* `SYMLINKAT`, and `LINKAT`, so a whole directory-mutation workflow can be driven
* through the ring.
*
* This example builds a small workflow inside a fresh `mkdtemp` scratch directory,
* issuing every step as an `io_uring` SQE rather than a blocking libc syscall:
* 1. `MKDIRAT` — create a `sub/` subdirectory.
* 2. (libc) — create a regular file `sub/file` (so there is something to act on).
* 3. `SYMLINKAT` — make `link` point at `sub/file`.
* 4. `RENAMEAT` — rename `sub/file` to `sub/renamed`.
* 5. `UNLINKAT` — remove `sub/renamed`, then remove `link`.
* 6. `UNLINKAT` — remove the now-empty `sub/` (with `AT_REMOVEDIR`).
* Each op's CQE `res` must be `>= 0`, and we verify the on-disk state with `stat`
* between the relevant steps. The scratch directory is always cleaned up.
*
* Companion to the io_uring chronology:
* see docs/research/async-io/io-uring/timeline.md
* § "5.11 — Filesystem mutation ops, SQPOLL without root".
*
* Run with: `dub run --single fs-mutations.d`
*
* Portability: if the running kernel has no `io_uring`, or lacks any of these
* filesystem opcodes (probe / `-EINVAL`), the program prints a `SKIP:` line and
* exits 0 so it stays green in CI regardless of the host kernel.
*/
module (module) io_uring_fs_mutationsio_uring — asynchronous filesystem mutation ops (Linux 5.11 / 5.15).
Before 5.11, path-based metadata operations (create, rename, unlink, …) had
no io_uring opcode and had to run on a worker thread or out-of-band. Linux
5.11 added RENAMEAT and UNLINKAT; 5.15 rounded out the set with MKDIRAT,
SYMLINKAT, and LINKAT, so a whole directory-mutation workflow can be driven
through the ring.
This example builds a small workflow inside a fresh mkdtemp scratch directory,
issuing every step as an io_uring SQE rather than a blocking libc syscall:
MKDIRAT — create a sub/ subdirectory.
(libc) — create a regular file sub/file (so there is something to act on).
SYMLINKAT — make link point at sub/file.
RENAMEAT — rename sub/file to sub/renamed.
UNLINKAT — remove sub/renamed, then remove link.
UNLINKAT — remove the now-empty sub/ (with AT_REMOVEDIR).
Each op's CQE res must be >= 0, and we verify the on-disk state with stat
between the relevant steps. The scratch directory is always cleaned up.
Companion to the io_uring chronology:
see docs/research/async-io/io-uring/timeline.md
§ "5.11 — Filesystem mutation ops, SQPOLL without root".
Run with: dub run --single fs-mutations.d
Portability
if the running kernel has no io_uring, or lacks any of these
filesystem opcodes (probe / -EINVAL), the program prints a SKIP: line and
exits 0 so it stays green in CI regardless of the host kernel.
io_uring_fs_mutations;
import (module) duringSimple idiomatic dlang wrapper around linux io_uring
(see: https://kernel.dk/io_uring.pdf) asynchronous API.
during;
import (package) corecore.(package) core.stdcstdc.(module) core.stdc.stringD header file for C99.
pubs.opengroup.org/onlinepubs/009695399/basedefs/string.h.html, string.h
Source
core/stdc/string.d
string : (alias) io_uring_fs_mutations.strlen = ulong core.stdc.string.strlen(scope const(char*) s) pure nothrow @nogcstrlen;
import (package) corecore.(package) core.syssys.(package) core.sys.posixposix.(module) core.sys.posix.stdlibD header file for POSIX.
stdlib : (alias) io_uring_fs_mutations.mkdtemp = char* core.sys.posix.stdlib.mkdtemp(char*) nothrow @nogcmkdtemp;
import (package) corecore.(package) core.syssys.(package) core.sys.posixposix.(module) core.sys.posix.fcntlD header file for POSIX.
fcntl : (alias constant) io_uring_fs_mutations.AT_FDCWD = int core.sys.posix.fcntl.AT_FDCWD = -100AT_FDCWD, open, (alias constant) io_uring_fs_mutations.O_CREAT = int core.sys.posix.fcntl.O_CREAT = 64O_CREAT, (alias constant) io_uring_fs_mutations.O_WRONLY = int core.sys.posix.fcntl.O_WRONLY = 1O_WRONLY;
import (package) corecore.(package) core.syssys.(package) core.sys.posixposix.(package) core.sys.posix.syssys.(module) core.sys.posix.sys.statD header file for POSIX.
stat : (alias) io_uring_fs_mutations.stat = int core.sys.posix.sys.stat.stat64(scope const(char*), core.sys.posix.sys.stat.stat_t*) nothrow @nogcstat, (struct) core.sys.posix.sys.stat.stat_tstat_t, lstat, (alias template) io_uring_fs_mutations.S_ISDIR = core.sys.posix.sys.stat.S_ISDIR()(mode_t mode)S_ISDIR, (alias template) io_uring_fs_mutations.S_ISLNK = core.sys.posix.sys.stat.S_ISLNK()(mode_t mode)S_ISLNK, (alias template) io_uring_fs_mutations.S_ISREG = core.sys.posix.sys.stat.S_ISREG()(mode_t mode)S_ISREG;
import (package) corecore.(package) core.syssys.(package) core.sys.posixposix.(module) core.sys.posix.unistdD header file for POSIX.
unistd : (alias) io_uring_fs_mutations.close = int core.sys.posix.unistd.close(int) nothrow @nogc @trustedclose, (alias) io_uring_fs_mutations.rmdir = int core.sys.posix.unistd.rmdir(scope const(char*)) nothrow @nogcrmdir, (alias) io_uring_fs_mutations.unlink = int core.sys.posix.unistd.unlink(scope const(char*)) nothrow @nogcunlink;
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 : stderr, (alias template) io_uring_fs_mutations.writefln = std.stdio.writefln(alias fmt, A...)(A args) if (isSomeString!(typeof(fmt)))Equivalent to writef(fmt, args, '\n').
writefln;
import (package) stdstd.(module) std.stringString handling functions.
Category Functions Searching column indexOf indexOfAny indexOfNeither lastIndexOf lastIndexOfAny lastIndexOfNeither Comparison isNumeric Mutation capitalize Pruning and Filling center chomp chompPrefix chop detabber detab entab entabber leftJustify outdent rightJustify strip stripLeft stripRight wrap Substitution abbrev soundex soundexer succ tr translate Miscellaneous assumeUTF fromStringz lineSplitter representation splitLines toStringz Objects of types string, wstring, and dstring are value types and cannot be mutated element-by-element. For using mutation during building strings, use char[], wchar[], or dchar[]. The xxxstring types are preferable because they don't exhibit undesired aliasing, thus making code more robust.
The following functions are publicly imported:
Module Functions Publicly imported functions std.algorithm cmp, std,algorithm,comparison count, std,algorithm,searching endsWith, std,algorithm,searching startsWith, std,algorithm,searching std.array join, std,array replace, std,array replaceInPlace, std,array split, std,array empty, std,array std.format format, std,format sformat, std,format std.uni icmp, std,uni toLower, std,uni toLowerInPlace, std,uni toUpper, std,uni toUpperInPlace, std,uni There is a rich set of functions for string handling defined in other modules. Functions related to Unicode and ASCII are found in std.uni and std.ascii, respectively. Other functions that have a wider generality than just strings can be found in std.algorithm and std.range.
Source
std/string.d
string : (alias) io_uring_fs_mutations.toStringz = immutable(char)* std.string.toStringz(scope const(char)[] s) pure nothrow @trustedtoStringz;
// `AT_REMOVEDIR` (== 0x200) tells unlinkat(2) to remove a directory instead of a
// file. core.sys.posix.fcntl doesn't expose it portably, so define it locally.
enum int (constant) int io_uring_fs_mutations.AT_REMOVEDIR = 512AT_REMOVEDIR = 0x200;
/// Submit one prepared SQE, wait for its single CQE, and return its `res` field.
/// Returns the (possibly negative) kernel result; the caller decides how to react.
int int io_uring_fs_mutations.runOne!(during.io_uring.SubmissionEntry delegate(ref during.io_uring.SubmissionEntry) nothrow @nogc @system)(ref during.Uring io, scope during.io_uring.SubmissionEntry delegate(ref during.io_uring.SubmissionEntry) nothrow @nogc @system prep, ulong cookie) nothrow @nogc @systemSubmit one prepared SQE, wait for its single CQE, and return its res field.
Returns the (possibly negative) kernel result; the caller decides how to react.
runOne(Op)(ref (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 (parameter) during.Uring ioio, scope (alias) Op = during.io_uring.SubmissionEntry delegate(ref during.io_uring.SubmissionEntry) nothrow @nogc @systemOp (parameter) during.io_uring.SubmissionEntry delegate(ref during.io_uring.SubmissionEntry) nothrow @nogc @system prepprep, ulong (parameter) ulong cookiecookie)
{
(parameter) during.Uring ioio.putWith!((ref SubmissionEntry e, scope Op p, ulong c) {
p(e);
e.user_data = c;
})(during.Uring during.Uring.putWith!(function (ref during.io_uring.SubmissionEntry e, scope during.io_uring.SubmissionEntry delegate(ref during.io_uring.SubmissionEntry) nothrow @nogc @system p, ulong c) nothrow @nogc @system
{
p(e);
e.user_data = c;
}
, during.io_uring.SubmissionEntry delegate(ref during.io_uring.SubmissionEntry) nothrow @nogc @system, ulong)(ref during.io_uring.SubmissionEntry delegate(ref during.io_uring.SubmissionEntry) nothrow @nogc @system __param_0, ref ulong __param_1) 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().
prep, (parameter) ulong cookiecookie);
const (local variable) const(int) submittedsubmitted = (parameter) during.Uring ioio.int during.Uring.submit(uint want) nothrow @nogc @safeSubmits qued SubmissionEntry to be processed by kernel.
submit(1);
if ((local variable) const(int) submittedsubmitted < 0)
return (local variable) const(int) submittedsubmitted; // surface submit failure as a negative result
(parameter) 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 = (parameter) 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;
assert((parameter) during.Uring ioio.during.io_uring.CompletionEntry during.Uring.front() pure nothrow @nogc return ref @safeGet first CompletionEntry from cq ring
front.(field) ulong during.io_uring.CompletionEntry.user_datasqe->data submission passed back
user_data == (parameter) ulong cookiecookie, "CQE cookie mismatch");
(parameter) during.Uring ioio.void during.Uring.popFront() pure nothrow @nogc @safeMove to next CompletionEntry
popFront();
return (local variable) const(int) resres;
}
/// True if `path` exists and matches the predicate over its `st_mode`.
bool bool io_uring_fs_mutations.checkMode(scope const(char)* path, bool delegate(uint) pred, bool useLstat = false)True if path exists and matches the predicate over its st_mode.
checkMode(scope const(char)* (parameter) const(char)* pathpath, bool delegate(uint) (parameter) bool delegate(uint) predpred, bool (parameter) bool useLstatuseLstat = false)
{
(struct) core.sys.posix.sys.stat.stat_tstat_t (local variable) core.sys.posix.sys.stat.stat_t stst;
const (local variable) const(int) rcrc = (parameter) bool useLstatuseLstat ? lstat(int core.sys.posix.sys.stat.lstat64(scope const(char*), core.sys.posix.sys.stat.stat_t*) nothrow @nogcpath, &(local variable) core.sys.posix.sys.stat.stat_t stst) : int core.sys.posix.sys.stat.stat64(scope const(char*), core.sys.posix.sys.stat.stat_t*) nothrow @nogcstat(int core.sys.posix.sys.stat.stat64(scope const(char*), core.sys.posix.sys.stat.stat_t*) nothrow @nogcpath, &(local variable) core.sys.posix.sys.stat.stat_t stst);
if ((local variable) const(int) rcrc != 0) return false;
return (parameter) bool delegate(uint) predpred((local variable) core.sys.posix.sys.stat.stat_t stst.(field) uint core.sys.posix.sys.stat.stat_t.st_modest_mode);
}
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;
}
// Capability probe: every op we use must be advertised by the kernel. If any
// is missing this kernel predates 5.11/5.15 for that opcode — skip cleanly.
auto (local variable) during.Probe probeprobe = (local variable) during.Uring ioio.during.Probe during.Uring.probe() nothrow @nogc @safeProbes supported operations
probe();
if (!cast(bool) (local variable) during.Probe probeprobe)
{
void std.stdio.writefln!char(in char[] fmt) @safeEquivalent to writef(fmt, args, '\n').
writefln("SKIP: io_uring probe failed — cannot determine supported ops");
return 0;
}
foreach ((parameter) during.io_uring.Operation opop; [(enum) during.io_uring.OperationDescribes the operation to be performed
Operation.(enum value) during.io_uring.Operation.MKDIRAT = cast(ubyte)37uIORING_OP_MKDIRAT - see mkdirat(2)
MKDIRAT, (enum) during.io_uring.OperationDescribes the operation to be performed
Operation.(enum value) during.io_uring.Operation.SYMLINKAT = cast(ubyte)38uIORING_OP_SYMLINKAT - see symlinkat(2)
SYMLINKAT, (enum) during.io_uring.OperationDescribes the operation to be performed
Operation.(enum value) during.io_uring.Operation.RENAMEAT = cast(ubyte)35uIORING_OP_RENAMEAT - see renameat2()
RENAMEAT, (enum) during.io_uring.OperationDescribes the operation to be performed
Operation.(enum value) during.io_uring.Operation.UNLINKAT = cast(ubyte)36uIORING_OP_UNLINKAT - see unlinkat(2)
UNLINKAT])
{
if (!(local variable) during.Probe probeprobe.bool during.Probe.isSupported(during.io_uring.Operation op) const pure nothrow @nogc @safeIs operation supported?
isSupported((local variable) during.io_uring.Operation opop))
{
void std.stdio.writefln!(char, during.io_uring.Operation)(in char[] fmt, during.io_uring.Operation __param_1) @safeEquivalent to writef(fmt, args, '\n').
writefln("SKIP: io_uring op %s unsupported on this kernel (needs 5.11/5.15)", (local variable) during.io_uring.Operation opop);
return 0;
}
}
// Fresh scratch directory under the system temp dir. mkdtemp mutates the
// template in place and returns null on failure.
char[] (local variable) char[] tmpltmpl = "/tmp/iouring_fs_XXXXXX\0".char[] object.dup!char(const(char)[] a) pure nothrow @property @safedup;
if (char* core.sys.posix.stdlib.mkdtemp(char*) nothrow @nogcmkdtemp((local variable) char[] tmpltmpl.(field) char* char[].ptrptr) is null)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("mkdtemp failed");
return 1;
}
const (local variable) const(string) basebase = cast((alias) object.string = stringstring) (local variable) char[] tmpltmpl[0 .. ulong core.stdc.string.strlen(scope const(char*) s) pure nothrow @nogcstrlen((local variable) char[] tmpltmpl.(field) char* char[].ptrptr)];
scope (exit) int core.sys.posix.unistd.rmdir(scope const(char*)) nothrow @nogcrmdir((local variable) const(string) basebase.immutable(char)* std.string.toStringz(scope const(char)[] s) pure nothrow @trustedExamples
import core.stdc.string : strlen;
import std.conv : to;
auto p = toStringz("foo");
assert(strlen(p) == 3);
const(char)[] foo = "abbzxyzzy";
p = toStringz(foo[3 .. 5]);
assert(strlen(p) == 2);
string test = "";
p = toStringz(test);
assert(*p == 0);
test = "\0";
p = toStringz(test);
assert(*p == 0);
test = "foo\0";
p = toStringz(test);
assert(p[0] == 'f' && p[1] == 'o' && p[2] == 'o' && p[3] == 0);
const string test2 = "";
p = toStringz(test2);
assert(*p == 0);
assert(toStringz([]) is toStringz(""));
toStringz); // remove the top-level scratch dir last
// Build absolute paths once; toStringz gives us GC'd, NUL-terminated copies
// that stay alive for the whole submit/complete cycle of each op.
const (local variable) const(immutable(char)*) subDirsubDir = ((local variable) const(string) basebase ~ "/sub").immutable(char)* std.string.toStringz(scope const(char)[] s) pure nothrow @trustedExamples
import core.stdc.string : strlen;
import std.conv : to;
auto p = toStringz("foo");
assert(strlen(p) == 3);
const(char)[] foo = "abbzxyzzy";
p = toStringz(foo[3 .. 5]);
assert(strlen(p) == 2);
string test = "";
p = toStringz(test);
assert(*p == 0);
test = "\0";
p = toStringz(test);
assert(*p == 0);
test = "foo\0";
p = toStringz(test);
assert(p[0] == 'f' && p[1] == 'o' && p[2] == 'o' && p[3] == 0);
const string test2 = "";
p = toStringz(test2);
assert(*p == 0);
assert(toStringz([]) is toStringz(""));
toStringz;
const (local variable) const(immutable(char)*) filePathfilePath = ((local variable) const(string) basebase ~ "/sub/file").immutable(char)* std.string.toStringz(scope const(char)[] s) pure nothrow @trustedExamples
import core.stdc.string : strlen;
import std.conv : to;
auto p = toStringz("foo");
assert(strlen(p) == 3);
const(char)[] foo = "abbzxyzzy";
p = toStringz(foo[3 .. 5]);
assert(strlen(p) == 2);
string test = "";
p = toStringz(test);
assert(*p == 0);
test = "\0";
p = toStringz(test);
assert(*p == 0);
test = "foo\0";
p = toStringz(test);
assert(p[0] == 'f' && p[1] == 'o' && p[2] == 'o' && p[3] == 0);
const string test2 = "";
p = toStringz(test2);
assert(*p == 0);
assert(toStringz([]) is toStringz(""));
toStringz;
const (local variable) const(immutable(char)*) renamedrenamed = ((local variable) const(string) basebase ~ "/sub/renamed").immutable(char)* std.string.toStringz(scope const(char)[] s) pure nothrow @trustedExamples
import core.stdc.string : strlen;
import std.conv : to;
auto p = toStringz("foo");
assert(strlen(p) == 3);
const(char)[] foo = "abbzxyzzy";
p = toStringz(foo[3 .. 5]);
assert(strlen(p) == 2);
string test = "";
p = toStringz(test);
assert(*p == 0);
test = "\0";
p = toStringz(test);
assert(*p == 0);
test = "foo\0";
p = toStringz(test);
assert(p[0] == 'f' && p[1] == 'o' && p[2] == 'o' && p[3] == 0);
const string test2 = "";
p = toStringz(test2);
assert(*p == 0);
assert(toStringz([]) is toStringz(""));
toStringz;
const (local variable) const(immutable(char)*) linkPathlinkPath = ((local variable) const(string) basebase ~ "/link").immutable(char)* std.string.toStringz(scope const(char)[] s) pure nothrow @trustedExamples
import core.stdc.string : strlen;
import std.conv : to;
auto p = toStringz("foo");
assert(strlen(p) == 3);
const(char)[] foo = "abbzxyzzy";
p = toStringz(foo[3 .. 5]);
assert(strlen(p) == 2);
string test = "";
p = toStringz(test);
assert(*p == 0);
test = "\0";
p = toStringz(test);
assert(*p == 0);
test = "foo\0";
p = toStringz(test);
assert(p[0] == 'f' && p[1] == 'o' && p[2] == 'o' && p[3] == 0);
const string test2 = "";
p = toStringz(test2);
assert(*p == 0);
assert(toStringz([]) is toStringz(""));
toStringz;
// Symlink target is relative-as-stored; we point the link at the absolute file.
const (local variable) const(immutable(char)*) linkTargetlinkTarget = (local variable) const(immutable(char)*) filePathfilePath;
// 1. MKDIRAT: create base/sub with mode 0755.
{
const (local variable) const(int) resres = int io_uring_fs_mutations.runOne!(during.io_uring.SubmissionEntry delegate(ref during.io_uring.SubmissionEntry) nothrow @nogc @system)(ref during.Uring io, scope during.io_uring.SubmissionEntry delegate(ref during.io_uring.SubmissionEntry) nothrow @nogc @system prep, ulong cookie) nothrow @nogc @systemSubmit one prepared SQE, wait for its single CQE, and return its res field.
Returns the (possibly negative) kernel result; the caller decides how to react.
runOne((local variable) during.Uring ioio,
(ref (struct) during.io_uring.SubmissionEntryIO operation submission data structure (Submission queue entry).
C API: struct io_uring_sqe
SubmissionEntry (parameter) during.io_uring.SubmissionEntry ee) => (parameter) during.io_uring.SubmissionEntry ee.during.io_uring.SubmissionEntry during.prepMkdirat(return ref during.io_uring.SubmissionEntry entry, int dirfd, const(char)* path, uint mode) nothrow @nogc refNote
Available from Linux 5.15
prepMkdirat((constant) int core.sys.posix.fcntl.AT_FDCWD = -100AT_FDCWD, (local variable) const(immutable(char)*) subDirsubDir, (template instance) io_uring_fs_mutations.octal!755octal!755),
1);
if ((local variable) const(int) resres == -(constant) int core.stdc.errno.EINVAL = 22EINVAL)
{
void std.stdio.writefln!char(in char[] fmt) @safeEquivalent to writef(fmt, args, '\n').
writefln("SKIP: MKDIRAT rejected with -EINVAL — unsupported on this kernel");
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("MKDIRAT failed: errno %d", -(local variable) const(int) resres);
return 1;
}
}
if (!bool io_uring_fs_mutations.checkMode(scope const(char)* path, bool delegate(uint) pred, bool useLstat = false)True if path exists and matches the predicate over its st_mode.
checkMode((local variable) const(immutable(char)*) subDirsubDir, (alias) __T1 = uintm => bool core.sys.posix.sys.stat.S_ISDIR!()(uint mode) pure nothrow @nogc @safeS_ISDIR((parameter) uint mm)))
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("post-MKDIRAT: sub/ is not a directory");
return 1;
}
// 2. Create the regular file via libc (there's no IORING_OP_CREATE; openat
// direct/fixed exists but a plain file is all we need to mutate below).
{
const (local variable) const(int) fdfd = open(int core.sys.posix.fcntl.open64(scope const(char*), int, ...) nothrow @nogcfilePath, (constant) int core.sys.posix.fcntl.O_CREAT = 64O_CREAT | (constant) int core.sys.posix.fcntl.O_WRONLY = 1O_WRONLY, (template instance) io_uring_fs_mutations.octal!644octal!644);
if ((local variable) const(int) fdfd < 0)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("open(sub/file) failed");
return 1;
}
int core.sys.posix.unistd.close(int) nothrow @nogc @trustedclose((local variable) const(int) fdfd);
}
// 3. SYMLINKAT: base/link -> base/sub/file.
{
const (local variable) const(int) resres = int io_uring_fs_mutations.runOne!(during.io_uring.SubmissionEntry delegate(ref during.io_uring.SubmissionEntry) nothrow @nogc @system)(ref during.Uring io, scope during.io_uring.SubmissionEntry delegate(ref during.io_uring.SubmissionEntry) nothrow @nogc @system prep, ulong cookie) nothrow @nogc @systemSubmit one prepared SQE, wait for its single CQE, and return its res field.
Returns the (possibly negative) kernel result; the caller decides how to react.
runOne((local variable) during.Uring ioio,
(ref (struct) during.io_uring.SubmissionEntryIO operation submission data structure (Submission queue entry).
C API: struct io_uring_sqe
SubmissionEntry (parameter) during.io_uring.SubmissionEntry ee) => (parameter) during.io_uring.SubmissionEntry ee.during.io_uring.SubmissionEntry during.prepSymlinkat(return ref during.io_uring.SubmissionEntry entry, const(char)* target, int newdirfd, const(char)* linkpath) nothrow @nogc refNote
Available from Linux 5.15
prepSymlinkat((local variable) const(immutable(char)*) linkTargetlinkTarget, (constant) int core.sys.posix.fcntl.AT_FDCWD = -100AT_FDCWD, (local variable) const(immutable(char)*) linkPathlinkPath),
3);
if ((local variable) const(int) resres < 0)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("SYMLINKAT failed: errno %d", -(local variable) const(int) resres);
return 1;
}
}
// lstat the link itself (don't follow) to confirm it's a symlink, and stat it
// (follow) to confirm it resolves to the regular file.
if (!bool io_uring_fs_mutations.checkMode(scope const(char)* path, bool delegate(uint) pred, bool useLstat = false)True if path exists and matches the predicate over its st_mode.
checkMode((local variable) const(immutable(char)*) linkPathlinkPath, (alias) __T2 = uintm => bool core.sys.posix.sys.stat.S_ISLNK!()(uint mode) pure nothrow @nogc @safeS_ISLNK((parameter) uint mm), /*useLstat*/ true))
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("post-SYMLINKAT: link is not a symlink");
return 1;
}
if (!bool io_uring_fs_mutations.checkMode(scope const(char)* path, bool delegate(uint) pred, bool useLstat = false)True if path exists and matches the predicate over its st_mode.
checkMode((local variable) const(immutable(char)*) linkPathlinkPath, (alias) __T3 = uintm => bool core.sys.posix.sys.stat.S_ISREG!()(uint mode) pure nothrow @nogc @safeS_ISREG((parameter) uint mm)))
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("post-SYMLINKAT: link does not resolve to a regular file");
return 1;
}
// 4. RENAMEAT: sub/file -> sub/renamed (flags 0 == plain renameat semantics).
{
const (local variable) const(int) resres = int io_uring_fs_mutations.runOne!(during.io_uring.SubmissionEntry delegate(ref during.io_uring.SubmissionEntry) nothrow @nogc @system)(ref during.Uring io, scope during.io_uring.SubmissionEntry delegate(ref during.io_uring.SubmissionEntry) nothrow @nogc @system prep, ulong cookie) nothrow @nogc @systemSubmit one prepared SQE, wait for its single CQE, and return its res field.
Returns the (possibly negative) kernel result; the caller decides how to react.
runOne((local variable) during.Uring ioio,
(ref (struct) during.io_uring.SubmissionEntryIO operation submission data structure (Submission queue entry).
C API: struct io_uring_sqe
SubmissionEntry (parameter) during.io_uring.SubmissionEntry ee) => (parameter) during.io_uring.SubmissionEntry ee.during.io_uring.SubmissionEntry during.prepRenameat(return ref during.io_uring.SubmissionEntry entry, int olddfd, const(char)* oldpath, int newfd, const(char)* newpath, int flags) nothrow @nogc refNote
Available from Linux 5.11
prepRenameat((constant) int core.sys.posix.fcntl.AT_FDCWD = -100AT_FDCWD, (local variable) const(immutable(char)*) filePathfilePath, (constant) int core.sys.posix.fcntl.AT_FDCWD = -100AT_FDCWD, (local variable) const(immutable(char)*) renamedrenamed, 0),
4);
if ((local variable) const(int) resres < 0)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("RENAMEAT failed: errno %d", -(local variable) const(int) resres);
return 1;
}
}
if (bool io_uring_fs_mutations.checkMode(scope const(char)* path, bool delegate(uint) pred, bool useLstat = false)True if path exists and matches the predicate over its st_mode.
checkMode((local variable) const(immutable(char)*) filePathfilePath, (alias) __T4 = uintm => true) || !bool io_uring_fs_mutations.checkMode(scope const(char)* path, bool delegate(uint) pred, bool useLstat = false)True if path exists and matches the predicate over its st_mode.
checkMode((local variable) const(immutable(char)*) renamedrenamed, (alias) __T5 = uintm => bool core.sys.posix.sys.stat.S_ISREG!()(uint mode) pure nothrow @nogc @safeS_ISREG((parameter) uint mm)))
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("post-RENAMEAT: rename did not take effect");
return 1;
}
// 5a. UNLINKAT: remove sub/renamed (the regular file).
{
const (local variable) const(int) resres = int io_uring_fs_mutations.runOne!(during.io_uring.SubmissionEntry delegate(ref during.io_uring.SubmissionEntry) nothrow @nogc @system)(ref during.Uring io, scope during.io_uring.SubmissionEntry delegate(ref during.io_uring.SubmissionEntry) nothrow @nogc @system prep, ulong cookie) nothrow @nogc @systemSubmit one prepared SQE, wait for its single CQE, and return its res field.
Returns the (possibly negative) kernel result; the caller decides how to react.
runOne((local variable) during.Uring ioio,
(ref (struct) during.io_uring.SubmissionEntryIO operation submission data structure (Submission queue entry).
C API: struct io_uring_sqe
SubmissionEntry (parameter) during.io_uring.SubmissionEntry ee) => (parameter) during.io_uring.SubmissionEntry ee.during.io_uring.SubmissionEntry during.prepUnlinkat(return ref during.io_uring.SubmissionEntry entry, int dirfd, const(char)* path, int flags) nothrow @nogc refNote
Available from Linux 5.11
prepUnlinkat((constant) int core.sys.posix.fcntl.AT_FDCWD = -100AT_FDCWD, (local variable) const(immutable(char)*) renamedrenamed, 0),
5);
if ((local variable) const(int) resres < 0)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("UNLINKAT(file) failed: errno %d", -(local variable) const(int) resres);
return 1;
}
}
// 5b. UNLINKAT: remove the dangling symlink (flags 0 unlinks the link itself).
{
const (local variable) const(int) resres = int io_uring_fs_mutations.runOne!(during.io_uring.SubmissionEntry delegate(ref during.io_uring.SubmissionEntry) nothrow @nogc @system)(ref during.Uring io, scope during.io_uring.SubmissionEntry delegate(ref during.io_uring.SubmissionEntry) nothrow @nogc @system prep, ulong cookie) nothrow @nogc @systemSubmit one prepared SQE, wait for its single CQE, and return its res field.
Returns the (possibly negative) kernel result; the caller decides how to react.
runOne((local variable) during.Uring ioio,
(ref (struct) during.io_uring.SubmissionEntryIO operation submission data structure (Submission queue entry).
C API: struct io_uring_sqe
SubmissionEntry (parameter) during.io_uring.SubmissionEntry ee) => (parameter) during.io_uring.SubmissionEntry ee.during.io_uring.SubmissionEntry during.prepUnlinkat(return ref during.io_uring.SubmissionEntry entry, int dirfd, const(char)* path, int flags) nothrow @nogc refNote
Available from Linux 5.11
prepUnlinkat((constant) int core.sys.posix.fcntl.AT_FDCWD = -100AT_FDCWD, (local variable) const(immutable(char)*) linkPathlinkPath, 0),
6);
if ((local variable) const(int) resres < 0)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("UNLINKAT(symlink) failed: errno %d", -(local variable) const(int) resres);
return 1;
}
}
if (bool io_uring_fs_mutations.checkMode(scope const(char)* path, bool delegate(uint) pred, bool useLstat = false)True if path exists and matches the predicate over its st_mode.
checkMode((local variable) const(immutable(char)*) renamedrenamed, (alias) __T6 = uintm => true, true) || bool io_uring_fs_mutations.checkMode(scope const(char)* path, bool delegate(uint) pred, bool useLstat = false)True if path exists and matches the predicate over its st_mode.
checkMode((local variable) const(immutable(char)*) linkPathlinkPath, (alias) __T7 = uintm => true, true))
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("post-UNLINKAT: file or symlink still present");
return 1;
}
// 6. UNLINKAT with AT_REMOVEDIR: remove the now-empty sub/ directory.
{
const (local variable) const(int) resres = int io_uring_fs_mutations.runOne!(during.io_uring.SubmissionEntry delegate(ref during.io_uring.SubmissionEntry) nothrow @nogc @system)(ref during.Uring io, scope during.io_uring.SubmissionEntry delegate(ref during.io_uring.SubmissionEntry) nothrow @nogc @system prep, ulong cookie) nothrow @nogc @systemSubmit one prepared SQE, wait for its single CQE, and return its res field.
Returns the (possibly negative) kernel result; the caller decides how to react.
runOne((local variable) during.Uring ioio,
(ref (struct) during.io_uring.SubmissionEntryIO operation submission data structure (Submission queue entry).
C API: struct io_uring_sqe
SubmissionEntry (parameter) during.io_uring.SubmissionEntry ee) => (parameter) during.io_uring.SubmissionEntry ee.during.io_uring.SubmissionEntry during.prepUnlinkat(return ref during.io_uring.SubmissionEntry entry, int dirfd, const(char)* path, int flags) nothrow @nogc refNote
Available from Linux 5.11
prepUnlinkat((constant) int core.sys.posix.fcntl.AT_FDCWD = -100AT_FDCWD, (local variable) const(immutable(char)*) subDirsubDir, (constant) int io_uring_fs_mutations.AT_REMOVEDIR = 512AT_REMOVEDIR),
7);
if ((local variable) const(int) resres < 0)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("UNLINKAT(dir) failed: errno %d", -(local variable) const(int) resres);
return 1;
}
}
if (bool io_uring_fs_mutations.checkMode(scope const(char)* path, bool delegate(uint) pred, bool useLstat = false)True if path exists and matches the predicate over its st_mode.
checkMode((local variable) const(immutable(char)*) subDirsubDir, (alias) __T8 = uintm => true, true))
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("post-UNLINKAT: sub/ directory still present");
return 1;
}
void std.stdio.writefln!(char, string)(in char[] fmt, string __param_1) @safeEquivalent to writef(fmt, args, '\n').
writefln("ok: drove MKDIRAT/SYMLINKAT/RENAMEAT/UNLINKAT through the ring in %s — final state verified", (local variable) const(string) basebase);
return 0;
}
// errno + octal helpers (kept local to avoid extra imports cluttering the header).
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_fs_mutations.EINVAL = int core.stdc.errno.EINVAL = 22EINVAL;
/// Compile-time octal literal (D dropped the `0NNN` syntax; std.conv.octal works too).
template (template) io_uring_fs_mutations.octal(int n)Compile-time octal literal (D dropped the 0NNN syntax; std.conv.octal works too).
octal(int n) { enum (constant) int io_uring_fs_mutations.octal!644 = 420Compile-time octal literal (D dropped the 0NNN syntax; std.conv.octal works too).
octal = int io_uring_fs_mutations.octalImpl(int decimalDigits) pure nothrow @nogc @safeoctalImpl((constant) int io_uring_fs_mutations.n = 755n); }
private int int io_uring_fs_mutations.octalImpl(int decimalDigits) pure nothrow @nogc @safeoctalImpl(int (parameter) int decimalDigitsdecimalDigits) pure nothrow @nogc @safe
{
int (local variable) int resultresult = 0, (local variable) int multmult = 1;
while ((parameter) int decimalDigitsdecimalDigits > 0)
{
(local variable) int resultresult += ((parameter) int decimalDigitsdecimalDigits % 10) * (local variable) int multmult;
(local variable) int multmult *= 8;
(parameter) int decimalDigitsdecimalDigits /= 10;
}
return (local variable) int resultresult;
}