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 25/* <DESC> 26 * multi_socket API using libevent 27 * </DESC> 28 */ 29 30#include <stdio.h> 31#include <stdlib.h> 32#include <event2/event.h> 33#include <curl/curl.h> 34 35struct event_base *base; 36CURLM *curl_handle; 37struct event *timeout; 38 39typedef struct curl_context_s { 40 struct event *event; 41 curl_socket_t sockfd; 42} curl_context_t; 43 44static void curl_perform(int fd, short event, void *arg); 45 46static curl_context_t *create_curl_context(curl_socket_t sockfd) 47{ 48 curl_context_t *context; 49 50 context = (curl_context_t *) malloc(sizeof(*context)); 51 52 context->sockfd = sockfd; 53 54 context->event = event_new(base, sockfd, 0, curl_perform, context); 55 56 return context; 57} 58 59static void destroy_curl_context(curl_context_t *context) 60{ 61 event_del(context->event); 62 event_free(context->event); 63 free(context); 64} 65 66static void add_download(const char *url, int num) 67{ 68 char filename[50]; 69 FILE *file; 70 CURL *handle; 71 72 snprintf(filename, 50, "%d.download", num); 73 74 file = fopen(filename, "wb"); 75 if(!file) { 76 fprintf(stderr, "Error opening %s\n", filename); 77 return; 78 } 79 80 handle = curl_easy_init(); 81 curl_easy_setopt(handle, CURLOPT_WRITEDATA, file); 82 curl_easy_setopt(handle, CURLOPT_PRIVATE, file); 83 curl_easy_setopt(handle, CURLOPT_URL, url); 84 curl_multi_add_handle(curl_handle, handle); 85 fprintf(stderr, "Added download %s -> %s\n", url, filename); 86} 87 88static void check_multi_info(void) 89{ 90 char *done_url; 91 CURLMsg *message; 92 int pending; 93 CURL *easy_handle; 94 FILE *file; 95 96 while((message = curl_multi_info_read(curl_handle, &pending))) { 97 switch(message->msg) { 98 case CURLMSG_DONE: 99 /* Do not use message data after calling curl_multi_remove_handle() and 100 curl_easy_cleanup(). As per curl_multi_info_read() docs: 101 "WARNING: The data the returned pointer points to will not survive 102 calling curl_multi_cleanup, curl_multi_remove_handle or 103 curl_easy_cleanup." */ 104 easy_handle = message->easy_handle; 105 106 curl_easy_getinfo(easy_handle, CURLINFO_EFFECTIVE_URL, &done_url); 107 curl_easy_getinfo(easy_handle, CURLINFO_PRIVATE, &file); 108 printf("%s DONE\n", done_url); 109 110 curl_multi_remove_handle(curl_handle, easy_handle); 111 curl_easy_cleanup(easy_handle); 112 if(file) { 113 fclose(file); 114 } 115 break; 116 117 default: 118 fprintf(stderr, "CURLMSG default\n"); 119 break; 120 } 121 } 122} 123 124static void curl_perform(int fd, short event, void *arg) 125{ 126 int running_handles; 127 int flags = 0; 128 curl_context_t *context; 129 130 if(event & EV_READ) 131 flags |= CURL_CSELECT_IN; 132 if(event & EV_WRITE) 133 flags |= CURL_CSELECT_OUT; 134 135 context = (curl_context_t *) arg; 136 137 curl_multi_socket_action(curl_handle, context->sockfd, flags, 138 &running_handles); 139 140 check_multi_info(); 141} 142 143static void on_timeout(evutil_socket_t fd, short events, void *arg) 144{ 145 int running_handles; 146 curl_multi_socket_action(curl_handle, CURL_SOCKET_TIMEOUT, 0, 147 &running_handles); 148 check_multi_info(); 149} 150 151static int start_timeout(CURLM *multi, long timeout_ms, void *userp) 152{ 153 if(timeout_ms < 0) { 154 evtimer_del(timeout); 155 } 156 else { 157 if(timeout_ms == 0) 158 timeout_ms = 1; /* 0 means directly call socket_action, but we will do it 159 in a bit */ 160 struct timeval tv; 161 tv.tv_sec = timeout_ms / 1000; 162 tv.tv_usec = (timeout_ms % 1000) * 1000; 163 evtimer_del(timeout); 164 evtimer_add(timeout, &tv); 165 } 166 return 0; 167} 168 169static int handle_socket(CURL *easy, curl_socket_t s, int action, void *userp, 170 void *socketp) 171{ 172 curl_context_t *curl_context; 173 int events = 0; 174 175 switch(action) { 176 case CURL_POLL_IN: 177 case CURL_POLL_OUT: 178 case CURL_POLL_INOUT: 179 curl_context = socketp ? 180 (curl_context_t *) socketp : create_curl_context(s); 181 182 curl_multi_assign(curl_handle, s, (void *) curl_context); 183 184 if(action != CURL_POLL_IN) 185 events |= EV_WRITE; 186 if(action != CURL_POLL_OUT) 187 events |= EV_READ; 188 189 events |= EV_PERSIST; 190 191 event_del(curl_context->event); 192 event_assign(curl_context->event, base, curl_context->sockfd, events, 193 curl_perform, curl_context); 194 event_add(curl_context->event, NULL); 195 196 break; 197 case CURL_POLL_REMOVE: 198 if(socketp) { 199 event_del(((curl_context_t*) socketp)->event); 200 destroy_curl_context((curl_context_t*) socketp); 201 curl_multi_assign(curl_handle, s, NULL); 202 } 203 break; 204 default: 205 abort(); 206 } 207 208 return 0; 209} 210 211int main(int argc, char **argv) 212{ 213 if(argc <= 1) 214 return 0; 215 216 if(curl_global_init(CURL_GLOBAL_ALL)) { 217 fprintf(stderr, "Could not init curl\n"); 218 return 1; 219 } 220 221 base = event_base_new(); 222 timeout = evtimer_new(base, on_timeout, NULL); 223 224 curl_handle = curl_multi_init(); 225 curl_multi_setopt(curl_handle, CURLMOPT_SOCKETFUNCTION, handle_socket); 226 curl_multi_setopt(curl_handle, CURLMOPT_TIMERFUNCTION, start_timeout); 227 228 while(argc-- > 1) { 229 add_download(argv[argc], argc); 230 } 231 232 event_base_dispatch(base); 233 234 curl_multi_cleanup(curl_handle); 235 event_free(timeout); 236 event_base_free(base); 237 238 libevent_global_shutdown(); 239 curl_global_cleanup(); 240 241 return 0; 242} 243