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