Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions exported_symbols/osv_ld-musl.so.1.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ clock_getres
clock_gettime
clock_nanosleep
close
close_range
closedir
closelog
confstr
Expand Down
1 change: 1 addition & 0 deletions exported_symbols/osv_libc.so.6.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ clock_getres
clock_gettime
clock_nanosleep
close
close_range
closedir
closelog
confstr
Expand Down
40 changes: 40 additions & 0 deletions fs/vfs/main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,46 @@ int close(int fd)
return -1;
}

// close_range(2): close (or set close-on-exec on) every open fd in the inclusive
// range [first, last]. CLOSE_RANGE_UNSHARE is a no-op in a single-process
// unikernel (there is nothing to unshare). CLOSE_RANGE_CLOEXEC sets O_CLOEXEC
// instead of closing.
#ifndef CLOSE_RANGE_UNSHARE
#define CLOSE_RANGE_UNSHARE (1U << 1)
#endif
#ifndef CLOSE_RANGE_CLOEXEC
#define CLOSE_RANGE_CLOEXEC (1U << 2)
#endif
Comment thread
gburd marked this conversation as resolved.
extern "C" OSV_LIBC_API
int close_range(unsigned int first, unsigned int last, unsigned int flags)
{
if (first > last || (flags & ~(CLOSE_RANGE_UNSHARE | CLOSE_RANGE_CLOEXEC))) {
errno = EINVAL;
return -1;
}

unsigned int end = last;
if (end >= (unsigned int)FDMAX) {
end = FDMAX - 1;
}
for (unsigned int fd = first; fd <= end; fd++) {
if (flags & CLOSE_RANGE_CLOEXEC) {
// Set close-on-exec on the open fds in the range, leave them open.
struct file *fp;
if (fget(fd, &fp) == 0) {
FD_LOCK(fp);
fp->f_flags |= O_CLOEXEC;
FD_UNLOCK(fp);
fdrop(fp);
}
} else {
// Close each fd, ignoring ones that are not open (as Linux does).
fdclose(fd);
}
}
return 0;
}

TRACEPOINT(trace_vfs_mknod, "\"%s\" 0%0o 0x%x", const char*, mode_t, dev_t);
TRACEPOINT(trace_vfs_mknod_ret, "");
TRACEPOINT(trace_vfs_mknod_err, "%d", int);
Expand Down
2 changes: 2 additions & 0 deletions include/api/aarch64/bits/syscall.h
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@
#define __NR_io_uring_enter 426
#define __NR_io_uring_register 427
#define __NR_epoll_pwait2 441
#define __NR_close_range 436
#define __NR_sys_io_uring_setup __NR_io_uring_setup
#define __NR_sys_io_uring_enter __NR_io_uring_enter
#define __NR_sys_io_uring_register __NR_io_uring_register
Expand Down Expand Up @@ -586,6 +587,7 @@
#define SYS_io_uring_enter 426
#define SYS_io_uring_register 427
#define SYS_epoll_pwait2 441
#define SYS_close_range 436
#define SYS_open_tree 428
#define SYS_move_mount 429
#define SYS_fsopen 430
Expand Down
1 change: 1 addition & 0 deletions include/api/unistd.h
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ int getresuid(uid_t *, uid_t *, uid_t *);
int getresgid(gid_t *, gid_t *, gid_t *);
char *get_current_dir_name(void);
int syncfs(int);
int close_range(unsigned int, unsigned int, unsigned int);
#endif

#if defined(_LARGEFILE64_SOURCE) || defined(_GNU_SOURCE)
Expand Down
2 changes: 2 additions & 0 deletions include/api/x64/bits/syscall.h
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,7 @@
#define SYS_io_uring_enter 426
#define SYS_io_uring_register 427
#define SYS_epoll_pwait2 441
#define SYS_close_range 436

#undef SYS_fstatat
#undef SYS_pread
Expand All @@ -663,6 +664,7 @@
#define __NR_io_uring_enter 426
#define __NR_io_uring_register 427
#define __NR_epoll_pwait2 441
#define __NR_close_range 436
#define __NR_sys_io_uring_setup __NR_io_uring_setup
#define __NR_sys_io_uring_enter __NR_io_uring_enter
#define __NR_sys_io_uring_register __NR_io_uring_register
1 change: 1 addition & 0 deletions linux.cc
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ extern "C" int eventfd2(unsigned int, int);
extern "C" int osv_sigtimedwait(const sigset_t *, siginfo_t *, const struct timespec *);
extern "C" int epoll_pwait2(int epfd, struct epoll_event *events, int maxevents,
const struct timespec *timeout, const sigset_t *sigmask);
extern "C" int close_range(unsigned int first, unsigned int last, unsigned int flags);

