Blame debuginfod/debuginfod.cxx

Packit 032894
/* Debuginfo-over-http server.
Packit 032894
   Copyright (C) 2019 Red Hat, Inc.
Packit 032894
   This file is part of elfutils.
Packit 032894
Packit 032894
   This file is free software; you can redistribute it and/or modify
Packit 032894
   it under the terms of the GNU General Public License as published by
Packit 032894
   the Free Software Foundation; either version 3 of the License, or
Packit 032894
   (at your option) any later version.
Packit 032894
Packit 032894
   elfutils is distributed in the hope that it will be useful, but
Packit 032894
   WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 032894
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit 032894
   GNU General Public License for more details.
Packit 032894
Packit 032894
   You should have received a copy of the GNU General Public License
Packit 032894
   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
Packit 032894
Packit 032894
Packit 032894
/* cargo-cult from libdwfl linux-kernel-modules.c */
Packit 032894
/* In case we have a bad fts we include this before config.h because it
Packit 032894
   can't handle _FILE_OFFSET_BITS.
Packit 032894
   Everything we need here is fine if its declarations just come first.
Packit 032894
   Also, include sys/types.h before fts. On some systems fts.h is not self
Packit 032894
   contained. */
Packit 032894
#ifdef BAD_FTS
Packit 032894
  #include <sys/types.h>
Packit 032894
  #include <fts.h>
Packit 032894
#endif
Packit 032894
Packit 032894
#ifdef HAVE_CONFIG_H
Packit 032894
  #include "config.h"
Packit 032894
#endif
Packit 032894
Packit 032894
extern "C" {
Packit 032894
#include "printversion.h"
Packit 032894
}
Packit 032894
Packit 032894
#include "debuginfod.h"
Packit 032894
#include <dwarf.h>
Packit 032894
Packit 032894
#include <argp.h>
Packit 032894
#ifdef __GNUC__
Packit 032894
#undef __attribute__ /* glibc bug - rhbz 1763325 */
Packit 032894
#endif
Packit 032894
Packit 032894
#include <unistd.h>
Packit 032894
#include <stdlib.h>
Packit 032894
#include <error.h>
Packit 032894
// #include <libintl.h> // not until it supports C++ << better
Packit 032894
#include <locale.h>
Packit 032894
#include <pthread.h>
Packit 032894
#include <signal.h>
Packit 032894
#include <sys/stat.h>
Packit 032894
#include <sys/time.h>
Packit 032894
#include <unistd.h>
Packit 032894
#include <fcntl.h>
Packit 032894
#include <netdb.h>
Packit 032894
Packit 032894
Packit 032894
/* If fts.h is included before config.h, its indirect inclusions may not
Packit 032894
   give us the right LFS aliases of these functions, so map them manually.  */
Packit 032894
#ifdef BAD_FTS
Packit 032894
  #ifdef _FILE_OFFSET_BITS
Packit 032894
    #define open open64
Packit 032894
    #define fopen fopen64
Packit 032894
  #endif
Packit 032894
#else
Packit 032894
  #include <sys/types.h>
Packit 032894
  #include <fts.h>
Packit 032894
#endif
Packit 032894
Packit 032894
#include <cstring>
Packit 032894
#include <vector>
Packit 032894
#include <set>
Packit 032894
#include <map>
Packit 032894
#include <string>
Packit 032894
#include <iostream>
Packit 032894
#include <iomanip>
Packit 032894
#include <ostream>
Packit 032894
#include <sstream>
Packit 032894
#include <mutex>
Packit 032894
#include <condition_variable>
Packit 032894
#include <thread>
Packit 032894
// #include <regex> // on rhel7 gcc 4.8, not competent
Packit 032894
#include <regex.h>
Packit 032894
// #include <algorithm>
Packit 032894
using namespace std;
Packit 032894
Packit 032894
#include <gelf.h>
Packit 032894
#include <libdwelf.h>
Packit 032894
Packit 032894
#include <microhttpd.h>
Packit 032894
#include <curl/curl.h>
Packit 032894
#include <archive.h>
Packit 032894
#include <archive_entry.h>
Packit 032894
#include <sqlite3.h>
Packit 032894
Packit 032894
#ifdef __linux__
Packit 032894
#include <sys/syscall.h>
Packit 032894
#endif
Packit 032894
Packit 032894
#ifdef __linux__
Packit 032894
#define tid() syscall(SYS_gettid)
Packit 032894
#else
Packit 032894
#define tid() pthread_self()
Packit 032894
#endif
Packit 032894
Packit 032894
Packit 032894
// Roll this identifier for every sqlite schema incompatiblity.
Packit 032894
#define BUILDIDS "buildids9"
Packit 032894
Packit 032894
#if SQLITE_VERSION_NUMBER >= 3008000
Packit 032894
#define WITHOUT_ROWID "without rowid"
Packit 032894
#else
Packit 032894
#define WITHOUT_ROWID ""
Packit 032894
#endif
Packit 032894
Packit 032894
static const char DEBUGINFOD_SQLITE_DDL[] =
Packit 032894
  "pragma foreign_keys = on;\n"
Packit 032894
  "pragma synchronous = 0;\n" // disable fsync()s - this cache is disposable across a machine crash
Packit 032894
  "pragma journal_mode = wal;\n" // https://sqlite.org/wal.html
Packit 032894
  "pragma wal_checkpoint = truncate;\n" // clean out any preexisting wal file
Packit 032894
  "pragma journal_size_limit = 0;\n" // limit steady state file (between grooming, which also =truncate's)
Packit 032894
  "pragma auto_vacuum = incremental;\n" // https://sqlite.org/pragma.html
Packit 032894
  "pragma busy_timeout = 1000;\n" // https://sqlite.org/pragma.html
Packit 032894
  // NB: all these are overridable with -D option
Packit 032894
Packit 032894
  // Normalization table for interning file names
Packit 032894
  "create table if not exists " BUILDIDS "_files (\n"
Packit 032894
  "        id integer primary key not null,\n"
Packit 032894
  "        name text unique not null\n"
Packit 032894
  "        );\n"
Packit 032894
  // Normalization table for interning buildids
Packit 032894
  "create table if not exists " BUILDIDS "_buildids (\n"
Packit 032894
  "        id integer primary key not null,\n"
Packit 032894
  "        hex text unique not null);\n"
Packit 032894
  // Track the completion of scanning of a given file & sourcetype at given time
Packit 032894
  "create table if not exists " BUILDIDS "_file_mtime_scanned (\n"
Packit 032894
  "        mtime integer not null,\n"
Packit 032894
  "        file integer not null,\n"
Packit 032894
  "        size integer not null,\n" // in bytes
Packit 032894
  "        sourcetype text(1) not null\n"
Packit 032894
  "            check (sourcetype IN ('F', 'R')),\n"
Packit 032894
  "        foreign key (file) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n"
Packit 032894
  "        primary key (file, mtime, sourcetype)\n"
Packit 032894
  "        ) " WITHOUT_ROWID ";\n"
Packit 032894
  "create table if not exists " BUILDIDS "_f_de (\n"
Packit 032894
  "        buildid integer not null,\n"
Packit 032894
  "        debuginfo_p integer not null,\n"
Packit 032894
  "        executable_p integer not null,\n"
Packit 032894
  "        file integer not null,\n"
Packit 032894
  "        mtime integer not null,\n"
Packit 032894
  "        foreign key (file) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n"
Packit 032894
  "        foreign key (buildid) references " BUILDIDS "_buildids(id) on update cascade on delete cascade,\n"
Packit 032894
  "        primary key (buildid, file, mtime)\n"
Packit 032894
  "        ) " WITHOUT_ROWID ";\n"
Packit 032894
  "create table if not exists " BUILDIDS "_f_s (\n"
Packit 032894
  "        buildid integer not null,\n"
Packit 032894
  "        artifactsrc integer not null,\n"
Packit 032894
  "        file integer not null,\n" // NB: not necessarily entered into _mtime_scanned
Packit 032894
  "        mtime integer not null,\n"
Packit 032894
  "        foreign key (file) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n"
Packit 032894
  "        foreign key (artifactsrc) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n"
Packit 032894
  "        foreign key (buildid) references " BUILDIDS "_buildids(id) on update cascade on delete cascade,\n"
Packit 032894
  "        primary key (buildid, artifactsrc, file, mtime)\n"
Packit 032894
  "        ) " WITHOUT_ROWID ";\n"
Packit 032894
  "create table if not exists " BUILDIDS "_r_de (\n"
Packit 032894
  "        buildid integer not null,\n"
Packit 032894
  "        debuginfo_p integer not null,\n"
Packit 032894
  "        executable_p integer not null,\n"
Packit 032894
  "        file integer not null,\n"
Packit 032894
  "        mtime integer not null,\n"
Packit 032894
  "        content integer not null,\n"
Packit 032894
  "        foreign key (file) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n"
Packit 032894
  "        foreign key (content) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n"
Packit 032894
  "        foreign key (buildid) references " BUILDIDS "_buildids(id) on update cascade on delete cascade,\n"
Packit 032894
  "        primary key (buildid, debuginfo_p, executable_p, file, content, mtime)\n"
Packit 032894
  "        ) " WITHOUT_ROWID ";\n"
Packit 032894
  "create table if not exists " BUILDIDS "_r_sref (\n" // outgoing dwarf sourcefile references from rpm
Packit 032894
  "        buildid integer not null,\n"
Packit 032894
  "        artifactsrc integer not null,\n"
Packit 032894
  "        foreign key (artifactsrc) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n"
Packit 032894
  "        foreign key (buildid) references " BUILDIDS "_buildids(id) on update cascade on delete cascade,\n"
Packit 032894
  "        primary key (buildid, artifactsrc)\n"
Packit 032894
  "        ) " WITHOUT_ROWID ";\n"
Packit 032894
  "create table if not exists " BUILDIDS "_r_sdef (\n" // rpm contents that may satisfy sref
Packit 032894
  "        file integer not null,\n"
Packit 032894
  "        mtime integer not null,\n"
Packit 032894
  "        content integer not null,\n"
Packit 032894
  "        foreign key (file) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n"
Packit 032894
  "        foreign key (content) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n"
Packit 032894
  "        primary key (content, file, mtime)\n"
Packit 032894
  "        ) " WITHOUT_ROWID ";\n"
Packit 032894
  // create views to glue together some of the above tables, for webapi D queries
Packit 032894
  "create view if not exists " BUILDIDS "_query_d as \n"
Packit 032894
  "select\n"
Packit 032894
  "        b.hex as buildid, n.mtime, 'F' as sourcetype, f0.name as source0, n.mtime as mtime, null as source1\n"
Packit 032894
  "        from " BUILDIDS "_buildids b, " BUILDIDS "_files f0, " BUILDIDS "_f_de n\n"
Packit 032894
  "        where b.id = n.buildid and f0.id = n.file and n.debuginfo_p = 1\n"
Packit 032894
  "union all select\n"
Packit 032894
  "        b.hex as buildid, n.mtime, 'R' as sourcetype, f0.name as source0, n.mtime as mtime, f1.name as source1\n"
Packit 032894
  "        from " BUILDIDS "_buildids b, " BUILDIDS "_files f0, " BUILDIDS "_files f1, " BUILDIDS "_r_de n\n"
Packit 032894
  "        where b.id = n.buildid and f0.id = n.file and f1.id = n.content and n.debuginfo_p = 1\n"
Packit 032894
  ";"
Packit 032894
  // ... and for E queries
Packit 032894
  "create view if not exists " BUILDIDS "_query_e as \n"
Packit 032894
  "select\n"
Packit 032894
  "        b.hex as buildid, n.mtime, 'F' as sourcetype, f0.name as source0, n.mtime as mtime, null as source1\n"
Packit 032894
  "        from " BUILDIDS "_buildids b, " BUILDIDS "_files f0, " BUILDIDS "_f_de n\n"
Packit 032894
  "        where b.id = n.buildid and f0.id = n.file and n.executable_p = 1\n"
Packit 032894
  "union all select\n"
Packit 032894
  "        b.hex as buildid, n.mtime, 'R' as sourcetype, f0.name as source0, n.mtime as mtime, f1.name as source1\n"
Packit 032894
  "        from " BUILDIDS "_buildids b, " BUILDIDS "_files f0, " BUILDIDS "_files f1, " BUILDIDS "_r_de n\n"
Packit 032894
  "        where b.id = n.buildid and f0.id = n.file and f1.id = n.content and n.executable_p = 1\n"
Packit 032894
  ";"
Packit 032894
  // ... and for S queries
Packit 032894
  "create view if not exists " BUILDIDS "_query_s as \n"
Packit 032894
  "select\n"
Packit 032894
  "        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"
Packit 032894
  "        from " BUILDIDS "_buildids b, " BUILDIDS "_files f0, " BUILDIDS "_files fs, " BUILDIDS "_f_s n\n"
Packit 032894
  "        where b.id = n.buildid and f0.id = n.file and fs.id = n.artifactsrc\n"
Packit 032894
  "union all select\n"
Packit 032894
  "        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"
Packit 032894
  "        from " BUILDIDS "_buildids b, " BUILDIDS "_files f0, " BUILDIDS "_files f1, " BUILDIDS "_files fsref, "
Packit 032894
  "        " BUILDIDS "_r_sdef sd, " BUILDIDS "_r_sref sr, " BUILDIDS "_r_de sde\n"
Packit 032894
  "        where b.id = sr.buildid and f0.id = sd.file and fsref.id = sde.file and f1.id = sd.content\n"
Packit 032894
  "        and sr.artifactsrc = sd.content and sde.buildid = sr.buildid\n"
Packit 032894
  ";"
Packit 032894
  // and for startup overview counts
Packit 032894
  "drop view if exists " BUILDIDS "_stats;\n"
Packit 032894
  "create view if not exists " BUILDIDS "_stats as\n"
Packit 032894
  "          select 'file d/e' as label,count(*) as quantity from " BUILDIDS "_f_de\n"
Packit 032894
  "union all select 'file s',count(*) from " BUILDIDS "_f_s\n"
Packit 032894
  "union all select 'rpm d/e',count(*) from " BUILDIDS "_r_de\n"
Packit 032894
  "union all select 'rpm sref',count(*) from " BUILDIDS "_r_sref\n"
Packit 032894
  "union all select 'rpm sdef',count(*) from " BUILDIDS "_r_sdef\n"
Packit 032894
  "union all select 'buildids',count(*) from " BUILDIDS "_buildids\n"
Packit 032894
  "union all select 'filenames',count(*) from " BUILDIDS "_files\n"
Packit 032894
  "union all select 'files scanned (#)',count(*) from " BUILDIDS "_file_mtime_scanned\n"
Packit 032894
  "union all select 'files scanned (mb)',coalesce(sum(size)/1024/1024,0) from " BUILDIDS "_file_mtime_scanned\n"
Packit 032894
#if SQLITE_VERSION_NUMBER >= 3016000
Packit 032894
  "union all select 'index db size (mb)',page_count*page_size/1024/1024 as size FROM pragma_page_count(), pragma_page_size()\n"
Packit 032894
#endif
Packit 032894
  ";\n"
Packit 032894
Packit 032894
// schema change history & garbage collection
Packit 032894
//
Packit 032894
// XXX: we could have migration queries here to bring prior-schema
Packit 032894
// data over instead of just dropping it.
Packit 032894
//
Packit 032894
// buildids9: widen the mtime_scanned table
Packit 032894
  "" // <<< we are here
Packit 032894
// buildids8: slim the sref table
Packit 032894
  "drop table if exists buildids8_f_de;\n"
Packit 032894
  "drop table if exists buildids8_f_s;\n"
Packit 032894
  "drop table if exists buildids8_r_de;\n"
Packit 032894
  "drop table if exists buildids8_r_sref;\n"
Packit 032894
  "drop table if exists buildids8_r_sdef;\n"
Packit 032894
  "drop table if exists buildids8_file_mtime_scanned;\n"
Packit 032894
  "drop table if exists buildids8_files;\n"
Packit 032894
  "drop table if exists buildids8_buildids;\n"
Packit 032894
// buildids7: separate _norm table into dense subtype tables
Packit 032894
  "drop table if exists buildids7_f_de;\n"
Packit 032894
  "drop table if exists buildids7_f_s;\n"
Packit 032894
  "drop table if exists buildids7_r_de;\n"
Packit 032894
  "drop table if exists buildids7_r_sref;\n"
Packit 032894
  "drop table if exists buildids7_r_sdef;\n"
Packit 032894
  "drop table if exists buildids7_file_mtime_scanned;\n"
Packit 032894
  "drop table if exists buildids7_files;\n"
Packit 032894
  "drop table if exists buildids7_buildids;\n"
Packit 032894
// buildids6: drop bolo/rfolo again, represent sources / rpmcontents in main table
Packit 032894
  "drop table if exists buildids6_norm;\n"
Packit 032894
  "drop table if exists buildids6_files;\n"
Packit 032894
  "drop table if exists buildids6_buildids;\n"
Packit 032894
  "drop view if exists buildids6;\n"
Packit 032894
// buildids5: redefine srcfile1 column to be '.'-less (for rpms)
Packit 032894
  "drop table if exists buildids5_norm;\n"
Packit 032894
  "drop table if exists buildids5_files;\n"
Packit 032894
  "drop table if exists buildids5_buildids;\n"
Packit 032894
  "drop table if exists buildids5_bolo;\n"
Packit 032894
  "drop table if exists buildids5_rfolo;\n"
Packit 032894
  "drop view if exists buildids5;\n"
