LCOV - code coverage report
Current view: top level - debuginfod - debuginfod.cxx (source / functions) Coverage Total Hit
Test: elfutils-0.194 Lines: 83.8 % 2800 2347
Test Date: 2026-02-17 22:11:54 Functions: 90.5 % 116 105
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 46.3 % 5301 2456

             Branch data     Line data    Source code
       1                 :             : /* Debuginfo-over-http server.
       2                 :             :    Copyright (C) 2019-2024 Red Hat, Inc.
       3                 :             :    Copyright (C) 2021, 2022 Mark J. Wielaard <mark@klomp.org>
       4                 :             :    This file is part of elfutils.
       5                 :             : 
       6                 :             :    This file is free software; you can redistribute it and/or modify
       7                 :             :    it under the terms of the GNU General Public License as published by
       8                 :             :    the Free Software Foundation; either version 3 of the License, or
       9                 :             :    (at your option) any later version.
      10                 :             : 
      11                 :             :    elfutils is distributed in the hope that it will be useful, but
      12                 :             :    WITHOUT ANY WARRANTY; without even the implied warranty of
      13                 :             :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      14                 :             :    GNU General Public License for more details.
      15                 :             : 
      16                 :             :    You should have received a copy of the GNU General Public License
      17                 :             :    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
      18                 :             : 
      19                 :             : 
      20                 :             : /* cargo-cult from libdwfl linux-kernel-modules.c */
      21                 :             : /* In case we have a bad fts we include this before config.h because it
      22                 :             :    can't handle _FILE_OFFSET_BITS.
      23                 :             :    Everything we need here is fine if its declarations just come first.
      24                 :             :    Also, include sys/types.h before fts. On some systems fts.h is not self
      25                 :             :    contained. */
      26                 :             : #ifdef BAD_FTS
      27                 :             :   #include <sys/types.h>
      28                 :             :   #include <fts.h>
      29                 :             : #endif
      30                 :             : 
      31                 :             : #ifdef HAVE_CONFIG_H
      32                 :             :   #include "config.h"
      33                 :             : #endif
      34                 :             : 
      35                 :             : // #define _GNU_SOURCE
      36                 :             : #ifdef HAVE_SCHED_H
      37                 :             : extern "C" {
      38                 :             : #include <sched.h>
      39                 :             : }
      40                 :             : #endif
      41                 :             : #ifdef HAVE_SYS_RESOURCE_H
      42                 :             : extern "C" {
      43                 :             : #include <sys/resource.h>
      44                 :             : }
      45                 :             : #endif
      46                 :             : 
      47                 :             : #ifdef HAVE_EXECINFO_H
      48                 :             : extern "C" {
      49                 :             : #include <execinfo.h>
      50                 :             : }
      51                 :             : #endif
      52                 :             : #ifdef HAVE_MALLOC_H
      53                 :             : extern "C" {
      54                 :             : #include <malloc.h>
      55                 :             : }
      56                 :             : #endif
      57                 :             : 
      58                 :             : #include "debuginfod.h"
      59                 :             : #include <dwarf.h>
      60                 :             : 
      61                 :             : #include <argp.h>
      62                 :             : #ifdef __GNUC__
      63                 :             : #undef __attribute__ /* glibc bug - rhbz 1763325 */
      64                 :             : #endif
      65                 :             : 
      66                 :             : #ifdef USE_LZMA
      67                 :             : #include <lzma.h>
      68                 :             : #endif
      69                 :             : 
      70                 :             : #include <unistd.h>
      71                 :             : #include <stdlib.h>
      72                 :             : #include <locale.h>
      73                 :             : #include <pthread.h>
      74                 :             : #include <signal.h>
      75                 :             : #include <sys/stat.h>
      76                 :             : #include <sys/time.h>
      77                 :             : #include <sys/vfs.h>
      78                 :             : #include <unistd.h>
      79                 :             : #include <fcntl.h>
      80                 :             : #include <netdb.h>
      81                 :             : #include <math.h>
      82                 :             : #include <float.h>
      83                 :             : #include <fnmatch.h>
      84                 :             : #include <arpa/inet.h>
      85                 :             : 
      86                 :             : 
      87                 :             : /* If fts.h is included before config.h, its indirect inclusions may not
      88                 :             :    give us the right LFS aliases of these functions, so map them manually.  */
      89                 :             : #ifdef BAD_FTS
      90                 :             :   #ifdef _FILE_OFFSET_BITS
      91                 :             :     #define open open64
      92                 :             :     #define fopen fopen64
      93                 :             :   #endif
      94                 :             : #else
      95                 :             :   #include <sys/types.h>
      96                 :             :   #include <fts.h>
      97                 :             : #endif
      98                 :             : 
      99                 :             : #include <cstring>
     100                 :             : #include <vector>
     101                 :             : #include <set>
     102                 :             : #include <unordered_set>
     103                 :             : #include <map>
     104                 :             : #include <string>
     105                 :             : #include <iostream>
     106                 :             : #include <iomanip>
     107                 :             : #include <ostream>
     108                 :             : #include <sstream>
     109                 :             : #include <mutex>
     110                 :             : #include <deque>
     111                 :             : #include <condition_variable>
     112                 :             : #include <exception>
     113                 :             : #include <thread>
     114                 :             : // #include <regex> // on rhel7 gcc 4.8, not competent
     115                 :             : #include <regex.h>
     116                 :             : // #include <algorithm>
     117                 :             : using namespace std;
     118                 :             : 
     119                 :             : #include <gelf.h>
     120                 :             : #include <libdwelf.h>
     121                 :             : 
     122                 :             : #include <microhttpd.h>
     123                 :             : 
     124                 :             : #if MHD_VERSION >= 0x00097002
     125                 :             : // libmicrohttpd 0.9.71 broke API
     126                 :             : #define MHD_RESULT enum MHD_Result
     127                 :             : #else
     128                 :             : #define MHD_RESULT int
     129                 :             : #endif
     130                 :             : 
     131                 :             : #ifdef ENABLE_IMA_VERIFICATION
     132                 :             :   #include <rpm/rpmlib.h>
     133                 :             :   #include <rpm/rpmfi.h>
     134                 :             :   #include <rpm/header.h>
     135                 :             :   #include <glob.h>
     136                 :             : #endif
     137                 :             : 
     138                 :             : #include <curl/curl.h>
     139                 :             : #include <archive.h>
     140                 :             : #include <archive_entry.h>
     141                 :             : #include <sqlite3.h>
     142                 :             : 
     143                 :             : #ifdef __linux__
     144                 :             : #include <sys/syscall.h>
     145                 :             : #endif
     146                 :             : 
     147                 :             : #ifdef __linux__
     148                 :             : #define tid() syscall(SYS_gettid)
     149                 :             : #else
     150                 :             : #define tid() pthread_self()
     151                 :             : #endif
     152                 :             : 
     153                 :             : extern "C" {
     154                 :             : #include "printversion.h"
     155                 :             : #include "system.h"
     156                 :             : }
     157                 :             : #include <json-c/json.h>
     158                 :             : 
     159                 :             : 
     160                 :             : inline bool
     161                 :      141643 : string_endswith(const string& haystack, const string& needle)
     162                 :             : {
     163         [ +  + ]:      141643 :   return (haystack.size() >= needle.size() &&
     164                 :      138551 :           equal(haystack.end()-needle.size(), haystack.end(),
     165                 :      141643 :                 needle.begin()));
     166                 :             : }
     167                 :             : 
     168                 :             : 
     169                 :             : // Roll this identifier for every sqlite schema incompatibility.
     170                 :             : #define BUILDIDS "buildids10"
     171                 :             : 
     172                 :             : #if SQLITE_VERSION_NUMBER >= 3008000
     173                 :             : #define WITHOUT_ROWID "without rowid"
     174                 :             : #else
     175                 :             : #define WITHOUT_ROWID ""
     176                 :             : #endif
     177                 :             : 
     178                 :             : static const char DEBUGINFOD_SQLITE_DDL[] =
     179                 :             :   "pragma foreign_keys = on;\n"
     180                 :             :   "pragma synchronous = 0;\n" // disable fsync()s - this cache is disposable across a machine crash
     181                 :             :   "pragma journal_mode = wal;\n" // https://sqlite.org/wal.html
     182                 :             :   "pragma wal_checkpoint = truncate;\n" // clean out any preexisting wal file
     183                 :             :   "pragma journal_size_limit = 0;\n" // limit steady state file (between grooming, which also =truncate's)
     184                 :             :   "pragma auto_vacuum = incremental;\n" // https://sqlite.org/pragma.html
     185                 :             :   "pragma busy_timeout = 1000;\n" // https://sqlite.org/pragma.html
     186                 :             :   // NB: all these are overridable with -D option
     187                 :             : 
     188                 :             :   // Normalization table for interning file names
     189                 :             :   "create table if not exists " BUILDIDS "_fileparts (\n"
     190                 :             :   "        id integer primary key not null,\n"
     191                 :             :   "        name text unique not null\n"
     192                 :             :   "        );\n"
     193                 :             :   "create table if not exists " BUILDIDS "_files (\n"
     194                 :             :   "        id integer primary key not null,\n"
     195                 :             :   "        dirname integer not null,\n"
     196                 :             :   "        basename integer not null,\n"
     197                 :             :   "        unique (dirname, basename),\n"
     198                 :             :   "        foreign key (dirname) references " BUILDIDS "_fileparts(id) on delete cascade,\n"
     199                 :             :   "        foreign key (basename) references " BUILDIDS "_fileparts(id) on delete cascade\n"
     200                 :             :   "        );\n"
     201                 :             :   "create view if not exists " BUILDIDS "_files_v as\n" // a 
     202                 :             :   "        select f.id, n1.name || '/' || n2.name as name\n"
     203                 :             :   "        from " BUILDIDS "_files f, " BUILDIDS "_fileparts n1, " BUILDIDS "_fileparts n2\n"
     204                 :             :   "        where f.dirname = n1.id and f.basename = n2.id;\n"
     205                 :             :   
     206                 :             :   // Normalization table for interning buildids
     207                 :             :   "create table if not exists " BUILDIDS "_buildids (\n"
     208                 :             :   "        id integer primary key not null,\n"
     209                 :             :   "        hex text unique not null);\n"
     210                 :             :   // Track the completion of scanning of a given file & sourcetype at given time
     211                 :             :   "create table if not exists " BUILDIDS "_file_mtime_scanned (\n"
     212                 :             :   "        mtime integer not null,\n"
     213                 :             :   "        file integer not null,\n"
     214                 :             :   "        size integer not null,\n" // in bytes
     215                 :             :   "        sourcetype text(1) not null\n"
     216                 :             :   "            check (sourcetype IN ('F', 'R')),\n"
     217                 :             :   "        foreign key (file) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n"
     218                 :             :   "        primary key (file, mtime, sourcetype)\n"
     219                 :             :   "        ) " WITHOUT_ROWID ";\n"
     220                 :             :   "create table if not exists " BUILDIDS "_f_de (\n"
     221                 :             :   "        buildid integer not null,\n"
     222                 :             :   "        debuginfo_p integer not null,\n"
     223                 :             :   "        executable_p integer not null,\n"
     224                 :             :   "        file integer not null,\n"
     225                 :             :   "        mtime integer not null,\n"
     226                 :             :   "        foreign key (file) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n"
     227                 :             :   "        foreign key (buildid) references " BUILDIDS "_buildids(id) on update cascade on delete cascade,\n"
     228                 :             :   "        primary key (buildid, file, mtime)\n"
     229                 :             :   "        ) " WITHOUT_ROWID ";\n"
     230                 :             :   // Index for faster delete by file identifier and metadata searches
     231                 :             :   "create index if not exists " BUILDIDS "_f_de_idx on " BUILDIDS "_f_de (file, mtime);\n"
     232                 :             :   "create table if not exists " BUILDIDS "_f_s (\n"
     233                 :             :   "        buildid integer not null,\n"
     234                 :             :   "        artifactsrc integer not null,\n"
     235                 :             :   "        file integer not null,\n" // NB: not necessarily entered into _mtime_scanned
     236                 :             :   "        mtime integer not null,\n"
     237                 :             :   "        foreign key (file) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n"
     238                 :             :   "        foreign key (artifactsrc) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n"
     239                 :             :   "        foreign key (buildid) references " BUILDIDS "_buildids(id) on update cascade on delete cascade,\n"
     240                 :             :   "        primary key (buildid, artifactsrc, file, mtime)\n"
     241                 :             :   "        ) " WITHOUT_ROWID ";\n"
     242                 :             :   "create table if not exists " BUILDIDS "_r_de (\n"
     243                 :             :   "        buildid integer not null,\n"
     244                 :             :   "        debuginfo_p integer not null,\n"
     245                 :             :   "        executable_p integer not null,\n"
     246                 :             :   "        file integer not null,\n"
     247                 :             :   "        mtime integer not null,\n"
     248                 :             :   "        content integer not null,\n"
     249                 :             :   "        foreign key (file) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n"
     250                 :             :   "        foreign key (content) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n"
     251                 :             :   "        foreign key (buildid) references " BUILDIDS "_buildids(id) on update cascade on delete cascade,\n"
     252                 :             :   "        primary key (buildid, debuginfo_p, executable_p, file, content, mtime)\n"
     253                 :             :   "        ) " WITHOUT_ROWID ";\n"
     254                 :             :   // Index for faster delete by archive file identifier
     255                 :             :   "create index if not exists " BUILDIDS "_r_de_idx on " BUILDIDS "_r_de (file, mtime);\n"
     256                 :             :   // Index for metadata searches
     257                 :             :   "create index if not exists " BUILDIDS "_r_de_idx2 on " BUILDIDS "_r_de (content);\n"  
     258                 :             :   "create table if not exists " BUILDIDS "_r_sref (\n" // outgoing dwarf sourcefile references from rpm
     259                 :             :   "        buildid integer not null,\n"
     260                 :             :   "        artifactsrc integer not null,\n"
     261                 :             :   "        foreign key (artifactsrc) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n"
     262                 :             :   "        foreign key (buildid) references " BUILDIDS "_buildids(id) on update cascade on delete cascade,\n"
     263                 :             :   "        primary key (buildid, artifactsrc)\n"
     264                 :             :   "        ) " WITHOUT_ROWID ";\n"
     265                 :             :   "create table if not exists " BUILDIDS "_r_sdef (\n" // rpm contents that may satisfy sref
     266                 :             :   "        file integer not null,\n"
     267                 :             :   "        mtime integer not null,\n"
     268                 :             :   "        content integer not null,\n"
     269                 :             :   "        foreign key (file) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n"
     270                 :             :   "        foreign key (content) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n"
     271                 :             :   "        primary key (content, file, mtime)\n"
     272                 :             :   "        ) " WITHOUT_ROWID ";\n"
     273                 :             :   "create table if not exists " BUILDIDS "_r_seekable (\n" // seekable rpm contents
     274                 :             :   "        file integer not null,\n"
     275                 :             :   "        content integer not null,\n"
     276                 :             :   "        type text not null,\n"
     277                 :             :   "        size integer not null,\n"
     278                 :             :   "        offset integer not null,\n"
     279                 :             :   "        mtime integer not null,\n"
     280                 :             :   "        foreign key (file) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n"
     281                 :             :   "        foreign key (content) references " BUILDIDS "_files(id) on update cascade on delete cascade,\n"
     282                 :             :   "        primary key (file, content)\n"
     283                 :             :   "        ) " WITHOUT_ROWID ";\n"
     284                 :             :   // create views to glue together some of the above tables, for webapi D queries
     285                 :             :   // NB: _query_d2 and _query_e2 were added to replace _query_d and _query_e
     286                 :             :   // without updating BUILDIDS.  They can be renamed back the next time BUILDIDS
     287                 :             :   // is updated.
     288                 :             :   "create view if not exists " BUILDIDS "_query_d2 as \n"
     289                 :             :   "select\n"
     290                 :             :   "        b.hex as buildid, 'F' as sourcetype, n.file as id0, f0.name as source0, n.mtime as mtime, null as id1, null as source1\n"
     291                 :             :   "        from " BUILDIDS "_buildids b, " BUILDIDS "_files_v f0, " BUILDIDS "_f_de n\n"
     292                 :             :   "        where b.id = n.buildid and f0.id = n.file and n.debuginfo_p = 1\n"
     293                 :             :   "union all select\n"
     294                 :             :   "        b.hex as buildid, 'R' as sourcetype, n.file as id0, f0.name as source0, n.mtime as mtime, n.content as id1, f1.name as source1\n"
     295                 :             :   "        from " BUILDIDS "_buildids b, " BUILDIDS "_files_v f0, " BUILDIDS "_files_v f1, " BUILDIDS "_r_de n\n"
     296                 :             :   "        where b.id = n.buildid and f0.id = n.file and f1.id = n.content and n.debuginfo_p = 1\n"
     297                 :             :   ";"
     298                 :             :   // ... and for E queries
     299                 :             :   "create view if not exists " BUILDIDS "_query_e2 as \n"
     300                 :             :   "select\n"
     301                 :             :   "        b.hex as buildid, 'F' as sourcetype, n.file as id0, f0.name as source0, n.mtime as mtime, null as id1, null as source1\n"
     302                 :             :   "        from " BUILDIDS "_buildids b, " BUILDIDS "_files_v f0, " BUILDIDS "_f_de n\n"
     303                 :             :   "        where b.id = n.buildid and f0.id = n.file and n.executable_p = 1\n"
     304                 :             :   "union all select\n"
     305                 :             :   "        b.hex as buildid, 'R' as sourcetype, n.file as id0, f0.name as source0, n.mtime as mtime, n.content as id1, f1.name as source1\n"
     306                 :             :   "        from " BUILDIDS "_buildids b, " BUILDIDS "_files_v f0, " BUILDIDS "_files_v f1, " BUILDIDS "_r_de n\n"
     307                 :             :   "        where b.id = n.buildid and f0.id = n.file and f1.id = n.content and n.executable_p = 1\n"
     308                 :             :   ";"
     309                 :             :   // ... and for S queries
     310                 :             :   "create view if not exists " BUILDIDS "_query_s as \n"
     311                 :             :   "select\n"
     312                 :             :   "        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"
     313                 :             :   "        from " BUILDIDS "_buildids b, " BUILDIDS "_files_v f0, " BUILDIDS "_files_v fs, " BUILDIDS "_f_s n\n"
     314                 :             :   "        where b.id = n.buildid and f0.id = n.file and fs.id = n.artifactsrc\n"
     315                 :             :   "union all select\n"
     316                 :             :   "        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"
     317                 :             :   "        from " BUILDIDS "_buildids b, " BUILDIDS "_files_v f0, " BUILDIDS "_files_v f1, " BUILDIDS "_files_v fsref, "
     318                 :             :   "        " BUILDIDS "_r_sdef sd, " BUILDIDS "_r_sref sr, " BUILDIDS "_r_de sde\n"
     319                 :             :   "        where b.id = sr.buildid and f0.id = sd.file and fsref.id = sde.file and f1.id = sd.content\n"
     320                 :             :   "        and sr.artifactsrc = sd.content and sde.buildid = sr.buildid\n"
     321                 :             :   ";"
     322                 :             :   // and for startup overview counts
     323                 :             :   "drop view if exists " BUILDIDS "_stats;\n"
     324                 :             :   "create view if not exists " BUILDIDS "_stats as\n"
     325                 :             :   "          select 'file d/e' as label,count(*) as quantity from " BUILDIDS "_f_de\n"
     326                 :             :   "union all select 'file s',count(*) from " BUILDIDS "_f_s\n"
     327                 :             :   "union all select 'archive d/e',count(*) from " BUILDIDS "_r_de\n"
     328                 :             :   "union all select 'archive sref',count(*) from " BUILDIDS "_r_sref\n"
     329                 :             :   "union all select 'archive sdef',count(*) from " BUILDIDS "_r_sdef\n"
     330                 :             :   "union all select 'buildids',count(*) from " BUILDIDS "_buildids\n"
     331                 :             :   "union all select 'filenames',count(*) from " BUILDIDS "_files\n"
     332                 :             :   "union all select 'fileparts',count(*) from " BUILDIDS "_fileparts\n"  
     333                 :             :   "union all select 'files scanned (#)',count(*) from " BUILDIDS "_file_mtime_scanned\n"
     334                 :             :   "union all select 'files scanned (mb)',coalesce(sum(size)/1024/1024,0) from " BUILDIDS "_file_mtime_scanned\n"
     335                 :             : #if SQLITE_VERSION_NUMBER >= 3016000
     336                 :             :   "union all select 'index db size (mb)',page_count*page_size/1024/1024 as size FROM pragma_page_count(), pragma_page_size()\n"
     337                 :             : #endif
     338                 :             :   ";\n"
     339                 :             : 
     340                 :             : // schema change history & garbage collection
     341                 :             : //
     342                 :             : // XXX: we could have migration queries here to bring prior-schema
     343                 :             : // data over instead of just dropping it.  But that could incur
     344                 :             : // doubled storage costs.
     345                 :             : //
     346                 :             : // buildids10: split the _files table into _parts
     347                 :             :   "" // <<< we are here
     348                 :             : // buildids9: widen the mtime_scanned table
     349                 :             :   "DROP VIEW IF EXISTS buildids9_stats;\n"
     350                 :             :   "DROP INDEX IF EXISTS buildids9_r_de_idx;\n"
     351                 :             :   "DROP INDEX IF EXISTS buildids9_f_de_idx;\n"
     352                 :             :   "DROP VIEW IF EXISTS buildids9_query_s;\n"
     353                 :             :   "DROP VIEW IF EXISTS buildids9_query_e;\n"
     354                 :             :   "DROP VIEW IF EXISTS buildids9_query_d;\n"
     355                 :             :   "DROP TABLE IF EXISTS buildids9_r_sdef;\n"
     356                 :             :   "DROP TABLE IF EXISTS buildids9_r_sref;\n"
     357                 :             :   "DROP TABLE IF EXISTS buildids9_r_de;\n"
     358                 :             :   "DROP TABLE IF EXISTS buildids9_f_s;\n"
     359                 :             :   "DROP TABLE IF EXISTS buildids9_f_de;\n"
     360                 :             :   "DROP TABLE IF EXISTS buildids9_file_mtime_scanned;\n"
     361                 :             :   "DROP TABLE IF EXISTS buildids9_buildids;\n"
     362                 :             :   "DROP TABLE IF EXISTS buildids9_files;\n"
     363                 :             : // buildids8: slim the sref table
     364                 :             :   "drop table if exists buildids8_f_de;\n"
     365                 :             :   "drop table if exists buildids8_f_s;\n"
     366                 :             :   "drop table if exists buildids8_r_de;\n"
     367                 :             :   "drop table if exists buildids8_r_sref;\n"
     368                 :             :   "drop table if exists buildids8_r_sdef;\n"
     369                 :             :   "drop table if exists buildids8_file_mtime_scanned;\n"
     370                 :             :   "drop table if exists buildids8_files;\n"
     371                 :             :   "drop table if exists buildids8_buildids;\n"
     372                 :             : // buildids7: separate _norm table into dense subtype tables
     373                 :             :   "drop table if exists buildids7_f_de;\n"
     374                 :             :   "drop table if exists buildids7_f_s;\n"
     375                 :             :   "drop table if exists buildids7_r_de;\n"
     376                 :             :   "drop table if exists buildids7_r_sref;\n"
     377                 :             :   "drop table if exists buildids7_r_sdef;\n"
     378                 :             :   "drop table if exists buildids7_file_mtime_scanned;\n"
     379                 :             :   "drop table if exists buildids7_files;\n"
     380                 :             :   "drop table if exists buildids7_buildids;\n"
     381                 :             : // buildids6: drop bolo/rfolo again, represent sources / rpmcontents in main table
     382                 :             :   "drop table if exists buildids6_norm;\n"
     383                 :             :   "drop table if exists buildids6_files;\n"
     384                 :             :   "drop table if exists buildids6_buildids;\n"
     385                 :             :   "drop view if exists buildids6;\n"
     386                 :             : // buildids5: redefine srcfile1 column to be '.'-less (for rpms)
     387                 :             :   "drop table if exists buildids5_norm;\n"
     388                 :             :   "drop table if exists buildids5_files;\n"
     389                 :             :   "drop table if exists buildids5_buildids;\n"
     390                 :             :   "drop table if exists buildids5_bolo;\n"
     391                 :             :   "drop table if exists buildids5_rfolo;\n"
     392                 :             :   "drop view if exists buildids5;\n"
     393                 :             : // buildids4: introduce rpmfile RFOLO
     394                 :             :   "drop table if exists buildids4_norm;\n"
     395                 :             :   "drop table if exists buildids4_files;\n"
     396                 :             :   "drop table if exists buildids4_buildids;\n"
     397                 :             :   "drop table if exists buildids4_bolo;\n"
     398                 :             :   "drop table if exists buildids4_rfolo;\n"
     399                 :             :   "drop view if exists buildids4;\n"
     400                 :             : // buildids3*: split out srcfile BOLO
     401                 :             :   "drop table if exists buildids3_norm;\n"
     402                 :             :   "drop table if exists buildids3_files;\n"
     403                 :             :   "drop table if exists buildids3_buildids;\n"
     404                 :             :   "drop table if exists buildids3_bolo;\n"
     405                 :             :   "drop view if exists buildids3;\n"
     406                 :             : // buildids2: normalized buildid and filenames into interning tables;
     407                 :             :   "drop table if exists buildids2_norm;\n"
     408                 :             :   "drop table if exists buildids2_files;\n"
     409                 :             :   "drop table if exists buildids2_buildids;\n"
     410                 :             :   "drop view if exists buildids2;\n"
     411                 :             :   // buildids1: made buildid and artifacttype NULLable, to represent cached-negative
     412                 :             : //           lookups from sources, e.g. files or rpms that contain no buildid-indexable content
     413                 :             :   "drop table if exists buildids1;\n"
     414                 :             : // buildids: original
     415                 :             :   "drop table if exists buildids;\n"
     416                 :             :   ;
     417                 :             : 
     418                 :             : static const char DEBUGINFOD_SQLITE_CLEANUP_DDL[] =
     419                 :             :   "pragma wal_checkpoint = truncate;\n" // clean out any preexisting wal file
     420                 :             :   ;
     421                 :             : 
     422                 :             : 
     423                 :             : 
     424                 :             : 
     425                 :             : /* Name and version of program.  */
     426                 :             : ARGP_PROGRAM_VERSION_HOOK_DEF = print_version;
     427                 :             : 
     428                 :             : /* Bug report address.  */
     429                 :             : ARGP_PROGRAM_BUG_ADDRESS_DEF = PACKAGE_BUGREPORT;
     430                 :             : 
     431                 :             : /* Definitions of arguments for argp functions.  */
     432                 :             : static const struct argp_option options[] =
     433                 :             :   {
     434                 :             :    { NULL, 0, NULL, 0, "Scanners:", 1 },
     435                 :             :    { "scan-file-dir", 'F', NULL, 0, "Enable ELF/DWARF file scanning.", 0 },
     436                 :             :    { "scan-rpm-dir", 'R', NULL, 0, "Enable RPM scanning.", 0 },
     437                 :             :    { "scan-deb-dir", 'U', NULL, 0, "Enable DEB scanning.", 0 },
     438                 :             :    { "scan-archive", 'Z', "EXT=CMD", 0, "Enable arbitrary archive scanning.", 0 },
     439                 :             :    // "source-oci-imageregistry"  ...
     440                 :             : 
     441                 :             :    { NULL, 0, NULL, 0, "Options:", 2 },
     442                 :             :    { "logical", 'L', NULL, 0, "Follow symlinks, default=ignore.", 0 },
     443                 :             :    { "rescan-time", 't', "SECONDS", 0, "Number of seconds to wait between rescans, 0=disable.", 0 },
     444                 :             :    { "groom-time", 'g', "SECONDS", 0, "Number of seconds to wait between database grooming, 0=disable.", 0 },
     445                 :             :    { "maxigroom", 'G', NULL, 0, "Run a complete database groom/shrink pass at startup.", 0 },
     446                 :             :    { "concurrency", 'c', "NUM", 0, "Limit scanning thread concurrency to NUM, default=#CPUs.", 0 },
     447                 :             :    { "connection-pool", 'C', "NUM", OPTION_ARG_OPTIONAL,
     448                 :             :      "Use webapi connection pool with NUM threads, default=unlim.", 0 },
     449                 :             :    { "include", 'I', "REGEX", 0, "Include files matching REGEX, default=all.", 0 },
     450                 :             :    { "exclude", 'X', "REGEX", 0, "Exclude files matching REGEX, default=none.", 0 },
     451                 :             :    { "port", 'p', "NUM", 0, "HTTP port to listen on, default 8002.", 0 },
     452                 :             : #define ARGP_KEY_CORS 0x1000
     453                 :             :    { "cors", ARGP_KEY_CORS, NULL, 0, "Add CORS response headers to HTTP queries, default no.", 0 },
     454                 :             :    { "database", 'd', "FILE", 0, "Path to sqlite database.", 0 },
     455                 :             :    { "ddl", 'D', "SQL", 0, "Apply extra sqlite ddl/pragma to connection.", 0 },
     456                 :             :    { "verbose", 'v', NULL, 0, "Increase verbosity.", 0 },
     457                 :             :    { "regex-groom", 'r', NULL, 0,"Uses regexes from -I and -X arguments to groom the database.",0},
     458                 :             : #define ARGP_KEY_FDCACHE_FDS 0x1001
     459                 :             :    { "fdcache-fds", ARGP_KEY_FDCACHE_FDS, "NUM", OPTION_HIDDEN, NULL, 0 },
     460                 :             : #define ARGP_KEY_FDCACHE_MBS 0x1002
     461                 :             :    { "fdcache-mbs", ARGP_KEY_FDCACHE_MBS, "MB", 0, "Maximum total size of archive file fdcache.", 0 },
     462                 :             : #define ARGP_KEY_FDCACHE_PREFETCH 0x1003
     463                 :             :    { "fdcache-prefetch", ARGP_KEY_FDCACHE_PREFETCH, "NUM", 0, "Number of archive files to prefetch into fdcache.", 0 },
     464                 :             : #define ARGP_KEY_FDCACHE_MINTMP 0x1004
     465                 :             :    { "fdcache-mintmp", ARGP_KEY_FDCACHE_MINTMP, "NUM", 0, "Minimum free space% on tmpdir.", 0 },
     466                 :             : #define ARGP_KEY_FDCACHE_PREFETCH_MBS 0x1005
     467                 :             :    { "fdcache-prefetch-mbs", ARGP_KEY_FDCACHE_PREFETCH_MBS, "MB", OPTION_HIDDEN, NULL, 0},
     468                 :             : #define ARGP_KEY_FDCACHE_PREFETCH_FDS 0x1006
     469                 :             :    { "fdcache-prefetch-fds", ARGP_KEY_FDCACHE_PREFETCH_FDS, "NUM", OPTION_HIDDEN, NULL, 0},
     470                 :             : #define ARGP_KEY_FORWARDED_TTL_LIMIT 0x1007
     471                 :             :    {"forwarded-ttl-limit", ARGP_KEY_FORWARDED_TTL_LIMIT, "NUM", 0, "Limit of X-Forwarded-For hops, default 8.", 0},
     472                 :             : #define ARGP_KEY_PASSIVE 0x1008
     473                 :             :    { "passive", ARGP_KEY_PASSIVE, NULL, 0, "Do not scan or groom, read-only database.", 0 },
     474                 :             : #define ARGP_KEY_DISABLE_SOURCE_SCAN 0x1009
     475                 :             :    { "disable-source-scan", ARGP_KEY_DISABLE_SOURCE_SCAN, NULL, 0, "Do not scan dwarf source info.", 0 },
     476                 :             : #define ARGP_SCAN_CHECKPOINT 0x100A
     477                 :             :    { "scan-checkpoint", ARGP_SCAN_CHECKPOINT, "NUM", 0, "Number of files scanned before a WAL checkpoint.", 0 },
     478                 :             : #ifdef ENABLE_IMA_VERIFICATION
     479                 :             : #define ARGP_KEY_KOJI_SIGCACHE 0x100B
     480                 :             :    { "koji-sigcache", ARGP_KEY_KOJI_SIGCACHE, NULL, 0, "Do a koji specific mapping of rpm paths to get IMA signatures.", 0 },
     481                 :             : #endif
     482                 :             : #define ARGP_KEY_METADATA_MAXTIME 0x100C
     483                 :             :    { "metadata-maxtime", ARGP_KEY_METADATA_MAXTIME, "SECONDS", 0,
     484                 :             :      "Number of seconds to limit metadata query run time, 0=unlimited.", 0 },
     485                 :             : #define ARGP_KEY_HTTP_ADDR 0x100D
     486                 :             :    { "listen-address", ARGP_KEY_HTTP_ADDR, "ADDR", 0, "HTTP address to listen on.", 0 },
     487                 :             :    { "home-redirect", 'h', "URL", 0, "Custom homepage - redirect.", 0 },
     488                 :             :    { "home-html", 'H', "FILE", 0, "Custom homepage - htmlfile.", 0 },
     489                 :             :    { NULL, 0, NULL, 0, NULL, 0 },
     490                 :             :   };
     491                 :             : 
     492                 :             : /* Short description of program.  */
     493                 :             : static const char doc[] = "Serve debuginfo-related content across HTTP from files under PATHs.";
     494                 :             : 
     495                 :             : /* Strings for arguments in help texts.  */
     496                 :             : static const char args_doc[] = "[PATH ...]";
     497                 :             : 
     498                 :             : /* Prototype for option handler.  */
     499                 :             : static error_t parse_opt (int key, char *arg, struct argp_state *state);
     500                 :             : 
     501                 :             : static unsigned default_concurrency();
     502                 :             : 
     503                 :             : /* Data structure to communicate with argp functions.  */
     504                 :             : static struct argp argp =
     505                 :             :   {
     506                 :             :    options, parse_opt, args_doc, doc, NULL, NULL, NULL
     507                 :             :   };
     508                 :             : 
     509                 :             : 
     510                 :             : static string db_path;
     511                 :             : static sqlite3 *db;  // single connection, serialized across all our threads!
     512                 :             : static sqlite3 *dbq; // webapi query-servicing readonly connection, serialized ditto!
     513                 :             : static unsigned verbose;
     514                 :             : static volatile sig_atomic_t interrupted = 0;
     515                 :             : static volatile sig_atomic_t forced_rescan_count = 0;
     516                 :             : static volatile sig_atomic_t sigusr1 = 0;
     517                 :             : static volatile sig_atomic_t forced_groom_count = 0;
     518                 :             : static volatile sig_atomic_t sigusr2 = 0;
     519                 :             : static unsigned http_port = 8002;
     520                 :             : static struct sockaddr_in6 http_sockaddr;
     521                 :             : static string addr_info = "";
     522                 :             : static bool webapi_cors = false;
     523                 :             : static unsigned rescan_s = 300;
     524                 :             : static unsigned groom_s = 86400;
     525                 :             : static bool maxigroom = false;
     526                 :             : static unsigned concurrency = default_concurrency();
     527                 :             : static int connection_pool = 0;
     528                 :             : static set<string> source_paths;
     529                 :             : static bool scan_files = false;
     530                 :             : static map<string,string> scan_archives;
     531                 :             : static vector<string> extra_ddl;
     532                 :             : static regex_t file_include_regex;
     533                 :             : static regex_t file_exclude_regex;
     534                 :             : static bool regex_groom = false;
     535                 :             : static bool traverse_logical;
     536                 :             : static long fdcache_mbs;
     537                 :             : static long fdcache_prefetch;
     538                 :             : static long fdcache_mintmp;
     539                 :             : static unsigned forwarded_ttl_limit = 8;
     540                 :             : static bool scan_source_info = true;
     541                 :             : static string tmpdir;
     542                 :             : static bool passive_p = false;
     543                 :             : static long scan_checkpoint = 256;
     544                 :             : #ifdef ENABLE_IMA_VERIFICATION
     545                 :             : static bool requires_koji_sigcache_mapping = false;
     546                 :             : #endif
     547                 :             : static unsigned metadata_maxtime_s = 5;
     548                 :             : static string cust_homepage_redirect = "";
     549                 :             : static string cust_homepage_file = "";
     550                 :             : 
     551                 :             : static void set_metric(const string& key, double value);
     552                 :             : static void inc_metric(const string& key);
     553                 :             : static void add_metric(const string& metric,
     554                 :             :                        double value);
     555                 :             : static void set_metric(const string& metric,
     556                 :             :                        const string& lname, const string& lvalue,
     557                 :             :                        double value);
     558                 :             : static void inc_metric(const string& metric,
     559                 :             :                        const string& lname, const string& lvalue);
     560                 :             : static void add_metric(const string& metric,
     561                 :             :                        const string& lname, const string& lvalue,
     562                 :             :                        double value);
     563                 :             : static void inc_metric(const string& metric,
     564                 :             :                        const string& lname, const string& lvalue,
     565                 :             :                        const string& rname, const string& rvalue);
     566                 :             : static void add_metric(const string& metric,
     567                 :             :                        const string& lname, const string& lvalue,
     568                 :             :                        const string& rname, const string& rvalue,                       
     569                 :             :                        double value);
     570                 :             : 
     571                 :             : 
     572                 :             : class tmp_inc_metric { // a RAII style wrapper for exception-safe scoped increment & decrement
     573                 :             :   string m, n, v;
     574                 :             : public:
     575                 :        3373 :   tmp_inc_metric(const string& mname, const string& lname, const string& lvalue):
     576   [ +  -  +  - ]:        3373 :     m(mname), n(lname), v(lvalue)
     577                 :             :   {
     578         [ +  - ]:        3373 :     add_metric (m, n, v, 1);
     579                 :        3373 :   }
     580                 :        3373 :   ~tmp_inc_metric()
     581                 :             :   {
     582                 :        3373 :     add_metric (m, n, v, -1);
     583                 :        3373 :   }
     584                 :             : };
     585                 :             : 
     586                 :             : class tmp_ms_metric { // a RAII style wrapper for exception-safe scoped timing
     587                 :             :   string m, n, v;
     588                 :             :   struct timespec ts_start;
     589                 :             : public:
     590                 :      359881 :   tmp_ms_metric(const string& mname, const string& lname, const string& lvalue):
     591   [ +  -  +  - ]:      359881 :     m(mname), n(lname), v(lvalue)
     592                 :             :   {
     593                 :      359846 :     clock_gettime (CLOCK_MONOTONIC, & ts_start);
     594                 :      360255 :   }
     595                 :      360914 :   ~tmp_ms_metric()
     596                 :             :   {
     597                 :      360914 :     struct timespec ts_end;
     598                 :      360914 :     clock_gettime (CLOCK_MONOTONIC, & ts_end);
     599                 :      360990 :     double deltas = (ts_end.tv_sec - ts_start.tv_sec)
     600                 :      360990 :       + (ts_end.tv_nsec - ts_start.tv_nsec)/1.e9;
     601                 :             : 
     602                 :      360990 :     add_metric (m + "_milliseconds_sum", n, v, (deltas*1000.0));
     603                 :      361104 :     inc_metric (m + "_milliseconds_count", n, v);
     604                 :      361079 :   }
     605                 :             : };
     606                 :             : 
     607                 :             : 
     608                 :             : /* Handle program arguments.  */
     609                 :             : static error_t
     610                 :        1276 : parse_opt (int key, char *arg,
     611                 :             :            struct argp_state *state __attribute__ ((unused)))
     612                 :             : {
     613                 :        1276 :   int rc;
     614   [ +  +  +  +  :        1276 :   switch (key)
          +  +  +  +  +  
          +  -  +  +  -  
          -  +  +  +  +  
          +  +  +  +  +  
          +  -  +  -  -  
                +  +  + ]
     615                 :             :     {
     616                 :         308 :     case 'v': verbose ++; break;
     617                 :          80 :     case 'd':
     618                 :             :       /* When using the in-memory database make sure it is shareable,
     619                 :             :          so we can open it twice as read/write and read-only.  */
     620         [ +  + ]:          80 :       if (strcmp (arg, ":memory:") == 0)
     621                 :          14 :         db_path = "file::memory:?cache=shared";
     622                 :             :       else
     623                 :          66 :         db_path = string(arg);
     624                 :             :       break;
     625                 :          84 :     case 'p': http_port = (unsigned) atoi(arg);
     626         [ +  - ]:          84 :       if (http_port == 0 || http_port > 65535)
     627                 :           0 :         argp_failure(state, 1, EINVAL, "port number");
     628                 :             :       break;
     629                 :           6 :     case ARGP_KEY_CORS:
     630                 :           6 :       webapi_cors = true;
     631                 :           6 :       break;
     632                 :          50 :     case 'F': scan_files = true; break;
     633                 :          24 :     case 'R':
     634   [ +  -  +  - ]:          24 :       scan_archives[".rpm"]="cat"; // libarchive groks rpm natively
     635                 :          24 :       break;
     636                 :          18 :     case 'U':
     637   [ +  -  +  - ]:          18 :       scan_archives[".deb"]="(bsdtar -O -x -f - data.tar\\*)<";
     638   [ +  -  +  - ]:          18 :       scan_archives[".ddeb"]="(bsdtar -O -x -f - data.tar\\*)<";
     639   [ +  -  +  - ]:          18 :       scan_archives[".ipk"]="(bsdtar -O -x -f - data.tar\\*)<";
     640                 :             :       // .udeb too?
     641                 :          18 :       break;
     642                 :          40 :     case 'Z':
     643                 :          40 :       {
     644         [ -  + ]:          40 :         char* extension = strchr(arg, '=');
     645         [ -  + ]:          40 :         if (arg[0] == '\0')
     646                 :           0 :           argp_failure(state, 1, EINVAL, "missing EXT");
     647         [ +  + ]:          40 :         else if (extension)
     648   [ +  -  +  - ]:          20 :           scan_archives[string(arg, (extension-arg))]=string(extension+1);
     649                 :             :         else
     650   [ +  -  +  - ]:          20 :           scan_archives[string(arg)]=string("cat");
     651                 :             :       }
     652                 :             :       break;
     653                 :           8 :     case 'L':
     654         [ -  + ]:           8 :       if (passive_p)
     655                 :           0 :         argp_failure(state, 1, EINVAL, "-L option inconsistent with passive mode");
     656                 :           8 :       traverse_logical = true;
     657                 :           8 :       break;
     658                 :           0 :     case 'D':
     659         [ #  # ]:           0 :       if (passive_p)
     660                 :           0 :         argp_failure(state, 1, EINVAL, "-D option inconsistent with passive mode");
     661                 :           0 :       extra_ddl.push_back(string(arg));
     662                 :           0 :       break;
     663                 :          64 :     case 't':
     664         [ -  + ]:          64 :       if (passive_p)
     665                 :           0 :         argp_failure(state, 1, EINVAL, "-t option inconsistent with passive mode");
     666                 :          64 :       rescan_s = (unsigned) atoi(arg);
     667                 :          64 :       break;
     668                 :          64 :     case 'g':
     669         [ -  + ]:          64 :       if (passive_p)
     670                 :           0 :         argp_failure(state, 1, EINVAL, "-g option inconsistent with passive mode");
     671                 :          64 :       groom_s = (unsigned) atoi(arg);
     672                 :          64 :       break;
     673                 :           0 :     case 'G':
     674         [ #  # ]:           0 :       if (passive_p)
     675                 :           0 :         argp_failure(state, 1, EINVAL, "-G option inconsistent with passive mode");
     676                 :           0 :       maxigroom = true;
     677                 :           0 :       break;
     678                 :           0 :     case 'c':
     679         [ #  # ]:           0 :       if (passive_p)
     680                 :           0 :         argp_failure(state, 1, EINVAL, "-c option inconsistent with passive mode");
     681                 :           0 :       concurrency = (unsigned) atoi(arg);
     682         [ #  # ]:           0 :       if (concurrency < 1) concurrency = 1;
     683                 :             :       break;
     684                 :           6 :     case 'C':
     685         [ +  + ]:           6 :       if (arg)
     686                 :             :         {
     687                 :           4 :           connection_pool = atoi(arg);
     688         [ +  - ]:           4 :           if (connection_pool < 2)
     689                 :           0 :             argp_failure(state, 1, EINVAL, "-C NUM minimum 2");
     690                 :             :         }
     691                 :             :       break;
     692                 :           4 :     case 'I':
     693                 :             :       // NB: no problem with unconditional free here - an earlier failed regcomp would exit program
     694         [ -  + ]:           4 :       if (passive_p)
     695                 :           0 :         argp_failure(state, 1, EINVAL, "-I option inconsistent with passive mode");
     696                 :           4 :       regfree (&file_include_regex);
     697                 :           4 :       rc = regcomp (&file_include_regex, arg, REG_EXTENDED|REG_NOSUB);
     698         [ +  - ]:           4 :       if (rc != 0)
     699                 :           0 :         argp_failure(state, 1, EINVAL, "regular expression");
     700                 :             :       break;
     701                 :           6 :     case 'X':
     702         [ -  + ]:           6 :       if (passive_p)
     703                 :           0 :         argp_failure(state, 1, EINVAL, "-X option inconsistent with passive mode");
     704                 :           6 :       regfree (&file_exclude_regex);
     705                 :           6 :       rc = regcomp (&file_exclude_regex, arg, REG_EXTENDED|REG_NOSUB);
     706         [ +  - ]:           6 :       if (rc != 0)
     707                 :           0 :         argp_failure(state, 1, EINVAL, "regular expression");
     708                 :             :       break;
     709                 :           4 :     case 'r':
     710         [ -  + ]:           4 :       if (passive_p)
     711                 :           0 :         argp_failure(state, 1, EINVAL, "-r option inconsistent with passive mode");
     712                 :           4 :       regex_groom = true;
     713                 :           4 :       break;
     714                 :             :     case ARGP_KEY_FDCACHE_FDS:
     715                 :             :       // deprecated
     716                 :             :       break;
     717                 :           4 :     case ARGP_KEY_FDCACHE_MBS:
     718                 :           4 :       fdcache_mbs = atol (arg);
     719                 :           4 :       break;
     720                 :           4 :     case ARGP_KEY_FDCACHE_PREFETCH:
     721                 :           4 :       fdcache_prefetch = atol (arg);
     722                 :           4 :       break;
     723                 :           4 :     case ARGP_KEY_FDCACHE_MINTMP:
     724                 :           4 :       fdcache_mintmp = atol (arg);
     725         [ +  - ]:           4 :       if( fdcache_mintmp > 100 || fdcache_mintmp < 0 )
     726                 :           0 :         argp_failure(state, 1, EINVAL, "fdcache mintmp percent");
     727                 :             :       break;
     728                 :           4 :     case ARGP_KEY_FORWARDED_TTL_LIMIT:
     729                 :           4 :       forwarded_ttl_limit = (unsigned) atoi(arg);
     730                 :           4 :       break;
     731                 :         110 :     case ARGP_KEY_ARG:
     732         [ +  - ]:         110 :       source_paths.insert(string(arg));
     733                 :         110 :       break;
     734                 :             :     case ARGP_KEY_FDCACHE_PREFETCH_FDS:
     735                 :             :       // deprecated
     736                 :             :       break;
     737                 :             :     case ARGP_KEY_FDCACHE_PREFETCH_MBS:
     738                 :             :       // deprecated
     739                 :             :       break;
     740                 :           2 :     case ARGP_KEY_PASSIVE:
     741                 :           2 :       passive_p = true;
     742         [ +  - ]:           2 :       if (source_paths.size() > 0
     743         [ +  - ]:           2 :           || maxigroom
     744         [ +  - ]:           2 :           || extra_ddl.size() > 0
     745   [ +  -  +  - ]:           4 :           || traverse_logical)
     746                 :             :         // other conflicting options tricky to check
     747                 :           0 :         argp_failure(state, 1, EINVAL, "inconsistent options with passive mode");
     748                 :             :       break;
     749                 :           0 :     case ARGP_KEY_DISABLE_SOURCE_SCAN:
     750                 :           0 :       scan_source_info = false;
     751                 :           0 :       break;
     752                 :           2 :     case ARGP_SCAN_CHECKPOINT:
     753                 :           2 :       scan_checkpoint = atol (arg);
     754         [ +  - ]:           2 :       if (scan_checkpoint < 0)
     755                 :           0 :         argp_failure(state, 1, EINVAL, "scan checkpoint");
     756                 :             :       break;
     757                 :           0 :     case ARGP_KEY_METADATA_MAXTIME:
     758                 :           0 :       metadata_maxtime_s = (unsigned) atoi(arg);
     759                 :           0 :       break;
     760                 :             : #ifdef ENABLE_IMA_VERIFICATION
     761                 :             :     case ARGP_KEY_KOJI_SIGCACHE:
     762                 :             :       requires_koji_sigcache_mapping = true;
     763                 :             :       break;
     764                 :             : #endif
     765                 :           0 :     case ARGP_KEY_HTTP_ADDR:
     766         [ #  # ]:           0 :       if (inet_pton(AF_INET, arg, &(((sockaddr_in*)&http_sockaddr)->sin_addr)) == 1)
     767                 :           0 :           http_sockaddr.sin6_family = AF_INET;
     768                 :             :       else
     769         [ #  # ]:           0 :           if (inet_pton(AF_INET6, arg, &http_sockaddr.sin6_addr) == 1)
     770                 :           0 :               http_sockaddr.sin6_family = AF_INET6;
     771                 :             :           else
     772                 :           0 :               argp_failure(state, 1, EINVAL, "listen-address");
     773                 :           0 :       addr_info = arg;
     774                 :           0 :       break;
     775                 :             :       // case 'h': argp_state_help (state, stderr, ARGP_HELP_LONG|ARGP_HELP_EXIT_OK);
     776                 :           4 :     case 'h':
     777                 :           4 :       cust_homepage_redirect = arg;
     778                 :           4 :       break;
     779                 :           4 :     case 'H':
     780                 :           4 :       cust_homepage_file = arg;
     781                 :           4 :       break;
     782                 :             :     default: return ARGP_ERR_UNKNOWN;
     783                 :             :     }
     784                 :             : 
     785                 :             :   return 0;
     786                 :             : }
     787                 :             : 
     788                 :             : 
     789                 :             : ////////////////////////////////////////////////////////////////////////
     790                 :             : 
     791                 :             : 
     792                 :             : static void add_mhd_response_header (struct MHD_Response *r,
     793                 :             :                                      const char *h, const char *v);
     794                 :             : 
     795                 :             : // represent errors that may get reported to an ostream and/or a libmicrohttpd connection
     796                 :             : 
     797                 :           8 : struct reportable_exception
     798                 :             : {
     799                 :             :   int code;
     800                 :             :   string message;
     801                 :             : 
     802   [ -  -  +  -  :         106 :   reportable_exception(int c, const string& m): code(c), message(m) {}
          -  -  +  -  +  
                      - ]
     803   [ -  -  -  -  :         603 :   reportable_exception(const string& m): code(503), message(m) {}
          -  -  -  -  -  
          -  -  -  +  -  
          -  -  -  -  +  
          -  -  -  -  -  
          -  -  -  -  -  
          -  -  -  -  -  
          -  -  -  -  -  
          -  -  -  -  -  
             +  -  -  - ]
     804                 :             :   reportable_exception(): code(503), message() {}
     805                 :             : 
     806                 :             :   void report(ostream& o) const; // defined under obatched() class below
     807                 :             : 
     808                 :         631 :   MHD_RESULT mhd_send_response(MHD_Connection* c) const {
     809                 :        1262 :     MHD_Response* r = MHD_create_response_from_buffer (message.size(),
     810                 :         631 :                                                        (void*) message.c_str(),
     811                 :             :                                                        MHD_RESPMEM_MUST_COPY);
     812                 :         631 :     add_mhd_response_header (r, "Content-Type", "text/plain");
     813                 :         631 :     MHD_RESULT rc = MHD_queue_response (c, code, r);
     814                 :         631 :     MHD_destroy_response (r);
     815                 :         631 :     return rc;
     816                 :             :   }
     817                 :             : };
     818                 :             : 
     819                 :             : 
     820                 :             : struct sqlite_exception: public reportable_exception
     821                 :             : {
     822                 :           0 :   sqlite_exception(int rc, const string& msg):
     823   [ #  #  #  #  :           0 :     reportable_exception(string("sqlite3 error: ") + msg + ": " + string(sqlite3_errstr(rc) ?: "?")) {
             #  #  #  # ]
     824   [ #  #  #  #  :           0 :     inc_metric("error_count","sqlite3",sqlite3_errstr(rc));
          #  #  #  #  #  
                      # ]
     825                 :           0 :   }
     826                 :             : };
     827                 :             : 
     828                 :           4 : struct libc_exception: public reportable_exception
     829                 :             : {
     830                 :         596 :   libc_exception(int rc, const string& msg):
     831   [ -  +  +  -  :        1788 :     reportable_exception(string("libc error: ") + msg + ": " + string(strerror(rc) ?: "?")) {
             +  -  +  - ]
     832   [ +  -  +  -  :        1192 :     inc_metric("error_count","libc",strerror(rc));
             +  -  +  - ]
     833                 :         596 :   }
     834                 :             : };
     835                 :             : 
     836                 :             : 
     837                 :             : struct archive_exception: public reportable_exception
     838                 :             : {
     839                 :           0 :   archive_exception(const string& msg):
     840         [ #  # ]:           0 :     reportable_exception(string("libarchive error: ") + msg) {
     841   [ #  #  #  #  :           0 :       inc_metric("error_count","libarchive",msg);
                   #  # ]
     842                 :           0 :   }
     843                 :           0 :   archive_exception(struct archive* a, const string& msg):
     844   [ #  #  #  #  :           0 :     reportable_exception(string("libarchive error: ") + msg + ": " + string(archive_error_string(a) ?: "?")) {
             #  #  #  # ]
     845   [ #  #  #  #  :           0 :     inc_metric("error_count","libarchive",msg + ": " + string(archive_error_string(a) ?: "?"));
          #  #  #  #  #  
          #  #  #  #  #  
                   #  # ]
     846                 :           0 :   }
     847                 :           0 :   archive_exception(struct archive* a, const string& fname, const string& msg):
     848   [ #  #  #  #  :           0 :     reportable_exception(string("libarchive error: ") + fname + string(" ") + msg + ": " +
          #  #  #  #  #  
                      # ]
     849   [ #  #  #  # ]:           0 :                          string(archive_error_string(a) ?: "?")) {
     850   [ #  #  #  #  :           0 :     inc_metric("error_count","libarchive",msg + ": " + string(archive_error_string(a) ?: "?"));
          #  #  #  #  #  
          #  #  #  #  #  
                   #  # ]
     851                 :           0 :   }
     852                 :             : };
     853                 :             : 
     854                 :             : 
     855                 :             : struct elfutils_exception: public reportable_exception
     856                 :             : {
     857                 :           0 :   elfutils_exception(int rc, const string& msg):
     858   [ #  #  #  #  :           0 :     reportable_exception(string("elfutils error: ") + msg + ": " + string(elf_errmsg(rc) ?: "?")) {
             #  #  #  # ]
     859   [ #  #  #  #  :           0 :     inc_metric("error_count","elfutils",elf_errmsg(rc));
          #  #  #  #  #  
                      # ]
     860                 :           0 :   }
     861                 :             : };
     862                 :             : 
     863                 :             : 
     864                 :             : ////////////////////////////////////////////////////////////////////////
     865                 :             : 
     866                 :             : template <typename Payload>
     867                 :             : class workq
     868                 :             : {
     869                 :             :   unordered_set<Payload> q; // eliminate duplicates
     870                 :             :   mutex mtx;
     871                 :             :   condition_variable cv;
     872                 :             :   bool dead;
     873                 :             :   unsigned idlers;   // number of threads busy with wait_idle / done_idle
     874                 :             :   unsigned fronters; // number of threads busy with wait_front / done_front
     875                 :             : 
     876                 :             : public:
     877                 :          84 :   workq() { dead = false; idlers = 0; fronters = 0; }
     878                 :          84 :   ~workq() {}
     879                 :             : 
     880                 :        1314 :   void push_back(const Payload& p)
     881                 :             :   {
     882                 :        1314 :     unique_lock<mutex> lock(mtx);
     883         [ +  - ]:        1314 :     q.insert (p);
     884   [ +  -  +  -  :        2628 :     set_metric("thread_work_pending","role","scan", q.size());
             +  -  +  - ]
     885                 :        1314 :     cv.notify_all();
     886                 :        1314 :   }
     887                 :             : 
     888                 :             :   // kill this workqueue, wake up all idlers / scanners
     889                 :          84 :   void nuke() {
     890                 :          84 :     unique_lock<mutex> lock(mtx);
     891                 :             :     // optional: q.clear();
     892                 :          84 :     dead = true;
     893                 :          84 :     cv.notify_all();
     894                 :          84 :   }
     895                 :             : 
     896                 :             :   // clear the workqueue, when scanning is interrupted with USR2
     897                 :           0 :   void clear() {
     898                 :           0 :     unique_lock<mutex> lock(mtx);
     899                 :           0 :     q.clear();
     900   [ #  #  #  #  :           0 :     set_metric("thread_work_pending","role","scan", q.size());
             #  #  #  # ]
     901                 :             :     // NB: there may still be some live fronters
     902                 :           0 :     cv.notify_all(); // maybe wake up waiting idlers
     903                 :           0 :   }
     904                 :             : 
     905                 :             :   // block this scanner thread until there is work to do and no active idler
     906                 :        1601 :   bool wait_front (Payload& p)
     907                 :             :   {
     908                 :        1601 :     unique_lock<mutex> lock(mtx);
     909   [ +  +  +  +  :        5601 :     while (!dead && (q.size() == 0 || idlers > 0))
                   +  + ]
     910         [ +  - ]:        3999 :       cv.wait(lock);
     911         [ +  + ]:        1602 :     if (dead)
     912                 :             :       return false;
     913                 :             :     else
     914                 :             :       {
     915         [ +  - ]:        1314 :         p = * q.begin();
     916                 :        1314 :         q.erase (q.begin());
     917                 :        1314 :         fronters ++; // prevent idlers from starting awhile, even if empty q
     918   [ +  -  +  -  :        2628 :         set_metric("thread_work_pending","role","scan", q.size());
          +  -  +  -  -  
                      - ]
     919                 :             :         // NB: don't wake up idlers yet!  The consumer is busy
     920                 :             :         // processing this element until it calls done_front().
     921                 :        1314 :         return true;
     922                 :             :       }
     923                 :        1602 :   }
     924                 :             : 
     925                 :             :   // notify waitq that scanner thread is done with that last item
     926                 :        1314 :   void done_front ()
     927                 :             :   {
     928                 :        1314 :     unique_lock<mutex> lock(mtx);
     929                 :        1314 :     fronters --;
     930   [ +  +  +  + ]:        1314 :     if (q.size() == 0 && fronters == 0)
     931                 :         100 :       cv.notify_all(); // maybe wake up waiting idlers
     932                 :        1314 :   }
     933                 :             :   
     934                 :             :   // block this idler thread until there is no work to do
     935                 :         600 :   void wait_idle ()
     936                 :             :   {
     937                 :         600 :     unique_lock<mutex> lock(mtx);
     938                 :         600 :     cv.notify_all(); // maybe wake up waiting scanners
     939   [ +  +  +  +  :         648 :     while (!dead && ((q.size() != 0) || fronters > 0))
                   +  + ]
     940         [ +  - ]:          48 :       cv.wait(lock);
     941         [ +  - ]:         600 :     idlers ++;
     942                 :         600 :   }
     943                 :             : 
     944                 :         518 :   void done_idle ()
     945                 :             :   {
     946                 :         518 :     unique_lock<mutex> lock(mtx);
     947                 :         518 :     idlers --;
     948                 :         518 :     cv.notify_all(); // maybe wake up waiting scanners, but probably not (shutting down)
     949                 :         518 :   }
     950                 :             : };
     951                 :             : 
     952                 :             : typedef struct stat stat_t;
     953                 :             : typedef pair<string,stat_t> scan_payload;
     954                 :             : inline bool operator< (const scan_payload& a, const scan_payload& b)
     955                 :             : {
     956                 :             :   return a.first < b.first; // don't bother compare the stat fields
     957                 :             : }
     958                 :             : 
     959                 :             : namespace std { // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56480
     960                 :             :   template<> struct hash<::scan_payload>
     961                 :             :   {
     962                 :        5372 :     std::size_t operator() (const ::scan_payload& p) const noexcept
     963                 :             :     {
     964   [ +  +  +  + ]:        5372 :       return hash<string>()(p.first);
     965                 :             :     }
     966                 :             :   };
     967                 :             :   template<> struct equal_to<::scan_payload>
     968                 :             :   {
     969                 :         578 :     std::size_t operator() (const ::scan_payload& a, const ::scan_payload& b) const noexcept
     970                 :             :     {
     971   [ +  -  -  - ]:         578 :       return a.first == b.first;
     972                 :             :     }
     973                 :             :   };
     974                 :             : }
     975                 :             : 
     976                 :             : static workq<scan_payload> scanq; // just a single one
     977                 :             : // producer & idler: thread_main_fts_source_paths()
     978                 :             : // consumer: thread_main_scanner()
     979                 :             : // idler: thread_main_groom()
     980                 :             : 
     981                 :             : 
     982                 :             : ////////////////////////////////////////////////////////////////////////
     983                 :             : 
     984                 :             : // Unique set is a thread-safe structure that lends 'ownership' of a value
     985                 :             : // to a thread.  Other threads requesting the same thing are made to wait.
     986                 :             : // It's like a semaphore-on-demand.
     987                 :             : template <typename T>
     988                 :             : class unique_set
     989                 :             : {
     990                 :             : private:
     991                 :             :   set<T> values;
     992                 :             :   mutex mtx;
     993                 :             :   condition_variable cv;
     994                 :             : public:
     995                 :          60 :   unique_set() {}
     996                 :          60 :   ~unique_set() {}
     997                 :             : 
     998                 :        2595 :   void acquire(const T& value)
     999                 :             :   {
    1000                 :        2595 :     unique_lock<mutex> lock(mtx);
    1001         [ +  + ]:        6679 :     while (values.find(value) != values.end())
    1002         [ +  - ]:        4084 :       cv.wait(lock);
    1003         [ +  - ]:        2595 :     values.insert(value);
    1004                 :        2595 :   }
    1005                 :             : 
    1006                 :        2595 :   void release(const T& value)
    1007                 :             :   {
    1008                 :        2595 :     unique_lock<mutex> lock(mtx);
    1009                 :             :     // assert (values.find(value) != values.end());
    1010                 :        2595 :     values.erase(value);
    1011                 :        2595 :     cv.notify_all();
    1012                 :        2595 :   }
    1013                 :             : };
    1014                 :             : 
    1015                 :             : 
    1016                 :             : // This is the object that's instantiate to uniquely hold a value in a
    1017                 :             : // RAII-pattern way.
    1018                 :             : template <typename T>
    1019                 :             : class unique_set_reserver
    1020                 :             : {
    1021                 :             : private:
    1022                 :             :   unique_set<T>& please_hold;
    1023                 :             :   T mine;
    1024                 :             : public:
    1025                 :        2595 :   unique_set_reserver(unique_set<T>& t, const T& value):
    1026         [ +  - ]:        2595 :     please_hold(t), mine(value)  { please_hold.acquire(mine); }
    1027                 :        2595 :   ~unique_set_reserver() { please_hold.release(mine); }
    1028                 :             : };
    1029                 :             : 
    1030                 :             : 
    1031                 :             : ////////////////////////////////////////////////////////////////////////
    1032                 :             : 
    1033                 :             : // periodic_barrier is a concurrency control object that lets N threads
    1034                 :             : // periodically (based on counter value) agree to wait at a barrier,
    1035                 :             : // let one of them carry out some work, then be set free
    1036                 :             : 
    1037                 :             : class periodic_barrier
    1038                 :             : {
    1039                 :             : private:
    1040                 :             :   unsigned period; // number of count() reports to trigger barrier activation
    1041                 :             :   unsigned threads; // number of threads participating
    1042                 :             :   mutex mtx; // protects all the following fields
    1043                 :             :   unsigned counter; // count of count() reports in the current generation
    1044                 :             :   unsigned generation; // barrier activation generation
    1045                 :             :   unsigned waiting; // number of threads waiting for barrier
    1046                 :             :   bool dead; // bring out your
    1047                 :             :   condition_variable cv;
    1048                 :             : public:
    1049                 :          72 :   periodic_barrier(unsigned t, unsigned p):
    1050                 :          72 :     period(p), threads(t), counter(0), generation(0), waiting(0), dead(false) { }
    1051                 :             :   virtual ~periodic_barrier() {}
    1052                 :             : 
    1053                 :             :   virtual void periodic_barrier_work() noexcept = 0;
    1054                 :          72 :   void nuke() {
    1055                 :          72 :     unique_lock<mutex> lock(mtx);
    1056                 :          72 :     dead = true;
    1057                 :          72 :     cv.notify_all();
    1058                 :          72 :   }
    1059                 :             :   
    1060                 :        1602 :   void count()
    1061                 :             :   {
    1062                 :        1602 :     unique_lock<mutex> lock(mtx);
    1063                 :        1602 :     unsigned prev_generation = this->generation;
    1064         [ +  + ]:        1602 :     if (counter < period-1) // normal case: counter just freely running
    1065                 :             :       {
    1066                 :        1458 :         counter ++;
    1067                 :        1458 :         return;
    1068                 :             :       }
    1069         [ +  + ]:         144 :     else if (counter == period-1) // we're the doer
    1070                 :             :       {
    1071                 :          36 :         counter = period; // entering barrier holding phase
    1072                 :          36 :         cv.notify_all();
    1073   [ +  +  +  - ]:         175 :         while (waiting < threads-1 && !dead)
    1074         [ +  - ]:         103 :           cv.wait(lock);
    1075                 :             :         // all other threads are now stuck in the barrier
    1076                 :          36 :         this->periodic_barrier_work(); // NB: we're holding the mutex the whole time
    1077                 :             :         // reset for next barrier, releasing other waiters
    1078                 :          36 :         counter = 0;
    1079                 :          36 :         generation ++;
    1080                 :          36 :         cv.notify_all();
    1081                 :          36 :         return;
    1082                 :             :       }
    1083         [ +  - ]:         108 :     else if (counter == period) // we're a waiter, in holding phase
    1084                 :             :       {
    1085                 :         108 :         waiting ++;
    1086                 :         108 :         cv.notify_all();
    1087   [ +  +  +  +  :         388 :         while (counter == period && generation == prev_generation && !dead)
                   +  - ]
    1088         [ +  - ]:         172 :           cv.wait(lock);
    1089                 :         108 :         waiting --;
    1090                 :         108 :         return;
    1091                 :             :       }
    1092                 :        1602 :   }
    1093                 :             : };
    1094                 :             : 
    1095                 :             : 
    1096                 :             : 
    1097                 :             : ////////////////////////////////////////////////////////////////////////
    1098                 :             : 
    1099                 :             : 
    1100                 :             : // Print a standard timestamp.
    1101                 :             : static ostream&
    1102                 :       51256 : timestamp (ostream &o)
    1103                 :             : {
    1104                 :       51256 :   char datebuf[80];
    1105                 :       51256 :   char *now2 = NULL;
    1106                 :       51256 :   time_t now_t = time(NULL);
    1107                 :       51257 :   struct tm now;
    1108                 :       51257 :   struct tm *nowp = gmtime_r (&now_t, &now);
    1109         [ +  - ]:       51258 :   if (nowp)
    1110                 :             :     {
    1111                 :       51258 :       (void) strftime (datebuf, sizeof (datebuf), "%c", nowp);
    1112                 :       51258 :       now2 = datebuf;
    1113                 :             :     }
    1114                 :             : 
    1115                 :       51258 :   return o << "[" << (now2 ? now2 : "") << "] "
    1116         [ -  + ]:       51258 :            << "(" << getpid () << "/" << tid() << "): ";
    1117                 :             : }
    1118                 :             : 
    1119                 :             : 
    1120                 :             : // A little class that impersonates an ostream to the extent that it can
    1121                 :             : // take << streaming operations.  It batches up the bits into an internal
    1122                 :             : // stringstream until it is destroyed; then flushes to the original ostream.
    1123                 :             : // It adds a timestamp
    1124                 :             : class obatched
    1125                 :             : {
    1126                 :             : private:
    1127                 :             :   ostream& o;
    1128                 :             :   stringstream stro;
    1129                 :             :   static mutex lock;
    1130                 :             : public:
    1131                 :       51254 :   obatched(ostream& oo, bool timestamp_p = true): o(oo)
    1132                 :             :   {
    1133         [ +  - ]:       51258 :     if (timestamp_p)
    1134         [ +  - ]:       51258 :       timestamp(stro);
    1135                 :       51249 :   }
    1136                 :       51253 :   ~obatched()
    1137                 :             :   {
    1138                 :       51253 :     unique_lock<mutex> do_not_cross_the_streams(obatched::lock);
    1139                 :       51264 :     o << stro.str();
    1140                 :       51264 :     o.flush();
    1141                 :       51264 :   }
    1142                 :             :   operator ostream& () { return stro; }
    1143   [ -  -  +  -  :       41131 :   template <typename T> ostream& operator << (const T& t) { stro << t; return stro; }
          +  -  +  -  +  
          -  +  -  +  -  
          -  -  -  -  -  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          -  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  -  
          -  +  -  -  -  
          +  -  -  -  +  
          -  +  -  +  -  
          -  -  -  -  -  
          -  -  -  -  -  
          -  -  +  -  -  
          -  +  -  +  -  
          -  -  +  -  +  
          -  -  -  +  -  
          -  -  +  -  -  
          -  -  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
             +  -  -  - ]
    1144                 :             : };
    1145                 :             : mutex obatched::lock; // just the one, since cout/cerr iostreams are not thread-safe
    1146                 :             : 
    1147                 :             : 
    1148                 :         695 : void reportable_exception::report(ostream& o) const {
    1149   [ +  -  +  - ]:         695 :   obatched(o) << message << endl;
    1150                 :         695 : }
    1151                 :             : 
    1152                 :             : 
    1153                 :             : ////////////////////////////////////////////////////////////////////////
    1154                 :             : 
    1155                 :             : 
    1156                 :             : // RAII style sqlite prepared-statement holder that matches { } block lifetime
    1157                 :             : 
    1158                 :             : struct sqlite_ps
    1159                 :             : {
    1160                 :             : private:
    1161                 :             :   sqlite3* db;
    1162                 :             :   const string nickname;
    1163                 :             :   const string sql;
    1164                 :             :   sqlite3_stmt *pp;
    1165                 :             :   // for step_timeout()/callback
    1166                 :             :   struct timespec ts_start;
    1167                 :             :   double ts_timeout;
    1168                 :             :   
    1169                 :             :   sqlite_ps(const sqlite_ps&); // make uncopyable
    1170                 :             :   sqlite_ps& operator=(const sqlite_ps &); // make unassignable
    1171                 :             : 
    1172                 :             : public:
    1173         [ +  - ]:        9836 :   sqlite_ps (sqlite3* d, const string& n, const string& s): db(d), nickname(n), sql(s) {
    1174                 :             :     // tmp_ms_metric tick("sqlite3","prep",nickname);
    1175         [ +  + ]:        9837 :     if (verbose > 4)
    1176   [ +  -  +  -  :         174 :       obatched(clog) << nickname << " prep " << sql << endl;
          +  -  +  -  +  
                      - ]
    1177         [ +  - ]:        9837 :     int rc = sqlite3_prepare_v2 (db, sql.c_str(), -1 /* to \0 */, & this->pp, NULL);
    1178         [ -  + ]:        9838 :     if (rc != SQLITE_OK)
    1179   [ #  #  #  # ]:           0 :       throw sqlite_exception(rc, "prepare " + sql);
    1180                 :       19676 :     this->reset_timeout(0.0);
    1181                 :           0 :   }
    1182                 :             : 
    1183                 :      198254 :   sqlite_ps& reset()
    1184                 :             :   {
    1185   [ +  -  +  - ]:      396478 :     tmp_ms_metric tick("sqlite3","reset",nickname);
    1186         [ +  - ]:      198225 :     sqlite3_reset(this->pp);
    1187                 :      198300 :     return *this;
    1188                 :      198228 :   }
    1189                 :             : 
    1190                 :      226753 :   sqlite_ps& bind(int parameter, const string& str)
    1191                 :             :   {
    1192         [ +  + ]:      226753 :     if (verbose > 4)
    1193   [ +  -  +  -  :         196 :       obatched(clog) << nickname << " bind " << parameter << "=" << str << endl;
          +  -  +  -  +  
                -  +  - ]
    1194                 :      226753 :     int rc = sqlite3_bind_text (this->pp, parameter, str.c_str(), -1, SQLITE_TRANSIENT);
    1195         [ -  + ]:      226597 :     if (rc != SQLITE_OK)
    1196   [ #  #  #  # ]:           0 :       throw sqlite_exception(rc, "sqlite3 bind");
    1197                 :      226597 :     return *this;
    1198                 :             :   }
    1199                 :             : 
    1200                 :       61788 :   sqlite_ps& bind(int parameter, int64_t value)
    1201                 :             :   {
    1202         [ +  + ]:       61788 :     if (verbose > 4)
    1203   [ +  -  +  -  :          64 :       obatched(clog) << nickname << " bind " << parameter << "=" << value << endl;
          +  -  +  -  +  
                -  +  - ]
    1204                 :       61788 :     int rc = sqlite3_bind_int64 (this->pp, parameter, value);
    1205         [ -  + ]:       61799 :     if (rc != SQLITE_OK)
    1206   [ #  #  #  # ]:           0 :       throw sqlite_exception(rc, "sqlite3 bind");
    1207                 :       61799 :     return *this;
    1208                 :             :   }
    1209                 :             : 
    1210                 :             :   sqlite_ps& bind(int parameter)
    1211                 :             :   {
    1212                 :             :     if (verbose > 4)
    1213                 :             :       obatched(clog) << nickname << " bind " << parameter << "=" << "NULL" << endl;
    1214                 :             :     int rc = sqlite3_bind_null (this->pp, parameter);
    1215                 :             :     if (rc != SQLITE_OK)
    1216                 :             :       throw sqlite_exception(rc, "sqlite3 bind");
    1217                 :             :     return *this;
    1218                 :             :   }
    1219                 :             : 
    1220                 :             : 
    1221                 :      121703 :   void step_ok_done() {
    1222   [ +  -  +  - ]:      243293 :     tmp_ms_metric tick("sqlite3","step_done",nickname);
    1223         [ +  - ]:      121592 :     int rc = sqlite3_step (this->pp);
    1224         [ +  + ]:      121915 :     if (verbose > 4)
    1225   [ +  -  +  -  :         128 :       obatched(clog) << nickname << " step-ok-done(" << sqlite3_errstr(rc) << ") " << sql << endl;
          +  -  +  -  +  
          -  +  -  +  -  
                   +  - ]
    1226   [ +  +  -  + ]:      121915 :     if (rc != SQLITE_OK && rc != SQLITE_DONE && rc != SQLITE_ROW)
    1227   [ #  #  #  # ]:           0 :       throw sqlite_exception(rc, "sqlite3 step");
    1228         [ +  - ]:      121915 :     (void) sqlite3_reset (this->pp);
    1229                 :      121913 :   }
    1230                 :             : 
    1231                 :             : 
    1232                 :       40869 :   int step() {
    1233   [ +  -  +  - ]:       81742 :     tmp_ms_metric tick("sqlite3","step",nickname);
    1234         [ +  - ]:       40873 :     int rc = sqlite3_step (this->pp);
    1235         [ +  + ]:       40874 :     if (verbose > 4)
    1236   [ +  -  +  -  :          62 :       obatched(clog) << nickname << " step(" << sqlite3_errstr(rc) << ") " << sql << endl;
          +  -  +  -  +  
          -  +  -  +  -  
                   +  - ]
    1237                 :       40874 :     return rc;
    1238                 :       40874 :   }
    1239                 :             : 
    1240                 :             : 
    1241                 :        9868 :   void reset_timeout(double s) // set starting point for maximum elapsed time in step_timeouts() 
    1242                 :             :   {
    1243                 :        9838 :     clock_gettime (CLOCK_MONOTONIC, &this->ts_start);
    1244                 :        9838 :     this->ts_timeout = s;
    1245                 :             :   }
    1246                 :             : 
    1247                 :             :   
    1248                 :           0 :   static int sqlite3_progress_handler_cb (void *param)
    1249                 :             :   {
    1250                 :           0 :     sqlite_ps *pp = (sqlite_ps*) param;
    1251                 :           0 :     struct timespec ts_end;
    1252                 :           0 :     clock_gettime (CLOCK_MONOTONIC, &ts_end);
    1253                 :           0 :     double deltas = (ts_end.tv_sec - pp->ts_start.tv_sec) + (ts_end.tv_nsec - pp->ts_start.tv_nsec)/1.e9;
    1254   [ #  #  #  # ]:           0 :     return (interrupted || (deltas > pp->ts_timeout)); // non-zero => interrupt sqlite operation in progress
    1255                 :             :   }
    1256                 :             :   
    1257                 :             : 
    1258                 :          48 :   int step_timeout() {
    1259                 :             :     // Do the same thing as step(), except wrapping it into a timeout
    1260                 :             :     // relative to the last reset_timeout() invocation.
    1261                 :             :     //
    1262                 :             :     // Do this by attaching a progress_handler to the database
    1263                 :             :     // connection, for the duration of this operation.  It should be a
    1264                 :             :     // private connection to the calling thread, so other operations
    1265                 :             :     // cannot begin concurrently.
    1266                 :             :     
    1267                 :          48 :     sqlite3_progress_handler(this->db, 10000 /* bytecode insns */,
    1268                 :             :                              & sqlite3_progress_handler_cb, (void*) this);
    1269                 :          48 :     int rc = this->step();
    1270                 :          48 :     sqlite3_progress_handler(this->db, 0, 0, 0); // disable
    1271                 :          48 :     struct timespec ts_end;
    1272                 :          48 :     clock_gettime (CLOCK_MONOTONIC, &ts_end);
    1273                 :          48 :     double deltas = (ts_end.tv_sec - this->ts_start.tv_sec) + (ts_end.tv_nsec - this->ts_start.tv_nsec)/1.e9;
    1274         [ -  + ]:          48 :     if (verbose > 3)
    1275   [ #  #  #  #  :           0 :       obatched(clog) << this->nickname << " progress-delta-final " << deltas << endl;
             #  #  #  # ]
    1276                 :          48 :     return rc;
    1277                 :             :   }
    1278                 :             : 
    1279                 :             :   
    1280                 :        9824 :   ~sqlite_ps () { sqlite3_finalize (this->pp); }
    1281   [ +  -  +  -  :        7088 :   operator sqlite3_stmt* () { return this->pp; }
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
                      - ]
    1282                 :             : };
    1283                 :             : 
    1284                 :             : 
    1285                 :             : ////////////////////////////////////////////////////////////////////////
    1286                 :             : 
    1287                 :             : 
    1288                 :             : struct sqlite_checkpoint_pb: public periodic_barrier
    1289                 :             : {
    1290                 :             :   // NB: don't use sqlite_ps since it can throw exceptions during ctor etc.
    1291                 :          72 :   sqlite_checkpoint_pb(unsigned t, unsigned p):
    1292                 :         144 :     periodic_barrier(t, p) { }
    1293                 :             :   
    1294                 :          36 :   void periodic_barrier_work() noexcept
    1295                 :             :   {
    1296                 :          36 :     (void) sqlite3_exec (db, "pragma wal_checkpoint(truncate);", NULL, NULL, NULL);
    1297                 :          36 :   }
    1298                 :             : };
    1299                 :             :   
    1300                 :             : static periodic_barrier* scan_barrier = 0; // initialized in main()
    1301                 :             : 
    1302                 :             : 
    1303                 :             : ////////////////////////////////////////////////////////////////////////
    1304                 :             : 
    1305                 :             : // RAII style templated autocloser
    1306                 :             : 
    1307                 :             : template <class Payload, class Ignore>
    1308                 :             : struct defer_dtor
    1309                 :             : {
    1310                 :             : public:
    1311                 :             :   typedef Ignore (*dtor_fn) (Payload);
    1312                 :             : 
    1313                 :             : private:
    1314                 :             :   Payload p;
    1315                 :             :   dtor_fn fn;
    1316                 :             : 
    1317                 :             : public:
    1318                 :        8022 :   defer_dtor(Payload _p, dtor_fn _fn): p(_p), fn(_fn) {}
    1319                 :         414 :   ~defer_dtor() { (void) (*fn)(p); }
    1320                 :             : 
    1321                 :             : private:
    1322                 :             :   defer_dtor(const defer_dtor<Payload,Ignore>&); // make uncopyable
    1323                 :             :   defer_dtor& operator=(const defer_dtor<Payload,Ignore> &); // make unassignable
    1324                 :             : };
    1325                 :             : 
    1326                 :             : 
    1327                 :             : 
    1328                 :             : ////////////////////////////////////////////////////////////////////////
    1329                 :             : 
    1330                 :             : 
    1331                 :             : static string
    1332                 :        6766 : header_censor(const string& str)
    1333                 :             : {
    1334                 :        6766 :   string y;
    1335         [ +  + ]:       83809 :   for (auto&& x : str)
    1336                 :             :     {
    1337         [ +  + ]:       77043 :       if (isalnum(x) || x == '/' || x == '.' || x == ',' || x == '_' || x == ':')
    1338         [ +  - ]:      154080 :         y += x;
    1339                 :             :     }
    1340                 :        6766 :   return y;
    1341                 :           0 : }
    1342                 :             : 
    1343                 :             : 
    1344                 :             : static string
    1345                 :        3383 : conninfo (struct MHD_Connection * conn)
    1346                 :             : {
    1347                 :        3383 :   char hostname[256]; // RFC1035
    1348                 :        3383 :   char servname[256];
    1349                 :        3383 :   int sts = -1;
    1350                 :             : 
    1351         [ -  + ]:        3383 :   if (conn == 0)
    1352                 :           0 :     return "internal";
    1353                 :             : 
    1354                 :             :   /* Look up client address data. */
    1355                 :        3383 :   const union MHD_ConnectionInfo *u = MHD_get_connection_info (conn,
    1356                 :             :                                                                MHD_CONNECTION_INFO_CLIENT_ADDRESS);
    1357         [ +  - ]:        3383 :   struct sockaddr *so = u ? u->client_addr : 0;
    1358                 :             : 
    1359   [ +  -  -  + ]:        3383 :   if (so && so->sa_family == AF_INET) {
    1360                 :           0 :     sts = getnameinfo (so, sizeof (struct sockaddr_in),
    1361                 :             :                        hostname, sizeof (hostname),
    1362                 :             :                        servname, sizeof (servname),
    1363                 :             :                        NI_NUMERICHOST | NI_NUMERICSERV);
    1364         [ +  - ]:        3383 :   } else if (so && so->sa_family == AF_INET6) {
    1365                 :        3383 :     struct sockaddr_in6* addr6 = (struct sockaddr_in6*) so;
    1366   [ +  -  +  -  :        3383 :     if (IN6_IS_ADDR_V4MAPPED(&addr6->sin6_addr)) {
                   +  + ]
    1367                 :        1621 :       struct sockaddr_in addr4;
    1368                 :        1621 :       memset (&addr4, 0, sizeof(addr4));
    1369                 :        1621 :       addr4.sin_family = AF_INET;
    1370                 :        1621 :       addr4.sin_port = addr6->sin6_port;
    1371                 :        1621 :       memcpy (&addr4.sin_addr.s_addr, addr6->sin6_addr.s6_addr+12, sizeof(addr4.sin_addr.s_addr));
    1372                 :        1621 :       sts = getnameinfo ((struct sockaddr*) &addr4, sizeof (addr4),
    1373                 :             :                          hostname, sizeof (hostname),
    1374                 :             :                          servname, sizeof (servname),
    1375                 :             :                          NI_NUMERICHOST | NI_NUMERICSERV);
    1376                 :             :     } else {
    1377                 :        1762 :       sts = getnameinfo (so, sizeof (struct sockaddr_in6),
    1378                 :             :                          hostname, sizeof (hostname),
    1379                 :             :                          servname, sizeof (servname),
    1380                 :             :                          NI_NUMERICHOST | NI_NUMERICSERV);
    1381                 :             :     }
    1382                 :             :   }
    1383                 :             :   
    1384         [ -  + ]:        3383 :   if (sts != 0) {
    1385                 :           0 :     hostname[0] = servname[0] = '\0';
    1386                 :             :   }
    1387                 :             : 
    1388                 :             :   // extract headers relevant to administration
    1389         [ +  + ]:        3383 :   const char* user_agent = MHD_lookup_connection_value (conn, MHD_HEADER_KIND, "User-Agent") ?: "";
    1390         [ +  + ]:        3383 :   const char* x_forwarded_for = MHD_lookup_connection_value (conn, MHD_HEADER_KIND, "X-Forwarded-For") ?: "";
    1391                 :             :   // NB: these are untrustworthy, beware if machine-processing log files
    1392                 :             : 
    1393   [ +  -  +  -  :       10149 :   return string(hostname) + string(":") + string(servname) +
          +  -  +  -  +  
                      - ]
    1394   [ +  -  +  -  :       13532 :     string(" UA:") + header_censor(string(user_agent)) +
          +  -  +  -  +  
                      - ]
    1395   [ +  -  +  -  :       10149 :     string(" XFF:") + header_censor(string(x_forwarded_for));
             +  -  +  - ]
    1396                 :             : }
    1397                 :             : 
    1398                 :             : 
    1399                 :             : 
    1400                 :             : ////////////////////////////////////////////////////////////////////////
    1401                 :             : 
    1402                 :             : /* Wrapper for MHD_add_response_header that logs an error if we
    1403                 :             :    couldn't add the specified header.  */
    1404                 :             : static void
    1405                 :       13309 : add_mhd_response_header (struct MHD_Response *r,
    1406                 :             :                          const char *h, const char *v)
    1407                 :             : {
    1408         [ -  + ]:       13309 :   if (MHD_add_response_header (r, h, v) == MHD_NO)
    1409   [ #  #  #  #  :           0 :     obatched(clog) << "Error: couldn't add '" << h << "' header" << endl;
                   #  # ]
    1410                 :       13309 : }
    1411                 :             : 
    1412                 :             : static void
    1413                 :        2152 : add_mhd_last_modified (struct MHD_Response *resp, time_t mtime)
    1414                 :             : {
    1415                 :        2152 :   struct tm now;
    1416                 :        2152 :   struct tm *nowp = gmtime_r (&mtime, &now);
    1417         [ +  - ]:        2152 :   if (nowp != NULL)
    1418                 :             :     {
    1419                 :        2152 :       char datebuf[80];
    1420                 :        2152 :       size_t rc = strftime (datebuf, sizeof (datebuf), "%a, %d %b %Y %T GMT",
    1421                 :             :                             nowp);
    1422         [ +  - ]:        2152 :       if (rc > 0 && rc < sizeof (datebuf))
    1423                 :        2152 :         add_mhd_response_header (resp, "Last-Modified", datebuf);
    1424                 :             :     }
    1425                 :             : 
    1426                 :        2152 :   add_mhd_response_header (resp, "Cache-Control", "public");
    1427                 :        2152 : }
    1428                 :             : 
    1429                 :             : // quote all questionable characters of str for safe passage through a sh -c expansion.
    1430                 :             : static string
    1431                 :         568 : shell_escape(const string& str)
    1432                 :             : {
    1433                 :         568 :   string y;
    1434         [ +  + ]:       72928 :   for (auto&& x : str)
    1435                 :             :     {
    1436   [ +  +  +  + ]:       72360 :       if (! isalnum(x) && x != '/')
    1437         [ +  - ]:        8966 :         y += "\\";
    1438         [ +  - ]:      144720 :       y += x;
    1439                 :             :     }
    1440                 :         568 :   return y;
    1441                 :           0 : }
    1442                 :             : 
    1443                 :             : 
    1444                 :             : // PR25548: Perform POSIX / RFC3986 style path canonicalization on the input string.
    1445                 :             : //
    1446                 :             : // Namely:
    1447                 :             : //    //         ->   /
    1448                 :             : //    /foo/../   ->   /
    1449                 :             : //    /./        ->   /
    1450                 :             : //
    1451                 :             : // This mapping is done on dwarf-side source path names, which may
    1452                 :             : // include these constructs, so we can deal with debuginfod clients
    1453                 :             : // that accidentally canonicalize the paths.
    1454                 :             : //
    1455                 :             : // realpath(3) is close but not quite right, because it also resolves
    1456                 :             : // symbolic links.  Symlinks at the debuginfod server have nothing to
    1457                 :             : // do with the build-time symlinks, thus they must not be considered.
    1458                 :             : //
    1459                 :             : // see also curl Curl_dedotdotify() aka RFC3986, which we mostly follow here
    1460                 :             : // see also libc __realpath()
    1461                 :             : // see also llvm llvm::sys::path::remove_dots()
    1462                 :             : static string
    1463                 :       16942 : canon_pathname (const string& input)
    1464                 :             : {
    1465                 :       16942 :   string i = input; // 5.2.4 (1)
    1466                 :       16942 :   string o;
    1467                 :             : 
    1468                 :       16942 :   while (i.size() != 0)
    1469                 :             :     {
    1470                 :             :       // 5.2.4 (2) A
    1471   [ +  -  -  + ]:      145114 :       if (i.substr(0,3) == "../")
    1472         [ #  # ]:           0 :         i = i.substr(3);
    1473   [ +  -  -  + ]:      145113 :       else if(i.substr(0,2) == "./")
    1474         [ #  # ]:           0 :         i = i.substr(2);
    1475                 :             : 
    1476                 :             :       // 5.2.4 (2) B
    1477   [ +  -  +  + ]:      145114 :       else if (i.substr(0,3) == "/./")
    1478         [ +  - ]:        1872 :         i = i.substr(2);
    1479         [ -  + ]:      143242 :       else if (i == "/.")
    1480         [ #  # ]:           0 :         i = ""; // no need to handle "/." complete-path-segment case; we're dealing with file names
    1481                 :             : 
    1482                 :             :       // 5.2.4 (2) C
    1483   [ +  -  +  + ]:      143242 :       else if (i.substr(0,4) == "/../") {
    1484         [ +  - ]:        2518 :         i = i.substr(3);
    1485                 :        2518 :         string::size_type sl = o.rfind("/");
    1486         [ +  - ]:        2518 :         if (sl != string::npos)
    1487         [ +  - ]:        2518 :           o = o.substr(0, sl);
    1488                 :             :         else
    1489         [ #  # ]:           0 :           o = "";
    1490         [ -  + ]:      140723 :       } else if (i == "/..")
    1491         [ #  # ]:           0 :         i = ""; // no need to handle "/.." complete-path-segment case; we're dealing with file names
    1492                 :             : 
    1493                 :             :       // 5.2.4 (2) D
    1494                 :             :       // no need to handle these cases; we're dealing with file names
    1495         [ -  + ]:      140723 :       else if (i == ".")
    1496         [ #  # ]:           0 :         i = "";
    1497         [ -  + ]:      140723 :       else if (i == "..")
    1498         [ #  # ]:           0 :         i = "";
    1499                 :             : 
    1500                 :             :       // POSIX special: map // to /
    1501   [ +  -  +  + ]:      140723 :       else if (i.substr(0,2) == "//")
    1502         [ +  - ]:         128 :         i = i.substr(1);
    1503                 :             : 
    1504                 :             :       // 5.2.4 (2) E
    1505                 :             :       else {
    1506         [ -  + ]:      140596 :         string::size_type next_slash = i.find("/", (i[0]=='/' ? 1 : 0)); // skip first slash
    1507         [ +  - ]:      281192 :         o += i.substr(0, next_slash);
    1508         [ +  + ]:      140596 :         if (next_slash == string::npos)
    1509   [ +  -  +  + ]:      178998 :           i = "";
    1510                 :             :         else
    1511         [ +  - ]:      123654 :           i = i.substr(next_slash);
    1512                 :             :       }
    1513                 :             :     }
    1514                 :             : 
    1515                 :       16942 :   return o;
    1516                 :       16942 : }
    1517                 :             : 
    1518                 :             : 
    1519                 :             : // Estimate available free space for a given filesystem via statfs(2).
    1520                 :             : // Return true if the free fraction is known to be smaller than the
    1521                 :             : // given minimum percentage.  Also update a related metric.
    1522                 :        4210 : bool statfs_free_enough_p(const string& path, const string& label, long minfree = 0)
    1523                 :             : {
    1524                 :        4210 :   struct statfs sfs;
    1525                 :        4210 :   int rc = statfs(path.c_str(), &sfs);
    1526         [ +  + ]:        4209 :   if (rc == 0)
    1527                 :             :     {
    1528                 :        4135 :       double s = (double) sfs.f_bavail / (double) sfs.f_blocks;
    1529   [ +  -  +  - ]:        8271 :       set_metric("filesys_free_ratio","purpose",label, s);
    1530                 :        4136 :       return ((s * 100.0) < minfree);
    1531                 :             :     }
    1532                 :             :   return false;
    1533                 :             : }
    1534                 :             : 
    1535                 :             : 
    1536                 :             : 
    1537                 :             : // A map-like class that owns a cache of file descriptors (indexed by
    1538                 :             : // file / content names).
    1539                 :             : //
    1540                 :             : // If only it could use fd's instead of file names ... but we can't
    1541                 :             : // dup(2) to create independent descriptors for the same unlinked
    1542                 :             : // files, so would have to use some goofy linux /proc/self/fd/%d
    1543                 :             : // hack such as the following
    1544                 :             : 
    1545                 :             : #if 0
    1546                 :             : int superdup(int fd)
    1547                 :             : {
    1548                 :             : #ifdef __linux__
    1549                 :             :   char *fdpath = NULL;
    1550                 :             :   int rc = asprintf(& fdpath, "/proc/self/fd/%d", fd);
    1551                 :             :   int newfd;
    1552                 :             :   if (rc >= 0)
    1553                 :             :     newfd = open(fdpath, O_RDONLY);
    1554                 :             :   else
    1555                 :             :     newfd = -1;
    1556                 :             :   free (fdpath);
    1557                 :             :   return newfd;
    1558                 :             : #else
    1559                 :             :   return -1;
    1560                 :             : #endif
    1561                 :             : }
    1562                 :             : #endif
    1563                 :             : 
    1564                 :             : class libarchive_fdcache
    1565                 :             : {
    1566                 :             : private:
    1567                 :             :   mutex fdcache_lock;
    1568                 :             : 
    1569                 :             :   typedef pair<string,string> key; // archive, entry
    1570                 :        1504 :   struct fdcache_entry
    1571                 :             :   {
    1572                 :             :     string fd; // file name (probably in $TMPDIR), not an actual open fd (EMFILE)
    1573                 :             :     double fd_size_mb; // slightly rounded up megabytes
    1574                 :             :     time_t freshness; // when was this entry created or requested last
    1575                 :             :     unsigned request_count; // how many requests were made; or 0=prefetch only
    1576                 :             :     double latency; // how many seconds it took to extract the file
    1577                 :             :   };
    1578                 :             : 
    1579                 :             :   map<key,fdcache_entry> entries; // optimized for lookup
    1580                 :             :   time_t last_cleaning;
    1581                 :             :   long max_mbs;
    1582                 :             : 
    1583                 :             : public:
    1584                 :        3006 :   void set_metrics()
    1585                 :             :   {
    1586                 :        3006 :     double fdcache_mb = 0.0;
    1587                 :        3006 :     double prefetch_mb = 0.0;
    1588                 :        3006 :     unsigned fdcache_count = 0;
    1589                 :        3006 :     unsigned prefetch_count = 0;
    1590         [ +  + ]:        6152 :     for (auto &i : entries) {
    1591         [ +  + ]:        3146 :       if (i.second.request_count) {
    1592                 :        2550 :         fdcache_mb += i.second.fd_size_mb;
    1593                 :        2550 :         fdcache_count ++;
    1594                 :             :       } else {
    1595                 :         596 :         prefetch_mb += i.second.fd_size_mb;
    1596                 :         596 :         prefetch_count ++;
    1597                 :             :       }
    1598                 :             :     }
    1599         [ +  - ]:        3006 :     set_metric("fdcache_bytes", fdcache_mb*1024.0*1024.0);
    1600         [ +  - ]:        3006 :     set_metric("fdcache_count", fdcache_count);
    1601         [ +  - ]:        3006 :     set_metric("fdcache_prefetch_bytes", prefetch_mb*1024.0*1024.0);
    1602         [ +  - ]:        3006 :     set_metric("fdcache_prefetch_count", prefetch_count);
    1603                 :        3006 :   }
    1604                 :             : 
    1605                 :        1504 :   void intern(const string& a, const string& b, string fd, off_t sz,
    1606                 :             :               bool requested_p, double lat)
    1607                 :             :   {
    1608                 :        1504 :     {
    1609                 :        1504 :       unique_lock<mutex> lock(fdcache_lock);
    1610                 :        1504 :       time_t now = time(NULL);
    1611                 :             :       // there is a chance it's already in here, just wasn't found last time
    1612                 :             :       // if so, there's nothing to do but count our luck
    1613         [ +  - ]:        1504 :       auto i = entries.find(make_pair(a,b));
    1614         [ -  + ]:        1504 :       if (i != entries.end())
    1615                 :             :         {
    1616   [ #  #  #  #  :           0 :           inc_metric("fdcache_op_count","op","redundant_intern");
             #  #  #  # ]
    1617         [ #  # ]:           0 :           if (requested_p) i->second.request_count ++; // repeat prefetch doesn't count
    1618                 :           0 :           i->second.freshness = now;
    1619                 :             :           // We need to nuke the temp file, since interning passes
    1620                 :             :           // responsibility over the path to this structure.  It is
    1621                 :             :           // possible that the caller still has an fd open, but that's
    1622                 :             :           // OK.
    1623                 :           0 :           unlink (fd.c_str());
    1624                 :           0 :           return;
    1625                 :             :         }
    1626                 :        1504 :       double mb = (sz+65535)/1048576.0; // round up to 64K block
    1627                 :        1504 :       fdcache_entry n = { .fd=fd, .fd_size_mb=mb,
    1628                 :        1504 :                           .freshness=now, .request_count = requested_p?1U:0U,
    1629   [ +  -  +  + ]:        1504 :                           .latency=lat};
    1630   [ +  -  +  -  :        1504 :       entries.insert(make_pair(make_pair(a,b),n));
                   +  - ]
    1631                 :             :       
    1632         [ +  + ]:        1504 :       if (requested_p)
    1633   [ +  -  +  -  :        1816 :         inc_metric("fdcache_op_count","op","enqueue");
             +  -  +  - ]
    1634                 :             :       else
    1635   [ +  -  +  -  :        1192 :         inc_metric("fdcache_op_count","op","prefetch_enqueue");
             +  -  +  - ]
    1636                 :             :       
    1637         [ +  + ]:        1504 :       if (verbose > 3)
    1638   [ +  -  +  - ]:        4164 :         obatched(clog) << "fdcache interned a=" << a << " b=" << b
    1639   [ +  -  +  -  :        1388 :                        << " fd=" << fd << " mb=" << mb << " front=" << requested_p
          +  -  +  -  +  
          -  +  -  +  -  
                   +  - ]
    1640   [ +  -  +  -  :        1388 :                        << " latency=" << lat << endl;
                   +  - ]
    1641                 :             :       
    1642         [ +  - ]:        1504 :       set_metrics();
    1643                 :        1504 :     }
    1644                 :             : 
    1645                 :             :     // NB: we age the cache at lookup time too
    1646   [ +  -  +  + ]:        1504 :     if (statfs_free_enough_p(tmpdir, "tmpdir", fdcache_mintmp))
    1647                 :             :       {
    1648   [ +  -  +  -  :        2836 :         inc_metric("fdcache_op_count","op","emerg-flush");
                   +  - ]
    1649         [ +  - ]:        2836 :         obatched(clog) << "fdcache emergency flush for filling tmpdir" << endl;
    1650                 :        1418 :         this->limit(0); // emergency flush
    1651                 :             :       }
    1652                 :             :     else // age cache normally
    1653                 :          86 :       this->limit(max_mbs);
    1654                 :             :   }
    1655                 :             : 
    1656                 :        1012 :   int lookup(const string& a, const string& b)
    1657                 :             :   {
    1658                 :        1012 :     int fd = -1;
    1659                 :        1012 :     {
    1660                 :        1012 :       unique_lock<mutex> lock(fdcache_lock);
    1661         [ +  - ]:        1012 :       auto i = entries.find(make_pair(a,b));
    1662         [ +  + ]:        1012 :       if (i != entries.end())
    1663                 :             :         {
    1664         [ +  + ]:         100 :           if (i->second.request_count == 0) // was a prefetch!
    1665                 :             :             {
    1666   [ +  -  +  - ]:           2 :               inc_metric("fdcache_prefetch_saved_milliseconds_count");
    1667   [ +  -  +  - ]:           4 :               add_metric("fdcache_prefetch_saved_milliseconds_sum", i->second.latency*1000.);
    1668                 :             :             }
    1669                 :         100 :           i->second.request_count ++;
    1670                 :         100 :           i->second.freshness = time(NULL);
    1671                 :             :           // brag about our success
    1672   [ +  -  +  -  :         200 :           inc_metric("fdcache_op_count","op","prefetch_access"); // backward compat
             +  -  +  - ]
    1673   [ +  -  +  - ]:         100 :           inc_metric("fdcache_saved_milliseconds_count");
    1674   [ +  -  +  - ]:         100 :           add_metric("fdcache_saved_milliseconds_sum", i->second.latency*1000.);
    1675         [ +  - ]:        1012 :           fd = open(i->second.fd.c_str(), O_RDONLY); 
    1676                 :             :         }
    1677                 :           0 :     }
    1678                 :             : 
    1679         [ +  + ]:        1012 :     if (fd >= 0)
    1680   [ +  -  +  -  :         200 :       inc_metric("fdcache_op_count","op","lookup_hit");
                   +  - ]
    1681                 :             :     else
    1682   [ +  -  +  -  :        1824 :       inc_metric("fdcache_op_count","op","lookup_miss");
                   +  - ]
    1683                 :             :     
    1684                 :             :     // NB: no need to age the cache after just a lookup
    1685                 :             : 
    1686                 :        1012 :     return fd;
    1687                 :             :   }
    1688                 :             : 
    1689                 :        1296 :   int probe(const string& a, const string& b) // just a cache residency check - don't modify state, don't open
    1690                 :             :   {
    1691                 :        1296 :     unique_lock<mutex> lock(fdcache_lock);
    1692         [ +  - ]:        1296 :     auto i = entries.find(make_pair(a,b));
    1693         [ -  + ]:        1296 :     if (i != entries.end()) {
    1694   [ #  #  #  #  :           0 :       inc_metric("fdcache_op_count","op","probe_hit");
             #  #  #  # ]
    1695                 :           0 :       return true;
    1696                 :             :     } else {
    1697   [ +  -  +  -  :        2592 :       inc_metric("fdcache_op_count","op","probe_miss");
             +  -  +  - ]
    1698                 :        1296 :       return false;
    1699                 :             :    }
    1700                 :        1296 :   }
    1701                 :             :   
    1702                 :           0 :   void clear(const string& a, const string& b)
    1703                 :             :   {
    1704                 :           0 :     unique_lock<mutex> lock(fdcache_lock);
    1705         [ #  # ]:           0 :     auto i = entries.find(make_pair(a,b));
    1706         [ #  # ]:           0 :     if (i != entries.end()) {
    1707   [ #  #  #  #  :           0 :       inc_metric("fdcache_op_count","op",
          #  #  #  #  #  
                      # ]
    1708         [ #  # ]:           0 :                  i->second.request_count > 0 ? "clear" : "prefetch_clear");
    1709                 :           0 :       unlink (i->second.fd.c_str());
    1710                 :           0 :       entries.erase(i);
    1711         [ #  # ]:           0 :       set_metrics();
    1712                 :           0 :       return;
    1713                 :             :     }
    1714                 :           0 :   }
    1715                 :             : 
    1716                 :        1672 :   void limit(long maxmbs, bool metrics_p = true)
    1717                 :             :   {
    1718                 :        1672 :     time_t now = time(NULL);
    1719                 :             : 
    1720                 :             :     // avoid overly frequent limit operations
    1721   [ +  +  +  + ]:        1672 :     if (maxmbs > 0 && (now - this->last_cleaning) < 10) // probably not worth parametrizing
    1722                 :          86 :       return;
    1723                 :        1586 :     this->last_cleaning = now;
    1724                 :             :     
    1725   [ +  +  +  + ]:        1586 :     if (verbose > 3 && (this->max_mbs != maxmbs))
    1726   [ +  -  +  - ]:         192 :       obatched(clog) << "fdcache limited to maxmbs=" << maxmbs << endl;
    1727                 :             : 
    1728                 :        1586 :     unique_lock<mutex> lock(fdcache_lock);
    1729                 :             :     
    1730                 :        1586 :     this->max_mbs = maxmbs;
    1731                 :        1586 :     double total_mb = 0.0;
    1732                 :             : 
    1733                 :        1586 :     map<double, pair<string,string>> sorted_entries;
    1734         [ +  + ]:        3090 :     for (auto &i: entries)
    1735                 :             :       {
    1736                 :        1504 :         total_mb += i.second.fd_size_mb;
    1737                 :             : 
    1738                 :             :         // need a scalar quantity that combines these inputs in a sensible way:
    1739                 :             :         //
    1740                 :             :         // 1) freshness of this entry (last time it was accessed)
    1741                 :             :         // 2) size of this entry
    1742                 :             :         // 3) number of times it has been accessed (or if just prefetched with 0 accesses)
    1743                 :             :         // 4) latency it required to extract
    1744                 :             :         //
    1745                 :             :         // The lower the "score", the earlier garbage collection will
    1746                 :             :         // nuke it, so to prioritize entries for preservation, the
    1747                 :             :         // score should be higher, and vice versa.
    1748                 :        1504 :         time_t factor_1_freshness = (now - i.second.freshness); // seconds
    1749                 :        1504 :         double factor_2_size = i.second.fd_size_mb; // megabytes
    1750                 :        1504 :         unsigned factor_3_accesscount = i.second.request_count; // units
    1751                 :        1504 :         double factor_4_latency = i.second.latency; // seconds
    1752                 :             : 
    1753                 :             :         #if 0
    1754                 :             :         double score = - factor_1_freshness; // simple LRU
    1755                 :             :         #endif
    1756                 :             : 
    1757         [ +  + ]:        1504 :         double score = 0.
    1758                 :        1504 :           - log1p(factor_1_freshness)                // penalize old file
    1759                 :        1504 :           - log1p(factor_2_size)                     // penalize large file
    1760                 :        1504 :           + factor_4_latency * factor_3_accesscount; // reward slow + repeatedly read files
    1761                 :             : 
    1762         [ +  + ]:        1504 :         if (verbose > 4)
    1763         [ +  - ]:           8 :           obatched(clog) << "fdcache scored score=" << score
    1764   [ +  -  +  - ]:           8 :                          << " a=" << i.first.first << " b=" << i.first.second
    1765   [ +  -  +  -  :          12 :                          << " f1=" << factor_1_freshness << " f2=" << factor_2_size
          +  -  +  -  +  
             -  +  -  +  
                      - ]
    1766   [ +  -  +  -  :           4 :                          << " f3=" << factor_3_accesscount << " f4=" << factor_4_latency
          +  -  +  -  +  
                      - ]
    1767                 :           4 :                          << endl;
    1768                 :             :         
    1769   [ +  -  +  - ]:        4512 :         sorted_entries.insert(make_pair(score, i.first));
    1770                 :             :       }
    1771                 :             : 
    1772                 :        1586 :     unsigned cleaned = 0;
    1773                 :        1586 :     unsigned entries_original = entries.size();
    1774                 :        1586 :     double cleaned_score_min = DBL_MAX;
    1775                 :        1586 :     double cleaned_score_max = DBL_MIN;
    1776                 :             :     
    1777                 :             :     // drop as many entries[] as needed to bring total mb down to the threshold
    1778         [ +  + ]:        3090 :     for (auto &i: sorted_entries) // in increasing score order!
    1779                 :             :       {
    1780         [ -  + ]:        1504 :         if (this->max_mbs > 0 // if this is not a "clear entire table"
    1781         [ #  # ]:           0 :             && total_mb < this->max_mbs) // we've cleared enough to meet threshold
    1782                 :             :           break; // stop clearing
    1783                 :             : 
    1784         [ -  + ]:        1504 :         auto j = entries.find(i.second);
    1785         [ -  + ]:        1504 :         if (j == entries.end())
    1786                 :           0 :           continue; // should not happen
    1787                 :             : 
    1788         [ +  + ]:        1504 :         if (cleaned == 0)
    1789                 :        1422 :           cleaned_score_min = i.first;
    1790                 :        1504 :         cleaned++;
    1791                 :        1504 :         cleaned_score_max = i.first;
    1792                 :             :         
    1793         [ +  + ]:        1504 :         if (verbose > 3)
    1794   [ +  -  +  - ]:        4164 :           obatched(clog) << "fdcache evicted score=" << i.first
    1795   [ +  -  +  - ]:        2776 :                          << " a=" << i.second.first << " b=" << i.second.second
    1796   [ +  -  +  -  :        4164 :                          << " fd=" << j->second.fd << " mb=" << j->second.fd_size_mb
          +  -  +  -  +  
                -  +  - ]
    1797   [ +  -  +  -  :        1388 :                          << " rq=" << j->second.request_count << " lat=" << j->second.latency
             +  -  +  - ]
    1798   [ +  -  +  -  :        1388 :                          << " fr=" << (now - j->second.freshness)
                   +  - ]
    1799                 :        1388 :                          << endl;
    1800         [ +  + ]:        1504 :         if (metrics_p)
    1801   [ +  -  +  -  :        2836 :           inc_metric("fdcache_op_count","op","evict");
             +  -  +  - ]
    1802                 :             :         
    1803                 :        1504 :         total_mb -= j->second.fd_size_mb;
    1804                 :        1504 :         unlink (j->second.fd.c_str());
    1805                 :        1504 :         entries.erase(j);
    1806                 :             :       }
    1807                 :             : 
    1808         [ +  + ]:        1586 :     if (metrics_p)
    1809   [ +  -  +  -  :        3004 :       inc_metric("fdcache_op_count","op","evict_cycle");
             +  -  +  - ]
    1810                 :             :     
    1811   [ +  -  +  + ]:        1586 :     if (verbose > 1 && cleaned > 0)
    1812                 :             :       {
    1813   [ +  -  +  -  :        4266 :         obatched(clog) << "fdcache evicted num=" << cleaned << " of=" << entries_original
             +  -  +  - ]
    1814   [ +  -  +  -  :        1422 :                        << " min=" << cleaned_score_min << " max=" << cleaned_score_max
          +  -  +  -  +  
                      - ]
    1815                 :        1422 :                        << endl;
    1816                 :             :       }
    1817                 :             :     
    1818   [ +  +  +  - ]:        1586 :     if (metrics_p) set_metrics();
    1819                 :        1586 :   }
    1820                 :             : 
    1821                 :             : 
    1822                 :          84 :   ~libarchive_fdcache()
    1823                 :             :   {
    1824                 :             :     // unlink any fdcache entries in $TMPDIR
    1825                 :             :     // don't update metrics; those globals may be already destroyed
    1826                 :          84 :     limit(0, false);
    1827                 :          84 :   }
    1828                 :             : };
    1829                 :             : static libarchive_fdcache fdcache;
    1830                 :             : 
    1831                 :             : /* Search ELF_FD for an ELF/DWARF section with name SECTION.
    1832                 :             :    If found copy the section to a temporary file and return
    1833                 :             :    its file descriptor, otherwise return -1.
    1834                 :             : 
    1835                 :             :    The temporary file's mtime will be set to PARENT_MTIME.
    1836                 :             :    B_SOURCE should be a description of the parent file suitable
    1837                 :             :    for printing to the log.  */
    1838                 :             : 
    1839                 :             : static int
    1840                 :          12 : extract_section (int elf_fd, int64_t parent_mtime,
    1841                 :             :                  const string& b_source, const string& section,
    1842                 :             :                  const timespec& extract_begin)
    1843                 :             : {
    1844                 :             :   /* Search the fdcache.  */
    1845                 :          12 :   struct stat fs;
    1846                 :          12 :   int fd = fdcache.lookup (b_source, section);
    1847         [ -  + ]:          12 :   if (fd >= 0)
    1848                 :             :     {
    1849         [ #  # ]:           0 :       if (fstat (fd, &fs) != 0)
    1850                 :             :         {
    1851         [ #  # ]:           0 :           if (verbose)
    1852         [ #  # ]:           0 :             obatched (clog) << "cannot fstate fdcache "
    1853   [ #  #  #  #  :           0 :                             << b_source << " " << section << endl;
                   #  # ]
    1854                 :           0 :           close (fd);
    1855                 :           0 :           return -1;
    1856                 :             :         }
    1857         [ #  # ]:           0 :       if ((int64_t) fs.st_mtime != parent_mtime)
    1858                 :             :         {
    1859         [ #  # ]:           0 :           if (verbose)
    1860         [ #  # ]:           0 :             obatched(clog) << "mtime mismatch for "
    1861   [ #  #  #  #  :           0 :                            << b_source << " " << section << endl;
                   #  # ]
    1862                 :           0 :           close (fd);
    1863                 :           0 :           return -1;
    1864                 :             :         }
    1865                 :             :       /* Success.  */
    1866                 :             :       return fd;
    1867                 :             :     }
    1868                 :             : 
    1869                 :          12 :   Elf *elf = elf_begin (elf_fd, ELF_C_READ_MMAP_PRIVATE, NULL);
    1870         [ -  + ]:          12 :   if (elf == NULL)
    1871                 :             :     return -1;
    1872                 :             : 
    1873                 :             :   /* Try to find the section and copy the contents into a separate file.  */
    1874                 :          12 :   try
    1875                 :             :     {
    1876                 :          12 :       size_t shstrndx;
    1877         [ +  - ]:          12 :       int rc = elf_getshdrstrndx (elf, &shstrndx);
    1878         [ -  + ]:          12 :       if (rc < 0)
    1879   [ #  #  #  # ]:           0 :         throw elfutils_exception (rc, "getshdrstrndx");
    1880                 :             : 
    1881                 :             :       Elf_Scn *scn = NULL;
    1882                 :         424 :       while (true)
    1883                 :             :         {
    1884         [ +  - ]:         218 :           scn = elf_nextscn (elf, scn);
    1885         [ +  - ]:         218 :           if (scn == NULL)
    1886                 :             :             break;
    1887                 :         218 :           GElf_Shdr shdr_storage;
    1888         [ +  - ]:         218 :           GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_storage);
    1889         [ +  - ]:         218 :           if (shdr == NULL)
    1890                 :             :             break;
    1891                 :             : 
    1892         [ +  - ]:         218 :           const char *scn_name = elf_strptr (elf, shstrndx, shdr->sh_name);
    1893         [ +  - ]:         218 :           if (scn_name == NULL)
    1894                 :             :             break;
    1895         [ +  + ]:         218 :           if (scn_name == section)
    1896                 :             :             {
    1897                 :          12 :               Elf_Data *data = NULL;
    1898                 :             : 
    1899                 :             :               /* We found the desired section.  */
    1900         [ +  - ]:          12 :               data = elf_rawdata (scn, NULL);
    1901         [ -  + ]:          12 :               if (data == NULL)
    1902   [ #  #  #  #  :           0 :                 throw elfutils_exception (elf_errno (), "elfraw_data");
                   #  # ]
    1903         [ +  + ]:          12 :               if (data->d_buf == NULL)
    1904                 :             :                 {
    1905   [ +  -  +  - ]:           8 :                   obatched(clog) << "section " << section
    1906   [ +  -  +  - ]:           4 :                                  << " is empty" << endl;
    1907                 :           4 :                   break;
    1908                 :             :                 }
    1909                 :             : 
    1910                 :             :               /* Create temporary file containing the section.  */
    1911                 :           8 :               char *tmppath = NULL;
    1912                 :           8 :               rc = asprintf (&tmppath, "%s/debuginfod-section.XXXXXX", tmpdir.c_str());
    1913         [ -  + ]:           8 :               if (rc < 0)
    1914   [ #  #  #  # ]:           0 :                 throw libc_exception (ENOMEM, "cannot allocate tmppath");
    1915                 :           8 :               defer_dtor<void*,void> tmmpath_freer (tmppath, free);
    1916         [ +  - ]:           8 :               fd = mkstemp (tmppath);
    1917         [ -  + ]:           8 :               if (fd < 0)
    1918   [ #  #  #  # ]:           0 :                 throw libc_exception (errno, "cannot create temporary file");
    1919                 :             : 
    1920         [ +  - ]:           8 :               ssize_t res = write_retry (fd, data->d_buf, data->d_size);
    1921   [ +  -  -  + ]:           8 :               if (res < 0 || (size_t) res != data->d_size) {
    1922         [ #  # ]:           0 :                 close (fd);
    1923                 :           0 :                 unlink (tmppath);
    1924   [ #  #  #  # ]:           0 :                 throw libc_exception (errno, "cannot write to temporary file");
    1925                 :             :               }
    1926                 :             : 
    1927                 :             :               /* Set mtime to be the same as the parent file's mtime.  */
    1928                 :           8 :               struct timespec tvs[2];
    1929         [ -  + ]:           8 :               if (fstat (elf_fd, &fs) != 0) {
    1930         [ #  # ]:           0 :                 close (fd);
    1931                 :           0 :                 unlink (tmppath);
    1932   [ #  #  #  # ]:           0 :                 throw libc_exception (errno, "cannot fstat file");
    1933                 :             :               }
    1934                 :             :               
    1935                 :           8 :               tvs[0].tv_sec = 0;
    1936                 :           8 :               tvs[0].tv_nsec = UTIME_OMIT;
    1937                 :           8 :               tvs[1] = fs.st_mtim;
    1938                 :           8 :               (void) futimens (fd, tvs);
    1939                 :             : 
    1940                 :           8 :               struct timespec extract_end;
    1941                 :           8 :               clock_gettime (CLOCK_MONOTONIC, &extract_end);
    1942                 :           8 :               double extract_time = (extract_end.tv_sec - extract_begin.tv_sec)
    1943                 :           8 :                 + (extract_end.tv_nsec - extract_begin.tv_nsec)/1.e9;
    1944                 :             :               
    1945                 :             :               /* Add to fdcache.  */
    1946   [ +  -  +  - ]:           8 :               fdcache.intern (b_source, section, tmppath, data->d_size, true, extract_time);
    1947                 :           8 :               break;
    1948                 :           8 :             }
    1949                 :         206 :         }
    1950                 :             :     }
    1951         [ -  - ]:           0 :   catch (const reportable_exception &e)
    1952                 :             :     {
    1953         [ -  - ]:           0 :       e.report (clog);
    1954         [ -  - ]:           0 :       close (fd);
    1955                 :           0 :       fd = -1;
    1956                 :           0 :     }
    1957                 :             : 
    1958                 :          12 :   elf_end (elf);
    1959                 :             :   return fd;
    1960                 :             : }
    1961                 :             : 
    1962                 :             : static struct MHD_Response*
    1963                 :        1152 : handle_buildid_f_match (bool internal_req_t,
    1964                 :             :                         int64_t b_mtime,
    1965                 :             :                         const string& b_source0,
    1966                 :             :                         const string& section,
    1967                 :             :                         int *result_fd)
    1968                 :             : {
    1969                 :        1152 :   (void) internal_req_t; // ignored
    1970                 :             : 
    1971                 :        1152 :   struct timespec extract_begin;
    1972                 :        1152 :   clock_gettime (CLOCK_MONOTONIC, &extract_begin);
    1973                 :             :   
    1974                 :        1152 :   int fd = open(b_source0.c_str(), O_RDONLY);
    1975         [ -  + ]:        1152 :   if (fd < 0)
    1976   [ #  #  #  #  :           0 :     throw libc_exception (errno, string("open ") + b_source0);
                   #  # ]
    1977                 :             :   
    1978                 :             :   // NB: use manual close(2) in error case instead of defer_dtor, because
    1979                 :             :   // in the normal case, we want to hand the fd over to libmicrohttpd for
    1980                 :             :   // file transfer.
    1981                 :             : 
    1982                 :        1152 :   struct stat s;
    1983                 :        1152 :   int rc = fstat(fd, &s);
    1984         [ -  + ]:        1152 :   if (rc < 0)
    1985                 :             :     {
    1986                 :           0 :       close(fd);
    1987   [ #  #  #  #  :           0 :       throw libc_exception (errno, string("fstat ") + b_source0);
                   #  # ]
    1988                 :             :     }
    1989                 :             : 
    1990         [ -  + ]:        1152 :   if ((int64_t) s.st_mtime != b_mtime)
    1991                 :             :     {
    1992         [ #  # ]:           0 :       if (verbose)
    1993   [ #  #  #  # ]:           0 :         obatched(clog) << "mtime mismatch for " << b_source0 << endl;
    1994                 :           0 :       close(fd);
    1995                 :           0 :       return 0;
    1996                 :             :     }
    1997                 :             : 
    1998         [ +  + ]:        1152 :   if (!section.empty ())
    1999                 :             :     {
    2000                 :           6 :       int scn_fd = extract_section (fd, s.st_mtime, b_source0, section, extract_begin);
    2001                 :           6 :       close (fd);
    2002                 :             : 
    2003         [ +  + ]:           6 :       if (scn_fd >= 0)
    2004                 :           4 :         fd = scn_fd;
    2005                 :             :       else
    2006                 :             :         {
    2007         [ +  - ]:           2 :           if (verbose)
    2008         [ +  - ]:           6 :             obatched (clog) << "cannot find section " << section
    2009   [ +  -  +  -  :           2 :                             << " for " << b_source0 << endl;
                   +  - ]
    2010                 :           2 :           return 0;
    2011                 :             :         }
    2012                 :             : 
    2013                 :           4 :       rc = fstat(fd, &s);
    2014         [ -  + ]:           4 :       if (rc < 0)
    2015                 :             :         {
    2016                 :           0 :           close (fd);
    2017   [ #  #  #  # ]:           0 :           throw libc_exception (errno, string ("fstat ") + b_source0
    2018   [ #  #  #  #  :           0 :                                        + string (" ") + section);
             #  #  #  # ]
    2019                 :             :         }
    2020                 :             :     }
    2021                 :             : 
    2022                 :        1150 :   struct MHD_Response* r = MHD_create_response_from_fd ((uint64_t) s.st_size, fd);
    2023   [ +  -  +  -  :        2300 :   inc_metric ("http_responses_total","result","file");
                   +  - ]
    2024         [ -  + ]:        1150 :   if (r == 0)
    2025                 :             :     {
    2026         [ #  # ]:           0 :       if (verbose)
    2027         [ #  # ]:           0 :         obatched(clog) << "cannot create fd-response for " << b_source0
    2028   [ #  #  #  #  :           0 :                        << " section=" << section << endl;
                   #  # ]
    2029                 :           0 :       close(fd);
    2030                 :             :     }
    2031                 :             :   else
    2032                 :             :     {
    2033                 :        1150 :       add_mhd_response_header (r, "Content-Type", "application/octet-stream");
    2034         [ +  - ]:        1150 :       add_mhd_response_header (r, "X-DEBUGINFOD-SIZE",
    2035                 :        1150 :                                to_string(s.st_size).c_str());
    2036                 :        1150 :       add_mhd_response_header (r, "X-DEBUGINFOD-FILE", b_source0.c_str());
    2037                 :        1150 :       add_mhd_last_modified (r, s.st_mtime);
    2038         [ +  - ]:        1150 :       if (verbose > 1)
    2039   [ +  -  +  -  :        2300 :         obatched(clog) << "serving file " << b_source0 << " section=" << section << endl;
             +  -  +  - ]
    2040                 :             :       /* libmicrohttpd will close it. */
    2041         [ -  + ]:        1150 :       if (result_fd)
    2042                 :        1150 :         *result_fd = fd;
    2043                 :             :     }
    2044                 :             : 
    2045                 :             :   return r;
    2046                 :             : }
    2047                 :             : 
    2048                 :             : 
    2049                 :             : #ifdef USE_LZMA
    2050                 :             : struct lzma_exception: public reportable_exception
    2051                 :             : {
    2052                 :           0 :   lzma_exception(int rc, const string& msg):
    2053                 :             :     // liblzma doesn't have a lzma_ret -> string conversion function, so just
    2054                 :             :     // report the value.
    2055   [ #  #  #  #  :           0 :     reportable_exception(string ("lzma error: ") + msg + ": error " + to_string(rc)) {
                   #  # ]
    2056   [ #  #  #  #  :           0 :       inc_metric("error_count","lzma",to_string(rc));
                   #  # ]
    2057                 :           0 :     }
    2058                 :             : };
    2059                 :             : 
    2060                 :             : // Neither RPM nor deb files support seeking to a specific file in the package.
    2061                 :             : // Instead, to extract a specific file, we normally need to read the archive
    2062                 :             : // sequentially until we find the file.  This is very slow for files at the end
    2063                 :             : // of a large package with lots of files, like kernel debuginfo.
    2064                 :             : //
    2065                 :             : // However, if the compression format used in the archive supports seeking, we
    2066                 :             : // can accelerate this.  As of July 2024, xz is the only widely-used format that
    2067                 :             : // supports seeking, and usually only in multi-threaded mode.  Luckily, the
    2068                 :             : // kernel-debuginfo package in Fedora and its downstreams, and the
    2069                 :             : // linux-image-*-dbg package in Debian and its downstreams, all happen to use
    2070                 :             : // this.
    2071                 :             : //
    2072                 :             : // The xz format [1] ends with an index of independently compressed blocks in
    2073                 :             : // the stream.  In RPM and deb files, the xz stream is the last thing in the
    2074                 :             : // file, so we assume that the xz Stream Footer is at the end of the package
    2075                 :             : // file and do everything relative to that.  For each file in the archive, we
    2076                 :             : // remember the size and offset of the file data in the uncompressed xz stream,
    2077                 :             : // then we use the index to seek to that offset when we need that file.
    2078                 :             : //
    2079                 :             : // 1: https://xz.tukaani.org/format/xz-file-format.txt
    2080                 :             : 
    2081                 :             : // Return whether an archive supports seeking.
    2082                 :             : static bool
    2083                 :        1092 : is_seekable_archive (const string& rps, struct archive* a)
    2084                 :             : {
    2085                 :             :   // Only xz supports seeking.
    2086         [ +  + ]:        1092 :   if (archive_filter_code (a, 0) != ARCHIVE_FILTER_XZ)
    2087                 :             :     return false;
    2088                 :             : 
    2089                 :         390 :   int fd = open (rps.c_str(), O_RDONLY);
    2090         [ -  + ]:         390 :   if (fd < 0)
    2091                 :             :     return false;
    2092                 :         390 :   defer_dtor<int,int> fd_closer (fd, close);
    2093                 :             : 
    2094                 :             :   // Seek to the xz Stream Footer.  We assume that it's the last thing in the
    2095                 :             :   // file, which is true for RPM and deb files.
    2096                 :         390 :   off_t footer_pos = -LZMA_STREAM_HEADER_SIZE;
    2097         [ -  + ]:         390 :   if (lseek (fd, footer_pos, SEEK_END) == -1)
    2098                 :             :     return false;
    2099                 :             : 
    2100                 :             :   // Decode the Stream Footer.
    2101                 :             :   uint8_t footer[LZMA_STREAM_HEADER_SIZE];
    2102                 :             :   size_t footer_read = 0;
    2103         [ +  + ]:         780 :   while (footer_read < sizeof (footer))
    2104                 :             :     {
    2105         [ -  + ]:         390 :       ssize_t bytes_read = read (fd, footer + footer_read,
    2106                 :             :                                  sizeof (footer) - footer_read);
    2107         [ -  + ]:         390 :       if (bytes_read < 0)
    2108                 :             :         {
    2109         [ #  # ]:           0 :           if (errno == EINTR)
    2110                 :           0 :             continue;
    2111                 :             :           return false;
    2112                 :             :         }
    2113         [ -  + ]:         390 :       if (bytes_read == 0)
    2114                 :             :         return false;
    2115                 :         390 :       footer_read += bytes_read;
    2116                 :             :     }
    2117                 :             : 
    2118                 :         390 :   lzma_stream_flags stream_flags;
    2119                 :         390 :   lzma_ret ret = lzma_stream_footer_decode (&stream_flags, footer);
    2120         [ -  + ]:         390 :   if (ret != LZMA_OK)
    2121                 :             :     return false;
    2122                 :             : 
    2123                 :             :   // Seek to the xz Index.
    2124         [ -  + ]:         390 :   if (lseek (fd, footer_pos - stream_flags.backward_size, SEEK_END) == -1)
    2125                 :             :     return false;
    2126                 :             : 
    2127                 :             :   // Decode the Number of Records in the Index.  liblzma doesn't have an API for
    2128                 :             :   // this if you don't want to decode the whole Index, so we have to do it
    2129                 :             :   // ourselves.
    2130                 :             :   //
    2131                 :             :   // We need 1 byte for the Index Indicator plus 1-9 bytes for the
    2132                 :             :   // variable-length integer Number of Records.
    2133                 :             :   uint8_t index[10];
    2134                 :             :   size_t index_read = 0;
    2135         [ +  + ]:         780 :   while (index_read == 0) {
    2136         [ +  - ]:         390 :       ssize_t bytes_read = read (fd, index, sizeof (index));
    2137         [ -  + ]:         390 :       if (bytes_read < 0)
    2138                 :             :         {
    2139         [ #  # ]:           0 :           if (errno == EINTR)
    2140                 :           0 :             continue;
    2141                 :             :           return false;
    2142                 :             :         }
    2143         [ -  + ]:         390 :       if (bytes_read == 0)
    2144                 :             :         return false;
    2145                 :         390 :       index_read += bytes_read;
    2146                 :             :   }
    2147                 :             :   // The Index Indicator must be 0.
    2148         [ -  + ]:         390 :   if (index[0] != 0)
    2149                 :             :     return false;
    2150                 :             : 
    2151                 :         390 :   lzma_vli num_records;
    2152                 :         390 :   size_t pos = 0;
    2153                 :         390 :   size_t in_pos = 1;
    2154                 :         390 :   while (true)
    2155                 :             :     {
    2156         [ -  + ]:         390 :       if (in_pos >= index_read)
    2157                 :             :         {
    2158         [ #  # ]:           0 :           ssize_t bytes_read = read (fd, index, sizeof (index));
    2159         [ #  # ]:           0 :           if (bytes_read < 0)
    2160                 :             :           {
    2161         [ #  # ]:           0 :             if (errno == EINTR)
    2162                 :           0 :               continue;
    2163                 :             :             return false;
    2164                 :             :           }
    2165         [ #  # ]:           0 :           if (bytes_read == 0)
    2166                 :             :             return false;
    2167                 :           0 :           index_read = bytes_read;
    2168                 :           0 :           in_pos = 0;
    2169                 :             :         }
    2170                 :         390 :       ret = lzma_vli_decode (&num_records, &pos, index, &in_pos, index_read);
    2171         [ -  + ]:         390 :       if (ret == LZMA_STREAM_END)
    2172                 :             :         break;
    2173         [ #  # ]:           0 :       else if (ret != LZMA_OK)
    2174                 :             :         return false;
    2175                 :             :     }
    2176                 :             : 
    2177         [ +  + ]:         390 :   if (verbose > 3)
    2178   [ +  -  +  -  :         676 :     obatched(clog) << rps << " has " << num_records << " xz Blocks" << endl;
          +  -  +  -  +  
                      - ]
    2179                 :             : 
    2180                 :             :   // The file is only seekable if it has more than one Block.
    2181                 :         390 :   return num_records > 1;
    2182                 :         390 : }
    2183                 :             : 
    2184                 :             : // Read the Index at the end of an xz file.
    2185                 :             : static lzma_index*
    2186                 :         202 : read_xz_index (int fd)
    2187                 :             : {
    2188                 :         202 :   off_t footer_pos = -LZMA_STREAM_HEADER_SIZE;
    2189         [ -  + ]:         202 :   if (lseek (fd, footer_pos, SEEK_END) == -1)
    2190   [ #  #  #  # ]:           0 :     throw libc_exception (errno, "lseek");
    2191                 :             : 
    2192                 :             :   uint8_t footer[LZMA_STREAM_HEADER_SIZE];
    2193                 :             :   size_t footer_read = 0;
    2194         [ +  + ]:         404 :   while (footer_read < sizeof (footer))
    2195                 :             :     {
    2196         [ -  + ]:         202 :       ssize_t bytes_read = read (fd, footer + footer_read,
    2197                 :             :                                  sizeof (footer) - footer_read);
    2198         [ -  + ]:         202 :       if (bytes_read < 0)
    2199                 :             :         {
    2200         [ #  # ]:           0 :           if (errno == EINTR)
    2201                 :           0 :             continue;
    2202   [ #  #  #  # ]:           0 :           throw libc_exception (errno, "read");
    2203                 :             :         }
    2204         [ -  + ]:         202 :       if (bytes_read == 0)
    2205         [ #  # ]:           0 :         throw reportable_exception ("truncated file");
    2206                 :         202 :       footer_read += bytes_read;
    2207                 :             :     }
    2208                 :             : 
    2209                 :         202 :   lzma_stream_flags stream_flags;
    2210                 :         202 :   lzma_ret ret = lzma_stream_footer_decode (&stream_flags, footer);
    2211         [ -  + ]:         202 :   if (ret != LZMA_OK)
    2212   [ #  #  #  # ]:           0 :     throw lzma_exception (ret, "lzma_stream_footer_decode");
    2213                 :             : 
    2214         [ -  + ]:         202 :   if (lseek (fd, footer_pos - stream_flags.backward_size, SEEK_END) == -1)
    2215   [ #  #  #  # ]:           0 :     throw libc_exception (errno, "lseek");
    2216                 :             : 
    2217                 :         202 :   lzma_stream strm = LZMA_STREAM_INIT;
    2218                 :         202 :   lzma_index* index = NULL;
    2219                 :         202 :   ret = lzma_index_decoder (&strm, &index, UINT64_MAX);
    2220         [ -  + ]:         202 :   if (ret != LZMA_OK)
    2221   [ #  #  #  # ]:           0 :     throw lzma_exception (ret, "lzma_index_decoder");
    2222                 :         202 :   defer_dtor<lzma_stream*,void> strm_ender (&strm, lzma_end);
    2223                 :             : 
    2224                 :         202 :   uint8_t in_buf[4096];
    2225                 :         202 :   while (true)
    2226                 :             :     {
    2227         [ +  - ]:         202 :       if (strm.avail_in == 0)
    2228                 :             :         {
    2229         [ +  - ]:         202 :           ssize_t bytes_read = read (fd, in_buf, sizeof (in_buf));
    2230         [ -  + ]:         202 :           if (bytes_read < 0)
    2231                 :             :             {
    2232         [ #  # ]:           0 :               if (errno == EINTR)
    2233                 :           0 :                 continue;
    2234   [ #  #  #  # ]:           0 :               throw libc_exception (errno, "read");
    2235                 :             :             }
    2236         [ -  + ]:         202 :           if (bytes_read == 0)
    2237         [ #  # ]:           0 :             throw reportable_exception ("truncated file");
    2238                 :         202 :           strm.avail_in = bytes_read;
    2239                 :         202 :           strm.next_in = in_buf;
    2240                 :             :         }
    2241                 :             : 
    2242                 :         202 :         ret = lzma_code (&strm, LZMA_RUN);
    2243         [ -  + ]:         202 :         if (ret == LZMA_STREAM_END)
    2244                 :             :           break;
    2245         [ #  # ]:           0 :         else if (ret != LZMA_OK)
    2246   [ #  #  #  # ]:           0 :           throw lzma_exception (ret, "lzma_code index");
    2247                 :             :     }
    2248                 :             : 
    2249                 :         202 :   ret = lzma_index_stream_flags (index, &stream_flags);
    2250         [ -  + ]:         202 :   if (ret != LZMA_OK)
    2251                 :             :     {
    2252                 :           0 :       lzma_index_end (index, NULL);
    2253   [ #  #  #  # ]:           0 :       throw lzma_exception (ret, "lzma_index_stream_flags");
    2254                 :             :     }
    2255                 :         202 :   return index;
    2256                 :         202 : }
    2257                 :             : 
    2258                 :             : static void
    2259                 :         202 : my_lzma_index_end (lzma_index* index)
    2260                 :             : {
    2261                 :         202 :   lzma_index_end (index, NULL);
    2262                 :         202 : }
    2263                 :             : 
    2264                 :             : static void
    2265                 :         210 : free_lzma_block_filter_options (lzma_block* block)
    2266                 :             : {
    2267         [ +  + ]:        1050 :   for (int i = 0; i < LZMA_FILTERS_MAX; i++)
    2268                 :             :     {
    2269                 :         840 :       free (block->filters[i].options);
    2270                 :         840 :       block->filters[i].options = NULL;
    2271                 :             :     }
    2272                 :         210 : }
    2273                 :             : 
    2274                 :             : static void
    2275                 :         202 : free_lzma_block_filters (lzma_block* block)
    2276                 :             : {
    2277         [ +  - ]:         202 :   if (block->filters != NULL)
    2278                 :             :     {
    2279                 :         202 :       free_lzma_block_filter_options (block);
    2280                 :         202 :       free (block->filters);
    2281                 :             :     }
    2282                 :         202 : }
    2283                 :             : 
    2284                 :             : static void
    2285                 :         202 : extract_xz_blocks_into_fd (const string& srcpath,
    2286                 :             :                            int src,
    2287                 :             :                            int dst,
    2288                 :             :                            lzma_index_iter* iter,
    2289                 :             :                            uint64_t offset,
    2290                 :             :                            uint64_t size)
    2291                 :             : {
    2292                 :             :   // Seek to the Block.  Seeking from the end using the compressed size from the
    2293                 :             :   // footer means we don't need to know where the xz stream starts in the
    2294                 :             :   // archive.
    2295         [ -  + ]:         202 :   if (lseek (src,
    2296                 :         202 :              (off_t) iter->block.compressed_stream_offset
    2297                 :         202 :              - (off_t) iter->stream.compressed_size,
    2298                 :             :              SEEK_END) == -1)
    2299   [ #  #  #  # ]:           0 :     throw libc_exception (errno, "lseek");
    2300                 :             : 
    2301                 :         202 :   offset -= iter->block.uncompressed_file_offset;
    2302                 :             : 
    2303                 :         202 :   lzma_block block{};
    2304                 :         202 :   block.filters = (lzma_filter*) calloc (LZMA_FILTERS_MAX + 1,
    2305                 :             :                                          sizeof (lzma_filter));
    2306         [ -  + ]:         202 :   if (block.filters == NULL)
    2307   [ #  #  #  # ]:           0 :     throw libc_exception (ENOMEM, "cannot allocate lzma_block filters");
    2308                 :         202 :   defer_dtor<lzma_block*,void> filters_freer (&block, free_lzma_block_filters);
    2309                 :             : 
    2310                 :         202 :   uint8_t in_buf[4096];
    2311                 :         202 :   uint8_t out_buf[4096];
    2312                 :         202 :   size_t header_read = 0;
    2313                 :         202 :   bool need_log_extracting = verbose > 3;
    2314                 :           8 :   while (true)
    2315                 :             :     {
    2316                 :             :       // The first byte of the Block is the encoded Block Header Size.  Read the
    2317                 :             :       // first byte and whatever extra fits in the buffer.
    2318         [ +  + ]:         412 :       while (header_read == 0)
    2319                 :             :         {
    2320         [ +  - ]:         202 :           ssize_t bytes_read = read (src, in_buf, sizeof (in_buf));
    2321         [ -  + ]:         202 :           if (bytes_read < 0)
    2322                 :             :             {
    2323         [ #  # ]:           0 :               if (errno == EINTR)
    2324                 :           0 :                 continue;
    2325   [ #  #  #  # ]:           0 :               throw libc_exception (errno, "read");
    2326                 :             :             }
    2327         [ -  + ]:         202 :           if (bytes_read == 0)
    2328         [ #  # ]:           0 :             throw reportable_exception ("truncated file");
    2329                 :         202 :           header_read += bytes_read;
    2330                 :             :         }
    2331                 :             : 
    2332                 :         210 :       block.header_size = lzma_block_header_size_decode (in_buf[0]);
    2333                 :             : 
    2334                 :             :       // If we didn't buffer the whole Block Header earlier, get the rest.
    2335                 :         210 :       eu_static_assert (sizeof (in_buf)
    2336                 :             :                         >= lzma_block_header_size_decode (UINT8_MAX));
    2337         [ -  + ]:         210 :       while (header_read < block.header_size)
    2338                 :             :         {
    2339         [ #  # ]:           0 :           ssize_t bytes_read = read (src, in_buf + header_read,
    2340                 :             :                                      sizeof (in_buf) - header_read);
    2341         [ #  # ]:           0 :           if (bytes_read < 0)
    2342                 :             :             {
    2343         [ #  # ]:           0 :               if (errno == EINTR)
    2344                 :           0 :                 continue;
    2345   [ #  #  #  # ]:           0 :               throw libc_exception (errno, "read");
    2346                 :             :             }
    2347         [ #  # ]:           0 :           if (bytes_read == 0)
    2348         [ #  # ]:           0 :             throw reportable_exception ("truncated file");
    2349                 :           0 :           header_read += bytes_read;
    2350                 :             :         }
    2351                 :             : 
    2352                 :             :       // Decode the Block Header.
    2353                 :         210 :       block.check = iter->stream.flags->check;
    2354                 :         210 :       lzma_ret ret = lzma_block_header_decode (&block, NULL, in_buf);
    2355         [ -  + ]:         210 :       if (ret != LZMA_OK)
    2356   [ #  #  #  # ]:           0 :         throw lzma_exception (ret, "lzma_block_header_decode");
    2357                 :         210 :       ret = lzma_block_compressed_size (&block, iter->block.unpadded_size);
    2358         [ -  + ]:         210 :       if (ret != LZMA_OK)
    2359   [ #  #  #  # ]:           0 :         throw lzma_exception (ret, "lzma_block_compressed_size");
    2360                 :             : 
    2361                 :             :       // Start decoding the Block data.
    2362                 :         210 :       lzma_stream strm = LZMA_STREAM_INIT;
    2363                 :         210 :       ret = lzma_block_decoder (&strm, &block);
    2364         [ -  + ]:         210 :       if (ret != LZMA_OK)
    2365   [ #  #  #  # ]:           0 :         throw lzma_exception (ret, "lzma_block_decoder");
    2366                 :         210 :       defer_dtor<lzma_stream*,void> strm_ender (&strm, lzma_end);
    2367                 :             : 
    2368                 :             :       // We might still have some input buffered from when we read the header.
    2369                 :         210 :       strm.avail_in = header_read - block.header_size;
    2370                 :         210 :       strm.next_in = in_buf + block.header_size;
    2371                 :         210 :       strm.avail_out = sizeof (out_buf);
    2372                 :         210 :       strm.next_out = out_buf;
    2373                 :       11734 :       while (true)
    2374                 :             :         {
    2375         [ +  + ]:       11734 :           if (strm.avail_in == 0)
    2376                 :             :             {
    2377         [ +  - ]:          14 :               ssize_t bytes_read = read (src, in_buf, sizeof (in_buf));
    2378         [ -  + ]:          14 :               if (bytes_read < 0)
    2379                 :             :                 {
    2380         [ #  # ]:           0 :                   if (errno == EINTR)
    2381                 :           0 :                     continue;
    2382   [ #  #  #  # ]:           0 :                   throw libc_exception (errno, "read");
    2383                 :             :                 }
    2384         [ -  + ]:          14 :               if (bytes_read == 0)
    2385         [ #  # ]:           0 :                 throw reportable_exception ("truncated file");
    2386                 :          14 :               strm.avail_in = bytes_read;
    2387                 :          14 :               strm.next_in = in_buf;
    2388                 :             :             }
    2389                 :             : 
    2390                 :       11734 :           ret = lzma_code (&strm, LZMA_RUN);
    2391         [ -  + ]:       11734 :           if (ret != LZMA_OK && ret != LZMA_STREAM_END)
    2392   [ #  #  #  # ]:           0 :             throw lzma_exception (ret, "lzma_code block");
    2393                 :             : 
    2394                 :             :           // Throw away anything we decode until we reach the offset, then
    2395                 :             :           // start writing to the destination.
    2396         [ +  + ]:       11734 :           if (strm.total_out > offset)
    2397                 :             :             {
    2398                 :        3006 :               size_t bytes_to_write = strm.next_out - out_buf;
    2399                 :        3006 :               uint8_t* buf_to_write = out_buf;
    2400                 :             : 
    2401                 :             :               // Ignore anything in the buffer before the offset.
    2402         [ +  + ]:        3006 :               if (bytes_to_write > strm.total_out - offset)
    2403                 :             :                 {
    2404                 :         196 :                   buf_to_write += bytes_to_write - (strm.total_out - offset);
    2405                 :         196 :                   bytes_to_write = strm.total_out - offset;
    2406                 :             :                 }
    2407                 :             : 
    2408                 :             :               // Ignore anything after the size.
    2409         [ +  + ]:        3006 :               if (strm.total_out - offset >= size)
    2410                 :         202 :                 bytes_to_write -= strm.total_out - offset - size;
    2411                 :             : 
    2412         [ +  + ]:        3006 :               if (need_log_extracting)
    2413                 :             :                 {
    2414   [ +  -  +  - ]:         240 :                   obatched(clog) << "extracting from xz archive " << srcpath
    2415   [ +  -  +  -  :         120 :                                  << " size=" << size << endl;
                   +  - ]
    2416                 :         120 :                   need_log_extracting = false;
    2417                 :             :                 }
    2418                 :             : 
    2419         [ +  + ]:        6012 :               while (bytes_to_write > 0)
    2420                 :             :                 {
    2421         [ +  - ]:        3006 :                   ssize_t written = write (dst, buf_to_write, bytes_to_write);
    2422         [ -  + ]:        3006 :                   if (written < 0)
    2423                 :             :                     {
    2424         [ #  # ]:           0 :                       if (errno == EAGAIN)
    2425                 :           0 :                         continue;
    2426   [ #  #  #  # ]:           0 :                       throw libc_exception (errno, "write");
    2427                 :             :                     }
    2428                 :        3006 :                   bytes_to_write -= written;
    2429                 :        3006 :                   buf_to_write += written;
    2430                 :             :                 }
    2431                 :             : 
    2432                 :             :               // If we reached the size, we're done.
    2433         [ +  + ]:        3006 :               if (strm.total_out - offset >= size)
    2434                 :         202 :                 return;
    2435                 :             :             }
    2436                 :             : 
    2437                 :       11532 :           strm.avail_out = sizeof (out_buf);
    2438                 :       11532 :           strm.next_out = out_buf;
    2439                 :             : 
    2440         [ +  + ]:       11532 :           if (ret == LZMA_STREAM_END)
    2441                 :             :             break;
    2442                 :             :         }
    2443                 :             : 
    2444                 :             :       // This Block didn't have enough data.  Go to the next one.
    2445         [ -  + ]:           8 :       if (lzma_index_iter_next (iter, LZMA_INDEX_ITER_BLOCK))
    2446         [ #  # ]:           0 :         throw reportable_exception ("no more blocks");
    2447         [ +  - ]:           8 :       if (strm.total_out > offset)
    2448                 :           8 :         size -= strm.total_out - offset;
    2449                 :           8 :       offset = 0;
    2450                 :             :       // If we had any buffered input left, move it to the beginning of the
    2451                 :             :       // buffer to decode the next Block Header.
    2452         [ +  - ]:           8 :       if (strm.avail_in > 0)
    2453                 :             :         {
    2454                 :           8 :           memmove (in_buf, strm.next_in, strm.avail_in);
    2455                 :           8 :           header_read = strm.avail_in;
    2456                 :             :         }
    2457                 :             :       else
    2458                 :             :         header_read = 0;
    2459                 :           8 :       free_lzma_block_filter_options (&block);
    2460                 :         210 :     }
    2461                 :         202 : }
    2462                 :             : 
    2463                 :             : static int
    2464                 :         202 : extract_from_seekable_archive (const string& srcpath,
    2465                 :             :                                char* tmppath,
    2466                 :             :                                uint64_t offset,
    2467                 :             :                                uint64_t size)
    2468                 :             : {
    2469   [ +  -  +  -  :         404 :   inc_metric ("seekable_archive_extraction_attempts","type","xz");
                   +  - ]
    2470                 :         202 :   try
    2471                 :             :     {
    2472         [ +  - ]:         202 :       int src = open (srcpath.c_str(), O_RDONLY);
    2473         [ -  + ]:         202 :       if (src < 0)
    2474   [ #  #  #  #  :           0 :         throw libc_exception (errno, string("open ") + srcpath);
                   #  # ]
    2475                 :         202 :       defer_dtor<int,int> src_closer (src, close);
    2476                 :             : 
    2477         [ +  - ]:         202 :       lzma_index* index = read_xz_index (src);
    2478                 :         202 :       defer_dtor<lzma_index*,void> index_ender (index, my_lzma_index_end);
    2479                 :             : 
    2480                 :             :       // Find the Block containing the offset.
    2481                 :         202 :       lzma_index_iter iter;
    2482                 :         202 :       lzma_index_iter_init (&iter, index);
    2483         [ -  + ]:         202 :       if (lzma_index_iter_locate (&iter, offset))
    2484         [ #  # ]:           0 :         throw reportable_exception ("offset not found");
    2485                 :             : 
    2486         [ +  + ]:         202 :       if (verbose > 3)
    2487   [ +  -  +  - ]:         360 :         obatched(clog) << "seeking in xz archive " << srcpath
    2488   [ +  -  +  -  :         120 :                        << " offset=" << offset << " block_offset="
                   +  - ]
    2489   [ +  -  +  - ]:         120 :                        << iter.block.uncompressed_file_offset << endl;
    2490                 :             : 
    2491         [ +  - ]:         202 :       int dst = mkstemp (tmppath);
    2492         [ -  + ]:         202 :       if (dst < 0)
    2493   [ #  #  #  # ]:           0 :         throw libc_exception (errno, "cannot create temporary file");
    2494                 :             : 
    2495                 :         202 :       try
    2496                 :             :         {
    2497         [ +  - ]:         202 :           extract_xz_blocks_into_fd (srcpath, src, dst, &iter, offset, size);
    2498                 :             :         }
    2499                 :           0 :       catch (...)
    2500                 :             :         {
    2501                 :           0 :           unlink (tmppath);
    2502         [ -  - ]:           0 :           close (dst);
    2503                 :           0 :           throw;
    2504                 :           0 :         }
    2505                 :             : 
    2506   [ +  -  +  -  :         404 :       inc_metric ("seekable_archive_extraction_successes","type","xz");
             +  -  +  - ]
    2507                 :         202 :       return dst;
    2508                 :         202 :     }
    2509         [ -  - ]:           0 :   catch (const reportable_exception &e)
    2510                 :             :     {
    2511   [ -  -  -  -  :           0 :       inc_metric ("seekable_archive_extraction_failures","type","xz");
             -  -  -  - ]
    2512         [ -  - ]:           0 :       if (verbose)
    2513   [ -  -  -  - ]:           0 :         obatched(clog) << "failed to extract from seekable xz archive "
    2514   [ -  -  -  -  :           0 :                        << srcpath << ": " << e.message << endl;
                   -  - ]
    2515                 :           0 :       return -1;
    2516                 :           0 :     }
    2517                 :             : }
    2518                 :             : #else
    2519                 :             : static bool
    2520                 :             : is_seekable_archive (const string& rps __attribute__ ((unused)),
    2521                 :             :                      struct archive* a __attribute__ ((unused)))
    2522                 :             : {
    2523                 :             :   return false;
    2524                 :             : }
    2525                 :             : static int
    2526                 :             : extract_from_seekable_archive (const string& srcpath __attribute__ ((unused)),
    2527                 :             :                                char* tmppath __attribute__ ((unused)),
    2528                 :             :                                uint64_t offset __attribute__ ((unused)),
    2529                 :             :                                uint64_t size __attribute__ ((unused)))
    2530                 :             : {
    2531                 :             :   return -1;
    2532                 :             : }
    2533                 :             : #endif
    2534                 :             : 
    2535                 :             : 
    2536                 :             : // For security/portability reasons, many distro-package archives have
    2537                 :             : // a "./" in front of path names; others have nothing, others have
    2538                 :             : // "/".  Canonicalize them all to a single leading "/", with the
    2539                 :             : // assumption that this matches the dwarf-derived file names too.
    2540                 :        3966 : string canonicalized_archive_entry_pathname(struct archive_entry *e)
    2541                 :             : {
    2542                 :        3966 :   string fn = archive_entry_pathname(e);
    2543         [ -  + ]:        3966 :   if (fn.size() == 0)
    2544                 :           0 :     return fn;
    2545         [ -  + ]:        3966 :   if (fn[0] == '/')
    2546                 :           0 :     return fn;
    2547         [ +  + ]:        3966 :   if (fn[0] == '.')
    2548         [ +  - ]:        2726 :     return fn.substr(1);
    2549                 :             :   else
    2550   [ +  -  +  - ]:        2480 :     return string("/")+fn;
    2551                 :        3966 : }
    2552                 :             : 
    2553                 :             : 
    2554                 :             : // NB: takes ownership of, and may reassign, fd.
    2555                 :             : static struct MHD_Response*
    2556                 :        1000 : create_buildid_r_response (int64_t b_mtime0,
    2557                 :             :                            const string& b_source0,
    2558                 :             :                            const string& b_source1,
    2559                 :             :                            const string& section,
    2560                 :             :                            const string& ima_sig,
    2561                 :             :                            const char* tmppath,
    2562                 :             :                            int& fd,
    2563                 :             :                            off_t size,
    2564                 :             :                            time_t mtime,
    2565                 :             :                            const string& metric,
    2566                 :             :                            const struct timespec& extract_begin)
    2567                 :             : {
    2568         [ +  + ]:        1000 :   if (tmppath != NULL)
    2569                 :             :     {
    2570                 :         900 :       struct timespec extract_end;
    2571                 :         900 :       clock_gettime (CLOCK_MONOTONIC, &extract_end);
    2572                 :         900 :       double extract_time = (extract_end.tv_sec - extract_begin.tv_sec)
    2573                 :         900 :         + (extract_end.tv_nsec - extract_begin.tv_nsec)/1.e9;
    2574         [ +  - ]:        1800 :       fdcache.intern(b_source0, b_source1, tmppath, size, true, extract_time);
    2575                 :             :     }
    2576                 :             : 
    2577         [ +  + ]:        1000 :   if (!section.empty ())
    2578                 :             :     {
    2579         [ +  - ]:           6 :       int scn_fd = extract_section (fd, b_mtime0,
    2580         [ +  - ]:          12 :                                     b_source0 + ":" + b_source1,
    2581                 :             :                                     section, extract_begin);
    2582                 :           6 :       close (fd);
    2583         [ +  + ]:           6 :       if (scn_fd >= 0)
    2584                 :           4 :         fd = scn_fd;
    2585                 :             :       else
    2586                 :             :         {
    2587         [ +  - ]:           2 :           if (verbose)
    2588         [ +  - ]:           6 :             obatched (clog) << "cannot find section " << section
    2589                 :             :                             << " for archive " << b_source0
    2590   [ +  -  +  -  :           2 :                             << " file " << b_source1 << endl;
          +  -  +  -  +  
                      - ]
    2591                 :           2 :           return 0;
    2592                 :             :         }
    2593                 :             : 
    2594                 :           4 :       struct stat fs;
    2595         [ -  + ]:           4 :       if (fstat (fd, &fs) < 0)
    2596                 :             :         {
    2597                 :           0 :           close (fd);
    2598                 :           0 :           throw libc_exception (errno,
    2599   [ #  #  #  #  :           0 :             string ("fstat ") + b_source0 + string (" ") + section);
          #  #  #  #  #  
                #  #  # ]
    2600                 :             :         }
    2601                 :           4 :       size = fs.st_size;
    2602                 :             :     }
    2603                 :             : 
    2604                 :         998 :   struct MHD_Response* r = MHD_create_response_from_fd (size, fd);
    2605         [ -  + ]:         998 :   if (r == 0)
    2606                 :             :     {
    2607         [ #  # ]:           0 :       if (verbose)
    2608   [ #  #  #  # ]:           0 :         obatched(clog) << "cannot create fd-response for " << b_source0 << endl;
    2609                 :           0 :       close(fd);
    2610                 :             :     }
    2611                 :             :   else
    2612                 :             :     {
    2613   [ +  -  +  - ]:        1996 :       inc_metric ("http_responses_total","result",metric);
    2614                 :         998 :       add_mhd_response_header (r, "Content-Type", "application/octet-stream");
    2615         [ +  - ]:         998 :       add_mhd_response_header (r, "X-DEBUGINFOD-SIZE", to_string(size).c_str());
    2616                 :         998 :       add_mhd_response_header (r, "X-DEBUGINFOD-ARCHIVE", b_source0.c_str());
    2617                 :         998 :       add_mhd_response_header (r, "X-DEBUGINFOD-FILE", b_source1.c_str());
    2618         [ -  + ]:         998 :       if(!ima_sig.empty()) add_mhd_response_header(r, "X-DEBUGINFOD-IMASIGNATURE", ima_sig.c_str());
    2619                 :         998 :       add_mhd_last_modified (r, mtime);
    2620         [ -  + ]:         998 :       if (verbose > 1)
    2621         [ +  - ]:        2994 :         obatched(clog) << "serving " << metric << " " << b_source0
    2622                 :             :                        << " file " << b_source1
    2623                 :             :                        << " section=" << section
    2624   [ +  -  +  -  :         998 :                        << " IMA signature=" << ima_sig << endl;
          +  -  +  -  +  
          -  +  -  +  -  
             +  -  +  - ]
    2625                 :             :       /* libmicrohttpd will close fd. */
    2626                 :             :     }
    2627                 :             :   return r;
    2628                 :             : }
    2629                 :             : 
    2630                 :             : static struct MHD_Response*
    2631                 :        1058 : handle_buildid_r_match (bool internal_req_p,
    2632                 :             :                         int64_t b_mtime,
    2633                 :             :                         const string& b_source0,
    2634                 :             :                         const string& b_source1,
    2635                 :             :                         int64_t b_id0,
    2636                 :             :                         int64_t b_id1,
    2637                 :             :                         const string& section,
    2638                 :             :                         int *result_fd)
    2639                 :             : {
    2640                 :        1058 :   struct timespec extract_begin;
    2641                 :        1058 :   clock_gettime (CLOCK_MONOTONIC, &extract_begin);
    2642                 :             : 
    2643                 :        1058 :   struct stat fs;
    2644                 :        1058 :   int rc = stat (b_source0.c_str(), &fs);
    2645         [ +  + ]:        1058 :   if (rc != 0)
    2646   [ +  -  +  -  :         116 :     throw libc_exception (errno, string("stat ") + b_source0);
                   +  - ]
    2647                 :             : 
    2648         [ -  + ]:        1000 :   if ((int64_t) fs.st_mtime != b_mtime)
    2649                 :             :     {
    2650         [ #  # ]:           0 :       if (verbose)
    2651   [ #  #  #  # ]:           0 :         obatched(clog) << "mtime mismatch for " << b_source0 << endl;
    2652                 :           0 :       return 0;
    2653                 :             :     }
    2654                 :             : 
    2655                 :             :   // Extract the IMA per-file signature (if it exists)
    2656                 :        1000 :   string ima_sig = "";
    2657                 :             :   #ifdef ENABLE_IMA_VERIFICATION
    2658                 :             :   do
    2659                 :             :     {
    2660                 :             :       FD_t rpm_fd;
    2661                 :             :       if(!(rpm_fd = Fopen(b_source0.c_str(), "r.ufdio"))) // read, uncompressed, rpm/rpmio.h
    2662                 :             :         {
    2663                 :             :           if (verbose) obatched(clog) << "There was an error while opening " << b_source0 << endl;
    2664                 :             :           break; // Exit IMA extraction
    2665                 :             :         }
    2666                 :             : 
    2667                 :             :       Header rpm_hdr;
    2668                 :             :       if(RPMRC_FAIL == rpmReadPackageFile(NULL, rpm_fd, b_source0.c_str(), &rpm_hdr))
    2669                 :             :         {
    2670                 :             :           if (verbose) obatched(clog) << "There was an error while reading the header of " << b_source0 << endl;
    2671                 :             :           Fclose(rpm_fd);
    2672                 :             :           break; // Exit IMA extraction
    2673                 :             :         }
    2674                 :             : 
    2675                 :             :       // Fill sig_tag_data with an alloc'd copy of the array of IMA signatures (if they exist)
    2676                 :             :       struct rpmtd_s sig_tag_data;
    2677                 :             :       rpmtdReset(&sig_tag_data);
    2678                 :             :       do{ /* A do-while so we can break out of the koji sigcache checking on failure */
    2679                 :             :         if(requires_koji_sigcache_mapping)
    2680                 :             :           {
    2681                 :             :             /* NB: Koji builds result in a directory structure like the following
    2682                 :             :                - PACKAGE/VERSION/RELEASE
    2683                 :             :                - ARCH1
    2684                 :             :                - foo.rpm           // The rpm known by debuginfod
    2685                 :             :                - ...
    2686                 :             :                - ARCHN
    2687                 :             :                - data
    2688                 :             :                - signed            // Periodically purged (and not scanned by debuginfod)
    2689                 :             :                - sigcache
    2690                 :             :                - ARCH1
    2691                 :             :                - foo.rpm.sig   // An empty rpm header
    2692                 :             :                - ...
    2693                 :             :                - ARCHN
    2694                 :             :                - PACKAGE_KEYID1
    2695                 :             :                - ARCH1
    2696                 :             :                - foo.rpm.sig   // The header of the signed rpm. This is the file we need to extract the IMA signatures
    2697                 :             :                - ...
    2698                 :             :                - ARCHN
    2699                 :             :                - ...
    2700                 :             :                - PACKAGE_KEYIDn
    2701                 :             :             
    2702                 :             :                We therefore need to do a mapping:
    2703                 :             :       
    2704                 :             :                P/V/R/A/N-V-R.A.rpm ->
    2705                 :             :                P/V/R/data/sigcache/KEYID/A/N-V-R.A.rpm.sig
    2706                 :             : 
    2707                 :             :                There are 2 key insights here         
    2708                 :             :       
    2709                 :             :                1. We need to go 2 directories down from sigcache to get to the
    2710                 :             :                rpm header. So to distinguish ARCH1/foo.rpm.sig and
    2711                 :             :                PACKAGE_KEYID1/ARCH1/foo.rpm.sig we can look 2 directories down
    2712                 :             :       
    2713                 :             :                2. It's safe to assume that the user will have all of the
    2714                 :             :                required verification certs. So we can pick from any of the
    2715                 :             :                PACKAGE_KEYID* directories.  For simplicity we choose first we
    2716                 :             :                match against
    2717                 :             :       
    2718                 :             :                See: https://pagure.io/koji/issue/3670
    2719                 :             :             */
    2720                 :             : 
    2721                 :             :             // Do the mapping from b_source0 to the koji path for the signed rpm header
    2722                 :             :             string signed_rpm_path = b_source0;
    2723                 :             :             size_t insert_pos = string::npos;
    2724                 :             :             for(int i = 0; i < 2; i++) insert_pos = signed_rpm_path.rfind("/", insert_pos) - 1;
    2725                 :             :             string globbed_path  = signed_rpm_path.insert(insert_pos + 1, "/data/sigcache/*").append(".sig"); // The globbed path we're seeking
    2726                 :             :             glob_t pglob;
    2727                 :             :             int grc;
    2728                 :             :             if(0 != (grc = glob(globbed_path.c_str(), GLOB_NOSORT, NULL, &pglob)))
    2729                 :             :               {
    2730                 :             :                 // Break out, but only report real errors
    2731                 :             :                 if (verbose && grc != GLOB_NOMATCH) obatched(clog) << "There was an error (" << strerror(errno) << ") globbing " << globbed_path << endl;
    2732                 :             :                 break; // Exit koji sigcache check
    2733                 :             :               }
    2734                 :             :             signed_rpm_path = pglob.gl_pathv[0]; // See insight 2 above
    2735                 :             :             globfree(&pglob);
    2736                 :             : 
    2737                 :             :             if (verbose > 2) obatched(clog) << "attempting IMA signature extraction from koji header " << signed_rpm_path << endl;
    2738                 :             : 
    2739                 :             :             FD_t sig_rpm_fd;
    2740                 :             :             if(NULL == (sig_rpm_fd = Fopen(signed_rpm_path.c_str(), "r")))
    2741                 :             :               {
    2742                 :             :                 if (verbose) obatched(clog) << "There was an error while opening " << signed_rpm_path << endl;
    2743                 :             :                 break; // Exit koji sigcache check
    2744                 :             :               }
    2745                 :             : 
    2746                 :             :             Header sig_hdr = headerRead(sig_rpm_fd, HEADER_MAGIC_YES /* Validate magic too */ );
    2747                 :             :             if (!sig_hdr || 1 != headerGet(sig_hdr, RPMSIGTAG_FILESIGNATURES, &sig_tag_data, HEADERGET_ALLOC))
    2748                 :             :               {
    2749                 :             :                 if (verbose) obatched(clog) << "Unable to extract RPMSIGTAG_FILESIGNATURES from " << signed_rpm_path << endl;
    2750                 :             :               }
    2751                 :             :             headerFree(sig_hdr); // We can free here since sig_tag_data has an alloc'd copy of the data
    2752                 :             :             Fclose(sig_rpm_fd);
    2753                 :             :           }
    2754                 :             :       }while(false);
    2755                 :             : 
    2756                 :             :       if(0 == sig_tag_data.count)
    2757                 :             :         {
    2758                 :             :           // In the general case (or a fallback from the koji sigcache mapping not finding signatures)
    2759                 :             :           // we can just (try) extract the signatures from the rpm header
    2760                 :             :           if (1 != headerGet(rpm_hdr, RPMTAG_FILESIGNATURES, &sig_tag_data, HEADERGET_ALLOC))
    2761                 :             :             {
    2762                 :             :               if (verbose) obatched(clog) << "Unable to extract RPMTAG_FILESIGNATURES from " << b_source0 << endl;
    2763                 :             :             }
    2764                 :             :         }
    2765                 :             :       // Search the array for the signature coresponding to b_source1
    2766                 :             :       int idx = -1;
    2767                 :             :       char *sig = NULL;
    2768                 :             :       rpmfi hdr_fi = rpmfiNew(NULL, rpm_hdr, RPMTAG_BASENAMES, RPMFI_FLAGS_QUERY);
    2769                 :             :       do
    2770                 :             :         {
    2771                 :             :           sig = (char*)rpmtdNextString(&sig_tag_data);
    2772                 :             :           idx = rpmfiNext(hdr_fi);
    2773                 :             :         }
    2774                 :             :       while (idx != -1 && 0 != strcmp(b_source1.c_str(), rpmfiFN(hdr_fi)));
    2775                 :             :       rpmfiFree(hdr_fi);
    2776                 :             : 
    2777                 :             :       if(sig && 0 != strlen(sig) && idx != -1)
    2778                 :             :         {
    2779                 :             :           if (verbose > 2) obatched(clog) << "Found IMA signature for " << b_source1 << ":\n" << sig << endl;
    2780                 :             :           ima_sig = sig;
    2781                 :             :           inc_metric("http_responses_total","extra","ima-sigs-extracted");
    2782                 :             :         }
    2783                 :             :       else
    2784                 :             :         {
    2785                 :             :           if (verbose > 2) obatched(clog) << "Could not find IMA signature for " << b_source1 << endl;
    2786                 :             :         }
    2787                 :             : 
    2788                 :             :       rpmtdFreeData (&sig_tag_data);
    2789                 :             :       headerFree(rpm_hdr);
    2790                 :             :       Fclose(rpm_fd);
    2791                 :             :     } while(false);
    2792                 :             :   #endif
    2793                 :             : 
    2794                 :             :   // check for a match in the fdcache first
    2795         [ +  - ]:        1000 :   int fd = fdcache.lookup(b_source0, b_source1);
    2796         [ +  + ]:        1000 :   while (fd >= 0) // got one!; NB: this is really an if() with a possible branch out to the end
    2797                 :             :     {
    2798                 :         100 :       rc = fstat(fd, &fs);
    2799         [ -  + ]:         100 :       if (rc < 0) // disappeared?
    2800                 :             :         {
    2801         [ #  # ]:           0 :           if (verbose)
    2802   [ #  #  #  #  :           0 :             obatched(clog) << "cannot fstat fdcache " << b_source0 << endl;
                   #  # ]
    2803         [ #  # ]:           0 :           close(fd);
    2804         [ #  # ]:           0 :           fdcache.clear(b_source0, b_source1);
    2805                 :             :           break; // branch out of if "loop", to try new libarchive fetch attempt
    2806                 :             :         }
    2807                 :             : 
    2808   [ +  -  +  - ]:         100 :       struct MHD_Response* r = create_buildid_r_response (b_mtime, b_source0,
    2809                 :             :                                                           b_source1, section,
    2810                 :             :                                                           ima_sig, NULL, fd,
    2811                 :             :                                                           fs.st_size,
    2812                 :             :                                                           fs.st_mtime,
    2813                 :             :                                                           "archive fdcache",
    2814                 :             :                                                           extract_begin);
    2815         [ +  - ]:         100 :       if (r == 0)
    2816                 :             :         break; // branch out of if "loop", to try new libarchive fetch attempt
    2817         [ +  - ]:         100 :       if (result_fd)
    2818                 :         100 :         *result_fd = fd;
    2819                 :             :       return r;
    2820                 :             :       // NB: see, we never go around the 'loop' more than once
    2821                 :             :     }
    2822                 :             : 
    2823                 :             :   // no match ... look for a seekable entry
    2824                 :         900 :   bool populate_seekable = ! passive_p;
    2825                 :         900 :   unique_ptr<sqlite_ps> pp (new sqlite_ps (internal_req_p ? db : dbq,
    2826                 :             :                                            "rpm-seekable-query",
    2827                 :             :                                            "select type, size, offset, mtime from " BUILDIDS "_r_seekable "
    2828   [ +  -  +  -  :         900 :                                            "where file = ? and content = ?"));
          +  -  +  +  +  
                      - ]
    2829   [ +  -  +  -  :         900 :   rc = pp->reset().bind(1, b_id0).bind(2, b_id1).step();
             +  -  +  - ]
    2830         [ +  + ]:         900 :   if (rc != SQLITE_DONE)
    2831                 :             :     {
    2832         [ -  + ]:         202 :       if (rc != SQLITE_ROW)
    2833   [ #  #  #  # ]:           0 :         throw sqlite_exception(rc, "step");
    2834                 :             :       // if we found a match in _r_seekable but we fail to extract it, don't
    2835                 :             :       // bother populating it again
    2836                 :         202 :       populate_seekable = false;
    2837         [ +  - ]:         202 :       const char* seekable_type = (const char*) sqlite3_column_text (*pp, 0);
    2838   [ +  -  -  + ]:         202 :       if (seekable_type != NULL && strcmp (seekable_type, "xz") == 0)
    2839                 :             :         {
    2840         [ +  - ]:         202 :           int64_t seekable_size = sqlite3_column_int64 (*pp, 1);
    2841         [ +  - ]:         202 :           int64_t seekable_offset = sqlite3_column_int64 (*pp, 2);
    2842         [ +  - ]:         202 :           int64_t seekable_mtime = sqlite3_column_int64 (*pp, 3);
    2843                 :             : 
    2844                 :         202 :           char* tmppath = NULL;
    2845         [ -  + ]:         202 :           if (asprintf (&tmppath, "%s/debuginfod-fdcache.XXXXXX", tmpdir.c_str()) < 0)
    2846   [ #  #  #  # ]:           0 :             throw libc_exception (ENOMEM, "cannot allocate tmppath");
    2847                 :         202 :           defer_dtor<void*,void> tmmpath_freer (tmppath, free);
    2848                 :             : 
    2849         [ +  - ]:         202 :           fd = extract_from_seekable_archive (b_source0, tmppath,
    2850                 :             :                                               seekable_offset, seekable_size);
    2851         [ +  - ]:         202 :           if (fd >= 0)
    2852                 :             :             {
    2853                 :             :               // Set the mtime so the fdcache file mtimes propagate to future webapi
    2854                 :             :               // clients.
    2855                 :         202 :               struct timespec tvs[2];
    2856                 :         202 :               tvs[0].tv_sec = 0;
    2857                 :         202 :               tvs[0].tv_nsec = UTIME_OMIT;
    2858                 :         202 :               tvs[1].tv_sec = seekable_mtime;
    2859                 :         202 :               tvs[1].tv_nsec = 0;
    2860                 :         202 :               (void) futimens (fd, tvs);  /* best effort */
    2861   [ +  -  +  - ]:         202 :               struct MHD_Response* r = create_buildid_r_response (b_mtime,
    2862                 :             :                                                                   b_source0,
    2863                 :             :                                                                   b_source1,
    2864                 :             :                                                                   section,
    2865                 :             :                                                                   ima_sig,
    2866                 :             :                                                                   tmppath, fd,
    2867                 :             :                                                                   seekable_size,
    2868                 :             :                                                                   seekable_mtime,
    2869                 :             :                                                                   "seekable xz archive",
    2870                 :             :                                                                   extract_begin);
    2871         [ +  - ]:         202 :               if (r != 0 && result_fd)
    2872                 :         202 :                 *result_fd = fd;
    2873                 :         202 :               return r;
    2874                 :             :             }
    2875                 :         202 :         }
    2876                 :             :     }
    2877                 :         698 :   pp.reset();
    2878                 :             : 
    2879                 :             :   // still no match ... grumble, must process the archive
    2880         [ +  - ]:         698 :   string archive_decoder = "/dev/null";
    2881         [ +  - ]:         698 :   string archive_extension = "";
    2882         [ +  + ]:        1942 :   for (auto&& arch : scan_archives)
    2883         [ +  + ]:        1244 :     if (string_endswith(b_source0, arch.first))
    2884                 :             :       {
    2885         [ +  - ]:         698 :         archive_extension = arch.first;
    2886         [ +  - ]:        1942 :         archive_decoder = arch.second;
    2887                 :             :       }
    2888                 :         698 :   FILE* fp;
    2889                 :             :   
    2890                 :         698 :   defer_dtor<FILE*,int>::dtor_fn dfn;
    2891         [ +  + ]:         698 :   if (archive_decoder != "cat")
    2892                 :             :     {
    2893   [ +  -  +  -  :        1056 :       string popen_cmd = archive_decoder + " " + shell_escape(b_source0);
                   +  - ]
    2894         [ +  - ]:         528 :       fp = popen (popen_cmd.c_str(), "r"); // "e" O_CLOEXEC?
    2895                 :         528 :       dfn = pclose;
    2896         [ -  + ]:         528 :       if (fp == NULL)
    2897   [ #  #  #  #  :           0 :         throw libc_exception (errno, string("popen ") + popen_cmd);
                   #  # ]
    2898                 :         528 :     }
    2899                 :             :   else
    2900                 :             :     {
    2901         [ +  - ]:         170 :       fp = fopen (b_source0.c_str(), "r");
    2902                 :         170 :       dfn = fclose;
    2903         [ -  + ]:         170 :       if (fp == NULL)
    2904   [ #  #  #  #  :           0 :         throw libc_exception (errno, string("fopen ") + b_source0);
                   #  # ]
    2905                 :             :     }
    2906                 :         698 :   defer_dtor<FILE*,int> fp_closer (fp, dfn);
    2907                 :             : 
    2908                 :         698 :   struct archive *a;
    2909         [ +  - ]:         698 :   a = archive_read_new();
    2910         [ -  + ]:         698 :   if (a == NULL)
    2911   [ #  #  #  # ]:           0 :     throw archive_exception("cannot create archive reader");
    2912                 :         698 :   defer_dtor<struct archive*,int> archive_closer (a, archive_read_free);
    2913                 :             : 
    2914         [ +  - ]:         698 :   rc = archive_read_support_format_all(a);
    2915         [ -  + ]:         698 :   if (rc != ARCHIVE_OK)
    2916   [ #  #  #  # ]:           0 :     throw archive_exception(a, "cannot select all format");
    2917         [ +  - ]:         698 :   rc = archive_read_support_filter_all(a);
    2918         [ -  + ]:         698 :   if (rc != ARCHIVE_OK)
    2919   [ #  #  #  # ]:           0 :     throw archive_exception(a, "cannot select all filters");
    2920                 :             : 
    2921         [ +  - ]:         698 :   rc = archive_read_open_FILE (a, fp);
    2922         [ -  + ]:         698 :   if (rc != ARCHIVE_OK)
    2923                 :             :     {
    2924   [ #  #  #  #  :           0 :       obatched(clog) << "cannot open archive from pipe " << b_source0 << endl;
                   #  # ]
    2925   [ #  #  #  # ]:           0 :       throw archive_exception(a, "cannot open archive from pipe");
    2926                 :             :     }
    2927                 :             : 
    2928                 :             :   // If the archive was scanned in a version without _r_seekable, then we may
    2929                 :             :   // need to populate _r_seekable now.  This can be removed the next time
    2930                 :             :   // BUILDIDS is updated.
    2931         [ +  + ]:         698 :   if (populate_seekable)
    2932                 :             :     {
    2933         [ +  - ]:         696 :       populate_seekable = is_seekable_archive (b_source0, a);
    2934         [ +  - ]:         696 :       if (populate_seekable)
    2935                 :             :         {
    2936                 :             :           // NB: the names are already interned
    2937                 :           0 :           pp.reset(new sqlite_ps (db, "rpm-seekable-insert2",
    2938                 :             :                                   "insert or ignore into " BUILDIDS "_r_seekable (file, content, type, size, offset, mtime) "
    2939                 :             :                                   "values (?, "
    2940                 :             :                                   "(select id from " BUILDIDS "_files "
    2941                 :             :                                   "where dirname = (select id from " BUILDIDS "_fileparts where name = ?) "
    2942                 :             :                                   "and basename = (select id from " BUILDIDS "_fileparts where name = ?) "
    2943   [ #  #  #  #  :           0 :                                   "), 'xz', ?, ?, ?)"));
             #  #  #  # ]
    2944                 :             :         }
    2945                 :             :     }
    2946                 :             : 
    2947                 :             :   // archive traversal is in five stages:
    2948                 :             :   // 1) before we find a matching entry, insert it into _r_seekable if needed or
    2949                 :             :   //    skip it otherwise
    2950                 :             :   // 2) extract the matching entry (set r = result).  Also insert it into
    2951                 :             :   //    _r_seekable if needed
    2952                 :             :   // 3) extract some number of prefetched entries (just into fdcache).  Also
    2953                 :             :   //    insert them into _r_seekable if needed
    2954                 :             :   // 4) if needed, insert all of the remaining entries into _r_seekable
    2955                 :             :   // 5) abort any further processing
    2956                 :         698 :   struct MHD_Response* r = 0;                 // will set in stage 2
    2957         [ +  + ]:         698 :   unsigned prefetch_count =
    2958                 :             :     internal_req_p ? 0 : fdcache_prefetch;    // will decrement in stage 3
    2959                 :             : 
    2960   [ +  +  -  + ]:       10822 :   while(r == 0 || prefetch_count > 0 || populate_seekable) // stage 1-4
    2961                 :             :     {
    2962         [ +  - ]:       10782 :       if (interrupted)
    2963                 :             :         break;
    2964                 :             : 
    2965                 :       10782 :       struct archive_entry *e;
    2966         [ +  - ]:       10782 :       rc = archive_read_next_header (a, &e);
    2967         [ +  + ]:       10782 :       if (rc != ARCHIVE_OK)
    2968                 :             :         break;
    2969                 :             : 
    2970   [ +  -  +  + ]:       10126 :       if (! S_ISREG(archive_entry_mode (e))) // skip non-files completely
    2971                 :        9428 :         continue;
    2972                 :             : 
    2973         [ +  - ]:        2926 :       string fn = canonicalized_archive_entry_pathname (e);
    2974                 :             : 
    2975         [ -  + ]:        2926 :       if (populate_seekable)
    2976                 :             :         {
    2977                 :           0 :           string dn, bn;
    2978                 :           0 :           size_t slash = fn.rfind('/');
    2979         [ #  # ]:           0 :           if (slash == std::string::npos) {
    2980         [ #  # ]:           0 :             dn = "";
    2981         [ #  # ]:           0 :             bn = fn;
    2982                 :             :           } else {
    2983         [ #  # ]:           0 :             dn = fn.substr(0, slash);
    2984         [ #  # ]:           0 :             bn = fn.substr(slash + 1);
    2985                 :             :           }
    2986                 :             : 
    2987         [ #  # ]:           0 :           int64_t seekable_size = archive_entry_size (e);
    2988         [ #  # ]:           0 :           int64_t seekable_offset = archive_filter_bytes (a, 0);
    2989         [ #  # ]:           0 :           time_t seekable_mtime = archive_entry_mtime (e);
    2990                 :             : 
    2991         [ #  # ]:           0 :           pp->reset();
    2992         [ #  # ]:           0 :           pp->bind(1, b_id0);
    2993         [ #  # ]:           0 :           pp->bind(2, dn);
    2994         [ #  # ]:           0 :           pp->bind(3, bn);
    2995         [ #  # ]:           0 :           pp->bind(4, seekable_size);
    2996         [ #  # ]:           0 :           pp->bind(5, seekable_offset);
    2997         [ #  # ]:           0 :           pp->bind(6, seekable_mtime);
    2998         [ #  # ]:           0 :           rc = pp->step();
    2999         [ #  # ]:           0 :           if (rc != SQLITE_DONE)
    3000   [ #  #  #  # ]:           0 :             obatched(clog) << "recording seekable file=" << fn
    3001   [ #  #  #  #  :           0 :                            << " sqlite3 error: " << (sqlite3_errstr(rc) ?: "?") << endl;
          #  #  #  #  #  
                      # ]
    3002         [ #  # ]:           0 :           else if (verbose > 2)
    3003   [ #  #  #  # ]:           0 :             obatched(clog) << "recorded seekable file=" << fn
    3004   [ #  #  #  # ]:           0 :                            << " size=" << seekable_size
    3005   [ #  #  #  # ]:           0 :                            << " offset=" << seekable_offset
    3006   [ #  #  #  #  :           0 :                            << " mtime=" << seekable_mtime << endl;
                   #  # ]
    3007         [ #  # ]:           0 :           if (r != 0 && prefetch_count == 0) // stage 4
    3008                 :           0 :             continue;
    3009                 :           0 :         }
    3010                 :             : 
    3011   [ +  +  +  + ]:        2926 :       if ((r == 0) && (fn != b_source1)) // stage 1
    3012                 :        1632 :         continue;
    3013                 :             : 
    3014   [ +  -  -  + ]:        1294 :       if (fdcache.probe (b_source0, fn) && // skip if already interned
    3015         [ #  # ]:           0 :           fn != b_source1) // but only if we'd just be prefetching, PR29474
    3016                 :           0 :         continue;
    3017                 :             : 
    3018                 :             :       // extract this file to a temporary file
    3019                 :        1294 :       char* tmppath = NULL;
    3020                 :        1294 :       rc = asprintf (&tmppath, "%s/debuginfod-fdcache.XXXXXX", tmpdir.c_str());
    3021         [ -  + ]:        1294 :       if (rc < 0)
    3022   [ #  #  #  # ]:           0 :         throw libc_exception (ENOMEM, "cannot allocate tmppath");
    3023                 :        1294 :       defer_dtor<void*,void> tmmpath_freer (tmppath, free);
    3024         [ +  - ]:        1294 :       fd = mkstemp (tmppath);
    3025         [ -  + ]:        1294 :       if (fd < 0)
    3026   [ #  #  #  # ]:           0 :         throw libc_exception (errno, "cannot create temporary file");
    3027                 :             :       // NB: don't unlink (tmppath), as fdcache will take charge of it.
    3028                 :             : 
    3029                 :             :       // NB: this can take many uninterruptible seconds for a huge file
    3030         [ +  - ]:        1294 :       rc = archive_read_data_into_fd (a, fd);
    3031         [ -  + ]:        1294 :       if (rc != ARCHIVE_OK) // e.g. ENOSPC!
    3032                 :             :         {
    3033         [ #  # ]:           0 :           close (fd);
    3034                 :           0 :           unlink (tmppath);
    3035   [ #  #  #  # ]:           0 :           throw archive_exception(a, b_source0, "cannot extract file");
    3036                 :             :         }
    3037                 :             : 
    3038                 :             :       // Set the mtime so the fdcache file mtimes, even prefetched ones,
    3039                 :             :       // propagate to future webapi clients.
    3040                 :        1294 :       struct timespec tvs[2];
    3041                 :        1294 :       tvs[0].tv_sec = 0;
    3042                 :        1294 :       tvs[0].tv_nsec = UTIME_OMIT;
    3043         [ +  - ]:        1294 :       tvs[1].tv_sec = archive_entry_mtime(e);
    3044         [ +  - ]:        1294 :       tvs[1].tv_nsec = archive_entry_mtime_nsec(e);
    3045                 :        1294 :       (void) futimens (fd, tvs);  /* best effort */
    3046                 :             : 
    3047         [ +  + ]:        1294 :       if (r != 0) // stage 3
    3048                 :             :         {
    3049                 :         596 :           struct timespec extract_end;
    3050                 :         596 :           clock_gettime (CLOCK_MONOTONIC, &extract_end);
    3051                 :         596 :           double extract_time = (extract_end.tv_sec - extract_begin.tv_sec)
    3052                 :         596 :             + (extract_end.tv_nsec - extract_begin.tv_nsec)/1.e9;
    3053                 :             :           // NB: now we know we have a complete reusable file; make fdcache
    3054                 :             :           // responsible for unlinking it later.
    3055   [ +  -  +  -  :         596 :           fdcache.intern(b_source0, fn,
                   +  - ]
    3056                 :             :                          tmppath, archive_entry_size(e),
    3057                 :             :                          false, extract_time); // prefetched ones go to the prefetch cache
    3058                 :         596 :           prefetch_count --;
    3059         [ +  - ]:         596 :           close (fd); // we're not saving this fd to make a mhd-response from!
    3060                 :         596 :           continue;
    3061                 :         596 :         }
    3062                 :             : 
    3063   [ +  -  +  -  :         698 :       r = create_buildid_r_response (b_mtime, b_source0, b_source1, section,
                   +  - ]
    3064                 :             :                                      ima_sig, tmppath, fd,
    3065                 :             :                                      archive_entry_size(e),
    3066                 :             :                                      archive_entry_mtime(e),
    3067         [ +  - ]:         698 :                                      archive_extension + " archive",
    3068                 :             :                                      extract_begin);
    3069         [ +  + ]:         698 :       if (r == 0)
    3070                 :             :         break; // assume no chance of better luck around another iteration; no other copies of same file
    3071         [ +  - ]:         696 :       if (result_fd)
    3072                 :         696 :         *result_fd = fd;
    3073                 :        3522 :     }
    3074                 :             : 
    3075                 :             :   // XXX: rpm/file not found: delete this R entry?
    3076                 :         698 :   return r;
    3077                 :        1900 : }
    3078                 :             : 
    3079                 :             : void
    3080                 :         650 : add_client_federation_headers(debuginfod_client *client, MHD_Connection* conn){
    3081                 :             :   // Transcribe incoming User-Agent:
    3082         [ -  + ]:         650 :   string ua = MHD_lookup_connection_value (conn, MHD_HEADER_KIND, "User-Agent") ?: "";
    3083   [ +  -  +  - ]:         654 :   string ua_complete = string("User-Agent: ") + ua;
    3084         [ +  - ]:         650 :   debuginfod_add_http_header (client, ua_complete.c_str());
    3085                 :             : 
    3086                 :             :   // Compute larger XFF:, for avoiding info loss during
    3087                 :             :   // federation, and for future cyclicity detection.
    3088   [ +  -  +  +  :        1278 :   string xff = MHD_lookup_connection_value (conn, MHD_HEADER_KIND, "X-Forwarded-For") ?: "";
                   +  - ]
    3089         [ +  + ]:         650 :   if (xff != "")
    3090         [ +  - ]:          52 :     xff += string(", "); // comma separated list
    3091                 :             : 
    3092                 :         650 :   unsigned int xff_count = 0;
    3093         [ +  + ]:         978 :   for (auto&& i : xff){
    3094         [ +  + ]:         328 :     if (i == ',') xff_count++;
    3095                 :             :   }
    3096                 :             : 
    3097                 :             :   // if X-Forwarded-For: exceeds N hops,
    3098                 :             :   // do not delegate a local lookup miss to upstream debuginfods.
    3099         [ +  + ]:         650 :   if (xff_count >= forwarded_ttl_limit)
    3100                 :           4 :     throw reportable_exception(MHD_HTTP_NOT_FOUND, "not found, --forwared-ttl-limit reached \
    3101         [ +  - ]:           8 : and will not query the upstream servers");
    3102                 :             : 
    3103                 :             :   // Compute the client's numeric IP address only - so can't merge with conninfo()
    3104         [ +  - ]:         646 :   const union MHD_ConnectionInfo *u = MHD_get_connection_info (conn,
    3105                 :             :                                                                 MHD_CONNECTION_INFO_CLIENT_ADDRESS);
    3106         [ +  - ]:         646 :   struct sockaddr *so = u ? u->client_addr : 0;
    3107                 :         646 :   char hostname[256] = ""; // RFC1035
    3108   [ +  -  -  + ]:         646 :   if (so && so->sa_family == AF_INET) {
    3109         [ #  # ]:           0 :     (void) getnameinfo (so, sizeof (struct sockaddr_in), hostname, sizeof (hostname), NULL, 0,
    3110                 :             :                         NI_NUMERICHOST);
    3111         [ +  - ]:         646 :   } else if (so && so->sa_family == AF_INET6) {
    3112                 :         646 :     struct sockaddr_in6* addr6 = (struct sockaddr_in6*) so;
    3113   [ +  -  +  -  :         646 :     if (IN6_IS_ADDR_V4MAPPED(&addr6->sin6_addr)) {
                   -  + ]
    3114                 :         646 :       struct sockaddr_in addr4;
    3115         [ +  - ]:         646 :       memset (&addr4, 0, sizeof(addr4));
    3116                 :         646 :       addr4.sin_family = AF_INET;
    3117                 :         646 :       addr4.sin_port = addr6->sin6_port;
    3118         [ +  - ]:         646 :       memcpy (&addr4.sin_addr.s_addr, addr6->sin6_addr.s6_addr+12, sizeof(addr4.sin_addr.s_addr));
    3119         [ +  - ]:         646 :       (void) getnameinfo ((struct sockaddr*) &addr4, sizeof (addr4),
    3120                 :             :                           hostname, sizeof (hostname), NULL, 0,
    3121                 :             :                           NI_NUMERICHOST);
    3122                 :             :     } else {
    3123         [ #  # ]:           0 :       (void) getnameinfo (so, sizeof (struct sockaddr_in6), hostname, sizeof (hostname), NULL, 0,
    3124                 :             :                           NI_NUMERICHOST);
    3125                 :             :     }
    3126                 :             :   }
    3127                 :             : 
    3128   [ +  -  +  -  :        1296 :   string xff_complete = string("X-Forwarded-For: ")+xff+string(hostname);
             +  -  +  - ]
    3129         [ +  - ]:         646 :   debuginfod_add_http_header (client, xff_complete.c_str());
    3130                 :         654 : }
    3131                 :             : 
    3132                 :             : static struct MHD_Response*
    3133                 :        2210 : handle_buildid_match (bool internal_req_p,
    3134                 :             :                       int64_t b_mtime,
    3135                 :             :                       const string& b_stype,
    3136                 :             :                       const string& b_source0,
    3137                 :             :                       const string& b_source1,
    3138                 :             :                       int64_t b_id0,
    3139                 :             :                       int64_t b_id1,
    3140                 :             :                       const string& section,
    3141                 :             :                       int *result_fd)
    3142                 :             : {
    3143                 :        2210 :   try
    3144                 :             :     {
    3145         [ +  + ]:        2210 :       if (b_stype == "F")
    3146         [ +  - ]:        1152 :         return handle_buildid_f_match(internal_req_p, b_mtime, b_source0,
    3147                 :             :                                       section, result_fd);
    3148         [ +  - ]:        1058 :       else if (b_stype == "R")
    3149         [ +  + ]:        1058 :         return handle_buildid_r_match(internal_req_p, b_mtime, b_source0,
    3150                 :             :                                       b_source1, b_id0, b_id1, section,
    3151                 :             :                                       result_fd);
    3152                 :             :     }
    3153         [ -  + ]:          58 :   catch (const reportable_exception &e)
    3154                 :             :     {
    3155         [ +  - ]:          58 :       e.report(clog);
    3156                 :             :       // Report but swallow libc etc. errors here; let the caller
    3157                 :             :       // iterate to other matches of the content.
    3158                 :          58 :     }
    3159                 :             : 
    3160                 :             :   return 0;
    3161                 :             : }
    3162                 :             : 
    3163                 :             : 
    3164                 :             : static int
    3165                 :           4 : debuginfod_find_progress (debuginfod_client *, long a, long b)
    3166                 :             : {
    3167         [ -  + ]:           4 :   if (verbose > 4)
    3168   [ #  #  #  #  :           0 :     obatched(clog) << "federated debuginfod progress=" << a << "/" << b << endl;
             #  #  #  # ]
    3169                 :             : 
    3170                 :           4 :   return interrupted;
    3171                 :             : }
    3172                 :             : 
    3173                 :             : 
    3174                 :             : // a little lru pool of debuginfod_client*s for reuse between query threads
    3175                 :             : 
    3176                 :             : mutex dc_pool_lock;
    3177                 :             : deque<debuginfod_client*> dc_pool;
    3178                 :             : 
    3179                 :         670 : debuginfod_client* debuginfod_pool_begin()
    3180                 :             : {
    3181                 :         670 :   unique_lock<mutex> lock(dc_pool_lock);
    3182         [ +  + ]:         670 :   if (dc_pool.size() > 0)
    3183                 :             :     {
    3184   [ +  -  +  -  :        1276 :       inc_metric("dc_pool_op_count","op","begin-reuse");
             +  -  +  - ]
    3185                 :         638 :       debuginfod_client *c = dc_pool.front();
    3186                 :         638 :       dc_pool.pop_front();
    3187                 :         638 :       return c;
    3188                 :             :     }
    3189   [ +  -  +  -  :          64 :   inc_metric("dc_pool_op_count","op","begin-new");
             +  -  +  - ]
    3190         [ +  - ]:          32 :   return debuginfod_begin();
    3191                 :         670 : }
    3192                 :             : 
    3193                 :             : 
    3194                 :         162 : void debuginfod_pool_groom()
    3195                 :             : {
    3196                 :         162 :   unique_lock<mutex> lock(dc_pool_lock);
    3197         [ +  + ]:         194 :   while (dc_pool.size() > 0)
    3198                 :             :     {
    3199   [ +  -  +  -  :          64 :       inc_metric("dc_pool_op_count","op","end");
             +  -  +  - ]
    3200         [ +  - ]:          32 :       debuginfod_end(dc_pool.front());
    3201                 :          32 :       dc_pool.pop_front();
    3202                 :             :     }
    3203                 :         162 : }
    3204                 :             : 
    3205                 :             : 
    3206                 :         670 : void debuginfod_pool_end(debuginfod_client* c)
    3207                 :             : {
    3208                 :         670 :   unique_lock<mutex> lock(dc_pool_lock);
    3209   [ +  -  +  -  :        1340 :   inc_metric("dc_pool_op_count","op","end-save");
             +  -  +  - ]
    3210         [ +  - ]:         670 :   dc_pool.push_front(c); // accelerate reuse, vs. push_back
    3211                 :         670 : }
    3212                 :             : 
    3213                 :             : 
    3214                 :             : static struct MHD_Response*
    3215                 :        2795 : handle_buildid (MHD_Connection* conn,
    3216                 :             :                 const string& buildid /* unsafe */,
    3217                 :             :                 string& artifacttype /* unsafe, cleanse on exception/return */,
    3218                 :             :                 const string& suffix /* unsafe */,
    3219                 :             :                 int *result_fd)
    3220                 :             : {
    3221                 :             :   // validate artifacttype
    3222         [ +  + ]:        2795 :   string atype_code;
    3223   [ +  +  +  - ]:        2795 :   if (artifacttype == "debuginfo") atype_code = "D";
    3224   [ +  +  +  - ]:        1827 :   else if (artifacttype == "executable") atype_code = "E";
    3225   [ +  +  +  - ]:        1138 :   else if (artifacttype == "source") atype_code = "S";
    3226   [ +  +  +  - ]:          12 :   else if (artifacttype == "section") atype_code = "I";
    3227                 :             :   else {
    3228         [ +  - ]:           4 :     artifacttype = "invalid"; // PR28242 ensure http_resposes metrics don't propagate unclean user data 
    3229         [ +  - ]:           8 :     throw reportable_exception("invalid artifacttype");
    3230                 :             :   }
    3231                 :             : 
    3232         [ +  + ]:        2791 :   if (conn != 0)
    3233   [ +  -  +  -  :        5825 :     inc_metric("http_requests_total", "type", artifacttype);
                   +  - ]
    3234                 :             : 
    3235         [ +  + ]:        2791 :   string section;
    3236         [ +  + ]:        2791 :   if (atype_code == "I")
    3237                 :             :     {
    3238         [ -  + ]:           8 :       if (suffix.size () < 2)
    3239         [ #  # ]:           0 :         throw reportable_exception ("invalid section suffix");
    3240                 :             : 
    3241                 :             :       // Remove leading '/'
    3242         [ +  - ]:           8 :       section = suffix.substr(1);
    3243                 :             :     }
    3244                 :             : 
    3245   [ +  +  -  + ]:        2791 :   if (atype_code == "S" && suffix == "")
    3246         [ #  # ]:           0 :      throw reportable_exception("invalid source suffix");
    3247                 :             : 
    3248                 :             :   // validate buildid
    3249         [ +  + ]:        2791 :   if ((buildid.size() < 2) || // not empty
    3250   [ +  +  +  -  :        5579 :       (buildid.size() % 2) || // even number
                   +  - ]
    3251                 :        2788 :       (buildid.find_first_not_of("0123456789abcdef") != string::npos)) // pure tasty lowercase hex
    3252         [ +  - ]:           6 :     throw reportable_exception("invalid buildid");
    3253                 :             : 
    3254         [ +  - ]:        2788 :   if (verbose > 1)
    3255   [ +  -  +  - ]:        8364 :     obatched(clog) << "searching for buildid=" << buildid << " artifacttype=" << artifacttype
    3256   [ +  -  +  -  :        2788 :          << " suffix=" << suffix << endl;
          +  -  +  -  +  
                      - ]
    3257                 :             : 
    3258                 :             :   // If invoked from the scanner threads, use the scanners' read-write
    3259                 :             :   // connection.  Otherwise use the web query threads' read-only connection.
    3260         [ +  + ]:        2788 :   sqlite3 *thisdb = (conn == 0) ? db : dbq;
    3261                 :             : 
    3262                 :        2788 :   sqlite_ps *pp = 0;
    3263                 :             : 
    3264         [ +  + ]:        2788 :   if (atype_code == "D")
    3265                 :             :     {
    3266                 :         968 :       pp = new sqlite_ps (thisdb, "mhd-query-d",
    3267                 :             :                           "select mtime, sourcetype, source0, source1, id0, id1 from " BUILDIDS "_query_d2 where buildid = ? "
    3268   [ +  -  +  -  :        1936 :                           "order by mtime desc");
             +  -  +  - ]
    3269         [ +  - ]:         968 :       pp->reset();
    3270         [ +  - ]:         968 :       pp->bind(1, buildid);
    3271                 :             :     }
    3272         [ +  + ]:        1820 :   else if (atype_code == "E")
    3273                 :             :     {
    3274                 :         686 :       pp = new sqlite_ps (thisdb, "mhd-query-e",
    3275                 :             :                           "select mtime, sourcetype, source0, source1, id0, id1 from " BUILDIDS "_query_e2 where buildid = ? "
    3276   [ +  -  +  -  :        1372 :                           "order by mtime desc");
             +  -  +  - ]
    3277         [ +  - ]:         686 :       pp->reset();
    3278         [ +  - ]:         686 :       pp->bind(1, buildid);
    3279                 :             :     }
    3280         [ +  + ]:        1134 :   else if (atype_code == "S")
    3281                 :             :     {
    3282                 :             :       // PR25548
    3283                 :             :       // Incoming source queries may come in with either dwarf-level OR canonicalized paths.
    3284                 :             :       // We let the query pass with either one.
    3285                 :             : 
    3286                 :        1126 :       pp = new sqlite_ps (thisdb, "mhd-query-s",
    3287                 :             :                           "select mtime, sourcetype, source0, source1 from " BUILDIDS "_query_s where buildid = ? and artifactsrc in (?,?) "
    3288   [ +  -  +  -  :        2252 :                           "order by sharedprefix(source0,source0ref) desc, mtime desc");
             +  -  +  - ]
    3289         [ +  - ]:        1126 :       pp->reset();
    3290         [ +  - ]:        1126 :       pp->bind(1, buildid);
    3291                 :             :       // NB: we don't store the non-canonicalized path names any more, but old databases
    3292                 :             :       // might have them (and no canon ones), so we keep searching for both.
    3293         [ +  - ]:        1126 :       pp->bind(2, suffix);
    3294   [ +  -  +  - ]:        2891 :       pp->bind(3, canon_pathname(suffix));
    3295                 :             :     }
    3296         [ +  - ]:           8 :   else if (atype_code == "I")
    3297                 :             :     {
    3298                 :           8 :       pp = new sqlite_ps (thisdb, "mhd-query-i",
    3299                 :             :         "select mtime, sourcetype, source0, source1, 1 as debug_p from " BUILDIDS "_query_d2 where buildid = ? "
    3300                 :             :         "union all "
    3301                 :             :         "select mtime, sourcetype, source0, source1, 0 as debug_p from " BUILDIDS "_query_e2 where buildid = ? "
    3302   [ +  -  +  -  :          16 :         "order by debug_p desc, mtime desc");
             +  -  +  - ]
    3303         [ +  - ]:           8 :       pp->reset();
    3304         [ +  - ]:           8 :       pp->bind(1, buildid);
    3305         [ +  - ]:           8 :       pp->bind(2, buildid);
    3306                 :             :     }
    3307                 :        2788 :   unique_ptr<sqlite_ps> ps_closer(pp); // release pp if exception or return
    3308                 :             : 
    3309                 :        2788 :   bool do_upstream_section_query = true;
    3310                 :             : 
    3311                 :             :   // consume all the rows
    3312                 :        2912 :   while (1)
    3313                 :             :     {
    3314         [ +  - ]:        2850 :       int rc = pp->step();
    3315         [ +  + ]:        2850 :       if (rc == SQLITE_DONE) break;
    3316         [ -  + ]:        2210 :       if (rc != SQLITE_ROW)
    3317   [ #  #  #  # ]:           0 :         throw sqlite_exception(rc, "step");
    3318                 :             : 
    3319         [ +  - ]:        2210 :       int64_t b_mtime = sqlite3_column_int64 (*pp, 0);
    3320   [ +  -  -  +  :        2210 :       string b_stype = string((const char*) sqlite3_column_text (*pp, 1) ?: ""); /* by DDL may not be NULL */
                   +  - ]
    3321   [ +  -  -  +  :        2210 :       string b_source0 = string((const char*) sqlite3_column_text (*pp, 2) ?: ""); /* may be NULL */
                   +  - ]
    3322   [ +  -  +  +  :        3362 :       string b_source1 = string((const char*) sqlite3_column_text (*pp, 3) ?: ""); /* may be NULL */
                   +  - ]
    3323                 :        2210 :       int64_t b_id0 = 0, b_id1 = 0;
    3324   [ +  +  +  + ]:        2210 :       if (atype_code == "D" || atype_code == "E")
    3325                 :             :         {
    3326         [ +  - ]:        1070 :           b_id0 = sqlite3_column_int64 (*pp, 4);
    3327         [ +  - ]:        1070 :           b_id1 = sqlite3_column_int64 (*pp, 5);
    3328                 :             :         }
    3329                 :             : 
    3330         [ +  - ]:        2210 :       if (verbose > 1)
    3331   [ +  -  +  - ]:        6630 :         obatched(clog) << "found mtime=" << b_mtime << " stype=" << b_stype
    3332   [ +  -  +  -  :        2210 :              << " source0=" << b_source0 << " source1=" << b_source1 << endl;
          +  -  +  -  +  
             -  +  -  +  
                      - ]
    3333                 :             : 
    3334                 :             :       // Try accessing the located match.
    3335                 :             :       // XXX: in case of multiple matches, attempt them in parallel?
    3336         [ +  - ]:        2210 :       auto r = handle_buildid_match (conn ? false : true,
    3337                 :             :                                      b_mtime, b_stype, b_source0, b_source1,
    3338                 :             :                                      b_id0, b_id1, section, result_fd);
    3339         [ +  + ]:        2210 :       if (r)
    3340                 :        2148 :         return r;
    3341                 :             : 
    3342                 :             :       // If a debuginfo file matching BUILDID was found but didn't contain
    3343                 :             :       // the desired section, then the section should not exist.  Don't
    3344                 :             :       // bother querying upstream servers.
    3345   [ +  +  +  -  :          62 :       if (!section.empty () && (sqlite3_column_int (*pp, 4) == 1))
                   -  + ]
    3346                 :             :         {
    3347                 :           4 :           struct stat st;
    3348                 :             : 
    3349                 :             :           // For "F" sourcetype, check if the debuginfo exists. For "R"
    3350                 :             :           // sourcetype, check if the debuginfo was interned into the fdcache.
    3351         [ -  + ]:           2 :           if ((b_stype == "F" && (stat (b_source0.c_str (), &st) == 0))
    3352   [ +  +  +  -  :           4 :               || (b_stype == "R" && fdcache.probe (b_source0, b_source1)))
             +  -  +  - ]
    3353                 :             :             do_upstream_section_query = false;
    3354                 :             :         }
    3355                 :        2210 :     }
    3356         [ +  - ]:         640 :   pp->reset();
    3357                 :             : 
    3358         [ -  + ]:         640 :   if (!do_upstream_section_query)
    3359         [ #  # ]:           0 :     throw reportable_exception(MHD_HTTP_NOT_FOUND, "not found");
    3360                 :             : 
    3361                 :             :   // We couldn't find it in the database.  Last ditch effort
    3362                 :             :   // is to defer to other debuginfo servers.
    3363                 :             : 
    3364                 :         640 :   int fd = -1;
    3365         [ +  - ]:         640 :   debuginfod_client *client = debuginfod_pool_begin ();
    3366         [ -  + ]:         640 :   if (client == NULL)
    3367   [ #  #  #  # ]:           0 :     throw libc_exception(errno, "debuginfod client pool alloc");
    3368                 :         640 :   defer_dtor<debuginfod_client*,void> client_closer (client, debuginfod_pool_end);
    3369                 :             :   
    3370         [ +  - ]:         640 :   debuginfod_set_progressfn (client, & debuginfod_find_progress);
    3371                 :             : 
    3372         [ +  + ]:         640 :   if (conn)
    3373         [ +  + ]:         620 :     add_client_federation_headers(client, conn);
    3374                 :             : 
    3375         [ +  + ]:         636 :   if (artifacttype == "debuginfo")
    3376         [ +  - ]:          88 :     fd = debuginfod_find_debuginfo (client,
    3377         [ +  - ]:          88 :                                     (const unsigned char*) buildid.c_str(),
    3378                 :             :                                     0, NULL);
    3379         [ +  + ]:         548 :   else if (artifacttype == "executable")
    3380         [ +  - ]:         546 :     fd = debuginfod_find_executable (client,
    3381         [ +  - ]:         546 :                                      (const unsigned char*) buildid.c_str(),
    3382                 :             :                                      0, NULL);
    3383         [ +  - ]:           2 :   else if (artifacttype == "source")
    3384         [ +  - ]:           2 :     fd = debuginfod_find_source (client,
    3385         [ +  - ]:           2 :                                  (const unsigned char*) buildid.c_str(),
    3386                 :             :                                  0, suffix.c_str(), NULL);
    3387         [ #  # ]:           0 :   else if (artifacttype == "section")
    3388         [ #  # ]:           0 :     fd = debuginfod_find_section (client,
    3389         [ #  # ]:           0 :                                   (const unsigned char*) buildid.c_str(),
    3390                 :             :                                   0, section.c_str(), NULL);
    3391                 :             :   
    3392         [ +  + ]:         636 :   if (fd >= 0)
    3393                 :             :     {
    3394         [ +  - ]:           4 :       if (conn != 0)
    3395   [ +  -  +  -  :         644 :         inc_metric ("http_responses_total","result","upstream");
             +  -  +  - ]
    3396                 :           4 :       struct stat s;
    3397                 :           4 :       int rc = fstat (fd, &s);
    3398         [ +  - ]:           4 :       if (rc == 0)
    3399                 :             :         {
    3400         [ +  - ]:           4 :           auto r = MHD_create_response_from_fd ((uint64_t) s.st_size, fd);
    3401         [ +  - ]:           4 :           if (r)
    3402                 :             :             {
    3403         [ +  - ]:           4 :               add_mhd_response_header (r, "Content-Type",
    3404                 :             :                                        "application/octet-stream");
    3405                 :             :               // Copy the incoming headers
    3406         [ +  - ]:           4 :               const char * hdrs = debuginfod_get_headers(client);
    3407         [ +  - ]:           4 :               string header_dup;
    3408         [ +  - ]:           4 :               if (hdrs)
    3409         [ +  - ]:           4 :                 header_dup = string(hdrs);
    3410                 :             :               // Parse the "header: value\n" lines into (h,v) tuples and pass on
    3411                 :          20 :               while(1)
    3412                 :             :                 {
    3413                 :          12 :                   size_t newline = header_dup.find('\n');
    3414         [ +  + ]:          12 :                   if (newline == string::npos) break;
    3415                 :           8 :                   size_t colon = header_dup.find(':');
    3416         [ +  - ]:           8 :                   if (colon == string::npos) break;
    3417         [ +  - ]:           8 :                   string header = header_dup.substr(0,colon);
    3418         [ +  - ]:           8 :                   string value = header_dup.substr(colon+1,newline-colon-1);
    3419                 :             :                   // strip leading spaces from value
    3420                 :           8 :                   size_t nonspace = value.find_first_not_of(" ");
    3421         [ +  - ]:           8 :                   if (nonspace != string::npos)
    3422         [ +  - ]:           8 :                     value = value.substr(nonspace);
    3423         [ +  - ]:           8 :                   add_mhd_response_header(r, header.c_str(), value.c_str());
    3424         [ +  - ]:           8 :                   header_dup = header_dup.substr(newline+1);
    3425                 :           8 :                 }
    3426                 :             : 
    3427         [ +  - ]:           4 :               add_mhd_last_modified (r, s.st_mtime);
    3428         [ +  - ]:           4 :               if (verbose > 1)
    3429   [ +  -  +  - ]:           8 :                 obatched(clog) << "serving file from upstream debuginfod/cache" << endl;
    3430         [ +  - ]:           4 :               if (result_fd)
    3431                 :           4 :                 *result_fd = fd;
    3432                 :           4 :               return r; // NB: don't close fd; libmicrohttpd will
    3433                 :           0 :             }
    3434                 :             :         }
    3435         [ #  # ]:           0 :       close (fd);
    3436                 :             :     }
    3437                 :             :   else
    3438         [ +  + ]:         632 :     switch(fd)
    3439                 :             :       {
    3440                 :             :       case -ENOSYS:
    3441                 :             :         break;
    3442                 :             :       case -ENOENT:
    3443                 :             :         break;
    3444                 :         532 :       default: // some more tricky error
    3445   [ +  -  +  - ]:        1064 :         throw libc_exception(-fd, "upstream debuginfod query failed");
    3446                 :             :       }
    3447                 :             : 
    3448         [ +  - ]:         200 :   throw reportable_exception(MHD_HTTP_NOT_FOUND, "not found");
    3449                 :        3427 : }
    3450                 :             : 
    3451                 :             : 
    3452                 :             : ////////////////////////////////////////////////////////////////////////
    3453                 :             : 
    3454                 :             : static map<string,double> metrics; // arbitrary data for /metrics query
    3455                 :             : // NB: store int64_t since all our metrics are integers; prometheus accepts double
    3456                 :             : static mutex metrics_lock;
    3457                 :             : // NB: these objects get released during the process exit via global dtors
    3458                 :             : // do not call them from within other global dtors
    3459                 :             : 
    3460                 :             : // utility function for assembling prometheus-compatible
    3461                 :             : // name="escaped-value" strings
    3462                 :             : // https://prometheus.io/docs/instrumenting/exposition_formats/
    3463                 :             : static string
    3464                 :      825507 : metric_label(const string& name, const string& value)
    3465                 :             : {
    3466                 :      825507 :   string x = name + "=\"";
    3467         [ +  + ]:    14199056 :   for (auto&& c : value)
    3468   [ -  -  -  + ]:    13374429 :     switch(c)
    3469                 :             :       {
    3470         [ #  # ]:           0 :       case '\\': x += "\\\\"; break;
    3471         [ #  # ]:           0 :       case '\"': x += "\\\""; break;
    3472         [ #  # ]:           0 :       case '\n': x += "\\n"; break;
    3473         [ +  - ]:    26748332 :       default: x += c; break;
    3474                 :             :       }
    3475         [ +  - ]:      824627 :   x += "\"";
    3476                 :      824811 :   return x;
    3477                 :           0 : }
    3478                 :             : 
    3479                 :             : 
    3480                 :             : // add prometheus-format metric name + label tuple (if any) + value
    3481                 :             : 
    3482                 :             : static void
    3483                 :       12192 : set_metric(const string& metric, double value)
    3484                 :             : {
    3485                 :       12192 :   unique_lock<mutex> lock(metrics_lock);
    3486         [ +  - ]:       12192 :   metrics[metric] = value;
    3487                 :       12192 : }
    3488                 :             : static void
    3489                 :         102 : inc_metric(const string& metric)
    3490                 :             : {
    3491                 :         102 :   unique_lock<mutex> lock(metrics_lock);
    3492         [ +  - ]:         102 :   metrics[metric] ++;
    3493                 :         102 : }
    3494                 :             : static void
    3495                 :        8307 : set_metric(const string& metric,
    3496                 :             :            const string& lname, const string& lvalue,
    3497                 :             :            double value)
    3498                 :             : {
    3499   [ +  -  +  - ]:       16617 :   string key = (metric + "{" + metric_label(lname, lvalue) + "}");
    3500         [ +  - ]:        8310 :   unique_lock<mutex> lock(metrics_lock);
    3501         [ +  - ]:        8310 :   metrics[key] = value;
    3502                 :        8310 : }
    3503                 :             : 
    3504                 :             : static void
    3505                 :      398442 : inc_metric(const string& metric,
    3506                 :             :            const string& lname, const string& lvalue)
    3507                 :             : {
    3508   [ +  -  +  - ]:      796878 :   string key = (metric + "{" + metric_label(lname, lvalue) + "}");
    3509         [ +  - ]:      398438 :   unique_lock<mutex> lock(metrics_lock);
    3510         [ +  - ]:      398506 :   metrics[key] ++;
    3511                 :      398481 : }
    3512                 :             : static void
    3513                 :      379147 : add_metric(const string& metric,
    3514                 :             :            const string& lname, const string& lvalue,
    3515                 :             :            double value)
    3516                 :             : {
    3517   [ +  -  +  - ]:      758374 :   string key = (metric + "{" + metric_label(lname, lvalue) + "}");
    3518         [ +  - ]:      379254 :   unique_lock<mutex> lock(metrics_lock);
    3519         [ +  - ]:      379520 :   metrics[key] += value;
    3520                 :      379513 : }
    3521                 :             : static void
    3522                 :         102 : add_metric(const string& metric,
    3523                 :             :            double value)
    3524                 :             : {
    3525                 :         102 :   unique_lock<mutex> lock(metrics_lock);
    3526         [ +  - ]:         102 :   metrics[metric] += value;
    3527                 :         102 : }
    3528                 :             : 
    3529                 :             : 
    3530                 :             : // and more for higher arity labels if needed
    3531                 :             : 
    3532                 :             : static void
    3533                 :       10149 : inc_metric(const string& metric,
    3534                 :             :            const string& lname, const string& lvalue,
    3535                 :             :            const string& rname, const string& rvalue)
    3536                 :             : {
    3537         [ +  - ]:       20298 :   string key = (metric + "{"
    3538   [ +  -  +  - ]:       40596 :                 + metric_label(lname, lvalue) + ","
    3539         [ +  - ]:       30447 :                 + metric_label(rname, rvalue) + "}");
    3540         [ +  - ]:       10149 :   unique_lock<mutex> lock(metrics_lock);
    3541         [ +  - ]:       10149 :   metrics[key] ++;
    3542                 :       10149 : }
    3543                 :             : static void
    3544                 :       10149 : add_metric(const string& metric,
    3545                 :             :            const string& lname, const string& lvalue,
    3546                 :             :            const string& rname, const string& rvalue,
    3547                 :             :            double value)
    3548                 :             : {
    3549         [ +  - ]:       20298 :   string key = (metric + "{"
    3550   [ +  -  +  - ]:       40596 :                 + metric_label(lname, lvalue) + ","
    3551         [ +  - ]:       30447 :                 + metric_label(rname, rvalue) + "}");
    3552         [ +  - ]:       10149 :   unique_lock<mutex> lock(metrics_lock);
    3553         [ +  - ]:       10149 :   metrics[key] += value;
    3554                 :       10149 : }
    3555                 :             : 
    3556                 :             : static struct MHD_Response*
    3557                 :         748 : handle_metrics (off_t* size)
    3558                 :             : {
    3559                 :         748 :   stringstream o;
    3560                 :         748 :   {
    3561         [ +  - ]:         748 :     unique_lock<mutex> lock(metrics_lock);
    3562         [ +  + ]:       79517 :     for (auto&& i : metrics)
    3563         [ +  - ]:       78769 :       o << i.first
    3564                 :             :         << " "
    3565   [ +  -  +  - ]:       78769 :         << std::setprecision(std::numeric_limits<double>::digits10 + 1)
    3566   [ +  -  +  - ]:       78769 :         << i.second
    3567                 :       78769 :         << endl;
    3568                 :           0 :   }
    3569         [ +  - ]:         748 :   const string& os = o.str();
    3570         [ +  - ]:         748 :   MHD_Response* r = MHD_create_response_from_buffer (os.size(),
    3571         [ +  - ]:         748 :                                                      (void*) os.c_str(),
    3572                 :             :                                                      MHD_RESPMEM_MUST_COPY);
    3573         [ +  - ]:         748 :   if (r != NULL)
    3574                 :             :     {
    3575         [ +  - ]:         748 :       *size = os.size();
    3576         [ +  - ]:         748 :       add_mhd_response_header (r, "Content-Type", "text/plain");
    3577                 :             :     }
    3578                 :        1496 :   return r;
    3579                 :         748 : }
    3580                 :             : 
    3581                 :             : 
    3582                 :             : static struct MHD_Response*
    3583                 :          30 : handle_metadata (MHD_Connection* conn,
    3584                 :             :                  string key, string value, off_t* size)
    3585                 :             : {
    3586                 :          30 :   MHD_Response* r;
    3587                 :             :   // Because this query can take on the order of many seconds, we need
    3588                 :             :   // to prevent DoS against the other normal quick queries, so we use
    3589                 :             :   // a dedicated database connection.
    3590                 :          30 :   sqlite3 *thisdb = 0;
    3591                 :          30 :   int rc = sqlite3_open_v2 (db_path.c_str(), &thisdb, (SQLITE_OPEN_READONLY
    3592                 :             :                                                        |SQLITE_OPEN_URI
    3593                 :             :                                                        |SQLITE_OPEN_PRIVATECACHE
    3594                 :             :                                                        |SQLITE_OPEN_NOMUTEX), /* private to us */
    3595                 :             :                             NULL);
    3596         [ -  + ]:          30 :   if (rc)
    3597   [ #  #  #  # ]:           0 :     throw sqlite_exception(rc, "cannot open database for metadata query");
    3598                 :          30 :   defer_dtor<sqlite3*,int> sqlite_db_closer (thisdb, sqlite3_close_v2);
    3599                 :             :                                            
    3600                 :             :   // Query locally for matching e, d files
    3601         [ +  + ]:          30 :   string op;
    3602         [ +  + ]:          30 :   if (key == "glob")
    3603         [ +  - ]:          26 :     op = "glob";
    3604         [ +  - ]:           4 :   else if (key == "file")
    3605         [ +  - ]:           4 :     op = "=";
    3606                 :             :   else
    3607         [ #  # ]:           0 :     throw reportable_exception("/metadata webapi error, unsupported key");
    3608                 :             : 
    3609                 :             :   // Since PR30378, the file names are segmented into two tables.  We
    3610                 :             :   // could do a glob/= search over the _files_v view that combines
    3611                 :             :   // them, but that means that the entire _files_v thing has to be
    3612                 :             :   // materialized & scanned to do the query.  Slow!  Instead, we can
    3613                 :             :   // segment the incoming file/glob pattern into dirname / basename
    3614                 :             :   // parts, and apply them to the corresponding table.  This is done
    3615                 :             :   // by splitting the value at the last "/".  If absent, the same
    3616                 :             :   // convention as is used in register_file_name().
    3617                 :             : 
    3618                 :          30 :   string dirname, bname; // basename is a "poisoned" identifier on some distros
    3619                 :          30 :   size_t slash = value.rfind('/');
    3620         [ -  + ]:          30 :   if (slash == std::string::npos) {
    3621         [ #  # ]:           0 :     dirname = "";
    3622         [ #  # ]:           0 :     bname = value;
    3623                 :             :   } else {
    3624         [ +  - ]:          30 :     dirname = value.substr(0, slash);
    3625         [ +  - ]:          30 :     bname = value.substr(slash+1);
    3626                 :             :   }
    3627                 :             : 
    3628                 :             :   // NB: further optimization is possible: replacing the 'glob' op
    3629                 :             :   // with simple equality, if the corresponding value segment lacks
    3630                 :             :   // metacharacters.  sqlite may or may not be smart enough to do so,
    3631                 :             :   // so we help out.
    3632         [ +  - ]:          30 :   string metacharacters = "[]*?";
    3633   [ +  +  +  +  :          56 :   string dop = (op == "glob" && dirname.find_first_of(metacharacters) == string::npos) ? "=" : op;
             +  -  +  - ]
    3634   [ +  +  -  +  :          56 :   string bop = (op == "glob" && bname.find_first_of(metacharacters) == string::npos) ? "=" : op;
             -  -  +  - ]
    3635                 :             :   
    3636                 :          30 :   string sql = string(
    3637                 :             :                       // explicit query r_de and f_de once here, rather than the query_d and query_e
    3638                 :             :                       // separately, because they scan the same tables, so we'd double the work
    3639                 :             :                       "select d1.executable_p, d1.debuginfo_p, 0 as source_p, "
    3640                 :             :                       "       b1.hex, f1d.name || '/' || f1b.name as file, a1.name as archive "
    3641                 :             :                       "from " BUILDIDS "_r_de d1, " BUILDIDS "_files f1, " BUILDIDS "_fileparts f1b, " BUILDIDS "_fileparts f1d, "
    3642                 :             :                       BUILDIDS "_buildids b1, " BUILDIDS "_files_v a1 "
    3643                 :             :                       "where f1.id = d1.content and a1.id = d1.file and d1.buildid = b1.id "
    3644   [ +  -  +  - ]:          90 :                       "      and f1d.name " + dop + " ? and f1b.name " + bop + " ? and f1.dirname = f1d.id and f1.basename = f1b.id "
    3645                 :             :                       "union all \n"
    3646                 :             :                       "select d2.executable_p, d2.debuginfo_p, 0, "
    3647                 :             :                       "       b2.hex, f2d.name || '/' || f2b.name, NULL "
    3648                 :             :                       "from " BUILDIDS "_f_de d2, " BUILDIDS "_files f2, " BUILDIDS "_fileparts f2b, " BUILDIDS "_fileparts f2d, "
    3649                 :             :                       BUILDIDS "_buildids b2 "
    3650                 :             :                       "where f2.id = d2.file and d2.buildid = b2.id "
    3651   [ +  -  +  - ]:          90 :                       "      and f2d.name " + dop + " ? and f2b.name " + bop + " ? "
    3652                 :          30 :                       "      and f2.dirname = f2d.id and f2.basename = f2b.id");
    3653                 :             :   
    3654                 :             :   // NB: we could query source file names too, thusly:
    3655                 :             :   //
    3656                 :             :   //    select * from " BUILDIDS "_buildids b, " BUILDIDS "_files_v f1, " BUILDIDS "_r_sref sr
    3657                 :             :   //    where b.id = sr.buildid and f1.id = sr.artifactsrc and f1.name " + op + "?"
    3658                 :             :   //    UNION ALL something with BUILDIDS "_f_s"
    3659                 :             :   //
    3660                 :             :   // But the first part of this query cannot run fast without the same index temp-created
    3661                 :             :   // during "maxigroom":
    3662                 :             :   //    create index " BUILDIDS "_r_sref_arc on " BUILDIDS "_r_sref(artifactsrc);
    3663                 :             :   // and unfortunately this index is HUGE.  It's similar to the size of the _r_sref
    3664                 :             :   // table, which is already the largest part of a debuginfod index.  Adding that index
    3665                 :             :   // would nearly double the .sqlite db size.
    3666                 :             :                       
    3667   [ +  -  +  -  :          30 :   sqlite_ps *pp = new sqlite_ps (thisdb, "mhd-query-meta-glob", sql);
                   +  - ]
    3668         [ +  - ]:          30 :   pp->reset();
    3669         [ +  - ]:          30 :   pp->bind(1, dirname);
    3670         [ +  - ]:          30 :   pp->bind(2, bname);
    3671         [ +  - ]:          30 :   pp->bind(3, dirname);
    3672         [ +  - ]:          30 :   pp->bind(4, bname);
    3673                 :          30 :   unique_ptr<sqlite_ps> ps_closer(pp); // release pp if exception or return
    3674                 :          30 :   pp->reset_timeout(metadata_maxtime_s);
    3675                 :             :       
    3676         [ +  - ]:          30 :   json_object *metadata = json_object_new_object();
    3677   [ -  +  -  -  :          30 :   if (!metadata) throw libc_exception(ENOMEM, "json allocation");
                   -  - ]
    3678                 :          30 :   defer_dtor<json_object*,int> metadata_d(metadata, json_object_put);
    3679         [ +  - ]:          30 :   json_object *metadata_arr = json_object_new_array();
    3680   [ -  +  -  -  :          30 :   if (!metadata_arr) throw libc_exception(ENOMEM, "json allocation");
                   -  - ]
    3681         [ +  - ]:          30 :   json_object_object_add(metadata, "results", metadata_arr);
    3682                 :             :   // consume all the rows
    3683                 :             :   
    3684                 :          48 :   bool metadata_complete = true;
    3685                 :          48 :   while (1)
    3686                 :             :     {
    3687         [ +  - ]:          48 :       rc = pp->step_timeout();
    3688         [ +  + ]:          48 :       if (rc == SQLITE_DONE) // success
    3689                 :             :         break;
    3690         [ +  - ]:          18 :       if (rc == SQLITE_ABORT || rc == SQLITE_INTERRUPT) // interrupted such as by timeout
    3691                 :             :         {
    3692                 :             :           metadata_complete = false;
    3693                 :             :           break;
    3694                 :             :         }
    3695         [ -  + ]:          18 :       if (rc != SQLITE_ROW) // error
    3696   [ #  #  #  # ]:           0 :         throw sqlite_exception(rc, "step");
    3697                 :             : 
    3698         [ +  - ]:          18 :       int m_executable_p = sqlite3_column_int (*pp, 0);
    3699         [ +  - ]:          18 :       int m_debuginfo_p  = sqlite3_column_int (*pp, 1);
    3700         [ +  - ]:          18 :       int m_source_p     = sqlite3_column_int (*pp, 2);
    3701   [ +  -  -  +  :          18 :       string m_buildid   = (const char*) sqlite3_column_text (*pp, 3) ?: ""; // should always be non-null
                   +  - ]
    3702   [ +  -  -  +  :          18 :       string m_file      = (const char*) sqlite3_column_text (*pp, 4) ?: "";
                   +  - ]
    3703   [ +  -  -  +  :          18 :       string m_archive   = (const char*) sqlite3_column_text (*pp, 5) ?: "";      
                   +  - ]
    3704                 :             : 
    3705                 :             :       // Confirm that m_file matches in the fnmatch(FNM_PATHNAME)
    3706                 :             :       // sense, since sqlite's GLOB operator is a looser filter.
    3707   [ +  -  +  -  :          18 :       if (key == "glob" && fnmatch(value.c_str(), m_file.c_str(), FNM_PATHNAME) != 0)
                   -  + ]
    3708                 :           0 :         continue;
    3709                 :             :       
    3710                 :          36 :       auto add_metadata = [metadata_arr, m_buildid, m_file, m_archive](const string& type) {
    3711                 :          18 :         json_object* entry = json_object_new_object();
    3712   [ -  +  -  -  :          18 :         if (NULL == entry) throw libc_exception (ENOMEM, "cannot allocate json");
                   -  - ]
    3713                 :          18 :         defer_dtor<json_object*,int> entry_d(entry, json_object_put);
    3714                 :             :         
    3715                 :          90 :         auto add_entry_metadata = [entry](const char* k, string v) {
    3716                 :          72 :           json_object* s;
    3717         [ +  - ]:          72 :           if(v != "") {
    3718                 :          72 :             s = json_object_new_string(v.c_str());
    3719   [ -  +  -  -  :          72 :             if (NULL == s) throw libc_exception (ENOMEM, "cannot allocate json");
                   -  - ]
    3720                 :          72 :             json_object_object_add(entry, k, s);
    3721                 :             :           }
    3722                 :          72 :         };
    3723                 :             :         
    3724   [ +  -  +  - ]:          18 :         add_entry_metadata("type", type.c_str());
    3725   [ +  -  +  - ]:          18 :         add_entry_metadata("buildid", m_buildid);
    3726   [ +  -  +  - ]:          18 :         add_entry_metadata("file", m_file);
    3727   [ +  -  +  -  :          36 :         if (m_archive != "") add_entry_metadata("archive", m_archive);        
                   +  - ]
    3728         [ -  + ]:          18 :         if (verbose > 3)
    3729         [ #  # ]:           0 :           obatched(clog) << "metadata found local "
    3730                 :             :                          << json_object_to_json_string_ext(entry,
    3731   [ #  #  #  #  :           0 :                                                            JSON_C_TO_STRING_PRETTY)
                   #  # ]
    3732                 :           0 :                          << endl;
    3733                 :             :         
    3734                 :             :         // Increase ref count to switch its ownership
    3735   [ +  -  +  - ]:          18 :         json_object_array_add(metadata_arr, json_object_get(entry));
    3736   [ +  -  +  -  :          36 :       };
                   +  - ]
    3737                 :             : 
    3738   [ +  -  +  -  :          36 :       if (m_executable_p) add_metadata("executable");
                   +  - ]
    3739   [ -  +  -  -  :          18 :       if (m_debuginfo_p) add_metadata("debuginfo");      
                   -  - ]
    3740   [ -  +  -  -  :          18 :       if (m_source_p) add_metadata("source");              
                   -  - ]
    3741                 :          18 :     }
    3742         [ +  - ]:          30 :   pp->reset();
    3743                 :             : 
    3744         [ +  - ]:          30 :   unsigned num_local_results = json_object_array_length(metadata_arr);
    3745                 :             :   
    3746                 :             :   // Query upstream as well
    3747         [ +  - ]:          30 :   debuginfod_client *client = debuginfod_pool_begin();
    3748         [ +  - ]:          30 :   if (client != NULL)
    3749                 :             :   {
    3750         [ +  - ]:          30 :     add_client_federation_headers(client, conn);
    3751                 :             : 
    3752                 :          30 :     int upstream_metadata_fd;
    3753                 :          30 :     char *upstream_metadata_file = NULL;
    3754         [ +  - ]:          30 :     upstream_metadata_fd = debuginfod_find_metadata(client, key.c_str(), (char*)value.c_str(),
    3755                 :             :                                                     &upstream_metadata_file);
    3756         [ +  + ]:          30 :     if (upstream_metadata_fd >= 0) {
    3757                 :             :        /* json-c >= 0.13 has json_object_from_fd(). */
    3758         [ +  - ]:          18 :       json_object *upstream_metadata_json = json_object_from_file(upstream_metadata_file);
    3759                 :          18 :       free (upstream_metadata_file);
    3760                 :          18 :       json_object *upstream_metadata_json_arr;
    3761                 :          18 :       json_object *upstream_complete;
    3762         [ -  + ]:          18 :       if (NULL != upstream_metadata_json &&
    3763   [ +  -  +  -  :          36 :           json_object_object_get_ex(upstream_metadata_json, "results", &upstream_metadata_json_arr) &&
                   -  + ]
    3764         [ +  - ]:          18 :           json_object_object_get_ex(upstream_metadata_json, "complete", &upstream_complete))
    3765                 :             :         {
    3766         [ +  - ]:          18 :           metadata_complete &= json_object_get_boolean(upstream_complete);
    3767   [ +  -  +  + ]:          22 :           for (int i = 0, n = json_object_array_length(upstream_metadata_json_arr); i < n; i++)
    3768                 :             :             {
    3769         [ +  - ]:           4 :               json_object *entry = json_object_array_get_idx(upstream_metadata_json_arr, i);
    3770         [ -  + ]:           4 :               if (verbose > 3)
    3771         [ #  # ]:           0 :                 obatched(clog) << "metadata found remote "
    3772                 :             :                                << json_object_to_json_string_ext(entry,
    3773   [ #  #  #  #  :           0 :                                                                  JSON_C_TO_STRING_PRETTY)
                   #  # ]
    3774                 :           0 :                                << endl;
    3775                 :             :               
    3776         [ +  - ]:           4 :               json_object_get(entry); // increment reference count
    3777         [ +  - ]:           4 :               json_object_array_add(metadata_arr, entry);
    3778                 :             :             }
    3779         [ +  - ]:          18 :           json_object_put(upstream_metadata_json);
    3780                 :             :         }
    3781         [ +  - ]:          18 :       close(upstream_metadata_fd);
    3782                 :             :     }
    3783         [ +  - ]:          30 :     debuginfod_pool_end (client);
    3784                 :             :   }
    3785                 :             : 
    3786         [ +  - ]:          30 :   unsigned num_total_results = json_object_array_length(metadata_arr);
    3787                 :             : 
    3788         [ +  - ]:          30 :   if (verbose > 2)
    3789   [ +  -  +  - ]:          90 :     obatched(clog) << "metadata found local=" << num_local_results
    3790   [ +  -  +  - ]:          30 :                    << " remote=" << (num_total_results-num_local_results)
    3791   [ +  -  +  -  :          30 :                    << " total=" << num_total_results
                   +  - ]
    3792                 :          30 :                    << endl;
    3793                 :             :   
    3794   [ +  -  +  - ]:          30 :   json_object_object_add(metadata, "complete", json_object_new_boolean(metadata_complete));
    3795         [ +  - ]:          30 :   const char* metadata_str = json_object_to_json_string(metadata);
    3796         [ -  + ]:          30 :   if (!metadata_str)
    3797   [ #  #  #  # ]:           0 :     throw libc_exception (ENOMEM, "cannot allocate json");
    3798         [ +  - ]:          30 :   r = MHD_create_response_from_buffer (strlen(metadata_str),
    3799                 :             :                                        (void*) metadata_str,
    3800                 :             :                                        MHD_RESPMEM_MUST_COPY);
    3801                 :          30 :   *size = strlen(metadata_str);
    3802         [ +  - ]:          30 :   if (r)
    3803         [ +  - ]:          30 :     add_mhd_response_header(r, "Content-Type", "application/json");
    3804                 :          30 :   return r;
    3805                 :          30 : }
    3806                 :             : 
    3807                 :             : 
    3808                 :             : static struct MHD_Response*
    3809                 :           4 : handle_root (off_t* size)
    3810                 :             : {
    3811                 :           4 :   MHD_Response* r;
    3812         [ +  - ]:           4 :   if (cust_homepage_file != "")
    3813                 :           4 :     try
    3814                 :             :       {
    3815         [ +  - ]:           4 :         int fd = open (cust_homepage_file.c_str(), O_RDONLY);
    3816         [ +  + ]:           4 :         if (fd != -1) {
    3817                 :           2 :           struct stat buf;
    3818                 :           2 :           stat (cust_homepage_file.c_str(), &buf);
    3819         [ +  - ]:           2 :           r =  MHD_create_response_from_fd(buf.st_size, fd);
    3820                 :             :           // NB: MHD owns and handles the fd from now.  Must not close()!
    3821         [ +  - ]:           2 :           if (r != NULL)
    3822                 :             :             {
    3823                 :           2 :               *size = buf.st_size;
    3824         [ +  - ]:           2 :               add_mhd_response_header (r, "Content-Type", "text/html");
    3825                 :             :             }
    3826                 :             :         } else {
    3827   [ +  -  +  - ]:           4 :           throw libc_exception (errno, "cannot open file " + cust_homepage_file);
    3828                 :             :         }
    3829                 :           2 :         return r;
    3830                 :             :       }
    3831         [ -  + ]:           2 :     catch (const reportable_exception& e)
    3832                 :             :       {
    3833         [ +  - ]:           2 :         e.report(clog);
    3834                 :           2 :       }
    3835                 :             : 
    3836   [ +  -  +  - ]:           4 :   static string version = "debuginfod (" + string (PACKAGE_NAME) + ") "
    3837   [ +  -  +  -  :          66 :                           + string (PACKAGE_VERSION);
             +  -  +  - ]
    3838                 :           2 :   r = MHD_create_response_from_buffer (version.size (),
    3839                 :           2 :                                        (void *) version.c_str (),
    3840                 :             :                                        MHD_RESPMEM_PERSISTENT);
    3841         [ +  - ]:           2 :   if (r != NULL)
    3842                 :             :     {
    3843                 :           2 :       *size = version.size ();
    3844                 :           2 :       add_mhd_response_header (r, "Content-Type", "text/plain");
    3845                 :             :     }
    3846                 :             :   return r;
    3847                 :             : }
    3848                 :             : 
    3849                 :             : 
    3850                 :             : static struct MHD_Response*
    3851                 :           2 : handle_options (off_t* size)
    3852                 :             : {
    3853                 :           2 :   static char empty_body[] = " ";
    3854                 :           2 :   MHD_Response* r = MHD_create_response_from_buffer (1, empty_body,
    3855                 :             :                                                      MHD_RESPMEM_PERSISTENT);
    3856         [ +  - ]:           2 :   if (r != NULL)
    3857                 :             :     {
    3858                 :           2 :       *size = 1;
    3859                 :           2 :       add_mhd_response_header (r, "Access-Control-Allow-Origin", "*");
    3860                 :           2 :       add_mhd_response_header (r, "Access-Control-Allow-Methods", "GET, OPTIONS");
    3861                 :           2 :       add_mhd_response_header (r, "Access-Control-Allow-Headers", "cache-control");
    3862                 :             :     }
    3863                 :           2 :   return r;
    3864                 :             : }
    3865                 :             : 
    3866                 :             : 
    3867                 :             : ////////////////////////////////////////////////////////////////////////
    3868                 :             : 
    3869                 :             : 
    3870                 :             : /* libmicrohttpd callback */
    3871                 :             : static MHD_RESULT
    3872                 :        6769 : handler_cb (void * /*cls*/,
    3873                 :             :             struct MHD_Connection *connection,
    3874                 :             :             const char *url,
    3875                 :             :             const char *method,
    3876                 :             :             const char * /*version*/,
    3877                 :             :             const char * /*upload_data*/,
    3878                 :             :             size_t * /*upload_data_size*/,
    3879                 :             :             void ** ptr)
    3880                 :             : {
    3881                 :        6769 :   struct MHD_Response *r = NULL;
    3882                 :        6769 :   string url_copy = url;
    3883                 :             : 
    3884                 :             :   /* libmicrohttpd always makes (at least) two callbacks: once just
    3885                 :             :      past the headers, and one after the request body is finished
    3886                 :             :      being received.  If we process things early (first callback) and
    3887                 :             :      queue a response, libmicrohttpd would suppress http keep-alive
    3888                 :             :      (via connection->read_closed = true). */
    3889                 :        6770 :   static int aptr; /* just some random object to use as a flag */
    3890         [ +  + ]:        6770 :   if (&aptr != *ptr)
    3891                 :             :     {
    3892                 :             :       /* do never respond on first call */
    3893                 :        3385 :       *ptr = &aptr;
    3894                 :        3385 :       return MHD_YES;
    3895                 :             :     }
    3896                 :        3385 :   *ptr = NULL;                     /* reset when done */
    3897                 :             :   
    3898         [ +  - ]:        3385 :   const char *maxsize_string = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "X-DEBUGINFOD-MAXSIZE");
    3899                 :        3385 :   long maxsize = 0;
    3900   [ +  +  +  - ]:        3385 :   if (maxsize_string != NULL && maxsize_string[0] != '\0')
    3901                 :           2 :     maxsize = atol(maxsize_string);
    3902                 :             :   else
    3903                 :             :     maxsize = 0;
    3904                 :             : 
    3905                 :             : #if MHD_VERSION >= 0x00097002
    3906                 :        3385 :   enum MHD_Result rc;
    3907                 :             : #else
    3908                 :             :   int rc = MHD_NO; // mhd
    3909                 :             : #endif
    3910                 :        3385 :   int http_code = 500;
    3911                 :        3385 :   off_t http_size = -1;
    3912                 :        3385 :   struct timespec ts_start, ts_end;
    3913                 :        3385 :   clock_gettime (CLOCK_MONOTONIC, &ts_start);
    3914                 :        3385 :   double afteryou = 0.0;
    3915         [ +  + ]:        3385 :   string artifacttype, suffix;
    3916                 :        3385 :   string urlargs; // for logging
    3917                 :             : 
    3918                 :        3385 :   try
    3919                 :             :     {
    3920   [ +  +  +  -  :        3385 :       if (webapi_cors && method == string("OPTIONS"))
                   +  + ]
    3921                 :             :         {
    3922   [ +  -  +  -  :           4 :           inc_metric("http_requests_total", "type", method);
             +  -  +  - ]
    3923         [ +  - ]:           2 :           r = handle_options(& http_size);
    3924         [ +  - ]:           2 :           rc = MHD_queue_response (connection, MHD_HTTP_OK, r);
    3925                 :           2 :           http_code = MHD_HTTP_OK;
    3926         [ +  - ]:           2 :           MHD_destroy_response (r);
    3927                 :           2 :           return rc;
    3928                 :             :         }
    3929   [ +  -  -  + ]:        4014 :       else if (string(method) != "GET")
    3930         [ #  # ]:           0 :         throw reportable_exception(400, "we support OPTIONS+GET only");
    3931                 :             : 
    3932                 :             :       /* Start decoding the URL. */
    3933                 :        3383 :       size_t slash1 = url_copy.find('/', 1);
    3934         [ +  - ]:        3383 :       string url1 = url_copy.substr(0, slash1); // ok even if slash1 not found
    3935                 :             : 
    3936   [ +  +  -  + ]:        3383 :       if (slash1 != string::npos && url1 == "/buildid")
    3937                 :             :         {
    3938                 :             :           // PR27863: block this thread awhile if another thread is already busy
    3939                 :             :           // fetching the exact same thing.  This is better for Everyone.
    3940                 :             :           // The latecomer says "... after you!" and waits.
    3941   [ +  -  +  -  :        5813 :           add_metric ("thread_busy", "role", "http-buildid-after-you", 1);
             +  -  +  - ]
    3942                 :             : #ifdef HAVE_PTHREAD_SETNAME_NP
    3943                 :        2595 :           (void) pthread_setname_np (pthread_self(), "mhd-buildid-after-you");
    3944                 :             : #endif
    3945                 :        2595 :           struct timespec tsay_start, tsay_end;
    3946                 :        2595 :           clock_gettime (CLOCK_MONOTONIC, &tsay_start);
    3947   [ +  +  +  - ]:        2655 :           static unique_set<string> busy_urls;
    3948         [ +  - ]:        2595 :           unique_set_reserver<string> after_you(busy_urls, url_copy);
    3949                 :        2595 :           clock_gettime (CLOCK_MONOTONIC, &tsay_end);
    3950                 :        2595 :           afteryou = (tsay_end.tv_sec - tsay_start.tv_sec) + (tsay_end.tv_nsec - tsay_start.tv_nsec)/1.e9;
    3951   [ +  -  +  -  :        5190 :           add_metric ("thread_busy", "role", "http-buildid-after-you", -1);
             +  -  +  - ]
    3952                 :             :           
    3953   [ +  -  +  -  :        5190 :           tmp_inc_metric m ("thread_busy", "role", "http-buildid");
             +  -  +  - ]
    3954                 :             : #ifdef HAVE_PTHREAD_SETNAME_NP
    3955                 :        2595 :           (void) pthread_setname_np (pthread_self(), "mhd-buildid");
    3956                 :             : #endif
    3957                 :        2595 :           size_t slash2 = url_copy.find('/', slash1+1);
    3958         [ -  + ]:        2595 :           if (slash2 == string::npos)
    3959         [ #  # ]:           0 :             throw reportable_exception("/buildid/ webapi error, need buildid");
    3960                 :             : 
    3961         [ +  - ]:        2595 :           string buildid = url_copy.substr(slash1+1, slash2-slash1-1);
    3962                 :             : 
    3963                 :        2595 :           size_t slash3 = url_copy.find('/', slash2+1);
    3964                 :             : 
    3965         [ +  + ]:        2595 :           if (slash3 == string::npos)
    3966                 :             :             {
    3967         [ +  - ]:        1461 :               artifacttype = url_copy.substr(slash2+1);
    3968         [ +  - ]:        1461 :               suffix = "";
    3969                 :             :             }
    3970                 :             :           else
    3971                 :             :             {
    3972         [ +  - ]:        1134 :               artifacttype = url_copy.substr(slash2+1, slash3-slash2-1);
    3973         [ +  - ]:        1757 :               suffix = url_copy.substr(slash3); // include the slash in the suffix
    3974                 :             :             }
    3975                 :             : 
    3976                 :             :           // get the resulting fd so we can report its size
    3977                 :        2595 :           int fd;
    3978         [ +  + ]:        2595 :           r = handle_buildid (connection, buildid, artifacttype, suffix, &fd);
    3979                 :        1972 :           if (r)
    3980                 :             :             {
    3981                 :        1972 :               struct stat fs;
    3982         [ +  - ]:        1972 :               if (fstat(fd, &fs) == 0)
    3983                 :        1972 :                 http_size = fs.st_size;
    3984                 :             :               // libmicrohttpd will close (fd);
    3985                 :             :             }
    3986                 :        3218 :         }
    3987         [ +  + ]:         788 :       else if (url1 == "/metrics")
    3988                 :             :         {
    3989   [ +  -  +  -  :        1496 :           tmp_inc_metric m ("thread_busy", "role", "http-metrics");
             +  -  +  - ]
    3990         [ +  - ]:         748 :           artifacttype = "metrics";
    3991   [ +  -  +  -  :        1496 :           inc_metric("http_requests_total", "type", artifacttype);
                   +  - ]
    3992         [ +  - ]:         748 :           r = handle_metrics(& http_size);
    3993                 :         748 :         }
    3994         [ +  + ]:          40 :       else if (url1 == "/metadata")
    3995                 :             :         {
    3996   [ +  -  +  -  :          60 :           tmp_inc_metric m ("thread_busy", "role", "http-metadata");
             +  -  +  - ]
    3997         [ +  - ]:          30 :           const char* key = MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND, "key");
    3998         [ +  - ]:          30 :           const char* value = MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND, "value");
    3999         [ -  + ]:          30 :           if (NULL == value || NULL == key)
    4000         [ #  # ]:           0 :             throw reportable_exception("/metadata webapi error, need key and value");
    4001                 :             : 
    4002   [ +  -  +  -  :          30 :           urlargs = string("?key=") + string(key) + string("&value=") + string(value); // apprx., for logging
          +  -  +  -  +  
             -  +  -  +  
                      - ]
    4003         [ +  - ]:          30 :           artifacttype = "metadata";
    4004   [ +  -  +  -  :          60 :           inc_metric("http_requests_total", "type", artifacttype);
                   +  - ]
    4005   [ +  -  +  -  :          30 :           r = handle_metadata(connection, key, value, &http_size);
                   +  - ]
    4006                 :          30 :         }
    4007         [ +  + ]:          10 :       else if (url1 == "/")
    4008                 :             :         {
    4009         [ +  - ]:           4 :           artifacttype = "/";
    4010   [ +  -  +  -  :         639 :           inc_metric("http_requests_total", "type", artifacttype);
                   +  - ]
    4011         [ +  - ]:           4 :           r = handle_root(& http_size);
    4012                 :             :         }
    4013                 :             :       else
    4014   [ +  -  +  - ]:          12 :         throw reportable_exception("webapi error, unrecognized '" + url1 + "'");
    4015                 :             : 
    4016         [ -  + ]:        2754 :       if (r == 0)
    4017         [ #  # ]:           0 :         throw reportable_exception("internal error, missing response");
    4018                 :             : 
    4019   [ +  +  +  - ]:        2754 :       if (maxsize > 0 && http_size > maxsize)
    4020                 :             :         {
    4021         [ +  - ]:           2 :           MHD_destroy_response(r);
    4022   [ +  -  +  - ]:           4 :           throw reportable_exception(406, "File too large, max size=" + std::to_string(maxsize));
    4023                 :             :         }
    4024                 :             : 
    4025         [ +  + ]:        2752 :       if (webapi_cors)
    4026                 :             :         // add ACAO header for all successful requests
    4027         [ +  - ]:         132 :         add_mhd_response_header (r, "Access-Control-Allow-Origin", "*");
    4028   [ +  +  +  + ]:        2752 :       if ((cust_homepage_redirect) != "" && (url1 == "/"))
    4029                 :             :         {
    4030                 :             :           // redirect to given custom --homepage
    4031         [ +  - ]:           4 :           MHD_add_response_header(r, "Location", cust_homepage_redirect.c_str());
    4032         [ +  - ]:           4 :           rc = MHD_queue_response (connection, MHD_HTTP_FOUND, r);
    4033                 :             :           http_code = MHD_HTTP_FOUND;
    4034                 :             :         }
    4035                 :             :       else
    4036                 :             :         {
    4037         [ +  - ]:        2748 :           rc = MHD_queue_response (connection, MHD_HTTP_OK, r);
    4038                 :             :           http_code = MHD_HTTP_OK;
    4039                 :             :         }
    4040         [ +  - ]:        2752 :       MHD_destroy_response (r);
    4041                 :        3383 :     }
    4042         [ -  + ]:         631 :   catch (const reportable_exception& e)
    4043                 :             :     {
    4044   [ +  -  +  -  :        1262 :       inc_metric("http_responses_total","result","error");
             +  -  +  - ]
    4045         [ +  - ]:         631 :       e.report(clog);
    4046                 :         631 :       http_code = e.code;
    4047         [ +  - ]:         631 :       http_size = e.message.size();
    4048         [ +  - ]:         631 :       rc = e.mhd_send_response (connection);
    4049                 :         631 :     }
    4050                 :             : 
    4051                 :        3383 :   clock_gettime (CLOCK_MONOTONIC, &ts_end);
    4052                 :        3383 :   double deltas = (ts_end.tv_sec - ts_start.tv_sec) + (ts_end.tv_nsec - ts_start.tv_nsec)/1.e9;
    4053                 :             :   // afteryou: delay waiting for other client's identical query to complete
    4054                 :             :   // deltas: total latency, including afteryou waiting
    4055   [ +  -  +  - ]:        6766 :   obatched(clog) << conninfo(connection)
    4056                 :             :                  << ' ' << method << ' ' << url << urlargs
    4057   [ +  -  +  -  :        3383 :                  << ' ' << http_code << ' ' << http_size
          +  -  +  -  +  
          -  +  -  +  -  
                   +  - ]
    4058   [ +  -  +  -  :        3383 :                  << ' ' << (int)(afteryou*1000) << '+' << (int)((deltas-afteryou)*1000) << "ms"
          +  -  +  -  +  
             -  +  -  +  
                      - ]
    4059                 :        3383 :                  << endl;
    4060                 :             : 
    4061                 :             :   // related prometheus metrics
    4062                 :        3383 :   string http_code_str = to_string(http_code);
    4063   [ +  -  +  -  :        6766 :   add_metric("http_responses_transfer_bytes_sum",
             +  -  +  - ]
    4064                 :             :              "code", http_code_str, "type", artifacttype, http_size);
    4065   [ +  -  +  -  :        6766 :   inc_metric("http_responses_transfer_bytes_count",
             +  -  +  - ]
    4066                 :             :              "code", http_code_str, "type", artifacttype);
    4067                 :             : 
    4068   [ +  -  +  -  :        6766 :   add_metric("http_responses_duration_milliseconds_sum",
             +  -  +  - ]
    4069                 :             :              "code", http_code_str, "type", artifacttype, deltas*1000); // prometheus prefers _seconds and floating point
    4070   [ +  -  +  -  :        6766 :   inc_metric("http_responses_duration_milliseconds_count",
             +  -  +  - ]
    4071                 :             :              "code", http_code_str, "type", artifacttype);
    4072                 :             : 
    4073   [ +  -  +  -  :        6766 :   add_metric("http_responses_after_you_milliseconds_sum",
             +  -  +  - ]
    4074                 :             :              "code", http_code_str, "type", artifacttype, afteryou*1000);
    4075   [ +  -  +  -  :        6766 :   inc_metric("http_responses_after_you_milliseconds_count",
             +  -  +  - ]
    4076                 :             :              "code", http_code_str, "type", artifacttype);
    4077                 :             : 
    4078                 :        3383 :   return rc;
    4079                 :       13538 : }
    4080                 :             : 
    4081                 :             : 
    4082                 :             : ////////////////////////////////////////////////////////////////////////
    4083                 :             : // borrowed originally from src/nm.c get_local_names()
    4084                 :             : 
    4085                 :             : static void
    4086                 :         412 : dwarf_extract_source_paths (Elf *elf, set<string>& debug_sourcefiles)
    4087                 :             :   noexcept // no exceptions - so we can simplify the altdbg resource release at end
    4088                 :             : {
    4089                 :         412 :   Dwarf* dbg = dwarf_begin_elf (elf, DWARF_C_READ, NULL);
    4090         [ -  + ]:         412 :   if (dbg == NULL)
    4091                 :           0 :     return;
    4092                 :             : 
    4093                 :         412 :   Dwarf* altdbg = NULL;
    4094                 :         412 :   int    altdbg_fd = -1;
    4095                 :             : 
    4096                 :             :   // DWZ handling: if we have an unsatisfied debug-alt-link, add an
    4097                 :             :   // empty string into the outgoing sourcefiles set, so the caller
    4098                 :             :   // should know that our data is incomplete.
    4099                 :         412 :   const char *alt_name_p;
    4100                 :         412 :   const void *alt_build_id; // elfutils-owned memory
    4101                 :         412 :   ssize_t sz = dwelf_dwarf_gnu_debugaltlink (dbg, &alt_name_p, &alt_build_id);
    4102         [ +  + ]:         412 :   if (sz > 0) // got one!
    4103                 :             :     {
    4104                 :         200 :       string buildid;
    4105                 :         200 :       unsigned char* build_id_bytes = (unsigned char*) alt_build_id;
    4106         [ +  + ]:        4200 :       for (ssize_t idx=0; idx<sz; idx++)
    4107                 :             :         {
    4108                 :        4000 :           buildid += "0123456789abcdef"[build_id_bytes[idx] >> 4];
    4109                 :        4000 :           buildid += "0123456789abcdef"[build_id_bytes[idx] & 0xf];
    4110                 :             :         }
    4111                 :             : 
    4112         [ +  + ]:         200 :       if (verbose > 3)
    4113                 :         156 :         obatched(clog) << "Need altdebug buildid=" << buildid << endl;
    4114                 :             : 
    4115                 :             :       // but is it unsatisfied the normal elfutils ways?
    4116                 :         200 :       Dwarf* alt = dwarf_getalt (dbg);
    4117         [ +  - ]:         200 :       if (alt == NULL)
    4118                 :             :         {
    4119                 :             :           // Yup, unsatisfied the normal way.  Maybe we can satisfy it
    4120                 :             :           // from our own debuginfod database.
    4121                 :         200 :           int alt_fd;
    4122                 :         200 :           struct MHD_Response *r = 0;
    4123                 :         200 :           try
    4124                 :             :             {
    4125         [ +  - ]:         200 :               string artifacttype = "debuginfo";
    4126   [ +  -  +  + ]:         220 :               r = handle_buildid (0, buildid, artifacttype, "", &alt_fd);
    4127                 :             :               // NB: no need for ACAO etc. headers; this is not getting sent to a client 
    4128                 :          20 :             }
    4129         [ -  + ]:          20 :           catch (const reportable_exception& e)
    4130                 :             :             {
    4131                 :             :               // swallow exceptions
    4132                 :          20 :             }
    4133                 :             : 
    4134                 :             :           // NB: this is not actually recursive!  This invokes the web-query
    4135                 :             :           // path, which cannot get back into the scan code paths.
    4136                 :         200 :           if (r)
    4137                 :             :             {
    4138                 :             :               // Found it!
    4139                 :         180 :               altdbg_fd = dup(alt_fd); // ok if this fails, downstream failures ok
    4140                 :         180 :               alt = altdbg = dwarf_begin (altdbg_fd, DWARF_C_READ);
    4141                 :             :               // NB: must close this dwarf and this fd at the bottom of the function!
    4142                 :         180 :               MHD_destroy_response (r); // will close alt_fd
    4143         [ -  + ]:         180 :               if (alt)
    4144                 :         180 :                 dwarf_setalt (dbg, alt);
    4145                 :             :             }
    4146                 :             :         }
    4147                 :             :       else
    4148                 :             :         {
    4149                 :             :           // NB: dwarf_setalt(alt) inappropriate - already done!
    4150                 :             :           // NB: altdbg will stay 0 so nothing tries to redundantly dealloc.
    4151                 :             :         }
    4152                 :             : 
    4153         [ +  + ]:         200 :       if (alt)
    4154                 :             :         {
    4155         [ +  + ]:         180 :           if (verbose > 3)
    4156                 :         156 :             obatched(clog) << "Resolved altdebug buildid=" << buildid << endl;
    4157                 :             :         }
    4158                 :             :       else // (alt == NULL) - signal possible presence of poor debuginfo
    4159                 :             :         {
    4160                 :          20 :           debug_sourcefiles.insert("");
    4161         [ +  - ]:          20 :           if (verbose > 3)
    4162                 :           0 :             obatched(clog) << "Unresolved altdebug buildid=" << buildid << endl;
    4163                 :             :         }
    4164                 :         200 :     }
    4165                 :             : 
    4166                 :         412 :   Dwarf_Off offset = 0;
    4167                 :         412 :   Dwarf_Off old_offset;
    4168                 :         412 :   size_t hsize;
    4169                 :             : 
    4170         [ +  + ]:        9436 :   while (dwarf_nextcu (dbg, old_offset = offset, &offset, &hsize, NULL, NULL, NULL) == 0)
    4171                 :             :     {
    4172                 :        9024 :       Dwarf_Die cudie_mem;
    4173                 :        9024 :       Dwarf_Die *cudie = dwarf_offdie (dbg, old_offset + hsize, &cudie_mem);
    4174                 :             : 
    4175         [ -  + ]:        9023 :       if (cudie == NULL)
    4176                 :          36 :         continue;
    4177         [ +  + ]:        9023 :       if (dwarf_tag (cudie) != DW_TAG_compile_unit)
    4178                 :          36 :         continue;
    4179                 :             : 
    4180         [ -  + ]:        8988 :       const char *cuname = dwarf_diename(cudie) ?: "unknown";
    4181                 :             : 
    4182                 :        8988 :       Dwarf_Files *files;
    4183                 :        8988 :       size_t nfiles;
    4184         [ -  + ]:        8988 :       if (dwarf_getsrcfiles (cudie, &files, &nfiles) != 0)
    4185                 :           0 :         continue;
    4186                 :             : 
    4187                 :             :       // extract DW_AT_comp_dir to resolve relative file names
    4188                 :        8988 :       const char *comp_dir = "";
    4189                 :        8988 :       const char *const *dirs;
    4190                 :        8988 :       size_t ndirs;
    4191         [ -  + ]:        8988 :       if (dwarf_getsrcdirs (files, &dirs, &ndirs) == 0 &&
    4192         [ -  + ]:        8988 :           dirs[0] != NULL)
    4193                 :             :         comp_dir = dirs[0];
    4194                 :             :       if (comp_dir == NULL)
    4195                 :             :         comp_dir = "";
    4196                 :             : 
    4197         [ +  + ]:        8988 :       if (verbose > 3)
    4198                 :       13812 :         obatched(clog) << "searching for sources for cu=" << cuname << " comp_dir=" << comp_dir
    4199                 :        6906 :                        << " #files=" << nfiles << " #dirs=" << ndirs << endl;
    4200                 :             : 
    4201   [ -  +  -  - ]:        8988 :       if (comp_dir[0] == '\0' && cuname[0] != '/')
    4202                 :             :         {
    4203         [ #  # ]:           0 :           if (verbose > 3)
    4204                 :           0 :             obatched(clog) << "skipping cu=" << cuname << " due to empty comp_dir" << endl;
    4205                 :           0 :           continue;
    4206                 :             :         }
    4207                 :             : 
    4208         [ +  + ]:      147201 :       for (size_t f = 1; f < nfiles; f++)
    4209                 :             :         {
    4210                 :      138213 :           const char *hat = dwarf_filesrc (files, f, NULL, NULL);
    4211         [ -  + ]:      138244 :           if (hat == NULL)
    4212                 :        1690 :             continue;
    4213                 :             : 
    4214         [ +  + ]:      138265 :           if (string(hat) == "<built-in>"
    4215   [ +  +  +  + ]:      276501 :               || string_endswith(hat, "<built-in>")) // gcc intrinsics, don't bother record
    4216                 :        1690 :             continue;
    4217                 :             : 
    4218         [ +  + ]:      136575 :           string waldo;
    4219         [ +  + ]:      136575 :           if (hat[0] == '/') // absolute
    4220                 :       85815 :             waldo = (string (hat));
    4221         [ +  - ]:       50760 :           else if (comp_dir[0] != '\0') // comp_dir relative
    4222                 :       50760 :             waldo = (string (comp_dir) + string("/") + string (hat));
    4223                 :             :           else
    4224                 :             :            {
    4225         [ #  # ]:           0 :              if (verbose > 3)
    4226                 :           0 :                obatched(clog) << "skipping hat=" << hat << " due to empty comp_dir" << endl;
    4227                 :           0 :              continue;
    4228                 :             :            }
    4229                 :             : 
    4230                 :             :           // NB: this is the 'waldo' that a dbginfo client will have
    4231                 :             :           // to supply for us to give them the file The comp_dir
    4232                 :             :           // prefixing is a definite complication.  Otherwise we'd
    4233                 :             :           // have to return a setof comp_dirs (one per CU!) with
    4234                 :             :           // corresponding filesrc[] names, instead of one absolute
    4235                 :             :           // resoved set.  Maybe we'll have to do that anyway.  XXX
    4236                 :             : 
    4237         [ +  + ]:      136586 :           if (verbose > 4)
    4238         [ -  + ]:          32 :             obatched(clog) << waldo
    4239         [ -  + ]:          16 :                            << (debug_sourcefiles.find(waldo)==debug_sourcefiles.end() ? " new" : " dup") <<  endl;
    4240                 :             : 
    4241                 :      136586 :           debug_sourcefiles.insert (waldo);
    4242                 :      136523 :         }
    4243                 :             :     }
    4244                 :             : 
    4245                 :         412 :   dwarf_end(dbg);
    4246         [ +  + ]:         412 :   if (altdbg)
    4247                 :         180 :     dwarf_end(altdbg);
    4248         [ +  + ]:         412 :   if (altdbg_fd >= 0)
    4249                 :         180 :     close(altdbg_fd);
    4250                 :             : }
    4251                 :             : 
    4252                 :             : 
    4253                 :             : 
    4254                 :             : static void
    4255                 :        1708 : elf_classify (int fd, bool &executable_p, bool &debuginfo_p, string &buildid, set<string>& debug_sourcefiles)
    4256                 :             : {
    4257                 :        1708 :   Elf *elf = elf_begin (fd, ELF_C_READ_MMAP_PRIVATE, NULL);
    4258         [ +  - ]:        1708 :   if (elf == NULL)
    4259                 :             :     return;
    4260                 :             : 
    4261                 :        1708 :   try // catch our types of errors and clean up the Elf* object
    4262                 :             :     {
    4263   [ +  -  +  + ]:        1708 :       if (elf_kind (elf) != ELF_K_ELF)
    4264                 :             :         {
    4265         [ +  - ]:         906 :           elf_end (elf);
    4266                 :         946 :           return;
    4267                 :             :         }
    4268                 :             : 
    4269                 :         802 :       GElf_Ehdr ehdr_storage;
    4270         [ +  - ]:         802 :       GElf_Ehdr *ehdr = gelf_getehdr (elf, &ehdr_storage);
    4271         [ -  + ]:         802 :       if (ehdr == NULL)
    4272                 :             :         {
    4273         [ #  # ]:           0 :           elf_end (elf);
    4274                 :             :           return;
    4275                 :             :         }
    4276                 :         802 :       auto elf_type = ehdr->e_type;
    4277                 :             : 
    4278                 :         802 :       const void *build_id; // elfutils-owned memory
    4279         [ +  - ]:         802 :       ssize_t sz = dwelf_elf_gnu_build_id (elf, & build_id);
    4280         [ +  + ]:         799 :       if (sz <= 0)
    4281                 :             :         {
    4282                 :             :           // It's not a diagnostic-worthy error for an elf file to lack build-id.
    4283                 :             :           // It might just be very old.
    4284         [ +  - ]:          40 :           elf_end (elf);
    4285                 :             :           return;
    4286                 :             :         }
    4287                 :             : 
    4288                 :             :       // build_id is a raw byte array; convert to hexadecimal *lowercase*
    4289                 :         759 :       unsigned char* build_id_bytes = (unsigned char*) build_id;
    4290         [ +  + ]:       15948 :       for (ssize_t idx=0; idx<sz; idx++)
    4291                 :             :         {
    4292         [ +  - ]:       15186 :           buildid += "0123456789abcdef"[build_id_bytes[idx] >> 4];
    4293         [ +  - ]:       30380 :           buildid += "0123456789abcdef"[build_id_bytes[idx] & 0xf];
    4294                 :             :         }
    4295                 :             : 
    4296                 :             :       // now decide whether it's an executable - namely, any allocatable section has
    4297                 :             :       // PROGBITS;
    4298         [ +  + ]:         762 :       if (elf_type == ET_EXEC || elf_type == ET_DYN)
    4299                 :             :         {
    4300                 :         700 :           size_t shnum;
    4301         [ +  - ]:         700 :           int rc = elf_getshdrnum (elf, &shnum);
    4302         [ -  + ]:         699 :           if (rc < 0)
    4303   [ #  #  #  # ]:           0 :             throw elfutils_exception(rc, "getshdrnum");
    4304                 :             : 
    4305                 :         699 :           executable_p = false;
    4306         [ +  + ]:       13338 :           for (size_t sc = 0; sc < shnum; sc++)
    4307                 :             :             {
    4308         [ +  - ]:       13012 :               Elf_Scn *scn = elf_getscn (elf, sc);
    4309         [ -  + ]:       13011 :               if (scn == NULL)
    4310                 :           0 :                 continue;
    4311                 :             : 
    4312                 :       13011 :               GElf_Shdr shdr_mem;
    4313         [ +  - ]:       13011 :               GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
    4314         [ -  + ]:       13013 :               if (shdr == NULL)
    4315                 :           0 :                 continue;
    4316                 :             : 
    4317                 :             :               // allocated (loadable / vm-addr-assigned) section with available content?
    4318   [ +  +  +  + ]:       13013 :               if ((shdr->sh_type == SHT_PROGBITS) && (shdr->sh_flags & SHF_ALLOC))
    4319                 :             :                 {
    4320         [ -  + ]:         374 :                   if (verbose > 4)
    4321   [ #  #  #  #  :           0 :                     obatched(clog) << "executable due to SHF_ALLOC SHT_PROGBITS sc=" << sc << endl;
                   #  # ]
    4322                 :         374 :                   executable_p = true;
    4323                 :         374 :                   break; // no need to keep looking for others
    4324                 :             :                 }
    4325                 :             :             } // iterate over sections
    4326                 :             :         } // executable_p classification
    4327                 :             : 
    4328                 :             :       // now decide whether it's a debuginfo - namely, if it has any .debug* or .zdebug* sections
    4329                 :             :       // logic mostly stolen from fweimer@redhat.com's elfclassify drafts
    4330                 :         762 :       size_t shstrndx;
    4331         [ +  - ]:         762 :       int rc = elf_getshdrstrndx (elf, &shstrndx);
    4332         [ -  + ]:         762 :       if (rc < 0)
    4333   [ #  #  #  # ]:           0 :         throw elfutils_exception(rc, "getshdrstrndx");
    4334                 :             : 
    4335                 :             :       Elf_Scn *scn = NULL;
    4336                 :             :       bool symtab_p = false;
    4337                 :             :       bool bits_alloc_p = false;
    4338                 :       40866 :       while (true)
    4339                 :             :         {
    4340         [ +  - ]:       20814 :           scn = elf_nextscn (elf, scn);
    4341         [ +  + ]:       20684 :           if (scn == NULL)
    4342                 :             :             break;
    4343                 :       20334 :           GElf_Shdr shdr_storage;
    4344         [ +  - ]:       20334 :           GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_storage);
    4345         [ +  - ]:       20331 :           if (shdr == NULL)
    4346                 :             :             break;
    4347         [ +  - ]:       20331 :           const char *section_name = elf_strptr (elf, shstrndx, shdr->sh_name);
    4348         [ +  - ]:       20464 :           if (section_name == NULL)
    4349                 :             :             break;
    4350         [ +  + ]:       20464 :           if (startswith (section_name, ".debug_line") ||
    4351         [ -  + ]:       20052 :               startswith (section_name, ".zdebug_line"))
    4352                 :             :             {
    4353                 :         412 :               debuginfo_p = true;
    4354         [ +  - ]:         412 :               if (scan_source_info)
    4355                 :         412 :                 dwarf_extract_source_paths (elf, debug_sourcefiles);
    4356                 :             :               break; // expecting only one .*debug_line, so no need to look for others
    4357                 :             :             }
    4358         [ +  + ]:       20052 :           else if (startswith (section_name, ".debug_") ||
    4359         [ +  - ]:       18688 :                    startswith (section_name, ".zdebug_"))
    4360                 :             :             {
    4361                 :        1250 :               debuginfo_p = true;
    4362                 :             :               // NB: don't break; need to parse .debug_line for sources
    4363                 :             :             }
    4364         [ +  + ]:       18802 :           else if (shdr->sh_type == SHT_SYMTAB)
    4365                 :             :             {
    4366                 :             :               symtab_p = true;
    4367                 :             :             }
    4368                 :       18778 :           else if (shdr->sh_type != SHT_NOBITS
    4369         [ +  + ]:       18778 :                    && shdr->sh_type != SHT_NOTE
    4370         [ +  + ]:        9188 :                    && (shdr->sh_flags & SHF_ALLOC) != 0)
    4371                 :             :             {
    4372                 :       20052 :               bits_alloc_p = true;
    4373                 :             :             }
    4374                 :       20052 :         }
    4375                 :             : 
    4376                 :             :       // For more expansive elf/split-debuginfo classification, we
    4377                 :             :       // want to identify as debuginfo "strip -s"-produced files
    4378                 :             :       // without .debug_info* (like libicudata), but we don't want to
    4379                 :             :       // identify "strip -g" executables (with .symtab left there).
    4380         [ -  + ]:         762 :       if (symtab_p && !bits_alloc_p)
    4381                 :           0 :         debuginfo_p = true;
    4382                 :             :     }
    4383         [ #  # ]:           0 :   catch (const reportable_exception& e)
    4384                 :             :     {
    4385         [ #  # ]:           0 :       e.report(clog);
    4386                 :           0 :     }
    4387                 :         762 :   elf_end (elf);
    4388                 :             : }
    4389                 :             : 
    4390                 :             : 
    4391                 :             : // Intern the given file name in two parts (dirname & basename) and
    4392                 :             : // return the resulting file's id.
    4393                 :             : static int64_t
    4394                 :       34057 : register_file_name(sqlite_ps& ps_upsert_fileparts,
    4395                 :             :                    sqlite_ps& ps_upsert_file,
    4396                 :             :                    sqlite_ps& ps_lookup_file,
    4397                 :             :                    const string& name)
    4398                 :             : {
    4399                 :       34057 :   std::size_t slash = name.rfind('/');
    4400         [ +  + ]:       34058 :   string dirname, filename;
    4401         [ +  + ]:       34058 :   if (slash == std::string::npos)
    4402                 :             :     {
    4403         [ +  - ]:          90 :       dirname = "";
    4404         [ +  - ]:          90 :       filename = name;
    4405                 :             :     }
    4406                 :             :   else
    4407                 :             :     {
    4408         [ +  - ]:       33968 :       dirname = name.substr(0, slash);
    4409         [ +  - ]:       33972 :       filename = name.substr(slash+1);
    4410                 :             :     }
    4411                 :             :   // NB: see also handle_metadata()
    4412                 :             : 
    4413                 :             :   // intern the two substrings
    4414                 :       34061 :   ps_upsert_fileparts
    4415         [ +  - ]:       34061 :     .reset()
    4416         [ +  - ]:       34059 :     .bind(1, dirname)
    4417         [ +  - ]:       34045 :     .step_ok_done();
    4418                 :       34062 :   ps_upsert_fileparts
    4419         [ +  - ]:       34062 :     .reset()
    4420         [ +  - ]:       34062 :     .bind(1, filename)
    4421         [ +  - ]:       34060 :     .step_ok_done();
    4422                 :             : 
    4423                 :             :   // intern the tuple
    4424                 :       34062 :   ps_upsert_file
    4425         [ +  - ]:       34062 :     .reset()
    4426         [ +  - ]:       34062 :     .bind(1, dirname)
    4427         [ +  - ]:       34059 :     .bind(2, filename)
    4428         [ +  - ]:       34056 :     .step_ok_done();
    4429                 :             : 
    4430                 :             :   // look up the tuple's id
    4431                 :       34062 :   ps_lookup_file
    4432         [ +  - ]:       34062 :     .reset()
    4433         [ +  - ]:       34062 :     .bind(1, dirname)
    4434         [ +  - ]:       34056 :     .bind(2, filename);
    4435         [ +  - ]:       34057 :   int rc = ps_lookup_file.step();
    4436   [ -  +  -  -  :       34062 :   if (rc != SQLITE_ROW) throw sqlite_exception(rc, "step");
                   -  - ]
    4437                 :             :   
    4438         [ +  - ]:       34062 :   int64_t id = sqlite3_column_int64 (ps_lookup_file, 0);
    4439         [ +  - ]:       34060 :   ps_lookup_file.reset();
    4440                 :       34062 :   return id;
    4441                 :       34062 : }
    4442                 :             : 
    4443                 :             : 
    4444                 :             : 
    4445                 :             : static void
    4446                 :        1106 : scan_source_file (const string& rps, const stat_t& st,
    4447                 :             :                   sqlite_ps& ps_upsert_buildids,
    4448                 :             :                   sqlite_ps& ps_upsert_fileparts,
    4449                 :             :                   sqlite_ps& ps_upsert_file,
    4450                 :             :                   sqlite_ps& ps_lookup_file,
    4451                 :             :                   sqlite_ps& ps_upsert_de,
    4452                 :             :                   sqlite_ps& ps_upsert_s,
    4453                 :             :                   sqlite_ps& ps_query,
    4454                 :             :                   sqlite_ps& ps_scan_done,
    4455                 :             :                   unsigned& fts_cached,
    4456                 :             :                   unsigned& fts_executable,
    4457                 :             :                   unsigned& fts_debuginfo,
    4458                 :             :                   unsigned& fts_sourcefiles)
    4459                 :             : {
    4460                 :        1106 :   int64_t fileid = register_file_name(ps_upsert_fileparts, ps_upsert_file, ps_lookup_file, rps);
    4461                 :             : 
    4462                 :             :   /* See if we know of it already. */
    4463                 :        1106 :   int rc = ps_query
    4464                 :        1106 :     .reset()
    4465                 :        1106 :     .bind(1, fileid)
    4466                 :        1105 :     .bind(2, st.st_mtime)
    4467                 :        1106 :     .step();
    4468                 :        1106 :   ps_query.reset();
    4469         [ +  + ]:        1106 :   if (rc == SQLITE_ROW) // i.e., a result, as opposed to DONE (no results)
    4470                 :             :     // no need to recheck a file/version we already know
    4471                 :             :     // specifically, no need to elf-begin a file we already determined is non-elf
    4472                 :             :     // (so is stored with buildid=NULL)
    4473                 :             :     {
    4474                 :         438 :       fts_cached++;
    4475                 :         438 :       return;
    4476                 :             :     }
    4477                 :             : 
    4478                 :         668 :   bool executable_p = false, debuginfo_p = false; // E and/or D
    4479         [ +  - ]:         668 :   string buildid;
    4480         [ +  - ]:         668 :   set<string> sourcefiles;
    4481                 :             : 
    4482         [ +  - ]:         668 :   int fd = open (rps.c_str(), O_RDONLY);
    4483                 :         668 :   try
    4484                 :             :     {
    4485         [ +  - ]:         668 :       if (fd >= 0)
    4486         [ +  - ]:         668 :         elf_classify (fd, executable_p, debuginfo_p, buildid, sourcefiles);
    4487                 :             :       else
    4488   [ #  #  #  #  :           0 :         throw libc_exception(errno, string("open ") + rps);
                   #  # ]
    4489   [ +  -  +  -  :        1336 :       add_metric ("scanned_bytes_total","source","file",
                   +  - ]
    4490         [ +  - ]:         668 :                   st.st_size);
    4491   [ +  -  +  -  :        1336 :       inc_metric ("scanned_files_total","source","file");
             +  -  +  - ]
    4492                 :             :     }
    4493                 :             :   // NB: we catch exceptions here too, so that we can
    4494                 :             :   // cache the corrupt-elf case (!executable_p &&
    4495                 :             :   // !debuginfo_p) just below, just as if we had an
    4496                 :             :   // EPERM error from open(2).
    4497         [ -  - ]:           0 :   catch (const reportable_exception& e)
    4498                 :             :     {
    4499         [ -  - ]:           0 :       e.report(clog);
    4500                 :           0 :     }
    4501                 :             : 
    4502         [ +  - ]:         668 :   if (fd >= 0)
    4503         [ +  - ]:         668 :     close (fd);
    4504                 :             : 
    4505         [ +  + ]:         668 :   if (buildid == "")
    4506                 :             :     {
    4507                 :             :       // no point storing an elf file without buildid
    4508                 :         572 :       executable_p = false;
    4509                 :         572 :       debuginfo_p = false;
    4510                 :             :     }
    4511                 :             :   else
    4512                 :             :     {
    4513                 :             :       // register this build-id in the interning table
    4514                 :          96 :       ps_upsert_buildids
    4515         [ +  - ]:          96 :         .reset()
    4516         [ +  - ]:          96 :         .bind(1, buildid)
    4517         [ +  - ]:          96 :         .step_ok_done();
    4518                 :             :     }
    4519                 :             : 
    4520         [ +  + ]:         668 :   if (executable_p)
    4521                 :          72 :     fts_executable ++;
    4522         [ +  + ]:         668 :   if (debuginfo_p)
    4523                 :          72 :     fts_debuginfo ++;
    4524   [ +  +  +  + ]:         668 :   if (executable_p || debuginfo_p)
    4525                 :             :     {
    4526                 :          96 :       ps_upsert_de
    4527         [ +  - ]:          96 :         .reset()
    4528         [ +  - ]:          96 :         .bind(1, buildid)
    4529   [ +  +  +  - ]:         120 :         .bind(2, debuginfo_p ? 1 : 0)
    4530   [ +  +  +  - ]:         120 :         .bind(3, executable_p ? 1 : 0)
    4531         [ +  - ]:          96 :         .bind(4, fileid)
    4532         [ +  - ]:          96 :         .bind(5, st.st_mtime)
    4533         [ +  - ]:          96 :         .step_ok_done();
    4534                 :             :     }
    4535         [ +  + ]:         668 :   if (executable_p)
    4536   [ +  -  +  -  :         144 :     inc_metric("found_executable_total","source","files");
             +  -  +  - ]
    4537         [ +  + ]:         668 :   if (debuginfo_p)
    4538   [ +  -  +  -  :         144 :     inc_metric("found_debuginfo_total","source","files");
             +  -  +  - ]
    4539                 :             : 
    4540   [ +  +  +  - ]:         668 :   if (sourcefiles.size() && buildid != "")
    4541                 :             :     {
    4542                 :          72 :       fts_sourcefiles += sourcefiles.size();
    4543                 :             : 
    4544         [ +  + ]:       15410 :       for (auto&& dwarfsrc : sourcefiles)
    4545                 :             :         {
    4546         [ +  - ]:       15338 :           char *srp = realpath(dwarfsrc.c_str(), NULL);
    4547         [ -  + ]:       15338 :           if (srp == NULL) // also if DWZ unresolved dwarfsrc=""
    4548                 :           0 :             continue; // unresolvable files are not a serious problem
    4549                 :             :           // throw libc_exception(errno, "fts/file realpath " + srcpath);
    4550         [ +  - ]:       15338 :           string srps = string(srp);
    4551                 :       15338 :           free (srp);
    4552                 :             : 
    4553                 :       15338 :           struct stat sfs;
    4554                 :       15338 :           rc = stat(srps.c_str(), &sfs);
    4555         [ -  + ]:       15338 :           if (rc != 0)
    4556                 :           0 :             continue;
    4557                 :             : 
    4558         [ +  - ]:       15338 :           if (verbose > 2)
    4559   [ +  -  +  - ]:       46014 :             obatched(clog) << "recorded buildid=" << buildid << " file=" << srps
    4560   [ +  -  +  -  :       15338 :                            << " mtime=" << sfs.st_mtime
             +  -  +  - ]
    4561   [ +  -  +  -  :       15338 :                            << " as source " << dwarfsrc << endl;
                   +  - ]
    4562                 :             : 
    4563                 :             :           // PR25548: store canonicalized dwarfsrc path
    4564         [ +  - ]:       15338 :           string dwarfsrc_canon = canon_pathname (dwarfsrc);
    4565         [ +  + ]:       15338 :           if (dwarfsrc_canon != dwarfsrc)
    4566                 :             :             {
    4567         [ +  + ]:        2938 :               if (verbose > 3)
    4568   [ +  -  +  -  :        4568 :                 obatched(clog) << "canonicalized src=" << dwarfsrc << " alias=" << dwarfsrc_canon << endl;
          +  -  +  -  +  
                      - ]
    4569                 :             :             }
    4570                 :             : 
    4571         [ +  - ]:       15338 :           int64_t fileid1 = register_file_name (ps_upsert_fileparts, ps_upsert_file, ps_lookup_file, dwarfsrc_canon);
    4572         [ +  - ]:       15338 :           int64_t fileid2 = register_file_name (ps_upsert_fileparts, ps_upsert_file, ps_lookup_file, srps);
    4573                 :             : 
    4574                 :       15338 :           ps_upsert_s
    4575         [ +  - ]:       15338 :             .reset()
    4576         [ +  - ]:       15338 :             .bind(1, buildid)
    4577         [ +  - ]:       15332 :             .bind(2, fileid1)
    4578         [ +  - ]:       15327 :             .bind(3, fileid2)
    4579         [ +  - ]:       15334 :             .bind(4, sfs.st_mtime)
    4580         [ +  - ]:       15332 :             .step_ok_done();
    4581                 :             : 
    4582   [ +  -  +  -  :       30676 :           inc_metric("found_sourcerefs_total","source","files");
             +  -  +  - ]
    4583                 :       15338 :         }
    4584                 :             :     }
    4585                 :             : 
    4586                 :         668 :   ps_scan_done
    4587         [ +  - ]:         668 :     .reset()
    4588         [ +  - ]:         668 :     .bind(1, fileid)
    4589         [ +  - ]:         668 :     .bind(2, st.st_mtime)
    4590         [ +  - ]:         668 :     .bind(3, st.st_size)
    4591         [ +  - ]:         668 :     .step_ok_done();
    4592                 :             : 
    4593         [ +  - ]:         668 :   if (verbose > 2)
    4594   [ +  -  +  - ]:        2004 :     obatched(clog) << "recorded buildid=" << buildid << " file=" << rps
    4595   [ +  -  +  -  :         668 :                    << " mtime=" << st.st_mtime << " atype="
             +  -  +  - ]
    4596                 :             :                    << (executable_p ? "E" : "")
    4597   [ +  -  +  +  :        1860 :                    << (debuginfo_p ? "D" : "") << endl;
          +  -  +  +  +  
                -  +  - ]
    4598                 :         668 : }
    4599                 :             : 
    4600                 :             : 
    4601                 :             : 
    4602                 :             : 
    4603                 :             : 
    4604                 :             : // Analyze given archive file of given age; record buildids / exec/debuginfo-ness of its
    4605                 :             : // constituent files with given upsert statements.
    4606                 :             : static void
    4607                 :         396 : archive_classify (const string& rps, string& archive_extension, int64_t archiveid,
    4608                 :             :                   sqlite_ps& ps_upsert_buildids, sqlite_ps& ps_upsert_fileparts, sqlite_ps& ps_upsert_file,
    4609                 :             :                   sqlite_ps& ps_lookup_file,
    4610                 :             :                   sqlite_ps& ps_upsert_de, sqlite_ps& ps_upsert_sref, sqlite_ps& ps_upsert_sdef,
    4611                 :             :                   sqlite_ps& ps_upsert_seekable,
    4612                 :             :                   time_t mtime,
    4613                 :             :                   unsigned& fts_executable, unsigned& fts_debuginfo, unsigned& fts_sref, unsigned& fts_sdef,
    4614                 :             :                   bool& fts_sref_complete_p)
    4615                 :             : {
    4616                 :         396 :   string archive_decoder = "/dev/null";
    4617         [ +  + ]:        1024 :   for (auto&& arch : scan_archives)
    4618         [ +  + ]:         628 :     if (string_endswith(rps, arch.first))
    4619                 :             :       {
    4620         [ +  - ]:         396 :         archive_extension = arch.first;
    4621         [ +  - ]:        1024 :         archive_decoder = arch.second;
    4622                 :             :       }
    4623                 :             : 
    4624                 :         396 :   FILE* fp;
    4625                 :         396 :   defer_dtor<FILE*,int>::dtor_fn dfn;
    4626         [ +  + ]:         396 :   if (archive_decoder != "cat")
    4627                 :             :     {
    4628   [ +  -  +  -  :          80 :       string popen_cmd = archive_decoder + " " + shell_escape(rps);
                   +  - ]
    4629         [ +  - ]:          40 :       fp = popen (popen_cmd.c_str(), "r"); // "e" O_CLOEXEC?
    4630                 :          40 :       dfn = pclose;
    4631         [ -  + ]:          40 :       if (fp == NULL)
    4632   [ #  #  #  #  :           0 :         throw libc_exception (errno, string("popen ") + popen_cmd);
                   #  # ]
    4633                 :          40 :     }
    4634                 :             :   else
    4635                 :             :     {
    4636         [ +  - ]:         356 :       fp = fopen (rps.c_str(), "r");
    4637                 :         356 :       dfn = fclose;
    4638         [ -  + ]:         356 :       if (fp == NULL)
    4639   [ #  #  #  #  :           0 :         throw libc_exception (errno, string("fopen ") + rps);
                   #  # ]
    4640                 :             :     }
    4641                 :         396 :   defer_dtor<FILE*,int> fp_closer (fp, dfn);
    4642                 :             : 
    4643                 :         396 :   struct archive *a;
    4644         [ +  - ]:         396 :   a = archive_read_new();
    4645         [ -  + ]:         396 :   if (a == NULL)
    4646   [ #  #  #  # ]:           0 :     throw archive_exception("cannot create archive reader");
    4647                 :         396 :   defer_dtor<struct archive*,int> archive_closer (a, archive_read_free);
    4648                 :             : 
    4649         [ +  - ]:         396 :   int rc = archive_read_support_format_all(a);
    4650         [ -  + ]:         396 :   if (rc != ARCHIVE_OK)
    4651   [ #  #  #  # ]:           0 :     throw archive_exception(a, "cannot select all formats");
    4652         [ +  - ]:         396 :   rc = archive_read_support_filter_all(a);
    4653         [ -  + ]:         396 :   if (rc != ARCHIVE_OK)
    4654   [ #  #  #  # ]:           0 :     throw archive_exception(a, "cannot select all filters");
    4655                 :             : 
    4656         [ +  - ]:         396 :   rc = archive_read_open_FILE (a, fp);
    4657         [ -  + ]:         396 :   if (rc != ARCHIVE_OK)
    4658                 :             :     {
    4659   [ #  #  #  #  :           0 :       obatched(clog) << "cannot open archive from pipe " << rps << endl;
                   #  # ]
    4660   [ #  #  #  # ]:           0 :       throw archive_exception(a, "cannot open archive from pipe");
    4661                 :             :     }
    4662                 :             : 
    4663         [ +  + ]:         396 :   if (verbose > 3)
    4664   [ +  -  +  -  :         712 :     obatched(clog) << "libarchive scanning " << rps << " id " << archiveid << endl;
          +  -  +  -  +  
                      - ]
    4665                 :             : 
    4666         [ +  - ]:         396 :   bool seekable = is_seekable_archive (rps, a);
    4667   [ +  -  +  + ]:         396 :   if (verbose> 2 && seekable)
    4668   [ +  -  +  -  :          64 :     obatched(clog) << rps << " is seekable" << endl;
                   +  - ]
    4669                 :             : 
    4670                 :             :   bool any_exceptions = false;
    4671                 :        3774 :   while(1) // parse archive entries
    4672                 :             :     {
    4673         [ +  - ]:        3774 :     if (interrupted)
    4674                 :             :       break;
    4675                 :             : 
    4676                 :        3774 :     try
    4677                 :             :         {
    4678                 :        3774 :           struct archive_entry *e;
    4679         [ +  - ]:        3774 :           rc = archive_read_next_header (a, &e);
    4680         [ +  + ]:        3774 :           if (rc != ARCHIVE_OK)
    4681                 :             :             break;
    4682                 :             : 
    4683   [ +  -  +  + ]:        3378 :           if (! S_ISREG(archive_entry_mode (e))) // skip non-files completely
    4684                 :        2338 :             continue;
    4685                 :             : 
    4686         [ +  - ]:        1040 :           string fn = canonicalized_archive_entry_pathname (e);
    4687                 :             : 
    4688         [ +  + ]:        1040 :           if (verbose > 3)
    4689   [ +  -  +  -  :        1756 :             obatched(clog) << "libarchive checking " << fn << endl;
                   +  - ]
    4690                 :             : 
    4691         [ +  - ]:        1040 :           int64_t seekable_size = archive_entry_size (e);
    4692         [ +  - ]:        1040 :           int64_t seekable_offset = archive_filter_bytes (a, 0);
    4693         [ +  - ]:        1040 :           time_t seekable_mtime = archive_entry_mtime (e);
    4694                 :             : 
    4695                 :             :           // extract this file to a temporary file
    4696                 :        1040 :           char* tmppath = NULL;
    4697                 :        1040 :           rc = asprintf (&tmppath, "%s/debuginfod-classify.XXXXXX", tmpdir.c_str());
    4698         [ -  + ]:        1040 :           if (rc < 0)
    4699   [ #  #  #  # ]:           0 :             throw libc_exception (ENOMEM, "cannot allocate tmppath");
    4700                 :        1040 :           defer_dtor<void*,void> tmmpath_freer (tmppath, free);
    4701         [ +  - ]:        1040 :           int fd = mkstemp (tmppath);
    4702         [ -  + ]:        1040 :           if (fd < 0)
    4703   [ #  #  #  # ]:           0 :             throw libc_exception (errno, "cannot create temporary file");
    4704                 :        1040 :           unlink (tmppath); // unlink now so OS will release the file as soon as we close the fd
    4705                 :        1040 :           defer_dtor<int,int> minifd_closer (fd, close);
    4706                 :             : 
    4707         [ +  - ]:        1040 :           rc = archive_read_data_into_fd (a, fd);
    4708         [ -  + ]:        1040 :           if (rc != ARCHIVE_OK) {
    4709         [ #  # ]:           0 :             close (fd);
    4710   [ #  #  #  # ]:           0 :             throw archive_exception(a, rps, "cannot extract file");
    4711                 :             :           }
    4712                 :             : 
    4713                 :             :           // finally ... time to run elf_classify on this bad boy and update the database
    4714                 :        1040 :           bool executable_p = false, debuginfo_p = false;
    4715         [ +  - ]:        1040 :           string buildid;
    4716         [ +  - ]:        1040 :           set<string> sourcefiles;
    4717         [ +  - ]:        1040 :           elf_classify (fd, executable_p, debuginfo_p, buildid, sourcefiles);
    4718                 :             :           // NB: might throw
    4719                 :             : 
    4720         [ +  + ]:        1040 :           if (buildid != "") // intern buildid
    4721                 :             :             {
    4722                 :         666 :               ps_upsert_buildids
    4723         [ +  - ]:         666 :                 .reset()
    4724         [ +  - ]:         666 :                 .bind(1, buildid)
    4725         [ +  - ]:         666 :                 .step_ok_done();
    4726                 :             :             }
    4727                 :             : 
    4728         [ +  - ]:        1040 :           int64_t fileid = register_file_name (ps_upsert_fileparts, ps_upsert_file, ps_lookup_file, fn);
    4729                 :             : 
    4730         [ +  + ]:        1040 :           if (sourcefiles.size() > 0) // sref records needed
    4731                 :             :             {
    4732                 :             :               // NB: we intern each source file once.  Once raw, as it
    4733                 :             :               // appears in the DWARF file list coming back from
    4734                 :             :               // elf_classify() - because it'll end up in the
    4735                 :             :               // _norm.artifactsrc column.  We don't also put another
    4736                 :             :               // version with a '.' at the front, even though that's
    4737                 :             :               // how rpm/cpio packs names, because we hide that from
    4738                 :             :               // the database for storage efficiency.
    4739                 :             : 
    4740         [ +  + ]:         802 :               for (auto&& s : sourcefiles)
    4741                 :             :                 {
    4742         [ +  + ]:         498 :                   if (s == "")
    4743                 :             :                     {
    4744                 :          20 :                       fts_sref_complete_p = false;
    4745                 :          20 :                       continue;
    4746                 :             :                     }
    4747                 :             : 
    4748                 :             :                   // PR25548: store canonicalized source path
    4749                 :         478 :                   const string& dwarfsrc = s;
    4750         [ +  - ]:         478 :                   string dwarfsrc_canon = canon_pathname (dwarfsrc);
    4751         [ +  + ]:         478 :                   if (dwarfsrc_canon != dwarfsrc)
    4752                 :             :                     {
    4753         [ +  - ]:          28 :                       if (verbose > 3)
    4754   [ +  -  +  -  :          56 :                         obatched(clog) << "canonicalized src=" << dwarfsrc << " alias=" << dwarfsrc_canon << endl;
          +  -  +  -  +  
                      - ]
    4755                 :             :                     }
    4756                 :             : 
    4757         [ +  - ]:         478 :                   int64_t srcfileid = register_file_name(ps_upsert_fileparts, ps_upsert_file, ps_lookup_file,
    4758                 :             :                                                          dwarfsrc_canon);
    4759                 :             :                 
    4760                 :         478 :                   ps_upsert_sref
    4761         [ +  - ]:         478 :                     .reset()
    4762         [ +  - ]:         478 :                     .bind(1, buildid)
    4763         [ +  - ]:         478 :                     .bind(2, srcfileid)
    4764         [ +  - ]:         478 :                     .step_ok_done();
    4765                 :             : 
    4766                 :         478 :                   fts_sref ++;
    4767                 :         478 :                 }
    4768                 :             :             }
    4769                 :             : 
    4770         [ +  + ]:        1040 :           if (executable_p)
    4771                 :         302 :             fts_executable ++;
    4772         [ +  + ]:        1040 :           if (debuginfo_p)
    4773                 :         366 :             fts_debuginfo ++;
    4774                 :             : 
    4775   [ +  +  +  + ]:        1040 :           if (executable_p || debuginfo_p)
    4776                 :             :             {
    4777                 :         666 :               ps_upsert_de
    4778         [ +  - ]:         666 :                 .reset()
    4779         [ +  - ]:         666 :                 .bind(1, buildid)
    4780   [ +  +  +  - ]:         966 :                 .bind(2, debuginfo_p ? 1 : 0)
    4781   [ +  +  +  - ]:        1030 :                 .bind(3, executable_p ? 1 : 0)
    4782         [ +  - ]:         665 :                 .bind(4, archiveid)
    4783         [ +  - ]:         666 :                 .bind(5, mtime)
    4784         [ +  - ]:         666 :                 .bind(6, fileid)
    4785         [ +  - ]:         666 :                 .step_ok_done();
    4786         [ +  + ]:         666 :               if (seekable)
    4787                 :         336 :                 ps_upsert_seekable
    4788         [ +  - ]:         336 :                   .reset()
    4789         [ +  - ]:         336 :                   .bind(1, archiveid)
    4790         [ +  - ]:         336 :                   .bind(2, fileid)
    4791         [ +  - ]:         336 :                   .bind(3, seekable_size)
    4792         [ +  - ]:         336 :                   .bind(4, seekable_offset)
    4793         [ +  - ]:         336 :                   .bind(5, seekable_mtime)
    4794         [ +  - ]:         336 :                   .step_ok_done();
    4795                 :             :             }
    4796                 :             :           else // potential source - sdef record
    4797                 :             :             {
    4798                 :         374 :               fts_sdef ++;
    4799                 :         374 :               ps_upsert_sdef
    4800         [ +  - ]:         374 :                 .reset()
    4801         [ +  - ]:         374 :                 .bind(1, archiveid)
    4802         [ +  - ]:         374 :                 .bind(2, mtime)
    4803         [ +  - ]:         374 :                 .bind(3, fileid)
    4804         [ +  - ]:         374 :                 .step_ok_done();
    4805                 :             :             }
    4806                 :             : 
    4807   [ +  -  +  +  :        1040 :           if ((verbose > 2) && (executable_p || debuginfo_p))
                   +  + ]
    4808                 :             :             {
    4809         [ +  - ]:         666 :               obatched ob(clog);
    4810   [ +  -  +  - ]:         666 :               auto& o = ob << "recorded buildid=" << buildid << " rpm=" << rps << " file=" << fn
    4811   [ +  -  +  -  :         666 :                            << " mtime=" << mtime << " atype="
          +  -  +  -  +  
                -  +  - ]
    4812                 :             :                            << (executable_p ? "E" : "")
    4813                 :             :                            << (debuginfo_p ? "D" : "")
    4814   [ +  -  +  +  :        1330 :                            << " sourcefiles=" << sourcefiles.size();
          +  -  +  +  +  
             -  +  -  +  
                      - ]
    4815         [ +  + ]:         666 :               if (seekable)
    4816   [ +  -  +  - ]:         336 :                 o << " seekable size=" << seekable_size
    4817   [ +  -  +  - ]:         336 :                   << " offset=" << seekable_offset
    4818   [ +  -  +  - ]:         336 :                   << " mtime=" << seekable_mtime;
    4819         [ +  - ]:         666 :               o << endl;
    4820                 :         666 :             }
    4821                 :             : 
    4822                 :        1040 :         }
    4823         [ -  - ]:           0 :       catch (const reportable_exception& e)
    4824                 :             :         {
    4825         [ -  - ]:           0 :           e.report(clog);
    4826                 :           0 :           any_exceptions = true;
    4827                 :             :           // NB: but we allow the libarchive iteration to continue, in
    4828                 :             :           // case we can still gather some useful information.  That
    4829                 :             :           // would allow some webapi queries to work, until later when
    4830                 :             :           // this archive is rescanned.  (Its vitals won't go into the
    4831                 :             :           // _file_mtime_scanned table until after a successful scan.)
    4832                 :           0 :         }
    4833                 :             :     }
    4834                 :             : 
    4835         [ -  + ]:         396 :   if (any_exceptions)
    4836         [ #  # ]:           0 :     throw reportable_exception("exceptions encountered during archive scan");
    4837                 :         396 : }
    4838                 :             : 
    4839                 :             : 
    4840                 :             : 
    4841                 :             : // scan for archive files such as .rpm
    4842                 :             : static void
    4843                 :         762 : scan_archive_file (const string& rps, const stat_t& st,
    4844                 :             :                    sqlite_ps& ps_upsert_buildids,
    4845                 :             :                    sqlite_ps& ps_upsert_fileparts,
    4846                 :             :                    sqlite_ps& ps_upsert_file,
    4847                 :             :                    sqlite_ps& ps_lookup_file,
    4848                 :             :                    sqlite_ps& ps_upsert_de,
    4849                 :             :                    sqlite_ps& ps_upsert_sref,
    4850                 :             :                    sqlite_ps& ps_upsert_sdef,
    4851                 :             :                    sqlite_ps& ps_upsert_seekable,
    4852                 :             :                    sqlite_ps& ps_query,
    4853                 :             :                    sqlite_ps& ps_scan_done,
    4854                 :             :                    unsigned& fts_cached,
    4855                 :             :                    unsigned& fts_executable,
    4856                 :             :                    unsigned& fts_debuginfo,
    4857                 :             :                    unsigned& fts_sref,
    4858                 :             :                    unsigned& fts_sdef)
    4859                 :             : {
    4860                 :             :   // intern the archive file name
    4861                 :         762 :   int64_t archiveid = register_file_name (ps_upsert_fileparts, ps_upsert_file, ps_lookup_file, rps);
    4862                 :             : 
    4863                 :             :   /* See if we know of it already. */
    4864                 :         762 :   int rc = ps_query
    4865                 :         762 :     .reset()
    4866                 :         762 :     .bind(1, archiveid)
    4867                 :         762 :     .bind(2, st.st_mtime)
    4868                 :         762 :     .step();
    4869                 :         762 :   ps_query.reset();
    4870         [ +  + ]:         762 :   if (rc == SQLITE_ROW) // i.e., a result, as opposed to DONE (no results)
    4871                 :             :     // no need to recheck a file/version we already know
    4872                 :             :     // specifically, no need to parse this archive again, since we already have
    4873                 :             :     // it as a D or E or S record,
    4874                 :             :     // (so is stored with buildid=NULL)
    4875                 :             :     {
    4876                 :         366 :       fts_cached ++;
    4877                 :         366 :       return;
    4878                 :             :     }
    4879                 :             : 
    4880                 :             :   // extract the archive contents
    4881                 :         396 :   unsigned my_fts_executable = 0, my_fts_debuginfo = 0, my_fts_sref = 0, my_fts_sdef = 0;
    4882                 :         396 :   bool my_fts_sref_complete_p = true;
    4883                 :         396 :   bool any_exceptions = false;
    4884                 :         396 :   try
    4885                 :             :     {
    4886         [ +  - ]:         396 :       string archive_extension;
    4887                 :         396 :       archive_classify (rps, archive_extension, archiveid,
    4888                 :             :                         ps_upsert_buildids, ps_upsert_fileparts, ps_upsert_file, ps_lookup_file,
    4889                 :             :                         ps_upsert_de, ps_upsert_sref, ps_upsert_sdef, ps_upsert_seekable, // dalt
    4890         [ +  - ]:         396 :                         st.st_mtime,
    4891                 :             :                         my_fts_executable, my_fts_debuginfo, my_fts_sref, my_fts_sdef,
    4892                 :             :                         my_fts_sref_complete_p);
    4893   [ +  -  +  -  :         792 :       add_metric ("scanned_bytes_total","source",archive_extension + " archive",
                   +  - ]
    4894         [ +  - ]:         396 :                   st.st_size);
    4895   [ +  -  +  -  :         792 :       inc_metric ("scanned_files_total","source",archive_extension + " archive");
             +  -  +  - ]
    4896   [ +  -  +  -  :         792 :       add_metric("found_debuginfo_total","source",archive_extension + " archive",
             +  -  +  - ]
    4897                 :             :                  my_fts_debuginfo);
    4898   [ +  -  +  -  :         792 :       add_metric("found_executable_total","source",archive_extension + " archive",
             +  -  +  - ]
    4899                 :             :                  my_fts_executable);
    4900   [ +  -  +  -  :         792 :       add_metric("found_sourcerefs_total","source",archive_extension + " archive",
             +  -  +  - ]
    4901                 :             :                  my_fts_sref);
    4902                 :         396 :     }
    4903         [ -  - ]:           0 :   catch (const reportable_exception& e)
    4904                 :             :     {
    4905         [ -  - ]:           0 :       e.report(clog);
    4906                 :           0 :       any_exceptions = true;
    4907                 :           0 :     }
    4908                 :             : 
    4909         [ +  - ]:         396 :   if (verbose > 2)
    4910         [ +  - ]:        1188 :     obatched(clog) << "scanned archive=" << rps
    4911   [ +  -  +  - ]:         396 :                    << " mtime=" << st.st_mtime
    4912         [ +  - ]:         396 :                    << " executables=" << my_fts_executable
    4913   [ +  -  +  - ]:         396 :                    << " debuginfos=" << my_fts_debuginfo
    4914   [ +  -  +  - ]:         396 :                    << " srefs=" << my_fts_sref
    4915   [ +  -  +  - ]:         396 :                    << " sdefs=" << my_fts_sdef
    4916   [ +  -  +  -  :         396 :                    << " exceptions=" << any_exceptions
             +  -  +  - ]
    4917                 :         396 :                    << endl;
    4918                 :             : 
    4919                 :         396 :   fts_executable += my_fts_executable;
    4920                 :         396 :   fts_debuginfo += my_fts_debuginfo;
    4921                 :         396 :   fts_sref += my_fts_sref;
    4922                 :         396 :   fts_sdef += my_fts_sdef;
    4923                 :             : 
    4924         [ -  + ]:         396 :   if (any_exceptions)
    4925         [ #  # ]:           0 :     throw reportable_exception("exceptions encountered during archive scan");
    4926                 :             : 
    4927         [ +  + ]:         396 :   if (my_fts_sref_complete_p) // leave incomplete?
    4928                 :         394 :     ps_scan_done
    4929                 :         394 :       .reset()
    4930                 :         394 :       .bind(1, archiveid)
    4931                 :         394 :       .bind(2, st.st_mtime)
    4932                 :         394 :       .bind(3, st.st_size)
    4933                 :         394 :       .step_ok_done();
    4934                 :             : }
    4935                 :             : 
    4936                 :             : 
    4937                 :             : 
    4938                 :             : ////////////////////////////////////////////////////////////////////////
    4939                 :             : 
    4940                 :             : 
    4941                 :             : 
    4942                 :             : // The thread that consumes file names off of the scanq.  We hold
    4943                 :             : // the persistent sqlite_ps's at this level and delegate file/archive
    4944                 :             : // scanning to other functions.
    4945                 :             : static void
    4946                 :         288 : scan ()
    4947                 :             : {
    4948                 :             :   // all the prepared statements fit to use, the _f_ set:
    4949   [ +  -  +  - ]:         576 :   sqlite_ps ps_f_upsert_buildids (db, "file-buildids-intern", "insert or ignore into " BUILDIDS "_buildids VALUES (NULL, ?);");
    4950   [ +  -  +  -  :         576 :   sqlite_ps ps_f_upsert_fileparts (db, "file-fileparts-intern", "insert or ignore into " BUILDIDS "_fileparts VALUES (NULL, ?);");
                   +  - ]
    4951         [ +  - ]:         288 :   sqlite_ps ps_f_upsert_file (db, "file-file-intern", "insert or ignore into " BUILDIDS "_files VALUES (NULL, \n"
    4952                 :             :                               "(select id from " BUILDIDS "_fileparts where name = ?),\n"
    4953   [ +  -  +  -  :         576 :                               "(select id from " BUILDIDS "_fileparts where name = ?));");
                   +  - ]
    4954         [ +  - ]:         288 :   sqlite_ps ps_f_lookup_file (db, "file-file-lookup",
    4955                 :             :                               "select f.id\n"
    4956                 :             :                               " from " BUILDIDS "_files f, " BUILDIDS "_fileparts p1, " BUILDIDS "_fileparts p2 \n"
    4957   [ +  -  +  -  :         576 :                               " where f.dirname = p1.id and f.basename = p2.id and p1.name = ? and p2.name = ?;\n");
                   +  - ]
    4958         [ +  - ]:         288 :   sqlite_ps ps_f_upsert_de (db, "file-de-upsert",
    4959                 :             :                           "insert or ignore into " BUILDIDS "_f_de "
    4960                 :             :                           "(buildid, debuginfo_p, executable_p, file, mtime) "
    4961                 :             :                           "values ((select id from " BUILDIDS "_buildids where hex = ?),"
    4962   [ +  -  +  -  :         576 :                             "        ?,?,?,?);");
                   +  - ]
    4963         [ +  - ]:         288 :   sqlite_ps ps_f_upsert_s (db, "file-s-upsert",
    4964                 :             :                          "insert or ignore into " BUILDIDS "_f_s "
    4965                 :             :                          "(buildid, artifactsrc, file, mtime) "
    4966                 :             :                          "values ((select id from " BUILDIDS "_buildids where hex = ?),"
    4967   [ +  -  +  -  :         576 :                          "      ?,?,?);");
                   +  - ]
    4968         [ +  - ]:         288 :   sqlite_ps ps_f_query (db, "file-negativehit-find",
    4969                 :             :                         "select 1 from " BUILDIDS "_file_mtime_scanned where sourcetype = 'F' "
    4970   [ +  -  +  -  :         576 :                         "and file = ? and mtime = ?;");
                   +  - ]
    4971         [ +  - ]:         288 :   sqlite_ps ps_f_scan_done (db, "file-scanned",
    4972                 :             :                           "insert or ignore into " BUILDIDS "_file_mtime_scanned (sourcetype, file, mtime, size)"
    4973   [ +  -  +  -  :         576 :                           "values ('F', ?,?,?);");
                   +  - ]
    4974                 :             : 
    4975                 :             :   // and now for the _r_ set
    4976   [ +  -  +  -  :         576 :   sqlite_ps ps_r_upsert_buildids (db, "rpm-buildid-intern", "insert or ignore into " BUILDIDS "_buildids VALUES (NULL, ?);");
                   +  - ]
    4977   [ +  -  +  -  :         576 :   sqlite_ps ps_r_upsert_fileparts (db, "rpm-fileparts-intern", "insert or ignore into " BUILDIDS "_fileparts VALUES (NULL, ?);");
                   +  - ]
    4978         [ +  - ]:         288 :   sqlite_ps ps_r_upsert_file (db, "rpm-file-intern", "insert or ignore into " BUILDIDS "_files VALUES (NULL, \n"
    4979                 :             :                               "(select id from " BUILDIDS "_fileparts where name = ?),\n"
    4980   [ +  -  +  -  :         576 :                               "(select id from " BUILDIDS "_fileparts where name = ?));");
                   +  - ]
    4981         [ +  - ]:         288 :   sqlite_ps ps_r_lookup_file (db, "rpm-file-lookup",
    4982                 :             :                               "select f.id\n"
    4983                 :             :                               " from " BUILDIDS "_files f, " BUILDIDS "_fileparts p1, " BUILDIDS "_fileparts p2 \n"
    4984   [ +  -  +  -  :         576 :                               " where f.dirname = p1.id and f.basename = p2.id and p1.name = ? and p2.name = ?;\n");
                   +  - ]
    4985         [ +  - ]:         288 :   sqlite_ps ps_r_upsert_de (db, "rpm-de-insert",
    4986                 :             :                           "insert or ignore into " BUILDIDS "_r_de (buildid, debuginfo_p, executable_p, file, mtime, content) values ("
    4987   [ +  -  +  -  :         576 :                           "(select id from " BUILDIDS "_buildids where hex = ?), ?, ?, ?, ?, ?);");
                   +  - ]
    4988         [ +  - ]:         288 :   sqlite_ps ps_r_upsert_sref (db, "rpm-sref-insert",
    4989                 :             :                             "insert or ignore into " BUILDIDS "_r_sref (buildid, artifactsrc) values ("
    4990                 :             :                             "(select id from " BUILDIDS "_buildids where hex = ?), "
    4991   [ +  -  +  -  :         576 :                             "?);");
                   +  - ]
    4992         [ +  - ]:         288 :   sqlite_ps ps_r_upsert_sdef (db, "rpm-sdef-insert",
    4993                 :             :                             "insert or ignore into " BUILDIDS "_r_sdef (file, mtime, content) values ("
    4994   [ +  -  +  -  :         576 :                             "?, ?, ?);");
                   +  - ]
    4995         [ +  - ]:         288 :   sqlite_ps ps_r_upsert_seekable (db, "rpm-seekable-insert",
    4996                 :             :                                   "insert or ignore into " BUILDIDS "_r_seekable (file, content, type, size, offset, mtime) "
    4997   [ +  -  +  -  :         576 :                                   "values (?, ?, 'xz', ?, ?, ?);");
                   +  - ]
    4998         [ +  - ]:         288 :   sqlite_ps ps_r_query (db, "rpm-negativehit-query",
    4999                 :             :                       "select 1 from " BUILDIDS "_file_mtime_scanned where "
    5000   [ +  -  +  -  :         576 :                       "sourcetype = 'R' and file = ? and mtime = ?;");
                   +  - ]
    5001         [ +  - ]:         288 :   sqlite_ps ps_r_scan_done (db, "rpm-scanned",
    5002                 :             :                           "insert or ignore into " BUILDIDS "_file_mtime_scanned (sourcetype, file, mtime, size)"
    5003   [ +  -  +  -  :         576 :                           "values ('R', ?, ?, ?);");
                   +  - ]
    5004                 :             :   
    5005                 :             : 
    5006                 :         288 :   unsigned fts_cached = 0, fts_executable = 0, fts_debuginfo = 0, fts_sourcefiles = 0;
    5007                 :         288 :   unsigned fts_sref = 0, fts_sdef = 0;
    5008                 :             : 
    5009   [ +  -  +  -  :         576 :   add_metric("thread_count", "role", "scan", 1);
             +  -  +  - ]
    5010   [ +  -  +  -  :         576 :   add_metric("thread_busy", "role", "scan", 1);
             +  -  +  - ]
    5011         [ +  + ]:        1890 :   while (! interrupted)
    5012                 :             :     {
    5013         [ +  - ]:        1602 :       scan_payload p;
    5014                 :             : 
    5015   [ +  -  +  -  :        3204 :       add_metric("thread_busy", "role", "scan", -1);
             +  -  +  - ]
    5016                 :             :       // NB: threads may be blocked within either of these two waiting
    5017                 :             :       // states, if the work queue happens to run dry.  That's OK.
    5018   [ +  -  +  - ]:        1602 :       if (scan_barrier) scan_barrier->count();
    5019         [ +  - ]:        1602 :       bool gotone = scanq.wait_front(p);
    5020   [ +  -  +  -  :        3204 :       add_metric("thread_busy", "role", "scan", 1);
             +  -  +  - ]
    5021                 :             : 
    5022         [ +  + ]:        1602 :       if (! gotone) continue; // go back to waiting
    5023                 :             : 
    5024                 :        1314 :       try
    5025                 :             :         {
    5026                 :        1314 :           bool scan_archive = false;
    5027         [ +  + ]:        2886 :           for (auto&& arch : scan_archives)
    5028         [ +  + ]:        1572 :             if (string_endswith(p.first, arch.first))
    5029                 :         762 :               scan_archive = true;
    5030                 :             : 
    5031         [ +  + ]:        1314 :           if (scan_archive)
    5032         [ +  - ]:         762 :             scan_archive_file (p.first, p.second,
    5033                 :             :                                ps_r_upsert_buildids,
    5034                 :             :                                ps_r_upsert_fileparts,
    5035                 :             :                                ps_r_upsert_file,
    5036                 :             :                                ps_r_lookup_file,
    5037                 :             :                                ps_r_upsert_de,
    5038                 :             :                                ps_r_upsert_sref,
    5039                 :             :                                ps_r_upsert_sdef,
    5040                 :             :                                ps_r_upsert_seekable,
    5041                 :             :                                ps_r_query,
    5042                 :             :                                ps_r_scan_done,
    5043                 :             :                                fts_cached,
    5044                 :             :                                fts_executable,
    5045                 :             :                                fts_debuginfo,
    5046                 :             :                                fts_sref,
    5047                 :             :                                fts_sdef);
    5048                 :             : 
    5049         [ +  + ]:        1314 :           if (scan_files) // NB: maybe "else if" ?
    5050         [ +  - ]:        1106 :             scan_source_file (p.first, p.second,
    5051                 :             :                               ps_f_upsert_buildids,
    5052                 :             :                               ps_f_upsert_fileparts,
    5053                 :             :                               ps_f_upsert_file,
    5054                 :             :                               ps_f_lookup_file,
    5055                 :             :                               ps_f_upsert_de,
    5056                 :             :                               ps_f_upsert_s,
    5057                 :             :                               ps_f_query,
    5058                 :             :                               ps_f_scan_done,
    5059                 :             :                               fts_cached, fts_executable, fts_debuginfo, fts_sourcefiles);
    5060                 :             :         }
    5061         [ -  - ]:           0 :       catch (const reportable_exception& e)
    5062                 :             :         {
    5063         [ -  - ]:           0 :           e.report(cerr);
    5064                 :           0 :         }
    5065                 :             : 
    5066         [ +  - ]:        1314 :       scanq.done_front(); // let idlers run
    5067                 :             :       
    5068                 :        1314 :       if (fts_cached || fts_executable || fts_debuginfo || fts_sourcefiles || fts_sref || fts_sdef)
    5069                 :             :         {} // NB: not just if a successful scan - we might have encountered -ENOSPC & failed
    5070   [ +  -  +  - ]:        1314 :       (void) statfs_free_enough_p(db_path, "database"); // report sqlite filesystem size
    5071   [ +  -  +  - ]:        1314 :       (void) statfs_free_enough_p(tmpdir, "tmpdir"); // this too, in case of fdcache/tmpfile usage
    5072                 :             : 
    5073                 :             :       // finished a scanning step -- not a "loop", because we just
    5074                 :             :       // consume the traversal loop's work, whenever
    5075   [ +  -  +  -  :        2628 :       inc_metric("thread_work_total","role","scan");
             +  -  +  - ]
    5076                 :        1602 :     }
    5077                 :             : 
    5078   [ +  -  +  -  :         576 :   add_metric("thread_busy", "role", "scan", -1);
             +  -  +  - ]
    5079                 :         288 : }
    5080                 :             : 
    5081                 :             : 
    5082                 :             : // Use this function as the thread entry point, so it can catch our
    5083                 :             : // fleet of exceptions (incl. the sqlite_ps ctors) and report.
    5084                 :             : static void*
    5085                 :         288 : thread_main_scanner (void* arg)
    5086                 :             : {
    5087                 :         288 :   (void) arg;
    5088         [ +  + ]:         864 :   while (! interrupted)
    5089                 :         288 :     try
    5090                 :             :       {
    5091         [ +  - ]:         288 :         scan();
    5092                 :             :       }
    5093         [ -  - ]:           0 :     catch (const reportable_exception& e)
    5094                 :             :       {
    5095         [ -  - ]:           0 :         e.report(cerr);
    5096                 :           0 :       }
    5097                 :         288 :   return 0;
    5098                 :             : }
    5099                 :             : 
    5100                 :             : 
    5101                 :             : 
    5102                 :             : // The thread that traverses all the source_paths and enqueues all the
    5103                 :             : // matching files into the file/archive scan queue.
    5104                 :             : static void
    5105                 :         126 : scan_source_paths()
    5106                 :             : {
    5107                 :             :   // NB: fedora 31 glibc/fts(3) crashes inside fts_read() on empty
    5108                 :             :   // path list.
    5109         [ +  + ]:         126 :   if (source_paths.empty())
    5110                 :           2 :     return;
    5111                 :             : 
    5112                 :             :   // Turn the source_paths into an fts(3)-compatible char**.  Since
    5113                 :             :   // source_paths[] does not change after argv processing, the
    5114                 :             :   // c_str()'s are safe to keep around awile.
    5115                 :         124 :   vector<const char *> sps;
    5116         [ +  + ]:         332 :   for (auto&& sp: source_paths)
    5117         [ +  - ]:         208 :     sps.push_back(sp.c_str());
    5118   [ +  -  -  - ]:         124 :   sps.push_back(NULL);
    5119                 :             : 
    5120   [ +  +  +  - ]:         234 :   FTS *fts = fts_open ((char * const *)sps.data(),
    5121                 :             :                       (traverse_logical ? FTS_LOGICAL : FTS_PHYSICAL|FTS_XDEV)
    5122                 :             :                       | FTS_NOCHDIR /* multithreaded */,
    5123                 :             :                       NULL);
    5124         [ -  + ]:         124 :   if (fts == NULL)
    5125   [ #  #  #  # ]:           0 :     throw libc_exception(errno, "cannot fts_open");
    5126                 :         124 :   defer_dtor<FTS*,int> fts_cleanup (fts, fts_close);
    5127                 :             : 
    5128                 :         124 :   struct timespec ts_start, ts_end;
    5129                 :         124 :   clock_gettime (CLOCK_MONOTONIC, &ts_start);
    5130                 :         124 :   unsigned fts_scanned = 0, fts_regex = 0;
    5131                 :             : 
    5132                 :         124 :   FTSENT *f;
    5133   [ +  -  +  + ]:        2644 :   while ((f = fts_read (fts)) != NULL)
    5134                 :             :   {
    5135         [ +  - ]:        2396 :     if (interrupted) break;
    5136                 :             : 
    5137         [ -  + ]:        2396 :     if (sigusr2 != forced_groom_count) // stop early if groom triggered
    5138                 :             :       {
    5139         [ #  # ]:           0 :         scanq.clear(); // clear previously issued work for scanner threads
    5140                 :             :         break;
    5141                 :             :       }
    5142                 :             : 
    5143                 :        2396 :     fts_scanned ++;
    5144                 :             : 
    5145         [ +  - ]:        2396 :     if (verbose > 2)
    5146   [ +  -  +  -  :        4792 :       obatched(clog) << "fts traversing " << f->fts_path << endl;
                   +  - ]
    5147                 :             : 
    5148   [ +  +  +  +  :        2396 :     switch (f->fts_info)
                      + ]
    5149                 :             :       {
    5150                 :        1424 :       case FTS_F:
    5151                 :        1424 :         {
    5152                 :             :           /* Found a file.  Convert it to an absolute path, so
    5153                 :             :              the buildid database does not have relative path
    5154                 :             :              names that are unresolvable from a subsequent run
    5155                 :             :              in a different cwd. */
    5156         [ +  - ]:        1424 :           char *rp = realpath(f->fts_path, NULL);
    5157         [ -  + ]:        1424 :           if (rp == NULL)
    5158                 :           0 :             continue; // ignore dangling symlink or such
    5159         [ +  - ]:        1424 :           string rps = string(rp);
    5160                 :        1424 :           free (rp);
    5161                 :             : 
    5162         [ +  - ]:        1424 :           bool ri = !regexec (&file_include_regex, rps.c_str(), 0, 0, 0);
    5163         [ +  - ]:        1424 :           bool rx = !regexec (&file_exclude_regex, rps.c_str(), 0, 0, 0);
    5164         [ +  + ]:        1424 :           if (!ri || rx)
    5165                 :             :             {
    5166         [ +  - ]:         110 :               if (verbose > 3)
    5167         [ +  - ]:         220 :                 obatched(clog) << "fts skipped by regex "
    5168   [ +  +  +  -  :         134 :                                << (!ri ? "I" : "") << (rx ? "X" : "") << endl;
          +  +  +  -  +  
                      - ]
    5169                 :         110 :               fts_regex ++;
    5170         [ +  + ]:         110 :               if (!ri)
    5171   [ +  -  +  -  :          24 :                 inc_metric("traversed_total","type","file-skipped-I");
             +  -  +  - ]
    5172         [ +  + ]:         110 :               if (rx)
    5173   [ +  -  +  -  :         196 :                 inc_metric("traversed_total","type","file-skipped-X");
             +  -  +  - ]
    5174                 :             :             }
    5175                 :             :           else
    5176                 :             :             {
    5177   [ +  -  +  - ]:        1314 :               scanq.push_back (make_pair(rps, *f->fts_statp));
    5178   [ +  -  +  -  :        2628 :               inc_metric("traversed_total","type","file");
             +  -  +  - ]
    5179                 :             :             }
    5180                 :           0 :         }
    5181                 :        1424 :         break;
    5182                 :             : 
    5183                 :           4 :       case FTS_ERR:
    5184                 :           4 :       case FTS_NS:
    5185                 :             :         // report on some types of errors because they may reflect fixable misconfiguration
    5186                 :           4 :         {
    5187   [ +  -  +  -  :           8 :           auto x = libc_exception(f->fts_errno, string("fts traversal ") + string(f->fts_path));
             +  -  +  - ]
    5188         [ +  - ]:           4 :           x.report(cerr);
    5189                 :           0 :         }
    5190   [ +  -  +  -  :           8 :         inc_metric("traversed_total","type","error");
             +  -  +  - ]
    5191                 :           4 :         break;
    5192                 :             : 
    5193                 :          32 :       case FTS_SL: // ignore, but count because debuginfod -L would traverse these
    5194   [ +  -  +  -  :          64 :         inc_metric("traversed_total","type","symlink");
             +  -  +  - ]
    5195                 :          32 :         break;
    5196                 :             : 
    5197                 :         468 :       case FTS_D: // ignore
    5198   [ +  -  +  -  :         936 :         inc_metric("traversed_total","type","directory");
             +  -  +  - ]
    5199                 :         468 :         break;
    5200                 :             : 
    5201                 :         468 :       default: // ignore
    5202   [ +  -  +  -  :         936 :         inc_metric("traversed_total","type","other");
             +  -  +  - ]
    5203                 :         468 :         break;
    5204                 :             :       }
    5205                 :             :   }
    5206                 :         124 :   clock_gettime (CLOCK_MONOTONIC, &ts_end);
    5207                 :         124 :   double deltas = (ts_end.tv_sec - ts_start.tv_sec) + (ts_end.tv_nsec - ts_start.tv_nsec)/1.e9;
    5208                 :             : 
    5209   [ +  -  +  -  :         372 :   obatched(clog) << "fts traversed source paths in " << deltas << "s, scanned=" << fts_scanned
             +  -  +  - ]
    5210   [ +  -  +  -  :         124 :                  << ", regex-skipped=" << fts_regex << endl;
                   +  - ]
    5211         [ +  - ]:         248 : }
    5212                 :             : 
    5213                 :             : 
    5214                 :             : static void*
    5215                 :          72 : thread_main_fts_source_paths (void* arg)
    5216                 :             : {
    5217                 :          72 :   (void) arg; // ignore; we operate on global data
    5218                 :             : 
    5219   [ +  -  +  -  :         144 :   set_metric("thread_tid", "role","traverse", tid());
                   +  - ]
    5220   [ +  -  +  -  :         144 :   add_metric("thread_count", "role", "traverse", 1);
                   +  - ]
    5221                 :             : 
    5222                 :          72 :   time_t last_rescan = 0;
    5223                 :             : 
    5224         [ +  - ]:         294 :   while (! interrupted)
    5225                 :             :     {
    5226                 :         294 :       sleep (1);
    5227                 :         294 :       scanq.wait_idle(); // don't start a new traversal while scanners haven't finished the job
    5228                 :         294 :       scanq.done_idle(); // release the hounds
    5229         [ +  + ]:         294 :       if (interrupted) break;
    5230                 :             : 
    5231                 :         222 :       time_t now = time(NULL);
    5232                 :         222 :       bool rescan_now = false;
    5233         [ +  + ]:         222 :       if (last_rescan == 0) // at least one initial rescan is documented even for -t0
    5234                 :          70 :         rescan_now = true;
    5235   [ +  +  +  + ]:         222 :       if (rescan_s > 0 && (long)now > (long)(last_rescan + rescan_s))
    5236                 :         222 :         rescan_now = true;
    5237         [ +  + ]:         222 :       if (sigusr1 != forced_rescan_count)
    5238                 :             :         {
    5239                 :          58 :           forced_rescan_count = sigusr1;
    5240                 :          58 :           rescan_now = true;
    5241                 :             :         }
    5242         [ +  + ]:         222 :       if (rescan_now)
    5243                 :             :         {
    5244   [ +  -  +  -  :         252 :           set_metric("thread_busy", "role","traverse", 1);
                   +  - ]
    5245                 :         126 :           try
    5246                 :             :             {
    5247         [ +  - ]:         126 :               scan_source_paths();
    5248                 :             :             }
    5249         [ -  - ]:           0 :           catch (const reportable_exception& e)
    5250                 :             :             {
    5251         [ -  - ]:           0 :               e.report(cerr);
    5252                 :           0 :             }
    5253                 :         126 :           last_rescan = time(NULL); // NB: now was before scanning
    5254                 :             :           // finished a traversal loop
    5255   [ +  -  +  -  :         252 :           inc_metric("thread_work_total", "role","traverse");
                   +  - ]
    5256   [ +  -  +  -  :         252 :           set_metric("thread_busy", "role","traverse", 0);
                   +  - ]
    5257                 :             :         }
    5258                 :             :     }
    5259                 :             : 
    5260                 :          72 :   return 0;
    5261                 :             : }
    5262                 :             : 
    5263                 :             : 
    5264                 :             : 
    5265                 :             : ////////////////////////////////////////////////////////////////////////
    5266                 :             : 
    5267                 :             : static void
    5268                 :          78 : database_stats_report()
    5269                 :             : {
    5270                 :          78 :   sqlite_ps ps_query (db, "database-overview",
    5271   [ +  -  +  - ]:         156 :                       "select label,quantity from " BUILDIDS "_stats");
    5272                 :             : 
    5273   [ +  -  +  - ]:         156 :   obatched(clog) << "database record counts:" << endl;
    5274                 :        1794 :   while (1)
    5275                 :             :     {
    5276         [ +  - ]:         936 :       if (interrupted) break;
    5277         [ +  - ]:         936 :       if (sigusr1 != forced_rescan_count) // stop early if scan triggered
    5278                 :             :         break;
    5279                 :             : 
    5280         [ +  - ]:         936 :       int rc = ps_query.step();
    5281         [ +  + ]:         936 :       if (rc == SQLITE_DONE) break;
    5282         [ -  + ]:         858 :       if (rc != SQLITE_ROW)
    5283   [ #  #  #  # ]:           0 :         throw sqlite_exception(rc, "step");
    5284                 :             : 
    5285         [ +  - ]:         858 :       obatched(clog)
    5286   [ +  -  -  +  :         858 :         << ((const char*) sqlite3_column_text(ps_query, 0) ?: (const char*) "NULL")
                   +  - ]
    5287                 :             :         << " "
    5288   [ +  -  +  -  :        1716 :         << (sqlite3_column_text(ps_query, 1) ?: (const unsigned char*) "NULL")
             -  +  +  - ]
    5289                 :         858 :         << endl;
    5290                 :             : 
    5291   [ +  -  +  -  :        1716 :       set_metric("groom", "statistic",
          -  +  +  -  +  
             -  +  -  +  
                      - ]
    5292         [ +  - ]:         858 :                  ((const char*) sqlite3_column_text(ps_query, 0) ?: (const char*) "NULL"),
    5293                 :             :                  (sqlite3_column_double(ps_query, 1)));
    5294                 :         858 :     }
    5295                 :          78 : }
    5296                 :             : 
    5297                 :             : 
    5298                 :             : // Do a round of database grooming that might take many minutes to run.
    5299                 :          78 : void groom()
    5300                 :             : {
    5301         [ +  - ]:         156 :   obatched(clog) << "grooming database" << endl;
    5302                 :             : 
    5303                 :          78 :   struct timespec ts_start, ts_end;
    5304                 :          78 :   clock_gettime (CLOCK_MONOTONIC, &ts_start);
    5305                 :             : 
    5306                 :             :   // scan for files that have disappeared
    5307                 :          78 :   sqlite_ps files (db, "check old files",
    5308                 :             :                    "select distinct s.mtime, s.file, f.name from "
    5309                 :             :                    BUILDIDS "_file_mtime_scanned s, " BUILDIDS "_files_v f "
    5310   [ +  -  +  - ]:         156 :                    "where f.id = s.file");
    5311                 :             :   // NB: Because _ftime_mtime_scanned can contain both F and
    5312                 :             :   // R records for the same file, this query would return duplicates if the
    5313                 :             :   // DISTINCT qualifier were not there.
    5314         [ +  - ]:          78 :   files.reset();
    5315                 :             : 
    5316                 :             :   // DECISION TIME - we enumerate stale fileids/mtimes
    5317         [ +  - ]:          78 :   deque<pair<int64_t,int64_t> > stale_fileid_mtime;
    5318                 :             :   
    5319                 :          78 :   time_t time_start = time(NULL);
    5320                 :         342 :   while(1)
    5321                 :             :     {
    5322                 :             :       // PR28514: limit grooming iteration to O(rescan time), to avoid
    5323                 :             :       // slow filesystem tests over many files locking out rescans for
    5324                 :             :       // too long.
    5325   [ +  +  -  + ]:         210 :       if (rescan_s > 0 && (long)time(NULL) > (long)(time_start + rescan_s))
    5326                 :             :         {
    5327   [ #  #  #  #  :           0 :           inc_metric("groomed_total", "decision", "aborted");
             #  #  #  # ]
    5328                 :           0 :           break;
    5329                 :             :         }
    5330                 :             : 
    5331         [ +  - ]:         210 :       if (interrupted) break;
    5332                 :             : 
    5333         [ +  - ]:         210 :       int rc = files.step();
    5334         [ +  + ]:         210 :       if (rc != SQLITE_ROW)
    5335                 :             :         break;
    5336                 :             : 
    5337         [ +  - ]:         132 :       int64_t mtime = sqlite3_column_int64 (files, 0);
    5338         [ +  - ]:         132 :       int64_t fileid = sqlite3_column_int64 (files, 1);
    5339   [ +  -  -  + ]:         132 :       const char* filename = ((const char*) sqlite3_column_text (files, 2) ?: "");
    5340                 :         132 :       struct stat s;
    5341                 :         132 :       bool regex_file_drop = 0;
    5342                 :             : 
    5343         [ +  + ]:         132 :       if (regex_groom)
    5344                 :             :         {
    5345         [ +  - ]:          16 :           bool reg_include = !regexec (&file_include_regex, filename, 0, 0, 0);
    5346         [ +  - ]:          16 :           bool reg_exclude = !regexec (&file_exclude_regex, filename, 0, 0, 0);
    5347                 :          16 :           regex_file_drop = !reg_include || reg_exclude; // match logic of scan_source_paths  
    5348                 :             :         }
    5349                 :             : 
    5350                 :         132 :       rc = stat(filename, &s);
    5351   [ +  +  -  + ]:         132 :       if ( regex_file_drop ||  rc < 0 || (mtime != (int64_t) s.st_mtime) )
    5352                 :             :         {
    5353         [ +  - ]:          24 :           if (verbose > 2)
    5354   [ +  -  +  -  :          48 :             obatched(clog) << "groom: stale file=" << filename << " mtime=" << mtime << endl;
          +  -  +  -  +  
                      - ]
    5355         [ +  - ]:          24 :           stale_fileid_mtime.push_back(make_pair(fileid,mtime));
    5356   [ +  -  +  -  :          48 :           inc_metric("groomed_total", "decision", "stale");
             +  -  +  - ]
    5357   [ +  -  +  -  :          48 :           set_metric("thread_work_pending","role","groom", stale_fileid_mtime.size());
             +  -  +  - ]
    5358                 :             :         }
    5359                 :             :       else
    5360   [ +  -  +  -  :         216 :         inc_metric("groomed_total", "decision", "fresh");
             +  -  +  - ]
    5361                 :             :       
    5362         [ +  - ]:         132 :       if (sigusr1 != forced_rescan_count) // stop early if scan triggered
    5363                 :             :         break;
    5364                 :         132 :     }
    5365         [ +  - ]:          78 :   files.reset();
    5366                 :             : 
    5367                 :             :   // ACTION TIME
    5368                 :             : 
    5369                 :             :   // Now that we know which file/mtime tuples are stale, actually do
    5370                 :             :   // the deletion from the database.  Doing this during the SELECT
    5371                 :             :   // iteration above results in undefined behaviour in sqlite, as per
    5372                 :             :   // https://www.sqlite.org/isolation.html
    5373                 :             : 
    5374                 :             :   // We could shuffle stale_fileid_mtime[] here.  It'd let aborted
    5375                 :             :   // sequences of nuke operations resume at random locations, instead
    5376                 :             :   // of just starting over.  But it doesn't matter much either way,
    5377                 :             :   // as long as we make progress.
    5378                 :             : 
    5379   [ +  -  +  -  :         156 :   sqlite_ps files_del_f_de (db, "nuke f_de", "delete from " BUILDIDS "_f_de where file = ? and mtime = ?");
                   +  - ]
    5380   [ +  -  +  -  :         156 :   sqlite_ps files_del_r_de (db, "nuke r_de", "delete from " BUILDIDS "_r_de where file = ? and mtime = ?");
                   +  - ]
    5381         [ +  - ]:          78 :   sqlite_ps files_del_scan (db, "nuke f_m_s", "delete from " BUILDIDS "_file_mtime_scanned "
    5382   [ +  -  +  -  :         156 :                             "where file = ? and mtime = ?");
                   +  - ]
    5383                 :             : 
    5384         [ +  + ]:         102 :   while (! stale_fileid_mtime.empty())
    5385                 :             :     {
    5386                 :          24 :       auto stale = stale_fileid_mtime.front();
    5387                 :          24 :       stale_fileid_mtime.pop_front();
    5388   [ +  -  +  -  :          48 :       set_metric("thread_work_pending","role","groom", stale_fileid_mtime.size());
             +  -  +  - ]
    5389                 :             : 
    5390                 :             :       // PR28514: limit grooming iteration to O(rescan time), to avoid
    5391                 :             :       // slow nuke_* queries over many files locking out rescans for too
    5392                 :             :       // long.  We iterate over the files in random() sequence to avoid
    5393                 :             :       // partial checks going over the same set.
    5394   [ -  +  -  - ]:          24 :       if (rescan_s > 0 && (long)time(NULL) > (long)(time_start + rescan_s))
    5395                 :             :         {
    5396   [ #  #  #  #  :           0 :           inc_metric("groomed_total", "action", "aborted");
             #  #  #  # ]
    5397                 :           0 :           break;
    5398                 :             :         }
    5399                 :             : 
    5400         [ +  - ]:          24 :       if (interrupted) break;
    5401                 :             : 
    5402                 :          24 :       int64_t fileid = stale.first;
    5403                 :          24 :       int64_t mtime = stale.second;
    5404   [ +  -  +  -  :          24 :       files_del_f_de.reset().bind(1,fileid).bind(2,mtime).step_ok_done();
             +  -  +  - ]
    5405   [ +  -  +  -  :          24 :       files_del_r_de.reset().bind(1,fileid).bind(2,mtime).step_ok_done();
             +  -  +  - ]
    5406   [ +  -  +  -  :          24 :       files_del_scan.reset().bind(1,fileid).bind(2,mtime).step_ok_done();
             +  -  +  - ]
    5407   [ +  -  +  -  :          48 :       inc_metric("groomed_total", "action", "cleaned");
             +  -  +  - ]
    5408                 :             :       
    5409         [ +  - ]:          24 :        if (sigusr1 != forced_rescan_count) // stop early if scan triggered
    5410                 :             :         break;
    5411                 :             :     }
    5412                 :          78 :   stale_fileid_mtime.clear(); // no need for this any longer
    5413   [ +  -  +  -  :         156 :   set_metric("thread_work_pending","role","groom", stale_fileid_mtime.size());
             +  -  +  - ]
    5414                 :             :       
    5415                 :             :   // delete buildids with no references in _r_de or _f_de tables;
    5416                 :             :   // cascades to _r_sref & _f_s records
    5417         [ +  - ]:          78 :   sqlite_ps buildids_del (db, "nuke orphan buildids",
    5418                 :             :                           "delete from " BUILDIDS "_buildids "
    5419                 :             :                           "where not exists (select 1 from " BUILDIDS "_f_de d where " BUILDIDS "_buildids.id = d.buildid) "
    5420   [ +  -  +  -  :         156 :                           "and not exists (select 1 from " BUILDIDS "_r_de d where " BUILDIDS "_buildids.id = d.buildid)");
                   +  - ]
    5421   [ +  -  +  - ]:          78 :   buildids_del.reset().step_ok_done();
    5422                 :             : 
    5423         [ -  + ]:          78 :   if (interrupted) return;
    5424                 :             : 
    5425                 :             :   // NB: "vacuum" is too heavy for even daily runs: it rewrites the entire db, so is done as maxigroom -G
    5426   [ +  -  +  -  :         156 :   { sqlite_ps g (db, "incremental vacuum", "pragma incremental_vacuum"); g.reset().step_ok_done(); }
          +  -  +  -  +  
                      - ]
    5427                 :             :   // https://www.sqlite.org/lang_analyze.html#approx
    5428   [ +  -  +  -  :         156 :   { sqlite_ps g (db, "analyze setup", "pragma analysis_limit = 1000;\n"); g.reset().step_ok_done(); }
          +  -  +  -  +  
                      - ]
    5429   [ +  -  +  -  :         156 :   { sqlite_ps g (db, "analyze", "analyze"); g.reset().step_ok_done(); }
          +  -  +  -  +  
                      - ]
    5430   [ +  -  +  -  :         156 :   { sqlite_ps g (db, "analyze reload", "analyze sqlite_schema"); g.reset().step_ok_done(); } 
          +  -  +  -  +  
                      - ]
    5431   [ +  -  +  -  :         156 :   { sqlite_ps g (db, "optimize", "pragma optimize"); g.reset().step_ok_done(); }
          +  -  +  -  +  
                      - ]
    5432   [ +  -  +  -  :         156 :   { sqlite_ps g (db, "wal checkpoint", "pragma wal_checkpoint=truncate"); g.reset().step_ok_done(); }
          +  -  +  -  +  
                      - ]
    5433                 :             : 
    5434         [ +  - ]:          78 :   database_stats_report();
    5435                 :             : 
    5436   [ +  -  +  - ]:          78 :   (void) statfs_free_enough_p(db_path, "database"); // report sqlite filesystem size
    5437                 :             : 
    5438         [ +  - ]:          78 :   sqlite3_db_release_memory(db); // shrink the process if possible
    5439         [ +  - ]:          78 :   sqlite3_db_release_memory(dbq); // ... for both connections
    5440         [ +  - ]:          78 :   debuginfod_pool_groom(); // and release any debuginfod_client objects we've been holding onto
    5441                 :             : #if HAVE_MALLOC_TRIM
    5442                 :          78 :   malloc_trim(0); // PR31103: release memory allocated for temporary purposes
    5443                 :             : #endif
    5444                 :             :   
    5445                 :             : #if 0 /* PR31265: don't jettison cache unnecessarily */
    5446                 :             :   fdcache.limit(0); // release the fdcache contents
    5447                 :             :   fdcache.limit(fdcache_mbs); // restore status quo parameters
    5448                 :             : #endif
    5449                 :             :   
    5450                 :          78 :   clock_gettime (CLOCK_MONOTONIC, &ts_end);
    5451                 :          78 :   double deltas = (ts_end.tv_sec - ts_start.tv_sec) + (ts_end.tv_nsec - ts_start.tv_nsec)/1.e9;
    5452                 :             : 
    5453   [ +  -  +  -  :         156 :   obatched(clog) << "groomed database in " << deltas << "s" << endl;
             +  -  +  - ]
    5454                 :          78 : }
    5455                 :             : 
    5456                 :             : 
    5457                 :             : static void*
    5458                 :          82 : thread_main_groom (void* /*arg*/)
    5459                 :             : {
    5460   [ +  -  +  -  :         164 :   set_metric("thread_tid", "role", "groom", tid());
                   +  - ]
    5461   [ +  -  +  -  :         164 :   add_metric("thread_count", "role", "groom", 1);
                   +  - ]
    5462                 :             : 
    5463                 :          82 :   time_t last_groom = 0;
    5464                 :             : 
    5465                 :         530 :   while (1)
    5466                 :             :     {
    5467                 :         306 :       sleep (1);
    5468                 :         306 :       scanq.wait_idle(); // PR25394: block scanners during grooming!
    5469         [ +  + ]:         306 :       if (interrupted) break;
    5470                 :             : 
    5471                 :         224 :       time_t now = time(NULL);
    5472                 :         224 :       bool groom_now = false;
    5473         [ +  + ]:         224 :       if (last_groom == 0) // at least one initial groom is documented even for -g0
    5474                 :          72 :         groom_now = true;
    5475   [ +  +  +  + ]:         224 :       if (groom_s > 0 && (long)now > (long)(last_groom + groom_s))
    5476                 :         224 :         groom_now = true;
    5477         [ +  + ]:         224 :       if (sigusr2 != forced_groom_count)
    5478                 :             :         {
    5479                 :           6 :           forced_groom_count = sigusr2;
    5480                 :           6 :           groom_now = true;
    5481                 :             :         }
    5482         [ +  + ]:         224 :       if (groom_now)
    5483                 :             :         {
    5484   [ +  -  +  -  :         156 :           set_metric("thread_busy", "role", "groom", 1);
                   +  - ]
    5485                 :          78 :           try
    5486                 :             :             {
    5487         [ +  - ]:          78 :               groom ();
    5488                 :             :             }
    5489         [ -  - ]:           0 :           catch (const sqlite_exception& e)
    5490                 :             :             {
    5491   [ -  -  -  -  :           0 :               obatched(cerr) << e.message << endl;
                   -  - ]
    5492                 :           0 :             }
    5493                 :          78 :           last_groom = time(NULL); // NB: now was before grooming
    5494                 :             :           // finished a grooming loop
    5495   [ +  -  +  -  :         156 :           inc_metric("thread_work_total", "role", "groom");
                   +  - ]
    5496   [ +  -  +  -  :         156 :           set_metric("thread_busy", "role", "groom", 0);
                   +  - ]
    5497                 :             :         }
    5498                 :             : 
    5499                 :         224 :       scanq.done_idle();
    5500                 :         224 :     }
    5501                 :             : 
    5502                 :          82 :   return 0;
    5503                 :             : }
    5504                 :             : 
    5505                 :             : 
    5506                 :             : ////////////////////////////////////////////////////////////////////////
    5507                 :             : 
    5508                 :             : 
    5509                 :             : static void
    5510                 :          84 : signal_handler (int /* sig */)
    5511                 :             : {
    5512                 :          84 :   interrupted ++;
    5513                 :             : 
    5514         [ +  + ]:          84 :   if (db)
    5515                 :          82 :     sqlite3_interrupt (db);
    5516         [ +  - ]:          84 :   if (dbq)
    5517                 :          84 :     sqlite3_interrupt (dbq);
    5518                 :             : 
    5519                 :             :   // NB: don't do anything else in here
    5520                 :          84 : }
    5521                 :             : 
    5522                 :             : static void
    5523                 :          58 : sigusr1_handler (int /* sig */)
    5524                 :             : {
    5525                 :          58 :    sigusr1 ++;
    5526                 :             :   // NB: don't do anything else in here
    5527                 :          58 : }
    5528                 :             : 
    5529                 :             : static void
    5530                 :           6 : sigusr2_handler (int /* sig */)
    5531                 :             : {
    5532                 :           6 :    sigusr2 ++;
    5533                 :             :   // NB: don't do anything else in here
    5534                 :           6 : }
    5535                 :             : 
    5536                 :             : 
    5537                 :             : static void // error logging callback from libmicrohttpd internals
    5538                 :           0 : error_cb (void *arg, const char *fmt, va_list ap)
    5539                 :             : {
    5540                 :           0 :   (void) arg;
    5541   [ #  #  #  #  :           0 :   inc_metric("error_count","libmicrohttpd",fmt);
                   #  # ]
    5542                 :           0 :   char errmsg[512];
    5543                 :           0 :   (void) vsnprintf (errmsg, sizeof(errmsg), fmt, ap); // ok if slightly truncated
    5544         [ #  # ]:           0 :   obatched(cerr) << "libmicrohttpd error: " << errmsg; // MHD_DLOG calls already include \n
    5545                 :           0 : }
    5546                 :             : 
    5547                 :             : 
    5548                 :             : // A user-defined sqlite function, to score the sharedness of the
    5549                 :             : // prefix of two strings.  This is used to compare candidate debuginfo
    5550                 :             : // / source-rpm names, so that the closest match
    5551                 :             : // (directory-topology-wise closest) is found.  This is important in
    5552                 :             : // case the same sref (source file name) is in many -debuginfo or
    5553                 :             : // -debugsource RPMs, such as when multiple versions/releases of the
    5554                 :             : // same package are in the database.
    5555                 :             : 
    5556                 :        1344 : static void sqlite3_sharedprefix_fn (sqlite3_context* c, int argc, sqlite3_value** argv)
    5557                 :             : {
    5558         [ -  + ]:        1344 :   if (argc != 2)
    5559                 :           0 :     sqlite3_result_error(c, "expect 2 string arguments", -1);
    5560   [ +  -  +  + ]:        2688 :   else if ((sqlite3_value_type(argv[0]) != SQLITE_TEXT) ||
    5561                 :        1344 :            (sqlite3_value_type(argv[1]) != SQLITE_TEXT))
    5562                 :        1082 :     sqlite3_result_null(c);
    5563                 :             :   else
    5564                 :             :     {
    5565                 :         262 :       const unsigned char* a = sqlite3_value_text (argv[0]);
    5566                 :         262 :       const unsigned char* b = sqlite3_value_text (argv[1]);
    5567                 :         262 :       int i = 0;
    5568   [ +  +  +  -  :       27722 :       while (*a != '\0' && *b != '\0' && *a++ == *b++)
                   +  + ]
    5569                 :       27198 :         i++;
    5570                 :         262 :       sqlite3_result_int (c, i);
    5571                 :             :     }
    5572                 :        1344 : }
    5573                 :             : 
    5574                 :             : 
    5575                 :             : static unsigned
    5576                 :         164 : default_concurrency() // guaranteed >= 1
    5577                 :             : {
    5578                 :             :   // Prior to PR29975 & PR29976, we'd just use this: 
    5579                 :         164 :   unsigned sth = std::thread::hardware_concurrency();
    5580                 :             :   // ... but on many-CPU boxes, admins or distros may throttle
    5581                 :             :   // resources in such a way that debuginfod would mysteriously fail.
    5582                 :             :   // So we reduce the defaults:
    5583                 :             : 
    5584                 :         164 :   unsigned aff = 0;
    5585                 :             : #ifdef HAVE_SCHED_GETAFFINITY
    5586                 :         164 :   {
    5587                 :         164 :     int ret;
    5588                 :         164 :     cpu_set_t mask;
    5589                 :         164 :     CPU_ZERO(&mask);
    5590                 :         164 :     ret = sched_getaffinity(0, sizeof(mask), &mask);
    5591         [ +  - ]:         164 :     if (ret == 0)
    5592                 :         164 :       aff = CPU_COUNT(&mask);
    5593                 :             :   }
    5594                 :             : #endif
    5595                 :             :   
    5596                 :         164 :   unsigned fn = 0;
    5597                 :             : #ifdef HAVE_GETRLIMIT
    5598                 :         164 :   {
    5599                 :         164 :     struct rlimit rlim;
    5600                 :         164 :     int rc = getrlimit(RLIMIT_NOFILE, &rlim);
    5601         [ +  - ]:         164 :     if (rc == 0)
    5602         [ -  + ]:         164 :       fn = max((rlim_t)1, (rlim.rlim_cur - 100) / 4);
    5603                 :             :     // at least 2 fds are used by each listener thread etc.
    5604                 :             :     // plus a bunch to account for shared libraries and such
    5605                 :             :   }
    5606                 :             : #endif
    5607                 :             : 
    5608   [ -  +  +  -  :         328 :   unsigned d = min(max(sth, 1U),
                   -  + ]
    5609         [ +  - ]:         164 :                    min(max(aff, 1U),
    5610         [ -  + ]:         164 :                        max(fn, 1U)));
    5611                 :         164 :   return d;
    5612                 :             : }
    5613                 :             : 
    5614                 :             : 
    5615                 :             : // 30879: Something to help out in case of an uncaught exception.
    5616                 :           0 : void my_terminate_handler()
    5617                 :             : {
    5618                 :             : #if defined(__GLIBC__)
    5619                 :           0 :   void *array[40];
    5620                 :           0 :   int size = backtrace (array, 40);
    5621                 :           0 :   backtrace_symbols_fd (array, size, STDERR_FILENO);
    5622                 :             : #endif
    5623                 :             : #if defined(__GLIBCXX__) || defined(__GLIBCPP__)
    5624                 :           0 :   __gnu_cxx::__verbose_terminate_handler();
    5625                 :             : #endif
    5626                 :           0 :   abort();
    5627                 :             : }
    5628                 :             : 
    5629                 :             : 
    5630                 :             : int
    5631                 :          84 : main (int argc, char *argv[])
    5632                 :             : {
    5633                 :          84 :   (void) setlocale (LC_ALL, "");
    5634                 :          84 :   (void) bindtextdomain (PACKAGE_TARNAME, LOCALEDIR);
    5635                 :          84 :   (void) textdomain (PACKAGE_TARNAME);
    5636                 :             : 
    5637                 :          84 :   std::set_terminate(& my_terminate_handler);
    5638                 :             : 
    5639                 :             :   /* Tell the library which version we are expecting.  */
    5640                 :          84 :   elf_version (EV_CURRENT);
    5641                 :             : 
    5642         [ +  - ]:         168 :   tmpdir = string(getenv("TMPDIR") ?: "/tmp");
    5643                 :             : 
    5644                 :             :   /* Set computed default values. */
    5645   [ -  +  +  -  :          84 :   db_path = string(getenv("HOME") ?: "/") + string("/.debuginfod.sqlite"); /* XDG? */
                   +  - ]
    5646                 :          84 :   int rc = regcomp (& file_include_regex, ".*", REG_EXTENDED|REG_NOSUB); // match everything
    5647         [ -  + ]:          84 :   if (rc != 0)
    5648                 :           0 :     error (EXIT_FAILURE, 0, "regcomp failure: %d", rc);
    5649                 :          84 :   rc = regcomp (& file_exclude_regex, "^$", REG_EXTENDED|REG_NOSUB); // match nothing
    5650         [ -  + ]:          84 :   if (rc != 0)
    5651                 :           0 :     error (EXIT_FAILURE, 0, "regcomp failure: %d", rc);
    5652                 :             : 
    5653                 :             :   // default parameters for fdcache are computed from system stats
    5654                 :          84 :   struct statfs sfs;
    5655                 :          84 :   rc = statfs(tmpdir.c_str(), &sfs);
    5656         [ -  + ]:          84 :   if (rc < 0)
    5657                 :           0 :     fdcache_mbs = 1024; // 1 gigabyte
    5658                 :             :   else
    5659                 :          84 :     fdcache_mbs = sfs.f_bavail * sfs.f_bsize / 1024 / 1024 / 4; // 25% of free space
    5660                 :          84 :   fdcache_mintmp = 25; // emergency flush at 25% remaining (75% full)
    5661                 :          84 :   fdcache_prefetch = 64; // guesstimate storage is this much less costly than re-decompression
    5662                 :             : 
    5663                 :             :   /* Parse and process arguments.  */
    5664                 :          84 :   memset(&http_sockaddr, 0, sizeof(http_sockaddr));
    5665                 :          84 :   http_sockaddr.sin6_family = AF_UNSPEC;
    5666                 :          84 :   int remaining;
    5667                 :          84 :   (void) argp_parse (&argp, argc, argv, ARGP_IN_ORDER, &remaining, NULL);
    5668         [ -  + ]:          84 :   if (remaining != argc)
    5669                 :           0 :       error (EXIT_FAILURE, 0,
    5670                 :           0 :              "unexpected argument: %s", argv[remaining]);
    5671                 :             : 
    5672   [ +  +  +  +  :          84 :   if (scan_archives.size()==0 && !scan_files && source_paths.size()>0)
                   -  + ]
    5673         [ #  # ]:           0 :     obatched(clog) << "warning: without -F -R -U -Z, ignoring PATHs" << endl;
    5674                 :             : 
    5675                 :          84 :   fdcache.limit(fdcache_mbs);
    5676                 :             : 
    5677                 :          84 :   (void) signal (SIGPIPE, SIG_IGN); // microhttpd can generate it incidentally, ignore
    5678                 :          84 :   (void) signal (SIGINT, signal_handler); // ^C
    5679                 :          84 :   (void) signal (SIGHUP, signal_handler); // EOF
    5680                 :          84 :   (void) signal (SIGTERM, signal_handler); // systemd
    5681                 :          84 :   (void) signal (SIGUSR1, sigusr1_handler); // end-user
    5682                 :          84 :   (void) signal (SIGUSR2, sigusr2_handler); // end-user
    5683                 :             : 
    5684                 :             :   /* Get database ready. */
    5685         [ +  + ]:          84 :   if (! passive_p)
    5686                 :             :     {
    5687                 :          82 :       rc = sqlite3_open_v2 (db_path.c_str(), &db, (SQLITE_OPEN_READWRITE
    5688                 :             :                                                    |SQLITE_OPEN_URI
    5689                 :             :                                                    |SQLITE_OPEN_PRIVATECACHE
    5690                 :             :                                                    |SQLITE_OPEN_CREATE
    5691                 :             :                                                    |SQLITE_OPEN_FULLMUTEX), /* thread-safe */
    5692                 :             :                             NULL);
    5693         [ -  + ]:          82 :       if (rc == SQLITE_CORRUPT)
    5694                 :             :         {
    5695                 :           0 :           (void) unlink (db_path.c_str());
    5696                 :           0 :           error (EXIT_FAILURE, 0,
    5697                 :             :                  "cannot open %s, deleted database: %s", db_path.c_str(), sqlite3_errmsg(db));
    5698                 :             :         }
    5699         [ -  + ]:          82 :       else if (rc)
    5700                 :             :         {
    5701                 :           0 :           error (EXIT_FAILURE, 0,
    5702                 :             :                  "cannot open %s, consider deleting database: %s", db_path.c_str(), sqlite3_errmsg(db));
    5703                 :             :         }
    5704                 :             :     }
    5705                 :             : 
    5706                 :             :   // open the readonly query variant
    5707                 :             :   // NB: PRIVATECACHE allows web queries to operate in parallel with
    5708                 :             :   // much other grooming/scanning operation.
    5709                 :          84 :   rc = sqlite3_open_v2 (db_path.c_str(), &dbq, (SQLITE_OPEN_READONLY
    5710                 :             :                                                 |SQLITE_OPEN_URI
    5711                 :             :                                                 |SQLITE_OPEN_PRIVATECACHE
    5712                 :             :                                                 |SQLITE_OPEN_FULLMUTEX), /* thread-safe */
    5713                 :             :                         NULL);
    5714         [ -  + ]:          84 :   if (rc)
    5715                 :             :     {
    5716                 :           0 :       error (EXIT_FAILURE, 0,
    5717                 :             :              "cannot open %s, consider deleting database: %s", db_path.c_str(), sqlite3_errmsg(dbq));
    5718                 :             :     }
    5719                 :             : 
    5720                 :             : 
    5721         [ +  - ]:         168 :   obatched(clog) << "opened database " << db_path
    5722   [ +  +  +  -  :          86 :                  << (db?" rw":"") << (dbq?" ro":"") << endl;
          -  +  +  -  +  
                      - ]
    5723   [ +  -  +  - ]:         168 :   obatched(clog) << "sqlite version " << sqlite3_version << endl;
    5724   [ +  +  +  -  :         250 :   obatched(clog) << "service mode " << (passive_p ? "passive":"active") << endl;
                   +  - ]
    5725                 :             : 
    5726                 :             :   // add special string-prefix-similarity function used in rpm sref/sdef resolution
    5727                 :          84 :   rc = sqlite3_create_function(dbq, "sharedprefix", 2, SQLITE_UTF8, NULL,
    5728                 :             :                                & sqlite3_sharedprefix_fn, NULL, NULL);
    5729         [ -  + ]:          84 :   if (rc != SQLITE_OK)
    5730                 :           0 :     error (EXIT_FAILURE, 0,
    5731                 :             :            "cannot create sharedprefix function: %s", sqlite3_errmsg(dbq));
    5732                 :             : 
    5733         [ +  + ]:          84 :   if (! passive_p)
    5734                 :             :     {
    5735         [ +  + ]:          82 :       if (verbose > 3)
    5736   [ +  -  +  - ]:          92 :         obatched(clog) << "ddl: " << DEBUGINFOD_SQLITE_DDL << endl;
    5737                 :          82 :       rc = sqlite3_exec (db, DEBUGINFOD_SQLITE_DDL, NULL, NULL, NULL);
    5738         [ -  + ]:          82 :       if (rc != SQLITE_OK)
    5739                 :             :         {
    5740                 :           0 :           error (EXIT_FAILURE, 0,
    5741                 :             :                  "cannot run database schema ddl: %s", sqlite3_errmsg(db));
    5742                 :             :         }
    5743                 :             :     }
    5744                 :             : 
    5745   [ +  -  +  -  :         168 :   obatched(clog) << "libmicrohttpd version " << MHD_get_version() << endl;
                   +  - ]
    5746                 :             :   
    5747                 :             :   /* If '-C' wasn't given or was given with no arg, pick a reasonable default
    5748                 :             :      for the number of worker threads.  */
    5749         [ +  + ]:          84 :   if (connection_pool == 0)
    5750                 :          80 :     connection_pool = default_concurrency();
    5751                 :             : 
    5752                 :             :   /* Note that MHD_USE_EPOLL and MHD_USE_THREAD_PER_CONNECTION don't
    5753                 :             :      work together.  */
    5754                 :          84 :   unsigned int use_epoll = 0;
    5755                 :             : #if MHD_VERSION >= 0x00095100
    5756                 :          84 :   use_epoll = MHD_USE_EPOLL;
    5757                 :             : #endif
    5758                 :             : 
    5759                 :          84 :   unsigned int mhd_flags = (
    5760                 :             : #if MHD_VERSION >= 0x00095300
    5761                 :             :                             MHD_USE_INTERNAL_POLLING_THREAD
    5762                 :             : #else
    5763                 :             :                             MHD_USE_SELECT_INTERNALLY
    5764                 :             : #endif
    5765                 :             :                             | MHD_USE_DUAL_STACK
    5766                 :             :                             | use_epoll
    5767                 :             : #if MHD_VERSION >= 0x00095200
    5768                 :             :                             | MHD_USE_ITC
    5769                 :             : #endif
    5770                 :             :                             | MHD_USE_DEBUG); /* report errors to stderr */
    5771                 :             : 
    5772                 :          84 :   MHD_Daemon *dsa = NULL,
    5773                 :          84 :              *d4 = NULL,
    5774                 :          84 :              *d46 = NULL;
    5775                 :             : 
    5776         [ -  + ]:          84 :   if (http_sockaddr.sin6_family != AF_UNSPEC)
    5777                 :             :     {
    5778         [ #  # ]:           0 :       if (http_sockaddr.sin6_family == AF_INET)
    5779                 :           0 :         ((sockaddr_in*)&http_sockaddr)->sin_port = htons(http_port);
    5780         [ #  # ]:           0 :       if (http_sockaddr.sin6_family == AF_INET6)
    5781                 :           0 :         http_sockaddr.sin6_port = htons(http_port);
    5782                 :             :       // Start httpd server threads on socket addr:port.
    5783                 :           0 :       dsa = MHD_start_daemon (mhd_flags & ~MHD_USE_DUAL_STACK, http_port,
    5784                 :             :                               NULL, NULL, /* default accept policy */
    5785                 :             :                              handler_cb, NULL, /* handler callback */
    5786                 :             :                              MHD_OPTION_EXTERNAL_LOGGER,
    5787                 :             :                              error_cb, NULL,
    5788                 :             :                              MHD_OPTION_SOCK_ADDR,
    5789                 :             :                              (struct sockaddr *) &http_sockaddr,
    5790                 :             :                              MHD_OPTION_THREAD_POOL_SIZE,
    5791                 :             :                              (int)connection_pool,
    5792                 :             :                              MHD_OPTION_END);
    5793                 :             :     }
    5794                 :             :   else
    5795                 :             :     {
    5796                 :             :       // Start httpd server threads.  Use a single dual-homed pool.
    5797                 :          84 :       d46 = MHD_start_daemon (mhd_flags, http_port,
    5798                 :             :                               NULL, NULL, /* default accept policy */
    5799                 :             :                               handler_cb, NULL, /* handler callback */
    5800                 :             :                               MHD_OPTION_EXTERNAL_LOGGER,
    5801                 :             :                               error_cb, NULL,
    5802                 :             :                               MHD_OPTION_THREAD_POOL_SIZE,
    5803                 :             :                               (int)connection_pool,
    5804                 :             :                               MHD_OPTION_END);
    5805                 :          84 :       addr_info = "IPv4 IPv6";
    5806         [ -  + ]:          84 :       if (d46 == NULL)
    5807                 :             :         {
    5808                 :             :           // Cannot use dual_stack, use ipv4 only
    5809                 :           0 :           mhd_flags &= ~(MHD_USE_DUAL_STACK);
    5810         [ #  # ]:           0 :           d4 = MHD_start_daemon (mhd_flags, http_port,
    5811                 :             :                                  NULL, NULL, /* default accept policy */
    5812                 :             :                                  handler_cb, NULL, /* handler callback */
    5813                 :             :                                  MHD_OPTION_EXTERNAL_LOGGER,
    5814                 :             :                                  error_cb, NULL,
    5815                 :             :                                  (connection_pool
    5816                 :             :                                   ? MHD_OPTION_THREAD_POOL_SIZE
    5817                 :             :                                   : MHD_OPTION_END),
    5818                 :             :                                  (connection_pool
    5819                 :             :                                   ? (int)connection_pool
    5820                 :             :                                   : MHD_OPTION_END),
    5821                 :             :                                  MHD_OPTION_END);
    5822                 :           0 :           addr_info = "IPv4";
    5823                 :             :         }
    5824                 :             :     }
    5825   [ -  +  -  - ]:          84 :   if (d4 == NULL && d46 == NULL && dsa == NULL)
    5826                 :             :     {
    5827                 :           0 :       sqlite3 *database = db;
    5828                 :           0 :       sqlite3 *databaseq = dbq;
    5829                 :           0 :       db = dbq = 0; // for signal_handler not to freak
    5830                 :           0 :       sqlite3_close (databaseq);
    5831                 :           0 :       sqlite3_close (database);
    5832                 :           0 :       error (EXIT_FAILURE, 0, "cannot start http server on %s port %d",
    5833                 :             :              addr_info.c_str(), http_port);
    5834                 :             :     }
    5835                 :             : 
    5836         [ +  - ]:         168 :   obatched(clog) << "started http server on "
    5837                 :             :                  << addr_info
    5838         [ +  - ]:          84 :                  << " port=" << http_port
    5839   [ +  -  +  +  :         162 :                  << (webapi_cors ? " with cors" : "")
             +  -  +  - ]
    5840                 :          84 :                  << endl;
    5841                 :             : 
    5842                 :             :   // add maxigroom sql if -G given
    5843         [ -  + ]:          84 :   if (maxigroom)
    5844                 :             :     {
    5845         [ #  # ]:           0 :       obatched(clog) << "maxigrooming database, please wait." << endl;
    5846                 :             :       // NB: this index alone can nearly double the database size!
    5847                 :             :       // NB: this index would be necessary to run source-file metadata searches fast
    5848                 :           0 :       extra_ddl.push_back("create index if not exists " BUILDIDS "_r_sref_arc on " BUILDIDS "_r_sref(artifactsrc);");
    5849                 :           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);");
    5850                 :           0 :       extra_ddl.push_back("drop index if exists " BUILDIDS "_r_sref_arc;");
    5851                 :             : 
    5852                 :             :       // NB: we don't maxigroom the _files interning table.  It'd require a temp index on all the
    5853                 :             :       // tables that have file foreign-keys, which is a lot.
    5854                 :             : 
    5855                 :             :       // NB: with =delete, may take up 3x disk space total during vacuum process
    5856                 :             :       //     vs.  =off (only 2x but may corrupt database if program dies mid-vacuum)
    5857                 :             :       //     vs.  =wal (>3x observed, but safe)
    5858                 :           0 :       extra_ddl.push_back("pragma journal_mode=delete;");
    5859                 :           0 :       extra_ddl.push_back("vacuum;");
    5860                 :           0 :       extra_ddl.push_back("pragma journal_mode=wal;");
    5861                 :             :     }
    5862                 :             : 
    5863                 :             :   // run extra -D sql if given
    5864         [ +  + ]:          84 :   if (! passive_p)
    5865         [ -  + ]:          82 :     for (auto&& i: extra_ddl)
    5866                 :             :       {
    5867         [ #  # ]:           0 :         if (verbose > 1)
    5868   [ #  #  #  # ]:           0 :           obatched(clog) << "extra ddl:\n" << i << endl;
    5869                 :           0 :         rc = sqlite3_exec (db, i.c_str(), NULL, NULL, NULL);
    5870   [ #  #  #  # ]:           0 :         if (rc != SQLITE_OK && rc != SQLITE_DONE && rc != SQLITE_ROW)
    5871                 :           0 :           error (0, 0,
    5872                 :             :                  "warning: cannot run database extra ddl %s: %s", i.c_str(), sqlite3_errmsg(db));
    5873                 :             : 
    5874         [ #  # ]:           0 :         if (maxigroom)
    5875         [ #  # ]:           0 :           obatched(clog) << "maxigroomed database" << endl;
    5876                 :             :       }
    5877                 :             : 
    5878         [ +  + ]:          84 :   if (! passive_p)
    5879   [ +  -  +  - ]:         164 :     obatched(clog) << "search concurrency " << concurrency << endl;
    5880                 :          84 :   obatched(clog) << "webapi connection pool " << connection_pool
    5881   [ +  -  -  +  :          84 :                  << (connection_pool ? "" : " (unlimited)") << endl;
             +  -  +  - ]
    5882         [ +  + ]:          84 :   if (! passive_p) {
    5883   [ +  -  +  - ]:         164 :     obatched(clog) << "rescan time " << rescan_s << endl;
    5884   [ +  -  +  - ]:         164 :     obatched(clog) << "scan checkpoint " << scan_checkpoint << endl;
    5885                 :             :   }
    5886   [ +  -  +  - ]:         168 :   obatched(clog) << "fdcache mbs " << fdcache_mbs << endl;
    5887   [ +  -  +  - ]:         168 :   obatched(clog) << "fdcache prefetch " << fdcache_prefetch << endl;
    5888   [ +  -  +  - ]:         168 :   obatched(clog) << "fdcache tmpdir " << tmpdir << endl;
    5889   [ +  -  +  - ]:         168 :   obatched(clog) << "fdcache tmpdir min% " << fdcache_mintmp << endl;
    5890         [ +  + ]:          84 :   if (! passive_p)
    5891   [ +  -  +  - ]:         164 :     obatched(clog) << "groom time " << groom_s << endl;
    5892   [ +  -  +  - ]:         168 :   obatched(clog) << "forwarded ttl limit " << forwarded_ttl_limit << endl;
    5893                 :             : 
    5894         [ +  + ]:          84 :   if (scan_archives.size()>0)
    5895                 :             :     {
    5896                 :          56 :       obatched ob(clog);
    5897         [ +  - ]:          56 :       auto& o = ob << "accepting archive types ";
    5898         [ +  + ]:         174 :       for (auto&& arch : scan_archives)
    5899   [ +  -  +  -  :         118 :         o << arch.first << "(" << arch.second << ") ";
             +  -  +  - ]
    5900         [ +  - ]:          56 :       o << endl;
    5901                 :          56 :     }
    5902                 :          84 :   const char* du = getenv(DEBUGINFOD_URLS_ENV_VAR);
    5903   [ +  +  +  + ]:          84 :   if (du && du[0] != '\0') // set to non-empty string?
    5904   [ +  -  +  - ]:          32 :     obatched(clog) << "upstream debuginfod servers: " << du << endl;
    5905                 :             : 
    5906         [ +  + ]:          84 :   vector<pthread_t> all_threads;
    5907                 :             : 
    5908         [ +  + ]:          84 :   if (! passive_p)
    5909                 :             :     {
    5910                 :          82 :       pthread_t pt;
    5911                 :          82 :       rc = pthread_create (& pt, NULL, thread_main_groom, NULL);
    5912         [ -  + ]:          82 :       if (rc)
    5913                 :           0 :         error (EXIT_FAILURE, rc, "cannot spawn thread to groom database\n");
    5914                 :             :       else
    5915                 :             :         {
    5916                 :             : #ifdef HAVE_PTHREAD_SETNAME_NP
    5917                 :          82 :           (void) pthread_setname_np (pt, "groom");
    5918                 :             : #endif
    5919         [ +  - ]:          82 :           all_threads.push_back(pt);
    5920                 :             :         }
    5921                 :             : 
    5922   [ +  +  +  + ]:          82 :       if (scan_files || scan_archives.size() > 0)
    5923                 :             :         {
    5924         [ +  - ]:          72 :           if (scan_checkpoint > 0)
    5925         [ +  - ]:          72 :             scan_barrier = new sqlite_checkpoint_pb(concurrency, (unsigned) scan_checkpoint);
    5926                 :             : 
    5927                 :          72 :           rc = pthread_create (& pt, NULL, thread_main_fts_source_paths, NULL);
    5928         [ -  + ]:          72 :           if (rc)
    5929                 :           0 :             error (EXIT_FAILURE, rc, "cannot spawn thread to traverse source paths\n");
    5930                 :             : #ifdef HAVE_PTHREAD_SETNAME_NP
    5931                 :          72 :           (void) pthread_setname_np (pt, "traverse");
    5932                 :             : #endif
    5933         [ +  - ]:          72 :           all_threads.push_back(pt);
    5934                 :             : 
    5935         [ +  + ]:         360 :           for (unsigned i=0; i<concurrency; i++)
    5936                 :             :             {
    5937                 :         288 :               rc = pthread_create (& pt, NULL, thread_main_scanner, NULL);
    5938         [ -  + ]:         288 :               if (rc)
    5939                 :           0 :                 error (EXIT_FAILURE, rc, "cannot spawn thread to scan source files / archives\n");
    5940                 :             : #ifdef HAVE_PTHREAD_SETNAME_NP
    5941                 :         288 :               (void) pthread_setname_np (pt, "scan");
    5942                 :             : #endif
    5943         [ +  - ]:         288 :               all_threads.push_back(pt);
    5944                 :             :             }
    5945                 :             :         }
    5946                 :             :     }
    5947                 :             :   
    5948                 :             :   /* Trivial main loop! */
    5949   [ +  -  +  - ]:          84 :   set_metric("ready", 1);
    5950         [ +  + ]:         232 :   while (! interrupted)
    5951         [ +  - ]:         148 :     pause ();
    5952         [ +  - ]:          84 :   scanq.nuke(); // wake up any remaining scanq-related threads, let them die
    5953   [ +  +  +  - ]:          84 :   if (scan_barrier) scan_barrier->nuke(); // ... in case they're stuck in a barrier
    5954   [ +  -  +  - ]:          84 :   set_metric("ready", 0);
    5955                 :             : 
    5956         [ +  - ]:          84 :   if (verbose)
    5957   [ +  -  +  -  :         168 :     obatched(clog) << "stopping" << endl;
                   -  - ]
    5958                 :             : 
    5959                 :             :   /* Join all our threads. */
    5960         [ +  + ]:         526 :   for (auto&& it : all_threads)
    5961         [ +  - ]:         442 :     pthread_join (it, NULL);
    5962                 :             : 
    5963                 :             :   /* Stop all the web service threads. */
    5964   [ -  +  -  - ]:          84 :   if (dsa) MHD_stop_daemon (dsa);
    5965   [ +  -  +  - ]:          84 :   if (d46) MHD_stop_daemon (d46);
    5966   [ -  +  -  - ]:          84 :   if (d4) MHD_stop_daemon (d4);
    5967                 :             : 
    5968         [ +  + ]:          84 :   if (! passive_p)
    5969                 :             :     {
    5970                 :             :       /* With all threads known dead, we can clean up the global resources. */
    5971         [ +  - ]:          82 :       rc = sqlite3_exec (db, DEBUGINFOD_SQLITE_CLEANUP_DDL, NULL, NULL, NULL);
    5972         [ -  + ]:          82 :       if (rc != SQLITE_OK)
    5973                 :             :         {
    5974         [ #  # ]:           0 :           error (0, 0,
    5975                 :             :                  "warning: cannot run database cleanup ddl: %s", sqlite3_errmsg(db));
    5976                 :             :         }
    5977                 :             :     }
    5978                 :             : 
    5979         [ +  - ]:          84 :   debuginfod_pool_groom ();
    5980         [ +  + ]:          84 :   delete scan_barrier;
    5981                 :             : 
    5982                 :             :   // NB: no problem with unconditional free here - an earlier failed regcomp would exit program
    5983         [ +  - ]:          84 :   (void) regfree (& file_include_regex);
    5984         [ +  - ]:          84 :   (void) regfree (& file_exclude_regex);
    5985                 :             : 
    5986                 :          84 :   sqlite3 *database = db;
    5987                 :          84 :   sqlite3 *databaseq = dbq;
    5988                 :          84 :   db = dbq = 0; // for signal_handler not to freak
    5989         [ +  - ]:          84 :   (void) sqlite3_close (databaseq);
    5990         [ +  + ]:          84 :   if (! passive_p)
    5991         [ +  - ]:          82 :     (void) sqlite3_close (database);
    5992                 :             : 
    5993         [ +  + ]:          84 :   return 0;
    5994                 :          84 : }
        

Generated by: LCOV version 2.0-1