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:
Родитель
b830f9ba9e
Коммит
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
|
||||
|
||||
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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -84,6 +84,7 @@ struct OutStruct {
|
|||
struct InStruct {
|
||||
int fd;
|
||||
struct OperationConfig *config;
|
||||
struct per_transfer *per;
|
||||
};
|
||||
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче