patch 1/2 debuginfod client

Frank Ch. Eigler fche@redhat.com
Mon Oct 28 19:06:00 GMT 2019


From 3e1f8d93569004d06902459d84baceb636e139d5 Mon Sep 17 00:00:00 2001
From: Aaron Merey <amerey@redhat.com>
Date: Mon, 28 Oct 2019 13:29:26 -0400
Subject: [PATCH 1/2] debuginfod 1/2: client side

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.

Signed-off-by: Aaron Merey <amerey@redhat.com>
Signed-off-by: Frank Ch. Eigler <fche@redhat.com>
---
 ChangeLog                               |   6 +
 Makefile.am                             |   4 +
 config/Makefile.am                      |   2 +-
 config/libdebuginfod.pc.in              |  12 +
 configure.ac                            |  23 +-
 debuginfod/ChangeLog                    |   9 +
 debuginfod/Makefile.am                  | 116 +++++
 debuginfod/debuginfod-client.c          | 624 ++++++++++++++++++++++++
 debuginfod/debuginfod-find.1            | 131 +++++
 debuginfod/debuginfod-find.c            | 108 ++++
 debuginfod/debuginfod.h                 |  69 +++
 debuginfod/debuginfod_find_debuginfo.3  | 173 +++++++
 debuginfod/debuginfod_find_executable.3 |   1 +
 debuginfod/debuginfod_find_source.3     |   1 +
 debuginfod/libdebuginfod.map            |   7 +
 libdw/ChangeLog                         |   4 +
 libdw/Makefile.am                       |   2 +-
 libdwfl/ChangeLog                       |   6 +
 libdwfl/Makefile.am                     |   3 +-
 libdwfl/dwfl_build_id_find_elf.c        |  30 +-
 libdwfl/find-debuginfo.c                |  31 +-
 m4/ChangeLog                            |   4 +
 m4/Makefile.am                          |   2 +-
 m4/ax_check_compile_flag.m4             |  74 +++
 m4/ax_cxx_compile_stdcxx.m4             | 556 +++++++++++++++++++++
 25 files changed, 1989 insertions(+), 9 deletions(-)
 create mode 100644 config/libdebuginfod.pc.in
 create mode 100644 debuginfod/ChangeLog
 create mode 100644 debuginfod/Makefile.am
 create mode 100644 debuginfod/debuginfod-client.c
 create mode 100644 debuginfod/debuginfod-find.1
 create mode 100644 debuginfod/debuginfod-find.c
 create mode 100644 debuginfod/debuginfod.h
 create mode 100644 debuginfod/debuginfod_find_debuginfo.3
 create mode 100644 debuginfod/debuginfod_find_executable.3
 create mode 100644 debuginfod/debuginfod_find_source.3
 create mode 100644 debuginfod/libdebuginfod.map
 create mode 100644 m4/ax_check_compile_flag.m4
 create mode 100644 m4/ax_cxx_compile_stdcxx.m4

diff --git a/ChangeLog b/ChangeLog
index 911cf35432c9..4f33657df976 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+2019-10-28  Aaron Merey  <amerey@redhat.com>
+
+	* debuginfod/: New directory for debuginfod code.
+	* Makefile.am (SUBDIRS): Recurse there.
+	* configure.ac (--enable-debuginfod): New flag & checks.
+
 2019-07-05  Omar Sandoval  <osandov@fb.com>
 
 	* configure.ac: Get rid of --enable-libebl-subdir.
diff --git a/Makefile.am b/Makefile.am
index 52f64fc904f4..bd8926b52344 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -29,6 +29,10 @@ pkginclude_HEADERS = version.h
 SUBDIRS = config m4 lib libelf libcpu backends libebl libdwelf libdwfl libdw \
 	  libasm src po doc tests
 
+if DEBUGINFOD
+SUBDIRS += debuginfod
+endif
+
 EXTRA_DIST = elfutils.spec GPG-KEY NOTES CONTRIBUTING \
 	     COPYING COPYING-GPLV2 COPYING-LGPLV3
 
diff --git a/config/Makefile.am b/config/Makefile.am
index 9d292cee66c8..6425718efc54 100644
--- a/config/Makefile.am
+++ b/config/Makefile.am
@@ -32,7 +32,7 @@ EXTRA_DIST = elfutils.spec.in known-dwarf.awk 10-default-yama-scope.conf
 	     libelf.pc.in libdw.pc.in
 
 pkgconfigdir = $(libdir)/pkgconfig
-pkgconfig_DATA = libelf.pc libdw.pc
+pkgconfig_DATA = libelf.pc libdw.pc libdebuginfod.pc
 
 if MAINTAINER_MODE
 $(srcdir)/elfutils.spec.in: $(top_srcdir)/NEWS
diff --git a/config/libdebuginfod.pc.in b/config/libdebuginfod.pc.in
new file mode 100644
index 000000000000..46722a76b593
--- /dev/null
+++ b/config/libdebuginfod.pc.in
@@ -0,0 +1,12 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: debuginfod
+Description: elfutils library to query debuginfo files from debuginfod servers
+Version: @VERSION@
+URL: http://elfutils.org/
+
+Libs: -L${libdir} -ldebuginfod
+Cflags: -I${includedir}
diff --git a/configure.ac b/configure.ac
index 9be34d124387..738a96f18bf5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -60,6 +60,8 @@ AC_CONFIG_FILES([m4/Makefile])
 dnl The RPM spec file.  We substitute a few values in the file.
 AC_CONFIG_FILES([elfutils.spec:config/elfutils.spec.in])
 
+dnl debuginfo-server client & server parts.
+AC_CONFIG_FILES([debuginfod/Makefile])
 
 AC_CANONICAL_HOST
 
