git: f3f3e3c44d3b - main - fd: add close_range(..., CLOSE_RANGE_CLOEXEC)

From: Mateusz Guzik <mjg_at_FreeBSD.org>
Date: Thu, 03 Mar 2022 17:30:53 UTC
The branch main has been updated by mjg:

URL: https://cgit.FreeBSD.org/src/commit/?id=f3f3e3c44d3b1776653bbf19eab17ce006a815d8

commit f3f3e3c44d3b1776653bbf19eab17ce006a815d8
Author:     Mateusz Guzik <mjg@FreeBSD.org>
AuthorDate: 2022-03-03 12:45:11 +0000
Commit:     Mateusz Guzik <mjg@FreeBSD.org>
CommitDate: 2022-03-03 17:21:58 +0000

    fd: add close_range(..., CLOSE_RANGE_CLOEXEC)
    
    For compatibility with Linux.
    
    MFC after:      3 days
    Reviewed by:    markj
    Differential Revision:  https://reviews.freebsd.org/D34424
---
 lib/libc/sys/closefrom.2          | 10 ++++--
 lib/libsysdecode/flags.c          |  7 ++++
 lib/libsysdecode/mktables         |  1 +
 lib/libsysdecode/sysdecode.h      |  1 +
 lib/libsysdecode/sysdecode_mask.3 |  5 ++-
 sys/kern/kern_descrip.c           | 67 +++++++++++++++++++++++++++++----------
 sys/sys/syscallsubr.h             |  2 +-
 sys/sys/unistd.h                  |  5 +++
 tests/sys/file/closefrom_test.c   | 35 +++++++++++++++++++-
 usr.bin/kdump/kdump.c             |  8 +++++
 usr.bin/truss/syscall.h           |  1 +
 usr.bin/truss/syscalls.c          |  5 +++
 12 files changed, 125 insertions(+), 22 deletions(-)

diff --git a/lib/libc/sys/closefrom.2 b/lib/libc/sys/closefrom.2
index db41c617dc7f..64c210f7897a 100644
--- a/lib/libc/sys/closefrom.2
+++ b/lib/libc/sys/closefrom.2
@@ -25,7 +25,7 @@
 .\"
 .\" $FreeBSD$
 .\"
-.Dd April 12, 2020
+.Dd March 3, 2022
 .Dt CLOSEFROM 2
 .Os
 .Sh NAME
@@ -56,8 +56,12 @@ and
 .Fa highfd
 inclusive, clamped to the range of open file descriptors.
 Any errors encountered while closing file descriptors are ignored.
-There are currently no defined
-.Fa flags .
+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.
+.El
 .Sh RETURN VALUES
 Upon successful completion,
 .Fn close_range
diff --git a/lib/libsysdecode/flags.c b/lib/libsysdecode/flags.c
index df461cf0dca1..35bce1ff77f9 100644
--- a/lib/libsysdecode/flags.c
+++ b/lib/libsysdecode/flags.c
@@ -400,6 +400,13 @@ sysdecode_cap_fcntlrights(FILE *fp, uint32_t rights, uint32_t *rem)
 	return (print_mask_int(fp, capfcntl, rights, rem));
 }
 
