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

@ -23,6 +23,10 @@
***************************************************************************/
#include "tool_setup.h"
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#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;
}

View file

@ -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 */

View file

@ -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;

View file

@ -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);

View file

@ -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 */

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
* 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;
}

View file

@ -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,

View file

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