@@ -86,6 +88,8 @@ AS_IF([test "$use_locks" = yes],
 AH_TEMPLATE([USE_LOCKS], [Defined if libraries should be thread-safe.])
 
 AC_PROG_CC
+AC_PROG_CXX
+AX_CXX_COMPILE_STDCXX(11, noext, optional)
 AC_PROG_RANLIB
 AC_PROG_YACC
 AM_PROG_LEX
@@ -538,7 +542,7 @@ AM_CONDITIONAL(STANDALONE, false)dnl Used in tests/Makefile.am, which see.
 AC_CONFIG_FILES([tests/Makefile])
 
 dnl pkgconfig files
-AC_CONFIG_FILES([config/libelf.pc config/libdw.pc])
+AC_CONFIG_FILES([config/libelf.pc config/libdw.pc config/libdebuginfod.pc])
 
 # Get the definitions necessary to create the Makefiles in the po
 # subdirectories.  This is a small subset of the gettext rules.
@@ -641,6 +645,22 @@ if test "$HAVE_BUNZIP2" = "no"; then
   AC_MSG_WARN([No bunzip2, needed to run make check])
 fi
 
+# Look for libmicrohttpd, libcurl, libarchive, sqlite for debuginfo server
+# minimum versions as per rhel7.  Single --enable-* option arranges to build
+# both client libs and server process.
+
+PKG_PROG_PKG_CONFIG
+AC_ARG_ENABLE([debuginfod], AC_HELP_STRING([--enable-debuginfod], [Build debuginfo server and client solib]))
+AS_IF([test "x$enable_debuginfod" = "xyes"], [
+    AC_DEFINE([ENABLE_DEBUGINFOD],[1],[Build debuginfo-server])
+    PKG_CHECK_MODULES([libmicrohttpd],[libmicrohttpd >= 0.9.33])
+    PKG_CHECK_MODULES([libcurl],[libcurl >= 7.29.0])
+    PKG_CHECK_MODULES([sqlite3],[sqlite3 >= 3.7.17])
+    PKG_CHECK_MODULES([libarchive],[libarchive >= 3.1.2])
+], [enable_debuginfod="no"])
+AM_CONDITIONAL([DEBUGINFOD],[test "x$enable_debuginfod" = "xyes"])
+
+
 AC_OUTPUT
 
 AC_MSG_NOTICE([
@@ -669,6 +689,7 @@ AC_MSG_NOTICE([
   OTHER FEATURES
     Deterministic archives by default  : ${default_ar_deterministic}
     Native language support            : ${USE_NLS}
+    Debuginfo server support           : ${enable_debuginfod}
 
   EXTRA TEST FEATURES (used with make check)
     have bunzip2 installed (required)  : ${HAVE_BUNZIP2}
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
+
+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
+
+debuginfod_find_SOURCES = debuginfod-find.c
+debuginfod_find_LDADD = $(libeu) $(libdebuginfod)
+
+noinst_LIBRARIES = libdebuginfod.a
+noinst_LIBRARIES += libdebuginfod_pic.a
+
+libdebuginfod_a_SOURCES = debuginfod-client.c
+libdebuginfod_pic_a_SOURCES = debuginfod-client.c
+am_libdebuginfod_pic_a_OBJECTS = $(libdebuginfod_a_SOURCES:.c=.os)
+
+pkginclude_HEADERS = debuginfod.h
+
+libdebuginfod_so_LIBS = libdebuginfod_pic.a
+libdebuginfod_so_LDLIBS = $(libcurl_LIBS)
+libdebuginfod.so$(EXEEXT): $(srcdir)/libdebuginfod.map $(libdebuginfod_so_LIBS)
+	$(AM_V_CCLD)$(LINK) $(dso_LDFLAGS) -o $@ \
+		-Wl,--soname,$@.$(VERSION) \
+		-Wl,--version-script,$<,--no-undefined \
+		-Wl,--whole-archive $(libdebuginfod_so_LIBS) -Wl,--no-whole-archive \
+		$(libdebuginfod_so_LDLIBS)
+	@$(textrel_check)
+	$(AM_V_at)ln -fs $@ $@.$(VERSION)
+
+install: install-am libdebuginfod.so
+	$(mkinstalldirs) $(DESTDIR)$(libdir)
+	$(INSTALL_PROGRAM) libdebuginfod.so $(DESTDIR)$(libdir)/libdebuginfod-$(PACKAGE_VERSION).so
+	ln -fs libdebuginfod-$(PACKAGE_VERSION).so $(DESTDIR)$(libdir)/libdebuginfod.so.$(VERSION)
+	ln -fs libdebuginfod.so.$(VERSION) $(DESTDIR)$(libdir)/libdebuginfod.so
+
+uninstall: uninstall-am
+	rm -f $(DESTDIR)$(libdir)/libdebuginfod-$(PACKAGE_VERSION).so
+	rm -f $(DESTDIR)$(libdir)/libdebuginfod.so.$(VERSION)
+	rm -f $(DESTDIR)$(libdir)/libdebuginfod.so
+	rmdir --ignore-fail-on-non-empty $(DESTDIR)$(includedir)/elfutils
+
+EXTRA_DIST = libdebuginfod.map
+MOSTLYCLEANFILES = $(am_libdebuginfod_pic_a_OBJECTS) libdebuginfod.so.$(VERSION)
+CLEANFILES += $(am_libdebuginfod_pic_a_OBJECTS) libdebuginfod.so
+
+# automake std-options override: arrange to pass LD_LIBRARY_PATH
+installcheck-binPROGRAMS: $(bin_PROGRAMS)
+	bad=0; pid=$$$$; list="$(bin_PROGRAMS)"; for p in $$list; do \
+	  case ' $(AM_INSTALLCHECK_STD_OPTIONS_EXEMPT) ' in \
+	   *" $$p "* | *" $(srcdir)/$$p "*) continue;; \
+	  esac; \
+	  f=`echo "$$p" | \
+	     sed 's,^.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/'`; \
+	  for opt in --help --version; do \
+	    if LD_LIBRARY_PATH=$(DESTDIR)$(libdir) \
+	       $(DESTDIR)$(bindir)/$$f $$opt > c$${pid}_.out 2> c$${pid}_.err \
+		 && test -n "`cat c$${pid}_.out`" \
+		 && test -z "`cat c$${pid}_.err`"; then :; \
+	    else echo "$$f does not support $$opt" 1>&2; bad=1; fi; \
+	  done; \
+	done; rm -f c$${pid}_.???; exit $$bad
diff --git a/debuginfod/debuginfod-client.c b/debuginfod/debuginfod-client.c
new file mode 100644
index 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>
+#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];
+
+  /* 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.  */
+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];
+  char build_id_bytes[max_build_id_bytes * 2 + 1];
+
+  /* Copy lowercase hex representation of build_id into buf.  */
+  if ((build_id_len >= max_build_id_bytes) ||
+      (build_id_len == 0 &&
+       sizeof(build_id_bytes) > max_build_id_bytes*2 + 1))
+    return -EINVAL;
+  if (build_id_len == 0) /* expect clean hexadecimal */
+    strcpy (build_id_bytes, (const char *) build_id);
+  else
+    for (int i = 0; i < build_id_len; i++)
+      sprintf(build_id_bytes + (i * 2), "%02x", build_id[i]);
+
+  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);
+
+  /* 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);
+
+      /* If the target file has been found, abort the other queries.  */
+      if (target_handle != NULL)
+        for (int i = 0; i < num_urls; i++)
+          if (data[i].handle != target_handle)
+            curl_multi_remove_handle(curlm, data[i].handle);
+
+      curl_res = curl_multi_perform(curlm, &still_running);
+      if (curl_res != CURLM_OK)
+        {
+          switch (curl_res)
+            {
+            case CURLM_CALL_MULTI_PERFORM: continue;
+            case CURLM_OUT_OF_MEMORY: rc = -ENOMEM; break;
+            default: rc = -ENETUNREACH; break;
+            }
+          goto out1;
+        }
+    } while (still_running);
+
+  /* Check whether a query was successful. If so, assign its handle
+     to verified_handle.  */
+  int num_msg;
+  rc = -ENOENT;
+  CURL *verified_handle = NULL;
+  do
+    {
+      CURLMsg *msg;
+
+      msg = curl_multi_info_read(curlm, &num_msg);
+      if (msg != NULL && msg->msg == CURLMSG_DONE)
+        {
+          if (msg->data.result != CURLE_OK)
+            {
+              /* Unsucessful query, determine error code.  */
+              switch (msg->data.result)
+                {
+                case CURLE_COULDNT_RESOLVE_HOST: rc = -EHOSTUNREACH; break; // no NXDOMAIN
+                case CURLE_URL_MALFORMAT: rc = -EINVAL; break;
+                case CURLE_COULDNT_CONNECT: rc = -ECONNREFUSED; break;
+                case CURLE_REMOTE_ACCESS_DENIED: rc = -EACCES; break;
+                case CURLE_WRITE_ERROR: rc = -EIO; break;
+                case CURLE_OUT_OF_MEMORY: rc = -ENOMEM; break;
+                case CURLE_TOO_MANY_REDIRECTS: rc = -EMLINK; break;
+                case CURLE_SEND_ERROR: rc = -ECONNRESET; break;
+                case CURLE_RECV_ERROR: rc = -ECONNRESET; break;
+                case CURLE_OPERATION_TIMEDOUT: rc = -ETIME; break;
+                default: rc = -ENOENT; break;
+                }
+            }
+          else
+            {
+              /* Query completed without an error. Confirm that the
+                 response code is 200 and set verified_handle.  */
+              long resp_code = 500;
+              CURLcode curl_res;
+
+              curl_res = curl_easy_getinfo(target_handle,
+                                           CURLINFO_RESPONSE_CODE,
+                                           &resp_code);
+
+              if (curl_res == CURLE_OK
+                  && resp_code == 200
+                  && msg->easy_handle != NULL)
+                {
+                  verified_handle = msg->easy_handle;
+                  break;
+                }
+            }
+        }
+    } while (num_msg > 0);
+
+  if (verified_handle == NULL)
+    goto out1;
+
+  /* we've got one!!!! */
+  time_t mtime;
+  CURLcode curl_res = curl_easy_getinfo(verified_handle, CURLINFO_FILETIME, (void*) &mtime);
+  if (curl_res != CURLE_OK)
+    mtime = time(NULL); /* fall back to current time */
+
+  struct timeval tvs[2];
+  tvs[0].tv_sec = tvs[1].tv_sec = mtime;
+  tvs[0].tv_usec = tvs[1].tv_usec = 0;
+  (void) futimes (fd, tvs);  /* best effort */
+
+  /* rename tmp->real */
+  rc = rename (target_cache_tmppath, target_cache_path);
+  if (rc < 0)
+    {
+      rc = -errno;
+      goto out1;
+      /* Perhaps we need not give up right away; could retry or something ... */
+    }
+
+  /* Success!!!! */
+  for (int i = 0; i < num_urls; i++)
+    curl_easy_cleanup(data[i].handle);
+
+  curl_multi_cleanup (curlm);
+  free (data);
+  free (server_urls);
+  /* don't close fd - we're returning it */
+  /* don't unlink the tmppath; it's already been renamed. */
+  if (path != NULL)
+   *path = strdup(target_cache_path);
+
+  return fd;
+
+/* error exits */
+ out1:
+  for (int i = 0; i < num_urls; i++)
+    curl_easy_cleanup(data[i].handle);
+
+  curl_multi_cleanup(curlm);
+  unlink (target_cache_tmppath);
+  (void) rmdir (target_cache_dir); /* nop if not empty */
+  free(data);
+  close (fd);
+
+ out0:
+  free (server_urls);
+
+ out:
+  return rc;
+}
+
+
+/* See debuginfod.h  */
+int
+debuginfod_find_debuginfo (const unsigned char *build_id, int build_id_len,
+                          char **path)
+{
+  return debuginfod_query_server(build_id, build_id_len,
+                                "debuginfo", NULL, path);
+}
+
+
+/* See debuginfod.h  */
+int
+debuginfod_find_executable(const unsigned char *build_id, int build_id_len,
+                          char **path)
+{
+  return debuginfod_query_server(build_id, build_id_len,
+                                "executable", NULL, path);
+}
+
+/* See debuginfod.h  */
+int debuginfod_find_source(const unsigned char *build_id,
+                          int build_id_len,
+                          const char *filename,
+                          char **path)
+{
+  return debuginfod_query_server(build_id, build_id_len,
+                                "source", filename, path);
+}
+
+
+
+/* NB: these are thread-unsafe. */
+__attribute__((constructor)) attribute_hidden void libdebuginfod_ctor(void)
+{
+  curl_global_init(CURL_GLOBAL_DEFAULT);
+}
+
+/* NB: this is very thread-unsafe: it breaks other threads that are still in libcurl */
+__attribute__((destructor)) attribute_hidden void libdebuginfod_dtor(void)
+{
+  /* ... so don't do this: */
+  /* curl_global_cleanup(); */
+}
diff --git a/debuginfod/debuginfod-find.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
+
+.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.
+
+.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)"
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/>.  */
+
+#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;
+    }
+
+  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
+
+/* Names of environment variables that control the client logic. */
+#define DEBUGINFOD_URLS_ENV_VAR "DEBUGINFOD_URLS"
+#define DEBUGINFOD_CACHE_PATH_ENV_VAR "DEBUGINFOD_CACHE_PATH"
+#define DEBUGINFOD_TIMEOUT_ENV_VAR "DEBUGINFOD_TIMEOUT"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Query the urls contained in $DEBUGINFOD_URLS for a file with
+   the specified type and build id.  If build_id_len == 0, the
+   build_id is supplied as a lowercase hexadecimal string; otherwise
+   it is a binary blob of given legnth.
+
+   If successful, return a file descriptor to the target, otherwise
+   return a posix error code.  If successful, set *path to a
+   strdup'd copy of the name of the same file in the cache.
+   Caller must free() it later. */
+  
+int debuginfod_find_debuginfo (const unsigned char *build_id,
+                             int build_id_len,
+                             char **path);
+
+int debuginfod_find_executable (const unsigned char *build_id,
+                               int build_id_len,
+                               char **path);
+
+int debuginfod_find_source (const unsigned char *build_id,
+                           int build_id_len,
+                           const char *filename,
+                           char **path);
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif /* _LIBBGSERVER_CLIENT_H */
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.
+
+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.
+
+.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
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;
diff --git a/libdw/ChangeLog b/libdw/ChangeLog
index 394c0df293f0..f0af348aff6e 100644
--- a/libdw/ChangeLog
+++ b/libdw/ChangeLog
@@ -1,3 +1,7 @@
+2019-10-28  Aaron Merey  <amerey@redhat.com>
+
+	* Makefile.am (libdw_so_LDLIBS): Add -ldl for libdebuginfod.so dlopen.
+
 2019-08-26  Jonathon Anderson  <jma14@rice.edu>
 
 	* libdw_alloc.c (__libdw_allocate): Added thread-safe stack allocator.