+bool
+sysdecode_close_range_flags(FILE *fp, int flags, int *rem)
+{
+
+	return (print_mask_int(fp, closerangeflags, flags, rem));
+}
+
 const char *
 sysdecode_extattrnamespace(int namespace)
 {
diff --git a/lib/libsysdecode/mktables b/lib/libsysdecode/mktables
index afc8d82d1556..66f243eab6e6 100644
--- a/lib/libsysdecode/mktables
+++ b/lib/libsysdecode/mktables
@@ -94,6 +94,7 @@ gen_table "accessmode"      "[A-Z]_OK[[:space:]]+0?x?[0-9A-Fa-f]+"         "sys/
 gen_table "acltype"         "ACL_TYPE_[A-Z4_]+[[:space:]]+0x[0-9]+"        "sys/acl.h"
 gen_table "atflags"         "AT_[A-Z_]+[[:space:]]+0x[0-9]+"               "sys/fcntl.h"
 gen_table "capfcntl"        "CAP_FCNTL_[A-Z]+[[:space:]]+\(1"              "sys/capsicum.h"
+gen_table "closerangeflags" "CLOSE_RANGE_[A-Z]+[[:space:]]+\([0-9]+<<[0-9]+\)"       "sys/unistd.h"
 gen_table "extattrns"       "EXTATTR_NAMESPACE_[A-Z]+[[:space:]]+0x[0-9]+" "sys/extattr.h"
 gen_table "fadvisebehav"    "POSIX_FADV_[A-Z]+[[:space:]]+[0-9]+"          "sys/fcntl.h"
 gen_table "openflags"       "O_[A-Z]+[[:space:]]+0x[0-9A-Fa-f]+"           "sys/fcntl.h"	"O_RDONLY|O_RDWR|O_WRONLY"
diff --git a/lib/libsysdecode/sysdecode.h b/lib/libsysdecode/sysdecode.h
index 1a35d0c9d685..5ab17340818c 100644
--- a/lib/libsysdecode/sysdecode.h
+++ b/lib/libsysdecode/sysdecode.h
@@ -43,6 +43,7 @@ const char *sysdecode_atfd(int _fd);
 bool	sysdecode_atflags(FILE *_fp, int _flags, int *_rem);
 bool	sysdecode_cap_fcntlrights(FILE *_fp, uint32_t _rights, uint32_t *_rem);
 void	sysdecode_cap_rights(FILE *_fp, cap_rights_t *_rightsp);
+bool	sysdecode_close_range_flags(FILE *_fp, int _flags, int *_rem);
 const char *sysdecode_cmsg_type(int _cmsg_level, int _cmsg_type);
 const char *sysdecode_extattrnamespace(int _namespace);
 const char *sysdecode_fadvice(int _advice);
diff --git a/lib/libsysdecode/sysdecode_mask.3 b/lib/libsysdecode/sysdecode_mask.3
index 54e182db31d7..45464e333eb4 100644
--- a/lib/libsysdecode/sysdecode_mask.3
+++ b/lib/libsysdecode/sysdecode_mask.3
@@ -24,7 +24,7 @@
 .\"
 .\" $FreeBSD$
 .\"
-.Dd January 16, 2018
+.Dd March 3, 2022
 .Dt sysdecode_mask 3
 .Os
 .Sh NAME
@@ -32,6 +32,7 @@
 .Nm sysdecode_accessmode ,
 .Nm sysdecode_atflags ,
 .Nm sysdecode_capfcntlrights ,
+.Nm sysdecode_close_range_flags ,
 .Nm sysdecode_fcntl_fileflags ,
 .Nm sysdecode_fileflags ,
 .Nm sysdecode_filemode ,
@@ -74,6 +75,8 @@
 .Ft bool
 .Fn sysdecode_cap_fcntlrights "FILE *fp" "uint32_t rights" "uint32_t *rem"
 .Ft bool
+.Fn sysdecode_close_range_flags "FILE *fp" "int flags" "int *rem"
+.Ft bool
 .Fn sysdecode_fcntl_fileflags "FILE *fp" "int flags" "int *rem"
 .Ft bool
 .Fn sysdecode_fileflags "FILE *fp" "fflags_t flags" "fflags_t *rem"
diff --git a/sys/kern/kern_descrip.c b/sys/kern/kern_descrip.c
index ed9d0bc3c253..07984b9a4640 100644
--- a/sys/kern/kern_descrip.c
+++ b/sys/kern/kern_descrip.c
@@ -1393,23 +1393,39 @@ kern_close(struct thread *td, int fd)
 	return (closefp(fdp, fd, fp, td, true, true));
 }
 
