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