Packit 032894
// buildids4: introduce rpmfile RFOLO
Packit 032894
  "drop table if exists buildids4_norm;\n"
Packit 032894
  "drop table if exists buildids4_files;\n"
Packit 032894
  "drop table if exists buildids4_buildids;\n"
Packit 032894
  "drop table if exists buildids4_bolo;\n"
Packit 032894
  "drop table if exists buildids4_rfolo;\n"
Packit 032894
  "drop view if exists buildids4;\n"
Packit 032894
// buildids3*: split out srcfile BOLO
Packit 032894
  "drop table if exists buildids3_norm;\n"
Packit 032894
  "drop table if exists buildids3_files;\n"
Packit 032894
  "drop table if exists buildids3_buildids;\n"
Packit 032894
  "drop table if exists buildids3_bolo;\n"
Packit 032894
  "drop view if exists buildids3;\n"
Packit 032894
// buildids2: normalized buildid and filenames into interning tables;
Packit 032894
  "drop table if exists buildids2_norm;\n"
Packit 032894
  "drop table if exists buildids2_files;\n"
Packit 032894
  "drop table if exists buildids2_buildids;\n"
Packit 032894
  "drop view if exists buildids2;\n"
Packit 032894
  // buildids1: made buildid and artifacttype NULLable, to represent cached-negative
Packit 032894
//           lookups from sources, e.g. files or rpms that contain no buildid-indexable content
Packit 032894
  "drop table if exists buildids1;\n"
Packit 032894
// buildids: original
Packit 032894
  "drop table if exists buildids;\n"
Packit 032894
  ;
Packit 032894
Packit 032894
static const char DEBUGINFOD_SQLITE_CLEANUP_DDL[] =
Packit 032894
  "pragma wal_checkpoint = truncate;\n" // clean out any preexisting wal file
Packit 032894
  ;
Packit 032894
Packit 032894
Packit 032894
Packit 032894
Packit 032894
/* Name and version of program.  */
Packit 032894
/* ARGP_PROGRAM_VERSION_HOOK_DEF = print_version; */ // not this simple for C++
Packit 032894
Packit 032894
/* Bug report address.  */
Packit 032894
ARGP_PROGRAM_BUG_ADDRESS_DEF = PACKAGE_BUGREPORT;
Packit 032894
Packit 032894
/* Definitions of arguments for argp functions.  */
Packit 032894
static const struct argp_option options[] =
Packit 032894
  {
Packit 032894
   { NULL, 0, NULL, 0, "Scanners:", 1 },
Packit 032894
   { "scan-file-dir", 'F', NULL, 0, "Enable ELF/DWARF file scanning threads.", 0 },
Packit 032894
   { "scan-rpm-dir", 'R', NULL, 0, "Enable RPM scanning threads.", 0 },
Packit 032894
   // "source-oci-imageregistry"  ... 
Packit 032894
Packit 032894
   { NULL, 0, NULL, 0, "Options:", 2 },
Packit 032894
   { "logical", 'L', NULL, 0, "Follow symlinks, default=ignore.", 0 },
Packit 032894
   { "rescan-time", 't', "SECONDS", 0, "Number of seconds to wait between rescans, 0=disable.", 0 },
Packit 032894
   { "groom-time", 'g', "SECONDS", 0, "Number of seconds to wait between database grooming, 0=disable.", 0 },
Packit 032894
   { "maxigroom", 'G', NULL, 0, "Run a complete database groom/shrink pass at startup.", 0 },
Packit 032894
   { "concurrency", 'c', "NUM", 0, "Limit scanning thread concurrency to NUM.", 0 },
Packit 032894
   { "include", 'I', "REGEX", 0, "Include files matching REGEX, default=all.", 0 },
Packit 032894
   { "exclude", 'X', "REGEX", 0, "Exclude files matching REGEX, default=none.", 0 },
Packit 032894
   { "port", 'p', "NUM", 0, "HTTP port to listen on, default 8002.", 0 },
Packit 032894
   { "database", 'd', "FILE", 0, "Path to sqlite database.", 0 },
Packit 032894
   { "ddl", 'D', "SQL", 0, "Apply extra sqlite ddl/pragma to connection.", 0 },
Packit 032894
   { "verbose", 'v', NULL, 0, "Increase verbosity.", 0 },
Packit 032894
Packit 032894
   { NULL, 0, NULL, 0, NULL, 0 }
Packit 032894
  };
Packit 032894
Packit 032894
/* Short description of program.  */
Packit 032894
static const char doc[] = "Serve debuginfo-related content across HTTP from files under PATHs.";
Packit 032894
Packit 032894
/* Strings for arguments in help texts.  */
Packit 032894
static const char args_doc[] = "[PATH ...]";
Packit 032894
Packit 032894
/* Prototype for option handler.  */
Packit 032894
static error_t parse_opt (int key, char *arg, struct argp_state *state);
Packit 032894
Packit 032894
/* Data structure to communicate with argp functions.  */
Packit 032894
static struct argp argp =
Packit 032894
  {
Packit 032894
   options, parse_opt, args_doc, doc, NULL, NULL, NULL
Packit 032894
  };
Packit 032894
Packit 032894
Packit 032894
static string db_path;
Packit 032894
static sqlite3 *db;
Packit 032894
static unsigned verbose;
Packit 032894
static volatile sig_atomic_t interrupted = 0;
Packit 032894
static volatile sig_atomic_t sigusr1 = 0;
Packit 032894
static volatile sig_atomic_t sigusr2 = 0;
Packit 032894
static unsigned http_port = 8002;
Packit 032894
static unsigned rescan_s = 300;
Packit 032894
static unsigned groom_s = 86400;
Packit 032894
static unsigned maxigroom = false;
Packit 032894
static unsigned concurrency = std::thread::hardware_concurrency() ?: 1;
Packit 032894
static set<string> source_paths;
Packit 032894
static bool scan_files = false;
Packit 032894
static bool scan_rpms = false;
Packit 032894
static vector<string> extra_ddl;
Packit 032894
static regex_t file_include_regex;
Packit 032894
static regex_t file_exclude_regex;
Packit 032894
static bool traverse_logical;
Packit 032894
Packit 032894
static void set_metric(const string& key, int64_t value);
Packit 032894
// static void inc_metric(const string& key);
Packit 032894
static void set_metric(const string& metric,
Packit 032894
                       const string& lname, const string& lvalue,
Packit 032894
                       int64_t value);
Packit 032894
static void inc_metric(const string& metric,
Packit 032894
                       const string& lname, const string& lvalue);
Packit 032894
static void add_metric(const string& metric,
Packit 032894
                       const string& lname, const string& lvalue,
Packit 032894
                       int64_t value);
Packit 032894
Packit 032894
/* Handle program arguments.  */
Packit 032894
static error_t
Packit 032894
parse_opt (int key, char *arg,
Packit 032894
	   struct argp_state *state __attribute__ ((unused)))
Packit 032894
{
Packit 032894
  int rc;
Packit 032894
  switch (key)
Packit 032894
    {
Packit 032894
    case 'v': verbose ++; break;
Packit 032894
    case 'd': db_path = string(arg); break;
Packit 032894
    case 'p': http_port = (unsigned) atoi(arg);
Packit 032894
      if (http_port > 65535) argp_failure(state, 1, EINVAL, "port number");
Packit 032894
      break;
Packit 032894
    case 'F': scan_files = true; break;
Packit 032894
    case 'R': scan_rpms = true; break;
Packit 032894
    case 'L':
Packit 032894
      traverse_logical = true;
Packit 032894
      break;
Packit 032894
    case 'D': extra_ddl.push_back(string(arg)); break;
Packit 032894
    case 't':
Packit 032894
      rescan_s = (unsigned) atoi(arg);
Packit 032894
      break;
Packit 032894
    case 'g':
Packit 032894
      groom_s = (unsigned) atoi(arg);
Packit 032894
      break;
Packit 032894
    case 'G':
Packit 032894
      maxigroom = true;
Packit 032894
      break;
Packit 032894
    case 'c':
Packit 032894
      concurrency = (unsigned) atoi(arg);
Packit 032894
      if (concurrency < 1) concurrency = 1;
Packit 032894
      break;
Packit 032894
    case 'I':
Packit 032894
      // NB: no problem with unconditional free here - an earlier failed regcomp would exit program
Packit 032894
      regfree (&file_include_regex);
Packit 032894
      rc = regcomp (&file_include_regex, arg, REG_EXTENDED|REG_NOSUB);
Packit 032894
      if (rc != 0)
Packit 032894
        argp_failure(state, 1, EINVAL, "regular expession");
Packit 032894
      break;
Packit 032894
    case 'X':
Packit 032894
      regfree (&file_exclude_regex);
Packit 032894
      rc = regcomp (&file_exclude_regex, arg, REG_EXTENDED|REG_NOSUB);
Packit 032894
      if (rc != 0)
Packit 032894
        argp_failure(state, 1, EINVAL, "regular expession");
Packit 032894
      break;
Packit 032894
    case ARGP_KEY_ARG:
Packit 032894
      source_paths.insert(string(arg));
Packit 032894
      break;
Packit 032894
      // case 'h': argp_state_help (state, stderr, ARGP_HELP_LONG|ARGP_HELP_EXIT_OK);
Packit 032894
    default: return ARGP_ERR_UNKNOWN;
Packit 032894
    }
Packit 032894
Packit 032894
  return 0;
Packit 032894
}
Packit 032894
Packit 032894
Packit 032894
////////////////////////////////////////////////////////////////////////
Packit 032894
Packit 032894
Packit 032894
// represent errors that may get reported to an ostream and/or a libmicrohttpd connection
Packit 032894
Packit 032894
struct reportable_exception
Packit 032894
{
Packit 032894
  int code;
Packit 032894
  string message;
Packit 032894
Packit 032894
  reportable_exception(int c, const string& m): code(c), message(m) {}
Packit 032894
  reportable_exception(const string& m): code(503), message(m) {}
Packit 032894
  reportable_exception(): code(503), message() {}
Packit 032894
Packit 032894
  void report(ostream& o) const; // defined under obatched() class below
Packit 032894
Packit 032894
  int mhd_send_response(MHD_Connection* c) const {
Packit 032894
    MHD_Response* r = MHD_create_response_from_buffer (message.size(),
Packit 032894
                                                       (void*) message.c_str(),
Packit 032894
                                                       MHD_RESPMEM_MUST_COPY);
Packit 032894
    MHD_add_response_header (r, "Content-Type", "text/plain");
Packit 032894
    int rc = MHD_queue_response (c, code, r);
Packit 032894
    MHD_destroy_response (r);
Packit 032894
    return rc;
Packit 032894
  }
Packit 032894
};
Packit 032894
Packit 032894
Packit 032894
struct sqlite_exception: public reportable_exception
Packit 032894
{
Packit 032894
  sqlite_exception(int rc, const string& msg):
Packit 032894
    reportable_exception(string("sqlite3 error: ") + msg + ": " + string(sqlite3_errstr(rc) ?: "?")) {}
Packit 032894
};
Packit 032894
Packit 032894
struct libc_exception: public reportable_exception
Packit 032894
{
Packit 032894
  libc_exception(int rc, const string& msg):
Packit 032894
    reportable_exception(string("libc error: ") + msg + ": " + string(strerror(rc) ?: "?")) {}
Packit 032894
};
Packit 032894
Packit 032894
Packit 032894
struct archive_exception: public reportable_exception
Packit 032894
{
Packit 032894
  archive_exception(const string& msg):
Packit 032894
    reportable_exception(string("libarchive error: ") + msg) {}
Packit 032894
  archive_exception(struct archive* a, const string& msg):
Packit 032894
    reportable_exception(string("libarchive error: ") + msg + ": " + string(archive_error_string(a) ?: "?")) {}
Packit 032894
};
Packit 032894
Packit 032894
Packit 032894
struct elfutils_exception: public reportable_exception
Packit 032894
{
Packit 032894
  elfutils_exception(int rc, const string& msg):
Packit 032894
    reportable_exception(string("elfutils error: ") + msg + ": " + string(elf_errmsg(rc) ?: "?")) {}
Packit 032894
};
Packit 032894
Packit 032894
Packit 032894
////////////////////////////////////////////////////////////////////////
Packit 032894
Packit 032894
// a c++ counting-semaphore class ... since we're c++11 not c++20
Packit 032894
Packit 032894
class semaphore
Packit 032894
{
Packit 032894
public:
Packit 032894
  semaphore (unsigned c=1): count(c) {}
Packit 032894
  inline void notify () {
Packit 032894
    unique_lock<mutex> lock(mtx);
Packit 032894
    count++;
Packit 032894
    cv.notify_one();
Packit 032894
  }
Packit 032894
  inline void wait() {
Packit 032894
    unique_lock<mutex> lock(mtx);
Packit 032894
    while (count == 0)
Packit 032894
      cv.wait(lock);
Packit 032894
    count--;
Packit 032894
  }
Packit 032894
private:
Packit 032894
  mutex mtx;
Packit 032894
  condition_variable cv;
Packit 032894
  unsigned count;
Packit 032894
};
Packit 032894
Packit 032894
Packit 032894
class semaphore_borrower
Packit 032894
{
Packit 032894
public:
Packit 032894
  semaphore_borrower(semaphore* s): sem(s) { sem->wait(); }
Packit 032894
  ~semaphore_borrower() { sem->notify(); }
Packit 032894
private:
Packit 032894
  semaphore* sem;
Packit 032894
};
Packit 032894
Packit 032894
Packit 032894
////////////////////////////////////////////////////////////////////////
Packit 032894
Packit 032894
Packit 032894
// Print a standard timestamp.
Packit 032894
static ostream&
Packit 032894
timestamp (ostream &o)
Packit 032894
{
Packit 032894
  char datebuf[80];
Packit 032894
  char *now2 = NULL;
Packit 032894
  time_t now_t = time(NULL);
Packit 032894
  struct tm *now = gmtime (&now_t);
Packit 032894
  if (now)
Packit 032894
    {
Packit 032894
      (void) strftime (datebuf, sizeof (datebuf), "%c", now);
Packit 032894
      now2 = datebuf;
Packit 032894
    }
Packit 032894
Packit 032894
  return o << "[" << (now2 ? now2 : "") << "] "
Packit 032894
           << "(" << getpid () << "/" << tid() << "): ";
Packit 032894
}
Packit 032894
Packit 032894
Packit 032894
// A little class that impersonates an ostream to the extent that it can
Packit 032894
// take << streaming operations.  It batches up the bits into an internal
Packit 032894
// stringstream until it is destroyed; then flushes to the original ostream.
Packit 032894
// It adds a timestamp
Packit 032894
class obatched
Packit 032894
{
Packit 032894
private:
Packit 032894
  ostream& o;
Packit 032894
  stringstream stro;
Packit 032894
  static mutex lock;
Packit 032894
public:
Packit 032894
  obatched(ostream& oo, bool timestamp_p = true): o(oo)
Packit 032894
  {
Packit 032894
    if (timestamp_p)
Packit 032894
      timestamp(stro);
Packit 032894
  }
Packit 032894
  ~obatched()
Packit 032894
  {
Packit 032894
    unique_lock<mutex> do_not_cross_the_streams(obatched::lock);
Packit 032894
    o << stro.str();
Packit 032894
    o.flush();
Packit 032894
  }
Packit 032894
  operator ostream& () { return stro; }
Packit 032894
  template <typename T> ostream& operator << (const T& t) { stro << t; return stro; }
Packit 032894
};
Packit 032894
mutex obatched::lock; // just the one, since cout/cerr iostreams are not thread-safe
Packit 032894
Packit 032894
Packit 032894
void reportable_exception::report(ostream& o) const {
Packit 032894
  obatched(o) << message << endl;
Packit 032894
}
Packit 032894
Packit 032894
Packit 032894
////////////////////////////////////////////////////////////////////////
Packit 032894
Packit 032894
Packit 032894
// RAII style sqlite prepared-statement holder that matches { } block lifetime
Packit 032894
Packit 032894
struct sqlite_ps
Packit 032894
{
Packit 032894
private:
Packit 032894
  sqlite3* db;
Packit 032894
  const string nickname;
Packit 032894
  const string sql;
Packit 032894
  sqlite3_stmt *pp;
Packit 032894
Packit 032894
  sqlite_ps(const sqlite_ps&); // make uncopyable
Packit 032894
  sqlite_ps& operator=(const sqlite_ps &); // make unassignable
Packit 032894
Packit 032894
public:
Packit 032894
  sqlite_ps (sqlite3* d, const string& n, const string& s): db(d), nickname(n), sql(s) {
Packit 032894
    if (verbose > 4)
Packit 032894
      obatched(clog) << nickname << " prep " << sql << endl;
Packit 032894
    int rc = sqlite3_prepare_v2 (db, sql.c_str(), -1 /* to \0 */, & this->pp, NULL);
Packit 032894
    if (rc != SQLITE_OK)
Packit 032894
      throw sqlite_exception(rc, "prepare " + sql);
Packit 032894
  }
Packit 032894
Packit 032894
  sqlite_ps& reset()
Packit 032894
  {
Packit 032894
    sqlite3_reset(this->pp);
Packit 032894
    return *this;
Packit 032894
  }
Packit 032894
Packit 032894
  sqlite_ps& bind(int parameter, const string& str)
Packit 032894
  {
Packit 032894
    if (verbose > 4)
Packit 032894
      obatched(clog) << nickname << " bind " << parameter << "=" << str << endl;
Packit 032894
    int rc = sqlite3_bind_text (this->pp, parameter, str.c_str(), -1, SQLITE_TRANSIENT);
Packit 032894
    if (rc != SQLITE_OK)
Packit 032894
      throw sqlite_exception(rc, "sqlite3 bind");
Packit 032894
    return *this;
Packit 032894
  }
Packit 032894
Packit 032894
  sqlite_ps& bind(int parameter, int64_t value)
Packit 032894
  {
Packit 032894
    if (verbose > 4)
Packit 032894
      obatched(clog) << nickname << " bind " << parameter << "=" << value << endl;
Packit 032894
    int rc = sqlite3_bind_int64 (this->pp, parameter, value);
Packit 032894
    if (rc != SQLITE_OK)
Packit 032894
      throw sqlite_exception(rc, "sqlite3 bind");
Packit 032894
    return *this;
Packit 032894
  }
Packit 032894
Packit 032894
  sqlite_ps& bind(int parameter)
Packit 032894
  {
Packit 032894
    if (verbose > 4)
Packit 032894
      obatched(clog) << nickname << " bind " << parameter << "=" << "NULL" << endl;
Packit 032894
    int rc = sqlite3_bind_null (this->pp, parameter);
Packit 032894
    if (rc != SQLITE_OK)
Packit 032894
      throw sqlite_exception(rc, "sqlite3 bind");
Packit 032894
    return *this;
Packit 032894
  }
Packit 032894
Packit 032894
Packit 032894
  void step_ok_done() {
Packit 032894
    int rc = sqlite3_step (this->pp);
Packit 032894
    if (verbose > 4)
Packit 032894
      obatched(clog) << nickname << " step-ok-done(" << sqlite3_errstr(rc) << ") " << sql << endl;
Packit 032894
    if (rc != SQLITE_OK && rc != SQLITE_DONE && rc != SQLITE_ROW)
Packit 032894
      throw sqlite_exception(rc, "sqlite3 step");
Packit 032894
    (void) sqlite3_reset (this->pp);
Packit 032894
  }
Packit 032894
Packit 032894
Packit 032894
  int step() {
Packit 032894
    int rc = sqlite3_step (this->pp);
Packit 032894
    if (verbose > 4)
Packit 032894
      obatched(clog) << nickname << " step(" << sqlite3_errstr(rc) << ") " << sql << endl;
Packit 032894
    return rc;
Packit 032894
  }
Packit 032894
Packit 032894
Packit 032894
Packit 032894
  ~sqlite_ps () { sqlite3_finalize (this->pp); }
Packit 032894
  operator sqlite3_stmt* () { return this->pp; }
Packit 032894
};
Packit 032894
Packit 032894
Packit 032894
////////////////////////////////////////////////////////////////////////
Packit 032894
Packit 032894
// RAII style templated autocloser
Packit 032894
Packit 032894
template <class Payload, class Ignore>
Packit 032894
struct defer_dtor
Packit 032894
{
Packit 032894
public:
Packit 032894
  typedef Ignore (*dtor_fn) (Payload);
Packit 032894
Packit 032894
private:
Packit 032894
  Payload p;
Packit 032894
  dtor_fn fn;
Packit 032894
Packit 032894
public:
Packit 032894
  defer_dtor(Payload _p, dtor_fn _fn): p(_p), fn(_fn) {}
Packit 032894
  ~defer_dtor() { (void) (*fn)(p); }
Packit 032894
Packit 032894
private:
Packit 032894
  defer_dtor(const defer_dtor<Payload,Ignore>&); // make uncopyable
Packit 032894
  defer_dtor& operator=(const defer_dtor<Payload,Ignore> &); // make unassignable
Packit 032894
};
Packit 032894
Packit 032894
Packit 032894
Packit 032894
////////////////////////////////////////////////////////////////////////
Packit 032894
Packit 032894
Packit 032894
Packit 032894
Packit 032894
Packit 032894
static string
Packit 032894
conninfo (struct MHD_Connection * conn)
Packit 032894
{
Packit 032894
  char hostname[256]; // RFC1035
Packit 032894
  char servname[256];
Packit 032894
  int sts = -1;
Packit 032894
Packit 032894
  if (conn == 0)
Packit 032894
    return "internal";
Packit 032894
Packit 032894
  /* Look up client address data. */
Packit 032894
  const union MHD_ConnectionInfo *u = MHD_get_connection_info (conn,
Packit 032894
                                                               MHD_CONNECTION_INFO_CLIENT_ADDRESS);
Packit 032894
  struct sockaddr *so = u ? u->client_addr : 0;
Packit 032894
Packit 032894
  if (so && so->sa_family == AF_INET) {
Packit 032894
    sts = getnameinfo (so, sizeof (struct sockaddr_in), hostname, sizeof (hostname), servname,
Packit 032894
                       sizeof (servname), NI_NUMERICHOST | NI_NUMERICSERV);
Packit 032894
  } else if (so && so->sa_family == AF_INET6) {
Packit 032894
    sts = getnameinfo (so, sizeof (struct sockaddr_in6), hostname, sizeof (hostname),
Packit 032894
                       servname, sizeof (servname), NI_NUMERICHOST | NI_NUMERICSERV);
Packit 032894
  }
Packit 032894
  if (sts != 0) {
Packit 032894
    hostname[0] = servname[0] = '\0';
Packit 032894
  }
Packit 032894
Packit 032894
  return string(hostname) + string(":") + string(servname);
Packit 032894
}
Packit 032894
Packit 032894
Packit 032894
Packit 032894
////////////////////////////////////////////////////////////////////////
Packit 032894
Packit 032894
static void
Packit 032894
add_mhd_last_modified (struct MHD_Response *resp, time_t mtime)
Packit 032894
{
Packit 032894
  struct tm *now = gmtime (&mtime);
Packit 032894
  if (now != NULL)
Packit 032894
    {
Packit 032894
      char datebuf[80];
Packit 032894
      size_t rc = strftime (datebuf, sizeof (datebuf), "%a, %d %b %Y %T GMT", now);
Packit 032894
      if (rc > 0 && rc < sizeof (datebuf))
Packit 032894
        (void) MHD_add_response_header (resp, "Last-Modified", datebuf);
Packit 032894
    }
Packit 032894
Packit 032894
  (void) MHD_add_response_header (resp, "Cache-Control", "public");
Packit 032894
}
Packit 032894
Packit 032894
Packit 032894
Packit 032894
static struct MHD_Response*
Packit 032894
handle_buildid_f_match (int64_t b_mtime,
Packit 032894
                        const string& b_source0,
Packit 032894
                        int *result_fd)
Packit 032894
{
Packit 032894
  int fd = open(b_source0.c_str(), O_RDONLY);
Packit 032894
  if (fd < 0)
Packit 032894
    {
Packit 032894
      if (verbose)
Packit 032894
        obatched(clog) << "cannot open " << b_source0 << endl;
Packit 032894
      // if still missing, a periodic groom pass will delete this buildid record
Packit 032894
      return 0;
Packit 032894
    }
Packit 032894
Packit 032894
  // NB: use manual close(2) in error case instead of defer_dtor, because
Packit 032894
  // in the normal case, we want to hand the fd over to libmicrohttpd for
Packit 032894
  // file transfer.
Packit 032894
Packit 032894
  struct stat s;
Packit 032894
  int rc = fstat(fd, &s);
Packit 032894
  if (rc < 0)
Packit 032894
    {
Packit 032894
      if (verbose)
Packit 032894
        clog << "cannot fstat " << b_source0 << endl;
Packit 032894
      close(fd);
Packit 032894
      return 0;
Packit 032894
    }
Packit 032894
Packit 032894
  if ((int64_t) s.st_mtime != b_mtime)
Packit 032894
    {
Packit 032894
      if (verbose)
Packit 032894
        obatched(clog) << "mtime mismatch for " << b_source0 << endl;
Packit 032894
      close(fd);
Packit 032894
      return 0;
Packit 032894
    }
Packit 032894
Packit 032894
  inc_metric ("http_responses_total","result","file");
Packit 032894
  struct MHD_Response* r = MHD_create_response_from_fd ((uint64_t) s.st_size, fd);
Packit 032894
  if (r == 0)
Packit 032894
    {
Packit 032894
      if (verbose)
Packit 032894
        obatched(clog) << "cannot create fd-response for " << b_source0 << endl;
Packit 032894
      close(fd);
Packit 032894
    }
Packit 032894
  else
Packit 032894
    {
Packit 032894
      MHD_add_response_header (r, "Content-Type", "application/octet-stream");
Packit 032894
      add_mhd_last_modified (r, s.st_mtime);
Packit 032894
      if (verbose > 1)
Packit 032894
        obatched(clog) << "serving file " << b_source0 << endl;
Packit 032894
      /* libmicrohttpd will close it. */
Packit 032894
      if (result_fd)
Packit 032894
        *result_fd = fd;
Packit 032894
    }
Packit 032894
Packit 032894
  return r;
Packit 032894
}
Packit 032894
Packit 032894
Packit 032894
// quote all questionable characters of str for safe passage through a sh -c expansion.
Packit 032894
static string
Packit 032894
shell_escape(const string& str)
Packit 032894
{
Packit 032894
  string y;
Packit 032894
  for (auto&& x : str)
Packit 032894
    {
Packit 032894
      if (! isalnum(x) && x != '/')
Packit 032894
        y += "\\";
Packit 032894
      y += x;
Packit 032894
    }
Packit 032894
  return y;
Packit 032894
}
Packit 032894
Packit 032894
Packit 032894
static struct MHD_Response*
Packit 032894
handle_buildid_r_match (int64_t b_mtime,
Packit 032894
                        const string& b_source0,
Packit 032894
                        const string& b_source1,
Packit 032894
                        int *result_fd)
Packit 032894
{
Packit 032894
  struct stat fs;
Packit 032894
  int rc = stat (b_source0.c_str(), &fs);
Packit 032894
  if (rc != 0)
Packit 032894
    throw libc_exception (errno, string("stat ") + b_source0);
Packit 032894
Packit 032894
  if ((int64_t) fs.st_mtime != b_mtime)
Packit 032894
    {
Packit 032894
      if (verbose)
Packit 032894
        obatched(clog) << "mtime mismatch for " << b_source0 << endl;
Packit 032894
      return 0;
Packit 032894
    }
Packit 032894
Packit 032894
  string popen_cmd = string("rpm2cpio " + shell_escape(b_source0));
Packit 032894
  FILE* fp = popen (popen_cmd.c_str(), "r"); // "e" O_CLOEXEC?
Packit 032894
  if (fp == NULL)
Packit 032894
    throw libc_exception (errno, string("popen ") + popen_cmd);
Packit 032894
  defer_dtor<FILE*,int> fp_closer (fp, pclose);
Packit 032894
Packit 032894
  struct archive *a;
Packit 032894
  a = archive_read_new();
Packit 032894
  if (a == NULL)
Packit 032894
    throw archive_exception("cannot create archive reader");
Packit 032894
  defer_dtor<struct archive*,int> archive_closer (a, archive_read_free);
Packit 032894
Packit 032894
  rc = archive_read_support_format_cpio(a);
Packit 032894
  if (rc != ARCHIVE_OK)
Packit 032894
    throw archive_exception(a, "cannot select cpio format");
Packit 032894
  rc = archive_read_support_filter_all(a);
Packit 032894
  if (rc != ARCHIVE_OK)
Packit 032894
    throw archive_exception(a, "cannot select all filters");
Packit 032894
Packit 032894
  rc = archive_read_open_FILE (a, fp);
Packit 032894
  if (rc != ARCHIVE_OK)
Packit 032894
    throw archive_exception(a, "cannot open archive from rpm2cpio pipe");
Packit 032894
Packit 032894
  while(1) // parse cpio archive entries
Packit 032894
    {
Packit 032894
      struct archive_entry *e;
Packit 032894
      rc = archive_read_next_header (a, &e);
Packit 032894
      if (rc != ARCHIVE_OK)
Packit 032894
        break;
Packit 032894
Packit 032894
      if (! S_ISREG(archive_entry_mode (e))) // skip non-files completely
Packit 032894
        continue;
Packit 032894
Packit 032894
      string fn = archive_entry_pathname (e);
Packit 032894
      if (fn != string(".")+b_source1)
Packit 032894
        continue;
Packit 032894
Packit 032894
      // extract this file to a temporary file
Packit 032894
      char tmppath[PATH_MAX] = "/tmp/debuginfod.XXXXXX"; // XXX: $TMP_DIR etc.
Packit 032894
      int fd = mkstemp (tmppath);
Packit 032894
      if (fd < 0)
Packit 032894
        throw libc_exception (errno, "cannot create temporary file");
Packit 032894
      unlink (tmppath); // unlink now so OS will release the file as soon as we close the fd
Packit 032894
Packit 032894
      rc = archive_read_data_into_fd (a, fd);
Packit 032894
      if (rc != ARCHIVE_OK)
Packit 032894
        {
Packit 032894
          close (fd);
Packit 032894
          throw archive_exception(a, "cannot extract file");
Packit 032894
        }
Packit 032894
Packit 032894
      inc_metric ("http_responses_total","result","rpm");
Packit 032894
      struct MHD_Response* r = MHD_create_response_from_fd (archive_entry_size(e), fd);
Packit 032894
      if (r == 0)
Packit 032894
        {
Packit 032894
          if (verbose)
Packit 032894
            obatched(clog) << "cannot create fd-response for " << b_source0 << endl;
Packit 032894
          close(fd);
Packit 032894
          break; // assume no chance of better luck around another iteration
Packit 032894
        }
Packit 032894
      else
Packit 032894
        {
Packit 032894
          MHD_add_response_header (r, "Content-Type", "application/octet-stream");
Packit 032894
          add_mhd_last_modified (r, archive_entry_mtime(e));
Packit 032894
          if (verbose > 1)
Packit 032894
            obatched(clog) << "serving rpm " << b_source0 << " file " << b_source1 << endl;
Packit 032894
          /* libmicrohttpd will close it. */
Packit 032894
          if (result_fd)
Packit 032894
            *result_fd = fd;
Packit 032894
          return r;
Packit 032894
        }
Packit 032894
    }
Packit 032894
Packit 032894
  // XXX: rpm/file not found: delete this R entry?
Packit 032894
  return 0;
Packit 032894
}
Packit 032894
Packit 032894
Packit 032894
static struct MHD_Response*
Packit 032894
handle_buildid_match (int64_t b_mtime,
Packit 032894
                      const string& b_stype,
Packit 032894
                      const string& b_source0,
Packit 032894
                      const string& b_source1,
Packit 032894
                      int *result_fd)
Packit 032894
{
Packit 032894
  if (b_stype == "F")
Packit 032894
    return handle_buildid_f_match(b_mtime, b_source0, result_fd);
Packit 032894
  else if (b_stype == "R")
Packit 032894
    return handle_buildid_r_match(b_mtime, b_source0, b_source1, result_fd);
Packit 032894
  else
Packit 032894
    return 0;
Packit 032894
}
Packit 032894
Packit 032894
Packit 032894
static int
Packit 032894
debuginfod_find_progress (debuginfod_client *, long a, long b)
Packit 032894
{
Packit 032894
  if (verbose > 4)
Packit 032894
    obatched(clog) << "federated debuginfod progress=" << a << "/" << b << endl;
Packit 032894
Packit 032894
  return interrupted;
Packit 032894
}
Packit 032894
Packit 032894
Packit 032894
static struct MHD_Response* handle_buildid (const string& buildid /* unsafe */,
Packit 032894
                                            const string& artifacttype /* unsafe */,
Packit 032894
                                            const string& suffix /* unsafe */,
Packit 032894
                                            int *result_fd
Packit 032894
                                            )