-int
-kern_close_range(struct thread *td, u_int lowfd, u_int highfd)
+static int
+close_range_cloexec(struct thread *td, u_int lowfd, u_int highfd)
 {
 	struct filedesc *fdp;
-	const struct fdescenttbl *fdt;
-	struct file *fp;
+	struct fdescenttbl *fdt;
+	struct filedescent *fde;
 	int fd;
 
-	/*
-	 * Check this prior to clamping; closefrom(3) with only fd 0, 1, and 2
-	 * open should not be a usage error.  From a close_range() perspective,
-	 * close_range(3, ~0U, 0) in the same scenario should also likely not
-	 * be a usage error as all fd above 3 are in-fact already closed.
-	 */
-	if (highfd < lowfd) {
-		return (EINVAL);
+	fdp = td->td_proc->p_fd;
+	FILEDESC_XLOCK(fdp);
+	fdt = atomic_load_ptr(&fdp->fd_files);
+	highfd = MIN(highfd, fdt->fdt_nfiles - 1);
+	fd = lowfd;
+	if (__predict_false(fd > highfd)) {
+		goto out_locked;
+	}
+	for (; fd <= highfd; fd++) {
+		fde = &fdt->fdt_ofiles[fd];
+		if (fde->fde_file != NULL)
+			fde->fde_flags |= UF_EXCLOSE;
 	}
+out_locked:
+	FILEDESC_XUNLOCK(fdp);
+	return (0);
+}
+
+static int
+close_range_impl(struct thread *td, u_int lowfd, u_int highfd)
+{
+	struct filedesc *fdp;
+	const struct fdescenttbl *fdt;
+	struct file *fp;
+	int fd;
 
 	fdp = td->td_proc->p_fd;
 	FILEDESC_XLOCK(fdp);
@@ -1440,6 +1456,26 @@ out_unlocked:
 	return (0);
 }
 
+int
+kern_close_range(struct thread *td, int flags, u_int lowfd, u_int highfd)
+{
+
+	/*
+	 * Check this prior to clamping; closefrom(3) with only fd 0, 1, and 2
+	 * open should not be a usage error.  From a close_range() perspective,
+	 * close_range(3, ~0U, 0) in the same scenario should also likely not
+	 * be a usage error as all fd above 3 are in-fact already closed.
+	 */
+	if (highfd < lowfd) {
+		return (EINVAL);
+	}
+
+	if ((flags & CLOSE_RANGE_CLOEXEC) != 0)
+		return (close_range_cloexec(td, lowfd, highfd));
+
+	return (close_range_impl(td, lowfd, highfd));
+}
+
 #ifndef _SYS_SYSPROTO_H_
 struct close_range_args {
 	u_int	lowfd;
@@ -1455,10 +1491,9 @@ sys_close_range(struct thread *td, struct close_range_args *uap)
 	AUDIT_ARG_CMD(uap->highfd);
 	AUDIT_ARG_FFLAGS(uap->flags);
 
-	/* No flags currently defined */
-	if (uap->flags != 0)
+	if ((uap->flags & ~(CLOSE_RANGE_CLOEXEC)) != 0)
 		return (EINVAL);
-	return (kern_close_range(td, uap->lowfd, uap->highfd));
+	return (kern_close_range(td, uap->flags, uap->lowfd, uap->highfd));
 }
 
 #ifdef COMPAT_FREEBSD12
@@ -1483,7 +1518,7 @@ freebsd12_closefrom(struct thread *td, struct freebsd12_closefrom_args *uap)
 	 * closefrom(0) which closes all files.
 	 */
 	lowfd = MAX(0, uap->lowfd);
-	return (kern_close_range(td, lowfd, ~0U));
+	return (kern_close_range(td, 0, lowfd, ~0U));
 }
 #endif	/* COMPAT_FREEBSD12 */
 
diff --git a/sys/sys/syscallsubr.h b/sys/sys/syscallsubr.h
index 62dde37c0c1f..c66a34536444 100644
--- a/sys/sys/syscallsubr.h
+++ b/sys/sys/syscallsubr.h
@@ -111,7 +111,7 @@ int	kern_clock_settime(struct thread *td, clockid_t clock_id,
 	    struct timespec *ats);
 void	kern_thread_cputime(struct thread *targettd, struct timespec *ats);
 void	kern_process_cputime(struct proc *targetp, struct timespec *ats);
-int	kern_close_range(struct thread *td, u_int lowfd, u_int highfd);
+int	kern_close_range(struct thread *td, int flags, u_int lowfd, u_int highfd);
 int	kern_close(struct thread *td, int fd);
 int	kern_connectat(struct thread *td, int dirfd, int fd,
 	    struct sockaddr *sa);
diff --git a/sys/sys/unistd.h b/sys/sys/unistd.h
index be71edbd6e23..449d35678275 100644
--- a/sys/sys/unistd.h
+++ b/sys/sys/unistd.h
@@ -200,6 +200,11 @@
 
 #define	SWAPOFF_FORCE	0x00000001
 
+/*
+ * close_range() options.
+ */
+#define	CLOSE_RANGE_CLOEXEC	(1<<2)
+
 #endif /* __BSD_VISIBLE */
 
 #endif /* !_SYS_UNISTD_H_ */
