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