Packit 032894
{
Packit 032894
  // validate artifacttype
Packit 032894
  string atype_code;
Packit 032894
  if (artifacttype == "debuginfo") atype_code = "D";
Packit 032894
  else if (artifacttype == "executable") atype_code = "E";
Packit 032894
  else if (artifacttype == "source") atype_code = "S";
Packit 032894
  else throw reportable_exception("invalid artifacttype");
Packit 032894
Packit 032894
  if (atype_code == "S" && suffix == "")
Packit 032894
     throw reportable_exception("invalid source suffix");
Packit 032894
Packit 032894
  // validate buildid
Packit 032894
  if ((buildid.size() < 2) || // not empty
Packit 032894
      (buildid.size() % 2) || // even number
Packit 032894
      (buildid.find_first_not_of("0123456789abcdef") != string::npos)) // pure tasty lowercase hex
Packit 032894
    throw reportable_exception("invalid buildid");
Packit 032894
Packit 032894
  if (verbose > 1)
Packit 032894
    obatched(clog) << "searching for buildid=" << buildid << " artifacttype=" << artifacttype
Packit 032894
         << " suffix=" << suffix << endl;
Packit 032894
Packit 032894
  sqlite_ps *pp = 0;
Packit 032894
Packit 032894
  if (atype_code == "D")
Packit 032894
    {
Packit 032894
      pp = new sqlite_ps (db, "mhd-query-d",
Packit 032894
                          "select mtime, sourcetype, source0, source1 from " BUILDIDS "_query_d where buildid = ? "
Packit 032894
                          "order by mtime desc");
Packit 032894
      pp->reset();
Packit 032894
      pp->bind(1, buildid);
Packit 032894
    }
Packit 032894
  else if (atype_code == "E")
Packit 032894
    {
Packit 032894
      pp = new sqlite_ps (db, "mhd-query-e",
Packit 032894
                          "select mtime, sourcetype, source0, source1 from " BUILDIDS "_query_e where buildid = ? "
Packit 032894
                          "order by mtime desc");
Packit 032894
      pp->reset();
Packit 032894
      pp->bind(1, buildid);
Packit 032894
    }
Packit 032894
  else if (atype_code == "S")
Packit 032894
    {
Packit 032894
      pp = new sqlite_ps (db, "mhd-query-s",
Packit 032894
                          "select mtime, sourcetype, source0, source1 from " BUILDIDS "_query_s where buildid = ? and artifactsrc = ? "
Packit 032894
                          "order by sharedprefix(source0,source0ref) desc, mtime desc");
Packit 032894
      pp->reset();
Packit 032894
      pp->bind(1, buildid);
Packit 032894
      pp->bind(2, suffix);
Packit 032894
    }
Packit 032894
  unique_ptr<sqlite_ps> ps_closer(pp); // release pp if exception or return
Packit 032894
Packit 032894
  // consume all the rows
Packit 032894
  while (1)
Packit 032894
    {
Packit 032894
      int rc = pp->step();
Packit 032894
      if (rc == SQLITE_DONE) break;
Packit 032894
      if (rc != SQLITE_ROW)
Packit 032894
        throw sqlite_exception(rc, "step");
Packit 032894
Packit 032894
      int64_t b_mtime = sqlite3_column_int64 (*pp, 0);
Packit 032894
      string b_stype = string((const char*) sqlite3_column_text (*pp, 1) ?: ""); /* by DDL may not be NULL */
Packit 032894
      string b_source0 = string((const char*) sqlite3_column_text (*pp, 2) ?: ""); /* may be NULL */
Packit 032894
      string b_source1 = string((const char*) sqlite3_column_text (*pp, 3) ?: ""); /* may be NULL */
Packit 032894
Packit 032894
      if (verbose > 1)
Packit 032894
        obatched(clog) << "found mtime=" << b_mtime << " stype=" << b_stype
Packit 032894
             << " source0=" << b_source0 << " source1=" << b_source1 << endl;
Packit 032894
Packit 032894
      // Try accessing the located match.
Packit 032894
      // XXX: in case of multiple matches, attempt them in parallel?
Packit 032894
      auto r = handle_buildid_match (b_mtime, b_stype, b_source0, b_source1, result_fd);
Packit 032894
      if (r)
Packit 032894
        return r;
Packit 032894
    }
Packit 032894
Packit 032894
  // We couldn't find it in the database.  Last ditch effort
Packit 032894
  // is to defer to other debuginfo servers.
Packit 032894
Packit 032894
  int fd = -1;
Packit 032894
  debuginfod_client *client = debuginfod_begin ();
Packit 032894
  if (client != NULL)
Packit 032894
    {
Packit 032894
      debuginfod_set_progressfn (client, & debuginfod_find_progress);
Packit 032894
Packit 032894
      if (artifacttype == "debuginfo")
Packit 032894
	fd = debuginfod_find_debuginfo (client,
Packit 032894
					(const unsigned char*) buildid.c_str(),
Packit 032894
					0, NULL);
Packit 032894
      else if (artifacttype == "executable")
Packit 032894
	fd = debuginfod_find_executable (client,
Packit 032894
					 (const unsigned char*) buildid.c_str(),
Packit 032894
					 0, NULL);
Packit 032894
      else if (artifacttype == "source")
Packit 032894
	fd = debuginfod_find_source (client,
Packit 032894
				     (const unsigned char*) buildid.c_str(),
Packit 032894
				     0, suffix.c_str(), NULL);
Packit 032894
    }
Packit 032894
  else
Packit 032894
    fd = -errno; /* Set by debuginfod_begin.  */
Packit 032894
  debuginfod_end (client);
Packit 032894
Packit 032894
  if (fd >= 0)
Packit 032894
    {
Packit 032894
      inc_metric ("http_responses_total","result","upstream");
Packit 032894
      struct stat s;
Packit 032894
      int rc = fstat (fd, &s);
Packit 032894
      if (rc == 0)
Packit 032894
        {
Packit 032894
          auto r = MHD_create_response_from_fd ((uint64_t) s.st_size, fd);
Packit 032894
          if (r)
Packit 032894
            {
Packit 032894
              MHD_add_response_header (r, "Content-Type", "application/octet-stream");
Packit 032894
              add_mhd_last_modified (r, s.st_mtime);
Packit 032894
              if (verbose > 1)
Packit 032894
                obatched(clog) << "serving file from upstream debuginfod/cache" << endl;
Packit 032894
              if (result_fd)
Packit 032894
                *result_fd = fd;
Packit 032894
              return r; // NB: don't close fd; libmicrohttpd will
Packit 032894
            }
Packit 032894
        }
Packit 032894
      close (fd);
Packit 032894
    }
Packit 032894
  else if (fd != -ENOSYS) // no DEBUGINFOD_URLS configured
Packit 032894
    throw libc_exception(-fd, "upstream debuginfod query failed");
Packit 032894
Packit 032894
  throw reportable_exception(MHD_HTTP_NOT_FOUND, "not found");
Packit 032894
}
Packit 032894
Packit 032894
Packit 032894
////////////////////////////////////////////////////////////////////////
Packit 032894
Packit 032894
static map<string,int64_t> metrics; // arbitrary data for /metrics query
Packit 032894
// NB: store int64_t since all our metrics are integers; prometheus accepts double
Packit 032894
static mutex metrics_lock;
Packit 032894
Packit 032894
// utility function for assembling prometheus-compatible
Packit 032894
// name="escaped-value" strings
Packit 032894
// https://prometheus.io/docs/instrumenting/exposition_formats/
Packit 032894
static string
Packit 032894
metric_label(const string& name, const string& value)
Packit 032894
{
Packit 032894
  string x = name + "=\"";
Packit 032894
  for (auto&& c : value)
Packit 032894
    switch(c)
Packit 032894
      {
Packit 032894
      case '\\': x += "\\\\"; break;
Packit 032894
      case '\"': x += "\\\""; break;
Packit 032894
      case '\n': x += "\\n"; break;
Packit 032894
      default: x += c; break;
Packit 032894
      }
Packit 032894
  x += "\"";
Packit 032894
  return x;
Packit 032894
}
Packit 032894
Packit 032894
Packit 032894
// add prometheus-format metric name + label tuple (if any) + value
Packit 032894
Packit 032894
static void
Packit 032894
set_metric(const string& metric, int64_t value)
Packit 032894
{
Packit 032894
  unique_lock<mutex> lock(metrics_lock);
Packit 032894
  metrics[metric] = value;
Packit 032894
}
Packit 032894
#if 0 /* unused */
Packit 032894
static void
Packit 032894
inc_metric(const string& metric)
Packit 032894
{
Packit 032894
  unique_lock<mutex> lock(metrics_lock);
Packit 032894
  metrics[metric] ++;
Packit 032894
}
Packit 032894
#endif
Packit 032894
static void
Packit 032894
set_metric(const string& metric,
Packit 032894
           const string& lname, const string& lvalue,
Packit 032894
           int64_t value)
Packit 032894
{
Packit 032894
  string key = (metric + "{" + metric_label(lname, lvalue) + "}");
Packit 032894
  unique_lock<mutex> lock(metrics_lock);
Packit 032894
  metrics[key] = value;
Packit 032894
}
Packit 032894
Packit 032894
static void
Packit 032894
inc_metric(const string& metric,
Packit 032894
           const string& lname, const string& lvalue)
Packit 032894
{
Packit 032894
  string key = (metric + "{" + metric_label(lname, lvalue) + "}");
Packit 032894
  unique_lock<mutex> lock(metrics_lock);
Packit 032894
  metrics[key] ++;
Packit 032894
}
Packit 032894
static void
Packit 032894
add_metric(const string& metric,
Packit 032894
           const string& lname, const string& lvalue,
Packit 032894
           int64_t value)
Packit 032894
{
Packit 032894
  string key = (metric + "{" + metric_label(lname, lvalue) + "}");
Packit 032894
  unique_lock<mutex> lock(metrics_lock);
Packit 032894
  metrics[key] += value;
Packit 032894
}
Packit 032894
Packit 032894
Packit 032894
// and more for higher arity labels if needed
Packit 032894
Packit 032894
Packit 032894
static struct MHD_Response*
Packit 032894
handle_metrics ()
Packit 032894
{
Packit 032894
  stringstream o;
Packit 032894
  {
Packit 032894
    unique_lock<mutex> lock(metrics_lock);
Packit 032894
    for (auto&& i : metrics)
Packit 032894
      o << i.first << " " << i.second << endl;
Packit 032894
  }
Packit 032894
  const string& os = o.str();
Packit 032894
  MHD_Response* r = MHD_create_response_from_buffer (os.size(),
Packit 032894
                                                     (void*) os.c_str(),
Packit 032894
                                                     MHD_RESPMEM_MUST_COPY);
Packit 032894
  MHD_add_response_header (r, "Content-Type", "text/plain");
Packit 032894
  return r;
Packit 032894
}
Packit 032894
Packit 032894
Packit 032894
////////////////////////////////////////////////////////////////////////
Packit 032894
Packit 032894
Packit 032894
/* libmicrohttpd callback */
Packit 032894
static int
Packit 032894
handler_cb (void * /*cls*/,
Packit 032894
            struct MHD_Connection *connection,
Packit 032894
            const char *url,
Packit 032894
            const char *method,
Packit 032894
            const char * /*version*/,
Packit 032894
            const char * /*upload_data*/,
Packit 032894
            size_t * /*upload_data_size*/,
Packit 032894
            void ** /*con_cls*/)
Packit 032894
{
Packit 032894
  struct MHD_Response *r = NULL;
Packit 032894
  string url_copy = url;
Packit 032894
Packit 032894
  if (verbose)
Packit 032894
    obatched(clog) << conninfo(connection) << " " << method << " " << url << endl;
Packit 032894
Packit 032894
  try
Packit 032894
    {
Packit 032894
      if (string(method) != "GET")
Packit 032894
        throw reportable_exception(400, "we support GET only");
Packit 032894
Packit 032894
      /* Start decoding the URL. */
Packit 032894
      size_t slash1 = url_copy.find('/', 1);
Packit 032894
      string url1 = url_copy.substr(0, slash1); // ok even if slash1 not found
Packit 032894
Packit 032894
      if (slash1 != string::npos && url1 == "/buildid")
Packit 032894
        {
Packit 032894
          size_t slash2 = url_copy.find('/', slash1+1);
Packit 032894
          if (slash2 == string::npos)
Packit 032894
            throw reportable_exception("/buildid/ webapi error, need buildid");
Packit 032894
Packit 032894
          string buildid = url_copy.substr(slash1+1, slash2-slash1-1);
Packit 032894
Packit 032894
          size_t slash3 = url_copy.find('/', slash2+1);
Packit 032894
          string artifacttype, suffix;
Packit 032894
          if (slash3 == string::npos)
Packit 032894
            {
Packit 032894
              artifacttype = url_copy.substr(slash2+1);
Packit 032894
              suffix = "";
Packit 032894
            }
Packit 032894
          else
Packit 032894
            {
Packit 032894
              artifacttype = url_copy.substr(slash2+1, slash3-slash2-1);
Packit 032894
              suffix = url_copy.substr(slash3); // include the slash in the suffix
Packit 032894
            }
Packit 032894
Packit 032894
          inc_metric("http_requests_total", "type", artifacttype);
Packit 032894
          r = handle_buildid(buildid, artifacttype, suffix, 0); // NB: don't care about result-fd
Packit 032894
        }
Packit 032894
      else if (url1 == "/metrics")
Packit 032894
        {
Packit 032894
          inc_metric("http_requests_total", "type", "metrics");
Packit 032894
          r = handle_metrics();
Packit 032894
        }
Packit 032894
      else
Packit 032894
        throw reportable_exception("webapi error, unrecognized /operation");
Packit 032894
Packit 032894
      if (r == 0)
Packit 032894
        throw reportable_exception("internal error, missing response");
Packit 032894
Packit 032894
      int rc = MHD_queue_response (connection, MHD_HTTP_OK, r);
Packit 032894
      MHD_destroy_response (r);
Packit 032894
      return rc;
Packit 032894
    }
Packit 032894
  catch (const reportable_exception& e)
Packit 032894
    {
Packit 032894
      inc_metric("http_responses_total","result","error");
Packit 032894
      e.report(clog);
Packit 032894
      return e.mhd_send_response (connection);
Packit 032894
    }
Packit 032894
}
Packit 032894
Packit 032894
Packit 032894
////////////////////////////////////////////////////////////////////////
Packit 032894
// borrowed originally from src/nm.c get_local_names()
Packit 032894
Packit 032894
static void
Packit 032894
dwarf_extract_source_paths (Elf *elf, set<string>& debug_sourcefiles)
Packit 032894
  noexcept // no exceptions - so we can simplify the altdbg resource release at end
