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