diff options
-rw-r--r-- | docs/libcurl/opts/CURLMOPT_SOCKETFUNCTION.3 | 6 | ||||
-rw-r--r-- | docs/libcurl/opts/CURLMOPT_TIMERFUNCTION.3 | 8 | ||||
-rw-r--r-- | docs/libcurl/symbols-in-versions | 1 | ||||
-rw-r--r-- | include/curl/multi.h | 5 | ||||
-rw-r--r-- | lib/easy.c | 8 | ||||
-rw-r--r-- | lib/multi.c | 173 | ||||
-rw-r--r-- | lib/multihandle.h | 2 | ||||
-rw-r--r-- | lib/multiif.h | 4 | ||||
-rw-r--r-- | lib/strerror.c | 3 | ||||
-rw-r--r-- | tests/data/Makefile.inc | 2 | ||||
-rw-r--r-- | tests/data/test1538 | 3 | ||||
-rw-r--r-- | tests/data/test530 | 49 | ||||
-rw-r--r-- | tests/libtest/Makefile.inc | 6 | ||||
-rw-r--r-- | tests/libtest/lib530.c | 371 |
14 files changed, 574 insertions, 67 deletions
diff --git a/docs/libcurl/opts/CURLMOPT_SOCKETFUNCTION.3 b/docs/libcurl/opts/CURLMOPT_SOCKETFUNCTION.3 index 8efbce490..935d45554 100644 --- a/docs/libcurl/opts/CURLMOPT_SOCKETFUNCTION.3 +++ b/docs/libcurl/opts/CURLMOPT_SOCKETFUNCTION.3 @@ -5,7 +5,7 @@ .\" * | (__| |_| | _ <| |___ .\" * \___|\___/|_| \_\_____| .\" * -.\" * Copyright (C) 1998 - 2019, Daniel Stenberg, <daniel@haxx.se>, et al. +.\" * Copyright (C) 1998 - 2021, 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 @@ -53,6 +53,10 @@ what activity on this socket the application is supposed to monitor. Subsequent calls to this callback might update the \fBwhat\fP bits for a socket that is already monitored. +The socket callback should return 0 on success, and -1 on error. If this +callback returns error, \fBall\fP transfers currently in progress in this +multi handle will be aborted and fail. + \fBuserp\fP is set with \fICURLMOPT_SOCKETDATA(3)\fP. \fBsocketp\fP is set with \fIcurl_multi_assign(3)\fP or will be NULL. diff --git a/docs/libcurl/opts/CURLMOPT_TIMERFUNCTION.3 b/docs/libcurl/opts/CURLMOPT_TIMERFUNCTION.3 index bea24e293..5b394296e 100644 --- a/docs/libcurl/opts/CURLMOPT_TIMERFUNCTION.3 +++ b/docs/libcurl/opts/CURLMOPT_TIMERFUNCTION.3 @@ -52,8 +52,12 @@ changed. The \fBuserp\fP pointer is set with \fICURLMOPT_TIMERDATA(3)\fP. -The timer callback should return 0 on success, and -1 on error. This callback -can be used instead of, or in addition to, \fIcurl_multi_timeout(3)\fP. +The timer callback should return 0 on success, and -1 on error. If this +callback returns error, \fBall\fP transfers currently in progress in this +multi handle will be aborted and fail. + +This callback can be used instead of, or in addition to, +\fIcurl_multi_timeout(3)\fP. \fBWARNING:\fP even if it feels tempting, avoid calling libcurl directly from within the callback itself when the \fBtimeout_ms\fP value is zero, since it diff --git a/docs/libcurl/symbols-in-versions b/docs/libcurl/symbols-in-versions index feec675fe..dfc8c04ec 100644 --- a/docs/libcurl/symbols-in-versions +++ b/docs/libcurl/symbols-in-versions @@ -343,6 +343,7 @@ CURLMOPT_TIMERDATA 7.16.0 CURLMOPT_TIMERFUNCTION 7.16.0 CURLMSG_DONE 7.9.6 CURLMSG_NONE 7.9.6 +CURLM_ABORTED_BY_CALLBACK 7.81.0 CURLM_ADDED_ALREADY 7.32.1 CURLM_BAD_EASY_HANDLE 7.9.6 CURLM_BAD_FUNCTION_ARGUMENT 7.69.0 diff --git a/include/curl/multi.h b/include/curl/multi.h index 37f9829b3..91cd95d32 100644 --- a/include/curl/multi.h +++ b/include/curl/multi.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2020, Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) 1998 - 2021, 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 @@ -73,7 +73,8 @@ typedef enum { CURLM_RECURSIVE_API_CALL, /* an api function was called from inside a callback */ CURLM_WAKEUP_FAILURE, /* wakeup is unavailable or failed */ - CURLM_BAD_FUNCTION_ARGUMENT, /* function called with a bad parameter */ + CURLM_BAD_FUNCTION_ARGUMENT, /* function called with a bad parameter */ + CURLM_ABORTED_BY_CALLBACK, CURLM_LAST } CURLMcode; diff --git a/lib/easy.c b/lib/easy.c index 2aca93845..349802a67 100644 --- a/lib/easy.c +++ b/lib/easy.c @@ -1087,14 +1087,16 @@ CURLcode curl_easy_pause(struct Curl_easy *data, int action) /* if not pausing again, force a recv/send check of this connection as the data might've been read off the socket already */ data->conn->cselect_bits = CURL_CSELECT_IN | CURL_CSELECT_OUT; - if(data->multi) - Curl_update_timer(data->multi); + if(data->multi) { + if(Curl_update_timer(data->multi)) + return CURLE_ABORTED_BY_CALLBACK; + } } if(!data->state.done) /* This transfer may have been moved in or out of the bundle, update the corresponding socket callback, if used */ - Curl_updatesocket(data); + result = Curl_updatesocket(data); return result; } diff --git a/lib/multi.c b/lib/multi.c index ce634fcac..c34780f73 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -424,6 +424,7 @@ struct Curl_multi *curl_multi_init(void) CURLMcode curl_multi_add_handle(struct Curl_multi *multi, struct Curl_easy *data) { + CURLMcode rc; /* First, make some basic checks that the CURLM handle is a good handle */ if(!GOOD_MULTI_HANDLE(multi)) return CURLM_BAD_HANDLE; @@ -440,6 +441,15 @@ CURLMcode curl_multi_add_handle(struct Curl_multi *multi, if(multi->in_callback) return CURLM_RECURSIVE_API_CALL; + if(multi->dead) { + /* a "dead" handle cannot get added transfers while any existing easy + handles are still alive - but if there are none alive anymore, it is + fine to start over and unmark the "deadness" of this handle */ + if(multi->num_alive) + return CURLM_ABORTED_BY_CALLBACK; + multi->dead = FALSE; + } + /* Initialize timeout list for this handle */ Curl_llist_init(&data->state.timeoutlist, NULL); @@ -452,6 +462,34 @@ CURLMcode curl_multi_add_handle(struct Curl_multi *multi, if(data->set.errorbuffer) data->set.errorbuffer[0] = 0; + /* make the Curl_easy refer back to this multi handle - before Curl_expire() + is called. */ + data->multi = multi; + + /* Set the timeout for this handle to expire really soon so that it will + be taken care of even when this handle is added in the midst of operation + when only the curl_multi_socket() API is used. During that flow, only + sockets that time-out or have actions will be dealt with. Since this + handle has no action yet, we make sure it times out to get things to + happen. */ + Curl_expire(data, 0, EXPIRE_RUN_NOW); + + /* A somewhat crude work-around for a little glitch in Curl_update_timer() + that happens if the lastcall time is set to the same time when the handle + is removed as when the next handle is added, as then the check in + Curl_update_timer() that prevents calling the application multiple times + with the same timer info will not trigger and then the new handle's + timeout will not be notified to the app. + + The work-around is thus simply to clear the 'lastcall' variable to force + Curl_update_timer() to always trigger a callback to the app when a new + easy handle is added */ + memset(&multi->timer_lastcall, 0, sizeof(multi->timer_lastcall)); + + rc = Curl_update_timer(multi); + if(rc) + return rc; + /* set the easy handle */ multistate(data, MSTATE_INIT); @@ -492,35 +530,12 @@ CURLMcode curl_multi_add_handle(struct Curl_multi *multi, multi->easylp = multi->easyp = data; /* both first and last */ } - /* make the Curl_easy refer back to this multi handle */ - data->multi = multi; - - /* Set the timeout for this handle to expire really soon so that it will - be taken care of even when this handle is added in the midst of operation - when only the curl_multi_socket() API is used. During that flow, only - sockets that time-out or have actions will be dealt with. Since this - handle has no action yet, we make sure it times out to get things to - happen. */ - Curl_expire(data, 0, EXPIRE_RUN_NOW); - /* increase the node-counter */ multi->num_easy++; /* increase the alive-counter */ multi->num_alive++; - /* A somewhat crude work-around for a little glitch in Curl_update_timer() - that happens if the lastcall time is set to the same time when the handle - is removed as when the next handle is added, as then the check in - Curl_update_timer() that prevents calling the application multiple times - with the same timer info will not trigger and then the new handle's - timeout will not be notified to the app. - - The work-around is thus simply to clear the 'lastcall' variable to force - Curl_update_timer() to always trigger a callback to the app when a new - easy handle is added */ - memset(&multi->timer_lastcall, 0, sizeof(multi->timer_lastcall)); - CONNCACHE_LOCK(data); /* The closure handle only ever has default timeouts set. To improve the state somewhat we clone the timeouts from each added handle so that the @@ -533,7 +548,6 @@ CURLMcode curl_multi_add_handle(struct Curl_multi *multi, data->set.no_signal; CONNCACHE_UNLOCK(data); - Curl_update_timer(multi); return CURLM_OK; } @@ -719,6 +733,7 @@ CURLMcode curl_multi_remove_handle(struct Curl_multi *multi, struct Curl_easy *easy = data; bool premature; struct Curl_llist_element *e; + CURLMcode rc; /* First, make some basic checks that the CURLM handle is a good handle */ if(!GOOD_MULTI_HANDLE(multi)) @@ -792,8 +807,11 @@ CURLMcode curl_multi_remove_handle(struct Curl_multi *multi, /* change state without using multistate(), only to make singlesocket() do what we want */ data->mstate = MSTATE_COMPLETED; - singlesocket(multi, easy); /* to let the application know what sockets that - vanish with this handle */ + + /* This ignores the return code even in case of problems because there's + nothing more to do about that, here */ + (void)singlesocket(multi, easy); /* to let the application know what sockets + that vanish with this handle */ /* Remove the association between the connection and the handle */ Curl_detach_connnection(data); @@ -858,7 +876,9 @@ CURLMcode curl_multi_remove_handle(struct Curl_multi *multi, process_pending_handles(multi); - Curl_update_timer(multi); + rc = Curl_update_timer(multi); + if(rc) + return rc; return CURLM_OK; } @@ -1743,6 +1763,15 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, if(!GOOD_EASY_HANDLE(data)) return CURLM_BAD_EASY_HANDLE; + if(multi->dead) { + /* a multi-level callback returned error before, meaning every individual + transfer now has failed */ + result = CURLE_ABORTED_BY_CALLBACK; + Curl_posttransfer(data); + multi_done(data, result, FALSE); + multistate(data, MSTATE_COMPLETED); + } + do { /* A "stream" here is a logical stream if the protocol can handle that (HTTP/2), or the full connection for older protocols */ @@ -1893,7 +1922,9 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, down. If the name has not yet been resolved, it is likely that new sockets have been opened in an attempt to contact another resolver. */ - singlesocket(multi, data); + rc = singlesocket(multi, data); + if(rc) + return rc; if(dns) { /* Perform the next step in the connection phase, and then move on @@ -2618,7 +2649,7 @@ CURLMcode curl_multi_perform(struct Curl_multi *multi, int *running_handles) *running_handles = multi->num_alive; if(CURLM_OK >= returncode) - Curl_update_timer(multi); + returncode = Curl_update_timer(multi); return returncode; } @@ -2738,6 +2769,7 @@ static CURLMcode singlesocket(struct Curl_multi *multi, int num; unsigned int curraction; unsigned char actions[MAX_SOCKSPEREASYHANDLE]; + int rc; for(i = 0; i< MAX_SOCKSPEREASYHANDLE; i++) socks[i] = CURL_SOCKET_BAD; @@ -2809,8 +2841,10 @@ static CURLMcode singlesocket(struct Curl_multi *multi, /* add 'data' to the transfer hash on this socket! */ if(!Curl_hash_add(&entry->transfers, (char *)&data, /* hash key */ - sizeof(struct Curl_easy *), data)) + sizeof(struct Curl_easy *), data)) { + Curl_hash_destroy(&entry->transfers); return CURLM_OUT_OF_MEMORY; + } } comboaction = (entry->writers? CURL_POLL_OUT : 0) | @@ -2821,9 +2855,14 @@ static CURLMcode singlesocket(struct Curl_multi *multi, /* same, continue */ continue; - if(multi->socket_cb) - multi->socket_cb(data, s, comboaction, multi->socket_userp, - entry->socketp); + if(multi->socket_cb) { + rc = multi->socket_cb(data, s, comboaction, multi->socket_userp, + entry->socketp); + if(rc == -1) { + multi->dead = TRUE; + return CURLM_ABORTED_BY_CALLBACK; + } + } entry->action = comboaction; /* store the current action state */ } @@ -2858,10 +2897,14 @@ static CURLMcode singlesocket(struct Curl_multi *multi, if(oldactions & CURL_POLL_IN) entry->readers--; if(!entry->users) { - if(multi->socket_cb) - multi->socket_cb(data, s, CURL_POLL_REMOVE, - multi->socket_userp, - entry->socketp); + if(multi->socket_cb) { + rc = multi->socket_cb(data, s, CURL_POLL_REMOVE, + multi->socket_userp, entry->socketp); + if(rc == -1) { + multi->dead = TRUE; + return CURLM_ABORTED_BY_CALLBACK; + } + } sh_delentry(entry, &multi->sockhash, s); } else { @@ -2880,9 +2923,11 @@ static CURLMcode singlesocket(struct Curl_multi *multi, return CURLM_OK; } -void Curl_updatesocket(struct Curl_easy *data) +CURLcode Curl_updatesocket(struct Curl_easy *data) { - singlesocket(data->multi, data); + if(singlesocket(data->multi, data)) + return CURLE_ABORTED_BY_CALLBACK; + return CURLE_OK; } @@ -2907,13 +2952,18 @@ void Curl_multi_closed(struct Curl_easy *data, curl_socket_t s) struct Curl_sh_entry *entry = sh_getentry(&multi->sockhash, s); if(entry) { + int rc = 0; if(multi->socket_cb) - multi->socket_cb(data, s, CURL_POLL_REMOVE, - multi->socket_userp, - entry->socketp); + rc = multi->socket_cb(data, s, CURL_POLL_REMOVE, + multi->socket_userp, entry->socketp); /* now remove it from the socket hash */ sh_delentry(entry, &multi->sockhash, s); + if(rc == -1) + /* This just marks the multi handle as "dead" without returning an + error code primarily because this function is used from many + places where propagating an error back is tricky. */ + multi->dead = TRUE; } } } @@ -3173,7 +3223,7 @@ CURLMcode curl_multi_socket(struct Curl_multi *multi, curl_socket_t s, return CURLM_RECURSIVE_API_CALL; result = multi_socket(multi, FALSE, s, 0, running_handles); if(CURLM_OK >= result) - Curl_update_timer(multi); + result = Curl_update_timer(multi); return result; } @@ -3185,7 +3235,7 @@ CURLMcode curl_multi_socket_action(struct Curl_multi *multi, curl_socket_t s, return CURLM_RECURSIVE_API_CALL; result = multi_socket(multi, FALSE, s, ev_bitmask, running_handles); if(CURLM_OK >= result) - Curl_update_timer(multi); + result = Curl_update_timer(multi); return result; } @@ -3196,7 +3246,7 @@ CURLMcode curl_multi_socket_all(struct Curl_multi *multi, int *running_handles) return CURLM_RECURSIVE_API_CALL; result = multi_socket(multi, TRUE, CURL_SOCKET_BAD, 0, running_handles); if(CURLM_OK >= result) - Curl_update_timer(multi); + result = Curl_update_timer(multi); return result; } @@ -3205,6 +3255,11 @@ static CURLMcode multi_timeout(struct Curl_multi *multi, { static const struct curltime tv_zero = {0, 0}; + if(multi->dead) { + *timeout_ms = 0; + return CURLM_OK; + } + if(multi->timetree) { /* we have a tree of expire times */ struct curltime now = Curl_now(); @@ -3256,14 +3311,15 @@ CURLMcode curl_multi_timeout(struct Curl_multi *multi, * Tell the application it should update its timers, if it subscribes to the * update timer callback. */ -void Curl_update_timer(struct Curl_multi *multi) +CURLMcode Curl_update_timer(struct Curl_multi *multi) { long timeout_ms; + int rc; - if(!multi->timer_cb) - return; + if(!multi->timer_cb || multi->dead) + return CURLM_OK; if(multi_timeout(multi, &timeout_ms)) { - return; + return CURLM_OK; } if(timeout_ms < 0) { static const struct curltime none = {0, 0}; @@ -3271,10 +3327,14 @@ void Curl_update_timer(struct Curl_multi *multi) multi->timer_lastcall = none; /* there's no timeout now but there was one previously, tell the app to disable it */ - multi->timer_cb(multi, -1, multi->timer_userp); - return; + rc = multi->timer_cb(multi, -1, multi->timer_userp); + if(rc == -1) { + multi->dead = TRUE; + return CURLM_ABORTED_BY_CALLBACK; + } + return CURLM_OK; } - return; + return CURLM_OK; } /* When multi_timeout() is done, multi->timetree points to the node with the @@ -3282,11 +3342,16 @@ void Curl_update_timer(struct Curl_multi *multi) * if this is the same (fixed) time as we got in a previous call and then * avoid calling the callback again. */ if(Curl_splaycomparekeys(multi->timetree->key, multi->timer_lastcall) == 0) - return; + return CURLM_OK; multi->timer_lastcall = multi->timetree->key; - multi->timer_cb(multi, timeout_ms, multi->timer_userp); + rc = multi->timer_cb(multi, timeout_ms, multi->timer_userp); + if(rc == -1) { + multi->dead = TRUE; + return CURLM_ABORTED_BY_CALLBACK; + } + return CURLM_OK; } /* diff --git a/lib/multihandle.h b/lib/multihandle.h index 2e4a6ffba..db7f130ef 100644 --- a/lib/multihandle.h +++ b/lib/multihandle.h @@ -156,6 +156,8 @@ struct Curl_multi { #ifdef USE_OPENSSL bool ssl_seeded; #endif + bool dead; /* a callback returned error, everything needs to crash and + burn */ }; #endif /* HEADER_CURL_MULTIHANDLE_H */ diff --git a/lib/multiif.h b/lib/multiif.h index 2fbef53c4..f4d0ada8e 100644 --- a/lib/multiif.h +++ b/lib/multiif.h @@ -26,11 +26,11 @@ * Prototypes for library-wide functions provided by multi.c */ -void Curl_updatesocket(struct Curl_easy *data); +CURLcode Curl_updatesocket(struct Curl_easy *data); void Curl_expire(struct Curl_easy *data, timediff_t milli, expire_id); void Curl_expire_clear(struct Curl_easy *data); void Curl_expire_done(struct Curl_easy *data, expire_id id); -void Curl_update_timer(struct Curl_multi *multi); +CURLMcode Curl_update_timer(struct Curl_multi *multi) WARN_UNUSED_RESULT; void Curl_attach_connnection(struct Curl_easy *data, struct connectdata *conn); void Curl_detach_connnection(struct Curl_easy *data); diff --git a/lib/strerror.c b/lib/strerror.c index b8ab0abbc..07d73a74b 100644 --- a/lib/strerror.c +++ b/lib/strerror.c @@ -404,6 +404,9 @@ curl_multi_strerror(CURLMcode error) case CURLM_BAD_FUNCTION_ARGUMENT: return "A libcurl function was given a bad argument"; + case CURLM_ABORTED_BY_CALLBACK: + return "Operation was aborted by an application callback"; + case CURLM_LAST: break; } diff --git a/tests/data/Makefile.inc b/tests/data/Makefile.inc index cbc709d63..2e317173c 100644 --- a/tests/data/Makefile.inc +++ b/tests/data/Makefile.inc @@ -75,7 +75,7 @@ test490 test491 test492 test493 test494 \ test500 test501 test502 test503 test504 test505 test506 test507 test508 \ test509 test510 test511 test512 test513 test514 test515 test516 test517 \ test518 test519 test520 test521 test522 test523 test524 test525 test526 \ -test527 test528 test529 test531 test532 test533 test534 test535 \ +test527 test528 test529 test530 test531 test532 test533 test534 test535 \ test537 test538 test539 test540 test541 test542 test543 test544 \ test545 test546 test547 test548 test549 test550 test551 test552 test553 \ test554 test555 test556 test557 test558 test559 test560 test561 test562 \ diff --git a/tests/data/test1538 b/tests/data/test1538 index 06810d42a..d92ec219f 100644 --- a/tests/data/test1538 +++ b/tests/data/test1538 @@ -144,7 +144,8 @@ m7: The easy handle is already added to a multi handle m8: API function called from within callback m9: Wakeup is unavailable or failed m10: A libcurl function was given a bad argument -m11: Unknown error +m11: Operation was aborted by an application callback +m12: Unknown error s0: No error s1: Unknown share option s2: Share currently in use diff --git a/tests/data/test530 b/tests/data/test530 new file mode 100644 index 000000000..42e27a0d8 --- /dev/null +++ b/tests/data/test530 @@ -0,0 +1,49 @@ +<testcase> +<info> +<keywords> +multi +HTTP +</keywords> +</info> + +# Server-side +<reply> +<data nocheck="yes"> +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]% +</data> +</reply> + +# Client-side +<client> +<server> +http +</server> +<tool> +lib%TESTNUMBER +</tool> +<name> +multi_socket interface transfer with callbacks returning error +</name> +<command> +http://%HOSTIP:%HTTPPORT/file%TESTNUMBER +</command> +</client> + +# Verify data after the test has been "shot" +<verify> +<protocol> +</protocol> +</verify> +</testcase> diff --git a/tests/libtest/Makefile.inc b/tests/libtest/Makefile.inc index 62a7675b1..cbd16f5a4 100644 --- a/tests/libtest/Makefile.inc +++ b/tests/libtest/Makefile.inc @@ -39,7 +39,7 @@ noinst_PROGRAMS = chkhostname libauthretry libntlmconnect \ chkdecimalpoint libprereq \ lib500 lib501 lib502 lib503 lib504 lib505 lib506 lib507 lib508 lib509 \ lib510 lib511 lib512 lib513 lib514 lib515 lib516 lib517 lib518 lib519 \ - lib520 lib521 lib523 lib524 lib525 lib526 lib527 lib529 lib532 \ + lib520 lib521 lib523 lib524 lib525 lib526 lib527 lib529 lib530 lib532 \ lib533 lib537 lib539 lib540 lib541 lib542 lib543 lib544 lib545 \ lib547 lib548 lib549 lib552 lib553 lib554 lib555 lib556 lib557 lib558 \ lib559 lib560 lib562 lib564 lib565 lib566 lib567 lib568 lib569 lib570 \ @@ -179,6 +179,10 @@ lib529_SOURCES = lib525.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS) $(MULTIBYTE) lib529_LDADD = $(TESTUTIL_LIBS) lib529_CPPFLAGS = $(AM_CPPFLAGS) -DLIB529 +lib530_SOURCES = lib530.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS) +lib530_LDADD = $(TESTUTIL_LIBS) +lib530_CPPFLAGS = $(AM_CPPFLAGS) + lib532_SOURCES = lib526.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS) lib532_LDADD = $(TESTUTIL_LIBS) lib532_CPPFLAGS = $(AM_CPPFLAGS) -DLIB532 diff --git a/tests/libtest/lib530.c b/tests/libtest/lib530.c new file mode 100644 index 000000000..517f890c8 --- /dev/null +++ b/tests/libtest/lib530.c @@ -0,0 +1,371 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2021, 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. + * + ***************************************************************************/ + +/* + * The purpose of this test is to make sure that if CURLMOPT_SOCKETFUNCTION or + * CURLMOPT_TIMERFUNCTION returns error, the associated transfer should be + * aborted correctly. + */ + +#include "test.h" + +#include <fcntl.h> + +#include "testutil.h" +#include "warnless.h" +#include "memdebug.h" + +#define TEST_HANG_TIMEOUT 60 * 1000 + +struct Sockets +{ + curl_socket_t *sockets; + int count; /* number of sockets actually stored in array */ + int max_count; /* max number of sockets that fit in allocated array */ +}; + +struct ReadWriteSockets +{ + struct Sockets read, write; +}; + +/** + * Remove a file descriptor from a sockets array. + */ +static void removeFd(struct Sockets *sockets, curl_socket_t fd, int mention) +{ + int i; + + if(mention) + fprintf(stderr, "Remove socket fd %d\n", (int) fd); + + for(i = 0; i < sockets->count; ++i) { + if(sockets->sockets[i] == fd) { + if(i < sockets->count - 1) + memmove(&sockets->sockets[i], &sockets->sockets[i + 1], + sizeof(curl_socket_t) * (sockets->count - (i + 1))); + --sockets->count; + } + } +} + +/** + * Add a file descriptor to a sockets array. + */ +static void addFd(struct Sockets *sockets, curl_socket_t fd, const char *what) +{ + /** + * To ensure we only have each file descriptor once, we remove it then add + * it again. + */ + fprintf(stderr, "Add socket fd %d for %s\n", (int) fd, what); + removeFd(sockets, fd, 0); + /* + * Allocate array storage when required. + */ + if(!sockets->sockets) { + sockets->sockets = malloc(sizeof(curl_socket_t) * 20U); + if(!sockets->sockets) + return; + sockets->max_count = 20; + } + else if(sockets->count + 1 > sockets->max_count) { + curl_socket_t *oldptr = sockets->sockets; + sockets->sockets = realloc(oldptr, sizeof(curl_socket_t) * + (sockets->max_count + 20)); + if(!sockets->sockets) { + /* cleanup in test_cleanup */ + sockets->sockets = oldptr; + return; + } + sockets->max_count += 20; + } + /* + * Add file descriptor to array. + */ + sockets->sockets[sockets->count] = fd; + ++sockets->count; +} + +static int max_socket_calls; +static int socket_calls = 0; + +/** + * Callback invoked by curl to poll reading / writing of a socket. + */ +static int curlSocketCallback(CURL *easy, curl_socket_t s, int action, + void *userp, void *socketp) +{ + struct ReadWriteSockets *sockets = userp; + + (void)easy; /* unused */ + (void)socketp; /* unused */ + + fprintf(stderr, "CURLMOPT_SOCKETFUNCTION called: %u\n", socket_calls++); + if(socket_calls == max_socket_calls) { + fprintf(stderr, "curlSocketCallback returns error\n"); + return -1; + } + + if(action == CURL_POLL_IN || action == CURL_POLL_INOUT) + addFd(&sockets->read, s, "read"); + + if(action == CURL_POLL_OUT || action == CURL_POLL_INOUT) + addFd(&sockets->write, s, "write"); + + if(action == CURL_POLL_REMOVE) { + removeFd(&sockets->read, s, 1); + removeFd(&sockets->write, s, 0); + } + + return 0; +} + +static int max_timer_calls; +static int timer_calls = 0; + +/** + * Callback invoked by curl to set a timeout. + */ +static int curlTimerCallback(CURLM *multi, long timeout_ms, void *userp) +{ + struct timeval *timeout = userp; + + (void)multi; /* unused */ + fprintf(stderr, "CURLMOPT_TIMERFUNCTION called: %u\n", timer_calls++); + if(timer_calls == max_timer_calls) { + fprintf(stderr, "curlTimerCallback returns error\n"); + return -1; + } + if(timeout_ms != -1) { + *timeout = tutil_tvnow(); + timeout->tv_usec += timeout_ms * 1000; + } + else { + timeout->tv_sec = -1; + } + return 0; +} + +/** + * Check for curl completion. + */ +static int checkForCompletion(CURLM *curl, int *success) +{ + int numMessages; + CURLMsg *message; + int result = 0; + *success = 0; + while((message = curl_multi_info_read(curl, &numMessages)) != NULL) { + if(message->msg == CURLMSG_DONE) { + result = 1; + if(message->data.result == CURLE_OK) + *success = 1; + else + *success = 0; + } + else { + fprintf(stderr, "Got an unexpected message from curl: %i\n", + (int)message->msg); + result = 1; + *success = 0; + } + } + return result; +} + +static int getMicroSecondTimeout(struct timeval *timeout) +{ + struct timeval now; + ssize_t result; + now = tutil_tvnow(); + result = (ssize_t)((timeout->tv_sec - now.tv_sec) * 1000000 + + timeout->tv_usec - now.tv_usec); + if(result < 0) + result = 0; + + return curlx_sztosi(result); +} + +/** + * Update a fd_set with all of the sockets in use. + */ +static void updateFdSet(struct Sockets *sockets, fd_set* fdset, + curl_socket_t *maxFd) +{ + int i; + for(i = 0; i < sockets->count; ++i) { + FD_SET(sockets->sockets[i], fdset); + if(*maxFd < sockets->sockets[i] + 1) { + *maxFd = sockets->sockets[i] + 1; + } + } +} + +static void notifyCurl(CURLM *curl, curl_socket_t s, int evBitmask, + const char *info) +{ + int numhandles = 0; + CURLMcode result = curl_multi_socket_action(curl, s, evBitmask, &numhandles); + if(result != CURLM_OK) { + fprintf(stderr, "Curl error on %s: %i (%s)\n", + info, result, curl_multi_strerror(result)); + } +} + +/** + * Invoke curl when a file descriptor is set. + */ +static void checkFdSet(CURLM *curl, struct Sockets *sockets, fd_set *fdset, + int evBitmask, const char *name) +{ + int i; + for(i = 0; i < sockets->count; ++i) { + if(FD_ISSET(sockets->sockets[i], fdset)) { + notifyCurl(curl, sockets->sockets[i], evBitmask, name); + } + } +} + +static int testone(char *URL, int timercb, int socketcb) +{ + int res = 0; + CURL *curl = NULL; CURLM *m = NULL; + struct ReadWriteSockets sockets = {{NULL, 0, 0}, {NULL, 0, 0}}; + struct timeval timeout = {-1, 0}; + int success = 0; + + /* set the limits */ + max_timer_calls = timercb; + max_socket_calls = socketcb; + timer_calls = 0; /* reset the globals */ + socket_calls = 0; + + fprintf(stderr, "start test: %d %d\n", timercb, socketcb); + start_test_timing(); + + res_global_init(CURL_GLOBAL_ALL); + if(res) + return res; + + easy_init(curl); + + /* specify target */ + easy_setopt(curl, CURLOPT_URL, URL); + + /* go verbose */ + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + + multi_init(m); + + multi_setopt(m, CURLMOPT_SOCKETFUNCTION, curlSocketCallback); + multi_setopt(m, CURLMOPT_SOCKETDATA, &sockets); + + multi_setopt(m, CURLMOPT_TIMERFUNCTION, curlTimerCallback); + multi_setopt(m, CURLMOPT_TIMERDATA, &timeout); + + multi_add_handle(m, curl); + + notifyCurl(m, CURL_SOCKET_TIMEOUT, 0, "timeout"); + + while(!checkForCompletion(m, &success)) { + fd_set readSet, writeSet; + curl_socket_t maxFd = 0; + struct timeval tv = {10, 0}; + + FD_ZERO(&readSet); + FD_ZERO(&writeSet); + updateFdSet(&sockets.read, &readSet, &maxFd); + updateFdSet(&sockets.write, &writeSet, &maxFd); + + if(timeout.tv_sec != -1) { + int usTimeout = getMicroSecondTimeout(&timeout); + tv.tv_sec = usTimeout / 1000000; + tv.tv_usec = usTimeout % 1000000; + } + else if(maxFd <= 0) { + tv.tv_sec = 0; + tv.tv_usec = 100000; + } + + select_test((int)maxFd, &readSet, &writeSet, NULL, &tv); + + /* Check the sockets for reading / writing */ + checkFdSet(m, &sockets.read, &readSet, CURL_CSELECT_IN, "read"); + checkFdSet(m, &sockets.write, &writeSet, CURL_CSELECT_OUT, "write"); + + if(timeout.tv_sec != -1 && getMicroSecondTimeout(&timeout) == 0) { + /* Curl's timer has elapsed. */ + notifyCurl(m, CURL_SOCKET_TIMEOUT, 0, "timeout"); + } + + abort_on_test_timeout(); + } + + if(!success) { + fprintf(stderr, "Error getting file.\n"); + res = TEST_ERR_MAJOR_BAD; + } + +test_cleanup: + + /* proper cleanup sequence */ + fprintf(stderr, "cleanup: %d %d\n", timercb, socketcb); + curl_multi_remove_handle(m, curl); + curl_easy_cleanup(curl); + curl_multi_cleanup(m); + curl_global_cleanup(); + + /* free local memory */ + free(sockets.read.sockets); + free(sockets.write.sockets); + + return res; +} + +int test(char *URL) +{ + int rc; + /* rerun the same transfer multiple times and make it fail in different + callback calls */ + rc = testone(URL, 0, 0); + if(rc) + fprintf(stderr, "test 0/0 failed: %d\n", rc); + + rc = testone(URL, 1, 0); + if(!rc) + fprintf(stderr, "test 1/0 failed: %d\n", rc); + + rc = testone(URL, 2, 0); + if(!rc) + fprintf(stderr, "test 2/0 failed: %d\n", rc); + + rc = testone(URL, 0, 1); + if(!rc) + fprintf(stderr, "test 0/1 failed: %d\n", rc); + + rc = testone(URL, 0, 2); + if(!rc) + fprintf(stderr, "test 0/2 failed: %d\n", rc); + + return 0; +} |