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