Packit 032894
{
Packit 032894
  Dwarf* dbg = dwarf_begin_elf (elf, DWARF_C_READ, NULL);
Packit 032894
  if (dbg == NULL)
Packit 032894
    return;
Packit 032894
Packit 032894
  Dwarf* altdbg = NULL;
Packit 032894
  int    altdbg_fd = -1;
Packit 032894
Packit 032894
  // DWZ handling: if we have an unsatisfied debug-alt-link, add an
Packit 032894
  // empty string into the outgoing sourcefiles set, so the caller
Packit 032894
  // should know that our data is incomplete.
Packit 032894
  const char *alt_name_p;
Packit 032894
  const void *alt_build_id; // elfutils-owned memory
Packit 032894
  ssize_t sz = dwelf_dwarf_gnu_debugaltlink (dbg, &alt_name_p, &alt_build_id);
Packit 032894
  if (sz > 0) // got one!
Packit 032894
    {
Packit 032894
      string buildid;
Packit 032894
      unsigned char* build_id_bytes = (unsigned char*) alt_build_id;
Packit 032894
      for (ssize_t idx=0; idx
Packit 032894
        {
Packit 032894
          buildid += "0123456789abcdef"[build_id_bytes[idx] >> 4];
Packit 032894
          buildid += "0123456789abcdef"[build_id_bytes[idx] & 0xf];
Packit 032894
        }
Packit 032894
Packit 032894
      if (verbose > 3)
Packit 032894
        obatched(clog) << "Need altdebug buildid=" << buildid << endl;
Packit 032894
Packit 032894
      // but is it unsatisfied the normal elfutils ways?
Packit 032894
      Dwarf* alt = dwarf_getalt (dbg);
Packit 032894
      if (alt == NULL)
Packit 032894
        {
Packit 032894
          // Yup, unsatisfied the normal way.  Maybe we can satisfy it
Packit 032894
          // from our own debuginfod database.
Packit 032894
          int alt_fd;
Packit 032894
          struct MHD_Response *r = 0;
Packit 032894
          try
Packit 032894
            {
Packit 032894
              r = handle_buildid (buildid, "debuginfo", "", &alt_fd);
Packit 032894
            }
Packit 032894
          catch (const reportable_exception& e)
Packit 032894
            {
Packit 032894
              // swallow exceptions
Packit 032894
            }
Packit 032894
Packit 032894
          // NB: this is not actually recursive!  This invokes the web-query
Packit 032894
          // path, which cannot get back into the scan code paths.
Packit 032894
          if (r)
Packit 032894
            {
Packit 032894
              // Found it!
Packit 032894
              altdbg_fd = dup(alt_fd); // ok if this fails, downstream failures ok
Packit 032894
              alt = altdbg = dwarf_begin (altdbg_fd, DWARF_C_READ);
Packit 032894
              // NB: must close this dwarf and this fd at the bottom of the function!
Packit 032894
              MHD_destroy_response (r); // will close alt_fd
Packit 032894
              if (alt)
Packit 032894
                dwarf_setalt (dbg, alt);
Packit 032894
            }
Packit 032894
        }
Packit 032894
      else
Packit 032894
        {
Packit 032894
          // NB: dwarf_setalt(alt) inappropriate - already done!
Packit 032894
          // NB: altdbg will stay 0 so nothing tries to redundantly dealloc.
Packit 032894
        }
Packit 032894
Packit 032894
      if (alt)
Packit 032894
        {
Packit 032894
          if (verbose > 3)
Packit 032894
            obatched(clog) << "Resolved altdebug buildid=" << buildid << endl;
Packit 032894
        }
Packit 032894
      else // (alt == NULL) - signal possible presence of poor debuginfo
Packit 032894
        {
Packit 032894
          debug_sourcefiles.insert("");
Packit 032894
          if (verbose > 3)
Packit 032894
            obatched(clog) << "Unresolved altdebug buildid=" << buildid << endl;
Packit 032894
        }
Packit 032894
    }
Packit 032894
Packit 032894
  Dwarf_Off offset = 0;
Packit 032894
  Dwarf_Off old_offset;
Packit 032894
  size_t hsize;
Packit 032894
Packit 032894
  while (dwarf_nextcu (dbg, old_offset = offset, &offset, &hsize, NULL, NULL, NULL) == 0)
Packit 032894
    {
Packit 032894
      Dwarf_Die cudie_mem;
Packit 032894
      Dwarf_Die *cudie = dwarf_offdie (dbg, old_offset + hsize, &cudie_mem);
Packit 032894
Packit 032894
      if (cudie == NULL)
Packit 032894
        continue;
Packit 032894
      if (dwarf_tag (cudie) != DW_TAG_compile_unit)
Packit 032894
        continue;
Packit 032894
Packit 032894
      const char *cuname = dwarf_diename(cudie) ?: "unknown";
Packit 032894
Packit 032894
      Dwarf_Files *files;
Packit 032894
      size_t nfiles;
Packit 032894
      if (dwarf_getsrcfiles (cudie, &files, &nfiles) != 0)
Packit 032894
        continue;
Packit 032894
Packit 032894
      // extract DW_AT_comp_dir to resolve relative file names
Packit 032894
      const char *comp_dir = "";
Packit 032894
      const char *const *dirs;
Packit 032894
      size_t ndirs;
Packit 032894
      if (dwarf_getsrcdirs (files, &dirs, &ndirs) == 0 &&
Packit 032894
          dirs[0] != NULL)
Packit 032894
        comp_dir = dirs[0];
Packit 032894
      if (comp_dir == NULL)
Packit 032894
        comp_dir = "";
Packit 032894
Packit 032894
      if (verbose > 3)
Packit 032894
        obatched(clog) << "searching for sources for cu=" << cuname << " comp_dir=" << comp_dir
Packit 032894
                       << " #files=" << nfiles << " #dirs=" << ndirs << endl;
Packit 032894
Packit 032894
      if (comp_dir[0] == '\0' && cuname[0] != '/')
Packit 032894
        {
Packit 032894
          // This is a common symptom for dwz-compressed debug files,
Packit 032894
          // where the altdebug file cannot be resolved.
Packit 032894
          if (verbose > 3)
Packit 032894
            obatched(clog) << "skipping cu=" << cuname << " due to empty comp_dir" << endl;
Packit 032894
          continue;
Packit 032894
        }
Packit 032894
Packit 032894
      for (size_t f = 1; f < nfiles; f++)
Packit 032894
        {
Packit 032894
          const char *hat = dwarf_filesrc (files, f, NULL, NULL);
Packit 032894
          if (hat == NULL)
Packit 032894
            continue;
Packit 032894
Packit 032894
          if (string(hat) == "<built-in>") // gcc intrinsics, don't bother record
Packit 032894
            continue;
Packit 032894
Packit 032894
          string waldo;
Packit 032894
          if (hat[0] == '/') // absolute
Packit 032894
            waldo = (string (hat));
Packit 032894
          else if (comp_dir[0] != '\0') // comp_dir relative
Packit 032894
            waldo = (string (comp_dir) + string("/") + string (hat));
Packit 032894
          else
Packit 032894
           {
Packit 032894
             obatched(clog) << "skipping hat=" << hat << " due to empty comp_dir" << endl;
Packit 032894
             continue;
Packit 032894
           }
Packit 032894
Packit 032894
          // NB: this is the 'waldo' that a dbginfo client will have
Packit 032894
          // to supply for us to give them the file The comp_dir
Packit 032894
          // prefixing is a definite complication.  Otherwise we'd
Packit 032894
          // have to return a setof comp_dirs (one per CU!) with
Packit 032894
          // corresponding filesrc[] names, instead of one absolute
Packit 032894
          // resoved set.  Maybe we'll have to do that anyway.  XXX
Packit 032894
Packit 032894
          if (verbose > 4)
Packit 032894
            obatched(clog) << waldo
Packit 032894
                           << (debug_sourcefiles.find(waldo)==debug_sourcefiles.end() ? " new" : " dup") <<  endl;
Packit 032894
Packit 032894
          debug_sourcefiles.insert (waldo);
Packit 032894
        }
Packit 032894
    }
Packit 032894
Packit 032894
  dwarf_end(dbg);
Packit 032894
  if (altdbg)
Packit 032894
    dwarf_end(altdbg);
Packit 032894
  if (altdbg_fd >= 0)
Packit 032894
    close(altdbg_fd);
Packit 032894
}
Packit 032894
Packit 032894
Packit 032894
Packit 032894
static void
Packit 032894
elf_classify (int fd, bool &executable_p, bool &debuginfo_p, string &buildid, set<string>& debug_sourcefiles)
Packit 032894
{
Packit 032894
  Elf *elf = elf_begin (fd, ELF_C_READ_MMAP_PRIVATE, NULL);
Packit 032894
  if (elf == NULL)
Packit 032894
    return;
Packit 032894
Packit 032894
  try // catch our types of errors and clean up the Elf* object
Packit 032894
    {
Packit 032894
      if (elf_kind (elf) != ELF_K_ELF)
Packit 032894
        {
Packit 032894
          elf_end (elf);
Packit 032894
          return;
Packit 032894
        }
Packit 032894
Packit 032894
      GElf_Ehdr ehdr_storage;
Packit 032894
      GElf_Ehdr *ehdr = gelf_getehdr (elf, &ehdr_storage);
Packit 032894
      if (ehdr == NULL)
Packit 032894
        {
Packit 032894
          elf_end (elf);
Packit 032894
          return;
Packit 032894
        }
Packit 032894
      auto elf_type = ehdr->e_type;
Packit 032894
Packit 032894
      const void *build_id; // elfutils-owned memory
Packit 032894
      ssize_t sz = dwelf_elf_gnu_build_id (elf, & build_id);
Packit 032894
      if (sz <= 0)
Packit 032894
        {
Packit 032894
          // It's not a diagnostic-worthy error for an elf file to lack build-id.
Packit 032894
          // It might just be very old.
Packit 032894
          elf_end (elf);
Packit 032894
          return;
Packit 032894
        }
Packit 032894
Packit 032894
      // build_id is a raw byte array; convert to hexadecimal *lowercase*
Packit 032894
      unsigned char* build_id_bytes = (unsigned char*) build_id;
Packit 032894
      for (ssize_t idx=0; idx
Packit 032894
        {
Packit 032894
          buildid += "0123456789abcdef"[build_id_bytes[idx] >> 4];
Packit 032894
          buildid += "0123456789abcdef"[build_id_bytes[idx] & 0xf];
Packit 032894
        }
Packit 032894
Packit 032894
      // now decide whether it's an executable - namely, any allocatable section has
Packit 032894
      // PROGBITS;
Packit 032894
      if (elf_type == ET_EXEC || elf_type == ET_DYN)
Packit 032894
        {
Packit 032894
          size_t shnum;
Packit 032894
          int rc = elf_getshdrnum (elf, &shnum);
Packit 032894
          if (rc < 0)
Packit 032894
            throw elfutils_exception(rc, "getshdrnum");
Packit 032894
Packit 032894
          executable_p = false;
Packit 032894
          for (size_t sc = 0; sc < shnum; sc++)
Packit 032894
            {
Packit 032894
              Elf_Scn *scn = elf_getscn (elf, sc);
Packit 032894
              if (scn == NULL)
Packit 032894
                continue;
Packit 032894
Packit 032894
              GElf_Shdr shdr_mem;
Packit 032894
              GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
Packit 032894
              if (shdr == NULL)
Packit 032894
                continue;
Packit 032894
Packit 032894
              // allocated (loadable / vm-addr-assigned) section with available content?
Packit 032894
              if ((shdr->sh_type == SHT_PROGBITS) && (shdr->sh_flags & SHF_ALLOC))
Packit 032894
                {
Packit 032894
                  if (verbose > 4)
Packit 032894
                    obatched(clog) << "executable due to SHF_ALLOC SHT_PROGBITS sc=" << sc << endl;
Packit 032894
                  executable_p = true;
Packit 032894
                  break; // no need to keep looking for others
Packit 032894
                }
Packit 032894
            } // iterate over sections
Packit 032894
        } // executable_p classification
Packit 032894
Packit 032894
      // now decide whether it's a debuginfo - namely, if it has any .debug* or .zdebug* sections
Packit 032894
      // logic mostly stolen from fweimer@redhat.com's elfclassify drafts
Packit 032894
      size_t shstrndx;
Packit 032894
      int rc = elf_getshdrstrndx (elf, &shstrndx);
Packit 032894
      if (rc < 0)
Packit 032894
        throw elfutils_exception(rc, "getshdrstrndx");
Packit 032894
Packit 032894
      Elf_Scn *scn = NULL;
Packit 032894
      while (true)
Packit 032894
        {
Packit 032894
          scn = elf_nextscn (elf, scn);
Packit 032894
          if (scn == NULL)
Packit 032894
            break;
Packit 032894
          GElf_Shdr shdr_storage;
Packit 032894
          GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_storage);
Packit 032894
          if (shdr == NULL)
Packit 032894
            break;
Packit 032894
          const char *section_name = elf_strptr (elf, shstrndx, shdr->sh_name);
Packit 032894
          if (section_name == NULL)
Packit 032894
            break;
Packit 032894
          if (strncmp(section_name, ".debug_line", 11) == 0 ||
Packit 032894
              strncmp(section_name, ".zdebug_line", 12) == 0)
Packit 032894
            {
Packit 032894
              debuginfo_p = true;
Packit 032894
              dwarf_extract_source_paths (elf, debug_sourcefiles);
Packit 032894
              break; // expecting only one .*debug_line, so no need to look for others
Packit 032894
            }
Packit 032894
          else if (strncmp(section_name, ".debug_", 7) == 0 ||
Packit 032894
                   strncmp(section_name, ".zdebug_", 8) == 0)
Packit 032894
            {
Packit 032894
              debuginfo_p = true;
Packit 032894
              // NB: don't break; need to parse .debug_line for sources
Packit 032894
            }
Packit 032894
        }
Packit 032894
    }
Packit 032894
  catch (const reportable_exception& e)
Packit 032894
    {
Packit 032894
      e.report(clog);
Packit 032894
    }
Packit 032894
  elf_end (elf);
Packit 032894
}
Packit 032894
Packit 032894
Packit 032894
static semaphore* scan_concurrency_sem = 0; // used to implement -c load limiting
Packit 032894
Packit 032894
Packit 032894
static void
Packit 032894
scan_source_file_path (const string& dir)
Packit 032894
{
Packit 032894
  obatched(clog) << "fts/file traversing " << dir << endl;
Packit 032894
Packit 032894
  struct timeval tv_start, tv_end;
Packit 032894
  gettimeofday (&tv_start, NULL);
Packit 032894
Packit 032894
  sqlite_ps ps_upsert_buildids (db, "file-buildids-intern", "insert or ignore into " BUILDIDS "_buildids VALUES (NULL, ?);");
Packit 032894
  sqlite_ps ps_upsert_files (db, "file-files-intern", "insert or ignore into " BUILDIDS "_files VALUES (NULL, ?);");
Packit 032894
  sqlite_ps ps_upsert_de (db, "file-de-upsert",
Packit 032894
                          "insert or ignore into " BUILDIDS "_f_de "
Packit 032894
                          "(buildid, debuginfo_p, executable_p, file, mtime) "
Packit 032894
                          "values ((select id from " BUILDIDS "_buildids where hex = ?),"
Packit 032894
                          "        ?,?,"
Packit 032894
                          "        (select id from " BUILDIDS "_files where name = ?), ?);");
Packit 032894
  sqlite_ps ps_upsert_s (db, "file-s-upsert",
Packit 032894
                         "insert or ignore into " BUILDIDS "_f_s "
Packit 032894
                         "(buildid, artifactsrc, file, mtime) "
Packit 032894
                         "values ((select id from " BUILDIDS "_buildids where hex = ?),"
Packit 032894
                         "        (select id from " BUILDIDS "_files where name = ?),"
Packit 032894
                         "        (select id from " BUILDIDS "_files where name = ?),"
Packit 032894
                         "        ?);");
Packit 032894
  sqlite_ps ps_query (db, "file-negativehit-find",
Packit 032894
                      "select 1 from " BUILDIDS "_file_mtime_scanned where sourcetype = 'F' and file = (select id from " BUILDIDS "_files where name = ?) and mtime = ?;");
Packit 032894
  sqlite_ps ps_scan_done (db, "file-scanned",
Packit 032894
                          "insert or ignore into " BUILDIDS "_file_mtime_scanned (sourcetype, file, mtime, size)"
Packit 032894
                          "values ('F', (select id from " BUILDIDS "_files where name = ?), ?, ?);");
Packit 032894
Packit 032894
Packit 032894
  char * const dirs[] = { (char*) dir.c_str(), NULL };
Packit 032894
Packit 032894
  unsigned fts_scanned=0, fts_regex=0, fts_cached=0, fts_debuginfo=0, fts_executable=0, fts_sourcefiles=0;
Packit 032894
Packit 032894
  FTS *fts = fts_open (dirs,
Packit 032894
                       (traverse_logical ? FTS_LOGICAL : FTS_PHYSICAL|FTS_XDEV)
Packit 032894
                       | FTS_NOCHDIR /* multithreaded */,
Packit 032894
                       NULL);
Packit 032894
  if (fts == NULL)
Packit 032894
    {
Packit 032894
      obatched(cerr) << "cannot fts_open " << dir << endl;
Packit 032894
      return;
Packit 032894
    }
Packit 032894
Packit 032894
  FTSENT *f;
Packit 032894
  while ((f = fts_read (fts)) != NULL)
Packit 032894
    {
Packit 032894
      semaphore_borrower handle_one_file (scan_concurrency_sem);
Packit 032894
Packit 032894
      fts_scanned ++;
Packit 032894
      if (interrupted)
Packit 032894
        break;
Packit 032894
Packit 032894
      if (verbose > 2)
Packit 032894
        obatched(clog) << "fts/file traversing " << f->fts_path << endl;
Packit 032894
Packit 032894
      try
Packit 032894
        {
Packit 032894
          /* Found a file.  Convert it to an absolute path, so
Packit 032894
             the buildid database does not have relative path
Packit 032894
             names that are unresolvable from a subsequent run
Packit 032894
             in a different cwd. */
Packit 032894
          char *rp = realpath(f->fts_path, NULL);
Packit 032894
          if (rp == NULL)
Packit 032894
            continue; // ignore dangling symlink or such
Packit 032894
          string rps = string(rp);
Packit 032894
          free (rp);
Packit 032894
Packit 032894
          bool ri = !regexec (&file_include_regex, rps.c_str(), 0, 0, 0);
Packit 032894
          bool rx = !regexec (&file_exclude_regex, rps.c_str(), 0, 0, 0);
Packit 032894
          if (!ri || rx)
Packit 032894
            {
Packit 032894
              if (verbose > 3)
Packit 032894
                obatched(clog) << "fts/file skipped by regex " << (!ri ? "I" : "") << (rx ? "X" : "") << endl;
Packit 032894
              fts_regex ++;
Packit 032894
              continue;
Packit 032894
            }
Packit 032894
Packit 032894
          switch (f->fts_info)
Packit 032894
            {
Packit 032894
            case FTS_D:
Packit 032894
              break;
Packit 032894
Packit 032894
            case FTS_DP:
Packit 032894
              break;
Packit 032894
Packit 032894
            case FTS_F:
Packit 032894
              {
Packit 032894
                /* See if we know of it already. */
Packit 032894
                int rc = ps_query
Packit 032894
                  .reset()
Packit 032894
                  .bind(1, rps)
Packit 032894
                  .bind(2, f->fts_statp->st_mtime)
Packit 032894
                  .step();
Packit 032894
                ps_query.reset();
Packit 032894
                if (rc == SQLITE_ROW) // i.e., a result, as opposed to DONE (no results)
Packit 032894
                  // no need to recheck a file/version we already know
Packit 032894
                  // specifically, no need to elf-begin a file we already determined is non-elf
Packit 032894
                  // (so is stored with buildid=NULL)
Packit 032894
                  {
Packit 032894
                    fts_cached ++;
Packit 032894
                    continue;
Packit 032894
                  }
Packit 032894
Packit 032894
                bool executable_p = false, debuginfo_p = false; // E and/or D
Packit 032894
                string buildid;
Packit 032894
                set<string> sourcefiles;
Packit 032894
Packit 032894
                int fd = open (rps.c_str(), O_RDONLY);
Packit 032894
                try
Packit 032894
                  {
Packit 032894
                    if (fd >= 0)
Packit 032894
                      elf_classify (fd, executable_p, debuginfo_p, buildid, sourcefiles);
Packit 032894
                    else
Packit 032894
                      throw libc_exception(errno, string("open ") + rps);
Packit 032894
                    inc_metric ("scanned_total","source","file");
Packit 032894
                  }
Packit 032894
Packit 032894
                // NB: we catch exceptions here too, so that we can
Packit 032894
                // cache the corrupt-elf case (!executable_p &&
Packit 032894
                // !debuginfo_p) just below, just as if we had an
Packit 032894
                // EPERM error from open(2).
Packit 032894
Packit 032894
                catch (const reportable_exception& e)
Packit 032894
                  {
Packit 032894
                    e.report(clog);
Packit 032894
                  }
Packit 032894
Packit 032894
                if (fd >= 0)
Packit 032894
                  close (fd);
Packit 032894
Packit 032894
                // register this file name in the interning table
Packit 032894
                ps_upsert_files
Packit 032894
                  .reset()
Packit 032894
                  .bind(1, rps)
Packit 032894
                  .step_ok_done();
Packit 032894
Packit 032894
                if (buildid == "")
Packit 032894
                  {
Packit 032894
                    // no point storing an elf file without buildid
Packit 032894
                    executable_p = false;
Packit 032894
                    debuginfo_p = false;
Packit 032894
                  }
Packit 032894
                else
Packit 032894
                  {
Packit 032894
                    // register this build-id in the interning table
Packit 032894
                    ps_upsert_buildids
Packit 032894
                      .reset()
Packit 032894
                      .bind(1, buildid)
Packit 032894
                      .step_ok_done();
Packit 032894
                  }
Packit 032894
Packit 032894
                if (executable_p)
Packit 032894
                  fts_executable ++;
Packit 032894
                if (debuginfo_p)
Packit 032894
                  fts_debuginfo ++;
Packit 032894
                if (executable_p || debuginfo_p)
Packit 032894
                  {
Packit 032894
                    ps_upsert_de
Packit 032894
                      .reset()
Packit 032894
                      .bind(1, buildid)
Packit 032894
                      .bind(2, debuginfo_p ? 1 : 0)
Packit 032894
                      .bind(3, executable_p ? 1 : 0)
Packit 032894
                      .bind(4, rps)
Packit 032894
                      .bind(5, f->fts_statp->st_mtime)
Packit 032894
                      .step_ok_done();
Packit 032894
                  }
Packit 032894
                if (executable_p)
Packit 032894
                  inc_metric("found_executable_total","source","files");
Packit 032894
                if (debuginfo_p)
Packit 032894
                  inc_metric("found_debuginfo_total","source","files");
Packit 032894
Packit 032894
                if (sourcefiles.size() && buildid != "")
Packit 032894
                  {
Packit 032894
                    fts_sourcefiles += sourcefiles.size();
Packit 032894
Packit 032894
                    for (auto&& dwarfsrc : sourcefiles)
Packit 032894
                      {
Packit 032894
                        char *srp = realpath(dwarfsrc.c_str(), NULL);
Packit 032894
                        if (srp == NULL) // also if DWZ unresolved dwarfsrc=""
Packit 032894
                          continue; // unresolvable files are not a serious problem
Packit 032894
                        // throw libc_exception(errno, "fts/file realpath " + srcpath);
Packit 032894
                        string srps = string(srp);
Packit 032894
                        free (srp);
Packit 032894
Packit 032894
                        struct stat sfs;
Packit 032894
                        rc = stat(srps.c_str(), &sfs;;
Packit 032894
                        if (rc != 0)
Packit 032894
                          continue;
Packit 032894
Packit 032894
                        if (verbose > 2)
Packit 032894
                          obatched(clog) << "recorded buildid=" << buildid << " file=" << srps
Packit 032894
                                         << " mtime=" << sfs.st_mtime
Packit 032894
                                         << " as source " << dwarfsrc << endl;
Packit 032894
Packit 032894
                        ps_upsert_files
Packit 032894
                          .reset()
Packit 032894
                          .bind(1, srps)
Packit 032894
                          .step_ok_done();
Packit 032894
Packit 032894
                        // register the dwarfsrc name in the interning table too
Packit 032894
                        ps_upsert_files
Packit 032894
                          .reset()
Packit 032894
                          .bind(1, dwarfsrc)
Packit 032894
                          .step_ok_done();
Packit 032894
Packit 032894
                        ps_upsert_s
Packit 032894
                          .reset()
Packit 032894
                          .bind(1, buildid)
Packit 032894
                          .bind(2, dwarfsrc)
Packit 032894
                          .bind(3, srps)
Packit 032894
                          .bind(4, sfs.st_mtime)
Packit 032894
                          .step_ok_done();
Packit 032894
Packit 032894
			inc_metric("found_sourcerefs_total","source","files");
Packit 032894
                      }
Packit 032894
                  }
Packit 032894
Packit 032894
                ps_scan_done
Packit 032894
                  .reset()
Packit 032894
                  .bind(1, rps)
Packit 032894
                  .bind(2, f->fts_statp->st_mtime)
Packit 032894
                  .bind(3, f->fts_statp->st_size)
Packit 032894
                  .step_ok_done();
Packit 032894
Packit 032894
                if (verbose > 2)
Packit 032894
                  obatched(clog) << "recorded buildid=" << buildid << " file=" << rps
Packit 032894
                                 << " mtime=" << f->fts_statp->st_mtime << " atype="
Packit 032894
                                 << (executable_p ? "E" : "")
Packit 032894
                                 << (debuginfo_p ? "D" : "") << endl;
Packit 032894
              }
Packit 032894
              break;
Packit 032894
Packit 032894
            case FTS_ERR:
Packit 032894
            case FTS_NS:
Packit 032894
              throw libc_exception(f->fts_errno, string("fts/file traversal ") + string(f->fts_path));
Packit 032894
Packit 032894
            default:
Packit 032894
            case FTS_SL: /* ignore symlinks; seen in non-L mode only */
Packit 032894
              break;
Packit 032894
            }
Packit 032894
Packit 032894
          if ((verbose && f->fts_info == FTS_DP) ||
Packit 032894
              (verbose > 1 && f->fts_info == FTS_F))
Packit 032894
            obatched(clog) << "fts/file traversing " << rps << ", scanned=" << fts_scanned
Packit 032894
                 << ", regex-skipped=" << fts_regex
Packit 032894
                 << ", cached=" << fts_cached << ", debuginfo=" << fts_debuginfo
Packit 032894
                 << ", executable=" << fts_executable << ", source=" << fts_sourcefiles << endl;
Packit 032894
        }
Packit 032894
      catch (const reportable_exception& e)
Packit 032894
        {
Packit 032894
          e.report(clog);
Packit 032894
        }
Packit 032894
    }
Packit 032894
  fts_close (fts);
Packit 032894
Packit 032894
  gettimeofday (&tv_end, NULL);
Packit 032894
  double deltas = (tv_end.tv_sec - tv_start.tv_sec) + (tv_end.tv_usec - tv_start.tv_usec)*0.000001;
Packit 032894
Packit 032894
  obatched(clog) << "fts/file traversed " << dir << " in " << deltas << "s, scanned=" << fts_scanned
Packit 032894
                 << ", regex-skipped=" << fts_regex
Packit 032894
                 << ", cached=" << fts_cached << ", debuginfo=" << fts_debuginfo
Packit 032894
                 << ", executable=" << fts_executable << ", source=" << fts_sourcefiles << endl;
Packit 032894
}
Packit 032894
Packit 032894
Packit 032894
static void*
Packit 032894
thread_main_scan_source_file_path (void* arg)
Packit 032894
{
Packit 032894
  string dir = string((const char*) arg);
Packit 032894
Packit 032894
  unsigned rescan_timer = 0;
Packit 032894
  sig_atomic_t forced_rescan_count = 0;
Packit 032894
  set_metric("thread_timer_max", "file", dir, rescan_s);
Packit 032894
  set_metric("thread_tid", "file", dir, tid());
Packit 032894
  while (! interrupted)
Packit 032894
    {
Packit 032894
      set_metric("thread_timer", "file", dir, rescan_timer);
Packit 032894
      set_metric("thread_forced_total", "file", dir, forced_rescan_count);
Packit 032894
      if (rescan_s && rescan_timer > rescan_s)
Packit 032894
        rescan_timer = 0;
Packit 032894
      if (sigusr1 != forced_rescan_count)
Packit 032894
        {
Packit 032894
          forced_rescan_count = sigusr1;
Packit 032894
          rescan_timer = 0;
Packit 032894
        }
Packit 032894
      if (rescan_timer == 0)
Packit 032894
        try
Packit 032894
          {
Packit 032894
            set_metric("thread_working", "file", dir, time(NULL));
Packit 032894
            inc_metric("thread_work_total", "file", dir);
Packit 032894
            scan_source_file_path (dir);
Packit 032894
            set_metric("thread_working", "file", dir, 0);
Packit 032894
          }
Packit 032894
        catch (const sqlite_exception& e)
Packit 032894
          {
Packit 032894
            obatched(cerr) << e.message << endl;
Packit 032894
          }
Packit 032894
      sleep (1);
Packit 032894
      rescan_timer ++;
Packit 032894
    }
Packit 032894
Packit 032894
  return 0;
Packit 032894
}
Packit 032894
Packit 032894
Packit 032894
////////////////////////////////////////////////////////////////////////
Packit 032894
Packit 032894
Packit 032894
Packit 032894
Packit 032894
// Analyze given *.rpm file of given age; record buildids / exec/debuginfo-ness of its
Packit 032894
// constituent files with given upsert statements.
Packit 032894
static void
Packit 032894
rpm_classify (const string& rps, sqlite_ps& ps_upsert_buildids, sqlite_ps& ps_upsert_files,
Packit 032894
              sqlite_ps& ps_upsert_de, sqlite_ps& ps_upsert_sref, sqlite_ps& ps_upsert_sdef,
Packit 032894
              time_t mtime,
Packit 032894
              unsigned& fts_executable, unsigned& fts_debuginfo, unsigned& fts_sref, unsigned& fts_sdef,
Packit 032894
              bool& fts_sref_complete_p)
Packit 032894
{
Packit 032894
  string popen_cmd = string("rpm2cpio " + shell_escape(rps));
Packit 032894
  FILE* fp = popen (popen_cmd.c_str(), "r"); // "e" O_CLOEXEC?
Packit 032894
  if (fp == NULL)
Packit 032894
    throw libc_exception (errno, string("popen ") + popen_cmd);
Packit 032894
  defer_dtor<FILE*,int> fp_closer (fp, pclose);
Packit 032894
Packit 032894
  struct archive *a;
Packit 032894
  a = archive_read_new();
Packit 032894
  if (a == NULL)
Packit 032894
    throw archive_exception("cannot create archive reader");
Packit 032894
  defer_dtor<struct archive*,int> archive_closer (a, archive_read_free);
Packit 032894
Packit 032894
  int rc = archive_read_support_format_cpio(a);
Packit 032894
  if (rc != ARCHIVE_OK)
Packit 032894
    throw archive_exception(a, "cannot select cpio format");
Packit 032894
  rc = archive_read_support_filter_all(a);
Packit 032894
  if (rc != ARCHIVE_OK)
Packit 032894
    throw archive_exception(a, "cannot select all filters");
Packit 032894
Packit 032894
  rc = archive_read_open_FILE (a, fp);
Packit 032894
  if (rc != ARCHIVE_OK)
Packit 032894
    throw archive_exception(a, "cannot open archive from rpm2cpio pipe");
Packit 032894
Packit 032894
  if (verbose > 3)
Packit 032894
    obatched(clog) << "rpm2cpio|libarchive scanning " << rps << endl;
Packit 032894
Packit 032894
  while(1) // parse cpio archive entries
Packit 032894
    {
Packit 032894
      try
Packit 032894
        {
Packit 032894
          struct archive_entry *e;
Packit 032894
          rc = archive_read_next_header (a, &e);
Packit 032894
          if (rc != ARCHIVE_OK)
Packit 032894
            break;
Packit 032894
Packit 032894
          if (! S_ISREG(archive_entry_mode (e))) // skip non-files completely
Packit 032894
            continue;
Packit 032894
Packit 032894
          string fn = archive_entry_pathname (e);
Packit 032894
          if (fn.size() > 1 && fn[0] == '.')
Packit 032894
            fn = fn.substr(1); // trim off the leading '.'
Packit 032894
Packit 032894
          if (verbose > 3)
Packit 032894
            obatched(clog) << "rpm2cpio|libarchive checking " << fn << endl;
Packit 032894
Packit 032894
          // extract this file to a temporary file
Packit 032894
          const char *tmpdir_env = getenv ("TMPDIR") ?: "/tmp";
Packit 032894
          char* tmppath = NULL;
Packit 032894
          rc = asprintf (&tmppath, "%s/debuginfod.XXXXXX", tmpdir_env);
Packit 032894
          if (rc < 0)
Packit 032894
            throw libc_exception (ENOMEM, "cannot allocate tmppath");
Packit 032894
          defer_dtor<void*,void> tmmpath_freer (tmppath, free);
Packit 032894
          int fd = mkstemp (tmppath);
Packit 032894
          if (fd < 0)
Packit 032894
            throw libc_exception (errno, "cannot create temporary file");
Packit 032894
          unlink (tmppath); // unlink now so OS will release the file as soon as we close the fd
Packit 032894
          defer_dtor<int,int> minifd_closer (fd, close);
Packit 032894
Packit 032894
          rc = archive_read_data_into_fd (a, fd);
Packit 032894
          if (rc != ARCHIVE_OK)
Packit 032894
            throw archive_exception(a, "cannot extract file");
Packit 032894
Packit 032894
          // finally ... time to run elf_classify on this bad boy and update the database
Packit 032894
          bool executable_p = false, debuginfo_p = false;
Packit 032894
          string buildid;
Packit 032894
          set<string> sourcefiles;
Packit 032894
          elf_classify (fd, executable_p, debuginfo_p, buildid, sourcefiles);
Packit 032894
          // NB: might throw
Packit 032894
Packit 032894
          if (buildid != "") // intern buildid
Packit 032894
            {
Packit 032894
              ps_upsert_buildids
Packit 032894
                .reset()
Packit 032894
                .bind(1, buildid)
Packit 032894
                .step_ok_done();
Packit 032894
            }
Packit 032894
Packit 032894
          ps_upsert_files // register this rpm constituent file name in interning table
Packit 032894
            .reset()
Packit 032894
            .bind(1, fn)
Packit 032894
            .step_ok_done();
Packit 032894
Packit 032894
          if (sourcefiles.size() > 0) // sref records needed
Packit 032894
            {
Packit 032894
              // NB: we intern each source file once.  Once raw, as it
Packit 032894
              // appears in the DWARF file list coming back from
Packit 032894
              // elf_classify() - because it'll end up in the
Packit 032894
              // _norm.artifactsrc column.  We don't also put another
Packit 032894
              // version with a '.' at the front, even though that's
Packit 032894
              // how rpm/cpio packs names, because we hide that from
Packit 032894
              // the database for storage efficiency.
Packit 032894
Packit 032894
              for (auto&& s : sourcefiles)
Packit 032894
                {
Packit 032894
                  if (s == "")
Packit 032894
                    {
Packit 032894
                      fts_sref_complete_p = false;
Packit 032894
                      continue;
Packit 032894
                    }
Packit 032894
Packit 032894
                  ps_upsert_files
Packit 032894
                    .reset()
Packit 032894
                    .bind(1, s)
Packit 032894
                    .step_ok_done();
Packit 032894
Packit 032894
                  ps_upsert_sref
Packit 032894
                    .reset()
Packit 032894
                    .bind(1, buildid)
Packit 032894
                    .bind(2, s)
Packit 032894
                    .step_ok_done();
Packit 032894
Packit 032894
                  fts_sref ++;
Packit 032894
                }
Packit 032894
            }
Packit 032894
Packit 032894
          if (executable_p)
Packit 032894
            fts_executable ++;
Packit 032894
          if (debuginfo_p)
Packit 032894
            fts_debuginfo ++;
Packit 032894
Packit 032894
          if (executable_p || debuginfo_p)
Packit 032894
            {
Packit 032894
              ps_upsert_de
Packit 032894
                .reset()
Packit 032894
                .bind(1, buildid)
Packit 032894
                .bind(2, debuginfo_p ? 1 : 0)
Packit 032894
                .bind(3, executable_p ? 1 : 0)
Packit 032894
                .bind(4, rps)
Packit 032894
                .bind(5, mtime)
Packit 032894
                .bind(6, fn)
Packit 032894
                .step_ok_done();
Packit 032894
            }
Packit 032894
          else // potential source - sdef record
Packit 032894
            {
Packit 032894
              fts_sdef ++;
Packit 032894
              ps_upsert_sdef
Packit 032894
                .reset()
Packit 032894
                .bind(1, rps)
Packit 032894
                .bind(2, mtime)
Packit 032894
                .bind(3, fn)
Packit 032894
                .step_ok_done();
Packit 032894
            }
Packit 032894
Packit 032894
          if ((verbose > 2) && (executable_p || debuginfo_p))
Packit 032894
            obatched(clog) << "recorded buildid=" << buildid << " rpm=" << rps << " file=" << fn
Packit 032894
                           << " mtime=" << mtime << " atype="
Packit 032894
                           << (executable_p ? "E" : "")
Packit 032894
                           << (debuginfo_p ? "D" : "")
Packit 032894
                           << " sourcefiles=" << sourcefiles.size() << endl;
Packit 032894
Packit 032894
        }
Packit 032894
      catch (const reportable_exception& e)
Packit 032894
        {
Packit 032894
          e.report(clog);
Packit 032894
        }
Packit 032894
    }
Packit 032894
}
Packit 032894
Packit 032894
Packit 032894
Packit 032894
// scan for *.rpm files
Packit 032894
static void
Packit 032894
scan_source_rpm_path (const string& dir)
Packit 032894
{
Packit 032894
  obatched(clog) << "fts/rpm traversing " << dir << endl;
Packit 032894
Packit 032894
  sqlite_ps ps_upsert_buildids (db, "rpm-buildid-intern", "insert or ignore into " BUILDIDS "_buildids VALUES (NULL, ?);");
Packit 032894
  sqlite_ps ps_upsert_files (db, "rpm-file-intern", "insert or ignore into " BUILDIDS "_files VALUES (NULL, ?);");
Packit 032894
  sqlite_ps ps_upsert_de (db, "rpm-de-insert",
Packit 032894
                          "insert or ignore into " BUILDIDS "_r_de (buildid, debuginfo_p, executable_p, file, mtime, content) values ("
Packit 032894
                          "(select id from " BUILDIDS "_buildids where hex = ?), ?, ?, "
Packit 032894
                          "(select id from " BUILDIDS "_files where name = ?), ?, "
Packit 032894
                          "(select id from " BUILDIDS "_files where name = ?));");
Packit 032894
  sqlite_ps ps_upsert_sref (db, "rpm-sref-insert",
Packit 032894
                            "insert or ignore into " BUILDIDS "_r_sref (buildid, artifactsrc) values ("
Packit 032894
                            "(select id from " BUILDIDS "_buildids where hex = ?), "
Packit 032894
                            "(select id from " BUILDIDS "_files where name = ?));");
Packit 032894
  sqlite_ps ps_upsert_sdef (db, "rpm-sdef-insert",
Packit 032894
                            "insert or ignore into " BUILDIDS "_r_sdef (file, mtime, content) values ("
Packit 032894
                            "(select id from " BUILDIDS "_files where name = ?), ?,"
Packit 032894
                            "(select id from " BUILDIDS "_files where name = ?));");
Packit 032894
  sqlite_ps ps_query (db, "rpm-negativehit-query",
Packit 032894
                      "select 1 from " BUILDIDS "_file_mtime_scanned where "
Packit 032894
                      "sourcetype = 'R' and file = (select id from " BUILDIDS "_files where name = ?) and mtime = ?;");
Packit 032894
  sqlite_ps ps_scan_done (db, "rpm-scanned",
Packit 032894
                          "insert or ignore into " BUILDIDS "_file_mtime_scanned (sourcetype, file, mtime, size)"
Packit 032894
                          "values ('R', (select id from " BUILDIDS "_files where name = ?), ?, ?);");
Packit 032894
Packit 032894
  char * const dirs[] = { (char*) dir.c_str(), NULL };
Packit 032894
Packit 032894
  struct timeval tv_start, tv_end;
Packit 032894
  gettimeofday (&tv_start, NULL);
Packit 032894
  unsigned fts_scanned=0, fts_regex=0, fts_cached=0, fts_debuginfo=0;
Packit 032894
  unsigned fts_executable=0, fts_rpm = 0, fts_sref=0, fts_sdef=0;
Packit 032894
Packit 032894
  FTS *fts = fts_open (dirs,
Packit 032894
                       (traverse_logical ? FTS_LOGICAL : FTS_PHYSICAL|FTS_XDEV)
Packit 032894
                       | FTS_NOCHDIR /* multithreaded */,
Packit 032894
                       NULL);
Packit 032894
  if (fts == NULL)
Packit 032894
    {
Packit 032894
      obatched(cerr) << "cannot fts_open " << dir << endl;
Packit 032894
      return;
Packit 032894
    }
Packit 032894
Packit 032894
  FTSENT *f;
Packit 032894
  while ((f = fts_read (fts)) != NULL)
Packit 032894
    {
Packit 032894
      semaphore_borrower handle_one_file (scan_concurrency_sem);
Packit 032894
Packit 032894
      fts_scanned ++;
Packit 032894
      if (interrupted)
Packit 032894
        break;
Packit 032894
Packit 032894
      if (verbose > 2)
Packit 032894
        obatched(clog) << "fts/rpm traversing " << f->fts_path << endl;
Packit 032894
Packit 032894
      try
Packit 032894
        {
Packit 032894
          /* Found a file.  Convert it to an absolute path, so
Packit 032894
             the buildid database does not have relative path
Packit 032894
             names that are unresolvable from a subsequent run
Packit 032894
             in a different cwd. */
Packit 032894
          char *rp = realpath(f->fts_path, NULL);
Packit 032894
          if (rp == NULL)
Packit 032894
            continue; // ignore dangling symlink or such
Packit 032894
          string rps = string(rp);
Packit 032894
          free (rp);
Packit 032894
Packit 032894
          bool ri = !regexec (&file_include_regex, rps.c_str(), 0, 0, 0);
Packit 032894
          bool rx = !regexec (&file_exclude_regex, rps.c_str(), 0, 0, 0);
Packit 032894
          if (!ri || rx)
Packit 032894
            {
Packit 032894
              if (verbose > 3)
Packit 032894
                obatched(clog) << "fts/rpm skipped by regex " << (!ri ? "I" : "") << (rx ? "X" : "") << endl;
Packit 032894
              fts_regex ++;
Packit 032894
              continue;
Packit 032894
            }
Packit 032894
Packit 032894
          switch (f->fts_info)
Packit 032894
            {
Packit 032894
            case FTS_D:
Packit 032894
              break;
Packit 032894
Packit 032894
            case FTS_DP:
Packit 032894
              break;
Packit 032894
Packit 032894
            case FTS_F:
Packit 032894
              {
Packit 032894
                // heuristic: reject if file name does not end with ".rpm"
Packit 032894
                // (alternative: try opening with librpm etc., caching)
Packit 032894
                string suffix = ".rpm";
Packit 032894
                if (rps.size() < suffix.size() ||
Packit 032894
                    rps.substr(rps.size()-suffix.size()) != suffix)
Packit 032894
                  continue;
Packit 032894
                fts_rpm ++;
Packit 032894
Packit 032894
                /* See if we know of it already. */
Packit 032894
                int rc = ps_query
Packit 032894
                  .reset()
Packit 032894
                  .bind(1, rps)
Packit 032894
                  .bind(2, f->fts_statp->st_mtime)
Packit 032894
                  .step();
Packit 032894
                ps_query.reset();
Packit 032894
                if (rc == SQLITE_ROW) // i.e., a result, as opposed to DONE (no results)
Packit 032894
                  // no need to recheck a file/version we already know
Packit 032894
                  // specifically, no need to parse this rpm again, since we already have
Packit 032894
                  // it as a D or E or S record,
Packit 032894
                  // (so is stored with buildid=NULL)
Packit 032894
                  {
Packit 032894
                    fts_cached ++;
Packit 032894
                    continue;
Packit 032894
                  }
Packit 032894
Packit 032894
                // intern the rpm file name
Packit 032894
                ps_upsert_files
Packit 032894
                  .reset()
Packit 032894
                  .bind(1, rps)
Packit 032894
                  .step_ok_done();
Packit 032894
Packit 032894
                // extract the rpm contents via popen("rpm2cpio") | libarchive | loop-of-elf_classify()
Packit 032894
                unsigned my_fts_executable = 0, my_fts_debuginfo = 0, my_fts_sref = 0, my_fts_sdef = 0;
Packit 032894
                bool my_fts_sref_complete_p = true;
Packit 032894
                try
Packit 032894
                  {
Packit 032894
                    rpm_classify (rps,
Packit 032894
                                  ps_upsert_buildids, ps_upsert_files,
Packit 032894
                                  ps_upsert_de, ps_upsert_sref, ps_upsert_sdef, // dalt
Packit 032894
                                  f->fts_statp->st_mtime,
Packit 032894
                                  my_fts_executable, my_fts_debuginfo, my_fts_sref, my_fts_sdef,
Packit 032894
                                  my_fts_sref_complete_p);
Packit 032894
                    inc_metric ("scanned_total","source","rpm");
Packit 032894
                    add_metric("found_debuginfo_total","source","rpm",
Packit 032894
                               my_fts_debuginfo);
Packit 032894
                    add_metric("found_executable_total","source","rpm",
Packit 032894
                               my_fts_executable);
Packit 032894
                    add_metric("found_sourcerefs_total","source","rpm",
Packit 032894
                               my_fts_sref);
Packit 032894
                  }
Packit 032894
                catch (const reportable_exception& e)
Packit 032894
                  {
Packit 032894
                    e.report(clog);
Packit 032894
                  }
Packit 032894
Packit 032894
                if (verbose > 2)
Packit 032894
                  obatched(clog) << "scanned rpm=" << rps
Packit 032894
                                 << " mtime=" << f->fts_statp->st_mtime
Packit 032894
                                 << " executables=" << my_fts_executable
Packit 032894
                                 << " debuginfos=" << my_fts_debuginfo
Packit 032894
                                 << " srefs=" << my_fts_sref
Packit 032894
                                 << " sdefs=" << my_fts_sdef
Packit 032894
                                 << endl;
Packit 032894
 
Packit 032894
                fts_executable += my_fts_executable;
Packit 032894
                fts_debuginfo += my_fts_debuginfo;
Packit 032894
                fts_sref += my_fts_sref;
Packit 032894
                fts_sdef += my_fts_sdef;
Packit 032894
Packit 032894
                if (my_fts_sref_complete_p) // leave incomplete?
Packit 032894
                  ps_scan_done
Packit 032894
                    .reset()
Packit 032894
                    .bind(1, rps)
Packit 032894
                    .bind(2, f->fts_statp->st_mtime)
Packit 032894
                    .bind(3, f->fts_statp->st_size)
Packit 032894
                    .step_ok_done();
Packit 032894
              }
Packit 032894
              break;
Packit 032894
Packit 032894
            case FTS_ERR:
Packit 032894
            case FTS_NS:
Packit 032894
              throw libc_exception(f->fts_errno, string("fts/rpm traversal ") + string(f->fts_path));
Packit 032894
Packit 032894
            default:
Packit 032894
            case FTS_SL: /* ignore symlinks; seen in non-L mode only */
Packit 032894
              break;
Packit 032894
            }
Packit 032894
Packit 032894
          if ((verbose && f->fts_info == FTS_DP) ||
Packit 032894
              (verbose > 1 && f->fts_info == FTS_F))
Packit 032894
            obatched(clog) << "fts/rpm traversing " << rps << ", scanned=" << fts_scanned
Packit 032894
                           << ", regex-skipped=" << fts_regex
Packit 032894
                           << ", rpm=" << fts_rpm << ", cached=" << fts_cached << ", debuginfo=" << fts_debuginfo
Packit 032894
                           << ", executable=" << fts_executable
Packit 032894
                           << ", sourcerefs=" << fts_sref << ", sourcedefs=" << fts_sdef << endl;
Packit 032894
        }
Packit 032894
      catch (const reportable_exception& e)
Packit 032894
        {
Packit 032894
          e.report(clog);
Packit 032894
        }
Packit 032894
    }
Packit 032894
  fts_close (fts);
Packit 032894
Packit 032894
  gettimeofday (&tv_end, NULL);
Packit 032894
  double deltas = (tv_end.tv_sec - tv_start.tv_sec) + (tv_end.tv_usec - tv_start.tv_usec)*0.000001;
Packit 032894
Packit 032894
  obatched(clog) << "fts/rpm traversed " << dir << " in " << deltas << "s, scanned=" << fts_scanned
Packit 032894
                 << ", regex-skipped=" << fts_regex
Packit 032894
                 << ", rpm=" << fts_rpm << ", cached=" << fts_cached << ", debuginfo=" << fts_debuginfo
Packit 032894
                 << ", executable=" << fts_executable
Packit 032894
                 << ", sourcerefs=" << fts_sref << ", sourcedefs=" << fts_sdef << endl;
Packit 032894
}
Packit 032894
Packit 032894
Packit 032894
Packit 032894
static void*
Packit 032894
thread_main_scan_source_rpm_path (void* arg)
Packit 032894
{
Packit 032894
  string dir = string((const char*) arg);
Packit 032894
Packit 032894
  unsigned rescan_timer = 0;
Packit 032894
  sig_atomic_t forced_rescan_count = 0;
Packit 032894
  set_metric("thread_timer_max", "rpm", dir, rescan_s);
Packit 032894
  set_metric("thread_tid", "rpm", dir, tid());
Packit 032894
  while (! interrupted)
Packit 032894
    {
Packit 032894
      set_metric("thread_timer", "rpm", dir, rescan_timer);
Packit 032894
      set_metric("thread_forced_total", "rpm", dir, forced_rescan_count);
Packit 032894
      if (rescan_s && rescan_timer > rescan_s)
Packit 032894
        rescan_timer = 0;
Packit 032894
      if (sigusr1 != forced_rescan_count)
Packit 032894
        {
Packit 032894
          forced_rescan_count = sigusr1;
Packit 032894
          rescan_timer = 0;
Packit 032894
        }
Packit 032894
      if (rescan_timer == 0)
Packit 032894
        try
Packit 032894
          {
Packit 032894
            set_metric("thread_working", "rpm", dir, time(NULL));
Packit 032894
            inc_metric("thread_work_total", "rpm", dir);
Packit 032894
            scan_source_rpm_path (dir);
Packit 032894
            set_metric("thread_working", "rpm", dir, 0);
Packit 032894
          }
Packit 032894
        catch (const sqlite_exception& e)
Packit 032894
          {
Packit 032894
            obatched(cerr) << e.message << endl;
Packit 032894
          }
Packit 032894
      sleep (1);
Packit 032894
      rescan_timer ++;
Packit 032894
    }
Packit 032894
Packit 032894
  return 0;
Packit 032894
}
Packit 032894
Packit 032894
Packit 032894
////////////////////////////////////////////////////////////////////////
Packit 032894
Packit 032894
static void
Packit 032894
database_stats_report()
Packit 032894
{
Packit 032894
  sqlite_ps ps_query (db, "database-overview",
Packit 032894
                      "select label,quantity from " BUILDIDS "_stats");
Packit 032894
Packit 032894
  obatched(clog) << "database record counts:" << endl;
Packit 032894
  while (1)
Packit 032894
    {
Packit 032894
      int rc = sqlite3_step (ps_query);
Packit 032894
      if (rc == SQLITE_DONE) break;
Packit 032894
      if (rc != SQLITE_ROW)
Packit 032894
        throw sqlite_exception(rc, "step");
Packit 032894
Packit 032894
      obatched(clog)
Packit 032894
        << right << setw(20) << ((const char*) sqlite3_column_text(ps_query, 0) ?: (const char*) "NULL")
Packit 032894
        << " "
Packit 032894
        << (sqlite3_column_text(ps_query, 1) ?: (const unsigned char*) "NULL")
Packit 032894
        << endl;
Packit 032894
Packit 032894
      set_metric("groom", "statistic",
Packit 032894
                 ((const char*) sqlite3_column_text(ps_query, 0) ?: (const char*) "NULL"),
Packit 032894
                 (sqlite3_column_double(ps_query, 1)));
Packit 032894
    }
Packit 032894
}
Packit 032894
Packit 032894
Packit 032894
// Do a round of database grooming that might take many minutes to run.
Packit 032894
void groom()
Packit 032894
{
Packit 032894
  obatched(clog) << "grooming database" << endl;
Packit 032894
Packit 032894
  struct timeval tv_start, tv_end;
Packit 032894
  gettimeofday (&tv_start, NULL);
Packit 032894
Packit 032894
  // scan for files that have disappeared
Packit 032894
  sqlite_ps files (db, "check old files", "select s.mtime, s.file, f.name from "
Packit 032894
                       BUILDIDS "_file_mtime_scanned s, " BUILDIDS "_files f "
Packit 032894
                       "where f.id = s.file");
Packit 032894
  sqlite_ps files_del_f_de (db, "nuke f_de", "delete from " BUILDIDS "_f_de where file = ? and mtime = ?");
Packit 032894
  sqlite_ps files_del_r_de (db, "nuke r_de", "delete from " BUILDIDS "_r_de where file = ? and mtime = ?");
Packit 032894
  sqlite_ps files_del_scan (db, "nuke f_m_s", "delete from " BUILDIDS "_file_mtime_scanned "
Packit 032894
                            "where file = ? and mtime = ?");
Packit 032894
  files.reset();
Packit 032894
  while(1)
Packit 032894
    {
Packit 032894
      int rc = files.step();
Packit 032894
      if (rc != SQLITE_ROW)
Packit 032894
        break;
Packit 032894
Packit 032894
      int64_t mtime = sqlite3_column_int64 (files, 0);
Packit 032894
      int64_t fileid = sqlite3_column_int64 (files, 1);
Packit 032894
      const char* filename = ((const char*) sqlite3_column_text (files, 2) ?: "");
Packit 032894
      struct stat s;
Packit 032894
      rc = stat(filename, &s);
Packit 032894
      if (rc < 0 || (mtime != (int64_t) s.st_mtime))
Packit 032894
        {
Packit 032894
          if (verbose > 2)
Packit 032894
            obatched(clog) << "groom: forgetting file=" << filename << " mtime=" << mtime << endl;
Packit 032894
          files_del_f_de.reset().bind(1,fileid).bind(2,mtime).step_ok_done();
Packit 032894
          files_del_r_de.reset().bind(1,fileid).bind(2,mtime).step_ok_done();
Packit 032894
          files_del_scan.reset().bind(1,fileid).bind(2,mtime).step_ok_done();
Packit 032894
        }
Packit 032894
    }
Packit 032894
  files.reset();
Packit 032894
Packit 032894
  // delete buildids with no references in _r_de or _f_de tables;
Packit 032894
  // cascades to _r_sref & _f_s records
Packit 032894
  sqlite_ps buildids_del (db, "nuke orphan buildids",
Packit 032894
                          "delete from " BUILDIDS "_buildids "
Packit 032894
                          "where not exists (select 1 from " BUILDIDS "_f_de d where " BUILDIDS "_buildids.id = d.buildid) "
Packit 032894
                          "and not exists (select 1 from " BUILDIDS "_r_de d where " BUILDIDS "_buildids.id = d.buildid)");
Packit 032894
  buildids_del.reset().step_ok_done();
Packit 032894
Packit 032894
  // NB: "vacuum" is too heavy for even daily runs: it rewrites the entire db, so is done as maxigroom -G
Packit 032894
  sqlite_ps g1 (db, "incremental vacuum", "pragma incremental_vacuum");
Packit 032894
  g1.reset().step_ok_done();
Packit 032894
  sqlite_ps g2 (db, "optimize", "pragma optimize");
Packit 032894
  g2.reset().step_ok_done();
Packit 032894
  sqlite_ps g3 (db, "wal checkpoint", "pragma wal_checkpoint=truncate");
Packit 032894
  g3.reset().step_ok_done();
Packit 032894
Packit 032894
  database_stats_report();
Packit 032894
Packit 032894
  sqlite3_db_release_memory(db); // shrink the process if possible
Packit 032894
Packit 032894
  gettimeofday (&tv_end, NULL);
Packit 032894
  double deltas = (tv_end.tv_sec - tv_start.tv_sec) + (tv_end.tv_usec - tv_start.tv_usec)*0.000001;
Packit 032894
Packit 032894
  obatched(clog) << "groomed database in " << deltas << "s" << endl;
Packit 032894
}
Packit 032894
Packit 032894
Packit 032894
static void*
Packit 032894
thread_main_groom (void* /*arg*/)
Packit 032894
{
Packit 032894
  unsigned groom_timer = 0;
Packit 032894
  sig_atomic_t forced_groom_count = 0;
Packit 032894
  set_metric("thread_timer_max", "role", "groom", groom_s);
Packit 032894
  set_metric("thread_tid", "role", "groom", tid());
Packit 032894
  while (! interrupted)
Packit 032894
    {
Packit 032894
      set_metric("thread_timer", "role", "groom", groom_timer);
Packit 032894
      set_metric("thread_forced_total", "role", "groom", forced_groom_count);      
Packit 032894
      if (groom_s && groom_timer > groom_s)
Packit 032894
        groom_timer = 0;
Packit 032894
      if (sigusr2 != forced_groom_count)
Packit 032894
        {
Packit 032894
          forced_groom_count = sigusr2;
Packit 032894
          groom_timer = 0;
Packit 032894
        }
Packit 032894
      if (groom_timer == 0)
Packit 032894
        try
Packit 032894
          {
Packit 032894
            set_metric("thread_working", "role", "groom", time(NULL));
Packit 032894
            inc_metric("thread_work_total", "role", "groom");
Packit 032894
            groom ();
Packit 032894
            set_metric("thread_working", "role", "groom", 0);
Packit 032894
          }
Packit 032894
        catch (const sqlite_exception& e)
Packit 032894
          {
Packit 032894
            obatched(cerr) << e.message << endl;
Packit 032894
          }
Packit 032894
      sleep (1);
Packit 032894
      groom_timer ++;
Packit 032894
    }
Packit 032894
Packit 032894
  return 0;
Packit 032894
}
Packit 032894
Packit 032894
Packit 032894
////////////////////////////////////////////////////////////////////////
Packit 032894
Packit 032894
Packit 032894
static void
Packit 032894
signal_handler (int /* sig */)
Packit 032894
{
Packit 032894
  interrupted ++;
Packit 032894
Packit 032894
  if (db)
Packit 032894
    sqlite3_interrupt (db);
Packit 032894
Packit 032894
  // NB: don't do anything else in here
Packit 032894
}
Packit 032894
Packit 032894
static void
Packit 032894
sigusr1_handler (int /* sig */)
Packit 032894
{
Packit 032894
   sigusr1 ++;
Packit 032894
  // NB: don't do anything else in here
Packit 032894
}
Packit 032894
Packit 032894
static void
Packit 032894
sigusr2_handler (int /* sig */)
Packit 032894
{
Packit 032894
   sigusr2 ++;
Packit 032894
  // NB: don't do anything else in here
Packit 032894
}
Packit 032894
Packit 032894
Packit 032894
Packit 032894
Packit 032894
Packit 032894
// A user-defined sqlite function, to score the sharedness of the
Packit 032894
// prefix of two strings.  This is used to compare candidate debuginfo
Packit 032894
// / source-rpm names, so that the closest match
Packit 032894
// (directory-topology-wise closest) is found.  This is important in
Packit 032894
// case the same sref (source file name) is in many -debuginfo or
Packit 032894
// -debugsource RPMs, such as when multiple versions/releases of the
Packit 032894
// same package are in the database.
Packit 032894
Packit 032894
static void sqlite3_sharedprefix_fn (sqlite3_context* c, int argc, sqlite3_value** argv)
Packit 032894
{
Packit 032894
  if (argc != 2)
Packit 032894
    sqlite3_result_error(c, "expect 2 string arguments", -1);
Packit 032894
  else if ((sqlite3_value_type(argv[0]) != SQLITE_TEXT) ||
Packit 032894
           (sqlite3_value_type(argv[1]) != SQLITE_TEXT))
Packit 032894
    sqlite3_result_null(c);
Packit 032894
  else
Packit 032894
    {
Packit 032894
      const unsigned char* a = sqlite3_value_text (argv[0]);
Packit 032894
      const unsigned char* b = sqlite3_value_text (argv[1]);
Packit 032894
      int i = 0;
Packit 032894
      while (*a++ == *b++)
Packit 032894
        i++;
Packit 032894
      sqlite3_result_int (c, i);
Packit 032894
    }
Packit 032894
}
Packit 032894
Packit 032894
Packit 032894
int
Packit 032894
main (int argc, char *argv[])
Packit 032894
{
Packit 032894
  (void) setlocale (LC_ALL, "");
Packit 032894
  (void) bindtextdomain (PACKAGE_TARNAME, LOCALEDIR);
Packit 032894
  (void) textdomain (PACKAGE_TARNAME);
Packit 032894
Packit 032894
  /* Tell the library which version we are expecting.  */
Packit 032894
  elf_version (EV_CURRENT);
Packit 032894
Packit 032894
  /* Set computed default values. */
Packit 032894
  db_path = string(getenv("HOME") ?: "/") + string("/.debuginfod.sqlite"); /* XDG? */
Packit 032894
  int rc = regcomp (& file_include_regex, ".*", REG_EXTENDED|REG_NOSUB); // match everything
Packit 032894
  if (rc != 0)
Packit 032894
    error (EXIT_FAILURE, 0, "regcomp failure: %d", rc);
Packit 032894
  rc = regcomp (& file_exclude_regex, "^$", REG_EXTENDED|REG_NOSUB); // match nothing
Packit 032894
  if (rc != 0)
Packit 032894
    error (EXIT_FAILURE, 0, "regcomp failure: %d", rc);
Packit 032894
Packit 032894
  /* Parse and process arguments.  */
Packit 032894
  int remaining;
Packit 032894
  argp_program_version_hook = print_version; // this works
Packit 032894
  (void) argp_parse (&argp, argc, argv, ARGP_IN_ORDER, &remaining, NULL);
Packit 032894
  if (remaining != argc)
Packit 032894
      error (EXIT_FAILURE, 0,
Packit 032894
             "unexpected argument: %s", argv[remaining]);
Packit 032894
Packit 032894
  if (!scan_rpms && !scan_files && source_paths.size()>0)
Packit 032894
    obatched(clog) << "warning: without -F and/or -R, ignoring PATHs" << endl;
Packit 032894
Packit 032894
  (void) signal (SIGPIPE, SIG_IGN); // microhttpd can generate it incidentally, ignore
Packit 032894
  (void) signal (SIGINT, signal_handler); // ^C
Packit 032894
  (void) signal (SIGHUP, signal_handler); // EOF
Packit 032894
  (void) signal (SIGTERM, signal_handler); // systemd
Packit 032894
  (void) signal (SIGUSR1, sigusr1_handler); // end-user
Packit 032894
  (void) signal (SIGUSR2, sigusr2_handler); // end-user
Packit 032894
Packit 032894
  // do this before any threads start
Packit 032894
  scan_concurrency_sem = new semaphore(concurrency);
Packit 032894
Packit 032894
  /* Get database ready. */
Packit 032894
  rc = sqlite3_open_v2 (db_path.c_str(), &db, (SQLITE_OPEN_READWRITE
Packit 032894
                                               |SQLITE_OPEN_CREATE
Packit 032894
                                               |SQLITE_OPEN_FULLMUTEX), /* thread-safe */
Packit 032894
                        NULL);
Packit 032894
  if (rc == SQLITE_CORRUPT)
Packit 032894
    {
Packit 032894
      (void) unlink (db_path.c_str());
Packit 032894
      error (EXIT_FAILURE, 0,
Packit 032894
             "cannot open %s, deleted database: %s", db_path.c_str(), sqlite3_errmsg(db));
Packit 032894
    }
Packit 032894
  else if (rc)
Packit 032894
    {
Packit 032894
      error (EXIT_FAILURE, 0,
Packit 032894
             "cannot open %s, consider deleting database: %s", db_path.c_str(), sqlite3_errmsg(db));
Packit 032894
    }
Packit 032894
Packit 032894
  obatched(clog) << "opened database " << db_path << endl;
Packit 032894
  obatched(clog) << "sqlite version " << sqlite3_version << endl;
Packit 032894
Packit 032894
  // add special string-prefix-similarity function used in rpm sref/sdef resolution
Packit 032894
  rc = sqlite3_create_function(db, "sharedprefix", 2, SQLITE_UTF8, NULL,
Packit 032894
                               & sqlite3_sharedprefix_fn, NULL, NULL);
Packit 032894
  if (rc != SQLITE_OK)
Packit 032894
    error (EXIT_FAILURE, 0,
Packit 032894
           "cannot create sharedprefix( function: %s", sqlite3_errmsg(db));
Packit 032894
Packit 032894
  if (verbose > 3)
Packit 032894
    obatched(clog) << "ddl: " << DEBUGINFOD_SQLITE_DDL << endl;
Packit 032894
  rc = sqlite3_exec (db, DEBUGINFOD_SQLITE_DDL, NULL, NULL, NULL);
Packit 032894
  if (rc != SQLITE_OK)
Packit 032894
    {
Packit 032894
      error (EXIT_FAILURE, 0,
Packit 032894
             "cannot run database schema ddl: %s", sqlite3_errmsg(db));
Packit 032894
    }
Packit 032894
Packit 032894
  // Start httpd server threads.  Separate pool for IPv4 and IPv6, in
Packit 032894
  // case the host only has one protocol stack.
Packit 032894
  MHD_Daemon *d4 = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION
Packit 032894
#if MHD_VERSION >= 0x00095300
Packit 032894
                                     | MHD_USE_INTERNAL_POLLING_THREAD
Packit 032894
#else
Packit 032894
                                     | MHD_USE_SELECT_INTERNALLY
Packit 032894
#endif
Packit 032894
                                     | MHD_USE_DEBUG, /* report errors to stderr */
Packit 032894
                                     http_port,
Packit 032894
                                     NULL, NULL, /* default accept policy */
Packit 032894
                                     handler_cb, NULL, /* handler callback */
Packit 032894
                                     MHD_OPTION_END);
Packit 032894
  MHD_Daemon *d6 = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION
Packit 032894
#if MHD_VERSION >= 0x00095300
Packit 032894
                                     | MHD_USE_INTERNAL_POLLING_THREAD
Packit 032894
#else
Packit 032894
                                     | MHD_USE_SELECT_INTERNALLY
Packit 032894
#endif
Packit 032894
                                     | MHD_USE_IPv6
Packit 032894
                                     | MHD_USE_DEBUG, /* report errors to stderr */
Packit 032894
                                     http_port,
Packit 032894
                                     NULL, NULL, /* default accept policy */
Packit 032894
                                     handler_cb, NULL, /* handler callback */
Packit 032894
                                     MHD_OPTION_END);
Packit 032894
Packit 032894
  if (d4 == NULL && d6 == NULL) // neither ipv4 nor ipv6? boo
Packit 032894
    {
Packit 032894
      sqlite3 *database = db;
Packit 032894
      db = 0; // for signal_handler not to freak
Packit 032894
      sqlite3_close (database);
Packit 032894
      error (EXIT_FAILURE, 0, "cannot start http server at port %d", http_port);
Packit 032894
    }
Packit 032894
Packit 032894
  obatched(clog) << "started http server on "
Packit 032894
                 << (d4 != NULL ? "IPv4 " : "")
Packit 032894
                 << (d6 != NULL ? "IPv6 " : "")
Packit 032894
                 << "port=" << http_port << endl;
Packit 032894
Packit 032894
  // add maxigroom sql if -G given
Packit 032894
  if (maxigroom)
Packit 032894
    {
Packit 032894
      obatched(clog) << "maxigrooming database, please wait." << endl;
Packit 032894
      extra_ddl.push_back("create index if not exists " BUILDIDS "_r_sref_arc on " BUILDIDS "_r_sref(artifactsrc);");
Packit 032894
      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);");
Packit 032894
      extra_ddl.push_back("drop index if exists " BUILDIDS "_r_sref_arc;");
Packit 032894
Packit 032894
      // NB: we don't maxigroom the _files interning table.  It'd require a temp index on all the
Packit 032894
      // tables that have file foreign-keys, which is a lot.
Packit 032894
Packit 032894
      // NB: with =delete, may take up 3x disk space total during vacuum process
Packit 032894
      //     vs.  =off (only 2x but may corrupt database if program dies mid-vacuum)
Packit 032894
      //     vs.  =wal (>3x observed, but safe)
Packit 032894
      extra_ddl.push_back("pragma journal_mode=delete;");
Packit 032894
      extra_ddl.push_back("vacuum;");
Packit 032894
      extra_ddl.push_back("pragma journal_mode=wal;");
Packit 032894
    }
