]> git.ozlabs.org Git - ccan/blob - ccan/closefrom/closefrom.c
base64: fix for unsigned chars (e.g. ARM).
[ccan] / ccan / closefrom / closefrom.c
1 /* CC0 license (public domain) - see LICENSE file for details */
2 #include <ccan/closefrom/closefrom.h>
3 #include <dirent.h>
4 #include <errno.h>
5 #include <limits.h>
6 #include <stdlib.h>
7 #include <stdio.h>
8 #include <sys/time.h>
9 #include <sys/resource.h>
10 #include <sys/types.h>
11 #include <unistd.h>
12
13 /* See also:
14  * https://stackoverflow.com/a/918469
15  *
16  * The implementation below is not exhaustive of all the suggested above.
17  */
18
19 #if !HAVE_CLOSEFROM
20
21 /* IBM AIX.
22  * https://www.ibm.com/docs/en/aix/7.2?topic=f-fcntl-dup-dup2-subroutine
23  */
24 #if HAVE_F_CLOSEM
25
26 #include <fcntl.h>
27
28 void closefrom(int fromfd)
29 {
30         (void) fcntl(fromfd, F_CLOSEM, 0);
31 }
32
33 bool closefrom_may_be_slow(void)
34 {
35         return false;
36 }
37
38 #else /* !HAVE_F_CLOSEM */
39
40 #if HAVE_NR_CLOSE_RANGE
41 #include <sys/syscall.h>
42 #endif
43
44 #define PROC_PID_FD_LEN \
45         ( 6  /* /proc/ */ \
46         + 20 /* 64-bit $PID */ \
47         + 3  /* /fd */ \
48         + 1  /* NUL */ \
49         )
50
51 static bool can_get_maxfd(void)
52 {
53 #if HAVE_F_MAXFD
54         int res = fcntl(0, F_MAXFD);
55         if (res < 0)
56                 return false;
57         else
58                 return true;
59 #else
60         return false;
61 #endif
62 }
63
64 /* Linux >= 5.9 */
65 static bool can_close_range(void)
66 {
67 #if HAVE_NR_CLOSE_RANGE
68         int res = syscall(__NR_close_range, INT_MAX, INT_MAX, 0);
69         if (res < 0)
70                 return false;
71         return true;
72 #else
73         return false;
74 #endif
75 }
76
77 /* On Linux, Solaris, AIX, Cygwin, and NetBSD.  */
78 static bool can_open_proc_pid_fd(void)
79 {
80         char dnam[PROC_PID_FD_LEN];
81         DIR *dir;
82
83         sprintf(dnam, "/proc/%ld/fd", (long) getpid());
84         dir = opendir(dnam);
85         if (!dir)
86                 return false;
87         closedir(dir);
88         return true;
89 }
90
91 /* On FreeBSD and MacOS.  */
92 static bool can_open_dev_fd(void)
93 {
94         DIR *dir;
95         dir = opendir("/dev/fd");
96         if (!dir)
97                 return false;
98         closedir(dir);
99         return true;
100 }
101
102 bool closefrom_may_be_slow(void)
103 {
104         if (can_get_maxfd())
105                 return false;
106         else if (can_close_range())
107                 return false;
108         else if (can_open_proc_pid_fd())
109                 return false;
110         else if (can_open_dev_fd())
111                 return false;
112         else
113                 return true;
114 }
115
116 /* It is possible that we run out of available file descriptors.
117  * However, if we are going to close anyway, we could just try
118  * closing file descriptors until we reach maxfd.
119  */
120 static
121 DIR *try_opendir(const char *dnam, int *fromfd, int maxfd)
122 {
123         DIR *dir;
124
125         do {
126                 dir = opendir(dnam);
127                 if (!dir && (errno == ENFILE || errno == EMFILE)) {
128                         if (*fromfd < maxfd)
129                                 close((*fromfd)++);
130                         else
131                                 break;
132                 }
133         } while (!dir && (errno == ENFILE || errno == EMFILE));
134
135         return dir;
136 }
137
138 void closefrom(int fromfd)
139 {
140         int saved_errno = errno;
141
142         int res;
143         int maxfd;
144
145         char dnam[PROC_PID_FD_LEN];
146         DIR *dir;
147         struct dirent *entry;
148
149         (void) res;
150
151         if (fromfd < 0)
152                 goto quit;
153
154 #if HAVE_NR_CLOSE_RANGE
155         res = syscall(__NR_close_range, fromfd, INT_MAX, 0);
156         if (res == 0)
157                 goto quit;
158 #endif
159
160         maxfd = sysconf(_SC_OPEN_MAX);
161
162         sprintf(dnam, "/proc/%ld/fd", (long) getpid());
163         dir = try_opendir(dnam, &fromfd, maxfd);
164         if (!dir)
165                 dir = try_opendir("/dev/fd", &fromfd, maxfd);
166
167         if (dir) {
168                 while ((entry = readdir(dir))) {
169                         long fd;
170                         char *endp;
171
172                         fd = strtol(entry->d_name, &endp, 10);
173                         if (entry->d_name != endp && *endp == '\0' &&
174                             fd >= 0 && fd < INT_MAX && fd >= fromfd &&
175                             fd != dirfd(dir) )
176                                 close(fd);
177                 }
178                 closedir(dir);
179                 goto quit;
180         }
181
182 #if HAVE_F_MAXFD
183         res = fcntl(0, F_MAXFD);
184         if (res >= 0)
185                 maxfd = res + 1;
186 #endif
187
188         /* Fallback.  */
189         for (; fromfd < maxfd; ++fromfd)
190                 close(fromfd);
191
192 quit:
193         errno = saved_errno;
194 }
195
196 #endif /* !HAVE_F_CLOSEM */
197
198 void closefrom_limit(unsigned int arg_limit)
199 {
200         rlim_t limit = (rlim_t) arg_limit;
201
202         struct rlimit nofile;
203
204         if (!closefrom_may_be_slow())
205                 return;
206
207         if (limit == 0)
208                 limit = 4096;
209
210         getrlimit(RLIMIT_NOFILE, &nofile);
211
212         /* Respect the max limit.
213          * If we are not running as root then we cannot raise
214          * it, but we *can* lower the max limit.
215          */
216         if (nofile.rlim_max != RLIM_INFINITY && limit > nofile.rlim_max)
217                 limit = nofile.rlim_max;
218
219         nofile.rlim_cur = limit;
220         nofile.rlim_max = limit;
221
222         setrlimit(RLIMIT_NOFILE, &nofile);
223 }
224
225 #endif /* !HAVE_CLOSEFROM */