Branch data Line data Source code
1 : : /* Retrieve ELF / DWARF / source files from the debuginfod.
2 : : Copyright (C) 2019-2021 Red Hat, Inc.
3 : : Copyright (C) 2021, 2022 Mark J. Wielaard <mark@klomp.org>
4 : : This file is part of elfutils.
5 : :
6 : : This file is free software; you can redistribute it and/or modify
7 : : it under the terms of either
8 : :
9 : : * the GNU Lesser General Public License as published by the Free
10 : : Software Foundation; either version 3 of the License, or (at
11 : : your option) any later version
12 : :
13 : : or
14 : :
15 : : * the GNU General Public License as published by the Free
16 : : Software Foundation; either version 2 of the License, or (at
17 : : your option) any later version
18 : :
19 : : or both in parallel, as here.
20 : :
21 : : elfutils is distributed in the hope that it will be useful, but
22 : : WITHOUT ANY WARRANTY; without even the implied warranty of
23 : : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
24 : : General Public License for more details.
25 : :
26 : : You should have received copies of the GNU General Public License and
27 : : the GNU Lesser General Public License along with this program. If
28 : : not, see <http://www.gnu.org/licenses/>. */
29 : :
30 : :
31 : : /* cargo-cult from libdwfl linux-kernel-modules.c */
32 : : /* In case we have a bad fts we include this before config.h because it
33 : : can't handle _FILE_OFFSET_BITS.
34 : : Everything we need here is fine if its declarations just come first.
35 : : Also, include sys/types.h before fts. On some systems fts.h is not self
36 : : contained. */
37 : : #ifdef BAD_FTS
38 : : #include <sys/types.h>
39 : : #include <fts.h>
40 : : #endif
41 : :
42 : : #include "config.h"
43 : : #include "debuginfod.h"
44 : : #include "system.h"
45 : : #include <errno.h>
46 : : #include <stdlib.h>
47 : :
48 : : /* We might be building a bootstrap dummy library, which is really simple. */
49 : : #ifdef DUMMY_LIBDEBUGINFOD
50 : :
51 : : debuginfod_client *debuginfod_begin (void) { errno = ENOSYS; return NULL; }
52 : : int debuginfod_find_debuginfo (debuginfod_client *c, const unsigned char *b,
53 : : int s, char **p) { return -ENOSYS; }
54 : : int debuginfod_find_executable (debuginfod_client *c, const unsigned char *b,
55 : : int s, char **p) { return -ENOSYS; }
56 : : int debuginfod_find_source (debuginfod_client *c, const unsigned char *b,
57 : : int s, const char *f, char **p) { return -ENOSYS; }
58 : : void debuginfod_set_progressfn(debuginfod_client *c,
59 : : debuginfod_progressfn_t fn) { }
60 : : void debuginfod_set_verbose_fd(debuginfod_client *c, int fd) { }
61 : : void debuginfod_set_user_data (debuginfod_client *c, void *d) { }
62 : : void* debuginfod_get_user_data (debuginfod_client *c) { return NULL; }
63 : : const char* debuginfod_get_url (debuginfod_client *c) { return NULL; }
64 : : int debuginfod_add_http_header (debuginfod_client *c,
65 : : const char *h) { return -ENOSYS; }
66 : : void debuginfod_end (debuginfod_client *c) { }
67 : :
68 : : #else /* DUMMY_LIBDEBUGINFOD */
69 : :
70 : : #include <assert.h>
71 : : #include <dirent.h>
72 : : #include <stdio.h>
73 : : #include <errno.h>
74 : : #include <unistd.h>
75 : : #include <fcntl.h>
76 : : #include <fts.h>
77 : : #include <regex.h>
78 : : #include <string.h>
79 : : #include <stdbool.h>
80 : : #include <linux/limits.h>
81 : : #include <time.h>
82 : : #include <utime.h>
83 : : #include <sys/syscall.h>
84 : : #include <sys/types.h>
85 : : #include <sys/stat.h>
86 : : #include <sys/utsname.h>
87 : : #include <curl/curl.h>
88 : :
89 : : /* If fts.h is included before config.h, its indirect inclusions may not
90 : : give us the right LFS aliases of these functions, so map them manually. */
91 : : #ifdef BAD_FTS
92 : : #ifdef _FILE_OFFSET_BITS
93 : : #define open open64
94 : : #define fopen fopen64
95 : : #endif
96 : : #else
97 : : #include <sys/types.h>
98 : : #include <fts.h>
99 : : #endif
100 : :
101 : : #include <pthread.h>
102 : :
103 : : static pthread_once_t init_control = PTHREAD_ONCE_INIT;
104 : :
105 : : static void
106 : 148 : libcurl_init(void)
107 : : {
108 : 148 : curl_global_init(CURL_GLOBAL_DEFAULT);
109 : 148 : }
110 : :
111 : : struct debuginfod_client
112 : : {
113 : : /* Progress/interrupt callback function. */
114 : : debuginfod_progressfn_t progressfn;
115 : :
116 : : /* Stores user data. */
117 : : void* user_data;
118 : :
119 : : /* Stores current/last url, if any. */
120 : : char* url;
121 : :
122 : : /* Accumulates outgoing http header names/values. */
123 : : int user_agent_set_p; /* affects add_default_headers */
124 : : struct curl_slist *headers;
125 : :
126 : : /* Flags the default_progressfn having printed something that
127 : : debuginfod_end needs to terminate. */
128 : : int default_progressfn_printed_p;
129 : :
130 : : /* File descriptor to output any verbose messages if > 0. */
131 : : int verbose_fd;
132 : :
133 : : /* Maintain a long-lived curl multi-handle, which keeps a
134 : : connection/tls/dns cache to recently seen servers. */
135 : : CURLM *server_mhandle;
136 : :
137 : : /* Can contain all other context, like cache_path, server_urls,
138 : : timeout or other info gotten from environment variables, the
139 : : handle data, etc. So those don't have to be reparsed and
140 : : recreated on each request. */
141 : : char * winning_headers;
142 : : };
143 : :
144 : : /* The cache_clean_interval_s file within the debuginfod cache specifies
145 : : how frequently the cache should be cleaned. The file's st_mtime represents
146 : : the time of last cleaning. */
147 : : static const char *cache_clean_interval_filename = "cache_clean_interval_s";
148 : : static const long cache_clean_default_interval_s = 86400; /* 1 day */
149 : :
150 : : /* The cache_miss_default_s within the debuginfod cache specifies how
151 : : frequently the empty file should be released.*/
152 : : static const long cache_miss_default_s = 600; /* 10 min */
153 : : static const char *cache_miss_filename = "cache_miss_s";
154 : :
155 : : /* The cache_max_unused_age_s file within the debuginfod cache specifies the
156 : : the maximum time since last access that a file will remain in the cache. */
157 : : static const char *cache_max_unused_age_filename = "max_unused_age_s";
158 : : static const long cache_default_max_unused_age_s = 604800; /* 1 week */
159 : :
160 : : /* Location of the cache of files downloaded from debuginfods.
161 : : The default parent directory is $HOME, or '/' if $HOME doesn't exist. */
162 : : static const char *cache_default_name = ".debuginfod_client_cache";
163 : : static const char *cache_xdg_name = "debuginfod_client";
164 : :
165 : : /* URLs of debuginfods, separated by url_delim. */
166 : : static const char *url_delim = " ";
167 : :
168 : : /* Timeout for debuginfods, in seconds (to get at least 100K). */
169 : : static const long default_timeout = 90;
170 : :
171 : : /* Default retry count for download error. */
172 : : static const long default_retry_limit = 2;
173 : :
174 : : /* Data associated with a particular CURL easy handle. Passed to
175 : : the write callback. */
176 : : struct handle_data
177 : : {
178 : : /* Cache file to be written to in case query is successful. */
179 : : int fd;
180 : :
181 : : /* URL queried by this handle. */
182 : : char url[PATH_MAX];
183 : :
184 : : /* error buffer for this handle. */
185 : : char errbuf[CURL_ERROR_SIZE];
186 : :
187 : : /* This handle. */
188 : : CURL *handle;
189 : :
190 : : /* The client object whom we're serving. */
191 : : debuginfod_client *client;
192 : :
193 : : /* Pointer to handle that should write to fd. Initially points to NULL,
194 : : then points to the first handle that begins writing the target file
195 : : to the cache. Used to ensure that a file is not downloaded from
196 : : multiple servers unnecessarily. */
197 : : CURL **target_handle;
198 : : /* Response http headers for this client handle, sent from the server */
199 : : char *response_data;
200 : : size_t response_data_size;
201 : : };
202 : :
203 : : static size_t
204 : 4258 : debuginfod_write_callback (char *ptr, size_t size, size_t nmemb, void *data)
205 : : {
206 : 4258 : ssize_t count = size * nmemb;
207 : :
208 : 4258 : struct handle_data *d = (struct handle_data*)data;
209 : :
210 : : /* Indicate to other handles that they can abort their transfer. */
211 [ + + ]: 4258 : if (*d->target_handle == NULL)
212 : : {
213 : 96 : *d->target_handle = d->handle;
214 : : /* update the client object */
215 : 96 : const char *url = NULL;
216 : 96 : (void) curl_easy_getinfo (d->handle, CURLINFO_EFFECTIVE_URL, &url);
217 [ + - ]: 96 : if (url)
218 : : {
219 : 96 : free (d->client->url);
220 : 96 : d->client->url = strdup(url); /* ok if fails */
221 : : }
222 : : }
223 : :
224 : : /* If this handle isn't the target handle, abort transfer. */
225 [ + - ]: 4258 : if (*d->target_handle != d->handle)
226 : : return -1;
227 : :
228 : 4258 : return (size_t) write(d->fd, (void*)ptr, count);
229 : : }
230 : :
231 : : /* handle config file read and write */
232 : : static int
233 : 257 : debuginfod_config_cache(char *config_path,
234 : : long cache_config_default_s,
235 : : struct stat *st)
236 : : {
237 : 257 : int fd;
238 : : /* if the config file doesn't exist, create one with DEFFILEMODE*/
239 [ + + ]: 257 : if(stat(config_path, st) == -1)
240 : : {
241 : 1 : fd = open(config_path, O_CREAT | O_RDWR, DEFFILEMODE);
242 [ - + ]: 1 : if (fd < 0)
243 : 0 : return -errno;
244 : :
245 [ - + ]: 1 : if (dprintf(fd, "%ld", cache_config_default_s) < 0)
246 : 0 : return -errno;
247 : : }
248 : :
249 : 257 : long cache_config;
250 : 257 : FILE *config_file = fopen(config_path, "r");
251 [ + - ]: 257 : if (config_file)
252 : : {
253 [ - + ]: 257 : if (fscanf(config_file, "%ld", &cache_config) != 1)
254 : 0 : cache_config = cache_config_default_s;
255 : 257 : fclose(config_file);
256 : : }
257 : : else
258 : 0 : cache_config = cache_config_default_s;
259 : :
260 : 257 : return cache_config;
261 : : }
262 : :
263 : : /* Create the cache and interval file if they do not already exist.
264 : : Return 0 if cache and config file are initialized, otherwise return
265 : : the appropriate error code. */
266 : : static int
267 : 254 : debuginfod_init_cache (char *cache_path, char *interval_path, char *maxage_path)
268 : : {
269 : 254 : struct stat st;
270 : :
271 : : /* If the cache and config file already exist then we are done. */
272 [ + + + + ]: 482 : if (stat(cache_path, &st) == 0 && stat(interval_path, &st) == 0)
273 : : return 0;
274 : :
275 : : /* Create the cache and config files as necessary. */
276 [ + + - + ]: 33 : if (stat(cache_path, &st) != 0 && mkdir(cache_path, ACCESSPERMS) < 0)
277 : 0 : return -errno;
278 : :
279 : 33 : int fd = -1;
280 : :
281 : : /* init cleaning interval config file. */
282 : 33 : fd = open(interval_path, O_CREAT | O_RDWR, DEFFILEMODE);
283 [ - + ]: 33 : if (fd < 0)
284 : 0 : return -errno;
285 : :
286 [ - + ]: 33 : if (dprintf(fd, "%ld", cache_clean_default_interval_s) < 0)
287 : 0 : return -errno;
288 : :
289 : : /* init max age config file. */
290 [ + - ]: 33 : if (stat(maxage_path, &st) != 0
291 [ - + ]: 33 : && (fd = open(maxage_path, O_CREAT | O_RDWR, DEFFILEMODE)) < 0)
292 : 0 : return -errno;
293 : :
294 [ - + ]: 33 : if (dprintf(fd, "%ld", cache_default_max_unused_age_s) < 0)
295 : 0 : return -errno;
296 : :
297 : : return 0;
298 : : }
299 : :
300 : :
301 : : /* Delete any files that have been unmodied for a period
302 : : longer than $DEBUGINFOD_CACHE_CLEAN_INTERVAL_S. */
303 : : static int
304 : 254 : debuginfod_clean_cache(debuginfod_client *c,
305 : : char *cache_path, char *interval_path,
306 : : char *max_unused_path)
307 : : {
308 : 254 : time_t clean_interval, max_unused_age;
309 : 254 : int rc = -1;
310 : 254 : struct stat st;
311 : :
312 : : /* Create new interval file. */
313 : 254 : rc = debuginfod_config_cache(interval_path,
314 : : cache_clean_default_interval_s, &st);
315 [ + - ]: 254 : if (rc < 0)
316 : : return rc;
317 : 254 : clean_interval = (time_t)rc;
318 : :
319 : : /* Check timestamp of interval file to see whether cleaning is necessary. */
320 [ + + ]: 254 : if (time(NULL) - st.st_mtime < clean_interval)
321 : : /* Interval has not passed, skip cleaning. */
322 : : return 0;
323 : :
324 : : /* Read max unused age value from config file. */
325 : 1 : rc = debuginfod_config_cache(max_unused_path,
326 : : cache_default_max_unused_age_s, &st);
327 [ + - ]: 1 : if (rc < 0)
328 : : return rc;
329 : 1 : max_unused_age = (time_t)rc;
330 : :
331 : 1 : char * const dirs[] = { cache_path, NULL, };
332 : :
333 : 1 : FTS *fts = fts_open(dirs, 0, NULL);
334 [ - + ]: 1 : if (fts == NULL)
335 : 0 : return -errno;
336 : :
337 : 1 : regex_t re;
338 : 1 : const char * pattern = ".*/[a-f0-9]+(/debuginfo|/executable|/source.*|)$"; /* include dirs */
339 [ + - ]: 1 : if (regcomp (&re, pattern, REG_EXTENDED | REG_NOSUB) != 0)
340 : : return -ENOMEM;
341 : :
342 : 1 : FTSENT *f;
343 : 1 : long files = 0;
344 : 1 : time_t now = time(NULL);
345 [ + + ]: 12 : while ((f = fts_read(fts)) != NULL)
346 : : {
347 : : /* ignore any files that do not match the pattern. */
348 [ + + ]: 10 : if (regexec (&re, f->fts_path, 0, NULL, 0) != 0)
349 : 8 : continue;
350 : :
351 : 2 : files++;
352 [ - + ]: 2 : if (c->progressfn) /* inform/check progress callback */
353 [ # # ]: 0 : if ((c->progressfn) (c, files, 0))
354 : : break;
355 : :
356 [ - + + ]: 2 : switch (f->fts_info)
357 : : {
358 : 0 : case FTS_F:
359 : : /* delete file if max_unused_age has been met or exceeded w.r.t. atime. */
360 [ # # ]: 0 : if (now - f->fts_statp->st_atime >= max_unused_age)
361 : 0 : (void) unlink (f->fts_path);
362 : : break;
363 : :
364 : 1 : case FTS_DP:
365 : : /* Remove if old & empty. Weaken race against concurrent creation by
366 : : checking mtime. */
367 [ + - ]: 1 : if (now - f->fts_statp->st_mtime >= max_unused_age)
368 : 1 : (void) rmdir (f->fts_path);
369 : : break;
370 : :
371 : : default:
372 : 11 : ;
373 : : }
374 : : }
375 : 1 : fts_close (fts);
376 : 1 : regfree (&re);
377 : :
378 : : /* Update timestamp representing when the cache was last cleaned. */
379 : 1 : utime (interval_path, NULL);
380 : 1 : return 0;
381 : : }
382 : :
383 : :
384 : : #define MAX_BUILD_ID_BYTES 64
385 : :
386 : :
387 : : static void
388 : 254 : add_default_headers(debuginfod_client *client)
389 : : {
390 [ + + ]: 254 : if (client->user_agent_set_p)
391 : 118 : return;
392 : :
393 : : /* Compute a User-Agent: string to send. The more accurately this
394 : : describes this host, the likelier that the debuginfod servers
395 : : might be able to locate debuginfo for us. */
396 : :
397 : 136 : char* utspart = NULL;
398 : 136 : struct utsname uts;
399 : 136 : int rc = 0;
400 : 136 : rc = uname (&uts);
401 [ + - ]: 136 : if (rc == 0)
402 : 136 : rc = asprintf(& utspart, "%s/%s", uts.sysname, uts.machine);
403 [ - + ]: 136 : if (rc < 0)
404 : 0 : utspart = NULL;
405 : :
406 : 136 : FILE *f = fopen ("/etc/os-release", "r");
407 [ - + ]: 136 : if (f == NULL)
408 : 0 : f = fopen ("/usr/lib/os-release", "r");
409 : 136 : char *id = NULL;
410 : 136 : char *version = NULL;
411 [ + - ]: 136 : if (f != NULL)
412 : : {
413 [ + + ]: 952 : while (id == NULL || version == NULL)
414 : : {
415 : 816 : char buf[128];
416 : 816 : char *s = &buf[0];
417 [ + - ]: 816 : if (fgets (s, sizeof(buf), f) == NULL)
418 : : break;
419 : :
420 : 816 : int len = strlen (s);
421 [ - + ]: 816 : if (len < 3)
422 : 0 : continue;
423 [ + - ]: 816 : if (s[len - 1] == '\n')
424 : : {
425 : 816 : s[len - 1] = '\0';
426 : 816 : len--;
427 : : }
428 : :
429 : 816 : char *v = strchr (s, '=');
430 [ + - - + ]: 816 : if (v == NULL || strlen (v) < 2)
431 : 0 : continue;
432 : :
433 : : /* Split var and value. */
434 : 816 : *v = '\0';
435 : 816 : v++;
436 : :
437 : : /* Remove optional quotes around value string. */
438 [ + + ]: 816 : if (*v == '"' || *v == '\'')
439 : : {
440 : 544 : v++;
441 : 544 : s[len - 1] = '\0';
442 : : }
443 [ + + ]: 816 : if (strcmp (s, "ID") == 0)
444 : 136 : id = strdup (v);
445 [ + + ]: 816 : if (strcmp (s, "VERSION_ID") == 0)
446 : 136 : version = strdup (v);
447 : : }
448 : 136 : fclose (f);
449 : : }
450 : :
451 : 136 : char *ua = NULL;
452 [ + - + - ]: 272 : rc = asprintf(& ua, "User-Agent: %s/%s,%s,%s/%s",
453 : : PACKAGE_NAME, PACKAGE_VERSION,
454 [ - + ]: 136 : utspart ?: "",
455 : : id ?: "",
456 : : version ?: "");
457 [ - + ]: 136 : if (rc < 0)
458 : 0 : ua = NULL;
459 : :
460 [ + - ]: 136 : if (ua)
461 : 136 : (void) debuginfod_add_http_header (client, ua);
462 : :
463 : 136 : free (ua);
464 : 136 : free (id);
465 : 136 : free (version);
466 : 136 : free (utspart);
467 : : }
468 : :
469 : :
470 : : #define xalloc_str(p, fmt, args...) \
471 : : do \
472 : : { \
473 : : if (asprintf (&p, fmt, args) < 0) \
474 : : { \
475 : : p = NULL; \
476 : : rc = -ENOMEM; \
477 : : goto out; \
478 : : } \
479 : : } while (0)
480 : :
481 : :
482 : : /* Offer a basic form of progress tracing */
483 : : static int
484 : 3 : default_progressfn (debuginfod_client *c, long a, long b)
485 : : {
486 : 3 : const char* url = debuginfod_get_url (c);
487 : 3 : int len = 0;
488 : :
489 : : /* We prefer to print the host part of the URL to keep the
490 : : message short. */
491 : 3 : if (url != NULL)
492 : : {
493 : 1 : const char* buildid = strstr(url, "buildid/");
494 [ + - ]: 1 : if (buildid != NULL)
495 : 1 : len = (buildid - url);
496 : : else
497 : 0 : len = strlen(url);
498 : : }
499 : :
500 [ + + ]: 3 : if (b == 0 || url==NULL) /* early stage */
501 : 5 : dprintf(STDERR_FILENO,
502 : 2 : "\rDownloading %c", "-/|\\"[a % 4]);
503 [ - + ]: 1 : else if (b < 0) /* download in progress but unknown total length */
504 : 0 : dprintf(STDERR_FILENO,
505 : : "\rDownloading from %.*s %ld",
506 : : len, url, a);
507 : : else /* download in progress, and known total length */
508 : 1 : dprintf(STDERR_FILENO,
509 : : "\rDownloading from %.*s %ld/%ld",
510 : : len, url, a, b);
511 : 3 : c->default_progressfn_printed_p = 1;
512 : :
513 : 3 : return 0;
514 : : }
515 : :
516 : : /* This is a callback function that receives http response headers in buffer for use
517 : : * in this program. https://curl.se/libcurl/c/CURLOPT_HEADERFUNCTION.html is the
518 : : * online documentation.
519 : : */
520 : : static size_t
521 : 1011 : header_callback (char * buffer, size_t size, size_t numitems, void * userdata)
522 : : {
523 [ + - ]: 1011 : if (size != 1)
524 : : return 0;
525 : : /* Temporary buffer for realloc */
526 : 1011 : char *temp = NULL;
527 : 1011 : struct handle_data *data = (struct handle_data *) userdata;
528 [ + + ]: 1011 : if (data->response_data == NULL)
529 : : {
530 : 96 : temp = malloc(numitems+1);
531 [ + - ]: 96 : if (temp == NULL)
532 : : return 0;
533 : : }
534 : : else
535 : : {
536 : 915 : temp = realloc(data->response_data, data->response_data_size + numitems + 1);
537 [ + - ]: 915 : if (temp == NULL)
538 : : return 0;
539 : : }
540 : :
541 : 1011 : memcpy(temp + data->response_data_size, buffer, numitems);
542 : 1011 : data->response_data = temp;
543 : 1011 : data->response_data_size += numitems;
544 : 1011 : data->response_data[data->response_data_size] = '\0';
545 : 1011 : return numitems;
546 : : }
547 : :
548 : : /* Query each of the server URLs found in $DEBUGINFOD_URLS for the file
549 : : with the specified build-id, type (debuginfo, executable or source)
550 : : and filename. filename may be NULL. If found, return a file
551 : : descriptor for the target, otherwise return an error code.
552 : : */
553 : : static int
554 : 373 : debuginfod_query_server (debuginfod_client *c,
555 : : const unsigned char *build_id,
556 : : int build_id_len,
557 : : const char *type,
558 : : const char *filename,
559 : : char **path)
560 : : {
561 : 373 : char *server_urls;
562 : 373 : char *urls_envvar;
563 : 373 : char *cache_path = NULL;
564 : 373 : char *maxage_path = NULL;
565 : 373 : char *interval_path = NULL;
566 : 373 : char *cache_miss_path = NULL;
567 : 373 : char *target_cache_dir = NULL;
568 : 373 : char *target_cache_path = NULL;
569 : 373 : char *target_cache_tmppath = NULL;
570 : 373 : char suffix[PATH_MAX + 1]; /* +1 for zero terminator. */
571 : 373 : char build_id_bytes[MAX_BUILD_ID_BYTES * 2 + 1];
572 : 373 : int vfd = c->verbose_fd;
573 : 373 : int rc;
574 : :
575 [ + + ]: 373 : if (vfd >= 0)
576 : : {
577 : 8 : dprintf (vfd, "debuginfod_find_%s ", type);
578 [ + + ]: 8 : if (build_id_len == 0) /* expect clean hexadecimal */
579 : 3 : dprintf (vfd, "%s", (const char *) build_id);
580 : : else
581 [ + + ]: 105 : for (int i = 0; i < build_id_len; i++)
582 : 100 : dprintf (vfd, "%02x", build_id[i]);
583 [ + + ]: 8 : if (filename != NULL)
584 : 1 : dprintf (vfd, " %s\n", filename);
585 : 8 : dprintf (vfd, "\n");
586 : : }
587 : :
588 : : /* Is there any server we can query? If not, don't do any work,
589 : : just return with ENOSYS. Don't even access the cache. */
590 : 373 : urls_envvar = getenv(DEBUGINFOD_URLS_ENV_VAR);
591 [ + + ]: 373 : if (vfd >= 0)
592 [ - + ]: 8 : dprintf (vfd, "server urls \"%s\"\n",
593 : : urls_envvar != NULL ? urls_envvar : "");
594 [ + + + + ]: 373 : if (urls_envvar == NULL || urls_envvar[0] == '\0')
595 : : {
596 : 119 : rc = -ENOSYS;
597 : 119 : goto out;
598 : : }
599 : :
600 : : /* Clear the obsolete URL from a previous _find operation. */
601 : 254 : free (c->url);
602 : 254 : c->url = NULL;
603 : :
604 : : /* PR 27982: Add max size if DEBUGINFOD_MAXSIZE is set. */
605 : 254 : long maxsize = 0;
606 : 254 : const char *maxsize_envvar;
607 : 254 : maxsize_envvar = getenv(DEBUGINFOD_MAXSIZE_ENV_VAR);
608 [ + + ]: 254 : if (maxsize_envvar != NULL)
609 : 1 : maxsize = atol (maxsize_envvar);
610 : :
611 : : /* PR 27982: Add max time if DEBUGINFOD_MAXTIME is set. */
612 : 254 : long maxtime = 0;
613 : 254 : const char *maxtime_envvar;
614 : 254 : maxtime_envvar = getenv(DEBUGINFOD_MAXTIME_ENV_VAR);
615 [ + + ]: 254 : if (maxtime_envvar != NULL)
616 : 1 : maxtime = atol (maxtime_envvar);
617 [ + + ]: 254 : if (maxtime && vfd >= 0)
618 : 1 : dprintf(vfd, "using max time %lds\n", maxtime);
619 : :
620 : : /* Maxsize is valid*/
621 [ + + ]: 254 : if (maxsize > 0)
622 : : {
623 [ + - ]: 1 : if (vfd)
624 : 1 : dprintf (vfd, "using max size %ldB\n", maxsize);
625 : 1 : char *size_header = NULL;
626 : 1 : rc = asprintf (&size_header, "X-DEBUGINFOD-MAXSIZE: %ld", maxsize);
627 [ - + ]: 1 : if (rc < 0)
628 : : {
629 : 0 : rc = -ENOMEM;
630 : 0 : goto out;
631 : : }
632 : 1 : rc = debuginfod_add_http_header(c, size_header);
633 : 1 : free(size_header);
634 [ - + ]: 1 : if (rc < 0)
635 : 0 : goto out;
636 : : }
637 : 254 : add_default_headers(c);
638 : :
639 : : /* Copy lowercase hex representation of build_id into buf. */
640 [ + + ]: 254 : if (vfd >= 0)
641 : 8 : dprintf (vfd, "checking build-id\n");
642 [ + - + + ]: 254 : if ((build_id_len >= MAX_BUILD_ID_BYTES) ||
643 : 248 : (build_id_len == 0 &&
644 [ - + ]: 248 : strlen ((const char *) build_id) > MAX_BUILD_ID_BYTES*2))
645 : : {
646 : 0 : rc = -EINVAL;
647 : 0 : goto out;
648 : : }
649 : :
650 [ + + ]: 254 : if (build_id_len == 0) /* expect clean hexadecimal */
651 : 248 : strcpy (build_id_bytes, (const char *) build_id);
652 : : else
653 [ + + ]: 126 : for (int i = 0; i < build_id_len; i++)
654 : 120 : sprintf(build_id_bytes + (i * 2), "%02x", build_id[i]);
655 : :
656 [ + + ]: 254 : if (filename != NULL)
657 : : {
658 [ + + ]: 23 : if (vfd >= 0)
659 : 1 : dprintf (vfd, "checking filename\n");
660 [ - + ]: 23 : if (filename[0] != '/') // must start with /
661 : : {
662 : 0 : rc = -EINVAL;
663 : 0 : goto out;
664 : : }
665 : :
666 : : /* copy the filename to suffix, s,/,#,g */
667 : : unsigned q = 0;
668 [ + + ]: 1000 : for (unsigned fi=0; q < PATH_MAX-2; fi++) /* -2, escape is 2 chars. */
669 [ + + + + ]: 977 : switch (filename[fi])
670 : : {
671 : 23 : case '\0':
672 : 23 : suffix[q] = '\0';
673 : 23 : q = PATH_MAX-1; /* escape for loop too */
674 : 23 : break;
675 : 146 : case '/': /* escape / to prevent dir escape */
676 : 146 : suffix[q++]='#';
677 : 146 : suffix[q++]='#';
678 : 146 : break;
679 : 1 : case '#': /* escape # to prevent /# vs #/ collisions */
680 : 1 : suffix[q++]='#';
681 : 1 : suffix[q++]='_';
682 : 1 : break;
683 : 807 : default:
684 : 807 : suffix[q++]=filename[fi];
685 : : }
686 : 23 : suffix[q] = '\0';
687 : : /* If the DWARF filenames are super long, this could exceed
688 : : PATH_MAX and truncate/collide. Oh well, that'll teach
689 : : them! */
690 : : }
691 : : else
692 : 231 : suffix[0] = '\0';
693 : :
694 [ + + + + ]: 254 : if (suffix[0] != '\0' && vfd >= 0)
695 : 1 : dprintf (vfd, "suffix %s\n", suffix);
696 : :
697 : : /* set paths needed to perform the query
698 : :
699 : : example format
700 : : cache_path: $HOME/.cache
701 : : target_cache_dir: $HOME/.cache/0123abcd
702 : : target_cache_path: $HOME/.cache/0123abcd/debuginfo
703 : : target_cache_path: $HOME/.cache/0123abcd/source#PATH#TO#SOURCE ?
704 : :
705 : : $XDG_CACHE_HOME takes priority over $HOME/.cache.
706 : : $DEBUGINFOD_CACHE_PATH takes priority over $HOME/.cache and $XDG_CACHE_HOME.
707 : : */
708 : :
709 : : /* Determine location of the cache. The path specified by the debuginfod
710 : : cache environment variable takes priority. */
711 : 254 : char *cache_var = getenv(DEBUGINFOD_CACHE_PATH_ENV_VAR);
712 [ + - + + ]: 254 : if (cache_var != NULL && strlen (cache_var) > 0)
713 [ - + ]: 250 : xalloc_str (cache_path, "%s", cache_var);
714 : : else
715 : : {
716 : : /* If a cache already exists in $HOME ('/' if $HOME isn't set), then use
717 : : that. Otherwise use the XDG cache directory naming format. */
718 [ - + - + ]: 4 : xalloc_str (cache_path, "%s/%s", getenv ("HOME") ?: "/", cache_default_name);
719 : :
720 : 4 : struct stat st;
721 [ + + ]: 4 : if (stat (cache_path, &st) < 0)
722 : : {
723 : 3 : char cachedir[PATH_MAX];
724 : 3 : char *xdg = getenv ("XDG_CACHE_HOME");
725 : :
726 [ + - + + ]: 3 : if (xdg != NULL && strlen (xdg) > 0)
727 : 1 : snprintf (cachedir, PATH_MAX, "%s", xdg);
728 : : else
729 [ - + ]: 2 : snprintf (cachedir, PATH_MAX, "%s/.cache", getenv ("HOME") ?: "/");
730 : :
731 : : /* Create XDG cache directory if it doesn't exist. */
732 [ + + ]: 3 : if (stat (cachedir, &st) == 0)
733 : : {
734 [ - + ]: 1 : if (! S_ISDIR (st.st_mode))
735 : : {
736 : 0 : rc = -EEXIST;
737 : 0 : goto out;
738 : : }
739 : : }
740 : : else
741 : : {
742 : 2 : rc = mkdir (cachedir, 0700);
743 : :
744 : : /* Also check for EEXIST and S_ISDIR in case another client just
745 : : happened to create the cache. */
746 [ - + ]: 2 : if (rc < 0
747 [ # # ]: 0 : && (errno != EEXIST
748 [ # # ]: 0 : || stat (cachedir, &st) != 0
749 [ # # ]: 0 : || ! S_ISDIR (st.st_mode)))
750 : : {
751 : 0 : rc = -errno;
752 : 0 : goto out;
753 : : }
754 : : }
755 : :
756 : 3 : free (cache_path);
757 [ - + ]: 3 : xalloc_str (cache_path, "%s/%s", cachedir, cache_xdg_name);
758 : : }
759 : : }
760 : :
761 [ - + ]: 254 : xalloc_str (target_cache_dir, "%s/%s", cache_path, build_id_bytes);
762 [ - + ]: 254 : xalloc_str (target_cache_path, "%s/%s%s", target_cache_dir, type, suffix);
763 [ - + ]: 254 : xalloc_str (target_cache_tmppath, "%s.XXXXXX", target_cache_path);
764 : :
765 : : /* XXX combine these */
766 [ - + ]: 254 : xalloc_str (interval_path, "%s/%s", cache_path, cache_clean_interval_filename);
767 [ - + ]: 254 : xalloc_str (cache_miss_path, "%s/%s", cache_path, cache_miss_filename);
768 [ - + ]: 254 : xalloc_str (maxage_path, "%s/%s", cache_path, cache_max_unused_age_filename);
769 : :
770 [ + + ]: 254 : if (vfd >= 0)
771 : 8 : dprintf (vfd, "checking cache dir %s\n", cache_path);
772 : :
773 : 254 : rc = debuginfod_init_cache(cache_path, interval_path, maxage_path);
774 [ - + ]: 254 : if (rc != 0)
775 : 0 : goto out;
776 : 254 : rc = debuginfod_clean_cache(c, cache_path, interval_path, maxage_path);
777 [ - + ]: 254 : if (rc != 0)
778 : 0 : goto out;
779 : :
780 : : /* Check if the target is already in the cache. */
781 : 254 : int fd = open(target_cache_path, O_RDONLY);
782 [ + + ]: 254 : if (fd >= 0)
783 : : {
784 : 28 : struct stat st;
785 [ - + ]: 28 : if (fstat(fd, &st) != 0)
786 : : {
787 : 0 : rc = -errno;
788 : 0 : close (fd);
789 : 27 : goto out;
790 : : }
791 : :
792 : : /* If the file is non-empty, then we are done. */
793 [ + + ]: 28 : if (st.st_size > 0)
794 : : {
795 [ + - ]: 26 : if (path != NULL)
796 : : {
797 : 26 : *path = strdup(target_cache_path);
798 [ - + ]: 26 : if (*path == NULL)
799 : : {
800 : 0 : rc = -errno;
801 : 0 : close (fd);
802 : 0 : goto out;
803 : : }
804 : : }
805 : : /* Success!!!! */
806 : 26 : rc = fd;
807 : 26 : goto out;
808 : : }
809 : : else
810 : : {
811 : : /* The file is empty. Attempt to download only if enough time
812 : : has passed since the last attempt. */
813 : 2 : time_t cache_miss;
814 : 2 : time_t target_mtime = st.st_mtime;
815 : 2 : rc = debuginfod_config_cache(cache_miss_path,
816 : : cache_miss_default_s, &st);
817 [ - + ]: 2 : if (rc < 0)
818 : : {
819 : 0 : close(fd);
820 : 0 : goto out;
821 : : }
822 : :
823 : 2 : cache_miss = (time_t)rc;
824 [ + + ]: 2 : if (time(NULL) - target_mtime <= cache_miss)
825 : : {
826 : 1 : close(fd);
827 : 1 : rc = -ENOENT;
828 : 1 : goto out;
829 : : }
830 : : else
831 : : /* TOCTOU non-problem: if another task races, puts a working
832 : : download or an empty file in its place, unlinking here just
833 : : means WE will try to download again as uncached. */
834 : 1 : unlink(target_cache_path);
835 : : }
836 : : }
837 [ - + ]: 226 : else if (errno == EACCES)
838 : : /* Ensure old 000-permission files are not lingering in the cache. */
839 : 0 : unlink(target_cache_path);
840 : :
841 : 227 : long timeout = default_timeout;
842 : 227 : const char* timeout_envvar = getenv(DEBUGINFOD_TIMEOUT_ENV_VAR);
843 [ - + ]: 227 : if (timeout_envvar != NULL)
844 : 0 : timeout = atoi (timeout_envvar);
845 : :
846 [ + + ]: 227 : if (vfd >= 0)
847 : 8 : dprintf (vfd, "using timeout %ld\n", timeout);
848 : :
849 : : /* make a copy of the envvar so it can be safely modified. */
850 : 227 : server_urls = strdup(urls_envvar);
851 [ - + ]: 227 : if (server_urls == NULL)
852 : : {
853 : 0 : rc = -ENOMEM;
854 : 0 : goto out;
855 : : }
856 : : /* thereafter, goto out0 on error*/
857 : :
858 : : /* Because of a race with cache cleanup / rmdir, try to mkdir/mkstemp up to twice. */
859 [ + - ]: 227 : for(int i=0; i<2; i++) {
860 : : /* (re)create target directory in cache */
861 : 227 : (void) mkdir(target_cache_dir, 0700); /* files will be 0400 later */
862 : :
863 : : /* NB: write to a temporary file first, to avoid race condition of
864 : : multiple clients checking the cache, while a partially-written or empty
865 : : file is in there, being written from libcurl. */
866 : 227 : fd = mkstemp (target_cache_tmppath);
867 [ - + ]: 227 : if (fd >= 0) break;
868 : : }
869 [ - + ]: 227 : if (fd < 0) /* Still failed after two iterations. */
870 : : {
871 : 0 : rc = -errno;
872 : 0 : goto out0;
873 : : }
874 : :
875 : : /* Initialize the memory to zero */
876 : 227 : char *strtok_saveptr;
877 : 227 : char **server_url_list = NULL;
878 : 227 : char *server_url = strtok_r(server_urls, url_delim, &strtok_saveptr);
879 : : /* Count number of URLs. */
880 : 227 : int num_urls = 0;
881 : :
882 [ + + ]: 481 : while (server_url != NULL)
883 : : {
884 : : /* PR 27983: If the url is already set to be used use, skip it */
885 : 254 : char *slashbuildid;
886 [ + - + + ]: 254 : if (strlen(server_url) > 1 && server_url[strlen(server_url)-1] == '/')
887 : : slashbuildid = "buildid";
888 : : else
889 : 241 : slashbuildid = "/buildid";
890 : :
891 : 254 : char *tmp_url;
892 [ - + ]: 254 : if (asprintf(&tmp_url, "%s%s", server_url, slashbuildid) == -1)
893 : : {
894 : 0 : rc = -ENOMEM;
895 : 0 : goto out1;
896 : : }
897 : : int url_index;
898 [ + + ]: 315 : for (url_index = 0; url_index < num_urls; ++url_index)
899 : : {
900 [ + + ]: 63 : if(strcmp(tmp_url, server_url_list[url_index]) == 0)
901 : : {
902 : : url_index = -1;
903 : : break;
904 : : }
905 : : }
906 [ + + ]: 254 : if (url_index == -1)
907 : : {
908 [ + - ]: 2 : if (vfd >= 0)
909 : 2 : dprintf(vfd, "duplicate url: %s, skipping\n", tmp_url);
910 : 2 : free(tmp_url);
911 : : }
912 : : else
913 : : {
914 : 252 : num_urls++;
915 : 252 : char ** realloc_ptr;
916 : 252 : realloc_ptr = reallocarray(server_url_list, num_urls,
917 : : sizeof(char*));
918 [ - + ]: 252 : if (realloc_ptr == NULL)
919 : : {
920 : 0 : free (tmp_url);
921 : 0 : rc = -ENOMEM;
922 : 0 : goto out1;
923 : : }
924 : 252 : server_url_list = realloc_ptr;
925 : 252 : server_url_list[num_urls-1] = tmp_url;
926 : : }
927 : 254 : server_url = strtok_r(NULL, url_delim, &strtok_saveptr);
928 : : }
929 : :
930 : 227 : int retry_limit = default_retry_limit;
931 : 227 : const char* retry_limit_envvar = getenv(DEBUGINFOD_RETRY_LIMIT_ENV_VAR);
932 [ + + ]: 227 : if (retry_limit_envvar != NULL)
933 : 2 : retry_limit = atoi (retry_limit_envvar);
934 : :
935 : 227 : CURLM *curlm = c->server_mhandle;
936 [ - + ]: 227 : assert (curlm != NULL);
937 : :
938 : : /* Tracks which handle should write to fd. Set to the first
939 : : handle that is ready to write the target file to the cache. */
940 : 227 : CURL *target_handle = NULL;
941 : 227 : struct handle_data *data = malloc(sizeof(struct handle_data) * num_urls);
942 [ - + ]: 227 : if (data == NULL)
943 : : {
944 : 0 : rc = -ENOMEM;
945 : 0 : goto out1;
946 : : }
947 : :
948 : : /* thereafter, goto out2 on error. */
949 : :
950 : : /*The beginning of goto block query_in_parallel.*/
951 : 227 : query_in_parallel:
952 : 457 : rc = -ENOENT; /* Reset rc to default.*/
953 : :
954 : : /* Initialize handle_data with default values. */
955 [ + + ]: 939 : for (int i = 0; i < num_urls; i++)
956 : : {
957 : 482 : data[i].handle = NULL;
958 : 482 : data[i].fd = -1;
959 : 482 : data[i].errbuf[0] = '\0';
960 : : }
961 : :
962 : 457 : char *escaped_string = NULL;
963 : 457 : size_t escaped_strlen = 0;
964 [ + + ]: 457 : if (filename)
965 : : {
966 : 23 : escaped_string = curl_easy_escape(&target_handle, filename+1, 0);
967 [ - + ]: 23 : if (!escaped_string)
968 : : {
969 : 0 : rc = -ENOMEM;
970 : 0 : goto out2;
971 : : }
972 : 23 : char *loc = escaped_string;
973 : 23 : escaped_strlen = strlen(escaped_string);
974 [ + + ]: 146 : while ((loc = strstr(loc, "%2F")))
975 : : {
976 : 123 : loc[0] = '/';
977 : : //pull the string back after replacement
978 : : // loc-escaped_string finds the distance from the origin to the new location
979 : : // - 2 accounts for the 2F which remain and don't need to be measured.
980 : : // The two above subtracted from escaped_strlen yields the remaining characters
981 : : // in the string which we want to pull back
982 : 123 : memmove(loc+1, loc+3,escaped_strlen - (loc-escaped_string) - 2);
983 : : //Because the 2F was overwritten in the memmove (as desired) escaped_strlen is
984 : : // now two shorter.
985 : 123 : escaped_strlen -= 2;
986 : : }
987 : : }
988 : : /* Initialize each handle. */
989 [ + + ]: 939 : for (int i = 0; i < num_urls; i++)
990 : : {
991 [ + - ]: 482 : if ((server_url = server_url_list[i]) == NULL)
992 : : break;
993 [ + + ]: 482 : if (vfd >= 0)
994 : 19 : dprintf (vfd, "init server %d %s\n", i, server_url);
995 : :
996 : 482 : data[i].fd = fd;
997 : 482 : data[i].target_handle = &target_handle;
998 : 482 : data[i].handle = curl_easy_init();
999 [ - + ]: 482 : if (data[i].handle == NULL)
1000 : : {
1001 [ # # ]: 0 : if (filename) curl_free (escaped_string);
1002 : 0 : rc = -ENETUNREACH;
1003 : 0 : goto out2;
1004 : : }
1005 : 482 : data[i].client = c;
1006 : :
1007 [ + + ]: 482 : if (filename) /* must start with / */
1008 : : {
1009 : : /* PR28034 escape characters in completed url to %hh format. */
1010 : 23 : snprintf(data[i].url, PATH_MAX, "%s/%s/%s/%s", server_url,
1011 : : build_id_bytes, type, escaped_string);
1012 : : }
1013 : : else
1014 : 459 : snprintf(data[i].url, PATH_MAX, "%s/%s/%s", server_url, build_id_bytes, type);
1015 [ + + ]: 482 : if (vfd >= 0)
1016 : 19 : dprintf (vfd, "url %d %s\n", i, data[i].url);
1017 : :
1018 : : /* Only allow http:// + https:// + file:// so we aren't being
1019 : : redirected to some unsupported protocol. */
1020 : 482 : curl_easy_setopt(data[i].handle, CURLOPT_PROTOCOLS,
1021 : : CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FILE);
1022 : 482 : curl_easy_setopt(data[i].handle, CURLOPT_URL, data[i].url);
1023 [ + + ]: 482 : if (vfd >= 0)
1024 : 19 : curl_easy_setopt(data[i].handle, CURLOPT_ERRORBUFFER, data[i].errbuf);
1025 : 482 : curl_easy_setopt(data[i].handle,
1026 : : CURLOPT_WRITEFUNCTION,
1027 : : debuginfod_write_callback);
1028 : 482 : curl_easy_setopt(data[i].handle, CURLOPT_WRITEDATA, (void*)&data[i]);
1029 [ + - ]: 482 : if (timeout > 0)
1030 : : {
1031 : : /* Make sure there is at least some progress,
1032 : : try to get at least 100K per timeout seconds. */
1033 : 482 : curl_easy_setopt (data[i].handle, CURLOPT_LOW_SPEED_TIME,
1034 : : timeout);
1035 : 482 : curl_easy_setopt (data[i].handle, CURLOPT_LOW_SPEED_LIMIT,
1036 : : 100 * 1024L);
1037 : : }
1038 : 482 : data[i].response_data = NULL;
1039 : 482 : data[i].response_data_size = 0;
1040 : 482 : curl_easy_setopt(data[i].handle, CURLOPT_FILETIME, (long) 1);
1041 : 482 : curl_easy_setopt(data[i].handle, CURLOPT_FOLLOWLOCATION, (long) 1);
1042 : 482 : curl_easy_setopt(data[i].handle, CURLOPT_FAILONERROR, (long) 1);
1043 : 482 : curl_easy_setopt(data[i].handle, CURLOPT_NOSIGNAL, (long) 1);
1044 : 482 : curl_easy_setopt(data[i].handle, CURLOPT_HEADERFUNCTION, header_callback);
1045 : 482 : curl_easy_setopt(data[i].handle, CURLOPT_HEADERDATA, (void *) &(data[i]));
1046 : : #if LIBCURL_VERSION_NUM >= 0x072a00 /* 7.42.0 */
1047 : 482 : curl_easy_setopt(data[i].handle, CURLOPT_PATH_AS_IS, (long) 1);
1048 : : #else
1049 : : /* On old curl; no big deal, canonicalization here is almost the
1050 : : same, except perhaps for ? # type decorations at the tail. */
1051 : : #endif
1052 : 482 : curl_easy_setopt(data[i].handle, CURLOPT_AUTOREFERER, (long) 1);
1053 : 482 : curl_easy_setopt(data[i].handle, CURLOPT_ACCEPT_ENCODING, "");
1054 : 482 : curl_easy_setopt(data[i].handle, CURLOPT_HTTPHEADER, c->headers);
1055 : :
1056 : 482 : curl_multi_add_handle(curlm, data[i].handle);
1057 : : }
1058 : :
1059 [ + + ]: 457 : if (filename) curl_free(escaped_string);
1060 : : /* Query servers in parallel. */
1061 [ + + ]: 457 : if (vfd >= 0)
1062 : 18 : dprintf (vfd, "query %d urls in parallel\n", num_urls);
1063 : 457 : int still_running;
1064 : 457 : long loops = 0;
1065 : 457 : int committed_to = -1;
1066 : 457 : bool verbose_reported = false;
1067 : 457 : struct timespec start_time, cur_time;
1068 [ + + ]: 457 : if (c->winning_headers != NULL)
1069 : : {
1070 : 2 : free (c->winning_headers);
1071 : 2 : c->winning_headers = NULL;
1072 : : }
1073 [ + + - + ]: 457 : if ( maxtime > 0 && clock_gettime(CLOCK_MONOTONIC_RAW, &start_time) == -1)
1074 : : {
1075 : 0 : rc = errno;
1076 : 0 : goto out2;
1077 : : }
1078 : 4836 : long delta = 0;
1079 : 4836 : do
1080 : : {
1081 : : /* Check to see how long querying is taking. */
1082 [ + + ]: 4836 : if (maxtime > 0)
1083 : : {
1084 [ - + ]: 3 : if (clock_gettime(CLOCK_MONOTONIC_RAW, &cur_time) == -1)
1085 : : {
1086 : 0 : rc = errno;
1087 : 0 : goto out2;
1088 : : }
1089 : 3 : delta = cur_time.tv_sec - start_time.tv_sec;
1090 [ - + ]: 3 : if ( delta > maxtime)
1091 : : {
1092 : 0 : dprintf(vfd, "Timeout with max time=%lds and transfer time=%lds\n", maxtime, delta );
1093 : 0 : rc = -ETIME;
1094 : 0 : goto out2;
1095 : : }
1096 : : }
1097 : : /* Wait 1 second, the minimum DEBUGINFOD_TIMEOUT. */
1098 : 4836 : curl_multi_wait(curlm, NULL, 0, 1000, NULL);
1099 : 4836 : CURLMcode curlm_res = curl_multi_perform(curlm, &still_running);
1100 : :
1101 : : /* If the target file has been found, abort the other queries. */
1102 [ + + ]: 4836 : if (target_handle != NULL)
1103 : : {
1104 [ + + ]: 16844 : for (int i = 0; i < num_urls; i++)
1105 [ + + ]: 12586 : if (data[i].handle != target_handle)
1106 : 8328 : curl_multi_remove_handle(curlm, data[i].handle);
1107 : : else
1108 : : {
1109 : 4258 : committed_to = i;
1110 [ + + ]: 4258 : if (c->winning_headers == NULL)
1111 : : {
1112 : 96 : c->winning_headers = data[committed_to].response_data;
1113 [ + + + - ]: 96 : if (vfd >= 0 && c->winning_headers != NULL)
1114 : 4 : dprintf(vfd, "\n%s", c->winning_headers);
1115 : 96 : data[committed_to].response_data = NULL;
1116 : 96 : data[committed_to].response_data_size = 0;
1117 : : }
1118 : :
1119 : : }
1120 : : }
1121 : :
1122 [ + + + + ]: 4836 : if (vfd >= 0 && !verbose_reported && committed_to >= 0)
1123 : : {
1124 [ - + - - ]: 4 : bool pnl = (c->default_progressfn_printed_p && vfd == STDERR_FILENO);
1125 [ + - ]: 4 : dprintf (vfd, "%scommitted to url %d\n", pnl ? "\n" : "",
1126 : : committed_to);
1127 [ - + ]: 4 : if (pnl)
1128 : 0 : c->default_progressfn_printed_p = 0;
1129 : : verbose_reported = true;
1130 : : }
1131 : :
1132 [ - + ]: 4836 : if (curlm_res != CURLM_OK)
1133 : : {
1134 [ # # # ]: 0 : switch (curlm_res)
1135 : : {
1136 : 0 : case CURLM_CALL_MULTI_PERFORM: continue;
1137 : : case CURLM_OUT_OF_MEMORY: rc = -ENOMEM; break;
1138 : 0 : default: rc = -ENETUNREACH; break;
1139 : : }
1140 : 0 : goto out2;
1141 : : }
1142 : :
1143 : 4836 : long dl_size = 0;
1144 [ + + + + : 4836 : if (target_handle && (c->progressfn || maxsize > 0))
- + ]
1145 : : {
1146 : : /* Get size of file being downloaded. NB: If going through
1147 : : deflate-compressing proxies, this number is likely to be
1148 : : unavailable, so -1 may show. */
1149 : 8 : CURLcode curl_res;
1150 : : #ifdef CURLINFO_CONTENT_LENGTH_DOWNLOAD_T
1151 : : curl_off_t cl;
1152 : : curl_res = curl_easy_getinfo(target_handle,
1153 : : CURLINFO_CONTENT_LENGTH_DOWNLOAD_T,
1154 : : &cl);
1155 : : if (curl_res == CURLE_OK && cl >= 0)
1156 : : dl_size = (cl > LONG_MAX ? LONG_MAX : (long)cl);
1157 : : #else
1158 : 8 : double cl;
1159 : 8 : curl_res = curl_easy_getinfo(target_handle,
1160 : : CURLINFO_CONTENT_LENGTH_DOWNLOAD,
1161 : : &cl);
1162 [ + - ]: 8 : if (curl_res == CURLE_OK)
1163 [ + - ]: 8 : dl_size = (cl >= (double)(LONG_MAX+1UL) ? LONG_MAX : (long)cl);
1164 : : #endif
1165 : : /* If Content-Length is -1, try to get the size from
1166 : : X-Debuginfod-Size */
1167 [ - + - - ]: 8 : if (dl_size == -1 && c->winning_headers != NULL)
1168 : : {
1169 : 0 : long xdl;
1170 : 0 : char *hdr = strcasestr(c->winning_headers, "x-debuginfod-size");
1171 : :
1172 [ # # ]: 0 : if (hdr != NULL
1173 [ # # ]: 0 : && sscanf(hdr, "x-debuginfod-size: %ld", &xdl) == 1)
1174 : 0 : dl_size = xdl;
1175 : : }
1176 : : }
1177 : :
1178 [ + + ]: 4836 : if (c->progressfn) /* inform/check progress callback */
1179 : : {
1180 : 370 : loops ++;
1181 : 370 : long pa = loops; /* default param for progress callback */
1182 [ + + ]: 370 : if (target_handle) /* we've committed to a server; report its download progress */
1183 : : {
1184 : 8 : CURLcode curl_res;
1185 : : #ifdef CURLINFO_SIZE_DOWNLOAD_T
1186 : : curl_off_t dl;
1187 : : curl_res = curl_easy_getinfo(target_handle,
1188 : : CURLINFO_SIZE_DOWNLOAD_T,
1189 : : &dl);
1190 : : if (curl_res == 0 && dl >= 0)
1191 : : pa = (dl > LONG_MAX ? LONG_MAX : (long)dl);
1192 : : #else
1193 : 8 : double dl;
1194 : 8 : curl_res = curl_easy_getinfo(target_handle,
1195 : : CURLINFO_SIZE_DOWNLOAD,
1196 : : &dl);
1197 [ + - ]: 8 : if (curl_res == 0)
1198 [ + - ]: 8 : pa = (dl >= (double)(LONG_MAX+1UL) ? LONG_MAX : (long)dl);
1199 : : #endif
1200 : :
1201 : : }
1202 : :
1203 [ + - ]: 370 : if ((*c->progressfn) (c, pa, dl_size))
1204 : : break;
1205 : : }
1206 : :
1207 : : /* Check to see if we are downloading something which exceeds maxsize, if set.*/
1208 [ + + - + ]: 4836 : if (target_handle && dl_size > maxsize && maxsize > 0)
1209 : : {
1210 [ # # ]: 0 : if (vfd >=0)
1211 : 0 : dprintf(vfd, "Content-Length too large.\n");
1212 : 0 : rc = -EFBIG;
1213 : 0 : goto out2;
1214 : : }
1215 [ + + ]: 4836 : } while (still_running);
1216 : :
1217 : : /* Check whether a query was successful. If so, assign its handle
1218 : : to verified_handle. */
1219 : : int num_msg;
1220 : : rc = -ENOENT;
1221 : 458 : CURL *verified_handle = NULL;
1222 : 458 : do
1223 : : {
1224 : 458 : CURLMsg *msg;
1225 : :
1226 : 458 : msg = curl_multi_info_read(curlm, &num_msg);
1227 [ + - + - ]: 458 : if (msg != NULL && msg->msg == CURLMSG_DONE)
1228 : : {
1229 [ + + ]: 458 : if (vfd >= 0)
1230 : : {
1231 : 38 : bool pnl = (c->default_progressfn_printed_p
1232 [ - + - - ]: 19 : && vfd == STDERR_FILENO);
1233 [ + - ]: 38 : dprintf (vfd, "%sserver response %s\n", pnl ? "\n" : "",
1234 : : curl_easy_strerror (msg->data.result));
1235 [ - + ]: 19 : if (pnl)
1236 : 0 : c->default_progressfn_printed_p = 0;
1237 [ + - ]: 20 : for (int i = 0; i < num_urls; i++)
1238 [ + + ]: 20 : if (msg->easy_handle == data[i].handle)
1239 : : {
1240 [ + + ]: 19 : if (strlen (data[i].errbuf) > 0)
1241 : 4 : dprintf (vfd, "url %d %s\n", i, data[i].errbuf);
1242 : : break;
1243 : : }
1244 : : }
1245 : :
1246 [ + + ]: 458 : if (msg->data.result != CURLE_OK)
1247 : : {
1248 : 362 : long resp_code;
1249 : 362 : CURLcode ok0;
1250 : : /* Unsuccessful query, determine error code. */
1251 [ + + - - : 362 : switch (msg->data.result)
- - - - -
- + - - ]
1252 : : {
1253 : : case CURLE_COULDNT_RESOLVE_HOST: rc = -EHOSTUNREACH; break; // no NXDOMAIN
1254 : 330 : case CURLE_URL_MALFORMAT: rc = -EINVAL; break;
1255 : 12 : case CURLE_COULDNT_CONNECT: rc = -ECONNREFUSED; break;
1256 : 0 : case CURLE_PEER_FAILED_VERIFICATION: rc = -ECONNREFUSED; break;
1257 : 0 : case CURLE_REMOTE_ACCESS_DENIED: rc = -EACCES; break;
1258 : 0 : case CURLE_WRITE_ERROR: rc = -EIO; break;
1259 : 0 : case CURLE_OUT_OF_MEMORY: rc = -ENOMEM; break;
1260 : 0 : case CURLE_TOO_MANY_REDIRECTS: rc = -EMLINK; break;
1261 : 0 : case CURLE_SEND_ERROR: rc = -ECONNRESET; break;
1262 : 0 : case CURLE_RECV_ERROR: rc = -ECONNRESET; break;
1263 : 0 : case CURLE_OPERATION_TIMEDOUT: rc = -ETIME; break;
1264 : : case CURLE_HTTP_RETURNED_ERROR:
1265 : 20 : ok0 = curl_easy_getinfo (msg->easy_handle,
1266 : : CURLINFO_RESPONSE_CODE,
1267 : : &resp_code);
1268 : : /* 406 signals that the requested file was too large */
1269 [ + - + + ]: 20 : if ( ok0 == CURLE_OK && resp_code == 406)
1270 : : rc = -EFBIG;
1271 : : else
1272 : 19 : rc = -ENOENT;
1273 : : break;
1274 : 0 : default: rc = -ENOENT; break;
1275 : : }
1276 : : }
1277 : : else
1278 : : {
1279 : : /* Query completed without an error. Confirm that the
1280 : : response code is 200 when using HTTP/HTTPS and 0 when
1281 : : using file:// and set verified_handle. */
1282 : :
1283 [ + - ]: 96 : if (msg->easy_handle != NULL)
1284 : : {
1285 : 96 : char *effective_url = NULL;
1286 : 96 : long resp_code = 500;
1287 : 96 : CURLcode ok1 = curl_easy_getinfo (target_handle,
1288 : : CURLINFO_EFFECTIVE_URL,
1289 : : &effective_url);
1290 : 96 : CURLcode ok2 = curl_easy_getinfo (target_handle,
1291 : : CURLINFO_RESPONSE_CODE,
1292 : : &resp_code);
1293 [ + - + - ]: 96 : if(ok1 == CURLE_OK && ok2 == CURLE_OK && effective_url)
1294 : : {
1295 [ + + ]: 96 : if (strncasecmp (effective_url, "HTTP", 4) == 0)
1296 [ + - ]: 95 : if (resp_code == 200)
1297 : : {
1298 : 95 : verified_handle = msg->easy_handle;
1299 : 96 : break;
1300 : : }
1301 [ + - ]: 1 : if (strncasecmp (effective_url, "FILE", 4) == 0)
1302 [ + - ]: 1 : if (resp_code == 0)
1303 : : {
1304 : 1 : verified_handle = msg->easy_handle;
1305 : 1 : break;
1306 : : }
1307 : : }
1308 : : /* - libcurl since 7.52.0 version start to support
1309 : : CURLINFO_SCHEME;
1310 : : - before 7.61.0, effective_url would give us a
1311 : : url with upper case SCHEME added in the front;
1312 : : - effective_url between 7.61 and 7.69 can be lack
1313 : : of scheme if the original url doesn't include one;
1314 : : - since version 7.69 effective_url will be provide
1315 : : a scheme in lower case. */
1316 : : #if LIBCURL_VERSION_NUM >= 0x073d00 /* 7.61.0 */
1317 : : #if LIBCURL_VERSION_NUM <= 0x074500 /* 7.69.0 */
1318 : : char *scheme = NULL;
1319 : : CURLcode ok3 = curl_easy_getinfo (target_handle,
1320 : : CURLINFO_SCHEME,
1321 : : &scheme);
1322 : : if(ok3 == CURLE_OK && scheme)
1323 : : {
1324 : : if (startswith (scheme, "HTTP"))
1325 : : if (resp_code == 200)
1326 : : {
1327 : : verified_handle = msg->easy_handle;
1328 : : break;
1329 : : }
1330 : : }
1331 : : #endif
1332 : : #endif
1333 : : }
1334 : : }
1335 : : }
1336 [ + + ]: 362 : } while (num_msg > 0);
1337 : :
1338 : : /* Create an empty file named as $HOME/.cache if the query fails
1339 : : with ENOENT.*/
1340 [ + + ]: 457 : if (rc == -ENOENT)
1341 : : {
1342 : 115 : int efd = open (target_cache_path, O_CREAT|O_EXCL, DEFFILEMODE);
1343 [ + + ]: 115 : if (efd >= 0)
1344 : 114 : close(efd);
1345 : : }
1346 [ + + ]: 342 : else if (rc == -EFBIG)
1347 : 1 : goto out2;
1348 : :
1349 : : /* If the verified_handle is NULL and rc != -ENOENT, the query fails with
1350 : : * an error code other than 404, then do several retry within the retry_limit.
1351 : : * Clean up all old handles and jump back to the beginning of query_in_parallel,
1352 : : * reinitialize handles and query again.*/
1353 [ + + ]: 456 : if (verified_handle == NULL)
1354 : : {
1355 [ + + + + ]: 360 : if (rc != -ENOENT && retry_limit-- > 0)
1356 : : {
1357 [ + + ]: 230 : if (vfd >= 0)
1358 : 10 : dprintf (vfd, "Retry failed query, %d attempt(s) remaining\n", retry_limit);
1359 : : /* remove all handles from multi */
1360 [ + + ]: 460 : for (int i = 0; i < num_urls; i++)
1361 : : {
1362 : 230 : curl_multi_remove_handle(curlm, data[i].handle); /* ok to repeat */
1363 : 230 : curl_easy_cleanup (data[i].handle);
1364 : 230 : free(data[i].response_data);
1365 : : }
1366 : 230 : free(c->winning_headers);
1367 : 230 : c->winning_headers = NULL;
1368 : 230 : goto query_in_parallel;
1369 : : }
1370 : : else
1371 : 130 : goto out2;
1372 : : }
1373 : :
1374 [ + + ]: 96 : if (vfd >= 0)
1375 : : {
1376 [ - + - - ]: 4 : bool pnl = c->default_progressfn_printed_p && vfd == STDERR_FILENO;
1377 [ + - ]: 4 : dprintf (vfd, "%sgot file from server\n", pnl ? "\n" : "");
1378 [ - + ]: 4 : if (pnl)
1379 : 0 : c->default_progressfn_printed_p = 0;
1380 : : }
1381 : :
1382 : : /* we've got one!!!! */
1383 : 96 : time_t mtime;
1384 : 96 : CURLcode curl_res = curl_easy_getinfo(verified_handle, CURLINFO_FILETIME, (void*) &mtime);
1385 [ - + ]: 96 : if (curl_res != CURLE_OK)
1386 : 0 : mtime = time(NULL); /* fall back to current time */
1387 : :
1388 : 96 : struct timeval tvs[2];
1389 : 96 : tvs[0].tv_sec = tvs[1].tv_sec = mtime;
1390 : 96 : tvs[0].tv_usec = tvs[1].tv_usec = 0;
1391 : 96 : (void) futimes (fd, tvs); /* best effort */
1392 : :
1393 : : /* PR27571: make cache files casually unwriteable; dirs are already 0700 */
1394 : 96 : (void) fchmod(fd, 0400);
1395 : :
1396 : : /* rename tmp->real */
1397 : 96 : rc = rename (target_cache_tmppath, target_cache_path);
1398 [ - + ]: 96 : if (rc < 0)
1399 : : {
1400 : 0 : rc = -errno;
1401 : 0 : goto out2;
1402 : : /* Perhaps we need not give up right away; could retry or something ... */
1403 : : }
1404 : :
1405 : : /* remove all handles from multi */
1406 [ + + ]: 216 : for (int i = 0; i < num_urls; i++)
1407 : : {
1408 : 120 : curl_multi_remove_handle(curlm, data[i].handle); /* ok to repeat */
1409 : 120 : curl_easy_cleanup (data[i].handle);
1410 : 120 : free (data[i].response_data);
1411 : : }
1412 : :
1413 [ + + ]: 216 : for (int i = 0; i < num_urls; ++i)
1414 : 120 : free(server_url_list[i]);
1415 : 96 : free(server_url_list);
1416 : 96 : free (data);
1417 : 96 : free (server_urls);
1418 : :
1419 : : /* don't close fd - we're returning it */
1420 : : /* don't unlink the tmppath; it's already been renamed. */
1421 [ + + ]: 96 : if (path != NULL)
1422 : 94 : *path = strdup(target_cache_path);
1423 : :
1424 : 96 : rc = fd;
1425 : 96 : goto out;
1426 : :
1427 : : /* error exits */
1428 : 131 : out2:
1429 : : /* remove all handles from multi */
1430 [ + + ]: 263 : for (int i = 0; i < num_urls; i++)
1431 : : {
1432 [ + - ]: 132 : if (data[i].handle != NULL)
1433 : : {
1434 : 132 : curl_multi_remove_handle(curlm, data[i].handle); /* ok to repeat */
1435 : 132 : curl_easy_cleanup (data[i].handle);
1436 : 132 : free (data[i].response_data);
1437 : : }
1438 : : }
1439 : :
1440 : 131 : unlink (target_cache_tmppath);
1441 : 131 : close (fd); /* before the rmdir, otherwise it'll fail */
1442 : 131 : (void) rmdir (target_cache_dir); /* nop if not empty */
1443 : 131 : free(data);
1444 : :
1445 : 131 : out1:
1446 [ + + ]: 263 : for (int i = 0; i < num_urls; ++i)
1447 : 132 : free(server_url_list[i]);
1448 : 131 : free(server_url_list);
1449 : :
1450 : 131 : out0:
1451 : 131 : free (server_urls);
1452 : :
1453 : : /* general purpose exit */
1454 : 373 : out:
1455 : : /* Reset sent headers */
1456 : 373 : curl_slist_free_all (c->headers);
1457 : 373 : c->headers = NULL;
1458 : 373 : c->user_agent_set_p = 0;
1459 : :
1460 : : /* Conclude the last \r status line */
1461 : : /* Another possibility is to use the ANSI CSI n K EL "Erase in Line"
1462 : : code. That way, the previously printed messages would be erased,
1463 : : and without a newline. */
1464 [ + + ]: 373 : if (c->default_progressfn_printed_p)
1465 : 1 : dprintf(STDERR_FILENO, "\n");
1466 : :
1467 [ + + ]: 373 : if (vfd >= 0)
1468 : : {
1469 [ + + ]: 8 : if (rc < 0)
1470 : 4 : dprintf (vfd, "not found %s (err=%d)\n", strerror (-rc), rc);
1471 : : else
1472 : 4 : dprintf (vfd, "found %s (fd=%d)\n", target_cache_path, rc);
1473 : : }
1474 : :
1475 : 373 : free (cache_path);
1476 : 373 : free (maxage_path);
1477 : 373 : free (interval_path);
1478 : 373 : free (cache_miss_path);
1479 : 373 : free (target_cache_dir);
1480 : 373 : free (target_cache_path);
1481 : 373 : free (target_cache_tmppath);
1482 : 373 : return rc;
1483 : : }
1484 : :
1485 : :
1486 : :
1487 : : /* See debuginfod.h */
1488 : : debuginfod_client *
1489 : 151 : debuginfod_begin (void)
1490 : : {
1491 : : /* Initialize libcurl lazily, but only once. */
1492 : 151 : pthread_once (&init_control, libcurl_init);
1493 : :
1494 : 151 : debuginfod_client *client;
1495 : 151 : size_t size = sizeof (struct debuginfod_client);
1496 : 151 : client = calloc (1, size);
1497 : :
1498 [ + - ]: 151 : if (client != NULL)
1499 : : {
1500 [ + + ]: 151 : if (getenv(DEBUGINFOD_PROGRESS_ENV_VAR))
1501 : 1 : client->progressfn = default_progressfn;
1502 [ + + ]: 151 : if (getenv(DEBUGINFOD_VERBOSE_ENV_VAR))
1503 : 1 : client->verbose_fd = STDERR_FILENO;
1504 : : else
1505 : 150 : client->verbose_fd = -1;
1506 : :
1507 : : // allocate 1 curl multi handle
1508 : 151 : client->server_mhandle = curl_multi_init ();
1509 [ - + ]: 151 : if (client->server_mhandle == NULL)
1510 : 0 : goto out1;
1511 : : }
1512 : :
1513 : : // extra future initialization
1514 : :
1515 : 151 : goto out;
1516 : :
1517 : 0 : out1:
1518 : 0 : free (client);
1519 : 0 : client = NULL;
1520 : :
1521 : 151 : out:
1522 : 151 : return client;
1523 : : }
1524 : :
1525 : : void
1526 : 137 : debuginfod_set_user_data(debuginfod_client *client,
1527 : : void *data)
1528 : : {
1529 : 137 : client->user_data = data;
1530 : 137 : }
1531 : :
1532 : : void *
1533 : 0 : debuginfod_get_user_data(debuginfod_client *client)
1534 : : {
1535 : 0 : return client->user_data;
1536 : : }
1537 : :
1538 : : const char *
1539 : 10 : debuginfod_get_url(debuginfod_client *client)
1540 : : {
1541 [ + + ]: 3 : return client->url;
1542 : : }
1543 : :
1544 : : void
1545 : 149 : debuginfod_end (debuginfod_client *client)
1546 : : {
1547 [ + - ]: 149 : if (client == NULL)
1548 : : return;
1549 : :
1550 : 149 : curl_multi_cleanup (client->server_mhandle);
1551 : 149 : curl_slist_free_all (client->headers);
1552 : 149 : free (client->winning_headers);
1553 : 149 : free (client->url);
1554 : 149 : free (client);
1555 : : }
1556 : :
1557 : : int
1558 : 96 : debuginfod_find_debuginfo (debuginfod_client *client,
1559 : : const unsigned char *build_id, int build_id_len,
1560 : : char **path)
1561 : : {
1562 : 96 : return debuginfod_query_server(client, build_id, build_id_len,
1563 : : "debuginfo", NULL, path);
1564 : : }
1565 : :
1566 : :
1567 : : /* See debuginfod.h */
1568 : : int
1569 : 252 : debuginfod_find_executable(debuginfod_client *client,
1570 : : const unsigned char *build_id, int build_id_len,
1571 : : char **path)
1572 : : {
1573 : 252 : return debuginfod_query_server(client, build_id, build_id_len,
1574 : : "executable", NULL, path);
1575 : : }
1576 : :
1577 : : /* See debuginfod.h */
1578 : 25 : int debuginfod_find_source(debuginfod_client *client,
1579 : : const unsigned char *build_id, int build_id_len,
1580 : : const char *filename, char **path)
1581 : : {
1582 : 25 : return debuginfod_query_server(client, build_id, build_id_len,
1583 : : "source", filename, path);
1584 : : }
1585 : :
1586 : :
1587 : : /* Add an outgoing HTTP header. */
1588 : 611 : int debuginfod_add_http_header (debuginfod_client *client, const char* header)
1589 : : {
1590 : : /* Sanity check header value is of the form Header: Value.
1591 : : It should contain at least one colon that isn't the first or
1592 : : last character. */
1593 : 611 : char *colon = strchr (header, ':'); /* first colon */
1594 : 611 : if (colon == NULL /* present */
1595 [ + - ]: 611 : || colon == header /* not at beginning - i.e., have a header name */
1596 [ + - ]: 611 : || *(colon + 1) == '\0') /* not at end - i.e., have a value */
1597 : : /* NB: but it's okay for a value to contain other colons! */
1598 : : return -EINVAL;
1599 : :
1600 : 611 : struct curl_slist *temp = curl_slist_append (client->headers, header);
1601 [ + - ]: 611 : if (temp == NULL)
1602 : : return -ENOMEM;
1603 : :
1604 : : /* Track if User-Agent: is being set. If so, signal not to add the
1605 : : default one. */
1606 [ + + ]: 611 : if (startswith (header, "User-Agent:"))
1607 : 374 : client->user_agent_set_p = 1;
1608 : :
1609 : 611 : client->headers = temp;
1610 : 611 : return 0;
1611 : : }
1612 : :
1613 : :
1614 : : void
1615 : 253 : debuginfod_set_progressfn(debuginfod_client *client,
1616 : : debuginfod_progressfn_t fn)
1617 : : {
1618 : 253 : client->progressfn = fn;
1619 : 253 : }
1620 : :
1621 : : void
1622 : 15 : debuginfod_set_verbose_fd(debuginfod_client *client, int fd)
1623 : : {
1624 : 15 : client->verbose_fd = fd;
1625 : 15 : }
1626 : :
1627 : : #endif /* DUMMY_LIBDEBUGINFOD */
|