Packit 032894
Packit 032894
  // run extra -D sql if given
Packit 032894
  for (auto&& i: extra_ddl)
Packit 032894
    {
Packit 032894
      if (verbose > 1)
Packit 032894
        obatched(clog) << "extra ddl:\n" << i << endl;
Packit 032894
      rc = sqlite3_exec (db, i.c_str(), NULL, NULL, NULL);
Packit 032894
      if (rc != SQLITE_OK && rc != SQLITE_DONE && rc != SQLITE_ROW)
Packit 032894
        error (0, 0,
Packit 032894
               "warning: cannot run database extra ddl %s: %s", i.c_str(), sqlite3_errmsg(db));
Packit 032894
    }
Packit 032894
Packit 032894
  if (maxigroom)
Packit 032894
    obatched(clog) << "maxigroomed database" << endl;
Packit 032894
Packit 032894
Packit 032894
  obatched(clog) << "search concurrency " << concurrency << endl;
Packit 032894
  obatched(clog) << "rescan time " << rescan_s << endl;
Packit 032894
  obatched(clog) << "groom time " << groom_s << endl;
Packit 032894
  const char* du = getenv(DEBUGINFOD_URLS_ENV_VAR);
Packit 032894
  if (du && du[0] != '\0') // set to non-empty string?
