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