patch 1/2 debuginfod client

Mark Wielaard mark@klomp.org
Tue Nov 12 21:25:00 GMT 2019


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



More information about the Elfutils-devel mailing list