Blame debuginfod/debuginfod.cxx

Packit Service 97d2fb
/* Debuginfo-over-http server.
Packit Service 97d2fb
   Copyright (C) 2019-2020 Red Hat, Inc.
Packit Service 97d2fb
   This file is part of elfutils.
Packit Service 97d2fb
Packit Service 97d2fb
   This file is free software; you can redistribute it and/or modify
Packit Service 97d2fb
   it under the terms of the GNU General Public License as published by
Packit Service 97d2fb
   the Free Software Foundation; either version 3 of the License, or
Packit Service 97d2fb
   (at your option) any later version.
Packit Service 97d2fb
Packit Service 97d2fb
   elfutils is distributed in the hope that it will be useful, but
Packit Service 97d2fb
   WITHOUT ANY WARRANTY; without even the implied warranty of
Packit Service 97d2fb
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit Service 97d2fb
   GNU General Public License for more details.
Packit Service 97d2fb
Packit Service 97d2fb
   You should have received a copy of the GNU General Public License
Packit Service 97d2fb
   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
/* cargo-cult from libdwfl linux-kernel-modules.c */
Packit Service 97d2fb
/* In case we have a bad fts we include this before config.h because it
Packit Service 97d2fb
   can't handle _FILE_OFFSET_BITS.
Packit Service 97d2fb
   Everything we need here is fine if its declarations just come first.
Packit Service 97d2fb
   Also, include sys/types.h before fts. On some systems fts.h is not self
Packit Service 97d2fb
   contained. */
Packit Service 97d2fb
#ifdef BAD_FTS
Packit Service 97d2fb
  #include <sys/types.h>
Packit Service 97d2fb
  #include <fts.h>
Packit Service 97d2fb
#endif
Packit Service 97d2fb
Packit Service 97d2fb
#ifdef HAVE_CONFIG_H
Packit Service 97d2fb
  #include "config.h"
Packit Service 97d2fb
#endif
Packit Service 97d2fb
Packit Service 97d2fb
extern "C" {
Packit Service 97d2fb
#include "printversion.h"
Packit Service 97d2fb
}
Packit Service 97d2fb
Packit Service 97d2fb
#include "debuginfod.h"
Packit Service 97d2fb
#include <dwarf.h>
Packit Service 97d2fb
Packit Service 97d2fb
#include <argp.h>
Packit Service 97d2fb
#ifdef __GNUC__
Packit Service 97d2fb
#undef __attribute__ /* glibc bug - rhbz 1763325 */
Packit Service 97d2fb
#endif
Packit Service 97d2fb
Packit Service 97d2fb
#include <unistd.h>
Packit Service 97d2fb
#include <stdlib.h>
Packit Service 97d2fb
#include <error.h>
Packit Service 97d2fb
// #include <libintl.h> // not until it supports C++ << better
Packit Service 97d2fb
#include <locale.h>
Packit Service 97d2fb
#include <pthread.h>
Packit Service 97d2fb
#include <signal.h>
Packit Service 97d2fb
#include <sys/stat.h>
Packit Service 97d2fb
#include <sys/time.h>
Packit Service 97d2fb
#include <sys/vfs.h>
Packit Service 97d2fb
#include <unistd.h>
Packit Service 97d2fb
#include <fcntl.h>
Packit Service 97d2fb
#include <netdb.h>
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
/* If fts.h is included before config.h, its indirect inclusions may not
Packit Service 97d2fb
   give us the right LFS aliases of these functions, so map them manually.  */
Packit Service 97d2fb
#ifdef BAD_FTS
Packit Service 97d2fb
  #ifdef _FILE_OFFSET_BITS
Packit Service 97d2fb
    #define open open64
Packit Service 97d2fb
    #define fopen fopen64
Packit Service 97d2fb
  #endif
Packit Service 97d2fb
#else
Packit Service 97d2fb
  #include <sys/types.h>
Packit Service 97d2fb
  #include <fts.h>
Packit Service 97d2fb
#endif
Packit Service 97d2fb
Packit Service 97d2fb
#include <cstring>
Packit Service 97d2fb
#include <vector>
Packit Service 97d2fb
#include <set>
Packit Service 97d2fb
#include <map>
Packit Service 97d2fb
#include <string>
Packit Service 97d2fb
#include <iostream>
Packit Service 97d2fb
#include <iomanip>
Packit Service 97d2fb
#include <ostream>
Packit Service 97d2fb
#include <sstream>
Packit Service 97d2fb
#include <mutex>
Packit Service 97d2fb
#include <deque>
Packit Service 97d2fb
#include <condition_variable>
Packit Service 97d2fb
#include <thread>
Packit Service 97d2fb
// #include <regex> // on rhel7 gcc 4.8, not competent
Packit Service 97d2fb
#include <regex.h>
Packit Service 97d2fb
// #include <algorithm>
Packit Service 97d2fb
using namespace std;
Packit Service 97d2fb
Packit Service 97d2fb
#include <gelf.h>
Packit Service 97d2fb
#include <libdwelf.h>
Packit Service 97d2fb
Packit Service 97d2fb
#include <microhttpd.h>
Packit Service 97d2fb
Packit Service 97d2fb
#if MHD_VERSION >= 0x00097002
Packit Service 97d2fb
// libmicrohttpd 0.9.71 broke API
Packit Service 97d2fb
#define MHD_RESULT enum MHD_Result
Packit Service 97d2fb
#else
Packit Service 97d2fb
#define MHD_RESULT int
Packit Service 97d2fb
#endif
Packit Service 97d2fb
Packit Service 97d2fb
#include <curl/curl.h>
Packit Service 97d2fb
#include <archive.h>
Packit Service 97d2fb
#include <archive_entry.h>
Packit Service 97d2fb
#include <sqlite3.h>
Packit Service 97d2fb
Packit Service 97d2fb
#ifdef __linux__
Packit Service 97d2fb
#include <sys/syscall.h>
Packit Service 97d2fb
#endif
Packit Service 97d2fb
Packit Service 97d2fb
#ifdef __linux__
Packit Service 97d2fb
#define tid() syscall(SYS_gettid)
Packit Service 97d2fb
#else
Packit Service 97d2fb
#define tid() pthread_self()
Packit Service 97d2fb
#endif
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
inline bool
Packit Service 97d2fb
string_endswith(const string& haystack, const string& needle)
Packit Service 97d2fb
{
Packit Service 97d2fb
  return (haystack.size() >= needle.size() &&
Packit Service 97d2fb
	  equal(haystack.end()-needle.size(), haystack.end(),
Packit Service 97d2fb
                needle.begin()));
Packit Service 97d2fb
}
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
// Roll this identifier for every sqlite schema incompatiblity.
Packit Service 97d2fb
#define BUILDIDS "buildids9"
Packit Service 97d2fb
Packit Service 97d2fb
#if SQLITE_VERSION_NUMBER >= 3008000
Packit Service 97d2fb
#define WITHOUT_ROWID "without rowid"
Packit Service 97d2fb
#else
Packit Service 97d2fb
#define WITHOUT_ROWID ""
Packit Service 97d2fb
#endif
Packit Service 97d2fb
Packit Service 97d2fb
static const char DEBUGINFOD_SQLITE_DDL[] =
Packit Service 97d2fb
  "pragma foreign_keys = on;\n"
Packit Service 97d2fb
  "pragma synchronous = 0;\n" // disable fsync()s - this cache is disposable across a machine crash
Packit Service 97d2fb
  "pragma journal_mode = wal;\n" // https://sqlite.org/wal.html
Packit Service 97d2fb
  "pragma wal_checkpoint = truncate;\n" // clean out any preexisting wal file
Packit Service 97d2fb
  "pragma journal_size_limit = 0;\n" // limit steady state file (between grooming, which also =truncate's)
Packit Service 97d2fb
  "pragma auto_vacuum = incremental;\n" // https://sqlite.org/pragma.html
Packit Service 97d2fb
  "pragma busy_timeout = 1000;\n" // https://sqlite.org/pragma.html
Packit Service 97d2fb
  // NB: all these are overridable with -D option
Packit Service 97d2fb
Packit Service 97d2fb
  // Normalization table for interning file names
Packit Service 97d2fb
  "create table if not exists " BUILDIDS "_files (\n"
Packit Service 97d2fb
  "        id integer primary key not null,\n"
Packit Service 97d2fb
  "        name text unique not null\n"
Packit Service 97d2fb
  "        );\n"
Packit Service 97d2fb
  // Normalization table for interning buildids
Packit Service 97d2fb
  "create table if not exists " BUILDIDS "_buildids (\n"
Packit Service 97d2fb
  "        id integer primary key not null,\n"
Packit Service 97d2fb
  "        hex text unique not null);\n"
Packit Service 97d2fb
  // Track the completion of scanning of a given file & sourcetype at given time
Packit Service 97d2fb
  "create table if not exists " BUILDIDS "_file_mtime_scanned (\n"
Packit Service 97d2fb
  "        mtime integer not null,\n"
Packit Service 97d2fb
  "        file integer not null,\n"
Packit Service 97d2fb
  "        size integer not null,\n" // in bytes
Packit Service 97d2fb
  "        sourcetype text(1) not null\n"
Packit Service 97d2fb
  "            check (sourcetype IN ('F', 'R')),\n"
Packit Service 97d2fb
  "        foreign key (file) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n"
Packit Service 97d2fb
  "        primary key (file, mtime, sourcetype)\n"
Packit Service 97d2fb
  "        ) " WITHOUT_ROWID ";\n"
Packit Service 97d2fb
  "create table if not exists " BUILDIDS "_f_de (\n"
Packit Service 97d2fb
  "        buildid integer not null,\n"
Packit Service 97d2fb
  "        debuginfo_p integer not null,\n"
Packit Service 97d2fb
  "        executable_p integer not null,\n"
Packit Service 97d2fb
  "        file integer not null,\n"
Packit Service 97d2fb
  "        mtime integer not null,\n"
Packit Service 97d2fb
  "        foreign key (file) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n"
Packit Service 97d2fb
  "        foreign key (buildid) references " BUILDIDS "_buildids(id) on update cascade on delete cascade,\n"
Packit Service 97d2fb
  "        primary key (buildid, file, mtime)\n"
Packit Service 97d2fb
  "        ) " WITHOUT_ROWID ";\n"
Packit Service 97d2fb
  "create table if not exists " BUILDIDS "_f_s (\n"
Packit Service 97d2fb
  "        buildid integer not null,\n"
Packit Service 97d2fb
  "        artifactsrc integer not null,\n"
Packit Service 97d2fb
  "        file integer not null,\n" // NB: not necessarily entered into _mtime_scanned
Packit Service 97d2fb
  "        mtime integer not null,\n"
Packit Service 97d2fb
  "        foreign key (file) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n"
Packit Service 97d2fb
  "        foreign key (artifactsrc) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n"
Packit Service 97d2fb
  "        foreign key (buildid) references " BUILDIDS "_buildids(id) on update cascade on delete cascade,\n"
Packit Service 97d2fb
  "        primary key (buildid, artifactsrc, file, mtime)\n"
Packit Service 97d2fb
  "        ) " WITHOUT_ROWID ";\n"
Packit Service 97d2fb
  "create table if not exists " BUILDIDS "_r_de (\n"
Packit Service 97d2fb
  "        buildid integer not null,\n"
Packit Service 97d2fb
  "        debuginfo_p integer not null,\n"
Packit Service 97d2fb
  "        executable_p integer not null,\n"
Packit Service 97d2fb
  "        file integer not null,\n"
Packit Service 97d2fb
  "        mtime integer not null,\n"
Packit Service 97d2fb
  "        content integer not null,\n"
Packit Service 97d2fb
  "        foreign key (file) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n"
Packit Service 97d2fb
  "        foreign key (content) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n"
Packit Service 97d2fb
  "        foreign key (buildid) references " BUILDIDS "_buildids(id) on update cascade on delete cascade,\n"
Packit Service 97d2fb
  "        primary key (buildid, debuginfo_p, executable_p, file, content, mtime)\n"
Packit Service 97d2fb
  "        ) " WITHOUT_ROWID ";\n"
Packit Service 97d2fb
  "create table if not exists " BUILDIDS "_r_sref (\n" // outgoing dwarf sourcefile references from rpm
Packit Service 97d2fb
  "        buildid integer not null,\n"
Packit Service 97d2fb
  "        artifactsrc integer not null,\n"
Packit Service 97d2fb
  "        foreign key (artifactsrc) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n"
Packit Service 97d2fb
  "        foreign key (buildid) references " BUILDIDS "_buildids(id) on update cascade on delete cascade,\n"
Packit Service 97d2fb
  "        primary key (buildid, artifactsrc)\n"
Packit Service 97d2fb
  "        ) " WITHOUT_ROWID ";\n"
Packit Service 97d2fb
  "create table if not exists " BUILDIDS "_r_sdef (\n" // rpm contents that may satisfy sref
Packit Service 97d2fb
  "        file integer not null,\n"
Packit Service 97d2fb
  "        mtime integer not null,\n"
Packit Service 97d2fb
  "        content integer not null,\n"
Packit Service 97d2fb
  "        foreign key (file) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n"
Packit Service 97d2fb
  "        foreign key (content) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n"
Packit Service 97d2fb
  "        primary key (content, file, mtime)\n"
Packit Service 97d2fb
  "        ) " WITHOUT_ROWID ";\n"
Packit Service 97d2fb
  // create views to glue together some of the above tables, for webapi D queries
Packit Service 97d2fb
  "create view if not exists " BUILDIDS "_query_d as \n"
Packit Service 97d2fb
  "select\n"
Packit Service 97d2fb
  "        b.hex as buildid, n.mtime, 'F' as sourcetype, f0.name as source0, n.mtime as mtime, null as source1\n"
Packit Service 97d2fb
  "        from " BUILDIDS "_buildids b, " BUILDIDS "_files f0, " BUILDIDS "_f_de n\n"
Packit Service 97d2fb
  "        where b.id = n.buildid and f0.id = n.file and n.debuginfo_p = 1\n"
Packit Service 97d2fb
  "union all select\n"
Packit Service 97d2fb
  "        b.hex as buildid, n.mtime, 'R' as sourcetype, f0.name as source0, n.mtime as mtime, f1.name as source1\n"
Packit Service 97d2fb
  "        from " BUILDIDS "_buildids b, " BUILDIDS "_files f0, " BUILDIDS "_files f1, " BUILDIDS "_r_de n\n"
Packit Service 97d2fb
  "        where b.id = n.buildid and f0.id = n.file and f1.id = n.content and n.debuginfo_p = 1\n"
Packit Service 97d2fb
  ";"
Packit Service 97d2fb
  // ... and for E queries
Packit Service 97d2fb
  "create view if not exists " BUILDIDS "_query_e as \n"
Packit Service 97d2fb
  "select\n"
Packit Service 97d2fb
  "        b.hex as buildid, n.mtime, 'F' as sourcetype, f0.name as source0, n.mtime as mtime, null as source1\n"
Packit Service 97d2fb
  "        from " BUILDIDS "_buildids b, " BUILDIDS "_files f0, " BUILDIDS "_f_de n\n"
Packit Service 97d2fb
  "        where b.id = n.buildid and f0.id = n.file and n.executable_p = 1\n"
Packit Service 97d2fb
  "union all select\n"
Packit Service 97d2fb
  "        b.hex as buildid, n.mtime, 'R' as sourcetype, f0.name as source0, n.mtime as mtime, f1.name as source1\n"
Packit Service 97d2fb
  "        from " BUILDIDS "_buildids b, " BUILDIDS "_files f0, " BUILDIDS "_files f1, " BUILDIDS "_r_de n\n"
Packit Service 97d2fb
  "        where b.id = n.buildid and f0.id = n.file and f1.id = n.content and n.executable_p = 1\n"
Packit Service 97d2fb
  ";"
Packit Service 97d2fb
  // ... and for S queries
Packit Service 97d2fb
  "create view if not exists " BUILDIDS "_query_s as \n"
Packit Service 97d2fb
  "select\n"
Packit Service 97d2fb
  "        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 Service 97d2fb
  "        from " BUILDIDS "_buildids b, " BUILDIDS "_files f0, " BUILDIDS "_files fs, " BUILDIDS "_f_s n\n"
Packit Service 97d2fb
  "        where b.id = n.buildid and f0.id = n.file and fs.id = n.artifactsrc\n"
Packit Service 97d2fb
  "union all select\n"
Packit Service 97d2fb
  "        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 Service 97d2fb
  "        from " BUILDIDS "_buildids b, " BUILDIDS "_files f0, " BUILDIDS "_files f1, " BUILDIDS "_files fsref, "
Packit Service 97d2fb
  "        " BUILDIDS "_r_sdef sd, " BUILDIDS "_r_sref sr, " BUILDIDS "_r_de sde\n"
Packit Service 97d2fb
  "        where b.id = sr.buildid and f0.id = sd.file and fsref.id = sde.file and f1.id = sd.content\n"
Packit Service 97d2fb
  "        and sr.artifactsrc = sd.content and sde.buildid = sr.buildid\n"
Packit Service 97d2fb
  ";"
Packit Service 97d2fb
  // and for startup overview counts
Packit Service 97d2fb
  "drop view if exists " BUILDIDS "_stats;\n"
Packit Service 97d2fb
  "create view if not exists " BUILDIDS "_stats as\n"
Packit Service 97d2fb
  "          select 'file d/e' as label,count(*) as quantity from " BUILDIDS "_f_de\n"
Packit Service 97d2fb
  "union all select 'file s',count(*) from " BUILDIDS "_f_s\n"
Packit Service 97d2fb
  "union all select 'archive d/e',count(*) from " BUILDIDS "_r_de\n"
Packit Service 97d2fb
  "union all select 'archive sref',count(*) from " BUILDIDS "_r_sref\n"
Packit Service 97d2fb
  "union all select 'archive sdef',count(*) from " BUILDIDS "_r_sdef\n"
Packit Service 97d2fb
  "union all select 'buildids',count(*) from " BUILDIDS "_buildids\n"
Packit Service 97d2fb
  "union all select 'filenames',count(*) from " BUILDIDS "_files\n"
Packit Service 97d2fb
  "union all select 'files scanned (#)',count(*) from " BUILDIDS "_file_mtime_scanned\n"
Packit Service 97d2fb
  "union all select 'files scanned (mb)',coalesce(sum(size)/1024/1024,0) from " BUILDIDS "_file_mtime_scanned\n"
Packit Service 97d2fb
#if SQLITE_VERSION_NUMBER >= 3016000
Packit Service 97d2fb
  "union all select 'index db size (mb)',page_count*page_size/1024/1024 as size FROM pragma_page_count(), pragma_page_size()\n"
Packit Service 97d2fb
#endif
Packit Service 97d2fb
  ";\n"
Packit Service 97d2fb
Packit Service 97d2fb
// schema change history & garbage collection
Packit Service 97d2fb
//
Packit Service 97d2fb
// XXX: we could have migration queries here to bring prior-schema
Packit Service 97d2fb
// data over instead of just dropping it.
Packit Service 97d2fb
//
Packit Service 97d2fb
// buildids9: widen the mtime_scanned table
Packit Service 97d2fb
  "" // <<< we are here
Packit Service 97d2fb
// buildids8: slim the sref table
Packit Service 97d2fb
  "drop table if exists buildids8_f_de;\n"
Packit Service 97d2fb
  "drop table if exists buildids8_f_s;\n"
Packit Service 97d2fb
  "drop table if exists buildids8_r_de;\n"
Packit Service 97d2fb
  "drop table if exists buildids8_r_sref;\n"
Packit Service 97d2fb
  "drop table if exists buildids8_r_sdef;\n"
Packit Service 97d2fb
  "drop table if exists buildids8_file_mtime_scanned;\n"
Packit Service 97d2fb
  "drop table if exists buildids8_files;\n"
Packit Service 97d2fb
  "drop table if exists buildids8_buildids;\n"
Packit Service 97d2fb
// buildids7: separate _norm table into dense subtype tables
Packit Service 97d2fb
  "drop table if exists buildids7_f_de;\n"
Packit Service 97d2fb
  "drop table if exists buildids7_f_s;\n"
Packit Service 97d2fb
  "drop table if exists buildids7_r_de;\n"
Packit Service 97d2fb
  "drop table if exists buildids7_r_sref;\n"
Packit Service 97d2fb
  "drop table if exists buildids7_r_sdef;\n"
Packit Service 97d2fb
  "drop table if exists buildids7_file_mtime_scanned;\n"
Packit Service 97d2fb
  "drop table if exists buildids7_files;\n"
Packit Service 97d2fb
  "drop table if exists buildids7_buildids;\n"
Packit Service 97d2fb
// buildids6: drop bolo/rfolo again, represent sources / rpmcontents in main table
Packit Service 97d2fb
  "drop table if exists buildids6_norm;\n"
Packit Service 97d2fb
  "drop table if exists buildids6_files;\n"
Packit Service 97d2fb
  "drop table if exists buildids6_buildids;\n"
Packit Service 97d2fb
  "drop view if exists buildids6;\n"
Packit Service 97d2fb
// buildids5: redefine srcfile1 column to be '.'-less (for rpms)
Packit Service 97d2fb
  "drop table if exists buildids5_norm;\n"
Packit Service 97d2fb
  "drop table if exists buildids5_files;\n"
Packit Service 97d2fb
  "drop table if exists buildids5_buildids;\n"
Packit Service 97d2fb
  "drop table if exists buildids5_bolo;\n"
Packit Service 97d2fb
  "drop table if exists buildids5_rfolo;\n"
Packit Service 97d2fb
  "drop view if exists buildids5;\n"
Packit Service 97d2fb
// buildids4: introduce rpmfile RFOLO
Packit Service 97d2fb
  "drop table if exists buildids4_norm;\n"
Packit Service 97d2fb
  "drop table if exists buildids4_files;\n"
Packit Service 97d2fb
  "drop table if exists buildids4_buildids;\n"
Packit Service 97d2fb
  "drop table if exists buildids4_bolo;\n"
Packit Service 97d2fb
  "drop table if exists buildids4_rfolo;\n"
Packit Service 97d2fb
  "drop view if exists buildids4;\n"
Packit Service 97d2fb
// buildids3*: split out srcfile BOLO
Packit Service 97d2fb
  "drop table if exists buildids3_norm;\n"
Packit Service 97d2fb
  "drop table if exists buildids3_files;\n"
Packit Service 97d2fb
  "drop table if exists buildids3_buildids;\n"
Packit Service 97d2fb
  "drop table if exists buildids3_bolo;\n"
Packit Service 97d2fb
  "drop view if exists buildids3;\n"
Packit Service 97d2fb
// buildids2: normalized buildid and filenames into interning tables;
Packit Service 97d2fb
  "drop table if exists buildids2_norm;\n"
Packit Service 97d2fb
  "drop table if exists buildids2_files;\n"
Packit Service 97d2fb
  "drop table if exists buildids2_buildids;\n"
Packit Service 97d2fb
  "drop view if exists buildids2;\n"
Packit Service 97d2fb
  // buildids1: made buildid and artifacttype NULLable, to represent cached-negative
Packit Service 97d2fb
//           lookups from sources, e.g. files or rpms that contain no buildid-indexable content
Packit Service 97d2fb
  "drop table if exists buildids1;\n"
Packit Service 97d2fb
// buildids: original
Packit Service 97d2fb
  "drop table if exists buildids;\n"
Packit Service 97d2fb
  ;
Packit Service 97d2fb
Packit Service 97d2fb
static const char DEBUGINFOD_SQLITE_CLEANUP_DDL[] =
Packit Service 97d2fb
  "pragma wal_checkpoint = truncate;\n" // clean out any preexisting wal file
Packit Service 97d2fb
  ;
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
/* Name and version of program.  */
Packit Service 97d2fb
/* ARGP_PROGRAM_VERSION_HOOK_DEF = print_version; */ // not this simple for C++
Packit Service 97d2fb
Packit Service 97d2fb
/* Bug report address.  */
Packit Service 97d2fb
ARGP_PROGRAM_BUG_ADDRESS_DEF = PACKAGE_BUGREPORT;
Packit Service 97d2fb
Packit Service 97d2fb
/* Definitions of arguments for argp functions.  */
Packit Service 97d2fb
static const struct argp_option options[] =
Packit Service 97d2fb
  {
Packit Service 97d2fb
   { NULL, 0, NULL, 0, "Scanners:", 1 },
Packit Service 97d2fb
   { "scan-file-dir", 'F', NULL, 0, "Enable ELF/DWARF file scanning.", 0 },
Packit Service 97d2fb
   { "scan-rpm-dir", 'R', NULL, 0, "Enable RPM scanning.", 0 },
Packit Service 97d2fb
   { "scan-deb-dir", 'U', NULL, 0, "Enable DEB scanning.", 0 },
Packit Service 97d2fb
   { "scan-archive", 'Z', "EXT=CMD", 0, "Enable arbitrary archive scanning.", 0 },
Packit Service 97d2fb
   // "source-oci-imageregistry"  ...
Packit Service 97d2fb
Packit Service 97d2fb
   { NULL, 0, NULL, 0, "Options:", 2 },
Packit Service 97d2fb
   { "logical", 'L', NULL, 0, "Follow symlinks, default=ignore.", 0 },
Packit Service 97d2fb
   { "rescan-time", 't', "SECONDS", 0, "Number of seconds to wait between rescans, 0=disable.", 0 },
Packit Service 97d2fb
   { "groom-time", 'g', "SECONDS", 0, "Number of seconds to wait between database grooming, 0=disable.", 0 },
Packit Service 97d2fb
   { "maxigroom", 'G', NULL, 0, "Run a complete database groom/shrink pass at startup.", 0 },
Packit Service 97d2fb
   { "concurrency", 'c', "NUM", 0, "Limit scanning thread concurrency to NUM.", 0 },
Packit Service 97d2fb
   { "include", 'I', "REGEX", 0, "Include files matching REGEX, default=all.", 0 },
Packit Service 97d2fb
   { "exclude", 'X', "REGEX", 0, "Exclude files matching REGEX, default=none.", 0 },
Packit Service 97d2fb
   { "port", 'p', "NUM", 0, "HTTP port to listen on, default 8002.", 0 },
Packit Service 97d2fb
   { "database", 'd', "FILE", 0, "Path to sqlite database.", 0 },
Packit Service 97d2fb
   { "ddl", 'D', "SQL", 0, "Apply extra sqlite ddl/pragma to connection.", 0 },
Packit Service 97d2fb
   { "verbose", 'v', NULL, 0, "Increase verbosity.", 0 },
Packit Service 97d2fb
#define ARGP_KEY_FDCACHE_FDS 0x1001
Packit Service 97d2fb
   { "fdcache-fds", ARGP_KEY_FDCACHE_FDS, "NUM", 0, "Maximum number of archive files to keep in fdcache.", 0 },
Packit Service 97d2fb
#define ARGP_KEY_FDCACHE_MBS 0x1002
Packit Service 97d2fb
   { "fdcache-mbs", ARGP_KEY_FDCACHE_MBS, "MB", 0, "Maximum total size of archive file fdcache.", 0 },
Packit Service 97d2fb
#define ARGP_KEY_FDCACHE_PREFETCH 0x1003
Packit Service 97d2fb
   { "fdcache-prefetch", ARGP_KEY_FDCACHE_PREFETCH, "NUM", 0, "Number of archive files to prefetch into fdcache.", 0 },
Packit Service 97d2fb
   { NULL, 0, NULL, 0, NULL, 0 }
Packit Service 97d2fb
  };
