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 _POSIX_C_SOURCE 200809L
22 #define _XOPEN_SOURCE 500
23 #include <glib.h>
24 #include <glib/gprintf.h>
25 #include <curl/curl.h>
26 #include <assert.h>
27 #include <errno.h>
28 #include <stdlib.h>
29 #include <stdio.h>
30 #include <string.h>
31 #include <unistd.h>
32 #include <sys/types.h>
33 #include <stdarg.h>
34 #include <ftw.h>
35 
36 #include "util.h"
37 #include "version.h"
38 #include "metalink.h"
39 #include "cleanup.h"
40 #include "yum.h"
41 
42 #define DIR_SEPARATOR   "/"
43 #define ENV_DEBUG       "LIBREPO_DEBUG"
44 
45 #ifdef CURL_GLOBAL_ACK_EINTR
46 #define EINTR_SUPPORT " with CURL_GLOBAL_ACK_EINTR support"
47 #define CURL_GLOBAL_INIT_FLAGS  CURL_GLOBAL_ALL|CURL_GLOBAL_ACK_EINTR
48 #else
49 #define EINTR_SUPPORT ""
50 #define CURL_GLOBAL_INIT_FLAGS  CURL_GLOBAL_ALL
51 #endif
52 
53 static void
lr_log_handler(G_GNUC_UNUSED const gchar * log_domain,G_GNUC_UNUSED GLogLevelFlags log_level,const gchar * message,G_GNUC_UNUSED gpointer user_data)54 lr_log_handler(G_GNUC_UNUSED const gchar *log_domain,
55                G_GNUC_UNUSED GLogLevelFlags log_level,
56                const gchar *message,
57                G_GNUC_UNUSED gpointer user_data)
58 {
59     g_fprintf(stderr, "%s\n", message);
60 }
61 
62 static void
lr_init_debugging(void)63 lr_init_debugging(void)
64 {
65     if (!g_getenv(ENV_DEBUG))
66         return;
67 
68     g_log_set_handler("librepo", G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL
69                                  | G_LOG_FLAG_RECURSION, lr_log_handler, NULL);
70 }
71 
72 void
lr_log_librepo_summary(void)73 lr_log_librepo_summary(void)
74 {
75     _cleanup_free_ gchar *time = NULL;
76     _cleanup_date_time_unref_ GDateTime *datetime = NULL;
77 
78     g_info("Librepo version: %d.%d.%d%s (%s)", LR_VERSION_MAJOR,
79                                                LR_VERSION_MINOR,
80                                                LR_VERSION_PATCH,
81                                                EINTR_SUPPORT,
82                                                curl_version());
83 
84     datetime = g_date_time_new_now_local();
85     // Date+Time in ISO 8601 format
86     time = g_date_time_format(datetime, "%Y-%m-%dT%H:%M:%S%z");
87     g_debug("Current date: %s", time);
88 }
89 
90 static gpointer
lr_init_once_cb(gpointer user_data G_GNUC_UNUSED)91 lr_init_once_cb(gpointer user_data G_GNUC_UNUSED)
92 {
93     curl_global_init((long) CURL_GLOBAL_INIT_FLAGS);
94     lr_init_debugging();
95     lr_log_librepo_summary();
96     return GINT_TO_POINTER(1);
97 }
98 
99 void
lr_global_init(void)100 lr_global_init(void)
101 {
102     static GOnce init_once = G_ONCE_INIT;
103     g_once(&init_once, lr_init_once_cb, NULL);
104 }
105 
106 
107 /*
108 void
109 lr_global_cleanup()
110 {
111     curl_global_cleanup();
112 }
113 */
114 
115 void
lr_out_of_memory(void)116 lr_out_of_memory(void)
117 {
118     fprintf(stderr, "Out of memory\n");
119     abort();
120     exit(1);
121 }
122 
123 void *
lr_malloc(size_t len)124 lr_malloc(size_t len)
125 {
126     void *m = malloc(len);
127     if (!m) lr_out_of_memory();
128     return m;
129 }
130 
131 void *
lr_malloc0(size_t len)132 lr_malloc0(size_t len)
133 {
134     void *m = calloc(1, len);
135     if (!m) lr_out_of_memory();
136     return m;
137 }
138 
139 void *
lr_realloc(void * ptr,size_t len)140 lr_realloc(void *ptr, size_t len)
141 {
142     void *m = realloc(ptr, len);
143     if (!m && len) lr_out_of_memory();
144     return m;
145 }
146 
147 void
lr_free(void * m)148 lr_free(void *m)
149 {
150     if (m) free(m);
151 }
152 
153 int
lr_gettmpfile(void)154 lr_gettmpfile(void)
155 {
156     int fd;
157     _cleanup_free_ char *template = NULL;
158     template = g_build_filename(g_get_tmp_dir(), "librepo-tmp-XXXXXX", NULL);
159     fd = mkstemp(template);
160     if (fd < 0) {
161         perror("Cannot create temporary file - mkstemp");
162         exit(1);
163     }
164     unlink(template);
165     return fd;
166 }
167 
168 char *
lr_gettmpdir(void)169 lr_gettmpdir(void)
170 {
171     char *template = g_build_filename(g_get_tmp_dir(), "librepo-tmpdir-XXXXXX", NULL);
172     if (!mkdtemp(template)) {
173         lr_free(template);
174         return NULL;
175     }
176     return template;
177 }
178 
179 char *
lr_pathconcat(const char * first,...)180 lr_pathconcat(const char *first, ...)
181 {
182     va_list args;
183     const char *next;
184     char *separator = DIR_SEPARATOR;
185     char *chunk, *res = NULL;
186     size_t separator_len = strlen(DIR_SEPARATOR);
187     size_t total_len;  // Maximal len of result
188     size_t offset = 0;
189     int is_first = 1;
190     char *qmark_section;
191     int previous_was_empty = 0; // If last chunk was "" then separator will be
192                                 // appended to the result
193 
194     if (!first)
195         return NULL;
196 
197     total_len = strlen(first);
198 
199     va_start(args, first);
200     while ((chunk = va_arg(args, char *)))
201         total_len += (strlen(chunk) + separator_len);
202     va_end(args);
203 
204     if (total_len == 0)
205         return g_strdup("");
206 
207     qmark_section = strchr(first, '?');
208 
209     res = lr_malloc(total_len + separator_len + 1);
210 
211     next = first;
212     va_start(args, first);
213     while (1) {
214         const char *current, *start, *end;
215         size_t current_len;
216 
217         if (next) {
218             current = next;
219             next = va_arg(args, char *);
220         } else
221             break;
222 
223         current_len = strlen(current);
224 
225         if (!current_len) {
226             previous_was_empty = 1;
227             continue;   /* Skip empty element */
228         } else
229             previous_was_empty = 0;
230 
231         start = current;
232         end = start + current_len;
233          if (is_first && qmark_section)
234              end -= strlen(qmark_section);
235 
236         /* Skip leading separators - except first element */
237         if (separator_len && is_first == 0) {
238             while (!strncmp(start, separator, separator_len))
239                 start += separator_len;
240         }
241 
242         /* Skip trailing separators */
243         if (separator_len) {
244             while (start + separator_len <= end &&
245                    !strncmp(end-separator_len, separator, separator_len))
246                 end -= separator_len;
247         }
248 
249         if (start >= end) {
250             /* Element is filled only by separators */
251             if (is_first)
252                 is_first = 0;
253             continue;
254         }
255 
256         /* Prepend separator - except first element */
257         if (is_first == 0) {
258             memcpy(res + offset, separator, separator_len);
259             offset += separator_len;
260         } else
261             is_first = 0;
262 
263         memcpy(res + offset, start, end - start);
264         offset += end - start;
265     }
266     va_end(args);
267 
268     if (qmark_section) {
269         strcpy(res + offset, qmark_section);
270         offset += strlen(qmark_section);
271     }
272 
273     assert(offset <= total_len);
274 
275     if (offset == 0) {
276         lr_free(res);
277         return g_strdup(first);
278     }
279 
280     /* If last element was emtpy string, append separator to the end */
281     if (previous_was_empty && is_first == 0) {
282         memcpy(res + offset, separator, separator_len);
283         offset += separator_len;
284     }
285 
286     assert(offset <= total_len);
287 
288     res[offset] = '\0';
289 
290     return res;
291 }
292 
293 int
lr_remove_dir_cb(const char * fpath,G_GNUC_UNUSED const struct stat * sb,G_GNUC_UNUSED int typeflag,G_GNUC_UNUSED struct FTW * ftwbuf)294 lr_remove_dir_cb(const char *fpath,
295                  G_GNUC_UNUSED const struct stat *sb,
296                  G_GNUC_UNUSED int typeflag,
297                  G_GNUC_UNUSED struct FTW *ftwbuf)
298 {
299     int rv = remove(fpath);
300     if (rv)
301         g_warning("Cannot remove: %s: %s", fpath, g_strerror(errno));
302     return rv;
303 }
304 
305 int
lr_remove_dir(const char * path)306 lr_remove_dir(const char *path)
307 {
308     return nftw(path, lr_remove_dir_cb, 64, FTW_DEPTH | FTW_PHYS);
309 }
310 
311 int
lr_copy_content(int source,int dest)312 lr_copy_content(int source, int dest)
313 {
314     const int bufsize = 2048;
315     char buf[bufsize];
316     ssize_t size;
317 
318     lseek(source, 0, SEEK_SET);
319     lseek(dest, 0, SEEK_SET);
320 
321     while ((size = read(source, buf, bufsize)) > 0)
322         if (write(dest, buf, size) == -1)
323             return -1;
324 
325     return (size < 0) ? -1 : 0;
326 }
327 
328 char *
lr_prepend_url_protocol(const char * path)329 lr_prepend_url_protocol(const char *path)
330 {
331     if (!path)
332         return NULL;
333 
334     if (strstr(path, "://"))  // Protocol was specified
335         return g_strdup(path);
336 
337     if (g_str_has_prefix(path, "file:/"))
338         return g_strdup(path);
339 
340     if (path[0] == '/')  // Path is absolute path
341         return g_strconcat("file://", path, NULL);
342 
343     char *path_with_protocol, *resolved_path = realpath(path, NULL);
344     if (!resolved_path) {
345         g_warning("Error resolving real path of %s: %s", path, g_strerror(errno));
346         return NULL;
347     }
348     path_with_protocol = g_strconcat("file://", resolved_path, NULL);
349     free(resolved_path);
350     return path_with_protocol;
351 }
352 
353 gchar *
lr_string_chunk_insert(GStringChunk * chunk,const gchar * string)354 lr_string_chunk_insert(GStringChunk *chunk, const gchar *string)
355 {
356     assert(chunk);
357 
358     if (!string)
359         return NULL;
360 
361     return g_string_chunk_insert(chunk, string);
362 }
363 
364 int
lr_xml_parser_warning_logger(LrXmlParserWarningType type G_GNUC_UNUSED,char * msg,void * cbdata,GError ** err G_GNUC_UNUSED)365 lr_xml_parser_warning_logger(LrXmlParserWarningType type G_GNUC_UNUSED,
366                              char *msg,
367                              void *cbdata,
368                              GError **err G_GNUC_UNUSED)
369 {
370     g_warning("WARNING: %s: %s", (char *) cbdata, msg);
371     return LR_CB_RET_OK;
372 }
373 
374 gboolean
lr_best_checksum(GSList * list,LrChecksumType * type,gchar ** value)375 lr_best_checksum(GSList *list, LrChecksumType *type, gchar **value)
376 {
377     if (!list)
378         return FALSE;
379 
380     assert(type);
381     assert(value);
382 
383     LrChecksumType tmp_type = LR_CHECKSUM_UNKNOWN;
384     gchar *tmp_value = NULL;
385 
386     for (GSList *elem = list; elem; elem = g_slist_next(elem)) {
387         LrMetalinkHash *hash = elem->data;
388 
389         if (!hash->type || !hash->value)
390             continue;
391 
392         LrChecksumType ltype = lr_checksum_type(hash->type);
393         if (ltype != LR_CHECKSUM_UNKNOWN && ltype > tmp_type) {
394             tmp_type = ltype;
395             tmp_value = hash->value;
396         }
397     }
398 
399     if (tmp_type != LR_CHECKSUM_UNKNOWN) {
400         *type = tmp_type;
401         *value = tmp_value;
402         return TRUE;
403     }
404 
405     return FALSE;
406 }
407 
408 gchar *
lr_url_without_path(const char * url)409 lr_url_without_path(const char *url)
410 {
411     if (!url) return NULL;
412 
413     // Filesystem
414     if (g_str_has_prefix(url, "file:///"))
415         return g_strdup("file://");
416     if (g_str_has_prefix(url, "file:/"))
417         return g_strdup("file://");
418 
419     // Skip protocol prefix (ftp://, http://, file://, etc.)
420     gchar *ptr = strstr(url, "://");
421     if (ptr)
422         ptr += 3;
423     else
424         ptr = (gchar *) url;
425 
426     // Find end of the host name
427     while (*ptr != '\0' && *ptr != '/')
428         ptr++;
429 
430     // Calculate length of hostname
431     size_t len = ptr - url;
432 
433     gchar *host = g_strndup(url, len);
434     //g_debug("%s: %s -> %s", __func__, url, host);
435 
436     return host;
437 }
438 
439 gchar **
lr_strv_dup(gchar ** array)440 lr_strv_dup(gchar **array)
441 {
442     guint length;
443     gchar **copy = NULL;
444     GPtrArray *ptrarray = NULL;
445 
446     if (!array)
447         return array;
448 
449     length = g_strv_length(array);
450     ptrarray = g_ptr_array_sized_new(length + 1);
451     for (guint x=0; x < length; x++)
452         g_ptr_array_add(ptrarray, g_strdup(array[x]));
453     g_ptr_array_add(ptrarray, NULL);
454     copy = (gchar **) ptrarray->pdata;
455     g_ptr_array_free(ptrarray, FALSE);
456     return copy;
457 }
458 
459 gboolean
lr_is_local_path(const gchar * path)460 lr_is_local_path(const gchar *path)
461 {
462     if (!path || !*path)
463         return FALSE;
464 
465     if (strstr(path, "://") && !g_str_has_prefix(path, "file://"))
466         return FALSE;
467 
468     return TRUE;
469 }
470 
471 gboolean
lr_key_file_save_to_file(GKeyFile * keyfile,const gchar * filename,GError ** err)472 lr_key_file_save_to_file(GKeyFile *keyfile,
473                          const gchar *filename,
474                          GError **err)
475 {
476     _cleanup_free_ gchar *content = NULL;
477     gsize length;
478 
479     content = g_key_file_to_data(keyfile, &length, err);
480     if (!content)
481         return FALSE;
482 
483     return g_file_set_contents(filename, content, length, err);
484 }
485 
486 #ifdef WITH_ZCHUNK
487 LrChecksumType
lr_checksum_from_zck_hash(zck_hash zck_checksum_type)488 lr_checksum_from_zck_hash(zck_hash zck_checksum_type)
489 {
490     switch (zck_checksum_type) {
491         case ZCK_HASH_SHA1:
492             return LR_CHECKSUM_SHA1;
493         case ZCK_HASH_SHA256:
494             return LR_CHECKSUM_SHA256;
495         default:
496             return LR_CHECKSUM_UNKNOWN;
497     }
498 }
499 
500 zck_hash
lr_zck_hash_from_lr_checksum(LrChecksumType checksum_type)501 lr_zck_hash_from_lr_checksum(LrChecksumType checksum_type)
502 {
503     switch (checksum_type) {
504         case LR_CHECKSUM_SHA1:
505             return ZCK_HASH_SHA1;
506         case LR_CHECKSUM_SHA256:
507             return ZCK_HASH_SHA256;
508         default:
509             return ZCK_HASH_UNKNOWN;
510     }
511 }
512 
513 static zckCtx *
init_zck_read(const char * checksum,LrChecksumType checksum_type,gint64 zck_header_size,int fd,GError ** err)514 init_zck_read(const char *checksum, LrChecksumType checksum_type,
515               gint64 zck_header_size, int fd, GError **err)
516 {
517     assert(!err || *err == NULL);
518 
519     zckCtx *zck = zck_create();
520     if(!zck_init_adv_read(zck, fd)) {
521         g_set_error(err, LR_DOWNLOADER_ERROR, LRE_ZCK,
522                     "Unable to initialize zchunk file for reading");
523         return FALSE;
524     }
525 
526     zck_hash ct = lr_zck_hash_from_lr_checksum(checksum_type);
527     if(ct == ZCK_HASH_UNKNOWN) {
528         g_set_error(err, LR_YUM_ERROR, LRE_ZCK,
529                     "Zchunk doesn't support checksum type %i",
530                     checksum_type);
531         free(zck);
532         return NULL;
533     }
534     if(!zck_set_ioption(zck, ZCK_VAL_HEADER_HASH_TYPE, ct)) {
535         g_set_error(err, LR_YUM_ERROR, LRE_ZCK,
536                     "Error setting validation checksum type");
537         free(zck);
538         return NULL;
539     }
540     if(!zck_set_ioption(zck, ZCK_VAL_HEADER_LENGTH, zck_header_size)) {
541         g_set_error(err, LR_YUM_ERROR, LRE_ZCK,
542                     "Error setting header size");
543         free(zck);
544         return NULL;
545     }
546     if(!zck_set_soption(zck, ZCK_VAL_HEADER_DIGEST, checksum,
547                         strlen(checksum))) {
548         g_set_error(err, LR_YUM_ERROR, LRE_ZCK,
549                     "Unable to set validation checksum: %s",
550                     checksum);
551         free(zck);
552         return NULL;
553     }
554     return zck;
555 }
556 
557 zckCtx *
lr_zck_init_read_base(const char * checksum,LrChecksumType checksum_type,gint64 zck_header_size,int fd,GError ** err)558 lr_zck_init_read_base(const char *checksum, LrChecksumType checksum_type,
559                       gint64 zck_header_size, int fd, GError **err)
560 {
561     assert(!err || *err == NULL);
562 
563     lseek(fd, 0, SEEK_SET);
564     zckCtx *zck = init_zck_read(checksum, checksum_type, zck_header_size, fd, err);
565     if(zck == NULL)
566         return NULL;
567 
568     if(!zck_read_lead(zck)) {
569         g_set_error(err, LR_YUM_ERROR, LRE_ZCK,
570                     "Unable to read zchunk lead");
571         zck_free(&zck);
572         return NULL;
573     }
574     if(!zck_read_header(zck)) {
575         g_set_error(err, LR_YUM_ERROR, LRE_ZCK,
576                     "Unable to read zchunk header");
577         zck_free(&zck);
578         return NULL;
579     }
580     return zck;
581 }
582 
583 gboolean
lr_zck_valid_header_base(const char * checksum,LrChecksumType checksum_type,gint64 zck_header_size,int fd,GError ** err)584 lr_zck_valid_header_base(const char *checksum, LrChecksumType checksum_type,
585                          gint64 zck_header_size, int fd, GError **err)
586 {
587     assert(!err || *err == NULL);
588 
589     lseek(fd, 0, SEEK_SET);
590     zckCtx *zck = init_zck_read(checksum, checksum_type, zck_header_size, fd, err);
591     if(zck == NULL)
592         return FALSE;
593 
594     if(!zck_validate_lead(zck)) {
595         g_set_error(err, LR_YUM_ERROR, LRE_ZCK,
596                     "Unable to read zchunk lead");
597         zck_free(&zck);
598         return FALSE;
599     }
600     zck_free(&zck);
601     return TRUE;
602 }
603 
604 zckCtx *
lr_zck_init_read(LrDownloadTarget * target,char * filename,int fd,GError ** err)605 lr_zck_init_read(LrDownloadTarget *target, char *filename, int fd, GError **err)
606 {
607     zckCtx *zck = NULL;
608     gboolean found = FALSE;
609     for(GSList *cksum = target->checksums; cksum; cksum = g_slist_next(cksum)) {
610         GError *tmp_err = NULL;
611         LrDownloadTargetChecksum *ck =
612             (LrDownloadTargetChecksum*)(cksum->data);
613         g_debug("Checking checksum: %i: %s", ck->type, ck->value);
614         zck = lr_zck_init_read_base(ck->value, ck->type, target->zck_header_size,
615                                     fd, &tmp_err);
616         if(zck == NULL) {
617             g_debug("%s: Didn't find matching header in %s: %s", __func__,
618                     filename, tmp_err->message);
619             g_clear_error(&tmp_err);
620             continue;
621         }
622         g_debug("%s: Found matching header in %s", __func__, filename);
623         found = TRUE;
624         break;
625     }
626     if(!found)
627         g_set_error(err, LR_YUM_ERROR, LRE_ZCK,
628                     "Zchunk header checksum didn't match expected checksum");
629     return zck;
630 }
631 
632 gboolean
lr_zck_valid_header(LrDownloadTarget * target,char * filename,int fd,GError ** err)633 lr_zck_valid_header(LrDownloadTarget *target, char *filename, int fd, GError **err)
634 {
635     assert(!err || *err == NULL);
636 
637     for(GSList *cksum = target->checksums; cksum; cksum = g_slist_next(cksum)) {
638         GError *tmp_err = NULL;
639         LrDownloadTargetChecksum *ck =
640             (LrDownloadTargetChecksum*)(cksum->data);
641         if(lr_zck_valid_header_base(ck->value, ck->type, target->zck_header_size,
642                                     fd, &tmp_err)) {
643             return TRUE;
644         }
645         g_clear_error(&tmp_err);
646     }
647     g_set_error(err, LR_DOWNLOADER_ERROR, LRE_ZCK,
648                 "%s's zchunk header doesn't match", filename);
649     return FALSE;
650 }
651 #endif /* WITH_ZCHUNK */
652 
653 gboolean
lr_get_recursive_files_rec(char * path,char * extension,GSList ** filelist,GError ** err)654 lr_get_recursive_files_rec(char *path, char *extension, GSList **filelist,
655                            GError **err)
656 {
657     assert(!err || *err == NULL);
658     assert(filelist);
659 
660     GDir *d = g_dir_open(path, 0, err);
661     if(d == NULL)
662         return FALSE;
663 
664     const char *file = NULL;
665     while((file = g_dir_read_name(d))) {
666         GError *tmp_err = NULL;
667 
668         char *fullpath = g_build_path("/", path, file, NULL);
669         if(g_file_test(fullpath, G_FILE_TEST_IS_DIR)) {
670             lr_get_recursive_files_rec(fullpath, extension, filelist, &tmp_err);
671             if(tmp_err) {
672                 g_warning("Unable to read directory %s: %s", fullpath, tmp_err->message);
673                 g_clear_error(&tmp_err);
674             }
675             g_free(fullpath);
676         } else if(g_file_test(fullpath, G_FILE_TEST_IS_REGULAR) &&
677                   g_str_has_suffix(fullpath, extension)) {
678             *filelist = g_slist_prepend(*filelist, fullpath);
679         } else {
680             g_free(fullpath);
681         }
682     }
683     g_dir_close(d);
684     return TRUE;
685 }
686 
687 GSList *
lr_get_recursive_files(char * path,char * extension,GError ** err)688 lr_get_recursive_files(char *path, char *extension, GError **err)
689 {
690     GSList *filelist = NULL;
691     assert(!err || *err == NULL);
692 
693     if(!lr_get_recursive_files_rec(path, extension, &filelist, err)) {
694         g_slist_free_full(filelist, free);
695         return NULL;
696     }
697     return filelist;
698 }
699