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 +- .../fedora30/hello2-1.0-2.src.rpm | Bin 0 -> 8087 bytes .../fedora30/hello2-1.0-2.x86_64.rpm | Bin 0 -> 10448 bytes .../hello2-debuginfo-1.0-2.x86_64.rpm | Bin 0 -> 11316 bytes .../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 .../hello2-two-debuginfo-1.0-2.x86_64.rpm | Bin 0 -> 10888 bytes tests/debuginfod-rpms/hello2.spec. | 57 + .../rhel6/hello2-1.0-2.i686.rpm | Bin 0 -> 4112 bytes .../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 .../rhel7/hello2-1.0-2.src.rpm | Bin 0 -> 3819 bytes .../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 b641d0d5b..73643f919 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 10bd8d316..55e895aca 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 000000000..d8ef072be --- /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 000000000..c56bcf3fc --- /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 6771d13ba..3cd15ce3d 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 82acda3ab..6c3c444f1 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 8a3ed3afe..5deec3368 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 1a31cf6f4..b5679a2f9 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 a8ee45945..ec0f49f11 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 000000000..a87ec4d0c --- /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 231026044..60e942cb1 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 000000000..eb1d89108 --- /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 369af37e9..a5e572824 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 ad0855dec..83d27a067 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 Hc-jL100001 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 Hc-jL100001 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 Hc-jL100001 diff --git a/tests/debuginfod-rpms/hello2.spec. b/tests/debuginfod-rpms/hello2.spec. new file mode 100644 index 000000000..0690992f8 --- /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 zc-ozpd0Z3M7RM(nBFH9n18N;BkEM_h0we)JHi1A{1f+<7bTTs`1KG$-fS{I5EkfN< zu~NZmg%2*Jg17)GAgGnvr&tx0B8m&E%WEr&;G2-E*1rE>ys6QS@qyn9Y1b3!yN}in{V-sI z(PipC{MO7v2XH@NL%;)oP5w47jb;XT7_bZA5im~*@DtEJ3-B|buL1lV=!Jm)19(5+ zzW^Tt`~vVvz@uRNJAhvU{UP9Sz^?&MXqf@#tEZ>+hcOkf#+D8(hB076z@~tWfW0~3 zY1;K<%m8c*I2N!8uwMq)4Csk~%>g5TX8`|1fM4#NsHSa&suV&dl4C3==~sT0M2;d7v7DqP)55f}=q2Isia0n>B2rO=goG)a zq+n7-P&f;U#xYocP;iV~L}emM9130%_CJ-d7d-&!tHd!eLa|V}bP*Iw_e4+xPC{ae zQg}K$XJ%%y-Wxy?LZ-@s2ri$i^&yYWL>k{jTz4Vg1;f}Fn}>O@*_hBx#KqVis4MD$ z@?209_29VTu6!Zl%63OYA|9KIxM6$_&z&#i@LXNq=5(c8PH8WHe`l~rCCbvQBt+-Y zH(>R(H~TC;m+8uJm~8DkS?CQldRyMIgb<=EQqJN+@1E>~!$SN6k~u8414Q>t*V_lk zr3xiZk~kJbNbm?mij(g&P0eB=g4Vm63jk0A~spz z7bEzmbqKjETjQ=Pi_LOlxI!GT1I1XsddB2Ws9Y=~=?L>m6{dcm zGGRXqqeFz0tCT2CM_B;aMm?c}7VZND4vI zmPokkB1%;3tvNsRGo_N@-Uua^ z(j6hBT84_1a+zF3GF7rnLWVIZgia?UgenL*l$A}V8cjQ3Vj^hYzsjo2~$ z>%!?QzR~yR=0`S9T`{xXsbOWCLC%RJztr&;je&DQcU+wp(UrLNc=IvqL67p7YR?Px zkm1O8!!0GZ`&Tmffr!af*V2LPX%!Yt^TJu`b}KU?|V<^q`%%rNlv|bWu;VKiu=m z>w%dcRv)xaI$WdjER)`h53}iUY*3x7w|kni;0M>$d8=~UYM{m--QrzitB&ou;hlFc z=HqOqMQ5P2(Nc>kA=`a6WLdWE-Ii#0xnkqPC%Y?a3e0Y;S$=ZQr#UlI;ssz`7gQ=XE!-|e( z3)F~6ViXR3cV*-!e|;kN4{rMXTG4*J?138rR-fLF-Z7M3WcRwP9Z<*wYS>Bds&UH#V{QHL~41e2w4}ftFu00 z)s@i-_Zd~*MbEd}FW-S2F)4n^%->}G+&{K7f;E zM#rD*nr!FI%KzQ+Y7sARp}1$2V@c%^haZz4jjD%^zc7s0deipFsi-p+Q`!v16O=K` zZ)Yn}>FRCGqv{c^YH(l#4r#OzZix}kuJpLd?gN&9^b>s)r*Fm}6iQ>8Jq zpnYxk(952c=P#9a=VH{IBZgoy1bV--z(r?GfV*!h~bX%G+;8hBTjUKb5-kz}bUMP5Yt@ zL;H*7U$i-GTwiEa<{VrbfVY-E!eSeX4IceG5|Hj0wQ=L`> zZ`)|5;YRe7i?}FQAdN)AO12z&-eG35Z^JjTmbQXCs}!YIct5eMLVPip{^0SGm+{bZCE+h!7{dIz-$GiD- z)m~p`px^#DHhjFY$Zy6#`A}C6yRCNTy73JC=P|-M^}GV>6_wwlzo{9&<2VeNeEDeM z`AZOKXRVGkJG|w<&KT49qA6cgI}C)_MDSBe!p`b<#140Seq@x*ywPuX;Y#iBr++5o z-EKV&K?N`N{_Dg!$rZP$G4N45b78Le9YM|??UnQ7d7nM4fB7xMI6ke6{)cl9a*V6* O@A+8sCk`@-U;bZTYUxV= literal 0 Hc-jL100001 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 Hc-jL100001 diff --git a/tests/debuginfod-rpms/rhel7/hello2-1.0-2.src.rpm b/tests/debuginfod-rpms/rhel7/hello2-1.0-2.src.rpm new file mode 100644 index 0000000000000000000000000000000000000000..5ca4d423c8ccf974bd7f4c034ba8f7bf5d36589b GIT binary patch literal 3819 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 m6q4r#I$L>VrB*Rf>gaXLGA{eJ+)pX|H< literal 0 Hc-jL100001 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 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 literal 0 Hc-jL100001 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 000000000..145c704a1 --- /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.47.3