extern "C" OSV_LIBC_API long gettid()
{
Expand Down
1 change: 1 addition & 0 deletions modules/tests/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ tests := tst-pthread.so misc-ramdisk.so tst-vblk.so tst-mq-smoke.so tst-bsd-evh.
tst-epoll-pwait2.so \
tst-prctl.so \
tst-mmap-file-cow.so \
tst-close-range.so \
tst-elf-permissions.so misc-mutex.so misc-sockets.so tst-condvar.so \
tst-queue-mpsc.so tst-af-local.so tst-pipe.so tst-yield.so \
misc-ctxsw.so tst-read.so tst-symlink.so tst-openat.so \
Expand Down
1 change: 1 addition & 0 deletions syscalls/syscall_tracepoints.cc.in
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ TRACEPOINT(trace_syscall_gettid, "%d <=", pid_t);
TRACEPOINT(trace_syscall_clock_gettime, "%d <= %d %p", int, clockid_t, struct timespec *);
TRACEPOINT(trace_syscall_clock_getres, "%d <= %d %p", int, clockid_t, struct timespec *);
TRACEPOINT(trace_syscall_close, "%d <= %d", int, int);
TRACEPOINT(trace_syscall_close_range, "%d <= %u %u %u", int, unsigned int, unsigned int, unsigned int);
TRACEPOINT(trace_syscall_pipe2, "%d <= %p 0%0o", int, int *, int);
#if CONF_core_epoll
TRACEPOINT(trace_syscall_epoll_create1, "%d <= 0%0o", int, int);
Expand Down
1 change: 1 addition & 0 deletions syscalls/syscalls.cc.in
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
SYSCALL2(clock_gettime, clockid_t, struct timespec *);
SYSCALL2(clock_getres, clockid_t, struct timespec *);
SYSCALL1(close, int);
SYSCALL3(close_range, unsigned int, unsigned int, unsigned int);
SYSCALL2(pipe2, int *, int);
#if CONF_core_epoll
SYSCALL1(epoll_create1, int);
Expand Down
76 changes: 76 additions & 0 deletions tests/tst-close-range.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright (C) 2026 Greg Burd
*
* This work is open source software, licensed under the terms of the
* BSD license as described in the LICENSE file in the top-level directory.
*/

// Exercises close_range(2). Built and run as part of the OSv test image.

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <unistd.h>
#include <fcntl.h>

#include <errno.h>

#include <cassert>
#include <iostream>

#ifndef CLOSE_RANGE_CLOEXEC
#define CLOSE_RANGE_CLOEXEC (1U << 2)
#endif

// Open n fds pointing at /dev/null and return the lowest one; they are
// contiguous because the fd table hands out the lowest free slots.
static int open_run(int n, int *fds)
{
for (int i = 0; i < n; i++) {
fds[i] = open("/dev/null", O_RDONLY);
assert(fds[i] >= 0);
}
return fds[0];
}

int main()
{
std::cerr << "Running close_range tests\n";

// Close a contiguous run of fds.
int fds[5];
open_run(5, fds);
assert(close_range(fds[0], fds[4], 0) == 0);
// All of them are now closed: fcntl must report EBADF.
for (int i = 0; i < 5; i++) {
errno = 0;
assert(fcntl(fds[i], F_GETFD) == -1 && errno == EBADF);
}

// A range that includes not-open fds is fine (Linux ignores them).
int a = open("/dev/null", O_RDONLY);
assert(a >= 0);
assert(close_range(a, a + 100, 0) == 0);
errno = 0;
assert(fcntl(a, F_GETFD) == -1 && errno == EBADF);

// CLOSE_RANGE_CLOEXEC sets close-on-exec instead of closing.
int b = open("/dev/null", O_RDONLY);
assert(b >= 0);
int flags = fcntl(b, F_GETFD);
assert(flags != -1 && (flags & FD_CLOEXEC) == 0); // not set yet
assert(close_range(b, b, CLOSE_RANGE_CLOEXEC) == 0);
flags = fcntl(b, F_GETFD);
assert(flags != -1); // still open
assert(flags & FD_CLOEXEC); // now set
close(b);

// first > last is rejected; an unknown flag is rejected.
errno = 0;
assert(close_range(10, 5, 0) == -1 && errno == EINVAL);
errno = 0;
assert(close_range(0, 3, 0x40) == -1 && errno == EINVAL);

std::cerr << "close_range tests PASSED\n";
return 0;
}