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