]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
tool_operate: make retrycheck() a separate function
authorDaniel Stenberg <daniel@haxx.se>
Mon, 19 May 2025 07:58:42 +0000 (09:58 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Mon, 19 May 2025 11:27:54 +0000 (13:27 +0200)
Simplifies post_per_transfer()

Closes #17381

src/tool_operate.c

index 91b3e97ac9ad30b5ac60870ba9ff4f7871218557..195a0f334b97b5eb0906207fab338e6d2044cda5 100644 (file)
@@ -361,6 +361,198 @@ void single_transfer_cleanup(struct OperationConfig *config)
   }
 }
 
+static CURLcode retrycheck(struct OperationConfig *config,
+                           struct per_transfer *per,
+                           CURLcode result,
+                           bool *retryp,
+                           long *delayms)
+{
+  CURL *curl = per->curl;
+  struct OutStruct *outs = &per->outs;
+  enum {
+    RETRY_NO,
+    RETRY_ALL_ERRORS,
+    RETRY_TIMEOUT,
+    RETRY_CONNREFUSED,
+    RETRY_HTTP,
+    RETRY_FTP,
+    RETRY_LAST /* not used */
+  } retry = RETRY_NO;
+  long response = 0;
+  if((CURLE_OPERATION_TIMEDOUT == result) ||
+     (CURLE_COULDNT_RESOLVE_HOST == result) ||
+     (CURLE_COULDNT_RESOLVE_PROXY == result) ||
+     (CURLE_FTP_ACCEPT_TIMEOUT == result))
+    /* retry timeout always */
+    retry = RETRY_TIMEOUT;
+  else if(config->retry_connrefused &&
+          (CURLE_COULDNT_CONNECT == result)) {
+    long oserrno = 0;
+    curl_easy_getinfo(curl, CURLINFO_OS_ERRNO, &oserrno);
+    if(SOCKECONNREFUSED == oserrno)
+      retry = RETRY_CONNREFUSED;
+  }
+  else if((CURLE_OK == result) ||
+          ((config->failonerror || config->failwithbody) &&
+           (CURLE_HTTP_RETURNED_ERROR == result))) {
+    /* If it returned OK. _or_ failonerror was enabled and it
+       returned due to such an error, check for HTTP transient
+       errors to retry on. */
+    const char *scheme;
+    curl_easy_getinfo(curl, CURLINFO_SCHEME, &scheme);
+    scheme = proto_token(scheme);
+    if(scheme == proto_http || scheme == proto_https) {
+      /* This was HTTP(S) */
+      curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response);
+
+      switch(response) {
+      case 408: /* Request Timeout */
+      case 429: /* Too Many Requests (RFC6585) */
+      case 500: /* Internal Server Error */
+      case 502: /* Bad Gateway */
+      case 503: /* Service Unavailable */
+      case 504: /* Gateway Timeout */
+        retry = RETRY_HTTP;
+        /*
+         * At this point, we have already written data to the output
+         * file (or terminal). If we write to a file, we must rewind
+         * or close/re-open the file so that the next attempt starts
+         * over from the beginning.
+         *
+         * For the upload case, we might need to start over reading from a
+         * previous point if we have uploaded something when this was
+         * returned.
+         */
+        break;
+      }
+    }
+  } /* if CURLE_OK */
+  else if(result) {
+    const char *scheme;
+
+    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response);
+    curl_easy_getinfo(curl, CURLINFO_SCHEME, &scheme);
+    scheme = proto_token(scheme);
+
+    if((scheme == proto_ftp || scheme == proto_ftps) && response / 100 == 4)
+      /*
+       * This is typically when the FTP server only allows a certain
+       * amount of users and we are not one of them. All 4xx codes
+       * are transient.
+       */
+      retry = RETRY_FTP;
+  }
+
+  if(result && !retry && config->retry_all_errors)
+    retry = RETRY_ALL_ERRORS;
+
+  if(retry) {
+    long sleeptime = 0;
+    curl_off_t retry_after = 0;
+    static const char * const m[]={
+      NULL,
+      "(retrying all errors)",
+      ": timeout",
+      ": connection refused",
+      ": HTTP error",
+      ": FTP error"
+    };
+
+    sleeptime = per->retry_sleep;
+    if(RETRY_HTTP == retry) {
+      curl_easy_getinfo(curl, CURLINFO_RETRY_AFTER, &retry_after);
+      if(retry_after) {
+        /* store in a 'long', make sure it does not overflow */
+        if(retry_after > LONG_MAX/1000)
+          sleeptime = LONG_MAX;
+        else if((retry_after * 1000) > sleeptime)
+          sleeptime = (long)retry_after * 1000; /* milliseconds */
+
+        /* if adding retry_after seconds to the process would exceed the
+           maximum time allowed for retrying, then exit the retries right
+           away */
+        if(config->retry_maxtime) {
+          curl_off_t seconds = curlx_timediff(curlx_now(),
+                                              per->retrystart)/1000;
+
+          if((CURL_OFF_T_MAX - retry_after < seconds) ||
+             (seconds + retry_after > config->retry_maxtime)) {
+            warnf(config->global, "The Retry-After: time would "
+                  "make this command line exceed the maximum allowed time "
+                  "for retries.");
+            *retryp = FALSE;
+            return CURLE_OK; /* no retry */
+          }
+        }
+      }
+    }
+    warnf(config->global, "Problem %s. "
+          "Will retry in %ld second%s. "
+          "%ld retr%s left.",
+          m[retry], sleeptime/1000L,
+          (sleeptime/1000L == 1 ? "" : "s"),
+          per->retry_remaining,
+          (per->retry_remaining > 1 ? "ies" : "y"));
+
+    per->retry_remaining--;
+    if(!config->retry_delay) {
+      per->retry_sleep *= 2;
+      if(per->retry_sleep > RETRY_SLEEP_MAX)
+        per->retry_sleep = RETRY_SLEEP_MAX;
+    }
+
+    if(outs->bytes && outs->filename && outs->stream) {
+#ifndef __MINGW32CE__
+      struct_stat fileinfo;
+
+      /* The output can be a named pipe or a character device etc that
+         cannot be truncated. Only truncate regular files. */
+      if(!fstat(fileno(outs->stream), &fileinfo) &&
+         S_ISREG(fileinfo.st_mode))
+#else
+        /* Windows CE's fileno() is bad so just skip the check */
+#endif
+      {
+        int rc;
+        /* We have written data to an output file, we truncate file */
+        fflush(outs->stream);
+        notef(config->global,
+              "Throwing away %"  CURL_FORMAT_CURL_OFF_T " bytes",
+              outs->bytes);
+        /* truncate file at the position where we started appending */
+#if defined(HAVE_FTRUNCATE) && !defined(__DJGPP__) && !defined(__AMIGA__) && \
+  !defined(__MINGW32CE__)
+        if(ftruncate(fileno(outs->stream), outs->init)) {
+          /* when truncate fails, we cannot just append as then we will
+             create something strange, bail out */
+          errorf(config->global, "Failed to truncate file");
+          return CURLE_WRITE_ERROR;
+        }
+        /* now seek to the end of the file, the position where we
+           just truncated the file in a large file-safe way */
+        rc = fseek(outs->stream, 0, SEEK_END);
+#else
+        /* ftruncate is not available, so just reposition the file
+           to the location we would have truncated it. This will not
+           work properly with large files on 32-bit systems, but
+           most of those will have ftruncate. */
+        rc = fseek(outs->stream, (long)outs->init, SEEK_SET);
+#endif
+        if(rc) {
+          errorf(config->global, "Failed seeking to end of file");
+          return CURLE_WRITE_ERROR;
+        }
+        outs->bytes = 0; /* clear for next round */
+      }
+    }
+    *retryp = TRUE;
+    per->num_retries++;
+    *delayms = sleeptime;
+  }
+  return CURLE_OK;
+}
+
+
 /*
  * Call this after a transfer has completed.
  */