Packit 032894
    obatched(clog) << "upstream debuginfod servers: " << du << endl;
Packit 032894
Packit 032894
  vector<pthread_t> source_file_scanner_threads;
Packit 032894
  vector<pthread_t> source_rpm_scanner_threads;
Packit 032894
  pthread_t groom_thread;
Packit 032894
Packit 032894
  rc = pthread_create (& groom_thread, NULL, thread_main_groom, NULL);
Packit 032894
  if (rc < 0)
Packit 032894
    error (0, 0, "warning: cannot spawn thread (%d) to groom database\n", rc);
Packit 032894
 
Packit 032894
  if (scan_files) for (auto&& it : source_paths)
Packit 032894
    {
Packit 032894
      pthread_t pt;
Packit 032894
      rc = pthread_create (& pt, NULL, thread_main_scan_source_file_path, (void*) it.c_str());
Packit 032894
      if (rc < 0)
Packit 032894
        error (0, 0, "warning: cannot spawn thread (%d) to scan source files %s\n", rc, it.c_str());
Packit 032894
      else
Packit 032894
        source_file_scanner_threads.push_back(pt);
Packit 032894
    }
Packit 032894
Packit 032894
  if (scan_rpms) for (auto&& it : source_paths)
Packit 032894
    {
Packit 032894
      pthread_t pt;
Packit 032894
      rc = pthread_create (& pt, NULL, thread_main_scan_source_rpm_path, (void*) it.c_str());
Packit 032894
      if (rc < 0)
Packit 032894
        error (0, 0, "warning: cannot spawn thread (%d) to scan source rpms %s\n", rc, it.c_str());
Packit 032894
      else
Packit 032894
        source_rpm_scanner_threads.push_back(pt);
Packit 032894
    }
