1/* Close a range of file descriptors. Linux version.
2 Copyright (C) 2021-2022 Free Software Foundation, Inc.
3 This file is part of the GNU C Library.
4
5 The GNU C Library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
9
10 The GNU C Library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public
16 License along with the GNU C Library; if not, see
17 <https://www.gnu.org/licenses/>. */
18
19#include <arch-fd_to_filename.h>
20#include <dirent.h>
21#include <not-cancel.h>
22#include <stdbool.h>
23
24#if !__ASSUME_CLOSE_RANGE
25
26/* Fallback code: iterates over /proc/self/fd, closing each file descriptor
27 that fall on the criteria. If DIRFD_FALLBACK is set, a failure on
28 /proc/self/fd open will trigger a fallback that tries to close a file
29 descriptor before proceed. */
30_Bool
31__closefrom_fallback (int from, _Bool dirfd_fallback)
32{
33 bool ret = false;
34
35 int dirfd = __open_nocancel (FD_TO_FILENAME_PREFIX, O_RDONLY | O_DIRECTORY,
36 0);
37 if (dirfd == -1)
38 {
39 /* The closefrom should work even when process can't open new files. */
40 if (errno == ENOENT || !dirfd_fallback)
41 goto err;
42
43 for (int i = from; i < INT_MAX; i++)
44 {
45 int r = __close_nocancel (i);
46 if (r == 0 || (r == -1 && errno != EBADF))
47 break;
48 }
49
50 dirfd = __open_nocancel (FD_TO_FILENAME_PREFIX, O_RDONLY | O_DIRECTORY,
51 0);
52 if (dirfd == -1)
53 return false;
54 }
55
56 char buffer[1024];
57 while (true)
58 {
59 ssize_t ret = __getdents64 (dirfd, buffer, sizeof (buffer));
60 if (ret == -1)
61 goto err;
62 else if (ret == 0)
63 break;
64
65 /* If any file descriptor is closed it resets the /proc/self position
66 read again from the start (to obtain any possible kernel update). */
67 bool closed = false;
68 char *begin = buffer, *end = buffer + ret;
69 while (begin != end)
70 {
71 unsigned short int d_reclen;
72 memcpy (&d_reclen, begin + offsetof (struct dirent64, d_reclen),
73 sizeof (d_reclen));
74 const char *dname = begin + offsetof (struct dirent64, d_name);
75 begin += d_reclen;
76
77 if (dname[0] == '.')
78 continue;
79
80 int fd = 0;
81 for (const char *s = dname; (unsigned int) (*s) - '0' < 10; s++)
82 fd = 10 * fd + (*s - '0');
83
84 if (fd == dirfd || fd < from)
85 continue;
86
87 /* We ignore close errors because EBADF, EINTR, and EIO means the
88 descriptor has been released. */
89 __close_nocancel (fd);
90 closed = true;
91 }
92
93 if (closed && __lseek (dirfd, 0, SEEK_SET) < 0)
94 goto err;
95 }
96
97 ret = true;
98err:
99 __close_nocancel (dirfd);
100 return ret;
101}
102
103#endif
104