1/*************************************************************************** 2 * _ _ ____ _ 3 * Project ___| | | | _ \| | 4 * / __| | | | |_) | | 5 * | (__| |_| | _ <| |___ 6 * \___|\___/|_| \_\_____| 7 * 8 * Copyright (C) 1998 - 2011, Daniel Stenberg, <daniel@haxx.se>, et al. 9 * 10 * This software is licensed as described in the file COPYING, which 11 * you should have received as part of this distribution. The terms 12 * are also available at http://curl.haxx.se/docs/copyright.html. 13 * 14 * You may opt to use, copy, modify, merge, publish, distribute and/or sell 15 * copies of the Software, and permit persons to whom the Software is 16 * furnished to do so, under the terms of the COPYING file. 17 * 18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 19 * KIND, either express or implied. 20 * 21 ***************************************************************************/ 22/* Example application source code using the multi socket interface to 23 * download many files at once. 24 * 25 * This example features the same basic functionality as hiperfifo.c does, 26 * but this uses libev instead of libevent. 27 * 28 * Written by Jeff Pohlmeyer, converted to use libev by Markus Koetter 29 30Requires libev and a (POSIX?) system that has mkfifo(). 31 32This is an adaptation of libcurl's "hipev.c" and libevent's "event-test.c" 33sample programs. 34 35When running, the program creates the named pipe "hiper.fifo" 36 37Whenever there is input into the fifo, the program reads the input as a list 38of URL's and creates some new easy handles to fetch each URL via the 39curl_multi "hiper" API. 40 41 42Thus, you can try a single URL: 43 % echo http://www.yahoo.com > hiper.fifo 44 45Or a whole bunch of them: 46 % cat my-url-list > hiper.fifo 47 48The fifo buffer is handled almost instantly, so you can even add more URL's 49while the previous requests are still being downloaded. 50 51Note: 52 For the sake of simplicity, URL length is limited to 1023 char's ! 53 54This is purely a demo app, all retrieved data is simply discarded by the write 55callback. 56 57*/ 58 59#include <stdio.h> 60#include <string.h> 61#include <stdlib.h> 62#include <sys/time.h> 63#include <time.h> 64#include <unistd.h> 65#include <sys/poll.h> 66#include <curl/curl.h> 67#include <ev.h> 68#include <fcntl.h> 69#include <sys/stat.h> 70#include <errno.h> 71 72#define DPRINT(x...) printf(x) 73 74#define MSG_OUT stdout /* Send info to stdout, change to stderr if you want */ 75 76 77/* Global information, common to all connections */ 78typedef struct _GlobalInfo 79{ 80 struct ev_loop *loop; 81 struct ev_io fifo_event; 82 struct ev_timer timer_event; 83 CURLM *multi; 84 int still_running; 85 FILE* input; 86} GlobalInfo; 87 88 89/* Information associated with a specific easy handle */ 90typedef struct _ConnInfo 91{ 92 CURL *easy; 93 char *url; 94 GlobalInfo *global; 95 char error[CURL_ERROR_SIZE]; 96} ConnInfo; 97 98 99/* Information associated with a specific socket */ 100typedef struct _SockInfo 101{ 102 curl_socket_t sockfd; 103 CURL *easy; 104 int action; 105 long timeout; 106 struct ev_io ev; 107 int evset; 108 GlobalInfo *global; 109} SockInfo; 110 111static void timer_cb(EV_P_ struct ev_timer *w, int revents); 112 113/* Update the event timer after curl_multi library calls */ 114static int multi_timer_cb(CURLM *multi, long timeout_ms, GlobalInfo *g) 115{ 116 DPRINT("%s %li\n", __PRETTY_FUNCTION__, timeout_ms); 117 ev_timer_stop(g->loop, &g->timer_event); 118 if (timeout_ms > 0) 119 { 120 double t = timeout_ms / 1000; 121 ev_timer_init(&g->timer_event, timer_cb, t, 0.); 122 ev_timer_start(g->loop, &g->timer_event); 123 }else 124 timer_cb(g->loop, &g->timer_event, 0); 125 return 0; 126} 127 128/* Die if we get a bad CURLMcode somewhere */ 129static void mcode_or_die(const char *where, CURLMcode code) 130{ 131 if ( CURLM_OK != code ) 132 { 133 const char *s; 134 switch ( code ) 135 { 136 case CURLM_CALL_MULTI_PERFORM: s="CURLM_CALL_MULTI_PERFORM"; break; 137 case CURLM_BAD_HANDLE: s="CURLM_BAD_HANDLE"; break; 138 case CURLM_BAD_EASY_HANDLE: s="CURLM_BAD_EASY_HANDLE"; break; 139 case CURLM_OUT_OF_MEMORY: s="CURLM_OUT_OF_MEMORY"; break; 140 case CURLM_INTERNAL_ERROR: s="CURLM_INTERNAL_ERROR"; break; 141 case CURLM_UNKNOWN_OPTION: s="CURLM_UNKNOWN_OPTION"; break; 142 case CURLM_LAST: s="CURLM_LAST"; break; 143 default: s="CURLM_unknown"; 144 break; 145 case CURLM_BAD_SOCKET: s="CURLM_BAD_SOCKET"; 146 fprintf(MSG_OUT, "ERROR: %s returns %s\n", where, s); 147 /* ignore this error */ 148 return; 149 } 150 fprintf(MSG_OUT, "ERROR: %s returns %s\n", where, s); 151 exit(code); 152 } 153} 154 155 156 157/* Check for completed transfers, and remove their easy handles */ 158static void check_multi_info(GlobalInfo *g) 159{ 160 char *eff_url; 161 CURLMsg *msg; 162 int msgs_left; 163 ConnInfo *conn; 164 CURL *easy; 165 CURLcode res; 166 167 fprintf(MSG_OUT, "REMAINING: %d\n", g->still_running); 168 while ((msg = curl_multi_info_read(g->multi, &msgs_left))) { 169 if (msg->msg == CURLMSG_DONE) { 170 easy = msg->easy_handle; 171 res = msg->data.result; 172 curl_easy_getinfo(easy, CURLINFO_PRIVATE, &conn); 173 curl_easy_getinfo(easy, CURLINFO_EFFECTIVE_URL, &eff_url); 174 fprintf(MSG_OUT, "DONE: %s => (%d) %s\n", eff_url, res, conn->error); 175 curl_multi_remove_handle(g->multi, easy); 176 free(conn->url); 177 curl_easy_cleanup(easy); 178 free(conn); 179 } 180 } 181} 182 183 184 185/* Called by libevent when we get action on a multi socket */ 186static void event_cb(EV_P_ struct ev_io *w, int revents) 187{ 188 DPRINT("%s w %p revents %i\n", __PRETTY_FUNCTION__, w, revents); 189 GlobalInfo *g = (GlobalInfo*) w->data; 190 CURLMcode rc; 191 192 int action = (revents&EV_READ?CURL_POLL_IN:0)| 193 (revents&EV_WRITE?CURL_POLL_OUT:0); 194 rc = curl_multi_socket_action(g->multi, w->fd, action, &g->still_running); 195 mcode_or_die("event_cb: curl_multi_socket_action", rc); 196 check_multi_info(g); 197 if ( g->still_running <= 0 ) 198 { 199 fprintf(MSG_OUT, "last transfer done, kill timeout\n"); 200 ev_timer_stop(g->loop, &g->timer_event); 201 } 202} 203 204/* Called by libevent when our timeout expires */ 205static void timer_cb(EV_P_ struct ev_timer *w, int revents) 206{ 207 DPRINT("%s w %p revents %i\n", __PRETTY_FUNCTION__, w, revents); 208 209 GlobalInfo *g = (GlobalInfo *)w->data; 210 CURLMcode rc; 211 212 rc = curl_multi_socket_action(g->multi, CURL_SOCKET_TIMEOUT, 0, &g->still_running); 213 mcode_or_die("timer_cb: curl_multi_socket_action", rc); 214 check_multi_info(g); 215} 216 217/* Clean up the SockInfo structure */ 218static void remsock(SockInfo *f, GlobalInfo *g) 219{ 220 printf("%s \n", __PRETTY_FUNCTION__); 221 if ( f ) 222 { 223 if ( f->evset ) 224 ev_io_stop(g->loop, &f->ev); 225 free(f); 226 } 227} 228 229 230 231/* Assign information to a SockInfo structure */ 232static void setsock(SockInfo*f, curl_socket_t s, CURL*e, int act, GlobalInfo*g) 233{ 234 printf("%s \n", __PRETTY_FUNCTION__); 235 236 int kind = (act&CURL_POLL_IN?EV_READ:0)|(act&CURL_POLL_OUT?EV_WRITE:0); 237 238 f->sockfd = s; 239 f->action = act; 240 f->easy = e; 241 if ( f->evset ) 242 ev_io_stop(g->loop, &f->ev); 243 ev_io_init(&f->ev, event_cb, f->sockfd, kind); 244 f->ev.data = g; 245 f->evset=1; 246 ev_io_start(g->loop, &f->ev); 247} 248 249 250 251/* Initialize a new SockInfo structure */ 252static void addsock(curl_socket_t s, CURL *easy, int action, GlobalInfo *g) 253{ 254 SockInfo *fdp = calloc(sizeof(SockInfo), 1); 255 256 fdp->global = g; 257 setsock(fdp, s, easy, action, g); 258 curl_multi_assign(g->multi, s, fdp); 259} 260 261/* CURLMOPT_SOCKETFUNCTION */ 262static int sock_cb(CURL *e, curl_socket_t s, int what, void *cbp, void *sockp) 263{ 264 DPRINT("%s e %p s %i what %i cbp %p sockp %p\n", 265 __PRETTY_FUNCTION__, e, s, what, cbp, sockp); 266 267 GlobalInfo *g = (GlobalInfo*) cbp; 268 SockInfo *fdp = (SockInfo*) sockp; 269 const char *whatstr[]={ "none", "IN", "OUT", "INOUT", "REMOVE"}; 270 271 fprintf(MSG_OUT, 272 "socket callback: s=%d e=%p what=%s ", s, e, whatstr[what]); 273 if ( what == CURL_POLL_REMOVE ) 274 { 275 fprintf(MSG_OUT, "\n"); 276 remsock(fdp, g); 277 } else 278 { 279 if ( !fdp ) 280 { 281 fprintf(MSG_OUT, "Adding data: %s\n", whatstr[what]); 282 addsock(s, e, what, g); 283 } else 284 { 285 fprintf(MSG_OUT, 286 "Changing action from %s to %s\n", 287 whatstr[fdp->action], whatstr[what]); 288 setsock(fdp, s, e, what, g); 289 } 290 } 291 return 0; 292} 293 294 295/* CURLOPT_WRITEFUNCTION */ 296static size_t write_cb(void *ptr, size_t size, size_t nmemb, void *data) 297{ 298 size_t realsize = size * nmemb; 299 ConnInfo *conn = (ConnInfo*) data; 300 (void)ptr; 301 (void)conn; 302 return realsize; 303} 304 305 306/* CURLOPT_PROGRESSFUNCTION */ 307static int prog_cb (void *p, double dltotal, double dlnow, double ult, 308 double uln) 309{ 310 ConnInfo *conn = (ConnInfo *)p; 311 (void)ult; 312 (void)uln; 313 314 fprintf(MSG_OUT, "Progress: %s (%g/%g)\n", conn->url, dlnow, dltotal); 315 return 0; 316} 317 318 319/* Create a new easy handle, and add it to the global curl_multi */ 320static void new_conn(char *url, GlobalInfo *g ) 321{ 322 ConnInfo *conn; 323 CURLMcode rc; 324 325 conn = calloc(1, sizeof(ConnInfo)); 326 memset(conn, 0, sizeof(ConnInfo)); 327 conn->error[0]='\0'; 328 329 conn->easy = curl_easy_init(); 330 if ( !conn->easy ) 331 { 332 fprintf(MSG_OUT, "curl_easy_init() failed, exiting!\n"); 333 exit(2); 334 } 335 conn->global = g; 336 conn->url = strdup(url); 337 curl_easy_setopt(conn->easy, CURLOPT_URL, conn->url); 338 curl_easy_setopt(conn->easy, CURLOPT_WRITEFUNCTION, write_cb); 339 curl_easy_setopt(conn->easy, CURLOPT_WRITEDATA, &conn); 340 curl_easy_setopt(conn->easy, CURLOPT_VERBOSE, 1L); 341 curl_easy_setopt(conn->easy, CURLOPT_ERRORBUFFER, conn->error); 342 curl_easy_setopt(conn->easy, CURLOPT_PRIVATE, conn); 343 curl_easy_setopt(conn->easy, CURLOPT_NOPROGRESS, 0L); 344 curl_easy_setopt(conn->easy, CURLOPT_PROGRESSFUNCTION, prog_cb); 345 curl_easy_setopt(conn->easy, CURLOPT_PROGRESSDATA, conn); 346 curl_easy_setopt(conn->easy, CURLOPT_LOW_SPEED_TIME, 3L); 347 curl_easy_setopt(conn->easy, CURLOPT_LOW_SPEED_LIMIT, 10L); 348 349 fprintf(MSG_OUT, 350 "Adding easy %p to multi %p (%s)\n", conn->easy, g->multi, url); 351 rc = curl_multi_add_handle(g->multi, conn->easy); 352 mcode_or_die("new_conn: curl_multi_add_handle", rc); 353 354 /* note that the add_handle() will set a time-out to trigger very soon so 355 that the necessary socket_action() call will be called by this app */ 356} 357 358/* This gets called whenever data is received from the fifo */ 359static void fifo_cb(EV_P_ struct ev_io *w, int revents) 360{ 361 char s[1024]; 362 long int rv=0; 363 int n=0; 364 GlobalInfo *g = (GlobalInfo *)w->data; 365 366 do 367 { 368 s[0]='\0'; 369 rv=fscanf(g->input, "%1023s%n", s, &n); 370 s[n]='\0'; 371 if ( n && s[0] ) 372 { 373 new_conn(s,g); /* if we read a URL, go get it! */ 374 } else break; 375 } while ( rv != EOF ); 376} 377 378/* Create a named pipe and tell libevent to monitor it */ 379static int init_fifo (GlobalInfo *g) 380{ 381 struct stat st; 382 static const char *fifo = "hiper.fifo"; 383 curl_socket_t sockfd; 384 385 fprintf(MSG_OUT, "Creating named pipe \"%s\"\n", fifo); 386 if ( lstat (fifo, &st) == 0 ) 387 { 388 if ( (st.st_mode & S_IFMT) == S_IFREG ) 389 { 390 errno = EEXIST; 391 perror("lstat"); 392 exit (1); 393 } 394 } 395 unlink(fifo); 396 if ( mkfifo (fifo, 0600) == -1 ) 397 { 398 perror("mkfifo"); 399 exit (1); 400 } 401 sockfd = open(fifo, O_RDWR | O_NONBLOCK, 0); 402 if ( sockfd == -1 ) 403 { 404 perror("open"); 405 exit (1); 406 } 407 g->input = fdopen(sockfd, "r"); 408 409 fprintf(MSG_OUT, "Now, pipe some URL's into > %s\n", fifo); 410 ev_io_init(&g->fifo_event, fifo_cb, sockfd, EV_READ); 411 ev_io_start(g->loop, &g->fifo_event); 412 return(0); 413} 414 415int main(int argc, char **argv) 416{ 417 GlobalInfo g; 418 CURLMcode rc; 419 (void)argc; 420 (void)argv; 421 422 memset(&g, 0, sizeof(GlobalInfo)); 423 g.loop = ev_default_loop(0); 424 425 init_fifo(&g); 426 g.multi = curl_multi_init(); 427 428 ev_timer_init(&g.timer_event, timer_cb, 0., 0.); 429 g.timer_event.data = &g; 430 g.fifo_event.data = &g; 431 curl_multi_setopt(g.multi, CURLMOPT_SOCKETFUNCTION, sock_cb); 432 curl_multi_setopt(g.multi, CURLMOPT_SOCKETDATA, &g); 433 curl_multi_setopt(g.multi, CURLMOPT_TIMERFUNCTION, multi_timer_cb); 434 curl_multi_setopt(g.multi, CURLMOPT_TIMERDATA, &g); 435 436 /* we don't call any curl_multi_socket*() function yet as we have no handles 437 added! */ 438 439 ev_loop(g.loop, 0); 440 curl_multi_cleanup(g.multi); 441 return 0; 442} 443