• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tstack / lnav / 23384146477-2867

21 Mar 2026 04:44PM UTC coverage: 69.017% (+0.01%) from 69.003%
23384146477-2867

push

github

tstack
[indexing] prevent nested rebuilds

24 of 38 new or added lines in 6 files covered. (63.16%)

2 existing lines in 2 files now uncovered.

52755 of 76438 relevant lines covered (69.02%)

522011.28 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

71.95
/src/lnav.cc
1
/**
2
 * Copyright (c) 2007-2016, Timothy Stack
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 * * Redistributions of source code must retain the above copyright notice, this
10
 * list of conditions and the following disclaimer.
11
 * * Redistributions in binary form must reproduce the above copyright notice,
12
 * this list of conditions and the following disclaimer in the documentation
13
 * and/or other materials provided with the distribution.
14
 * * Neither the name of Timothy Stack nor the names of its contributors
15
 * may be used to endorse or promote products derived from this software
16
 * without specific prior written permission.
17
 *
18
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
19
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
22
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
 *
29
 * @file lnav.cc
30
 *
31
 * XXX This file has become a dumping ground for code and needs to be broken up
32
 * a bit.
33
 */
34

35
#include <locale.h>
36
#include <signal.h>
37
#include <stdio.h>
38
#include <stdlib.h>
39
#include <string.h>
40
#include <sys/ioctl.h>
41
#include <sys/stat.h>
42
#include <sys/time.h>
43
#include <sys/wait.h>
44
#include <unistd.h>
45

46
#include "config.h"
47

48
#if defined(__OpenBSD__) && defined(__clang__) \
49
    && !defined(_WCHAR_H_CPLUSPLUS_98_CONFORMANCE_)
50
#    define _WCHAR_H_CPLUSPLUS_98_CONFORMANCE_
51
#endif
52
#include <algorithm>
53
#include <exception>
54
#include <filesystem>
55
#include <map>
56
#include <memory>
57
#include <string>
58
#include <unordered_set>
59
#include <utility>
60
#include <vector>
61

62
#include <sqlite3.h>
63

64
#ifdef HAVE_BZLIB_H
65
#    include <bzlib.h>
66
#endif
67

68
#include "all_logs_vtab.hh"
69
#include "base/ansi_scrubber.hh"
70
#include "base/ansi_vars.hh"
71
#include "base/fs_util.hh"
72
#include "base/func_util.hh"
73
#include "base/humanize.hh"
74
#include "base/humanize.time.hh"
75
#include "base/injector.bind.hh"
76
#include "base/injector.hh"
77
#include "base/isc.hh"
78
#include "base/itertools.hh"
79
#include "base/lnav.console.hh"
80
#include "base/lnav_log.hh"
81
#include "base/paths.hh"
82
#include "base/progress.hh"
83
#include "base/relative_time.hh"
84
#include "base/string_util.hh"
85
#include "bottom_status_source.hh"
86
#include "bound_tags.hh"
87
#include "breadcrumb_curses.hh"
88
#include "CLI/CLI.hpp"
89
#include "date/tz.h"
90
#include "dump_internals.hh"
91
#include "environ_vtab.hh"
92
#include "ext.longpoll.hh"
93
#include "file_converter_manager.hh"
94
#include "file_options.hh"
95
#include "filter_sub_source.hh"
96
#include "fstat_vtab.hh"
97
#include "hist_source.hh"
98
#include "init-sql.h"
99
#include "listview_curses.hh"
100
#include "lnav.events.hh"
101
#include "lnav.exec-phase.hh"
102
#include "lnav.hh"
103
#include "lnav.indexing.hh"
104
#include "lnav.management_cli.hh"
105
#include "lnav.prompt.hh"
106
#include "lnav_commands.hh"
107
#include "lnav_config.hh"
108
#include "lnav_util.hh"
109
#include "log_data_helper.hh"
110
#include "log_data_table.hh"
111
#include "log_format_loader.hh"
112
#include "log_gutter_source.hh"
113
#include "log_stmt_vtab.hh"
114
#include "log_vtab_impl.hh"
115
#include "logfile.hh"
116
#include "logfile_sub_source.hh"
117
#include "logline_window.hh"
118
#include "md4cpp.hh"
119
#include "piper.looper.hh"
120
#include "readline_context.hh"
121
#include "readline_highlighters.hh"
122
#include "regexp_vtab.hh"
123
#include "scn/scan.h"
124
#include "service_tags.hh"
125
#include "session_data.hh"
126
#include "spectro_source.hh"
127
#include "sql_help.hh"
128
#include "sql_util.hh"
129
#include "sqlite-extension-func.hh"
130
#include "sqlitepp.client.hh"
131
#include "static_file_vtab.hh"
132
#include "tailer/tailer.looper.hh"
133
#include "term_extra.hh"
134
#include "termios_guard.hh"
135
#include "textfile_highlighters.hh"
136
#include "textinput_curses.hh"
137
#include "textview_curses.hh"
138
#include "timeline_source.hh"
139
#include "top_status_source.hh"
140
#include "view_helpers.crumbs.hh"
141
#include "view_helpers.examples.hh"
142
#include "view_helpers.hist.hh"
143
#include "views_vtab.hh"
144
#include "xpath_vtab.hh"
145
#include "xterm_mouse.hh"
146

147
#ifdef HAVE_LIBCURL
148
#    include <curl/curl.h>
149
#endif
150

151
#include "curl_looper.hh"
152

153
#if HAVE_ARCHIVE_H
154
#    include <archive.h>
155
#endif
156

157
#include "archive_manager.hh"
158
#include "command_executor.hh"
159
#include "field_overlay_source.hh"
160
#include "fmt/compile.h"
161
#include "hotkeys.hh"
162
#include "readline_callbacks.hh"
163
#include "readline_possibilities.hh"
164
#include "url_loader.hh"
165
#include "yajlpp/json_ptr.hh"
166

167
#ifdef HAVE_RUST_DEPS
168
#    include "lnav_rs_ext.cxx.hh"
169
#endif
170

171
#ifndef SYSCONFDIR
172
#    define SYSCONFDIR "/usr/etc"
173
#endif
174

175
using namespace std::literals::chrono_literals;
176
using namespace lnav::roles::literals;
177
using namespace md4cpp::literals;
178

179
static std::vector<std::filesystem::path> DEFAULT_FILES;
180
static auto intern_lifetime = intern_string::get_table_lifetime();
181

182
static std::vector<std::pair<const char*, std::future<void>>> CLEANUP_TASKS;
183

184
template<std::intmax_t N>
185
class to_string_t {
186
    constexpr static auto buflen() noexcept
187
    {
188
        unsigned int len = N > 0 ? 1 : 2;
189
        for (auto n = N; n; len++, n /= 10)
190
            ;
191
        return len;
192
    }
193

194
    char buf[buflen()] = {};
195

196
public:
197
    constexpr to_string_t() noexcept
198
    {
199
        auto ptr = buf + buflen();
200
        *--ptr = '\0';
201

202
        if (N != 0) {
203
            for (auto n = N; n; n /= 10)
204
                *--ptr = "0123456789"[(N < 0 ? -1 : 1) * (n % 10)];
205
            if (N < 0)
206
                *--ptr = '-';
207
        } else {
208
            buf[0] = '0';
209
        }
210
    }
211

212
    constexpr operator const char*() const { return buf; }
213
};
214

215
static const std::unordered_set<std::string> DEFAULT_DB_KEY_NAMES = {
216
    "$id",         "capture_count", "capture_index",
217
    "device",      "enabled",       "filter_id",
218
    "id",          "inode",         "key",
219
    "match_index", "parent",        "range_start",
220
    "range_stop",  "rowid",         "st_dev",
221
    "st_gid",      "st_ino",        "st_mode",
222
    "st_rdev",     "st_uid",        "pattern",
223
    "paused",      "filtering",     "begin_line",
224
    "end_line",
225
};
226

227
static auto bound_pollable_supervisor
228
    = injector::bind<pollable_supervisor>::to_singleton();
229

230
static auto bound_active_files = injector::bind<file_collection>::to_instance(
231
    +[] { return &lnav_data.ld_active_files; });
795✔
232

233
static auto bound_sqlite_db
234
    = injector::bind<auto_sqlite3>::to_instance(&lnav_data.ld_db);
235

236
static auto bound_lnav_flags
237
    = injector::bind<lnav_flags_storage>::to_instance(&lnav_data.ld_flags);
238

239
static auto bound_lnav_exec_context
240
    = injector::bind<exec_context>::to_instance(&lnav_data.ld_exec_context);
241

242
static auto bound_log_source
243
    = injector::bind<logfile_sub_source>::to_instance(&lnav_data.ld_log_source);
244

245
static auto bound_last_rel_time
246
    = injector::bind<relative_time, last_relative_time_tag>::to_singleton();
247

248
static auto bound_term_extra = injector::bind<term_extra>::to_singleton();
249

250
static auto bound_xterm_mouse = injector::bind<xterm_mouse>::to_singleton();
251

252
static auto bound_scripts = injector::bind<available_scripts>::to_singleton();
253

254
static auto bound_curl
255
    = injector::bind_multiple<isc::service_base>()
256
          .add_singleton<curl_looper, services::curl_streamer_t>();
257

258
static auto bound_tailer
259
    = injector::bind_multiple<isc::service_base>()
260
          .add_singleton<tailer::looper, services::remote_tailer_t>();
261

262
static auto bound_main = injector::bind_multiple<static_service>()
263
                             .add_singleton<main_looper, services::main_t>();
264

265
static auto bound_file_options_hier
266
    = injector::bind<lnav::safe_file_options_hier>::to_singleton();
267

268
static auto bound_exec_phase = injector::bind<lnav::exec_phase>::to_singleton();
269

270
namespace injector {
271
template<>
272
void
273
force_linking(last_relative_time_tag anno)
3✔
274
{
275
}
3✔
276

277
template<>
278
void
279
force_linking(services::curl_streamer_t anno)
649✔
280
{
281
}
649✔
282

283
template<>
284
void
285
force_linking(services::remote_tailer_t anno)
21✔
286
{
287
}
21✔
288

289
template<>
290
void
291
force_linking(services::main_t anno)
652✔
292
{
293
}
652✔
294
}  // namespace injector
295

296
lnav_data_t lnav_data;
297

298
static auto nc_debug = false;
299

300
bool
301
setup_logline_table(exec_context& ec)
518✔
302
{
303
    auto* vtab_manager = injector::get<log_vtab_manager*>();
518✔
304
    auto& log_view = lnav_data.ld_views[LNV_LOG];
518✔
305
    bool retval = false;
518✔
306

307
    if (log_view.get_inner_height()) {
518✔
308
        static const intern_string_t logline = intern_string::lookup("logline");
949✔
309
        auto vl = log_view.get_selection();
433✔
310
        if (vl) {
433✔
311
            auto cl = lnav_data.ld_log_source.at_base(vl.value());
433✔
312
            auto file_and_line_opt
313
                = lnav_data.ld_log_source.find_line_with_file(cl);
433✔
314
            if (file_and_line_opt && file_and_line_opt->second->is_message()) {
433✔
315
                vtab_manager->unregister_vtab(logline.to_string_fragment());
425✔
316
                vtab_manager->register_vtab(std::make_shared<log_data_table>(
425✔
317
                    lnav_data.ld_log_source, *vtab_manager, cl, logline));
318
                retval = true;
425✔
319
            }
320
        }
433✔
321
    }
322

323
    auto& db_key_names = lnav_data.ld_db_key_names;
518✔
324

325
    db_key_names = DEFAULT_DB_KEY_NAMES;
518✔
326

327
    for (const auto& iter : *vtab_manager) {
47,703✔
328
        iter.second->get_foreign_keys(db_key_names);
47,185✔
329
    }
330

331
    return retval;
518✔
332
}
333

334
static bool
335
append_default_files()
×
336
{
337
    bool retval = true;
×
338
    auto cwd = std::filesystem::current_path();
×
339

340
    for (const auto& path : DEFAULT_FILES) {
×
341
        if (access(path.c_str(), R_OK) == 0) {
×
342
            auto full_path = cwd / path;
×
343
            auto realpath_res = lnav::filesystem::realpath(full_path);
×
344
            if (realpath_res.isOk()) {
×
345
                auto abspath = realpath_res.unwrap();
×
346
                lnav_data.ld_active_files.fc_file_names[abspath.string()]
×
347
                    .with_time_range(lnav_data.ld_default_time_range);
×
348
            } else {
×
349
                log_error("realpath() failed: %s -- %s",
×
350
                          full_path.c_str(),
351
                          realpath_res.unwrapErr().c_str());
352
            }
353
        } else if (lnav::filesystem::stat_file(path).isOk()) {
×
354
            lnav::console::print(
×
355
                stderr,
356
                lnav::console::user_message::error(
×
357
                    attr_line_t("default syslog file is not readable -- ")
×
358
                        .append(lnav::roles::file(cwd))
×
359
                        .append(lnav::roles::file(path))));
×
360
            retval = false;
×
361
        }
362
    }
363

364
    return retval;
×
365
}
366

367
static void
368
sigint(int sig)
×
369
{
370
    auto counter = lnav_data.ld_sigint_count.fetch_add(1);
×
371
    if (counter >= 3) {
×
372
        abort();
×
373
    }
374
}
375

376
static void
377
sigwinch(int sig)
×
378
{
379
    lnav_data.ld_winched = true;
×
380
}
381

382
static void
383
sigchld(int sig)
63✔
384
{
385
    lnav_data.ld_child_terminated = true;
63✔
386
}
63✔
387

388
static void
389
handle_rl_key(notcurses* nc, const ncinput& ch, const char* keyseq)
×
390
{
391
    static auto& prompt = lnav::prompt::get();
392

393
    switch (ch.eff_text[0]) {
×
394
        case NCKEY_F02: {
×
395
            auto& mouse_i = injector::get<xterm_mouse&>();
×
396
            mouse_i.set_enabled(nc, !mouse_i.is_enabled());
×
397
            break;
×
398
        }
399
        case NCKEY_F03:
×
400
            handle_paging_key(nc, ch, keyseq);
×
401
            break;
×
402
        case NCKEY_PGUP:
×
403
        case NCKEY_PGDOWN:
404
            if (prompt.p_editor.tc_height == 1) {
×
405
                handle_paging_key(nc, ch, keyseq);
×
406
            } else {
407
                prompt.p_editor.handle_key(ch);
×
408
            }
409
            break;
×
410

411
        default:
×
412
            prompt.p_editor.handle_key(ch);
×
413
            if (prompt.p_editor.tc_lines.size() > 1
×
414
                && prompt.p_editor.tc_height == 1)
×
415
            {
416
                prompt.p_editor.set_height(5);
×
417
            }
418
            break;
×
419
    }
420
}
421

422
readline_context::command_map_t lnav_commands;
423

424
static attr_line_t
425
command_arg_help()
1✔
426
{
427
    return attr_line_t()
2✔
428
        .append(
1✔
429
            "command arguments must start with one of the following symbols "
430
            "to denote the type of command:\n")
431
        .append("   ")
1✔
432
        .append(":"_symbol)
1✔
433
        .append(" - ")
1✔
434
        .append("an lnav command   (e.g. :goto 42)\n")
1✔
435
        .append("   ")
1✔
436
        .append(";"_symbol)
1✔
437
        .append(" - ")
1✔
438
        .append("an SQL statement  (e.g. ;SELECT * FROM syslog_log)\n")
1✔
439
        .append("   ")
1✔
440
        .append("|"_symbol)
1✔
441
        .append(" - ")
1✔
442
        .append("an lnav script    (e.g. |rename-stdin foo)\n");
2✔
443
}
444

445
static void
446
usage()
1✔
447
{
448
    attr_line_t ex1_term;
1✔
449

450
    ex1_term.append(lnav::roles::ok("$"))
1✔
451
        .append(" ")
1✔
452
        .append(lnav::roles::file("lnav"))
2✔
453
        .pad_to(40)
1✔
454
        .with_attr_for_all(VC_ROLE.value(role_t::VCR_QUOTED_CODE));
1✔
455

456
    attr_line_t ex2_term;
1✔
457

458
    ex2_term.append(lnav::roles::ok("$"))
1✔
459
        .append(" ")
1✔
460
        .append(lnav::roles::file("lnav"))
2✔
461
        .append(" ")
1✔
462
        .append(lnav::roles::file("/var/log"))
2✔
463
        .pad_to(40)
1✔
464
        .with_attr_for_all(VC_ROLE.value(role_t::VCR_QUOTED_CODE));
1✔
465

466
    attr_line_t ex3_term;
1✔
467

468
    ex3_term.append(lnav::roles::ok("$"))
1✔
469
        .append(" ")
1✔
470
        .append(lnav::roles::file("lnav"))
2✔
471
        .append(" ")
1✔
472
        .append("-e"_symbol)
1✔
473
        .append(" '")
1✔
474
        .append(lnav::roles::file("make"))
2✔
475
        .append(" ")
1✔
476
        .append("-j4"_symbol)
1✔
477
        .append("' ")
1✔
478
        .pad_to(40)
1✔
479
        .with_attr_for_all(VC_ROLE.value(role_t::VCR_QUOTED_CODE));
1✔
480

481
    attr_line_t usage_al;
1✔
482

483
    usage_al.append("usage"_h1)
1✔
484
        .append(": ")
1✔
485
        .append(lnav::roles::file(lnav_data.ld_program_name))
1✔
486
        .append(" [")
1✔
487
        .append("options"_variable)
1✔
488
        .append("] [")
1✔
489
        .append("logfile1"_variable)
1✔
490
        .append(" ")
1✔
491
        .append("logfile2"_variable)
1✔
492
        .append(" ")
1✔
493
        .append("..."_variable)
1✔
494
        .append("]\n")
1✔
495
        .append(R"(
1✔
496
A log file viewer for the terminal that indexes log messages to
497
make it easier to navigate through files quickly.
498

499
)")
500
        .append("Key Bindings"_h2)
1✔
501
        .append("\n")
1✔
502
        .append("  ?"_symbol)
1✔
503
        .append("    View/leave the online help text.\n")
1✔
504
        .append("  q"_symbol)
1✔
505
        .append("    Quit the program.\n")
1✔
506
        .append("\n")
1✔
507
        .append("Options"_h2)
1✔
508
        .append("\n")
1✔
509
        .append("  ")
1✔
510
        .append("-h"_symbol)
1✔
511
        .append("         ")
1✔
512
        .append("Print this message, then exit.\n")
1✔
513
        .append("  ")
1✔
514
        .append("-H"_symbol)
1✔
515
        .append("         ")
1✔
516
        .append("Display the internal help text.\n")
1✔
517
        .append("\n")
1✔
518
        .append("  ")
1✔
519
        .append("-I"_symbol)
1✔
520
        .append(" ")
1✔
521
        .append("dir"_variable)
1✔
522
        .append("     ")
1✔
523
        .append("An additional configuration directory.\n")
1✔
524
        .append("  ")
1✔
525
        .append("-W"_symbol)
1✔
526
        .append("         ")
1✔
527
        .append("Print warnings related to lnav's configuration.\n")
1✔
528
        .append("  ")
1✔
529
        .append("-u"_symbol)
1✔
530
        .append("         ")
1✔
531
        .append("Update formats installed from git repositories.\n")
1✔
532
        .append("  ")
1✔
533
        .append("-d"_symbol)
1✔
534
        .append(" ")
1✔
535
        .append("file"_variable)
1✔
536
        .append("    ")
1✔
537
        .append("Write debug messages to the given file.\n")
1✔
538
        .append("  ")
1✔
539
        .append("-V"_symbol)
1✔
540
        .append("         ")
1✔
541
        .append("Print version information.\n")
1✔
542
        .append("\n")
1✔
543

544
        .append("  ")
1✔
545
        .append("-S"_symbol)
1✔
546
        .append(",")
1✔
547
        .append("--since"_symbol)
1✔
548
        .append(" ")
1✔
549
        .append("Only index log content since this time.\n")
1✔
550
        .append("  ")
1✔
551
        .append("-U"_symbol)
1✔
552
        .append(",")
1✔
553
        .append("--until"_symbol)
1✔
554
        .append(" ")
1✔
555
        .append("Only index log content until this time.\n")
1✔
556
        .append("  ")
1✔
557
        .append("-r"_symbol)
1✔
558
        .append("         ")
1✔
559
        .append(
1✔
560
            "Recursively load files from the given directory hierarchies.\n")
561
        .append("  ")
1✔
562
        .append("-R"_symbol)
1✔
563
        .append("         ")
1✔
564
        .append("Load older rotated log files as well.\n")
1✔
565
        .append("  ")
1✔
566
        .append("-c"_symbol)
1✔
567
        .append(" ")
1✔
568
        .append("cmd"_variable)
1✔
569
        .append("     ")
1✔
570
        .append("Execute a command after the files have been loaded.\n")
1✔
571
        .append("  ")
1✔
572
        .append("-f"_symbol)
1✔
573
        .append(" ")
1✔
574
        .append("file"_variable)
1✔
575
        .append("    ")
1✔
576
        .append("Execute the commands in the given file.\n")
1✔
577
        .append("  ")
1✔
578
        .append("-e"_symbol)
1✔
579
        .append(" ")
1✔
580
        .append("cmd"_variable)
1✔
581
        .append("     ")
1✔
582
        .append("Execute a shell command-line.\n")
1✔
583
        .append("  ")
1✔
584
        .append("-t"_symbol)
1✔
585
        .append("         ")
1✔
586
        .append("Treat data piped into standard in as a log file.\n")
1✔
587
        .append("  ")
1✔
588
        .append("-n"_symbol)
1✔
589
        .append("         ")
1✔
590
        .append("Run without the curses UI. (headless mode)\n")
1✔
591
        .append("  ")
1✔
592
        .append("-N"_symbol)
1✔
593
        .append("         ")
1✔
594
        .append("Do not open the default syslog file if no files are given.\n")
1✔
595
        .append("  ")
1✔
596
        .append("-q"_symbol)
1✔
597
        .append("         ")
1✔
598
        .append("Do not print informational messages.\n")
1✔
599
        .append("\n")
1✔
600
        .append("Optional arguments"_h2)
1✔
601
        .append("\n")
1✔
602
        .append("  ")
1✔
603
        .append("logfileN"_variable)
1✔
604
        .append(R"(   The log files, directories, or remote paths to view.
1✔
605
             If a directory is given, all of the files in the
606
             directory will be loaded.
607
)")
608
        .append("\n")
1✔
609
        .append("Management-Mode Options"_h2)
1✔
610
        .append("\n")
1✔
611
        .append("  ")
1✔
612
        .append("-i"_symbol)
1✔
613
        .append("         ")
1✔
614
        .append(R"(Install the given format files and exit.  Pass 'extra'
1✔
615
             to install the default set of third-party formats.
616
)")
617
        .append("  ")
1✔
618
        .append("-m"_symbol)
1✔
619
        .append("         ")
1✔
620
        .append(R"(Switch to the management command-line mode.  This mode
1✔
621
             is used to work with lnav's configuration.
622
)")
623
        .append("\n")
1✔
624
        .append("Examples"_h2)
1✔
625
        .append("\n ")
1✔
626
        .append("\u2022"_list_glyph)
1✔
627
        .append(" To load and follow the syslog file:\n")
1✔
628
        .append("     ")
1✔
629
        .append(ex1_term)
1✔
630
        .append("\n\n ")
1✔
631
        .append("\u2022"_list_glyph)
1✔
632
        .append(" To load all of the files in ")
1✔
633
        .append(lnav::roles::file("/var/log"))
2✔
634
        .append(":\n")
1✔
635
        .append("     ")
1✔
636
        .append(ex2_term)
1✔
637
        .append("\n\n ")
1✔
638
        .append("\u2022"_list_glyph)
1✔
639
        .append(" To watch the output of ")
1✔
640
        .append(lnav::roles::file("make"))
2✔
641
        .append(":\n")
1✔
642
        .append("     ")
1✔
643
        .append(ex3_term)
1✔
644
        .append("\n\n")
1✔
645
        .append("Paths"_h2)
1✔
646
        .append("\n ")
1✔
647
        .append("\u2022"_list_glyph)
1✔
648
        .append(" Format files are read from:")
1✔
649
        .append("\n    ")
1✔
650
        .append(":open_file_folder:"_emoji)
2✔
651
        .append(" ")
1✔
652
        .append(lnav::roles::file("/etc/lnav"))
2✔
653
        .append("\n    ")
1✔
654
        .append(":open_file_folder:"_emoji)
2✔
655
        .append(" ")
1✔
656
        .append(lnav::roles::file(SYSCONFDIR "/lnav"))
2✔
657
        .append("\n ")
1✔
658
        .append("\u2022"_list_glyph)
1✔
659
        .append(" Configuration, session, and format files are stored in:\n")
1✔
660
        .append("    ")
1✔
661
        .append(":open_file_folder:"_emoji)
2✔
662
        .append(" ")
1✔
663
        .append(lnav::roles::file(lnav::paths::dotlnav().string()))
2✔
664
        .append("\n\n ")
1✔
665
        .append("\u2022"_list_glyph)
1✔
666
        .append(" Local copies of remote files, files extracted from\n")
1✔
667
        .append("   archives, execution output, and so on are stored in:\n")
1✔
668
        .append("    ")
1✔
669
        .append(":open_file_folder:"_emoji)
2✔
670
        .append(" ")
1✔
671
        .append(lnav::roles::file(lnav::paths::workdir().string()))
2✔
672
        .append("\n\n")
1✔
673
        .append("Documentation"_h1)
1✔
674
        .append(": ")
1✔
675
        .append("https://docs.lnav.org"_hyperlink)
1✔
676
        .append("\n")
1✔
677
        .append("Contact"_h1)
1✔
678
        .append("\n")
1✔
679
        .append("  ")
1✔
680
        .append(":speech_balloon:"_emoji)
2✔
681
        .append(" https://github.com/tstack/lnav/discussions\n")
1✔
682
        .append("  ")
1✔
683
        .append(":mailbox:"_emoji)
2✔
684
        .appendf(FMT_STRING(" {}\n"), PACKAGE_BUGREPORT)
4✔
685
        .append("Version"_h1)
1✔
686
        .appendf(FMT_STRING(": {}"), VCS_PACKAGE_STRING);
4✔
687

688
    lnav::console::println(stderr, usage_al);
1✔
689
}
1✔
690

691
static void
692
clear_last_user_mark(listview_curses* lv)
29✔
693
{
694
    auto* tc = (textview_curses*) lv;
29✔
695
    if (lnav_data.ld_select_start.find(tc) != lnav_data.ld_select_start.end()
29✔
696
        && !tc->is_line_visible(vis_line_t(lnav_data.ld_last_user_mark[tc])))
29✔
697
    {
698
        lnav_data.ld_select_start.erase(tc);
×
699
        lnav_data.ld_last_user_mark.erase(tc);
×
700
    }
701
}
29✔
702

703
static void
704
update_view_position(listview_curses* lv)
29✔
705
{
706
    lnav_data.ld_view_stack.top() | [lv](auto* top_lv) {
29✔
707
        if (lv != top_lv) {
29✔
708
            return;
13✔
709
        }
710

711
        lnav_data.ld_bottom_source.update_line_number(lv);
16✔
712
        lnav_data.ld_bottom_source.update_percent(lv);
16✔
713
        lnav_data.ld_bottom_source.update_marks(lv);
16✔
714
        lnav_data.ld_status[LNS_BOTTOM].set_needs_update();
16✔
715
    };
716
}
29✔
717

718
static bool
719
handle_config_ui_key(notcurses* nc, const ncinput& ch, const char* keyseq)
7✔
720
{
721
    bool retval = false;
7✔
722

723
    if (ch.id == NCKEY_F02) {
7✔
724
        auto& mouse_i = injector::get<xterm_mouse&>();
×
725
        mouse_i.set_enabled(nc, !mouse_i.is_enabled());
×
726
        return true;
×
727
    }
728

729
    switch (lnav_data.ld_mode) {
7✔
730
        case ln_mode_t::FILES:
1✔
731
            if (ch.id == NCKEY_PASTE) {
1✔
732
                handle_paste_content(nc, ch);
×
733
                return true;
×
734
            }
735

736
            if (ch.eff_text[0] == NCKEY_GS
2✔
737
                || (ch.id == ']' && ncinput_ctrl_p(&ch)))
1✔
738
            {
739
                set_view_mode(ln_mode_t::FILE_DETAILS);
×
740
                retval = true;
×
741
            } else {
742
                retval = lnav_data.ld_files_view.handle_key(ch);
1✔
743
            }
744
            break;
1✔
745
        case ln_mode_t::FILE_DETAILS:
×
746
            if (ch.id == NCKEY_ESC || ch.eff_text[0] == NCKEY_GS
×
747
                || (ch.id == ']' && ncinput_ctrl_p(&ch)))
×
748
            {
749
                set_view_mode(ln_mode_t::FILES);
×
750
                retval = true;
×
751
            } else {
752
                retval = lnav_data.ld_file_details_view.handle_key(ch);
×
753
            }
754
            break;
×
755
        case ln_mode_t::FILTER:
6✔
756
            retval = lnav_data.ld_filter_view.handle_key(ch);
6✔
757
            break;
6✔
758
        default:
×
759
            ensure(0);
×
760
    }
761

762
    if (retval) {
7✔
763
        return retval;
5✔
764
    }
765

766
    std::optional<ln_mode_t> new_mode;
2✔
767

768
    lnav_data.ld_filter_help_status_source.fss_error.clear();
2✔
769
    if (ch.id == 'F') {
2✔
770
        new_mode = ln_mode_t::FILES;
×
771
    } else if (ch.id == 'T') {
2✔
772
        new_mode = ln_mode_t::FILTER;
×
773
    } else if (ch.id == '\t'
2✔
774
               || (ch.id == NCKEY_TAB && ch.modifiers & NCKEY_MOD_SHIFT))
1✔
775
    {
776
        if (lnav_data.ld_mode == ln_mode_t::FILES) {
1✔
777
            new_mode = ln_mode_t::FILTER;
1✔
778
        } else {
779
            new_mode = ln_mode_t::FILES;
×
780
        }
781
    } else if (ch.id == 'q' || ch.id == NCKEY_ESC) {
1✔
782
        new_mode = ln_mode_t::PAGING;
1✔
783
    }
784

785
    if (new_mode) {
2✔
786
        if (new_mode.value() == ln_mode_t::FILES
2✔
787
            || new_mode.value() == ln_mode_t::FILTER)
2✔
788
        {
789
            lnav_data.ld_last_config_mode = new_mode.value();
1✔
790
        }
791
        set_view_mode(new_mode.value());
2✔
792
        lnav_data.ld_files_view.reload_data();
2✔
793
        lnav_data.ld_file_details_view.reload_data();
2✔
794
        lnav_data.ld_filter_view.reload_data();
2✔
795
        lnav_data.ld_status[LNS_FILTER].set_needs_update();
2✔
796
    } else {
797
        return handle_paging_key(nc, ch, keyseq);
×
798
    }
799

800
    return true;
2✔
801
}
802

803
static bool
804
handle_key(notcurses* nc, const ncinput& ch, const char* keyseq)
10✔
805
{
806
    static auto* breadcrumb_view = injector::get<breadcrumb_curses*>();
10✔
807

808
    switch (ch.id) {
10✔
809
        case NCKEY_RESIZE:
×
810
            break;
×
811
        default: {
10✔
812
            switch (lnav_data.ld_mode) {
10✔
813
                case ln_mode_t::PAGING:
3✔
814
                    return handle_paging_key(nc, ch, keyseq);
3✔
815

816
                case ln_mode_t::BREADCRUMBS:
×
817
                    if (ch.id == '`' || !breadcrumb_view->handle_key(ch)) {
×
818
                        set_view_mode(ln_mode_t::PAGING);
×
819
                        return true;
×
820
                    }
821
                    return true;
×
822

823
                case ln_mode_t::FILTER:
7✔
824
                case ln_mode_t::FILES:
825
                case ln_mode_t::FILE_DETAILS:
826
                    return handle_config_ui_key(nc, ch, keyseq);
7✔
827

828
                case ln_mode_t::SPECTRO_DETAILS: {
×
829
                    if (ch.id == '\t' || ch.id == 'q') {
×
830
                        set_view_mode(ln_mode_t::PAGING);
×
831
                        return true;
×
832
                    }
833
                    if (ch.id == NCKEY_PASTE) {
×
834
                        handle_paste_content(nc, ch);
×
835
                        return true;
×
836
                    }
837
                    if (lnav_data.ld_spectro_details_view.handle_key(ch)) {
×
838
                        return true;
×
839
                    }
840
                    switch (ch.eff_text[0]) {
×
841
                        case 'n': {
×
842
                            execute_command(lnav_data.ld_exec_context,
×
843
                                            "next-mark search");
844
                            return true;
×
845
                        }
846
                        case 'N': {
×
847
                            execute_command(lnav_data.ld_exec_context,
×
848
                                            "prev-mark search");
849
                            return true;
×
850
                        }
851
                        case '/': {
×
852
                            execute_command(lnav_data.ld_exec_context,
×
853
                                            "prompt search-spectro-details");
854
                            return true;
×
855
                        }
856
                    }
857
                    return false;
×
858
                }
859

860
                case ln_mode_t::COMMAND:
×
861
                case ln_mode_t::SEARCH:
862
                case ln_mode_t::SEARCH_FILTERS:
863
                case ln_mode_t::SEARCH_FILES:
864
                case ln_mode_t::SEARCH_SPECTRO_DETAILS:
865
                case ln_mode_t::CAPTURE:
866
                case ln_mode_t::SQL:
867
                case ln_mode_t::EXEC:
868
                case ln_mode_t::USER:
869
                    handle_rl_key(nc, ch, keyseq);
×
870
                    break;
×
871

872
                case ln_mode_t::BUSY: {
×
873
                    if (ch.id == NCKEY_ESC || ch.eff_text[0] == NCKEY_GS
×
874
                        || (ch.id == ']' && ncinput_ctrl_p(&ch)))
×
875
                    {
876
                        log_vtab_data.lvd_looping = false;
×
877
                    }
878
                    break;
×
879
                }
880

881
                default:
×
882
                    require(0);
×
883
                    break;
884
            }
885
        }
886
    }
887

888
    return true;
×
889
}
890

891
static input_dispatcher::escape_match_t
892
match_escape_seq(const char* keyseq)
×
893
{
894
    if (lnav_data.ld_mode != ln_mode_t::PAGING) {
×
895
        return input_dispatcher::escape_match_t::NONE;
×
896
    }
897

898
    auto& km = lnav_config.lc_active_keymap;
×
899
    auto iter = km.km_seq_to_cmd.find(keyseq);
×
900
    if (iter != km.km_seq_to_cmd.end()) {
×
901
        return input_dispatcher::escape_match_t::FULL;
×
902
    }
903

904
    auto lb = km.km_seq_to_cmd.lower_bound(keyseq);
×
905
    if (lb == km.km_seq_to_cmd.end()) {
×
906
        return input_dispatcher::escape_match_t::NONE;
×
907
    }
908

909
    auto ub = km.km_seq_to_cmd.upper_bound(keyseq);
×
910
    auto longest = max_element(
×
911
        lb, ub, [](auto l, auto r) { return l.first.size() < r.first.size(); });
×
912

913
    if (strlen(keyseq) < longest->first.size()) {
×
914
        return input_dispatcher::escape_match_t::PARTIAL;
×
915
    }
916

917
    return input_dispatcher::escape_match_t::NONE;
×
918
}
919

920
static bool
921
gather_pipers()
3,058✔
922
{
923
    auto retval = false;
3,058✔
924
    for (auto iter = lnav_data.ld_child_pollers.begin();
3,058✔
925
         iter != lnav_data.ld_child_pollers.end();)
3,065✔
926
    {
927
        if ((*iter)->poll(lnav_data.ld_active_files)
7✔
928
            == child_poll_result_t::FINISHED)
7✔
929
        {
930
            iter = lnav_data.ld_child_pollers.erase(iter);
6✔
931
            retval = true;
6✔
932
        } else {
933
            ++iter;
1✔
934
        }
935
    }
936

937
    return retval;
3,058✔
938
}
939

940
void
941
wait_for_pipers(std::optional<ui_clock::time_point> deadline)
3,051✔
942
{
943
    static constexpr auto MAX_SLEEP_TIME = 300ms;
944
    auto sleep_time = 10ms;
3,051✔
945
    auto loop_count = 0;
3,051✔
946

947
    for (;;) {
948
        gather_pipers();
3,058✔
949
        auto piper_count = lnav_data.ld_active_files.active_pipers();
3,058✔
950
        if (piper_count == 0 && lnav_data.ld_child_pollers.empty()) {
3,058✔
951
            if (loop_count > 0) {
3,051✔
952
                log_debug("all pipers finished");
7✔
953
            }
954
            break;
3,051✔
955
        }
956
        if (deadline && ui_clock::now() > deadline.value()) {
7✔
957
            break;
×
958
        }
959
        // Use usleep() since it is defined to be interruptable by a signal.
960
        auto urc = usleep(
7✔
961
            std::chrono::duration_cast<std::chrono::microseconds>(sleep_time)
7✔
962
                .count());
7✔
963
        if (urc == -1 && errno == EINTR) {
7✔
UNCOV
964
            log_trace("wait_for_pipers(): sleep interrupted");
×
965
        }
966
        rebuild_indexes();
7✔
967

968
        log_debug("%zu pipers and %zu children are still active",
7✔
969
                  piper_count,
970
                  lnav_data.ld_child_pollers.size());
971
        if (sleep_time < MAX_SLEEP_TIME) {
7✔
972
            sleep_time = sleep_time * 2;
7✔
973
        }
974
        loop_count += 1;
7✔
975
    }
7✔
976
}
3,051✔
977

978
struct refresh_status_bars {
979
    refresh_status_bars(std::shared_ptr<top_status_source> top_source)
2✔
980
        : rsb_top_source(std::move(top_source))
2✔
981
    {
982
    }
2✔
983

984
    using injectable
985
        = refresh_status_bars(std::shared_ptr<top_status_source> top_source);
986

987
    lnav::progress_result_t doit(lnav::func::op_type ot) const
17✔
988
    {
989
        static auto* breadcrumb_view = injector::get<breadcrumb_curses*>();
17✔
990
        static auto& prompt = lnav::prompt::get();
17✔
991
        static auto& exec_phase = injector::get<lnav::exec_phase&>();
17✔
992
        static const auto cancel_msg
993
            = lnav::console::user_message::info(
2✔
994
                  attr_line_t("performing operation, press ")
2✔
995
                      .append("CTRL+]"_hotkey)
2✔
996
                      .append(" to cancel"))
2✔
997
                  .to_attr_line();
19✔
998
        auto retval = lnav::progress_result_t::ok;
17✔
999
        timeval current_time{};
17✔
1000
        ncinput ch;
1001

1002
        if (!lnav_data.ld_looping || lnav_data.ld_view_stack.empty()) {
17✔
1003
            return lnav::progress_result_t::interrupt;
4✔
1004
        }
1005

1006
        gettimeofday(&current_time, nullptr);
13✔
1007
        auto time_diff = current_time - this->rsb_last_loop_read_time;
13✔
1008
        if (to_us(time_diff) > (exec_phase.interactive() ? 100ms : 2s)) {
13✔
1009
            while (notcurses_get_nblock(this->rsb_screen->get_notcurses(), &ch)
2✔
1010
                   > 0)
2✔
1011
            {
NEW
1012
                lnav_data.ld_user_message_source.clear();
×
NEW
1013
                if ((!exec_phase.interactive() && ch.id == 'q')
×
NEW
1014
                    || (ncinput_ctrl_p(&ch) && ch.id == ']'))
×
1015
                {
NEW
1016
                    lnav_data.ld_bottom_source.update_loading(0, 0);
×
NEW
1017
                    lnav_data.ld_status[LNS_BOTTOM].set_needs_update();
×
NEW
1018
                    retval = lnav::progress_result_t::interrupt;
×
1019
                } else {
NEW
1020
                    log_warning(
×
1021
                        "ignoring input while refreshing status bars: %x",
1022
                        ch.id);
1023
                }
1024

NEW
1025
                ncinput_free_paste_content(&ch);
×
1026

NEW
1027
                if (!lnav_data.ld_looping) {
×
1028
                    // No reason to keep processing input after the
1029
                    // user has quit.  The view stack will also be
1030
                    // empty, which will cause issues.
NEW
1031
                    retval = lnav::progress_result_t::interrupt;
×
NEW
1032
                    break;
×
1033
                }
1034
            }
1035
        }
1036

1037
        if (ot == lnav::func::op_type::interactive) {
13✔
1038
        } else if (lnav_data.ld_bottom_source
6✔
1039
                       .get_field(bottom_status_source::BSF_LOADING)
6✔
1040
                       .empty())
6✔
1041
        {
1042
            prompt.p_editor.clear_inactive_value();
6✔
1043
        } else if (prompt.p_editor.tc_inactive_value.al_string
×
1044
                   != cancel_msg.al_string)
×
1045
        {
1046
            prompt.p_editor.set_inactive_value(cancel_msg);
×
1047
        }
1048

1049
        if (lnav_data.ld_progress_source.poll()) {
13✔
1050
            layout_views();
×
1051
            lnav_data.ld_progress_view.reload_data();
×
1052
            lnav_data.ld_progress_view.do_update();
×
1053
        }
1054
        if (!lnav_data.ld_log_source.is_indexing_in_progress()
13✔
1055
            || lnav_data.ld_log_source.lss_index_generation == 0)
13✔
1056
        {
1057
            if (lnav_data.ld_log_source.lss_index_generation == 0
20✔
1058
                && !lnav_data.ld_view_stack.empty())
10✔
1059
            {
1060
                lnav_data.ld_view_stack.top().value()->set_needs_update();
2✔
1061
            }
1062
            lnav_data.ld_view_stack.do_update();
10✔
1063
        }
1064
        if (lnav_data.ld_mode == ln_mode_t::FILES) {
13✔
1065
            lnav_data.ld_files_view.do_update();
×
1066
            lnav_data.ld_file_details_view.do_update();
×
1067
        }
1068
        if (this->rsb_top_source->update_time(current_time)) {
13✔
1069
            lnav_data.ld_status[LNS_TOP].set_needs_update();
×
1070
        }
1071
        for (auto& sc : lnav_data.ld_status) {
130✔
1072
            sc.do_update();
117✔
1073
        }
1074
        breadcrumb_view->do_update();
13✔
1075
        lnav::prompt::get().p_editor.do_update();
13✔
1076
        if (handle_winch(this->rsb_screen)) {
13✔
1077
            layout_views();
×
1078
            lnav_data.ld_view_stack.do_update();
×
1079
        }
1080

1081
        notcurses_render(this->rsb_screen->get_notcurses());
13✔
1082

1083
        return retval;
13✔
1084
    }
1085

1086
    screen_curses* rsb_screen;
1087
    std::shared_ptr<top_status_source> rsb_top_source;
1088
    timeval rsb_last_loop_read_time{0};
1089
};
1090

1091
static void
1092
check_for_enough_colors(const screen_curses& sc)
2✔
1093
{
1094
    const auto* nc_caps = notcurses_capabilities(sc.get_notcurses());
2✔
1095
    auto last_run_diff = (std::filesystem::file_time_type::clock::now()
2✔
1096
                          - lnav_data.ld_last_dot_lnav_time);
2✔
1097
    auto last_run_diff_h
1098
        = std::chrono::duration_cast<std::chrono::hours>(last_run_diff);
2✔
1099
    if (nc_caps->colors >= 256 || last_run_diff_h < 24h) {
2✔
1100
        return;
2✔
1101
    }
1102
    auto um = lnav::console::user_message::info(
×
1103
                  attr_line_t("The terminal ")
×
1104
                      .append_quoted(getenv("TERM"))
×
1105
                      .append(" appears to have a limited color "
×
1106
                              "palette, which can make things hard "
1107
                              "to read"))
1108
                  .with_reason(attr_line_t("The terminal appears to only have ")
×
1109
                                   .append(lnav::roles::number(
×
1110
                                       fmt::to_string(nc_caps->colors)))
×
1111
                                   .append(" colors"))
×
1112
                  .with_help(attr_line_t("Try setting ")
×
1113
                                 .append("TERM"_symbol)
×
1114
                                 .append(" to ")
×
1115
                                 .append_quoted("xterm-256color"));
×
1116
    lnav_data.ld_user_message_source.replace_with(um.to_attr_line());
×
1117
    lnav_data.ld_user_message_view.reload_data();
×
1118
    lnav_data.ld_user_message_expiration
1119
        = std::chrono::steady_clock::now() + 20s;
×
1120
}
1121

1122
static void
1123
check_for_file_zones()
1✔
1124
{
1125
    auto with_tz_count = 0;
1✔
1126
    std::vector<std::string> without_tz_files;
1✔
1127

1128
    for (const auto& lf : lnav_data.ld_active_files.fc_files) {
2✔
1129
        auto format = lf->get_format_ptr();
1✔
1130
        if (format == nullptr) {
1✔
1131
            continue;
×
1132
        }
1133

1134
        if (format->lf_timestamp_flags & ETF_ZONE_SET
1✔
1135
            || format->lf_date_time.dts_default_zone != nullptr)
1✔
1136
        {
1137
            with_tz_count += 1;
×
1138
        } else {
1139
            without_tz_files.emplace_back(lf->get_unique_path());
1✔
1140
        }
1141
    }
1142
    if (with_tz_count > 0 && !without_tz_files.empty()
×
1143
        && !lnav_data.ld_exec_context.ec_msg_callback_stack.empty())
1✔
1144
    {
1145
        const auto note
1146
            = attr_line_t("The file(s) without a zone: ")
×
1147
                  .join(without_tz_files, VC_ROLE.value(role_t::VCR_FILE), ", ")
×
1148
                  .move();
×
1149
        const auto um
1150
            = lnav::console::user_message::warning(
×
1151
                  "Some messages may not be sorted by time correctly")
1152
                  .with_reason(
×
1153
                      "There are one or more files whose messages do not have "
1154
                      "a timezone in their timestamps mixed in with files that "
1155
                      "do have timezones")
1156
                  .with_note(note)
×
1157
                  .with_help(
×
1158
                      attr_line_t("Use the ")
×
1159
                          .append(":set-file-timezone"_symbol)
×
1160
                          .append(
×
1161
                              " command to set the zone for messages in files "
1162
                              "that do not include a zone in the timestamp"))
1163
                  .move();
×
1164

1165
        lnav_data.ld_exec_context.ec_msg_callback_stack.back()(um);
×
1166
    }
1167
}
1✔
1168

1169
static void
1170
ui_execute_init_commands(
2✔
1171
    exec_context& ec,
1172
    std::vector<std::pair<Result<std::string, lnav::console::user_message>,
1173
                          std::string>>& cmd_results,
1174
    std::optional<ui_clock::time_point> deadline)
1175
{
1176
    if (lnav_data.ld_commands.empty()) {
2✔
1177
        lnav_data.ld_cmd_init_done = true;
1✔
1178
        return;
1✔
1179
    }
1180

1181
    std::error_code errc;
1✔
1182
    std::filesystem::create_directories(lnav::paths::workdir(), errc);
1✔
1183
    auto open_temp_res = lnav::filesystem::open_temp_file(lnav::paths::workdir()
2✔
1184
                                                          / "exec.XXXXXX");
3✔
1185

1186
    if (open_temp_res.isErr()) {
1✔
1187
        lnav::prompt::get().p_editor.set_inactive_value(
×
1188
            fmt::format(FMT_STRING("Unable to open temporary output file: {}"),
×
1189
                        open_temp_res.unwrapErr()));
×
1190
    } else {
1191
        auto tmp_pair = open_temp_res.unwrap();
1✔
1192
        auto fd_copy = tmp_pair.second.dup();
1✔
1193
        auto tf = text_format_t::TF_PLAINTEXT;
1✔
1194

1195
        {
1196
            exec_context::output_guard og(
1197
                ec,
1198
                "tmp",
1199
                std::make_pair(fdopen(tmp_pair.second.release(), "w"), fclose));
2✔
1200
            execute_init_commands(ec, cmd_results);
1✔
1201
            tf = ec.ec_output_stack.back().od_format;
1✔
1202
        }
1✔
1203

1204
        struct stat st;
1205
        if (fstat(fd_copy, &st) != -1 && st.st_size > 0) {
1✔
1206
            static const auto OUTPUT_NAME
1207
                = std::string("Initial command output");
×
1208
            lnav_data.ld_active_files.fc_file_names[tmp_pair.first]
×
1209
                .with_filename(OUTPUT_NAME)
×
1210
                .with_include_in_session(false)
×
1211
                .with_detect_format(false)
×
1212
                .with_text_format(tf)
×
1213
                .with_init_location(0_vl);
×
1214
            lnav_data.ld_files_to_front.emplace_back(OUTPUT_NAME);
×
1215

1216
            lnav::prompt::get().p_editor.set_alt_value(
×
1217
                HELP_MSG_1(X, "to close the file"));
1218
        }
1219
    }
1✔
1220
}
1✔
1221

1222
static void
1223
run_cleanup_tasks()
651✔
1224
{
1225
    CLEANUP_TASKS.emplace_back("line_buffer", line_buffer::cleanup_cache());
651✔
1226
    CLEANUP_TASKS.emplace_back("archive_manager",
651✔
1227
                               archive_manager::cleanup_cache());
1,302✔
1228
    CLEANUP_TASKS.emplace_back("tailer", tailer::cleanup_cache());
651✔
1229
    CLEANUP_TASKS.emplace_back("piper", lnav::piper::cleanup());
651✔
1230
    CLEANUP_TASKS.emplace_back("file_converter_manager",
651✔
1231
                               file_converter_manager::cleanup());
1,302✔
1232
}
651✔
1233

1234
static void
1235
looper()
2✔
1236
{
1237
    auto filter_sub_life
1238
        = injector::bind<filter_sub_source>::to_scoped_singleton();
2✔
1239
    auto crumb_life = injector::bind<breadcrumb_curses>::to_scoped_singleton();
2✔
1240
    auto* ps = injector::get<pollable_supervisor*>();
2✔
1241
    auto* filter_source = injector::get<filter_sub_source*>();
2✔
1242
    auto* breadcrumb_view = injector::get<breadcrumb_curses*>();
2✔
1243
    auto& exec_phase = injector::get<lnav::exec_phase&>();
2✔
1244

1245
    auto& ec = lnav_data.ld_exec_context;
2✔
1246
    sig_atomic_t overlay_counter = 0;
2✔
1247

1248
    lnav_data.ld_filter_view.set_sub_source(filter_source)
2✔
1249
        .add_input_delegate(*filter_source);
2✔
1250
    lnav_data.ld_log_source.lss_sorting_observer
1251
        = [](auto& lss, auto off, auto size) {
8✔
1252
              if (off == (file_ssize_t) size) {
6✔
1253
                  lnav_data.ld_bottom_source.update_loading(0, 0);
5✔
1254
              } else {
1255
                  lnav_data.ld_bottom_source.update_loading(off, size);
1✔
1256
              }
1257
              do_observer_update(nullptr);
6✔
1258
              lnav_data.ld_status[LNS_BOTTOM].set_needs_update();
6✔
1259
          };
2✔
1260

1261
    auto& sb = lnav_data.ld_scroll_broadcaster;
2✔
1262
    auto& vsb = lnav_data.ld_view_stack_broadcaster;
2✔
1263

1264
    auto echo_views_stmt_res = prepare_stmt(lnav_data.ld_db,
1265
#if SQLITE_VERSION_NUMBER < 3033000
1266
                                            R"(
1267
        UPDATE lnav_views_echo
1268
          SET top = (SELECT top FROM lnav_views WHERE lnav_views.name = lnav_views_echo.name),
1269
              left = (SELECT left FROM lnav_views WHERE lnav_views.name = lnav_views_echo.name),
1270
              height = (SELECT height FROM lnav_views WHERE lnav_views.name = lnav_views_echo.name),
1271
              inner_height = (SELECT inner_height FROM lnav_views WHERE lnav_views.name = lnav_views_echo.name),
1272
              top_time = (SELECT top_time FROM lnav_views WHERE lnav_views.name = lnav_views_echo.name),
1273
              search = (SELECT search FROM lnav_views WHERE lnav_views.name = lnav_views_echo.name),
1274
              selection = (SELECT selection FROM lnav_views WHERE lnav_views.name = lnav_views_echo.name)
1275
          WHERE EXISTS (SELECT * FROM lnav_views WHERE name = lnav_views_echo.name AND
1276
                    (
1277
                        lnav_views.top != lnav_views_echo.top OR
1278
                        lnav_views.left != lnav_views_echo.left OR
1279
                        lnav_views.height != lnav_views_echo.height OR
1280
                        lnav_views.inner_height != lnav_views_echo.inner_height OR
1281
                        lnav_views.top_time != lnav_views_echo.top_time OR
1282
                        lnav_views.search != lnav_views_echo.search OR
1283
                        lnav_views.selection != lnav_views_echo.selection
1284
                    ))
1285
        )"
1286
#else
1287
                                            R"(
1288
        UPDATE lnav_views_echo
1289
          SET top = orig.top,
1290
              left = orig.left,
1291
              height = orig.height,
1292
              inner_height = orig.inner_height,
1293
              top_time = orig.top_time,
1294
              search = orig.search,
1295
              selection = orig.selection
1296
          FROM (SELECT * FROM lnav_views) AS orig
1297
          WHERE orig.name = lnav_views_echo.name AND
1298
                (
1299
                    orig.top != lnav_views_echo.top OR
1300
                    orig.left != lnav_views_echo.left OR
1301
                    orig.height != lnav_views_echo.height OR
1302
                    orig.inner_height != lnav_views_echo.inner_height OR
1303
                    orig.top_time != lnav_views_echo.top_time OR
1304
                    orig.search != lnav_views_echo.search OR
1305
                    orig.selection != lnav_views_echo.selection
1306
                )
1307
        )"
