• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* Retrieve ELF / DWARF / source files from the debuginfod.
2    Copyright (C) 2019 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 <assert.h>
44 #include <dirent.h>
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <errno.h>
48 #include <unistd.h>
49 #include <errno.h>
50 #include <fcntl.h>
51 #include <fts.h>
52 #include <string.h>
53 #include <stdbool.h>
54 #include <linux/limits.h>
55 #include <time.h>
56 #include <utime.h>
57 #include <sys/syscall.h>
58 #include <sys/types.h>
59 #include <sys/stat.h>
60 #include <curl/curl.h>
61 
62 /* If fts.h is included before config.h, its indirect inclusions may not
63    give us the right LFS aliases of these functions, so map them manually.  */
64 #ifdef BAD_FTS
65   #ifdef _FILE_OFFSET_BITS
66     #define open open64
67     #define fopen fopen64
68   #endif
69 #else
70   #include <sys/types.h>
71   #include <fts.h>
72 #endif
73 
74 struct debuginfod_client
75 {
76   /* Progress/interrupt callback function. */
77   debuginfod_progressfn_t progressfn;
78 
79   /* Can contain all other context, like cache_path, server_urls,
80      timeout or other info gotten from environment variables, the
81      handle data, etc. So those don't have to be reparsed and
82      recreated on each request.  */
83 };
84 
85 /* The cache_clean_interval_s file within the debuginfod cache specifies
86    how frequently the cache should be cleaned. The file's st_mtime represents
87    the time of last cleaning.  */
88 static const char *cache_clean_interval_filename = "cache_clean_interval_s";
89 static const time_t cache_clean_default_interval_s = 86400; /* 1 day */
90 
91 /* The cache_max_unused_age_s file within the debuginfod cache specifies the
92    the maximum time since last access that a file will remain in the cache.  */
93 static const char *cache_max_unused_age_filename = "max_unused_age_s";
94 static const time_t cache_default_max_unused_age_s = 604800; /* 1 week */
95 
96 /* Location of the cache of files downloaded from debuginfods.
97    The default parent directory is $HOME, or '/' if $HOME doesn't exist.  */
98 static const char *cache_default_name = ".debuginfod_client_cache";
99 static const char *cache_path_envvar = DEBUGINFOD_CACHE_PATH_ENV_VAR;
100 
101 /* URLs of debuginfods, separated by url_delim.
102    This env var must be set for debuginfod-client to run.  */
103 static const char *server_urls_envvar = DEBUGINFOD_URLS_ENV_VAR;
104 static const char *url_delim =  " ";
105 static const char url_delim_char = ' ';
106 
107 /* Timeout for debuginfods, in seconds.
108    This env var must be set for debuginfod-client to run.  */
109 static const char *server_timeout_envvar = DEBUGINFOD_TIMEOUT_ENV_VAR;
110 static int server_timeout = 5;
111 
112 /* Data associated with a particular CURL easy handle. Passed to
113    the write callback.  */
114 struct handle_data
115 {
116   /* Cache file to be written to in case query is successful.  */
117   int fd;
118 
119   /* URL queried by this handle.  */
120   char url[PATH_MAX];
121 
122   /* This handle.  */
123   CURL *handle;
124 
125   /* Pointer to handle that should write to fd. Initially points to NULL,
126      then points to the first handle that begins writing the target file
127      to the cache. Used to ensure that a file is not downloaded from
128      multiple servers unnecessarily.  */
129   CURL **target_handle;
130 };
131 
132 static size_t
debuginfod_write_callback(char * ptr,size_t size,size_t nmemb,void * data)133 debuginfod_write_callback (char *ptr, size_t size, size_t nmemb, void *data)
134 {
135   ssize_t count = size * nmemb;
136 
137   struct handle_data *d = (struct handle_data*)data;
138 
139   /* Indicate to other handles that they can abort their transfer.  */
140   if (*d->target_handle == NULL)
141     *d->target_handle = d->handle;
142 
143   /* If this handle isn't the target handle, abort transfer.  */
144   if (*d->target_handle != d->handle)
145     return -1;
146 
147   return (size_t) write(d->fd, (void*)ptr, count);
148 }
149 
150 /* Create the cache and interval file if they do not already exist.
151    Return 0 if cache and config file are initialized, otherwise return
152    the appropriate error code.  */
153 static int
debuginfod_init_cache(char * cache_path,char * interval_path,char * maxage_path)154 debuginfod_init_cache (char *cache_path, char *interval_path, char *maxage_path)
155 {
156   struct stat st;
157 
158   /* If the cache and config file already exist then we are done.  */
159   if (stat(cache_path, &st) == 0 && stat(interval_path, &st) == 0)
160     return 0;
161 
162   /* Create the cache and config files as necessary.  */
163   if (stat(cache_path, &st) != 0 && mkdir(cache_path, 0777) < 0)
164     return -errno;
165 
166   int fd = -1;
167 
168   /* init cleaning interval config file.  */
169   fd = open(interval_path, O_CREAT | O_RDWR, 0666);
170   if (fd < 0)
171     return -errno;
172 
173   if (dprintf(fd, "%ld", cache_clean_default_interval_s) < 0)
174     return -errno;
175 
176   /* init max age config file.  */
177   if (stat(maxage_path, &st) != 0
178       && (fd = open(maxage_path, O_CREAT | O_RDWR, 0666)) < 0)
179     return -errno;
180 
181   if (dprintf(fd, "%ld", cache_default_max_unused_age_s) < 0)
182     return -errno;
183 
184   return 0;
185 }
186 
187 
188 /* Delete any files that have been unmodied for a period
189    longer than $DEBUGINFOD_CACHE_CLEAN_INTERVAL_S.  */
190 static int
debuginfod_clean_cache(debuginfod_client * c,char * cache_path,char * interval_path,char * max_unused_path)191 debuginfod_clean_cache(debuginfod_client *c,
192 		       char *cache_path, char *interval_path,
193 		       char *max_unused_path)
194 {
195   struct stat st;
196   FILE *interval_file;
197   FILE *max_unused_file;
198 
199   if (stat(interval_path, &st) == -1)
200     {
201       /* Create new interval file.  */
202       interval_file = fopen(interval_path, "w");
203 
204       if (interval_file == NULL)
205         return -errno;
206 
207       int rc = fprintf(interval_file, "%ld", cache_clean_default_interval_s);
208       fclose(interval_file);
209 
210       if (rc < 0)
211         return -errno;
212     }
213 
214   /* Check timestamp of interval file to see whether cleaning is necessary.  */
215   time_t clean_interval;
216   interval_file = fopen(interval_path, "r");
217   if (fscanf(interval_file, "%ld", &clean_interval) != 1)
218     clean_interval = cache_clean_default_interval_s;
219   fclose(interval_file);
220 
221   if (time(NULL) - st.st_mtime < clean_interval)
222     /* Interval has not passed, skip cleaning.  */
223     return 0;
224 
225   /* Read max unused age value from config file.  */
226   time_t max_unused_age;
227   max_unused_file = fopen(max_unused_path, "r");
228   if (max_unused_file)
229     {
230       if (fscanf(max_unused_file, "%ld", &max_unused_age) != 1)
231         max_unused_age = cache_default_max_unused_age_s;
232       fclose(max_unused_file);
233     }
234   else
235     max_unused_age = cache_default_max_unused_age_s;
236 
237   char * const dirs[] = { cache_path, NULL, };
238 
239   FTS *fts = fts_open(dirs, 0, NULL);
240   if (fts == NULL)
241     return -errno;
242 
243   FTSENT *f;
244   long files = 0;
245   while ((f = fts_read(fts)) != NULL)
246     {
247       files++;
248       if (c->progressfn) /* inform/check progress callback */
249         if ((c->progressfn) (c, files, 0))
250           break;
251 
252       switch (f->fts_info)
253         {
254         case FTS_F:
255           /* delete file if max_unused_age has been met or exceeded.  */
256           /* XXX consider extra effort to clean up old tmp files */
257           if (time(NULL) - f->fts_statp->st_atime >= max_unused_age)
258             unlink (f->fts_path);
259           break;
260 
261         case FTS_DP:
262           /* Remove if empty. */
263           (void) rmdir (f->fts_path);
264           break;
265 
266         default:
267           ;
268         }
269     }
270   fts_close(fts);
271 
272   /* Update timestamp representing when the cache was last cleaned.  */
273   utime (interval_path, NULL);
274   return 0;
275 }
276 
277 
278 #define MAX_BUILD_ID_BYTES 64
279 
280 
281 /* Query each of the server URLs found in $DEBUGINFOD_URLS for the file
282    with the specified build-id, type (debuginfo, executable or source)
283    and filename. filename may be NULL. If found, return a file
284    descriptor for the target, otherwise return an error code.
285 */
286 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)287 debuginfod_query_server (debuginfod_client *c,
288 			 const unsigned char *build_id,
289                          int build_id_len,
290                          const char *type,
291                          const char *filename,
292                          char **path)
293 {
294   char *urls_envvar;
295   char *server_urls;
296   char cache_path[PATH_MAX];
297   char maxage_path[PATH_MAX*3]; /* These *3 multipliers are to shut up gcc -Wformat-truncation */
298   char interval_path[PATH_MAX*4];
299   char target_cache_dir[PATH_MAX*2];
300   char target_cache_path[PATH_MAX*4];
301   char target_cache_tmppath[PATH_MAX*5];
302   char suffix[PATH_MAX*2];
303   char build_id_bytes[MAX_BUILD_ID_BYTES * 2 + 1];
304 
305   /* Copy lowercase hex representation of build_id into buf.  */
306   if ((build_id_len >= MAX_BUILD_ID_BYTES) ||
307       (build_id_len == 0 &&
308        sizeof(build_id_bytes) > MAX_BUILD_ID_BYTES*2 + 1))
309     return -EINVAL;
310   if (build_id_len == 0) /* expect clean hexadecimal */
311     strcpy (build_id_bytes, (const char *) build_id);
312   else
313     for (int i = 0; i < build_id_len; i++)
314       sprintf(build_id_bytes + (i * 2), "%02x", build_id[i]);
315 
316   if (filename != NULL)
317     {
318       if (filename[0] != '/') // must start with /
319         return -EINVAL;
320 
321       /* copy the filename to suffix, s,/,#,g */
322       unsigned q = 0;
323       for (unsigned fi=0; q < PATH_MAX-1; fi++)
324         switch (filename[fi])
325           {
326           case '\0':
327             suffix[q] = '\0';
328             q = PATH_MAX-1; /* escape for loop too */
329             break;
330           case '/': /* escape / to prevent dir escape */
331             suffix[q++]='#';
332             suffix[q++]='#';
333             break;
334           case '#': /* escape # to prevent /# vs #/ collisions */
335             suffix[q++]='#';
336             suffix[q++]='_';
337             break;
338           default:
339             suffix[q++]=filename[fi];
340           }
341       suffix[q] = '\0';
342       /* If the DWARF filenames are super long, this could exceed
343          PATH_MAX and truncate/collide.  Oh well, that'll teach
344          them! */
345     }
346   else
347     suffix[0] = '\0';
348 
349   /* set paths needed to perform the query
350 
351      example format
352      cache_path:        $HOME/.debuginfod_cache
353      target_cache_dir:  $HOME/.debuginfod_cache/0123abcd
354      target_cache_path: $HOME/.debuginfod_cache/0123abcd/debuginfo
355      target_cache_path: $HOME/.debuginfod_cache/0123abcd/source#PATH#TO#SOURCE ?
356   */
357 
358   if (getenv(cache_path_envvar))
359     strcpy(cache_path, getenv(cache_path_envvar));
360   else
361     {
362       if (getenv("HOME"))
363         sprintf(cache_path, "%s/%s", getenv("HOME"), cache_default_name);
364       else
365         sprintf(cache_path, "/%s", cache_default_name);
366     }
367 
368   /* avoid using snprintf here due to compiler warning.  */
369   snprintf(target_cache_dir, sizeof(target_cache_dir), "%s/%s", cache_path, build_id_bytes);
370   snprintf(target_cache_path, sizeof(target_cache_path), "%s/%s%s", target_cache_dir, type, suffix);
371   snprintf(target_cache_tmppath, sizeof(target_cache_tmppath), "%s.XXXXXX", target_cache_path);
372 
373   /* XXX combine these */
374   snprintf(interval_path, sizeof(interval_path), "%s/%s", cache_path, cache_clean_interval_filename);
375   snprintf(maxage_path, sizeof(maxage_path), "%s/%s", cache_path, cache_max_unused_age_filename);
376   int rc = debuginfod_init_cache(cache_path, interval_path, maxage_path);
377   if (rc != 0)
378     goto out;
379   rc = debuginfod_clean_cache(c, cache_path, interval_path, maxage_path);
380   if (rc != 0)
381     goto out;
382 
383   /* If the target is already in the cache then we are done.  */
384   int fd = open (target_cache_path, O_RDONLY);
385   if (fd >= 0)
386     {
387       /* Success!!!! */
388       if (path != NULL)
389         *path = strdup(target_cache_path);
390       return fd;
391     }
392 
393 
394   urls_envvar = getenv(server_urls_envvar);
395   if (urls_envvar == NULL || urls_envvar[0] == '\0')
396     {
397       rc = -ENOSYS;
398       goto out;
399     }
400 
401   if (getenv(server_timeout_envvar))
402     server_timeout = atoi (getenv(server_timeout_envvar));
403 
404   /* make a copy of the envvar so it can be safely modified.  */
405   server_urls = strdup(urls_envvar);
406   if (server_urls == NULL)
407     {
408       rc = -ENOMEM;
409       goto out;
410     }
411   /* thereafter, goto out0 on error*/
412 
413   /* create target directory in cache if not found.  */
414   struct stat st;
415   if (stat(target_cache_dir, &st) == -1 && mkdir(target_cache_dir, 0700) < 0)
416     {
417       rc = -errno;
418       goto out0;
419     }
420 
421   /* NB: write to a temporary file first, to avoid race condition of
422      multiple clients checking the cache, while a partially-written or empty
423      file is in there, being written from libcurl. */
424   fd = mkstemp (target_cache_tmppath);
425   if (fd < 0)
426     {
427       rc = -errno;
428       goto out0;
429     }
430 
431   /* Count number of URLs.  */
432   int num_urls = 0;
433   for (int i = 0; server_urls[i] != '\0'; i++)
434     if (server_urls[i] != url_delim_char
435         && (i == 0 || server_urls[i - 1] == url_delim_char))
436       num_urls++;
437 
438   /* Tracks which handle should write to fd. Set to the first
439      handle that is ready to write the target file to the cache.  */
440   CURL *target_handle = NULL;
441   struct handle_data *data = malloc(sizeof(struct handle_data) * num_urls);
442 
443   /* Initalize handle_data with default values. */
444   for (int i = 0; i < num_urls; i++)
445     {
446       data[i].handle = NULL;
447       data[i].fd = -1;
448     }
449 
450   CURLM *curlm = curl_multi_init();
451   if (curlm == NULL)
452     {
453       rc = -ENETUNREACH;
454       goto out0;
455     }
456   /* thereafter, goto out1 on error.  */
457 
458   char *strtok_saveptr;
459   char *server_url = strtok_r(server_urls, url_delim, &strtok_saveptr);
460 
461   /* Initialize each handle.  */
462   for (int i = 0; i < num_urls && server_url != NULL; i++)
463     {
464       data[i].fd = fd;
465       data[i].target_handle = &target_handle;
466       data[i].handle = curl_easy_init();
467 
468       if (data[i].handle == NULL)
469         {
470           rc = -ENETUNREACH;
471           goto out1;
472         }
473 
474       /* Build handle url. Tolerate both  http://foo:999  and
475          http://foo:999/  forms */
476       char *slashbuildid;
477       if (strlen(server_url) > 1 && server_url[strlen(server_url)-1] == '/')
478         slashbuildid = "buildid";
479       else
480         slashbuildid = "/buildid";
481 
482       if (filename) /* must start with / */
483         snprintf(data[i].url, PATH_MAX, "%s%s/%s/%s%s", server_url,
484                  slashbuildid, build_id_bytes, type, filename);
485       else
486         snprintf(data[i].url, PATH_MAX, "%s%s/%s/%s", server_url,
487                  slashbuildid, build_id_bytes, type);
488 
489       curl_easy_setopt(data[i].handle, CURLOPT_URL, data[i].url);
490       curl_easy_setopt(data[i].handle,
491                        CURLOPT_WRITEFUNCTION,
492                        debuginfod_write_callback);
493       curl_easy_setopt(data[i].handle, CURLOPT_WRITEDATA, (void*)&data[i]);
494       curl_easy_setopt(data[i].handle, CURLOPT_TIMEOUT, (long) server_timeout);
495       curl_easy_setopt(data[i].handle, CURLOPT_FILETIME, (long) 1);
496       curl_easy_setopt(data[i].handle, CURLOPT_FOLLOWLOCATION, (long) 1);
497       curl_easy_setopt(data[i].handle, CURLOPT_FAILONERROR, (long) 1);
498       curl_easy_setopt(data[i].handle, CURLOPT_NOSIGNAL, (long) 1);
499       curl_easy_setopt(data[i].handle, CURLOPT_AUTOREFERER, (long) 1);
500       curl_easy_setopt(data[i].handle, CURLOPT_ACCEPT_ENCODING, "");
501       curl_easy_setopt(data[i].handle, CURLOPT_USERAGENT, (void*) PACKAGE_STRING);
502 
503       curl_multi_add_handle(curlm, data[i].handle);
504       server_url = strtok_r(NULL, url_delim, &strtok_saveptr);
505     }
506 
507   /* Query servers in parallel.  */
508   int still_running;
509   long loops = 0;
510   do
511     {
512       CURLMcode curl_res;
513 
514       if (c->progressfn) /* inform/check progress callback */
515         {
516           loops ++;
517           long pa = loops; /* default params for progress callback */
518           long pb = 0;
519           if (target_handle) /* we've committed to a server; report its download progress */
520             {
521 #ifdef CURLINFO_SIZE_DOWNLOAD_T
522               curl_off_t dl;
523               curl_res = curl_easy_getinfo(target_handle,
524                                            CURLINFO_SIZE_DOWNLOAD_T,
525                                            &dl);
526               if (curl_res == 0 && dl >= 0)
527                 pa = (dl > LONG_MAX ? LONG_MAX : (long)dl);
528 #else
529               double dl;
530               curl_res = curl_easy_getinfo(target_handle,
531                                            CURLINFO_SIZE_DOWNLOAD,
532                                            &dl);
533               if (curl_res == 0)
534                 pa = (dl > LONG_MAX ? LONG_MAX : (long)dl);
535 #endif
536 
537 #ifdef CURLINFO_CURLINFO_CONTENT_LENGTH_DOWNLOAD_T
538               curl_off_t cl;
539               curl_res = curl_easy_getinfo(target_handle,
540                                            CURLINFO_CONTENT_LENGTH_DOWNLOAD_T,
541                                            &cl);
542               if (curl_res == 0 && cl >= 0)
543                 pb = (cl > LONG_MAX ? LONG_MAX : (long)cl);
544 #else
545               double cl;
546               curl_res = curl_easy_getinfo(target_handle,
547                                            CURLINFO_CONTENT_LENGTH_DOWNLOAD,
548                                            &cl);
549               if (curl_res == 0)
550                 pb = (cl > LONG_MAX ? LONG_MAX : (long)cl);
551 #endif
552             }
553 
554           if ((*c->progressfn) (c, pa, pb))
555             break;
556         }
557 
558       /* Wait 1 second, the minimum DEBUGINFOD_TIMEOUT.  */
559       curl_multi_wait(curlm, NULL, 0, 1000, NULL);
560 
561       /* If the target file has been found, abort the other queries.  */
562       if (target_handle != NULL)
563         for (int i = 0; i < num_urls; i++)
564           if (data[i].handle != target_handle)
565             curl_multi_remove_handle(curlm, data[i].handle);
566 
567       curl_res = curl_multi_perform(curlm, &still_running);
568       if (curl_res != CURLM_OK)
569         {
570           switch (curl_res)
571             {
572             case CURLM_CALL_MULTI_PERFORM: continue;
573             case CURLM_OUT_OF_MEMORY: rc = -ENOMEM; break;
574             default: rc = -ENETUNREACH; break;
575             }
576           goto out1;
577         }
578     } while (still_running);
579 
580   /* Check whether a query was successful. If so, assign its handle
581      to verified_handle.  */
582   int num_msg;
583   rc = -ENOENT;
584   CURL *verified_handle = NULL;
585   do
586     {
587       CURLMsg *msg;
588 
589       msg = curl_multi_info_read(curlm, &num_msg);
590       if (msg != NULL && msg->msg == CURLMSG_DONE)
591         {
592           if (msg->data.result != CURLE_OK)
593             {
594               /* Unsucessful query, determine error code.  */
595               switch (msg->data.result)
596                 {
597                 case CURLE_COULDNT_RESOLVE_HOST: rc = -EHOSTUNREACH; break; // no NXDOMAIN
598                 case CURLE_URL_MALFORMAT: rc = -EINVAL; break;
599                 case CURLE_COULDNT_CONNECT: rc = -ECONNREFUSED; break;
600                 case CURLE_REMOTE_ACCESS_DENIED: rc = -EACCES; break;
601                 case CURLE_WRITE_ERROR: rc = -EIO; break;
602                 case CURLE_OUT_OF_MEMORY: rc = -ENOMEM; break;
603                 case CURLE_TOO_MANY_REDIRECTS: rc = -EMLINK; break;
604                 case CURLE_SEND_ERROR: rc = -ECONNRESET; break;
605                 case CURLE_RECV_ERROR: rc = -ECONNRESET; break;
606                 case CURLE_OPERATION_TIMEDOUT: rc = -ETIME; break;
607                 default: rc = -ENOENT; break;
608                 }
609             }
610           else
611             {
612               /* Query completed without an error. Confirm that the
613                  response code is 200 and set verified_handle.  */
614               long resp_code = 500;
615               CURLcode curl_res;
616 
617               curl_res = curl_easy_getinfo(target_handle,
618                                            CURLINFO_RESPONSE_CODE,
619                                            &resp_code);
620 
621               if (curl_res == CURLE_OK
622                   && resp_code == 200
623                   && msg->easy_handle != NULL)
624                 {
625                   verified_handle = msg->easy_handle;
626                   break;
627                 }
628             }
629         }
630     } while (num_msg > 0);
631 
632   if (verified_handle == NULL)
633     goto out1;
634 
635   /* we've got one!!!! */
636   time_t mtime;
637   CURLcode curl_res = curl_easy_getinfo(verified_handle, CURLINFO_FILETIME, (void*) &mtime);
638   if (curl_res != CURLE_OK)
639     mtime = time(NULL); /* fall back to current time */
640 
641   struct timeval tvs[2];
642   tvs[0].tv_sec = tvs[1].tv_sec = mtime;
643   tvs[0].tv_usec = tvs[1].tv_usec = 0;
644   (void) futimes (fd, tvs);  /* best effort */
645 
646   /* rename tmp->real */
647   rc = rename (target_cache_tmppath, target_cache_path);
648   if (rc < 0)
649     {
650       rc = -errno;
651       goto out1;
652       /* Perhaps we need not give up right away; could retry or something ... */
653     }
654 
655   /* Success!!!! */
656   for (int i = 0; i < num_urls; i++)
657     curl_easy_cleanup(data[i].handle);
658 
659   curl_multi_cleanup (curlm);
660   free (data);
661   free (server_urls);
662   /* don't close fd - we're returning it */
663   /* don't unlink the tmppath; it's already been renamed. */
664   if (path != NULL)
665    *path = strdup(target_cache_path);
666 
667   return fd;
668 
669 /* error exits */
670  out1:
671   for (int i = 0; i < num_urls; i++)
672     curl_easy_cleanup(data[i].handle);
673 
674   curl_multi_cleanup(curlm);
675   unlink (target_cache_tmppath);
676   (void) rmdir (target_cache_dir); /* nop if not empty */
677   free(data);
678   close (fd);
679 
680  out0:
681   free (server_urls);
682 
683  out:
684   return rc;
685 }
686 
687 /* See debuginfod.h  */
688 debuginfod_client  *
debuginfod_begin(void)689 debuginfod_begin (void)
690 {
691   debuginfod_client *client;
692   size_t size = sizeof (struct debuginfod_client);
693   client = (debuginfod_client *) malloc (size);
694   if (client != NULL)
695     client->progressfn = NULL;
696   return client;
697 }
698 
699 void
debuginfod_end(debuginfod_client * client)700 debuginfod_end (debuginfod_client *client)
701 {
702   free (client);
703 }
704 
705 int
debuginfod_find_debuginfo(debuginfod_client * client,const unsigned char * build_id,int build_id_len,char ** path)706 debuginfod_find_debuginfo (debuginfod_client *client,
707 			   const unsigned char *build_id, int build_id_len,
708                            char **path)
709 {
710   return debuginfod_query_server(client, build_id, build_id_len,
711                                  "debuginfo", NULL, path);
712 }
713 
714 
715 /* See debuginfod.h  */
716 int
debuginfod_find_executable(debuginfod_client * client,const unsigned char * build_id,int build_id_len,char ** path)717 debuginfod_find_executable(debuginfod_client *client,
718 			   const unsigned char *build_id, int build_id_len,
719                            char **path)
720 {
721   return debuginfod_query_server(client, build_id, build_id_len,
722                                  "executable", NULL, path);
723 }
724 
725 /* See debuginfod.h  */
debuginfod_find_source(debuginfod_client * client,const unsigned char * build_id,int build_id_len,const char * filename,char ** path)726 int debuginfod_find_source(debuginfod_client *client,
727 			   const unsigned char *build_id, int build_id_len,
728                            const char *filename, char **path)
729 {
730   return debuginfod_query_server(client, build_id, build_id_len,
731                                  "source", filename, path);
732 }
733 
734 
735 void
debuginfod_set_progressfn(debuginfod_client * client,debuginfod_progressfn_t fn)736 debuginfod_set_progressfn(debuginfod_client *client,
737 			  debuginfod_progressfn_t fn)
738 {
739   client->progressfn = fn;
740 }
741 
742 
743 /* NB: these are thread-unsafe. */
libdebuginfod_ctor(void)744 __attribute__((constructor)) attribute_hidden void libdebuginfod_ctor(void)
745 {
746   curl_global_init(CURL_GLOBAL_DEFAULT);
747 }
748 
749 /* NB: this is very thread-unsafe: it breaks other threads that are still in libcurl */
libdebuginfod_dtor(void)750 __attribute__((destructor)) attribute_hidden void libdebuginfod_dtor(void)
751 {
752   /* ... so don't do this: */
753   /* curl_global_cleanup(); */
754 }
755