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"
56 #include <linux/limits.h>
59 #include <sys/syscall.h>
60 #include <sys/types.h>
62 #include <sys/utsname.h>
63 #include <curl/curl.h>
65 /* If fts.h is included before config.h, its indirect inclusions may not
66 give us the right LFS aliases of these functions, so map them manually. */
68 #ifdef _FILE_OFFSET_BITS
73 #include <sys/types.h>
77 struct debuginfod_client
79 /* Progress/interrupt callback function. */
80 debuginfod_progressfn_t progressfn;
82 /* Stores user data. */
85 /* Stores current/last url, if any. */
88 /* Accumulates outgoing http header names/values. */
89 int user_agent_set_p; /* affects add_default_headers */
90 struct curl_slist *headers;
92 /* Flags the default_progressfn having printed something that
93 debuginfod_end needs to terminate. */
94 int default_progressfn_printed_p;
96 /* Can contain all other context, like cache_path, server_urls,
97 timeout or other info gotten from environment variables, the
98 handle data, etc. So those don't have to be reparsed and
99 recreated on each request. */
102 /* The cache_clean_interval_s file within the debuginfod cache specifies
103 how frequently the cache should be cleaned. The file's st_mtime represents
104 the time of last cleaning. */
105 static const char *cache_clean_interval_filename = "cache_clean_interval_s";
106 static const time_t cache_clean_default_interval_s = 86400; /* 1 day */
108 /* The cache_max_unused_age_s file within the debuginfod cache specifies the
109 the maximum time since last access that a file will remain in the cache. */
110 static const char *cache_max_unused_age_filename = "max_unused_age_s";
111 static const time_t cache_default_max_unused_age_s = 604800; /* 1 week */
113 /* Location of the cache of files downloaded from debuginfods.
114 The default parent directory is $HOME, or '/' if $HOME doesn't exist. */
115 static const char *cache_default_name = ".debuginfod_client_cache";
116 static const char *cache_xdg_name = "debuginfod_client";
117 static const char *cache_path_envvar = DEBUGINFOD_CACHE_PATH_ENV_VAR;
119 /* URLs of debuginfods, separated by url_delim. */
120 static const char *server_urls_envvar = DEBUGINFOD_URLS_ENV_VAR;
121 static const char *url_delim = " ";
122 static const char url_delim_char = ' ';
124 /* Timeout for debuginfods, in seconds (to get at least 100K). */
125 static const char *server_timeout_envvar = DEBUGINFOD_TIMEOUT_ENV_VAR;
126 static const long default_timeout = 90;
129 /* Data associated with a particular CURL easy handle. Passed to
130 the write callback. */
133 /* Cache file to be written to in case query is successful. */
136 /* URL queried by this handle. */
142 /* The client object whom we're serving. */
143 debuginfod_client *client;
145 /* Pointer to handle that should write to fd. Initially points to NULL,
146 then points to the first handle that begins writing the target file
147 to the cache. Used to ensure that a file is not downloaded from
148 multiple servers unnecessarily. */
149 CURL **target_handle;
153 debuginfod_write_callback (char *ptr, size_t size, size_t nmemb, void *data)
155 ssize_t count = size * nmemb;
157 struct handle_data *d = (struct handle_data*)data;
159 /* Indicate to other handles that they can abort their transfer. */
160 if (*d->target_handle == NULL)
162 *d->target_handle = d->handle;
163 /* update the client object */
164 const char *url = NULL;
165 (void) curl_easy_getinfo (d->handle, CURLINFO_EFFECTIVE_URL, &url);
168 free (d->client->url);
169 d->client->url = strdup(url); /* ok if fails */
173 /* If this handle isn't the target handle, abort transfer. */
174 if (*d->target_handle != d->handle)
177 return (size_t) write(d->fd, (void*)ptr, count);
180 /* Create the cache and interval file if they do not already exist.
181 Return 0 if cache and config file are initialized, otherwise return
182 the appropriate error code. */
184 debuginfod_init_cache (char *cache_path, char *interval_path, char *maxage_path)
188 /* If the cache and config file already exist then we are done. */
189 if (stat(cache_path, &st) == 0 && stat(interval_path, &st) == 0)
192 /* Create the cache and config files as necessary. */
193 if (stat(cache_path, &st) != 0 && mkdir(cache_path, 0777) < 0)
198 /* init cleaning interval config file. */
199 fd = open(interval_path, O_CREAT | O_RDWR, 0666);
203 if (dprintf(fd, "%ld", cache_clean_default_interval_s) < 0)
206 /* init max age config file. */
207 if (stat(maxage_path, &st) != 0
208 && (fd = open(maxage_path, O_CREAT | O_RDWR, 0666)) < 0)
211 if (dprintf(fd, "%ld", cache_default_max_unused_age_s) < 0)
218 /* Delete any files that have been unmodied for a period
219 longer than $DEBUGINFOD_CACHE_CLEAN_INTERVAL_S. */
221 debuginfod_clean_cache(debuginfod_client *c,
222 char *cache_path, char *interval_path,
223 char *max_unused_path)
227 FILE *max_unused_file;
229 if (stat(interval_path, &st) == -1)
231 /* Create new interval file. */
232 interval_file = fopen(interval_path, "w");
234 if (interval_file == NULL)
237 int rc = fprintf(interval_file, "%ld", cache_clean_default_interval_s);
238 fclose(interval_file);
244 /* Check timestamp of interval file to see whether cleaning is necessary. */
245 time_t clean_interval;
246 interval_file = fopen(interval_path, "r");
247 if (fscanf(interval_file, "%ld", &clean_interval) != 1)
248 clean_interval = cache_clean_default_interval_s;
249 fclose(interval_file);
251 if (time(NULL) - st.st_mtime < clean_interval)
252 /* Interval has not passed, skip cleaning. */
255 /* Read max unused age value from config file. */
256 time_t max_unused_age;
257 max_unused_file = fopen(max_unused_path, "r");
260 if (fscanf(max_unused_file, "%ld", &max_unused_age) != 1)
261 max_unused_age = cache_default_max_unused_age_s;
262 fclose(max_unused_file);
265 max_unused_age = cache_default_max_unused_age_s;
267 char * const dirs[] = { cache_path, NULL, };
269 FTS *fts = fts_open(dirs, 0, NULL);
274 const char * pattern = ".*/[a-f0-9]+/(debuginfo|executable|source.*)$";
275 if (regcomp (&re, pattern, REG_EXTENDED | REG_NOSUB) != 0)
280 while ((f = fts_read(fts)) != NULL)
282 /* ignore any files that do not match the pattern. */
283 if (regexec (&re, f->fts_path, 0, NULL, 0) != 0)
287 if (c->progressfn) /* inform/check progress callback */
288 if ((c->progressfn) (c, files, 0))
294 /* delete file if max_unused_age has been met or exceeded. */
295 /* XXX consider extra effort to clean up old tmp files */
296 if (time(NULL) - f->fts_statp->st_atime >= max_unused_age)
297 unlink (f->fts_path);
301 /* Remove if empty. */
302 (void) rmdir (f->fts_path);
312 /* Update timestamp representing when the cache was last cleaned. */
313 utime (interval_path, NULL);
318 #define MAX_BUILD_ID_BYTES 64
322 add_default_headers(debuginfod_client *client)
324 if (client->user_agent_set_p)
327 /* Compute a User-Agent: string to send. The more accurately this
328 describes this host, the likelier that the debuginfod servers
329 might be able to locate debuginfo for us. */
331 char* utspart = NULL;
336 rc = asprintf(& utspart, "%s/%s", uts.sysname, uts.machine);
340 FILE *f = fopen ("/etc/os-release", "r");
342 f = fopen ("/usr/lib/os-release", "r");
344 char *version = NULL;
347 while (id == NULL || version == NULL)
351 if (fgets (s, sizeof(buf), f) == NULL)
354 int len = strlen (s);
357 if (s[len - 1] == '\n')
363 char *v = strchr (s, '=');
364 if (v == NULL || strlen (v) < 2)
367 /* Split var and value. */
371 /* Remove optional quotes around value string. */
372 if (*v == '"' || *v == '\'')
377 if (strcmp (s, "ID") == 0)
379 if (strcmp (s, "VERSION_ID") == 0)
380 version = strdup (v);
386 rc = asprintf(& ua, "User-Agent: %s/%s,%s,%s/%s",
387 PACKAGE_NAME, PACKAGE_VERSION,
395 (void) debuginfod_add_http_header (client, ua);
404 #define xalloc_str(p, fmt, args...) \
407 if (asprintf (&p, fmt, args) < 0) \
416 /* Offer a basic form of progress tracing */
418 default_progressfn (debuginfod_client *c, long a, long b)
420 const char* url = debuginfod_get_url (c);
423 /* We prefer to print the host part of the URL to keep the
427 const char* buildid = strstr(url, "buildid/");
429 len = (buildid - url);
434 if (b == 0 || url==NULL) /* early stage */
435 dprintf(STDERR_FILENO,
436 "\rDownloading %c", "-/|\\"[a % 4]);
437 else if (b < 0) /* download in progress but unknown total length */
438 dprintf(STDERR_FILENO,
439 "\rDownloading from %.*s %ld",
441 else /* download in progress, and known total length */
442 dprintf(STDERR_FILENO,
443 "\rDownloading from %.*s %ld/%ld",
445 c->default_progressfn_printed_p = 1;
451 /* Query each of the server URLs found in $DEBUGINFOD_URLS for the file
452 with the specified build-id, type (debuginfo, executable or source)
453 and filename. filename may be NULL. If found, return a file
454 descriptor for the target, otherwise return an error code.
457 debuginfod_query_server (debuginfod_client *c,
458 const unsigned char *build_id,
461 const char *filename,
466 char *cache_path = NULL;
467 char *maxage_path = NULL;
468 char *interval_path = NULL;
469 char *target_cache_dir = NULL;
470 char *target_cache_path = NULL;
471 char *target_cache_tmppath = NULL;
472 char suffix[PATH_MAX];
473 char build_id_bytes[MAX_BUILD_ID_BYTES * 2 + 1];
476 /* Clear the obsolete URL from a previous _find operation. */
480 add_default_headers(c);
482 /* Is there any server we can query? If not, don't do any work,
483 just return with ENOSYS. Don't even access the cache. */
484 urls_envvar = getenv(server_urls_envvar);
485 if (urls_envvar == NULL || urls_envvar[0] == '\0')
491 /* Copy lowercase hex representation of build_id into buf. */
492 if ((build_id_len >= MAX_BUILD_ID_BYTES) ||
493 (build_id_len == 0 &&
494 sizeof(build_id_bytes) > MAX_BUILD_ID_BYTES*2 + 1))
496 if (build_id_len == 0) /* expect clean hexadecimal */
497 strcpy (build_id_bytes, (const char *) build_id);
499 for (int i = 0; i < build_id_len; i++)
500 sprintf(build_id_bytes + (i * 2), "%02x", build_id[i]);
502 if (filename != NULL)
504 if (filename[0] != '/') // must start with /
507 /* copy the filename to suffix, s,/,#,g */
509 for (unsigned fi=0; q < PATH_MAX-1; fi++)
510 switch (filename[fi])
514 q = PATH_MAX-1; /* escape for loop too */
516 case '/': /* escape / to prevent dir escape */
520 case '#': /* escape # to prevent /# vs #/ collisions */
525 suffix[q++]=filename[fi];
528 /* If the DWARF filenames are super long, this could exceed
529 PATH_MAX and truncate/collide. Oh well, that'll teach
535 /* set paths needed to perform the query
538 cache_path: $HOME/.cache
539 target_cache_dir: $HOME/.cache/0123abcd
540 target_cache_path: $HOME/.cache/0123abcd/debuginfo
541 target_cache_path: $HOME/.cache/0123abcd/source#PATH#TO#SOURCE ?
543 $XDG_CACHE_HOME takes priority over $HOME/.cache.
544 $DEBUGINFOD_CACHE_PATH takes priority over $HOME/.cache and $XDG_CACHE_HOME.
547 /* Determine location of the cache. The path specified by the debuginfod
548 cache environment variable takes priority. */
549 char *cache_var = getenv(cache_path_envvar);
550 if (cache_var != NULL && strlen (cache_var) > 0)
551 xalloc_str (cache_path, "%s", cache_var);
554 /* If a cache already exists in $HOME ('/' if $HOME isn't set), then use
555 that. Otherwise use the XDG cache directory naming format. */
556 xalloc_str (cache_path, "%s/%s", getenv ("HOME") ?: "/", cache_default_name);
559 if (stat (cache_path, &st) < 0)
561 char cachedir[PATH_MAX];
562 char *xdg = getenv ("XDG_CACHE_HOME");
564 if (xdg != NULL && strlen (xdg) > 0)
565 snprintf (cachedir, PATH_MAX, "%s", xdg);
567 snprintf (cachedir, PATH_MAX, "%s/.cache", getenv ("HOME") ?: "/");
569 /* Create XDG cache directory if it doesn't exist. */
570 if (stat (cachedir, &st) == 0)
572 if (! S_ISDIR (st.st_mode))
580 rc = mkdir (cachedir, 0700);
582 /* Also check for EEXIST and S_ISDIR in case another client just
583 happened to create the cache. */
586 || stat (cachedir, &st) != 0
587 || ! S_ISDIR (st.st_mode)))
595 xalloc_str (cache_path, "%s/%s", cachedir, cache_xdg_name);
599 xalloc_str (target_cache_dir, "%s/%s", cache_path, build_id_bytes);
600 xalloc_str (target_cache_path, "%s/%s%s", target_cache_dir, type, suffix);
601 xalloc_str (target_cache_tmppath, "%s.XXXXXX", target_cache_path);
603 /* XXX combine these */
604 xalloc_str (interval_path, "%s/%s", cache_path, cache_clean_interval_filename);
605 xalloc_str (maxage_path, "%s/%s", cache_path, cache_max_unused_age_filename);
606 rc = debuginfod_init_cache(cache_path, interval_path, maxage_path);
609 rc = debuginfod_clean_cache(c, cache_path, interval_path, maxage_path);
613 /* If the target is already in the cache then we are done. */
614 int fd = open (target_cache_path, O_RDONLY);
619 *path = strdup(target_cache_path);
624 long timeout = default_timeout;
625 const char* timeout_envvar = getenv(server_timeout_envvar);
626 if (timeout_envvar != NULL)
627 timeout = atoi (timeout_envvar);
629 /* make a copy of the envvar so it can be safely modified. */
630 server_urls = strdup(urls_envvar);
631 if (server_urls == NULL)
636 /* thereafter, goto out0 on error*/
638 /* create target directory in cache if not found. */
640 if (stat(target_cache_dir, &st) == -1 && mkdir(target_cache_dir, 0700) < 0)
646 /* NB: write to a temporary file first, to avoid race condition of
647 multiple clients checking the cache, while a partially-written or empty
648 file is in there, being written from libcurl. */
649 fd = mkstemp (target_cache_tmppath);
656 /* Count number of URLs. */
658 for (int i = 0; server_urls[i] != '\0'; i++)
659 if (server_urls[i] != url_delim_char
660 && (i == 0 || server_urls[i - 1] == url_delim_char))
663 /* Tracks which handle should write to fd. Set to the first
664 handle that is ready to write the target file to the cache. */
665 CURL *target_handle = NULL;
666 struct handle_data *data = malloc(sizeof(struct handle_data) * num_urls);
668 /* Initalize handle_data with default values. */
669 for (int i = 0; i < num_urls; i++)
671 data[i].handle = NULL;
675 CURLM *curlm = curl_multi_init();
681 /* thereafter, goto out1 on error. */
683 char *strtok_saveptr;
684 char *server_url = strtok_r(server_urls, url_delim, &strtok_saveptr);
686 /* Initialize each handle. */
687 for (int i = 0; i < num_urls && server_url != NULL; i++)
690 data[i].target_handle = &target_handle;
691 data[i].handle = curl_easy_init();
694 if (data[i].handle == NULL)
700 /* Build handle url. Tolerate both http://foo:999 and
701 http://foo:999/ forms */
703 if (strlen(server_url) > 1 && server_url[strlen(server_url)-1] == '/')
704 slashbuildid = "buildid";
706 slashbuildid = "/buildid";
708 if (filename) /* must start with / */
709 snprintf(data[i].url, PATH_MAX, "%s%s/%s/%s%s", server_url,
710 slashbuildid, build_id_bytes, type, filename);
712 snprintf(data[i].url, PATH_MAX, "%s%s/%s/%s", server_url,
713 slashbuildid, build_id_bytes, type);
715 curl_easy_setopt(data[i].handle, CURLOPT_URL, data[i].url);
716 curl_easy_setopt(data[i].handle,
717 CURLOPT_WRITEFUNCTION,
718 debuginfod_write_callback);
719 curl_easy_setopt(data[i].handle, CURLOPT_WRITEDATA, (void*)&data[i]);
722 /* Make sure there is at least some progress,
723 try to get at least 100K per timeout seconds. */
724 curl_easy_setopt (data[i].handle, CURLOPT_LOW_SPEED_TIME,
726 curl_easy_setopt (data[i].handle, CURLOPT_LOW_SPEED_LIMIT,
729 curl_easy_setopt(data[i].handle, CURLOPT_FILETIME, (long) 1);
730 curl_easy_setopt(data[i].handle, CURLOPT_FOLLOWLOCATION, (long) 1);
731 curl_easy_setopt(data[i].handle, CURLOPT_FAILONERROR, (long) 1);
732 curl_easy_setopt(data[i].handle, CURLOPT_NOSIGNAL, (long) 1);
733 #if LIBCURL_VERSION_NUM >= 0x072a00 /* 7.42.0 */
734 curl_easy_setopt(data[i].handle, CURLOPT_PATH_AS_IS, (long) 1);
736 /* On old curl; no big deal, canonicalization here is almost the
737 same, except perhaps for ? # type decorations at the tail. */
739 curl_easy_setopt(data[i].handle, CURLOPT_AUTOREFERER, (long) 1);
740 curl_easy_setopt(data[i].handle, CURLOPT_ACCEPT_ENCODING, "");
741 curl_easy_setopt(data[i].handle, CURLOPT_HTTPHEADER, c->headers);
743 curl_multi_add_handle(curlm, data[i].handle);
744 server_url = strtok_r(NULL, url_delim, &strtok_saveptr);
747 /* Query servers in parallel. */
752 /* Wait 1 second, the minimum DEBUGINFOD_TIMEOUT. */
753 curl_multi_wait(curlm, NULL, 0, 1000, NULL);
755 /* If the target file has been found, abort the other queries. */
756 if (target_handle != NULL)
757 for (int i = 0; i < num_urls; i++)
758 if (data[i].handle != target_handle)
759 curl_multi_remove_handle(curlm, data[i].handle);
761 CURLMcode curlm_res = curl_multi_perform(curlm, &still_running);
762 if (curlm_res != CURLM_OK)
766 case CURLM_CALL_MULTI_PERFORM: continue;
767 case CURLM_OUT_OF_MEMORY: rc = -ENOMEM; break;
768 default: rc = -ENETUNREACH; break;
773 if (c->progressfn) /* inform/check progress callback */
776 long pa = loops; /* default params for progress callback */
777 long pb = 0; /* transfer_timeout tempting, but loops != elapsed-time */
778 if (target_handle) /* we've committed to a server; report its download progress */
781 #ifdef CURLINFO_SIZE_DOWNLOAD_T
783 curl_res = curl_easy_getinfo(target_handle,
784 CURLINFO_SIZE_DOWNLOAD_T,
786 if (curl_res == 0 && dl >= 0)
787 pa = (dl > LONG_MAX ? LONG_MAX : (long)dl);
790 curl_res = curl_easy_getinfo(target_handle,
791 CURLINFO_SIZE_DOWNLOAD,
794 pa = (dl > LONG_MAX ? LONG_MAX : (long)dl);
797 /* NB: If going through deflate-compressing proxies, this
798 number is likely to be unavailable, so -1 may show. */
799 #ifdef CURLINFO_CURLINFO_CONTENT_LENGTH_DOWNLOAD_T
801 curl_res = curl_easy_getinfo(target_handle,
802 CURLINFO_CONTENT_LENGTH_DOWNLOAD_T,
804 if (curl_res == 0 && cl >= 0)
805 pb = (cl > LONG_MAX ? LONG_MAX : (long)cl);
808 curl_res = curl_easy_getinfo(target_handle,
809 CURLINFO_CONTENT_LENGTH_DOWNLOAD,
812 pb = (cl > LONG_MAX ? LONG_MAX : (long)cl);
816 if ((*c->progressfn) (c, pa, pb))
819 } while (still_running);
821 /* Check whether a query was successful. If so, assign its handle
822 to verified_handle. */
825 CURL *verified_handle = NULL;
830 msg = curl_multi_info_read(curlm, &num_msg);
831 if (msg != NULL && msg->msg == CURLMSG_DONE)
833 if (msg->data.result != CURLE_OK)
835 /* Unsucessful query, determine error code. */
836 switch (msg->data.result)
838 case CURLE_COULDNT_RESOLVE_HOST: rc = -EHOSTUNREACH; break; // no NXDOMAIN
839 case CURLE_URL_MALFORMAT: rc = -EINVAL; break;
840 case CURLE_COULDNT_CONNECT: rc = -ECONNREFUSED; break;
841 case CURLE_REMOTE_ACCESS_DENIED: rc = -EACCES; break;
842 case CURLE_WRITE_ERROR: rc = -EIO; break;
843 case CURLE_OUT_OF_MEMORY: rc = -ENOMEM; break;
844 case CURLE_TOO_MANY_REDIRECTS: rc = -EMLINK; break;
845 case CURLE_SEND_ERROR: rc = -ECONNRESET; break;
846 case CURLE_RECV_ERROR: rc = -ECONNRESET; break;
847 case CURLE_OPERATION_TIMEDOUT: rc = -ETIME; break;
848 default: rc = -ENOENT; break;
853 /* Query completed without an error. Confirm that the
854 response code is 200 when using HTTP/HTTPS and 0 when
855 using file:// and set verified_handle. */
857 if (msg->easy_handle != NULL)
859 char *effective_url = NULL;
860 long resp_code = 500;
861 CURLcode ok1 = curl_easy_getinfo (target_handle,
862 CURLINFO_EFFECTIVE_URL,
864 CURLcode ok2 = curl_easy_getinfo (target_handle,
865 CURLINFO_RESPONSE_CODE,
867 if(ok1 == CURLE_OK && ok2 == CURLE_OK && effective_url)
869 if (strncmp (effective_url, "http", 4) == 0)
870 if (resp_code == 200)
872 verified_handle = msg->easy_handle;
875 if (strncmp (effective_url, "file", 4) == 0)
878 verified_handle = msg->easy_handle;
885 } while (num_msg > 0);
887 if (verified_handle == NULL)
890 /* we've got one!!!! */
892 CURLcode curl_res = curl_easy_getinfo(verified_handle, CURLINFO_FILETIME, (void*) &mtime);
893 if (curl_res != CURLE_OK)
894 mtime = time(NULL); /* fall back to current time */
896 struct timeval tvs[2];
897 tvs[0].tv_sec = tvs[1].tv_sec = mtime;
898 tvs[0].tv_usec = tvs[1].tv_usec = 0;
899 (void) futimes (fd, tvs); /* best effort */
901 /* rename tmp->real */
902 rc = rename (target_cache_tmppath, target_cache_path);
907 /* Perhaps we need not give up right away; could retry or something ... */
911 for (int i = 0; i < num_urls; i++)
912 curl_easy_cleanup(data[i].handle);
914 curl_multi_cleanup (curlm);
918 /* don't close fd - we're returning it */
919 /* don't unlink the tmppath; it's already been renamed. */
921 *path = strdup(target_cache_path);
928 for (int i = 0; i < num_urls; i++)
929 curl_easy_cleanup(data[i].handle);
931 curl_multi_cleanup(curlm);
932 unlink (target_cache_tmppath);
933 close (fd); /* before the rmdir, otherwise it'll fail */
934 (void) rmdir (target_cache_dir); /* nop if not empty */
940 /* general purpose exit */
942 /* Conclude the last \r status line */
943 /* Another possibility is to use the ANSI CSI n K EL "Erase in Line"
944 code. That way, the previously printed messages would be erased,
945 and without a newline. */
946 if (c->default_progressfn_printed_p)
947 dprintf(STDERR_FILENO, "\n");
951 free (interval_path);
952 free (target_cache_dir);
953 free (target_cache_path);
954 free (target_cache_tmppath);
960 /* See debuginfod.h */
962 debuginfod_begin (void)
964 debuginfod_client *client;
965 size_t size = sizeof (struct debuginfod_client);
966 client = (debuginfod_client *) calloc (1, size);
969 if (getenv(DEBUGINFOD_PROGRESS_ENV_VAR))
970 client->progressfn = default_progressfn;
976 debuginfod_set_user_data(debuginfod_client *client,
979 client->user_data = data;
983 debuginfod_get_user_data(debuginfod_client *client)
985 return client->user_data;
989 debuginfod_get_url(debuginfod_client *client)
995 debuginfod_end (debuginfod_client *client)
1000 curl_slist_free_all (client->headers);
1006 debuginfod_find_debuginfo (debuginfod_client *client,
1007 const unsigned char *build_id, int build_id_len,
1010 return debuginfod_query_server(client, build_id, build_id_len,
1011 "debuginfo", NULL, path);
1015 /* See debuginfod.h */
1017 debuginfod_find_executable(debuginfod_client *client,
1018 const unsigned char *build_id, int build_id_len,
1021 return debuginfod_query_server(client, build_id, build_id_len,
1022 "executable", NULL, path);
1025 /* See debuginfod.h */
1026 int debuginfod_find_source(debuginfod_client *client,
1027 const unsigned char *build_id, int build_id_len,
1028 const char *filename, char **path)
1030 return debuginfod_query_server(client, build_id, build_id_len,
1031 "source", filename, path);
1035 /* Add an outgoing HTTP header. */
1036 int debuginfod_add_http_header (debuginfod_client *client, const char* header)
1038 /* Sanity check header value is of the form Header: Value.
1039 It should contain exactly one colon that isn't the first or
1041 char *colon = strchr (header, ':');
1044 || *(colon + 1) == '\0'
1045 || strchr (colon + 1, ':') != NULL)
1048 struct curl_slist *temp = curl_slist_append (client->headers, header);
1052 /* Track if User-Agent: is being set. If so, signal not to add the
1054 if (strncmp (header, "User-Agent:", 11) == 0)
1055 client->user_agent_set_p = 1;
1057 client->headers = temp;
1063 debuginfod_set_progressfn(debuginfod_client *client,
1064 debuginfod_progressfn_t fn)
1066 client->progressfn = fn;
1070 /* NB: these are thread-unsafe. */
1071 __attribute__((constructor)) attribute_hidden void libdebuginfod_ctor(void)
1073 curl_global_init(CURL_GLOBAL_DEFAULT);
1076 /* NB: this is very thread-unsafe: it breaks other threads that are still in libcurl */
1077 __attribute__((destructor)) attribute_hidden void libdebuginfod_dtor(void)
1079 /* ... so don't do this: */
1080 /* curl_global_cleanup(); */