1308
#endif
1309
    );
2✔
1310

1311
    if (echo_views_stmt_res.isErr()) {
2✔
1312
        lnav::console::print(
×
1313
            stderr,
1314
            lnav::console::user_message::error(
×
1315
                "unable to prepare UPDATE statement for lnav_views_echo "
1316
                "table")
1317
                .with_reason(echo_views_stmt_res.unwrapErr()));
×
1318
        return;
×
1319
    }
1320
    auto echo_views_stmt = echo_views_stmt_res.unwrap();
2✔
1321

1322
    if (lnav_config.lc_mouse_mode == lnav_mouse_mode::disabled) {
2✔
1323
        auto mouse_note = prepare_stmt(lnav_data.ld_db, R"(
1324
INSERT INTO lnav_user_notifications (id, priority, expiration, message)
1325
VALUES ('org.lnav.mouse-support', -1, DATETIME('now', '+1 minute'),
1326
        'Press <span class="-lnav_status-styles_hotkey">F2</span> to enable mouse support');
1327
)");
×
1328
        if (mouse_note.isErr()) {
×
1329
            lnav::console::print(stderr,
×
1330
                                 lnav::console::user_message::error(
×
1331
                                     "unable to prepare INSERT statement for "
1332
                                     "lnav_user_notifications table")
1333
                                     .with_reason(mouse_note.unwrapErr()));
×
1334
            return;
×
1335
        }
1336

1337
        mouse_note.unwrap().execute();
×
1338
    }
1339

1340
    (void) signal(SIGINT, sigint);
2✔
1341
    (void) signal(SIGTERM, sigint);
2✔
1342
    (void) signal(SIGWINCH, sigwinch);
2✔
1343
    (void) signal(SIGCONT, sigwinch);
2✔
1344
    auto _ign_signal = finally([] {
2✔
1345
        signal(SIGWINCH, SIG_IGN);
2✔
1346
        lnav_data.ld_winched = false;
2✔
1347
        lnav_data.ld_window = nullptr;
2✔
1348
        lnav_data.ld_filter_view.set_sub_source(nullptr);
2✔
1349
        for (auto& tv : lnav_data.ld_views) {
20✔
1350
            tv.set_window(nullptr);
18✔
1351
        }
1352
        for (auto* view : all_views()) {
46✔
1353
            if (view == nullptr) {
44✔
1354
                continue;
×
1355
            }
1356
            view->set_window(nullptr);
44✔
1357
        }
2✔
1358
    });
4✔
1359

1360
    auto_fd errpipe[2];
12✔
1361
    auto_fd::pipe(errpipe);
2✔
1362

1363
    errpipe[0].close_on_exec();
2✔
1364
    errpipe[1].close_on_exec();
2✔
1365
    auto pipe_err_handle = std::make_optional(
1366
        log_pipe_err(errpipe[0].release(), errpipe[1].release()));
2✔
1367

1368
    notcurses_options nco = {};
2✔
1369
    nco.flags |= NCOPTION_SUPPRESS_BANNERS | NCOPTION_NO_WINCH_SIGHANDLER;
2✔
1370
    nco.loglevel = nc_debug ? NCLOGLEVEL_DEBUG : NCLOGLEVEL_PANIC;
2✔
1371
    auto create_screen_res = screen_curses::create(nco);
2✔
1372

1373
    if (create_screen_res.isErr()) {
2✔
1374
        pipe_err_handle = std::nullopt;
×
1375
        log_error("create screen failed with: %s",
×
1376
                  create_screen_res.unwrapErr().c_str());
1377
        auto help_txt = attr_line_t();
×
1378
        auto term_var = getenv("TERM");
×
1379
        if (term_var == nullptr) {
×
1380
            help_txt.append("The ")
×
1381
                .append("TERM"_symbol)
×
1382
                .append(" environment variable is not set.  ");
×
1383
        } else {
1384
            help_txt.append("The ")
×
1385
                .append("TERM"_symbol)
×
1386
                .append(" value of ")
×
1387
                .append_quoted(term_var)
×
1388
                .append(" is not known.  ");
×
1389
        }
1390
        help_txt
1391
            .append(
×
1392
                "Check for your "
1393
                "terminal in ")
1394
            .append(
×
1395
                "https://github.com/dankamongmen/notcurses/blob/master/TERMINALS.md"_hyperlink)
1396
            .append(" or use ")
×
1397
            .append_quoted("xterm-256color");
×
1398
        lnav::console::print(
×
1399
            stderr,
1400
            lnav::console::user_message::error("unable to open TUI")
×
1401
                .with_reason(create_screen_res.unwrapErr())
×
1402
                .with_help(help_txt));
×
1403
        return;
×
1404
    }
1405

1406
    auto sc = create_screen_res.unwrap();
2✔
1407
    auto inputready_fd = notcurses_inputready_fd(sc.get_notcurses());
2✔
1408
    auto& mouse_i = injector::get<xterm_mouse&>();
2✔
1409

1410
    auto _paste = finally(
1411
        [&sc] { notcurses_bracketed_paste_disable(sc.get_notcurses()); });
4✔
1412
    notcurses_bracketed_paste_enable(sc.get_notcurses());
2✔
1413

1414
    auto ui_cb_mouse = false;
2✔
1415
    ec.ec_ui_callbacks.uc_pre_stdout_write = [&sc, &mouse_i, &ui_cb_mouse]() {
×
1416
        ui_cb_mouse = mouse_i.is_enabled();
×
1417
        if (ui_cb_mouse) {
×
1418
            mouse_i.set_enabled(sc.get_notcurses(), false);
×
1419
        }
1420
        notcurses_leave_alternate_screen(sc.get_notcurses());
×
1421

1422
        // notcurses sets stdio to non-blocking, which can cause an
1423
        // issue when writing since there is a chance of an EAGAIN
1424
        // happening
1425
        const auto fl = fcntl(STDOUT_FILENO, F_GETFL, 0);
×
1426
        fcntl(STDOUT_FILENO, F_SETFL, fl & ~O_NONBLOCK);
×
1427
    };
2✔
1428
    ec.ec_ui_callbacks.uc_post_stdout_write = [&sc, &mouse_i, &ui_cb_mouse]() {
×
1429
        const auto fl = fcntl(STDOUT_FILENO, F_GETFL, 0);
×
1430
        fcntl(STDOUT_FILENO, F_SETFL, fl | O_NONBLOCK);
×
1431

1432
        auto nci = ncinput{};
×
1433
        do {
1434
            notcurses_get_blocking(sc.get_notcurses(), &nci);
×
1435
            ncinput_free_paste_content(&nci);
×
1436
        } while (nci.evtype == NCTYPE_RELEASE || ncinput_lock_p(&nci)
×
1437
                 || ncinput_modifier_p(&nci));
×
1438
        notcurses_enter_alternate_screen(sc.get_notcurses());
×
1439

1440
        if (ui_cb_mouse) {
×
1441
            mouse_i.set_enabled(sc.get_notcurses(), true);
×
1442
        }
1443
        notcurses_refresh(sc.get_notcurses(), nullptr, nullptr);
×
1444
        // XXX doing this refresh twice since it doesn't seem to be
1445
        // enough to do it once...
1446
        notcurses_render(sc.get_notcurses());
×
1447
        notcurses_refresh(sc.get_notcurses(), nullptr, nullptr);
×
1448
    };
2✔
1449
    ec.ec_ui_callbacks.uc_redraw
1450
        = [&sc]() { notcurses_refresh(sc.get_notcurses(), nullptr, nullptr); };
2✔
1451

1452
    lnav_behavior lb;
2✔
1453

1454
    ui_periodic_timer::singleton();
2✔
1455

1456
    mouse_i.set_behavior(&lb);
2✔
1457
    mouse_i.set_enabled(
2✔
1458
        sc.get_notcurses(),
1459
        check_experimental("mouse")
2✔
1460
            || lnav_config.lc_mouse_mode == lnav_mouse_mode::enabled);
2✔
1461

1462
    lnav_data.ld_window = sc.get_std_plane();
2✔
1463

1464
    view_colors::init(sc.get_notcurses());
2✔
1465

1466
    auto ecb_guard
1467
        = lnav_data.ld_exec_context.add_msg_callback([](const auto& um) {
×
1468
              auto al = um.to_attr_line().rtrim();
12✔
1469

1470
              if (al.get_string().find('\n') == std::string::npos) {
12✔
1471
                  lnav::prompt::get().p_editor.set_inactive_value(al);
10✔
1472
              } else {
1473
                  lnav_data.ld_user_message_source.replace_with(al);
2✔
1474
                  lnav_data.ld_user_message_view.reload_data();
2✔
1475
                  lnav_data.ld_user_message_expiration
1476
                      = std::chrono::steady_clock::now() + 20s;
2✔
1477
              }
1478
          });
14✔
1479

1480
    {
1481
        setup_highlights(lnav_data.ld_views[LNV_LOG].get_highlights());
2✔
1482
        setup_highlights(lnav_data.ld_views[LNV_TEXT].get_highlights());
2✔
1483
        setup_highlights(lnav_data.ld_views[LNV_SCHEMA].get_highlights());
2✔
1484
        setup_highlights(lnav_data.ld_views[LNV_PRETTY].get_highlights());
2✔
1485
        setup_highlights(lnav_data.ld_preview_view[0].get_highlights());
2✔
1486
        setup_highlights(lnav_data.ld_preview_view[1].get_highlights());
2✔
1487
    }
1488

1489
    auto& prompt = lnav::prompt::get();
2✔
1490
    {
1491
        prompt.p_editor.set_title("main prompt");
4✔
1492
        prompt.p_editor.tc_window = lnav_data.ld_window;
2✔
1493
        prompt.p_editor.tc_height = 1;
2✔
1494
        prompt.p_editor.tc_text_format = text_format_t::TF_LNAV_SCRIPT;
2✔
1495
        prompt.p_editor.tc_on_help = bind_mem(&lnav::prompt::rl_help, &prompt);
2✔
1496
        prompt.p_editor.tc_on_reformat
1497
            = bind_mem(&lnav::prompt::rl_reformat, &prompt);
2✔
1498
        prompt.p_editor.tc_on_focus = rl_focus;
2✔
1499
        prompt.p_editor.tc_on_change = rl_change;
2✔
1500
        prompt.p_editor.tc_on_popup_change
1501
            = bind_mem(&lnav::prompt::rl_popup_change, &prompt);
2✔
1502
        prompt.p_editor.tc_on_popup_cancel
1503
            = bind_mem(&lnav::prompt::rl_popup_cancel, &prompt);
2✔
1504
        prompt.p_editor.tc_on_perform = rl_callback;
2✔
1505
        prompt.p_editor.tc_on_timeout = rl_search;
2✔
1506
        prompt.p_editor.tc_on_abort = lnav_rl_abort;
2✔
1507
        prompt.p_editor.tc_on_blur = rl_blur;
2✔
1508
        prompt.p_editor.tc_on_history_list
1509
            = bind_mem(&lnav::prompt::rl_history_list, &prompt);
2✔
1510
        prompt.p_editor.tc_on_history_search
1511
            = bind_mem(&lnav::prompt::rl_history_search, &prompt);
2✔
1512
        prompt.p_editor.tc_on_completion
1513
            = bind_mem(&lnav::prompt::rl_completion, &prompt);
2✔
1514
        prompt.p_editor.tc_on_completion_request = rl_completion_request;
2✔
1515
        prompt.p_editor.tc_on_external_open
1516
            = bind_mem(&lnav::prompt::rl_external_edit, &prompt);
2✔
1517
    }
1518

1519
    if (lnav_data.ld_view_stack.empty()) {
2✔
1520
        lnav_data.ld_view_stack.push_back(&lnav_data.ld_views[LNV_LOG]);
2✔
1521
    }
1522

1523
    sb.push_back(clear_last_user_mark);
2✔
1524
    sb.push_back(update_view_position);
2✔
1525
    vsb.push_back(
2✔
1526
        bind_mem(&term_extra::update_title, injector::get<term_extra*>()));
2✔
1527
    vsb.push_back([](listview_curses* lv) {
2✔
1528
        auto* tc = dynamic_cast<textview_curses*>(lv);
2✔
1529

1530
        tc->tc_state_event_handler(*tc);
2✔
1531
    });
2✔
1532

1533
    vsb.push_back(sb);
2✔
1534

1535
    breadcrumb_view->on_focus
2✔
1536
        = [](breadcrumb_curses&) { set_view_mode(ln_mode_t::BREADCRUMBS); };
2✔
1537
    breadcrumb_view->on_blur = [](breadcrumb_curses&) {
4✔
1538
        set_view_mode(ln_mode_t::PAGING);
×
1539
        lnav_data.ld_view_stack.set_needs_update();
×
1540
    };
2✔
1541
    breadcrumb_view->set_y(1);
2✔
1542
    breadcrumb_view->set_window(lnav_data.ld_window);
2✔
1543
    breadcrumb_view->set_line_source(lnav_crumb_source);
2✔
1544
    breadcrumb_view->bc_perform_handler
2✔
1545
        = [](breadcrumb_curses& bc,
2✔
1546
             breadcrumb::crumb::perform p,
1547
             const breadcrumb::crumb::key_t& key) {
1548
              isc::to<main_looper&, services::main_t>().send(
×
1549
                  [p, key, &bc](auto& mlooper) {
×
1550
                      static auto op = lnav_operation{"crumb_perform"};
1551

1552
                      auto op_guard = lnav_opid_guard::internal(op);
×
1553

1554
                      p(key);
×
1555
                      bc.reload_data();
×
1556
                      if (bc.is_focused()) {
×
1557
                          bc.focus_next();
×
1558
                      }
1559
                      bc.set_needs_update();
×
1560
                  });
×
1561
          };
2✔
1562
    auto event_handler = [](auto&& tc) {
4✔
1563
        auto top_view = lnav_data.ld_view_stack.top();
4✔
1564

1565
        if (top_view && *top_view == &tc) {
4✔
1566
            lnav_data.ld_bottom_source.update_search_term(tc);
4✔
1567
            lnav_data.ld_status[LNS_BOTTOM].set_needs_update();
4✔
1568
        }
1569
        if (!lnav::prompt::get().p_editor.is_enabled()) {
4✔
1570
            auto search_duration = tc.consume_search_duration();
4✔
1571
            if (search_duration) {
4✔
1572
                double secs = search_duration->count() / 1000.0;
×
1573
                lnav::prompt::get().p_editor.set_inactive_value(
×
1574
                    attr_line_t("search completed in ")
×
1575
                        .append(lnav::roles::number(
×
1576
                            fmt::format(FMT_STRING("{:.3}"), secs)))
×
1577
                        .append(" seconds"));
×
1578
            }
1579
        }
1580
    };
4✔
1581
    auto click_handler = [](textview_curses& tc, const attr_line_t& al, int x) {
×
1582
        if (tc.tc_selected_text) {
×
1583
            return;
×
1584
        }
1585
        static auto& prompt = lnav::prompt::get();
1586
        static auto& ec = lnav_data.ld_exec_context;
1587
        auto cmd_iter
1588
            = find_string_attr_containing(al.get_attrs(), &VC_COMMAND, x);
×
1589
        if (cmd_iter != al.al_attrs.end()) {
×
1590
            auto cmd = cmd_iter->sa_value.get<ui_command>();
×
1591
            auto exec_res = ec.execute(cmd.uc_location, cmd.uc_command);
×
1592
            if (exec_res.isOk()) {
×
1593
                auto val = exec_res.unwrap();
×
1594
                prompt.p_editor.set_inactive_value(val);
×
1595
            }
1596
        }
1597
        auto link_iter
1598
            = find_string_attr_containing(al.get_attrs(), &VC_HYPERLINK, x);
×
1599
        if (link_iter != al.al_attrs.end()) {
×
1600
            auto href = link_iter->sa_value.get<std::string>();
×
1601
            if (!startswith(href, "#")) {
×
1602
                ec.execute_with(INTERNAL_SRC_LOC,
×
1603
                                ":xopen $href",
1604
                                std::make_pair("href", href));
×
1605
            }
1606
        }
1607
    };
1608
    for (auto& ld_view : lnav_data.ld_views) {
20✔
1609
        ld_view.set_window(lnav_data.ld_window);
18✔
1610
        ld_view.set_y(2);
18✔
1611
        ld_view.set_height(vis_line_t(-4));
18✔
1612
        ld_view.set_scroll_action(sb);
18✔
1613
        ld_view.set_search_action(update_hits);
18✔
1614
        ld_view.tc_cursor_role = role_t::VCR_CURSOR_LINE;
18✔
1615
        ld_view.tc_disabled_cursor_role = role_t::VCR_DISABLED_CURSOR_LINE;
18✔
1616
        ld_view.tc_state_event_handler = event_handler;
18✔
1617
        ld_view.tc_on_click = click_handler;
18✔
1618
        ld_view.tc_interactive = true;
18✔
1619
    }
1620
    lnav_data.ld_views[LNV_SPECTRO].tc_cursor_role = std::nullopt;
2✔
1621
    lnav_data.ld_views[LNV_SPECTRO].tc_disabled_cursor_role = std::nullopt;
2✔
1622

1623
    lnav_data.ld_views[LNV_DB].set_supports_marks(true);
2✔
1624
    lnav_data.ld_views[LNV_HELP].set_supports_marks(true);
2✔
1625
    lnav_data.ld_views[LNV_HISTOGRAM].set_supports_marks(true);
2✔
1626
    lnav_data.ld_views[LNV_LOG].set_supports_marks(true);
2✔
1627
    lnav_data.ld_views[LNV_TEXT].set_supports_marks(true);
2✔
1628
    lnav_data.ld_views[LNV_SCHEMA].set_supports_marks(true);
2✔
1629
    lnav_data.ld_views[LNV_PRETTY].set_supports_marks(true);
2✔
1630

1631
    lnav_data.ld_doc_view.set_title("Documentation");
2✔
1632
    lnav_data.ld_doc_view.set_window(lnav_data.ld_window);
2✔
1633
    lnav_data.ld_doc_view.set_show_scrollbar(false);
2✔
1634

1635
    lnav_data.ld_example_view.set_title("Examples");
2✔
1636
    lnav_data.ld_example_view.set_window(lnav_data.ld_window);
2✔
1637
    lnav_data.ld_example_view.set_show_scrollbar(false);
2✔
1638

1639
    lnav_data.ld_preview_view[0].set_title("Preview #0");
2✔
1640
    lnav_data.ld_preview_view[0].set_window(lnav_data.ld_window);
2✔
1641
    lnav_data.ld_preview_view[0].set_show_scrollbar(false);
2✔
1642
    lnav_data.ld_preview_view[1].set_title("Preview #1");
2✔
1643
    lnav_data.ld_preview_view[1].set_window(lnav_data.ld_window);
2✔
1644
    lnav_data.ld_preview_view[1].set_show_scrollbar(false);
2✔
1645

1646
    lnav_data.ld_filter_view.set_title("Text Filters");
2✔
1647
    lnav_data.ld_filter_view.set_selectable(true);
2✔
1648
    lnav_data.ld_filter_view.set_window(lnav_data.ld_window);
2✔
1649
    lnav_data.ld_filter_view.set_show_scrollbar(true);
2✔
1650
    filter_source->fss_editor->tc_window = lnav_data.ld_window;
2✔
1651

1652
    lnav_data.ld_files_view.set_title("Files");
2✔
1653
    lnav_data.ld_files_view.set_selectable(true);
2✔
1654
    lnav_data.ld_files_view.set_window(lnav_data.ld_window);
2✔
1655
    lnav_data.ld_files_view.set_show_scrollbar(true);
2✔
1656
    lnav_data.ld_files_view.get_disabled_highlights().set(
2✔
1657
        highlight_source_t::THEME);
1658
    lnav_data.ld_files_view.set_overlay_source(&lnav_data.ld_files_overlay);
2✔
1659

1660
    lnav_data.ld_file_details_view.set_title("File Details");
2✔
1661
    lnav_data.ld_file_details_view.set_selectable(true);
2✔
1662
    lnav_data.ld_file_details_view.set_window(lnav_data.ld_window);
2✔
1663
    lnav_data.ld_file_details_view.set_show_scrollbar(true);
2✔
1664
    lnav_data.ld_file_details_view.set_supports_marks(true);
2✔
1665
    lnav_data.ld_file_details_view.get_disabled_highlights().set(
2✔
1666
        highlight_source_t::THEME);
1667
    lnav_data.ld_file_details_view.tc_cursor_role
1668
        = role_t::VCR_DISABLED_CURSOR_LINE;
2✔
1669
    lnav_data.ld_file_details_view.tc_disabled_cursor_role
1670
        = role_t::VCR_DISABLED_CURSOR_LINE;
2✔
1671

1672
    lnav_data.ld_progress_view.set_window(lnav_data.ld_window);
2✔
1673
    lnav_data.ld_user_message_view.set_window(lnav_data.ld_window);
2✔
1674

1675
    lnav_data.ld_spectro_details_view.set_title("spectro-details");
2✔
1676
    lnav_data.ld_spectro_details_view.set_window(lnav_data.ld_window);
2✔
1677
    lnav_data.ld_spectro_details_view.set_show_scrollbar(true);
2✔
1678
    lnav_data.ld_spectro_details_view.set_selectable(true);
2✔
1679
    lnav_data.ld_spectro_details_view.tc_cursor_role = role_t::VCR_CURSOR_LINE;
2✔
1680
    lnav_data.ld_spectro_details_view.tc_disabled_cursor_role
1681
        = role_t::VCR_DISABLED_CURSOR_LINE;
2✔
1682
    lnav_data.ld_spectro_details_view.set_height(5_vl);
2✔
1683
    lnav_data.ld_spectro_details_view.set_sub_source(
2✔
1684
        &lnav_data.ld_spectro_no_details_source);
1685
    lnav_data.ld_spectro_details_view.tc_state_event_handler = event_handler;
2✔
1686
    lnav_data.ld_spectro_details_view.set_scroll_action(sb);
2✔
1687

1688
    lnav_data.ld_spectro_no_details_source.replace_with(
2✔
1689
        attr_line_t().append(lnav::roles::comment(" No details available")));
2✔
1690
    lnav_data.ld_spectro_source->ss_details_view
2✔
1691
        = &lnav_data.ld_spectro_details_view;
2✔
1692
    lnav_data.ld_spectro_source->ss_no_details_source
2✔
1693
        = &lnav_data.ld_spectro_no_details_source;
2✔
1694
    lnav_data.ld_spectro_source->ss_exec_context = &lnav_data.ld_exec_context;
2✔
1695

1696
    lnav_data.ld_timeline_details_view.set_title("timeline-details");
2✔
1697
    lnav_data.ld_timeline_details_view.set_window(lnav_data.ld_window);
2✔
1698
    lnav_data.ld_timeline_details_view.set_selectable(true);
2✔
1699
    lnav_data.ld_timeline_details_view.set_show_scrollbar(true);
2✔
1700
    lnav_data.ld_timeline_details_view.set_height(5_vl);
2✔
1701
    lnav_data.ld_timeline_details_view.set_supports_marks(true);
2✔
1702
    lnav_data.ld_timeline_details_view.set_sub_source(
2✔
1703
        &lnav_data.ld_timeline_details_source);
1704
    lnav_data.ld_timeline_details_view.tc_cursor_role = role_t::VCR_CURSOR_LINE;
2✔
1705
    lnav_data.ld_timeline_details_view.tc_disabled_cursor_role
1706
        = role_t::VCR_DISABLED_CURSOR_LINE;
2✔
1707

1708
    auto top_status_lifetime
1709
        = injector::bind<top_status_source>::to_scoped_singleton();
2✔
1710
    auto top_source = injector::get<std::shared_ptr<top_status_source>>();
2✔
1711

1712
    lnav_data.ld_bottom_source.on_drag = [](mouse_event& me) {
2✔
1713
        static auto& prompt = lnav::prompt::get();
1714

1715
        if (!prompt.p_editor.is_enabled() || prompt.p_editor.tc_height == 1) {
×
1716
            return;
×
1717
        }
1718

1719
        auto full_height = (int) ncplane_dim_y(prompt.p_editor.tc_window);
×
1720
        auto max_height = full_height - 16;
×
1721
        auto new_height
1722
            = std::max(2, full_height - (prompt.p_editor.get_y() + me.me_y));
×
1723
        prompt.p_editor.set_height(std::min(max_height, new_height));
×
1724
    };
2✔
1725
    lnav_data.ld_bottom_source.get_field(bottom_status_source::BSF_HELP)
2✔
1726
        .on_click
1727
        = [](status_field&) { ensure_view(&lnav_data.ld_views[LNV_HELP]); };
2✔
1728
    lnav_data.ld_bottom_source.get_field(bottom_status_source::BSF_LINE_NUMBER)
2✔
1729
        .on_click = [](status_field&) {
2✔
1730
        auto cmd = fmt::format(
1731
            FMT_STRING("prompt command : 'goto {}'"),
×
1732
            (int) lnav_data.ld_view_stack.top().value()->get_top());
×
1733

1734
        execute_command(lnav_data.ld_exec_context, cmd);
×
1735
    };
2✔
1736
    lnav_data.ld_bottom_source.get_field(bottom_status_source::BSF_SEARCH_TERM)
