close_range(2)

πŸ’– close_range(2) β€” the "bye bestie" syscall πŸ’–

okay so you're in the close_range(2) page of the System Calls Manual, and honestly? this one is a whole mood. it's the "close ALL of these file descriptors at once, no cap" call. let's get into it 🫢


🌸 NAME 🌸

close_range β€” close all file descriptors in a given range

πŸ“š LIBRARY πŸ“š

Standard C library (libc, -lc)

✨ SYNOPSIS ✨

#define _GNU_SOURCE         /* See feature_test_macros(7) */
#include <unistd.h>

#include <linux/close_range.h> /* Definition of CLOSE_RANGE_*
                                  constants */

int close_range(unsigned int first, unsigned int last, int flags);

πŸ¦‹ DESCRIPTION πŸ¦‹

okay so the close_range() system call closes every open file descriptor from first to last β€” and yes bestie, last is included, we love an inclusive queen πŸ’…

errors that happen while closing a particular file descriptor are currently ignored. yeah. it just moves on. not me being like "anyway" 😭

flags is a bit mask with 0 or more of these lil cuties:

  • CLOSE_RANGE_CLOEXEC (since Linux 5.11) β€” sets the close-on-exec flag on the descriptors you specified instead of immediately closing them. so it's not "goodbye" it's "goodbye at exec time" πŸ™ˆ
  • CLOSE_RANGE_UNSHARE β€” unshares the specified file descriptors from any other processes before closing them, which dodges races with other threads that share the file descriptor table. keeping it exclusive, periodt ✨

πŸ’« RETURN VALUE πŸ’«

on success, close_range() returns 0. iconic. on error, -1 comes back and errno is set to tell you what went wrong 😭

