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
debuginfod_write_callback(char * ptr,size_t size,size_t nmemb,void * data)153 debuginfod_write_callback (char *ptr, size_t size, size_t nmemb, void *data)
154 {
155 ssize_t count = size * nmemb;
156
157 struct handle_data *d = (struct handle_data*)data;
158
159 /* Indicate to other handles that they can abort their transfer. */
160 if (*d->target_handle == NULL)
161 {
162 *d->target_handle = d->handle;
163 /* update the client object */
164 const char *url = NULL;
165 (void) curl_easy_getinfo (d->handle, CURLINFO_EFFECTIVE_URL, &url);
166 if (url)
167 {
168 free (d->client->url);
169 d->client->url = strdup(url); /* ok if fails */
170 }
171 }
172
173 /* If this handle isn't the target handle, abort transfer. */
174 if (*d->target_handle != d->handle)
175 return -1;
176
177 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
debuginfod_init_cache(char * cache_path,char * interval_path,char * maxage_path)184 debuginfod_init_cache (char *cache_path, char *interval_path, char *maxage_path)
185 {
186 struct stat st;
187
188 /* If the cache and config file already exist then we are done. */
189 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 if (stat(cache_path, &st) != 0 && mkdir(cache_path, 0777) < 0)
194 return -errno;
195
196 int fd = -1;
197
198 /* init cleaning interval config file. */
199 fd = open(interval_path, O_CREAT | O_RDWR, 0666);
200 if (fd < 0)
201 return -errno;
202
203 if (dprintf(fd, "%ld", cache_clean_default_interval_s) < 0)
204 return -errno;
205
206 /* init max age config file. */
207 if (stat(maxage_path, &st) != 0
208 && (fd = open(maxage_path, O_CREAT | O_RDWR, 0666)) < 0)
209 return -errno;
210
211 if (dprintf(fd, "%ld", cache_default_max_unused_age_s) < 0)
212 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
debuginfod_clean_cache(debuginfod_client * c,char * cache_path,char * interval_path,char * max_unused_path)221 debuginfod_clean_cache(debuginfod_client *c,
222 char *cache_path, char *interval_path,
223 char *max_unused_path)
224 {
225 struct stat st;
226 FILE *interval_file;
227 FILE *max_unused_file;
228
229 if (stat(interval_path, &st) == -1)
230 {
231 /* Create new interval file. */
232 interval_file = fopen(interval_path, "w");
233
234 if (interval_file == NULL)
235 return -errno;
236
237 int rc = fprintf(interval_file, "%ld", cache_clean_default_interval_s);
238 fclose(interval_file);
239
240 if (rc < 0)
241 return -errno;
242 }
243
244 /* Check timestamp of interval file to see whether cleaning is necessary. */
245 time_t clean_interval;
246 interval_file = fopen(interval_path, "r");
247 if (fscanf(interval_file, "%ld", &clean_interval) != 1)
248 clean_interval = cache_clean_default_interval_s;
249 fclose(interval_file);
250
251 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 time_t max_unused_age;
257 max_unused_file = fopen(max_unused_path, "r");
258 if (max_unused_file)
259 {
260 if (fscanf(max_unused_file, "%ld", &max_unused_age) != 1)
261 max_unused_age = cache_default_max_unused_age_s;
262 fclose(max_unused_file);
263 }
264 else
265 max_unused_age = cache_default_max_unused_age_s;
266
267 char * const dirs[] = { cache_path, NULL, };
268
269 FTS *fts = fts_open(dirs, 0, NULL);
270 if (fts == NULL)
271 return -errno;
272
273 regex_t re;
274 const char * pattern = ".*/[a-f0-9]+/(debuginfo|executable|source.*)$";
275 if (regcomp (&re, pattern, REG_EXTENDED | REG_NOSUB) != 0)
276 return -ENOMEM;
277
278 FTSENT *f;
279 long files = 0;
280 while ((f = fts_read(fts)) != NULL)
281 {
282 /* ignore any files that do not match the pattern. */
283 if (regexec (&re, f->fts_path, 0, NULL, 0) != 0)
284 continue;
285
286 files++;
287 if (c->progressfn) /* inform/check progress callback */
288 if ((c->progressfn) (c, files, 0))
289 break;
290
291 switch (f->fts_info)
292 {
293 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 if (time(NULL) - f->fts_statp->st_atime >= max_unused_age)
297 unlink (f->fts_path);
298 break;
299
300 case FTS_DP:
301 /* Remove if empty. */
302 (void) rmdir (f->fts_path);
303 break;
304
305 default:
306 ;
307 }
308 }
309 fts_close (fts);
310 regfree (&re);
311
312 /* Update timestamp representing when the cache was last cleaned. */
313 utime (interval_path, NULL);
314 return 0;
315 }
316
317
318 #define MAX_BUILD_ID_BYTES 64
319
320
321 static void
add_default_headers(debuginfod_client * client)322 add_default_headers(debuginfod_client *client)
323 {
324 if (client->user_agent_set_p)
325 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 char* utspart = NULL;
332 struct utsname uts;
333 int rc = 0;
334 rc = uname (&uts);
335 if (rc == 0)
336 rc = asprintf(& utspart, "%s/%s", uts.sysname, uts.machine);
337 if (rc < 0)
338 utspart = NULL;
339
340 FILE *f = fopen ("/etc/os-release", "r");
341 if (f == NULL)
342 f = fopen ("/usr/lib/os-release", "r");
343 char *id = NULL;
344 char *version = NULL;
345 if (f != NULL)
346 {
347 while (id == NULL || version == NULL)
348 {
349 char buf[128];
350 char *s = &buf[0];
351 if (fgets (s, sizeof(buf), f) == NULL)
352 break;
353
354 int len = strlen (s);
355 if (len < 3)
356 continue;
357 if (s[len - 1] == '\n')
358 {
359 s[len - 1] = '\0';
360 len--;
361 }
362
363 char *v = strchr (s, '=');
364 if (v == NULL || strlen (v) < 2)
365 continue;
366
367 /* Split var and value. */
368 *v = '\0';
369 v++;
370
371 /* Remove optional quotes around value string. */
372 if (*v == '"' || *v == '\'')
373 {
374 v++;
375 s[len - 1] = '\0';
376 }
377 if (strcmp (s, "ID") == 0)
378 id = strdup (v);
379 if (strcmp (s, "VERSION_ID") == 0)
380 version = strdup (v);
381 }
382 fclose (f);
383 }
384
385 char *ua = NULL;
386 rc = asprintf(& ua, "User-Agent: %s/%s,%s,%s/%s",
387 PACKAGE_NAME, PACKAGE_VERSION,
388 utspart ?: "",
389 id ?: "",
390 version ?: "");
391 if (rc < 0)
392 ua = NULL;
393
394 if (ua)
395 (void) debuginfod_add_http_header (client, ua);
396
397 free (ua);
398 free (id);
399 free (version);
400 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
default_progressfn(debuginfod_client * c,long a,long b)418 default_progressfn (debuginfod_client *c, long a, long b)
419 {
420 const char* url = debuginfod_get_url (c);
421 int len = 0;
422
423 /* We prefer to print the host part of the URL to keep the
424 message short. */
425 if (url != NULL)
426 {
427 const char* buildid = strstr(url, "buildid/");
428 if (buildid != NULL)
429 len = (buildid - url);
430 else
431 len = strlen(url);
432 }
433
434 if (b == 0 || url==NULL) /* early stage */
435 dprintf(STDERR_FILENO,
436 "\rDownloading %c", "-/|\\"[a % 4]);
437 else if (b < 0) /* download in progress but unknown total length */
438 dprintf(STDERR_FILENO,
439 "\rDownloading from %.*s %ld",
440 len, url, a);
441 else /* download in progress, and known total length */
442 dprintf(STDERR_FILENO,
443 "\rDownloading from %.*s %ld/%ld",
444 len, url, a, b);
445 c->default_progressfn_printed_p = 1;
446
447 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
debuginfod_query_server(debuginfod_client * c,const unsigned char * build_id,int build_id_len,const char * type,const char * filename,char ** path)457 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 char *server_urls;
465 char *urls_envvar;
466 char *cache_path = NULL;
467 char *maxage_path = NULL;
468 char *interval_path = NULL;
469 char *target_cache_dir = NULL;
470 char *target_cache_path = NULL;
471 char *target_cache_tmppath = NULL;
472 char suffix[PATH_MAX];
473 char build_id_bytes[MAX_BUILD_ID_BYTES * 2 + 1];
474 int rc;
475
476 /* Clear the obsolete URL from a previous _find operation. */
477 free (c->url);
478 c->url = NULL;
479
480 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 urls_envvar = getenv(server_urls_envvar);
485 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 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 if (build_id_len == 0) /* expect clean hexadecimal */
497 strcpy (build_id_bytes, (const char *) build_id);
498 else
499 for (int i = 0; i < build_id_len; i++)
500 sprintf(build_id_bytes + (i * 2), "%02x", build_id[i]);
501
502 if (filename != NULL)
503 {
504 if (filename[0] != '/') // must start with /
505 return -EINVAL;
506
507 /* copy the filename to suffix, s,/,#,g */
508 unsigned q = 0;
509 for (unsigned fi=0; q < PATH_MAX-1; fi++)
510 switch (filename[fi])
511 {
512 case '\0':
513 suffix[q] = '\0';
514 q = PATH_MAX-1; /* escape for loop too */
515 break;
516 case '/': /* escape / to prevent dir escape */
517 suffix[q++]='#';
518 suffix[q++]='#';
519 break;
520 case '#': /* escape # to prevent /# vs #/ collisions */
521 suffix[q++]='#';
522 suffix[q++]='_';
523 break;
524 default:
525 suffix[q++]=filename[fi];
526 }
527 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 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 char *cache_var = getenv(cache_path_envvar);
550 if (cache_var != NULL && strlen (cache_var) > 0)
551 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 xalloc_str (cache_path, "%s/%s", getenv ("HOME") ?: "/", cache_default_name);
557
558 struct stat st;
559 if (stat (cache_path, &st) < 0)
560 {
561 char cachedir[PATH_MAX];
562 char *xdg = getenv ("XDG_CACHE_HOME");
563
564 if (xdg != NULL && strlen (xdg) > 0)
565 snprintf (cachedir, PATH_MAX, "%s", xdg);
566 else
567 snprintf (cachedir, PATH_MAX, "%s/.cache", getenv ("HOME") ?: "/");
568
569 /* Create XDG cache directory if it doesn't exist. */
570 if (stat (cachedir, &st) == 0)
571 {
572 if (! S_ISDIR (st.st_mode))
573 {
574 rc = -EEXIST;
575 goto out;
576 }
577 }
578 else
579 {
580 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 if (rc < 0
585 && (errno != EEXIST
586 || stat (cachedir, &st) != 0
587 || ! S_ISDIR (st.st_mode)))
588 {
589 rc = -errno;
590 goto out;
591 }
592 }
593
594 free (cache_path);
595 xalloc_str (cache_path, "%s/%s", cachedir, cache_xdg_name);
596 }
597 }
598
599 xalloc_str (target_cache_dir, "%s/%s", cache_path, build_id_bytes);
600 xalloc_str (target_cache_path, "%s/%s%s", target_cache_dir, type, suffix);
601 xalloc_str (target_cache_tmppath, "%s.XXXXXX", target_cache_path);
602
603 /* XXX combine these */
604 xalloc_str (interval_path, "%s/%s", cache_path, cache_clean_interval_filename);
605 xalloc_str (maxage_path, "%s/%s", cache_path, cache_max_unused_age_filename);
606 rc = debuginfod_init_cache(cache_path, interval_path, maxage_path);
607 if (rc != 0)
608 goto out;
609 rc = debuginfod_clean_cache(c, cache_path, interval_path, maxage_path);
610 if (rc != 0)
611 goto out;
612
613 /* If the target is already in the cache then we are done. */
614 int fd = open (target_cache_path, O_RDONLY);
615 if (fd >= 0)
616 {
617 /* Success!!!! */
618 if (path != NULL)
619 *path = strdup(target_cache_path);
620 rc = fd;
621 goto out;
622 }
623
624 long timeout = default_timeout;
625 const char* timeout_envvar = getenv(server_timeout_envvar);
626 if (timeout_envvar != NULL)
627 timeout = atoi (timeout_envvar);
628
629 /* make a copy of the envvar so it can be safely modified. */
630 server_urls = strdup(urls_envvar);
631 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 struct stat st;
640 if (stat(target_cache_dir, &st) == -1 && mkdir(target_cache_dir, 0700) < 0)
641 {
642 rc = -errno;
643 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 fd = mkstemp (target_cache_tmppath);
650 if (fd < 0)
651 {
652 rc = -errno;
653 goto out0;
654 }
655
656 /* Count number of URLs. */
657 int num_urls = 0;
658 for (int i = 0; server_urls[i] != '\0'; i++)
659 if (server_urls[i] != url_delim_char
660 && (i == 0 || server_urls[i - 1] == url_delim_char))
661 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 CURL *target_handle = NULL;
666 struct handle_data *data = malloc(sizeof(struct handle_data) * num_urls);
667
668 /* Initalize handle_data with default values. */
669 for (int i = 0; i < num_urls; i++)
670 {
671 data[i].handle = NULL;
672 data[i].fd = -1;
673 }
674
675 CURLM *curlm = curl_multi_init();
676 if (curlm == NULL)
677 {
678 rc = -ENETUNREACH;
679 goto out0;
680 }
681 /* thereafter, goto out1 on error. */
682
683 char *strtok_saveptr;
684 char *server_url = strtok_r(server_urls, url_delim, &strtok_saveptr);
685
686 /* Initialize each handle. */
687 for (int i = 0; i < num_urls && server_url != NULL; i++)
688 {
689 data[i].fd = fd;
690 data[i].target_handle = &target_handle;
691 data[i].handle = curl_easy_init();
692 data[i].client = c;
693
694 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 char *slashbuildid;
703 if (strlen(server_url) > 1 && server_url[strlen(server_url)-1] == '/')
704 slashbuildid = "buildid";
705 else
706 slashbuildid = "/buildid";
707
708 if (filename) /* must start with / */
709 snprintf(data[i].url, PATH_MAX, "%s%s/%s/%s%s", server_url,
710 slashbuildid, build_id_bytes, type, filename);
711 else
712 snprintf(data[i].url, PATH_MAX, "%s%s/%s/%s", server_url,
713 slashbuildid, build_id_bytes, type);
714
715 curl_easy_setopt(data[i].handle, CURLOPT_URL, data[i].url);
716 curl_easy_setopt(data[i].handle,
717 CURLOPT_WRITEFUNCTION,
718 debuginfod_write_callback);
719 curl_easy_setopt(data[i].handle, CURLOPT_WRITEDATA, (void*)&data[i]);
720 if (timeout > 0)
721 {
722 /* Make sure there is at least some progress,
723 try to get at least 100K per timeout seconds. */
724 curl_easy_setopt (data[i].handle, CURLOPT_LOW_SPEED_TIME,
725 timeout);
726 curl_easy_setopt (data[i].handle, CURLOPT_LOW_SPEED_LIMIT,
727 100 * 1024L);
728 }
729 curl_easy_setopt(data[i].handle, CURLOPT_FILETIME, (long) 1);
730 curl_easy_setopt(data[i].handle, CURLOPT_FOLLOWLOCATION, (long) 1);
731 curl_easy_setopt(data[i].handle, CURLOPT_FAILONERROR, (long) 1);
732 curl_easy_setopt(data[i].handle, CURLOPT_NOSIGNAL, (long) 1);
733 #if LIBCURL_VERSION_NUM >= 0x072a00 /* 7.42.0 */
734 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 curl_easy_setopt(data[i].handle, CURLOPT_AUTOREFERER, (long) 1);
740 curl_easy_setopt(data[i].handle, CURLOPT_ACCEPT_ENCODING, "");
741 curl_easy_setopt(data[i].handle, CURLOPT_HTTPHEADER, c->headers);
742
743 curl_multi_add_handle(curlm, data[i].handle);
744 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 do
751 {
752 /* Wait 1 second, the minimum DEBUGINFOD_TIMEOUT. */
753 curl_multi_wait(curlm, NULL, 0, 1000, NULL);
754
755 /* If the target file has been found, abort the other queries. */
756 if (target_handle != NULL)
757 for (int i = 0; i < num_urls; i++)
758 if (data[i].handle != target_handle)
759 curl_multi_remove_handle(curlm, data[i].handle);
760
761 CURLMcode curlm_res = curl_multi_perform(curlm, &still_running);
762 if (curlm_res != CURLM_OK)
763 {
764 switch (curlm_res)
765 {
766 case CURLM_CALL_MULTI_PERFORM: continue;
767 case CURLM_OUT_OF_MEMORY: rc = -ENOMEM; break;
768 default: rc = -ENETUNREACH; break;
769 }
770 goto out1;
771 }
772
773 if (c->progressfn) /* inform/check progress callback */
774 {
775 loops ++;
776 long pa = loops; /* default params for progress callback */
777 long pb = 0; /* transfer_timeout tempting, but loops != elapsed-time */
778 if (target_handle) /* we've committed to a server; report its download progress */
779 {
780 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 double dl;
790 curl_res = curl_easy_getinfo(target_handle,
791 CURLINFO_SIZE_DOWNLOAD,
792 &dl);
793 if (curl_res == 0)
794 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 double cl;
808 curl_res = curl_easy_getinfo(target_handle,
809 CURLINFO_CONTENT_LENGTH_DOWNLOAD,
810 &cl);
811 if (curl_res == 0)
812 pb = (cl > LONG_MAX ? LONG_MAX : (long)cl);
813 #endif
814 }
815
816 if ((*c->progressfn) (c, pa, pb))
817 break;
818 }
819 } 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 do
827 {
828 CURLMsg *msg;
829
830 msg = curl_multi_info_read(curlm, &num_msg);
831 if (msg != NULL && msg->msg == CURLMSG_DONE)
832 {
833 if (msg->data.result != CURLE_OK)
834 {
835 /* Unsucessful query, determine error code. */
836 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 if (msg->easy_handle != NULL)
858 {
859 char *effective_url = NULL;
860 long resp_code = 500;
861 CURLcode ok1 = curl_easy_getinfo (target_handle,
862 CURLINFO_EFFECTIVE_URL,
863 &effective_url);
864 CURLcode ok2 = curl_easy_getinfo (target_handle,
865 CURLINFO_RESPONSE_CODE,
866 &resp_code);
867 if(ok1 == CURLE_OK && ok2 == CURLE_OK && effective_url)
868 {
869 if (strncmp (effective_url, "http", 4) == 0)
870 if (resp_code == 200)
871 {
872 verified_handle = msg->easy_handle;
873 break;
874 }
875 if (strncmp (effective_url, "file", 4) == 0)
876 if (resp_code == 0)
877 {
878 verified_handle = msg->easy_handle;
879 break;
880 }
881 }
882 }
883 }
884 }
885 } while (num_msg > 0);
886
887 if (verified_handle == NULL)
888 goto out1;
889
890 /* we've got one!!!! */
891 time_t mtime;
892 CURLcode curl_res = curl_easy_getinfo(verified_handle, CURLINFO_FILETIME, (void*) &mtime);
893 if (curl_res != CURLE_OK)
894 mtime = time(NULL); /* fall back to current time */
895
896 struct timeval tvs[2];
897 tvs[0].tv_sec = tvs[1].tv_sec = mtime;
898 tvs[0].tv_usec = tvs[1].tv_usec = 0;
899 (void) futimes (fd, tvs); /* best effort */
900
901 /* rename tmp->real */
902 rc = rename (target_cache_tmppath, target_cache_path);
903 if (rc < 0)
904 {
905 rc = -errno;
906 goto out1;
907 /* Perhaps we need not give up right away; could retry or something ... */
908 }
909
910 /* Success!!!! */
911 for (int i = 0; i < num_urls; i++)
912 curl_easy_cleanup(data[i].handle);
913
914 curl_multi_cleanup (curlm);
915 free (data);
916 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 if (path != NULL)
921 *path = strdup(target_cache_path);
922
923 rc = fd;
924 goto out;
925
926 /* error exits */
927 out1:
928 for (int i = 0; i < num_urls; i++)
929 curl_easy_cleanup(data[i].handle);
930
931 curl_multi_cleanup(curlm);
932 unlink (target_cache_tmppath);
933 close (fd); /* before the rmdir, otherwise it'll fail */
934 (void) rmdir (target_cache_dir); /* nop if not empty */
935 free(data);
936
937 out0:
938 free (server_urls);
939
940 /* general purpose exit */
941 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 if (c->default_progressfn_printed_p)
947 dprintf(STDERR_FILENO, "\n");
948
949 free (cache_path);
950 free (maxage_path);
951 free (interval_path);
952 free (target_cache_dir);
953 free (target_cache_path);
954 free (target_cache_tmppath);
955 return rc;
956 }
957
958
959
960 /* See debuginfod.h */
961 debuginfod_client *
debuginfod_begin(void)962 debuginfod_begin (void)
963 {
964 debuginfod_client *client;
965 size_t size = sizeof (struct debuginfod_client);
966 client = (debuginfod_client *) calloc (1, size);
967 if (client != NULL)
968 {
969 if (getenv(DEBUGINFOD_PROGRESS_ENV_VAR))
970 client->progressfn = default_progressfn;
971 }
972 return client;
973 }
974
975 void
debuginfod_set_user_data(debuginfod_client * client,void * data)976 debuginfod_set_user_data(debuginfod_client *client,
977 void *data)
978 {
979 client->user_data = data;
980 }
981
982 void *
debuginfod_get_user_data(debuginfod_client * client)983 debuginfod_get_user_data(debuginfod_client *client)
984 {
985 return client->user_data;
986 }
987
988 const char *
debuginfod_get_url(debuginfod_client * client)989 debuginfod_get_url(debuginfod_client *client)
990 {
991 return client->url;
992 }
993
994 void
debuginfod_end(debuginfod_client * client)995 debuginfod_end (debuginfod_client *client)
996 {
997 if (client == NULL)
998 return;
999
1000 curl_slist_free_all (client->headers);
1001 free (client->url);
1002 free (client);
1003 }
1004
1005 int
debuginfod_find_debuginfo(debuginfod_client * client,const unsigned char * build_id,int build_id_len,char ** path)1006 debuginfod_find_debuginfo (debuginfod_client *client,
1007 const unsigned char *build_id, int build_id_len,
1008 char **path)
1009 {
1010 return debuginfod_query_server(client, build_id, build_id_len,
1011 "debuginfo", NULL, path);
1012 }
1013
1014
1015 /* See debuginfod.h */
1016 int
debuginfod_find_executable(debuginfod_client * client,const unsigned char * build_id,int build_id_len,char ** path)1017 debuginfod_find_executable(debuginfod_client *client,
1018 const unsigned char *build_id, int build_id_len,
1019 char **path)
1020 {
1021 return debuginfod_query_server(client, build_id, build_id_len,
1022 "executable", NULL, path);
1023 }
1024
1025 /* See debuginfod.h */
debuginfod_find_source(debuginfod_client * client,const unsigned char * build_id,int build_id_len,const char * filename,char ** path)1026 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 return debuginfod_query_server(client, build_id, build_id_len,
1031 "source", filename, path);
1032 }
1033
1034
1035 /* Add an outgoing HTTP header. */
debuginfod_add_http_header(debuginfod_client * client,const char * header)1036 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 char *colon = strchr (header, ':');
1042 if (colon == NULL
1043 || colon == header
1044 || *(colon + 1) == '\0'
1045 || strchr (colon + 1, ':') != NULL)
1046 return -EINVAL;
1047
1048 struct curl_slist *temp = curl_slist_append (client->headers, header);
1049 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 if (strncmp (header, "User-Agent:", 11) == 0)
1055 client->user_agent_set_p = 1;
1056
1057 client->headers = temp;
1058 return 0;
1059 }
1060
1061
1062 void
debuginfod_set_progressfn(debuginfod_client * client,debuginfod_progressfn_t fn)1063 debuginfod_set_progressfn(debuginfod_client *client,
1064 debuginfod_progressfn_t fn)
1065 {
1066 client->progressfn = fn;
1067 }
1068
1069
1070 /* NB: these are thread-unsafe. */
libdebuginfod_ctor(void)1071 __attribute__((constructor)) attribute_hidden void libdebuginfod_ctor(void)
1072 {
1073 curl_global_init(CURL_GLOBAL_DEFAULT);
1074 }
1075
1076 /* NB: this is very thread-unsafe: it breaks other threads that are still in libcurl */
libdebuginfod_dtor(void)1077 __attribute__((destructor)) attribute_hidden void libdebuginfod_dtor(void)
1078 {
1079 /* ... so don't do this: */
1080 /* curl_global_cleanup(); */
1081 }
1082