1 /* Retrieve ELF / DWARF / source files from the debuginfod.
2 Copyright (C) 2019-2020 Red Hat, Inc.
3 This file is part of elfutils.
5 This file is free software; you can redistribute it and/or modify
6 it under the terms of either
8 * the GNU Lesser General Public License as published by the Free
9 Software Foundation; either version 3 of the License, or (at
10 your option) any later version
14 * the GNU General Public License as published by the Free
15 Software Foundation; either version 2 of the License, or (at
16 your option) any later version
18 or both in parallel, as here.
20 elfutils is distributed in the hope that it will be useful, but
21 WITHOUT ANY WARRANTY; without even the implied warranty of
22 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
23 General Public License for more details.
25 You should have received copies of the GNU General Public License and
26 the GNU Lesser General Public License along with this program. If
27 not, see <http://www.gnu.org/licenses/>. */
30 /* cargo-cult from libdwfl linux-kernel-modules.c */
31 /* In case we have a bad fts we include this before config.h because it
32 can't handle _FILE_OFFSET_BITS.
33 Everything we need here is fine if its declarations just come first.
34 Also, include sys/types.h before fts. On some systems fts.h is not self
37 #include <sys/types.h>
42 #include "debuginfod.h"
47 /* We might be building a bootstrap dummy library, which is really simple. */
48 #ifdef DUMMY_LIBDEBUGINFOD
50 debuginfod_client *debuginfod_begin (void) { errno = ENOSYS; return NULL; }
51 int debuginfod_find_debuginfo (debuginfod_client *c, const unsigned char *b,
52 int s, char **p) { return -ENOSYS; }
53 int debuginfod_find_executable (debuginfod_client *c, const unsigned char *b,
54 int s, char **p) { return -ENOSYS; }
55 int debuginfod_find_source (debuginfod_client *c, const unsigned char *b,
56 int s, const char *f, char **p) { return -ENOSYS; }
57 void debuginfod_set_progressfn(debuginfod_client *c,
58 debuginfod_progressfn_t fn) { }
59 void debuginfod_set_verbose_fd(debuginfod_client *c, int fd) { }
60 void debuginfod_set_user_data (debuginfod_client *c, void *d) { }
61 void* debuginfod_get_user_data (debuginfod_client *c) { return NULL; }
62 const char* debuginfod_get_url (debuginfod_client *c) { return NULL; }
63 int debuginfod_add_http_header (debuginfod_client *c,
64 const char *h) { return -ENOSYS; }
65 void debuginfod_end (debuginfod_client *c) { }
67 #else /* DUMMY_LIBDEBUGINFOD */
79 #include <linux/limits.h>
82 #include <sys/syscall.h>
83 #include <sys/types.h>
85 #include <sys/utsname.h>
86 #include <curl/curl.h>
88 /* If fts.h is included before config.h, its indirect inclusions may not
89 give us the right LFS aliases of these functions, so map them manually. */
91 #ifdef _FILE_OFFSET_BITS
96 #include <sys/types.h>
100 struct debuginfod_client
102 /* Progress/interrupt callback function. */
103 debuginfod_progressfn_t progressfn;
105 /* Stores user data. */
108 /* Stores current/last url, if any. */
111 /* Accumulates outgoing http header names/values. */
112 int user_agent_set_p; /* affects add_default_headers */
113 struct curl_slist *headers;
115 /* Flags the default_progressfn having printed something that
116 debuginfod_end needs to terminate. */
117 int default_progressfn_printed_p;
119 /* File descriptor to output any verbose messages if > 0. */
122 /* Can contain all other context, like cache_path, server_urls,
123 timeout or other info gotten from environment variables, the
124 handle data, etc. So those don't have to be reparsed and
125 recreated on each request. */
128 /* The cache_clean_interval_s file within the debuginfod cache specifies
129 how frequently the cache should be cleaned. The file's st_mtime represents
130 the time of last cleaning. */
131 static const char *cache_clean_interval_filename = "cache_clean_interval_s";
132 static const time_t cache_clean_default_interval_s = 86400; /* 1 day */
134 /* The cache_max_unused_age_s file within the debuginfod cache specifies the
135 the maximum time since last access that a file will remain in the cache. */
136 static const char *cache_max_unused_age_filename = "max_unused_age_s";
137 static const time_t cache_default_max_unused_age_s = 604800; /* 1 week */
139 /* Location of the cache of files downloaded from debuginfods.
140 The default parent directory is $HOME, or '/' if $HOME doesn't exist. */
141 static const char *cache_default_name = ".debuginfod_client_cache";
142 static const char *cache_xdg_name = "debuginfod_client";
143 static const char *cache_path_envvar = DEBUGINFOD_CACHE_PATH_ENV_VAR;
145 /* URLs of debuginfods, separated by url_delim. */
146 static const char *server_urls_envvar = DEBUGINFOD_URLS_ENV_VAR;
147 static const char *url_delim = " ";
148 static const char url_delim_char = ' ';
150 /* Timeout for debuginfods, in seconds (to get at least 100K). */
151 static const char *server_timeout_envvar = DEBUGINFOD_TIMEOUT_ENV_VAR;
152 static const long default_timeout = 90;
155 /* Data associated with a particular CURL easy handle. Passed to
156 the write callback. */
159 /* Cache file to be written to in case query is successful. */
162 /* URL queried by this handle. */
165 /* error buffer for this handle. */
166 char errbuf[CURL_ERROR_SIZE];
171 /* The client object whom we're serving. */
172 debuginfod_client *client;
174 /* Pointer to handle that should write to fd. Initially points to NULL,
175 then points to the first handle that begins writing the target file
176 to the cache. Used to ensure that a file is not downloaded from
177 multiple servers unnecessarily. */
178 CURL **target_handle;
182 debuginfod_write_callback (char *ptr, size_t size, size_t nmemb, void *data)
184 ssize_t count = size * nmemb;
186 struct handle_data *d = (struct handle_data*)data;
188 /* Indicate to other handles that they can abort their transfer. */
189 if (*d->target_handle == NULL)
191 *d->target_handle = d->handle;
192 /* update the client object */
193 const char *url = NULL;
194 (void) curl_easy_getinfo (d->handle, CURLINFO_EFFECTIVE_URL, &url);
197 free (d->client->url);
198 d->client->url = strdup(url); /* ok if fails */
202 /* If this handle isn't the target handle, abort transfer. */
203 if (*d->target_handle != d->handle)
206 return (size_t) write(d->fd, (void*)ptr, count);
209 /* Create the cache and interval file if they do not already exist.
210 Return 0 if cache and config file are initialized, otherwise return
211 the appropriate error code. */
213 debuginfod_init_cache (char *cache_path, char *interval_path, char *maxage_path)
217 /* If the cache and config file already exist then we are done. */
218 if (stat(cache_path, &st) == 0 && stat(interval_path, &st) == 0)
221 /* Create the cache and config files as necessary. */
222 if (stat(cache_path, &st) != 0 && mkdir(cache_path, ACCESSPERMS) < 0)
227 /* init cleaning interval config file. */
228 fd = open(interval_path, O_CREAT | O_RDWR, DEFFILEMODE);
232 if (dprintf(fd, "%ld", cache_clean_default_interval_s) < 0)
235 /* init max age config file. */
236 if (stat(maxage_path, &st) != 0
237 && (fd = open(maxage_path, O_CREAT | O_RDWR, DEFFILEMODE)) < 0)
240 if (dprintf(fd, "%ld", cache_default_max_unused_age_s) < 0)
247 /* Delete any files that have been unmodied for a period
248 longer than $DEBUGINFOD_CACHE_CLEAN_INTERVAL_S. */
250 debuginfod_clean_cache(debuginfod_client *c,
251 char *cache_path, char *interval_path,
252 char *max_unused_path)
256 FILE *max_unused_file;
258 if (stat(interval_path, &st) == -1)
260 /* Create new interval file. */
261 interval_file = fopen(interval_path, "w");
263 if (interval_file == NULL)
266 int rc = fprintf(interval_file, "%ld", cache_clean_default_interval_s);
267 fclose(interval_file);
273 /* Check timestamp of interval file to see whether cleaning is necessary. */
274 time_t clean_interval;
275 interval_file = fopen(interval_path, "r");
278 if (fscanf(interval_file, "%ld", &clean_interval) != 1)
279 clean_interval = cache_clean_default_interval_s;
280 fclose(interval_file);
283 clean_interval = cache_clean_default_interval_s;
285 if (time(NULL) - st.st_mtime < clean_interval)
286 /* Interval has not passed, skip cleaning. */
289 /* Read max unused age value from config file. */
290 time_t max_unused_age;
291 max_unused_file = fopen(max_unused_path, "r");
294 if (fscanf(max_unused_file, "%ld", &max_unused_age) != 1)
295 max_unused_age = cache_default_max_unused_age_s;
296 fclose(max_unused_file);
299 max_unused_age = cache_default_max_unused_age_s;
301 char * const dirs[] = { cache_path, NULL, };
303 FTS *fts = fts_open(dirs, 0, NULL);
308 const char * pattern = ".*/[a-f0-9]+/(debuginfo|executable|source.*)$";
309 if (regcomp (&re, pattern, REG_EXTENDED | REG_NOSUB) != 0)
314 while ((f = fts_read(fts)) != NULL)
316 /* ignore any files that do not match the pattern. */
317 if (regexec (&re, f->fts_path, 0, NULL, 0) != 0)
321 if (c->progressfn) /* inform/check progress callback */
322 if ((c->progressfn) (c, files, 0))
328 /* delete file if max_unused_age has been met or exceeded. */
329 /* XXX consider extra effort to clean up old tmp files */
330 if (time(NULL) - f->fts_statp->st_atime >= max_unused_age)
331 unlink (f->fts_path);
335 /* Remove if empty. */
336 (void) rmdir (f->fts_path);
346 /* Update timestamp representing when the cache was last cleaned. */
347 utime (interval_path, NULL);
352 #define MAX_BUILD_ID_BYTES 64
356 add_default_headers(debuginfod_client *client)
358 if (client->user_agent_set_p)
361 /* Compute a User-Agent: string to send. The more accurately this
362 describes this host, the likelier that the debuginfod servers
363 might be able to locate debuginfo for us. */
365 char* utspart = NULL;
370 rc = asprintf(& utspart, "%s/%s", uts.sysname, uts.machine);
374 FILE *f = fopen ("/etc/os-release", "r");
376 f = fopen ("/usr/lib/os-release", "r");
378 char *version = NULL;
381 while (id == NULL || version == NULL)
385 if (fgets (s, sizeof(buf), f) == NULL)
388 int len = strlen (s);
391 if (s[len - 1] == '\n')
397 char *v = strchr (s, '=');
398 if (v == NULL || strlen (v) < 2)
401 /* Split var and value. */
405 /* Remove optional quotes around value string. */
406 if (*v == '"' || *v == '\'')
411 if (strcmp (s, "ID") == 0)
413 if (strcmp (s, "VERSION_ID") == 0)
414 version = strdup (v);
420 rc = asprintf(& ua, "User-Agent: %s/%s,%s,%s/%s",
421 PACKAGE_NAME, PACKAGE_VERSION,
429 (void) debuginfod_add_http_header (client, ua);
438 #define xalloc_str(p, fmt, args...) \
441 if (asprintf (&p, fmt, args) < 0) \
450 /* Offer a basic form of progress tracing */
452 default_progressfn (debuginfod_client *c, long a, long b)
454 const char* url = debuginfod_get_url (c);
457 /* We prefer to print the host part of the URL to keep the
461 const char* buildid = strstr(url, "buildid/");
463 len = (buildid - url);
468 if (b == 0 || url==NULL) /* early stage */
469 dprintf(STDERR_FILENO,
470 "\rDownloading %c", "-/|\\"[a % 4]);
471 else if (b < 0) /* download in progress but unknown total length */
472 dprintf(STDERR_FILENO,
473 "\rDownloading from %.*s %ld",
475 else /* download in progress, and known total length */
476 dprintf(STDERR_FILENO,
477 "\rDownloading from %.*s %ld/%ld",
479 c->default_progressfn_printed_p = 1;
485 /* Query each of the server URLs found in $DEBUGINFOD_URLS for the file
486 with the specified build-id, type (debuginfo, executable or source)
487 and filename. filename may be NULL. If found, return a file
488 descriptor for the target, otherwise return an error code.
491 debuginfod_query_server (debuginfod_client *c,
492 const unsigned char *build_id,
495 const char *filename,
500 char *cache_path = NULL;
501 char *maxage_path = NULL;
502 char *interval_path = NULL;
503 char *target_cache_dir = NULL;
504 char *target_cache_path = NULL;
505 char *target_cache_tmppath = NULL;
506 char suffix[PATH_MAX + 1]; /* +1 for zero terminator. */
507 char build_id_bytes[MAX_BUILD_ID_BYTES * 2 + 1];
508 int vfd = c->verbose_fd;
513 dprintf (vfd, "debuginfod_find_%s ", type);
514 if (build_id_len == 0) /* expect clean hexadecimal */
515 dprintf (vfd, "%s", (const char *) build_id);
517 for (int i = 0; i < build_id_len; i++)
518 dprintf (vfd, "%02x", build_id[i]);
519 if (filename != NULL)
520 dprintf (vfd, " %s\n", filename);
524 /* Is there any server we can query? If not, don't do any work,
525 just return with ENOSYS. Don't even access the cache. */
526 urls_envvar = getenv(server_urls_envvar);
528 dprintf (vfd, "server urls \"%s\"\n",
529 urls_envvar != NULL ? urls_envvar : "");
530 if (urls_envvar == NULL || urls_envvar[0] == '\0')
536 /* Clear the obsolete URL from a previous _find operation. */
540 add_default_headers(c);
542 /* Copy lowercase hex representation of build_id into buf. */
544 dprintf (vfd, "checking build-id\n");
545 if ((build_id_len >= MAX_BUILD_ID_BYTES) ||
546 (build_id_len == 0 &&
547 strlen ((const char *) build_id) > MAX_BUILD_ID_BYTES*2))
553 if (build_id_len == 0) /* expect clean hexadecimal */
554 strcpy (build_id_bytes, (const char *) build_id);
556 for (int i = 0; i < build_id_len; i++)
557 sprintf(build_id_bytes + (i * 2), "%02x", build_id[i]);
559 if (filename != NULL)
562 dprintf (vfd, "checking filename\n");
563 if (filename[0] != '/') // must start with /
569 /* copy the filename to suffix, s,/,#,g */
571 for (unsigned fi=0; q < PATH_MAX-2; fi++) /* -2, escape is 2 chars. */
572 switch (filename[fi])
576 q = PATH_MAX-1; /* escape for loop too */
578 case '/': /* escape / to prevent dir escape */
582 case '#': /* escape # to prevent /# vs #/ collisions */
587 suffix[q++]=filename[fi];
590 /* If the DWARF filenames are super long, this could exceed
591 PATH_MAX and truncate/collide. Oh well, that'll teach
597 if (suffix[0] != '\0' && vfd >= 0)
598 dprintf (vfd, "suffix %s\n", suffix);
600 /* set paths needed to perform the query
603 cache_path: $HOME/.cache
604 target_cache_dir: $HOME/.cache/0123abcd
605 target_cache_path: $HOME/.cache/0123abcd/debuginfo
606 target_cache_path: $HOME/.cache/0123abcd/source#PATH#TO#SOURCE ?
608 $XDG_CACHE_HOME takes priority over $HOME/.cache.
609 $DEBUGINFOD_CACHE_PATH takes priority over $HOME/.cache and $XDG_CACHE_HOME.
612 /* Determine location of the cache. The path specified by the debuginfod
613 cache environment variable takes priority. */
614 char *cache_var = getenv(cache_path_envvar);
615 if (cache_var != NULL && strlen (cache_var) > 0)
616 xalloc_str (cache_path, "%s", cache_var);
619 /* If a cache already exists in $HOME ('/' if $HOME isn't set), then use
620 that. Otherwise use the XDG cache directory naming format. */
621 xalloc_str (cache_path, "%s/%s", getenv ("HOME") ?: "/", cache_default_name);
624 if (stat (cache_path, &st) < 0)
626 char cachedir[PATH_MAX];
627 char *xdg = getenv ("XDG_CACHE_HOME");
629 if (xdg != NULL && strlen (xdg) > 0)
630 snprintf (cachedir, PATH_MAX, "%s", xdg);
632 snprintf (cachedir, PATH_MAX, "%s/.cache", getenv ("HOME") ?: "/");
634 /* Create XDG cache directory if it doesn't exist. */
635 if (stat (cachedir, &st) == 0)
637 if (! S_ISDIR (st.st_mode))
645 rc = mkdir (cachedir, 0700);
647 /* Also check for EEXIST and S_ISDIR in case another client just
648 happened to create the cache. */
651 || stat (cachedir, &st) != 0
652 || ! S_ISDIR (st.st_mode)))
660 xalloc_str (cache_path, "%s/%s", cachedir, cache_xdg_name);
664 xalloc_str (target_cache_dir, "%s/%s", cache_path, build_id_bytes);
665 xalloc_str (target_cache_path, "%s/%s%s", target_cache_dir, type, suffix);
666 xalloc_str (target_cache_tmppath, "%s.XXXXXX", target_cache_path);
668 /* XXX combine these */
669 xalloc_str (interval_path, "%s/%s", cache_path, cache_clean_interval_filename);
670 xalloc_str (maxage_path, "%s/%s", cache_path, cache_max_unused_age_filename);
673 dprintf (vfd, "checking cache dir %s\n", cache_path);
675 rc = debuginfod_init_cache(cache_path, interval_path, maxage_path);
678 rc = debuginfod_clean_cache(c, cache_path, interval_path, maxage_path);
682 /* If the target is already in the cache then we are done. */
683 int fd = open (target_cache_path, O_RDONLY);
688 *path = strdup(target_cache_path);
693 long timeout = default_timeout;
694 const char* timeout_envvar = getenv(server_timeout_envvar);
695 if (timeout_envvar != NULL)
696 timeout = atoi (timeout_envvar);
699 dprintf (vfd, "using timeout %ld\n", timeout);
701 /* make a copy of the envvar so it can be safely modified. */
702 server_urls = strdup(urls_envvar);
703 if (server_urls == NULL)
708 /* thereafter, goto out0 on error*/
710 /* create target directory in cache if not found. */
712 if (stat(target_cache_dir, &st) == -1 && mkdir(target_cache_dir, 0700) < 0)
718 /* NB: write to a temporary file first, to avoid race condition of
719 multiple clients checking the cache, while a partially-written or empty
720 file is in there, being written from libcurl. */
721 fd = mkstemp (target_cache_tmppath);
728 /* Count number of URLs. */
730 for (int i = 0; server_urls[i] != '\0'; i++)
731 if (server_urls[i] != url_delim_char
732 && (i == 0 || server_urls[i - 1] == url_delim_char))
735 CURLM *curlm = curl_multi_init();
742 /* Tracks which handle should write to fd. Set to the first
743 handle that is ready to write the target file to the cache. */
744 CURL *target_handle = NULL;
745 struct handle_data *data = malloc(sizeof(struct handle_data) * num_urls);
752 /* thereafter, goto out1 on error. */
754 /* Initalize handle_data with default values. */
755 for (int i = 0; i < num_urls; i++)
757 data[i].handle = NULL;
759 data[i].errbuf[0] = '\0';
762 char *strtok_saveptr;
763 char *server_url = strtok_r(server_urls, url_delim, &strtok_saveptr);
765 /* Initialize each handle. */
766 for (int i = 0; i < num_urls && server_url != NULL; i++)
769 dprintf (vfd, "init server %d %s\n", i, server_url);
772 data[i].target_handle = &target_handle;
773 data[i].handle = curl_easy_init();
776 if (data[i].handle == NULL)
782 /* Build handle url. Tolerate both http://foo:999 and
783 http://foo:999/ forms */
785 if (strlen(server_url) > 1 && server_url[strlen(server_url)-1] == '/')
786 slashbuildid = "buildid";
788 slashbuildid = "/buildid";
790 if (filename) /* must start with / */
791 snprintf(data[i].url, PATH_MAX, "%s%s/%s/%s%s", server_url,
792 slashbuildid, build_id_bytes, type, filename);
794 snprintf(data[i].url, PATH_MAX, "%s%s/%s/%s", server_url,
795 slashbuildid, build_id_bytes, type);
798 dprintf (vfd, "url %d %s\n", i, data[i].url);
800 curl_easy_setopt(data[i].handle, CURLOPT_URL, data[i].url);
802 curl_easy_setopt(data[i].handle, CURLOPT_ERRORBUFFER, data[i].errbuf);
803 curl_easy_setopt(data[i].handle,
804 CURLOPT_WRITEFUNCTION,
805 debuginfod_write_callback);
806 curl_easy_setopt(data[i].handle, CURLOPT_WRITEDATA, (void*)&data[i]);
809 /* Make sure there is at least some progress,
810 try to get at least 100K per timeout seconds. */
811 curl_easy_setopt (data[i].handle, CURLOPT_LOW_SPEED_TIME,
813 curl_easy_setopt (data[i].handle, CURLOPT_LOW_SPEED_LIMIT,
816 curl_easy_setopt(data[i].handle, CURLOPT_FILETIME, (long) 1);
817 curl_easy_setopt(data[i].handle, CURLOPT_FOLLOWLOCATION, (long) 1);
818 curl_easy_setopt(data[i].handle, CURLOPT_FAILONERROR, (long) 1);
819 curl_easy_setopt(data[i].handle, CURLOPT_NOSIGNAL, (long) 1);
820 #if LIBCURL_VERSION_NUM >= 0x072a00 /* 7.42.0 */
821 curl_easy_setopt(data[i].handle, CURLOPT_PATH_AS_IS, (long) 1);
823 /* On old curl; no big deal, canonicalization here is almost the
824 same, except perhaps for ? # type decorations at the tail. */
826 curl_easy_setopt(data[i].handle, CURLOPT_AUTOREFERER, (long) 1);
827 curl_easy_setopt(data[i].handle, CURLOPT_ACCEPT_ENCODING, "");
828 curl_easy_setopt(data[i].handle, CURLOPT_HTTPHEADER, c->headers);
830 curl_multi_add_handle(curlm, data[i].handle);
831 server_url = strtok_r(NULL, url_delim, &strtok_saveptr);
834 /* Query servers in parallel. */
836 dprintf (vfd, "query %d urls in parallel\n", num_urls);
839 int committed_to = -1;
840 bool verbose_reported = false;
843 /* Wait 1 second, the minimum DEBUGINFOD_TIMEOUT. */
844 curl_multi_wait(curlm, NULL, 0, 1000, NULL);
846 /* If the target file has been found, abort the other queries. */
847 if (target_handle != NULL)
849 for (int i = 0; i < num_urls; i++)
850 if (data[i].handle != target_handle)
851 curl_multi_remove_handle(curlm, data[i].handle);
856 if (vfd >= 0 && !verbose_reported && committed_to >= 0)
858 bool pnl = (c->default_progressfn_printed_p && vfd == STDERR_FILENO);
859 dprintf (vfd, "%scommitted to url %d\n", pnl ? "\n" : "",
862 c->default_progressfn_printed_p = 0;
863 verbose_reported = true;
866 CURLMcode curlm_res = curl_multi_perform(curlm, &still_running);
867 if (curlm_res != CURLM_OK)
871 case CURLM_CALL_MULTI_PERFORM: continue;
872 case CURLM_OUT_OF_MEMORY: rc = -ENOMEM; break;
873 default: rc = -ENETUNREACH; break;
878 if (c->progressfn) /* inform/check progress callback */
881 long pa = loops; /* default params for progress callback */
882 long pb = 0; /* transfer_timeout tempting, but loops != elapsed-time */
883 if (target_handle) /* we've committed to a server; report its download progress */
886 #ifdef CURLINFO_SIZE_DOWNLOAD_T
888 curl_res = curl_easy_getinfo(target_handle,
889 CURLINFO_SIZE_DOWNLOAD_T,
891 if (curl_res == 0 && dl >= 0)
892 pa = (dl > LONG_MAX ? LONG_MAX : (long)dl);
895 curl_res = curl_easy_getinfo(target_handle,
896 CURLINFO_SIZE_DOWNLOAD,
899 pa = (dl > LONG_MAX ? LONG_MAX : (long)dl);
902 /* NB: If going through deflate-compressing proxies, this
903 number is likely to be unavailable, so -1 may show. */
904 #ifdef CURLINFO_CURLINFO_CONTENT_LENGTH_DOWNLOAD_T
906 curl_res = curl_easy_getinfo(target_handle,
907 CURLINFO_CONTENT_LENGTH_DOWNLOAD_T,
909 if (curl_res == 0 && cl >= 0)
910 pb = (cl > LONG_MAX ? LONG_MAX : (long)cl);
913 curl_res = curl_easy_getinfo(target_handle,
914 CURLINFO_CONTENT_LENGTH_DOWNLOAD,
917 pb = (cl > LONG_MAX ? LONG_MAX : (long)cl);
921 if ((*c->progressfn) (c, pa, pb))
924 } while (still_running);
926 /* Check whether a query was successful. If so, assign its handle
927 to verified_handle. */
930 CURL *verified_handle = NULL;
935 msg = curl_multi_info_read(curlm, &num_msg);
936 if (msg != NULL && msg->msg == CURLMSG_DONE)
940 bool pnl = (c->default_progressfn_printed_p
941 && vfd == STDERR_FILENO);
942 dprintf (vfd, "%sserver response %s\n", pnl ? "\n" : "",
943 curl_easy_strerror (msg->data.result));
945 c->default_progressfn_printed_p = 0;
946 for (int i = 0; i < num_urls; i++)
947 if (msg->easy_handle == data[i].handle)
949 if (strlen (data[i].errbuf) > 0)
950 dprintf (vfd, "url %d %s\n", i, data[i].errbuf);
955 if (msg->data.result != CURLE_OK)
957 /* Unsucessful query, determine error code. */
958 switch (msg->data.result)
960 case CURLE_COULDNT_RESOLVE_HOST: rc = -EHOSTUNREACH; break; // no NXDOMAIN
961 case CURLE_URL_MALFORMAT: rc = -EINVAL; break;
962 case CURLE_COULDNT_CONNECT: rc = -ECONNREFUSED; break;
963 case CURLE_PEER_FAILED_VERIFICATION: rc = -ECONNREFUSED; break;
964 case CURLE_REMOTE_ACCESS_DENIED: rc = -EACCES; break;
965 case CURLE_WRITE_ERROR: rc = -EIO; break;
966 case CURLE_OUT_OF_MEMORY: rc = -ENOMEM; break;
967 case CURLE_TOO_MANY_REDIRECTS: rc = -EMLINK; break;
968 case CURLE_SEND_ERROR: rc = -ECONNRESET; break;
969 case CURLE_RECV_ERROR: rc = -ECONNRESET; break;
970 case CURLE_OPERATION_TIMEDOUT: rc = -ETIME; break;
971 default: rc = -ENOENT; break;
976 /* Query completed without an error. Confirm that the
977 response code is 200 when using HTTP/HTTPS and 0 when
978 using file:// and set verified_handle. */
980 if (msg->easy_handle != NULL)
982 char *effective_url = NULL;
983 long resp_code = 500;
984 CURLcode ok1 = curl_easy_getinfo (target_handle,
985 CURLINFO_EFFECTIVE_URL,
987 CURLcode ok2 = curl_easy_getinfo (target_handle,
988 CURLINFO_RESPONSE_CODE,
990 if(ok1 == CURLE_OK && ok2 == CURLE_OK && effective_url)
992 if (strncasecmp (effective_url, "HTTP", 4) == 0)
993 if (resp_code == 200)
995 verified_handle = msg->easy_handle;
998 if (strncasecmp (effective_url, "FILE", 4) == 0)
1001 verified_handle = msg->easy_handle;
1005 /* - libcurl since 7.52.0 version start to support
1007 - before 7.61.0, effective_url would give us a
1008 url with upper case SCHEME added in the front;
1009 - effective_url between 7.61 and 7.69 can be lack
1010 of scheme if the original url doesn't include one;
1011 - since version 7.69 effective_url will be provide
1012 a scheme in lower case. */
1013 #if LIBCURL_VERSION_NUM >= 0x073d00 /* 7.61.0 */
1014 #if LIBCURL_VERSION_NUM <= 0x074500 /* 7.69.0 */
1015 char *scheme = NULL;
1016 CURLcode ok3 = curl_easy_getinfo (target_handle,
1019 if(ok3 == CURLE_OK && scheme)
1021 if (strncmp (scheme, "HTTP", 4) == 0)
1022 if (resp_code == 200)
1024 verified_handle = msg->easy_handle;
1033 } while (num_msg > 0);
1035 if (verified_handle == NULL)
1040 bool pnl = c->default_progressfn_printed_p && vfd == STDERR_FILENO;
1041 dprintf (vfd, "%sgot file from server\n", pnl ? "\n" : "");
1043 c->default_progressfn_printed_p = 0;
1046 /* we've got one!!!! */
1048 CURLcode curl_res = curl_easy_getinfo(verified_handle, CURLINFO_FILETIME, (void*) &mtime);
1049 if (curl_res != CURLE_OK)
1050 mtime = time(NULL); /* fall back to current time */
1052 struct timeval tvs[2];
1053 tvs[0].tv_sec = tvs[1].tv_sec = mtime;
1054 tvs[0].tv_usec = tvs[1].tv_usec = 0;
1055 (void) futimes (fd, tvs); /* best effort */
1057 /* rename tmp->real */
1058 rc = rename (target_cache_tmppath, target_cache_path);
1063 /* Perhaps we need not give up right away; could retry or something ... */
1067 for (int i = 0; i < num_urls; i++)
1068 curl_easy_cleanup(data[i].handle);
1070 curl_multi_cleanup (curlm);
1074 /* don't close fd - we're returning it */
1075 /* don't unlink the tmppath; it's already been renamed. */
1077 *path = strdup(target_cache_path);
1084 for (int i = 0; i < num_urls; i++)
1085 curl_easy_cleanup(data[i].handle);
1087 curl_multi_cleanup(curlm);
1088 unlink (target_cache_tmppath);
1089 close (fd); /* before the rmdir, otherwise it'll fail */
1090 (void) rmdir (target_cache_dir); /* nop if not empty */
1096 /* general purpose exit */
1098 /* Conclude the last \r status line */
1099 /* Another possibility is to use the ANSI CSI n K EL "Erase in Line"
1100 code. That way, the previously printed messages would be erased,
1101 and without a newline. */
1102 if (c->default_progressfn_printed_p)
1103 dprintf(STDERR_FILENO, "\n");
1108 dprintf (vfd, "not found %s (err=%d)\n", strerror (-rc), rc);
1110 dprintf (vfd, "found %s (fd=%d)\n", target_cache_path, rc);
1115 free (interval_path);
1116 free (target_cache_dir);
1117 free (target_cache_path);
1118 free (target_cache_tmppath);
1124 /* See debuginfod.h */
1126 debuginfod_begin (void)
1128 debuginfod_client *client;
1129 size_t size = sizeof (struct debuginfod_client);
1130 client = (debuginfod_client *) calloc (1, size);
1133 if (getenv(DEBUGINFOD_PROGRESS_ENV_VAR))
1134 client->progressfn = default_progressfn;
1135 if (getenv(DEBUGINFOD_VERBOSE_ENV_VAR))
1136 client->verbose_fd = STDERR_FILENO;
1138 client->verbose_fd = -1;
1144 debuginfod_set_user_data(debuginfod_client *client,
1147 client->user_data = data;
1151 debuginfod_get_user_data(debuginfod_client *client)
1153 return client->user_data;
1157 debuginfod_get_url(debuginfod_client *client)
1163 debuginfod_end (debuginfod_client *client)
1168 curl_slist_free_all (client->headers);
1174 debuginfod_find_debuginfo (debuginfod_client *client,
1175 const unsigned char *build_id, int build_id_len,
1178 return debuginfod_query_server(client, build_id, build_id_len,
1179 "debuginfo", NULL, path);
1183 /* See debuginfod.h */
1185 debuginfod_find_executable(debuginfod_client *client,
1186 const unsigned char *build_id, int build_id_len,
1189 return debuginfod_query_server(client, build_id, build_id_len,
1190 "executable", NULL, path);
1193 /* See debuginfod.h */
1194 int debuginfod_find_source(debuginfod_client *client,
1195 const unsigned char *build_id, int build_id_len,
1196 const char *filename, char **path)
1198 return debuginfod_query_server(client, build_id, build_id_len,
1199 "source", filename, path);
1203 /* Add an outgoing HTTP header. */
1204 int debuginfod_add_http_header (debuginfod_client *client, const char* header)
1206 /* Sanity check header value is of the form Header: Value.
1207 It should contain exactly one colon that isn't the first or
1209 char *colon = strchr (header, ':');
1212 || *(colon + 1) == '\0'
1213 || strchr (colon + 1, ':') != NULL)
1216 struct curl_slist *temp = curl_slist_append (client->headers, header);
1220 /* Track if User-Agent: is being set. If so, signal not to add the
1222 if (strncmp (header, "User-Agent:", 11) == 0)
1223 client->user_agent_set_p = 1;
1225 client->headers = temp;
1231 debuginfod_set_progressfn(debuginfod_client *client,
1232 debuginfod_progressfn_t fn)
1234 client->progressfn = fn;
1238 debuginfod_set_verbose_fd(debuginfod_client *client, int fd)
1240 client->verbose_fd = fd;
1244 /* NB: these are thread-unsafe. */
1245 __attribute__((constructor)) attribute_hidden void libdebuginfod_ctor(void)
1247 curl_global_init(CURL_GLOBAL_DEFAULT);
1250 /* NB: this is very thread-unsafe: it breaks other threads that are still in libcurl */
1251 __attribute__((destructor)) attribute_hidden void libdebuginfod_dtor(void)
1253 /* ... so don't do this: */
1254 /* curl_global_cleanup(); */
1257 #endif /* DUMMY_LIBDEBUGINFOD */