Commit 4adcf7e9 authored by Dan Fandrich's avatar Dan Fandrich
Browse files

Moved all signal-based name resolution timeout handling into a single new

Curl_resolv_timeout function to reduce coupling.
parent 9f8c40ed
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -6,6 +6,10 @@

                                  Changelog

Daniel Fandrich (29 Sep 2008)
- Moved all signal-based name resolution timeout handling into a single new
  Curl_resolv_timeout function to reduce coupling.

Daniel Stenberg (29 Sep 2008)
- Ian Lynagh provided a patch that now makes CURLOPT_RANGE work fine for SFTP
  downloads!
+178 −12
Original line number Diff line number Diff line
@@ -55,6 +55,9 @@
#ifdef HAVE_SETJMP_H
#include <setjmp.h>
#endif
#ifdef HAVE_SIGNAL_H
#include <signal.h>
#endif

#ifdef HAVE_PROCESS_H
#include <process.h>
@@ -76,6 +79,12 @@
/* The last #include file should be: */
#include "memdebug.h"

#if defined(HAVE_ALARM) && defined(SIGALRM) && defined(HAVE_SIGSETJMP) \
    && !defined(USE_ARES)
/* alarm-based timeouts can only be used with all the dependencies satisfied */
#define USE_ALARM_TIMEOUT
#endif

/*
 * hostip.c explained
 * ==================
@@ -388,19 +397,8 @@ int Curl_resolv(struct connectdata *conn,
  struct SessionHandle *data = conn->data;
  CURLcode result;
  int rc = CURLRESOLV_ERROR; /* default to failure */
  *entry = NULL;

#ifdef HAVE_SIGSETJMP
  /* this allows us to time-out from the name resolver, as the timeout
     will generate a signal and we will siglongjmp() from that here */
  if(!data->set.no_signal) {
    if(sigsetjmp(curl_jmpenv, 1)) {
      /* this is coming from a siglongjmp() */
      failf(data, "name lookup timed out");
      return rc;
    }
  }
#endif
  *entry = NULL;

  /* Create an entry id, based upon the hostname and port */
  entry_id = create_hostcache_id(hostname, port);
@@ -484,6 +482,174 @@ int Curl_resolv(struct connectdata *conn,
  return rc;
}

#ifdef USE_ALARM_TIMEOUT 
/*
 * This signal handler jumps back into the main libcurl code and continues
 * execution.  This effectively causes the remainder of the application to run
 * within a signal handler which is nonportable and could lead to problems.
 */
static
RETSIGTYPE alarmfunc(int sig)
{
  /* this is for "-ansi -Wall -pedantic" to stop complaining!   (rabe) */
  (void)sig;
  siglongjmp(curl_jmpenv, 1);
  return;
}
#endif /* USE_ALARM_TIMEOUT */

/*
 * Curl_resolv_timeout() is the same as Curl_resolv() but specifies a
 * timeout.  This function might return immediately if we're using asynch
 * resolves. See the return codes.
 *
 * The cache entry we return will get its 'inuse' counter increased when this
 * function is used. You MUST call Curl_resolv_unlock() later (when you're
 * done using this struct) to decrease the counter again.
 *
 * If built with a synchronous resolver and use of signals is not
 * disabled by the application, then a nonzero timeout will cause a
 * timeout after the specified number of milliseconds. Otherwise, timeout
 * is ignored.
 *
 * Return codes:
 *
 * CURLRESOLV_TIMEDOUT(-2) = warning, time too short or previous alarm expired
 * CURLRESOLV_ERROR   (-1) = error, no pointer
 * CURLRESOLV_RESOLVED (0) = OK, pointer provided
 * CURLRESOLV_PENDING  (1) = waiting for response, no pointer
 */