2✔
1737
        .on_click = [](status_field&) {
2✔
1738
        auto term = lnav_data.ld_view_stack.top().value()->get_current_search();
×
1739
        auto cmd = fmt::format(FMT_STRING("prompt search / '{}'"), term);
×
1740

1741
        execute_command(lnav_data.ld_exec_context, cmd);
×
1742
    };
2✔
1743

1744
    lnav_data.ld_status[LNS_TOP].set_title("top");
2✔
1745
    lnav_data.ld_status[LNS_TOP].set_y(0);
2✔
1746
    lnav_data.ld_status[LNS_TOP].set_data_source(top_source.get());
2✔
1747
    lnav_data.ld_status[LNS_TOP].set_default_role(role_t::VCR_INACTIVE_STATUS);
2✔
1748
    lnav_data.ld_status[LNS_BOTTOM].set_title("bottom");
2✔
1749
    lnav_data.ld_status[LNS_BOTTOM].set_y(-2);
2✔
1750
    for (auto& stat_bar : lnav_data.ld_status) {
20✔
1751
        stat_bar.set_window(lnav_data.ld_window);
18✔
1752
    }
1753
    lnav_data.ld_status[LNS_BOTTOM].set_data_source(
2✔
1754
        &lnav_data.ld_bottom_source);
1755
    lnav_data.ld_status[LNS_FILTER].set_title("filter");
2✔
1756
    lnav_data.ld_status[LNS_FILTER].set_data_source(
2✔
1757
        &lnav_data.ld_filter_status_source);
1758
    lnav_data.ld_status[LNS_FILTER_HELP].set_title("filter help");
2✔
1759
    lnav_data.ld_status[LNS_FILTER_HELP].set_data_source(
2✔
1760
        &lnav_data.ld_filter_help_status_source);
1761

1762
    lnav_data.ld_status[LNS_DOC].set_title("Doc");
2✔
1763
    lnav_data.ld_status[LNS_DOC].set_data_source(
2✔
1764
        &lnav_data.ld_doc_status_source);
1765
    lnav_data.ld_preview_status_source[0]
1766
        .statusview_value_for_field(preview_status_source::TSF_TOGGLE)
2✔
1767
        .on_click = [](status_field&) {
2✔
1768
        lnav_data.ld_preview_status_source->update_toggle_msg(
×
1769
            lnav_data.ld_preview_hidden);
×
1770
        lnav_data.ld_preview_hidden = !lnav_data.ld_preview_hidden;
×
1771
    };
2✔
1772
    lnav_data.ld_status[LNS_PREVIEW0].set_title("preview0");
2✔
1773
    lnav_data.ld_status[LNS_PREVIEW0].set_data_source(
2✔
1774
        &lnav_data.ld_preview_status_source[0]);
1775
    lnav_data.ld_status[LNS_PREVIEW1].set_title("preview1");
2✔
1776
    lnav_data.ld_status[LNS_PREVIEW1].set_data_source(
2✔
1777
        &lnav_data.ld_preview_status_source[1]);
1778
    lnav_data.ld_spectro_status_source
1779
        = std::make_unique<spectro_status_source>();
2✔
1780
    lnav_data.ld_spectro_status_source
1781
        ->statusview_value_for_field(spectro_status_source::field_t::F_TITLE)
2✔
1782
        .on_click
1783
        = [](status_field&) { set_view_mode(ln_mode_t::SPECTRO_DETAILS); };
2✔
1784
    lnav_data.ld_status[LNS_SPECTRO].set_data_source(
2✔
1785
        lnav_data.ld_spectro_status_source.get());
2✔
1786
    lnav_data.ld_status[LNS_TIMELINE].set_enabled(false);
2✔
1787
    lnav_data.ld_status[LNS_TIMELINE].set_data_source(
2✔
1788
        &lnav_data.ld_timeline_status_source);
1789

1790
    lnav_data.ld_user_message_view.set_show_bottom_border(true);
2✔
1791

1792
    for (auto& sc : lnav_data.ld_status) {
20✔
1793
        sc.window_change();
18✔
1794
    }
1795

1796
    auto session_path = lnav::paths::dotlnav() / "session";
2✔
1797
    execute_file(ec, session_path.string());
2✔
1798

1799
    sb(*lnav_data.ld_view_stack.top());
2✔
1800
    vsb(*lnav_data.ld_view_stack.top());
2✔
1801

1802
    lnav_data.ld_view_stack.vs_change_handler
1803
        = [](textview_curses* tc) { lnav_data.ld_view_stack_broadcaster(tc); };
2✔
1804

1805
    {
1806
        auto& id = lnav_data.ld_input_dispatcher;
2✔
1807

1808
        id.id_escape_matcher = match_escape_seq;
2✔
1809
        id.id_escape_handler = handle_keyseq;
2✔
1810
        id.id_key_handler = handle_key;
2✔
1811
        id.id_mouse_handler = [&mouse_i](notcurses* nc, const ncinput& ch) {
4✔
1812
            mouse_i.handle_mouse(nc, ch);
×
1813
        };
2✔
1814
        id.id_unhandled_handler = [](const char* keyseq) {
2✔
1815
            stack_buf allocator;
×
1816
            log_info("unbound keyseq: %s", keyseq);
×
1817
            auto encoded_name
1818
                = json_ptr::encode(lnav_config.lc_ui_keymap, allocator);
×
1819
            // XXX we should have a hotkey for opening a prompt that is
1820
            // pre-filled with a suggestion that the user can complete.
1821
            // This quick-fix key could be used for other stuff as well
1822
            auto keycmd = fmt::format(
1823
                FMT_STRING(":config /ui/keymap-defs/{}/{}/command <cmd>"),
×
1824
                encoded_name,
1825
                keyseq);
×
1826
            lnav::prompt::get().p_editor.set_inactive_value(
×
1827
                attr_line_t()
×
1828
                    .append("Unrecognized key"_warning)
×
1829
                    .append(", bind to a command using \u2014 ")
×
1830
                    .append(lnav::roles::quoted_code(keycmd)));
×
1831
            alerter::singleton().chime("unrecognized key");
×
1832
        };
2✔
1833
    }
1834

1835
    auto refresher_lifetime
1836
        = injector::bind<refresh_status_bars>::to_scoped_singleton();
2✔
1837

1838
    auto refresher = injector::get<std::shared_ptr<refresh_status_bars>>();
2✔
1839
    refresher->rsb_screen = &sc;
2✔
1840

1841
    auto refresh_guard = lnav_data.ld_status_refresher.install(
1842
        [refresher](lnav::func::op_type ot) { return refresher->doit(ot); });
19✔
1843

1844
    {
1845
        auto* tss = static_cast<timeline_source*>(
1846
            lnav_data.ld_views[LNV_TIMELINE].get_sub_source());
2✔
1847
        tss->ts_index_progress
1848
            = [refresher](std::optional<timeline_source::progress_t> prog) {
4✔
1849
                  if (prog) {
×
1850
                      lnav_data.ld_bottom_source.update_loading(prog->p_curr,
×
1851
                                                                prog->p_total);
×
1852
                  } else {
1853
                      lnav_data.ld_bottom_source.update_loading(0, 0);
×
1854
                  }
1855
                  lnav_data.ld_status[LNS_BOTTOM].set_needs_update();
×
1856
                  return refresher->doit(lnav::func::op_type::blocking);
×
1857
              };
2✔
1858
    }
1859

1860
    auto& timer = ui_periodic_timer::singleton();
2✔
1861
    timeval current_time;
1862

1863
    static sig_atomic_t index_counter;
1864

1865
    timer.start_fade(index_counter, 1);
2✔
1866

1867
    std::future<file_collection> rescan_future;
2✔
1868

1869
    log_info("initial rescan started");
2✔
1870
    rescan_future = std::async(std::launch::async,
2✔
1871
                               &file_collection::rescan_files,
2✔
1872
                               lnav_data.ld_active_files.copy(),
2✔
1873
                               false);
4✔
1874

1875
    auto rescan_needed = false;
2✔
1876
    auto ui_start_time = ui_clock::now();
2✔
1877
    auto next_rebuild_time = ui_start_time;
2✔
1878
    auto next_status_update_time = ui_start_time;
2✔
1879
    auto next_rescan_time = ui_start_time;
2✔
1880
    auto got_user_input = true;
2✔
1881
    auto loop_count = 0;
2✔
1882
    auto opened_files = false;
2✔
1883
    std::vector<view_curses*> updated_views;
2✔
1884
    updated_views.emplace_back(&lnav_data.ld_view_stack);
2✔
1885

1886
    auto wakeup_pair = auto_pipe();
2✔
1887
    wakeup_pair.open();
2✔
1888
    wakeup_pair.read_end().non_blocking();
2✔
1889

1890
    static auto& mlooper = injector::get<main_looper&, services::main_t>();
2✔
1891
    mlooper.s_wakeup_fd = wakeup_pair.write_end().get();
2✔
1892

1893
    exec_phase.completed(lnav::phase_t::init);
2✔
1894
    while (lnav_data.ld_looping) {
12✔
1895
        auto loop_deadline
1896
            = ui_clock::now() + (exec_phase.spinning_up() ? 3s : 50ms);
10✔
1897
        loop_count += 1;
10✔
1898

1899
        std::vector<pollfd> pollfds;
10✔
1900
        size_t starting_view_stack_size = lnav_data.ld_view_stack.size();
10✔
1901
        size_t changes = 0;
10✔
1902
        int rc;
1903

1904
        pollfds.emplace_back(pollfd{wakeup_pair.read_end(), POLLIN, 0});
10✔
1905

1906
        auto ui_now = ui_clock::now();
10✔
1907
        gettimeofday(&current_time, nullptr);
10✔
1908

1909
        if (top_source->update_time(current_time)) {
10✔
1910
            lnav_data.ld_status[LNS_TOP].set_needs_update();
2✔
1911
        }
1912

1913
        layout_views();
10✔
1914

1915
        auto scan_timeout = exec_phase.scanning() ? 10ms : 0ms;
10✔
1916
        if (rescan_future.valid()
10✔
1917
            && rescan_future.wait_for(scan_timeout)
10✔
1918
                == std::future_status::ready)
1919
        {
1920
            auto new_files = rescan_future.get();
7✔
1921
            auto indexing_pipers
1922
                = lnav_data.ld_active_files.initial_indexing_pipers();
7✔
1923
            if (exec_phase.scanning() && new_files.empty()
10✔
1924
                && indexing_pipers == 0)
10✔
1925
            {
1926
                if (!opened_files
4✔
1927
                    && (!lnav_data.ld_active_files.fc_other_files.empty()
4✔
1928
                        || lnav_data.ld_active_files.fc_files.size() > 1
2✔
1929
                        || !lnav_data.ld_active_files.fc_name_to_stubs
2✔
1930
                                ->readAccess()
4✔
1931
                                ->empty()))
2✔
1932
                {
1933
                    opened_files = true;
×
1934
                    set_view_mode(ln_mode_t::FILES);
×
1935
                }
1936
                log_trace("%d: BEGIN initial rescan rebuild", loop_count);
2✔
1937
                auto rebuild_res = rebuild_indexes(loop_deadline);
2✔
1938
                log_trace("%d: END initial rescan rebuild", loop_count);
2✔
1939
                changes += rebuild_res.rir_changes;
2✔
1940
                load_session();
2✔
1941
                lnav::session::apply_view_commands();
2✔
1942
                if (session_data.sd_save_time > 0) {
2✔
1943
                    std::string ago;
×
1944

1945
                    ago = humanize::time::point::from_tv(
×
1946
                              {(time_t) session_data.sd_save_time, 0})
×
1947
                              .as_time_ago();
×
1948
                    auto um = lnav::console::user_message::ok(
1949
                        attr_line_t("restored session from ")
×
1950
                            .append(lnav::roles::number(ago))
×
1951
                            .append("; press ")
×
1952
                            .append("CTRL-R"_hotkey)
×
1953
                            .append(" to reset session"));
×
1954
                    lnav::prompt::get().p_editor.set_inactive_value(
×
1955
                        um.to_attr_line());
×
1956
                }
1957

1958
                lnav_data.ld_session_loaded = true;
2✔
1959
                loop_deadline = ui_now;
2✔
1960
                log_debug("initial rescan found %zu files",
2✔
1961
                          lnav_data.ld_active_files.fc_files.size());
1962
                exec_phase.completed(lnav::phase_t::scan);
2✔
1963
            }
1964
            auto old_gen = lnav_data.ld_active_files.fc_files_generation;
7✔
1965
            update_active_files(new_files);
7✔
1966
            if (old_gen != lnav_data.ld_active_files.fc_files_generation) {
7✔
1967
                next_rebuild_time = ui_now;
1✔
1968
            }
1969
            if (!exec_phase.scan_completed()) {
7✔
1970
                auto& fview = lnav_data.ld_files_view;
1✔
1971
                auto height = fview.get_inner_height();
1✔
1972

1973
                if (height > 0_vl) {
1✔
1974
                    fview.set_selection(height - 1_vl);
1✔
1975
                }
1976
            }
1977

1978
            rescan_future = std::future<file_collection>{};
7✔
1979
            next_rescan_time
1980
                = ui_now + (std::exchange(rescan_needed, false) ? 0ms : 333ms);
7✔
1981
        }
7✔
1982

1983
        if (!opened_files && exec_phase.scanning()
3✔
1984
            && ui_now - ui_start_time >= 500ms)
13✔
1985
        {
1986
            set_view_mode(ln_mode_t::FILES);
×
1987
        }
1988

1989
        if (!rescan_future.valid()
10✔
1990
            && (exec_phase.spinning_up()
15✔
1991
                || (lnav_data.ld_active_files.is_below_open_file_limit()
5✔
1992
                    && ui_clock::now() >= next_rescan_time)))
15✔
1993
        {
1994
            rescan_future = std::async(std::launch::async,
5✔
1995
                                       &file_collection::rescan_files,
5✔
1996
                                       lnav_data.ld_active_files.copy(),
5✔
1997
                                       false);
10✔
1998
            if (exec_phase.interactive()) {
5✔
1999
                // log_trace("%d: shortening deadline", loop_count);
2000
                loop_deadline = ui_clock::now() + 10ms;
×
2001
            }
2002
        }
2003

2004
        mlooper.get_port().process_for(0s);
10✔
2005

2006
        ui_now = ui_clock::now();
10✔
2007
        if (exec_phase.scan_completed()) {
10✔
2008
            auto* tc = lnav_data.ld_view_stack.top().value_or(nullptr);
9✔
2009
            if (tc != nullptr
9✔
2010
                && (tc->tc_text_selection_active || tc->tc_selected_text))
9✔
2011
            {
2012
                // skip rebuild while text is selected
2013
            } else if (ui_now >= next_rebuild_time) {
9✔
2014
                // log_trace("%d: BEGIN rebuild", loop_count);
2015
                auto rebuild_res = rebuild_indexes(loop_deadline);
4✔
2016
                // log_trace("%d: END rebuild changes=%d",
2017
                // loop_count,
2018
                // rebuild_res.rir_changes);
2019
                changes += rebuild_res.rir_changes;
4✔
2020
                if (!rebuild_res.rir_completed) {
4✔
2021
                    changes += 1;
×
2022
                    next_rebuild_time = ui_now;
×
2023
                } else if (!changes && ui_clock::now() < loop_deadline) {
4✔
2024
                    next_rebuild_time = ui_clock::now() + 333ms;
2✔
2025
                }
2026
                if (rebuild_res.rir_rescan_needed) {
4✔
2027
                    log_trace("%d: rebuild detected a rescan needed",
×
2028
                              loop_count);
2029
                    rescan_needed = true;
×
2030
                    next_rescan_time = loop_deadline = ui_now;
×
2031
                }
2032
            }
2033
        } else {
2034
            lnav_data.ld_files_view.set_overlay_needs_update();
1✔
2035
        }
2036

2037
        if (lnav_data.ld_mode == ln_mode_t::BREADCRUMBS
20✔
2038
            && breadcrumb_view->get_needs_update())
10✔
2039
        {
2040
            lnav_data.ld_view_stack.set_needs_update();
×
2041
        }
2042
        ncplane_resize_maximize(sc.get_std_plane());
10✔
2043
        if (lnav_data.ld_preview_view[0].get_needs_update()
10✔
2044
            || lnav_data.ld_preview_view[1].get_needs_update())
10✔
2045
        {
2046
            // log_trace("preview updated, update prompt");
2047
            prompt.p_editor.set_needs_update();
×
2048
        }
2049
        if (filter_source->fss_editing
20✔
2050
            && filter_source->fss_editor->get_needs_update())
10✔
2051
        {
2052
            lnav_data.ld_filter_view.set_needs_update();
1✔
2053
        }
2054
        if ((lnav_data.ld_files_view.is_visible()
10✔
2055
             && lnav_data.ld_files_view.get_needs_update())
2✔
2056
            || (lnav_data.ld_filter_view.is_visible()
14✔
2057
                && lnav_data.ld_filter_view.get_needs_update()))
2✔
2058
        {
2059
            // log_trace("config panels updated, update prompt");
2060
            prompt.p_editor.set_needs_update();
4✔
2061
            lnav_data.ld_status[LNS_FILTER].set_needs_update();
4✔
2062
            lnav_data.ld_status[LNS_FILTER_HELP].set_needs_update();
4✔
2063
        }
2064
        if (prompt.p_editor.get_needs_update()) {
10✔
2065
            // log_trace("prompt updated, update others");
2066
            if (lnav_data.ld_files_view.is_visible()) {
7✔
2067
                lnav_data.ld_files_view.set_needs_update();
2✔
2068
            }
2069
            if (lnav_data.ld_filter_view.is_visible()) {
7✔
2070
                lnav_data.ld_filter_view.set_needs_update();
2✔
2071
            }
2072
            if (lnav_data.ld_timeline_details_view.is_visible()) {
7✔
2073
                lnav_data.ld_timeline_details_view.set_needs_update();
×
2074
            }
2075
            lnav_data.ld_doc_view.set_needs_update();
7✔
2076
            lnav_data.ld_example_view.set_needs_update();
7✔
2077
            lnav_data.ld_view_stack.set_needs_update();
7✔
2078
            lnav_data.ld_preview_view
2079
                | lnav::itertools::for_each(&view_curses::set_needs_update);
7✔
2080
            lnav_data.ld_preview_view[1].set_needs_update();
7✔
2081
            lnav_data.ld_status[LNS_FILTER].set_needs_update();
7✔
2082
            lnav_data.ld_status[LNS_BOTTOM].set_needs_update();
7✔
2083
        }
2084
        if (lnav_data.ld_view_stack.do_update()) {
10✔
2085
            updated_views.emplace_back(&lnav_data.ld_view_stack);
10✔
2086
            breadcrumb_view->set_needs_update();
10✔
2087
        }
2088
        if (lnav_data.ld_doc_view.do_update()) {
10✔
2089
            updated_views.emplace_back(&lnav_data.ld_doc_view);
×
2090
        }
2091
        if (lnav_data.ld_example_view.do_update()) {
10✔
2092
            updated_views.emplace_back(&lnav_data.ld_example_view);
×
2093
        }
2094
        if (lnav_data.ld_preview_view[0].do_update()) {
10✔
2095
            updated_views.emplace_back(&lnav_data.ld_preview_view[0]);
×
2096
        }
2097
        if (lnav_data.ld_preview_view[1].do_update()) {
10✔
2098
            updated_views.emplace_back(&lnav_data.ld_preview_view[1]);
×
2099
        }
2100
        if (lnav_data.ld_spectro_details_view.do_update()) {
10✔
2101
            updated_views.emplace_back(&lnav_data.ld_spectro_details_view);
×
2102
        }
2103
        if (lnav_data.ld_timeline_details_view.do_update()) {
10✔
2104
            updated_views.emplace_back(&lnav_data.ld_timeline_details_view);
×
2105
        }
2106
        if (lnav_data.ld_progress_view.do_update()) {
10✔
2107
            updated_views.emplace_back(&lnav_data.ld_progress_view);
×
2108
        }
2109
        if (lnav_data.ld_user_message_view.do_update()) {
10✔
2110
            updated_views.emplace_back(&lnav_data.ld_user_message_view);
×
2111
        }
2112
        if (ui_now >= next_status_update_time
10✔
2113
            || lnav_data.ld_status[LNS_FILTER].get_needs_update())
10✔
2114
        {
2115
            if (exec_phase.scanning()) {
9✔
2116
                lnav_data.ld_files_view.set_needs_update();
1✔
2117
            }
2118
            if (lnav_data.ld_status[LNS_BOTTOM].get_needs_update()
9✔
2119
                || lnav_data.ld_status[LNS_FILTER].get_needs_update())
9✔
2120
            {
2121
                log_trace("bottom status (%d, %d) updated, update prompt",
9✔
2122
                          lnav_data.ld_status[LNS_BOTTOM].get_needs_update(),
2123
                          lnav_data.ld_status[LNS_FILTER].get_needs_update());
2124
                prompt.p_editor.set_needs_update();
9✔
2125
            }
2126
            echo_views_stmt.execute();
9✔
2127
            {
2128
                lnav::ext::view_states vs;
9✔
2129

2130
                {
2131
                    hasher h;
9✔
2132

2133
                    lnav_data.ld_views[LNV_LOG].update_hash_state(h);
9✔
2134
                    vs.vs_log = h.to_uuid_string();
9✔
2135
                }
2136
                {
2137
                    auto sel_opt = lnav_data.ld_views[LNV_LOG].get_selection();
9✔
2138
                    if (sel_opt
9✔
2139
                        && sel_opt.value()
11✔
2140
                            < lnav_data.ld_log_source.text_line_count())
11✔
2141
                    {
2142
                        auto win = lnav_data.ld_log_source.window_at(
2143
                            sel_opt.value());
2✔
2144
                        auto win_iter = win->begin();
2✔
2145
                        auto hash_res = win_iter->get_line_hash();
2✔
2146
                        if (hash_res.isOk()) {
2✔
2147
                            vs.vs_log_selection = hash_res.unwrap().to_string();
2✔
2148
                        }
2149
                    }
2✔
2150
                }
2151

2152
                {
2153
                    hasher h;
9✔
2154

2155
                    lnav_data.ld_views[LNV_TEXT].update_hash_state(h);
9✔
2156
                    vs.vs_text = h.to_uuid_string();
9✔
2157
                }
2158
                lnav::ext::notify_pollers(vs);
9✔
2159
            }
9✔
2160
            if (top_source->update_user_msg()) {
9✔
2161
                lnav_data.ld_status[LNS_TOP].set_needs_update();
2✔
2162
            }
2163
            for (auto& sc : lnav_data.ld_status) {
90✔
2164
                if (sc.do_update()) {
81✔
2165
                    updated_views.emplace_back(&sc);
23✔
2166
                }
2167
            }
2168
            if (lnav_data.ld_progress_source.poll()) {
9✔
2169
                lnav_data.ld_progress_view.reload_data();
×
2170
            }
2171
            next_status_update_time = ui_clock::now() + 100ms;
9✔
2172
        }
2173
        if (breadcrumb_view->do_update()) {
10✔
2174
            log_trace("update crumb");
10✔
2175
            updated_views.emplace_back(breadcrumb_view);
10✔
2176
        }
2177
        // These updates need to be done last so their textinput views can
2178
        // put the cursor in the right place.
2179
        switch (lnav_data.ld_mode) {
10✔
2180
            case ln_mode_t::FILTER:
2✔
2181
            case ln_mode_t::SEARCH_FILTERS: {
2182
                if (lnav_data.ld_filter_view.do_update()) {
2✔
2183
                    updated_views.emplace_back(&lnav_data.ld_files_view);
2✔
2184
                }
2185
                break;
2✔
2186
            }
2187
            case ln_mode_t::SEARCH_FILES:
2✔
2188
            case ln_mode_t::FILES:
2189
            case ln_mode_t::FILE_DETAILS:
2190
                if (lnav_data.ld_files_view.do_update()) {
2✔
2191
                    updated_views.emplace_back(&lnav_data.ld_files_view);
2✔
2192
                }
2193
                if (lnav_data.ld_file_details_view.do_update()) {
2✔
2194
                    updated_views.emplace_back(&lnav_data.ld_file_details_view);
2✔
2195
                }
2196
                break;
2✔
2197
            default:
6✔
2198
                break;
6✔
2199
        }
2200
        if (prompt.p_editor.do_update()) {
10✔
2201
            updated_views.emplace_back(&prompt.p_editor);
9✔
2202
        }
2203

2204
        if (prompt.p_editor.is_enabled()) {
10✔
2205
            prompt.p_editor.focus();
×
2206
        } else if (filter_source->fss_editing) {
10✔
2207
            filter_source->fss_editor->focus();
1✔
2208
        }
2209
        if (got_user_input || !updated_views.empty()) {
10✔
2210
            if (true) {
2211
                for (const auto* view_ptr : updated_views) {
70✔
2212
                    log_trace("updated view %s %s",
60✔
2213
                              typeid(*view_ptr).name(),
2214
                              view_ptr->get_title().c_str());
2215
                }
2216
            }
2217
            notcurses_render(sc.get_notcurses());
10✔
2218
            updated_views.clear();
10✔
2219
        }
2220

2221
        if (exec_phase.allow_user_input()) {
10✔
2222
            // Only take input from the user after everything has loaded.
2223
            pollfds.push_back((struct pollfd) {inputready_fd, POLLIN, 0});
9✔
2224
            switch (lnav_data.ld_mode) {
9✔
2225
                case ln_mode_t::COMMAND:
×
2226
                case ln_mode_t::SEARCH:
2227
                case ln_mode_t::SEARCH_FILTERS:
2228
                case ln_mode_t::SEARCH_FILES:
2229
                case ln_mode_t::SEARCH_SPECTRO_DETAILS:
2230
                case ln_mode_t::SQL:
2231
                case ln_mode_t::EXEC:
2232
                case ln_mode_t::USER:
2233
#if 0
2234
                            if (rlc->consume_ready_for_input()) {
2235
                                // log_debug("waiting for readline input")
2236
                            }
2237
#endif
2238
                    view_curses::awaiting_user_input();
×
2239
                    break;
×
2240
                case ln_mode_t::FILTER:
2✔
2241
                    view_curses::awaiting_user_input();
2✔
2242
                    break;
2✔
2243
                default:
7✔
2244
                    // log_debug("waiting for paging input");
2245
                    view_curses::awaiting_user_input();
7✔
2246
                    break;
7✔
2247
            }
2248
        }
2249

2250
        ps->update_poll_set(pollfds);
10✔
2251
        ui_now = ui_clock::now();
10✔
2252
        auto poll_to
2253
            = (exec_phase.interactive() && !changes && ui_now < loop_deadline)
15✔
2254
            ? std::chrono::duration_cast<std::chrono::milliseconds>(
15✔
2255
                  loop_deadline - ui_now)
15✔
2256
            : 0ms;
10✔
2257

2258
        if (false && poll_to.count() > 0) {
2259
            log_trace(
2260
                "%d: poll() with timeout %lld ", loop_count, poll_to.count());
2261
            log_trace("  (changes=%zu; before_deadline=%d; exec_phase=%d)",
2262
                      changes,
2263
                      ui_now < loop_deadline,
2264
                      exec_phase.ep_value);
2265
        }
2266
        rc = poll(pollfds.data(), pollfds.size(), poll_to.count());
10✔
2267

2268
        if (pollfds[0].revents & POLLIN) {
10✔
2269
            char buffer[128];
2270

2271
            while (read(wakeup_pair.read_end(), buffer, sizeof(buffer)) > 0) {
×
2272
                // wakeup received
2273
            }
2274
        }
2275

2276
        gettimeofday(&current_time, nullptr);
10✔
2277
        ui_now = ui_clock::now();
10✔
2278
        lb.tick(current_time);
10✔
2279
        refresher->rsb_last_loop_read_time = current_time;
10✔
2280

2281
        got_user_input = false;
10✔
2282
        if (rc < 0) {
10✔
2283
            switch (errno) {
×
2284
                case 0:
×
2285
                case EINTR:
2286
                    break;
×
2287

2288
                default:
×
2289
                    log_error("select %s", strerror(errno));
×
2290
                    lnav_data.ld_looping = false;
×
2291
                    break;
×
2292
            }
2293
        } else {
2294
            auto in_revents = pollfd_revents(pollfds, inputready_fd);
10✔
2295
            auto old_file_names_size
2296
                = lnav_data.ld_active_files.fc_file_names.size();
10✔
2297
            auto old_files_to_front_size = lnav_data.ld_files_to_front.size();
10✔
2298

2299
            if (in_revents & (POLLHUP | POLLNVAL)) {
10✔
2300
                log_info("stdin has been closed, exiting...");
×
2301
                lnav_data.ld_looping = false;
×
2302
            } else if (in_revents & POLLIN) {
10✔
2303
                static auto op = lnav_operation{"user_input"};
6✔
2304

2305
                auto op_guard = lnav_opid_guard::internal(op);
6✔
2306
                got_user_input = true;
6✔
2307
                ncinput nci;
2308
                auto old_gen = lnav_data.ld_active_files.fc_files_generation;
6✔
2309
                while (notcurses_get_nblock(sc.get_notcurses(), &nci) > 0) {
16✔
2310
                    if (nci.evtype != NCTYPE_RELEASE) {
10✔
2311
                        lnav_data.ld_user_message_source.clear();
10✔
2312
                    }
2313

2314
                    alerter::singleton().new_input(nci);
10✔
2315

2316
                    lnav_data.ld_input_dispatcher.new_input(
10✔
2317
                        current_time, sc.get_notcurses(), nci);
2318

2319
                    lnav_data.ld_view_stack.top() | [&nci](auto tc) {
10✔
2320
                        lnav_data.ld_key_repeat_history.update(nci.id,
8✔
2321
                                                               tc->get_top());
2322
                    };
8✔
2323
                    ncinput_free_paste_content(&nci);
10✔
2324

2325
                    if (!lnav_data.ld_looping) {
10✔
2326
                        // No reason to keep processing input after the
2327
                        // user has quit.  The view stack will also be
2328
                        // empty, which will cause issues.
2329
                        break;
×
2330
                    }
2331
                }
2332

2333
                next_status_update_time = ui_now;
6✔
2334
                switch (lnav_data.ld_mode) {
6✔
2335
                    case ln_mode_t::PAGING:
6✔
2336
                    case ln_mode_t::FILTER:
2337
                    case ln_mode_t::FILES:
2338
                    case ln_mode_t::FILE_DETAILS:
2339
                    case ln_mode_t::SPECTRO_DETAILS:
2340
                    case ln_mode_t::BUSY:
2341
                        if (old_gen
6✔
2342
                            == lnav_data.ld_active_files.fc_files_generation)
6✔
2343
                        {
2344
                            next_rescan_time = next_status_update_time + 1s;
6✔
2345
                        } else {
2346
                            next_rescan_time = next_status_update_time;
×
2347
                        }
2348
                        break;
6✔
2349
                    case ln_mode_t::BREADCRUMBS:
×
2350
                    case ln_mode_t::COMMAND:
2351
                    case ln_mode_t::SEARCH:
2352
                    case ln_mode_t::SEARCH_FILTERS:
2353
                    case ln_mode_t::SEARCH_FILES:
2354
                    case ln_mode_t::SEARCH_SPECTRO_DETAILS:
2355
                    case ln_mode_t::CAPTURE:
2356
                    case ln_mode_t::SQL:
2357
                    case ln_mode_t::EXEC:
2358
                    case ln_mode_t::USER:
2359
                        next_rescan_time = next_status_update_time + 1min;
×
2360
                        break;
×
2361
                }
2362
                next_rebuild_time = next_rescan_time;
6✔
2363
            }
6✔
2364

2365
            auto old_mode = lnav_data.ld_mode;
10✔
2366

2367
            ps->check_poll_set(pollfds);
10✔
2368
            lnav_data.ld_view_stack.top() | [](auto tc) { update_hits(tc); };
18✔
2369

2370
            if (lnav_data.ld_mode != old_mode) {
10✔
2371
                switch (lnav_data.ld_mode) {
×
2372
                    case ln_mode_t::PAGING:
×
2373
                    case ln_mode_t::FILTER:
2374
                    case ln_mode_t::FILES:
2375
                        next_rescan_time = next_status_update_time;
×
2376
                        next_rebuild_time = next_rescan_time;
×
2377
                        break;
×
2378
                    default:
×
2379
                        break;
×
2380
                }
2381
                got_user_input = true;
×
2382
            }
2383
            if (old_file_names_size
10✔
2384
                    != lnav_data.ld_active_files.fc_file_names.size()
10✔
2385
                || old_files_to_front_size != lnav_data.ld_files_to_front.size()
10✔
2386
                || lnav_data.ld_active_files.finished_pipers() > 0)
20✔
2387
            {
2388
                got_user_input = true;
×
2389
                next_rescan_time = ui_now;
×
2390
                next_rebuild_time = next_rescan_time;
×
2391
                next_status_update_time = next_rescan_time;
×
2392
            }
2393
        }
2394

2395
        if (prompt.p_editor.is_enabled()) {
10✔
2396
            prompt.p_editor.tick(ui_now);
×
2397
        }
2398

2399
        if (timer.time_to_update(overlay_counter)) {
10✔
2400
            lnav_data.ld_view_stack.top() |
3✔
2401
                [](auto tc) { tc->set_overlay_needs_update(); };
2✔
2402
        }
2403

2404
        if (exec_phase.building_index()) {
10✔
2405
            log_trace("%d: BEGIN initial build rebuild", loop_count);
4✔
2406
            auto rebuild_res = rebuild_indexes(loop_deadline);
4✔
2407
            log_trace("%d: END initial build rebuild", loop_count);
4✔
2408
            changes += rebuild_res.rir_changes;
4✔
2409
            if (lnav_data.ld_input_dispatcher.id_count > 0
4✔
2410
                || (opened_files && lnav_data.ld_mode != ln_mode_t::FILES)
4✔
2411
                || (changes == 0 && rebuild_res.rir_completed
3✔
2412
                    && !rebuild_res.rir_rescan_needed))
1✔
2413
            {
2414
                log_info("%d: initial build completed", loop_count);
2✔
2415
                exec_phase.completed(lnav::phase_t::build);
2✔
2416
                setup_initial_view_stack();
2✔
2417
            } else if (!opened_files && ui_now - ui_start_time > 100ms) {
2✔
2418
                log_debug("set mode to files");
2✔
2419
                set_view_mode(ln_mode_t::FILES);
2✔
2420
                lnav_data.ld_views[LNV_LOG].set_top_for_last_row();
2✔
2421
                opened_files = true;
2✔
2422
            }
2423
        }
2424

2425
        if (exec_phase.running_commands()) {
10✔
2426
            static bool ran_cleanup = false;
2427
            std::vector<
2428
                std::pair<Result<std::string, lnav::console::user_message>,
2429
                          std::string>>
2430
                cmd_results;
2✔
2431
            std::optional<ui_clock::time_point> deadline;
2✔
2432

2433
            if (lnav_data.ld_input_dispatcher.id_count > 0) {
2✔
2434
                deadline = loop_deadline;
×
2435
            }
2436
            ui_execute_init_commands(ec, cmd_results, deadline);
2✔
2437

2438
            if (!cmd_results.empty()) {
2✔
2439
                auto& prompt = lnav::prompt::get();
1✔
2440
                auto last_cmd_result = cmd_results.back();
1✔
2441

2442
                if (last_cmd_result.first.isOk()) {
1✔
2443
                    prompt.p_editor.set_inactive_value(
1✔
2444
                        last_cmd_result.first.unwrap());
2✔
2445
                } else {
2446
                    ec.ec_msg_callback_stack.back()(
×
2447
                        last_cmd_result.first.unwrapErr());
×
2448
                }
2449
                prompt.p_editor.set_alt_value(last_cmd_result.second);
1✔
2450
            }
1✔
2451

2452
            if (!ran_cleanup) {
2✔
2453
                run_cleanup_tasks();
2✔
2454
                ran_cleanup = true;
2✔
2455
            }
2456

2457
            exec_phase.completed(lnav::phase_t::commands);
2✔
2458
            check_for_enough_colors(sc);
2✔
2459
        }
2✔
2460

2461
        if (exec_phase.loading_session()) {
10✔
2462
            if (lnav_data.ld_mode == ln_mode_t::FILES) {
2✔
2463
                if (lnav_data.ld_active_files.fc_other_files.empty()
2✔
2464
                    && lnav_data.ld_active_files.fc_name_to_stubs->readAccess()
3✔
2465
                           ->empty()
1✔
2466
                    && lnav_data.ld_view_stack.top().value()->get_inner_height()
3✔
2467
                        > 0)
1✔
2468
                {
2469
                    log_info("%d: switching to paging!", loop_count);
1✔
2470
                    set_view_mode(ln_mode_t::PAGING);
1✔
2471
                    lnav_data.ld_active_files.fc_files
2472
                        | lnav::itertools::for_each(&logfile::dump_stats);
1✔
2473

2474
                    check_for_file_zones();
1✔
2475
                } else {
2476
                    lnav_data.ld_files_view.set_selection(0_vl);
×
2477
                }
2478
            }
2479
            lnav_data.ld_views[LNV_LOG].set_top_for_last_row();
2✔
2480
            lnav::session::restore_view_states();
2✔
2481
            log_info("%d: going interactive", loop_count);
2✔
2482
            auto log_sel_opt = lnav_data.ld_views[LNV_LOG].get_selection();
2✔
2483
            if (!log_sel_opt.has_value()) {
2✔
2484
                lnav_data.ld_views[LNV_LOG].set_selection_to_last_row();
1✔
2485
            }
2486
            lnav_data.ld_text_source.tss_apply_default_init_location = true;
2✔
2487
            load_time_bookmarks();
2✔
2488
            exec_phase.completed(lnav::phase_t::load_session);
2✔
2489

2490
            if (!lnav_data.ld_view_stack.empty()) {
2✔
2491
                auto* top_view = lnav_data.ld_view_stack.top().value();
2✔
2492
                std::string alt_value;
2✔
2493

2494
                if (top_view == &lnav_data.ld_views[LNV_LOG]) {
2✔
2495
                    alt_value = fmt::format(
2✔
2496
                        FMT_STRING(HELP_MSG_2(
4✔
2497
                            e,
2498
                            E,
2499
                            "to move forward/backward through " ANSI_ROLE_FMT(
2500
                                "error") " messages")),
2501
                        lnav::enums::to_underlying(role_t::VCR_ERROR));
6✔
2502
                }
2503
                if (!alt_value.empty()) {
2✔
2504
                    prompt.p_editor.set_alt_value(alt_value);
2✔
2505
                }
2506
            }
2✔
2507
        }
2508

2509
        if (handle_winch(&sc)) {
10✔
2510
            got_user_input = true;
×
2511
            next_status_update_time = ui_now;
×
2512
            layout_views();
×
2513
        }
2514

2515
        if (lnav_data.ld_child_terminated) {
10✔
2516
            lnav_data.ld_child_terminated = false;
×
2517

2518
            log_info("checking for terminated child processes");
×
2519
            for (auto iter = lnav_data.ld_children.begin();
×
2520
                 iter != lnav_data.ld_children.end();
×
2521
                 ++iter)
×
2522
            {
2523
                int rc, child_stat;
2524

2525
                rc = waitpid(*iter, &child_stat, WNOHANG);
×
2526
                if (rc == -1 || rc == 0) {
×
2527
                    continue;
×
2528
                }
2529

2530
                if (WIFEXITED(child_stat)) {
×
2531
                    log_info("child %d exited with status %d",
×
2532
                             *iter,
2533
                             WEXITSTATUS(child_stat));
2534
                } else if (WTERMSIG(child_stat)) {
×
2535
                    log_error("child %d terminated with signal %d",
×
2536
                              *iter,
2537
                              WTERMSIG(child_stat));
2538
                } else {
2539
                    log_info("child %d exited", *iter);
×
2540
                }
2541
                iter = lnav_data.ld_children.erase(iter);
×
2542
            }
2543

2544
            if (gather_pipers()) {
×
2545
                breadcrumb_view->set_needs_update();
×
2546
            }
2547
            lnav_data.ld_files_view.reload_data();
×
2548

2549
            next_rescan_time = ui_clock::now();
×
2550
            next_rebuild_time = next_rescan_time;
×
2551
            next_status_update_time = next_rescan_time;
×
2552
        }
2553

2554
        if (lnav_data.ld_view_stack.empty()) {
10✔
2555
            log_info("no more views, exiting...");
2✔
2556
            lnav_data.ld_looping = false;
2✔
2557
        } else if (lnav_data.ld_view_stack.size() == 1
8✔
2558
                   && starting_view_stack_size == 2
8✔
2559
                   && lnav_data.ld_log_source.file_count() == 0
×
2560
                   && lnav_data.ld_active_files.fc_file_names.size()
16✔
2561
                       == lnav_data.ld_text_source.size())
×
2562
        {
2563
            log_info("text view popped and no other files, exiting...");
×
2564
            lnav_data.ld_looping = false;
×
2565
        }
2566

2567
        if (lnav_data.ld_sigint_count > 0) {
10✔
2568
            auto found_piper = false;
×
2569

2570
            lnav_data.ld_sigint_count = 0;
×
2571
            if (!lnav_data.ld_view_stack.empty()) {
×
2572
                auto* tc = *lnav_data.ld_view_stack.top();
×
2573
                auto sel = tc->get_selection();
×
2574

2575
                if (tc->get_inner_height() > 0_vl && sel) {
×
2576
                    std::vector<attr_line_t> rows(1);
×
2577

2578
                    tc->get_data_source()->listview_value_for_rows(
×
2579
                        *tc, sel.value(), rows);
×
2580
                    auto& sa = rows[0].get_attrs();
×
2581
                    auto line_attr_opt = get_string_attr(sa, L_FILE);
×
2582
                    if (line_attr_opt) {
×
2583
                        auto lf = line_attr_opt.value().get();
×
2584

2585
                        log_debug("file name when SIGINT: %s",
×
2586
                                  lf->get_filename().c_str());
2587
                        for (auto& cp : lnav_data.ld_child_pollers) {
×
2588
                            auto cp_name = cp->get_filename();
×
2589

2590
                            if (!cp_name) {
×
2591
                                log_debug("no child_poller");
×
2592
                                continue;
×
2593
                            }
2594

2595
                            if (lf->get_filename() == cp_name.value()) {
×
2596
                                log_debug("found it, sending signal!");
×
2597
                                cp->send_sigint();
×
2598
                                found_piper = true;
×
2599
                            }
2600
                        }
2601
                    }
2602
                }
2603
            }
2604
            if (!found_piper) {
×
2605
                log_info("user requested exit...");
×
2606
                lnav_data.ld_looping = false;
×
2607
            }
2608
        }
2609
    }
