summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/libcurl/opts/CURLMOPT_SOCKETFUNCTION.36
-rw-r--r--docs/libcurl/opts/CURLMOPT_TIMERFUNCTION.38
-rw-r--r--docs/libcurl/symbols-in-versions1
-rw-r--r--include/curl/multi.h5
-rw-r--r--lib/easy.c8
-rw-r--r--lib/multi.c173
-rw-r--r--lib/multihandle.h2
-rw-r--r--lib/multiif.h4
-rw-r--r--lib/strerror.c3
-rw-r--r--tests/data/Makefile.inc2
-rw-r--r--tests/data/test15383
-rw-r--r--tests/data/test53049
-rw-r--r--tests/libtest/Makefile.inc6
-rw-r--r--tests/libtest/lib530.c371
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;
+}