Packit Service 97d2fb
Packit Service 97d2fb
/* Short description of program.  */
Packit Service 97d2fb
static const char doc[] = "Serve debuginfo-related content across HTTP from files under PATHs.";
Packit Service 97d2fb
Packit Service 97d2fb
/* Strings for arguments in help texts.  */
Packit Service 97d2fb
static const char args_doc[] = "[PATH ...]";
Packit Service 97d2fb
Packit Service 97d2fb
/* Prototype for option handler.  */
Packit Service 97d2fb
static error_t parse_opt (int key, char *arg, struct argp_state *state);
Packit Service 97d2fb
Packit Service 97d2fb
/* Data structure to communicate with argp functions.  */
Packit Service 97d2fb
static struct argp argp =
Packit Service 97d2fb
  {
Packit Service 97d2fb
   options, parse_opt, args_doc, doc, NULL, NULL, NULL
Packit Service 97d2fb
  };
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
static string db_path;
Packit Service 97d2fb
static sqlite3 *db; // single connection, serialized across all our threads!
Packit Service 97d2fb
static unsigned verbose;
Packit Service 97d2fb
static volatile sig_atomic_t interrupted = 0;
Packit Service 97d2fb
static volatile sig_atomic_t forced_rescan_count = 0;
Packit Service 97d2fb
static volatile sig_atomic_t sigusr1 = 0;
Packit Service 97d2fb
static volatile sig_atomic_t forced_groom_count = 0;
Packit Service 97d2fb
static volatile sig_atomic_t sigusr2 = 0;
Packit Service 97d2fb
static unsigned http_port = 8002;
Packit Service 97d2fb
static unsigned rescan_s = 300;
Packit Service 97d2fb
static unsigned groom_s = 86400;
Packit Service 97d2fb
static bool maxigroom = false;
Packit Service 97d2fb
static unsigned concurrency = std::thread::hardware_concurrency() ?: 1;
Packit Service 97d2fb
static set<string> source_paths;
Packit Service 97d2fb
static bool scan_files = false;
Packit Service 97d2fb
static map<string,string> scan_archives;
Packit Service 97d2fb
static vector<string> extra_ddl;
Packit Service 97d2fb
static regex_t file_include_regex;
Packit Service 97d2fb
static regex_t file_exclude_regex;
Packit Service 97d2fb
static bool traverse_logical;
Packit Service 97d2fb
static long fdcache_fds;
Packit Service 97d2fb
static long fdcache_mbs;
Packit Service 97d2fb
static long fdcache_prefetch;
Packit Service 97d2fb
static string tmpdir;
Packit Service 97d2fb
Packit Service 97d2fb
static void set_metric(const string& key, int64_t value);
Packit Service 97d2fb
// static void inc_metric(const string& key);
Packit Service 97d2fb
static void set_metric(const string& metric,
Packit Service 97d2fb
                       const string& lname, const string& lvalue,
Packit Service 97d2fb
                       int64_t value);
Packit Service 97d2fb
static void inc_metric(const string& metric,
Packit Service 97d2fb
                       const string& lname, const string& lvalue);
Packit Service 97d2fb
static void add_metric(const string& metric,
Packit Service 97d2fb
                       const string& lname, const string& lvalue,
Packit Service 97d2fb
                       int64_t value);
Packit Service 97d2fb
// static void add_metric(const string& metric, int64_t value);
Packit Service 97d2fb
Packit Service 97d2fb
/* Handle program arguments.  */
Packit Service 97d2fb
static error_t
Packit Service 97d2fb
parse_opt (int key, char *arg,
Packit Service 97d2fb
	   struct argp_state *state __attribute__ ((unused)))
Packit Service 97d2fb
{
Packit Service 97d2fb
  int rc;
Packit Service 97d2fb
  switch (key)
Packit Service 97d2fb
    {
Packit Service 97d2fb
    case 'v': verbose ++; break;
Packit Service 97d2fb
    case 'd': db_path = string(arg); break;
Packit Service 97d2fb
    case 'p': http_port = (unsigned) atoi(arg);
Packit Service 97d2fb
      if (http_port == 0 || http_port > 65535)
Packit Service 97d2fb
        argp_failure(state, 1, EINVAL, "port number");
Packit Service 97d2fb
      break;
Packit Service 97d2fb
    case 'F': scan_files = true; break;
Packit Service 97d2fb
    case 'R':
Packit Service 97d2fb
      scan_archives[".rpm"]="cat"; // libarchive groks rpm natively
Packit Service 97d2fb
      break;
Packit Service 97d2fb
    case 'U':
Packit Service 97d2fb
      if (access("/usr/bin/dpkg-deb", X_OK) == 0)
Packit Service 97d2fb
        {
Packit Service 97d2fb
          scan_archives[".deb"]="dpkg-deb --fsys-tarfile";
Packit Service 97d2fb
          scan_archives[".ddeb"]="dpkg-deb --fsys-tarfile";
Packit Service 97d2fb
        }
Packit Service 97d2fb
      else
Packit Service 97d2fb
        {
Packit Service 97d2fb
          scan_archives[".deb"]="(bsdtar -O -x -f - data.tar.xz)<";
Packit Service 97d2fb
          scan_archives[".ddeb"]="(bsdtar -O -x -f - data.tar.xz)<";
Packit Service 97d2fb
        }
Packit Service 97d2fb
      // .udeb too?
Packit Service 97d2fb
      break;
Packit Service 97d2fb
    case 'Z':
Packit Service 97d2fb
      {
Packit Service 97d2fb
        char* extension = strchr(arg, '=');
Packit Service 97d2fb
        if (arg[0] == '\0')
Packit Service 97d2fb
          argp_failure(state, 1, EINVAL, "missing EXT");
Packit Service 97d2fb
        else if (extension)
Packit Service 97d2fb
          scan_archives[string(arg, (extension-arg))]=string(extension+1);
Packit Service 97d2fb
        else
Packit Service 97d2fb
          scan_archives[string(arg)]=string("cat");
Packit Service 97d2fb
      }
Packit Service 97d2fb
      break;
Packit Service 97d2fb
    case 'L':
Packit Service 97d2fb
      traverse_logical = true;
Packit Service 97d2fb
      break;
Packit Service 97d2fb
    case 'D': extra_ddl.push_back(string(arg)); break;
Packit Service 97d2fb
    case 't':
Packit Service 97d2fb
      rescan_s = (unsigned) atoi(arg);
Packit Service 97d2fb
      break;
Packit Service 97d2fb
    case 'g':
Packit Service 97d2fb
      groom_s = (unsigned) atoi(arg);
Packit Service 97d2fb
      break;
Packit Service 97d2fb
    case 'G':
Packit Service 97d2fb
      maxigroom = true;
Packit Service 97d2fb
      break;
Packit Service 97d2fb
    case 'c':
Packit Service 97d2fb
      concurrency = (unsigned) atoi(arg);
Packit Service 97d2fb
      if (concurrency < 1) concurrency = 1;
Packit Service 97d2fb
      break;
Packit Service 97d2fb
    case 'I':
Packit Service 97d2fb
      // NB: no problem with unconditional free here - an earlier failed regcomp would exit program
Packit Service 97d2fb
      regfree (&file_include_regex);
Packit Service 97d2fb
      rc = regcomp (&file_include_regex, arg, REG_EXTENDED|REG_NOSUB);
Packit Service 97d2fb
      if (rc != 0)
Packit Service 97d2fb
        argp_failure(state, 1, EINVAL, "regular expession");
Packit Service 97d2fb
      break;
Packit Service 97d2fb
    case 'X':
Packit Service 97d2fb
      regfree (&file_exclude_regex);
Packit Service 97d2fb
      rc = regcomp (&file_exclude_regex, arg, REG_EXTENDED|REG_NOSUB);
Packit Service 97d2fb
      if (rc != 0)
Packit Service 97d2fb
        argp_failure(state, 1, EINVAL, "regular expession");
Packit Service 97d2fb
      break;
Packit Service 97d2fb
    case ARGP_KEY_FDCACHE_FDS:
Packit Service 97d2fb
      fdcache_fds = atol (arg);
Packit Service 97d2fb
      break;
Packit Service 97d2fb
    case ARGP_KEY_FDCACHE_MBS:
Packit Service 97d2fb
      fdcache_mbs = atol (arg);
Packit Service 97d2fb
      break;
Packit Service 97d2fb
    case ARGP_KEY_FDCACHE_PREFETCH:
Packit Service 97d2fb
      fdcache_prefetch = atol (arg);
Packit Service 97d2fb
      break;
Packit Service 97d2fb
    case ARGP_KEY_ARG:
Packit Service 97d2fb
      source_paths.insert(string(arg));
Packit Service 97d2fb
      break;
Packit Service 97d2fb
      // case 'h': argp_state_help (state, stderr, ARGP_HELP_LONG|ARGP_HELP_EXIT_OK);
Packit Service 97d2fb
    default: return ARGP_ERR_UNKNOWN;
Packit Service 97d2fb
    }
Packit Service 97d2fb
Packit Service 97d2fb
  return 0;
Packit Service 97d2fb
}
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
////////////////////////////////////////////////////////////////////////
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
// represent errors that may get reported to an ostream and/or a libmicrohttpd connection
Packit Service 97d2fb
Packit Service 97d2fb
struct reportable_exception
Packit Service 97d2fb
{
Packit Service 97d2fb
  int code;
Packit Service 97d2fb
  string message;
Packit Service 97d2fb
Packit Service 97d2fb
  reportable_exception(int c, const string& m): code(c), message(m) {}
Packit Service 97d2fb
  reportable_exception(const string& m): code(503), message(m) {}
Packit Service 97d2fb
  reportable_exception(): code(503), message() {}
Packit Service 97d2fb
Packit Service 97d2fb
  void report(ostream& o) const; // defined under obatched() class below
Packit Service 97d2fb
Packit Service 97d2fb
  MHD_RESULT mhd_send_response(MHD_Connection* c) const {
Packit Service 97d2fb
    MHD_Response* r = MHD_create_response_from_buffer (message.size(),
Packit Service 97d2fb
                                                       (void*) message.c_str(),
Packit Service 97d2fb
                                                       MHD_RESPMEM_MUST_COPY);
Packit Service 97d2fb
    MHD_add_response_header (r, "Content-Type", "text/plain");
Packit Service 97d2fb
    MHD_RESULT rc = MHD_queue_response (c, code, r);
Packit Service 97d2fb
    MHD_destroy_response (r);
Packit Service 97d2fb
    return rc;
Packit Service 97d2fb
  }
Packit Service 97d2fb
};
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
struct sqlite_exception: public reportable_exception
Packit Service 97d2fb
{
Packit Service 97d2fb
  sqlite_exception(int rc, const string& msg):
Packit Service 97d2fb
    reportable_exception(string("sqlite3 error: ") + msg + ": " + string(sqlite3_errstr(rc) ?: "?")) {}
Packit Service 97d2fb
};
Packit Service 97d2fb
Packit Service 97d2fb
struct libc_exception: public reportable_exception
Packit Service 97d2fb
{
Packit Service 97d2fb
  libc_exception(int rc, const string& msg):
Packit Service 97d2fb
    reportable_exception(string("libc error: ") + msg + ": " + string(strerror(rc) ?: "?")) {
Packit Service 97d2fb
    inc_metric("error_count","libc",strerror(rc));
Packit Service 97d2fb
  }
Packit Service 97d2fb
};
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
struct archive_exception: public reportable_exception
Packit Service 97d2fb
{
Packit Service 97d2fb
  archive_exception(const string& msg):
Packit Service 97d2fb
    reportable_exception(string("libarchive error: ") + msg) {
Packit Service 97d2fb
      inc_metric("error_count","libarchive",msg);
Packit Service 97d2fb
  }
Packit Service 97d2fb
  archive_exception(struct archive* a, const string& msg):
Packit Service 97d2fb
    reportable_exception(string("libarchive error: ") + msg + ": " + string(archive_error_string(a) ?: "?")) {
Packit Service 97d2fb
    inc_metric("error_count","libarchive",msg);
Packit Service 97d2fb
  }
Packit Service 97d2fb
};
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
struct elfutils_exception: public reportable_exception
Packit Service 97d2fb
{
Packit Service 97d2fb
  elfutils_exception(int rc, const string& msg):
Packit Service 97d2fb
    reportable_exception(string("elfutils error: ") + msg + ": " + string(elf_errmsg(rc) ?: "?")) {
Packit Service 97d2fb
    inc_metric("error_count","elfutils",elf_errmsg(rc));
Packit Service 97d2fb
  }
Packit Service 97d2fb
};
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
////////////////////////////////////////////////////////////////////////
Packit Service 97d2fb
Packit Service 97d2fb
template <typename Payload>
Packit Service 97d2fb
class workq
Packit Service 97d2fb
{
Packit Service 97d2fb
  set<Payload> q; // eliminate duplicates
Packit Service 97d2fb
  mutex mtx;
Packit Service 97d2fb
  condition_variable cv;
Packit Service 97d2fb
  bool dead;
Packit Service 97d2fb
  unsigned idlers;
Packit Service 97d2fb
Packit Service 97d2fb
public:
Packit Service 97d2fb
  workq() { dead = false; idlers = 0; }
Packit Service 97d2fb
  ~workq() {}
Packit Service 97d2fb
Packit Service 97d2fb
  void push_back(const Payload& p)
Packit Service 97d2fb
  {
Packit Service 97d2fb
    unique_lock<mutex> lock(mtx);
Packit Service 97d2fb
    q.insert (p);
Packit Service 97d2fb
    set_metric("thread_work_pending","role","scan", q.size());
Packit Service 97d2fb
    cv.notify_all();
Packit Service 97d2fb
  }
Packit Service 97d2fb
Packit Service 97d2fb
  // kill this workqueue, wake up all idlers / scanners
Packit Service 97d2fb
  void nuke() {
Packit Service 97d2fb
    unique_lock<mutex> lock(mtx);
Packit Service 97d2fb
    // optional: q.clear();
Packit Service 97d2fb
    dead = true;
Packit Service 97d2fb
    cv.notify_all();
Packit Service 97d2fb
  }
Packit Service 97d2fb
Packit Service 97d2fb
  // clear the workqueue, when scanning is interrupted with USR2
Packit Service 97d2fb
  void clear() {
Packit Service 97d2fb
    unique_lock<mutex> lock(mtx);
Packit Service 97d2fb
    q.clear();
Packit Service 97d2fb
    set_metric("thread_work_pending","role","scan", q.size());
Packit Service 97d2fb
    cv.notify_all(); // maybe wake up waiting idlers
Packit Service 97d2fb
  }
Packit Service 97d2fb
Packit Service 97d2fb
  // block this scanner thread until there is work to do and no active
Packit Service 97d2fb
  bool wait_front (Payload& p)
Packit Service 97d2fb
  {
Packit Service 97d2fb
    unique_lock<mutex> lock(mtx);
Packit Service 97d2fb
    while (!dead && (q.size() == 0 || idlers > 0))
Packit Service 97d2fb
      cv.wait(lock);
Packit Service 97d2fb
    if (dead)
Packit Service 97d2fb
      return false;
Packit Service 97d2fb
    else
Packit Service 97d2fb
      {
Packit Service 97d2fb
        p = * q.begin();
Packit Service 97d2fb
        q.erase (q.begin());
Packit Service 97d2fb
        set_metric("thread_work_pending","role","scan", q.size());
Packit Service 97d2fb
        if (q.size() == 0)
Packit Service 97d2fb
          cv.notify_all(); // maybe wake up waiting idlers
Packit Service 97d2fb
        return true;
Packit Service 97d2fb
      }
Packit Service 97d2fb
  }
Packit Service 97d2fb
Packit Service 97d2fb
  // block this idler thread until there is no work to do
Packit Service 97d2fb
  void wait_idle ()
Packit Service 97d2fb
  {
Packit Service 97d2fb
    unique_lock<mutex> lock(mtx);
Packit Service 97d2fb
    cv.notify_all(); // maybe wake up waiting scanners
Packit Service 97d2fb
    while (!dead && (q.size() != 0))
Packit Service 97d2fb
      cv.wait(lock);
Packit Service 97d2fb
    idlers ++;
Packit Service 97d2fb
  }
Packit Service 97d2fb
Packit Service 97d2fb
  void done_idle ()
Packit Service 97d2fb
  {
Packit Service 97d2fb
    unique_lock<mutex> lock(mtx);
Packit Service 97d2fb
    idlers --;
Packit Service 97d2fb
    cv.notify_all(); // maybe wake up waiting scanners, but probably not (shutting down)
Packit Service 97d2fb
  }
Packit Service 97d2fb
};
Packit Service 97d2fb
Packit Service 97d2fb
typedef struct stat stat_t;
Packit Service 97d2fb
typedef pair<string,stat_t> scan_payload;
Packit Service 97d2fb
inline bool operator< (const scan_payload& a, const scan_payload& b)
Packit Service 97d2fb
{
Packit Service 97d2fb
  return a.first < b.first; // don't bother compare the stat fields
Packit Service 97d2fb
}
Packit Service 97d2fb
static workq<scan_payload> scanq; // just a single one
Packit Service 97d2fb
// producer & idler: thread_main_fts_source_paths()
Packit Service 97d2fb
// consumer: thread_main_scanner()
Packit Service 97d2fb
// idler: thread_main_groom()
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
////////////////////////////////////////////////////////////////////////
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
// Print a standard timestamp.
Packit Service 97d2fb
static ostream&
Packit Service 97d2fb
timestamp (ostream &o)
Packit Service 97d2fb
{
Packit Service 97d2fb
  char datebuf[80];
Packit Service 97d2fb
  char *now2 = NULL;
Packit Service 97d2fb
  time_t now_t = time(NULL);
Packit Service 97d2fb
  struct tm *now = gmtime (&now_t);
Packit Service 97d2fb
  if (now)
Packit Service 97d2fb
    {
Packit Service 97d2fb
      (void) strftime (datebuf, sizeof (datebuf), "%c", now);
Packit Service 97d2fb
      now2 = datebuf;
Packit Service 97d2fb
    }
Packit Service 97d2fb
Packit Service 97d2fb
  return o << "[" << (now2 ? now2 : "") << "] "
Packit Service 97d2fb
           << "(" << getpid () << "/" << tid() << "): ";
Packit Service 97d2fb
}
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
// A little class that impersonates an ostream to the extent that it can
Packit Service 97d2fb
// take << streaming operations.  It batches up the bits into an internal
Packit Service 97d2fb
// stringstream until it is destroyed; then flushes to the original ostream.
Packit Service 97d2fb
// It adds a timestamp
Packit Service 97d2fb
class obatched
Packit Service 97d2fb
{
Packit Service 97d2fb
private:
Packit Service 97d2fb
  ostream& o;
Packit Service 97d2fb
  stringstream stro;
Packit Service 97d2fb
  static mutex lock;
Packit Service 97d2fb
public:
Packit Service 97d2fb
  obatched(ostream& oo, bool timestamp_p = true): o(oo)
Packit Service 97d2fb
  {
Packit Service 97d2fb
    if (timestamp_p)
Packit Service 97d2fb
      timestamp(stro);
Packit Service 97d2fb
  }
Packit Service 97d2fb
  ~obatched()
Packit Service 97d2fb
  {
Packit Service 97d2fb
    unique_lock<mutex> do_not_cross_the_streams(obatched::lock);
Packit Service 97d2fb
    o << stro.str();
Packit Service 97d2fb
    o.flush();
Packit Service 97d2fb
  }
Packit Service 97d2fb
  operator ostream& () { return stro; }
Packit Service 97d2fb
  template <typename T> ostream& operator << (const T& t) { stro << t; return stro; }
Packit Service 97d2fb
};
Packit Service 97d2fb
mutex obatched::lock; // just the one, since cout/cerr iostreams are not thread-safe
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
void reportable_exception::report(ostream& o) const {
Packit Service 97d2fb
  obatched(o) << message << endl;
Packit Service 97d2fb
}
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
////////////////////////////////////////////////////////////////////////
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
// RAII style sqlite prepared-statement holder that matches { } block lifetime
Packit Service 97d2fb
Packit Service 97d2fb
struct sqlite_ps
Packit Service 97d2fb
{
Packit Service 97d2fb
private:
Packit Service 97d2fb
  sqlite3* db;
Packit Service 97d2fb
  const string nickname;
Packit Service 97d2fb
  const string sql;
Packit Service 97d2fb
  sqlite3_stmt *pp;
Packit Service 97d2fb
Packit Service 97d2fb
  sqlite_ps(const sqlite_ps&); // make uncopyable
Packit Service 97d2fb
  sqlite_ps& operator=(const sqlite_ps &); // make unassignable
Packit Service 97d2fb
Packit Service 97d2fb
public:
Packit Service 97d2fb
  sqlite_ps (sqlite3* d, const string& n, const string& s): db(d), nickname(n), sql(s) {
Packit Service 97d2fb
    if (verbose > 4)
Packit Service 97d2fb
      obatched(clog) << nickname << " prep " << sql << endl;
Packit Service 97d2fb
    int rc = sqlite3_prepare_v2 (db, sql.c_str(), -1 /* to \0 */, & this->pp, NULL);
Packit Service 97d2fb
    if (rc != SQLITE_OK)
Packit Service 97d2fb
      throw sqlite_exception(rc, "prepare " + sql);
Packit Service 97d2fb
  }
Packit Service 97d2fb
Packit Service 97d2fb
  sqlite_ps& reset()
Packit Service 97d2fb
  {
Packit Service 97d2fb
    sqlite3_reset(this->pp);
Packit Service 97d2fb
    return *this;
Packit Service 97d2fb
  }
Packit Service 97d2fb
Packit Service 97d2fb
  sqlite_ps& bind(int parameter, const string& str)
Packit Service 97d2fb
  {
Packit Service 97d2fb
    if (verbose > 4)
Packit Service 97d2fb
      obatched(clog) << nickname << " bind " << parameter << "=" << str << endl;
Packit Service 97d2fb
    int rc = sqlite3_bind_text (this->pp, parameter, str.c_str(), -1, SQLITE_TRANSIENT);
Packit Service 97d2fb
    if (rc != SQLITE_OK)
Packit Service 97d2fb
      throw sqlite_exception(rc, "sqlite3 bind");
Packit Service 97d2fb
    return *this;
Packit Service 97d2fb
  }
Packit Service 97d2fb
Packit Service 97d2fb
  sqlite_ps& bind(int parameter, int64_t value)
Packit Service 97d2fb
  {
Packit Service 97d2fb
    if (verbose > 4)
Packit Service 97d2fb
      obatched(clog) << nickname << " bind " << parameter << "=" << value << endl;
Packit Service 97d2fb
    int rc = sqlite3_bind_int64 (this->pp, parameter, value);
Packit Service 97d2fb
    if (rc != SQLITE_OK)
Packit Service 97d2fb
      throw sqlite_exception(rc, "sqlite3 bind");
Packit Service 97d2fb
    return *this;
Packit Service 97d2fb
  }
Packit Service 97d2fb
Packit Service 97d2fb
  sqlite_ps& bind(int parameter)
Packit Service 97d2fb
  {
Packit Service 97d2fb
    if (verbose > 4)
Packit Service 97d2fb
      obatched(clog) << nickname << " bind " << parameter << "=" << "NULL" << endl;
Packit Service 97d2fb
    int rc = sqlite3_bind_null (this->pp, parameter);
Packit Service 97d2fb
    if (rc != SQLITE_OK)
Packit Service 97d2fb
      throw sqlite_exception(rc, "sqlite3 bind");
Packit Service 97d2fb
    return *this;
Packit Service 97d2fb
  }
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
  void step_ok_done() {
Packit Service 97d2fb
    int rc = sqlite3_step (this->pp);
Packit Service 97d2fb
    if (verbose > 4)
Packit Service 97d2fb
      obatched(clog) << nickname << " step-ok-done(" << sqlite3_errstr(rc) << ") " << sql << endl;
Packit Service 97d2fb
    if (rc != SQLITE_OK && rc != SQLITE_DONE && rc != SQLITE_ROW)
Packit Service 97d2fb
      throw sqlite_exception(rc, "sqlite3 step");
Packit Service 97d2fb
    (void) sqlite3_reset (this->pp);
Packit Service 97d2fb
  }
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
  int step() {
Packit Service 97d2fb
    int rc = sqlite3_step (this->pp);
Packit Service 97d2fb
    if (verbose > 4)
Packit Service 97d2fb
      obatched(clog) << nickname << " step(" << sqlite3_errstr(rc) << ") " << sql << endl;
Packit Service 97d2fb
    return rc;
Packit Service 97d2fb
  }
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
  ~sqlite_ps () { sqlite3_finalize (this->pp); }
Packit Service 97d2fb
  operator sqlite3_stmt* () { return this->pp; }
Packit Service 97d2fb
};
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
////////////////////////////////////////////////////////////////////////
Packit Service 97d2fb
Packit Service 97d2fb
// RAII style templated autocloser
Packit Service 97d2fb
Packit Service 97d2fb
template <class Payload, class Ignore>
Packit Service 97d2fb
struct defer_dtor
Packit Service 97d2fb
{
Packit Service 97d2fb
public:
Packit Service 97d2fb
  typedef Ignore (*dtor_fn) (Payload);
Packit Service 97d2fb
Packit Service 97d2fb
private:
Packit Service 97d2fb
  Payload p;
Packit Service 97d2fb
  dtor_fn fn;
Packit Service 97d2fb
Packit Service 97d2fb
public:
Packit Service 97d2fb
  defer_dtor(Payload _p, dtor_fn _fn): p(_p), fn(_fn) {}
Packit Service 97d2fb
  ~defer_dtor() { (void) (*fn)(p); }
Packit Service 97d2fb
Packit Service 97d2fb
private:
Packit Service 97d2fb
  defer_dtor(const defer_dtor<Payload,Ignore>&); // make uncopyable
Packit Service 97d2fb
  defer_dtor& operator=(const defer_dtor<Payload,Ignore> &); // make unassignable
Packit Service 97d2fb
};
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
////////////////////////////////////////////////////////////////////////
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
static string
Packit Service 97d2fb
header_censor(const string& str)
Packit Service 97d2fb
{
Packit Service 97d2fb
  string y;
Packit Service 97d2fb
  for (auto&& x : str)
Packit Service 97d2fb
    {
Packit Service 97d2fb
      if (isalnum(x) || x == '/' || x == '.' || x == ',' || x == '_' || x == ':')
Packit Service 97d2fb
        y += x;
Packit Service 97d2fb
    }
Packit Service 97d2fb
  return y;
Packit Service 97d2fb
}
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
static string
Packit Service 97d2fb
conninfo (struct MHD_Connection * conn)
Packit Service 97d2fb
{
Packit Service 97d2fb
  char hostname[256]; // RFC1035
Packit Service 97d2fb
  char servname[256];
Packit Service 97d2fb
  int sts = -1;
Packit Service 97d2fb
Packit Service 97d2fb
  if (conn == 0)
Packit Service 97d2fb
    return "internal";
Packit Service 97d2fb
Packit Service 97d2fb
  /* Look up client address data. */
Packit Service 97d2fb
  const union MHD_ConnectionInfo *u = MHD_get_connection_info (conn,
Packit Service 97d2fb
                                                               MHD_CONNECTION_INFO_CLIENT_ADDRESS);
Packit Service 97d2fb
  struct sockaddr *so = u ? u->client_addr : 0;
Packit Service 97d2fb
Packit Service 97d2fb
  if (so && so->sa_family == AF_INET) {
Packit Service 97d2fb
    sts = getnameinfo (so, sizeof (struct sockaddr_in), hostname, sizeof (hostname), servname,
Packit Service 97d2fb
                       sizeof (servname), NI_NUMERICHOST | NI_NUMERICSERV);
Packit Service 97d2fb
  } else if (so && so->sa_family == AF_INET6) {
Packit Service 97d2fb
    sts = getnameinfo (so, sizeof (struct sockaddr_in6), hostname, sizeof (hostname),
Packit Service 97d2fb
                       servname, sizeof (servname), NI_NUMERICHOST | NI_NUMERICSERV);
Packit Service 97d2fb
  }
Packit Service 97d2fb
  if (sts != 0) {
Packit Service 97d2fb
    hostname[0] = servname[0] = '\0';
Packit Service 97d2fb
  }
Packit Service 97d2fb
Packit Service 97d2fb
  // extract headers relevant to administration
Packit Service 97d2fb
  const char* user_agent = MHD_lookup_connection_value (conn, MHD_HEADER_KIND, "User-Agent") ?: "";
Packit Service 97d2fb
  const char* x_forwarded_for = MHD_lookup_connection_value (conn, MHD_HEADER_KIND, "X-Forwarded-For") ?: "";
Packit Service 97d2fb
  // NB: these are untrustworthy, beware if machine-processing log files
Packit Service 97d2fb
Packit Service 97d2fb
  return string(hostname) + string(":") + string(servname) +
Packit Service 97d2fb
    string(" UA:") + header_censor(string(user_agent)) +
Packit Service 97d2fb
    string(" XFF:") + header_censor(string(x_forwarded_for));
Packit Service 97d2fb
}
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
////////////////////////////////////////////////////////////////////////
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
static void
Packit Service 97d2fb
add_mhd_last_modified (struct MHD_Response *resp, time_t mtime)
Packit Service 97d2fb
{
Packit Service 97d2fb
  struct tm *now = gmtime (&mtime);
Packit Service 97d2fb
  if (now != NULL)
Packit Service 97d2fb
    {
Packit Service 97d2fb
      char datebuf[80];
Packit Service 97d2fb
      size_t rc = strftime (datebuf, sizeof (datebuf), "%a, %d %b %Y %T GMT", now);
Packit Service 97d2fb
      if (rc > 0 && rc < sizeof (datebuf))
Packit Service 97d2fb
        (void) MHD_add_response_header (resp, "Last-Modified", datebuf);
Packit Service 97d2fb
    }
Packit Service 97d2fb
Packit Service 97d2fb
  (void) MHD_add_response_header (resp, "Cache-Control", "public");
Packit Service 97d2fb
}
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
static struct MHD_Response*
Packit Service 97d2fb
handle_buildid_f_match (bool internal_req_t,
Packit Service 97d2fb
                        int64_t b_mtime,
Packit Service 97d2fb
                        const string& b_source0,
Packit Service 97d2fb
                        int *result_fd)
