cURL / Mailing Lists / curl-library / Single Mail

curl-library

[PATCH] Callback to abort libcurl when receiving a signal

From: Pierre Ynard <linkfanel_at_yahoo.fr>
Date: Mon, 3 Mar 2008 10:31:35 +0100

Hello,

This patch implements a way for applications using libcurl to cleanly
abort operations upon receiving a signal in the middle of performing
a request with libcurl. It defines a new CURLOPT_INTERRUPTFUNCTION
callback option, which is called by libcurl everytime a blocking call
to poll() or select() is interrupted. Then, the application may define
some signal handlers, and pass to libcurl a callback that will return
non-zero if one of these signals has been received and libcurl needs to
abort. A typical example of use would be:

static volatile int termsig = 0;

static void term_handler(int signum)
{
    termsig = signum;
}

static int interrupt_cb(void *clientp)
{
    if (termsig)
        return -1;

    return 0;
}

[...]

static int do_stuff()
{
    struct sigaction act;
    memset(&act, 0, sizeof(struct sigaction));
    act.sa_handler = &term_handler;
    sigaction(SIGTERM, &act, NULL);

    CURL *curlh = curl_easy_init();
    curl_easy_setopt(curlh, CURLOPT_URL, my_url);
    curl_easy_setopt(curlh, CURLOPT_INTERRUPTFUNCTION, &interrupt_cb);

    curl_easy_perform(curlh);

    [...]
}

If SIGTERM is received, the request will abort and curl_easy_perform()
will return instantly, even if it is stalled in the middle of connect(),
which is currently impossible to do.

I have looked through the mailing-list archives and seen several users
requesting such a feature. This patch does not address signal blocking
or the use ppoll() or pselect() on platforms where it is available, but
the code is laid out so that it could be easily done in a second time.
For now, requests should almost always be interrupted successfully, and
in the event of a race condition, it should at worst wait another 1000
ms before aborting. I believe that this is quite better than what we
currently have, and good enough for many applications that would need
this feature.

Regards,

diff -urNp curl-7.18.0-orig/include/curl/curl.h curl-7.18.0/include/curl/curl.h
--- curl-7.18.0-orig/include/curl/curl.h 2008-01-10 11:30:20.000000000 +0100
+++ curl-7.18.0/include/curl/curl.h 2008-02-28 17:36:26.000000000 +0100
@@ -277,6 +277,8 @@ typedef curl_socket_t
                             curlsocktype purpose,
                             struct curl_sockaddr *address);
 
