Browse Source

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
Daniel Stenberg 1 year ago
parent
commit
a55256cfb2
9 changed files with 58 additions and 29 deletions
  1. 0 6
      docs/KNOWN_BUGS
  2. 36 1
      src/tool_cb_rea.c
  3. 3 3
      src/tool_cfgable.h
  4. 3 5
      src/tool_getparam.c
  5. 7 8
      src/tool_operate.c
  6. 1 0
      src/tool_operate.h
  7. 6 5
      src/tool_paramhlp.c
  8. 1 1
      src/tool_paramhlp.h
  9. 1 0
      src/tool_sdecls.h

+ 0 - 6
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
  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.

+ 36 - 1
src/tool_cb_rea.c

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

+ 3 - 3
src/tool_cfgable.h

@@ -70,8 +70,8 @@ struct OperationConfig {
   char *postfields;
   char *postfields;
   curl_off_t postfieldsize;
   curl_off_t postfieldsize;
   char *referer;
   char *referer;
-  double timeout;
-  double connecttimeout;
+  long timeout_ms;
+  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 */

+ 3 - 5
src/tool_getparam.c

@@ -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,
-                          (double)LONG_MAX/1000);
+        err = secs2ms(&config->connecttimeout_ms, nextarg);
         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,
-                          (double)LONG_MAX/1000);
+        err = secs2ms(&config->expect100timeout_ms, nextarg);
         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;

+ 7 - 8
src/tool_operate.c

@@ -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,
-                  (long)(config->connecttimeout * 1000));
+        my_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, config->connecttimeout_ms);
 
 
         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);

+ 1 - 0
src/tool_operate.h

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

+ 6 - 5
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
  * 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;
 }
 }
 
 

+ 1 - 1
src/tool_paramhlp.h

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

+ 1 - 0
src/tool_sdecls.h

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