This is the mail archive of the
elfutils-devel@sourceware.org
mailing list for the elfutils project.
Re: patch 1/2 debuginfod client
Hi,
On Mon, 2019-10-28 at 15:06 -0400, Frank Ch. Eigler wrote:
> Introduce the debuginfod/ subdirectory, containing the client for a
> new debuginfo-over-http service, in shared-library and command-line
> forms. Two functions in libdwfl make calls into the client library to
> fetch elf/dwarf files by buildid, as a fallback. Instead of normal
> dynamic linking (thus pulling in a variety of curl dependencies),
> the libdwfl hooks use dlopen/dlsym. Server & tests coming in patch 2.
And a bit more review. The debuginfod subdir in this case.
diff --git a/debuginfod/ChangeLog b/debuginfod/ChangeLog
> new file mode 100644
> index 000000000000..1a31cf6f4e27
> --- /dev/null
> +++ b/debuginfod/ChangeLog
> @@ -0,0 +1,9 @@
> +2019-10-28 Aaron Merey <amerey@redhat.com>
> +
> + * 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 000000000000..3a4e94da2ad0
> --- /dev/null
> +++ b/debuginfod/Makefile.am
> @@ -0,0 +1,116 @@
> +## 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
I am not sure the BUILD_STATIC support is really tested and works. But
it looks correct. It is used when building with profiling code (
--enable-gcov).
> +AM_LDFLAGS = -Wl,-rpath-link,../libelf:../libdw:.
> +
> +bin_PROGRAMS = debuginfod-find
> +dist_man3_MANS = debuginfod_find_debuginfo.3 debuginfod_find_source.3 debuginfod_find_executable.3
> +dist_man1_MANS = debuginfod-find.1
Hurrah! Documentation! Thanks.
But given that all other documentation is under doc/ could you move it
there (guarded by DEBUGINFOD). It is just more consistent. If you leave
it in this subdir I think you should also move the existing
documentation files (and I think that is not worth it at the moment).
> +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)
Nice, properly versioned library.
> +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
Yeah, unfortunate dance. Looks correct though.
> +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
I see we also do this in src/Makefile.am but, ehe, why?
> diff --git a/debuginfod/debuginfod-client.c b/debuginfod/debuginfod-client.c
> new file mode 100644
> index 000000000000..2b91bb8bb1d2
> --- /dev/null
> +++ b/debuginfod/debuginfod-client.c
> @@ -0,0 +1,624 @@
> +/* 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/>. */
> +
> +#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>
O, o... older (glibc) fts is problematic on 32bit systems when using
large file offsets. See libdwfl/linux-kernel-modules.c. See the BAD_FTS
check in configure.ac
Older glibc had a broken fts that didn't work with Large File Systems.
We want the version that can handler LFS, but include workaround if we
get a bad one. Add define to CFLAGS (not AC_DEFINE it) since we need to
check it before including config.h (which might define _FILE_OFFSET_BITS).
Have you tried to build and run on i686 on Debian stable or CentOS7?
> +#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>
> +
> +static const int max_build_id_bytes = 256; /* typical: 40 for gnu C toolchain */
> +
> +
> +/* 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 = 600;
> +
> +/* 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 = 600;
> +
> +/* 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];
Are you sure that is the correct limit?
Could it be made dynamic?
> + /* 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;
> +}
> +
> +/* 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. */
You don't describe PATH. I assume the caller is responsible for freeing
it? Is it set on error?
> +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*3];
> + char target_cache_tmppath[PATH_MAX*4];
> + char suffix[PATH_MAX];
Ah, lots more uses of PATH_MAX. Now my worry is kind of the opposite.
These are all stack allocated. Won't that blow up the stack?
> + 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;
I wonder if you also want to check for a build_id that is simply too
small. Since build_id are supposed to be globally unique it doesn't
really make sense to have a build_id length that is too small. libdwfl
for example needs at least 3 bytes:
/* We don't handle very short or really large build-ids. We need at
at least 3 and allow for up to 64 (normally ids are 20 long). */
#define MIN_BUILD_ID_BYTES 3
#define MAX_BUILD_ID_BYTES 64
> + 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]);
I would sanity check the "clean hexadecimal" == 0 case.
> + unsigned q = 0;
> + if (filename != NULL)
> + {
> + if (filename[0] != '/') // must start with /
> + return -EINVAL;
> +
> + /* copy the filename to suffix, s,/,#,g */
> + for (q=0; q<sizeof(suffix)-1; q++)
> + {
> + if (filename[q] == '\0') break;
> + if (filename[q] == '/' || filename[q] == '.') suffix[q] = '#';
> + else suffix[q] = filename[q];
> + }
> + /* XXX: if we had a CURL* handle at this time, we could
> + curl_easy_escape() to url-escape the filename in a
> + collision-free, reversible manner. */
> + }
> + suffix[q] = '\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);
I don't understand the comment, you are actually using snprintf?
> + /* 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
> + && (server_urls[i - 1] == url_delim_char || i == 0))
> + 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_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);
Why not actually use DEBUGINFOD_TIMEOUT?
> + /* 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;
OK, looks like both PATHs where *path is set you return a zero or
positive value.
> +/* 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;
> +}
Question about writing/creating and removal of target_cache.
It seems they rely on an environment variable. Can a user trick this
call into overwriting some existing files? Are you scrubbing all paths
of things like ../ ?
Just a bit concerned about weird paths, file names, URLs being set
accidentally and the wrong files being over-written/removed.
> +/* 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);
> +}
How does this interact with a program that uses libcurl itself and also
links with libdebuginfod?
> +/* 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(); */
> +}
yeah, so something leaks, o well.
> diff --git a/debuginfod/debuginfod-find.1 b/debuginfod/debuginfod-find.1
> new file mode 100644
> index 000000000000..6d2251662a57
> --- /dev/null
> +++ b/debuginfod/debuginfod-find.1
> @@ -0,0 +1,131 @@
> +'\"! tbl | nroff \-man
> +'\" t macro stdmacro
> +
> +.de SAMPLE
> +.br
> +.RS 0
> +.nf
> +.nh
> +..
> +.de ESAMPLE
> +.hy
> +.fi
> +.RE
> +..
> +
> +.TH DEBUGINFOD-FIND 1
> +.SH NAME
> +debuginfod-find \- request debuginfo-related data
> +
> +.SH SYNOPSIS
> +.B debuginfod-find debuginfo \fIBUILDID\fP
> +
> +.B debuginfod-find executable \fIBUILDID\fP
> +
> +.B debuginfod-find source \fIBUILDID\fP \fI/FILENAME\fP
> +
> +.SH DESCRIPTION
> +\fBdebuginfod-find\fP queries one or more \fBdebuginfod\fP servers for
> +debuginfo-related data. In case of a match, it saves the the
> +requested file into a local cache, prints the file name to standard
> +output, and exits with a success status of 0. In case of any error,
> +it exits with a failure status and an error message to standard error.
> +
> +.\" Much of the following text is duplicated with debuginfod.8
> +
> +The debuginfod system uses buildids to identify debuginfo-related data.
> +These are stored as binary notes in ELF/DWARF files, and are
> +represented as lowercase hexadecimal. For example, for a program
> +/bin/ls, look at the ELF note GNU_BUILD_ID:
> +
> +.SAMPLE
> +% readelf -n /bin/ls | grep -A4 build.id
> +Note section [ 4] '.note.gnu.buildid' of 36 bytes at offset 0x340:
> +Owner Data size Type
> +GNU 20 GNU_BUILD_ID
> +Build ID: 8713b9c3fb8a720137a4a08b325905c7aaf8429d
> +.ESAMPLE
> +
> +Then the hexadecimal BUILDID is simply:
> +
> +.SAMPLE
> +8713b9c3fb8a720137a4a08b325905c7aaf8429d
> +.ESAMPLE
> +
> +.SS debuginfo \fIBUILDID\fP
> +
> +If the given buildid is known to a server, this request will result
> +in a binary object that contains the customary \fB.*debug_*\fP
> +sections. This may be a split debuginfo file as created by
> +\fBstrip\fP, or it may be an original unstripped executable.
> +
> +.SS executable \fIBUILDID\fP
> +
> +If the given buildid is known to the server, this request will result
> +in a binary object that contains the normal executable segments. This
> +may be a executable stripped by \fBstrip\fP, or it may be an original
> +unstripped executable. \fBET_DYN\fP shared libraries are considered
> +to be a type of executable.
> +
> +.SS source \fIBUILDID\fP \fI/SOURCE/FILE\fP
> +
> +If the given buildid is known to the server, this request will result
> +in a binary object that contains the source file mentioned. The path
> +should be absolute. Relative path names commonly appear in the DWARF
> +file's source directory, but these paths are relative to
> +individual compilation unit AT_comp_dir paths, and yet an executable
> +is made up of multiple CUs. Therefore, to disambiguate, debuginfod
> +expects source queries to prefix relative path names with the CU
> +compilation-directory.
> +
> +Note: you should not elide \fB../\fP or \fB/./\fP sorts of relative
> +path components in the directory names, because if this is how those
> +names appear in the DWARF files, that is what debuginfod needs to see
> +too.
> +
> +For example:
> +.TS
> +l l.
> +#include <stdio.h> source BUILDID /usr/include/stdio.h
> +/path/to/foo.c source BUILDID /path/to/foo.c
> +\../bar/foo.c AT_comp_dir=/zoo source BUILDID /zoo/../bar/foo.c
> +.TE
Good that you give an example. This somewhat ties into my question
above. So you don't scrub /../ normally. I am still somewhat worried
about bogus paths to go outside of what is expected.
> +.SH "SECURITY"
> +
> +debuginfod-find \fBdoes not\fP include any particular security
> +features. It trusts that the binaries returned by the debuginfod(s)
> +are accurate. Therefore, the list of servers should include only
> +trustworthy ones. If accessed across HTTP rather than HTTPS, the
> +network should be trustworthy.
I assume libcurl handles tls certificates for https? Does that need any
mention here?
> +.SH "ENVIRONMENT VARIABLES"
> +
> +.TP 21
> +.B DEBUGINFOD_URLS
> +This environment variable contains a list of URL prefixes for trusted
> +debuginfod instances. Alternate URL prefixes are separated by space.
> +
> +.TP 21
> +.B DEBUGINFOD_TIMEOUT
> +This environment variable governs the timeout for each debuginfod HTTP
> +connection. A server that fails to respond within this many seconds
> +is skipped. The default is 5.
> +
> +.TP 21
> +.B DEBUGINFOD_CACHE_PATH
> +This environment variable governs the location of the cache where
> +downloaded files are kept. It is cleaned periodically as this
> +program is reexecuted. The default is $HOME/.debuginfod_client_cache.
> +.\" XXX describe cache eviction policy
> +
> +.SH "FILES"
> +.LP
> +.PD .1v
> +.TP 20
> +.B $HOME/.debuginfod_client_cache
> +Default cache directory.
> +.PD
> +
> +.SH "SEE ALSO"
> +.I "debuginfod(8)"
See note above (and below) about where to put this doc file.
> diff --git a/debuginfod/debuginfod-find.c b/debuginfod/debuginfod-find.c
> new file mode 100644
> index 000000000000..78322fc6cd29
> --- /dev/null
> +++ b/debuginfod/debuginfod-find.c
> @@ -0,0 +1,108 @@
> +/* 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 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/>. */
For standalone tools, like those in src, we normally just use GPLv3+.
> +#include "config.h"
> +#include "printversion.h"
> +#include "debuginfod.h"
> +#include <errno.h>
> +#include <stdio.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;
> + }
Is there any way we can get/print the actual URL tried here?
That would really help the user trying to figure out what happened.
> + printf("%s\n", cache_name);
> + return 0;
> +}
> diff --git a/debuginfod/debuginfod.h b/debuginfod/debuginfod.h
> new file mode 100644
> index 000000000000..c268ffebcdb6
> --- /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 _LIBBGSERVER_CLIENT_H
> +#define _LIBBGSERVER_CLIENT_H 1
Not that it really matters, but should that be _LIBDEBUGINFOD_CLIENT_H?
> +
> +/* 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
You can also use __BEGIN_DECLS
> +
> +/* 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
and __END_DECLS here?
> +
> +#endif /* _LIBBGSERVER_CLIENT_H */
_LIBDEBUGINFOD_CLIENT_H here too?
> diff --git a/debuginfod/debuginfod_find_debuginfo.3 b/debuginfod/debuginfod_find_debuginfo.3
> new file mode 100644
> index 000000000000..18caec5576ba
> --- /dev/null
> +++ b/debuginfod/debuginfod_find_debuginfo.3
> @@ -0,0 +1,173 @@
> +'\"! tbl | nroff \-man
> +'\" t macro stdmacro
> +
> +.de SAMPLE
> +.br
> +.RS 0
> +.nf
> +.nh
> +..
> +.de ESAMPLE
> +.hy
> +.fi
> +.RE
> +..
> +
> +.TH DEBUGINFOD_FIND_DEBUGINFO 3
> +.SH NAME
> +debuginfod_find_debuginfo \- request debuginfo from debuginfod
> +
> +.SH SYNOPSIS
> +.nf
> +.B #include <elfutils/debuginfod.h>
> +.PP
> +.BI "debuginfod_find_debuginfo(const unsigned char *" build_id ", int " build_id_len ", char ** " path ");"
> +.BI "debuginfod_find_executable(const unsigned char *" build_id ", int " build_id_len ", char ** " path ");"
> +.BI "debuginfod_find_source(const unsigned char *" build_id ", int " build_id_len ", const char *" filename ", char ** " path ");"
> +
> +.SH DESCRIPTION
> +.BR debuginfod_find_debuginfo (),
> +.BR debuginfod_find_executable (),
> +and
> +.BR debuginfod_find_source ()
> +query the debuginfod server URLs contained in
> +.BR $DEBUGINFOD_URLS
> +(see below) for the debuginfo, executable or source file with the
> +given \fIbuild_id\fP. \fIbuild_id\fP should be a pointer to either
> +a null-terminated, lowercase hex string or a binary blob. If
> +\fIbuild_id\fP is given as a hex string, \fIbuild_id_len\fP should
> +be 0. Otherwise \fIbuild_id_len\fP should be the number of bytes in
> +the binary blob.
> +
> +.BR debuginfod_find_source ()
> +also requries a \fIfilename\fP in order to specify a particular
> +source file. \fIfilename\fP should be an absolute path that includes
> +the compilation directory of the CU associated with the source file.
> +Relative path names commonly appear in the DWARF file's source directory,
> +but these paths are relative to individual compilation unit AT_comp_dir
> +paths, and yet an executable is made up of multiple CUs. Therefore, to
> +disambiguate, debuginfod expects source queries to prefix relative path
> +names with the CU compilation-directory.
> +
> +If \fIpath\fP is not NULL and the query is successful, \fIpath\fP is set
> +to the path of the file in the cache. The caller must \fBfree\fP() this value.
Ah, good, path is mentioned here. Caller owns it.
> +The URLs in \fB$DEBUGINFOD_URLS\fP are queried in parallel. As soon as a
> +debuginfod server begins transfering the target file all of the connections
> +to the other servers are closed.
> +
> +.SH "CACHE"
> +If the query is successful, the \fBdebuginfod_find_*\fP() functions save
> +the target file to a local cache. The location of the cache is controlled
> +by the \fB$DEBUGINFOD_CACHE_PATH\fP environment variable (see below).
> +Cleaning of the cache is controlled by the \fIcache_clean_interval_s\fP
> +and \fImax_unused_age_s\fP files, which are found in the
> +\fB$DEBUGINFOD_CACHE_PATH\fP directory. \fIcache_clean_interval_s\fP controls
> +how frequently the cache is traversed for cleaning and \fImax_unused_age_s\fP
> +controls how long a file can go unused before its removed from the cache
> +during cleaning. These files should contain only an ASCII integer
> +representing the interval or max unused age in seconds. The files contain a
> +default value of 600.
> +
> +.SH "SECURITY"
> +.BR debuginfod_find_debuginfo (),
> +.BR debuginfod_find_executable (),
> +and
> +.BR debuginfod_find_source ()
> +\fBdo not\fP include any particular security
> +features. They trust that the binaries returned by the debuginfod(s)
> +are accurate. Therefore, the list of servers should include only
> +trustworthy ones. If accessed across HTTP rather than HTTPS, the
> +network should be trustworthy.
> +
> +.SH "ENVIRONMENT VARIABLES"
> +
> +.TP 21
> +.B DEBUGINFOD_URLS
> +This environment variable contains a list of URL prefixes for trusted
> +debuginfod instances. Alternate URL prefixes are separated by space.
> +
> +.TP 21
> +.B DEBUGINFOD_TIMEOUT
> +This environment variable governs the timeout for each debuginfod HTTP
> +connection. A server that fails to respond within this many seconds
> +is skipped. The default is 5.
zero isn't allowed?
> +.TP 21
> +.B DEBUGINFOD_CACHE_PATH
> +This environment variable governs the location of the cache where
> +downloaded files are kept. It is cleaned periodically as this
> +program is reexecuted. The default is $HOME/.debuginfod_client_cache.
> +
> +.SH "RETURN VALUE"
> +If the query is successful, these functions save the target file
> +to the client cache and return a file descriptor to that file.
> +Otherwise an error code is returned.
> +
> +.SH "ERRORS"
> +The following list is not comprehensive. Error codes may also
> +originate from calls to various C Library functions.
> +
> +.TP
> +.BR EACCESS
> +Denied access to resource located at the URL.
> +
> +.TP
> +.BR ECONNREFUSED
> +Unable to connect to remote host.
> +
> +.TP
> +.BR ECONNRESET
> +Unable to either send or recieve network data.
> +
> +.TP
> +.BR EHOSTUNREACH
> +Unable to resolve remote host.
> +
> +.TP
> +.BR EINVAL
> +One or more arguments are incorrectly formatted. \fIbuild_id\fP may
> +be too long (greater than 256 characters), \fIfilename\fP may not
> +be an absolute path or a debuginfod URL is malformed.
> +
> +.TP
> +.BR EIO
> +Unable to write data received from server to local file.
> +
> +.TP
> +.BR EMLINK
> +Too many HTTP redirects.
> +
> +.TP
> +.BR ENETUNREACH
> +Unable to initialize network connection.
> +
> +.TP
> +.BR ENOENT
> +Could not find the resource located at URL. Often this error code
> +indicates that a debuginfod server was successfully contacted but
> +the server could not find the target file.
> +
> +.TP
> +.BR ENOMEM
> +System is unable to allocate resources.
> +
> +.TP
> +.BR ENOSYS
> +\fB$DEBUGINFOD_URLS\fP is not defined.
> +
> +.TP
> +.BR ETIME
> +Query failed due to timeout. \fB$DEBUGINFOD_TIMEOUT\fP controls
> +the timeout duration. See debuginfod(8) for more information.
> +
> +.SH "FILES"
> +.LP
> +.PD .1v
> +.TP 20
> +.B $HOME/.debuginfod_client_cache
> +Default cache directory.
> +.PD
> +
> +.SH "SEE ALSO"
> +.I "debuginfod(8)"
> diff --git a/debuginfod/debuginfod_find_executable.3 b/debuginfod/debuginfod_find_executable.3
> new file mode 100644
> index 000000000000..16279936e2ea
> --- /dev/null
> +++ b/debuginfod/debuginfod_find_executable.3
> @@ -0,0 +1 @@
> +.so man3/debuginfod_find_debuginfo.3
> diff --git a/debuginfod/debuginfod_find_source.3 b/debuginfod/debuginfod_find_source.3
> new file mode 100644
> index 000000000000..16279936e2ea
> --- /dev/null
> +++ b/debuginfod/debuginfod_find_source.3
> @@ -0,0 +1 @@
> +.so man3/debuginfod_find_debuginfo.3
So these all look good, but like I said above, I think they should move
into the doc/ subdir.
> diff --git a/debuginfod/libdebuginfod.map b/debuginfod/libdebuginfod.map
> new file mode 100644
> index 000000000000..50d6fbaec639
> --- /dev/null
> +++ b/debuginfod/libdebuginfod.map
> @@ -0,0 +1,7 @@
> +ELFUTILS_0 { };
> +ELFUTILS_0.177 {
> + global:
> + debuginfod_find_debuginfo;
> + debuginfod_find_executable;
> + debuginfod_find_source;
> +} ELFUTILS_0;
Make that ELFUTILS_0.178 since that is the first version where they are
added.
Thanks,
Mark