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
This commit is contained in:
Daniel Stenberg 2022-10-27 13:40:06 +02:00
parent b830f9ba9e
commit a55256cfb2
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
9 changed files with 58 additions and 29 deletions

View File

@ -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 7.12 FTPS directory listing hangs on Windows with Schannel
8. TELNET 8. TELNET
8.1 TELNET and time limitations do not work
8.2 Microsoft telnet server 8.2 Microsoft telnet server
9. SFTP and SCP 9. SFTP and SCP
@ -781,11 +780,6 @@ problems may have been fixed or changed somewhat since this was written.
8. TELNET 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 8.2 Microsoft telnet server
There seems to be a problem when connecting to the Microsoft telnet server. There seems to be a problem when connecting to the Microsoft telnet server.

View File

@ -23,6 +23,10 @@
***************************************************************************/ ***************************************************************************/
#include "tool_setup.h" #include "tool_setup.h"
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif
#define ENABLE_CURLX_PRINTF #define ENABLE_CURLX_PRINTF
/* use our own printf() functions */ /* use our own printf() functions */
#include "curlx.h" #include "curlx.h"
@ -30,6 +34,7 @@
#include "tool_cfgable.h" #include "tool_cfgable.h"
#include "tool_cb_rea.h" #include "tool_cb_rea.h"
#include "tool_operate.h" #include "tool_operate.h"
#include "tool_util.h"
#include "memdebug.h" /* keep this as LAST include */ #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) 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 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); rc = read(in->fd, buffer, sz*nmemb);
if(rc < 0) { if(rc < 0) {
@ -53,6 +86,8 @@ size_t tool_read_cb(char *buffer, size_t sz, size_t nmemb, void *userdata)
rc = 0; rc = 0;
} }
in->config->readbusy = FALSE; in->config->readbusy = FALSE;
/* when select() rerturned zero here, it timed out */
return (size_t)rc; return (size_t)rc;
} }

View File