Packit Service 97d2fb
{
Packit Service 97d2fb
  (void) internal_req_t; // ignored
Packit Service 97d2fb
  int fd = open(b_source0.c_str(), O_RDONLY);
Packit Service 97d2fb
  if (fd < 0)
Packit Service 97d2fb
    throw libc_exception (errno, string("open ") + b_source0);
Packit Service 97d2fb
Packit Service 97d2fb
  // NB: use manual close(2) in error case instead of defer_dtor, because
Packit Service 97d2fb
  // in the normal case, we want to hand the fd over to libmicrohttpd for
Packit Service 97d2fb
  // file transfer.
Packit Service 97d2fb
Packit Service 97d2fb
  struct stat s;
Packit Service 97d2fb
  int rc = fstat(fd, &s);
Packit Service 97d2fb
  if (rc < 0)
Packit Service 97d2fb
    {
Packit Service 97d2fb
      close(fd);
Packit Service 97d2fb
      throw libc_exception (errno, string("fstat ") + b_source0);
Packit Service 97d2fb
    }
Packit Service 97d2fb
Packit Service 97d2fb
  if ((int64_t) s.st_mtime != b_mtime)
Packit Service 97d2fb
    {
Packit Service 97d2fb
      if (verbose)
Packit Service 97d2fb
        obatched(clog) << "mtime mismatch for " << b_source0 << endl;
Packit Service 97d2fb
      close(fd);
Packit Service 97d2fb
      return 0;
Packit Service 97d2fb
    }
Packit Service 97d2fb
Packit Service 97d2fb
  inc_metric ("http_responses_total","result","file");
Packit Service 97d2fb
  struct MHD_Response* r = MHD_create_response_from_fd ((uint64_t) s.st_size, fd);
Packit Service 97d2fb
  if (r == 0)
Packit Service 97d2fb
    {
Packit Service 97d2fb
      if (verbose)
Packit Service 97d2fb
        obatched(clog) << "cannot create fd-response for " << b_source0 << endl;
Packit Service 97d2fb
      close(fd);
Packit Service 97d2fb
    }
Packit Service 97d2fb
  else
Packit Service 97d2fb
    {
Packit Service 97d2fb
      MHD_add_response_header (r, "Content-Type", "application/octet-stream");
Packit Service 97d2fb
      add_mhd_last_modified (r, s.st_mtime);
Packit Service 97d2fb
      if (verbose > 1)
Packit Service 97d2fb
        obatched(clog) << "serving file " << b_source0 << endl;
Packit Service 97d2fb
      /* libmicrohttpd will close it. */
Packit Service 97d2fb
      if (result_fd)
Packit Service 97d2fb
        *result_fd = fd;
Packit Service 97d2fb
    }
Packit Service 97d2fb
Packit Service 97d2fb
  return r;
Packit Service 97d2fb
}
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
// quote all questionable characters of str for safe passage through a sh -c expansion.
Packit Service 97d2fb
static string
Packit Service 97d2fb
shell_escape(const string& str)
Packit Service 97d2fb
{
Packit Service 97d2fb
  string y;
Packit Service 97d2fb
  for (auto&& x : str)
Packit Service 97d2fb
    {
Packit Service 97d2fb
      if (! isalnum(x) && x != '/')
Packit Service 97d2fb
        y += "\\";
Packit Service 97d2fb
      y += x;
Packit Service 97d2fb
    }
Packit Service 97d2fb
  return y;
Packit Service 97d2fb
}
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
// PR25548: Perform POSIX / RFC3986 style path canonicalization on the input string.
Packit Service 97d2fb
//
Packit Service 97d2fb
// Namely:
Packit Service 97d2fb
//    //         ->   /
Packit Service 97d2fb
//    /foo/../   ->   /
Packit Service 97d2fb
//    /./        ->   /
Packit Service 97d2fb
//
Packit Service 97d2fb
// This mapping is done on dwarf-side source path names, which may
Packit Service 97d2fb
// include these constructs, so we can deal with debuginfod clients
Packit Service 97d2fb
// that accidentally canonicalize the paths.
Packit Service 97d2fb
//
Packit Service 97d2fb
// realpath(3) is close but not quite right, because it also resolves
Packit Service 97d2fb
// symbolic links.  Symlinks at the debuginfod server have nothing to
Packit Service 97d2fb
// do with the build-time symlinks, thus they must not be considered.
Packit Service 97d2fb
//
Packit Service 97d2fb
// see also curl Curl_dedotdotify() aka RFC3986, which we mostly follow here
Packit Service 97d2fb
// see also libc __realpath()
Packit Service 97d2fb
// see also llvm llvm::sys::path::remove_dots()
Packit Service 97d2fb
static string
Packit Service 97d2fb
canon_pathname (const string& input)
Packit Service 97d2fb
{
Packit Service 97d2fb
  string i = input; // 5.2.4 (1)
Packit Service 97d2fb
  string o;
Packit Service 97d2fb
Packit Service 97d2fb
  while (i.size() != 0)
Packit Service 97d2fb
    {
Packit Service 97d2fb
      // 5.2.4 (2) A
Packit Service 97d2fb
      if (i.substr(0,3) == "../")
Packit Service 97d2fb
        i = i.substr(3);
Packit Service 97d2fb
      else if(i.substr(0,2) == "./")
Packit Service 97d2fb
        i = i.substr(2);
Packit Service 97d2fb
Packit Service 97d2fb
      // 5.2.4 (2) B
Packit Service 97d2fb
      else if (i.substr(0,3) == "/./")
Packit Service 97d2fb
        i = i.substr(2);
Packit Service 97d2fb
      else if (i == "/.")
Packit Service 97d2fb
        i = ""; // no need to handle "/." complete-path-segment case; we're dealing with file names
Packit Service 97d2fb
Packit Service 97d2fb
      // 5.2.4 (2) C
Packit Service 97d2fb
      else if (i.substr(0,4) == "/../") {
Packit Service 97d2fb
        i = i.substr(3);
Packit Service 97d2fb
        string::size_type sl = o.rfind("/");
Packit Service 97d2fb
        if (sl != string::npos)
Packit Service 97d2fb
          o = o.substr(0, sl);
Packit Service 97d2fb
        else
Packit Service 97d2fb
          o = "";
Packit Service 97d2fb
      } else if (i == "/..")
Packit Service 97d2fb
        i = ""; // no need to handle "/.." complete-path-segment case; we're dealing with file names
Packit Service 97d2fb
Packit Service 97d2fb
      // 5.2.4 (2) D
Packit Service 97d2fb
      // no need to handle these cases; we're dealing with file names
Packit Service 97d2fb
      else if (i == ".")
Packit Service 97d2fb
        i = "";
Packit Service 97d2fb
      else if (i == "..")
Packit Service 97d2fb
        i = "";
Packit Service 97d2fb
Packit Service 97d2fb
      // POSIX special: map // to /
Packit Service 97d2fb
      else if (i.substr(0,2) == "//")
Packit Service 97d2fb
        i = i.substr(1);
Packit Service 97d2fb
Packit Service 97d2fb
      // 5.2.4 (2) E
Packit Service 97d2fb
      else {
Packit Service 97d2fb
        string::size_type next_slash = i.find("/", (i[0]=='/' ? 1 : 0)); // skip first slash
Packit Service 97d2fb
        o += i.substr(0, next_slash);
Packit Service 97d2fb
        if (next_slash == string::npos)
Packit Service 97d2fb
          i = "";
Packit Service 97d2fb
        else
Packit Service 97d2fb
          i = i.substr(next_slash);
Packit Service 97d2fb
      }
Packit Service 97d2fb
    }
Packit Service 97d2fb
Packit Service 97d2fb
  return o;
Packit Service 97d2fb
}
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
// A map-like class that owns a cache of file descriptors (indexed by
Packit Service 97d2fb
// file / content names).
Packit Service 97d2fb
//
Packit Service 97d2fb
// If only it could use fd's instead of file names ... but we can't
Packit Service 97d2fb
// dup(2) to create independent descriptors for the same unlinked
Packit Service 97d2fb
// files, so would have to use some goofy linux /proc/self/fd/%d
Packit Service 97d2fb
// hack such as the following
Packit Service 97d2fb
Packit Service 97d2fb
#if 0
Packit Service 97d2fb
int superdup(int fd)
Packit Service 97d2fb
{
Packit Service 97d2fb
#ifdef __linux__
Packit Service 97d2fb
  char *fdpath = NULL;
Packit Service 97d2fb
  int rc = asprintf(& fdpath, "/proc/self/fd/%d", fd);
Packit Service 97d2fb
  int newfd;
Packit Service 97d2fb
  if (rc >= 0)
Packit Service 97d2fb
    newfd = open(fdpath, O_RDONLY);
Packit Service 97d2fb
  else
Packit Service 97d2fb
    newfd = -1;
Packit Service 97d2fb
  free (fdpath);
Packit Service 97d2fb
  return newfd;
Packit Service 97d2fb
#else
Packit Service 97d2fb
  return -1;
Packit Service 97d2fb
#endif
Packit Service 97d2fb
}
Packit Service 97d2fb
#endif
Packit Service 97d2fb
Packit Service 97d2fb
class libarchive_fdcache
Packit Service 97d2fb
{
Packit Service 97d2fb
private:
Packit Service 97d2fb
  mutex fdcache_lock;
Packit Service 97d2fb
Packit Service 97d2fb
  struct fdcache_entry
Packit Service 97d2fb
  {
Packit Service 97d2fb
    string archive;
Packit Service 97d2fb
    string entry;
Packit Service 97d2fb
    string fd;
Packit Service 97d2fb
    double fd_size_mb; // slightly rounded up megabytes
Packit Service 97d2fb
  };
Packit Service 97d2fb
  deque<fdcache_entry> lru; // @head: most recently used
Packit Service 97d2fb
  long max_fds;
Packit Service 97d2fb
  long max_mbs;
Packit Service 97d2fb
Packit Service 97d2fb
public:
Packit Service 97d2fb
  void set_metrics()
Packit Service 97d2fb
  {
Packit Service 97d2fb
    double total_mb = 0.0;
Packit Service 97d2fb
    for (auto i = lru.begin(); i < lru.end(); i++)
Packit Service 97d2fb
      total_mb += i->fd_size_mb;
Packit Service 97d2fb
    set_metric("fdcache_bytes", (int64_t)(total_mb*1024.0*1024.0));
Packit Service 97d2fb
    set_metric("fdcache_count", lru.size());
Packit Service 97d2fb
  }
Packit Service 97d2fb
Packit Service 97d2fb
  void intern(const string& a, const string& b, string fd, off_t sz, bool front_p)
Packit Service 97d2fb
  {
Packit Service 97d2fb
    {
Packit Service 97d2fb
      unique_lock<mutex> lock(fdcache_lock);
Packit Service 97d2fb
      for (auto i = lru.begin(); i < lru.end(); i++) // nuke preexisting copy
Packit Service 97d2fb
        {
Packit Service 97d2fb
          if (i->archive == a && i->entry == b)
Packit Service 97d2fb
            {
Packit Service 97d2fb
              unlink (i->fd.c_str());
Packit Service 97d2fb
              lru.erase(i);
Packit Service 97d2fb
              inc_metric("fdcache_op_count","op","dequeue");
Packit Service 97d2fb
              break; // must not continue iterating
Packit Service 97d2fb
            }
Packit Service 97d2fb
        }
Packit Service 97d2fb
      double mb = (sz+65535)/1048576.0; // round up to 64K block
Packit Service 97d2fb
      fdcache_entry n = { a, b, fd, mb };
Packit Service 97d2fb
      if (front_p)
Packit Service 97d2fb
        {
Packit Service 97d2fb
          inc_metric("fdcache_op_count","op","enqueue_front");
Packit Service 97d2fb
          lru.push_front(n);
Packit Service 97d2fb
        }
Packit Service 97d2fb
      else
Packit Service 97d2fb
        {
Packit Service 97d2fb
          inc_metric("fdcache_op_count","op","enqueue_back");
Packit Service 97d2fb
          lru.push_back(n);
Packit Service 97d2fb
        }
Packit Service 97d2fb
      if (verbose > 3)
Packit Service 97d2fb
        obatched(clog) << "fdcache interned a=" << a << " b=" << b
Packit Service 97d2fb
                       << " fd=" << fd << " mb=" << mb << " front=" << front_p << endl;
Packit Service 97d2fb
    }
Packit Service 97d2fb
    set_metrics();
Packit Service 97d2fb
Packit Service 97d2fb
    // NB: we age the cache at lookup time too
Packit Service 97d2fb
    if (front_p)
Packit Service 97d2fb
      this->limit(max_fds, max_mbs); // age cache if required
Packit Service 97d2fb
  }
Packit Service 97d2fb
Packit Service 97d2fb
  int lookup(const string& a, const string& b)
Packit Service 97d2fb
  {
Packit Service 97d2fb
    int fd = -1;
Packit Service 97d2fb
    {
Packit Service 97d2fb
      unique_lock<mutex> lock(fdcache_lock);
Packit Service 97d2fb
      for (auto i = lru.begin(); i < lru.end(); i++)
Packit Service 97d2fb
        {
Packit Service 97d2fb
          if (i->archive == a && i->entry == b)
Packit Service 97d2fb
            { // found it; move it to head of lru
Packit Service 97d2fb
              fdcache_entry n = *i;
Packit Service 97d2fb
              lru.erase(i); // invalidates i, so no more iteration!
Packit Service 97d2fb
              lru.push_front(n);
Packit Service 97d2fb
              inc_metric("fdcache_op_count","op","requeue_front");
Packit Service 97d2fb
              fd = open(n.fd.c_str(), O_RDONLY); // NB: no problem if dup() fails; looks like cache miss
Packit Service 97d2fb
              break;
Packit Service 97d2fb
            }
Packit Service 97d2fb
        }
Packit Service 97d2fb
    }
Packit Service 97d2fb
Packit Service 97d2fb
    if (fd >= 0)
Packit Service 97d2fb
      this->limit(max_fds, max_mbs); // age cache if required
Packit Service 97d2fb
Packit Service 97d2fb
    return fd;
Packit Service 97d2fb
  }
Packit Service 97d2fb
Packit Service 97d2fb
  int probe(const string& a, const string& b) // just a cache residency check - don't modify LRU state, don't open
Packit Service 97d2fb
  {
Packit Service 97d2fb
    unique_lock<mutex> lock(fdcache_lock);
Packit Service 97d2fb
    for (auto i = lru.begin(); i < lru.end(); i++)
Packit Service 97d2fb
      {
Packit Service 97d2fb
        if (i->archive == a && i->entry == b)
Packit Service 97d2fb
          {
Packit Service 97d2fb
            inc_metric("fdcache_op_count","op","probe_hit");
Packit Service 97d2fb
            return true;
Packit Service 97d2fb
          }
Packit Service 97d2fb
      }
Packit Service 97d2fb
    inc_metric("fdcache_op_count","op","probe_miss");
Packit Service 97d2fb
    return false;
Packit Service 97d2fb
  }
Packit Service 97d2fb
Packit Service 97d2fb
  void clear(const string& a, const string& b)
Packit Service 97d2fb
  {
Packit Service 97d2fb
    unique_lock<mutex> lock(fdcache_lock);
Packit Service 97d2fb
    for (auto i = lru.begin(); i < lru.end(); i++)
Packit Service 97d2fb
      {
Packit Service 97d2fb
        if (i->archive == a && i->entry == b)
Packit Service 97d2fb
          { // found it; move it to head of lru
Packit Service 97d2fb
            fdcache_entry n = *i;
Packit Service 97d2fb
            lru.erase(i); // invalidates i, so no more iteration!
Packit Service 97d2fb
            inc_metric("fdcache_op_count","op","clear");
Packit Service 97d2fb
            unlink (n.fd.c_str());
Packit Service 97d2fb
            set_metrics();
Packit Service 97d2fb
            return;
Packit Service 97d2fb
          }
Packit Service 97d2fb
      }
Packit Service 97d2fb
  }
Packit Service 97d2fb
Packit Service 97d2fb
  void limit(long maxfds, long maxmbs, bool metrics_p = true)
Packit Service 97d2fb
  {
Packit Service 97d2fb
    if (verbose > 3 && (this->max_fds != maxfds || this->max_mbs != maxmbs))
Packit Service 97d2fb
      obatched(clog) << "fdcache limited to maxfds=" << maxfds << " maxmbs=" << maxmbs << endl;
Packit Service 97d2fb
Packit Service 97d2fb
    unique_lock<mutex> lock(fdcache_lock);
Packit Service 97d2fb
    this->max_fds = maxfds;
Packit Service 97d2fb
    this->max_mbs = maxmbs;
Packit Service 97d2fb
Packit Service 97d2fb
    long total_fd = 0;
Packit Service 97d2fb
    double total_mb = 0.0;
Packit Service 97d2fb
    for (auto i = lru.begin(); i < lru.end(); i++)
Packit Service 97d2fb
      {
Packit Service 97d2fb
        // accumulate totals from most recently used one going backward
Packit Service 97d2fb
        total_fd ++;
Packit Service 97d2fb
        total_mb += i->fd_size_mb;
Packit Service 97d2fb
        if (total_fd > max_fds || total_mb > max_mbs)
Packit Service 97d2fb
          {
Packit Service 97d2fb
            // found the cut here point!
Packit Service 97d2fb
Packit Service 97d2fb
            for (auto j = i; j < lru.end(); j++) // close all the fds from here on in
Packit Service 97d2fb
              {
Packit Service 97d2fb
                if (verbose > 3)
Packit Service 97d2fb
                  obatched(clog) << "fdcache evicted a=" << j->archive << " b=" << j->entry
Packit Service 97d2fb
                                 << " fd=" << j->fd << " mb=" << j->fd_size_mb << endl;
Packit Service 97d2fb
                if (metrics_p)
Packit Service 97d2fb
                  inc_metric("fdcache_op_count","op","evict");
Packit Service 97d2fb
                unlink (j->fd.c_str());
Packit Service 97d2fb
              }
Packit Service 97d2fb
Packit Service 97d2fb
            lru.erase(i, lru.end()); // erase the nodes generally
Packit Service 97d2fb
            break;
Packit Service 97d2fb
          }
Packit Service 97d2fb
      }
Packit Service 97d2fb
    if (metrics_p) set_metrics();
Packit Service 97d2fb
  }
Packit Service 97d2fb
Packit Service 97d2fb
  ~libarchive_fdcache()
Packit Service 97d2fb
  {
Packit Service 97d2fb
    // unlink any fdcache entries in $TMPDIR
Packit Service 97d2fb
    // don't update metrics; those globals may be already destroyed 
Packit Service 97d2fb
    limit(0, 0, false);
Packit Service 97d2fb
  }
Packit Service 97d2fb
};
Packit Service 97d2fb
static libarchive_fdcache fdcache;
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
// For security/portability reasons, many distro-package archives have
Packit Service 97d2fb
// a "./" in front of path names; others have nothing, others have
Packit Service 97d2fb
// "/".  Canonicalize them all to a single leading "/", with the
Packit Service 97d2fb
// assumption that this matches the dwarf-derived file names too.
Packit Service 97d2fb
string canonicalized_archive_entry_pathname(struct archive_entry *e)
Packit Service 97d2fb
{
Packit Service 97d2fb
  string fn = archive_entry_pathname(e);
Packit Service 97d2fb
  if (fn.size() == 0)
Packit Service 97d2fb
    return fn;
Packit Service 97d2fb
  if (fn[0] == '/')
Packit Service 97d2fb
    return fn;
Packit Service 97d2fb
  if (fn[0] == '.')
Packit Service 97d2fb
    return fn.substr(1);
Packit Service 97d2fb
  else
Packit Service 97d2fb
    return string("/")+fn;
Packit Service 97d2fb
}
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
static struct MHD_Response*
Packit Service 97d2fb
handle_buildid_r_match (bool internal_req_p,
Packit Service 97d2fb
                        int64_t b_mtime,
Packit Service 97d2fb
                        const string& b_source0,
Packit Service 97d2fb
                        const string& b_source1,
Packit Service 97d2fb
                        int *result_fd)
