diff --git a/docs/libcurl/Makefile.inc b/docs/libcurl/Makefile.inc index 336b4192e..fe990cc1e 100644 --- a/docs/libcurl/Makefile.inc +++ b/docs/libcurl/Makefile.inc @@ -87,6 +87,7 @@ man_MANS = \ curl_multi_timeout.3 \ curl_multi_wakeup.3 \ curl_multi_wait.3 \ + curl_multi_waitfds.3 \ curl_pushheader_bynum.3 \ curl_pushheader_byname.3 \ curl_share_cleanup.3 \ diff --git a/docs/libcurl/curl_multi_fdset.md b/docs/libcurl/curl_multi_fdset.md index 1fe6e1f67..78299610c 100644 --- a/docs/libcurl/curl_multi_fdset.md +++ b/docs/libcurl/curl_multi_fdset.md @@ -10,6 +10,7 @@ See-also: - curl_multi_perform (3) - curl_multi_timeout (3) - curl_multi_wait (3) + - curl_multi_waitfds (3) - select (2) Protocol: - All diff --git a/docs/libcurl/curl_multi_waitfds.md b/docs/libcurl/curl_multi_waitfds.md new file mode 100644 index 000000000..93472f0ce --- /dev/null +++ b/docs/libcurl/curl_multi_waitfds.md @@ -0,0 +1,107 @@ +--- +c: Copyright (C) Daniel Stenberg, , et al. +SPDX-License-Identifier: curl +Title: curl_multi_waitfds +Section: 3 +Source: libcurl +See-also: + - curl_multi_perform (3) + - curl_multi_poll (3) + - curl_multi_wait (3) + - curl_multi_fdset (3) +--- + +# NAME + +curl_multi_waitfds - extracts file descriptor information from a multi handle + +# SYNOPSIS + +~~~c +#include +#include + +CURLMcode curl_multi_waitfds(CURLM *multi, + struct curl_waitfd *ufds, + unsigned int size, + unsigned int *fd_count); +~~~ + +# DESCRIPTION + +This function extracts *curl_waitfd* structures which are similar to +*poll(2)*'s *pollfd* structure from a given multi_handle. + +These structures can be used for polling on multi_handle file descriptors in a +fashion similar to curl_multi_poll(3). The curl_multi_perform(3) +function should be called as soon as one of them is ready to be read from or +written to. + +libcurl fills provided *ufds* array up to the *size*. +If a number of descriptors used by the multi_handle is greater than the +*size* parameter then libcurl returns CURLM_OUT_OF_MEMORY error. + +If the *fd_count* argument is not a null pointer, it points to a variable +that on returns specifies the number of descriptors used by the multi_handle to +be checked for being ready to read or write. + +The client code can pass *size* equal to zero just to get the number of the +descriptors and allocate appropriate storage for them to be used in a +subsequent function call. + +# EXAMPLE + +~~~c +int main(void) +{ + CURLMcode mc; + struct curl_waitfd *ufds; + + CURLM *multi = curl_multi_init(); + + do { + /* call curl_multi_perform() */ + + /* get the count of file descriptors from the transfers */ + unsigned int fd_count = 0; + + mc = curl_multi_waitfds(multi, NULL, 0, &fd_count); + + if(mc != CURLM_OK) { + fprintf(stderr, "curl_multi_waitfds() failed, code %d.\n", mc); + break; + } + + if(!fd_count) + continue; /* no descriptors yet */ + + /* Allocate storage for our descriptors. + * Note that a better approach can be used to minimize allocations and + * deallocations, if needed, like pre-allocated or grow-only array. + */ + ufds = (struct curl_waitfd*)malloc(fd_count * sizeof(struct curl_waitfd)); + + /* get wait descriptors from the transfers and put them into array. */ + mc = curl_multi_waitfds(multi, ufds, fd_count, NULL); + + if(mc != CURLM_OK) { + fprintf(stderr, "curl_multi_waitfds() failed, code %d.\n", mc); + free(ufds); + break; + } + + /* Do polling on descriptors in ufds */ + + free(ufds); + } while (!mc); +} +~~~ + +# AVAILABILITY + +Added in 8.8.0 + +# RETURN VALUE + +**CURLMcode** type, general libcurl multi interface error code. See +libcurl-errors(3) diff --git a/include/curl/multi.h b/include/curl/multi.h index e79b48ff3..561470ce7 100644 --- a/include/curl/multi.h +++ b/include/curl/multi.h @@ -464,6 +464,20 @@ typedef int (*curl_push_callback)(CURL *parent, struct curl_pushheaders *headers, void *userp); +/* + * Name: curl_multi_waitfds() + * + * Desc: Ask curl for fds for polling. The app can use these to poll on. + * We want curl_multi_perform() called as soon as one of them are + * ready. Passing zero size allows to get just a number of fds. + * + * Returns: CURLMcode type, general multi error code. + */ +CURL_EXTERN CURLMcode curl_multi_waitfds(CURLM *multi, + struct curl_waitfd *ufds, + unsigned int size, + unsigned int *fd_count); + #ifdef __cplusplus } /* end of extern "C" */ #endif diff --git a/lib/multi.c b/lib/multi.c index 03002436c..ee2b9cbde 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -1208,6 +1208,68 @@ CURLMcode curl_multi_fdset(struct Curl_multi *multi, return CURLM_OK; } +CURLMcode curl_multi_waitfds(struct Curl_multi *multi, + struct curl_waitfd *ufds, + unsigned int size, + unsigned int *fd_count) +{ + struct Curl_easy *data; + unsigned int nfds = 0; + struct easy_pollset ps; + unsigned int i; + CURLMcode result = CURLM_OK; + struct curl_waitfd *ufd; + unsigned int j; + + if(!ufds) + return CURLM_BAD_FUNCTION_ARGUMENT; + + if(!GOOD_MULTI_HANDLE(multi)) + return CURLM_BAD_HANDLE; + + if(multi->in_callback) + return CURLM_RECURSIVE_API_CALL; + + memset(&ps, 0, sizeof(ps)); + for(data = multi->easyp; data; data = data->next) { + multi_getsock(data, &ps); + + for(i = 0; i < ps.num; i++) { + if(nfds < size) { + curl_socket_t fd = ps.sockets[i]; + int fd_idx = -1; + + /* Simple linear search to skip an already added descriptor */ + for(j = 0; j < nfds; j++) { + if(ufds[j].fd == fd) { + fd_idx = (int)j; + break; + } + } + + if(fd_idx < 0) { + ufd = &ufds[nfds++]; + ufd->fd = ps.sockets[i]; + ufd->events = 0; + } + else + ufd = &ufds[fd_idx]; + + if(ps.actions[i] & CURL_POLL_IN) + ufd->events |= CURL_WAIT_POLLIN; + if(ps.actions[i] & CURL_POLL_OUT) + ufd->events |= CURL_WAIT_POLLOUT; + } + else + return CURLM_OUT_OF_MEMORY; + } + } + + if(fd_count) + *fd_count = nfds; + return result; +} + #ifdef USE_WINSOCK /* Reset FD_WRITE for TCP sockets. Nothing is actually sent. UDP sockets can't * be reset this way because an empty datagram would be sent. #9203 diff --git a/libcurl.def b/libcurl.def index c6c96063a..9bf9fcc95 100644 --- a/libcurl.def +++ b/libcurl.def @@ -64,6 +64,7 @@ curl_multi_socket_all curl_multi_strerror curl_multi_timeout curl_multi_wait +curl_multi_waitfds curl_multi_wakeup curl_mvaprintf curl_mvfprintf diff --git a/scripts/singleuse.pl b/scripts/singleuse.pl index 064990226..3731edc85 100755 --- a/scripts/singleuse.pl +++ b/scripts/singleuse.pl @@ -114,6 +114,7 @@ my %api = ( 'curl_multi_strerror' => 'API', 'curl_multi_timeout' => 'API', 'curl_multi_wait' => 'API', + 'curl_multi_waitfds' => 'API', 'curl_multi_wakeup' => 'API', 'curl_mvaprintf' => 'API', 'curl_mvfprintf' => 'API', diff --git a/tests/data/Makefile.inc b/tests/data/Makefile.inc index 2fc287dbc..cfbfb7860 100644 --- a/tests/data/Makefile.inc +++ b/tests/data/Makefile.inc @@ -248,7 +248,7 @@ test2200 test2201 test2202 test2203 test2204 test2205 \ \ test2300 test2301 test2302 test2303 test2304 test2305 test2306 test2307 \ \ -test2400 test2401 test2402 test2403 test2404 \ +test2400 test2401 test2402 test2403 test2404 test2405 \ \ test2500 test2501 test2502 test2503 \ \ diff --git a/tests/data/test1135 b/tests/data/test1135 index de028a0c9..e1e74752a 100644 --- a/tests/data/test1135 +++ b/tests/data/test1135 @@ -109,6 +109,7 @@ curl_multi_assign curl_multi_get_handles curl_pushheader_bynum curl_pushheader_byname +curl_multi_waitfds curl_easy_option_by_name curl_easy_option_by_id curl_easy_option_next diff --git a/tests/data/test2405 b/tests/data/test2405 new file mode 100644 index 000000000..38bb1cd8c --- /dev/null +++ b/tests/data/test2405 @@ -0,0 +1,51 @@ + + + +multi +HTTP +HTTP/2 + + + +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT +ETag: "21025-dc7-39462498" +Accept-Ranges: bytes +Content-Length: 6007 +Connection: close +Content-Type: text/html +Funny-head: yesyes + +-foo- +%repeat[1000 x foobar]% + + + +# Client-side + + +http +http/2 + + +lib%TESTNUMBER + + +checking curl_multi_waitfds functionality + + +http://%HOSTIP:%HTTP2PORT/%TESTNUMBER + + + +# Verify data after the test has been "shot" + + + + + diff --git a/tests/libtest/Makefile.inc b/tests/libtest/Makefile.inc index c55d528ab..65cc73a91 100644 --- a/tests/libtest/Makefile.inc +++ b/tests/libtest/Makefile.inc @@ -74,7 +74,7 @@ noinst_PROGRAMS = chkhostname libauthretry libntlmconnect libprereq \ lib1960 lib1964 \ lib1970 lib1971 lib1972 lib1973 lib1974 lib1975 \ lib2301 lib2302 lib2304 lib2305 lib2306 \ - lib2402 lib2404 \ + lib2402 lib2404 lib2405 \ lib2502 \ lib3010 lib3025 lib3026 lib3027 \ lib3100 lib3101 lib3102 lib3103 @@ -683,6 +683,9 @@ lib2402_LDADD = $(TESTUTIL_LIBS) lib2404_SOURCES = lib2404.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS) lib2404_LDADD = $(TESTUTIL_LIBS) +lib2405_SOURCES = lib2405.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS) +lib2405_LDADD = $(TESTUTIL_LIBS) + lib2502_SOURCES = lib2502.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS) lib2502_LDADD = $(TESTUTIL_LIBS) diff --git a/tests/libtest/lib2405.c b/tests/libtest/lib2405.c new file mode 100644 index 000000000..a4a7fd363 --- /dev/null +++ b/tests/libtest/lib2405.c @@ -0,0 +1,309 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Dmitry Karpov + * + * 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 + * + ***************************************************************************/ + +/* + * The purpose of this test is to test behavior of curl_multi_waitfds + * function in different scenarios: + * empty multi handle (expected zero descriptors), + * HTTP1 amd HTTP2 (no multiplexing) two transfers (expected two descriptors), + * HTTP2 with multiplexing (expected one descriptors) + * + * It is also expected that all transfers run by multi-handle should complete + * successfully. + */ + +#include "test.h" + +#include "testutil.h" +#include "warnless.h" +#include "memdebug.h" + + + /* ---------------------------------------------------------------- */ + +#define test_check(expected_fds) \ + if(res != CURLE_OK) { \ + fprintf(stderr, "test failed with code: %d\n", res); \ + goto test_cleanup; \ + } \ + else if(fd_count != expected_fds) { \ + fprintf(stderr, "Max number of waitfds: %d not as expected: %d\n", \ + fd_count, expected_fds); \ + res = TEST_ERR_FAILURE; \ + goto test_cleanup; \ + } + +#define test_run_check(option, expected_fds) do { \ + res = test_run(URL, option, &fd_count); \ + test_check(expected_fds); \ +} while(0) + + /* ---------------------------------------------------------------- */ + +enum { + TEST_USE_HTTP1 = 0, + TEST_USE_HTTP2, + TEST_USE_HTTP2_MPLEX +}; + +static size_t emptyWriteFunc(void *ptr, size_t size, size_t nmemb, + void *data) { + (void)ptr; (void)data; + return size * nmemb; +} + +static int set_easy(char *URL, CURL *easy, long option) +{ + int res = CURLE_OK; + + /* First set the URL that is about to receive our POST. */ + easy_setopt(easy, CURLOPT_URL, URL); + + /* get verbose debug output please */ + easy_setopt(easy, CURLOPT_VERBOSE, 1L); + + switch(option) { + case TEST_USE_HTTP1: + /* go http1 */ + easy_setopt(easy, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + break; + + case TEST_USE_HTTP2: + /* go http2 */ + easy_setopt(easy, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0); + break; + + case TEST_USE_HTTP2_MPLEX: + /* go http2 with multiplexing */ + easy_setopt(easy, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0); + easy_setopt(easy, CURLOPT_PIPEWAIT, 1L); + break; + } + + /* no peer verify */ + easy_setopt(easy, CURLOPT_SSL_VERIFYPEER, 0L); + easy_setopt(easy, CURLOPT_SSL_VERIFYHOST, 0L); + + /* include headers */ + easy_setopt(easy, CURLOPT_HEADER, 1L); + + /* empty write function */ + easy_setopt(easy, CURLOPT_WRITEFUNCTION, emptyWriteFunc); + +test_cleanup: + return res; +} + +static int test_run(char *URL, long option, unsigned int *max_fd_count) +{ + CURLMcode mc = CURLM_OK; + CURLM *multi = NULL; + CURLM *multi1 = NULL; + + CURL *easy1 = NULL; + CURL *easy2 = NULL; + + unsigned int max_count = 0; + + int still_running; /* keep number of running handles */ + CURLMsg *msg; /* for picking up messages with the transfer status */ + int msgs_left; /* how many messages are left */ + + CURLcode result; + int res = CURLE_OK; + + struct curl_waitfd ufds[10]; + struct curl_waitfd ufds1[10]; + int numfds; + + easy_init(easy1); + easy_init(easy2); + + if(set_easy(URL, easy1, option) != CURLE_OK) + goto test_cleanup; + + if(set_easy(URL, easy2, option) != CURLE_OK) + goto test_cleanup; + + multi_init(multi); + multi_init(multi1); + + if(option == TEST_USE_HTTP2_MPLEX) + multi_setopt(multi, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX); + + multi_add_handle(multi, easy1); + multi_add_handle(multi, easy2); + + while(!mc) { + /* get the count of file descriptors from the transfers */ + unsigned int fd_count = 0; + + mc = curl_multi_perform(multi, &still_running); + if(!still_running || mc != CURLM_OK) + break; + + mc = curl_multi_waitfds(multi, ufds, 10, &fd_count); + + if(mc != CURLM_OK) { + fprintf(stderr, "curl_multi_waitfds() failed, code %d.\n", mc); + res = TEST_ERR_FAILURE; + break; + } + + if(!fd_count) + continue; /* no descriptors yet */ + + /* checking case when we don't have enough space for waitfds */ + mc = curl_multi_waitfds(multi, ufds1, fd_count - 1, NULL); + + if(mc != CURLM_OUT_OF_MEMORY) { + fprintf(stderr, "curl_multi_waitfds() return code %d instead of " + "CURLM_OUT_OF_MEMORY.\n", mc); + res = TEST_ERR_FAILURE; + break; + } + + if(fd_count > max_count) + max_count = fd_count; + + /* Do polling on descriptors in ufds in Multi 1 */ + mc = curl_multi_poll(multi1, ufds, fd_count, 500, &numfds); + + if(mc != CURLM_OK) { + fprintf(stderr, "curl_multi_poll() failed, code %d.\\n", mc); + res = TEST_ERR_FAILURE; + break; + } + } + + for(;;) { + msg = curl_multi_info_read(multi, &msgs_left); + if(!msg) + break; + if(msg->msg == CURLMSG_DONE) { + result = msg->data.result; + + if(!res) + res = (int)result; + } + } + + curl_multi_remove_handle(multi, easy1); + curl_multi_remove_handle(multi, easy2); + +test_cleanup: + curl_easy_cleanup(easy1); + curl_easy_cleanup(easy2); + + curl_multi_cleanup(multi); + curl_multi_cleanup(multi1); + + if(max_fd_count) + *max_fd_count = max_count; + return res; +} + +static int empty_multi_test(void) +{ + CURLMcode mc = CURLM_OK; + CURLM *multi = NULL; + CURL *easy = NULL; + + struct curl_waitfd ufds[10]; + + int res = CURLE_OK; + unsigned int fd_count = 0; + + multi_init(multi); + + /* calling curl_multi_waitfds() on an empty multi handle. */ + mc = curl_multi_waitfds(multi, ufds, 10, &fd_count); + + if(mc != CURLM_OK) { + fprintf(stderr, "curl_multi_waitfds() failed, code %d.\n", mc); + res = TEST_ERR_FAILURE; + goto test_cleanup; + } + else if(fd_count > 0) { + fprintf(stderr, "curl_multi_waitfds() returned non-zero count of " + "waitfds: %d.\n", fd_count); + res = TEST_ERR_FAILURE; + goto test_cleanup; + } + + /* calling curl_multi_waitfds() on multi handle with added easy handle. */ + easy_init(easy); + + if(set_easy((char *)"http://example.com", easy, TEST_USE_HTTP1) != CURLE_OK) + goto test_cleanup; + + multi_add_handle(multi, easy); + + mc = curl_multi_waitfds(multi, ufds, 10, &fd_count); + + if(mc != CURLM_OK) { + fprintf(stderr, "curl_multi_waitfds() failed, code %d.\n", mc); + res = TEST_ERR_FAILURE; + goto test_cleanup; + } + else if(fd_count > 0) { + fprintf(stderr, "curl_multi_waitfds() returned non-zero count of " + "waitfds: %d.\n", fd_count); + res = TEST_ERR_FAILURE; + goto test_cleanup; + } + + curl_multi_remove_handle(multi, easy); + +test_cleanup: + curl_easy_cleanup(easy); + curl_multi_cleanup(multi); + return res; +} + +int test(char *URL) +{ + int res = CURLE_OK; + unsigned int fd_count = 0; + + global_init(CURL_GLOBAL_ALL); + + /* Testing curl_multi_waitfds on empty and not started handles */ + res = empty_multi_test(); + if(res != CURLE_OK) + goto test_cleanup; + + /* HTTP1, expected 2 waitfds - one for each transfer */ + test_run_check(TEST_USE_HTTP1, 2); + + /* HTTP2, expected 2 waitfds - one for each transfer */ + test_run_check(TEST_USE_HTTP2, 2); + + /* HTTP2 with multiplexing, expected 1 waitfds - one for all transfers */ + test_run_check(TEST_USE_HTTP2_MPLEX, 1); + +test_cleanup: + curl_global_cleanup(); + return res; +}