@ -70,8 +70,8 @@ struct OperationConfig {
char *postfields; char *postfields;
curl_off_t postfieldsize; curl_off_t postfieldsize;
char *referer; char *referer;
double timeout; long timeout_ms;
double connecttimeout; long connecttimeout_ms;
long maxredirs; long maxredirs;
curl_off_t max_filesize; curl_off_t max_filesize;
char *output_dir; char *output_dir;
@ -272,7 +272,7 @@ struct OperationConfig {
bool abstract_unix_socket; /* path to an abstract Unix domain socket */ bool abstract_unix_socket; /* path to an abstract Unix domain socket */
bool falsestart; bool falsestart;
bool path_as_is; bool path_as_is;
double expect100timeout; long expect100timeout_ms;
bool suppress_connect_headers; /* suppress proxy CONNECT response headers bool suppress_connect_headers; /* suppress proxy CONNECT response headers
from user callbacks */ from user callbacks */
bool synthetic_error; /* if TRUE, this is tool-internal error */ bool synthetic_error; /* if TRUE, this is tool-internal error */

View File

@ -807,8 +807,7 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */
config->authtype |= CURLAUTH_BEARER; config->authtype |= CURLAUTH_BEARER;
break; break;
case 'c': /* connect-timeout */ case 'c': /* connect-timeout */
err = str2udouble(&config->connecttimeout, nextarg, err = secs2ms(&config->connecttimeout_ms, nextarg);
(double)LONG_MAX/1000);
if(err) if(err)
return err; return err;
break; break;
@ -1374,8 +1373,7 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */
return err; return err;
break; break;
case 'R': /* --expect100-timeout */ case 'R': /* --expect100-timeout */
err = str2udouble(&config->expect100timeout, nextarg, err = secs2ms(&config->expect100timeout_ms, nextarg);
(double)LONG_MAX/1000);
if(err) if(err)
return err; return err;
break; break;
@ -2068,7 +2066,7 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */
break; break;
case 'm': case 'm':
/* specified max time */ /* specified max time */
err = str2udouble(&config->timeout, nextarg, (double)LONG_MAX/1000); err = secs2ms(&config->timeout_ms, nextarg);
if(err) if(err)
return err; return err;
break; break;

View File

@ -328,6 +328,7 @@ static CURLcode pre_transfer(struct GlobalConfig *global,
} }
per->input.fd = per->infd; per->input.fd = per->infd;
} }
per->start = tvnow();
return result; return result;
} }
@ -1243,6 +1244,7 @@ static CURLcode single_transfer(struct GlobalConfig *global,
/* for uploads */ /* for uploads */
input->config = config; input->config = config;
input->per = per;
/* Note that if CURLOPT_READFUNCTION is fread (the default), then /* Note that if CURLOPT_READFUNCTION is fread (the default), then
* lib/telnet.c will Curl_poll() on the input file descriptor * lib/telnet.c will Curl_poll() on the input file descriptor
* rather than calling the READFUNCTION at regular intervals. * rather than calling the READFUNCTION at regular intervals.
@ -1344,7 +1346,7 @@ static CURLcode single_transfer(struct GlobalConfig *global,
per->errorbuffer = global_errorbuffer; per->errorbuffer = global_errorbuffer;
my_setopt(curl, CURLOPT_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) { switch(config->httpreq) {
case HTTPREQ_SIMPLEPOST: case HTTPREQ_SIMPLEPOST:
@ -1832,8 +1834,7 @@ static CURLcode single_transfer(struct GlobalConfig *global,
my_setopt_slist(curl, CURLOPT_TELNETOPTIONS, config->telnet_options); my_setopt_slist(curl, CURLOPT_TELNETOPTIONS, config->telnet_options);
/* new in libcurl 7.7: */ /* new in libcurl 7.7: */
my_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, my_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, config->connecttimeout_ms);
(long)(config->connecttimeout * 1000));
if(config->doh_url) if(config->doh_url)
my_setopt_str(curl, CURLOPT_DOH_URL, 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); my_setopt_str(curl, CURLOPT_DEFAULT_PROTOCOL, config->proto_default);
/* new in 7.47.0 */ /* new in 7.47.0 */
if(config->expect100timeout > 0) if(config->expect100timeout_ms > 0)
my_setopt_str(curl, CURLOPT_EXPECT_100_TIMEOUT_MS, my_setopt_str(curl, CURLOPT_EXPECT_100_TIMEOUT_MS,
(long)(config->expect100timeout*1000)); config->expect100timeout_ms);
/* new in 7.48.0 */ /* new in 7.48.0 */
if(config->tftp_no_options && proto_tftp) if(config->tftp_no_options && proto_tftp)
@ -2386,7 +2387,6 @@ static CURLcode serial_transfers(struct GlobalConfig *global,
bool retry; bool retry;
long delay_ms; long delay_ms;
bool bailout = FALSE; bool bailout = FALSE;
struct timeval start;
result = pre_transfer(global, per); result = pre_transfer(global, per);
if(result) if(result)
break; break;
@ -2397,7 +2397,6 @@ static CURLcode serial_transfers(struct GlobalConfig *global,
break; break;
} }
start = tvnow();
#ifdef CURLDEBUG #ifdef CURLDEBUG
if(global->test_event_based) if(global->test_event_based)
result = curl_easy_perform_ev(per->curl); result = curl_easy_perform_ev(per->curl);
@ -2429,7 +2428,7 @@ static CURLcode serial_transfers(struct GlobalConfig *global,
if(per && global->ms_per_transfer) { if(per && global->ms_per_transfer) {
/* how long time did the most recent transfer take in number of /* how long time did the most recent transfer take in number of
milliseconds */ milliseconds */
long milli = tvdiff(tvnow(), start); long milli = tvdiff(tvnow(), per->start);
if(milli < global->ms_per_transfer) { if(milli < global->ms_per_transfer) {
notef(global, "Transfer took %ld ms, waits %ldms as set by --rate\n", notef(global, "Transfer took %ld ms, waits %ldms as set by --rate\n",
milli, global->ms_per_transfer - milli); milli, global->ms_per_transfer - milli);

View File

@ -37,6 +37,7 @@ struct per_transfer {
long retry_numretries; long retry_numretries;
long retry_sleep_default; long retry_sleep_default;
long retry_sleep; long retry_sleep;
struct timeval start; /* start of this transfer */
struct timeval retrystart; struct timeval retrystart;
char *this_url; char *this_url;
unsigned int urlnum; /* the index of the given URL */ unsigned int urlnum; /* the index of the given URL */

View File

@ -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 * Parse the string as seconds with decimals, and write the number of
* on success, otherwise a parameter error enum. ONLY ACCEPTS POSITIVE NUMBERS! * 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 * The 'max' argument is the maximum value allowed, as the numbers are often
* multiplied when later used. * multiplied when later used.
@ -251,16 +252,16 @@ static ParameterError str2double(double *val, const char *str, double max)
* data. * data.
*/ */
ParameterError str2udouble(double *valp, const char *str, double max) ParameterError secs2ms(long *valp, const char *str)
{ {
double value; double value;
ParameterError result = str2double(&value, str, max); ParameterError result = str2double(&value, str, (double)LONG_MAX/1000);
if(result != PARAM_OK) if(result != PARAM_OK)
return result; return result;
if(value < 0) if(value < 0)
return PARAM_NEGATIVE_NUMERIC; return PARAM_NEGATIVE_NUMERIC;
*valp = value; *valp = (long)(value*1000);
return PARAM_OK; return PARAM_OK;
} }

View File

@ -36,7 +36,7 @@ ParameterError str2num(long *val, const char *str);
ParameterError str2unum(long *val, const char *str); ParameterError str2unum(long *val, const char *str);
ParameterError oct2nummax(long *val, const char *str, long max); ParameterError oct2nummax(long *val, const char *str, long max);
ParameterError str2unummax(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, ParameterError proto2num(struct OperationConfig *config,
const char * const *val, char **obuf, const char * const *val, char **obuf,

View File

@ -84,6 +84,7 @@ struct OutStruct {
struct InStruct { struct InStruct {
int fd; int fd;
struct OperationConfig *config; struct OperationConfig *config;
struct per_transfer *per;
}; };