Packit Service 97d2fb
{
Packit Service 97d2fb
  struct stat fs;
Packit Service 97d2fb
  int rc = stat (b_source0.c_str(), &fs);
Packit Service 97d2fb
  if (rc != 0)
Packit Service 97d2fb
    throw libc_exception (errno, string("stat ") + b_source0);
Packit Service 97d2fb
Packit Service 97d2fb
  if ((int64_t) fs.st_mtime != b_mtime)
Packit Service 97d2fb
    {
Packit Service 97d2fb
      if (verbose)
Packit Service 97d2fb
        obatched(clog) << "mtime mismatch for " << b_source0 << endl;
Packit Service 97d2fb
      return 0;
Packit Service 97d2fb
    }
Packit Service 97d2fb
Packit Service 97d2fb
  // check for a match in the fdcache first
Packit Service 97d2fb
  int fd = fdcache.lookup(b_source0, b_source1);
Packit Service 97d2fb
  while (fd >= 0) // got one!; NB: this is really an if() with a possible branch out to the end
Packit Service 97d2fb
    {
Packit Service 97d2fb
      rc = fstat(fd, &fs);
Packit Service 97d2fb
      if (rc < 0) // disappeared?
Packit Service 97d2fb
        {
Packit Service 97d2fb
          if (verbose)
Packit Service 97d2fb
            obatched(clog) << "cannot fstat fdcache " << b_source0 << endl;
Packit Service 97d2fb
          close(fd);
Packit Service 97d2fb
          fdcache.clear(b_source0, b_source1);
Packit Service 97d2fb
          break; // branch out of if "loop", to try new libarchive fetch attempt
Packit Service 97d2fb
        }
Packit Service 97d2fb
Packit Service 97d2fb
      struct MHD_Response* r = MHD_create_response_from_fd (fs.st_size, fd);
Packit Service 97d2fb
      if (r == 0)
Packit Service 97d2fb
        {
Packit Service 97d2fb
          if (verbose)
Packit Service 97d2fb
            obatched(clog) << "cannot create fd-response for " << b_source0 << endl;
Packit Service 97d2fb
          close(fd);
Packit Service 97d2fb
          break; // branch out of if "loop", to try new libarchive fetch attempt
Packit Service 97d2fb
        }
Packit Service 97d2fb
Packit Service 97d2fb
      inc_metric ("http_responses_total","result","archive fdcache");
Packit Service 97d2fb
Packit Service 97d2fb
      MHD_add_response_header (r, "Content-Type", "application/octet-stream");
Packit Service 97d2fb
      add_mhd_last_modified (r, fs.st_mtime);
Packit Service 97d2fb
      if (verbose > 1)
Packit Service 97d2fb
        obatched(clog) << "serving fdcache archive " << b_source0 << " file " << b_source1 << endl;
Packit Service 97d2fb
      /* libmicrohttpd will close it. */
Packit Service 97d2fb
      if (result_fd)
Packit Service 97d2fb
        *result_fd = fd;
Packit Service 97d2fb
      return r;
Packit Service 97d2fb
      // NB: see, we never go around the 'loop' more than once
Packit Service 97d2fb
    }
Packit Service 97d2fb
Packit Service 97d2fb
  // no match ... grumble, must process the archive
Packit Service 97d2fb
  string archive_decoder = "/dev/null";
Packit Service 97d2fb
  string archive_extension = "";
Packit Service 97d2fb
  for (auto&& arch : scan_archives)
Packit Service 97d2fb
    if (string_endswith(b_source0, arch.first))
Packit Service 97d2fb
      {
Packit Service 97d2fb
        archive_extension = arch.first;
Packit Service 97d2fb
        archive_decoder = arch.second;
Packit Service 97d2fb
      }
Packit Service 97d2fb
  FILE* fp;
Packit Service 97d2fb
  defer_dtor<FILE*,int>::dtor_fn dfn;
Packit Service 97d2fb
  if (archive_decoder != "cat")
Packit Service 97d2fb
    {
Packit Service 97d2fb
      string popen_cmd = archive_decoder + " " + shell_escape(b_source0);
Packit Service 97d2fb
      fp = popen (popen_cmd.c_str(), "r"); // "e" O_CLOEXEC?
Packit Service 97d2fb
      dfn = pclose;
Packit Service 97d2fb
      if (fp == NULL)
Packit Service 97d2fb
        throw libc_exception (errno, string("popen ") + popen_cmd);
Packit Service 97d2fb
    }
Packit Service 97d2fb
  else
Packit Service 97d2fb
    {
Packit Service 97d2fb
      fp = fopen (b_source0.c_str(), "r");
Packit Service 97d2fb
      dfn = fclose;
Packit Service 97d2fb
      if (fp == NULL)
Packit Service 97d2fb
        throw libc_exception (errno, string("fopen ") + b_source0);
Packit Service 97d2fb
    }
Packit Service 97d2fb
  defer_dtor<FILE*,int> fp_closer (fp, dfn);
Packit Service 97d2fb
Packit Service 97d2fb
  struct archive *a;
Packit Service 97d2fb
  a = archive_read_new();
Packit Service 97d2fb
  if (a == NULL)
Packit Service 97d2fb
    throw archive_exception("cannot create archive reader");
Packit Service 97d2fb
  defer_dtor<struct archive*,int> archive_closer (a, archive_read_free);
Packit Service 97d2fb
Packit Service 97d2fb
  rc = archive_read_support_format_all(a);
Packit Service 97d2fb
  if (rc != ARCHIVE_OK)
Packit Service 97d2fb
    throw archive_exception(a, "cannot select all format");
Packit Service 97d2fb
  rc = archive_read_support_filter_all(a);
Packit Service 97d2fb
  if (rc != ARCHIVE_OK)
Packit Service 97d2fb
    throw archive_exception(a, "cannot select all filters");
Packit Service 97d2fb
Packit Service 97d2fb
  rc = archive_read_open_FILE (a, fp);
Packit Service 97d2fb
  if (rc != ARCHIVE_OK)
Packit Service 97d2fb
    throw archive_exception(a, "cannot open archive from pipe");
Packit Service 97d2fb
Packit Service 97d2fb
  // archive traversal is in three stages, no, four stages:
Packit Service 97d2fb
  // 1) skip entries whose names do not match the requested one
Packit Service 97d2fb
  // 2) extract the matching entry name (set r = result)
Packit Service 97d2fb
  // 3) extract some number of prefetched entries (just into fdcache)
Packit Service 97d2fb
  // 4) abort any further processing
Packit Service 97d2fb
  struct MHD_Response* r = 0;                 // will set in stage 2
Packit Service 97d2fb
  unsigned prefetch_count =
Packit Service 97d2fb
    internal_req_p ? 0 : fdcache_prefetch;    // will decrement in stage 3
Packit Service 97d2fb
Packit Service 97d2fb
  while(r == 0 || prefetch_count > 0) // stage 1, 2, or 3
Packit Service 97d2fb
    {
Packit Service 97d2fb
      if (interrupted)
Packit Service 97d2fb
        break;
Packit Service 97d2fb
Packit Service 97d2fb
      struct archive_entry *e;
Packit Service 97d2fb
      rc = archive_read_next_header (a, &e);
Packit Service 97d2fb
      if (rc != ARCHIVE_OK)
Packit Service 97d2fb
        break;
Packit Service 97d2fb
Packit Service 97d2fb
      if (! S_ISREG(archive_entry_mode (e))) // skip non-files completely
Packit Service 97d2fb
        continue;
Packit Service 97d2fb
Packit Service 97d2fb
      string fn = canonicalized_archive_entry_pathname (e);
Packit Service 97d2fb
      if ((r == 0) && (fn != b_source1)) // stage 1
Packit Service 97d2fb
        continue;
Packit Service 97d2fb
Packit Service 97d2fb
      if (fdcache.probe (b_source0, fn)) // skip if already interned
Packit Service 97d2fb
        continue;
Packit Service 97d2fb
Packit Service 97d2fb
      // extract this file to a temporary file
Packit Service 97d2fb
      char* tmppath = NULL;
Packit Service 97d2fb
      rc = asprintf (&tmppath, "%s/debuginfod.XXXXXX", tmpdir.c_str());
Packit Service 97d2fb
      if (rc < 0)
Packit Service 97d2fb
        throw libc_exception (ENOMEM, "cannot allocate tmppath");
Packit Service 97d2fb
      defer_dtor<void*,void> tmmpath_freer (tmppath, free);
Packit Service 97d2fb
      fd = mkstemp (tmppath);
Packit Service 97d2fb
      if (fd < 0)
Packit Service 97d2fb
        throw libc_exception (errno, "cannot create temporary file");
Packit Service 97d2fb
      // NB: don't unlink (tmppath), as fdcache will take charge of it.
Packit Service 97d2fb
Packit Service 97d2fb
      // NB: this can take many uninterruptible seconds for a huge file
Packit Service 97d2fb
      rc = archive_read_data_into_fd (a, fd); 
Packit Service 97d2fb
      if (rc != ARCHIVE_OK) // e.g. ENOSPC!
Packit Service 97d2fb
        {
Packit Service 97d2fb
          close (fd);
Packit Service 97d2fb
          unlink (tmppath);
Packit Service 97d2fb
          throw archive_exception(a, "cannot extract file");
Packit Service 97d2fb
        }
Packit Service 97d2fb
Packit Service 97d2fb
      // Set the mtime so the fdcache file mtimes, even prefetched ones,
Packit Service 97d2fb
      // propagate to future webapi clients.
Packit Service 97d2fb
      struct timeval tvs[2];
Packit Service 97d2fb
      tvs[0].tv_sec = tvs[1].tv_sec = archive_entry_mtime(e);
Packit Service 97d2fb
      tvs[0].tv_usec = tvs[1].tv_usec = 0;
Packit Service 97d2fb
      (void) futimes (fd, tvs);  /* best effort */
Packit Service 97d2fb
Packit Service 97d2fb
      if (r != 0) // stage 3
Packit Service 97d2fb
        {
Packit Service 97d2fb
          // NB: now we know we have a complete reusable file; make fdcache
Packit Service 97d2fb
          // responsible for unlinking it later.
Packit Service 97d2fb
          fdcache.intern(b_source0, fn,
Packit Service 97d2fb
                         tmppath, archive_entry_size(e),
Packit Service 97d2fb
                         false); // prefetched ones go to back of lru
Packit Service 97d2fb
          prefetch_count --;
Packit Service 97d2fb
          close (fd); // we're not saving this fd to make a mhd-response from!
Packit Service 97d2fb
          continue;
Packit Service 97d2fb
        }
Packit Service 97d2fb
Packit Service 97d2fb
      // NB: now we know we have a complete reusable file; make fdcache
Packit Service 97d2fb
      // responsible for unlinking it later.
Packit Service 97d2fb
      fdcache.intern(b_source0, b_source1,
Packit Service 97d2fb
                     tmppath, archive_entry_size(e),
Packit Service 97d2fb
                     true); // requested ones go to the front of lru
Packit Service 97d2fb
Packit Service 97d2fb
      inc_metric ("http_responses_total","result",archive_extension + " archive");
Packit Service 97d2fb
      r = MHD_create_response_from_fd (archive_entry_size(e), fd);
Packit Service 97d2fb
      if (r == 0)
Packit Service 97d2fb
        {
Packit Service 97d2fb
          if (verbose)
Packit Service 97d2fb
            obatched(clog) << "cannot create fd-response for " << b_source0 << endl;
Packit Service 97d2fb
          close(fd);
Packit Service 97d2fb
          break; // assume no chance of better luck around another iteration; no other copies of same file
Packit Service 97d2fb
        }
Packit Service 97d2fb
      else
Packit Service 97d2fb
        {
Packit Service 97d2fb
          MHD_add_response_header (r, "Content-Type", "application/octet-stream");
Packit Service 97d2fb
          add_mhd_last_modified (r, archive_entry_mtime(e));
Packit Service 97d2fb
          if (verbose > 1)
Packit Service 97d2fb
            obatched(clog) << "serving archive " << b_source0 << " file " << b_source1 << endl;
Packit Service 97d2fb
          /* libmicrohttpd will close it. */
Packit Service 97d2fb
          if (result_fd)
Packit Service 97d2fb
            *result_fd = fd;
Packit Service 97d2fb
          continue;
Packit Service 97d2fb
        }
Packit Service 97d2fb
    }
Packit Service 97d2fb
Packit Service 97d2fb
  // XXX: rpm/file not found: delete this R entry?
Packit Service 97d2fb
  return r;
Packit Service 97d2fb
}
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
static struct MHD_Response*
Packit Service 97d2fb
handle_buildid_match (bool internal_req_p,
Packit Service 97d2fb
                      int64_t b_mtime,
Packit Service 97d2fb
                      const string& b_stype,
Packit Service 97d2fb
                      const string& b_source0,
Packit Service 97d2fb
                      const string& b_source1,
Packit Service 97d2fb
                      int *result_fd)
Packit Service 97d2fb
{
Packit Service 97d2fb
  try
Packit Service 97d2fb
    {
Packit Service 97d2fb
      if (b_stype == "F")
Packit Service 97d2fb
        return handle_buildid_f_match(internal_req_p, b_mtime, b_source0, result_fd);
Packit Service 97d2fb
      else if (b_stype == "R")
Packit Service 97d2fb
        return handle_buildid_r_match(internal_req_p, b_mtime, b_source0, b_source1, result_fd);
Packit Service 97d2fb
    }
Packit Service 97d2fb
  catch (const reportable_exception &e)
Packit Service 97d2fb
    {
Packit Service 97d2fb
      e.report(clog);
Packit Service 97d2fb
      // Report but swallow libc etc. errors here; let the caller
Packit Service 97d2fb
      // iterate to other matches of the content.
Packit Service 97d2fb
    }
Packit Service 97d2fb
  
Packit Service 97d2fb
  return 0;
Packit Service 97d2fb
}
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
static int
Packit Service 97d2fb
debuginfod_find_progress (debuginfod_client *, long a, long b)
Packit Service 97d2fb
{
Packit Service 97d2fb
  if (verbose > 4)
Packit Service 97d2fb
    obatched(clog) << "federated debuginfod progress=" << a << "/" << b << endl;
Packit Service 97d2fb
Packit Service 97d2fb
  return interrupted;
Packit Service 97d2fb
}
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
static struct MHD_Response*
Packit Service 97d2fb
handle_buildid (MHD_Connection* conn,
Packit Service 97d2fb
                const string& buildid /* unsafe */,
Packit Service 97d2fb
                const string& artifacttype /* unsafe */,
Packit Service 97d2fb
                const string& suffix /* unsafe */,
Packit Service 97d2fb
                int *result_fd)
Packit Service 97d2fb
{
Packit Service 97d2fb
  // validate artifacttype
Packit Service 97d2fb
  string atype_code;
Packit Service 97d2fb
  if (artifacttype == "debuginfo") atype_code = "D";
Packit Service 97d2fb
  else if (artifacttype == "executable") atype_code = "E";
Packit Service 97d2fb
  else if (artifacttype == "source") atype_code = "S";
Packit Service 97d2fb
  else throw reportable_exception("invalid artifacttype");
Packit Service 97d2fb
Packit Service 97d2fb
  if (atype_code == "S" && suffix == "")
Packit Service 97d2fb
     throw reportable_exception("invalid source suffix");
Packit Service 97d2fb
Packit Service 97d2fb
  // validate buildid
Packit Service 97d2fb
  if ((buildid.size() < 2) || // not empty
Packit Service 97d2fb
      (buildid.size() % 2) || // even number
Packit Service 97d2fb
      (buildid.find_first_not_of("0123456789abcdef") != string::npos)) // pure tasty lowercase hex
Packit Service 97d2fb
    throw reportable_exception("invalid buildid");
Packit Service 97d2fb
Packit Service 97d2fb
  if (verbose > 1)
Packit Service 97d2fb
    obatched(clog) << "searching for buildid=" << buildid << " artifacttype=" << artifacttype
Packit Service 97d2fb
         << " suffix=" << suffix << endl;
Packit Service 97d2fb
Packit Service 97d2fb
  sqlite_ps *pp = 0;
Packit Service 97d2fb
Packit Service 97d2fb
  if (atype_code == "D")
Packit Service 97d2fb
    {
Packit Service 97d2fb
      pp = new sqlite_ps (db, "mhd-query-d",
Packit Service 97d2fb
                          "select mtime, sourcetype, source0, source1 from " BUILDIDS "_query_d where buildid = ? "
Packit Service 97d2fb
                          "order by mtime desc");
Packit Service 97d2fb
      pp->reset();
Packit Service 97d2fb
      pp->bind(1, buildid);
Packit Service 97d2fb
    }
Packit Service 97d2fb
  else if (atype_code == "E")
Packit Service 97d2fb
    {
Packit Service 97d2fb
      pp = new sqlite_ps (db, "mhd-query-e",
Packit Service 97d2fb
                          "select mtime, sourcetype, source0, source1 from " BUILDIDS "_query_e where buildid = ? "
Packit Service 97d2fb
                          "order by mtime desc");
Packit Service 97d2fb
      pp->reset();
Packit Service 97d2fb
      pp->bind(1, buildid);
Packit Service 97d2fb
    }
Packit Service 97d2fb
  else if (atype_code == "S")
Packit Service 97d2fb
    {
Packit Service 97d2fb
      // PR25548
Packit Service 97d2fb
      // Incoming source queries may come in with either dwarf-level OR canonicalized paths.
Packit Service 97d2fb
      // We let the query pass with either one.
Packit Service 97d2fb
Packit Service 97d2fb
      pp = new sqlite_ps (db, "mhd-query-s",
Packit Service 97d2fb
                          "select mtime, sourcetype, source0, source1 from " BUILDIDS "_query_s where buildid = ? and artifactsrc in (?,?) "
Packit Service 97d2fb
                          "order by sharedprefix(source0,source0ref) desc, mtime desc");
Packit Service 97d2fb
      pp->reset();
Packit Service 97d2fb
      pp->bind(1, buildid);
Packit Service 97d2fb
      // NB: we don't store the non-canonicalized path names any more, but old databases
Packit Service 97d2fb
      // might have them (and no canon ones), so we keep searching for both.
Packit Service 97d2fb
      pp->bind(2, suffix);
Packit Service 97d2fb
      pp->bind(3, canon_pathname(suffix));
Packit Service 97d2fb
    }
Packit Service 97d2fb
  unique_ptr<sqlite_ps> ps_closer(pp); // release pp if exception or return
Packit Service 97d2fb
Packit Service 97d2fb
  // consume all the rows
Packit Service 97d2fb
  while (1)
Packit Service 97d2fb
    {
Packit Service 97d2fb
      int rc = pp->step();
Packit Service 97d2fb
      if (rc == SQLITE_DONE) break;
Packit Service 97d2fb
      if (rc != SQLITE_ROW)
Packit Service 97d2fb
        throw sqlite_exception(rc, "step");
Packit Service 97d2fb
Packit Service 97d2fb
      int64_t b_mtime = sqlite3_column_int64 (*pp, 0);
Packit Service 97d2fb
      string b_stype = string((const char*) sqlite3_column_text (*pp, 1) ?: ""); /* by DDL may not be NULL */
Packit Service 97d2fb
      string b_source0 = string((const char*) sqlite3_column_text (*pp, 2) ?: ""); /* may be NULL */
Packit Service 97d2fb
      string b_source1 = string((const char*) sqlite3_column_text (*pp, 3) ?: ""); /* may be NULL */
Packit Service 97d2fb
Packit Service 97d2fb
      if (verbose > 1)
Packit Service 97d2fb
        obatched(clog) << "found mtime=" << b_mtime << " stype=" << b_stype
Packit Service 97d2fb
             << " source0=" << b_source0 << " source1=" << b_source1 << endl;
Packit Service 97d2fb
Packit Service 97d2fb
      // Try accessing the located match.
Packit Service 97d2fb
      // XXX: in case of multiple matches, attempt them in parallel?
Packit Service 97d2fb
      auto r = handle_buildid_match (conn ? false : true,
Packit Service 97d2fb
                                     b_mtime, b_stype, b_source0, b_source1, result_fd);
Packit Service 97d2fb
      if (r)
Packit Service 97d2fb
        return r;
Packit Service 97d2fb
    }
Packit Service 97d2fb
Packit Service 97d2fb
  // We couldn't find it in the database.  Last ditch effort
Packit Service 97d2fb
  // is to defer to other debuginfo servers.
Packit Service 97d2fb
Packit Service 97d2fb
  int fd = -1;
Packit Service 97d2fb
  debuginfod_client *client = debuginfod_begin ();
Packit Service 97d2fb
  if (client != NULL)
Packit Service 97d2fb
    {
Packit Service 97d2fb
      debuginfod_set_progressfn (client, & debuginfod_find_progress);
Packit Service 97d2fb
Packit Service 97d2fb
      if (conn)
Packit Service 97d2fb
        {
Packit Service 97d2fb
          // Transcribe incoming User-Agent:
Packit Service 97d2fb
          string ua = MHD_lookup_connection_value (conn, MHD_HEADER_KIND, "User-Agent") ?: "";
Packit Service 97d2fb
          string ua_complete = string("User-Agent: ") + ua;
Packit Service 97d2fb
          debuginfod_add_http_header (client, ua_complete.c_str());
Packit Service 97d2fb
Packit Service 97d2fb
          // Compute larger XFF:, for avoiding info loss during
Packit Service 97d2fb
          // federation, and for future cyclicity detection.
Packit Service 97d2fb
          string xff = MHD_lookup_connection_value (conn, MHD_HEADER_KIND, "X-Forwarded-For") ?: "";
Packit Service 97d2fb
          if (xff != "")
Packit Service 97d2fb
            xff += string(", "); // comma separated list
Packit Service 97d2fb
Packit Service 97d2fb
          // Compute the client's numeric IP address only - so can't merge with conninfo()
Packit Service 97d2fb
          const union MHD_ConnectionInfo *u = MHD_get_connection_info (conn,
Packit Service 97d2fb
                                                                       MHD_CONNECTION_INFO_CLIENT_ADDRESS);
Packit Service 97d2fb
          struct sockaddr *so = u ? u->client_addr : 0;
Packit Service 97d2fb
          char hostname[256] = ""; // RFC1035
Packit Service 97d2fb
          if (so && so->sa_family == AF_INET)
Packit Service 97d2fb
            (void) getnameinfo (so, sizeof (struct sockaddr_in), hostname, sizeof (hostname), NULL, 0,
Packit Service 97d2fb
                                NI_NUMERICHOST);
Packit Service 97d2fb
          else if (so && so->sa_family == AF_INET6)
Packit Service 97d2fb
            (void) getnameinfo (so, sizeof (struct sockaddr_in6), hostname, sizeof (hostname), NULL, 0,
Packit Service 97d2fb
                                NI_NUMERICHOST);
Packit Service 97d2fb
Packit Service 97d2fb
          string xff_complete = string("X-Forwarded-For: ")+xff+string(hostname);
Packit Service 97d2fb
          debuginfod_add_http_header (client, xff_complete.c_str());
Packit Service 97d2fb
        }
Packit Service 97d2fb
Packit Service 97d2fb
      if (artifacttype == "debuginfo")
Packit Service 97d2fb
	fd = debuginfod_find_debuginfo (client,
Packit Service 97d2fb
					(const unsigned char*) buildid.c_str(),
Packit Service 97d2fb
					0, NULL);
Packit Service 97d2fb
      else if (artifacttype == "executable")
Packit Service 97d2fb
	fd = debuginfod_find_executable (client,
Packit Service 97d2fb
					 (const unsigned char*) buildid.c_str(),
Packit Service 97d2fb
					 0, NULL);
Packit Service 97d2fb
      else if (artifacttype == "source")
Packit Service 97d2fb
	fd = debuginfod_find_source (client,
Packit Service 97d2fb
				     (const unsigned char*) buildid.c_str(),
Packit Service 97d2fb
				     0, suffix.c_str(), NULL);
Packit Service 97d2fb
    }
Packit Service 97d2fb
  else
Packit Service 97d2fb
    fd = -errno; /* Set by debuginfod_begin.  */
Packit Service 97d2fb
  debuginfod_end (client);
Packit Service 97d2fb
Packit Service 97d2fb
  if (fd >= 0)
Packit Service 97d2fb
    {
Packit Service 97d2fb
      inc_metric ("http_responses_total","result","upstream");
Packit Service 97d2fb
      struct stat s;
Packit Service 97d2fb
      int rc = fstat (fd, &s);
Packit Service 97d2fb
      if (rc == 0)
Packit Service 97d2fb
        {
Packit Service 97d2fb
          auto r = MHD_create_response_from_fd ((uint64_t) s.st_size, fd);
Packit Service 97d2fb
          if (r)
Packit Service 97d2fb
            {
Packit Service 97d2fb
              MHD_add_response_header (r, "Content-Type", "application/octet-stream");
Packit Service 97d2fb
              add_mhd_last_modified (r, s.st_mtime);
Packit Service 97d2fb
              if (verbose > 1)
Packit Service 97d2fb
                obatched(clog) << "serving file from upstream debuginfod/cache" << endl;
Packit Service 97d2fb
              if (result_fd)
Packit Service 97d2fb
                *result_fd = fd;
Packit Service 97d2fb
              return r; // NB: don't close fd; libmicrohttpd will
Packit Service 97d2fb
            }
Packit Service 97d2fb
        }
Packit Service 97d2fb
      close (fd);
Packit Service 97d2fb
    }
Packit Service 97d2fb
  else
Packit Service 97d2fb
    switch(fd)
Packit Service 97d2fb
      {
Packit Service 97d2fb
      case -ENOSYS:
Packit Service 97d2fb
        break;
Packit Service 97d2fb
      case -ENOENT:
Packit Service 97d2fb
        break;
Packit Service 97d2fb
      default: // some more tricky error
Packit Service 97d2fb
        throw libc_exception(-fd, "upstream debuginfod query failed");
Packit Service 97d2fb
      }
Packit Service 97d2fb
Packit Service 97d2fb
  throw reportable_exception(MHD_HTTP_NOT_FOUND, "not found");
Packit Service 97d2fb
}
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
////////////////////////////////////////////////////////////////////////
Packit Service 97d2fb
Packit Service 97d2fb
static map<string,int64_t> metrics; // arbitrary data for /metrics query
Packit Service 97d2fb
// NB: store int64_t since all our metrics are integers; prometheus accepts double
Packit Service 97d2fb
static mutex metrics_lock;
Packit Service 97d2fb
// NB: these objects get released during the process exit via global dtors
Packit Service 97d2fb
// do not call them from within other global dtors
Packit Service 97d2fb
Packit Service 97d2fb
// utility function for assembling prometheus-compatible
Packit Service 97d2fb
// name="escaped-value" strings
Packit Service 97d2fb
// https://prometheus.io/docs/instrumenting/exposition_formats/
Packit Service 97d2fb
static string
Packit Service 97d2fb
metric_label(const string& name, const string& value)
Packit Service 97d2fb
{
Packit Service 97d2fb
  string x = name + "=\"";
Packit Service 97d2fb
  for (auto&& c : value)
Packit Service 97d2fb
    switch(c)
Packit Service 97d2fb
      {
Packit Service 97d2fb
      case '\\': x += "\\\\"; break;
Packit Service 97d2fb
      case '\"': x += "\\\""; break;
Packit Service 97d2fb
      case '\n': x += "\\n"; break;
Packit Service 97d2fb
      default: x += c; break;
Packit Service 97d2fb
      }
Packit Service 97d2fb
  x += "\"";
Packit Service 97d2fb
  return x;
Packit Service 97d2fb
}
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
// add prometheus-format metric name + label tuple (if any) + value
Packit Service 97d2fb
Packit Service 97d2fb
static void
Packit Service 97d2fb
set_metric(const string& metric, int64_t value)
Packit Service 97d2fb
{
Packit Service 97d2fb
  unique_lock<mutex> lock(metrics_lock);
Packit Service 97d2fb
  metrics[metric] = value;
Packit Service 97d2fb
}
Packit Service 97d2fb
#if 0 /* unused */
Packit Service 97d2fb
static void
Packit Service 97d2fb
inc_metric(const string& metric)
Packit Service 97d2fb
{
Packit Service 97d2fb
  unique_lock<mutex> lock(metrics_lock);
Packit Service 97d2fb
  metrics[metric] ++;
Packit Service 97d2fb
}
Packit Service 97d2fb
#endif
Packit Service 97d2fb
static void
Packit Service 97d2fb
set_metric(const string& metric,
Packit Service 97d2fb
           const string& lname, const string& lvalue,
Packit Service 97d2fb
           int64_t value)