Packit 032894
Packit 032894
  /* Trivial main loop! */
Packit 032894
  set_metric("ready", 1);
Packit 032894
  while (! interrupted)
Packit 032894
    pause ();
Packit 032894
  set_metric("ready", 0);
Packit 032894
Packit 032894
  if (verbose)
Packit 032894
    obatched(clog) << "stopping" << endl;
Packit 032894
Packit 032894
  /* Join any source scanning threads. */
Packit 032894
  for (auto&& it : source_file_scanner_threads)
Packit 032894
    pthread_join (it, NULL);
Packit 032894
  for (auto&& it : source_rpm_scanner_threads)
Packit 032894
    pthread_join (it, NULL);
Packit 032894
  pthread_join (groom_thread, NULL);
Packit 032894
  
Packit 032894
  /* Stop all the web service threads. */
Packit 032894
  if (d4) MHD_stop_daemon (d4);
Packit 032894
  if (d6) MHD_stop_daemon (d6);
Packit 032894
Packit 032894
  /* With all threads known dead, we can clean up the global resources. */
Packit 032894
  delete scan_concurrency_sem;
Packit 032894
  rc = sqlite3_exec (db, DEBUGINFOD_SQLITE_CLEANUP_DDL, NULL, NULL, NULL);
Packit 032894
  if (rc != SQLITE_OK)
Packit 032894
    {
Packit 032894
      error (0, 0,
Packit 032894
             "warning: cannot run database cleanup ddl: %s", sqlite3_errmsg(db));
Packit 032894
    }
Packit 032894
Packit 032894
  // NB: no problem with unconditional free here - an earlier failed regcomp would exit program
Packit 032894
  (void) regfree (& file_include_regex);
Packit 032894
  (void) regfree (& file_exclude_regex);
Packit 032894
Packit 032894
  sqlite3 *database = db;
Packit 032894
  db = 0; // for signal_handler not to freak
Packit 032894
  (void) sqlite3_close (database);
Packit 032894
Packit 032894
  return 0;
Packit 032894
}