]>
Commit | Line | Data |
---|---|---|
ba26680e MT |
1 | /*############################################################################# |
2 | # # | |
3 | # Pakfire - The IPFire package management system # | |
4 | # Copyright (C) 2021 Pakfire development team # | |
5 | # # | |
6 | # This program is free software: you can redistribute it and/or modify # | |
7 | # it under the terms of the GNU General Public License as published by # | |
8 | # the Free Software Foundation, either version 3 of the License, or # | |
9 | # (at your option) any later version. # | |
10 | # # | |
11 | # This program is distributed in the hope that it will be useful, # | |
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of # | |
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # | |
14 | # GNU General Public License for more details. # | |
15 | # # | |
16 | # You should have received a copy of the GNU General Public License # | |
17 | # along with this program. If not, see <http://www.gnu.org/licenses/>. # | |
18 | # # | |
19 | #############################################################################*/ | |
20 | ||
21 | #include <errno.h> | |
22 | #include <stdlib.h> | |
91438bdb | 23 | #include <sys/queue.h> |
62b60e37 MT |
24 | |
25 | #include <curl/curl.h> | |
ba26680e | 26 | |
10db1fae | 27 | #include <pakfire/httpclient.h> |
62b60e37 | 28 | #include <pakfire/logging.h> |
ffbc0988 | 29 | #include <pakfire/progress.h> |
c26aca37 | 30 | #include <pakfire/xfer.h> |
62b60e37 MT |
31 | |
32 | // The number of concurrent downloads | |
33 | #define MAX_PARALLEL 4 | |
34 | ||
c26aca37 MT |
35 | struct pakfire_xfer_element { |
36 | TAILQ_ENTRY(pakfire_xfer_element) nodes; | |
2d2ae99c | 37 | |
c26aca37 | 38 | struct pakfire_xfer* xfer; |
91438bdb MT |
39 | }; |
40 | ||
10db1fae | 41 | struct pakfire_httpclient { |
88b4d102 | 42 | struct pakfire_ctx* ctx; |
ba26680e | 43 | int nrefs; |
62b60e37 | 44 | |
91438bdb MT |
45 | unsigned int parallel; |
46 | ||
fcececd1 MT |
47 | // cURL share handle |
48 | CURLSH* share; | |
49 | ||
62b60e37 MT |
50 | // cURL multi handle |
51 | CURLM* curl; | |
62b60e37 | 52 | |
f31b9f87 | 53 | // Transfers |
c26aca37 MT |
54 | TAILQ_HEAD(xfers_queued, pakfire_xfer_element) xfers_queued; |
55 | TAILQ_HEAD(xfers_running, pakfire_xfer_element) xfers_running; | |
f31b9f87 | 56 | }; |
18824be9 | 57 | |
10db1fae | 58 | static int pakfire_httpclient_setup_curl(struct pakfire_httpclient* client) { |
0421c1df MT |
59 | int r; |
60 | ||
61 | // Initialize cURL | |
62 | r = curl_global_init(CURL_GLOBAL_DEFAULT); | |
63 | if (r) { | |
10db1fae | 64 | CTX_ERROR(client->ctx, "Could not initialize cURL: %d\n", r); |
0421c1df MT |
65 | return r; |
66 | } | |
67 | ||
fcececd1 | 68 | // Create a new share handle |
10db1fae MT |
69 | client->share = curl_share_init(); |
70 | if (!client->share) { | |
71 | CTX_ERROR(client->ctx, "Could not setup cURL share handle\n"); | |
fcececd1 MT |
72 | return 1; |
73 | } | |
74 | ||
62b60e37 | 75 | // Create a new multi handle |
10db1fae MT |
76 | client->curl = curl_multi_init(); |
77 | if (!client->curl) { | |
78 | CTX_ERROR(client->ctx, "Could not create cURL multi handle\n"); | |
62b60e37 MT |
79 | return 1; |
80 | } | |
81 | ||
fcececd1 | 82 | // Share connections between handles |
10db1fae | 83 | r = curl_share_setopt(client->share, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT); |
fcececd1 | 84 | if (r) { |
10db1fae | 85 | CTX_ERROR(client->ctx, "Could not configure the share handle: %s\n", |
fcececd1 MT |
86 | curl_share_strerror(r)); |
87 | return r; | |
88 | } | |
89 | ||
62b60e37 MT |
90 | return 0; |
91 | } | |
92 | ||
10db1fae | 93 | static struct pakfire_xfer_element* pakfire_httpclient_xfer_create(struct pakfire_xfer* xfer) { |
c26aca37 MT |
94 | struct pakfire_xfer_element* x = NULL; |
95 | ||
96 | // Allocate a new element | |
97 | x = calloc(1, sizeof(*x)); | |
98 | if (!x) | |
99 | return NULL; | |
100 | ||
101 | // Store a reference to the xfer | |
102 | x->xfer = pakfire_xfer_ref(xfer); | |
103 | ||
104 | return x; | |
105 | } | |
106 | ||
10db1fae MT |
107 | static struct pakfire_xfer_element* pakfire_httpclient_xfer_find_running( |
108 | struct pakfire_httpclient* client, struct pakfire_xfer* xfer) { | |
c26aca37 MT |
109 | struct pakfire_xfer_element* x = NULL; |
110 | ||
10db1fae | 111 | TAILQ_FOREACH(x, &client->xfers_running, nodes) { |
c26aca37 MT |
112 | if (x->xfer == xfer) |
113 | return x; | |
114 | } | |
115 | ||
116 | return NULL; | |
117 | } | |
118 | ||
10db1fae | 119 | static void pakfire_httpclient_xfer_free(struct pakfire_xfer_element* x) { |
c26aca37 MT |
120 | if (x->xfer) |
121 | pakfire_xfer_unref(x->xfer); | |
122 | ||
123 | free(x); | |
91438bdb MT |
124 | } |
125 | ||
10db1fae | 126 | static void pakfire_httpclient_free(struct pakfire_httpclient* client) { |
c26aca37 | 127 | struct pakfire_xfer_element* x = NULL; |
f31b9f87 MT |
128 | |
129 | // Free any queued transfers | |
10db1fae MT |
130 | while (!TAILQ_EMPTY(&client->xfers_queued)) { |
131 | x = TAILQ_LAST(&client->xfers_queued, xfers_queued); | |
132 | TAILQ_REMOVE(&client->xfers_queued, x, nodes); | |
f31b9f87 | 133 | |
10db1fae | 134 | pakfire_httpclient_xfer_free(x); |
0691b7c9 | 135 | } |
91438bdb | 136 | |
f31b9f87 | 137 | // Free any running transfers |
10db1fae MT |
138 | while (!TAILQ_EMPTY(&client->xfers_running)) { |
139 | x = TAILQ_LAST(&client->xfers_running, xfers_running); | |
140 | TAILQ_REMOVE(&client->xfers_running, x, nodes); | |
f31b9f87 | 141 | |
10db1fae | 142 | pakfire_httpclient_xfer_free(x); |
f31b9f87 MT |
143 | } |
144 | ||
10db1fae MT |
145 | if (client->share) |
146 | curl_share_cleanup(client->share); | |
147 | if (client->curl) | |
148 | curl_multi_cleanup(client->curl); | |
149 | if (client->ctx) | |
150 | pakfire_ctx_unref(client->ctx); | |
151 | free(client); | |
ba26680e MT |
152 | } |
153 | ||
10db1fae MT |
154 | int pakfire_httpclient_create(struct pakfire_httpclient** client, struct pakfire_ctx* ctx) { |
155 | struct pakfire_httpclient* c = NULL; | |
0421c1df MT |
156 | int r; |
157 | ||
d11380dd MT |
158 | // Fail if the context is flagged as offline |
159 | if (pakfire_ctx_has_flag(ctx, PAKFIRE_CTX_OFFLINE)) { | |
160 | CTX_ERROR(ctx, "Cannot initialize downloader in offline mode\n"); | |
161 | return -EPERM; | |
464c81b3 MT |
162 | } |
163 | ||
f31b9f87 | 164 | // Allocate a new object |
10db1fae MT |
165 | c = calloc(1, sizeof(*c)); |
166 | if (!c) | |
f31b9f87 | 167 | return -ENOMEM; |
ba26680e | 168 | |
88b4d102 | 169 | // Store reference to the context |
10db1fae | 170 | c->ctx = pakfire_ctx_ref(ctx); |
ba26680e MT |
171 | |
172 | // Initialize reference counting | |
10db1fae | 173 | c->nrefs = 1; |
ba26680e | 174 | |
91438bdb | 175 | // Set parallelism |
10db1fae | 176 | c->parallel = MAX_PARALLEL; |
91438bdb | 177 | |
f31b9f87 | 178 | // Init transfer queues |
10db1fae MT |
179 | TAILQ_INIT(&c->xfers_queued); |
180 | TAILQ_INIT(&c->xfers_running); | |
91438bdb | 181 | |
62b60e37 | 182 | // Setup cURL |
10db1fae | 183 | r = pakfire_httpclient_setup_curl(c); |
62b60e37 MT |
184 | if (r) |
185 | goto ERROR; | |
186 | ||
f31b9f87 | 187 | // Success |
10db1fae | 188 | *client = c; |
ba26680e MT |
189 | |
190 | return 0; | |
62b60e37 MT |
191 | |
192 | ERROR: | |
10db1fae | 193 | pakfire_httpclient_free(c); |
62b60e37 MT |
194 | |
195 | return r; | |
ba26680e MT |
196 | } |
197 | ||
10db1fae MT |
198 | struct pakfire_httpclient* pakfire_httpclient_ref(struct pakfire_httpclient* client) { |
199 | ++client->nrefs; | |
ba26680e | 200 | |
10db1fae | 201 | return client; |
ba26680e MT |
202 | } |
203 | ||
10db1fae MT |
204 | struct pakfire_httpclient* pakfire_httpclient_unref(struct pakfire_httpclient* client) { |
205 | if (--client->nrefs > 0) | |
206 | return client; | |
ba26680e | 207 | |
10db1fae | 208 | pakfire_httpclient_free(client); |
ba26680e MT |
209 | return NULL; |
210 | } | |
211 | ||
10db1fae MT |
212 | CURLSH* pakfire_httpclient_share(struct pakfire_httpclient* client) { |
213 | return client->share; | |
fcb74f50 MT |
214 | } |
215 | ||
10db1fae MT |
216 | int pakfire_httpclient_create_xfer(struct pakfire_xfer** xfer, |
217 | struct pakfire_httpclient* client, const char* url) { | |
218 | return pakfire_xfer_create(xfer, client->ctx, client, url); | |
f31b9f87 MT |
219 | } |
220 | ||
10db1fae | 221 | int pakfire_httpclient_enqueue_xfer(struct pakfire_httpclient* client, |
c26aca37 MT |
222 | struct pakfire_xfer* xfer) { |
223 | struct pakfire_xfer_element* x = NULL; | |
f31b9f87 | 224 | |
c26aca37 | 225 | // Create a new queueable object |
10db1fae | 226 | x = pakfire_httpclient_xfer_create(xfer); |
c26aca37 | 227 | if (!x) |
f31b9f87 MT |
228 | return -errno; |
229 | ||
f31b9f87 | 230 | // Push this transfer onto the queue |
10db1fae | 231 | TAILQ_INSERT_HEAD(&client->xfers_queued, x, nodes); |
f31b9f87 MT |
232 | |
233 | return 0; | |
234 | } | |
235 | ||
10db1fae | 236 | static size_t pakfire_httpclient_total_downloadsize(struct pakfire_httpclient* client) { |
c26aca37 | 237 | struct pakfire_xfer_element* x = NULL; |
f31b9f87 MT |
238 | size_t size; |
239 | ||
240 | size_t total_size = 0; | |
241 | ||
10db1fae | 242 | TAILQ_FOREACH(x, &client->xfers_queued, nodes) { |
c26aca37 | 243 | size = pakfire_xfer_get_size(x->xfer); |
f31b9f87 MT |
244 | |
245 | // Return zero if the size isn't known | |
246 | if (!size) | |
247 | return 0; | |
248 | ||
249 | total_size += size; | |
250 | } | |
251 | ||
252 | return total_size; | |
253 | } | |
254 | ||
10db1fae | 255 | static unsigned int pakfire_httpclient_total_queued_xfers(struct pakfire_httpclient* client) { |
c26aca37 | 256 | struct pakfire_xfer_element* x = NULL; |
f31b9f87 MT |
257 | unsigned int counter = 0; |
258 | ||
10db1fae | 259 | TAILQ_FOREACH(x, &client->xfers_queued, nodes) |
f31b9f87 MT |
260 | counter++; |
261 | ||
262 | return counter; | |
263 | } | |
264 | ||
10db1fae | 265 | static int pakfire_httpclient_start_transfers(struct pakfire_httpclient* client, |
f31b9f87 | 266 | struct pakfire_progress* progress, unsigned int* running_transfers) { |
c26aca37 | 267 | struct pakfire_xfer_element* x = NULL; |
91438bdb | 268 | int r; |
f31b9f87 MT |
269 | |
270 | // Keep running until we have reached our ceiling | |
10db1fae | 271 | while (*running_transfers < client->parallel) { |
f31b9f87 | 272 | // We are done if there are no more transfers in the queue |
10db1fae | 273 | if (TAILQ_EMPTY(&client->xfers_queued)) |
f31b9f87 MT |
274 | break; |
275 | ||
276 | // Fetch the next transfer | |
10db1fae MT |
277 | x = TAILQ_LAST(&client->xfers_queued, xfers_queued); |
278 | TAILQ_REMOVE(&client->xfers_queued, x, nodes); | |
c26aca37 MT |
279 | |
280 | // Prepare the xfer | |
281 | r = pakfire_xfer_prepare(x->xfer, progress, 0); | |
282 | if (r) | |
283 | goto ERROR; | |
f31b9f87 | 284 | |
c26aca37 | 285 | // Add the handle to cURL |
10db1fae | 286 | r = curl_multi_add_handle(client->curl, pakfire_xfer_handle(x->xfer)); |
f31b9f87 | 287 | if (r) { |
10db1fae | 288 | CTX_ERROR(client->ctx, "Adding handle failed: %s\n", curl_multi_strerror(r)); |
c26aca37 | 289 | goto ERROR; |
f31b9f87 MT |
290 | } |
291 | ||
10db1fae | 292 | TAILQ_INSERT_TAIL(&client->xfers_running, x, nodes); |
f31b9f87 MT |
293 | (*running_transfers)++; |
294 | } | |
295 | ||
296 | return 0; | |
c26aca37 MT |
297 | |
298 | ERROR: | |
469f2dd8 | 299 | pakfire_httpclient_xfer_free(x); |
c26aca37 MT |
300 | |
301 | return r; | |
f31b9f87 MT |
302 | } |
303 | ||
10db1fae | 304 | int pakfire_httpclient_run(struct pakfire_httpclient* client, const char* title) { |
f31b9f87 | 305 | struct pakfire_progress* progress = NULL; |
c26aca37 MT |
306 | struct pakfire_xfer* xfer = NULL; |
307 | unsigned int running_xfers = 0; | |
f31b9f87 MT |
308 | int progress_flags = |
309 | PAKFIRE_PROGRESS_SHOW_PERCENTAGE | | |
310 | PAKFIRE_PROGRESS_SHOW_ETA; | |
311 | int r = 0; | |
312 | ||
c26aca37 MT |
313 | struct pakfire_xfer_element* x = NULL; |
314 | ||
f31b9f87 | 315 | CURLMsg* msg = NULL; |
62b60e37 MT |
316 | int still_running; |
317 | int msgs_left = -1; | |
f31b9f87 MT |
318 | |
319 | // Fetch the total downloadsize | |
10db1fae | 320 | const size_t downloadsize = pakfire_httpclient_total_downloadsize(client); |
f31b9f87 MT |
321 | |
322 | // If we know the downloadsize, we can show more details | |
323 | if (downloadsize) { | |
324 | progress_flags |= | |
325 | PAKFIRE_PROGRESS_SHOW_BYTES_TRANSFERRED | | |
326 | PAKFIRE_PROGRESS_SHOW_TRANSFER_SPEED; | |
327 | } else { | |
328 | progress_flags |= | |
329 | PAKFIRE_PROGRESS_SHOW_COUNTER; | |
330 | } | |
331 | ||
332 | // Fetch the total number of queued transfers | |
10db1fae | 333 | const unsigned int num_queued_xfers = pakfire_httpclient_total_queued_xfers(client); |
f31b9f87 MT |
334 | |
335 | // Create a new progress indicator | |
10db1fae | 336 | r = pakfire_progress_create(&progress, client->ctx, progress_flags, NULL); |
f31b9f87 MT |
337 | if (r) |
338 | goto ERROR; | |
339 | ||
340 | // Set the title | |
341 | if (title) { | |
342 | r = pakfire_progress_set_title(progress, title); | |
343 | if (r) | |
344 | goto ERROR; | |
345 | } | |
346 | ||
347 | // Start the progress | |
c26aca37 | 348 | r = pakfire_progress_start(progress, (downloadsize) ? downloadsize : num_queued_xfers); |
f31b9f87 MT |
349 | if (r) |
350 | goto ERROR; | |
62b60e37 MT |
351 | |
352 | do { | |
91438bdb | 353 | // Make sure that we have up to parallel transfers active |
f31b9f87 | 354 | if (!r) { |
10db1fae | 355 | r = pakfire_httpclient_start_transfers(client, progress, &running_xfers); |
f31b9f87 MT |
356 | if (r) |
357 | goto ERROR; | |
358 | } | |
91438bdb | 359 | |
f31b9f87 | 360 | // Run cURL |
10db1fae | 361 | r = curl_multi_perform(client->curl, &still_running); |
f31b9f87 | 362 | if (r) { |
10db1fae | 363 | CTX_ERROR(client->ctx, "cURL error: %s\n", curl_easy_strerror(r)); |
f31b9f87 | 364 | goto ERROR; |
91438bdb MT |
365 | } |
366 | ||
f31b9f87 MT |
367 | for (;;) { |
368 | // Read the next message | |
10db1fae | 369 | msg = curl_multi_info_read(client->curl, &msgs_left); |
f31b9f87 MT |
370 | if (!msg) |
371 | break; | |
372 | ||
373 | switch (msg->msg) { | |
374 | case CURLMSG_DONE: | |
375 | // Update reference to transfer | |
c26aca37 | 376 | curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &xfer); |
f31b9f87 MT |
377 | |
378 | // Remove the handle | |
10db1fae | 379 | curl_multi_remove_handle(client->curl, pakfire_xfer_handle(xfer)); |
c26aca37 MT |
380 | |
381 | // Find the matching xfer element | |
10db1fae | 382 | x = pakfire_httpclient_xfer_find_running(client, xfer); |
f31b9f87 MT |
383 | |
384 | // Remove the transfer from the running list | |
c26aca37 | 385 | if (x) |
10db1fae | 386 | TAILQ_REMOVE(&client->xfers_running, x, nodes); |
c26aca37 | 387 | running_xfers--; |
f31b9f87 MT |
388 | |
389 | // Call the done callback | |
c26aca37 | 390 | r = pakfire_xfer_done(xfer, msg->data.result); |
f31b9f87 MT |
391 | switch (-r) { |
392 | // If we are asked to try again we will re-queue the transfer | |
393 | case EAGAIN: | |
c26aca37 | 394 | if (x) |
10db1fae | 395 | TAILQ_INSERT_TAIL(&client->xfers_queued, x, nodes); |
f31b9f87 MT |
396 | break; |
397 | ||
398 | // Otherwise this transfer has finished | |
399 | default: | |
c26aca37 | 400 | if (x) |
469f2dd8 | 401 | pakfire_httpclient_xfer_free(x); |
f31b9f87 MT |
402 | if (r) |
403 | goto ERROR; | |
404 | break; | |
7da3f0b0 MT |
405 | } |
406 | ||
f31b9f87 | 407 | // Reset transfer |
c26aca37 | 408 | xfer = NULL; |
f31b9f87 | 409 | break; |
62b60e37 | 410 | |
f31b9f87 | 411 | default: |
10db1fae | 412 | CTX_ERROR(client->ctx, "Received unhandled cURL message %u\n", msg->msg); |
f31b9f87 | 413 | break; |
62b60e37 MT |
414 | } |
415 | } | |
416 | ||
417 | // Wait a little before going through the loop again | |
418 | if (still_running) | |
10db1fae MT |
419 | curl_multi_wait(client->curl, NULL, 0, 100, NULL); |
420 | } while (still_running || !TAILQ_EMPTY(&client->xfers_queued)); | |
62b60e37 | 421 | |
f31b9f87 MT |
422 | // We are finished! |
423 | r = pakfire_progress_finish(progress); | |
424 | if (r) | |
425 | goto ERROR; | |
426 | ||
427 | ERROR: | |
10db1fae MT |
428 | // If the client was aborted, we need to fail all remaining running transfers |
429 | while (!TAILQ_EMPTY(&client->xfers_running)) { | |
430 | x = TAILQ_LAST(&client->xfers_running, xfers_running); | |
431 | TAILQ_REMOVE(&client->xfers_running, x, nodes); | |
f31b9f87 MT |
432 | |
433 | // Fail the transfer | |
6aed39be | 434 | pakfire_xfer_fail(x->xfer); |
f31b9f87 MT |
435 | } |
436 | ||
437 | if (progress) | |
438 | pakfire_progress_unref(progress); | |
439 | ||
440 | return r; | |
ba26680e | 441 | } |