+typedef int (*curl_interrupt_callback)(void *clientp);
+
 #ifndef CURL_NO_OLDIES
   /* not used since 7.10.8, will be removed in a future release */
 typedef int (*curl_passwd_callback)(void *clientp,
@@ -1188,6 +1190,11 @@ typedef enum {
   CINIT(SEEKFUNCTION, FUNCTIONPOINT, 167),
   CINIT(SEEKDATA, OBJECTPOINT, 168),
 
+ /* Callback function for checking conditions to abort after receiving a
+ signal */
+ CINIT(INTERRUPTFUNCTION, FUNCTIONPOINT, 169),
+ CINIT(INTERRUPTDATA, OBJECTPOINT, 170),
+
   CURLOPT_LASTENTRY /* the last unused */
 } CURLoption;
 
diff -urNp curl-7.18.0-orig/lib/connect.c curl-7.18.0/lib/connect.c
--- curl-7.18.0-orig/lib/connect.c 2008-01-22 00:47:28.000000000 +0100
+++ curl-7.18.0/lib/connect.c 2008-02-28 17:36:26.000000000 +0100
@@ -175,17 +175,20 @@ int Curl_nonblock(curl_socket_t sockfd,
  * number if milliseconds. It returns:
  * 0 fine connect
  * -1 select() error
+ * -2 select() interrupted by a signal and aborting
  * 1 select() timeout
  * 2 select() returned with an error condition fd_set
  */
 
 #define WAITCONN_CONNECTED 0
 #define WAITCONN_SELECT_ERROR -1
+#define WAITCONN_ABORTED -2
 #define WAITCONN_TIMEOUT 1
 #define WAITCONN_FDSET_ERROR 2
 
 static
-int waitconnect(curl_socket_t sockfd, /* socket */
+int waitconnect(struct connectdata *conn,
+ curl_socket_t sockfd, /* socket */
                 long timeout_msec)
 {
   int rc;
@@ -197,11 +200,15 @@ int waitconnect(curl_socket_t sockfd, /*
 #endif
 
   /* now select() until we get connect or timeout */
- rc = Curl_socket_ready(CURL_SOCKET_BAD, sockfd, (int)timeout_msec);
+ rc = Curl_socket_ready(conn, CURL_SOCKET_BAD, sockfd, (int)timeout_msec);
   if(-1 == rc)
     /* error, no connect here, try next */
     return WAITCONN_SELECT_ERROR;
 
+ else if(-2 == rc)
+ /* aborting after receiving a signal */
+ return WAITCONN_ABORTED;
+
   else if(0 == rc)
     /* timeout, no connect today */
     return WAITCONN_TIMEOUT;
@@ -572,7 +579,7 @@ CURLcode Curl_is_connected(struct connec
   Curl_expire(data, allow);
 
   /* check for connect without timeout as we want to return immediately */
- rc = waitconnect(sockfd, 0);
+ rc = waitconnect(conn, sockfd, 0);
 
   if(WAITCONN_CONNECTED == rc) {
     int error;
@@ -763,7 +770,7 @@ singleipconnect(struct connectdata *conn
        */
     case EAGAIN:
 #endif
- rc = waitconnect(sockfd, timeout_ms);
+ rc = waitconnect(conn, sockfd, timeout_ms);
       break;
     default:
       /* unknown error, fallthrough and try another address! */
@@ -792,6 +799,8 @@ singleipconnect(struct connectdata *conn
   }
   else if(WAITCONN_TIMEOUT == rc)
     infof(data, "Timeout\n");
+ else if(WAITCONN_ABORTED == rc)
+ infof(data, "interrupted, aborting\n");
   else {
     data->state.os_errno = error;
     infof(data, "%s\n", Curl_strerror(conn, error));
diff -urNp curl-7.18.0-orig/lib/ftp.c curl-7.18.0/lib/ftp.c
--- curl-7.18.0-orig/lib/ftp.c 2008-01-26 00:56:20.000000000 +0100
+++ curl-7.18.0/lib/ftp.c 2008-02-28 17:36:26.000000000 +0100
@@ -339,11 +339,14 @@ static CURLcode AllowServerConnect(struc
     }
   }
 
- switch (Curl_socket_ready(sock, CURL_SOCKET_BAD, (int)timeout_ms)) {
+ switch (Curl_socket_ready(conn, sock, CURL_SOCKET_BAD, (int)timeout_ms)) {
   case -1: /* error */
     /* let's die here */
     failf(data, "Error while waiting for server connect");
     return CURLE_FTP_PORT_FAILED;
+ case -2: /* stopped by signal handling */
+ infof(data, "Signal received while waiting for server connect, aborting");
+ return CURLE_ABORTED_BY_CALLBACK;
   case 0: /* timeout */
     /* let's die here */
     failf(data, "Timeout while waiting for server connect");
@@ -701,12 +704,16 @@ CURLcode Curl_GetFTPResponse(ssize_t *nr
        */
     }
     else {
- switch (Curl_socket_ready(sockfd, CURL_SOCKET_BAD, (int)interval_ms)) {
+ switch (Curl_socket_ready(conn, sockfd, CURL_SOCKET_BAD, (int)interval_ms)) {
       case -1: /* select() error, stop reading */
         failf(data, "FTP response aborted due to select/poll error: %d",
               SOCKERRNO);
         return CURLE_RECV_ERROR;
 
+ case -2: /* stopped by signal handling */
+ infof(data, "FTP response aborted after receiving a signal");
+ return CURLE_ABORTED_BY_CALLBACK;
+
       case 0: /* timeout */
         if(Curl_pgrsUpdate(conn))
           return CURLE_ABORTED_BY_CALLBACK;
@@ -2964,7 +2971,8 @@ static CURLcode ftp_multi_statemach(stru
     return CURLE_OPERATION_TIMEDOUT;
   }
 
- rc = Curl_socket_ready(ftpc->sendleft?CURL_SOCKET_BAD:sock, /* reading */
+ rc = Curl_socket_ready(conn,
+ ftpc->sendleft?CURL_SOCKET_BAD:sock, /* reading */
                          ftpc->sendleft?sock:CURL_SOCKET_BAD, /* writing */
                          0);
 
@@ -2997,7 +3005,8 @@ static CURLcode ftp_easy_statemach(struc
       return CURLE_OPERATION_TIMEDOUT; /* already too little time */
     }
 
- rc = Curl_socket_ready(ftpc->sendleft?CURL_SOCKET_BAD:sock, /* reading */
+ rc = Curl_socket_ready(conn,
+ ftpc->sendleft?CURL_SOCKET_BAD:sock, /* reading */
                            ftpc->sendleft?sock:CURL_SOCKET_BAD, /* writing */
                            (int)timeout_ms);
 
@@ -3005,6 +3014,10 @@ static CURLcode ftp_easy_statemach(struc
       failf(data, "select/poll error");
       return CURLE_OUT_OF_MEMORY;
     }
+ else if(rc == -2) {
+ infof(data, "interrupted by signal, aborting");
+ return CURLE_ABORTED_BY_CALLBACK;
+ }
     else if(rc == 0) {
       result = CURLE_OPERATION_TIMEDOUT;
       break;
diff -urNp curl-7.18.0-orig/lib/gtls.c curl-7.18.0/lib/gtls.c
--- curl-7.18.0-orig/lib/gtls.c 2008-01-27 23:41:56.000000000 +0100
+++ curl-7.18.0/lib/gtls.c 2008-02-28 17:36:26.000000000 +0100
@@ -178,7 +178,7 @@ static CURLcode handshake(struct connect
         return CURLE_OPERATION_TIMEDOUT;
       }
 
- rc = Curl_socket_ready(conn->sock[sockindex],
+ rc = Curl_socket_ready(conn, conn->sock[sockindex],
                        conn->sock[sockindex], (int)timeout_ms);
       if(rc > 0)
         /* reabable or writable, go loop*/
@@ -188,6 +188,10 @@ static CURLcode handshake(struct connect
         failf(data, "SSL connection timeout");
         return CURLE_OPERATION_TIMEDOUT;
       }
+ else if(-2 == rc) {
+ infof(data, "signal received, SSL connection aborting");
+ return CURLE_ABORTED_BY_CALLBACK;
+ }
       else {
         /* anything that gets here is fatally bad */
         failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO);
@@ -593,7 +597,7 @@ int Curl_gtls_shutdown(struct connectdat
 
   if(conn->ssl[sockindex].session) {
     while(!done) {
- int what = Curl_socket_ready(conn->sock[sockindex],
+ int what = Curl_socket_ready(conn, conn->sock[sockindex],
                              CURL_SOCKET_BAD, SSL_SHUTDOWN_TIMEOUT);
       if(what > 0) {
         /* Something to read, let's do it and hope that it is the close
@@ -622,6 +626,11 @@ int Curl_gtls_shutdown(struct connectdat
         done = 1;
         break;
       }
+ else if(-2 == what) {
+ infof(data, "signal received, aborting SSL shutdown");
+ retval = -1;
+ done = 1;
+ }
       else {
         /* anything that gets here is fatally bad */
         failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO);
diff -urNp curl-7.18.0-orig/lib/hostares.c curl-7.18.0/lib/hostares.c
--- curl-7.18.0-orig/lib/hostares.c 2007-11-07 10:21:35.000000000 +0100
+++ curl-7.18.0/lib/hostares.c 2008-02-28 17:36:26.000000000 +0100
@@ -169,7 +169,7 @@ static int ares_waitperform(struct conne
   num = i;
 
   if(num)
- nfds = Curl_poll(pfd, num, timeout_ms);
+ nfds = Curl_poll(conn, pfd, num, timeout_ms);
   else
     nfds = 0;
 
diff -urNp curl-7.18.0-orig/lib/http.c curl-7.18.0/lib/http.c
--- curl-7.18.0-orig/lib/http.c 2008-01-26 00:33:45.000000000 +0100
+++ curl-7.18.0/lib/http.c 2008-02-28 17:36:26.000000000 +0100
@@ -1231,6 +1231,7 @@ CURLcode Curl_proxyCONNECT(struct connec
 #define SELECT_OK 0
 #define SELECT_ERROR 1
 #define SELECT_TIMEOUT 2
+#define SELECT_ABORTED 3
   int error = SELECT_OK;
 
   conn->bits.proxy_connect_closed = FALSE;
@@ -1344,7 +1345,7 @@ CURLcode Curl_proxyCONNECT(struct connec
 
     /* if we're in multi-mode and we would block, return instead for a retry */
     if(Curl_if_multi == data->state.used_interface) {
- if(0 == Curl_socket_ready(tunnelsocket, CURL_SOCKET_BAD, 0))
+ if(0 == Curl_socket_ready(conn, tunnelsocket, CURL_SOCKET_BAD, 0))
         /* return so we'll be called again polling-style */
         return CURLE_OK;
       else {
@@ -1392,12 +1393,16 @@ CURLcode Curl_proxyCONNECT(struct connec
         }
 
         /* loop every second at least, less if the timeout is near */
- switch (Curl_socket_ready(tunnelsocket, CURL_SOCKET_BAD,
+ switch (Curl_socket_ready(conn, tunnelsocket, CURL_SOCKET_BAD,
                             check<1000L?(int)check:1000)) {
         case -1: /* select() error, stop reading */
           error = SELECT_ERROR;
           failf(data, "Proxy CONNECT aborted due to select/poll error");
           break;
+ case -2: /* stopped by signal handling */
+ error = SELECT_ABORTED;
+ failf(data, "Proxy CONNECT aborted after receiving a signal");
+ break;
         case 0: /* timeout */
           break;
         default:
@@ -1602,7 +1607,8 @@ CURLcode Curl_proxyCONNECT(struct connec
       } /* while there's buffer left and loop is requested */
 
       if(error)
- return CURLE_RECV_ERROR;
+ return (error == SELECT_ABORTED) ? CURLE_ABORTED_BY_CALLBACK :
+ CURLE_RECV_ERROR;
 
       if(data->info.httpproxycode != 200)
         /* Deal with the possibly already received authenticate
diff -urNp curl-7.18.0-orig/lib/qssl.c curl-7.18.0/lib/qssl.c
--- curl-7.18.0-orig/lib/qssl.c 2008-01-27 23:41:56.000000000 +0100
+++ curl-7.18.0/lib/qssl.c 2008-02-28 17:36:26.000000000 +0100
@@ -337,11 +337,16 @@ int Curl_qsossl_shutdown(struct connectd
 
   rc = 0;
 
- what = Curl_socket_ready(conn->sock[sockindex],
+ what = Curl_socket_ready(conn, conn->sock[sockindex],
                            CURL_SOCKET_BAD, SSL_SHUTDOWN_TIMEOUT);
 
   for (;;) {
- if(what < 0) {
+ if(what == -2) {
+ infof(data, "signal received, aborting SSL shutdown");
+ rc = -1;
+ break;
+ }
+ else if(what < 0) {
       /* anything that gets here is fatally bad */
       failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO);
       rc = -1;
@@ -366,7 +371,7 @@ int Curl_qsossl_shutdown(struct connectd
     if(nread <= 0)
       break;
 
- what = Curl_socket_ready(conn->sock[sockindex], CURL_SOCKET_BAD, 0);
+ what = Curl_socket_ready(conn, conn->sock[sockindex], CURL_SOCKET_BAD, 0);
   }
 
   return rc;
diff -urNp curl-7.18.0-orig/lib/select.c curl-7.18.0/lib/select.c
--- curl-7.18.0-orig/lib/select.c 2007-11-05 10:45:09.000000000 +0100
+++ curl-7.18.0/lib/select.c 2008-02-28 17:42:17.000000000 +0100
@@ -67,12 +67,6 @@
 
 #define elapsed_ms (int)curlx_tvdiff(curlx_tvnow(), initial_tv)
 
-#ifdef CURL_ACKNOWLEDGE_EINTR
-#define error_is_EINTR (error == EINTR)
-#else
-#define error_is_EINTR (0)
-#endif
-
 /*
  * Internal function used for waiting a specific amount of ms
  * in Curl_socket_ready() and Curl_poll() when no file descriptor
@@ -91,10 +85,13 @@
  *
  * Return values:
  * -1 = system call error, invalid timeout value, or interrupted
+ * -2 = interrupted by a signal, and aborting according to the
+ * CURLOPT_INTERRUPTFUNCTION callback passed by the user
  * 0 = specified timeout has elapsed
  */
-static int wait_ms(int timeout_ms)
+static int wait_ms(struct connectdata *conn, int timeout_ms)
 {
+ struct SessionHandle *data = conn->data;
 #if !defined(MSDOS) && !defined(USE_WINSOCK)
 #ifndef HAVE_POLL_FINE
   struct timeval pending_tv;
@@ -111,6 +108,7 @@ static int wait_ms(int timeout_ms)
     SET_SOCKERRNO(EINVAL);
     return -1;
   }
+
 #if defined(MSDOS)
   delay(timeout_ms);
 #elif defined(USE_WINSOCK)
@@ -119,6 +117,16 @@ static int wait_ms(int timeout_ms)
   pending_ms = timeout_ms;
   initial_tv = curlx_tvnow();
   do {
+ int intr = 0;
+ /* Extra check because we're not blocking signals. After that and
+ before the call to poll()/select(), there is a race condition.
+ TODO: block signals and use ppoll()/pselect() */
+ if(data->set.finterrupt) {
+ intr = data->set.finterrupt(data->set.interrupt_client);
+ if (intr)
+ goto skipped;
+ }
+
 #if defined(HAVE_POLL_FINE)
     r = poll(NULL, 0, pending_ms);
 #else
@@ -126,18 +134,38 @@ static int wait_ms(int timeout_ms)
     pending_tv.tv_usec = (pending_ms % 1000) * 1000;
     r = select(0, NULL, NULL, NULL, &pending_tv);
 #endif /* HAVE_POLL_FINE */
+
+ if(data->set.finterrupt) {
+ int sockerrno = SOCKERRNO;
+ intr = data->set.finterrupt(data->set.interrupt_client);
+ SET_SOCKERRNO(sockerrno);
+ }
+
+ /* Mimic return from ppoll() */
+ if (intr && r != -1) {
+skipped:
+ r = -1;
+ SET_SOCKERRNO(EINTR);
+ }
+
     if(r != -1)
       break;
     error = SOCKERRNO;
- if((error == EINVAL) || error_is_EINTR)
+ if(error != EINTR)
+ break;
+#ifdef CURL_ACKNOWLEDGE_EINTR
+ break;
+#endif
+ if (intr) {
+ r = -2;
       break;
+ }
     pending_ms = timeout_ms - elapsed_ms;
     if(pending_ms <= 0)
       break;
   } while(r == -1);
 #endif /* USE_WINSOCK */
- if(r)
- r = -1;
+
   return r;
 }
 
@@ -156,12 +184,15 @@ static int wait_ms(int timeout_ms)
  *
  * Return values:
  * -1 = system call error or fd >= FD_SETSIZE
+ * -2 = interrupted by a signal, and aborting according to the
+ * CURLOPT_INTERRUPTFUNCTION callback passed by the user
  * 0 = timeout
  * CURL_CSELECT_IN | CURL_CSELECT_OUT | CURL_CSELECT_ERR
  */
-int Curl_socket_ready(curl_socket_t readfd, curl_socket_t writefd,
- int timeout_ms)
+int Curl_socket_ready(struct connectdata *conn, curl_socket_t readfd,
+ curl_socket_t writefd, int timeout_ms)
 {
+ struct SessionHandle *data = conn->data;
 #ifdef HAVE_POLL_FINE
   struct pollfd pfd[2];
   int num;
@@ -180,7 +211,7 @@ int Curl_socket_ready(curl_socket_t read
   int ret;
 
   if((readfd == CURL_SOCKET_BAD) && (writefd == CURL_SOCKET_BAD)) {
- r = wait_ms(timeout_ms);
+ r = wait_ms(conn, timeout_ms);
     return r;
   }
 
@@ -211,16 +242,56 @@ int Curl_socket_ready(curl_socket_t read
   }
 
   do {
+ int intr = 0;
     if(timeout_ms < 0)
       pending_ms = -1;
     else if(!timeout_ms)
       pending_ms = 0;
- r = poll(pfd, num, pending_ms);
+
+ if(! timeout_ms)
+ /* We do not arrange to interrupt anything here, as the caller is
+ only doing non-blocking checks during setup or something. */
+ r = poll(pfd, num, pending_ms);
+ else {
+ /* Extra check because we're not blocking signals. After that
+ and before the call to poll(), there is a race condition.
+ TODO: block signals and use ppoll() */
+ if(data->set.finterrupt) {
+ intr = data->set.finterrupt(data->set.interrupt_client);
+ if (intr)
+ goto skipped;
+ }
+
+ r = poll(pfd, num, pending_ms);
+
+ if(data->set.finterrupt) {
+ int sockerrno = SOCKERRNO;
+ intr = data->set.finterrupt(data->set.interrupt_client);
+ SET_SOCKERRNO(sockerrno);
+ }
+
+ /* Mimic return from ppoll() */
+ if (intr && r != -1) {
+skipped:
+ r = -1;
+ SET_SOCKERRNO(EINTR);
+ }
+ }
+
     if(r != -1)
       break;
     error = SOCKERRNO;
- if((error == EINVAL) || error_is_EINTR)
+
+ if(error != EINTR)
       break;
+#ifdef CURL_ACKNOWLEDGE_EINTR
+ break;
+#endif
+ if (intr) {
+ r = -2;
+ break;
+ }
+
     if(timeout_ms > 0) {
       pending_ms = timeout_ms - elapsed_ms;
       if(pending_ms <= 0)
@@ -228,10 +299,8 @@ int Curl_socket_ready(curl_socket_t read
     }
   } while(r == -1);
 
- if(r < 0)
- return -1;
- if(r == 0)
- return 0;
+ if(r <= 0)
+ return r;
 
   ret = 0;
   num = 0;
@@ -276,6 +345,7 @@ int Curl_socket_ready(curl_socket_t read
   ptimeout = (timeout_ms < 0) ? NULL : &pending_tv;
 
   do {
+ int intr = 0;
     if(timeout_ms > 0) {
       pending_tv.tv_sec = pending_ms / 1000;
       pending_tv.tv_usec = (pending_ms % 1000) * 1000;
@@ -284,12 +354,51 @@ int Curl_socket_ready(curl_socket_t read
       pending_tv.tv_sec = 0;
       pending_tv.tv_usec = 0;
     }
- r = select((int)maxfd + 1, &fds_read, &fds_write, &fds_err, ptimeout);
+
+ if(! timeout_ms)
+ /* We do not arrange to interrupt anything here, as the caller is
+ only doing non-blocking checks during setup or something. */
+ r = select((int)maxfd + 1, &fds_read, &fds_write, &fds_err, ptimeout);
+ else {
+ /* Extra check because we're not blocking signals. After that
+ and before the call to select(), there is a race condition.
+ TODO: block signals and use pselect() */
+ if(data->set.finterrupt) {
+ intr = data->set.finterrupt(data->set.interrupt_client);
+ if (intr)
+ goto skipped;
+ }
+
+ r = select((int)maxfd + 1, &fds_read, &fds_write, &fds_err, ptimeout);
+
+ if(data->set.finterrupt) {
+ int sockerrno = SOCKERRNO;
+ intr = data->set.finterrupt(data->set.interrupt_client);
+ SET_SOCKERRNO(sockerrno);
+ }
+
+ /* Mimic return from pselect() */
+ if (intr && r != -1) {
+skipped:
+ r = -1;
+ SET_SOCKERRNO(EINTR);
+ }
+ }
+
     if(r != -1)
       break;
     error = SOCKERRNO;
- if((error == EINVAL) || (error == EBADF) || error_is_EINTR)
+
+ if(error != EINTR)
+ break;
+#ifdef CURL_ACKNOWLEDGE_EINTR
+ break;
+#endif
+ if (intr) {
+ r = -2;
       break;
+ }
+
     if(timeout_ms > 0) {
       pending_ms = timeout_ms - elapsed_ms;
       if(pending_ms <= 0)
@@ -297,10 +406,8 @@ int Curl_socket_ready(curl_socket_t read
     }
   } while(r == -1);
 
- if(r < 0)
- return -1;
- if(r == 0)
- return 0;
+ if(r <= 0)
+ return r;
 
   ret = 0;
   if(readfd != CURL_SOCKET_BAD) {
@@ -335,11 +442,15 @@ int Curl_socket_ready(curl_socket_t read
  *
  * Return values:
  * -1 = system call error or fd >= FD_SETSIZE
+ * -2 = interrupted by a signal, and aborting according to the
+ * CURLOPT_INTERRUPTFUNCTION callback passed by the user
  * 0 = timeout
  * N = number of structures with non zero revent fields
  */
-int Curl_poll(struct pollfd ufds[], unsigned int nfds, int timeout_ms)
+int Curl_poll(struct connectdata *conn, struct pollfd ufds[],
+ unsigned int nfds, int timeout_ms)
 {
+ struct SessionHandle *data = conn->data;
 #ifndef HAVE_POLL_FINE
   struct timeval pending_tv;
   struct timeval *ptimeout;
@@ -364,7 +475,7 @@ int Curl_poll(struct pollfd ufds[], unsi
     }
   }
   if(fds_none) {
- r = wait_ms(timeout_ms);
+ r = wait_ms(conn, timeout_ms);
     return r;
   }
 
@@ -381,16 +492,56 @@ int Curl_poll(struct pollfd ufds[], unsi
 #ifdef HAVE_POLL_FINE
 
   do {
+ int intr = 0;
     if(timeout_ms < 0)
       pending_ms = -1;
     else if(!timeout_ms)
       pending_ms = 0;
- r = poll(ufds, nfds, pending_ms);
+
+ if(! timeout_ms)
+ /* We do not arrange to interrupt anything here, as the caller is
+ only doing non-blocking checks during setup or something. */
+ r = poll(ufds, nfds, pending_ms);
+ else {
+ /* Extra check because we're not blocking signals. After that
+ and before the call to poll(), there is a race condition.
+ TODO: block signals and use ppoll() */
+ if(data->set.finterrupt) {
+ intr = data->set.finterrupt(data->set.interrupt_client);
+ if (intr)
+ goto skipped;
+ }
+
+ r = poll(ufds, nfds, pending_ms);
+
+ if(data->set.finterrupt) {
+ int sockerrno = SOCKERRNO;
+ intr = data->set.finterrupt(data->set.interrupt_client);
+ SET_SOCKERRNO(sockerrno);
+ }
+
+ /* Mimic return from ppoll() */
+ if (intr && r != -1) {
+skipped:
+ r = -1;
+ SET_SOCKERRNO(EINTR);
+ }
+ }
+
     if(r != -1)
       break;
     error = SOCKERRNO;
- if((error == EINVAL) || error_is_EINTR)
+
+ if(error != EINTR)
       break;
+#ifdef CURL_ACKNOWLEDGE_EINTR
+ break;
+#endif
+ if (intr) {
+ r = -2;
+ break;
+ }
+
     if(timeout_ms > 0) {
       pending_ms = timeout_ms - elapsed_ms;
       if(pending_ms <= 0)
@@ -426,6 +577,7 @@ int Curl_poll(struct pollfd ufds[], unsi
   ptimeout = (timeout_ms < 0) ? NULL : &pending_tv;
 
   do {
+ int intr = 0;
     if(timeout_ms > 0) {
       pending_tv.tv_sec = pending_ms / 1000;
       pending_tv.tv_usec = (pending_ms % 1000) * 1000;
@@ -434,12 +586,51 @@ int Curl_poll(struct pollfd ufds[], unsi
       pending_tv.tv_sec = 0;
       pending_tv.tv_usec = 0;
     }
- r = select((int)maxfd + 1, &fds_read, &fds_write, &fds_err, ptimeout);
+
+ if(! timeout_ms)
+ /* We do not arrange to interrupt anything here, as the caller is
+ only doing non-blocking checks during setup or something. */
+ r = select((int)maxfd + 1, &fds_read, &fds_write, &fds_err, ptimeout);
+ else {
+ /* Extra check because we're not blocking signals. After that
+ and before the call to select(), there is a race condition.
+ TODO: block signals and use pselect() */
+ if(data->set.finterrupt) {
+ intr = data->set.finterrupt(data->set.interrupt_client);
+ if (intr)
+ goto skipped;
+ }
+
+ r = select((int)maxfd + 1, &fds_read, &fds_write, &fds_err, ptimeout);
+
+ if(data->set.finterrupt) {
+ int sockerrno = SOCKERRNO;
+ intr = data->set.finterrupt(data->set.interrupt_client);
+ SET_SOCKERRNO(sockerrno);
+ }
+
+ /* Mimic return from pselect() */
+ if (intr && r != -1) {
+skipped:
+ r = -1;
+ SET_SOCKERRNO(EINTR);
+ }
+ }
+
     if(r != -1)
       break;
     error = SOCKERRNO;
- if((error == EINVAL) || (error == EBADF) || error_is_EINTR)
+
+ if(error != EINTR)
+ break;
+#ifdef CURL_ACKNOWLEDGE_EINTR
+ break;
+#endif
+ if (intr) {
+ r = -2;
       break;
+ }
+
     if(timeout_ms > 0) {
       pending_ms = timeout_ms - elapsed_ms;
       if(pending_ms <= 0)
@@ -447,10 +638,8 @@ int Curl_poll(struct pollfd ufds[], unsi
     }
   } while(r == -1);
 
- if(r < 0)
- return -1;
- if(r == 0)
- return 0;
+ if(r <= 0)
+ return r;
 
   r = 0;
   for (i = 0; i < nfds; i++) {
diff -urNp curl-7.18.0-orig/lib/select.h curl-7.18.0/lib/select.h
--- curl-7.18.0-orig/lib/select.h 2008-01-23 23:20:22.000000000 +0100
+++ curl-7.18.0/lib/select.h 2008-02-28 17:36:26.000000000 +0100
@@ -83,10 +83,11 @@ struct pollfd
 #define POLLRDBAND POLLPRI
 #endif
 
-int Curl_socket_ready(curl_socket_t readfd, curl_socket_t writefd,
- int timeout_ms);
+int Curl_socket_ready(struct connectdata *conn, curl_socket_t readfd,
+ curl_socket_t writefd, int timeout_ms);
 
-int Curl_poll(struct pollfd ufds[], unsigned int nfds, int timeout_ms);
+int Curl_poll(struct connectdata *conn, struct pollfd ufds[],
+ unsigned int nfds, int timeout_ms);
 
 #ifdef TPF
 int tpf_select_libcurl(int maxfds, fd_set* reads, fd_set* writes,
diff -urNp curl-7.18.0-orig/lib/socks.c curl-7.18.0/lib/socks.c
--- curl-7.18.0-orig/lib/socks.c 2008-01-22 00:47:28.000000000 +0100
+++ curl-7.18.0/lib/socks.c 2008-02-28 17:36:26.000000000 +0100
@@ -83,7 +83,7 @@ static int blockread_all(struct connectd
       result = ~CURLE_OK;
       break;
     }
- if(Curl_socket_ready(sockfd, CURL_SOCKET_BAD,
+ if(Curl_socket_ready(conn, sockfd, CURL_SOCKET_BAD,
                    (int)(conn_timeout - conntime)) <= 0) {
       result = ~CURLE_OK;
       break;
@@ -419,12 +419,16 @@ CURLcode Curl_SOCKS5(const char *proxy_n
   Curl_nonblock(sock, TRUE);
 
   /* wait until socket gets connected */
- result = Curl_socket_ready(CURL_SOCKET_BAD, sock, (int)timeout);
+ result = Curl_socket_ready(conn, CURL_SOCKET_BAD, sock, (int)timeout);
 
   if(-1 == result) {
     failf(conn->data, "SOCKS5: no connection here");
     return CURLE_COULDNT_CONNECT;
   }
+ else if(-2 == result) {
+ infof(conn->data, "SOCKS5: signal received, aborting connection");
+ return CURLE_ABORTED_BY_CALLBACK;
+ }
   else if(0 == result) {
     failf(conn->data, "SOCKS5: connection timeout");
     return CURLE_OPERATION_TIMEDOUT;
@@ -451,12 +455,16 @@ CURLcode Curl_SOCKS5(const char *proxy_n
 
   Curl_nonblock(sock, TRUE);
 
- result = Curl_socket_ready(sock, CURL_SOCKET_BAD, (int)timeout);
+ result = Curl_socket_ready(conn, sock, CURL_SOCKET_BAD, (int)timeout);
 
   if(-1 == result) {
     failf(conn->data, "SOCKS5 nothing to read");
     return CURLE_COULDNT_CONNECT;
   }
+ else if(-2 == result) {
+ infof(conn->data, "SOCKS5 signal received, aborting");
+ return CURLE_ABORTED_BY_CALLBACK;
+ }
   else if(0 == result) {
     failf(conn->data, "SOCKS5 read timeout");
     return CURLE_OPERATION_TIMEDOUT;
diff -urNp curl-7.18.0-orig/lib/ssluse.c curl-7.18.0/lib/ssluse.c
--- curl-7.18.0-orig/lib/ssluse.c 2008-01-27 23:41:56.000000000 +0100
+++ curl-7.18.0/lib/ssluse.c 2008-02-28 17:36:26.000000000 +0100
@@ -743,7 +743,7 @@ int Curl_ossl_shutdown(struct connectdat
 
   if(connssl->handle) {
     while(!done) {
- int what = Curl_socket_ready(conn->sock[sockindex],
+ int what = Curl_socket_ready(conn, conn->sock[sockindex],
                              CURL_SOCKET_BAD, SSL_SHUTDOWN_TIMEOUT);
       if(what > 0) {
         /* Something to read, let's do it and hope that it is the close
@@ -784,6 +784,11 @@ int Curl_ossl_shutdown(struct connectdat
         done = 1;
         break;
       }
+ else if(-2 == what) {
+ infof(data, "signal received, aborting SSL shutdown");
+ retval = -1;
+ done = 1;
+ }
       else {
         /* anything that gets here is fatally bad */
         failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO);
@@ -1756,7 +1761,7 @@ ossl_connect_common(struct connectdata *
         connssl->connecting_state?sockfd:CURL_SOCKET_BAD;
 
       while(1) {
- int what = Curl_socket_ready(readfd, writefd, nonblocking?0:(int)timeout_ms);
+ int what = Curl_socket_ready(conn, readfd, writefd, nonblocking?0:(int)timeout_ms);
         if(what > 0)
           /* readable or writable, go loop in the outer loop */
           break;
@@ -1771,6 +1776,10 @@ ossl_connect_common(struct connectdata *
             return CURLE_OPERATION_TIMEDOUT;
           }
         }
+ else if(-2 == what) {
+ infof(data, "signal received, SSL connection aborting");
+ return CURLE_ABORTED_BY_CALLBACK;
+ }
         else {
           /* anything that gets here is fatally bad */
           failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO);
diff -urNp curl-7.18.0-orig/lib/telnet.c curl-7.18.0/lib/telnet.c
--- curl-7.18.0-orig/lib/telnet.c 2007-12-09 00:03:52.000000000 +0100
+++ curl-7.18.0/lib/telnet.c 2008-02-28 17:36:26.000000000 +0100
@@ -1381,8 +1381,9 @@ static CURLcode telnet_do(struct connect
   interval_ms = 1 * 1000;
 
   while(keepon) {
- switch (Curl_poll(pfd, 2, interval_ms)) {
+ switch (Curl_poll(conn, pfd, 2, interval_ms)) {
     case -1: /* error, stop reading */
+ case -2: /* stopped by signal handling */
       keepon = FALSE;
       continue;
     case 0: /* timeout */
diff -urNp curl-7.18.0-orig/lib/tftp.c curl-7.18.0/lib/tftp.c
--- curl-7.18.0-orig/lib/tftp.c 2008-01-22 00:47:28.000000000 +0100
+++ curl-7.18.0/lib/tftp.c 2008-02-28 17:36:26.000000000 +0100
@@ -728,10 +728,10 @@ static CURLcode tftp_do(struct connectda
       code=tftp_state_machine(state, event) ) {
 
     /* Wait until ready to read or timeout occurs */
- rc=Curl_socket_ready(state->sockfd, CURL_SOCKET_BAD,
+ rc=Curl_socket_ready(conn, state->sockfd, CURL_SOCKET_BAD,
                          state->retry_time * 1000);
 
- if(rc == -1) {
+ if(rc == -1 || rc == -2) {
       /* bail out */
       int error = SOCKERRNO;
       failf(data, "%s", Curl_strerror(conn, error));
diff -urNp curl-7.18.0-orig/lib/transfer.c curl-7.18.0/lib/transfer.c
--- curl-7.18.0-orig/lib/transfer.c 2008-01-16 13:24:00.000000000 +0100
+++ curl-7.18.0/lib/transfer.c 2008-02-28 17:36:26.000000000 +0100
@@ -363,7 +363,7 @@ CURLcode Curl_readwrite(struct connectda
 
    if(!select_res) { /* Call for select()/poll() only, if read/write/error
                          status is not known. */
- select_res = Curl_socket_ready(fd_read, fd_write, 0);
+ select_res = Curl_socket_ready(conn, fd_read, fd_write, 0);
    }
 
   if(select_res == CURL_CSELECT_ERR) {
@@ -1793,17 +1793,13 @@ Transfer(struct connectdata *conn)
        must make sure that this function doesn't transfer anything while in
        HOLD status. */
 
- switch (Curl_socket_ready(fd_read, fd_write, 1000)) {
+ switch (Curl_socket_ready(conn, fd_read, fd_write, 1000)) {
     case -1: /* select() error, stop reading */
-#ifdef EINTR
- /* The EINTR is not serious, and it seems you might get this more
- ofen when using the lib in a multi-threaded environment! */
- if(SOCKERRNO == EINTR)
- ;
- else
-#endif
- done = TRUE; /* no more read or write */
+ done = TRUE; /* no more read or write */
       continue;
+ case -2: /* stopped by signal handling */
+ result = CURLE_ABORTED_BY_CALLBACK;
+ break;
     case 0: /* timeout */
     default: /* readable descriptors */
 
diff -urNp curl-7.18.0-orig/lib/url.c curl-7.18.0/lib/url.c
--- curl-7.18.0-orig/lib/url.c 2008-01-27 23:41:56.000000000 +0100
+++ curl-7.18.0/lib/url.c 2008-02-28 17:36:26.000000000 +0100
@@ -1996,6 +1996,21 @@ CURLcode Curl_setopt(struct SessionHandl
     data->set.opensocket_client = va_arg(param, void *);
     break;
 
+ case CURLOPT_INTERRUPTFUNCTION:
+ /*
+ * interrupt callback function: called after poll() or select() to
+ * check if it is needed to abort after receving a signal
+ */
+ data->set.finterrupt = va_arg(param, curl_interrupt_callback);
+ break;
+
+ case CURLOPT_INTERRUPTDATA:
+ /*
+ * interrupt callback data pointer. Might be NULL.
+ */
+ data->set.interrupt_client = va_arg(param, void *);
+ break;
+
   case CURLOPT_SSL_SESSIONID_CACHE:
     data->set.ssl.sessionid = (bool)(0 != va_arg(param, long));
     break;
@@ -2213,12 +2228,12 @@ CURLcode Curl_disconnect(struct connectd
  * be dead. Most commonly this happens when the server has closed the
  * connection due to inactivity.
  */
-static bool SocketIsDead(curl_socket_t sock)
+static bool SocketIsDead(struct connectdata *conn, curl_socket_t sock)
 {
   int sval;
   bool ret_val = TRUE;
 
- sval = Curl_socket_ready(sock, CURL_SOCKET_BAD, 0);
+ sval = Curl_socket_ready(conn, sock, CURL_SOCKET_BAD, 0);
   if(sval == 0)
     /* timeout */
     ret_val = FALSE;
@@ -2480,7 +2495,7 @@ ConnectionExists(struct SessionHandle *d
       if(!check->is_in_pipeline) {
         /* The check for a dead socket makes sense only in the
            non-pipelining case */
- bool dead = SocketIsDead(check->sock[FIRSTSOCKET]);
+ bool dead = SocketIsDead(needle, check->sock[FIRSTSOCKET]);
         if(dead) {
           check->data = data;
           infof(data, "Connection #%d seems to be dead!\n", i);
diff -urNp curl-7.18.0-orig/lib/urldata.h curl-7.18.0/lib/urldata.h
--- curl-7.18.0-orig/lib/urldata.h 2008-01-27 23:41:56.000000000 +0100
+++ curl-7.18.0/lib/urldata.h 2008-02-28 17:36:26.000000000 +0100
@@ -1352,6 +1352,9 @@ struct UserDefined {
   curl_opensocket_callback fopensocket; /* function for checking/translating
                                            the address and opening the socket */
   void* opensocket_client;
+ curl_interrupt_callback finterrupt; /* function for handling interruptions
+ by signals */
+ void *interrupt_client; /* pointer to pass to the interrupt callback */
 
   void *seek_client; /* pointer to pass to the seek callback */
   /* the 3 curl_conv_callback functions below are used on non-ASCII hosts */

-- 
Pierre Ynard
"Une âme dans un corps, c'est comme un dessin sur une feuille de papier."
Received on 2008-03-03