LCOV - code coverage report
Current view: top level - debuginfod - debuginfod.cxx (source / functions) Hit Total Coverage
Test: elfutils-0.191 Lines: 1905 2242 85.0 %
Date: 2024-03-24 18:28:45 Functions: 90 99 90.9 %
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 2442 5694 42.9 %

           Branch data     Line data    Source code
       1                 :            : /* Debuginfo-over-http server.
       2                 :            :    Copyright (C) 2019-2024 Red Hat, Inc.
       3                 :            :    Copyright (C) 2021, 2022 Mark J. Wielaard <mark@klomp.org>
       4                 :            :    This file is part of elfutils.
       5                 :            : 
       6                 :            :    This file is free software; you can redistribute it and/or modify
       7                 :            :    it under the terms of the GNU General Public License as published by
       8                 :            :    the Free Software Foundation; either version 3 of the License, or
       9                 :            :    (at your option) any later version.
      10                 :            : 
      11                 :            :    elfutils is distributed in the hope that it will be useful, but
      12                 :            :    WITHOUT ANY WARRANTY; without even the implied warranty of
      13                 :            :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      14                 :            :    GNU General Public License for more details.
      15                 :            : 
      16                 :            :    You should have received a copy of the GNU General Public License
      17                 :            :    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
      18                 :            : 
      19                 :            : 
      20                 :            : /* cargo-cult from libdwfl linux-kernel-modules.c */
      21                 :            : /* In case we have a bad fts we include this before config.h because it
      22                 :            :    can't handle _FILE_OFFSET_BITS.
      23                 :            :    Everything we need here is fine if its declarations just come first.
      24                 :            :    Also, include sys/types.h before fts. On some systems fts.h is not self
      25                 :            :    contained. */
      26                 :            : #ifdef BAD_FTS
      27                 :            :   #include <sys/types.h>
      28                 :            :   #include <fts.h>
      29                 :            : #endif
      30                 :            : 
      31                 :            : #ifdef HAVE_CONFIG_H
      32                 :            :   #include "config.h"
      33                 :            : #endif
      34                 :            : 
      35                 :            : // #define _GNU_SOURCE
      36                 :            : #ifdef HAVE_SCHED_H
      37                 :            : extern "C" {
      38                 :            : #include <sched.h>
      39                 :            : }
      40                 :            : #endif
      41                 :            : #ifdef HAVE_SYS_RESOURCE_H
      42                 :            : extern "C" {
      43                 :            : #include <sys/resource.h>
      44                 :            : }
      45                 :            : #endif
      46                 :            : 
      47                 :            : #ifdef HAVE_EXECINFO_H
      48                 :            : extern "C" {
      49                 :            : #include <execinfo.h>
      50                 :            : }
      51                 :            : #endif
      52                 :            : #ifdef HAVE_MALLOC_H
      53                 :            : extern "C" {
      54                 :            : #include <malloc.h>
      55                 :            : }
      56                 :            : #endif
      57                 :            : 
      58                 :            : #include "debuginfod.h"
      59                 :            : #include <dwarf.h>
      60                 :            : 
      61                 :            : #include <argp.h>
      62                 :            : #ifdef __GNUC__
      63                 :            : #undef __attribute__ /* glibc bug - rhbz 1763325 */
      64                 :            : #endif
      65                 :            : 
      66                 :            : #include <unistd.h>
      67                 :            : #include <stdlib.h>
      68                 :            : #include <locale.h>
      69                 :            : #include <pthread.h>
      70                 :            : #include <signal.h>
      71                 :            : #include <sys/stat.h>
      72                 :            : #include <sys/time.h>
      73                 :            : #include <sys/vfs.h>
      74                 :            : #include <unistd.h>
      75                 :            : #include <fcntl.h>
      76                 :            : #include <netdb.h>
      77                 :            : #include <math.h>
      78                 :            : #include <float.h>
      79                 :            : 
      80                 :            : 
      81                 :            : /* If fts.h is included before config.h, its indirect inclusions may not
      82                 :            :    give us the right LFS aliases of these functions, so map them manually.  */
      83                 :            : #ifdef BAD_FTS
      84                 :            :   #ifdef _FILE_OFFSET_BITS
      85                 :            :     #define open open64
      86                 :            :     #define fopen fopen64
      87                 :            :   #endif
      88                 :            : #else
      89                 :            :   #include <sys/types.h>
      90                 :            :   #include <fts.h>
      91                 :            : #endif
      92                 :            : 
      93                 :            : #include <cstring>
      94                 :            : #include <vector>
      95                 :            : #include <set>
      96                 :            : #include <unordered_set>
      97                 :            : #include <map>
      98                 :            : #include <string>
      99                 :            : #include <iostream>
     100                 :            : #include <iomanip>
     101                 :            : #include <ostream>
     102                 :            : #include <sstream>
     103                 :            : #include <mutex>
     104                 :            : #include <deque>
     105                 :            : #include <condition_variable>
     106                 :            : #include <exception>
     107                 :            : #include <thread>
     108                 :            : // #include <regex> // on rhel7 gcc 4.8, not competent
     109                 :            : #include <regex.h>
     110                 :            : // #include <algorithm>
     111                 :            : using namespace std;
     112                 :            : 
     113                 :            : #include <gelf.h>
     114                 :            : #include <libdwelf.h>
     115                 :            : 
     116                 :            : #include <microhttpd.h>
     117                 :            : 
     118                 :            : #if MHD_VERSION >= 0x00097002
     119                 :            : // libmicrohttpd 0.9.71 broke API
     120                 :            : #define MHD_RESULT enum MHD_Result
     121                 :            : #else
     122                 :            : #define MHD_RESULT int
     123                 :            : #endif
     124                 :            : 
     125                 :            : #include <curl/curl.h>
     126                 :            : #include <archive.h>
     127                 :            : #include <archive_entry.h>
     128                 :            : #include <sqlite3.h>
     129                 :            : 
     130                 :            : #ifdef __linux__
     131                 :            : #include <sys/syscall.h>
     132                 :            : #endif
     133                 :            : 
     134                 :            : #ifdef __linux__
     135                 :            : #define tid() syscall(SYS_gettid)
     136                 :            : #else
     137                 :            : #define tid() pthread_self()
     138                 :            : #endif
     139                 :            : 
     140                 :            : extern "C" {
     141                 :            : #include "printversion.h"
     142                 :            : #include "system.h"
     143                 :            : }
     144                 :            : 
     145                 :            : 
     146                 :            : inline bool
     147                 :       2024 : string_endswith(const string& haystack, const string& needle)
     148                 :            : {
     149         [ +  - ]:       2024 :   return (haystack.size() >= needle.size() &&
     150                 :       2024 :           equal(haystack.end()-needle.size(), haystack.end(),
     151                 :       2024 :                 needle.begin()));
     152                 :            : }
     153                 :            : 
     154                 :            : 
     155                 :            : // Roll this identifier for every sqlite schema incompatibility.
     156                 :            : #define BUILDIDS "buildids10"
     157                 :            : 
     158                 :            : #if SQLITE_VERSION_NUMBER >= 3008000
     159                 :            : #define WITHOUT_ROWID "without rowid"
     160                 :            : #else
     161                 :            : #define WITHOUT_ROWID ""
     162                 :            : #endif
     163                 :            : 
     164                 :            : static const char DEBUGINFOD_SQLITE_DDL[] =
     165                 :            :   "pragma foreign_keys = on;\n"
     166                 :            :   "pragma synchronous = 0;\n" // disable fsync()s - this cache is disposable across a machine crash
     167                 :            :   "pragma journal_mode = wal;\n" // https://sqlite.org/wal.html
     168                 :            :   "pragma wal_checkpoint = truncate;\n" // clean out any preexisting wal file
     169                 :            :   "pragma journal_size_limit = 0;\n" // limit steady state file (between grooming, which also =truncate's)
     170                 :            :   "pragma auto_vacuum = incremental;\n" // https://sqlite.org/pragma.html
     171                 :            :   "pragma busy_timeout = 1000;\n" // https://sqlite.org/pragma.html
     172                 :            :   // NB: all these are overridable with -D option
     173                 :            : 
     174                 :            :   // Normalization table for interning file names
     175                 :            :   "create table if not exists " BUILDIDS "_fileparts (\n"
     176                 :            :   "        id integer primary key not null,\n"
     177                 :            :   "        name text unique not null\n"
     178                 :            :   "        );\n"
     179                 :            :   "create table if not exists " BUILDIDS "_files (\n"
     180                 :            :   "        id integer primary key not null,\n"
     181                 :            :   "        dirname integer not null,\n"
     182                 :            :   "        basename integer not null,\n"
     183                 :            :   "        unique (dirname, basename),\n"
     184                 :            :   "        foreign key (dirname) references " BUILDIDS "_fileparts(id) on delete cascade,\n"
     185                 :            :   "        foreign key (basename) references " BUILDIDS "_fileparts(id) on delete cascade\n"
     186                 :            :   "        );\n"
     187                 :            :   "create view if not exists " BUILDIDS "_files_v as\n" // a 
     188                 :            :   "        select f.id, n1.name || '/' || n2.name as name\n"
     189                 :            :   "        from " BUILDIDS "_files f, " BUILDIDS "_fileparts n1, " BUILDIDS "_fileparts n2\n"
     190                 :            :   "        where f.dirname = n1.id and f.basename = n2.id;\n"
     191                 :            :   
     192                 :            :   // Normalization table for interning buildids
     193                 :            :   "create table if not exists " BUILDIDS "_buildids (\n"
     194                 :            :   "        id integer primary key not null,\n"
     195                 :            :   "        hex text unique not null);\n"
     196                 :            :   // Track the completion of scanning of a given file & sourcetype at given time
     197                 :            :   "create table if not exists " BUILDIDS "_file_mtime_scanned (\n"
     198                 :            :   "        mtime integer not null,\n"
     199                 :            :   "        file integer not null,\n"
     200                 :            :   "        size integer not null,\n" // in bytes
     201                 :            :   "        sourcetype text(1) not null\n"
     202                 :            :   "            check (sourcetype IN ('F', 'R')),\n"
     203                 :            :   "        foreign key (file) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n"
     204                 :            :   "        primary key (file, mtime, sourcetype)\n"
     205                 :            :   "        ) " WITHOUT_ROWID ";\n"
     206                 :            :   "create table if not exists " BUILDIDS "_f_de (\n"
     207                 :            :   "        buildid integer not null,\n"
     208                 :            :   "        debuginfo_p integer not null,\n"
     209                 :            :   "        executable_p integer not null,\n"
     210                 :            :   "        file integer not null,\n"
     211                 :            :   "        mtime integer not null,\n"
     212                 :            :   "        foreign key (file) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n"
     213                 :            :   "        foreign key (buildid) references " BUILDIDS "_buildids(id) on update cascade on delete cascade,\n"
     214                 :            :   "        primary key (buildid, file, mtime)\n"
     215                 :            :   "        ) " WITHOUT_ROWID ";\n"
     216                 :            :   // Index for faster delete by file identifier
     217                 :            :   "create index if not exists " BUILDIDS "_f_de_idx on " BUILDIDS "_f_de (file, mtime);\n"
     218                 :            :   "create table if not exists " BUILDIDS "_f_s (\n"
     219                 :            :   "        buildid integer not null,\n"
     220                 :            :   "        artifactsrc integer not null,\n"
     221                 :            :   "        file integer not null,\n" // NB: not necessarily entered into _mtime_scanned
     222                 :            :   "        mtime integer not null,\n"
     223                 :            :   "        foreign key (file) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n"
     224                 :            :   "        foreign key (artifactsrc) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n"
     225                 :            :   "        foreign key (buildid) references " BUILDIDS "_buildids(id) on update cascade on delete cascade,\n"
     226                 :            :   "        primary key (buildid, artifactsrc, file, mtime)\n"
     227                 :            :   "        ) " WITHOUT_ROWID ";\n"
     228                 :            :   "create table if not exists " BUILDIDS "_r_de (\n"
     229                 :            :   "        buildid integer not null,\n"
     230                 :            :   "        debuginfo_p integer not null,\n"
     231                 :            :   "        executable_p integer not null,\n"
     232                 :            :   "        file integer not null,\n"
     233                 :            :   "        mtime integer not null,\n"
     234                 :            :   "        content integer not null,\n"
     235                 :            :   "        foreign key (file) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n"
     236                 :            :   "        foreign key (content) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n"
     237                 :            :   "        foreign key (buildid) references " BUILDIDS "_buildids(id) on update cascade on delete cascade,\n"
     238                 :            :   "        primary key (buildid, debuginfo_p, executable_p, file, content, mtime)\n"
     239                 :            :   "        ) " WITHOUT_ROWID ";\n"
     240                 :            :   // Index for faster delete by archive file identifier
     241                 :            :   "create index if not exists " BUILDIDS "_r_de_idx on " BUILDIDS "_r_de (file, mtime);\n"
     242                 :            :   "create table if not exists " BUILDIDS "_r_sref (\n" // outgoing dwarf sourcefile references from rpm
     243                 :            :   "        buildid integer not null,\n"
     244                 :            :   "        artifactsrc integer not null,\n"
     245                 :            :   "        foreign key (artifactsrc) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n"
     246                 :            :   "        foreign key (buildid) references " BUILDIDS "_buildids(id) on update cascade on delete cascade,\n"
     247                 :            :   "        primary key (buildid, artifactsrc)\n"
     248                 :            :   "        ) " WITHOUT_ROWID ";\n"
     249                 :            :   "create table if not exists " BUILDIDS "_r_sdef (\n" // rpm contents that may satisfy sref
     250                 :            :   "        file integer not null,\n"
     251                 :            :   "        mtime integer not null,\n"
     252                 :            :   "        content integer not null,\n"
     253                 :            :   "        foreign key (file) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n"
     254                 :            :   "        foreign key (content) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n"
     255                 :            :   "        primary key (content, file, mtime)\n"
     256                 :            :   "        ) " WITHOUT_ROWID ";\n"
     257                 :            :   // create views to glue together some of the above tables, for webapi D queries
     258                 :            :   "create view if not exists " BUILDIDS "_query_d as \n"
     259                 :            :   "select\n"
     260                 :            :   "        b.hex as buildid, n.mtime, 'F' as sourcetype, f0.name as source0, n.mtime as mtime, null as source1\n"
     261                 :            :   "        from " BUILDIDS "_buildids b, " BUILDIDS "_files_v f0, " BUILDIDS "_f_de n\n"
     262                 :            :   "        where b.id = n.buildid and f0.id = n.file and n.debuginfo_p = 1\n"
     263                 :            :   "union all select\n"
     264                 :            :   "        b.hex as buildid, n.mtime, 'R' as sourcetype, f0.name as source0, n.mtime as mtime, f1.name as source1\n"
     265                 :            :   "        from " BUILDIDS "_buildids b, " BUILDIDS "_files_v f0, " BUILDIDS "_files_v f1, " BUILDIDS "_r_de n\n"
     266                 :            :   "        where b.id = n.buildid and f0.id = n.file and f1.id = n.content and n.debuginfo_p = 1\n"
     267                 :            :   ";"
     268                 :            :   // ... and for E queries
     269                 :            :   "create view if not exists " BUILDIDS "_query_e as \n"
     270                 :            :   "select\n"
     271                 :            :   "        b.hex as buildid, n.mtime, 'F' as sourcetype, f0.name as source0, n.mtime as mtime, null as source1\n"
     272                 :            :   "        from " BUILDIDS "_buildids b, " BUILDIDS "_files_v f0, " BUILDIDS "_f_de n\n"
     273                 :            :   "        where b.id = n.buildid and f0.id = n.file and n.executable_p = 1\n"
     274                 :            :   "union all select\n"
     275                 :            :   "        b.hex as buildid, n.mtime, 'R' as sourcetype, f0.name as source0, n.mtime as mtime, f1.name as source1\n"
     276                 :            :   "        from " BUILDIDS "_buildids b, " BUILDIDS "_files_v f0, " BUILDIDS "_files_v f1, " BUILDIDS "_r_de n\n"
     277                 :            :   "        where b.id = n.buildid and f0.id = n.file and f1.id = n.content and n.executable_p = 1\n"
     278                 :            :   ";"
     279                 :            :   // ... and for S queries
     280                 :            :   "create view if not exists " BUILDIDS "_query_s as \n"
     281                 :            :   "select\n"
     282                 :            :   "        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"
     283                 :            :   "        from " BUILDIDS "_buildids b, " BUILDIDS "_files_v f0, " BUILDIDS "_files_v fs, " BUILDIDS "_f_s n\n"
     284                 :            :   "        where b.id = n.buildid and f0.id = n.file and fs.id = n.artifactsrc\n"
     285                 :            :   "union all select\n"
     286                 :            :   "        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"
     287                 :            :   "        from " BUILDIDS "_buildids b, " BUILDIDS "_files_v f0, " BUILDIDS "_files_v f1, " BUILDIDS "_files_v fsref, "
     288                 :            :   "        " BUILDIDS "_r_sdef sd, " BUILDIDS "_r_sref sr, " BUILDIDS "_r_de sde\n"
     289                 :            :   "        where b.id = sr.buildid and f0.id = sd.file and fsref.id = sde.file and f1.id = sd.content\n"
     290                 :            :   "        and sr.artifactsrc = sd.content and sde.buildid = sr.buildid\n"
     291                 :            :   ";"
     292                 :            :   // and for startup overview counts
     293                 :            :   "drop view if exists " BUILDIDS "_stats;\n"
     294                 :            :   "create view if not exists " BUILDIDS "_stats as\n"
     295                 :            :   "          select 'file d/e' as label,count(*) as quantity from " BUILDIDS "_f_de\n"
     296                 :            :   "union all select 'file s',count(*) from " BUILDIDS "_f_s\n"
     297                 :            :   "union all select 'archive d/e',count(*) from " BUILDIDS "_r_de\n"
     298                 :            :   "union all select 'archive sref',count(*) from " BUILDIDS "_r_sref\n"
     299                 :            :   "union all select 'archive sdef',count(*) from " BUILDIDS "_r_sdef\n"
     300                 :            :   "union all select 'buildids',count(*) from " BUILDIDS "_buildids\n"
     301                 :            :   "union all select 'filenames',count(*) from " BUILDIDS "_files\n"
     302                 :            :   "union all select 'fileparts',count(*) from " BUILDIDS "_fileparts\n"  
     303                 :            :   "union all select 'files scanned (#)',count(*) from " BUILDIDS "_file_mtime_scanned\n"
     304                 :            :   "union all select 'files scanned (mb)',coalesce(sum(size)/1024/1024,0) from " BUILDIDS "_file_mtime_scanned\n"
     305                 :            : #if SQLITE_VERSION_NUMBER >= 3016000
     306                 :            :   "union all select 'index db size (mb)',page_count*page_size/1024/1024 as size FROM pragma_page_count(), pragma_page_size()\n"
     307                 :            : #endif
     308                 :            :   ";\n"
     309                 :            : 
     310                 :            : // schema change history & garbage collection
     311                 :            : //
     312                 :            : // XXX: we could have migration queries here to bring prior-schema
     313                 :            : // data over instead of just dropping it.  But that could incur
     314                 :            : // doubled storage costs.
     315                 :            : //
     316                 :            : // buildids10: split the _files table into _parts
     317                 :            :   "" // <<< we are here
     318                 :            : // buildids9: widen the mtime_scanned table
     319                 :            :   "DROP VIEW IF EXISTS buildids9_stats;\n"
     320                 :            :   "DROP INDEX IF EXISTS buildids9_r_de_idx;\n"
     321                 :            :   "DROP INDEX IF EXISTS buildids9_f_de_idx;\n"
     322                 :            :   "DROP VIEW IF EXISTS buildids9_query_s;\n"
     323                 :            :   "DROP VIEW IF EXISTS buildids9_query_e;\n"
     324                 :            :   "DROP VIEW IF EXISTS buildids9_query_d;\n"
     325                 :            :   "DROP TABLE IF EXISTS buildids9_r_sdef;\n"
     326                 :            :   "DROP TABLE IF EXISTS buildids9_r_sref;\n"
     327                 :            :   "DROP TABLE IF EXISTS buildids9_r_de;\n"
     328                 :            :   "DROP TABLE IF EXISTS buildids9_f_s;\n"
     329                 :            :   "DROP TABLE IF EXISTS buildids9_f_de;\n"
     330                 :            :   "DROP TABLE IF EXISTS buildids9_file_mtime_scanned;\n"
     331                 :            :   "DROP TABLE IF EXISTS buildids9_buildids;\n"
     332                 :            :   "DROP TABLE IF EXISTS buildids9_files;\n"
     333                 :            : // buildids8: slim the sref table
     334                 :            :   "drop table if exists buildids8_f_de;\n"
     335                 :            :   "drop table if exists buildids8_f_s;\n"
     336                 :            :   "drop table if exists buildids8_r_de;\n"
     337                 :            :   "drop table if exists buildids8_r_sref;\n"
     338                 :            :   "drop table if exists buildids8_r_sdef;\n"
     339                 :            :   "drop table if exists buildids8_file_mtime_scanned;\n"
     340                 :            :   "drop table if exists buildids8_files;\n"
     341                 :            :   "drop table if exists buildids8_buildids;\n"
     342                 :            : // buildids7: separate _norm table into dense subtype tables
     343                 :            :   "drop table if exists buildids7_f_de;\n"
     344                 :            :   "drop table if exists buildids7_f_s;\n"
     345                 :            :   "drop table if exists buildids7_r_de;\n"
     346                 :            :   "drop table if exists buildids7_r_sref;\n"
     347                 :            :   "drop table if exists buildids7_r_sdef;\n"
     348                 :            :   "drop table if exists buildids7_file_mtime_scanned;\n"
     349                 :            :   "drop table if exists buildids7_files;\n"
     350                 :            :   "drop table if exists buildids7_buildids;\n"
     351                 :            : // buildids6: drop bolo/rfolo again, represent sources / rpmcontents in main table
     352                 :            :   "drop table if exists buildids6_norm;\n"
     353                 :            :   "drop table if exists buildids6_files;\n"
     354                 :            :   "drop table if exists buildids6_buildids;\n"
     355                 :            :   "drop view if exists buildids6;\n"
     356                 :            : // buildids5: redefine srcfile1 column to be '.'-less (for rpms)
     357                 :            :   "drop table if exists buildids5_norm;\n"
     358                 :            :   "drop table if exists buildids5_files;\n"
     359                 :            :   "drop table if exists buildids5_buildids;\n"
     360                 :            :   "drop table if exists buildids5_bolo;\n"
     361                 :            :   "drop table if exists buildids5_rfolo;\n"
     362                 :            :   "drop view if exists buildids5;\n"
     363                 :            : // buildids4: introduce rpmfile RFOLO
     364                 :            :   "drop table if exists buildids4_norm;\n"
     365                 :            :   "drop table if exists buildids4_files;\n"
     366                 :            :   "drop table if exists buildids4_buildids;\n"
     367                 :            :   "drop table if exists buildids4_bolo;\n"
     368                 :            :   "drop table if exists buildids4_rfolo;\n"
     369                 :            :   "drop view if exists buildids4;\n"
     370                 :            : // buildids3*: split out srcfile BOLO
     371                 :            :   "drop table if exists buildids3_norm;\n"
     372                 :            :   "drop table if exists buildids3_files;\n"
     373                 :            :   "drop table if exists buildids3_buildids;\n"
     374                 :            :   "drop table if exists buildids3_bolo;\n"
     375                 :            :   "drop view if exists buildids3;\n"
     376                 :            : // buildids2: normalized buildid and filenames into interning tables;
     377                 :            :   "drop table if exists buildids2_norm;\n"
     378                 :            :   "drop table if exists buildids2_files;\n"
     379                 :            :   "drop table if exists buildids2_buildids;\n"
     380                 :            :   "drop view if exists buildids2;\n"
     381                 :            :   // buildids1: made buildid and artifacttype NULLable, to represent cached-negative
     382                 :            : //           lookups from sources, e.g. files or rpms that contain no buildid-indexable content
     383                 :            :   "drop table if exists buildids1;\n"
     384                 :            : // buildids: original
     385                 :            :   "drop table if exists buildids;\n"
     386                 :            :   ;
     387                 :            : 
     388                 :            : static const char DEBUGINFOD_SQLITE_CLEANUP_DDL[] =
     389                 :            :   "pragma wal_checkpoint = truncate;\n" // clean out any preexisting wal file
     390                 :            :   ;
     391                 :            : 
     392                 :            : 
     393                 :            : 
     394                 :            : 
     395                 :            : /* Name and version of program.  */
     396                 :            : ARGP_PROGRAM_VERSION_HOOK_DEF = print_version;
     397                 :            : 
     398                 :            : /* Bug report address.  */
     399                 :            : ARGP_PROGRAM_BUG_ADDRESS_DEF = PACKAGE_BUGREPORT;
     400                 :            : 
     401                 :            : /* Definitions of arguments for argp functions.  */
     402                 :            : static const struct argp_option options[] =
     403                 :            :   {
     404                 :            :    { NULL, 0, NULL, 0, "Scanners:", 1 },
     405                 :            :    { "scan-file-dir", 'F', NULL, 0, "Enable ELF/DWARF file scanning.", 0 },
     406                 :            :    { "scan-rpm-dir", 'R', NULL, 0, "Enable RPM scanning.", 0 },
     407                 :            :    { "scan-deb-dir", 'U', NULL, 0, "Enable DEB scanning.", 0 },
     408                 :            :    { "scan-archive", 'Z', "EXT=CMD", 0, "Enable arbitrary archive scanning.", 0 },
     409                 :            :    // "source-oci-imageregistry"  ...
     410                 :            : 
     411                 :            :    { NULL, 0, NULL, 0, "Options:", 2 },
     412                 :            :    { "logical", 'L', NULL, 0, "Follow symlinks, default=ignore.", 0 },
     413                 :            :    { "rescan-time", 't', "SECONDS", 0, "Number of seconds to wait between rescans, 0=disable.", 0 },
     414                 :            :    { "groom-time", 'g', "SECONDS", 0, "Number of seconds to wait between database grooming, 0=disable.", 0 },
     415                 :            :    { "maxigroom", 'G', NULL, 0, "Run a complete database groom/shrink pass at startup.", 0 },
     416                 :            :    { "concurrency", 'c', "NUM", 0, "Limit scanning thread concurrency to NUM, default=#CPUs.", 0 },
     417                 :            :    { "connection-pool", 'C', "NUM", OPTION_ARG_OPTIONAL,
     418                 :            :      "Use webapi connection pool with NUM threads, default=unlim.", 0 },
     419                 :            :    { "include", 'I', "REGEX", 0, "Include files matching REGEX, default=all.", 0 },
     420                 :            :    { "exclude", 'X', "REGEX", 0, "Exclude files matching REGEX, default=none.", 0 },
     421                 :            :    { "port", 'p', "NUM", 0, "HTTP port to listen on, default 8002.", 0 },
     422                 :            :    { "database", 'd', "FILE", 0, "Path to sqlite database.", 0 },
     423                 :            :    { "ddl", 'D', "SQL", 0, "Apply extra sqlite ddl/pragma to connection.", 0 },
     424                 :            :    { "verbose", 'v', NULL, 0, "Increase verbosity.", 0 },
     425                 :            :    { "regex-groom", 'r', NULL, 0,"Uses regexes from -I and -X arguments to groom the database.",0},
     426                 :            : #define ARGP_KEY_FDCACHE_FDS 0x1001
     427                 :            :    { "fdcache-fds", ARGP_KEY_FDCACHE_FDS, "NUM", OPTION_HIDDEN, NULL, 0 },
     428                 :            : #define ARGP_KEY_FDCACHE_MBS 0x1002
     429                 :            :    { "fdcache-mbs", ARGP_KEY_FDCACHE_MBS, "MB", 0, "Maximum total size of archive file fdcache.", 0 },
     430                 :            : #define ARGP_KEY_FDCACHE_PREFETCH 0x1003
     431                 :            :    { "fdcache-prefetch", ARGP_KEY_FDCACHE_PREFETCH, "NUM", 0, "Number of archive files to prefetch into fdcache.", 0 },
     432                 :            : #define ARGP_KEY_FDCACHE_MINTMP 0x1004
     433                 :            :    { "fdcache-mintmp", ARGP_KEY_FDCACHE_MINTMP, "NUM", 0, "Minimum free space% on tmpdir.", 0 },
     434                 :            : #define ARGP_KEY_FDCACHE_PREFETCH_MBS 0x1005
     435                 :            :    { "fdcache-prefetch-mbs", ARGP_KEY_FDCACHE_PREFETCH_MBS, "MB", OPTION_HIDDEN, NULL, 0},
     436                 :            : #define ARGP_KEY_FDCACHE_PREFETCH_FDS 0x1006
     437                 :            :    { "fdcache-prefetch-fds", ARGP_KEY_FDCACHE_PREFETCH_FDS, "NUM", OPTION_HIDDEN, NULL, 0},
     438                 :            : #define ARGP_KEY_FORWARDED_TTL_LIMIT 0x1007
     439                 :            :    {"forwarded-ttl-limit", ARGP_KEY_FORWARDED_TTL_LIMIT, "NUM", 0, "Limit of X-Forwarded-For hops, default 8.", 0},
     440                 :            : #define ARGP_KEY_PASSIVE 0x1008
     441                 :            :    { "passive", ARGP_KEY_PASSIVE, NULL, 0, "Do not scan or groom, read-only database.", 0 },
     442                 :            : #define ARGP_KEY_DISABLE_SOURCE_SCAN 0x1009
     443                 :            :    { "disable-source-scan", ARGP_KEY_DISABLE_SOURCE_SCAN, NULL, 0, "Do not scan dwarf source info.", 0 },
     444                 :            : #define ARGP_SCAN_CHECKPOINT 0x100A
     445                 :            :    { "scan-checkpoint", ARGP_SCAN_CHECKPOINT, "NUM", 0, "Number of files scanned before a WAL checkpoint.", 0 },
     446                 :            :    { NULL, 0, NULL, 0, NULL, 0 },
     447                 :            :   };
     448                 :            : 
     449                 :            : /* Short description of program.  */
     450                 :            : static const char doc[] = "Serve debuginfo-related content across HTTP from files under PATHs.";
     451                 :            : 
     452                 :            : /* Strings for arguments in help texts.  */
     453                 :            : static const char args_doc[] = "[PATH ...]";
     454                 :            : 
     455                 :            : /* Prototype for option handler.  */
     456                 :            : static error_t parse_opt (int key, char *arg, struct argp_state *state);
     457                 :            : 
     458                 :            : static unsigned default_concurrency();
     459                 :            : 
     460                 :            : /* Data structure to communicate with argp functions.  */
     461                 :            : static struct argp argp =
     462                 :            :   {
     463                 :            :    options, parse_opt, args_doc, doc, NULL, NULL, NULL
     464                 :            :   };
     465                 :            : 
     466                 :            : 
     467                 :            : static string db_path;
     468                 :            : static sqlite3 *db;  // single connection, serialized across all our threads!
     469                 :            : static sqlite3 *dbq; // webapi query-servicing readonly connection, serialized ditto!
     470                 :            : static unsigned verbose;
     471                 :            : static volatile sig_atomic_t interrupted = 0;
     472                 :            : static volatile sig_atomic_t forced_rescan_count = 0;
     473                 :            : static volatile sig_atomic_t sigusr1 = 0;
     474                 :            : static volatile sig_atomic_t forced_groom_count = 0;
     475                 :            : static volatile sig_atomic_t sigusr2 = 0;
     476                 :            : static unsigned http_port = 8002;
     477                 :            : static unsigned rescan_s = 300;
     478                 :            : static unsigned groom_s = 86400;
     479                 :            : static bool maxigroom = false;
     480                 :            : static unsigned concurrency = default_concurrency();
     481                 :            : static int connection_pool = 0;
     482                 :            : static set<string> source_paths;
     483                 :            : static bool scan_files = false;
     484                 :            : static map<string,string> scan_archives;
     485                 :            : static vector<string> extra_ddl;
     486                 :            : static regex_t file_include_regex;
     487                 :            : static regex_t file_exclude_regex;
     488                 :            : static bool regex_groom = false;
     489                 :            : static bool traverse_logical;
     490                 :            : static long fdcache_mbs;
     491                 :            : static long fdcache_prefetch;
     492                 :            : static long fdcache_mintmp;
     493                 :            : static unsigned forwarded_ttl_limit = 8;
     494                 :            : static bool scan_source_info = true;
     495                 :            : static string tmpdir;
     496                 :            : static bool passive_p = false;
     497                 :            : static long scan_checkpoint = 256;
     498                 :            : 
     499                 :            : static void set_metric(const string& key, double value);
     500                 :            : static void inc_metric(const string& key);
     501                 :            : static void add_metric(const string& metric,
     502                 :            :                        double value);
     503                 :            : static void set_metric(const string& metric,
     504                 :            :                        const string& lname, const string& lvalue,
     505                 :            :                        double value);
     506                 :            : static void inc_metric(const string& metric,
     507                 :            :                        const string& lname, const string& lvalue);
     508                 :            : static void add_metric(const string& metric,
     509                 :            :                        const string& lname, const string& lvalue,
     510                 :            :                        double value);
     511                 :            : static void inc_metric(const string& metric,
     512                 :            :                        const string& lname, const string& lvalue,
     513                 :            :                        const string& rname, const string& rvalue);
     514                 :            : static void add_metric(const string& metric,
     515                 :            :                        const string& lname, const string& lvalue,
     516                 :            :                        const string& rname, const string& rvalue,                       
     517                 :            :                        double value);
     518                 :            : 
     519                 :            : 
     520                 :            : class tmp_inc_metric { // a RAII style wrapper for exception-safe scoped increment & decrement
     521                 :            :   string m, n, v;
     522                 :            : public:
     523                 :       2608 :   tmp_inc_metric(const string& mname, const string& lname, const string& lvalue):
     524   [ +  -  +  - ]:       2608 :     m(mname), n(lname), v(lvalue)
     525                 :            :   {
     526         [ +  - ]:       2608 :     add_metric (m, n, v, 1);
     527   [ -  -  -  -  :       2608 :   }
                   -  - ]
     528                 :       2608 :   ~tmp_inc_metric()
     529                 :            :   {
     530                 :       2608 :     add_metric (m, n, v, -1);
     531   [ -  +  -  +  :       2608 :   }
                   -  + ]
     532                 :            : };
     533                 :            : 
     534                 :            : class tmp_ms_metric { // a RAII style wrapper for exception-safe scoped timing
     535                 :            :   string m, n, v;
     536                 :            :   struct timespec ts_start;
     537                 :            : public:
     538                 :     322329 :   tmp_ms_metric(const string& mname, const string& lname, const string& lvalue):
     539   [ +  -  +  - ]:     322329 :     m(mname), n(lname), v(lvalue)
     540                 :            :   {
     541                 :     322371 :     clock_gettime (CLOCK_MONOTONIC, & ts_start);
     542   [ -  -  -  - ]:     322445 :   }
     543                 :     322457 :   ~tmp_ms_metric()
     544                 :            :   {
     545                 :     322457 :     struct timespec ts_end;
     546                 :     322457 :     clock_gettime (CLOCK_MONOTONIC, & ts_end);
     547                 :     322452 :     double deltas = (ts_end.tv_sec - ts_start.tv_sec)
     548                 :     322452 :       + (ts_end.tv_nsec - ts_start.tv_nsec)/1.e9;
     549                 :            : 
     550         [ +  - ]:     322452 :     add_metric (m + "_milliseconds_sum", n, v, (deltas*1000.0));
     551         [ +  + ]:     322458 :     inc_metric (m + "_milliseconds_count", n, v);
     552   [ +  +  -  +  :     597982 :   }
                   -  + ]
     553                 :            : };
     554                 :            : 
     555                 :            : 
     556                 :            : /* Handle program arguments.  */
     557                 :            : static error_t
     558                 :       1148 : parse_opt (int key, char *arg,
     559                 :            :            struct argp_state *state __attribute__ ((unused)))
     560                 :            : {
     561                 :       1148 :   int rc;
     562   [ +  +  +  +  :       1148 :   switch (key)
          +  +  +  +  +  
          -  +  +  -  -  
          +  +  +  +  +  
          +  +  +  +  +  
                -  +  + ]
     563                 :            :     {
     564                 :        286 :     case 'v': verbose ++; break;
     565                 :         74 :     case 'd':
     566                 :            :       /* When using the in-memory database make sure it is shareable,
     567                 :            :          so we can open it twice as read/write and read-only.  */
     568         [ +  + ]:         74 :       if (strcmp (arg, ":memory:") == 0)
     569                 :       1162 :         db_path = "file::memory:?cache=shared";
     570                 :            :       else
     571         [ +  - ]:        120 :         db_path = string(arg);
     572                 :            :       break;
     573                 :         74 :     case 'p': http_port = (unsigned) atoi(arg);
     574         [ +  - ]:         74 :       if (http_port == 0 || http_port > 65535)
     575                 :          0 :         argp_failure(state, 1, EINVAL, "port number");
     576                 :            :       break;
     577                 :         50 :     case 'F': scan_files = true; break;
     578                 :         22 :     case 'R':
     579   [ +  -  +  -  :         22 :       scan_archives[".rpm"]="cat"; // libarchive groks rpm natively
                   -  + ]
     580                 :         22 :       break;
     581                 :         14 :     case 'U':
     582   [ +  -  +  -  :         14 :       scan_archives[".deb"]="(bsdtar -O -x -f - data.tar\\*)<";
                   -  + ]
     583   [ +  -  +  -  :         14 :       scan_archives[".ddeb"]="(bsdtar -O -x -f - data.tar\\*)<";
                   -  + ]
     584   [ +  -  +  -  :         14 :       scan_archives[".ipk"]="(bsdtar -O -x -f - data.tar\\*)<";
                   -  + ]
     585                 :            :       // .udeb too?
     586                 :         14 :       break;
     587                 :         38 :     case 'Z':
     588                 :         38 :       {
     589         [ -  + ]:         38 :         char* extension = strchr(arg, '=');
     590         [ -  + ]:         38 :         if (arg[0] == '\0')
     591                 :          0 :           argp_failure(state, 1, EINVAL, "missing EXT");
     592         [ +  + ]:         38 :         else if (extension)
     593   [ +  -  +  -  :         20 :           scan_archives[string(arg, (extension-arg))]=string(extension+1);
          -  +  -  +  -  
                      - ]
     594                 :            :         else
     595   [ +  -  +  -  :         18 :           scan_archives[string(arg)]=string("cat");
          -  +  -  +  -  
                      - ]
     596                 :            :       }
     597                 :            :       break;
     598                 :          8 :     case 'L':
     599         [ -  + ]:          8 :       if (passive_p)
     600                 :          0 :         argp_failure(state, 1, EINVAL, "-L option inconsistent with passive mode");
     601                 :          8 :       traverse_logical = true;
     602                 :          8 :       break;
     603                 :          0 :     case 'D':
     604         [ #  # ]:          0 :       if (passive_p)
     605                 :          0 :         argp_failure(state, 1, EINVAL, "-D option inconsistent with passive mode");
     606         [ #  # ]:          0 :       extra_ddl.push_back(string(arg));
     607                 :          0 :       break;
     608                 :         58 :     case 't':
     609         [ -  + ]:         58 :       if (passive_p)
     610                 :          0 :         argp_failure(state, 1, EINVAL, "-t option inconsistent with passive mode");
     611                 :         58 :       rescan_s = (unsigned) atoi(arg);
     612                 :         58 :       break;
     613                 :         58 :     case 'g':
     614         [ -  + ]:         58 :       if (passive_p)
     615                 :          0 :         argp_failure(state, 1, EINVAL, "-g option inconsistent with passive mode");
     616                 :         58 :       groom_s = (unsigned) atoi(arg);
     617                 :         58 :       break;
     618                 :          0 :     case 'G':
     619         [ #  # ]:          0 :       if (passive_p)
     620                 :          0 :         argp_failure(state, 1, EINVAL, "-G option inconsistent with passive mode");
     621                 :          0 :       maxigroom = true;
     622                 :          0 :       break;
     623                 :          0 :     case 'c':
     624         [ #  # ]:          0 :       if (passive_p)
     625                 :          0 :         argp_failure(state, 1, EINVAL, "-c option inconsistent with passive mode");
     626                 :          0 :       concurrency = (unsigned) atoi(arg);
     627         [ #  # ]:          0 :       if (concurrency < 1) concurrency = 1;
     628                 :            :       break;
     629                 :          6 :     case 'C':
     630         [ +  + ]:          6 :       if (arg)
     631                 :            :         {
     632                 :          4 :           connection_pool = atoi(arg);
     633         [ +  - ]:          4 :           if (connection_pool < 2)
     634                 :          0 :             argp_failure(state, 1, EINVAL, "-C NUM minimum 2");
     635                 :            :         }
     636                 :            :       break;
     637                 :          4 :     case 'I':
     638                 :            :       // NB: no problem with unconditional free here - an earlier failed regcomp would exit program
     639         [ -  + ]:          4 :       if (passive_p)
     640                 :          0 :         argp_failure(state, 1, EINVAL, "-I option inconsistent with passive mode");
     641                 :          4 :       regfree (&file_include_regex);
     642                 :          4 :       rc = regcomp (&file_include_regex, arg, REG_EXTENDED|REG_NOSUB);
     643         [ +  - ]:          4 :       if (rc != 0)
     644                 :          0 :         argp_failure(state, 1, EINVAL, "regular expression");
     645                 :            :       break;
     646                 :          6 :     case 'X':
     647         [ -  + ]:          6 :       if (passive_p)
     648                 :          0 :         argp_failure(state, 1, EINVAL, "-X option inconsistent with passive mode");
     649                 :          6 :       regfree (&file_exclude_regex);
     650                 :          6 :       rc = regcomp (&file_exclude_regex, arg, REG_EXTENDED|REG_NOSUB);
     651         [ +  - ]:          6 :       if (rc != 0)
     652                 :          0 :         argp_failure(state, 1, EINVAL, "regular expression");
     653                 :            :       break;
     654                 :          4 :     case 'r':
     655         [ -  + ]:          4 :       if (passive_p)
     656                 :          0 :         argp_failure(state, 1, EINVAL, "-r option inconsistent with passive mode");
     657                 :          4 :       regex_groom = true;
     658                 :          4 :       break;
     659                 :            :     case ARGP_KEY_FDCACHE_FDS:
     660                 :            :       // deprecated
     661                 :            :       break;
     662                 :          4 :     case ARGP_KEY_FDCACHE_MBS:
     663                 :          4 :       fdcache_mbs = atol (arg);
     664                 :          4 :       break;
     665                 :          2 :     case ARGP_KEY_FDCACHE_PREFETCH:
     666                 :          2 :       fdcache_prefetch = atol (arg);
     667                 :          2 :       break;
     668                 :          2 :     case ARGP_KEY_FDCACHE_MINTMP:
     669                 :          2 :       fdcache_mintmp = atol (arg);
     670         [ +  - ]:          2 :       if( fdcache_mintmp > 100 || fdcache_mintmp < 0 )
     671                 :          0 :         argp_failure(state, 1, EINVAL, "fdcache mintmp percent");
     672                 :            :       break;
     673                 :          4 :     case ARGP_KEY_FORWARDED_TTL_LIMIT:
     674                 :          4 :       forwarded_ttl_limit = (unsigned) atoi(arg);
     675                 :          4 :       break;
     676                 :        102 :     case ARGP_KEY_ARG:
     677         [ +  - ]:        102 :       source_paths.insert(string(arg));
     678                 :        102 :       break;
     679                 :            :     case ARGP_KEY_FDCACHE_PREFETCH_FDS:
     680                 :            :       // deprecated
     681                 :            :       break;
     682                 :            :     case ARGP_KEY_FDCACHE_PREFETCH_MBS:
     683                 :            :       // deprecated
     684                 :            :       break;
     685                 :          2 :     case ARGP_KEY_PASSIVE:
     686                 :          2 :       passive_p = true;
     687         [ +  - ]:          2 :       if (source_paths.size() > 0
     688         [ +  - ]:          2 :           || maxigroom
     689         [ +  - ]:          2 :           || extra_ddl.size() > 0
     690   [ +  -  +  - ]:          4 :           || traverse_logical)
     691                 :            :         // other conflicting options tricky to check
     692                 :          0 :         argp_failure(state, 1, EINVAL, "inconsistent options with passive mode");
     693                 :            :       break;
     694                 :          0 :     case ARGP_KEY_DISABLE_SOURCE_SCAN:
     695                 :          0 :       scan_source_info = false;
     696                 :          0 :       break;
     697                 :          2 :     case ARGP_SCAN_CHECKPOINT:
     698                 :          2 :       scan_checkpoint = atol (arg);
     699         [ +  - ]:          2 :       if (scan_checkpoint < 0)
     700                 :          0 :         argp_failure(state, 1, EINVAL, "scan checkpoint");        
     701                 :            :       break;
     702                 :            :       // case 'h': argp_state_help (state, stderr, ARGP_HELP_LONG|ARGP_HELP_EXIT_OK);
     703                 :            :     default: return ARGP_ERR_UNKNOWN;
     704                 :            :     }
     705                 :            : 
     706                 :            :   return 0;
     707                 :            : }
     708                 :            : 
     709                 :            : 
     710                 :            : ////////////////////////////////////////////////////////////////////////
     711                 :            : 
     712                 :            : 
     713                 :            : static void add_mhd_response_header (struct MHD_Response *r,
     714                 :            :                                      const char *h, const char *v);
     715                 :            : 
     716                 :            : // represent errors that may get reported to an ostream and/or a libmicrohttpd connection
     717                 :            : 
     718                 :          8 : struct reportable_exception
     719                 :            : {
     720                 :            :   int code;
     721                 :            :   string message;
     722                 :            : 
     723   [ -  -  +  -  :         86 :   reportable_exception(int c, const string& m): code(c), message(m) {}
          -  -  +  -  +  
                      - ]
     724   [ -  -  -  -  :        602 :   reportable_exception(const string& m): code(503), message(m) {}
          -  -  -  -  +  
          -  -  -  -  -  
          +  -  -  -  -  
          -  -  -  +  -  
                   -  - ]
     725                 :            :   reportable_exception(): code(503), message() {}
     726                 :            : 
     727                 :            :   void report(ostream& o) const; // defined under obatched() class below
     728                 :            : 
     729                 :        632 :   MHD_RESULT mhd_send_response(MHD_Connection* c) const {
     730                 :       1264 :     MHD_Response* r = MHD_create_response_from_buffer (message.size(),
     731                 :        632 :                                                        (void*) message.c_str(),
     732                 :            :                                                        MHD_RESPMEM_MUST_COPY);
     733                 :        632 :     add_mhd_response_header (r, "Content-Type", "text/plain");
     734                 :        632 :     MHD_RESULT rc = MHD_queue_response (c, code, r);
     735                 :        632 :     MHD_destroy_response (r);
     736                 :        632 :     return rc;
     737                 :            :   }
     738                 :            : };
     739                 :            : 
     740                 :            : 
     741                 :            : struct sqlite_exception: public reportable_exception
     742                 :            : {
     743                 :          0 :   sqlite_exception(int rc, const string& msg):
     744   [ #  #  #  #  :          0 :     reportable_exception(string("sqlite3 error: ") + msg + ": " + string(sqlite3_errstr(rc) ?: "?")) {
          #  #  #  #  #  
          #  #  #  #  #  
          #  #  #  #  #  
                      # ]
     745   [ #  #  #  #  :          0 :     inc_metric("error_count","sqlite3",sqlite3_errstr(rc));
          #  #  #  #  #  
          #  #  #  #  #  
             #  #  #  # ]
     746         [ #  # ]:          0 :   }
     747                 :            : };
     748                 :            : 
     749   [ +  -  -  - ]:          4 : struct libc_exception: public reportable_exception
     750                 :            : {
     751                 :        594 :   libc_exception(int rc, const string& msg):
     752   [ -  +  +  -  :       2376 :     reportable_exception(string("libc error: ") + msg + ": " + string(strerror(rc) ?: "?")) {
          +  -  +  -  +  
          -  -  +  -  +  
          -  +  +  -  -  
                      - ]
     753   [ +  -  +  -  :       1188 :     inc_metric("error_count","libc",strerror(rc));
          +  -  +  -  -  
          +  +  -  -  -  
                   -  - ]
     754         [ -  - ]:        594 :   }
     755                 :            : };
     756                 :            : 
     757                 :            : 
     758                 :            : struct archive_exception: public reportable_exception
     759                 :            : {
     760                 :          0 :   archive_exception(const string& msg):
     761   [ #  #  #  #  :          0 :     reportable_exception(string("libarchive error: ") + msg) {
                   #  # ]
     762   [ #  #  #  #  :          0 :       inc_metric("error_count","libarchive",msg);
          #  #  #  #  #  
                      # ]
     763         [ #  # ]:          0 :   }
     764                 :          0 :   archive_exception(struct archive* a, const string& msg):
     765   [ #  #  #  #  :          0 :     reportable_exception(string("libarchive error: ") + msg + ": " + string(archive_error_string(a) ?: "?")) {
          #  #  #  #  #  
          #  #  #  #  #  
          #  #  #  #  #  
                      # ]
     766   [ #  #  #  #  :          0 :     inc_metric("error_count","libarchive",msg + ": " + string(archive_error_string(a) ?: "?"));
          #  #  #  #  #  
          #  #  #  #  #  
          #  #  #  #  #  
          #  #  #  #  #  
          #  #  #  #  #  
                      # ]
     767         [ #  # ]:          0 :   }
     768                 :            : };
     769                 :            : 
     770                 :            : 
     771                 :            : struct elfutils_exception: public reportable_exception
     772                 :            : {
     773                 :          0 :   elfutils_exception(int rc, const string& msg):
     774   [ #  #  #  #  :          0 :     reportable_exception(string("elfutils error: ") + msg + ": " + string(elf_errmsg(rc) ?: "?")) {
          #  #  #  #  #  
          #  #  #  #  #  
          #  #  #  #  #  
                      # ]
     775   [ #  #  #  #  :          0 :     inc_metric("error_count","elfutils",elf_errmsg(rc));
          #  #  #  #  #  
          #  #  #  #  #  
             #  #  #  # ]
     776         [ #  # ]:          0 :   }
     777                 :            : };
     778                 :            : 
     779                 :            : 
     780                 :            : ////////////////////////////////////////////////////////////////////////
     781                 :            : 
     782                 :            : template <typename Payload>
     783                 :            : class workq
     784                 :            : {
     785                 :            :   unordered_set<Payload> q; // eliminate duplicates
     786                 :            :   mutex mtx;
     787                 :            :   condition_variable cv;
     788                 :            :   bool dead;
     789                 :            :   unsigned idlers;   // number of threads busy with wait_idle / done_idle
     790                 :            :   unsigned fronters; // number of threads busy with wait_front / done_front
     791                 :            : 
     792                 :            : public:
     793                 :         74 :   workq() { dead = false; idlers = 0; fronters = 0; }
     794                 :         74 :   ~workq() {}
     795                 :            : 
     796                 :       1186 :   void push_back(const Payload& p)
     797                 :            :   {
     798                 :       1186 :     unique_lock<mutex> lock(mtx);
     799         [ +  - ]:       1186 :     q.insert (p);
     800   [ +  -  +  -  :       2372 :     set_metric("thread_work_pending","role","scan", q.size());
          +  -  +  -  -  
          +  -  +  -  -  
                   -  - ]
     801                 :       1186 :     cv.notify_all();
     802                 :       1186 :   }
     803                 :            : 
     804                 :            :   // kill this workqueue, wake up all idlers / scanners
     805                 :         74 :   void nuke() {
     806                 :         74 :     unique_lock<mutex> lock(mtx);
     807                 :            :     // optional: q.clear();
     808                 :         74 :     dead = true;
     809                 :         74 :     cv.notify_all();
     810                 :         74 :   }
     811                 :            : 
     812                 :            :   // clear the workqueue, when scanning is interrupted with USR2
     813                 :          0 :   void clear() {
     814                 :          0 :     unique_lock<mutex> lock(mtx);
     815                 :          0 :     q.clear();
     816   [ #  #  #  #  :          0 :     set_metric("thread_work_pending","role","scan", q.size());
          #  #  #  #  #  
          #  #  #  #  #  
                   #  # ]
     817                 :            :     // NB: there may still be some live fronters
     818                 :          0 :     cv.notify_all(); // maybe wake up waiting idlers
     819                 :          0 :   }
     820                 :            : 
     821                 :            :   // block this scanner thread until there is work to do and no active idler
     822                 :       1449 :   bool wait_front (Payload& p)
     823                 :            :   {
     824                 :       1449 :     unique_lock<mutex> lock(mtx);
     825   [ +  +  +  +  :       5261 :     while (!dead && (q.size() == 0 || idlers > 0))
                   +  + ]
     826         [ +  - ]:       3811 :       cv.wait(lock);
     827         [ +  + ]:       1450 :     if (dead)
     828                 :            :       return false;
     829                 :            :     else
     830                 :            :       {
     831         [ +  - ]:       1186 :         p = * q.begin();
     832                 :       1186 :         q.erase (q.begin());
     833                 :       1186 :         fronters ++; // prevent idlers from starting awhile, even if empty q
     834   [ +  -  +  -  :       2372 :         set_metric("thread_work_pending","role","scan", q.size());
          +  -  +  -  -  
          +  -  +  -  -  
             -  -  -  - ]
     835                 :            :         // NB: don't wake up idlers yet!  The consumer is busy
     836                 :            :         // processing this element until it calls done_front().
     837                 :       1186 :         return true;
     838                 :            :       }
     839                 :       1450 :   }
     840                 :            : 
     841                 :            :   // notify waitq that scanner thread is done with that last item
     842                 :       1186 :   void done_front ()
     843                 :            :   {
     844                 :       1186 :     unique_lock<mutex> lock(mtx);
     845                 :       1186 :     fronters --;
     846   [ +  +  +  + ]:       1186 :     if (q.size() == 0 && fronters == 0)
     847                 :         99 :       cv.notify_all(); // maybe wake up waiting idlers
     848                 :       1186 :   }
     849                 :            :   
     850                 :            :   // block this idler thread until there is no work to do
     851                 :        635 :   void wait_idle ()
     852                 :            :   {
     853                 :        635 :     unique_lock<mutex> lock(mtx);
     854                 :        635 :     cv.notify_all(); // maybe wake up waiting scanners
     855   [ +  +  +  +  :        697 :     while (!dead && ((q.size() != 0) || fronters > 0))
                   +  + ]
     856         [ +  - ]:         62 :       cv.wait(lock);
     857         [ +  - ]:        635 :     idlers ++;
     858                 :        635 :   }
     859                 :            : 
     860                 :        563 :   void done_idle ()
     861                 :            :   {
     862                 :        563 :     unique_lock<mutex> lock(mtx);
     863                 :        563 :     idlers --;
     864                 :        563 :     cv.notify_all(); // maybe wake up waiting scanners, but probably not (shutting down)
     865                 :        563 :   }
     866                 :            : };
     867                 :            : 
     868                 :            : typedef struct stat stat_t;
     869                 :            : typedef pair<string,stat_t> scan_payload;
     870                 :            : inline bool operator< (const scan_payload& a, const scan_payload& b)
     871                 :            : {
     872                 :            :   return a.first < b.first; // don't bother compare the stat fields
     873                 :            : }
     874                 :            : 
     875                 :            : namespace std { // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56480
     876                 :            :   template<> struct hash<::scan_payload>
     877                 :            :   {
     878                 :       4719 :     std::size_t operator() (const ::scan_payload& p) const noexcept
     879                 :            :     {
     880   [ +  +  +  + ]:       4719 :       return hash<string>()(p.first);
     881                 :            :     }
     882                 :            :   };
     883                 :            :   template<> struct equal_to<::scan_payload>
     884                 :            :   {
     885                 :        443 :     std::size_t operator() (const ::scan_payload& a, const ::scan_payload& b) const noexcept
     886                 :            :     {
     887   [ -  +  -  - ]:        443 :       return a.first == b.first;
     888                 :            :     }
     889                 :            :   };
     890                 :            : }
     891                 :            : 
     892                 :            : static workq<scan_payload> scanq; // just a single one
     893                 :            : // producer & idler: thread_main_fts_source_paths()
     894                 :            : // consumer: thread_main_scanner()
     895                 :            : // idler: thread_main_groom()
     896                 :            : 
     897                 :            : 
     898                 :            : ////////////////////////////////////////////////////////////////////////
     899                 :            : 
     900                 :            : // Unique set is a thread-safe structure that lends 'ownership' of a value
     901                 :            : // to a thread.  Other threads requesting the same thing are made to wait.
     902                 :            : // It's like a semaphore-on-demand.
     903                 :            : template <typename T>
     904                 :            : class unique_set
     905                 :            : {
     906                 :            : private:
     907                 :            :   set<T> values;
     908                 :            :   mutex mtx;
     909                 :            :   condition_variable cv;
     910                 :            : public:
     911                 :         57 :   unique_set() {}
     912                 :         57 :   ~unique_set() {}
     913                 :            : 
     914                 :       1895 :   void acquire(const T& value)
     915                 :            :   {
     916                 :       1895 :     unique_lock<mutex> lock(mtx);
     917         [ +  + ]:       2650 :     while (values.find(value) != values.end())
     918         [ +  - ]:        755 :       cv.wait(lock);
     919         [ +  - ]:       1895 :     values.insert(value);
     920                 :       1895 :   }
     921                 :            : 
     922                 :       1895 :   void release(const T& value)
     923                 :            :   {
     924                 :       1895 :     unique_lock<mutex> lock(mtx);
     925                 :            :     // assert (values.find(value) != values.end());
     926                 :       1895 :     values.erase(value);
     927                 :       1895 :     cv.notify_all();
     928                 :       1895 :   }
     929                 :            : };
     930                 :            : 
     931                 :            : 
     932                 :            : // This is the object that's instantiate to uniquely hold a value in a
     933                 :            : // RAII-pattern way.
     934                 :            : template <typename T>
     935                 :            : class unique_set_reserver
     936                 :            : {
     937                 :            : private:
     938                 :            :   unique_set<T>& please_hold;
     939                 :            :   T mine;
     940                 :            : public:
     941                 :       1895 :   unique_set_reserver(unique_set<T>& t, const T& value):
     942   [ +  -  -  - ]:       1895 :     please_hold(t), mine(value)  { please_hold.acquire(mine); }
     943         [ +  - ]:       1895 :   ~unique_set_reserver() { please_hold.release(mine); }
     944                 :            : };
     945                 :            : 
     946                 :            : 
     947                 :            : ////////////////////////////////////////////////////////////////////////
     948                 :            : 
     949                 :            : // periodic_barrier is a concurrency control object that lets N threads
     950                 :            : // periodically (based on counter value) agree to wait at a barrier,
     951                 :            : // let one of them carry out some work, then be set free
     952                 :            : 
     953                 :            : class periodic_barrier
     954                 :            : {
     955                 :            : private:
     956                 :            :   unsigned period; // number of count() reports to trigger barrier activation
     957                 :            :   unsigned threads; // number of threads participating
     958                 :            :   mutex mtx; // protects all the following fields
     959                 :            :   unsigned counter; // count of count() reports in the current generation
     960                 :            :   unsigned generation; // barrier activation generation
     961                 :            :   unsigned waiting; // number of threads waiting for barrier
     962                 :            :   bool dead; // bring out your
     963                 :            :   condition_variable cv;
     964                 :            : public:
     965                 :         66 :   periodic_barrier(unsigned t, unsigned p):
     966                 :         66 :     period(p), threads(t), counter(0), generation(0), waiting(0), dead(false) { }
     967                 :            :   virtual ~periodic_barrier() {}
     968                 :            : 
     969                 :            :   virtual void periodic_barrier_work() noexcept = 0;
     970                 :         66 :   void nuke() {
     971                 :         66 :     unique_lock<mutex> lock(mtx);
     972                 :         66 :     dead = true;
     973                 :         66 :     cv.notify_all();
     974                 :         66 :   }
     975                 :            :   
     976                 :       1450 :   void count()
     977                 :            :   {
     978                 :       1450 :     unique_lock<mutex> lock(mtx);
     979                 :       1450 :     unsigned prev_generation = this->generation;
     980         [ +  + ]:       1450 :     if (counter < period-1) // normal case: counter just freely running
     981                 :            :       {
     982                 :       1334 :         counter ++;
     983                 :       1334 :         return;
     984                 :            :       }
     985         [ +  + ]:        116 :     else if (counter == period-1) // we're the doer
     986                 :            :       {
     987                 :         30 :         counter = period; // entering barrier holding phase
     988                 :         30 :         cv.notify_all();
     989   [ +  +  +  + ]:        146 :         while (waiting < threads-1 && !dead)
     990         [ +  - ]:         86 :           cv.wait(lock);
     991                 :            :         // all other threads are now stuck in the barrier
     992                 :         30 :         this->periodic_barrier_work(); // NB: we're holding the mutex the whole time
     993                 :            :         // reset for next barrier, releasing other waiters
     994                 :         30 :         counter = 0;
     995                 :         30 :         generation ++;
     996                 :         30 :         cv.notify_all();
     997                 :         30 :         return;
     998                 :            :       }
     999         [ +  - ]:         86 :     else if (counter == period) // we're a waiter, in holding phase
    1000                 :            :       {
    1001                 :         86 :         waiting ++;
    1002                 :         86 :         cv.notify_all();
    1003   [ +  +  +  -  :        317 :         while (counter == period && generation == prev_generation && !dead)
                   +  - ]
    1004         [ +  - ]:        145 :           cv.wait(lock);
    1005                 :         86 :         waiting --;
    1006                 :         86 :         return;
    1007                 :            :       }
    1008                 :       1450 :   }
    1009                 :            : };
    1010                 :            : 
    1011                 :            : 
    1012                 :            : 
    1013                 :            : ////////////////////////////////////////////////////////////////////////
    1014                 :            : 
    1015                 :            : 
    1016                 :            : // Print a standard timestamp.
    1017                 :            : static ostream&
    1018                 :      41525 : timestamp (ostream &o)
    1019                 :            : {
    1020                 :      41525 :   char datebuf[80];
    1021                 :      41525 :   char *now2 = NULL;
    1022                 :      41525 :   time_t now_t = time(NULL);
    1023                 :      41524 :   struct tm now;
    1024                 :      41524 :   struct tm *nowp = gmtime_r (&now_t, &now);
    1025         [ +  - ]:      41523 :   if (nowp)
    1026                 :            :     {
    1027                 :      41523 :       (void) strftime (datebuf, sizeof (datebuf), "%c", nowp);
    1028                 :      41523 :       now2 = datebuf;
    1029                 :            :     }
    1030                 :            : 
    1031                 :      41523 :   return o << "[" << (now2 ? now2 : "") << "] "
    1032         [ -  + ]:      41523 :            << "(" << getpid () << "/" << tid() << "): ";
    1033                 :            : }
    1034                 :            : 
    1035                 :            : 
    1036                 :            : // A little class that impersonates an ostream to the extent that it can
    1037                 :            : // take << streaming operations.  It batches up the bits into an internal
    1038                 :            : // stringstream until it is destroyed; then flushes to the original ostream.
    1039                 :            : // It adds a timestamp
    1040                 :            : class obatched
    1041                 :            : {
    1042                 :            : private:
    1043                 :            :   ostream& o;
    1044                 :            :   stringstream stro;
    1045                 :            :   static mutex lock;
    1046                 :            : public:
    1047                 :      41524 :   obatched(ostream& oo, bool timestamp_p = true): o(oo)
    1048                 :            :   {
    1049         [ +  - ]:      41525 :     if (timestamp_p)
    1050         [ +  - ]:      41525 :       timestamp(stro);
    1051                 :      41523 :   }
    1052                 :      41523 :   ~obatched()
    1053                 :            :   {
    1054                 :      41523 :     unique_lock<mutex> do_not_cross_the_streams(obatched::lock);
    1055         [ +  - ]:      41525 :     o << stro.str();
    1056                 :      41525 :     o.flush();
    1057                 :      41525 :   }
    1058                 :            :   operator ostream& () { return stro; }
    1059   [ -  -  +  -  :      29815 :   template <typename T> ostream& operator << (const T& t) { stro << t; return stro; }
          +  -  +  -  +  
          -  +  -  +  -  
          -  -  -  -  -  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          -  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  -  
          -  -  -  -  -  
          +  -  -  -  +  
          -  -  -  -  -  
          -  -  +  -  -  
          -  +  -  -  -  
          +  -  -  -  -  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  -  
                   -  - ]
    1060                 :            : };
    1061                 :            : mutex obatched::lock; // just the one, since cout/cerr iostreams are not thread-safe
    1062                 :            : 
    1063                 :            : 
    1064                 :        694 : void reportable_exception::report(ostream& o) const {
    1065   [ +  -  +  - ]:        694 :   obatched(o) << message << endl;
    1066                 :        694 : }
    1067                 :            : 
    1068                 :            : 
    1069                 :            : ////////////////////////////////////////////////////////////////////////
    1070                 :            : 
    1071                 :            : 
    1072                 :            : // RAII style sqlite prepared-statement holder that matches { } block lifetime
    1073                 :            : 
    1074                 :            : struct sqlite_ps
    1075                 :            : {
    1076                 :            : private:
    1077                 :            :   sqlite3* db;
    1078                 :            :   const string nickname;
    1079                 :            :   const string sql;
    1080                 :            :   sqlite3_stmt *pp;
    1081                 :            : 
    1082                 :            :   sqlite_ps(const sqlite_ps&); // make uncopyable
    1083                 :            :   sqlite_ps& operator=(const sqlite_ps &); // make unassignable
    1084                 :            : 
    1085                 :            : public:
    1086   [ +  -  -  - ]:       7063 :   sqlite_ps (sqlite3* d, const string& n, const string& s): db(d), nickname(n), sql(s) {
    1087                 :            :     // tmp_ms_metric tick("sqlite3","prep",nickname);
    1088         [ +  + ]:       7063 :     if (verbose > 4)
    1089   [ +  -  +  -  :        348 :       obatched(clog) << nickname << " prep " << sql << endl;
          +  -  +  -  +  
                -  -  - ]
    1090         [ +  - ]:       7063 :     int rc = sqlite3_prepare_v2 (db, sql.c_str(), -1 /* to \0 */, & this->pp, NULL);
    1091         [ -  + ]:       7063 :     if (rc != SQLITE_OK)
    1092   [ #  #  #  # ]:          0 :       throw sqlite_exception(rc, "prepare " + sql);
    1093                 :       7063 :   }
    1094                 :            : 
    1095                 :     177315 :   sqlite_ps& reset()
    1096                 :            :   {
    1097   [ +  -  +  -  :     354630 :     tmp_ms_metric tick("sqlite3","reset",nickname);
             -  +  -  - ]
    1098         [ +  - ]:     177315 :     sqlite3_reset(this->pp);
    1099                 :     177318 :     return *this;
    1100                 :     177318 :   }
    1101                 :            : 
    1102                 :     203529 :   sqlite_ps& bind(int parameter, const string& str)
    1103                 :            :   {
    1104         [ +  + ]:     203529 :     if (verbose > 4)
    1105   [ +  -  +  -  :       1418 :       obatched(clog) << nickname << " bind " << parameter << "=" << str << endl;
          +  -  +  -  +  
                -  +  - ]
    1106                 :     203529 :     int rc = sqlite3_bind_text (this->pp, parameter, str.c_str(), -1, SQLITE_TRANSIENT);
    1107         [ -  + ]:     203529 :     if (rc != SQLITE_OK)
    1108   [ #  #  #  # ]:          0 :       throw sqlite_exception(rc, "sqlite3 bind");
    1109                 :     203529 :     return *this;
    1110                 :            :   }
    1111                 :            : 
    1112                 :      52026 :   sqlite_ps& bind(int parameter, int64_t value)
    1113                 :            :   {
    1114         [ +  + ]:      52026 :     if (verbose > 4)
    1115   [ +  -  +  -  :        636 :       obatched(clog) << nickname << " bind " << parameter << "=" << value << endl;
          +  -  +  -  +  
                -  +  - ]
    1116                 :      52026 :     int rc = sqlite3_bind_int64 (this->pp, parameter, value);
    1117         [ -  + ]:      52026 :     if (rc != SQLITE_OK)
    1118   [ #  #  #  # ]:          0 :       throw sqlite_exception(rc, "sqlite3 bind");
    1119                 :      52026 :     return *this;
    1120                 :            :   }
    1121                 :            : 
    1122                 :            :   sqlite_ps& bind(int parameter)
    1123                 :            :   {
    1124                 :            :     if (verbose > 4)
    1125                 :            :       obatched(clog) << nickname << " bind " << parameter << "=" << "NULL" << endl;
    1126                 :            :     int rc = sqlite3_bind_null (this->pp, parameter);
    1127                 :            :     if (rc != SQLITE_OK)
    1128                 :            :       throw sqlite_exception(rc, "sqlite3 bind");
    1129                 :            :     return *this;
    1130                 :            :   }
    1131                 :            : 
    1132                 :            : 
    1133                 :     109576 :   void step_ok_done() {
    1134   [ +  -  +  -  :     219149 :     tmp_ms_metric tick("sqlite3","step_done",nickname);
             -  +  -  - ]
    1135         [ +  - ]:     109573 :     int rc = sqlite3_step (this->pp);
    1136         [ +  + ]:     109578 :     if (verbose > 4)
    1137   [ +  -  +  -  :        832 :       obatched(clog) << nickname << " step-ok-done(" << sqlite3_errstr(rc) << ") " << sql << endl;
          +  -  +  -  +  
          -  +  -  +  -  
                   +  - ]
    1138   [ +  +  -  + ]:     109578 :     if (rc != SQLITE_OK && rc != SQLITE_DONE && rc != SQLITE_ROW)
    1139   [ #  #  #  # ]:          0 :       throw sqlite_exception(rc, "sqlite3 step");
    1140         [ +  - ]:     109578 :     (void) sqlite3_reset (this->pp);
    1141                 :     109578 :   }
    1142                 :            : 
    1143                 :            : 
    1144                 :      35566 :   int step() {
    1145   [ +  -  +  -  :      71133 :     tmp_ms_metric tick("sqlite3","step",nickname);
             -  +  -  - ]
    1146         [ +  - ]:      35567 :     int rc = sqlite3_step (this->pp);
    1147         [ +  + ]:      35567 :     if (verbose > 4)
    1148   [ +  -  +  -  :        378 :       obatched(clog) << nickname << " step(" << sqlite3_errstr(rc) << ") " << sql << endl;
          +  -  +  -  +  
          -  +  -  +  -  
                   +  - ]
    1149                 :      35567 :     return rc;
    1150                 :      35567 :   }
    1151                 :            : 
    1152   [ +  +  +  + ]:      14052 :   ~sqlite_ps () { sqlite3_finalize (this->pp); }
    1153   [ +  -  +  -  :       4107 :   operator sqlite3_stmt* () { return this->pp; }
                   +  - ]
    1154                 :            : };
    1155                 :            : 
    1156                 :            : 
    1157                 :            : ////////////////////////////////////////////////////////////////////////
    1158                 :            : 
    1159                 :            : 
    1160                 :            : struct sqlite_checkpoint_pb: public periodic_barrier
    1161                 :            : {
    1162                 :            :   // NB: don't use sqlite_ps since it can throw exceptions during ctor etc.
    1163                 :         66 :   sqlite_checkpoint_pb(unsigned t, unsigned p):
    1164                 :        132 :     periodic_barrier(t, p) { }
    1165                 :            :   
    1166                 :         30 :   void periodic_barrier_work() noexcept
    1167                 :            :   {
    1168                 :         30 :     (void) sqlite3_exec (db, "pragma wal_checkpoint(truncate);", NULL, NULL, NULL);
    1169                 :         30 :   }
    1170                 :            : };
    1171                 :            :   
    1172                 :            : static periodic_barrier* scan_barrier = 0; // initialized in main()
    1173                 :            : 
    1174                 :            : 
    1175                 :            : ////////////////////////////////////////////////////////////////////////
    1176                 :            : 
    1177                 :            : // RAII style templated autocloser
    1178                 :            : 
    1179                 :            : template <class Payload, class Ignore>
    1180                 :            : struct defer_dtor
    1181                 :            : {
    1182                 :            : public:
    1183                 :            :   typedef Ignore (*dtor_fn) (Payload);
    1184                 :            : 
    1185                 :            : private:
    1186                 :            :   Payload p;
    1187                 :            :   dtor_fn fn;
    1188                 :            : 
    1189                 :            : public:
    1190                 :       3321 :   defer_dtor(Payload _p, dtor_fn _fn): p(_p), fn(_fn) {}
    1191                 :        714 :   ~defer_dtor() { (void) (*fn)(p); }
    1192                 :            : 
    1193                 :            : private:
    1194                 :            :   defer_dtor(const defer_dtor<Payload,Ignore>&); // make uncopyable
    1195                 :            :   defer_dtor& operator=(const defer_dtor<Payload,Ignore> &); // make unassignable
    1196                 :            : };
    1197                 :            : 
    1198                 :            : 
    1199                 :            : 
    1200                 :            : ////////////////////////////////////////////////////////////////////////
    1201                 :            : 
    1202                 :            : 
    1203                 :            : static string
    1204                 :       5228 : header_censor(const string& str)
    1205                 :            : {
    1206                 :       5228 :   string y;
    1207         [ +  + ]:      55324 :   for (auto&& x : str)
    1208                 :            :     {
    1209         [ +  + ]:      50096 :       if (isalnum(x) || x == '/' || x == '.' || x == ',' || x == '_' || x == ':')
    1210         [ +  - ]:     100186 :         y += x;
    1211                 :            :     }
    1212                 :       5228 :   return y;
    1213                 :          0 : }
    1214                 :            : 
    1215                 :            : 
    1216                 :            : static string
    1217                 :       2614 : conninfo (struct MHD_Connection * conn)
    1218                 :            : {
    1219                 :       2614 :   char hostname[256]; // RFC1035
    1220                 :       2614 :   char servname[256];
    1221                 :       2614 :   int sts = -1;
    1222                 :            : 
    1223         [ -  + ]:       2614 :   if (conn == 0)
    1224                 :          0 :     return "internal";
    1225                 :            : 
    1226                 :            :   /* Look up client address data. */
    1227                 :       2614 :   const union MHD_ConnectionInfo *u = MHD_get_connection_info (conn,
    1228                 :            :                                                                MHD_CONNECTION_INFO_CLIENT_ADDRESS);
    1229         [ +  - ]:       2614 :   struct sockaddr *so = u ? u->client_addr : 0;
    1230                 :            : 
    1231   [ +  -  -  + ]:       2614 :   if (so && so->sa_family == AF_INET) {
    1232                 :          0 :     sts = getnameinfo (so, sizeof (struct sockaddr_in),
    1233                 :            :                        hostname, sizeof (hostname),
    1234                 :            :                        servname, sizeof (servname),
    1235                 :            :                        NI_NUMERICHOST | NI_NUMERICSERV);
    1236         [ +  - ]:       2614 :   } else if (so && so->sa_family == AF_INET6) {
    1237                 :       2614 :     struct sockaddr_in6* addr6 = (struct sockaddr_in6*) so;
    1238   [ +  -  +  -  :       2614 :     if (IN6_IS_ADDR_V4MAPPED(&addr6->sin6_addr)) {
                   -  + ]
    1239                 :       2614 :       struct sockaddr_in addr4;
    1240                 :       2614 :       memset (&addr4, 0, sizeof(addr4));
    1241                 :       2614 :       addr4.sin_family = AF_INET;
    1242                 :       2614 :       addr4.sin_port = addr6->sin6_port;
    1243                 :       2614 :       memcpy (&addr4.sin_addr.s_addr, addr6->sin6_addr.s6_addr+12, sizeof(addr4.sin_addr.s_addr));
    1244                 :       2614 :       sts = getnameinfo ((struct sockaddr*) &addr4, sizeof (addr4),
    1245                 :            :                          hostname, sizeof (hostname),
    1246                 :            :                          servname, sizeof (servname),
    1247                 :            :                          NI_NUMERICHOST | NI_NUMERICSERV);
    1248                 :            :     } else {
    1249                 :          0 :       sts = getnameinfo (so, sizeof (struct sockaddr_in6),
    1250                 :            :                          hostname, sizeof (hostname),
    1251                 :            :                          servname, sizeof (servname),
    1252                 :            :                          NI_NUMERICHOST | NI_NUMERICSERV);
    1253                 :            :     }
    1254                 :            :   }
    1255                 :            :   
    1256         [ -  + ]:       2614 :   if (sts != 0) {
    1257                 :          0 :     hostname[0] = servname[0] = '\0';
    1258                 :            :   }
    1259                 :            : 
    1260                 :            :   // extract headers relevant to administration
    1261         [ -  + ]:       2614 :   const char* user_agent = MHD_lookup_connection_value (conn, MHD_HEADER_KIND, "User-Agent") ?: "";
    1262         [ +  + ]:       2614 :   const char* x_forwarded_for = MHD_lookup_connection_value (conn, MHD_HEADER_KIND, "X-Forwarded-For") ?: "";
    1263                 :            :   // NB: these are untrustworthy, beware if machine-processing log files
    1264                 :            : 
    1265   [ +  -  +  -  :       7842 :   return string(hostname) + string(":") + string(servname) +
          +  -  +  -  +  
          -  +  -  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  -  -  -  -  
                      - ]
    1266   [ +  -  +  -  :      11269 :     string(" UA:") + header_censor(string(user_agent)) +
          +  -  +  -  +  
          -  -  +  +  +  
          -  +  +  +  -  
             +  -  -  -  
                      - ]
    1267   [ +  -  +  -  :       7854 :     string(" XFF:") + header_censor(string(x_forwarded_for));
          +  -  +  +  +  
                +  -  - ]
    1268                 :            : }
    1269                 :            : 
    1270                 :            : 
    1271                 :            : 
    1272                 :            : ////////////////////////////////////////////////////////////////////////
    1273                 :            : 
    1274                 :            : /* Wrapper for MHD_add_response_header that logs an error if we
    1275                 :            :    couldn't add the specified header.  */
    1276                 :            : static void
    1277                 :       8626 : add_mhd_response_header (struct MHD_Response *r,
    1278                 :            :                          const char *h, const char *v)
    1279                 :            : {
    1280         [ -  + ]:       8626 :   if (MHD_add_response_header (r, h, v) == MHD_NO)
    1281   [ #  #  #  #  :          0 :     obatched(clog) << "Error: couldn't add '" << h << "' header" << endl;
                   #  # ]
    1282                 :       8626 : }
    1283                 :            : 
    1284                 :            : static void
    1285                 :       1311 : add_mhd_last_modified (struct MHD_Response *resp, time_t mtime)
    1286                 :            : {
    1287                 :       1311 :   struct tm now;
    1288                 :       1311 :   struct tm *nowp = gmtime_r (&mtime, &now);
    1289         [ +  - ]:       1311 :   if (nowp != NULL)
    1290                 :            :     {
    1291                 :       1311 :       char datebuf[80];
    1292                 :       1311 :       size_t rc = strftime (datebuf, sizeof (datebuf), "%a, %d %b %Y %T GMT",
    1293                 :            :                             nowp);
    1294         [ +  - ]:       1311 :       if (rc > 0 && rc < sizeof (datebuf))
    1295                 :       1311 :         add_mhd_response_header (resp, "Last-Modified", datebuf);
    1296                 :            :     }
    1297                 :            : 
    1298                 :       1311 :   add_mhd_response_header (resp, "Cache-Control", "public");
    1299                 :       1311 : }
    1300                 :            : 
    1301                 :            : // quote all questionable characters of str for safe passage through a sh -c expansion.
    1302                 :            : static string
    1303                 :         56 : shell_escape(const string& str)
    1304                 :            : {
    1305                 :         56 :   string y;
    1306         [ +  + ]:       7308 :   for (auto&& x : str)
    1307                 :            :     {
    1308   [ +  +  +  + ]:       7252 :       if (! isalnum(x) && x != '/')
    1309         [ +  - ]:        574 :         y += "\\";
    1310         [ +  - ]:      14504 :       y += x;
    1311                 :            :     }
    1312                 :         56 :   return y;
    1313                 :          0 : }
    1314                 :            : 
    1315                 :            : 
    1316                 :            : // PR25548: Perform POSIX / RFC3986 style path canonicalization on the input string.
    1317                 :            : //
    1318                 :            : // Namely:
    1319                 :            : //    //         ->   /
    1320                 :            : //    /foo/../   ->   /
    1321                 :            : //    /./        ->   /
    1322                 :            : //
    1323                 :            : // This mapping is done on dwarf-side source path names, which may
    1324                 :            : // include these constructs, so we can deal with debuginfod clients
    1325                 :            : // that accidentally canonicalize the paths.
    1326                 :            : //
    1327                 :            : // realpath(3) is close but not quite right, because it also resolves
    1328                 :            : // symbolic links.  Symlinks at the debuginfod server have nothing to
    1329                 :            : // do with the build-time symlinks, thus they must not be considered.
    1330                 :            : //
    1331                 :            : // see also curl Curl_dedotdotify() aka RFC3986, which we mostly follow here
    1332                 :            : // see also libc __realpath()
    1333                 :            : // see also llvm llvm::sys::path::remove_dots()
    1334                 :            : static string
    1335                 :      15047 : canon_pathname (const string& input)
    1336                 :            : {
    1337                 :      15047 :   string i = input; // 5.2.4 (1)
    1338                 :      15047 :   string o;
    1339                 :            : 
    1340                 :     143354 :   while (i.size() != 0)
    1341                 :            :     {
    1342                 :            :       // 5.2.4 (2) A
    1343   [ +  -  -  +  :     256613 :       if (i.substr(0,3) == "../")
                   -  + ]
    1344   [ #  #  #  # ]:          0 :         i = i.substr(3);
    1345   [ +  -  -  +  :     256612 :       else if(i.substr(0,2) == "./")
                   -  + ]
    1346   [ #  #  #  # ]:          0 :         i = i.substr(2);
    1347                 :            : 
    1348                 :            :       // 5.2.4 (2) B
    1349   [ +  -  -  +  :     256613 :       else if (i.substr(0,3) == "/./")
                   +  + ]
    1350   [ +  -  +  + ]:       2826 :         i = i.substr(2);
    1351         [ -  + ]:     126659 :       else if (i == "/.")
    1352         [ #  # ]:          0 :         i = ""; // no need to handle "/." complete-path-segment case; we're dealing with file names
    1353                 :            : 
    1354                 :            :       // 5.2.4 (2) C
    1355   [ +  -  -  +  :     253318 :       else if (i.substr(0,4) == "/../") {
                   +  + ]
    1356   [ +  -  +  + ]:       2016 :         i = i.substr(3);
    1357                 :       2016 :         string::size_type sl = o.rfind("/");
    1358         [ +  - ]:       2016 :         if (sl != string::npos)
    1359   [ +  -  +  - ]:       4032 :           o = o.substr(0, sl);
    1360                 :            :         else
    1361         [ #  # ]:          0 :           o = "";
    1362         [ -  + ]:     124643 :       } else if (i == "/..")
    1363         [ #  # ]:          0 :         i = ""; // no need to handle "/.." complete-path-segment case; we're dealing with file names
    1364                 :            : 
    1365                 :            :       // 5.2.4 (2) D
    1366                 :            :       // no need to handle these cases; we're dealing with file names
    1367         [ -  + ]:     124643 :       else if (i == ".")
    1368         [ #  # ]:          0 :         i = "";
    1369         [ -  + ]:     124642 :       else if (i == "..")
    1370         [ #  # ]:          0 :         i = "";
    1371                 :            : 
    1372                 :            :       // POSIX special: map // to /
    1373   [ +  -  -  +  :     249284 :       else if (i.substr(0,2) == "//")
                   +  + ]
    1374   [ +  -  +  + ]:        152 :         i = i.substr(1);
    1375                 :            : 
    1376                 :            :       // 5.2.4 (2) E
    1377                 :            :       else {
    1378         [ -  + ]:     124506 :         string::size_type next_slash = i.find("/", (i[0]=='/' ? 1 : 0)); // skip first slash
    1379   [ +  -  +  + ]:     249011 :         o += i.substr(0, next_slash);
    1380         [ +  + ]:     124506 :         if (next_slash == string::npos)
    1381   [ +  -  +  + ]:     158401 :           i = "";
    1382                 :            :         else
    1383   [ +  -  +  +  :     206807 :           i = i.substr(next_slash);
                   -  - ]
    1384                 :            :       }
    1385                 :            :     }
    1386                 :            : 
    1387         [ +  - ]:      15047 :   return o;
    1388                 :      15047 : }
    1389                 :            : 
    1390                 :            : 
    1391                 :            : // Estimate available free space for a given filesystem via statfs(2).
    1392                 :            : // Return true if the free fraction is known to be smaller than the
    1393                 :            : // given minimum percentage.  Also update a related metric.
    1394                 :       2652 : bool statfs_free_enough_p(const string& path, const string& label, long minfree = 0)
    1395                 :            : {
    1396                 :       2652 :   struct statfs sfs;
    1397                 :       2652 :   int rc = statfs(path.c_str(), &sfs);
    1398         [ +  + ]:       2652 :   if (rc == 0)
    1399                 :            :     {
    1400                 :       2602 :       double s = (double) sfs.f_bavail / (double) sfs.f_blocks;
    1401   [ +  -  +  -  :       5204 :       set_metric("filesys_free_ratio","purpose",label, s);
             -  +  -  - ]
    1402                 :       2602 :       return ((s * 100.0) < minfree);
    1403                 :            :     }
    1404                 :            :   return false;
    1405                 :            : }
    1406                 :            : 
    1407                 :            : 
    1408                 :            : 
    1409                 :            : // A map-like class that owns a cache of file descriptors (indexed by
    1410                 :            : // file / content names).
    1411                 :            : //
    1412                 :            : // If only it could use fd's instead of file names ... but we can't
    1413                 :            : // dup(2) to create independent descriptors for the same unlinked
    1414                 :            : // files, so would have to use some goofy linux /proc/self/fd/%d
    1415                 :            : // hack such as the following
    1416                 :            : 
    1417                 :            : #if 0
    1418                 :            : int superdup(int fd)
    1419                 :            : {
    1420                 :            : #ifdef __linux__
    1421                 :            :   char *fdpath = NULL;
    1422                 :            :   int rc = asprintf(& fdpath, "/proc/self/fd/%d", fd);
    1423                 :            :   int newfd;
    1424                 :            :   if (rc >= 0)
    1425                 :            :     newfd = open(fdpath, O_RDONLY);
    1426                 :            :   else
    1427                 :            :     newfd = -1;
    1428                 :            :   free (fdpath);
    1429                 :            :   return newfd;
    1430                 :            : #else
    1431                 :            :   return -1;
    1432                 :            : #endif
    1433                 :            : }
    1434                 :            : #endif
    1435                 :            : 
    1436                 :            : class libarchive_fdcache
    1437                 :            : {
    1438                 :            : private:
    1439                 :            :   mutex fdcache_lock;
    1440                 :            : 
    1441                 :            :   typedef pair<string,string> key; // archive, entry
    1442         [ +  - ]:        208 :   struct fdcache_entry
    1443                 :            :   {
    1444                 :            :     string fd; // file name (probably in $TMPDIR), not an actual open fd (EMFILE)
    1445                 :            :     double fd_size_mb; // slightly rounded up megabytes
    1446                 :            :     time_t freshness; // when was this entry created or requested last
    1447                 :            :     unsigned request_count; // how many requests were made; or 0=prefetch only
    1448                 :            :     double latency; // how many seconds it took to extract the file
    1449                 :            :   };
    1450                 :            : 
    1451                 :            :   map<key,fdcache_entry> entries; // optimized for lookup
    1452                 :            :   time_t last_cleaning;
    1453                 :            :   long max_mbs;
    1454                 :            : 
    1455                 :            : public:
    1456                 :        282 :   void set_metrics()
    1457                 :            :   {
    1458                 :        282 :     double fdcache_mb = 0.0;
    1459                 :        282 :     double prefetch_mb = 0.0;
    1460                 :        282 :     unsigned fdcache_count = 0;
    1461                 :        282 :     unsigned prefetch_count = 0;
    1462         [ +  + ]:       1730 :     for (auto &i : entries) {
    1463         [ +  + ]:       1448 :       if (i.second.request_count) {
    1464                 :       1354 :         fdcache_mb += i.second.fd_size_mb;
    1465                 :       1354 :         fdcache_count ++;
    1466                 :            :       } else {
    1467                 :         94 :         prefetch_mb += i.second.fd_size_mb;
    1468                 :         94 :         prefetch_count ++;
    1469                 :            :       }
    1470                 :            :     }
    1471         [ +  - ]:        282 :     set_metric("fdcache_bytes", fdcache_mb*1024.0*1024.0);
    1472         [ +  - ]:        282 :     set_metric("fdcache_count", fdcache_count);
    1473         [ +  - ]:        282 :     set_metric("fdcache_prefetch_bytes", prefetch_mb*1024.0*1024.0);
    1474         [ +  - ]:        282 :     set_metric("fdcache_prefetch_count", prefetch_count);
    1475                 :        282 :   }
    1476                 :            : 
    1477                 :        208 :   void intern(const string& a, const string& b, string fd, off_t sz,
    1478                 :            :               bool requested_p, double lat)
    1479                 :            :   {
    1480                 :        208 :     {
    1481                 :        208 :       unique_lock<mutex> lock(fdcache_lock);
    1482                 :        208 :       time_t now = time(NULL);
    1483                 :            :       // there is a chance it's already in here, just wasn't found last time
    1484                 :            :       // if so, there's nothing to do but count our luck
    1485         [ +  - ]:        208 :       auto i = entries.find(make_pair(a,b));
    1486         [ -  + ]:        208 :       if (i != entries.end())
    1487                 :            :         {
    1488   [ #  #  #  #  :          0 :           inc_metric("fdcache_op_count","op","redundant_intern");
          #  #  #  #  #  
          #  #  #  #  #  
                   #  # ]
    1489         [ #  # ]:          0 :           if (requested_p) i->second.request_count ++; // repeat prefetch doesn't count
    1490                 :          0 :           i->second.freshness = now;
    1491                 :            :           // We need to nuke the temp file, since interning passes
    1492                 :            :           // responsibility over the path to this structure.  It is
    1493                 :            :           // possible that the caller still has an fd open, but that's
    1494                 :            :           // OK.
    1495                 :          0 :           unlink (fd.c_str());
    1496                 :          0 :           return;
    1497                 :            :         }
    1498                 :        208 :       double mb = (sz+65535)/1048576.0; // round up to 64K block
    1499                 :        208 :       fdcache_entry n = { .fd=fd, .fd_size_mb=mb,
    1500                 :        208 :                           .freshness=now, .request_count = requested_p?1U:0U,
    1501   [ +  -  +  + ]:        208 :                           .latency=lat};
    1502   [ +  -  +  -  :        208 :       entries.insert(make_pair(make_pair(a,b),n));
                   +  - ]
    1503                 :            :       
    1504         [ +  + ]:        208 :       if (requested_p)
    1505   [ +  -  +  -  :        320 :         inc_metric("fdcache_op_count","op","enqueue");
          +  -  +  -  -  
          +  -  +  -  -  
                   -  - ]
    1506                 :            :       else
    1507   [ +  -  +  -  :        144 :         inc_metric("fdcache_op_count","op","prefetch_enqueue");
          +  -  +  -  -  
          +  +  -  -  -  
                   -  - ]
    1508                 :            :       
    1509         [ +  + ]:        208 :       if (verbose > 3)
    1510   [ +  -  +  -  :        534 :         obatched(clog) << "fdcache interned a=" << a << " b=" << b
                   -  - ]
    1511   [ +  -  +  -  :        178 :                        << " fd=" << fd << " mb=" << mb << " front=" << requested_p
          +  -  +  -  +  
          -  +  -  +  -  
                   +  - ]
    1512   [ +  -  +  -  :        178 :                        << " latency=" << lat << endl;
                   +  - ]
    1513                 :            :       
    1514         [ +  - ]:        208 :       set_metrics();
    1515                 :        208 :     }
    1516                 :            : 
    1517                 :            :     // NB: we age the cache at lookup time too
    1518   [ +  -  -  +  :        208 :     if (statfs_free_enough_p(tmpdir, "tmpdir", fdcache_mintmp))
                   -  + ]
    1519                 :            :       {
    1520   [ #  #  #  #  :          0 :         inc_metric("fdcache_op_count","op","emerg-flush");
          #  #  #  #  #  
             #  #  #  #  
                      # ]
    1521         [ #  # ]:          0 :         obatched(clog) << "fdcache emergency flush for filling tmpdir" << endl;
    1522                 :          0 :         this->limit(0); // emergency flush
    1523                 :            :       }
    1524                 :            :     else // age cache normally
    1525                 :        208 :       this->limit(max_mbs);
    1526                 :            :   }
    1527                 :            : 
    1528                 :        740 :   int lookup(const string& a, const string& b)
    1529                 :            :   {
    1530                 :        740 :     int fd = -1;
    1531                 :        740 :     {
    1532                 :        740 :       unique_lock<mutex> lock(fdcache_lock);
    1533         [ +  - ]:        740 :       auto i = entries.find(make_pair(a,b));
    1534         [ +  + ]:        740 :       if (i != entries.end())
    1535                 :            :         {
    1536         [ +  + ]:        576 :           if (i->second.request_count == 0) // was a prefetch!
    1537                 :            :             {
    1538   [ +  -  +  - ]:         16 :               inc_metric("fdcache_prefetch_saved_milliseconds_count");
    1539   [ +  -  +  - ]:         32 :               add_metric("fdcache_prefetch_saved_milliseconds_sum", i->second.latency*1000.);
    1540                 :            :             }
    1541                 :        576 :           i->second.request_count ++;
    1542                 :        576 :           i->second.freshness = time(NULL);
    1543                 :            :           // brag about our success
    1544   [ +  -  +  -  :       1152 :           inc_metric("fdcache_op_count","op","prefetch_access"); // backward compat
          +  -  +  -  -  
          +  -  +  -  -  
                   -  - ]
    1545   [ +  -  +  - ]:        576 :           inc_metric("fdcache_saved_milliseconds_count");
    1546   [ +  -  +  - ]:        576 :           add_metric("fdcache_saved_milliseconds_sum", i->second.latency*1000.);
    1547         [ +  - ]:        740 :           fd = open(i->second.fd.c_str(), O_RDONLY); 
    1548                 :            :         }
    1549                 :          0 :     }
    1550                 :            : 
    1551         [ +  + ]:        740 :     if (fd >= 0)
    1552   [ +  -  +  -  :       1152 :       inc_metric("fdcache_op_count","op","lookup_hit");
          +  -  -  +  -  
             +  -  -  -  
                      - ]
    1553                 :            :     else
    1554   [ +  -  +  -  :        328 :       inc_metric("fdcache_op_count","op","lookup_miss");
          +  -  -  +  -  
             +  -  -  -  
                      - ]
    1555                 :            :     
    1556                 :            :     // NB: no need to age the cache after just a lookup
    1557                 :            : 
    1558                 :        740 :     return fd;
    1559                 :            :   }
    1560                 :            : 
    1561                 :        226 :   int probe(const string& a, const string& b) // just a cache residency check - don't modify state, don't open
    1562                 :            :   {
    1563                 :        226 :     unique_lock<mutex> lock(fdcache_lock);
    1564         [ +  - ]:        226 :     auto i = entries.find(make_pair(a,b));
    1565         [ +  + ]:        226 :     if (i != entries.end()) {
    1566   [ +  -  +  -  :         52 :       inc_metric("fdcache_op_count","op","probe_hit");
          +  -  +  -  -  
          +  -  +  -  -  
                   -  - ]
    1567                 :         26 :       return true;
    1568                 :            :     } else {
    1569   [ +  -  +  -  :        400 :       inc_metric("fdcache_op_count","op","probe_miss");
          +  -  +  -  -  
          +  -  +  -  -  
                   -  - ]
    1570                 :        200 :       return false;
    1571                 :            :    }
    1572                 :        226 :   }
    1573                 :            :   
    1574                 :          0 :   void clear(const string& a, const string& b)
    1575                 :            :   {
    1576                 :          0 :     unique_lock<mutex> lock(fdcache_lock);
    1577         [ #  # ]:          0 :     auto i = entries.find(make_pair(a,b));
    1578         [ #  # ]:          0 :     if (i != entries.end()) {
    1579   [ #  #  #  #  :          0 :       inc_metric("fdcache_op_count","op",
          #  #  #  #  #  
          #  #  #  #  #  
             #  #  #  # ]
    1580         [ #  # ]:          0 :                  i->second.request_count > 0 ? "clear" : "prefetch_clear");
    1581                 :          0 :       unlink (i->second.fd.c_str());
    1582                 :          0 :       entries.erase(i);
    1583         [ #  # ]:          0 :       set_metrics();
    1584                 :          0 :       return;
    1585                 :            :     }
    1586                 :          0 :   }
    1587                 :            : 
    1588                 :        356 :   void limit(long maxmbs, bool metrics_p = true)
    1589                 :            :   {
    1590                 :        356 :     time_t now = time(NULL);
    1591                 :            : 
    1592                 :            :     // avoid overly frequent limit operations
    1593   [ +  +  +  + ]:        356 :     if (maxmbs > 0 && (now - this->last_cleaning) < 10) // probably not worth parametrizing
    1594                 :        208 :       return;
    1595                 :        148 :     this->last_cleaning = now;
    1596                 :            :     
    1597   [ +  +  +  - ]:        148 :     if (verbose > 3 && (this->max_mbs != maxmbs))
    1598   [ +  -  +  - ]:        192 :       obatched(clog) << "fdcache limited to maxmbs=" << maxmbs << endl;
    1599                 :            : 
    1600                 :        148 :     unique_lock<mutex> lock(fdcache_lock);
    1601                 :            :     
    1602                 :        148 :     this->max_mbs = maxmbs;
    1603                 :        148 :     double total_mb = 0.0;
    1604                 :            : 
    1605                 :        148 :     map<double, pair<string,string>> sorted_entries;
    1606         [ +  + ]:        356 :     for (auto &i: entries)
    1607                 :            :       {
    1608                 :        208 :         total_mb += i.second.fd_size_mb;
    1609                 :            : 
    1610                 :            :         // need a scalar quantity that combines these inputs in a sensible way:
    1611                 :            :         //
    1612                 :            :         // 1) freshness of this entry (last time it was accessed)
    1613                 :            :         // 2) size of this entry
    1614                 :            :         // 3) number of times it has been accessed (or if just prefetched with 0 accesses)
    1615                 :            :         // 4) latency it required to extract
    1616                 :            :         //
    1617                 :            :         // The lower the "score", the earlier garbage collection will
    1618                 :            :         // nuke it, so to prioritize entries for preservation, the
    1619                 :            :         // score should be higher, and vice versa.
    1620                 :        208 :         time_t factor_1_freshness = (now - i.second.freshness); // seconds
    1621                 :        208 :         double factor_2_size = i.second.fd_size_mb; // megabytes
    1622                 :        208 :         unsigned factor_3_accesscount = i.second.request_count; // units
    1623                 :        208 :         double factor_4_latency = i.second.latency; // seconds
    1624                 :            : 
    1625                 :            :         #if 0
    1626                 :            :         double score = - factor_1_freshness; // simple LRU
    1627                 :            :         #endif
    1628                 :            : 
    1629         [ +  + ]:        208 :         double score = 0.
    1630                 :        208 :           - log1p(factor_1_freshness)                // penalize old file
    1631                 :        208 :           - log1p(factor_2_size)                     // penalize large file
    1632                 :        208 :           + factor_4_latency * factor_3_accesscount; // reward slow + repeatedly read files
    1633                 :            : 
    1634         [ +  + ]:        208 :         if (verbose > 4)
    1635         [ +  - ]:         64 :           obatched(clog) << "fdcache scored score=" << score
    1636   [ +  -  +  - ]:         64 :                          << " a=" << i.first.first << " b=" << i.first.second
    1637   [ +  -  +  -  :         96 :                          << " f1=" << factor_1_freshness << " f2=" << factor_2_size
          +  -  +  -  +  
             -  +  -  +  
                      - ]
    1638   [ +  -  +  -  :         32 :                          << " f3=" << factor_3_accesscount << " f4=" << factor_4_latency
          +  -  +  -  +  
                      - ]
    1639                 :         32 :                          << endl;
    1640                 :            :         
    1641   [ +  -  +  - ]:        416 :         sorted_entries.insert(make_pair(score, i.first));
    1642                 :            :       }
    1643                 :            : 
    1644                 :        148 :     unsigned cleaned = 0;
    1645                 :        148 :     unsigned entries_original = entries.size();
    1646                 :        148 :     double cleaned_score_min = DBL_MAX;
    1647                 :        148 :     double cleaned_score_max = DBL_MIN;
    1648                 :            :     
    1649                 :            :     // drop as many entries[] as needed to bring total mb down to the threshold
    1650         [ +  + ]:        356 :     for (auto &i: sorted_entries) // in increasing score order!
    1651                 :            :       {
    1652         [ -  + ]:        208 :         if (this->max_mbs > 0 // if this is not a "clear entire table"
    1653         [ #  # ]:          0 :             && total_mb < this->max_mbs) // we've cleared enough to meet threshold
    1654                 :            :           break; // stop clearing
    1655                 :            : 
    1656         [ -  + ]:        208 :         auto j = entries.find(i.second);
    1657         [ -  + ]:        208 :         if (j == entries.end())
    1658                 :          0 :           continue; // should not happen
    1659                 :            : 
    1660         [ +  + ]:        208 :         if (cleaned == 0)
    1661                 :         32 :           cleaned_score_min = i.first;
    1662                 :        208 :         cleaned++;
    1663                 :        208 :         cleaned_score_max = i.first;
    1664                 :            :         
    1665         [ +  + ]:        208 :         if (verbose > 3)
    1666   [ +  -  +  - ]:        534 :           obatched(clog) << "fdcache evicted score=" << i.first
    1667   [ +  -  +  - ]:        356 :                          << " a=" << i.second.first << " b=" << i.second.second
    1668   [ +  -  +  -  :        534 :                          << " fd=" << j->second.fd << " mb=" << j->second.fd_size_mb
          +  -  +  -  +  
                -  +  - ]
    1669   [ +  -  +  -  :        178 :                          << " rq=" << j->second.request_count << " lat=" << j->second.latency
             +  -  +  - ]
    1670   [ +  -  +  -  :        178 :                          << " fr=" << (now - j->second.freshness)
                   +  - ]
    1671                 :        178 :                          << endl;
    1672         [ -  + ]:        208 :         if (metrics_p)
    1673   [ #  #  #  #  :          0 :           inc_metric("fdcache_op_count","op","evict");
          #  #  #  #  #  
          #  #  #  #  #  
                   #  # ]
    1674                 :            :         
    1675                 :        208 :         total_mb -= j->second.fd_size_mb;
    1676                 :        208 :         unlink (j->second.fd.c_str());
    1677                 :        208 :         entries.erase(j);
    1678                 :            :       }
    1679                 :            : 
    1680         [ +  + ]:        148 :     if (metrics_p)
    1681   [ +  -  +  -  :        148 :       inc_metric("fdcache_op_count","op","evict_cycle");
          +  -  +  -  -  
          +  -  +  -  -  
                   -  - ]
    1682                 :            :     
    1683   [ +  -  +  + ]:        148 :     if (verbose > 1 && cleaned > 0)
    1684                 :            :       {
    1685   [ +  -  +  -  :         96 :         obatched(clog) << "fdcache evicted num=" << cleaned << " of=" << entries_original
             +  -  +  - ]
    1686   [ +  -  +  -  :         32 :                        << " min=" << cleaned_score_min << " max=" << cleaned_score_max
          +  -  +  -  +  
                      - ]
    1687                 :         32 :                        << endl;
    1688                 :            :       }
    1689                 :            :     
    1690   [ +  +  +  - ]:        148 :     if (metrics_p) set_metrics();
    1691                 :        148 :   }
    1692                 :            : 
    1693                 :            : 
    1694                 :         74 :   ~libarchive_fdcache()
    1695                 :            :   {
    1696                 :            :     // unlink any fdcache entries in $TMPDIR
    1697                 :            :     // don't update metrics; those globals may be already destroyed
    1698                 :         74 :     limit(0, false);
    1699                 :         74 :   }
    1700                 :            : };
    1701                 :            : static libarchive_fdcache fdcache;
    1702                 :            : 
    1703                 :            : /* Search ELF_FD for an ELF/DWARF section with name SECTION.
    1704                 :            :    If found copy the section to a temporary file and return
    1705                 :            :    its file descriptor, otherwise return -1.
    1706                 :            : 
    1707                 :            :    The temporary file's mtime will be set to PARENT_MTIME.
    1708                 :            :    B_SOURCE should be a description of the parent file suitable
    1709                 :            :    for printing to the log.  */
    1710                 :            : 
    1711                 :            : static int
    1712                 :         12 : extract_section (int elf_fd, int64_t parent_mtime,
    1713                 :            :                  const string& b_source, const string& section,
    1714                 :            :                  const timespec& extract_begin)
    1715                 :            : {
    1716                 :            :   /* Search the fdcache.  */
    1717                 :         12 :   struct stat fs;
    1718                 :         12 :   int fd = fdcache.lookup (b_source, section);
    1719         [ -  + ]:         12 :   if (fd >= 0)
    1720                 :            :     {
    1721         [ #  # ]:          0 :       if (fstat (fd, &fs) != 0)
    1722                 :            :         {
    1723         [ #  # ]:          0 :           if (verbose)
    1724         [ #  # ]:          0 :             obatched (clog) << "cannot fstate fdcache "
    1725   [ #  #  #  #  :          0 :                             << b_source << " " << section << endl;
                   #  # ]
    1726                 :          0 :           close (fd);
    1727                 :          0 :           return -1;
    1728                 :            :         }
    1729         [ #  # ]:          0 :       if ((int64_t) fs.st_mtime != parent_mtime)
    1730                 :            :         {
    1731         [ #  # ]:          0 :           if (verbose)
    1732         [ #  # ]:          0 :             obatched(clog) << "mtime mismatch for "
    1733   [ #  #  #  #  :          0 :                            << b_source << " " << section << endl;
                   #  # ]
    1734                 :          0 :           close (fd);
    1735                 :          0 :           return -1;
    1736                 :            :         }
    1737                 :            :       /* Success.  */
    1738                 :            :       return fd;
    1739                 :            :     }
    1740                 :            : 
    1741                 :         12 :   Elf *elf = elf_begin (elf_fd, ELF_C_READ_MMAP_PRIVATE, NULL);
    1742         [ -  + ]:         12 :   if (elf == NULL)
    1743                 :            :     return -1;
    1744                 :            : 
    1745                 :            :   /* Try to find the section and copy the contents into a separate file.  */
    1746                 :         12 :   try
    1747                 :            :     {
    1748                 :         12 :       size_t shstrndx;
    1749         [ +  - ]:         12 :       int rc = elf_getshdrstrndx (elf, &shstrndx);
    1750         [ -  + ]:         12 :       if (rc < 0)
    1751   [ #  #  #  # ]:          0 :         throw elfutils_exception (rc, "getshdrstrndx");
    1752                 :            : 
    1753                 :            :       Elf_Scn *scn = NULL;
    1754                 :        432 :       while (true)
    1755                 :            :         {
    1756         [ +  - ]:        222 :           scn = elf_nextscn (elf, scn);
    1757         [ +  - ]:        222 :           if (scn == NULL)
    1758                 :            :             break;
    1759                 :        222 :           GElf_Shdr shdr_storage;
    1760         [ +  - ]:        222 :           GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_storage);
    1761         [ +  - ]:        222 :           if (shdr == NULL)
    1762                 :            :             break;
    1763                 :            : 
    1764         [ +  - ]:        222 :           const char *scn_name = elf_strptr (elf, shstrndx, shdr->sh_name);
    1765         [ +  - ]:        222 :           if (scn_name == NULL)
    1766                 :            :             break;
    1767         [ +  + ]:        222 :           if (scn_name == section)
    1768                 :            :             {
    1769                 :         12 :               Elf_Data *data = NULL;
    1770                 :            : 
    1771                 :            :               /* We found the desired section.  */
    1772         [ +  - ]:         12 :               data = elf_rawdata (scn, NULL);
    1773         [ -  + ]:         12 :               if (data == NULL)
    1774   [ #  #  #  #  :          0 :                 throw elfutils_exception (elf_errno (), "elfraw_data");
                   #  # ]
    1775         [ +  + ]:         12 :               if (data->d_buf == NULL)
    1776                 :            :                 {
    1777   [ +  -  +  - ]:          8 :                   obatched(clog) << "section " << section
    1778   [ +  -  +  - ]:          4 :                                  << " is empty" << endl;
    1779                 :          4 :                   break;
    1780                 :            :                 }
    1781                 :            : 
    1782                 :            :               /* Create temporary file containing the section.  */
    1783                 :          8 :               char *tmppath = NULL;
    1784                 :          8 :               rc = asprintf (&tmppath, "%s/debuginfod-section.XXXXXX", tmpdir.c_str());
    1785         [ -  + ]:          8 :               if (rc < 0)
    1786   [ #  #  #  # ]:          0 :                 throw libc_exception (ENOMEM, "cannot allocate tmppath");
    1787                 :          8 :               defer_dtor<void*,void> tmmpath_freer (tmppath, free);
    1788         [ +  - ]:          8 :               fd = mkstemp (tmppath);
    1789         [ -  + ]:          8 :               if (fd < 0)
    1790   [ #  #  #  # ]:          0 :                 throw libc_exception (errno, "cannot create temporary file");
    1791                 :            : 
    1792         [ +  - ]:          8 :               ssize_t res = write_retry (fd, data->d_buf, data->d_size);
    1793   [ +  -  -  + ]:          8 :               if (res < 0 || (size_t) res != data->d_size) {
    1794         [ #  # ]:          0 :                 close (fd);
    1795                 :          0 :                 unlink (tmppath);
    1796   [ #  #  #  # ]:          0 :                 throw libc_exception (errno, "cannot write to temporary file");
    1797                 :            :               }
    1798                 :            : 
    1799                 :            :               /* Set mtime to be the same as the parent file's mtime.  */
    1800                 :          8 :               struct timespec tvs[2];
    1801         [ -  + ]:          8 :               if (fstat (elf_fd, &fs) != 0) {
    1802         [ #  # ]:          0 :                 close (fd);
    1803                 :          0 :                 unlink (tmppath);
    1804   [ #  #  #  # ]:          0 :                 throw libc_exception (errno, "cannot fstat file");
    1805                 :            :               }
    1806                 :            :               
    1807                 :          8 :               tvs[0].tv_sec = 0;
    1808                 :          8 :               tvs[0].tv_nsec = UTIME_OMIT;
    1809                 :          8 :               tvs[1] = fs.st_mtim;
    1810                 :          8 :               (void) futimens (fd, tvs);
    1811                 :            : 
    1812                 :          8 :               struct timespec extract_end;
    1813                 :          8 :               clock_gettime (CLOCK_MONOTONIC, &extract_end);
    1814                 :          8 :               double extract_time = (extract_end.tv_sec - extract_begin.tv_sec)
    1815                 :          8 :                 + (extract_end.tv_nsec - extract_begin.tv_nsec)/1.e9;
    1816                 :            :               
    1817                 :            :               /* Add to fdcache.  */
    1818   [ +  -  +  - ]:          8 :               fdcache.intern (b_source, section, tmppath, data->d_size, true, extract_time);
    1819                 :          8 :               break;
    1820                 :         12 :             }
    1821                 :        210 :         }
    1822                 :            :     }
    1823         [ -  - ]:          0 :   catch (const reportable_exception &e)
    1824                 :            :     {
    1825         [ -  - ]:          0 :       e.report (clog);
    1826         [ -  - ]:          0 :       close (fd);
    1827                 :          0 :       fd = -1;
    1828                 :          0 :     }
    1829                 :            : 
    1830                 :         12 :   elf_end (elf);
    1831                 :            :   return fd;
    1832                 :            : }
    1833                 :            : 
    1834                 :            : static struct MHD_Response*
    1835                 :        583 : handle_buildid_f_match (bool internal_req_t,
    1836                 :            :                         int64_t b_mtime,
    1837                 :            :                         const string& b_source0,
    1838                 :            :                         const string& section,
    1839                 :            :                         int *result_fd)
    1840                 :            : {
    1841                 :        583 :   (void) internal_req_t; // ignored
    1842                 :            : 
    1843                 :        583 :   struct timespec extract_begin;
    1844                 :        583 :   clock_gettime (CLOCK_MONOTONIC, &extract_begin);
    1845                 :            :   
    1846                 :        583 :   int fd = open(b_source0.c_str(), O_RDONLY);
    1847         [ -  + ]:        583 :   if (fd < 0)
    1848   [ #  #  #  #  :          0 :     throw libc_exception (errno, string("open ") + b_source0);
             #  #  #  # ]
    1849                 :            :   
    1850                 :            :   // NB: use manual close(2) in error case instead of defer_dtor, because
    1851                 :            :   // in the normal case, we want to hand the fd over to libmicrohttpd for
    1852                 :            :   // file transfer.
    1853                 :            : 
    1854                 :        583 :   struct stat s;
    1855                 :        583 :   int rc = fstat(fd, &s);
    1856         [ -  + ]:        583 :   if (rc < 0)
    1857                 :            :     {
    1858                 :          0 :       close(fd);
    1859   [ #  #  #  #  :          0 :       throw libc_exception (errno, string("fstat ") + b_source0);
             #  #  #  # ]
    1860                 :            :     }
    1861                 :            : 
    1862         [ -  + ]:        583 :   if ((int64_t) s.st_mtime != b_mtime)
    1863                 :            :     {
    1864         [ #  # ]:          0 :       if (verbose)
    1865   [ #  #  #  # ]:          0 :         obatched(clog) << "mtime mismatch for " << b_source0 << endl;
    1866                 :          0 :       close(fd);
    1867                 :          0 :       return 0;
    1868                 :            :     }
    1869                 :            : 
    1870         [ +  + ]:        583 :   if (!section.empty ())
    1871                 :            :     {
    1872                 :          6 :       int scn_fd = extract_section (fd, s.st_mtime, b_source0, section, extract_begin);
    1873                 :          6 :       close (fd);
    1874                 :            : 
    1875         [ +  + ]:          6 :       if (scn_fd >= 0)
    1876                 :          4 :         fd = scn_fd;
    1877                 :            :       else
    1878                 :            :         {
    1879         [ +  - ]:          2 :           if (verbose)
    1880         [ +  - ]:          6 :             obatched (clog) << "cannot find section " << section
    1881   [ +  -  +  -  :          2 :                             << " for " << b_source0 << endl;
                   +  - ]
    1882                 :          2 :           return 0;
    1883                 :            :         }
    1884                 :            : 
    1885                 :          4 :       rc = fstat(fd, &s);
    1886         [ -  + ]:          4 :       if (rc < 0)
    1887                 :            :         {
    1888                 :          0 :           close (fd);
    1889   [ #  #  #  #  :          0 :           throw libc_exception (errno, string ("fstat ") + b_source0
          #  #  #  #  #  
             #  #  #  #  
                      # ]
    1890   [ #  #  #  #  :          0 :                                        + string (" ") + section);
          #  #  #  #  #  
                      # ]
    1891                 :            :         }
    1892                 :            :     }
    1893                 :            : 
    1894                 :        581 :   struct MHD_Response* r = MHD_create_response_from_fd ((uint64_t) s.st_size, fd);
    1895   [ +  -  +  -  :       1162 :   inc_metric ("http_responses_total","result","file");
          +  -  -  +  -  
             +  -  -  -  
                      - ]
    1896         [ -  + ]:        581 :   if (r == 0)
    1897                 :            :     {
    1898         [ #  # ]:          0 :       if (verbose)
    1899         [ #  # ]:          0 :         obatched(clog) << "cannot create fd-response for " << b_source0
    1900   [ #  #  #  #  :          0 :                        << " section=" << section << endl;
                   #  # ]
    1901                 :          0 :       close(fd);
    1902                 :            :     }
    1903                 :            :   else
    1904                 :            :     {
    1905                 :        581 :       add_mhd_response_header (r, "Content-Type", "application/octet-stream");
    1906         [ +  - ]:        581 :       add_mhd_response_header (r, "X-DEBUGINFOD-SIZE",
    1907                 :        581 :                                to_string(s.st_size).c_str());
    1908                 :        581 :       add_mhd_response_header (r, "X-DEBUGINFOD-FILE", b_source0.c_str());
    1909                 :        581 :       add_mhd_last_modified (r, s.st_mtime);
    1910         [ +  - ]:        581 :       if (verbose > 1)
    1911   [ +  -  +  -  :       1162 :         obatched(clog) << "serving file " << b_source0 << " section=" << section << endl;
             +  -  +  - ]
    1912                 :            :       /* libmicrohttpd will close it. */
    1913         [ -  + ]:        581 :       if (result_fd)
    1914                 :        581 :         *result_fd = fd;
    1915                 :            :     }
    1916                 :            : 
    1917                 :            :   return r;
    1918                 :            : }
    1919                 :            : 
    1920                 :            : // For security/portability reasons, many distro-package archives have
    1921                 :            : // a "./" in front of path names; others have nothing, others have
    1922                 :            : // "/".  Canonicalize them all to a single leading "/", with the
    1923                 :            : // assumption that this matches the dwarf-derived file names too.
    1924                 :        974 : string canonicalized_archive_entry_pathname(struct archive_entry *e)
    1925                 :            : {
    1926                 :        974 :   string fn = archive_entry_pathname(e);
    1927         [ -  + ]:        974 :   if (fn.size() == 0)
    1928                 :          0 :     return fn;
    1929         [ -  + ]:        974 :   if (fn[0] == '/')
    1930                 :          0 :     return fn;
    1931         [ +  + ]:        974 :   if (fn[0] == '.')
    1932         [ +  - ]:        776 :     return fn.substr(1);
    1933                 :            :   else
    1934   [ +  -  +  -  :        396 :     return string("/")+fn;
                   -  - ]
    1935                 :        974 : }
    1936                 :            : 
    1937                 :            : 
    1938                 :            : 
    1939                 :            : static struct MHD_Response*
    1940                 :        786 : handle_buildid_r_match (bool internal_req_p,
    1941                 :            :                         int64_t b_mtime,
    1942                 :            :                         const string& b_source0,
    1943                 :            :                         const string& b_source1,
    1944                 :            :                         const string& section,
    1945                 :            :                         int *result_fd)
    1946                 :            : {
    1947                 :        786 :   struct timespec extract_begin;
    1948                 :        786 :   clock_gettime (CLOCK_MONOTONIC, &extract_begin);
    1949                 :            : 
    1950                 :        786 :   struct stat fs;
    1951                 :        786 :   int rc = stat (b_source0.c_str(), &fs);
    1952         [ +  + ]:        786 :   if (rc != 0)
    1953   [ +  -  +  -  :        116 :     throw libc_exception (errno, string("stat ") + b_source0);
             +  -  -  + ]
    1954                 :            : 
    1955         [ -  + ]:        728 :   if ((int64_t) fs.st_mtime != b_mtime)
    1956                 :            :     {
    1957         [ #  # ]:          0 :       if (verbose)
    1958   [ #  #  #  # ]:          0 :         obatched(clog) << "mtime mismatch for " << b_source0 << endl;
    1959                 :          0 :       return 0;
    1960                 :            :     }
    1961                 :            : 
    1962                 :            :   // check for a match in the fdcache first
    1963                 :        728 :   int fd = fdcache.lookup(b_source0, b_source1);
    1964         [ +  + ]:        728 :   while (fd >= 0) // got one!; NB: this is really an if() with a possible branch out to the end
    1965                 :            :     {
    1966                 :        576 :       rc = fstat(fd, &fs);
    1967         [ -  + ]:        576 :       if (rc < 0) // disappeared?
    1968                 :            :         {
    1969         [ #  # ]:          0 :           if (verbose)
    1970   [ #  #  #  # ]:          0 :             obatched(clog) << "cannot fstat fdcache " << b_source0 << endl;
    1971                 :          0 :           close(fd);
    1972                 :          0 :           fdcache.clear(b_source0, b_source1);
    1973                 :            :           break; // branch out of if "loop", to try new libarchive fetch attempt
    1974                 :            :         }
    1975                 :            : 
    1976         [ +  + ]:        576 :       if (!section.empty ())
    1977                 :            :         {
    1978   [ +  -  +  - ]:          2 :           int scn_fd = extract_section (fd, fs.st_mtime,
    1979   [ +  -  -  + ]:          4 :                                         b_source0 + ":" + b_source1,
    1980                 :            :                                         section, extract_begin);
    1981                 :          2 :           close (fd);
    1982         [ -  + ]:          2 :           if (scn_fd >= 0)
    1983                 :          0 :             fd = scn_fd;
    1984                 :            :           else
    1985                 :            :             {
    1986         [ +  - ]:          2 :               if (verbose)
    1987         [ +  - ]:          6 :                 obatched (clog) << "cannot find section " << section
    1988                 :            :                                 << " for archive " << b_source0
    1989   [ +  -  +  -  :          2 :                                 << " file " << b_source1 << endl;
          +  -  +  -  +  
                      - ]
    1990                 :          2 :               return 0;
    1991                 :            :             }
    1992                 :            : 
    1993                 :          0 :           rc = fstat(fd, &fs);
    1994         [ #  # ]:          0 :           if (rc < 0)
    1995                 :            :             {
    1996                 :          0 :               close (fd);
    1997   [ #  #  #  # ]:          0 :               throw libc_exception (errno,
    1998   [ #  #  #  #  :          0 :                 string ("fstat archive ") + b_source0 + string (" file ") + b_source1
          #  #  #  #  #  
          #  #  #  #  #  
          #  #  #  #  #  
             #  #  #  #  
                      # ]
    1999   [ #  #  #  #  :          0 :                 + string (" section ") + section);
          #  #  #  #  #  
                      # ]
    2000                 :            :             }
    2001                 :            :         }
    2002                 :            : 
    2003                 :        574 :       struct MHD_Response* r = MHD_create_response_from_fd (fs.st_size, fd);
    2004         [ -  + ]:        574 :       if (r == 0)
    2005                 :            :         {
    2006         [ #  # ]:          0 :           if (verbose)
    2007   [ #  #  #  # ]:          0 :             obatched(clog) << "cannot create fd-response for " << b_source0 << endl;
    2008                 :          0 :           close(fd);
    2009                 :            :           break; // branch out of if "loop", to try new libarchive fetch attempt
    2010                 :            :         }
    2011                 :            : 
    2012   [ +  -  +  -  :       1148 :       inc_metric ("http_responses_total","result","archive fdcache");
          +  -  -  +  -  
             +  -  -  -  
                      - ]
    2013                 :            : 
    2014                 :        574 :       add_mhd_response_header (r, "Content-Type", "application/octet-stream");
    2015         [ +  - ]:        574 :       add_mhd_response_header (r, "X-DEBUGINFOD-SIZE",
    2016                 :        574 :                                to_string(fs.st_size).c_str());
    2017                 :        574 :       add_mhd_response_header (r, "X-DEBUGINFOD-ARCHIVE", b_source0.c_str());
    2018                 :        574 :       add_mhd_response_header (r, "X-DEBUGINFOD-FILE", b_source1.c_str());
    2019                 :        574 :       add_mhd_last_modified (r, fs.st_mtime);
    2020         [ +  - ]:        574 :       if (verbose > 1)
    2021         [ +  - ]:       1722 :         obatched(clog) << "serving fdcache archive " << b_source0
    2022                 :            :                        << " file " << b_source1
    2023   [ +  -  +  -  :        574 :                        << " section=" << section << endl;
          +  -  +  -  +  
                      - ]
    2024                 :            :       /* libmicrohttpd will close it. */
    2025         [ +  - ]:        574 :       if (result_fd)
    2026                 :        574 :         *result_fd = fd;
    2027                 :            :       return r;
    2028                 :            :       // NB: see, we never go around the 'loop' more than once
    2029                 :            :     }
    2030                 :            : 
    2031                 :            :   // no match ... grumble, must process the archive
    2032                 :        152 :   string archive_decoder = "/dev/null";
    2033   [ +  -  -  - ]:        152 :   string archive_extension = "";
    2034         [ +  + ]:        342 :   for (auto&& arch : scan_archives)
    2035         [ +  + ]:        190 :     if (string_endswith(b_source0, arch.first))
    2036                 :            :       {
    2037         [ +  - ]:        152 :         archive_extension = arch.first;
    2038         [ +  - ]:        342 :         archive_decoder = arch.second;
    2039                 :            :       }
    2040                 :        152 :   FILE* fp;
    2041                 :            :   
    2042                 :        152 :   defer_dtor<FILE*,int>::dtor_fn dfn;
    2043         [ +  + ]:        152 :   if (archive_decoder != "cat")
    2044                 :            :     {
    2045   [ +  -  +  -  :         48 :       string popen_cmd = archive_decoder + " " + shell_escape(b_source0);
          +  -  -  +  -  
                -  -  - ]
    2046         [ +  - ]:         24 :       fp = popen (popen_cmd.c_str(), "r"); // "e" O_CLOEXEC?
    2047                 :         24 :       dfn = pclose;
    2048         [ -  + ]:         24 :       if (fp == NULL)
    2049   [ #  #  #  #  :          0 :         throw libc_exception (errno, string("popen ") + popen_cmd);
             #  #  #  # ]
    2050                 :         24 :     }
    2051                 :            :   else
    2052                 :            :     {
    2053         [ +  - ]:        128 :       fp = fopen (b_source0.c_str(), "r");
    2054                 :        128 :       dfn = fclose;
    2055         [ -  + ]:        128 :       if (fp == NULL)
    2056   [ #  #  #  #  :          0 :         throw libc_exception (errno, string("fopen ") + b_source0);
             #  #  #  # ]
    2057                 :            :     }
    2058                 :        152 :   defer_dtor<FILE*,int> fp_closer (fp, dfn);
    2059                 :            : 
    2060                 :        152 :   struct archive *a;
    2061         [ +  - ]:        152 :   a = archive_read_new();
    2062         [ -  + ]:        152 :   if (a == NULL)
    2063   [ #  #  #  # ]:          0 :     throw archive_exception("cannot create archive reader");
    2064                 :        152 :   defer_dtor<struct archive*,int> archive_closer (a, archive_read_free);
    2065                 :            : 
    2066         [ +  - ]:        152 :   rc = archive_read_support_format_all(a);
    2067         [ -  + ]:        152 :   if (rc != ARCHIVE_OK)
    2068   [ #  #  #  # ]:          0 :     throw archive_exception(a, "cannot select all format");
    2069         [ +  - ]:        152 :   rc = archive_read_support_filter_all(a);
    2070         [ -  + ]:        152 :   if (rc != ARCHIVE_OK)
    2071   [ #  #  #  # ]:          0 :     throw archive_exception(a, "cannot select all filters");
    2072                 :            : 
    2073         [ +  - ]:        152 :   rc = archive_read_open_FILE (a, fp);
    2074         [ -  + ]:        152 :   if (rc != ARCHIVE_OK)
    2075                 :            :     {
    2076   [ #  #  #  #  :          0 :       obatched(clog) << "cannot open archive from pipe " << b_source0 << endl;
                   #  # ]
    2077   [ #  #  #  # ]:          0 :       throw archive_exception(a, "cannot open archive from pipe");
    2078                 :            :     }
    2079                 :            : 
    2080                 :            :   // archive traversal is in three stages, no, four stages:
    2081                 :            :   // 1) skip entries whose names do not match the requested one
    2082                 :            :   // 2) extract the matching entry name (set r = result)
    2083                 :            :   // 3) extract some number of prefetched entries (just into fdcache)
    2084                 :            :   // 4) abort any further processing
    2085                 :        152 :   struct MHD_Response* r = 0;                 // will set in stage 2
    2086         [ +  + ]:        152 :   unsigned prefetch_count =
    2087                 :            :     internal_req_p ? 0 : fdcache_prefetch;    // will decrement in stage 3
    2088                 :            : 
    2089         [ +  + ]:       1536 :   while(r == 0 || prefetch_count > 0) // stage 1, 2, or 3
    2090                 :            :     {
    2091         [ +  - ]:       1516 :       if (interrupted)
    2092                 :            :         break;
    2093                 :            : 
    2094                 :       1516 :       struct archive_entry *e;
    2095         [ +  - ]:       1516 :       rc = archive_read_next_header (a, &e);
    2096         [ +  + ]:       1516 :       if (rc != ARCHIVE_OK)
    2097                 :            :         break;
    2098                 :            : 
    2099   [ +  -  +  + ]:       1384 :       if (! S_ISREG(archive_entry_mode (e))) // skip non-files completely
    2100                 :       1084 :         continue;
    2101                 :            : 
    2102         [ +  - ]:        300 :       string fn = canonicalized_archive_entry_pathname (e);
    2103   [ +  +  +  + ]:        300 :       if ((r == 0) && (fn != b_source1)) // stage 1
    2104                 :         76 :         continue;
    2105                 :            : 
    2106   [ +  -  +  + ]:        224 :       if (fdcache.probe (b_source0, fn) && // skip if already interned
    2107         [ -  + ]:         24 :           fn != b_source1) // but only if we'd just be prefetching, PR29474
    2108                 :         24 :         continue;
    2109                 :            : 
    2110                 :            :       // extract this file to a temporary file
    2111                 :        200 :       char* tmppath = NULL;
    2112                 :        200 :       rc = asprintf (&tmppath, "%s/debuginfod-fdcache.XXXXXX", tmpdir.c_str());
    2113         [ -  + ]:        200 :       if (rc < 0)
    2114   [ #  #  #  # ]:          0 :         throw libc_exception (ENOMEM, "cannot allocate tmppath");
    2115                 :        200 :       defer_dtor<void*,void> tmmpath_freer (tmppath, free);
    2116         [ +  - ]:        200 :       fd = mkstemp (tmppath);
    2117         [ -  + ]:        200 :       if (fd < 0)
    2118   [ #  #  #  # ]:          0 :         throw libc_exception (errno, "cannot create temporary file");
    2119                 :            :       // NB: don't unlink (tmppath), as fdcache will take charge of it.
    2120                 :            : 
    2121                 :            :       // NB: this can take many uninterruptible seconds for a huge file
    2122         [ +  - ]:        200 :       rc = archive_read_data_into_fd (a, fd);
    2123         [ -  + ]:        200 :       if (rc != ARCHIVE_OK) // e.g. ENOSPC!
    2124                 :            :         {
    2125         [ #  # ]:          0 :           close (fd);
    2126                 :          0 :           unlink (tmppath);
    2127   [ #  #  #  # ]:          0 :           throw archive_exception(a, "cannot extract file");
    2128                 :            :         }
    2129                 :            : 
    2130                 :            :       // Set the mtime so the fdcache file mtimes, even prefetched ones,
    2131                 :            :       // propagate to future webapi clients.
    2132                 :        200 :       struct timespec tvs[2];
    2133                 :        200 :       tvs[0].tv_sec = 0;
    2134                 :        200 :       tvs[0].tv_nsec = UTIME_OMIT;
    2135         [ +  - ]:        200 :       tvs[1].tv_sec = archive_entry_mtime(e);
    2136         [ +  - ]:        200 :       tvs[1].tv_nsec = archive_entry_mtime_nsec(e);
    2137                 :        200 :       (void) futimens (fd, tvs);  /* best effort */
    2138                 :            : 
    2139                 :        200 :       struct timespec extract_end;
    2140                 :        200 :       clock_gettime (CLOCK_MONOTONIC, &extract_end);
    2141                 :        200 :       double extract_time = (extract_end.tv_sec - extract_begin.tv_sec)
    2142                 :        200 :         + (extract_end.tv_nsec - extract_begin.tv_nsec)/1.e9;
    2143                 :            :       
    2144         [ +  + ]:        200 :       if (r != 0) // stage 3
    2145                 :            :         {
    2146                 :            :           // NB: now we know we have a complete reusable file; make fdcache
    2147                 :            :           // responsible for unlinking it later.
    2148   [ +  -  +  -  :         48 :           fdcache.intern(b_source0, fn,
                   +  - ]
    2149                 :            :                          tmppath, archive_entry_size(e),
    2150                 :            :                          false, extract_time); // prefetched ones go to the prefetch cache
    2151                 :         48 :           prefetch_count --;
    2152         [ +  - ]:         48 :           close (fd); // we're not saving this fd to make a mhd-response from!
    2153                 :         48 :           continue;
    2154                 :            :         }
    2155                 :            : 
    2156                 :            :       // NB: now we know we have a complete reusable file; make fdcache
    2157                 :            :       // responsible for unlinking it later.
    2158   [ +  -  +  -  :        152 :       fdcache.intern(b_source0, b_source1,
                   +  - ]
    2159                 :            :                      tmppath, archive_entry_size(e),
    2160                 :            :                      true, extract_time); // requested ones go to the front of the line
    2161                 :            : 
    2162         [ +  + ]:        152 :       if (!section.empty ())
    2163                 :            :         {
    2164   [ +  -  +  - ]:          4 :           int scn_fd = extract_section (fd, b_mtime,
    2165   [ +  -  +  -  :          8 :                                         b_source0 + ":" + b_source1,
                   -  + ]
    2166                 :            :                                         section, extract_begin);
    2167         [ +  - ]:          4 :           close (fd);
    2168         [ +  - ]:          4 :           if (scn_fd >= 0)
    2169                 :          4 :             fd = scn_fd;
    2170                 :            :           else
    2171                 :            :             {
    2172         [ #  # ]:          0 :               if (verbose)
    2173   [ #  #  #  # ]:          0 :                 obatched (clog) << "cannot find section " << section
    2174                 :            :                                 << " for archive " << b_source0
    2175   [ #  #  #  #  :          0 :                                 << " file " << b_source1 << endl;
          #  #  #  #  #  
                      # ]
    2176                 :          0 :               return 0;
    2177                 :            :             }
    2178                 :            : 
    2179                 :          4 :           rc = fstat(fd, &fs);
    2180         [ -  + ]:          4 :           if (rc < 0)
    2181                 :            :             {
    2182         [ #  # ]:          0 :               close (fd);
    2183   [ #  #  #  # ]:          0 :               throw libc_exception (errno,
    2184   [ #  #  #  #  :          0 :                 string ("fstat ") + b_source0 + string (" ") + section);
          #  #  #  #  #  
          #  #  #  #  #  
          #  #  #  #  #  
                      # ]
    2185                 :            :             }
    2186         [ +  - ]:          4 :           r = MHD_create_response_from_fd (fs.st_size, fd);
    2187                 :            :         }
    2188                 :            :       else
    2189   [ +  -  +  - ]:        148 :         r = MHD_create_response_from_fd (archive_entry_size(e), fd);
    2190                 :            : 
    2191   [ +  -  +  -  :        304 :       inc_metric ("http_responses_total","result",archive_extension + " archive");
          +  -  +  -  -  
          +  +  +  -  -  
                   -  - ]
    2192         [ -  + ]:        152 :       if (r == 0)
    2193                 :            :         {
    2194         [ #  # ]:          0 :           if (verbose)
    2195   [ #  #  #  #  :          0 :             obatched(clog) << "cannot create fd-response for " << b_source0 << endl;
                   #  # ]
    2196         [ #  # ]:          0 :           close(fd);
    2197                 :          0 :           break; // assume no chance of better luck around another iteration; no other copies of same file
    2198                 :            :         }
    2199                 :            :       else
    2200                 :            :         {
    2201         [ +  - ]:        152 :           add_mhd_response_header (r, "Content-Type",
    2202                 :            :                                    "application/octet-stream");
    2203         [ +  - ]:        152 :           add_mhd_response_header (r, "X-DEBUGINFOD-SIZE",
    2204   [ +  -  +  - ]:        152 :                                    to_string(archive_entry_size(e)).c_str());
    2205         [ +  - ]:        152 :           add_mhd_response_header (r, "X-DEBUGINFOD-ARCHIVE", b_source0.c_str());
    2206         [ +  - ]:        152 :           add_mhd_response_header (r, "X-DEBUGINFOD-FILE", b_source1.c_str());
    2207   [ +  -  +  - ]:        152 :           add_mhd_last_modified (r, archive_entry_mtime(e));
    2208         [ +  - ]:        152 :           if (verbose > 1)
    2209   [ +  -  +  - ]:        456 :             obatched(clog) << "serving archive " << b_source0
    2210                 :            :                            << " file " << b_source1
    2211   [ +  -  +  -  :        152 :                            << " section=" << section << endl;
          +  -  +  -  +  
                      - ]
    2212                 :            :           /* libmicrohttpd will close it. */
    2213         [ +  - ]:        152 :           if (result_fd)
    2214                 :        152 :             *result_fd = fd;
    2215                 :        152 :           continue;
    2216                 :            :         }
    2217   [ -  -  -  -  :       1816 :     }
                   +  + ]
    2218                 :            : 
    2219                 :            :   // XXX: rpm/file not found: delete this R entry?
    2220                 :            :   return r;
    2221   [ -  +  +  + ]:        880 : }
    2222                 :            : 
    2223                 :            : 
    2224                 :            : static struct MHD_Response*
    2225                 :       1369 : handle_buildid_match (bool internal_req_p,
    2226                 :            :                       int64_t b_mtime,
    2227                 :            :                       const string& b_stype,
    2228                 :            :                       const string& b_source0,
    2229                 :            :                       const string& b_source1,
    2230                 :            :                       const string& section,
    2231                 :            :                       int *result_fd)
    2232                 :            : {
    2233                 :       1369 :   try
    2234                 :            :     {
    2235         [ +  + ]:       1369 :       if (b_stype == "F")
    2236         [ +  - ]:        583 :         return handle_buildid_f_match(internal_req_p, b_mtime, b_source0,
    2237                 :            :                                       section, result_fd);
    2238         [ +  - ]:        786 :       else if (b_stype == "R")
    2239         [ +  + ]:        786 :         return handle_buildid_r_match(internal_req_p, b_mtime, b_source0,
    2240                 :            :                                       b_source1, section, result_fd);
    2241                 :            :     }
    2242         [ -  + ]:         58 :   catch (const reportable_exception &e)
    2243                 :            :     {
    2244         [ +  - ]:         58 :       e.report(clog);
    2245                 :            :       // Report but swallow libc etc. errors here; let the caller
    2246                 :            :       // iterate to other matches of the content.
    2247                 :         58 :     }
    2248                 :            : 
    2249                 :            :   return 0;
    2250                 :            : }
    2251                 :            : 
    2252                 :            : 
    2253                 :            : static int
    2254                 :       3236 : debuginfod_find_progress (debuginfod_client *, long a, long b)
    2255                 :            : {
    2256         [ -  + ]:       3236 :   if (verbose > 4)
    2257   [ #  #  #  #  :          0 :     obatched(clog) << "federated debuginfod progress=" << a << "/" << b << endl;
             #  #  #  # ]
    2258                 :            : 
    2259                 :       3236 :   return interrupted;
    2260                 :            : }
    2261                 :            : 
    2262                 :            : 
    2263                 :            : // a little lru pool of debuginfod_client*s for reuse between query threads
    2264                 :            : 
    2265                 :            : mutex dc_pool_lock;
    2266                 :            : deque<debuginfod_client*> dc_pool;
    2267                 :            : 
    2268                 :        620 : debuginfod_client* debuginfod_pool_begin()
    2269                 :            : {
    2270                 :        620 :   unique_lock<mutex> lock(dc_pool_lock);
    2271         [ +  + ]:        620 :   if (dc_pool.size() > 0)
    2272                 :            :     {
    2273   [ +  -  +  -  :       1188 :       inc_metric("dc_pool_op_count","op","begin-reuse");
          +  -  +  -  -  
          +  -  +  -  -  
                   -  - ]
    2274                 :        594 :       debuginfod_client *c = dc_pool.front();
    2275                 :        594 :       dc_pool.pop_front();
    2276                 :        594 :       return c;
    2277                 :            :     }
    2278   [ +  -  +  -  :         52 :   inc_metric("dc_pool_op_count","op","begin-new");
          +  -  +  -  -  
          +  -  +  -  -  
                   -  - ]
    2279         [ +  - ]:         26 :   return debuginfod_begin();
    2280                 :        620 : }
    2281                 :            : 
    2282                 :            : 
    2283                 :        146 : void debuginfod_pool_groom()
    2284                 :            : {
    2285                 :        146 :   unique_lock<mutex> lock(dc_pool_lock);
    2286         [ +  + ]:        172 :   while (dc_pool.size() > 0)
    2287                 :            :     {
    2288   [ +  -  +  -  :         52 :       inc_metric("dc_pool_op_count","op","end");
          +  -  +  -  -  
          +  -  +  -  -  
                   -  - ]
    2289         [ +  - ]:         26 :       debuginfod_end(dc_pool.front());
    2290                 :         26 :       dc_pool.pop_front();
    2291                 :            :     }
    2292                 :        146 : }
    2293                 :            : 
    2294                 :            : 
    2295                 :        620 : void debuginfod_pool_end(debuginfod_client* c)
    2296                 :            : {
    2297                 :        620 :   unique_lock<mutex> lock(dc_pool_lock);
    2298   [ +  -  +  -  :       1240 :   inc_metric("dc_pool_op_count","op","end-save");
          +  -  +  -  -  
          +  -  +  -  -  
                   -  - ]
    2299         [ +  - ]:        620 :   dc_pool.push_front(c); // accelerate reuse, vs. push_back
    2300                 :        620 : }
    2301                 :            : 
    2302                 :            : 
    2303                 :            : static struct MHD_Response*
    2304                 :       1935 : handle_buildid (MHD_Connection* conn,
    2305                 :            :                 const string& buildid /* unsafe */,
    2306                 :            :                 string& artifacttype /* unsafe, cleanse on exception/return */,
    2307                 :            :                 const string& suffix /* unsafe */,
    2308                 :            :                 int *result_fd)
    2309                 :            : {
    2310                 :            :   // validate artifacttype
    2311                 :       1935 :   string atype_code;
    2312   [ +  +  +  - ]:       1935 :   if (artifacttype == "debuginfo") atype_code = "D";
    2313   [ +  +  +  - ]:       1197 :   else if (artifacttype == "executable") atype_code = "E";
    2314   [ +  +  +  - ]:        577 :   else if (artifacttype == "source") atype_code = "S";
    2315   [ +  +  +  - ]:         12 :   else if (artifacttype == "section") atype_code = "I";
    2316                 :            :   else {
    2317         [ +  - ]:          4 :     artifacttype = "invalid"; // PR28242 ensure http_resposes metrics don't propagate unclean user data 
    2318   [ +  -  +  - ]:         12 :     throw reportable_exception("invalid artifacttype");
    2319                 :            :   }
    2320                 :            : 
    2321         [ +  + ]:       1931 :   if (conn != 0)
    2322   [ +  -  +  -  :       4406 :     inc_metric("http_requests_total", "type", artifacttype);
          +  -  -  +  -  
                -  -  + ]
    2323                 :            : 
    2324                 :       1931 :   string section;
    2325         [ +  + ]:       1931 :   if (atype_code == "I")
    2326                 :            :     {
    2327         [ -  + ]:          8 :       if (suffix.size () < 2)
    2328   [ #  #  #  # ]:          0 :         throw reportable_exception ("invalid section suffix");
    2329                 :            : 
    2330                 :            :       // Remove leading '/'
    2331   [ +  -  -  + ]:          8 :       section = suffix.substr(1);
    2332                 :            :     }
    2333                 :            : 
    2334   [ +  +  -  + ]:       2496 :   if (atype_code == "S" && suffix == "")
    2335   [ #  #  #  # ]:          0 :      throw reportable_exception("invalid source suffix");
    2336                 :            : 
    2337                 :            :   // validate buildid
    2338         [ +  + ]:       1931 :   if ((buildid.size() < 2) || // not empty
    2339   [ +  +  +  -  :       3858 :       (buildid.size() % 2) || // even number
                   +  - ]
    2340                 :       1927 :       (buildid.find_first_not_of("0123456789abcdef") != string::npos)) // pure tasty lowercase hex
    2341   [ +  -  -  + ]:          8 :     throw reportable_exception("invalid buildid");
    2342                 :            : 
    2343         [ +  - ]:       1927 :   if (verbose > 1)
    2344   [ +  -  +  - ]:       5781 :     obatched(clog) << "searching for buildid=" << buildid << " artifacttype=" << artifacttype
    2345   [ +  -  +  -  :       1927 :          << " suffix=" << suffix << endl;
          +  -  +  -  +  
                      - ]
    2346                 :            : 
    2347                 :            :   // If invoked from the scanner threads, use the scanners' read-write
    2348                 :            :   // connection.  Otherwise use the web query threads' read-only connection.
    2349         [ +  + ]:       1927 :   sqlite3 *thisdb = (conn == 0) ? db : dbq;
    2350                 :            : 
    2351                 :       1927 :   sqlite_ps *pp = 0;
    2352                 :            : 
    2353         [ +  + ]:       1927 :   if (atype_code == "D")
    2354                 :            :     {
    2355         [ -  + ]:        738 :       pp = new sqlite_ps (thisdb, "mhd-query-d",
    2356                 :            :                           "select mtime, sourcetype, source0, source1 from " BUILDIDS "_query_d where buildid = ? "
    2357   [ +  -  +  -  :       1476 :                           "order by mtime desc");
          +  -  +  -  +  
                -  -  - ]
    2358         [ +  - ]:        738 :       pp->reset();
    2359         [ +  - ]:        738 :       pp->bind(1, buildid);
    2360                 :            :     }
    2361         [ +  + ]:       1189 :   else if (atype_code == "E")
    2362                 :            :     {
    2363         [ -  + ]:        616 :       pp = new sqlite_ps (thisdb, "mhd-query-e",
    2364                 :            :                           "select mtime, sourcetype, source0, source1 from " BUILDIDS "_query_e where buildid = ? "
    2365   [ +  -  +  -  :       1232 :                           "order by mtime desc");
          +  -  +  -  +  
                -  -  - ]
    2366         [ +  - ]:        616 :       pp->reset();
    2367         [ +  - ]:        616 :       pp->bind(1, buildid);
    2368                 :            :     }
    2369         [ +  + ]:        573 :   else if (atype_code == "S")
    2370                 :            :     {
    2371                 :            :       // PR25548
    2372                 :            :       // Incoming source queries may come in with either dwarf-level OR canonicalized paths.
    2373                 :            :       // We let the query pass with either one.
    2374                 :            : 
    2375         [ -  + ]:        565 :       pp = new sqlite_ps (thisdb, "mhd-query-s",
    2376                 :            :                           "select mtime, sourcetype, source0, source1 from " BUILDIDS "_query_s where buildid = ? and artifactsrc in (?,?) "
    2377   [ +  -  +  -  :       1130 :                           "order by sharedprefix(source0,source0ref) desc, mtime desc");
          +  -  +  -  +  
                -  -  - ]
    2378         [ +  - ]:        565 :       pp->reset();
    2379         [ +  - ]:        565 :       pp->bind(1, buildid);
    2380                 :            :       // NB: we don't store the non-canonicalized path names any more, but old databases
    2381                 :            :       // might have them (and no canon ones), so we keep searching for both.
    2382         [ +  - ]:        565 :       pp->bind(2, suffix);
    2383   [ +  -  +  -  :       1750 :       pp->bind(3, canon_pathname(suffix));
                   -  + ]
    2384                 :            :     }
    2385         [ +  - ]:          8 :   else if (atype_code == "I")
    2386                 :            :     {
    2387         [ -  + ]:          8 :       pp = new sqlite_ps (thisdb, "mhd-query-i",
    2388                 :            :         "select mtime, sourcetype, source0, source1, 1 as debug_p from " BUILDIDS "_query_d where buildid = ? "
    2389                 :            :         "union all "
    2390                 :            :         "select mtime, sourcetype, source0, source1, 0 as debug_p from " BUILDIDS "_query_e where buildid = ? "
    2391   [ +  -  +  -  :         16 :         "order by debug_p desc, mtime desc");
          +  -  +  -  +  
                -  -  - ]
    2392         [ +  - ]:          8 :       pp->reset();
    2393         [ +  - ]:          8 :       pp->bind(1, buildid);
    2394         [ +  - ]:          8 :       pp->bind(2, buildid);
    2395                 :            :     }
    2396                 :       1927 :   unique_ptr<sqlite_ps> ps_closer(pp); // release pp if exception or return
    2397                 :            : 
    2398                 :       1927 :   bool do_upstream_section_query = true;
    2399                 :            : 
    2400                 :            :   // consume all the rows
    2401                 :       1989 :   while (1)
    2402                 :            :     {
    2403         [ +  - ]:       1989 :       int rc = pp->step();
    2404         [ +  + ]:       1989 :       if (rc == SQLITE_DONE) break;
    2405         [ -  + ]:       1369 :       if (rc != SQLITE_ROW)
    2406   [ #  #  #  # ]:          0 :         throw sqlite_exception(rc, "step");
    2407                 :            : 
    2408         [ +  - ]:       1369 :       int64_t b_mtime = sqlite3_column_int64 (*pp, 0);
    2409   [ +  -  -  +  :       1369 :       string b_stype = string((const char*) sqlite3_column_text (*pp, 1) ?: ""); /* by DDL may not be NULL */
                   +  - ]
    2410   [ +  -  -  +  :       1369 :       string b_source0 = string((const char*) sqlite3_column_text (*pp, 2) ?: ""); /* may be NULL */
             +  -  -  - ]
    2411   [ +  -  +  +  :       1952 :       string b_source1 = string((const char*) sqlite3_column_text (*pp, 3) ?: ""); /* may be NULL */
             +  -  -  - ]
    2412                 :            : 
    2413         [ +  - ]:       1369 :       if (verbose > 1)
    2414   [ +  -  +  -  :       4107 :         obatched(clog) << "found mtime=" << b_mtime << " stype=" << b_stype
                   -  - ]
    2415   [ +  -  +  -  :       1369 :              << " source0=" << b_source0 << " source1=" << b_source1 << endl;
          +  -  +  -  +  
             -  +  -  +  
                      - ]
    2416                 :            : 
    2417                 :            :       // Try accessing the located match.
    2418                 :            :       // XXX: in case of multiple matches, attempt them in parallel?
    2419         [ +  - ]:       1369 :       auto r = handle_buildid_match (conn ? false : true,
    2420                 :            :                                      b_mtime, b_stype, b_source0, b_source1,
    2421                 :            :                                      section, result_fd);
    2422         [ +  + ]:       1369 :       if (r)
    2423         [ +  + ]:       1307 :         return r;
    2424                 :            : 
    2425                 :            :       // If a debuginfo file matching BUILDID was found but didn't contain
    2426                 :            :       // the desired section, then the section should not exist.  Don't
    2427                 :            :       // bother querying upstream servers.
    2428   [ +  +  +  -  :         62 :       if (!section.empty () && (sqlite3_column_int (*pp, 4) == 1))
                   -  + ]
    2429                 :            :         {
    2430                 :          4 :           struct stat st;
    2431                 :            : 
    2432                 :            :           // For "F" sourcetype, check if the debuginfo exists. For "R"
    2433                 :            :           // sourcetype, check if the debuginfo was interned into the fdcache.
    2434         [ -  + ]:          6 :           if ((b_stype == "F" && (stat (b_source0.c_str (), &st) == 0))
    2435   [ +  +  +  -  :          6 :               || (b_stype == "R" && fdcache.probe (b_source0, b_source1)))
             +  -  -  + ]
    2436                 :            :             do_upstream_section_query = false;
    2437                 :            :         }
    2438   [ +  -  -  +  :       2738 :     }
             +  -  -  + ]
    2439         [ +  - ]:        620 :   pp->reset();
    2440                 :            : 
    2441         [ -  + ]:        620 :   if (!do_upstream_section_query)
    2442   [ #  #  #  # ]:          0 :     throw reportable_exception(MHD_HTTP_NOT_FOUND, "not found");
    2443                 :            : 
    2444                 :            :   // We couldn't find it in the database.  Last ditch effort
    2445                 :            :   // is to defer to other debuginfo servers.
    2446                 :            : 
    2447                 :        620 :   int fd = -1;
    2448         [ +  - ]:        620 :   debuginfod_client *client = debuginfod_pool_begin ();
    2449         [ -  + ]:        620 :   if (client == NULL)
    2450   [ #  #  #  # ]:          0 :     throw libc_exception(errno, "debuginfod client pool alloc");
    2451                 :        620 :   defer_dtor<debuginfod_client*,void> client_closer (client, debuginfod_pool_end);
    2452                 :            :   
    2453         [ +  - ]:        620 :   debuginfod_set_progressfn (client, & debuginfod_find_progress);
    2454                 :            : 
    2455         [ +  - ]:        620 :   if (conn)
    2456                 :            :     {
    2457                 :            :       // Transcribe incoming User-Agent:
    2458   [ +  -  -  +  :        620 :       string ua = MHD_lookup_connection_value (conn, MHD_HEADER_KIND, "User-Agent") ?: "";
                   +  - ]
    2459   [ +  -  +  -  :        624 :       string ua_complete = string("User-Agent: ") + ua;
                   +  - ]
    2460         [ +  - ]:        620 :       debuginfod_add_http_header (client, ua_complete.c_str());
    2461                 :            :       
    2462                 :            :       // Compute larger XFF:, for avoiding info loss during
    2463                 :            :       // federation, and for future cyclicity detection.
    2464   [ +  -  +  +  :       1226 :       string xff = MHD_lookup_connection_value (conn, MHD_HEADER_KIND, "X-Forwarded-For") ?: "";
             +  -  +  - ]
    2465         [ +  + ]:        620 :       if (xff != "")
    2466   [ +  -  -  + ]:         36 :         xff += string(", "); // comma separated list
    2467                 :            :       
    2468                 :        620 :       unsigned int xff_count = 0;
    2469         [ +  + ]:        860 :       for (auto&& i : xff){
    2470         [ +  + ]:        240 :         if (i == ',') xff_count++;
    2471                 :            :       }
    2472                 :            : 
    2473                 :            :       // if X-Forwarded-For: exceeds N hops,
    2474                 :            :       // do not delegate a local lookup miss to upstream debuginfods.
    2475         [ +  + ]:        620 :       if (xff_count >= forwarded_ttl_limit)
    2476   [ +  -  +  - ]:          8 :         throw reportable_exception(MHD_HTTP_NOT_FOUND, "not found, --forwared-ttl-limit reached \
    2477                 :          8 : and will not query the upstream servers");
    2478                 :            : 
    2479                 :            :       // Compute the client's numeric IP address only - so can't merge with conninfo()
    2480         [ +  - ]:        616 :       const union MHD_ConnectionInfo *u = MHD_get_connection_info (conn,
    2481                 :            :                                                                    MHD_CONNECTION_INFO_CLIENT_ADDRESS);
    2482         [ +  - ]:        616 :       struct sockaddr *so = u ? u->client_addr : 0;
    2483                 :        616 :       char hostname[256] = ""; // RFC1035
    2484   [ +  -  -  + ]:        616 :       if (so && so->sa_family == AF_INET) {
    2485         [ #  # ]:          0 :         (void) getnameinfo (so, sizeof (struct sockaddr_in), hostname, sizeof (hostname), NULL, 0,
    2486                 :            :                             NI_NUMERICHOST);
    2487         [ +  - ]:        616 :       } else if (so && so->sa_family == AF_INET6) {
    2488                 :        616 :         struct sockaddr_in6* addr6 = (struct sockaddr_in6*) so;
    2489   [ +  -  +  -  :        616 :         if (IN6_IS_ADDR_V4MAPPED(&addr6->sin6_addr)) {
                   -  + ]
    2490                 :        616 :           struct sockaddr_in addr4;
    2491         [ +  - ]:        616 :           memset (&addr4, 0, sizeof(addr4));
    2492                 :        616 :           addr4.sin_family = AF_INET;
    2493                 :        616 :           addr4.sin_port = addr6->sin6_port;
    2494         [ +  - ]:        616 :           memcpy (&addr4.sin_addr.s_addr, addr6->sin6_addr.s6_addr+12, sizeof(addr4.sin_addr.s_addr));
    2495         [ +  - ]:        616 :           (void) getnameinfo ((struct sockaddr*) &addr4, sizeof (addr4),
    2496                 :            :                               hostname, sizeof (hostname), NULL, 0,
    2497                 :            :                               NI_NUMERICHOST);
    2498                 :            :         } else {
    2499         [ #  # ]:          0 :           (void) getnameinfo (so, sizeof (struct sockaddr_in6), hostname, sizeof (hostname), NULL, 0,
    2500                 :            :                               NI_NUMERICHOST);
    2501                 :            :         }
    2502                 :            :       }
    2503                 :            :           
    2504   [ +  -  +  -  :       1236 :       string xff_complete = string("X-Forwarded-For: ")+xff+string(hostname);
          +  -  +  -  -  
          +  -  +  -  -  
                   -  + ]
    2505         [ +  - ]:        616 :       debuginfod_add_http_header (client, xff_complete.c_str());
    2506   [ +  +  +  -  :       1300 :     }
                   +  + ]
    2507                 :            :   
    2508         [ +  + ]:        616 :   if (artifacttype == "debuginfo")
    2509         [ +  - ]:         68 :     fd = debuginfod_find_debuginfo (client,
    2510         [ +  - ]:         68 :                                     (const unsigned char*) buildid.c_str(),
    2511                 :            :                                     0, NULL);
    2512         [ +  + ]:        548 :   else if (artifacttype == "executable")
    2513         [ +  - ]:        546 :     fd = debuginfod_find_executable (client,
    2514         [ +  - ]:        546 :                                      (const unsigned char*) buildid.c_str(),
    2515                 :            :                                      0, NULL);
    2516         [ +  - ]:          2 :   else if (artifacttype == "source")
    2517         [ +  - ]:          2 :     fd = debuginfod_find_source (client,
    2518         [ +  - ]:          2 :                                  (const unsigned char*) buildid.c_str(),
    2519                 :            :                                  0, suffix.c_str(), NULL);
    2520         [ #  # ]:          0 :   else if (artifacttype == "section")
    2521         [ #  # ]:          0 :     fd = debuginfod_find_section (client,
    2522         [ #  # ]:          0 :                                   (const unsigned char*) buildid.c_str(),
    2523                 :            :                                   0, section.c_str(), NULL);
    2524                 :            :   
    2525         [ +  + ]:        616 :   if (fd >= 0)
    2526                 :            :     {
    2527         [ +  - ]:          4 :       if (conn != 0)
    2528   [ +  -  +  -  :        624 :         inc_metric ("http_responses_total","result","upstream");
          +  -  +  -  -  
          +  -  +  -  -  
                   -  - ]
    2529                 :          4 :       struct stat s;
    2530                 :          4 :       int rc = fstat (fd, &s);
    2531         [ +  - ]:          4 :       if (rc == 0)
    2532                 :            :         {
    2533         [ +  - ]:          4 :           auto r = MHD_create_response_from_fd ((uint64_t) s.st_size, fd);
    2534         [ +  - ]:          4 :           if (r)
    2535                 :            :             {
    2536         [ +  - ]:          4 :               add_mhd_response_header (r, "Content-Type",
    2537                 :            :                                        "application/octet-stream");
    2538                 :            :               // Copy the incoming headers
    2539         [ +  - ]:          4 :               const char * hdrs = debuginfod_get_headers(client);
    2540         [ +  - ]:          4 :               string header_dup;
    2541         [ +  - ]:          4 :               if (hdrs)
    2542   [ +  -  -  + ]:          4 :                 header_dup = string(hdrs);
    2543                 :            :               // Parse the "header: value\n" lines into (h,v) tuples and pass on
    2544                 :         20 :               while(1)
    2545                 :            :                 {
    2546                 :         12 :                   size_t newline = header_dup.find('\n');
    2547         [ +  + ]:         12 :                   if (newline == string::npos) break;
    2548                 :          8 :                   size_t colon = header_dup.find(':');
    2549         [ +  - ]:          8 :                   if (colon == string::npos) break;
    2550         [ +  - ]:          8 :                   string header = header_dup.substr(0,colon);
    2551         [ +  - ]:          8 :                   string value = header_dup.substr(colon+1,newline-colon-1);
    2552                 :            :                   // strip leading spaces from value
    2553                 :          8 :                   size_t nonspace = value.find_first_not_of(" ");
    2554         [ +  - ]:          8 :                   if (nonspace != string::npos)
    2555   [ +  -  +  + ]:         12 :                     value = value.substr(nonspace);
    2556         [ +  - ]:          8 :                   add_mhd_response_header(r, header.c_str(), value.c_str());
    2557   [ +  -  +  +  :         12 :                   header_dup = header_dup.substr(newline+1);
             +  +  -  - ]
    2558         [ +  - ]:         16 :                 }
    2559                 :            : 
    2560         [ +  - ]:          4 :               add_mhd_last_modified (r, s.st_mtime);
    2561         [ +  - ]:          4 :               if (verbose > 1)
    2562   [ +  -  +  -  :          8 :                 obatched(clog) << "serving file from upstream debuginfod/cache" << endl;
                   -  - ]
    2563         [ +  - ]:          4 :               if (result_fd)
    2564                 :          4 :                 *result_fd = fd;
    2565         [ +  - ]:          4 :               return r; // NB: don't close fd; libmicrohttpd will
    2566                 :          4 :             }
    2567                 :            :         }
    2568         [ #  # ]:          0 :       close (fd);
    2569                 :            :     }
    2570                 :            :   else
    2571         [ +  + ]:        612 :     switch(fd)
    2572                 :            :       {
    2573                 :            :       case -ENOSYS:
    2574                 :            :         break;
    2575                 :            :       case -ENOENT:
    2576                 :            :         break;
    2577                 :        532 :       default: // some more tricky error
    2578   [ +  -  +  - ]:       1064 :         throw libc_exception(-fd, "upstream debuginfod query failed");
    2579                 :            :       }
    2580                 :            : 
    2581   [ +  -  -  + ]:        160 :   throw reportable_exception(MHD_HTTP_NOT_FOUND, "not found");
    2582   [ -  +  -  + ]:       2547 : }
    2583                 :            : 
    2584                 :            : 
    2585                 :            : ////////////////////////////////////////////////////////////////////////
    2586                 :            : 
    2587                 :            : static map<string,double> metrics; // arbitrary data for /metrics query
    2588                 :            : // NB: store int64_t since all our metrics are integers; prometheus accepts double
    2589                 :            : static mutex metrics_lock;
    2590                 :            : // NB: these objects get released during the process exit via global dtors
    2591                 :            : // do not call them from within other global dtors
    2592                 :            : 
    2593                 :            : // utility function for assembling prometheus-compatible
    2594                 :            : // name="escaped-value" strings
    2595                 :            : // https://prometheus.io/docs/instrumenting/exposition_formats/
    2596                 :            : static string
    2597                 :     724550 : metric_label(const string& name, const string& value)
    2598                 :            : {
    2599                 :     724550 :   string x = name + "=\"";
    2600         [ +  + ]:   12618422 :   for (auto&& c : value)
    2601   [ -  -  -  + ]:   11894008 :     switch(c)
    2602                 :            :       {
    2603         [ #  # ]:          0 :       case '\\': x += "\\\\"; break;
    2604         [ #  # ]:          0 :       case '\"': x += "\\\""; break;
    2605         [ #  # ]:          0 :       case '\n': x += "\\n"; break;
    2606         [ +  - ]:   23787997 :       default: x += c; break;
    2607                 :            :       }
    2608         [ +  - ]:     724414 :   x += "\"";
    2609                 :     724474 :   return x;
    2610                 :          0 : }
    2611                 :            : 
    2612                 :            : 
    2613                 :            : // add prometheus-format metric name + label tuple (if any) + value
    2614                 :            : 
    2615                 :            : static void
    2616                 :       1276 : set_metric(const string& metric, double value)
    2617                 :            : {
    2618                 :       1276 :   unique_lock<mutex> lock(metrics_lock);
    2619         [ +  - ]:       1276 :   metrics[metric] = value;
    2620                 :       1276 : }
    2621                 :            : static void
    2622                 :        592 : inc_metric(const string& metric)
    2623                 :            : {
    2624                 :        592 :   unique_lock<mutex> lock(metrics_lock);
    2625         [ +  - ]:        592 :   metrics[metric] ++;
    2626                 :        592 : }
    2627                 :            : static void
    2628                 :       6408 : set_metric(const string& metric,
    2629                 :            :            const string& lname, const string& lvalue,
    2630                 :            :            double value)
    2631                 :            : {
    2632   [ +  -  +  -  :      12816 :   string key = (metric + "{" + metric_label(lname, lvalue) + "}");
          -  +  -  +  +  
                +  -  - ]
    2633         [ +  - ]:       6408 :   unique_lock<mutex> lock(metrics_lock);
    2634         [ +  - ]:       6408 :   metrics[key] = value;
    2635         [ +  - ]:      12816 : }
    2636                 :            : 
    2637                 :            : static void
    2638                 :     349475 : inc_metric(const string& metric,
    2639                 :            :            const string& lname, const string& lvalue)
    2640                 :            : {
    2641   [ +  -  +  -  :     760818 :   string key = (metric + "{" + metric_label(lname, lvalue) + "}");
          -  +  +  +  +  
                +  -  - ]
    2642         [ +  - ]:     349481 :   unique_lock<mutex> lock(metrics_lock);
    2643         [ +  - ]:     349483 :   metrics[key] ++;
    2644         [ +  - ]:     698963 : }
    2645                 :            : static void
    2646                 :     337339 : add_metric(const string& metric,
    2647                 :            :            const string& lname, const string& lvalue,
    2648                 :            :            double value)
    2649                 :            : {
    2650   [ +  -  +  -  :     736531 :   string key = (metric + "{" + metric_label(lname, lvalue) + "}");
          -  +  +  +  +  
                +  -  - ]
    2651         [ +  - ]:     337331 :   unique_lock<mutex> lock(metrics_lock);
    2652         [ +  - ]:     337356 :   metrics[key] += value;
    2653         [ +  - ]:     674703 : }
    2654                 :            : static void
    2655                 :        592 : add_metric(const string& metric,
    2656                 :            :            double value)
    2657                 :            : {
    2658                 :        592 :   unique_lock<mutex> lock(metrics_lock);
    2659         [ +  - ]:        592 :   metrics[metric] += value;
    2660                 :        592 : }
    2661                 :            : 
    2662                 :            : 
    2663                 :            : // and more for higher arity labels if needed
    2664                 :            : 
    2665                 :            : static void
    2666                 :       7842 : inc_metric(const string& metric,
    2667                 :            :            const string& lname, const string& lvalue,
    2668                 :            :            const string& rname, const string& rvalue)
    2669                 :            : {
    2670   [ +  -  -  +  :      15684 :   string key = (metric + "{"
                   -  - ]
    2671   [ +  -  +  -  :      31368 :                 + metric_label(lname, lvalue) + ","
          -  +  -  +  +  
                +  -  - ]
    2672   [ +  -  -  +  :      23526 :                 + metric_label(rname, rvalue) + "}");
                   -  + ]
    2673         [ +  - ]:       7842 :   unique_lock<mutex> lock(metrics_lock);
    2674         [ +  - ]:       7842 :   metrics[key] ++;
    2675         [ +  - ]:      15684 : }
    2676                 :            : static void
    2677                 :       7842 : add_metric(const string& metric,
    2678                 :            :            const string& lname, const string& lvalue,
    2679                 :            :            const string& rname, const string& rvalue,
    2680                 :            :            double value)
    2681                 :            : {
    2682   [ +  -  -  +  :      15684 :   string key = (metric + "{"
                   -  - ]
    2683   [ +  -  +  -  :      31368 :                 + metric_label(lname, lvalue) + ","
          -  +  -  +  +  
                +  -  - ]
    2684   [ +  -  -  +  :      23526 :                 + metric_label(rname, rvalue) + "}");
                   -  + ]
    2685         [ +  - ]:       7842 :   unique_lock<mutex> lock(metrics_lock);
    2686         [ +  - ]:       7842 :   metrics[key] += value;
    2687         [ +  - ]:      15684 : }
    2688                 :            : 
    2689                 :            : static struct MHD_Response*
    2690                 :        713 : handle_metrics (off_t* size)
    2691                 :            : {
    2692                 :        713 :   stringstream o;
    2693                 :        713 :   {
    2694         [ +  - ]:        713 :     unique_lock<mutex> lock(metrics_lock);
    2695         [ +  + ]:      67901 :     for (auto&& i : metrics)
    2696         [ +  - ]:      67188 :       o << i.first
    2697                 :            :         << " "
    2698   [ +  -  +  - ]:      67188 :         << std::setprecision(std::numeric_limits<double>::digits10 + 1)
    2699   [ +  -  +  - ]:      67188 :         << i.second
    2700                 :      67188 :         << endl;
    2701                 :          0 :   }
    2702         [ +  - ]:        713 :   const string& os = o.str();
    2703         [ +  - ]:        713 :   MHD_Response* r = MHD_create_response_from_buffer (os.size(),
    2704         [ +  - ]:        713 :                                                      (void*) os.c_str(),
    2705                 :            :                                                      MHD_RESPMEM_MUST_COPY);
    2706         [ +  - ]:        713 :   if (r != NULL)
    2707                 :            :     {
    2708         [ +  - ]:        713 :       *size = os.size();
    2709         [ +  - ]:        713 :       add_mhd_response_header (r, "Content-Type", "text/plain");
    2710                 :            :     }
    2711         [ +  - ]:       1426 :   return r;
    2712                 :        713 : }
    2713                 :            : 
    2714                 :            : static struct MHD_Response*
    2715                 :          0 : handle_root (off_t* size)
    2716                 :            : {
    2717   [ #  #  #  #  :          0 :   static string version = "debuginfod (" + string (PACKAGE_NAME) + ") "
          #  #  #  #  #  
                #  #  # ]
    2718   [ #  #  #  #  :          0 :                           + string (PACKAGE_VERSION);
          #  #  #  #  #  
                      # ]
    2719                 :          0 :   MHD_Response* r = MHD_create_response_from_buffer (version.size (),
    2720                 :          0 :                                                      (void *) version.c_str (),
    2721                 :            :                                                      MHD_RESPMEM_PERSISTENT);
    2722         [ #  # ]:          0 :   if (r != NULL)
    2723                 :            :     {
    2724                 :          0 :       *size = version.size ();
    2725                 :          0 :       add_mhd_response_header (r, "Content-Type", "text/plain");
    2726                 :            :     }
    2727                 :          0 :   return r;
    2728                 :            : }
    2729                 :            : 
    2730                 :            : 
    2731                 :            : ////////////////////////////////////////////////////////////////////////
    2732                 :            : 
    2733                 :            : 
    2734                 :            : /* libmicrohttpd callback */
    2735                 :            : static MHD_RESULT
    2736                 :       5228 : handler_cb (void * /*cls*/,
    2737                 :            :             struct MHD_Connection *connection,
    2738                 :            :             const char *url,
    2739                 :            :             const char *method,
    2740                 :            :             const char * /*version*/,
    2741                 :            :             const char * /*upload_data*/,
    2742                 :            :             size_t * /*upload_data_size*/,
    2743                 :            :             void ** ptr)
    2744                 :            : {
    2745                 :       5228 :   struct MHD_Response *r = NULL;
    2746                 :       5228 :   string url_copy = url;
    2747                 :            : 
    2748                 :            :   /* libmicrohttpd always makes (at least) two callbacks: once just
    2749                 :            :      past the headers, and one after the request body is finished
    2750                 :            :      being received.  If we process things early (first callback) and
    2751                 :            :      queue a response, libmicrohttpd would suppress http keep-alive
    2752                 :            :      (via connection->read_closed = true). */
    2753                 :       5228 :   static int aptr; /* just some random object to use as a flag */
    2754         [ +  + ]:       5228 :   if (&aptr != *ptr)
    2755                 :            :     {
    2756                 :            :       /* do never respond on first call */
    2757                 :       2614 :       *ptr = &aptr;
    2758                 :       2614 :       return MHD_YES;
    2759                 :            :     }
    2760                 :       2614 :   *ptr = NULL;                     /* reset when done */
    2761                 :            :   
    2762         [ +  - ]:       2614 :   const char *maxsize_string = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "X-DEBUGINFOD-MAXSIZE");
    2763                 :       2614 :   long maxsize = 0;
    2764   [ +  +  +  - ]:       2614 :   if (maxsize_string != NULL && maxsize_string[0] != '\0')
    2765                 :          2 :     maxsize = atol(maxsize_string);
    2766                 :            :   else
    2767                 :            :     maxsize = 0;
    2768                 :            : 
    2769                 :            : #if MHD_VERSION >= 0x00097002
    2770                 :       2614 :   enum MHD_Result rc;
    2771                 :            : #else
    2772                 :            :   int rc = MHD_NO; // mhd
    2773                 :            : #endif
    2774                 :       2614 :   int http_code = 500;
    2775                 :       2614 :   off_t http_size = -1;
    2776                 :       2614 :   struct timespec ts_start, ts_end;
    2777                 :       2614 :   clock_gettime (CLOCK_MONOTONIC, &ts_start);
    2778                 :       2614 :   double afteryou = 0.0;
    2779         [ +  - ]:       2614 :   string artifacttype, suffix;
    2780                 :            : 
    2781                 :       2614 :   try
    2782                 :            :     {
    2783   [ +  -  -  +  :       5860 :       if (string(method) != "GET")
                   -  + ]
    2784   [ #  #  #  # ]:          0 :         throw reportable_exception(400, "we support GET only");
    2785                 :            : 
    2786                 :            :       /* Start decoding the URL. */
    2787                 :       2614 :       size_t slash1 = url_copy.find('/', 1);
    2788         [ +  - ]:       2614 :       string url1 = url_copy.substr(0, slash1); // ok even if slash1 not found
    2789                 :            : 
    2790   [ +  +  +  + ]:       4509 :       if (slash1 != string::npos && url1 == "/buildid")
    2791                 :            :         {
    2792                 :            :           // PR27863: block this thread awhile if another thread is already busy
    2793                 :            :           // fetching the exact same thing.  This is better for Everyone.
    2794                 :            :           // The latecomer says "... after you!" and waits.
    2795   [ +  -  +  -  :       4413 :           add_metric ("thread_busy", "role", "http-buildid-after-you", 1);
          +  -  +  -  -  
          +  +  -  -  -  
                   -  - ]
    2796                 :            : #ifdef HAVE_PTHREAD_SETNAME_NP
    2797                 :       1895 :           (void) pthread_setname_np (pthread_self(), "mhd-buildid-after-you");
    2798                 :            : #endif
    2799                 :       1895 :           struct timespec tsay_start, tsay_end;
    2800                 :       1895 :           clock_gettime (CLOCK_MONOTONIC, &tsay_start);
    2801   [ +  +  +  - ]:       1895 :           static unique_set<string> busy_urls;
    2802         [ +  - ]:       1895 :           unique_set_reserver<string> after_you(busy_urls, url_copy);
    2803                 :       1895 :           clock_gettime (CLOCK_MONOTONIC, &tsay_end);
    2804                 :       1895 :           afteryou = (tsay_end.tv_sec - tsay_start.tv_sec) + (tsay_end.tv_nsec - tsay_start.tv_nsec)/1.e9;
    2805   [ +  -  +  -  :       3790 :           add_metric ("thread_busy", "role", "http-buildid-after-you", -1);
          +  -  +  -  -  
          +  +  -  -  -  
                   -  - ]
    2806                 :            :           
    2807   [ +  -  +  -  :       3790 :           tmp_inc_metric m ("thread_busy", "role", "http-buildid");
          +  -  +  -  -  
          +  -  +  -  -  
                   -  - ]
    2808                 :            : #ifdef HAVE_PTHREAD_SETNAME_NP
    2809                 :       1895 :           (void) pthread_setname_np (pthread_self(), "mhd-buildid");
    2810                 :            : #endif
    2811                 :       1895 :           size_t slash2 = url_copy.find('/', slash1+1);
    2812         [ -  + ]:       1895 :           if (slash2 == string::npos)
    2813   [ #  #  #  # ]:          0 :             throw reportable_exception("/buildid/ webapi error, need buildid");
    2814                 :            : 
    2815         [ +  - ]:       1895 :           string buildid = url_copy.substr(slash1+1, slash2-slash1-1);
    2816                 :            : 
    2817                 :       1895 :           size_t slash3 = url_copy.find('/', slash2+1);
    2818                 :            : 
    2819         [ +  + ]:       1895 :           if (slash3 == string::npos)
    2820                 :            :             {
    2821   [ +  -  -  + ]:       1322 :               artifacttype = url_copy.substr(slash2+1);
    2822         [ +  - ]:       1322 :               suffix = "";
    2823                 :            :             }
    2824                 :            :           else
    2825                 :            :             {
    2826   [ +  -  -  + ]:        573 :               artifacttype = url_copy.substr(slash2+1, slash3-slash2-1);
    2827   [ +  -  -  +  :       1197 :               suffix = url_copy.substr(slash3); // include the slash in the suffix
                   +  + ]
    2828                 :            :             }
    2829                 :            : 
    2830                 :            :           // get the resulting fd so we can report its size
    2831                 :       1895 :           int fd;
    2832         [ +  + ]:       1895 :           r = handle_buildid(connection, buildid, artifacttype, suffix, &fd);
    2833         [ +  - ]:       1271 :           if (r)
    2834                 :            :             {
    2835                 :       1271 :               struct stat fs;
    2836         [ +  - ]:       1271 :               if (fstat(fd, &fs) == 0)
    2837                 :       1271 :                 http_size = fs.st_size;
    2838                 :            :               // libmicrohttpd will close (fd);
    2839                 :            :             }
    2840                 :       2519 :         }
    2841         [ +  + ]:        720 :       else if (url1 == "/metrics")
    2842                 :            :         {
    2843   [ +  -  +  -  :       1426 :           tmp_inc_metric m ("thread_busy", "role", "http-metrics");
          +  -  +  -  -  
          +  -  +  -  -  
                   -  - ]
    2844         [ +  - ]:        713 :           artifacttype = "metrics";
    2845   [ +  -  +  -  :       1426 :           inc_metric("http_requests_total", "type", artifacttype);
          +  -  -  +  -  
                      - ]
    2846         [ +  - ]:        713 :           r = handle_metrics(& http_size);
    2847                 :        713 :         }
    2848         [ -  + ]:          6 :       else if (url1 == "/")
    2849                 :            :         {
    2850         [ #  # ]:          0 :           artifacttype = "/";
    2851   [ -  -  -  -  :        632 :           inc_metric("http_requests_total", "type", artifacttype);
          -  -  -  -  -  
                -  -  + ]
    2852         [ #  # ]:          0 :           r = handle_root(& http_size);
    2853                 :            :         }
    2854                 :            :       else
    2855   [ +  -  +  -  :         18 :         throw reportable_exception("webapi error, unrecognized '" + url1 + "'");
             +  -  -  + ]
    2856                 :            : 
    2857         [ -  + ]:       1984 :       if (r == 0)
    2858   [ #  #  #  # ]:          0 :         throw reportable_exception("internal error, missing response");
    2859                 :            : 
    2860   [ +  +  +  - ]:       1984 :       if (maxsize > 0 && http_size > maxsize)
    2861                 :            :         {
    2862         [ +  - ]:          2 :           MHD_destroy_response(r);
    2863   [ +  -  +  -  :          6 :           throw reportable_exception(406, "File too large, max size=" + std::to_string(maxsize));
             +  -  -  + ]
    2864                 :            :         }
    2865                 :            : 
    2866         [ +  - ]:       1982 :       rc = MHD_queue_response (connection, MHD_HTTP_OK, r);
    2867                 :       1982 :       http_code = MHD_HTTP_OK;
    2868         [ +  - ]:       1982 :       MHD_destroy_response (r);
    2869                 :       2614 :     }
    2870         [ -  + ]:        632 :   catch (const reportable_exception& e)
    2871                 :            :     {
    2872   [ +  -  +  -  :       1264 :       inc_metric("http_responses_total","result","error");
          +  -  +  -  -  
          +  -  +  -  -  
                   -  - ]
    2873         [ +  - ]:        632 :       e.report(clog);
    2874                 :        632 :       http_code = e.code;
    2875         [ +  - ]:        632 :       http_size = e.message.size();
    2876         [ +  - ]:        632 :       rc = e.mhd_send_response (connection);
    2877                 :        632 :     }
    2878                 :            : 
    2879                 :       2614 :   clock_gettime (CLOCK_MONOTONIC, &ts_end);
    2880                 :       2614 :   double deltas = (ts_end.tv_sec - ts_start.tv_sec) + (ts_end.tv_nsec - ts_start.tv_nsec)/1.e9;
    2881                 :            :   // afteryou: delay waiting for other client's identical query to complete
    2882                 :            :   // deltas: total latency, including afteryou waiting
    2883   [ +  -  +  -  :       5228 :   obatched(clog) << conninfo(connection)
                   -  - ]
    2884                 :            :                  << ' ' << method << ' ' << url
    2885   [ +  -  +  -  :       2614 :                  << ' ' << http_code << ' ' << http_size
          +  -  +  -  +  
             -  +  -  +  
                      - ]
    2886   [ +  -  +  -  :       2614 :                  << ' ' << (int)(afteryou*1000) << '+' << (int)((deltas-afteryou)*1000) << "ms"
          +  -  +  -  +  
             -  +  -  +  
                      - ]
    2887         [ +  - ]:       2614 :                  << endl;
    2888                 :            : 
    2889                 :            :   // related prometheus metrics
    2890                 :       2614 :   string http_code_str = to_string(http_code);
    2891   [ +  -  +  -  :       5228 :   add_metric("http_responses_transfer_bytes_sum",
          +  -  +  -  -  
          +  -  +  -  -  
                   -  - ]
    2892                 :            :              "code", http_code_str, "type", artifacttype, http_size);
    2893   [ +  -  +  -  :       5228 :   inc_metric("http_responses_transfer_bytes_count",
          +  -  +  -  -  
          +  -  +  -  -  
                   -  - ]
    2894                 :            :              "code", http_code_str, "type", artifacttype);
    2895                 :            : 
    2896   [ +  -  +  -  :       5228 :   add_metric("http_responses_duration_milliseconds_sum",
          +  -  +  -  -  
          +  -  +  -  -  
                   -  - ]
    2897                 :            :              "code", http_code_str, "type", artifacttype, deltas*1000); // prometheus prefers _seconds and floating point
    2898   [ +  -  +  -  :       5228 :   inc_metric("http_responses_duration_milliseconds_count",
          +  -  +  -  -  
          +  -  +  -  -  
                   -  - ]
    2899                 :            :              "code", http_code_str, "type", artifacttype);
    2900                 :            : 
    2901   [ +  -  +  -  :       5228 :   add_metric("http_responses_after_you_milliseconds_sum",
          +  -  +  -  -  
          +  -  +  -  -  
                   -  - ]
    2902                 :            :              "code", http_code_str, "type", artifacttype, afteryou*1000);
    2903   [ +  -  +  -  :       5228 :   inc_metric("http_responses_after_you_milliseconds_count",
          +  -  +  -  -  
          +  -  +  -  -  
             -  -  -  - ]
    2904                 :            :              "code", http_code_str, "type", artifacttype);
    2905                 :            : 
    2906         [ -  + ]:       2614 :   return rc;
    2907   [ +  +  -  +  :      12197 : }
                   +  + ]
    2908                 :            : 
    2909                 :            : 
    2910                 :            : ////////////////////////////////////////////////////////////////////////
    2911                 :            : // borrowed originally from src/nm.c get_local_names()
    2912                 :            : 
    2913                 :            : static void
    2914                 :        244 : dwarf_extract_source_paths (Elf *elf, set<string>& debug_sourcefiles)
    2915                 :            :   noexcept // no exceptions - so we can simplify the altdbg resource release at end
    2916                 :            : {
    2917                 :        244 :   Dwarf* dbg = dwarf_begin_elf (elf, DWARF_C_READ, NULL);
    2918         [ -  + ]:        244 :   if (dbg == NULL)
    2919                 :          0 :     return;
    2920                 :            : 
    2921                 :        244 :   Dwarf* altdbg = NULL;
    2922                 :        244 :   int    altdbg_fd = -1;
    2923                 :            : 
    2924                 :            :   // DWZ handling: if we have an unsatisfied debug-alt-link, add an
    2925                 :            :   // empty string into the outgoing sourcefiles set, so the caller
    2926                 :            :   // should know that our data is incomplete.
    2927                 :        244 :   const char *alt_name_p;
    2928                 :        244 :   const void *alt_build_id; // elfutils-owned memory
    2929                 :        244 :   ssize_t sz = dwelf_dwarf_gnu_debugaltlink (dbg, &alt_name_p, &alt_build_id);
    2930         [ +  + ]:        244 :   if (sz > 0) // got one!
    2931                 :            :     {
    2932                 :         40 :       string buildid;
    2933                 :         40 :       unsigned char* build_id_bytes = (unsigned char*) alt_build_id;
    2934         [ +  + ]:        840 :       for (ssize_t idx=0; idx<sz; idx++)
    2935                 :            :         {
    2936                 :        800 :           buildid += "0123456789abcdef"[build_id_bytes[idx] >> 4];
    2937                 :        800 :           buildid += "0123456789abcdef"[build_id_bytes[idx] & 0xf];
    2938                 :            :         }
    2939                 :            : 
    2940         [ +  - ]:         40 :       if (verbose > 3)
    2941                 :         40 :         obatched(clog) << "Need altdebug buildid=" << buildid << endl;
    2942                 :            : 
    2943                 :            :       // but is it unsatisfied the normal elfutils ways?
    2944                 :         40 :       Dwarf* alt = dwarf_getalt (dbg);
    2945         [ +  - ]:         40 :       if (alt == NULL)
    2946                 :            :         {
    2947                 :            :           // Yup, unsatisfied the normal way.  Maybe we can satisfy it
    2948                 :            :           // from our own debuginfod database.
    2949                 :         40 :           int alt_fd;
    2950                 :         40 :           struct MHD_Response *r = 0;
    2951                 :         40 :           try
    2952                 :            :             {
    2953         [ +  - ]:         40 :               string artifacttype = "debuginfo";
    2954   [ +  -  +  -  :         40 :               r = handle_buildid (0, buildid, artifacttype, "", &alt_fd);
          -  +  -  +  -  
                      - ]
    2955                 :          0 :             }
    2956         [ -  - ]:          0 :           catch (const reportable_exception& e)
    2957                 :            :             {
    2958                 :            :               // swallow exceptions
    2959                 :          0 :             }
    2960                 :            : 
    2961                 :            :           // NB: this is not actually recursive!  This invokes the web-query
    2962                 :            :           // path, which cannot get back into the scan code paths.
    2963         [ +  - ]:         40 :           if (r)
    2964                 :            :             {
    2965                 :            :               // Found it!
    2966                 :         40 :               altdbg_fd = dup(alt_fd); // ok if this fails, downstream failures ok
    2967                 :         40 :               alt = altdbg = dwarf_begin (altdbg_fd, DWARF_C_READ);
    2968                 :            :               // NB: must close this dwarf and this fd at the bottom of the function!
    2969                 :         40 :               MHD_destroy_response (r); // will close alt_fd
    2970         [ +  - ]:         40 :               if (alt)
    2971                 :         40 :                 dwarf_setalt (dbg, alt);
    2972                 :            :             }
    2973                 :            :         }
    2974                 :            :       else
    2975                 :            :         {
    2976                 :            :           // NB: dwarf_setalt(alt) inappropriate - already done!
    2977                 :            :           // NB: altdbg will stay 0 so nothing tries to redundantly dealloc.
    2978                 :            :         }
    2979                 :            : 
    2980         [ +  - ]:         40 :       if (alt)
    2981                 :            :         {
    2982         [ +  - ]:         40 :           if (verbose > 3)
    2983                 :         40 :             obatched(clog) << "Resolved altdebug buildid=" << buildid << endl;
    2984                 :            :         }
    2985                 :            :       else // (alt == NULL) - signal possible presence of poor debuginfo
    2986                 :            :         {
    2987         [ #  # ]:          0 :           debug_sourcefiles.insert("");
    2988         [ #  # ]:          0 :           if (verbose > 3)
    2989                 :          0 :             obatched(clog) << "Unresolved altdebug buildid=" << buildid << endl;
    2990                 :            :         }
    2991                 :         40 :     }
    2992                 :            : 
    2993                 :        244 :   Dwarf_Off offset = 0;
    2994                 :        244 :   Dwarf_Off old_offset;
    2995                 :        244 :   size_t hsize;
    2996                 :            : 
    2997         [ +  + ]:       8618 :   while (dwarf_nextcu (dbg, old_offset = offset, &offset, &hsize, NULL, NULL, NULL) == 0)
    2998                 :            :     {
    2999                 :       8374 :       Dwarf_Die cudie_mem;
    3000                 :       8374 :       Dwarf_Die *cudie = dwarf_offdie (dbg, old_offset + hsize, &cudie_mem);
    3001                 :            : 
    3002         [ -  + ]:       8374 :       if (cudie == NULL)
    3003                 :         20 :         continue;
    3004         [ +  + ]:       8374 :       if (dwarf_tag (cudie) != DW_TAG_compile_unit)
    3005                 :         20 :         continue;
    3006                 :            : 
    3007         [ -  + ]:       8354 :       const char *cuname = dwarf_diename(cudie) ?: "unknown";
    3008                 :            : 
    3009                 :       8354 :       Dwarf_Files *files;
    3010                 :       8354 :       size_t nfiles;
    3011         [ -  + ]:       8354 :       if (dwarf_getsrcfiles (cudie, &files, &nfiles) != 0)
    3012                 :          0 :         continue;
    3013                 :            : 
    3014                 :            :       // extract DW_AT_comp_dir to resolve relative file names
    3015                 :       8354 :       const char *comp_dir = "";
    3016                 :       8354 :       const char *const *dirs;
    3017                 :       8354 :       size_t ndirs;
    3018         [ -  + ]:       8354 :       if (dwarf_getsrcdirs (files, &dirs, &ndirs) == 0 &&
    3019         [ -  + ]:       8354 :           dirs[0] != NULL)
    3020                 :            :         comp_dir = dirs[0];
    3021                 :            :       if (comp_dir == NULL)
    3022                 :            :         comp_dir = "";
    3023                 :            : 
    3024         [ +  + ]:       8354 :       if (verbose > 3)
    3025                 :      12864 :         obatched(clog) << "searching for sources for cu=" << cuname << " comp_dir=" << comp_dir
    3026                 :       6432 :                        << " #files=" << nfiles << " #dirs=" << ndirs << endl;
    3027                 :            : 
    3028   [ +  -  -  - ]:       8354 :       if (comp_dir[0] == '\0' && cuname[0] != '/')
    3029                 :            :         {
    3030         [ #  # ]:          0 :           if (verbose > 3)
    3031                 :          0 :             obatched(clog) << "skipping cu=" << cuname << " due to empty comp_dir" << endl;
    3032                 :          0 :           continue;
    3033                 :            :         }
    3034                 :            : 
    3035         [ +  + ]:     134855 :       for (size_t f = 1; f < nfiles; f++)
    3036                 :            :         {
    3037                 :     126501 :           const char *hat = dwarf_filesrc (files, f, NULL, NULL);
    3038         [ -  + ]:     126504 :           if (hat == NULL)
    3039                 :          0 :             continue;
    3040                 :            : 
    3041   [ +  +  -  + ]:     240257 :           if (string(hat) == "<built-in>") // gcc intrinsics, don't bother record
    3042                 :          0 :             continue;
    3043                 :            : 
    3044         [ +  + ]:     126500 :           string waldo;
    3045         [ +  + ]:     126500 :           if (hat[0] == '/') // absolute
    3046         [ -  + ]:      81145 :             waldo = (string (hat));
    3047         [ +  - ]:      45355 :           else if (comp_dir[0] != '\0') // comp_dir relative
    3048   [ -  +  -  +  :      77965 :             waldo = (string (comp_dir) + string("/") + string (hat));
          -  +  -  +  +  
                      + ]
    3049                 :            :           else
    3050                 :            :            {
    3051         [ #  # ]:          0 :              if (verbose > 3)
    3052                 :          0 :                obatched(clog) << "skipping hat=" << hat << " due to empty comp_dir" << endl;
    3053         [ #  # ]:          0 :              continue;
    3054                 :            :            }
    3055                 :            : 
    3056                 :            :           // NB: this is the 'waldo' that a dbginfo client will have
    3057                 :            :           // to supply for us to give them the file The comp_dir
    3058                 :            :           // prefixing is a definite complication.  Otherwise we'd
    3059                 :            :           // have to return a setof comp_dirs (one per CU!) with
    3060                 :            :           // corresponding filesrc[] names, instead of one absolute
    3061                 :            :           // resoved set.  Maybe we'll have to do that anyway.  XXX
    3062                 :            : 
    3063         [ +  + ]:     126503 :           if (verbose > 4)
    3064         [ -  + ]:        100 :             obatched(clog) << waldo
    3065         [ -  + ]:         50 :                            << (debug_sourcefiles.find(waldo)==debug_sourcefiles.end() ? " new" : " dup") <<  endl;
    3066                 :            : 
    3067         [ +  - ]:     126503 :           debug_sourcefiles.insert (waldo);
    3068                 :     126501 :         }
    3069                 :            :     }
    3070                 :            : 
    3071                 :        244 :   dwarf_end(dbg);
    3072         [ +  + ]:        244 :   if (altdbg)
    3073                 :         40 :     dwarf_end(altdbg);
    3074         [ +  + ]:        244 :   if (altdbg_fd >= 0)
    3075                 :         40 :     close(altdbg_fd);
    3076                 :            : }
    3077                 :            : 
    3078                 :            : 
    3079                 :            : 
    3080                 :            : static void
    3081                 :       1280 : elf_classify (int fd, bool &executable_p, bool &debuginfo_p, string &buildid, set<string>& debug_sourcefiles)
    3082                 :            : {
    3083                 :       1280 :   Elf *elf = elf_begin (fd, ELF_C_READ_MMAP_PRIVATE, NULL);
    3084         [ +  - ]:       1280 :   if (elf == NULL)
    3085                 :            :     return;
    3086                 :            : 
    3087                 :       1280 :   try // catch our types of errors and clean up the Elf* object
    3088                 :            :     {
    3089   [ +  -  +  + ]:       1280 :       if (elf_kind (elf) != ELF_K_ELF)
    3090                 :            :         {
    3091         [ +  - ]:        792 :           elf_end (elf);
    3092                 :        832 :           return;
    3093                 :            :         }
    3094                 :            : 
    3095                 :        488 :       GElf_Ehdr ehdr_storage;
    3096         [ +  - ]:        488 :       GElf_Ehdr *ehdr = gelf_getehdr (elf, &ehdr_storage);
    3097         [ -  + ]:        488 :       if (ehdr == NULL)
    3098                 :            :         {
    3099         [ #  # ]:          0 :           elf_end (elf);
    3100                 :            :           return;
    3101                 :            :         }
    3102                 :        488 :       auto elf_type = ehdr->e_type;
    3103                 :            : 
    3104                 :        488 :       const void *build_id; // elfutils-owned memory
    3105         [ +  - ]:        488 :       ssize_t sz = dwelf_elf_gnu_build_id (elf, & build_id);
    3106         [ +  + ]:        488 :       if (sz <= 0)
    3107                 :            :         {
    3108                 :            :           // It's not a diagnostic-worthy error for an elf file to lack build-id.
    3109                 :            :           // It might just be very old.
    3110         [ +  - ]:         40 :           elf_end (elf);
    3111                 :            :           return;
    3112                 :            :         }
    3113                 :            : 
    3114                 :            :       // build_id is a raw byte array; convert to hexadecimal *lowercase*
    3115                 :        448 :       unsigned char* build_id_bytes = (unsigned char*) build_id;
    3116         [ +  + ]:       9408 :       for (ssize_t idx=0; idx<sz; idx++)
    3117                 :            :         {
    3118         [ +  - ]:       8960 :           buildid += "0123456789abcdef"[build_id_bytes[idx] >> 4];
    3119         [ +  - ]:      17920 :           buildid += "0123456789abcdef"[build_id_bytes[idx] & 0xf];
    3120                 :            :         }
    3121                 :            : 
    3122                 :            :       // now decide whether it's an executable - namely, any allocatable section has
    3123                 :            :       // PROGBITS;
    3124         [ +  + ]:        448 :       if (elf_type == ET_EXEC || elf_type == ET_DYN)
    3125                 :            :         {
    3126                 :        398 :           size_t shnum;
    3127         [ +  - ]:        398 :           int rc = elf_getshdrnum (elf, &shnum);
    3128         [ -  + ]:        398 :           if (rc < 0)
    3129   [ #  #  #  # ]:          0 :             throw elfutils_exception(rc, "getshdrnum");
    3130                 :            : 
    3131                 :        398 :           executable_p = false;
    3132         [ +  + ]:       7054 :           for (size_t sc = 0; sc < shnum; sc++)
    3133                 :            :             {
    3134         [ +  - ]:       6878 :               Elf_Scn *scn = elf_getscn (elf, sc);
    3135         [ -  + ]:       6877 :               if (scn == NULL)
    3136                 :          0 :                 continue;
    3137                 :            : 
    3138                 :       6877 :               GElf_Shdr shdr_mem;
    3139         [ +  - ]:       6877 :               GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
    3140         [ -  + ]:       6878 :               if (shdr == NULL)
    3141                 :          0 :                 continue;
    3142                 :            : 
    3143                 :            :               // allocated (loadable / vm-addr-assigned) section with available content?
    3144   [ +  +  +  + ]:       6878 :               if ((shdr->sh_type == SHT_PROGBITS) && (shdr->sh_flags & SHF_ALLOC))
    3145                 :            :                 {
    3146         [ +  + ]:        222 :                   if (verbose > 4)
    3147   [ +  -  +  -  :         32 :                     obatched(clog) << "executable due to SHF_ALLOC SHT_PROGBITS sc=" << sc << endl;
                   +  - ]
    3148                 :        222 :                   executable_p = true;
    3149                 :        222 :                   break; // no need to keep looking for others
    3150                 :            :                 }
    3151                 :            :             } // iterate over sections
    3152                 :            :         } // executable_p classification
    3153                 :            : 
    3154                 :            :       // now decide whether it's a debuginfo - namely, if it has any .debug* or .zdebug* sections
    3155                 :            :       // logic mostly stolen from fweimer@redhat.com's elfclassify drafts
    3156                 :        448 :       size_t shstrndx;
    3157         [ +  - ]:        448 :       int rc = elf_getshdrstrndx (elf, &shstrndx);
    3158         [ -  + ]:        448 :       if (rc < 0)
    3159   [ #  #  #  # ]:          0 :         throw elfutils_exception(rc, "getshdrstrndx");
    3160                 :            : 
    3161                 :            :       Elf_Scn *scn = NULL;
    3162                 :            :       bool symtab_p = false;
    3163                 :            :       bool bits_alloc_p = false;
    3164                 :      23592 :       while (true)
    3165                 :            :         {
    3166         [ +  - ]:      12020 :           scn = elf_nextscn (elf, scn);
    3167         [ +  + ]:      12020 :           if (scn == NULL)
    3168                 :            :             break;
    3169                 :      11816 :           GElf_Shdr shdr_storage;
    3170         [ +  - ]:      11816 :           GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_storage);
    3171         [ -  + ]:      11816 :           if (shdr == NULL)
    3172                 :            :             break;
    3173         [ +  - ]:      11816 :           const char *section_name = elf_strptr (elf, shstrndx, shdr->sh_name);
    3174         [ -  + ]:      11816 :           if (section_name == NULL)
    3175                 :            :             break;
    3176         [ +  + ]:      11816 :           if (startswith (section_name, ".debug_line") ||
    3177         [ -  + ]:      11572 :               startswith (section_name, ".zdebug_line"))
    3178                 :            :             {
    3179                 :        244 :               debuginfo_p = true;
    3180         [ +  - ]:        244 :               if (scan_source_info)
    3181                 :        244 :                 dwarf_extract_source_paths (elf, debug_sourcefiles);
    3182                 :            :               break; // expecting only one .*debug_line, so no need to look for others
    3183                 :            :             }
    3184         [ +  + ]:      11572 :           else if (startswith (section_name, ".debug_") ||
    3185         [ +  - ]:      10802 :                    startswith (section_name, ".zdebug_"))
    3186                 :            :             {
    3187                 :        770 :               debuginfo_p = true;
    3188                 :            :               // NB: don't break; need to parse .debug_line for sources
    3189                 :            :             }
    3190         [ +  + ]:      10802 :           else if (shdr->sh_type == SHT_SYMTAB)
    3191                 :            :             {
    3192                 :            :               symtab_p = true;
    3193                 :            :             }
    3194                 :      10778 :           else if (shdr->sh_type != SHT_NOBITS
    3195         [ +  + ]:      10778 :                    && shdr->sh_type != SHT_NOTE
    3196         [ +  + ]:       5538 :                    && (shdr->sh_flags & SHF_ALLOC) != 0)
    3197                 :            :             {
    3198                 :      11572 :               bits_alloc_p = true;
    3199                 :            :             }
    3200                 :      11572 :         }
    3201                 :            : 
    3202                 :            :       // For more expansive elf/split-debuginfo classification, we
    3203                 :            :       // want to identify as debuginfo "strip -s"-produced files
    3204                 :            :       // without .debug_info* (like libicudata), but we don't want to
    3205                 :            :       // identify "strip -g" executables (with .symtab left there).
    3206         [ -  + ]:        448 :       if (symtab_p && !bits_alloc_p)
    3207                 :          0 :         debuginfo_p = true;
    3208                 :            :     }
    3209         [ #  # ]:          0 :   catch (const reportable_exception& e)
    3210                 :            :     {
    3211         [ #  # ]:          0 :       e.report(clog);
    3212                 :          0 :     }
    3213                 :        448 :   elf_end (elf);
    3214                 :            : }
    3215                 :            : 
    3216                 :            : 
    3217                 :            : // Intern the given file name in two parts (dirname & basename) and
    3218                 :            : // return the resulting file's id.
    3219                 :            : static int64_t
    3220                 :      30850 : register_file_name(sqlite_ps& ps_upsert_fileparts,
    3221                 :            :                    sqlite_ps& ps_upsert_file,
    3222                 :            :                    sqlite_ps& ps_lookup_file,
    3223                 :            :                    const string& name)
    3224                 :            : {
    3225                 :      30850 :   std::size_t slash = name.rfind('/');
    3226         [ +  + ]:      30849 :   string dirname, filename;
    3227         [ +  + ]:      30849 :   if (slash == std::string::npos)
    3228                 :            :     {
    3229         [ +  - ]:         90 :       dirname = "";
    3230         [ +  - ]:         90 :       filename = name;
    3231                 :            :     }
    3232                 :            :   else
    3233                 :            :     {
    3234   [ +  -  -  + ]:      30759 :       dirname = name.substr(0, slash);
    3235   [ +  -  -  +  :      30759 :       filename = name.substr(slash+1);
                   -  - ]
    3236                 :            :     }
    3237                 :            : 
    3238                 :            :   // intern the two substrings
    3239                 :      30849 :   ps_upsert_fileparts
    3240         [ +  - ]:      30849 :     .reset()
    3241         [ +  - ]:      30850 :     .bind(1, dirname)
    3242         [ +  - ]:      30850 :     .step_ok_done();
    3243                 :      30850 :   ps_upsert_fileparts
    3244         [ +  - ]:      30850 :     .reset()
    3245         [ +  - ]:      30850 :     .bind(1, filename)
    3246         [ +  - ]:      30850 :     .step_ok_done();
    3247                 :            : 
    3248                 :            :   // intern the tuple
    3249                 :      30850 :   ps_upsert_file
    3250         [ +  - ]:      30850 :     .reset()
    3251         [ +  - ]:      30850 :     .bind(1, dirname)
    3252         [ +  - ]:      30850 :     .bind(2, filename)
    3253         [ +  - ]:      30850 :     .step_ok_done();
    3254                 :            : 
    3255                 :            :   // look up the tuple's id
    3256                 :      30850 :   ps_lookup_file
    3257         [ +  - ]:      30850 :     .reset()
    3258         [ +  - ]:      30850 :     .bind(1, dirname)
    3259         [ +  - ]:      30850 :     .bind(2, filename);
    3260         [ +  - ]:      30849 :   int rc = ps_lookup_file.step();
    3261   [ -  +  -  -  :      30850 :   if (rc != SQLITE_ROW) throw sqlite_exception(rc, "step");
                   -  - ]
    3262                 :            :   
    3263         [ +  - ]:      30850 :   int64_t id = sqlite3_column_int64 (ps_lookup_file, 0);
    3264         [ +  - ]:      30850 :   ps_lookup_file.reset();
    3265         [ +  + ]:      30850 :   return id;
    3266         [ +  + ]:      59528 : }
    3267                 :            : 
    3268                 :            : 
    3269                 :            : 
    3270                 :            : static void
    3271                 :        974 : scan_source_file (const string& rps, const stat_t& st,
    3272                 :            :                   sqlite_ps& ps_upsert_buildids,
    3273                 :            :                   sqlite_ps& ps_upsert_fileparts,
    3274                 :            :                   sqlite_ps& ps_upsert_file,
    3275                 :            :                   sqlite_ps& ps_lookup_file,
    3276                 :            :                   sqlite_ps& ps_upsert_de,
    3277                 :            :                   sqlite_ps& ps_upsert_s,
    3278                 :            :                   sqlite_ps& ps_query,
    3279                 :            :                   sqlite_ps& ps_scan_done,
    3280                 :            :                   unsigned& fts_cached,
    3281                 :            :                   unsigned& fts_executable,
    3282                 :            :                   unsigned& fts_debuginfo,
    3283                 :            :                   unsigned& fts_sourcefiles)
    3284                 :            : {
    3285                 :        974 :   int64_t fileid = register_file_name(ps_upsert_fileparts, ps_upsert_file, ps_lookup_file, rps);
    3286                 :            : 
    3287                 :            :   /* See if we know of it already. */
    3288                 :        974 :   int rc = ps_query
    3289                 :        974 :     .reset()
    3290                 :        974 :     .bind(1, fileid)
    3291                 :        974 :     .bind(2, st.st_mtime)
    3292                 :        974 :     .step();
    3293                 :        974 :   ps_query.reset();
    3294         [ +  + ]:        974 :   if (rc == SQLITE_ROW) // i.e., a result, as opposed to DONE (no results)
    3295                 :            :     // no need to recheck a file/version we already know
    3296                 :            :     // specifically, no need to elf-begin a file we already determined is non-elf
    3297                 :            :     // (so is stored with buildid=NULL)
    3298                 :            :     {
    3299                 :        368 :       fts_cached++;
    3300                 :        368 :       return;
    3301                 :            :     }
    3302                 :            : 
    3303                 :        606 :   bool executable_p = false, debuginfo_p = false; // E and/or D
    3304         [ +  - ]:        606 :   string buildid;
    3305         [ +  - ]:        606 :   set<string> sourcefiles;
    3306                 :            : 
    3307         [ +  - ]:        606 :   int fd = open (rps.c_str(), O_RDONLY);
    3308                 :        606 :   try
    3309                 :            :     {
    3310         [ +  - ]:        606 :       if (fd >= 0)
    3311         [ +  - ]:        606 :         elf_classify (fd, executable_p, debuginfo_p, buildid, sourcefiles);
    3312                 :            :       else
    3313   [ #  #  #  #  :          0 :         throw libc_exception(errno, string("open ") + rps);
             #  #  #  # ]
    3314   [ +  -  +  -  :       1212 :       add_metric ("scanned_bytes_total","source","file",
          +  -  -  +  -  
             +  -  -  -  
                      - ]
    3315         [ +  - ]:        606 :                   st.st_size);
    3316   [ +  -  +  -  :       1212 :       inc_metric ("scanned_files_total","source","file");
          +  -  +  -  -  
          +  -  +  -  -  
                   -  - ]
    3317                 :            :     }
    3318                 :            :   // NB: we catch exceptions here too, so that we can
    3319                 :            :   // cache the corrupt-elf case (!executable_p &&
    3320                 :            :   // !debuginfo_p) just below, just as if we had an
    3321                 :            :   // EPERM error from open(2).
    3322         [ -  - ]:          0 :   catch (const reportable_exception& e)
    3323                 :            :     {
    3324         [ -  - ]:          0 :       e.report(clog);
    3325                 :          0 :     }
    3326                 :            : 
    3327         [ +  - ]:        606 :   if (fd >= 0)
    3328         [ +  - ]:        606 :     close (fd);
    3329                 :            : 
    3330         [ +  + ]:        606 :   if (buildid == "")
    3331                 :            :     {
    3332                 :            :       // no point storing an elf file without buildid
    3333                 :        510 :       executable_p = false;
    3334                 :        510 :       debuginfo_p = false;
    3335                 :            :     }
    3336                 :            :   else
    3337                 :            :     {
    3338                 :            :       // register this build-id in the interning table
    3339                 :         96 :       ps_upsert_buildids
    3340         [ +  - ]:         96 :         .reset()
    3341         [ +  - ]:         96 :         .bind(1, buildid)
    3342         [ +  - ]:         96 :         .step_ok_done();
    3343                 :            :     }
    3344                 :            : 
    3345         [ +  + ]:        606 :   if (executable_p)
    3346                 :         72 :     fts_executable ++;
    3347         [ +  + ]:        606 :   if (debuginfo_p)
    3348                 :         72 :     fts_debuginfo ++;
    3349   [ +  +  +  + ]:        606 :   if (executable_p || debuginfo_p)
    3350                 :            :     {
    3351                 :         96 :       ps_upsert_de
    3352         [ +  - ]:         96 :         .reset()
    3353         [ +  - ]:         96 :         .bind(1, buildid)
    3354   [ +  +  +  - ]:        120 :         .bind(2, debuginfo_p ? 1 : 0)
    3355   [ +  +  +  - ]:        120 :         .bind(3, executable_p ? 1 : 0)
    3356         [ +  - ]:         96 :         .bind(4, fileid)
    3357         [ +  - ]:         96 :         .bind(5, st.st_mtime)
    3358         [ +  - ]:         96 :         .step_ok_done();
    3359                 :            :     }
    3360         [ +  + ]:        606 :   if (executable_p)
    3361   [ +  -  +  -  :        144 :     inc_metric("found_executable_total","source","files");
          +  -  +  -  -  
          +  -  +  -  -  
                   -  - ]
    3362         [ +  + ]:        606 :   if (debuginfo_p)
    3363   [ +  -  +  -  :        144 :     inc_metric("found_debuginfo_total","source","files");
          +  -  +  -  -  
          +  -  +  -  -  
                   -  - ]
    3364                 :            : 
    3365   [ +  +  +  - ]:        678 :   if (sourcefiles.size() && buildid != "")
    3366                 :            :     {
    3367                 :         72 :       fts_sourcefiles += sourcefiles.size();
    3368                 :            : 
    3369         [ +  + ]:      14310 :       for (auto&& dwarfsrc : sourcefiles)
    3370                 :            :         {
    3371         [ +  - ]:      14238 :           char *srp = realpath(dwarfsrc.c_str(), NULL);
    3372         [ +  + ]:      14238 :           if (srp == NULL) // also if DWZ unresolved dwarfsrc=""
    3373                 :        220 :             continue; // unresolvable files are not a serious problem
    3374                 :            :           // throw libc_exception(errno, "fts/file realpath " + srcpath);
    3375         [ +  - ]:      14018 :           string srps = string(srp);
    3376                 :      14018 :           free (srp);
    3377                 :            : 
    3378                 :      14018 :           struct stat sfs;
    3379                 :      14018 :           rc = stat(srps.c_str(), &sfs);
    3380         [ -  + ]:      14018 :           if (rc != 0)
    3381         [ #  # ]:          0 :             continue;
    3382                 :            : 
    3383         [ +  - ]:      14018 :           if (verbose > 2)
    3384   [ +  -  +  -  :      42054 :             obatched(clog) << "recorded buildid=" << buildid << " file=" << srps
                   -  - ]
    3385   [ +  -  +  -  :      14018 :                            << " mtime=" << sfs.st_mtime
             +  -  +  - ]
    3386   [ +  -  +  -  :      14018 :                            << " as source " << dwarfsrc << endl;
                   +  - ]
    3387                 :            : 
    3388                 :            :           // PR25548: store canonicalized dwarfsrc path
    3389         [ +  - ]:      14018 :           string dwarfsrc_canon = canon_pathname (dwarfsrc);
    3390         [ +  + ]:      14018 :           if (dwarfsrc_canon != dwarfsrc)
    3391                 :            :             {
    3392         [ +  + ]:       2382 :               if (verbose > 3)
    3393   [ +  -  +  -  :       3768 :                 obatched(clog) << "canonicalized src=" << dwarfsrc << " alias=" << dwarfsrc_canon << endl;
          +  -  +  -  +  
                      - ]
    3394                 :            :             }
    3395                 :            : 
    3396         [ +  - ]:      14018 :           int64_t fileid1 = register_file_name (ps_upsert_fileparts, ps_upsert_file, ps_lookup_file, dwarfsrc_canon);
    3397         [ +  - ]:      14018 :           int64_t fileid2 = register_file_name (ps_upsert_fileparts, ps_upsert_file, ps_lookup_file, srps);
    3398                 :            : 
    3399                 :      14018 :           ps_upsert_s
    3400         [ +  - ]:      14018 :             .reset()
    3401         [ +  - ]:      14018 :             .bind(1, buildid)
    3402         [ +  - ]:      14018 :             .bind(2, fileid1)
    3403         [ +  - ]:      14018 :             .bind(3, fileid2)
    3404         [ +  - ]:      14018 :             .bind(4, sfs.st_mtime)
    3405         [ +  - ]:      14018 :             .step_ok_done();
    3406                 :            : 
    3407   [ +  -  +  -  :      28036 :           inc_metric("found_sourcerefs_total","source","files");
          +  -  +  -  -  
          +  -  +  +  -  
          -  -  -  -  -  
                      - ]
    3408         [ +  - ]:      28256 :         }
    3409                 :            :     }
    3410                 :            : 
    3411                 :        606 :   ps_scan_done
    3412         [ +  - ]:        606 :     .reset()
    3413         [ +  - ]:        606 :     .bind(1, fileid)
    3414         [ +  - ]:        606 :     .bind(2, st.st_mtime)
    3415         [ +  - ]:        606 :     .bind(3, st.st_size)
    3416         [ +  - ]:        606 :     .step_ok_done();
    3417                 :            : 
    3418         [ +  - ]:        606 :   if (verbose > 2)
    3419   [ +  -  +  - ]:       1818 :     obatched(clog) << "recorded buildid=" << buildid << " file=" << rps
    3420   [ +  -  +  -  :        606 :                    << " mtime=" << st.st_mtime << " atype="
             +  -  +  - ]
    3421                 :            :                    << (executable_p ? "E" : "")
    3422   [ +  -  +  +  :       1674 :                    << (debuginfo_p ? "D" : "") << endl;
          +  -  +  +  +  
                -  +  - ]
    3423         [ +  + ]:        702 : }
    3424                 :            : 
    3425                 :            : 
    3426                 :            : 
    3427                 :            : 
    3428                 :            : 
    3429                 :            : // Analyze given archive file of given age; record buildids / exec/debuginfo-ness of its
    3430                 :            : // constituent files with given upsert statements.
    3431                 :            : static void
    3432                 :        362 : archive_classify (const string& rps, string& archive_extension, int64_t archiveid,
    3433                 :            :                   sqlite_ps& ps_upsert_buildids, sqlite_ps& ps_upsert_fileparts, sqlite_ps& ps_upsert_file,
    3434                 :            :                   sqlite_ps& ps_lookup_file,
    3435                 :            :                   sqlite_ps& ps_upsert_de, sqlite_ps& ps_upsert_sref, sqlite_ps& ps_upsert_sdef,
    3436                 :            :                   time_t mtime,
    3437                 :            :                   unsigned& fts_executable, unsigned& fts_debuginfo, unsigned& fts_sref, unsigned& fts_sdef,
    3438                 :            :                   bool& fts_sref_complete_p)
    3439                 :            : {
    3440                 :        362 :   string archive_decoder = "/dev/null";
    3441         [ +  + ]:        896 :   for (auto&& arch : scan_archives)
    3442         [ +  + ]:        534 :     if (string_endswith(rps, arch.first))
    3443                 :            :       {
    3444         [ +  - ]:        362 :         archive_extension = arch.first;
    3445         [ +  - ]:        896 :         archive_decoder = arch.second;
    3446                 :            :       }
    3447                 :            : 
    3448                 :        362 :   FILE* fp;
    3449                 :        362 :   defer_dtor<FILE*,int>::dtor_fn dfn;
    3450         [ +  + ]:        362 :   if (archive_decoder != "cat")
    3451                 :            :     {
    3452   [ +  -  +  -  :         64 :       string popen_cmd = archive_decoder + " " + shell_escape(rps);
          +  -  -  +  -  
                -  -  - ]
    3453         [ +  - ]:         32 :       fp = popen (popen_cmd.c_str(), "r"); // "e" O_CLOEXEC?
    3454                 :         32 :       dfn = pclose;
    3455         [ -  + ]:         32 :       if (fp == NULL)
    3456   [ #  #  #  #  :          0 :         throw libc_exception (errno, string("popen ") + popen_cmd);
             #  #  #  # ]
    3457                 :         32 :     }
    3458                 :            :   else
    3459                 :            :     {
    3460         [ +  - ]:        330 :       fp = fopen (rps.c_str(), "r");
    3461                 :        329 :       dfn = fclose;
    3462         [ -  + ]:        329 :       if (fp == NULL)
    3463   [ #  #  #  #  :          0 :         throw libc_exception (errno, string("fopen ") + rps);
             #  #  #  # ]
    3464                 :            :     }
    3465                 :        361 :   defer_dtor<FILE*,int> fp_closer (fp, dfn);
    3466                 :            : 
    3467                 :        361 :   struct archive *a;
    3468         [ +  - ]:        361 :   a = archive_read_new();
    3469         [ -  + ]:        362 :   if (a == NULL)
    3470   [ #  #  #  # ]:          0 :     throw archive_exception("cannot create archive reader");
    3471                 :        362 :   defer_dtor<struct archive*,int> archive_closer (a, archive_read_free);
    3472                 :            : 
    3473         [ +  - ]:        362 :   int rc = archive_read_support_format_all(a);
    3474         [ -  + ]:        361 :   if (rc != ARCHIVE_OK)
    3475   [ #  #  #  # ]:          0 :     throw archive_exception(a, "cannot select all formats");
    3476         [ +  - ]:        361 :   rc = archive_read_support_filter_all(a);
    3477         [ -  + ]:        361 :   if (rc != ARCHIVE_OK)
    3478   [ #  #  #  # ]:          0 :     throw archive_exception(a, "cannot select all filters");
    3479                 :            : 
    3480         [ +  - ]:        361 :   rc = archive_read_open_FILE (a, fp);
    3481         [ -  + ]:        362 :   if (rc != ARCHIVE_OK)
    3482                 :            :     {
    3483   [ #  #  #  #  :          0 :       obatched(clog) << "cannot open archive from pipe " << rps << endl;
                   #  # ]
    3484   [ #  #  #  # ]:          0 :       throw archive_exception(a, "cannot open archive from pipe");
    3485                 :            :     }
    3486                 :            : 
    3487         [ +  + ]:        362 :   if (verbose > 3)
    3488   [ +  -  +  -  :        692 :     obatched(clog) << "libarchive scanning " << rps << " id " << archiveid << endl;
          +  -  +  -  +  
                      - ]
    3489                 :            : 
    3490                 :            :   bool any_exceptions = false;
    3491                 :       2564 :   while(1) // parse archive entries
    3492                 :            :     {
    3493         [ +  - ]:       2564 :     if (interrupted)
    3494                 :            :       break;
    3495                 :            : 
    3496                 :       2564 :     try
    3497                 :            :         {
    3498                 :       2564 :           struct archive_entry *e;
    3499         [ +  - ]:       2564 :           rc = archive_read_next_header (a, &e);
    3500         [ +  + ]:       2564 :           if (rc != ARCHIVE_OK)
    3501                 :            :             break;
    3502                 :            : 
    3503   [ +  -  +  + ]:       2202 :           if (! S_ISREG(archive_entry_mode (e))) // skip non-files completely
    3504                 :       1528 :             continue;
    3505                 :            : 
    3506         [ +  - ]:        674 :           string fn = canonicalized_archive_entry_pathname (e);
    3507                 :            : 
    3508         [ +  + ]:        674 :           if (verbose > 3)
    3509   [ +  -  +  -  :       1268 :             obatched(clog) << "libarchive checking " << fn << endl;
             +  -  -  - ]
    3510                 :            : 
    3511                 :            :           // extract this file to a temporary file
    3512                 :        674 :           char* tmppath = NULL;
    3513                 :        674 :           rc = asprintf (&tmppath, "%s/debuginfod-classify.XXXXXX", tmpdir.c_str());
    3514         [ -  + ]:        674 :           if (rc < 0)
    3515   [ #  #  #  # ]:          0 :             throw libc_exception (ENOMEM, "cannot allocate tmppath");
    3516                 :        674 :           defer_dtor<void*,void> tmmpath_freer (tmppath, free);
    3517         [ +  - ]:        674 :           int fd = mkstemp (tmppath);
    3518         [ -  + ]:        674 :           if (fd < 0)
    3519   [ #  #  #  # ]:          0 :             throw libc_exception (errno, "cannot create temporary file");
    3520                 :        674 :           unlink (tmppath); // unlink now so OS will release the file as soon as we close the fd
    3521                 :        674 :           defer_dtor<int,int> minifd_closer (fd, close);
    3522                 :            : 
    3523         [ +  - ]:        674 :           rc = archive_read_data_into_fd (a, fd);
    3524         [ -  + ]:        674 :           if (rc != ARCHIVE_OK) {
    3525         [ #  # ]:          0 :             close (fd);
    3526   [ #  #  #  # ]:          0 :             throw archive_exception(a, "cannot extract file");
    3527                 :            :           }
    3528                 :            : 
    3529                 :            :           // finally ... time to run elf_classify on this bad boy and update the database
    3530                 :        674 :           bool executable_p = false, debuginfo_p = false;
    3531         [ +  - ]:        674 :           string buildid;
    3532         [ +  - ]:        674 :           set<string> sourcefiles;
    3533         [ +  - ]:        674 :           elf_classify (fd, executable_p, debuginfo_p, buildid, sourcefiles);
    3534                 :            :           // NB: might throw
    3535                 :            : 
    3536         [ +  + ]:        674 :           if (buildid != "") // intern buildid
    3537                 :            :             {
    3538                 :        352 :               ps_upsert_buildids
    3539         [ +  - ]:        352 :                 .reset()
    3540         [ +  - ]:        352 :                 .bind(1, buildid)
    3541         [ +  - ]:        352 :                 .step_ok_done();
    3542                 :            :             }
    3543                 :            : 
    3544         [ +  - ]:        674 :           int64_t fileid = register_file_name (ps_upsert_fileparts, ps_upsert_file, ps_lookup_file, fn);
    3545                 :            : 
    3546         [ +  + ]:        674 :           if (sourcefiles.size() > 0) // sref records needed
    3547                 :            :             {
    3548                 :            :               // NB: we intern each source file once.  Once raw, as it
    3549                 :            :               // appears in the DWARF file list coming back from
    3550                 :            :               // elf_classify() - because it'll end up in the
    3551                 :            :               // _norm.artifactsrc column.  We don't also put another
    3552                 :            :               // version with a '.' at the front, even though that's
    3553                 :            :               // how rpm/cpio packs names, because we hide that from
    3554                 :            :               // the database for storage efficiency.
    3555                 :            : 
    3556         [ +  + ]:        616 :               for (auto&& s : sourcefiles)
    3557                 :            :                 {
    3558         [ -  + ]:        464 :                   if (s == "")
    3559                 :            :                     {
    3560                 :          0 :                       fts_sref_complete_p = false;
    3561                 :          0 :                       continue;
    3562                 :            :                     }
    3563                 :            : 
    3564                 :            :                   // PR25548: store canonicalized source path
    3565                 :        464 :                   const string& dwarfsrc = s;
    3566         [ +  - ]:        464 :                   string dwarfsrc_canon = canon_pathname (dwarfsrc);
    3567         [ +  + ]:        464 :                   if (dwarfsrc_canon != dwarfsrc)
    3568                 :            :                     {
    3569         [ +  - ]:         32 :                       if (verbose > 3)
    3570   [ +  -  +  -  :         64 :                         obatched(clog) << "canonicalized src=" << dwarfsrc << " alias=" << dwarfsrc_canon << endl;
          +  -  +  -  +  
                -  -  - ]
    3571                 :            :                     }
    3572                 :            : 
    3573         [ +  - ]:        464 :                   int64_t srcfileid = register_file_name(ps_upsert_fileparts, ps_upsert_file, ps_lookup_file,
    3574                 :            :                                                          dwarfsrc_canon);
    3575                 :            :                 
    3576                 :        464 :                   ps_upsert_sref
    3577         [ +  - ]:        464 :                     .reset()
    3578         [ +  - ]:        464 :                     .bind(1, buildid)
    3579         [ +  - ]:        464 :                     .bind(2, srcfileid)
    3580         [ +  - ]:        464 :                     .step_ok_done();
    3581                 :            : 
    3582         [ +  - ]:        464 :                   fts_sref ++;
    3583                 :        464 :                 }
    3584                 :            :             }
    3585                 :            : 
    3586         [ +  + ]:        674 :           if (executable_p)
    3587                 :        150 :             fts_executable ++;
    3588         [ +  + ]:        674 :           if (debuginfo_p)
    3589                 :        202 :             fts_debuginfo ++;
    3590                 :            : 
    3591   [ +  +  +  + ]:        674 :           if (executable_p || debuginfo_p)
    3592                 :            :             {
    3593                 :        352 :               ps_upsert_de
    3594         [ +  - ]:        352 :                 .reset()
    3595         [ +  - ]:        352 :                 .bind(1, buildid)
    3596   [ +  +  +  - ]:        502 :                 .bind(2, debuginfo_p ? 1 : 0)
    3597   [ +  +  +  - ]:        554 :                 .bind(3, executable_p ? 1 : 0)
    3598         [ +  - ]:        352 :                 .bind(4, archiveid)
    3599         [ +  - ]:        352 :                 .bind(5, mtime)
    3600         [ +  - ]:        352 :                 .bind(6, fileid)
    3601         [ +  - ]:        352 :                 .step_ok_done();
    3602                 :            :             }
    3603                 :            :           else // potential source - sdef record
    3604                 :            :             {
    3605                 :        322 :               fts_sdef ++;
    3606                 :        322 :               ps_upsert_sdef
    3607         [ +  - ]:        322 :                 .reset()
    3608         [ +  - ]:        322 :                 .bind(1, archiveid)
    3609         [ +  - ]:        322 :                 .bind(2, mtime)
    3610         [ +  - ]:        322 :                 .bind(3, fileid)
    3611         [ +  - ]:        322 :                 .step_ok_done();
    3612                 :            :             }
    3613                 :            : 
    3614   [ +  -  +  +  :        674 :           if ((verbose > 2) && (executable_p || debuginfo_p))
                   +  + ]
    3615   [ +  -  +  - ]:       1056 :             obatched(clog) << "recorded buildid=" << buildid << " rpm=" << rps << " file=" << fn
    3616   [ +  -  +  -  :        352 :                            << " mtime=" << mtime << " atype="
          +  -  +  -  +  
                -  +  - ]
    3617                 :            :                            << (executable_p ? "E" : "")
    3618                 :            :                            << (debuginfo_p ? "D" : "")
    3619   [ +  -  +  +  :        704 :                            << " sourcefiles=" << sourcefiles.size() << endl;
          +  -  +  +  +  
          -  +  -  +  -  
                   +  - ]
    3620                 :            : 
    3621   [ +  +  +  + ]:       1532 :         }
    3622         [ -  - ]:          0 :       catch (const reportable_exception& e)
    3623                 :            :         {
    3624         [ -  - ]:          0 :           e.report(clog);
    3625                 :          0 :           any_exceptions = true;
    3626                 :            :           // NB: but we allow the libarchive iteration to continue, in
    3627                 :            :           // case we can still gather some useful information.  That
    3628                 :            :           // would allow some webapi queries to work, until later when
    3629                 :            :           // this archive is rescanned.  (Its vitals won't go into the
    3630                 :            :           // _file_mtime_scanned table until after a successful scan.)
    3631                 :          0 :         }
    3632                 :            :     }
    3633                 :            : 
    3634         [ -  + ]:        362 :   if (any_exceptions)
    3635   [ #  #  #  # ]:          0 :     throw reportable_exception("exceptions encountered during archive scan");
    3636         [ +  + ]:        378 : }
    3637                 :            : 
    3638                 :            : 
    3639                 :            : 
    3640                 :            : // scan for archive files such as .rpm
    3641                 :            : static void
    3642                 :        702 : scan_archive_file (const string& rps, const stat_t& st,
    3643                 :            :                    sqlite_ps& ps_upsert_buildids,
    3644                 :            :                    sqlite_ps& ps_upsert_fileparts,
    3645                 :            :                    sqlite_ps& ps_upsert_file,
    3646                 :            :                    sqlite_ps& ps_lookup_file,
    3647                 :            :                    sqlite_ps& ps_upsert_de,
    3648                 :            :                    sqlite_ps& ps_upsert_sref,
    3649                 :            :                    sqlite_ps& ps_upsert_sdef,
    3650                 :            :                    sqlite_ps& ps_query,
    3651                 :            :                    sqlite_ps& ps_scan_done,
    3652                 :            :                    unsigned& fts_cached,
    3653                 :            :                    unsigned& fts_executable,
    3654                 :            :                    unsigned& fts_debuginfo,
    3655                 :            :                    unsigned& fts_sref,
    3656                 :            :                    unsigned& fts_sdef)
    3657                 :            : {
    3658                 :            :   // intern the archive file name
    3659                 :        702 :   int64_t archiveid = register_file_name (ps_upsert_fileparts, ps_upsert_file, ps_lookup_file, rps);
    3660                 :            : 
    3661                 :            :   /* See if we know of it already. */
    3662                 :        702 :   int rc = ps_query
    3663                 :        702 :     .reset()
    3664                 :        702 :     .bind(1, archiveid)
    3665                 :        702 :     .bind(2, st.st_mtime)
    3666                 :        702 :     .step();
    3667                 :        702 :   ps_query.reset();
    3668         [ +  + ]:        702 :   if (rc == SQLITE_ROW) // i.e., a result, as opposed to DONE (no results)
    3669                 :            :     // no need to recheck a file/version we already know
    3670                 :            :     // specifically, no need to parse this archive again, since we already have
    3671                 :            :     // it as a D or E or S record,
    3672                 :            :     // (so is stored with buildid=NULL)
    3673                 :            :     {
    3674                 :        340 :       fts_cached ++;
    3675                 :        340 :       return;
    3676                 :            :     }
    3677                 :            : 
    3678                 :            :   // extract the archive contents
    3679                 :        362 :   unsigned my_fts_executable = 0, my_fts_debuginfo = 0, my_fts_sref = 0, my_fts_sdef = 0;
    3680                 :        362 :   bool my_fts_sref_complete_p = true;
    3681                 :        362 :   bool any_exceptions = false;
    3682                 :        362 :   try
    3683                 :            :     {
    3684         [ +  - ]:        362 :       string archive_extension;
    3685                 :        362 :       archive_classify (rps, archive_extension, archiveid,
    3686                 :            :                         ps_upsert_buildids, ps_upsert_fileparts, ps_upsert_file, ps_lookup_file,
    3687                 :            :                         ps_upsert_de, ps_upsert_sref, ps_upsert_sdef, // dalt
    3688         [ +  - ]:        362 :                         st.st_mtime,
    3689                 :            :                         my_fts_executable, my_fts_debuginfo, my_fts_sref, my_fts_sdef,
    3690                 :            :                         my_fts_sref_complete_p);
    3691   [ +  -  +  -  :        724 :       add_metric ("scanned_bytes_total","source",archive_extension + " archive",
          +  -  -  +  +  
             +  -  -  -  
                      - ]
    3692         [ +  - ]:        362 :                   st.st_size);
    3693   [ +  -  +  -  :        724 :       inc_metric ("scanned_files_total","source",archive_extension + " archive");
          +  -  +  -  -  
          +  +  +  -  -  
                   -  - ]
    3694   [ +  -  +  -  :        724 :       add_metric("found_debuginfo_total","source",archive_extension + " archive",
          +  -  +  -  -  
          +  +  +  -  -  
                   -  - ]
    3695                 :            :                  my_fts_debuginfo);
    3696   [ +  -  +  -  :        724 :       add_metric("found_executable_total","source",archive_extension + " archive",
          +  -  +  -  -  
          +  +  +  -  -  
                   -  - ]
    3697                 :            :                  my_fts_executable);
    3698   [ +  -  +  -  :        740 :       add_metric("found_sourcerefs_total","source",archive_extension + " archive",
          +  -  +  -  -  
          +  +  +  -  +  
          -  -  -  -  -  
                      - ]
    3699                 :            :                  my_fts_sref);
    3700                 :        362 :     }
    3701         [ -  - ]:          0 :   catch (const reportable_exception& e)
    3702                 :            :     {
    3703         [ -  - ]:          0 :       e.report(clog);
    3704                 :          0 :       any_exceptions = true;
    3705                 :          0 :     }
    3706                 :            : 
    3707         [ +  - ]:        362 :   if (verbose > 2)
    3708         [ +  - ]:       1086 :     obatched(clog) << "scanned archive=" << rps
    3709   [ +  -  +  - ]:        362 :                    << " mtime=" << st.st_mtime
    3710         [ +  - ]:        362 :                    << " executables=" << my_fts_executable
    3711   [ +  -  +  - ]:        362 :                    << " debuginfos=" << my_fts_debuginfo
    3712   [ +  -  +  - ]:        362 :                    << " srefs=" << my_fts_sref
    3713   [ +  -  +  - ]:        362 :                    << " sdefs=" << my_fts_sdef
    3714   [ +  -  +  -  :        362 :                    << " exceptions=" << any_exceptions
             +  -  +  - ]
    3715                 :        362 :                    << endl;
    3716                 :            : 
    3717                 :        362 :   fts_executable += my_fts_executable;
    3718                 :        362 :   fts_debuginfo += my_fts_debuginfo;
    3719                 :        362 :   fts_sref += my_fts_sref;
    3720                 :        362 :   fts_sdef += my_fts_sdef;
    3721                 :            : 
    3722         [ -  + ]:        362 :   if (any_exceptions)
    3723   [ #  #  #  # ]:          0 :     throw reportable_exception("exceptions encountered during archive scan");
    3724                 :            : 
    3725         [ +  - ]:        362 :   if (my_fts_sref_complete_p) // leave incomplete?
    3726                 :        362 :     ps_scan_done
    3727                 :        362 :       .reset()
    3728                 :        362 :       .bind(1, archiveid)
    3729                 :        362 :       .bind(2, st.st_mtime)
    3730                 :        362 :       .bind(3, st.st_size)
    3731                 :        362 :       .step_ok_done();
    3732                 :            : }
    3733                 :            : 
    3734                 :            : 
    3735                 :            : 
    3736                 :            : ////////////////////////////////////////////////////////////////////////
    3737                 :            : 
    3738                 :            : 
    3739                 :            : 
    3740                 :            : // The thread that consumes file names off of the scanq.  We hold
    3741                 :            : // the persistent sqlite_ps's at this level and delegate file/archive
    3742                 :            : // scanning to other functions.
    3743                 :            : static void
    3744                 :        264 : scan ()
    3745                 :            : {
    3746                 :            :   // all the prepared statements fit to use, the _f_ set:
    3747   [ +  -  +  -  :        528 :   sqlite_ps ps_f_upsert_buildids (db, "file-buildids-intern", "insert or ignore into " BUILDIDS "_buildids VALUES (NULL, ?);");
             +  -  -  - ]
    3748   [ +  -  +  -  :        528 :   sqlite_ps ps_f_upsert_fileparts (db, "file-fileparts-intern", "insert or ignore into " BUILDIDS "_fileparts VALUES (NULL, ?);");
          +  -  +  -  -  
                      - ]
    3749         [ +  - ]:        264 :   sqlite_ps ps_f_upsert_file (db, "file-file-intern", "insert or ignore into " BUILDIDS "_files VALUES (NULL, \n"
    3750                 :            :                               "(select id from " BUILDIDS "_fileparts where name = ?),\n"
    3751   [ +  -  +  -  :        528 :                               "(select id from " BUILDIDS "_fileparts where name = ?));");
          +  -  +  -  -  
                      - ]
    3752         [ +  - ]:        264 :   sqlite_ps ps_f_lookup_file (db, "file-file-lookup",
    3753                 :            :                               "select f.id\n"
    3754                 :            :                               " from " BUILDIDS "_files f, " BUILDIDS "_fileparts p1, " BUILDIDS "_fileparts p2 \n"
    3755   [ +  -  +  -  :        528 :                               " where f.dirname = p1.id and f.basename = p2.id and p1.name = ? and p2.name = ?;\n");
          +  -  +  -  -  
                      - ]
    3756         [ +  - ]:        264 :   sqlite_ps ps_f_upsert_de (db, "file-de-upsert",
    3757                 :            :                           "insert or ignore into " BUILDIDS "_f_de "
    3758                 :            :                           "(buildid, debuginfo_p, executable_p, file, mtime) "
    3759                 :            :                           "values ((select id from " BUILDIDS "_buildids where hex = ?),"
    3760   [ +  -  +  -  :        528 :                             "        ?,?,?,?);");
          +  -  +  -  -  
                      - ]
    3761         [ +  - ]:        264 :   sqlite_ps ps_f_upsert_s (db, "file-s-upsert",
    3762                 :            :                          "insert or ignore into " BUILDIDS "_f_s "
    3763                 :            :                          "(buildid, artifactsrc, file, mtime) "
    3764                 :            :                          "values ((select id from " BUILDIDS "_buildids where hex = ?),"
    3765   [ +  -  +  -  :        528 :                          "      ?,?,?);");
          +  -  +  -  -  
                      - ]
    3766         [ +  - ]:        264 :   sqlite_ps ps_f_query (db, "file-negativehit-find",
    3767                 :            :                         "select 1 from " BUILDIDS "_file_mtime_scanned where sourcetype = 'F' "
    3768   [ +  -  +  -  :        528 :                         "and file = ? and mtime = ?;");
          +  -  +  -  -  
                      - ]
    3769         [ +  - ]:        264 :   sqlite_ps ps_f_scan_done (db, "file-scanned",
    3770                 :            :                           "insert or ignore into " BUILDIDS "_file_mtime_scanned (sourcetype, file, mtime, size)"
    3771   [ +  -  +  -  :        528 :                           "values ('F', ?,?,?);");
          +  -  +  -  -  
                      - ]
    3772                 :            : 
    3773                 :            :   // and now for the _r_ set
    3774   [ +  -  +  -  :        528 :   sqlite_ps ps_r_upsert_buildids (db, "rpm-buildid-intern", "insert or ignore into " BUILDIDS "_buildids VALUES (NULL, ?);");
          +  -  +  -  -  
                      - ]
    3775   [ +  -  +  -  :        528 :   sqlite_ps ps_r_upsert_fileparts (db, "rpm-fileparts-intern", "insert or ignore into " BUILDIDS "_fileparts VALUES (NULL, ?);");
          +  -  +  -  -  
                      - ]
    3776         [ +  - ]:        264 :   sqlite_ps ps_r_upsert_file (db, "rpm-file-intern", "insert or ignore into " BUILDIDS "_files VALUES (NULL, \n"
    3777                 :            :                               "(select id from " BUILDIDS "_fileparts where name = ?),\n"
    3778   [ +  -  +  -  :        528 :                               "(select id from " BUILDIDS "_fileparts where name = ?));");
          +  -  +  -  -  
                      - ]
    3779         [ +  - ]:        264 :   sqlite_ps ps_r_lookup_file (db, "rpm-file-lookup",
    3780                 :            :                               "select f.id\n"
    3781                 :            :                               " from " BUILDIDS "_files f, " BUILDIDS "_fileparts p1, " BUILDIDS "_fileparts p2 \n"
    3782   [ +  -  +  -  :        528 :                               " where f.dirname = p1.id and f.basename = p2.id and p1.name = ? and p2.name = ?;\n");
          +  -  +  -  -  
                      - ]
    3783         [ +  - ]:        264 :   sqlite_ps ps_r_upsert_de (db, "rpm-de-insert",
    3784                 :            :                           "insert or ignore into " BUILDIDS "_r_de (buildid, debuginfo_p, executable_p, file, mtime, content) values ("
    3785   [ +  -  +  -  :        528 :                           "(select id from " BUILDIDS "_buildids where hex = ?), ?, ?, ?, ?, ?);");
          +  -  +  -  -  
                      - ]
    3786         [ +  - ]:        264 :   sqlite_ps ps_r_upsert_sref (db, "rpm-sref-insert",
    3787                 :            :                             "insert or ignore into " BUILDIDS "_r_sref (buildid, artifactsrc) values ("
    3788                 :            :                             "(select id from " BUILDIDS "_buildids where hex = ?), "
    3789   [ +  -  +  -  :        528 :                             "?);");
          +  -  +  -  -  
                      - ]
    3790         [ +  - ]:        264 :   sqlite_ps ps_r_upsert_sdef (db, "rpm-sdef-insert",
    3791                 :            :                             "insert or ignore into " BUILDIDS "_r_sdef (file, mtime, content) values ("
    3792   [ +  -  +  -  :        528 :                             "?, ?, ?);");
          +  -  +  -  -  
                      - ]
    3793         [ +  - ]:        264 :   sqlite_ps ps_r_query (db, "rpm-negativehit-query",
    3794                 :            :                       "select 1 from " BUILDIDS "_file_mtime_scanned where "
    3795   [ +  -  +  -  :        528 :                       "sourcetype = 'R' and file = ? and mtime = ?;");
          +  -  +  -  -  
                      - ]
    3796         [ +  - ]:        264 :   sqlite_ps ps_r_scan_done (db, "rpm-scanned",
    3797                 :            :                           "insert or ignore into " BUILDIDS "_file_mtime_scanned (sourcetype, file, mtime, size)"
    3798   [ +  -  +  -  :        528 :                           "values ('R', ?, ?, ?);");
          +  -  +  -  -  
                      - ]
    3799                 :            :   
    3800                 :            : 
    3801                 :        264 :   unsigned fts_cached = 0, fts_executable = 0, fts_debuginfo = 0, fts_sourcefiles = 0;
    3802                 :        264 :   unsigned fts_sref = 0, fts_sdef = 0;
    3803                 :            : 
    3804   [ +  -  +  -  :        528 :   add_metric("thread_count", "role", "scan", 1);
          +  -  +  -  -  
          +  -  +  -  -  
                   -  - ]
    3805   [ +  -  +  -  :        528 :   add_metric("thread_busy", "role", "scan", 1);
          +  -  +  -  -  
          +  -  +  -  -  
                   -  - ]
    3806         [ +  + ]:       1714 :   while (! interrupted)
    3807                 :            :     {
    3808         [ +  - ]:       1450 :       scan_payload p;
    3809                 :            : 
    3810   [ +  -  +  -  :       2900 :       add_metric("thread_busy", "role", "scan", -1);
          +  -  +  -  -  
          +  -  +  -  -  
                   -  - ]
    3811                 :            :       // NB: threads may be blocked within either of these two waiting
    3812                 :            :       // states, if the work queue happens to run dry.  That's OK.
    3813   [ +  -  +  - ]:       1450 :       if (scan_barrier) scan_barrier->count();
    3814         [ +  - ]:       1449 :       bool gotone = scanq.wait_front(p);
    3815   [ +  -  +  -  :       2900 :       add_metric("thread_busy", "role", "scan", 1);
          +  -  +  -  -  
          +  -  +  -  -  
                   -  - ]
    3816                 :            : 
    3817   [ +  +  -  + ]:       1450 :       if (! gotone) continue; // go back to waiting
    3818                 :            : 
    3819                 :       1186 :       try
    3820                 :            :         {
    3821                 :       1186 :           bool scan_archive = false;
    3822         [ +  + ]:       2485 :           for (auto&& arch : scan_archives)
    3823         [ +  + ]:       1299 :             if (string_endswith(p.first, arch.first))
    3824                 :        702 :               scan_archive = true;
    3825                 :            : 
    3826         [ +  + ]:       1186 :           if (scan_archive)
    3827         [ +  - ]:        702 :             scan_archive_file (p.first, p.second,
    3828                 :            :                                ps_r_upsert_buildids,
    3829                 :            :                                ps_r_upsert_fileparts,
    3830                 :            :                                ps_r_upsert_file,
    3831                 :            :                                ps_r_lookup_file,
    3832                 :            :                                ps_r_upsert_de,
    3833                 :            :                                ps_r_upsert_sref,
    3834                 :            :                                ps_r_upsert_sdef,
    3835                 :            :                                ps_r_query,
    3836                 :            :                                ps_r_scan_done,
    3837                 :            :                                fts_cached,
    3838                 :            :                                fts_executable,
    3839                 :            :                                fts_debuginfo,
    3840                 :            :                                fts_sref,
    3841                 :            :                                fts_sdef);
    3842                 :            : 
    3843         [ +  + ]:       1186 :           if (scan_files) // NB: maybe "else if" ?
    3844         [ +  - ]:        974 :             scan_source_file (p.first, p.second,
    3845                 :            :                               ps_f_upsert_buildids,
    3846                 :            :                               ps_f_upsert_fileparts,
    3847                 :            :                               ps_f_upsert_file,
    3848                 :            :                               ps_f_lookup_file,
    3849                 :            :                               ps_f_upsert_de,
    3850                 :            :                               ps_f_upsert_s,
    3851                 :            :                               ps_f_query,
    3852                 :            :                               ps_f_scan_done,
    3853                 :            :                               fts_cached, fts_executable, fts_debuginfo, fts_sourcefiles);
    3854                 :            :         }
    3855         [ -  - ]:          0 :       catch (const reportable_exception& e)
    3856                 :            :         {
    3857         [ -  - ]:          0 :           e.report(cerr);
    3858                 :          0 :         }
    3859                 :            : 
    3860         [ +  - ]:       1186 :       scanq.done_front(); // let idlers run
    3861                 :            :       
    3862                 :       1186 :       if (fts_cached || fts_executable || fts_debuginfo || fts_sourcefiles || fts_sref || fts_sdef)
    3863                 :            :         {} // NB: not just if a successful scan - we might have encountered -ENOSPC & failed
    3864   [ +  -  +  - ]:       1186 :       (void) statfs_free_enough_p(db_path, "database"); // report sqlite filesystem size
    3865   [ +  -  +  - ]:       1186 :       (void) statfs_free_enough_p(tmpdir, "tmpdir"); // this too, in case of fdcache/tmpfile usage
    3866                 :            : 
    3867                 :            :       // finished a scanning step -- not a "loop", because we just
    3868                 :            :       // consume the traversal loop's work, whenever
    3869   [ +  -  +  -  :       2372 :       inc_metric("thread_work_total","role","scan");
          +  -  +  -  -  
          +  -  +  +  -  
          -  -  -  -  -  
                      - ]
    3870                 :       1450 :     }
    3871                 :            : 
    3872   [ +  -  +  -  :        528 :   add_metric("thread_busy", "role", "scan", -1);
          +  -  +  -  -  
          +  -  +  -  -  
                   -  - ]
    3873                 :        264 : }
    3874                 :            : 
    3875                 :            : 
    3876                 :            : // Use this function as the thread entry point, so it can catch our
    3877                 :            : // fleet of exceptions (incl. the sqlite_ps ctors) and report.
    3878                 :            : static void*
    3879                 :        264 : thread_main_scanner (void* arg)
    3880                 :            : {
    3881                 :        264 :   (void) arg;
    3882         [ +  + ]:        792 :   while (! interrupted)
    3883                 :        264 :     try
    3884                 :            :       {
    3885         [ +  - ]:        264 :         scan();
    3886                 :            :       }
    3887         [ -  - ]:          0 :     catch (const reportable_exception& e)
    3888                 :            :       {
    3889         [ -  - ]:          0 :         e.report(cerr);
    3890                 :          0 :       }
    3891                 :        264 :   return 0;
    3892                 :            : }
    3893                 :            : 
    3894                 :            : 
    3895                 :            : 
    3896                 :            : // The thread that traverses all the source_paths and enqueues all the
    3897                 :            : // matching files into the file/archive scan queue.
    3898                 :            : static void
    3899                 :        120 : scan_source_paths()
    3900                 :            : {
    3901                 :            :   // NB: fedora 31 glibc/fts(3) crashes inside fts_read() on empty
    3902                 :            :   // path list.
    3903         [ +  + ]:        120 :   if (source_paths.empty())
    3904                 :          2 :     return;
    3905                 :            : 
    3906                 :            :   // Turn the source_paths into an fts(3)-compatible char**.  Since
    3907                 :            :   // source_paths[] does not change after argv processing, the
    3908                 :            :   // c_str()'s are safe to keep around awile.
    3909                 :        118 :   vector<const char *> sps;
    3910         [ +  + ]:        318 :   for (auto&& sp: source_paths)
    3911         [ +  - ]:        200 :     sps.push_back(sp.c_str());
    3912   [ +  -  -  - ]:        118 :   sps.push_back(NULL);
    3913                 :            : 
    3914   [ +  +  +  - ]:        222 :   FTS *fts = fts_open ((char * const *)sps.data(),
    3915                 :            :                       (traverse_logical ? FTS_LOGICAL : FTS_PHYSICAL|FTS_XDEV)
    3916                 :            :                       | FTS_NOCHDIR /* multithreaded */,
    3917                 :            :                       NULL);
    3918         [ -  + ]:        118 :   if (fts == NULL)
    3919   [ #  #  #  # ]:          0 :     throw libc_exception(errno, "cannot fts_open");
    3920                 :        118 :   defer_dtor<FTS*,int> fts_cleanup (fts, fts_close);
    3921                 :            : 
    3922                 :        118 :   struct timespec ts_start, ts_end;
    3923                 :        118 :   clock_gettime (CLOCK_MONOTONIC, &ts_start);
    3924                 :        118 :   unsigned fts_scanned = 0, fts_regex = 0;
    3925                 :            : 
    3926                 :        118 :   FTSENT *f;
    3927   [ +  -  +  + ]:       2374 :   while ((f = fts_read (fts)) != NULL)
    3928                 :            :   {
    3929         [ +  - ]:       2138 :     if (interrupted) break;
    3930                 :            : 
    3931         [ -  + ]:       2138 :     if (sigusr2 != forced_groom_count) // stop early if groom triggered
    3932                 :            :       {
    3933         [ #  # ]:          0 :         scanq.clear(); // clear previously issued work for scanner threads
    3934                 :            :         break;
    3935                 :            :       }
    3936                 :            : 
    3937                 :       2138 :     fts_scanned ++;
    3938                 :            : 
    3939         [ +  - ]:       2138 :     if (verbose > 2)
    3940   [ +  -  +  -  :       4276 :       obatched(clog) << "fts traversing " << f->fts_path << endl;
                   +  - ]
    3941                 :            : 
    3942   [ +  +  +  +  :       2138 :     switch (f->fts_info)
                      + ]
    3943                 :            :       {
    3944                 :       1274 :       case FTS_F:
    3945                 :       1274 :         {
    3946                 :            :           /* Found a file.  Convert it to an absolute path, so
    3947                 :            :              the buildid database does not have relative path
    3948                 :            :              names that are unresolvable from a subsequent run
    3949                 :            :              in a different cwd. */
    3950         [ +  - ]:       1274 :           char *rp = realpath(f->fts_path, NULL);
    3951         [ -  + ]:       1274 :           if (rp == NULL)
    3952                 :          0 :             continue; // ignore dangling symlink or such
    3953         [ +  - ]:       1274 :           string rps = string(rp);
    3954                 :       1274 :           free (rp);
    3955                 :            : 
    3956         [ +  - ]:       1274 :           bool ri = !regexec (&file_include_regex, rps.c_str(), 0, 0, 0);
    3957         [ +  - ]:       1274 :           bool rx = !regexec (&file_exclude_regex, rps.c_str(), 0, 0, 0);
    3958         [ +  + ]:       1274 :           if (!ri || rx)
    3959                 :            :             {
    3960         [ +  - ]:         88 :               if (verbose > 3)
    3961         [ +  - ]:        176 :                 obatched(clog) << "fts skipped by regex "
    3962   [ +  +  +  -  :         96 :                                << (!ri ? "I" : "") << (rx ? "X" : "") << endl;
          +  +  +  -  +  
                      - ]
    3963                 :         88 :               fts_regex ++;
    3964         [ +  + ]:         88 :               if (!ri)
    3965   [ +  -  +  -  :          8 :                 inc_metric("traversed_total","type","file-skipped-I");
          +  -  +  -  -  
          +  -  +  -  -  
                   -  - ]
    3966         [ +  + ]:         88 :               if (rx)
    3967   [ +  -  +  -  :        168 :                 inc_metric("traversed_total","type","file-skipped-X");
          +  -  +  -  -  
          +  -  +  -  -  
                   -  - ]
    3968                 :            :             }
    3969                 :            :           else
    3970                 :            :             {
    3971   [ +  -  +  - ]:       1186 :               scanq.push_back (make_pair(rps, *f->fts_statp));
    3972   [ +  -  +  -  :       2372 :               inc_metric("traversed_total","type","file");
          +  -  +  -  -  
          +  -  +  -  -  
             -  -  -  - ]
    3973                 :            :             }
    3974                 :          0 :         }
    3975                 :       1274 :         break;
    3976                 :            : 
    3977                 :          4 :       case FTS_ERR:
    3978                 :          4 :       case FTS_NS:
    3979                 :            :         // report on some types of errors because they may reflect fixable misconfiguration
    3980                 :          4 :         {
    3981   [ +  -  +  -  :          8 :           auto x = libc_exception(f->fts_errno, string("fts traversal ") + string(f->fts_path));
          +  -  +  -  -  
             +  -  +  -  
                      - ]
    3982         [ +  - ]:          4 :           x.report(cerr);
    3983                 :          0 :         }
    3984   [ +  -  +  -  :          8 :         inc_metric("traversed_total","type","error");
          +  -  +  -  -  
          +  -  +  -  -  
                   -  - ]
    3985                 :          4 :         break;
    3986                 :            : 
    3987                 :         12 :       case FTS_SL: // ignore, but count because debuginfod -L would traverse these
    3988   [ +  -  +  -  :         24 :         inc_metric("traversed_total","type","symlink");
          +  -  +  -  -  
          +  -  +  -  -  
                   -  - ]
    3989                 :         12 :         break;
    3990                 :            : 
    3991                 :        424 :       case FTS_D: // ignore
    3992   [ +  -  +  -  :        848 :         inc_metric("traversed_total","type","directory");
          +  -  +  -  -  
          +  -  +  -  -  
                   -  - ]
    3993                 :        424 :         break;
    3994                 :            : 
    3995                 :        424 :       default: // ignore
    3996   [ +  -  +  -  :        848 :         inc_metric("traversed_total","type","other");
          +  -  +  -  -  
          +  -  +  -  -  
                   -  - ]
    3997                 :        424 :         break;
    3998                 :            :       }
    3999                 :            :   }
    4000                 :        118 :   clock_gettime (CLOCK_MONOTONIC, &ts_end);
    4001                 :        118 :   double deltas = (ts_end.tv_sec - ts_start.tv_sec) + (ts_end.tv_nsec - ts_start.tv_nsec)/1.e9;
    4002                 :            : 
    4003   [ +  -  +  -  :        354 :   obatched(clog) << "fts traversed source paths in " << deltas << "s, scanned=" << fts_scanned
             +  -  +  - ]
    4004   [ +  -  +  -  :        118 :                  << ", regex-skipped=" << fts_regex << endl;
                   +  - ]
    4005         [ +  - ]:        236 : }
    4006                 :            : 
    4007                 :            : 
    4008                 :            : static void*
    4009                 :         66 : thread_main_fts_source_paths (void* arg)
    4010                 :            : {
    4011                 :         66 :   (void) arg; // ignore; we operate on global data
    4012                 :            : 
    4013   [ +  -  +  -  :        132 :   set_metric("thread_tid", "role","traverse", tid());
          +  -  -  +  -  
             +  -  -  -  
                      - ]
    4014   [ +  -  +  -  :        132 :   add_metric("thread_count", "role", "traverse", 1);
          +  -  -  +  -  
             +  -  -  -  
                      - ]
    4015                 :            : 
    4016                 :         66 :   time_t last_rescan = 0;
    4017                 :            : 
    4018         [ +  - ]:        314 :   while (! interrupted)
    4019                 :            :     {
    4020                 :        314 :       sleep (1);
    4021                 :        314 :       scanq.wait_idle(); // don't start a new traversal while scanners haven't finished the job
    4022                 :        314 :       scanq.done_idle(); // release the hounds
    4023         [ +  + ]:        314 :       if (interrupted) break;
    4024                 :            : 
    4025                 :        248 :       time_t now = time(NULL);
    4026                 :        248 :       bool rescan_now = false;
    4027         [ +  + ]:        248 :       if (last_rescan == 0) // at least one initial rescan is documented even for -t0
    4028                 :         64 :         rescan_now = true;
    4029   [ +  +  +  + ]:        248 :       if (rescan_s > 0 && (long)now > (long)(last_rescan + rescan_s))
    4030                 :        248 :         rescan_now = true;
    4031         [ +  + ]:        248 :       if (sigusr1 != forced_rescan_count)
    4032                 :            :         {
    4033                 :         60 :           forced_rescan_count = sigusr1;
    4034                 :         60 :           rescan_now = true;
    4035                 :            :         }
    4036         [ +  + ]:        248 :       if (rescan_now)
    4037                 :            :         {
    4038   [ +  -  +  -  :        240 :           set_metric("thread_busy", "role","traverse", 1);
          +  -  -  +  -  
             +  -  -  -  
                      - ]
    4039                 :        120 :           try
    4040                 :            :             {
    4041         [ +  - ]:        120 :               scan_source_paths();
    4042                 :            :             }
    4043         [ -  - ]:          0 :           catch (const reportable_exception& e)
    4044                 :            :             {
    4045         [ -  - ]:          0 :               e.report(cerr);
    4046                 :          0 :             }
    4047                 :        120 :           last_rescan = time(NULL); // NB: now was before scanning
    4048                 :            :           // finished a traversal loop
    4049   [ +  -  +  -  :        240 :           inc_metric("thread_work_total", "role","traverse");
          +  -  -  +  -  
             +  -  -  -  
                      - ]
    4050   [ +  -  +  -  :        240 :           set_metric("thread_busy", "role","traverse", 0);
          +  -  -  +  -  
             +  -  -  -  
                      - ]
    4051                 :            :         }
    4052                 :            :     }
    4053                 :            : 
    4054                 :         66 :   return 0;
    4055                 :            : }
    4056                 :            : 
    4057                 :            : 
    4058                 :            : 
    4059                 :            : ////////////////////////////////////////////////////////////////////////
    4060                 :            : 
    4061                 :            : static void
    4062                 :         72 : database_stats_report()
    4063                 :            : {
    4064                 :         72 :   sqlite_ps ps_query (db, "database-overview",
    4065   [ +  -  +  -  :        144 :                       "select label,quantity from " BUILDIDS "_stats");
             +  -  -  - ]
    4066                 :            : 
    4067   [ +  -  +  - ]:        144 :   obatched(clog) << "database record counts:" << endl;
    4068                 :       1656 :   while (1)
    4069                 :            :     {
    4070         [ +  - ]:        864 :       if (interrupted) break;
    4071         [ +  - ]:        864 :       if (sigusr1 != forced_rescan_count) // stop early if scan triggered
    4072                 :            :         break;
    4073                 :            : 
    4074         [ +  - ]:        864 :       int rc = ps_query.step();
    4075         [ +  + ]:        864 :       if (rc == SQLITE_DONE) break;
    4076         [ -  + ]:        792 :       if (rc != SQLITE_ROW)
    4077   [ #  #  #  # ]:          0 :         throw sqlite_exception(rc, "step");
    4078                 :            : 
    4079         [ +  - ]:        792 :       obatched(clog)
    4080   [ +  -  -  +  :        792 :         << ((const char*) sqlite3_column_text(ps_query, 0) ?: (const char*) "NULL")
                   +  - ]
    4081                 :            :         << " "
    4082   [ +  -  +  -  :       1584 :         << (sqlite3_column_text(ps_query, 1) ?: (const unsigned char*) "NULL")
             -  +  +  - ]
    4083                 :        792 :         << endl;
    4084                 :            : 
    4085   [ +  -  +  -  :       1584 :       set_metric("groom", "statistic",
          -  +  +  -  +  
          -  +  -  +  -  
          -  +  +  +  -  
                -  -  - ]
    4086         [ +  - ]:        792 :                  ((const char*) sqlite3_column_text(ps_query, 0) ?: (const char*) "NULL"),
    4087                 :            :                  (sqlite3_column_double(ps_query, 1)));
    4088                 :        792 :     }
    4089                 :         72 : }
    4090                 :            : 
    4091                 :            : 
    4092                 :            : // Do a round of database grooming that might take many minutes to run.
    4093                 :         72 : void groom()
    4094                 :            : {
    4095         [ +  - ]:        144 :   obatched(clog) << "grooming database" << endl;
    4096                 :            : 
    4097                 :         72 :   struct timespec ts_start, ts_end;
    4098                 :         72 :   clock_gettime (CLOCK_MONOTONIC, &ts_start);
    4099                 :            : 
    4100                 :            :   // scan for files that have disappeared
    4101                 :         72 :   sqlite_ps files (db, "check old files",
    4102                 :            :                    "select distinct s.mtime, s.file, f.name from "
    4103                 :            :                    BUILDIDS "_file_mtime_scanned s, " BUILDIDS "_files_v f "
    4104   [ +  -  +  -  :        144 :                    "where f.id = s.file");
             +  -  -  - ]
    4105                 :            :   // NB: Because _ftime_mtime_scanned can contain both F and
    4106                 :            :   // R records for the same file, this query would return duplicates if the
    4107                 :            :   // DISTINCT qualifier were not there.
    4108         [ +  - ]:         72 :   files.reset();
    4109                 :            : 
    4110                 :            :   // DECISION TIME - we enumerate stale fileids/mtimes
    4111         [ +  - ]:         72 :   deque<pair<int64_t,int64_t> > stale_fileid_mtime;
    4112                 :            :   
    4113                 :         72 :   time_t time_start = time(NULL);
    4114                 :        304 :   while(1)
    4115                 :            :     {
    4116                 :            :       // PR28514: limit grooming iteration to O(rescan time), to avoid
    4117                 :            :       // slow filesystem tests over many files locking out rescans for
    4118                 :            :       // too long.
    4119   [ +  +  -  + ]:        188 :       if (rescan_s > 0 && (long)time(NULL) > (long)(time_start + rescan_s))
    4120                 :            :         {
    4121   [ #  #  #  #  :          0 :           inc_metric("groomed_total", "decision", "aborted");
          #  #  #  #  #  
          #  #  #  #  #  
                   #  # ]
    4122                 :          0 :           break;
    4123                 :            :         }
    4124                 :            : 
    4125         [ +  - ]:        188 :       if (interrupted) break;
    4126                 :            : 
    4127         [ +  - ]:        188 :       int rc = files.step();
    4128         [ +  + ]:        188 :       if (rc != SQLITE_ROW)
    4129                 :            :         break;
    4130                 :            : 
    4131         [ +  - ]:        116 :       int64_t mtime = sqlite3_column_int64 (files, 0);
    4132         [ +  - ]:        116 :       int64_t fileid = sqlite3_column_int64 (files, 1);
    4133   [ +  -  -  + ]:        116 :       const char* filename = ((const char*) sqlite3_column_text (files, 2) ?: "");
    4134                 :        116 :       struct stat s;
    4135                 :        116 :       bool regex_file_drop = 0;
    4136                 :            : 
    4137         [ +  + ]:        116 :       if (regex_groom)
    4138                 :            :         {
    4139         [ +  - ]:         16 :           bool reg_include = !regexec (&file_include_regex, filename, 0, 0, 0);
    4140         [ +  - ]:         16 :           bool reg_exclude = !regexec (&file_exclude_regex, filename, 0, 0, 0);
    4141                 :         16 :           regex_file_drop = !reg_include || reg_exclude; // match logic of scan_source_paths  
    4142                 :            :         }
    4143                 :            : 
    4144                 :        116 :       rc = stat(filename, &s);
    4145   [ +  +  -  + ]:        116 :       if ( regex_file_drop ||  rc < 0 || (mtime != (int64_t) s.st_mtime) )
    4146                 :            :         {
    4147         [ +  - ]:         24 :           if (verbose > 2)
    4148   [ +  -  +  -  :         48 :             obatched(clog) << "groom: stale file=" << filename << " mtime=" << mtime << endl;
          +  -  +  -  +  
                      - ]
    4149         [ +  - ]:         24 :           stale_fileid_mtime.push_back(make_pair(fileid,mtime));
    4150   [ +  -  +  -  :         48 :           inc_metric("groomed_total", "decision", "stale");
          +  -  +  -  -  
          +  -  +  -  -  
                   -  - ]
    4151   [ +  -  +  -  :         48 :           set_metric("thread_work_pending","role","groom", stale_fileid_mtime.size());
          +  -  +  -  -  
          +  -  +  -  -  
                   -  - ]
    4152                 :            :         }
    4153                 :            :       else
    4154   [ +  -  +  -  :        184 :         inc_metric("groomed_total", "decision", "fresh");
          +  -  +  -  -  
          +  -  +  -  -  
                   -  - ]
    4155                 :            :       
    4156         [ +  - ]:        116 :       if (sigusr1 != forced_rescan_count) // stop early if scan triggered
    4157                 :            :         break;
    4158                 :        116 :     }
    4159         [ +  - ]:         72 :   files.reset();
    4160                 :            : 
    4161                 :            :   // ACTION TIME
    4162                 :            : 
    4163                 :            :   // Now that we know which file/mtime tuples are stale, actually do
    4164                 :            :   // the deletion from the database.  Doing this during the SELECT
    4165                 :            :   // iteration above results in undefined behaviour in sqlite, as per
    4166                 :            :   // https://www.sqlite.org/isolation.html
    4167                 :            : 
    4168                 :            :   // We could shuffle stale_fileid_mtime[] here.  It'd let aborted
    4169                 :            :   // sequences of nuke operations resume at random locations, instead
    4170                 :            :   // of just starting over.  But it doesn't matter much either way,
    4171                 :            :   // as long as we make progress.
    4172                 :            : 
    4173   [ +  -  +  -  :        144 :   sqlite_ps files_del_f_de (db, "nuke f_de", "delete from " BUILDIDS "_f_de where file = ? and mtime = ?");
          +  -  +  -  -  
                      - ]
    4174   [ +  -  +  -  :        144 :   sqlite_ps files_del_r_de (db, "nuke r_de", "delete from " BUILDIDS "_r_de where file = ? and mtime = ?");
          +  -  +  -  -  
                      - ]
    4175         [ +  - ]:         72 :   sqlite_ps files_del_scan (db, "nuke f_m_s", "delete from " BUILDIDS "_file_mtime_scanned "
    4176   [ +  -  +  -  :        144 :                             "where file = ? and mtime = ?");
          +  -  +  -  -  
                      - ]
    4177                 :            : 
    4178         [ +  + ]:         96 :   while (! stale_fileid_mtime.empty())
    4179                 :            :     {
    4180                 :         24 :       auto stale = stale_fileid_mtime.front();
    4181                 :         24 :       stale_fileid_mtime.pop_front();
    4182   [ +  -  +  -  :         48 :       set_metric("thread_work_pending","role","groom", stale_fileid_mtime.size());
          +  -  +  -  -  
          +  -  +  -  -  
                   -  - ]
    4183                 :            : 
    4184                 :            :       // PR28514: limit grooming iteration to O(rescan time), to avoid
    4185                 :            :       // slow nuke_* queries over many files locking out rescans for too
    4186                 :            :       // long.  We iterate over the files in random() sequence to avoid
    4187                 :            :       // partial checks going over the same set.
    4188   [ -  +  -  - ]:         24 :       if (rescan_s > 0 && (long)time(NULL) > (long)(time_start + rescan_s))
    4189                 :            :         {
    4190   [ #  #  #  #  :          0 :           inc_metric("groomed_total", "action", "aborted");
          #  #  #  #  #  
          #  #  #  #  #  
                   #  # ]
    4191                 :          0 :           break;
    4192                 :            :         }
    4193                 :            : 
    4194         [ +  - ]:         24 :       if (interrupted) break;
    4195                 :            : 
    4196                 :         24 :       int64_t fileid = stale.first;
    4197                 :         24 :       int64_t mtime = stale.second;
    4198   [ +  -  +  -  :         24 :       files_del_f_de.reset().bind(1,fileid).bind(2,mtime).step_ok_done();
             +  -  +  - ]
    4199   [ +  -  +  -  :         24 :       files_del_r_de.reset().bind(1,fileid).bind(2,mtime).step_ok_done();
             +  -  +  - ]
    4200   [ +  -  +  -  :         24 :       files_del_scan.reset().bind(1,fileid).bind(2,mtime).step_ok_done();
             +  -  +  - ]
    4201   [ +  -  +  -  :         48 :       inc_metric("groomed_total", "action", "cleaned");
          +  -  +  -  -  
          +  -  +  -  -  
                   -  - ]
    4202                 :            :       
    4203         [ +  - ]:         24 :        if (sigusr1 != forced_rescan_count) // stop early if scan triggered
    4204                 :            :         break;
    4205                 :            :     }
    4206                 :         72 :   stale_fileid_mtime.clear(); // no need for this any longer
    4207   [ +  -  +  -  :        144 :   set_metric("thread_work_pending","role","groom", stale_fileid_mtime.size());
          +  -  +  -  -  
          +  -  +  -  -  
                   -  - ]
    4208                 :            :       
    4209                 :            :   // delete buildids with no references in _r_de or _f_de tables;
    4210                 :            :   // cascades to _r_sref & _f_s records
    4211         [ +  - ]:         72 :   sqlite_ps buildids_del (db, "nuke orphan buildids",
    4212                 :            :                           "delete from " BUILDIDS "_buildids "
    4213                 :            :                           "where not exists (select 1 from " BUILDIDS "_f_de d where " BUILDIDS "_buildids.id = d.buildid) "
    4214   [ +  -  +  -  :        144 :                           "and not exists (select 1 from " BUILDIDS "_r_de d where " BUILDIDS "_buildids.id = d.buildid)");
          +  -  +  -  -  
                      - ]
    4215   [ +  -  +  - ]:         72 :   buildids_del.reset().step_ok_done();
    4216                 :            : 
    4217         [ -  + ]:         72 :   if (interrupted) return;
    4218                 :            : 
    4219                 :            :   // NB: "vacuum" is too heavy for even daily runs: it rewrites the entire db, so is done as maxigroom -G
    4220   [ +  -  +  -  :        144 :   sqlite_ps g1 (db, "incremental vacuum", "pragma incremental_vacuum");
          +  -  +  -  -  
                      - ]
    4221   [ +  -  +  - ]:         72 :   g1.reset().step_ok_done();
    4222   [ +  -  +  -  :        144 :   sqlite_ps g2 (db, "optimize", "pragma optimize");
          +  -  -  +  -  
                      - ]
    4223   [ +  -  +  - ]:         72 :   g2.reset().step_ok_done();
    4224   [ +  -  +  -  :        144 :   sqlite_ps g3 (db, "wal checkpoint", "pragma wal_checkpoint=truncate");
          +  -  +  -  -  
                      - ]
    4225   [ +  -  +  - ]:         72 :   g3.reset().step_ok_done();
    4226                 :            : 
    4227         [ +  - ]:         72 :   database_stats_report();
    4228                 :            : 
    4229   [ +  -  +  - ]:         72 :   (void) statfs_free_enough_p(db_path, "database"); // report sqlite filesystem size
    4230                 :            : 
    4231         [ +  - ]:         72 :   sqlite3_db_release_memory(db); // shrink the process if possible
    4232         [ +  - ]:         72 :   sqlite3_db_release_memory(dbq); // ... for both connections
    4233         [ +  - ]:         72 :   debuginfod_pool_groom(); // and release any debuginfod_client objects we've been holding onto
    4234                 :            : #if HAVE_MALLOC_TRIM
    4235                 :         72 :   malloc_trim(0); // PR31103: release memory allocated for temporary purposes
    4236                 :            : #endif
    4237                 :            :   
    4238                 :            : #if 0 /* PR31265: don't jettison cache unnecessarily */
    4239                 :            :   fdcache.limit(0); // release the fdcache contents
    4240                 :            :   fdcache.limit(fdcache_mbs); // restore status quo parameters
    4241                 :            : #endif
    4242                 :            :   
    4243                 :         72 :   clock_gettime (CLOCK_MONOTONIC, &ts_end);
    4244                 :         72 :   double deltas = (ts_end.tv_sec - ts_start.tv_sec) + (ts_end.tv_nsec - ts_start.tv_nsec)/1.e9;
    4245                 :            : 
    4246   [ +  -  +  -  :        144 :   obatched(clog) << "groomed database in " << deltas << "s" << endl;
             +  -  +  - ]
    4247                 :         72 : }
    4248                 :            : 
    4249                 :            : 
    4250                 :            : static void*
    4251                 :         72 : thread_main_groom (void* /*arg*/)
    4252                 :            : {
    4253   [ +  -  +  -  :        144 :   set_metric("thread_tid", "role", "groom", tid());
          +  -  -  +  -  
             +  -  -  -  
                      - ]
    4254   [ +  -  +  -  :        144 :   add_metric("thread_count", "role", "groom", 1);
          +  -  -  +  -  
             +  -  -  -  
                      - ]
    4255                 :            : 
    4256                 :         72 :   time_t last_groom = 0;
    4257                 :            : 
    4258                 :        570 :   while (1)
    4259                 :            :     {
    4260                 :        321 :       sleep (1);
    4261                 :        321 :       scanq.wait_idle(); // PR25394: block scanners during grooming!
    4262         [ +  + ]:        321 :       if (interrupted) break;
    4263                 :            : 
    4264                 :        249 :       time_t now = time(NULL);
    4265                 :        249 :       bool groom_now = false;
    4266         [ +  + ]:        249 :       if (last_groom == 0) // at least one initial groom is documented even for -g0
    4267                 :         66 :         groom_now = true;
    4268   [ +  +  +  + ]:        249 :       if (groom_s > 0 && (long)now > (long)(last_groom + groom_s))
    4269                 :        249 :         groom_now = true;
    4270         [ +  + ]:        249 :       if (sigusr2 != forced_groom_count)
    4271                 :            :         {
    4272                 :          6 :           forced_groom_count = sigusr2;
    4273                 :          6 :           groom_now = true;
    4274                 :            :         }
    4275         [ +  + ]:        249 :       if (groom_now)
    4276                 :            :         {
    4277   [ +  -  +  -  :        144 :           set_metric("thread_busy", "role", "groom", 1);
          +  -  -  +  -  
             +  -  -  -  
                      - ]
    4278                 :         72 :           try
    4279                 :            :             {
    4280         [ +  - ]:         72 :               groom ();
    4281                 :            :             }
    4282         [ -  - ]:          0 :           catch (const sqlite_exception& e)
    4283                 :            :             {
    4284   [ -  -  -  -  :          0 :               obatched(cerr) << e.message << endl;
                   -  - ]
    4285                 :          0 :             }
    4286                 :         72 :           last_groom = time(NULL); // NB: now was before grooming
    4287                 :            :           // finished a grooming loop
    4288   [ +  -  +  -  :        144 :           inc_metric("thread_work_total", "role", "groom");
          +  -  -  +  -  
             +  -  -  -  
                      - ]
    4289   [ +  -  +  -  :        144 :           set_metric("thread_busy", "role", "groom", 0);
          +  -  -  +  -  
             +  -  -  -  
                      - ]
    4290                 :            :         }
    4291                 :            : 
    4292                 :        249 :       scanq.done_idle();
    4293                 :        249 :     }
    4294                 :            : 
    4295                 :         72 :   return 0;
    4296                 :            : }
    4297                 :            : 
    4298                 :            : 
    4299                 :            : ////////////////////////////////////////////////////////////////////////
    4300                 :            : 
    4301                 :            : 
    4302                 :            : static void
    4303                 :         74 : signal_handler (int /* sig */)
    4304                 :            : {
    4305                 :         74 :   interrupted ++;
    4306                 :            : 
    4307         [ +  + ]:         74 :   if (db)
    4308                 :         72 :     sqlite3_interrupt (db);
    4309         [ +  - ]:         74 :   if (dbq)
    4310                 :         74 :     sqlite3_interrupt (dbq);
    4311                 :            : 
    4312                 :            :   // NB: don't do anything else in here
    4313                 :         74 : }
    4314                 :            : 
    4315                 :            : static void
    4316                 :         60 : sigusr1_handler (int /* sig */)
    4317                 :            : {
    4318                 :         60 :    sigusr1 ++;
    4319                 :            :   // NB: don't do anything else in here
    4320                 :         60 : }
    4321                 :            : 
    4322                 :            : static void
    4323                 :          6 : sigusr2_handler (int /* sig */)
    4324                 :            : {
    4325                 :          6 :    sigusr2 ++;
    4326                 :            :   // NB: don't do anything else in here
    4327                 :          6 : }
    4328                 :            : 
    4329                 :            : 
    4330                 :            : static void // error logging callback from libmicrohttpd internals
    4331                 :          0 : error_cb (void *arg, const char *fmt, va_list ap)
    4332                 :            : {
    4333                 :          0 :   (void) arg;
    4334   [ #  #  #  #  :          0 :   inc_metric("error_count","libmicrohttpd",fmt);
          #  #  #  #  #  
             #  #  #  #  
                      # ]
    4335                 :          0 :   char errmsg[512];
    4336                 :          0 :   (void) vsnprintf (errmsg, sizeof(errmsg), fmt, ap); // ok if slightly truncated
    4337         [ #  # ]:          0 :   obatched(cerr) << "libmicrohttpd error: " << errmsg; // MHD_DLOG calls already include \n
    4338                 :          0 : }
    4339                 :            : 
    4340                 :            : 
    4341                 :            : // A user-defined sqlite function, to score the sharedness of the
    4342                 :            : // prefix of two strings.  This is used to compare candidate debuginfo
    4343                 :            : // / source-rpm names, so that the closest match
    4344                 :            : // (directory-topology-wise closest) is found.  This is important in
    4345                 :            : // case the same sref (source file name) is in many -debuginfo or
    4346                 :            : // -debugsource RPMs, such as when multiple versions/releases of the
    4347                 :            : // same package are in the database.
    4348                 :            : 
    4349                 :        805 : static void sqlite3_sharedprefix_fn (sqlite3_context* c, int argc, sqlite3_value** argv)
    4350                 :            : {
    4351         [ -  + ]:        805 :   if (argc != 2)
    4352                 :          0 :     sqlite3_result_error(c, "expect 2 string arguments", -1);
    4353   [ +  -  +  + ]:       1610 :   else if ((sqlite3_value_type(argv[0]) != SQLITE_TEXT) ||
    4354                 :        805 :            (sqlite3_value_type(argv[1]) != SQLITE_TEXT))
    4355                 :        515 :     sqlite3_result_null(c);
    4356                 :            :   else
    4357                 :            :     {
    4358                 :        290 :       const unsigned char* a = sqlite3_value_text (argv[0]);
    4359                 :        290 :       const unsigned char* b = sqlite3_value_text (argv[1]);
    4360                 :        290 :       int i = 0;
    4361   [ +  +  +  -  :      35878 :       while (*a != '\0' && *b != '\0' && *a++ == *b++)
             +  +  +  + ]
    4362                 :      35048 :         i++;
    4363                 :        290 :       sqlite3_result_int (c, i);
    4364                 :            :     }
    4365                 :        805 : }
    4366                 :            : 
    4367                 :            : 
    4368                 :            : static unsigned
    4369                 :        144 : default_concurrency() // guaranteed >= 1
    4370                 :            : {
    4371                 :            :   // Prior to PR29975 & PR29976, we'd just use this: 
    4372                 :        144 :   unsigned sth = std::thread::hardware_concurrency();
    4373                 :            :   // ... but on many-CPU boxes, admins or distros may throttle
    4374                 :            :   // resources in such a way that debuginfod would mysteriously fail.
    4375                 :            :   // So we reduce the defaults:
    4376                 :            : 
    4377                 :        144 :   unsigned aff = 0;
    4378                 :            : #ifdef HAVE_SCHED_GETAFFINITY
    4379                 :        144 :   {
    4380                 :        144 :     int ret;
    4381                 :        144 :     cpu_set_t mask;
    4382                 :        144 :     CPU_ZERO(&mask);
    4383                 :        144 :     ret = sched_getaffinity(0, sizeof(mask), &mask);
    4384         [ +  - ]:        144 :     if (ret == 0)
    4385                 :        144 :       aff = CPU_COUNT(&mask);
    4386                 :            :   }
    4387                 :            : #endif
    4388                 :            :   
    4389                 :        144 :   unsigned fn = 0;
    4390                 :            : #ifdef HAVE_GETRLIMIT
    4391                 :        144 :   {
    4392                 :        144 :     struct rlimit rlim;
    4393                 :        144 :     int rc = getrlimit(RLIMIT_NOFILE, &rlim);
    4394         [ +  - ]:        144 :     if (rc == 0)
    4395         [ +  - ]:        288 :       fn = max((rlim_t)1, (rlim.rlim_cur - 100) / 4);
    4396                 :            :     // at least 2 fds are used by each listener thread etc.
    4397                 :            :     // plus a bunch to account for shared libraries and such
    4398                 :            :   }
    4399                 :            : #endif
    4400                 :            : 
    4401   [ -  +  -  +  :        144 :   unsigned d = min(max(sth, 1U),
                   -  + ]
    4402         [ -  + ]:        144 :                    min(max(aff, 1U),
    4403         [ -  + ]:        144 :                        max(fn, 1U)));
    4404                 :        144 :   return d;
    4405                 :            : }
    4406                 :            : 
    4407                 :            : 
    4408                 :            : // 30879: Something to help out in case of an uncaught exception.
    4409                 :          0 : void my_terminate_handler()
    4410                 :            : {
    4411                 :            : #if defined(__GLIBC__)
    4412                 :          0 :   void *array[40];
    4413                 :          0 :   int size = backtrace (array, 40);
    4414                 :          0 :   backtrace_symbols_fd (array, size, STDERR_FILENO);
    4415                 :            : #endif
    4416                 :            : #if defined(__GLIBCXX__) || defined(__GLIBCPP__)
    4417                 :          0 :   __gnu_cxx::__verbose_terminate_handler();
    4418                 :            : #endif
    4419                 :          0 :   abort();
    4420                 :            : }
    4421                 :            : 
    4422                 :            : 
    4423                 :            : int
    4424                 :         74 : main (int argc, char *argv[])
    4425                 :            : {
    4426                 :         74 :   (void) setlocale (LC_ALL, "");
    4427                 :         74 :   (void) bindtextdomain (PACKAGE_TARNAME, LOCALEDIR);
    4428                 :         74 :   (void) textdomain (PACKAGE_TARNAME);
    4429                 :            : 
    4430                 :         74 :   std::set_terminate(& my_terminate_handler);
    4431                 :            : 
    4432                 :            :   /* Tell the library which version we are expecting.  */
    4433                 :         74 :   elf_version (EV_CURRENT);
    4434                 :            : 
    4435   [ +  -  -  + ]:        148 :   tmpdir = string(getenv("TMPDIR") ?: "/tmp");
    4436                 :            : 
    4437                 :            :   /* Set computed default values. */
    4438   [ -  +  +  -  :         74 :   db_path = string(getenv("HOME") ?: "/") + string("/.debuginfod.sqlite"); /* XDG? */
          +  -  -  +  -  
             +  +  -  -  
                      - ]
    4439                 :         74 :   int rc = regcomp (& file_include_regex, ".*", REG_EXTENDED|REG_NOSUB); // match everything
    4440         [ -  + ]:         74 :   if (rc != 0)
    4441                 :          0 :     error (EXIT_FAILURE, 0, "regcomp failure: %d", rc);
    4442                 :         74 :   rc = regcomp (& file_exclude_regex, "^$", REG_EXTENDED|REG_NOSUB); // match nothing
    4443         [ -  + ]:         74 :   if (rc != 0)
    4444                 :          0 :     error (EXIT_FAILURE, 0, "regcomp failure: %d", rc);
    4445                 :            : 
    4446                 :            :   // default parameters for fdcache are computed from system stats
    4447                 :         74 :   struct statfs sfs;
    4448                 :         74 :   rc = statfs(tmpdir.c_str(), &sfs);
    4449         [ -  + ]:         74 :   if (rc < 0)
    4450                 :          0 :     fdcache_mbs = 1024; // 1 gigabyte
    4451                 :            :   else
    4452                 :         74 :     fdcache_mbs = sfs.f_bavail * sfs.f_bsize / 1024 / 1024 / 4; // 25% of free space
    4453                 :         74 :   fdcache_mintmp = 25; // emergency flush at 25% remaining (75% full)
    4454                 :         74 :   fdcache_prefetch = 64; // guesstimate storage is this much less costly than re-decompression
    4455                 :            : 
    4456                 :            :   /* Parse and process arguments.  */
    4457                 :         74 :   int remaining;
    4458                 :         74 :   (void) argp_parse (&argp, argc, argv, ARGP_IN_ORDER, &remaining, NULL);
    4459         [ -  + ]:         74 :   if (remaining != argc)
    4460                 :          0 :       error (EXIT_FAILURE, 0,
    4461                 :          0 :              "unexpected argument: %s", argv[remaining]);
    4462                 :            : 
    4463   [ +  +  +  +  :         74 :   if (scan_archives.size()==0 && !scan_files && source_paths.size()>0)
                   -  + ]
    4464         [ #  # ]:          0 :     obatched(clog) << "warning: without -F -R -U -Z, ignoring PATHs" << endl;
    4465                 :            : 
    4466                 :         74 :   fdcache.limit(fdcache_mbs);
    4467                 :            : 
    4468                 :         74 :   (void) signal (SIGPIPE, SIG_IGN); // microhttpd can generate it incidentally, ignore
    4469                 :         74 :   (void) signal (SIGINT, signal_handler); // ^C
    4470                 :         74 :   (void) signal (SIGHUP, signal_handler); // EOF
    4471                 :         74 :   (void) signal (SIGTERM, signal_handler); // systemd
    4472                 :         74 :   (void) signal (SIGUSR1, sigusr1_handler); // end-user
    4473                 :         74 :   (void) signal (SIGUSR2, sigusr2_handler); // end-user
    4474                 :            : 
    4475                 :            :   /* Get database ready. */
    4476         [ +  + ]:         74 :   if (! passive_p)
    4477                 :            :     {
    4478                 :         72 :       rc = sqlite3_open_v2 (db_path.c_str(), &db, (SQLITE_OPEN_READWRITE
    4479                 :            :                                                    |SQLITE_OPEN_URI
    4480                 :            :                                                    |SQLITE_OPEN_PRIVATECACHE
    4481                 :            :                                                    |SQLITE_OPEN_CREATE
    4482                 :            :                                                    |SQLITE_OPEN_FULLMUTEX), /* thread-safe */
    4483                 :            :                             NULL);
    4484         [ -  + ]:         72 :       if (rc == SQLITE_CORRUPT)
    4485                 :            :         {
    4486                 :          0 :           (void) unlink (db_path.c_str());
    4487                 :          0 :           error (EXIT_FAILURE, 0,
    4488                 :            :                  "cannot open %s, deleted database: %s", db_path.c_str(), sqlite3_errmsg(db));
    4489                 :            :         }
    4490         [ -  + ]:         72 :       else if (rc)
    4491                 :            :         {
    4492                 :          0 :           error (EXIT_FAILURE, 0,
    4493                 :            :                  "cannot open %s, consider deleting database: %s", db_path.c_str(), sqlite3_errmsg(db));
    4494                 :            :         }
    4495                 :            :     }
    4496                 :            : 
    4497                 :            :   // open the readonly query variant
    4498                 :            :   // NB: PRIVATECACHE allows web queries to operate in parallel with
    4499                 :            :   // much other grooming/scanning operation.
    4500                 :         74 :   rc = sqlite3_open_v2 (db_path.c_str(), &dbq, (SQLITE_OPEN_READONLY
    4501                 :            :                                                 |SQLITE_OPEN_URI
    4502                 :            :                                                 |SQLITE_OPEN_PRIVATECACHE
    4503                 :            :                                                 |SQLITE_OPEN_FULLMUTEX), /* thread-safe */
    4504                 :            :                         NULL);
    4505         [ -  + ]:         74 :   if (rc)
    4506                 :            :     {
    4507                 :          0 :       error (EXIT_FAILURE, 0,
    4508                 :            :              "cannot open %s, consider deleting database: %s", db_path.c_str(), sqlite3_errmsg(dbq));
    4509                 :            :     }
    4510                 :            : 
    4511                 :            : 
    4512         [ +  - ]:        148 :   obatched(clog) << "opened database " << db_path
    4513   [ +  +  +  -  :         76 :                  << (db?" rw":"") << (dbq?" ro":"") << endl;
          -  +  +  -  +  
                      - ]
    4514   [ +  -  +  - ]:        148 :   obatched(clog) << "sqlite version " << sqlite3_version << endl;
    4515   [ +  +  +  -  :        220 :   obatched(clog) << "service mode " << (passive_p ? "passive":"active") << endl;
                   +  - ]
    4516                 :            : 
    4517                 :            :   // add special string-prefix-similarity function used in rpm sref/sdef resolution
    4518                 :         74 :   rc = sqlite3_create_function(dbq, "sharedprefix", 2, SQLITE_UTF8, NULL,
    4519                 :            :                                & sqlite3_sharedprefix_fn, NULL, NULL);
    4520         [ -  + ]:         74 :   if (rc != SQLITE_OK)
    4521                 :          0 :     error (EXIT_FAILURE, 0,
    4522                 :            :            "cannot create sharedprefix function: %s", sqlite3_errmsg(dbq));
    4523                 :            : 
    4524         [ +  + ]:         74 :   if (! passive_p)
    4525                 :            :     {
    4526         [ +  + ]:         72 :       if (verbose > 3)
    4527   [ +  -  +  - ]:         92 :         obatched(clog) << "ddl: " << DEBUGINFOD_SQLITE_DDL << endl;
    4528                 :         72 :       rc = sqlite3_exec (db, DEBUGINFOD_SQLITE_DDL, NULL, NULL, NULL);
    4529         [ -  + ]:         72 :       if (rc != SQLITE_OK)
    4530                 :            :         {
    4531                 :          0 :           error (EXIT_FAILURE, 0,
    4532                 :            :                  "cannot run database schema ddl: %s", sqlite3_errmsg(db));
    4533                 :            :         }
    4534                 :            :     }
    4535                 :            : 
    4536   [ +  -  +  -  :        148 :   obatched(clog) << "libmicrohttpd version " << MHD_get_version() << endl;
                   +  - ]
    4537                 :            :   
    4538                 :            :   /* If '-C' wasn't given or was given with no arg, pick a reasonable default
    4539                 :            :      for the number of worker threads.  */
    4540         [ +  + ]:         74 :   if (connection_pool == 0)
    4541                 :         70 :     connection_pool = default_concurrency();
    4542                 :            : 
    4543                 :            :   /* Note that MHD_USE_EPOLL and MHD_USE_THREAD_PER_CONNECTION don't
    4544                 :            :      work together.  */
    4545                 :         74 :   unsigned int use_epoll = 0;
    4546                 :            : #if MHD_VERSION >= 0x00095100
    4547                 :         74 :   use_epoll = MHD_USE_EPOLL;
    4548                 :            : #endif
    4549                 :            : 
    4550                 :         74 :   unsigned int mhd_flags = (
    4551                 :            : #if MHD_VERSION >= 0x00095300
    4552                 :            :                             MHD_USE_INTERNAL_POLLING_THREAD
    4553                 :            : #else
    4554                 :            :                             MHD_USE_SELECT_INTERNALLY
    4555                 :            : #endif
    4556                 :            :                             | MHD_USE_DUAL_STACK
    4557                 :            :                             | use_epoll
    4558                 :            : #if MHD_VERSION >= 0x00095200
    4559                 :            :                             | MHD_USE_ITC
    4560                 :            : #endif
    4561                 :            :                             | MHD_USE_DEBUG); /* report errors to stderr */
    4562                 :            : 
    4563                 :            :   // Start httpd server threads.  Use a single dual-homed pool.
    4564                 :         74 :   MHD_Daemon *d46 = MHD_start_daemon (mhd_flags, http_port,
    4565                 :            :                                       NULL, NULL, /* default accept policy */
    4566                 :            :                                       handler_cb, NULL, /* handler callback */
    4567                 :            :                                       MHD_OPTION_EXTERNAL_LOGGER,
    4568                 :            :                                       error_cb, NULL,
    4569                 :            :                                       MHD_OPTION_THREAD_POOL_SIZE,
    4570                 :            :                                       (int)connection_pool,
    4571                 :            :                                       MHD_OPTION_END);
    4572                 :            : 
    4573                 :         74 :   MHD_Daemon *d4 = NULL;
    4574         [ -  + ]:         74 :   if (d46 == NULL)
    4575                 :            :     {
    4576                 :            :       // Cannot use dual_stack, use ipv4 only
    4577                 :          0 :       mhd_flags &= ~(MHD_USE_DUAL_STACK);
    4578         [ #  # ]:          0 :       d4 = MHD_start_daemon (mhd_flags, http_port,
    4579                 :            :                              NULL, NULL, /* default accept policy */
    4580                 :            :                              handler_cb, NULL, /* handler callback */
    4581                 :            :                              MHD_OPTION_EXTERNAL_LOGGER,
    4582                 :            :                              error_cb, NULL,
    4583                 :            :                              (connection_pool
    4584                 :            :                               ? MHD_OPTION_THREAD_POOL_SIZE
    4585                 :            :                               : MHD_OPTION_END),
    4586                 :            :                              (connection_pool
    4587                 :            :                               ? (int)connection_pool
    4588                 :            :                               : MHD_OPTION_END),
    4589                 :            :                              MHD_OPTION_END);
    4590         [ #  # ]:          0 :       if (d4 == NULL)
    4591                 :            :         {
    4592                 :          0 :           sqlite3 *database = db;
    4593                 :          0 :           sqlite3 *databaseq = dbq;
    4594                 :          0 :           db = dbq = 0; // for signal_handler not to freak
    4595                 :          0 :           sqlite3_close (databaseq);
    4596                 :          0 :           sqlite3_close (database);
    4597                 :          0 :           error (EXIT_FAILURE, 0, "cannot start http server at port %d",
    4598                 :            :                  http_port);
    4599                 :            :         }
    4600                 :            : 
    4601                 :            :     }
    4602                 :         74 :   obatched(clog) << "started http server on"
    4603                 :            :                  << (d4 != NULL ? " IPv4 " : " IPv4 IPv6 ")
    4604   [ +  -  +  -  :        148 :                  << "port=" << http_port << endl;
          +  -  +  -  +  
                      - ]
    4605                 :            : 
    4606                 :            :   // add maxigroom sql if -G given
    4607         [ -  + ]:         74 :   if (maxigroom)
    4608                 :            :     {
    4609         [ #  # ]:          0 :       obatched(clog) << "maxigrooming database, please wait." << endl;
    4610         [ #  # ]:          0 :       extra_ddl.push_back("create index if not exists " BUILDIDS "_r_sref_arc on " BUILDIDS "_r_sref(artifactsrc);");
    4611         [ #  # ]:          0 :       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);");
    4612         [ #  # ]:          0 :       extra_ddl.push_back("drop index if exists " BUILDIDS "_r_sref_arc;");
    4613                 :            : 
    4614                 :            :       // NB: we don't maxigroom the _files interning table.  It'd require a temp index on all the
    4615                 :            :       // tables that have file foreign-keys, which is a lot.
    4616                 :            : 
    4617                 :            :       // NB: with =delete, may take up 3x disk space total during vacuum process
    4618                 :            :       //     vs.  =off (only 2x but may corrupt database if program dies mid-vacuum)
    4619                 :            :       //     vs.  =wal (>3x observed, but safe)
    4620         [ #  # ]:          0 :       extra_ddl.push_back("pragma journal_mode=delete;");
    4621         [ #  # ]:          0 :       extra_ddl.push_back("vacuum;");
    4622         [ #  # ]:          0 :       extra_ddl.push_back("pragma journal_mode=wal;");
    4623                 :            :     }
    4624                 :            : 
    4625                 :            :   // run extra -D sql if given
    4626         [ +  + ]:         74 :   if (! passive_p)
    4627         [ -  + ]:         72 :     for (auto&& i: extra_ddl)
    4628                 :            :       {
    4629         [ #  # ]:          0 :         if (verbose > 1)
    4630   [ #  #  #  # ]:          0 :           obatched(clog) << "extra ddl:\n" << i << endl;
    4631                 :          0 :         rc = sqlite3_exec (db, i.c_str(), NULL, NULL, NULL);
    4632   [ #  #  #  # ]:          0 :         if (rc != SQLITE_OK && rc != SQLITE_DONE && rc != SQLITE_ROW)
    4633                 :          0 :           error (0, 0,
    4634                 :            :                  "warning: cannot run database extra ddl %s: %s", i.c_str(), sqlite3_errmsg(db));
    4635                 :            : 
    4636         [ #  # ]:          0 :         if (maxigroom)
    4637         [ #  # ]:          0 :           obatched(clog) << "maxigroomed database" << endl;
    4638                 :            :       }
    4639                 :            : 
    4640         [ +  + ]:         74 :   if (! passive_p)
    4641   [ +  -  +  - ]:        144 :     obatched(clog) << "search concurrency " << concurrency << endl;
    4642                 :         74 :   obatched(clog) << "webapi connection pool " << connection_pool
    4643   [ +  -  -  +  :         74 :                  << (connection_pool ? "" : " (unlimited)") << endl;
             +  -  +  - ]
    4644         [ +  + ]:         74 :   if (! passive_p) {
    4645   [ +  -  +  - ]:        144 :     obatched(clog) << "rescan time " << rescan_s << endl;
    4646   [ +  -  +  - ]:        144 :     obatched(clog) << "scan checkpoint " << scan_checkpoint << endl;
    4647                 :            :   }
    4648   [ +  -  +  - ]:        148 :   obatched(clog) << "fdcache mbs " << fdcache_mbs << endl;
    4649   [ +  -  +  - ]:        148 :   obatched(clog) << "fdcache prefetch " << fdcache_prefetch << endl;
    4650   [ +  -  +  - ]:        148 :   obatched(clog) << "fdcache tmpdir " << tmpdir << endl;
    4651   [ +  -  +  - ]:        148 :   obatched(clog) << "fdcache tmpdir min% " << fdcache_mintmp << endl;
    4652         [ +  + ]:         74 :   if (! passive_p)
    4653   [ +  -  +  - ]:        144 :     obatched(clog) << "groom time " << groom_s << endl;
    4654   [ +  -  +  - ]:        148 :   obatched(clog) << "forwarded ttl limit " << forwarded_ttl_limit << endl;
    4655                 :            : 
    4656         [ +  + ]:         74 :   if (scan_archives.size()>0)
    4657                 :            :     {
    4658                 :         50 :       obatched ob(clog);
    4659         [ +  - ]:         50 :       auto& o = ob << "accepting archive types ";
    4660         [ +  + ]:        152 :       for (auto&& arch : scan_archives)
    4661   [ +  -  +  -  :        102 :         o << arch.first << "(" << arch.second << ") ";
             +  -  +  - ]
    4662         [ +  - ]:         50 :       o << endl;
    4663                 :         50 :     }
    4664                 :         74 :   const char* du = getenv(DEBUGINFOD_URLS_ENV_VAR);
    4665   [ +  +  +  + ]:         74 :   if (du && du[0] != '\0') // set to non-empty string?
    4666   [ +  -  +  - ]:         28 :     obatched(clog) << "upstream debuginfod servers: " << du << endl;
    4667                 :            : 
    4668         [ +  + ]:         74 :   vector<pthread_t> all_threads;
    4669                 :            : 
    4670         [ +  + ]:         74 :   if (! passive_p)
    4671                 :            :     {
    4672                 :         72 :       pthread_t pt;
    4673                 :         72 :       rc = pthread_create (& pt, NULL, thread_main_groom, NULL);
    4674         [ -  + ]:         72 :       if (rc)
    4675                 :          0 :         error (EXIT_FAILURE, rc, "cannot spawn thread to groom database\n");
    4676                 :            :       else
    4677                 :            :         {
    4678                 :            : #ifdef HAVE_PTHREAD_SETNAME_NP
    4679                 :         72 :           (void) pthread_setname_np (pt, "groom");
    4680                 :            : #endif
    4681         [ +  - ]:         72 :           all_threads.push_back(pt);
    4682                 :            :         }
    4683                 :            : 
    4684   [ +  +  +  + ]:         72 :       if (scan_files || scan_archives.size() > 0)
    4685                 :            :         {
    4686         [ +  - ]:         66 :           if (scan_checkpoint > 0)
    4687         [ +  - ]:         66 :             scan_barrier = new sqlite_checkpoint_pb(concurrency, (unsigned) scan_checkpoint);
    4688                 :            : 
    4689                 :         66 :           rc = pthread_create (& pt, NULL, thread_main_fts_source_paths, NULL);
    4690         [ -  + ]:         66 :           if (rc)
    4691                 :          0 :             error (EXIT_FAILURE, rc, "cannot spawn thread to traverse source paths\n");
    4692                 :            : #ifdef HAVE_PTHREAD_SETNAME_NP
    4693                 :         66 :           (void) pthread_setname_np (pt, "traverse");
    4694                 :            : #endif
    4695         [ +  - ]:         66 :           all_threads.push_back(pt);
    4696                 :            : 
    4697         [ +  + ]:        330 :           for (unsigned i=0; i<concurrency; i++)
    4698                 :            :             {
    4699                 :        264 :               rc = pthread_create (& pt, NULL, thread_main_scanner, NULL);
    4700         [ -  + ]:        264 :               if (rc)
    4701                 :          0 :                 error (EXIT_FAILURE, rc, "cannot spawn thread to scan source files / archives\n");
    4702                 :            : #ifdef HAVE_PTHREAD_SETNAME_NP
    4703                 :        264 :               (void) pthread_setname_np (pt, "scan");
    4704                 :            : #endif
    4705         [ +  - ]:        264 :               all_threads.push_back(pt);
    4706                 :            :             }
    4707                 :            :         }
    4708                 :            :     }
    4709                 :            :   
    4710                 :            :   /* Trivial main loop! */
    4711   [ +  -  +  - ]:         74 :   set_metric("ready", 1);
    4712         [ +  + ]:        214 :   while (! interrupted)
    4713         [ +  - ]:        140 :     pause ();
    4714         [ +  - ]:         74 :   scanq.nuke(); // wake up any remaining scanq-related threads, let them die
    4715   [ +  +  +  - ]:         74 :   if (scan_barrier) scan_barrier->nuke(); // ... in case they're stuck in a barrier
    4716   [ +  -  +  - ]:         74 :   set_metric("ready", 0);
    4717                 :            : 
    4718         [ +  - ]:         74 :   if (verbose)
    4719   [ +  -  +  -  :        148 :     obatched(clog) << "stopping" << endl;
                   -  - ]
    4720                 :            : 
    4721                 :            :   /* Join all our threads. */
    4722         [ +  + ]:        476 :   for (auto&& it : all_threads)
    4723         [ +  - ]:        402 :     pthread_join (it, NULL);
    4724                 :            : 
    4725                 :            :   /* Stop all the web service threads. */
    4726   [ +  -  +  - ]:         74 :   if (d46) MHD_stop_daemon (d46);
    4727   [ -  +  -  - ]:         74 :   if (d4) MHD_stop_daemon (d4);
    4728                 :            : 
    4729         [ +  + ]:         74 :   if (! passive_p)
    4730                 :            :     {
    4731                 :            :       /* With all threads known dead, we can clean up the global resources. */
    4732         [ +  - ]:         72 :       rc = sqlite3_exec (db, DEBUGINFOD_SQLITE_CLEANUP_DDL, NULL, NULL, NULL);
    4733         [ -  + ]:         72 :       if (rc != SQLITE_OK)
    4734                 :            :         {
    4735   [ #  #  #  # ]:          0 :           error (0, 0,
    4736                 :            :                  "warning: cannot run database cleanup ddl: %s", sqlite3_errmsg(db));
    4737                 :            :         }
    4738                 :            :     }
    4739                 :            : 
    4740         [ +  - ]:         74 :   debuginfod_pool_groom ();
    4741         [ +  + ]:         74 :   delete scan_barrier;
    4742                 :            : 
    4743                 :            :   // NB: no problem with unconditional free here - an earlier failed regcomp would exit program
    4744         [ +  - ]:         74 :   (void) regfree (& file_include_regex);
    4745         [ +  - ]:         74 :   (void) regfree (& file_exclude_regex);
    4746                 :            : 
    4747                 :         74 :   sqlite3 *database = db;
    4748                 :         74 :   sqlite3 *databaseq = dbq;
    4749                 :         74 :   db = dbq = 0; // for signal_handler not to freak
    4750         [ +  - ]:         74 :   (void) sqlite3_close (databaseq);
    4751         [ +  + ]:         74 :   if (! passive_p)
    4752         [ +  - ]:         72 :     (void) sqlite3_close (database);
    4753                 :            : 
    4754         [ +  + ]:         74 :   return 0;
    4755                 :         74 : }

Generated by: LCOV version 1.16