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 * Written by Jeff Pohlmeyer
26
27Requires glib-2.x and a (POSIX?) system that has mkfifo().
28
29This is an adaptation of libcurl's "hipev.c" and libevent's "event-test.c"
30sample programs, adapted to use glib's g_io_channel in place of libevent.
31
32When running, the program creates the named pipe "hiper.fifo"
33
34Whenever there is input into the fifo, the program reads the input as a list
35of URL's and creates some new easy handles to fetch each URL via the
36curl_multi "hiper" API.
37
38
39Thus, you can try a single URL:
40  % echo http://www.yahoo.com > hiper.fifo
41
42Or a whole bunch of them:
43  % cat my-url-list > hiper.fifo
44
45The fifo buffer is handled almost instantly, so you can even add more URL's
46while the previous requests are still being downloaded.
47
48This is purely a demo app, all retrieved data is simply discarded by the write
49callback.
50
51*/
52
53
54#include <glib.h>
55#include <sys/stat.h>
56#include <unistd.h>
57#include <fcntl.h>
58#include <stdlib.h>
59#include <stdio.h>
60#include <errno.h>
61#include <curl/curl.h>
62
63
64#define MSG_OUT g_print   /* Change to "g_error" to write to stderr */
65#define SHOW_VERBOSE 0    /* Set to non-zero for libcurl messages */
66#define SHOW_PROGRESS 0   /* Set to non-zero to enable progress callback */
67
68
69
70/* Global information, common to all connections */
71typedef struct _GlobalInfo {
72  CURLM *multi;
73  guint timer_event;
74  int still_running;
75} GlobalInfo;
76
77
78
79/* Information associated with a specific easy handle */
80typedef struct _ConnInfo {
81  CURL *easy;
82  char *url;
83  GlobalInfo *global;
84  char error[CURL_ERROR_SIZE];
85} ConnInfo;
86
87
88/* Information associated with a specific socket */
89typedef struct _SockInfo {
90  curl_socket_t sockfd;
91  CURL *easy;
92  int action;
93  long timeout;
94  GIOChannel *ch;
95  guint ev;
96  GlobalInfo *global;
97} SockInfo;
98
99
100
101
102/* Die if we get a bad CURLMcode somewhere */
103static void mcode_or_die(const char *where, CURLMcode code) {
104  if ( CURLM_OK != code ) {
105    const char *s;
106    switch (code) {
107      case     CURLM_CALL_MULTI_PERFORM: s="CURLM_CALL_MULTI_PERFORM"; break;
108      case     CURLM_BAD_HANDLE:         s="CURLM_BAD_HANDLE";         break;
109      case     CURLM_BAD_EASY_HANDLE:    s="CURLM_BAD_EASY_HANDLE";    break;
110      case     CURLM_OUT_OF_MEMORY:      s="CURLM_OUT_OF_MEMORY";      break;
111      case     CURLM_INTERNAL_ERROR:     s="CURLM_INTERNAL_ERROR";     break;
112      case     CURLM_BAD_SOCKET:         s="CURLM_BAD_SOCKET";         break;
113      case     CURLM_UNKNOWN_OPTION:     s="CURLM_UNKNOWN_OPTION";     break;
114      case     CURLM_LAST:               s="CURLM_LAST";               break;
115      default: s="CURLM_unknown";
116    }
117    MSG_OUT("ERROR: %s returns %s\n", where, s);
118    exit(code);
119  }
120}
121
122
123
124/* Check for completed transfers, and remove their easy handles */
125static void check_multi_info(GlobalInfo *g)
126{
127  char *eff_url;
128  CURLMsg *msg;
129  int msgs_left;
130  ConnInfo *conn;
131  CURL *easy;
132  CURLcode res;
133
134  MSG_OUT("REMAINING: %d\n", g->still_running);
135  while ((msg = curl_multi_info_read(g->multi, &msgs_left))) {
136    if (msg->msg == CURLMSG_DONE) {
137      easy = msg->easy_handle;
138      res = msg->data.result;
139      curl_easy_getinfo(easy, CURLINFO_PRIVATE, &conn);
140      curl_easy_getinfo(easy, CURLINFO_EFFECTIVE_URL, &eff_url);
141      MSG_OUT("DONE: %s => (%d) %s\n", eff_url, res, conn->error);
142      curl_multi_remove_handle(g->multi, easy);
143      free(conn->url);
144      curl_easy_cleanup(easy);
145      free(conn);
146    }
147  }
148}
149
150
151
152/* Called by glib when our timeout expires */
153static gboolean timer_cb(gpointer data)
154{
155  GlobalInfo *g = (GlobalInfo *)data;
156  CURLMcode rc;
157
158  rc = curl_multi_socket_action(g->multi,
159                                  CURL_SOCKET_TIMEOUT, 0, &g->still_running);
160  mcode_or_die("timer_cb: curl_multi_socket_action", rc);
161  check_multi_info(g);
162  return FALSE;
163}
164
165
166
167/* Update the event timer after curl_multi library calls */
168static int update_timeout_cb(CURLM *multi, long timeout_ms, void *userp)
169{
170  struct timeval timeout;
171  GlobalInfo *g=(GlobalInfo *)userp;
172  timeout.tv_sec = timeout_ms/1000;
173  timeout.tv_usec = (timeout_ms%1000)*1000;
174
175  MSG_OUT("*** update_timeout_cb %ld => %ld:%ld ***\n",
176              timeout_ms, timeout.tv_sec, timeout.tv_usec);
177
178  g->timer_event = g_timeout_add(timeout_ms, timer_cb, g);
179  return 0;
180}
181
182
183
184
185/* Called by glib when we get action on a multi socket */
186static gboolean event_cb(GIOChannel *ch, GIOCondition condition, gpointer data)
187{
188  GlobalInfo *g = (GlobalInfo*) data;
189  CURLMcode rc;
190  int fd=g_io_channel_unix_get_fd(ch);
191
192  int action =
193    (condition & G_IO_IN ? CURL_CSELECT_IN : 0) |
194    (condition & G_IO_OUT ? CURL_CSELECT_OUT : 0);
195
196  rc = curl_multi_socket_action(g->multi, fd, action, &g->still_running);
197  mcode_or_die("event_cb: curl_multi_socket_action", rc);
198
199  check_multi_info(g);
200  if(g->still_running) {
201    return TRUE;
202  } else {
203    MSG_OUT("last transfer done, kill timeout\n");
204    if (g->timer_event) { g_source_remove(g->timer_event); }
205    return FALSE;
206  }
207}
208
209
210
211/* Clean up the SockInfo structure */
212static void remsock(SockInfo *f)
213{
214  if (!f) { return; }
215  if (f->ev) { g_source_remove(f->ev); }
216  g_free(f);
217}
218
219
220
221/* Assign information to a SockInfo structure */
222static void setsock(SockInfo*f, curl_socket_t s, CURL*e, int act, GlobalInfo*g)
223{
224  GIOCondition kind =
225     (act&CURL_POLL_IN?G_IO_IN:0)|(act&CURL_POLL_OUT?G_IO_OUT:0);
226
227  f->sockfd = s;
228  f->action = act;
229  f->easy = e;
230  if (f->ev) { g_source_remove(f->ev); }
231  f->ev=g_io_add_watch(f->ch, kind, event_cb,g);
232
233}
234
235
236
237/* Initialize a new SockInfo structure */
238static void addsock(curl_socket_t s, CURL *easy, int action, GlobalInfo *g)
239{
240  SockInfo *fdp = g_malloc0(sizeof(SockInfo));
241
242  fdp->global = g;
243  fdp->ch=g_io_channel_unix_new(s);
244  setsock(fdp, s, easy, action, g);
245  curl_multi_assign(g->multi, s, fdp);
246}
247
248
249
250/* CURLMOPT_SOCKETFUNCTION */
251static int sock_cb(CURL *e, curl_socket_t s, int what, void *cbp, void *sockp)
252{
253  GlobalInfo *g = (GlobalInfo*) cbp;
254  SockInfo *fdp = (SockInfo*) sockp;
255  static const char *whatstr[]={ "none", "IN", "OUT", "INOUT", "REMOVE" };
256
257  MSG_OUT("socket callback: s=%d e=%p what=%s ", s, e, whatstr[what]);
258  if (what == CURL_POLL_REMOVE) {
259    MSG_OUT("\n");
260    remsock(fdp);
261  } else {
262    if (!fdp) {
263      MSG_OUT("Adding data: %s%s\n",
264             what&CURL_POLL_IN?"READ":"",
265             what&CURL_POLL_OUT?"WRITE":"" );
266      addsock(s, e, what, g);
267    }
268    else {
269      MSG_OUT(
270        "Changing action from %d to %d\n", fdp->action, what);
271      setsock(fdp, s, e, what, g);
272    }
273  }
274  return 0;
275}
276
277
278
279/* CURLOPT_WRITEFUNCTION */
280static size_t write_cb(void *ptr, size_t size, size_t nmemb, void *data)
281{
282  size_t realsize = size * nmemb;
283  ConnInfo *conn = (ConnInfo*) data;
284  (void)ptr;
285  (void)conn;
286  return realsize;
287}
288
289
290
291/* CURLOPT_PROGRESSFUNCTION */
292static int prog_cb (void *p, double dltotal, double dlnow, double ult, double uln)
293{
294  ConnInfo *conn = (ConnInfo *)p;
295  MSG_OUT("Progress: %s (%g/%g)\n", conn->url, dlnow, dltotal);
296  return 0;
297}
298
299
300
301/* Create a new easy handle, and add it to the global curl_multi */
302static void new_conn(char *url, GlobalInfo *g )
303{
304  ConnInfo *conn;
305  CURLMcode rc;
306
307  conn = g_malloc0(sizeof(ConnInfo));
308
309  conn->error[0]='\0';
310
311  conn->easy = curl_easy_init();
312  if (!conn->easy) {
313    MSG_OUT("curl_easy_init() failed, exiting!\n");
314    exit(2);
315  }
316  conn->global = g;
317  conn->url = g_strdup(url);
318  curl_easy_setopt(conn->easy, CURLOPT_URL, conn->url);
319  curl_easy_setopt(conn->easy, CURLOPT_WRITEFUNCTION, write_cb);
320  curl_easy_setopt(conn->easy, CURLOPT_WRITEDATA, &conn);
321  curl_easy_setopt(conn->easy, CURLOPT_VERBOSE, (long)SHOW_VERBOSE);
322  curl_easy_setopt(conn->easy, CURLOPT_ERRORBUFFER, conn->error);
323  curl_easy_setopt(conn->easy, CURLOPT_PRIVATE, conn);
324  curl_easy_setopt(conn->easy, CURLOPT_NOPROGRESS, SHOW_PROGRESS?0L:1L);
325  curl_easy_setopt(conn->easy, CURLOPT_PROGRESSFUNCTION, prog_cb);
326  curl_easy_setopt(conn->easy, CURLOPT_PROGRESSDATA, conn);
327  curl_easy_setopt(conn->easy, CURLOPT_FOLLOWLOCATION, 1L);
328  curl_easy_setopt(conn->easy, CURLOPT_CONNECTTIMEOUT, 30L);
329  curl_easy_setopt(conn->easy, CURLOPT_LOW_SPEED_LIMIT, 1L);
330  curl_easy_setopt(conn->easy, CURLOPT_LOW_SPEED_TIME, 30L);
331
332  MSG_OUT("Adding easy %p to multi %p (%s)\n", conn->easy, g->multi, url);
333  rc =curl_multi_add_handle(g->multi, conn->easy);
334  mcode_or_die("new_conn: curl_multi_add_handle", rc);
335
336  /* note that the add_handle() will set a time-out to trigger very soon so
337     that the necessary socket_action() call will be called by this app */
338}
339
340
341/* This gets called by glib whenever data is received from the fifo */
342static gboolean fifo_cb (GIOChannel *ch, GIOCondition condition, gpointer data)
343{
344  #define BUF_SIZE 1024
345  gsize len, tp;
346  gchar *buf, *tmp, *all=NULL;
347  GIOStatus rv;
348
349  do {
350    GError *err=NULL;
351    rv = g_io_channel_read_line (ch,&buf,&len,&tp,&err);
352    if ( buf ) {
353      if (tp) { buf[tp]='\0'; }
354      new_conn(buf,(GlobalInfo*)data);
355      g_free(buf);
356    } else {
357      buf = g_malloc(BUF_SIZE+1);
358      while (TRUE) {
359        buf[BUF_SIZE]='\0';
360        g_io_channel_read_chars(ch,buf,BUF_SIZE,&len,&err);
361        if (len) {
362          buf[len]='\0';
363          if (all) {
364            tmp=all;
365            all=g_strdup_printf("%s%s", tmp, buf);
366            g_free(tmp);
367          } else {
368            all = g_strdup(buf);
369          }
370        } else {
371           break;
372        }
373      }
374      if (all) {
375        new_conn(all,(GlobalInfo*)data);
376        g_free(all);
377      }
378      g_free(buf);
379    }
380    if ( err ) {
381      g_error("fifo_cb: %s", err->message);
382      g_free(err);
383      break;
384    }
385  } while ( (len) && (rv == G_IO_STATUS_NORMAL) );
386  return TRUE;
387}
388
389
390
391
392int init_fifo(void)
393{
394 struct stat st;
395 const char *fifo = "hiper.fifo";
396 int socket;
397
398 if (lstat (fifo, &st) == 0) {
399  if ((st.st_mode & S_IFMT) == S_IFREG) {
400   errno = EEXIST;
401   perror("lstat");
402   exit (1);
403  }
404 }
405
406 unlink (fifo);
407 if (mkfifo (fifo, 0600) == -1) {
408  perror("mkfifo");
409  exit (1);
410 }
411
412 socket = open (fifo, O_RDWR | O_NONBLOCK, 0);
413
414 if (socket == -1) {
415  perror("open");
416  exit (1);
417 }
418 MSG_OUT("Now, pipe some URL's into > %s\n", fifo);
419
420 return socket;
421
422}
423
424
425
426
427int main(int argc, char **argv)
428{
429  GlobalInfo *g;
430  CURLMcode rc;
431  GMainLoop*gmain;
432  int fd;
433  GIOChannel* ch;
434  g=g_malloc0(sizeof(GlobalInfo));
435
436  fd=init_fifo();
437  ch=g_io_channel_unix_new(fd);
438  g_io_add_watch(ch,G_IO_IN,fifo_cb,g);
439  gmain=g_main_loop_new(NULL,FALSE);
440  g->multi = curl_multi_init();
441  curl_multi_setopt(g->multi, CURLMOPT_SOCKETFUNCTION, sock_cb);
442  curl_multi_setopt(g->multi, CURLMOPT_SOCKETDATA, g);
443  curl_multi_setopt(g->multi, CURLMOPT_TIMERFUNCTION, update_timeout_cb);
444  curl_multi_setopt(g->multi, CURLMOPT_TIMERDATA, g);
445
446  /* we don't call any curl_multi_socket*() function yet as we have no handles
447     added! */
448
449  g_main_loop_run(gmain);
450  curl_multi_cleanup(g->multi);
451  return 0;
452}
453