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