diff --git a/tests/sys/file/closefrom_test.c b/tests/sys/file/closefrom_test.c
index 8516f7f2598c..d11faa22b55d 100644
--- a/tests/sys/file/closefrom_test.c
+++ b/tests/sys/file/closefrom_test.c
@@ -144,7 +144,7 @@ main(void)
 {
 	struct shared_info *info;
 	pid_t pid;
-	int fd, i, start;
+	int fd, flags, i, start;
 
 	printf("1..20\n");
 
@@ -325,5 +325,38 @@ main(void)
 		fail(info->tag, "%s", info->message);
 	ok(info->tag);
 
+	/* test CLOSE_RANGE_CLOEXEC */
+	for (i = 0; i < 8; i++)
+		(void)devnull();
+	fd = highest_fd();
+	start = fd - 8;
+	if (close_range(start + 1, start + 4, CLOSE_RANGE_CLOEXEC) < 0)
+		fail_err("close_range(..., CLOSE_RANGE_CLOEXEC)");
+	flags = fcntl(start, F_GETFD);
+	if (flags < 0)
+		fail_err("fcntl(.., F_GETFD)");
+	if ((flags & FD_CLOEXEC) != 0)
+		fail("close_range", "CLOSE_RANGE_CLOEXEC set close-on-exec "
+		    "when it should not have on fd %d", start);
+	for (i = start + 1; i <= start + 4; i++) {
+		flags = fcntl(i, F_GETFD);
+		if (flags < 0)
+			fail_err("fcntl(.., F_GETFD)");
+		if ((flags & FD_CLOEXEC) == 0)
+			fail("close_range", "CLOSE_RANGE_CLOEXEC did not set "
+			    "close-on-exec on fd %d", i);
+	}
+	for (; i < start + 8; i++) {
+		flags = fcntl(i, F_GETFD);
+		if (flags < 0)
+			fail_err("fcntl(.., F_GETFD)");
+		if ((flags & FD_CLOEXEC) != 0)
+			fail("close_range", "CLOSE_RANGE_CLOEXEC set close-on-exec "
+			    "when it should not have on fd %d", i);
+	}
+	if (close_range(start, start + 8, 0) < 0)
+		fail_err("close_range");
+	ok("close_range(..., CLOSE_RANGE_CLOEXEC)");
+
 	return (0);
 }
diff --git a/usr.bin/kdump/kdump.c b/usr.bin/kdump/kdump.c
index 611c9021cd78..845e2000a4c2 100644
--- a/usr.bin/kdump/kdump.c
+++ b/usr.bin/kdump/kdump.c
@@ -864,6 +864,14 @@ ktrsyscall(struct ktr_syscall *ktr, u_int sv_flags)
 				ip++;
 				narg--;
 				break;
+			case SYS_close_range:
+				print_number(ip, narg, c);
+				print_number(ip, narg, c);
+				putchar(',');
+				print_mask_arg(sysdecode_close_range_flags, *ip);
+				ip += 3;
+				narg -= 3;
+				break;
 			case SYS_open:
 			case SYS_openat:
 				print_number(ip, narg, c);
diff --git a/usr.bin/truss/syscall.h b/usr.bin/truss/syscall.h
index 7a403d5b680d..53a1fd6ee8d7 100644
--- a/usr.bin/truss/syscall.h
+++ b/usr.bin/truss/syscall.h
@@ -91,6 +91,7 @@ enum Argtype {
 	Atfd,
 	Atflags,
 	CapFcntlRights,
+	Closerangeflags,
 	Extattrnamespace,
 	Fadvice,
 	Fcntl,
diff --git a/usr.bin/truss/syscalls.c b/usr.bin/truss/syscalls.c
index 586b583c3209..d611f3f73471 100644
--- a/usr.bin/truss/syscalls.c
+++ b/usr.bin/truss/syscalls.c
@@ -183,6 +183,8 @@ static const struct syscall_decode decoded_syscalls[] = {
 	  .args = { { Int, 0 } } },
 	{ .name = "closefrom", .ret_type = 1, .nargs = 1,
 	  .args = { { Int, 0 } } },
+	{ .name = "close_range", .ret_type = 1, .nargs = 3,
+	  .args = { { Int, 0 }, { Int, 1 }, { Closerangeflags, 2 } } },
 	{ .name = "compat11.fstat", .ret_type = 1, .nargs = 2,
 	  .args = { { Int, 0 }, { Stat11 | OUT, 1 } } },
 	{ .name = "compat11.fstatat", .ret_type = 1, .nargs = 4,
@@ -1983,6 +1985,9 @@ print_arg(struct syscall_arg *sc, unsigned long *args, register_t *retval,
 	case Fcntl:
 		print_integer_arg(sysdecode_fcntl_cmd, fp, args[sc->offset]);
 		break;
+	case Closerangeflags:
+		print_mask_arg(sysdecode_close_range_flags, fp, args[sc->offset]);
+		break;
 	case Mprot:
 		print_mask_arg(sysdecode_mmap_prot, fp, args[sc->offset]);
 		break;