LCOV - code coverage report
Current view: top level - debuginfod - debuginfod.cxx (source / functions) Hit Total Coverage
Test: elfutils-0.184 Lines: 1327 1586 83.7 %
Date: 2021-05-22 20:18:50 Functions: 105 116 90.5 %
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 1691 3703 45.7 %

           Branch data     Line data    Source code
       1                 :            : /* Debuginfo-over-http server.
       2                 :            :    Copyright (C) 2019-2021 Red Hat, Inc.
       3                 :            :    This file is part of elfutils.
       4                 :            : 
       5                 :            :    This file is free software; you can redistribute it and/or modify
       6                 :            :    it under the terms of the GNU General Public License as published by
       7                 :            :    the Free Software Foundation; either version 3 of the License, or
       8                 :            :    (at your option) any later version.
       9                 :            : 
      10                 :            :    elfutils is distributed in the hope that it will be useful, but
      11                 :            :    WITHOUT ANY WARRANTY; without even the implied warranty of
      12                 :            :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      13                 :            :    GNU General Public License for more details.
      14                 :            : 
      15                 :            :    You should have received a copy of the GNU General Public License
      16                 :            :    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
      17                 :            : 
      18                 :            : 
      19                 :            : /* cargo-cult from libdwfl linux-kernel-modules.c */
      20                 :            : /* In case we have a bad fts we include this before config.h because it
      21                 :            :    can't handle _FILE_OFFSET_BITS.
      22                 :            :    Everything we need here is fine if its declarations just come first.
      23                 :            :    Also, include sys/types.h before fts. On some systems fts.h is not self
      24                 :            :    contained. */
      25                 :            : #ifdef BAD_FTS
      26                 :            :   #include <sys/types.h>
      27                 :            :   #include <fts.h>
      28                 :            : #endif
      29                 :            : 
      30                 :            : #ifdef HAVE_CONFIG_H
      31                 :            :   #include "config.h"
      32                 :            : #endif
      33                 :            : 
      34                 :            : extern "C" {
      35                 :            : #include "printversion.h"
      36                 :            : }
      37                 :            : 
      38                 :            : #include "debuginfod.h"
      39                 :            : #include <dwarf.h>
      40                 :            : 
      41                 :            : #include <argp.h>
      42                 :            : #ifdef __GNUC__
      43                 :            : #undef __attribute__ /* glibc bug - rhbz 1763325 */
      44                 :            : #endif
      45                 :            : 
      46                 :            : #include <unistd.h>
      47                 :            : #include <stdlib.h>
      48                 :            : #include <error.h>
      49                 :            : #include <libintl.h>
      50                 :            : #include <locale.h>
      51                 :            : #include <pthread.h>
      52                 :            : #include <signal.h>
      53                 :            : #include <sys/stat.h>
      54                 :            : #include <sys/time.h>
      55                 :            : #include <sys/vfs.h>
      56                 :            : #include <unistd.h>
      57                 :            : #include <fcntl.h>
      58                 :            : #include <netdb.h>
      59                 :            : 
      60                 :            : 
      61                 :            : /* If fts.h is included before config.h, its indirect inclusions may not
      62                 :            :    give us the right LFS aliases of these functions, so map them manually.  */
      63                 :            : #ifdef BAD_FTS
      64                 :            :   #ifdef _FILE_OFFSET_BITS
      65                 :            :     #define open open64
      66                 :            :     #define fopen fopen64
      67                 :            :   #endif
      68                 :            : #else
      69                 :            :   #include <sys/types.h>
      70                 :            :   #include <fts.h>
      71                 :            : #endif
      72                 :            : 
      73                 :            : #include <cstring>
      74                 :            : #include <vector>
      75                 :            : #include <set>
      76                 :            : #include <map>
      77                 :            : #include <string>
      78                 :            : #include <iostream>
      79                 :            : #include <iomanip>
      80                 :            : #include <ostream>
      81                 :            : #include <sstream>
      82                 :            : #include <mutex>
      83                 :            : #include <deque>
      84                 :            : #include <condition_variable>
      85                 :            : #include <thread>
      86                 :            : // #include <regex> // on rhel7 gcc 4.8, not competent
      87                 :            : #include <regex.h>
      88                 :            : // #include <algorithm>
      89                 :            : using namespace std;
      90                 :            : 
      91                 :            : #include <gelf.h>
      92                 :            : #include <libdwelf.h>
      93                 :            : 
      94                 :            : #include <microhttpd.h>
      95                 :            : 
      96                 :            : #if MHD_VERSION >= 0x00097002
      97                 :            : // libmicrohttpd 0.9.71 broke API
      98                 :            : #define MHD_RESULT enum MHD_Result
      99                 :            : #else
     100                 :            : #define MHD_RESULT int
     101                 :            : #endif
     102                 :            : 
     103                 :            : #include <curl/curl.h>
     104                 :            : #include <archive.h>
     105                 :            : #include <archive_entry.h>
     106                 :            : #include <sqlite3.h>
     107                 :            : 
     108                 :            : #ifdef __linux__
     109                 :            : #include <sys/syscall.h>
     110                 :            : #endif
     111                 :            : 
     112                 :            : #ifdef __linux__
     113                 :            : #define tid() syscall(SYS_gettid)
     114                 :            : #else
     115                 :            : #define tid() pthread_self()
     116                 :            : #endif
     117                 :            : 
     118                 :            : 
     119                 :            : inline bool
     120                 :        729 : string_endswith(const string& haystack, const string& needle)
     121                 :            : {
     122         [ +  - ]:       1458 :   return (haystack.size() >= needle.size() &&
     123   [ +  -  +  + ]:        729 :           equal(haystack.end()-needle.size(), haystack.end(),
     124                 :        729 :                 needle.begin()));
     125                 :            : }
     126                 :            : 
     127                 :            : 
     128                 :            : // Roll this identifier for every sqlite schema incompatibility.
     129                 :            : #define BUILDIDS "buildids9"
     130                 :            : 
     131                 :            : #if SQLITE_VERSION_NUMBER >= 3008000
     132                 :            : #define WITHOUT_ROWID "without rowid"
     133                 :            : #else
     134                 :            : #define WITHOUT_ROWID ""
     135                 :            : #endif
     136                 :            : 
     137                 :            : static const char DEBUGINFOD_SQLITE_DDL[] =
     138                 :            :   "pragma foreign_keys = on;\n"
     139                 :            :   "pragma synchronous = 0;\n" // disable fsync()s - this cache is disposable across a machine crash
     140                 :            :   "pragma journal_mode = wal;\n" // https://sqlite.org/wal.html
     141                 :            :   "pragma wal_checkpoint = truncate;\n" // clean out any preexisting wal file
     142                 :            :   "pragma journal_size_limit = 0;\n" // limit steady state file (between grooming, which also =truncate's)
     143                 :            :   "pragma auto_vacuum = incremental;\n" // https://sqlite.org/pragma.html
     144                 :            :   "pragma busy_timeout = 1000;\n" // https://sqlite.org/pragma.html
     145                 :            :   // NB: all these are overridable with -D option
     146                 :            : 
     147                 :            :   // Normalization table for interning file names
     148                 :            :   "create table if not exists " BUILDIDS "_files (\n"
     149                 :            :   "        id integer primary key not null,\n"
     150                 :            :   "        name text unique not null\n"
     151                 :            :   "        );\n"
     152                 :            :   // Normalization table for interning buildids
     153                 :            :   "create table if not exists " BUILDIDS "_buildids (\n"
     154                 :            :   "        id integer primary key not null,\n"
     155                 :            :   "        hex text unique not null);\n"
     156                 :            :   // Track the completion of scanning of a given file & sourcetype at given time
     157                 :            :   "create table if not exists " BUILDIDS "_file_mtime_scanned (\n"
     158                 :            :   "        mtime integer not null,\n"
     159                 :            :   "        file integer not null,\n"
     160                 :            :   "        size integer not null,\n" // in bytes
     161                 :            :   "        sourcetype text(1) not null\n"
     162                 :            :   "            check (sourcetype IN ('F', 'R')),\n"
     163                 :            :   "        foreign key (file) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n"
     164                 :            :   "        primary key (file, mtime, sourcetype)\n"
     165                 :            :   "        ) " WITHOUT_ROWID ";\n"
     166                 :            :   "create table if not exists " BUILDIDS "_f_de (\n"
     167                 :            :   "        buildid integer not null,\n"
     168                 :            :   "        debuginfo_p integer not null,\n"
     169                 :            :   "        executable_p integer not null,\n"
     170                 :            :   "        file integer not null,\n"
     171                 :            :   "        mtime integer not null,\n"
     172                 :            :   "        foreign key (file) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n"
     173                 :            :   "        foreign key (buildid) references " BUILDIDS "_buildids(id) on update cascade on delete cascade,\n"
     174                 :            :   "        primary key (buildid, file, mtime)\n"
     175                 :            :   "        ) " WITHOUT_ROWID ";\n"
     176                 :            :   "create table if not exists " BUILDIDS "_f_s (\n"
     177                 :            :   "        buildid integer not null,\n"
     178                 :            :   "        artifactsrc integer not null,\n"
     179                 :            :   "        file integer not null,\n" // NB: not necessarily entered into _mtime_scanned
     180                 :            :   "        mtime integer not null,\n"
     181                 :            :   "        foreign key (file) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n"
     182                 :            :   "        foreign key (artifactsrc) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n"
     183                 :            :   "        foreign key (buildid) references " BUILDIDS "_buildids(id) on update cascade on delete cascade,\n"
     184                 :            :   "        primary key (buildid, artifactsrc, file, mtime)\n"
     185                 :            :   "        ) " WITHOUT_ROWID ";\n"
     186                 :            :   "create table if not exists " BUILDIDS "_r_de (\n"
     187                 :            :   "        buildid integer not null,\n"
     188                 :            :   "        debuginfo_p integer not null,\n"
     189                 :            :   "        executable_p integer not null,\n"
     190                 :            :   "        file integer not null,\n"
     191                 :            :   "        mtime integer not null,\n"
     192                 :            :   "        content integer not null,\n"
     193                 :            :   "        foreign key (file) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n"
     194                 :            :   "        foreign key (content) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n"
     195                 :            :   "        foreign key (buildid) references " BUILDIDS "_buildids(id) on update cascade on delete cascade,\n"
     196                 :            :   "        primary key (buildid, debuginfo_p, executable_p, file, content, mtime)\n"
     197                 :            :   "        ) " WITHOUT_ROWID ";\n"
     198                 :            :   "create table if not exists " BUILDIDS "_r_sref (\n" // outgoing dwarf sourcefile references from rpm
     199                 :            :   "        buildid integer not null,\n"
     200                 :            :   "        artifactsrc integer not null,\n"
     201                 :            :   "        foreign key (artifactsrc) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n"
     202                 :            :   "        foreign key (buildid) references " BUILDIDS "_buildids(id) on update cascade on delete cascade,\n"
     203                 :            :   "        primary key (buildid, artifactsrc)\n"
     204                 :            :   "        ) " WITHOUT_ROWID ";\n"
     205                 :            :   "create table if not exists " BUILDIDS "_r_sdef (\n" // rpm contents that may satisfy sref
     206                 :            :   "        file integer not null,\n"
     207                 :            :   "        mtime integer not null,\n"
     208                 :            :   "        content integer not null,\n"
     209                 :            :   "        foreign key (file) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n"
     210                 :            :   "        foreign key (content) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n"
     211                 :            :   "        primary key (content, file, mtime)\n"
     212                 :            :   "        ) " WITHOUT_ROWID ";\n"
     213                 :            :   // create views to glue together some of the above tables, for webapi D queries
     214                 :            :   "create view if not exists " BUILDIDS "_query_d as \n"
     215                 :            :   "select\n"
     216                 :            :   "        b.hex as buildid, n.mtime, 'F' as sourcetype, f0.name as source0, n.mtime as mtime, null as source1\n"
     217                 :            :   "        from " BUILDIDS "_buildids b, " BUILDIDS "_files f0, " BUILDIDS "_f_de n\n"
     218                 :            :   "        where b.id = n.buildid and f0.id = n.file and n.debuginfo_p = 1\n"
     219                 :            :   "union all select\n"
     220                 :            :   "        b.hex as buildid, n.mtime, 'R' as sourcetype, f0.name as source0, n.mtime as mtime, f1.name as source1\n"
     221                 :            :   "        from " BUILDIDS "_buildids b, " BUILDIDS "_files f0, " BUILDIDS "_files f1, " BUILDIDS "_r_de n\n"
     222                 :            :   "        where b.id = n.buildid and f0.id = n.file and f1.id = n.content and n.debuginfo_p = 1\n"
     223                 :            :   ";"
     224                 :            :   // ... and for E queries
     225                 :            :   "create view if not exists " BUILDIDS "_query_e as \n"
     226                 :            :   "select\n"
     227                 :            :   "        b.hex as buildid, n.mtime, 'F' as sourcetype, f0.name as source0, n.mtime as mtime, null as source1\n"
     228                 :            :   "        from " BUILDIDS "_buildids b, " BUILDIDS "_files f0, " BUILDIDS "_f_de n\n"
     229                 :            :   "        where b.id = n.buildid and f0.id = n.file and n.executable_p = 1\n"
     230                 :            :   "union all select\n"
     231                 :            :   "        b.hex as buildid, n.mtime, 'R' as sourcetype, f0.name as source0, n.mtime as mtime, f1.name as source1\n"
     232                 :            :   "        from " BUILDIDS "_buildids b, " BUILDIDS "_files f0, " BUILDIDS "_files f1, " BUILDIDS "_r_de n\n"
     233                 :            :   "        where b.id = n.buildid and f0.id = n.file and f1.id = n.content and n.executable_p = 1\n"
     234                 :            :   ";"
     235                 :            :   // ... and for S queries
     236                 :            :   "create view if not exists " BUILDIDS "_query_s as \n"
     237                 :            :   "select\n"
     238                 :            :   "        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"
     239                 :            :   "        from " BUILDIDS "_buildids b, " BUILDIDS "_files f0, " BUILDIDS "_files fs, " BUILDIDS "_f_s n\n"
     240                 :            :   "        where b.id = n.buildid and f0.id = n.file and fs.id = n.artifactsrc\n"
     241                 :            :   "union all select\n"
     242                 :            :   "        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"
     243                 :            :   "        from " BUILDIDS "_buildids b, " BUILDIDS "_files f0, " BUILDIDS "_files f1, " BUILDIDS "_files fsref, "
     244                 :            :   "        " BUILDIDS "_r_sdef sd, " BUILDIDS "_r_sref sr, " BUILDIDS "_r_de sde\n"
     245                 :            :   "        where b.id = sr.buildid and f0.id = sd.file and fsref.id = sde.file and f1.id = sd.content\n"
     246                 :            :   "        and sr.artifactsrc = sd.content and sde.buildid = sr.buildid\n"
     247                 :            :   ";"
     248                 :            :   // and for startup overview counts
     249                 :            :   "drop view if exists " BUILDIDS "_stats;\n"
     250                 :            :   "create view if not exists " BUILDIDS "_stats as\n"
     251                 :            :   "          select 'file d/e' as label,count(*) as quantity from " BUILDIDS "_f_de\n"
     252                 :            :   "union all select 'file s',count(*) from " BUILDIDS "_f_s\n"
     253                 :            :   "union all select 'archive d/e',count(*) from " BUILDIDS "_r_de\n"
     254                 :            :   "union all select 'archive sref',count(*) from " BUILDIDS "_r_sref\n"
     255                 :            :   "union all select 'archive sdef',count(*) from " BUILDIDS "_r_sdef\n"
     256                 :            :   "union all select 'buildids',count(*) from " BUILDIDS "_buildids\n"
     257                 :            :   "union all select 'filenames',count(*) from " BUILDIDS "_files\n"
     258                 :            :   "union all select 'files scanned (#)',count(*) from " BUILDIDS "_file_mtime_scanned\n"
     259                 :            :   "union all select 'files scanned (mb)',coalesce(sum(size)/1024/1024,0) from " BUILDIDS "_file_mtime_scanned\n"
     260                 :            : #if SQLITE_VERSION_NUMBER >= 3016000
     261                 :            :   "union all select 'index db size (mb)',page_count*page_size/1024/1024 as size FROM pragma_page_count(), pragma_page_size()\n"
     262                 :            : #endif
     263                 :            :   ";\n"
     264                 :            : 
     265                 :            : // schema change history & garbage collection
     266                 :            : //
     267                 :            : // XXX: we could have migration queries here to bring prior-schema
     268                 :            : // data over instead of just dropping it.
     269                 :            : //
     270                 :            : // buildids9: widen the mtime_scanned table
     271                 :            :   "" // <<< we are here
     272                 :            : // buildids8: slim the sref table
     273                 :            :   "drop table if exists buildids8_f_de;\n"
     274                 :            :   "drop table if exists buildids8_f_s;\n"
     275                 :            :   "drop table if exists buildids8_r_de;\n"
     276                 :            :   "drop table if exists buildids8_r_sref;\n"
     277                 :            :   "drop table if exists buildids8_r_sdef;\n"
     278                 :            :   "drop table if exists buildids8_file_mtime_scanned;\n"
     279                 :            :   "drop table if exists buildids8_files;\n"
     280                 :            :   "drop table if exists buildids8_buildids;\n"
     281                 :            : // buildids7: separate _norm table into dense subtype tables
     282                 :            :   "drop table if exists buildids7_f_de;\n"
     283                 :            :   "drop table if exists buildids7_f_s;\n"
     284                 :            :   "drop table if exists buildids7_r_de;\n"
     285                 :            :   "drop table if exists buildids7_r_sref;\n"
     286                 :            :   "drop table if exists buildids7_r_sdef;\n"
     287                 :            :   "drop table if exists buildids7_file_mtime_scanned;\n"
     288                 :            :   "drop table if exists buildids7_files;\n"
     289                 :            :   "drop table if exists buildids7_buildids;\n"
     290                 :            : // buildids6: drop bolo/rfolo again, represent sources / rpmcontents in main table
     291                 :            :   "drop table if exists buildids6_norm;\n"
     292                 :            :   "drop table if exists buildids6_files;\n"
     293                 :            :   "drop table if exists buildids6_buildids;\n"
     294                 :            :   "drop view if exists buildids6;\n"
     295                 :            : // buildids5: redefine srcfile1 column to be '.'-less (for rpms)
     296                 :            :   "drop table if exists buildids5_norm;\n"
     297                 :            :   "drop table if exists buildids5_files;\n"
     298                 :            :   "drop table if exists buildids5_buildids;\n"
     299                 :            :   "drop table if exists buildids5_bolo;\n"
     300                 :            :   "drop table if exists buildids5_rfolo;\n"
     301                 :            :   "drop view if exists buildids5;\n"
     302                 :            : // buildids4: introduce rpmfile RFOLO
     303                 :            :   "drop table if exists buildids4_norm;\n"
     304                 :            :   "drop table if exists buildids4_files;\n"
     305                 :            :   "drop table if exists buildids4_buildids;\n"
     306                 :            :   "drop table if exists buildids4_bolo;\n"
     307                 :            :   "drop table if exists buildids4_rfolo;\n"
     308                 :            :   "drop view if exists buildids4;\n"
     309                 :            : // buildids3*: split out srcfile BOLO
     310                 :            :   "drop table if exists buildids3_norm;\n"
     311                 :            :   "drop table if exists buildids3_files;\n"
     312                 :            :   "drop table if exists buildids3_buildids;\n"
     313                 :            :   "drop table if exists buildids3_bolo;\n"
     314                 :            :   "drop view if exists buildids3;\n"
     315                 :            : // buildids2: normalized buildid and filenames into interning tables;
     316                 :            :   "drop table if exists buildids2_norm;\n"
     317                 :            :   "drop table if exists buildids2_files;\n"
     318                 :            :   "drop table if exists buildids2_buildids;\n"
     319                 :            :   "drop view if exists buildids2;\n"
     320                 :            :   // buildids1: made buildid and artifacttype NULLable, to represent cached-negative
     321                 :            : //           lookups from sources, e.g. files or rpms that contain no buildid-indexable content
     322                 :            :   "drop table if exists buildids1;\n"
     323                 :            : // buildids: original
     324                 :            :   "drop table if exists buildids;\n"
     325                 :            :   ;
     326                 :            : 
     327                 :            : static const char DEBUGINFOD_SQLITE_CLEANUP_DDL[] =
     328                 :            :   "pragma wal_checkpoint = truncate;\n" // clean out any preexisting wal file
     329                 :            :   ;
     330                 :            : 
     331                 :            : 
     332                 :            : 
     333                 :            : 
     334                 :            : /* Name and version of program.  */
     335                 :            : /* ARGP_PROGRAM_VERSION_HOOK_DEF = print_version; */ // not this simple for C++
     336                 :            : 
     337                 :            : /* Bug report address.  */
     338                 :            : ARGP_PROGRAM_BUG_ADDRESS_DEF = PACKAGE_BUGREPORT;
     339                 :            : 
     340                 :            : /* Definitions of arguments for argp functions.  */
     341                 :            : static const struct argp_option options[] =
     342                 :            :   {
     343                 :            :    { NULL, 0, NULL, 0, "Scanners:", 1 },
     344                 :            :    { "scan-file-dir", 'F', NULL, 0, "Enable ELF/DWARF file scanning.", 0 },
     345                 :            :    { "scan-rpm-dir", 'R', NULL, 0, "Enable RPM scanning.", 0 },
     346                 :            :    { "scan-deb-dir", 'U', NULL, 0, "Enable DEB scanning.", 0 },
     347                 :            :    { "scan-archive", 'Z', "EXT=CMD", 0, "Enable arbitrary archive scanning.", 0 },
     348                 :            :    // "source-oci-imageregistry"  ...
     349                 :            : 
     350                 :            :    { NULL, 0, NULL, 0, "Options:", 2 },
     351                 :            :    { "logical", 'L', NULL, 0, "Follow symlinks, default=ignore.", 0 },
     352                 :            :    { "rescan-time", 't', "SECONDS", 0, "Number of seconds to wait between rescans, 0=disable.", 0 },
     353                 :            :    { "groom-time", 'g', "SECONDS", 0, "Number of seconds to wait between database grooming, 0=disable.", 0 },
     354                 :            :    { "maxigroom", 'G', NULL, 0, "Run a complete database groom/shrink pass at startup.", 0 },
     355                 :            :    { "concurrency", 'c', "NUM", 0, "Limit scanning thread concurrency to NUM.", 0 },
     356                 :            :    { "include", 'I', "REGEX", 0, "Include files matching REGEX, default=all.", 0 },
     357                 :            :    { "exclude", 'X', "REGEX", 0, "Exclude files matching REGEX, default=none.", 0 },
     358                 :            :    { "port", 'p', "NUM", 0, "HTTP port to listen on, default 8002.", 0 },
     359                 :            :    { "database", 'd', "FILE", 0, "Path to sqlite database.", 0 },
     360                 :            :    { "ddl", 'D', "SQL", 0, "Apply extra sqlite ddl/pragma to connection.", 0 },
     361                 :            :    { "verbose", 'v', NULL, 0, "Increase verbosity.", 0 },
     362                 :            : #define ARGP_KEY_FDCACHE_FDS 0x1001
     363                 :            :    { "fdcache-fds", ARGP_KEY_FDCACHE_FDS, "NUM", 0, "Maximum number of archive files to keep in fdcache.", 0 },
     364                 :            : #define ARGP_KEY_FDCACHE_MBS 0x1002
     365                 :            :    { "fdcache-mbs", ARGP_KEY_FDCACHE_MBS, "MB", 0, "Maximum total size of archive file fdcache.", 0 },
     366                 :            : #define ARGP_KEY_FDCACHE_PREFETCH 0x1003
     367                 :            :    { "fdcache-prefetch", ARGP_KEY_FDCACHE_PREFETCH, "NUM", 0, "Number of archive files to prefetch into fdcache.", 0 },
     368                 :            : #define ARGP_KEY_FDCACHE_MINTMP 0x1004
     369                 :            :    { "fdcache-mintmp", ARGP_KEY_FDCACHE_MINTMP, "NUM", 0, "Minimum free space% on tmpdir.", 0 },
     370                 :            :    { NULL, 0, NULL, 0, NULL, 0 }
     371                 :            :   };
     372                 :            : 
     373                 :            : /* Short description of program.  */
     374                 :            : static const char doc[] = "Serve debuginfo-related content across HTTP from files under PATHs.";
     375                 :            : 
     376                 :            : /* Strings for arguments in help texts.  */
     377                 :            : static const char args_doc[] = "[PATH ...]";
     378                 :            : 
     379                 :            : /* Prototype for option handler.  */
     380                 :            : static error_t parse_opt (int key, char *arg, struct argp_state *state);
     381                 :            : 
     382                 :            : /* Data structure to communicate with argp functions.  */
     383                 :            : static struct argp argp =
     384                 :            :   {
     385                 :            :    options, parse_opt, args_doc, doc, NULL, NULL, NULL
     386                 :            :   };
     387                 :            : 
     388                 :            : 
     389                 :            : static string db_path;
     390                 :            : static sqlite3 *db;  // single connection, serialized across all our threads!
     391                 :            : static sqlite3 *dbq; // webapi query-servicing readonly connection, serialized ditto!
     392                 :            : static unsigned verbose;
     393                 :            : static volatile sig_atomic_t interrupted = 0;
     394                 :            : static volatile sig_atomic_t forced_rescan_count = 0;
     395                 :            : static volatile sig_atomic_t sigusr1 = 0;
     396                 :            : static volatile sig_atomic_t forced_groom_count = 0;
     397                 :            : static volatile sig_atomic_t sigusr2 = 0;
     398                 :            : static unsigned http_port = 8002;
     399                 :            : static unsigned rescan_s = 300;
     400                 :            : static unsigned groom_s = 86400;
     401                 :            : static bool maxigroom = false;
     402                 :            : static unsigned concurrency = std::thread::hardware_concurrency() ?: 1;
     403                 :            : static set<string> source_paths;
     404                 :            : static bool scan_files = false;
     405                 :            : static map<string,string> scan_archives;
     406                 :            : static vector<string> extra_ddl;
     407                 :            : static regex_t file_include_regex;
     408                 :            : static regex_t file_exclude_regex;
     409                 :            : static bool traverse_logical;
     410                 :            : static long fdcache_fds;
     411                 :            : static long fdcache_mbs;
     412                 :            : static long fdcache_prefetch;
     413                 :            : static long fdcache_mintmp;
     414                 :            : static string tmpdir;
     415                 :            : 
     416                 :            : static void set_metric(const string& key, double value);
     417                 :            : // static void inc_metric(const string& key);
     418                 :            : static void set_metric(const string& metric,
     419                 :            :                        const string& lname, const string& lvalue,
     420                 :            :                        double value);
     421                 :            : static void inc_metric(const string& metric,
     422                 :            :                        const string& lname, const string& lvalue);
     423                 :            : static void add_metric(const string& metric,
     424                 :            :                        const string& lname, const string& lvalue,
     425                 :            :                        double value);
     426                 :            : // static void add_metric(const string& metric, double value);
     427                 :            : 
     428                 :            : class tmp_inc_metric { // a RAII style wrapper for exception-safe scoped increment & decrement
     429                 :            :   string m, n, v;
     430                 :            : public:
     431                 :        144 :   tmp_inc_metric(const string& mname, const string& lname, const string& lvalue):
     432   [ +  -  +  - ]:        144 :     m(mname), n(lname), v(lvalue)
     433                 :            :   {
     434         [ +  - ]:        144 :     add_metric (m, n, v, 1);
     435                 :        144 :   }
     436                 :        144 :   ~tmp_inc_metric()
     437                 :        144 :   {
     438                 :        144 :     add_metric (m, n, v, -1);
     439                 :        144 :   }
     440                 :            : };
     441                 :            : 
     442                 :            : class tmp_ms_metric { // a RAII style wrapper for exception-safe scoped timing
     443                 :            :   string m, n, v;
     444                 :            :   struct timespec ts_start;
     445                 :            : public:
     446                 :       5136 :   tmp_ms_metric(const string& mname, const string& lname, const string& lvalue):
     447   [ +  -  +  - ]:       5136 :     m(mname), n(lname), v(lvalue)
     448                 :            :   {
     449                 :       5137 :     clock_gettime (CLOCK_MONOTONIC, & ts_start);
     450                 :       5138 :   }
     451                 :       5137 :   ~tmp_ms_metric()
     452                 :       5138 :   {
     453                 :            :     struct timespec ts_end;
     454                 :       5137 :     clock_gettime (CLOCK_MONOTONIC, & ts_end);
     455                 :       5137 :     double deltas = (ts_end.tv_sec - ts_start.tv_sec)
     456                 :       5137 :       + (ts_end.tv_nsec - ts_start.tv_nsec)/1.e9;
     457                 :            : 
     458                 :       5137 :     add_metric (m + "_milliseconds_sum", n, v, (deltas*1000.0));
     459                 :       5138 :     inc_metric (m + "_milliseconds_count", n, v);
     460                 :       5138 :   }
     461                 :            : };
     462                 :            : 
     463                 :            : 
     464                 :            : /* Handle program arguments.  */
     465                 :            : static error_t
     466                 :         51 : parse_opt (int key, char *arg,
     467                 :            :            struct argp_state *state __attribute__ ((unused)))
     468                 :            : {
     469                 :            :   int rc;
     470   [ +  +  +  +  :         51 :   switch (key)
          +  +  +  +  -  
          +  +  -  -  -  
          -  +  +  -  +  
                   +  + ]
     471                 :            :     {
     472                 :         10 :     case 'v': verbose ++; break;
     473         [ +  - ]:          3 :     case 'd': db_path = string(arg); break;
     474                 :          3 :     case 'p': http_port = (unsigned) atoi(arg);
     475   [ +  -  -  + ]:          3 :       if (http_port == 0 || http_port > 65535)
     476                 :          0 :         argp_failure(state, 1, EINVAL, "port number");
     477                 :          3 :       break;
     478                 :          4 :     case 'F': scan_files = true; break;
     479                 :          1 :     case 'R':
     480   [ +  -  +  -  :          1 :       scan_archives[".rpm"]="cat"; // libarchive groks rpm natively
                   +  - ]
     481                 :          1 :       break;
     482                 :          2 :     case 'U':
     483   [ +  -  +  -  :          2 :       scan_archives[".deb"]="(bsdtar -O -x -f - data.tar\\*)<";
                   +  - ]
     484   [ +  -  +  -  :          2 :       scan_archives[".ddeb"]="(bsdtar -O -x -f - data.tar\\*)<";
                   +  - ]
     485   [ +  -  +  -  :          2 :       scan_archives[".ipk"]="(bsdtar -O -x -f - data.tar\\*)<";
                   +  - ]
     486                 :            :       // .udeb too?
     487                 :          2 :       break;
     488                 :          2 :     case 'Z':
     489                 :            :       {
     490                 :          2 :         char* extension = strchr(arg, '=');
     491         [ -  + ]:          2 :         if (arg[0] == '\0')
     492                 :          0 :           argp_failure(state, 1, EINVAL, "missing EXT");
     493         [ +  + ]:          2 :         else if (extension)
     494   [ +  -  +  -  :          1 :           scan_archives[string(arg, (extension-arg))]=string(extension+1);
                   +  - ]
     495                 :            :         else
     496   [ +  -  +  -  :          1 :           scan_archives[string(arg)]=string("cat");
                   +  - ]
     497                 :            :       }
     498                 :          2 :       break;
     499                 :          2 :     case 'L':
     500                 :          2 :       traverse_logical = true;
     501                 :          2 :       break;
     502   [ #  #  #  # ]:          0 :     case 'D': extra_ddl.push_back(string(arg)); break;
     503                 :          1 :     case 't':
     504                 :          1 :       rescan_s = (unsigned) atoi(arg);
     505                 :          1 :       break;
     506                 :          1 :     case 'g':
     507                 :          1 :       groom_s = (unsigned) atoi(arg);
     508                 :          1 :       break;
     509                 :          0 :     case 'G':
     510                 :          0 :       maxigroom = true;
     511                 :          0 :       break;
     512                 :          0 :     case 'c':
     513                 :          0 :       concurrency = (unsigned) atoi(arg);
     514         [ #  # ]:          0 :       if (concurrency < 1) concurrency = 1;
     515                 :          0 :       break;
     516                 :          0 :     case 'I':
     517                 :            :       // NB: no problem with unconditional free here - an earlier failed regcomp would exit program
     518                 :          0 :       regfree (&file_include_regex);
     519                 :          0 :       rc = regcomp (&file_include_regex, arg, REG_EXTENDED|REG_NOSUB);
     520         [ #  # ]:          0 :       if (rc != 0)
     521                 :          0 :         argp_failure(state, 1, EINVAL, "regular expression");
     522                 :          0 :       break;
     523                 :          0 :     case 'X':
     524                 :          0 :       regfree (&file_exclude_regex);
     525                 :          0 :       rc = regcomp (&file_exclude_regex, arg, REG_EXTENDED|REG_NOSUB);
     526         [ #  # ]:          0 :       if (rc != 0)
     527                 :          0 :         argp_failure(state, 1, EINVAL, "regular expression");
     528                 :          0 :       break;
     529                 :          1 :     case ARGP_KEY_FDCACHE_FDS:
     530                 :          1 :       fdcache_fds = atol (arg);
     531                 :          1 :       break;
     532                 :          1 :     case ARGP_KEY_FDCACHE_MBS:
     533                 :          1 :       fdcache_mbs = atol (arg);
     534                 :          1 :       break;
     535                 :          0 :     case ARGP_KEY_FDCACHE_PREFETCH:
     536                 :          0 :       fdcache_prefetch = atol (arg);
     537                 :          0 :       break;
     538                 :          1 :     case ARGP_KEY_FDCACHE_MINTMP:
     539                 :          1 :       fdcache_mintmp = atol (arg);
     540                 :          1 :       break;
     541                 :          6 :     case ARGP_KEY_ARG:
     542   [ +  -  +  - ]:          6 :       source_paths.insert(string(arg));
     543                 :          6 :       break;
     544                 :            :       // case 'h': argp_state_help (state, stderr, ARGP_HELP_LONG|ARGP_HELP_EXIT_OK);
     545                 :         13 :     default: return ARGP_ERR_UNKNOWN;
     546                 :            :     }
     547                 :            : 
     548                 :         38 :   return 0;
     549                 :            : }
     550                 :            : 
     551                 :            : 
     552                 :            : ////////////////////////////////////////////////////////////////////////
     553                 :            : 
     554                 :            : 
     555                 :            : // represent errors that may get reported to an ostream and/or a libmicrohttpd connection
     556                 :            : 
     557                 :            : struct reportable_exception
     558                 :            : {
     559                 :            :   int code;
     560                 :            :   string message;
     561                 :            : 
     562                 :          8 :   reportable_exception(int c, const string& m): code(c), message(m) {}
     563                 :         36 :   reportable_exception(const string& m): code(503), message(m) {}
     564                 :            :   reportable_exception(): code(503), message() {}
     565                 :            : 
     566                 :            :   void report(ostream& o) const; // defined under obatched() class below
     567                 :            : 
     568                 :         12 :   MHD_RESULT mhd_send_response(MHD_Connection* c) const {
     569                 :         24 :     MHD_Response* r = MHD_create_response_from_buffer (message.size(),
     570                 :         12 :                                                        (void*) message.c_str(),
     571                 :            :                                                        MHD_RESPMEM_MUST_COPY);
     572                 :         12 :     MHD_add_response_header (r, "Content-Type", "text/plain");
     573                 :         12 :     MHD_RESULT rc = MHD_queue_response (c, code, r);
     574                 :         12 :     MHD_destroy_response (r);
     575                 :         12 :     return rc;
     576                 :            :   }
     577                 :            : };
     578                 :            : 
     579                 :            : 
     580                 :            : struct sqlite_exception: public reportable_exception
     581                 :            : {
     582                 :          2 :   sqlite_exception(int rc, const string& msg):
     583   [ +  -  +  -  :          2 :     reportable_exception(string("sqlite3 error: ") + msg + ": " + string(sqlite3_errstr(rc) ?: "?")) {
          +  -  +  -  +  
          -  +  -  +  -  
                   +  - ]
     584   [ +  -  +  -  :          2 :     inc_metric("error_count","sqlite3",sqlite3_errstr(rc));
          +  -  +  -  +  
                      - ]
     585                 :          2 :   }
     586                 :            : };
     587                 :            : 
     588                 :            : struct libc_exception: public reportable_exception
     589                 :            : {
     590                 :         31 :   libc_exception(int rc, const string& msg):
     591   [ +  -  +  -  :         31 :     reportable_exception(string("libc error: ") + msg + ": " + string(strerror(rc) ?: "?")) {
          +  -  +  -  +  
             -  +  -  +  
                      - ]
     592   [ +  -  +  -  :         31 :     inc_metric("error_count","libc",strerror(rc));
             +  -  +  - ]
     593                 :         31 :   }
     594                 :            : };
     595                 :            : 
     596                 :            : 
     597                 :            : struct archive_exception: public reportable_exception
     598                 :            : {
     599                 :          0 :   archive_exception(const string& msg):
     600   [ #  #  #  #  :          0 :     reportable_exception(string("libarchive error: ") + msg) {
                   #  # ]
     601   [ #  #  #  #  :          0 :       inc_metric("error_count","libarchive",msg);
                   #  # ]
     602                 :          0 :   }
     603                 :          0 :   archive_exception(struct archive* a, const string& msg):
     604   [ #  #  #  #  :          0 :     reportable_exception(string("libarchive error: ") + msg + ": " + string(archive_error_string(a) ?: "?")) {
          #  #  #  #  #  
          #  #  #  #  #  
                   #  # ]
     605   [ #  #  #  #  :          0 :     inc_metric("error_count","libarchive",msg + ": " + string(archive_error_string(a) ?: "?"));
          #  #  #  #  #  
          #  #  #  #  #  
                   #  # ]
     606                 :          0 :   }
     607                 :            : };
     608                 :            : 
     609                 :            : 
     610                 :            : struct elfutils_exception: public reportable_exception
     611                 :            : {
     612                 :          0 :   elfutils_exception(int rc, const string& msg):
     613   [ #  #  #  #  :          0 :     reportable_exception(string("elfutils error: ") + msg + ": " + string(elf_errmsg(rc) ?: "?")) {
          #  #  #  #  #  
          #  #  #  #  #  
                   #  # ]
     614   [ #  #  #  #  :          0 :     inc_metric("error_count","elfutils",elf_errmsg(rc));
          #  #  #  #  #  
                      # ]
     615                 :          0 :   }
     616                 :            : };
     617                 :            : 
     618                 :            : 
     619                 :            : ////////////////////////////////////////////////////////////////////////
     620                 :            : 
     621                 :            : template <typename Payload>
     622                 :            : class workq
     623                 :            : {
     624                 :            :   set<Payload> q; // eliminate duplicates
     625                 :            :   mutex mtx;
     626                 :            :   condition_variable cv;
     627                 :            :   bool dead;
     628                 :            :   unsigned idlers;
     629                 :            : 
     630                 :            : public:
     631                 :          3 :   workq() { dead = false; idlers = 0; }
     632                 :          3 :   ~workq() {}
     633                 :            : 
     634                 :        173 :   void push_back(const Payload& p)
     635                 :            :   {
     636         [ +  - ]:        346 :     unique_lock<mutex> lock(mtx);
     637         [ +  - ]:        173 :     q.insert (p);
     638   [ +  -  +  -  :        173 :     set_metric("thread_work_pending","role","scan", q.size());
             +  -  +  - ]
     639                 :        173 :     cv.notify_all();
     640                 :        173 :   }
     641                 :            : 
     642                 :            :   // kill this workqueue, wake up all idlers / scanners
     643                 :          3 :   void nuke() {
     644         [ +  - ]:          6 :     unique_lock<mutex> lock(mtx);
     645                 :            :     // optional: q.clear();
     646                 :          3 :     dead = true;
     647                 :          3 :     cv.notify_all();
     648                 :          3 :   }
     649                 :            : 
     650                 :            :   // clear the workqueue, when scanning is interrupted with USR2
     651                 :          0 :   void clear() {
     652         [ #  # ]:          0 :     unique_lock<mutex> lock(mtx);
     653                 :          0 :     q.clear();
     654   [ #  #  #  #  :          0 :     set_metric("thread_work_pending","role","scan", q.size());
             #  #  #  # ]
     655                 :          0 :     cv.notify_all(); // maybe wake up waiting idlers
     656                 :          0 :   }
     657                 :            : 
     658                 :            :   // block this scanner thread until there is work to do and no active
     659                 :        185 :   bool wait_front (Payload& p)
     660                 :            :   {
     661         [ +  - ]:        370 :     unique_lock<mutex> lock(mtx);
     662   [ +  +  +  +  :        687 :     while (!dead && (q.size() == 0 || idlers > 0))
             +  +  +  + ]
     663                 :        502 :       cv.wait(lock);
     664         [ +  + ]:        185 :     if (dead)
     665                 :         12 :       return false;
     666                 :            :     else
     667                 :            :       {
     668         [ +  - ]:        173 :         p = * q.begin();
     669         [ +  - ]:        173 :         q.erase (q.begin());
     670   [ +  -  +  -  :        173 :         set_metric("thread_work_pending","role","scan", q.size());
             +  -  +  - ]
     671         [ +  + ]:        173 :         if (q.size() == 0)
     672                 :         92 :           cv.notify_all(); // maybe wake up waiting idlers
     673                 :        173 :         return true;
     674                 :            :       }
     675                 :            :   }
     676                 :            : 
     677                 :            :   // block this idler thread until there is no work to do
     678                 :         70 :   void wait_idle ()
     679                 :            :   {
     680         [ +  - ]:         70 :     unique_lock<mutex> lock(mtx);
     681                 :         70 :     cv.notify_all(); // maybe wake up waiting scanners
     682   [ +  +  +  +  :         73 :     while (!dead && (q.size() != 0))
                   +  + ]
     683                 :          3 :       cv.wait(lock);
     684                 :         70 :     idlers ++;
     685                 :         70 :   }
     686                 :            : 
     687                 :         67 :   void done_idle ()
     688                 :            :   {
     689         [ +  - ]:        134 :     unique_lock<mutex> lock(mtx);
     690                 :         67 :     idlers --;
     691                 :         67 :     cv.notify_all(); // maybe wake up waiting scanners, but probably not (shutting down)
     692                 :         67 :   }
     693                 :            : };
     694                 :            : 
     695                 :            : typedef struct stat stat_t;
     696                 :            : typedef pair<string,stat_t> scan_payload;
     697                 :        364 : inline bool operator< (const scan_payload& a, const scan_payload& b)
     698                 :            : {
     699                 :        364 :   return a.first < b.first; // don't bother compare the stat fields
     700                 :            : }
     701                 :            : static workq<scan_payload> scanq; // just a single one
     702                 :            : // producer & idler: thread_main_fts_source_paths()
     703                 :            : // consumer: thread_main_scanner()
     704                 :            : // idler: thread_main_groom()
     705                 :            : 
     706                 :            : 
     707                 :            : 
     708                 :            : ////////////////////////////////////////////////////////////////////////
     709                 :            : 
     710                 :            : 
     711                 :            : // Print a standard timestamp.
     712                 :            : static ostream&
     713                 :       1734 : timestamp (ostream &o)
     714                 :            : {
     715                 :            :   char datebuf[80];
     716                 :       1734 :   char *now2 = NULL;
     717                 :       1734 :   time_t now_t = time(NULL);
     718                 :       1734 :   struct tm *now = gmtime (&now_t);
     719         [ +  - ]:       1734 :   if (now)
     720                 :            :     {
     721                 :       1734 :       (void) strftime (datebuf, sizeof (datebuf), "%c", now);
     722                 :       1734 :       now2 = datebuf;
     723                 :            :     }
     724                 :            : 
     725                 :            :   return o << "[" << (now2 ? now2 : "") << "] "
     726   [ +  -  +  -  :       1734 :            << "(" << getpid () << "/" << tid() << "): ";
          +  -  +  -  +  
          -  +  -  +  -  
             +  -  +  - ]
     727                 :            : }
     728                 :            : 
     729                 :            : 
     730                 :            : // A little class that impersonates an ostream to the extent that it can
     731                 :            : // take << streaming operations.  It batches up the bits into an internal
     732                 :            : // stringstream until it is destroyed; then flushes to the original ostream.
     733                 :            : // It adds a timestamp
     734                 :            : class obatched
     735                 :            : {
     736                 :            : private:
     737                 :            :   ostream& o;
     738                 :            :   stringstream stro;
     739                 :            :   static mutex lock;
     740                 :            : public:
     741                 :       1734 :   obatched(ostream& oo, bool timestamp_p = true): o(oo)
     742                 :            :   {
     743         [ +  - ]:       1734 :     if (timestamp_p)
     744         [ +  - ]:       1734 :       timestamp(stro);
     745                 :       1734 :   }
     746                 :       1734 :   ~obatched()
     747                 :       1734 :   {
     748                 :       3468 :     unique_lock<mutex> do_not_cross_the_streams(obatched::lock);
     749                 :       1734 :     o << stro.str();
     750                 :       1734 :     o.flush();
     751                 :       1734 :   }
     752                 :            :   operator ostream& () { return stro; }
     753                 :       1734 :   template <typename T> ostream& operator << (const T& t) { stro << t; return stro; }
     754                 :            : };
     755                 :            : mutex obatched::lock; // just the one, since cout/cerr iostreams are not thread-safe
     756                 :            : 
     757                 :            : 
     758                 :         43 : void reportable_exception::report(ostream& o) const {
     759   [ +  -  +  - ]:         43 :   obatched(o) << message << endl;
     760                 :         43 : }
     761                 :            : 
     762                 :            : 
     763                 :            : ////////////////////////////////////////////////////////////////////////
     764                 :            : 
     765                 :            : 
     766                 :            : // RAII style sqlite prepared-statement holder that matches { } block lifetime
     767                 :            : 
     768                 :            : struct sqlite_ps
     769                 :            : {
     770                 :            : private:
     771                 :            :   sqlite3* db;
     772                 :            :   const string nickname;
     773                 :            :   const string sql;
     774                 :            :   sqlite3_stmt *pp;
     775                 :            : 
     776                 :            :   sqlite_ps(const sqlite_ps&); // make uncopyable
     777                 :            :   sqlite_ps& operator=(const sqlite_ps &); // make unassignable
     778                 :            : 
     779                 :            : public:
     780         [ +  - ]:        278 :   sqlite_ps (sqlite3* d, const string& n, const string& s): db(d), nickname(n), sql(s) {
     781                 :            :     // tmp_ms_metric tick("sqlite3","prep",nickname);
     782         [ -  + ]:        278 :     if (verbose > 4)
     783   [ #  #  #  #  :          0 :       obatched(clog) << nickname << " prep " << sql << endl;
          #  #  #  #  #  
                      # ]
     784         [ +  - ]:        278 :     int rc = sqlite3_prepare_v2 (db, sql.c_str(), -1 /* to \0 */, & this->pp, NULL);
     785         [ -  + ]:        278 :     if (rc != SQLITE_OK)
     786   [ #  #  #  # ]:          0 :       throw sqlite_exception(rc, "prepare " + sql);
     787                 :        278 :   }
     788                 :            : 
     789                 :       2605 :   sqlite_ps& reset()
     790                 :            :   {
     791   [ +  -  +  -  :       7814 :     tmp_ms_metric tick("sqlite3","reset",nickname);
                   +  - ]
     792         [ +  - ]:       2605 :     sqlite3_reset(this->pp);
     793                 :       5209 :     return *this;
     794                 :            :   }
     795                 :            : 
     796                 :       3356 :   sqlite_ps& bind(int parameter, const string& str)
     797                 :            :   {
     798         [ -  + ]:       3356 :     if (verbose > 4)
     799   [ #  #  #  #  :          0 :       obatched(clog) << nickname << " bind " << parameter << "=" << str << endl;
          #  #  #  #  #  
                #  #  # ]
     800                 :       3356 :     int rc = sqlite3_bind_text (this->pp, parameter, str.c_str(), -1, SQLITE_TRANSIENT);
     801         [ -  + ]:       3356 :     if (rc != SQLITE_OK)
     802   [ #  #  #  # ]:          0 :       throw sqlite_exception(rc, "sqlite3 bind");
     803                 :       3356 :     return *this;
     804                 :            :   }
     805                 :            : 
     806                 :       1181 :   sqlite_ps& bind(int parameter, int64_t value)
     807                 :            :   {
     808         [ -  + ]:       1181 :     if (verbose > 4)
     809   [ #  #  #  #  :          0 :       obatched(clog) << nickname << " bind " << parameter << "=" << value << endl;
          #  #  #  #  #  
                #  #  # ]
     810                 :       1181 :     int rc = sqlite3_bind_int64 (this->pp, parameter, value);
     811         [ -  + ]:       1180 :     if (rc != SQLITE_OK)
     812   [ #  #  #  # ]:          0 :       throw sqlite_exception(rc, "sqlite3 bind");
     813                 :       1180 :     return *this;
     814                 :            :   }
     815                 :            : 
     816                 :            :   sqlite_ps& bind(int parameter)
     817                 :            :   {
     818                 :            :     if (verbose > 4)
     819                 :            :       obatched(clog) << nickname << " bind " << parameter << "=" << "NULL" << endl;
     820                 :            :     int rc = sqlite3_bind_null (this->pp, parameter);
     821                 :            :     if (rc != SQLITE_OK)
     822                 :            :       throw sqlite_exception(rc, "sqlite3 bind");
     823                 :            :     return *this;
     824                 :            :   }
     825                 :            : 
     826                 :            : 
     827                 :       1932 :   void step_ok_done() {
     828   [ +  -  +  -  :       7728 :     tmp_ms_metric tick("sqlite3","step_done",nickname);
                   +  - ]
     829         [ +  - ]:       1932 :     int rc = sqlite3_step (this->pp);
     830         [ -  + ]:       1932 :     if (verbose > 4)
     831   [ #  #  #  #  :          0 :       obatched(clog) << nickname << " step-ok-done(" << sqlite3_errstr(rc) << ") " << sql << endl;
          #  #  #  #  #  
          #  #  #  #  #  
                   #  # ]
     832   [ +  -  +  +  :       1932 :     if (rc != SQLITE_OK && rc != SQLITE_DONE && rc != SQLITE_ROW)
                   +  + ]
     833   [ +  -  +  - ]:          1 :       throw sqlite_exception(rc, "sqlite3 step");
     834         [ +  - ]:       1931 :     (void) sqlite3_reset (this->pp);
     835                 :       1931 :   }
     836                 :            : 
     837                 :            : 
     838                 :        601 :   int step() {
     839   [ +  -  +  -  :       1803 :     tmp_ms_metric tick("sqlite3","step",nickname);
                   +  - ]
     840         [ +  - ]:        601 :     int rc = sqlite3_step (this->pp);
     841         [ -  + ]:        601 :     if (verbose > 4)
     842   [ #  #  #  #  :          0 :       obatched(clog) << nickname << " step(" << sqlite3_errstr(rc) << ") " << sql << endl;
          #  #  #  #  #  
          #  #  #  #  #  
                   #  # ]
     843                 :       1202 :     return rc;
     844                 :            :   }
     845                 :            : 
     846                 :        278 :   ~sqlite_ps () { sqlite3_finalize (this->pp); }
     847                 :       1008 :   operator sqlite3_stmt* () { return this->pp; }
     848                 :            : };
     849                 :            : 
     850                 :            : 
     851                 :            : ////////////////////////////////////////////////////////////////////////
     852                 :            : 
     853                 :            : // RAII style templated autocloser
     854                 :            : 
     855                 :            : template <class Payload, class Ignore>
     856                 :            : struct defer_dtor
     857                 :            : {
     858                 :            : public:
     859                 :            :   typedef Ignore (*dtor_fn) (Payload);
     860                 :            : 
     861                 :            : private:
     862                 :            :   Payload p;
     863                 :            :   dtor_fn fn;
     864                 :            : 
     865                 :            : public:
     866                 :        342 :   defer_dtor(Payload _p, dtor_fn _fn): p(_p), fn(_fn) {}
     867                 :        342 :   ~defer_dtor() { (void) (*fn)(p); }
     868                 :            : 
     869                 :            : private:
     870                 :            :   defer_dtor(const defer_dtor<Payload,Ignore>&); // make uncopyable
     871                 :            :   defer_dtor& operator=(const defer_dtor<Payload,Ignore> &); // make unassignable
     872                 :            : };
     873                 :            : 
     874                 :            : 
     875                 :            : 
     876                 :            : ////////////////////////////////////////////////////////////////////////
     877                 :            : 
     878                 :            : 
     879                 :            : static string
     880                 :        292 : header_censor(const string& str)
     881                 :            : {
     882                 :        292 :   string y;
     883         [ +  + ]:       3492 :   for (auto&& x : str)
     884                 :            :     {
     885   [ +  +  +  +  :       3200 :       if (isalnum(x) || x == '/' || x == '.' || x == ',' || x == '_' || x == ':')
          +  +  +  +  +  
                -  -  + ]
     886         [ +  - ]:       3199 :         y += x;
     887                 :            :     }
     888                 :        292 :   return y;
     889                 :            : }
     890                 :            : 
     891                 :            : 
     892                 :            : static string
     893                 :        146 : conninfo (struct MHD_Connection * conn)
     894                 :            : {
     895                 :            :   char hostname[256]; // RFC1035
     896                 :            :   char servname[256];
     897                 :        146 :   int sts = -1;
     898                 :            : 
     899         [ -  + ]:        146 :   if (conn == 0)
     900         [ #  # ]:          0 :     return "internal";
     901                 :            : 
     902                 :            :   /* Look up client address data. */
     903         [ +  - ]:        146 :   const union MHD_ConnectionInfo *u = MHD_get_connection_info (conn,
     904                 :            :                                                                MHD_CONNECTION_INFO_CLIENT_ADDRESS);
     905         [ +  - ]:        146 :   struct sockaddr *so = u ? u->client_addr : 0;
     906                 :            : 
     907   [ +  -  +  - ]:        146 :   if (so && so->sa_family == AF_INET) {
     908         [ +  - ]:        146 :     sts = getnameinfo (so, sizeof (struct sockaddr_in), hostname, sizeof (hostname), servname,
     909                 :            :                        sizeof (servname), NI_NUMERICHOST | NI_NUMERICSERV);
     910   [ #  #  #  # ]:          0 :   } else if (so && so->sa_family == AF_INET6) {
     911         [ #  # ]:          0 :     sts = getnameinfo (so, sizeof (struct sockaddr_in6), hostname, sizeof (hostname),
     912                 :            :                        servname, sizeof (servname), NI_NUMERICHOST | NI_NUMERICSERV);
     913                 :            :   }
     914         [ -  + ]:        146 :   if (sts != 0) {
     915                 :          0 :     hostname[0] = servname[0] = '\0';
     916                 :            :   }
     917                 :            : 
     918                 :            :   // extract headers relevant to administration
     919   [ +  -  +  - ]:        146 :   const char* user_agent = MHD_lookup_connection_value (conn, MHD_HEADER_KIND, "User-Agent") ?: "";
     920   [ +  -  +  + ]:        146 :   const char* x_forwarded_for = MHD_lookup_connection_value (conn, MHD_HEADER_KIND, "X-Forwarded-For") ?: "";
     921                 :            :   // NB: these are untrustworthy, beware if machine-processing log files
     922                 :            : 
     923   [ +  -  +  -  :        438 :   return string(hostname) + string(":") + string(servname) +
          +  -  +  -  +  
                      - ]
     924   [ +  -  +  -  :        584 :     string(" UA:") + header_censor(string(user_agent)) +
          +  -  +  -  +  
                      - ]
     925   [ +  -  +  -  :        438 :     string(" XFF:") + header_censor(string(x_forwarded_for));
          +  -  +  -  +  
                      - ]
     926                 :            : }
     927                 :            : 
     928                 :            : 
     929                 :            : 
     930                 :            : ////////////////////////////////////////////////////////////////////////
     931                 :            : 
     932                 :            : 
     933                 :            : static void
     934                 :         61 : add_mhd_last_modified (struct MHD_Response *resp, time_t mtime)
     935                 :            : {
     936                 :         61 :   struct tm *now = gmtime (&mtime);
     937         [ +  - ]:         61 :   if (now != NULL)
     938                 :            :     {
     939                 :            :       char datebuf[80];
     940                 :         61 :       size_t rc = strftime (datebuf, sizeof (datebuf), "%a, %d %b %Y %T GMT", now);
     941   [ +  -  +  - ]:         61 :       if (rc > 0 && rc < sizeof (datebuf))
     942         [ +  - ]:         61 :         (void) MHD_add_response_header (resp, "Last-Modified", datebuf);
     943                 :            :     }
     944                 :            : 
     945                 :         61 :   (void) MHD_add_response_header (resp, "Cache-Control", "public");
     946                 :         61 : }
     947                 :            : 
     948                 :            : 
     949                 :            : 
     950                 :            : static struct MHD_Response*
     951                 :         15 : handle_buildid_f_match (bool internal_req_t,
     952                 :            :                         int64_t b_mtime,
     953                 :            :                         const string& b_source0,
     954                 :            :                         int *result_fd)
     955                 :            : {
     956                 :            :   (void) internal_req_t; // ignored
     957         [ +  - ]:         15 :   int fd = open(b_source0.c_str(), O_RDONLY);
     958         [ -  + ]:         15 :   if (fd < 0)
     959   [ #  #  #  #  :          0 :     throw libc_exception (errno, string("open ") + b_source0);
                   #  # ]
     960                 :            : 
     961                 :            :   // NB: use manual close(2) in error case instead of defer_dtor, because
     962                 :            :   // in the normal case, we want to hand the fd over to libmicrohttpd for
     963                 :            :   // file transfer.
     964                 :            : 
     965                 :            :   struct stat s;
     966                 :         15 :   int rc = fstat(fd, &s);
     967         [ -  + ]:         15 :   if (rc < 0)
     968                 :            :     {
     969         [ #  # ]:          0 :       close(fd);
     970   [ #  #  #  #  :          0 :       throw libc_exception (errno, string("fstat ") + b_source0);
                   #  # ]
     971                 :            :     }
     972                 :            : 
     973         [ -  + ]:         15 :   if ((int64_t) s.st_mtime != b_mtime)
     974                 :            :     {
     975         [ #  # ]:          0 :       if (verbose)
     976   [ #  #  #  #  :          0 :         obatched(clog) << "mtime mismatch for " << b_source0 << endl;
             #  #  #  # ]
     977         [ #  # ]:          0 :       close(fd);
     978                 :          0 :       return 0;
     979                 :            :     }
     980                 :            : 
     981   [ +  -  +  -  :         15 :   inc_metric ("http_responses_total","result","file");
             +  -  +  - ]
     982         [ +  - ]:         15 :   struct MHD_Response* r = MHD_create_response_from_fd ((uint64_t) s.st_size, fd);
     983         [ -  + ]:         15 :   if (r == 0)
     984                 :            :     {
     985         [ #  # ]:          0 :       if (verbose)
     986   [ #  #  #  #  :          0 :         obatched(clog) << "cannot create fd-response for " << b_source0 << endl;
             #  #  #  # ]
     987         [ #  # ]:          0 :       close(fd);
     988                 :            :     }
     989                 :            :   else
     990                 :            :     {
     991         [ +  - ]:         15 :       MHD_add_response_header (r, "Content-Type", "application/octet-stream");
     992         [ +  - ]:         15 :       add_mhd_last_modified (r, s.st_mtime);
     993         [ +  - ]:         15 :       if (verbose > 1)
     994   [ +  -  +  -  :         15 :         obatched(clog) << "serving file " << b_source0 << endl;
             +  -  +  - ]
     995                 :            :       /* libmicrohttpd will close it. */
     996         [ +  - ]:         15 :       if (result_fd)
     997                 :         15 :         *result_fd = fd;
     998                 :            :     }
     999                 :            : 
    1000                 :         15 :   return r;
    1001                 :            : }
    1002                 :            : 
    1003                 :            : 
    1004                 :            : // quote all questionable characters of str for safe passage through a sh -c expansion.
    1005                 :            : static string
    1006                 :          6 : shell_escape(const string& str)
    1007                 :            : {
    1008                 :          6 :   string y;
    1009         [ +  + ]:        508 :   for (auto&& x : str)
    1010                 :            :     {
    1011   [ +  +  +  + ]:        502 :       if (! isalnum(x) && x != '/')
    1012         [ +  - ]:         52 :         y += "\\";
    1013         [ +  - ]:        502 :       y += x;
    1014                 :            :     }
    1015                 :          6 :   return y;
    1016                 :            : }
    1017                 :            : 
    1018                 :            : 
    1019                 :            : // PR25548: Perform POSIX / RFC3986 style path canonicalization on the input string.
    1020                 :            : //
    1021                 :            : // Namely:
    1022                 :            : //    //         ->   /
    1023                 :            : //    /foo/../   ->   /
    1024                 :            : //    /./        ->   /
    1025                 :            : //
    1026                 :            : // This mapping is done on dwarf-side source path names, which may
    1027                 :            : // include these constructs, so we can deal with debuginfod clients
    1028                 :            : // that accidentally canonicalize the paths.
    1029                 :            : //
    1030                 :            : // realpath(3) is close but not quite right, because it also resolves
    1031                 :            : // symbolic links.  Symlinks at the debuginfod server have nothing to
    1032                 :            : // do with the build-time symlinks, thus they must not be considered.
    1033                 :            : //
    1034                 :            : // see also curl Curl_dedotdotify() aka RFC3986, which we mostly follow here
    1035                 :            : // see also libc __realpath()
    1036                 :            : // see also llvm llvm::sys::path::remove_dots()
    1037                 :            : static string
    1038                 :        534 : canon_pathname (const string& input)
    1039                 :            : {
    1040         [ +  - ]:       1068 :   string i = input; // 5.2.4 (1)
    1041                 :        534 :   string o;
    1042                 :            : 
    1043         [ +  + ]:       3392 :   while (i.size() != 0)
    1044                 :            :     {
    1045                 :            :       // 5.2.4 (2) A
    1046   [ +  -  -  + ]:       2858 :       if (i.substr(0,3) == "../")
    1047         [ #  # ]:          0 :         i = i.substr(3);
    1048   [ +  -  -  + ]:       2858 :       else if(i.substr(0,2) == "./")
    1049         [ #  # ]:          0 :         i = i.substr(2);
    1050                 :            : 
    1051                 :            :       // 5.2.4 (2) B
    1052   [ +  -  +  + ]:       2858 :       else if (i.substr(0,3) == "/./")
    1053         [ +  - ]:         55 :         i = i.substr(2);
    1054         [ -  + ]:       2803 :       else if (i == "/.")
    1055         [ #  # ]:          0 :         i = ""; // no need to handle "/." complete-path-segment case; we're dealing with file names
    1056                 :            : 
    1057                 :            :       // 5.2.4 (2) C
    1058   [ +  -  +  + ]:       2803 :       else if (i.substr(0,4) == "/../") {
    1059         [ +  - ]:         78 :         i = i.substr(3);
    1060         [ +  - ]:         78 :         string::size_type sl = o.rfind("/");
    1061         [ +  - ]:         78 :         if (sl != string::npos)
    1062         [ +  - ]:         78 :           o = o.substr(0, sl);
    1063                 :            :         else
    1064         [ #  # ]:          0 :           o = "";
    1065         [ -  + ]:       2725 :       } else if (i == "/..")
    1066         [ #  # ]:          0 :         i = ""; // no need to handle "/.." complete-path-segment case; we're dealing with file names
    1067                 :            : 
    1068                 :            :       // 5.2.4 (2) D
    1069                 :            :       // no need to handle these cases; we're dealing with file names
    1070         [ -  + ]:       2725 :       else if (i == ".")
    1071         [ #  # ]:          0 :         i = "";
    1072         [ -  + ]:       2725 :       else if (i == "..")
    1073         [ #  # ]:          0 :         i = "";
    1074                 :            : 
    1075                 :            :       // POSIX special: map // to /
    1076   [ +  -  +  + ]:       2725 :       else if (i.substr(0,2) == "//")
    1077         [ +  - ]:         14 :         i = i.substr(1);
    1078                 :            : 
    1079                 :            :       // 5.2.4 (2) E
    1080                 :            :       else {
    1081   [ +  -  +  - ]:       2711 :         string::size_type next_slash = i.find("/", (i[0]=='/' ? 1 : 0)); // skip first slash
    1082   [ +  -  +  - ]:       2711 :         o += i.substr(0, next_slash);
    1083         [ +  + ]:       2711 :         if (next_slash == string::npos)
    1084         [ +  - ]:        534 :           i = "";
    1085                 :            :         else
    1086         [ +  - ]:       2177 :           i = i.substr(next_slash);
    1087                 :            :       }
    1088                 :            :     }
    1089                 :            : 
    1090                 :       1068 :   return o;
    1091                 :            : }
    1092                 :            : 
    1093                 :            : 
    1094                 :            : // Estimate available free space for a given filesystem via statfs(2).
    1095                 :            : // Return true if the free fraction is known to be smaller than the
    1096                 :            : // given minimum percentage.  Also update a related metric.
    1097                 :        448 : bool statfs_free_enough_p(const string& path, const string& label, long minfree = 0)
    1098                 :            : {
    1099                 :            :   struct statfs sfs;
    1100                 :        448 :   int rc = statfs(path.c_str(), &sfs);
    1101         [ +  + ]:        449 :   if (rc == 0)
    1102                 :            :     {
    1103                 :        448 :       double s = (double) sfs.f_bavail / (double) sfs.f_blocks;
    1104   [ +  -  +  -  :        448 :       set_metric("filesys_free_ratio","purpose",label, s);
                   +  - ]
    1105                 :        448 :       return ((s * 100.0) < minfree);
    1106                 :            :     }
    1107                 :          1 :   return false;
    1108                 :            : }
    1109                 :            : 
    1110                 :            : 
    1111                 :            : 
    1112                 :            : // A map-like class that owns a cache of file descriptors (indexed by
    1113                 :            : // file / content names).
    1114                 :            : //
    1115                 :            : // If only it could use fd's instead of file names ... but we can't
    1116                 :            : // dup(2) to create independent descriptors for the same unlinked
    1117                 :            : // files, so would have to use some goofy linux /proc/self/fd/%d
    1118                 :            : // hack such as the following
    1119                 :            : 
    1120                 :            : #if 0
    1121                 :            : int superdup(int fd)
    1122                 :            : {
    1123                 :            : #ifdef __linux__
    1124                 :            :   char *fdpath = NULL;
    1125                 :            :   int rc = asprintf(& fdpath, "/proc/self/fd/%d", fd);
    1126                 :            :   int newfd;
    1127                 :            :   if (rc >= 0)
    1128                 :            :     newfd = open(fdpath, O_RDONLY);
    1129                 :            :   else
    1130                 :            :     newfd = -1;
    1131                 :            :   free (fdpath);
    1132                 :            :   return newfd;
    1133                 :            : #else
    1134                 :            :   return -1;
    1135                 :            : #endif
    1136                 :            : }
    1137                 :            : #endif
    1138                 :            : 
    1139                 :            : class libarchive_fdcache
    1140                 :            : {
    1141                 :            : private:
    1142                 :            :   mutex fdcache_lock;
    1143                 :            : 
    1144                 :            :   struct fdcache_entry
    1145                 :            :   {
    1146                 :            :     string archive;
    1147                 :            :     string entry;
    1148                 :            :     string fd;
    1149                 :            :     double fd_size_mb; // slightly rounded up megabytes
    1150                 :            :   };
    1151                 :            :   deque<fdcache_entry> lru; // @head: most recently used
    1152                 :            :   long max_fds;
    1153                 :            :   long max_mbs;
    1154                 :            : 
    1155                 :            : public:
    1156                 :        111 :   void set_metrics()
    1157                 :            :   {
    1158                 :        111 :     double total_mb = 0.0;
    1159         [ +  + ]:        280 :     for (auto i = lru.begin(); i < lru.end(); i++)
    1160                 :        169 :       total_mb += i->fd_size_mb;
    1161   [ +  -  +  - ]:        111 :     set_metric("fdcache_bytes", (int64_t)(total_mb*1024.0*1024.0));
    1162   [ +  -  +  - ]:        111 :     set_metric("fdcache_count", lru.size());
    1163                 :        111 :   }
    1164                 :            : 
    1165                 :         53 :   void intern(const string& a, const string& b, string fd, off_t sz, bool front_p)
    1166                 :            :   {
    1167                 :            :     {
    1168         [ +  - ]:        106 :       unique_lock<mutex> lock(fdcache_lock);
    1169         [ +  + ]:        120 :       for (auto i = lru.begin(); i < lru.end(); i++) // nuke preexisting copy
    1170                 :            :         {
    1171   [ +  +  -  +  :         67 :           if (i->archive == a && i->entry == b)
                   -  + ]
    1172                 :            :             {
    1173                 :          0 :               unlink (i->fd.c_str());
    1174         [ #  # ]:          0 :               lru.erase(i);
    1175   [ #  #  #  #  :          0 :               inc_metric("fdcache_op_count","op","dequeue");
             #  #  #  # ]
    1176                 :          0 :               break; // must not continue iterating
    1177                 :            :             }
    1178                 :            :         }
    1179                 :         53 :       double mb = (sz+65535)/1048576.0; // round up to 64K block
    1180   [ +  -  +  -  :        106 :       fdcache_entry n = { a, b, fd, mb };
                   +  - ]
    1181         [ +  + ]:         53 :       if (front_p)
    1182                 :            :         {
    1183   [ +  -  +  -  :         34 :           inc_metric("fdcache_op_count","op","enqueue_front");
             +  -  +  - ]
    1184         [ +  - ]:         34 :           lru.push_front(n);
    1185                 :            :         }
    1186                 :            :       else
    1187                 :            :         {
    1188   [ +  -  +  -  :         19 :           inc_metric("fdcache_op_count","op","enqueue_back");
             +  -  +  - ]
    1189         [ +  - ]:         19 :           lru.push_back(n);
    1190                 :            :         }
    1191         [ +  + ]:         53 :       if (verbose > 3)
    1192   [ +  -  +  - ]:         96 :         obatched(clog) << "fdcache interned a=" << a << " b=" << b
    1193   [ +  -  +  -  :         48 :                        << " fd=" << fd << " mb=" << mb << " front=" << front_p << endl;
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
                      - ]
    1194                 :            :     }
    1195                 :         53 :     set_metrics();
    1196                 :            : 
    1197                 :            :     // NB: we age the cache at lookup time too
    1198   [ +  -  +  -  :         53 :     if (statfs_free_enough_p(tmpdir, "tmpdir", fdcache_mintmp))
                   -  + ]
    1199                 :            :       {
    1200   [ #  #  #  #  :          0 :         inc_metric("fdcache_op_count","op","emerg-flush");
             #  #  #  # ]
    1201   [ #  #  #  # ]:          0 :         obatched(clog) << "fdcache emergency flush for filling tmpdir" << endl;
    1202                 :          0 :         this->limit(0, 0); // emergency flush
    1203                 :            :       }
    1204         [ +  + ]:         53 :     else if (front_p)
    1205                 :         34 :       this->limit(max_fds, max_mbs); // age cache if required
    1206                 :         53 :   }
    1207                 :            : 
    1208                 :         45 :   int lookup(const string& a, const string& b)
    1209                 :            :   {
    1210                 :         45 :     int fd = -1;
    1211                 :            :     {
    1212         [ +  - ]:         90 :       unique_lock<mutex> lock(fdcache_lock);
    1213         [ +  + ]:         95 :       for (auto i = lru.begin(); i < lru.end(); i++)
    1214                 :            :         {
    1215   [ +  +  +  +  :         61 :           if (i->archive == a && i->entry == b)
                   +  + ]
    1216                 :            :             { // found it; move it to head of lru
    1217         [ +  - ]:         22 :               fdcache_entry n = *i;
    1218         [ +  - ]:         11 :               lru.erase(i); // invalidates i, so no more iteration!
    1219         [ +  - ]:         11 :               lru.push_front(n);
    1220   [ +  -  +  -  :         11 :               inc_metric("fdcache_op_count","op","requeue_front");
             +  -  +  - ]
    1221         [ +  - ]:         11 :               fd = open(n.fd.c_str(), O_RDONLY); // NB: no problem if dup() fails; looks like cache miss
    1222                 :         11 :               break;
    1223                 :            :             }
    1224                 :            :         }
    1225                 :            :     }
    1226                 :            : 
    1227   [ +  -  +  -  :         45 :     if (statfs_free_enough_p(tmpdir, "tmpdir", fdcache_mintmp))
                   -  + ]
    1228                 :            :       {
    1229   [ #  #  #  #  :          0 :         inc_metric("fdcache_op_count","op","emerg-flush");
             #  #  #  # ]
    1230         [ #  # ]:          0 :         obatched(clog) << "fdcache emergency flush for filling tmpdir";
    1231                 :          0 :         this->limit(0, 0); // emergency flush
    1232                 :            :       }
    1233         [ +  + ]:         45 :     else if (fd >= 0)
    1234                 :         11 :       this->limit(max_fds, max_mbs); // age cache if required
    1235                 :            : 
    1236                 :         45 :     return fd;
    1237                 :            :   }
    1238                 :            : 
    1239                 :         53 :   int probe(const string& a, const string& b) // just a cache residency check - don't modify LRU state, don't open
    1240                 :            :   {
    1241         [ +  - ]:        106 :     unique_lock<mutex> lock(fdcache_lock);
    1242         [ +  + ]:        120 :     for (auto i = lru.begin(); i < lru.end(); i++)
    1243                 :            :       {
    1244   [ +  +  -  +  :         67 :         if (i->archive == a && i->entry == b)
                   -  + ]
    1245                 :            :           {
    1246   [ #  #  #  #  :          0 :             inc_metric("fdcache_op_count","op","probe_hit");
             #  #  #  # ]
    1247                 :          0 :             return true;
    1248                 :            :           }
    1249                 :            :       }
    1250   [ +  -  +  -  :         53 :     inc_metric("fdcache_op_count","op","probe_miss");
             +  -  +  - ]
    1251                 :         53 :     return false;
    1252                 :            :   }
    1253                 :            : 
    1254                 :          0 :   void clear(const string& a, const string& b)
    1255                 :            :   {
    1256         [ #  # ]:          0 :     unique_lock<mutex> lock(fdcache_lock);
    1257         [ #  # ]:          0 :     for (auto i = lru.begin(); i < lru.end(); i++)
    1258                 :            :       {
    1259   [ #  #  #  #  :          0 :         if (i->archive == a && i->entry == b)
                   #  # ]
    1260                 :            :           { // found it; move it to head of lru
    1261         [ #  # ]:          0 :             fdcache_entry n = *i;
    1262         [ #  # ]:          0 :             lru.erase(i); // invalidates i, so no more iteration!
    1263   [ #  #  #  #  :          0 :             inc_metric("fdcache_op_count","op","clear");
             #  #  #  # ]
    1264                 :          0 :             unlink (n.fd.c_str());
    1265         [ #  # ]:          0 :             set_metrics();
    1266                 :          0 :             return;
    1267                 :            :           }
    1268                 :            :       }
    1269                 :            :   }
    1270                 :            : 
    1271                 :            : 
    1272                 :         61 :   void limit(long maxfds, long maxmbs, bool metrics_p = true)
    1273                 :            :   {
    1274   [ +  +  +  +  :         61 :     if (verbose > 3 && (this->max_fds != maxfds || this->max_mbs != maxmbs))
                   -  + ]
    1275   [ +  -  +  -  :          8 :       obatched(clog) << "fdcache limited to maxfds=" << maxfds << " maxmbs=" << maxmbs << endl;
          +  -  +  -  +  
                -  +  - ]
    1276                 :            : 
    1277         [ +  - ]:        122 :     unique_lock<mutex> lock(fdcache_lock);
    1278                 :         61 :     this->max_fds = maxfds;
    1279                 :         61 :     this->max_mbs = maxmbs;
    1280                 :            : 
    1281                 :         61 :     long total_fd = 0;
    1282                 :         61 :     double total_mb = 0.0;
    1283         [ +  + ]:        110 :     for (auto i = lru.begin(); i < lru.end(); i++)
    1284                 :            :       {
    1285                 :            :         // accumulate totals from most recently used one going backward
    1286                 :         89 :         total_fd ++;
    1287                 :         89 :         total_mb += i->fd_size_mb;
    1288   [ +  +  -  + ]:         89 :         if (total_fd > max_fds || total_mb > max_mbs)
    1289                 :            :           {
    1290                 :            :             // found the cut here point!
    1291                 :            : 
    1292         [ +  + ]:         93 :             for (auto j = i; j < lru.end(); j++) // close all the fds from here on in
    1293                 :            :               {
    1294         [ +  + ]:         53 :                 if (verbose > 3)
    1295   [ +  -  +  - ]:         96 :                   obatched(clog) << "fdcache evicted a=" << j->archive << " b=" << j->entry
    1296   [ +  -  +  -  :         48 :                                  << " fd=" << j->fd << " mb=" << j->fd_size_mb << endl;
          +  -  +  -  +  
          -  +  -  +  -  
                   +  - ]
    1297         [ +  + ]:         53 :                 if (metrics_p)
    1298   [ +  -  +  -  :         47 :                   inc_metric("fdcache_op_count","op","evict");
             +  -  +  - ]
    1299                 :         53 :                 unlink (j->fd.c_str());
    1300                 :            :               }
    1301                 :            : 
    1302         [ +  - ]:         40 :             lru.erase(i, lru.end()); // erase the nodes generally
    1303                 :         40 :             break;
    1304                 :            :           }
    1305                 :            :       }
    1306   [ +  +  +  - ]:         61 :     if (metrics_p) set_metrics();
    1307                 :         61 :   }
    1308                 :            : 
    1309                 :            : 
    1310                 :          3 :   ~libarchive_fdcache()
    1311                 :          3 :   {
    1312                 :            :     // unlink any fdcache entries in $TMPDIR
    1313                 :            :     // don't update metrics; those globals may be already destroyed
    1314                 :          3 :     limit(0, 0, false);
    1315                 :          3 :   }
    1316                 :            : };
    1317                 :            : static libarchive_fdcache fdcache;
    1318                 :            : 
    1319                 :            : 
    1320                 :            : // For security/portability reasons, many distro-package archives have
    1321                 :            : // a "./" in front of path names; others have nothing, others have
    1322                 :            : // "/".  Canonicalize them all to a single leading "/", with the
    1323                 :            : // assumption that this matches the dwarf-derived file names too.
    1324                 :        137 : string canonicalized_archive_entry_pathname(struct archive_entry *e)
    1325                 :            : {
    1326   [ +  -  +  - ]:        274 :   string fn = archive_entry_pathname(e);
    1327         [ -  + ]:        137 :   if (fn.size() == 0)
    1328                 :          0 :     return fn;
    1329   [ +  -  -  + ]:        137 :   if (fn[0] == '/')
    1330                 :          0 :     return fn;
    1331   [ +  -  +  + ]:        137 :   if (fn[0] == '.')
    1332         [ +  - ]:        117 :     return fn.substr(1);
    1333                 :            :   else
    1334   [ +  -  +  - ]:         40 :     return string("/")+fn;
    1335                 :            : }
    1336                 :            : 
    1337                 :            : 
    1338                 :            : 
    1339                 :            : static struct MHD_Response*
    1340                 :         76 : handle_buildid_r_match (bool internal_req_p,
    1341                 :            :                         int64_t b_mtime,
    1342                 :            :                         const string& b_source0,
    1343                 :            :                         const string& b_source1,
    1344                 :            :                         int *result_fd)
    1345                 :            : {
    1346                 :            :   struct stat fs;
    1347                 :         76 :   int rc = stat (b_source0.c_str(), &fs);
    1348         [ +  + ]:         76 :   if (rc != 0)
    1349   [ +  -  +  -  :         31 :     throw libc_exception (errno, string("stat ") + b_source0);
                   +  - ]
    1350                 :            : 
    1351         [ -  + ]:         45 :   if ((int64_t) fs.st_mtime != b_mtime)
    1352                 :            :     {
    1353         [ #  # ]:          0 :       if (verbose)
    1354   [ #  #  #  #  :          0 :         obatched(clog) << "mtime mismatch for " << b_source0 << endl;
             #  #  #  # ]
    1355                 :          0 :       return 0;
    1356                 :            :     }
    1357                 :            : 
    1358                 :            :   // check for a match in the fdcache first
    1359         [ +  - ]:         45 :   int fd = fdcache.lookup(b_source0, b_source1);
    1360         [ +  + ]:         45 :   while (fd >= 0) // got one!; NB: this is really an if() with a possible branch out to the end
    1361                 :            :     {
    1362                 :         11 :       rc = fstat(fd, &fs);
    1363         [ -  + ]:         11 :       if (rc < 0) // disappeared?
    1364                 :            :         {
    1365         [ #  # ]:          0 :           if (verbose)
    1366   [ #  #  #  #  :          0 :             obatched(clog) << "cannot fstat fdcache " << b_source0 << endl;
             #  #  #  # ]
    1367         [ #  # ]:          0 :           close(fd);
    1368         [ #  # ]:          0 :           fdcache.clear(b_source0, b_source1);
    1369                 :          0 :           break; // branch out of if "loop", to try new libarchive fetch attempt
    1370                 :            :         }
    1371                 :            : 
    1372         [ +  - ]:         11 :       struct MHD_Response* r = MHD_create_response_from_fd (fs.st_size, fd);
    1373         [ -  + ]:         11 :       if (r == 0)
    1374                 :            :         {
    1375         [ #  # ]:          0 :           if (verbose)
    1376   [ #  #  #  #  :          0 :             obatched(clog) << "cannot create fd-response for " << b_source0 << endl;
             #  #  #  # ]
    1377         [ #  # ]:          0 :           close(fd);
    1378                 :          0 :           break; // branch out of if "loop", to try new libarchive fetch attempt
    1379                 :            :         }
    1380                 :            : 
    1381   [ +  -  +  -  :         11 :       inc_metric ("http_responses_total","result","archive fdcache");
             +  -  +  - ]
    1382                 :            : 
    1383         [ +  - ]:         11 :       MHD_add_response_header (r, "Content-Type", "application/octet-stream");
    1384         [ +  - ]:         11 :       add_mhd_last_modified (r, fs.st_mtime);
    1385         [ +  - ]:         11 :       if (verbose > 1)
    1386   [ +  -  +  -  :         11 :         obatched(clog) << "serving fdcache archive " << b_source0 << " file " << b_source1 << endl;
          +  -  +  -  +  
                -  +  - ]
    1387                 :            :       /* libmicrohttpd will close it. */
    1388         [ +  - ]:         11 :       if (result_fd)
    1389                 :         11 :         *result_fd = fd;
    1390                 :         11 :       return r;
    1391                 :            :       // NB: see, we never go around the 'loop' more than once
    1392                 :            :     }
    1393                 :            : 
    1394                 :            :   // no match ... grumble, must process the archive
    1395         [ +  - ]:         68 :   string archive_decoder = "/dev/null";
    1396         [ +  - ]:         68 :   string archive_extension = "";
    1397         [ +  + ]:        136 :   for (auto&& arch : scan_archives)
    1398   [ +  -  +  + ]:        102 :     if (string_endswith(b_source0, arch.first))
    1399                 :            :       {
    1400         [ +  - ]:         34 :         archive_extension = arch.first;
    1401         [ +  - ]:         34 :         archive_decoder = arch.second;
    1402                 :            :       }
    1403                 :            :   FILE* fp;
    1404                 :            :   defer_dtor<FILE*,int>::dtor_fn dfn;
    1405         [ +  + ]:         34 :   if (archive_decoder != "cat")
    1406                 :            :     {
    1407   [ +  -  +  -  :          9 :       string popen_cmd = archive_decoder + " " + shell_escape(b_source0);
                   +  - ]
    1408         [ +  - ]:          3 :       fp = popen (popen_cmd.c_str(), "r"); // "e" O_CLOEXEC?
    1409                 :          3 :       dfn = pclose;
    1410         [ -  + ]:          3 :       if (fp == NULL)
    1411   [ #  #  #  #  :          0 :         throw libc_exception (errno, string("popen ") + popen_cmd);
                   #  # ]
    1412                 :            :     }
    1413                 :            :   else
    1414                 :            :     {
    1415         [ +  - ]:         31 :       fp = fopen (b_source0.c_str(), "r");
    1416                 :         31 :       dfn = fclose;
    1417         [ -  + ]:         31 :       if (fp == NULL)
    1418   [ #  #  #  #  :          0 :         throw libc_exception (errno, string("fopen ") + b_source0);
                   #  # ]
    1419                 :            :     }
    1420                 :         68 :   defer_dtor<FILE*,int> fp_closer (fp, dfn);
    1421                 :            : 
    1422                 :            :   struct archive *a;
    1423         [ +  - ]:         34 :   a = archive_read_new();
    1424         [ -  + ]:         34 :   if (a == NULL)
    1425   [ #  #  #  # ]:          0 :     throw archive_exception("cannot create archive reader");
    1426                 :         34 :   defer_dtor<struct archive*,int> archive_closer (a, archive_read_free);
    1427                 :            : 
    1428         [ +  - ]:         34 :   rc = archive_read_support_format_all(a);
    1429         [ -  + ]:         34 :   if (rc != ARCHIVE_OK)
    1430   [ #  #  #  # ]:          0 :     throw archive_exception(a, "cannot select all format");
    1431         [ +  - ]:         34 :   rc = archive_read_support_filter_all(a);
    1432         [ -  + ]:         34 :   if (rc != ARCHIVE_OK)
    1433   [ #  #  #  # ]:          0 :     throw archive_exception(a, "cannot select all filters");
    1434                 :            : 
    1435         [ +  - ]:         34 :   rc = archive_read_open_FILE (a, fp);
    1436         [ -  + ]:         34 :   if (rc != ARCHIVE_OK)
    1437   [ #  #  #  # ]:          0 :     throw archive_exception(a, "cannot open archive from pipe");
    1438                 :            : 
    1439                 :            :   // archive traversal is in three stages, no, four stages:
    1440                 :            :   // 1) skip entries whose names do not match the requested one
    1441                 :            :   // 2) extract the matching entry name (set r = result)
    1442                 :            :   // 3) extract some number of prefetched entries (just into fdcache)
    1443                 :            :   // 4) abort any further processing
    1444                 :         34 :   struct MHD_Response* r = 0;                 // will set in stage 2
    1445         [ +  + ]:         34 :   unsigned prefetch_count =
    1446                 :            :     internal_req_p ? 0 : fdcache_prefetch;    // will decrement in stage 3
    1447                 :            : 
    1448   [ +  +  +  + ]:        340 :   while(r == 0 || prefetch_count > 0) // stage 1, 2, or 3
    1449                 :            :     {
    1450         [ -  + ]:        336 :       if (interrupted)
    1451                 :         30 :         break;
    1452                 :            : 
    1453                 :            :       struct archive_entry *e;
    1454         [ +  - ]:        336 :       rc = archive_read_next_header (a, &e);
    1455         [ +  + ]:        336 :       if (rc != ARCHIVE_OK)
    1456                 :         30 :         break;
    1457                 :            : 
    1458   [ +  -  +  + ]:        306 :       if (! S_ISREG(archive_entry_mode (e))) // skip non-files completely
    1459                 :        306 :         continue;
    1460                 :            : 
    1461         [ +  - ]:         68 :       string fn = canonicalized_archive_entry_pathname (e);
    1462   [ +  +  +  +  :         68 :       if ((r == 0) && (fn != b_source1)) // stage 1
                   +  + ]
    1463                 :         15 :         continue;
    1464                 :            : 
    1465   [ +  -  -  + ]:         53 :       if (fdcache.probe (b_source0, fn)) // skip if already interned
    1466                 :          0 :         continue;
    1467                 :            : 
    1468                 :            :       // extract this file to a temporary file
    1469                 :         53 :       char* tmppath = NULL;
    1470                 :         53 :       rc = asprintf (&tmppath, "%s/debuginfod.XXXXXX", tmpdir.c_str());
    1471         [ -  + ]:         53 :       if (rc < 0)
    1472   [ #  #  #  # ]:          0 :         throw libc_exception (ENOMEM, "cannot allocate tmppath");
    1473                 :         53 :       defer_dtor<void*,void> tmmpath_freer (tmppath, free);
    1474         [ +  - ]:         53 :       fd = mkstemp (tmppath);
    1475         [ -  + ]:         53 :       if (fd < 0)
    1476   [ #  #  #  # ]:          0 :         throw libc_exception (errno, "cannot create temporary file");
    1477                 :            :       // NB: don't unlink (tmppath), as fdcache will take charge of it.
    1478                 :            : 
    1479                 :            :       // NB: this can take many uninterruptible seconds for a huge file
    1480         [ +  - ]:         53 :       rc = archive_read_data_into_fd (a, fd);
    1481         [ -  + ]:         53 :       if (rc != ARCHIVE_OK) // e.g. ENOSPC!
    1482                 :            :         {
    1483         [ #  # ]:          0 :           close (fd);
    1484                 :          0 :           unlink (tmppath);
    1485   [ #  #  #  # ]:          0 :           throw archive_exception(a, "cannot extract file");
    1486                 :            :         }
    1487                 :            : 
    1488                 :            :       // Set the mtime so the fdcache file mtimes, even prefetched ones,
    1489                 :            :       // propagate to future webapi clients.
    1490                 :            :       struct timeval tvs[2];
    1491         [ +  - ]:         53 :       tvs[0].tv_sec = tvs[1].tv_sec = archive_entry_mtime(e);
    1492                 :         53 :       tvs[0].tv_usec = tvs[1].tv_usec = 0;
    1493                 :         53 :       (void) futimes (fd, tvs);  /* best effort */
    1494                 :            : 
    1495         [ +  + ]:         53 :       if (r != 0) // stage 3
    1496                 :            :         {
    1497                 :            :           // NB: now we know we have a complete reusable file; make fdcache
    1498                 :            :           // responsible for unlinking it later.
    1499   [ +  -  +  - ]:         19 :           fdcache.intern(b_source0, fn,
    1500                 :            :                          tmppath, archive_entry_size(e),
    1501         [ +  - ]:         38 :                          false); // prefetched ones go to back of lru
    1502                 :         19 :           prefetch_count --;
    1503         [ +  - ]:         19 :           close (fd); // we're not saving this fd to make a mhd-response from!
    1504                 :         19 :           continue;
    1505                 :            :         }
    1506                 :            : 
    1507                 :            :       // NB: now we know we have a complete reusable file; make fdcache
    1508                 :            :       // responsible for unlinking it later.
    1509   [ +  -  +  - ]:         34 :       fdcache.intern(b_source0, b_source1,
    1510                 :            :                      tmppath, archive_entry_size(e),
    1511         [ +  - ]:         68 :                      true); // requested ones go to the front of lru
    1512                 :            : 
    1513   [ +  -  +  -  :         34 :       inc_metric ("http_responses_total","result",archive_extension + " archive");
             +  -  +  - ]
    1514   [ +  -  +  - ]:         34 :       r = MHD_create_response_from_fd (archive_entry_size(e), fd);
    1515         [ -  + ]:         34 :       if (r == 0)
    1516                 :            :         {
    1517         [ #  # ]:          0 :           if (verbose)
    1518   [ #  #  #  #  :          0 :             obatched(clog) << "cannot create fd-response for " << b_source0 << endl;
             #  #  #  # ]
    1519         [ #  # ]:          0 :           close(fd);
    1520                 :          0 :           break; // assume no chance of better luck around another iteration; no other copies of same file
    1521                 :            :         }
    1522                 :            :       else
    1523                 :            :         {
    1524         [ +  - ]:         34 :           MHD_add_response_header (r, "Content-Type", "application/octet-stream");
    1525   [ +  -  +  - ]:         34 :           add_mhd_last_modified (r, archive_entry_mtime(e));
    1526         [ +  - ]:         34 :           if (verbose > 1)
    1527   [ +  -  +  -  :         34 :             obatched(clog) << "serving archive " << b_source0 << " file " << b_source1 << endl;
          +  -  +  -  +  
                -  +  - ]
    1528                 :            :           /* libmicrohttpd will close it. */
    1529         [ +  - ]:         34 :           if (result_fd)
    1530                 :         34 :             *result_fd = fd;
    1531                 :         34 :           continue;
    1532                 :            :         }
    1533                 :            :     }
    1534                 :            : 
    1535                 :            :   // XXX: rpm/file not found: delete this R entry?
    1536                 :         34 :   return r;
    1537                 :            : }
    1538                 :            : 
    1539                 :            : 
    1540                 :            : static struct MHD_Response*
    1541                 :         91 : handle_buildid_match (bool internal_req_p,
    1542                 :            :                       int64_t b_mtime,
    1543                 :            :                       const string& b_stype,
    1544                 :            :                       const string& b_source0,
    1545                 :            :                       const string& b_source1,
    1546                 :            :                       int *result_fd)
    1547                 :            : {
    1548                 :            :   try
    1549                 :            :     {
    1550         [ +  + ]:         91 :       if (b_stype == "F")
    1551         [ +  - ]:         15 :         return handle_buildid_f_match(internal_req_p, b_mtime, b_source0, result_fd);
    1552         [ +  - ]:         76 :       else if (b_stype == "R")
    1553         [ +  + ]:         76 :         return handle_buildid_r_match(internal_req_p, b_mtime, b_source0, b_source1, result_fd);
    1554                 :            :     }
    1555         [ -  + ]:         62 :   catch (const reportable_exception &e)
    1556                 :            :     {
    1557         [ +  - ]:         31 :       e.report(clog);
    1558                 :            :       // Report but swallow libc etc. errors here; let the caller
    1559                 :            :       // iterate to other matches of the content.
    1560                 :            :     }
    1561                 :            : 
    1562                 :         31 :   return 0;
    1563                 :            : }
    1564                 :            : 
    1565                 :            : 
    1566                 :            : static int
    1567                 :          6 : debuginfod_find_progress (debuginfod_client *, long a, long b)
    1568                 :            : {
    1569         [ -  + ]:          6 :   if (verbose > 4)
    1570   [ #  #  #  #  :          0 :     obatched(clog) << "federated debuginfod progress=" << a << "/" << b << endl;
          #  #  #  #  #  
                      # ]
    1571                 :            : 
    1572                 :          6 :   return interrupted;
    1573                 :            : }
    1574                 :            : 
    1575                 :            : 
    1576                 :            : // a little lru pool of debuginfod_client*s for reuse between query threads
    1577                 :            : 
    1578                 :            : mutex dc_pool_lock;
    1579                 :            : deque<debuginfod_client*> dc_pool;
    1580                 :            : 
    1581                 :          9 : debuginfod_client* debuginfod_pool_begin()
    1582                 :            : {
    1583         [ +  - ]:         18 :   unique_lock<mutex> lock(dc_pool_lock);
    1584         [ +  + ]:          9 :   if (dc_pool.size() > 0)
    1585                 :            :     {
    1586   [ +  -  +  -  :          5 :       inc_metric("dc_pool_op_count","op","begin-reuse");
             +  -  +  - ]
    1587                 :          5 :       debuginfod_client *c = dc_pool.front();
    1588                 :          5 :       dc_pool.pop_front();
    1589                 :          5 :       return c;
    1590                 :            :     }
    1591   [ +  -  +  -  :          4 :   inc_metric("dc_pool_op_count","op","begin-new");
             +  -  +  - ]
    1592         [ +  - ]:          4 :   return debuginfod_begin();
    1593                 :            : }
    1594                 :            : 
    1595                 :            : 
    1596                 :          5 : void debuginfod_pool_groom()
    1597                 :            : {
    1598         [ +  - ]:         10 :   unique_lock<mutex> lock(dc_pool_lock);
    1599         [ +  + ]:          7 :   while (dc_pool.size() > 0)
    1600                 :            :     {
    1601   [ +  -  +  -  :          2 :       inc_metric("dc_pool_op_count","op","end");
             +  -  +  - ]
    1602         [ +  - ]:          2 :       debuginfod_end(dc_pool.front());
    1603                 :          2 :       dc_pool.pop_front();
    1604                 :            :     }
    1605                 :          5 : }
    1606                 :            : 
    1607                 :            : 
    1608                 :          9 : void debuginfod_pool_end(debuginfod_client* c)
    1609                 :            : {
    1610         [ +  - ]:         18 :   unique_lock<mutex> lock(dc_pool_lock);
    1611   [ +  -  +  -  :          9 :   inc_metric("dc_pool_op_count","op","end-save");
             +  -  +  - ]
    1612         [ +  - ]:          9 :   dc_pool.push_front(c); // accelerate reuse, vs. push_back
    1613                 :          9 : }
    1614                 :            : 
    1615                 :            : 
    1616                 :            : static struct MHD_Response*
    1617                 :         71 : handle_buildid (MHD_Connection* conn,
    1618                 :            :                 const string& buildid /* unsafe */,
    1619                 :            :                 const string& artifacttype /* unsafe */,
    1620                 :            :                 const string& suffix /* unsafe */,
    1621                 :            :                 int *result_fd)
    1622                 :            : {
    1623                 :            :   // validate artifacttype
    1624                 :        142 :   string atype_code;
    1625   [ +  +  +  - ]:         71 :   if (artifacttype == "debuginfo") atype_code = "D";
    1626   [ +  +  +  - ]:         32 :   else if (artifacttype == "executable") atype_code = "E";
    1627   [ +  +  +  - ]:         15 :   else if (artifacttype == "source") atype_code = "S";
    1628   [ +  -  +  - ]:          1 :   else throw reportable_exception("invalid artifacttype");
    1629                 :            : 
    1630   [ +  -  +  -  :         70 :   inc_metric("http_requests_total", "type", artifacttype);
                   +  - ]
    1631                 :            :   
    1632   [ +  +  -  +  :         70 :   if (atype_code == "S" && suffix == "")
                   -  + ]
    1633   [ #  #  #  # ]:          0 :      throw reportable_exception("invalid source suffix");
    1634                 :            : 
    1635                 :            :   // validate buildid
    1636                 :         70 :   if ((buildid.size() < 2) || // not empty
    1637   [ +  -  +  -  :        140 :       (buildid.size() % 2) || // even number
             -  +  -  + ]
    1638                 :         70 :       (buildid.find_first_not_of("0123456789abcdef") != string::npos)) // pure tasty lowercase hex
    1639   [ #  #  #  # ]:          0 :     throw reportable_exception("invalid buildid");
    1640                 :            : 
    1641         [ +  - ]:         70 :   if (verbose > 1)
    1642   [ +  -  +  - ]:        140 :     obatched(clog) << "searching for buildid=" << buildid << " artifacttype=" << artifacttype
    1643   [ +  -  +  -  :         70 :          << " suffix=" << suffix << endl;
          +  -  +  -  +  
                -  +  - ]
    1644                 :            : 
    1645                 :            :   // If invoked from the scanner threads, use the scanners' read-write
    1646                 :            :   // connection.  Otherwise use the web query threads' read-only connection.
    1647         [ +  + ]:         70 :   sqlite3 *thisdb = (conn == 0) ? db : dbq;
    1648                 :            : 
    1649                 :         70 :   sqlite_ps *pp = 0;
    1650                 :            : 
    1651         [ +  + ]:         70 :   if (atype_code == "D")
    1652                 :            :     {
    1653                 :         39 :       pp = new sqlite_ps (thisdb, "mhd-query-d",
    1654                 :            :                           "select mtime, sourcetype, source0, source1 from " BUILDIDS "_query_d where buildid = ? "
    1655   [ +  -  +  -  :         39 :                           "order by mtime desc");
             +  -  +  - ]
    1656         [ +  - ]:         39 :       pp->reset();
    1657         [ +  - ]:         39 :       pp->bind(1, buildid);
    1658                 :            :     }
    1659         [ +  + ]:         31 :   else if (atype_code == "E")
    1660                 :            :     {
    1661                 :         17 :       pp = new sqlite_ps (thisdb, "mhd-query-e",
    1662                 :            :                           "select mtime, sourcetype, source0, source1 from " BUILDIDS "_query_e where buildid = ? "
    1663   [ +  -  +  -  :         17 :                           "order by mtime desc");
             +  -  +  - ]
    1664         [ +  - ]:         17 :       pp->reset();
    1665         [ +  - ]:         17 :       pp->bind(1, buildid);
    1666                 :            :     }
    1667         [ +  - ]:         14 :   else if (atype_code == "S")
    1668                 :            :     {
    1669                 :            :       // PR25548
    1670                 :            :       // Incoming source queries may come in with either dwarf-level OR canonicalized paths.
    1671                 :            :       // We let the query pass with either one.
    1672                 :            : 
    1673                 :         14 :       pp = new sqlite_ps (thisdb, "mhd-query-s",
    1674                 :            :                           "select mtime, sourcetype, source0, source1 from " BUILDIDS "_query_s where buildid = ? and artifactsrc in (?,?) "
    1675   [ +  -  +  -  :         14 :                           "order by sharedprefix(source0,source0ref) desc, mtime desc");
             +  -  +  - ]
    1676         [ +  - ]:         14 :       pp->reset();
    1677         [ +  - ]:         14 :       pp->bind(1, buildid);
    1678                 :            :       // NB: we don't store the non-canonicalized path names any more, but old databases
    1679                 :            :       // might have them (and no canon ones), so we keep searching for both.
    1680         [ +  - ]:         14 :       pp->bind(2, suffix);
    1681   [ +  -  +  - ]:         14 :       pp->bind(3, canon_pathname(suffix));
    1682                 :            :     }
    1683                 :        140 :   unique_ptr<sqlite_ps> ps_closer(pp); // release pp if exception or return
    1684                 :            : 
    1685                 :            :   // consume all the rows
    1686                 :            :   while (1)
    1687                 :            :     {
    1688         [ +  - ]:        101 :       int rc = pp->step();
    1689         [ +  + ]:        101 :       if (rc == SQLITE_DONE) break;
    1690         [ +  + ]:         92 :       if (rc != SQLITE_ROW)
    1691   [ +  -  +  - ]:          1 :         throw sqlite_exception(rc, "step");
    1692                 :            : 
    1693         [ +  - ]:         91 :       int64_t b_mtime = sqlite3_column_int64 (*pp, 0);
    1694   [ +  -  +  -  :         91 :       string b_stype = string((const char*) sqlite3_column_text (*pp, 1) ?: ""); /* by DDL may not be NULL */
                   +  - ]
    1695   [ +  -  +  -  :         91 :       string b_source0 = string((const char*) sqlite3_column_text (*pp, 2) ?: ""); /* may be NULL */
                   +  - ]
    1696   [ +  -  +  +  :         91 :       string b_source1 = string((const char*) sqlite3_column_text (*pp, 3) ?: ""); /* may be NULL */
                   +  - ]
    1697                 :            : 
    1698         [ +  - ]:         91 :       if (verbose > 1)
    1699   [ +  -  +  -  :        182 :         obatched(clog) << "found mtime=" << b_mtime << " stype=" << b_stype
                   +  - ]
    1700   [ +  -  +  -  :         91 :              << " source0=" << b_source0 << " source1=" << b_source1 << endl;
          +  -  +  -  +  
             -  +  -  +  
                      - ]
    1701                 :            : 
    1702                 :            :       // Try accessing the located match.
    1703                 :            :       // XXX: in case of multiple matches, attempt them in parallel?
    1704         [ +  - ]:         91 :       auto r = handle_buildid_match (conn ? false : true,
    1705                 :            :                                      b_mtime, b_stype, b_source0, b_source1, result_fd);
    1706         [ +  + ]:         91 :       if (r)
    1707                 :         60 :         return r;
    1708                 :         31 :     }
    1709         [ +  - ]:          9 :   pp->reset();
    1710                 :            : 
    1711                 :            :   // We couldn't find it in the database.  Last ditch effort
    1712                 :            :   // is to defer to other debuginfo servers.
    1713                 :            : 
    1714                 :          9 :   int fd = -1;
    1715         [ +  - ]:          9 :   debuginfod_client *client = debuginfod_pool_begin ();
    1716         [ +  - ]:          9 :   if (client != NULL)
    1717                 :            :     {
    1718         [ +  - ]:          9 :       debuginfod_set_progressfn (client, & debuginfod_find_progress);
    1719                 :            : 
    1720         [ +  - ]:          9 :       if (conn)
    1721                 :            :         {
    1722                 :            :           // Transcribe incoming User-Agent:
    1723   [ +  -  +  -  :         18 :           string ua = MHD_lookup_connection_value (conn, MHD_HEADER_KIND, "User-Agent") ?: "";
                   +  - ]
    1724   [ +  -  +  - ]:         27 :           string ua_complete = string("User-Agent: ") + ua;
    1725         [ +  - ]:          9 :           debuginfod_add_http_header (client, ua_complete.c_str());
    1726                 :            : 
    1727                 :            :           // Compute larger XFF:, for avoiding info loss during
    1728                 :            :           // federation, and for future cyclicity detection.
    1729   [ +  -  +  +  :         18 :           string xff = MHD_lookup_connection_value (conn, MHD_HEADER_KIND, "X-Forwarded-For") ?: "";
                   +  - ]
    1730         [ +  + ]:          9 :           if (xff != "")
    1731   [ +  -  +  - ]:          2 :             xff += string(", "); // comma separated list
    1732                 :            : 
    1733                 :            :           // Compute the client's numeric IP address only - so can't merge with conninfo()
    1734         [ +  - ]:          9 :           const union MHD_ConnectionInfo *u = MHD_get_connection_info (conn,
    1735                 :            :                                                                        MHD_CONNECTION_INFO_CLIENT_ADDRESS);
    1736         [ +  - ]:          9 :           struct sockaddr *so = u ? u->client_addr : 0;
    1737                 :          9 :           char hostname[256] = ""; // RFC1035
    1738   [ +  -  +  - ]:          9 :           if (so && so->sa_family == AF_INET)
    1739         [ +  - ]:          9 :             (void) getnameinfo (so, sizeof (struct sockaddr_in), hostname, sizeof (hostname), NULL, 0,
    1740                 :            :                                 NI_NUMERICHOST);
    1741   [ #  #  #  # ]:          0 :           else if (so && so->sa_family == AF_INET6)
    1742         [ #  # ]:          0 :             (void) getnameinfo (so, sizeof (struct sockaddr_in6), hostname, sizeof (hostname), NULL, 0,
    1743                 :            :                                 NI_NUMERICHOST);
    1744                 :            : 
    1745   [ +  -  +  -  :         36 :           string xff_complete = string("X-Forwarded-For: ")+xff+string(hostname);
             +  -  +  - ]
    1746         [ +  - ]:          9 :           debuginfod_add_http_header (client, xff_complete.c_str());
    1747                 :            :         }
    1748                 :            : 
    1749         [ +  + ]:          9 :       if (artifacttype == "debuginfo")
    1750         [ +  - ]:          8 :         fd = debuginfod_find_debuginfo (client,
    1751                 :          8 :                                         (const unsigned char*) buildid.c_str(),
    1752                 :            :                                         0, NULL);
    1753         [ +  - ]:          1 :       else if (artifacttype == "executable")
    1754         [ +  - ]:          1 :         fd = debuginfod_find_executable (client,
    1755                 :          1 :                                          (const unsigned char*) buildid.c_str(),
    1756                 :            :                                          0, NULL);
    1757         [ #  # ]:          0 :       else if (artifacttype == "source")
    1758         [ #  # ]:          0 :         fd = debuginfod_find_source (client,
    1759                 :          0 :                                      (const unsigned char*) buildid.c_str(),
    1760                 :            :                                      0, suffix.c_str(), NULL);
    1761                 :            :     }
    1762                 :            :   else
    1763                 :          0 :     fd = -errno; /* Set by debuginfod_begin.  */
    1764         [ +  - ]:          9 :   debuginfod_pool_end (client);
    1765                 :            : 
    1766         [ +  + ]:          9 :   if (fd >= 0)
    1767                 :            :     {
    1768   [ +  -  +  -  :          1 :       inc_metric ("http_responses_total","result","upstream");
             +  -  +  - ]
    1769                 :            :       struct stat s;
    1770                 :          1 :       int rc = fstat (fd, &s);
    1771         [ +  - ]:          1 :       if (rc == 0)
    1772                 :            :         {
    1773         [ +  - ]:          1 :           auto r = MHD_create_response_from_fd ((uint64_t) s.st_size, fd);
    1774         [ +  - ]:          1 :           if (r)
    1775                 :            :             {
    1776         [ +  - ]:          1 :               MHD_add_response_header (r, "Content-Type", "application/octet-stream");
    1777         [ +  - ]:          1 :               add_mhd_last_modified (r, s.st_mtime);
    1778         [ +  - ]:          1 :               if (verbose > 1)
    1779   [ +  -  +  -  :          1 :                 obatched(clog) << "serving file from upstream debuginfod/cache" << endl;
                   +  - ]
    1780         [ +  - ]:          1 :               if (result_fd)
    1781                 :          1 :                 *result_fd = fd;
    1782                 :          1 :               return r; // NB: don't close fd; libmicrohttpd will
    1783                 :            :             }
    1784                 :            :         }
    1785         [ #  # ]:          0 :       close (fd);
    1786                 :            :     }
    1787                 :            :   else
    1788      [ +  +  - ]:          8 :     switch(fd)
    1789                 :            :       {
    1790                 :          6 :       case -ENOSYS:
    1791                 :          6 :         break;
    1792                 :          2 :       case -ENOENT:
    1793                 :          2 :         break;
    1794                 :          0 :       default: // some more tricky error
    1795   [ #  #  #  # ]:          0 :         throw libc_exception(-fd, "upstream debuginfod query failed");
    1796                 :            :       }
    1797                 :            : 
    1798   [ +  -  +  - ]:          8 :   throw reportable_exception(MHD_HTTP_NOT_FOUND, "not found");
    1799                 :            : }
    1800                 :            : 
    1801                 :            : 
    1802                 :            : ////////////////////////////////////////////////////////////////////////
    1803                 :            : 
    1804                 :            : static map<string,double> metrics; // arbitrary data for /metrics query
    1805                 :            : // NB: store int64_t since all our metrics are integers; prometheus accepts double
    1806                 :            : static mutex metrics_lock;
    1807                 :            : // NB: these objects get released during the process exit via global dtors
    1808                 :            : // do not call them from within other global dtors
    1809                 :            : 
    1810                 :            : // utility function for assembling prometheus-compatible
    1811                 :            : // name="escaped-value" strings
    1812                 :            : // https://prometheus.io/docs/instrumenting/exposition_formats/
    1813                 :            : static string
    1814                 :      14329 : metric_label(const string& name, const string& value)
    1815                 :            : {
    1816                 :      14329 :   string x = name + "=\"";
    1817         [ +  + ]:     204867 :   for (auto&& c : value)
    1818   [ -  -  -  + ]:     190593 :     switch(c)
    1819                 :            :       {
    1820         [ #  # ]:          0 :       case '\\': x += "\\\\"; break;
    1821         [ #  # ]:          0 :       case '\"': x += "\\\""; break;
    1822         [ #  # ]:          0 :       case '\n': x += "\\n"; break;
    1823         [ +  - ]:     190593 :       default: x += c; break;
    1824                 :            :       }
    1825         [ +  - ]:      14325 :   x += "\"";
    1826                 :      14330 :   return x;
    1827                 :            : }
    1828                 :            : 
    1829                 :            : 
    1830                 :            : // add prometheus-format metric name + label tuple (if any) + value
    1831                 :            : 
    1832                 :            : static void
    1833                 :        228 : set_metric(const string& metric, double value)
    1834                 :            : {
    1835         [ +  - ]:        228 :   unique_lock<mutex> lock(metrics_lock);
    1836         [ +  - ]:        228 :   metrics[metric] = value;
    1837                 :        228 : }
    1838                 :            : #if 0 /* unused */
    1839                 :            : static void
    1840                 :            : inc_metric(const string& metric)
    1841                 :            : {
    1842                 :            :   unique_lock<mutex> lock(metrics_lock);
    1843                 :            :   metrics[metric] ++;
    1844                 :            : }
    1845                 :            : #endif
    1846                 :            : static void
    1847                 :        885 : set_metric(const string& metric,
    1848                 :            :            const string& lname, const string& lvalue,
    1849                 :            :            double value)
    1850                 :            : {
    1851   [ +  -  +  -  :       2657 :   string key = (metric + "{" + metric_label(lname, lvalue) + "}");
             +  -  +  - ]
    1852         [ +  - ]:        886 :   unique_lock<mutex> lock(metrics_lock);
    1853         [ +  - ]:        886 :   metrics[key] = value;
    1854                 :        886 : }
    1855                 :            : 
    1856                 :            : static void
    1857                 :       7127 : inc_metric(const string& metric,
    1858                 :            :            const string& lname, const string& lvalue)
    1859                 :            : {
    1860   [ +  -  +  -  :      21381 :   string key = (metric + "{" + metric_label(lname, lvalue) + "}");
             +  -  +  - ]
    1861         [ +  - ]:       7127 :   unique_lock<mutex> lock(metrics_lock);
    1862         [ +  - ]:       7127 :   metrics[key] ++;
    1863                 :       7127 : }
    1864                 :            : static void
    1865                 :       6319 : add_metric(const string& metric,
    1866                 :            :            const string& lname, const string& lvalue,
    1867                 :            :            double value)
    1868                 :            : {
    1869   [ +  -  +  -  :      18955 :   string key = (metric + "{" + metric_label(lname, lvalue) + "}");
             +  -  +  - ]
    1870         [ +  - ]:       6319 :   unique_lock<mutex> lock(metrics_lock);
    1871         [ +  - ]:       6319 :   metrics[key] += value;
    1872                 :       6319 : }
    1873                 :            : #if 0
    1874                 :            : static void
    1875                 :            : add_metric(const string& metric,
    1876                 :            :            double value)
    1877                 :            : {
    1878                 :            :   unique_lock<mutex> lock(metrics_lock);
    1879                 :            :   metrics[metric] += value;
    1880                 :            : }
    1881                 :            : #endif
    1882                 :            : 
    1883                 :            : 
    1884                 :            : // and more for higher arity labels if needed
    1885                 :            : 
    1886                 :            : 
    1887                 :            : static struct MHD_Response*
    1888                 :         81 : handle_metrics (off_t* size)
    1889                 :            : {
    1890         [ +  - ]:        162 :   stringstream o;
    1891                 :            :   {
    1892         [ +  - ]:        162 :     unique_lock<mutex> lock(metrics_lock);
    1893         [ +  + ]:      11186 :     for (auto&& i : metrics)
    1894                 :      11105 :       o << i.first
    1895                 :            :         << " "
    1896   [ +  -  +  -  :      11105 :         << std::setprecision(std::numeric_limits<double>::digits10 + 1)
                   +  - ]
    1897         [ +  - ]:      11105 :         << i.second
    1898         [ +  - ]:      11105 :         << endl;
    1899                 :            :   }
    1900         [ +  - ]:         81 :   const string& os = o.str();
    1901         [ +  - ]:        162 :   MHD_Response* r = MHD_create_response_from_buffer (os.size(),
    1902                 :         81 :                                                      (void*) os.c_str(),
    1903                 :            :                                                      MHD_RESPMEM_MUST_COPY);
    1904                 :         81 :   *size = os.size();
    1905         [ +  - ]:         81 :   MHD_add_response_header (r, "Content-Type", "text/plain");
    1906                 :        162 :   return r;
    1907                 :            : }
    1908                 :            : 
    1909                 :            : static struct MHD_Response*
    1910                 :          0 : handle_root (off_t* size)
    1911                 :            : {
    1912   [ #  #  #  #  :          0 :   static string version = "debuginfod (" + string (PACKAGE_NAME) + ") "
                   #  # ]
    1913   [ #  #  #  #  :          0 :                           + string (PACKAGE_VERSION);
          #  #  #  #  #  
                      # ]
    1914                 :          0 :   MHD_Response* r = MHD_create_response_from_buffer (version.size (),
    1915                 :          0 :                                                      (void *) version.c_str (),
    1916                 :            :                                                      MHD_RESPMEM_PERSISTENT);
    1917                 :          0 :   *size = version.size ();
    1918                 :          0 :   MHD_add_response_header (r, "Content-Type", "text/plain");
    1919                 :          0 :   return r;
    1920                 :            : }
    1921                 :            : 
    1922                 :            : 
    1923                 :            : ////////////////////////////////////////////////////////////////////////
    1924                 :            : 
    1925                 :            : 
    1926                 :            : /* libmicrohttpd callback */
    1927                 :            : static MHD_RESULT
    1928                 :        292 : handler_cb (void * /*cls*/,
    1929                 :            :             struct MHD_Connection *connection,
    1930                 :            :             const char *url,
    1931                 :            :             const char *method,
    1932                 :            :             const char * /*version*/,
    1933                 :            :             const char * /*upload_data*/,
    1934                 :            :             size_t * /*upload_data_size*/,
    1935                 :            :             void ** ptr)
    1936                 :            : {
    1937                 :        292 :   struct MHD_Response *r = NULL;
    1938         [ +  - ]:        584 :   string url_copy = url;
    1939                 :            : 
    1940                 :            :   /* libmicrohttpd always makes (at least) two callbacks: once just
    1941                 :            :      past the headers, and one after the request body is finished
    1942                 :            :      being received.  If we process things early (first callback) and
    1943                 :            :      queue a response, libmicrohttpd would suppress http keep-alive
    1944                 :            :      (via connection->read_closed = true). */
    1945                 :            :   static int aptr; /* just some random object to use as a flag */
    1946         [ +  + ]:        292 :   if (&aptr != *ptr)
    1947                 :            :     {
    1948                 :            :       /* do never respond on first call */
    1949                 :        146 :       *ptr = &aptr;
    1950                 :        146 :       return MHD_YES;
    1951                 :            :     }
    1952                 :        146 :   *ptr = NULL;                     /* reset when done */
    1953                 :            :   
    1954                 :            : #if MHD_VERSION >= 0x00097002
    1955                 :            :   enum MHD_Result rc;
    1956                 :            : #else
    1957                 :            :   int rc = MHD_NO; // mhd
    1958                 :            : #endif
    1959                 :        146 :   int http_code = 500;
    1960                 :        146 :   off_t http_size = -1;
    1961                 :            :   struct timespec ts_start, ts_end;
    1962                 :        146 :   clock_gettime (CLOCK_MONOTONIC, &ts_start);
    1963                 :            : 
    1964                 :            :   try
    1965                 :            :     {
    1966   [ +  -  -  + ]:        146 :       if (string(method) != "GET")
    1967   [ #  #  #  # ]:          0 :         throw reportable_exception(400, "we support GET only");
    1968                 :            : 
    1969                 :            :       /* Start decoding the URL. */
    1970                 :        146 :       size_t slash1 = url_copy.find('/', 1);
    1971         [ +  - ]:        292 :       string url1 = url_copy.substr(0, slash1); // ok even if slash1 not found
    1972                 :            : 
    1973   [ +  +  +  -  :        146 :       if (slash1 != string::npos && url1 == "/buildid")
                   +  + ]
    1974                 :            :         {
    1975   [ +  -  +  -  :        315 :           tmp_inc_metric m ("thread_busy", "role", "http-buildid");
             +  -  +  - ]
    1976                 :         63 :           size_t slash2 = url_copy.find('/', slash1+1);
    1977         [ -  + ]:         63 :           if (slash2 == string::npos)
    1978   [ #  #  #  # ]:          0 :             throw reportable_exception("/buildid/ webapi error, need buildid");
    1979                 :            : 
    1980         [ +  - ]:        126 :           string buildid = url_copy.substr(slash1+1, slash2-slash1-1);
    1981                 :            : 
    1982                 :         63 :           size_t slash3 = url_copy.find('/', slash2+1);
    1983                 :        136 :           string artifacttype, suffix;
    1984         [ +  + ]:         63 :           if (slash3 == string::npos)
    1985                 :            :             {
    1986         [ +  - ]:         49 :               artifacttype = url_copy.substr(slash2+1);
    1987         [ +  - ]:         49 :               suffix = "";
    1988                 :            :             }
    1989                 :            :           else
    1990                 :            :             {
    1991         [ +  - ]:         14 :               artifacttype = url_copy.substr(slash2+1, slash3-slash2-1);
    1992         [ +  - ]:         14 :               suffix = url_copy.substr(slash3); // include the slash in the suffix
    1993                 :            :             }
    1994                 :            : 
    1995                 :            :           // get the resulting fd so we can report its size
    1996                 :            :           int fd;
    1997         [ +  + ]:         63 :           r = handle_buildid(connection, buildid, artifacttype, suffix, &fd);
    1998         [ +  - ]:         53 :           if (r)
    1999                 :            :             {
    2000                 :            :               struct stat fs;
    2001         [ +  - ]:         53 :               if (fstat(fd, &fs) == 0)
    2002                 :         53 :                 http_size = fs.st_size;
    2003                 :            :               // libmicrohttpd will close (fd);
    2004                 :            :             }
    2005                 :            :         }
    2006         [ +  + ]:         83 :       else if (url1 == "/metrics")
    2007                 :            :         {
    2008   [ +  -  +  -  :        324 :           tmp_inc_metric m ("thread_busy", "role", "http-metrics");
             +  -  +  - ]
    2009   [ +  -  +  -  :         81 :           inc_metric("http_requests_total", "type", "metrics");
             +  -  +  - ]
    2010         [ +  - ]:         81 :           r = handle_metrics(& http_size);
    2011                 :            :         }
    2012         [ -  + ]:          2 :       else if (url1 == "/")
    2013                 :            :         {
    2014   [ #  #  #  #  :          0 :           inc_metric("http_requests_total", "type", "/");
             #  #  #  # ]
    2015         [ #  # ]:          0 :           r = handle_root(& http_size);
    2016                 :            :         }
    2017                 :            :       else
    2018   [ +  -  +  -  :          2 :         throw reportable_exception("webapi error, unrecognized '" + url1 + "'");
                   +  - ]
    2019                 :            : 
    2020         [ -  + ]:        134 :       if (r == 0)
    2021   [ #  #  #  # ]:          0 :         throw reportable_exception("internal error, missing response");
    2022                 :            : 
    2023         [ +  - ]:        134 :       rc = MHD_queue_response (connection, MHD_HTTP_OK, r);
    2024                 :        134 :       http_code = MHD_HTTP_OK;
    2025         [ +  - ]:        134 :       MHD_destroy_response (r);
    2026                 :            :     }
    2027         [ -  + ]:         12 :   catch (const reportable_exception& e)
    2028                 :            :     {
    2029   [ +  -  +  -  :         12 :       inc_metric("http_responses_total","result","error");
             +  -  +  - ]
    2030         [ +  - ]:         12 :       e.report(clog);
    2031                 :         12 :       http_code = e.code;
    2032                 :         12 :       http_size = e.message.size();
    2033         [ +  - ]:         12 :       rc = e.mhd_send_response (connection);
    2034                 :            :     }
    2035                 :            : 
    2036                 :        146 :   clock_gettime (CLOCK_MONOTONIC, &ts_end);
    2037                 :        146 :   double deltas = (ts_end.tv_sec - ts_start.tv_sec) + (ts_end.tv_nsec - ts_start.tv_nsec)/1.e9;
    2038   [ +  -  +  -  :        292 :   obatched(clog) << conninfo(connection)
                   +  - ]
    2039                 :            :                  << ' ' << method << ' ' << url
    2040   [ +  -  +  -  :        146 :                  << ' ' << http_code << ' ' << http_size
          +  -  +  -  +  
             -  +  -  +  
                      - ]
    2041   [ +  -  +  -  :        146 :                  << ' ' << (int)(deltas*1000) << "ms"
             +  -  +  - ]
    2042         [ +  - ]:        146 :                  << endl;
    2043                 :            : 
    2044                 :            :   // related prometheus metrics
    2045         [ +  - ]:        146 :   string http_code_str = to_string(http_code);
    2046         [ +  - ]:        146 :   if (http_size >= 0)
    2047         [ +  - ]:        146 :     add_metric("http_responses_transfer_bytes_sum","code",http_code_str,
    2048   [ +  -  +  - ]:        292 :                http_size);
    2049   [ +  -  +  -  :        146 :   inc_metric("http_responses_transfer_bytes_count","code",http_code_str);
                   +  - ]
    2050                 :            : 
    2051         [ +  - ]:        146 :   add_metric("http_responses_duration_milliseconds_sum","code",http_code_str,
    2052   [ +  -  +  - ]:        292 :              deltas*1000); // prometheus prefers _seconds and floating point
    2053   [ +  -  +  -  :        146 :   inc_metric("http_responses_duration_milliseconds_count","code",http_code_str);
                   +  - ]
    2054                 :            : 
    2055                 :        146 :   return rc;
    2056                 :            : }
    2057                 :            : 
    2058                 :            : 
    2059                 :            : ////////////////////////////////////////////////////////////////////////
    2060                 :            : // borrowed originally from src/nm.c get_local_names()
    2061                 :            : 
    2062                 :            : static void
    2063                 :         23 : dwarf_extract_source_paths (Elf *elf, set<string>& debug_sourcefiles)
    2064                 :            :   noexcept // no exceptions - so we can simplify the altdbg resource release at end
    2065                 :            : {
    2066                 :         23 :   Dwarf* dbg = dwarf_begin_elf (elf, DWARF_C_READ, NULL);
    2067         [ -  + ]:         23 :   if (dbg == NULL)
    2068                 :          0 :     return;
    2069                 :            : 
    2070                 :         23 :   Dwarf* altdbg = NULL;
    2071                 :         23 :   int    altdbg_fd = -1;
    2072                 :            : 
    2073                 :            :   // DWZ handling: if we have an unsatisfied debug-alt-link, add an
    2074                 :            :   // empty string into the outgoing sourcefiles set, so the caller
    2075                 :            :   // should know that our data is incomplete.
    2076                 :            :   const char *alt_name_p;
    2077                 :            :   const void *alt_build_id; // elfutils-owned memory
    2078                 :         23 :   ssize_t sz = dwelf_dwarf_gnu_debugaltlink (dbg, &alt_name_p, &alt_build_id);
    2079         [ +  + ]:         23 :   if (sz > 0) // got one!
    2080                 :            :     {
    2081                 :         16 :       string buildid;
    2082                 :          8 :       unsigned char* build_id_bytes = (unsigned char*) alt_build_id;
    2083         [ +  + ]:        168 :       for (ssize_t idx=0; idx<sz; idx++)
    2084                 :            :         {
    2085                 :        160 :           buildid += "0123456789abcdef"[build_id_bytes[idx] >> 4];
    2086                 :        160 :           buildid += "0123456789abcdef"[build_id_bytes[idx] & 0xf];
    2087                 :            :         }
    2088                 :            : 
    2089         [ +  - ]:          8 :       if (verbose > 3)
    2090                 :          8 :         obatched(clog) << "Need altdebug buildid=" << buildid << endl;
    2091                 :            : 
    2092                 :            :       // but is it unsatisfied the normal elfutils ways?
    2093                 :          8 :       Dwarf* alt = dwarf_getalt (dbg);
    2094         [ +  - ]:          8 :       if (alt == NULL)
    2095                 :            :         {
    2096                 :            :           // Yup, unsatisfied the normal way.  Maybe we can satisfy it
    2097                 :            :           // from our own debuginfod database.
    2098                 :            :           int alt_fd;
    2099                 :          8 :           struct MHD_Response *r = 0;
    2100                 :            :           try
    2101                 :            :             {
    2102   [ +  -  +  -  :          8 :               r = handle_buildid (0, buildid, "debuginfo", "", &alt_fd);
                   +  - ]
    2103                 :            :             }
    2104         [ -  - ]:          0 :           catch (const reportable_exception& e)
    2105                 :            :             {
    2106                 :            :               // swallow exceptions
    2107                 :            :             }
    2108                 :            : 
    2109                 :            :           // NB: this is not actually recursive!  This invokes the web-query
    2110                 :            :           // path, which cannot get back into the scan code paths.
    2111         [ +  - ]:          8 :           if (r)
    2112                 :            :             {
    2113                 :            :               // Found it!
    2114                 :          8 :               altdbg_fd = dup(alt_fd); // ok if this fails, downstream failures ok
    2115                 :          8 :               alt = altdbg = dwarf_begin (altdbg_fd, DWARF_C_READ);
    2116                 :            :               // NB: must close this dwarf and this fd at the bottom of the function!
    2117                 :          8 :               MHD_destroy_response (r); // will close alt_fd
    2118         [ +  - ]:          8 :               if (alt)
    2119                 :          8 :                 dwarf_setalt (dbg, alt);
    2120                 :            :             }
    2121                 :            :         }
    2122                 :            :       else
    2123                 :            :         {
    2124                 :            :           // NB: dwarf_setalt(alt) inappropriate - already done!
    2125                 :            :           // NB: altdbg will stay 0 so nothing tries to redundantly dealloc.
    2126                 :            :         }
    2127                 :            : 
    2128         [ +  - ]:          8 :       if (alt)
    2129                 :            :         {
    2130         [ +  - ]:          8 :           if (verbose > 3)
    2131                 :          8 :             obatched(clog) << "Resolved altdebug buildid=" << buildid << endl;
    2132                 :            :         }
    2133                 :            :       else // (alt == NULL) - signal possible presence of poor debuginfo
    2134                 :            :         {
    2135                 :          0 :           debug_sourcefiles.insert("");
    2136         [ #  # ]:          0 :           if (verbose > 3)
    2137                 :          0 :             obatched(clog) << "Unresolved altdebug buildid=" << buildid << endl;
    2138                 :            :         }
    2139                 :            :     }
    2140                 :            : 
    2141                 :         23 :   Dwarf_Off offset = 0;
    2142                 :            :   Dwarf_Off old_offset;
    2143                 :            :   size_t hsize;
    2144                 :            : 
    2145         [ +  + ]:        350 :   while (dwarf_nextcu (dbg, old_offset = offset, &offset, &hsize, NULL, NULL, NULL) == 0)
    2146                 :            :     {
    2147                 :            :       Dwarf_Die cudie_mem;
    2148                 :        327 :       Dwarf_Die *cudie = dwarf_offdie (dbg, old_offset + hsize, &cudie_mem);
    2149                 :            : 
    2150         [ -  + ]:        327 :       if (cudie == NULL)
    2151                 :          4 :         continue;
    2152         [ +  + ]:        327 :       if (dwarf_tag (cudie) != DW_TAG_compile_unit)
    2153                 :          4 :         continue;
    2154                 :            : 
    2155         [ +  - ]:        323 :       const char *cuname = dwarf_diename(cudie) ?: "unknown";
    2156                 :            : 
    2157                 :            :       Dwarf_Files *files;
    2158                 :            :       size_t nfiles;
    2159         [ -  + ]:        323 :       if (dwarf_getsrcfiles (cudie, &files, &nfiles) != 0)
    2160                 :          0 :         continue;
    2161                 :            : 
    2162                 :            :       // extract DW_AT_comp_dir to resolve relative file names
    2163                 :        323 :       const char *comp_dir = "";
    2164                 :            :       const char *const *dirs;
    2165                 :            :       size_t ndirs;
    2166   [ +  -  +  - ]:        646 :       if (dwarf_getsrcdirs (files, &dirs, &ndirs) == 0 &&
    2167         [ +  - ]:        323 :           dirs[0] != NULL)
    2168                 :        323 :         comp_dir = dirs[0];
    2169         [ -  + ]:        323 :       if (comp_dir == NULL)
    2170                 :          0 :         comp_dir = "";
    2171                 :            : 
    2172         [ +  + ]:        323 :       if (verbose > 3)
    2173                 :         34 :         obatched(clog) << "searching for sources for cu=" << cuname << " comp_dir=" << comp_dir
    2174                 :         17 :                        << " #files=" << nfiles << " #dirs=" << ndirs << endl;
    2175                 :            : 
    2176   [ -  +  -  - ]:        323 :       if (comp_dir[0] == '\0' && cuname[0] != '/')
    2177                 :            :         {
    2178                 :            :           // This is a common symptom for dwz-compressed debug files,
    2179                 :            :           // where the altdebug file cannot be resolved.
    2180         [ #  # ]:          0 :           if (verbose > 3)
    2181                 :          0 :             obatched(clog) << "skipping cu=" << cuname << " due to empty comp_dir" << endl;
    2182                 :          0 :           continue;
    2183                 :            :         }
    2184                 :            : 
    2185         [ +  + ]:       5069 :       for (size_t f = 1; f < nfiles; f++)
    2186                 :            :         {
    2187                 :       4746 :           const char *hat = dwarf_filesrc (files, f, NULL, NULL);
    2188         [ -  + ]:       4746 :           if (hat == NULL)
    2189                 :          0 :             continue;
    2190                 :            : 
    2191         [ -  + ]:       4746 :           if (string(hat) == "<built-in>") // gcc intrinsics, don't bother record
    2192                 :          0 :             continue;
    2193                 :            : 
    2194                 :       4746 :           string waldo;
    2195         [ +  + ]:       4746 :           if (hat[0] == '/') // absolute
    2196                 :       2933 :             waldo = (string (hat));
    2197         [ +  - ]:       1813 :           else if (comp_dir[0] != '\0') // comp_dir relative
    2198                 :       1813 :             waldo = (string (comp_dir) + string("/") + string (hat));
    2199                 :            :           else
    2200                 :            :            {
    2201         [ #  # ]:          0 :              if (verbose > 3)
    2202                 :          0 :                obatched(clog) << "skipping hat=" << hat << " due to empty comp_dir" << endl;
    2203                 :          0 :              continue;
    2204                 :            :            }
    2205                 :            : 
    2206                 :            :           // NB: this is the 'waldo' that a dbginfo client will have
    2207                 :            :           // to supply for us to give them the file The comp_dir
    2208                 :            :           // prefixing is a definite complication.  Otherwise we'd
    2209                 :            :           // have to return a setof comp_dirs (one per CU!) with
    2210                 :            :           // corresponding filesrc[] names, instead of one absolute
    2211                 :            :           // resoved set.  Maybe we'll have to do that anyway.  XXX
    2212                 :            : 
    2213         [ -  + ]:       4746 :           if (verbose > 4)
    2214                 :          0 :             obatched(clog) << waldo
    2215         [ #  # ]:          0 :                            << (debug_sourcefiles.find(waldo)==debug_sourcefiles.end() ? " new" : " dup") <<  endl;
    2216                 :            : 
    2217                 :       4746 :           debug_sourcefiles.insert (waldo);
    2218                 :            :         }
    2219                 :            :     }
    2220                 :            : 
    2221                 :         23 :   dwarf_end(dbg);
    2222         [ +  + ]:         23 :   if (altdbg)
    2223                 :          8 :     dwarf_end(altdbg);
    2224         [ +  + ]:         23 :   if (altdbg_fd >= 0)
    2225                 :          8 :     close(altdbg_fd);
    2226                 :            : }
    2227                 :            : 
    2228                 :            : 
    2229                 :            : 
    2230                 :            : static void
    2231                 :        112 : elf_classify (int fd, bool &executable_p, bool &debuginfo_p, string &buildid, set<string>& debug_sourcefiles)
    2232                 :            : {
    2233                 :        112 :   Elf *elf = elf_begin (fd, ELF_C_READ_MMAP_PRIVATE, NULL);
    2234         [ -  + ]:        114 :   if (elf == NULL)
    2235                 :          0 :     return;
    2236                 :            : 
    2237                 :            :   try // catch our types of errors and clean up the Elf* object
    2238                 :            :     {
    2239   [ +  -  +  + ]:        114 :       if (elf_kind (elf) != ELF_K_ELF)
    2240                 :            :         {
    2241         [ +  - ]:         72 :           elf_end (elf);
    2242                 :         72 :           return;
    2243                 :            :         }
    2244                 :            : 
    2245                 :            :       GElf_Ehdr ehdr_storage;
    2246         [ +  - ]:         42 :       GElf_Ehdr *ehdr = gelf_getehdr (elf, &ehdr_storage);
    2247         [ -  + ]:         42 :       if (ehdr == NULL)
    2248                 :            :         {
    2249         [ #  # ]:          0 :           elf_end (elf);
    2250                 :          0 :           return;
    2251                 :            :         }
    2252                 :         42 :       auto elf_type = ehdr->e_type;
    2253                 :            : 
    2254                 :            :       const void *build_id; // elfutils-owned memory
    2255         [ +  - ]:         42 :       ssize_t sz = dwelf_elf_gnu_build_id (elf, & build_id);
    2256         [ -  + ]:         42 :       if (sz <= 0)
    2257                 :            :         {
    2258                 :            :           // It's not a diagnostic-worthy error for an elf file to lack build-id.
    2259                 :            :           // It might just be very old.
    2260         [ #  # ]:          0 :           elf_end (elf);
    2261                 :          0 :           return;
    2262                 :            :         }
    2263                 :            : 
    2264                 :            :       // build_id is a raw byte array; convert to hexadecimal *lowercase*
    2265                 :         42 :       unsigned char* build_id_bytes = (unsigned char*) build_id;
    2266         [ +  + ]:        878 :       for (ssize_t idx=0; idx<sz; idx++)
    2267                 :            :         {
    2268         [ +  - ]:        836 :           buildid += "0123456789abcdef"[build_id_bytes[idx] >> 4];
    2269         [ +  - ]:        833 :           buildid += "0123456789abcdef"[build_id_bytes[idx] & 0xf];
    2270                 :            :         }
    2271                 :            : 
    2272                 :            :       // now decide whether it's an executable - namely, any allocatable section has
    2273                 :            :       // PROGBITS;
    2274   [ +  +  +  + ]:         42 :       if (elf_type == ET_EXEC || elf_type == ET_DYN)
    2275                 :            :         {
    2276                 :            :           size_t shnum;
    2277         [ +  - ]:         36 :           int rc = elf_getshdrnum (elf, &shnum);
    2278         [ -  + ]:         36 :           if (rc < 0)
    2279   [ #  #  #  # ]:          0 :             throw elfutils_exception(rc, "getshdrnum");
    2280                 :            : 
    2281                 :         36 :           executable_p = false;
    2282         [ +  + ]:        680 :           for (size_t sc = 0; sc < shnum; sc++)
    2283                 :            :             {
    2284         [ +  - ]:        661 :               Elf_Scn *scn = elf_getscn (elf, sc);
    2285         [ -  + ]:        661 :               if (scn == NULL)
    2286                 :          0 :                 continue;
    2287                 :            : 
    2288                 :            :               GElf_Shdr shdr_mem;
    2289         [ +  - ]:        661 :               GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
    2290         [ -  + ]:        663 :               if (shdr == NULL)
    2291                 :          0 :                 continue;
    2292                 :            : 
    2293                 :            :               // allocated (loadable / vm-addr-assigned) section with available content?
    2294   [ +  +  +  + ]:        663 :               if ((shdr->sh_type == SHT_PROGBITS) && (shdr->sh_flags & SHF_ALLOC))
    2295                 :            :                 {
    2296         [ -  + ]:         19 :                   if (verbose > 4)
    2297   [ #  #  #  #  :          0 :                     obatched(clog) << "executable due to SHF_ALLOC SHT_PROGBITS sc=" << sc << endl;
             #  #  #  # ]
    2298                 :         19 :                   executable_p = true;
    2299                 :         19 :                   break; // no need to keep looking for others
    2300                 :            :                 }
    2301                 :            :             } // iterate over sections
    2302                 :            :         } // executable_p classification
    2303                 :            : 
    2304                 :            :       // now decide whether it's a debuginfo - namely, if it has any .debug* or .zdebug* sections
    2305                 :            :       // logic mostly stolen from fweimer@redhat.com's elfclassify drafts
    2306                 :            :       size_t shstrndx;
    2307         [ +  - ]:         44 :       int rc = elf_getshdrstrndx (elf, &shstrndx);
    2308         [ -  + ]:         42 :       if (rc < 0)
    2309   [ #  #  #  # ]:          0 :         throw elfutils_exception(rc, "getshdrstrndx");
    2310                 :            : 
    2311                 :         42 :       Elf_Scn *scn = NULL;
    2312                 :         42 :       bool symtab_p = false;
    2313                 :         42 :       bool bits_alloc_p = false;
    2314                 :            :       while (true)
    2315                 :            :         {
    2316         [ +  - ]:       1075 :           scn = elf_nextscn (elf, scn);
    2317         [ +  + ]:       1077 :           if (scn == NULL)
    2318                 :         19 :             break;
    2319                 :            :           GElf_Shdr shdr_storage;
    2320         [ +  - ]:       1058 :           GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_storage);
    2321         [ -  + ]:       1057 :           if (shdr == NULL)
    2322                 :          0 :             break;
    2323         [ +  - ]:       1057 :           const char *section_name = elf_strptr (elf, shstrndx, shdr->sh_name);
    2324         [ -  + ]:       1056 :           if (section_name == NULL)
    2325                 :          0 :             break;
    2326         [ +  + ]:       1056 :           if (strncmp(section_name, ".debug_line", 11) == 0 ||
    2327         [ -  + ]:       1033 :               strncmp(section_name, ".zdebug_line", 12) == 0)
    2328                 :            :             {
    2329                 :         23 :               debuginfo_p = true;
    2330                 :         23 :               dwarf_extract_source_paths (elf, debug_sourcefiles);
    2331                 :         23 :               break; // expecting only one .*debug_line, so no need to look for others
    2332                 :            :             }
    2333         [ +  + ]:       1033 :           else if (strncmp(section_name, ".debug_", 7) == 0 ||
    2334         [ #  + ]:        965 :                    strncmp(section_name, ".zdebug_", 8) == 0)
    2335                 :            :             {
    2336                 :         67 :               debuginfo_p = true;
    2337                 :            :               // NB: don't break; need to parse .debug_line for sources
    2338                 :            :             }
    2339         [ +  + ]:        966 :           else if (shdr->sh_type == SHT_SYMTAB)
    2340                 :            :             {
    2341                 :          1 :               symtab_p = true;
    2342                 :            :             }
    2343         [ +  + ]:        965 :           else if (shdr->sh_type != SHT_NOBITS
    2344         [ +  + ]:        563 :                    && shdr->sh_type != SHT_NOTE
    2345         [ +  + ]:        477 :                    && (shdr->sh_flags & SHF_ALLOC) != 0)
    2346                 :            :             {
    2347                 :        408 :               bits_alloc_p = true;
    2348                 :            :             }
    2349                 :       1033 :         }
    2350                 :            : 
    2351                 :            :       // For more expansive elf/split-debuginfo classification, we
    2352                 :            :       // want to identify as debuginfo "strip -s"-produced files
    2353                 :            :       // without .debug_info* (like libicudata), but we don't want to
    2354                 :            :       // identify "strip -g" executables (with .symtab left there).
    2355   [ +  +  -  + ]:         42 :       if (symtab_p && !bits_alloc_p)
    2356                 :          0 :         debuginfo_p = true;
    2357                 :            :     }
    2358         [ #  # ]:          0 :   catch (const reportable_exception& e)
    2359                 :            :     {
    2360         [ #  # ]:          0 :       e.report(clog);
    2361                 :            :     }
    2362                 :         42 :   elf_end (elf);
    2363                 :            : }
    2364                 :            : 
    2365                 :            : 
    2366                 :            : static void
    2367                 :        173 : scan_source_file (const string& rps, const stat_t& st,
    2368                 :            :                   sqlite_ps& ps_upsert_buildids,
    2369                 :            :                   sqlite_ps& ps_upsert_files,
    2370                 :            :                   sqlite_ps& ps_upsert_de,
    2371                 :            :                   sqlite_ps& ps_upsert_s,
    2372                 :            :                   sqlite_ps& ps_query,
    2373                 :            :                   sqlite_ps& ps_scan_done,
    2374                 :            :                   unsigned& fts_cached,
    2375                 :            :                   unsigned& fts_executable,
    2376                 :            :                   unsigned& fts_debuginfo,
    2377                 :            :                   unsigned& fts_sourcefiles)
    2378                 :            : {
    2379                 :            :   /* See if we know of it already. */
    2380                 :            :   int rc = ps_query
    2381         [ +  - ]:        173 :     .reset()
    2382         [ +  - ]:        173 :     .bind(1, rps)
    2383         [ +  - ]:        173 :     .bind(2, st.st_mtime)
    2384         [ +  - ]:        173 :     .step();
    2385         [ +  - ]:        173 :   ps_query.reset();
    2386         [ +  + ]:        173 :   if (rc == SQLITE_ROW) // i.e., a result, as opposed to DONE (no results)
    2387                 :            :     // no need to recheck a file/version we already know
    2388                 :            :     // specifically, no need to elf-begin a file we already determined is non-elf
    2389                 :            :     // (so is stored with buildid=NULL)
    2390                 :            :     {
    2391                 :        128 :       fts_cached++;
    2392                 :        128 :       return;
    2393                 :            :     }
    2394                 :            : 
    2395                 :         45 :   bool executable_p = false, debuginfo_p = false; // E and/or D
    2396                 :         90 :   string buildid;
    2397                 :         90 :   set<string> sourcefiles;
    2398                 :            : 
    2399         [ +  - ]:         45 :   int fd = open (rps.c_str(), O_RDONLY);
    2400                 :            :   try
    2401                 :            :     {
    2402         [ +  - ]:         45 :       if (fd >= 0)
    2403         [ +  - ]:         45 :         elf_classify (fd, executable_p, debuginfo_p, buildid, sourcefiles);
    2404                 :            :       else
    2405   [ #  #  #  #  :          0 :         throw libc_exception(errno, string("open ") + rps);
                   #  # ]
    2406                 :         45 :       add_metric ("scanned_bytes_total","source","file",
    2407   [ +  -  +  -  :         45 :                   st.st_size);
             +  -  +  - ]
    2408   [ +  -  +  -  :         45 :       inc_metric ("scanned_files_total","source","file");
             +  -  +  - ]
    2409                 :            :     }
    2410                 :            :   // NB: we catch exceptions here too, so that we can
    2411                 :            :   // cache the corrupt-elf case (!executable_p &&
    2412                 :            :   // !debuginfo_p) just below, just as if we had an
    2413                 :            :   // EPERM error from open(2).
    2414         [ -  - ]:          0 :   catch (const reportable_exception& e)
    2415                 :            :     {
    2416         [ -  - ]:          0 :       e.report(clog);
    2417                 :            :     }
    2418                 :            : 
    2419         [ +  - ]:         45 :   if (fd >= 0)
    2420         [ +  - ]:         45 :     close (fd);
    2421                 :            : 
    2422                 :            :   // register this file name in the interning table
    2423                 :            :   ps_upsert_files
    2424         [ +  - ]:         45 :     .reset()
    2425         [ +  - ]:         45 :     .bind(1, rps)
    2426         [ +  - ]:         45 :     .step_ok_done();
    2427                 :            : 
    2428         [ +  + ]:         45 :   if (buildid == "")
    2429                 :            :     {
    2430                 :            :       // no point storing an elf file without buildid
    2431                 :         41 :       executable_p = false;
    2432                 :         41 :       debuginfo_p = false;
    2433                 :            :     }
    2434                 :            :   else
    2435                 :            :     {
    2436                 :            :       // register this build-id in the interning table
    2437                 :            :       ps_upsert_buildids
    2438         [ +  - ]:          4 :         .reset()
    2439         [ +  - ]:          4 :         .bind(1, buildid)
    2440         [ +  - ]:          4 :         .step_ok_done();
    2441                 :            :     }
    2442                 :            : 
    2443         [ +  + ]:         45 :   if (executable_p)
    2444                 :          3 :     fts_executable ++;
    2445         [ +  + ]:         45 :   if (debuginfo_p)
    2446                 :          3 :     fts_debuginfo ++;
    2447   [ +  +  +  + ]:         45 :   if (executable_p || debuginfo_p)
    2448                 :            :     {
    2449                 :            :       ps_upsert_de
    2450         [ +  - ]:          4 :         .reset()
    2451         [ +  - ]:          4 :         .bind(1, buildid)
    2452   [ +  +  +  - ]:          4 :         .bind(2, debuginfo_p ? 1 : 0)
    2453   [ +  +  +  - ]:          4 :         .bind(3, executable_p ? 1 : 0)
    2454         [ +  - ]:          4 :         .bind(4, rps)
    2455         [ +  - ]:          4 :         .bind(5, st.st_mtime)
    2456         [ +  - ]:          4 :         .step_ok_done();
    2457                 :            :     }
    2458         [ +  + ]:         45 :   if (executable_p)
    2459   [ +  -  +  -  :          3 :     inc_metric("found_executable_total","source","files");
             +  -  +  - ]
    2460         [ +  + ]:         45 :   if (debuginfo_p)
    2461   [ +  -  +  -  :          3 :     inc_metric("found_debuginfo_total","source","files");
             +  -  +  - ]
    2462                 :            : 
    2463   [ +  +  +  -  :         45 :   if (sourcefiles.size() && buildid != "")
                   +  + ]
    2464                 :            :     {
    2465                 :          3 :       fts_sourcefiles += sourcefiles.size();
    2466                 :            : 
    2467         [ +  + ]:        484 :       for (auto&& dwarfsrc : sourcefiles)
    2468                 :            :         {
    2469                 :        481 :           char *srp = realpath(dwarfsrc.c_str(), NULL);
    2470         [ +  + ]:        481 :           if (srp == NULL) // also if DWZ unresolved dwarfsrc=""
    2471                 :          6 :             continue; // unresolvable files are not a serious problem
    2472                 :            :           // throw libc_exception(errno, "fts/file realpath " + srcpath);
    2473         [ +  - ]:        475 :           string srps = string(srp);
    2474                 :        475 :           free (srp);
    2475                 :            : 
    2476                 :            :           struct stat sfs;
    2477                 :        475 :           rc = stat(srps.c_str(), &sfs);
    2478         [ -  + ]:        475 :           if (rc != 0)
    2479                 :          0 :             continue;
    2480                 :            : 
    2481         [ +  - ]:        475 :           if (verbose > 2)
    2482   [ +  -  +  - ]:        950 :             obatched(clog) << "recorded buildid=" << buildid << " file=" << srps
    2483   [ +  -  +  -  :        475 :                            << " mtime=" << sfs.st_mtime
          +  -  +  -  +  
                      - ]
    2484   [ +  -  +  -  :        475 :                            << " as source " << dwarfsrc << endl;
                   +  - ]
    2485                 :            : 
    2486                 :            :           ps_upsert_files
    2487         [ +  - ]:        475 :             .reset()
    2488         [ +  - ]:        475 :             .bind(1, srps)
    2489         [ +  - ]:        475 :             .step_ok_done();
    2490                 :            : 
    2491                 :            :           // PR25548: store canonicalized dwarfsrc path
    2492         [ +  - ]:        950 :           string dwarfsrc_canon = canon_pathname (dwarfsrc);
    2493         [ +  + ]:        475 :           if (dwarfsrc_canon != dwarfsrc)
    2494                 :            :             {
    2495         [ +  + ]:         85 :               if (verbose > 3)
    2496   [ +  -  +  -  :          1 :                 obatched(clog) << "canonicalized src=" << dwarfsrc << " alias=" << dwarfsrc_canon << endl;
          +  -  +  -  +  
                -  +  - ]
    2497                 :            :             }
    2498                 :            : 
    2499                 :            :           ps_upsert_files
    2500         [ +  - ]:        475 :             .reset()
    2501         [ +  - ]:        475 :             .bind(1, dwarfsrc_canon)
    2502         [ +  - ]:        475 :             .step_ok_done();
    2503                 :            : 
    2504                 :            :           ps_upsert_s
    2505         [ +  - ]:        475 :             .reset()
    2506         [ +  - ]:        475 :             .bind(1, buildid)
    2507         [ +  - ]:        475 :             .bind(2, dwarfsrc_canon)
    2508         [ +  - ]:        475 :             .bind(3, srps)
    2509         [ +  - ]:        475 :             .bind(4, sfs.st_mtime)
    2510         [ +  - ]:        475 :             .step_ok_done();
    2511                 :            : 
    2512   [ +  -  +  -  :        475 :           inc_metric("found_sourcerefs_total","source","files");
             +  -  +  - ]
    2513                 :            :         }
    2514                 :            :     }
    2515                 :            : 
    2516                 :            :   ps_scan_done
    2517         [ +  - ]:         45 :     .reset()
    2518         [ +  - ]:         45 :     .bind(1, rps)
    2519         [ +  - ]:         45 :     .bind(2, st.st_mtime)
    2520         [ +  - ]:         45 :     .bind(3, st.st_size)
    2521         [ +  - ]:         45 :     .step_ok_done();
    2522                 :            : 
    2523         [ +  - ]:         45 :   if (verbose > 2)
    2524   [ +  -  +  - ]:         90 :     obatched(clog) << "recorded buildid=" << buildid << " file=" << rps
    2525   [ +  -  +  -  :         45 :                    << " mtime=" << st.st_mtime << " atype="
          +  -  +  -  +  
                      - ]
    2526                 :            :                    << (executable_p ? "E" : "")
    2527   [ +  -  +  +  :         45 :                    << (debuginfo_p ? "D" : "") << endl;
          +  -  +  +  +  
                -  +  - ]
    2528                 :            : }
    2529                 :            : 
    2530                 :            : 
    2531                 :            : 
    2532                 :            : 
    2533                 :            : 
    2534                 :            : // Analyze given archive file of given age; record buildids / exec/debuginfo-ness of its
    2535                 :            : // constituent files with given upsert statements.
    2536                 :            : static void
    2537                 :         36 : archive_classify (const string& rps, string& archive_extension,
    2538                 :            :                   sqlite_ps& ps_upsert_buildids, sqlite_ps& ps_upsert_files,
    2539                 :            :                   sqlite_ps& ps_upsert_de, sqlite_ps& ps_upsert_sref, sqlite_ps& ps_upsert_sdef,
    2540                 :            :                   time_t mtime,
    2541                 :            :                   unsigned& fts_executable, unsigned& fts_debuginfo, unsigned& fts_sref, unsigned& fts_sdef,
    2542                 :            :                   bool& fts_sref_complete_p)
    2543                 :            : {
    2544         [ +  - ]:         72 :   string archive_decoder = "/dev/null";
    2545         [ +  + ]:        144 :   for (auto&& arch : scan_archives)
    2546   [ +  -  +  + ]:        108 :     if (string_endswith(rps, arch.first))
    2547                 :            :       {
    2548         [ +  - ]:         36 :         archive_extension = arch.first;
    2549         [ +  - ]:         36 :         archive_decoder = arch.second;
    2550                 :            :       }
    2551                 :            : 
    2552                 :            :   FILE* fp;
    2553                 :            :   defer_dtor<FILE*,int>::dtor_fn dfn;
    2554         [ +  + ]:         36 :   if (archive_decoder != "cat")
    2555                 :            :     {
    2556   [ +  -  +  -  :          9 :       string popen_cmd = archive_decoder + " " + shell_escape(rps);
                   +  - ]
    2557         [ +  - ]:          3 :       fp = popen (popen_cmd.c_str(), "r"); // "e" O_CLOEXEC?
    2558                 :          3 :       dfn = pclose;
    2559         [ -  + ]:          3 :       if (fp == NULL)
    2560   [ #  #  #  #  :          0 :         throw libc_exception (errno, string("popen ") + popen_cmd);
                   #  # ]
    2561                 :            :     }
    2562                 :            :   else
    2563                 :            :     {
    2564         [ +  - ]:         33 :       fp = fopen (rps.c_str(), "r");
    2565                 :         33 :       dfn = fclose;
    2566         [ -  + ]:         33 :       if (fp == NULL)
    2567   [ #  #  #  #  :          0 :         throw libc_exception (errno, string("fopen ") + rps);
                   #  # ]
    2568                 :            :     }
    2569                 :         72 :   defer_dtor<FILE*,int> fp_closer (fp, dfn);
    2570                 :            : 
    2571                 :            :   struct archive *a;
    2572         [ +  - ]:         36 :   a = archive_read_new();
    2573         [ -  + ]:         36 :   if (a == NULL)
    2574   [ #  #  #  # ]:          0 :     throw archive_exception("cannot create archive reader");
    2575                 :         72 :   defer_dtor<struct archive*,int> archive_closer (a, archive_read_free);
    2576                 :            : 
    2577         [ +  - ]:         36 :   int rc = archive_read_support_format_all(a);
    2578         [ -  + ]:         36 :   if (rc != ARCHIVE_OK)
    2579   [ #  #  #  # ]:          0 :     throw archive_exception(a, "cannot select all formats");
    2580         [ +  - ]:         36 :   rc = archive_read_support_filter_all(a);
    2581         [ -  + ]:         36 :   if (rc != ARCHIVE_OK)
    2582   [ #  #  #  # ]:          0 :     throw archive_exception(a, "cannot select all filters");
    2583                 :            : 
    2584         [ +  - ]:         36 :   rc = archive_read_open_FILE (a, fp);
    2585         [ -  + ]:         36 :   if (rc != ARCHIVE_OK)
    2586   [ #  #  #  # ]:          0 :     throw archive_exception(a, "cannot open archive from pipe");
    2587                 :            : 
    2588         [ +  + ]:         36 :   if (verbose > 3)
    2589   [ +  -  +  -  :         34 :     obatched(clog) << "libarchive scanning " << rps << endl;
             +  -  +  - ]
    2590                 :            : 
    2591                 :            :   while(1) // parse archive entries
    2592                 :            :     {
    2593         [ -  + ]:        255 :     if (interrupted)
    2594                 :          0 :       break;
    2595                 :            : 
    2596                 :            :     try
    2597                 :            :         {
    2598                 :            :           struct archive_entry *e;
    2599         [ +  - ]:        255 :           rc = archive_read_next_header (a, &e);
    2600         [ +  + ]:        255 :           if (rc != ARCHIVE_OK)
    2601                 :         36 :             break;
    2602                 :            : 
    2603   [ +  -  +  + ]:        219 :           if (! S_ISREG(archive_entry_mode (e))) // skip non-files completely
    2604                 :        150 :             continue;
    2605                 :            : 
    2606         [ +  - ]:        138 :           string fn = canonicalized_archive_entry_pathname (e);
    2607                 :            : 
    2608         [ +  + ]:         69 :           if (verbose > 3)
    2609   [ +  -  +  -  :         64 :             obatched(clog) << "libarchive checking " << fn << endl;
             +  -  +  - ]
    2610                 :            : 
    2611                 :            :           // extract this file to a temporary file
    2612                 :         69 :           char* tmppath = NULL;
    2613                 :         69 :           rc = asprintf (&tmppath, "%s/debuginfod.XXXXXX", tmpdir.c_str());
    2614         [ -  + ]:         69 :           if (rc < 0)
    2615   [ #  #  #  # ]:          0 :             throw libc_exception (ENOMEM, "cannot allocate tmppath");
    2616                 :        138 :           defer_dtor<void*,void> tmmpath_freer (tmppath, free);
    2617         [ +  - ]:         69 :           int fd = mkstemp (tmppath);
    2618         [ -  + ]:         69 :           if (fd < 0)
    2619   [ #  #  #  # ]:          0 :             throw libc_exception (errno, "cannot create temporary file");
    2620                 :         69 :           unlink (tmppath); // unlink now so OS will release the file as soon as we close the fd
    2621                 :        138 :           defer_dtor<int,int> minifd_closer (fd, close);
    2622                 :            : 
    2623         [ +  - ]:         69 :           rc = archive_read_data_into_fd (a, fd);
    2624         [ -  + ]:         69 :           if (rc != ARCHIVE_OK)
    2625   [ #  #  #  # ]:          0 :             throw archive_exception(a, "cannot extract file");
    2626                 :            : 
    2627                 :            :           // finally ... time to run elf_classify on this bad boy and update the database
    2628                 :         69 :           bool executable_p = false, debuginfo_p = false;
    2629                 :        138 :           string buildid;
    2630                 :        138 :           set<string> sourcefiles;
    2631         [ +  - ]:         69 :           elf_classify (fd, executable_p, debuginfo_p, buildid, sourcefiles);
    2632                 :            :           // NB: might throw
    2633                 :            : 
    2634         [ +  + ]:         69 :           if (buildid != "") // intern buildid
    2635                 :            :             {
    2636                 :            :               ps_upsert_buildids
    2637         [ +  - ]:         38 :                 .reset()
    2638         [ +  - ]:         38 :                 .bind(1, buildid)
    2639         [ +  - ]:         38 :                 .step_ok_done();
    2640                 :            :             }
    2641                 :            : 
    2642                 :            :           ps_upsert_files // register this rpm constituent file name in interning table
    2643         [ +  - ]:         69 :             .reset()
    2644         [ +  - ]:         69 :             .bind(1, fn)
    2645         [ +  - ]:         69 :             .step_ok_done();
    2646                 :            : 
    2647         [ +  + ]:         69 :           if (sourcefiles.size() > 0) // sref records needed
    2648                 :            :             {
    2649                 :            :               // NB: we intern each source file once.  Once raw, as it
    2650                 :            :               // appears in the DWARF file list coming back from
    2651                 :            :               // elf_classify() - because it'll end up in the
    2652                 :            :               // _norm.artifactsrc column.  We don't also put another
    2653                 :            :               // version with a '.' at the front, even though that's
    2654                 :            :               // how rpm/cpio packs names, because we hide that from
    2655                 :            :               // the database for storage efficiency.
    2656                 :            : 
    2657         [ +  + ]:         61 :               for (auto&& s : sourcefiles)
    2658                 :            :                 {
    2659         [ -  + ]:         45 :                   if (s == "")
    2660                 :            :                     {
    2661                 :          0 :                       fts_sref_complete_p = false;
    2662                 :          0 :                       continue;
    2663                 :            :                     }
    2664                 :            : 
    2665                 :            :                   // PR25548: store canonicalized source path
    2666                 :         45 :                   const string& dwarfsrc = s;
    2667         [ +  - ]:         45 :                   string dwarfsrc_canon = canon_pathname (dwarfsrc);
    2668         [ +  + ]:         45 :                   if (dwarfsrc_canon != dwarfsrc)
    2669                 :            :                     {
    2670         [ +  - ]:          2 :                       if (verbose > 3)
    2671   [ +  -  +  -  :          2 :                         obatched(clog) << "canonicalized src=" << dwarfsrc << " alias=" << dwarfsrc_canon << endl;
          +  -  +  -  +  
                -  +  - ]
    2672                 :            :                     }
    2673                 :            : 
    2674                 :            :                   ps_upsert_files
    2675         [ +  - ]:         45 :                     .reset()
    2676         [ +  - ]:         45 :                     .bind(1, dwarfsrc_canon)
    2677         [ +  - ]:         45 :                     .step_ok_done();
    2678                 :            : 
    2679                 :            :                   ps_upsert_sref
    2680         [ +  - ]:         45 :                     .reset()
    2681         [ +  - ]:         45 :                     .bind(1, buildid)
    2682         [ +  - ]:         45 :                     .bind(2, dwarfsrc_canon)
    2683         [ +  - ]:         45 :                     .step_ok_done();
    2684                 :            : 
    2685                 :         45 :                   fts_sref ++;
    2686                 :            :                 }
    2687                 :            :             }
    2688                 :            : 
    2689         [ +  + ]:         69 :           if (executable_p)
    2690                 :         16 :             fts_executable ++;
    2691         [ +  + ]:         69 :           if (debuginfo_p)
    2692                 :         22 :             fts_debuginfo ++;
    2693                 :            : 
    2694   [ +  +  +  + ]:         69 :           if (executable_p || debuginfo_p)
    2695                 :            :             {
    2696                 :            :               ps_upsert_de
    2697         [ +  - ]:         38 :                 .reset()
    2698         [ +  - ]:         38 :                 .bind(1, buildid)
    2699   [ +  +  +  - ]:         38 :                 .bind(2, debuginfo_p ? 1 : 0)
    2700   [ +  +  +  - ]:         38 :                 .bind(3, executable_p ? 1 : 0)
    2701         [ +  - ]:         38 :                 .bind(4, rps)
    2702         [ +  - ]:         38 :                 .bind(5, mtime)
    2703         [ +  - ]:         38 :                 .bind(6, fn)
    2704         [ +  - ]:         38 :                 .step_ok_done();
    2705                 :            :             }
    2706                 :            :           else // potential source - sdef record
    2707                 :            :             {
    2708                 :         31 :               fts_sdef ++;
    2709                 :            :               ps_upsert_sdef
    2710         [ +  - ]:         31 :                 .reset()
    2711         [ +  - ]:         31 :                 .bind(1, rps)
    2712         [ +  - ]:         31 :                 .bind(2, mtime)
    2713         [ +  - ]:         31 :                 .bind(3, fn)
    2714         [ +  - ]:         31 :                 .step_ok_done();
    2715                 :            :             }
    2716                 :            : 
    2717   [ +  -  +  +  :         69 :           if ((verbose > 2) && (executable_p || debuginfo_p))
                   +  + ]
    2718   [ +  -  +  - ]:         76 :             obatched(clog) << "recorded buildid=" << buildid << " rpm=" << rps << " file=" << fn
    2719   [ +  -  +  -  :         38 :                            << " mtime=" << mtime << " atype="
          +  -  +  -  +  
             -  +  -  +  
                      - ]
    2720                 :            :                            << (executable_p ? "E" : "")
    2721                 :            :                            << (debuginfo_p ? "D" : "")
    2722   [ +  -  +  +  :         38 :                            << " sourcefiles=" << sourcefiles.size() << endl;
          +  -  +  +  +  
          -  +  -  +  -  
                   +  - ]
    2723                 :            : 
    2724                 :            :         }
    2725         [ -  - ]:          0 :       catch (const reportable_exception& e)
    2726                 :            :         {
    2727         [ -  - ]:          0 :           e.report(clog);
    2728                 :            :         }
    2729                 :        219 :     }
    2730                 :         36 : }
    2731                 :            : 
    2732                 :            : 
    2733                 :            : 
    2734                 :            : // scan for archive files such as .rpm
    2735                 :            : static void
    2736                 :        118 : scan_archive_file (const string& rps, const stat_t& st,
    2737                 :            :                    sqlite_ps& ps_upsert_buildids,
    2738                 :            :                    sqlite_ps& ps_upsert_files,
    2739                 :            :                    sqlite_ps& ps_upsert_de,
    2740                 :            :                    sqlite_ps& ps_upsert_sref,
    2741                 :            :                    sqlite_ps& ps_upsert_sdef,
    2742                 :            :                    sqlite_ps& ps_query,
    2743                 :            :                    sqlite_ps& ps_scan_done,
    2744                 :            :                    unsigned& fts_cached,
    2745                 :            :                    unsigned& fts_executable,
    2746                 :            :                    unsigned& fts_debuginfo,
    2747                 :            :                    unsigned& fts_sref,
    2748                 :            :                    unsigned& fts_sdef)
    2749                 :            : {
    2750                 :            :   /* See if we know of it already. */
    2751                 :            :   int rc = ps_query
    2752         [ +  - ]:        118 :     .reset()
    2753         [ +  - ]:        118 :     .bind(1, rps)
    2754         [ +  - ]:        118 :     .bind(2, st.st_mtime)
    2755         [ +  - ]:        118 :     .step();
    2756         [ +  - ]:        118 :   ps_query.reset();
    2757         [ +  + ]:        118 :   if (rc == SQLITE_ROW) // i.e., a result, as opposed to DONE (no results)
    2758                 :            :     // no need to recheck a file/version we already know
    2759                 :            :     // specifically, no need to parse this archive again, since we already have
    2760                 :            :     // it as a D or E or S record,
    2761                 :            :     // (so is stored with buildid=NULL)
    2762                 :            :     {
    2763                 :         82 :       fts_cached ++;
    2764                 :         82 :       return;
    2765                 :            :     }
    2766                 :            : 
    2767                 :            :   // intern the archive file name
    2768                 :            :   ps_upsert_files
    2769         [ +  - ]:         36 :     .reset()
    2770         [ +  - ]:         36 :     .bind(1, rps)
    2771         [ +  - ]:         36 :     .step_ok_done();
    2772                 :            : 
    2773                 :            :   // extract the archive contents
    2774                 :         36 :   unsigned my_fts_executable = 0, my_fts_debuginfo = 0, my_fts_sref = 0, my_fts_sdef = 0;
    2775                 :         36 :   bool my_fts_sref_complete_p = true;
    2776                 :            :   try
    2777                 :            :     {
    2778                 :         72 :       string archive_extension;
    2779                 :         36 :       archive_classify (rps, archive_extension,
    2780                 :            :                         ps_upsert_buildids, ps_upsert_files,
    2781                 :            :                         ps_upsert_de, ps_upsert_sref, ps_upsert_sdef, // dalt
    2782         [ +  - ]:         36 :                         st.st_mtime,
    2783                 :            :                         my_fts_executable, my_fts_debuginfo, my_fts_sref, my_fts_sdef,
    2784                 :            :                         my_fts_sref_complete_p);
    2785         [ +  - ]:         72 :       add_metric ("scanned_bytes_total","source",archive_extension + " archive",
    2786   [ +  -  +  -  :        108 :                   st.st_size);
                   +  - ]
    2787   [ +  -  +  -  :         36 :       inc_metric ("scanned_files_total","source",archive_extension + " archive");
             +  -  +  - ]
    2788   [ +  -  +  - ]:         36 :       add_metric("found_debuginfo_total","source",archive_extension + " archive",
    2789   [ +  -  +  - ]:         72 :                  my_fts_debuginfo);
    2790   [ +  -  +  - ]:         36 :       add_metric("found_executable_total","source",archive_extension + " archive",
    2791   [ +  -  +  - ]:         72 :                  my_fts_executable);
    2792   [ +  -  +  - ]:         36 :       add_metric("found_sourcerefs_total","source",archive_extension + " archive",
    2793   [ +  -  +  - ]:         72 :                  my_fts_sref);
    2794                 :            :     }
    2795         [ -  - ]:          0 :   catch (const reportable_exception& e)
    2796                 :            :     {
    2797         [ -  - ]:          0 :       e.report(clog);
    2798                 :            :     }
    2799                 :            : 
    2800         [ +  - ]:         36 :   if (verbose > 2)
    2801   [ +  -  +  - ]:         72 :     obatched(clog) << "scanned archive=" << rps
    2802   [ +  -  +  -  :         36 :                    << " mtime=" << st.st_mtime
                   +  - ]
    2803         [ +  - ]:         36 :                    << " executables=" << my_fts_executable
    2804   [ +  -  +  - ]:         36 :                    << " debuginfos=" << my_fts_debuginfo
    2805   [ +  -  +  - ]:         36 :                    << " srefs=" << my_fts_sref
    2806   [ +  -  +  - ]:         36 :                    << " sdefs=" << my_fts_sdef
    2807   [ +  -  +  - ]:         36 :                    << endl;
    2808                 :            : 
    2809                 :         36 :   fts_executable += my_fts_executable;
    2810                 :         36 :   fts_debuginfo += my_fts_debuginfo;
    2811                 :         36 :   fts_sref += my_fts_sref;
    2812                 :         36 :   fts_sdef += my_fts_sdef;
    2813                 :            : 
    2814         [ +  - ]:         36 :   if (my_fts_sref_complete_p) // leave incomplete?
    2815                 :            :     ps_scan_done
    2816         [ +  - ]:         36 :       .reset()
    2817         [ +  - ]:         36 :       .bind(1, rps)
    2818         [ +  - ]:         36 :       .bind(2, st.st_mtime)
    2819         [ +  - ]:         36 :       .bind(3, st.st_size)
    2820         [ +  - ]:         36 :       .step_ok_done();
    2821                 :            : }
    2822                 :            : 
    2823                 :            : 
    2824                 :            : 
    2825                 :            : ////////////////////////////////////////////////////////////////////////
    2826                 :            : 
    2827                 :            : 
    2828                 :            : 
    2829                 :            : // The thread that consumes file names off of the scanq.  We hold
    2830                 :            : // the persistent sqlite_ps's at this level and delegate file/archive
    2831                 :            : // scanning to other functions.
    2832                 :            : static void*
    2833                 :         12 : thread_main_scanner (void* arg)
    2834                 :            : {
    2835                 :            :   (void) arg;
    2836                 :            : 
    2837                 :            :   // all the prepared statements fit to use, the _f_ set:
    2838   [ +  -  +  -  :         48 :   sqlite_ps ps_f_upsert_buildids (db, "file-buildids-intern", "insert or ignore into " BUILDIDS "_buildids VALUES (NULL, ?);");
                   +  - ]
    2839   [ +  -  +  -  :         48 :   sqlite_ps ps_f_upsert_files (db, "file-files-intern", "insert or ignore into " BUILDIDS "_files VALUES (NULL, ?);");
                   +  - ]
    2840                 :            :   sqlite_ps ps_f_upsert_de (db, "file-de-upsert",
    2841                 :            :                           "insert or ignore into " BUILDIDS "_f_de "
    2842                 :            :                           "(buildid, debuginfo_p, executable_p, file, mtime) "
    2843                 :            :                           "values ((select id from " BUILDIDS "_buildids where hex = ?),"
    2844                 :            :                           "        ?,?,"
    2845   [ +  -  +  -  :         48 :                           "        (select id from " BUILDIDS "_files where name = ?), ?);");
                   +  - ]
    2846                 :            :   sqlite_ps ps_f_upsert_s (db, "file-s-upsert",
    2847                 :            :                          "insert or ignore into " BUILDIDS "_f_s "
    2848                 :            :                          "(buildid, artifactsrc, file, mtime) "
    2849                 :            :                          "values ((select id from " BUILDIDS "_buildids where hex = ?),"
    2850                 :            :                          "        (select id from " BUILDIDS "_files where name = ?),"
    2851                 :            :                          "        (select id from " BUILDIDS "_files where name = ?),"
    2852   [ +  -  +  -  :         48 :                          "        ?);");
                   +  - ]
    2853                 :            :   sqlite_ps ps_f_query (db, "file-negativehit-find",
    2854                 :            :                         "select 1 from " BUILDIDS "_file_mtime_scanned where sourcetype = 'F' "
    2855   [ +  -  +  -  :         48 :                         "and file = (select id from " BUILDIDS "_files where name = ?) and mtime = ?;");
                   +  - ]
    2856                 :            :   sqlite_ps ps_f_scan_done (db, "file-scanned",
    2857                 :            :                           "insert or ignore into " BUILDIDS "_file_mtime_scanned (sourcetype, file, mtime, size)"
    2858   [ +  -  +  -  :         48 :                           "values ('F', (select id from " BUILDIDS "_files where name = ?), ?, ?);");
                   +  - ]
    2859                 :            : 
    2860                 :            :   // and now for the _r_ set
    2861   [ +  -  +  -  :         48 :   sqlite_ps ps_r_upsert_buildids (db, "rpm-buildid-intern", "insert or ignore into " BUILDIDS "_buildids VALUES (NULL, ?);");
                   +  - ]
    2862   [ +  -  +  -  :         48 :   sqlite_ps ps_r_upsert_files (db, "rpm-file-intern", "insert or ignore into " BUILDIDS "_files VALUES (NULL, ?);");
                   +  - ]
    2863                 :            :   sqlite_ps ps_r_upsert_de (db, "rpm-de-insert",
    2864                 :            :                           "insert or ignore into " BUILDIDS "_r_de (buildid, debuginfo_p, executable_p, file, mtime, content) values ("
    2865                 :            :                           "(select id from " BUILDIDS "_buildids where hex = ?), ?, ?, "
    2866                 :            :                           "(select id from " BUILDIDS "_files where name = ?), ?, "
    2867   [ +  -  +  -  :         48 :                           "(select id from " BUILDIDS "_files where name = ?));");
                   +  - ]
    2868                 :            :   sqlite_ps ps_r_upsert_sref (db, "rpm-sref-insert",
    2869                 :            :                             "insert or ignore into " BUILDIDS "_r_sref (buildid, artifactsrc) values ("
    2870                 :            :                             "(select id from " BUILDIDS "_buildids where hex = ?), "
    2871   [ +  -  +  -  :         48 :                             "(select id from " BUILDIDS "_files where name = ?));");
                   +  - ]
    2872                 :            :   sqlite_ps ps_r_upsert_sdef (db, "rpm-sdef-insert",
    2873                 :            :                             "insert or ignore into " BUILDIDS "_r_sdef (file, mtime, content) values ("
    2874                 :            :                             "(select id from " BUILDIDS "_files where name = ?), ?,"
    2875   [ +  -  +  -  :         48 :                             "(select id from " BUILDIDS "_files where name = ?));");
                   +  - ]
    2876                 :            :   sqlite_ps ps_r_query (db, "rpm-negativehit-query",
    2877                 :            :                       "select 1 from " BUILDIDS "_file_mtime_scanned where "
    2878   [ +  -  +  -  :         48 :                       "sourcetype = 'R' and file = (select id from " BUILDIDS "_files where name = ?) and mtime = ?;");
                   +  - ]
    2879                 :            :   sqlite_ps ps_r_scan_done (db, "rpm-scanned",
    2880                 :            :                           "insert or ignore into " BUILDIDS "_file_mtime_scanned (sourcetype, file, mtime, size)"
    2881   [ +  -  +  -  :         36 :                           "values ('R', (select id from " BUILDIDS "_files where name = ?), ?, ?);");
                   +  - ]
    2882                 :            : 
    2883                 :            : 
    2884                 :         12 :   unsigned fts_cached = 0, fts_executable = 0, fts_debuginfo = 0, fts_sourcefiles = 0;
    2885                 :         12 :   unsigned fts_sref = 0, fts_sdef = 0;
    2886                 :            : 
    2887   [ +  -  +  -  :         12 :   add_metric("thread_count", "role", "scan", 1);
             +  -  +  - ]
    2888   [ +  -  +  -  :         12 :   add_metric("thread_busy", "role", "scan", 1);
             +  -  +  - ]
    2889         [ +  + ]:        197 :   while (! interrupted)
    2890                 :            :     {
    2891                 :        185 :       scan_payload p;
    2892                 :            : 
    2893   [ +  -  +  -  :        185 :       add_metric("thread_busy", "role", "scan", -1);
             +  -  +  - ]
    2894         [ +  - ]:        185 :       bool gotone = scanq.wait_front(p);
    2895   [ +  -  +  -  :        185 :       add_metric("thread_busy", "role", "scan", 1);
             +  -  +  - ]
    2896                 :            : 
    2897         [ +  + ]:        185 :       if (! gotone) continue; // go back to waiting
    2898                 :            : 
    2899                 :            :       try
    2900                 :            :         {
    2901                 :        173 :           bool scan_archive = false;
    2902         [ +  + ]:        692 :           for (auto&& arch : scan_archives)
    2903   [ +  -  +  + ]:        519 :             if (string_endswith(p.first, arch.first))
    2904                 :        118 :               scan_archive = true;
    2905                 :            : 
    2906         [ +  + ]:        173 :           if (scan_archive)
    2907         [ +  - ]:        118 :             scan_archive_file (p.first, p.second,
    2908                 :            :                                ps_r_upsert_buildids,
    2909                 :            :                                ps_r_upsert_files,
    2910                 :            :                                ps_r_upsert_de,
    2911                 :            :                                ps_r_upsert_sref,
    2912                 :            :                                ps_r_upsert_sdef,
    2913                 :            :                                ps_r_query,
    2914                 :            :                                ps_r_scan_done,
    2915                 :            :                                fts_cached,
    2916                 :            :                                fts_executable,
    2917                 :            :                                fts_debuginfo,
    2918                 :            :                                fts_sref,
    2919                 :            :                                fts_sdef);
    2920                 :            : 
    2921         [ +  - ]:        173 :           if (scan_files) // NB: maybe "else if" ?
    2922         [ +  - ]:        173 :             scan_source_file (p.first, p.second,
    2923                 :            :                               ps_f_upsert_buildids,
    2924                 :            :                               ps_f_upsert_files,
    2925                 :            :                               ps_f_upsert_de,
    2926                 :            :                               ps_f_upsert_s,
    2927                 :            :                               ps_f_query,
    2928                 :            :                               ps_f_scan_done,
    2929                 :            :                               fts_cached, fts_executable, fts_debuginfo, fts_sourcefiles);
    2930                 :            :         }
    2931         [ -  - ]:          0 :       catch (const reportable_exception& e)
    2932                 :            :         {
    2933         [ -  - ]:          0 :           e.report(cerr);
    2934                 :            :         }
    2935                 :            : 
    2936   [ +  +  +  +  :        173 :       if (fts_cached || fts_executable || fts_debuginfo || fts_sourcefiles || fts_sref || fts_sdef)
          -  +  -  -  -  
                      - ]
    2937                 :            :         {} // NB: not just if a successful scan - we might have encountered -ENOSPC & failed
    2938   [ +  -  +  - ]:        173 :       (void) statfs_free_enough_p(db_path, "database"); // report sqlite filesystem size
    2939   [ +  -  +  - ]:        173 :       (void) statfs_free_enough_p(tmpdir, "tmpdir"); // this too, in case of fdcache/tmpfile usage
    2940                 :            : 
    2941                 :            :       // finished a scanning step -- not a "loop", because we just
    2942                 :            :       // consume the traversal loop's work, whenever
    2943   [ +  -  +  -  :        173 :       inc_metric("thread_work_total","role","scan");
             +  -  +  - ]
    2944                 :            :     }
    2945                 :            : 
    2946                 :            : 
    2947   [ +  -  +  -  :         12 :   add_metric("thread_busy", "role", "scan", -1);
             +  -  +  - ]
    2948                 :         24 :   return 0;
    2949                 :            : }
    2950                 :            : 
    2951                 :            : 
    2952                 :            : 
    2953                 :            : // The thread that traverses all the source_paths and enqueues all the
    2954                 :            : // matching files into the file/archive scan queue.
    2955                 :            : static void
    2956                 :         12 : scan_source_paths()
    2957                 :            : {
    2958                 :            :   // NB: fedora 31 glibc/fts(3) crashes inside fts_read() on empty
    2959                 :            :   // path list.
    2960         [ +  + ]:         12 :   if (source_paths.empty())
    2961                 :          1 :     return;
    2962                 :            : 
    2963                 :            :   // Turn the source_paths into an fts(3)-compatible char**.  Since
    2964                 :            :   // source_paths[] does not change after argv processing, the
    2965                 :            :   // c_str()'s are safe to keep around awile.
    2966                 :         22 :   vector<const char *> sps;
    2967         [ +  + ]:         51 :   for (auto&& sp: source_paths)
    2968         [ +  - ]:         40 :     sps.push_back(sp.c_str());
    2969         [ +  - ]:         11 :   sps.push_back(NULL);
    2970                 :            : 
    2971   [ +  +  +  - ]:         11 :   FTS *fts = fts_open ((char * const *)sps.data(),
    2972                 :            :                       (traverse_logical ? FTS_LOGICAL : FTS_PHYSICAL|FTS_XDEV)
    2973                 :            :                       | FTS_NOCHDIR /* multithreaded */,
    2974                 :            :                       NULL);
    2975         [ -  + ]:         11 :   if (fts == NULL)
    2976   [ #  #  #  # ]:          0 :     throw libc_exception(errno, "cannot fts_open");
    2977                 :         11 :   defer_dtor<FTS*,int> fts_cleanup (fts, fts_close);
    2978                 :            : 
    2979                 :            :   struct timespec ts_start, ts_end;
    2980                 :         11 :   clock_gettime (CLOCK_MONOTONIC, &ts_start);
    2981                 :         11 :   unsigned fts_scanned = 0, fts_regex = 0;
    2982                 :            : 
    2983                 :            :   FTSENT *f;
    2984   [ +  -  +  + ]:        366 :   while ((f = fts_read (fts)) != NULL)
    2985                 :            :   {
    2986         [ -  + ]:        355 :     if (interrupted) break;
    2987                 :            : 
    2988         [ -  + ]:        355 :     if (sigusr2 != forced_groom_count) // stop early if groom triggered
    2989                 :            :       {
    2990         [ #  # ]:          0 :         scanq.clear(); // clear previously issued work for scanner threads
    2991                 :          0 :         break;
    2992                 :            :       }
    2993                 :            : 
    2994                 :        355 :     fts_scanned ++;
    2995                 :            : 
    2996         [ +  - ]:        355 :     if (verbose > 2)
    2997   [ +  -  +  -  :        355 :       obatched(clog) << "fts traversing " << f->fts_path << endl;
             +  -  +  - ]
    2998                 :            : 
    2999   [ +  -  +  +  :        355 :     switch (f->fts_info)
                      + ]
    3000                 :            :       {
    3001                 :        173 :       case FTS_F:
    3002                 :            :         {
    3003                 :            :           /* Found a file.  Convert it to an absolute path, so
    3004                 :            :              the buildid database does not have relative path
    3005                 :            :              names that are unresolvable from a subsequent run
    3006                 :            :              in a different cwd. */
    3007                 :        173 :           char *rp = realpath(f->fts_path, NULL);
    3008         [ -  + ]:        173 :           if (rp == NULL)
    3009                 :          0 :             continue; // ignore dangling symlink or such
    3010         [ +  - ]:        346 :           string rps = string(rp);
    3011                 :        173 :           free (rp);
    3012                 :            : 
    3013         [ +  - ]:        173 :           bool ri = !regexec (&file_include_regex, rps.c_str(), 0, 0, 0);
    3014         [ +  - ]:        173 :           bool rx = !regexec (&file_exclude_regex, rps.c_str(), 0, 0, 0);
    3015   [ +  -  -  + ]:        173 :           if (!ri || rx)
    3016                 :            :             {
    3017         [ #  # ]:          0 :               if (verbose > 3)
    3018   [ #  #  #  # ]:          0 :                 obatched(clog) << "fts skipped by regex "
    3019   [ #  #  #  #  :          0 :                                << (!ri ? "I" : "") << (rx ? "X" : "") << endl;
          #  #  #  #  #  
                      # ]
    3020                 :          0 :               fts_regex ++;
    3021         [ #  # ]:          0 :               if (!ri)
    3022   [ #  #  #  #  :          0 :                 inc_metric("traversed_total","type","file-skipped-I");
             #  #  #  # ]
    3023         [ #  # ]:          0 :               if (rx)
    3024   [ #  #  #  #  :          0 :                 inc_metric("traversed_total","type","file-skipped-X");
             #  #  #  # ]
    3025                 :            :             }
    3026                 :            :           else
    3027                 :            :             {
    3028   [ +  -  +  - ]:        173 :               scanq.push_back (make_pair(rps, *f->fts_statp));
    3029   [ +  -  +  -  :        173 :               inc_metric("traversed_total","type","file");
             +  -  +  - ]
    3030                 :            :             }
    3031                 :            :         }
    3032                 :        173 :         break;
    3033                 :            : 
    3034                 :          0 :       case FTS_ERR:
    3035                 :            :       case FTS_NS:
    3036                 :            :         // report on some types of errors because they may reflect fixable misconfiguration
    3037                 :            :         {
    3038   [ #  #  #  #  :          0 :           auto x = libc_exception(f->fts_errno, string("fts traversal ") + string(f->fts_path));
             #  #  #  # ]
    3039         [ #  # ]:          0 :           x.report(cerr);
    3040                 :            :         }
    3041   [ #  #  #  #  :          0 :         inc_metric("traversed_total","type","error");
             #  #  #  # ]
    3042                 :          0 :         break;
    3043                 :            : 
    3044                 :         18 :       case FTS_SL: // ignore, but count because debuginfod -L would traverse these
    3045   [ +  -  +  -  :         18 :         inc_metric("traversed_total","type","symlink");
             +  -  +  - ]
    3046                 :         18 :         break;
    3047                 :            : 
    3048                 :         82 :       case FTS_D: // ignore
    3049   [ +  -  +  -  :         82 :         inc_metric("traversed_total","type","directory");
             +  -  +  - ]
    3050                 :         82 :         break;
    3051                 :            : 
    3052                 :         82 :       default: // ignore
    3053   [ +  -  +  -  :         82 :         inc_metric("traversed_total","type","other");
             +  -  +  - ]
    3054                 :         82 :         break;
    3055                 :            :       }
    3056                 :            :   }
    3057                 :         11 :   clock_gettime (CLOCK_MONOTONIC, &ts_end);
    3058                 :         11 :   double deltas = (ts_end.tv_sec - ts_start.tv_sec) + (ts_end.tv_nsec - ts_start.tv_nsec)/1.e9;
    3059                 :            : 
    3060   [ +  -  +  -  :         22 :   obatched(clog) << "fts traversed source paths in " << deltas << "s, scanned=" << fts_scanned
          +  -  +  -  +  
                      - ]
    3061   [ +  -  +  -  :         11 :                  << ", regex-skipped=" << fts_regex << endl;
                   +  - ]
    3062                 :            : }
    3063                 :            : 
    3064                 :            : 
    3065                 :            : static void*
    3066                 :          3 : thread_main_fts_source_paths (void* arg)
    3067                 :            : {
    3068                 :            :   (void) arg; // ignore; we operate on global data
    3069                 :            : 
    3070   [ +  -  +  -  :          3 :   set_metric("thread_tid", "role","traverse", tid());
             +  -  +  - ]
    3071   [ +  -  +  -  :          3 :   add_metric("thread_count", "role", "traverse", 1);
             +  -  +  - ]
    3072                 :            : 
    3073                 :          3 :   time_t last_rescan = 0;
    3074                 :            : 
    3075         [ +  - ]:         35 :   while (! interrupted)
    3076                 :            :     {
    3077                 :         35 :       sleep (1);
    3078                 :         35 :       scanq.wait_idle(); // don't start a new traversal while scanners haven't finished the job
    3079                 :         35 :       scanq.done_idle(); // release the hounds
    3080         [ +  + ]:         35 :       if (interrupted) break;
    3081                 :            : 
    3082                 :         32 :       time_t now = time(NULL);
    3083                 :         32 :       bool rescan_now = false;
    3084         [ +  + ]:         32 :       if (last_rescan == 0) // at least one initial rescan is documented even for -t0
    3085                 :          3 :         rescan_now = true;
    3086   [ +  +  +  + ]:         32 :       if (rescan_s > 0 && (long)now > (long)(last_rescan + rescan_s))
    3087                 :          2 :         rescan_now = true;
    3088         [ +  + ]:         32 :       if (sigusr1 != forced_rescan_count)
    3089                 :            :         {
    3090                 :          9 :           forced_rescan_count = sigusr1;
    3091                 :          9 :           rescan_now = true;
    3092                 :            :         }
    3093         [ +  + ]:         32 :       if (rescan_now)
    3094                 :            :         {
    3095   [ +  -  +  -  :         12 :           set_metric("thread_busy", "role","traverse", 1);
             +  -  +  - ]
    3096                 :            :           try
    3097                 :            :             {
    3098         [ +  - ]:         12 :               scan_source_paths();
    3099                 :            :             }
    3100         [ -  - ]:          0 :           catch (const reportable_exception& e)
    3101                 :            :             {
    3102         [ -  - ]:          0 :               e.report(cerr);
    3103                 :            :             }
    3104                 :         12 :           last_rescan = time(NULL); // NB: now was before scanning
    3105                 :            :           // finished a traversal loop
    3106   [ +  -  +  -  :         12 :           inc_metric("thread_work_total", "role","traverse");
             +  -  +  - ]
    3107   [ +  -  +  -  :         12 :           set_metric("thread_busy", "role","traverse", 0);
             +  -  +  - ]
    3108                 :            :         }
    3109                 :            :     }
    3110                 :            : 
    3111                 :          3 :   return 0;
    3112                 :            : }
    3113                 :            : 
    3114                 :            : 
    3115                 :            : 
    3116                 :            : ////////////////////////////////////////////////////////////////////////
    3117                 :            : 
    3118                 :            : static void
    3119                 :          5 : database_stats_report()
    3120                 :            : {
    3121                 :            :   sqlite_ps ps_query (db, "database-overview",
    3122   [ +  -  +  -  :         20 :                       "select label,quantity from " BUILDIDS "_stats");
                   +  - ]
    3123                 :            : 
    3124   [ +  -  +  -  :          5 :   obatched(clog) << "database record counts:" << endl;
                   +  - ]
    3125                 :            :   while (1)
    3126                 :            :     {
    3127         [ -  + ]:         55 :       if (interrupted) break;
    3128         [ -  + ]:         55 :       if (sigusr1 != forced_rescan_count) // stop early if scan triggered
    3129                 :          0 :         break;
    3130                 :            : 
    3131         [ +  - ]:         55 :       int rc = ps_query.step();
    3132         [ +  + ]:         55 :       if (rc == SQLITE_DONE) break;
    3133         [ -  + ]:         50 :       if (rc != SQLITE_ROW)
    3134   [ #  #  #  # ]:          0 :         throw sqlite_exception(rc, "step");
    3135                 :            : 
    3136         [ +  - ]:        100 :       obatched(clog)
    3137   [ +  -  +  - ]:        100 :         << right << setw(20) << ((const char*) sqlite3_column_text(ps_query, 0) ?: (const char*) "NULL")
    3138                 :            :         << " "
    3139   [ +  -  +  -  :        100 :         << (sqlite3_column_text(ps_query, 1) ?: (const unsigned char*) "NULL")
          +  -  +  -  +  
             -  +  -  +  
                      - ]
    3140         [ +  - ]:         50 :         << endl;
    3141                 :            : 
    3142   [ +  -  +  - ]:         50 :       set_metric("groom", "statistic",
    3143         [ +  - ]:         50 :                  ((const char*) sqlite3_column_text(ps_query, 0) ?: (const char*) "NULL"),
    3144   [ +  -  +  -  :        150 :                  (sqlite3_column_double(ps_query, 1)));
             +  -  +  - ]
    3145                 :         50 :     }
    3146                 :          5 : }
    3147                 :            : 
    3148                 :            : 
    3149                 :            : // Do a round of database grooming that might take many minutes to run.
    3150                 :          6 : void groom()
    3151                 :            : {
    3152   [ +  -  +  -  :          6 :   obatched(clog) << "grooming database" << endl;
                   +  - ]
    3153                 :            : 
    3154                 :            :   struct timespec ts_start, ts_end;
    3155                 :          6 :   clock_gettime (CLOCK_MONOTONIC, &ts_start);
    3156                 :            : 
    3157                 :            :   // scan for files that have disappeared
    3158                 :            :   sqlite_ps files (db, "check old files", "select s.mtime, s.file, f.name from "
    3159                 :            :                        BUILDIDS "_file_mtime_scanned s, " BUILDIDS "_files f "
    3160   [ +  -  +  -  :         19 :                        "where f.id = s.file");
                   +  - ]
    3161   [ +  -  +  -  :         19 :   sqlite_ps files_del_f_de (db, "nuke f_de", "delete from " BUILDIDS "_f_de where file = ? and mtime = ?");
                   +  - ]
    3162   [ +  -  +  -  :         19 :   sqlite_ps files_del_r_de (db, "nuke r_de", "delete from " BUILDIDS "_r_de where file = ? and mtime = ?");
                   +  - ]
    3163                 :            :   sqlite_ps files_del_scan (db, "nuke f_m_s", "delete from " BUILDIDS "_file_mtime_scanned "
    3164   [ +  -  +  -  :         19 :                             "where file = ? and mtime = ?");
                   +  - ]
    3165         [ +  - ]:          6 :   files.reset();
    3166                 :            :   while(1)
    3167                 :            :     {
    3168         [ -  + ]:        154 :       if (interrupted) break;
    3169                 :            : 
    3170         [ +  - ]:        154 :       int rc = files.step();
    3171         [ +  + ]:        154 :       if (rc != SQLITE_ROW)
    3172                 :          6 :         break;
    3173                 :            : 
    3174         [ +  - ]:        148 :       int64_t mtime = sqlite3_column_int64 (files, 0);
    3175         [ +  - ]:        148 :       int64_t fileid = sqlite3_column_int64 (files, 1);
    3176   [ +  -  +  - ]:        148 :       const char* filename = ((const char*) sqlite3_column_text (files, 2) ?: "");
    3177                 :            :       struct stat s;
    3178                 :        148 :       rc = stat(filename, &s);
    3179   [ +  +  -  + ]:        148 :       if (rc < 0 || (mtime != (int64_t) s.st_mtime))
    3180                 :            :         {
    3181         [ +  - ]:         16 :           if (verbose > 2)
    3182   [ +  -  +  -  :         16 :             obatched(clog) << "groom: forgetting file=" << filename << " mtime=" << mtime << endl;
          +  -  +  -  +  
                -  +  - ]
    3183   [ +  -  +  -  :         16 :           files_del_f_de.reset().bind(1,fileid).bind(2,mtime).step_ok_done();
             +  -  +  - ]
    3184   [ +  -  +  -  :         16 :           files_del_r_de.reset().bind(1,fileid).bind(2,mtime).step_ok_done();
             +  -  +  - ]
    3185   [ +  -  +  -  :         16 :           files_del_scan.reset().bind(1,fileid).bind(2,mtime).step_ok_done();
             +  -  +  - ]
    3186   [ +  -  +  -  :         16 :           inc_metric("groomed_total", "decision", "stale");
             +  -  +  - ]
    3187                 :            :         }
    3188                 :            :       else
    3189   [ +  -  +  -  :        132 :         inc_metric("groomed_total", "decision", "fresh");
             +  -  +  - ]
    3190                 :            : 
    3191         [ -  + ]:        148 :       if (sigusr1 != forced_rescan_count) // stop early if scan triggered
    3192                 :          0 :         break;
    3193                 :        148 :     }
    3194         [ +  - ]:          6 :   files.reset();
    3195                 :            : 
    3196                 :            :   // delete buildids with no references in _r_de or _f_de tables;
    3197                 :            :   // cascades to _r_sref & _f_s records
    3198                 :            :   sqlite_ps buildids_del (db, "nuke orphan buildids",
    3199                 :            :                           "delete from " BUILDIDS "_buildids "
    3200                 :            :                           "where not exists (select 1 from " BUILDIDS "_f_de d where " BUILDIDS "_buildids.id = d.buildid) "
    3201   [ +  -  +  -  :         19 :                           "and not exists (select 1 from " BUILDIDS "_r_de d where " BUILDIDS "_buildids.id = d.buildid)");
                   +  - ]
    3202   [ +  -  +  - ]:          6 :   buildids_del.reset().step_ok_done();
    3203                 :            : 
    3204         [ -  + ]:          6 :   if (interrupted) return;
    3205                 :            : 
    3206                 :            :   // NB: "vacuum" is too heavy for even daily runs: it rewrites the entire db, so is done as maxigroom -G
    3207   [ +  -  +  -  :         24 :   sqlite_ps g1 (db, "incremental vacuum", "pragma incremental_vacuum");
                   +  - ]
    3208   [ +  -  +  - ]:          6 :   g1.reset().step_ok_done();
    3209   [ +  -  +  -  :         24 :   sqlite_ps g2 (db, "optimize", "pragma optimize");
                   +  - ]
    3210   [ +  -  +  + ]:          6 :   g2.reset().step_ok_done();
    3211   [ +  -  +  -  :         15 :   sqlite_ps g3 (db, "wal checkpoint", "pragma wal_checkpoint=truncate");
                   +  - ]
    3212   [ +  -  +  - ]:          5 :   g3.reset().step_ok_done();
    3213                 :            : 
    3214         [ +  - ]:          5 :   database_stats_report();
    3215                 :            : 
    3216   [ +  -  +  - ]:          5 :   (void) statfs_free_enough_p(db_path, "database"); // report sqlite filesystem size
    3217                 :            : 
    3218         [ +  - ]:          5 :   sqlite3_db_release_memory(db); // shrink the process if possible
    3219         [ +  - ]:          5 :   sqlite3_db_release_memory(dbq); // ... for both connections
    3220         [ +  - ]:          5 :   debuginfod_pool_groom(); // and release any debuginfod_client objects we've been holding onto
    3221                 :            : 
    3222         [ +  - ]:          5 :   fdcache.limit(0,0); // release the fdcache contents
    3223         [ +  - ]:          5 :   fdcache.limit(fdcache_fds,fdcache_mbs); // restore status quo parameters
    3224                 :            : 
    3225                 :          5 :   clock_gettime (CLOCK_MONOTONIC, &ts_end);
    3226                 :          5 :   double deltas = (ts_end.tv_sec - ts_start.tv_sec) + (ts_end.tv_nsec - ts_start.tv_nsec)/1.e9;
    3227                 :            : 
    3228   [ +  -  +  -  :          5 :   obatched(clog) << "groomed database in " << deltas << "s" << endl;
          +  -  +  -  +  
                      - ]
    3229                 :            : }
    3230                 :            : 
    3231                 :            : 
    3232                 :            : static void*
    3233                 :          3 : thread_main_groom (void* /*arg*/)
    3234                 :            : {
    3235   [ +  -  +  -  :          3 :   set_metric("thread_tid", "role", "groom", tid());
             +  -  +  - ]
    3236   [ +  -  +  -  :          3 :   add_metric("thread_count", "role", "groom", 1);
             +  -  +  - ]
    3237                 :            : 
    3238                 :          3 :   time_t last_groom = 0;
    3239                 :            : 
    3240                 :            :   while (1)
    3241                 :            :     {
    3242                 :         35 :       sleep (1);
    3243                 :         35 :       scanq.wait_idle(); // PR25394: block scanners during grooming!
    3244         [ +  + ]:         35 :       if (interrupted) break;
    3245                 :            : 
    3246                 :         32 :       time_t now = time(NULL);
    3247                 :         32 :       bool groom_now = false;
    3248         [ +  + ]:         32 :       if (last_groom == 0) // at least one initial groom is documented even for -g0
    3249                 :          3 :         groom_now = true;
    3250   [ +  +  +  + ]:         32 :       if (groom_s > 0 && (long)now > (long)(last_groom + groom_s))
    3251                 :          2 :         groom_now = true;
    3252         [ +  + ]:         32 :       if (sigusr2 != forced_groom_count)
    3253                 :            :         {
    3254                 :          3 :           forced_groom_count = sigusr2;
    3255                 :          3 :           groom_now = true;
    3256                 :            :         }
    3257         [ +  + ]:         32 :       if (groom_now)
    3258                 :            :         {
    3259   [ +  -  +  -  :          6 :           set_metric("thread_busy", "role", "groom", 1);
             +  -  +  - ]
    3260                 :            :           try
    3261                 :            :             {
    3262         [ +  + ]:          6 :               groom ();
    3263                 :            :             }
    3264         [ -  + ]:          1 :           catch (const sqlite_exception& e)
    3265                 :            :             {
    3266   [ +  -  +  -  :          1 :               obatched(cerr) << e.message << endl;
                   +  - ]
    3267                 :            :             }
    3268                 :          6 :           last_groom = time(NULL); // NB: now was before grooming
    3269                 :            :           // finished a grooming loop
    3270   [ +  -  +  -  :          6 :           inc_metric("thread_work_total", "role", "groom");
             +  -  +  - ]
    3271   [ +  -  +  -  :          6 :           set_metric("thread_busy", "role", "groom", 0);
             +  -  +  - ]
    3272                 :            :         }
    3273                 :            : 
    3274                 :         32 :       scanq.done_idle();
    3275                 :         32 :     }
    3276                 :            : 
    3277                 :          3 :   return 0;
    3278                 :            : }
    3279                 :            : 
    3280                 :            : 
    3281                 :            : ////////////////////////////////////////////////////////////////////////
    3282                 :            : 
    3283                 :            : 
    3284                 :            : static void
    3285                 :          3 : signal_handler (int /* sig */)
    3286                 :            : {
    3287                 :          3 :   interrupted ++;
    3288                 :            : 
    3289         [ +  - ]:          3 :   if (db)
    3290                 :          3 :     sqlite3_interrupt (db);
    3291         [ +  - ]:          3 :   if (dbq)
    3292                 :          3 :     sqlite3_interrupt (dbq);
    3293                 :            : 
    3294                 :            :   // NB: don't do anything else in here
    3295                 :          3 : }
    3296                 :            : 
    3297                 :            : static void
    3298                 :          9 : sigusr1_handler (int /* sig */)
    3299                 :            : {
    3300                 :          9 :    sigusr1 ++;
    3301                 :            :   // NB: don't do anything else in here
    3302                 :          9 : }
    3303                 :            : 
    3304                 :            : static void
    3305                 :          3 : sigusr2_handler (int /* sig */)
    3306                 :            : {
    3307                 :          3 :    sigusr2 ++;
    3308                 :            :   // NB: don't do anything else in here
    3309                 :          3 : }
    3310                 :            : 
    3311                 :            : 
    3312                 :            : 
    3313                 :            : 
    3314                 :            : 
    3315                 :            : // A user-defined sqlite function, to score the sharedness of the
    3316                 :            : // prefix of two strings.  This is used to compare candidate debuginfo
    3317                 :            : // / source-rpm names, so that the closest match
    3318                 :            : // (directory-topology-wise closest) is found.  This is important in
    3319                 :            : // case the same sref (source file name) is in many -debuginfo or
    3320                 :            : // -debugsource RPMs, such as when multiple versions/releases of the
    3321                 :            : // same package are in the database.
    3322                 :            : 
    3323                 :        109 : static void sqlite3_sharedprefix_fn (sqlite3_context* c, int argc, sqlite3_value** argv)
    3324                 :            : {
    3325         [ -  + ]:        109 :   if (argc != 2)
    3326                 :          0 :     sqlite3_result_error(c, "expect 2 string arguments", -1);
    3327   [ +  -  +  + ]:        218 :   else if ((sqlite3_value_type(argv[0]) != SQLITE_TEXT) ||
    3328         [ +  + ]:        109 :            (sqlite3_value_type(argv[1]) != SQLITE_TEXT))
    3329                 :          3 :     sqlite3_result_null(c);
    3330                 :            :   else
    3331                 :            :     {
    3332                 :        106 :       const unsigned char* a = sqlite3_value_text (argv[0]);
    3333                 :        106 :       const unsigned char* b = sqlite3_value_text (argv[1]);
    3334                 :        106 :       int i = 0;
    3335         [ +  + ]:       8516 :       while (*a++ == *b++)
    3336                 :       8410 :         i++;
    3337                 :        106 :       sqlite3_result_int (c, i);
    3338                 :            :     }
    3339                 :        109 : }
    3340                 :            : 
    3341                 :            : 
    3342                 :            : int
    3343                 :          3 : main (int argc, char *argv[])
    3344                 :            : {
    3345                 :          3 :   (void) setlocale (LC_ALL, "");
    3346                 :          3 :   (void) bindtextdomain (PACKAGE_TARNAME, LOCALEDIR);
    3347                 :          3 :   (void) textdomain (PACKAGE_TARNAME);
    3348                 :            : 
    3349                 :            :   /* Tell the library which version we are expecting.  */
    3350         [ +  - ]:          3 :   elf_version (EV_CURRENT);
    3351                 :            : 
    3352   [ -  +  +  - ]:          3 :   tmpdir = string(getenv("TMPDIR") ?: "/tmp");
    3353                 :            : 
    3354                 :            :   /* Set computed default values. */
    3355   [ +  -  +  -  :          3 :   db_path = string(getenv("HOME") ?: "/") + string("/.debuginfod.sqlite"); /* XDG? */
             +  -  +  - ]
    3356         [ +  - ]:          3 :   int rc = regcomp (& file_include_regex, ".*", REG_EXTENDED|REG_NOSUB); // match everything
    3357         [ -  + ]:          3 :   if (rc != 0)
    3358                 :            :     error (EXIT_FAILURE, 0, "regcomp failure: %d", rc);
    3359         [ +  - ]:          3 :   rc = regcomp (& file_exclude_regex, "^$", REG_EXTENDED|REG_NOSUB); // match nothing
    3360         [ -  + ]:          3 :   if (rc != 0)
    3361                 :            :     error (EXIT_FAILURE, 0, "regcomp failure: %d", rc);
    3362                 :            : 
    3363                 :            :   // default parameters for fdcache are computed from system stats
    3364                 :            :   struct statfs sfs;
    3365                 :          3 :   rc = statfs(tmpdir.c_str(), &sfs);
    3366         [ -  + ]:          3 :   if (rc < 0)
    3367                 :          0 :     fdcache_mbs = 1024; // 1 gigabyte
    3368                 :            :   else
    3369                 :          3 :     fdcache_mbs = sfs.f_bavail * sfs.f_bsize / 1024 / 1024 / 4; // 25% of free space
    3370                 :          3 :   fdcache_mintmp = 25; // emergency flush at 25% remaining (75% full)
    3371                 :          3 :   fdcache_prefetch = 64; // guesstimate storage is this much less costly than re-decompression
    3372                 :          3 :   fdcache_fds = (concurrency + fdcache_prefetch) * 2;
    3373                 :            : 
    3374                 :            :   /* Parse and process arguments.  */
    3375                 :            :   int remaining;
    3376                 :          3 :   argp_program_version_hook = print_version; // this works
    3377         [ +  - ]:          3 :   (void) argp_parse (&argp, argc, argv, ARGP_IN_ORDER, &remaining, NULL);
    3378         [ -  + ]:          3 :   if (remaining != argc)
    3379                 :          0 :       error (EXIT_FAILURE, 0,
    3380                 :          0 :              "unexpected argument: %s", argv[remaining]);
    3381                 :            : 
    3382   [ -  +  -  -  :          3 :   if (scan_archives.size()==0 && !scan_files && source_paths.size()>0)
             -  -  -  + ]
    3383   [ #  #  #  #  :          0 :     obatched(clog) << "warning: without -F -R -U -Z, ignoring PATHs" << endl;
                   #  # ]
    3384                 :            : 
    3385         [ +  - ]:          3 :   fdcache.limit(fdcache_fds, fdcache_mbs);
    3386                 :            : 
    3387                 :          3 :   (void) signal (SIGPIPE, SIG_IGN); // microhttpd can generate it incidentally, ignore
    3388                 :          3 :   (void) signal (SIGINT, signal_handler); // ^C
    3389                 :          3 :   (void) signal (SIGHUP, signal_handler); // EOF
    3390                 :          3 :   (void) signal (SIGTERM, signal_handler); // systemd
    3391                 :          3 :   (void) signal (SIGUSR1, sigusr1_handler); // end-user
    3392                 :          3 :   (void) signal (SIGUSR2, sigusr2_handler); // end-user
    3393                 :            : 
    3394                 :            :   /* Get database ready. */
    3395         [ +  - ]:          3 :   rc = sqlite3_open_v2 (db_path.c_str(), &db, (SQLITE_OPEN_READWRITE
    3396                 :            :                                                |SQLITE_OPEN_URI
    3397                 :            :                                                |SQLITE_OPEN_PRIVATECACHE
    3398                 :            :                                                |SQLITE_OPEN_CREATE
    3399                 :            :                                                |SQLITE_OPEN_FULLMUTEX), /* thread-safe */
    3400                 :            :                         NULL);
    3401         [ -  + ]:          3 :   if (rc == SQLITE_CORRUPT)
    3402                 :            :     {
    3403                 :          0 :       (void) unlink (db_path.c_str());
    3404         [ #  # ]:          0 :       error (EXIT_FAILURE, 0,
    3405                 :            :              "cannot open %s, deleted database: %s", db_path.c_str(), sqlite3_errmsg(db));
    3406                 :            :     }
    3407         [ -  + ]:          3 :   else if (rc)
    3408                 :            :     {
    3409         [ #  # ]:          0 :       error (EXIT_FAILURE, 0,
    3410                 :            :              "cannot open %s, consider deleting database: %s", db_path.c_str(), sqlite3_errmsg(db));
    3411                 :            :     }
    3412                 :            : 
    3413                 :            :   // open the readonly query variant
    3414                 :            :   // NB: PRIVATECACHE allows web queries to operate in parallel with
    3415                 :            :   // much other grooming/scanning operation.
    3416         [ +  - ]:          3 :   rc = sqlite3_open_v2 (db_path.c_str(), &dbq, (SQLITE_OPEN_READONLY
    3417                 :            :                                                 |SQLITE_OPEN_URI
    3418                 :            :                                                 |SQLITE_OPEN_PRIVATECACHE
    3419                 :            :                                                 |SQLITE_OPEN_FULLMUTEX), /* thread-safe */
    3420                 :            :                         NULL);
    3421         [ -  + ]:          3 :   if (rc)
    3422                 :            :     {
    3423         [ #  # ]:          0 :       error (EXIT_FAILURE, 0,
    3424                 :            :              "cannot open %s, consider deleting database: %s", db_path.c_str(), sqlite3_errmsg(dbq));
    3425                 :            :     }
    3426                 :            : 
    3427                 :            : 
    3428   [ +  -  +  -  :          3 :   obatched(clog) << "opened database " << db_path << endl;
             +  -  +  - ]
    3429   [ +  -  +  -  :          3 :   obatched(clog) << "sqlite version " << sqlite3_version << endl;
             +  -  +  - ]
    3430                 :            : 
    3431                 :            :   // add special string-prefix-similarity function used in rpm sref/sdef resolution
    3432         [ +  - ]:          3 :   rc = sqlite3_create_function(dbq, "sharedprefix", 2, SQLITE_UTF8, NULL,
    3433                 :            :                                & sqlite3_sharedprefix_fn, NULL, NULL);
    3434         [ -  + ]:          3 :   if (rc != SQLITE_OK)
    3435         [ #  # ]:          0 :     error (EXIT_FAILURE, 0,
    3436                 :            :            "cannot create sharedprefix function: %s", sqlite3_errmsg(dbq));
    3437                 :            : 
    3438         [ +  + ]:          3 :   if (verbose > 3)
    3439   [ +  -  +  -  :          1 :     obatched(clog) << "ddl: " << DEBUGINFOD_SQLITE_DDL << endl;
             +  -  +  - ]
    3440         [ +  - ]:          3 :   rc = sqlite3_exec (db, DEBUGINFOD_SQLITE_DDL, NULL, NULL, NULL);
    3441         [ -  + ]:          3 :   if (rc != SQLITE_OK)
    3442                 :            :     {
    3443         [ #  # ]:          0 :       error (EXIT_FAILURE, 0,
    3444                 :            :              "cannot run database schema ddl: %s", sqlite3_errmsg(db));
    3445                 :            :     }
    3446                 :            : 
    3447                 :            :   // Start httpd server threads.  Separate pool for IPv4 and IPv6, in
    3448                 :            :   // case the host only has one protocol stack.
    3449         [ +  - ]:          3 :   MHD_Daemon *d4 = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION
    3450                 :            : #if MHD_VERSION >= 0x00095300
    3451                 :            :                                      | MHD_USE_INTERNAL_POLLING_THREAD
    3452                 :            : #else
    3453                 :            :                                      | MHD_USE_SELECT_INTERNALLY
    3454                 :            : #endif
    3455                 :            :                                      | MHD_USE_DEBUG, /* report errors to stderr */
    3456                 :            :                                      http_port,
    3457                 :            :                                      NULL, NULL, /* default accept policy */
    3458                 :            :                                      handler_cb, NULL, /* handler callback */
    3459                 :            :                                      MHD_OPTION_END);
    3460         [ +  - ]:          3 :   MHD_Daemon *d6 = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION
    3461                 :            : #if MHD_VERSION >= 0x00095300
    3462                 :            :                                      | MHD_USE_INTERNAL_POLLING_THREAD
    3463                 :            : #else
    3464                 :            :                                      | MHD_USE_SELECT_INTERNALLY
    3465                 :            : #endif
    3466                 :            :                                      | MHD_USE_IPv6
    3467                 :            :                                      | MHD_USE_DEBUG, /* report errors to stderr */
    3468                 :            :                                      http_port,
    3469                 :            :                                      NULL, NULL, /* default accept policy */
    3470                 :            :                                      handler_cb, NULL, /* handler callback */
    3471                 :            :                                      MHD_OPTION_END);
    3472                 :            : 
    3473   [ -  +  -  - ]:          3 :   if (d4 == NULL && d6 == NULL) // neither ipv4 nor ipv6? boo
    3474                 :            :     {
    3475                 :          0 :       sqlite3 *database = db;
    3476                 :          0 :       sqlite3 *databaseq = dbq;
    3477                 :          0 :       db = dbq = 0; // for signal_handler not to freak
    3478         [ #  # ]:          0 :       sqlite3_close (databaseq);
    3479         [ #  # ]:          0 :       sqlite3_close (database);
    3480                 :          0 :       error (EXIT_FAILURE, 0, "cannot start http server at port %d", http_port);
    3481                 :            :     }
    3482                 :            : 
    3483   [ +  -  +  - ]:          6 :   obatched(clog) << "started http server on "
    3484                 :            :                  << (d4 != NULL ? "IPv4 " : "")
    3485                 :            :                  << (d6 != NULL ? "IPv6 " : "")
    3486   [ +  -  +  -  :          3 :                  << "port=" << http_port << endl;
          +  -  +  -  +  
             -  +  -  +  
                      - ]
    3487                 :            : 
    3488                 :            :   // add maxigroom sql if -G given
    3489         [ -  + ]:          3 :   if (maxigroom)
    3490                 :            :     {
    3491   [ #  #  #  #  :          0 :       obatched(clog) << "maxigrooming database, please wait." << endl;
                   #  # ]
    3492   [ #  #  #  # ]:          0 :       extra_ddl.push_back("create index if not exists " BUILDIDS "_r_sref_arc on " BUILDIDS "_r_sref(artifactsrc);");
    3493   [ #  #  #  # ]:          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);");
    3494   [ #  #  #  # ]:          0 :       extra_ddl.push_back("drop index if exists " BUILDIDS "_r_sref_arc;");
    3495                 :            : 
    3496                 :            :       // NB: we don't maxigroom the _files interning table.  It'd require a temp index on all the
    3497                 :            :       // tables that have file foreign-keys, which is a lot.
    3498                 :            : 
    3499                 :            :       // NB: with =delete, may take up 3x disk space total during vacuum process
    3500                 :            :       //     vs.  =off (only 2x but may corrupt database if program dies mid-vacuum)
    3501                 :            :       //     vs.  =wal (>3x observed, but safe)
    3502   [ #  #  #  # ]:          0 :       extra_ddl.push_back("pragma journal_mode=delete;");
    3503   [ #  #  #  # ]:          0 :       extra_ddl.push_back("vacuum;");
    3504   [ #  #  #  # ]:          0 :       extra_ddl.push_back("pragma journal_mode=wal;");
    3505                 :            :     }
    3506                 :            : 
    3507                 :            :   // run extra -D sql if given
    3508         [ -  + ]:          3 :   for (auto&& i: extra_ddl)
    3509                 :            :     {
    3510         [ #  # ]:          0 :       if (verbose > 1)
    3511   [ #  #  #  #  :          0 :         obatched(clog) << "extra ddl:\n" << i << endl;
             #  #  #  # ]
    3512         [ #  # ]:          0 :       rc = sqlite3_exec (db, i.c_str(), NULL, NULL, NULL);
    3513   [ #  #  #  #  :          0 :       if (rc != SQLITE_OK && rc != SQLITE_DONE && rc != SQLITE_ROW)
                   #  # ]
    3514         [ #  # ]:          0 :         error (0, 0,
    3515                 :            :                "warning: cannot run database extra ddl %s: %s", i.c_str(), sqlite3_errmsg(db));
    3516                 :            :     }
    3517                 :            : 
    3518         [ -  + ]:          3 :   if (maxigroom)
    3519   [ #  #  #  #  :          0 :     obatched(clog) << "maxigroomed database" << endl;
                   #  # ]
    3520                 :            : 
    3521   [ +  -  +  -  :          3 :   obatched(clog) << "search concurrency " << concurrency << endl;
             +  -  +  - ]
    3522   [ +  -  +  -  :          3 :   obatched(clog) << "rescan time " << rescan_s << endl;
             +  -  +  - ]
    3523   [ +  -  +  -  :          3 :   obatched(clog) << "fdcache fds " << fdcache_fds << endl;
             +  -  +  - ]
    3524   [ +  -  +  -  :          3 :   obatched(clog) << "fdcache mbs " << fdcache_mbs << endl;
             +  -  +  - ]
    3525   [ +  -  +  -  :          3 :   obatched(clog) << "fdcache prefetch " << fdcache_prefetch << endl;
             +  -  +  - ]
    3526   [ +  -  +  -  :          3 :   obatched(clog) << "fdcache tmpdir " << tmpdir << endl;
             +  -  +  - ]
    3527   [ +  -  +  -  :          3 :   obatched(clog) << "fdcache tmpdir min% " << fdcache_mintmp << endl;
             +  -  +  - ]
    3528   [ +  -  +  -  :          3 :   obatched(clog) << "groom time " << groom_s << endl;
             +  -  +  - ]
    3529         [ +  - ]:          3 :   if (scan_archives.size()>0)
    3530                 :            :     {
    3531         [ +  - ]:          6 :       obatched ob(clog);
    3532         [ +  - ]:          3 :       auto& o = ob << "scanning archive types ";
    3533         [ +  + ]:         12 :       for (auto&& arch : scan_archives)
    3534   [ +  -  +  -  :          9 :         o << arch.first << "(" << arch.second << ") ";
             +  -  +  - ]
    3535         [ +  - ]:          3 :       o << endl;
    3536                 :            :     }
    3537                 :          3 :   const char* du = getenv(DEBUGINFOD_URLS_ENV_VAR);
    3538   [ +  -  +  + ]:          3 :   if (du && du[0] != '\0') // set to non-empty string?
    3539   [ +  -  +  -  :          2 :     obatched(clog) << "upstream debuginfod servers: " << du << endl;
             +  -  +  - ]
    3540                 :            : 
    3541                 :          3 :   vector<pthread_t> all_threads;
    3542                 :            : 
    3543                 :            :   pthread_t pt;
    3544                 :          3 :   rc = pthread_create (& pt, NULL, thread_main_groom, NULL);
    3545         [ -  + ]:          3 :   if (rc)
    3546                 :            :     error (EXIT_FAILURE, rc, "cannot spawn thread to groom database\n");
    3547                 :            :   else
    3548                 :            :     {
    3549                 :            : #ifdef HAVE_PTHREAD_SETNAME_NP
    3550                 :          3 :       (void) pthread_setname_np (pt, "groom");
    3551                 :            : #endif
    3552         [ +  - ]:          3 :       all_threads.push_back(pt);
    3553                 :            :     }
    3554                 :            : 
    3555   [ -  +  -  -  :          3 :   if (scan_files || scan_archives.size() > 0)
                   +  - ]
    3556                 :            :     {
    3557                 :          3 :       rc = pthread_create (& pt, NULL, thread_main_fts_source_paths, NULL);
    3558         [ -  + ]:          3 :       if (rc)
    3559                 :            :         error (EXIT_FAILURE, rc, "cannot spawn thread to traverse source paths\n");
    3560                 :            : #ifdef HAVE_PTHREAD_SETNAME_NP
    3561                 :          3 :       (void) pthread_setname_np (pt, "traverse");
    3562                 :            : #endif
    3563         [ +  - ]:          3 :       all_threads.push_back(pt);
    3564                 :            : 
    3565         [ +  + ]:         15 :       for (unsigned i=0; i<concurrency; i++)
    3566                 :            :         {
    3567                 :         12 :           rc = pthread_create (& pt, NULL, thread_main_scanner, NULL);
    3568         [ -  + ]:         12 :           if (rc)
    3569                 :            :             error (EXIT_FAILURE, rc, "cannot spawn thread to scan source files / archives\n");
    3570                 :            : #ifdef HAVE_PTHREAD_SETNAME_NP
    3571                 :         12 :           (void) pthread_setname_np (pt, "scan");          
    3572                 :            : #endif
    3573         [ +  - ]:         12 :           all_threads.push_back(pt);
    3574                 :            :         }
    3575                 :            :     }
    3576                 :            :   
    3577                 :            :   /* Trivial main loop! */
    3578   [ +  -  +  - ]:          3 :   set_metric("ready", 1);
    3579         [ +  + ]:         18 :   while (! interrupted)
    3580         [ +  - ]:         15 :     pause ();
    3581         [ +  - ]:          3 :   scanq.nuke(); // wake up any remaining scanq-related threads, let them die
    3582   [ +  -  +  - ]:          3 :   set_metric("ready", 0);
    3583                 :            : 
    3584         [ +  - ]:          3 :   if (verbose)
    3585   [ +  -  +  -  :          3 :     obatched(clog) << "stopping" << endl;
                   +  - ]
    3586                 :            : 
    3587                 :            :   /* Join all our threads. */
    3588         [ +  + ]:         21 :   for (auto&& it : all_threads)
    3589         [ +  - ]:         18 :     pthread_join (it, NULL);
    3590                 :            : 
    3591                 :            :   /* Stop all the web service threads. */
    3592   [ +  -  +  - ]:          3 :   if (d4) MHD_stop_daemon (d4);
    3593   [ +  -  +  - ]:          3 :   if (d6) MHD_stop_daemon (d6);
    3594                 :            : 
    3595                 :            :   /* With all threads known dead, we can clean up the global resources. */
    3596         [ +  - ]:          3 :   rc = sqlite3_exec (db, DEBUGINFOD_SQLITE_CLEANUP_DDL, NULL, NULL, NULL);
    3597         [ -  + ]:          3 :   if (rc != SQLITE_OK)
    3598                 :            :     {
    3599         [ #  # ]:          0 :       error (0, 0,
    3600                 :            :              "warning: cannot run database cleanup ddl: %s", sqlite3_errmsg(db));
    3601                 :            :     }
    3602                 :            : 
    3603                 :            :   // NB: no problem with unconditional free here - an earlier failed regcomp would exit program
    3604         [ +  - ]:          3 :   (void) regfree (& file_include_regex);
    3605         [ +  - ]:          3 :   (void) regfree (& file_exclude_regex);
    3606                 :            : 
    3607                 :          3 :   sqlite3 *database = db;
    3608                 :          3 :   sqlite3 *databaseq = dbq;
    3609                 :          3 :   db = dbq = 0; // for signal_handler not to freak
    3610         [ +  - ]:          3 :   (void) sqlite3_close (databaseq);
    3611         [ +  - ]:          3 :   (void) sqlite3_close (database);
    3612                 :            : 
    3613                 :          3 :   return 0;
    3614                 :            : }

Generated by: LCOV version 1.14