10✔
2610

2611
    if (rescan_future.valid()) {
2✔
2612
        rescan_future.get();
×
2613
    }
2614

2615
    save_session();
2✔
2616
}
10✔
2617

2618
void
2619
wait_for_children()
2,402✔
2620
{
2621
    std::vector<pollfd> pollfds;
2,402✔
2622
    auto to = timeval{0, 333000};
2,402✔
2623
    static auto* ps = injector::get<pollable_supervisor*>();
2,402✔
2624

2625
    for (auto iter = lnav_data.ld_children.begin();
2,402✔
2626
         iter != lnav_data.ld_children.end();
2,409✔
2627
         ++iter)
7✔
2628
    {
2629
        int rc, child_stat;
2630

2631
        rc = waitpid(*iter, &child_stat, WNOHANG);
7✔
2632
        if (rc == -1 || rc == 0) {
7✔
2633
            continue;
×
2634
        }
2635

2636
        if (WIFEXITED(child_stat)) {
7✔
2637
            log_info("child %d exited with status %d",
7✔
2638
                     *iter,
2639
                     WEXITSTATUS(child_stat));
2640
        } else if (WTERMSIG(child_stat)) {
×
2641
            log_error("child %d terminated with signal %d",
×
2642
                      *iter,
2643
                      WTERMSIG(child_stat));
2644
        } else {
2645
            log_info("child %d exited", *iter);
×
2646
        }
2647
        iter = lnav_data.ld_children.erase(iter);
7✔
2648
    }
2649

2650
    do {
2651
        pollfds.clear();
2,419✔
2652

2653
        auto update_res = ps->update_poll_set(pollfds);
2,419✔
2654

2655
        if (update_res.ur_background == 0) {
2,419✔
2656
            break;
2,402✔
2657
        }
2658

2659
        int rc = poll(pollfds.data(), pollfds.size(), to.tv_usec / 1000);
17✔
2660

2661
        if (rc < 0) {
17✔
2662
            switch (errno) {
×
2663
                case 0:
×
2664
                case EINTR:
2665
                    break;
×
2666
                default:
×
2667
                    return;
×
2668
            }
2669
        }
2670

2671
        ps->check_poll_set(pollfds);
17✔
2672
        lnav_data.ld_view_stack.top() | [](auto tc) { update_hits(tc); };
34✔
2673
    } while (lnav_data.ld_looping);
17✔
2674
}
2,402✔
2675

2676
struct mode_flags_t {
2677
    bool mf_check_configs{false};
2678
    bool mf_install{false};
2679
    bool mf_update_formats{false};
2680
    bool mf_no_default{false};
2681
    bool mf_print_warnings{false};
2682
};
2683

2684
static int
2685
print_user_msgs(std::vector<lnav::console::user_message> error_list,
14✔
2686
                mode_flags_t mf)
2687
{
2688
    size_t warning_count = 0;
14✔
2689
    int retval = EXIT_SUCCESS;
14✔
2690

2691
    for (auto& iter : error_list) {
94✔
2692
        FILE* out_file;
2693

2694
        switch (iter.um_level) {
80✔
2695
            case lnav::console::user_message::level::raw:
6✔
2696
            case lnav::console::user_message::level::ok:
2697
                out_file = stdout;
6✔
2698
                break;
6✔
2699
            case lnav::console::user_message::level::warning:
13✔
2700
                warning_count += 1;
13✔
2701
                if (!mf.mf_print_warnings) {
13✔
2702
                    continue;
×
2703
                }
2704
                out_file = stderr;
13✔
2705
                break;
13✔
2706
            default:
61✔
2707
                out_file = stderr;
61✔
2708
                break;
61✔
2709
        }
2710

2711
        lnav::console::print(out_file, iter);
80✔
2712
        if (iter.um_level == lnav::console::user_message::level::error) {
80✔
2713
            retval = EXIT_FAILURE;
60✔
2714
        }
2715
    }
2716

2717
    if (warning_count > 0 && !mf.mf_print_warnings
3✔
2718
        && verbosity != verbosity_t::quiet
×
2719
        && !lnav_data.ld_flags.is_set<lnav_flags::headless>()
×
2720
        && (std::filesystem::file_time_type::clock::now()
17✔
2721
                - lnav_data.ld_last_dot_lnav_time
×
2722
            > 24h))
14✔
2723
    {
2724
        lnav::console::print(
×
2725
            stderr,
2726
            lnav::console::user_message::warning(
×
2727
                attr_line_t()
×
2728
                    .append(lnav::roles::number(fmt::to_string(warning_count)))
×
2729
                    .append(" issues were detected when checking lnav's "
×
2730
                            "configuration"))
2731
                .with_help(
×
2732
                    attr_line_t("pass ")
×
2733
                        .append(lnav::roles::symbol("-W"))
×
2734
                        .append(" on the command line to display the issues\n")
×
2735
                        .append("(this message will only be displayed once a "
×
2736
                                "day)")));
2737
    }
2738

2739
    return retval;
14✔
2740
}
2741

2742
verbosity_t verbosity = verbosity_t::standard;
2743

2744
int
2745
main(int argc, char* argv[])
795✔
2746
{
2747
    std::vector<lnav::console::user_message> config_errors;
795✔
2748
    std::vector<lnav::console::user_message> loader_errors;
795✔
2749
    auto& ec = lnav_data.ld_exec_context;
795✔
2750
    int retval = EXIT_SUCCESS;
795✔
2751

2752
    auto exec_stdin = false;
795✔
2753
    auto load_stdin = false;
795✔
2754
    mode_flags_t mode_flags;
795✔
2755
    std::string since_time;
795✔
2756
    std::string until_time;
795✔
2757
    const char* LANG = getenv("LANG");
795✔
2758
    winsize term_size{};
795✔
2759

2760
    if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &term_size) != 0) {
795✔
2761
        term_size.ws_col = 80;
793✔
2762
        term_size.ws_row = 24;
793✔
2763
    }
2764

2765
    if (LANG == nullptr || strcmp(LANG, "C") == 0) {
795✔
2766
        setenv("LANG", "en_US.UTF-8", 1);
×
2767
    }
2768

2769
    {
2770
        const auto* TEMP = getenv("TEMP");
795✔
2771

2772
        if (TEMP != nullptr) {
795✔
2773
            setenv("TMPDIR", TEMP, 0);
×
2774
        } else {
2775
            setenv("TMPDIR",
795✔
2776
                   std::filesystem::temp_directory_path().string().c_str(),
1,590✔
2777
                   0);
2778
        }
2779
    }
2780

2781
    if (lnav::console::only_process_attached_to_win32_console()) {
795✔
2782
        mode_flags.mf_no_default = true;
×
2783
    }
2784

2785
    ec.ec_label_source_stack.push_back(&lnav_data.ld_db_row_source);
795✔
2786

2787
    (void) signal(SIGPIPE, SIG_IGN);
795✔
2788
    (void) signal(SIGCHLD, sigchld);
795✔
2789
    setlocale(LC_ALL, "");
795✔
2790
    try {
2791
        std::locale::global(std::locale(""));
795✔
2792
    } catch (const std::runtime_error& e) {
×
2793
        log_error("unable to set locale to ''");
×
2794
    }
×
2795
    umask(027);
795✔
2796

2797
    /* Disable Lnav from being able to execute external commands if
2798
     * "LNAVSECURE" environment variable is set by the user.
2799
     */
2800
    if (getenv("LNAVSECURE") != nullptr) {
795✔
2801
        lnav_data.ld_flags.set<lnav_flags::secure_mode>();
5✔
2802
    }
2803

2804
    // Set PAGER so that stuff run from `:sh` will just dump their
2805
    // output for lnav to display.  One example would be `man`, as
2806
    // in `:sh man ls`.
2807
    setenv("PAGER", "cat", 1);
795✔
2808
    setenv("LNAV_HOME_DIR", lnav::paths::dotlnav().c_str(), 1);
795✔
2809
    setenv("LNAV_WORK_DIR", lnav::paths::workdir().c_str(), 1);
795✔
2810

2811
    try {
2812
        auto& safe_options_hier
2813
            = injector::get<lnav::safe_file_options_hier&>();
795✔
2814

2815
        auto opt_path = lnav::paths::dotlnav() / "file-options.json";
795✔
2816
        auto read_res = lnav::filesystem::read_file(opt_path);
795✔
2817
        auto curr_tz = date::get_tzdb().current_zone();
795✔
2818
        auto options_coll = lnav::file_options_collection{};
795✔
2819

2820
        if (read_res.isOk()) {
795✔
2821
            intern_string_t opt_path_src = intern_string::lookup(opt_path);
16✔
2822
            auto parse_res = lnav::file_options_collection::from_json(
2823
                opt_path_src, read_res.unwrap());
16✔
2824
            if (parse_res.isErr()) {
16✔
2825
                for (const auto& um : parse_res.unwrapErr()) {
×
2826
                    lnav::console::print(stderr, um);
×
2827
                }
2828
                return EXIT_FAILURE;
×
2829
            }
2830

2831
            options_coll = parse_res.unwrap();
16✔
2832
        }
16✔
2833

2834
        safe::WriteAccess<lnav::safe_file_options_hier> options_hier(
2835
            safe_options_hier);
795✔
2836

2837
        options_hier->foh_generation += 1;
795✔
2838
        auto_mem<char> var_path;
795✔
2839

2840
        var_path = realpath("/var/log", nullptr);
795✔
2841
        options_coll.foc_pattern_to_options[fmt::format(
×
2842
            FMT_STRING("{}/*"), var_path.in())] = lnav::file_options{
3,180✔
2843
            {
2844
                intern_string_t{},
795✔
2845
                source_location{},
795✔
2846
                curr_tz,
2847
            },
2848
        };
2849
        options_hier->foh_path_to_collection.emplace(std::filesystem::path("/"),
795✔
2850
                                                     options_coll);
2851
    } catch (const std::runtime_error& e) {
795✔
2852
        log_error("failed to setup tz: %s", e.what());
×
2853
    }
×
2854

2855
    lnav_data.ld_exec_context.ec_sql_callback = sql_callback;
