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