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