Branch data Line data Source code
1 : : /* Retrieve ELF / DWARF / source files from the debuginfod.
2 : : Copyright (C) 2019-2020 Red Hat, Inc.
3 : : This file is part of elfutils.
4 : :
5 : : This file is free software; you can redistribute it and/or modify
6 : : it under the terms of either
7 : :
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
11 : :
12 : : or
13 : :
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
17 : :
18 : : or both in parallel, as here.
19 : :
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.
24 : :
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/>. */
28 : :
29 : :
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
35 : : contained. */
36 : : #ifdef BAD_FTS
37 : : #include <sys/types.h>
38 : : #include <fts.h>
39 : : #endif
40 : :
41 : : #include "config.h"
42 : : #include "debuginfod.h"
43 : : #include "system.h"
44 : : #include <errno.h>
45 : : #include <stdlib.h>
46 : :
47 : : /* We might be building a bootstrap dummy library, which is really simple. */
48 : : #ifdef DUMMY_LIBDEBUGINFOD
49 : :
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) { }
66 : :
67 : : #else /* DUMMY_LIBDEBUGINFOD */
68 : :
69 : : #include <assert.h>
70 : : #include <dirent.h>
71 : : #include <stdio.h>
72 : : #include <errno.h>
73 : : #include <unistd.h>
74 : : #include <fcntl.h>
75 : : #include <fts.h>
76 : : #include <regex.h>
77 : : #include <string.h>
78 : : #include <stdbool.h>
79 : : #include <linux/limits.h>
80 : : #include <time.h>
81 : : #include <utime.h>
82 : : #include <sys/syscall.h>
83 : : #include <sys/types.h>
84 : : #include <sys/stat.h>
85 : : #include <sys/utsname.h>
86 : : #include <curl/curl.h>
87 : :
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. */
90 : : #ifdef BAD_FTS
91 : : #ifdef _FILE_OFFSET_BITS
92 : : #define open open64
93 : : #define fopen fopen64
94 : : #endif
95 : : #else
96 : : #include <sys/types.h>
97 : : #include <fts.h>
98 : : #endif
99 : :
100 : : struct debuginfod_client
101 : : {
102 : : /* Progress/interrupt callback function. */
103 : : debuginfod_progressfn_t progressfn;
104 : :
105 : : /* Stores user data. */
106 : : void* user_data;
107 : :
108 : : /* Stores current/last url, if any. */
109 : : char* url;
110 : :
111 : : /* Accumulates outgoing http header names/values. */
112 : : int user_agent_set_p; /* affects add_default_headers */
113 : : struct curl_slist *headers;
114 : :
115 : : /* Flags the default_progressfn having printed something that
116 : : debuginfod_end needs to terminate. */
117 : : int default_progressfn_printed_p;
118 : :
119 : : /* File descriptor to output any verbose messages if > 0. */
120 : : int verbose_fd;
121 : :
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. */
126 : : };
127 : :
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 */
133 : :
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 */
138 : :
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;
144 : :
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 = ' ';
149 : :
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;
153 : :
154 : :
155 : : /* Data associated with a particular CURL easy handle. Passed to
156 : : the write callback. */
157 : : struct handle_data
158 : : {
159 : : /* Cache file to be written to in case query is successful. */
160 : : int fd;
161 : :
162 : : /* URL queried by this handle. */
163 : : char url[PATH_MAX];
164 : :
165 : : /* error buffer for this handle. */
166 : : char errbuf[CURL_ERROR_SIZE];
167 : :
168 : : /* This handle. */
169 : : CURL *handle;
170 : :
171 : : /* The client object whom we're serving. */
172 : : debuginfod_client *client;
173 : :
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;
179 : : };
180 : :
181 : : static size_t
182 : 729 : debuginfod_write_callback (char *ptr, size_t size, size_t nmemb, void *data)
183 : : {
184 : 729 : ssize_t count = size * nmemb;
185 : :
186 : 729 : struct handle_data *d = (struct handle_data*)data;
187 : :
188 : : /* Indicate to other handles that they can abort their transfer. */
189 [ + + ]: 729 : if (*d->target_handle == NULL)
190 : : {
191 : 44 : *d->target_handle = d->handle;
192 : : /* update the client object */
193 : 44 : const char *url = NULL;
194 : 44 : (void) curl_easy_getinfo (d->handle, CURLINFO_EFFECTIVE_URL, &url);
195 [ + - ]: 44 : if (url)
196 : : {
197 : 44 : free (d->client->url);
198 : 44 : d->client->url = strdup(url); /* ok if fails */
199 : : }
200 : : }
201 : :
202 : : /* If this handle isn't the target handle, abort transfer. */
203 [ + - ]: 729 : if (*d->target_handle != d->handle)
204 : : return -1;
205 : :
206 : 729 : return (size_t) write(d->fd, (void*)ptr, count);
207 : : }
208 : :
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. */
212 : : static int
213 : 60 : debuginfod_init_cache (char *cache_path, char *interval_path, char *maxage_path)
214 : : {
215 : 60 : struct stat st;
216 : :
217 : : /* If the cache and config file already exist then we are done. */
218 [ + + ][ - + ]: 60 : if (stat(cache_path, &st) == 0 && stat(interval_path, &st) == 0)
219 : : return 0;
220 : :
221 : : /* Create the cache and config files as necessary. */
222 [ + - ][ - + ]: 8 : if (stat(cache_path, &st) != 0 && mkdir(cache_path, ACCESSPERMS) < 0)
223 : 0 : return -errno;
224 : :
225 : 8 : int fd = -1;
226 : :
227 : : /* init cleaning interval config file. */
228 : 8 : fd = open(interval_path, O_CREAT | O_RDWR, DEFFILEMODE);
229 [ - + ]: 8 : if (fd < 0)
230 : 0 : return -errno;
231 : :
232 [ - + ]: 8 : if (dprintf(fd, "%ld", cache_clean_default_interval_s) < 0)
233 : 0 : return -errno;
234 : :
235 : : /* init max age config file. */
236 [ + - ]: 8 : if (stat(maxage_path, &st) != 0
237 [ - + ]: 8 : && (fd = open(maxage_path, O_CREAT | O_RDWR, DEFFILEMODE)) < 0)
238 : 0 : return -errno;
239 : :
240 [ - + ]: 8 : if (dprintf(fd, "%ld", cache_default_max_unused_age_s) < 0)
241 : 0 : return -errno;
242 : :
243 : : return 0;
244 : : }
245 : :
246 : :
247 : : /* Delete any files that have been unmodied for a period
248 : : longer than $DEBUGINFOD_CACHE_CLEAN_INTERVAL_S. */
249 : : static int
250 : 60 : debuginfod_clean_cache(debuginfod_client *c,
251 : : char *cache_path, char *interval_path,
252 : : char *max_unused_path)
253 : : {
254 : 60 : struct stat st;
255 : 60 : FILE *interval_file;
256 : 60 : FILE *max_unused_file;
257 : :
258 [ - + ]: 60 : if (stat(interval_path, &st) == -1)
259 : : {
260 : : /* Create new interval file. */
261 : 0 : interval_file = fopen(interval_path, "w");
262 : :
263 [ # # ]: 0 : if (interval_file == NULL)
264 : 0 : return -errno;
265 : :
266 : 0 : int rc = fprintf(interval_file, "%ld", cache_clean_default_interval_s);
267 : 0 : fclose(interval_file);
268 : :
269 [ # # ]: 0 : if (rc < 0)
270 : 0 : return -errno;
271 : : }
272 : :
273 : : /* Check timestamp of interval file to see whether cleaning is necessary. */
274 : 60 : time_t clean_interval;
275 : 60 : interval_file = fopen(interval_path, "r");
276 [ + - ]: 60 : if (interval_file)
277 : : {
278 [ - + ]: 60 : if (fscanf(interval_file, "%ld", &clean_interval) != 1)
279 : 0 : clean_interval = cache_clean_default_interval_s;
280 : 60 : fclose(interval_file);
281 : : }
282 : : else
283 : 0 : clean_interval = cache_clean_default_interval_s;
284 : :
285 [ + + ]: 60 : if (time(NULL) - st.st_mtime < clean_interval)
286 : : /* Interval has not passed, skip cleaning. */
287 : : return 0;
288 : :
289 : : /* Read max unused age value from config file. */
290 : 2 : time_t max_unused_age;
291 : 2 : max_unused_file = fopen(max_unused_path, "r");
292 [ + - ]: 2 : if (max_unused_file)
293 : : {
294 [ - + ]: 2 : if (fscanf(max_unused_file, "%ld", &max_unused_age) != 1)
295 : 0 : max_unused_age = cache_default_max_unused_age_s;
296 : 2 : fclose(max_unused_file);
297 : : }
298 : : else
299 : 0 : max_unused_age = cache_default_max_unused_age_s;
300 : :
301 : 2 : char * const dirs[] = { cache_path, NULL, };
302 : :
303 : 2 : FTS *fts = fts_open(dirs, 0, NULL);
304 [ - + ]: 2 : if (fts == NULL)
305 : 0 : return -errno;
306 : :
307 : 2 : regex_t re;
308 : 2 : const char * pattern = ".*/[a-f0-9]+/(debuginfo|executable|source.*)$";
309 [ + - ]: 2 : if (regcomp (&re, pattern, REG_EXTENDED | REG_NOSUB) != 0)
310 : : return -ENOMEM;
311 : :
312 : : FTSENT *f;
313 : : long files = 0;
314 [ + + ]: 18 : while ((f = fts_read(fts)) != NULL)
315 : : {
316 : : /* ignore any files that do not match the pattern. */
317 [ - + ]: 16 : if (regexec (&re, f->fts_path, 0, NULL, 0) != 0)
318 : : continue;
319 : :
320 : 0 : files++;
321 [ # # ]: 0 : if (c->progressfn) /* inform/check progress callback */
322 [ # # ]: 0 : if ((c->progressfn) (c, files, 0))
323 : : break;
324 : :
325 [ # # # ]: 0 : switch (f->fts_info)
326 : : {
327 : 0 : case FTS_F:
328 : : /* delete file if max_unused_age has been met or exceeded. */
329 : : /* XXX consider extra effort to clean up old tmp files */
330 [ # # ]: 0 : if (time(NULL) - f->fts_statp->st_atime >= max_unused_age)
331 : 0 : unlink (f->fts_path);
332 : : break;
333 : :
334 : 0 : case FTS_DP:
335 : : /* Remove if empty. */
336 : 0 : (void) rmdir (f->fts_path);
337 : 0 : break;
338 : :
339 : : default:
340 : 18 : ;
341 : : }
342 : : }
343 : 2 : fts_close (fts);
344 : 2 : regfree (&re);
345 : :
346 : : /* Update timestamp representing when the cache was last cleaned. */
347 : 2 : utime (interval_path, NULL);
348 : 2 : return 0;
349 : : }
350 : :
351 : :
352 : : #define MAX_BUILD_ID_BYTES 64
353 : :
354 : :
355 : : static void
356 : 60 : add_default_headers(debuginfod_client *client)
357 : : {
358 [ + + ]: 60 : if (client->user_agent_set_p)
359 : 3 : return;
360 : :
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. */
364 : :
365 : 57 : char* utspart = NULL;
366 : 57 : struct utsname uts;
367 : 57 : int rc = 0;
368 : 57 : rc = uname (&uts);
369 [ + - ]: 57 : if (rc == 0)
370 : 114 : rc = asprintf(& utspart, "%s/%s", uts.sysname, uts.machine);
371 [ - + ]: 57 : if (rc < 0)
372 : 0 : utspart = NULL;
373 : :
374 : 57 : FILE *f = fopen ("/etc/os-release", "r");
375 [ - + ]: 57 : if (f == NULL)
376 : 0 : f = fopen ("/usr/lib/os-release", "r");
377 : 57 : char *id = NULL;
378 : 57 : char *version = NULL;
379 [ + - ]: 57 : if (f != NULL)
380 : : {
381 [ + + ]: 342 : while (id == NULL || version == NULL)
382 : : {
383 : 285 : char buf[128];
384 : 285 : char *s = &buf[0];
385 [ + - ]: 285 : if (fgets (s, sizeof(buf), f) == NULL)
386 : : break;
387 : :
388 : 285 : int len = strlen (s);
389 [ + + ]: 285 : if (len < 3)
390 : 57 : continue;
391 [ + - ]: 228 : if (s[len - 1] == '\n')
392 : : {
393 : 228 : s[len - 1] = '\0';
394 : 228 : len--;
395 : : }
396 : :
397 : 228 : char *v = strchr (s, '=');
398 [ + - ][ + - ]: 228 : if (v == NULL || strlen (v) < 2)
399 : : continue;
400 : :
401 : : /* Split var and value. */
402 : 228 : *v = '\0';
403 : 228 : v++;
404 : :
405 : : /* Remove optional quotes around value string. */
406 [ - + ]: 228 : if (*v == '"' || *v == '\'')
407 : : {
408 : 0 : v++;
409 : 0 : s[len - 1] = '\0';
410 : : }
411 [ + + ]: 228 : if (strcmp (s, "ID") == 0)
412 : 57 : id = strdup (v);
413 [ + + ]: 228 : if (strcmp (s, "VERSION_ID") == 0)
414 : 57 : version = strdup (v);
415 : : }
416 : 57 : fclose (f);
417 : : }
418 : :
419 : 57 : char *ua = NULL;
420 [ + - ][ + - ]: 171 : rc = asprintf(& ua, "User-Agent: %s/%s,%s,%s/%s",
421 : : PACKAGE_NAME, PACKAGE_VERSION,
422 [ - + ]: 57 : utspart ?: "",
423 : : id ?: "",
424 : : version ?: "");
425 [ - + ]: 57 : if (rc < 0)
426 : 0 : ua = NULL;
427 : :
428 [ + - ]: 57 : if (ua)
429 : 57 : (void) debuginfod_add_http_header (client, ua);
430 : :
431 : 57 : free (ua);
432 : 57 : free (id);
433 : 57 : free (version);
434 : 57 : free (utspart);
435 : : }
436 : :
437 : :
438 : : #define xalloc_str(p, fmt, args...) \
439 : : do \
440 : : { \
441 : : if (asprintf (&p, fmt, args) < 0) \
442 : : { \
443 : : p = NULL; \
444 : : rc = -ENOMEM; \
445 : : goto out; \
446 : : } \
447 : : } while (0)
448 : :
449 : :
450 : : /* Offer a basic form of progress tracing */
451 : : static int
452 : 3 : default_progressfn (debuginfod_client *c, long a, long b)
453 : : {
454 : 6 : const char* url = debuginfod_get_url (c);
455 : 3 : int len = 0;
456 : :
457 : : /* We prefer to print the host part of the URL to keep the
458 : : message short. */
459 : 3 : if (url != NULL)
460 : : {
461 : 2 : const char* buildid = strstr(url, "buildid/");
462 [ + - ]: 2 : if (buildid != NULL)
463 : 2 : len = (buildid - url);
464 : : else
465 : 0 : len = strlen(url);
466 : : }
467 : :
468 [ + + ]: 3 : if (b == 0 || url==NULL) /* early stage */
469 : 2 : dprintf(STDERR_FILENO,
470 : 1 : "\rDownloading %c", "-/|\\"[a % 4]);
471 [ - + ]: 2 : else if (b < 0) /* download in progress but unknown total length */
472 : 0 : dprintf(STDERR_FILENO,
473 : : "\rDownloading from %.*s %ld",
474 : : len, url, a);
475 : : else /* download in progress, and known total length */
476 : 2 : dprintf(STDERR_FILENO,
477 : : "\rDownloading from %.*s %ld/%ld",
478 : : len, url, a, b);
479 : 3 : c->default_progressfn_printed_p = 1;
480 : :
481 : 3 : return 0;
482 : : }
483 : :
484 : :
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.
489 : : */
490 : : static int
491 : 67 : debuginfod_query_server (debuginfod_client *c,
492 : : const unsigned char *build_id,
493 : : int build_id_len,
494 : : const char *type,
495 : : const char *filename,
496 : : char **path)
497 : : {
498 : 67 : char *server_urls;
499 : 67 : char *urls_envvar;
500 : 67 : char *cache_path = NULL;
501 : 67 : char *maxage_path = NULL;
502 : 67 : char *interval_path = NULL;
503 : 67 : char *target_cache_dir = NULL;
504 : 67 : char *target_cache_path = NULL;
505 : 67 : char *target_cache_tmppath = NULL;
506 : 67 : char suffix[PATH_MAX + 1]; /* +1 for zero terminator. */
507 : 67 : char build_id_bytes[MAX_BUILD_ID_BYTES * 2 + 1];
508 : 67 : int vfd = c->verbose_fd;
509 : 67 : int rc;
510 : :
511 [ + + ]: 67 : if (vfd >= 0)
512 : : {
513 : 1 : dprintf (vfd, "debuginfod_find_%s ", type);
514 [ + - ]: 1 : if (build_id_len == 0) /* expect clean hexadecimal */
515 : 1 : dprintf (vfd, "%s", (const char *) build_id);
516 : : else
517 [ # # ]: 0 : for (int i = 0; i < build_id_len; i++)
518 : 0 : dprintf (vfd, "%02x", build_id[i]);
519 [ - + ]: 1 : if (filename != NULL)
520 : 0 : dprintf (vfd, " %s\n", filename);
521 : 1 : dprintf (vfd, "\n");
522 : : }
523 : :
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 : 67 : urls_envvar = getenv(server_urls_envvar);
527 [ + + ]: 67 : if (vfd >= 0)
528 [ - + ]: 1 : dprintf (vfd, "server urls \"%s\"\n",
529 : : urls_envvar != NULL ? urls_envvar : "");
530 [ + - ][ + + ]: 67 : if (urls_envvar == NULL || urls_envvar[0] == '\0')
531 : : {
532 : : rc = -ENOSYS;
533 : : goto out;
534 : : }
535 : :
536 : : /* Clear the obsolete URL from a previous _find operation. */
537 : 60 : free (c->url);
538 : 60 : c->url = NULL;
539 : :
540 : 60 : add_default_headers(c);
541 : :
542 : : /* Copy lowercase hex representation of build_id into buf. */
543 [ + + ]: 60 : if (vfd >= 0)
544 : 1 : dprintf (vfd, "checking build-id\n");
545 [ + - ][ + + ]: 60 : if ((build_id_len >= MAX_BUILD_ID_BYTES) ||
546 : 59 : (build_id_len == 0 &&
547 [ + - ]: 59 : strlen ((const char *) build_id) > MAX_BUILD_ID_BYTES*2))
548 : : {
549 : : rc = -EINVAL;
550 : : goto out;
551 : : }
552 : :
553 [ + + ]: 60 : if (build_id_len == 0) /* expect clean hexadecimal */
554 : 59 : strcpy (build_id_bytes, (const char *) build_id);
555 : : else
556 [ + + ]: 21 : for (int i = 0; i < build_id_len; i++)
557 : 40 : sprintf(build_id_bytes + (i * 2), "%02x", build_id[i]);
558 : :
559 [ + + ]: 60 : if (filename != NULL)
560 : : {
561 [ - + ]: 13 : if (vfd >= 0)
562 : 0 : dprintf (vfd, "checking filename\n");
563 [ + - ]: 13 : if (filename[0] != '/') // must start with /
564 : : {
565 : : rc = -EINVAL;
566 : : goto out;
567 : : }
568 : :
569 : : /* copy the filename to suffix, s,/,#,g */
570 : : unsigned q = 0;
571 [ + + ]: 510 : for (unsigned fi=0; q < PATH_MAX-2; fi++) /* -2, escape is 2 chars. */
572 [ + + - + ]: 497 : switch (filename[fi])
573 : : {
574 : 13 : case '\0':
575 : 13 : suffix[q] = '\0';
576 : 13 : q = PATH_MAX-1; /* escape for loop too */
577 : 13 : break;
578 : 70 : case '/': /* escape / to prevent dir escape */
579 : 70 : suffix[q++]='#';
580 : 70 : suffix[q++]='#';
581 : 70 : break;
582 : 0 : case '#': /* escape # to prevent /# vs #/ collisions */
583 : 0 : suffix[q++]='#';
584 : 0 : suffix[q++]='_';
585 : 0 : break;
586 : 414 : default:
587 : 414 : suffix[q++]=filename[fi];
588 : : }
589 : 13 : suffix[q] = '\0';
590 : : /* If the DWARF filenames are super long, this could exceed
591 : : PATH_MAX and truncate/collide. Oh well, that'll teach
592 : : them! */
593 : : }
594 : : else
595 : 47 : suffix[0] = '\0';
596 : :
597 [ + + ][ - + ]: 60 : if (suffix[0] != '\0' && vfd >= 0)
598 : 0 : dprintf (vfd, "suffix %s\n", suffix);
599 : :
600 : : /* set paths needed to perform the query
601 : :
602 : : example format
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 ?
607 : :
608 : : $XDG_CACHE_HOME takes priority over $HOME/.cache.
609 : : $DEBUGINFOD_CACHE_PATH takes priority over $HOME/.cache and $XDG_CACHE_HOME.
610 : : */
611 : :
612 : : /* Determine location of the cache. The path specified by the debuginfod
613 : : cache environment variable takes priority. */
614 : 60 : char *cache_var = getenv(cache_path_envvar);
615 [ + - ][ + + ]: 60 : if (cache_var != NULL && strlen (cache_var) > 0)
616 [ - + ]: 56 : xalloc_str (cache_path, "%s", cache_var);
617 : : else
618 : : {
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 [ - + ][ - + ]: 4 : xalloc_str (cache_path, "%s/%s", getenv ("HOME") ?: "/", cache_default_name);
622 : :
623 : 4 : struct stat st;
624 [ + + ]: 4 : if (stat (cache_path, &st) < 0)
625 : : {
626 : 3 : char cachedir[PATH_MAX];
627 : 3 : char *xdg = getenv ("XDG_CACHE_HOME");
628 : :
629 [ + - ][ + + ]: 3 : if (xdg != NULL && strlen (xdg) > 0)
630 : 1 : snprintf (cachedir, PATH_MAX, "%s", xdg);
631 : : else
632 [ - + ]: 2 : snprintf (cachedir, PATH_MAX, "%s/.cache", getenv ("HOME") ?: "/");
633 : :
634 : : /* Create XDG cache directory if it doesn't exist. */
635 [ + + ]: 3 : if (stat (cachedir, &st) == 0)
636 : : {
637 [ + - ]: 1 : if (! S_ISDIR (st.st_mode))
638 : : {
639 : : rc = -EEXIST;
640 : 0 : goto out;
641 : : }
642 : : }
643 : : else
644 : : {
645 : 2 : rc = mkdir (cachedir, 0700);
646 : :
647 : : /* Also check for EEXIST and S_ISDIR in case another client just
648 : : happened to create the cache. */
649 [ - + ]: 2 : if (rc < 0
650 [ # # ]: 0 : && (errno != EEXIST
651 [ # # ]: 0 : || stat (cachedir, &st) != 0
652 [ # # ]: 0 : || ! S_ISDIR (st.st_mode)))
653 : : {
654 : 0 : rc = -errno;
655 : 0 : goto out;
656 : : }
657 : : }
658 : :
659 : 3 : free (cache_path);
660 [ - + ]: 3 : xalloc_str (cache_path, "%s/%s", cachedir, cache_xdg_name);
661 : : }
662 : : }
663 : :
664 [ - + ]: 60 : xalloc_str (target_cache_dir, "%s/%s", cache_path, build_id_bytes);
665 [ - + ]: 60 : xalloc_str (target_cache_path, "%s/%s%s", target_cache_dir, type, suffix);
666 [ - + ]: 60 : xalloc_str (target_cache_tmppath, "%s.XXXXXX", target_cache_path);
667 : :
668 : : /* XXX combine these */
669 [ - + ]: 60 : xalloc_str (interval_path, "%s/%s", cache_path, cache_clean_interval_filename);
670 [ - + ]: 60 : xalloc_str (maxage_path, "%s/%s", cache_path, cache_max_unused_age_filename);
671 : :
672 [ + + ]: 60 : if (vfd >= 0)
673 : 1 : dprintf (vfd, "checking cache dir %s\n", cache_path);
674 : :
675 : 60 : rc = debuginfod_init_cache(cache_path, interval_path, maxage_path);
676 [ + - ]: 60 : if (rc != 0)
677 : : goto out;
678 : 60 : rc = debuginfod_clean_cache(c, cache_path, interval_path, maxage_path);
679 [ + - ]: 60 : if (rc != 0)
680 : : goto out;
681 : :
682 : : /* If the target is already in the cache then we are done. */
683 : 60 : int fd = open (target_cache_path, O_RDONLY);
684 [ + + ]: 60 : if (fd >= 0)
685 : : {
686 : : /* Success!!!! */
687 [ + - ]: 10 : if (path != NULL)
688 : 10 : *path = strdup(target_cache_path);
689 : 10 : rc = fd;
690 : 10 : goto out;
691 : : }
692 : :
693 : 50 : long timeout = default_timeout;
694 : 50 : const char* timeout_envvar = getenv(server_timeout_envvar);
695 [ + - ]: 50 : if (timeout_envvar != NULL)
696 : 50 : timeout = atoi (timeout_envvar);
697 : :
698 [ + + ]: 50 : if (vfd >= 0)
699 : 1 : dprintf (vfd, "using timeout %ld\n", timeout);
700 : :
701 : : /* make a copy of the envvar so it can be safely modified. */
702 : 50 : server_urls = strdup(urls_envvar);
703 [ + - ]: 50 : if (server_urls == NULL)
704 : : {
705 : : rc = -ENOMEM;
706 : : goto out;
707 : : }
708 : : /* thereafter, goto out0 on error*/
709 : :
710 : : /* create target directory in cache if not found. */
711 : 50 : struct stat st;
712 [ + + ][ - + ]: 50 : if (stat(target_cache_dir, &st) == -1 && mkdir(target_cache_dir, 0700) < 0)
713 : : {
714 : 0 : rc = -errno;
715 : 0 : goto out0;
716 : : }
717 : :
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 : 50 : fd = mkstemp (target_cache_tmppath);
722 [ - + ]: 50 : if (fd < 0)
723 : : {
724 : 0 : rc = -errno;
725 : 0 : goto out0;
726 : : }
727 : :
728 : : /* Count number of URLs. */
729 : : int num_urls = 0;
730 [ + + ]: 1215 : for (int i = 0; server_urls[i] != '\0'; i++)
731 [ + + ]: 1165 : if (server_urls[i] != url_delim_char
732 [ + + ][ + + ]: 1161 : && (i == 0 || server_urls[i - 1] == url_delim_char))
733 : 54 : num_urls++;
734 : :
735 : 50 : CURLM *curlm = curl_multi_init();
736 [ + - ]: 50 : if (curlm == NULL)
737 : : {
738 : : rc = -ENETUNREACH;
739 : : goto out0;
740 : : }
741 : :
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 : 50 : CURL *target_handle = NULL;
745 : 50 : struct handle_data *data = malloc(sizeof(struct handle_data) * num_urls);
746 [ + - ]: 50 : if (data == NULL)
747 : : {
748 : : rc = -ENOMEM;
749 : : goto out0;
750 : : }
751 : :
752 : : /* thereafter, goto out1 on error. */
753 : :
754 : : /* Initialize handle_data with default values. */
755 [ + + ]: 104 : for (int i = 0; i < num_urls; i++)
756 : : {
757 : 54 : data[i].handle = NULL;
758 : 54 : data[i].fd = -1;
759 : 54 : data[i].errbuf[0] = '\0';
760 : : }
761 : :
762 : 50 : char *strtok_saveptr;
763 : 50 : char *server_url = strtok_r(server_urls, url_delim, &strtok_saveptr);
764 : :
765 : : /* Initialize each handle. */
766 [ + + ]: 104 : for (int i = 0; i < num_urls && server_url != NULL; i++)
767 : : {
768 [ + + ]: 54 : if (vfd >= 0)
769 : 1 : dprintf (vfd, "init server %d %s\n", i, server_url);
770 : :
771 : 54 : data[i].fd = fd;
772 : 54 : data[i].target_handle = &target_handle;
773 : 54 : data[i].handle = curl_easy_init();
774 : 54 : data[i].client = c;
775 : :
776 [ + - ]: 54 : if (data[i].handle == NULL)
777 : : {
778 : : rc = -ENETUNREACH;
779 : : goto out1;
780 : : }
781 : :
782 : : /* Build handle url. Tolerate both http://foo:999 and
783 : : http://foo:999/ forms */
784 : 54 : char *slashbuildid;
785 [ + - ][ + + ]: 54 : if (strlen(server_url) > 1 && server_url[strlen(server_url)-1] == '/')
786 : : slashbuildid = "buildid";
787 : : else
788 : 10 : slashbuildid = "/buildid";
789 : :
790 [ + + ]: 54 : if (filename) /* must start with / */
791 : 13 : snprintf(data[i].url, PATH_MAX, "%s%s/%s/%s%s", server_url,
792 : : slashbuildid, build_id_bytes, type, filename);
793 : : else
794 : 41 : snprintf(data[i].url, PATH_MAX, "%s%s/%s/%s", server_url,
795 : : slashbuildid, build_id_bytes, type);
796 : :
797 [ + + ]: 54 : if (vfd >= 0)
798 : 1 : dprintf (vfd, "url %d %s\n", i, data[i].url);
799 : :
800 : 54 : curl_easy_setopt(data[i].handle, CURLOPT_URL, data[i].url);
801 [ + + ]: 54 : if (vfd >= 0)
802 : 1 : curl_easy_setopt(data[i].handle, CURLOPT_ERRORBUFFER, data[i].errbuf);
803 : 54 : curl_easy_setopt(data[i].handle,
804 : : CURLOPT_WRITEFUNCTION,
805 : : debuginfod_write_callback);
806 : 54 : curl_easy_setopt(data[i].handle, CURLOPT_WRITEDATA, (void*)&data[i]);
807 [ + - ]: 54 : if (timeout > 0)
808 : : {
809 : : /* Make sure there is at least some progress,
810 : : try to get at least 100K per timeout seconds. */
811 : 54 : curl_easy_setopt (data[i].handle, CURLOPT_LOW_SPEED_TIME,
812 : : timeout);
813 : 54 : curl_easy_setopt (data[i].handle, CURLOPT_LOW_SPEED_LIMIT,
814 : : 100 * 1024L);
815 : : }
816 : 54 : curl_easy_setopt(data[i].handle, CURLOPT_FILETIME, (long) 1);
817 : 54 : curl_easy_setopt(data[i].handle, CURLOPT_FOLLOWLOCATION, (long) 1);
818 : 54 : curl_easy_setopt(data[i].handle, CURLOPT_FAILONERROR, (long) 1);
819 : 54 : curl_easy_setopt(data[i].handle, CURLOPT_NOSIGNAL, (long) 1);
820 : : #if LIBCURL_VERSION_NUM >= 0x072a00 /* 7.42.0 */
821 : 54 : curl_easy_setopt(data[i].handle, CURLOPT_PATH_AS_IS, (long) 1);
822 : : #else
823 : : /* On old curl; no big deal, canonicalization here is almost the
824 : : same, except perhaps for ? # type decorations at the tail. */
825 : : #endif
826 : 54 : curl_easy_setopt(data[i].handle, CURLOPT_AUTOREFERER, (long) 1);
827 : 54 : curl_easy_setopt(data[i].handle, CURLOPT_ACCEPT_ENCODING, "");
828 : 54 : curl_easy_setopt(data[i].handle, CURLOPT_HTTPHEADER, c->headers);
829 : :
830 : 54 : curl_multi_add_handle(curlm, data[i].handle);
831 : 54 : server_url = strtok_r(NULL, url_delim, &strtok_saveptr);
832 : : }
833 : :
834 : : /* Query servers in parallel. */
835 [ + + ]: 50 : if (vfd >= 0)
836 : 1 : dprintf (vfd, "query %d urls in parallel\n", num_urls);
837 : : int still_running;
838 : : long loops = 0;
839 : : int committed_to = -1;
840 : : bool verbose_reported = false;
841 : 6379 : do
842 : : {
843 : : /* Wait 1 second, the minimum DEBUGINFOD_TIMEOUT. */
844 : 6379 : curl_multi_wait(curlm, NULL, 0, 1000, NULL);
845 : :
846 : : /* If the target file has been found, abort the other queries. */
847 [ + + ]: 6379 : if (target_handle != NULL)
848 : : {
849 [ + + ]: 1370 : for (int i = 0; i < num_urls; i++)
850 [ - + ]: 685 : if (data[i].handle != target_handle)
851 : 0 : curl_multi_remove_handle(curlm, data[i].handle);
852 : : else
853 : : committed_to = i;
854 : : }
855 : :
856 [ + + ][ + + ]: 6379 : if (vfd >= 0 && !verbose_reported && committed_to >= 0)
857 : : {
858 [ - + # # ]: 1 : bool pnl = (c->default_progressfn_printed_p && vfd == STDERR_FILENO);
859 [ + - ]: 2 : dprintf (vfd, "%scommitted to url %d\n", pnl ? "\n" : "",
860 : : committed_to);
861 [ - + ]: 1 : if (pnl)
862 : 0 : c->default_progressfn_printed_p = 0;
863 : : verbose_reported = true;
864 : : }
865 : :
866 : 6379 : CURLMcode curlm_res = curl_multi_perform(curlm, &still_running);
867 [ - + ]: 6379 : if (curlm_res != CURLM_OK)
868 : : {
869 [ # # # ]: 0 : switch (curlm_res)
870 : : {
871 : : case CURLM_CALL_MULTI_PERFORM: continue;
872 : : case CURLM_OUT_OF_MEMORY: rc = -ENOMEM; break;
873 : 0 : default: rc = -ENETUNREACH; break;
874 : : }
875 : 0 : goto out1;
876 : : }
877 : :
878 [ + + ]: 6379 : if (c->progressfn) /* inform/check progress callback */
879 : : {
880 : 12 : loops ++;
881 : 12 : long pa = loops; /* default params for progress callback */
882 : 12 : long pb = 0; /* transfer_timeout tempting, but loops != elapsed-time */
883 [ + + ]: 12 : if (target_handle) /* we've committed to a server; report its download progress */
884 : : {
885 : 5 : CURLcode curl_res;
886 : : #ifdef CURLINFO_SIZE_DOWNLOAD_T
887 : : curl_off_t dl;
888 : : curl_res = curl_easy_getinfo(target_handle,
889 : : CURLINFO_SIZE_DOWNLOAD_T,
890 : : &dl);
891 : : if (curl_res == 0 && dl >= 0)
892 : : pa = (dl > LONG_MAX ? LONG_MAX : (long)dl);
893 : : #else
894 : 5 : double dl;
895 : 5 : curl_res = curl_easy_getinfo(target_handle,
896 : : CURLINFO_SIZE_DOWNLOAD,
897 : : &dl);
898 [ + - ]: 5 : if (curl_res == 0)
899 [ + - ]: 5 : pa = (dl > LONG_MAX ? LONG_MAX : (long)dl);
900 : : #endif
901 : :
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
905 : : curl_off_t cl;
906 : : curl_res = curl_easy_getinfo(target_handle,
907 : : CURLINFO_CONTENT_LENGTH_DOWNLOAD_T,
908 : : &cl);
909 : : if (curl_res == 0 && cl >= 0)
910 : : pb = (cl > LONG_MAX ? LONG_MAX : (long)cl);
911 : : #else
912 : 5 : double cl;
913 : 5 : curl_res = curl_easy_getinfo(target_handle,
914 : : CURLINFO_CONTENT_LENGTH_DOWNLOAD,
915 : : &cl);
916 [ + - ]: 5 : if (curl_res == 0)
917 [ + - ]: 5 : pb = (cl > LONG_MAX ? LONG_MAX : (long)cl);
918 : : #endif
919 : : }
920 : :
921 [ + - ]: 12 : if ((*c->progressfn) (c, pa, pb))
922 : : break;
923 : : }
924 [ + + ]: 6379 : } while (still_running);
925 : :
926 : : /* Check whether a query was successful. If so, assign its handle
927 : : to verified_handle. */
928 : : int num_msg;
929 : : rc = -ENOENT;
930 : : CURL *verified_handle = NULL;
931 : 54 : do
932 : : {
933 : 54 : CURLMsg *msg;
934 : :
935 : 54 : msg = curl_multi_info_read(curlm, &num_msg);
936 [ + - ][ + - ]: 54 : if (msg != NULL && msg->msg == CURLMSG_DONE)
937 : : {
938 [ + + ]: 54 : if (vfd >= 0)
939 : : {
940 : 2 : bool pnl = (c->default_progressfn_printed_p
941 [ - + # # ]: 1 : && vfd == STDERR_FILENO);
942 [ + - ]: 2 : dprintf (vfd, "%sserver response %s\n", pnl ? "\n" : "",
943 : : curl_easy_strerror (msg->data.result));
944 [ - + ]: 1 : if (pnl)
945 : 0 : c->default_progressfn_printed_p = 0;
946 [ + - ]: 1 : for (int i = 0; i < num_urls; i++)
947 [ + - ]: 1 : if (msg->easy_handle == data[i].handle)
948 : : {
949 [ - + ]: 1 : if (strlen (data[i].errbuf) > 0)
950 : 0 : dprintf (vfd, "url %d %s\n", i, data[i].errbuf);
951 : : break;
952 : : }
953 : : }
954 : :
955 [ + + ]: 54 : if (msg->data.result != CURLE_OK)
956 : : {
957 : : /* Unsuccessful query, determine error code. */
958 [ - + ]: 10 : switch (msg->data.result)
959 : : {
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;
972 : : }
973 : : }
974 : : else
975 : : {
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. */
979 : :
980 [ + - ]: 44 : if (msg->easy_handle != NULL)
981 : : {
982 : 44 : char *effective_url = NULL;
983 : 44 : long resp_code = 500;
984 : 44 : CURLcode ok1 = curl_easy_getinfo (target_handle,
985 : : CURLINFO_EFFECTIVE_URL,
986 : : &effective_url);
987 : 44 : CURLcode ok2 = curl_easy_getinfo (target_handle,
988 : : CURLINFO_RESPONSE_CODE,
989 : : &resp_code);
990 [ + - ][ + - ]: 44 : if(ok1 == CURLE_OK && ok2 == CURLE_OK && effective_url)
991 : : {
992 [ + + ]: 44 : if (strncasecmp (effective_url, "HTTP", 4) == 0)
993 [ + - ]: 42 : if (resp_code == 200)
994 : : {
995 : 42 : verified_handle = msg->easy_handle;
996 : 44 : break;
997 : : }
998 [ + + ]: 2 : if (strncasecmp (effective_url, "FILE", 4) == 0)
999 [ + - ]: 1 : if (resp_code == 0)
1000 : : {
1001 : 1 : verified_handle = msg->easy_handle;
1002 : 1 : break;
1003 : : }
1004 : : }
1005 : : /* - libcurl since 7.52.0 version start to support
1006 : : CURLINFO_SCHEME;
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 : 1 : char *scheme = NULL;
1016 : 1 : CURLcode ok3 = curl_easy_getinfo (target_handle,
1017 : : CURLINFO_SCHEME,
1018 : : &scheme);
1019 [ + - ][ + - ]: 1 : if(ok3 == CURLE_OK && scheme)
1020 : : {
1021 [ + - ]: 1 : if (strncmp (scheme, "HTTP", 4) == 0)
1022 [ + - ]: 1 : if (resp_code == 200)
1023 : : {
1024 : 1 : verified_handle = msg->easy_handle;
1025 : 1 : break;
1026 : : }
1027 : : }
1028 : : #endif
1029 : : #endif
1030 : : }
1031 : : }
1032 : : }
1033 [ + + ]: 10 : } while (num_msg > 0);
1034 : :
1035 [ + + ]: 50 : if (verified_handle == NULL)
1036 : : goto out1;
1037 : :
1038 [ + + ]: 44 : if (vfd >= 0)
1039 : : {
1040 [ - + # # ]: 1 : bool pnl = c->default_progressfn_printed_p && vfd == STDERR_FILENO;
1041 [ + - ]: 2 : dprintf (vfd, "%sgot file from server\n", pnl ? "\n" : "");
1042 [ - + ]: 1 : if (pnl)
1043 : 0 : c->default_progressfn_printed_p = 0;
1044 : : }
1045 : :
1046 : : /* we've got one!!!! */
1047 : 44 : time_t mtime;
1048 : 44 : CURLcode curl_res = curl_easy_getinfo(verified_handle, CURLINFO_FILETIME, (void*) &mtime);
1049 [ - + ]: 44 : if (curl_res != CURLE_OK)
1050 : 0 : mtime = time(NULL); /* fall back to current time */
1051 : :
1052 : 44 : struct timeval tvs[2];
1053 : 44 : tvs[0].tv_sec = tvs[1].tv_sec = mtime;
1054 : 44 : tvs[0].tv_usec = tvs[1].tv_usec = 0;
1055 : 44 : (void) futimes (fd, tvs); /* best effort */
1056 : :
1057 : : /* rename tmp->real */
1058 : 44 : rc = rename (target_cache_tmppath, target_cache_path);
1059 [ - + ]: 44 : if (rc < 0)
1060 : : {
1061 : 0 : rc = -errno;
1062 : 0 : goto out1;
1063 : : /* Perhaps we need not give up right away; could retry or something ... */
1064 : : }
1065 : :
1066 : : /* Success!!!! */
1067 [ + + ]: 88 : for (int i = 0; i < num_urls; i++)
1068 : 44 : curl_easy_cleanup(data[i].handle);
1069 : :
1070 : 44 : curl_multi_cleanup (curlm);
1071 : 44 : free (data);
1072 : 44 : free (server_urls);
1073 : :
1074 : : /* don't close fd - we're returning it */
1075 : : /* don't unlink the tmppath; it's already been renamed. */
1076 [ + + ]: 44 : if (path != NULL)
1077 : 43 : *path = strdup(target_cache_path);
1078 : :
1079 : 44 : rc = fd;
1080 : 44 : goto out;
1081 : :
1082 : : /* error exits */
1083 : 6 : out1:
1084 [ + + ]: 16 : for (int i = 0; i < num_urls; i++)
1085 : 10 : curl_easy_cleanup(data[i].handle);
1086 : :
1087 : 6 : curl_multi_cleanup(curlm);
1088 : 6 : unlink (target_cache_tmppath);
1089 : 6 : close (fd); /* before the rmdir, otherwise it'll fail */
1090 : 6 : (void) rmdir (target_cache_dir); /* nop if not empty */
1091 : 6 : free(data);
1092 : :
1093 : 6 : out0:
1094 : 6 : free (server_urls);
1095 : :
1096 : : /* general purpose exit */
1097 : 67 : out:
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 [ + + ]: 67 : if (c->default_progressfn_printed_p)
1103 : 1 : dprintf(STDERR_FILENO, "\n");
1104 : :
1105 [ + + ]: 67 : if (vfd >= 0)
1106 : : {
1107 [ - + ]: 1 : if (rc < 0)
1108 : 0 : dprintf (vfd, "not found %s (err=%d)\n", strerror (-rc), rc);
1109 : : else
1110 : 1 : dprintf (vfd, "found %s (fd=%d)\n", target_cache_path, rc);
1111 : : }
1112 : :
1113 : 67 : free (cache_path);
1114 : 67 : free (maxage_path);
1115 : 67 : free (interval_path);
1116 : 67 : free (target_cache_dir);
1117 : 67 : free (target_cache_path);
1118 : 67 : free (target_cache_tmppath);
1119 : 67 : return rc;
1120 : : }
1121 : :
1122 : :
1123 : :
1124 : : /* See debuginfod.h */
1125 : : debuginfod_client *
1126 : 67 : debuginfod_begin (void)
1127 : : {
1128 : 67 : debuginfod_client *client;
1129 : 67 : size_t size = sizeof (struct debuginfod_client);
1130 : 67 : client = (debuginfod_client *) calloc (1, size);
1131 [ + - ]: 67 : if (client != NULL)
1132 : : {
1133 [ + + ]: 67 : if (getenv(DEBUGINFOD_PROGRESS_ENV_VAR))
1134 : 1 : client->progressfn = default_progressfn;
1135 [ - + ]: 67 : if (getenv(DEBUGINFOD_VERBOSE_ENV_VAR))
1136 : 0 : client->verbose_fd = STDERR_FILENO;
1137 : : else
1138 : 67 : client->verbose_fd = -1;
1139 : : }
1140 : 67 : return client;
1141 : : }
1142 : :
1143 : : void
1144 : 58 : debuginfod_set_user_data(debuginfod_client *client,
1145 : : void *data)
1146 : : {
1147 : 58 : client->user_data = data;
1148 : 58 : }
1149 : :
1150 : : void *
1151 : 0 : debuginfod_get_user_data(debuginfod_client *client)
1152 : : {
1153 : 0 : return client->user_data;
1154 : : }
1155 : :
1156 : : const char *
1157 : 1 : debuginfod_get_url(debuginfod_client *client)
1158 : : {
1159 [ + + ]: 3 : return client->url;
1160 : : }
1161 : :
1162 : : void
1163 : 67 : debuginfod_end (debuginfod_client *client)
1164 : : {
1165 [ + - ]: 67 : if (client == NULL)
1166 : : return;
1167 : :
1168 : 67 : curl_slist_free_all (client->headers);
1169 : 67 : free (client->url);
1170 : 67 : free (client);
1171 : : }
1172 : :
1173 : : int
1174 : 29 : debuginfod_find_debuginfo (debuginfod_client *client,
1175 : : const unsigned char *build_id, int build_id_len,
1176 : : char **path)
1177 : : {
1178 : 29 : return debuginfod_query_server(client, build_id, build_id_len,
1179 : : "debuginfo", NULL, path);
1180 : : }
1181 : :
1182 : :
1183 : : /* See debuginfod.h */
1184 : : int
1185 : 24 : debuginfod_find_executable(debuginfod_client *client,
1186 : : const unsigned char *build_id, int build_id_len,
1187 : : char **path)
1188 : : {
1189 : 24 : return debuginfod_query_server(client, build_id, build_id_len,
1190 : : "executable", NULL, path);
1191 : : }
1192 : :
1193 : : /* See debuginfod.h */
1194 : 14 : int debuginfod_find_source(debuginfod_client *client,
1195 : : const unsigned char *build_id, int build_id_len,
1196 : : const char *filename, char **path)
1197 : : {
1198 : 14 : return debuginfod_query_server(client, build_id, build_id_len,
1199 : : "source", filename, path);
1200 : : }
1201 : :
1202 : :
1203 : : /* Add an outgoing HTTP header. */
1204 : 75 : int debuginfod_add_http_header (debuginfod_client *client, const char* header)
1205 : : {
1206 : : /* Sanity check header value is of the form Header: Value.
1207 : : It should contain exactly one colon that isn't the first or
1208 : : last character. */
1209 : 75 : char *colon = strchr (header, ':');
1210 : 150 : if (colon == NULL
1211 [ + - ]: 75 : || colon == header
1212 [ + - ]: 75 : || *(colon + 1) == '\0'
1213 [ + - ]: 75 : || strchr (colon + 1, ':') != NULL)
1214 : : return -EINVAL;
1215 : :
1216 : 75 : struct curl_slist *temp = curl_slist_append (client->headers, header);
1217 [ + - ]: 75 : if (temp == NULL)
1218 : : return -ENOMEM;
1219 : :
1220 : : /* Track if User-Agent: is being set. If so, signal not to add the
1221 : : default one. */
1222 [ + + ]: 75 : if (strncmp (header, "User-Agent:", 11) == 0)
1223 : 66 : client->user_agent_set_p = 1;
1224 : :
1225 : 75 : client->headers = temp;
1226 : 75 : return 0;
1227 : : }
1228 : :
1229 : :
1230 : : void
1231 : 10 : debuginfod_set_progressfn(debuginfod_client *client,
1232 : : debuginfod_progressfn_t fn)
1233 : : {
1234 : 10 : client->progressfn = fn;
1235 : 10 : }
1236 : :
1237 : : void
1238 : 1 : debuginfod_set_verbose_fd(debuginfod_client *client, int fd)
1239 : : {
1240 : 1 : client->verbose_fd = fd;
1241 : 1 : }
1242 : :
1243 : :
1244 : : /* NB: these are thread-unsafe. */
1245 : 61 : __attribute__((constructor)) attribute_hidden void libdebuginfod_ctor(void)
1246 : : {
1247 : 61 : curl_global_init(CURL_GLOBAL_DEFAULT);
1248 : 61 : }
1249 : :
1250 : : /* NB: this is very thread-unsafe: it breaks other threads that are still in libcurl */
1251 : 0 : __attribute__((destructor)) attribute_hidden void libdebuginfod_dtor(void)
1252 : : {
1253 : : /* ... so don't do this: */
1254 : : /* curl_global_cleanup(); */
1255 : 0 : }
1256 : :
1257 : : #endif /* DUMMY_LIBDEBUGINFOD */
|