{
  "name": "RodHat syscall reference",
  "url": "https://rodhat.com/syscalls/",
  "checker": "https://rodhat.com/syscalls/check/",
  "description": "Linux and BSD kernel interfaces: version introduced, glibc wrapper, flags, errno, BSD/Linux equivalent. Every version is sourced from the man pages listed per row.",
  "count": 42,
  "syscalls": [
    {
      "slug": "openat2",
      "name": "openat2",
      "section": "2",
      "os": "linux",
      "category": "filesystem",
      "summary": "Open a file with kernel-enforced path-resolution constraints (RESOLVE_* flags).",
      "synopsis": "#include <fcntl.h>\n#include <linux/openat2.h>\n#include <sys/syscall.h>\n#include <unistd.h>\n\nlong syscall(SYS_openat2, int dirfd, const char *path,\n             struct open_how *how, size_t size);",
      "since": "Linux 5.6",
      "glibc": "none (use syscall(2))",
      "flags": [
        {
          "name": "RESOLVE_BENEATH",
          "meaning": "Fail if any path component resolves outside dirfd's tree; absolute paths and absolute symlinks are rejected (EXDEV)."
        },
        {
          "name": "RESOLVE_IN_ROOT",
          "meaning": "Treat dirfd as the root for this lookup, like a per-open chroot(2): absolute paths and '..' are clamped to dirfd."
        },
        {
          "name": "RESOLVE_NO_SYMLINKS",
          "meaning": "Refuse to follow any symlink in any component (ELOOP). Implies RESOLVE_NO_MAGICLINKS."
        },
        {
          "name": "RESOLVE_NO_MAGICLINKS",
          "meaning": "Refuse procfs-style magic links such as /proc/self/fd/N (ELOOP)."
        },
        {
          "name": "RESOLVE_NO_XDEV",
          "meaning": "Refuse to cross mount points, bind mounts included (EXDEV)."
        },
        {
          "name": "RESOLVE_CACHED",
          "since": "Linux 5.12",
          "meaning": "Fail with EAGAIN unless every component is already in the lookup cache; retry without it.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              5,
              12,
              0
            ]
          }
        },
        {
          "name": "OPENAT2_REGULAR",
          "since": "Linux 7.2",
          "meaning": "Open only if the target is a regular file; otherwise fail with EFTYPE.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              7,
              2,
              0
            ]
          }
        },
        {
          "name": "struct open_how",
          "meaning": "Versioned argument struct: flags, mode, resolve. Pass sizeof(struct open_how) as size."
        }
      ],
      "errors": [
        {
          "name": "EXDEV",
          "meaning": "RESOLVE_BENEATH/RESOLVE_IN_ROOT detected an escape, or RESOLVE_NO_XDEV saw a mount crossing."
        },
        {
          "name": "ELOOP",
          "meaning": "A symlink (or magic link) was hit with RESOLVE_NO_SYMLINKS or RESOLVE_NO_MAGICLINKS set."
        },
        {
          "name": "E2BIG",
          "meaning": "how contains an extension this kernel doesn't support (non-zero unknown fields)."
        },
        {
          "name": "EAGAIN",
          "meaning": "RESOLVE_CACHED needed I/O, or a '..' race could not be ruled out under BENEATH/IN_ROOT; retry."
        },
        {
          "name": "EINVAL",
          "meaning": "Unknown flag or bad value in how, mode set without O_CREAT/O_TMPFILE, or size too small."
        }
      ],
      "bsd": "FreeBSD: O_RESOLVE_BENEATH flag to openat(2) (FreeBSD 13), which fails with ENOTCAPABLE on escape. OpenBSD: none; closest is unveil(2).",
      "man": "https://man7.org/linux/man-pages/man2/openat2.2.html",
      "sources": [
        {
          "label": "openat2(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/openat2.2.html"
        },
        {
          "label": "open(2), FreeBSD",
          "url": "https://man.freebsd.org/cgi/man.cgi?query=open&sektion=2"
        },
        {
          "label": "unveil(2), OpenBSD",
          "url": "https://man.openbsd.org/unveil.2"
        }
      ],
      "take": "chroot needs root, leaks through any directory fd you forgot to close, and was never a security boundary. openat2 doesn't fix chroot. It fixes the real bug: open() never let you say \"don't resolve outside this tree.\" RESOLVE_BENEATH turns zip slip into an EXDEV instead of an overwritten file, and RESOLVE_NO_SYMLINKS kills the symlink games inside the tree too. No root, no subprocess, one struct. Unknown struct fields get E2BIG instead of being silently ignored, which is the first time open() has grown a spine in decades. Handle ENOSYS on old kernels and get on with your life.",
      "example": "int base = open(root, O_RDONLY | O_DIRECTORY | O_CLOEXEC);\n\nstruct open_how how = {\n    .flags   = O_RDONLY | O_CLOEXEC,\n    .resolve = RESOLVE_BENEATH | RESOLVE_NO_SYMLINKS,\n};\nlong fd = syscall(SYS_openat2, base, untrusted_path, &how, sizeof(how));\nif (fd < 0) {\n    if (errno == EXDEV || errno == ELOOP)\n        warnx(\"rejected: %s escapes or uses symlinks\", untrusted_path);\n    else if (errno == ENOSYS)\n        warnx(\"kernel < 5.6: no openat2\");\n}",
      "exampleLang": "c",
      "ref": "openat2(2)",
      "url": "https://rodhat.com/syscalls/openat2/",
      "sinceParsed": {
        "kind": "linux",
        "min": [
          5,
          6,
          0
        ]
      },
      "tips": [
        "https://rodhat.com/tips/2026-09-12-openat2-resolve-flags/"
      ],
      "related": [
        "https://rodhat.com/syscalls/landlock_create_ruleset/",
        "https://rodhat.com/syscalls/seccomp/",
        "https://rodhat.com/syscalls/close_range/",
        "https://rodhat.com/syscalls/fanotify_init/",
        "https://rodhat.com/syscalls/unveil/"
      ]
    },
    {
      "slug": "pidfd_open",
      "name": "pidfd_open",
      "section": "2",
      "os": "linux",
      "category": "process",
      "summary": "Get a file descriptor that refers to a process, immune to PID reuse.",
      "synopsis": "#include <sys/syscall.h>\n#include <unistd.h>\n\nint syscall(SYS_pidfd_open, pid_t pid, unsigned int flags);",
      "since": "Linux 5.3",
      "glibc": "glibc 2.36",
      "flags": [
        {
          "name": "PIDFD_NONBLOCK",
          "since": "Linux 5.10",
          "meaning": "waitid(2) on the fd returns EAGAIN instead of blocking if the task hasn't terminated.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              5,
              10,
              0
            ]
          }
        },
        {
          "name": "PIDFD_THREAD",
          "since": "Linux 6.9",
          "meaning": "Refer to a specific thread rather than a thread-group leader.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              6,
              9,
              0
            ]
          }
        },
        {
          "name": "poll/epoll",
          "meaning": "The fd turns readable (EPOLLIN) when the process exits and becomes a zombie; EPOLLHUP once it is reaped."
        },
        {
          "name": "waitid(P_PIDFD)",
          "meaning": "If the pidfd refers to your child, wait on it by fd instead of by PID."
        }
      ],
      "errors": [
        {
          "name": "ESRCH",
          "meaning": "The process specified by pid does not exist."
        },
        {
          "name": "EINVAL",
          "meaning": "flags is not valid, or pid is not valid."
        },
        {
          "name": "EMFILE",
          "meaning": "Per-process open file descriptor limit reached."
        },
        {
          "name": "ENODEV",
          "meaning": "The anonymous inode filesystem is not available in this kernel."
        }
      ],
      "bsd": "FreeBSD: process descriptors created at fork time with pdfork(2), plus pdgetpid(2) and EVFILT_PROCDESC in kqueue(2) (FreeBSD 9.0). OpenBSD: none.",
      "man": "https://man7.org/linux/man-pages/man2/pidfd_open.2.html",
      "sources": [
        {
          "label": "pidfd_open(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/pidfd_open.2.html"
        },
        {
          "label": "pdfork(2), FreeBSD",
          "url": "https://man.freebsd.org/cgi/man.cgi?query=pdfork&sektion=2"
        },
        {
          "label": "kqueue(2), FreeBSD",
          "url": "https://man.freebsd.org/cgi/man.cgi?query=kqueue&sektion=2"
        },
        {
          "label": "glibc 2.36 NEWS (pidfd_open, pidfd_getfd, pidfd_send_signal added)",
          "url": "https://raw.githubusercontent.com/bminor/glibc/release/2.36/master/NEWS"
        }
      ],
      "take": "A PID is a number the kernel will hand to someone else the moment your child dies, and kill(2) has trusted that number since Edition 6. pidfd_open gives you a reference instead. Poll it, signal it, waitid(P_PIDFD) on it, and you're talking to the process you meant, not whatever inherited the slot. Fine print: open it after fork() and it's only race-free if nobody else reaped the child and SIGCHLD isn't ignored; otherwise use clone3 with CLONE_PIDFD. PIDs for logging, pidfds for control. Fifty years late, but correct.",
      "example": "int pfd = syscall(SYS_pidfd_open, pid, 0);\nif (pfd < 0)\n    err(1, \"pidfd_open\");           /* ESRCH: already gone */\n\nstruct pollfd p = { .fd = pfd, .events = POLLIN };\npoll(&p, 1, -1);                     /* readable once it exits */\n\nsiginfo_t info = {0};\nwaitid(P_PIDFD, pfd, &info, WEXITED); /* only if it's your child */\nclose(pfd);",
      "exampleLang": "c",
      "ref": "pidfd_open(2)",
      "url": "https://rodhat.com/syscalls/pidfd_open/",
      "sinceParsed": {
        "kind": "linux",
        "min": [
          5,
          3,
          0
        ]
      },
      "tips": [
        "https://rodhat.com/tips/2026-08-26-pidfd-process-file-descriptors/"
      ],
      "related": [
        "https://rodhat.com/syscalls/pidfd_send_signal/",
        "https://rodhat.com/syscalls/pidfd_getfd/",
        "https://rodhat.com/syscalls/signalfd/",
        "https://rodhat.com/syscalls/setns/"
      ]
    },
    {
      "slug": "pidfd_send_signal",
      "name": "pidfd_send_signal",
      "section": "2",
      "os": "linux",
      "category": "process",
      "summary": "Send a signal to the process a pidfd refers to, without PID-reuse races.",
      "synopsis": "#include <linux/signal.h>\n#include <signal.h>\n#include <sys/syscall.h>\n#include <unistd.h>\n\nint syscall(SYS_pidfd_send_signal, int pidfd, int sig,\n            siginfo_t *_Nullable info, unsigned int flags);",
      "since": "Linux 5.1",
      "glibc": "glibc 2.36",
      "flags": [
        {
          "name": "info = NULL",
          "meaning": "Equivalent to kill(2): the kernel fills in si_signo, si_code = SI_USER, sender PID and UID."
        },
        {
          "name": "PIDFD_SIGNAL_THREAD",
          "since": "Linux 6.9",
          "meaning": "Send the signal to the specific thread referenced by pidfd.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              6,
              9,
              0
            ]
          }
        },
        {
          "name": "PIDFD_SIGNAL_THREAD_GROUP",
          "since": "Linux 6.9",
          "meaning": "If pidfd refers to a thread-group leader, signal the whole thread group.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              6,
              9,
              0
            ]
          }
        },
        {
          "name": "PIDFD_SIGNAL_PROCESS_GROUP",
          "since": "Linux 6.9",
          "meaning": "If pidfd refers to a process-group leader, signal the whole process group.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              6,
              9,
              0
            ]
          }
        }
      ],
      "errors": [
        {
          "name": "ESRCH",
          "meaning": "The target process has terminated and been waited on."
        },
        {
          "name": "EBADF",
          "meaning": "pidfd is not a valid PID file descriptor."
        },
        {
          "name": "EPERM",
          "meaning": "No permission to signal the target, or invalid si_code when signalling another process."
        },
        {
          "name": "EINVAL",
          "meaning": "Invalid signal, invalid flags, or caller's PID namespace can't signal the target."
        }
      ],
      "bsd": "FreeBSD: pdkill(2) on a process descriptor from pdfork(2) (FreeBSD 9.0). OpenBSD: none.",
      "man": "https://man7.org/linux/man-pages/man2/pidfd_send_signal.2.html",
      "sources": [
        {
          "label": "pidfd_send_signal(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/pidfd_send_signal.2.html"
        },
        {
          "label": "pdfork(2), FreeBSD",
          "url": "https://man.freebsd.org/cgi/man.cgi?query=pdfork&sektion=2"
        },
        {
          "label": "glibc 2.36 NEWS (pidfd_open, pidfd_getfd, pidfd_send_signal added)",
          "url": "https://raw.githubusercontent.com/bminor/glibc/release/2.36/master/NEWS"
        }
      ],
      "take": "kill(child, SIGTERM) after the child is gone is how you SIGTERM a stranger. pidfd_send_signal takes the fd, checks the actual task, and hands you ESRCH if your process already exited and got reaped, instead of shooting whoever inherited the number. It landed in 5.1, two releases before pidfd_open, back when you got the fd by opening /proc/<pid>. Pass NULL for info and it behaves like kill(2), which makes the migration close to a one-line diff. No excuse left for signalling by number.",
      "example": "int pfd = syscall(SYS_pidfd_open, pid, 0);\nif (pfd < 0)\n    err(1, \"pidfd_open\");\n\nif (syscall(SYS_pidfd_send_signal, pfd, SIGTERM, NULL, 0) < 0) {\n    if (errno == ESRCH)\n        warnx(\"already exited and reaped\");\n    else\n        err(1, \"pidfd_send_signal\");\n}\nclose(pfd);",
      "exampleLang": "c",
      "ref": "pidfd_send_signal(2)",
      "url": "https://rodhat.com/syscalls/pidfd_send_signal/",
      "sinceParsed": {
        "kind": "linux",
        "min": [
          5,
          1,
          0
        ]
      },
      "tips": [
        "https://rodhat.com/tips/2026-08-26-pidfd-process-file-descriptors/"
      ],
      "related": [
        "https://rodhat.com/syscalls/pidfd_open/",
        "https://rodhat.com/syscalls/pidfd_getfd/",
        "https://rodhat.com/syscalls/signalfd/"
      ]
    },
    {
      "slug": "pidfd_getfd",
      "name": "pidfd_getfd",
      "section": "2",
      "os": "linux",
      "category": "file-descriptors",
      "summary": "Duplicate a file descriptor out of another process, addressed by its pidfd.",
      "synopsis": "#include <sys/syscall.h>\n#include <unistd.h>\n\nint syscall(SYS_pidfd_getfd, int pidfd, int targetfd,\n            unsigned int flags);",
      "since": "Linux 5.6",
      "glibc": "glibc 2.36",
      "flags": [
        {
          "name": "targetfd",
          "meaning": "The descriptor number in the target process to duplicate; the copy shares its open file description."
        },
        {
          "name": "flags",
          "meaning": "Reserved; must be 0, otherwise EINVAL."
        },
        {
          "name": "FD_CLOEXEC",
          "meaning": "Always set on the returned descriptor."
        },
        {
          "name": "PTRACE_MODE_ATTACH_REALCREDS",
          "meaning": "Permission check applied: you need ptrace-attach rights over the target."
        }
      ],
      "errors": [
        {
          "name": "EPERM",
          "meaning": "Caller lacks PTRACE_MODE_ATTACH_REALCREDS permission over the target process."
        },
        {
          "name": "EBADF",
          "meaning": "pidfd is not a PID fd, or targetfd is not open in the target process."
        },
        {
          "name": "ESRCH",
          "meaning": "The target process has terminated and been waited on."
        },
        {
          "name": "EINVAL",
          "meaning": "flags is not 0."
        },
        {
          "name": "EMFILE",
          "meaning": "Per-process open file descriptor limit reached."
        }
      ],
      "bsd": "none; on FreeBSD and OpenBSD the closest is passing the descriptor with SCM_RIGHTS over a unix(4) socket, which needs the owner's cooperation.",
      "man": "https://man7.org/linux/man-pages/man2/pidfd_getfd.2.html",
      "sources": [
        {
          "label": "pidfd_getfd(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/pidfd_getfd.2.html"
        },
        {
          "label": "unix(4), FreeBSD",
          "url": "https://man.freebsd.org/cgi/man.cgi?query=unix&sektion=4"
        },
        {
          "label": "unix(4), OpenBSD",
          "url": "https://man.openbsd.org/unix.4"
        },
        {
          "label": "glibc 2.36 NEWS (pidfd_open, pidfd_getfd, pidfd_send_signal added)",
          "url": "https://raw.githubusercontent.com/bminor/glibc/release/2.36/master/NEWS"
        }
      ],
      "take": "The /proc/<pid>/fd/N trick is a path lookup keyed on a PID, so it inherits every reuse race PIDs have. pidfd_getfd goes through the stable handle and drops a duplicate of the target's descriptor into your table, sharing offset and flags exactly like dup(2). Unlike SCM_RIGHTS, the other process doesn't get a vote, which is precisely why it's gated on a ptrace attach check. Supervisors and container runtimes want this. Everyone else should be mildly alarmed that it exists.",
      "example": "int pfd = syscall(SYS_pidfd_open, pid, 0);\nif (pfd < 0)\n    err(1, \"pidfd_open\");\n\n/* copy the target's fd 0; needs ptrace-attach rights over it */\nint fd = syscall(SYS_pidfd_getfd, pfd, 0, 0);\nif (fd < 0)\n    err(1, \"pidfd_getfd\");   /* EPERM, EBADF, ESRCH */\n\n/* fd shares the open file description; FD_CLOEXEC is set */\nclose(pfd);",
      "exampleLang": "c",
      "ref": "pidfd_getfd(2)",
      "url": "https://rodhat.com/syscalls/pidfd_getfd/",
      "sinceParsed": {
        "kind": "linux",
        "min": [
          5,
          6,
          0
        ]
      },
      "tips": [
        "https://rodhat.com/tips/2026-08-26-pidfd-process-file-descriptors/"
      ],
      "related": [
        "https://rodhat.com/syscalls/pidfd_open/",
        "https://rodhat.com/syscalls/pidfd_send_signal/",
        "https://rodhat.com/syscalls/scm-rights/",
        "https://rodhat.com/syscalls/memfd_create/"
      ]
    },
    {
      "slug": "memfd_create",
      "name": "memfd_create",
      "section": "2",
      "os": "linux",
      "category": "memory",
      "summary": "Create an anonymous RAM-backed file with no path, optionally sealable.",
      "synopsis": "#define _GNU_SOURCE\n#include <sys/mman.h>\n\nint memfd_create(const char *name, unsigned int flags);",
      "since": "Linux 3.17",
      "glibc": "glibc 2.27",
      "flags": [
        {
          "name": "MFD_CLOEXEC",
          "meaning": "Set close-on-exec on the new descriptor."
        },
        {
          "name": "MFD_ALLOW_SEALING",
          "meaning": "Allow fcntl(F_ADD_SEALS). Without it the file starts with F_SEAL_SEAL and can never be sealed."
        },
        {
          "name": "MFD_HUGETLB",
          "since": "Linux 4.14",
          "meaning": "Back the file with hugetlbfs huge pages; pick size with MFD_HUGE_2MB, MFD_HUGE_1GB, etc.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              4,
              14,
              0
            ]
          }
        },
        {
          "name": "F_SEAL_SHRINK / F_SEAL_GROW",
          "meaning": "fcntl(2) seals that lock the file size against shrinking or growing."
        },
        {
          "name": "F_SEAL_WRITE",
          "meaning": "fcntl(2) seal blocking writes; can't be added while writable shared mappings exist."
        },
        {
          "name": "F_SEAL_FUTURE_WRITE",
          "meaning": "fcntl(2) seal blocking future writes while allowing existing writable mappings to remain."
        },
        {
          "name": "F_SEAL_SEAL",
          "meaning": "fcntl(2) seal preventing any further seals from being added."
        }
      ],
      "errors": [
        {
          "name": "EINVAL",
          "meaning": "flags contained unknown bits, or name exceeded 249 bytes."
        },
        {
          "name": "EFAULT",
          "meaning": "name points to invalid memory."
        },
        {
          "name": "EMFILE",
          "meaning": "Per-process open file descriptor limit reached."
        },
        {
          "name": "ENOMEM",
          "meaning": "Insufficient memory to create the anonymous file."
        },
        {
          "name": "EPERM",
          "meaning": "MFD_HUGETLB requested without CAP_IPC_LOCK or hugetlb_shm_group membership."
        }
      ],
      "bsd": "FreeBSD: memfd_create(2) (FreeBSD 13.0), an SHM_ANON shared memory object that supports MFD_ALLOW_SEALING. OpenBSD: none.",
      "man": "https://man7.org/linux/man-pages/man2/memfd_create.2.html",
      "sources": [
        {
          "label": "memfd_create(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/memfd_create.2.html"
        },
        {
          "label": "shm_open(2), FreeBSD",
          "url": "https://man.freebsd.org/cgi/man.cgi?query=shm_open&sektion=2"
        }
      ],
      "take": "Every older way to hand a blob to another process has a name somebody can race: a temp file, a /dev/shm object you hoped you unlinked in time. memfd_create has no name. It's an fd backed by anonymous memory, and the only way in is the fd. Create it with MFD_ALLOW_SEALING, fill it, slap on SHRINK, GROW, WRITE and SEAL, and nobody holding a copy can change a byte, you included. Forget the flag at creation and you can't retrofit it. In the kernel since 3.17 and still mostly a Wayland secret.",
      "example": "int fd = memfd_create(\"config\", MFD_CLOEXEC | MFD_ALLOW_SEALING);\nif (fd < 0)\n    err(1, \"memfd_create\");\nif (ftruncate(fd, len) < 0 || write(fd, data, len) != (ssize_t)len)\n    err(1, \"fill memfd\");\n\nif (fcntl(fd, F_ADD_SEALS,\n          F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE | F_SEAL_SEAL) < 0)\n    err(1, \"F_ADD_SEALS\");\n/* immutable now: hand it over with SCM_RIGHTS */",
      "exampleLang": "c",
      "ref": "memfd_create(2)",
      "url": "https://rodhat.com/syscalls/memfd_create/",
      "sinceParsed": {
        "kind": "linux",
        "min": [
          3,
          17,
          0
        ]
      },
      "tips": [
        "https://rodhat.com/tips/2026-08-27-memfd-create-sealed-memory/"
      ],
      "related": [
        "https://rodhat.com/syscalls/memfd_secret/",
        "https://rodhat.com/syscalls/scm-rights/",
        "https://rodhat.com/syscalls/pidfd_getfd/",
        "https://rodhat.com/syscalls/splice/"
      ]
    },
    {
      "slug": "memfd_secret",
      "name": "memfd_secret",
      "section": "2",
      "os": "linux",
      "category": "memory",
      "summary": "Create memory removed from the kernel's direct map, mapped only by fd holders.",
      "synopsis": "#include <sys/syscall.h>\n#include <unistd.h>\n\nint syscall(SYS_memfd_secret, unsigned int flags);",
      "since": "Linux 5.14",
      "glibc": "none (use syscall(2))",
      "flags": [
        {
          "name": "O_CLOEXEC",
          "meaning": "The only accepted flag: close-on-exec. The man page says FD_CLOEXEC; mm/secretmem.c checks O_CLOEXEC, and FD_CLOEXEC gets EINVAL."
        },
        {
          "name": "secretmem.enable=y",
          "meaning": "Before Linux 6.5 the call was disabled by default unless the kernel was booted with this parameter."
        },
        {
          "name": "RLIMIT_MEMLOCK",
          "meaning": "Mappings are locked like mlock(2) and count against RLIMIT_MEMLOCK; pages never swap."
        }
      ],
      "errors": [
        {
          "name": "ENOSYS",
          "meaning": "Not implemented on this architecture, or not enabled on the kernel command line."
        },
        {
          "name": "EINVAL",
          "meaning": "flags included unknown bits."
        },
        {
          "name": "EMFILE",
          "meaning": "Per-process open file descriptor limit reached."
        },
        {
          "name": "ENOMEM",
          "meaning": "Insufficient memory to create the anonymous file."
        }
      ],
      "bsd": "none",
      "man": "https://man7.org/linux/man-pages/man2/memfd_secret.2.html",
      "sources": [
        {
          "label": "mm/secretmem.c (flag mask is O_CLOEXEC), torvalds/linux",
          "url": "https://github.com/torvalds/linux/blob/master/mm/secretmem.c"
        },
        {
          "label": "memfd_secret(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/memfd_secret.2.html"
        }
      ],
      "take": "mlock keeps a secret out of swap, but the kernel can still read every byte through its direct map. memfd_secret pulls the pages out of the direct map, so only the processes holding the fd have them mapped at all. The kernel can't reach them either, so don't bother passing pointers into syscalls. The price is chopping up the kernel's huge-page direct map, and that TLB tax shows up on busy servers, so benchmark before you sprinkle it around. Wipe it with explicit_bzero, not memset, or the compiler will helpfully delete your wipe.",
      "example": "int fd = syscall(SYS_memfd_secret, 0);\nif (fd < 0)\n    err(1, \"memfd_secret\");   /* ENOSYS: not enabled */\nif (ftruncate(fd, size) < 0)\n    err(1, \"ftruncate\");\n\nvoid *p = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);\nclose(fd);                      /* the mapping keeps the pages */\nif (p == MAP_FAILED)\n    err(1, \"mmap\");\n/* ... use the key ... */\nexplicit_bzero(p, size);\nmunmap(p, size);",
      "exampleLang": "c",
      "ref": "memfd_secret(2)",
      "url": "https://rodhat.com/syscalls/memfd_secret/",
      "sinceParsed": {
        "kind": "linux",
        "min": [
          5,
          14,
          0
        ]
      },
      "tips": [
        "https://rodhat.com/tips/2026-09-18-memfd-secret-kernel-protected-memory/"
      ],
      "related": [
        "https://rodhat.com/syscalls/memfd_create/",
        "https://rodhat.com/syscalls/mlock/",
        "https://rodhat.com/syscalls/madvise/",
        "https://rodhat.com/syscalls/prctl/"
      ]
    },
    {
      "slug": "io_uring_setup",
      "name": "io_uring_setup",
      "section": "2",
      "os": "linux",
      "category": "io",
      "summary": "Create an io_uring instance: shared submission/completion rings for async I/O.",
      "synopsis": "#include <liburing.h>\n\nint io_uring_setup(u32 entries, struct io_uring_params *params);",
      "since": "Linux 5.1",
      "glibc": "none (use syscall(2))",
      "flags": [
        {
          "name": "IORING_SETUP_IOPOLL",
          "meaning": "Busy-poll for completions instead of IRQs; storage only, typically O_DIRECT on a polling-capable device."
        },
        {
          "name": "IORING_SETUP_SQPOLL",
          "meaning": "A kernel thread polls the SQ ring so submission needs no syscall; it parks after sq_thread_idle ms."
        },
        {
          "name": "IORING_SETUP_SQ_AFF",
          "meaning": "Pin the SQPOLL thread to sq_thread_cpu. Only meaningful with IORING_SETUP_SQPOLL."
        },
        {
          "name": "IORING_SETUP_CQSIZE",
          "meaning": "Size the completion queue from params.cq_entries instead of the default."
        },
        {
          "name": "IORING_SETUP_R_DISABLED",
          "since": "Linux 5.10",
          "meaning": "Start the ring disabled so restrictions can be registered before submissions are allowed.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              5,
              10,
              0
            ]
          }
        },
        {
          "name": "IORING_SETUP_SUBMIT_ALL",
          "since": "Linux 5.18",
          "meaning": "Keep submitting the batch even if one request errors during submission.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              5,
              18,
              0
            ]
          }
        },
        {
          "name": "IORING_SETUP_COOP_TASKRUN",
          "since": "Linux 5.19",
          "meaning": "Don't interrupt the task with IPIs on completion; process at the next kernel transition.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              5,
              19,
              0
            ]
          }
        },
        {
          "name": "IORING_SETUP_SINGLE_ISSUER",
          "since": "Linux 6.0",
          "meaning": "Only one task may submit; violations fail with -EEXIST.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              6,
              0,
              0
            ]
          }
        },
        {
          "name": "IORING_SETUP_DEFER_TASKRUN",
          "since": "Linux 6.1",
          "meaning": "Defer completion work until io_uring_enter(2) with GETEVENTS. Requires SINGLE_ISSUER.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              6,
              1,
              0
            ]
          }
        },
        {
          "name": "IORING_SETUP_NO_MMAP",
          "since": "Linux 6.5",
          "meaning": "Use caller-allocated memory for the rings instead of mmap(2)-ing kernel memory.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              6,
              5,
              0
            ]
          }
        }
      ],
      "errors": [
        {
          "name": "EPERM",
          "meaning": "io_uring_disabled is 2, or 1 and caller lacks CAP_SYS_ADMIN and isn't in io_uring_group."
        },
        {
          "name": "EPERM",
          "meaning": "IORING_SETUP_SQPOLL requested without sufficient privileges."
        },
        {
          "name": "EINVAL",
          "meaning": "Non-zero resv, unsupported flag, entries out of bounds, or inconsistent flag combination."
        },
        {
          "name": "ENOMEM",
          "meaning": "Insufficient kernel resources."
        },
        {
          "name": "ENXIO",
          "meaning": "IORING_SETUP_ATTACH_WQ given but wq_fd isn't a live io_uring instance."
        }
      ],
      "bsd": "FreeBSD: aio(4) (kernel option since FreeBSD 3.0, in all kernels since FreeBSD 11.0), with completions deliverable through kqueue(2) EVFILT_AIO. OpenBSD: none.",
      "man": "https://man7.org/linux/man-pages/man2/io_uring_setup.2.html",
      "sources": [
        {
          "label": "io_uring_setup(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/io_uring_setup.2.html"
        },
        {
          "label": "io_uring(7), man7.org",
          "url": "https://man7.org/linux/man-pages/man7/io_uring.7.html"
        },
        {
          "label": "Linux 5.1, kernelnewbies.org",
          "url": "https://kernelnewbies.org/Linux_5.1"
        },
        {
          "label": "aio(4), FreeBSD",
          "url": "https://man.freebsd.org/cgi/man.cgi?query=aio&sektion=4"
        },
        {
          "label": "kqueue(2), FreeBSD",
          "url": "https://man.freebsd.org/cgi/man.cgi?query=kqueue&sektion=2"
        }
      ],
      "take": "Every older async I/O interface on Linux is broken somehow: aio(7) really wants O_DIRECT, and POSIX AIO is a thread pool in a trench coat. io_uring is two rings in shared memory, and with SQPOLL you submit without a syscall at all. The design is right. The implementation has also coughed up a pile of kernel escapes, and a seccomp filter only ever sees io_uring_enter, never what's in the ring. Use it on trusted servers you control. For everything else, turn it off with io_uring_disabled or restrict the ring before the fd leaves your hands.",
      "example": "struct io_uring ring;\nint ret = io_uring_queue_init(32, &ring, 0);   /* io_uring_setup(2) */\nif (ret < 0)\n    errx(1, \"io_uring_queue_init: %s\", strerror(-ret));\n\nstruct io_uring_sqe *sqe = io_uring_get_sqe(&ring);\nio_uring_prep_read(sqe, fd, buf, sizeof(buf), 0);\nio_uring_submit(&ring);\n\nstruct io_uring_cqe *cqe;\nif (io_uring_wait_cqe(&ring, &cqe) == 0) {\n    printf(\"res = %d\\n\", cqe->res);           /* bytes or -errno */\n    io_uring_cqe_seen(&ring, cqe);\n}\nio_uring_queue_exit(&ring);",
      "exampleLang": "c",
      "ref": "io_uring_setup(2)",
      "url": "https://rodhat.com/syscalls/io_uring_setup/",
      "sinceParsed": {
        "kind": "linux",
        "min": [
          5,
          1,
          0
        ]
      },
      "tips": [
        "https://rodhat.com/tips/2026-08-25-io-uring-zero-syscall-io/",
        "https://rodhat.com/tips/2026-09-23-io-uring-attack-surface-and-restrictions/"
      ],
      "related": [
        "https://rodhat.com/syscalls/seccomp/",
        "https://rodhat.com/syscalls/splice/",
        "https://rodhat.com/syscalls/eventfd/",
        "https://rodhat.com/syscalls/userfaultfd/"
      ]
    },
    {
      "slug": "landlock_create_ruleset",
      "name": "landlock_create_ruleset",
      "section": "2",
      "os": "linux",
      "category": "sandboxing",
      "summary": "Create a Landlock ruleset fd, step one of unprivileged filesystem/network sandboxing.",
      "synopsis": "#include <linux/landlock.h>\n#include <sys/syscall.h>\n#include <unistd.h>\n\nint syscall(SYS_landlock_create_ruleset,\n            const struct landlock_ruleset_attr *attr,\n            size_t size, uint32_t flags);",
      "since": "Linux 5.13",
      "glibc": "none (use syscall(2))",
      "flags": [
        {
          "name": "LANDLOCK_CREATE_RULESET_VERSION",
          "meaning": "With attr NULL and size 0, return the highest supported Landlock ABI version (starting at 1)."
        },
        {
          "name": "LANDLOCK_CREATE_RULESET_ERRATA",
          "meaning": "With attr NULL and size 0, return a bitmask of Landlock errata fixed in the running kernel."
        },
        {
          "name": "handled_access_fs",
          "meaning": "Filesystem rights this ruleset handles. Rights not listed here are not restricted at all."
        },
        {
          "name": "handled_access_net",
          "meaning": "Network rights (TCP bind/connect) this ruleset handles."
        },
        {
          "name": "scoped",
          "meaning": "Scope flags restricting IPC (abstract UNIX sockets, signals) outside the Landlock domain."
        },
        {
          "name": "LANDLOCK_ACCESS_FS_REFER",
          "meaning": "Cross-directory rename/link right; always denied by default, even if not set in handled_access_fs."
        }
      ],
      "errors": [
        {
          "name": "EOPNOTSUPP",
          "meaning": "Landlock is supported by the kernel but disabled at boot."
        },
        {
          "name": "EINVAL",
          "meaning": "Unknown flags, access right, or scope, or size too small."
        },
        {
          "name": "ENOMSG",
          "meaning": "attr specifies no access rights to restrict."
        },
        {
          "name": "E2BIG",
          "meaning": "size is too big."
        },
        {
          "name": "EFAULT",
          "meaning": "attr is not a valid address."
        }
      ],
      "bsd": "OpenBSD: unveil(2) (OpenBSD 6.4). FreeBSD: Capsicum capability mode via cap_enter(2).",
      "man": "https://man7.org/linux/man-pages/man2/landlock_create_ruleset.2.html",
      "sources": [
        {
          "label": "landlock_create_ruleset(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/landlock_create_ruleset.2.html"
        },
        {
          "label": "unveil(2), OpenBSD",
          "url": "https://man.openbsd.org/unveil.2"
        },
        {
          "label": "cap_enter(2), FreeBSD",
          "url": "https://man.freebsd.org/cgi/man.cgi?query=cap_enter&sektion=2"
        }
      ],
      "take": "Before Landlock, path-based sandboxing on Linux meant root, a MAC policy some other team wrote, or chroot pretending to be security. Now an unprivileged process builds a ruleset, adds paths, and locks itself in for good. The trap: a right you leave out of handled_access_fs isn't restricted at all, so a hardcoded ABI-1 mask on a newer kernel leaves truncate wide open and never says a word. Ask for the ABI version, handle everything the kernel knows, grant back what you need. unveil does it in four lines. Landlock still got the architecture right.",
      "example": "int abi = syscall(SYS_landlock_create_ruleset, NULL, 0,\n                  LANDLOCK_CREATE_RULESET_VERSION);\nif (abi < 0)\n    err(1, \"landlock unavailable\");   /* ENOSYS or EOPNOTSUPP */\n\nstruct landlock_ruleset_attr attr = {\n    .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE |\n                         LANDLOCK_ACCESS_FS_READ_DIR  |\n                         LANDLOCK_ACCESS_FS_WRITE_FILE,\n};\nint rfd = syscall(SYS_landlock_create_ruleset, &attr, sizeof(attr), 0);\nif (rfd < 0)\n    err(1, \"landlock_create_ruleset\");\n/* landlock_add_rule(2), PR_SET_NO_NEW_PRIVS, landlock_restrict_self(2) */",
      "exampleLang": "c",
      "ref": "landlock_create_ruleset(2)",
      "url": "https://rodhat.com/syscalls/landlock_create_ruleset/",
      "sinceParsed": {
        "kind": "linux",
        "min": [
          5,
          13,
          0
        ]
      },
      "tips": [
        "https://rodhat.com/tips/2026-08-29-landlock-unprivileged-sandbox/"
      ],
      "related": [
        "https://rodhat.com/syscalls/seccomp/",
        "https://rodhat.com/syscalls/openat2/",
        "https://rodhat.com/syscalls/unveil/",
        "https://rodhat.com/syscalls/pledge/",
        "https://rodhat.com/syscalls/prctl/"
      ]
    },
    {
      "slug": "seccomp",
      "name": "seccomp",
      "section": "2",
      "os": "linux",
      "category": "sandboxing",
      "summary": "Set the thread's seccomp state: strict mode or a BPF filter on every syscall.",
      "synopsis": "#include <linux/seccomp.h>\n#include <linux/filter.h>\n#include <linux/audit.h>\n#include <sys/syscall.h>\n#include <unistd.h>\n\nint syscall(SYS_seccomp, unsigned int operation, unsigned int flags,\n            void *args);",
      "since": "Linux 3.17",
      "glibc": "none (use syscall(2))",
      "flags": [
        {
          "name": "SECCOMP_SET_MODE_STRICT",
          "meaning": "Allow only read(2), write(2), _exit(2) and sigreturn(2); anything else kills the caller."
        },
        {
          "name": "SECCOMP_SET_MODE_FILTER",
          "meaning": "Install a classic BPF program (struct sock_fprog) run on every syscall. Needs no_new_privs or CAP_SYS_ADMIN."
        },
        {
          "name": "SECCOMP_FILTER_FLAG_TSYNC",
          "meaning": "Synchronize all threads of the process to the new filter tree."
        },
        {
          "name": "SECCOMP_FILTER_FLAG_LOG",
          "since": "Linux 4.14",
          "meaning": "Log all filter actions except SECCOMP_RET_ALLOW.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              4,
              14,
              0
            ]
          }
        },
        {
          "name": "SECCOMP_FILTER_FLAG_NEW_LISTENER",
          "since": "Linux 5.0",
          "meaning": "Return a user-space notification fd for SECCOMP_RET_USER_NOTIF.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              5,
              0,
              0
            ]
          }
        },
        {
          "name": "SECCOMP_GET_ACTION_AVAIL",
          "since": "Linux 4.14",
          "meaning": "Test whether the kernel supports a given filter return action.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              4,
              14,
              0
            ]
          }
        },
        {
          "name": "SECCOMP_RET_KILL_PROCESS",
          "since": "Linux 4.14",
          "meaning": "Filter action: kill the whole process with a core dump; the syscall is not executed.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              4,
              14,
              0
            ]
          }
        },
        {
          "name": "SECCOMP_RET_ERRNO",
          "meaning": "Filter action: skip the syscall and return the low 16 data bits as errno."
        },
        {
          "name": "SECCOMP_RET_USER_NOTIF",
          "since": "Linux 5.0",
          "meaning": "Filter action: hand the decision to a user-space supervisor via the listener fd.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              5,
              0,
              0
            ]
          }
        },
        {
          "name": "SECCOMP_RET_LOG",
          "since": "Linux 4.14",
          "meaning": "Filter action: allow the syscall but log it.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              4,
              14,
              0
            ]
          }
        }
      ],
      "errors": [
        {
          "name": "EACCES",
          "meaning": "Caller lacks CAP_SYS_ADMIN and hasn't set no_new_privs before SECCOMP_SET_MODE_FILTER."
        },
        {
          "name": "EINVAL",
          "meaning": "Unknown operation, invalid flags, or an invalid/empty/too-long (>4096 insns) filter."
        },
        {
          "name": "EFAULT",
          "meaning": "args was not a valid address."
        },
        {
          "name": "EBUSY",
          "meaning": "NEW_LISTENER requested but a filter with a listener is already installed."
        },
        {
          "name": "ENOMEM",
          "meaning": "Out of memory, or total filter length on the thread exceeds 32768 instructions."
        }
      ],
      "bsd": "OpenBSD: pledge(2) (OpenBSD 5.9), coarse promise strings rather than BPF. FreeBSD: Capsicum capability mode via cap_enter(2).",
      "man": "https://man7.org/linux/man-pages/man2/seccomp.2.html",
      "sources": [
        {
          "label": "seccomp(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/seccomp.2.html"
        },
        {
          "label": "pledge(2), OpenBSD",
          "url": "https://man.openbsd.org/pledge.2"
        },
        {
          "label": "cap_enter(2), FreeBSD",
          "url": "https://man.freebsd.org/cgi/man.cgi?query=cap_enter&sektion=2"
        }
      ],
      "take": "A typical daemon uses forty-odd syscalls and leaves a few hundred more loaded and pointed at whoever pops your input parser. seccomp is how you unload them. Set no_new_privs first or the load fails with EACCES and you're running naked while thinking you're armored, so check the return. Check arch before the syscall number or the 32-bit ABI walks right past you. The filter compares integers; it can't chase pointers, so paths are Landlock's problem. pledge does the coarse version in one line. This is the two-day precise version, and for untrusted code it earns it.",
      "example": "prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);\n\nscmp_filter_ctx ctx = seccomp_init(SCMP_ACT_ERRNO(EPERM));\nif (!ctx)\n    errx(1, \"seccomp_init\");\nseccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);\nseccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);\nseccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(close), 0);\nseccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);\n\nif (seccomp_load(ctx) < 0)      /* libseccomp calls seccomp(2) */\n    errx(1, \"seccomp_load\");\nseccomp_release(ctx);",
      "exampleLang": "c",
      "ref": "seccomp(2)",
      "url": "https://rodhat.com/syscalls/seccomp/",
      "sinceParsed": {
        "kind": "linux",
        "min": [
          3,
          17,
          0
        ]
      },
      "tips": [
        "https://rodhat.com/tips/2026-08-24-seccomp-bpf-syscall-filter/"
      ],
      "related": [
        "https://rodhat.com/syscalls/landlock_create_ruleset/",
        "https://rodhat.com/syscalls/prctl/",
        "https://rodhat.com/syscalls/pledge/",
        "https://rodhat.com/syscalls/io_uring_setup/",
        "https://rodhat.com/syscalls/capset/"
      ]
    },
    {
      "slug": "userfaultfd",
      "name": "userfaultfd",
      "section": "2",
      "os": "linux",
      "category": "memory",
      "summary": "Create an fd that lets userspace resolve page faults on registered memory ranges.",
      "synopsis": "#include <fcntl.h>\n#include <sys/syscall.h>\n#include <linux/userfaultfd.h>\n#include <unistd.h>\n\nint syscall(SYS_userfaultfd, int flags);",
      "since": "Linux 4.3",
      "glibc": "none (use syscall(2))",
      "flags": [
        {
          "name": "O_CLOEXEC",
          "meaning": "Set close-on-exec on the new userfaultfd."
        },
        {
          "name": "O_NONBLOCK",
          "meaning": "Non-blocking reads; use poll(2)/epoll(7) to wait for events."
        },
        {
          "name": "UFFD_USER_MODE_ONLY",
          "since": "Linux 5.11",
          "meaning": "Only handle faults originating in user space; kernel-originated faults get SIGBUS.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              5,
              11,
              0
            ]
          }
        },
        {
          "name": "UFFDIO_REGISTER_MODE_MISSING",
          "since": "Linux 4.10",
          "meaning": "Notify on access to a missing page; resolve with UFFDIO_COPY or UFFDIO_ZEROPAGE.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              4,
              10,
              0
            ]
          }
        },
        {
          "name": "UFFDIO_REGISTER_MODE_WP",
          "since": "Linux 5.7",
          "meaning": "Notify on writes to write-protected pages; resolve with UFFDIO_WRITEPROTECT.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              5,
              7,
              0
            ]
          }
        },
        {
          "name": "UFFDIO_REGISTER_MODE_MINOR",
          "since": "Linux 5.13",
          "meaning": "Notify on minor faults (page in page cache, no PTE); resolve with UFFDIO_CONTINUE.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              5,
              13,
              0
            ]
          }
        },
        {
          "name": "UFFDIO_REGISTER_MODE_RWP",
          "since": "Linux 7.2",
          "meaning": "Notify on any read or write to a present page; can't be combined with MODE_WP.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              7,
              2,
              0
            ]
          }
        },
        {
          "name": "UFFD_EVENT_FORK / REMAP / REMOVE / UNMAP",
          "since": "Linux 4.11",
          "meaning": "Non-page-fault events about VM layout changes, enabled via UFFDIO_API features.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              4,
              11,
              0
            ]
          }
        }
      ],
      "errors": [
        {
          "name": "EPERM",
          "meaning": "Since Linux 5.2: no CAP_SYS_PTRACE in the initial user ns and vm.unprivileged_userfaultfd is 0."
        },
        {
          "name": "EINVAL",
          "meaning": "An unsupported value was specified in flags."
        },
        {
          "name": "EMFILE",
          "meaning": "Per-process open file descriptor limit reached."
        },
        {
          "name": "ENOMEM",
          "meaning": "Insufficient kernel memory."
        }
      ],
      "bsd": "none",
      "man": "https://man7.org/linux/man-pages/man2/userfaultfd.2.html",
      "sources": [
        {
          "label": "userfaultfd(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/userfaultfd.2.html"
        }
      ],
      "take": "Normally a touch on a missing page gets you SIGSEGV and a funeral. userfaultfd says hold on, I'll supply that page myself: the faulting thread stalls, a handler reads the event off the fd, fills the page with UFFDIO_COPY, and the thread carries on none the wiser. That's the trick under post-copy migration and lazy restore. The rule that bites everyone once: the handler must never fault on the region it's serving, or you've built a very elegant deadlock. Unprivileged use hinges on vm.unprivileged_userfaultfd, so check before you promise anybody anything.",
      "example": "int uffd = syscall(SYS_userfaultfd, O_CLOEXEC | O_NONBLOCK);\nif (uffd < 0)\n    err(1, \"userfaultfd\");  /* EPERM: vm.unprivileged_userfaultfd */\n\nstruct uffdio_api api = { .api = UFFD_API, .features = 0 };\nif (ioctl(uffd, UFFDIO_API, &api) < 0)\n    err(1, \"UFFDIO_API\");\n\nstruct uffdio_register reg = {\n    .range = { .start = (uintptr_t)region, .len = len },\n    .mode  = UFFDIO_REGISTER_MODE_MISSING,\n};\nif (ioctl(uffd, UFFDIO_REGISTER, &reg) < 0)\n    err(1, \"UFFDIO_REGISTER\");\n/* handler thread: poll(), read struct uffd_msg, UFFDIO_COPY */",
      "exampleLang": "c",
      "ref": "userfaultfd(2)",
      "url": "https://rodhat.com/syscalls/userfaultfd/",
      "sinceParsed": {
        "kind": "linux",
        "min": [
          4,
          3,
          0
        ]
      },
      "tips": [
        "https://rodhat.com/tips/2026-08-28-userfaultfd-userspace-page-faults/"
      ],
      "related": [
        "https://rodhat.com/syscalls/memfd_create/",
        "https://rodhat.com/syscalls/madvise/",
        "https://rodhat.com/syscalls/process_madvise/",
        "https://rodhat.com/syscalls/mlock/"
      ]
    },
    {
      "slug": "close_range",
      "name": "close_range",
      "section": "2",
      "os": "linux",
      "category": "file-descriptors",
      "summary": "Close, or mark close-on-exec, every file descriptor in a range in one call.",
      "synopsis": "#define _GNU_SOURCE\n#include <unistd.h>\n#include <linux/close_range.h>\n\nint close_range(unsigned int first, unsigned int last, int flags);",
      "since": "Linux 5.9",
      "glibc": "glibc 2.34",
      "flags": [
        {
          "name": "CLOSE_RANGE_UNSHARE",
          "meaning": "Unshare the fd table first, so closing doesn't race other threads sharing it."
        },
        {
          "name": "CLOSE_RANGE_CLOEXEC",
          "since": "Linux 5.11",
          "meaning": "Set close-on-exec on the range instead of closing the descriptors.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              5,
              11,
              0
            ]
          }
        },
        {
          "name": "last = ~0U",
          "meaning": "Pass UINT_MAX as last to cover everything from first upward."
        }
      ],
      "errors": [
        {
          "name": "EINVAL",
          "meaning": "flags is not valid, or first is greater than last."
        },
        {
          "name": "EMFILE",
          "meaning": "With CLOSE_RANGE_UNSHARE: open fds exceed /proc/sys/fs/nr_open when building the new table."
        },
        {
          "name": "ENOMEM",
          "meaning": "With CLOSE_RANGE_UNSHARE: insufficient kernel memory for the new table."
        }
      ],
      "bsd": "FreeBSD: close_range(2) with CLOSE_RANGE_CLOEXEC (FreeBSD 12.2) and closefrom(2) (FreeBSD 8.0). OpenBSD: closefrom(2) (OpenBSD 3.5).",
      "man": "https://man7.org/linux/man-pages/man2/close_range.2.html",
      "sources": [
        {
          "label": "close_range(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/close_range.2.html"
        },
        {
          "label": "closefrom(2), FreeBSD",
          "url": "https://man.freebsd.org/cgi/man.cgi?query=closefrom&sektion=2"
        },
        {
          "label": "closefrom(2), OpenBSD",
          "url": "https://man.openbsd.org/closefrom.2"
        }
      ],
      "take": "The pre-exec loop that calls close() on every number up to _SC_OPEN_MAX is a million syscalls returning EBADF, and it's sitting in code you depend on right now. close_range(3, ~0U, 0) does it in one. CLOSE_RANGE_UNSHARE does the work on a private copy of the fd table, so sibling threads don't get their descriptors yanked mid-use. CLOSE_RANGE_CLOEXEC is the clever one: leave everything open here, guarantee none of it leaks across exec. The BSDs had closefrom for years. Linux showed up late, but it brought flags.",
      "example": "/* in the child, right before exec: nothing above stderr survives */\nif (close_range(3, ~0U, CLOSE_RANGE_UNSHARE) < 0)\n    err(1, \"close_range\");\nexecvp(argv[0], argv);\n\n/* or: keep fds open here, but stop them leaking into children */\nclose_range(3, ~0U, CLOSE_RANGE_CLOEXEC);\ndup2(pipe_wr, 3);   /* dup2 clears FD_CLOEXEC on fd 3 */",
      "exampleLang": "c",
      "ref": "close_range(2)",
      "url": "https://rodhat.com/syscalls/close_range/",
      "sinceParsed": {
        "kind": "linux",
        "min": [
          5,
          9,
          0
        ]
      },
      "tips": [
        "https://rodhat.com/tips/2026-08-31-close-range-atomic-fd-cleanup/"
      ],
      "related": [
        "https://rodhat.com/syscalls/openat2/",
        "https://rodhat.com/syscalls/pidfd_getfd/",
        "https://rodhat.com/syscalls/scm-rights/",
        "https://rodhat.com/syscalls/memfd_create/"
      ]
    },
    {
      "slug": "splice",
      "name": "splice",
      "section": "2",
      "os": "linux",
      "category": "io",
      "summary": "Move data between a pipe and another fd without copying through userspace.",
      "synopsis": "#define _GNU_SOURCE\n#define _FILE_OFFSET_BITS 64\n#include <fcntl.h>\n\nssize_t splice(int fd_in, off_t *_Nullable off_in,\n               int fd_out, off_t *_Nullable off_out,\n               size_t size, unsigned int flags);",
      "since": "Linux 2.6.17",
      "glibc": "glibc 2.5",
      "flags": [
        {
          "name": "SPLICE_F_MOVE",
          "meaning": "Hint to move pages instead of copying. A no-op since Linux 2.6.21, but still permitted."
        },
        {
          "name": "SPLICE_F_NONBLOCK",
          "meaning": "Make the pipe side nonblocking; the other fd may still block unless it has O_NONBLOCK."
        },
        {
          "name": "SPLICE_F_MORE",
          "meaning": "More data coming; useful when fd_out is a socket (like MSG_MORE / TCP_CORK)."
        },
        {
          "name": "SPLICE_F_GIFT",
          "meaning": "Unused for splice(); see vmsplice(2)."
        }
      ],
      "errors": [
        {
          "name": "EINVAL",
          "meaning": "Neither fd is a pipe, target is append-mode or doesn't support splicing, or both ends are the same pipe."
        },
        {
          "name": "ESPIPE",
          "meaning": "off_in or off_out was non-NULL but the corresponding fd is a pipe."
        },
        {
          "name": "EAGAIN",
          "meaning": "SPLICE_F_NONBLOCK or O_NONBLOCK set and the operation would block."
        },
        {
          "name": "EBADF",
          "meaning": "A file descriptor is invalid or lacks the needed read/write mode."
        },
        {
          "name": "ENOMEM",
          "meaning": "Out of memory."
        }
      ],
      "bsd": "FreeBSD: sendfile(2) for file-to-socket (FreeBSD 3.0), and the SO_SPLICE socket option for socket-to-socket (FreeBSD 14.3). OpenBSD: SO_SPLICE via setsockopt(2) (OpenBSD 4.9).",
      "man": "https://man7.org/linux/man-pages/man2/splice.2.html",
      "sources": [
        {
          "label": "splice(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/splice.2.html"
        },
        {
          "label": "sendfile(2), FreeBSD",
          "url": "https://man.freebsd.org/cgi/man.cgi?query=sendfile&sektion=2"
        },
        {
          "label": "getsockopt(2), FreeBSD",
          "url": "https://man.freebsd.org/cgi/man.cgi?query=getsockopt&sektion=2"
        },
        {
          "label": "getsockopt(2), OpenBSD",
          "url": "https://man.openbsd.org/getsockopt.2"
        }
      ],
      "take": "read() into a buffer, write() it back out, and you've hauled page-cache data through your address space twice to accomplish nothing. splice moves page references instead of bytes. The catch, every time, no exceptions: one end must be a pipe, because the pipe is the kernel's staging buffer. File to socket is two splices with a pipe in the middle. If it really is just file to socket, sendfile is simpler; use it. Loop on short returns like any write. Twenty years in the kernel and people are still copying by hand.",
      "example": "int p[2];\nif (pipe(p) < 0)\n    err(1, \"pipe\");\nwhile (left > 0) {\n    ssize_t n = splice(file_fd, &off, p[1], NULL, left, SPLICE_F_MORE);\n    if (n <= 0)\n        break;\n    for (ssize_t m = n; m > 0; ) {\n        ssize_t s = splice(p[0], NULL, sock_fd, NULL, m, SPLICE_F_MORE);\n        if (s <= 0)\n            err(1, \"splice to socket\");\n        m -= s;\n    }\n    left -= n;\n}",
      "exampleLang": "c",
      "ref": "splice(2)",
      "url": "https://rodhat.com/syscalls/splice/",
      "sinceParsed": {
        "kind": "linux",
        "min": [
          2,
          6,
          17
        ]
      },
      "tips": [
        "https://rodhat.com/tips/2026-09-01-splice-tee-zero-copy-kernel-pipes/"
      ],
      "related": [
        "https://rodhat.com/syscalls/tee/",
        "https://rodhat.com/syscalls/io_uring_setup/",
        "https://rodhat.com/syscalls/memfd_create/",
        "https://rodhat.com/syscalls/so-reuseport/"
      ]
    },
    {
      "slug": "tee",
      "name": "tee",
      "section": "2",
      "os": "linux",
      "category": "io",
      "summary": "Duplicate data from one pipe into another without consuming it from the source.",
      "synopsis": "#define _GNU_SOURCE\n#include <fcntl.h>\n\nssize_t tee(int fd_in, int fd_out, size_t size, unsigned int flags);",
      "since": "Linux 2.6.17",
      "glibc": "glibc 2.5",
      "flags": [
        {
          "name": "SPLICE_F_NONBLOCK",
          "meaning": "Do not block on I/O; see splice(2)."
        },
        {
          "name": "SPLICE_F_MOVE",
          "meaning": "Currently has no effect for tee()."
        },
        {
          "name": "SPLICE_F_MORE",
          "meaning": "Currently has no effect for tee(); may be implemented in future."
        },
        {
          "name": "SPLICE_F_GIFT",
          "meaning": "Unused for tee(); see vmsplice(2)."
        }
      ],
      "errors": [
        {
          "name": "EINVAL",
          "meaning": "fd_in or fd_out is not a pipe, or both refer to the same pipe."
        },
        {
          "name": "EAGAIN",
          "meaning": "SPLICE_F_NONBLOCK or O_NONBLOCK set and the operation would block."
        },
        {
          "name": "ENOMEM",
          "meaning": "Out of memory."
        }
      ],
      "bsd": "none",
      "man": "https://man7.org/linux/man-pages/man2/tee.2.html",
      "sources": [
        {
          "label": "tee(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/tee.2.html"
        },
        {
          "label": "splice(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/splice.2.html"
        }
      ],
      "take": "tee(1) reads stdin and writes it twice, through userspace both times. tee(2) does it inside the kernel: it duplicates what's sitting in one pipe into another without consuming the source, so a following splice can still drain the original. Both ends must be pipes. Log fanout is tee into a scratch pipe, splice the original to one place, splice the copy to the other, and nobody forks a shell pipeline. Only page pointers get duplicated, never the pages. Small, obscure, and exactly right for the job.",
      "example": "/* stdin and stdout are pipes; also copy everything to logfd */\nfor (;;) {\n    ssize_t n = tee(STDIN_FILENO, STDOUT_FILENO, INT_MAX, SPLICE_F_NONBLOCK);\n    if (n < 0 && errno == EAGAIN)\n        continue;               /* real code: poll() first */\n    if (n < 0)\n        err(1, \"tee\");\n    if (n == 0)\n        break;\n    while (n > 0) {             /* consume what tee duplicated */\n        ssize_t s = splice(STDIN_FILENO, NULL, logfd, NULL, n, 0);\n        if (s <= 0)\n            err(1, \"splice\");\n        n -= s;\n    }\n}",
      "exampleLang": "c",
      "ref": "tee(2)",
      "url": "https://rodhat.com/syscalls/tee/",
      "sinceParsed": {
        "kind": "linux",
        "min": [
          2,
          6,
          17
        ]
      },
      "tips": [
        "https://rodhat.com/tips/2026-09-01-splice-tee-zero-copy-kernel-pipes/"
      ],
      "related": [
        "https://rodhat.com/syscalls/splice/",
        "https://rodhat.com/syscalls/io_uring_setup/"
      ]
    },
    {
      "slug": "scm-rights",
      "name": "SCM_RIGHTS",
      "section": "unix(7)",
      "os": "linux",
      "category": "ipc",
      "summary": "Pass open file descriptors to another process over a UNIX domain socket.",
      "synopsis": "#include <sys/socket.h>\n#include <sys/un.h>\n\nunix_socket = socket(AF_UNIX, type, 0);\nssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);\n/* ancillary data: cmsg_level = SOL_SOCKET, cmsg_type = SCM_RIGHTS */",
      "since": "unknown",
      "glibc": "none (ancillary message for sendmsg(2)/recvmsg(2), not a syscall)",
      "flags": [
        {
          "name": "SCM_MAX_FD",
          "meaning": "Max fds per message: 253 (255 before Linux 2.6.38). Exceeding it makes sendmsg(2) fail with EINVAL."
        },
        {
          "name": "MSG_CMSG_CLOEXEC",
          "since": "Linux 2.6.23",
          "meaning": "recvmsg(2) flag: set close-on-exec on every received descriptor.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              2,
              6,
              23
            ]
          }
        },
        {
          "name": "MSG_CTRUNC",
          "meaning": "Set in msg_flags when control data was truncated; the excess fds were closed by the kernel."
        },
        {
          "name": "CMSG_SPACE / CMSG_LEN",
          "meaning": "Size the control buffer and cmsg_len for n descriptors: CMSG_SPACE(n * sizeof(int))."
        }
      ],
      "errors": [
        {
          "name": "EBADF",
          "meaning": "sendmsg(2): a descriptor being sent is not an open file descriptor."
        },
        {
          "name": "ETOOMANYREFS",
          "meaning": "sendmsg(2): in-flight fds exceed RLIMIT_NOFILE and caller lacks CAP_SYS_RESOURCE (Linux 4.5+)."
        },
        {
          "name": "EINVAL",
          "meaning": "sendmsg(2): more than SCM_MAX_FD descriptors in one message."
        },
        {
          "name": "ENOTCONN",
          "meaning": "Socket operation needs a target address but the socket is not connected."
        }
      ],
      "bsd": "FreeBSD and OpenBSD: the same SCM_RIGHTS control message on unix(4) sockets.",
      "man": "https://man7.org/linux/man-pages/man7/unix.7.html",
      "sources": [
        {
          "label": "unix(7), man7.org",
          "url": "https://man7.org/linux/man-pages/man7/unix.7.html"
        },
        {
          "label": "recv(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/recv.2.html"
        },
        {
          "label": "unix(4), FreeBSD",
          "url": "https://man.freebsd.org/cgi/man.cgi?query=unix&sektion=4"
        },
        {
          "label": "unix(4), OpenBSD",
          "url": "https://man.openbsd.org/unix.4"
        }
      ],
      "take": "Your instinct is /proc/<pid>/fd/N. Wrong instinct. SCM_RIGHTS hands the live open file description across a UNIX socket: same offset, same flags, same access the original opener had, which is the entire point of privilege separation. Open the port as root, pass the fd, drop privileges. Receive with MSG_CMSG_CLOEXEC every time, size the control buffer with CMSG_SPACE, and check MSG_CTRUNC, because if the buffer's too small the kernel closes the extras and they're just gone. And close what you receive, unless a slow fd leak is your idea of fun.",
      "example": "char dummy;\nstruct iovec iov = { .iov_base = &dummy, .iov_len = 1 };\nunion { char buf[CMSG_SPACE(sizeof(int))]; struct cmsghdr align; } u;\nstruct msghdr msg = {\n    .msg_iov = &iov, .msg_iovlen = 1,\n    .msg_control = u.buf, .msg_controllen = sizeof(u.buf),\n};\nif (recvmsg(sock, &msg, MSG_CMSG_CLOEXEC) < 0 || (msg.msg_flags & MSG_CTRUNC))\n    errx(1, \"recvmsg failed or control data truncated\");\n\nint fd = -1;\nstruct cmsghdr *c = CMSG_FIRSTHDR(&msg);\nif (c && c->cmsg_level == SOL_SOCKET && c->cmsg_type == SCM_RIGHTS)\n    memcpy(&fd, CMSG_DATA(c), sizeof(fd));",
      "exampleLang": "c",
      "ref": "SCM_RIGHTS",
      "url": "https://rodhat.com/syscalls/scm-rights/",
      "sinceParsed": {
        "kind": "unknown"
      },
      "tips": [
        "https://rodhat.com/tips/2026-09-02-scm-rights-fd-passing/"
      ],
      "related": [
        "https://rodhat.com/syscalls/pidfd_getfd/",
        "https://rodhat.com/syscalls/memfd_create/",
        "https://rodhat.com/syscalls/so-reuseport/",
        "https://rodhat.com/syscalls/close_range/"
      ]
    },
    {
      "slug": "so-reuseport",
      "name": "SO_REUSEPORT",
      "section": "socket(7)",
      "os": "linux",
      "category": "io",
      "summary": "Let multiple sockets bind one address:port; the kernel spreads traffic across them.",
      "synopsis": "#include <sys/socket.h>\n\nint setsockopt(int sockfd, int level, int optname,\n               const void optval[optlen], socklen_t optlen);\n/* level = SOL_SOCKET, optname = SO_REUSEPORT, set before bind(2) */",
      "since": "Linux 3.9",
      "glibc": "none (socket option set with setsockopt(2))",
      "flags": [
        {
          "name": "SO_REUSEPORT",
          "meaning": "Must be set on every socket, first included, before bind(2); all binders need the same effective UID."
        },
        {
          "name": "SO_ATTACH_REUSEPORT_CBPF",
          "meaning": "Attach a classic BPF program returning the index (0..N-1) of the socket that gets the packet."
        },
        {
          "name": "SO_ATTACH_REUSEPORT_EBPF",
          "meaning": "Attach an eBPF program to pick the socket. UDP support since Linux 4.5, TCP since Linux 4.6."
        },
        {
          "name": "BPF_PROG_TYPE_SK_REUSEPORT",
          "since": "Linux 4.19",
          "meaning": "eBPF program type returning SK_PASS/SK_DROP; selects via bpf_sk_select_reuseport.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              4,
              19,
              0
            ]
          }
        }
      ],
      "errors": [
        {
          "name": "EADDRINUSE",
          "meaning": "bind(2): address already in use, e.g. SO_REUSEPORT missing on one of the sockets."
        },
        {
          "name": "ENOPROTOOPT",
          "meaning": "setsockopt(2): the option is unknown at the level indicated."
        },
        {
          "name": "EINVAL",
          "meaning": "setsockopt(2): optlen is invalid."
        },
        {
          "name": "ENOTSOCK",
          "meaning": "setsockopt(2): sockfd does not refer to a socket."
        },
        {
          "name": "EBADF",
          "meaning": "setsockopt(2): sockfd is not a valid file descriptor."
        }
      ],
      "bsd": "FreeBSD: SO_REUSEPORT_LB load-balances TCP and UDP across up to 256 sockets per group. OpenBSD: SO_REUSEPORT allows duplicate bindings, documented for multicast/broadcast reception rather than load balancing.",
      "man": "https://man7.org/linux/man-pages/man7/socket.7.html",
      "sources": [
        {
          "label": "socket(7), man7.org",
          "url": "https://man7.org/linux/man-pages/man7/socket.7.html"
        },
        {
          "label": "setsockopt(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/setsockopt.2.html"
        },
        {
          "label": "bind(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/bind.2.html"
        },
        {
          "label": "getsockopt(2), FreeBSD",
          "url": "https://man.freebsd.org/cgi/man.cgi?query=getsockopt&sektion=2"
        },
        {
          "label": "getsockopt(2), OpenBSD",
          "url": "https://man.openbsd.org/getsockopt.2"
        }
      ],
      "take": "One listening socket shared by every worker means one accept queue, one lock, and the whole herd waking up for each connection. SO_REUSEPORT gives each worker its own socket on the same port, and the kernel hashes connections across the group. Set it on every socket before bind, same effective UID on all of them. Don't like the hash? Attach a BPF program and pick the socket yourself. What it won't save you from: close a socket and the connections already parked in its accept queue go down with it. Drain before you restart.",
      "example": "int fd = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0);\nif (fd < 0)\n    err(1, \"socket\");\n\nint one = 1;\nif (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one)) < 0)\n    err(1, \"SO_REUSEPORT\");\nif (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)\n    err(1, \"bind\");\nif (listen(fd, 1024) < 0)\n    err(1, \"listen\");\n/* run this in every worker; each accept()s on its own fd */",
      "exampleLang": "c",
      "ref": "SO_REUSEPORT",
      "url": "https://rodhat.com/syscalls/so-reuseport/",
      "sinceParsed": {
        "kind": "linux",
        "min": [
          3,
          9,
          0
        ]
      },
      "tips": [
        "https://rodhat.com/tips/2026-09-03-so-reuseport-bpf-dispatch/"
      ],
      "related": [
        "https://rodhat.com/syscalls/scm-rights/",
        "https://rodhat.com/syscalls/splice/",
        "https://rodhat.com/syscalls/eventfd/"
      ]
    },
    {
      "slug": "timerfd_create",
      "name": "timerfd_create",
      "section": "2",
      "os": "linux",
      "category": "events",
      "summary": "Create a timer that reports expirations through a readable file descriptor.",
      "synopsis": "#include <sys/timerfd.h>\n\nint timerfd_create(int clockid, int flags);\nint timerfd_settime(int fd, int flags,\n                    const struct itimerspec *new_value,\n                    struct itimerspec *_Nullable old_value);\nint timerfd_gettime(int fd, struct itimerspec *curr_value);",
      "since": "Linux 2.6.25",
      "glibc": "glibc 2.8",
      "flags": [
        {
          "name": "CLOCK_MONOTONIC",
          "meaning": "Nonsettable monotonic clock; the right choice for intervals."
        },
        {
          "name": "CLOCK_REALTIME",
          "meaning": "Settable wall clock; use with TFD_TIMER_ABSTIME for wall-clock deadlines."
        },
        {
          "name": "CLOCK_BOOTTIME",
          "since": "Linux 3.15",
          "meaning": "Like CLOCK_MONOTONIC but keeps counting while the system is suspended.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              3,
              15,
              0
            ]
          }
        },
        {
          "name": "CLOCK_REALTIME_ALARM / CLOCK_BOOTTIME_ALARM",
          "since": "Linux 3.11",
          "meaning": "Wake the system from suspend; setting requires CAP_WAKE_ALARM.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              3,
              11,
              0
            ]
          }
        },
        {
          "name": "TFD_NONBLOCK",
          "since": "Linux 2.6.27",
          "meaning": "Set O_NONBLOCK on the new descriptor.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              2,
              6,
              27
            ]
          }
        },
        {
          "name": "TFD_CLOEXEC",
          "since": "Linux 2.6.27",
          "meaning": "Set close-on-exec on the new descriptor.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              2,
              6,
              27
            ]
          }
        },
        {
          "name": "TFD_TIMER_ABSTIME",
          "meaning": "timerfd_settime(): treat it_value as an absolute time on the timer's clock."
        },
        {
          "name": "TFD_TIMER_CANCEL_ON_SET",
          "meaning": "With ABSTIME on a REALTIME clock: read(2) fails with ECANCELED if the clock jumps."
        },
        {
          "name": "TFD_IOC_SET_TICKS",
          "since": "Linux 3.17",
          "meaning": "ioctl(2) to set the expiration count; for checkpoint/restore.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              3,
              17,
              0
            ]
          }
        }
      ],
      "errors": [
        {
          "name": "EINVAL",
          "meaning": "clockid is not valid, or flags is invalid."
        },
        {
          "name": "EPERM",
          "meaning": "An *_ALARM clock was requested without CAP_WAKE_ALARM."
        },
        {
          "name": "EMFILE",
          "meaning": "Per-process open file descriptor limit reached."
        },
        {
          "name": "ENOMEM",
          "meaning": "Insufficient kernel memory to create the timer."
        },
        {
          "name": "ECANCELED",
          "meaning": "timerfd_settime()/read(2): TFD_TIMER_CANCEL_ON_SET timer and the real-time clock changed."
        }
      ],
      "bsd": "FreeBSD: native timerfd(2) (FreeBSD 14.0), or EVFILT_TIMER in kqueue(2). OpenBSD: EVFILT_TIMER in kqueue(2).",
      "man": "https://man7.org/linux/man-pages/man2/timerfd_create.2.html",
      "sources": [
        {
          "label": "timerfd_create(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/timerfd_create.2.html"
        },
        {
          "label": "timerfd(2), FreeBSD",
          "url": "https://man.freebsd.org/cgi/man.cgi?query=timerfd&sektion=2"
        },
        {
          "label": "kqueue(2), FreeBSD",
          "url": "https://man.freebsd.org/cgi/man.cgi?query=kqueue&sektion=2"
        },
        {
          "label": "kqueue(2), OpenBSD",
          "url": "https://man.openbsd.org/kqueue.2"
        }
      ],
      "take": "SIGALRM plus setitimer means a handler firing at some arbitrary point in your code to flip a global you'll check later. Garbage, and always was. timerfd makes the timer a file descriptor: it goes readable when it fires, and read() hands back a 64-bit count of expirations, so a slow loop finds out it missed three ticks instead of silently losing them. CLOCK_MONOTONIC for intervals; CLOCK_REALTIME only for wall-clock deadlines, with TFD_TIMER_ABSTIME. Then drop it in epoll next to everything else and stop thinking about signals.",
      "example": "int tfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);\nif (tfd < 0)\n    err(1, \"timerfd_create\");\n\nstruct itimerspec its = {\n    .it_interval = { .tv_sec = 5 },   /* repeat every 5s */\n    .it_value    = { .tv_sec = 5 },   /* first fire in 5s */\n};\nif (timerfd_settime(tfd, 0, &its, NULL) < 0)\n    err(1, \"timerfd_settime\");\n\n/* when epoll says readable: */\nuint64_t expirations;\nif (read(tfd, &expirations, sizeof(expirations)) == sizeof(expirations))\n    do_periodic_work(expirations);",
      "exampleLang": "c",
      "ref": "timerfd_create(2)",
      "url": "https://rodhat.com/syscalls/timerfd_create/",
      "sinceParsed": {
        "kind": "linux",
        "min": [
          2,
          6,
          25
        ]
      },
      "tips": [
        "https://rodhat.com/tips/2026-09-04-timerfd-signalfd-eventfd-epoll-event-sources/"
      ],
      "related": [
        "https://rodhat.com/syscalls/signalfd/",
        "https://rodhat.com/syscalls/eventfd/",
        "https://rodhat.com/syscalls/pidfd_open/"
      ]
    },
    {
      "slug": "signalfd",
      "name": "signalfd",
      "section": "2",
      "os": "linux",
      "category": "events",
      "summary": "Accept signals by reading a file descriptor instead of running a handler.",
      "synopsis": "#include <sys/signalfd.h>\n\nint signalfd(int fd, const sigset_t *mask, int flags);",
      "since": "Linux 2.6.22",
      "glibc": "glibc 2.8",
      "flags": [
        {
          "name": "fd = -1",
          "meaning": "Create a new signalfd; pass an existing signalfd instead to replace its mask."
        },
        {
          "name": "mask",
          "meaning": "Signals to accept. Block them with sigprocmask(2) first. SIGKILL and SIGSTOP are silently ignored."
        },
        {
          "name": "SFD_NONBLOCK",
          "since": "Linux 2.6.27",
          "meaning": "Set O_NONBLOCK on the new descriptor.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              2,
              6,
              27
            ]
          }
        },
        {
          "name": "SFD_CLOEXEC",
          "since": "Linux 2.6.27",
          "meaning": "Set close-on-exec on the new descriptor.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              2,
              6,
              27
            ]
          }
        },
        {
          "name": "struct signalfd_siginfo",
          "meaning": "What read(2) returns per signal: ssi_signo, ssi_pid, ssi_uid, ssi_code and more."
        }
      ],
      "errors": [
        {
          "name": "EINVAL",
          "meaning": "fd is not a valid signalfd, or flags is invalid."
        },
        {
          "name": "EBADF",
          "meaning": "fd is not a valid file descriptor."
        },
        {
          "name": "EMFILE",
          "meaning": "Per-process open file descriptor limit reached."
        },
        {
          "name": "ENOMEM",
          "meaning": "Insufficient memory to create a new signalfd."
        },
        {
          "name": "ENODEV",
          "meaning": "Could not mount (internal) anonymous inode device."
        }
      ],
      "bsd": "FreeBSD and OpenBSD: EVFILT_SIGNAL in kqueue(2).",
      "man": "https://man7.org/linux/man-pages/man2/signalfd.2.html",
      "sources": [
        {
          "label": "signalfd(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/signalfd.2.html"
        },
        {
          "label": "kqueue(2), FreeBSD",
          "url": "https://man.freebsd.org/cgi/man.cgi?query=kqueue&sektion=2"
        },
        {
          "label": "kqueue(2), OpenBSD",
          "url": "https://man.openbsd.org/kqueue.2"
        }
      ],
      "take": "The self-pipe trick is thirty years of a signal handler writing one byte so select() has something to wake up on. signalfd retires it. Block the signals with sigprocmask, create the fd on the same mask, and pending signals come out of read() as struct signalfd_siginfo, sender PID and UID included, which your old handler never had. No handler, no global flag, no race. The step people skip is the blocking: leave a signal unblocked and its default disposition gets there first. SIGKILL and SIGSTOP aren't invited, obviously.",
      "example": "sigset_t mask;\nsigemptyset(&mask);\nsigaddset(&mask, SIGTERM);\nsigaddset(&mask, SIGHUP);\nif (sigprocmask(SIG_BLOCK, &mask, NULL) < 0)\n    err(1, \"sigprocmask\");\n\nint sfd = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC);\nif (sfd < 0)\n    err(1, \"signalfd\");\n\nstruct signalfd_siginfo si;\nif (read(sfd, &si, sizeof(si)) == sizeof(si))\n    printf(\"signal %u from pid %u\\n\", si.ssi_signo, si.ssi_pid);",
      "exampleLang": "c",
      "ref": "signalfd(2)",
      "url": "https://rodhat.com/syscalls/signalfd/",
      "sinceParsed": {
        "kind": "linux",
        "min": [
          2,
          6,
          22
        ]
      },
      "tips": [
        "https://rodhat.com/tips/2026-09-04-timerfd-signalfd-eventfd-epoll-event-sources/"
      ],
      "related": [
        "https://rodhat.com/syscalls/timerfd_create/",
        "https://rodhat.com/syscalls/eventfd/",
        "https://rodhat.com/syscalls/pidfd_send_signal/",
        "https://rodhat.com/syscalls/pidfd_open/"
      ]
    },
    {
      "slug": "eventfd",
      "name": "eventfd",
      "section": "2",
      "os": "linux",
      "category": "events",
      "summary": "Create a kernel-backed 64-bit counter fd for wakeups between threads or processes.",
      "synopsis": "#include <sys/eventfd.h>\n\nint eventfd(unsigned int initval, int flags);",
      "since": "Linux 2.6.22",
      "glibc": "glibc 2.8",
      "flags": [
        {
          "name": "initval",
          "meaning": "Initial value of the counter."
        },
        {
          "name": "EFD_CLOEXEC",
          "since": "Linux 2.6.27",
          "meaning": "Set close-on-exec on the new descriptor.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              2,
              6,
              27
            ]
          }
        },
        {
          "name": "EFD_NONBLOCK",
          "since": "Linux 2.6.27",
          "meaning": "Set O_NONBLOCK; read on zero or write past the max returns EAGAIN.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              2,
              6,
              27
            ]
          }
        },
        {
          "name": "EFD_SEMAPHORE",
          "since": "Linux 2.6.30",
          "meaning": "read(2) returns 1 and decrements by 1 instead of returning and resetting the whole count.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              2,
              6,
              30
            ]
          }
        }
      ],
      "errors": [
        {
          "name": "EINVAL",
          "meaning": "An unsupported value was specified in flags."
        },
        {
          "name": "EMFILE",
          "meaning": "Per-process open file descriptor limit reached."
        },
        {
          "name": "ENFILE",
          "meaning": "System-wide open file limit reached."
        },
        {
          "name": "ENOMEM",
          "meaning": "Insufficient memory to create a new eventfd."
        },
        {
          "name": "ENODEV",
          "meaning": "Could not mount (internal) anonymous inode device."
        }
      ],
      "bsd": "FreeBSD: eventfd(2) (FreeBSD 13.0). OpenBSD: closest is EVFILT_USER in kqueue(2).",
      "man": "https://man7.org/linux/man-pages/man2/eventfd.2.html",
      "sources": [
        {
          "label": "eventfd(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/eventfd.2.html"
        },
        {
          "label": "eventfd(2), FreeBSD",
          "url": "https://man.freebsd.org/cgi/man.cgi?query=eventfd&sektion=2"
        },
        {
          "label": "kqueue(2), OpenBSD",
          "url": "https://man.openbsd.org/kqueue.2"
        }
      ],
      "take": "eventfd is a counter with a file descriptor bolted on, and that's the entire API. write() adds, read() drains, and the fd is readable whenever the count is nonzero. That makes it the cheapest way to poke an epoll loop from another thread without dragging a pipe into it. EFD_SEMAPHORE switches reads to one token at a time when several readers compete. Reads and writes are eight bytes; anything shorter gets EINVAL. Small, boring, correct. I have no complaints, and frankly that bothers me.",
      "example": "int efd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);\nif (efd < 0)\n    err(1, \"eventfd\");\n\n/* producer thread: wake the loop */\nuint64_t one = 1;\nif (write(efd, &one, sizeof(one)) != sizeof(one))\n    err(1, \"write eventfd\");\n\n/* event loop, after epoll reports EPOLLIN: */\nuint64_t count;\nif (read(efd, &count, sizeof(count)) == sizeof(count))\n    drain_work_queue(count);",
      "exampleLang": "c",
      "ref": "eventfd(2)",
      "url": "https://rodhat.com/syscalls/eventfd/",
      "sinceParsed": {
        "kind": "linux",
        "min": [
          2,
          6,
          22
        ]
      },
      "tips": [
        "https://rodhat.com/tips/2026-09-04-timerfd-signalfd-eventfd-epoll-event-sources/"
      ],
      "related": [
        "https://rodhat.com/syscalls/timerfd_create/",
        "https://rodhat.com/syscalls/signalfd/",
        "https://rodhat.com/syscalls/io_uring_setup/",
        "https://rodhat.com/syscalls/futex/"
      ]
    },
    {
      "slug": "futex",
      "name": "futex",
      "section": "2",
      "os": "linux",
      "category": "ipc",
      "summary": "Fast userspace locking: wait on or wake threads sleeping on a 32-bit word in memory.",
      "synopsis": "#include <linux/futex.h>  /* Definition of FUTEX_* constants */\n#include <sys/syscall.h>  /* Definition of SYS_* constants */\n#include <unistd.h>\n\nlong syscall(SYS_futex, uint32_t *uaddr, int op, ...);",
      "since": "Linux 2.6.0",
      "glibc": "none (use syscall(2))",
      "flags": [
        {
          "name": "FUTEX_WAIT",
          "since": "Linux 2.6.0",
          "meaning": "Sleep if *uaddr still equals val; the compare and the enqueue are atomic, so a wake can't slip in between.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              2,
              6,
              0
            ]
          }
        },
        {
          "name": "FUTEX_WAKE",
          "since": "Linux 2.6.0",
          "meaning": "Wake up to val waiters sleeping on uaddr; returns how many were actually woken.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              2,
              6,
              0
            ]
          }
        },
        {
          "name": "FUTEX_PRIVATE_FLAG",
          "since": "Linux 2.6.22",
          "meaning": "OR into op when the futex isn't shared between processes; lets the kernel skip cross-process bookkeeping.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              2,
              6,
              22
            ]
          }
        },
        {
          "name": "FUTEX_CMP_REQUEUE",
          "since": "Linux 2.6.7",
          "meaning": "Wake some waiters and move the rest to a second futex, after checking *uaddr still equals val3.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              2,
              6,
              7
            ]
          }
        },
        {
          "name": "FUTEX_REQUEUE",
          "since": "Linux 2.6.0",
          "meaning": "Same as FUTEX_CMP_REQUEUE but without the value comparison.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              2,
              6,
              0
            ]
          }
        },
        {
          "name": "FUTEX_LOCK_PI",
          "since": "Linux 2.6.18",
          "meaning": "Priority-inheritance lock: futex word holds the owner TID and the kernel boosts the owner while others wait.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              2,
              6,
              18
            ]
          }
        },
        {
          "name": "FUTEX_UNLOCK_PI",
          "since": "Linux 2.6.18",
          "meaning": "Release a PI futex taken with FUTEX_LOCK_PI.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              2,
              6,
              18
            ]
          }
        },
        {
          "name": "FUTEX_WAIT_BITSET",
          "since": "Linux 2.6.25",
          "meaning": "FUTEX_WAIT with a bitmask so wakers can target a subset of waiters.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              2,
              6,
              25
            ]
          }
        },
        {
          "name": "FUTEX_CLOCK_REALTIME",
          "since": "Linux 2.6.28",
          "meaning": "Measure the timeout against CLOCK_REALTIME instead of CLOCK_MONOTONIC (for ops that accept it).",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              2,
              6,
              28
            ]
          }
        }
      ],
      "errors": [
        {
          "name": "EAGAIN",
          "meaning": "FUTEX_WAIT: the word at uaddr didn't equal val when the call was made. Not a failure; re-check and retry."
        },
        {
          "name": "ETIMEDOUT",
          "meaning": "The timeout expired before the operation completed."
        },
        {
          "name": "EINTR",
          "meaning": "Interrupted by a signal. Since Linux 2.6.22 this no longer doubles as a spurious wakeup."
        },
        {
          "name": "EFAULT",
          "meaning": "uaddr (or timeout) did not point to a valid user-space address."
        },
        {
          "name": "ENOSYS",
          "meaning": "Invalid operation in op, or FUTEX_CLOCK_REALTIME used with an op that doesn't support it."
        }
      ],
      "bsd": "FreeBSD: _umtx_op(2) (UMTX_OP_WAIT_UINT / UMTX_OP_WAKE), the undocumented-in-spirit substrate under libthr. OpenBSD: futex(2), added in OpenBSD 6.2.",
      "man": "https://man7.org/linux/man-pages/man2/futex.2.html",
      "sources": [
        {
          "label": "futex(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/futex.2.html"
        },
        {
          "label": "FUTEX_WAIT(2const), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/FUTEX_WAIT.2const.html"
        },
        {
          "label": "FUTEX_CMP_REQUEUE(2const), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/FUTEX_CMP_REQUEUE.2const.html"
        },
        {
          "label": "FUTEX_LOCK_PI(2const), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/FUTEX_LOCK_PI.2const.html"
        },
        {
          "label": "FUTEX_WAIT_BITSET(2const), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/FUTEX_WAIT_BITSET.2const.html"
        },
        {
          "label": "_umtx_op(2), FreeBSD",
          "url": "https://man.freebsd.org/cgi/man.cgi?query=_umtx_op&sektion=2"
        },
        {
          "label": "futex(2), OpenBSD",
          "url": "https://man.openbsd.org/futex.2"
        }
      ],
      "take": "Every pthread_mutex_lock you've ever called is a polite wrapper over two kernel operations: FUTEX_WAIT and FUTEX_WAKE. The fast path is a compare-and-swap the kernel never sees; the kernel only shows up to park and unpark threads when there's real contention. No glibc wrapper, raw syscall(2) only. Drepper's three-state design is still the sane way to avoid a syscall on every unlock. Set FUTEX_PRIVATE_FLAG for in-process locks, and skip acquire/release ordering only if you enjoy learning about ARM memory models in production. Grudgingly: it's a beautiful little interface.",
      "example": "static long futex_wait(uint32_t *addr, uint32_t val) {\n    return syscall(SYS_futex, addr, FUTEX_WAIT | FUTEX_PRIVATE_FLAG,\n                   val, NULL, NULL, 0);\n}\nstatic long futex_wake(uint32_t *addr, int n) {\n    return syscall(SYS_futex, addr, FUTEX_WAKE | FUTEX_PRIVATE_FLAG,\n                   n, NULL, NULL, 0);\n}\n\n/* waiter: sleep while the flag is still 0 */\nwhile (atomic_load(&flag) == 0)\n    futex_wait((uint32_t *)&flag, 0);\n\n/* waker */\natomic_store(&flag, 1);\nfutex_wake((uint32_t *)&flag, 1);",
      "exampleLang": "c",
      "ref": "futex(2)",
      "url": "https://rodhat.com/syscalls/futex/",
      "sinceParsed": {
        "kind": "linux",
        "min": [
          2,
          6,
          0
        ]
      },
      "tips": [
        "https://rodhat.com/tips/2026-09-05-futex-raw-linux-mutex-primitive/"
      ],
      "related": [
        "https://rodhat.com/syscalls/seccomp/",
        "https://rodhat.com/syscalls/eventfd/"
      ]
    },
    {
      "slug": "fanotify_init",
      "name": "fanotify_init",
      "section": "2",
      "os": "linux",
      "category": "events",
      "summary": "Create an fanotify group: VFS-level file access events with live fds and blockable opens.",
      "synopsis": "#include <fcntl.h>            /* Definition of O_* constants */\n#include <sys/fanotify.h>\n\nint fanotify_init(unsigned int flags, unsigned int event_f_flags);",
      "since": "Linux 2.6.37",
      "glibc": "unknown",
      "flags": [
        {
          "name": "FAN_CLASS_NOTIF",
          "meaning": "Default class. Notification only; no permission decisions possible."
        },
        {
          "name": "FAN_CLASS_CONTENT",
          "meaning": "Notification plus permission events, for listeners that need files with their final content (e.g. malware scanners). CAP_SYS_ADMIN."
        },
        {
          "name": "FAN_CLASS_PRE_CONTENT",
          "meaning": "Notification plus permission events, for listeners that may write data before it's accessed (e.g. HSMs). CAP_SYS_ADMIN."
        },
        {
          "name": "FAN_CLOEXEC",
          "meaning": "Set close-on-exec on the group fd."
        },
        {
          "name": "FAN_NONBLOCK",
          "meaning": "Make the group fd nonblocking; read(2) returns EAGAIN when the queue is empty."
        },
        {
          "name": "FAN_UNLIMITED_QUEUE",
          "meaning": "Remove the event queue limit. Requires CAP_SYS_ADMIN."
        },
        {
          "name": "FAN_REPORT_TID",
          "since": "Linux 4.20",
          "meaning": "Report the thread ID instead of the process ID in the event metadata pid field.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              4,
              20,
              0
            ]
          }
        },
        {
          "name": "FAN_REPORT_FID",
          "since": "Linux 5.1",
          "meaning": "Identify objects by file handle instead of an open fd; not allowed with the CONTENT/PRE_CONTENT classes.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              5,
              1,
              0
            ]
          }
        },
        {
          "name": "FAN_REPORT_DFID_NAME",
          "meaning": "Synonym for FAN_REPORT_DIR_FID|FAN_REPORT_NAME (both since Linux 5.9): report parent dir handle plus entry name."
        },
        {
          "name": "FAN_REPORT_PIDFD",
          "since": "Linux 5.15 and 5.10.220",
          "meaning": "Attach a pidfd for the process that caused the event, so PID reuse can't fool you.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              5,
              15,
              0
            ],
            "backports": [
              [
                5,
                10,
                220
              ]
            ]
          }
        }
      ],
      "errors": [
        {
          "name": "EPERM",
          "meaning": "Caller lacks a required capability (CAP_SYS_ADMIN for the content classes and unlimited flags)."
        },
        {
          "name": "EINVAL",
          "meaning": "Invalid value in flags or event_f_flags."
        },
        {
          "name": "EMFILE",
          "meaning": "Per-user fanotify group limit exceeded, or the per-process fd limit was hit."
        },
        {
          "name": "ENOSYS",
          "meaning": "Kernel built without CONFIG_FANOTIFY."
        },
        {
          "name": "ENOMEM",
          "meaning": "Allocating the notification group failed."
        }
      ],
      "bsd": "FreeBSD and OpenBSD: no equivalent. kqueue(2) EVFILT_VNODE gives after-the-fact vnode notifications only, with no permission decisions.",
      "man": "https://man7.org/linux/man-pages/man2/fanotify_init.2.html",
      "sources": [
        {
          "label": "fanotify_init(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/fanotify_init.2.html"
        },
        {
          "label": "fanotify(7), man7.org",
          "url": "https://man7.org/linux/man-pages/man7/fanotify.7.html"
        },
        {
          "label": "fanotify_mark(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/fanotify_mark.2.html"
        }
      ],
      "take": "inotify tells you what happened after it happened, which is useless when you want to say no. fanotify hands you a live fd to the actual file instead of a name that may already be wrong, and with a content-class group it parks the opener in the kernel until your daemon writes FAN_ALLOW or FAN_DENY. That's what the AV and EDR crowd actually hook. The bill: CAP_SYS_ADMIN for anything useful, every event fd must be closed or you bleed descriptors, and a handler that opens files on its own marked filesystem has built itself a deadlock. Sharp tool. Respect it.",
      "example": "int fan = fanotify_init(FAN_CLASS_CONTENT | FAN_CLOEXEC,\n                        O_RDONLY | O_LARGEFILE);\nif (fan < 0) err(1, \"fanotify_init\");\nif (fanotify_mark(fan, FAN_MARK_ADD | FAN_MARK_FILESYSTEM,\n                  FAN_OPEN_PERM, AT_FDCWD, \"/sensitive\") < 0)\n    err(1, \"fanotify_mark\");\n\nchar buf[4096];\nssize_t n = read(fan, buf, sizeof buf);\nstruct fanotify_event_metadata *ev = (void *)buf;\nfor (; FAN_EVENT_OK(ev, n); ev = FAN_EVENT_NEXT(ev, n)) {\n    if (ev->fd == FAN_NOFD) continue;   /* queue overflow */\n    struct fanotify_response r = { .fd = ev->fd, .response = FAN_ALLOW };\n    if (ev->mask & FAN_OPEN_PERM) write(fan, &r, sizeof r);\n    close(ev->fd);                      /* mandatory */\n}",
      "exampleLang": "c",
      "ref": "fanotify_init(2)",
      "url": "https://rodhat.com/syscalls/fanotify_init/",
      "sinceParsed": {
        "kind": "linux",
        "min": [
          2,
          6,
          37
        ]
      },
      "tips": [
        "https://rodhat.com/tips/2026-09-07-fanotify-vfs-file-access-control/"
      ],
      "related": [
        "https://rodhat.com/syscalls/landlock_create_ruleset/",
        "https://rodhat.com/syscalls/seccomp/",
        "https://rodhat.com/syscalls/openat2/",
        "https://rodhat.com/syscalls/pidfd_open/"
      ]
    },
    {
      "slug": "fsopen",
      "name": "fsopen",
      "section": "2",
      "os": "linux",
      "category": "filesystem",
      "summary": "Open a filesystem context: step one of the fd-based mount API (fsconfig, fsmount, move_mount).",
      "synopsis": "#include <sys/mount.h>\n\nint fsopen(const char *fsname, unsigned int flags);\nint fsconfig(int fd, unsigned int cmd, const char *key,\n             const void *value, int aux);\nint fsmount(int fsfd, unsigned int flags, unsigned int attr_flags);\nint move_mount(int from_dirfd, const char *from_path,\n               int to_dirfd, const char *to_path, unsigned int flags);",
      "since": "Linux 5.2",
      "glibc": "glibc 2.36",
      "flags": [
        {
          "name": "FSOPEN_CLOEXEC",
          "meaning": "fsopen(): set close-on-exec on the new filesystem context fd."
        },
        {
          "name": "FSCONFIG_SET_FLAG",
          "meaning": "fsconfig(): set a boolean option with no value, e.g. \"ro\"."
        },
        {
          "name": "FSCONFIG_SET_STRING",
          "meaning": "fsconfig(): set a string option such as \"source\" or \"errors\"."
        },
        {
          "name": "FSCONFIG_SET_FD",
          "meaning": "fsconfig(): pass an open file descriptor as the option value."
        },
        {
          "name": "FSCONFIG_CMD_CREATE",
          "meaning": "fsconfig(): commit the configuration and create (or reuse) the superblock."
        },
        {
          "name": "FSCONFIG_CMD_CREATE_EXCL",
          "since": "Linux 6.6",
          "meaning": "Like FSCONFIG_CMD_CREATE but fail instead of silently reusing an existing matching superblock.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              6,
              6,
              0
            ]
          }
        },
        {
          "name": "FSCONFIG_CMD_RECONFIGURE",
          "meaning": "fsconfig(): apply changes to an existing superblock (context from fspick(2) or a used fsmount context)."
        },
        {
          "name": "FSMOUNT_CLOEXEC",
          "meaning": "fsmount(): set close-on-exec on the returned detached mount fd. attr_flags takes MOUNT_ATTR_* values."
        },
        {
          "name": "MOVE_MOUNT_F_EMPTY_PATH",
          "meaning": "move_mount(): from_path is empty, so the source is from_dirfd itself (e.g. the fd fsmount returned)."
        },
        {
          "name": "MOVE_MOUNT_BENEATH",
          "since": "Linux 6.5",
          "meaning": "move_mount(): mount beneath the top mount at the target instead of on top of it.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              6,
              5,
              0
            ]
          }
        }
      ],
      "errors": [
        {
          "name": "EPERM",
          "meaning": "fsopen(): caller lacks CAP_SYS_ADMIN."
        },
        {
          "name": "ENODEV",
          "meaning": "fsopen(): the filesystem named by fsname isn't supported by this kernel."
        },
        {
          "name": "EINVAL",
          "meaning": "Invalid flags; for fsmount(), also an invalid MOUNT_ATTR_* value in attr_flags."
        },
        {
          "name": "EMFILE",
          "meaning": "The calling process has too many open files to create another fd."
        },
        {
          "name": "EFAULT",
          "meaning": "fsopen(): fsname is NULL or points outside the accessible address space."
        }
      ],
      "bsd": "FreeBSD: nmount(2) takes structured name/value option iovecs (FreeBSD 5.0), but it's still a one-shot call. OpenBSD: none; plain mount(2).",
      "man": "https://man7.org/linux/man-pages/man2/fsopen.2.html",
      "sources": [
        {
          "label": "fsopen(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/fsopen.2.html"
        },
        {
          "label": "fsconfig(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/fsconfig.2.html"
        },
        {
          "label": "fsmount(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/fsmount.2.html"
        },
        {
          "label": "move_mount(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/move_mount.2.html"
        },
        {
          "label": "open_tree(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/open_tree.2.html"
        },
        {
          "label": "nmount(2), FreeBSD",
          "url": "https://man.freebsd.org/cgi/man.cgi?query=nmount&sektion=2"
        }
      ],
      "take": "mount(2) is one void pointer full of comma-separated wishes and a single undifferentiated EINVAL when one of them is wrong. The fd-based API that landed in Linux 5.2 builds a mount like any other kernel object: open a context, feed it options one fsconfig call at a time, commit, get a detached mount fd, attach it with move_mount. Per-option errors. A mount you can hand to another process over a Unix socket before deciding where it lives. No half-built rootfs flashing through the host namespace. More lines of C, sure. Worth every one of them.",
      "example": "int fs = fsopen(\"ext4\", FSOPEN_CLOEXEC);\nif (fs < 0) err(1, \"fsopen\");\nif (fsconfig(fs, FSCONFIG_SET_STRING, \"source\", \"/dev/sdb1\", 0) < 0 ||\n    fsconfig(fs, FSCONFIG_SET_FLAG, \"ro\", NULL, 0) < 0 ||\n    fsconfig(fs, FSCONFIG_SET_STRING, \"errors\", \"remount-ro\", 0) < 0 ||\n    fsconfig(fs, FSCONFIG_CMD_CREATE, NULL, NULL, 0) < 0)\n    err(1, \"fsconfig\");\n\nint mnt = fsmount(fs, FSMOUNT_CLOEXEC, MOUNT_ATTR_NODEV);\nif (mnt < 0) err(1, \"fsmount\");\nclose(fs);\n\nif (move_mount(mnt, \"\", AT_FDCWD, \"/mnt/data\", MOVE_MOUNT_F_EMPTY_PATH) < 0)\n    err(1, \"move_mount\");\nclose(mnt);",
      "exampleLang": "c",
      "ref": "fsopen(2)",
      "url": "https://rodhat.com/syscalls/fsopen/",
      "sinceParsed": {
        "kind": "linux",
        "min": [
          5,
          2,
          0
        ]
      },
      "tips": [
        "https://rodhat.com/tips/2026-09-08-new-mount-api-fsopen-fsmount/"
      ],
      "related": [
        "https://rodhat.com/syscalls/openat2/",
        "https://rodhat.com/syscalls/setns/",
        "https://rodhat.com/syscalls/scm-rights/",
        "https://rodhat.com/syscalls/statx/"
      ]
    },
    {
      "slug": "statx",
      "name": "statx",
      "section": "2",
      "os": "linux",
      "category": "filesystem",
      "summary": "Extended stat: request specific fields, get birth time, inode attribute flags and mount ID.",
      "synopsis": "#define _GNU_SOURCE          /* See feature_test_macros(7) */\n#include <fcntl.h>           /* Definition of AT_* constants */\n#include <sys/stat.h>\n\nint statx(int dirfd, const char *_Nullable restrict path,\n          int flags, unsigned int mask,\n          struct statx *restrict statxbuf);",
      "since": "Linux 4.11",
      "glibc": "glibc 2.28",
      "flags": [
        {
          "name": "STATX_BASIC_STATS",
          "meaning": "Mask: everything classic stat(2) returns (type, mode, nlink, uid, gid, times, ino, size, blocks)."
        },
        {
          "name": "STATX_BTIME",
          "meaning": "Mask: request stx_btime, the file creation time. Check stx_mask; not every filesystem has it."
        },
        {
          "name": "STATX_MNT_ID",
          "since": "Linux 5.8",
          "meaning": "Mask: request stx_mnt_id, the ID of the mount containing the file.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              5,
              8,
              0
            ]
          }
        },
        {
          "name": "STATX_MNT_ID_UNIQUE",
          "since": "Linux 6.8",
          "meaning": "Mask: request a unique (non-reused) mount ID in stx_mnt_id.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              6,
              8,
              0
            ]
          }
        },
        {
          "name": "STATX_DIOALIGN",
          "since": "Linux 6.1",
          "meaning": "Mask: request direct I/O alignment limits; support varies by filesystem.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              6,
              1,
              0
            ]
          }
        },
        {
          "name": "STATX_ALL",
          "meaning": "Mask: STATX_BASIC_STATS | STATX_BTIME. Deprecated; don't use it."
        },
        {
          "name": "AT_STATX_SYNC_AS_STAT",
          "meaning": "Flags: do whatever stat(2) does about syncing attributes with a remote server."
        },
        {
          "name": "AT_STATX_FORCE_SYNC",
          "meaning": "Flags: force attribute sync with the server (network filesystems)."
        },
        {
          "name": "AT_STATX_DONT_SYNC",
          "meaning": "Flags: return cached attributes even if stale; no server round trip."
        },
        {
          "name": "AT_SYMLINK_NOFOLLOW",
          "meaning": "Flags: if path is a symlink, report on the link itself."
        }
      ],
      "errors": [
        {
          "name": "ENOENT",
          "meaning": "A path component doesn't exist, or path is empty without AT_EMPTY_PATH."
        },
        {
          "name": "EACCES",
          "meaning": "Search permission denied on a directory in the path prefix."
        },
        {
          "name": "EINVAL",
          "meaning": "Invalid flag in flags, or the reserved STATX__RESERVED bit set in mask."
        },
        {
          "name": "EBADF",
          "meaning": "path is relative but dirfd is neither AT_FDCWD nor a valid fd."
        },
        {
          "name": "ENOTDIR",
          "meaning": "A prefix component isn't a directory, or path is relative and dirfd isn't a directory."
        }
      ],
      "bsd": "FreeBSD: no statx; stat(2) already carries st_birthtim (st_birthtime). OpenBSD: no statx; plain stat(2)/fstatat(2).",
      "man": "https://man7.org/linux/man-pages/man2/statx.2.html",
      "sources": [
        {
          "label": "statx(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/statx.2.html"
        },
        {
          "label": "stat(2), FreeBSD",
          "url": "https://man.freebsd.org/cgi/man.cgi?query=stat&sektion=2"
        }
      ],
      "take": "stat(2) froze its struct back when a filesystem fit on a floppy: three timestamps, none of them birth time, and ctime still means inode change no matter how many juniors swear otherwise. statx lets you ask for just the fields you want, then tells you through stx_mask which ones it actually filled. Check that mask before trusting anything, btime especially. You also get immutable, append-only, encrypted and verity flags without opening the file and fumbling an ioctl, plus a mount ID. FreeBSD had birth time in stat first, naturally. Still a good syscall.",
      "example": "struct statx sx;\nif (statx(AT_FDCWD, path, AT_STATX_SYNC_AS_STAT,\n          STATX_BASIC_STATS | STATX_BTIME, &sx) == 0) {\n    if (sx.stx_mask & STATX_BTIME)\n        printf(\"born: %lld.%09u\\n\",\n               (long long)sx.stx_btime.tv_sec, sx.stx_btime.tv_nsec);\n    else\n        puts(\"no birth time on this filesystem\");\n\n    if ((sx.stx_attributes_mask & STATX_ATTR_IMMUTABLE) &&\n        (sx.stx_attributes      & STATX_ATTR_IMMUTABLE))\n        puts(\"immutable\");\n}",
      "exampleLang": "c",
      "ref": "statx(2)",
      "url": "https://rodhat.com/syscalls/statx/",
      "sinceParsed": {
        "kind": "linux",
        "min": [
          4,
          11,
          0
        ]
      },
      "tips": [
        "https://rodhat.com/tips/2026-09-09-statx-extended-stat-syscall/"
      ],
      "related": [
        "https://rodhat.com/syscalls/openat2/",
        "https://rodhat.com/syscalls/fsopen/"
      ]
    },
    {
      "slug": "pledge",
      "name": "pledge",
      "section": "2",
      "os": "openbsd",
      "category": "sandboxing",
      "summary": "Restrict the process to named groups of system operations; violations kill it with SIGABRT.",
      "synopsis": "#include <unistd.h>\n\nint pledge(const char *promises, const char *execpromises);",
      "since": "OpenBSD 5.9",
      "glibc": null,
      "flags": [
        {
          "name": "stdio",
          "meaning": "Baseline: I/O on already-open fds, memory management, clocks, process info. Almost always needed."
        },
        {
          "name": "rpath",
          "meaning": "Path traversal, reading struct stat, and opening files for reading."
        },
        {
          "name": "wpath",
          "meaning": "Like rpath, but files can be opened for writing."
        },
        {
          "name": "cpath",
          "meaning": "Like wpath, but files can also be created and removed."
        },
        {
          "name": "inet",
          "meaning": "socket/connect/bind/listen/accept and friends in AF_INET and AF_INET6, with reduced setsockopt."
        },
        {
          "name": "dns",
          "meaning": "Resolver support: reads resolv.conf, hosts, services; sockets restricted to SOCK_DNS."
        },
        {
          "name": "proc",
          "meaning": "fork, vfork, kill, setpgid, setsid and related process-relationship calls. Not execve."
        },
        {
          "name": "exec",
          "meaning": "Allow execve(2). The new program starts with execpromises if set, otherwise unpledged."
        },
        {
          "name": "sendfd / recvfd",
          "meaning": "Send or receive file descriptors over sendmsg/recvmsg. Directory fds may not be passed."
        },
        {
          "name": "error",
          "meaning": "Return ENOSYS on violation instead of killing the process; requests for more promises are ignored."
        }
      ],
      "errors": [
        {
          "name": "EPERM",
          "meaning": "The process tried to increase its permissions with a later pledge() call."
        },
        {
          "name": "EINVAL",
          "meaning": "promises is malformed or contains an invalid keyword."
        },
        {
          "name": "EFAULT",
          "meaning": "promises or execpromises points outside the process's address space."
        }
      ],
      "bsd": "Linux: no single equivalent; combine seccomp(2) filters, Landlock (landlock_create_ruleset(2)) and prctl(PR_SET_NO_NEW_PRIVS). FreeBSD: Capsicum, cap_enter(2) and cap_rights_limit(2).",
      "man": "https://man.openbsd.org/pledge.2",
      "sources": [
        {
          "label": "pledge(2), OpenBSD",
          "url": "https://man.openbsd.org/pledge.2"
        },
        {
          "label": "cap_enter(2), FreeBSD",
          "url": "https://man.freebsd.org/cgi/man.cgi?query=cap_enter&sektion=2"
        }
      ],
      "take": "One syscall, one string. Call pledge(\"stdio inet dns\", NULL) once setup is done and that's all the process gets; step outside it and the kernel kills it with SIGABRT, core and all. Do the broad work first, open the config and the log file, then narrow. Later calls can only take promises away, never add them back. Compare Linux, where the same box means a seccomp filter, Landlock rules and no_new_privs: three APIs, three ways to screw it up. OpenBSD retrofitted nearly its whole base system with this. That's your proof the ergonomics are right.",
      "example": "FILE *cfg = fopen(\"/etc/myapp/config\", \"r\");\nif (cfg == NULL) err(1, \"fopen config\");\nparse_config(cfg);\nfclose(cfg);\n\nFILE *log = fopen(\"/var/log/myapp.log\", \"a\");\nif (log == NULL) err(1, \"fopen log\");\n\n/* setup done: no filesystem, no exec, no fork from here */\nif (pledge(\"stdio inet dns\", NULL) == -1)\n    err(1, \"pledge\");\n\nrun_daemon(log);",
      "exampleLang": "c",
      "ref": "pledge(2)",
      "url": "https://rodhat.com/syscalls/pledge/",
      "sinceParsed": {
        "kind": "openbsd",
        "min": [
          5,
          9,
          0
        ]
      },
      "tips": [
        "https://rodhat.com/tips/2026-09-13-pledge-unveil-openbsd-sandboxing/"
      ],
      "related": [
        "https://rodhat.com/syscalls/unveil/",
        "https://rodhat.com/syscalls/seccomp/",
        "https://rodhat.com/syscalls/landlock_create_ruleset/",
        "https://rodhat.com/syscalls/prctl/"
      ]
    },
    {
      "slug": "unveil",
      "name": "unveil",
      "section": "2",
      "os": "openbsd",
      "category": "sandboxing",
      "summary": "Hide the whole filesystem except the paths you name, with per-path r/w/x/c permissions.",
      "synopsis": "#include <unistd.h>\n\nint unveil(const char *path, const char *permissions);",
      "since": "OpenBSD 6.4",
      "glibc": null,
      "flags": [
        {
          "name": "r",
          "meaning": "Path available for read operations; corresponds to the pledge \"rpath\" promise."
        },
        {
          "name": "w",
          "meaning": "Path available for writes (pledge chown, fattr, wpath); also allows connect(2) to AF_UNIX sockets."
        },
        {
          "name": "x",
          "meaning": "Path available for execute; corresponds to the pledge \"exec\" promise."
        },
        {
          "name": "c",
          "meaning": "Path may be created and removed; corresponds to pledge cpath, dpath and unix."
        },
        {
          "name": "unveil(NULL, NULL)",
          "meaning": "Lock: no further unveil() calls allowed. A pledge without the \"unveil\" promise also locks it."
        }
      ],
      "errors": [
        {
          "name": "EPERM",
          "meaning": "Attempt to increase permissions, path not accessible, or unveil() called after locking."
        },
        {
          "name": "E2BIG",
          "meaning": "Adding path would exceed the per-process limit on unveiled paths."
        },
        {
          "name": "ENOENT",
          "meaning": "A directory in path did not exist."
        },
        {
          "name": "EINVAL",
          "meaning": "Invalid permissions string."
        }
      ],
      "bsd": "Linux: Landlock (landlock_create_ruleset(2)) is the closest path-based restriction, though denied paths aren't made invisible. FreeBSD: Capsicum cap_enter(2) with pre-opened directory fds.",
      "man": "https://man.openbsd.org/unveil.2",
      "sources": [
        {
          "label": "unveil(2), OpenBSD",
          "url": "https://man.openbsd.org/unveil.2"
        },
        {
          "label": "pledge(2), OpenBSD",
          "url": "https://man.openbsd.org/pledge.2"
        },
        {
          "label": "cap_enter(2), FreeBSD",
          "url": "https://man.freebsd.org/cgi/man.cgi?query=cap_enter&sektion=2"
        }
      ],
      "take": "pledge decides which syscall families you get; unveil decides which parts of the filesystem exist at all. Name the paths with r, w, x or c, then lock it with unveil(NULL, NULL). Anything you didn't name comes back ENOENT, so a popped process can't even confirm /etc/shadow is there; a named path used the wrong way gets EACCES. You can tighten later, never loosen. Stack it with pledge and a daemon is boxed on both axes in an afternoon. Unveil directories rather than single files; programs misbehave badly when their files quietly vanish.",
      "example": "/* privilege-separated worker: docroot only, then lock */\nvoid worker_setup(const char *docroot) {\n    if (unveil(docroot, \"r\") == -1)\n        err(1, \"unveil docroot\");\n    if (unveil(NULL, NULL) == -1)\n        err(1, \"unveil lock\");\n    if (pledge(\"stdio rpath inet sendfd\", NULL) == -1)\n        err(1, \"pledge\");\n}",
      "exampleLang": "c",
      "ref": "unveil(2)",
      "url": "https://rodhat.com/syscalls/unveil/",
      "sinceParsed": {
        "kind": "openbsd",
        "min": [
          6,
          4,
          0
        ]
      },
      "tips": [
        "https://rodhat.com/tips/2026-09-13-pledge-unveil-openbsd-sandboxing/"
      ],
      "related": [
        "https://rodhat.com/syscalls/pledge/",
        "https://rodhat.com/syscalls/landlock_create_ruleset/",
        "https://rodhat.com/syscalls/openat2/"
      ]
    },
    {
      "slug": "getrandom",
      "name": "getrandom",
      "section": "2",
      "os": "linux",
      "category": "security",
      "summary": "Fill a buffer with random bytes from the kernel CSPRNG, without opening /dev/urandom.",
      "synopsis": "#include <sys/random.h>\n\nssize_t getrandom(void buf[size], size_t size, unsigned int flags);",
      "since": "Linux 3.17",
      "glibc": "glibc 2.25",
      "flags": [
        {
          "name": "GRND_NONBLOCK",
          "meaning": "Don't block if the pool isn't initialized (or random source is empty); fail with EAGAIN instead."
        },
        {
          "name": "GRND_RANDOM",
          "meaning": "Draw from the random source (/dev/random) instead of urandom; may return fewer bytes than asked."
        },
        {
          "name": "GRND_INSECURE",
          "since": "Linux 5.6",
          "meaning": "Return bytes even before the pool is seeded, with no cryptographic guarantee. Early-boot use only.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              5,
              6,
              0
            ]
          }
        }
      ],
      "errors": [
        {
          "name": "EAGAIN",
          "meaning": "Requested entropy wasn't available and GRND_NONBLOCK was set."
        },
        {
          "name": "EINTR",
          "meaning": "Interrupted by a signal handler; small (<= 256 byte) urandom reads after init don't hit this."
        },
        {
          "name": "EINVAL",
          "meaning": "Invalid flag in flags."
        },
        {
          "name": "EFAULT",
          "meaning": "buf points outside the accessible address space."
        },
        {
          "name": "ENOSYS",
          "meaning": "glibc wrapper found the kernel doesn't implement getrandom()."
        }
      ],
      "bsd": "FreeBSD: getrandom(2) since FreeBSD 12.0, plus arc4random(3). OpenBSD: getentropy(2) since OpenBSD 5.6; arc4random(3) for bulk use.",
      "man": "https://man7.org/linux/man-pages/man2/getrandom.2.html",
      "sources": [
        {
          "label": "getrandom(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/getrandom.2.html"
        },
        {
          "label": "Linux 5.6 (GRND_INSECURE), kernelnewbies",
          "url": "https://kernelnewbies.org/Linux_5.6"
        },
        {
          "label": "Linux 6.11 (vDSO getrandom), kernelnewbies",
          "url": "https://kernelnewbies.org/Linux_6.11"
        },
        {
          "label": "getrandom(2), FreeBSD",
          "url": "https://man.freebsd.org/cgi/man.cgi?query=getrandom&sektion=2"
        },
        {
          "label": "getentropy(2), OpenBSD",
          "url": "https://man.openbsd.org/getentropy.2"
        }
      ],
      "take": "getrandom(2) showed up in Linux 3.17 and fixed the early-boot /dev/urandom mess, but every call still crossed into the kernel and back. The BSDs have done userspace ChaCha20 behind arc4random for years: seed once, generate in-process, reseed when it matters. Linux finally caught up in 6.11 with a vDSO getrandom that keeps per-thread state in userspace and notices fork and reseeds on its own. Fine. Better late than never. For your code nothing changes: ask for small chunks, check the return value every time, and let the default block until the pool is seeded.",
      "example": "unsigned char nonce[12];\nif (getrandom(nonce, sizeof nonce, 0) != (ssize_t)sizeof nonce)\n    err(1, \"getrandom\");\n\n/* early-boot code that must not block */\nunsigned char seed[32];\nssize_t n = getrandom(seed, sizeof seed, GRND_NONBLOCK);\nif (n == -1 && errno == EAGAIN) {\n    /* pool not initialized yet: retry later */\n}",
      "exampleLang": "c",
      "ref": "getrandom(2)",
      "url": "https://rodhat.com/syscalls/getrandom/",
      "sinceParsed": {
        "kind": "linux",
        "min": [
          3,
          17,
          0
        ]
      },
      "tips": [
        "https://rodhat.com/tips/2026-09-14-getrandom-vdso-userspace-rng/"
      ],
      "related": [
        "https://rodhat.com/syscalls/mlock/",
        "https://rodhat.com/syscalls/memfd_secret/"
      ]
    },
    {
      "slug": "mlock",
      "name": "mlock",
      "section": "2",
      "os": "linux",
      "category": "memory",
      "summary": "Lock pages into RAM so they can't be swapped out; mlockall() does the whole address space.",
      "synopsis": "#include <sys/mman.h>\n\nint mlock(const void addr[size], size_t size);\nint mlock2(const void addr[size], size_t size, unsigned int flags);\nint munlock(const void addr[size], size_t size);\n\nint mlockall(int flags);\nint munlockall(void);",
      "since": "POSIX.1-1996 (SVr4)",
      "glibc": "unknown (mlock2(): glibc 2.27)",
      "flags": [
        {
          "name": "MCL_CURRENT",
          "meaning": "mlockall(): lock every page currently mapped into the address space."
        },
        {
          "name": "MCL_FUTURE",
          "meaning": "mlockall(): lock pages mapped in the future too; later mmap/brk can fail if over the limit."
        },
        {
          "name": "MCL_ONFAULT",
          "since": "Linux 4.4",
          "meaning": "With MCL_CURRENT/MCL_FUTURE: lock pages as they're faulted in instead of populating them up front.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              4,
              4,
              0
            ]
          }
        },
        {
          "name": "MLOCK_ONFAULT",
          "since": "mlock2(), Linux 4.4",
          "meaning": "mlock2(): lock resident pages now, the rest of the range when they're first faulted in.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              4,
              4,
              0
            ]
          }
        },
        {
          "name": "RLIMIT_MEMLOCK",
          "meaning": "Caps how much an unprivileged process can lock (since Linux 2.6.9). CAP_IPC_LOCK bypasses it."
        }
      ],
      "errors": [
        {
          "name": "ENOMEM",
          "meaning": "Would exceed the RLIMIT_MEMLOCK soft limit without CAP_IPC_LOCK, or the range isn't fully mapped."
        },
        {
          "name": "EPERM",
          "meaning": "Caller is not privileged but needs CAP_IPC_LOCK for the requested operation."
        },
        {
          "name": "EAGAIN",
          "meaning": "Some or all of the specified range could not be locked."
        },
        {
          "name": "EINVAL",
          "meaning": "addr+size overflowed, unknown flags, or MCL_ONFAULT without MCL_CURRENT/MCL_FUTURE."
        }
      ],
      "bsd": "FreeBSD: mlock(2)/mlockall(2) (mlock since 4.4BSD); madvise(2) MADV_NOCORE for core-dump exclusion. OpenBSD: mlock(2)/mlockall(2) (4.4BSD).",
      "man": "https://man7.org/linux/man-pages/man2/mlock.2.html",
      "sources": [
        {
          "label": "mlock(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/mlock.2.html"
        },
        {
          "label": "madvise(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/madvise.2.html"
        },
        {
          "label": "mlock(2), FreeBSD",
          "url": "https://man.freebsd.org/cgi/man.cgi?query=mlock&sektion=2"
        },
        {
          "label": "madvise(2), FreeBSD",
          "url": "https://man.freebsd.org/cgi/man.cgi?query=madvise&sektion=2"
        },
        {
          "label": "mlock(2), OpenBSD",
          "url": "https://man.openbsd.org/mlock.2"
        }
      ],
      "take": "Your private key leaks three ways: swap, core dumps and fork, and mlock only fixes the first. Lock the pages, and if mlockall comes back ENOMEM, die. Logging a warning and carrying on means the key hits swap during some 3am memory crunch and nobody ever notices. Raise RLIMIT_MEMLOCK or hold CAP_IPC_LOCK. Then add MADV_DONTDUMP for crash dumps, MADV_WIPEONFORK so children see zeros, and explicit_bzero when you're done, because the compiler will happily delete your memset. None of it stops root reading your memory. It stops the boring leaks.",
      "example": "unsigned char *alloc_secret(size_t len) {\n    unsigned char *buf;\n    if (posix_memalign((void **)&buf, getpagesize(), len) != 0)\n        return NULL;\n    if (mlock(buf, len) != 0) { free(buf); return NULL; }\n    madvise(buf, len, MADV_DONTDUMP);\n    madvise(buf, len, MADV_WIPEONFORK);\n    return buf;\n}\n\nvoid free_secret(unsigned char *buf, size_t len) {\n    explicit_bzero(buf, len);\n    munlock(buf, len);\n    free(buf);\n}",
      "exampleLang": "c",
      "ref": "mlock(2)",
      "url": "https://rodhat.com/syscalls/mlock/",
      "sinceParsed": {
        "kind": "ancient"
      },
      "tips": [
        "https://rodhat.com/tips/2026-09-16-mlock-secret-memory-linux/"
      ],
      "related": [
        "https://rodhat.com/syscalls/madvise/",
        "https://rodhat.com/syscalls/memfd_secret/",
        "https://rodhat.com/syscalls/prctl/",
        "https://rodhat.com/syscalls/keyctl/"
      ]
    },
    {
      "slug": "prctl",
      "name": "prctl",
      "section": "2",
      "os": "linux",
      "category": "process",
      "summary": "Per-process/thread control knobs: no_new_privs, dumpable, parent-death signal, VMA names and more.",
      "synopsis": "#include <linux/prctl.h>  /* Definition of PR_* constants */\n#include <sys/prctl.h>\n\nint prctl(int op, ...);",
      "since": "Linux 2.1.57",
      "glibc": "glibc 2.0.6",
      "flags": [
        {
          "name": "PR_SET_NO_NEW_PRIVS",
          "since": "Linux 3.5",
          "meaning": "Permanently stop execve from granting privileges (setuid/setgid bits, file caps). Inherited, can't be unset.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              3,
              5,
              0
            ]
          }
        },
        {
          "name": "PR_SET_DUMPABLE",
          "meaning": "0 or 1 only. Not dumpable: no core dumps, no PTRACE_ATTACH, /proc/pid files owned by root. Value 2 was removed."
        },
        {
          "name": "PR_SET_PDEATHSIG",
          "meaning": "Signal to receive when the parent thread dies. Cleared on fork and setuid/caps exec; kept across plain execve."
        },
        {
          "name": "PR_SET_SECCOMP",
          "meaning": "Set seccomp mode; SECCOMP_MODE_FILTER (Linux 3.5) needs CAP_SYS_ADMIN or no_new_privs. seccomp(2) is preferred."
        },
        {
          "name": "PR_CAP_AMBIENT",
          "since": "Linux 4.3",
          "meaning": "Read or change the ambient capability set: RAISE, LOWER, IS_SET, CLEAR_ALL.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              4,
              3,
              0
            ]
          }
        },
        {
          "name": "PR_SET_VMA",
          "since": "Linux 5.17",
          "meaning": "PR_SET_VMA_ANON_NAME names an anonymous mapping. Max 80 bytes with NUL; printable ASCII except [ ] \\ $ `.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              5,
              17,
              0
            ]
          }
        },
        {
          "name": "PR_SET_NAME",
          "meaning": "Set the calling thread's name (comm)."
        }
      ],
      "errors": [
        {
          "name": "EINVAL",
          "meaning": "Unrecognized op, or a bad argument for it (e.g. NO_NEW_PRIVS arg2 != 1, DUMPABLE arg2 not 0/1)."
        },
        {
          "name": "EACCES",
          "meaning": "PR_SET_SECCOMP filter mode without CAP_SYS_ADMIN and without no_new_privs set."
        },
        {
          "name": "EFAULT",
          "meaning": "A pointer argument (e.g. a seccomp filter) is an invalid address."
        }
      ],
      "bsd": "FreeBSD: procctl(2) (FreeBSD 9.3), including PROC_PDEATHSIG_CTL (FreeBSD 11.2, modeled on PR_SET_PDEATHSIG) and PROC_NO_NEW_PRIVS_CTL. OpenBSD: none; pledge(2) covers the sandboxing side.",
      "man": "https://man7.org/linux/man-pages/man2/prctl.2.html",
      "sources": [
        {
          "label": "prctl(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/prctl.2.html"
        },
        {
          "label": "PR_SET_NO_NEW_PRIVS(2const), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/PR_SET_NO_NEW_PRIVS.2const.html"
        },
        {
          "label": "PR_SET_DUMPABLE(2const), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/PR_SET_DUMPABLE.2const.html"
        },
        {
          "label": "PR_SET_PDEATHSIG(2const), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/PR_SET_PDEATHSIG.2const.html"
        },
        {
          "label": "PR_SET_VMA(2const), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/PR_SET_VMA.2const.html"
        },
        {
          "label": "PR_SET_SECCOMP(2const), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/PR_SET_SECCOMP.2const.html"
        },
        {
          "label": "PR_CAP_AMBIENT(2const), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/PR_CAP_AMBIENT.2const.html"
        },
        {
          "label": "procctl(2), FreeBSD",
          "url": "https://man.freebsd.org/cgi/man.cgi?query=procctl&sektion=2"
        }
      ],
      "take": "prctl is a junk drawer with forty-odd operations in it, and three of them matter for a daemon that drops privileges and wants to stay dropped. PR_SET_NO_NEW_PRIVS turns setuid bits and file capabilities into dead letters across execve, permanently, and it's your ticket to installing seccomp filters without CAP_SYS_ADMIN. PR_SET_DUMPABLE at 0 keeps your memory out of core files and out of reach of ptrace. PR_SET_PDEATHSIG keeps orphaned workers from wandering off when the supervisor dies. Each plugs a different hole. They stack. Do the privileged work, set the bits, stop being interesting.",
      "example": "/* after binding ports and dropping capabilities */\nif (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0)\n    err(1, \"PR_SET_NO_NEW_PRIVS\");\nif (prctl(PR_SET_DUMPABLE, 0, 0, 0, 0) < 0)\n    err(1, \"PR_SET_DUMPABLE\");\n/* install_seccomp_filter(); */\n\npid_t parent = getpid();\nif (fork() == 0) {\n    if (prctl(PR_SET_PDEATHSIG, SIGTERM, 0, 0, 0) < 0)\n        err(1, \"PR_SET_PDEATHSIG\");\n    if (getppid() != parent)   /* parent already gone: no signal coming */\n        _exit(1);\n    run_worker();\n}",
      "exampleLang": "c",
      "ref": "prctl(2)",
      "url": "https://rodhat.com/syscalls/prctl/",
      "sinceParsed": {
        "kind": "linux",
        "min": [
          2,
          1,
          57
        ]
      },
      "tips": [
        "https://rodhat.com/tips/2026-09-19-prctl-process-hardening/"
      ],
      "related": [
        "https://rodhat.com/syscalls/seccomp/",
        "https://rodhat.com/syscalls/capset/",
        "https://rodhat.com/syscalls/landlock_create_ruleset/",
        "https://rodhat.com/syscalls/memfd_secret/",
        "https://rodhat.com/syscalls/mlock/"
      ]
    },
    {
      "slug": "capset",
      "name": "capset",
      "section": "2",
      "os": "linux",
      "category": "security",
      "summary": "Raw syscall to set a thread's permitted, effective and inheritable capability sets.",
      "synopsis": "#include <linux/capability.h> /* Definition of CAP_* and\n                                 _LINUX_CAPABILITY_* constants */\n#include <sys/syscall.h>      /* Definition of SYS_* constants */\n#include <unistd.h>\n\nint syscall(SYS_capget, cap_user_header_t hdrp,\n            cap_user_data_t datap);\nint syscall(SYS_capset, cap_user_header_t hdrp,\n            const cap_user_data_t datap);",
      "since": "Linux 2.2",
      "glibc": "none (use syscall(2))",
      "flags": [
        {
          "name": "_LINUX_CAPABILITY_VERSION_3",
          "since": "Linux 2.6.26",
          "meaning": "Header version for 64-bit capability sets (datap[0] and datap[1]). V2 (2.6.25) was a glitch; V1 is 32-bit.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              2,
              6,
              26
            ]
          }
        },
        {
          "name": "Permitted",
          "meaning": "Limiting superset of what the thread may make effective. Can be dropped, not regained without exec of a privileged file."
        },
        {
          "name": "Effective",
          "meaning": "The set the kernel actually checks for permission decisions."
        },
        {
          "name": "Inheritable",
          "meaning": "Caps preserved across execve; only useful combined with file inheritable caps or ambient."
        },
        {
          "name": "Bounding",
          "since": "Linux 2.6.25 (per-thread)",
          "meaning": "Caps file capabilities can grant at exec. Shrink with PR_CAPBSET_DROP (needs CAP_SETPCAP); can't be restored.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              2,
              6,
              25
            ]
          }
        },
        {
          "name": "Ambient",
          "since": "Linux 4.3",
          "meaning": "Caps kept across execve of unprivileged binaries; always a subset of permitted and inheritable. Set via prctl.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              4,
              3,
              0
            ]
          }
        },
        {
          "name": "SECBIT_NOROOT / SECBIT_KEEP_CAPS",
          "meaning": "Securebits: stop UID 0 getting caps on exec; KEEP_CAPS keeps permitted (not effective) caps across a UID switch."
        }
      ],
      "errors": [
        {
          "name": "EPERM",
          "meaning": "Tried to add a cap to permitted, or make effective a cap not in permitted."
        },
        {
          "name": "EPERM",
          "meaning": "Tried to add to inheritable a cap outside the bounding set, or outside permitted without CAP_SETPCAP."
        },
        {
          "name": "EINVAL",
          "meaning": "One of the arguments was invalid (e.g. an unsupported header version)."
        },
        {
          "name": "EFAULT",
          "meaning": "Bad memory address; hdrp must not be NULL."
        },
        {
          "name": "ESRCH",
          "meaning": "No such thread."
        }
      ],
      "bsd": "FreeBSD: no POSIX.1e-style process capability sets; Capsicum (cap_enter(2), cap_rights_limit(2)) restricts per-fd rights instead. OpenBSD: none; pledge(2).",
      "man": "https://man7.org/linux/man-pages/man2/capset.2.html",
      "sources": [
        {
          "label": "capset(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/capset.2.html"
        },
        {
          "label": "capabilities(7), man7.org",
          "url": "https://man7.org/linux/man-pages/man7/capabilities.7.html"
        },
        {
          "label": "PR_CAP_AMBIENT(2const), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/PR_CAP_AMBIENT.2const.html"
        },
        {
          "label": "cap_enter(2), FreeBSD",
          "url": "https://man.freebsd.org/cgi/man.cgi?query=cap_enter&sektion=2"
        }
      ],
      "take": "Five per-thread capability sets, and the execve transformation between them is where everybody's mental model falls apart. Permitted is the ceiling, effective is what the kernel actually checks, inheritable and ambient decide what survives exec, and bounding caps what file capabilities can hand out. Don't drive capset(2) by hand; the header versioning has bitten people and libcap's cap_set_proc exists for a reason. Drop what you don't need, clear ambient, lock the securebits so root can't quietly get everything back on the next setuid exec. Kerrisk spends a whole chapter on this. Read it.",
      "example": "/* still root here: lock securebits, switch UID, then drop the rest */\nif (prctl(PR_SET_SECUREBITS,\n          SECBIT_NOROOT | SECBIT_NOROOT_LOCKED, 0, 0, 0) < 0)\n    err(1, \"PR_SET_SECUREBITS\");\nif (setgroups(0, NULL) < 0 ||\n    setresgid(gid, gid, gid) < 0 ||\n    setresuid(uid, uid, uid) < 0)\n    err(1, \"drop uid/gid\");\n\ncap_t empty = cap_init();          /* libcap; calls capset(2) */\nif (cap_set_proc(empty) < 0)\n    err(1, \"cap_set_proc\");\ncap_free(empty);\nprctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0, 0, 0);",
      "exampleLang": "c",
      "ref": "capset(2)",
      "url": "https://rodhat.com/syscalls/capset/",
      "sinceParsed": {
        "kind": "linux",
        "min": [
          2,
          2,
          0
        ]
      },
      "tips": [
        "https://rodhat.com/tips/2026-09-20-linux-capabilities-set-model/"
      ],
      "related": [
        "https://rodhat.com/syscalls/prctl/",
        "https://rodhat.com/syscalls/seccomp/",
        "https://rodhat.com/syscalls/landlock_create_ruleset/"
      ]
    },
    {
      "slug": "keyctl",
      "name": "keyctl",
      "section": "2",
      "os": "linux",
      "category": "security",
      "summary": "Manipulate the kernel key retention service: read, revoke, time out and link keys and keyrings.",
      "synopsis": "#include <linux/keyctl.h>  /* Definition of KEY* constants */\n#include <sys/syscall.h>   /* Definition of SYS_* constants */\n#include <unistd.h>\n\nlong syscall(SYS_keyctl, int op, ...);",
      "since": "Linux 2.6.10",
      "glibc": "none (use syscall(2))",
      "flags": [
        {
          "name": "KEYCTL_READ",
          "since": "Linux 2.6.10",
          "meaning": "Read a key's payload; returns the full payload size so you can detect a short buffer.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              2,
              6,
              10
            ]
          }
        },
        {
          "name": "KEYCTL_REVOKE",
          "since": "Linux 2.6.10",
          "meaning": "Revoke a key; further operations on it fail with EKEYREVOKED.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              2,
              6,
              10
            ]
          }
        },
        {
          "name": "KEYCTL_SET_TIMEOUT",
          "since": "Linux 2.6.16",
          "meaning": "Expire the key t seconds from now (realtime clock); 0 clears the timeout.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              2,
              6,
              16
            ]
          }
        },
        {
          "name": "KEYCTL_SETPERM",
          "since": "Linux 2.6.10",
          "meaning": "Set the possessor/user/group/other permission mask on a key.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              2,
              6,
              10
            ]
          }
        },
        {
          "name": "KEYCTL_SEARCH",
          "since": "Linux 2.6.10",
          "meaning": "Search a keyring tree for a key of a given type and description.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              2,
              6,
              10
            ]
          }
        },
        {
          "name": "KEYCTL_INVALIDATE",
          "since": "Linux 3.5",
          "meaning": "Mark a key invalid immediately and schedule it for garbage collection.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              3,
              5,
              0
            ]
          }
        },
        {
          "name": "KEYCTL_GET_KEYRING_ID",
          "since": "Linux 2.6.10",
          "meaning": "Resolve a special ID (KEY_SPEC_PROCESS_KEYRING etc.) to a real keyring serial.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              2,
              6,
              10
            ]
          }
        },
        {
          "name": "KEY_SPEC_PROCESS_KEYRING",
          "meaning": "Process keyring: shared by threads, absent in a fork() child, cleared on execve."
        },
        {
          "name": "KEY_SPEC_SESSION_KEYRING",
          "meaning": "Session keyring: inherited across fork and preserved across execve."
        }
      ],
      "errors": [
        {
          "name": "ENOKEY",
          "meaning": "No matching key was found, or an invalid key was specified."
        },
        {
          "name": "EACCES",
          "meaning": "The requested operation wasn't permitted by the key's permissions."
        },
        {
          "name": "EKEYEXPIRED",
          "meaning": "An expired key was found or specified."
        },
        {
          "name": "EKEYREVOKED",
          "meaning": "A revoked key was found or specified."
        },
        {
          "name": "EDQUOT",
          "meaning": "Creating or linking the key would exceed the caller's key quota."
        }
      ],
      "bsd": "FreeBSD and OpenBSD: no kernel keyring equivalent.",
      "man": "https://man7.org/linux/man-pages/man2/keyctl.2.html",
      "sources": [
        {
          "label": "keyctl(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/keyctl.2.html"
        },
        {
          "label": "KEYCTL_READ(2const), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/KEYCTL_READ.2const.html"
        },
        {
          "label": "KEYCTL_SET_TIMEOUT(2const), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/KEYCTL_SET_TIMEOUT.2const.html"
        },
        {
          "label": "KEYCTL_INVALIDATE(2const), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/KEYCTL_INVALIDATE.2const.html"
        },
        {
          "label": "KEYCTL_REVOKE(2const), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/KEYCTL_REVOKE.2const.html"
        },
        {
          "label": "process-keyring(7), man7.org",
          "url": "https://man7.org/linux/man-pages/man7/process-keyring.7.html"
        },
        {
          "label": "session-keyring(7), man7.org",
          "url": "https://man7.org/linux/man-pages/man7/session-keyring.7.html"
        }
      ],
      "take": "Environment variables are where secrets go to die: /proc/PID/environ, core dumps, every child you ever fork. The kernel keyring has been doing this properly since 2.6.10: typed keys, per-key permissions, timeouts the kernel enforces itself, and revocation that doesn't mean killing the process. Process keyring if the secret should die with the process, session keyring if children need it. Set a timeout, read it with keyctl_read_alloc, revoke it when you're done. The API is ugly, the permission bitmask is hostile and the quotas are stingy. Still beats DB_PASSWORD in the environment by a mile.",
      "example": "#include <keyutils.h>   /* -lkeyutils */\n\nkey_serial_t key = add_key(\"user\", \"myapp:db:password\",\n                           pw, pwlen, KEY_SPEC_PROCESS_KEYRING);\nif (key == -1) err(1, \"add_key\");\nif (keyctl(KEYCTL_SET_TIMEOUT, key, 600) < 0)\n    perror(\"KEYCTL_SET_TIMEOUT\");\n\nvoid *payload = NULL;\nlong len = keyctl_read_alloc(key, &payload);\nif (len < 0) err(1, \"keyctl_read_alloc\");\nconnect_db(payload, (size_t)len);\nexplicit_bzero(payload, (size_t)len);\nfree(payload);\nkeyctl(KEYCTL_REVOKE, key);",
      "exampleLang": "c",
      "ref": "keyctl(2)",
      "url": "https://rodhat.com/syscalls/keyctl/",
      "sinceParsed": {
        "kind": "linux",
        "min": [
          2,
          6,
          10
        ]
      },
      "tips": [
        "https://rodhat.com/tips/2026-09-21-kernel-keyring-keyctl/"
      ],
      "related": [
        "https://rodhat.com/syscalls/memfd_secret/",
        "https://rodhat.com/syscalls/mlock/",
        "https://rodhat.com/syscalls/seccomp/",
        "https://rodhat.com/syscalls/prctl/"
      ]
    },
    {
      "slug": "madvise",
      "name": "madvise",
      "section": "2",
      "os": "linux",
      "category": "memory",
      "summary": "Advise the kernel how a memory range will be used: free it, page it out, exclude it from dumps.",
      "synopsis": "#include <sys/mman.h>\n\nint madvise(void addr[size], size_t size, int advice);",
      "since": "4.4BSD",
      "glibc": "unknown",
      "flags": [
        {
          "name": "MADV_DONTNEED",
          "meaning": "Drop the pages now: RSS falls immediately; next touch gets zero-fill (private anon) or file contents."
        },
        {
          "name": "MADV_FREE",
          "since": "Linux 4.5",
          "meaning": "Kernel may free the pages lazily under memory pressure; a write before then cancels the free. Private anon only.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              4,
              5,
              0
            ]
          }
        },
        {
          "name": "MADV_DONTDUMP",
          "since": "Linux 3.4",
          "meaning": "Exclude the range from core dumps; overrides /proc/pid/coredump_filter.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              3,
              4,
              0
            ]
          }
        },
        {
          "name": "MADV_DODUMP",
          "since": "Linux 3.4",
          "meaning": "Undo an earlier MADV_DONTDUMP.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              3,
              4,
              0
            ]
          }
        },
        {
          "name": "MADV_DONTFORK",
          "since": "Linux 2.6.16",
          "meaning": "Don't make the range available to the child after fork(2).",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              2,
              6,
              16
            ]
          }
        },
        {
          "name": "MADV_WIPEONFORK",
          "since": "Linux 4.14",
          "meaning": "Child sees zero-filled memory in this range after fork; cleared on execve. Private anon only.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              4,
              14,
              0
            ]
          }
        },
        {
          "name": "MADV_COLD",
          "since": "Linux 5.4",
          "meaning": "Deactivate the pages so they're likelier reclaim targets. Nondestructive.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              5,
              4,
              0
            ]
          }
        },
        {
          "name": "MADV_PAGEOUT",
          "since": "Linux 5.4",
          "meaning": "Reclaim the pages now: anon goes to swap, dirty file pages get written back.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              5,
              4,
              0
            ]
          }
        },
        {
          "name": "MADV_HUGEPAGE",
          "since": "Linux 2.6.38",
          "meaning": "Enable transparent huge pages for the range.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              2,
              6,
              38
            ]
          }
        },
        {
          "name": "MADV_POPULATE_WRITE",
          "since": "Linux 5.14",
          "meaning": "Prefault page tables writable, as if writing to each page, without actually writing.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              5,
              14,
              0
            ]
          }
        }
      ],
      "errors": [
        {
          "name": "EINVAL",
          "meaning": "addr not page-aligned, invalid advice, or advice not allowed on this mapping type (e.g. MADV_FREE on shared)."
        },
        {
          "name": "ENOMEM",
          "meaning": "Part of the range isn't mapped; Linux applies the advice to the rest and still returns this."
        },
        {
          "name": "EAGAIN",
          "meaning": "A kernel resource was temporarily unavailable."
        },
        {
          "name": "EPERM",
          "meaning": "MADV_HWPOISON without CAP_SYS_ADMIN."
        },
        {
          "name": "EACCES",
          "meaning": "MADV_REMOVE on a range that isn't a shared writable mapping."
        }
      ],
      "bsd": "FreeBSD: madvise(2) (4.4BSD) with MADV_FREE and MADV_NOCORE; minherit(2) INHERIT_ZERO for wipe-on-fork. OpenBSD: madvise(2) (since OpenBSD 2.7).",
      "man": "https://man7.org/linux/man-pages/man2/madvise.2.html",
      "sources": [
        {
          "label": "madvise(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/madvise.2.html"
        },
        {
          "label": "madvise(2), FreeBSD",
          "url": "https://man.freebsd.org/cgi/man.cgi?query=madvise&sektion=2"
        },
        {
          "label": "madvise(2), OpenBSD",
          "url": "https://man.openbsd.org/madvise.2"
        }
      ],
      "take": "Your service shows 7 GB RSS, gets OOM-killed, comes back at 800 MB, and somebody files a memory leak ticket. Wrong ticket. Allocators like jemalloc hand freed memory back with MADV_FREE, which lets the kernel reclaim those pages lazily, whenever it gets around to it, and until then they sit in your RSS like ghosts. MADV_DONTNEED drops them on the spot and charges you a page fault on reuse. MADV_FREE is the right call for throughput and a liar for monitoring. Read LazyFree out of smaps_rollup, subtract it, and stop paging people over phantom memory.",
      "example": "# RSS minus MADV_FREE pages the kernel can take back anytime\npid=$(pgrep -n \"${1:?usage: $0 <process-name>}\")\nawk '\n  /^Rss:/      { rss  = $2 }\n  /^LazyFree:/ { lazy = $2 }\n  END { printf \"rss=%d kB  lazy=%d kB  committed=%d kB\\n\", rss, lazy, rss-lazy }\n' /proc/$pid/smaps_rollup\n\n# force jemalloc back to immediate MADV_DONTNEED for an honest RSS\nMALLOC_CONF=\"dirty_decay_ms:0,muzzy_decay_ms:0\" ./your-service",
      "exampleLang": "sh",
      "ref": "madvise(2)",
      "url": "https://rodhat.com/syscalls/madvise/",
      "sinceParsed": {
        "kind": "ancient"
      },
      "tips": [
        "https://rodhat.com/tips/2026-09-25-madv-free-rss-lies/"
      ],
      "related": [
        "https://rodhat.com/syscalls/process_madvise/",
        "https://rodhat.com/syscalls/mlock/",
        "https://rodhat.com/syscalls/userfaultfd/"
      ]
    },
    {
      "slug": "process_madvise",
      "name": "process_madvise",
      "section": "2",
      "os": "linux",
      "category": "memory",
      "summary": "madvise() for another process's address space, addressed by pidfd and an iovec of ranges.",
      "synopsis": "#include <sys/mman.h>\n\nssize_t process_madvise(int pidfd, const struct iovec iovec[n],\n                        size_t n, int advice, unsigned int flags);",
      "since": "Linux 5.10",
      "glibc": "glibc 2.36",
      "flags": [
        {
          "name": "MADV_COLD",
          "meaning": "Deactivate the target's pages so they age toward reclaim. Valid on another process."
        },
        {
          "name": "MADV_PAGEOUT",
          "meaning": "Reclaim the target's pages now (anon to swap, dirty file pages written back). Valid on another process."
        },
        {
          "name": "MADV_WILLNEED",
          "meaning": "Expect access soon; read ahead. Valid on another process."
        },
        {
          "name": "MADV_COLLAPSE",
          "meaning": "Synchronously collapse the range into transparent huge pages. Valid on another process."
        },
        {
          "name": "any advice (self)",
          "since": "Linux 6.13",
          "meaning": "When pidfd refers to the calling process, any madvise advice is permitted.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              6,
              13,
              0
            ]
          }
        },
        {
          "name": "flags",
          "meaning": "Reserved; must be 0."
        }
      ],
      "errors": [
        {
          "name": "EPERM",
          "meaning": "No permission: needs PTRACE_MODE_READ_FSCREDS access to the target plus CAP_SYS_NICE (since Linux 5.12)."
        },
        {
          "name": "ESRCH",
          "meaning": "The target process has terminated and been waited on."
        },
        {
          "name": "EBADF",
          "meaning": "pidfd is not a valid PID file descriptor."
        },
        {
          "name": "EINVAL",
          "meaning": "flags is not 0, n is too large, or the iov_len sum overflows ssize_t."
        },
        {
          "name": "EFAULT",
          "meaning": "iovec describes memory outside the target's accessible address space."
        }
      ],
      "bsd": "FreeBSD and OpenBSD: none; madvise(2) only works on the caller's own address space.",
      "man": "https://man7.org/linux/man-pages/man2/process_madvise.2.html",
      "sources": [
        {
          "label": "process_madvise(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/process_madvise.2.html"
        },
        {
          "label": "madvise(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/madvise.2.html"
        },
        {
          "label": "glibc 2.36 NEWS (process_madvise added)",
          "url": "https://raw.githubusercontent.com/bminor/glibc/release/2.36/master/NEWS"
        }
      ],
      "take": "madvise only works on memory you own, which is useless when you're the supervisor staring at someone else's bloated process. process_madvise takes a pidfd and an iovec of ranges inside the target, so a memory daemon or checkpoint tool can say cold or page-this-out from outside. Android's low memory killer does exactly that before it pulls the trigger. The pidfd means PID reuse can't trick you into advising the wrong process. Check the return value; it's bytes advised and it can be partial. And read the permissions section: CAP_SYS_NICE on top of ptrace read access.",
      "example": "int pidfd = pidfd_open(target_pid, 0);\nif (pidfd == -1) err(1, \"pidfd_open\");\n\n/* one 512 MB anonymous range, taken from /proc/<pid>/maps */\nstruct iovec range = {\n    .iov_base = (void *)0x7f0000000000,\n    .iov_len  = 512UL * 1024 * 1024,\n};\nssize_t done = process_madvise(pidfd, &range, 1, MADV_PAGEOUT, 0);\nif (done < 0) err(1, \"process_madvise\");\nif ((size_t)done < range.iov_len)\n    warnx(\"partial: %zd bytes advised\", done);",
      "exampleLang": "c",
      "ref": "process_madvise(2)",
      "url": "https://rodhat.com/syscalls/process_madvise/",
      "sinceParsed": {
        "kind": "linux",
        "min": [
          5,
          10,
          0
        ]
      },
      "tips": [
        "https://rodhat.com/tips/2026-10-07-process-madvise-cross-process-memory-hints/"
      ],
      "related": [
        "https://rodhat.com/syscalls/madvise/",
        "https://rodhat.com/syscalls/pidfd_open/",
        "https://rodhat.com/syscalls/pidfd_getfd/"
      ]
    },
    {
      "slug": "setns",
      "name": "setns",
      "section": "2",
      "os": "linux",
      "category": "process",
      "summary": "Move the calling thread into an existing namespace, given an fd for it (or a pidfd).",
      "synopsis": "#define _GNU_SOURCE             /* See feature_test_macros(7) */\n#include <sched.h>\n\nint setns(int fd, int nstype);",
      "since": "Linux 3.0",
      "glibc": "glibc 2.14",
      "flags": [
        {
          "name": "0",
          "meaning": "Join whatever namespace type fd refers to; fine when you don't need the type checked."
        },
        {
          "name": "CLONE_NEWNET",
          "since": "Linux 3.0",
          "meaning": "fd must refer to a network namespace.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              3,
              0,
              0
            ]
          }
        },
        {
          "name": "CLONE_NEWIPC",
          "since": "Linux 3.0",
          "meaning": "fd must refer to an IPC namespace.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              3,
              0,
              0
            ]
          }
        },
        {
          "name": "CLONE_NEWUTS",
          "since": "Linux 3.0",
          "meaning": "fd must refer to a UTS namespace.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              3,
              0,
              0
            ]
          }
        },
        {
          "name": "CLONE_NEWNS",
          "since": "Linux 3.8",
          "meaning": "Mount namespace. Needs CAP_SYS_CHROOT and CAP_SYS_ADMIN; caller must not share CLONE_FS state.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              3,
              8,
              0
            ]
          }
        },
        {
          "name": "CLONE_NEWPID",
          "since": "Linux 3.8",
          "meaning": "Descendant PID namespace. Only affects children created afterwards, not the caller.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              3,
              8,
              0
            ]
          }
        },
        {
          "name": "CLONE_NEWUSER",
          "since": "Linux 3.8",
          "meaning": "User namespace. Caller must be single-threaded; needs CAP_SYS_ADMIN in the target namespace.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              3,
              8,
              0
            ]
          }
        },
        {
          "name": "CLONE_NEWCGROUP",
          "since": "Linux 4.6",
          "meaning": "fd must refer to a cgroup namespace.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              4,
              6,
              0
            ]
          }
        },
        {
          "name": "CLONE_NEWTIME",
          "since": "Linux 5.8",
          "meaning": "fd must refer to a time namespace.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              5,
              8,
              0
            ]
          }
        }
      ],
      "errors": [
        {
          "name": "EPERM",
          "meaning": "The calling thread lacks the capability this namespace type requires."
        },
        {
          "name": "EINVAL",
          "meaning": "Namespace type doesn't match nstype, ancestor PID ns, or multithreaded caller joining a user ns."
        },
        {
          "name": "EBADF",
          "meaning": "fd is not a valid file descriptor."
        },
        {
          "name": "ESRCH",
          "meaning": "fd is a pidfd but that process has terminated and been waited on."
        },
        {
          "name": "ENOMEM",
          "meaning": "Cannot allocate memory to change the namespace."
        }
      ],
      "bsd": "FreeBSD: no namespaces; jail_attach(2) (FreeBSD 5.1) is the closest thing to joining an existing isolated environment. OpenBSD: none.",
      "man": "https://man7.org/linux/man-pages/man2/setns.2.html",
      "sources": [
        {
          "label": "setns(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/setns.2.html"
        },
        {
          "label": "nsenter(1), man7.org",
          "url": "https://man7.org/linux/man-pages/man1/nsenter.1.html"
        },
        {
          "label": "jail(2), FreeBSD",
          "url": "https://man.freebsd.org/cgi/man.cgi?query=jail&sektion=2"
        }
      ],
      "take": "Containers aren't magic, they're namespace flags, and setns(2) is how you walk into one from the outside. nsenter opens the files under /proc/PID/ns and calls setns, so you can run the host's tcpdump and ss inside a distroless container's network namespace without installing a damn thing in the image. Join only the namespace you need; skip the mount namespace and you keep your own tools while /proc/PID/root shows you the container's files. Hold an fd on the ns file and the namespace sticks around for forensics after its processes are gone. Root required for most of it, as it should be.",
      "example": "pid=$(docker inspect --format '{{.State.Pid}}' mycontainer)\n\n# container's network stack, host's binaries\nsudo nsenter -t \"$pid\" --net -- ss -tulpn\nsudo nsenter -t \"$pid\" --net -- tcpdump -i eth0 -n\n\n# distroless image: its pid/net/ipc/uts namespaces, host's /bin/sh\nsudo nsenter -t \"$pid\" --pid --net --ipc --uts -- /bin/sh\n\n# what nsenter calls setns(2) on\nls -la /proc/\"$pid\"/ns/",
      "exampleLang": "sh",
      "ref": "setns(2)",
      "url": "https://rodhat.com/syscalls/setns/",
      "sinceParsed": {
        "kind": "linux",
        "min": [
          3,
          0,
          0
        ]
      },
      "tips": [
        "https://rodhat.com/tips/2026-08-12-nsenter-linux-namespaces/",
        "https://rodhat.com/tips/2026-10-12-setns-unshare-linux-namespaces/"
      ],
      "related": [
        "https://rodhat.com/syscalls/unshare/",
        "https://rodhat.com/syscalls/clone3/",
        "https://rodhat.com/syscalls/pidfd_open/",
        "https://rodhat.com/syscalls/fsopen/",
        "https://rodhat.com/syscalls/jail/"
      ]
    },
    {
      "slug": "sched_setaffinity",
      "name": "sched_setaffinity",
      "section": "2",
      "os": "linux",
      "category": "process",
      "summary": "Set the set of CPUs a thread may run on (taskset is the CLI wrapper).",
      "synopsis": "#define _GNU_SOURCE             /* See feature_test_macros(7) */\n#include <sched.h>\n\nint sched_setaffinity(pid_t pid, size_t cpusetsize,\n                      const cpu_set_t *mask);\nint sched_getaffinity(pid_t pid, size_t cpusetsize,\n                      cpu_set_t *mask);",
      "since": "Linux 2.5.8",
      "glibc": "glibc 2.3",
      "flags": [
        {
          "name": "pid",
          "meaning": "A thread ID, not a whole process; 0 means the calling thread. Other threads keep their own masks."
        },
        {
          "name": "cpusetsize",
          "meaning": "Size of mask in bytes; normally sizeof(cpu_set_t)."
        },
        {
          "name": "mask",
          "meaning": "cpu_set_t built with the CPU_SET(3) macros. If the thread isn't on an allowed CPU it gets migrated."
        }
      ],
      "errors": [
        {
          "name": "EINVAL",
          "meaning": "mask has no CPUs that are physically present and permitted by cpuset cgroups."
        },
        {
          "name": "EPERM",
          "meaning": "Caller's EUID doesn't match the target's real/effective UID and it lacks CAP_SYS_NICE."
        },
        {
          "name": "ESRCH",
          "meaning": "No thread with that ID."
        },
        {
          "name": "EFAULT",
          "meaning": "A supplied memory address was invalid."
        }
      ],
      "bsd": "FreeBSD: cpuset_setaffinity(2) (FreeBSD 7.1) and the cpuset(1) tool. OpenBSD: none.",
      "man": "https://man7.org/linux/man-pages/man2/sched_setaffinity.2.html",
      "sources": [
        {
          "label": "sched_setaffinity(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/sched_setaffinity.2.html"
        },
        {
          "label": "taskset(1), man7.org",
          "url": "https://man7.org/linux/man-pages/man1/taskset.1.html"
        },
        {
          "label": "cpuset_setaffinity(2), FreeBSD",
          "url": "https://man.freebsd.org/cgi/man.cgi?query=cpuset_setaffinity&sektion=2"
        }
      ],
      "take": "The scheduler can do its job perfectly and still trash your L3 by shuffling a hot thread across cores every few hundred milliseconds. sched_setaffinity, usually through taskset, pins it. Know your topology first: hyperthread siblings share a core, and another socket means every cache miss crosses the NUMA interconnect. Affinity says nothing about where memory lands; that's numactl's job. Mind that it's per-thread, so pinning a running PID without -a leaves its other threads wherever they were. And profile first. Pinning without evidence is chasing shadows and adding complexity you'll forget about in six months.",
      "example": "lscpu -e                       # topology: node, socket, core, HT siblings\n\ntaskset -c 0-3 ./myapp         # launch pinned to CPUs 0-3\ntaskset -cp \"$pid\"             # read current affinity\ntaskset -a -cp 0-7 \"$pid\"      # re-pin every thread of a running process\n\n# CPU and memory both on NUMA node 0\nnumactl --cpunodebind=0 --membind=0 ./myapp",
      "exampleLang": "sh",
      "ref": "sched_setaffinity(2)",
      "url": "https://rodhat.com/syscalls/sched_setaffinity/",
      "sinceParsed": {
        "kind": "linux",
        "min": [
          2,
          5,
          8
        ]
      },
      "tips": [
        "https://rodhat.com/tips/2026-08-15-taskset-numactl-cpu-pinning/"
      ],
      "related": [
        "https://rodhat.com/syscalls/ioprio_set/"
      ]
    },
    {
      "slug": "ioprio_set",
      "name": "ioprio_set",
      "section": "2",
      "os": "linux",
      "category": "io",
      "summary": "Set a thread's I/O scheduling class and priority (ionice is the CLI wrapper).",
      "synopsis": "#include <linux/ioprio.h>    /* Definition of IOPRIO_* constants */\n#include <sys/syscall.h>     /* Definition of SYS_* constants */\n#include <unistd.h>\n\nint syscall(SYS_ioprio_get, int which, int who);\nint syscall(SYS_ioprio_set, int which, int who, int ioprio);",
      "since": "Linux 2.6.13",
      "glibc": "none (use syscall(2))",
      "flags": [
        {
          "name": "IOPRIO_WHO_PROCESS",
          "meaning": "who is a process or thread ID; 0 means the calling thread."
        },
        {
          "name": "IOPRIO_WHO_PGRP",
          "meaning": "who is a process group ID; 0 means the caller's process group."
        },
        {
          "name": "IOPRIO_WHO_USER",
          "meaning": "who is a UID; applies to all processes with that real UID."
        },
        {
          "name": "IOPRIO_CLASS_RT",
          "meaning": "Real-time class, levels 0-7; gets the disk first and can starve everyone. Needs CAP_SYS_ADMIN."
        },
        {
          "name": "IOPRIO_CLASS_BE",
          "meaning": "Best-effort class, levels 0 (highest) to 7; the default."
        },
        {
          "name": "IOPRIO_CLASS_IDLE",
          "meaning": "Only gets disk time when nobody else wants it. No CAP_SYS_ADMIN needed since Linux 2.6.25."
        },
        {
          "name": "IOPRIO_PRIO_VALUE(class, data)",
          "meaning": "Macro that packs class and level into the ioprio argument."
        }
      ],
      "errors": [
        {
          "name": "EPERM",
          "meaning": "Not allowed: target owned by another UID without CAP_SYS_NICE, or RT class without CAP_SYS_ADMIN."
        },
        {
          "name": "EINVAL",
          "meaning": "Invalid value for which or ioprio."
        },
        {
          "name": "ESRCH",
          "meaning": "No process matched which and who."
        }
      ],
      "bsd": "FreeBSD and OpenBSD: no per-process I/O priority syscall.",
      "man": "https://man7.org/linux/man-pages/man2/ioprio_set.2.html",
      "sources": [
        {
          "label": "ioprio_set(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/ioprio_set.2.html"
        },
        {
          "label": "ionice(1), man7.org",
          "url": "https://man7.org/linux/man-pages/man1/ionice.1.html"
        }
      ],
      "take": "Somebody slapped nice -n 19 on the 2am backup and p99 still tripled, because nice is CPU and the backup was fighting for disk. ionice, which is ioprio_set under the hood, is the next guess, and it only does anything if the device's I/O scheduler honors priorities at all. The man page still talks about CFQ as if it's 2006; on a modern box, go read /sys/block/*/queue/scheduler before you trust it. It also admits priorities don't apply to async buffered writes. If you need real protection, cgroup v2 io.latency guards the workload that matters. ionice is a hint, not a fence.",
      "example": "# does this device's scheduler even look at I/O priorities?\ncat /sys/block/nvme0n1/queue/scheduler\n\n# idle class: only gets the disk when nobody else wants it\nionice -c 3 /usr/local/bin/nightly-backup\nionice -p \"$pid\"               # read a running process's class/priority\n\n# what actually protects the database: cgroup v2 io.latency\nmkdir /sys/fs/cgroup/database\necho \"259:0 target=10\" > /sys/fs/cgroup/database/io.latency\necho \"$pgpid\" > /sys/fs/cgroup/database/cgroup.procs",
      "exampleLang": "sh",
      "ref": "ioprio_set(2)",
      "url": "https://rodhat.com/syscalls/ioprio_set/",
      "sinceParsed": {
        "kind": "linux",
        "min": [
          2,
          6,
          13
        ]
      },
      "tips": [
        "https://rodhat.com/tips/2026-07-28-ionice-cgroup-io-latency/"
      ],
      "related": [
        "https://rodhat.com/syscalls/sched_setaffinity/"
      ]
    },
    {
      "slug": "flock",
      "name": "flock",
      "section": "2",
      "os": "linux",
      "category": "file-descriptors",
      "summary": "Apply or remove an advisory whole-file lock tied to an open file description.",
      "synopsis": "#include <sys/file.h>\n\nint flock(int fd, int op);",
      "since": "4.2BSD; Linux 2.0",
      "glibc": "unknown",
      "flags": [
        {
          "name": "LOCK_SH",
          "meaning": "Shared lock; many processes can hold one at the same time."
        },
        {
          "name": "LOCK_EX",
          "meaning": "Exclusive lock; only one holder at a time."
        },
        {
          "name": "LOCK_UN",
          "meaning": "Release the lock held via this open file description."
        },
        {
          "name": "LOCK_NB",
          "meaning": "OR in to fail with EWOULDBLOCK instead of blocking when the lock is held."
        }
      ],
      "errors": [
        {
          "name": "EWOULDBLOCK",
          "meaning": "The file is locked and LOCK_NB was given."
        },
        {
          "name": "EINTR",
          "meaning": "A caught signal interrupted the wait for the lock."
        },
        {
          "name": "EBADF",
          "meaning": "fd is not an open file descriptor."
        },
        {
          "name": "EINVAL",
          "meaning": "op is invalid."
        },
        {
          "name": "ENOLCK",
          "meaning": "The kernel ran out of memory for lock records."
        }
      ],
      "bsd": "FreeBSD: flock(2) (appeared in 4.2BSD). OpenBSD: flock(2) (first appeared in 4.1cBSD per its man page). On the modern BSDs flock and fcntl locks interact; on Linux they don't (except over NFS/SMB).",
      "man": "https://man7.org/linux/man-pages/man2/flock.2.html",
      "sources": [
        {
          "label": "flock(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/flock.2.html"
        },
        {
          "label": "flock(1), man7.org",
          "url": "https://man7.org/linux/man-pages/man1/flock.1.html"
        },
        {
          "label": "flock(2), FreeBSD",
          "url": "https://man.freebsd.org/cgi/man.cgi?query=flock&sektion=2"
        },
        {
          "label": "flock(2), OpenBSD",
          "url": "https://man.openbsd.org/flock.2"
        }
      ],
      "take": "The pidfile you were about to write is broken three ways: test-and-set races, a SIGKILL leaves it stale forever, and PIDs get recycled. flock(2) is one kernel operation on an open file description, so when the holder dies, however it dies, the lock goes with it. Wrap the cron job in flock -n and an overlapping run just exits. Lock the contested resource, not the job, and share the lock file across every script that touches it. It's advisory: anything that doesn't ask walks straight through. Across hosts or NFS, use something built for distributed locking, not a file and a prayer.",
      "example": "# crontab: skip this run if the last one is still going\n# */5 * * * * /usr/bin/flock -n /var/lock/report.lock /usr/local/bin/report.sh\n\n#!/bin/bash\n# self-locking script: fd 9 holds the lock until the script exits\nexec 9>/var/lock/job.lock\nflock -n 9 || { echo \"already running\"; exit 0; }\n# ... work ...\n\n# wait up to 60s instead of skipping\nflock -w 60 /var/lock/job.lock /usr/local/bin/job.sh",
      "exampleLang": "sh",
      "ref": "flock(2)",
      "url": "https://rodhat.com/syscalls/flock/",
      "sinceParsed": {
        "kind": "linux",
        "min": [
          2,
          0,
          0
        ]
      },
      "tips": [
        "https://rodhat.com/tips/2026-08-09-flock-cron-overlap/"
      ],
      "related": []
    },
    {
      "slug": "copy_file_range",
      "name": "copy_file_range",
      "section": "2",
      "os": "linux",
      "category": "io",
      "summary": "Copy a range of bytes between two file descriptors entirely inside the kernel, with server-side offload on NFS 4.2 and reflink on btrfs.",
      "synopsis": "#define _GNU_SOURCE\n#include <unistd.h>\n\nssize_t copy_file_range(int fd_in, off64_t *_Nullable off_in,\n                        int fd_out, off64_t *_Nullable off_out,\n                        size_t len, unsigned int flags);",
      "since": "Linux 4.5",
      "glibc": "glibc 2.27",
      "flags": [
        {
          "name": "flags",
          "meaning": "Must be 0; no flags are defined."
        },
        {
          "name": "off_in = NULL",
          "meaning": "Use and advance fd_in's current file position, like read(2)."
        },
        {
          "name": "off_in = &pos",
          "meaning": "Read from pos without moving fd_in's position, like pread(2)."
        },
        {
          "name": "off_out = NULL / &pos",
          "meaning": "Same semantics as off_in but for the write side."
        },
        {
          "name": "NFS 4.2 server-side copy",
          "meaning": "On NFS 4.2, the kernel sends a CLONE or COPY op to the server; data never crosses the network."
        },
        {
          "name": "btrfs reflink",
          "meaning": "On btrfs, extents are shared via reflink; an instantaneous copy regardless of file size."
        }
      ],
      "errors": [
        {
          "name": "EINVAL",
          "meaning": "flags != 0, fd_in == fd_out with overlapping ranges, or fd is not a regular file."
        },
        {
          "name": "EXDEV",
          "meaning": "Cross-filesystem copy on kernels before 5.3; fall back to read/write on those kernels."
        },
        {
          "name": "EBADF",
          "meaning": "A file descriptor is invalid or opened with the wrong mode."
        },
        {
          "name": "EFBIG",
          "meaning": "Write would exceed the file size limit or the filesystem's maximum file size."
        },
        {
          "name": "EOPNOTSUPP",
          "meaning": "The filesystem does not support this operation."
        }
      ],
      "bsd": "FreeBSD: copy_file_range(2) (FreeBSD 13.0), identical interface; uses ZFS reflink when both fds are on the same dataset.",
      "man": "https://man7.org/linux/man-pages/man2/copy_file_range.2.html",
      "sources": [
        {
          "label": "copy_file_range(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/copy_file_range.2.html"
        },
        {
          "label": "copy_file_range(2), FreeBSD",
          "url": "https://man.freebsd.org/cgi/man.cgi?query=copy_file_range&sektion=2"
        },
        {
          "label": "RFC 7862: NFS Version 4 Minor Version 2 Protocol",
          "url": "https://www.rfc-editor.org/rfc/rfc7862"
        }
      ],
      "take": "cp reads your file into a userspace buffer and writes it back out. sendfile fixed file-to-socket. splice fixed the general case but required a pipe. copy_file_range is the one that was missing: file to file, entirely in the kernel, no pipe, no buffer. On NFS 4.2 the data doesn't cross the network at all. On btrfs it's a reflink and costs nothing regardless of file size. On anything else it copies in the page cache, which beats two userspace copies every time. Loop on the return like write(), handle EXDEV if you care about kernels before 5.3, and stop shipping programs that read files into buffers just to write them somewhere else.",
      "example": "/* copy entire file, kernel handles the fast path */\nstruct stat st;\nif (fstat(fd_in, &st) < 0)\n    err(1, \"fstat\");\n\nsize_t left = st.st_size;\nwhile (left > 0) {\n    ssize_t n = copy_file_range(fd_in, NULL, fd_out, NULL, left, 0);\n    if (n < 0) {\n        if (errno == EXDEV) goto fallback;  /* kernel < 5.3 */\n        err(1, \"copy_file_range\");\n    }\n    if (n == 0) break;          /* EOF or sparse hole */\n    left -= n;\n}",
      "exampleLang": "c",
      "ref": "copy_file_range(2)",
      "url": "https://rodhat.com/syscalls/copy_file_range/",
      "sinceParsed": {
        "kind": "linux",
        "min": [
          4,
          5,
          0
        ]
      },
      "tips": [
        "https://rodhat.com/tips/2026-10-08-copy-file-range-kernel-file-copies/"
      ],
      "related": [
        "https://rodhat.com/syscalls/splice/",
        "https://rodhat.com/syscalls/statx/",
        "https://rodhat.com/syscalls/memfd_create/"
      ]
    },
    {
      "slug": "jail",
      "name": "jail",
      "section": "2",
      "os": "freebsd",
      "category": "sandboxing",
      "summary": "Create a FreeBSD jail and lock the current process into it (jail_set/jail_attach for more).",
      "synopsis": "#include <sys/param.h>\n#include <sys/jail.h>\n\nint jail(struct jail *jail);\nint jail_attach(int jid);\nint jail_remove(int jid);\n\n#include <sys/uio.h>\n\nint jail_get(struct iovec *iov, u_int niov, int flags);\nint jail_set(struct iovec *iov, u_int niov, int flags);",
      "since": "FreeBSD 4.0",
      "glibc": null,
      "flags": [
        {
          "name": "JAIL_CREATE",
          "meaning": "jail_set(): create a new jail; jid/name must not already exist."
        },
        {
          "name": "JAIL_UPDATE",
          "meaning": "jail_set(): modify an existing jail. With JAIL_CREATE: create if missing, else modify."
        },
        {
          "name": "JAIL_ATTACH",
          "meaning": "jail_set(): also attach the calling process, as jail_attach() would."
        },
        {
          "name": "JAIL_GET_DESC / JAIL_OWN_DESC",
          "meaning": "Return a jail descriptor fd; an owning descriptor removes the jail when closed."
        },
        {
          "name": "JAIL_DYING",
          "meaning": "jail_get(): allow fetching a jail that is being removed (no effect in jail_set())."
        },
        {
          "name": "enforce_statfs",
          "meaning": "Jail parameter limiting what statfs(2) and friends reveal: 0 all mounts, 1 only below the jail root, 2 (default) only the root's own mount."
        }
      ],
      "errors": [
        {
          "name": "EPERM",
          "meaning": "Not the super-user, children.max exceeded, or a parameter loosened beyond the current environment."
        },
        {
          "name": "EEXIST",
          "meaning": "jail_set(): jid or name exists and JAIL_UPDATE is not set."
        },
        {
          "name": "ENOENT",
          "meaning": "jail_set(): jid or name doesn't exist and JAIL_CREATE is not set."
        },
        {
          "name": "EINVAL",
          "meaning": "Wrong API version, bad parameter size/range/name, or neither JAIL_CREATE nor JAIL_UPDATE."
        },
        {
          "name": "EAGAIN",
          "meaning": "No free jail ID could be found."
        }
      ],
      "bsd": "Linux: no single equivalent; namespaces via clone(2)/unshare(2)/setns(2) plus chroot/pivot_root, assembled by container runtimes.",
      "man": "https://man.freebsd.org/cgi/man.cgi?query=jail&sektion=2",
      "sources": [
        {
          "label": "jail(2), FreeBSD",
          "url": "https://man.freebsd.org/cgi/man.cgi?query=jail&sektion=2"
        },
        {
          "label": "jail(8), FreeBSD",
          "url": "https://man.freebsd.org/cgi/man.cgi?query=jail&sektion=8"
        },
        {
          "label": "setns(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/setns.2.html"
        }
      ],
      "take": "I spent six years saying jails already solved containers and Docker was chroot with a marketing budget. Half right. OCI solved image distribution, and jails never tried. But for one service on hardware you own, a jail on its own ZFS dataset is tighter isolation with less machinery: clone a base snapshot for a copy-on-write root, keep enforce_statfs at 2 so the host's mounts stay invisible, snapshot before every deploy, and roll back in the time it takes ZFS to flip a pointer. If you're shipping to a fleet of strangers, use the container stack. Otherwise, sit with this.",
      "example": "# copy-on-write jail root from a clean base snapshot\nzfs snapshot zroot/jails/base@clean\nzfs clone zroot/jails/base@clean zroot/jails/myapp\n\n# create the jail defined as 'myapp' in /etc/jail.conf\njail -c myapp\n\n# before every deploy\nzfs snapshot zroot/jails/myapp@pre-deploy-2026-07-23\n\n# deploy went sideways: stop, roll back, start\njail -r myapp\nzfs rollback zroot/jails/myapp@pre-deploy-2026-07-23\njail -c myapp",
      "exampleLang": "sh",
      "ref": "jail(2)",
      "url": "https://rodhat.com/syscalls/jail/",
      "sinceParsed": {
        "kind": "freebsd",
        "min": [
          4,
          0,
          0
        ]
      },
      "tips": [
        "https://rodhat.com/tips/2026-07-23-zfs-jails-poor-mans-containers/"
      ],
      "related": [
        "https://rodhat.com/syscalls/setns/"
      ]
    },
    {
      "slug": "rseq",
      "name": "rseq",
      "section": "2",
      "os": "linux",
      "category": "scheduling",
      "summary": "Register a per-thread struct that the kernel keeps updated with the current CPU ID, enabling lock-free per-CPU data updates via restartable critical sections.",
      "synopsis": "#include <linux/rseq.h>\n#include <sys/syscall.h>\n#include <unistd.h>\n\nlong syscall(SYS_rseq, struct rseq *rseq, uint32_t rseq_len,\n             int flags, uint32_t sig);",
      "since": "Linux 4.18",
      "glibc": "none (use syscall(2)); glibc 2.35+ registers automatically and exposes __rseq_offset/__rseq_size",
      "flags": [
        {
          "name": "RSEQ_FLAG_UNREGISTER",
          "meaning": "Unregister the previously registered rseq struct for this thread."
        },
        {
          "name": "struct rseq.cpu_id",
          "meaning": "Kernel-maintained current CPU ID; RSEQ_CPU_ID_UNINITIALIZED (-1) before registration, RSEQ_CPU_ID_REGISTRATION_FAILED (-2) on failure."
        },
        {
          "name": "struct rseq.rseq_cs",
          "meaning": "Pointer to an active struct rseq_cs critical section descriptor. If set when the kernel migrates or preempts the thread, execution jumps to rseq_cs.abort_ip and rseq_cs is cleared."
        },
        {
          "name": "struct rseq_cs.start_ip / post_commit_offset",
          "meaning": "Define the critical-section code window [start_ip, start_ip + post_commit_offset). Preemption inside this window triggers the abort."
        },
        {
          "name": "struct rseq_cs.abort_ip",
          "meaning": "Instruction to jump to on abort. The 4 bytes immediately before this address must equal the sig passed to sys_rseq."
        }
      ],
      "errors": [
        {
          "name": "EINVAL",
          "meaning": "rseq_len is not sizeof(struct rseq), or flags has unknown bits set (without RSEQ_FLAG_UNREGISTER)."
        },
        {
          "name": "EBUSY",
          "meaning": "Thread already has a registered rseq struct; unregister with RSEQ_FLAG_UNREGISTER first."
        },
        {
          "name": "EFAULT",
          "meaning": "rseq pointer is NULL or not readable/writable by the process."
        },
        {
          "name": "EPERM",
          "meaning": "RSEQ_FLAG_UNREGISTER: sig does not match the sig used at registration."
        }
      ],
      "bsd": "No equivalent on FreeBSD, OpenBSD, or NetBSD. Per-CPU userspace data structures at this layer are Linux-specific. Portable alternative: sched_getcpu(3) on Linux, thread-local storage everywhere else.",
      "man": "https://man7.org/linux/man-pages/man2/rseq.2.html",
      "sources": [
        {
          "label": "rseq(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/rseq.2.html"
        },
        {
          "label": "Linux restartable sequences, EfficiOS (Mathieu Desnoyers)",
          "url": "https://www.efficios.com/blog/2019/02/08/linux-restartable-sequences/"
        }
      ],
      "take": "glibc 2.35 has been registering rseq in every thread you spawn since March 2022 and most people have no idea. The kernel updates cpu_id on every context switch; reading it costs one load. The critical-section mechanism is genuinely clever: you define a code window, and if you get preempted inside it, the kernel jumps you to your abort handler rather than letting a half-done per-CPU write commit. Fast path is pure userspace, slow path is rare. musl doesn't use it, which is fine until you're writing an allocator. If you are, the infrastructure is already in your threads.",
      "example": "/* read current CPU without a syscall (glibc 2.35+) */\n#include <sys/rseq.h>\n#include <pthread.h>\n\nstruct rseq *r = (struct rseq *)((char *)pthread_self() + __rseq_offset);\nint cpu = r->cpu_id;\n\n/* manual registration on older glibc or musl */\n#include <linux/rseq.h>\n#include <sys/syscall.h>\n#define RSEQ_SIG 0x53053053   /* x86-64 */\nstatic __thread struct rseq rseq_abi = { .cpu_id = RSEQ_CPU_ID_UNINITIALIZED };\nsyscall(SYS_rseq, &rseq_abi, sizeof(rseq_abi), 0, RSEQ_SIG);",
      "exampleLang": "c",
      "ref": "rseq(2)",
      "url": "https://rodhat.com/syscalls/rseq/",
      "sinceParsed": {
        "kind": "linux",
        "min": [
          4,
          18,
          0
        ]
      },
      "tips": [
        "https://rodhat.com/tips/2026-10-09-rseq-per-cpu-without-locks/"
      ],
      "related": [
        "https://rodhat.com/syscalls/futex/",
        "https://rodhat.com/syscalls/sched_setaffinity/"
      ]
    },
    {
      "slug": "clone3",
      "name": "clone3",
      "section": "2",
      "os": "linux",
      "category": "process",
      "summary": "Fork a child process using a struct-based API; write a pidfd atomically with CLONE_PIDFD.",
      "synopsis": "#include <linux/sched.h>\n#include <sys/syscall.h>\n#include <unistd.h>\n\nlong syscall(SYS_clone3, struct clone_args *args, size_t size);",
      "since": "Linux 5.3",
      "glibc": "none (use syscall(2))",
      "flags": [
        {
          "name": "CLONE_PIDFD",
          "meaning": "Write a pidfd to args.pidfd on return in the parent; the handle is written before any return, eliminating the PID-reuse race of fork+pidfd_open."
        },
        {
          "name": "CLONE_CLEAR_SIGHAND",
          "since": "Linux 5.3",
          "meaning": "Reset all signal handlers to SIG_DFL in the child; SIG_IGN handlers are not touched (they follow exec semantics).",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              5,
              3,
              0
            ]
          }
        },
        {
          "name": "CLONE_INTO_CGROUP",
          "since": "Linux 5.7",
          "meaning": "Place the child in the cgroup v2 specified by args.cgroup (a cgroup fd) from the first instruction; no fork-then-move race.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              5,
              7,
              0
            ]
          }
        },
        {
          "name": "set_tid / set_tid_size",
          "since": "Linux 5.5",
          "meaning": "Assign a specific TID in each namespace level listed in args.set_tid. Used by checkpoint/restore tools (CRIU). Requires CAP_SYS_ADMIN in each namespace.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              5,
              5,
              0
            ]
          }
        },
        {
          "name": "struct clone_args size",
          "meaning": "Third argument to clone3; the version number. Zero-fill the struct and pass sizeof(struct clone_args). Unknown non-zero fields → E2BIG."
        }
      ],
      "errors": [
        {
          "name": "ENOSYS",
          "meaning": "Kernel is older than 5.3. Fall back to fork(2) or clone(2)."
        },
        {
          "name": "E2BIG",
          "meaning": "size is larger than the kernel knows and a field beyond the known size is non-zero."
        },
        {
          "name": "EINVAL",
          "meaning": "Inconsistent or unknown flags, bad stack/size combination, or invalid pidfd pointer."
        },
        {
          "name": "ENOMEM",
          "meaning": "Insufficient kernel memory for the new task."
        },
        {
          "name": "EPERM",
          "meaning": "CLONE_INTO_CGROUP requires CAP_SYS_ADMIN; some flag combinations require privilege."
        }
      ],
      "bsd": "FreeBSD: pdfork(2) (FreeBSD 9.0) writes a process descriptor on fork in a single call; pdkill(2), pdwait4(2), and EVFILT_PROCDESC in kqueue(2) complete the API. OpenBSD: none.",
      "man": "https://man7.org/linux/man-pages/man2/clone3.2.html",
      "sources": [
        {
          "label": "clone3(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/clone3.2.html"
        },
        {
          "label": "pdfork(2), FreeBSD",
          "url": "https://man.freebsd.org/cgi/man.cgi?query=pdfork&sektion=2"
        }
      ],
      "take": "clone(2) has been there since Linux 2.0 and the calling convention is different on x86-64 and ARM, documented in a footnote everyone ignores until production burns. clone3 replaced the whole mess with a struct: zero-fill it, set what you need, pass sizeof. The one flag worth the upgrade by itself is CLONE_PIDFD: the kernel writes a pidfd to args.pidfd before returning in the parent, so there is no window between fork and pidfd_open where the child can exit, get reaped, and hand the PID to a stranger. CLONE_CLEAR_SIGHAND is the other one you probably want: inherited signal handlers in a worker subprocess are a quiet source of misbehavior that fork never warned you about. Handle ENOSYS and fall back to fork on old kernels. Everything else is just less architecture-specific footgunning.",
      "example": "#include <linux/sched.h>\n#include <sys/syscall.h>\n#include <unistd.h>\n\nint child_pidfd = -1;\nstruct clone_args args = {\n    .flags      = CLONE_PIDFD | CLONE_CLEAR_SIGHAND,\n    .pidfd      = (uint64_t)(uintptr_t)&child_pidfd,\n    .exit_signal = SIGCHLD,\n};\npid_t pid = syscall(SYS_clone3, &args, sizeof(args));\nif (pid < 0 && errno == ENOSYS) {\n    pid = fork();           /* kernel < 5.3 */\n    if (pid > 0)\n        child_pidfd = syscall(SYS_pidfd_open, pid, 0);\n} else if (pid < 0) {\n    err(1, \"clone3\");\n}\nif (pid == 0) {\n    execvp(argv[0], argv); /* child */\n    err(127, \"exec\");\n}",
      "exampleLang": "c",
      "ref": "clone3(2)",
      "url": "https://rodhat.com/syscalls/clone3/",
      "sinceParsed": {
        "kind": "linux",
        "min": [
          5,
          3,
          0
        ]
      },
      "tips": [
        "https://rodhat.com/tips/2026-10-10-clone3-pidfd-fork/"
      ],
      "related": [
        "https://rodhat.com/syscalls/pidfd_open/",
        "https://rodhat.com/syscalls/pidfd_send_signal/",
        "https://rodhat.com/syscalls/landlock_create_ruleset/",
        "https://rodhat.com/syscalls/close_range/"
      ]
    },
    {
      "slug": "pkey_alloc",
      "name": "pkey_alloc / pkey_mprotect",
      "section": "2",
      "os": "linux",
      "category": "memory",
      "summary": "Allocate a memory protection key and use it to gate access to a memory region via a per-thread register, without a syscall per permission switch.",
      "synopsis": "#include <sys/pkey.h>\n\nint pkey_alloc(unsigned int flags, unsigned int access_rights);\nint pkey_mprotect(void *addr, size_t len, int prot, int pkey);\nint pkey_free(int pkey);\n\n/* permission switches via glibc wrapper (WRPKRU instruction): */\nint pkey_set(int pkey, unsigned int access_rights);\nint pkey_get(int pkey);",
      "since": "Linux 4.8",
      "glibc": "glibc 2.27 (pkey_alloc, pkey_mprotect, pkey_free, pkey_set, pkey_get)",
      "flags": [
        {
          "name": "PKEY_DISABLE_ACCESS",
          "meaning": "Set the Access Disable bit in PKRU for this key: any read or write to tagged memory faults with SIGSEGV."
        },
        {
          "name": "PKEY_DISABLE_WRITE",
          "meaning": "Set the Write Disable bit in PKRU for this key: writes fault, reads are allowed."
        }
      ],
      "errors": [
        {
          "name": "ENOSYS",
          "meaning": "Kernel older than 4.8, or CPU lacks PKU feature (non-x86, or Intel pre-Skylake / AMD pre-Zen)."
        },
        {
          "name": "EINVAL",
          "meaning": "flags is non-zero, or access_rights contains bits other than PKEY_DISABLE_ACCESS and PKEY_DISABLE_WRITE."
        },
        {
          "name": "ENOSPC",
          "meaning": "All 15 user-allocatable pkeys (1-15) are in use; pkey 0 is implicit and never allocatable."
        }
      ],
      "bsd": "FreeBSD: no equivalent in base as of 14.2. ARM: MTE (Memory Tagging Extension, ARMv8.5-A) provides a related but incompatible mechanism. OpenBSD: none.",
      "man": "https://man7.org/linux/man-pages/man2/pkey_alloc.2.html",
      "sources": [
        {
          "label": "pkey_alloc(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/pkey_alloc.2.html"
        },
        {
          "label": "pkey_mprotect(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/pkey_mprotect.2.html"
        }
      ],
      "take": "mprotect(2) costs a syscall. For setup code that runs once, fine. For a JIT that toggles W^X on every emit pass, not fine. PKU fixes this: pkey_mprotect stamps a key number onto the VMA, and WRPKRU changes the per-thread PKRU register to enable or disable that key without ever entering the kernel. It's x86-only (Intel Skylake+, AMD Zen 1+), you get 15 usable keys, and the signal handler footgun will catch you: PKRU is inherited at signal delivery, so if your handler touches a disabled domain it faults. Read PKRU at handler entry and unlock what you need. Check for ENOSYS on every architecture you ship to. Everything else is straightforward.",
      "example": "#include <sys/mman.h>\n#include <sys/pkey.h>\n#include <err.h>\n#include <string.h>\n\nint pk = pkey_alloc(0, PKEY_DISABLE_ACCESS);\nif (pk == -1)\n    err(1, \"pkey_alloc\");  /* ENOSYS on non-x86 or kernel < 4.8 */\n\nvoid *buf = mmap(NULL, 4096, PROT_READ | PROT_WRITE,\n                 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);\nif (buf == MAP_FAILED)\n    err(1, \"mmap\");\n\nif (pkey_mprotect(buf, 4096, PROT_READ | PROT_WRITE, pk) == -1)\n    err(1, \"pkey_mprotect\");\n\n/* access here faults (SIGSEGV) */\n\npkey_set(pk, 0);                     /* unlock: one WRPKRU, no syscall */\nmemcpy(buf, key_material, key_len);\npkey_set(pk, PKEY_DISABLE_ACCESS);   /* lock again */\n\npkey_free(pk);",
      "exampleLang": "c",
      "ref": "pkey_alloc / pkey_mprotect(2)",
      "url": "https://rodhat.com/syscalls/pkey_alloc/",
      "sinceParsed": {
        "kind": "linux",
        "min": [
          4,
          8,
          0
        ]
      },
      "tips": [
        "https://rodhat.com/tips/2026-10-11-pkey-mprotect-in-process-memory-isolation/"
      ],
      "related": [
        "https://rodhat.com/syscalls/mlock/",
        "https://rodhat.com/syscalls/memfd_secret/",
        "https://rodhat.com/syscalls/seccomp/",
        "https://rodhat.com/syscalls/landlock_create_ruleset/"
      ]
    },
    {
      "slug": "unshare",
      "name": "unshare",
      "section": "2",
      "os": "linux",
      "category": "process",
      "summary": "Create new namespaces and move the calling process into them without forking.",
      "synopsis": "#define _GNU_SOURCE\n#include <sched.h>\n\nint unshare(int flags);",
      "since": "Linux 2.6.16",
      "glibc": "glibc 2.14",
      "flags": [
        {
          "name": "CLONE_NEWNS",
          "since": "Linux 2.4.19",
          "meaning": "New mount namespace; the caller gets a private copy of the mount table.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              2,
              4,
              19
            ]
          }
        },
        {
          "name": "CLONE_NEWUTS",
          "since": "Linux 2.6.19",
          "meaning": "New UTS namespace; hostname and domainname changes are private.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              2,
              6,
              19
            ]
          }
        },
        {
          "name": "CLONE_NEWIPC",
          "since": "Linux 2.6.19",
          "meaning": "New IPC namespace; SysV IPC objects and POSIX message queues are isolated.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              2,
              6,
              19
            ]
          }
        },
        {
          "name": "CLONE_NEWNET",
          "since": "Linux 2.6.24",
          "meaning": "New network namespace; private network stack, interfaces, routing tables.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              2,
              6,
              24
            ]
          }
        },
        {
          "name": "CLONE_NEWPID",
          "since": "Linux 3.8",
          "meaning": "New PID namespace for future children only. The caller stays in its original namespace; the next forked child becomes PID 1 in the new namespace.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              3,
              8,
              0
            ]
          }
        },
        {
          "name": "CLONE_NEWUSER",
          "since": "Linux 3.8",
          "meaning": "New user namespace; unprivileged processes may call this and then write uid_map/gid_map to appear as root inside.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              3,
              8,
              0
            ]
          }
        },
        {
          "name": "CLONE_NEWCGROUP",
          "since": "Linux 4.6",
          "meaning": "New cgroup namespace; the caller sees its current cgroup as root.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              4,
              6,
              0
            ]
          }
        },
        {
          "name": "CLONE_NEWTIME",
          "since": "Linux 5.6",
          "meaning": "New time namespace; CLOCK_MONOTONIC and CLOCK_BOOTTIME can be offset independently.",
          "sinceParsed": {
            "kind": "linux",
            "min": [
              5,
              6,
              0
            ]
          }
        }
      ],
      "errors": [
        {
          "name": "EPERM",
          "meaning": "Caller lacks CAP_SYS_ADMIN (or, for CLONE_NEWUSER, the unprivileged_userns_clone sysctl is 0)."
        },
        {
          "name": "EINVAL",
          "meaning": "Unknown flag, or CLONE_NEWUSER requested with a multithreaded process."
        },
        {
          "name": "ENOMEM",
          "meaning": "Insufficient kernel memory to create the namespace."
        },
        {
          "name": "ENOSPC",
          "meaning": "The per-user or system-wide namespace limit has been reached."
        }
      ],
      "bsd": "FreeBSD: jail(2) (FreeBSD 4.0) wraps filesystem, network, and PID visibility in one coarser-grained call. OpenBSD: none; isolation is pledge(2), unveil(2), and chroot(2).",
      "man": "https://man7.org/linux/man-pages/man2/unshare.2.html",
      "sources": [
        {
          "label": "unshare(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/unshare.2.html"
        },
        {
          "label": "setns(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/setns.2.html"
        },
        {
          "label": "jail(2), FreeBSD",
          "url": "https://man.freebsd.org/cgi/man.cgi?query=jail&sektion=2"
        }
      ],
      "take": "unshare(CLONE_NEWPID) does not move the caller into the new PID namespace. The caller stays where it is. The next fork creates a child that sees itself as PID 1. This surprises everyone once. Mount a new /proc before anything tries to read it. CLONE_NEWUSER is the exception to the CAP_SYS_ADMIN rule: unprivileged processes can create user namespaces, write uid_map and gid_map, and own root inside; this is rootless containers. FreeBSD's jail does the same job with one call and fewer footguns, but you can't compose namespace types independently. The composability is both the feature and the reason container runtimes are thousands of lines of C.",
      "example": "#define _GNU_SOURCE\n#include <sched.h>\n#include <err.h>\n\n/* private mount and UTS namespaces */\nif (unshare(CLONE_NEWNS | CLONE_NEWUTS) < 0)\n    err(1, \"unshare\");\n\n/* PID namespace: caller stays in parent ns; child is PID 1 */\nif (unshare(CLONE_NEWPID) < 0)\n    err(1, \"unshare NEWPID\");\npid_t child = fork();\nif (child == 0) {\n    /* PID 1 in new namespace; mount /proc before any ps(1) */\n    mount(\"proc\", \"/proc\", \"proc\", 0, NULL);\n    execvp(argv[0], argv);\n}",
      "exampleLang": "c",
      "ref": "unshare(2)",
      "url": "https://rodhat.com/syscalls/unshare/",
      "sinceParsed": {
        "kind": "linux",
        "min": [
          2,
          6,
          16
        ]
      },
      "tips": [
        "https://rodhat.com/tips/2026-10-12-setns-unshare-linux-namespaces/"
      ],
      "related": [
        "https://rodhat.com/syscalls/setns/",
        "https://rodhat.com/syscalls/clone3/",
        "https://rodhat.com/syscalls/pidfd_open/",
        "https://rodhat.com/syscalls/landlock_create_ruleset/"
      ]
    },
    {
      "slug": "cachestat",
      "name": "cachestat",
      "section": "2",
      "os": "linux",
      "category": "filesystem",
      "summary": "Query page cache status (resident, dirty, writeback, evicted) for a byte range of an open file.",
      "synopsis": "#include <linux/mman.h>\n#include <sys/syscall.h>\n#include <unistd.h>\n\nlong syscall(SYS_cachestat, int fd,\n             struct cachestat_range *cstat_range,\n             struct cachestat *cstat,\n             unsigned int flags);",
      "since": "Linux 6.5",
      "glibc": "none (use syscall(2)); glibc 2.41+ may provide a wrapper",
      "flags": [
        {
          "name": "struct cachestat_range",
          "meaning": "off: start byte; len: length in bytes (0 = to EOF)."
        },
        {
          "name": "nr_cache",
          "meaning": "Pages currently resident in the page cache."
        },
        {
          "name": "nr_dirty",
          "meaning": "Pages modified in cache but not yet written to storage."
        },
        {
          "name": "nr_writeback",
          "meaning": "Pages currently being written back to storage."
        },
        {
          "name": "nr_evicted",
          "meaning": "Pages evicted from cache since the last cachestat call on this fd."
        },
        {
          "name": "nr_recently_evicted",
          "meaning": "Pages evicted under memory pressure (PG_workingset) since the last call."
        }
      ],
      "errors": [
        {
          "name": "EFAULT",
          "meaning": "cstat_range or cstat is not a valid user address."
        },
        {
          "name": "EINVAL",
          "meaning": "flags is non-zero."
        },
        {
          "name": "EBADF",
          "meaning": "fd is not a valid open file descriptor."
        }
      ],
      "bsd": "FreeBSD: no equivalent. mincore(3) is available with the same mmap-first requirement as Linux mincore(2).",
      "man": "https://man7.org/linux/man-pages/man2/cachestat.2.html",
      "sources": [
        {
          "label": "cachestat(2), man7.org",
          "url": "https://man7.org/linux/man-pages/man2/cachestat.2.html"
        },
        {
          "label": "cachestat: query the page cache, LWN.net",
          "url": "https://lwn.net/Articles/933415/"
        }
      ],
      "take": "cachestat(2) replaces the mmap-first mincore(2) dance for resident-page checks and adds what mincore never had: dirty count, writeback count, and eviction statistics. Pass len=0 for the whole file. flags must be 0. No glibc wrapper on most systems; use syscall(SYS_cachestat, ...). Hugepage-backed files and DAX mappings report zeros. Added in Linux 6.5; check uname -r before deploying.",
      "example": "#include <linux/mman.h>\n#include <sys/syscall.h>\n#include <unistd.h>\n\nstruct cachestat_range range = { .off = 0, .len = 0 };\nstruct cachestat cs = {0};\n\nif (syscall(SYS_cachestat, fd, &range, &cs, 0) < 0)\n    err(1, \"cachestat\");\n\nprintf(\"resident: %llu pages\\ndirty: %llu pages\\n\",\n       cs.nr_cache, cs.nr_dirty);",
      "exampleLang": "c",
      "ref": "cachestat(2)",
      "url": "https://rodhat.com/syscalls/cachestat/",
      "sinceParsed": {
        "kind": "linux",
        "min": [
          6,
          5,
          0
        ]
      },
      "tips": [
        "https://rodhat.com/tips/2026-10-13-cachestat-page-cache-query/"
      ],
      "related": [
        "https://rodhat.com/syscalls/madvise/",
        "https://rodhat.com/syscalls/process_madvise/",
        "https://rodhat.com/syscalls/statx/",
        "https://rodhat.com/syscalls/openat2/"
      ]
    }
  ]
}