1 | /*************************************************************************** |
2 | * _ _ ____ _ |
3 | * Project ___| | | | _ \| | |
4 | * / __| | | | |_) | | |
5 | * | (__| |_| | _ <| |___ |
6 | * \___|\___/|_| \_\_____| |
7 | * |
8 | * Copyright (C) 1998 - 2021, Daniel Stenberg, <daniel@haxx.se>, et al. |
9 | * |
10 | * This software is licensed as described in the file COPYING, which |
11 | * you should have received as part of this distribution. The terms |
12 | * are also available at https://curl.se/docs/copyright.html. |
13 | * |
14 | * You may opt to use, copy, modify, merge, publish, distribute and/or sell |
15 | * copies of the Software, and permit persons to whom the Software is |
16 | * furnished to do so, under the terms of the COPYING file. |
17 | * |
18 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY |
19 | * KIND, either express or implied. |
20 | * |
21 | ***************************************************************************/ |
22 | #include "test.h" |
23 | |
24 | #ifdef HAVE_SYS_RESOURCE_H |
25 | #include <sys/resource.h> |
26 | #endif |
27 | #ifdef HAVE_FCNTL_H |
28 | #include <fcntl.h> |
29 | #endif |
30 | #include <limits.h> |
31 | |
32 | #include "warnless.h" |
33 | #include "memdebug.h" |
34 | |
35 | #ifndef FD_SETSIZE |
36 | #error "this test requires FD_SETSIZE" |
37 | #endif |
38 | |
39 | #define SAFETY_MARGIN (16) |
40 | #define NUM_OPEN (FD_SETSIZE + 10) |
41 | #define NUM_NEEDED (NUM_OPEN + SAFETY_MARGIN) |
42 | |
43 | #if defined(WIN32) || defined(_WIN32) || defined(MSDOS) |
44 | #define DEV_NULL "NUL" |
45 | #else |
46 | #define DEV_NULL "/dev/null" |
47 | #endif |
48 | |
49 | #if defined(HAVE_GETRLIMIT) && defined(HAVE_SETRLIMIT) |
50 | |
51 | static int *fd = NULL; |
52 | static struct rlimit num_open; |
53 | static char msgbuff[256]; |
54 | |
55 | static void store_errmsg(const char *msg, int err) |
56 | { |
57 | if(!err) |
58 | msnprintf(msgbuff, sizeof(msgbuff), "%s" , msg); |
59 | else |
60 | msnprintf(msgbuff, sizeof(msgbuff), "%s, errno %d, %s" , msg, |
61 | err, strerror(err)); |
62 | } |
63 | |
64 | static void close_file_descriptors(void) |
65 | { |
66 | for(num_open.rlim_cur = 0; |
67 | num_open.rlim_cur < num_open.rlim_max; |
68 | num_open.rlim_cur++) |
69 | if(fd[num_open.rlim_cur] > 0) |
70 | close(fd[num_open.rlim_cur]); |
71 | free(fd); |
72 | fd = NULL; |
73 | } |
74 | |
75 | static int fopen_works(void) |
76 | { |
77 | FILE *fpa[3]; |
78 | int i; |
79 | int ret = 1; |
80 | |
81 | for(i = 0; i < 3; i++) { |
82 | fpa[i] = NULL; |
83 | } |
84 | for(i = 0; i < 3; i++) { |
85 | fpa[i] = fopen(DEV_NULL, FOPEN_READTEXT); |
86 | if(!fpa[i]) { |
87 | store_errmsg("fopen failed" , errno); |
88 | fprintf(stderr, "%s\n" , msgbuff); |
89 | ret = 0; |
90 | break; |
91 | } |
92 | } |
93 | for(i = 0; i < 3; i++) { |
94 | if(fpa[i] != NULL) |
95 | fclose(fpa[i]); |
96 | } |
97 | return ret; |
98 | } |
99 | |
100 | static int rlimit(int keep_open) |
101 | { |
102 | int nitems, i; |
103 | int *memchunk = NULL; |
104 | char *fmt; |
105 | struct rlimit rl; |
106 | char strbuff[256]; |
107 | char strbuff1[81]; |
108 | char strbuff2[81]; |
109 | char fmt_u[] = "%u" ; |
110 | char fmt_lu[] = "%lu" ; |
111 | #ifdef HAVE_LONGLONG |
112 | char fmt_llu[] = "%llu" ; |
113 | |
114 | if(sizeof(rl.rlim_max) > sizeof(long)) |
115 | fmt = fmt_llu; |
116 | else |
117 | #endif |
118 | fmt = (sizeof(rl.rlim_max) < sizeof(long))?fmt_u:fmt_lu; |
119 | |
120 | /* get initial open file limits */ |
121 | |
122 | if(getrlimit(RLIMIT_NOFILE, &rl) != 0) { |
123 | store_errmsg("getrlimit() failed" , errno); |
124 | fprintf(stderr, "%s\n" , msgbuff); |
125 | return -1; |
126 | } |
127 | |
128 | /* show initial open file limits */ |
129 | |
130 | #ifdef RLIM_INFINITY |
131 | if(rl.rlim_cur == RLIM_INFINITY) |
132 | strcpy(strbuff, "INFINITY" ); |
133 | else |
134 | #endif |
135 | msnprintf(strbuff, sizeof(strbuff), fmt, rl.rlim_cur); |
136 | fprintf(stderr, "initial soft limit: %s\n" , strbuff); |
137 | |
138 | #ifdef RLIM_INFINITY |
139 | if(rl.rlim_max == RLIM_INFINITY) |
140 | strcpy(strbuff, "INFINITY" ); |
141 | else |
142 | #endif |
143 | msnprintf(strbuff, sizeof(strbuff), fmt, rl.rlim_max); |
144 | fprintf(stderr, "initial hard limit: %s\n" , strbuff); |
145 | |
146 | /* show our constants */ |
147 | |
148 | fprintf(stderr, "test518 FD_SETSIZE: %d\n" , FD_SETSIZE); |
149 | fprintf(stderr, "test518 NUM_OPEN : %d\n" , NUM_OPEN); |
150 | fprintf(stderr, "test518 NUM_NEEDED: %d\n" , NUM_NEEDED); |
151 | |
152 | /* |
153 | * if soft limit and hard limit are different we ask the |
154 | * system to raise soft limit all the way up to the hard |
155 | * limit. Due to some other system limit the soft limit |
156 | * might not be raised up to the hard limit. So from this |
157 | * point the resulting soft limit is our limit. Trying to |
158 | * open more than soft limit file descriptors will fail. |
159 | */ |
160 | |
161 | if(rl.rlim_cur != rl.rlim_max) { |
162 | |
163 | #ifdef OPEN_MAX |
164 | if((rl.rlim_cur > 0) && |
165 | (rl.rlim_cur < OPEN_MAX)) { |
166 | fprintf(stderr, "raising soft limit up to OPEN_MAX\n" ); |
167 | rl.rlim_cur = OPEN_MAX; |
168 | if(setrlimit(RLIMIT_NOFILE, &rl) != 0) { |
169 | /* on failure don't abort just issue a warning */ |
170 | store_errmsg("setrlimit() failed" , errno); |
171 | fprintf(stderr, "%s\n" , msgbuff); |
172 | msgbuff[0] = '\0'; |
173 | } |
174 | } |
175 | #endif |
176 | |
177 | fprintf(stderr, "raising soft limit up to hard limit\n" ); |
178 | rl.rlim_cur = rl.rlim_max; |
179 | if(setrlimit(RLIMIT_NOFILE, &rl) != 0) { |
180 | /* on failure don't abort just issue a warning */ |
181 | store_errmsg("setrlimit() failed" , errno); |
182 | fprintf(stderr, "%s\n" , msgbuff); |
183 | msgbuff[0] = '\0'; |
184 | } |
185 | |
186 | /* get current open file limits */ |
187 | |
188 | if(getrlimit(RLIMIT_NOFILE, &rl) != 0) { |
189 | store_errmsg("getrlimit() failed" , errno); |
190 | fprintf(stderr, "%s\n" , msgbuff); |
191 | return -3; |
192 | } |
193 | |
194 | /* show current open file limits */ |
195 | |
196 | #ifdef RLIM_INFINITY |
197 | if(rl.rlim_cur == RLIM_INFINITY) |
198 | strcpy(strbuff, "INFINITY" ); |
199 | else |
200 | #endif |
201 | msnprintf(strbuff, sizeof(strbuff), fmt, rl.rlim_cur); |
202 | fprintf(stderr, "current soft limit: %s\n" , strbuff); |
203 | |
204 | #ifdef RLIM_INFINITY |
205 | if(rl.rlim_max == RLIM_INFINITY) |
206 | strcpy(strbuff, "INFINITY" ); |
207 | else |
208 | #endif |
209 | msnprintf(strbuff, sizeof(strbuff), fmt, rl.rlim_max); |
210 | fprintf(stderr, "current hard limit: %s\n" , strbuff); |
211 | |
212 | } /* (rl.rlim_cur != rl.rlim_max) */ |
213 | |
214 | /* |
215 | * test 518 is all about testing libcurl functionality |
216 | * when more than FD_SETSIZE file descriptors are open. |
217 | * This means that if for any reason we are not able to |
218 | * open more than FD_SETSIZE file descriptors then test |
219 | * 518 should not be run. |
220 | */ |
221 | |
222 | /* |
223 | * verify that soft limit is higher than NUM_NEEDED, |
224 | * which is the number of file descriptors we would |
225 | * try to open plus SAFETY_MARGIN to not exhaust the |
226 | * file descriptor pool |
227 | */ |
228 | |
229 | num_open.rlim_cur = NUM_NEEDED; |
230 | |
231 | if((rl.rlim_cur > 0) && |
232 | #ifdef RLIM_INFINITY |
233 | (rl.rlim_cur != RLIM_INFINITY) && |
234 | #endif |
235 | (rl.rlim_cur <= num_open.rlim_cur)) { |
236 | msnprintf(strbuff2, sizeof(strbuff2), fmt, rl.rlim_cur); |
237 | msnprintf(strbuff1, sizeof(strbuff1), fmt, num_open.rlim_cur); |
238 | msnprintf(strbuff, sizeof(strbuff), "fds needed %s > system limit %s" , |
239 | strbuff1, strbuff2); |
240 | store_errmsg(strbuff, 0); |
241 | fprintf(stderr, "%s\n" , msgbuff); |
242 | return -4; |
243 | } |
244 | |
245 | /* |
246 | * reserve a chunk of memory before opening file descriptors to |
247 | * avoid a low memory condition once the file descriptors are |
248 | * open. System conditions that could make the test fail should |
249 | * be addressed in the precheck phase. This chunk of memory shall |
250 | * be always free()ed before exiting the rlimit() function so |
251 | * that it becomes available to the test. |
252 | */ |
253 | |
254 | for(nitems = i = 1; nitems <= i; i *= 2) |
255 | nitems = i; |
256 | if(nitems > 0x7fff) |
257 | nitems = 0x40000; |
258 | do { |
259 | num_open.rlim_max = sizeof(*memchunk) * (size_t)nitems; |
260 | msnprintf(strbuff, sizeof(strbuff), fmt, num_open.rlim_max); |
261 | fprintf(stderr, "allocating memchunk %s byte array\n" , strbuff); |
262 | memchunk = malloc(sizeof(*memchunk) * (size_t)nitems); |
263 | if(!memchunk) { |
264 | fprintf(stderr, "memchunk, malloc() failed\n" ); |
265 | nitems /= 2; |
266 | } |
267 | } while(nitems && !memchunk); |
268 | if(!memchunk) { |
269 | store_errmsg("memchunk, malloc() failed" , errno); |
270 | fprintf(stderr, "%s\n" , msgbuff); |
271 | return -5; |
272 | } |
273 | |
274 | /* initialize it to fight lazy allocation */ |
275 | |
276 | fprintf(stderr, "initializing memchunk array\n" ); |
277 | |
278 | for(i = 0; i < nitems; i++) |
279 | memchunk[i] = -1; |
280 | |
281 | /* set the number of file descriptors we will try to open */ |
282 | |
283 | num_open.rlim_max = NUM_OPEN; |
284 | |
285 | /* verify that we won't overflow size_t in malloc() */ |
286 | |
287 | if((size_t)(num_open.rlim_max) > ((size_t)-1) / sizeof(*fd)) { |
288 | msnprintf(strbuff1, sizeof(strbuff1), fmt, num_open.rlim_max); |
289 | msnprintf(strbuff, sizeof(strbuff), "unable to allocate an array for %s " |
290 | "file descriptors, would overflow size_t" , strbuff1); |
291 | store_errmsg(strbuff, 0); |
292 | fprintf(stderr, "%s\n" , msgbuff); |
293 | free(memchunk); |
294 | return -6; |
295 | } |
296 | |
297 | /* allocate array for file descriptors */ |
298 | |
299 | msnprintf(strbuff, sizeof(strbuff), fmt, num_open.rlim_max); |
300 | fprintf(stderr, "allocating array for %s file descriptors\n" , strbuff); |
301 | |
302 | fd = malloc(sizeof(*fd) * (size_t)(num_open.rlim_max)); |
303 | if(!fd) { |
304 | store_errmsg("fd, malloc() failed" , errno); |
305 | fprintf(stderr, "%s\n" , msgbuff); |
306 | free(memchunk); |
307 | return -7; |
308 | } |
309 | |
310 | /* initialize it to fight lazy allocation */ |
311 | |
312 | fprintf(stderr, "initializing fd array\n" ); |
313 | |
314 | for(num_open.rlim_cur = 0; |
315 | num_open.rlim_cur < num_open.rlim_max; |
316 | num_open.rlim_cur++) |
317 | fd[num_open.rlim_cur] = -1; |
318 | |
319 | msnprintf(strbuff, sizeof(strbuff), fmt, num_open.rlim_max); |
320 | fprintf(stderr, "trying to open %s file descriptors\n" , strbuff); |
321 | |
322 | /* open a dummy descriptor */ |
323 | |
324 | fd[0] = open(DEV_NULL, O_RDONLY); |
325 | if(fd[0] < 0) { |
326 | msnprintf(strbuff, sizeof(strbuff), "opening of %s failed" , DEV_NULL); |
327 | store_errmsg(strbuff, errno); |
328 | fprintf(stderr, "%s\n" , msgbuff); |
329 | free(fd); |
330 | fd = NULL; |
331 | free(memchunk); |
332 | return -8; |
333 | } |
334 | |
335 | /* create a bunch of file descriptors */ |
336 | |
337 | for(num_open.rlim_cur = 1; |
338 | num_open.rlim_cur < num_open.rlim_max; |
339 | num_open.rlim_cur++) { |
340 | |
341 | fd[num_open.rlim_cur] = dup(fd[0]); |
342 | |
343 | if(fd[num_open.rlim_cur] < 0) { |
344 | |
345 | fd[num_open.rlim_cur] = -1; |
346 | |
347 | msnprintf(strbuff1, sizeof(strbuff1), fmt, num_open.rlim_cur); |
348 | msnprintf(strbuff, sizeof(strbuff), "dup() attempt %s failed" , strbuff1); |
349 | fprintf(stderr, "%s\n" , strbuff); |
350 | |
351 | msnprintf(strbuff1, sizeof(strbuff), fmt, num_open.rlim_cur); |
352 | msnprintf(strbuff, sizeof(strbuff), "fds system limit seems close to %s" , |
353 | strbuff1); |
354 | fprintf(stderr, "%s\n" , strbuff); |
355 | |
356 | num_open.rlim_max = NUM_NEEDED; |
357 | |
358 | msnprintf(strbuff2, sizeof(strbuff2), fmt, num_open.rlim_max); |
359 | msnprintf(strbuff1, sizeof(strbuff1), fmt, num_open.rlim_cur); |
360 | msnprintf(strbuff, sizeof(strbuff), "fds needed %s > system limit %s" , |
361 | strbuff2, strbuff1); |
362 | store_errmsg(strbuff, 0); |
363 | fprintf(stderr, "%s\n" , msgbuff); |
364 | |
365 | for(num_open.rlim_cur = 0; |
366 | fd[num_open.rlim_cur] >= 0; |
367 | num_open.rlim_cur++) |
368 | close(fd[num_open.rlim_cur]); |
369 | free(fd); |
370 | fd = NULL; |
371 | free(memchunk); |
372 | return -9; |
373 | |
374 | } |
375 | |
376 | } |
377 | |
378 | msnprintf(strbuff, sizeof(strbuff), fmt, num_open.rlim_max); |
379 | fprintf(stderr, "%s file descriptors open\n" , strbuff); |
380 | |
381 | #if !defined(HAVE_POLL_FINE) && \ |
382 | !defined(USE_WINSOCK) && \ |
383 | !defined(TPF) |
384 | |
385 | /* |
386 | * when using select() instead of poll() we cannot test |
387 | * libcurl functionality with a socket number equal or |
388 | * greater than FD_SETSIZE. In any case, macro VERIFY_SOCK |
389 | * in lib/select.c enforces this check and protects libcurl |
390 | * from a possible crash. The effect of this protection |
391 | * is that test 518 will always fail, since the actual |
392 | * call to select() never takes place. We skip test 518 |
393 | * with an indication that select limit would be exceeded. |
394 | */ |
395 | |
396 | num_open.rlim_cur = FD_SETSIZE - SAFETY_MARGIN; |
397 | if(num_open.rlim_max > num_open.rlim_cur) { |
398 | msnprintf(strbuff, sizeof(strbuff), "select limit is FD_SETSIZE %d" , |
399 | FD_SETSIZE); |
400 | store_errmsg(strbuff, 0); |
401 | fprintf(stderr, "%s\n" , msgbuff); |
402 | close_file_descriptors(); |
403 | free(memchunk); |
404 | return -10; |
405 | } |
406 | |
407 | num_open.rlim_cur = FD_SETSIZE - SAFETY_MARGIN; |
408 | for(rl.rlim_cur = 0; |
409 | rl.rlim_cur < num_open.rlim_max; |
410 | rl.rlim_cur++) { |
411 | if((fd[rl.rlim_cur] > 0) && |
412 | ((unsigned int)fd[rl.rlim_cur] > num_open.rlim_cur)) { |
413 | msnprintf(strbuff, sizeof(strbuff), "select limit is FD_SETSIZE %d" , |
414 | FD_SETSIZE); |
415 | store_errmsg(strbuff, 0); |
416 | fprintf(stderr, "%s\n" , msgbuff); |
417 | close_file_descriptors(); |
418 | free(memchunk); |
419 | return -11; |
420 | } |
421 | } |
422 | |
423 | #endif /* using a FD_SETSIZE bound select() */ |
424 | |
425 | /* |
426 | * Old or 'backwards compatible' implementations of stdio do not allow |
427 | * handling of streams with an underlying file descriptor number greater |
428 | * than 255, even when allowing high numbered file descriptors for sockets. |
429 | * At this point we have a big number of file descriptors which have been |
430 | * opened using dup(), so lets test the stdio implementation and discover |
431 | * if it is capable of fopen()ing some additional files. |
432 | */ |
433 | |
434 | if(!fopen_works()) { |
435 | msnprintf(strbuff1, sizeof(strbuff1), fmt, num_open.rlim_max); |
436 | msnprintf(strbuff, sizeof(strbuff), |
437 | "fopen fails with %s fds open()" , |
438 | strbuff1); |
439 | fprintf(stderr, "%s\n" , msgbuff); |
440 | msnprintf(strbuff, sizeof(strbuff), |
441 | "fopen fails with lots of fds open()" ); |
442 | store_errmsg(strbuff, 0); |
443 | close_file_descriptors(); |
444 | free(memchunk); |
445 | return -12; |
446 | } |
447 | |
448 | /* free the chunk of memory we were reserving so that it |
449 | becomes becomes available to the test */ |
450 | |
451 | free(memchunk); |
452 | |
453 | /* close file descriptors unless instructed to keep them */ |
454 | |
455 | if(!keep_open) { |
456 | close_file_descriptors(); |
457 | } |
458 | |
459 | return 0; |
460 | } |
461 | |
462 | int test(char *URL) |
463 | { |
464 | CURLcode res; |
465 | CURL *curl; |
466 | |
467 | if(!strcmp(URL, "check" )) { |
468 | /* used by the test script to ask if we can run this test or not */ |
469 | if(rlimit(FALSE)) { |
470 | fprintf(stdout, "rlimit problem: %s\n" , msgbuff); |
471 | return 1; |
472 | } |
473 | return 0; /* sure, run this! */ |
474 | } |
475 | |
476 | if(rlimit(TRUE)) { |
477 | /* failure */ |
478 | return TEST_ERR_MAJOR_BAD; |
479 | } |
480 | |
481 | /* run the test with the bunch of open file descriptors |
482 | and close them all once the test is over */ |
483 | |
484 | if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) { |
485 | fprintf(stderr, "curl_global_init() failed\n" ); |
486 | close_file_descriptors(); |
487 | return TEST_ERR_MAJOR_BAD; |
488 | } |
489 | |
490 | curl = curl_easy_init(); |
491 | if(!curl) { |
492 | fprintf(stderr, "curl_easy_init() failed\n" ); |
493 | close_file_descriptors(); |
494 | curl_global_cleanup(); |
495 | return TEST_ERR_MAJOR_BAD; |
496 | } |
497 | |
498 | test_setopt(curl, CURLOPT_URL, URL); |
499 | test_setopt(curl, CURLOPT_HEADER, 1L); |
500 | |
501 | res = curl_easy_perform(curl); |
502 | |
503 | test_cleanup: |
504 | |
505 | close_file_descriptors(); |
506 | curl_easy_cleanup(curl); |
507 | curl_global_cleanup(); |
508 | |
509 | return (int)res; |
510 | } |
511 | |
512 | #else /* defined(HAVE_GETRLIMIT) && defined(HAVE_SETRLIMIT) */ |
513 | |
514 | int test(char *URL) |
515 | { |
516 | (void)URL; |
517 | printf("system lacks necessary system function(s)" ); |
518 | return 1; /* skip test */ |
519 | } |
520 | |
521 | #endif /* defined(HAVE_GETRLIMIT) && defined(HAVE_SETRLIMIT) */ |
522 | |