Packit Service 97d2fb
{
Packit Service 97d2fb
  string key = (metric + "{" + metric_label(lname, lvalue) + "}");
Packit Service 97d2fb
  unique_lock<mutex> lock(metrics_lock);
Packit Service 97d2fb
  metrics[key] = value;
Packit Service 97d2fb
}
Packit Service 97d2fb
Packit Service 97d2fb
static void
Packit Service 97d2fb
inc_metric(const string& metric,
Packit Service 97d2fb
           const string& lname, const string& lvalue)
Packit Service 97d2fb
{
Packit Service 97d2fb
  string key = (metric + "{" + metric_label(lname, lvalue) + "}");
Packit Service 97d2fb
  unique_lock<mutex> lock(metrics_lock);
Packit Service 97d2fb
  metrics[key] ++;
Packit Service 97d2fb
}
Packit Service 97d2fb
static void
Packit Service 97d2fb
add_metric(const string& metric,
Packit Service 97d2fb
           const string& lname, const string& lvalue,
Packit Service 97d2fb
           int64_t value)
Packit Service 97d2fb
{
Packit Service 97d2fb
  string key = (metric + "{" + metric_label(lname, lvalue) + "}");
Packit Service 97d2fb
  unique_lock<mutex> lock(metrics_lock);
Packit Service 97d2fb
  metrics[key] += value;
Packit Service 97d2fb
}
Packit Service 97d2fb
#if 0
Packit Service 97d2fb
static void
Packit Service 97d2fb
add_metric(const string& metric,
Packit Service 97d2fb
           int64_t value)
Packit Service 97d2fb
{
Packit Service 97d2fb
  unique_lock<mutex> lock(metrics_lock);
Packit Service 97d2fb
  metrics[metric] += value;
Packit Service 97d2fb
}
Packit Service 97d2fb
#endif
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
// and more for higher arity labels if needed
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
static struct MHD_Response*
Packit Service 97d2fb
handle_metrics (off_t* size)
Packit Service 97d2fb
{
Packit Service 97d2fb
  stringstream o;
Packit Service 97d2fb
  {
Packit Service 97d2fb
    unique_lock<mutex> lock(metrics_lock);
Packit Service 97d2fb
    for (auto&& i : metrics)
Packit Service 97d2fb
      o << i.first << " " << i.second << endl;
Packit Service 97d2fb
  }
Packit Service 97d2fb
  const string& os = o.str();
Packit Service 97d2fb
  MHD_Response* r = MHD_create_response_from_buffer (os.size(),
Packit Service 97d2fb
                                                     (void*) os.c_str(),
Packit Service 97d2fb
                                                     MHD_RESPMEM_MUST_COPY);
Packit Service 97d2fb
  *size = os.size();
Packit Service 97d2fb
  MHD_add_response_header (r, "Content-Type", "text/plain");
Packit Service 97d2fb
  return r;
Packit Service 97d2fb
}
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
////////////////////////////////////////////////////////////////////////
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
/* libmicrohttpd callback */
Packit Service 97d2fb
static MHD_RESULT
Packit Service 97d2fb
handler_cb (void * /*cls*/,
Packit Service 97d2fb
            struct MHD_Connection *connection,
Packit Service 97d2fb
            const char *url,
Packit Service 97d2fb
            const char *method,
Packit Service 97d2fb
            const char * /*version*/,
Packit Service 97d2fb
            const char * /*upload_data*/,
Packit Service 97d2fb
            size_t * /*upload_data_size*/,
Packit Service 97d2fb
            void ** /*con_cls*/)
