diff options
Diffstat (limited to 'debuginfod')
| -rw-r--r-- | debuginfod/ChangeLog | 9 | ||||
| -rw-r--r-- | debuginfod/Makefile.am | 114 | ||||
| -rw-r--r-- | debuginfod/debuginfod-client.c | 665 | ||||
| -rw-r--r-- | debuginfod/debuginfod-find.c | 101 | ||||
| -rw-r--r-- | debuginfod/debuginfod.h | 69 | ||||
| -rw-r--r-- | debuginfod/libdebuginfod.map | 7 |
6 files changed, 965 insertions, 0 deletions
diff --git a/debuginfod/ChangeLog b/debuginfod/ChangeLog new file mode 100644 index 00000000..1a31cf6f --- /dev/null +++ b/debuginfod/ChangeLog @@ -0,0 +1,9 @@ +2019-10-28 Aaron Merey <[email protected]> + + * debuginfod-client.c: New file: debuginfod client library. + * debuginfod.h: New file: header for same. + * libdebuginfod.map: New file: govern its solib exports. + * debuginfod-find.c: New file: command line frontend. + * debuginfod-find.1, debuginfod_find_source.3, + debuginfod_find_executable.3, debuginfod_find_debuginfo.3: + New man pages. diff --git a/debuginfod/Makefile.am b/debuginfod/Makefile.am new file mode 100644 index 00000000..a8ee4594 --- /dev/null +++ b/debuginfod/Makefile.am @@ -0,0 +1,114 @@ +## Makefile.am for libdebuginfod library subdirectory in elfutils. +## +## Process this file with automake to create Makefile.in +## +## Copyright (C) 2019 Red Hat, Inc. +## This file is part of elfutils. +## +## This file is free software; you can redistribute it and/or modify +## it under the terms of either +## +## * the GNU Lesser General Public License as published by the Free +## Software Foundation; either version 3 of the License, or (at +## your option) any later version +## +## or +## +## * the GNU General Public License as published by the Free +## Software Foundation; either version 2 of the License, or (at +## your option) any later version +## +## or both in parallel, as here. +## +## elfutils is distributed in the hope that it will be useful, but +## WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +## General Public License for more details. +## +## You should have received copies of the GNU General Public License and +## the GNU Lesser General Public License along with this program. If +## not, see <http://www.gnu.org/licenses/>. +## +include $(top_srcdir)/config/eu.am +AM_CPPFLAGS += -I$(srcdir) -I$(srcdir)/../libelf -I$(srcdir)/../libebl \ + -I$(srcdir)/../libdw -I$(srcdir)/../libdwelf +VERSION = 1 + +# Disable eu- prefixing for artifacts (binaries & man pages) in this +# directory, since they do not conflict with binutils tools. +program_prefix= +program_transform_name = s,x,x, + +if BUILD_STATIC +libasm = ../libasm/libasm.a +libdw = ../libdw/libdw.a -lz $(zip_LIBS) $(libelf) $(libebl) -ldl +libelf = ../libelf/libelf.a -lz +libdebuginfod = ./libdebuginfod.a +else +libasm = ../libasm/libasm.so +libdw = ../libdw/libdw.so +libelf = ../libelf/libelf.so +libdebuginfod = ./libdebuginfod.so +endif +libebl = ../libebl/libebl.a +libeu = ../lib/libeu.a + +AM_LDFLAGS = -Wl,-rpath-link,../libelf:../libdw:. + +bin_PROGRAMS = debuginfod-find + +debuginfod_find_SOURCES = debuginfod-find.c +debuginfod_find_LDADD = $(libeu) $(libdebuginfod) + +noinst_LIBRARIES = libdebuginfod.a +noinst_LIBRARIES += libdebuginfod_pic.a + +libdebuginfod_a_SOURCES = debuginfod-client.c +libdebuginfod_pic_a_SOURCES = debuginfod-client.c +am_libdebuginfod_pic_a_OBJECTS = $(libdebuginfod_a_SOURCES:.c=.os) + +pkginclude_HEADERS = debuginfod.h + +libdebuginfod_so_LIBS = libdebuginfod_pic.a +libdebuginfod_so_LDLIBS = $(libcurl_LIBS) +libdebuginfod.so$(EXEEXT): $(srcdir)/libdebuginfod.map $(libdebuginfod_so_LIBS) + $(AM_V_CCLD)$(LINK) $(dso_LDFLAGS) -o $@ \ + -Wl,--soname,$@.$(VERSION) \ + -Wl,--version-script,$<,--no-undefined \ + -Wl,--whole-archive $(libdebuginfod_so_LIBS) -Wl,--no-whole-archive \ + $(libdebuginfod_so_LDLIBS) + @$(textrel_check) + $(AM_V_at)ln -fs $@ $@.$(VERSION) + +install: install-am libdebuginfod.so + $(mkinstalldirs) $(DESTDIR)$(libdir) + $(INSTALL_PROGRAM) libdebuginfod.so $(DESTDIR)$(libdir)/libdebuginfod-$(PACKAGE_VERSION).so + ln -fs libdebuginfod-$(PACKAGE_VERSION).so $(DESTDIR)$(libdir)/libdebuginfod.so.$(VERSION) + ln -fs libdebuginfod.so.$(VERSION) $(DESTDIR)$(libdir)/libdebuginfod.so + +uninstall: uninstall-am + rm -f $(DESTDIR)$(libdir)/libdebuginfod-$(PACKAGE_VERSION).so + rm -f $(DESTDIR)$(libdir)/libdebuginfod.so.$(VERSION) + rm -f $(DESTDIR)$(libdir)/libdebuginfod.so + rmdir --ignore-fail-on-non-empty $(DESTDIR)$(includedir)/elfutils + +EXTRA_DIST = libdebuginfod.map +MOSTLYCLEANFILES = $(am_libdebuginfod_pic_a_OBJECTS) libdebuginfod.so.$(VERSION) +CLEANFILES += $(am_libdebuginfod_pic_a_OBJECTS) libdebuginfod.so + +# automake std-options override: arrange to pass LD_LIBRARY_PATH +installcheck-binPROGRAMS: $(bin_PROGRAMS) + bad=0; pid=$$$$; list="$(bin_PROGRAMS)"; for p in $$list; do \ + case ' $(AM_INSTALLCHECK_STD_OPTIONS_EXEMPT) ' in \ + *" $$p "* | *" $(srcdir)/$$p "*) continue;; \ + esac; \ + f=`echo "$$p" | \ + sed 's,^.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/'`; \ + for opt in --help --version; do \ + if LD_LIBRARY_PATH=$(DESTDIR)$(libdir) \ + $(DESTDIR)$(bindir)/$$f $$opt > c$${pid}_.out 2> c$${pid}_.err \ + && test -n "`cat c$${pid}_.out`" \ + && test -z "`cat c$${pid}_.err`"; then :; \ + else echo "$$f does not support $$opt" 1>&2; bad=1; fi; \ + done; \ + done; rm -f c$${pid}_.???; exit $$bad diff --git a/debuginfod/debuginfod-client.c b/debuginfod/debuginfod-client.c new file mode 100644 index 00000000..1aa9edac --- /dev/null +++ b/debuginfod/debuginfod-client.c @@ -0,0 +1,665 @@ +/* Retrieve ELF / DWARF / source files from the debuginfod. + Copyright (C) 2019 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of either + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at + your option) any later version + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at + your option) any later version + + or both in parallel, as here. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see <http://www.gnu.org/licenses/>. */ + + +/* cargo-cult from libdwfl linux-kernel-modules.c */ +/* In case we have a bad fts we include this before config.h because it + can't handle _FILE_OFFSET_BITS. + Everything we need here is fine if its declarations just come first. + Also, include sys/types.h before fts. On some systems fts.h is not self + contained. */ +#ifdef BAD_FTS + #include <sys/types.h> + #include <fts.h> +#endif + +#include "config.h" +#include "debuginfod.h" +#include <assert.h> +#include <dirent.h> +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <fts.h> +#include <string.h> +#include <stdbool.h> +#include <linux/limits.h> +#include <time.h> +#include <utime.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <curl/curl.h> + +/* If fts.h is included before config.h, its indirect inclusions may not + give us the right LFS aliases of these functions, so map them manually. */ +#ifdef BAD_FTS + #ifdef _FILE_OFFSET_BITS + #define open open64 + #define fopen fopen64 + #endif +#else + #include <sys/types.h> + #include <fts.h> +#endif + + +/* The cache_clean_interval_s file within the debuginfod cache specifies + how frequently the cache should be cleaned. The file's st_mtime represents + the time of last cleaning. */ +static const char *cache_clean_interval_filename = "cache_clean_interval_s"; +static const time_t cache_clean_default_interval_s = 86400; /* 1 day */ + +/* The cache_max_unused_age_s file within the debuginfod cache specifies the + the maximum time since last access that a file will remain in the cache. */ +static const char *cache_max_unused_age_filename = "max_unused_age_s"; +static const time_t cache_default_max_unused_age_s = 604800; /* 1 week */ + +/* Location of the cache of files downloaded from debuginfods. + The default parent directory is $HOME, or '/' if $HOME doesn't exist. */ +static const char *cache_default_name = ".debuginfod_client_cache"; +static const char *cache_path_envvar = DEBUGINFOD_CACHE_PATH_ENV_VAR; + +/* URLs of debuginfods, separated by url_delim. + This env var must be set for debuginfod-client to run. */ +static const char *server_urls_envvar = DEBUGINFOD_URLS_ENV_VAR; +static const char *url_delim = " "; +static const char url_delim_char = ' '; + +/* Timeout for debuginfods, in seconds. + This env var must be set for debuginfod-client to run. */ +static const char *server_timeout_envvar = DEBUGINFOD_TIMEOUT_ENV_VAR; +static int server_timeout = 5; + +/* Data associated with a particular CURL easy handle. Passed to + the write callback. */ +struct handle_data +{ + /* Cache file to be written to in case query is successful. */ + int fd; + + /* URL queried by this handle. */ + char url[PATH_MAX]; + + /* This handle. */ + CURL *handle; + + /* Pointer to handle that should write to fd. Initially points to NULL, + then points to the first handle that begins writing the target file + to the cache. Used to ensure that a file is not downloaded from + multiple servers unnecessarily. */ + CURL **target_handle; +}; + +static size_t +debuginfod_write_callback (char *ptr, size_t size, size_t nmemb, void *data) +{ + ssize_t count = size * nmemb; + + struct handle_data *d = (struct handle_data*)data; + + /* Indicate to other handles that they can abort their transfer. */ + if (*d->target_handle == NULL) + *d->target_handle = d->handle; + + /* If this handle isn't the target handle, abort transfer. */ + if (*d->target_handle != d->handle) + return -1; + + return (size_t) write(d->fd, (void*)ptr, count); +} + +/* Create the cache and interval file if they do not already exist. + Return 0 if cache and config file are initialized, otherwise return + the appropriate error code. */ +static int +debuginfod_init_cache (char *cache_path, char *interval_path, char *maxage_path) +{ + struct stat st; + + /* If the cache and config file already exist then we are done. */ + if (stat(cache_path, &st) == 0 && stat(interval_path, &st) == 0) + return 0; + + /* Create the cache and config files as necessary. */ + if (stat(cache_path, &st) != 0 && mkdir(cache_path, 0777) < 0) + return -errno; + + int fd = -1; + + /* init cleaning interval config file. */ + fd = open(interval_path, O_CREAT | O_RDWR, 0666); + if (fd < 0) + return -errno; + + if (dprintf(fd, "%ld", cache_clean_default_interval_s) < 0) + return -errno; + + /* init max age config file. */ + if (stat(maxage_path, &st) != 0 + && (fd = open(maxage_path, O_CREAT | O_RDWR, 0666)) < 0) + return -errno; + + if (dprintf(fd, "%ld", cache_default_max_unused_age_s) < 0) + return -errno; + + return 0; +} + + +/* Delete any files that have been unmodied for a period + longer than $DEBUGINFOD_CACHE_CLEAN_INTERVAL_S. */ +static int +debuginfod_clean_cache(char *cache_path, char *interval_path, char *max_unused_path) +{ + struct stat st; + FILE *interval_file; + FILE *max_unused_file; + + if (stat(interval_path, &st) == -1) + { + /* Create new interval file. */ + interval_file = fopen(interval_path, "w"); + + if (interval_file == NULL) + return -errno; + + int rc = fprintf(interval_file, "%ld", cache_clean_default_interval_s); + fclose(interval_file); + + if (rc < 0) + return -errno; + } + + /* Check timestamp of interval file to see whether cleaning is necessary. */ + time_t clean_interval; + interval_file = fopen(interval_path, "r"); + if (fscanf(interval_file, "%ld", &clean_interval) != 1) + clean_interval = cache_clean_default_interval_s; + fclose(interval_file); + + if (time(NULL) - st.st_mtime < clean_interval) + /* Interval has not passed, skip cleaning. */ + return 0; + + /* Read max unused age value from config file. */ + time_t max_unused_age; + max_unused_file = fopen(max_unused_path, "r"); + if (max_unused_file) + { + if (fscanf(max_unused_file, "%ld", &max_unused_age) != 1) + max_unused_age = cache_default_max_unused_age_s; + fclose(max_unused_file); + } + else + max_unused_age = cache_default_max_unused_age_s; + + char * const dirs[] = { cache_path, NULL, }; + + FTS *fts = fts_open(dirs, 0, NULL); + if (fts == NULL) + return -errno; + + FTSENT *f; + while ((f = fts_read(fts)) != NULL) + { + switch (f->fts_info) + { + case FTS_F: + /* delete file if max_unused_age has been met or exceeded. */ + /* XXX consider extra effort to clean up old tmp files */ + if (time(NULL) - f->fts_statp->st_atime >= max_unused_age) + unlink (f->fts_path); + break; + + case FTS_DP: + /* Remove if empty. */ + (void) rmdir (f->fts_path); + break; + + default: + ; + } + } + fts_close(fts); + + /* Update timestamp representing when the cache was last cleaned. */ + utime (interval_path, NULL); + return 0; +} + + +#define MAX_BUILD_ID_BYTES 64 + + +/* Query each of the server URLs found in $DEBUGINFOD_URLS for the file + with the specified build-id, type (debuginfo, executable or source) + and filename. filename may be NULL. If found, return a file + descriptor for the target, otherwise return an error code. */ +static int +debuginfod_query_server (const unsigned char *build_id, + int build_id_len, + const char *type, + const char *filename, + char **path) +{ + char *urls_envvar; + char *server_urls; + char cache_path[PATH_MAX]; + char maxage_path[PATH_MAX*3]; /* These *3 multipliers are to shut up gcc -Wformat-truncation */ + char interval_path[PATH_MAX*4]; + char target_cache_dir[PATH_MAX*2]; + char target_cache_path[PATH_MAX*4]; + char target_cache_tmppath[PATH_MAX*5]; + char suffix[PATH_MAX*2]; + char build_id_bytes[MAX_BUILD_ID_BYTES * 2 + 1]; + + /* Copy lowercase hex representation of build_id into buf. */ + if ((build_id_len >= MAX_BUILD_ID_BYTES) || + (build_id_len == 0 && + sizeof(build_id_bytes) > MAX_BUILD_ID_BYTES*2 + 1)) + return -EINVAL; + if (build_id_len == 0) /* expect clean hexadecimal */ + strcpy (build_id_bytes, (const char *) build_id); + else + for (int i = 0; i < build_id_len; i++) + sprintf(build_id_bytes + (i * 2), "%02x", build_id[i]); + + if (filename != NULL) + { + if (filename[0] != '/') // must start with / + return -EINVAL; + + /* copy the filename to suffix, s,/,#,g */ + unsigned q = 0; + for (unsigned fi=0; q < PATH_MAX-1; fi++) + switch (filename[fi]) + { + case '\0': + suffix[q] = '\0'; + q = PATH_MAX-1; /* escape for loop too */ + break; + case '/': /* escape / to prevent dir escape */ + suffix[q++]='#'; + suffix[q++]='#'; + break; + case '#': /* escape # to prevent /# vs #/ collisions */ + suffix[q++]='#'; + suffix[q++]='_'; + break; + default: + suffix[q++]=filename[fi]; + } + suffix[q] = '\0'; + /* If the DWARF filenames are super long, this could exceed + PATH_MAX and truncate/collide. Oh well, that'll teach + them! */ + } + else + suffix[0] = '\0'; + + /* set paths needed to perform the query + + example format + cache_path: $HOME/.debuginfod_cache + target_cache_dir: $HOME/.debuginfod_cache/0123abcd + target_cache_path: $HOME/.debuginfod_cache/0123abcd/debuginfo + target_cache_path: $HOME/.debuginfod_cache/0123abcd/source#PATH#TO#SOURCE ? + */ + + if (getenv(cache_path_envvar)) + strcpy(cache_path, getenv(cache_path_envvar)); + else + { + if (getenv("HOME")) + sprintf(cache_path, "%s/%s", getenv("HOME"), cache_default_name); + else + sprintf(cache_path, "/%s", cache_default_name); + } + + /* avoid using snprintf here due to compiler warning. */ + snprintf(target_cache_dir, sizeof(target_cache_dir), "%s/%s", cache_path, build_id_bytes); + snprintf(target_cache_path, sizeof(target_cache_path), "%s/%s%s", target_cache_dir, type, suffix); + snprintf(target_cache_tmppath, sizeof(target_cache_tmppath), "%s.XXXXXX", target_cache_path); + + /* XXX combine these */ + snprintf(interval_path, sizeof(interval_path), "%s/%s", cache_path, cache_clean_interval_filename); + snprintf(maxage_path, sizeof(maxage_path), "%s/%s", cache_path, cache_max_unused_age_filename); + int rc = debuginfod_init_cache(cache_path, interval_path, maxage_path); + if (rc != 0) + goto out; + rc = debuginfod_clean_cache(cache_path, interval_path, maxage_path); + if (rc != 0) + goto out; + + /* If the target is already in the cache then we are done. */ + int fd = open (target_cache_path, O_RDONLY); + if (fd >= 0) + { + /* Success!!!! */ + if (path != NULL) + *path = strdup(target_cache_path); + return fd; + } + + + urls_envvar = getenv(server_urls_envvar); + if (urls_envvar == NULL || urls_envvar[0] == '\0') + { + rc = -ENOSYS; + goto out; + } + + if (getenv(server_timeout_envvar)) + server_timeout = atoi (getenv(server_timeout_envvar)); + + /* make a copy of the envvar so it can be safely modified. */ + server_urls = strdup(urls_envvar); + if (server_urls == NULL) + { + rc = -ENOMEM; + goto out; + } + /* thereafter, goto out0 on error*/ + + /* create target directory in cache if not found. */ + struct stat st; + if (stat(target_cache_dir, &st) == -1 && mkdir(target_cache_dir, 0700) < 0) + { + rc = -errno; + goto out0; + } + + /* NB: write to a temporary file first, to avoid race condition of + multiple clients checking the cache, while a partially-written or empty + file is in there, being written from libcurl. */ + fd = mkstemp (target_cache_tmppath); + if (fd < 0) + { + rc = -errno; + goto out0; + } + + /* Count number of URLs. */ + int num_urls = 0; + for (int i = 0; server_urls[i] != '\0'; i++) + if (server_urls[i] != url_delim_char + && (i == 0 || server_urls[i - 1] == url_delim_char)) + num_urls++; + + /* Tracks which handle should write to fd. Set to the first + handle that is ready to write the target file to the cache. */ + CURL *target_handle = NULL; + struct handle_data *data = malloc(sizeof(struct handle_data) * num_urls); + + /* Initalize handle_data with default values. */ + for (int i = 0; i < num_urls; i++) + { + data[i].handle = NULL; + data[i].fd = -1; + } + + CURLM *curlm = curl_multi_init(); + if (curlm == NULL) + { + rc = -ENETUNREACH; + goto out0; + } + /* thereafter, goto out1 on error. */ + + char *strtok_saveptr; + char *server_url = strtok_r(server_urls, url_delim, &strtok_saveptr); + + /* Initialize each handle. */ + for (int i = 0; i < num_urls && server_url != NULL; i++) + { + data[i].fd = fd; + data[i].target_handle = &target_handle; + data[i].handle = curl_easy_init(); + + if (data[i].handle == NULL) + { + rc = -ENETUNREACH; + goto out1; + } + + /* Build handle url. Tolerate both http://foo:999 and + http://foo:999/ forms */ + char *slashbuildid; + if (strlen(server_url) > 1 && server_url[strlen(server_url)-1] == '/') + slashbuildid = "buildid"; + else + slashbuildid = "/buildid"; + + if (filename) /* must start with / */ + snprintf(data[i].url, PATH_MAX, "%s%s/%s/%s%s", server_url, + slashbuildid, build_id_bytes, type, filename); + else + snprintf(data[i].url, PATH_MAX, "%s%s/%s/%s", server_url, + slashbuildid, build_id_bytes, type); + + curl_easy_setopt(data[i].handle, CURLOPT_URL, data[i].url); + curl_easy_setopt(data[i].handle, + CURLOPT_WRITEFUNCTION, + debuginfod_write_callback); + curl_easy_setopt(data[i].handle, CURLOPT_WRITEDATA, (void*)&data[i]); + curl_easy_setopt(data[i].handle, CURLOPT_TIMEOUT, (long) server_timeout); + curl_easy_setopt(data[i].handle, CURLOPT_FILETIME, (long) 1); + curl_easy_setopt(data[i].handle, CURLOPT_FOLLOWLOCATION, (long) 1); + curl_easy_setopt(data[i].handle, CURLOPT_FAILONERROR, (long) 1); + curl_easy_setopt(data[i].handle, CURLOPT_NOSIGNAL, (long) 1); + curl_easy_setopt(data[i].handle, CURLOPT_AUTOREFERER, (long) 1); + curl_easy_setopt(data[i].handle, CURLOPT_ACCEPT_ENCODING, ""); + curl_easy_setopt(data[i].handle, CURLOPT_USERAGENT, (void*) PACKAGE_STRING); + + curl_multi_add_handle(curlm, data[i].handle); + server_url = strtok_r(NULL, url_delim, &strtok_saveptr); + } + + /* Query servers in parallel. */ + int still_running; + do + { + CURLMcode curl_res; + + /* Wait 1 second, the minimum DEBUGINFOD_TIMEOUT. */ + curl_multi_wait(curlm, NULL, 0, 1000, NULL); + + /* If the target file has been found, abort the other queries. */ + if (target_handle != NULL) + for (int i = 0; i < num_urls; i++) + if (data[i].handle != target_handle) + curl_multi_remove_handle(curlm, data[i].handle); + + curl_res = curl_multi_perform(curlm, &still_running); + if (curl_res != CURLM_OK) + { + switch (curl_res) + { + case CURLM_CALL_MULTI_PERFORM: continue; + case CURLM_OUT_OF_MEMORY: rc = -ENOMEM; break; + default: rc = -ENETUNREACH; break; + } + goto out1; + } + } while (still_running); + + /* Check whether a query was successful. If so, assign its handle + to verified_handle. */ + int num_msg; + rc = -ENOENT; + CURL *verified_handle = NULL; + do + { + CURLMsg *msg; + + msg = curl_multi_info_read(curlm, &num_msg); + if (msg != NULL && msg->msg == CURLMSG_DONE) + { + if (msg->data.result != CURLE_OK) + { + /* Unsucessful query, determine error code. */ + switch (msg->data.result) + { + case CURLE_COULDNT_RESOLVE_HOST: rc = -EHOSTUNREACH; break; // no NXDOMAIN + case CURLE_URL_MALFORMAT: rc = -EINVAL; break; + case CURLE_COULDNT_CONNECT: rc = -ECONNREFUSED; break; + case CURLE_REMOTE_ACCESS_DENIED: rc = -EACCES; break; + case CURLE_WRITE_ERROR: rc = -EIO; break; + case CURLE_OUT_OF_MEMORY: rc = -ENOMEM; break; + case CURLE_TOO_MANY_REDIRECTS: rc = -EMLINK; break; + case CURLE_SEND_ERROR: rc = -ECONNRESET; break; + case CURLE_RECV_ERROR: rc = -ECONNRESET; break; + case CURLE_OPERATION_TIMEDOUT: rc = -ETIME; break; + default: rc = -ENOENT; break; + } + } + else + { + /* Query completed without an error. Confirm that the + response code is 200 and set verified_handle. */ + long resp_code = 500; + CURLcode curl_res; + + curl_res = curl_easy_getinfo(target_handle, + CURLINFO_RESPONSE_CODE, + &resp_code); + + if (curl_res == CURLE_OK + && resp_code == 200 + && msg->easy_handle != NULL) + { + verified_handle = msg->easy_handle; + break; + } + } + } + } while (num_msg > 0); + + if (verified_handle == NULL) + goto out1; + + /* we've got one!!!! */ + time_t mtime; + CURLcode curl_res = curl_easy_getinfo(verified_handle, CURLINFO_FILETIME, (void*) &mtime); + if (curl_res != CURLE_OK) + mtime = time(NULL); /* fall back to current time */ + + struct timeval tvs[2]; + tvs[0].tv_sec = tvs[1].tv_sec = mtime; + tvs[0].tv_usec = tvs[1].tv_usec = 0; + (void) futimes (fd, tvs); /* best effort */ + + /* rename tmp->real */ + rc = rename (target_cache_tmppath, target_cache_path); + if (rc < 0) + { + rc = -errno; + goto out1; + /* Perhaps we need not give up right away; could retry or something ... */ + } + + /* Success!!!! */ + for (int i = 0; i < num_urls; i++) + curl_easy_cleanup(data[i].handle); + + curl_multi_cleanup (curlm); + free (data); + free (server_urls); + /* don't close fd - we're returning it */ + /* don't unlink the tmppath; it's already been renamed. */ + if (path != NULL) + *path = strdup(target_cache_path); + + return fd; + +/* error exits */ + out1: + for (int i = 0; i < num_urls; i++) + curl_easy_cleanup(data[i].handle); + + curl_multi_cleanup(curlm); + unlink (target_cache_tmppath); + (void) rmdir (target_cache_dir); /* nop if not empty */ + free(data); + close (fd); + + out0: + free (server_urls); + + out: + return rc; +} + + +/* See debuginfod.h */ +int +debuginfod_find_debuginfo (const unsigned char *build_id, int build_id_len, + char **path) +{ + return debuginfod_query_server(build_id, build_id_len, + "debuginfo", NULL, path); +} + + +/* See debuginfod.h */ +int +debuginfod_find_executable(const unsigned char *build_id, int build_id_len, + char **path) +{ + return debuginfod_query_server(build_id, build_id_len, + "executable", NULL, path); +} + +/* See debuginfod.h */ +int debuginfod_find_source(const unsigned char *build_id, + int build_id_len, + const char *filename, + char **path) +{ + return debuginfod_query_server(build_id, build_id_len, + "source", filename, path); +} + + + +/* NB: these are thread-unsafe. */ +__attribute__((constructor)) attribute_hidden void libdebuginfod_ctor(void) +{ + curl_global_init(CURL_GLOBAL_DEFAULT); +} + +/* NB: this is very thread-unsafe: it breaks other threads that are still in libcurl */ +__attribute__((destructor)) attribute_hidden void libdebuginfod_dtor(void) +{ + /* ... so don't do this: */ + /* curl_global_cleanup(); */ +} diff --git a/debuginfod/debuginfod-find.c b/debuginfod/debuginfod-find.c new file mode 100644 index 00000000..ff47b807 --- /dev/null +++ b/debuginfod/debuginfod-find.c @@ -0,0 +1,101 @@ +/* Command-line frontend for retrieving ELF / DWARF / source files + from the debuginfod. + Copyright (C) 2019 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see <http://www.gnu.org/licenses/>. */ + +#include "config.h" +#include "printversion.h" +#include "debuginfod.h" +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <argp.h> + + +/* Name and version of program. */ +ARGP_PROGRAM_VERSION_HOOK_DEF = print_version; + +/* Bug report address. */ +ARGP_PROGRAM_BUG_ADDRESS_DEF = PACKAGE_BUGREPORT; + +/* Short description of program. */ +static const char doc[] = N_("Request debuginfo-related content " + "from debuginfods listed in $" DEBUGINFOD_URLS_ENV_VAR "."); + +/* Strings for arguments in help texts. */ +static const char args_doc[] = N_("debuginfo BUILDID\n" + "executable BUILDID\n" + "source BUILDID /FILENAME"); + +/* Data structure to communicate with argp functions. */ +static struct argp argp = + { + NULL, NULL, args_doc, doc, NULL, NULL, NULL + }; + + + +int +main(int argc, char** argv) +{ + int remaining; + (void) argp_parse (&argp, argc, argv, ARGP_IN_ORDER|ARGP_NO_ARGS, &remaining, NULL); + + if (argc < 2 || remaining+1 == argc) /* no arguments or at least two non-option words */ + { + argp_help (&argp, stderr, ARGP_HELP_USAGE, argv[0]); + return 1; + } + + int rc; + char *cache_name; + + /* Check whether FILETYPE is valid and call the appropriate + debuginfod_find_* function. If FILETYPE is "source" + then ensure a FILENAME was also supplied as an argument. */ + if (strcmp(argv[remaining], "debuginfo") == 0) + rc = debuginfod_find_debuginfo((unsigned char *)argv[remaining+1], 0, &cache_name); + else if (strcmp(argv[remaining], "executable") == 0) + rc = debuginfod_find_executable((unsigned char *)argv[remaining+1], 0, &cache_name); + else if (strcmp(argv[remaining], "source") == 0) + { + if (remaining+2 == argc || argv[3][0] != '/') + { + fprintf(stderr, "If FILETYPE is \"source\" then absolute /FILENAME must be given\n"); + return 1; + } + rc = debuginfod_find_source((unsigned char *)argv[remaining+1], 0, + argv[remaining+2], &cache_name); + } + else + { + argp_help (&argp, stderr, ARGP_HELP_USAGE, argv[0]); + return 1; + } + + if (rc < 0) + { + fprintf(stderr, "Server query failed: %s\n", strerror(-rc)); + return 1; + } + + printf("%s\n", cache_name); + + free (cache_name); + return 0; +} diff --git a/debuginfod/debuginfod.h b/debuginfod/debuginfod.h new file mode 100644 index 00000000..d2bb0c94 --- /dev/null +++ b/debuginfod/debuginfod.h @@ -0,0 +1,69 @@ +/* External declarations for the libdebuginfod client library. + Copyright (C) 2019 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of either + + * the GNU Lesser General Public License as published by the Free + Software Foundation; either version 3 of the License, or (at + your option) any later version + + or + + * the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at + your option) any later version + + or both in parallel, as here. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received copies of the GNU General Public License and + the GNU Lesser General Public License along with this program. If + not, see <http://www.gnu.org/licenses/>. */ + +#ifndef _DEBUGINFOD_CLIENT_H +#define _DEBUGINFOD_CLIENT_H 1 + +/* Names of environment variables that control the client logic. */ +#define DEBUGINFOD_URLS_ENV_VAR "DEBUGINFOD_URLS" +#define DEBUGINFOD_CACHE_PATH_ENV_VAR "DEBUGINFOD_CACHE_PATH" +#define DEBUGINFOD_TIMEOUT_ENV_VAR "DEBUGINFOD_TIMEOUT" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Query the urls contained in $DEBUGINFOD_URLS for a file with + the specified type and build id. If build_id_len == 0, the + build_id is supplied as a lowercase hexadecimal string; otherwise + it is a binary blob of given legnth. + + If successful, return a file descriptor to the target, otherwise + return a posix error code. If successful, set *path to a + strdup'd copy of the name of the same file in the cache. + Caller must free() it later. */ + +int debuginfod_find_debuginfo (const unsigned char *build_id, + int build_id_len, + char **path); + +int debuginfod_find_executable (const unsigned char *build_id, + int build_id_len, + char **path); + +int debuginfod_find_source (const unsigned char *build_id, + int build_id_len, + const char *filename, + char **path); + +#ifdef __cplusplus +} +#endif + + +#endif /* _DEBUGINFOD_CLIENT_H */ diff --git a/debuginfod/libdebuginfod.map b/debuginfod/libdebuginfod.map new file mode 100644 index 00000000..4d3daf32 --- /dev/null +++ b/debuginfod/libdebuginfod.map @@ -0,0 +1,7 @@ +ELFUTILS_0 { }; +ELFUTILS_0.178 { + global: + debuginfod_find_debuginfo; + debuginfod_find_executable; + debuginfod_find_source; +} ELFUTILS_0; |
