[2/3] io: Add closefrom [BZ #10353]
Commit Message
The symbol closes all open file descriptors greater than or equal to
input argument. Negative values are campled to 0, i.e, it will close
all file descriptors.
As indicated by the bug report, this is a common symbol provided by
different systems (Solaris, OpenBSD, NetBSD, FreeBSD) and, although
its has inherent issues with not taking in consideration internal libc
file descriptors (such as syslog), this is also a common feature used
in multiple projects [1][2][3][4][5].
The Linux fallback implementation iterates over /proc and close all
file descriptors sequentially. Although it was raised the questioning
whether getdents on /proc/self/fd might return disjointed entries
when file descriptor are closed; it does not seems the case on my
testing on multiple kernel (v4.18, v5.4, v5.9) and the same strategy
is used on different projects [1][2][3][5].
Also, the interface is set a fail-safe meaning that a failure in the
fallback results in a process abort.
Checked on x86_64-linux-gnu on kernel v5.9 and v5.4.
[1] https://github.com/systemd/systemd/blob/5238e9575906297608ff802a27e2ff9effa3b338/src/basic/fd-util.c#L217
[2] https://github.com/lxc/lxc/blob/ddf4b77e11a4d08f09b7b9cd13e593f8c047edc5/src/lxc/start.c#L236
[3] https://github.com/python/cpython/blob/9e4f2f3a6b8ee995c365e86d976937c141d867f8/Modules/_posixsubprocess.c#L220
[4] https://github.com/rust-lang/rust/blob/5f47c0613ed4eb46fca3633c1297364c09e5e451/src/libstd/sys/unix/process2.rs#L303-L308
[5] https://github.com/openjdk/jdk/blob/master/src/java.base/unix/native/libjava/childproc.c#L82
---
NEWS | 4 +
include/unistd.h | 1 +
io/Makefile | 5 +-
io/Versions | 1 +
io/closefrom.c | 34 +++++++
io/tst-closefrom.c | 94 +++++++++++++++++++
posix/unistd.h | 8 ++
sysdeps/mach/hurd/i386/libc.abilist | 1 +
sysdeps/unix/sysv/linux/Makefile | 2 +-
sysdeps/unix/sysv/linux/aarch64/libc.abilist | 1 +
sysdeps/unix/sysv/linux/alpha/libc.abilist | 1 +
sysdeps/unix/sysv/linux/arc/libc.abilist | 1 +
sysdeps/unix/sysv/linux/arm/be/libc.abilist | 1 +
sysdeps/unix/sysv/linux/arm/le/libc.abilist | 1 +
sysdeps/unix/sysv/linux/closefrom.c | 35 +++++++
sysdeps/unix/sysv/linux/closefrom_fallback.c | 74 +++++++++++++++
sysdeps/unix/sysv/linux/csky/libc.abilist | 1 +
sysdeps/unix/sysv/linux/hppa/libc.abilist | 1 +
sysdeps/unix/sysv/linux/i386/libc.abilist | 1 +
sysdeps/unix/sysv/linux/ia64/libc.abilist | 1 +
.../sysv/linux/m68k/coldfire/libc.abilist | 1 +
.../unix/sysv/linux/m68k/m680x0/libc.abilist | 1 +
.../sysv/linux/microblaze/be/libc.abilist | 1 +
.../sysv/linux/microblaze/le/libc.abilist | 1 +
.../sysv/linux/mips/mips32/fpu/libc.abilist | 1 +
.../sysv/linux/mips/mips32/nofpu/libc.abilist | 1 +
.../sysv/linux/mips/mips64/n32/libc.abilist | 1 +
.../sysv/linux/mips/mips64/n64/libc.abilist | 1 +
sysdeps/unix/sysv/linux/nios2/libc.abilist | 1 +
.../linux/powerpc/powerpc32/fpu/libc.abilist | 1 +
.../powerpc/powerpc32/nofpu/libc.abilist | 1 +
.../linux/powerpc/powerpc64/be/libc.abilist | 1 +
.../linux/powerpc/powerpc64/le/libc.abilist | 1 +
.../unix/sysv/linux/riscv/rv32/libc.abilist | 1 +
.../unix/sysv/linux/riscv/rv64/libc.abilist | 1 +
.../unix/sysv/linux/s390/s390-32/libc.abilist | 1 +
.../unix/sysv/linux/s390/s390-64/libc.abilist | 1 +
sysdeps/unix/sysv/linux/sh/be/libc.abilist | 1 +
sysdeps/unix/sysv/linux/sh/le/libc.abilist | 1 +
.../sysv/linux/sparc/sparc32/libc.abilist | 1 +
.../sysv/linux/sparc/sparc64/libc.abilist | 1 +
.../unix/sysv/linux/x86_64/64/libc.abilist | 1 +
.../unix/sysv/linux/x86_64/x32/libc.abilist | 1 +
43 files changed, 288 insertions(+), 3 deletions(-)
create mode 100644 io/closefrom.c
create mode 100644 io/tst-closefrom.c
create mode 100644 sysdeps/unix/sysv/linux/closefrom.c
create mode 100644 sysdeps/unix/sysv/linux/closefrom_fallback.c
Comments
* Adhemerval Zanella via Libc-alpha:
> diff --git a/NEWS b/NEWS
> index 9e829841f6..221eb4d98e 100644
> --- a/NEWS
> +++ b/NEWS
> @@ -30,6 +30,10 @@ Major new features:
>
> * On Linux, the close_range function has been added.
>
> +* The function closefrom has been adeed. It closes all file descriptors
> + greater than given integer. This function is a GNU extension, although it
> + also present in other systems.
Typo: “adeed”
> diff --git a/io/closefrom.c b/io/closefrom.c
> new file mode 100644
> index 0000000000..7e8f5a1ce5
> --- /dev/null
> +++ b/io/closefrom.c
> @@ -0,0 +1,34 @@
> +void
> +__closefrom (int lowfd)
> +{
> + int maxfd = __getdtablesize ();
> + if (maxfd == -1)
> + __fortify_fail ("closefrom failed to get the file descriptor table size");
> +
> + for (int i = 0; i < maxfd; i++)
> + if (i >= lowfd)
> + if (__close (i) == -1)
> + __fortify_fail ("closefrom failed to close a file descriptor");
> +}
> +weak_alias (__closefrom, closefrom)
Is __getdtablesize accurate on Hurd? Then I guess this part is okay.
__close should probably be __close_nocancel_nostatus. The error check
is incorrect.
> diff --git a/io/tst-closefrom.c b/io/tst-closefrom.c
> new file mode 100644
> index 0000000000..6eb97cec10
> --- /dev/null
> +++ b/io/tst-closefrom.c
> + int fds[maximum_fd + 1];
Same issue as the other test? Contiguous set of descriptors or not?
> +#include <support/test-driver.c>
> diff --git a/posix/unistd.h b/posix/unistd.h
> index 32b8161619..43ddd516aa 100644
> --- a/posix/unistd.h
> +++ b/posix/unistd.h
> @@ -352,6 +352,14 @@ extern __off64_t lseek64 (int __fd, __off64_t __offset, int __whence)
> __THROW. */
> extern int close (int __fd);
>
> +#ifdef __USE_GNU
> +/* Close all open file descriptors greater than or equal to LOWFD.
> + Negative LOWFD is clamped to 0.
> +
> + Similar to close, this functions is a cancellation point. */
> +extern void closefrom (int __lowfd);
> +#endif
I think this function should not be a cancellation point, so you can add
__THROW.
> +weak_alias (__closefrom, closefrom)
> diff --git a/sysdeps/unix/sysv/linux/closefrom_fallback.c b/sysdeps/unix/sysv/linux/closefrom_fallback.c
> new file mode 100644
> index 0000000000..48815941dd
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/closefrom_fallback.c
> +/* Fallback code: iterates over /proc/self/fd, closing each file descriptor
> + that fall on the criteria. */
> +_Bool
> +__closefrom_fallback (int from)
> +{
> + int dirfd = __open_nocancel (FD_TO_FILENAME_PREFIX, O_RDONLY | O_DIRECTORY,
> + 0);
> + if (dirfd == -1)
> + return false;
You could try to close a few descriptors if the error is ENOENT and try
again.
> + enum { buffer_size = 1024 };
> + char buffer[buffer_size];
> + bool ret = true;
buffer_size is only used once, so I think it's not needed.
> + while (true)
> + {
> + ssize_t ret = __getdents64 (dirfd, buffer, sizeof (buffer));
> + if (ret == -1)
> + goto err;
> + else if (ret == 0)
> + break;
> +
> + char *begin = buffer, *end = buffer + ret;
> + while (begin != end)
> + {
> + unsigned short int d_reclen;
> + memcpy (&d_reclen, begin + offsetof (struct dirent64, d_reclen),
> + sizeof (d_reclen));
> + const char *dname = begin + offsetof (struct dirent64, d_name);
> + begin += d_reclen;
> +
> + if (dname[0] == '.')
> + continue;
> +
> + int fd = 0;
> + for (const char *s = dname; (unsigned int) (*s) - '0' < 10; s++)
> + fd = 10 * fd + (*s - '0');
> +
> + if (fd == dirfd || fd < from)
> + continue;
> +
> + if (__close_nocancel (fd) < 0)
> + goto err;
> + }
> + }
> +
> + ret = true;
> +err:
> + __close_nocancel (dirfd);
> + return ret;
> +}
I still think it's necessary to seek to position zero after exhausting
the current buffer and some files have been closed.
The error check for __close_nocancel seems superfluous. It's possible
that you get EBADF due to a race with another thread, and that does not
seem sufficient reason to terminate the process?
Please also add a brief documentation to the manual (likewise for
close_range).
Thanks,
Florian
On 22/12/2020 07:49, Florian Weimer wrote:
> * Adhemerval Zanella via Libc-alpha:
>
>> diff --git a/NEWS b/NEWS
>> index 9e829841f6..221eb4d98e 100644
>> --- a/NEWS
>> +++ b/NEWS
>> @@ -30,6 +30,10 @@ Major new features:
>>
>> * On Linux, the close_range function has been added.
>>
>> +* The function closefrom has been adeed. It closes all file descriptors
>> + greater than given integer. This function is a GNU extension, although it
>> + also present in other systems.
>
> Typo: “adeed”
>
Ack.
>
>> diff --git a/io/closefrom.c b/io/closefrom.c
>> new file mode 100644
>> index 0000000000..7e8f5a1ce5
>> --- /dev/null
>> +++ b/io/closefrom.c
>> @@ -0,0 +1,34 @@
>
>> +void
>> +__closefrom (int lowfd)
>> +{
>> + int maxfd = __getdtablesize ();
>> + if (maxfd == -1)
>> + __fortify_fail ("closefrom failed to get the file descriptor table size");
>> +
>> + for (int i = 0; i < maxfd; i++)
>> + if (i >= lowfd)
>> + if (__close (i) == -1)
>> + __fortify_fail ("closefrom failed to close a file descriptor");
>> +}
>> +weak_alias (__closefrom, closefrom)
>
> Is __getdtablesize accurate on Hurd? Then I guess this part is okay.
Not sure in fact, it seems so.
>
> __close should probably be __close_nocancel_nostatus. The error check
> is incorrect.
Yeah, I figure out after I sent it. I will chance to __close_nocancel_nostatus.
>
>> diff --git a/io/tst-closefrom.c b/io/tst-closefrom.c
>> new file mode 100644
>> index 0000000000..6eb97cec10
>> --- /dev/null
>> +++ b/io/tst-closefrom.c
>
>> + int fds[maximum_fd + 1];
>
> Same issue as the other test? Contiguous set of descriptors or not?
It assumes a contiguous, if you prefer I can remove the array and just use
a maximum value to hold the value.
>
>> +#include <support/test-driver.c>
>> diff --git a/posix/unistd.h b/posix/unistd.h
>> index 32b8161619..43ddd516aa 100644
>> --- a/posix/unistd.h
>> +++ b/posix/unistd.h
>> @@ -352,6 +352,14 @@ extern __off64_t lseek64 (int __fd, __off64_t __offset, int __whence)
>> __THROW. */
>> extern int close (int __fd);
>>
>> +#ifdef __USE_GNU
>> +/* Close all open file descriptors greater than or equal to LOWFD.
>> + Negative LOWFD is clamped to 0.
>> +
>> + Similar to close, this functions is a cancellation point. */
>> +extern void closefrom (int __lowfd);
>> +#endif
>
> I think this function should not be a cancellation point, so you can add
> __THROW.
>
Yeah, from close_range discussion it does seems a better alternative.
>> +weak_alias (__closefrom, closefrom)
>> diff --git a/sysdeps/unix/sysv/linux/closefrom_fallback.c b/sysdeps/unix/sysv/linux/closefrom_fallback.c
>> new file mode 100644
>> index 0000000000..48815941dd
>> --- /dev/null
>> +++ b/sysdeps/unix/sysv/linux/closefrom_fallback.c
>
>> +/* Fallback code: iterates over /proc/self/fd, closing each file descriptor
>> + that fall on the criteria. */
>> +_Bool
>> +__closefrom_fallback (int from)
>> +{
>> + int dirfd = __open_nocancel (FD_TO_FILENAME_PREFIX, O_RDONLY | O_DIRECTORY,
>> + 0);
>> + if (dirfd == -1)
>> + return false;
>
> You could try to close a few descriptors if the error is ENOENT and try
> again.
Do you mean EMFILE and issue a close on [from, ...] and then try again?
I am not really fan of this heuristics, it might mitigate the failure if
limit of file-descriptors are reached, but it would really depend whether
if 'from' is close of the first open descriptor and the how many close
we issue.
>
>> + enum { buffer_size = 1024 };
>> + char buffer[buffer_size];
>> + bool ret = true;
>
> buffer_size is only used once, so I think it's not needed.
Ack.
>
>> + while (true)
>> + {
>> + ssize_t ret = __getdents64 (dirfd, buffer, sizeof (buffer));
>> + if (ret == -1)
>> + goto err;
>> + else if (ret == 0)
>> + break;
>> +
>> + char *begin = buffer, *end = buffer + ret;
>> + while (begin != end)
>> + {
>> + unsigned short int d_reclen;
>> + memcpy (&d_reclen, begin + offsetof (struct dirent64, d_reclen),
>> + sizeof (d_reclen));
>> + const char *dname = begin + offsetof (struct dirent64, d_name);
>> + begin += d_reclen;
>> +
>> + if (dname[0] == '.')
>> + continue;
>> +
>> + int fd = 0;
>> + for (const char *s = dname; (unsigned int) (*s) - '0' < 10; s++)
>> + fd = 10 * fd + (*s - '0');
>> +
>> + if (fd == dirfd || fd < from)
>> + continue;
>> +
>> + if (__close_nocancel (fd) < 0)
>> + goto err;
>> + }
>> + }
>> +
>> + ret = true;
>> +err:
>> + __close_nocancel (dirfd);
>> + return ret;
>> +}
>
> I still think it's necessary to seek to position zero after exhausting
> the current buffer and some files have been closed.
Alright, this should only add some overhead; although based on various
current usages it really seems not required.
>
> The error check for __close_nocancel seems superfluous. It's possible
> that you get EBADF due to a race with another thread, and that does not
> seem sufficient reason to terminate the process?
Alright, I will remove the check.
>
> Please also add a brief documentation to the manual (likewise for
> close_range).
Ack.
* Adhemerval Zanella:
>>> +weak_alias (__closefrom, closefrom)
>>> diff --git a/sysdeps/unix/sysv/linux/closefrom_fallback.c b/sysdeps/unix/sysv/linux/closefrom_fallback.c
>>> new file mode 100644
>>> index 0000000000..48815941dd
>>> --- /dev/null
>>> +++ b/sysdeps/unix/sysv/linux/closefrom_fallback.c
>>
>>> +/* Fallback code: iterates over /proc/self/fd, closing each file descriptor
>>> + that fall on the criteria. */
>>> +_Bool
>>> +__closefrom_fallback (int from)
>>> +{
>>> + int dirfd = __open_nocancel (FD_TO_FILENAME_PREFIX, O_RDONLY | O_DIRECTORY,
>>> + 0);
>>> + if (dirfd == -1)
>>> + return false;
>>
>> You could try to close a few descriptors if the error is ENOENT and try
>> again.
>
> Do you mean EMFILE and issue a close on [from, ...] and then try again?
> I am not really fan of this heuristics, it might mitigate the failure if
> limit of file-descriptors are reached, but it would really depend whether
> if 'from' is close of the first open descriptor and the how many close
> we issue.
Right, but I think it's needed for correctness. closefrom (3) should
work even if all descriptors are open.
Thanks,
Florian
On 22/12/2020 08:57, Florian Weimer wrote:
> * Adhemerval Zanella:
>
>>>> +weak_alias (__closefrom, closefrom)
>>>> diff --git a/sysdeps/unix/sysv/linux/closefrom_fallback.c b/sysdeps/unix/sysv/linux/closefrom_fallback.c
>>>> new file mode 100644
>>>> index 0000000000..48815941dd
>>>> --- /dev/null
>>>> +++ b/sysdeps/unix/sysv/linux/closefrom_fallback.c
>>>
>>>> +/* Fallback code: iterates over /proc/self/fd, closing each file descriptor
>>>> + that fall on the criteria. */
>>>> +_Bool
>>>> +__closefrom_fallback (int from)
>>>> +{
>>>> + int dirfd = __open_nocancel (FD_TO_FILENAME_PREFIX, O_RDONLY | O_DIRECTORY,
>>>> + 0);
>>>> + if (dirfd == -1)
>>>> + return false;
>>>
>>> You could try to close a few descriptors if the error is ENOENT and try
>>> again.
>>
>> Do you mean EMFILE and issue a close on [from, ...] and then try again?
>> I am not really fan of this heuristics, it might mitigate the failure if
>> limit of file-descriptors are reached, but it would really depend whether
>> if 'from' is close of the first open descriptor and the how many close
>> we issue.
>
> Right, but I think it's needed for correctness. closefrom (3) should
> work even if all descriptors are open.
Alright, I think we can do something like:
int dirfd = __open_nocancel (FD_TO_FILENAME_PREFIX, O_RDONLY | O_DIRECTORY, 0);
if (dirfd == -1 && errno == EMFILE)
{
int maxfd = __getdtablesize ();
for (int i = lowfd; i < maxfd; i++)
if (__close_nocancel (i) == 0)
break;
dirfd = __open_nocancel (FD_TO_FILENAME_PREFIX, O_RDONLY | O_DIRECTORY, 0);
if (dirfd == -1)
goto err;
}
* Adhemerval Zanella:
>> Right, but I think it's needed for correctness. closefrom (3) should
>> work even if all descriptors are open.
>
> Alright, I think we can do something like:
>
> int dirfd = __open_nocancel (FD_TO_FILENAME_PREFIX, O_RDONLY | O_DIRECTORY, 0);
> if (dirfd == -1 && errno == EMFILE)
> {
> int maxfd = __getdtablesize ();
> for (int i = lowfd; i < maxfd; i++)
> if (__close_nocancel (i) == 0)
> break;
> dirfd = __open_nocancel (FD_TO_FILENAME_PREFIX, O_RDONLY | O_DIRECTORY, 0);
> if (dirfd == -1)
> goto err;
> }
>
I suggest errno != ENOENT (not mounted /proc), but otherwise looks good.
Thanks,
Florian
On 22/12/2020 10:17, Florian Weimer wrote:
> * Adhemerval Zanella:
>
>>> Right, but I think it's needed for correctness. closefrom (3) should
>>> work even if all descriptors are open.
>>
>> Alright, I think we can do something like:
>>
>> int dirfd = __open_nocancel (FD_TO_FILENAME_PREFIX, O_RDONLY | O_DIRECTORY, 0);
>> if (dirfd == -1 && errno == EMFILE)
>> {
>> int maxfd = __getdtablesize ();
>> for (int i = lowfd; i < maxfd; i++)
>> if (__close_nocancel (i) == 0)
>> break;
>> dirfd = __open_nocancel (FD_TO_FILENAME_PREFIX, O_RDONLY | O_DIRECTORY, 0);
>> if (dirfd == -1)
>> goto err;
>> }
>>
>
> I suggest errno != ENOENT (not mounted /proc), but otherwise looks good.
Do you mean fail with /proc is not mounted?
int dirfd = __open_nocancel (FD_TO_FILENAME_PREFIX, O_RDONLY | O_DIRECTORY, 0);
if (dirfd == -1)
{
if (errno != EMFILE)
goto err;
int maxfd = __getdtablesize ();
for (int i = lowfd; i < maxfd; i++)
if (__close_nocancel (i) == 0)
break;
dirfd = __open_nocancel (FD_TO_FILENAME_PREFIX, O_RDONLY | O_DIRECTORY, 0);
if (dirfd == -1)
goto err;
}
* Adhemerval Zanella:
> On 22/12/2020 10:17, Florian Weimer wrote:
>> * Adhemerval Zanella:
>>
>>>> Right, but I think it's needed for correctness. closefrom (3) should
>>>> work even if all descriptors are open.
>>>
>>> Alright, I think we can do something like:
>>>
>>> int dirfd = __open_nocancel (FD_TO_FILENAME_PREFIX, O_RDONLY | O_DIRECTORY, 0);
>>> if (dirfd == -1 && errno == EMFILE)
>>> {
>>> int maxfd = __getdtablesize ();
>>> for (int i = lowfd; i < maxfd; i++)
>>> if (__close_nocancel (i) == 0)
>>> break;
>>> dirfd = __open_nocancel (FD_TO_FILENAME_PREFIX, O_RDONLY | O_DIRECTORY, 0);
>>> if (dirfd == -1)
>>> goto err;
>>> }
>>>
>>
>> I suggest errno != ENOENT (not mounted /proc), but otherwise looks good.
>
> Do you mean fail with /proc is not mounted?
Yes, and that's what ENOENT corresponds to. Closing descriptors may
also make ENFILE and ENOMEM errors go away, which is why I'm a bit
doubtful about the EMFILE check.
Thanks,
Florian
@@ -30,6 +30,10 @@ Major new features:
* On Linux, the close_range function has been added.
+* The function closefrom has been adeed. It closes all file descriptors
+ greater than given integer. This function is a GNU extension, although it
+ also present in other systems.
+
Deprecated and removed features, and other changes affecting compatibility:
* The mallinfo function is marked deprecated. Callers should call
@@ -153,6 +153,7 @@ extern int __brk (void *__addr) attribute_hidden;
extern int __close (int __fd);
libc_hidden_proto (__close)
extern int __libc_close (int __fd);
+extern _Bool __closefrom_fallback (int __lowfd) attribute_hidden;
extern ssize_t __read (int __fd, void *__buf, size_t __nbytes);
libc_hidden_proto (__read)
extern ssize_t __write (int __fd, const void *__buf, size_t __n);
@@ -55,7 +55,8 @@ routines := \
posix_fadvise posix_fadvise64 \
posix_fallocate posix_fallocate64 \
sendfile sendfile64 copy_file_range \
- utimensat futimens file_change_detection
+ utimensat futimens file_change_detection \
+ closefrom
others := pwd
test-srcs := ftwtest
@@ -69,7 +70,7 @@ tests := test-utime test-stat test-stat2 test-lfs tst-getcwd \
tst-fts tst-fts-lfs tst-open-tmpfile \
tst-copy_file_range tst-getcwd-abspath tst-lockf \
tst-ftw-lnk tst-file_change_detection tst-lchmod \
- tst-ftw-bz26353
+ tst-ftw-bz26353 tst-closefrom
# Likewise for statx, but we do not need static linking here.
tests-internal += tst-statx
@@ -135,6 +135,7 @@ libc {
GLIBC_2.33 {
stat; stat64; fstat; fstat64; lstat; lstat64; fstatat; fstatat64;
mknod; mknodat;
+ closefrom;
}
GLIBC_PRIVATE {
__libc_fcntl64;
new file mode 100644
@@ -0,0 +1,34 @@
+/* Close a range of file descriptors.
+ Copyright (C) 2020 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <stdio.h>
+#include <unistd.h>
+
+void
+__closefrom (int lowfd)
+{
+ int maxfd = __getdtablesize ();
+ if (maxfd == -1)
+ __fortify_fail ("closefrom failed to get the file descriptor table size");
+
+ for (int i = 0; i < maxfd; i++)
+ if (i >= lowfd)
+ if (__close (i) == -1)
+ __fortify_fail ("closefrom failed to close a file descriptor");
+}
+weak_alias (__closefrom, closefrom)
new file mode 100644
@@ -0,0 +1,94 @@
+/* Smoke test for the closefrom.
+ Copyright (C) 2020 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <support/check.h>
+#include <support/descriptors.h>
+#include <support/xunistd.h>
+
+#include <array_length.h>
+
+static int
+do_test (void)
+{
+ enum
+ {
+ maximum_fd = 100,
+ half_fd = maximum_fd / 2,
+ gap = maximum_fd / 4
+ };
+
+ int fds[maximum_fd + 1];
+
+ struct support_descriptors *descrs = support_descriptors_list ();
+
+ for (int i = 0; i < array_length (fds); i++)
+ fds[i] = xopen ("/dev/null", O_RDONLY | O_CLOEXEC, 0600);
+
+ /* Close half of the descriptors and check result. */
+ closefrom (fds[half_fd]);
+
+ for (int i = half_fd; i <= maximum_fd; i++)
+ {
+ TEST_COMPARE (fcntl (fds[i], F_GETFL), -1);
+ TEST_COMPARE (errno, EBADF);
+ }
+ for (int i = 0; i < half_fd; i++)
+ TEST_VERIFY (fcntl (fds[i], F_GETFL) > -1);
+
+ /* Create some gaps, close up to a threshold, and check result. */
+ xclose (35);
+ xclose (38);
+ xclose (42);
+ xclose (46);
+
+ /* Close half of the descriptors and check result. */
+ closefrom (fds[gap]);
+ for (int i = gap + 1; i < maximum_fd; i++)
+ {
+ TEST_COMPARE (fcntl (fds[i], F_GETFL), -1);
+ TEST_COMPARE (errno, EBADF);
+ }
+ for (int i = 0; i < gap; i++)
+ TEST_VERIFY (fcntl (fds[i], F_GETFL) > -1);
+
+ /* Close the remmaining but the last one. */
+ closefrom (fds[1]);
+ for (int i = 1; i <= maximum_fd; i++)
+ {
+ TEST_COMPARE (fcntl (fds[i], F_GETFL), -1);
+ TEST_COMPARE (errno, EBADF);
+ }
+ TEST_VERIFY (fcntl (fds[0], F_GETFL) > -1);
+
+ /* Close the last one. */
+ closefrom (fds[0]);
+ TEST_COMPARE (fcntl (fds[0], F_GETFL), -1);
+ TEST_COMPARE (errno, EBADF);
+
+ /* Double check by check the /proc. */
+ support_descriptors_check (descrs);
+ support_descriptors_free (descrs);
+
+ return 0;
+}
+
+#include <support/test-driver.c>
@@ -352,6 +352,14 @@ extern __off64_t lseek64 (int __fd, __off64_t __offset, int __whence)
__THROW. */
extern int close (int __fd);
+#ifdef __USE_GNU
+/* Close all open file descriptors greater than or equal to LOWFD.
+ Negative LOWFD is clamped to 0.
+
+ Similar to close, this functions is a cancellation point. */
+extern void closefrom (int __lowfd);
+#endif
+
/* Read NBYTES into BUF from FD. Return the
number read, -1 for errors or 0 for EOF.
@@ -2191,6 +2191,7 @@ GLIBC_2.32 thrd_current F
GLIBC_2.32 thrd_equal F
GLIBC_2.32 thrd_sleep F
GLIBC_2.32 thrd_yield F
+GLIBC_2.33 closefrom F
GLIBC_2.33 fstat F
GLIBC_2.33 fstat64 F
GLIBC_2.33 fstatat F
@@ -61,7 +61,7 @@ sysdep_routines += adjtimex clone umount umount2 readahead sysctl \
open_by_handle_at mlock2 pkey_mprotect pkey_set pkey_get \
timerfd_gettime timerfd_settime prctl \
process_vm_readv process_vm_writev clock_adjtime \
- time64-support pselect32 close_range
+ time64-support pselect32 close_range closefrom_fallback
CFLAGS-gethostid.c = -fexceptions
CFLAGS-tee.c = -fexceptions -fasynchronous-unwind-tables
@@ -2161,6 +2161,7 @@ GLIBC_2.32 sigdescr_np F
GLIBC_2.32 strerrordesc_np F
GLIBC_2.32 strerrorname_np F
GLIBC_2.33 close_range F
+GLIBC_2.33 closefrom F
GLIBC_2.33 fstat F
GLIBC_2.33 fstat64 F
GLIBC_2.33 fstatat F
@@ -2243,6 +2243,7 @@ GLIBC_2.32 sigdescr_np F
GLIBC_2.32 strerrordesc_np F
GLIBC_2.32 strerrorname_np F
GLIBC_2.33 close_range F
+GLIBC_2.33 closefrom F
GLIBC_2.33 fstat F
GLIBC_2.33 fstat64 F
GLIBC_2.33 fstatat F
@@ -1921,6 +1921,7 @@ GLIBC_2.32 write F
GLIBC_2.32 writev F
GLIBC_2.32 wscanf F
GLIBC_2.33 close_range F
+GLIBC_2.33 closefrom F
GLIBC_2.33 fstat F
GLIBC_2.33 fstat64 F
GLIBC_2.33 fstatat F
@@ -144,6 +144,7 @@ GLIBC_2.32 sigabbrev_np F
GLIBC_2.32 sigdescr_np F
GLIBC_2.32 strerrordesc_np F
GLIBC_2.32 strerrorname_np F
+GLIBC_2.33 closefrom F
GLIBC_2.33 fstat F
GLIBC_2.33 fstat64 F
GLIBC_2.33 fstatat F
@@ -142,6 +142,7 @@ GLIBC_2.32 sigdescr_np F
GLIBC_2.32 strerrordesc_np F
GLIBC_2.32 strerrorname_np F
GLIBC_2.33 close_range F
+GLIBC_2.33 closefrom F
GLIBC_2.33 fstat F
GLIBC_2.33 fstat64 F
GLIBC_2.33 fstatat F
new file mode 100644
@@ -0,0 +1,35 @@
+/* Close a range of file descriptors. Linux version.
+ Copyright (C) 2020 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <stdio.h>
+#include <sys/param.h>
+#include <unistd.h>
+
+void
+__closefrom (int lowfd)
+{
+ int l = MAX (0, lowfd);
+
+ int r = __close_range (l, ~0U, 0);
+ if (r == 0)
+ return;
+
+ if (!__closefrom_fallback (l))
+ __fortify_fail ("closefrom failed to close a file descriptor");
+}
+weak_alias (__closefrom, closefrom)
new file mode 100644
@@ -0,0 +1,74 @@
+/* Close a range of file descriptors. Linux version.
+ Copyright (C) 2020 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <arch-fd_to_filename.h>
+#include <dirent.h>
+#include <not-cancel.h>
+#include <stdbool.h>
+
+/* Fallback code: iterates over /proc/self/fd, closing each file descriptor
+ that fall on the criteria. */
+_Bool
+__closefrom_fallback (int from)
+{
+ int dirfd = __open_nocancel (FD_TO_FILENAME_PREFIX, O_RDONLY | O_DIRECTORY,
+ 0);
+ if (dirfd == -1)
+ return false;
+
+ enum { buffer_size = 1024 };
+ char buffer[buffer_size];
+ bool ret = true;
+
+ while (true)
+ {
+ ssize_t ret = __getdents64 (dirfd, buffer, sizeof (buffer));
+ if (ret == -1)
+ goto err;
+ else if (ret == 0)
+ break;
+
+ char *begin = buffer, *end = buffer + ret;
+ while (begin != end)
+ {
+ unsigned short int d_reclen;
+ memcpy (&d_reclen, begin + offsetof (struct dirent64, d_reclen),
+ sizeof (d_reclen));
+ const char *dname = begin + offsetof (struct dirent64, d_name);
+ begin += d_reclen;
+
+ if (dname[0] == '.')
+ continue;
+
+ int fd = 0;
+ for (const char *s = dname; (unsigned int) (*s) - '0' < 10; s++)
+ fd = 10 * fd + (*s - '0');
+
+ if (fd == dirfd || fd < from)
+ continue;
+
+ if (__close_nocancel (fd) < 0)
+ goto err;
+ }
+ }
+
+ ret = true;
+err:
+ __close_nocancel (dirfd);
+ return ret;
+}
@@ -2105,6 +2105,7 @@ GLIBC_2.32 sigdescr_np F
GLIBC_2.32 strerrordesc_np F
GLIBC_2.32 strerrorname_np F
GLIBC_2.33 close_range F
+GLIBC_2.33 closefrom F
GLIBC_2.33 fstat F
GLIBC_2.33 fstat64 F
GLIBC_2.33 fstatat F
@@ -2064,6 +2064,7 @@ GLIBC_2.32 sigdescr_np F
GLIBC_2.32 strerrordesc_np F
GLIBC_2.32 strerrorname_np F
GLIBC_2.33 close_range F
+GLIBC_2.33 closefrom F
GLIBC_2.33 fstat F
GLIBC_2.33 fstat64 F
GLIBC_2.33 fstatat F
@@ -2230,6 +2230,7 @@ GLIBC_2.32 sigdescr_np F
GLIBC_2.32 strerrordesc_np F
GLIBC_2.32 strerrorname_np F
GLIBC_2.33 close_range F
+GLIBC_2.33 closefrom F
GLIBC_2.33 fstat F
GLIBC_2.33 fstat64 F
GLIBC_2.33 fstatat F
@@ -2096,6 +2096,7 @@ GLIBC_2.32 sigdescr_np F
GLIBC_2.32 strerrordesc_np F
GLIBC_2.32 strerrorname_np F
GLIBC_2.33 close_range F
+GLIBC_2.33 closefrom F
GLIBC_2.33 fstat F
GLIBC_2.33 fstat64 F
GLIBC_2.33 fstatat F
@@ -145,6 +145,7 @@ GLIBC_2.32 sigabbrev_np F
GLIBC_2.32 sigdescr_np F
GLIBC_2.32 strerrordesc_np F
GLIBC_2.32 strerrorname_np F
+GLIBC_2.33 closefrom F
GLIBC_2.33 fstat F
GLIBC_2.33 fstat64 F
GLIBC_2.33 fstatat F
@@ -2176,6 +2176,7 @@ GLIBC_2.32 sigdescr_np F
GLIBC_2.32 strerrordesc_np F
GLIBC_2.32 strerrorname_np F
GLIBC_2.33 close_range F
+GLIBC_2.33 closefrom F
GLIBC_2.33 fstat F
GLIBC_2.33 fstat64 F
GLIBC_2.33 fstatat F
@@ -2156,6 +2156,7 @@ GLIBC_2.32 sigdescr_np F
GLIBC_2.32 strerrordesc_np F
GLIBC_2.32 strerrorname_np F
GLIBC_2.33 close_range F
+GLIBC_2.33 closefrom F
GLIBC_2.33 fstat F
GLIBC_2.33 fstat64 F
GLIBC_2.33 fstatat F
@@ -2152,6 +2152,7 @@ GLIBC_2.32 sigabbrev_np F
GLIBC_2.32 sigdescr_np F
GLIBC_2.32 strerrordesc_np F
GLIBC_2.32 strerrorname_np F
+GLIBC_2.33 closefrom F
GLIBC_2.33 fstat F
GLIBC_2.33 fstat64 F
GLIBC_2.33 fstatat F
@@ -2147,6 +2147,7 @@ GLIBC_2.32 sigdescr_np F
GLIBC_2.32 strerrordesc_np F
GLIBC_2.32 strerrorname_np F
GLIBC_2.33 close_range F
+GLIBC_2.33 closefrom F
GLIBC_2.33 fstat F
GLIBC_2.33 fstat64 F
GLIBC_2.33 fstatat F
@@ -2144,6 +2144,7 @@ GLIBC_2.32 sigabbrev_np F
GLIBC_2.32 sigdescr_np F
GLIBC_2.32 strerrordesc_np F
GLIBC_2.32 strerrorname_np F
+GLIBC_2.33 closefrom F
GLIBC_2.33 fstat F
GLIBC_2.33 fstat64 F
GLIBC_2.33 fstatat F
@@ -2153,6 +2153,7 @@ GLIBC_2.32 sigdescr_np F
GLIBC_2.32 strerrordesc_np F
GLIBC_2.32 strerrorname_np F
GLIBC_2.33 close_range F
+GLIBC_2.33 closefrom F
GLIBC_2.33 fstat F
GLIBC_2.33 fstat64 F
GLIBC_2.33 fstatat F
@@ -2147,6 +2147,7 @@ GLIBC_2.32 sigdescr_np F
GLIBC_2.32 strerrordesc_np F
GLIBC_2.32 strerrorname_np F
GLIBC_2.33 close_range F
+GLIBC_2.33 closefrom F
GLIBC_2.33 fstat F
GLIBC_2.33 fstat64 F
GLIBC_2.33 fstatat F
@@ -2194,6 +2194,7 @@ GLIBC_2.32 sigdescr_np F
GLIBC_2.32 strerrordesc_np F
GLIBC_2.32 strerrorname_np F
GLIBC_2.33 close_range F
+GLIBC_2.33 closefrom F
GLIBC_2.33 fstat F
GLIBC_2.33 fstat64 F
GLIBC_2.33 fstatat F
@@ -2203,6 +2203,7 @@ GLIBC_2.32 sigdescr_np F
GLIBC_2.32 strerrordesc_np F
GLIBC_2.32 strerrorname_np F
GLIBC_2.33 close_range F
+GLIBC_2.33 closefrom F
GLIBC_2.33 fstat F
GLIBC_2.33 fstat64 F
GLIBC_2.33 fstatat F
@@ -2235,6 +2235,7 @@ GLIBC_2.32 sigabbrev_np F
GLIBC_2.32 sigdescr_np F
GLIBC_2.32 strerrordesc_np F
GLIBC_2.32 strerrorname_np F
+GLIBC_2.33 closefrom F
GLIBC_2.33 fstat F
GLIBC_2.33 fstat64 F
GLIBC_2.33 fstatat F
@@ -2066,6 +2066,7 @@ GLIBC_2.32 sigdescr_np F
GLIBC_2.32 strerrordesc_np F
GLIBC_2.32 strerrorname_np F
GLIBC_2.33 close_range F
+GLIBC_2.33 closefrom F
GLIBC_2.33 fstat F
GLIBC_2.33 fstat64 F
GLIBC_2.33 fstatat F
@@ -2356,6 +2356,7 @@ GLIBC_2.32 sigdescr_np F
GLIBC_2.32 strerrordesc_np F
GLIBC_2.32 strerrorname_np F
GLIBC_2.33 close_range F
+GLIBC_2.33 closefrom F
GLIBC_2.33 fstat F
GLIBC_2.33 fstat64 F
GLIBC_2.33 fstatat F
@@ -597,6 +597,7 @@ GLIBC_2.33 clone F
GLIBC_2.33 close F
GLIBC_2.33 close_range F
GLIBC_2.33 closedir F
+GLIBC_2.33 closefrom F
GLIBC_2.33 closelog F
GLIBC_2.33 confstr F
GLIBC_2.33 connect F
@@ -2123,6 +2123,7 @@ GLIBC_2.32 sigdescr_np F
GLIBC_2.32 strerrordesc_np F
GLIBC_2.32 strerrorname_np F
GLIBC_2.33 close_range F
+GLIBC_2.33 closefrom F
GLIBC_2.33 fstat F
GLIBC_2.33 fstat64 F
GLIBC_2.33 fstatat F
@@ -2201,6 +2201,7 @@ GLIBC_2.32 sigdescr_np F
GLIBC_2.32 strerrordesc_np F
GLIBC_2.32 strerrorname_np F
GLIBC_2.33 close_range F
+GLIBC_2.33 closefrom F
GLIBC_2.33 fstat F
GLIBC_2.33 fstat64 F
GLIBC_2.33 fstatat F
@@ -2102,6 +2102,7 @@ GLIBC_2.32 sigdescr_np F
GLIBC_2.32 strerrordesc_np F
GLIBC_2.32 strerrorname_np F
GLIBC_2.33 close_range F
+GLIBC_2.33 closefrom F
GLIBC_2.33 fstat F
GLIBC_2.33 fstat64 F
GLIBC_2.33 fstatat F
@@ -2070,6 +2070,7 @@ GLIBC_2.32 sigabbrev_np F
GLIBC_2.32 sigdescr_np F
GLIBC_2.32 strerrordesc_np F
GLIBC_2.32 strerrorname_np F
+GLIBC_2.33 closefrom F
GLIBC_2.33 fstat F
GLIBC_2.33 fstat64 F
GLIBC_2.33 fstatat F
@@ -2068,6 +2068,7 @@ GLIBC_2.32 sigdescr_np F
GLIBC_2.32 strerrordesc_np F
GLIBC_2.32 strerrorname_np F
GLIBC_2.33 close_range F
+GLIBC_2.33 closefrom F
GLIBC_2.33 fstat F
GLIBC_2.33 fstat64 F
GLIBC_2.33 fstatat F
@@ -2192,6 +2192,7 @@ GLIBC_2.32 sigdescr_np F
GLIBC_2.32 strerrordesc_np F
GLIBC_2.32 strerrorname_np F
GLIBC_2.33 close_range F
+GLIBC_2.33 closefrom F
GLIBC_2.33 fstat F
GLIBC_2.33 fstat64 F
GLIBC_2.33 fstatat F
@@ -2119,6 +2119,7 @@ GLIBC_2.32 sigdescr_np F
GLIBC_2.32 strerrordesc_np F
GLIBC_2.32 strerrorname_np F
GLIBC_2.33 close_range F
+GLIBC_2.33 closefrom F
GLIBC_2.33 fstat F
GLIBC_2.33 fstat64 F
GLIBC_2.33 fstatat F
@@ -2077,6 +2077,7 @@ GLIBC_2.32 sigdescr_np F
GLIBC_2.32 strerrordesc_np F
GLIBC_2.32 strerrorname_np F
GLIBC_2.33 close_range F
+GLIBC_2.33 closefrom F
GLIBC_2.33 fstat F
GLIBC_2.33 fstat64 F
GLIBC_2.33 fstatat F
@@ -2174,6 +2174,7 @@ GLIBC_2.32 sigdescr_np F
GLIBC_2.32 strerrordesc_np F
GLIBC_2.32 strerrorname_np F
GLIBC_2.33 close_range F
+GLIBC_2.33 closefrom F
GLIBC_2.33 fstat F
GLIBC_2.33 fstat64 F
GLIBC_2.33 fstatat F