int Curl_resolv_timeout(struct connectdata *conn,
                        const char *hostname,
                        int port,
                        struct Curl_dns_entry **entry,
                        long timeout)
{
#ifdef USE_ALARM_TIMEOUT 
#ifdef HAVE_SIGACTION
  struct sigaction keep_sigact;   /* store the old struct here */
  bool keep_copysig=FALSE;        /* did copy it? */
  struct sigaction sigact;
#else
#ifdef HAVE_SIGNAL
  void (*keep_sigact)(int);       /* store the old handler here */
#endif /* HAVE_SIGNAL */
#endif /* HAVE_SIGACTION */

  unsigned int prev_alarm=0;
#endif /* USE_ALARM_TIMEOUT */

  struct SessionHandle *data = conn->data;
  int rc = CURLRESOLV_ERROR; /* error by default */

  *entry = NULL;

#ifdef USE_ALARM_TIMEOUT 
  if (data->set.no_signal)
    /* Ignore the timeout when signals are disabled */
    timeout = 0;

  if(timeout && timeout < 1000)
    /* The alarm() function only provides integer second resolution, so if
       we want to wait less than one second we must bail out already now. */
    return CURLRESOLV_TIMEDOUT;

  if (timeout > 0) {
    /* This allows us to time-out from the name resolver, as the timeout
       will generate a signal and we will siglongjmp() from that here.
       This technique has problems (see alarmfunc). */
      if(sigsetjmp(curl_jmpenv, 1)) {
        /* this is coming from a siglongjmp() after an alarm signal */
        failf(data, "name lookup timed out");
        return rc;
      }

    /*************************************************************
     * Set signal handler to catch SIGALRM
     * Store the old value to be able to set it back later!
     *************************************************************/
#ifdef HAVE_SIGACTION
    sigaction(SIGALRM, NULL, &sigact);
    keep_sigact = sigact;
    keep_copysig = TRUE; /* yes, we have a copy */
    sigact.sa_handler = alarmfunc;
#ifdef SA_RESTART
    /* HPUX doesn't have SA_RESTART but defaults to that behaviour! */
    sigact.sa_flags &= ~SA_RESTART;
#endif
    /* now set the new struct */
    sigaction(SIGALRM, &sigact, NULL);
#else /* HAVE_SIGACTION */
    /* no sigaction(), revert to the much lamer signal() */
#ifdef HAVE_SIGNAL
    keep_sigact = signal(SIGALRM, alarmfunc);
#endif
#endif /* HAVE_SIGACTION */

    /* alarm() makes a signal get sent when the timeout fires off, and that
       will abort system calls */
    prev_alarm = alarm((unsigned int) (timeout ? timeout/1000L : timeout));
  }

#else
#ifndef CURLRES_ASYNCH
  if(timeout)
    infof(data, "timeout on name lookup is not supported\n");
#endif
#endif /* USE_ALARM_TIMEOUT */

  /* Perform the actual name resolution. This might be interrupted by an
   * alarm if it takes too long.
   */
  rc = Curl_resolv(conn, hostname, port, entry);

#ifdef USE_ALARM_TIMEOUT 
  if (timeout > 0) {

#ifdef HAVE_SIGACTION
    if(keep_copysig) {
      /* we got a struct as it looked before, now put that one back nice
         and clean */
      sigaction(SIGALRM, &keep_sigact, NULL); /* put it back */
    }
#else
#ifdef HAVE_SIGNAL
    /* restore the previous SIGALRM handler */
    signal(SIGALRM, keep_sigact);
#endif
#endif /* HAVE_SIGACTION */

    /* switch back the alarm() to either zero or to what it was before minus
       the time we spent until now! */
    if(prev_alarm) {
      /* there was an alarm() set before us, now put it back */
      unsigned long elapsed_ms = Curl_tvdiff(Curl_tvnow(), conn->created);

      /* the alarm period is counted in even number of seconds */
      unsigned long alarm_set = prev_alarm - elapsed_ms/1000;

      if(!alarm_set ||
         ((alarm_set >= 0x80000000) && (prev_alarm < 0x80000000)) ) {
        /* if the alarm time-left reached zero or turned "negative" (counted
           with unsigned values), we should fire off a SIGALRM here, but we
           won't, and zero would be to switch it off so we never set it to
           less than 1! */
        alarm(1);
        rc = CURLRESOLV_TIMEDOUT;
        failf(data, "Previous alarm fired off!");
      }
      else
        alarm((unsigned int)alarm_set);
    }
    else
      alarm(0); /* just shut it off */
  }
#endif /* USE_ALARM_TIMEOUT */

  return rc;
}

/*
 * Curl_resolv_unlock() unlocks the given cached DNS entry. When this has been
 * made, the struct may be destroyed due to pruning. It is important that only
+4 −0
Original line number Diff line number Diff line
@@ -158,11 +158,15 @@ struct Curl_dns_entry {
 * use, or we'll leak memory!
 */
