Index: distrib/sets/lists/comp/mi =================================================================== RCS file: /cvsroot/src/distrib/sets/lists/comp/mi,v retrieving revision 1.2490 diff -u -p -r1.2490 mi --- distrib/sets/lists/comp/mi 22 Mar 2025 17:09:09 -0000 1.2490 +++ distrib/sets/lists/comp/mi 27 Mar 2025 21:14:37 -0000 @@ -4845,6 +4845,7 @@ ./usr/share/man/cat2/clock_settime.0 comp-c-catman .cat ./usr/share/man/cat2/clone.0 comp-c-catman .cat ./usr/share/man/cat2/close.0 comp-c-catman .cat +./usr/share/man/cat2/close_range.0 comp-c-catman .cat ./usr/share/man/cat2/connect.0 comp-c-catman .cat ./usr/share/man/cat2/dup.0 comp-c-catman .cat ./usr/share/man/cat2/dup2.0 comp-c-catman .cat @@ -13431,6 +13432,7 @@ ./usr/share/man/html2/clock_settime.html comp-c-htmlman html ./usr/share/man/html2/clone.html comp-c-htmlman html ./usr/share/man/html2/close.html comp-c-htmlman html +./usr/share/man/html2/close_range.html comp-c-htmlman html ./usr/share/man/html2/connect.html comp-c-htmlman html ./usr/share/man/html2/dup.html comp-c-htmlman html ./usr/share/man/html2/dup2.html comp-c-htmlman html @@ -21846,6 +21848,7 @@ ./usr/share/man/man2/clock_settime.2 comp-c-man .man ./usr/share/man/man2/clone.2 comp-c-man .man ./usr/share/man/man2/close.2 comp-c-man .man +./usr/share/man/man2/close_range.2 comp-c-man .man ./usr/share/man/man2/connect.2 comp-c-man .man ./usr/share/man/man2/dup.2 comp-c-man .man ./usr/share/man/man2/dup2.2 comp-c-man .man Index: distrib/sets/lists/debug/mi =================================================================== RCS file: /cvsroot/src/distrib/sets/lists/debug/mi,v retrieving revision 1.469 diff -u -p -r1.469 mi --- distrib/sets/lists/debug/mi 13 Mar 2025 01:27:27 -0000 1.469 +++ distrib/sets/lists/debug/mi 27 Mar 2025 21:14:37 -0000 @@ -2214,6 +2214,7 @@ ./usr/libdata/debug/usr/tests/lib/libc/sys/t_clock_gettime.debug tests-lib-debug debug,atf,compattestfile ./usr/libdata/debug/usr/tests/lib/libc/sys/t_clock_nanosleep.debug tests-lib-debug debug,atf,compattestfile ./usr/libdata/debug/usr/tests/lib/libc/sys/t_clone.debug tests-lib-debug debug,atf,compattestfile +./usr/libdata/debug/usr/tests/lib/libc/sys/t_close_range.debug tests-lib-debug debug,atf,compattestfile ./usr/libdata/debug/usr/tests/lib/libc/sys/t_connect.debug tests-lib-debug debug,atf,compattestfile ./usr/libdata/debug/usr/tests/lib/libc/sys/t_context.debug tests-obsolete obsolete,compattestfile ./usr/libdata/debug/usr/tests/lib/libc/sys/t_dup.debug tests-lib-debug debug,atf,compattestfile Index: distrib/sets/lists/tests/mi =================================================================== RCS file: /cvsroot/src/distrib/sets/lists/tests/mi,v retrieving revision 1.1361 diff -u -p -r1.1361 mi --- distrib/sets/lists/tests/mi 13 Mar 2025 01:27:27 -0000 1.1361 +++ distrib/sets/lists/tests/mi 27 Mar 2025 21:14:38 -0000 @@ -3327,6 +3327,7 @@ ./usr/tests/lib/libc/sys/t_clock_gettime tests-lib-tests compattestfile,atf ./usr/tests/lib/libc/sys/t_clock_nanosleep tests-lib-tests compattestfile,atf ./usr/tests/lib/libc/sys/t_clone tests-lib-tests compattestfile,atf +./usr/tests/lib/libc/sys/t_close_range tests-lib-tests compattestfile,atf ./usr/tests/lib/libc/sys/t_connect tests-lib-tests compattestfile,atf ./usr/tests/lib/libc/sys/t_context tests-obsolete obsolete ./usr/tests/lib/libc/sys/t_dup tests-lib-tests compattestfile,atf Index: include/unistd.h =================================================================== RCS file: /cvsroot/src/include/unistd.h,v retrieving revision 1.169 diff -u -p -r1.169 unistd.h --- include/unistd.h 1 Nov 2024 18:48:17 -0000 1.169 +++ include/unistd.h 27 Mar 2025 21:14:38 -0000 @@ -338,6 +338,7 @@ int pipe2(int *, int); #if defined(_NETBSD_SOURCE) int acct(const char *); int closefrom(int); +int close_range(unsigned int, unsigned int, int); int des_cipher(const char *, char *, long, int); int des_setkey(const char *); void endusershell(void); Index: lib/libc/sys/Makefile.inc =================================================================== RCS file: /cvsroot/src/lib/libc/sys/Makefile.inc,v retrieving revision 1.259 diff -u -p -r1.259 Makefile.inc --- lib/libc/sys/Makefile.inc 9 Mar 2025 17:02:56 -0000 1.259 +++ lib/libc/sys/Makefile.inc 27 Mar 2025 21:14:38 -0000 @@ -102,6 +102,7 @@ ASM=\ chdir.S chflags.S chmod.S chown.S chroot.S \ clock_getcpuclockid2.S \ __clock_getres50.S __clock_gettime50.S \ + close_range.S \ dup.S dup2.S __dup3100.S \ eventfd.S \ extattrctl.S \ @@ -259,7 +260,7 @@ LintSysPseudoNoerr.c: ${LIBCDIR}/sys/mak MAN+= accept.2 access.2 acct.2 adjtime.2 bind.2 brk.2 chdir.2 \ chflags.2 chmod.2 chown.2 chroot.2 clock_getcpuclockid2.2 \ - clock_settime.2 clone.2 close.2 \ + clock_settime.2 clone.2 close.2 close_range.2 \ connect.2 dup.2 eventfd.2 execve.2 _exit.2 extattr_get_file.2 \ extattrctl.2 \ fcntl.2 fdatasync.2 fdiscard.2 fhopen.2 \ Index: lib/libc/sys/close_range.2 =================================================================== RCS file: lib/libc/sys/close_range.2 diff -N lib/libc/sys/close_range.2 --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ lib/libc/sys/close_range.2 27 Mar 2025 21:14:38 -0000 @@ -0,0 +1,61 @@ +.Dd Feb 16, 2024 +.Dt CLOSE_RANGE 2 +.Os +.Sh NAME +.Nm close_range +.Nd delete open file descriptors +.Sh LIBRARY +.Lb libc +.Sh SYNOPSIS +.In unistd.h +.Ft int +.Fn close_range "u_int first" "u_int last" "int flags" +.Sh DESCRIPTION +The +.Fn close_range +system call deletes all open file descriptors between +.Fa first +and +.Fa last +inclusive, clamped to the range of open file descriptors. +Any errors encountered while closing file descriptors are ignored. +Supported +.Fa flags : +.Bl -tag -width ".Dv CLOSE_RANGE_CLOEXEC" +.It Dv CLOSE_RANGE_CLOEXEC +Set the close-on-exec flag on descriptors in the range instead of closing them. +.It Dv CLOSE_RANGE_UNSHARE +Unshare the file descriptor table from other processes before closing +the file descriptors. +.El +.Sh RETURN VALUES +Upon successful completion, +.Fn close_range +returns a value +of 0. +Otherwise, a value of -1 is returned and the global variable +.Va errno +is set to indicate the error. +.Sh ERRORS +The +.Fn close_range +system call +will fail if: +.Bl -tag -width Er +.It Bq Er EINVAL +The +.Fa last +argument is lower than the +.Fa first +argument. +.It Bq Er EINVAL +An invalid flag was set. +.El +.Sh SEE ALSO +.Xr close 2 +.Xr closefrom 3 +.Sh HISTORY +The +.Fn close_range +function first appeared in +.Nx 11.0 . Index: sys/compat/linux/common/linux_misc.c =================================================================== RCS file: /cvsroot/src/sys/compat/linux/common/linux_misc.c,v retrieving revision 1.267 diff -u -p -r1.267 linux_misc.c --- sys/compat/linux/common/linux_misc.c 1 Oct 2024 16:41:29 -0000 1.267 +++ sys/compat/linux/common/linux_misc.c 27 Mar 2025 21:14:38 -0000 @@ -2077,37 +2077,21 @@ linux_sys_close_range(struct lwp *l, syscallarg(unsigned int) last; syscallarg(unsigned int) flags; } */ - unsigned int fd, last; - file_t *fp; - filedesc_t *fdp; - const unsigned int flags = SCARG(uap, flags); + struct sys_close_range_args ua; + const unsigned int lflags = SCARG(uap, flags); + int flags = 0; - if (flags & ~(LINUX_CLOSE_RANGE_CLOEXEC|LINUX_CLOSE_RANGE_UNSHARE)) - return EINVAL; - if (SCARG(uap, first) > SCARG(uap, last)) + if (lflags & ~(LINUX_CLOSE_RANGE_CLOEXEC|LINUX_CLOSE_RANGE_UNSHARE)) return EINVAL; + if (lflags & LINUX_CLOSE_RANGE_CLOEXEC) + flags |= CLOSE_RANGE_CLOEXEC; + if (lflags & LINUX_CLOSE_RANGE_UNSHARE) + flags |= CLOSE_RANGE_UNSHARE; + SCARG(&ua, first) = SCARG(uap, first); + SCARG(&ua, last) = SCARG(uap, last); + SCARG(&ua, flags) = flags; - if (flags & LINUX_CLOSE_RANGE_UNSHARE) { - fdp = fd_copy(); - fd_free(); - l->l_proc->p_fd = fdp; - l->l_fd = fdp; - } - - last = MIN(SCARG(uap, last), l->l_proc->p_fd->fd_lastfile); - for (fd = SCARG(uap, first); fd <= last; fd++) { - fp = fd_getfile(fd); - if (fp == NULL) - continue; - - if (flags & LINUX_CLOSE_RANGE_CLOEXEC) { - fd_set_exclose(l, fd, true); - fd_putfile(fd); - } else - fd_close(fd); - } - - return 0; + return sys_close_range(l, &ua, retval); } /* Index: sys/kern/sys_descrip.c =================================================================== RCS file: /cvsroot/src/sys/kern/sys_descrip.c,v retrieving revision 1.51 diff -u -p -r1.51 sys_descrip.c --- sys/kern/sys_descrip.c 20 May 2024 09:37:34 -0000 1.51 +++ sys/kern/sys_descrip.c 27 Mar 2025 21:14:38 -0000 @@ -731,3 +731,45 @@ sys_pipe2(struct lwp *l, const struct sy retval[0] = 0; return 0; } + +int +sys_close_range(struct lwp *l, const struct sys_close_range_args *uap, + register_t *retval) +{ + /* { + syscallarg(unsigned int) first; + syscallarg(unsigned int) last; + syscallarg(int) flags; + } */ + const unsigned int flags = SCARG(uap, flags); + unsigned int fd, last; + file_t *fp; + + if (flags & ~(CLOSE_RANGE_UNSHARE|CLOSE_RANGE_CLOEXEC)) + return EINVAL; + + if (SCARG(uap, first) > SCARG(uap, last)) + return EINVAL; + + if ((flags & CLOSE_RANGE_UNSHARE) && l->l_fd->fd_refcnt > 1) { + struct filedesc *fdp = fd_copy(); + fd_free(); + l->l_proc->p_fd = fdp; + l->l_fd = fdp; + } + + last = MIN(SCARG(uap, last), l->l_proc->p_fd->fd_lastfile); + for (fd = SCARG(uap, first); fd <= last; fd++) { + fp = fd_getfile(fd); + if (fp == NULL) + continue; + + if (flags & CLOSE_RANGE_CLOEXEC) { + fd_set_exclose(l, fd, true); + fd_putfile(fd); + } else + fd_close(fd); + } + + return 0; +} Index: sys/kern/syscalls.master =================================================================== RCS file: /cvsroot/src/sys/kern/syscalls.master,v retrieving revision 1.316 diff -u -p -r1.316 syscalls.master --- sys/kern/syscalls.master 9 Oct 2024 16:27:28 -0000 1.316 +++ sys/kern/syscalls.master 27 Mar 2025 21:14:38 -0000 @@ -1068,3 +1068,5 @@ 506 STD MODULAR sysv_ipc { int|sys||semtimedop(int semid, \ struct sembuf *sops, size_t nsops, \ struct timespec *timeout); } +507 STD { int|sys||close_range(u_int first, u_int last, \ + int flags); } Index: sys/sys/unistd.h =================================================================== RCS file: /cvsroot/src/sys/sys/unistd.h,v retrieving revision 1.65 diff -u -p -r1.65 unistd.h --- sys/sys/unistd.h 25 Oct 2023 08:22:25 -0000 1.65 +++ sys/sys/unistd.h 27 Mar 2025 21:14:38 -0000 @@ -344,4 +344,10 @@ /* configurable system strings */ #define _CS_PATH 1 +/* + * close_range(2) options + */ +#define CLOSE_RANGE_UNSHARE 0x2 +#define CLOSE_RANGE_CLOEXEC 0x4 + #endif /* !_SYS_UNISTD_H_ */ Index: tests/lib/libc/sys/Makefile =================================================================== RCS file: /cvsroot/src/tests/lib/libc/sys/Makefile,v retrieving revision 1.78 diff -u -p -r1.78 Makefile --- tests/lib/libc/sys/Makefile 9 Feb 2025 17:10:37 -0000 1.78 +++ tests/lib/libc/sys/Makefile 27 Mar 2025 21:14:38 -0000 @@ -14,6 +14,7 @@ TESTS_C+= t_chroot TESTS_C+= t_clock_gettime TESTS_C+= t_clock_nanosleep TESTS_C+= t_clone +TESTS_C+= t_close_range TESTS_C+= t_connect TESTS_C+= t_dup TESTS_C+= t_eventfd Index: tests/lib/libc/sys/t_close_range.c =================================================================== RCS file: tests/lib/libc/sys/t_close_range.c diff -N tests/lib/libc/sys/t_close_range.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ tests/lib/libc/sys/t_close_range.c 27 Mar 2025 21:14:38 -0000 @@ -0,0 +1,275 @@ +/*- + * Copyright (c) 2011 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Jukka Ruohonen. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#include + +#include + +#include +#include +#include +#include +#include + +static const char path[] = "close_range"; + +ATF_TC_WITH_CLEANUP(close_range_basic); +ATF_TC_HEAD(close_range_basic, tc) +{ + atf_tc_set_md_var(tc, "descr", "A basic test of close_range(2), #1"); +} + +ATF_TC_BODY(close_range_basic, tc) +{ + int fd, cur1, cur2; + + (void)close_range(STDERR_FILENO + 1, UINT_MAX, 0); + + fd = open(path, O_RDONLY | O_CREAT, 0400); + ATF_REQUIRE(fd >= 0); + + cur1 = fcntl(0, F_MAXFD); + + ATF_REQUIRE(cur1 == STDERR_FILENO + 1); + ATF_REQUIRE(close_range(cur1, UINT_MAX, 0) == 0); + + cur2 = fcntl(0, F_MAXFD); + + ATF_REQUIRE(cur1 - 1 == cur2); + ATF_REQUIRE(close(fd) == -1); + ATF_REQUIRE(unlink(path) == 0); +} + +ATF_TC_CLEANUP(close_range_basic, tc) +{ + (void)unlink(path); +} + +ATF_TC_WITH_CLEANUP(close_range_buffer); +ATF_TC_HEAD(close_range_buffer, tc) +{ + atf_tc_set_md_var(tc, "descr", "A basic test of close_range(2), #2"); +} + +ATF_TC_BODY(close_range_buffer, tc) +{ + int buf[16], cur, half; + size_t i; + + /* + * Open a buffer of descriptors, close the half of + * these and verify that the result is consistent. + */ + ATF_REQUIRE(close_range(STDERR_FILENO + 1, UINT_MAX, 0) == 0); + + cur = fcntl(0, F_MAXFD); + ATF_REQUIRE(cur == STDERR_FILENO); + + for (i = 0; i < __arraycount(buf); i++) { + buf[i] = open(path, O_RDWR | O_CREAT, 0600); + ATF_REQUIRE(buf[i] >= 0); + } + + cur = fcntl(0, F_MAXFD); + ATF_REQUIRE(cur == __arraycount(buf) + STDERR_FILENO); + + half = STDERR_FILENO + __arraycount(buf) / 2; + ATF_REQUIRE(close_range(half, UINT_MAX, 0) == 0); + + cur = fcntl(0, F_MAXFD); + ATF_REQUIRE(cur == half - 1); + + for (i = 0; i < __arraycount(buf); i++) + (void)close(buf[i]); +} + +ATF_TC_CLEANUP(close_range_buffer, tc) +{ + (void)unlink(path); +} + +ATF_TC(close_range_err); +ATF_TC_HEAD(close_range_err, tc) +{ + atf_tc_set_md_var(tc, "descr", "Test errors from close_range(2)"); +} + +ATF_TC_BODY(close_range_err, tc) +{ + + errno = 0; + ATF_REQUIRE_ERRNO(EINVAL, close_range(UINT_MAX, 0, 0) == -1); +} + +ATF_TC(close_range_one); +ATF_TC_HEAD(close_range_one, tc) +{ + atf_tc_set_md_var(tc, "descr", "Test close_range(1, UINT_MAX, 0)"); +} + +ATF_TC_BODY(close_range_one, tc) +{ + pid_t pid; + int sta; + + pid = fork(); + ATF_REQUIRE(pid >= 0); + + if (pid == 0) { + + if (close_range(1, UINT_MAX, 0) != 0) + _exit(10); + + _exit(fcntl(0, F_MAXFD)); + } + + + (void)wait(&sta); + + /* + * STDIN_FILENO should still be open; WEXITSTATUS(1) == 0. + */ + if (WIFEXITED(sta) == 0 || WEXITSTATUS(sta) != 0) + atf_tc_fail("not all descriptors were closed"); +} + +ATF_TC_WITH_CLEANUP(close_range_cloexec); +ATF_TC_HEAD(close_range_cloexec, tc) +{ + atf_tc_set_md_var(tc, "descr", "Test close_range(2) with CLOSE_RANGE_CLOEXEC"); +} + +ATF_TC_BODY(close_range_cloexec, tc) +{ + int fd, cur1, cur2; + int flags; + + fd = open(path, O_RDONLY | O_CREAT, 0400); + ATF_REQUIRE(fd >= 0); + + flags = fcntl(fd, F_GETFD); + ATF_REQUIRE(flags != -1); + ATF_REQUIRE(fcntl(fd, F_SETFD, flags | FD_CLOEXEC) != -1); + + cur1 = fcntl(0, F_MAXFD); + ATF_REQUIRE(cur1 == STDERR_FILENO + 1); + ATF_REQUIRE(close_range(cur1, UINT_MAX, CLOSE_RANGE_CLOEXEC) == 0); + + cur2 = fcntl(0, F_MAXFD); + ATF_REQUIRE(cur1 == cur2); + + flags = fcntl(fd, F_GETFD); + ATF_REQUIRE(flags != -1); + ATF_CHECK((flags & FD_CLOEXEC) != 0); + + ATF_REQUIRE(close(fd) == 0); + ATF_REQUIRE(unlink(path) == 0); +} + +ATF_TC_CLEANUP(close_range_cloexec, tc) +{ + (void)unlink(path); +} + +ATF_TC_WITH_CLEANUP(close_range_invalid_flag); +ATF_TC_HEAD(close_range_invalid_flag, tc) +{ + atf_tc_set_md_var(tc, "descr", "Test close_range(2) with an invalid flag"); +} + +ATF_TC_BODY(close_range_invalid_flag, tc) +{ + int cur1, cur2; + int unused_flag = 0x40000000; + + cur1 = fcntl(0, F_MAXFD); + ATF_REQUIRE(cur1 == STDERR_FILENO); + + errno = 0; + ATF_REQUIRE_ERRNO(EINVAL, close_range(STDERR_FILENO + 1, UINT_MAX, unused_flag) == -1); + + cur2 = fcntl(0, F_MAXFD); + ATF_REQUIRE(cur1 == cur2); +} + +ATF_TC_CLEANUP(close_range_invalid_flag, tc) +{ + (void)unlink(path); +} + +ATF_TC_WITH_CLEANUP(close_range_above); +ATF_TC_HEAD(close_range_above, tc) +{ + atf_tc_set_md_var(tc, "descr", "Test close_range(2) doesn't close above a limit"); +} + +ATF_TC_BODY(close_range_above, tc) +{ + int fd, fd2; + int cur1, cur2; + + fd = open(path, O_RDONLY | O_CREAT, 0400); + ATF_REQUIRE(fd >= 0); + + fd2 = dup2(fd, 777); + ATF_REQUIRE(fd2 == 777); + + cur1 = fcntl(0, F_MAXFD); + + ATF_REQUIRE(close_range(fd + 1, fd2 - 1, 0) == 0); + + cur2 = fcntl(0, F_MAXFD); + ATF_REQUIRE(cur1 == cur2); + + ATF_REQUIRE(fcntl(fd, F_GETFD) != -1); + ATF_REQUIRE(close(fd) == 0); + + ATF_REQUIRE(fcntl(fd2, F_GETFD) != -1); + ATF_REQUIRE(close(fd2) == 0); + + ATF_REQUIRE(unlink(path) == 0); +} + +ATF_TC_CLEANUP(close_range_above, tc) +{ + (void)unlink(path); +} + +ATF_TP_ADD_TCS(tp) +{ + + ATF_TP_ADD_TC(tp, close_range_basic); + ATF_TP_ADD_TC(tp, close_range_buffer); + ATF_TP_ADD_TC(tp, close_range_err); + ATF_TP_ADD_TC(tp, close_range_one); + ATF_TP_ADD_TC(tp, close_range_cloexec); + ATF_TP_ADD_TC(tp, close_range_invalid_flag); + ATF_TP_ADD_TC(tp, close_range_above); + + return atf_no_error(); +}