diff --git a/libdw/Makefile.am b/libdw/Makefile.am
index ce793e903b88..33b5838dc4e1 100644
--- a/libdw/Makefile.am
+++ b/libdw/Makefile.am
@@ -109,7 +109,7 @@ libdw_so_LIBS = ../libebl/libebl_pic.a ../backends/libebl_backends_pic.a \
 		../libcpu/libcpu_pic.a libdw_pic.a ../libdwelf/libdwelf_pic.a \
 		../libdwfl/libdwfl_pic.a
 libdw_so_DEPS = ../lib/libeu.a ../libelf/libelf.so
-libdw_so_LDLIBS = $(libdw_so_DEPS) -lz $(argp_LDADD) $(zip_LIBS) -pthread
+libdw_so_LDLIBS = $(libdw_so_DEPS) -ldl -lz $(argp_LDADD) $(zip_LIBS) -pthread
 libdw_so_SOURCES =
 libdw.so$(EXEEXT): $(srcdir)/libdw.map $(libdw_so_LIBS) $(libdw_so_DEPS)
 	$(AM_V_CCLD)$(LINK) $(dso_LDFLAGS) -o $@ \
diff --git a/libdwfl/ChangeLog b/libdwfl/ChangeLog
index 04a39637e9a4..be29cc00bc7e 100644
--- a/libdwfl/ChangeLog
+++ b/libdwfl/ChangeLog
@@ -1,3 +1,9 @@
+2019-10-28  Aaron Merey  <amerey@redhat.com>
+
+	* dwfl_build_id_find_elf.c (dwfl_build_id_find_elf): Call debuginfod
+	client functions via dlopen to look for elf/dwarf files as fallback.
+	* find-debuginfo.c (dwfl_standard_find_debuginfo): Ditto.
+
 2019-08-12  Mark Wielaard  <mark@klomp.org>
 
 	* gzip.c (open_stream): Return DWFL_E_ERRNO on bad file operation.
