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