@@ -457,187 +649,10 @@ static CURLcode post_per_transfer(struct GlobalConfig *global,
      (!config->retry_maxtime ||
       (curlx_timediff(curlx_now(), per->retrystart) <
        config->retry_maxtime*1000L)) ) {
-    enum {
-      RETRY_NO,
-      RETRY_ALL_ERRORS,
-      RETRY_TIMEOUT,
-      RETRY_CONNREFUSED,
-      RETRY_HTTP,
-      RETRY_FTP,
-      RETRY_LAST /* not used */
-    } retry = RETRY_NO;
-    long response = 0;
-    if((CURLE_OPERATION_TIMEDOUT == result) ||
-       (CURLE_COULDNT_RESOLVE_HOST == result) ||
-       (CURLE_COULDNT_RESOLVE_PROXY == result) ||
-       (CURLE_FTP_ACCEPT_TIMEOUT == result))
-      /* retry timeout always */
-      retry = RETRY_TIMEOUT;
-    else if(config->retry_connrefused &&
-            (CURLE_COULDNT_CONNECT == result)) {
-      long oserrno = 0;
-      curl_easy_getinfo(curl, CURLINFO_OS_ERRNO, &oserrno);
-      if(SOCKECONNREFUSED == oserrno)
-        retry = RETRY_CONNREFUSED;
-    }
-    else if((CURLE_OK == result) ||
-            ((config->failonerror || config->failwithbody) &&
-             (CURLE_HTTP_RETURNED_ERROR == result))) {
-      /* If it returned OK. _or_ failonerror was enabled and it
-         returned due to such an error, check for HTTP transient
-         errors to retry on. */
-      const char *scheme;
-      curl_easy_getinfo(curl, CURLINFO_SCHEME, &scheme);
-      scheme = proto_token(scheme);
-      if(scheme == proto_http || scheme == proto_https) {
-        /* This was HTTP(S) */
-        curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response);
-
-        switch(response) {
-        case 408: /* Request Timeout */
-        case 429: /* Too Many Requests (RFC6585) */
-        case 500: /* Internal Server Error */
-        case 502: /* Bad Gateway */
-        case 503: /* Service Unavailable */
-        case 504: /* Gateway Timeout */
-          retry = RETRY_HTTP;
-          /*
-           * At this point, we have already written data to the output
-           * file (or terminal). If we write to a file, we must rewind
-           * or close/re-open the file so that the next attempt starts
-           * over from the beginning.
-           *
-           * For the upload case, we might need to start over reading from a
-           * previous point if we have uploaded something when this was
-           * returned.
-           */
-          break;
-        }
-      }
-    } /* if CURLE_OK */
-    else if(result) {
-      const char *scheme;
-
-      curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response);
-      curl_easy_getinfo(curl, CURLINFO_SCHEME, &scheme);
-      scheme = proto_token(scheme);
-
-      if((scheme == proto_ftp || scheme == proto_ftps) && response / 100 == 4)
-        /*
-         * This is typically when the FTP server only allows a certain
-         * amount of users and we are not one of them. All 4xx codes
-         * are transient.
-         */
-        retry = RETRY_FTP;
-    }
-
-    if(result && !retry && config->retry_all_errors)
-      retry = RETRY_ALL_ERRORS;
-
-    if(retry) {
-      long sleeptime = 0;
-      curl_off_t retry_after = 0;
-      static const char * const m[]={
-        NULL,
-        "(retrying all errors)",
-        ": timeout",
-        ": connection refused",
-        ": HTTP error",
-        ": FTP error"
-      };
-
-      sleeptime = per->retry_sleep;
-      if(RETRY_HTTP == retry) {
-        curl_easy_getinfo(curl, CURLINFO_RETRY_AFTER, &retry_after);
-        if(retry_after) {
-          /* store in a 'long', make sure it does not overflow */
-          if(retry_after > LONG_MAX/1000)
-            sleeptime = LONG_MAX;
-          else if((retry_after * 1000) > sleeptime)
-            sleeptime = (long)retry_after * 1000; /* milliseconds */
-
-          /* if adding retry_after seconds to the process would exceed the
-             maximum time allowed for retrying, then exit the retries right
-             away */
-          if(config->retry_maxtime) {
-            curl_off_t seconds = curlx_timediff(curlx_now(),
-                                                per->retrystart)/1000;
-
-            if((CURL_OFF_T_MAX - retry_after < seconds) ||
-               (seconds + retry_after > config->retry_maxtime)) {
-              warnf(config->global, "The Retry-After: time would "
-                    "make this command line exceed the maximum allowed time "
-                    "for retries.");
-              goto noretry;
-            }
-          }
-        }
-      }
-      warnf(config->global, "Problem %s. "
-            "Will retry in %ld second%s. "
-            "%ld retr%s left.",
-            m[retry], sleeptime/1000L,
-            (sleeptime/1000L == 1 ? "" : "s"),
-            per->retry_remaining,
-            (per->retry_remaining > 1 ? "ies" : "y"));
-
-      per->retry_remaining--;
-      if(!config->retry_delay) {
-        per->retry_sleep *= 2;
-        if(per->retry_sleep > RETRY_SLEEP_MAX)
-          per->retry_sleep = RETRY_SLEEP_MAX;
-      }
-
-      if(outs->bytes && outs->filename && outs->stream) {
-#ifndef __MINGW32CE__
-        struct_stat fileinfo;
-
-        /* The output can be a named pipe or a character device etc that
-           cannot be truncated. Only truncate regular files. */
-        if(!fstat(fileno(outs->stream), &fileinfo) &&
-           S_ISREG(fileinfo.st_mode))
-#else
-          /* Windows CE's fileno() is bad so just skip the check */
-#endif
-        {
-          /* We have written data to an output file, we truncate file */
-          fflush(outs->stream);
-          notef(config->global,
-                "Throwing away %"  CURL_FORMAT_CURL_OFF_T " bytes",
-                outs->bytes);
-          /* truncate file at the position where we started appending */
-#if defined(HAVE_FTRUNCATE) && !defined(__DJGPP__) && !defined(__AMIGA__) && \
-  !defined(__MINGW32CE__)
-          if(ftruncate(fileno(outs->stream), outs->init)) {
-            /* when truncate fails, we cannot just append as then we will
-               create something strange, bail out */
-            errorf(config->global, "Failed to truncate file");
-            return CURLE_WRITE_ERROR;
-          }
-          /* now seek to the end of the file, the position where we
-             just truncated the file in a large file-safe way */
-          rc = fseek(outs->stream, 0, SEEK_END);
-#else
-          /* ftruncate is not available, so just reposition the file
-             to the location we would have truncated it. This will not
-             work properly with large files on 32-bit systems, but
-             most of those will have ftruncate. */
-          rc = fseek(outs->stream, (long)outs->init, SEEK_SET);
-#endif
-          if(rc) {
-            errorf(config->global, "Failed seeking to end of file");
-            return CURLE_WRITE_ERROR;
-          }
-          outs->bytes = 0; /* clear for next round */
-        }
-      }
-      *retryp = TRUE;
-      per->num_retries++;
-      *delay = sleeptime;
-      return CURLE_OK;
-    }
-  } /* if retry_remaining */
-noretry:
+    result = retrycheck(config, per, result, retryp, delay);
+    if(!result && *retryp)
+      return CURLE_OK; /* retry! */
+  }
 
   if((global->progressmode == CURL_PROGRESS_BAR) &&
      per->progressbar.calls)