795✔
2856
    lnav_data.ld_exec_context.ec_pipe_callback = pipe_callback;
795✔
2857

2858
    lnav_data.ld_program_name = argv[0];
795✔
2859
    add_ansi_vars(ec.ec_global_vars);
795✔
2860

2861
    lnav_data.ld_db_key_names = DEFAULT_DB_KEY_NAMES;
795✔
2862

2863
    auto dot_lnav_path = lnav::paths::dotlnav();
795✔
2864
    std::error_code last_write_ec;
795✔
2865
    lnav_data.ld_last_dot_lnav_time
2866
        = std::filesystem::last_write_time(dot_lnav_path, last_write_ec);
795✔
2867

2868
    ensure_dotlnav();
795✔
2869

2870
    log_install_handlers();
795✔
2871
    sql_install_logger();
795✔
2872

2873
    log_info("opening main sqlite3 (%s) DB", sqlite3_version);
795✔
2874
    if (sqlite3_open_v2(
795✔
2875
            "file:user_db?mode=memory&cache=shared",
2876
            lnav_data.ld_db.out(),
2877
            SQLITE_OPEN_URI | SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
2878
            nullptr)
2879
        != SQLITE_OK)
795✔
2880
    {
2881
        fprintf(stderr, "error: unable to create sqlite memory database\n");
×
2882
        exit(EXIT_FAILURE);
×
2883
    }
2884

2885
    {
2886
        auto stmt = prepare_stmt(lnav_data.ld_db, LNAV_ATTACH_DB).unwrap();
795✔
2887
        auto exec_res = stmt.execute();
795✔
2888
        if (exec_res.isErr()) {
795✔
2889
            fprintf(stderr,
×
2890
                    "failed to attach memory database: %s\n",
2891
                    exec_res.unwrapErr().c_str());
×
2892
            exit(EXIT_FAILURE);
×
2893
        }
2894
    }
795✔
2895

2896
    {
2897
        int register_collation_functions(sqlite3 * db);
2898

2899
        register_sqlite_funcs(lnav_data.ld_db.in(), sqlite_registration_funcs);
795✔
2900
        register_collation_functions(lnav_data.ld_db.in());
795✔
2901
    }
2902

2903
    register_environ_vtab(lnav_data.ld_db.in());
795✔
2904
    register_static_file_vtab(lnav_data.ld_db.in());
795✔
2905
    {
2906
        static auto vtab_modules
2907
            = injector::get<std::vector<std::shared_ptr<vtab_module_base>>>();
795✔
2908

2909
        for (const auto& mod : vtab_modules) {
7,950✔
2910
            mod->create(lnav_data.ld_db.in());
7,155✔
2911
        }
2912
    }
2913

2914
    register_views_vtab(lnav_data.ld_db.in());
795✔
2915
    register_regexp_vtab(lnav_data.ld_db.in());
795✔
2916
    register_xpath_vtab(lnav_data.ld_db.in());
795✔
2917
    register_fstat_vtab(lnav_data.ld_db.in());
795✔
2918
    lnav::events::register_events_tab(lnav_data.ld_db.in());
795✔
2919
    register_log_stmt_vtab(lnav_data.ld_db.in());
795✔
2920

2921
#ifdef HAVE_RUST_DEPS
2922
    {
2923
        lnav_rs_ext::init_ext();
795✔
2924
    }
2925
#endif
2926

2927
    auto log_fos = std::make_unique<field_overlay_source>(
2928
        lnav_data.ld_log_source, lnav_data.ld_text_source);
795✔
2929

2930
    auto vtab_man_life
2931
        = injector::bind<log_vtab_manager>::to_scoped_singleton();
795✔
2932
    auto _vtab_cleanup = finally([&] {
795✔
2933
        static const char* VIRT_TABLES = R"(
2934
SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%'
2935
)";
2936

2937
        auto op_guard = lnav_opid_guard::once("cleanup");
795✔
2938

2939
        log_info("performing cleanup");
795✔
2940

2941
#ifdef HAVE_RUST_DEPS
2942
        lnav_rs_ext::stop_ext_access();
795✔
2943
#endif
2944

2945
        {
2946
            auto& dls = lnav_data.ld_db_row_source;
795✔
2947
            size_t memory_usage = 0, total_size = 0, cached_chunks = 0;
795✔
2948
            for (auto cc = dls.dls_cell_container.cc_first.get(); cc != nullptr;
1,591✔
2949
                 cc = cc->cc_next.get())
796✔
2950
            {
2951
                total_size += cc->cc_capacity;
796✔
2952
                if (cc->cc_data) {
796✔
2953
                    cached_chunks += 1;
795✔
2954
                    memory_usage += cc->cc_capacity;
795✔
2955
                } else {
2956
                    memory_usage += cc->cc_compressed_size;
1✔
2957
                }
2958
            }
2959
            log_debug(
795✔
2960
                "cell memory footprint: total=%zu; actual=%zu; "
2961
                "cached-chunks=%zu",
2962
                total_size,
2963
                memory_usage,
2964
                cached_chunks);
2965
        }
2966

2967
        if (lnav_data.ld_spectro_source != nullptr) {
795✔
2968
            delete std::exchange(lnav_data.ld_spectro_source->ss_value_source,
671✔
2969
                                 nullptr);
1,342✔
2970
        }
2971

2972
        lnav_data.ld_child_pollers.clear();
795✔
2973

2974
        delete lnav_data.ld_views[LNV_SCHEMA].get_sub_source();
795✔
2975
        delete lnav_data.ld_views[LNV_PRETTY].get_sub_source();
795✔
2976
        for (auto& tc : lnav_data.ld_views) {
7,950✔
2977
            tc.deinit();
7,155✔
2978
        }
2979

2980
        log_info("marking files as closed");
795✔
2981
        for (auto& lf : lnav_data.ld_active_files.fc_files) {
1,444✔
2982
            lf->close();
649✔
2983
        }
2984
        log_info("rebuilding after closures");
795✔
2985
        rebuild_indexes(ui_clock::now());
795✔
2986
        log_info("clearing file collection");
795✔
2987
        lnav_data.ld_active_files.clear();
795✔
2988

2989
        log_info("dropping tables");
795✔
2990
        vtab_man_life = {};
795✔
2991

2992
        std::vector<std::string> tables_to_drop;
795✔
2993
        {
2994
            auto prep_res = prepare_stmt(lnav_data.ld_db.in(), VIRT_TABLES);
795✔
2995
            if (prep_res.isErr()) {
795✔
2996
                log_error("unable to prepare VIRT_TABLES: %s",
×
2997
                          prep_res.unwrapErr().c_str());
2998
            } else {
2999
                auto stmt = prep_res.unwrap();
795✔
3000

3001
                stmt.for_each_row<std::string>(
795✔
3002
                    [&tables_to_drop](auto table_name) {
×
3003
                        tables_to_drop.emplace_back(fmt::format(
×
3004
                            FMT_STRING("DROP TABLE {}"), table_name));
×
3005
                        return false;
×
3006
                    });
3007
            }
795✔
3008
        }
795✔
3009

3010
        // XXX
3011
        lnav_data.ld_log_source.set_preview_sql_filter(nullptr);
795✔
3012
        lnav_data.ld_log_source.set_sql_filter("", nullptr);
1,590✔
3013
        lnav_data.ld_log_source.set_sql_marker("", nullptr);
795✔
3014
        lnav_config_listener::unload_all();
795✔
3015

3016
        {
3017
            sqlite3_stmt* stmt_iter = nullptr;
795✔
3018

3019
            do {
3020
                stmt_iter = sqlite3_next_stmt(lnav_data.ld_db.in(), stmt_iter);
795✔
3021
                if (stmt_iter != nullptr) {
795✔
3022
                    const auto* stmt_sql = sqlite3_sql(stmt_iter);
×
3023

3024
                    log_warning("unfinalized SQL statement: %s", stmt_sql);
×
3025
                    ensure(false);
×
3026
                }
3027
            } while (stmt_iter != nullptr);
795✔
3028
        }
3029

3030
        for (auto& drop_stmt : tables_to_drop) {
795✔
3031
            auto prep_res
3032
                = prepare_stmt(lnav_data.ld_db.in(), drop_stmt.c_str());
×
3033
            if (prep_res.isErr()) {
×
3034
                log_error("unable to prepare DROP statement: %s",
×
3035
                          prep_res.unwrapErr().c_str());
3036
                continue;
×
3037
            }
3038

3039
            auto stmt = prep_res.unwrap();
×
3040
            stmt.execute();
×
3041
        }
3042
#if defined(HAVE_SQLITE3_DROP_MODULES)
3043
        sqlite3_drop_modules(lnav_data.ld_db.in(), nullptr);
795✔
3044
#endif
3045

3046
        lnav_data.ld_db.reset();
795✔
3047

3048
        log_info("waiting for cleanup tasks");
795✔
3049
        while (!CLEANUP_TASKS.empty()) {
4,050✔
3050
            auto& [name, task] = CLEANUP_TASKS.back();
3,255✔
3051
            if (task.wait_for(10ms) != std::future_status::ready) {
3,255✔
3052
                log_warning("cleanup task '%s' is not yet ready", name);
×
3053
            }
3054
            CLEANUP_TASKS.pop_back();
3,255✔
3055
        }
3056

3057
        log_info("cleanup finished");
795✔
3058
    });
1,590✔
3059

3060
#ifdef HAVE_LIBCURL
3061
    curl_global_init(CURL_GLOBAL_DEFAULT);
795✔
3062
#endif
3063

3064
    static const std::string DEFAULT_DEBUG_LOG = "/dev/null";
2,385✔
3065

3066
    // lnav_data.ld_debug_log_name = DEFAULT_DEBUG_LOG;
3067

3068
    std::vector<std::string> file_args;
795✔
3069
    std::vector<lnav::console::user_message> arg_errors;
795✔
3070

3071
    CLI::App app{"The Logfile Navigator"};
3,180✔
3072

3073
    app.add_option("-d",
3074
                   lnav_data.ld_debug_log_name,
3075
                   "Write debug messages to the given file.")
3076
        ->type_name("FILE");
4,770✔
3077
    app.add_flag("-D", nc_debug, "Enable debugging of notcurses");
3,180✔
3078
    app.add_option("-I", lnav_data.ld_config_paths, "include paths")
3079
        ->check(CLI::ExistingDirectory)
3080
        ->check([&arg_errors](std::string inc_path) -> std::string {
×
3081
            if (access(inc_path.c_str(), X_OK) != 0) {
56✔
3082
                arg_errors.emplace_back(
×
3083
                    lnav::console::user_message::error(
×
3084
                        attr_line_t("invalid configuration directory: ")
×
3085
                            .append(lnav::roles::file(inc_path)))
×
3086
                        .with_errno_reason());
3087
                return "unreadable";
×
3088
            }
3089

3090
            return std::string();
56✔
3091
        })
3092
        ->allow_extra_args(false);
7,950✔
3093
    app.add_flag("-q{0},-v{2}", verbosity, "Control the verbosity");
3,180✔
3094
    app.set_version_flag("-V,--version");
3,975✔
3095
    app.footer(fmt::format(FMT_STRING("Version: {}"), VCS_PACKAGE_STRING));
3,180✔
3096

3097
    std::shared_ptr<lnav::management::operations> mmode_ops;
795✔
3098

3099
    if (argc < 2 || strcmp(argv[1], "-m") != 0) {
795✔
3100
        app.add_flag("-H", lnav_data.ld_show_help_view, "show help");
3,140✔
3101
        app.add_flag("-C", mode_flags.mf_check_configs, "check");
3,140✔
3102
        auto* install_flag
3103
            = app.add_flag("-i", mode_flags.mf_install, "install");
3,140✔
3104
        app.add_flag("-u", mode_flags.mf_update_formats, "update");
3,140✔
3105
        auto* no_default_flag
3106
            = app.add_flag("-N", mode_flags.mf_no_default, "no def");
3,140✔
3107
        auto* rotated_flag = app.add_flag(
3,140✔
3108
            "-R", lnav_data.ld_active_files.fc_rotated, "rotated");
3109
        auto* recurse_flag = app.add_flag(
3,140✔
3110
            "-r", lnav_data.ld_active_files.fc_recursive, "recurse");
3111
        auto* as_log_flag
3112
            = app.add_flag("-t", lnav_data.ld_treat_stdin_as_log, "as-log");
3,140✔
3113
        app.add_flag("-W", mode_flags.mf_print_warnings);
3,140✔
3114
        auto* headless_flag = app.add_flag(
3,140✔
3115
            "-n",
3116
            [](size_t count) {
785✔
3117
                lnav_data.ld_flags.set<lnav_flags::headless>();
654✔
3118
            },
654✔
3119
            "headless");
3120
        auto* file_opt = app.add_option("file", file_args, "files");
3,140✔
3121

3122
        app.add_option("-S,--since", since_time, "since");
3,140✔
3123
        app.add_option("-U,--until", until_time, "until");
3,140✔
3124

3125
        auto wait_cb = [](size_t count) {
×
3126
            fprintf(stderr, "PID %d waiting for attachment\n", getpid());
×
3127
            char b;
3128
            if (isatty(STDIN_FILENO) && read(STDIN_FILENO, &b, 1) == -1) {
×
3129
                perror("Read key from STDIN");
×
3130
            }
3131
        };
3132
        app.add_flag("--stop", wait_cb);
2,355✔
3133

3134
        auto cmd_appender
3135
            = [](std::string cmd) { lnav_data.ld_commands.emplace_back(cmd); };
1,090✔
3136
        auto cmd_validator = [&arg_errors](std::string cmd) -> std::string {
1,091✔
3137
            static const auto ARG_SRC
3138
                = intern_string::lookup("command-line argument");
2,213✔
3139

3140
            if (cmd.empty()) {
1,091✔
3141
                return "empty commands are not allowed";
×
3142
            }
3143

3144
            switch (cmd[0]) {
1,091✔
3145
                case ':':
1,090✔
3146
                case '/':
3147
                case ';':
3148
                case '|':
3149
                    break;
1,090✔
3150
                default:
1✔
3151
                    cmd.push_back(' ');
1✔
3152
                    arg_errors.emplace_back(
1✔
3153
                        lnav::console::user_message::error(
×
3154
                            attr_line_t("invalid value for ")
2✔
3155
                                .append_quoted("-c"_symbol)
1✔
3156
                                .append(" option"))
1✔
3157
                            .with_snippet(lnav::console::snippet::from(
1✔
3158
                                ARG_SRC,
3159
                                attr_line_t()
2✔
3160
                                    .append(" -c "_quoted_code)
1✔
3161
                                    .append(lnav::roles::quoted_code(cmd))
2✔
3162
                                    .append("\n")
1✔
3163
                                    .append(4, ' ')
1✔
3164
                                    .append(lnav::roles::error(
2✔
3165
                                        "^ command type prefix "
3166
                                        "is missing"))))
3167
                            .with_help(command_arg_help()));
2✔
3168
                    return "invalid prefix";
2✔
3169
            }
3170
            return std::string();
1,090✔
3171
        };
785✔
3172
        auto* cmd_opt = app.add_option("-c")
3173
                            ->check(cmd_validator)
3174
                            ->each(cmd_appender)
3175
                            ->allow_extra_args(false)
3176
                            ->trigger_on_parse(true);
4,710✔
3177

3178
        auto file_appender = [](std::string file_path) {
7✔
3179
            lnav_data.ld_commands.emplace_back(
7✔
3180
                fmt::format(FMT_STRING("|{}"), file_path));
28✔
3181
        };
7✔
3182
        auto* exec_file_opt = app.add_option("-f")
3183
                                  ->trigger_on_parse(true)
3184
                                  ->allow_extra_args(false)
3185
                                  ->each(file_appender);
785✔
3186

3187
        auto shexec_appender = [&mode_flags](std::string cmd) {
4✔
3188
            mode_flags.mf_no_default = true;
4✔
3189
            lnav_data.ld_commands.emplace_back(
4✔
3190
                fmt::format(FMT_STRING(":sh {}"), cmd));
16✔
3191
        };
789✔
3192
        auto* cmdline_opt = app.add_option("-e")
3193
                                ->each(shexec_appender)
3194
                                ->allow_extra_args(false)
3195
                                ->trigger_on_parse(true);
785✔
3196

3197
        install_flag->needs(file_opt);
785✔
3198
        install_flag->excludes(no_default_flag,
785✔
3199
                               as_log_flag,
3200
                               rotated_flag,
3201
                               recurse_flag,
3202
                               headless_flag,
3203
                               cmd_opt,
3204
                               exec_file_opt,
3205
                               cmdline_opt);
3206
    }
3207

3208
    auto is_mmode = argc >= 2 && strcmp(argv[1], "-m") == 0;
795✔
3209
    try {
3210
#if defined(__MSYS__)
3211
        lnav::console::get_command_line_args(&argc, &argv);
3212
#endif
3213
        if (is_mmode) {
795✔
3214
            mmode_ops = lnav::management::describe_cli(app, argc, argv);
10✔
3215
        } else {
3216
            app.parse(argc, argv);
785✔
3217
        }
3218
    } catch (const CLI::CallForHelp& e) {
118✔
3219
        if (is_mmode) {
1✔
3220
            fmt::print(FMT_STRING("{}\n"), app.help());
×
3221
        } else {
3222
            usage();
1✔
3223
        }
3224
        return EXIT_SUCCESS;
1✔
3225
    } catch (const CLI::CallForVersion& e) {
117✔
3226
        fmt::print(FMT_STRING("{}\n"), VCS_PACKAGE_STRING);
348✔
3227
        return EXIT_SUCCESS;
116✔
3228
    } catch (const CLI::ParseError& e) {
117✔
3229
        if (!arg_errors.empty()) {
1✔
3230
            print_user_msgs(arg_errors, mode_flags);
1✔
3231
            return e.get_exit_code();
1✔
3232
        }
3233

3234
        lnav::console::print(
×
3235
            stderr,
3236
            lnav::console::user_message::error("invalid command-line arguments")
×
3237
                .with_reason(e.what()));
×
3238
        return e.get_exit_code();
×
3239
    }
1✔
3240

3241
    lnav_data.ld_config_paths.insert(lnav_data.ld_config_paths.begin(),
677✔
3242
                                     lnav::paths::dotlnav());
1,354✔
3243
    lnav_data.ld_config_paths.insert(lnav_data.ld_config_paths.begin(),
677✔
3244
                                     SYSCONFDIR "/lnav");
3245
    lnav_data.ld_config_paths.insert(lnav_data.ld_config_paths.begin(),
677✔
3246
                                     "/etc/lnav");
3247

3248
    if (!lnav_data.ld_debug_log_name.empty()
677✔
3249
        && lnav_data.ld_debug_log_name != DEFAULT_DEBUG_LOG)
677✔
3250
    {
3251
        lnav_log_level = lnav_log_level_t::TRACE;
46✔
3252
    }
3253

3254
    if (!lnav_data.ld_debug_log_name.empty()) {
677✔
3255
        lnav_log_file = make_optional_from_nullable(
46✔
3256
            fopen(lnav_data.ld_debug_log_name.c_str(), "ae"));
46✔
3257
        lnav_log_file | [](auto* file) {
46✔
3258
            fcntl(fileno(file), F_SETFD, FD_CLOEXEC);
46✔
3259
            log_write_ring_to(fileno(file));
46✔
3260
        };
46✔
3261
    }
3262
    log_info("lnav started %d", lnav_log_file.has_value());
677✔
3263
    for (int argi = 0; argi < argc; argi++) {
5,072✔
3264
        log_info("  arg[%d] = %s", argi, argv[argi]);
4,395✔
3265
    }
3266

3267
    {
3268
        static auto builtin_formats
3269
            = injector::get<std::vector<std::shared_ptr<log_format>>>();
677✔
3270
        auto& root_formats = log_format::get_root_formats();
677✔
3271

3272
        log_format::get_root_formats().insert(root_formats.begin(),
677✔
3273
                                              builtin_formats.begin(),
3274
                                              builtin_formats.end());
3275
        builtin_formats.clear();
677✔
3276
    }
3277

3278
    load_config(lnav_data.ld_config_paths, config_errors);
677✔
3279

3280
    if (!config_errors.empty()) {
677✔
3281
        if (print_user_msgs(config_errors, mode_flags) != EXIT_SUCCESS) {
1✔
3282
            return EXIT_FAILURE;
1✔
3283
        }
3284
    }
3285
    add_global_vars(ec);
676✔
3286

3287
    if (mode_flags.mf_update_formats) {
676✔
3288
        if (!update_installs_from_git()) {
×
3289
            return EXIT_FAILURE;
×
3290
        }
3291
        return EXIT_SUCCESS;
×
3292
    }
3293

3294
    if (mode_flags.mf_install) {
676✔
3295
        auto formats_installed_path
3296
            = lnav::paths::dotlnav() / "formats/installed";
5✔
3297
        auto configs_installed_path
3298
            = lnav::paths::dotlnav() / "configs/installed";
5✔
3299

3300
        if (argc == 0) {
5✔
3301
            const auto install_reason
3302
                = attr_line_t("the ")
×
3303
                      .append("-i"_symbol)
×
3304
                      .append(
×
3305
                          " option expects one or more log format "
3306
                          "definition "
3307
                          "files to install in your lnav "
3308
                          "configuration "
3309
                          "directory")
3310
                      .move();
×
3311
            const auto install_help
3312
                = attr_line_t(
×
3313
                      "log format definitions are JSON files that "
3314
                      "tell lnav "
3315
                      "how to understand log files\n")
3316
                      .append(
×
3317
                          "See: "
3318
                          "https://docs.lnav.org/en/latest/"
3319
                          "formats.html")
3320
                      .move();
×
3321

3322
            lnav::console::print(stderr,
×
3323
                                 lnav::console::user_message::error(
×
3324
                                     "missing format files to install")
3325
                                     .with_reason(install_reason)
×
3326
                                     .with_help(install_help));
×
3327
            return EXIT_FAILURE;
×
3328
        }
3329

3330
        for (const auto& file_path : file_args) {
8✔
3331
            if (endswith(file_path, ".git")) {
5✔
3332
                if (!install_from_git(file_path)) {
×
3333
                    return EXIT_FAILURE;
2✔
3334
                }
3335
                continue;
1✔
3336
            }
3337

3338
            if (endswith(file_path, ".lnav")) {
5✔
3339
                auto script_path = std::filesystem::path(file_path);
×
3340
                auto read_res = lnav::filesystem::read_file(script_path);
×
3341
                if (read_res.isErr()) {
×
3342
                    lnav::console::print(
×
3343
                        stderr,
3344
                        lnav::console::user_message::error(
×
3345
                            attr_line_t("unable to read script file: ")
×
3346
                                .append(lnav::roles::file(file_path)))
×
3347
                            .with_reason(read_res.unwrapErr()));
×
3348
                    return EXIT_FAILURE;
×
3349
                }
3350

3351
                auto dst_path = formats_installed_path / script_path.filename();
×
3352
                auto write_res
3353
                    = lnav::filesystem::write_file(dst_path, read_res.unwrap());
×
3354
                if (write_res.isErr()) {
×
3355
                    lnav::console::print(
×
3356
                        stderr,
3357
                        lnav::console::user_message::error(
×
3358
                            attr_line_t("unable to write script file: ")
×
3359
                                .append(lnav::roles::file(file_path)))
×
3360
                            .with_reason(write_res.unwrapErr()));
×
3361
                    return EXIT_FAILURE;
×
3362
                }
3363

3364
                lnav::console::print(
×
3365
                    stderr,
3366
                    lnav::console::user_message::ok(
×
3367
                        attr_line_t("installed -- ")
×
3368
                            .append(lnav::roles::file(dst_path))));
×
3369
                continue;
×
3370
            }
3371

3372
            if (endswith(file_path, ".sql")) {
5✔
3373
                auto sql_path = std::filesystem::path(file_path);
×
3374
                auto read_res = lnav::filesystem::read_file(sql_path);
×
3375
                if (read_res.isErr()) {
×
3376
                    lnav::console::print(
×
3377
                        stderr,
3378
                        lnav::console::user_message::error(
×
3379
                            attr_line_t("unable to read SQL file: ")
×
3380
                                .append(lnav::roles::file(file_path)))
×
3381
                            .with_reason(read_res.unwrapErr()));
×
3382
                    return EXIT_FAILURE;
×
3383
                }
3384

3385
                auto dst_path = formats_installed_path / sql_path.filename();
×
3386
                auto write_res
3387
                    = lnav::filesystem::write_file(dst_path, read_res.unwrap());
×
3388
                if (write_res.isErr()) {
×
3389
                    lnav::console::print(
×
3390
                        stderr,
3391
                        lnav::console::user_message::error(
×
3392
                            attr_line_t("unable to write SQL file: ")
×
3393
                                .append(lnav::roles::file(file_path)))
×
3394
                            .with_reason(write_res.unwrapErr()));
×
3395
                    return EXIT_FAILURE;
×
3396
                }
3397

3398
                lnav::console::print(
×
3399
                    stderr,
3400
                    lnav::console::user_message::ok(
×
3401
                        attr_line_t("installed -- ")
×
3402
                            .append(lnav::roles::file(dst_path))));
×
3403
                continue;
×
3404
            }
3405

3406
            if (file_path == "extra") {
5✔
3407
                install_extra_formats();
1✔
3408
                continue;
1✔
3409
            }
3410

3411
            auto file_type_result = detect_config_file_type(file_path);
4✔
3412
            if (file_type_result.isErr()) {
4✔
3413
                lnav::console::print(
1✔
3414
                    stderr,
3415
                    lnav::console::user_message::error(
×
3416
                        attr_line_t("unable to open configuration file: ")
1✔
3417
                            .append(lnav::roles::file(file_path)))
2✔
3418
                        .with_reason(file_type_result.unwrapErr()));
1✔
3419
                return EXIT_FAILURE;
1✔
3420
            }
3421
            auto file_type = file_type_result.unwrap();
3✔
3422

3423
            auto src_path = std::filesystem::path(file_path);
3✔
3424
            std::filesystem::path dst_name;
3✔
3425
            if (file_type == config_file_type::CONFIG) {
3✔
3426
                dst_name = src_path.filename();
×
3427
            } else {
3428
                auto format_list = load_format_file(src_path, loader_errors);
3✔
3429

3430
                if (!loader_errors.empty()) {
3✔
3431
                    if (print_user_msgs(loader_errors, mode_flags)
×
3432
                        != EXIT_SUCCESS)
×
3433
                    {
3434
                        return EXIT_FAILURE;
×
3435
                    }
3436
                }
3437
                if (format_list.empty()) {
3✔
3438
                    lnav::console::print(
×
3439
                        stderr,
3440
                        lnav::console::user_message::error(
×
3441
                            attr_line_t("invalid format file: ")
×
3442
                                .append(lnav::roles::file(src_path.string())))
×
3443
                            .with_reason("there must be at least one format "
×
3444
                                         "definition in the file"));
3445
                    return EXIT_FAILURE;
×
3446
                }
3447

3448
                dst_name = format_list[0].to_string() + ".json";
3✔
3449
            }
3✔
3450
            auto dst_path = (file_type == config_file_type::CONFIG
3451
                                 ? configs_installed_path
3452
                                 : formats_installed_path)
3453
                / dst_name;
3✔
3454

3455
            auto read_res = lnav::filesystem::read_file(file_path);
3✔
3456
            if (read_res.isErr()) {
3✔
3457
                auto um = lnav::console::user_message::error(
×
3458
                              attr_line_t("cannot read file to install -- ")
×
3459
                                  .append(lnav::roles::file(file_path)))
×
3460
                              .with_reason(read_res.unwrap())
×
3461
                              .move();
×
3462

3463
                lnav::console::print(stderr, um);
×
3464
                return EXIT_FAILURE;
×
3465
            }
3466

3467
            auto file_content = read_res.unwrap();
3✔
3468

3469
            auto read_dst_res = lnav::filesystem::read_file(dst_path);
3✔
3470
            if (read_dst_res.isOk()) {
3✔
3471
                auto dst_content = read_dst_res.unwrap();
2✔
3472

3473
                if (dst_content == file_content) {
2✔
3474
                    auto um = lnav::console::user_message::info(
3475
                        attr_line_t("file is already installed at -- ")
1✔
3476
                            .append(lnav::roles::file(dst_path)));
1✔
3477

3478
                    lnav::console::print(stdout, um);
1✔
3479

3480
                    return EXIT_SUCCESS;
1✔
3481
                }
1✔
3482
            }
2✔
3483

3484
            auto write_res = lnav::filesystem::write_file(
3485
                dst_path,
3486
                file_content,
3487
                {lnav::filesystem::write_file_options::backup_existing});
2✔
3488
            if (write_res.isErr()) {
2✔
3489
                auto um = lnav::console::user_message::error(
×
3490
                              attr_line_t("failed to install file to -- ")
×
3491
                                  .append(lnav::roles::file(dst_path)))
×
3492
                              .with_reason(write_res.unwrapErr())
×
3493
                              .move();
×
3494

3495
                lnav::console::print(stderr, um);
×
3496
                return EXIT_FAILURE;
×
3497
            }
3498

3499
            auto write_file_res = write_res.unwrap();
2✔
3500
            auto um = lnav::console::user_message::ok(
3501
                attr_line_t("installed -- ")
2✔
3502
                    .append(lnav::roles::file(dst_path)));
2✔
3503
            if (write_file_res.wfr_backup_path) {
2✔
3504
                um.with_note(
1✔
3505
                    attr_line_t("the previously installed ")
2✔
3506
                        .append_quoted(
1✔
3507
                            lnav::roles::file(dst_path.filename().string()))
2✔
3508
                        .append(" was backed up to -- ")
1✔
3509
                        .append(lnav::roles::file(
1✔
3510
                            write_file_res.wfr_backup_path.value().string())));
2✔
3511
            }
3512

3513
            lnav::console::print(stdout, um);
2✔
3514
        }
10✔
3515
        return EXIT_SUCCESS;
3✔
3516
    }