/* return codes */
#define CURLRESOLV_TIMEDOUT -2
#define CURLRESOLV_ERROR    -1
#define CURLRESOLV_RESOLVED  0
#define CURLRESOLV_PENDING   1
int Curl_resolv(struct connectdata *conn, const char *hostname,
                int port, struct Curl_dns_entry **dnsentry);
int Curl_resolv_timeout(struct connectdata *conn, const char *hostname,
                        int port, struct Curl_dns_entry **dnsentry,
                        long timeout);

/*
 * Curl_ipvalid() checks what CURL_IPRESOLVE_* requirements that might've
+17 −126
Original line number Diff line number Diff line
@@ -60,9 +60,6 @@
#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif
#ifdef HAVE_SIGNAL_H
#include <signal.h>
#endif

#ifdef HAVE_SYS_PARAM_H
#include <sys/param.h>
@@ -73,10 +70,6 @@
#include <inet.h>
#endif

#ifdef HAVE_SETJMP_H
#include <setjmp.h>
#endif

#ifndef HAVE_SOCKET
#error "We can't compile without socket() support!"
#endif
@@ -169,27 +162,6 @@ static void flush_cookies(struct SessionHandle *data, int cleanup);
#define verboseconnect(x)  do { } while (0)
#endif

#ifndef WIN32
/* not for WIN32 builds */

#if defined(HAVE_ALARM) && defined(SIGALRM) && !defined(USE_ARES)
/*
 * This signal handler jumps back into the main libcurl code and continues
 * execution.  This effectively causes the remainder of the application to run
 * within a signal handler which is nonportable and could lead to problems.
 */
static
RETSIGTYPE alarmfunc(int sig)
{
  /* this is for "-ansi -Wall -pedantic" to stop complaining!   (rabe) */
  (void)sig;
#ifdef HAVE_SIGSETJMP
  siglongjmp(curl_jmpenv, 1);
#endif
  return;
}
#endif /* HAVE_ALARM && SIGALRM && !USE_ARES */
#endif /* WIN32 */

/*
 * Protocol table.
@@ -3788,34 +3760,19 @@ static CURLcode resolve_server(struct SessionHandle *data,
                               bool *async)
{
  CURLcode result=CURLE_OK;

#if defined(HAVE_ALARM) && defined(SIGALRM) && !defined(USE_ARES)
#ifdef HAVE_SIGACTION
  struct sigaction keep_sigact;   /* store the old struct here */
  bool keep_copysig=FALSE;        /* did copy it? */
#else
#ifdef HAVE_SIGNAL
  void (*keep_sigact)(int);       /* store the old handler here */
