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:
parent
b830f9ba9e
commit
a55256cfb2
@ -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.
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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 */
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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 */
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -84,6 +84,7 @@ struct OutStruct {
|
|||||||
struct InStruct {
|
struct InStruct {
|
||||||
int fd;
|
int fd;
|
||||||
struct OperationConfig *config;
|
struct OperationConfig *config;
|
||||||
|
struct per_transfer *per;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user