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