From: Daniel Stenberg Date: Thu, 27 Oct 2022 11:40:06 +0000 (+0200) Subject: curl: timeout in the read callback X-Git-Tag: curl-7_87_0~221 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=a55256cfb2425a6d37ac8173a6a5cdf5b3529be0;p=thirdparty%2Fcurl.git curl: timeout in the read callback The read callback can timeout if there's nothing to read within the given maximum period. Example use case is when doing "curl -m 3 telnet://example.com" or anything else that expects input on stdin or similar that otherwise would "hang" until something happens and then not respect the timeout. This fixes KNOWN_BUG 8.1, first filed in July 2009. Bug: https://sourceforge.net/p/curl/bugs/846/ Closes #9815 --- diff --git a/docs/KNOWN_BUGS b/docs/KNOWN_BUGS index 8e87000e09..4fd3fc7b1f 100644 --- a/docs/KNOWN_BUGS +++ b/docs/KNOWN_BUGS @@ -95,7 +95,6 @@ problems may have been fixed or changed somewhat since this was written. 7.12 FTPS directory listing hangs on Windows with Schannel 8. TELNET - 8.1 TELNET and time limitations do not work 8.2 Microsoft telnet server 9. SFTP and SCP @@ -781,11 +780,6 @@ problems may have been fixed or changed somewhat since this was written. 8. TELNET -8.1 TELNET and time limitations do not work - - When using telnet, the time limitation options do not work. - https://curl.se/bug/view.cgi?id=846 - 8.2 Microsoft telnet server There seems to be a problem when connecting to the Microsoft telnet server. diff --git a/src/tool_cb_rea.c b/src/tool_cb_rea.c index 4aed55c3aa..2f1e2f4875 100644 --- a/src/tool_cb_rea.c +++ b/src/tool_cb_rea.c @@ -23,6 +23,10 @@ ***************************************************************************/ #include "tool_setup.h" +#ifdef HAVE_SYS_SELECT_H +#include +#endif + #define ENABLE_CURLX_PRINTF /* use our own printf() functions */ #include "curlx.h" @@ -30,6 +34,7 @@ #include "tool_cfgable.h" #include "tool_cb_rea.h" #include "tool_operate.h" +#include "tool_util.h" #include "memdebug.h" /* keep this as LAST include */ @@ -39,8 +44,36 @@ size_t tool_read_cb(char *buffer, size_t sz, size_t nmemb, void *userdata) { - ssize_t rc; + ssize_t rc = 0; struct InStruct *in = userdata; + struct OperationConfig *config = in->config; + + if(config->timeout_ms) { + struct timeval now = tvnow(); + long msdelta = tvdiff(now, in->per->start); + + if(msdelta > config->timeout_ms) + /* timeout */ + return 0; +#ifndef WIN32 + /* this logic waits on read activity on a file descriptor that is not a + socket which makes it not work with select() on Windows */ + else { + fd_set bits; + struct timeval timeout; + long wait = config->timeout_ms - msdelta; + + /* wait this long at the most */ + timeout.tv_sec = wait/1000; + timeout.tv_usec = (wait%1000)*1000; + + FD_ZERO(&bits); + FD_SET(in->fd, &bits); + if(!select(in->fd + 1, &bits, NULL, NULL, &timeout)) + return 0; /* timeout */ + } +#endif + } rc = read(in->fd, buffer, sz*nmemb); if(rc < 0) { @@ -53,6 +86,8 @@ size_t tool_read_cb(char *buffer, size_t sz, size_t nmemb, void *userdata) rc = 0; } in->config->readbusy = FALSE; + + /* when select() rerturned zero here, it timed out */ return (size_t)rc; } diff --git a/src/tool_cfgable.h b/src/tool_cfgable.h index c26cddd5e0..17db872894 100644 --- a/src/tool_cfgable.h +++ b/src/tool_cfgable.h @@ -70,8 +70,8 @@ struct OperationConfig { char *postfields; curl_off_t postfieldsize; char *referer; - double timeout; - double connecttimeout; + long timeout_ms; + long connecttimeout_ms; long maxredirs; curl_off_t max_filesize; char *output_dir; @@ -272,7 +272,7 @@ struct OperationConfig { bool abstract_unix_socket; /* path to an abstract Unix domain socket */ bool falsestart; bool path_as_is; - double expect100timeout; + long expect100timeout_ms; bool suppress_connect_headers; /* suppress proxy CONNECT response headers from user callbacks */ bool synthetic_error; /* if TRUE, this is tool-internal error */ diff --git a/src/tool_getparam.c b/src/tool_getparam.c index af4b3a6984..605b274a93 100644 --- a/src/tool_getparam.c +++ b/src/tool_getparam.c @@ -807,8 +807,7 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */ config->authtype |= CURLAUTH_BEARER; break; case 'c': /* connect-timeout */ - err = str2udouble(&config->connecttimeout, nextarg, - (double)LONG_MAX/1000); + err = secs2ms(&config->connecttimeout_ms, nextarg); if(err) return err; break; @@ -1374,8 +1373,7 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */ return err; break; case 'R': /* --expect100-timeout */ - err = str2udouble(&config->expect100timeout, nextarg, - (double)LONG_MAX/1000); + err = secs2ms(&config->expect100timeout_ms, nextarg); if(err) return err; break; @@ -2068,7 +2066,7 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */ break; case 'm': /* specified max time */ - err = str2udouble(&config->timeout, nextarg, (double)LONG_MAX/1000); + err = secs2ms(&config->timeout_ms, nextarg); if(err) return err; break; diff --git a/src/tool_operate.c b/src/tool_operate.c index b7fde7ec8c..0accb554a8 100644 --- a/src/tool_operate.c +++ b/src/tool_operate.c @@ -328,6 +328,7 @@ static CURLcode pre_transfer(struct GlobalConfig *global, } per->input.fd = per->infd; } + per->start = tvnow(); return result; } @@ -1243,6 +1244,7 @@ static CURLcode single_transfer(struct GlobalConfig *global, /* for uploads */ input->config = config; + input->per = per; /* Note that if CURLOPT_READFUNCTION is fread (the default), then * lib/telnet.c will Curl_poll() on the input file descriptor * rather than calling the READFUNCTION at regular intervals. @@ -1344,7 +1346,7 @@ static CURLcode single_transfer(struct GlobalConfig *global, per->errorbuffer = global_errorbuffer; my_setopt(curl, CURLOPT_ERRORBUFFER, global_errorbuffer); } - my_setopt(curl, CURLOPT_TIMEOUT_MS, (long)(config->timeout * 1000)); + my_setopt(curl, CURLOPT_TIMEOUT_MS, config->timeout_ms); switch(config->httpreq) { case HTTPREQ_SIMPLEPOST: @@ -1832,8 +1834,7 @@ static CURLcode single_transfer(struct GlobalConfig *global, my_setopt_slist(curl, CURLOPT_TELNETOPTIONS, config->telnet_options); /* new in libcurl 7.7: */ - my_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, - (long)(config->connecttimeout * 1000)); + my_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, config->connecttimeout_ms); if(config->doh_url) my_setopt_str(curl, CURLOPT_DOH_URL, config->doh_url); @@ -2079,9 +2080,9 @@ static CURLcode single_transfer(struct GlobalConfig *global, my_setopt_str(curl, CURLOPT_DEFAULT_PROTOCOL, config->proto_default); /* new in 7.47.0 */ - if(config->expect100timeout > 0) + if(config->expect100timeout_ms > 0) my_setopt_str(curl, CURLOPT_EXPECT_100_TIMEOUT_MS, - (long)(config->expect100timeout*1000)); + config->expect100timeout_ms); /* new in 7.48.0 */ if(config->tftp_no_options && proto_tftp) @@ -2386,7 +2387,6 @@ static CURLcode serial_transfers(struct GlobalConfig *global, bool retry; long delay_ms; bool bailout = FALSE; - struct timeval start; result = pre_transfer(global, per); if(result) break; @@ -2397,7 +2397,6 @@ static CURLcode serial_transfers(struct GlobalConfig *global, break; } - start = tvnow(); #ifdef CURLDEBUG if(global->test_event_based) result = curl_easy_perform_ev(per->curl); @@ -2429,7 +2428,7 @@ static CURLcode serial_transfers(struct GlobalConfig *global, if(per && global->ms_per_transfer) { /* how long time did the most recent transfer take in number of milliseconds */ - long milli = tvdiff(tvnow(), start); + long milli = tvdiff(tvnow(), per->start); if(milli < global->ms_per_transfer) { notef(global, "Transfer took %ld ms, waits %ldms as set by --rate\n", milli, global->ms_per_transfer - milli); diff --git a/src/tool_operate.h b/src/tool_operate.h index c714da1bc2..5c08b99aec 100644 --- a/src/tool_operate.h +++ b/src/tool_operate.h @@ -37,6 +37,7 @@ struct per_transfer { long retry_numretries; long retry_sleep_default; long retry_sleep; + struct timeval start; /* start of this transfer */ struct timeval retrystart; char *this_url; unsigned int urlnum; /* the index of the given URL */ diff --git a/src/tool_paramhlp.c b/src/tool_paramhlp.c index 05afb8d3a4..acf34ac24d 100644 --- a/src/tool_paramhlp.c +++ b/src/tool_paramhlp.c @@ -240,8 +240,9 @@ static ParameterError str2double(double *val, const char *str, double max) } /* - * Parse the string and write the double in the given address. Return PARAM_OK - * on success, otherwise a parameter error enum. ONLY ACCEPTS POSITIVE NUMBERS! + * Parse the string as seconds with decimals, and write the number of + * milliseconds that corresponds in the given address. Return PARAM_OK on + * success, otherwise a parameter error enum. ONLY ACCEPTS POSITIVE NUMBERS! * * The 'max' argument is the maximum value allowed, as the numbers are often * multiplied when later used. @@ -251,16 +252,16 @@ static ParameterError str2double(double *val, const char *str, double max) * data. */ -ParameterError str2udouble(double *valp, const char *str, double max) +ParameterError secs2ms(long *valp, const char *str) { double value; - ParameterError result = str2double(&value, str, max); + ParameterError result = str2double(&value, str, (double)LONG_MAX/1000); if(result != PARAM_OK) return result; if(value < 0) return PARAM_NEGATIVE_NUMERIC; - *valp = value; + *valp = (long)(value*1000); return PARAM_OK; } diff --git a/src/tool_paramhlp.h b/src/tool_paramhlp.h index 7d68583e67..98cc8d09d7 100644 --- a/src/tool_paramhlp.h +++ b/src/tool_paramhlp.h @@ -36,7 +36,7 @@ ParameterError str2num(long *val, const char *str); ParameterError str2unum(long *val, const char *str); ParameterError oct2nummax(long *val, const char *str, long max); ParameterError str2unummax(long *val, const char *str, long max); -ParameterError str2udouble(double *val, const char *str, double max); +ParameterError secs2ms(long *val, const char *str); ParameterError proto2num(struct OperationConfig *config, const char * const *val, char **obuf, diff --git a/src/tool_sdecls.h b/src/tool_sdecls.h index 70e44d4ed2..c468b640e2 100644 --- a/src/tool_sdecls.h +++ b/src/tool_sdecls.h @@ -84,6 +84,7 @@ struct OutStruct { struct InStruct { int fd; struct OperationConfig *config; + struct per_transfer *per; };