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