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