#endif /* HAVE_SIGNAL */
#endif /* HAVE_SIGACTION */

  unsigned int prev_alarm=0;
  long shortest = 0; /* default to no timeout */

  /*************************************************************
   * Set timeout if that is being used, and we're not using an asynchronous
   * name resolve.
   * Set timeout if that is being used
   *************************************************************/
  if((data->set.timeout || data->set.connecttimeout) && !data->set.no_signal) {
#ifdef HAVE_SIGACTION
    struct sigaction sigact;
#endif /* HAVE_SIGACTION */
  if(data->set.timeout || data->set.connecttimeout) {

    /* We set the timeout on the name resolving phase first, separately from
     * the download/upload part to allow a maximum time on everything. This is
     * a signal-based timeout, why it won't work and shouldn't be used in
     * multi-threaded environments. */

    long shortest = data->set.timeout; /* default to this timeout value */
    shortest = data->set.timeout; /* default to this timeout value */
    if(shortest && data->set.connecttimeout &&
       (data->set.connecttimeout < shortest))
      /* if both are set, pick the shortest */
@@ -3823,42 +3780,10 @@ static CURLcode resolve_server(struct SessionHandle *data,
    else if(!shortest)
      /* if timeout is not set, use the connect timeout */
      shortest = data->set.connecttimeout;

    if(shortest < 1000)
      /* the alarm() function only provide integer second resolution, so if
         we want to wait less than one second we must bail out already now. */
      return CURLE_OPERATION_TIMEDOUT;

    /*************************************************************
     * Set signal handler to catch SIGALRM
     * Store the old value to be able to set it back later!
     *************************************************************/
#ifdef HAVE_SIGACTION
    sigaction(SIGALRM, NULL, &sigact);
    keep_sigact = sigact;
    keep_copysig = TRUE; /* yes, we have a copy */
    sigact.sa_handler = alarmfunc;
#ifdef SA_RESTART
    /* HPUX doesn't have SA_RESTART but defaults to that behaviour! */
    sigact.sa_flags &= ~SA_RESTART;
#endif
    /* now set the new struct */
    sigaction(SIGALRM, &sigact, NULL);
#else /* HAVE_SIGACTION */
    /* no sigaction(), revert to the much lamer signal() */
#ifdef HAVE_SIGNAL
    keep_sigact = signal(SIGALRM, alarmfunc);
#endif
#endif /* HAVE_SIGACTION */

    /* alarm() makes a signal get sent when the timeout fires off, and that
       will abort system calls */
    prev_alarm = alarm((unsigned int) (shortest ? shortest/1000L : shortest));
  /* We can expect the conn->created time to be "now", as that was just
     recently set in the beginning of this function and nothing slow
     has been done since then until now. */
  }
#endif /* HAVE_ALARM && SIGALRM && !USE_ARES */

  /*************************************************************
   * Resolve the name of the server or proxy
@@ -3885,10 +3810,14 @@ static CURLcode resolve_server(struct SessionHandle *data,
      conn->port =  conn->remote_port; /* it is the same port */

      /* Resolve target host right on */
      rc = Curl_resolv(conn, conn->host.name, (int)conn->port, &hostaddr);
      rc = Curl_resolv_timeout(conn, conn->host.name, (int)conn->port,
                               &hostaddr, shortest);
      if(rc == CURLRESOLV_PENDING)
        *async = TRUE;

      else if (rc == CURLRESOLV_TIMEDOUT)
        result = CURLE_OPERATION_TIMEDOUT;

      else if(!hostaddr) {
        failf(data, "Couldn't resolve host '%s'", conn->host.dispname);
        result =  CURLE_COULDNT_RESOLVE_HOST;
@@ -3902,11 +3831,15 @@ static CURLcode resolve_server(struct SessionHandle *data,
      fix_hostname(data, conn, &conn->proxy);

      /* resolve proxy */
      rc = Curl_resolv(conn, conn->proxy.name, (int)conn->port, &hostaddr);
      rc = Curl_resolv_timeout(conn, conn->proxy.name, (int)conn->port,
                               &hostaddr, shortest);

      if(rc == CURLRESOLV_PENDING)
        *async = TRUE;

      else if (rc == CURLRESOLV_TIMEDOUT)
        result = CURLE_OPERATION_TIMEDOUT;

      else if(!hostaddr) {
        failf(data, "Couldn't resolve proxy '%s'", conn->proxy.dispname);
        result = CURLE_COULDNT_RESOLVE_PROXY;
@@ -3916,48 +3849,6 @@ static CURLcode resolve_server(struct SessionHandle *data,
    *addr = hostaddr;
  }

#if defined(HAVE_ALARM) && defined(SIGALRM) && !defined(USE_ARES)
  if((data->set.timeout || data->set.connecttimeout) && !data->set.no_signal) {
#ifdef HAVE_SIGACTION
    if(keep_copysig) {
      /* we got a struct as it looked before, now put that one back nice
         and clean */
      sigaction(SIGALRM, &keep_sigact, NULL); /* put it back */
    }
#else
#ifdef HAVE_SIGNAL
    /* restore the previous SIGALRM handler */
    signal(SIGALRM, keep_sigact);
#endif
#endif /* HAVE_SIGACTION */

    /* switch back the alarm() to either zero or to what it was before minus
       the time we spent until now! */
    if(prev_alarm) {
      /* there was an alarm() set before us, now put it back */
      unsigned long elapsed_ms = Curl_tvdiff(Curl_tvnow(), conn->created);
      unsigned long alarm_set;

      /* the alarm period is counted in even number of seconds */
      alarm_set = prev_alarm - elapsed_ms/1000;

      if(!alarm_set ||
         ((alarm_set >= 0x80000000) && (prev_alarm < 0x80000000)) ) {
        /* if the alarm time-left reached zero or turned "negative" (counted
           with unsigned values), we should fire off a SIGALRM here, but we
           won't, and zero would be to switch it off so we never set it to
           less than 1! */
        alarm(1);
        result = CURLE_OPERATION_TIMEDOUT;
        failf(data, "Previous alarm fired off!");
      }
      else
        alarm((unsigned int)alarm_set);
    }
    else
      alarm(0); /* just shut it off */
  }
#endif /* HAVE_ALARM && SIGALRM && !USE_ARES */
  return result;
}