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