Line data Source code
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
133 377 : debuginfod_write_callback (char *ptr, size_t size, size_t nmemb, void *data)
134 : {
135 377 : ssize_t count = size * nmemb;
136 :
137 377 : struct handle_data *d = (struct handle_data*)data;
138 :
139 : /* Indicate to other handles that they can abort their transfer. */
140 377 : if (*d->target_handle == NULL)
141 28 : *d->target_handle = d->handle;
142 :
143 : /* If this handle isn't the target handle, abort transfer. */
144 377 : if (*d->target_handle != d->handle)
145 : return -1;
146 :
147 377 : 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
154 33 : debuginfod_init_cache (char *cache_path, char *interval_path, char *maxage_path)
155 : {
156 33 : struct stat st;
157 :
158 : /* If the cache and config file already exist then we are done. */
159 33 : 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 4 : if (stat(cache_path, &st) != 0 && mkdir(cache_path, 0777) < 0)
164 0 : return -errno;
165 :
166 4 : int fd = -1;
167 :
168 : /* init cleaning interval config file. */
169 4 : fd = open(interval_path, O_CREAT | O_RDWR, 0666);
170 4 : if (fd < 0)
171 0 : return -errno;
172 :
173 4 : if (dprintf(fd, "%ld", cache_clean_default_interval_s) < 0)
174 0 : return -errno;
175 :
176 : /* init max age config file. */
177 4 : if (stat(maxage_path, &st) != 0
178 4 : && (fd = open(maxage_path, O_CREAT | O_RDWR, 0666)) < 0)
179 0 : return -errno;
180 :
181 4 : if (dprintf(fd, "%ld", cache_default_max_unused_age_s) < 0)
182 0 : 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
191 33 : debuginfod_clean_cache(debuginfod_client *c,
192 : char *cache_path, char *interval_path,
193 : char *max_unused_path)
194 : {
195 33 : struct stat st;
196 33 : FILE *interval_file;
197 33 : FILE *max_unused_file;
198 :
199 33 : if (stat(interval_path, &st) == -1)
200 : {
201 : /* Create new interval file. */
202 0 : interval_file = fopen(interval_path, "w");
203 :
204 0 : if (interval_file == NULL)
205 0 : return -errno;
206 :
207 0 : int rc = fprintf(interval_file, "%ld", cache_clean_default_interval_s);
208 0 : fclose(interval_file);
209 :
210 0 : if (rc < 0)
211 0 : return -errno;
212 : }
213 :
214 : /* Check timestamp of interval file to see whether cleaning is necessary. */
215 33 : time_t clean_interval;
216 33 : interval_file = fopen(interval_path, "r");
217 33 : if (fscanf(interval_file, "%ld", &clean_interval) != 1)
218 0 : clean_interval = cache_clean_default_interval_s;
219 33 : fclose(interval_file);
220 :
221 33 : 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 1 : time_t max_unused_age;
227 1 : max_unused_file = fopen(max_unused_path, "r");
228 1 : if (max_unused_file)
229 : {
230 1 : if (fscanf(max_unused_file, "%ld", &max_unused_age) != 1)
231 0 : max_unused_age = cache_default_max_unused_age_s;
232 1 : fclose(max_unused_file);
233 : }
234 : else
235 0 : max_unused_age = cache_default_max_unused_age_s;
236 :
237 1 : char * const dirs[] = { cache_path, NULL, };
238 :
239 1 : FTS *fts = fts_open(dirs, 0, NULL);
240 1 : if (fts == NULL)
241 0 : return -errno;
242 :
243 : FTSENT *f;
244 : long files = 0;
245 5 : while ((f = fts_read(fts)) != NULL)
246 : {
247 4 : files++;
248 4 : if (c->progressfn) /* inform/check progress callback */
249 0 : if ((c->progressfn) (c, files, 0))
250 : break;
251 :
252 4 : switch (f->fts_info)
253 : {
254 2 : 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 2 : if (time(NULL) - f->fts_statp->st_atime >= max_unused_age)
258 2 : unlink (f->fts_path);
259 : break;
260 :
261 1 : case FTS_DP:
262 : /* Remove if empty. */
263 1 : (void) rmdir (f->fts_path);
264 1 : break;
265 :
266 : default:
267 5 : ;
268 : }
269 : }
270 1 : fts_close(fts);
271 :
272 : /* Update timestamp representing when the cache was last cleaned. */
273 1 : utime (interval_path, NULL);
274 1 : 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
287 33 : 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 33 : char *urls_envvar;
295 33 : char *server_urls;
296 33 : char cache_path[PATH_MAX];
297 33 : char maxage_path[PATH_MAX*3]; /* These *3 multipliers are to shut up gcc -Wformat-truncation */
298 33 : char interval_path[PATH_MAX*4];
299 33 : char target_cache_dir[PATH_MAX*2];
300 33 : char target_cache_path[PATH_MAX*4];
301 33 : char target_cache_tmppath[PATH_MAX*5];
302 33 : char suffix[PATH_MAX*2];
303 33 : char build_id_bytes[MAX_BUILD_ID_BYTES * 2 + 1];
304 :
305 : /* Copy lowercase hex representation of build_id into buf. */
306 33 : 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 33 : if (build_id_len == 0) /* expect clean hexadecimal */
311 33 : strcpy (build_id_bytes, (const char *) build_id);
312 : else
313 0 : for (int i = 0; i < build_id_len; i++)
314 0 : sprintf(build_id_bytes + (i * 2), "%02x", build_id[i]);
315 :
316 33 : if (filename != NULL)
317 : {
318 8 : if (filename[0] != '/') // must start with /
319 : return -EINVAL;
320 :
321 : /* copy the filename to suffix, s,/,#,g */
322 : unsigned q = 0;
323 333 : for (unsigned fi=0; q < PATH_MAX-1; fi++)
324 325 : switch (filename[fi])
325 : {
326 8 : case '\0':
327 8 : suffix[q] = '\0';
328 8 : q = PATH_MAX-1; /* escape for loop too */
329 8 : break;
330 42 : case '/': /* escape / to prevent dir escape */
331 42 : suffix[q++]='#';
332 42 : suffix[q++]='#';
333 42 : break;
334 0 : case '#': /* escape # to prevent /# vs #/ collisions */
335 0 : suffix[q++]='#';
336 0 : suffix[q++]='_';
337 0 : break;
338 275 : default:
339 275 : suffix[q++]=filename[fi];
340 : }
341 8 : 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 25 : 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 33 : if (getenv(cache_path_envvar))
359 33 : strcpy(cache_path, getenv(cache_path_envvar));
360 : else
361 : {
362 0 : if (getenv("HOME"))
363 0 : sprintf(cache_path, "%s/%s", getenv("HOME"), cache_default_name);
364 : else
365 0 : sprintf(cache_path, "/%s", cache_default_name);
366 : }
367 :
368 : /* avoid using snprintf here due to compiler warning. */
369 33 : snprintf(target_cache_dir, sizeof(target_cache_dir), "%s/%s", cache_path, build_id_bytes);
370 33 : snprintf(target_cache_path, sizeof(target_cache_path), "%s/%s%s", target_cache_dir, type, suffix);
371 33 : snprintf(target_cache_tmppath, sizeof(target_cache_tmppath), "%s.XXXXXX", target_cache_path);
372 :
373 : /* XXX combine these */
374 33 : snprintf(interval_path, sizeof(interval_path), "%s/%s", cache_path, cache_clean_interval_filename);
375 33 : snprintf(maxage_path, sizeof(maxage_path), "%s/%s", cache_path, cache_max_unused_age_filename);
376 33 : int rc = debuginfod_init_cache(cache_path, interval_path, maxage_path);
377 33 : if (rc != 0)
378 : goto out;
379 33 : rc = debuginfod_clean_cache(c, cache_path, interval_path, maxage_path);
380 33 : if (rc != 0)
381 : goto out;
382 :
383 : /* If the target is already in the cache then we are done. */
384 33 : int fd = open (target_cache_path, O_RDONLY);
385 33 : if (fd >= 0)
386 : {
387 : /* Success!!!! */
388 0 : if (path != NULL)
389 0 : *path = strdup(target_cache_path);
390 0 : return fd;
391 : }
392 :
393 :
394 33 : urls_envvar = getenv(server_urls_envvar);
395 33 : if (urls_envvar == NULL || urls_envvar[0] == '\0')
396 : {
397 : rc = -ENOSYS;
398 : goto out;
399 : }
400 :
401 31 : if (getenv(server_timeout_envvar))
402 31 : server_timeout = atoi (getenv(server_timeout_envvar));
403 :
404 : /* make a copy of the envvar so it can be safely modified. */
405 31 : server_urls = strdup(urls_envvar);
406 31 : 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 31 : struct stat st;
415 31 : if (stat(target_cache_dir, &st) == -1 && mkdir(target_cache_dir, 0700) < 0)
416 : {
417 1 : rc = -errno;
418 1 : 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 30 : fd = mkstemp (target_cache_tmppath);
425 30 : if (fd < 0)
426 : {
427 0 : rc = -errno;
428 0 : goto out0;
429 : }
430 :
431 : /* Count number of URLs. */
432 : int num_urls = 0;
433 687 : for (int i = 0; server_urls[i] != '\0'; i++)
434 657 : if (server_urls[i] != url_delim_char
435 657 : && (i == 0 || server_urls[i - 1] == url_delim_char))
436 30 : 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 30 : CURL *target_handle = NULL;
441 30 : struct handle_data *data = malloc(sizeof(struct handle_data) * num_urls);
442 :
443 : /* Initalize handle_data with default values. */
444 60 : for (int i = 0; i < num_urls; i++)
445 : {
446 30 : data[i].handle = NULL;
447 30 : data[i].fd = -1;
448 : }
449 :
450 30 : CURLM *curlm = curl_multi_init();
451 30 : if (curlm == NULL)
452 : {
453 : rc = -ENETUNREACH;
454 : goto out0;
455 : }
456 : /* thereafter, goto out1 on error. */
457 :
458 30 : char *strtok_saveptr;
459 30 : char *server_url = strtok_r(server_urls, url_delim, &strtok_saveptr);
460 :
461 : /* Initialize each handle. */
462 60 : for (int i = 0; i < num_urls && server_url != NULL; i++)
463 : {
464 30 : data[i].fd = fd;
465 30 : data[i].target_handle = &target_handle;
466 30 : data[i].handle = curl_easy_init();
467 :
468 30 : 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 30 : char *slashbuildid;
477 30 : if (strlen(server_url) > 1 && server_url[strlen(server_url)-1] == '/')
478 : slashbuildid = "buildid";
479 : else
480 3 : slashbuildid = "/buildid";
481 :
482 30 : if (filename) /* must start with / */
483 8 : snprintf(data[i].url, PATH_MAX, "%s%s/%s/%s%s", server_url,
484 : slashbuildid, build_id_bytes, type, filename);
485 : else
486 22 : snprintf(data[i].url, PATH_MAX, "%s%s/%s/%s", server_url,
487 : slashbuildid, build_id_bytes, type);
488 :
489 30 : curl_easy_setopt(data[i].handle, CURLOPT_URL, data[i].url);
490 30 : curl_easy_setopt(data[i].handle,
491 : CURLOPT_WRITEFUNCTION,
492 : debuginfod_write_callback);
493 30 : curl_easy_setopt(data[i].handle, CURLOPT_WRITEDATA, (void*)&data[i]);
494 30 : curl_easy_setopt(data[i].handle, CURLOPT_TIMEOUT, (long) server_timeout);
495 30 : curl_easy_setopt(data[i].handle, CURLOPT_FILETIME, (long) 1);
496 30 : curl_easy_setopt(data[i].handle, CURLOPT_FOLLOWLOCATION, (long) 1);
497 30 : curl_easy_setopt(data[i].handle, CURLOPT_FAILONERROR, (long) 1);
498 30 : curl_easy_setopt(data[i].handle, CURLOPT_NOSIGNAL, (long) 1);
499 30 : curl_easy_setopt(data[i].handle, CURLOPT_AUTOREFERER, (long) 1);
500 30 : curl_easy_setopt(data[i].handle, CURLOPT_ACCEPT_ENCODING, "");
501 30 : curl_easy_setopt(data[i].handle, CURLOPT_USERAGENT, (void*) PACKAGE_STRING);
502 :
503 30 : curl_multi_add_handle(curlm, data[i].handle);
504 30 : 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 416 : do
511 : {
512 416 : CURLMcode curl_res;
513 :
514 416 : if (c->progressfn) /* inform/check progress callback */
515 : {
516 5 : loops ++;
517 5 : long pa = loops; /* default params for progress callback */
518 5 : long pb = 0;
519 5 : 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 1 : double dl;
530 1 : curl_res = curl_easy_getinfo(target_handle,
531 : CURLINFO_SIZE_DOWNLOAD,
532 : &dl);
533 1 : if (curl_res == 0)
534 1 : 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 1 : double cl;
546 1 : curl_res = curl_easy_getinfo(target_handle,
547 : CURLINFO_CONTENT_LENGTH_DOWNLOAD,
548 : &cl);
549 1 : if (curl_res == 0)
550 1 : pb = (cl > LONG_MAX ? LONG_MAX : (long)cl);
551 : #endif
552 : }
553 :
554 5 : if ((*c->progressfn) (c, pa, pb))
555 : break;
556 : }
557 :
558 : /* Wait 1 second, the minimum DEBUGINFOD_TIMEOUT. */
559 416 : curl_multi_wait(curlm, NULL, 0, 1000, NULL);
560 :
561 : /* If the target file has been found, abort the other queries. */
562 416 : if (target_handle != NULL)
563 698 : for (int i = 0; i < num_urls; i++)
564 349 : if (data[i].handle != target_handle)
565 0 : curl_multi_remove_handle(curlm, data[i].handle);
566 :
567 416 : curl_res = curl_multi_perform(curlm, &still_running);
568 416 : if (curl_res != CURLM_OK)
569 : {
570 0 : switch (curl_res)
571 : {
572 : case CURLM_CALL_MULTI_PERFORM: continue;
573 : case CURLM_OUT_OF_MEMORY: rc = -ENOMEM; break;
574 0 : default: rc = -ENETUNREACH; break;
575 : }
576 0 : goto out1;
577 : }
578 416 : } 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 30 : do
586 : {
587 30 : CURLMsg *msg;
588 :
589 30 : msg = curl_multi_info_read(curlm, &num_msg);
590 30 : if (msg != NULL && msg->msg == CURLMSG_DONE)
591 : {
592 30 : if (msg->data.result != CURLE_OK)
593 : {
594 : /* Unsucessful query, determine error code. */
595 2 : 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 28 : long resp_code = 500;
615 28 : CURLcode curl_res;
616 :
617 28 : curl_res = curl_easy_getinfo(target_handle,
618 : CURLINFO_RESPONSE_CODE,
619 : &resp_code);
620 :
621 28 : if (curl_res == CURLE_OK
622 28 : && resp_code == 200
623 28 : && msg->easy_handle != NULL)
624 : {
625 28 : verified_handle = msg->easy_handle;
626 28 : break;
627 : }
628 : }
629 : }
630 2 : } while (num_msg > 0);
631 :
632 30 : if (verified_handle == NULL)
633 : goto out1;
634 :
635 : /* we've got one!!!! */
636 28 : time_t mtime;
637 28 : CURLcode curl_res = curl_easy_getinfo(verified_handle, CURLINFO_FILETIME, (void*) &mtime);
638 28 : if (curl_res != CURLE_OK)
639 0 : mtime = time(NULL); /* fall back to current time */
640 :
641 28 : struct timeval tvs[2];
642 28 : tvs[0].tv_sec = tvs[1].tv_sec = mtime;
643 28 : tvs[0].tv_usec = tvs[1].tv_usec = 0;
644 28 : (void) futimes (fd, tvs); /* best effort */
645 :
646 : /* rename tmp->real */
647 28 : rc = rename (target_cache_tmppath, target_cache_path);
648 28 : if (rc < 0)
649 : {
650 0 : rc = -errno;
651 0 : goto out1;
652 : /* Perhaps we need not give up right away; could retry or something ... */
653 : }
654 :
655 : /* Success!!!! */
656 56 : for (int i = 0; i < num_urls; i++)
657 28 : curl_easy_cleanup(data[i].handle);
658 :
659 28 : curl_multi_cleanup (curlm);
660 28 : free (data);
661 28 : free (server_urls);
662 : /* don't close fd - we're returning it */
663 : /* don't unlink the tmppath; it's already been renamed. */
664 28 : if (path != NULL)
665 27 : *path = strdup(target_cache_path);
666 :
667 : return fd;
668 :
669 : /* error exits */
670 2 : out1:
671 4 : for (int i = 0; i < num_urls; i++)
672 2 : curl_easy_cleanup(data[i].handle);
673 :
674 2 : curl_multi_cleanup(curlm);
675 2 : unlink (target_cache_tmppath);
676 2 : (void) rmdir (target_cache_dir); /* nop if not empty */
677 2 : free(data);
678 2 : close (fd);
679 :
680 3 : out0:
681 3 : free (server_urls);
682 :
683 : out:
684 : return rc;
685 : }
686 :
687 : /* See debuginfod.h */
688 : debuginfod_client *
689 33 : debuginfod_begin (void)
690 : {
691 33 : debuginfod_client *client;
692 33 : size_t size = sizeof (struct debuginfod_client);
693 33 : client = (debuginfod_client *) malloc (size);
694 33 : if (client != NULL)
695 33 : client->progressfn = NULL;
696 33 : return client;
697 : }
698 :
699 : void
700 30 : debuginfod_end (debuginfod_client *client)
701 : {
702 30 : free (client);
703 30 : }
704 :
705 : int
706 14 : debuginfod_find_debuginfo (debuginfod_client *client,
707 : const unsigned char *build_id, int build_id_len,
708 : char **path)
709 : {
710 14 : return debuginfod_query_server(client, build_id, build_id_len,
711 : "debuginfo", NULL, path);
712 : }
713 :
714 :
715 : /* See debuginfod.h */
716 : int
717 11 : debuginfod_find_executable(debuginfod_client *client,
718 : const unsigned char *build_id, int build_id_len,
719 : char **path)
720 : {
721 11 : return debuginfod_query_server(client, build_id, build_id_len,
722 : "executable", NULL, path);
723 : }
724 :
725 : /* See debuginfod.h */
726 8 : 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 8 : return debuginfod_query_server(client, build_id, build_id_len,
731 : "source", filename, path);
732 : }
733 :
734 :
735 : void
736 4 : debuginfod_set_progressfn(debuginfod_client *client,
737 : debuginfod_progressfn_t fn)
738 : {
739 4 : client->progressfn = fn;
740 4 : }
741 :
742 :
743 : /* NB: these are thread-unsafe. */
744 32 : __attribute__((constructor)) attribute_hidden void libdebuginfod_ctor(void)
745 : {
746 32 : curl_global_init(CURL_GLOBAL_DEFAULT);
747 32 : }
748 :
749 : /* NB: this is very thread-unsafe: it breaks other threads that are still in libcurl */
750 0 : __attribute__((destructor)) attribute_hidden void libdebuginfod_dtor(void)
751 : {
752 : /* ... so don't do this: */
753 : /* curl_global_cleanup(); */
754 0 : }
|