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