| xf.li | 6c8fc1e | 2023-08-12 00:11:09 -0700 | [diff] [blame] | 1 | /*************************************************************************** | 
|  | 2 | *                                  _   _ ____  _ | 
|  | 3 | *  Project                     ___| | | |  _ \| | | 
|  | 4 | *                             / __| | | | |_) | | | 
|  | 5 | *                            | (__| |_| |  _ <| |___ | 
|  | 6 | *                             \___|\___/|_| \_\_____| | 
|  | 7 | * | 
|  | 8 | * Copyright (C) 1998 - 2022, 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 |  | 
|  | 35 | struct event_base *base; | 
|  | 36 | CURLM *curl_handle; | 
|  | 37 | struct event *timeout; | 
|  | 38 |  | 
|  | 39 | typedef struct curl_context_s { | 
|  | 40 | struct event *event; | 
|  | 41 | curl_socket_t sockfd; | 
|  | 42 | } curl_context_t; | 
|  | 43 |  | 
|  | 44 | static void curl_perform(int fd, short event, void *arg); | 
|  | 45 |  | 
|  | 46 | static 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 |  | 
|  | 59 | static void destroy_curl_context(curl_context_t *context) | 
|  | 60 | { | 
|  | 61 | event_del(context->event); | 
|  | 62 | event_free(context->event); | 
|  | 63 | free(context); | 
|  | 64 | } | 
|  | 65 |  | 
|  | 66 | static 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 |  | 
|  | 88 | static 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 |  | 
|  | 124 | static 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 |  | 
|  | 143 | static 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 |  | 
|  | 151 | static 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 |  | 
|  | 169 | static 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 |  | 
|  | 211 | int 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 | } |