1 /* Retrieve ELF / DWARF / source files from the debuginfod.
2 Copyright (C) 2019-2021 Red Hat, Inc.
3 Copyright (C) 2021, 2022 Mark J. Wielaard <mark@klomp.org>
4 This file is part of elfutils.
5
6 This file is free software; you can redistribute it and/or modify
7 it under the terms of either
8
9 * the GNU Lesser General Public License as published by the Free
10 Software Foundation; either version 3 of the License, or (at
11 your option) any later version
12
13 or
14
15 * the GNU General Public License as published by the Free
16 Software Foundation; either version 2 of the License, or (at
17 your option) any later version
18
19 or both in parallel, as here.
20
21 elfutils is distributed in the hope that it will be useful, but
22 WITHOUT ANY WARRANTY; without even the implied warranty of
23 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
24 General Public License for more details.
25
26 You should have received copies of the GNU General Public License and
27 the GNU Lesser General Public License along with this program. If
28 not, see <http://www.gnu.org/licenses/>. */
29
30
31 /* cargo-cult from libdwfl linux-kernel-modules.c */
32 /* In case we have a bad fts we include this before config.h because it
33 can't handle _FILE_OFFSET_BITS.
34 Everything we need here is fine if its declarations just come first.
35 Also, include sys/types.h before fts. On some systems fts.h is not self
36 contained. */
37 #ifdef BAD_FTS
38 #include <sys/types.h>
39 #include <fts.h>
40 #endif
41
42 #include "config.h"
43 #include "debuginfod.h"
44 #include "system.h"
45 #include <ctype.h>
46 #include <errno.h>
47 #include <stdlib.h>
48 #include <gelf.h>
49
50 /* We might be building a bootstrap dummy library, which is really simple. */
51 #ifdef DUMMY_LIBDEBUGINFOD
52
debuginfod_begin(void)53 debuginfod_client *debuginfod_begin (void) { errno = ENOSYS; return NULL; }
debuginfod_find_debuginfo(debuginfod_client * c,const unsigned char * b,int s,char ** p)54 int debuginfod_find_debuginfo (debuginfod_client *c, const unsigned char *b,
55 int s, char **p) { return -ENOSYS; }
debuginfod_find_executable(debuginfod_client * c,const unsigned char * b,int s,char ** p)56 int debuginfod_find_executable (debuginfod_client *c, const unsigned char *b,
57 int s, char **p) { return -ENOSYS; }
debuginfod_find_source(debuginfod_client * c,const unsigned char * b,int s,const char * f,char ** p)58 int debuginfod_find_source (debuginfod_client *c, const unsigned char *b,
59 int s, const char *f, char **p) { return -ENOSYS; }
debuginfod_find_section(debuginfod_client * c,const unsigned char * b,int s,const char * scn,char ** p)60 int debuginfod_find_section (debuginfod_client *c, const unsigned char *b,
61 int s, const char *scn, char **p)
62 { return -ENOSYS; }
debuginfod_set_progressfn(debuginfod_client * c,debuginfod_progressfn_t fn)63 void debuginfod_set_progressfn(debuginfod_client *c,
64 debuginfod_progressfn_t fn) { }
debuginfod_set_verbose_fd(debuginfod_client * c,int fd)65 void debuginfod_set_verbose_fd(debuginfod_client *c, int fd) { }
debuginfod_set_user_data(debuginfod_client * c,void * d)66 void debuginfod_set_user_data (debuginfod_client *c, void *d) { }
debuginfod_get_user_data(debuginfod_client * c)67 void* debuginfod_get_user_data (debuginfod_client *c) { return NULL; }
debuginfod_get_url(debuginfod_client * c)68 const char* debuginfod_get_url (debuginfod_client *c) { return NULL; }
debuginfod_add_http_header(debuginfod_client * c,const char * h)69 int debuginfod_add_http_header (debuginfod_client *c,
70 const char *h) { return -ENOSYS; }
debuginfod_get_headers(debuginfod_client * c)71 const char* debuginfod_get_headers (debuginfod_client *c) { return NULL; }
72
debuginfod_end(debuginfod_client * c)73 void debuginfod_end (debuginfod_client *c) { }
74
75 #else /* DUMMY_LIBDEBUGINFOD */
76
77 #include <assert.h>
78 #include <dirent.h>
79 #include <stdio.h>
80 #include <errno.h>
81 #include <unistd.h>
82 #include <fcntl.h>
83 #include <fts.h>
84 #include <regex.h>
85 #include <string.h>
86 #include <stdbool.h>
87 #include <linux/limits.h>
88 #include <time.h>
89 #include <utime.h>
90 #include <sys/syscall.h>
91 #include <sys/types.h>
92 #include <sys/stat.h>
93 #include <sys/utsname.h>
94 #include <curl/curl.h>
95
96 /* If fts.h is included before config.h, its indirect inclusions may not
97 give us the right LFS aliases of these functions, so map them manually. */
98 #ifdef BAD_FTS
99 #ifdef _FILE_OFFSET_BITS
100 #define open open64
101 #define fopen fopen64
102 #endif
103 #else
104 #include <sys/types.h>
105 #include <fts.h>
106 #endif
107
108 #include <pthread.h>
109
110 static pthread_once_t init_control = PTHREAD_ONCE_INIT;
111
112 static void
libcurl_init(void)113 libcurl_init(void)
114 {
115 curl_global_init(CURL_GLOBAL_DEFAULT);
116 }
117
118 struct debuginfod_client
119 {
120 /* Progress/interrupt callback function. */
121 debuginfod_progressfn_t progressfn;
122
123 /* Stores user data. */
124 void* user_data;
125
126 /* Stores current/last url, if any. */
127 char* url;
128
129 /* Accumulates outgoing http header names/values. */
130 int user_agent_set_p; /* affects add_default_headers */
131 struct curl_slist *headers;
132
133 /* Flags the default_progressfn having printed something that
134 debuginfod_end needs to terminate. */
135 int default_progressfn_printed_p;
136
137 /* Indicates whether the last query was cancelled by progressfn. */
138 bool progressfn_cancel;
139
140 /* File descriptor to output any verbose messages if > 0. */
141 int verbose_fd;
142
143 /* Maintain a long-lived curl multi-handle, which keeps a
144 connection/tls/dns cache to recently seen servers. */
145 CURLM *server_mhandle;
146
147 /* Can contain all other context, like cache_path, server_urls,
148 timeout or other info gotten from environment variables, the
149 handle data, etc. So those don't have to be reparsed and
150 recreated on each request. */
151 char * winning_headers;
152 };
153
154 /* The cache_clean_interval_s file within the debuginfod cache specifies
155 how frequently the cache should be cleaned. The file's st_mtime represents
156 the time of last cleaning. */
157 static const char *cache_clean_interval_filename = "cache_clean_interval_s";
158 static const long cache_clean_default_interval_s = 86400; /* 1 day */
159
160 /* The cache_miss_default_s within the debuginfod cache specifies how
161 frequently the empty file should be released.*/
162 static const long cache_miss_default_s = 600; /* 10 min */
163 static const char *cache_miss_filename = "cache_miss_s";
164
165 /* The cache_max_unused_age_s file within the debuginfod cache specifies the
166 the maximum time since last access that a file will remain in the cache. */
167 static const char *cache_max_unused_age_filename = "max_unused_age_s";
168 static const long cache_default_max_unused_age_s = 604800; /* 1 week */
169
170 /* Location of the cache of files downloaded from debuginfods.
171 The default parent directory is $HOME, or '/' if $HOME doesn't exist. */
172 static const char *cache_default_name = ".debuginfod_client_cache";
173 static const char *cache_xdg_name = "debuginfod_client";
174
175 /* URLs of debuginfods, separated by url_delim. */
176 static const char *url_delim = " ";
177
178 /* Timeout for debuginfods, in seconds (to get at least 100K). */
179 static const long default_timeout = 90;
180
181 /* Default retry count for download error. */
182 static const long default_retry_limit = 2;
183
184 /* Data associated with a particular CURL easy handle. Passed to
185 the write callback. */
186 struct handle_data
187 {
188 /* Cache file to be written to in case query is successful. */
189 int fd;
190
191 /* URL queried by this handle. */
192 char url[PATH_MAX];
193
194 /* error buffer for this handle. */
195 char errbuf[CURL_ERROR_SIZE];
196
197 /* This handle. */
198 CURL *handle;
199
200 /* The client object whom we're serving. */
201 debuginfod_client *client;
202
203 /* Pointer to handle that should write to fd. Initially points to NULL,
204 then points to the first handle that begins writing the target file
205 to the cache. Used to ensure that a file is not downloaded from
206 multiple servers unnecessarily. */
207 CURL **target_handle;
208 /* Response http headers for this client handle, sent from the server */
209 char *response_data;
210 size_t response_data_size;
211 };
212
213 static size_t
debuginfod_write_callback(char * ptr,size_t size,size_t nmemb,void * data)214 debuginfod_write_callback (char *ptr, size_t size, size_t nmemb, void *data)
215 {
216 ssize_t count = size * nmemb;
217
218 struct handle_data *d = (struct handle_data*)data;
219
220 /* Indicate to other handles that they can abort their transfer. */
221 if (*d->target_handle == NULL)
222 {
223 *d->target_handle = d->handle;
224 /* update the client object */
225 const char *url = NULL;
226 CURLcode curl_res = curl_easy_getinfo (d->handle,
227 CURLINFO_EFFECTIVE_URL, &url);
228 if (curl_res == CURLE_OK && url)
229 {
230 free (d->client->url);
231 d->client->url = strdup(url); /* ok if fails */
232 }
233 }
234
235 /* If this handle isn't the target handle, abort transfer. */
236 if (*d->target_handle != d->handle)
237 return -1;
238
239 return (size_t) write(d->fd, (void*)ptr, count);
240 }
241
242 /* handle config file read and write */
243 static int
debuginfod_config_cache(char * config_path,long cache_config_default_s,struct stat * st)244 debuginfod_config_cache(char *config_path,
245 long cache_config_default_s,
246 struct stat *st)
247 {
248 int fd = open(config_path, O_CREAT | O_RDWR, DEFFILEMODE);
249 if (fd < 0)
250 return -errno;
251
252 if (fstat (fd, st) < 0)
253 {
254 int ret = -errno;
255 close (fd);
256 return ret;
257 }
258
259 if (st->st_size == 0)
260 {
261 if (dprintf(fd, "%ld", cache_config_default_s) < 0)
262 {
263 int ret = -errno;
264 close (fd);
265 return ret;
266 }
267
268 close (fd);
269 return cache_config_default_s;
270 }
271
272 long cache_config;
273 FILE *config_file = fdopen(fd, "r");
274 if (config_file)
275 {
276 if (fscanf(config_file, "%ld", &cache_config) != 1)
277 cache_config = cache_config_default_s;
278 fclose(config_file);
279 }
280 else
281 cache_config = cache_config_default_s;
282
283 close (fd);
284 return cache_config;
285 }
286
287 /* Delete any files that have been unmodied for a period
288 longer than $DEBUGINFOD_CACHE_CLEAN_INTERVAL_S. */
289 static int
debuginfod_clean_cache(debuginfod_client * c,char * cache_path,char * interval_path,char * max_unused_path)290 debuginfod_clean_cache(debuginfod_client *c,
291 char *cache_path, char *interval_path,
292 char *max_unused_path)
293 {
294 time_t clean_interval, max_unused_age;
295 int rc = -1;
296 struct stat st;
297
298 /* Create new interval file. */
299 rc = debuginfod_config_cache(interval_path,
300 cache_clean_default_interval_s, &st);
301 if (rc < 0)
302 return rc;
303 clean_interval = (time_t)rc;
304
305 /* Check timestamp of interval file to see whether cleaning is necessary. */
306 if (time(NULL) - st.st_mtime < clean_interval)
307 /* Interval has not passed, skip cleaning. */
308 return 0;
309
310 /* Update timestamp representing when the cache was last cleaned.
311 Do it at the start to reduce the number of threads trying to do a
312 cleanup simultaniously. */
313 utime (interval_path, NULL);
314
315 /* Read max unused age value from config file. */
316 rc = debuginfod_config_cache(max_unused_path,
317 cache_default_max_unused_age_s, &st);
318 if (rc < 0)
319 return rc;
320 max_unused_age = (time_t)rc;
321
322 char * const dirs[] = { cache_path, NULL, };
323
324 FTS *fts = fts_open(dirs, 0, NULL);
325 if (fts == NULL)
326 return -errno;
327
328 regex_t re;
329 const char * pattern = ".*/[a-f0-9]+(/debuginfo|/executable|/source.*|)$"; /* include dirs */
330 if (regcomp (&re, pattern, REG_EXTENDED | REG_NOSUB) != 0)
331 return -ENOMEM;
332
333 FTSENT *f;
334 long files = 0;
335 time_t now = time(NULL);
336 while ((f = fts_read(fts)) != NULL)
337 {
338 /* ignore any files that do not match the pattern. */
339 if (regexec (&re, f->fts_path, 0, NULL, 0) != 0)
340 continue;
341
342 files++;
343 if (c->progressfn) /* inform/check progress callback */
344 if ((c->progressfn) (c, files, 0))
345 break;
346
347 switch (f->fts_info)
348 {
349 case FTS_F:
350 /* delete file if max_unused_age has been met or exceeded w.r.t. atime. */
351 if (now - f->fts_statp->st_atime >= max_unused_age)
352 (void) unlink (f->fts_path);
353 break;
354
355 case FTS_DP:
356 /* Remove if old & empty. Weaken race against concurrent creation by
357 checking mtime. */
358 if (now - f->fts_statp->st_mtime >= max_unused_age)
359 (void) rmdir (f->fts_path);
360 break;
361
362 default:
363 ;
364 }
365 }
366 fts_close (fts);
367 regfree (&re);
368
369 return 0;
370 }
371
372
373 #define MAX_BUILD_ID_BYTES 64
374
375
376 static void
add_default_headers(debuginfod_client * client)377 add_default_headers(debuginfod_client *client)
378 {
379 if (client->user_agent_set_p)
380 return;
381
382 /* Compute a User-Agent: string to send. The more accurately this
383 describes this host, the likelier that the debuginfod servers
384 might be able to locate debuginfo for us. */
385
386 char* utspart = NULL;
387 struct utsname uts;
388 int rc = 0;
389 rc = uname (&uts);
390 if (rc == 0)
391 rc = asprintf(& utspart, "%s/%s", uts.sysname, uts.machine);
392 if (rc < 0)
393 utspart = NULL;
394
395 FILE *f = fopen ("/etc/os-release", "r");
396 if (f == NULL)
397 f = fopen ("/usr/lib/os-release", "r");
398 char *id = NULL;
399 char *version = NULL;
400 if (f != NULL)
401 {
402 while (id == NULL || version == NULL)
403 {
404 char buf[128];
405 char *s = &buf[0];
406 if (fgets (s, sizeof(buf), f) == NULL)
407 break;
408
409 int len = strlen (s);
410 if (len < 3)
411 continue;
412 if (s[len - 1] == '\n')
413 {
414 s[len - 1] = '\0';
415 len--;
416 }
417
418 char *v = strchr (s, '=');
419 if (v == NULL || strlen (v) < 2)
420 continue;
421
422 /* Split var and value. */
423 *v = '\0';
424 v++;
425
426 /* Remove optional quotes around value string. */
427 if (*v == '"' || *v == '\'')
428 {
429 v++;
430 s[len - 1] = '\0';
431 }
432 if (strcmp (s, "ID") == 0)
433 id = strdup (v);
434 if (strcmp (s, "VERSION_ID") == 0)
435 version = strdup (v);
436 }
437 fclose (f);
438 }
439
440 char *ua = NULL;
441 rc = asprintf(& ua, "User-Agent: %s/%s,%s,%s/%s",
442 PACKAGE_NAME, PACKAGE_VERSION,
443 utspart ?: "",
444 id ?: "",
445 version ?: "");
446 if (rc < 0)
447 ua = NULL;
448
449 if (ua)
450 (void) debuginfod_add_http_header (client, ua);
451
452 free (ua);
453 free (id);
454 free (version);
455 free (utspart);
456 }
457
458 /* Add HTTP headers found in the given file, one per line. Blank lines or invalid
459 * headers are ignored.
460 */
461 static void
add_headers_from_file(debuginfod_client * client,const char * filename)462 add_headers_from_file(debuginfod_client *client, const char* filename)
463 {
464 int vds = client->verbose_fd;
465 FILE *f = fopen (filename, "r");
466 if (f == NULL)
467 {
468 if (vds >= 0)
469 dprintf(vds, "header file %s: %s\n", filename, strerror(errno));
470 return;
471 }
472
473 while (1)
474 {
475 char buf[8192];
476 char *s = &buf[0];
477 if (feof(f))
478 break;
479 if (fgets (s, sizeof(buf), f) == NULL)
480 break;
481 for (char *c = s; *c != '\0'; ++c)
482 if (!isspace(*c))
483 goto nonempty;
484 continue;
485 nonempty:
486 ;
487 size_t last = strlen(s)-1;
488 if (s[last] == '\n')
489 s[last] = '\0';
490 int rc = debuginfod_add_http_header(client, s);
491 if (rc < 0 && vds >= 0)
492 dprintf(vds, "skipping bad header: %s\n", strerror(-rc));
493 }
494 fclose (f);
495 }
496
497
498 #define xalloc_str(p, fmt, args...) \
499 do \
500 { \
501 if (asprintf (&p, fmt, args) < 0) \
502 { \
503 p = NULL; \
504 rc = -ENOMEM; \
505 goto out; \
506 } \
507 } while (0)
508
509
510 /* Offer a basic form of progress tracing */
511 static int
default_progressfn(debuginfod_client * c,long a,long b)512 default_progressfn (debuginfod_client *c, long a, long b)
513 {
514 const char* url = debuginfod_get_url (c);
515 int len = 0;
516
517 /* We prefer to print the host part of the URL to keep the
518 message short. */
519 if (url != NULL)
520 {
521 const char* buildid = strstr(url, "buildid/");
522 if (buildid != NULL)
523 len = (buildid - url);
524 else
525 len = strlen(url);
526 }
527
528 if (b == 0 || url==NULL) /* early stage */
529 dprintf(STDERR_FILENO,
530 "\rDownloading %c", "-/|\\"[a % 4]);
531 else if (b < 0) /* download in progress but unknown total length */
532 dprintf(STDERR_FILENO,
533 "\rDownloading from %.*s %ld",
534 len, url, a);
535 else /* download in progress, and known total length */
536 dprintf(STDERR_FILENO,
537 "\rDownloading from %.*s %ld/%ld",
538 len, url, a, b);
539 c->default_progressfn_printed_p = 1;
540
541 return 0;
542 }
543
544 /* This is a callback function that receives http response headers in buffer for use
545 * in this program. https://curl.se/libcurl/c/CURLOPT_HEADERFUNCTION.html is the
546 * online documentation.
547 */
548 static size_t
header_callback(char * buffer,size_t size,size_t numitems,void * userdata)549 header_callback (char * buffer, size_t size, size_t numitems, void * userdata)
550 {
551 struct handle_data *data = (struct handle_data *) userdata;
552 if (size != 1)
553 return 0;
554 if (data->client && data->client->verbose_fd >= 0)
555 dprintf (data->client->verbose_fd, "header %.*s", (int)numitems, buffer);
556 // Some basic checks to ensure the headers received are of the expected format
557 if (strncasecmp(buffer, "X-DEBUGINFOD", 11)
558 || buffer[numitems-2] != '\r'
559 || buffer[numitems-1] != '\n'
560 || (buffer == strstr(buffer, ":")) ){
561 return numitems;
562 }
563 /* Temporary buffer for realloc */
564 char *temp = NULL;
565 if (data->response_data == NULL)
566 {
567 temp = malloc(numitems);
568 if (temp == NULL)
569 return 0;
570 }
571 else
572 {
573 temp = realloc(data->response_data, data->response_data_size + numitems);
574 if (temp == NULL)
575 return 0;
576 }
577
578 memcpy(temp + data->response_data_size, buffer, numitems-1);
579 data->response_data = temp;
580 data->response_data_size += numitems-1;
581 data->response_data[data->response_data_size-1] = '\n';
582 data->response_data[data->response_data_size] = '\0';
583 return numitems;
584 }
585
586 /* Copy SRC to DEST, s,/,#,g */
587
588 static void
path_escape(const char * src,char * dest)589 path_escape (const char *src, char *dest)
590 {
591 unsigned q = 0;
592
593 for (unsigned fi=0; q < PATH_MAX-2; fi++) /* -2, escape is 2 chars. */
594 switch (src[fi])
595 {
596 case '\0':
597 dest[q] = '\0';
598 return;
599 case '/': /* escape / to prevent dir escape */
600 dest[q++]='#';
601 dest[q++]='#';
602 break;
603 case '#': /* escape # to prevent /# vs #/ collisions */
604 dest[q++]='#';
605 dest[q++]='_';
606 break;
607 default:
608 dest[q++]=src[fi];
609 }
610
611 dest[q] = '\0';
612 }
613
614 /* Attempt to read an ELF/DWARF section with name SECTION from FD and write
615 it to a separate file in the debuginfod cache. If successful the absolute
616 path of the separate file containing SECTION will be stored in USR_PATH.
617 FD_PATH is the absolute path for FD.
618
619 If the section cannot be extracted, then return a negative error code.
620 -ENOENT indicates that the parent file was able to be read but the
621 section name was not found. -EEXIST indicates that the section was
622 found but had type SHT_NOBITS. */
623
624 int
extract_section(int fd,const char * section,char * fd_path,char ** usr_path)625 extract_section (int fd, const char *section, char *fd_path, char **usr_path)
626 {
627 elf_version (EV_CURRENT);
628
629 Elf *elf = elf_begin (fd, ELF_C_READ_MMAP_PRIVATE, NULL);
630 if (elf == NULL)
631 return -EIO;
632
633 size_t shstrndx;
634 int rc = elf_getshdrstrndx (elf, &shstrndx);
635 if (rc < 0)
636 {
637 rc = -EIO;
638 goto out;
639 }
640
641 int sec_fd = -1;
642 char *escaped_name = NULL;
643 char *sec_path_tmp = NULL;
644 Elf_Scn *scn = NULL;
645
646 /* Try to find the target section and copy the contents into a
647 separate file. */
648 while (true)
649 {
650 scn = elf_nextscn (elf, scn);
651 if (scn == NULL)
652 {
653 rc = -ENOENT;
654 goto out;
655 }
656 GElf_Shdr shdr_storage;
657 GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_storage);
658 if (shdr == NULL)
659 {
660 rc = -EIO;
661 goto out;
662 }
663
664 const char *scn_name = elf_strptr (elf, shstrndx, shdr->sh_name);
665 if (scn_name == NULL)
666 {
667 rc = -EIO;
668 goto out;
669 }
670 if (strcmp (scn_name, section) == 0)
671 {
672 /* We found the desired section. */
673 if (shdr->sh_type == SHT_NOBITS)
674 {
675 rc = -EEXIST;
676 goto out;
677 }
678
679 Elf_Data *data = NULL;
680 data = elf_rawdata (scn, NULL);
681 if (data == NULL)
682 {
683 rc = -EIO;
684 goto out;
685 }
686
687 if (data->d_buf == NULL)
688 {
689 rc = -EIO;
690 goto out;
691 }
692
693 /* Compute the absolute filename we'll write the section to.
694 Replace the last component of FD_PATH with the path-escaped
695 section filename. */
696 int i = strlen (fd_path);
697 while (i >= 0)
698 {
699 if (fd_path[i] == '/')
700 {
701 fd_path[i] = '\0';
702 break;
703 }
704 --i;
705 }
706
707 escaped_name = malloc (strlen (section) * 2 + 1);
708 if (escaped_name == NULL)
709 {
710 rc = -ENOMEM;
711 goto out;
712 }
713 path_escape (section, escaped_name);
714
715 rc = asprintf (&sec_path_tmp, "%s/section-%s.XXXXXX",
716 fd_path, escaped_name);
717 if (rc == -1)
718 {
719 rc = -ENOMEM;
720 goto out1;
721 }
722
723 sec_fd = mkstemp (sec_path_tmp);
724 if (sec_fd < 0)
725 {
726 rc = -EIO;
727 goto out2;
728 }
729
730 ssize_t res = write_retry (sec_fd, data->d_buf, data->d_size);
731 if (res < 0 || (size_t) res != data->d_size)
732 {
733 rc = -EIO;
734 goto out3;
735 }
736
737 /* Success. Rename tmp file and update USR_PATH. */
738 char *sec_path;
739 if (asprintf (&sec_path, "%s/section-%s", fd_path, section) == -1)
740 {
741 rc = -ENOMEM;
742 goto out3;
743 }
744
745 rc = rename (sec_path_tmp, sec_path);
746 if (rc < 0)
747 {
748 free (sec_path);
749 rc = -EIO;
750 goto out3;
751 }
752
753 if (usr_path != NULL)
754 *usr_path = sec_path;
755 else
756 free (sec_path);
757 rc = sec_fd;
758 goto out2;
759 }
760 }
761
762 out3:
763 close (sec_fd);
764 unlink (sec_path_tmp);
765
766 out2:
767 free (sec_path_tmp);
768
769 out1:
770 free (escaped_name);
771
772 out:
773 elf_end (elf);
774 return rc;
775 }
776
777 /* Search TARGET_CACHE_DIR for a debuginfo or executable file containing
778 an ELF/DWARF section with name SCN_NAME. If found, extract the section
779 to a separate file in TARGET_CACHE_DIR and return a file descriptor
780 for the section file. The path for this file will be stored in USR_PATH.
781 Return a negative errno if unsuccessful. */
782
783 static int
cache_find_section(const char * scn_name,const char * target_cache_dir,char ** usr_path)784 cache_find_section (const char *scn_name, const char *target_cache_dir,
785 char **usr_path)
786 {
787 int fd;
788 int rc = -EEXIST;
789 char parent_path[PATH_MAX];
790
791 /* Check the debuginfo first. */
792 snprintf (parent_path, PATH_MAX, "%s/debuginfo", target_cache_dir);
793 fd = open (parent_path, O_RDONLY);
794 if (fd >= 0)
795 {
796 rc = extract_section (fd, scn_name, parent_path, usr_path);
797 close (fd);
798 }
799
800 /* If the debuginfo file couldn't be found or the section type was
801 SHT_NOBITS, check the executable. */
802 if (rc == -EEXIST)
803 {
804 snprintf (parent_path, PATH_MAX, "%s/executable", target_cache_dir);
805 fd = open (parent_path, O_RDONLY);
806
807 if (fd >= 0)
808 {
809 rc = extract_section (fd, scn_name, parent_path, usr_path);
810 close (fd);
811 }
812 }
813
814 return rc;
815 }
816
817 /* Query each of the server URLs found in $DEBUGINFOD_URLS for the file
818 with the specified build-id and type (debuginfo, executable, source or
819 section). If type is source, then type_arg should be a filename. If
820 type is section, then type_arg should be the name of an ELF/DWARF
821 section. Otherwise type_arg may be NULL. Return a file descriptor
822 for the target if successful, otherwise return an error code.
823 */
824 static int
debuginfod_query_server(debuginfod_client * c,const unsigned char * build_id,int build_id_len,const char * type,const char * type_arg,char ** path)825 debuginfod_query_server (debuginfod_client *c,
826 const unsigned char *build_id,
827 int build_id_len,
828 const char *type,
829 const char *type_arg,
830 char **path)
831 {
832 char *server_urls;
833 char *urls_envvar;
834 const char *section = NULL;
835 const char *filename = NULL;
836 char *cache_path = NULL;
837 char *maxage_path = NULL;
838 char *interval_path = NULL;
839 char *cache_miss_path = NULL;
840 char *target_cache_dir = NULL;
841 char *target_cache_path = NULL;
842 char *target_cache_tmppath = NULL;
843 char suffix[PATH_MAX + 1]; /* +1 for zero terminator. */
844 char build_id_bytes[MAX_BUILD_ID_BYTES * 2 + 1];
845 int vfd = c->verbose_fd;
846 int rc;
847
848 c->progressfn_cancel = false;
849
850 if (strcmp (type, "source") == 0)
851 filename = type_arg;
852 else if (strcmp (type, "section") == 0)
853 {
854 section = type_arg;
855 if (section == NULL)
856 return -EINVAL;
857 }
858
859 if (vfd >= 0)
860 {
861 dprintf (vfd, "debuginfod_find_%s ", type);
862 if (build_id_len == 0) /* expect clean hexadecimal */
863 dprintf (vfd, "%s", (const char *) build_id);
864 else
865 for (int i = 0; i < build_id_len; i++)
866 dprintf (vfd, "%02x", build_id[i]);
867 if (filename != NULL)
868 dprintf (vfd, " %s\n", filename);
869 dprintf (vfd, "\n");
870 }
871
872 /* Is there any server we can query? If not, don't do any work,
873 just return with ENOSYS. Don't even access the cache. */
874 urls_envvar = getenv(DEBUGINFOD_URLS_ENV_VAR);
875 if (vfd >= 0)
876 dprintf (vfd, "server urls \"%s\"\n",
877 urls_envvar != NULL ? urls_envvar : "");
878 if (urls_envvar == NULL || urls_envvar[0] == '\0')
879 {
880 rc = -ENOSYS;
881 goto out;
882 }
883
884 /* Clear the obsolete data from a previous _find operation. */
885 free (c->url);
886 c->url = NULL;
887 free (c->winning_headers);
888 c->winning_headers = NULL;
889
890 /* PR 27982: Add max size if DEBUGINFOD_MAXSIZE is set. */
891 long maxsize = 0;
892 const char *maxsize_envvar;
893 maxsize_envvar = getenv(DEBUGINFOD_MAXSIZE_ENV_VAR);
894 if (maxsize_envvar != NULL)
895 maxsize = atol (maxsize_envvar);
896
897 /* PR 27982: Add max time if DEBUGINFOD_MAXTIME is set. */
898 long maxtime = 0;
899 const char *maxtime_envvar;
900 maxtime_envvar = getenv(DEBUGINFOD_MAXTIME_ENV_VAR);
901 if (maxtime_envvar != NULL)
902 maxtime = atol (maxtime_envvar);
903 if (maxtime && vfd >= 0)
904 dprintf(vfd, "using max time %lds\n", maxtime);
905
906 const char *headers_file_envvar;
907 headers_file_envvar = getenv(DEBUGINFOD_HEADERS_FILE_ENV_VAR);
908 if (headers_file_envvar != NULL)
909 add_headers_from_file(c, headers_file_envvar);
910
911 /* Maxsize is valid*/
912 if (maxsize > 0)
913 {
914 if (vfd)
915 dprintf (vfd, "using max size %ldB\n", maxsize);
916 char *size_header = NULL;
917 rc = asprintf (&size_header, "X-DEBUGINFOD-MAXSIZE: %ld", maxsize);
918 if (rc < 0)
919 {
920 rc = -ENOMEM;
921 goto out;
922 }
923 rc = debuginfod_add_http_header(c, size_header);
924 free(size_header);
925 if (rc < 0)
926 goto out;
927 }
928 add_default_headers(c);
929
930 /* Copy lowercase hex representation of build_id into buf. */
931 if (vfd >= 0)
932 dprintf (vfd, "checking build-id\n");
933 if ((build_id_len >= MAX_BUILD_ID_BYTES) ||
934 (build_id_len == 0 &&
935 strlen ((const char *) build_id) > MAX_BUILD_ID_BYTES*2))
936 {
937 rc = -EINVAL;
938 goto out;
939 }
940
941 if (build_id_len == 0) /* expect clean hexadecimal */
942 strcpy (build_id_bytes, (const char *) build_id);
943 else
944 for (int i = 0; i < build_id_len; i++)
945 sprintf(build_id_bytes + (i * 2), "%02x", build_id[i]);
946
947 if (filename != NULL)
948 {
949 if (vfd >= 0)
950 dprintf (vfd, "checking filename\n");
951 if (filename[0] != '/') // must start with /
952 {
953 rc = -EINVAL;
954 goto out;
955 }
956
957 path_escape (filename, suffix);
958 /* If the DWARF filenames are super long, this could exceed
959 PATH_MAX and truncate/collide. Oh well, that'll teach
960 them! */
961 }
962 else if (section != NULL)
963 path_escape (section, suffix);
964 else
965 suffix[0] = '\0';
966
967 if (suffix[0] != '\0' && vfd >= 0)
968 dprintf (vfd, "suffix %s\n", suffix);
969
970 /* set paths needed to perform the query
971
972 example format
973 cache_path: $HOME/.cache
974 target_cache_dir: $HOME/.cache/0123abcd
975 target_cache_path: $HOME/.cache/0123abcd/debuginfo
976 target_cache_path: $HOME/.cache/0123abcd/source#PATH#TO#SOURCE ?
977
978 $XDG_CACHE_HOME takes priority over $HOME/.cache.
979 $DEBUGINFOD_CACHE_PATH takes priority over $HOME/.cache and $XDG_CACHE_HOME.
980 */
981
982 /* Determine location of the cache. The path specified by the debuginfod
983 cache environment variable takes priority. */
984 char *cache_var = getenv(DEBUGINFOD_CACHE_PATH_ENV_VAR);
985 if (cache_var != NULL && strlen (cache_var) > 0)
986 xalloc_str (cache_path, "%s", cache_var);
987 else
988 {
989 /* If a cache already exists in $HOME ('/' if $HOME isn't set), then use
990 that. Otherwise use the XDG cache directory naming format. */
991 xalloc_str (cache_path, "%s/%s", getenv ("HOME") ?: "/", cache_default_name);
992
993 struct stat st;
994 if (stat (cache_path, &st) < 0)
995 {
996 char cachedir[PATH_MAX];
997 char *xdg = getenv ("XDG_CACHE_HOME");
998
999 if (xdg != NULL && strlen (xdg) > 0)
1000 snprintf (cachedir, PATH_MAX, "%s", xdg);
1001 else
1002 snprintf (cachedir, PATH_MAX, "%s/.cache", getenv ("HOME") ?: "/");
1003
1004 /* Create XDG cache directory if it doesn't exist. */
1005 if (stat (cachedir, &st) == 0)
1006 {
1007 if (! S_ISDIR (st.st_mode))
1008 {
1009 rc = -EEXIST;
1010 goto out;
1011 }
1012 }
1013 else
1014 {
1015 rc = mkdir (cachedir, 0700);
1016
1017 /* Also check for EEXIST and S_ISDIR in case another client just
1018 happened to create the cache. */
1019 if (rc < 0
1020 && (errno != EEXIST
1021 || stat (cachedir, &st) != 0
1022 || ! S_ISDIR (st.st_mode)))
1023 {
1024 rc = -errno;
1025 goto out;
1026 }
1027 }
1028
1029 free (cache_path);
1030 xalloc_str (cache_path, "%s/%s", cachedir, cache_xdg_name);
1031 }
1032 }
1033
1034 xalloc_str (target_cache_dir, "%s/%s", cache_path, build_id_bytes);
1035 if (section != NULL)
1036 xalloc_str (target_cache_path, "%s/%s-%s", target_cache_dir, type, suffix);
1037 else
1038 xalloc_str (target_cache_path, "%s/%s%s", target_cache_dir, type, suffix);
1039 xalloc_str (target_cache_tmppath, "%s.XXXXXX", target_cache_path);
1040
1041 /* XXX combine these */
1042 xalloc_str (interval_path, "%s/%s", cache_path, cache_clean_interval_filename);
1043 xalloc_str (cache_miss_path, "%s/%s", cache_path, cache_miss_filename);
1044 xalloc_str (maxage_path, "%s/%s", cache_path, cache_max_unused_age_filename);
1045
1046 if (vfd >= 0)
1047 dprintf (vfd, "checking cache dir %s\n", cache_path);
1048
1049 /* Make sure cache dir exists. debuginfo_clean_cache will then make
1050 sure the interval, cache_miss and maxage files exist. */
1051 if (mkdir (cache_path, ACCESSPERMS) != 0
1052 && errno != EEXIST)
1053 {
1054 rc = -errno;
1055 goto out;
1056 }
1057
1058 rc = debuginfod_clean_cache(c, cache_path, interval_path, maxage_path);
1059 if (rc != 0)
1060 goto out;
1061
1062 /* Check if the target is already in the cache. */
1063 int fd = open(target_cache_path, O_RDONLY);
1064 if (fd >= 0)
1065 {
1066 struct stat st;
1067 if (fstat(fd, &st) != 0)
1068 {
1069 rc = -errno;
1070 close (fd);
1071 goto out;
1072 }
1073
1074 /* If the file is non-empty, then we are done. */
1075 if (st.st_size > 0)
1076 {
1077 if (path != NULL)
1078 {
1079 *path = strdup(target_cache_path);
1080 if (*path == NULL)
1081 {
1082 rc = -errno;
1083 close (fd);
1084 goto out;
1085 }
1086 }
1087 /* Success!!!! */
1088 rc = fd;
1089 goto out;
1090 }
1091 else
1092 {
1093 /* The file is empty. Attempt to download only if enough time
1094 has passed since the last attempt. */
1095 time_t cache_miss;
1096 time_t target_mtime = st.st_mtime;
1097
1098 close(fd); /* no need to hold onto the negative-hit file descriptor */
1099
1100 rc = debuginfod_config_cache(cache_miss_path,
1101 cache_miss_default_s, &st);
1102 if (rc < 0)
1103 goto out;
1104
1105 cache_miss = (time_t)rc;
1106 if (time(NULL) - target_mtime <= cache_miss)
1107 {
1108 rc = -ENOENT;
1109 goto out;
1110 }
1111 else
1112 /* TOCTOU non-problem: if another task races, puts a working
1113 download or an empty file in its place, unlinking here just
1114 means WE will try to download again as uncached. */
1115 unlink(target_cache_path);
1116 }
1117 }
1118 else if (errno == EACCES)
1119 /* Ensure old 000-permission files are not lingering in the cache. */
1120 unlink(target_cache_path);
1121
1122 if (section != NULL)
1123 {
1124 /* Try to extract the section from a cached file before querying
1125 any servers. */
1126 rc = cache_find_section (section, target_cache_dir, path);
1127
1128 /* If the section was found or confirmed to not exist, then we
1129 are done. */
1130 if (rc >= 0 || rc == -ENOENT)
1131 goto out;
1132 }
1133
1134 long timeout = default_timeout;
1135 const char* timeout_envvar = getenv(DEBUGINFOD_TIMEOUT_ENV_VAR);
1136 if (timeout_envvar != NULL)
1137 timeout = atoi (timeout_envvar);
1138
1139 if (vfd >= 0)
1140 dprintf (vfd, "using timeout %ld\n", timeout);
1141
1142 /* make a copy of the envvar so it can be safely modified. */
1143 server_urls = strdup(urls_envvar);
1144 if (server_urls == NULL)
1145 {
1146 rc = -ENOMEM;
1147 goto out;
1148 }
1149 /* thereafter, goto out0 on error*/
1150
1151 /* Because of a race with cache cleanup / rmdir, try to mkdir/mkstemp up to twice. */
1152 for(int i=0; i<2; i++) {
1153 /* (re)create target directory in cache */
1154 (void) mkdir(target_cache_dir, 0700); /* files will be 0400 later */
1155
1156 /* NB: write to a temporary file first, to avoid race condition of
1157 multiple clients checking the cache, while a partially-written or empty
1158 file is in there, being written from libcurl. */
1159 fd = mkstemp (target_cache_tmppath);
1160 if (fd >= 0) break;
1161 }
1162 if (fd < 0) /* Still failed after two iterations. */
1163 {
1164 rc = -errno;
1165 goto out0;
1166 }
1167
1168 /* Initialize the memory to zero */
1169 char *strtok_saveptr;
1170 char **server_url_list = NULL;
1171 char *server_url = strtok_r(server_urls, url_delim, &strtok_saveptr);
1172 /* Count number of URLs. */
1173 int num_urls = 0;
1174
1175 while (server_url != NULL)
1176 {
1177 /* PR 27983: If the url is already set to be used use, skip it */
1178 char *slashbuildid;
1179 if (strlen(server_url) > 1 && server_url[strlen(server_url)-1] == '/')
1180 slashbuildid = "buildid";
1181 else
1182 slashbuildid = "/buildid";
1183
1184 char *tmp_url;
1185 if (asprintf(&tmp_url, "%s%s", server_url, slashbuildid) == -1)
1186 {
1187 rc = -ENOMEM;
1188 goto out1;
1189 }
1190 int url_index;
1191 for (url_index = 0; url_index < num_urls; ++url_index)
1192 {
1193 if(strcmp(tmp_url, server_url_list[url_index]) == 0)
1194 {
1195 url_index = -1;
1196 break;
1197 }
1198 }
1199 if (url_index == -1)
1200 {
1201 if (vfd >= 0)
1202 dprintf(vfd, "duplicate url: %s, skipping\n", tmp_url);
1203 free(tmp_url);
1204 }
1205 else
1206 {
1207 num_urls++;
1208 char ** realloc_ptr;
1209 realloc_ptr = reallocarray(server_url_list, num_urls,
1210 sizeof(char*));
1211 if (realloc_ptr == NULL)
1212 {
1213 free (tmp_url);
1214 rc = -ENOMEM;
1215 goto out1;
1216 }
1217 server_url_list = realloc_ptr;
1218 server_url_list[num_urls-1] = tmp_url;
1219 }
1220 server_url = strtok_r(NULL, url_delim, &strtok_saveptr);
1221 }
1222
1223 int retry_limit = default_retry_limit;
1224 const char* retry_limit_envvar = getenv(DEBUGINFOD_RETRY_LIMIT_ENV_VAR);
1225 if (retry_limit_envvar != NULL)
1226 retry_limit = atoi (retry_limit_envvar);
1227
1228 CURLM *curlm = c->server_mhandle;
1229 assert (curlm != NULL);
1230
1231 /* Tracks which handle should write to fd. Set to the first
1232 handle that is ready to write the target file to the cache. */
1233 CURL *target_handle = NULL;
1234 struct handle_data *data = malloc(sizeof(struct handle_data) * num_urls);
1235 if (data == NULL)
1236 {
1237 rc = -ENOMEM;
1238 goto out1;
1239 }
1240
1241 /* thereafter, goto out2 on error. */
1242
1243 /*The beginning of goto block query_in_parallel.*/
1244 query_in_parallel:
1245 rc = -ENOENT; /* Reset rc to default.*/
1246
1247 /* Initialize handle_data with default values. */
1248 for (int i = 0; i < num_urls; i++)
1249 {
1250 data[i].handle = NULL;
1251 data[i].fd = -1;
1252 data[i].errbuf[0] = '\0';
1253 }
1254
1255 char *escaped_string = NULL;
1256 size_t escaped_strlen = 0;
1257 if (filename)
1258 {
1259 escaped_string = curl_easy_escape(&target_handle, filename+1, 0);
1260 if (!escaped_string)
1261 {
1262 rc = -ENOMEM;
1263 goto out2;
1264 }
1265 char *loc = escaped_string;
1266 escaped_strlen = strlen(escaped_string);
1267 while ((loc = strstr(loc, "%2F")))
1268 {
1269 loc[0] = '/';
1270 //pull the string back after replacement
1271 // loc-escaped_string finds the distance from the origin to the new location
1272 // - 2 accounts for the 2F which remain and don't need to be measured.
1273 // The two above subtracted from escaped_strlen yields the remaining characters
1274 // in the string which we want to pull back
1275 memmove(loc+1, loc+3,escaped_strlen - (loc-escaped_string) - 2);
1276 //Because the 2F was overwritten in the memmove (as desired) escaped_strlen is
1277 // now two shorter.
1278 escaped_strlen -= 2;
1279 }
1280 }
1281 /* Initialize each handle. */
1282 for (int i = 0; i < num_urls; i++)
1283 {
1284 if ((server_url = server_url_list[i]) == NULL)
1285 break;
1286 if (vfd >= 0)
1287 dprintf (vfd, "init server %d %s\n", i, server_url);
1288
1289 data[i].fd = fd;
1290 data[i].target_handle = &target_handle;
1291 data[i].handle = curl_easy_init();
1292 if (data[i].handle == NULL)
1293 {
1294 if (filename) curl_free (escaped_string);
1295 rc = -ENETUNREACH;
1296 goto out2;
1297 }
1298 data[i].client = c;
1299
1300 if (filename) /* must start with / */
1301 {
1302 /* PR28034 escape characters in completed url to %hh format. */
1303 snprintf(data[i].url, PATH_MAX, "%s/%s/%s/%s", server_url,
1304 build_id_bytes, type, escaped_string);
1305 }
1306 else if (section)
1307 snprintf(data[i].url, PATH_MAX, "%s/%s/%s/%s", server_url,
1308 build_id_bytes, type, section);
1309 else
1310 snprintf(data[i].url, PATH_MAX, "%s/%s/%s", server_url, build_id_bytes, type);
1311 if (vfd >= 0)
1312 dprintf (vfd, "url %d %s\n", i, data[i].url);
1313
1314 /* Some boilerplate for checking curl_easy_setopt. */
1315 #define curl_easy_setopt_ck(H,O,P) do { \
1316 CURLcode curl_res = curl_easy_setopt (H,O,P); \
1317 if (curl_res != CURLE_OK) \
1318 { \
1319 if (vfd >= 0) \
1320 dprintf (vfd, \
1321 "Bad curl_easy_setopt: %s\n", \
1322 curl_easy_strerror(curl_res)); \
1323 rc = -EINVAL; \
1324 goto out2; \
1325 } \
1326 } while (0)
1327
1328 /* Only allow http:// + https:// + file:// so we aren't being
1329 redirected to some unsupported protocol. */
1330 curl_easy_setopt_ck(data[i].handle, CURLOPT_PROTOCOLS,
1331 (CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FILE));
1332 curl_easy_setopt_ck(data[i].handle, CURLOPT_URL, data[i].url);
1333 if (vfd >= 0)
1334 curl_easy_setopt_ck(data[i].handle, CURLOPT_ERRORBUFFER,
1335 data[i].errbuf);
1336 curl_easy_setopt_ck(data[i].handle,
1337 CURLOPT_WRITEFUNCTION,
1338 debuginfod_write_callback);
1339 curl_easy_setopt_ck(data[i].handle, CURLOPT_WRITEDATA, (void*)&data[i]);
1340 if (timeout > 0)
1341 {
1342 /* Make sure there is at least some progress,
1343 try to get at least 100K per timeout seconds. */
1344 curl_easy_setopt_ck (data[i].handle, CURLOPT_LOW_SPEED_TIME,
1345 timeout);
1346 curl_easy_setopt_ck (data[i].handle, CURLOPT_LOW_SPEED_LIMIT,
1347 100 * 1024L);
1348 }
1349 data[i].response_data = NULL;
1350 data[i].response_data_size = 0;
1351 curl_easy_setopt_ck(data[i].handle, CURLOPT_FILETIME, (long) 1);
1352 curl_easy_setopt_ck(data[i].handle, CURLOPT_FOLLOWLOCATION, (long) 1);
1353 curl_easy_setopt_ck(data[i].handle, CURLOPT_FAILONERROR, (long) 1);
1354 curl_easy_setopt_ck(data[i].handle, CURLOPT_NOSIGNAL, (long) 1);
1355 curl_easy_setopt_ck(data[i].handle, CURLOPT_HEADERFUNCTION,
1356 header_callback);
1357 curl_easy_setopt_ck(data[i].handle, CURLOPT_HEADERDATA,
1358 (void *) &(data[i]));
1359 #if LIBCURL_VERSION_NUM >= 0x072a00 /* 7.42.0 */
1360 curl_easy_setopt_ck(data[i].handle, CURLOPT_PATH_AS_IS, (long) 1);
1361 #else
1362 /* On old curl; no big deal, canonicalization here is almost the
1363 same, except perhaps for ? # type decorations at the tail. */
1364 #endif
1365 curl_easy_setopt_ck(data[i].handle, CURLOPT_AUTOREFERER, (long) 1);
1366 curl_easy_setopt_ck(data[i].handle, CURLOPT_ACCEPT_ENCODING, "");
1367 curl_easy_setopt_ck(data[i].handle, CURLOPT_HTTPHEADER, c->headers);
1368
1369 curl_multi_add_handle(curlm, data[i].handle);
1370 }
1371
1372 if (filename) curl_free(escaped_string);
1373 /* Query servers in parallel. */
1374 if (vfd >= 0)
1375 dprintf (vfd, "query %d urls in parallel\n", num_urls);
1376 int still_running;
1377 long loops = 0;
1378 int committed_to = -1;
1379 bool verbose_reported = false;
1380 struct timespec start_time, cur_time;
1381
1382 free (c->winning_headers);
1383 c->winning_headers = NULL;
1384 if ( maxtime > 0 && clock_gettime(CLOCK_MONOTONIC_RAW, &start_time) == -1)
1385 {
1386 rc = -errno;
1387 goto out2;
1388 }
1389 long delta = 0;
1390 do
1391 {
1392 /* Check to see how long querying is taking. */
1393 if (maxtime > 0)
1394 {
1395 if (clock_gettime(CLOCK_MONOTONIC_RAW, &cur_time) == -1)
1396 {
1397 rc = -errno;
1398 goto out2;
1399 }
1400 delta = cur_time.tv_sec - start_time.tv_sec;
1401 if ( delta > maxtime)
1402 {
1403 dprintf(vfd, "Timeout with max time=%lds and transfer time=%lds\n", maxtime, delta );
1404 rc = -ETIME;
1405 goto out2;
1406 }
1407 }
1408 /* Wait 1 second, the minimum DEBUGINFOD_TIMEOUT. */
1409 curl_multi_wait(curlm, NULL, 0, 1000, NULL);
1410 CURLMcode curlm_res = curl_multi_perform(curlm, &still_running);
1411
1412 /* If the target file has been found, abort the other queries. */
1413 if (target_handle != NULL)
1414 {
1415 for (int i = 0; i < num_urls; i++)
1416 if (data[i].handle != target_handle)
1417 curl_multi_remove_handle(curlm, data[i].handle);
1418 else
1419 {
1420 committed_to = i;
1421 if (c->winning_headers == NULL)
1422 {
1423 c->winning_headers = data[committed_to].response_data;
1424 data[committed_to].response_data = NULL;
1425 data[committed_to].response_data_size = 0;
1426 }
1427
1428 }
1429 }
1430
1431 if (vfd >= 0 && !verbose_reported && committed_to >= 0)
1432 {
1433 bool pnl = (c->default_progressfn_printed_p && vfd == STDERR_FILENO);
1434 dprintf (vfd, "%scommitted to url %d\n", pnl ? "\n" : "",
1435 committed_to);
1436 if (pnl)
1437 c->default_progressfn_printed_p = 0;
1438 verbose_reported = true;
1439 }
1440
1441 if (curlm_res != CURLM_OK)
1442 {
1443 switch (curlm_res)
1444 {
1445 case CURLM_CALL_MULTI_PERFORM: continue;
1446 case CURLM_OUT_OF_MEMORY: rc = -ENOMEM; break;
1447 default: rc = -ENETUNREACH; break;
1448 }
1449 goto out2;
1450 }
1451
1452 long dl_size = 0;
1453 if (target_handle && (c->progressfn || maxsize > 0))
1454 {
1455 /* Get size of file being downloaded. NB: If going through
1456 deflate-compressing proxies, this number is likely to be
1457 unavailable, so -1 may show. */
1458 CURLcode curl_res;
1459 #ifdef CURLINFO_CONTENT_LENGTH_DOWNLOAD_T
1460 curl_off_t cl;
1461 curl_res = curl_easy_getinfo(target_handle,
1462 CURLINFO_CONTENT_LENGTH_DOWNLOAD_T,
1463 &cl);
1464 if (curl_res == CURLE_OK && cl >= 0)
1465 dl_size = (cl > LONG_MAX ? LONG_MAX : (long)cl);
1466 #else
1467 double cl;
1468 curl_res = curl_easy_getinfo(target_handle,
1469 CURLINFO_CONTENT_LENGTH_DOWNLOAD,
1470 &cl);
1471 if (curl_res == CURLE_OK)
1472 dl_size = (cl >= (double)(LONG_MAX+1UL) ? LONG_MAX : (long)cl);
1473 #endif
1474 /* If Content-Length is -1, try to get the size from
1475 X-Debuginfod-Size */
1476 if (dl_size == -1 && c->winning_headers != NULL)
1477 {
1478 long xdl;
1479 char *hdr = strcasestr(c->winning_headers, "x-debuginfod-size");
1480
1481 if (hdr != NULL
1482 && sscanf(hdr, "x-debuginfod-size: %ld", &xdl) == 1)
1483 dl_size = xdl;
1484 }
1485 }
1486
1487 if (c->progressfn) /* inform/check progress callback */
1488 {
1489 loops ++;
1490 long pa = loops; /* default param for progress callback */
1491 if (target_handle) /* we've committed to a server; report its download progress */
1492 {
1493 CURLcode curl_res;
1494 #ifdef CURLINFO_SIZE_DOWNLOAD_T
1495 curl_off_t dl;
1496 curl_res = curl_easy_getinfo(target_handle,
1497 CURLINFO_SIZE_DOWNLOAD_T,
1498 &dl);
1499 if (curl_res == 0 && dl >= 0)
1500 pa = (dl > LONG_MAX ? LONG_MAX : (long)dl);
1501 #else
1502 double dl;
1503 curl_res = curl_easy_getinfo(target_handle,
1504 CURLINFO_SIZE_DOWNLOAD,
1505 &dl);
1506 if (curl_res == 0)
1507 pa = (dl >= (double)(LONG_MAX+1UL) ? LONG_MAX : (long)dl);
1508 #endif
1509
1510 }
1511
1512 if ((*c->progressfn) (c, pa, dl_size))
1513 {
1514 c->progressfn_cancel = true;
1515 break;
1516 }
1517 }
1518
1519 /* Check to see if we are downloading something which exceeds maxsize, if set.*/
1520 if (target_handle && dl_size > maxsize && maxsize > 0)
1521 {
1522 if (vfd >=0)
1523 dprintf(vfd, "Content-Length too large.\n");
1524 rc = -EFBIG;
1525 goto out2;
1526 }
1527 } while (still_running);
1528
1529 /* Check whether a query was successful. If so, assign its handle
1530 to verified_handle. */
1531 int num_msg;
1532 rc = -ENOENT;
1533 CURL *verified_handle = NULL;
1534 do
1535 {
1536 CURLMsg *msg;
1537
1538 msg = curl_multi_info_read(curlm, &num_msg);
1539 if (msg != NULL && msg->msg == CURLMSG_DONE)
1540 {
1541 if (vfd >= 0)
1542 {
1543 bool pnl = (c->default_progressfn_printed_p
1544 && vfd == STDERR_FILENO);
1545 dprintf (vfd, "%sserver response %s\n", pnl ? "\n" : "",
1546 curl_easy_strerror (msg->data.result));
1547 if (pnl)
1548 c->default_progressfn_printed_p = 0;
1549 for (int i = 0; i < num_urls; i++)
1550 if (msg->easy_handle == data[i].handle)
1551 {
1552 if (strlen (data[i].errbuf) > 0)
1553 dprintf (vfd, "url %d %s\n", i, data[i].errbuf);
1554 break;
1555 }
1556 }
1557
1558 if (msg->data.result != CURLE_OK)
1559 {
1560 long resp_code;
1561 CURLcode ok0;
1562 /* Unsuccessful query, determine error code. */
1563 switch (msg->data.result)
1564 {
1565 case CURLE_COULDNT_RESOLVE_HOST: rc = -EHOSTUNREACH; break; // no NXDOMAIN
1566 case CURLE_URL_MALFORMAT: rc = -EINVAL; break;
1567 case CURLE_COULDNT_CONNECT: rc = -ECONNREFUSED; break;
1568 case CURLE_PEER_FAILED_VERIFICATION: rc = -ECONNREFUSED; break;
1569 case CURLE_REMOTE_ACCESS_DENIED: rc = -EACCES; break;
1570 case CURLE_WRITE_ERROR: rc = -EIO; break;
1571 case CURLE_OUT_OF_MEMORY: rc = -ENOMEM; break;
1572 case CURLE_TOO_MANY_REDIRECTS: rc = -EMLINK; break;
1573 case CURLE_SEND_ERROR: rc = -ECONNRESET; break;
1574 case CURLE_RECV_ERROR: rc = -ECONNRESET; break;
1575 case CURLE_OPERATION_TIMEDOUT: rc = -ETIME; break;
1576 case CURLE_HTTP_RETURNED_ERROR:
1577 ok0 = curl_easy_getinfo (msg->easy_handle,
1578 CURLINFO_RESPONSE_CODE,
1579 &resp_code);
1580 /* 406 signals that the requested file was too large */
1581 if ( ok0 == CURLE_OK && resp_code == 406)
1582 rc = -EFBIG;
1583 else if (section != NULL && resp_code == 503)
1584 rc = -EINVAL;
1585 else
1586 rc = -ENOENT;
1587 break;
1588 default: rc = -ENOENT; break;
1589 }
1590 }
1591 else
1592 {
1593 /* Query completed without an error. Confirm that the
1594 response code is 200 when using HTTP/HTTPS and 0 when
1595 using file:// and set verified_handle. */
1596
1597 if (msg->easy_handle != NULL)
1598 {
1599 char *effective_url = NULL;
1600 long resp_code = 500;
1601 CURLcode ok1 = curl_easy_getinfo (target_handle,
1602 CURLINFO_EFFECTIVE_URL,
1603 &effective_url);
1604 CURLcode ok2 = curl_easy_getinfo (target_handle,
1605 CURLINFO_RESPONSE_CODE,
1606 &resp_code);
1607 if(ok1 == CURLE_OK && ok2 == CURLE_OK && effective_url)
1608 {
1609 if (strncasecmp (effective_url, "HTTP", 4) == 0)
1610 if (resp_code == 200)
1611 {
1612 verified_handle = msg->easy_handle;
1613 break;
1614 }
1615 if (strncasecmp (effective_url, "FILE", 4) == 0)
1616 if (resp_code == 0)
1617 {
1618 verified_handle = msg->easy_handle;
1619 break;
1620 }
1621 }
1622 /* - libcurl since 7.52.0 version start to support
1623 CURLINFO_SCHEME;
1624 - before 7.61.0, effective_url would give us a
1625 url with upper case SCHEME added in the front;
1626 - effective_url between 7.61 and 7.69 can be lack
1627 of scheme if the original url doesn't include one;
1628 - since version 7.69 effective_url will be provide
1629 a scheme in lower case. */
1630 #if LIBCURL_VERSION_NUM >= 0x073d00 /* 7.61.0 */
1631 #if LIBCURL_VERSION_NUM <= 0x074500 /* 7.69.0 */
1632 char *scheme = NULL;
1633 CURLcode ok3 = curl_easy_getinfo (target_handle,
1634 CURLINFO_SCHEME,
1635 &scheme);
1636 if(ok3 == CURLE_OK && scheme)
1637 {
1638 if (startswith (scheme, "HTTP"))
1639 if (resp_code == 200)
1640 {
1641 verified_handle = msg->easy_handle;
1642 break;
1643 }
1644 }
1645 #endif
1646 #endif
1647 }
1648 }
1649 }
1650 } while (num_msg > 0);
1651
1652 /* Create an empty file named as $HOME/.cache if the query fails
1653 with ENOENT.*/
1654 if (rc == -ENOENT)
1655 {
1656 int efd = open (target_cache_path, O_CREAT|O_EXCL, DEFFILEMODE);
1657 if (efd >= 0)
1658 close(efd);
1659 }
1660 else if (rc == -EFBIG)
1661 goto out2;
1662
1663 /* If the verified_handle is NULL and rc != -ENOENT, the query fails with
1664 * an error code other than 404, then do several retry within the retry_limit.
1665 * Clean up all old handles and jump back to the beginning of query_in_parallel,
1666 * reinitialize handles and query again.*/
1667 if (verified_handle == NULL)
1668 {
1669 if (rc != -ENOENT && retry_limit-- > 0)
1670 {
1671 if (vfd >= 0)
1672 dprintf (vfd, "Retry failed query, %d attempt(s) remaining\n", retry_limit);
1673 /* remove all handles from multi */
1674 for (int i = 0; i < num_urls; i++)
1675 {
1676 curl_multi_remove_handle(curlm, data[i].handle); /* ok to repeat */
1677 curl_easy_cleanup (data[i].handle);
1678 free(data[i].response_data);
1679 }
1680 free(c->winning_headers);
1681 c->winning_headers = NULL;
1682 goto query_in_parallel;
1683 }
1684 else
1685 goto out2;
1686 }
1687
1688 if (vfd >= 0)
1689 {
1690 bool pnl = c->default_progressfn_printed_p && vfd == STDERR_FILENO;
1691 dprintf (vfd, "%sgot file from server\n", pnl ? "\n" : "");
1692 if (pnl)
1693 c->default_progressfn_printed_p = 0;
1694 }
1695
1696 /* we've got one!!!! */
1697 time_t mtime;
1698 #if defined(_TIME_BITS) && _TIME_BITS == 64
1699 CURLcode curl_res = curl_easy_getinfo(verified_handle, CURLINFO_FILETIME_T, (void*) &mtime);
1700 #else
1701 CURLcode curl_res = curl_easy_getinfo(verified_handle, CURLINFO_FILETIME, (void*) &mtime);
1702 #endif
1703 if (curl_res != CURLE_OK)
1704 mtime = time(NULL); /* fall back to current time */
1705
1706 struct timeval tvs[2];
1707 tvs[0].tv_sec = tvs[1].tv_sec = mtime;
1708 tvs[0].tv_usec = tvs[1].tv_usec = 0;
1709 (void) futimes (fd, tvs); /* best effort */
1710
1711 /* PR27571: make cache files casually unwriteable; dirs are already 0700 */
1712 (void) fchmod(fd, 0400);
1713
1714 /* rename tmp->real */
1715 rc = rename (target_cache_tmppath, target_cache_path);
1716 if (rc < 0)
1717 {
1718 rc = -errno;
1719 goto out2;
1720 /* Perhaps we need not give up right away; could retry or something ... */
1721 }
1722
1723 /* remove all handles from multi */
1724 for (int i = 0; i < num_urls; i++)
1725 {
1726 curl_multi_remove_handle(curlm, data[i].handle); /* ok to repeat */
1727 curl_easy_cleanup (data[i].handle);
1728 free (data[i].response_data);
1729 }
1730
1731 for (int i = 0; i < num_urls; ++i)
1732 free(server_url_list[i]);
1733 free(server_url_list);
1734 free (data);
1735 free (server_urls);
1736
1737 /* don't close fd - we're returning it */
1738 /* don't unlink the tmppath; it's already been renamed. */
1739 if (path != NULL)
1740 *path = strdup(target_cache_path);
1741
1742 rc = fd;
1743 goto out;
1744
1745 /* error exits */
1746 out2:
1747 /* remove all handles from multi */
1748 for (int i = 0; i < num_urls; i++)
1749 {
1750 if (data[i].handle != NULL)
1751 {
1752 curl_multi_remove_handle(curlm, data[i].handle); /* ok to repeat */
1753 curl_easy_cleanup (data[i].handle);
1754 free (data[i].response_data);
1755 }
1756 }
1757
1758 unlink (target_cache_tmppath);
1759 close (fd); /* before the rmdir, otherwise it'll fail */
1760 (void) rmdir (target_cache_dir); /* nop if not empty */
1761 free(data);
1762
1763 out1:
1764 for (int i = 0; i < num_urls; ++i)
1765 free(server_url_list[i]);
1766 free(server_url_list);
1767
1768 out0:
1769 free (server_urls);
1770
1771 /* general purpose exit */
1772 out:
1773 /* Reset sent headers */
1774 curl_slist_free_all (c->headers);
1775 c->headers = NULL;
1776 c->user_agent_set_p = 0;
1777
1778 /* Conclude the last \r status line */
1779 /* Another possibility is to use the ANSI CSI n K EL "Erase in Line"
1780 code. That way, the previously printed messages would be erased,
1781 and without a newline. */
1782 if (c->default_progressfn_printed_p)
1783 dprintf(STDERR_FILENO, "\n");
1784
1785 if (vfd >= 0)
1786 {
1787 if (rc < 0)
1788 dprintf (vfd, "not found %s (err=%d)\n", strerror (-rc), rc);
1789 else
1790 dprintf (vfd, "found %s (fd=%d)\n", target_cache_path, rc);
1791 }
1792
1793 free (cache_path);
1794 free (maxage_path);
1795 free (interval_path);
1796 free (cache_miss_path);
1797 free (target_cache_dir);
1798 free (target_cache_path);
1799 free (target_cache_tmppath);
1800 return rc;
1801 }
1802
1803
1804
1805 /* See debuginfod.h */
1806 debuginfod_client *
debuginfod_begin(void)1807 debuginfod_begin (void)
1808 {
1809 /* Initialize libcurl lazily, but only once. */
1810 pthread_once (&init_control, libcurl_init);
1811
1812 debuginfod_client *client;
1813 size_t size = sizeof (struct debuginfod_client);
1814 client = calloc (1, size);
1815
1816 if (client != NULL)
1817 {
1818 if (getenv(DEBUGINFOD_PROGRESS_ENV_VAR))
1819 client->progressfn = default_progressfn;
1820 if (getenv(DEBUGINFOD_VERBOSE_ENV_VAR))
1821 client->verbose_fd = STDERR_FILENO;
1822 else
1823 client->verbose_fd = -1;
1824
1825 // allocate 1 curl multi handle
1826 client->server_mhandle = curl_multi_init ();
1827 if (client->server_mhandle == NULL)
1828 goto out1;
1829 }
1830
1831 // extra future initialization
1832
1833 goto out;
1834
1835 out1:
1836 free (client);
1837 client = NULL;
1838
1839 out:
1840 return client;
1841 }
1842
1843 void
debuginfod_set_user_data(debuginfod_client * client,void * data)1844 debuginfod_set_user_data(debuginfod_client *client,
1845 void *data)
1846 {
1847 client->user_data = data;
1848 }
1849
1850 void *
debuginfod_get_user_data(debuginfod_client * client)1851 debuginfod_get_user_data(debuginfod_client *client)
1852 {
1853 return client->user_data;
1854 }
1855
1856 const char *
debuginfod_get_url(debuginfod_client * client)1857 debuginfod_get_url(debuginfod_client *client)
1858 {
1859 return client->url;
1860 }
1861
1862 const char *
debuginfod_get_headers(debuginfod_client * client)1863 debuginfod_get_headers(debuginfod_client *client)
1864 {
1865 return client->winning_headers;
1866 }
1867
1868 void
debuginfod_end(debuginfod_client * client)1869 debuginfod_end (debuginfod_client *client)
1870 {
1871 if (client == NULL)
1872 return;
1873
1874 curl_multi_cleanup (client->server_mhandle);
1875 curl_slist_free_all (client->headers);
1876 free (client->winning_headers);
1877 free (client->url);
1878 free (client);
1879 }
1880
1881 int
debuginfod_find_debuginfo(debuginfod_client * client,const unsigned char * build_id,int build_id_len,char ** path)1882 debuginfod_find_debuginfo (debuginfod_client *client,
1883 const unsigned char *build_id, int build_id_len,
1884 char **path)
1885 {
1886 return debuginfod_query_server(client, build_id, build_id_len,
1887 "debuginfo", NULL, path);
1888 }
1889
1890
1891 /* See debuginfod.h */
1892 int
debuginfod_find_executable(debuginfod_client * client,const unsigned char * build_id,int build_id_len,char ** path)1893 debuginfod_find_executable(debuginfod_client *client,
1894 const unsigned char *build_id, int build_id_len,
1895 char **path)
1896 {
1897 return debuginfod_query_server(client, build_id, build_id_len,
1898 "executable", NULL, path);
1899 }
1900
1901 /* See debuginfod.h */
debuginfod_find_source(debuginfod_client * client,const unsigned char * build_id,int build_id_len,const char * filename,char ** path)1902 int debuginfod_find_source(debuginfod_client *client,
1903 const unsigned char *build_id, int build_id_len,
1904 const char *filename, char **path)
1905 {
1906 return debuginfod_query_server(client, build_id, build_id_len,
1907 "source", filename, path);
1908 }
1909
1910 int
debuginfod_find_section(debuginfod_client * client,const unsigned char * build_id,int build_id_len,const char * section,char ** path)1911 debuginfod_find_section (debuginfod_client *client,
1912 const unsigned char *build_id, int build_id_len,
1913 const char *section, char **path)
1914 {
1915 int rc = debuginfod_query_server(client, build_id, build_id_len,
1916 "section", section, path);
1917 if (rc != -EINVAL)
1918 return rc;
1919
1920 /* The servers may have lacked support for section queries. Attempt to
1921 download the debuginfo or executable containing the section in order
1922 to extract it. */
1923 rc = -EEXIST;
1924 int fd = -1;
1925 char *tmp_path = NULL;
1926
1927 fd = debuginfod_find_debuginfo (client, build_id, build_id_len, &tmp_path);
1928 if (client->progressfn_cancel)
1929 {
1930 if (fd >= 0)
1931 {
1932 /* This shouldn't happen, but we'll check this condition
1933 just in case. */
1934 close (fd);
1935 free (tmp_path);
1936 }
1937 return -ENOENT;
1938 }
1939 if (fd > 0)
1940 {
1941 rc = extract_section (fd, section, tmp_path, path);
1942 close (fd);
1943 }
1944
1945 if (rc == -EEXIST)
1946 {
1947 /* The section should be found in the executable. */
1948 fd = debuginfod_find_executable (client, build_id,
1949 build_id_len, &tmp_path);
1950 if (fd > 0)
1951 {
1952 rc = extract_section (fd, section, tmp_path, path);
1953 close (fd);
1954 }
1955 }
1956
1957 free (tmp_path);
1958 return rc;
1959 }
1960
1961 /* Add an outgoing HTTP header. */
debuginfod_add_http_header(debuginfod_client * client,const char * header)1962 int debuginfod_add_http_header (debuginfod_client *client, const char* header)
1963 {
1964 /* Sanity check header value is of the form Header: Value.
1965 It should contain at least one colon that isn't the first or
1966 last character. */
1967 char *colon = strchr (header, ':'); /* first colon */
1968 if (colon == NULL /* present */
1969 || colon == header /* not at beginning - i.e., have a header name */
1970 || *(colon + 1) == '\0') /* not at end - i.e., have a value */
1971 /* NB: but it's okay for a value to contain other colons! */
1972 return -EINVAL;
1973
1974 struct curl_slist *temp = curl_slist_append (client->headers, header);
1975 if (temp == NULL)
1976 return -ENOMEM;
1977
1978 /* Track if User-Agent: is being set. If so, signal not to add the
1979 default one. */
1980 if (startswith (header, "User-Agent:"))
1981 client->user_agent_set_p = 1;
1982
1983 client->headers = temp;
1984 return 0;
1985 }
1986
1987
1988 void
debuginfod_set_progressfn(debuginfod_client * client,debuginfod_progressfn_t fn)1989 debuginfod_set_progressfn(debuginfod_client *client,
1990 debuginfod_progressfn_t fn)
1991 {
1992 client->progressfn = fn;
1993 }
1994
1995 void
debuginfod_set_verbose_fd(debuginfod_client * client,int fd)1996 debuginfod_set_verbose_fd(debuginfod_client *client, int fd)
1997 {
1998 client->verbose_fd = fd;
1999 }
2000
2001 #endif /* DUMMY_LIBDEBUGINFOD */
2002