--- /dev/null
+---
+c: Copyright (C) Daniel Stenberg, <daniel.se>, 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 <curl/curl.h>
+#include <stdlib.h>
+
+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)
--- /dev/null
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Dmitry Karpov <dkarpov1970@gmail.com>
+ *
+ * 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;
+}