diff --git a/include/unistd.h b/include/unistd.h index 5a4991ef9fe1..02df2515eaaa 100644 --- a/include/unistd.h +++ b/include/unistd.h @@ -494,6 +494,7 @@ struct crypt_data { int acct(const char *); int async_daemon(void); int check_utility_compat(const char *); +int close_range(unsigned int, unsigned int, int); ssize_t copy_file_range(int, off_t *, int, off_t *, size_t, unsigned int); const char * crypt_get_format(void); diff --git a/lib/libc/sys/Makefile.inc b/lib/libc/sys/Makefile.inc index cc882e21a607..913fe56eefbe 100644 --- a/lib/libc/sys/Makefile.inc +++ b/lib/libc/sys/Makefile.inc @@ -371,6 +371,7 @@ MLINKS+=chown.2 fchown.2 \ chown.2 lchown.2 MLINKS+=clock_gettime.2 clock_getres.2 \ clock_gettime.2 clock_settime.2 +MLINKS+=closefrom.2 close_range.2 MLINKS+=nanosleep.2 clock_nanosleep.2 MLINKS+=cpuset.2 cpuset_getid.2 \ cpuset.2 cpuset_setid.2 diff --git a/lib/libc/sys/Symbol.map b/lib/libc/sys/Symbol.map index aa60da2a6789..4d3bd5115af3 100644 --- a/lib/libc/sys/Symbol.map +++ b/lib/libc/sys/Symbol.map @@ -403,6 +403,7 @@ FBSD_1.5 { FBSD_1.6 { __sysctlbyname; + close_range; copy_file_range; fhlink; fhlinkat; diff --git a/lib/libc/sys/closefrom.2 b/lib/libc/sys/closefrom.2 index a0b5fc218666..db41c617dc7f 100644 --- a/lib/libc/sys/closefrom.2 +++ b/lib/libc/sys/closefrom.2 @@ -25,11 +25,12 @@ .\" .\" $FreeBSD$ .\" -.Dd June 12, 2009 +.Dd April 12, 2020 .Dt CLOSEFROM 2 .Os .Sh NAME -.Nm closefrom +.Nm closefrom , +.Nm close_range .Nd delete open file descriptors .Sh LIBRARY .Lb libc @@ -37,6 +38,8 @@ .In unistd.h .Ft void .Fn closefrom "int lowfd" +.Ft int +.Fn close_range "u_int lowfd" "u_int highfd" "int flags" .Sh DESCRIPTION The .Fn closefrom @@ -44,6 +47,40 @@ system call deletes all open file descriptors greater than or equal to .Fa lowfd from the per-process object reference table. Any errors encountered while closing file descriptors are ignored. +.Pp +The +.Fn close_range +system call deletes all open file descriptors between +.Fa lowfd +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 . +.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 highfd +argument is lower than the +.Fa lowfd +argument. +.It Bq Er EINVAL +An invalid flag was set. +.El .Sh SEE ALSO .Xr close 2 .Sh HISTORY diff --git a/sys/compat/freebsd32/syscalls.master b/sys/compat/freebsd32/syscalls.master index 049e0b78500c..0f735faca8e1 100644 --- a/sys/compat/freebsd32/syscalls.master +++ b/sys/compat/freebsd32/syscalls.master @@ -1162,5 +1162,7 @@ 573 AUE_NULL NOPROTO { int sigfastblock(int cmd, uint32_t *ptr); } 574 AUE_REALPATHAT NOPROTO { int __realpathat(int fd, const char *path, \ char *buf, size_t size, int flags); } +575 AUE_NULL NOPROTO { int close_range(u_int lowfd, u_int highfd, \ + int flags); } ; vim: syntax=off diff --git a/sys/kern/capabilities.conf b/sys/kern/capabilities.conf index 555a0232bde3..c465eca0bc6d 100644 --- a/sys/kern/capabilities.conf +++ b/sys/kern/capabilities.conf @@ -131,6 +131,7 @@ clock_gettime ## Always allow file descriptor close(2). ## close +close_range closefrom ## diff --git a/sys/kern/kern_descrip.c b/sys/kern/kern_descrip.c index 535718993631..580956c5a0e3 100644 --- a/sys/kern/kern_descrip.c +++ b/sys/kern/kern_descrip.c @@ -1313,6 +1313,57 @@ kern_close(struct thread *td, int fd) return (closefp(fdp, fd, fp, td, 1)); } +int +kern_close_range(struct thread *td, u_int lowfd, u_int highfd) +{ + struct filedesc *fdp; + int fd, ret; + + ret = 0; + fdp = td->td_proc->p_fd; + FILEDESC_SLOCK(fdp); + + /* + * 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) { + ret = EINVAL; + goto out; + } + /* Clamped to [lowfd, fd_lastfile] */ + highfd = MIN(highfd, fdp->fd_lastfile); + for (fd = lowfd; fd <= highfd; fd++) { + if (fdp->fd_ofiles[fd].fde_file != NULL) { + FILEDESC_SUNLOCK(fdp); + (void)kern_close(td, fd); + FILEDESC_SLOCK(fdp); + } + } +out: + FILEDESC_SUNLOCK(fdp); + return (ret); +} + +#ifndef _SYS_SYSPROTO_H_ +struct close_range_args { + u_int lowfd; + u_int highfd; + int flags; +}; +#endif +int +sys_close_range(struct thread *td, struct close_range_args *uap) +{ + + /* No flags currently defined */ + if (uap->flags != 0) + return (EINVAL); + return (kern_close_range(td, uap->lowfd, uap->highfd)); +} + /* * Close open file descriptors. */ @@ -1325,28 +1376,16 @@ struct closefrom_args { int sys_closefrom(struct thread *td, struct closefrom_args *uap) { - struct filedesc *fdp; - int fd; + u_int lowfd; - fdp = td->td_proc->p_fd; AUDIT_ARG_FD(uap->lowfd); /* * Treat negative starting file descriptor values identical to * closefrom(0) which closes all files. */ - if (uap->lowfd < 0) - uap->lowfd = 0; - FILEDESC_SLOCK(fdp); - for (fd = uap->lowfd; fd <= fdp->fd_lastfile; fd++) { - if (fdp->fd_ofiles[fd].fde_file != NULL) { - FILEDESC_SUNLOCK(fdp); - (void)kern_close(td, fd); - FILEDESC_SLOCK(fdp); - } - } - FILEDESC_SUNLOCK(fdp); - return (0); + lowfd = MAX(0, uap->lowfd); + return (kern_close_range(td, lowfd, ~0U)); } #if defined(COMPAT_43) diff --git a/sys/kern/syscalls.master b/sys/kern/syscalls.master index 8a61610daf06..ec8e03b65ee9 100644 --- a/sys/kern/syscalls.master +++ b/sys/kern/syscalls.master @@ -3227,6 +3227,13 @@ int flags ); } +575 AUE_NULL STD { + int close_range( + u_int lowfd, + u_int highfd, + int flags + ); + } ; Please copy any additions and changes to the following compatability tables: ; sys/compat/freebsd32/syscalls.master diff --git a/sys/sys/syscallsubr.h b/sys/sys/syscallsubr.h index 5a7f2169e74d..1593ef2c4941 100644 --- a/sys/sys/syscallsubr.h +++ b/sys/sys/syscallsubr.h @@ -105,6 +105,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(struct thread *td, int fd); int kern_connectat(struct thread *td, int dirfd, int fd, struct sockaddr *sa); diff --git a/tests/sys/file/closefrom_test.c b/tests/sys/file/closefrom_test.c index 78cfeecae026..7ce93415519e 100644 --- a/tests/sys/file/closefrom_test.c +++ b/tests/sys/file/closefrom_test.c @@ -146,7 +146,7 @@ main(void) pid_t pid; int fd, i, start; - printf("1..15\n"); + printf("1..19\n"); /* We better start up with fd's 0, 1, and 2 open. */ start = devnull(); @@ -271,5 +271,43 @@ main(void) fail("closefrom", "highest fd %d", fd); ok("closefrom"); + /* Chew up another 8 fd */ + for (i = 0; i < 8; i++) + (void)devnull(); + fd = highest_fd(); + start = fd - 7; + + /* close_range() a hole in the middle */ + close_range(start + 3, start + 5, 0); + for (i = start + 3; i < start + 6; ++i) { + if (close(i) == 0 || errno != EBADF) { + --i; + break; + } + } + if (i != start + 6) + fail("close_range", "failed to close at %d in %d - %d", i + 1, + start + 3, start + 6); + ok("close_range"); + + /* close_range from the middle of the hole */ + close_range(start + 4, start + 6, 0); + if ((i = highest_fd()) != fd) + fail("close_range", "highest fd %d", i); + ok("close_range"); + + /* close_range to the end; effectively closefrom(2) */ + close_range(start + 3, ~0L, 0); + if ((i = highest_fd()) != start + 2) + fail("close_range", "highest fd %d", i); + ok("close_range"); + + /* Now close the rest */ + close_range(start, start + 4, 0); + fd = highest_fd(); + if (fd != 3) + fail("close_range", "highest fd %d", fd); + ok("close_range"); + return (0); }