5✔
3517

3518
    if (lnav_data.ld_flags.is_set<lnav_flags::secure_mode>()) {
671✔
3519
        if (sqlite3_set_authorizer(
5✔
3520
                lnav_data.ld_db.in(), sqlite_authorizer, nullptr)
3521
            != SQLITE_OK)
5✔
3522
        {
3523
            fprintf(stderr, "error: unable to attach sqlite authorizer\n");
×
3524
            exit(EXIT_FAILURE);
×
3525
        }
3526
    }
3527

3528
    /* If we statically linked against an ncurses library that had a
3529
     * non-standard path to the terminfo database, we need to set this
3530
     * variable so that it will try the default path.
3531
     */
3532
    setenv("TERMINFO_DIRS",
671✔
3533
           "/usr/share/terminfo:/lib/terminfo:/usr/share/lib/terminfo",
3534
           0);
3535

3536
    auto* vtab_manager = injector::get<log_vtab_manager*>();
671✔
3537

3538
    lnav_data.ld_log_source.set_exec_context(&lnav_data.ld_exec_context);
671✔
3539
    lnav_data.ld_views[LNV_HELP]
3540
        .set_sub_source(&lnav_data.ld_help_source)
671✔
3541
        .set_word_wrap(false);
671✔
3542
    log_fos->fos_contexts.emplace("", false, true, true);
671✔
3543
    lnav_data.ld_views[LNV_LOG]
3544
        .set_sub_source(&lnav_data.ld_log_source)
671✔
3545
#if 0
3546
        .set_delegate(std::make_shared<action_delegate>(
3547
            lnav_data.ld_log_source,
3548
            [](auto child_pid) { lnav_data.ld_children.push_back(child_pid); },
3549
            [](const auto& desc, auto pp) {
3550
                lnav_data.ld_pipers.push_back(pp);
3551
                lnav_data.ld_active_files.fc_file_names[desc].with_fd(
3552
                    pp->get_fd());
3553
                lnav_data.ld_files_to_front.template emplace_back(desc, 0_vl);
3554
            }))
3555
#endif
3556
        .add_input_delegate(lnav_data.ld_log_source)
671✔
3557
        .set_head_space(0_vl)
671✔
3558
        .set_tail_space(2_vl)
671✔
3559
        .set_overlay_source(log_fos.get());
671✔
3560
    auto sel_reload_delegate = [](textview_curses& tc) {
4,164✔
3561
        if (!lnav_data.ld_flags.is_set<lnav_flags::headless>()
4,164✔
3562
            && lnav_config.lc_ui_movement.mode == config_movement_mode::CURSOR)
4,164✔
3563
        {
3564
            tc.set_selectable(true);
108✔
3565
        }
3566
    };
4,164✔
3567
    lnav_data.ld_views[LNV_LOG].set_reload_config_delegate(sel_reload_delegate);
671✔
3568
    lnav_data.ld_views[LNV_PRETTY].set_reload_config_delegate(
671✔
3569
        sel_reload_delegate);
3570
    auto text_header_source = std::make_shared<textfile_header_overlay>(
3571
        &lnav_data.ld_text_source, &lnav_data.ld_log_source);
671✔
3572
    lnav_data.ld_views[LNV_TEXT].set_overlay_source(text_header_source.get());
671✔
3573
    lnav_data.ld_views[LNV_TEXT].set_sub_source(&lnav_data.ld_text_source);
671✔
3574
    lnav_data.ld_views[LNV_TEXT].set_reload_config_delegate(
671✔
3575
        sel_reload_delegate);
3576
    lnav_data.ld_views[LNV_HISTOGRAM]
3577
        .set_reload_config_delegate(sel_reload_delegate)
1,342✔
3578
        .set_sub_source(&lnav_data.ld_hist_source2);
671✔
3579
    lnav_data.ld_views[LNV_DB].set_sub_source(&lnav_data.ld_db_row_source);
671✔
3580
    lnav_data.ld_views[LNV_DB].add_input_delegate(lnav_data.ld_db_row_source);
671✔
3581
    lnav_data.ld_db_overlay.dos_labels = &lnav_data.ld_db_row_source;
671✔
3582
    lnav_data.ld_db_example_row_source.dls_max_column_width = 15;
671✔
3583
    lnav_data.ld_db_example_overlay.dos_labels
3584
        = &lnav_data.ld_db_example_row_source;
671✔
3585
    lnav_data.ld_db_preview_overlay_source[0].dos_labels
3586
        = &lnav_data.ld_db_preview_source[0];
671✔
3587
    lnav_data.ld_db_preview_overlay_source[1].dos_labels
3588
        = &lnav_data.ld_db_preview_source[1];
671✔
3589
    lnav_data.ld_views[LNV_DB]
3590
        .set_reload_config_delegate(sel_reload_delegate)
1,342✔
3591
        .set_overlay_source(&lnav_data.ld_db_overlay)
671✔
3592
        .set_tail_space(3_vl);
671✔
3593
    lnav_data.ld_spectro_source = std::make_unique<spectrogram_source>();
671✔
3594
    lnav_data.ld_views[LNV_SPECTRO]
3595
        .set_reload_config_delegate(sel_reload_delegate)
1,342✔
3596
        .set_sub_source(lnav_data.ld_spectro_source.get())
671✔
3597
        .set_overlay_source(lnav_data.ld_spectro_source.get())
671✔
3598
        .add_input_delegate(*lnav_data.ld_spectro_source)
671✔
3599
        .set_tail_space(4_vl);
671✔
3600
    lnav_data.ld_views[LNV_SPECTRO].set_selectable(true);
671✔
3601
    auto timeline_view_source = std::make_shared<timeline_source>(
3602
        lnav_data.ld_views[LNV_LOG],
3603
        lnav_data.ld_log_source,
3604
        lnav_data.ld_timeline_details_view,
3605
        lnav_data.ld_timeline_details_source,
3606
        lnav_data.ld_status[LNS_TIMELINE],
3607
        lnav_data.ld_timeline_status_source);
671✔
3608
    timeline_view_source->ts_exec_context = &lnav_data.ld_exec_context;
671✔
3609
    auto timeline_header_source
3610
        = std::make_shared<timeline_header_overlay>(timeline_view_source);
671✔
3611
    lnav_data.ld_views[LNV_TIMELINE]
3612
        .set_sub_source(timeline_view_source.get())
671✔
3613
        .set_overlay_source(timeline_header_source.get())
671✔
3614
        .add_input_delegate(*timeline_view_source)
671✔
3615
        .set_tail_space(4_vl);
671✔
3616
    lnav_data.ld_views[LNV_TIMELINE].set_selectable(true);
671✔
3617

3618
    lnav_data.ld_doc_view.set_sub_source(&lnav_data.ld_doc_source);
671✔
3619
    lnav_data.ld_example_view.set_sub_source(&lnav_data.ld_example_source);
671✔
3620
    lnav_data.ld_preview_view[0].set_sub_source(
671✔
3621
        &lnav_data.ld_preview_source[0]);
3622
    lnav_data.ld_files_view.set_sub_source(&lnav_data.ld_files_source)
671✔
3623
        .add_input_delegate(lnav_data.ld_files_source);
671✔
3624
    lnav_data.ld_file_details_view.set_sub_source(
671✔
3625
        &lnav_data.ld_file_details_source);
3626
    lnav_data.ld_files_source.fss_details_source
3627
        = &lnav_data.ld_file_details_source;
671✔
3628
    lnav_data.ld_progress_view.set_title("progress");
671✔
3629
    lnav_data.ld_progress_view.set_default_role(role_t::VCR_INACTIVE_STATUS);
671✔
3630
    lnav_data.ld_progress_view.set_sub_source(&lnav_data.ld_progress_source);
671✔
3631
    lnav_data.ld_progress_view.set_show_scrollbar(true);
671✔
3632
    lnav_data.ld_progress_view.set_height(2_vl);
671✔
3633
    lnav_data.ld_user_message_view.set_sub_source(
671✔
3634
        &lnav_data.ld_user_message_source);
3635

3636
#if 0
3637
    auto overlay_menu = std::make_shared<text_overlay_menu>();
3638
    lnav_data.ld_file_details_view.set_overlay_source(overlay_menu.get());
3639
#endif
3640

3641
    for (int lpc = 0; lpc < LNV__MAX; lpc++) {
6,710✔
3642
        lnav_data.ld_views[lpc].set_gutter_source(new log_gutter_source());
6,039✔
3643
    }
3644

3645
    {
3646
        auto& hs = lnav_data.ld_hist_source2;
671✔
3647

3648
        lnav_data.ld_log_source.set_index_delegate(new hist_index_delegate(
671✔
3649
            lnav_data.ld_hist_source2, lnav_data.ld_views[LNV_HISTOGRAM]));
671✔
3650
        hs.init();
671✔
3651
        lnav_data.ld_zoom_level = 3;
671✔
3652
        hs.set_time_slice(ZOOM_LEVELS[lnav_data.ld_zoom_level]);
671✔
3653
    }
3654

3655
    for (int lpc = 0; lpc < LNV__MAX; lpc++) {
6,710✔
3656
        lnav_data.ld_views[lpc].set_title(lnav_view_titles[lpc]);
18,117✔
3657
    }
3658

3659
    load_formats(lnav_data.ld_config_paths, loader_errors);
671✔
3660

3661
    {
3662
        auto_mem<char, sqlite3_free> errmsg;
671✔
3663
        auto init_sql_str = init_sql.to_string_fragment_producer()->to_string();
671✔
3664

3665
        if (sqlite3_exec(lnav_data.ld_db.in(),
1,342✔
3666
                         init_sql_str.data(),
671✔
3667
                         nullptr,
3668
                         nullptr,
3669
                         errmsg.out())
3670
            != SQLITE_OK)
671✔
3671
        {
3672
            fprintf(stderr,
×
3673
                    "error: unable to execute DB init -- %s\n",
3674
                    errmsg.in());
3675
        }
3676
    }
671✔
3677

3678
    {
3679
        auto op_guard = lnav_opid_guard::once("register_vtab");
671✔
3680

3681
        vtab_manager->register_vtab(std::make_shared<all_logs_vtab>());
671✔
3682
        vtab_manager->register_vtab(std::make_shared<log_format_vtab_impl>(
671✔
3683
            log_format::find_root_format("generic_log")));
1,342✔
3684
        vtab_manager->register_vtab(std::make_shared<log_format_vtab_impl>(
671✔
3685
            log_format::find_root_format("lnav_piper_log")));
1,342✔
3686

3687
        for (auto& iter : log_format::get_root_formats()) {
52,313✔
3688
            auto lvi = iter->get_vtab_impl();
51,642✔
3689

3690
            if (lvi != nullptr) {
51,642✔
3691
                vtab_manager->register_vtab(lvi);
48,958✔
3692
            }
3693
        }
51,642✔
3694

3695
        load_format_extra(lnav_data.ld_db.in(),
671✔
3696
                          ec.ec_global_vars,
671✔
3697
                          lnav_data.ld_config_paths,
3698
                          loader_errors);
3699
        load_format_vtabs(vtab_manager, loader_errors);
671✔
3700

3701
        if (!loader_errors.empty()) {
671✔
3702
            if (print_user_msgs(loader_errors, mode_flags) != EXIT_SUCCESS) {
2✔
3703
                if (mmode_ops == nullptr) {
2✔
3704
                    return EXIT_FAILURE;
2✔
3705
                }
3706
            }
3707
        }
3708
    }
671✔
3709

3710
    if (mmode_ops) {
669✔
3711
        isc::supervisor root_superv(injector::get<isc::service_list>());
10✔
3712
        auto perform_res = lnav::management::perform(mmode_ops);
10✔
3713

3714
        return print_user_msgs(perform_res, mode_flags);
10✔
3715
    }
10✔
3716

3717
    if (!mode_flags.mf_check_configs && !lnav_data.ld_show_help_view) {
659✔
3718
        DEFAULT_FILES.emplace_back("var/log/messages");
658✔
3719
        DEFAULT_FILES.emplace_back("var/log/system.log");
658✔
3720
        DEFAULT_FILES.emplace_back("var/log/syslog");
658✔
3721
        DEFAULT_FILES.emplace_back("var/log/syslog.log");
658✔
3722
    }
3723

3724
    init_lnav_commands(lnav_commands);
659✔
3725
    init_lnav_bookmark_commands(lnav_commands);
659✔
3726
    init_lnav_breakpoint_commands(lnav_commands);
659✔
3727
    init_lnav_display_commands(lnav_commands);
659✔
3728
    init_lnav_filtering_commands(lnav_commands);
659✔
3729
    init_lnav_io_commands(lnav_commands);
659✔
3730
    init_lnav_metadata_commands(lnav_commands);
659✔
3731
    init_lnav_scripting_commands(lnav_commands);
659✔
3732

3733
    lnav_data.ld_looping = true;
659✔
3734
    set_view_mode(ln_mode_t::PAGING);
659✔
3735

3736
    {
3737
        if (!since_time.empty()) {
659✔
3738
            log_info("setting default since time: %s", since_time.c_str());
5✔
3739
            auto from_res = humanize::time::point::from(since_time);
5✔
3740
            if (from_res.isErr()) {
5✔
3741
                auto um = from_res.unwrapErr();
2✔
3742
                um.um_message = attr_line_t("invalid 'since' time ")
2✔
3743
                                    .append_quoted(since_time);
2✔
3744
                lnav::console::print(stderr, um);
2✔
3745
                return EXIT_FAILURE;
2✔
3746
            }
2✔
3747
            lnav_data.ld_default_time_range.tr_begin
3748
                = to_us(from_res.unwrap().get_point());
3✔
3749
        }
5✔
3750
        if (!until_time.empty()) {
657✔
3751
            log_info("setting default until time: %s", until_time.c_str());
1✔
3752
            auto from_res = humanize::time::point::from(until_time);
1✔
3753
            if (from_res.isErr()) {
1✔
3754
                auto um = from_res.unwrapErr();
×
3755
                um.um_message = attr_line_t("invalid 'until' time ")
×
3756
                                    .append_quoted(until_time);
×
3757
                lnav::console::print(stderr, um);
×
3758
                return EXIT_FAILURE;
×
3759
            }
3760
            lnav_data.ld_default_time_range.tr_end
3761
                = to_us(from_res.unwrap().get_point());
1✔
3762
        }
1✔
3763

3764
        if (lnav_data.ld_default_time_range.tr_end
657✔
3765
            < lnav_data.ld_default_time_range.tr_begin)
657✔
3766
        {
3767
            auto um
3768
                = lnav::console::user_message::error(
×
3769
                      attr_line_t("The low time cutoff ")
1✔
3770
                          .append_quoted(lnav::roles::symbol(since_time))
2✔
3771
                          .append(" is not less than the high time cutoff ")
1✔
3772
                          .append_quoted(lnav::roles::symbol(until_time)))
2✔
3773
                      .with_note(attr_line_t("The resolved low time is ")
2✔
3774
                                     .append_quoted(lnav::roles::symbol(
1✔
3775
                                         lnav::to_rfc3339_string(
2✔
3776
                                             lnav_data.ld_default_time_range
3777
                                                 .tr_begin))))
3778
                      .with_note(
2✔
3779
                          attr_line_t("The resolved high time is ")
2✔
3780
                              .append_quoted(
1✔
3781
                                  lnav::roles::symbol(lnav::to_rfc3339_string(
2✔
3782
                                      lnav_data.ld_default_time_range.tr_end))))
3783
                      .with_help(
2✔
3784
                          "Ensure that the low time cutoff is less than "
3785
                          "the high time cutoff.");
1✔
3786
            lnav::console::print(stderr, um);
1✔
3787
            return EXIT_FAILURE;
1✔
3788
        }
1✔
3789
    }
3790

3791
    if ((isatty(STDIN_FILENO) || is_dev_null(STDIN_FILENO)) && file_args.empty()
1,312✔
3792
        && lnav_data.ld_active_files.fc_file_names.empty()
1✔
3793
        && !mode_flags.mf_no_default)
1,312✔
3794
    {
3795
        char start_dir[FILENAME_MAX];
3796

3797
        if (getcwd(start_dir, sizeof(start_dir)) == nullptr) {
×
3798
            perror("getcwd");
×
3799
        } else {
3800
            do {
3801
                if (!append_default_files()) {
×
3802
                    retval = EXIT_FAILURE;
×
3803
                }
3804
            } while (lnav_data.ld_active_files.fc_file_names.empty()
×
3805
                     && change_to_parent_dir());
×
3806

3807
            if (chdir(start_dir) == -1) {
×
3808
                perror("chdir(start_dir)");
×
3809
            }
3810
        }
3811
    }
3812

3813
    {
3814
        const auto internals_dir_opt = getenv_opt("DUMP_INTERNALS_DIR");
656✔
3815

3816
        if (internals_dir_opt) {
656✔
3817
            lnav::dump_internals(internals_dir_opt.value());
2✔
3818

3819
            return EXIT_SUCCESS;
2✔
3820
        }
3821
    }
3822

3823
    if (file_args.empty() && !mode_flags.mf_no_default) {
654✔
3824
        load_stdin = true;
35✔
3825
    }
3826

3827
    for (const auto& file_path_str : file_args) {
1,241✔
3828
        auto [file_path_without_trailer, file_loc]
587✔
3829
            = lnav::filesystem::split_file_location(file_path_str);
587✔
3830
        auto_mem<char> abspath;
587✔
3831
        struct stat st;
3832

3833
        auto file_path = std::filesystem::path(
3834
            stat(file_path_without_trailer.c_str(), &st) == 0
587✔
3835
                ? file_path_without_trailer
3836
                : file_path_str);
587✔
3837

3838
        auto file_path_type
3839
            = lnav::filesystem::determine_path_type(file_path.string());
587✔
3840

3841
        if (file_path_str == "-") {
587✔
3842
            load_stdin = true;
×
3843
        }
3844
#ifdef HAVE_LIBCURL
3845
        else if (file_path_type == lnav::filesystem::path_type::url)
587✔
3846
        {
3847
            auto ul = std::make_shared<url_loader>(file_path_str);
×
3848

3849
            lnav_data.ld_active_files.fc_file_names[ul->get_path()]
×
3850
                .with_filename(file_path)
×
3851
                .with_time_range(lnav_data.ld_default_time_range);
×
3852
            isc::to<curl_looper&, services::curl_streamer_t>().send(
×
3853
                [ul](auto& clooper) { clooper.add_request(ul); });
×
3854
        } else if (file_path_str.find("://") != std::string::npos) {
587✔
3855
            lnav_data.ld_commands.insert(
3✔
3856
                lnav_data.ld_commands.begin(),
×
3857
                fmt::format(FMT_STRING(":open {}"), file_path_str));
15✔
3858
        }
3859
#endif
3860
        else if (file_path_type == lnav::filesystem::path_type::pattern)
584✔
3861
        {
3862
            lnav_data.ld_active_files.fc_file_names[file_path]
24✔
3863
                .with_follow(!lnav_data.ld_flags.is_set<lnav_flags::headless>())
12✔
3864
                .with_time_range(lnav_data.ld_default_time_range);
12✔
3865
        } else if (lnav::filesystem::statp(file_path, &st) == -1) {
572✔
3866
            if (file_path_type == lnav::filesystem::path_type::remote) {
1✔
3867
                lnav_data.ld_active_files.fc_file_names[file_path]
×
3868
                    .with_follow(
×
3869
                        !lnav_data.ld_flags.is_set<lnav_flags::headless>())
×
3870
                    .with_time_range(lnav_data.ld_default_time_range);
×
3871
            } else {
3872
                lnav::console::print(
1✔
3873
                    stderr,
3874
                    lnav::console::user_message::error(
1✔
3875
                        attr_line_t("unable to open file: ")
1✔
3876
                            .append(lnav::roles::file(file_path)))
2✔
3877
                        .with_errno_reason());
1✔
3878
                retval = EXIT_FAILURE;
1✔
3879
            }
3880
        } else if (access(file_path.c_str(), R_OK) == -1) {
571✔
3881
            lnav::console::print(
1✔
3882
                stderr,
3883
                lnav::console::user_message::error(
1✔
3884
                    attr_line_t("file exists, but is not readable: ")
1✔
3885
                        .append(lnav::roles::file(file_path)))
2✔
3886
                    .with_errno_reason());
1✔
3887
            retval = EXIT_FAILURE;
1✔
3888
        } else if (S_ISFIFO(st.st_mode)) {
570✔
3889
            auto_fd fifo_fd;
×
3890

3891
            if ((fifo_fd = lnav::filesystem::openp(file_path, O_RDONLY)) == -1)
×
3892
            {
3893
                lnav::console::print(
×
3894
                    stderr,
3895
                    lnav::console::user_message::error(
×
3896
                        attr_line_t("cannot open fifo: ")
×
3897
                            .append(lnav::roles::file(file_path)))
×
3898
                        .with_errno_reason());
×
3899
                retval = EXIT_FAILURE;
×
3900
            } else {
3901
                auto desc = fmt::format(FMT_STRING("FIFO [{}]"),
×
3902
                                        lnav_data.ld_fifo_counter++);
×
3903
                auto create_piper_res = lnav::piper::create_looper(
3904
                    desc, std::move(fifo_fd), auto_fd{});
×
3905

3906
                if (create_piper_res.isOk()) {
×
3907
                    lnav_data.ld_active_files.fc_file_names[desc]
×
3908
                        .with_piper(create_piper_res.unwrap())
×
3909
                        .with_time_range(lnav_data.ld_default_time_range);
×
3910
                }
3911
            }
3912
        } else if ((abspath = realpath(file_path.c_str(), nullptr)) == nullptr)
570✔
3913
        {
3914
            perror("Cannot find file");
×
3915
            retval = EXIT_FAILURE;
×
3916
        } else if (S_ISDIR(st.st_mode)) {
570✔
3917
            std::string dir_wild(abspath.in());
2✔
3918

3919
            if (dir_wild[dir_wild.size() - 1] == '/') {
2✔
3920
                dir_wild.resize(dir_wild.size() - 1);
×
3921
            }
3922
            auto loo = logfile_open_options().with_time_range(
4✔
3923
                lnav_data.ld_default_time_range);
2✔
3924
            lnav_data.ld_active_files.fc_file_names.insert2(dir_wild + "/*",
2✔
3925
                                                            loo);
3926
        } else {
2✔
3927
            lnav_data.ld_active_files.fc_file_names.insert2(
568✔
3928
                abspath.in(),
568✔
3929
                logfile_open_options()
568✔
3930
                    .with_init_location(file_loc)
1,136✔
3931
                    .with_follow(
568✔
3932
                        !lnav_data.ld_flags.is_set<lnav_flags::headless>())
568✔
3933
                    .with_time_range(lnav_data.ld_default_time_range));
568✔
3934
            if (file_loc.valid()) {
568✔
3935
                lnav_data.ld_files_to_front.emplace_back(abspath.in());
568✔
3936
            }
3937
        }
3938
    }
587✔
3939

