From e27e30cae0468903473641efe3853c12d9294ac3 Mon Sep 17 00:00:00 2001 From: "Frank Ch. Eigler" Date: Mon, 28 Oct 2019 13:29:26 -0400 Subject: [PATCH] debuginfod 2/2: server side Add the server to the debuginfod/ subdirectory. This is a highly multithreaded c++11 program (still buildable on rhel7's gcc 4.8, which is only partly c++11 compliant). Includes an initial suite of tests, man pages, and a sample systemd service. Signed-off-by: Frank Ch. Eigler Signed-off-by: Aaron Merey --- config/ChangeLog | 7 + config/Makefile.am | 5 +- config/debuginfod.service | 15 + config/debuginfod.sysconfig | 14 + config/elfutils.spec.in | 100 +- config/eu.am | 10 + configure.ac | 36 +- debuginfod/ChangeLog | 6 + debuginfod/Makefile.am | 8 +- debuginfod/debuginfod.cxx | 2514 ++++++++++++++++++++ doc/Makefile.am | 3 +- doc/debuginfod.8 | 369 +++ tests/ChangeLog | 7 + tests/Makefile.am | 30 +- .../debuginfod-rpms/fedora30/hello2-1.0-2.src.rpm | Bin 0 -> 8087 bytes .../fedora30/hello2-1.0-2.x86_64.rpm | Bin 0 -> 10448 bytes .../fedora30/hello2-debuginfo-1.0-2.x86_64.rpm | Bin 0 -> 11316 bytes .../fedora30/hello2-debugsource-1.0-2.x86_64.rpm | Bin 0 -> 7308 bytes .../fedora30/hello2-two-1.0-2.x86_64.rpm | Bin 0 -> 10380 bytes .../fedora30/hello2-two-debuginfo-1.0-2.x86_64.rpm | Bin 0 -> 10888 bytes tests/debuginfod-rpms/hello2.spec. | 57 + tests/debuginfod-rpms/rhel6/hello2-1.0-2.i686.rpm | Bin 0 -> 4112 bytes tests/debuginfod-rpms/rhel6/hello2-1.0-2.src.rpm | Bin 0 -> 3816 bytes .../rhel6/hello2-debuginfo-1.0-2.i686.rpm | Bin 0 -> 6060 bytes .../rhel6/hello2-two-1.0-2.i686.rpm | Bin 0 -> 4052 bytes tests/debuginfod-rpms/rhel7/hello2-1.0-2.src.rpm | Bin 0 -> 3819 bytes .../debuginfod-rpms/rhel7/hello2-1.0-2.x86_64.rpm | Bin 0 -> 5156 bytes .../rhel7/hello2-debuginfo-1.0-2.x86_64.rpm | Bin 0 -> 6936 bytes .../rhel7/hello2-two-1.0-2.x86_64.rpm | Bin 0 -> 5092 bytes tests/debuginfod_build_id_find.c | 62 + tests/run-debuginfod-find.sh | 230 ++ 31 files changed, 3446 insertions(+), 27 deletions(-) create mode 100644 config/debuginfod.service create mode 100644 config/debuginfod.sysconfig create mode 100644 debuginfod/debuginfod.cxx create mode 100644 doc/debuginfod.8 create mode 100644 tests/debuginfod-rpms/fedora30/hello2-1.0-2.src.rpm create mode 100644 tests/debuginfod-rpms/fedora30/hello2-1.0-2.x86_64.rpm create mode 100644 tests/debuginfod-rpms/fedora30/hello2-debuginfo-1.0-2.x86_64.rpm create mode 100644 tests/debuginfod-rpms/fedora30/hello2-debugsource-1.0-2.x86_64.rpm create mode 100644 tests/debuginfod-rpms/fedora30/hello2-two-1.0-2.x86_64.rpm create mode 100644 tests/debuginfod-rpms/fedora30/hello2-two-debuginfo-1.0-2.x86_64.rpm create mode 100644 tests/debuginfod-rpms/hello2.spec. create mode 100644 tests/debuginfod-rpms/rhel6/hello2-1.0-2.i686.rpm create mode 100644 tests/debuginfod-rpms/rhel6/hello2-1.0-2.src.rpm create mode 100644 tests/debuginfod-rpms/rhel6/hello2-debuginfo-1.0-2.i686.rpm create mode 100644 tests/debuginfod-rpms/rhel6/hello2-two-1.0-2.i686.rpm create mode 100644 tests/debuginfod-rpms/rhel7/hello2-1.0-2.src.rpm create mode 100644 tests/debuginfod-rpms/rhel7/hello2-1.0-2.x86_64.rpm create mode 100644 tests/debuginfod-rpms/rhel7/hello2-debuginfo-1.0-2.x86_64.rpm create mode 100644 tests/debuginfod-rpms/rhel7/hello2-two-1.0-2.x86_64.rpm create mode 100644 tests/debuginfod_build_id_find.c create mode 100755 tests/run-debuginfod-find.sh diff --git a/config/ChangeLog b/config/ChangeLog index b641d0d..73643f9 100644 --- a/config/ChangeLog +++ b/config/ChangeLog @@ -1,3 +1,10 @@ +2019-10-28 Frank Ch. Eigler + + * eu.am (AM_CXXFLAGS): Clone & amend AM_CFLAGS for c++11 code. + * debuginfod.service, debuginfod.sysconfig: New files: systemd. + * Makefile.am: Install them. + * elfutils.spec.in: Add debuginfod and debuginfod-client subrpms. + 2019-08-29 Mark Wielaard * elfutils.spec.in (%description devel): Remove libebl text. diff --git a/config/Makefile.am b/config/Makefile.am index 10bd8d3..55e895a 100644 --- a/config/Makefile.am +++ b/config/Makefile.am @@ -28,8 +28,9 @@ ## the GNU Lesser General Public License along with this program. If ## not, see . ## -EXTRA_DIST = elfutils.spec.in known-dwarf.awk 10-default-yama-scope.conf - libelf.pc.in libdw.pc.in libdebuginfod.pc.in +EXTRA_DIST = elfutils.spec.in known-dwarf.awk 10-default-yama-scope.conf \ + libelf.pc.in libdw.pc.in libdebuginfod.pc.in \ + debuginfod.service debuginfod.sysconfig pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = libelf.pc libdw.pc libdebuginfod.pc diff --git a/config/debuginfod.service b/config/debuginfod.service new file mode 100644 index 0000000..d8ef072 --- /dev/null +++ b/config/debuginfod.service @@ -0,0 +1,15 @@ +[Unit] +Description=elfutils debuginfo-over-http server +Documentation=http://elfutils.org/ +After=network.target + +[Service] +EnvironmentFile=/etc/sysconfig/debuginfod +User=debuginfod +Group=debuginfod +#CacheDirectory=debuginfod +ExecStart=/usr/bin/debuginfod -d /var/cache/debuginfod/debuginfod.sqlite -p $DEBUGINFOD_PORT $DEBUGINFOD_VERBOSE $DEBUGINFOD_PRAGMAS $DEBUGINFOD_PATHS +TimeoutStopSec=10 + +[Install] +WantedBy=multi-user.target diff --git a/config/debuginfod.sysconfig b/config/debuginfod.sysconfig new file mode 100644 index 0000000..c56bcf3 --- /dev/null +++ b/config/debuginfod.sysconfig @@ -0,0 +1,14 @@ +# +DEBUGINFOD_PORT="8002" +#DEBUGINFOD_VERBOSE="-v" + +# some common places to find trustworthy ELF/DWARF files and RPMs +DEBUGINFOD_PATHS="-t43200 -F -R /usr/lib/debug /usr/bin /usr/libexec /usr/sbin /usr/lib /usr/lib64 /var/cache/yum /var/cache/dnf" + +# prefer reliability/durability over performance +#DEBUGINFOD_PRAGMAS="-D 'pragma synchronous=full;'" + +# upstream debuginfods +#DEBUGINFOD_URLS="http://secondhost:8002 http://thirdhost:8002" +#DEBUGINFOD_TIMEOUT="5" +#DEBUGINFOD_CACHE_DIR="" diff --git a/config/elfutils.spec.in b/config/elfutils.spec.in index 6771d13..3cd15ce 100644 --- a/config/elfutils.spec.in +++ b/config/elfutils.spec.in @@ -12,6 +12,11 @@ Requires: elfutils-libelf = %{version}-%{release} Requires: glibc >= 2.7 Requires: libstdc++ Requires: default-yama-scope +%if 0%{?rhel} >= 8 || 0%{?fedora} >= 20 +Recommends: elfutils-debuginfod-client +%else +Requires: elfutils-debuginfod-client +%endif # ExcludeArch: xxx @@ -23,10 +28,20 @@ BuildRequires: flex >= 2.5.4a BuildRequires: bzip2 BuildRequires: m4 BuildRequires: gettext -BuildRequires: zlib-devel +BuildRequires: pkgconfig(zlib) +%if 0%{?rhel} == 7 BuildRequires: bzip2-devel -BuildRequires: xz-devel +%else +BuildRequires: pkgconfig(bzip2) +%endif +BuildRequires: pkgconfig(liblzma) BuildRequires: gcc-c++ +BuildRequires: pkgconfig(libmicrohttpd) >= 0.9.33 +BuildRequires: pkgconfig(libcurl) >= 7.29.0 +BuildRequires: pkgconfig(sqlite3) >= 3.7.17 +BuildRequires: pkgconfig(libarchive) >= 3.1.2 +# for the run-debuginfod-find.sh test case in %check for /usr/sbin/ss +BuildRequires: iproute %define _gnu %{nil} %define _programprefix eu- @@ -116,18 +131,53 @@ interprocess services, communication and introspection (like synchronisation, signaling, debugging, tracing and profiling) of processes. +%package debuginfod-client +Summary: Libraries and command-line frontend for HTTP ELF/DWARF file server addressed by build-id. +License: GPLv3+ and (GPLv2+ or LGPLv3+) + +%package debuginfod-client-devel +Summary: Libraries and headers to build debuginfod client applications. +License: GPLv2+ or LGPLv3+ + +%package debuginfod +Summary: HTTP ELF/DWARF file server addressed by build-id. +License: GPLv3+ +BuildRequires: systemd +Requires(post): systemd +Requires(preun): systemd +Requires(postun): systemd +Requires: shadow-utils +Requires: /usr/bin/rpm2cpio + +%description debuginfod-client +The elfutils-debuginfod-client package contains shared libraries +dynamically loaded from -ldw, which use a debuginfod service +to look up debuginfo and associated data. Also includes a +command-line frontend. + +%description debuginfod-client-devel +The elfutils-debuginfod-client-devel package contains the libraries +to create applications to use the debuginfod service. + +%description debuginfod +The elfutils-debuginfod package contains the debuginfod binary +and control files for a service that can provide ELF/DWARF +files to remote clients, based on build-id identification. +The ELF/DWARF file searching functions in libdwfl can query +such servers to download those files on demand. + %prep %setup -q %build -%configure --program-prefix=%{_programprefix} +%configure --program-prefix=%{_programprefix} --enable-debuginfod make %install rm -rf ${RPM_BUILD_ROOT} mkdir -p ${RPM_BUILD_ROOT}%{_prefix} -%makeinstall +%make_install chmod +x ${RPM_BUILD_ROOT}%{_prefix}/%{_lib}/lib*.so* @@ -140,6 +190,11 @@ chmod +x ${RPM_BUILD_ROOT}%{_prefix}/%{_lib}/lib*.so* install -Dm0644 config/10-default-yama-scope.conf ${RPM_BUILD_ROOT}%{_sysctldir}/10-default-yama-scope.conf +install -Dm0644 config/debuginfod.service ${RPM_BUILD_ROOT}%{_unitdir}/debuginfod.service +install -Dm0644 config/debuginfod.sysconfig ${RPM_BUILD_ROOT}%{_sysconfdir}/sysconfig/debuginfod +mkdir -p ${RPM_BUILD_ROOT}%{_localstatedir}/cache/debuginfod +touch ${RPM_BUILD_ROOT}%{_localstatedir}/cache/debuginfod/debuginfod.sqlite + %check make check @@ -225,6 +280,43 @@ rm -rf ${RPM_BUILD_ROOT} %files default-yama-scope %{_sysctldir}/10-default-yama-scope.conf +%files debuginfod-client +%defattr(-,root,root) +%{_libdir}/libdebuginfod-%{version}.so +%{_bindir}/debuginfod-find +%{_mandir}/man1/debuginfod-find.1* + +%files debuginfod-client-devel +%defattr(-,root,root) +%{_libdir}/pkgconfig/libdebuginfod.pc +%{_mandir}/man3/debuginfod_*.3* +%{_includedir}/elfutils/debuginfod.h +%{_libdir}/libdebuginfod.so* + +%files debuginfod +%defattr(-,root,root) +%{_bindir}/debuginfod +%config(noreplace) %verify(not md5 size mtime) %{_sysconfdir}/sysconfig/debuginfod +%{_unitdir}/debuginfod.service +%{_sysconfdir}/sysconfig/debuginfod +%{_mandir}/man8/debuginfod.8* + +%dir %attr(0700,debuginfod,debuginfod) %{_localstatedir}/cache/debuginfod +%verify(not md5 size mtime) %attr(0600,debuginfod,debuginfod) %{_localstatedir}/cache/debuginfod/debuginfod.sqlite + +%pre debuginfod +getent group debuginfod >/dev/null || groupadd -r debuginfod +getent passwd debuginfod >/dev/null || \ + useradd -r -g debuginfod -d /var/cache/debuginfod -s /sbin/nologin \ + -c "elfutils debuginfo server" debuginfod +exit 0 + +%post debuginfod +%systemd_post debuginfod.service + +%postun debuginfod +%systemd_postun_with_restart debuginfod.service + %changelog * Tue Aug 13 2019 Mark Wielaard 0.177-1 - elfclassify: New tool to analyze ELF objects. diff --git a/config/eu.am b/config/eu.am index 82acda3..6c3c444 100644 --- a/config/eu.am +++ b/config/eu.am @@ -79,6 +79,16 @@ AM_CFLAGS = -std=gnu99 -Wall -Wshadow -Wformat=2 \ $(if $($(*F)_no_Wpacked_not_aligned),-Wno-packed-not-aligned,) \ $($(*F)_CFLAGS) +AM_CXXFLAGS = -std=c++11 -Wall -Wshadow \ + -Wtrampolines \ + $(LOGICAL_OP_WARNING) $(DUPLICATED_COND_WARNING) \ + $(NULL_DEREFERENCE_WARNING) $(IMPLICIT_FALLTHROUGH_WARNING) \ + $(if $($(*F)_no_Werror),,-Werror) \ + $(if $($(*F)_no_Wunused),,-Wunused -Wextra) \ + $(if $($(*F)_no_Wstack_usage),,$(STACK_USAGE_WARNING)) \ + $(if $($(*F)_no_Wpacked_not_aligned),-Wno-packed-not-aligned,) \ + $($(*F)_CXXFLAGS) + COMPILE.os = $(filter-out -fprofile-arcs -ftest-coverage, $(COMPILE)) DEFS.os = -DPIC -DSHARED diff --git a/configure.ac b/configure.ac index 8a3ed3a..5deec33 100644 --- a/configure.ac +++ b/configure.ac @@ -1,7 +1,7 @@ dnl Process this file with autoconf to produce a configure script. dnl Configure input file for elfutils. -*-autoconf-*- dnl -dnl Copyright (C) 1996-2018 Red Hat, Inc. +dnl Copyright (C) 1996-2019 Red Hat, Inc. dnl dnl This file is part of elfutils. dnl @@ -88,8 +88,6 @@ 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 @@ -676,17 +674,25 @@ 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"]) +# both client and server. +AC_ARG_ENABLE([debuginfod],AC_HELP_STRING([--enable-debuginfod], [Build debuginfod server and client])) +AC_PROG_CXX +AX_CXX_COMPILE_STDCXX(11, noext, optional) +AS_IF([test "x$enable_debuginfod" != "xno"], [ + AC_MSG_NOTICE([checking debuginfod dependencies, disable to skip]) + enable_debuginfod=yes # presume success + PKG_PROG_PKG_CONFIG + if test "x$ac_cv_prog_ac_ct_CXX" = "x"; then enable_debuginfod=no; fi + PKG_CHECK_MODULES([libmicrohttpd],[libmicrohttpd >= 0.9.33],[],[enable_debuginfod=no]) + PKG_CHECK_MODULES([libcurl],[libcurl >= 7.29.0],[],[enable_debuginfod=no]) + PKG_CHECK_MODULES([sqlite3],[sqlite3 >= 3.7.17],[],[enable_debuginfod=no]) + PKG_CHECK_MODULES([libarchive],[libarchive >= 3.1.2],[],[enable_debuginfod=no]) + if test "x$enable_debuginfod" = "xno"; then + AC_MSG_ERROR([C++ compiler or dependencies not found, use --disable-debuginfod to disable.]) + fi +]) + +AS_IF([test "x$enable_debuginfod" != "xno"],AC_DEFINE([ENABLE_DEBUGINFOD],[1],[Build debuginfod])) AM_CONDITIONAL([DEBUGINFOD],[test "x$enable_debuginfod" = "xyes"]) @@ -719,7 +725,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} + Debuginfod 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 index 1a31cf6..b5679a2 100644 --- a/debuginfod/ChangeLog +++ b/debuginfod/ChangeLog @@ -1,3 +1,9 @@ +2019-10-28 Frank Ch. Eigler + + * debuginfod.cxx: New file: debuginfod server. + * debuginfod.8: New file: man page. + * Makefile.am: Build it. + 2019-10-28 Aaron Merey * debuginfod-client.c: New file: debuginfod client library. diff --git a/debuginfod/Makefile.am b/debuginfod/Makefile.am index a8ee459..ec0f49f 100644 --- a/debuginfod/Makefile.am +++ b/debuginfod/Makefile.am @@ -31,7 +31,9 @@ ## 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 \ + $(libmicrohttpd_CFLAGS) $(libcurl_CFLAGS) $(sqlite3_CFLAGS) \ + $(libarchive_CFLAGS) VERSION = 1 # Disable eu- prefixing for artifacts (binaries & man pages) in this @@ -55,7 +57,9 @@ libeu = ../lib/libeu.a AM_LDFLAGS = -Wl,-rpath-link,../libelf:../libdw:. -bin_PROGRAMS = debuginfod-find +bin_PROGRAMS = debuginfod debuginfod-find +debuginfod_SOURCES = debuginfod.cxx +debuginfod_LDADD = $(libdw) $(libelf) $(libeu) $(libdebuginfod) $(libmicrohttpd_LIBS) $(libcurl_LIBS) $(sqlite3_LIBS) $(libarchive_LIBS) -lpthread -ldl debuginfod_find_SOURCES = debuginfod-find.c debuginfod_find_LDADD = $(libeu) $(libdebuginfod) diff --git a/debuginfod/debuginfod.cxx b/debuginfod/debuginfod.cxx new file mode 100644 index 0000000..a87ec4d --- /dev/null +++ b/debuginfod/debuginfod.cxx @@ -0,0 +1,2514 @@ +/* Debuginfo-over-http server. + 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 a copy of the GNU General Public License + along with this program. If not, see . */ + + +/* 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 + #include +#endif + +#ifdef HAVE_CONFIG_H + #include "config.h" +#endif + +extern "C" { +#include "printversion.h" +} + +#include "debuginfod.h" +#include + +#include +#ifdef __GNUC__ +#undef __attribute__ /* glibc bug - rhbz 1763325 */ +#endif + +#include +#include +#include +// #include // not until it supports C++ << better +#include +#include +#include +#include +#include +#include +#include +#include + + +/* 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 + #include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +// #include // on rhel7 gcc 4.8, not competent +#include +// #include +using namespace std; + +#include +#include + +#include +#include +#include +#include +#include + +#ifdef __linux__ +#include +#endif + +#ifdef __linux__ +#define tid() syscall(SYS_gettid) +#else +#define tid() pthread_self() +#endif + + +// Roll this identifier for every sqlite schema incompatiblity. +#define BUILDIDS "buildids9" + +#if SQLITE_VERSION_NUMBER >= 3008000 +#define WITHOUT_ROWID "without rowid" +#else +#define WITHOUT_ROWID "" +#endif + +static const char DEBUGINFOD_SQLITE_DDL[] = + "pragma foreign_keys = on;\n" + "pragma synchronous = 0;\n" // disable fsync()s - this cache is disposable across a machine crash + "pragma journal_mode = wal;\n" // https://sqlite.org/wal.html + "pragma wal_checkpoint = truncate;\n" // clean out any preexisting wal file + "pragma journal_size_limit = 0;\n" // limit steady state file (between grooming, which also =truncate's) + "pragma auto_vacuum = incremental;\n" // https://sqlite.org/pragma.html + "pragma busy_timeout = 1000;\n" // https://sqlite.org/pragma.html + // NB: all these are overridable with -D option + + // Normalization table for interning file names + "create table if not exists " BUILDIDS "_files (\n" + " id integer primary key not null,\n" + " name text unique not null\n" + " );\n" + // Normalization table for interning buildids + "create table if not exists " BUILDIDS "_buildids (\n" + " id integer primary key not null,\n" + " hex text unique not null);\n" + // Track the completion of scanning of a given file & sourcetype at given time + "create table if not exists " BUILDIDS "_file_mtime_scanned (\n" + " mtime integer not null,\n" + " file integer not null,\n" + " size integer not null,\n" // in bytes + " sourcetype text(1) not null\n" + " check (sourcetype IN ('F', 'R')),\n" + " foreign key (file) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n" + " primary key (file, mtime, sourcetype)\n" + " ) " WITHOUT_ROWID ";\n" + "create table if not exists " BUILDIDS "_f_de (\n" + " buildid integer not null,\n" + " debuginfo_p integer not null,\n" + " executable_p integer not null,\n" + " file integer not null,\n" + " mtime integer not null,\n" + " foreign key (file) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n" + " foreign key (buildid) references " BUILDIDS "_buildids(id) on update cascade on delete cascade,\n" + " primary key (buildid, file, mtime)\n" + " ) " WITHOUT_ROWID ";\n" + "create table if not exists " BUILDIDS "_f_s (\n" + " buildid integer not null,\n" + " artifactsrc integer not null,\n" + " file integer not null,\n" // NB: not necessarily entered into _mtime_scanned + " mtime integer not null,\n" + " foreign key (file) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n" + " foreign key (artifactsrc) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n" + " foreign key (buildid) references " BUILDIDS "_buildids(id) on update cascade on delete cascade,\n" + " primary key (buildid, artifactsrc, file, mtime)\n" + " ) " WITHOUT_ROWID ";\n" + "create table if not exists " BUILDIDS "_r_de (\n" + " buildid integer not null,\n" + " debuginfo_p integer not null,\n" + " executable_p integer not null,\n" + " file integer not null,\n" + " mtime integer not null,\n" + " content integer not null,\n" + " foreign key (file) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n" + " foreign key (content) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n" + " foreign key (buildid) references " BUILDIDS "_buildids(id) on update cascade on delete cascade,\n" + " primary key (buildid, debuginfo_p, executable_p, file, content, mtime)\n" + " ) " WITHOUT_ROWID ";\n" + "create table if not exists " BUILDIDS "_r_sref (\n" // outgoing dwarf sourcefile references from rpm + " buildid integer not null,\n" + " artifactsrc integer not null,\n" + " foreign key (artifactsrc) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n" + " foreign key (buildid) references " BUILDIDS "_buildids(id) on update cascade on delete cascade,\n" + " primary key (buildid, artifactsrc)\n" + " ) " WITHOUT_ROWID ";\n" + "create table if not exists " BUILDIDS "_r_sdef (\n" // rpm contents that may satisfy sref + " file integer not null,\n" + " mtime integer not null,\n" + " content integer not null,\n" + " foreign key (file) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n" + " foreign key (content) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n" + " primary key (content, file, mtime)\n" + " ) " WITHOUT_ROWID ";\n" + // create views to glue together some of the above tables, for webapi D queries + "create view if not exists " BUILDIDS "_query_d as \n" + "select\n" + " b.hex as buildid, n.mtime, 'F' as sourcetype, f0.name as source0, n.mtime as mtime, null as source1\n" + " from " BUILDIDS "_buildids b, " BUILDIDS "_files f0, " BUILDIDS "_f_de n\n" + " where b.id = n.buildid and f0.id = n.file and n.debuginfo_p = 1\n" + "union all select\n" + " b.hex as buildid, n.mtime, 'R' as sourcetype, f0.name as source0, n.mtime as mtime, f1.name as source1\n" + " from " BUILDIDS "_buildids b, " BUILDIDS "_files f0, " BUILDIDS "_files f1, " BUILDIDS "_r_de n\n" + " where b.id = n.buildid and f0.id = n.file and f1.id = n.content and n.debuginfo_p = 1\n" + ";" + // ... and for E queries + "create view if not exists " BUILDIDS "_query_e as \n" + "select\n" + " b.hex as buildid, n.mtime, 'F' as sourcetype, f0.name as source0, n.mtime as mtime, null as source1\n" + " from " BUILDIDS "_buildids b, " BUILDIDS "_files f0, " BUILDIDS "_f_de n\n" + " where b.id = n.buildid and f0.id = n.file and n.executable_p = 1\n" + "union all select\n" + " b.hex as buildid, n.mtime, 'R' as sourcetype, f0.name as source0, n.mtime as mtime, f1.name as source1\n" + " from " BUILDIDS "_buildids b, " BUILDIDS "_files f0, " BUILDIDS "_files f1, " BUILDIDS "_r_de n\n" + " where b.id = n.buildid and f0.id = n.file and f1.id = n.content and n.executable_p = 1\n" + ";" + // ... and for S queries + "create view if not exists " BUILDIDS "_query_s as \n" + "select\n" + " b.hex as buildid, fs.name as artifactsrc, 'F' as sourcetype, f0.name as source0, n.mtime as mtime, null as source1, null as source0ref\n" + " from " BUILDIDS "_buildids b, " BUILDIDS "_files f0, " BUILDIDS "_files fs, " BUILDIDS "_f_s n\n" + " where b.id = n.buildid and f0.id = n.file and fs.id = n.artifactsrc\n" + "union all select\n" + " b.hex as buildid, f1.name as artifactsrc, 'R' as sourcetype, f0.name as source0, sd.mtime as mtime, f1.name as source1, fsref.name as source0ref\n" + " from " BUILDIDS "_buildids b, " BUILDIDS "_files f0, " BUILDIDS "_files f1, " BUILDIDS "_files fsref, " + " " BUILDIDS "_r_sdef sd, " BUILDIDS "_r_sref sr, " BUILDIDS "_r_de sde\n" + " where b.id = sr.buildid and f0.id = sd.file and fsref.id = sde.file and f1.id = sd.content\n" + " and sr.artifactsrc = sd.content and sde.buildid = sr.buildid\n" + ";" + // and for startup overview counts + "drop view if exists " BUILDIDS "_stats;\n" + "create view if not exists " BUILDIDS "_stats as\n" + " select 'file d/e' as label,count(*) as quantity from " BUILDIDS "_f_de\n" + "union all select 'file s',count(*) from " BUILDIDS "_f_s\n" + "union all select 'rpm d/e',count(*) from " BUILDIDS "_r_de\n" + "union all select 'rpm sref',count(*) from " BUILDIDS "_r_sref\n" + "union all select 'rpm sdef',count(*) from " BUILDIDS "_r_sdef\n" + "union all select 'buildids',count(*) from " BUILDIDS "_buildids\n" + "union all select 'filenames',count(*) from " BUILDIDS "_files\n" + "union all select 'files scanned (#)',count(*) from " BUILDIDS "_file_mtime_scanned\n" + "union all select 'files scanned (mb)',coalesce(sum(size)/1024/1024,0) from " BUILDIDS "_file_mtime_scanned\n" +#if SQLITE_VERSION_NUMBER >= 3016000 + "union all select 'index db size (mb)',page_count*page_size/1024/1024 as size FROM pragma_page_count(), pragma_page_size()\n" +#endif + ";\n" + +// schema change history & garbage collection +// +// XXX: we could have migration queries here to bring prior-schema +// data over instead of just dropping it. +// +// buildids9: widen the mtime_scanned table + "" // <<< we are here +// buildids8: slim the sref table + "drop table if exists buildids8_f_de;\n" + "drop table if exists buildids8_f_s;\n" + "drop table if exists buildids8_r_de;\n" + "drop table if exists buildids8_r_sref;\n" + "drop table if exists buildids8_r_sdef;\n" + "drop table if exists buildids8_file_mtime_scanned;\n" + "drop table if exists buildids8_files;\n" + "drop table if exists buildids8_buildids;\n" +// buildids7: separate _norm table into dense subtype tables + "drop table if exists buildids7_f_de;\n" + "drop table if exists buildids7_f_s;\n" + "drop table if exists buildids7_r_de;\n" + "drop table if exists buildids7_r_sref;\n" + "drop table if exists buildids7_r_sdef;\n" + "drop table if exists buildids7_file_mtime_scanned;\n" + "drop table if exists buildids7_files;\n" + "drop table if exists buildids7_buildids;\n" +// buildids6: drop bolo/rfolo again, represent sources / rpmcontents in main table + "drop table if exists buildids6_norm;\n" + "drop table if exists buildids6_files;\n" + "drop table if exists buildids6_buildids;\n" + "drop view if exists buildids6;\n" +// buildids5: redefine srcfile1 column to be '.'-less (for rpms) + "drop table if exists buildids5_norm;\n" + "drop table if exists buildids5_files;\n" + "drop table if exists buildids5_buildids;\n" + "drop table if exists buildids5_bolo;\n" + "drop table if exists buildids5_rfolo;\n" + "drop view if exists buildids5;\n" +// buildids4: introduce rpmfile RFOLO + "drop table if exists buildids4_norm;\n" + "drop table if exists buildids4_files;\n" + "drop table if exists buildids4_buildids;\n" + "drop table if exists buildids4_bolo;\n" + "drop table if exists buildids4_rfolo;\n" + "drop view if exists buildids4;\n" +// buildids3*: split out srcfile BOLO + "drop table if exists buildids3_norm;\n" + "drop table if exists buildids3_files;\n" + "drop table if exists buildids3_buildids;\n" + "drop table if exists buildids3_bolo;\n" + "drop view if exists buildids3;\n" +// buildids2: normalized buildid and filenames into interning tables; + "drop table if exists buildids2_norm;\n" + "drop table if exists buildids2_files;\n" + "drop table if exists buildids2_buildids;\n" + "drop view if exists buildids2;\n" + // buildids1: made buildid and artifacttype NULLable, to represent cached-negative +// lookups from sources, e.g. files or rpms that contain no buildid-indexable content + "drop table if exists buildids1;\n" +// buildids: original + "drop table if exists buildids;\n" + ; + +static const char DEBUGINFOD_SQLITE_CLEANUP_DDL[] = + "pragma wal_checkpoint = truncate;\n" // clean out any preexisting wal file + ; + + + + +/* Name and version of program. */ +/* ARGP_PROGRAM_VERSION_HOOK_DEF = print_version; */ // not this simple for C++ + +/* Bug report address. */ +ARGP_PROGRAM_BUG_ADDRESS_DEF = PACKAGE_BUGREPORT; + +/* Definitions of arguments for argp functions. */ +static const struct argp_option options[] = + { + { NULL, 0, NULL, 0, "Scanners:", 1 }, + { "scan-file-dir", 'F', NULL, 0, "Enable ELF/DWARF file scanning threads.", 0 }, + { "scan-rpm-dir", 'R', NULL, 0, "Enable RPM scanning threads.", 0 }, + // "source-oci-imageregistry" ... + + { NULL, 0, NULL, 0, "Options:", 2 }, + { "rescan-time", 't', "SECONDS", 0, "Number of seconds to wait between rescans, 0=disable.", 0 }, + { "groom-time", 'g', "SECONDS", 0, "Number of seconds to wait between database grooming, 0=disable.", 0 }, + { "maxigroom", 'G', NULL, 0, "Run a complete database groom/shrink pass at startup.", 0 }, + { "concurrency", 'c', "NUM", 0, "Limit scanning thread concurrency to NUM.", 0 }, + { "include", 'I', "REGEX", 0, "Include files matching REGEX, default=all.", 0 }, + { "exclude", 'X', "REGEX", 0, "Exclude files matching REGEX, default=none.", 0 }, + { "port", 'p', "NUM", 0, "HTTP port to listen on, default 8002.", 0 }, + { "database", 'd', "FILE", 0, "Path to sqlite database.", 0 }, + { "ddl", 'D', "SQL", 0, "Apply extra sqlite ddl/pragma to connection.", 0 }, + { "verbose", 'v', NULL, 0, "Increase verbosity.", 0 }, + + { NULL, 0, NULL, 0, NULL, 0 } + }; + +/* Short description of program. */ +static const char doc[] = "Serve debuginfo-related content across HTTP from files under PATHs."; + +/* Strings for arguments in help texts. */ +static const char args_doc[] = "[PATH ...]"; + +/* Prototype for option handler. */ +static error_t parse_opt (int key, char *arg, struct argp_state *state); + +/* Data structure to communicate with argp functions. */ +static struct argp argp = + { + options, parse_opt, args_doc, doc, NULL, NULL, NULL + }; + + +static string db_path; +static sqlite3 *db; +static unsigned verbose; +static volatile sig_atomic_t interrupted = 0; +static volatile sig_atomic_t sigusr1 = 0; +static volatile sig_atomic_t sigusr2 = 0; +static unsigned http_port = 8002; +static unsigned rescan_s = 300; +static unsigned groom_s = 86400; +static unsigned maxigroom = false; +static unsigned concurrency = std::thread::hardware_concurrency() ?: 1; +static set source_paths; +static bool scan_files = false; +static bool scan_rpms = false; +static vector extra_ddl; +static regex_t file_include_regex; +static regex_t file_exclude_regex; + +/* Handle program arguments. */ +static error_t +parse_opt (int key, char *arg, + struct argp_state *state __attribute__ ((unused))) +{ + int rc; + switch (key) + { + case 'v': verbose ++; break; + case 'd': db_path = string(arg); break; + case 'p': http_port = (unsigned) atoi(arg); + if (http_port > 65535) argp_failure(state, 1, EINVAL, "port number"); + break; + case 'F': scan_files = true; break; + case 'R': scan_rpms = true; break; + case 'D': extra_ddl.push_back(string(arg)); break; + case 't': + rescan_s = (unsigned) atoi(arg); + break; + case 'g': + groom_s = (unsigned) atoi(arg); + break; + case 'G': + maxigroom = true; + break; + case 'c': + concurrency = (unsigned) atoi(arg); + if (concurrency < 1) concurrency = 1; + break; + case 'I': + // NB: no problem with unconditional free here - an earlier failed regcomp would exit program + regfree (&file_include_regex); + rc = regcomp (&file_include_regex, arg, REG_EXTENDED|REG_NOSUB); + if (rc != 0) + argp_failure(state, 1, EINVAL, "regular expession"); + break; + case 'X': + regfree (&file_exclude_regex); + rc = regcomp (&file_exclude_regex, arg, REG_EXTENDED|REG_NOSUB); + if (rc != 0) + argp_failure(state, 1, EINVAL, "regular expession"); + break; + case ARGP_KEY_ARG: + source_paths.insert(string(arg)); + break; + // case 'h': argp_state_help (state, stderr, ARGP_HELP_LONG|ARGP_HELP_EXIT_OK); + default: return ARGP_ERR_UNKNOWN; + } + + return 0; +} + + +//////////////////////////////////////////////////////////////////////// + + +// represent errors that may get reported to an ostream and/or a libmicrohttpd connection + +struct reportable_exception +{ + int code; + string message; + + reportable_exception(int c, const string& m): code(c), message(m) {} + reportable_exception(const string& m): code(503), message(m) {} + reportable_exception(): code(503), message() {} + + void report(ostream& o) const; // defined under obatched() class below + + int mhd_send_response(MHD_Connection* c) const { + MHD_Response* r = MHD_create_response_from_buffer (message.size(), + (void*) message.c_str(), + MHD_RESPMEM_MUST_COPY); + int rc = MHD_queue_response (c, code, r); + MHD_destroy_response (r); + return rc; + } +}; + + +struct sqlite_exception: public reportable_exception +{ + sqlite_exception(int rc, const string& msg): + reportable_exception(string("sqlite3 error: ") + msg + ": " + string(sqlite3_errstr(rc) ?: "?")) {} +}; + +struct libc_exception: public reportable_exception +{ + libc_exception(int rc, const string& msg): + reportable_exception(string("libc error: ") + msg + ": " + string(strerror(rc) ?: "?")) {} +}; + + +struct archive_exception: public reportable_exception +{ + archive_exception(const string& msg): + reportable_exception(string("libarchive error: ") + msg) {} + archive_exception(struct archive* a, const string& msg): + reportable_exception(string("libarchive error: ") + msg + ": " + string(archive_error_string(a) ?: "?")) {} +}; + + +struct elfutils_exception: public reportable_exception +{ + elfutils_exception(int rc, const string& msg): + reportable_exception(string("elfutils error: ") + msg + ": " + string(elf_errmsg(rc) ?: "?")) {} +}; + + +//////////////////////////////////////////////////////////////////////// + +// a c++ counting-semaphore class ... since we're c++11 not c++20 + +class semaphore +{ +public: + semaphore (unsigned c=1): count(c) {} + inline void notify () { + unique_lock lock(mtx); + count++; + cv.notify_one(); + } + inline void wait() { + unique_lock lock(mtx); + while (count == 0) + cv.wait(lock); + count--; + } +private: + mutex mtx; + condition_variable cv; + unsigned count; +}; + + +class semaphore_borrower +{ +public: + semaphore_borrower(semaphore* s): sem(s) { sem->wait(); } + ~semaphore_borrower() { sem->notify(); } +private: + semaphore* sem; +}; + + +//////////////////////////////////////////////////////////////////////// + + +// Print a standard timestamp. +static ostream& +timestamp (ostream &o) +{ + char datebuf[80]; + char *now2 = NULL; + time_t now_t = time(NULL); + struct tm *now = gmtime (&now_t); + if (now) + { + (void) strftime (datebuf, sizeof (datebuf), "%c", now); + now2 = datebuf; + } + + return o << "[" << (now2 ? now2 : "") << "] " + << "(" << getpid () << "/" << tid() << "): "; +} + + +// A little class that impersonates an ostream to the extent that it can +// take << streaming operations. It batches up the bits into an internal +// stringstream until it is destroyed; then flushes to the original ostream. +// It adds a timestamp +class obatched +{ +private: + ostream& o; + stringstream stro; + static mutex lock; +public: + obatched(ostream& oo, bool timestamp_p = true): o(oo) + { + if (timestamp_p) + timestamp(stro); + } + ~obatched() + { + unique_lock do_not_cross_the_streams(obatched::lock); + o << stro.str(); + o.flush(); + } + operator ostream& () { return stro; } + template ostream& operator << (const T& t) { stro << t; return stro; } +}; +mutex obatched::lock; // just the one, since cout/cerr iostreams are not thread-safe + + +void reportable_exception::report(ostream& o) const { + obatched(o) << message << endl; +} + + +//////////////////////////////////////////////////////////////////////// + + +// RAII style sqlite prepared-statement holder that matches { } block lifetime + +struct sqlite_ps +{ +private: + sqlite3* db; + const string nickname; + const string sql; + sqlite3_stmt *pp; + + sqlite_ps(const sqlite_ps&); // make uncopyable + sqlite_ps& operator=(const sqlite_ps &); // make unassignable + +public: + sqlite_ps (sqlite3* d, const string& n, const string& s): db(d), nickname(n), sql(s) { + if (verbose > 4) + obatched(clog) << nickname << " prep " << sql << endl; + int rc = sqlite3_prepare_v2 (db, sql.c_str(), -1 /* to \0 */, & this->pp, NULL); + if (rc != SQLITE_OK) + throw sqlite_exception(rc, "prepare " + sql); + } + + sqlite_ps& reset() + { + sqlite3_reset(this->pp); + return *this; + } + + sqlite_ps& bind(int parameter, const string& str) + { + if (verbose > 4) + obatched(clog) << nickname << " bind " << parameter << "=" << str << endl; + int rc = sqlite3_bind_text (this->pp, parameter, str.c_str(), -1, SQLITE_TRANSIENT); + if (rc != SQLITE_OK) + throw sqlite_exception(rc, "sqlite3 bind"); + return *this; + } + + sqlite_ps& bind(int parameter, int64_t value) + { + if (verbose > 4) + obatched(clog) << nickname << " bind " << parameter << "=" << value << endl; + int rc = sqlite3_bind_int64 (this->pp, parameter, value); + if (rc != SQLITE_OK) + throw sqlite_exception(rc, "sqlite3 bind"); + return *this; + } + + sqlite_ps& bind(int parameter) + { + if (verbose > 4) + obatched(clog) << nickname << " bind " << parameter << "=" << "NULL" << endl; + int rc = sqlite3_bind_null (this->pp, parameter); + if (rc != SQLITE_OK) + throw sqlite_exception(rc, "sqlite3 bind"); + return *this; + } + + + void step_ok_done() { + int rc = sqlite3_step (this->pp); + if (verbose > 4) + obatched(clog) << nickname << " step-ok-done(" << sqlite3_errstr(rc) << ") " << sql << endl; + if (rc != SQLITE_OK && rc != SQLITE_DONE && rc != SQLITE_ROW) + throw sqlite_exception(rc, "sqlite3 step"); + (void) sqlite3_reset (this->pp); + } + + + int step() { + int rc = sqlite3_step (this->pp); + if (verbose > 4) + obatched(clog) << nickname << " step(" << sqlite3_errstr(rc) << ") " << sql << endl; + return rc; + } + + + + ~sqlite_ps () { sqlite3_finalize (this->pp); } + operator sqlite3_stmt* () { return this->pp; } +}; + + +//////////////////////////////////////////////////////////////////////// + +// RAII style templated autocloser + +template +struct defer_dtor +{ +public: + typedef Ignore (*dtor_fn) (Payload); + +private: + Payload p; + dtor_fn fn; + +public: + defer_dtor(Payload _p, dtor_fn _fn): p(_p), fn(_fn) {} + ~defer_dtor() { (void) (*fn)(p); } + +private: + defer_dtor(const defer_dtor&); // make uncopyable + defer_dtor& operator=(const defer_dtor &); // make unassignable +}; + + + +//////////////////////////////////////////////////////////////////////// + + + + + +static string +conninfo (struct MHD_Connection * conn) +{ + char hostname[256]; // RFC1035 + char servname[256]; + int sts = -1; + + if (conn == 0) + return "internal"; + + /* Look up client address data. */ + const union MHD_ConnectionInfo *u = MHD_get_connection_info (conn, + MHD_CONNECTION_INFO_CLIENT_ADDRESS); + struct sockaddr *so = u ? u->client_addr : 0; + + if (so && so->sa_family == AF_INET) { + sts = getnameinfo (so, sizeof (struct sockaddr_in), hostname, sizeof (hostname), servname, + sizeof (servname), NI_NUMERICHOST | NI_NUMERICSERV); + } else if (so && so->sa_family == AF_INET6) { + sts = getnameinfo (so, sizeof (struct sockaddr_in6), hostname, sizeof (hostname), + servname, sizeof (servname), NI_NUMERICHOST | NI_NUMERICSERV); + } + if (sts != 0) { + hostname[0] = servname[0] = '\0'; + } + + return string(hostname) + string(":") + string(servname); +} + + + +//////////////////////////////////////////////////////////////////////// + +static void +add_mhd_last_modified (struct MHD_Response *resp, time_t mtime) +{ + struct tm *now = gmtime (&mtime); + if (now != NULL) + { + char datebuf[80]; + size_t rc = strftime (datebuf, sizeof (datebuf), "%a, %d %b %Y %T GMT", now); + if (rc > 0 && rc < sizeof (datebuf)) + (void) MHD_add_response_header (resp, "Last-Modified", datebuf); + } + + (void) MHD_add_response_header (resp, "Cache-Control", "public"); +} + + + +static struct MHD_Response* +handle_buildid_f_match (int64_t b_mtime, + const string& b_source0, + int *result_fd) +{ + int fd = open(b_source0.c_str(), O_RDONLY); + if (fd < 0) + { + if (verbose) + obatched(clog) << "cannot open " << b_source0 << endl; + // if still missing, a periodic groom pass will delete this buildid record + return 0; + } + + // NB: use manual close(2) in error case instead of defer_dtor, because + // in the normal case, we want to hand the fd over to libmicrohttpd for + // file transfer. + + struct stat s; + int rc = fstat(fd, &s); + if (rc < 0) + { + if (verbose) + clog << "cannot fstat " << b_source0 << endl; + close(fd); + return 0; + } + + if ((int64_t) s.st_mtime != b_mtime) + { + if (verbose) + obatched(clog) << "mtime mismatch for " << b_source0 << endl; + close(fd); + return 0; + } + + struct MHD_Response* r = MHD_create_response_from_fd ((uint64_t) s.st_size, fd); + if (r == 0) + { + if (verbose) + obatched(clog) << "cannot create fd-response for " << b_source0 << endl; + close(fd); + } + else + { + add_mhd_last_modified (r, s.st_mtime); + if (verbose > 1) + obatched(clog) << "serving file " << b_source0 << endl; + /* libmicrohttpd will close it. */ + if (result_fd) + *result_fd = fd; + } + + return r; +} + + +// quote all questionable characters of str for safe passage through a sh -c expansion. +static string +shell_escape(const string& str) +{ + string y; + for (auto&& x : str) + { + if (! isalnum(x) && x != '/') + y += "\\"; + y += x; + } + return y; +} + + +static struct MHD_Response* +handle_buildid_r_match (int64_t b_mtime, + const string& b_source0, + const string& b_source1, + int *result_fd) +{ + struct stat fs; + int rc = stat (b_source0.c_str(), &fs); + if (rc != 0) + throw libc_exception (errno, string("stat ") + b_source0); + + if ((int64_t) fs.st_mtime != b_mtime) + { + if (verbose) + obatched(clog) << "mtime mismatch for " << b_source0 << endl; + return 0; + } + + string popen_cmd = string("rpm2cpio " + shell_escape(b_source0)); + FILE* fp = popen (popen_cmd.c_str(), "r"); // "e" O_CLOEXEC? + if (fp == NULL) + throw libc_exception (errno, string("popen ") + popen_cmd); + defer_dtor fp_closer (fp, pclose); + + struct archive *a; + a = archive_read_new(); + if (a == NULL) + throw archive_exception("cannot create archive reader"); + defer_dtor archive_closer (a, archive_read_free); + + rc = archive_read_support_format_cpio(a); + if (rc != ARCHIVE_OK) + throw archive_exception(a, "cannot select cpio format"); + rc = archive_read_support_filter_all(a); + if (rc != ARCHIVE_OK) + throw archive_exception(a, "cannot select all filters"); + + rc = archive_read_open_FILE (a, fp); + if (rc != ARCHIVE_OK) + throw archive_exception(a, "cannot open archive from rpm2cpio pipe"); + + while(1) // parse cpio archive entries + { + struct archive_entry *e; + rc = archive_read_next_header (a, &e); + if (rc != ARCHIVE_OK) + break; + + if (! S_ISREG(archive_entry_mode (e))) // skip non-files completely + continue; + + string fn = archive_entry_pathname (e); + if (fn != string(".")+b_source1) + continue; + + // extract this file to a temporary file + char tmppath[PATH_MAX] = "/tmp/debuginfod.XXXXXX"; // XXX: $TMP_DIR etc. + int fd = mkstemp (tmppath); + if (fd < 0) + throw libc_exception (errno, "cannot create temporary file"); + unlink (tmppath); // unlink now so OS will release the file as soon as we close the fd + + rc = archive_read_data_into_fd (a, fd); + if (rc != ARCHIVE_OK) + { + close (fd); + throw archive_exception(a, "cannot extract file"); + } + + struct MHD_Response* r = MHD_create_response_from_fd (archive_entry_size(e), fd); + if (r == 0) + { + if (verbose) + obatched(clog) << "cannot create fd-response for " << b_source0 << endl; + close(fd); + break; // assume no chance of better luck around another iteration + } + else + { + add_mhd_last_modified (r, archive_entry_mtime(e)); + if (verbose > 1) + obatched(clog) << "serving rpm " << b_source0 << " file " << b_source1 << endl; + /* libmicrohttpd will close it. */ + if (result_fd) + *result_fd = fd; + return r; + } + } + + // XXX: rpm/file not found: delete this R entry? + return 0; +} + + +static struct MHD_Response* +handle_buildid_match (int64_t b_mtime, + const string& b_stype, + const string& b_source0, + const string& b_source1, + int *result_fd) +{ + if (b_stype == "F") + return handle_buildid_f_match(b_mtime, b_source0, result_fd); + else if (b_stype == "R") + return handle_buildid_r_match(b_mtime, b_source0, b_source1, result_fd); + else + return 0; +} + + + +static struct MHD_Response* handle_buildid (const string& buildid /* unsafe */, + const string& artifacttype /* unsafe */, + const string& suffix /* unsafe */, + int *result_fd + ) +{ + // validate artifacttype + string atype_code; + if (artifacttype == "debuginfo") atype_code = "D"; + else if (artifacttype == "executable") atype_code = "E"; + else if (artifacttype == "source") atype_code = "S"; + else throw reportable_exception("invalid artifacttype"); + + if (atype_code == "S" && suffix == "") + throw reportable_exception("invalid source suffix"); + + // validate buildid + if ((buildid.size() < 2) || // not empty + (buildid.size() % 2) || // even number + (buildid.find_first_not_of("0123456789abcdef") != string::npos)) // pure tasty lowercase hex + throw reportable_exception("invalid buildid"); + + if (verbose > 1) + obatched(clog) << "searching for buildid=" << buildid << " artifacttype=" << artifacttype + << " suffix=" << suffix << endl; + + sqlite_ps *pp = 0; + + if (atype_code == "D") + { + pp = new sqlite_ps (db, "mhd-query-d", + "select mtime, sourcetype, source0, source1 from " BUILDIDS "_query_d where buildid = ? " + "order by mtime desc"); + pp->reset(); + pp->bind(1, buildid); + } + else if (atype_code == "E") + { + pp = new sqlite_ps (db, "mhd-query-e", + "select mtime, sourcetype, source0, source1 from " BUILDIDS "_query_e where buildid = ? " + "order by mtime desc"); + pp->reset(); + pp->bind(1, buildid); + } + else if (atype_code == "S") + { + pp = new sqlite_ps (db, "mhd-query-s", + "select mtime, sourcetype, source0, source1 from " BUILDIDS "_query_s where buildid = ? and artifactsrc = ? " + "order by sharedprefix(source0,source0ref) desc, mtime desc"); + pp->reset(); + pp->bind(1, buildid); + pp->bind(2, suffix); + } + unique_ptr ps_closer(pp); // release pp if exception or return + + // consume all the rows + while (1) + { + int rc = pp->step(); + if (rc == SQLITE_DONE) break; + if (rc != SQLITE_ROW) + throw sqlite_exception(rc, "step"); + + int64_t b_mtime = sqlite3_column_int64 (*pp, 0); + string b_stype = string((const char*) sqlite3_column_text (*pp, 1) ?: ""); /* by DDL may not be NULL */ + string b_source0 = string((const char*) sqlite3_column_text (*pp, 2) ?: ""); /* may be NULL */ + string b_source1 = string((const char*) sqlite3_column_text (*pp, 3) ?: ""); /* may be NULL */ + + if (verbose > 1) + obatched(clog) << "found mtime=" << b_mtime << " stype=" << b_stype + << " source0=" << b_source0 << " source1=" << b_source1 << endl; + + // Try accessing the located match. + // XXX: in case of multiple matches, attempt them in parallel? + auto r = handle_buildid_match (b_mtime, b_stype, b_source0, b_source1, result_fd); + if (r) + return r; + } + + // We couldn't find it in the database. Last ditch effort + // is to defer to other debuginfo servers. + int fd = -1; + if (artifacttype == "debuginfo") + fd = debuginfod_find_debuginfo ((const unsigned char*) buildid.c_str(), 0, + NULL); + else if (artifacttype == "executable") + fd = debuginfod_find_executable ((const unsigned char*) buildid.c_str(), 0, + NULL); + else if (artifacttype == "source") + fd = debuginfod_find_source ((const unsigned char*) buildid.c_str(), 0, + suffix.c_str(), NULL); + if (fd >= 0) + { + struct stat s; + int rc = fstat (fd, &s); + if (rc == 0) + { + auto r = MHD_create_response_from_fd ((uint64_t) s.st_size, fd); + if (r) + { + add_mhd_last_modified (r, s.st_mtime); + if (verbose > 1) + obatched(clog) << "serving file from upstream debuginfod/cache" << endl; + if (result_fd) + *result_fd = fd; + return r; // NB: don't close fd; libmicrohttpd will + } + } + close (fd); + } + else if (fd != -ENOSYS) // no DEBUGINFOD_URLS configured + throw libc_exception(-fd, "upstream debuginfod query failed"); + + throw reportable_exception(MHD_HTTP_NOT_FOUND, "not found"); +} + + +//////////////////////////////////////////////////////////////////////// + + +static struct MHD_Response* +handle_metrics () +{ + throw reportable_exception("not yet implemented 2"); +} + + +//////////////////////////////////////////////////////////////////////// + + +/* libmicrohttpd callback */ +static int +handler_cb (void * /*cls*/, + struct MHD_Connection *connection, + const char *url, + const char *method, + const char * /*version*/, + const char * /*upload_data*/, + size_t * /*upload_data_size*/, + void ** /*con_cls*/) +{ + struct MHD_Response *r = NULL; + string url_copy = url; + + if (verbose) + obatched(clog) << conninfo(connection) << " " << method << " " << url << endl; + + try + { + if (string(method) != "GET") + throw reportable_exception(400, "we support GET only"); + + /* Start decoding the URL. */ + size_t slash1 = url_copy.find('/', 1); + string url1 = url_copy.substr(0, slash1); // ok even if slash1 not found + + if (slash1 != string::npos && url1 == "/buildid") + { + size_t slash2 = url_copy.find('/', slash1+1); + if (slash2 == string::npos) + throw reportable_exception("/buildid/ webapi error, need buildid"); + + string buildid = url_copy.substr(slash1+1, slash2-slash1-1); + + size_t slash3 = url_copy.find('/', slash2+1); + string artifacttype, suffix; + if (slash3 == string::npos) + { + artifacttype = url_copy.substr(slash2+1); + suffix = ""; + } + else + { + artifacttype = url_copy.substr(slash2+1, slash3-slash2-1); + suffix = url_copy.substr(slash3); // include the slash in the suffix + } + + r = handle_buildid(buildid, artifacttype, suffix, 0); // NB: don't care about result-fd + } + else if (url1 == "/metrics") + r = handle_metrics(); + else + throw reportable_exception("webapi error, unrecognized /operation"); + + if (r == 0) + throw reportable_exception("internal error, missing response"); + + int rc = MHD_queue_response (connection, MHD_HTTP_OK, r); + MHD_destroy_response (r); + return rc; + } + catch (const reportable_exception& e) + { + e.report(clog); + return e.mhd_send_response (connection); + } +} + + +//////////////////////////////////////////////////////////////////////// +// borrowed originally from src/nm.c get_local_names() + +static void +dwarf_extract_source_paths (Elf *elf, set& debug_sourcefiles) + noexcept // no exceptions - so we can simplify the altdbg resource release at end +{ + Dwarf* dbg = dwarf_begin_elf (elf, DWARF_C_READ, NULL); + if (dbg == NULL) + return; + + Dwarf* altdbg = NULL; + int altdbg_fd = -1; + + // DWZ handling: if we have an unsatisfied debug-alt-link, add an + // empty string into the outgoing sourcefiles set, so the caller + // should know that our data is incomplete. + const char *alt_name_p; + const void *alt_build_id; // elfutils-owned memory + ssize_t sz = dwelf_dwarf_gnu_debugaltlink (dbg, &alt_name_p, &alt_build_id); + if (sz > 0) // got one! + { + string buildid; + unsigned char* build_id_bytes = (unsigned char*) alt_build_id; + for (ssize_t idx=0; idx> 4]; + buildid += "0123456789abcdef"[build_id_bytes[idx] & 0xf]; + } + + if (verbose > 3) + obatched(clog) << "Need altdebug buildid=" << buildid << endl; + + // but is it unsatisfied the normal elfutils ways? + Dwarf* alt = dwarf_getalt (dbg); + if (alt == NULL) + { + // Yup, unsatisfied the normal way. Maybe we can satisfy it + // from our own debuginfod database. + int alt_fd; + struct MHD_Response *r = 0; + try + { + r = handle_buildid (buildid, "debuginfo", "", &alt_fd); + } + catch (const reportable_exception& e) + { + // swallow exceptions + } + + // NB: this is not actually recursive! This invokes the web-query + // path, which cannot get back into the scan code paths. + if (r) + { + // Found it! + altdbg_fd = dup(alt_fd); // ok if this fails, downstream failures ok + alt = altdbg = dwarf_begin (altdbg_fd, DWARF_C_READ); + // NB: must close this dwarf and this fd at the bottom of the function! + MHD_destroy_response (r); // will close alt_fd + if (alt) + dwarf_setalt (dbg, alt); + } + } + else + { + // NB: dwarf_setalt(alt) inappropriate - already done! + // NB: altdbg will stay 0 so nothing tries to redundantly dealloc. + } + + if (alt) + { + if (verbose > 3) + obatched(clog) << "Resolved altdebug buildid=" << buildid << endl; + } + else // (alt == NULL) - signal possible presence of poor debuginfo + { + debug_sourcefiles.insert(""); + if (verbose > 3) + obatched(clog) << "Unresolved altdebug buildid=" << buildid << endl; + } + } + + Dwarf_Off offset = 0; + Dwarf_Off old_offset; + size_t hsize; + + while (dwarf_nextcu (dbg, old_offset = offset, &offset, &hsize, NULL, NULL, NULL) == 0) + { + Dwarf_Die cudie_mem; + Dwarf_Die *cudie = dwarf_offdie (dbg, old_offset + hsize, &cudie_mem); + + if (cudie == NULL) + continue; + if (dwarf_tag (cudie) != DW_TAG_compile_unit) + continue; + + const char *cuname = dwarf_diename(cudie) ?: "unknown"; + + Dwarf_Files *files; + size_t nfiles; + if (dwarf_getsrcfiles (cudie, &files, &nfiles) != 0) + continue; + + // extract DW_AT_comp_dir to resolve relative file names + const char *comp_dir = ""; + const char *const *dirs; + size_t ndirs; + if (dwarf_getsrcdirs (files, &dirs, &ndirs) == 0 && + dirs[0] != NULL) + comp_dir = dirs[0]; + if (comp_dir == NULL) + comp_dir = ""; + + if (verbose > 3) + obatched(clog) << "searching for sources for cu=" << cuname << " comp_dir=" << comp_dir + << " #files=" << nfiles << " #dirs=" << ndirs << endl; + + if (comp_dir[0] == '\0' && cuname[0] != '/') + { + // This is a common symptom for dwz-compressed debug files, + // where the altdebug file cannot be resolved. + if (verbose > 3) + obatched(clog) << "skipping cu=" << cuname << " due to empty comp_dir" << endl; + continue; + } + + for (size_t f = 1; f < nfiles; f++) + { + const char *hat = dwarf_filesrc (files, f, NULL, NULL); + if (hat == NULL) + continue; + + if (string(hat) == "") // gcc intrinsics, don't bother record + continue; + + string waldo; + if (hat[0] == '/') // absolute + waldo = (string (hat)); + else if (comp_dir[0] != '\0') // comp_dir relative + waldo = (string (comp_dir) + string("/") + string (hat)); + else + { + obatched(clog) << "skipping hat=" << hat << " due to empty comp_dir" << endl; + continue; + } + + // NB: this is the 'waldo' that a dbginfo client will have + // to supply for us to give them the file The comp_dir + // prefixing is a definite complication. Otherwise we'd + // have to return a setof comp_dirs (one per CU!) with + // corresponding filesrc[] names, instead of one absolute + // resoved set. Maybe we'll have to do that anyway. XXX + + if (verbose > 4) + obatched(clog) << waldo + << (debug_sourcefiles.find(waldo)==debug_sourcefiles.end() ? " new" : " dup") << endl; + + debug_sourcefiles.insert (waldo); + } + } + + dwarf_end(dbg); + if (altdbg) + dwarf_end(altdbg); + if (altdbg_fd >= 0) + close(altdbg_fd); +} + + + +static void +elf_classify (int fd, bool &executable_p, bool &debuginfo_p, string &buildid, set& debug_sourcefiles) +{ + Elf *elf = elf_begin (fd, ELF_C_READ_MMAP_PRIVATE, NULL); + if (elf == NULL) + return; + + try // catch our types of errors and clean up the Elf* object + { + if (elf_kind (elf) != ELF_K_ELF) + { + elf_end (elf); + return; + } + + GElf_Ehdr ehdr_storage; + GElf_Ehdr *ehdr = gelf_getehdr (elf, &ehdr_storage); + if (ehdr == NULL) + { + elf_end (elf); + return; + } + auto elf_type = ehdr->e_type; + + const void *build_id; // elfutils-owned memory + ssize_t sz = dwelf_elf_gnu_build_id (elf, & build_id); + if (sz <= 0) + { + // It's not a diagnostic-worthy error for an elf file to lack build-id. + // It might just be very old. + elf_end (elf); + return; + } + + // build_id is a raw byte array; convert to hexadecimal *lowercase* + unsigned char* build_id_bytes = (unsigned char*) build_id; + for (ssize_t idx=0; idx> 4]; + buildid += "0123456789abcdef"[build_id_bytes[idx] & 0xf]; + } + + // now decide whether it's an executable - namely, any allocatable section has + // PROGBITS; + if (elf_type == ET_EXEC || elf_type == ET_DYN) + { + size_t shnum; + int rc = elf_getshdrnum (elf, &shnum); + if (rc < 0) + throw elfutils_exception(rc, "getshdrnum"); + + executable_p = false; + for (size_t sc = 0; sc < shnum; sc++) + { + Elf_Scn *scn = elf_getscn (elf, sc); + if (scn == NULL) + continue; + + GElf_Shdr shdr_mem; + GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem); + if (shdr == NULL) + continue; + + // allocated (loadable / vm-addr-assigned) section with available content? + if ((shdr->sh_type == SHT_PROGBITS) && (shdr->sh_flags & SHF_ALLOC)) + { + if (verbose > 4) + obatched(clog) << "executable due to SHF_ALLOC SHT_PROGBITS sc=" << sc << endl; + executable_p = true; + break; // no need to keep looking for others + } + } // iterate over sections + } // executable_p classification + + // now decide whether it's a debuginfo - namely, if it has any .debug* or .zdebug* sections + // logic mostly stolen from fweimer@redhat.com's elfclassify drafts + size_t shstrndx; + int rc = elf_getshdrstrndx (elf, &shstrndx); + if (rc < 0) + throw elfutils_exception(rc, "getshdrstrndx"); + + Elf_Scn *scn = NULL; + while (true) + { + scn = elf_nextscn (elf, scn); + if (scn == NULL) + break; + GElf_Shdr shdr_storage; + GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_storage); + if (shdr == NULL) + break; + const char *section_name = elf_strptr (elf, shstrndx, shdr->sh_name); + if (section_name == NULL) + break; + if (strncmp(section_name, ".debug_line", 11) == 0 || + strncmp(section_name, ".zdebug_line", 12) == 0) + { + debuginfo_p = true; + dwarf_extract_source_paths (elf, debug_sourcefiles); + break; // expecting only one .*debug_line, so no need to look for others + } + else if (strncmp(section_name, ".debug_", 7) == 0 || + strncmp(section_name, ".zdebug_", 8) == 0) + { + debuginfo_p = true; + // NB: don't break; need to parse .debug_line for sources + } + } + } + catch (const reportable_exception& e) + { + e.report(clog); + } + elf_end (elf); +} + + +static semaphore* scan_concurrency_sem = 0; // used to implement -c load limiting + + +static void +scan_source_file_path (const string& dir) +{ + obatched(clog) << "fts/file traversing " << dir << endl; + + struct timeval tv_start, tv_end; + gettimeofday (&tv_start, NULL); + + sqlite_ps ps_upsert_buildids (db, "file-buildids-intern", "insert or ignore into " BUILDIDS "_buildids VALUES (NULL, ?);"); + sqlite_ps ps_upsert_files (db, "file-files-intern", "insert or ignore into " BUILDIDS "_files VALUES (NULL, ?);"); + sqlite_ps ps_upsert_de (db, "file-de-upsert", + "insert or ignore into " BUILDIDS "_f_de " + "(buildid, debuginfo_p, executable_p, file, mtime) " + "values ((select id from " BUILDIDS "_buildids where hex = ?)," + " ?,?," + " (select id from " BUILDIDS "_files where name = ?), ?);"); + sqlite_ps ps_upsert_s (db, "file-s-upsert", + "insert or ignore into " BUILDIDS "_f_s " + "(buildid, artifactsrc, file, mtime) " + "values ((select id from " BUILDIDS "_buildids where hex = ?)," + " (select id from " BUILDIDS "_files where name = ?)," + " (select id from " BUILDIDS "_files where name = ?)," + " ?);"); + sqlite_ps ps_query (db, "file-negativehit-find", + "select 1 from " BUILDIDS "_file_mtime_scanned where sourcetype = 'F' and file = (select id from " BUILDIDS "_files where name = ?) and mtime = ?;"); + sqlite_ps ps_scan_done (db, "file-scanned", + "insert or ignore into " BUILDIDS "_file_mtime_scanned (sourcetype, file, mtime, size)" + "values ('F', (select id from " BUILDIDS "_files where name = ?), ?, ?);"); + + + char * const dirs[] = { (char*) dir.c_str(), NULL }; + + unsigned fts_scanned=0, fts_regex=0, fts_cached=0, fts_debuginfo=0, fts_executable=0, fts_sourcefiles=0; + + FTS *fts = fts_open (dirs, + FTS_PHYSICAL /* don't follow symlinks */ + | FTS_XDEV /* don't cross devices/mountpoints */ + | FTS_NOCHDIR /* multithreaded */, + NULL); + if (fts == NULL) + { + obatched(cerr) << "cannot fts_open " << dir << endl; + return; + } + + FTSENT *f; + while ((f = fts_read (fts)) != NULL) + { + semaphore_borrower handle_one_file (scan_concurrency_sem); + + fts_scanned ++; + if (interrupted) + break; + + if (verbose > 2) + obatched(clog) << "fts/file traversing " << f->fts_path << endl; + + try + { + /* Found a file. Convert it to an absolute path, so + the buildid database does not have relative path + names that are unresolvable from a subsequent run + in a different cwd. */ + char *rp = realpath(f->fts_path, NULL); + if (rp == NULL) + continue; // ignore dangling symlink or such + string rps = string(rp); + free (rp); + + bool ri = !regexec (&file_include_regex, rps.c_str(), 0, 0, 0); + bool rx = !regexec (&file_exclude_regex, rps.c_str(), 0, 0, 0); + if (!ri || rx) + { + if (verbose > 3) + obatched(clog) << "fts/file skipped by regex " << (!ri ? "I" : "") << (rx ? "X" : "") << endl; + fts_regex ++; + continue; + } + + switch (f->fts_info) + { + case FTS_D: + break; + + case FTS_DP: + break; + + case FTS_F: + { + /* See if we know of it already. */ + int rc = ps_query + .reset() + .bind(1, rps) + .bind(2, f->fts_statp->st_mtime) + .step(); + ps_query.reset(); + if (rc == SQLITE_ROW) // i.e., a result, as opposed to DONE (no results) + // no need to recheck a file/version we already know + // specifically, no need to elf-begin a file we already determined is non-elf + // (so is stored with buildid=NULL) + { + fts_cached ++; + continue; + } + + bool executable_p = false, debuginfo_p = false; // E and/or D + string buildid; + set sourcefiles; + + int fd = open (rps.c_str(), O_RDONLY); + try + { + if (fd >= 0) + elf_classify (fd, executable_p, debuginfo_p, buildid, sourcefiles); + else + throw libc_exception(errno, string("open ") + rps); + } + + // NB: we catch exceptions here too, so that we can + // cache the corrupt-elf case (!executable_p && + // !debuginfo_p) just below, just as if we had an + // EPERM error from open(2). + catch (const reportable_exception& e) + { + e.report(clog); + } + + if (fd >= 0) + close (fd); + + // register this file name in the interning table + ps_upsert_files + .reset() + .bind(1, rps) + .step_ok_done(); + + if (buildid == "") + { + // no point storing an elf file without buildid + executable_p = false; + debuginfo_p = false; + } + else + { + // register this build-id in the interning table + ps_upsert_buildids + .reset() + .bind(1, buildid) + .step_ok_done(); + } + + if (executable_p) + fts_executable ++; + if (debuginfo_p) + fts_debuginfo ++; + if (executable_p || debuginfo_p) + { + ps_upsert_de + .reset() + .bind(1, buildid) + .bind(2, debuginfo_p ? 1 : 0) + .bind(3, executable_p ? 1 : 0) + .bind(4, rps) + .bind(5, f->fts_statp->st_mtime) + .step_ok_done(); + } + + if (sourcefiles.size() && buildid != "") + { + fts_sourcefiles += sourcefiles.size(); + + for (auto&& dwarfsrc : sourcefiles) + { + char *srp = realpath(dwarfsrc.c_str(), NULL); + if (srp == NULL) // also if DWZ unresolved dwarfsrc="" + continue; // unresolvable files are not a serious problem + // throw libc_exception(errno, "fts/file realpath " + srcpath); + string srps = string(srp); + free (srp); + + struct stat sfs; + rc = stat(srps.c_str(), &sfs); + if (rc != 0) + continue; + + if (verbose > 2) + obatched(clog) << "recorded buildid=" << buildid << " file=" << srps + << " mtime=" << sfs.st_mtime + << " as source " << dwarfsrc << endl; + + ps_upsert_files + .reset() + .bind(1, srps) + .step_ok_done(); + + // register the dwarfsrc name in the interning table too + ps_upsert_files + .reset() + .bind(1, dwarfsrc) + .step_ok_done(); + + ps_upsert_s + .reset() + .bind(1, buildid) + .bind(2, dwarfsrc) + .bind(3, srps) + .bind(4, sfs.st_mtime) + .step_ok_done(); + } + } + + ps_scan_done + .reset() + .bind(1, rps) + .bind(2, f->fts_statp->st_mtime) + .bind(3, f->fts_statp->st_size) + .step_ok_done(); + + if (verbose > 2) + obatched(clog) << "recorded buildid=" << buildid << " file=" << rps + << " mtime=" << f->fts_statp->st_mtime << " atype=" + << (executable_p ? "E" : "") + << (debuginfo_p ? "D" : "") << endl; + } + break; + + case FTS_ERR: + case FTS_NS: + throw libc_exception(f->fts_errno, string("fts/file traversal ") + string(f->fts_path)); + + default: + case FTS_SL: /* NB: don't enter symbolic links into the database */ + break; + } + + if ((verbose && f->fts_info == FTS_DP) || + (verbose > 1 && f->fts_info == FTS_F)) + obatched(clog) << "fts/file traversing " << rps << ", scanned=" << fts_scanned + << ", regex-skipped=" << fts_regex + << ", cached=" << fts_cached << ", debuginfo=" << fts_debuginfo + << ", executable=" << fts_executable << ", source=" << fts_sourcefiles << endl; + } + catch (const reportable_exception& e) + { + e.report(clog); + } + } + fts_close (fts); + + gettimeofday (&tv_end, NULL); + double deltas = (tv_end.tv_sec - tv_start.tv_sec) + (tv_end.tv_usec - tv_start.tv_usec)*0.000001; + + obatched(clog) << "fts/file traversed " << dir << " in " << deltas << "s, scanned=" << fts_scanned + << ", regex-skipped=" << fts_regex + << ", cached=" << fts_cached << ", debuginfo=" << fts_debuginfo + << ", executable=" << fts_executable << ", source=" << fts_sourcefiles << endl; +} + + +static void* +thread_main_scan_source_file_path (void* arg) +{ + string dir = string((const char*) arg); + + unsigned rescan_timer = 0; + sig_atomic_t forced_rescan_count = 0; + while (! interrupted) + { + try + { + if (rescan_timer == 0) + scan_source_file_path (dir); + else if (sigusr1 != forced_rescan_count) + { + forced_rescan_count = sigusr1; + scan_source_file_path (dir); + } + } + catch (const sqlite_exception& e) + { + obatched(cerr) << e.message << endl; + } + sleep (1); + rescan_timer ++; + if (rescan_s) + rescan_timer %= rescan_s; + } + + return 0; +} + + +//////////////////////////////////////////////////////////////////////// + + + + +// Analyze given *.rpm file of given age; record buildids / exec/debuginfo-ness of its +// constituent files with given upsert statements. +static void +rpm_classify (const string& rps, sqlite_ps& ps_upsert_buildids, sqlite_ps& ps_upsert_files, + sqlite_ps& ps_upsert_de, sqlite_ps& ps_upsert_sref, sqlite_ps& ps_upsert_sdef, + time_t mtime, + unsigned& fts_executable, unsigned& fts_debuginfo, unsigned& fts_sref, unsigned& fts_sdef, + bool& fts_sref_complete_p) +{ + string popen_cmd = string("rpm2cpio " + shell_escape(rps)); + FILE* fp = popen (popen_cmd.c_str(), "r"); // "e" O_CLOEXEC? + if (fp == NULL) + throw libc_exception (errno, string("popen ") + popen_cmd); + defer_dtor fp_closer (fp, pclose); + + struct archive *a; + a = archive_read_new(); + if (a == NULL) + throw archive_exception("cannot create archive reader"); + defer_dtor archive_closer (a, archive_read_free); + + int rc = archive_read_support_format_cpio(a); + if (rc != ARCHIVE_OK) + throw archive_exception(a, "cannot select cpio format"); + rc = archive_read_support_filter_all(a); + if (rc != ARCHIVE_OK) + throw archive_exception(a, "cannot select all filters"); + + rc = archive_read_open_FILE (a, fp); + if (rc != ARCHIVE_OK) + throw archive_exception(a, "cannot open archive from rpm2cpio pipe"); + + if (verbose > 3) + obatched(clog) << "rpm2cpio|libarchive scanning " << rps << endl; + + while(1) // parse cpio archive entries + { + try + { + struct archive_entry *e; + rc = archive_read_next_header (a, &e); + if (rc != ARCHIVE_OK) + break; + + if (! S_ISREG(archive_entry_mode (e))) // skip non-files completely + continue; + + string fn = archive_entry_pathname (e); + if (fn.size() > 1 && fn[0] == '.') + fn = fn.substr(1); // trim off the leading '.' + + if (verbose > 3) + obatched(clog) << "rpm2cpio|libarchive checking " << fn << endl; + + // extract this file to a temporary file + const char *tmpdir_env = getenv ("TMPDIR") ?: "/tmp"; + char* tmppath = NULL; + rc = asprintf (&tmppath, "%s/debuginfod.XXXXXX", tmpdir_env); + if (rc < 0) + throw libc_exception (ENOMEM, "cannot allocate tmppath"); + defer_dtor tmmpath_freer (tmppath, free); + int fd = mkstemp (tmppath); + if (fd < 0) + throw libc_exception (errno, "cannot create temporary file"); + unlink (tmppath); // unlink now so OS will release the file as soon as we close the fd + defer_dtor minifd_closer (fd, close); + + rc = archive_read_data_into_fd (a, fd); + if (rc != ARCHIVE_OK) + throw archive_exception(a, "cannot extract file"); + + // finally ... time to run elf_classify on this bad boy and update the database + bool executable_p = false, debuginfo_p = false; + string buildid; + set sourcefiles; + elf_classify (fd, executable_p, debuginfo_p, buildid, sourcefiles); + // NB: might throw + + if (buildid != "") // intern buildid + { + ps_upsert_buildids + .reset() + .bind(1, buildid) + .step_ok_done(); + } + + ps_upsert_files // register this rpm constituent file name in interning table + .reset() + .bind(1, fn) + .step_ok_done(); + + if (sourcefiles.size() > 0) // sref records needed + { + // NB: we intern each source file once. Once raw, as it + // appears in the DWARF file list coming back from + // elf_classify() - because it'll end up in the + // _norm.artifactsrc column. We don't also put another + // version with a '.' at the front, even though that's + // how rpm/cpio packs names, because we hide that from + // the database for storage efficiency. + + for (auto&& s : sourcefiles) + { + if (s == "") + { + fts_sref_complete_p = false; + continue; + } + + ps_upsert_files + .reset() + .bind(1, s) + .step_ok_done(); + + ps_upsert_sref + .reset() + .bind(1, buildid) + .bind(2, s) + .step_ok_done(); + + fts_sref ++; + } + } + + if (executable_p) + fts_executable ++; + if (debuginfo_p) + fts_debuginfo ++; + + if (executable_p || debuginfo_p) + { + ps_upsert_de + .reset() + .bind(1, buildid) + .bind(2, debuginfo_p ? 1 : 0) + .bind(3, executable_p ? 1 : 0) + .bind(4, rps) + .bind(5, mtime) + .bind(6, fn) + .step_ok_done(); + } + else // potential source - sdef record + { + fts_sdef ++; + ps_upsert_sdef + .reset() + .bind(1, rps) + .bind(2, mtime) + .bind(3, fn) + .step_ok_done(); + } + + if ((verbose > 2) && (executable_p || debuginfo_p)) + obatched(clog) << "recorded buildid=" << buildid << " rpm=" << rps << " file=" << fn + << " mtime=" << mtime << " atype=" + << (executable_p ? "E" : "") + << (debuginfo_p ? "D" : "") + << " sourcefiles=" << sourcefiles.size() << endl; + + } + catch (const reportable_exception& e) + { + e.report(clog); + } + } +} + + + +// scan for *.rpm files +static void +scan_source_rpm_path (const string& dir) +{ + obatched(clog) << "fts/rpm traversing " << dir << endl; + + sqlite_ps ps_upsert_buildids (db, "rpm-buildid-intern", "insert or ignore into " BUILDIDS "_buildids VALUES (NULL, ?);"); + sqlite_ps ps_upsert_files (db, "rpm-file-intern", "insert or ignore into " BUILDIDS "_files VALUES (NULL, ?);"); + sqlite_ps ps_upsert_de (db, "rpm-de-insert", + "insert or ignore into " BUILDIDS "_r_de (buildid, debuginfo_p, executable_p, file, mtime, content) values (" + "(select id from " BUILDIDS "_buildids where hex = ?), ?, ?, " + "(select id from " BUILDIDS "_files where name = ?), ?, " + "(select id from " BUILDIDS "_files where name = ?));"); + sqlite_ps ps_upsert_sref (db, "rpm-sref-insert", + "insert or ignore into " BUILDIDS "_r_sref (buildid, artifactsrc) values (" + "(select id from " BUILDIDS "_buildids where hex = ?), " + "(select id from " BUILDIDS "_files where name = ?));"); + sqlite_ps ps_upsert_sdef (db, "rpm-sdef-insert", + "insert or ignore into " BUILDIDS "_r_sdef (file, mtime, content) values (" + "(select id from " BUILDIDS "_files where name = ?), ?," + "(select id from " BUILDIDS "_files where name = ?));"); + sqlite_ps ps_query (db, "rpm-negativehit-query", + "select 1 from " BUILDIDS "_file_mtime_scanned where " + "sourcetype = 'R' and file = (select id from " BUILDIDS "_files where name = ?) and mtime = ?;"); + sqlite_ps ps_scan_done (db, "rpm-scanned", + "insert or ignore into " BUILDIDS "_file_mtime_scanned (sourcetype, file, mtime, size)" + "values ('R', (select id from " BUILDIDS "_files where name = ?), ?, ?);"); + + char * const dirs[] = { (char*) dir.c_str(), NULL }; + + struct timeval tv_start, tv_end; + gettimeofday (&tv_start, NULL); + unsigned fts_scanned=0, fts_regex=0, fts_cached=0, fts_debuginfo=0; + unsigned fts_executable=0, fts_rpm = 0, fts_sref=0, fts_sdef=0; + + FTS *fts = fts_open (dirs, + FTS_PHYSICAL /* don't follow symlinks */ + | FTS_XDEV /* don't cross devices/mountpoints */ + | FTS_NOCHDIR /* multithreaded */, + NULL); + if (fts == NULL) + { + obatched(cerr) << "cannot fts_open " << dir << endl; + return; + } + + FTSENT *f; + while ((f = fts_read (fts)) != NULL) + { + semaphore_borrower handle_one_file (scan_concurrency_sem); + + fts_scanned ++; + if (interrupted) + break; + + if (verbose > 2) + obatched(clog) << "fts/rpm traversing " << f->fts_path << endl; + + try + { + /* Found a file. Convert it to an absolute path, so + the buildid database does not have relative path + names that are unresolvable from a subsequent run + in a different cwd. */ + char *rp = realpath(f->fts_path, NULL); + if (rp == NULL) + continue; // ignore dangling symlink or such + string rps = string(rp); + free (rp); + + bool ri = !regexec (&file_include_regex, rps.c_str(), 0, 0, 0); + bool rx = !regexec (&file_exclude_regex, rps.c_str(), 0, 0, 0); + if (!ri || rx) + { + if (verbose > 3) + obatched(clog) << "fts/rpm skipped by regex " << (!ri ? "I" : "") << (rx ? "X" : "") << endl; + fts_regex ++; + continue; + } + + switch (f->fts_info) + { + case FTS_D: + break; + + case FTS_DP: + break; + + case FTS_F: + { + // heuristic: reject if file name does not end with ".rpm" + // (alternative: try opening with librpm etc., caching) + string suffix = ".rpm"; + if (rps.size() < suffix.size() || + rps.substr(rps.size()-suffix.size()) != suffix) + continue; + fts_rpm ++; + + /* See if we know of it already. */ + int rc = ps_query + .reset() + .bind(1, rps) + .bind(2, f->fts_statp->st_mtime) + .step(); + ps_query.reset(); + if (rc == SQLITE_ROW) // i.e., a result, as opposed to DONE (no results) + // no need to recheck a file/version we already know + // specifically, no need to parse this rpm again, since we already have + // it as a D or E or S record, + // (so is stored with buildid=NULL) + { + fts_cached ++; + continue; + } + + // intern the rpm file name + ps_upsert_files + .reset() + .bind(1, rps) + .step_ok_done(); + + // extract the rpm contents via popen("rpm2cpio") | libarchive | loop-of-elf_classify() + unsigned my_fts_executable = 0, my_fts_debuginfo = 0, my_fts_sref = 0, my_fts_sdef = 0; + bool my_fts_sref_complete_p = true; + try + { + rpm_classify (rps, + ps_upsert_buildids, ps_upsert_files, + ps_upsert_de, ps_upsert_sref, ps_upsert_sdef, // dalt + f->fts_statp->st_mtime, + my_fts_executable, my_fts_debuginfo, my_fts_sref, my_fts_sdef, + my_fts_sref_complete_p); + } + catch (const reportable_exception& e) + { + e.report(clog); + } + + if (verbose > 2) + obatched(clog) << "scanned rpm=" << rps + << " mtime=" << f->fts_statp->st_mtime + << " executables=" << my_fts_executable + << " debuginfos=" << my_fts_debuginfo + << " srefs=" << my_fts_sref + << " sdefs=" << my_fts_sdef + << endl; + + fts_executable += my_fts_executable; + fts_debuginfo += my_fts_debuginfo; + fts_sref += my_fts_sref; + fts_sdef += my_fts_sdef; + + if (my_fts_sref_complete_p) // leave incomplete? + ps_scan_done + .reset() + .bind(1, rps) + .bind(2, f->fts_statp->st_mtime) + .bind(3, f->fts_statp->st_size) + .step_ok_done(); + } + break; + + case FTS_ERR: + case FTS_NS: + throw libc_exception(f->fts_errno, string("fts/rpm traversal ") + string(f->fts_path)); + + default: + case FTS_SL: /* NB: don't enter symbolic links into the database */ + break; + } + + if ((verbose && f->fts_info == FTS_DP) || + (verbose > 1 && f->fts_info == FTS_F)) + obatched(clog) << "fts/rpm traversing " << rps << ", scanned=" << fts_scanned + << ", regex-skipped=" << fts_regex + << ", rpm=" << fts_rpm << ", cached=" << fts_cached << ", debuginfo=" << fts_debuginfo + << ", executable=" << fts_executable + << ", sourcerefs=" << fts_sref << ", sourcedefs=" << fts_sdef << endl; + } + catch (const reportable_exception& e) + { + e.report(clog); + } + } + fts_close (fts); + + gettimeofday (&tv_end, NULL); + double deltas = (tv_end.tv_sec - tv_start.tv_sec) + (tv_end.tv_usec - tv_start.tv_usec)*0.000001; + + obatched(clog) << "fts/rpm traversed " << dir << " in " << deltas << "s, scanned=" << fts_scanned + << ", regex-skipped=" << fts_regex + << ", rpm=" << fts_rpm << ", cached=" << fts_cached << ", debuginfo=" << fts_debuginfo + << ", executable=" << fts_executable + << ", sourcerefs=" << fts_sref << ", sourcedefs=" << fts_sdef << endl; +} + + + +static void* +thread_main_scan_source_rpm_path (void* arg) +{ + string dir = string((const char*) arg); + + unsigned rescan_timer = 0; + sig_atomic_t forced_rescan_count = 0; + while (! interrupted) + { + try + { + if (rescan_timer == 0) + scan_source_rpm_path (dir); + else if (sigusr1 != forced_rescan_count) + { + forced_rescan_count = sigusr1; + scan_source_rpm_path (dir); + } + } + catch (const sqlite_exception& e) + { + obatched(cerr) << e.message << endl; + } + sleep (1); + rescan_timer ++; + if (rescan_s) + rescan_timer %= rescan_s; + } + + return 0; +} + + +//////////////////////////////////////////////////////////////////////// + +static void +database_stats_report() +{ + sqlite_ps ps_query (db, "database-overview", + "select label,quantity from " BUILDIDS "_stats"); + + obatched(clog) << "database record counts:" << endl; + while (1) + { + int rc = sqlite3_step (ps_query); + if (rc == SQLITE_DONE) break; + if (rc != SQLITE_ROW) + throw sqlite_exception(rc, "step"); + + obatched(clog) + << right << setw(20) << ((const char*) sqlite3_column_text(ps_query, 0) ?: (const char*) "NULL") + << " " + << (sqlite3_column_text(ps_query, 1) ?: (const unsigned char*) "NULL") + << endl; + } +} + + +// Do a round of database grooming that might take many minutes to run. +void groom() +{ + obatched(clog) << "grooming database" << endl; + + struct timeval tv_start, tv_end; + gettimeofday (&tv_start, NULL); + + // scan for files that have disappeared + sqlite_ps files (db, "check old files", "select s.mtime, s.file, f.name from " + BUILDIDS "_file_mtime_scanned s, " BUILDIDS "_files f " + "where f.id = s.file"); + sqlite_ps files_del_f_de (db, "nuke f_de", "delete from " BUILDIDS "_f_de where file = ? and mtime = ?"); + sqlite_ps files_del_r_de (db, "nuke r_de", "delete from " BUILDIDS "_r_de where file = ? and mtime = ?"); + sqlite_ps files_del_scan (db, "nuke f_m_s", "delete from " BUILDIDS "_file_mtime_scanned " + "where file = ? and mtime = ?"); + files.reset(); + while(1) + { + int rc = files.step(); + if (rc != SQLITE_ROW) + break; + + int64_t mtime = sqlite3_column_int64 (files, 0); + int64_t fileid = sqlite3_column_int64 (files, 1); + const char* filename = ((const char*) sqlite3_column_text (files, 2) ?: ""); + struct stat s; + rc = stat(filename, &s); + if (rc < 0 || (mtime != (int64_t) s.st_mtime)) + { + if (verbose > 2) + obatched(clog) << "groom: forgetting file=" << filename << " mtime=" << mtime << endl; + files_del_f_de.reset().bind(1,fileid).bind(2,mtime).step_ok_done(); + files_del_r_de.reset().bind(1,fileid).bind(2,mtime).step_ok_done(); + files_del_scan.reset().bind(1,fileid).bind(2,mtime).step_ok_done(); + } + } + files.reset(); + + // delete buildids with no references in _r_de or _f_de tables; + // cascades to _r_sref & _f_s records + sqlite_ps buildids_del (db, "nuke orphan buildids", + "delete from " BUILDIDS "_buildids " + "where not exists (select 1 from " BUILDIDS "_f_de d where " BUILDIDS "_buildids.id = d.buildid) " + "and not exists (select 1 from " BUILDIDS "_r_de d where " BUILDIDS "_buildids.id = d.buildid)"); + buildids_del.reset().step_ok_done(); + + // NB: "vacuum" is too heavy for even daily runs: it rewrites the entire db, so is done as maxigroom -G + sqlite_ps g1 (db, "incremental vacuum", "pragma incremental_vacuum"); + g1.reset().step_ok_done(); + sqlite_ps g2 (db, "optimize", "pragma optimize"); + g2.reset().step_ok_done(); + sqlite_ps g3 (db, "wal checkpoint", "pragma wal_checkpoint=truncate"); + g3.reset().step_ok_done(); + + database_stats_report(); + + gettimeofday (&tv_end, NULL); + double deltas = (tv_end.tv_sec - tv_start.tv_sec) + (tv_end.tv_usec - tv_start.tv_usec)*0.000001; + + obatched(clog) << "groomed database in " << deltas << "s" << endl; +} + + +static void* +thread_main_groom (void* /*arg*/) +{ + unsigned groom_timer = 0; + sig_atomic_t forced_groom_count = 0; + while (! interrupted) + { + try + { + if (groom_timer == 0) + groom (); + else if (sigusr2 != forced_groom_count) + { + forced_groom_count = sigusr2; + groom (); + } + } + catch (const sqlite_exception& e) + { + obatched(cerr) << e.message << endl; + } + sleep (1); + groom_timer ++; + if (groom_s) + groom_timer %= groom_s; + } + + return 0; +} + + +//////////////////////////////////////////////////////////////////////// + + +static void +signal_handler (int /* sig */) +{ + interrupted ++; + + if (db) + sqlite3_interrupt (db); + + // NB: don't do anything else in here +} + +static void +sigusr1_handler (int /* sig */) +{ + sigusr1 ++; + // NB: don't do anything else in here +} + +static void +sigusr2_handler (int /* sig */) +{ + sigusr2 ++; + // NB: don't do anything else in here +} + + + + + +// A user-defined sqlite function, to score the sharedness of the +// prefix of two strings. This is used to compare candidate debuginfo +// / source-rpm names, so that the closest match +// (directory-topology-wise closest) is found. This is important in +// case the same sref (source file name) is in many -debuginfo or +// -debugsource RPMs, such as when multiple versions/releases of the +// same package are in the database. + +static void sqlite3_sharedprefix_fn (sqlite3_context* c, int argc, sqlite3_value** argv) +{ + if (argc != 2) + sqlite3_result_error(c, "expect 2 string arguments", -1); + else if ((sqlite3_value_type(argv[0]) != SQLITE_TEXT) || + (sqlite3_value_type(argv[1]) != SQLITE_TEXT)) + sqlite3_result_null(c); + else + { + const unsigned char* a = sqlite3_value_text (argv[0]); + const unsigned char* b = sqlite3_value_text (argv[1]); + int i = 0; + while (*a++ == *b++) + i++; + sqlite3_result_int (c, i); + } +} + + +int +main (int argc, char *argv[]) +{ + (void) setlocale (LC_ALL, ""); + (void) bindtextdomain (PACKAGE_TARNAME, LOCALEDIR); + (void) textdomain (PACKAGE_TARNAME); + + /* Tell the library which version we are expecting. */ + elf_version (EV_CURRENT); + + /* Set computed default values. */ + db_path = string(getenv("HOME") ?: "/") + string("/.debuginfod.sqlite"); /* XDG? */ + int rc = regcomp (& file_include_regex, ".*", REG_EXTENDED|REG_NOSUB); // match everything + if (rc != 0) + error (EXIT_FAILURE, 0, "regcomp failure: %d", rc); + rc = regcomp (& file_exclude_regex, "^$", REG_EXTENDED|REG_NOSUB); // match nothing + if (rc != 0) + error (EXIT_FAILURE, 0, "regcomp failure: %d", rc); + + /* Parse and process arguments. */ + int remaining; + argp_program_version_hook = print_version; // this works + (void) argp_parse (&argp, argc, argv, ARGP_IN_ORDER, &remaining, NULL); + if (remaining != argc) + error (EXIT_FAILURE, 0, + "unexpected argument: %s", argv[remaining]); + + if (!scan_rpms && !scan_files && source_paths.size()>0) + obatched(clog) << "warning: without -F and/or -R, ignoring PATHs" << endl; + + (void) signal (SIGPIPE, SIG_IGN); // microhttpd can generate it incidentally, ignore + (void) signal (SIGINT, signal_handler); // ^C + (void) signal (SIGHUP, signal_handler); // EOF + (void) signal (SIGTERM, signal_handler); // systemd + (void) signal (SIGUSR1, sigusr1_handler); // end-user + (void) signal (SIGUSR2, sigusr2_handler); // end-user + + // do this before any threads start + scan_concurrency_sem = new semaphore(concurrency); + + /* Get database ready. */ + rc = sqlite3_open_v2 (db_path.c_str(), &db, (SQLITE_OPEN_READWRITE + |SQLITE_OPEN_CREATE + |SQLITE_OPEN_FULLMUTEX), /* thread-safe */ + NULL); + if (rc == SQLITE_CORRUPT) + { + (void) unlink (db_path.c_str()); + error (EXIT_FAILURE, 0, + "cannot open %s, deleted database: %s", db_path.c_str(), sqlite3_errmsg(db)); + } + else if (rc) + { + error (EXIT_FAILURE, 0, + "cannot open %s, consider deleting database: %s", db_path.c_str(), sqlite3_errmsg(db)); + } + + obatched(clog) << "opened database " << db_path << endl; + obatched(clog) << "sqlite version " << sqlite3_version << endl; + + // add special string-prefix-similarity function used in rpm sref/sdef resolution + rc = sqlite3_create_function(db, "sharedprefix", 2, SQLITE_UTF8, NULL, + & sqlite3_sharedprefix_fn, NULL, NULL); + if (rc != SQLITE_OK) + error (EXIT_FAILURE, 0, + "cannot create sharedprefix( function: %s", sqlite3_errmsg(db)); + + if (verbose > 3) + obatched(clog) << "ddl: " << DEBUGINFOD_SQLITE_DDL << endl; + rc = sqlite3_exec (db, DEBUGINFOD_SQLITE_DDL, NULL, NULL, NULL); + if (rc != SQLITE_OK) + { + error (EXIT_FAILURE, 0, + "cannot run database schema ddl: %s", sqlite3_errmsg(db)); + } + + // Start httpd server threads. Separate pool for IPv4 and IPv6, in + // case the host only has one protocol stack. + MHD_Daemon *d4 = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION +#if MHD_VERSION >= 0x00095300 + | MHD_USE_INTERNAL_POLLING_THREAD +#else + | MHD_USE_SELECT_INTERNALLY +#endif + | MHD_USE_DEBUG, /* report errors to stderr */ + http_port, + NULL, NULL, /* default accept policy */ + handler_cb, NULL, /* handler callback */ + MHD_OPTION_END); + MHD_Daemon *d6 = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION +#if MHD_VERSION >= 0x00095300 + | MHD_USE_INTERNAL_POLLING_THREAD +#else + | MHD_USE_SELECT_INTERNALLY +#endif + | MHD_USE_IPv6 + | MHD_USE_DEBUG, /* report errors to stderr */ + http_port, + NULL, NULL, /* default accept policy */ + handler_cb, NULL, /* handler callback */ + MHD_OPTION_END); + + if (d4 == NULL && d6 == NULL) // neither ipv4 nor ipv6? boo + { + sqlite3 *database = db; + db = 0; // for signal_handler not to freak + sqlite3_close (database); + error (EXIT_FAILURE, 0, "cannot start http server at port %d", http_port); + } + + obatched(clog) << "started http server on " + << (d4 != NULL ? "IPv4 " : "") + << (d6 != NULL ? "IPv6 " : "") + << "port=" << http_port << endl; + + // add maxigroom sql if -G given + if (maxigroom) + { + obatched(clog) << "maxigrooming database, please wait." << endl; + extra_ddl.push_back("create index if not exists " BUILDIDS "_r_sref_arc on " BUILDIDS "_r_sref(artifactsrc);"); + extra_ddl.push_back("delete from " BUILDIDS "_r_sdef where not exists (select 1 from " BUILDIDS "_r_sref b where " BUILDIDS "_r_sdef.content = b.artifactsrc);"); + extra_ddl.push_back("drop index if exists " BUILDIDS "_r_sref_arc;"); + + // NB: we don't maxigroom the _files interning table. It'd require a temp index on all the + // tables that have file foreign-keys, which is a lot. + + // NB: with =delete, may take up 3x disk space total during vacuum process + // vs. =off (only 2x but may corrupt database if program dies mid-vacuum) + // vs. =wal (>3x observed, but safe) + extra_ddl.push_back("pragma journal_mode=delete;"); + extra_ddl.push_back("vacuum;"); + extra_ddl.push_back("pragma journal_mode=wal;"); + } + + // run extra -D sql if given + for (auto&& i: extra_ddl) + { + if (verbose > 1) + obatched(clog) << "extra ddl:\n" << i << endl; + rc = sqlite3_exec (db, i.c_str(), NULL, NULL, NULL); + if (rc != SQLITE_OK && rc != SQLITE_DONE && rc != SQLITE_ROW) + error (0, 0, + "warning: cannot run database extra ddl %s: %s", i.c_str(), sqlite3_errmsg(db)); + } + + if (maxigroom) + obatched(clog) << "maxigroomed database" << endl; + + + obatched(clog) << "search concurrency " << concurrency << endl; + obatched(clog) << "rescan time " << rescan_s << endl; + obatched(clog) << "groom time " << groom_s << endl; + + vector source_file_scanner_threads; + vector source_rpm_scanner_threads; + pthread_t groom_thread; + + rc = pthread_create (& groom_thread, NULL, thread_main_groom, NULL); + if (rc < 0) + error (0, 0, "warning: cannot spawn thread (%d) to groom database\n", rc); + + if (scan_files) for (auto&& it : source_paths) + { + pthread_t pt; + rc = pthread_create (& pt, NULL, thread_main_scan_source_file_path, (void*) it.c_str()); + if (rc < 0) + error (0, 0, "warning: cannot spawn thread (%d) to scan source files %s\n", rc, it.c_str()); + else + source_file_scanner_threads.push_back(pt); + } + + if (scan_rpms) for (auto&& it : source_paths) + { + pthread_t pt; + rc = pthread_create (& pt, NULL, thread_main_scan_source_rpm_path, (void*) it.c_str()); + if (rc < 0) + error (0, 0, "warning: cannot spawn thread (%d) to scan source rpms %s\n", rc, it.c_str()); + else + source_rpm_scanner_threads.push_back(pt); + } + + + const char* du = getenv(DEBUGINFOD_URLS_ENV_VAR); + if (du && du[0] != '\0') // set to non-empty string? + obatched(clog) << "upstream debuginfod servers: " << du << endl; + + /* Trivial main loop! */ + while (! interrupted) + pause (); + + if (verbose) + obatched(clog) << "stopping" << endl; + + /* Stop all the web service threads. */ + if (d4) MHD_stop_daemon (d4); + if (d6) MHD_stop_daemon (d6); + + /* Join any source scanning threads. */ + for (auto&& it : source_file_scanner_threads) + pthread_join (it, NULL); + for (auto&& it : source_rpm_scanner_threads) + pthread_join (it, NULL); + pthread_join (groom_thread, NULL); + + /* With all threads known dead, we can clean up the global resources. */ + delete scan_concurrency_sem; + rc = sqlite3_exec (db, DEBUGINFOD_SQLITE_CLEANUP_DDL, NULL, NULL, NULL); + if (rc != SQLITE_OK) + { + error (0, 0, + "warning: cannot run database cleanup ddl: %s", sqlite3_errmsg(db)); + } + + // NB: no problem with unconditional free here - an earlier failed regcomp would exit program + (void) regfree (& file_include_regex); + (void) regfree (& file_exclude_regex); + + sqlite3 *database = db; + db = 0; // for signal_handler not to freak + (void) sqlite3_close (database); + + return 0; +} diff --git a/doc/Makefile.am b/doc/Makefile.am index 2310260..60e942c 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -19,10 +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_man8_MANS= notrans_dist_man1_MANS= if DEBUGINFOD +notrans_dist_man8_MANS += debuginfod.8 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.8 b/doc/debuginfod.8 new file mode 100644 index 0000000..eb1d891 --- /dev/null +++ b/doc/debuginfod.8 @@ -0,0 +1,369 @@ +'\"! tbl | nroff \-man +'\" t macro stdmacro + +.de SAMPLE +.br +.RS 0 +.nf +.nh +.. +.de ESAMPLE +.hy +.fi +.RE +.. + +.TH DEBUGINFOD 8 +.SH NAME +debuginfod \- debuginfo-related http file-server daemon + +.SH SYNOPSIS +.B debuginfod +[\fIOPTION\fP]... [\fIPATH\fP]... + +.SH DESCRIPTION +\fBdebuginfod\fP serves debuginfo-related artifacts over HTTP. It +periodically scans a set of directories for ELF/DWARF files and their +associated source code, as well as RPM files containing the above, to +build an index by their buildid. This index is used when remote +clients use the HTTP webapi, to fetch these files by the same buildid. + +If a debuginfod cannot service a given buildid artifact request +itself, and it is configured with information about upstream +debuginfod servers, it queries them for the same information, just as +\fBdebuginfod-find\fP would. If successful, it locally caches then +relays the file content to the original requester. + +If the \fB\-F\fP option is given, each listed PATH creates a thread to +scan for matching ELF/DWARF/source files under the given physical +directory. Source files are matched with DWARF files based on the +AT_comp_dir (compilation directory) attributes inside it. Duplicate +directories are ignored. You may use a file name for a PATH, but +source code indexing may be incomplete; prefer using a directory that +contains the binaries. Caution: source files listed in the DWARF may +be a path \fIanywhere\fP in the file system, and debuginfod will +readily serve their content on demand. (Imagine a doctored DWARF file +that lists \fI/etc/passwd\fP as a source file.) If this is a concern, +audit your binaries with tools such as: + +.SAMPLE +% eu-readelf -wline BINARY | sed -n '/^Directory.table/,/^File.name.table/p' +or +% eu-readelf -wline BINARY | sed -n '/^Directory.table/,/^Line.number/p' +or even use debuginfod itself: +% debuginfod -vvv -d :memory: -F BINARY 2>&1 | grep 'recorded.*source' +^C +.ESAMPLE + +If the \fB\-R\fP option is given each listed PATH creates a thread to +scan for ELF/DWARF/source files contained in matching RPMs under the +given physical directory. Duplicate directories are ignored. You may +use a file name for a PATH, but source code indexing may be +incomplete; prefer using a directory that contains normal RPMs +alongside debuginfo/debugsource RPMs. Because of complications such +as DWZ-compressed debuginfo, may require \fItwo\fP scan passes to +identify all source code. Source files for RPMs are only served +from other RPMs, so the caution for \-F does not apply. + +If no PATH is listed, or neither \-F nor \-R option is given, then +\fBdebuginfod\fP will simply serve content that it scanned into its +index in previous runs: the data is cumulative. + +File names must match extended regular expressions given by the \-I +option and not the \-X option (if any) in order to be considered. + + +.SH OPTIONS + +.TP +.B "\-F" +Activate ELF/DWARF file scanning threads. The default is off. + +.TP +.B "\-R" +Activate RPM file scanning threads. The default is off. + +.TP +.B "\-d FILE" "\-\-database=FILE" +Set the path of the sqlite database used to store the index. This +file is disposable in the sense that a later rescan will repopulate +data. It will contain absolute file path names, so it may not be +portable across machines. It may be frequently read/written, so it +should be on a fast filesytem. It should not be shared across +machines or users, to maximize sqlite locking performance. The +default database file is $HOME/.debuginfod.sqlite. + +.TP +.B "\-D SQL" "\-\-ddl=SQL" +Execute given sqlite statement after the database is opened and +initialized as extra DDL (SQL data definition language). This may be +useful to tune performance-related pragmas or indexes. May be +repeated. The default is nothing extra. + +.TP +.B "\-p NUM" "\-\-port=NUM" +Set the TCP port number on which debuginfod should listen, to service +HTTP requests. Both IPv4 and IPV6 sockets are opened, if possible. +The webapi is documented below. The default port number is 8002. + +.TP +.B "\-I REGEX" "\-\-include=REGEX" "\-X REGEX" "\-\-exclude=REGEX" +Govern the inclusion and exclusion of file names under the search +paths. The regular expressions are interpreted as unanchored POSIX +extended REs, thus may include alternation. They are evaluated +against the full path of each file, based on its \fBrealpath(3)\fP +canonicalization. By default, all files are included and none are +excluded. A file that matches both include and exclude REGEX is +excluded. (The \fIcontents\fP of RPM files are not subject to +inclusion or exclusion filtering: they are all processed.) + +.TP +.B "\-t SECONDS" "\-\-rescan\-time=SECONDS" +Set the rescan time for the file and RPM directories. This is the +amount of time the scanning threads will wait after finishing a scan, +before doing it again. A rescan for unchanged files is fast (because +the index also stores the file mtimes). A time of zero is acceptable, +and means that only one initial scan should performed. The default +rescan time is 300 seconds. Receiving a SIGUSR1 signal triggers a new +scan, independent of the rescan time (including if it was zero). + +.TP +.B "\-g SECONDS" "\-\-groom\-time=SECONDS" +Set the groom time for the index database. This is the amount of time +the grooming thread will wait after finishing a grooming pass before +doing it again. A groom operation quickly rescans all previously +scanned files, only to see if they are still present and current, so +it can deindex obsolete files. See also the \fIDATA MANAGEMENT\fP +section. The default groom time is 86400 seconds (1 day). A time of +zero is acceptable, and means that only one initial groom should be +performed. Receiving a SIGUSR2 signal triggers a new grooming pass, +independent of the groom time (including if it was zero). + +.TP +.B "\-G" +Run an extraordinary maximal-grooming pass at debuginfod startup. +This pass can take considerable time, because it tries to remove any +debuginfo-unrelated content from the RPM-related parts of the index. +It should not be run if any recent RPM-related indexing operations +were aborted early. It can take considerable space, because it +finishes up with an sqlite "vacuum" operation, which repacks the +database file by triplicating it temporarily. The default is not to +do maximal-grooming. See also the \fIDATA MANAGEMENT\fP section. + +.TP +.B "\-c NUM" "\-\-concurrency=NUM" +Set the concurrency limit for all the scanning threads. While many +threads may be spawned to cover all the given PATHs, only NUM may +concurrently do CPU-intensive operations like parsing an ELF file +or an RPM. The default is the number of processors on the system; +the minimum is 1. + +.TP +.B "\-v" +Increase verbosity of logging to the standard error file descriptor. +May be repeated to increase details. The default verbosity is 0. + +.SH WEBAPI + +.\" Much of the following text is duplicated with debuginfod-find.1 + +The debuginfod's webapi resembles ordinary file service, where a GET +request with a path containing a known buildid results in a file. +Unknown buildid / request combinations result in HTTP error codes. +This file service resemblance is intentional, so that an installation +can take advantage of standard HTTP management infrastructure. + +There are three requests. In each case, the buildid is encoded as a +lowercase hexadecimal string. For example, for a program \fI/bin/ls\fP, +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 /buildid/\fIBUILDID\fP/debuginfo + +If the given buildid is known to the 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 /buildid/\fIBUILDID\fP/executable + +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 /buildid/\fIBUILDID\fP/source\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: contrary to RFC 3986, the client 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 /buildid/BUILDID/source/usr/include/stdio.h +/path/to/foo.c /buildid/BUILDID/source/path/to/foo.c +\../bar/foo.c AT_comp_dir=/zoo/ /buildid/BUILDID/source/zoo//../bar/foo.c +.TE + +.SH DATA MANAGEMENT + +debuginfod stores its index in an sqlite database in a densely packed +set of interlinked tables. While the representation is as efficient +as we have been able to make it, it still takes a considerable amount +of data to record all debuginfo-related data of potentially a great +many files. This section offers some advice about the implications. + +As a general explanation for size, consider that debuginfod indexes +ELF/DWARF files, it stores their names and referenced source file +names, and buildids will be stored. When indexing RPMs, it stores +every file name \fIof or in\fP an RPM, every buildid, plus every +source file name referenced from a DWARF file. (Indexing RPMs takes +more space because the source files often reside in separate +subpackages that may not be indexed at the same pass, so extra +metadata has to be kept.) + +Getting down to numbers, in the case of Fedora RPMs (essentially, +gzip-compressed cpio files), the sqlite index database tends to be +from 0.5% to 3% of their size. It's larger for binaries that are +assembled out of a great many source files, or packages that carry +much debuginfo-unrelated content. It may be even larger during the +indexing phase due to temporary sqlite write-ahead-logging files; +these are checkpointed (cleaned out and removed) at shutdown. It may +be helpful to apply tight \-I or \-X regular-expression constraints to +exclude files from scanning that you know have no debuginfo-relevant +content. + +As debuginfod runs, it periodically rescans its target directories, +and any new content found is added to the database. Old content, such +as data for files that have disappeared or that have been replaced +with newer versions is removed at a periodic \fIgrooming\fP pass. +This means that the sqlite files grow fast during initial indexing, +slowly during index rescans, and periodically shrink during grooming. +There is also an optional one-shot \fImaximal grooming\fP pass is +available. It removes information debuginfo-unrelated data from the +RPM content index such as file names found in RPMs ("rpm sdef" +records) that are not referred to as source files from any binaries +find in RPMs ("rpm sref" records). This can save considerable disk +space. However, it is slow and temporarily requires up to twice the +database size as free space. Worse: it may result in missing +source-code info if the RPM traversals were interrupted, so the not +all source file references were known. Use it rarely to polish a +complete index. + +You should ensure that ample disk space remains available. (The flood +of error messages on -ENOSPC is ugly and nagging. But, like for most +other errors, debuginfod will resume when resources permit.) If +necessary, debuginfod can be stopped, the database file moved or +removed, and debuginfod restarted. + +sqlite offers several performance-related options in the form of +pragmas. Some may be useful to fine-tune the defaults plus the +debuginfod extras. The \-D option may be useful to tell debuginfod to +execute the given bits of SQL after the basic schema creation +commands. For example, the "synchronous", "cache_size", +"auto_vacuum", "threads", "journal_mode" pragmas may be fun to tweak +via \-D, if you're searching for peak performance. The "optimize", +"wal_checkpoint" pragmas may be useful to run periodically, outside +debuginfod. The default settings are performance- rather than +reliability-oriented, so a hardware crash might corrupt the database. +In these cases, it may be necessary to manually delete the sqlite +database and start over. + +As debuginfod changes in the future, we may have no choice but to +change the database schema in an incompatible manner. If this +happens, new versions of debuginfod will issue SQL statements to +\fIdrop\fP all prior schema & data, and start over. So, disk space +will not be wasted for retaining a no-longer-useable dataset. + +In summary, if your system can bear a 0.5%-3% index-to-RPM-dataset +size ratio, and slow growth afterwards, you should not need to +worry about disk space. If a system crash corrupts the database, +or you want to force debuginfod to reset and start over, simply +erase the sqlite file before restarting debuginfod. + + +.SH SECURITY + +debuginfod \fBdoes not\fP include any particular security features. +While it is robust with respect to inputs, some abuse is possible. It +forks a new thread for each incoming HTTP request, which could lead to +a denial-of-service in terms of RAM, CPU, disk I/O, or network I/O. +If this is a problem, users are advised to install debuginfod with a +HTTPS reverse-proxy front-end that enforces site policies for +firewalling, authentication, integrity, authorization, and load +control. + +When relaying queries to upstream debuginfods, debuginfod \fBdoes not\fP +include any particular security features. It trusts that the binaries +returned by the debuginfods 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. + + +.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. +Avoid referential loops that cause a server to contact itself, directly +or indirectly - the results would be hilarious. + +.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.sqlite +Default database file. +.PD + +.TP 20 +.B $HOME/.debuginfod_client_cache +Default cache directory for content from upstream debuginfods. +.PD + + +.SH "SEE ALSO" +.I "debuginfod-find(1)" +.I "sqlite3(1)" +.I \%https://prometheus.io/docs/instrumenting/exporters/ diff --git a/tests/ChangeLog b/tests/ChangeLog index 369af37..a5e5728 100644 --- a/tests/ChangeLog +++ b/tests/ChangeLog @@ -1,3 +1,10 @@ +2019-10-28 Aaron Merey + Frank Ch. Eigler + + * run-debuginfod-find.sh, debuginfod_build_id_find.c: New test. + * testfile-debuginfod-*.rpm.bz2: New data files for test. + * Makefile.am: Run it. + 2019-11-14 Andreas Schwab * run-large-elf-file.sh: Skip if available memory cannot be diff --git a/tests/Makefile.am b/tests/Makefile.am index ad0855d..83d27a0 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,6 +1,6 @@ ## Process this file with automake to create Makefile.in ## -## Copyright (C) 1996-2018 Red Hat, Inc. +## Copyright (C) 1996-2019 Red Hat, Inc. ## This file is part of elfutils. ## ## This file is free software; you can redistribute it and/or modify @@ -190,6 +190,11 @@ check_PROGRAMS += $(asm_TESTS) TESTS += $(asm_TESTS) run-disasm-bpf.sh endif +if DEBUGINFOD +check_PROGRAMS += debuginfod_build_id_find +TESTS += run-debuginfod-find.sh +endif + EXTRA_DIST = run-arextract.sh run-arsymtest.sh run-ar.sh \ run-show-die-info.sh run-get-files.sh run-get-lines.sh \ run-next-files.sh run-next-lines.sh testfile-only-debug-line.bz2 \ @@ -440,7 +445,25 @@ EXTRA_DIST = run-arextract.sh run-arsymtest.sh run-ar.sh \ run-dwelf_elf_e_machine_string.sh \ run-elfclassify.sh run-elfclassify-self.sh \ run-disasm-riscv64.sh \ - testfile-riscv64-dis1.o.bz2 testfile-riscv64-dis1.expect.bz2 + testfile-riscv64-dis1.o.bz2 testfile-riscv64-dis1.expect.bz2 \ + run-debuginfod-find.sh \ + debuginfod-rpms/fedora30/hello2-1.0-2.src.rpm \ + debuginfod-rpms/fedora30/hello2-1.0-2.x86_64.rpm \ + debuginfod-rpms/fedora30/hello2-debuginfo-1.0-2.x86_64.rpm \ + debuginfod-rpms/fedora30/hello2-debugsource-1.0-2.x86_64.rpm \ + debuginfod-rpms/fedora30/hello2-two-1.0-2.x86_64.rpm \ + debuginfod-rpms/fedora30/hello2-two-debuginfo-1.0-2.x86_64.rpm \ + debuginfod-rpms/hello2.spec. \ + debuginfod-rpms/rhel6/hello2-1.0-2.i686.rpm \ + debuginfod-rpms/rhel6/hello2-1.0-2.src.rpm \ + debuginfod-rpms/rhel6/hello2-debuginfo-1.0-2.i686.rpm \ + debuginfod-rpms/rhel6/hello2-two-1.0-2.i686.rpm \ + debuginfod-rpms/rhel7/hello2-1.0-2.src.rpm \ + debuginfod-rpms/rhel7/hello2-1.0-2.x86_64.rpm \ + debuginfod-rpms/rhel7/hello2-debuginfo-1.0-2.x86_64.rpm \ + debuginfod-rpms/rhel7/hello2-two-1.0-2.x86_64.rpm + + if USE_VALGRIND valgrind_cmd='valgrind -q --leak-check=full --error-exitcode=1' @@ -474,7 +497,7 @@ TESTS_ENVIRONMENT = LC_ALL=C; LANG=C; VALGRIND_CMD=$(valgrind_cmd); \ export LC_ALL; export LANG; export VALGRIND_CMD; \ NM=$(NM); export NM; LOG_COMPILER = $(abs_srcdir)/test-wrapper.sh \ - $(abs_top_builddir)/libdw:$(abs_top_builddir)/backends:$(abs_top_builddir)/libelf:$(abs_top_builddir)/libasm + $(abs_top_builddir)/libdw:$(abs_top_builddir)/backends:$(abs_top_builddir)/libelf:$(abs_top_builddir)/libasm:$(abs_top_builddir)/debuginfod installcheck-local: $(MAKE) $(AM_MAKEFLAGS) \ @@ -610,6 +633,7 @@ unit_info_LDADD = $(libdw) next_cfi_LDADD = $(libelf) $(libdw) elfcopy_LDADD = $(libelf) addsections_LDADD = $(libelf) +debuginfod_build_id_find_LDADD = $(libelf) $(libdw) xlate_notes_LDADD = $(libelf) elfrdwrnop_LDADD = $(libelf) dwelf_elf_e_machine_string_LDADD = $(libelf) $(libdw) diff --git a/tests/debuginfod-rpms/fedora30/hello2-1.0-2.src.rpm b/tests/debuginfod-rpms/fedora30/hello2-1.0-2.src.rpm new file mode 100644 index 0000000000000000000000000000000000000000..29a60999a309631bb7f9b98f46d49400c36afac7 GIT binary patch literal 8087 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 literal 0 HcmV?d00001 diff --git a/tests/debuginfod-rpms/fedora30/hello2-debuginfo-1.0-2.x86_64.rpm b/tests/debuginfod-rpms/fedora30/hello2-debuginfo-1.0-2.x86_64.rpm new file mode 100644 index 0000000000000000000000000000000000000000..dc6e0f2fc494bab624c902850ff99a6eaadba110 GIT binary patch literal 11316 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 literal 0 HcmV?d00001 diff --git a/tests/debuginfod-rpms/fedora30/hello2-debugsource-1.0-2.x86_64.rpm b/tests/debuginfod-rpms/fedora30/hello2-debugsource-1.0-2.x86_64.rpm new file mode 100644 index 0000000000000000000000000000000000000000..f036fa398b8c3222919ee7ade5447878838ccf4d GIT binary patch literal 7308 zcmeHMZH&~$8J@kpCFBAPgftDL5<{ufyXx$Yd9|l|j4_*$q1S%Hjn^_5nyes9|bAatoJ;DD92>zr2 z75uYr0HXatsNkRd8z9=h0A^ZLwUVNgRZFj^YQ-*>7}J%CU1Al3DVCu!qrwcXTclEA zT9KQ)QU(rpdeJZy!?x6d#!9@*%tA$DWu0@~DQk{iwu~aPEl1;)s;fFL!vhKFxcJ6d z`?;qtfBHYkaQw3wJxm)v?9=oKq~^R2(%*5ia;v@tq8Ot(277S0<8$N zBJlqnfg~p~KR>@80s%P-fOza#jR;O`3l`INV#7O0s=03inLs0C768H_1% zoGbE!f_DI7|E~&u6ClJh^DV)b3H~R+F;9j4&kBCCuwxu?d|VUs$9W>Z6%aJQEi?kh zbpZc1j0f631PJ}xsNmR+cAP(8JFXMjcMA@=q=X+8yb}<|e+>}FH`4aDbAnd@(I54p z{mztUQ8)5_K=99gMer?v@Z9Wo08!t7;Liy@2nge4Ul4p$*ncYcn6Muc{4>ISLhx~6 ze_8PR1-~e`1qgcDI{{Ijo$}n2;9T&}3GN8~px~b14+|ax!hCYy1jP9?1wSnK1R%^O z_dUU90O9@0{TdL~a@7Gq^nV2q^sG7#2>NoT1wRXj{;v!EXF%w`B+=hXuFt0u$fuG5 z*^C)hnxI5Nem~5|E!5v+JViG(`Lk-rbj1h z+z0;&?gb6kuTrzo@LXobZs12Wu9-1*gc~>NSdap$gcpddq7#}IRLNxgrQ*H`9!|^L zGo#pLau8Nw{#e#;AKpsu-$DC4ry08*Jb_0sjhb%EWwM>ybO0VW5;*ZBNF-Z^@4c>( zZonM$Cqiz6Hkk$WO%Sw1eVzMpKA9(}#c|`#e139rQhu)^@m#+-MWzgWm#(F9QcXQ` z58=8ey7lhqi=IKCqR>~ISbq!57ErmJdQmO2yMaxzc)7DK@ zvmj4wYGvKHYVa@!VzTHWRzJz0%TdVWuu(@BIOCJ@Uv|g!O`R|gN!Pl-4>n-O)fI;G zT&uG;s5e3$McnRpJ-*GX^Qemm8@6nAs}TJ6+J?<(FEEqN(SSL@hePhTQ&(Jsc`zEC z=^{m0ku^yrnykZj07Sf&|1(IIPC6Sw4yO z*fuPEy6Y(mY)(VBT8m-N!#<4zS~vG{8Z|hh4(O&4_Z*1@er&ovx8)9rZVrP+1BR%( zQQ9Y>eh|};dwjz5V;m!SMO@zr>}1cuu9eB+O-j{LxoGH0p@`=r76|K=3g<*p;JqeG z)6XB*d<0qeN|LUt*k;MX$&BQiQJ9Cd&L=wZi}^KMB6ZCZX{bP@DwP^;gHvgT>3MMF zVHkuP;bf?ru@rIE4BdDd+8f={=3V_m+wU9fAKNuD^r`K=eH+0&ZQpY5;61&gqq}-W zM*5(~(6$jOIed!oM*5yhP87pABf)_Za~21o6vbfxFSb+-ISWEtsxl@;lWq))7f}h< zBHEbD33gf@HdQ`-&jd&{GqgD@j>MSyKDwK}A6TxR_O9z1mFjWR=NqdqM(8pSYev&P ztAVDV8A(ll()DdAHZ2d;2=NR~o7%z@;P?UqbFSiqHQazq&A>66Mp#NUOUbz)Rek&s zSX45$3ju(DOp{)*LAzxS{K_XAABfO>X)|^t0}c`0 z;LOqu(<+*r8;+t@3{@?filb;HrZb~ZVU`6So-B(OZAU8>4EWkqv{lO}DTVZe!o8OK zx0|28jKBRW-YL(F?u7UK%-hSJY9s41w?O0i7rxwmI`a~Fa-7`0{MB}=@UP}gd*=S| z?C*DX=BB1z&$BgW9{9-9U(#o9dvoZscOAL)YqyNAnSSx;%B`#3dVJ679r56z5A)+p@bd*sl;nSH0mN+feKa{~T}eXP`% Q{n6}eA5LDnji_@% literal 0 HcmV?d00001 diff --git a/tests/debuginfod-rpms/fedora30/hello2-two-1.0-2.x86_64.rpm b/tests/debuginfod-rpms/fedora30/hello2-two-1.0-2.x86_64.rpm new file mode 100644 index 0000000000000000000000000000000000000000..e1c09dac2d57c8fad47fe023cb4b2cfb184ac963 GIT binary patch literal 10380 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 literal 0 HcmV?d00001 diff --git a/tests/debuginfod-rpms/fedora30/hello2-two-debuginfo-1.0-2.x86_64.rpm b/tests/debuginfod-rpms/fedora30/hello2-two-debuginfo-1.0-2.x86_64.rpm new file mode 100644 index 0000000000000000000000000000000000000000..b9a63dfa7e01a6614634beea692dc8c3de419485 GIT binary patch literal 10888 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 K2f#F9+P?sA^PFJ- literal 0 HcmV?d00001 diff --git a/tests/debuginfod-rpms/hello2.spec. b/tests/debuginfod-rpms/hello2.spec. new file mode 100644 index 0000000..0690992 --- /dev/null +++ b/tests/debuginfod-rpms/hello2.spec. @@ -0,0 +1,57 @@ +Summary: hello2 -- double hello, world rpm +Name: hello2 +Version: 1.0 +Release: 2 +Group: Utilities +License: GPL +Distribution: RPM ^W Elfutils test suite. +Vendor: Red Hat Software +Packager: Red Hat Software +URL: http://www.redhat.com +BuildRequires: gcc make +Source0: hello-1.0.tar.gz + +%description +Simple rpm demonstration with an eye to consumption by debuginfod. + +%package two +Summary: hello2two +License: GPL + +%description two +Dittoish. + +%prep +%setup -q -n hello-1.0 + +%build +gcc -g -O1 hello.c -o hello +gcc -g -O2 -D_FORTIFY_SOURCE=2 hello.c -o hello2 + +%install +rm -rf $RPM_BUILD_ROOT +mkdir -p $RPM_BUILD_ROOT/usr/local/bin +cp hello $RPM_BUILD_ROOT/usr/local/bin/ +cp hello2 $RPM_BUILD_ROOT/usr/local/bin/ + +%clean +rm -rf $RPM_BUILD_ROOT + +%files +%defattr(-,root,root) +%attr(0751,root,root) /usr/local/bin/hello + +%files two +%defattr(-,root,root) +%attr(0751,root,root) /usr/local/bin/hello2 + +%changelog +* Thu Nov 14 2019 Frank Ch. Eigler +- Added source code right here to make spec file self-contained. +- Dropped misc files not relevant to debuginfod testing. + +* Wed May 18 2016 Mark Wielaard +- Add hello2 for dwz testing support. + +* Tue Oct 20 1998 Jeff Johnson +- create. diff --git a/tests/debuginfod-rpms/rhel6/hello2-1.0-2.i686.rpm b/tests/debuginfod-rpms/rhel6/hello2-1.0-2.i686.rpm new file mode 100644 index 0000000000000000000000000000000000000000..cb99fd6e38b6be9f9dd6d16aeebf5cc91217e571 GIT binary patch literal 4112 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 Jz=@tT>px#==}Q0r literal 0 HcmV?d00001 diff --git a/tests/debuginfod-rpms/rhel6/hello2-1.0-2.src.rpm b/tests/debuginfod-rpms/rhel6/hello2-1.0-2.src.rpm new file mode 100644 index 0000000000000000000000000000000000000000..603a9b1a14deadb2f4da6ae643ae6c432d31bd9f GIT binary patch literal 3816 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 literal 0 HcmV?d00001 diff --git a/tests/debuginfod-rpms/rhel6/hello2-debuginfo-1.0-2.i686.rpm b/tests/debuginfod-rpms/rhel6/hello2-debuginfo-1.0-2.i686.rpm new file mode 100644 index 0000000000000000000000000000000000000000..19f4360c1e5bbb12ff21f5ddc7ced126dae04e83 GIT binary patch literal 6060 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 literal 0 HcmV?d00001 diff --git a/tests/debuginfod-rpms/rhel7/hello2-1.0-2.x86_64.rpm b/tests/debuginfod-rpms/rhel7/hello2-1.0-2.x86_64.rpm new file mode 100644 index 0000000000000000000000000000000000000000..0e6f6cc898657eaecd8e8b876cadb2586079a1f3 GIT binary patch literal 5156 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 literal 0 HcmV?d00001 diff --git a/tests/debuginfod-rpms/rhel7/hello2-debuginfo-1.0-2.x86_64.rpm b/tests/debuginfod-rpms/rhel7/hello2-debuginfo-1.0-2.x86_64.rpm new file mode 100644 index 0000000000000000000000000000000000000000..c1136f3a2daa4a6afb09f41cd328bf45ac361eb9 GIT binary patch literal 6936 zcmdT{c_39=)IWw~D9KQybTuOPy7Lf1B#}s^ROH-q&gJI1_jU)QiC09X43#L6Qc)42 zM6W?nG%0$8MvX6#rq_UPA5OjZdhdJR`{VudZPs1qx7XThuYLC3Ypv5+^tx3I0!<|t z6Nw~rGK%q)K|--WLZ(ruWI8VT*G&Z)@}q9@k>@HzA1+%6Xa=C6fLs9Sfr*C$t^o*d zKv(TK;Oc;^3H%ChT%QUE=&N-Bj>{bZsbE|tf^nD_lgkzGF)p9ZL@_20p>x@o0Ao{8 zCQL_IC=VYC+8C%RqwSr6(C+5??s2KV_Ce<%C~6lF4EWUF-(Lcr!5k3u2ftYmq*D*N z8~_17bWQ+{>!5%%0pabN-}pLI3H$}4=U^0O}5|~Ed-v9^xsNj2q&xhN^?Fk6n zOW?Hx?j!I|gIFE+1&~T2;9$+_xUb-7sr*7B@L>oWaIhYABf!D!q$M*%7-y-msL9D?cum#}2zQzK;ar@lCevRdTgLSI&00-kWf(bmG!1V-n z1sseYDgzuJKO1o1-_SU~@%Q6KV0?ZY&jDN${F4a01aP3Q$pjqt-w$wHZbx9;4_uDh z!N>a(@(cn83}P+ZK6uyae87P}+W0xe{Xqw@PBeir0>=_qK;U=+iwK-R;7Gvn`;reh z*c*d%z=1zH<+$7#`Z3->Al^W9$OVr-Bs>7gLScnaA|`<+k`zWl;2?}NI0#x8ER=um zlgcDvLKKse213{WF_fbH*9m|vr5qC|MWpaxObpC~VIqlCC=MdQQmIIY47edDDS}}I zt_U84a_w&(0q2NOz;6&Zg9gOkoTGrXLjKp-fnlL!8E_syJD@3Ce7rqL0gFkqL;|Hk zC<4YYxq>8D3KbXy^1)D&8<=Z>M4$+VWf(Nud+uL4Np@g9xuXn2fm;+r5;_CCx&g;S zF|opCV3km?LLr@QV-p@8PWfFC5(&l12q=QfUctuC4&J5#G6#h3=~zG{K=}DJ1BCaP z0vZp91djGo!1V#)_YI8Wv_KFSKO2UCCOUQEe|i0R{rK;VRFluf6B zL_%dz>8JqVvv@ozht1%!=uAG!K-nk_<>O0*U_Ort(hQf5Axr_Ag)vYnhsETv*$g_1 z%H%S*9E^qW*)%Sn&t$_WpMeM%EEbnXXV9r!n9hb+bPmGhg4D(5GEoK{;{YSDKtL7n zKo%1qD2K*nun`8#XeeEP^0+XQCV){8(|B}*BVe)xY$^=~o>F-ngiWUk z7(5OiL4j2^%%>s(4vWPR&=`D_N(G}}8k5gsAuyA}#-kNPq1b%?7fBFUWWyJVZHP-Y zC{fB~Hb06e12^saa5OxU7&BNiP`(|l@3z6i4>pLY|Hk?^{)hEQbEPzVBb%YW9083r~O55j*5(-vTJ$!w-2*cug(EdEHq8Ih20;VKkiF2W!X z(Bzg7(dP}X7D-?f7s=h=Fw9$q34{^f#mp;Fa47bdp>yO*A}t{%g$=4w z8sPXZgF>Y+0cTO@1AR0ag#rBXzv6Uev7MhI+j@B2*|J z)RB|K5(PFP1lRDJ6l4;qR3cO0T8IpT@vQPK z1CeMf4ui|4^4QeD>jkzD-^%}tKrMpTKX|Sm_#V&z9IxL&Z8A{r;&mTh>w%gB!fQHR zKzJ>O=aZ3u@Z19GmA`Gc4b8z1lRtf);QoJ`7oQt{#^=X5{@sBSu7mr6^FR8d1@M4R zgHesbR|-WaS%^Y>1fmHrz5t~%5SYrss0^xriqfep0f^Q-luzdgKse?zsQ*%ph-?TI zLKz$mgMqLy29pj`5h{bh=Tc!V2j*Z1i^<{e=?IU(AoK-P2!?4aJ{RW0OaTLY+aNFk z7!0{qNCtx%#E`+@@JE0Ld_o0* zYzCsakZ<$*)4(hJ3;zC!|3Sh>{y~UP|3N7D8zG*ee)J0W_V4-zGu8ht!Bga*6hvT~ zKd$h1Jn*LQmtzBwz33!z5Q!`mN-+|7F)R{+U#T_OWreGk&%!ybek&GuE%I@mWe?N@ zSOkJe@v2u2fLsBpUNR_e6&RwB$jEYqOakglGFb}C;81(HTuSCE1p*96gprs%6HFt+ z5Qz*W2O$Vq9xhaXP$wsmNaRp95QZw0V$41W6Js(V0wGeN1d50R75Ct_lLre#@<4G& z4iYON7AJB1x{wai4$9S6IZL|n&xL_IJn$Xd-~Y7)V7*>W7SH3D?4u%H3eE|tL!K;fx4y@1-`D3zdUL8Jf;?tu8z@K z_3p^bD-*41Zttn}^ze!vKdO=3-n~jQ*Wbaepg{7*CgPn>OGxulO60r)vz2!`8n(G^ zK~qJ=0k7JE61_K8&pqlim0_~M;b2Td{z@y&sXr|?I@6m6{V%{hypOd)jj7-Ps#bgFX$xTa2I9!_1 zOsj11MKmCCt!q0xSl+p~m*Ngr>1aah+nv8d~_ z?U5yWSI?I;XS-JBb$9hx+nZU*TgJwl1$f6jE^Q92G|3Mg_tS5}7g;V6D?3rI!8Y>M z_p!<6#^25~Quyqa>ru*67{6MYvWJ$G6{cP)apap`JM@VcX=Zep{&3?wu1me`%G#1T z(QZ8}cjeXtFFRt?+>5W;<@VO(zm)hPOJ9k z@6}D&y0EKAdL(O_l~Lx`my1$Vix-?d`)d4o^R=(J;T@}gvvIyNwazqFaXk0R>1Ndc zmH985g46=i^8-H3wYHk(<+EP9=O8~=T6ZF|Tr#Otb*q+T?#?uacL6^eH*_?vI%eCn z*JnkTP98L3>2)(90-KTMMN@>;yAIch_#xu4->n z$tGKY{&kb2t!FMuZ1q&!A~w%hJH~y+?q|+7gATm*d0Jao-sW(@IcxpVe5w8TE8>ag zY4mk%kH`s^-|LT?@JN!Blyn+N(cQmn@4lyPt1(Z`@X5uLKACTeQB^|?{8=L})w{7Z z_lr-h7C*Lq<*kgO3y%^SB|-A^$b{H}-lQ@6JX!_ziGE8-Z%)ygpSz|SIL)@ZOU)X4 zcjnwVdA6(XgpRt>niRlz=b~2WMLFv~E1{Wn*YNhvaRysnAFU501w zOx*KeUTPV~RcD8uQmRpER9-#F>S5uu0E?gMOwtYSo_M~seRkt7H;#5RyqLtvP#kNH zn)Uc{7*j3BalD^iWn+@TXx}$;3TEAqll+o39lr$b@Of3!lym&`<=mR2toCL~!-UTC zsata==xvV4%jj?FNY2}=`Ez>Io|?M-LtazZX$jLG28(_D(M2P*Y{d1qQ^GSEy@HR% zTRd9!lo98(bpH$wllst;!;ZM5n~l0U^zf8tXQOOPvs6mE*GhLgs_p#h)utNuhP(g$ z?AcdaEF*jy{T7j(dH1eA7$3f*?fDDwM70M=H>!~JohEVhfyx7ieV-P;*t^2HXb#zG zQH#?0_+;v`(==h@hJ%j;7nTWm%~v11-{A-chrT+jaJYP@x8`IKd+BF);mjp#<2Un? zmR5Z#dg&8)*mbm2Gw#sQBYjo;&ce8~R97l$Yhq2` zJzdecyXecVUAArh*O+5_`gW9tPg$^{7kYs@N;vQ!Sdy1**^t8iap!*)7HIBr&Z(NQA1TScin$b)$#E4Q;+UwopWx#-by-F z-M7moYozYyWkTgtCmoOUs)WOKiw})O!q-$e^J8q!6>ZxZO|!E-?$K}3`~*JOxaN-L zOxF86qp7d=Piry$t@Eu}le8){xVXQxamFuKjH;h{^-^{@ug+KWwr>kWluh{;Cw3Z| z?fx2b-}+W%nS6?)pMQ(~CH>>u-H8`G?EE4Y9{Hy@7Xvl0Zzgg;gDp}Jq(B*|$ z;EALQddBq>2f10<&#|8xl3+N^bP6=1*l-zj@1(ND+YRpR9J*bx;L=y~%IW7^&x)@l zF1X;a<1nG|Zs1QeI6qkNYOJsN)5q_B`iS{(szVzzC%c;m5=k?*~ z`d9l^3);O71R$>x<3ypaF2#*J&D53G=BU;`zs`K$*Shldu@B7R>Gr$lu3A1nj^h#B z9j%^vBy6vT@1r2&HG6Fmwrr2O=X_!bMyM$?_>pbFHSDZtA0LS^K@+svd1<@fiSqNmq|N_705mX^;$3-D!%2D z-SkF%FyHqsg>2Rqs=s|-U)$kz54{_7t&GN1qY$J#_PM;qa9edjNPgp!q{+`5HK{2> n*6F;xpby`f?ADb6sgxP*idKc%A1rUGcyWI${=o=o`z`$+SYSCp literal 0 HcmV?d00001 diff --git a/tests/debuginfod-rpms/rhel7/hello2-two-1.0-2.x86_64.rpm b/tests/debuginfod-rpms/rhel7/hello2-two-1.0-2.x86_64.rpm new file mode 100644 index 0000000000000000000000000000000000000000..5e89afd29be43afab82e5386e6e9840b09031d9f GIT binary patch literal 5092 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. */ + + +#ifdef HAVE_CONFIG_H +# include +#endif +#include +#include ELFUTILS_HEADER(dwfl) +#include +#include +#include +#include +#include + +static const char *debuginfo_path = ""; +static const Dwfl_Callbacks cb = + { + NULL, + dwfl_standard_find_debuginfo, + NULL, + (char **)&debuginfo_path, + }; + +int +main (int argc __attribute__ ((unused)), char **argv) +{ + int expect_pass = strcmp(argv[3], "0"); + Dwarf_Addr bias = 0; + Dwfl *dwfl = dwfl_begin(&cb); + dwfl_report_begin(dwfl); + + /* Open an executable. */ + Dwfl_Module *mod = dwfl_report_offline(dwfl, argv[2], argv[2], -1); + + /* The corresponding debuginfo will not be found in debuginfo_path + (since it's empty), causing the server to be queried. */ + + Dwarf *res = dwfl_module_getdwarf(mod, &bias); + if (expect_pass) + assert(res); + else + assert(!res); + + dwfl_end (dwfl); + + return 0; +} diff --git a/tests/run-debuginfod-find.sh b/tests/run-debuginfod-find.sh new file mode 100755 index 0000000..145c704 --- /dev/null +++ b/tests/run-debuginfod-find.sh @@ -0,0 +1,230 @@ +#!/bin/bash +# +# 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 a copy of the GNU General Public License +# along with this program. If not, see . + +set -x +. $srcdir/test-subr.sh # includes set -e + +DB=${PWD}/.debuginfod_tmp.sqlite +export DEBUGINFOD_CACHE_PATH=${PWD}/.client_cache + +# clean up trash if we were aborted early +trap 'kill $PID1 $PID2 || true; sleep 5; rm -rf F R ${PWD}/.client_cache*; exit_cleanup' 0 1 2 3 5 9 15 + +# find an unused port number +while true; do + PORT1=`expr '(' $RANDOM % 1000 ')' + 9000` + ss -atn | fgrep ":$PORT1" || break +done + +# We want to run debuginfod in the background. We also want to start +# it with the same check/installcheck-sensitive LD_LIBRARY_PATH stuff +# that the testrun alias sets. But: we if we just use +# testrun .../debuginfod +# it runs in a subshell, with different pid, so not helpful. +# +# So we gather the LD_LIBRARY_PATH with this cunning trick: +ldpath=`testrun sh -c 'echo $LD_LIBRARY_PATH'` + +mkdir F R +# not tempfiles F R - they are directories which we clean up manually +env DEBUGINFOD_TEST_WEBAPI_SLEEP=3 LD_LIBRARY_PATH=$ldpath DEBUGINFOD_URLS= ${abs_builddir}/../debuginfod/debuginfod -F -R -vvvv -d $DB -p $PORT1 -t0 -g0 R F & +PID1=$! +sleep 3 +export DEBUGINFOD_URLS=http://localhost:$PORT1/ # or without trailing / + +# Be patient when run on a busy machine things might take a bit. +# And under valgrind debuginfod-find is really, really slow. +if [ "x$VALGRIND_CMD" = "x" ]; then + export DEBUGINFOD_TIMEOUT=60 +else + export DEBUGINFOD_TIMEOUT=300 +fi + +# We use -t0 and -g0 here to turn off time-based scanning & grooming. +# For testing purposes, we just sic SIGUSR1 / SIGUSR2 at the process. + +######################################################################## + +# Compile a simple program, strip its debuginfo and save the build-id. +# Also move the debuginfo into another directory so that elfutils +# cannot find it without debuginfod. +echo "int main() { return 0; }" > ${PWD}/prog.c +tempfiles prog.c +gcc -g -o prog ${PWD}/prog.c + ${abs_top_builddir}/src/strip -g -f prog.debug ${PWD}/prog +BUILDID=`env LD_LIBRARY_PATH=$ldpath ${abs_builddir}/../src/readelf \ + -a prog | grep 'Build ID' | cut -d ' ' -f 7` + +mv prog F +mv prog.debug F +kill -USR1 $PID1 +sleep 3 # give enough time for scanning pass + +######################################################################## + +# Test whether elfutils, via the debuginfod client library dlopen hooks, +# is able to fetch debuginfo from the local debuginfod. +testrun ${abs_builddir}/debuginfod_build_id_find -e F/prog 1 + +######################################################################## + +# Test whether debuginfod-find is able to fetch those files. +rm -rf $DEBUGINFOD_CACHE_PATH # clean it from previous tests +filename=`testrun ${abs_top_builddir}/debuginfod/debuginfod-find debuginfo $BUILDID` +cmp $filename F/prog.debug + +filename=`testrun ${abs_top_builddir}/debuginfod/debuginfod-find executable $BUILDID` +cmp $filename F/prog + +filename=`testrun ${abs_top_builddir}/debuginfod/debuginfod-find source $BUILDID ${PWD}/prog.c` +cmp $filename ${PWD}/prog.c + +######################################################################## + +# Add artifacts to the search paths and test whether debuginfod finds them while already running. + +# Build another, non-stripped binary +echo "int main() { return 0; }" > ${PWD}/prog2.c +tempfiles prog2.c +gcc -g -o prog2 ${PWD}/prog2.c +BUILDID2=`env LD_LIBRARY_PATH=$ldpath ${abs_builddir}/../src/readelf \ + -a prog2 | grep 'Build ID' | cut -d ' ' -f 7` + +mv prog2 F +kill -USR1 $PID1 +sleep 3 + +# Rerun same tests for the prog2 binary +filename=`testrun ${abs_top_builddir}/debuginfod/debuginfod-find debuginfo $BUILDID2` +cmp $filename F/prog2 +filename=`testrun ${abs_top_builddir}/debuginfod/debuginfod-find executable $BUILDID2` +cmp $filename F/prog2 +filename=`testrun ${abs_top_builddir}/debuginfod/debuginfod-find source $BUILDID2 ${PWD}/prog2.c` +cmp $filename ${PWD}/prog2.c + +cp -rp ${abs_srcdir}/debuginfod-rpms R +kill -USR1 $PID1 +sleep 10 +kill -USR1 $PID1 # two hits of SIGUSR1 may be needed to resolve .debug->dwz->srefs +sleep 10 + + +# Run a bank of queries against the debuginfod-rpms test cases + +rpm_test() { + __BUILDID=$1 + __SOURCEPATH=$2 + __SOURCESHA1=$3 + + filename=`testrun ${abs_top_builddir}/debuginfod/debuginfod-find executable $__BUILDID` + buildid=`env LD_LIBRARY_PATH=$ldpath ${abs_builddir}/../src/readelf \ + -a $filename | grep 'Build ID' | cut -d ' ' -f 7` + test $__BUILDID = $buildid + + filename=`testrun ${abs_top_builddir}/debuginfod/debuginfod-find debuginfo $__BUILDID` + buildid=`env LD_LIBRARY_PATH=$ldpath ${abs_builddir}/../src/readelf \ + -a $filename | grep 'Build ID' | cut -d ' ' -f 7` + test $__BUILDID = $buildid + + filename=`testrun ${abs_top_builddir}/debuginfod/debuginfod-find source $__BUILDID $__SOURCEPATH` + hash=`cat $filename | sha1sum | awk '{print $1}'` + test $__SOURCESHA1 = $hash +} + + +# common source file sha1 +SHA=f4a1a8062be998ae93b8f1cd744a398c6de6dbb1 +# fedora30 +rpm_test c36708a78618d597dee15d0dc989f093ca5f9120 /usr/src/debug/hello2-1.0-2.x86_64/hello.c $SHA +rpm_test 41a236eb667c362a1c4196018cc4581e09722b1b /usr/src/debug/hello2-1.0-2.x86_64/hello.c $SHA +# rhel7 +rpm_test bc1febfd03ca05e030f0d205f7659db29f8a4b30 /usr/src/debug/hello-1.0/hello.c $SHA +rpm_test f0aa15b8aba4f3c28cac3c2a73801fefa644a9f2 /usr/src/debug/hello-1.0/hello.c $SHA +# rhel6 +rpm_test bbbf92ebee5228310e398609c23c2d7d53f6e2f9 /usr/src/debug/hello-1.0/hello.c $SHA +rpm_test d44d42cbd7d915bc938c81333a21e355a6022fb7 /usr/src/debug/hello-1.0/hello.c $SHA + +RPM_BUILDID=d44d42cbd7d915bc938c81333a21e355a6022fb7 # in rhel6/ subdir, for a later test + + +######################################################################## + +# Drop some of the artifacts, run a groom cycle; confirm that +# debuginfod has forgotten them, but remembers others + +rm -r R/debuginfod-rpms/rhel6/* +kill -USR2 $PID1 # groom cycle +sleep 3 +rm -rf $DEBUGINFOD_CACHE_PATH # clean it from previous tests + +testrun ${abs_top_builddir}/debuginfod/debuginfod-find executable $RPM_BUILDID && false || true + +testrun ${abs_top_builddir}/debuginfod/debuginfod-find executable $BUILDID2 + +######################################################################## + +# Federation mode + +# find another unused port +while true; do + PORT2=`expr '(' $RANDOM % 1000 ')' + 9000` + ss -atn | fgrep ":$PORT2" || break +done + +export DEBUGINFOD_CACHE_PATH=${PWD}/.client_cache2 +mkdir -p $DEBUGINFOD_CACHE_PATH +# NB: inherits the DEBUGINFOD_URLS to the first server +env LD_LIBRARY_PATH=$ldpath ${abs_builddir}/../debuginfod/debuginfod -F -vvvv -d ${DB}_2 -p $PORT2 & +PID2=$! +sleep 3 + +# have clients contact the new server +export DEBUGINFOD_URLS=http://localhost:$PORT2 +testrun ${abs_builddir}/debuginfod_build_id_find -e F/prog 1 + +# test parallel queries in client +export DEBUGINFOD_CACHE_PATH=${PWD}/.client_cache3 +mkdir -p $DEBUGINFOD_CACHE_PATH +export DEBUGINFOD_URLS="BAD http://localhost:$PORT1 localhost:$PORT1 http://localhost:$PORT2 DNE" + +testrun ${abs_builddir}/debuginfod_build_id_find -e F/prog2 1 + + +######################################################################## + +# Run the tests again without the servers running. The target file should +# be found in the cache. + +kill -INT $PID1 $PID2 +sleep 5 +tempfiles .debuginfod_* + +testrun ${abs_builddir}/debuginfod_build_id_find -e F/prog2 1 + +######################################################################## + +# Trigger a cache clean and run the tests again. The clients should be unable to +# find the target. +echo 0 > $DEBUGINFOD_CACHE_PATH/cache_clean_interval_s +echo 0 > $DEBUGINFOD_CACHE_PATH/max_unused_age_s + +testrun ${abs_builddir}/debuginfod_build_id_find -e F/prog 1 + +testrun ${abs_top_builddir}/debuginfod/debuginfod-find debuginfo $BUILDID2 && false || true + +exit 0 -- 2.7.4