Packit Service 97d2fb
{
Packit Service 97d2fb
  struct MHD_Response *r = NULL;
Packit Service 97d2fb
  string url_copy = url;
Packit Service 97d2fb
Packit Service 97d2fb
#if MHD_VERSION >= 0x00097002
Packit Service 97d2fb
  enum MHD_Result rc;
Packit Service 97d2fb
#else
Packit Service 97d2fb
  int rc = MHD_NO; // mhd
Packit Service 97d2fb
#endif
Packit Service 97d2fb
  int http_code = 500;
Packit Service 97d2fb
  off_t http_size = -1;
Packit Service 97d2fb
  struct timeval tv_start, tv_end;
Packit Service 97d2fb
  gettimeofday (&tv_start, NULL);
Packit Service 97d2fb
Packit Service 97d2fb
  try
Packit Service 97d2fb
    {
Packit Service 97d2fb
      if (string(method) != "GET")
Packit Service 97d2fb
        throw reportable_exception(400, "we support GET only");
Packit Service 97d2fb
Packit Service 97d2fb
      /* Start decoding the URL. */
Packit Service 97d2fb
      size_t slash1 = url_copy.find('/', 1);
Packit Service 97d2fb
      string url1 = url_copy.substr(0, slash1); // ok even if slash1 not found
Packit Service 97d2fb
Packit Service 97d2fb
      if (slash1 != string::npos && url1 == "/buildid")
Packit Service 97d2fb
        {
Packit Service 97d2fb
          size_t slash2 = url_copy.find('/', slash1+1);
Packit Service 97d2fb
          if (slash2 == string::npos)
Packit Service 97d2fb
            throw reportable_exception("/buildid/ webapi error, need buildid");
Packit Service 97d2fb
Packit Service 97d2fb
          string buildid = url_copy.substr(slash1+1, slash2-slash1-1);
Packit Service 97d2fb
Packit Service 97d2fb
          size_t slash3 = url_copy.find('/', slash2+1);
Packit Service 97d2fb
          string artifacttype, suffix;
Packit Service 97d2fb
          if (slash3 == string::npos)
Packit Service 97d2fb
            {
Packit Service 97d2fb
              artifacttype = url_copy.substr(slash2+1);
Packit Service 97d2fb
              suffix = "";
Packit Service 97d2fb
            }
Packit Service 97d2fb
          else
Packit Service 97d2fb
            {
Packit Service 97d2fb
              artifacttype = url_copy.substr(slash2+1, slash3-slash2-1);
Packit Service 97d2fb
              suffix = url_copy.substr(slash3); // include the slash in the suffix
Packit Service 97d2fb
            }
Packit Service 97d2fb
Packit Service 97d2fb
          inc_metric("http_requests_total", "type", artifacttype);
Packit Service 97d2fb
          // get the resulting fd so we can report its size
Packit Service 97d2fb
          int fd;
Packit Service 97d2fb
          r = handle_buildid(connection, buildid, artifacttype, suffix, &fd;;
Packit Service 97d2fb
          if (r)
Packit Service 97d2fb
            {
Packit Service 97d2fb
              struct stat fs;
Packit Service 97d2fb
              if (fstat(fd, &fs) == 0)
Packit Service 97d2fb
                http_size = fs.st_size;
Packit Service 97d2fb
              // libmicrohttpd will close (fd);
Packit Service 97d2fb
            }
Packit Service 97d2fb
        }
Packit Service 97d2fb
      else if (url1 == "/metrics")
Packit Service 97d2fb
        {
Packit Service 97d2fb
          inc_metric("http_requests_total", "type", "metrics");
Packit Service 97d2fb
          r = handle_metrics(& http_size);
Packit Service 97d2fb
        }
Packit Service 97d2fb
      else
Packit Service 97d2fb
        throw reportable_exception("webapi error, unrecognized /operation");
Packit Service 97d2fb
Packit Service 97d2fb
      if (r == 0)
Packit Service 97d2fb
        throw reportable_exception("internal error, missing response");
Packit Service 97d2fb
Packit Service 97d2fb
      rc = MHD_queue_response (connection, MHD_HTTP_OK, r);
Packit Service 97d2fb
      http_code = MHD_HTTP_OK;
Packit Service 97d2fb
      MHD_destroy_response (r);
Packit Service 97d2fb
    }
Packit Service 97d2fb
  catch (const reportable_exception& e)
Packit Service 97d2fb
    {
Packit Service 97d2fb
      inc_metric("http_responses_total","result","error");
Packit Service 97d2fb
      e.report(clog);
Packit Service 97d2fb
      http_code = e.code;
Packit Service 97d2fb
      http_size = e.message.size();
Packit Service 97d2fb
      rc = e.mhd_send_response (connection);
Packit Service 97d2fb
    }
Packit Service 97d2fb
Packit Service 97d2fb
  gettimeofday (&tv_end, NULL);
Packit Service 97d2fb
  double deltas = (tv_end.tv_sec - tv_start.tv_sec) + (tv_end.tv_usec - tv_start.tv_usec)*0.000001;
Packit Service 97d2fb
  obatched(clog) << conninfo(connection)
Packit Service 97d2fb
                 << ' ' << method << ' ' << url
Packit Service 97d2fb
                 << ' ' << http_code << ' ' << http_size
Packit Service 97d2fb
                 << ' ' << (int)(deltas*1000) << "ms"
Packit Service 97d2fb
                 << endl;
Packit Service 97d2fb
Packit Service 97d2fb
  // related prometheus metrics
Packit Service 97d2fb
  string http_code_str = to_string(http_code);
Packit Service 97d2fb
  if (http_size >= 0)
Packit Service 97d2fb
    add_metric("http_responses_transfer_bytes_sum","code",http_code_str,
Packit Service 97d2fb
               http_size);
Packit Service 97d2fb
  inc_metric("http_responses_transfer_bytes_count","code",http_code_str);
Packit Service 97d2fb
Packit Service 97d2fb
  add_metric("http_responses_duration_milliseconds_sum","code",http_code_str,
Packit Service 97d2fb
             deltas*1000); // prometheus prefers _seconds and floating point
Packit Service 97d2fb
  inc_metric("http_responses_duration_milliseconds_count","code",http_code_str);
Packit Service 97d2fb
Packit Service 97d2fb
  return rc;
Packit Service 97d2fb
}
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
////////////////////////////////////////////////////////////////////////
Packit Service 97d2fb
// borrowed originally from src/nm.c get_local_names()
Packit Service 97d2fb
Packit Service 97d2fb
static void
Packit Service 97d2fb
dwarf_extract_source_paths (Elf *elf, set<string>& debug_sourcefiles)
Packit Service 97d2fb
  noexcept // no exceptions - so we can simplify the altdbg resource release at end
Packit Service 97d2fb
{
Packit Service 97d2fb
  Dwarf* dbg = dwarf_begin_elf (elf, DWARF_C_READ, NULL);
Packit Service 97d2fb
  if (dbg == NULL)
Packit Service 97d2fb
    return;
Packit Service 97d2fb
Packit Service 97d2fb
  Dwarf* altdbg = NULL;
Packit Service 97d2fb
  int    altdbg_fd = -1;
Packit Service 97d2fb
Packit Service 97d2fb
  // DWZ handling: if we have an unsatisfied debug-alt-link, add an
Packit Service 97d2fb
  // empty string into the outgoing sourcefiles set, so the caller
Packit Service 97d2fb
  // should know that our data is incomplete.
Packit Service 97d2fb
  const char *alt_name_p;
Packit Service 97d2fb
  const void *alt_build_id; // elfutils-owned memory
Packit Service 97d2fb
  ssize_t sz = dwelf_dwarf_gnu_debugaltlink (dbg, &alt_name_p, &alt_build_id);
Packit Service 97d2fb
  if (sz > 0) // got one!
Packit Service 97d2fb
    {
Packit Service 97d2fb
      string buildid;
Packit Service 97d2fb
      unsigned char* build_id_bytes = (unsigned char*) alt_build_id;
Packit Service 97d2fb
      for (ssize_t idx=0; idx
Packit Service 97d2fb
        {
Packit Service 97d2fb
          buildid += "0123456789abcdef"[build_id_bytes[idx] >> 4];
Packit Service 97d2fb
          buildid += "0123456789abcdef"[build_id_bytes[idx] & 0xf];
Packit Service 97d2fb
        }
Packit Service 97d2fb
Packit Service 97d2fb
      if (verbose > 3)
Packit Service 97d2fb
        obatched(clog) << "Need altdebug buildid=" << buildid << endl;
Packit Service 97d2fb
Packit Service 97d2fb
      // but is it unsatisfied the normal elfutils ways?
Packit Service 97d2fb
      Dwarf* alt = dwarf_getalt (dbg);
Packit Service 97d2fb
      if (alt == NULL)
Packit Service 97d2fb
        {
Packit Service 97d2fb
          // Yup, unsatisfied the normal way.  Maybe we can satisfy it
Packit Service 97d2fb
          // from our own debuginfod database.
Packit Service 97d2fb
          int alt_fd;
Packit Service 97d2fb
          struct MHD_Response *r = 0;
Packit Service 97d2fb
          try
Packit Service 97d2fb
            {
Packit Service 97d2fb
              r = handle_buildid (0, buildid, "debuginfo", "", &alt_fd);
Packit Service 97d2fb
            }
Packit Service 97d2fb
          catch (const reportable_exception& e)
Packit Service 97d2fb
            {
Packit Service 97d2fb
              // swallow exceptions
Packit Service 97d2fb
            }
Packit Service 97d2fb
Packit Service 97d2fb
          // NB: this is not actually recursive!  This invokes the web-query
Packit Service 97d2fb
          // path, which cannot get back into the scan code paths.
Packit Service 97d2fb
          if (r)
Packit Service 97d2fb
            {
Packit Service 97d2fb
              // Found it!
Packit Service 97d2fb
              altdbg_fd = dup(alt_fd); // ok if this fails, downstream failures ok
Packit Service 97d2fb
              alt = altdbg = dwarf_begin (altdbg_fd, DWARF_C_READ);
Packit Service 97d2fb
              // NB: must close this dwarf and this fd at the bottom of the function!
Packit Service 97d2fb
              MHD_destroy_response (r); // will close alt_fd
Packit Service 97d2fb
              if (alt)
Packit Service 97d2fb
                dwarf_setalt (dbg, alt);
Packit Service 97d2fb
            }
Packit Service 97d2fb
        }
Packit Service 97d2fb
      else
Packit Service 97d2fb
        {
Packit Service 97d2fb
          // NB: dwarf_setalt(alt) inappropriate - already done!
Packit Service 97d2fb
          // NB: altdbg will stay 0 so nothing tries to redundantly dealloc.
Packit Service 97d2fb
        }
Packit Service 97d2fb
Packit Service 97d2fb
      if (alt)
Packit Service 97d2fb
        {
Packit Service 97d2fb
          if (verbose > 3)
Packit Service 97d2fb
            obatched(clog) << "Resolved altdebug buildid=" << buildid << endl;
Packit Service 97d2fb
        }
Packit Service 97d2fb
      else // (alt == NULL) - signal possible presence of poor debuginfo
Packit Service 97d2fb
        {
Packit Service 97d2fb
          debug_sourcefiles.insert("");
Packit Service 97d2fb
          if (verbose > 3)
Packit Service 97d2fb
            obatched(clog) << "Unresolved altdebug buildid=" << buildid << endl;
Packit Service 97d2fb
        }
Packit Service 97d2fb
    }
Packit Service 97d2fb
Packit Service 97d2fb
  Dwarf_Off offset = 0;
Packit Service 97d2fb
  Dwarf_Off old_offset;
Packit Service 97d2fb
  size_t hsize;
Packit Service 97d2fb
Packit Service 97d2fb
  while (dwarf_nextcu (dbg, old_offset = offset, &offset, &hsize, NULL, NULL, NULL) == 0)
Packit Service 97d2fb
    {
Packit Service 97d2fb
      Dwarf_Die cudie_mem;
Packit Service 97d2fb
      Dwarf_Die *cudie = dwarf_offdie (dbg, old_offset + hsize, &cudie_mem);
Packit Service 97d2fb
Packit Service 97d2fb
      if (cudie == NULL)
Packit Service 97d2fb
        continue;
Packit Service 97d2fb
      if (dwarf_tag (cudie) != DW_TAG_compile_unit)
Packit Service 97d2fb
        continue;
Packit Service 97d2fb
Packit Service 97d2fb
      const char *cuname = dwarf_diename(cudie) ?: "unknown";
Packit Service 97d2fb
Packit Service 97d2fb
      Dwarf_Files *files;
Packit Service 97d2fb
      size_t nfiles;
Packit Service 97d2fb
      if (dwarf_getsrcfiles (cudie, &files, &nfiles) != 0)
Packit Service 97d2fb
        continue;
Packit Service 97d2fb
Packit Service 97d2fb
      // extract DW_AT_comp_dir to resolve relative file names
Packit Service 97d2fb
      const char *comp_dir = "";
Packit Service 97d2fb
      const char *const *dirs;
Packit Service 97d2fb
      size_t ndirs;
Packit Service 97d2fb
      if (dwarf_getsrcdirs (files, &dirs, &ndirs) == 0 &&
Packit Service 97d2fb
          dirs[0] != NULL)
Packit Service 97d2fb
        comp_dir = dirs[0];
Packit Service 97d2fb
      if (comp_dir == NULL)
Packit Service 97d2fb
        comp_dir = "";
Packit Service 97d2fb
Packit Service 97d2fb
      if (verbose > 3)
Packit Service 97d2fb
        obatched(clog) << "searching for sources for cu=" << cuname << " comp_dir=" << comp_dir
Packit Service 97d2fb
                       << " #files=" << nfiles << " #dirs=" << ndirs << endl;
Packit Service 97d2fb
Packit Service 97d2fb
      if (comp_dir[0] == '\0' && cuname[0] != '/')
Packit Service 97d2fb
        {
Packit Service 97d2fb
          // This is a common symptom for dwz-compressed debug files,
Packit Service 97d2fb
          // where the altdebug file cannot be resolved.
Packit Service 97d2fb
          if (verbose > 3)
Packit Service 97d2fb
            obatched(clog) << "skipping cu=" << cuname << " due to empty comp_dir" << endl;
Packit Service 97d2fb
          continue;
Packit Service 97d2fb
        }
Packit Service 97d2fb
Packit Service 97d2fb
      for (size_t f = 1; f < nfiles; f++)
Packit Service 97d2fb
        {
Packit Service 97d2fb
          const char *hat = dwarf_filesrc (files, f, NULL, NULL);
Packit Service 97d2fb
          if (hat == NULL)
Packit Service 97d2fb
            continue;
Packit Service 97d2fb
Packit Service 97d2fb
          if (string(hat) == "<built-in>") // gcc intrinsics, don't bother record
Packit Service 97d2fb
            continue;
Packit Service 97d2fb
Packit Service 97d2fb
          string waldo;
Packit Service 97d2fb
          if (hat[0] == '/') // absolute
Packit Service 97d2fb
            waldo = (string (hat));
Packit Service 97d2fb
          else if (comp_dir[0] != '\0') // comp_dir relative
Packit Service 97d2fb
            waldo = (string (comp_dir) + string("/") + string (hat));
Packit Service 97d2fb
          else
Packit Service 97d2fb
           {
Packit Service 97d2fb
             if (verbose > 3)
Packit Service 97d2fb
               obatched(clog) << "skipping hat=" << hat << " due to empty comp_dir" << endl;
Packit Service 97d2fb
             continue;
Packit Service 97d2fb
           }
Packit Service 97d2fb
Packit Service 97d2fb
          // NB: this is the 'waldo' that a dbginfo client will have
Packit Service 97d2fb
          // to supply for us to give them the file The comp_dir
Packit Service 97d2fb
          // prefixing is a definite complication.  Otherwise we'd
Packit Service 97d2fb
          // have to return a setof comp_dirs (one per CU!) with
Packit Service 97d2fb
          // corresponding filesrc[] names, instead of one absolute
Packit Service 97d2fb
          // resoved set.  Maybe we'll have to do that anyway.  XXX
Packit Service 97d2fb
Packit Service 97d2fb
          if (verbose > 4)
Packit Service 97d2fb
            obatched(clog) << waldo
Packit Service 97d2fb
                           << (debug_sourcefiles.find(waldo)==debug_sourcefiles.end() ? " new" : " dup") <<  endl;
Packit Service 97d2fb
Packit Service 97d2fb
          debug_sourcefiles.insert (waldo);
Packit Service 97d2fb
        }
Packit Service 97d2fb
    }
Packit Service 97d2fb
Packit Service 97d2fb
  dwarf_end(dbg);
Packit Service 97d2fb
  if (altdbg)
Packit Service 97d2fb
    dwarf_end(altdbg);
Packit Service 97d2fb
  if (altdbg_fd >= 0)
Packit Service 97d2fb
    close(altdbg_fd);
Packit Service 97d2fb
}
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
static void
Packit Service 97d2fb
elf_classify (int fd, bool &executable_p, bool &debuginfo_p, string &buildid, set<string>& debug_sourcefiles)
Packit Service 97d2fb
{
Packit Service 97d2fb
  Elf *elf = elf_begin (fd, ELF_C_READ_MMAP_PRIVATE, NULL);
Packit Service 97d2fb
  if (elf == NULL)
Packit Service 97d2fb
    return;
Packit Service 97d2fb
Packit Service 97d2fb
  try // catch our types of errors and clean up the Elf* object
Packit Service 97d2fb
    {
Packit Service 97d2fb
      if (elf_kind (elf) != ELF_K_ELF)
Packit Service 97d2fb
        {
Packit Service 97d2fb
          elf_end (elf);
Packit Service 97d2fb
          return;
Packit Service 97d2fb
        }
Packit Service 97d2fb
Packit Service 97d2fb
      GElf_Ehdr ehdr_storage;
Packit Service 97d2fb
      GElf_Ehdr *ehdr = gelf_getehdr (elf, &ehdr_storage);
Packit Service 97d2fb
      if (ehdr == NULL)
Packit Service 97d2fb
        {
Packit Service 97d2fb
          elf_end (elf);
Packit Service 97d2fb
          return;
Packit Service 97d2fb
        }
Packit Service 97d2fb
      auto elf_type = ehdr->e_type;
Packit Service 97d2fb
Packit Service 97d2fb
      const void *build_id; // elfutils-owned memory
Packit Service 97d2fb
      ssize_t sz = dwelf_elf_gnu_build_id (elf, & build_id);
Packit Service 97d2fb
      if (sz <= 0)
Packit Service 97d2fb
        {
Packit Service 97d2fb
          // It's not a diagnostic-worthy error for an elf file to lack build-id.
Packit Service 97d2fb
          // It might just be very old.
Packit Service 97d2fb
          elf_end (elf);
Packit Service 97d2fb
          return;
Packit Service 97d2fb
        }
Packit Service 97d2fb
Packit Service 97d2fb
      // build_id is a raw byte array; convert to hexadecimal *lowercase*
Packit Service 97d2fb
      unsigned char* build_id_bytes = (unsigned char*) build_id;
Packit Service 97d2fb
      for (ssize_t idx=0; idx
Packit Service 97d2fb
        {
Packit Service 97d2fb
          buildid += "0123456789abcdef"[build_id_bytes[idx] >> 4];
Packit Service 97d2fb
          buildid += "0123456789abcdef"[build_id_bytes[idx] & 0xf];
Packit Service 97d2fb
        }
Packit Service 97d2fb
Packit Service 97d2fb
      // now decide whether it's an executable - namely, any allocatable section has
Packit Service 97d2fb
      // PROGBITS;
Packit Service 97d2fb
      if (elf_type == ET_EXEC || elf_type == ET_DYN)
Packit Service 97d2fb
        {
Packit Service 97d2fb
          size_t shnum;
Packit Service 97d2fb
          int rc = elf_getshdrnum (elf, &shnum);
Packit Service 97d2fb
          if (rc < 0)
Packit Service 97d2fb
            throw elfutils_exception(rc, "getshdrnum");
Packit Service 97d2fb
Packit Service 97d2fb
          executable_p = false;
Packit Service 97d2fb
          for (size_t sc = 0; sc < shnum; sc++)
Packit Service 97d2fb
            {
Packit Service 97d2fb
              Elf_Scn *scn = elf_getscn (elf, sc);
Packit Service 97d2fb
              if (scn == NULL)
Packit Service 97d2fb
                continue;
Packit Service 97d2fb
Packit Service 97d2fb
              GElf_Shdr shdr_mem;
Packit Service 97d2fb
              GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
Packit Service 97d2fb
              if (shdr == NULL)
Packit Service 97d2fb
                continue;
Packit Service 97d2fb
Packit Service 97d2fb
              // allocated (loadable / vm-addr-assigned) section with available content?
Packit Service 97d2fb
              if ((shdr->sh_type == SHT_PROGBITS) && (shdr->sh_flags & SHF_ALLOC))
Packit Service 97d2fb
                {
Packit Service 97d2fb
                  if (verbose > 4)
Packit Service 97d2fb
                    obatched(clog) << "executable due to SHF_ALLOC SHT_PROGBITS sc=" << sc << endl;
Packit Service 97d2fb
                  executable_p = true;
Packit Service 97d2fb
                  break; // no need to keep looking for others
Packit Service 97d2fb
                }
Packit Service 97d2fb
            } // iterate over sections
Packit Service 97d2fb
        } // executable_p classification
Packit Service 97d2fb
Packit Service 97d2fb
      // now decide whether it's a debuginfo - namely, if it has any .debug* or .zdebug* sections
Packit Service 97d2fb
      // logic mostly stolen from fweimer@redhat.com's elfclassify drafts
Packit Service 97d2fb
      size_t shstrndx;
Packit Service 97d2fb
      int rc = elf_getshdrstrndx (elf, &shstrndx);
Packit Service 97d2fb
      if (rc < 0)
Packit Service 97d2fb
        throw elfutils_exception(rc, "getshdrstrndx");
Packit Service 97d2fb
Packit Service 97d2fb
      Elf_Scn *scn = NULL;
Packit Service 97d2fb
      while (true)
Packit Service 97d2fb
        {
Packit Service 97d2fb
          scn = elf_nextscn (elf, scn);
Packit Service 97d2fb
          if (scn == NULL)
Packit Service 97d2fb
            break;
Packit Service 97d2fb
          GElf_Shdr shdr_storage;
Packit Service 97d2fb
          GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_storage);
Packit Service 97d2fb
          if (shdr == NULL)
Packit Service 97d2fb
            break;
Packit Service 97d2fb
          const char *section_name = elf_strptr (elf, shstrndx, shdr->sh_name);
Packit Service 97d2fb
          if (section_name == NULL)
Packit Service 97d2fb
            break;
Packit Service 97d2fb
          if (strncmp(section_name, ".debug_line", 11) == 0 ||
Packit Service 97d2fb
              strncmp(section_name, ".zdebug_line", 12) == 0)
Packit Service 97d2fb
            {
Packit Service 97d2fb
              debuginfo_p = true;
Packit Service 97d2fb
              dwarf_extract_source_paths (elf, debug_sourcefiles);
Packit Service 97d2fb
              break; // expecting only one .*debug_line, so no need to look for others
Packit Service 97d2fb
            }
Packit Service 97d2fb
          else if (strncmp(section_name, ".debug_", 7) == 0 ||
Packit Service 97d2fb
                   strncmp(section_name, ".zdebug_", 8) == 0)
Packit Service 97d2fb
            {
Packit Service 97d2fb
              debuginfo_p = true;
Packit Service 97d2fb
              // NB: don't break; need to parse .debug_line for sources
Packit Service 97d2fb
            }
Packit Service 97d2fb
        }
Packit Service 97d2fb
    }
Packit Service 97d2fb
  catch (const reportable_exception& e)
Packit Service 97d2fb
    {
Packit Service 97d2fb
      e.report(clog);
Packit Service 97d2fb
    }
Packit Service 97d2fb
  elf_end (elf);
Packit Service 97d2fb
}
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
static void
Packit Service 97d2fb
scan_source_file (const string& rps, const stat_t& st,
Packit Service 97d2fb
                  sqlite_ps& ps_upsert_buildids,
Packit Service 97d2fb
                  sqlite_ps& ps_upsert_files,
Packit Service 97d2fb
                  sqlite_ps& ps_upsert_de,
Packit Service 97d2fb
                  sqlite_ps& ps_upsert_s,
Packit Service 97d2fb
                  sqlite_ps& ps_query,
Packit Service 97d2fb
                  sqlite_ps& ps_scan_done,
Packit Service 97d2fb
                  unsigned& fts_cached,
Packit Service 97d2fb
                  unsigned& fts_executable,
Packit Service 97d2fb
                  unsigned& fts_debuginfo,
Packit Service 97d2fb
                  unsigned& fts_sourcefiles)
Packit Service 97d2fb
{
Packit Service 97d2fb
  /* See if we know of it already. */
Packit Service 97d2fb
  int rc = ps_query
Packit Service 97d2fb
    .reset()
Packit Service 97d2fb
    .bind(1, rps)
Packit Service 97d2fb
    .bind(2, st.st_mtime)
Packit Service 97d2fb
    .step();
Packit Service 97d2fb
  ps_query.reset();
Packit Service 97d2fb
  if (rc == SQLITE_ROW) // i.e., a result, as opposed to DONE (no results)
Packit Service 97d2fb
    // no need to recheck a file/version we already know
Packit Service 97d2fb
    // specifically, no need to elf-begin a file we already determined is non-elf
Packit Service 97d2fb
    // (so is stored with buildid=NULL)
Packit Service 97d2fb
    {
Packit Service 97d2fb
      fts_cached++;
Packit Service 97d2fb
      return;
Packit Service 97d2fb
    }
Packit Service 97d2fb
Packit Service 97d2fb
  bool executable_p = false, debuginfo_p = false; // E and/or D
Packit Service 97d2fb
  string buildid;
Packit Service 97d2fb
  set<string> sourcefiles;
Packit Service 97d2fb
Packit Service 97d2fb
  int fd = open (rps.c_str(), O_RDONLY);
Packit Service 97d2fb
  try
Packit Service 97d2fb
    {
Packit Service 97d2fb
      if (fd >= 0)
Packit Service 97d2fb
        elf_classify (fd, executable_p, debuginfo_p, buildid, sourcefiles);
Packit Service 97d2fb
      else
Packit Service 97d2fb
        throw libc_exception(errno, string("open ") + rps);
Packit Service 97d2fb
      add_metric ("scanned_bytes_total","source","file",
Packit Service 97d2fb
                  st.st_size);
Packit Service 97d2fb
      inc_metric ("scanned_files_total","source","file");
Packit Service 97d2fb
    }
Packit Service 97d2fb
  // NB: we catch exceptions here too, so that we can
Packit Service 97d2fb
  // cache the corrupt-elf case (!executable_p &&
Packit Service 97d2fb
  // !debuginfo_p) just below, just as if we had an
Packit Service 97d2fb
  // EPERM error from open(2).
Packit Service 97d2fb
  catch (const reportable_exception& e)
Packit Service 97d2fb
    {
Packit Service 97d2fb
      e.report(clog);
Packit Service 97d2fb
    }
Packit Service 97d2fb
Packit Service 97d2fb
  if (fd >= 0)
Packit Service 97d2fb
    close (fd);
Packit Service 97d2fb
Packit Service 97d2fb
  // register this file name in the interning table
Packit Service 97d2fb
  ps_upsert_files
Packit Service 97d2fb
    .reset()
Packit Service 97d2fb
    .bind(1, rps)
Packit Service 97d2fb
    .step_ok_done();
Packit Service 97d2fb
Packit Service 97d2fb
  if (buildid == "")
Packit Service 97d2fb
    {
Packit Service 97d2fb
      // no point storing an elf file without buildid
Packit Service 97d2fb
      executable_p = false;
Packit Service 97d2fb
      debuginfo_p = false;
Packit Service 97d2fb
    }
Packit Service 97d2fb
  else
Packit Service 97d2fb
    {
Packit Service 97d2fb
      // register this build-id in the interning table
Packit Service 97d2fb
      ps_upsert_buildids
Packit Service 97d2fb
        .reset()
Packit Service 97d2fb
        .bind(1, buildid)
Packit Service 97d2fb
        .step_ok_done();
Packit Service 97d2fb
    }
Packit Service 97d2fb
Packit Service 97d2fb
  if (executable_p)
Packit Service 97d2fb
    fts_executable ++;
Packit Service 97d2fb
  if (debuginfo_p)
Packit Service 97d2fb
    fts_debuginfo ++;
Packit Service 97d2fb
  if (executable_p || debuginfo_p)
Packit Service 97d2fb
    {
Packit Service 97d2fb
      ps_upsert_de
Packit Service 97d2fb
        .reset()
Packit Service 97d2fb
        .bind(1, buildid)
Packit Service 97d2fb
        .bind(2, debuginfo_p ? 1 : 0)
Packit Service 97d2fb
        .bind(3, executable_p ? 1 : 0)
Packit Service 97d2fb
        .bind(4, rps)
Packit Service 97d2fb
        .bind(5, st.st_mtime)
Packit Service 97d2fb
        .step_ok_done();
Packit Service 97d2fb
    }
Packit Service 97d2fb
  if (executable_p)
Packit Service 97d2fb
    inc_metric("found_executable_total","source","files");
Packit Service 97d2fb
  if (debuginfo_p)
Packit Service 97d2fb
    inc_metric("found_debuginfo_total","source","files");
Packit Service 97d2fb
Packit Service 97d2fb
  if (sourcefiles.size() && buildid != "")
Packit Service 97d2fb
    {
Packit Service 97d2fb
      fts_sourcefiles += sourcefiles.size();
Packit Service 97d2fb
Packit Service 97d2fb
      for (auto&& dwarfsrc : sourcefiles)
Packit Service 97d2fb
        {
Packit Service 97d2fb
          char *srp = realpath(dwarfsrc.c_str(), NULL);
Packit Service 97d2fb
          if (srp == NULL) // also if DWZ unresolved dwarfsrc=""
Packit Service 97d2fb
            continue; // unresolvable files are not a serious problem
Packit Service 97d2fb
          // throw libc_exception(errno, "fts/file realpath " + srcpath);
Packit Service 97d2fb
          string srps = string(srp);
Packit Service 97d2fb
          free (srp);
Packit Service 97d2fb
Packit Service 97d2fb
          struct stat sfs;
Packit Service 97d2fb
          rc = stat(srps.c_str(), &sfs;;
Packit Service 97d2fb
          if (rc != 0)
Packit Service 97d2fb
            continue;
Packit Service 97d2fb
Packit Service 97d2fb
          if (verbose > 2)
Packit Service 97d2fb
            obatched(clog) << "recorded buildid=" << buildid << " file=" << srps
Packit Service 97d2fb
                           << " mtime=" << sfs.st_mtime
Packit Service 97d2fb
                           << " as source " << dwarfsrc << endl;
Packit Service 97d2fb
Packit Service 97d2fb
          ps_upsert_files
Packit Service 97d2fb
            .reset()
Packit Service 97d2fb
            .bind(1, srps)
Packit Service 97d2fb
            .step_ok_done();
Packit Service 97d2fb
Packit Service 97d2fb
          // PR25548: store canonicalized dwarfsrc path
Packit Service 97d2fb
          string dwarfsrc_canon = canon_pathname (dwarfsrc);
Packit Service 97d2fb
          if (dwarfsrc_canon != dwarfsrc)
Packit Service 97d2fb
            {
Packit Service 97d2fb
              if (verbose > 3)
Packit Service 97d2fb
                obatched(clog) << "canonicalized src=" << dwarfsrc << " alias=" << dwarfsrc_canon << endl;
Packit Service 97d2fb
            }
Packit Service 97d2fb
Packit Service 97d2fb
          ps_upsert_files
Packit Service 97d2fb
            .reset()
Packit Service 97d2fb
            .bind(1, dwarfsrc_canon)
Packit Service 97d2fb
            .step_ok_done();
Packit Service 97d2fb
Packit Service 97d2fb
          ps_upsert_s
Packit Service 97d2fb
            .reset()
Packit Service 97d2fb
            .bind(1, buildid)
Packit Service 97d2fb
            .bind(2, dwarfsrc_canon)
Packit Service 97d2fb
            .bind(3, srps)
Packit Service 97d2fb
            .bind(4, sfs.st_mtime)
Packit Service 97d2fb
            .step_ok_done();
Packit Service 97d2fb
Packit Service 97d2fb
          inc_metric("found_sourcerefs_total","source","files");
Packit Service 97d2fb
        }
Packit Service 97d2fb
    }
Packit Service 97d2fb
Packit Service 97d2fb
  ps_scan_done
Packit Service 97d2fb
    .reset()
Packit Service 97d2fb
    .bind(1, rps)
Packit Service 97d2fb
    .bind(2, st.st_mtime)
Packit Service 97d2fb
    .bind(3, st.st_size)
Packit Service 97d2fb
    .step_ok_done();
Packit Service 97d2fb
Packit Service 97d2fb
  if (verbose > 2)
Packit Service 97d2fb
    obatched(clog) << "recorded buildid=" << buildid << " file=" << rps
Packit Service 97d2fb
                   << " mtime=" << st.st_mtime << " atype="
Packit Service 97d2fb
                   << (executable_p ? "E" : "")
Packit Service 97d2fb
                   << (debuginfo_p ? "D" : "") << endl;
Packit Service 97d2fb
}
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
// Analyze given archive file of given age; record buildids / exec/debuginfo-ness of its
Packit Service 97d2fb
// constituent files with given upsert statements.
Packit Service 97d2fb
static void
Packit Service 97d2fb
archive_classify (const string& rps, string& archive_extension,
Packit Service 97d2fb
                  sqlite_ps& ps_upsert_buildids, sqlite_ps& ps_upsert_files,
Packit Service 97d2fb
                  sqlite_ps& ps_upsert_de, sqlite_ps& ps_upsert_sref, sqlite_ps& ps_upsert_sdef,
Packit Service 97d2fb
                  time_t mtime,
Packit Service 97d2fb
                  unsigned& fts_executable, unsigned& fts_debuginfo, unsigned& fts_sref, unsigned& fts_sdef,
Packit Service 97d2fb
                  bool& fts_sref_complete_p)
Packit Service 97d2fb
{
Packit Service 97d2fb
  string archive_decoder = "/dev/null";
Packit Service 97d2fb
  for (auto&& arch : scan_archives)
Packit Service 97d2fb
    if (string_endswith(rps, arch.first))
Packit Service 97d2fb
      {
Packit Service 97d2fb
        archive_extension = arch.first;
Packit Service 97d2fb
        archive_decoder = arch.second;
Packit Service 97d2fb
      }
Packit Service 97d2fb
Packit Service 97d2fb
  FILE* fp;
Packit Service 97d2fb
  defer_dtor<FILE*,int>::dtor_fn dfn;
Packit Service 97d2fb
  if (archive_decoder != "cat")
Packit Service 97d2fb
    {
Packit Service 97d2fb
      string popen_cmd = archive_decoder + " " + shell_escape(rps);
Packit Service 97d2fb
      fp = popen (popen_cmd.c_str(), "r"); // "e" O_CLOEXEC?
Packit Service 97d2fb
      dfn = pclose;
Packit Service 97d2fb
      if (fp == NULL)
Packit Service 97d2fb
        throw libc_exception (errno, string("popen ") + popen_cmd);
Packit Service 97d2fb
    }
Packit Service 97d2fb
  else
Packit Service 97d2fb
    {
Packit Service 97d2fb
      fp = fopen (rps.c_str(), "r");
Packit Service 97d2fb
      dfn = fclose;
Packit Service 97d2fb
      if (fp == NULL)
Packit Service 97d2fb
        throw libc_exception (errno, string("fopen ") + rps);
Packit Service 97d2fb
    }
Packit Service 97d2fb
  defer_dtor<FILE*,int> fp_closer (fp, dfn);
Packit Service 97d2fb
Packit Service 97d2fb
  struct archive *a;
Packit Service 97d2fb
  a = archive_read_new();
Packit Service 97d2fb
  if (a == NULL)
Packit Service 97d2fb
    throw archive_exception("cannot create archive reader");
Packit Service 97d2fb
  defer_dtor<struct archive*,int> archive_closer (a, archive_read_free);
Packit Service 97d2fb
Packit Service 97d2fb
  int rc = archive_read_support_format_all(a);
Packit Service 97d2fb
  if (rc != ARCHIVE_OK)
Packit Service 97d2fb
    throw archive_exception(a, "cannot select all formats");
Packit Service 97d2fb
  rc = archive_read_support_filter_all(a);
Packit Service 97d2fb
  if (rc != ARCHIVE_OK)
Packit Service 97d2fb
    throw archive_exception(a, "cannot select all filters");
Packit Service 97d2fb
Packit Service 97d2fb
  rc = archive_read_open_FILE (a, fp);
Packit Service 97d2fb
  if (rc != ARCHIVE_OK)
Packit Service 97d2fb
    throw archive_exception(a, "cannot open archive from pipe");
Packit Service 97d2fb
Packit Service 97d2fb
  if (verbose > 3)
Packit Service 97d2fb
    obatched(clog) << "libarchive scanning " << rps << endl;
Packit Service 97d2fb
Packit Service 97d2fb
  while(1) // parse archive entries
Packit Service 97d2fb
    {
Packit Service 97d2fb
    if (interrupted)
Packit Service 97d2fb
      break;
Packit Service 97d2fb
Packit Service 97d2fb
    try
Packit Service 97d2fb
        {
Packit Service 97d2fb
          struct archive_entry *e;
Packit Service 97d2fb
          rc = archive_read_next_header (a, &e);
Packit Service 97d2fb
          if (rc != ARCHIVE_OK)
Packit Service 97d2fb
            break;
Packit Service 97d2fb
Packit Service 97d2fb
          if (! S_ISREG(archive_entry_mode (e))) // skip non-files completely
Packit Service 97d2fb
            continue;
Packit Service 97d2fb
Packit Service 97d2fb
          string fn = canonicalized_archive_entry_pathname (e);
Packit Service 97d2fb
Packit Service 97d2fb
          if (verbose > 3)
Packit Service 97d2fb
            obatched(clog) << "libarchive checking " << fn << endl;
Packit Service 97d2fb
Packit Service 97d2fb
          // extract this file to a temporary file
Packit Service 97d2fb
          char* tmppath = NULL;
Packit Service 97d2fb
          rc = asprintf (&tmppath, "%s/debuginfod.XXXXXX", tmpdir.c_str());
Packit Service 97d2fb
          if (rc < 0)
Packit Service 97d2fb
            throw libc_exception (ENOMEM, "cannot allocate tmppath");
Packit Service 97d2fb
          defer_dtor<void*,void> tmmpath_freer (tmppath, free);
Packit Service 97d2fb
          int fd = mkstemp (tmppath);
Packit Service 97d2fb
          if (fd < 0)
Packit Service 97d2fb
            throw libc_exception (errno, "cannot create temporary file");
Packit Service 97d2fb
          unlink (tmppath); // unlink now so OS will release the file as soon as we close the fd
Packit Service 97d2fb
          defer_dtor<int,int> minifd_closer (fd, close);
Packit Service 97d2fb
Packit Service 97d2fb
          rc = archive_read_data_into_fd (a, fd);
Packit Service 97d2fb
          if (rc != ARCHIVE_OK)
Packit Service 97d2fb
            throw archive_exception(a, "cannot extract file");
Packit Service 97d2fb
Packit Service 97d2fb
          // finally ... time to run elf_classify on this bad boy and update the database
Packit Service 97d2fb
          bool executable_p = false, debuginfo_p = false;
Packit Service 97d2fb
          string buildid;
Packit Service 97d2fb
          set<string> sourcefiles;
Packit Service 97d2fb
          elf_classify (fd, executable_p, debuginfo_p, buildid, sourcefiles);
Packit Service 97d2fb
          // NB: might throw
Packit Service 97d2fb
Packit Service 97d2fb
          if (buildid != "") // intern buildid
Packit Service 97d2fb
            {
Packit Service 97d2fb
              ps_upsert_buildids
Packit Service 97d2fb
                .reset()
Packit Service 97d2fb
                .bind(1, buildid)
Packit Service 97d2fb
                .step_ok_done();
Packit Service 97d2fb
            }
Packit Service 97d2fb
Packit Service 97d2fb
          ps_upsert_files // register this rpm constituent file name in interning table
Packit Service 97d2fb
            .reset()
Packit Service 97d2fb
            .bind(1, fn)
Packit Service 97d2fb
            .step_ok_done();
Packit Service 97d2fb
Packit Service 97d2fb
          if (sourcefiles.size() > 0) // sref records needed
Packit Service 97d2fb
            {
Packit Service 97d2fb
              // NB: we intern each source file once.  Once raw, as it
Packit Service 97d2fb
              // appears in the DWARF file list coming back from
Packit Service 97d2fb
              // elf_classify() - because it'll end up in the
Packit Service 97d2fb
              // _norm.artifactsrc column.  We don't also put another
Packit Service 97d2fb
              // version with a '.' at the front, even though that's
Packit Service 97d2fb
              // how rpm/cpio packs names, because we hide that from
Packit Service 97d2fb
              // the database for storage efficiency.
Packit Service 97d2fb
Packit Service 97d2fb
              for (auto&& s : sourcefiles)
Packit Service 97d2fb
                {
Packit Service 97d2fb
                  if (s == "")
Packit Service 97d2fb
                    {
Packit Service 97d2fb
                      fts_sref_complete_p = false;
Packit Service 97d2fb
                      continue;
Packit Service 97d2fb
                    }
Packit Service 97d2fb
Packit Service 97d2fb
                  // PR25548: store canonicalized source path
Packit Service 97d2fb
                  const string& dwarfsrc = s;
Packit Service 97d2fb
                  string dwarfsrc_canon = canon_pathname (dwarfsrc);
Packit Service 97d2fb
                  if (dwarfsrc_canon != dwarfsrc)
Packit Service 97d2fb
                    {
Packit Service 97d2fb
                      if (verbose > 3)
Packit Service 97d2fb
                        obatched(clog) << "canonicalized src=" << dwarfsrc << " alias=" << dwarfsrc_canon << endl;
Packit Service 97d2fb
                    }
Packit Service 97d2fb
Packit Service 97d2fb
                  ps_upsert_files
Packit Service 97d2fb
                    .reset()
Packit Service 97d2fb
                    .bind(1, dwarfsrc_canon)
Packit Service 97d2fb
                    .step_ok_done();
Packit Service 97d2fb
Packit Service 97d2fb
                  ps_upsert_sref
Packit Service 97d2fb
                    .reset()
Packit Service 97d2fb
                    .bind(1, buildid)
Packit Service 97d2fb
                    .bind(2, dwarfsrc_canon)
Packit Service 97d2fb
                    .step_ok_done();
Packit Service 97d2fb
Packit Service 97d2fb
                  fts_sref ++;
Packit Service 97d2fb
                }
Packit Service 97d2fb
            }
Packit Service 97d2fb
Packit Service 97d2fb
          if (executable_p)
Packit Service 97d2fb
            fts_executable ++;
Packit Service 97d2fb
          if (debuginfo_p)
Packit Service 97d2fb
            fts_debuginfo ++;
Packit Service 97d2fb
Packit Service 97d2fb
          if (executable_p || debuginfo_p)
Packit Service 97d2fb
            {
Packit Service 97d2fb
              ps_upsert_de
Packit Service 97d2fb
                .reset()
Packit Service 97d2fb
                .bind(1, buildid)
Packit Service 97d2fb
                .bind(2, debuginfo_p ? 1 : 0)
Packit Service 97d2fb
                .bind(3, executable_p ? 1 : 0)
Packit Service 97d2fb
                .bind(4, rps)
Packit Service 97d2fb
                .bind(5, mtime)
Packit Service 97d2fb
                .bind(6, fn)
Packit Service 97d2fb
                .step_ok_done();
Packit Service 97d2fb
            }
Packit Service 97d2fb
          else // potential source - sdef record
Packit Service 97d2fb
            {
Packit Service 97d2fb
              fts_sdef ++;
Packit Service 97d2fb
              ps_upsert_sdef
Packit Service 97d2fb
                .reset()
Packit Service 97d2fb
                .bind(1, rps)
Packit Service 97d2fb
                .bind(2, mtime)
Packit Service 97d2fb
                .bind(3, fn)
Packit Service 97d2fb
                .step_ok_done();
Packit Service 97d2fb
            }
Packit Service 97d2fb
Packit Service 97d2fb
          if ((verbose > 2) && (executable_p || debuginfo_p))
Packit Service 97d2fb
            obatched(clog) << "recorded buildid=" << buildid << " rpm=" << rps << " file=" << fn
Packit Service 97d2fb
                           << " mtime=" << mtime << " atype="
Packit Service 97d2fb
                           << (executable_p ? "E" : "")
Packit Service 97d2fb
                           << (debuginfo_p ? "D" : "")
Packit Service 97d2fb
                           << " sourcefiles=" << sourcefiles.size() << endl;
Packit Service 97d2fb
Packit Service 97d2fb
        }
Packit Service 97d2fb
      catch (const reportable_exception& e)
Packit Service 97d2fb
        {
Packit Service 97d2fb
          e.report(clog);
Packit Service 97d2fb
        }
Packit Service 97d2fb
    }
Packit Service 97d2fb
}
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
// scan for archive files such as .rpm
Packit Service 97d2fb
static void
Packit Service 97d2fb
scan_archive_file (const string& rps, const stat_t& st,
Packit Service 97d2fb
                   sqlite_ps& ps_upsert_buildids,
Packit Service 97d2fb
                   sqlite_ps& ps_upsert_files,
Packit Service 97d2fb
                   sqlite_ps& ps_upsert_de,
Packit Service 97d2fb
                   sqlite_ps& ps_upsert_sref,
Packit Service 97d2fb
                   sqlite_ps& ps_upsert_sdef,
Packit Service 97d2fb
                   sqlite_ps& ps_query,
Packit Service 97d2fb
                   sqlite_ps& ps_scan_done,
Packit Service 97d2fb
                   unsigned& fts_cached,
Packit Service 97d2fb
                   unsigned& fts_executable,
Packit Service 97d2fb
                   unsigned& fts_debuginfo,
Packit Service 97d2fb
                   unsigned& fts_sref,
Packit Service 97d2fb
                   unsigned& fts_sdef)
Packit Service 97d2fb
{
Packit Service 97d2fb
  /* See if we know of it already. */
Packit Service 97d2fb
  int rc = ps_query
Packit Service 97d2fb
    .reset()
Packit Service 97d2fb
    .bind(1, rps)
Packit Service 97d2fb
    .bind(2, st.st_mtime)
Packit Service 97d2fb
    .step();
Packit Service 97d2fb
  ps_query.reset();
Packit Service 97d2fb
  if (rc == SQLITE_ROW) // i.e., a result, as opposed to DONE (no results)
Packit Service 97d2fb
    // no need to recheck a file/version we already know
Packit Service 97d2fb
    // specifically, no need to parse this archive again, since we already have
Packit Service 97d2fb
    // it as a D or E or S record,
Packit Service 97d2fb
    // (so is stored with buildid=NULL)
Packit Service 97d2fb
    {
Packit Service 97d2fb
      fts_cached ++;
Packit Service 97d2fb
      return;
Packit Service 97d2fb
    }
Packit Service 97d2fb
Packit Service 97d2fb
  // intern the archive file name
Packit Service 97d2fb
  ps_upsert_files
Packit Service 97d2fb
    .reset()
Packit Service 97d2fb
    .bind(1, rps)
Packit Service 97d2fb
    .step_ok_done();
Packit Service 97d2fb
Packit Service 97d2fb
  // extract the archive contents
Packit Service 97d2fb
  unsigned my_fts_executable = 0, my_fts_debuginfo = 0, my_fts_sref = 0, my_fts_sdef = 0;
Packit Service 97d2fb
  bool my_fts_sref_complete_p = true;
Packit Service 97d2fb
  try
Packit Service 97d2fb
    {
Packit Service 97d2fb
      string archive_extension;
Packit Service 97d2fb
      archive_classify (rps, archive_extension,
Packit Service 97d2fb
                        ps_upsert_buildids, ps_upsert_files,
Packit Service 97d2fb
                        ps_upsert_de, ps_upsert_sref, ps_upsert_sdef, // dalt
Packit Service 97d2fb
                        st.st_mtime,
Packit Service 97d2fb
                        my_fts_executable, my_fts_debuginfo, my_fts_sref, my_fts_sdef,
Packit Service 97d2fb
                        my_fts_sref_complete_p);
Packit Service 97d2fb
      add_metric ("scanned_bytes_total","source",archive_extension + " archive",
Packit Service 97d2fb
                  st.st_size);
Packit Service 97d2fb
      inc_metric ("scanned_files_total","source",archive_extension + " archive");
Packit Service 97d2fb
      add_metric("found_debuginfo_total","source",archive_extension + " archive",
Packit Service 97d2fb
                 my_fts_debuginfo);
Packit Service 97d2fb
      add_metric("found_executable_total","source",archive_extension + " archive",
Packit Service 97d2fb
                 my_fts_executable);
Packit Service 97d2fb
      add_metric("found_sourcerefs_total","source",archive_extension + " archive",
Packit Service 97d2fb
                 my_fts_sref);
Packit Service 97d2fb
    }
Packit Service 97d2fb
  catch (const reportable_exception& e)
Packit Service 97d2fb
    {
Packit Service 97d2fb
      e.report(clog);
Packit Service 97d2fb
    }
Packit Service 97d2fb
Packit Service 97d2fb
  if (verbose > 2)
Packit Service 97d2fb
    obatched(clog) << "scanned archive=" << rps
Packit Service 97d2fb
                   << " mtime=" << st.st_mtime
Packit Service 97d2fb
                   << " executables=" << my_fts_executable
Packit Service 97d2fb
                   << " debuginfos=" << my_fts_debuginfo
Packit Service 97d2fb
                   << " srefs=" << my_fts_sref
Packit Service 97d2fb
                   << " sdefs=" << my_fts_sdef
Packit Service 97d2fb
                   << endl;
Packit Service 97d2fb
Packit Service 97d2fb
  fts_executable += my_fts_executable;
Packit Service 97d2fb
  fts_debuginfo += my_fts_debuginfo;
Packit Service 97d2fb
  fts_sref += my_fts_sref;
Packit Service 97d2fb
  fts_sdef += my_fts_sdef;
Packit Service 97d2fb
Packit Service 97d2fb
  if (my_fts_sref_complete_p) // leave incomplete?
Packit Service 97d2fb
    ps_scan_done
Packit Service 97d2fb
      .reset()
Packit Service 97d2fb
      .bind(1, rps)
Packit Service 97d2fb
      .bind(2, st.st_mtime)
Packit Service 97d2fb
      .bind(3, st.st_size)
Packit Service 97d2fb
      .step_ok_done();
Packit Service 97d2fb
}
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
////////////////////////////////////////////////////////////////////////
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
// The thread that consumes file names off of the scanq.  We hold
Packit Service 97d2fb
// the persistent sqlite_ps's at this level and delegate file/archive
Packit Service 97d2fb
// scanning to other functions.
Packit Service 97d2fb
static void*
Packit Service 97d2fb
thread_main_scanner (void* arg)
Packit Service 97d2fb
{
Packit Service 97d2fb
  (void) arg;
Packit Service 97d2fb
Packit Service 97d2fb
  // all the prepared statements fit to use, the _f_ set:
Packit Service 97d2fb
  sqlite_ps ps_f_upsert_buildids (db, "file-buildids-intern", "insert or ignore into " BUILDIDS "_buildids VALUES (NULL, ?);");
Packit Service 97d2fb
  sqlite_ps ps_f_upsert_files (db, "file-files-intern", "insert or ignore into " BUILDIDS "_files VALUES (NULL, ?);");
Packit Service 97d2fb
  sqlite_ps ps_f_upsert_de (db, "file-de-upsert",
Packit Service 97d2fb
                          "insert or ignore into " BUILDIDS "_f_de "
Packit Service 97d2fb
                          "(buildid, debuginfo_p, executable_p, file, mtime) "
Packit Service 97d2fb
                          "values ((select id from " BUILDIDS "_buildids where hex = ?),"
Packit Service 97d2fb
                          "        ?,?,"
Packit Service 97d2fb
                          "        (select id from " BUILDIDS "_files where name = ?), ?);");
Packit Service 97d2fb
  sqlite_ps ps_f_upsert_s (db, "file-s-upsert",
Packit Service 97d2fb
                         "insert or ignore into " BUILDIDS "_f_s "
Packit Service 97d2fb
                         "(buildid, artifactsrc, file, mtime) "
Packit Service 97d2fb
                         "values ((select id from " BUILDIDS "_buildids where hex = ?),"
Packit Service 97d2fb
                         "        (select id from " BUILDIDS "_files where name = ?),"
Packit Service 97d2fb
                         "        (select id from " BUILDIDS "_files where name = ?),"
Packit Service 97d2fb
                         "        ?);");
Packit Service 97d2fb
  sqlite_ps ps_f_query (db, "file-negativehit-find",
Packit Service 97d2fb
                        "select 1 from " BUILDIDS "_file_mtime_scanned where sourcetype = 'F' "
Packit Service 97d2fb
                        "and file = (select id from " BUILDIDS "_files where name = ?) and mtime = ?;");
Packit Service 97d2fb
  sqlite_ps ps_f_scan_done (db, "file-scanned",
Packit Service 97d2fb
                          "insert or ignore into " BUILDIDS "_file_mtime_scanned (sourcetype, file, mtime, size)"
Packit Service 97d2fb
                          "values ('F', (select id from " BUILDIDS "_files where name = ?), ?, ?);");
Packit Service 97d2fb
Packit Service 97d2fb
  // and now for the _r_ set
Packit Service 97d2fb
  sqlite_ps ps_r_upsert_buildids (db, "rpm-buildid-intern", "insert or ignore into " BUILDIDS "_buildids VALUES (NULL, ?);");
Packit Service 97d2fb
  sqlite_ps ps_r_upsert_files (db, "rpm-file-intern", "insert or ignore into " BUILDIDS "_files VALUES (NULL, ?);");
Packit Service 97d2fb
  sqlite_ps ps_r_upsert_de (db, "rpm-de-insert",
Packit Service 97d2fb
                          "insert or ignore into " BUILDIDS "_r_de (buildid, debuginfo_p, executable_p, file, mtime, content) values ("
Packit Service 97d2fb
                          "(select id from " BUILDIDS "_buildids where hex = ?), ?, ?, "
Packit Service 97d2fb
                          "(select id from " BUILDIDS "_files where name = ?), ?, "
Packit Service 97d2fb
                          "(select id from " BUILDIDS "_files where name = ?));");
Packit Service 97d2fb
  sqlite_ps ps_r_upsert_sref (db, "rpm-sref-insert",
Packit Service 97d2fb
                            "insert or ignore into " BUILDIDS "_r_sref (buildid, artifactsrc) values ("
Packit Service 97d2fb
                            "(select id from " BUILDIDS "_buildids where hex = ?), "
Packit Service 97d2fb
                            "(select id from " BUILDIDS "_files where name = ?));");
Packit Service 97d2fb
  sqlite_ps ps_r_upsert_sdef (db, "rpm-sdef-insert",
Packit Service 97d2fb
                            "insert or ignore into " BUILDIDS "_r_sdef (file, mtime, content) values ("
Packit Service 97d2fb
                            "(select id from " BUILDIDS "_files where name = ?), ?,"
Packit Service 97d2fb
                            "(select id from " BUILDIDS "_files where name = ?));");
Packit Service 97d2fb
  sqlite_ps ps_r_query (db, "rpm-negativehit-query",
Packit Service 97d2fb
                      "select 1 from " BUILDIDS "_file_mtime_scanned where "
Packit Service 97d2fb
                      "sourcetype = 'R' and file = (select id from " BUILDIDS "_files where name = ?) and mtime = ?;");
Packit Service 97d2fb
  sqlite_ps ps_r_scan_done (db, "rpm-scanned",
Packit Service 97d2fb
                          "insert or ignore into " BUILDIDS "_file_mtime_scanned (sourcetype, file, mtime, size)"
Packit Service 97d2fb
                          "values ('R', (select id from " BUILDIDS "_files where name = ?), ?, ?);");
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
  unsigned fts_cached = 0, fts_executable = 0, fts_debuginfo = 0, fts_sourcefiles = 0;
Packit Service 97d2fb
  unsigned fts_sref = 0, fts_sdef = 0;
Packit Service 97d2fb
Packit Service 97d2fb
  add_metric("thread_count", "role", "scan", 1);
Packit Service 97d2fb
  add_metric("thread_busy", "role", "scan", 1);
Packit Service 97d2fb
  while (! interrupted)
Packit Service 97d2fb
    {
Packit Service 97d2fb
      scan_payload p;
Packit Service 97d2fb
Packit Service 97d2fb
      add_metric("thread_busy", "role", "scan", -1);
Packit Service 97d2fb
      bool gotone = scanq.wait_front(p);
Packit Service 97d2fb
      add_metric("thread_busy", "role", "scan", 1);
Packit Service 97d2fb
Packit Service 97d2fb
      if (! gotone) continue; // go back to waiting
Packit Service 97d2fb
Packit Service 97d2fb
      try
Packit Service 97d2fb
        {
Packit Service 97d2fb
          bool scan_archive = false;
Packit Service 97d2fb
          for (auto&& arch : scan_archives)
Packit Service 97d2fb
            if (string_endswith(p.first, arch.first))
Packit Service 97d2fb
              scan_archive = true;
Packit Service 97d2fb
Packit Service 97d2fb
          if (scan_archive)
Packit Service 97d2fb
            scan_archive_file (p.first, p.second,
Packit Service 97d2fb
                               ps_r_upsert_buildids,
Packit Service 97d2fb
                               ps_r_upsert_files,
Packit Service 97d2fb
                               ps_r_upsert_de,
Packit Service 97d2fb
                               ps_r_upsert_sref,
Packit Service 97d2fb
                               ps_r_upsert_sdef,
Packit Service 97d2fb
                               ps_r_query,
Packit Service 97d2fb
                               ps_r_scan_done,
Packit Service 97d2fb
                               fts_cached,
Packit Service 97d2fb
                               fts_executable,
Packit Service 97d2fb
                               fts_debuginfo,
Packit Service 97d2fb
                               fts_sref,
Packit Service 97d2fb
                               fts_sdef);
Packit Service 97d2fb
Packit Service 97d2fb
          if (scan_files) // NB: maybe "else if" ?
Packit Service 97d2fb
            scan_source_file (p.first, p.second,
Packit Service 97d2fb
                              ps_f_upsert_buildids,
Packit Service 97d2fb
                              ps_f_upsert_files,
Packit Service 97d2fb
                              ps_f_upsert_de,
Packit Service 97d2fb
                              ps_f_upsert_s,
Packit Service 97d2fb
                              ps_f_query,
Packit Service 97d2fb
                              ps_f_scan_done,
Packit Service 97d2fb
                              fts_cached, fts_executable, fts_debuginfo, fts_sourcefiles);
Packit Service 97d2fb
        }
Packit Service 97d2fb
      catch (const reportable_exception& e)
Packit Service 97d2fb
        {
Packit Service 97d2fb
          e.report(cerr);
Packit Service 97d2fb
        }
Packit Service 97d2fb
Packit Service 97d2fb
      // finished a scanning step -- not a "loop", because we just
Packit Service 97d2fb
      // consume the traversal loop's work, whenever
Packit Service 97d2fb
      inc_metric("thread_work_total","role","scan");
Packit Service 97d2fb
    }
Packit Service 97d2fb
Packit Service 97d2fb
  add_metric("thread_busy", "role", "scan", -1);
Packit Service 97d2fb
  return 0;
Packit Service 97d2fb
}
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
// The thread that traverses all the source_paths and enqueues all the
Packit Service 97d2fb
// matching files into the file/archive scan queue.
Packit Service 97d2fb
static void
Packit Service 97d2fb
scan_source_paths()
Packit Service 97d2fb
{
Packit Service 97d2fb
  // NB: fedora 31 glibc/fts(3) crashes inside fts_read() on empty
Packit Service 97d2fb
  // path list.
Packit Service 97d2fb
  if (source_paths.empty())
Packit Service 97d2fb
    return;
Packit Service 97d2fb
Packit Service 97d2fb
  // Turn the source_paths into an fts(3)-compatible char**.  Since
Packit Service 97d2fb
  // source_paths[] does not change after argv processing, the
Packit Service 97d2fb
  // c_str()'s are safe to keep around awile.
Packit Service 97d2fb
  vector<const char *> sps;
Packit Service 97d2fb
  for (auto&& sp: source_paths)
Packit Service 97d2fb
    sps.push_back(sp.c_str());
Packit Service 97d2fb
  sps.push_back(NULL);
Packit Service 97d2fb
Packit Service 97d2fb
  FTS *fts = fts_open ((char * const *)sps.data(),
Packit Service 97d2fb
                      (traverse_logical ? FTS_LOGICAL : FTS_PHYSICAL|FTS_XDEV)
Packit Service 97d2fb
                      | FTS_NOCHDIR /* multithreaded */,
Packit Service 97d2fb
                      NULL);
Packit Service 97d2fb
  if (fts == NULL)
Packit Service 97d2fb
    throw libc_exception(errno, "cannot fts_open");
Packit Service 97d2fb
  defer_dtor<FTS*,int> fts_cleanup (fts, fts_close);
Packit Service 97d2fb
Packit Service 97d2fb
  struct timeval tv_start, tv_end;
Packit Service 97d2fb
  gettimeofday (&tv_start, NULL);
Packit Service 97d2fb
  unsigned fts_scanned = 0, fts_regex = 0;
Packit Service 97d2fb
Packit Service 97d2fb
  FTSENT *f;
Packit Service 97d2fb
  while ((f = fts_read (fts)) != NULL)
Packit Service 97d2fb
  {
Packit Service 97d2fb
    if (interrupted) break;
Packit Service 97d2fb
Packit Service 97d2fb
    if (sigusr2 != forced_groom_count) // stop early if groom triggered 
Packit Service 97d2fb
      {
Packit Service 97d2fb
        scanq.clear(); // clear previously issued work for scanner threads
Packit Service 97d2fb
        break;
Packit Service 97d2fb
      }
Packit Service 97d2fb
    
Packit Service 97d2fb
    fts_scanned ++;
Packit Service 97d2fb
Packit Service 97d2fb
    if (verbose > 2)
Packit Service 97d2fb
      obatched(clog) << "fts traversing " << f->fts_path << endl;
Packit Service 97d2fb
Packit Service 97d2fb
    switch (f->fts_info)
Packit Service 97d2fb
      {
Packit Service 97d2fb
      case FTS_F:
Packit Service 97d2fb
        {
Packit Service 97d2fb
          /* Found a file.  Convert it to an absolute path, so
Packit Service 97d2fb
             the buildid database does not have relative path
Packit Service 97d2fb
             names that are unresolvable from a subsequent run
Packit Service 97d2fb
             in a different cwd. */
Packit Service 97d2fb
          char *rp = realpath(f->fts_path, NULL);
Packit Service 97d2fb
          if (rp == NULL)
Packit Service 97d2fb
            continue; // ignore dangling symlink or such
Packit Service 97d2fb
          string rps = string(rp);
Packit Service 97d2fb
          free (rp);
Packit Service 97d2fb
          
Packit Service 97d2fb
          bool ri = !regexec (&file_include_regex, rps.c_str(), 0, 0, 0);
Packit Service 97d2fb
          bool rx = !regexec (&file_exclude_regex, rps.c_str(), 0, 0, 0);
Packit Service 97d2fb
          if (!ri || rx)
Packit Service 97d2fb
            {
Packit Service 97d2fb
              if (verbose > 3)
Packit Service 97d2fb
                obatched(clog) << "fts skipped by regex "
Packit Service 97d2fb
                               << (!ri ? "I" : "") << (rx ? "X" : "") << endl;
Packit Service 97d2fb
              fts_regex ++;
Packit Service 97d2fb
              if (!ri)
Packit Service 97d2fb
                inc_metric("traversed_total","type","file-skipped-I");
Packit Service 97d2fb
              if (rx)
Packit Service 97d2fb
                inc_metric("traversed_total","type","file-skipped-X");
Packit Service 97d2fb
            }
Packit Service 97d2fb
          else
Packit Service 97d2fb
            {
Packit Service 97d2fb
              scanq.push_back (make_pair(rps, *f->fts_statp));
Packit Service 97d2fb
              inc_metric("traversed_total","type","file");
Packit Service 97d2fb
            }
Packit Service 97d2fb
        }
Packit Service 97d2fb
        break;
Packit Service 97d2fb
Packit Service 97d2fb
      case FTS_ERR:
Packit Service 97d2fb
      case FTS_NS:
Packit Service 97d2fb
        // report on some types of errors because they may reflect fixable misconfiguration
Packit Service 97d2fb
        {
Packit Service 97d2fb
          auto x = libc_exception(f->fts_errno, string("fts traversal ") + string(f->fts_path));
Packit Service 97d2fb
          x.report(cerr);
Packit Service 97d2fb
        }
Packit Service 97d2fb
        inc_metric("traversed_total","type","error");
Packit Service 97d2fb
        break;
Packit Service 97d2fb
Packit Service 97d2fb
      case FTS_SL: // ignore, but count because debuginfod -L would traverse these
Packit Service 97d2fb
        inc_metric("traversed_total","type","symlink");
Packit Service 97d2fb
        break;
Packit Service 97d2fb
Packit Service 97d2fb
      case FTS_D: // ignore
Packit Service 97d2fb
        inc_metric("traversed_total","type","directory");
Packit Service 97d2fb
        break;
Packit Service 97d2fb
        
Packit Service 97d2fb
      default: // ignore
Packit Service 97d2fb
        inc_metric("traversed_total","type","other");
Packit Service 97d2fb
        break;
Packit Service 97d2fb
      }
Packit Service 97d2fb
  }
Packit Service 97d2fb
  gettimeofday (&tv_end, NULL);
Packit Service 97d2fb
  double deltas = (tv_end.tv_sec - tv_start.tv_sec) + (tv_end.tv_usec - tv_start.tv_usec)*0.000001;
Packit Service 97d2fb
Packit Service 97d2fb
  obatched(clog) << "fts traversed source paths in " << deltas << "s, scanned=" << fts_scanned
Packit Service 97d2fb
                 << ", regex-skipped=" << fts_regex << endl;
Packit Service 97d2fb
}
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
static void*
Packit Service 97d2fb
thread_main_fts_source_paths (void* arg)
Packit Service 97d2fb
{
Packit Service 97d2fb
  (void) arg; // ignore; we operate on global data
Packit Service 97d2fb
Packit Service 97d2fb
  set_metric("thread_tid", "role","traverse", tid());
Packit Service 97d2fb
  add_metric("thread_count", "role", "traverse", 1);
Packit Service 97d2fb
Packit Service 97d2fb
  time_t last_rescan = 0;
Packit Service 97d2fb
Packit Service 97d2fb
  while (! interrupted)
Packit Service 97d2fb
    {
Packit Service 97d2fb
      sleep (1);
Packit Service 97d2fb
      scanq.wait_idle(); // don't start a new traversal while scanners haven't finished the job
Packit Service 97d2fb
      scanq.done_idle(); // release the hounds
Packit Service 97d2fb
      if (interrupted) break;
Packit Service 97d2fb
Packit Service 97d2fb
      time_t now = time(NULL);
Packit Service 97d2fb
      bool rescan_now = false;
Packit Service 97d2fb
      if (last_rescan == 0) // at least one initial rescan is documented even for -t0
Packit Service 97d2fb
        rescan_now = true;
Packit Service 97d2fb
      if (rescan_s > 0 && (long)now > (long)(last_rescan + rescan_s))
Packit Service 97d2fb
        rescan_now = true;
Packit Service 97d2fb
      if (sigusr1 != forced_rescan_count)
Packit Service 97d2fb
        {
Packit Service 97d2fb
          forced_rescan_count = sigusr1;
Packit Service 97d2fb
          rescan_now = true;
Packit Service 97d2fb
        }
Packit Service 97d2fb
      if (rescan_now)
Packit Service 97d2fb
        try
Packit Service 97d2fb
          {
Packit Service 97d2fb
            set_metric("thread_busy", "role","traverse", 1);
Packit Service 97d2fb
            scan_source_paths();
Packit Service 97d2fb
            last_rescan = time(NULL); // NB: now was before scanning
Packit Service 97d2fb
            // finished a traversal loop
Packit Service 97d2fb
            inc_metric("thread_work_total", "role","traverse");
Packit Service 97d2fb
            set_metric("thread_busy", "role","traverse", 0);
Packit Service 97d2fb
          }
Packit Service 97d2fb
        catch (const reportable_exception& e)
Packit Service 97d2fb
          {
Packit Service 97d2fb
            e.report(cerr);
Packit Service 97d2fb
          }
Packit Service 97d2fb
    }
Packit Service 97d2fb
Packit Service 97d2fb
  return 0;
Packit Service 97d2fb
}
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
////////////////////////////////////////////////////////////////////////
Packit Service 97d2fb
Packit Service 97d2fb
static void
Packit Service 97d2fb
database_stats_report()
Packit Service 97d2fb
{
Packit Service 97d2fb
  sqlite_ps ps_query (db, "database-overview",
Packit Service 97d2fb
                      "select label,quantity from " BUILDIDS "_stats");
Packit Service 97d2fb
Packit Service 97d2fb
  obatched(clog) << "database record counts:" << endl;
Packit Service 97d2fb
  while (1)
Packit Service 97d2fb
    {
Packit Service 97d2fb
      int rc = sqlite3_step (ps_query);
Packit Service 97d2fb
      if (rc == SQLITE_DONE) break;
Packit Service 97d2fb
      if (rc != SQLITE_ROW)
Packit Service 97d2fb
        throw sqlite_exception(rc, "step");
Packit Service 97d2fb
Packit Service 97d2fb
      obatched(clog)
Packit Service 97d2fb
        << right << setw(20) << ((const char*) sqlite3_column_text(ps_query, 0) ?: (const char*) "NULL")
Packit Service 97d2fb
        << " "
Packit Service 97d2fb
        << (sqlite3_column_text(ps_query, 1) ?: (const unsigned char*) "NULL")
Packit Service 97d2fb
        << endl;
Packit Service 97d2fb
Packit Service 97d2fb
      set_metric("groom", "statistic",
Packit Service 97d2fb
                 ((const char*) sqlite3_column_text(ps_query, 0) ?: (const char*) "NULL"),
Packit Service 97d2fb
                 (sqlite3_column_double(ps_query, 1)));
Packit Service 97d2fb
    }
Packit Service 97d2fb
}
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
// Do a round of database grooming that might take many minutes to run.
Packit Service 97d2fb
void groom()
Packit Service 97d2fb
{
Packit Service 97d2fb
  obatched(clog) << "grooming database" << endl;
Packit Service 97d2fb
Packit Service 97d2fb
  struct timeval tv_start, tv_end;
Packit Service 97d2fb
  gettimeofday (&tv_start, NULL);
Packit Service 97d2fb
Packit Service 97d2fb
  database_stats_report();
Packit Service 97d2fb
  
Packit Service 97d2fb
  // scan for files that have disappeared
Packit Service 97d2fb
  sqlite_ps files (db, "check old files", "select s.mtime, s.file, f.name from "
Packit Service 97d2fb
                       BUILDIDS "_file_mtime_scanned s, " BUILDIDS "_files f "
Packit Service 97d2fb
                       "where f.id = s.file");
Packit Service 97d2fb
  sqlite_ps files_del_f_de (db, "nuke f_de", "delete from " BUILDIDS "_f_de where file = ? and mtime = ?");
Packit Service 97d2fb
  sqlite_ps files_del_r_de (db, "nuke r_de", "delete from " BUILDIDS "_r_de where file = ? and mtime = ?");
Packit Service 97d2fb
  sqlite_ps files_del_scan (db, "nuke f_m_s", "delete from " BUILDIDS "_file_mtime_scanned "
Packit Service 97d2fb
                            "where file = ? and mtime = ?");
Packit Service 97d2fb
  files.reset();
Packit Service 97d2fb
  while(1)
Packit Service 97d2fb
    {
Packit Service 97d2fb
      int rc = files.step();
Packit Service 97d2fb
      if (rc != SQLITE_ROW)
Packit Service 97d2fb
        break;
Packit Service 97d2fb
Packit Service 97d2fb
      int64_t mtime = sqlite3_column_int64 (files, 0);
Packit Service 97d2fb
      int64_t fileid = sqlite3_column_int64 (files, 1);
Packit Service 97d2fb
      const char* filename = ((const char*) sqlite3_column_text (files, 2) ?: "");
Packit Service 97d2fb
      struct stat s;
Packit Service 97d2fb
      rc = stat(filename, &s);
Packit Service 97d2fb
      if (rc < 0 || (mtime != (int64_t) s.st_mtime))
Packit Service 97d2fb
        {
Packit Service 97d2fb
          if (verbose > 2)
Packit Service 97d2fb
            obatched(clog) << "groom: forgetting file=" << filename << " mtime=" << mtime << endl;
Packit Service 97d2fb
          files_del_f_de.reset().bind(1,fileid).bind(2,mtime).step_ok_done();
Packit Service 97d2fb
          files_del_r_de.reset().bind(1,fileid).bind(2,mtime).step_ok_done();
Packit Service 97d2fb
          files_del_scan.reset().bind(1,fileid).bind(2,mtime).step_ok_done();
Packit Service 97d2fb
          inc_metric("groomed_total", "decision", "stale");
Packit Service 97d2fb
        }
Packit Service 97d2fb
      else
Packit Service 97d2fb
        inc_metric("groomed_total", "decision", "fresh");
Packit Service 97d2fb
Packit Service 97d2fb
      if (sigusr1 != forced_rescan_count) // stop early if scan triggered
Packit Service 97d2fb
        break;
Packit Service 97d2fb
    }
Packit Service 97d2fb
  files.reset();
Packit Service 97d2fb
Packit Service 97d2fb
  // delete buildids with no references in _r_de or _f_de tables;
Packit Service 97d2fb
  // cascades to _r_sref & _f_s records
Packit Service 97d2fb
  sqlite_ps buildids_del (db, "nuke orphan buildids",
Packit Service 97d2fb
                          "delete from " BUILDIDS "_buildids "
Packit Service 97d2fb
                          "where not exists (select 1 from " BUILDIDS "_f_de d where " BUILDIDS "_buildids.id = d.buildid) "
Packit Service 97d2fb
                          "and not exists (select 1 from " BUILDIDS "_r_de d where " BUILDIDS "_buildids.id = d.buildid)");
Packit Service 97d2fb
  buildids_del.reset().step_ok_done();
Packit Service 97d2fb
Packit Service 97d2fb
  // NB: "vacuum" is too heavy for even daily runs: it rewrites the entire db, so is done as maxigroom -G
Packit Service 97d2fb
  sqlite_ps g1 (db, "incremental vacuum", "pragma incremental_vacuum");
Packit Service 97d2fb
  g1.reset().step_ok_done();
Packit Service 97d2fb
  sqlite_ps g2 (db, "optimize", "pragma optimize");
Packit Service 97d2fb
  g2.reset().step_ok_done();
Packit Service 97d2fb
  sqlite_ps g3 (db, "wal checkpoint", "pragma wal_checkpoint=truncate");
Packit Service 97d2fb
  g3.reset().step_ok_done();
Packit Service 97d2fb
Packit Service 97d2fb
  database_stats_report();
Packit Service 97d2fb
Packit Service 97d2fb
  sqlite3_db_release_memory(db); // shrink the process if possible
Packit Service 97d2fb
Packit Service 97d2fb
  fdcache.limit(0,0); // release the fdcache contents
Packit Service 97d2fb
  fdcache.limit(fdcache_fds,fdcache_mbs); // restore status quo parameters
Packit Service 97d2fb
Packit Service 97d2fb
  gettimeofday (&tv_end, NULL);
Packit Service 97d2fb
  double deltas = (tv_end.tv_sec - tv_start.tv_sec) + (tv_end.tv_usec - tv_start.tv_usec)*0.000001;
Packit Service 97d2fb
Packit Service 97d2fb
  obatched(clog) << "groomed database in " << deltas << "s" << endl;
Packit Service 97d2fb
}
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
static void*
Packit Service 97d2fb
thread_main_groom (void* /*arg*/)
Packit Service 97d2fb
{
Packit Service 97d2fb
  set_metric("thread_tid", "role", "groom", tid());
Packit Service 97d2fb
  add_metric("thread_count", "role", "groom", 1);
Packit Service 97d2fb
Packit Service 97d2fb
  time_t last_groom = 0;
Packit Service 97d2fb
Packit Service 97d2fb
  while (1)
Packit Service 97d2fb
    {
Packit Service 97d2fb
      sleep (1);
Packit Service 97d2fb
      scanq.wait_idle(); // PR25394: block scanners during grooming!
Packit Service 97d2fb
      if (interrupted) break;
Packit Service 97d2fb
Packit Service 97d2fb
      time_t now = time(NULL);
Packit Service 97d2fb
      bool groom_now = false;
Packit Service 97d2fb
      if (last_groom == 0) // at least one initial groom is documented even for -g0
Packit Service 97d2fb
        groom_now = true;
Packit Service 97d2fb
      if (groom_s > 0 && (long)now > (long)(last_groom + groom_s))
Packit Service 97d2fb
        groom_now = true;
Packit Service 97d2fb
      if (sigusr2 != forced_groom_count)
Packit Service 97d2fb
        {
Packit Service 97d2fb
          forced_groom_count = sigusr2;
Packit Service 97d2fb
          groom_now = true;
Packit Service 97d2fb
        }
Packit Service 97d2fb
      if (groom_now)
Packit Service 97d2fb
        try
Packit Service 97d2fb
          {
Packit Service 97d2fb
            set_metric("thread_busy", "role", "groom", 1);
Packit Service 97d2fb
            groom ();
Packit Service 97d2fb
            last_groom = time(NULL); // NB: now was before grooming
Packit Service 97d2fb
            // finished a grooming loop
Packit Service 97d2fb
            inc_metric("thread_work_total", "role", "groom");
Packit Service 97d2fb
            set_metric("thread_busy", "role", "groom", 0);
Packit Service 97d2fb
          }
Packit Service 97d2fb
        catch (const sqlite_exception& e)
Packit Service 97d2fb
          {
Packit Service 97d2fb
            obatched(cerr) << e.message << endl;
Packit Service 97d2fb
          }
Packit Service 97d2fb
Packit Service 97d2fb
      scanq.done_idle();
Packit Service 97d2fb
    }
Packit Service 97d2fb
Packit Service 97d2fb
  return 0;
Packit Service 97d2fb
}
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
////////////////////////////////////////////////////////////////////////
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
static void
Packit Service 97d2fb
signal_handler (int /* sig */)
Packit Service 97d2fb
{
Packit Service 97d2fb
  interrupted ++;
Packit Service 97d2fb
Packit Service 97d2fb
  if (db)
Packit Service 97d2fb
    sqlite3_interrupt (db);
Packit Service 97d2fb
Packit Service 97d2fb
  // NB: don't do anything else in here
Packit Service 97d2fb
}
Packit Service 97d2fb
Packit Service 97d2fb
static void
Packit Service 97d2fb
sigusr1_handler (int /* sig */)
Packit Service 97d2fb
{
Packit Service 97d2fb
   sigusr1 ++;
Packit Service 97d2fb
  // NB: don't do anything else in here
Packit Service 97d2fb
}
Packit Service 97d2fb
Packit Service 97d2fb
static void
Packit Service 97d2fb
sigusr2_handler (int /* sig */)
Packit Service 97d2fb
{
Packit Service 97d2fb
   sigusr2 ++;
Packit Service 97d2fb
  // NB: don't do anything else in here
Packit Service 97d2fb
}
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
// A user-defined sqlite function, to score the sharedness of the
Packit Service 97d2fb
// prefix of two strings.  This is used to compare candidate debuginfo
Packit Service 97d2fb
// / source-rpm names, so that the closest match
Packit Service 97d2fb
// (directory-topology-wise closest) is found.  This is important in
Packit Service 97d2fb
// case the same sref (source file name) is in many -debuginfo or
Packit Service 97d2fb
// -debugsource RPMs, such as when multiple versions/releases of the
Packit Service 97d2fb
// same package are in the database.
Packit Service 97d2fb
Packit Service 97d2fb
static void sqlite3_sharedprefix_fn (sqlite3_context* c, int argc, sqlite3_value** argv)
Packit Service 97d2fb
{
Packit Service 97d2fb
  if (argc != 2)
Packit Service 97d2fb
    sqlite3_result_error(c, "expect 2 string arguments", -1);
Packit Service 97d2fb
  else if ((sqlite3_value_type(argv[0]) != SQLITE_TEXT) ||
Packit Service 97d2fb
           (sqlite3_value_type(argv[1]) != SQLITE_TEXT))
Packit Service 97d2fb
    sqlite3_result_null(c);
Packit Service 97d2fb
  else
Packit Service 97d2fb
    {
Packit Service 97d2fb
      const unsigned char* a = sqlite3_value_text (argv[0]);
Packit Service 97d2fb
      const unsigned char* b = sqlite3_value_text (argv[1]);
Packit Service 97d2fb
      int i = 0;
Packit Service 97d2fb
      while (*a++ == *b++)
Packit Service 97d2fb
        i++;
Packit Service 97d2fb
      sqlite3_result_int (c, i);
Packit Service 97d2fb
    }
Packit Service 97d2fb
}
Packit Service 97d2fb
Packit Service 97d2fb
Packit Service 97d2fb
int
Packit Service 97d2fb
main (int argc, char *argv[])
Packit Service 97d2fb
{
Packit Service 97d2fb
  (void) setlocale (LC_ALL, "");
Packit Service 97d2fb
  (void) bindtextdomain (PACKAGE_TARNAME, LOCALEDIR);
Packit Service 97d2fb
  (void) textdomain (PACKAGE_TARNAME);
Packit Service 97d2fb
Packit Service 97d2fb
  /* Tell the library which version we are expecting.  */
Packit Service 97d2fb
  elf_version (EV_CURRENT);
Packit Service 97d2fb
Packit Service 97d2fb
  tmpdir = string(getenv("TMPDIR") ?: "/tmp");
Packit Service 97d2fb
Packit Service 97d2fb
  /* Set computed default values. */
Packit Service 97d2fb
  db_path = string(getenv("HOME") ?: "/") + string("/.debuginfod.sqlite"); /* XDG? */
Packit Service 97d2fb
  int rc = regcomp (& file_include_regex, ".*", REG_EXTENDED|REG_NOSUB); // match everything
Packit Service 97d2fb
  if (rc != 0)
Packit Service 97d2fb
    error (EXIT_FAILURE, 0, "regcomp failure: %d", rc);
Packit Service 97d2fb
  rc = regcomp (& file_exclude_regex, "^$", REG_EXTENDED|REG_NOSUB); // match nothing
Packit Service 97d2fb
  if (rc != 0)
Packit Service 97d2fb
    error (EXIT_FAILURE, 0, "regcomp failure: %d", rc);
Packit Service 97d2fb
Packit Service 97d2fb
  // default parameters for fdcache are computed from system stats
Packit Service 97d2fb
  struct statfs sfs;
Packit Service 97d2fb
  rc = statfs(tmpdir.c_str(), &sfs;;
Packit Service 97d2fb
  if (rc < 0)
Packit Service 97d2fb
    fdcache_mbs = 1024; // 1 gigabyte
Packit Service 97d2fb
  else
Packit Service 97d2fb
    fdcache_mbs = sfs.f_bavail * sfs.f_bsize / 1024 / 1024 / 4; // 25% of free space
Packit Service 97d2fb
  fdcache_prefetch = 64; // guesstimate storage is this much less costly than re-decompression
Packit Service 97d2fb
  fdcache_fds = (concurrency + fdcache_prefetch) * 2;
Packit Service 97d2fb
Packit Service 97d2fb
  /* Parse and process arguments.  */
Packit Service 97d2fb
  int remaining;
Packit Service 97d2fb
  argp_program_version_hook = print_version; // this works
Packit Service 97d2fb
  (void) argp_parse (&argp, argc, argv, ARGP_IN_ORDER, &remaining, NULL);
Packit Service 97d2fb
  if (remaining != argc)
Packit Service 97d2fb
      error (EXIT_FAILURE, 0,
Packit Service 97d2fb
             "unexpected argument: %s", argv[remaining]);
Packit Service 97d2fb
Packit Service 97d2fb
  if (scan_archives.size()==0 && !scan_files && source_paths.size()>0)
Packit Service 97d2fb
    obatched(clog) << "warning: without -F -R -U -Z, ignoring PATHs" << endl;
Packit Service 97d2fb
Packit Service 97d2fb
  fdcache.limit(fdcache_fds, fdcache_mbs);
Packit Service 97d2fb
Packit Service 97d2fb
  (void) signal (SIGPIPE, SIG_IGN); // microhttpd can generate it incidentally, ignore
Packit Service 97d2fb
  (void) signal (SIGINT, signal_handler); // ^C
Packit Service 97d2fb
  (void) signal (SIGHUP, signal_handler); // EOF
Packit Service 97d2fb
  (void) signal (SIGTERM, signal_handler); // systemd
Packit Service 97d2fb
  (void) signal (SIGUSR1, sigusr1_handler); // end-user
Packit Service 97d2fb
  (void) signal (SIGUSR2, sigusr2_handler); // end-user
Packit Service 97d2fb
Packit Service 97d2fb
  /* Get database ready. */
Packit Service 97d2fb
  rc = sqlite3_open_v2 (db_path.c_str(), &db, (SQLITE_OPEN_READWRITE
Packit Service 97d2fb
                                               |SQLITE_OPEN_CREATE
Packit Service 97d2fb
                                               |SQLITE_OPEN_FULLMUTEX), /* thread-safe */
Packit Service 97d2fb
                        NULL);
Packit Service 97d2fb
  if (rc == SQLITE_CORRUPT)
Packit Service 97d2fb
    {
Packit Service 97d2fb
      (void) unlink (db_path.c_str());
Packit Service 97d2fb
      error (EXIT_FAILURE, 0,
Packit Service 97d2fb
             "cannot open %s, deleted database: %s", db_path.c_str(), sqlite3_errmsg(db));
Packit Service 97d2fb
    }
Packit Service 97d2fb
  else if (rc)
Packit Service 97d2fb
    {
Packit Service 97d2fb
      error (EXIT_FAILURE, 0,
Packit Service 97d2fb
             "cannot open %s, consider deleting database: %s", db_path.c_str(), sqlite3_errmsg(db));
Packit Service 97d2fb
    }
Packit Service 97d2fb
Packit Service 97d2fb
  obatched(clog) << "opened database " << db_path << endl;
Packit Service 97d2fb
  obatched(clog) << "sqlite version " << sqlite3_version << endl;
Packit Service 97d2fb
Packit Service 97d2fb
  // add special string-prefix-similarity function used in rpm sref/sdef resolution
Packit Service 97d2fb
  rc = sqlite3_create_function(db, "sharedprefix", 2, SQLITE_UTF8, NULL,
Packit Service 97d2fb
                               & sqlite3_sharedprefix_fn, NULL, NULL);
Packit Service 97d2fb
  if (rc != SQLITE_OK)
Packit Service 97d2fb
    error (EXIT_FAILURE, 0,
Packit Service 97d2fb
           "cannot create sharedprefix( function: %s", sqlite3_errmsg(db));
Packit Service 97d2fb
Packit Service 97d2fb
  if (verbose > 3)
Packit Service 97d2fb
    obatched(clog) << "ddl: " << DEBUGINFOD_SQLITE_DDL << endl;
Packit Service 97d2fb
  rc = sqlite3_exec (db, DEBUGINFOD_SQLITE_DDL, NULL, NULL, NULL);
Packit Service 97d2fb
  if (rc != SQLITE_OK)
Packit Service 97d2fb
    {
Packit Service 97d2fb
      error (EXIT_FAILURE, 0,
Packit Service 97d2fb
             "cannot run database schema ddl: %s", sqlite3_errmsg(db));
Packit Service 97d2fb
    }
Packit Service 97d2fb
Packit Service 97d2fb
  // Start httpd server threads.  Separate pool for IPv4 and IPv6, in
Packit Service 97d2fb
  // case the host only has one protocol stack.
Packit Service 97d2fb
  MHD_Daemon *d4 = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION
Packit Service 97d2fb
#if MHD_VERSION >= 0x00095300
Packit Service 97d2fb
                                     | MHD_USE_INTERNAL_POLLING_THREAD
Packit Service 97d2fb
#else
Packit Service 97d2fb
                                     | MHD_USE_SELECT_INTERNALLY
Packit Service 97d2fb
#endif
Packit Service 97d2fb
                                     | MHD_USE_DEBUG, /* report errors to stderr */
Packit Service 97d2fb
                                     http_port,
Packit Service 97d2fb
                                     NULL, NULL, /* default accept policy */
Packit Service 97d2fb
                                     handler_cb, NULL, /* handler callback */
Packit Service 97d2fb
                                     MHD_OPTION_END);
Packit Service 97d2fb
  MHD_Daemon *d6 = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION
Packit Service 97d2fb
#if MHD_VERSION >= 0x00095300
Packit Service 97d2fb
                                     | MHD_USE_INTERNAL_POLLING_THREAD
Packit Service 97d2fb
#else
Packit Service 97d2fb
                                     | MHD_USE_SELECT_INTERNALLY
Packit Service 97d2fb
#endif
Packit Service 97d2fb
                                     | MHD_USE_IPv6
Packit Service 97d2fb
                                     | MHD_USE_DEBUG, /* report errors to stderr */
Packit Service 97d2fb
                                     http_port,
Packit Service 97d2fb
                                     NULL, NULL, /* default accept policy */
Packit Service 97d2fb
                                     handler_cb, NULL, /* handler callback */
Packit Service 97d2fb
                                     MHD_OPTION_END);
Packit Service 97d2fb
Packit Service 97d2fb
  if (d4 == NULL && d6 == NULL) // neither ipv4 nor ipv6? boo
Packit Service 97d2fb
    {
Packit Service 97d2fb
      sqlite3 *database = db;
Packit Service 97d2fb
      db = 0; // for signal_handler not to freak
Packit Service 97d2fb
      sqlite3_close (database);
Packit Service 97d2fb
      error (EXIT_FAILURE, 0, "cannot start http server at port %d", http_port);
Packit Service 97d2fb
    }
Packit Service 97d2fb
Packit Service 97d2fb
  obatched(clog) << "started http server on "
Packit Service 97d2fb
                 << (d4 != NULL ? "IPv4 " : "")
Packit Service 97d2fb
                 << (d6 != NULL ? "IPv6 " : "")
Packit Service 97d2fb
                 << "port=" << http_port << endl;
Packit Service 97d2fb
Packit Service 97d2fb
  // add maxigroom sql if -G given
Packit Service 97d2fb
  if (maxigroom)
Packit Service 97d2fb
    {
Packit Service 97d2fb
      obatched(clog) << "maxigrooming database, please wait." << endl;
Packit Service 97d2fb
      extra_ddl.push_back("create index if not exists " BUILDIDS "_r_sref_arc on " BUILDIDS "_r_sref(artifactsrc);");
Packit Service 97d2fb
      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 Service 97d2fb
      extra_ddl.push_back("drop index if exists " BUILDIDS "_r_sref_arc;");
Packit Service 97d2fb
Packit Service 97d2fb
      // NB: we don't maxigroom the _files interning table.  It'd require a temp index on all the
Packit Service 97d2fb
      // tables that have file foreign-keys, which is a lot.
Packit Service 97d2fb
Packit Service 97d2fb
      // NB: with =delete, may take up 3x disk space total during vacuum process
Packit Service 97d2fb
      //     vs.  =off (only 2x but may corrupt database if program dies mid-vacuum)
Packit Service 97d2fb
      //     vs.  =wal (>3x observed, but safe)
Packit Service 97d2fb
      extra_ddl.push_back("pragma journal_mode=delete;");
Packit Service 97d2fb
      extra_ddl.push_back("vacuum;");
Packit Service 97d2fb
      extra_ddl.push_back("pragma journal_mode=wal;");
Packit Service 97d2fb
    }
Packit Service 97d2fb
Packit Service 97d2fb
  // run extra -D sql if given
Packit Service 97d2fb
  for (auto&& i: extra_ddl)
Packit Service 97d2fb
    {
Packit Service 97d2fb
      if (verbose > 1)
Packit Service 97d2fb
        obatched(clog) << "extra ddl:\n" << i << endl;
Packit Service 97d2fb
      rc = sqlite3_exec (db, i.c_str(), NULL, NULL, NULL);
Packit Service 97d2fb
      if (rc != SQLITE_OK && rc != SQLITE_DONE && rc != SQLITE_ROW)
Packit Service 97d2fb
        error (0, 0,
Packit Service 97d2fb
               "warning: cannot run database extra ddl %s: %s", i.c_str(), sqlite3_errmsg(db));
Packit Service 97d2fb
    }
Packit Service 97d2fb
Packit Service 97d2fb
  if (maxigroom)
Packit Service 97d2fb
    obatched(clog) << "maxigroomed database" << endl;
Packit Service 97d2fb
Packit Service 97d2fb
  obatched(clog) << "search concurrency " << concurrency << endl;
Packit Service 97d2fb
  obatched(clog) << "rescan time " << rescan_s << endl;
Packit Service 97d2fb
  obatched(clog) << "fdcache fds " << fdcache_fds << endl;
Packit Service 97d2fb
  obatched(clog) << "fdcache mbs " << fdcache_mbs << endl;
Packit Service 97d2fb
  obatched(clog) << "fdcache prefetch " << fdcache_prefetch << endl;
Packit Service 97d2fb
  obatched(clog) << "fdcache tmpdir " << tmpdir << endl;
Packit Service 97d2fb
  obatched(clog) << "groom time " << groom_s << endl;
Packit Service 97d2fb
  if (scan_archives.size()>0)
Packit Service 97d2fb
    {
Packit Service 97d2fb
      obatched ob(clog);
Packit Service 97d2fb
      auto& o = ob << "scanning archive types ";
Packit Service 97d2fb
      for (auto&& arch : scan_archives)
Packit Service 97d2fb
	o << arch.first << "(" << arch.second << ") ";
Packit Service 97d2fb
      o << endl;
Packit Service 97d2fb
    }
Packit Service 97d2fb
  const char* du = getenv(DEBUGINFOD_URLS_ENV_VAR);
Packit Service 97d2fb
  if (du && du[0] != '\0') // set to non-empty string?
Packit Service 97d2fb
    obatched(clog) << "upstream debuginfod servers: " << du << endl;
Packit Service 97d2fb
Packit Service 97d2fb
  vector<pthread_t> all_threads;
Packit Service 97d2fb
Packit Service 97d2fb
  pthread_t pt;
Packit Service 97d2fb
  rc = pthread_create (& pt, NULL, thread_main_groom, NULL);
Packit Service 97d2fb
  if (rc < 0)
Packit Service 97d2fb
    error (0, 0, "warning: cannot spawn thread (%d) to groom database\n", rc);
Packit Service 97d2fb
  else
Packit Service 97d2fb
    all_threads.push_back(pt);
Packit Service 97d2fb
Packit Service 97d2fb
  if (scan_files || scan_archives.size() > 0)
Packit Service 97d2fb
    {
Packit Service 97d2fb
      pthread_create (& pt, NULL, thread_main_fts_source_paths, NULL);
Packit Service 97d2fb
      if (rc < 0)
Packit Service 97d2fb
        error (0, 0, "warning: cannot spawn thread (%d) to traverse source paths\n", rc);
Packit Service 97d2fb
      all_threads.push_back(pt);
Packit Service 97d2fb
      for (unsigned i=0; i
Packit Service 97d2fb
        {
Packit Service 97d2fb
          pthread_create (& pt, NULL, thread_main_scanner, NULL);
Packit Service 97d2fb
          if (rc < 0)
Packit Service 97d2fb
            error (0, 0, "warning: cannot spawn thread (%d) to scan source files / archives\n", rc);
Packit Service 97d2fb
          all_threads.push_back(pt);
Packit Service 97d2fb
        }
Packit Service 97d2fb
    }
Packit Service 97d2fb
Packit Service 97d2fb
  /* Trivial main loop! */
Packit Service 97d2fb
  set_metric("ready", 1);
Packit Service 97d2fb
  while (! interrupted)
Packit Service 97d2fb
    pause ();
Packit Service 97d2fb
  scanq.nuke(); // wake up any remaining scanq-related threads, let them die
Packit Service 97d2fb
  set_metric("ready", 0);
Packit Service 97d2fb
Packit Service 97d2fb
  if (verbose)
Packit Service 97d2fb
    obatched(clog) << "stopping" << endl;
Packit Service 97d2fb
Packit Service 97d2fb
  /* Join all our threads. */
Packit Service 97d2fb
  for (auto&& it : all_threads)
Packit Service 97d2fb
    pthread_join (it, NULL);
Packit Service 97d2fb
Packit Service 97d2fb
  /* Stop all the web service threads. */
Packit Service 97d2fb
  if (d4) MHD_stop_daemon (d4);
Packit Service 97d2fb
  if (d6) MHD_stop_daemon (d6);
Packit Service 97d2fb
Packit Service 97d2fb
  /* With all threads known dead, we can clean up the global resources. */
Packit Service 97d2fb
  rc = sqlite3_exec (db, DEBUGINFOD_SQLITE_CLEANUP_DDL, NULL, NULL, NULL);
Packit Service 97d2fb
  if (rc != SQLITE_OK)
Packit Service 97d2fb
    {
Packit Service 97d2fb
      error (0, 0,
Packit Service 97d2fb
             "warning: cannot run database cleanup ddl: %s", sqlite3_errmsg(db));
Packit Service 97d2fb
    }
Packit Service 97d2fb
Packit Service 97d2fb
  // NB: no problem with unconditional free here - an earlier failed regcomp would exit program
Packit Service 97d2fb
  (void) regfree (& file_include_regex);
Packit Service 97d2fb
  (void) regfree (& file_exclude_regex);
Packit Service 97d2fb
Packit Service 97d2fb
  sqlite3 *database = db;
Packit Service 97d2fb
  db = 0; // for signal_handler not to freak
Packit Service 97d2fb
  (void) sqlite3_close (database);
Packit Service 97d2fb
Packit Service 97d2fb
  return 0;
Packit Service 97d2fb
}