3940
    if (mode_flags.mf_check_configs) {
654✔
3941
        isc::supervisor root_superv(injector::get<isc::service_list>());
1✔
3942
        rescan_files(true);
1✔
3943
        for (auto& lf : lnav_data.ld_active_files.fc_files) {
2✔
3944
            logfile::rebuild_result_t rebuild_result;
3945

3946
            do {
3947
                rebuild_result = lf->rebuild_index();
3✔
3948
            } while (rebuild_result == logfile::rebuild_result_t::NEW_LINES
3949
                     || rebuild_result == logfile::rebuild_result_t::NEW_ORDER);
3✔
3950
            auto fmt = lf->get_format();
1✔
3951
            if (fmt == nullptr) {
1✔
3952
                fprintf(stderr,
×
3953
                        "error:%s:no format found for file\n",
3954
                        lf->get_filename().c_str());
×
3955
                retval = EXIT_FAILURE;
×
3956
                continue;
×
3957
            }
3958
            for (auto line_iter = lf->begin(); line_iter != lf->end();
4✔
3959
                 ++line_iter)
3✔
3960
            {
3961
                if (line_iter->get_msg_level() != log_level_t::LEVEL_INVALID) {
3✔
3962
                    continue;
2✔
3963
                }
3964

3965
                size_t partial_len;
3966

3967
                auto read_result = lf->read_line(line_iter);
1✔
3968
                if (read_result.isErr()) {
1✔
3969
                    continue;
×
3970
                }
3971
                auto sbr = read_result.unwrap();
1✔
3972
                auto lffs = lf->get_format_file_state();
1✔
3973
                if (fmt->scan_for_partial(lffs, sbr, partial_len)) {
1✔
3974
                    long line_number = std::distance(lf->begin(), line_iter);
1✔
3975
                    auto full_line = to_string(sbr);
1✔
3976
                    std::string partial_line(sbr.get_data(), partial_len);
1✔
3977

3978
                    fprintf(stderr,
2✔
3979
                            "error:%s:%ld:line did not match format "
3980
                            "%s\n",
3981
                            lf->get_filename().c_str(),
1✔
3982
                            line_number,
3983
                            fmt->get_pattern_path(lffs.lffs_pattern_locks,
2✔
3984
                                                  line_number)
3985
                                .c_str());
3986
                    fprintf(stderr,
2✔
3987
                            "error:%s:%ld:         line -- %s\n",
3988
                            lf->get_filename().c_str(),
1✔
3989
                            line_number,
3990
                            full_line.c_str());
3991
                    if (partial_len > 0) {
1✔
3992
                        fprintf(stderr,
2✔
3993
                                "error:%s:%ld:partial match -- %s\n",
3994
                                lf->get_filename().c_str(),
1✔
3995
                                line_number,
3996
                                partial_line.c_str());
3997
                    } else {
3998
                        fprintf(stderr,
×
3999
                                "error:%s:%ld:no partial match found\n",
4000
                                lf->get_filename().c_str(),
×
4001
                                line_number);
4002
                    }
4003
                    retval = EXIT_FAILURE;
1✔
4004
                }
1✔
4005
            }
1✔
4006
        }
1✔
4007
        return retval;
1✔
4008
    }
1✔
4009

4010
    if (lnav_data.ld_flags.is_set<lnav_flags::headless>()
653✔
4011
        || mode_flags.mf_check_configs)
653✔
4012
    {
4013
    } else if (!isatty(STDOUT_FILENO)) {
3✔
4014
        lnav::console::print(
1✔
4015
            stderr,
4016
            lnav::console::user_message::error(
2✔
4017
                "unable to display interactive text UI")
4018
                .with_reason("stdout is not a TTY")
2✔
4019
                .with_help(attr_line_t("pass the ")
2✔
4020
                               .append("-n"_symbol)
1✔
4021
                               .append(" option to run lnav in headless mode "
1✔
4022
                                       "or don't redirect stdout")));
4023
        retval = EXIT_FAILURE;
1✔
4024
    }
4025

4026
    std::optional<std::string> stdin_url;
653✔
4027
    std::filesystem::path stdin_dir;
653✔
4028
    if (load_stdin && !isatty(STDIN_FILENO) && !is_dev_null(STDIN_FILENO)
35✔
4029
        && !exec_stdin)
688✔
4030
    {
4031
        static const std::string STDIN_NAME = "stdin";
105✔
4032
        struct stat stdin_st;
4033

4034
        if (fstat(STDIN_FILENO, &stdin_st) == -1) {
35✔
4035
            lnav::console::print(
×
4036
                stderr,
4037
                lnav::console::user_message::error("unable to stat() stdin")
×
4038
                    .with_errno_reason());
×
4039
            retval = EXIT_FAILURE;
×
4040
        } else if (S_ISFIFO(stdin_st.st_mode)) {
35✔
4041
            static auto op = lnav_operation{"load_stdin"};
25✔
4042
            auto op_guard = lnav_opid_guard::internal(op);
25✔
4043
            pollfd pfd[1];
4044

4045
            log_info("waiting for stdin FIFO");
25✔
4046
            pfd[0].fd = STDIN_FILENO;
25✔
4047
            pfd[0].events = POLLIN;
25✔
4048
            pfd[0].revents = 0;
25✔
4049
            auto prc = poll(pfd, 1, 0);
25✔
4050

4051
            if (prc == 0 || (pfd[0].revents & POLLIN)) {
25✔
4052
                auto stdin_piper_res = lnav::piper::create_looper(
4053
                    STDIN_NAME, auto_fd::dup_of(STDIN_FILENO), auto_fd{});
25✔
4054
                if (stdin_piper_res.isOk()) {
25✔
4055
                    auto stdin_piper = stdin_piper_res.unwrap();
25✔
4056
                    stdin_url = stdin_piper.get_url();
25✔
4057
                    stdin_dir = stdin_piper.get_out_dir();
25✔
4058
                    auto& loo = lnav_data.ld_active_files
4059
                                    .fc_file_names[stdin_piper.get_name()];
25✔
4060
                    loo.with_piper(stdin_piper)
50✔
4061
                        .with_include_in_session(
25✔
4062
                            lnav_data.ld_treat_stdin_as_log)
25✔
4063
                        .with_time_range(lnav_data.ld_default_time_range);
25✔
4064
                    if (lnav_data.ld_treat_stdin_as_log) {
25✔
4065
                        loo.with_text_format(text_format_t::TF_LOG);
1✔
4066
                    }
4067
                }
25✔
4068
            } else if (prc < 0) {
25✔
4069
                log_error("unable to poll() stdin: %s",
×
4070
                          lnav::from_errno().message().c_str());
4071
            }
4072
            log_info("  done waiting for stdin to start");
25✔
4073
        } else if (S_ISREG(stdin_st.st_mode)) {
35✔
4074
            // The shell connected a file directly, just open it up
4075
            // and add it in here.
4076
            auto loo = logfile_open_options{}
20✔
4077
                           .with_filename(STDIN_NAME)
10✔
4078
                           .with_include_in_session(false);
10✔
4079

4080
            auto open_res
4081
                = logfile::open(STDIN_NAME, loo, auto_fd::dup_of(STDIN_FILENO));
10✔
4082

4083
            if (open_res.isErr()) {
10✔
4084
                lnav::console::print(
×
4085
                    stderr,
4086
                    lnav::console::user_message::error("unable to open stdin")
×
4087
                        .with_reason(open_res.unwrapErr()));
×
4088
                retval = EXIT_FAILURE;
×
4089
            } else {
4090
                file_collection fc;
10✔
4091

4092
                fc.fc_files.emplace_back(open_res.unwrap());
10✔
4093
                update_active_files(fc);
10✔
4094
            }
10✔
4095
        }
10✔
4096
    }
4097

4098
    if (!isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)
1,304✔
4099
        && !lnav_data.ld_flags.is_set<lnav_flags::headless>())
1,304✔
4100
    {
4101
        if (dup2(STDOUT_FILENO, STDIN_FILENO) == -1) {
×
4102
            perror("cannot dup stdout to stdin");
×
4103
        }
4104
    }
4105

4106
    if (retval == EXIT_SUCCESS && lnav_data.ld_active_files.fc_files.empty()
651✔
4107
        && lnav_data.ld_active_files.fc_file_names.empty()
641✔
4108
        && lnav_data.ld_commands.empty()
62✔
4109
        && !(lnav_data.ld_show_help_view || mode_flags.mf_no_default))
1,304✔
4110
    {
4111
        lnav::console::print(
×
4112
            stderr,
4113
            lnav::console::user_message::error("nothing to do")
×
4114
                .with_reason("no files given or default files found")
×
4115
                .with_help(attr_line_t("use the ")
×
4116
                               .append_quoted(lnav::roles::keyword("-N"))
×
4117
                               .append(" option to open lnav without "
×
4118
                                       "loading any files")));
4119
        retval = EXIT_FAILURE;
×
4120
    }
4121

4122
    if (retval == EXIT_SUCCESS) {
653✔
4123
        isc::supervisor root_superv(injector::get<isc::service_list>());
651✔
4124

4125
        try {
4126
            char pcre2_version[128];
4127

4128
            pcre2_config(PCRE2_CONFIG_VERSION, pcre2_version);
651✔
4129
            log_info("startup: %s", VCS_PACKAGE_STRING);
651✔
4130
            log_host_info();
651✔
4131
            log_info("Libraries:");
651✔
4132
#ifdef HAVE_BZLIB_H
4133
            log_info("  bzip=%s", BZ2_bzlibVersion());
651✔
4134
#endif
4135
#ifdef HAVE_LIBCURL
4136
            log_info("  curl=%s (%s)", LIBCURL_VERSION, LIBCURL_TIMESTAMP);
651✔
4137
#endif
4138
#ifdef HAVE_ARCHIVE_H
4139
            log_info("  libarchive=%d", ARCHIVE_VERSION_NUMBER);
4140
            log_info("    details=%s", archive_version_details());
4141
#endif
4142
            log_info("  notcurses=%s", notcurses_version());
651✔
4143
            log_info("  pcre2=%s", pcre2_version);
651✔
4144
            log_info("  sqlite=%s", sqlite3_version);
651✔
4145
            log_info("  zlib=%s", zlibVersion());
651✔
4146
            log_info("lnav_data:");
651✔
4147
            log_info("  flags=%llx", lnav_data.ld_flags.bs_data);
651✔
4148
            log_info("  commands:");
651✔
4149
            for (auto cmd_iter = lnav_data.ld_commands.begin();
651✔
4150
                 cmd_iter != lnav_data.ld_commands.end();
1,755✔
4151
                 ++cmd_iter)
1,104✔
4152
            {
4153
                log_info("    %s", cmd_iter->c_str());
1,104✔
4154
            }
4155
            log_info("  files:");
651✔
4156
            for (auto file_iter
651✔
4157
                 = lnav_data.ld_active_files.fc_file_names.begin();
651✔
4158
                 file_iter != lnav_data.ld_active_files.fc_file_names.end();
1,257✔
4159
                 ++file_iter)
606✔
4160
            {
4161
                log_info("    %s", file_iter->first.c_str());
606✔
4162
            }
4163

4164
            if (!lnav_data.ld_flags.is_set<lnav_flags::headless>()
651✔
4165
                && verbosity == verbosity_t::quiet
2✔
4166
                && lnav_data.ld_active_files.fc_file_names.size() == 1)
653✔
4167
            {
4168
                log_info("pager mode, waiting for input to be consumed");
×
4169
                // give the pipers a chance to run to create the files to be
4170
                // scanned.
4171
                wait_for_pipers(ui_clock::now() + 10ms);
×
4172
                rescan_files(true);
×
4173
                // wait for the piper to actually finish running
4174
                wait_for_pipers(ui_clock::now() + 100ms);
×
4175
                auto rebuild_res = rebuild_indexes(ui_clock::now() + 15ms);
×
4176
                if (rebuild_res.rir_completed
×
4177
                    && lnav_data.ld_child_pollers.empty()
×
4178
                    && lnav_data.ld_active_files.active_pipers() == 0)
×
4179
                {
4180
                    log_info("  input fully consumed");
×
4181
                    rebuild_indexes_repeatedly();
×
4182

4183
                    auto max_height = lnav::math::vmax(
×
4184
                        lnav_data.ld_log_source.text_line_count(),
4185
                        lnav_data.ld_text_source.text_line_count());
4186

4187
                    if (max_height < term_size.ws_row - 3) {
×
4188
                        log_info("  input is smaller than screen, not paging");
×
4189
                        lnav_data.ld_flags.set<lnav_flags::headless>();
×
4190
                        verbosity = verbosity_t::standard;
×
4191
                        lnav_data.ld_views[LNV_LOG].set_top(0_vl);
×
4192
                    }
4193
                    lnav_data.ld_views[LNV_TEXT].set_top(0_vl);
×
4194
                } else {
4195
                    log_info("  input not fully consumed");
×
4196
                }
4197
            }
4198

4199
            if (lnav_data.ld_flags.is_set<lnav_flags::headless>()) {
651✔
4200
                std::vector<
4201
                    std::pair<Result<std::string, lnav::console::user_message>,
4202
                              std::string>>
4203
                    cmd_results;
649✔
4204
                textview_curses* tc;
4205
                bool output_view = true;
649✔
4206
                auto msg_cb_guard = lnav_data.ld_exec_context.add_msg_callback(
4207
                    [](const auto& um) {
×
4208
                        switch (um.um_level) {
114✔
4209
                            case lnav::console::user_message::level::error:
×
4210
                            case lnav::console::user_message::level::warning:
4211
                                lnav::console::println(stderr,
×
4212
                                                       um.to_attr_line());
4213
                                break;
×
4214
                            default:
114✔
4215
                                break;
114✔
4216
                        }
4217
                    });
763✔
4218

4219
                log_fos->fos_contexts.top().c_show_applicable_annotations
649✔
4220
                    = false;
649✔
4221

4222
                view_colors::init(nullptr);
649✔
4223
                rescan_files(true);
649✔
4224
                wait_for_pipers();
649✔
4225
                rescan_files(true);
649✔
4226
                rebuild_indexes_repeatedly();
649✔
4227
                {
4228
                    safe::WriteAccess<safe_name_to_stubs> errs(
4229
                        *lnav_data.ld_active_files.fc_name_to_stubs);
649✔
4230
                    if (!errs->empty()) {
649✔
4231
                        for (const auto& pair : *errs) {
×
4232
                            lnav::console::print(
×
4233
                                stderr,
4234
                                lnav::console::user_message::error(
×
4235
                                    attr_line_t("unable to open file: ")
×
4236
                                        .append(lnav::roles::file(pair.first)))
×
4237
                                    .with_reason(pair.second.fsi_description));
×
4238
                        }
4239

4240
                        return EXIT_FAILURE;
×
4241
                    }
4242
                }
649✔
4243
                init_session();
649✔
4244
                lnav_data.ld_exec_context.set_output("stdout", stdout, nullptr);
1,298✔
4245
                alerter::singleton().enabled(false);
649✔
4246

4247
                auto* log_tc = &lnav_data.ld_views[LNV_LOG];
649✔
4248
                log_tc->set_height(24_vl);
649✔
4249
                lnav_data.ld_view_stack.push_back(log_tc);
649✔
4250
                // Read all of stdin
4251
                wait_for_pipers();
649✔
4252
                rebuild_indexes_repeatedly();
649✔
4253
                wait_for_children();
649✔
4254

4255
                log_tc->set_top(0_vl);
649✔
4256
                auto* text_tc = &lnav_data.ld_views[LNV_TEXT];
649✔
4257
                if (text_tc->get_inner_height() > 0_vl
649✔
4258
                    && (!text_tc->get_selection().has_value()
1,382✔
4259
                        || lnav_data.ld_text_source.current_file()
817✔
4260
                               ->get_open_options()
84✔
4261
                               .loo_init_location
4262
                               .is<default_for_text_format>()))
84✔
4263
                {
4264
                    text_tc->set_selection(0_vl);
81✔
4265
                }
4266
                if (text_tc->get_selection().has_value()) {
649✔
4267
                    text_tc->set_height(
649✔
4268
                        vis_line_t(text_tc->get_inner_height()
×
4269
                                   - text_tc->get_selection().value()));
1,298✔
4270
                }
4271
                setup_highlights(lnav_data.ld_views[LNV_TEXT].get_highlights());
649✔
4272
                setup_initial_view_stack();
649✔
4273
                for (auto& tview : lnav_data.ld_views) {
6,490✔
4274
                    if (!tview.get_selection().has_value()) {
5,841✔
4275
                        tview.set_selection(0_vl);
1,298✔
4276
                    }
4277
                }
4278

4279
                log_info("Executing initial commands");
649✔
4280
                execute_init_commands(lnav_data.ld_exec_context, cmd_results);
649✔
4281
                run_cleanup_tasks();
649✔
4282
                wait_for_pipers();
649✔
4283
                rescan_files(true);
649✔
4284
                isc::to<curl_looper&, services::curl_streamer_t>()
×
4285
                    .send_and_wait(
649✔
4286
                        [](auto& clooper) { clooper.process_all(); });
649✔
4287
                rebuild_indexes_repeatedly();
649✔
4288
                wait_for_children();
649✔
4289
                {
4290
                    safe::WriteAccess<safe_name_to_stubs> errs(
4291
                        *lnav_data.ld_active_files.fc_name_to_stubs);
649✔
4292
                    if (!errs->empty()) {
649✔
4293
                        for (const auto& pair : *errs) {
×
4294
                            lnav::console::print(
×
4295
                                stderr,
4296
                                lnav::console::user_message::error(
×
4297
                                    attr_line_t("unable to open file: ")
×
4298
                                        .append(lnav::roles::file(pair.first)))
×
4299
                                    .with_reason(pair.second.fsi_description));
×
4300
                        }
4301

4302
                        return EXIT_FAILURE;
×
4303
                    }
4304
                }
649✔
4305

4306
                for (const auto& lf : lnav_data.ld_active_files.fc_files) {
1,296✔
4307
                    auto lf_notes = lf->get_notes();
647✔
4308
                    auto utf_note_opt
4309
                        = lf_notes.value_for(logfile::note_type::not_utf);
647✔
4310
                    if (utf_note_opt.has_value()) {
647✔
4311
                        lnav::console::print(stderr, *utf_note_opt.value());
×
4312
                    }
4313
                }
647✔
4314

4315
                for (const auto& pair : cmd_results) {
1,746✔
4316
                    if (pair.first.isErr()) {
1,097✔
4317
                        lnav::console::print(stderr, pair.first.unwrapErr());
106✔
4318
                        output_view = false;
106✔
4319
                    } else {
4320
                        auto msg = pair.first.unwrap();
991✔
4321

4322
                        if (startswith(msg, "info:")) {
991✔
4323
                            if (verbosity == verbosity_t::verbose) {
322✔
4324
                                printf("%s\n", msg.c_str());
10✔
4325
                            }
4326
                        } else if (!msg.empty()) {
669✔
4327
                            printf("%s\n", msg.c_str());
10✔
4328
                            output_view = false;
10✔
4329
                        }
4330
                    }
991✔
4331
                }
4332

4333
                if (getenv("LNAV_EXTERNAL_URL")) {
649✔
4334
                    auto wakeup_pair = auto_pipe();
×
4335
                    wakeup_pair.open();
×
4336
                    wakeup_pair.read_end().non_blocking();
×
4337

4338
                    static auto& mlooper
4339
                        = injector::get<main_looper&, services::main_t>();
×
4340
                    mlooper.s_wakeup_fd = wakeup_pair.write_end().get();
×
4341
                    while (lnav_data.ld_looping) {
×
4342
                        mlooper.get_port().process_for(50ms);
×
4343
                        rescan_files();
×
4344
                        auto deadline = ui_clock::now() + 1s;
×
4345
                        wait_for_pipers(deadline);
×
4346
                        rebuild_indexes(deadline);
×
4347
                    }
4348
                }
4349

4350
                {
4351
                    auto& bg_service
4352
                        = injector::get<bg_looper&, services::background_t>();
649✔
4353

4354
                    root_superv.stop_child(bg_service.shared_from_this());
649✔
4355
                }
4356

4357
                {
4358
                    auto& pt = lnav::progress_tracker::get_tasks();
649✔
4359

4360
                    for (auto& bt : **pt.readAccess()) {
1,947✔
4361
                        auto tp = bt();
1,298✔
4362
                        if (tp.tp_messages.empty()) {
1,298✔
4363
                            continue;
1,296✔
4364
                        }
4365

4366
                        for (const auto& msg : tp.tp_messages) {
4✔
4367
                            lnav::console::print(stderr, msg);
2✔
4368
                            output_view = false;
2✔
4369
                        }
4370
                    }
1,298✔
4371
                }
4372

4373
                if (output_view && verbosity != verbosity_t::quiet
532✔
4374
                    && !lnav_data.ld_view_stack.empty()
525✔
4375
                    && !lnav_data.ld_stdout_used)
1,181✔
4376
                {
4377
                    bool suppress_empty_lines = false;
394✔
4378
                    unsigned long view_index;
4379
                    vis_line_t y;
394✔
4380

4381
                    tc = *lnav_data.ld_view_stack.top();
394✔
4382
                    // turn off scrollbar since some stuff will resize to
4383
                    // account for it.
4384
                    tc->set_show_scrollbar(false);
394✔
4385
                    view_index = tc - lnav_data.ld_views;
394✔
4386
                    switch (view_index) {
394✔
4387
                        case LNV_DB:
106✔
4388
                        case LNV_HISTOGRAM:
4389
                            suppress_empty_lines = true;
106✔
4390
                            break;
106✔
4391
                        default:
288✔
4392
                            break;
288✔
4393
                    }
4394

4395
                    auto* los = tc->get_overlay_source();
394✔
4396
                    attr_line_t ov_al;
788✔
4397
                    while (los != nullptr && tc->get_inner_height() > 0_vl
887✔
4398
                           && los->list_static_overlay(
1,008✔
4399
                               *tc,
4400
                               list_overlay_source::media_t::file,
4401
                               y,
4402
                               tc->get_inner_height(),
984✔
4403
                               ov_al))
4404
                    {
4405
                        write_line_to(stdout, ov_al);
121✔
4406
                        ov_al.clear();
121✔
4407
                        ++y;
121✔
4408
                    }
4409

4410
                    vis_line_t vl;
394✔
4411
                    for (vl = tc->get_top(); vl < tc->get_inner_height(); ++vl)
13,826✔
4412
                    {
4413
                        std::vector<attr_line_t> rows(1);
13,433✔
4414
                        tc->listview_value_for_rows(*tc, vl, rows);
13,433✔
4415
                        if (suppress_empty_lines && rows[0].empty()) {
13,433✔
4416
                            continue;
×
4417
                        }
4418

4419
                        write_line_to(stdout, rows[0]);
13,433✔
4420

4421
                        std::vector<attr_line_t> row_overlay_content;
13,432✔
4422
                        if (los != nullptr) {
13,432✔
4423
                            los->list_value_for_overlay(
7,095✔
4424
                                *tc, vl, row_overlay_content);
4425
                            if (!row_overlay_content.empty()) {
7,095✔
4426
                                auto hdr_opt = los->list_header_for_overlay(
27✔
4427
                                    *tc,
4428
                                    list_overlay_source::media_t::file,
4429
                                    vl);
27✔
4430
                                if (hdr_opt) {
27✔
4431
                                    auto hdr = hdr_opt.value();
25✔
4432
                                    hdr.with_attr_for_all(VC_STYLE.value(
25✔
4433
                                        text_attrs::with_underline()));
25✔
4434
                                    write_line_to(stdout, hdr);
25✔
4435
                                }
25✔
4436
                            }
27✔
4437
                            for (const auto& ov_row : row_overlay_content) {
7,202✔
4438
                                write_line_to(stdout, ov_row);
107✔
4439
                            }
4440
                        }
4441
                    }
13,433✔
4442
                }
4443
            } else {
650✔
4444
                init_session();
2✔
4445

4446
                guard_termios gt(STDIN_FILENO);
2✔
4447
                lnav_log_orig_termios = gt.get_termios();
2✔
4448

4449
                looper();
2✔
4450

4451
                dup2(STDOUT_FILENO, STDERR_FILENO);
2✔
4452

4453
                signal(SIGINT, SIG_DFL);
2✔
4454
            }
2✔
4455

4456
            log_info("exiting main loop");
650✔
4457
        } catch (const std::system_error& e) {
1✔
4458
            if (e.code().value() != EPIPE) {
1✔
4459
                fprintf(stderr, "error: %s\n", e.what());
×
4460
            }
4461
        } catch (const line_buffer::error& e) {
1✔
4462
            auto um = lnav::console::user_message::error("internal error")
×
4463
                          .with_reason(strerror(e.e_err));
×
4464
            lnav::console::print(stderr, um);
×
4465
        } catch (const std::exception& e) {
×
4466
            auto um = lnav::console::user_message::error("internal error")
×
4467
                          .with_reason(e.what());
×
4468
            lnav::console::print(stderr, um);
×
4469
        }
×
4470

4471
        // When reading from stdin, tell the user where the capture
4472
        // file is stored so they can look at it later.
4473
        if (stdin_url && !lnav_data.ld_flags.is_set<lnav_flags::headless>()) {
651✔
4474
            if (verbosity == verbosity_t::quiet) {
×
4475
                std::error_code ec;
×
4476

4477
                log_debug("removing stdin dir: %s", stdin_dir.c_str());
×
4478
                std::filesystem::remove_all(stdin_dir, ec);
×
4479
            } else {
4480
                file_size_t stdin_size = 0;
×
4481
                for (const auto& ent :
×
4482
                     std::filesystem::directory_iterator(stdin_dir))
×
4483
                {
4484
                    stdin_size += ent.file_size();
×
4485
                }
4486

4487
                lnav::console::print(
×
4488
                    stderr,
4489
                    lnav::console::user_message::info(
×
4490
                        attr_line_t()
×
4491
                            .append(lnav::roles::number(humanize::file_size(
×
4492
                                stdin_size, humanize::alignment::none)))
4493
                            .append(" of data from stdin was captured and "
×
4494
                                    "will be saved for one day.  You can "
4495
                                    "reopen it by running:\n")
4496
                            .appendf(FMT_STRING("   {} "),
×
4497
                                     lnav_data.ld_program_name)
4498
                            .append(lnav::roles::file(stdin_url.value()))));
×
4499
            }
4500
        }
4501
    }
651✔
4502

4503
    return retval;
653✔
4504
}
795✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc