1/***************************************************************************
2 *                                  _   _ ____  _
3 *  Project                     ___| | | |  _ \| |
4 *                             / __| | | | |_) | |
5 *                            | (__| |_| |  _ <| |___
6 *                             \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) 1998 - 2014, 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_BAD_HANDLE:         s="CURLM_BAD_HANDLE";         break;
108      case     CURLM_BAD_EASY_HANDLE:    s="CURLM_BAD_EASY_HANDLE";    break;
109      case     CURLM_OUT_OF_MEMORY:      s="CURLM_OUT_OF_MEMORY";      break;
110      case     CURLM_INTERNAL_ERROR:     s="CURLM_INTERNAL_ERROR";     break;
111      case     CURLM_BAD_SOCKET:         s="CURLM_BAD_SOCKET";         break;
112      case     CURLM_UNKNOWN_OPTION:     s="CURLM_UNKNOWN_OPTION";     break;
113      case     CURLM_LAST:               s="CURLM_LAST";               break;
114      default: s="CURLM_unknown";
115    }
116    MSG_OUT("ERROR: %s returns %s\n", where, s);
117    exit(code);
118  }
119}
120
121
122
123/* Check for completed transfers, and remove their easy handles */
124static void check_multi_info(GlobalInfo *g)
125{
126  char *eff_url;
127  CURLMsg *msg;
128  int msgs_left;
129  ConnInfo *conn;
130  CURL *easy;
131  CURLcode res;
132
133  MSG_OUT("REMAINING: %d\n", g->still_running);
134  while ((msg = curl_multi_info_read(g->multi, &msgs_left))) {
135    if (msg->msg == CURLMSG_DONE) {
136      easy = msg->easy_handle;
137      res = msg->data.result;
138      curl_easy_getinfo(easy, CURLINFO_PRIVATE, &conn);
139      curl_easy_getinfo(easy, CURLINFO_EFFECTIVE_URL, &eff_url);
140      MSG_OUT("DONE: %s => (%d) %s\n", eff_url, res, conn->error);
141      curl_multi_remove_handle(g->multi, easy);
142      free(conn->url);
143      curl_easy_cleanup(easy);
144      free(conn);
145    }
146  }
147}
148
149
150
151/* Called by glib when our timeout expires */
152static gboolean timer_cb(gpointer data)
153{
154  GlobalInfo *g = (GlobalInfo *)data;
155  CURLMcode rc;
156
157  rc = curl_multi_socket_action(g->multi,
158                                  CURL_SOCKET_TIMEOUT, 0, &g->still_running);
159  mcode_or_die("timer_cb: curl_multi_socket_action", rc);
160  check_multi_info(g);
161  return FALSE;
162}
163
164
165
166/* Update the event timer after curl_multi library calls */
167static int update_timeout_cb(CURLM *multi, long timeout_ms, void *userp)
168{
169  struct timeval timeout;
170  GlobalInfo *g=(GlobalInfo *)userp;
171  timeout.tv_sec = timeout_ms/1000;
172  timeout.tv_usec = (timeout_ms%1000)*1000;
173
174  MSG_OUT("*** update_timeout_cb %ld => %ld:%ld ***\n",
175              timeout_ms, timeout.tv_sec, timeout.tv_usec);
176
177  g->timer_event = g_timeout_add(timeout_ms, timer_cb, g);
178  return 0;
179}
180
181
182
183
184/* Called by glib when we get action on a multi socket */
185static gboolean event_cb(GIOChannel *ch, GIOCondition condition, gpointer data)
186{
187  GlobalInfo *g = (GlobalInfo*) data;
188  CURLMcode rc;
189  int fd=g_io_channel_unix_get_fd(ch);
190
191  int action =
192    (condition & G_IO_IN ? CURL_CSELECT_IN : 0) |
193    (condition & G_IO_OUT ? CURL_CSELECT_OUT : 0);
194
195  rc = curl_multi_socket_action(g->multi, fd, action, &g->still_running);
196  mcode_or_die("event_cb: curl_multi_socket_action", rc);
197
198  check_multi_info(g);
199  if(g->still_running) {
200    return TRUE;
201  } else {
202    MSG_OUT("last transfer done, kill timeout\n");
203    if (g->timer_event) { g_source_remove(g->timer_event); }
204    return FALSE;
205  }
206}
207
208
209
210/* Clean up the SockInfo structure */
211static void remsock(SockInfo *f)
212{
213  if (!f) { return; }
214  if (f->ev) { g_source_remove(f->ev); }
215  g_free(f);
216}
217
218
219
220/* Assign information to a SockInfo structure */
221static void setsock(SockInfo*f, curl_socket_t s, CURL*e, int act, GlobalInfo*g)
222{
223  GIOCondition kind =
224     (act&CURL_POLL_IN?G_IO_IN:0)|(act&CURL_POLL_OUT?G_IO_OUT:0);
225
226  f->sockfd = s;
227  f->action = act;
228  f->easy = e;
229  if (f->ev) { g_source_remove(f->ev); }
230  f->ev=g_io_add_watch(f->ch, kind, event_cb,g);
231
232}
233
234
235
236/* Initialize a new SockInfo structure */
237static void addsock(curl_socket_t s, CURL *easy, int action, GlobalInfo *g)
238{
239  SockInfo *fdp = g_malloc0(sizeof(SockInfo));
240
241  fdp->global = g;
242  fdp->ch=g_io_channel_unix_new(s);
243  setsock(fdp, s, easy, action, g);
244  curl_multi_assign(g->multi, s, fdp);
245}
246
247
248
249/* CURLMOPT_SOCKETFUNCTION */
250static int sock_cb(CURL *e, curl_socket_t s, int what, void *cbp, void *sockp)
251{
252  GlobalInfo *g = (GlobalInfo*) cbp;
253  SockInfo *fdp = (SockInfo*) sockp;
254  static const char *whatstr[]={ "none", "IN", "OUT", "INOUT", "REMOVE" };
255
256  MSG_OUT("socket callback: s=%d e=%p what=%s ", s, e, whatstr[what]);
257  if (what == CURL_POLL_REMOVE) {
258    MSG_OUT("\n");
259    remsock(fdp);
260  } else {
261    if (!fdp) {
262      MSG_OUT("Adding data: %s%s\n",
263             what&CURL_POLL_IN?"READ":"",
264             what&CURL_POLL_OUT?"WRITE":"" );
265      addsock(s, e, what, g);
266    }
267    else {
268      MSG_OUT(
269        "Changing action from %d to %d\n", fdp->action, what);
270      setsock(fdp, s, e, what, g);
271    }
272  }
273  return 0;
274}
275
276
277
278/* CURLOPT_WRITEFUNCTION */
279static size_t write_cb(void *ptr, size_t size, size_t nmemb, void *data)
280{
281  size_t realsize = size * nmemb;
282  ConnInfo *conn = (ConnInfo*) data;
283  (void)ptr;
284  (void)conn;
285  return realsize;
286}
287
288
289
290/* CURLOPT_PROGRESSFUNCTION */
291static int prog_cb (void *p, double dltotal, double dlnow, double ult, double uln)
292{
293  ConnInfo *conn = (ConnInfo *)p;
294  MSG_OUT("Progress: %s (%g/%g)\n", conn->url, dlnow, dltotal);
295  return 0;
296}
297
298
299
300/* Create a new easy handle, and add it to the global curl_multi */
301static void new_conn(char *url, GlobalInfo *g )
302{
303  ConnInfo *conn;
304  CURLMcode rc;
305
306  conn = g_malloc0(sizeof(ConnInfo));
307
308  conn->error[0]='\0';
309
310  conn->easy = curl_easy_init();
311  if (!conn->easy) {
312    MSG_OUT("curl_easy_init() failed, exiting!\n");
313    exit(2);
314  }
315  conn->global = g;
316  conn->url = g_strdup(url);
317  curl_easy_setopt(conn->easy, CURLOPT_URL, conn->url);
318  curl_easy_setopt(conn->easy, CURLOPT_WRITEFUNCTION, write_cb);
319  curl_easy_setopt(conn->easy, CURLOPT_WRITEDATA, &conn);
320  curl_easy_setopt(conn->easy, CURLOPT_VERBOSE, (long)SHOW_VERBOSE);
321  curl_easy_setopt(conn->easy, CURLOPT_ERRORBUFFER, conn->error);
322  curl_easy_setopt(conn->easy, CURLOPT_PRIVATE, conn);
323  curl_easy_setopt(conn->easy, CURLOPT_NOPROGRESS, SHOW_PROGRESS?0L:1L);
324  curl_easy_setopt(conn->easy, CURLOPT_PROGRESSFUNCTION, prog_cb);
325  curl_easy_setopt(conn->easy, CURLOPT_PROGRESSDATA, conn);
326  curl_easy_setopt(conn->easy, CURLOPT_FOLLOWLOCATION, 1L);
327  curl_easy_setopt(conn->easy, CURLOPT_CONNECTTIMEOUT, 30L);
328  curl_easy_setopt(conn->easy, CURLOPT_LOW_SPEED_LIMIT, 1L);
329  curl_easy_setopt(conn->easy, CURLOPT_LOW_SPEED_TIME, 30L);
330
331  MSG_OUT("Adding easy %p to multi %p (%s)\n", conn->easy, g->multi, url);
332  rc =curl_multi_add_handle(g->multi, conn->easy);
333  mcode_or_die("new_conn: curl_multi_add_handle", rc);
334
335  /* note that the add_handle() will set a time-out to trigger very soon so
336     that the necessary socket_action() call will be called by this app */
337}
338
339
340/* This gets called by glib whenever data is received from the fifo */
341static gboolean fifo_cb (GIOChannel *ch, GIOCondition condition, gpointer data)
342{
343  #define BUF_SIZE 1024
344  gsize len, tp;
345  gchar *buf, *tmp, *all=NULL;
346  GIOStatus rv;
347
348  do {
349    GError *err=NULL;
350    rv = g_io_channel_read_line (ch,&buf,&len,&tp,&err);
351    if ( buf ) {
352      if (tp) { buf[tp]='\0'; }
353      new_conn(buf,(GlobalInfo*)data);
354      g_free(buf);
355    } else {
356      buf = g_malloc(BUF_SIZE+1);
357      while (TRUE) {
358        buf[BUF_SIZE]='\0';
359        g_io_channel_read_chars(ch,buf,BUF_SIZE,&len,&err);
360        if (len) {
361          buf[len]='\0';
362          if (all) {
363            tmp=all;
364            all=g_strdup_printf("%s%s", tmp, buf);
365            g_free(tmp);
366          } else {
367            all = g_strdup(buf);
368          }
369        } else {
370           break;
371        }
372      }
373      if (all) {
374        new_conn(all,(GlobalInfo*)data);
375        g_free(all);
376      }
377      g_free(buf);
378    }
379    if ( err ) {
380      g_error("fifo_cb: %s", err->message);
381      g_free(err);
382      break;
383    }
384  } while ( (len) && (rv == G_IO_STATUS_NORMAL) );
385  return TRUE;
386}
387
388
389
390
391int init_fifo(void)
392{
393 struct stat st;
394 const char *fifo = "hiper.fifo";
395 int socket;
396
397 if (lstat (fifo, &st) == 0) {
398  if ((st.st_mode & S_IFMT) == S_IFREG) {
399   errno = EEXIST;
400   perror("lstat");
401   exit (1);
402  }
403 }
404
405 unlink (fifo);
406 if (mkfifo (fifo, 0600) == -1) {
407  perror("mkfifo");
408  exit (1);
409 }
410
411 socket = open (fifo, O_RDWR | O_NONBLOCK, 0);
412
413 if (socket == -1) {
414  perror("open");
415  exit (1);
416 }
417 MSG_OUT("Now, pipe some URL's into > %s\n", fifo);
418
419 return socket;
420
421}
422
423
424
425
426int main(int argc, char **argv)
427{
428  GlobalInfo *g;
429  CURLMcode rc;
430  GMainLoop*gmain;
431  int fd;
432  GIOChannel* ch;
433  g=g_malloc0(sizeof(GlobalInfo));
434
435  fd=init_fifo();
436  ch=g_io_channel_unix_new(fd);
437  g_io_add_watch(ch,G_IO_IN,fifo_cb,g);
438  gmain=g_main_loop_new(NULL,FALSE);
439  g->multi = curl_multi_init();
440  curl_multi_setopt(g->multi, CURLMOPT_SOCKETFUNCTION, sock_cb);
441  curl_multi_setopt(g->multi, CURLMOPT_SOCKETDATA, g);
442  curl_multi_setopt(g->multi, CURLMOPT_TIMERFUNCTION, update_timeout_cb);
443  curl_multi_setopt(g->multi, CURLMOPT_TIMERDATA, g);
444
445  /* we don't call any curl_multi_socket*() function yet as we have no handles
446     added! */
447
448  g_main_loop_run(gmain);
449  curl_multi_cleanup(g->multi);
450  return 0;
451}
452