c2e87fcc561d8bc9ac0b288fbe2a6a2608e0bd06
[platform/upstream/curl.git] / docs / examples / evhiperfifo.c
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 1998 - 2012, 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
30 Requires libev and a (POSIX?) system that has mkfifo().
31
32 This is an adaptation of libcurl's "hipev.c" and libevent's "event-test.c"
33 sample programs.
34
35 When running, the program creates the named pipe "hiper.fifo"
36
37 Whenever there is input into the fifo, the program reads the input as a list
38 of URL's and creates some new easy handles to fetch each URL via the
39 curl_multi "hiper" API.
40
41
42 Thus, you can try a single URL:
43   % echo http://www.yahoo.com > hiper.fifo
44
45 Or a whole bunch of them:
46   % cat my-url-list > hiper.fifo
47
48 The fifo buffer is handled almost instantly, so you can even add more URL's
49 while the previous requests are still being downloaded.
50
51 Note:
52   For the sake of simplicity, URL length is limited to 1023 char's !
53
54 This is purely a demo app, all retrieved data is simply discarded by the write
55 callback.
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 */
78 typedef 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 */
90 typedef 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 */
100 typedef 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
111 static void timer_cb(EV_P_ struct ev_timer *w, int revents);
112
113 /* Update the event timer after curl_multi library calls */
114 static 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 */
129 static 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 */
158 static 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 */
186 static 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 */
205 static 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 */
218 static 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 */
232 static 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 */
252 static 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 */
262 static 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 */
296 static 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 */
307 static 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 */
320 static 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 */
359 static 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 */
379 static 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
415 int 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 }