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
 
  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.

+ 36 - 1
src/tool_cb_rea.c

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

+ 3 - 3
src/tool_cfgable.h

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

+ 3 - 5
src/tool_getparam.c

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

+ 7 - 8
src/tool_operate.c

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

+ 1 - 0
src/tool_operate.h

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

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

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

+ 1 - 0
src/tool_sdecls.h

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