<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[118713] trunk/base/src/pextlib1.0/curl.c</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="https://trac.macports.org/changeset/118713">118713</a></dd>
<dt>Author</dt> <dd>cal@macports.org</dd>
<dt>Date</dt> <dd>2014-04-08 17:26:55 -0700 (Tue, 08 Apr 2014)</dd>
</dl>

<h3>Log Message</h3>
<pre>base: switch to cURL multi API for curl fetch and handle signals, #43280

This allows users to cancel fetch using SIGINT and SIGTERM (or any other
signal, really) where they'd previously have to wait for the curl operation to
finish completely.
With this change the progress finish callback is always called, even if an
error occurs, which was previously not the case.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkbasesrcpextlib10curlc">trunk/base/src/pextlib1.0/curl.c</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkbasesrcpextlib10curlc"></a>
<div class="modfile"><h4>Modified: trunk/base/src/pextlib1.0/curl.c (118712 => 118713)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/base/src/pextlib1.0/curl.c        2014-04-08 18:19:21 UTC (rev 118712)
+++ trunk/base/src/pextlib1.0/curl.c        2014-04-09 00:26:55 UTC (rev 118713)
</span><span class="lines">@@ -41,6 +41,7 @@
</span><span class="cx"> #include &lt;stdio.h&gt;
</span><span class="cx"> #include &lt;stdlib.h&gt;
</span><span class="cx"> #include &lt;string.h&gt;
</span><ins>+#include &lt;sys/select.h&gt;
</ins><span class="cx"> #include &lt;utime.h&gt;
</span><span class="cx"> 
</span><span class="cx"> #include &lt;curl/curl.h&gt;
</span><span class="lines">@@ -64,9 +65,15 @@
</span><span class="cx"> #pragma mark Definitions
</span><span class="cx"> 
</span><span class="cx"> /* ------------------------------------------------------------------------- **
</span><del>- * Global cURL handle
</del><ins>+ * Global cURL handles
</ins><span class="cx">  * ------------------------------------------------------------------------- */
</span><del>-/* we use a single global handle rather than creating and destroying handles to
</del><ins>+/* If we want to use TclX' signal handling mechanism we need cURL to return
+ * control to our code from time to time so we can call Tcl_AsyncInvoke to
+ * process pending signals. To do that, we could either abuse the curl progress
+ * callback (which would mean we could no longer use the default curl progress
+ * callback, or we need to use the cURL multi API. */
+static CURLM* theMHandle = NULL;
+/* We use a single global handle rather than creating and destroying handles to
</ins><span class="cx">  * take advantage of HTTP pipelining, especially to the packages servers. */
</span><span class="cx"> static CURL* theHandle = NULL;
</span><span class="cx"> 
</span><span class="lines">@@ -74,6 +81,7 @@
</span><span class="cx">  * Prototypes
</span><span class="cx">  * ------------------------------------------------------------------------- */
</span><span class="cx"> int SetResultFromCurlErrorCode(Tcl_Interp* interp, CURLcode inErrorCode);
</span><ins>+int SetResultFromCurlMErrorCode(Tcl_Interp* interp, CURLMcode inErrorCode);
</ins><span class="cx"> int CurlFetchCmd(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]);
</span><span class="cx"> int CurlIsNewerCmd(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]);
</span><span class="cx"> int CurlGetSizeCmd(Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]);
</span><span class="lines">@@ -120,6 +128,29 @@
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> /**
</span><ins>+ * Set the result if a libcurl multi error occurred return TCL_ERROR.
+ * Otherwise, set the result to &quot;&quot; and return TCL_OK.
+ *
+ * @param interp                pointer to the interpreter.
+ * @param inErrorCode        code of the multi error.
+ * @return TCL_OK if inErrorCode is 0, TCL_ERROR otherwise.
+ */
+int
+SetResultFromCurlMErrorCode(Tcl_Interp *interp, CURLMcode inErrorCode)
+{
+        int result = TCL_ERROR;
+
+        if (inErrorCode == CURLM_OK) {
+                Tcl_SetResult(interp, &quot;&quot;, TCL_STATIC);
+                result = TCL_OK;
+        } else {
+                Tcl_SetResult(interp, (char *)curl_multi_strerror(inErrorCode), TCL_VOLATILE);
+        }
+
+        return result;
+}
+
+/**
</ins><span class="cx">  * curl fetch subcommand entry point.
</span><span class="cx">  *
</span><span class="cx">  * syntax: curl fetch [--disable-epsv] [--ignore-ssl-cert] [--remote-time] [-u userpass] [--effective-url lasturlvar] [--progress &quot;builtin&quot;|callback] url filename
</span><span class="lines">@@ -133,7 +164,6 @@
</span><span class="cx"> {
</span><span class="cx">         int theResult = TCL_OK;
</span><span class="cx">         FILE* theFile = NULL;
</span><del>-        bool performFailed = false;
</del><span class="cx">         char theErrorString[CURL_ERROR_SIZE];
</span><span class="cx"> 
</span><span class="cx">         do {
</span><span class="lines">@@ -156,7 +186,10 @@
</span><span class="cx">                 const char* theFilePath;
</span><span class="cx">                 long theFileTime = 0;
</span><span class="cx">                 CURLcode theCurlCode;
</span><ins>+                CURLMcode theCurlMCode;
</ins><span class="cx">                 struct curl_slist *headers = NULL;
</span><ins>+                struct CURLMsg *info = NULL;
+                int running; /* number of running transfers */
</ins><span class="cx"> 
</span><span class="cx">                 /* we might have options and then the url and the file */
</span><span class="cx">                 /* let's process the options first */
</span><span class="lines">@@ -260,10 +293,25 @@
</span><span class="cx">                         break;
</span><span class="cx">                 }
</span><span class="cx"> 
</span><del>-                /* Create the CURL handle */
</del><ins>+                /* Create the CURL handles */
+                if (theMHandle == NULL) {
+                        /* Re-use existing multi handle if theMHandle isn't NULL */
+                        theMHandle = curl_multi_init();
+                        if (theMHandle == NULL) {
+                                theResult = TCL_ERROR;
+                                Tcl_SetResult(interp, &quot;error in curl_multi_init&quot;, TCL_STATIC);
+                                break;
+                        }
+                }
+
</ins><span class="cx">                 if (theHandle == NULL) {
</span><span class="cx">                         /* Re-use existing handle if theHandle isn't NULL */
</span><span class="cx">                         theHandle = curl_easy_init();
</span><ins>+                        if (theHandle == NULL) {
+                                theResult = TCL_ERROR;
+                                Tcl_SetResult(interp, &quot;error in curl_easy_init&quot;, TCL_STATIC);
+                                break;
+                        }
</ins><span class="cx">                 }
</span><span class="cx">                 /* If we're re-using a handle, the previous call did ensure to reset it
</span><span class="cx">                  * to the default state using curl_easy_reset(3) */
</span><span class="lines">@@ -432,18 +480,119 @@
</span><span class="cx">                         break;
</span><span class="cx">                 }
</span><span class="cx"> 
</span><del>-                /* actually fetch the resource */
-                theCurlCode = curl_easy_perform(theHandle);
-                if (theCurlCode != CURLE_OK) {
-                        performFailed = true;
</del><ins>+                /* add the easy handle to the multi handle */
+                theCurlMCode = curl_multi_add_handle(theMHandle, theHandle);
+                if (theCurlMCode != CURLM_OK) {
+                        theResult = SetResultFromCurlMErrorCode(interp, theCurlMCode);
</ins><span class="cx">                         break;
</span><span class="cx">                 }
</span><span class="cx"> 
</span><ins>+                /* select(2) the file descriptors used by curl and interleave with
+                 * checks for TclX signals */
+                do {
+                        int rc; /* select() return code */
+
+                        /* arguments for select(2) */
+                        int nfds;
+                        fd_set readfds;
+                        fd_set writefds;
+                        fd_set errorfds;
+                        struct timeval timeout;
+
+                        long curl_timeout = -1;
+
+                        /* use at most half a second as timeout */
+                        timeout.tv_sec = 0;
+                        timeout.tv_usec = 500 * 1000;
+
+                        /* get the next timeout */
+                        theCurlMCode = curl_multi_timeout(theMHandle, &amp;curl_timeout);
+                        if (theCurlMCode != CURLM_OK) {
+                                theResult = SetResultFromCurlMErrorCode(interp, theCurlMCode);
+                                break;
+                        }
+
+                        /* convert the timeout into a suitable format for select(2) and
+                         * limit the timeout to 500 msecs at most */
+                        if (curl_timeout &gt;= 0 &amp;&amp; curl_timeout &lt; 500) {
+                                timeout.tv_usec = curl_timeout * 1000;
+                        } else {
+                                timeout.tv_usec = 500 * 1000;
+                        }
+
+                        /* get the fd sets for select(2) */
+                        theCurlMCode = curl_multi_fdset(theMHandle, &amp;readfds, &amp;writefds, &amp;errorfds, &amp;nfds);
+                        if (theCurlMCode != CURLM_OK) {
+                                theResult = SetResultFromCurlMErrorCode(interp, theCurlMCode);
+                                break;
+                        }
+
+                        /* The value of nfds is guaranteed to be &gt;= -1. Passing nfds + 1 to
+                         * select(2) makes the case of nfds == -1 a sleep. */
+                        rc = select(nfds + 1, &amp;readfds, &amp;writefds, &amp;errorfds, &amp;timeout);
+                        if (-1 == rc) {
+                                /* select error */
+                                Tcl_SetResult(interp, strerror(errno), TCL_VOLATILE);
+                                theResult = TCL_ERROR;
+                                break;
+                        }
+
+                        /* timeout or activity */
+                        theCurlMCode = curl_multi_perform(theMHandle, &amp;running);
+
+                        /* process signals from TclX */
+                        if (Tcl_AsyncReady()) {
+                                theResult = Tcl_AsyncInvoke(interp, theResult);
+                                if (theResult != TCL_OK) {
+                                        break;
+                                }
+                        }
+                } while (running &gt; 0);
+
+                /* Find out whether the transfer succeeded or failed. */
+                info = curl_multi_info_read(theMHandle, &amp;running);
+                if (running &gt; 0) {
+                        fprintf(stderr, &quot;Warning: curl_multi_info_read has %d more structs available\n&quot;, running);
+                }
+
+                /* Remove the handle from the multi handle, but ignore errors to avoid
+                 * cluttering the real error info that might be somewhere further up */
+                curl_multi_remove_handle(theMHandle, theHandle);
+
+                /* free header memory */
+                curl_slist_free_all(headers);
+
</ins><span class="cx">                 /* signal cleanup to the progress callback */
</span><span class="cx">                 if (noprogress == 0 &amp;&amp; strcmp(progressCallback.proc, &quot;builtin&quot;) != 0) {
</span><span class="cx">                         CurlProgressCleanup(&amp;progressCallback);
</span><span class="cx">                 }
</span><span class="cx"> 
</span><ins>+                /* check for errors in the loop */
+                if (theResult != TCL_OK || theCurlMCode != CURLM_OK) {
+                        break;
+                }
+
+                /* we should always get CURLMSG_DONE unless we aborted due to a Tcl
+                 * signal */
+                if (info == NULL) {
+                        Tcl_SetResult(interp, &quot;curl_multi_info_read() returned NULL&quot;, TCL_STATIC);
+                        theResult = TCL_ERROR;
+                        break;
+                }
+                
+                if (info-&gt;msg != CURLMSG_DONE) {
+                        Tcl_SetResult(interp, &quot;curl_multi_info_read() returned an unexpected value&quot;, TCL_STATIC);
+                        theResult = TCL_ERROR;
+                        break;
+                }
+
+                if (info-&gt;data.result != CURLE_OK) {
+                        /* execution failed, use the error string */
+                        Tcl_SetResult(interp, theErrorString, TCL_VOLATILE);
+                        theResult = TCL_ERROR;
+                        break;
+                }
+
</ins><span class="cx">                 /* close the file */
</span><span class="cx">                 (void) fclose(theFile);
</span><span class="cx">                 theFile = NULL;
</span><span class="lines">@@ -497,23 +646,14 @@
</span><span class="cx">                         }
</span><span class="cx">                 }
</span><span class="cx"> 
</span><del>-                /* free header memory */
-                curl_slist_free_all(headers);
-
</del><span class="cx">                 /* If --effective-url option was given, set given variable name to last effective url used by curl */
</span><span class="cx">                 if (effectiveURLVarName != NULL) {
</span><span class="cx">                         theCurlCode = curl_easy_getinfo(theHandle, CURLINFO_EFFECTIVE_URL, &amp;effectiveURL);
</span><span class="cx">                         Tcl_SetVar(interp, effectiveURLVarName,
</span><del>-                                (effectiveURL == NULL || theCurlCode != CURLE_OK) ? &quot;&quot; : effectiveURL,
-                                0);
</del><ins>+                                (effectiveURL == NULL || theCurlCode != CURLE_OK) ? &quot;&quot; : effectiveURL, 0);
</ins><span class="cx">                 }
</span><span class="cx">         } while (0);
</span><span class="cx"> 
</span><del>-        if (performFailed) {
-                Tcl_SetResult(interp, theErrorString, TCL_VOLATILE);
-                theResult = TCL_ERROR;
-        }
-
</del><span class="cx">         /* reset the connection */
</span><span class="cx">         if (theHandle != NULL) {
</span><span class="cx">                 curl_easy_reset(theHandle);
</span><span class="lines">@@ -1187,16 +1327,17 @@
</span><span class="cx"> 
</span><span class="cx">                 /* actually perform the POST */
</span><span class="cx">                 theCurlCode = curl_easy_perform(theHandle);
</span><del>-                if (theCurlCode != CURLE_OK) {
-                        theResult = SetResultFromCurlErrorCode(interp, theCurlCode);
-                        break;
-                }
</del><span class="cx"> 
</span><span class="cx">                 /* signal cleanup to the progress callback */
</span><span class="cx">                 if (noprogress == 0 &amp;&amp; strcmp(progressCallback.proc, &quot;builtin&quot;) != 0) {
</span><span class="cx">                         CurlProgressCleanup(&amp;progressCallback);
</span><span class="cx">                 }
</span><span class="cx"> 
</span><ins>+                if (theCurlCode != CURLE_OK) {
+                        theResult = SetResultFromCurlErrorCode(interp, theCurlCode);
+                        break;
+                }
+
</ins><span class="cx">                 /* close the file */
</span><span class="cx">                 (void) fclose(theFile);
</span><span class="cx">                 theFile = NULL;
</span><span class="lines">@@ -1409,6 +1550,7 @@
</span><span class="cx">         /*
</span><span class="cx">          * Transfer complete, signal the progress callback
</span><span class="cx">          */
</span><ins>+        Tcl_InterpState state;
</ins><span class="cx"> 
</span><span class="cx">         /*
</span><span class="cx">          * Command string, a space followed &quot;finish&quot; plus the trailing \0.
</span><span class="lines">@@ -1423,5 +1565,9 @@
</span><span class="cx">                 abort();
</span><span class="cx">         }
</span><span class="cx"> 
</span><ins>+        /* make sure to save and restore the interpreter state so a potential error
+         * message doesn't get lost */
+        state = Tcl_SaveInterpState(callback-&gt;interp, 0);
</ins><span class="cx">         Tcl_EvalEx(callback-&gt;interp, commandBuffer, len, TCL_EVAL_GLOBAL);
</span><ins>+        Tcl_RestoreInterpState(callback-&gt;interp, state);
</ins><span class="cx"> }
</span></span></pre>
</div>
</div>

</body>
</html>