1 /* librepo - A library providing (libcURL like) API to downloading repository
2 * Copyright (C) 2012 Tomas Mlcoch
3 *
4 * Licensed under the GNU Lesser General Public License Version 2.1
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library 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 GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21 #define _GNU_SOURCE // for GNU basename() implementation from string.h
22 #include <glib.h>
23 #include <glib/gstdio.h>
24 #include <assert.h>
25 #include <errno.h>
26 #include <string.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <unistd.h>
30 #include <fcntl.h>
31
32 #include "types.h"
33 #include "cleanup.h"
34 #include "util.h"
35 #include "package_downloader.h"
36 #include "handle_internal.h"
37 #include "downloader.h"
38 #include "fastestmirror_internal.h"
39
40 /* Do NOT use resume on successfully downloaded files - download will fail */
41
42 LrPackageTarget *
lr_packagetarget_new(LrHandle * handle,const char * relative_url,const char * dest,LrChecksumType checksum_type,const char * checksum,gint64 expectedsize,const char * base_url,gboolean resume,LrProgressCb progresscb,void * cbdata,GError ** err)43 lr_packagetarget_new(LrHandle *handle,
44 const char *relative_url,
45 const char *dest,
46 LrChecksumType checksum_type,
47 const char *checksum,
48 gint64 expectedsize,
49 const char *base_url,
50 gboolean resume,
51 LrProgressCb progresscb,
52 void *cbdata,
53 GError **err)
54 {
55 LrPackageTarget *target;
56
57 assert(relative_url);
58 assert(!err || *err == NULL);
59
60 target = lr_malloc0(sizeof(*target));
61 if (!target) {
62 g_set_error(err, LR_PACKAGE_DOWNLOADER_ERROR, LRE_MEMORY,
63 "Out of memory");
64 return NULL;
65 }
66
67 target->chunk = g_string_chunk_new(16);
68
69 target->handle = handle;
70 target->relative_url = lr_string_chunk_insert(target->chunk, relative_url);
71 target->dest = lr_string_chunk_insert(target->chunk, dest);
72 target->checksum_type = checksum_type;
73 target->checksum = lr_string_chunk_insert(target->chunk, checksum);
74 target->expectedsize = expectedsize;
75 target->base_url = lr_string_chunk_insert(target->chunk, base_url);
76 target->resume = resume;
77 target->progresscb = progresscb;
78 target->cbdata = cbdata;
79
80 return target;
81 }
82
83 LrPackageTarget *
lr_packagetarget_new_v2(LrHandle * handle,const char * relative_url,const char * dest,LrChecksumType checksum_type,const char * checksum,gint64 expectedsize,const char * base_url,gboolean resume,LrProgressCb progresscb,void * cbdata,LrEndCb endcb,LrMirrorFailureCb mirrorfailurecb,GError ** err)84 lr_packagetarget_new_v2(LrHandle *handle,
85 const char *relative_url,
86 const char *dest,
87 LrChecksumType checksum_type,
88 const char *checksum,
89 gint64 expectedsize,
90 const char *base_url,
91 gboolean resume,
92 LrProgressCb progresscb,
93 void *cbdata,
94 LrEndCb endcb,
95 LrMirrorFailureCb mirrorfailurecb,
96 GError **err)
97 {
98 LrPackageTarget *target;
99
100 target = lr_packagetarget_new(handle,
101 relative_url,
102 dest,
103 checksum_type,
104 checksum,
105 expectedsize,
106 base_url,
107 resume,
108 progresscb,
109 cbdata,
110 err);
111
112 if (!target)
113 return NULL;
114
115 target->endcb = endcb;
116 target->mirrorfailurecb = mirrorfailurecb;
117
118 return target;
119 }
120
121 LrPackageTarget *
lr_packagetarget_new_v3(LrHandle * handle,const char * relative_url,const char * dest,LrChecksumType checksum_type,const char * checksum,gint64 expectedsize,const char * base_url,gboolean resume,LrProgressCb progresscb,void * cbdata,LrEndCb endcb,LrMirrorFailureCb mirrorfailurecb,gint64 byterangestart,gint64 byterangeend,GError ** err)122 lr_packagetarget_new_v3(LrHandle *handle,
123 const char *relative_url,
124 const char *dest,
125 LrChecksumType checksum_type,
126 const char *checksum,
127 gint64 expectedsize,
128 const char *base_url,
129 gboolean resume,
130 LrProgressCb progresscb,
131 void *cbdata,
132 LrEndCb endcb,
133 LrMirrorFailureCb mirrorfailurecb,
134 gint64 byterangestart,
135 gint64 byterangeend,
136 GError **err)
137 {
138 LrPackageTarget *target;
139
140 target = lr_packagetarget_new_v2(handle,
141 relative_url,
142 dest,
143 checksum_type,
144 checksum,
145 expectedsize,
146 base_url,
147 resume,
148 progresscb,
149 cbdata,
150 endcb,
151 mirrorfailurecb,
152 err);
153
154 if (!target)
155 return NULL;
156
157 target->byterangestart = byterangestart;
158 target->byterangeend = byterangeend;
159
160 return target;
161 }
162
163 void
lr_packagetarget_reset(LrPackageTarget * target)164 lr_packagetarget_reset(LrPackageTarget *target)
165 {
166 target->local_path = NULL;
167 target->err = NULL;
168 }
169
170 void
lr_packagetarget_free(LrPackageTarget * target)171 lr_packagetarget_free(LrPackageTarget *target)
172 {
173 if (!target)
174 return;
175 g_string_chunk_free(target->chunk);
176 g_free(target);
177 }
178
179 gboolean
lr_download_packages(GSList * targets,LrPackageDownloadFlag flags,GError ** err)180 lr_download_packages(GSList *targets,
181 LrPackageDownloadFlag flags,
182 GError **err)
183 {
184 gboolean ret;
185 gboolean failfast = flags & LR_PACKAGEDOWNLOAD_FAILFAST;
186 struct sigaction old_sigact;
187 GSList *downloadtargets = NULL;
188 gboolean interruptible = FALSE;
189
190 assert(!err || *err == NULL);
191
192 if (!targets)
193 return TRUE;
194
195 // Check targets
196 for (GSList *elem = targets; elem; elem = g_slist_next(elem)) {
197 LrPackageTarget *packagetarget = elem->data;
198
199 if (!packagetarget->handle) {
200 continue;
201 /*
202 g_set_error(err, LR_PACKAGE_DOWNLOADER_ERROR, LRE_BADFUNCARG,
203 "Package target %s doesn't have specified a handle",
204 packagetarget->relative_url);
205 return FALSE;
206 */
207 }
208
209 if (packagetarget->handle->interruptible)
210 interruptible = TRUE;
211
212 // Check repotype
213 // Note: Checked because lr_handle_prepare_internal_mirrorlist
214 // support only LR_YUMREPO yet
215 if (packagetarget->handle->repotype != LR_YUMREPO) {
216 g_debug("%s: Bad repo type", __func__);
217 g_set_error(err, LR_PACKAGE_DOWNLOADER_ERROR, LRE_BADFUNCARG,
218 "Bad repo type");
219 return FALSE;
220 }
221 }
222
223 // Setup sighandler
224 if (interruptible) {
225 struct sigaction sigact;
226 g_debug("%s: Using own SIGINT handler", __func__);
227 memset(&sigact, 0, sizeof(old_sigact));
228 memset(&sigact, 0, sizeof(sigact));
229 sigemptyset(&sigact.sa_mask);
230 sigact.sa_handler = lr_sigint_handler;
231 sigaddset(&sigact.sa_mask, SIGINT);
232 sigact.sa_flags = SA_RESTART;
233 if (sigaction(SIGINT, &sigact, &old_sigact) == -1) {
234 g_set_error(err, LR_PACKAGE_DOWNLOADER_ERROR, LRE_SIGACTION,
235 "Cannot set Librepo SIGINT handler");
236 return FALSE;
237 }
238 }
239
240 // List of handles for fastest mirror resolving
241 GSList *fmr_handles = NULL;
242
243 // Prepare targets
244 for (GSList *elem = targets; elem; elem = g_slist_next(elem)) {
245 _cleanup_free_ gchar *local_path = NULL;
246 LrPackageTarget *packagetarget = elem->data;
247 LrDownloadTarget *downloadtarget;
248 gint64 realsize = -1;
249 gboolean doresume = packagetarget->resume;
250
251 // Reset output attributes of the handle
252 lr_packagetarget_reset(packagetarget);
253
254 // Prepare destination filename
255 if (packagetarget->dest) {
256 if (g_file_test(packagetarget->dest, G_FILE_TEST_IS_DIR)) {
257 // Dir specified
258 // unencode first in case there are any encoded slashes to
259 // prevent any path changing shenanigans
260 _cleanup_free_ gchar * unencoded_url = g_uri_unescape_string(packagetarget->relative_url, "");
261 _cleanup_free_ gchar * file_basename = g_path_get_basename(unencoded_url);
262
263 local_path = g_build_filename(packagetarget->dest,
264 file_basename,
265 NULL);
266 } else {
267 local_path = g_strdup(packagetarget->dest);
268 }
269 } else {
270 // No destination path specified
271 // unencode first in case there are any encoded slashes to
272 // prevent any path changing shenanigans
273 _cleanup_free_ gchar * unencoded_url = g_uri_unescape_string(packagetarget->relative_url, "");
274 local_path = g_path_get_basename(unencoded_url);
275 }
276
277 packagetarget->local_path = g_string_chunk_insert(packagetarget->chunk,
278 local_path);
279
280 // Check expected size and real size if the file exists
281 if (doresume
282 && g_access(packagetarget->local_path, R_OK) == 0
283 && packagetarget->expectedsize > 0)
284 {
285 struct stat buf;
286 if (stat(packagetarget->local_path, &buf)) {
287 g_set_error(err, LR_PACKAGE_DOWNLOADER_ERROR, LRE_IO,
288 "Cannot stat %s: %s", packagetarget->local_path,
289 g_strerror(errno));
290 return FALSE;
291 }
292
293 realsize = buf.st_size;
294
295 if (packagetarget->expectedsize < realsize)
296 // Existing file is bigger then the one that is expected,
297 // disable resuming
298 doresume = FALSE;
299 }
300
301 if (g_access(packagetarget->local_path, R_OK) == 0
302 && packagetarget->checksum
303 && packagetarget->checksum_type != LR_CHECKSUM_UNKNOWN)
304 {
305 /* If the file exists and checksum is ok, then is pointless to
306 * download the file again.
307 * Moreover, if the resume is enabled and the file is already
308 * completely downloaded, then the download is going to fail.
309 */
310 int fd_r = open(packagetarget->local_path, O_RDONLY);
311 if (fd_r != -1) {
312 gboolean matches;
313 ret = lr_checksum_fd_cmp(packagetarget->checksum_type,
314 fd_r,
315 packagetarget->checksum,
316 1,
317 &matches,
318 NULL);
319 close(fd_r);
320 if (ret && matches) {
321 // Checksum calculation was ok and checksum matches
322 g_debug("%s: Package %s is already downloaded (checksum matches)",
323 __func__, packagetarget->local_path);
324
325 packagetarget->err = g_string_chunk_insert(
326 packagetarget->chunk,
327 "Already downloaded");
328
329 // Call end callback
330 LrEndCb end_cb = packagetarget->endcb;
331 if (end_cb)
332 end_cb(packagetarget->cbdata,
333 LR_TRANSFER_ALREADYEXISTS,
334 "Already downloaded");
335
336 continue;
337 } else if (ret) {
338 // Checksum calculation was ok but checksum doesn't match
339 if (realsize != -1 && realsize == packagetarget->expectedsize)
340 // File size is the same as the expected one
341 // Don't try to resume
342 doresume = FALSE;
343 }
344 }
345 }
346
347 if (doresume && realsize != -1 && realsize == packagetarget->expectedsize) {
348 // File's size matches the expected one, the resume is enabled and
349 // no checksum is known => expect that the file is
350 // the one the user wants
351 g_debug("%s: Package %s is already downloaded (size matches)",
352 __func__, packagetarget->local_path);
353
354 packagetarget->err = g_string_chunk_insert(
355 packagetarget->chunk,
356 "Already downloaded");
357
358 // Call end callback
359 LrEndCb end_cb = packagetarget->endcb;
360 if (end_cb)
361 end_cb(packagetarget->cbdata,
362 LR_TRANSFER_ALREADYEXISTS,
363 "Already downloaded");
364
365 continue;
366 }
367
368 if (packagetarget->handle) {
369 ret = lr_handle_prepare_internal_mirrorlist(packagetarget->handle,
370 FALSE,
371 err);
372 if (!ret)
373 goto cleanup;
374
375 if (packagetarget->handle->fastestmirror) {
376 if (!g_slist_find(fmr_handles, packagetarget->handle))
377 fmr_handles = g_slist_prepend(fmr_handles,
378 packagetarget->handle);
379 }
380 }
381
382 GSList *checksums = NULL;
383 LrDownloadTargetChecksum *checksum;
384 checksum = lr_downloadtargetchecksum_new(packagetarget->checksum_type,
385 packagetarget->checksum);
386 checksums = g_slist_prepend(checksums, checksum);
387
388 downloadtarget = lr_downloadtarget_new(packagetarget->handle,
389 packagetarget->relative_url,
390 packagetarget->base_url,
391 -1,
392 packagetarget->local_path,
393 checksums,
394 packagetarget->expectedsize,
395 doresume,
396 packagetarget->progresscb,
397 packagetarget->cbdata,
398 packagetarget->endcb,
399 packagetarget->mirrorfailurecb,
400 packagetarget,
401 packagetarget->byterangestart,
402 packagetarget->byterangeend,
403 NULL,
404 FALSE,
405 FALSE);
406
407 downloadtargets = g_slist_append(downloadtargets, downloadtarget);
408 }
409
410 // Do Fastest Mirror resolving for all handles in one shot
411 if (fmr_handles) {
412 fmr_handles = g_slist_reverse(fmr_handles);
413 ret = lr_fastestmirror_sort_internalmirrorlists(fmr_handles, err);
414 g_slist_free(fmr_handles);
415
416 if (!ret) {
417 return FALSE;
418 }
419 }
420
421 // Start downloading
422 ret = lr_download(downloadtargets, failfast, err);
423
424 cleanup:
425
426 // Copy download statuses from downloadtargets to targets
427 for (GSList *elem = downloadtargets; elem; elem = g_slist_next(elem)) {
428 LrDownloadTarget *downloadtarget = elem->data;
429 LrPackageTarget *packagetarget = downloadtarget->userdata;
430 if (downloadtarget->err)
431 packagetarget->err = g_string_chunk_insert(packagetarget->chunk,
432 downloadtarget->err);
433 }
434
435 // Free downloadtargets list
436 g_slist_free_full(downloadtargets, (GDestroyNotify)lr_downloadtarget_free);
437
438 // Restore original signal handler
439 if (interruptible) {
440 g_debug("%s: Restoring an old SIGINT handler", __func__);
441 sigaction(SIGINT, &old_sigact, NULL);
442 if (lr_interrupt) {
443 if (err && *err != NULL)
444 g_clear_error(err);
445 g_set_error(err, LR_PACKAGE_DOWNLOADER_ERROR, LRE_INTERRUPTED,
446 "Interrupted by a SIGINT signal");
447 return FALSE;
448 }
449 }
450
451 return ret;
452 }
453
454 gboolean
lr_download_package(LrHandle * handle,const char * relative_url,const char * dest,LrChecksumType checksum_type,const char * checksum,gint64 expectedsize,const char * base_url,gboolean resume,GError ** err)455 lr_download_package(LrHandle *handle,
456 const char *relative_url,
457 const char *dest,
458 LrChecksumType checksum_type,
459 const char *checksum,
460 gint64 expectedsize,
461 const char *base_url,
462 gboolean resume,
463 GError **err)
464 {
465 LrPackageTarget *target;
466
467 assert(handle);
468 assert(!err || *err == NULL);
469
470 // XXX: Maybe remove in future
471 if (!dest)
472 dest = handle->destdir;
473
474 // XXX: Maybe remove usage of handle callback in future
475
476 target = lr_packagetarget_new(handle, relative_url, dest, checksum_type,
477 checksum, expectedsize, base_url, resume,
478 handle->user_cb, handle->user_data, err);
479 if (!target)
480 return FALSE;
481
482 GSList *targets = NULL;
483 targets = g_slist_append(targets, target);
484
485 gboolean ret = lr_download_packages(targets,
486 LR_PACKAGEDOWNLOAD_FAILFAST,
487 err);
488
489 g_slist_free_full(targets, (GDestroyNotify)lr_packagetarget_free);
490
491 return ret;
492 }
493
494
495 gboolean
lr_check_packages(GSList * targets,LrPackageCheckFlag flags,GError ** err)496 lr_check_packages(GSList *targets,
497 LrPackageCheckFlag flags,
498 GError **err)
499 {
500 gboolean ret = TRUE;
501 gboolean failfast = flags & LR_PACKAGECHECK_FAILFAST;
502 struct sigaction old_sigact;
503 gboolean interruptible = FALSE;
504
505 assert(!err || *err == NULL);
506
507 if (!targets)
508 return TRUE;
509
510 // Check targets
511 for (GSList *elem = targets; elem; elem = g_slist_next(elem)) {
512 LrPackageTarget *packagetarget = elem->data;
513
514 if (packagetarget->handle->interruptible)
515 interruptible = TRUE;
516
517 if (!packagetarget->checksum
518 || packagetarget->checksum_type == LR_CHECKSUM_UNKNOWN)
519 {
520 g_set_error(err, LR_PACKAGE_DOWNLOADER_ERROR, LRE_BADOPTARG,
521 "Target %s doesn't have specified "
522 "checksum value or checksum type!",
523 packagetarget->relative_url);
524 return FALSE;
525 }
526 }
527
528 // Setup sighandler
529 if (interruptible) {
530 g_debug("%s: Using own SIGINT handler", __func__);
531 struct sigaction sigact;
532 sigact.sa_handler = lr_sigint_handler;
533 sigaddset(&sigact.sa_mask, SIGINT);
534 sigact.sa_flags = SA_RESTART;
535 if (sigaction(SIGINT, &sigact, &old_sigact) == -1) {
536 g_set_error(err, LR_PACKAGE_DOWNLOADER_ERROR, LRE_SIGACTION,
537 "Cannot set Librepo SIGINT handler");
538 return FALSE;
539 }
540 }
541
542 for (GSList *elem = targets; elem; elem = g_slist_next(elem)) {
543 _cleanup_free_ gchar *local_path = NULL;
544 LrPackageTarget *packagetarget = elem->data;
545
546 // Prepare destination filename
547 if (packagetarget->dest) {
548 if (g_file_test(packagetarget->dest, G_FILE_TEST_IS_DIR)) {
549 // Dir specified
550 _cleanup_free_ gchar *file_basename = g_path_get_basename(packagetarget->relative_url);
551
552 local_path = g_build_filename(packagetarget->dest,
553 file_basename,
554 NULL);
555 } else {
556 local_path = g_strdup(packagetarget->dest);
557 }
558 } else {
559 // No destination path specified
560 local_path = g_path_get_basename(packagetarget->relative_url);
561 }
562
563 packagetarget->local_path = g_string_chunk_insert(packagetarget->chunk,
564 local_path);
565
566 if (g_access(packagetarget->local_path, R_OK) == 0) {
567 // If the file exists check its checksum
568 int fd_r = open(packagetarget->local_path, O_RDONLY);
569 if (fd_r != -1) {
570 // File was successfully opened
571 gboolean matches;
572 ret = lr_checksum_fd_cmp(packagetarget->checksum_type,
573 fd_r,
574 packagetarget->checksum,
575 1,
576 &matches,
577 NULL);
578 close(fd_r);
579 if (ret && matches) {
580 // Checksum is ok
581 packagetarget->err = NULL;
582 g_debug("%s: Package %s is already downloaded (checksum matches)",
583 __func__, packagetarget->local_path);
584 } else {
585 // Checksum doesn't match or checksumming error
586 packagetarget->err = g_string_chunk_insert(
587 packagetarget->chunk,
588 "Checksum of file doesn't match");
589 if (failfast) {
590 ret = FALSE;
591 g_set_error(err, LR_PACKAGE_DOWNLOADER_ERROR,
592 LRE_BADCHECKSUM,
593 "File with nonmatching checksum found");
594 break;
595 }
596 }
597 } else {
598 // Cannot open the file
599 packagetarget->err = g_string_chunk_insert(packagetarget->chunk,
600 "Cannot be opened");
601 if (failfast) {
602 ret = FALSE;
603 g_set_error(err, LR_PACKAGE_DOWNLOADER_ERROR, LRE_IO,
604 "Cannot open %s", packagetarget->local_path);
605 break;
606 }
607 }
608 } else {
609 // File doesn't exists
610 packagetarget->err = g_string_chunk_insert(packagetarget->chunk,
611 "Doesn't exist");
612 if (failfast) {
613 ret = FALSE;
614 g_set_error(err, LR_PACKAGE_DOWNLOADER_ERROR, LRE_IO,
615 "File %s doesn't exists", packagetarget->local_path);
616 break;
617 }
618 }
619 }
620
621 // Restore original signal handler
622 if (interruptible) {
623 g_debug("%s: Restoring an old SIGINT handler", __func__);
624 sigaction(SIGINT, &old_sigact, NULL);
625 if (lr_interrupt) {
626 if (err && *err != NULL)
627 g_clear_error(err);
628 g_set_error(err, LR_PACKAGE_DOWNLOADER_ERROR, LRE_INTERRUPTED,
629 "Interrupted by a SIGINT signal");
630 return FALSE;
631 }
632 }
633
634 return ret;
635 }
636