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