diff --git a/libdwfl/Makefile.am b/libdwfl/Makefile.am
index 89ca92ed8110..29046e9e5e85 100644
--- a/libdwfl/Makefile.am
+++ b/libdwfl/Makefile.am
@@ -31,7 +31,7 @@
 ##
 include $(top_srcdir)/config/eu.am
 AM_CPPFLAGS += -I$(srcdir) -I$(srcdir)/../libelf -I$(srcdir)/../libebl \
-	   -I$(srcdir)/../libdw -I$(srcdir)/../libdwelf
+	   -I$(srcdir)/../libdw -I$(srcdir)/../libdwelf -I$(srcdir)/../debuginfod
 VERSION = 1
 
 noinst_LIBRARIES = libdwfl.a
@@ -39,6 +39,7 @@ noinst_LIBRARIES += libdwfl_pic.a
 
 pkginclude_HEADERS = libdwfl.h
 
+
 libdwfl_a_SOURCES = dwfl_begin.c dwfl_end.c dwfl_error.c dwfl_version.c \
 		    dwfl_module.c dwfl_report_elf.c relocate.c \
 		    dwfl_module_build_id.c dwfl_module_report_build_id.c \
diff --git a/libdwfl/dwfl_build_id_find_elf.c b/libdwfl/dwfl_build_id_find_elf.c
index cc6c3f62d276..1f3834180c4a 100644
--- a/libdwfl/dwfl_build_id_find_elf.c
+++ b/libdwfl/dwfl_build_id_find_elf.c
@@ -1,5 +1,5 @@
 /* Find an ELF file for a module from its build ID.
-   Copyright (C) 2007-2010, 2014, 2015 Red Hat, Inc.
+   Copyright (C) 2007-2010, 2014, 2015, 2019 Red Hat, Inc.
    This file is part of elfutils.
 
    This file is free software; you can redistribute it and/or modify
@@ -34,7 +34,9 @@
 #include <inttypes.h>
 #include <fcntl.h>
 #include <unistd.h>
+#include <dlfcn.h>
 #include "system.h"
+#include "debuginfod.h"
 
 
 int
@@ -187,7 +189,31 @@ dwfl_build_id_find_elf (Dwfl_Module *mod,
       free (*file_name);
       *file_name = NULL;
     }
-  else if (errno == 0 && mod->build_id_len > 0)
+#if ENABLE_DEBUGINFOD
+  else {
+    static void *debuginfod_so;
+    static __typeof__ (debuginfod_find_executable) *fp_debuginfod_find_executable;
+
+    if (debuginfod_so == NULL)
+      debuginfod_so = dlopen("libdebuginfod-" VERSION ".so", RTLD_LAZY);
+    if (debuginfod_so == NULL)
+      debuginfod_so = dlopen("libdebuginfod.so", RTLD_LAZY);
+    if (debuginfod_so != NULL && fp_debuginfod_find_executable == NULL)
+      fp_debuginfod_find_executable = dlsym (debuginfod_so, "debuginfod_find_executable");
+
+    if (fp_debuginfod_find_executable != NULL)
+      {
+        /* If all else fails and a build-id is available, query the
+           debuginfo-server if enabled.  */
+        if (fd < 0 && mod->build_id_len > 0)
+          fd = (*fp_debuginfod_find_executable) (mod->build_id_bits,
+                                                mod->build_id_len,
+                                                NULL);
+      }
+  }
+#endif /* ENABLE_DEBUGINFOD */
+
+  if (fd < 0 && errno == 0 && mod->build_id_len > 0)
     /* Setting this with no file yet loaded is a marker that
        the build ID is authoritative even if we also know a
        putative *FILE_NAME.  */
diff --git a/libdwfl/find-debuginfo.c b/libdwfl/find-debuginfo.c
index 9267788d2d19..d36ec3cc39b8 100644
--- a/libdwfl/find-debuginfo.c
+++ b/libdwfl/find-debuginfo.c
@@ -1,5 +1,5 @@
 /* Standard find_debuginfo callback for libdwfl.
-   Copyright (C) 2005-2010, 2014, 2015 Red Hat, Inc.
+   Copyright (C) 2005-2010, 2014, 2015, 2019 Red Hat, Inc.
    This file is part of elfutils.
 
    This file is free software; you can redistribute it and/or modify
@@ -31,9 +31,13 @@
 #endif
 
 #include "libdwflP.h"
+#ifdef ENABLE_DEBUGINFOD
+#include "debuginfod.h"
+#endif
 #include <stdio.h>
 #include <fcntl.h>
 #include <unistd.h>
+#include <dlfcn.h>
 #include <sys/stat.h>
 #include "system.h"
 
@@ -359,7 +363,8 @@ dwfl_standard_find_debuginfo (Dwfl_Module *mod,
      other than just by finding nothing, that's all we do.  */
   const unsigned char *bits;
   GElf_Addr vaddr;
-  if (INTUSE(dwfl_module_build_id) (mod, &bits, &vaddr) > 0)
+  int bits_len;
+  if ((bits_len = INTUSE(dwfl_module_build_id) (mod, &bits, &vaddr)) > 0)
     {
       /* Dropping most arguments means we cannot rely on them in
 	 dwfl_build_id_find_debuginfo.  But leave it that way since
@@ -397,6 +402,28 @@ dwfl_standard_find_debuginfo (Dwfl_Module *mod,
       free (canon);
     }
 
+#if ENABLE_DEBUGINFOD
+  {
+    static void *debuginfod_so;
+    static __typeof__ (debuginfod_find_debuginfo) *fp_debuginfod_find_debuginfo;
+
+    if (debuginfod_so == NULL)
+      debuginfod_so = dlopen("libdebuginfod-" VERSION ".so", RTLD_LAZY);
+    if (debuginfod_so == NULL)
+      debuginfod_so = dlopen("libdebuginfod.so", RTLD_LAZY);
+    if (debuginfod_so != NULL && fp_debuginfod_find_debuginfo == NULL)
+      fp_debuginfod_find_debuginfo = dlsym (debuginfod_so, "debuginfod_find_debuginfo");
+
+    if (fp_debuginfod_find_debuginfo != NULL)
+      {
+        /* If all else fails and a build-id is available, query the
+           debuginfo-server if enabled.  */
+        if (fd < 0 && bits_len > 0)
+          fd = (*fp_debuginfod_find_debuginfo) (bits, bits_len, NULL);
+      }
+  }
+#endif /* ENABLE_DEBUGINFOD */
+
   return fd;
 }
 INTDEF (dwfl_standard_find_debuginfo)
diff --git a/m4/ChangeLog b/m4/ChangeLog
index 9ee06d750a1e..8ab0ff39610a 100644
--- a/m4/ChangeLog
+++ b/m4/ChangeLog
@@ -1,3 +1,7 @@
+2019-10-28  Aaron Merey  <amerey@redhat.com>
+
+	* ax_check_compile_flag.m4, ax_cxx_compile_stdcxx.m4: New files.
+
 2015-05-01  Mark Wielaard  <mjw@redhat.com>
 
 	* zip.m4: Explicitly set with_ to no, if not yes.
diff --git a/m4/Makefile.am b/m4/Makefile.am
index 3b0e11458748..ae7a565777e8 100644
--- a/m4/Makefile.am
+++ b/m4/Makefile.am
@@ -18,4 +18,4 @@
 ##
 
 ##m4-files-begin
-EXTRA_DIST = codeset.m4 gettext.m4 iconv.m4 lcmessage.m4 progtest.m4 zip.m4
+EXTRA_DIST = codeset.m4 gettext.m4 iconv.m4 lcmessage.m4 progtest.m4 zip.m4 ax_check_compile_flag.m4 ax_cxx_compile_stdcxx.m4
diff --git a/m4/ax_check_compile_flag.m4 b/m4/ax_check_compile_flag.m4
new file mode 100644
index 000000000000..ca3639715e72
--- /dev/null
+++ b/m4/ax_check_compile_flag.m4
@@ -0,0 +1,74 @@
+# ===========================================================================
+#   http://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+#   AX_CHECK_COMPILE_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS], [INPUT])
+#
+# DESCRIPTION
+#
+#   Check whether the given FLAG works with the current language's compiler
+#   or gives an error.  (Warnings, however, are ignored)
+#
+#   ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on
+#   success/failure.
+#
+#   If EXTRA-FLAGS is defined, it is added to the current language's default
+#   flags (e.g. CFLAGS) when the check is done.  The check is thus made with
+#   the flags: "CFLAGS EXTRA-FLAGS FLAG".  This can for example be used to
+#   force the compiler to issue an error when a bad flag is given.
+#
+#   INPUT gives an alternative input source to AC_COMPILE_IFELSE.
+#
+#   NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this
+#   macro in sync with AX_CHECK_{PREPROC,LINK}_FLAG.
+#
+# LICENSE
+#
+#   Copyright (c) 2008 Guido U. Draheim <guidod@gmx.de>
+#   Copyright (c) 2011 Maarten Bosmans <mkbosmans@gmail.com>
+#
+#   This program is free software: you can redistribute it and/or modify it
+#   under the terms of the GNU General Public License as published by the
+#   Free Software Foundation, either version 3 of the License, or (at your
+#   option) any later version.
+#
+#   This program 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 a copy of the GNU General Public License along
+#   with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#   As a special exception, the respective Autoconf Macro's copyright owner
+#   gives unlimited permission to copy, distribute and modify the configure
+#   scripts that are the output of Autoconf when processing the Macro. You
+#   need not follow the terms of the GNU General Public License when using
+#   or distributing such scripts, even though portions of the text of the
+#   Macro appear in them. The GNU General Public License (GPL) does govern
+#   all other use of the material that constitutes the Autoconf Macro.
+#
+#   This special exception to the GPL applies to versions of the Autoconf
+#   Macro released by the Autoconf Archive. When you make and distribute a
+#   modified version of the Autoconf Macro, you may extend this special
+#   exception to the GPL to apply to your modified version as well.
+
+#serial 4
+
+AC_DEFUN([AX_CHECK_COMPILE_FLAG],
+[AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF
+AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl
+AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [
+  ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS
+  _AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1"
+  AC_COMPILE_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])],
+    [AS_VAR_SET(CACHEVAR,[yes])],
+    [AS_VAR_SET(CACHEVAR,[no])])
+  _AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags])
+AS_VAR_IF(CACHEVAR,yes,
+  [m4_default([$2], :)],
+  [m4_default([$3], :)])
+AS_VAR_POPDEF([CACHEVAR])dnl
+])dnl AX_CHECK_COMPILE_FLAGS
diff --git a/m4/ax_cxx_compile_stdcxx.m4 b/m4/ax_cxx_compile_stdcxx.m4
new file mode 100644
index 000000000000..8adc76569aa7
--- /dev/null
+++ b/m4/ax_cxx_compile_stdcxx.m4
@@ -0,0 +1,556 @@
+# ===========================================================================
+#   http://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+#   AX_CXX_COMPILE_STDCXX(VERSION, [ext|noext], [mandatory|optional])
+#
+# DESCRIPTION
+#
+#   Check for baseline language coverage in the compiler for the specified
+#   version of the C++ standard.  If necessary, add switches to CXX to
+#   enable support.  VERSION may be '11' (for the C++11 standard) or '14'
+#   (for the C++14 standard).
+#
+#   The second argument, if specified, indicates whether you insist on an
+#   extended mode (e.g. -std=gnu++11) or a strict conformance mode (e.g.
+#   -std=c++11).  If neither is specified, you get whatever works, with
+#   preference for an extended mode.
+#
+#   The third argument, if specified 'mandatory' or if left unspecified,
+#   indicates that baseline support for the specified C++ standard is
+#   required and that the macro should error out if no mode with that
+#   support is found.  If specified 'optional', then configuration proceeds
+#   regardless, after defining HAVE_CXX${VERSION} if and only if a
+#   supporting mode is found.
+#
+# LICENSE
+#
+#   Copyright (c) 2008 Benjamin Kosnik <bkoz@redhat.com>
+#   Copyright (c) 2012 Zack Weinberg <zackw@panix.com>
+#   Copyright (c) 2013 Roy Stogner <roystgnr@ices.utexas.edu>
+#   Copyright (c) 2014, 2015 Google Inc.; contributed by Alexey Sokolov <sokolov@google.com>
+#   Copyright (c) 2015 Paul Norman <penorman@mac.com>
+#   Copyright (c) 2015 Moritz Klammler <moritz@klammler.eu>
+#
+#   Copying and distribution of this file, with or without modification, are
+#   permitted in any medium without royalty provided the copyright notice
+#   and this notice are preserved.  This file is offered as-is, without any
+#   warranty.
+
+#serial 3
+
+dnl  This macro is based on the code from the AX_CXX_COMPILE_STDCXX_11 macro
+dnl  (serial version number 13).
+
+AC_DEFUN([AX_CXX_COMPILE_STDCXX], [dnl
+  m4_if([$1], [11], [],
+        [$1], [14], [],
+        [$1], [17], [m4_fatal([support for C++17 not yet implemented in AX_CXX_COMPILE_STDCXX])],
+        [m4_fatal([invalid first argument `$1' to AX_CXX_COMPILE_STDCXX])])dnl
+  m4_if([$2], [], [],
+        [$2], [ext], [],
+        [$2], [noext], [],
+        [m4_fatal([invalid second argument `$2' to AX_CXX_COMPILE_STDCXX])])dnl
+  m4_if([$3], [], [ax_cxx_compile_cxx$1_required=true],
+        [$3], [mandatory], [ax_cxx_compile_cxx$1_required=true],
+        [$3], [optional], [ax_cxx_compile_cxx$1_required=false],
+        [m4_fatal([invalid third argument `$3' to AX_CXX_COMPILE_STDCXX])])
+  AC_LANG_PUSH([C++])dnl
+  ac_success=no
+  AC_CACHE_CHECK(whether $CXX supports C++$1 features by default,
+  ax_cv_cxx_compile_cxx$1,
+  [AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])],
+    [ax_cv_cxx_compile_cxx$1=yes],
+    [ax_cv_cxx_compile_cxx$1=no])])
+  if test x$ax_cv_cxx_compile_cxx$1 = xyes; then
+    ac_success=yes
+  fi
+
+  m4_if([$2], [noext], [], [dnl
+  if test x$ac_success = xno; then
+    for switch in -std=gnu++$1 -std=gnu++0x; do
+      cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch])
+      AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch,
+                     $cachevar,
+        [ac_save_CXX="$CXX"
+         CXX="$CXX $switch"
+         AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])],
+          [eval $cachevar=yes],
+          [eval $cachevar=no])
+         CXX="$ac_save_CXX"])
+      if eval test x\$$cachevar = xyes; then
+        CXX="$CXX $switch"
+        ac_success=yes
+        break
+      fi
+    done
+  fi])
+
+  m4_if([$2], [ext], [], [dnl
+  if test x$ac_success = xno; then
+    dnl HP's aCC needs +std=c++11 according to:
+    dnl http://h21007.www2.hp.com/portal/download/files/unprot/aCxx/PDF_Release_Notes/769149-001.pdf
+    dnl Cray's crayCC needs "-h std=c++11"
+    for switch in -std=c++$1 -std=c++0x +std=c++$1 "-h std=c++$1"; do
+      cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch])
+      AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch,
+                     $cachevar,
+        [ac_save_CXX="$CXX"
+         CXX="$CXX $switch"
+         AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])],
+          [eval $cachevar=yes],
+          [eval $cachevar=no])
+         CXX="$ac_save_CXX"])
+      if eval test x\$$cachevar = xyes; then
+        CXX="$CXX $switch"
+        ac_success=yes
+        break
+      fi
+    done
+  fi])
+  AC_LANG_POP([C++])
+  if test x$ax_cxx_compile_cxx$1_required = xtrue; then
+    if test x$ac_success = xno; then
+      AC_MSG_ERROR([*** A compiler with support for C++$1 language features is required.])
+    fi
+  fi
+  if test x$ac_success = xno; then
+    HAVE_CXX$1=0
+    AC_MSG_NOTICE([No compiler with C++$1 support was found])
+  else
+    HAVE_CXX$1=1
+    AC_DEFINE(HAVE_CXX$1,1,
+              [define if the compiler supports basic C++$1 syntax])
+  fi
+  AC_SUBST(HAVE_CXX$1)
+])
+
+
+dnl  Test body for checking C++11 support
+
+m4_define([_AX_CXX_COMPILE_STDCXX_testbody_11],
+  _AX_CXX_COMPILE_STDCXX_testbody_new_in_11
+)
+
+
+dnl  Test body for checking C++14 support
+
+m4_define([_AX_CXX_COMPILE_STDCXX_testbody_14],
+  _AX_CXX_COMPILE_STDCXX_testbody_new_in_11
+  _AX_CXX_COMPILE_STDCXX_testbody_new_in_14
+)
+
+
+dnl  Tests for new features in C++11
+
+m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_11], [[
+
+// If the compiler admits that it is not ready for C++11, why torture it?
+// Hopefully, this will speed up the test.
+
+#ifndef __cplusplus
+
+#error "This is not a C++ compiler"
+
+#elif __cplusplus < 201103L
+
+#error "This is not a C++11 compiler"
+
+#else
+
+namespace cxx11
+{
+
+  namespace test_static_assert
+  {
+
+    template <typename T>
+    struct check
+    {
+      static_assert(sizeof(int) <= sizeof(T), "not big enough");
+    };
+
+  }
+
+  namespace test_final_override
+  {
+
+    struct Base
+    {
+      virtual void f() {}
+    };
+
+    struct Derived : public Base
+    {
+      virtual void f() override {}
+    };
+
+  }
+
+  namespace test_double_right_angle_brackets
+  {
+
+    template < typename T >
+    struct check {};
+
+    typedef check<void> single_type;
+    typedef check<check<void>> double_type;
+    typedef check<check<check<void>>> triple_type;
+    typedef check<check<check<check<void>>>> quadruple_type;
+
+  }
+
+  namespace test_decltype
+  {
+
+    int
+    f()
+    {
+      int a = 1;
+      decltype(a) b = 2;
+      return a + b;
+    }
+
+  }
+
+  namespace test_type_deduction
+  {
+
+    template < typename T1, typename T2 >
+    struct is_same
+    {
+      static const bool value = false;
+    };
+
+    template < typename T >
+    struct is_same<T, T>
+    {
+      static const bool value = true;
+    };
+
+    template < typename T1, typename T2 >
+    auto
+    add(T1 a1, T2 a2) -> decltype(a1 + a2)
+    {
+      return a1 + a2;
+    }
+
+    int
+    test(const int c, volatile int v)
+    {
+      static_assert(is_same<int, decltype(0)>::value == true, "");
+      static_assert(is_same<int, decltype(c)>::value == false, "");
+      static_assert(is_same<int, decltype(v)>::value == false, "");
+      auto ac = c;
+      auto av = v;
+      auto sumi = ac + av + 'x';
+      auto sumf = ac + av + 1.0;
+      static_assert(is_same<int, decltype(ac)>::value == true, "");
+      static_assert(is_same<int, decltype(av)>::value == true, "");
+      static_assert(is_same<int, decltype(sumi)>::value == true, "");
+      static_assert(is_same<int, decltype(sumf)>::value == false, "");
+      static_assert(is_same<int, decltype(add(c, v))>::value == true, "");
+      return (sumf > 0.0) ? sumi : add(c, v);
+    }
+
+  }
+
+  namespace test_noexcept
+  {
+
+    int f() { return 0; }
+    int g() noexcept { return 0; }
+
+    static_assert(noexcept(f()) == false, "");
+    static_assert(noexcept(g()) == true, "");
+
+  }
+
+  namespace test_constexpr
+  {
+
+    template < typename CharT >
+    unsigned long constexpr
+    strlen_c_r(const CharT *const s, const unsigned long acc) noexcept
+    {
+      return *s ? strlen_c_r(s + 1, acc + 1) : acc;
+    }
+
+    template < typename CharT >
+    unsigned long constexpr
+    strlen_c(const CharT *const s) noexcept
+    {
+      return strlen_c_r(s, 0UL);
+    }
+
+    static_assert(strlen_c("") == 0UL, "");
+    static_assert(strlen_c("1") == 1UL, "");
+    static_assert(strlen_c("example") == 7UL, "");
+    static_assert(strlen_c("another\0example") == 7UL, "");
+
+  }
+
+  namespace test_rvalue_references
+  {
+
+    template < int N >
+    struct answer
+    {
+      static constexpr int value = N;
+    };
+
+    answer<1> f(int&)       { return answer<1>(); }
+    answer<2> f(const int&) { return answer<2>(); }
+    answer<3> f(int&&)      { return answer<3>(); }
+
+    void
+    test()
+    {
+      int i = 0;
+      const int c = 0;
+      static_assert(decltype(f(i))::value == 1, "");
+      static_assert(decltype(f(c))::value == 2, "");
+      static_assert(decltype(f(0))::value == 3, "");
+    }
+
+  }
+
+  namespace test_uniform_initialization
+  {
+
+    struct test
+    {
+      static const int zero {};
+      static const int one {1};
+    };
+
+    static_assert(test::zero == 0, "");
+    static_assert(test::one == 1, "");
+
+  }
+
+  namespace test_lambdas
+  {
+
+    void
+    test1()
+    {
+      auto lambda1 = [](){};
+      auto lambda2 = lambda1;
+      lambda1();
+      lambda2();
+    }
+
+    int
+    test2()
+    {
+      auto a = [](int i, int j){ return i + j; }(1, 2);
+      auto b = []() -> int { return '0'; }();
+      auto c = [=](){ return a + b; }();
+      auto d = [&](){ return c; }();
+      auto e = [a, &b](int x) mutable {
+        const auto identity = [](int y){ return y; };
+        for (auto i = 0; i < a; ++i)
+          a += b--;
+        return x + identity(a + b);
+      }(0);
+      return a + b + c + d + e;
+    }
+
+    int
+    test3()
+    {
+      const auto nullary = [](){ return 0; };
+      const auto unary = [](int x){ return x; };
+      using nullary_t = decltype(nullary);
+      using unary_t = decltype(unary);
+      const auto higher1st = [](nullary_t f){ return f(); };
+      const auto higher2nd = [unary](nullary_t f1){
+        return [unary, f1](unary_t f2){ return f2(unary(f1())); };
+      };
+      return higher1st(nullary) + higher2nd(nullary)(unary);
+    }
+
+  }
+
+  namespace test_variadic_templates
+  {
+
+    template <int...>
+    struct sum;
+
+    template <int N0, int... N1toN>
+    struct sum<N0, N1toN...>
+    {
+      static constexpr auto value = N0 + sum<N1toN...>::value;
+    };
+
+    template <>
+    struct sum<>
+    {
+      static constexpr auto value = 0;
+    };
+
+    static_assert(sum<>::value == 0, "");
+    static_assert(sum<1>::value == 1, "");
+    static_assert(sum<23>::value == 23, "");
+    static_assert(sum<1, 2>::value == 3, "");
+    static_assert(sum<5, 5, 11>::value == 21, "");
+    static_assert(sum<2, 3, 5, 7, 11, 13>::value == 41, "");
+
+  }
+
+  // http://stackoverflow.com/questions/13728184/template-aliases-and-sfinae
+  // Clang 3.1 fails with headers of libstd++ 4.8.3 when using std::function
+  // because of this.
+  namespace test_template_alias_sfinae
+  {
+
+    struct foo {};
+
+    template<typename T>
+    using member = typename T::member_type;
+
+    template<typename T>
+    void func(...) {}
+
+    template<typename T>
+    void func(member<T>*) {}
+
+    void test();
+
+    void test() { func<foo>(0); }
+
+  }
+
+}  // namespace cxx11
+
+#endif  // __cplusplus >= 201103L
+
+]])
+
+
+dnl  Tests for new features in C++14
+
+m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_14], [[
+
+// If the compiler admits that it is not ready for C++14, why torture it?
+// Hopefully, this will speed up the test.
+
+#ifndef __cplusplus
+
+#error "This is not a C++ compiler"
+
+#elif __cplusplus < 201402L
+
+#error "This is not a C++14 compiler"
+
+#else
+
+namespace cxx14
+{
+
+  namespace test_polymorphic_lambdas
+  {
+
+    int
+    test()
+    {
+      const auto lambda = [](auto&&... args){
+        const auto istiny = [](auto x){
+          return (sizeof(x) == 1UL) ? 1 : 0;
+        };
+        const int aretiny[] = { istiny(args)... };
+        return aretiny[0];
+      };
+      return lambda(1, 1L, 1.0f, '1');
+    }
+
+  }
+
+  namespace test_binary_literals
+  {
+
+    constexpr auto ivii = 0b0000000000101010;
+    static_assert(ivii == 42, "wrong value");
+
+  }
+
+  namespace test_generalized_constexpr
+  {
+
+    template < typename CharT >
+    constexpr unsigned long
+    strlen_c(const CharT *const s) noexcept
+    {
+      auto length = 0UL;
+      for (auto p = s; *p; ++p)
+        ++length;
+      return length;
+    }
+
+    static_assert(strlen_c("") == 0UL, "");
+    static_assert(strlen_c("x") == 1UL, "");
+    static_assert(strlen_c("test") == 4UL, "");
+    static_assert(strlen_c("another\0test") == 7UL, "");
+
+  }
+
+  namespace test_lambda_init_capture
+  {
+
+    int
+    test()
+    {
+      auto x = 0;
+      const auto lambda1 = [a = x](int b){ return a + b; };
+      const auto lambda2 = [a = lambda1(x)](){ return a; };
+      return lambda2();
+    }
+
+  }
+
+  namespace test_digit_seperators
+  {
+
+    constexpr auto ten_million = 100'000'000;
+    static_assert(ten_million == 100000000, "");
+
+  }
+
+  namespace test_return_type_deduction
+  {
+
+    auto f(int& x) { return x; }
+    decltype(auto) g(int& x) { return x; }
+
+    template < typename T1, typename T2 >
+    struct is_same
+    {
+      static constexpr auto value = false;
+    };
+
+    template < typename T >
+    struct is_same<T, T>
+    {
+      static constexpr auto value = true;
+    };
+
+    int
+    test()
+    {
+      auto x = 0;
+      static_assert(is_same<int, decltype(f(x))>::value, "");
+      static_assert(is_same<int&, decltype(g(x))>::value, "");
+      return x;
+    }
+
+  }
+
+}  // namespace cxx14
+
+#endif  // __cplusplus >= 201402L
+
+]])
-- 
2.21.0



More information about the Elfutils-devel mailing list