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