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 732 : debuginfod_write_callback (char *ptr, size_t size, size_t nmemb, void *data)
176 : {
177 732 : ssize_t count = size * nmemb;
178 :
179 732 : struct handle_data *d = (struct handle_data*)data;
180 :
181 : /* Indicate to other handles that they can abort their transfer. */
182 732 : if (*d->target_handle == NULL)
183 : {
184 38 : *d->target_handle = d->handle;
185 : /* update the client object */
186 38 : const char *url = NULL;
187 38 : (void) curl_easy_getinfo (d->handle, CURLINFO_EFFECTIVE_URL, &url);
188 38 : if (url)
189 : {
190 38 : free (d->client->url);
191 38 : d->client->url = strdup(url); /* ok if fails */
192 : }
193 : }
194 :
195 : /* If this handle isn't the target handle, abort transfer. */
196 732 : if (*d->target_handle != d->handle)
197 : return -1;
198 :
199 732 : 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 52 : debuginfod_init_cache (char *cache_path, char *interval_path, char *maxage_path)
207 : {
208 52 : struct stat st;
209 :
210 : /* If the cache and config file already exist then we are done. */
211 52 : 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 52 : debuginfod_clean_cache(debuginfod_client *c,
244 : char *cache_path, char *interval_path,
245 : char *max_unused_path)
246 : {
247 52 : struct stat st;
248 52 : FILE *interval_file;
249 52 : FILE *max_unused_file;
250 :
251 52 : 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 52 : time_t clean_interval;
268 52 : interval_file = fopen(interval_path, "r");
269 52 : if (interval_file)
270 : {
271 52 : if (fscanf(interval_file, "%ld", &clean_interval) != 1)
272 0 : clean_interval = cache_clean_default_interval_s;
273 52 : fclose(interval_file);
274 : }
275 : else
276 0 : clean_interval = cache_clean_default_interval_s;
277 :
278 52 : 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 58 : add_default_headers(debuginfod_client *client)
350 : {
351 58 : 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 50 : char* utspart = NULL;
359 50 : struct utsname uts;
360 50 : int rc = 0;
361 50 : rc = uname (&uts);
362 50 : if (rc == 0)
363 100 : rc = asprintf(& utspart, "%s/%s", uts.sysname, uts.machine);
364 50 : if (rc < 0)
365 0 : utspart = NULL;
366 :
367 50 : FILE *f = fopen ("/etc/os-release", "r");
368 50 : if (f == NULL)
369 0 : f = fopen ("/usr/lib/os-release", "r");
370 50 : char *id = NULL;
371 50 : char *version = NULL;
372 50 : if (f != NULL)
373 : {
374 300 : while (id == NULL || version == NULL)
375 : {
376 250 : char buf[128];
377 250 : char *s = &buf[0];
378 250 : if (fgets (s, sizeof(buf), f) == NULL)
379 : break;
380 :
381 250 : int len = strlen (s);
382 250 : if (len < 3)
383 50 : continue;
384 200 : if (s[len - 1] == '\n')
385 : {
386 200 : s[len - 1] = '\0';
387 200 : len--;
388 : }
389 :
390 200 : char *v = strchr (s, '=');
391 200 : if (v == NULL || strlen (v) < 2)
392 : continue;
393 :
394 : /* Split var and value. */
395 200 : *v = '\0';
396 200 : v++;
397 :
398 : /* Remove optional quotes around value string. */
399 200 : if (*v == '"' || *v == '\'')
400 : {
401 0 : v++;
402 0 : s[len - 1] = '\0';
403 : }
404 200 : if (strcmp (s, "ID") == 0)
405 50 : id = strdup (v);
406 200 : if (strcmp (s, "VERSION_ID") == 0)
407 50 : version = strdup (v);
408 : }
409 50 : fclose (f);
410 : }
411 :
412 50 : char *ua = NULL;
413 150 : rc = asprintf(& ua, "User-Agent: %s/%s,%s,%s/%s",
414 : PACKAGE_NAME, PACKAGE_VERSION,
415 50 : utspart ?: "",
416 : id ?: "",
417 : version ?: "");
418 50 : if (rc < 0)
419 0 : ua = NULL;
420 :
421 50 : if (ua)
422 50 : (void) debuginfod_add_http_header (client, ua);
423 :
424 50 : free (ua);
425 50 : free (id);
426 50 : free (version);
427 50 : 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 3 : default_progressfn (debuginfod_client *c, long a, long b)
446 : {
447 6 : const char* url = debuginfod_get_url (c);
448 3 : int len = 0;
449 :
450 : /* We prefer to print the host part of the URL to keep the
451 : message short. */
452 3 : 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 3 : if (b == 0 || url==NULL) /* early stage */
462 2 : dprintf(STDERR_FILENO,
463 1 : "\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 3 : c->default_progressfn_printed_p = 1;
473 :
474 3 : 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 58 : 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 58 : char *server_urls;
492 58 : char *urls_envvar;
493 58 : char *cache_path = NULL;
494 58 : char *maxage_path = NULL;
495 58 : char *interval_path = NULL;
496 58 : char *target_cache_dir = NULL;
497 58 : char *target_cache_path = NULL;
498 58 : char *target_cache_tmppath = NULL;
499 58 : char suffix[PATH_MAX + 1]; /* +1 for zero terminator. */
500 58 : char build_id_bytes[MAX_BUILD_ID_BYTES * 2 + 1];
501 58 : int rc;
502 :
503 : /* Clear the obsolete URL from a previous _find operation. */
504 58 : free (c->url);
505 58 : c->url = NULL;
506 :
507 58 : 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 58 : urls_envvar = getenv(server_urls_envvar);
512 58 : 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 52 : if ((build_id_len >= MAX_BUILD_ID_BYTES) ||
520 51 : (build_id_len == 0 &&
521 51 : strlen ((const char *) build_id) > MAX_BUILD_ID_BYTES*2))
522 : return -EINVAL;
523 52 : if (build_id_len == 0) /* expect clean hexadecimal */
524 51 : 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 52 : if (filename != NULL)
530 : {
531 11 : if (filename[0] != '/') // must start with /
532 : return -EINVAL;
533 :
534 : /* copy the filename to suffix, s,/,#,g */
535 : unsigned q = 0;
536 442 : for (unsigned fi=0; q < PATH_MAX-2; fi++) /* -2, escape is 2 chars. */
537 431 : switch (filename[fi])
538 : {
539 11 : case '\0':
540 11 : suffix[q] = '\0';
541 11 : q = PATH_MAX-1; /* escape for loop too */
542 11 : break;
543 60 : case '/': /* escape / to prevent dir escape */
544 60 : suffix[q++]='#';
545 60 : suffix[q++]='#';
546 60 : break;
547 0 : case '#': /* escape # to prevent /# vs #/ collisions */
548 0 : suffix[q++]='#';
549 0 : suffix[q++]='_';
550 0 : break;
551 360 : default:
552 360 : suffix[q++]=filename[fi];
553 : }
554 11 : 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 41 : 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 52 : char *cache_var = getenv(cache_path_envvar);
577 52 : if (cache_var != NULL && strlen (cache_var) > 0)
578 48 : 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 52 : xalloc_str (target_cache_dir, "%s/%s", cache_path, build_id_bytes);
627 52 : xalloc_str (target_cache_path, "%s/%s%s", target_cache_dir, type, suffix);
628 52 : xalloc_str (target_cache_tmppath, "%s.XXXXXX", target_cache_path);
629 :
630 : /* XXX combine these */
631 52 : xalloc_str (interval_path, "%s/%s", cache_path, cache_clean_interval_filename);
632 52 : xalloc_str (maxage_path, "%s/%s", cache_path, cache_max_unused_age_filename);
633 52 : rc = debuginfod_init_cache(cache_path, interval_path, maxage_path);
634 52 : if (rc != 0)
635 : goto out;
636 52 : rc = debuginfod_clean_cache(c, cache_path, interval_path, maxage_path);
637 52 : if (rc != 0)
638 : goto out;
639 :
640 : /* If the target is already in the cache then we are done. */
641 52 : int fd = open (target_cache_path, O_RDONLY);
642 52 : if (fd >= 0)
643 : {
644 : /* Success!!!! */
645 8 : if (path != NULL)
646 8 : *path = strdup(target_cache_path);
647 8 : rc = fd;
648 8 : goto out;
649 : }
650 :
651 44 : long timeout = default_timeout;
652 44 : const char* timeout_envvar = getenv(server_timeout_envvar);
653 44 : if (timeout_envvar != NULL)
654 44 : timeout = atoi (timeout_envvar);
655 :
656 : /* make a copy of the envvar so it can be safely modified. */
657 44 : server_urls = strdup(urls_envvar);
658 44 : 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 44 : struct stat st;
667 44 : 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 44 : fd = mkstemp (target_cache_tmppath);
677 44 : 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 1077 : for (int i = 0; server_urls[i] != '\0'; i++)
686 1033 : if (server_urls[i] != url_delim_char
687 1029 : && (i == 0 || server_urls[i - 1] == url_delim_char))
688 48 : num_urls++;
689 :
690 44 : CURLM *curlm = curl_multi_init();
691 44 : 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 44 : CURL *target_handle = NULL;
700 44 : struct handle_data *data = malloc(sizeof(struct handle_data) * num_urls);
701 44 : 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 92 : for (int i = 0; i < num_urls; i++)
711 : {
712 48 : data[i].handle = NULL;
713 48 : data[i].fd = -1;
714 : }
715 :
716 44 : char *strtok_saveptr;
717 44 : char *server_url = strtok_r(server_urls, url_delim, &strtok_saveptr);
718 :
719 : /* Initialize each handle. */
720 92 : for (int i = 0; i < num_urls && server_url != NULL; i++)
721 : {
722 48 : data[i].fd = fd;
723 48 : data[i].target_handle = &target_handle;
724 48 : data[i].handle = curl_easy_init();
725 48 : data[i].client = c;
726 :
727 48 : 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 48 : char *slashbuildid;
736 48 : if (strlen(server_url) > 1 && server_url[strlen(server_url)-1] == '/')
737 : slashbuildid = "buildid";
738 : else
739 10 : slashbuildid = "/buildid";
740 :
741 48 : if (filename) /* must start with / */
742 11 : snprintf(data[i].url, PATH_MAX, "%s%s/%s/%s%s", server_url,
743 : slashbuildid, build_id_bytes, type, filename);
744 : else
745 37 : snprintf(data[i].url, PATH_MAX, "%s%s/%s/%s", server_url,
746 : slashbuildid, build_id_bytes, type);
747 :
748 48 : curl_easy_setopt(data[i].handle, CURLOPT_URL, data[i].url);
749 48 : curl_easy_setopt(data[i].handle,
750 : CURLOPT_WRITEFUNCTION,
751 : debuginfod_write_callback);
752 48 : curl_easy_setopt(data[i].handle, CURLOPT_WRITEDATA, (void*)&data[i]);
753 48 : if (timeout > 0)
754 : {
755 : /* Make sure there is at least some progress,
756 : try to get at least 100K per timeout seconds. */
757 48 : curl_easy_setopt (data[i].handle, CURLOPT_LOW_SPEED_TIME,
758 : timeout);
759 48 : curl_easy_setopt (data[i].handle, CURLOPT_LOW_SPEED_LIMIT,
760 : 100 * 1024L);
761 : }
762 48 : curl_easy_setopt(data[i].handle, CURLOPT_FILETIME, (long) 1);
763 48 : curl_easy_setopt(data[i].handle, CURLOPT_FOLLOWLOCATION, (long) 1);
764 48 : curl_easy_setopt(data[i].handle, CURLOPT_FAILONERROR, (long) 1);
765 48 : curl_easy_setopt(data[i].handle, CURLOPT_NOSIGNAL, (long) 1);
766 : #if LIBCURL_VERSION_NUM >= 0x072a00 /* 7.42.0 */
767 48 : 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 48 : curl_easy_setopt(data[i].handle, CURLOPT_AUTOREFERER, (long) 1);
773 48 : curl_easy_setopt(data[i].handle, CURLOPT_ACCEPT_ENCODING, "");
774 48 : curl_easy_setopt(data[i].handle, CURLOPT_HTTPHEADER, c->headers);
775 :
776 48 : curl_multi_add_handle(curlm, data[i].handle);
777 48 : 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 6377 : do
784 : {
785 : /* Wait 1 second, the minimum DEBUGINFOD_TIMEOUT. */
786 6377 : curl_multi_wait(curlm, NULL, 0, 1000, NULL);
787 :
788 : /* If the target file has been found, abort the other queries. */
789 6377 : if (target_handle != NULL)
790 1388 : for (int i = 0; i < num_urls; i++)
791 694 : if (data[i].handle != target_handle)
792 0 : curl_multi_remove_handle(curlm, data[i].handle);
793 :
794 6377 : CURLMcode curlm_res = curl_multi_perform(curlm, &still_running);
795 6377 : 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 6377 : if (c->progressfn) /* inform/check progress callback */
807 : {
808 12 : loops ++;
809 12 : long pa = loops; /* default params for progress callback */
810 12 : long pb = 0; /* transfer_timeout tempting, but loops != elapsed-time */
811 12 : 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 12 : if ((*c->progressfn) (c, pa, pb))
850 : break;
851 : }
852 6377 : } 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 48 : do
860 : {
861 48 : CURLMsg *msg;
862 :
863 48 : msg = curl_multi_info_read(curlm, &num_msg);
864 48 : if (msg != NULL && msg->msg == CURLMSG_DONE)
865 : {
866 48 : 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_REMOTE_ACCESS_DENIED: rc = -EACCES; break;
875 : case CURLE_WRITE_ERROR: rc = -EIO; break;
876 : case CURLE_OUT_OF_MEMORY: rc = -ENOMEM; break;
877 : case CURLE_TOO_MANY_REDIRECTS: rc = -EMLINK; break;
878 : case CURLE_SEND_ERROR: rc = -ECONNRESET; break;
879 : case CURLE_RECV_ERROR: rc = -ECONNRESET; break;
880 : case CURLE_OPERATION_TIMEDOUT: rc = -ETIME; break;
881 : default: rc = -ENOENT; break;
882 : }
883 : }
884 : else
885 : {
886 : /* Query completed without an error. Confirm that the
887 : response code is 200 when using HTTP/HTTPS and 0 when
888 : using file:// and set verified_handle. */
889 :
890 38 : if (msg->easy_handle != NULL)
891 : {
892 38 : char *effective_url = NULL;
893 38 : long resp_code = 500;
894 38 : CURLcode ok1 = curl_easy_getinfo (target_handle,
895 : CURLINFO_EFFECTIVE_URL,
896 : &effective_url);
897 38 : CURLcode ok2 = curl_easy_getinfo (target_handle,
898 : CURLINFO_RESPONSE_CODE,
899 : &resp_code);
900 38 : if(ok1 == CURLE_OK && ok2 == CURLE_OK && effective_url)
901 : {
902 38 : if (strncasecmp (effective_url, "HTTP", 4) == 0)
903 36 : if (resp_code == 200)
904 : {
905 36 : verified_handle = msg->easy_handle;
906 38 : break;
907 : }
908 2 : if (strncasecmp (effective_url, "FILE", 4) == 0)
909 1 : if (resp_code == 0)
910 : {
911 1 : verified_handle = msg->easy_handle;
912 1 : break;
913 : }
914 : }
915 : /* - libcurl since 7.52.0 version start to support
916 : CURLINFO_SCHEME;
917 : - before 7.61.0, effective_url would give us a
918 : url with upper case SCHEME added in the front;
919 : - effective_url between 7.61 and 7.69 can be lack
920 : of scheme if the original url doesn't include one;
921 : - since version 7.69 effective_url will be provide
922 : a scheme in lower case. */
923 : #if LIBCURL_VERSION_NUM >= 0x073d00 /* 7.61.0 */
924 : #if LIBCURL_VERSION_NUM <= 0x074500 /* 7.69.0 */
925 1 : char *scheme = NULL;
926 1 : CURLcode ok3 = curl_easy_getinfo (target_handle,
927 : CURLINFO_SCHEME,
928 : &scheme);
929 1 : if(ok3 == CURLE_OK && scheme)
930 : {
931 1 : if (strncmp (scheme, "HTTP", 4) == 0)
932 1 : if (resp_code == 200)
933 : {
934 1 : verified_handle = msg->easy_handle;
935 1 : break;
936 : }
937 : }
938 : #endif
939 : #endif
940 : }
941 : }
942 : }
943 10 : } while (num_msg > 0);
944 :
945 44 : if (verified_handle == NULL)
946 : goto out1;
947 :
948 : /* we've got one!!!! */
949 38 : time_t mtime;
950 38 : CURLcode curl_res = curl_easy_getinfo(verified_handle, CURLINFO_FILETIME, (void*) &mtime);
951 38 : if (curl_res != CURLE_OK)
952 0 : mtime = time(NULL); /* fall back to current time */
953 :
954 38 : struct timeval tvs[2];
955 38 : tvs[0].tv_sec = tvs[1].tv_sec = mtime;
956 38 : tvs[0].tv_usec = tvs[1].tv_usec = 0;
957 38 : (void) futimes (fd, tvs); /* best effort */
958 :
959 : /* rename tmp->real */
960 38 : rc = rename (target_cache_tmppath, target_cache_path);
961 38 : if (rc < 0)
962 : {
963 0 : rc = -errno;
964 0 : goto out1;
965 : /* Perhaps we need not give up right away; could retry or something ... */
966 : }
967 :
968 : /* Success!!!! */
969 76 : for (int i = 0; i < num_urls; i++)
970 38 : curl_easy_cleanup(data[i].handle);
971 :
972 38 : curl_multi_cleanup (curlm);
973 38 : free (data);
974 38 : free (server_urls);
975 :
976 : /* don't close fd - we're returning it */
977 : /* don't unlink the tmppath; it's already been renamed. */
978 38 : if (path != NULL)
979 37 : *path = strdup(target_cache_path);
980 :
981 38 : rc = fd;
982 38 : goto out;
983 :
984 : /* error exits */
985 6 : out1:
986 16 : for (int i = 0; i < num_urls; i++)
987 10 : curl_easy_cleanup(data[i].handle);
988 :
989 6 : curl_multi_cleanup(curlm);
990 6 : unlink (target_cache_tmppath);
991 6 : close (fd); /* before the rmdir, otherwise it'll fail */
992 6 : (void) rmdir (target_cache_dir); /* nop if not empty */
993 6 : free(data);
994 :
995 6 : out0:
996 6 : free (server_urls);
997 :
998 : /* general purpose exit */
999 58 : out:
1000 : /* Conclude the last \r status line */
1001 : /* Another possibility is to use the ANSI CSI n K EL "Erase in Line"
1002 : code. That way, the previously printed messages would be erased,
1003 : and without a newline. */
1004 58 : if (c->default_progressfn_printed_p)
1005 1 : dprintf(STDERR_FILENO, "\n");
1006 :
1007 58 : free (cache_path);
1008 58 : free (maxage_path);
1009 58 : free (interval_path);
1010 58 : free (target_cache_dir);
1011 58 : free (target_cache_path);
1012 58 : free (target_cache_tmppath);
1013 58 : return rc;
1014 : }
1015 :
1016 :
1017 :
1018 : /* See debuginfod.h */
1019 : debuginfod_client *
1020 58 : debuginfod_begin (void)
1021 : {
1022 58 : debuginfod_client *client;
1023 58 : size_t size = sizeof (struct debuginfod_client);
1024 58 : client = (debuginfod_client *) calloc (1, size);
1025 58 : if (client != NULL)
1026 : {
1027 58 : if (getenv(DEBUGINFOD_PROGRESS_ENV_VAR))
1028 1 : client->progressfn = default_progressfn;
1029 : }
1030 58 : return client;
1031 : }
1032 :
1033 : void
1034 50 : debuginfod_set_user_data(debuginfod_client *client,
1035 : void *data)
1036 : {
1037 50 : client->user_data = data;
1038 50 : }
1039 :
1040 : void *
1041 0 : debuginfod_get_user_data(debuginfod_client *client)
1042 : {
1043 0 : return client->user_data;
1044 : }
1045 :
1046 : const char *
1047 1 : debuginfod_get_url(debuginfod_client *client)
1048 : {
1049 3 : return client->url;
1050 : }
1051 :
1052 : void
1053 58 : debuginfod_end (debuginfod_client *client)
1054 : {
1055 58 : if (client == NULL)
1056 : return;
1057 :
1058 58 : curl_slist_free_all (client->headers);
1059 58 : free (client->url);
1060 58 : free (client);
1061 : }
1062 :
1063 : int
1064 26 : debuginfod_find_debuginfo (debuginfod_client *client,
1065 : const unsigned char *build_id, int build_id_len,
1066 : char **path)
1067 : {
1068 26 : return debuginfod_query_server(client, build_id, build_id_len,
1069 : "debuginfo", NULL, path);
1070 : }
1071 :
1072 :
1073 : /* See debuginfod.h */
1074 : int
1075 20 : debuginfod_find_executable(debuginfod_client *client,
1076 : const unsigned char *build_id, int build_id_len,
1077 : char **path)
1078 : {
1079 20 : return debuginfod_query_server(client, build_id, build_id_len,
1080 : "executable", NULL, path);
1081 : }
1082 :
1083 : /* See debuginfod.h */
1084 12 : int debuginfod_find_source(debuginfod_client *client,
1085 : const unsigned char *build_id, int build_id_len,
1086 : const char *filename, char **path)
1087 : {
1088 12 : return debuginfod_query_server(client, build_id, build_id_len,
1089 : "source", filename, path);
1090 : }
1091 :
1092 :
1093 : /* Add an outgoing HTTP header. */
1094 66 : int debuginfod_add_http_header (debuginfod_client *client, const char* header)
1095 : {
1096 : /* Sanity check header value is of the form Header: Value.
1097 : It should contain exactly one colon that isn't the first or
1098 : last character. */
1099 66 : char *colon = strchr (header, ':');
1100 132 : if (colon == NULL
1101 66 : || colon == header
1102 66 : || *(colon + 1) == '\0'
1103 66 : || strchr (colon + 1, ':') != NULL)
1104 : return -EINVAL;
1105 :
1106 66 : struct curl_slist *temp = curl_slist_append (client->headers, header);
1107 66 : if (temp == NULL)
1108 : return -ENOMEM;
1109 :
1110 : /* Track if User-Agent: is being set. If so, signal not to add the
1111 : default one. */
1112 66 : if (strncmp (header, "User-Agent:", 11) == 0)
1113 58 : client->user_agent_set_p = 1;
1114 :
1115 66 : client->headers = temp;
1116 66 : return 0;
1117 : }
1118 :
1119 :
1120 : void
1121 9 : debuginfod_set_progressfn(debuginfod_client *client,
1122 : debuginfod_progressfn_t fn)
1123 : {
1124 9 : client->progressfn = fn;
1125 9 : }
1126 :
1127 :
1128 : /* NB: these are thread-unsafe. */
1129 53 : __attribute__((constructor)) attribute_hidden void libdebuginfod_ctor(void)
1130 : {
1131 53 : curl_global_init(CURL_GLOBAL_DEFAULT);
1132 53 : }
1133 :
1134 : /* NB: this is very thread-unsafe: it breaks other threads that are still in libcurl */
1135 0 : __attribute__((destructor)) attribute_hidden void libdebuginfod_dtor(void)
1136 : {
1137 : /* ... so don't do this: */
1138 : /* curl_global_cleanup(); */
1139 0 : }
1140 :
1141 : #endif /* DUMMY_LIBDEBUGINFOD */
|