πŸ’€ ERRORS πŸ’€

  • EINVAL β€” flags is not valid, or first is greater than last. bestie, first > last? make it make sense.
  • the following can happen with CLOSE_RANGE_UNSHARE (when it's constructing the new descriptor table):
    • EMFILE β€” the number of open file descriptors exceeds the limit specified in /proc/sys/fs/nr_open (see proc(5)). this error can occur in situations where that limit was lowered before a call to close_range() where the CLOSE_RANGE_UNSHARE flag is specified. sneaky lil lowered limit, fr fr.
    • ENOMEM β€” insufficient kernel memory was available. the kernel is broke, not you πŸ’œ

πŸ“œ STANDARDS πŸ“œ

None. literally none. no standard claims her. she's free 🌈

πŸŽ€ HISTORY πŸŽ€

FreeBSD. Linux 5.9, glibc 2.34.

πŸ‘‘ NOTES πŸ‘‘

🧚 Closing all open file descriptors

to avoid blindly closing file descriptors across the whole range of possible file descriptors, people sometimes implement this (on Linux) by listing open file descriptors in /proc/self/fd/ and calling close(2) on each one. close_range() takes care of all that without needing /proc and within a single system call, which gives you significant performance benefits. the way that's efficient? obsessed πŸ”₯

πŸ“ Closing file descriptors before exec

file descriptors can be closed safely using

/* we don't want anything past stderr here */
close_range(3, ~0U, CLOSE_RANGE_UNSHARE);
execve(....);

CLOSE_RANGE_UNSHARE is conceptually equivalent to

unshare(CLONE_FILES);
close_range(first, last, 0);

but it can be more efficient: if the unshared range extends past the current maximum number of file descriptors allocated in the caller's file descriptor table (the common case when last is ~0U), the kernel will unshare a new file descriptor table for the caller up to first, copying as few file descriptors as possible. this avoids subsequent close(2) calls entirely β€” the whole operation is complete once the table is unshared. no notes, just efficiency πŸ’…

🌈 Closing files on exec

this is particularly useful in cases where multiple pre-exec setup steps risk conflicting with each other. for example, setting up a seccomp(2) profile can conflict with a close_range() call: if the file descriptors are closed before the seccomp(2) profile is set up, the profile setup can't use them itself, or control their closure; if the file descriptors are closed afterwards, the seccomp profile can't block the close_range() call or any fallbacks. using CLOSE_RANGE_CLOEXEC avoids this whole drama: the descriptors can be marked before the seccomp(2) profile is set up, and the profile can control access to close_range() without affecting the calling process. we love a solution that keeps both parties happy 🫢

⭐ EXAMPLES ⭐

the program shown below opens the files named in its command-line arguments, displays the list of files that it has opened (by iterating through the entries in /proc/PID/fd), uses close_range() to close all file descriptors greater than or equal to 3, and then once more displays the process's list of open files. the following example demonstrates the use of the program:

$ touch /tmp/a /tmp/b /tmp/c;
$ ./a.out /tmp/a /tmp/b /tmp/c;
/tmp/a opened as FD 3
/tmp/b opened as FD 4
/tmp/c opened as FD 5
/proc/self/fd/0 ==> /dev/pts/1
/proc/self/fd/1 ==> /dev/pts/1
/proc/self/fd/2 ==> /dev/pts/1
/proc/self/fd/3 ==> /tmp/a
/proc/self/fd/4 ==> /tmp/b
/proc/self/fd/5 ==> /tmp/c
/proc/self/fd/6 ==> /proc/9005/fd
========= About to call close_range() =======
/proc/self/fd/0 ==> /dev/pts/1
/proc/self/fd/1 ==> /dev/pts/1
/proc/self/fd/2 ==> /dev/pts/1
/proc/self/fd/3 ==> /proc/9005/fd

note that the lines showing the pathname /proc/9005/fd result from the calls to opendir(3).

🌟 Program source

#define _GNU_SOURCE
#include <dirent.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

/* Show the contents of the symbolic links in /proc/self/fd */

static void
show_fds(void)
{
    DIR            *dirp;
    char           path[PATH_MAX], target[PATH_MAX];
    ssize_t        len;
    struct dirent  *dp;

    dirp = opendir("/proc/self/fd");
    if (dirp  == NULL) {
        perror("opendir");
        exit(EXIT_FAILURE);
    }

    for (;;) {
        dp = readdir(dirp);
        if (dp == NULL)
            break;

        if (dp->d_type == DT_LNK) {
            snprintf(path, sizeof(path), "/proc/self/fd/%s",
                     dp->d_name);

            len = readlink(path, target, sizeof(target));
            printf("%s ==> %.*s\n", path, (int) len, target);
        }
    }

    closedir(dirp);
}

int
main(int argc, char *argv[])
{
    int  fd;

    for (size_t j = 1; j < argc; j++) {
        fd = open(argv[j], O_RDONLY);
        if (fd == -1) {
            perror(argv[j]);
            exit(EXIT_FAILURE);
        }
        printf("%s opened as FD %d\n", argv[j], fd);
    }

    show_fds();

    printf("========= About to call close_range() =======\n");

    if (close_range(3, ~0U, 0) == -1) {
        perror("close_range");
        exit(EXIT_FAILURE);
    }

    show_fds();
    exit(EXIT_FAILURE);
}

πŸ’œ SEE ALSO πŸ’œ

close(2)


and that's the whole page bestie πŸŽ€ that's it. go close some file descriptors, you absolute main character πŸ‘‘

this lil masterpiece came to you from Linux man-pages 6.20, dated 2026-02-08, and the page itself is close_range(2) β€” slay, bye πŸ’–βœ¨

πŸ“¦ where this page comes from
  • arch rolling: man-pages 6.20-1 Β· BSD-2-Clause AND BSD-3-Clause AND BSD-4.3TAHOE AND BSD-4-Clause-UC AND GPL-1.0-or-later AND GPL-2.0-only AND GPL-2.0-or-later AND GPL-3.0-or-later AND LGPL-3.0-linking-exception AND LGPL-3.0-or-later AND LicenseRef-Public-Domain AND LicenseRef-UltraPermissive AND Linux-man-pages-1-para AND Linux-man-pages-copyleft AND Linux-man-pages-copyleft-2-para AND Linux-man-pages-copyleft-var AND MIT AND Spencer-94
  • nixpkgs unstable: man-pages 6.20
  • πŸ“œ the page's license: Linux-man-pages-copyleft
  • πŸ’– girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms