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

tstack / lnav / 19561959745-2698

21 Nov 2025 06:05AM UTC coverage: 68.864%. Remained the same
19561959745-2698

push

github

tstack
[perf] improve stuff related to archive extraction

19 of 27 new or added lines in 4 files covered. (70.37%)

3 existing lines in 2 files now uncovered.

51106 of 74213 relevant lines covered (68.86%)

431629.27 hits per line

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

71.65
/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/stat.h>
41
#include <sys/time.h>
42
#include <sys/wait.h>
43
#include <unistd.h>
44

45
#include "config.h"
46

47
#if defined(__OpenBSD__) && defined(__clang__) \
48
    && !defined(_WCHAR_H_CPLUSPLUS_98_CONFORMANCE_)
49
#    define _WCHAR_H_CPLUSPLUS_98_CONFORMANCE_
50
#endif
51
#include <algorithm>
52
#include <map>
53
#include <memory>
54
#include <set>
55
#include <utility>
56
#include <vector>
57

58
#include <sqlite3.h>
59

60
#ifdef HAVE_BZLIB_H
61
#    include <bzlib.h>
62
#endif
63

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

143
#ifdef HAVE_LIBCURL
144
#    include <curl/curl.h>
145
#endif
146

147
#include "curl_looper.hh"
148

149
#if HAVE_ARCHIVE_H
150
#    include <archive.h>
151
#endif
152

153
#include "archive_manager.hh"
154
#include "command_executor.hh"
155
#include "field_overlay_source.hh"
156
#include "fmt/compile.h"
157
#include "hotkeys.hh"
158
#include "readline_callbacks.hh"
159
#include "readline_possibilities.hh"
160
#include "url_loader.hh"
161
#include "yajlpp/json_ptr.hh"
162

163
#ifdef HAVE_RUST_DEPS
164
#    include "lnav_rs_ext.cxx.hh"
165
#endif
166

167
#ifndef SYSCONFDIR
168
#    define SYSCONFDIR "/usr/etc"
169
#endif
170

171
using namespace std::literals::chrono_literals;
172
using namespace lnav::roles::literals;
173
using namespace md4cpp::literals;
174

175
static std::vector<std::filesystem::path> DEFAULT_FILES;
176
static auto intern_lifetime = intern_string::get_table_lifetime();
177

178
static std::vector<std::pair<const char*, std::future<void>>> CLEANUP_TASKS;
179

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

190
    char buf[buflen()] = {};
191

192
public:
193
    constexpr to_string_t() noexcept
194
    {
195
        auto ptr = buf + buflen();
196
        *--ptr = '\0';
197

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

208
    constexpr operator const char*() const { return buf; }
209
};
210

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

223
static auto bound_pollable_supervisor
224
    = injector::bind<pollable_supervisor>::to_singleton();
225

226
static auto bound_active_files = injector::bind<file_collection>::to_instance(
227
    +[]() { return &lnav_data.ld_active_files; });
756✔
228

229
static auto bound_sqlite_db
230
    = injector::bind<auto_sqlite3>::to_instance(&lnav_data.ld_db);
231

232
static auto bound_lnav_flags
233
    = injector::bind<unsigned long, lnav_flags_tag>::to_instance(
234
        &lnav_data.ld_flags);
235

236
static auto bound_lnav_exec_context
237
    = injector::bind<exec_context>::to_instance(&lnav_data.ld_exec_context);
238

239
static auto bound_last_rel_time
240
    = injector::bind<relative_time, last_relative_time_tag>::to_singleton();
241

242
static auto bound_term_extra = injector::bind<term_extra>::to_singleton();
243

244
static auto bound_xterm_mouse = injector::bind<xterm_mouse>::to_singleton();
245

246
static auto bound_scripts = injector::bind<available_scripts>::to_singleton();
247

248
static auto bound_curl
249
    = injector::bind_multiple<isc::service_base>()
250
          .add_singleton<curl_looper, services::curl_streamer_t>();
251

252
static auto bound_tailer
253
    = injector::bind_multiple<isc::service_base>()
254
          .add_singleton<tailer::looper, services::remote_tailer_t>();
255

256
static auto bound_main = injector::bind_multiple<static_service>()
257
                             .add_singleton<main_looper, services::main_t>();
258

259
static auto bound_file_options_hier
260
    = injector::bind<lnav::safe_file_options_hier>::to_singleton();
261

262
static auto bound_exec_phase = injector::bind<lnav::exec_phase>::to_singleton();
263

264
namespace injector {
265
template<>
266
void
267
force_linking(last_relative_time_tag anno)
2✔
268
{
269
}
2✔
270

271
template<>
272
void
273
force_linking(lnav_flags_tag anno)
756✔
274
{
275
}
756✔
276

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

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

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

296
struct lnav_data_t lnav_data;
297

298
bool
299
setup_logline_table(exec_context& ec)
513✔
300
{
301
    auto& log_view = lnav_data.ld_views[LNV_LOG];
513✔
302
    bool retval = false;
513✔
303

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

324
    auto& db_key_names = lnav_data.ld_db_key_names;
513✔
325

326
    db_key_names = DEFAULT_DB_KEY_NAMES;
513✔
327

328
    for (const auto& iter : *lnav_data.ld_vtab_manager) {
44,127✔
329
        iter.second->get_foreign_keys(db_key_names);
43,614✔
330
    }
331

332
    return retval;
513✔
333
}
334

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

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

365
    return retval;
×
366
}
367

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

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

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

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

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

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

423
readline_context::command_map_t lnav_commands;
424

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

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

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

457
    attr_line_t ex2_term;
1✔
458

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

467
    attr_line_t ex3_term;
1✔
468

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

482
    attr_line_t usage_al;
1✔
483

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

801
    return true;
2✔
802
}
803

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

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

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

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

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

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

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

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

889
    return true;
×
890
}
891

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

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

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

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

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

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

921
static void
922
gather_pipers()
2,843✔
923
{
924
    for (auto iter = lnav_data.ld_child_pollers.begin();
2,843✔
925
         iter != lnav_data.ld_child_pollers.end();)
2,850✔
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
        } else {
932
            ++iter;
1✔
933
        }
934
    }
935
}
2,843✔
936

937
void
938
wait_for_pipers(std::optional<ui_clock::time_point> deadline)
2,836✔
939
{
940
    static constexpr auto MAX_SLEEP_TIME = 300ms;
941
    auto sleep_time = 10ms;
2,836✔
942
    auto loop_count = 0;
2,836✔
943

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

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

975
struct refresh_status_bars {
976
    refresh_status_bars(std::shared_ptr<top_status_source> top_source)
2✔
977
        : rsb_top_source(std::move(top_source))
2✔
978
    {
979
    }
2✔
980

981
    using injectable
982
        = refresh_status_bars(std::shared_ptr<top_status_source> top_source);
983

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

998
        if (!lnav_data.ld_looping || lnav_data.ld_view_stack.empty()) {
17✔
999
            return lnav::progress_result_t::interrupt;
4✔
1000
        }
1001

1002
        gettimeofday(&current_time, nullptr);
13✔
1003
        while (notcurses_get_nblock(this->rsb_screen->get_notcurses(), &ch) > 0)
13✔
1004
        {
1005
            lnav_data.ld_user_message_source.clear();
×
1006

1007
            alerter::singleton().new_input(ch);
×
1008

1009
            lnav_data.ld_input_dispatcher.new_input(
×
1010
                current_time, this->rsb_screen->get_notcurses(), ch);
×
1011

1012
            lnav_data.ld_view_stack.top() | [ch](auto tc) {
×
1013
                lnav_data.ld_key_repeat_history.update(ch.id, tc->get_top());
×
1014
            };
1015

1016
            if (ncinput_ctrl_p(&ch) && ch.id == ']') {
×
1017
                lnav_data.ld_bottom_source.update_loading(0, 0);
×
1018
                lnav_data.ld_status[LNS_BOTTOM].set_needs_update();
×
1019
                retval = lnav::progress_result_t::interrupt;
×
1020
            }
1021

1022
            ncinput_free_paste_content(&ch);
×
1023

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

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

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

1077
        notcurses_render(this->rsb_screen->get_notcurses());
13✔
1078

1079
        return retval;
13✔
1080
    }
1081

1082
    screen_curses* rsb_screen;
1083
    std::shared_ptr<top_status_source> rsb_top_source;
1084
};
1085

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

1117
static void
1118
check_for_file_zones()
1✔
1119
{
1120
    auto with_tz_count = 0;
1✔
1121
    std::vector<std::string> without_tz_files;
1✔
1122

1123
    for (const auto& lf : lnav_data.ld_active_files.fc_files) {
2✔
1124
        auto format = lf->get_format_ptr();
1✔
1125
        if (format == nullptr) {
1✔
1126
            continue;
×
1127
        }
1128

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

1160
        lnav_data.ld_exec_context.ec_msg_callback_stack.back()(um);
×
1161
    }
1162
}
1✔
1163

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

1176
    std::error_code errc;
1✔
1177
    std::filesystem::create_directories(lnav::paths::workdir(), errc);
1✔
1178
    auto open_temp_res = lnav::filesystem::open_temp_file(lnav::paths::workdir()
2✔
1179
                                                          / "exec.XXXXXX");
3✔
1180

1181
    if (open_temp_res.isErr()) {
1✔
1182
        lnav::prompt::get().p_editor.set_inactive_value(
×
1183
            fmt::format(FMT_STRING("Unable to open temporary output file: {}"),
×
1184
                        open_temp_res.unwrapErr()));
×
1185
    } else {
1186
        auto tmp_pair = open_temp_res.unwrap();
1✔
1187
        auto fd_copy = tmp_pair.second.dup();
1✔
1188
        auto tf = text_format_t::TF_UNKNOWN;
1✔
1189

1190
        {
1191
            exec_context::output_guard og(
1192
                ec,
1193
                "tmp",
1194
                std::make_pair(fdopen(tmp_pair.second.release(), "w"), fclose));
2✔
1195
            execute_init_commands(ec, cmd_results);
1✔
1196
            tf = ec.ec_output_stack.back().od_format;
1✔
1197
        }
1✔
1198

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

1211
            lnav::prompt::get().p_editor.set_alt_value(
×
1212
                HELP_MSG_1(X, "to close the file"));
1213
        }
1214
    }
1✔
1215
}
1✔
1216

1217
static void
1218
run_cleanup_tasks()
612✔
1219
{
1220
    CLEANUP_TASKS.emplace_back("line_buffer", line_buffer::cleanup_cache());
612✔
1221
    CLEANUP_TASKS.emplace_back("archive_manager",
612✔
1222
                               archive_manager::cleanup_cache());
1,224✔
1223
    CLEANUP_TASKS.emplace_back("tailer", tailer::cleanup_cache());
612✔
1224
    CLEANUP_TASKS.emplace_back("piper", lnav::piper::cleanup());
612✔
1225
    CLEANUP_TASKS.emplace_back("file_converter_manager",
612✔
1226
                               file_converter_manager::cleanup());
1,224✔
1227
}
612✔
1228

1229
static void
1230
looper()
2✔
1231
{
1232
    auto filter_sub_life
1233
        = injector::bind<filter_sub_source>::to_scoped_singleton();
2✔
1234
    auto crumb_life = injector::bind<breadcrumb_curses>::to_scoped_singleton();
2✔
1235
    auto* ps = injector::get<pollable_supervisor*>();
2✔
1236
    auto* filter_source = injector::get<filter_sub_source*>();
2✔
1237
    auto* breadcrumb_view = injector::get<breadcrumb_curses*>();
2✔
1238
    auto& exec_phase = injector::get<lnav::exec_phase&>();
2✔
1239

1240
    auto& ec = lnav_data.ld_exec_context;
2✔
1241
    sig_atomic_t overlay_counter = 0;
2✔
1242

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

1256
    auto& sb = lnav_data.ld_scroll_broadcaster;
2✔
1257
    auto& vsb = lnav_data.ld_view_stack_broadcaster;
2✔
1258

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

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

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

1332
        mouse_note.unwrap().execute();
×
1333
    }
1334

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

1355
    auto_fd errpipe[2];
12✔
1356
    auto_fd::pipe(errpipe);
2✔
1357

1358
    errpipe[0].close_on_exec();
2✔
1359
    errpipe[1].close_on_exec();
2✔
1360
    auto pipe_err_handle = std::make_optional(
1361
        log_pipe_err(errpipe[0].release(), errpipe[1].release()));
2✔
1362

1363
    notcurses_options nco = {};
2✔
1364
    nco.flags |= NCOPTION_SUPPRESS_BANNERS | NCOPTION_NO_WINCH_SIGHANDLER;
2✔
1365
    nco.loglevel = NCLOGLEVEL_PANIC;
2✔
1366
    auto create_screen_res = screen_curses::create(nco);
2✔
1367

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

1401
    auto sc = create_screen_res.unwrap();
2✔
1402
    auto inputready_fd = notcurses_inputready_fd(sc.get_notcurses());
2✔
1403
    auto& mouse_i = injector::get<xterm_mouse&>();
2✔
1404

1405
    auto _paste = finally(
1406
        [&sc] { notcurses_bracketed_paste_disable(sc.get_notcurses()); });
4✔
1407
    notcurses_bracketed_paste_enable(sc.get_notcurses());
2✔
1408

1409
    auto ui_cb_mouse = false;
2✔
1410
    ec.ec_ui_callbacks.uc_pre_stdout_write = [&sc, &mouse_i, &ui_cb_mouse]() {
×
1411
        ui_cb_mouse = mouse_i.is_enabled();
×
1412
        if (ui_cb_mouse) {
×
1413
            mouse_i.set_enabled(sc.get_notcurses(), false);
×
1414
        }
1415
        notcurses_leave_alternate_screen(sc.get_notcurses());
×
1416

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

1427
        auto nci = ncinput{};
×
1428
        do {
1429
            notcurses_get_blocking(sc.get_notcurses(), &nci);
×
1430
            ncinput_free_paste_content(&nci);
×
1431
        } while (nci.evtype == NCTYPE_RELEASE || ncinput_lock_p(&nci)
×
1432
                 || ncinput_modifier_p(&nci));
×
1433
        notcurses_enter_alternate_screen(sc.get_notcurses());
×
1434

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

1447
    lnav_behavior lb;
2✔
1448

1449
    ui_periodic_timer::singleton();
2✔
1450

1451
    mouse_i.set_behavior(&lb);
2✔
1452
    mouse_i.set_enabled(
2✔
1453
        sc.get_notcurses(),
1454
        check_experimental("mouse")
2✔
1455
            || lnav_config.lc_mouse_mode == lnav_mouse_mode::enabled);
2✔
1456

1457
    lnav_data.ld_window = sc.get_std_plane();
2✔
1458

1459
    auto& vc = view_colors::singleton();
2✔
1460
    view_colors::init(sc.get_notcurses());
2✔
1461

1462
    auto ecb_guard
1463
        = lnav_data.ld_exec_context.add_msg_callback([](const auto& um) {
×
1464
              auto al = um.to_attr_line().rtrim();
12✔
1465

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

1476
    {
1477
        setup_highlights(lnav_data.ld_views[LNV_LOG].get_highlights());
2✔
1478
        setup_highlights(lnav_data.ld_views[LNV_TEXT].get_highlights());
2✔
1479
        setup_highlights(lnav_data.ld_views[LNV_SCHEMA].get_highlights());
2✔
1480
        setup_highlights(lnav_data.ld_views[LNV_PRETTY].get_highlights());
2✔
1481
        setup_highlights(lnav_data.ld_preview_view[0].get_highlights());
2✔
1482
        setup_highlights(lnav_data.ld_preview_view[1].get_highlights());
2✔
1483

1484
        for (const auto& format : log_format::get_root_formats()) {
155✔
1485
            for (auto& hl : format->lf_highlighters) {
157✔
1486
                if (hl.h_attrs.empty()) {
4✔
1487
                    hl.with_attrs(vc.attrs_for_ident(hl.h_name));
×
1488
                }
1489

1490
                lnav_data.ld_views[LNV_LOG].get_highlights()[{
4✔
1491
                    highlight_source_t::CONFIGURATION,
×
1492
                    format->get_name().to_string() + "-" + hl.h_name}]
8✔
1493
                    = hl;
4✔
1494
            }
1495
        }
1496
    }
1497

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

1528
    lnav_data.ld_view_stack.push_back(&lnav_data.ld_views[LNV_LOG]);
2✔
1529

1530
    sb.push_back(clear_last_user_mark);
2✔
1531
    sb.push_back(update_view_position);
2✔
1532
    vsb.push_back(
2✔
1533
        bind_mem(&term_extra::update_title, injector::get<term_extra*>()));
2✔
1534
    vsb.push_back([](listview_curses* lv) {
2✔
1535
        auto* tc = dynamic_cast<textview_curses*>(lv);
2✔
1536

1537
        tc->tc_state_event_handler(*tc);
2✔
1538
    });
2✔
1539

1540
    vsb.push_back(sb);
2✔
1541

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

1559
                      auto op_guard = lnav_opid_guard::internal(op);
×
1560

1561
                      p(key);
×
1562
                      bc.reload_data();
×
1563
                      if (bc.is_focused()) {
×
1564
                          bc.focus_next();
×
1565
                      }
1566
                      bc.set_needs_update();
×
1567
                  });
×
1568
          };
2✔
1569
    auto event_handler = [](auto&& tc) {
4✔
1570
        auto top_view = lnav_data.ld_view_stack.top();
4✔
1571

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

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

1639
    lnav_data.ld_example_view.set_title("Examples");
2✔
1640
    lnav_data.ld_example_view.set_window(lnav_data.ld_window);
2✔
1641
    lnav_data.ld_example_view.set_show_scrollbar(false);
2✔
1642

1643
    lnav_data.ld_preview_view[0].set_title("Preview #0");
2✔
1644
    lnav_data.ld_preview_view[0].set_window(lnav_data.ld_window);
2✔
1645
    lnav_data.ld_preview_view[0].set_show_scrollbar(false);
2✔
1646
    lnav_data.ld_preview_view[1].set_title("Preview #1");
2✔
1647
    lnav_data.ld_preview_view[1].set_window(lnav_data.ld_window);
2✔
1648
    lnav_data.ld_preview_view[1].set_show_scrollbar(false);
2✔
1649

1650
    lnav_data.ld_filter_view.set_title("Text Filters");
2✔
1651
    lnav_data.ld_filter_view.set_selectable(true);
2✔
1652
    lnav_data.ld_filter_view.set_window(lnav_data.ld_window);
2✔
1653
    lnav_data.ld_filter_view.set_show_scrollbar(true);
2✔
1654
    filter_source->fss_editor->tc_window = lnav_data.ld_window;
2✔
1655

1656
    lnav_data.ld_files_view.set_title("Files");
2✔
1657
    lnav_data.ld_files_view.set_selectable(true);
2✔
1658
    lnav_data.ld_files_view.set_window(lnav_data.ld_window);
2✔
1659
    lnav_data.ld_files_view.set_show_scrollbar(true);
2✔
1660
    lnav_data.ld_files_view.get_disabled_highlights().set(
2✔
1661
        highlight_source_t::THEME);
1662
    lnav_data.ld_files_view.set_overlay_source(&lnav_data.ld_files_overlay);
2✔
1663

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

1676
    lnav_data.ld_progress_view.set_window(lnav_data.ld_window);
2✔
1677
    lnav_data.ld_user_message_view.set_window(lnav_data.ld_window);
2✔
1678

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

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

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

1712
    auto top_status_lifetime
1713
        = injector::bind<top_status_source>::to_scoped_singleton();
2✔
1714
    auto top_source = injector::get<std::shared_ptr<top_status_source>>();
2✔
1715

1716
    lnav_data.ld_bottom_source.on_drag = [](mouse_event& me) {
2✔
1717
        static auto& prompt = lnav::prompt::get();
1718

1719
        if (!prompt.p_editor.is_enabled() || prompt.p_editor.tc_height == 1) {
×
1720
            return;
×
1721
        }
1722

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

1739
              execute_command(lnav_data.ld_exec_context, cmd);
×
1740
          };
2✔
1741
    lnav_data.ld_bottom_source.get_field(bottom_status_source::BSF_SEARCH_TERM)
2✔
1742
        .on_click
1743
        = [](status_field&) {
2✔
1744
              auto term
1745
                  = lnav_data.ld_view_stack.top().value()->get_current_search();
×
1746
              auto cmd = fmt::format(FMT_STRING("prompt search / '{}'"), term);
×
1747

1748
              execute_command(lnav_data.ld_exec_context, cmd);
×
1749
          };
2✔
1750

1751
    lnav_data.ld_status[LNS_TOP].set_title("top");
2✔
1752
    lnav_data.ld_status[LNS_TOP].set_y(0);
2✔
1753
    lnav_data.ld_status[LNS_TOP].set_data_source(top_source.get());
2✔
1754
    lnav_data.ld_status[LNS_TOP].set_default_role(role_t::VCR_INACTIVE_STATUS);
2✔
1755
    lnav_data.ld_status[LNS_BOTTOM].set_title("bottom");
2✔
1756
    lnav_data.ld_status[LNS_BOTTOM].set_y(-2);
2✔
1757
    for (auto& stat_bar : lnav_data.ld_status) {
20✔
1758
        stat_bar.set_window(lnav_data.ld_window);
18✔
1759
    }
1760
    lnav_data.ld_status[LNS_BOTTOM].set_data_source(
2✔
1761
        &lnav_data.ld_bottom_source);
1762
    lnav_data.ld_status[LNS_FILTER].set_title("filter");
2✔
1763
    lnav_data.ld_status[LNS_FILTER].set_data_source(
2✔
1764
        &lnav_data.ld_filter_status_source);
1765
    lnav_data.ld_status[LNS_FILTER_HELP].set_title("filter help");
2✔
1766
    lnav_data.ld_status[LNS_FILTER_HELP].set_data_source(
2✔
1767
        &lnav_data.ld_filter_help_status_source);
1768

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

1798
    lnav_data.ld_user_message_view.set_show_bottom_border(true);
2✔
1799

1800
    for (auto& sc : lnav_data.ld_status) {
20✔
1801
        sc.window_change();
18✔
1802
    }
1803

1804
    auto session_path = lnav::paths::dotlnav() / "session";
2✔
1805
    execute_file(ec, session_path.string());
2✔
1806

1807
    sb(*lnav_data.ld_view_stack.top());
2✔
1808
    vsb(*lnav_data.ld_view_stack.top());
2✔
1809

1810
    lnav_data.ld_view_stack.vs_change_handler
1811
        = [](textview_curses* tc) { lnav_data.ld_view_stack_broadcaster(tc); };
2✔
1812

1813
    {
1814
        auto& id = lnav_data.ld_input_dispatcher;
2✔
1815

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

1843
    auto refresher_lifetime
1844
        = injector::bind<refresh_status_bars>::to_scoped_singleton();
2✔
1845

1846
    auto refresher = injector::get<std::shared_ptr<refresh_status_bars>>();
2✔
1847
    refresher->rsb_screen = &sc;
2✔
1848

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

1852
    {
1853
        auto* tss = static_cast<timeline_source*>(
1854
            lnav_data.ld_views[LNV_TIMELINE].get_sub_source());
2✔
1855
        tss->gs_index_progress
1856
            = [refresher](std::optional<timeline_source::progress_t> prog) {
4✔
1857
                  if (prog) {
×
1858
                      lnav_data.ld_bottom_source.update_loading(prog->p_curr,
×
1859
                                                                prog->p_total);
×
1860
                  } else {
1861
                      lnav_data.ld_bottom_source.update_loading(0, 0);
×
1862
                  }
1863
                  lnav_data.ld_status[LNS_BOTTOM].set_needs_update();
×
1864
                  return refresher->doit(lnav::func::op_type::blocking);
×
1865
              };
2✔
1866
    }
1867

1868
    auto& timer = ui_periodic_timer::singleton();
2✔
1869
    timeval current_time;
1870

1871
    static sig_atomic_t index_counter;
1872

1873
    timer.start_fade(index_counter, 1);
2✔
1874

1875
    std::future<file_collection> rescan_future;
2✔
1876

1877
    log_info("initial rescan started");
2✔
1878
    rescan_future = std::async(std::launch::async,
2✔
1879
                               &file_collection::rescan_files,
2✔
1880
                               lnav_data.ld_active_files.copy(),
2✔
1881
                               false);
4✔
1882

1883
    auto rescan_needed = false;
2✔
1884
    auto ui_start_time = ui_clock::now();
2✔
1885
    auto next_rebuild_time = ui_start_time;
2✔
1886
    auto next_status_update_time = ui_start_time;
2✔
1887
    auto next_rescan_time = ui_start_time;
2✔
1888
    auto got_user_input = true;
2✔
1889
    auto loop_count = 0;
2✔
1890
    auto opened_files = false;
2✔
1891
    std::vector<view_curses*> updated_views;
2✔
1892
    updated_views.emplace_back(&lnav_data.ld_view_stack);
2✔
1893

1894
    auto wakeup_pair = auto_pipe();
2✔
1895
    wakeup_pair.open();
2✔
1896
    wakeup_pair.read_end().non_blocking();
2✔
1897

1898
    static auto& mlooper = injector::get<main_looper&, services::main_t>();
2✔
1899
    mlooper.s_wakeup_fd = wakeup_pair.write_end().get();
2✔
1900

1901
    exec_phase.completed(lnav::phase_t::init);
2✔
1902
    while (lnav_data.ld_looping) {
11✔
1903
        auto loop_deadline
1904
            = ui_clock::now() + (exec_phase.spinning_up() ? 3s : 50ms);
9✔
1905
        loop_count += 1;
9✔
1906

1907
        std::vector<pollfd> pollfds;
9✔
1908
        size_t starting_view_stack_size = lnav_data.ld_view_stack.size();
9✔
1909
        size_t changes = 0;
9✔
1910
        int rc;
1911

1912
        pollfds.emplace_back(pollfd{wakeup_pair.read_end(), POLLIN, 0});
9✔
1913

1914
        auto ui_now = ui_clock::now();
9✔
1915
        gettimeofday(&current_time, nullptr);
9✔
1916

1917
        if (top_source->update_time(current_time)) {
9✔
1918
            lnav_data.ld_status[LNS_TOP].set_needs_update();
2✔
1919
        }
1920

1921
        layout_views();
9✔
1922

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

1952
                    ago = humanize::time::point::from_tv(
×
1953
                              {(time_t) session_data.sd_save_time, 0})
×
1954
                              .as_time_ago();
×
1955
                    auto um = lnav::console::user_message::ok(
1956
                        attr_line_t("restored session from ")
×
1957
                            .append(lnav::roles::number(ago))
×
1958
                            .append("; press ")
×
1959
                            .append("CTRL-R"_hotkey)
×
1960
                            .append(" to reset session"));
×
1961
                    lnav::prompt::get().p_editor.set_inactive_value(
×
1962
                        um.to_attr_line());
×
1963
                }
1964

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

1980
                if (height > 0_vl) {
1✔
1981
                    fview.set_selection(height - 1_vl);
1✔
1982
                }
1983
            }
1984

1985
            rescan_future = std::future<file_collection>{};
7✔
1986
            next_rescan_time
1987
                = ui_now + (std::exchange(rescan_needed, false) ? 0ms : 333ms);
7✔
1988
        }
7✔
1989

1990
        if (!opened_files && exec_phase.scanning()
3✔
1991
            && ui_now - ui_start_time >= 500ms)
12✔
1992
        {
NEW
1993
            set_view_mode(ln_mode_t::FILES);
×
1994
        }
1995

1996
        if (!rescan_future.valid()
9✔
1997
            && (exec_phase.spinning_up()
13✔
1998
                || (lnav_data.ld_active_files.is_below_open_file_limit()
4✔
1999
                    && ui_clock::now() >= next_rescan_time)))
13✔
2000
        {
2001
            rescan_future = std::async(std::launch::async,
5✔
2002
                                       &file_collection::rescan_files,
5✔
2003
                                       lnav_data.ld_active_files.copy(),
5✔
2004
                                       false);
10✔
2005
            if (exec_phase.interactive()) {
5✔
2006
                // log_trace("%d: shortening deadline", loop_count);
2007
                loop_deadline = ui_clock::now() + 10ms;
×
2008
            }
2009
        }
2010

2011
        mlooper.get_port().process_for(0s);
9✔
2012

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

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

2135
                {
2136
                    hasher h;
5✔
2137

2138
                    lnav_data.ld_views[LNV_LOG].update_hash_state(h);
5✔
2139
                    vs.vs_log = h.to_uuid_string();
5✔
2140
                }
2141
                {
2142
                    auto sel_opt = lnav_data.ld_views[LNV_LOG].get_selection();
5✔
2143
                    if (sel_opt
5✔
2144
                        && sel_opt.value()
5✔
2145
                            < lnav_data.ld_log_source.text_line_count())
5✔
2146
                    {
2147
                        auto win = lnav_data.ld_log_source.window_at(
2148
                            sel_opt.value());
×
2149
                        auto win_iter = win->begin();
×
2150
                        auto hash_res = win_iter->get_line_hash();
×
2151
                        if (hash_res.isOk()) {
×
2152
                            vs.vs_log_selection = hash_res.unwrap().to_string();
×
2153
                        }
2154
                    }
2155
                }
2156

2157
                {
2158
                    hasher h;
5✔
2159

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

2209
        if (prompt.p_editor.is_enabled()) {
9✔
2210
            prompt.p_editor.focus();
×
2211
        } else if (filter_source->fss_editing) {
9✔
2212
            filter_source->fss_editor->focus();
1✔
2213
        }
2214
        if (got_user_input || !updated_views.empty()) {
9✔
2215
            if (true) {
2216
                for (const auto* view_ptr : updated_views) {
56✔
2217
                    log_trace("updated view %s %s",
47✔
2218
                              typeid(*view_ptr).name(),
2219
                              view_ptr->get_title().c_str());
2220
                }
2221
            }
2222
            notcurses_render(sc.get_notcurses());
9✔
2223
            updated_views.clear();
9✔
2224
        }
2225

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

2255
        ps->update_poll_set(pollfds);
9✔
2256
        ui_now = ui_clock::now();
9✔
2257
        auto poll_to
2258
            = (exec_phase.interactive() && !changes && ui_now < loop_deadline)
13✔
2259
            ? std::chrono::duration_cast<std::chrono::milliseconds>(
13✔
2260
                  loop_deadline - ui_now)
13✔
2261
            : 0ms;
9✔
2262

2263
        if (false && poll_to.count() > 0) {
2264
            log_trace(
2265
                "%d: poll() with timeout %lld ", loop_count, poll_to.count());
2266
            log_trace("  (changes=%zu; before_deadline=%d; exec_phase=%d)",
2267
                      changes,
2268
                      ui_now < loop_deadline,
2269
                      exec_phase.ep_value);
2270
        }
2271
        rc = poll(pollfds.data(), pollfds.size(), poll_to.count());
9✔
2272

2273
        if (pollfds[0].revents & POLLIN) {
9✔
2274
            char buffer[128];
2275

2276
            while (read(wakeup_pair.read_end(), buffer, sizeof(buffer)) > 0) {
×
2277
                // wakeup received
2278
            }
2279
        }
2280

2281
        gettimeofday(&current_time, nullptr);
9✔
2282
        ui_now = ui_clock::now();
9✔
2283
        lb.tick(current_time);
9✔
2284

2285
        got_user_input = false;
9✔
2286
        if (rc < 0) {
9✔
2287
            switch (errno) {
×
2288
                case 0:
×
2289
                case EINTR:
2290
                    break;
×
2291

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

2303
            if (in_revents & (POLLHUP | POLLNVAL)) {
9✔
2304
                log_info("stdin has been closed, exiting...");
×
2305
                lnav_data.ld_looping = false;
×
2306
            } else if (in_revents & POLLIN) {
9✔
2307
                static auto op = lnav_operation{"user_input"};
5✔
2308

2309
                auto op_guard = lnav_opid_guard::internal(op);
5✔
2310
                got_user_input = true;
5✔
2311
                ncinput nci;
2312
                auto old_gen = lnav_data.ld_active_files.fc_files_generation;
5✔
2313
                while (notcurses_get_nblock(sc.get_notcurses(), &nci) > 0) {
15✔
2314
                    if (nci.evtype != NCTYPE_RELEASE) {
10✔
2315
                        lnav_data.ld_user_message_source.clear();
10✔
2316
                    }
2317

2318
                    alerter::singleton().new_input(nci);
10✔
2319

2320
                    lnav_data.ld_input_dispatcher.new_input(
10✔
2321
                        current_time, sc.get_notcurses(), nci);
2322

2323
                    lnav_data.ld_view_stack.top() | [&nci](auto tc) {
10✔
2324
                        lnav_data.ld_key_repeat_history.update(nci.id,
8✔
2325
                                                               tc->get_top());
2326
                    };
8✔
2327
                    ncinput_free_paste_content(&nci);
10✔
2328

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

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

2369
            auto old_mode = lnav_data.ld_mode;
9✔
2370

2371
            ps->check_poll_set(pollfds);
9✔
2372
            lnav_data.ld_view_stack.top() | [](auto tc) { update_hits(tc); };
16✔
2373

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

2399
        if (prompt.p_editor.is_enabled()) {
9✔
2400
            prompt.p_editor.tick(ui_now);
×
2401
        }
2402

2403
        if (timer.time_to_update(overlay_counter)) {
9✔
2404
            lnav_data.ld_view_stack.top() |
2✔
2405
                [](auto tc) { tc->set_overlay_needs_update(); };
2✔
2406
        }
2407

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

2429
        if (exec_phase.running_commands()) {
9✔
2430
            static bool ran_cleanup = false;
2431
            std::vector<
2432
                std::pair<Result<std::string, lnav::console::user_message>,
2433
                          std::string>>
2434
                cmd_results;
2✔
2435
            std::optional<ui_clock::time_point> deadline;
2✔
2436

2437
            if (lnav_data.ld_input_dispatcher.id_count > 0) {
2✔
2438
                deadline = loop_deadline;
×
2439
            }
2440
            ui_execute_init_commands(ec, cmd_results, deadline);
2✔
2441

2442
            if (!cmd_results.empty()) {
2✔
2443
                auto& prompt = lnav::prompt::get();
1✔
2444
                auto last_cmd_result = cmd_results.back();
1✔
2445

2446
                if (last_cmd_result.first.isOk()) {
1✔
2447
                    prompt.p_editor.set_inactive_value(
1✔
2448
                        last_cmd_result.first.unwrap());
2✔
2449
                } else {
2450
                    ec.ec_msg_callback_stack.back()(
×
2451
                        last_cmd_result.first.unwrapErr());
×
2452
                }
2453
                prompt.p_editor.set_alt_value(last_cmd_result.second);
1✔
2454
            }
1✔
2455

2456
            if (!ran_cleanup) {
2✔
2457
                run_cleanup_tasks();
2✔
2458
                ran_cleanup = true;
2✔
2459
            }
2460

2461
            exec_phase.completed(lnav::phase_t::commands);
2✔
2462
            check_for_enough_colors(sc);
2✔
2463
        }
2✔
2464

2465
        if (exec_phase.loading_session()) {
9✔
2466
            if (lnav_data.ld_mode == ln_mode_t::FILES) {
2✔
2467
                if (lnav_data.ld_active_files.fc_other_files.empty()
2✔
2468
                    && lnav_data.ld_active_files.fc_name_to_stubs->readAccess()
2✔
2469
                           ->empty())
1✔
2470
                {
2471
                    log_info("%d: switching to paging!", loop_count);
1✔
2472
                    set_view_mode(ln_mode_t::PAGING);
1✔
2473
                    lnav_data.ld_active_files.fc_files
2474
                        | lnav::itertools::for_each(&logfile::dump_stats);
1✔
2475

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

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

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

2511
        if (handle_winch(&sc)) {
9✔
2512
            got_user_input = true;
×
2513
            next_status_update_time = ui_now;
×
2514
            layout_views();
×
2515
        }
2516

2517
        if (lnav_data.ld_child_terminated) {
9✔
2518
            lnav_data.ld_child_terminated = false;
×
2519

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

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

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

2546
            gather_pipers();
×
2547

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2649
    do {
2650
        pollfds.clear();
2,245✔
2651

2652
        auto update_res = ps->update_poll_set(pollfds);
2,245✔
2653

2654
        if (update_res.ur_background == 0) {
2,245✔
2655
            break;
2,226✔
2656
        }
2657

2658
        int rc = poll(&pollfds[0], pollfds.size(), to.tv_usec / 1000);
19✔
2659

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

2670
        ps->check_poll_set(pollfds);
19✔
2671
        lnav_data.ld_view_stack.top() | [](auto tc) { update_hits(tc); };
38✔
2672
    } while (lnav_data.ld_looping);
19✔
2673
}
2,226✔
2674

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

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

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

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

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

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

2738
    return retval;
14✔
2739
}
2740

2741
verbosity_t verbosity = verbosity_t::standard;
2742

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

2751
    bool exec_stdin = false, load_stdin = false;
756✔
2752
    mode_flags_t mode_flags;
756✔
2753
    std::string since_time;
756✔
2754
    std::string until_time;
756✔
2755
    const char* LANG = getenv("LANG");
756✔
2756

2757
    if (LANG == nullptr || strcmp(LANG, "C") == 0) {
756✔
2758
        setenv("LANG", "en_US.UTF-8", 1);
×
2759
    }
2760

2761
    {
2762
        const auto* TEMP = getenv("TEMP");
756✔
2763

2764
        if (TEMP != nullptr) {
756✔
2765
            setenv("TMPDIR", TEMP, 0);
×
2766
        } else {
2767
            setenv("TMPDIR",
756✔
2768
                   std::filesystem::temp_directory_path().string().c_str(),
1,512✔
2769
                   0);
2770
        }
2771
    }
2772

2773
    if (lnav::console::only_process_attached_to_win32_console()) {
756✔
2774
        mode_flags.mf_no_default = true;
×
2775
    }
2776

2777
    ec.ec_label_source_stack.push_back(&lnav_data.ld_db_row_source);
756✔
2778

2779
    (void) signal(SIGPIPE, SIG_IGN);
756✔
2780
    (void) signal(SIGCHLD, sigchld);
756✔
2781
    setlocale(LC_ALL, "");
756✔
2782
    try {
2783
        std::locale::global(std::locale(""));
756✔
2784
    } catch (const std::runtime_error& e) {
×
2785
        log_error("unable to set locale to ''");
×
2786
    }
×
2787
    umask(027);
756✔
2788

2789
    /* Disable Lnav from being able to execute external commands if
2790
     * "LNAVSECURE" environment variable is set by the user.
2791
     */
2792
    if (getenv("LNAVSECURE") != nullptr) {
756✔
2793
        lnav_data.ld_flags |= LNF_SECURE_MODE;
5✔
2794
    }
2795

2796
    // Set PAGER so that stuff run from `:sh` will just dump their
2797
    // output for lnav to display.  One example would be `man`, as
2798
    // in `:sh man ls`.
2799
    setenv("PAGER", "cat", 1);
756✔
2800
    setenv("LNAV_HOME_DIR", lnav::paths::dotlnav().c_str(), 1);
756✔
2801
    setenv("LNAV_WORK_DIR", lnav::paths::workdir().c_str(), 1);
756✔
2802

2803
    try {
2804
        auto& safe_options_hier
2805
            = injector::get<lnav::safe_file_options_hier&>();
756✔
2806

2807
        auto opt_path = lnav::paths::dotlnav() / "file-options.json";
756✔
2808
        auto read_res = lnav::filesystem::read_file(opt_path);
756✔
2809
        auto curr_tz = date::get_tzdb().current_zone();
756✔
2810
        auto options_coll = lnav::file_options_collection{};
756✔
2811

2812
        if (read_res.isOk()) {
756✔
2813
            intern_string_t opt_path_src = intern_string::lookup(opt_path);
13✔
2814
            auto parse_res = lnav::file_options_collection::from_json(
2815
                opt_path_src, read_res.unwrap());
13✔
2816
            if (parse_res.isErr()) {
13✔
2817
                for (const auto& um : parse_res.unwrapErr()) {
×
2818
                    lnav::console::print(stderr, um);
×
2819
                }
2820
                return EXIT_FAILURE;
×
2821
            }
2822

2823
            options_coll = parse_res.unwrap();
13✔
2824
        }
13✔
2825

2826
        safe::WriteAccess<lnav::safe_file_options_hier> options_hier(
2827
            safe_options_hier);
756✔
2828

2829
        options_hier->foh_generation += 1;
756✔
2830
        auto_mem<char> var_path;
756✔
2831

2832
        var_path = realpath("/var/log", nullptr);
756✔
2833
        options_coll.foc_pattern_to_options[fmt::format(FMT_STRING("{}/*"),
1,512✔
2834
                                                        var_path.in())]
1,512✔
2835
            = lnav::file_options{
756✔
2836
                {
2837
                    intern_string_t{},
756✔
2838
                    source_location{},
756✔
2839
                    curr_tz,
2840
                },
2841
            };
2842
        options_hier->foh_path_to_collection.emplace(std::filesystem::path("/"),
756✔
2843
                                                     options_coll);
2844
    } catch (const std::runtime_error& e) {
756✔
2845
        log_error("failed to setup tz: %s", e.what());
×
2846
    }
×
2847

2848
    lnav_data.ld_exec_context.ec_sql_callback = sql_callback;
756✔
2849
    lnav_data.ld_exec_context.ec_pipe_callback = pipe_callback;
756✔
2850

2851
    lnav_data.ld_program_name = argv[0];
756✔
2852
    add_ansi_vars(ec.ec_global_vars);
756✔
2853

2854
    lnav_data.ld_db_key_names = DEFAULT_DB_KEY_NAMES;
756✔
2855

2856
    auto dot_lnav_path = lnav::paths::dotlnav();
756✔
2857
    std::error_code last_write_ec;
756✔
2858
    lnav_data.ld_last_dot_lnav_time
2859
        = std::filesystem::last_write_time(dot_lnav_path, last_write_ec);
756✔
2860

2861
    ensure_dotlnav();
756✔
2862

2863
    log_install_handlers();
756✔
2864
    sql_install_logger();
756✔
2865

2866
    log_info("opening main sqlite3 (%s) DB", sqlite3_version);
756✔
2867
    if (sqlite3_open_v2(
756✔
2868
            "file:user_db?mode=memory&cache=shared",
2869
            lnav_data.ld_db.out(),
2870
            SQLITE_OPEN_URI | SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
2871
            nullptr)
2872
        != SQLITE_OK)
756✔
2873
    {
2874
        fprintf(stderr, "error: unable to create sqlite memory database\n");
×
2875
        exit(EXIT_FAILURE);
×
2876
    }
2877

2878
    {
2879
        auto stmt = prepare_stmt(lnav_data.ld_db, LNAV_ATTACH_DB).unwrap();
756✔
2880
        auto exec_res = stmt.execute();
756✔
2881
        if (exec_res.isErr()) {
756✔
2882
            fprintf(stderr,
×
2883
                    "failed to attach memory database: %s\n",
2884
                    exec_res.unwrapErr().c_str());
×
2885
            exit(EXIT_FAILURE);
×
2886
        }
2887
    }
756✔
2888

2889
    {
2890
        int register_collation_functions(sqlite3 * db);
2891

2892
        register_sqlite_funcs(lnav_data.ld_db.in(), sqlite_registration_funcs);
756✔
2893
        register_collation_functions(lnav_data.ld_db.in());
756✔
2894
    }
2895

2896
    register_environ_vtab(lnav_data.ld_db.in());
756✔
2897
    register_static_file_vtab(lnav_data.ld_db.in());
756✔
2898
    {
2899
        static auto vtab_modules
2900
            = injector::get<std::vector<std::shared_ptr<vtab_module_base>>>();
756✔
2901

2902
        for (const auto& mod : vtab_modules) {
7,560✔
2903
            mod->create(lnav_data.ld_db.in());
6,804✔
2904
        }
2905
    }
2906

2907
    register_views_vtab(lnav_data.ld_db.in());
756✔
2908
    register_regexp_vtab(lnav_data.ld_db.in());
756✔
2909
    register_xpath_vtab(lnav_data.ld_db.in());
756✔
2910
    register_fstat_vtab(lnav_data.ld_db.in());
756✔
2911
    lnav::events::register_events_tab(lnav_data.ld_db.in());
756✔
2912
    register_log_stmt_vtab(lnav_data.ld_db.in());
756✔
2913

2914
#ifdef HAVE_RUST_DEPS
2915
    {
2916
        lnav_rs_ext::init_ext();
756✔
2917
    }
2918
#endif
2919

2920
    auto log_fos = std::make_unique<field_overlay_source>(
2921
        lnav_data.ld_log_source, lnav_data.ld_text_source);
756✔
2922

2923
    auto _vtab_cleanup = finally([] {
756✔
2924
        static const char* VIRT_TABLES = R"(
2925
SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%'
2926
)";
2927

2928
        auto op_guard = lnav_opid_guard::once("cleanup");
756✔
2929

2930
        log_info("performing cleanup");
756✔
2931

2932
#ifdef HAVE_RUST_DEPS
2933
        lnav_rs_ext::stop_ext_access();
756✔
2934
#endif
2935

2936
        {
2937
            auto& dls = lnav_data.ld_db_row_source;
756✔
2938
            size_t memory_usage = 0, total_size = 0, cached_chunks = 0;
756✔
2939
            for (auto cc = dls.dls_cell_container.cc_first.get(); cc != nullptr;
1,513✔
2940
                 cc = cc->cc_next.get())
757✔
2941
            {
2942
                total_size += cc->cc_capacity;
757✔
2943
                if (cc->cc_data) {
757✔
2944
                    cached_chunks += 1;
756✔
2945
                    memory_usage += cc->cc_capacity;
756✔
2946
                } else {
2947
                    memory_usage += cc->cc_compressed_size;
1✔
2948
                }
2949
            }
2950
            log_debug(
756✔
2951
                "cell memory footprint: total=%zu; actual=%zu; "
2952
                "cached-chunks=%zu",
2953
                total_size,
2954
                memory_usage,
2955
                cached_chunks);
2956
        }
2957

2958
        if (lnav_data.ld_spectro_source != nullptr) {
756✔
2959
            delete std::exchange(lnav_data.ld_spectro_source->ss_value_source,
632✔
2960
                                 nullptr);
1,264✔
2961
        }
2962

2963
        lnav_data.ld_child_pollers.clear();
756✔
2964

2965
        delete lnav_data.ld_views[LNV_SCHEMA].get_sub_source();
756✔
2966
        delete lnav_data.ld_views[LNV_PRETTY].get_sub_source();
756✔
2967
        for (auto& tc : lnav_data.ld_views) {
7,560✔
2968
            tc.deinit();
6,804✔
2969
        }
2970

2971
        log_info("marking files as closed");
756✔
2972
        for (auto& lf : lnav_data.ld_active_files.fc_files) {
1,365✔
2973
            lf->close();
609✔
2974
        }
2975
        log_info("rebuilding after closures");
756✔
2976
        rebuild_indexes(ui_clock::now());
756✔
2977
        log_info("clearing file collection");
756✔
2978
        lnav_data.ld_active_files.clear();
756✔
2979

2980
        log_info("dropping tables");
756✔
2981
        lnav_data.ld_vtab_manager = nullptr;
756✔
2982

2983
        std::vector<std::string> tables_to_drop;
756✔
2984
        {
2985
            auto prep_res = prepare_stmt(lnav_data.ld_db.in(), VIRT_TABLES);
756✔
2986
            if (prep_res.isErr()) {
756✔
2987
                log_error("unable to prepare VIRT_TABLES: %s",
×
2988
                          prep_res.unwrapErr().c_str());
2989
            } else {
2990
                auto stmt = prep_res.unwrap();
756✔
2991

2992
                stmt.for_each_row<std::string>(
756✔
2993
                    [&tables_to_drop](auto table_name) {
×
2994
                        tables_to_drop.emplace_back(fmt::format(
×
2995
                            FMT_STRING("DROP TABLE {}"), table_name));
×
2996
                        return false;
×
2997
                    });
2998
            }
756✔
2999
        }
756✔
3000

3001
        // XXX
3002
        lnav_data.ld_log_source.set_preview_sql_filter(nullptr);
756✔
3003
        lnav_data.ld_log_source.set_sql_filter("", nullptr);
1,512✔
3004
        lnav_data.ld_log_source.set_sql_marker("", nullptr);
756✔
3005
        lnav_config_listener::unload_all();
756✔
3006

3007
        {
3008
            sqlite3_stmt* stmt_iter = nullptr;
756✔
3009

3010
            do {
3011
                stmt_iter = sqlite3_next_stmt(lnav_data.ld_db.in(), stmt_iter);
756✔
3012
                if (stmt_iter != nullptr) {
756✔
3013
                    const auto* stmt_sql = sqlite3_sql(stmt_iter);
×
3014

3015
                    log_warning("unfinalized SQL statement: %s", stmt_sql);
×
3016
                    ensure(false);
×
3017
                }
3018
            } while (stmt_iter != nullptr);
756✔
3019
        }
3020

3021
        for (auto& drop_stmt : tables_to_drop) {
756✔
3022
            auto prep_res
3023
                = prepare_stmt(lnav_data.ld_db.in(), drop_stmt.c_str());
×
3024
            if (prep_res.isErr()) {
×
3025
                log_error("unable to prepare DROP statement: %s",
×
3026
                          prep_res.unwrapErr().c_str());
3027
                continue;
×
3028
            }
3029

3030
            auto stmt = prep_res.unwrap();
×
3031
            stmt.execute();
×
3032
        }
3033
#if defined(HAVE_SQLITE3_DROP_MODULES)
3034
        sqlite3_drop_modules(lnav_data.ld_db.in(), nullptr);
756✔
3035
#endif
3036

3037
        lnav_data.ld_db.reset();
756✔
3038

3039
        log_info("waiting for cleanup tasks");
756✔
3040
        while (!CLEANUP_TASKS.empty()) {
3,816✔
3041
            auto& [name, task] = CLEANUP_TASKS.back();
3,060✔
3042
            if (task.wait_for(10ms) != std::future_status::ready) {
3,060✔
3043
                log_warning("cleanup task '%s' is not yet ready", name);
×
3044
            }
3045
            CLEANUP_TASKS.pop_back();
3,060✔
3046
        }
3047

3048
        log_info("cleanup finished");
756✔
3049
    });
1,512✔
3050

3051
#ifdef HAVE_LIBCURL
3052
    curl_global_init(CURL_GLOBAL_DEFAULT);
756✔
3053
#endif
3054

3055
    static const std::string DEFAULT_DEBUG_LOG = "/dev/null";
2,268✔
3056

3057
    // lnav_data.ld_debug_log_name = DEFAULT_DEBUG_LOG;
3058

3059
    std::vector<std::string> file_args;
756✔
3060
    std::vector<lnav::console::user_message> arg_errors;
756✔
3061

3062
    CLI::App app{"The Logfile Navigator"};
3,024✔
3063

3064
    app.add_option("-d",
3065
                   lnav_data.ld_debug_log_name,
3066
                   "Write debug messages to the given file.")
3067
        ->type_name("FILE");
4,536✔
3068
    app.add_option("-I", lnav_data.ld_config_paths, "include paths")
3069
        ->check(CLI::ExistingDirectory)
3070
        ->check([&arg_errors](std::string inc_path) -> std::string {
×
3071
            if (access(inc_path.c_str(), X_OK) != 0) {
53✔
3072
                arg_errors.emplace_back(
×
3073
                    lnav::console::user_message::error(
×
3074
                        attr_line_t("invalid configuration directory: ")
×
3075
                            .append(lnav::roles::file(inc_path)))
×
3076
                        .with_errno_reason());
3077
                return "unreadable";
×
3078
            }
3079

3080
            return std::string();
53✔
3081
        })
3082
        ->allow_extra_args(false);
7,560✔
3083
    app.add_flag("-q{0},-v{2}", verbosity, "Control the verbosity");
3,024✔
3084
    app.set_version_flag("-V,--version");
3,780✔
3085
    app.footer(fmt::format(FMT_STRING("Version: {}"), VCS_PACKAGE_STRING));
3,024✔
3086

3087
    std::shared_ptr<lnav::management::operations> mmode_ops;
756✔
3088

3089
    if (argc < 2 || strcmp(argv[1], "-m") != 0) {
756✔
3090
        app.add_flag("-H", lnav_data.ld_show_help_view, "show help");
2,984✔
3091
        app.add_flag("-C", mode_flags.mf_check_configs, "check");
2,984✔
3092
        auto* install_flag
3093
            = app.add_flag("-i", mode_flags.mf_install, "install");
2,984✔
3094
        app.add_flag("-u", mode_flags.mf_update_formats, "update");
2,984✔
3095
        auto* no_default_flag
3096
            = app.add_flag("-N", mode_flags.mf_no_default, "no def");
2,984✔
3097
        auto* rotated_flag = app.add_flag(
2,984✔
3098
            "-R", lnav_data.ld_active_files.fc_rotated, "rotated");
3099
        auto* recurse_flag = app.add_flag(
2,984✔
3100
            "-r", lnav_data.ld_active_files.fc_recursive, "recurse");
3101
        auto* as_log_flag
3102
            = app.add_flag("-t", lnav_data.ld_treat_stdin_as_log, "as-log");
2,984✔
3103
        app.add_flag("-W", mode_flags.mf_print_warnings);
2,984✔
3104
        auto* headless_flag = app.add_flag(
2,984✔
3105
            "-n",
3106
            [](size_t count) { lnav_data.ld_flags |= LNF_HEADLESS; },
746✔
3107
            "headless");
3108
        auto* file_opt = app.add_option("file", file_args, "files");
2,984✔
3109

3110
        app.add_option("-S,--since", since_time, "since");
2,984✔
3111
        app.add_option("-U,--until", until_time, "until");
2,984✔
3112

3113
        auto wait_cb = [](size_t count) {
×
3114
            fprintf(stderr, "PID %d waiting for attachment\n", getpid());
×
3115
            char b;
3116
            if (isatty(STDIN_FILENO) && read(STDIN_FILENO, &b, 1) == -1) {
×
3117
                perror("Read key from STDIN");
×
3118
            }
3119
        };
3120
        app.add_flag("--stop", wait_cb);
2,238✔
3121

3122
        auto cmd_appender
3123
            = [](std::string cmd) { lnav_data.ld_commands.emplace_back(cmd); };
992✔
3124
        auto cmd_validator = [&arg_errors](std::string cmd) -> std::string {
993✔
3125
            static const auto ARG_SRC
3126
                = intern_string::lookup("command-line argument");
2,043✔
3127

3128
            if (cmd.empty()) {
993✔
3129
                return "empty commands are not allowed";
×
3130
            }
3131

3132
            switch (cmd[0]) {
993✔
3133
                case ':':
992✔
3134
                case '/':
3135
                case ';':
3136
                case '|':
3137
                    break;
992✔
3138
                default:
1✔
3139
                    cmd.push_back(' ');
1✔
3140
                    arg_errors.emplace_back(
1✔
3141
                        lnav::console::user_message::error(
×
3142
                            attr_line_t("invalid value for ")
2✔
3143
                                .append_quoted("-c"_symbol)
1✔
3144
                                .append(" option"))
1✔
3145
                            .with_snippet(lnav::console::snippet::from(
1✔
3146
                                ARG_SRC,
3147
                                attr_line_t()
2✔
3148
                                    .append(" -c "_quoted_code)
1✔
3149
                                    .append(lnav::roles::quoted_code(cmd))
2✔
3150
                                    .append("\n")
1✔
3151
                                    .append(4, ' ')
1✔
3152
                                    .append(lnav::roles::error(
2✔
3153
                                        "^ command type prefix "
3154
                                        "is missing"))))
3155
                            .with_help(command_arg_help()));
2✔
3156
                    return "invalid prefix";
2✔
3157
            }
3158
            return std::string();
992✔
3159
        };
746✔
3160
        auto* cmd_opt = app.add_option("-c")
3161
                            ->check(cmd_validator)
3162
                            ->each(cmd_appender)
3163
                            ->allow_extra_args(false)
3164
                            ->trigger_on_parse(true);
4,476✔
3165

3166
        auto file_appender = [](std::string file_path) {
7✔
3167
            lnav_data.ld_commands.emplace_back(
7✔
3168
                fmt::format(FMT_STRING("|{}"), file_path));
28✔
3169
        };
7✔
3170
        auto* exec_file_opt = app.add_option("-f")
3171
                                  ->trigger_on_parse(true)
3172
                                  ->allow_extra_args(false)
3173
                                  ->each(file_appender);
746✔
3174

3175
        auto shexec_appender = [&mode_flags](std::string cmd) {
4✔
3176
            mode_flags.mf_no_default = true;
4✔
3177
            lnav_data.ld_commands.emplace_back(
4✔
3178
                fmt::format(FMT_STRING(":sh {}"), cmd));
16✔
3179
        };
750✔
3180
        auto* cmdline_opt = app.add_option("-e")
3181
                                ->each(shexec_appender)
3182
                                ->allow_extra_args(false)
3183
                                ->trigger_on_parse(true);
746✔
3184

3185
        install_flag->needs(file_opt);
746✔
3186
        install_flag->excludes(no_default_flag,
746✔
3187
                               as_log_flag,
3188
                               rotated_flag,
3189
                               recurse_flag,
3190
                               headless_flag,
3191
                               cmd_opt,
3192
                               exec_file_opt,
3193
                               cmdline_opt);
3194
    }
3195

3196
    auto is_mmode = argc >= 2 && strcmp(argv[1], "-m") == 0;
756✔
3197
    try {
3198
#if defined(__MSYS__)
3199
        lnav::console::get_command_line_args(&argc, &argv);
3200
#endif
3201
        if (is_mmode) {
756✔
3202
            mmode_ops = lnav::management::describe_cli(app, argc, argv);
10✔
3203
        } else {
3204
            app.parse(argc, argv);
746✔
3205
        }
3206
    } catch (const CLI::CallForHelp& e) {
118✔
3207
        if (is_mmode) {
1✔
3208
            fmt::print(FMT_STRING("{}\n"), app.help());
×
3209
        } else {
3210
            usage();
1✔
3211
        }
3212
        return EXIT_SUCCESS;
1✔
3213
    } catch (const CLI::CallForVersion& e) {
117✔
3214
        fmt::print(FMT_STRING("{}\n"), VCS_PACKAGE_STRING);
348✔
3215
        return EXIT_SUCCESS;
116✔
3216
    } catch (const CLI::ParseError& e) {
117✔
3217
        if (!arg_errors.empty()) {
1✔
3218
            print_user_msgs(arg_errors, mode_flags);
1✔
3219
            return e.get_exit_code();
1✔
3220
        }
3221

3222
        lnav::console::print(
×
3223
            stderr,
3224
            lnav::console::user_message::error("invalid command-line arguments")
×
3225
                .with_reason(e.what()));
×
3226
        return e.get_exit_code();
×
3227
    }
1✔
3228

3229
    lnav_data.ld_config_paths.insert(lnav_data.ld_config_paths.begin(),
638✔
3230
                                     lnav::paths::dotlnav());
1,276✔
3231
    lnav_data.ld_config_paths.insert(lnav_data.ld_config_paths.begin(),
638✔
3232
                                     SYSCONFDIR "/lnav");
3233
    lnav_data.ld_config_paths.insert(lnav_data.ld_config_paths.begin(),
638✔
3234
                                     "/etc/lnav");
3235

3236
    if (!lnav_data.ld_debug_log_name.empty()
638✔
3237
        && lnav_data.ld_debug_log_name != DEFAULT_DEBUG_LOG)
638✔
3238
    {
3239
        lnav_log_level = lnav_log_level_t::TRACE;
46✔
3240
    }
3241

3242
    if (!lnav_data.ld_debug_log_name.empty()) {
638✔
3243
        lnav_log_file = make_optional_from_nullable(
46✔
3244
            fopen(lnav_data.ld_debug_log_name.c_str(), "ae"));
46✔
3245
        lnav_log_file | [](auto* file) {
46✔
3246
            fcntl(fileno(file), F_SETFD, FD_CLOEXEC);
46✔
3247
            log_write_ring_to(fileno(file));
46✔
3248
        };
46✔
3249
    }
3250
    log_info("lnav started %d", lnav_log_file.has_value());
638✔
3251

3252
    {
3253
        static auto builtin_formats
3254
            = injector::get<std::vector<std::shared_ptr<log_format>>>();
638✔
3255
        auto& root_formats = log_format::get_root_formats();
638✔
3256

3257
        log_format::get_root_formats().insert(root_formats.begin(),
638✔
3258
                                              builtin_formats.begin(),
3259
                                              builtin_formats.end());
3260
        builtin_formats.clear();
638✔
3261
    }
3262

3263
    load_config(lnav_data.ld_config_paths, config_errors);
638✔
3264

3265
    if (!config_errors.empty()) {
638✔
3266
        if (print_user_msgs(config_errors, mode_flags) != EXIT_SUCCESS) {
1✔
3267
            return EXIT_FAILURE;
1✔
3268
        }
3269
    }
3270
    add_global_vars(ec);
637✔
3271

3272
    if (mode_flags.mf_update_formats) {
637✔
3273
        if (!update_installs_from_git()) {
×
3274
            return EXIT_FAILURE;
×
3275
        }
3276
        return EXIT_SUCCESS;
×
3277
    }
3278

3279
    if (mode_flags.mf_install) {
637✔
3280
        auto formats_installed_path
3281
            = lnav::paths::dotlnav() / "formats/installed";
5✔
3282
        auto configs_installed_path
3283
            = lnav::paths::dotlnav() / "configs/installed";
5✔
3284

3285
        if (argc == 0) {
5✔
3286
            const auto install_reason
3287
                = attr_line_t("the ")
×
3288
                      .append("-i"_symbol)
×
3289
                      .append(
×
3290
                          " option expects one or more log format "
3291
                          "definition "
3292
                          "files to install in your lnav "
3293
                          "configuration "
3294
                          "directory")
3295
                      .move();
×
3296
            const auto install_help
3297
                = attr_line_t(
×
3298
                      "log format definitions are JSON files that "
3299
                      "tell lnav "
3300
                      "how to understand log files\n")
3301
                      .append(
×
3302
                          "See: "
3303
                          "https://docs.lnav.org/en/latest/"
3304
                          "formats.html")
3305
                      .move();
×
3306

3307
            lnav::console::print(stderr,
×
3308
                                 lnav::console::user_message::error(
×
3309
                                     "missing format files to install")
3310
                                     .with_reason(install_reason)
×
3311
                                     .with_help(install_help));
×
3312
            return EXIT_FAILURE;
×
3313
        }
3314

3315
        for (const auto& file_path : file_args) {
8✔
3316
            if (endswith(file_path, ".git")) {
5✔
3317
                if (!install_from_git(file_path)) {
×
3318
                    return EXIT_FAILURE;
2✔
3319
                }
3320
                continue;
1✔
3321
            }
3322

3323
            if (endswith(file_path, ".lnav")) {
5✔
3324
                auto script_path = std::filesystem::path(file_path);
×
3325
                auto read_res = lnav::filesystem::read_file(script_path);
×
3326
                if (read_res.isErr()) {
×
3327
                    lnav::console::print(
×
3328
                        stderr,
3329
                        lnav::console::user_message::error(
×
3330
                            attr_line_t("unable to read script file: ")
×
3331
                                .append(lnav::roles::file(file_path)))
×
3332
                            .with_reason(read_res.unwrapErr()));
×
3333
                    return EXIT_FAILURE;
×
3334
                }
3335

3336
                auto dst_path = formats_installed_path / script_path.filename();
×
3337
                auto write_res
3338
                    = lnav::filesystem::write_file(dst_path, read_res.unwrap());
×
3339
                if (write_res.isErr()) {
×
3340
                    lnav::console::print(
×
3341
                        stderr,
3342
                        lnav::console::user_message::error(
×
3343
                            attr_line_t("unable to write script file: ")
×
3344
                                .append(lnav::roles::file(file_path)))
×
3345
                            .with_reason(write_res.unwrapErr()));
×
3346
                    return EXIT_FAILURE;
×
3347
                }
3348

3349
                lnav::console::print(
×
3350
                    stderr,
3351
                    lnav::console::user_message::ok(
×
3352
                        attr_line_t("installed -- ")
×
3353
                            .append(lnav::roles::file(dst_path))));
×
3354
                continue;
×
3355
            }
3356

3357
            if (endswith(file_path, ".sql")) {
5✔
3358
                auto sql_path = std::filesystem::path(file_path);
×
3359
                auto read_res = lnav::filesystem::read_file(sql_path);
×
3360
                if (read_res.isErr()) {
×
3361
                    lnav::console::print(
×
3362
                        stderr,
3363
                        lnav::console::user_message::error(
×
3364
                            attr_line_t("unable to read SQL file: ")
×
3365
                                .append(lnav::roles::file(file_path)))
×
3366
                            .with_reason(read_res.unwrapErr()));
×
3367
                    return EXIT_FAILURE;
×
3368
                }
3369

3370
                auto dst_path = formats_installed_path / sql_path.filename();
×
3371
                auto write_res
3372
                    = lnav::filesystem::write_file(dst_path, read_res.unwrap());
×
3373
                if (write_res.isErr()) {
×
3374
                    lnav::console::print(
×
3375
                        stderr,
3376
                        lnav::console::user_message::error(
×
3377
                            attr_line_t("unable to write SQL file: ")
×
3378
                                .append(lnav::roles::file(file_path)))
×
3379
                            .with_reason(write_res.unwrapErr()));
×
3380
                    return EXIT_FAILURE;
×
3381
                }
3382

3383
                lnav::console::print(
×
3384
                    stderr,
3385
                    lnav::console::user_message::ok(
×
3386
                        attr_line_t("installed -- ")
×
3387
                            .append(lnav::roles::file(dst_path))));
×
3388
                continue;
×
3389
            }
3390

3391
            if (file_path == "extra") {
5✔
3392
                install_extra_formats();
1✔
3393
                continue;
1✔
3394
            }
3395

3396
            auto file_type_result = detect_config_file_type(file_path);
4✔
3397
            if (file_type_result.isErr()) {
4✔
3398
                lnav::console::print(
1✔
3399
                    stderr,
3400
                    lnav::console::user_message::error(
×
3401
                        attr_line_t("unable to open configuration file: ")
1✔
3402
                            .append(lnav::roles::file(file_path)))
2✔
3403
                        .with_reason(file_type_result.unwrapErr()));
1✔
3404
                return EXIT_FAILURE;
1✔
3405
            }
3406
            auto file_type = file_type_result.unwrap();
3✔
3407

3408
            auto src_path = std::filesystem::path(file_path);
3✔
3409
            std::filesystem::path dst_name;
3✔
3410
            if (file_type == config_file_type::CONFIG) {
3✔
3411
                dst_name = src_path.filename();
×
3412
            } else {
3413
                auto format_list = load_format_file(src_path, loader_errors);
3✔
3414

3415
                if (!loader_errors.empty()) {
3✔
3416
                    if (print_user_msgs(loader_errors, mode_flags)
×
3417
                        != EXIT_SUCCESS)
×
3418
                    {
3419
                        return EXIT_FAILURE;
×
3420
                    }
3421
                }
3422
                if (format_list.empty()) {
3✔
3423
                    lnav::console::print(
×
3424
                        stderr,
3425
                        lnav::console::user_message::error(
×
3426
                            attr_line_t("invalid format file: ")
×
3427
                                .append(lnav::roles::file(src_path.string())))
×
3428
                            .with_reason("there must be at least one format "
×
3429
                                         "definition in the file"));
3430
                    return EXIT_FAILURE;
×
3431
                }
3432

3433
                dst_name = format_list[0].to_string() + ".json";
3✔
3434
            }
3✔
3435
            auto dst_path = (file_type == config_file_type::CONFIG
3436
                                 ? configs_installed_path
3437
                                 : formats_installed_path)
3438
                / dst_name;
3✔
3439

3440
            auto read_res = lnav::filesystem::read_file(file_path);
3✔
3441
            if (read_res.isErr()) {
3✔
3442
                auto um = lnav::console::user_message::error(
×
3443
                              attr_line_t("cannot read file to install -- ")
×
3444
                                  .append(lnav::roles::file(file_path)))
×
3445
                              .with_reason(read_res.unwrap())
×
3446
                              .move();
×
3447

3448
                lnav::console::print(stderr, um);
×
3449
                return EXIT_FAILURE;
×
3450
            }
3451

3452
            auto file_content = read_res.unwrap();
3✔
3453

3454
            auto read_dst_res = lnav::filesystem::read_file(dst_path);
3✔
3455
            if (read_dst_res.isOk()) {
3✔
3456
                auto dst_content = read_dst_res.unwrap();
2✔
3457

3458
                if (dst_content == file_content) {
2✔
3459
                    auto um = lnav::console::user_message::info(
3460
                        attr_line_t("file is already installed at -- ")
1✔
3461
                            .append(lnav::roles::file(dst_path)));
1✔
3462

3463
                    lnav::console::print(stdout, um);
1✔
3464

3465
                    return EXIT_SUCCESS;
1✔
3466
                }
1✔
3467
            }
2✔
3468

3469
            auto write_res = lnav::filesystem::write_file(
3470
                dst_path,
3471
                file_content,
3472
                {lnav::filesystem::write_file_options::backup_existing});
2✔
3473
            if (write_res.isErr()) {
2✔
3474
                auto um = lnav::console::user_message::error(
×
3475
                              attr_line_t("failed to install file to -- ")
×
3476
                                  .append(lnav::roles::file(dst_path)))
×
3477
                              .with_reason(write_res.unwrapErr())
×
3478
                              .move();
×
3479

3480
                lnav::console::print(stderr, um);
×
3481
                return EXIT_FAILURE;
×
3482
            }
3483

3484
            auto write_file_res = write_res.unwrap();
2✔
3485
            auto um = lnav::console::user_message::ok(
3486
                attr_line_t("installed -- ")
2✔
3487
                    .append(lnav::roles::file(dst_path)));
2✔
3488
            if (write_file_res.wfr_backup_path) {
2✔
3489
                um.with_note(
1✔
3490
                    attr_line_t("the previously installed ")
2✔
3491
                        .append_quoted(
1✔
3492
                            lnav::roles::file(dst_path.filename().string()))
2✔
3493
                        .append(" was backed up to -- ")
1✔
3494
                        .append(lnav::roles::file(
1✔
3495
                            write_file_res.wfr_backup_path.value().string())));
2✔
3496
            }
3497

3498
            lnav::console::print(stdout, um);
2✔
3499
        }
10✔
3500
        return EXIT_SUCCESS;
3✔
3501
    }
5✔
3502

3503
    if (lnav_data.ld_flags & LNF_SECURE_MODE) {
632✔
3504
        if (sqlite3_set_authorizer(
5✔
3505
                lnav_data.ld_db.in(), sqlite_authorizer, nullptr)
3506
            != SQLITE_OK)
5✔
3507
        {
3508
            fprintf(stderr, "error: unable to attach sqlite authorizer\n");
×
3509
            exit(EXIT_FAILURE);
×
3510
        }
3511
    }
3512

3513
    /* If we statically linked against an ncurses library that had a
3514
     * non-standard path to the terminfo database, we need to set this
3515
     * variable so that it will try the default path.
3516
     */
3517
    setenv("TERMINFO_DIRS",
632✔
3518
           "/usr/share/terminfo:/lib/terminfo:/usr/share/lib/terminfo",
3519
           0);
3520

3521
    lnav_data.ld_vtab_manager = std::make_unique<log_vtab_manager>(
1,264✔
3522
        lnav_data.ld_db, lnav_data.ld_views[LNV_LOG], lnav_data.ld_log_source);
632✔
3523

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

3604
    lnav_data.ld_doc_view.set_sub_source(&lnav_data.ld_doc_source);
632✔
3605
    lnav_data.ld_example_view.set_sub_source(&lnav_data.ld_example_source);
632✔
3606
    lnav_data.ld_preview_view[0].set_sub_source(
632✔
3607
        &lnav_data.ld_preview_source[0]);
3608
    lnav_data.ld_files_view.set_sub_source(&lnav_data.ld_files_source)
632✔
3609
        .add_input_delegate(lnav_data.ld_files_source);
632✔
3610
    lnav_data.ld_file_details_view.set_sub_source(
632✔
3611
        &lnav_data.ld_file_details_source);
3612
    lnav_data.ld_files_source.fss_details_source
3613
        = &lnav_data.ld_file_details_source;
632✔
3614
    lnav_data.ld_progress_view.set_title("progress");
632✔
3615
    lnav_data.ld_progress_view.set_default_role(role_t::VCR_INACTIVE_STATUS);
632✔
3616
    lnav_data.ld_progress_view.set_sub_source(&lnav_data.ld_progress_source);
632✔
3617
    lnav_data.ld_progress_view.set_show_scrollbar(true);
632✔
3618
    lnav_data.ld_progress_view.set_height(2_vl);
632✔
3619
    lnav_data.ld_user_message_view.set_sub_source(
632✔
3620
        &lnav_data.ld_user_message_source);
3621

3622
#if 0
3623
    auto overlay_menu = std::make_shared<text_overlay_menu>();
3624
    lnav_data.ld_file_details_view.set_overlay_source(overlay_menu.get());
3625
#endif
3626

3627
    for (int lpc = 0; lpc < LNV__MAX; lpc++) {
6,320✔
3628
        lnav_data.ld_views[lpc].set_gutter_source(new log_gutter_source());
5,688✔
3629
    }
3630

3631
    {
3632
        auto& hs = lnav_data.ld_hist_source2;
632✔
3633

3634
        lnav_data.ld_log_source.set_index_delegate(new hist_index_delegate(
632✔
3635
            lnav_data.ld_hist_source2, lnav_data.ld_views[LNV_HISTOGRAM]));
632✔
3636
        hs.init();
632✔
3637
        lnav_data.ld_zoom_level = 3;
632✔
3638
        hs.set_time_slice(ZOOM_LEVELS[lnav_data.ld_zoom_level]);
632✔
3639
    }
3640

3641
    for (int lpc = 0; lpc < LNV__MAX; lpc++) {
6,320✔
3642
        lnav_data.ld_views[lpc].set_title(lnav_view_titles[lpc]);
17,064✔
3643
    }
3644

3645
    load_formats(lnav_data.ld_config_paths, loader_errors);
632✔
3646

3647
    {
3648
        auto_mem<char, sqlite3_free> errmsg;
632✔
3649
        auto init_sql_str = init_sql.to_string_fragment_producer()->to_string();
632✔
3650

3651
        if (sqlite3_exec(lnav_data.ld_db.in(),
1,264✔
3652
                         init_sql_str.data(),
632✔
3653
                         nullptr,
3654
                         nullptr,
3655
                         errmsg.out())
3656
            != SQLITE_OK)
632✔
3657
        {
3658
            fprintf(stderr,
×
3659
                    "error: unable to execute DB init -- %s\n",
3660
                    errmsg.in());
3661
        }
3662
    }
632✔
3663

3664
    {
3665
        auto op_guard = lnav_opid_guard::once("register_vtab");
632✔
3666

3667
        lnav_data.ld_vtab_manager->register_vtab(
1,264✔
3668
            std::make_shared<all_logs_vtab>());
1,264✔
3669
        lnav_data.ld_vtab_manager->register_vtab(
1,264✔
3670
            std::make_shared<log_format_vtab_impl>(
1,264✔
3671
                log_format::find_root_format("generic_log")));
1,264✔
3672
        lnav_data.ld_vtab_manager->register_vtab(
1,264✔
3673
            std::make_shared<log_format_vtab_impl>(
1,264✔
3674
                log_format::find_root_format("lnav_piper_log")));
1,264✔
3675

3676
        for (auto& iter : log_format::get_root_formats()) {
46,064✔
3677
            auto lvi = iter->get_vtab_impl();
45,432✔
3678

3679
            if (lvi != nullptr) {
45,432✔
3680
                lnav_data.ld_vtab_manager->register_vtab(lvi);
42,904✔
3681
            }
3682
        }
45,432✔
3683

3684
        load_format_extra(lnav_data.ld_db.in(),
632✔
3685
                          ec.ec_global_vars,
632✔
3686
                          lnav_data.ld_config_paths,
3687
                          loader_errors);
3688
        load_format_vtabs(lnav_data.ld_vtab_manager.get(), loader_errors);
632✔
3689

3690
        if (!loader_errors.empty()) {
632✔
3691
            if (print_user_msgs(loader_errors, mode_flags) != EXIT_SUCCESS) {
2✔
3692
                if (mmode_ops == nullptr) {
2✔
3693
                    return EXIT_FAILURE;
2✔
3694
                }
3695
            }
3696
        }
3697
    }
632✔
3698

3699
    if (mmode_ops) {
630✔
3700
        isc::supervisor root_superv(injector::get<isc::service_list>());
10✔
3701
        auto perform_res = lnav::management::perform(mmode_ops);
10✔
3702

3703
        return print_user_msgs(perform_res, mode_flags);
10✔
3704
    }
10✔
3705

3706
    if (!mode_flags.mf_check_configs && !lnav_data.ld_show_help_view) {
620✔
3707
        DEFAULT_FILES.emplace_back("var/log/messages");
619✔
3708
        DEFAULT_FILES.emplace_back("var/log/system.log");
619✔
3709
        DEFAULT_FILES.emplace_back("var/log/syslog");
619✔
3710
        DEFAULT_FILES.emplace_back("var/log/syslog.log");
619✔
3711
    }
3712

3713
    init_lnav_commands(lnav_commands);
620✔
3714
    init_lnav_bookmark_commands(lnav_commands);
620✔
3715
    init_lnav_breakpoint_commands(lnav_commands);
620✔
3716
    init_lnav_display_commands(lnav_commands);
620✔
3717
    init_lnav_filtering_commands(lnav_commands);
620✔
3718
    init_lnav_io_commands(lnav_commands);
620✔
3719
    init_lnav_metadata_commands(lnav_commands);
620✔
3720
    init_lnav_scripting_commands(lnav_commands);
620✔
3721

3722
    lnav_data.ld_looping = true;
620✔
3723
    set_view_mode(ln_mode_t::PAGING);
620✔
3724

3725
    {
3726
        if (!since_time.empty()) {
620✔
3727
            auto from_res = humanize::time::point::from(since_time);
5✔
3728
            if (from_res.isErr()) {
5✔
3729
                auto um = from_res.unwrapErr();
2✔
3730
                um.um_message = attr_line_t("invalid 'since' time ")
2✔
3731
                                    .append_quoted(since_time);
2✔
3732
                lnav::console::print(stderr, um);
2✔
3733
                return EXIT_FAILURE;
2✔
3734
            }
2✔
3735
            lnav_data.ld_default_time_range.tr_begin
3736
                = to_us(from_res.unwrap().get_point());
3✔
3737
        }
5✔
3738
        if (!until_time.empty()) {
618✔
3739
            auto from_res = humanize::time::point::from(until_time);
1✔
3740
            if (from_res.isErr()) {
1✔
3741
                auto um = from_res.unwrapErr();
×
3742
                um.um_message = attr_line_t("invalid 'until' time ")
×
3743
                                    .append_quoted(until_time);
×
3744
                lnav::console::print(stderr, um);
×
3745
                return EXIT_FAILURE;
×
3746
            }
3747
            lnav_data.ld_default_time_range.tr_end
3748
                = to_us(from_res.unwrap().get_point());
1✔
3749
        }
1✔
3750

3751
        if (lnav_data.ld_default_time_range.tr_end
618✔
3752
            < lnav_data.ld_default_time_range.tr_begin)
618✔
3753
        {
3754
            auto um
3755
                = lnav::console::user_message::error(
×
3756
                      attr_line_t("The low time cutoff ")
1✔
3757
                          .append_quoted(lnav::roles::symbol(since_time))
2✔
3758
                          .append(" is not less than the high time cutoff ")
1✔
3759
                          .append_quoted(lnav::roles::symbol(until_time)))
2✔
3760
                      .with_note(attr_line_t("The resolved low time is ")
2✔
3761
                                     .append_quoted(lnav::roles::symbol(
1✔
3762
                                         lnav::to_rfc3339_string(
2✔
3763
                                             lnav_data.ld_default_time_range
3764
                                                 .tr_begin))))
3765
                      .with_note(
2✔
3766
                          attr_line_t("The resolved high time is ")
2✔
3767
                              .append_quoted(
1✔
3768
                                  lnav::roles::symbol(lnav::to_rfc3339_string(
2✔
3769
                                      lnav_data.ld_default_time_range.tr_end))))
3770
                      .with_help(
2✔
3771
                          "Ensure that the low time cutoff is less than "
3772
                          "the high time cutoff.");
1✔
3773
            lnav::console::print(stderr, um);
1✔
3774
            return EXIT_FAILURE;
1✔
3775
        }
1✔
3776
    }
3777

3778
    if ((isatty(STDIN_FILENO) || is_dev_null(STDIN_FILENO)) && file_args.empty()
1,234✔
3779
        && lnav_data.ld_active_files.fc_file_names.empty()
1✔
3780
        && !mode_flags.mf_no_default)
1,234✔
3781
    {
3782
        char start_dir[FILENAME_MAX];
3783

3784
        if (getcwd(start_dir, sizeof(start_dir)) == nullptr) {
×
3785
            perror("getcwd");
×
3786
        } else {
3787
            do {
3788
                if (!append_default_files()) {
×
3789
                    retval = EXIT_FAILURE;
×
3790
                }
3791
            } while (lnav_data.ld_active_files.fc_file_names.empty()
×
3792
                     && change_to_parent_dir());
×
3793

3794
            if (chdir(start_dir) == -1) {
×
3795
                perror("chdir(start_dir)");
×
3796
            }
3797
        }
3798
    }
3799

3800
    {
3801
        const auto internals_dir_opt = getenv_opt("DUMP_INTERNALS_DIR");
617✔
3802

3803
        if (internals_dir_opt) {
617✔
3804
            lnav::dump_internals(internals_dir_opt.value());
2✔
3805

3806
            return EXIT_SUCCESS;
2✔
3807
        }
3808
    }
3809

3810
    if (file_args.empty() && !mode_flags.mf_no_default) {
615✔
3811
        load_stdin = true;
35✔
3812
    }
3813

3814
    for (const auto& file_path_str : file_args) {
1,162✔
3815
        auto [file_path_without_trailer, file_loc]
547✔
3816
            = lnav::filesystem::split_file_location(file_path_str);
547✔
3817
        auto_mem<char> abspath;
547✔
3818
        struct stat st;
3819

3820
        auto file_path = std::filesystem::path(
3821
            stat(file_path_without_trailer.c_str(), &st) == 0
547✔
3822
                ? file_path_without_trailer
3823
                : file_path_str);
547✔
3824

3825
        auto file_path_type
3826
            = lnav::filesystem::determine_path_type(file_path.string());
547✔
3827

3828
        if (file_path_str == "-") {
547✔
3829
            load_stdin = true;
×
3830
        }
3831
#ifdef HAVE_LIBCURL
3832
        else if (file_path_type == lnav::filesystem::path_type::url)
547✔
3833
        {
3834
            auto ul = std::make_shared<url_loader>(file_path_str);
×
3835

3836
            lnav_data.ld_active_files.fc_file_names[ul->get_path()]
×
3837
                .with_filename(file_path)
×
3838
                .with_time_range(lnav_data.ld_default_time_range);
×
3839
            isc::to<curl_looper&, services::curl_streamer_t>().send(
×
3840
                [ul](auto& clooper) { clooper.add_request(ul); });
×
3841
        } else if (file_path_str.find("://") != std::string::npos) {
547✔
3842
            lnav_data.ld_commands.insert(
3✔
3843
                lnav_data.ld_commands.begin(),
×
3844
                fmt::format(FMT_STRING(":open {}"), file_path_str));
15✔
3845
        }
3846
#endif
3847
        else if (file_path_type == lnav::filesystem::path_type::pattern)
544✔
3848
        {
3849
            lnav_data.ld_active_files.fc_file_names[file_path]
24✔
3850
                .with_follow(!(lnav_data.ld_flags & LNF_HEADLESS))
12✔
3851
                .with_time_range(lnav_data.ld_default_time_range);
12✔
3852
        } else if (lnav::filesystem::statp(file_path, &st) == -1) {
532✔
3853
            if (file_path_type == lnav::filesystem::path_type::remote) {
1✔
3854
                lnav_data.ld_active_files.fc_file_names[file_path]
×
3855
                    .with_follow(!(lnav_data.ld_flags & LNF_HEADLESS))
×
3856
                    .with_time_range(lnav_data.ld_default_time_range);
×
3857
            } else {
3858
                lnav::console::print(
1✔
3859
                    stderr,
3860
                    lnav::console::user_message::error(
1✔
3861
                        attr_line_t("unable to open file: ")
1✔
3862
                            .append(lnav::roles::file(file_path)))
2✔
3863
                        .with_errno_reason());
1✔
3864
                retval = EXIT_FAILURE;
1✔
3865
            }
3866
        } else if (access(file_path.c_str(), R_OK) == -1) {
531✔
3867
            lnav::console::print(
1✔
3868
                stderr,
3869
                lnav::console::user_message::error(
1✔
3870
                    attr_line_t("file exists, but is not readable: ")
1✔
3871
                        .append(lnav::roles::file(file_path)))
2✔
3872
                    .with_errno_reason());
1✔
3873
            retval = EXIT_FAILURE;
1✔
3874
        } else if (S_ISFIFO(st.st_mode)) {
530✔
3875
            auto_fd fifo_fd;
×
3876

3877
            if ((fifo_fd = lnav::filesystem::openp(file_path, O_RDONLY)) == -1)
×
3878
            {
3879
                lnav::console::print(
×
3880
                    stderr,
3881
                    lnav::console::user_message::error(
×
3882
                        attr_line_t("cannot open fifo: ")
×
3883
                            .append(lnav::roles::file(file_path)))
×
3884
                        .with_errno_reason());
×
3885
                retval = EXIT_FAILURE;
×
3886
            } else {
3887
                auto desc = fmt::format(FMT_STRING("FIFO [{}]"),
×
3888
                                        lnav_data.ld_fifo_counter++);
×
3889
                auto create_piper_res = lnav::piper::create_looper(
3890
                    desc, std::move(fifo_fd), auto_fd{});
×
3891

3892
                if (create_piper_res.isOk()) {
×
3893
                    lnav_data.ld_active_files.fc_file_names[desc]
×
3894
                        .with_piper(create_piper_res.unwrap())
×
3895
                        .with_time_range(lnav_data.ld_default_time_range);
×
3896
                }
3897
            }
3898
        } else if ((abspath = realpath(file_path.c_str(), nullptr)) == nullptr)
530✔
3899
        {
3900
            perror("Cannot find file");
×
3901
            retval = EXIT_FAILURE;
×
3902
        } else if (S_ISDIR(st.st_mode)) {
530✔
3903
            std::string dir_wild(abspath.in());
2✔
3904

3905
            if (dir_wild[dir_wild.size() - 1] == '/') {
2✔
3906
                dir_wild.resize(dir_wild.size() - 1);
×
3907
            }
3908
            auto loo = logfile_open_options().with_time_range(
4✔
3909
                lnav_data.ld_default_time_range);
2✔
3910
            lnav_data.ld_active_files.fc_file_names.insert2(dir_wild + "/*",
2✔
3911
                                                            loo);
3912
        } else {
2✔
3913
            lnav_data.ld_active_files.fc_file_names.insert2(
528✔
3914
                abspath.in(),
528✔
3915
                logfile_open_options()
528✔
3916
                    .with_init_location(file_loc)
1,056✔
3917
                    .with_follow(!(lnav_data.ld_flags & LNF_HEADLESS))
528✔
3918
                    .with_time_range(lnav_data.ld_default_time_range));
528✔
3919
            if (file_loc.valid()) {
528✔
3920
                lnav_data.ld_files_to_front.emplace_back(abspath.in());
528✔
3921
            }
3922
        }
3923
    }
547✔
3924

3925
    if (mode_flags.mf_check_configs) {
615✔
3926
        isc::supervisor root_superv(injector::get<isc::service_list>());
1✔
3927
        rescan_files(true);
1✔
3928
        for (auto& lf : lnav_data.ld_active_files.fc_files) {
2✔
3929
            logfile::rebuild_result_t rebuild_result;
3930

3931
            do {
3932
                rebuild_result = lf->rebuild_index();
3✔
3933
            } while (rebuild_result == logfile::rebuild_result_t::NEW_LINES
3934
                     || rebuild_result == logfile::rebuild_result_t::NEW_ORDER);
3✔
3935
            auto fmt = lf->get_format();
1✔
3936
            if (fmt == nullptr) {
1✔
3937
                fprintf(stderr,
×
3938
                        "error:%s:no format found for file\n",
3939
                        lf->get_filename().c_str());
×
3940
                retval = EXIT_FAILURE;
×
3941
                continue;
×
3942
            }
3943
            for (auto line_iter = lf->begin(); line_iter != lf->end();
4✔
3944
                 ++line_iter)
3✔
3945
            {
3946
                if (line_iter->get_msg_level() != log_level_t::LEVEL_INVALID) {
3✔
3947
                    continue;
2✔
3948
                }
3949

3950
                size_t partial_len;
3951

3952
                auto read_result = lf->read_line(line_iter);
1✔
3953
                if (read_result.isErr()) {
1✔
3954
                    continue;
×
3955
                }
3956
                auto sbr = read_result.unwrap();
1✔
3957
                auto lffs = lf->get_format_file_state();
1✔
3958
                if (fmt->scan_for_partial(lffs, sbr, partial_len)) {
1✔
3959
                    long line_number = std::distance(lf->begin(), line_iter);
1✔
3960
                    auto full_line = to_string(sbr);
1✔
3961
                    std::string partial_line(sbr.get_data(), partial_len);
1✔
3962

3963
                    fprintf(stderr,
2✔
3964
                            "error:%s:%ld:line did not match format "
3965
                            "%s\n",
3966
                            lf->get_filename().c_str(),
1✔
3967
                            line_number,
3968
                            fmt->get_pattern_path(lffs.lffs_pattern_locks,
2✔
3969
                                                  line_number)
3970
                                .c_str());
3971
                    fprintf(stderr,
2✔
3972
                            "error:%s:%ld:         line -- %s\n",
3973
                            lf->get_filename().c_str(),
1✔
3974
                            line_number,
3975
                            full_line.c_str());
3976
                    if (partial_len > 0) {
1✔
3977
                        fprintf(stderr,
2✔
3978
                                "error:%s:%ld:partial match -- %s\n",
3979
                                lf->get_filename().c_str(),
1✔
3980
                                line_number,
3981
                                partial_line.c_str());
3982
                    } else {
3983
                        fprintf(stderr,
×
3984
                                "error:%s:%ld:no partial match found\n",
3985
                                lf->get_filename().c_str(),
×
3986
                                line_number);
3987
                    }
3988
                    retval = EXIT_FAILURE;
1✔
3989
                }
1✔
3990
            }
1✔
3991
        }
1✔
3992
        return retval;
1✔
3993
    }
1✔
3994

3995
    if (lnav_data.ld_flags & LNF_HEADLESS || mode_flags.mf_check_configs) {
614✔
3996
    } else if (!isatty(STDOUT_FILENO)) {
3✔
3997
        lnav::console::print(
1✔
3998
            stderr,
3999
            lnav::console::user_message::error(
2✔
4000
                "unable to display interactive text UI")
4001
                .with_reason("stdout is not a TTY")
2✔
4002
                .with_help(attr_line_t("pass the ")
2✔
4003
                               .append("-n"_symbol)
1✔
4004
                               .append(" option to run lnav in headless mode "
1✔
4005
                                       "or don't redirect stdout")));
4006
        retval = EXIT_FAILURE;
1✔
4007
    }
4008

4009
    std::optional<std::string> stdin_url;
614✔
4010
    std::filesystem::path stdin_dir;
614✔
4011
    if (load_stdin && !isatty(STDIN_FILENO) && !is_dev_null(STDIN_FILENO)
35✔
4012
        && !exec_stdin)
649✔
4013
    {
4014
        static const std::string STDIN_NAME = "stdin";
105✔
4015
        struct stat stdin_st;
4016

4017
        if (fstat(STDIN_FILENO, &stdin_st) == -1) {
35✔
4018
            lnav::console::print(
×
4019
                stderr,
4020
                lnav::console::user_message::error("unable to stat() stdin")
×
4021
                    .with_errno_reason());
×
4022
            retval = EXIT_FAILURE;
×
4023
        } else if (S_ISFIFO(stdin_st.st_mode)) {
35✔
4024
            static auto op = lnav_operation{"load_stdin"};
25✔
4025
            auto op_guard = lnav_opid_guard::internal(op);
25✔
4026
            pollfd pfd[1];
4027

4028
            log_info("waiting for stdin FIFO");
25✔
4029
            pfd[0].fd = STDIN_FILENO;
25✔
4030
            pfd[0].events = POLLIN;
25✔
4031
            pfd[0].revents = 0;
25✔
4032
            auto prc = poll(pfd, 1, 0);
25✔
4033

4034
            if (prc == 0 || (pfd[0].revents & POLLIN)) {
25✔
4035
                auto stdin_piper_res = lnav::piper::create_looper(
4036
                    STDIN_NAME, auto_fd::dup_of(STDIN_FILENO), auto_fd{});
25✔
4037
                if (stdin_piper_res.isOk()) {
25✔
4038
                    auto stdin_piper = stdin_piper_res.unwrap();
25✔
4039
                    stdin_url = stdin_piper.get_url();
25✔
4040
                    stdin_dir = stdin_piper.get_out_dir();
25✔
4041
                    auto& loo = lnav_data.ld_active_files
4042
                                    .fc_file_names[stdin_piper.get_name()];
25✔
4043
                    loo.with_piper(stdin_piper)
50✔
4044
                        .with_include_in_session(
25✔
4045
                            lnav_data.ld_treat_stdin_as_log)
25✔
4046
                        .with_time_range(lnav_data.ld_default_time_range);
25✔
4047
                    if (lnav_data.ld_treat_stdin_as_log) {
25✔
4048
                        loo.with_text_format(text_format_t::TF_LOG);
1✔
4049
                    }
4050
                }
25✔
4051
            } else if (prc < 0) {
25✔
4052
                log_error("unable to poll() stdin: %s",
×
4053
                          lnav::from_errno().message().c_str());
4054
            }
4055
            log_info("  done waiting for stdin to start");
25✔
4056
        } else if (S_ISREG(stdin_st.st_mode)) {
35✔
4057
            // The shell connected a file directly, just open it up
4058
            // and add it in here.
4059
            auto loo = logfile_open_options{}
20✔
4060
                           .with_filename(STDIN_NAME)
10✔
4061
                           .with_include_in_session(false);
10✔
4062

4063
            auto open_res
4064
                = logfile::open(STDIN_NAME, loo, auto_fd::dup_of(STDIN_FILENO));
10✔
4065

4066
            if (open_res.isErr()) {
10✔
4067
                lnav::console::print(
×
4068
                    stderr,
4069
                    lnav::console::user_message::error("unable to open stdin")
×
4070
                        .with_reason(open_res.unwrapErr()));
×
4071
                retval = EXIT_FAILURE;
×
4072
            } else {
4073
                file_collection fc;
10✔
4074

4075
                fc.fc_files.emplace_back(open_res.unwrap());
10✔
4076
                update_active_files(fc);
10✔
4077
            }
10✔
4078
        }
10✔
4079
    }
4080

4081
    if (!isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)
1,226✔
4082
        && !(lnav_data.ld_flags & LNF_HEADLESS))
1,226✔
4083
    {
4084
        if (dup2(STDOUT_FILENO, STDIN_FILENO) == -1) {
×
4085
            perror("cannot dup stdout to stdin");
×
4086
        }
4087
    }
4088

4089
    if (retval == EXIT_SUCCESS && lnav_data.ld_active_files.fc_files.empty()
612✔
4090
        && lnav_data.ld_active_files.fc_file_names.empty()
602✔
4091
        && lnav_data.ld_commands.empty()
62✔
4092
        && !(lnav_data.ld_show_help_view || mode_flags.mf_no_default))
1,226✔
4093
    {
4094
        lnav::console::print(
×
4095
            stderr,
4096
            lnav::console::user_message::error("nothing to do")
×
4097
                .with_reason("no files given or default files found")
×
4098
                .with_help(attr_line_t("use the ")
×
4099
                               .append_quoted(lnav::roles::keyword("-N"))
×
4100
                               .append(" option to open lnav without "
×
4101
                                       "loading any files")));
4102
        retval = EXIT_FAILURE;
×
4103
    }
4104

4105
    if (retval == EXIT_SUCCESS) {
614✔
4106
        isc::supervisor root_superv(injector::get<isc::service_list>());
612✔
4107

4108
        try {
4109
            char pcre2_version[128];
4110

4111
            pcre2_config(PCRE2_CONFIG_VERSION, pcre2_version);
612✔
4112
            log_info("startup: %s", VCS_PACKAGE_STRING);
612✔
4113
            log_host_info();
612✔
4114
            log_info("Libraries:");
612✔
4115
#ifdef HAVE_BZLIB_H
4116
            log_info("  bzip=%s", BZ2_bzlibVersion());
612✔
4117
#endif
4118
#ifdef HAVE_LIBCURL
4119
            log_info("  curl=%s (%s)", LIBCURL_VERSION, LIBCURL_TIMESTAMP);
612✔
4120
#endif
4121
#ifdef HAVE_ARCHIVE_H
4122
            log_info("  libarchive=%d", ARCHIVE_VERSION_NUMBER);
4123
            log_info("    details=%s", archive_version_details());
4124
#endif
4125
            log_info("  notcurses=%s", notcurses_version());
612✔
4126
            log_info("  pcre2=%s", pcre2_version);
612✔
4127
            log_info("  sqlite=%s", sqlite3_version);
612✔
4128
            log_info("  zlib=%s", zlibVersion());
612✔
4129
            log_info("lnav_data:");
612✔
4130
            log_info("  flags=%lx", lnav_data.ld_flags);
612✔
4131
            log_info("  commands:");
612✔
4132
            for (auto cmd_iter = lnav_data.ld_commands.begin();
612✔
4133
                 cmd_iter != lnav_data.ld_commands.end();
1,618✔
4134
                 ++cmd_iter)
1,006✔
4135
            {
4136
                log_info("    %s", cmd_iter->c_str());
1,006✔
4137
            }
4138
            log_info("  files:");
612✔
4139
            for (auto file_iter
612✔
4140
                 = lnav_data.ld_active_files.fc_file_names.begin();
612✔
4141
                 file_iter != lnav_data.ld_active_files.fc_file_names.end();
1,178✔
4142
                 ++file_iter)
566✔
4143
            {
4144
                log_info("    %s", file_iter->first.c_str());
566✔
4145
            }
4146

4147
            if (!(lnav_data.ld_flags & LNF_HEADLESS)
1,224✔
4148
                && verbosity == verbosity_t::quiet && load_stdin
2✔
4149
                && lnav_data.ld_active_files.fc_file_names.size() == 1)
614✔
4150
            {
4151
                log_info("pager mode, waiting for input to be consumed");
×
4152
                // give the pipers a chance to run to create the files to be
4153
                // scanned.
4154
                wait_for_pipers(ui_clock::now() + 10ms);
×
4155
                rescan_files(true);
×
4156
                // wait for the piper to actually finish running
4157
                wait_for_pipers(ui_clock::now() + 100ms);
×
4158
                auto rebuild_res = rebuild_indexes(ui_clock::now() + 15ms);
×
4159
                if (rebuild_res.rir_completed
×
4160
                    && lnav_data.ld_child_pollers.empty()
×
4161
                    && lnav_data.ld_active_files.active_pipers() == 0)
×
4162
                {
4163
                    log_info("  input fully consumed");
×
4164
                    rebuild_indexes_repeatedly();
×
4165
                    if (lnav_data.ld_active_files.fc_files.empty()
×
4166
                        || lnav_data.ld_active_files.fc_files[0]->size() < 24)
×
4167
                    {
4168
                        log_info("  input is smaller than screen, not paging");
×
4169
                        lnav_data.ld_flags |= LNF_HEADLESS;
×
4170
                        verbosity = verbosity_t::standard;
×
4171
                        lnav_data.ld_views[LNV_LOG].set_top(0_vl);
×
4172
                    }
4173
                    lnav_data.ld_views[LNV_TEXT].set_top(0_vl);
×
4174
                } else {
4175
                    log_info("  input not fully consumed");
×
4176
                }
4177
            }
4178

4179
            if (lnav_data.ld_flags & LNF_HEADLESS) {
612✔
4180
                std::vector<
4181
                    std::pair<Result<std::string, lnav::console::user_message>,
4182
                              std::string>>
4183
                    cmd_results;
610✔
4184
                textview_curses *log_tc, *text_tc, *tc;
4185
                bool output_view = true;
610✔
4186
                auto msg_cb_guard = lnav_data.ld_exec_context.add_msg_callback(
4187
                    [](const auto& um) {
×
4188
                        switch (um.um_level) {
114✔
4189
                            case lnav::console::user_message::level::error:
×
4190
                            case lnav::console::user_message::level::warning:
4191
                                lnav::console::println(stderr,
×
4192
                                                       um.to_attr_line());
4193
                                break;
×
4194
                            default:
114✔
4195
                                break;
114✔
4196
                        }
4197
                    });
724✔
4198

4199
                log_fos->fos_contexts.top().c_show_applicable_annotations
610✔
4200
                    = false;
610✔
4201

4202
                view_colors::init(nullptr);
610✔
4203
                rescan_files(true);
610✔
4204
                wait_for_pipers();
610✔
4205
                rescan_files(true);
610✔
4206
                rebuild_indexes_repeatedly();
610✔
4207
                {
4208
                    safe::WriteAccess<safe_name_to_stubs> errs(
4209
                        *lnav_data.ld_active_files.fc_name_to_stubs);
610✔
4210
                    if (!errs->empty()) {
610✔
4211
                        for (const auto& pair : *errs) {
×
4212
                            lnav::console::print(
×
4213
                                stderr,
4214
                                lnav::console::user_message::error(
×
4215
                                    attr_line_t("unable to open file: ")
×
4216
                                        .append(lnav::roles::file(pair.first)))
×
4217
                                    .with_reason(pair.second.fsi_description));
×
4218
                        }
4219

4220
                        return EXIT_FAILURE;
×
4221
                    }
4222
                }
610✔
4223
                init_session();
610✔
4224
                lnav_data.ld_exec_context.set_output("stdout", stdout, nullptr);
1,220✔
4225
                alerter::singleton().enabled(false);
610✔
4226

4227
                log_tc = &lnav_data.ld_views[LNV_LOG];
610✔
4228
                log_tc->set_height(24_vl);
610✔
4229
                lnav_data.ld_view_stack.push_back(log_tc);
610✔
4230
                // Read all of stdin
4231
                wait_for_pipers();
610✔
4232
                rebuild_indexes_repeatedly();
610✔
4233
                wait_for_children();
610✔
4234

4235
                log_tc->set_top(0_vl);
610✔
4236
                text_tc = &lnav_data.ld_views[LNV_TEXT];
610✔
4237
                if (text_tc->get_inner_height() > 0_vl
610✔
4238
                    && (!text_tc->get_selection().has_value()
1,297✔
4239
                        || lnav_data.ld_text_source.current_file()
764✔
4240
                               ->get_open_options()
77✔
4241
                               .loo_init_location
4242
                               .is<default_for_text_format>()))
77✔
4243
                {
4244
                    text_tc->set_selection(0_vl);
74✔
4245
                }
4246
                if (text_tc->get_selection().has_value()) {
610✔
4247
                    text_tc->set_height(
610✔
4248
                        vis_line_t(text_tc->get_inner_height()
×
4249
                                   - text_tc->get_selection().value()));
1,220✔
4250
                }
4251
                setup_highlights(lnav_data.ld_views[LNV_TEXT].get_highlights());
610✔
4252
                setup_initial_view_stack();
610✔
4253
                for (auto& tview : lnav_data.ld_views) {
6,100✔
4254
                    if (!tview.get_selection().has_value()) {
5,490✔
4255
                        tview.set_selection(0_vl);
1,220✔
4256
                    }
4257
                }
4258
                lnav_data.ld_text_source.tss_apply_default_init_location = true;
610✔
4259

4260
                log_info("Executing initial commands");
610✔
4261
                execute_init_commands(lnav_data.ld_exec_context, cmd_results);
610✔
4262
                run_cleanup_tasks();
610✔
4263
                wait_for_pipers();
610✔
4264
                rescan_files(true);
610✔
4265
                isc::to<curl_looper&, services::curl_streamer_t>()
×
4266
                    .send_and_wait(
610✔
4267
                        [](auto& clooper) { clooper.process_all(); });
610✔
4268
                rebuild_indexes_repeatedly();
610✔
4269
                wait_for_children();
610✔
4270
                {
4271
                    safe::WriteAccess<safe_name_to_stubs> errs(
4272
                        *lnav_data.ld_active_files.fc_name_to_stubs);
610✔
4273
                    if (!errs->empty()) {
610✔
4274
                        for (const auto& pair : *errs) {
×
4275
                            lnav::console::print(
×
4276
                                stderr,
4277
                                lnav::console::user_message::error(
×
4278
                                    attr_line_t("unable to open file: ")
×
4279
                                        .append(lnav::roles::file(pair.first)))
×
4280
                                    .with_reason(pair.second.fsi_description));
×
4281
                        }
4282

4283
                        return EXIT_FAILURE;
×
4284
                    }
4285
                }
610✔
4286

4287
                for (const auto& lf : lnav_data.ld_active_files.fc_files) {
1,217✔
4288
                    auto lf_notes = lf->get_notes();
607✔
4289
                    auto utf_note_opt
4290
                        = lf_notes.value_for(logfile::note_type::not_utf);
607✔
4291
                    if (utf_note_opt.has_value()) {
607✔
4292
                        lnav::console::print(stderr, *utf_note_opt.value());
×
4293
                    }
4294
                }
607✔
4295

4296
                for (auto& pair : cmd_results) {
1,609✔
4297
                    if (pair.first.isErr()) {
999✔
4298
                        lnav::console::print(stderr, pair.first.unwrapErr());
100✔
4299
                        output_view = false;
100✔
4300
                    } else {
4301
                        auto msg = pair.first.unwrap();
899✔
4302

4303
                        if (startswith(msg, "info:")) {
899✔
4304
                            if (verbosity == verbosity_t::verbose) {
292✔
4305
                                printf("%s\n", msg.c_str());
10✔
4306
                            }
4307
                        } else if (!msg.empty()) {
607✔
4308
                            printf("%s\n", msg.c_str());
10✔
4309
                            output_view = false;
10✔
4310
                        }
4311
                    }
899✔
4312
                }
4313

4314
                if (getenv("LNAV_EXTERNAL_URL")) {
610✔
4315
                    auto wakeup_pair = auto_pipe();
×
4316
                    wakeup_pair.open();
×
4317
                    wakeup_pair.read_end().non_blocking();
×
4318

4319
                    static auto& mlooper
4320
                        = injector::get<main_looper&, services::main_t>();
×
4321
                    mlooper.s_wakeup_fd = wakeup_pair.write_end().get();
×
4322
                    while (lnav_data.ld_looping) {
×
4323
                        mlooper.get_port().process_for(50ms);
×
4324
                        rescan_files();
×
4325
                        auto deadline = ui_clock::now() + 1s;
×
4326
                        wait_for_pipers(deadline);
×
4327
                        rebuild_indexes(deadline);
×
4328
                    }
4329
                }
4330

4331
                {
4332
                    auto& bg_service
4333
                        = injector::get<bg_looper&, services::background_t>();
610✔
4334

4335
                    root_superv.stop_child(bg_service.shared_from_this());
610✔
4336
                }
4337

4338
                {
4339
                    auto& pt = lnav::progress_tracker::get_tasks();
610✔
4340

4341
                    for (auto& bt : **pt.readAccess()) {
1,830✔
4342
                        auto tp = bt();
1,220✔
4343
                        if (tp.tp_messages.empty()) {
1,220✔
4344
                            continue;
1,218✔
4345
                        }
4346

4347
                        for (const auto& msg : tp.tp_messages) {
4✔
4348
                            lnav::console::print(stderr, msg);
2✔
4349
                            output_view = false;
2✔
4350
                        }
4351
                    }
1,220✔
4352
                }
4353

4354
                if (output_view && verbosity != verbosity_t::quiet
499✔
4355
                    && !lnav_data.ld_view_stack.empty()
493✔
4356
                    && !lnav_data.ld_stdout_used)
1,109✔
4357
                {
4358
                    bool suppress_empty_lines = false;
369✔
4359
                    unsigned long view_index;
4360
                    vis_line_t y;
369✔
4361

4362
                    tc = *lnav_data.ld_view_stack.top();
369✔
4363
                    // turn off scrollbar since some stuff will resize to
4364
                    // account for it.
4365
                    tc->set_show_scrollbar(false);
369✔
4366
                    view_index = tc - lnav_data.ld_views;
369✔
4367
                    switch (view_index) {
369✔
4368
                        case LNV_DB:
106✔
4369
                        case LNV_HISTOGRAM:
4370
                            suppress_empty_lines = true;
106✔
4371
                            break;
106✔
4372
                        default:
263✔
4373
                            break;
263✔
4374
                    }
4375

4376
                    auto* los = tc->get_overlay_source();
369✔
4377
                    attr_line_t ov_al;
738✔
4378
                    while (los != nullptr && tc->get_inner_height() > 0_vl
828✔
4379
                           && los->list_static_overlay(
940✔
4380
                               *tc,
4381
                               list_overlay_source::media_t::file,
4382
                               y,
4383
                               tc->get_inner_height(),
915✔
4384
                               ov_al))
4385
                    {
4386
                        write_line_to(stdout, ov_al);
112✔
4387
                        ov_al.clear();
112✔
4388
                        ++y;
112✔
4389
                    }
4390

4391
                    vis_line_t vl;
369✔
4392
                    for (vl = tc->get_top(); vl < tc->get_inner_height(); ++vl)
14,780✔
4393
                    {
4394
                        std::vector<attr_line_t> rows(1);
14,412✔
4395
                        tc->listview_value_for_rows(*tc, vl, rows);
14,412✔
4396
                        if (suppress_empty_lines && rows[0].empty()) {
14,412✔
4397
                            continue;
×
4398
                        }
4399

4400
                        write_line_to(stdout, rows[0]);
14,412✔
4401

4402
                        std::vector<attr_line_t> row_overlay_content;
14,411✔
4403
                        if (los != nullptr) {
14,411✔
4404
                            los->list_value_for_overlay(
8,176✔
4405
                                *tc, vl, row_overlay_content);
4406
                            for (const auto& ov_row : row_overlay_content) {
8,261✔
4407
                                write_line_to(stdout, ov_row);
85✔
4408
                            }
4409
                        }
4410
                    }
14,412✔
4411
                }
4412
            } else {
611✔
4413
                init_session();
2✔
4414

4415
                guard_termios gt(STDIN_FILENO);
2✔
4416
                lnav_log_orig_termios = gt.get_termios();
2✔
4417

4418
                looper();
2✔
4419

4420
                dup2(STDOUT_FILENO, STDERR_FILENO);
2✔
4421

4422
                signal(SIGINT, SIG_DFL);
2✔
4423
            }
2✔
4424

4425
            log_info("exiting main loop");
611✔
4426
        } catch (const std::system_error& e) {
1✔
4427
            if (e.code().value() != EPIPE) {
1✔
4428
                fprintf(stderr, "error: %s\n", e.what());
×
4429
            }
4430
        } catch (const line_buffer::error& e) {
1✔
4431
            auto um = lnav::console::user_message::error("internal error")
×
4432
                          .with_reason(strerror(e.e_err));
×
4433
            lnav::console::print(stderr, um);
×
4434
        } catch (const std::exception& e) {
×
4435
            auto um = lnav::console::user_message::error("internal error")
×
4436
                          .with_reason(e.what());
×
4437
            lnav::console::print(stderr, um);
×
4438
        }
×
4439

4440
        // When reading from stdin, tell the user where the capture
4441
        // file is stored so they can look at it later.
4442
        if (stdin_url && !(lnav_data.ld_flags & LNF_HEADLESS)
637✔
4443
            && verbosity != verbosity_t::quiet)
637✔
4444
        {
4445
            file_size_t stdin_size = 0;
×
4446
            for (const auto& ent :
×
4447
                 std::filesystem::directory_iterator(stdin_dir))
×
4448
            {
4449
                stdin_size += ent.file_size();
×
4450
            }
4451

4452
            lnav::console::print(
×
4453
                stderr,
4454
                lnav::console::user_message::info(
×
4455
                    attr_line_t()
×
4456
                        .append(lnav::roles::number(humanize::file_size(
×
4457
                            stdin_size, humanize::alignment::none)))
4458
                        .append(" of data from stdin was captured and "
×
4459
                                "will be saved for one day.  You can "
4460
                                "reopen it by running:\n")
4461
                        .appendf(FMT_STRING("   {} "),
×
4462
                                 lnav_data.ld_program_name)
4463
                        .append(lnav::roles::file(stdin_url.value()))));
×
4464
        }
4465
    }
612✔
4466

4467
    return retval;
614✔
4468
}
756✔
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