summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAaron Merey <amerey@redhat.com>2019-10-28 13:29:26 -0400
committerMark Wielaard <mark@klomp.org>2019-11-22 22:49:21 +0100
commit288f6b199a8b1a60d4fb1f54ca7b883cdd5afca2 (patch)
treeb7d6a0dbcc0773de051aacdef8947d4adf72c941
parent4bad7d1eada6a0690ab30eed2e5ae42c6fd7dacd (diff)
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>
-rw-r--r--ChangeLog6
-rw-r--r--Makefile.am4
-rw-r--r--config/Makefile.am4
-rw-r--r--config/libdebuginfod.pc.in12
-rw-r--r--configure.ac23
-rw-r--r--debuginfod/ChangeLog9
-rw-r--r--debuginfod/Makefile.am114
-rw-r--r--debuginfod/debuginfod-client.c665
-rw-r--r--debuginfod/debuginfod-find.c101
-rw-r--r--debuginfod/debuginfod.h69
-rw-r--r--debuginfod/libdebuginfod.map7
-rw-r--r--doc/Makefile.am10
-rw-r--r--doc/debuginfod-find.1137
-rw-r--r--doc/debuginfod_find_debuginfo.3182
-rw-r--r--doc/debuginfod_find_executable.31
-rw-r--r--doc/debuginfod_find_source.31
-rw-r--r--libdw/ChangeLog4
-rw-r--r--libdw/Makefile.am2
-rw-r--r--libdwfl/ChangeLog6
-rw-r--r--libdwfl/Makefile.am3
-rw-r--r--libdwfl/dwfl_build_id_find_elf.c32
-rw-r--r--libdwfl/find-debuginfo.c31
-rw-r--r--m4/ChangeLog4
-rw-r--r--m4/Makefile.am2
-rw-r--r--m4/ax_check_compile_flag.m474
-rw-r--r--m4/ax_cxx_compile_stdcxx.m4556
26 files changed, 2048 insertions, 11 deletions
diff --git a/ChangeLog b/ChangeLog
index 433a5f3c..9d1c05f4 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-08-25 Jonathon Anderson <jma14@rice.edu>
* configure.ac: Add new --enable-valgrind-annotations
diff --git a/Makefile.am b/Makefile.am
index 52f64fc9..bd8926b5 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 9d292cee..10bd8d31 100644
--- a/config/Makefile.am
+++ b/config/Makefile.am
@@ -29,10 +29,10 @@
## not, see <http://www.gnu.org/licenses/>.
##
EXTRA_DIST = elfutils.spec.in known-dwarf.awk 10-default-yama-scope.conf
- libelf.pc.in libdw.pc.in
+ libelf.pc.in libdw.pc.in libdebuginfod.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 00000000..46722a76
--- /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 7fc3acb6..8a3ed3af 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
@@ -567,7 +571,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.
@@ -670,6 +674,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([
@@ -699,6 +719,7 @@ AC_MSG_NOTICE([
Deterministic archives by default : ${default_ar_deterministic}
Native language support : ${USE_NLS}
Extra Valgrind annotations : ${use_vg_annotations}
+ Debuginfo client/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 00000000..1a31cf6f
--- /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 00000000..a8ee4594
--- /dev/null
+++ b/debuginfod/Makefile.am
@@ -0,0 +1,114 @@
+## Makefile.am for libdebuginfod library subdirectory in elfutils.
+##
+## Process this file with automake to create Makefile.in
+##
+## Copyright (C) 2019 Red Hat, Inc.
+## This file is part of elfutils.
+##
+## This file is free software; you can redistribute it and/or modify
+## it under the terms of either
+##
+## * the GNU Lesser General Public License as published by the Free
+## Software Foundation; either version 3 of the License, or (at
+## your option) any later version
+##
+## or
+##
+## * the GNU General Public License as published by the Free
+## Software Foundation; either version 2 of the License, or (at
+## your option) any later version
+##
+## or both in parallel, as here.
+##
+## elfutils is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+## General Public License for more details.
+##
+## You should have received copies of the GNU General Public License and
+## the GNU Lesser General Public License along with this program. If
+## not, see <http://www.gnu.org/licenses/>.
+##
+include $(top_srcdir)/config/eu.am
+AM_CPPFLAGS += -I$(srcdir) -I$(srcdir)/../libelf -I$(srcdir)/../libebl \
+ -I$(srcdir)/../libdw -I$(srcdir)/../libdwelf
+VERSION = 1
+
+# Disable eu- prefixing for artifacts (binaries & man pages) in this
+# directory, since they do not conflict with binutils tools.
+program_prefix=
+program_transform_name = s,x,x,
+
+if BUILD_STATIC
+libasm = ../libasm/libasm.a
+libdw = ../libdw/libdw.a -lz $(zip_LIBS) $(libelf) $(libebl) -ldl
+libelf = ../libelf/libelf.a -lz
+libdebuginfod = ./libdebuginfod.a
+else
+libasm = ../libasm/libasm.so
+libdw = ../libdw/libdw.so
+libelf = ../libelf/libelf.so
+libdebuginfod = ./libdebuginfod.so
+endif
+libebl = ../libebl/libebl.a
+libeu = ../lib/libeu.a
+
+AM_LDFLAGS = -Wl,-rpath-link,../libelf:../libdw:.
+
+bin_PROGRAMS = debuginfod-find
+
+debuginfod_find_SOURCES = debuginfod-find.c
+debuginfod_find_LDADD = $(libeu) $(libdebuginfod)
+
+noinst_LIBRARIES = libdebuginfod.a
+noinst_LIBRARIES += libdebuginfod_pic.a
+
+libdebuginfod_a_SOURCES = debuginfod-client.c
+libdebuginfod_pic_a_SOURCES = debuginfod-client.c
+am_libdebuginfod_pic_a_OBJECTS = $(libdebuginfod_a_SOURCES:.c=.os)
+
+pkginclude_HEADERS = debuginfod.h
+
+libdebuginfod_so_LIBS = libdebuginfod_pic.a
+libdebuginfod_so_LDLIBS = $(libcurl_LIBS)
+libdebuginfod.so$(EXEEXT): $(srcdir)/libdebuginfod.map $(libdebuginfod_so_LIBS)
+ $(AM_V_CCLD)$(LINK) $(dso_LDFLAGS) -o $@ \
+ -Wl,--soname,$@.$(VERSION) \
+ -Wl,--version-script,$<,--no-undefined \
+ -Wl,--whole-archive $(libdebuginfod_so_LIBS) -Wl,--no-whole-archive \
+ $(libdebuginfod_so_LDLIBS)
+ @$(textrel_check)
+ $(AM_V_at)ln -fs $@ $@.$(VERSION)
+
+install: install-am libdebuginfod.so
+ $(mkinstalldirs) $(DESTDIR)$(libdir)
+ $(INSTALL_PROGRAM) libdebuginfod.so $(DESTDIR)$(libdir)/libdebuginfod-$(PACKAGE_VERSION).so
+ ln -fs libdebuginfod-$(PACKAGE_VERSION).so $(DESTDIR)$(libdir)/libdebuginfod.so.$(VERSION)
+ ln -fs libdebuginfod.so.$(VERSION) $(DESTDIR)$(libdir)/libdebuginfod.so
+
+uninstall: uninstall-am
+ rm -f $(DESTDIR)$(libdir)/libdebuginfod-$(PACKAGE_VERSION).so
+ rm -f $(DESTDIR)$(libdir)/libdebuginfod.so.$(VERSION)
+ rm -f $(DESTDIR)$(libdir)/libdebuginfod.so
+ rmdir --ignore-fail-on-non-empty $(DESTDIR)$(includedir)/elfutils
+
+EXTRA_DIST = libdebuginfod.map
+MOSTLYCLEANFILES = $(am_libdebuginfod_pic_a_OBJECTS) libdebuginfod.so.$(VERSION)
+CLEANFILES += $(am_libdebuginfod_pic_a_OBJECTS) libdebuginfod.so
+
+# automake std-options override: arrange to pass LD_LIBRARY_PATH
+installcheck-binPROGRAMS: $(bin_PROGRAMS)
+ bad=0; pid=$$$$; list="$(bin_PROGRAMS)"; for p in $$list; do \
+ case ' $(AM_INSTALLCHECK_STD_OPTIONS_EXEMPT) ' in \
+ *" $$p "* | *" $(srcdir)/$$p "*) continue;; \
+ esac; \
+ f=`echo "$$p" | \
+ sed 's,^.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/'`; \
+ for opt in --help --version; do \
+ if LD_LIBRARY_PATH=$(DESTDIR)$(libdir) \
+ $(DESTDIR)$(bindir)/$$f $$opt > c$${pid}_.out 2> c$${pid}_.err \
+ && test -n "`cat c$${pid}_.out`" \
+ && test -z "`cat c$${pid}_.err`"; then :; \
+ else echo "$$f does not support $$opt" 1>&2; bad=1; fi; \
+ done; \
+ done; rm -f c$${pid}_.???; exit $$bad
diff --git a/debuginfod/debuginfod-client.c b/debuginfod/debuginfod-client.c
new file mode 100644
index 00000000..1aa9edac
--- /dev/null
+++ b/debuginfod/debuginfod-client.c
@@ -0,0 +1,665 @@
+/* Retrieve ELF / DWARF / source files from the debuginfod.
+ Copyright (C) 2019 Red Hat, Inc.
+ This file is part of elfutils.
+
+ This file is free software; you can redistribute it and/or modify
+ it under the terms of either
+
+ * the GNU Lesser General Public License as published by the Free
+ Software Foundation; either version 3 of the License, or (at
+ your option) any later version
+
+ or
+
+ * the GNU General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at
+ your option) any later version
+
+ or both in parallel, as here.
+
+ elfutils is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received copies of the GNU General Public License and
+ the GNU Lesser General Public License along with this program. If
+ not, see <http://www.gnu.org/licenses/>. */
+
+
+/* cargo-cult from libdwfl linux-kernel-modules.c */
+/* In case we have a bad fts we include this before config.h because it
+ can't handle _FILE_OFFSET_BITS.
+ Everything we need here is fine if its declarations just come first.
+ Also, include sys/types.h before fts. On some systems fts.h is not self
+ contained. */
+#ifdef BAD_FTS
+ #include <sys/types.h>
+ #include <fts.h>
+#endif
+
+#include "config.h"
+#include "debuginfod.h"
+#include <assert.h>
+#include <dirent.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <fts.h>
+#include <string.h>
+#include <stdbool.h>
+#include <linux/limits.h>
+#include <time.h>
+#include <utime.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <curl/curl.h>
+
+/* If fts.h is included before config.h, its indirect inclusions may not
+ give us the right LFS aliases of these functions, so map them manually. */
+#ifdef BAD_FTS
+ #ifdef _FILE_OFFSET_BITS
+ #define open open64
+ #define fopen fopen64
+ #endif
+#else
+ #include <sys/types.h>
+ #include <fts.h>
+#endif
+
+
+/* The cache_clean_interval_s file within the debuginfod cache specifies
+ how frequently the cache should be cleaned. The file's st_mtime represents
+ the time of last cleaning. */
+static const char *cache_clean_interval_filename = "cache_clean_interval_s";
+static const time_t cache_clean_default_interval_s = 86400; /* 1 day */
+
+/* The cache_max_unused_age_s file within the debuginfod cache specifies the
+ the maximum time since last access that a file will remain in the cache. */
+static const char *cache_max_unused_age_filename = "max_unused_age_s";
+static const time_t cache_default_max_unused_age_s = 604800; /* 1 week */
+
+/* Location of the cache of files downloaded from debuginfods.
+ The default parent directory is $HOME, or '/' if $HOME doesn't exist. */
+static const char *cache_default_name = ".debuginfod_client_cache";
+static const char *cache_path_envvar = DEBUGINFOD_CACHE_PATH_ENV_VAR;
+
+/* URLs of debuginfods, separated by url_delim.
+ This env var must be set for debuginfod-client to run. */
+static const char *server_urls_envvar = DEBUGINFOD_URLS_ENV_VAR;
+static const char *url_delim = " ";
+static const char url_delim_char = ' ';
+
+/* Timeout for debuginfods, in seconds.
+ This env var must be set for debuginfod-client to run. */
+static const char *server_timeout_envvar = DEBUGINFOD_TIMEOUT_ENV_VAR;
+static int server_timeout = 5;
+
+/* Data associated with a particular CURL easy handle. Passed to
+ the write callback. */
+struct handle_data
+{
+ /* Cache file to be written to in case query is successful. */
+ int fd;
+
+ /* URL queried by this handle. */
+ char url[PATH_MAX];
+
+ /* This handle. */
+ CURL *handle;
+
+ /* Pointer to handle that should write to fd. Initially points to NULL,
+ then points to the first handle that begins writing the target file
+ to the cache. Used to ensure that a file is not downloaded from
+ multiple servers unnecessarily. */
+ CURL **target_handle;
+};
+
+static size_t
+debuginfod_write_callback (char *ptr, size_t size, size_t nmemb, void *data)
+{
+ ssize_t count = size * nmemb;
+
+ struct handle_data *d = (struct handle_data*)data;
+
+ /* Indicate to other handles that they can abort their transfer. */
+ if (*d->target_handle == NULL)
+ *d->target_handle = d->handle;
+
+ /* If this handle isn't the target handle, abort transfer. */
+ if (*d->target_handle != d->handle)
+ return -1;
+
+ return (size_t) write(d->fd, (void*)ptr, count);
+}
+
+/* Create the cache and interval file if they do not already exist.
+ Return 0 if cache and config file are initialized, otherwise return
+ the appropriate error code. */
+static int
+debuginfod_init_cache (char *cache_path, char *interval_path, char *maxage_path)
+{
+ struct stat st;
+
+ /* If the cache and config file already exist then we are done. */
+ if (stat(cache_path, &st) == 0 && stat(interval_path, &st) == 0)
+ return 0;
+
+ /* Create the cache and config files as necessary. */
+ if (stat(cache_path, &st) != 0 && mkdir(cache_path, 0777) < 0)
+ return -errno;
+
+ int fd = -1;
+
+ /* init cleaning interval config file. */
+ fd = open(interval_path, O_CREAT | O_RDWR, 0666);
+ if (fd < 0)
+ return -errno;
+
+ if (dprintf(fd, "%ld", cache_clean_default_interval_s) < 0)
+ return -errno;
+
+ /* init max age config file. */
+ if (stat(maxage_path, &st) != 0
+ && (fd = open(maxage_path, O_CREAT | O_RDWR, 0666)) < 0)
+ return -errno;
+
+ if (dprintf(fd, "%ld", cache_default_max_unused_age_s) < 0)
+ return -errno;
+
+ return 0;
+}
+
+
+/* Delete any files that have been unmodied for a period
+ longer than $DEBUGINFOD_CACHE_CLEAN_INTERVAL_S. */
+static int
+debuginfod_clean_cache(char *cache_path, char *interval_path, char *max_unused_path)
+{
+ struct stat st;
+ FILE *interval_file;
+ FILE *max_unused_file;
+
+ if (stat(interval_path, &st) == -1)
+ {
+ /* Create new interval file. */
+ interval_file = fopen(interval_path, "w");
+
+ if (interval_file == NULL)
+ return -errno;
+
+ int rc = fprintf(interval_file, "%ld", cache_clean_default_interval_s);
+ fclose(interval_file);
+
+ if (rc < 0)
+ return -errno;
+ }
+
+ /* Check timestamp of interval file to see whether cleaning is necessary. */
+ time_t clean_interval;
+ interval_file = fopen(interval_path, "r");
+ if (fscanf(interval_file, "%ld", &clean_interval) != 1)
+ clean_interval = cache_clean_default_interval_s;
+ fclose(interval_file);
+
+ if (time(NULL) - st.st_mtime < clean_interval)
+ /* Interval has not passed, skip cleaning. */
+ return 0;
+
+ /* Read max unused age value from config file. */
+ time_t max_unused_age;
+ max_unused_file = fopen(max_unused_path, "r");
+ if (max_unused_file)
+ {
+ if (fscanf(max_unused_file, "%ld", &max_unused_age) != 1)
+ max_unused_age = cache_default_max_unused_age_s;
+ fclose(max_unused_file);
+ }
+ else
+ max_unused_age = cache_default_max_unused_age_s;
+
+ char * const dirs[] = { cache_path, NULL, };
+
+ FTS *fts = fts_open(dirs, 0, NULL);
+ if (fts == NULL)
+ return -errno;
+
+ FTSENT *f;
+ while ((f = fts_read(fts)) != NULL)
+ {
+ switch (f->fts_info)
+ {
+ case FTS_F:
+ /* delete file if max_unused_age has been met or exceeded. */
+ /* XXX consider extra effort to clean up old tmp files */
+ if (time(NULL) - f->fts_statp->st_atime >= max_unused_age)
+ unlink (f->fts_path);
+ break;
+
+ case FTS_DP:
+ /* Remove if empty. */
+ (void) rmdir (f->fts_path);
+ break;
+
+ default:
+ ;
+ }
+ }
+ fts_close(fts);
+
+ /* Update timestamp representing when the cache was last cleaned. */
+ utime (interval_path, NULL);
+ return 0;
+}
+
+
+#define MAX_BUILD_ID_BYTES 64
+
+
+/* Query each of the server URLs found in $DEBUGINFOD_URLS for the file
+ with the specified build-id, type (debuginfo, executable or source)
+ and filename. filename may be NULL. If found, return a file
+ descriptor for the target, otherwise return an error code. */
+static int
+debuginfod_query_server (const unsigned char *build_id,
+ int build_id_len,
+ const char *type,
+ const char *filename,
+ char **path)
+{
+ char *urls_envvar;
+ char *server_urls;
+ char cache_path[PATH_MAX];
+ char maxage_path[PATH_MAX*3]; /* These *3 multipliers are to shut up gcc -Wformat-truncation */
+ char interval_path[PATH_MAX*4];
+ char target_cache_dir[PATH_MAX*2];
+ char target_cache_path[PATH_MAX*4];
+ char target_cache_tmppath[PATH_MAX*5];
+ char suffix[PATH_MAX*2];
+ char build_id_bytes[MAX_BUILD_ID_BYTES * 2 + 1];
+
+ /* Copy lowercase hex representation of build_id into buf. */
+ if ((build_id_len >= MAX_BUILD_ID_BYTES) ||
+ (build_id_len == 0 &&
+ sizeof(build_id_bytes) > MAX_BUILD_ID_BYTES*2 + 1))
+ return -EINVAL;
+ if (build_id_len == 0) /* expect clean hexadecimal */
+ strcpy (build_id_bytes, (const char *) build_id);
+ else
+ for (int i = 0; i < build_id_len; i++)
+ sprintf(build_id_bytes + (i * 2), "%02x", build_id[i]);
+
+ if (filename != NULL)
+ {
+ if (filename[0] != '/') // must start with /
+ return -EINVAL;
+
+ /* copy the filename to suffix, s,/,#,g */
+ unsigned q = 0;
+ for (unsigned fi=0; q < PATH_MAX-1; fi++)
+ switch (filename[fi])
+ {
+ case '\0':
+ suffix[q] = '\0';
+ q = PATH_MAX-1; /* escape for loop too */
+ break;
+ case '/': /* escape / to prevent dir escape */
+ suffix[q++]='#';
+ suffix[q++]='#';
+ break;
+ case '#': /* escape # to prevent /# vs #/ collisions */
+ suffix[q++]='#';
+ suffix[q++]='_';
+ break;
+ default:
+ suffix[q++]=filename[fi];
+ }
+ suffix[q] = '\0';
+ /* If the DWARF filenames are super long, this could exceed
+ PATH_MAX and truncate/collide. Oh well, that'll teach
+ them! */
+ }
+ else
+ suffix[0] = '\0';
+
+ /* set paths needed to perform the query
+
+ example format
+ cache_path: $HOME/.debuginfod_cache
+ target_cache_dir: $HOME/.debuginfod_cache/0123abcd
+ target_cache_path: $HOME/.debuginfod_cache/0123abcd/debuginfo
+ target_cache_path: $HOME/.debuginfod_cache/0123abcd/source#PATH#TO#SOURCE ?
+ */
+
+ if (getenv(cache_path_envvar))
+ strcpy(cache_path, getenv(cache_path_envvar));
+ else
+ {
+ if (getenv("HOME"))
+ sprintf(cache_path, "%s/%s", getenv("HOME"), cache_default_name);
+ else
+ sprintf(cache_path, "/%s", cache_default_name);
+ }
+
+ /* avoid using snprintf here due to compiler warning. */
+ snprintf(target_cache_dir, sizeof(target_cache_dir), "%s/%s", cache_path, build_id_bytes);
+ snprintf(target_cache_path, sizeof(target_cache_path), "%s/%s%s", target_cache_dir, type, suffix);
+ snprintf(target_cache_tmppath, sizeof(target_cache_tmppath), "%s.XXXXXX", target_cache_path);
+
+ /* XXX combine these */
+ snprintf(interval_path, sizeof(interval_path), "%s/%s", cache_path, cache_clean_interval_filename);
+ snprintf(maxage_path, sizeof(maxage_path), "%s/%s", cache_path, cache_max_unused_age_filename);
+ int rc = debuginfod_init_cache(cache_path, interval_path, maxage_path);
+ if (rc != 0)
+ goto out;
+ rc = debuginfod_clean_cache(cache_path, interval_path, maxage_path);
+ if (rc != 0)
+ goto out;
+
+ /* If the target is already in the cache then we are done. */
+ int fd = open (target_cache_path, O_RDONLY);
+ if (fd >= 0)
+ {
+ /* Success!!!! */
+ if (path != NULL)
+ *path = strdup(target_cache_path);
+ return fd;
+ }
+
+
+ urls_envvar = getenv(server_urls_envvar);
+ if (urls_envvar == NULL || urls_envvar[0] == '\0')
+ {
+ rc = -ENOSYS;
+ goto out;
+ }
+
+ if (getenv(server_timeout_envvar))
+ server_timeout = atoi (getenv(server_timeout_envvar));
+
+ /* make a copy of the envvar so it can be safely modified. */
+ server_urls = strdup(urls_envvar);
+ if (server_urls == NULL)
+ {
+ rc = -ENOMEM;
+ goto out;
+ }
+ /* thereafter, goto out0 on error*/
+
+ /* create target directory in cache if not found. */
+ struct stat st;
+ if (stat(target_cache_dir, &st) == -1 && mkdir(target_cache_dir, 0700) < 0)
+ {
+ rc = -errno;
+ goto out0;
+ }
+
+ /* NB: write to a temporary file first, to avoid race condition of
+ multiple clients checking the cache, while a partially-written or empty
+ file is in there, being written from libcurl. */
+ fd = mkstemp (target_cache_tmppath);
+ if (fd < 0)
+ {
+ rc = -errno;
+ goto out0;
+ }
+
+ /* Count number of URLs. */
+ int num_urls = 0;
+ for (int i = 0; server_urls[i] != '\0'; i++)
+ if (server_urls[i] != url_delim_char
+ && (i == 0 || server_urls[i - 1] == url_delim_char))
+ num_urls++;
+
+ /* Tracks which handle should write to fd. Set to the first
+ handle that is ready to write the target file to the cache. */
+ CURL *target_handle = NULL;
+ struct handle_data *data = malloc(sizeof(struct handle_data) * num_urls);
+
+ /* Initalize handle_data with default values. */
+ for (int i = 0; i < num_urls; i++)
+ {
+ data[i].handle = NULL;
+ data[i].fd = -1;
+ }
+
+ CURLM *curlm = curl_multi_init();
+ if (curlm == NULL)
+ {
+ rc = -ENETUNREACH;
+ goto out0;
+ }
+ /* thereafter, goto out1 on error. */
+
+ char *strtok_saveptr;
+ char *server_url = strtok_r(server_urls, url_delim, &strtok_saveptr);
+
+ /* Initialize each handle. */
+ for (int i = 0; i < num_urls && server_url != NULL; i++)
+ {
+ data[i].fd = fd;
+ data[i].target_handle = &target_handle;
+ data[i].handle = curl_easy_init();
+
+ if (data[i].handle == NULL)
+ {
+ rc = -ENETUNREACH;
+ goto out1;
+ }
+
+ /* Build handle url. Tolerate both http://foo:999 and
+ http://foo:999/ forms */
+ char *slashbuildid;
+ if (strlen(server_url) > 1 && server_url[strlen(server_url)-1] == '/')
+ slashbuildid = "buildid";
+ else
+ slashbuildid = "/buildid";
+
+ if (filename) /* must start with / */
+ snprintf(data[i].url, PATH_MAX, "%s%s/%s/%s%s", server_url,
+ slashbuildid, build_id_bytes, type, filename);
+ else
+ snprintf(data[i].url, PATH_MAX, "%s%s/%s/%s", server_url,
+ slashbuildid, build_id_bytes, type);
+
+ curl_easy_setopt(data[i].handle, CURLOPT_URL, data[i].url);
+ curl_easy_setopt(data[i].handle,
+ CURLOPT_WRITEFUNCTION,
+ debuginfod_write_callback);
+ curl_easy_setopt(data[i].handle, CURLOPT_WRITEDATA, (void*)&data[i]);
+ curl_easy_setopt(data[i].handle, CURLOPT_TIMEOUT, (long) server_timeout);
+ curl_easy_setopt(data[i].handle, CURLOPT_FILETIME, (long) 1);
+ curl_easy_setopt(data[i].handle, CURLOPT_FOLLOWLOCATION, (long) 1);
+ curl_easy_setopt(data[i].handle, CURLOPT_FAILONERROR, (long) 1);
+ curl_easy_setopt(data[i].handle, CURLOPT_NOSIGNAL, (long) 1);
+ curl_easy_setopt(data[i].handle, CURLOPT_AUTOREFERER, (long) 1);
+ curl_easy_setopt(data[i].handle, CURLOPT_ACCEPT_ENCODING, "");
+ curl_easy_setopt(data[i].handle, CURLOPT_USERAGENT, (void*) PACKAGE_STRING);
+
+ curl_multi_add_handle(curlm, data[i].handle);
+ server_url = strtok_r(NULL, url_delim, &strtok_saveptr);
+ }
+
+ /* Query servers in parallel. */
+ int still_running;
+ do
+ {
+ CURLMcode curl_res;
+
+ /* Wait 1 second, the minimum DEBUGINFOD_TIMEOUT. */
+ curl_multi_wait(curlm, NULL, 0, 1000, NULL);
+
+ /* If the target file has been found, abort the other queries. */
+ if (target_handle != NULL)
+ for (int i = 0; i < num_urls; i++)
+ if (data[i].handle != target_handle)
+ curl_multi_remove_handle(curlm, data[i].handle);
+
+ curl_res = curl_multi_perform(curlm, &still_running);
+ if (curl_res != CURLM_OK)
+ {
+ switch (curl_res)
+ {
+ case CURLM_CALL_MULTI_PERFORM: continue;
+ case CURLM_OUT_OF_MEMORY: rc = -ENOMEM; break;
+ default: rc = -ENETUNREACH; break;
+ }
+ goto out1;
+ }
+ } while (still_running);
+
+ /* Check whether a query was successful. If so, assign its handle
+ to verified_handle. */
+ int num_msg;
+ rc = -ENOENT;
+ CURL *verified_handle = NULL;
+ do
+ {
+ CURLMsg *msg;
+
+ msg = curl_multi_info_read(curlm, &num_msg);
+ if (msg != NULL && msg->msg == CURLMSG_DONE)
+ {
+ if (msg->data.result != CURLE_OK)
+ {
+ /* Unsucessful query, determine error code. */
+ switch (msg->data.result)
+ {
+ case CURLE_COULDNT_RESOLVE_HOST: rc = -EHOSTUNREACH; break; // no NXDOMAIN
+ case CURLE_URL_MALFORMAT: rc = -EINVAL; break;
+ case CURLE_COULDNT_CONNECT: rc = -ECONNREFUSED; break;
+ case CURLE_REMOTE_ACCESS_DENIED: rc = -EACCES; break;
+ case CURLE_WRITE_ERROR: rc = -EIO; break;
+ case CURLE_OUT_OF_MEMORY: rc = -ENOMEM; break;
+ case CURLE_TOO_MANY_REDIRECTS: rc = -EMLINK; break;
+ case CURLE_SEND_ERROR: rc = -ECONNRESET; break;
+ case CURLE_RECV_ERROR: rc = -ECONNRESET; break;
+ case CURLE_OPERATION_TIMEDOUT: rc = -ETIME; break;
+ default: rc = -ENOENT; break;
+ }
+ }
+ else
+ {
+ /* Query completed without an error. Confirm that the
+ response code is 200 and set verified_handle. */
+ long resp_code = 500;
+ CURLcode curl_res;
+
+ curl_res = curl_easy_getinfo(target_handle,
+ CURLINFO_RESPONSE_CODE,
+ &resp_code);
+
+ if (curl_res == CURLE_OK
+ && resp_code == 200
+ && msg->easy_handle != NULL)
+ {
+ verified_handle = msg->easy_handle;
+ break;
+ }
+ }
+ }
+ } while (num_msg > 0);
+
+ if (verified_handle == NULL)
+ goto out1;
+
+ /* we've got one!!!! */
+ time_t mtime;
+ CURLcode curl_res = curl_easy_getinfo(verified_handle, CURLINFO_FILETIME, (void*) &mtime);
+ if (curl_res != CURLE_OK)
+ mtime = time(NULL); /* fall back to current time */
+
+ struct timeval tvs[2];
+ tvs[0].tv_sec = tvs[1].tv_sec = mtime;
+ tvs[0].tv_usec = tvs[1].tv_usec = 0;
+ (void) futimes (fd, tvs); /* best effort */
+
+ /* rename tmp->real */
+ rc = rename (target_cache_tmppath, target_cache_path);
+ if (rc < 0)
+ {
+ rc = -errno;
+ goto out1;
+ /* Perhaps we need not give up right away; could retry or something ... */
+ }
+
+ /* Success!!!! */
+ for (int i = 0; i < num_urls; i++)
+ curl_easy_cleanup(data[i].handle);
+
+ curl_multi_cleanup (curlm);
+ free (data);
+ free (server_urls);
+ /* don't close fd - we're returning it */
+ /* don't unlink the tmppath; it's already been renamed. */
+ if (path != NULL)
+ *path = strdup(target_cache_path);
+
+ return fd;
+
+/* error exits */
+ out1:
+ for (int i = 0; i < num_urls; i++)
+ curl_easy_cleanup(data[i].handle);
+
+ curl_multi_cleanup(curlm);
+ unlink (target_cache_tmppath);
+ (void) rmdir (target_cache_dir); /* nop if not empty */
+ free(data);
+ close (fd);
+
+ out0:
+ free (server_urls);
+
+ out:
+ return rc;
+}
+
+
+/* See debuginfod.h */
+int
+debuginfod_find_debuginfo (const unsigned char *build_id, int build_id_len,
+ char **path)
+{
+ return debuginfod_query_server(build_id, build_id_len,
+ "debuginfo", NULL, path);
+}
+
+
+/* See debuginfod.h */
+int
+debuginfod_find_executable(const unsigned char *build_id, int build_id_len,
+ char **path)
+{
+ return debuginfod_query_server(build_id, build_id_len,
+ "executable", NULL, path);
+}
+
+/* See debuginfod.h */
+int debuginfod_find_source(const unsigned char *build_id,
+ int build_id_len,
+ const char *filename,
+ char **path)
+{
+ return debuginfod_query_server(build_id, build_id_len,
+ "source", filename, path);
+}
+
+
+
+/* NB: these are thread-unsafe. */
+__attribute__((constructor)) attribute_hidden void libdebuginfod_ctor(void)
+{
+ curl_global_init(CURL_GLOBAL_DEFAULT);
+}
+
+/* NB: this is very thread-unsafe: it breaks other threads that are still in libcurl */
+__attribute__((destructor)) attribute_hidden void libdebuginfod_dtor(void)
+{
+ /* ... so don't do this: */
+ /* curl_global_cleanup(); */
+}
diff --git a/debuginfod/debuginfod-find.c b/debuginfod/debuginfod-find.c
new file mode 100644
index 00000000..ff47b807
--- /dev/null
+++ b/debuginfod/debuginfod-find.c
@@ -0,0 +1,101 @@
+/* Command-line frontend for retrieving ELF / DWARF / source files
+ from the debuginfod.
+ Copyright (C) 2019 Red Hat, Inc.
+ This file is part of elfutils.
+
+ This file is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ elfutils is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received copies of the GNU General Public License and
+ the GNU Lesser General Public License along with this program. If
+ not, see <http://www.gnu.org/licenses/>. */
+
+#include "config.h"
+#include "printversion.h"
+#include "debuginfod.h"
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <argp.h>
+
+
+/* Name and version of program. */
+ARGP_PROGRAM_VERSION_HOOK_DEF = print_version;
+
+/* Bug report address. */
+ARGP_PROGRAM_BUG_ADDRESS_DEF = PACKAGE_BUGREPORT;
+
+/* Short description of program. */
+static const char doc[] = N_("Request debuginfo-related content "
+ "from debuginfods listed in $" DEBUGINFOD_URLS_ENV_VAR ".");
+
+/* Strings for arguments in help texts. */
+static const char args_doc[] = N_("debuginfo BUILDID\n"
+ "executable BUILDID\n"
+ "source BUILDID /FILENAME");
+
+/* Data structure to communicate with argp functions. */
+static struct argp argp =
+ {
+ NULL, NULL, args_doc, doc, NULL, NULL, NULL
+ };
+
+
+
+int
+main(int argc, char** argv)
+{
+ int remaining;
+ (void) argp_parse (&argp, argc, argv, ARGP_IN_ORDER|ARGP_NO_ARGS, &remaining, NULL);
+
+ if (argc < 2 || remaining+1 == argc) /* no arguments or at least two non-option words */
+ {
+ argp_help (&argp, stderr, ARGP_HELP_USAGE, argv[0]);
+ return 1;
+ }
+
+ int rc;
+ char *cache_name;
+
+ /* Check whether FILETYPE is valid and call the appropriate
+ debuginfod_find_* function. If FILETYPE is "source"
+ then ensure a FILENAME was also supplied as an argument. */
+ if (strcmp(argv[remaining], "debuginfo") == 0)
+ rc = debuginfod_find_debuginfo((unsigned char *)argv[remaining+1], 0, &cache_name);
+ else if (strcmp(argv[remaining], "executable") == 0)
+ rc = debuginfod_find_executable((unsigned char *)argv[remaining+1], 0, &cache_name);
+ else if (strcmp(argv[remaining], "source") == 0)
+ {
+ if (remaining+2 == argc || argv[3][0] != '/')
+ {
+ fprintf(stderr, "If FILETYPE is \"source\" then absolute /FILENAME must be given\n");
+ return 1;
+ }
+ rc = debuginfod_find_source((unsigned char *)argv[remaining+1], 0,
+ argv[remaining+2], &cache_name);
+ }
+ else
+ {
+ argp_help (&argp, stderr, ARGP_HELP_USAGE, argv[0]);
+ return 1;
+ }
+
+ if (rc < 0)
+ {
+ fprintf(stderr, "Server query failed: %s\n", strerror(-rc));
+ return 1;
+ }
+
+ printf("%s\n", cache_name);
+
+ free (cache_name);
+ return 0;
+}
diff --git a/debuginfod/debuginfod.h b/debuginfod/debuginfod.h
new file mode 100644
index 00000000..d2bb0c94
--- /dev/null
+++ b/debuginfod/debuginfod.h
@@ -0,0 +1,69 @@
+/* External declarations for the libdebuginfod client library.
+ Copyright (C) 2019 Red Hat, Inc.
+ This file is part of elfutils.
+
+ This file is free software; you can redistribute it and/or modify
+ it under the terms of either
+
+ * the GNU Lesser General Public License as published by the Free
+ Software Foundation; either version 3 of the License, or (at
+ your option) any later version
+
+ or
+
+ * the GNU General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at
+ your option) any later version
+
+ or both in parallel, as here.
+
+ elfutils is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received copies of the GNU General Public License and
+ the GNU Lesser General Public License along with this program. If
+ not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef _DEBUGINFOD_CLIENT_H
+#define _DEBUGINFOD_CLIENT_H 1
+
+/* Names of environment variables that control the client logic. */
+#define DEBUGINFOD_URLS_ENV_VAR "DEBUGINFOD_URLS"
+#define DEBUGINFOD_CACHE_PATH_ENV_VAR "DEBUGINFOD_CACHE_PATH"
+#define DEBUGINFOD_TIMEOUT_ENV_VAR "DEBUGINFOD_TIMEOUT"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Query the urls contained in $DEBUGINFOD_URLS for a file with
+ the specified type and build id. If build_id_len == 0, the
+ build_id is supplied as a lowercase hexadecimal string; otherwise
+ it is a binary blob of given legnth.
+
+ If successful, return a file descriptor to the target, otherwise
+ return a posix error code. If successful, set *path to a
+ strdup'd copy of the name of the same file in the cache.
+ Caller must free() it later. */
+
+int debuginfod_find_debuginfo (const unsigned char *build_id,
+ int build_id_len,
+ char **path);
+
+int debuginfod_find_executable (const unsigned char *build_id,
+ int build_id_len,
+ char **path);
+
+int debuginfod_find_source (const unsigned char *build_id,
+ int build_id_len,
+ const char *filename,
+ char **path);
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif /* _DEBUGINFOD_CLIENT_H */
diff --git a/debuginfod/libdebuginfod.map b/debuginfod/libdebuginfod.map
new file mode 100644
index 00000000..4d3daf32
--- /dev/null
+++ b/debuginfod/libdebuginfod.map
@@ -0,0 +1,7 @@
+ELFUTILS_0 { };
+ELFUTILS_0.178 {
+ global:
+ debuginfod_find_debuginfo;
+ debuginfod_find_executable;
+ debuginfod_find_source;
+} ELFUTILS_0;
diff --git a/doc/Makefile.am b/doc/Makefile.am
index d6f3eca8..23102604 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -1,7 +1,7 @@
## Process this file with automake to create Makefile.in
## Configure input file for elfutils.
##
-## Copyright (C) 1996-2001, 2002, 2005 Red Hat, Inc.
+## Copyright (C) 1996-2001, 2002, 2005, 2019 Red Hat, Inc.
## This file is part of elfutils.
##
## This file is free software; you can redistribute it and/or modify
@@ -19,3 +19,11 @@
EXTRA_DIST = COPYING-GFDL README
dist_man1_MANS=readelf.1 elfclassify.1
notrans_dist_man3_MANS=elf_update.3 elf_getdata.3 elf_clone.3 elf_begin.3
+
+notrans_dist_man1_MANS=
+
+if DEBUGINFOD
+notrans_dist_man3_MANS += debuginfod_find_debuginfo.3 debuginfod_find_source.3 debuginfod_find_executable.3
+notrans_dist_man1_MANS += debuginfod-find.1
+endif
+
diff --git a/doc/debuginfod-find.1 b/doc/debuginfod-find.1
new file mode 100644
index 00000000..ab2f4d17
--- /dev/null
+++ b/doc/debuginfod-find.1
@@ -0,0 +1,137 @@
+'\"! 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, followed by a mandatory "/".
+
+Note: the user should not elide \fB../\fP or \fB/./\fP or extraneous
+\fB///\fP sorts of 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. Authentication information through
+the internal \fIlibcurl\fP library is not currently enabled, except
+for the basic plaintext \%\fIhttp[s]://userid:password@hostname/\fP style.
+(The debuginfod server does not perform authentication, but a front-end
+proxy server could.)
+
+.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. Cache management parameters may be set by files under
+this directory: see the \fBdebuginfod_find_debuginfo(3)\fP man page
+for details. The default is $HOME/.debuginfod_client_cache.
+
+.SH "FILES"
+.LP
+.PD .1v
+.TP 20
+.B $HOME/.debuginfod_client_cache
+Default cache directory.
+.PD
+
+.SH "SEE ALSO"
+.I "debuginfod(8)"
+.I "debuginfod_find_debuginfod(3)"
diff --git a/doc/debuginfod_find_debuginfo.3 b/doc/debuginfod_find_debuginfo.3
new file mode 100644
index 00000000..e0923d8d
--- /dev/null
+++ b/doc/debuginfod_find_debuginfo.3
@@ -0,0 +1,182 @@
+'\"! 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, followed by a mandatory "/".
+
+Note: the caller should not elide \fB../\fP or \fB/./\fP or extraneous
+\fB///\fP sorts of 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.
+
+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 (fstat(2) atime) before it's
+removed from the cache during cleaning. These files should contain only an
+ASCII decimal integer representing the interval or max unused age in seconds.
+The default is one day and one week, respectively. Values of zero mean "immediately".
+
+.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. Passing user authentication information
+through the internal \fIlibcurl\fP library is not currently enabled, except
+for the basic plaintext \%\fIhttp[s]://userid:password@hostname/\fP style.
+(The debuginfod server does not perform authentication, but a front-end
+proxy server could.)
+
+.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/doc/debuginfod_find_executable.3 b/doc/debuginfod_find_executable.3
new file mode 100644
index 00000000..16279936
--- /dev/null
+++ b/doc/debuginfod_find_executable.3
@@ -0,0 +1 @@
+.so man3/debuginfod_find_debuginfo.3
diff --git a/doc/debuginfod_find_source.3 b/doc/debuginfod_find_source.3
new file mode 100644
index 00000000..16279936
--- /dev/null
+++ b/doc/debuginfod_find_source.3
@@ -0,0 +1 @@
+.so man3/debuginfod_find_debuginfo.3
diff --git a/libdw/ChangeLog b/libdw/ChangeLog
index 95ac28a7..59f33f9e 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-11-10 Mark Wielaard <mark@klomp.org>
* libdwP.h (libdw_unalloc): New define.
diff --git a/libdw/Makefile.am b/libdw/Makefile.am
index ce793e90..33b5838d 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 07a1e8df..b8222189 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-10-07 Omar Sandoval <osandov@fb.com>
* dwfl_frame.c (dwfl_getthreads): Get rid of unnecessary
diff --git a/libdwfl/Makefile.am b/libdwfl/Makefile.am
index 89ca92ed..29046e9e 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 cc6c3f62..b775f50a 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,33 @@ dwfl_build_id_find_elf (Dwfl_Module *mod,
free (*file_name);
*file_name = NULL;
}
- else if (errno == 0 && mod->build_id_len > 0)
+ else {
+ /* NB: this is slightly thread-unsafe */
+ static __typeof__ (debuginfod_find_executable) *fp_debuginfod_find_executable;
+
+ if (fp_debuginfod_find_executable == NULL)
+ {
+ void *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 = dlsym (debuginfod_so, "debuginfod_find_executable");
+ if (fp_debuginfod_find_executable == NULL)
+ fp_debuginfod_find_executable = (void *) -1; /* never try again */
+ }
+
+ if (fp_debuginfod_find_executable != NULL && fp_debuginfod_find_executable != (void *) -1)
+ {
+ /* 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);
+ }
+ }
+
+ 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 9267788d..ffc07f92 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,11 @@
#endif
#include "libdwflP.h"
+#include "debuginfod.h"
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
+#include <dlfcn.h>
#include <sys/stat.h>
#include "system.h"
@@ -359,7 +361,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 +400,30 @@ dwfl_standard_find_debuginfo (Dwfl_Module *mod,
free (canon);
}
+ {
+ /* NB: this is slightly thread-unsafe */
+ static __typeof__ (debuginfod_find_debuginfo) *fp_debuginfod_find_debuginfo;
+
+ if (fp_debuginfod_find_debuginfo == NULL)
+ {
+ void *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 = dlsym (debuginfod_so, "debuginfod_find_debuginfo");
+ if (fp_debuginfod_find_debuginfo == NULL)
+ fp_debuginfod_find_debuginfo = (void *) -1; /* never try again */
+ }
+
+ if (fp_debuginfod_find_debuginfo != NULL && fp_debuginfod_find_debuginfo != (void *) -1)
+ {
+ /* 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);
+ }
+ }
+
return fd;
}
INTDEF (dwfl_standard_find_debuginfo)
diff --git a/m4/ChangeLog b/m4/ChangeLog
index 9ee06d75..8ab0ff39 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 3b0e1145..ae7a5657 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 00000000..ca363971
--- /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 00000000..8adc7656
--- /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
+
+]])