|  | /*************************************************************************** | 
|  | *                                  _   _ ____  _ | 
|  | *  Project                     ___| | | |  _ \| | | 
|  | *                             / __| | | | |_) | | | 
|  | *                            | (__| |_| |  _ <| |___ | 
|  | *                             \___|\___/|_| \_\_____| | 
|  | * | 
|  | * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. | 
|  | * | 
|  | * This software is licensed as described in the file COPYING, which | 
|  | * you should have received as part of this distribution. The terms | 
|  | * are also available at https://curl.se/docs/copyright.html. | 
|  | * | 
|  | * You may opt to use, copy, modify, merge, publish, distribute and/or sell | 
|  | * copies of the Software, and permit persons to whom the Software is | 
|  | * furnished to do so, under the terms of the COPYING file. | 
|  | * | 
|  | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | 
|  | * KIND, either express or implied. | 
|  | * | 
|  | * SPDX-License-Identifier: curl | 
|  | * | 
|  | ***************************************************************************/ | 
|  | #include "test.h" | 
|  |  | 
|  | #ifdef HAVE_SYS_RESOURCE_H | 
|  | #include <sys/resource.h> | 
|  | #endif | 
|  | #ifdef HAVE_FCNTL_H | 
|  | #include <fcntl.h> | 
|  | #endif | 
|  | #include <limits.h> | 
|  |  | 
|  | #include "warnless.h" | 
|  | #include "memdebug.h" | 
|  |  | 
|  | #ifndef FD_SETSIZE | 
|  | #error "this test requires FD_SETSIZE" | 
|  | #endif | 
|  |  | 
|  | #define SAFETY_MARGIN (16) | 
|  | #define NUM_OPEN      (FD_SETSIZE + 10) | 
|  | #define NUM_NEEDED    (NUM_OPEN + SAFETY_MARGIN) | 
|  |  | 
|  | #if defined(WIN32) || defined(_WIN32) || defined(MSDOS) | 
|  | #define DEV_NULL "NUL" | 
|  | #else | 
|  | #define DEV_NULL "/dev/null" | 
|  | #endif | 
|  |  | 
|  | #if defined(HAVE_GETRLIMIT) && defined(HAVE_SETRLIMIT) | 
|  |  | 
|  | static int *fd = NULL; | 
|  | static struct rlimit num_open; | 
|  | static char msgbuff[256]; | 
|  |  | 
|  | static void store_errmsg(const char *msg, int err) | 
|  | { | 
|  | if(!err) | 
|  | msnprintf(msgbuff, sizeof(msgbuff), "%s", msg); | 
|  | else | 
|  | msnprintf(msgbuff, sizeof(msgbuff), "%s, errno %d, %s", msg, | 
|  | err, strerror(err)); | 
|  | } | 
|  |  | 
|  | static void close_file_descriptors(void) | 
|  | { | 
|  | for(num_open.rlim_cur = 0; | 
|  | num_open.rlim_cur < num_open.rlim_max; | 
|  | num_open.rlim_cur++) | 
|  | if(fd[num_open.rlim_cur] > 0) | 
|  | close(fd[num_open.rlim_cur]); | 
|  | free(fd); | 
|  | fd = NULL; | 
|  | } | 
|  |  | 
|  | static int fopen_works(void) | 
|  | { | 
|  | FILE *fpa[3]; | 
|  | int i; | 
|  | int ret = 1; | 
|  |  | 
|  | for(i = 0; i < 3; i++) { | 
|  | fpa[i] = NULL; | 
|  | } | 
|  | for(i = 0; i < 3; i++) { | 
|  | fpa[i] = fopen(DEV_NULL, FOPEN_READTEXT); | 
|  | if(!fpa[i]) { | 
|  | store_errmsg("fopen failed", errno); | 
|  | fprintf(stderr, "%s\n", msgbuff); | 
|  | ret = 0; | 
|  | break; | 
|  | } | 
|  | } | 
|  | for(i = 0; i < 3; i++) { | 
|  | if(fpa[i]) | 
|  | fclose(fpa[i]); | 
|  | } | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int rlimit(int keep_open) | 
|  | { | 
|  | int nitems, i; | 
|  | int *memchunk = NULL; | 
|  | char *fmt; | 
|  | struct rlimit rl; | 
|  | char strbuff[256]; | 
|  | char strbuff1[81]; | 
|  | char strbuff2[81]; | 
|  | char fmt_u[] = "%u"; | 
|  | char fmt_lu[] = "%lu"; | 
|  | #ifdef HAVE_LONGLONG | 
|  | char fmt_llu[] = "%llu"; | 
|  |  | 
|  | if(sizeof(rl.rlim_max) > sizeof(long)) | 
|  | fmt = fmt_llu; | 
|  | else | 
|  | #endif | 
|  | fmt = (sizeof(rl.rlim_max) < sizeof(long))?fmt_u:fmt_lu; | 
|  |  | 
|  | /* get initial open file limits */ | 
|  |  | 
|  | if(getrlimit(RLIMIT_NOFILE, &rl) != 0) { | 
|  | store_errmsg("getrlimit() failed", errno); | 
|  | fprintf(stderr, "%s\n", msgbuff); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | /* show initial open file limits */ | 
|  |  | 
|  | #ifdef RLIM_INFINITY | 
|  | if(rl.rlim_cur == RLIM_INFINITY) | 
|  | strcpy(strbuff, "INFINITY"); | 
|  | else | 
|  | #endif | 
|  | msnprintf(strbuff, sizeof(strbuff), fmt, rl.rlim_cur); | 
|  | fprintf(stderr, "initial soft limit: %s\n", strbuff); | 
|  |  | 
|  | #ifdef RLIM_INFINITY | 
|  | if(rl.rlim_max == RLIM_INFINITY) | 
|  | strcpy(strbuff, "INFINITY"); | 
|  | else | 
|  | #endif | 
|  | msnprintf(strbuff, sizeof(strbuff), fmt, rl.rlim_max); | 
|  | fprintf(stderr, "initial hard limit: %s\n", strbuff); | 
|  |  | 
|  | /* show our constants */ | 
|  |  | 
|  | fprintf(stderr, "test518 FD_SETSIZE: %d\n", FD_SETSIZE); | 
|  | fprintf(stderr, "test518 NUM_OPEN  : %d\n", NUM_OPEN); | 
|  | fprintf(stderr, "test518 NUM_NEEDED: %d\n", NUM_NEEDED); | 
|  |  | 
|  | /* | 
|  | * if soft limit and hard limit are different we ask the | 
|  | * system to raise soft limit all the way up to the hard | 
|  | * limit. Due to some other system limit the soft limit | 
|  | * might not be raised up to the hard limit. So from this | 
|  | * point the resulting soft limit is our limit. Trying to | 
|  | * open more than soft limit file descriptors will fail. | 
|  | */ | 
|  |  | 
|  | if(rl.rlim_cur != rl.rlim_max) { | 
|  |  | 
|  | #ifdef OPEN_MAX | 
|  | if((rl.rlim_cur > 0) && | 
|  | (rl.rlim_cur < OPEN_MAX)) { | 
|  | fprintf(stderr, "raising soft limit up to OPEN_MAX\n"); | 
|  | rl.rlim_cur = OPEN_MAX; | 
|  | if(setrlimit(RLIMIT_NOFILE, &rl) != 0) { | 
|  | /* on failure don't abort just issue a warning */ | 
|  | store_errmsg("setrlimit() failed", errno); | 
|  | fprintf(stderr, "%s\n", msgbuff); | 
|  | msgbuff[0] = '\0'; | 
|  | } | 
|  | } | 
|  | #endif | 
|  |  | 
|  | fprintf(stderr, "raising soft limit up to hard limit\n"); | 
|  | rl.rlim_cur = rl.rlim_max; | 
|  | if(setrlimit(RLIMIT_NOFILE, &rl) != 0) { | 
|  | /* on failure don't abort just issue a warning */ | 
|  | store_errmsg("setrlimit() failed", errno); | 
|  | fprintf(stderr, "%s\n", msgbuff); | 
|  | msgbuff[0] = '\0'; | 
|  | } | 
|  |  | 
|  | /* get current open file limits */ | 
|  |  | 
|  | if(getrlimit(RLIMIT_NOFILE, &rl) != 0) { | 
|  | store_errmsg("getrlimit() failed", errno); | 
|  | fprintf(stderr, "%s\n", msgbuff); | 
|  | return -3; | 
|  | } | 
|  |  | 
|  | /* show current open file limits */ | 
|  |  | 
|  | #ifdef RLIM_INFINITY | 
|  | if(rl.rlim_cur == RLIM_INFINITY) | 
|  | strcpy(strbuff, "INFINITY"); | 
|  | else | 
|  | #endif | 
|  | msnprintf(strbuff, sizeof(strbuff), fmt, rl.rlim_cur); | 
|  | fprintf(stderr, "current soft limit: %s\n", strbuff); | 
|  |  | 
|  | #ifdef RLIM_INFINITY | 
|  | if(rl.rlim_max == RLIM_INFINITY) | 
|  | strcpy(strbuff, "INFINITY"); | 
|  | else | 
|  | #endif | 
|  | msnprintf(strbuff, sizeof(strbuff), fmt, rl.rlim_max); | 
|  | fprintf(stderr, "current hard limit: %s\n", strbuff); | 
|  |  | 
|  | } /* (rl.rlim_cur != rl.rlim_max) */ | 
|  |  | 
|  | /* | 
|  | * test 518 is all about testing libcurl functionality | 
|  | * when more than FD_SETSIZE file descriptors are open. | 
|  | * This means that if for any reason we are not able to | 
|  | * open more than FD_SETSIZE file descriptors then test | 
|  | * 518 should not be run. | 
|  | */ | 
|  |  | 
|  | /* | 
|  | * verify that soft limit is higher than NUM_NEEDED, | 
|  | * which is the number of file descriptors we would | 
|  | * try to open plus SAFETY_MARGIN to not exhaust the | 
|  | * file descriptor pool | 
|  | */ | 
|  |  | 
|  | num_open.rlim_cur = NUM_NEEDED; | 
|  |  | 
|  | if((rl.rlim_cur > 0) && | 
|  | #ifdef RLIM_INFINITY | 
|  | (rl.rlim_cur != RLIM_INFINITY) && | 
|  | #endif | 
|  | (rl.rlim_cur <= num_open.rlim_cur)) { | 
|  | msnprintf(strbuff2, sizeof(strbuff2), fmt, rl.rlim_cur); | 
|  | msnprintf(strbuff1, sizeof(strbuff1), fmt, num_open.rlim_cur); | 
|  | msnprintf(strbuff, sizeof(strbuff), "fds needed %s > system limit %s", | 
|  | strbuff1, strbuff2); | 
|  | store_errmsg(strbuff, 0); | 
|  | fprintf(stderr, "%s\n", msgbuff); | 
|  | return -4; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * reserve a chunk of memory before opening file descriptors to | 
|  | * avoid a low memory condition once the file descriptors are | 
|  | * open. System conditions that could make the test fail should | 
|  | * be addressed in the precheck phase. This chunk of memory shall | 
|  | * be always free()ed before exiting the rlimit() function so | 
|  | * that it becomes available to the test. | 
|  | */ | 
|  |  | 
|  | for(nitems = i = 1; nitems <= i; i *= 2) | 
|  | nitems = i; | 
|  | if(nitems > 0x7fff) | 
|  | nitems = 0x40000; | 
|  | do { | 
|  | num_open.rlim_max = sizeof(*memchunk) * (size_t)nitems; | 
|  | msnprintf(strbuff, sizeof(strbuff), fmt, num_open.rlim_max); | 
|  | fprintf(stderr, "allocating memchunk %s byte array\n", strbuff); | 
|  | memchunk = malloc(sizeof(*memchunk) * (size_t)nitems); | 
|  | if(!memchunk) { | 
|  | fprintf(stderr, "memchunk, malloc() failed\n"); | 
|  | nitems /= 2; | 
|  | } | 
|  | } while(nitems && !memchunk); | 
|  | if(!memchunk) { | 
|  | store_errmsg("memchunk, malloc() failed", errno); | 
|  | fprintf(stderr, "%s\n", msgbuff); | 
|  | return -5; | 
|  | } | 
|  |  | 
|  | /* initialize it to fight lazy allocation */ | 
|  |  | 
|  | fprintf(stderr, "initializing memchunk array\n"); | 
|  |  | 
|  | for(i = 0; i < nitems; i++) | 
|  | memchunk[i] = -1; | 
|  |  | 
|  | /* set the number of file descriptors we will try to open */ | 
|  |  | 
|  | num_open.rlim_max = NUM_OPEN; | 
|  |  | 
|  | /* verify that we won't overflow size_t in malloc() */ | 
|  |  | 
|  | if((size_t)(num_open.rlim_max) > ((size_t)-1) / sizeof(*fd)) { | 
|  | msnprintf(strbuff1, sizeof(strbuff1), fmt, num_open.rlim_max); | 
|  | msnprintf(strbuff, sizeof(strbuff), "unable to allocate an array for %s " | 
|  | "file descriptors, would overflow size_t", strbuff1); | 
|  | store_errmsg(strbuff, 0); | 
|  | fprintf(stderr, "%s\n", msgbuff); | 
|  | free(memchunk); | 
|  | return -6; | 
|  | } | 
|  |  | 
|  | /* allocate array for file descriptors */ | 
|  |  | 
|  | msnprintf(strbuff, sizeof(strbuff), fmt, num_open.rlim_max); | 
|  | fprintf(stderr, "allocating array for %s file descriptors\n", strbuff); | 
|  |  | 
|  | fd = malloc(sizeof(*fd) * (size_t)(num_open.rlim_max)); | 
|  | if(!fd) { | 
|  | store_errmsg("fd, malloc() failed", errno); | 
|  | fprintf(stderr, "%s\n", msgbuff); | 
|  | free(memchunk); | 
|  | return -7; | 
|  | } | 
|  |  | 
|  | /* initialize it to fight lazy allocation */ | 
|  |  | 
|  | fprintf(stderr, "initializing fd array\n"); | 
|  |  | 
|  | for(num_open.rlim_cur = 0; | 
|  | num_open.rlim_cur < num_open.rlim_max; | 
|  | num_open.rlim_cur++) | 
|  | fd[num_open.rlim_cur] = -1; | 
|  |  | 
|  | msnprintf(strbuff, sizeof(strbuff), fmt, num_open.rlim_max); | 
|  | fprintf(stderr, "trying to open %s file descriptors\n", strbuff); | 
|  |  | 
|  | /* open a dummy descriptor */ | 
|  |  | 
|  | fd[0] = open(DEV_NULL, O_RDONLY); | 
|  | if(fd[0] < 0) { | 
|  | msnprintf(strbuff, sizeof(strbuff), "opening of %s failed", DEV_NULL); | 
|  | store_errmsg(strbuff, errno); | 
|  | fprintf(stderr, "%s\n", msgbuff); | 
|  | free(fd); | 
|  | fd = NULL; | 
|  | free(memchunk); | 
|  | return -8; | 
|  | } | 
|  |  | 
|  | /* create a bunch of file descriptors */ | 
|  |  | 
|  | for(num_open.rlim_cur = 1; | 
|  | num_open.rlim_cur < num_open.rlim_max; | 
|  | num_open.rlim_cur++) { | 
|  |  | 
|  | fd[num_open.rlim_cur] = dup(fd[0]); | 
|  |  | 
|  | if(fd[num_open.rlim_cur] < 0) { | 
|  |  | 
|  | fd[num_open.rlim_cur] = -1; | 
|  |  | 
|  | msnprintf(strbuff1, sizeof(strbuff1), fmt, num_open.rlim_cur); | 
|  | msnprintf(strbuff, sizeof(strbuff), "dup() attempt %s failed", strbuff1); | 
|  | fprintf(stderr, "%s\n", strbuff); | 
|  |  | 
|  | msnprintf(strbuff1, sizeof(strbuff), fmt, num_open.rlim_cur); | 
|  | msnprintf(strbuff, sizeof(strbuff), "fds system limit seems close to %s", | 
|  | strbuff1); | 
|  | fprintf(stderr, "%s\n", strbuff); | 
|  |  | 
|  | num_open.rlim_max = NUM_NEEDED; | 
|  |  | 
|  | msnprintf(strbuff2, sizeof(strbuff2), fmt, num_open.rlim_max); | 
|  | msnprintf(strbuff1, sizeof(strbuff1), fmt, num_open.rlim_cur); | 
|  | msnprintf(strbuff, sizeof(strbuff), "fds needed %s > system limit %s", | 
|  | strbuff2, strbuff1); | 
|  | store_errmsg(strbuff, 0); | 
|  | fprintf(stderr, "%s\n", msgbuff); | 
|  |  | 
|  | for(num_open.rlim_cur = 0; | 
|  | fd[num_open.rlim_cur] >= 0; | 
|  | num_open.rlim_cur++) | 
|  | close(fd[num_open.rlim_cur]); | 
|  | free(fd); | 
|  | fd = NULL; | 
|  | free(memchunk); | 
|  | return -9; | 
|  |  | 
|  | } | 
|  |  | 
|  | } | 
|  |  | 
|  | msnprintf(strbuff, sizeof(strbuff), fmt, num_open.rlim_max); | 
|  | fprintf(stderr, "%s file descriptors open\n", strbuff); | 
|  |  | 
|  | #if !defined(HAVE_POLL_FINE) && !defined(USE_WINSOCK) | 
|  |  | 
|  | /* | 
|  | * when using select() instead of poll() we cannot test | 
|  | * libcurl functionality with a socket number equal or | 
|  | * greater than FD_SETSIZE. In any case, macro VERIFY_SOCK | 
|  | * in lib/select.c enforces this check and protects libcurl | 
|  | * from a possible crash. The effect of this protection | 
|  | * is that test 518 will always fail, since the actual | 
|  | * call to select() never takes place. We skip test 518 | 
|  | * with an indication that select limit would be exceeded. | 
|  | */ | 
|  |  | 
|  | num_open.rlim_cur = FD_SETSIZE - SAFETY_MARGIN; | 
|  | if(num_open.rlim_max > num_open.rlim_cur) { | 
|  | msnprintf(strbuff, sizeof(strbuff), "select limit is FD_SETSIZE %d", | 
|  | FD_SETSIZE); | 
|  | store_errmsg(strbuff, 0); | 
|  | fprintf(stderr, "%s\n", msgbuff); | 
|  | close_file_descriptors(); | 
|  | free(memchunk); | 
|  | return -10; | 
|  | } | 
|  |  | 
|  | num_open.rlim_cur = FD_SETSIZE - SAFETY_MARGIN; | 
|  | for(rl.rlim_cur = 0; | 
|  | rl.rlim_cur < num_open.rlim_max; | 
|  | rl.rlim_cur++) { | 
|  | if((fd[rl.rlim_cur] > 0) && | 
|  | ((unsigned int)fd[rl.rlim_cur] > num_open.rlim_cur)) { | 
|  | msnprintf(strbuff, sizeof(strbuff), "select limit is FD_SETSIZE %d", | 
|  | FD_SETSIZE); | 
|  | store_errmsg(strbuff, 0); | 
|  | fprintf(stderr, "%s\n", msgbuff); | 
|  | close_file_descriptors(); | 
|  | free(memchunk); | 
|  | return -11; | 
|  | } | 
|  | } | 
|  |  | 
|  | #endif /* using a FD_SETSIZE bound select() */ | 
|  |  | 
|  | /* | 
|  | * Old or 'backwards compatible' implementations of stdio do not allow | 
|  | * handling of streams with an underlying file descriptor number greater | 
|  | * than 255, even when allowing high numbered file descriptors for sockets. | 
|  | * At this point we have a big number of file descriptors which have been | 
|  | * opened using dup(), so lets test the stdio implementation and discover | 
|  | * if it is capable of fopen()ing some additional files. | 
|  | */ | 
|  |  | 
|  | if(!fopen_works()) { | 
|  | msnprintf(strbuff1, sizeof(strbuff1), fmt, num_open.rlim_max); | 
|  | msnprintf(strbuff, sizeof(strbuff), | 
|  | "fopen fails with %s fds open()", | 
|  | strbuff1); | 
|  | fprintf(stderr, "%s\n", msgbuff); | 
|  | msnprintf(strbuff, sizeof(strbuff), | 
|  | "fopen fails with lots of fds open()"); | 
|  | store_errmsg(strbuff, 0); | 
|  | close_file_descriptors(); | 
|  | free(memchunk); | 
|  | return -12; | 
|  | } | 
|  |  | 
|  | /* free the chunk of memory we were reserving so that it | 
|  | becomes becomes available to the test */ | 
|  |  | 
|  | free(memchunk); | 
|  |  | 
|  | /* close file descriptors unless instructed to keep them */ | 
|  |  | 
|  | if(!keep_open) { | 
|  | close_file_descriptors(); | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int test(char *URL) | 
|  | { | 
|  | CURLcode res; | 
|  | CURL *curl; | 
|  |  | 
|  | if(!strcmp(URL, "check")) { | 
|  | /* used by the test script to ask if we can run this test or not */ | 
|  | if(rlimit(FALSE)) { | 
|  | fprintf(stdout, "rlimit problem: %s\n", msgbuff); | 
|  | return 1; | 
|  | } | 
|  | return 0; /* sure, run this! */ | 
|  | } | 
|  |  | 
|  | if(rlimit(TRUE)) { | 
|  | /* failure */ | 
|  | return TEST_ERR_MAJOR_BAD; | 
|  | } | 
|  |  | 
|  | /* run the test with the bunch of open file descriptors | 
|  | and close them all once the test is over */ | 
|  |  | 
|  | if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) { | 
|  | fprintf(stderr, "curl_global_init() failed\n"); | 
|  | close_file_descriptors(); | 
|  | return TEST_ERR_MAJOR_BAD; | 
|  | } | 
|  |  | 
|  | curl = curl_easy_init(); | 
|  | if(!curl) { | 
|  | fprintf(stderr, "curl_easy_init() failed\n"); | 
|  | close_file_descriptors(); | 
|  | curl_global_cleanup(); | 
|  | return TEST_ERR_MAJOR_BAD; | 
|  | } | 
|  |  | 
|  | test_setopt(curl, CURLOPT_URL, URL); | 
|  | test_setopt(curl, CURLOPT_HEADER, 1L); | 
|  |  | 
|  | res = curl_easy_perform(curl); | 
|  |  | 
|  | test_cleanup: | 
|  |  | 
|  | close_file_descriptors(); | 
|  | curl_easy_cleanup(curl); | 
|  | curl_global_cleanup(); | 
|  |  | 
|  | return (int)res; | 
|  | } | 
|  |  | 
|  | #else /* defined(HAVE_GETRLIMIT) && defined(HAVE_SETRLIMIT) */ | 
|  |  | 
|  | int test(char *URL) | 
|  | { | 
|  | (void)URL; | 
|  | printf("system lacks necessary system function(s)"); | 
|  | return 1; /* skip test */ | 
|  | } | 
|  |  | 
|  | #endif /* defined(HAVE_GETRLIMIT) && defined(HAVE_SETRLIMIT) */ |