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

tstack / lnav / 19505555563-2683

19 Nov 2025 02:52PM UTC coverage: 68.872% (-0.005%) from 68.877%
19505555563-2683

push

github

tstack
[textview] fix clicking a wrapped link

0 of 2 new or added lines in 1 file covered. (0.0%)

3 existing lines in 1 file now uncovered.

51073 of 74156 relevant lines covered (68.87%)

431959.48 hits per line

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

71.56
/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)
29✔
694
{
695
    auto* tc = (textview_curses*) lv;
29✔
696
    if (lnav_data.ld_select_start.find(tc) != lnav_data.ld_select_start.end()
29✔
697
        && !tc->is_line_visible(vis_line_t(lnav_data.ld_last_user_mark[tc])))
29✔
698
    {
699
        lnav_data.ld_select_start.erase(tc);
×
700
        lnav_data.ld_last_user_mark.erase(tc);
×
701
    }
702
}
29✔
703

704
static void
705
update_view_position(listview_curses* lv)
29✔
706
{
707
    lnav_data.ld_view_stack.top() | [lv](auto* top_lv) {
29✔
708
        if (lv != top_lv) {
29✔
709
            return;
13✔
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
}
29✔
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 (lnav_data.ld_active_files.fc_files.size() > 1) {
2✔
1935
                    opened_files = true;
×
1936
                    set_view_mode(ln_mode_t::FILES);
×
1937
                }
1938
                log_trace("%d: BEGIN initial rescan rebuild", loop_count);
2✔
1939
                auto rebuild_res = rebuild_indexes(loop_deadline);
2✔
1940
                log_trace("%d: END initial rescan rebuild", loop_count);
2✔
1941
                changes += rebuild_res.rir_changes;
2✔
1942
                load_session();
2✔
1943
                if (session_data.sd_save_time > 0) {
2✔
1944
                    std::string ago;
×
1945

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

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

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

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

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

1999
        mlooper.get_port().process_for(0s);
9✔
2000

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

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

2120
                {
2121
                    hasher h;
5✔
2122

2123
                    lnav_data.ld_views[LNV_LOG].update_hash_state(h);
5✔
2124
                    vs.vs_log = h.to_uuid_string();
5✔
2125
                }
2126
                {
2127
                    auto sel_opt = lnav_data.ld_views[LNV_LOG].get_selection();
5✔
2128
                    if (sel_opt
5✔
2129
                        && sel_opt.value()
5✔
2130
                            < lnav_data.ld_log_source.text_line_count())
5✔
2131
                    {
2132
                        auto win = lnav_data.ld_log_source.window_at(
2133
                            sel_opt.value());
×
2134
                        auto win_iter = win->begin();
×
2135
                        auto hash_res = win_iter->get_line_hash();
×
2136
                        if (hash_res.isOk()) {
×
2137
                            vs.vs_log_selection = hash_res.unwrap().to_string();
×
2138
                        }
2139
                    }
2140
                }
2141

2142
                {
2143
                    hasher h;
5✔
2144

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

2194
        if (prompt.p_editor.is_enabled()) {
9✔
2195
            prompt.p_editor.focus();
×
2196
        } else if (filter_source->fss_editing) {
9✔
2197
            filter_source->fss_editor->focus();
1✔
2198
        }
2199
        if (got_user_input || !updated_views.empty()) {
9✔
2200
            if (true) {
2201
                for (const auto* view_ptr : updated_views) {
56✔
2202
                    log_trace("updated view %s %s",
47✔
2203
                              typeid(*view_ptr).name(),
2204
                              view_ptr->get_title().c_str());
2205
                }
2206
            }
2207
            notcurses_render(sc.get_notcurses());
9✔
2208
            updated_views.clear();
9✔
2209
        }
2210

2211
        if (exec_phase.allow_user_input()) {
9✔
2212
            // Only take input from the user after everything has loaded.
2213
            pollfds.push_back((struct pollfd) {inputready_fd, POLLIN, 0});
8✔
2214
            switch (lnav_data.ld_mode) {
8✔
2215
                case ln_mode_t::COMMAND:
×
2216
                case ln_mode_t::SEARCH:
2217
                case ln_mode_t::SEARCH_FILTERS:
2218
                case ln_mode_t::SEARCH_FILES:
2219
                case ln_mode_t::SEARCH_SPECTRO_DETAILS:
2220
                case ln_mode_t::SQL:
2221
                case ln_mode_t::EXEC:
2222
                case ln_mode_t::USER:
2223
#if 0
2224
                            if (rlc->consume_ready_for_input()) {
2225
                                // log_debug("waiting for readline input")
2226
                            }
2227
#endif
2228
                    view_curses::awaiting_user_input();
×
2229
                    break;
×
2230
                case ln_mode_t::FILTER:
2✔
2231
                    view_curses::awaiting_user_input();
2✔
2232
                    break;
2✔
2233
                default:
6✔
2234
                    // log_debug("waiting for paging input");
2235
                    view_curses::awaiting_user_input();
6✔
2236
                    break;
6✔
2237
            }
2238
        }
2239

2240
        ps->update_poll_set(pollfds);
9✔
2241
        ui_now = ui_clock::now();
9✔
2242
        auto poll_to
2243
            = (exec_phase.interactive() && !changes && ui_now < loop_deadline)
13✔
2244
            ? std::chrono::duration_cast<std::chrono::milliseconds>(
13✔
2245
                  loop_deadline - ui_now)
13✔
2246
            : 0ms;
9✔
2247

2248
        if (false && poll_to.count() > 0) {
2249
            log_trace(
2250
                "%d: poll() with timeout %lld ", loop_count, poll_to.count());
2251
            log_trace("  (changes=%zu; before_deadline=%d; exec_phase=%d)",
2252
                      changes,
2253
                      ui_now < loop_deadline,
2254
                      exec_phase.ep_value);
2255
        }
2256
        rc = poll(pollfds.data(), pollfds.size(), poll_to.count());
9✔
2257

2258
        if (pollfds[0].revents & POLLIN) {
9✔
2259
            char buffer[128];
2260

2261
            while (read(wakeup_pair.read_end(), buffer, sizeof(buffer)) > 0) {
×
2262
                // wakeup received
2263
            }
2264
        }
2265

2266
        gettimeofday(&current_time, nullptr);
9✔
2267
        ui_now = ui_clock::now();
9✔
2268
        lb.tick(current_time);
9✔
2269

2270
        got_user_input = false;
9✔
2271
        if (rc < 0) {
9✔
UNCOV
2272
            switch (errno) {
×
UNCOV
2273
                case 0:
×
2274
                case EINTR:
UNCOV
2275
                    break;
×
2276

2277
                default:
×
2278
                    log_error("select %s", strerror(errno));
×
2279
                    lnav_data.ld_looping = false;
×
2280
                    break;
×
2281
            }
2282
        } else {
2283
            auto in_revents = pollfd_revents(pollfds, inputready_fd);
9✔
2284
            auto old_file_names_size
2285
                = lnav_data.ld_active_files.fc_file_names.size();
9✔
2286
            auto old_files_to_front_size = lnav_data.ld_files_to_front.size();
9✔
2287

2288
            if (in_revents & (POLLHUP | POLLNVAL)) {
9✔
2289
                log_info("stdin has been closed, exiting...");
×
2290
                lnav_data.ld_looping = false;
×
2291
            } else if (in_revents & POLLIN) {
9✔
2292
                static auto op = lnav_operation{"user_input"};
5✔
2293

2294
                auto op_guard = lnav_opid_guard::internal(op);
5✔
2295
                got_user_input = true;
5✔
2296
                ncinput nci;
2297
                auto old_gen = lnav_data.ld_active_files.fc_files_generation;
5✔
2298
                while (notcurses_get_nblock(sc.get_notcurses(), &nci) > 0) {
15✔
2299
                    if (nci.evtype != NCTYPE_RELEASE) {
10✔
2300
                        lnav_data.ld_user_message_source.clear();
10✔
2301
                    }
2302

2303
                    alerter::singleton().new_input(nci);
10✔
2304

2305
                    lnav_data.ld_input_dispatcher.new_input(
10✔
2306
                        current_time, sc.get_notcurses(), nci);
2307

2308
                    lnav_data.ld_view_stack.top() | [&nci](auto tc) {
10✔
2309
                        lnav_data.ld_key_repeat_history.update(nci.id,
8✔
2310
                                                               tc->get_top());
2311
                    };
8✔
2312
                    ncinput_free_paste_content(&nci);
10✔
2313

2314
                    if (!lnav_data.ld_looping) {
10✔
2315
                        // No reason to keep processing input after the
2316
                        // user has quit.  The view stack will also be
2317
                        // empty, which will cause issues.
2318
                        break;
×
2319
                    }
2320
                }
2321

2322
                next_status_update_time = ui_now;
5✔
2323
                switch (lnav_data.ld_mode) {
5✔
2324
                    case ln_mode_t::PAGING:
5✔
2325
                    case ln_mode_t::FILTER:
2326
                    case ln_mode_t::FILES:
2327
                    case ln_mode_t::FILE_DETAILS:
2328
                    case ln_mode_t::SPECTRO_DETAILS:
2329
                    case ln_mode_t::BUSY:
2330
                        if (old_gen
5✔
2331
                            == lnav_data.ld_active_files.fc_files_generation)
5✔
2332
                        {
2333
                            next_rescan_time = next_status_update_time + 1s;
5✔
2334
                        } else {
2335
                            next_rescan_time = next_status_update_time;
×
2336
                        }
2337
                        break;
5✔
2338
                    case ln_mode_t::BREADCRUMBS:
×
2339
                    case ln_mode_t::COMMAND:
2340
                    case ln_mode_t::SEARCH:
2341
                    case ln_mode_t::SEARCH_FILTERS:
2342
                    case ln_mode_t::SEARCH_FILES:
2343
                    case ln_mode_t::SEARCH_SPECTRO_DETAILS:
2344
                    case ln_mode_t::CAPTURE:
2345
                    case ln_mode_t::SQL:
2346
                    case ln_mode_t::EXEC:
2347
                    case ln_mode_t::USER:
2348
                        next_rescan_time = next_status_update_time + 1min;
×
2349
                        break;
×
2350
                }
2351
                next_rebuild_time = next_rescan_time;
5✔
2352
            }
5✔
2353

2354
            auto old_mode = lnav_data.ld_mode;
9✔
2355

2356
            ps->check_poll_set(pollfds);
9✔
2357
            lnav_data.ld_view_stack.top() | [](auto tc) { update_hits(tc); };
16✔
2358

2359
            if (lnav_data.ld_mode != old_mode) {
9✔
2360
                switch (lnav_data.ld_mode) {
×
2361
                    case ln_mode_t::PAGING:
×
2362
                    case ln_mode_t::FILTER:
2363
                    case ln_mode_t::FILES:
2364
                        next_rescan_time = next_status_update_time;
×
2365
                        next_rebuild_time = next_rescan_time;
×
2366
                        break;
×
2367
                    default:
×
2368
                        break;
×
2369
                }
2370
                got_user_input = true;
×
2371
            }
2372
            if (old_file_names_size
9✔
2373
                    != lnav_data.ld_active_files.fc_file_names.size()
9✔
2374
                || old_files_to_front_size != lnav_data.ld_files_to_front.size()
9✔
2375
                || lnav_data.ld_active_files.finished_pipers() > 0)
18✔
2376
            {
2377
                got_user_input = true;
×
2378
                next_rescan_time = ui_now;
×
2379
                next_rebuild_time = next_rescan_time;
×
2380
                next_status_update_time = next_rescan_time;
×
2381
            }
2382
        }
2383

2384
        if (prompt.p_editor.is_enabled()) {
9✔
2385
            prompt.p_editor.tick(ui_now);
×
2386
        }
2387

2388
        if (timer.time_to_update(overlay_counter)) {
9✔
2389
            lnav_data.ld_view_stack.top() |
3✔
2390
                [](auto tc) { tc->set_overlay_needs_update(); };
2✔
2391
        }
2392

2393
        if (exec_phase.building_index()) {
9✔
2394
            log_trace("%d: BEGIN initial build rebuild", loop_count);
4✔
2395
            auto rebuild_res = rebuild_indexes(loop_deadline);
4✔
2396
            log_trace("%d: END initial build rebuild", loop_count);
4✔
2397
            changes += rebuild_res.rir_changes;
4✔
2398
            if (lnav_data.ld_input_dispatcher.id_count > 0
4✔
2399
                || (opened_files && lnav_data.ld_mode != ln_mode_t::FILES)
4✔
2400
                || (changes == 0 && rebuild_res.rir_completed
3✔
2401
                    && !rebuild_res.rir_rescan_needed))
1✔
2402
            {
2403
                log_info("%d: initial build completed", loop_count);
2✔
2404
                exec_phase.completed(lnav::phase_t::build);
2✔
2405
                setup_initial_view_stack();
2✔
2406
            } else if (!opened_files && ui_now - ui_start_time > 100ms) {
2✔
2407
                log_debug("set mode to files");
2✔
2408
                set_view_mode(ln_mode_t::FILES);
2✔
2409
                lnav_data.ld_views[LNV_LOG].set_top_for_last_row();
2✔
2410
                opened_files = true;
2✔
2411
            }
2412
        }
2413

2414
        if (exec_phase.running_commands()) {
9✔
2415
            static bool ran_cleanup = false;
2416
            std::vector<
2417
                std::pair<Result<std::string, lnav::console::user_message>,
2418
                          std::string>>
2419
                cmd_results;
2✔
2420
            std::optional<ui_clock::time_point> deadline;
2✔
2421

2422
            if (lnav_data.ld_input_dispatcher.id_count > 0) {
2✔
2423
                deadline = loop_deadline;
×
2424
            }
2425
            ui_execute_init_commands(ec, cmd_results, deadline);
2✔
2426

2427
            if (!cmd_results.empty()) {
2✔
2428
                auto& prompt = lnav::prompt::get();
1✔
2429
                auto last_cmd_result = cmd_results.back();
1✔
2430

2431
                if (last_cmd_result.first.isOk()) {
1✔
2432
                    prompt.p_editor.set_inactive_value(
1✔
2433
                        last_cmd_result.first.unwrap());
2✔
2434
                } else {
2435
                    ec.ec_msg_callback_stack.back()(
×
2436
                        last_cmd_result.first.unwrapErr());
×
2437
                }
2438
                prompt.p_editor.set_alt_value(last_cmd_result.second);
1✔
2439
            }
1✔
2440

2441
            if (!ran_cleanup) {
2✔
2442
                run_cleanup_tasks();
2✔
2443
                ran_cleanup = true;
2✔
2444
            }
2445

2446
            exec_phase.completed(lnav::phase_t::commands);
2✔
2447
            check_for_enough_colors(sc);
2✔
2448
        }
2✔
2449

2450
        if (exec_phase.loading_session()) {
9✔
2451
            if (lnav_data.ld_mode == ln_mode_t::FILES) {
2✔
2452
                if (lnav_data.ld_active_files.fc_name_to_stubs->readAccess()
2✔
2453
                        ->empty())
1✔
2454
                {
2455
                    log_info("%d: switching to paging!", loop_count);
1✔
2456
                    set_view_mode(ln_mode_t::PAGING);
1✔
2457
                    lnav_data.ld_active_files.fc_files
2458
                        | lnav::itertools::for_each(&logfile::dump_stats);
1✔
2459

2460
                    check_for_file_zones();
1✔
2461
                } else {
2462
                    lnav_data.ld_files_view.set_selection(0_vl);
×
2463
                }
2464
            }
2465
            lnav_data.ld_views[LNV_LOG].set_top_for_last_row();
2✔
2466
            lnav::session::restore_view_states();
2✔
2467
            log_info("%d: going interactive", loop_count);
2✔
2468
            auto log_sel_opt = lnav_data.ld_views[LNV_LOG].get_selection();
2✔
2469
            if (!log_sel_opt.has_value()) {
2✔
2470
                lnav_data.ld_views[LNV_LOG].set_selection_to_last_row();
1✔
2471
            }
2472
            lnav_data.ld_text_source.tss_apply_default_init_location = true;
2✔
2473
            load_time_bookmarks();
2✔
2474
            exec_phase.completed(lnav::phase_t::load_session);
2✔
2475

2476
            if (!lnav_data.ld_view_stack.empty()) {
2✔
2477
                auto* top_view = lnav_data.ld_view_stack.top().value();
2✔
2478
                std::string alt_value;
2✔
2479

2480
                if (top_view == &lnav_data.ld_views[LNV_LOG]) {
2✔
2481
                    alt_value = fmt::format(
2✔
2482
                        FMT_STRING(HELP_MSG_2(
4✔
2483
                            e,
2484
                            E,
2485
                            "to move forward/backward through " ANSI_ROLE_FMT(
2486
                                "error") " messages")),
2487
                        lnav::enums::to_underlying(role_t::VCR_ERROR));
6✔
2488
                }
2489
                if (!alt_value.empty()) {
2✔
2490
                    prompt.p_editor.set_alt_value(alt_value);
2✔
2491
                }
2492
            }
2✔
2493
        }
2494

2495
        if (handle_winch(&sc)) {
9✔
2496
            got_user_input = true;
×
2497
            next_status_update_time = ui_now;
×
2498
            layout_views();
×
2499
        }
2500

2501
        if (lnav_data.ld_child_terminated) {
9✔
2502
            lnav_data.ld_child_terminated = false;
×
2503

2504
            log_info("checking for terminated child processes");
×
2505
            for (auto iter = lnav_data.ld_children.begin();
×
2506
                 iter != lnav_data.ld_children.end();
×
2507
                 ++iter)
×
2508
            {
2509
                int rc, child_stat;
2510

2511
                rc = waitpid(*iter, &child_stat, WNOHANG);
×
2512
                if (rc == -1 || rc == 0) {
×
2513
                    continue;
×
2514
                }
2515

2516
                if (WIFEXITED(child_stat)) {
×
2517
                    log_info("child %d exited with status %d",
×
2518
                             *iter,
2519
                             WEXITSTATUS(child_stat));
2520
                } else if (WTERMSIG(child_stat)) {
×
2521
                    log_error("child %d terminated with signal %d",
×
2522
                              *iter,
2523
                              WTERMSIG(child_stat));
2524
                } else {
2525
                    log_info("child %d exited", *iter);
×
2526
                }
2527
                iter = lnav_data.ld_children.erase(iter);
×
2528
            }
2529

2530
            gather_pipers();
×
2531

2532
            next_rescan_time = ui_clock::now();
×
2533
            next_rebuild_time = next_rescan_time;
×
2534
            next_status_update_time = next_rescan_time;
×
2535
        }
2536

2537
        if (lnav_data.ld_view_stack.empty()) {
9✔
2538
            log_info("no more views, exiting...");
2✔
2539
            lnav_data.ld_looping = false;
2✔
2540
        } else if (lnav_data.ld_view_stack.size() == 1
7✔
2541
                   && starting_view_stack_size == 2
7✔
2542
                   && lnav_data.ld_log_source.file_count() == 0
×
2543
                   && lnav_data.ld_active_files.fc_file_names.size()
14✔
2544
                       == lnav_data.ld_text_source.size())
×
2545
        {
2546
            log_info("text view popped and no other files, exiting...");
×
2547
            lnav_data.ld_looping = false;
×
2548
        }
2549

2550
        if (lnav_data.ld_sigint_count > 0) {
9✔
2551
            auto found_piper = false;
×
2552

2553
            lnav_data.ld_sigint_count = 0;
×
2554
            if (!lnav_data.ld_view_stack.empty()) {
×
2555
                auto* tc = *lnav_data.ld_view_stack.top();
×
2556
                auto sel = tc->get_selection();
×
2557

2558
                if (tc->get_inner_height() > 0_vl && sel) {
×
2559
                    std::vector<attr_line_t> rows(1);
×
2560

2561
                    tc->get_data_source()->listview_value_for_rows(
×
2562
                        *tc, sel.value(), rows);
×
2563
                    auto& sa = rows[0].get_attrs();
×
2564
                    auto line_attr_opt = get_string_attr(sa, L_FILE);
×
2565
                    if (line_attr_opt) {
×
2566
                        auto lf = line_attr_opt.value().get();
×
2567

2568
                        log_debug("file name when SIGINT: %s",
×
2569
                                  lf->get_filename().c_str());
2570
                        for (auto& cp : lnav_data.ld_child_pollers) {
×
2571
                            auto cp_name = cp.get_filename();
×
2572

2573
                            if (!cp_name) {
×
2574
                                log_debug("no child_poller");
×
2575
                                continue;
×
2576
                            }
2577

2578
                            if (lf->get_filename() == cp_name.value()) {
×
2579
                                log_debug("found it, sending signal!");
×
2580
                                cp.send_sigint();
×
2581
                                found_piper = true;
×
2582
                            }
2583
                        }
2584
                    }
2585
                }
2586
            }
2587
            if (!found_piper) {
×
2588
                log_info("user requested exit...");
×
2589
                lnav_data.ld_looping = false;
×
2590
            }
2591
        }
2592
    }
9✔
2593

2594
    if (rescan_future.valid()) {
2✔
2595
        rescan_future.get();
×
2596
    }
2597

2598
    save_session();
2✔
2599
}
10✔
2600

2601
void
2602
wait_for_children()
2,226✔
2603
{
2604
    std::vector<struct pollfd> pollfds;
2,226✔
2605
    struct timeval to = {0, 333000};
2,226✔
2606
    static auto* ps = injector::get<pollable_supervisor*>();
2,226✔
2607

2608
    for (auto iter = lnav_data.ld_children.begin();
2,226✔
2609
         iter != lnav_data.ld_children.end();
2,233✔
2610
         ++iter)
7✔
2611
    {
2612
        int rc, child_stat;
2613

2614
        rc = waitpid(*iter, &child_stat, WNOHANG);
7✔
2615
        if (rc == -1 || rc == 0) {
7✔
2616
            continue;
×
2617
        }
2618

2619
        if (WIFEXITED(child_stat)) {
7✔
2620
            log_info("child %d exited with status %d",
7✔
2621
                     *iter,
2622
                     WEXITSTATUS(child_stat));
2623
        } else if (WTERMSIG(child_stat)) {
×
2624
            log_error("child %d terminated with signal %d",
×
2625
                      *iter,
2626
                      WTERMSIG(child_stat));
2627
        } else {
2628
            log_info("child %d exited", *iter);
×
2629
        }
2630
        iter = lnav_data.ld_children.erase(iter);
7✔
2631
    }
2632

2633
    do {
2634
        pollfds.clear();
2,244✔
2635

2636
        auto update_res = ps->update_poll_set(pollfds);
2,244✔
2637

2638
        if (update_res.ur_background == 0) {
2,244✔
2639
            break;
2,226✔
2640
        }
2641

2642
        int rc = poll(&pollfds[0], pollfds.size(), to.tv_usec / 1000);
18✔
2643

2644
        if (rc < 0) {
18✔
2645
            switch (errno) {
×
2646
                case 0:
×
2647
                case EINTR:
2648
                    break;
×
2649
                default:
×
2650
                    return;
×
2651
            }
2652
        }
2653

2654
        ps->check_poll_set(pollfds);
18✔
2655
        lnav_data.ld_view_stack.top() | [](auto tc) { update_hits(tc); };
36✔
2656
    } while (lnav_data.ld_looping);
18✔
2657
}
2,226✔
2658

2659
struct mode_flags_t {
2660
    bool mf_check_configs{false};
2661
    bool mf_install{false};
2662
    bool mf_update_formats{false};
2663
    bool mf_no_default{false};
2664
    bool mf_print_warnings{false};
2665
};
2666

2667
static int
2668
print_user_msgs(std::vector<lnav::console::user_message> error_list,
14✔
2669
                mode_flags_t mf)
2670
{
2671
    size_t warning_count = 0;
14✔
2672
    int retval = EXIT_SUCCESS;
14✔
2673

2674
    for (auto& iter : error_list) {
94✔
2675
        FILE* out_file;
2676

2677
        switch (iter.um_level) {
80✔
2678
            case lnav::console::user_message::level::raw:
6✔
2679
            case lnav::console::user_message::level::ok:
2680
                out_file = stdout;
6✔
2681
                break;
6✔
2682
            case lnav::console::user_message::level::warning:
13✔
2683
                warning_count += 1;
13✔
2684
                if (!mf.mf_print_warnings) {
13✔
2685
                    continue;
×
2686
                }
2687
                out_file = stderr;
13✔
2688
                break;
13✔
2689
            default:
61✔
2690
                out_file = stderr;
61✔
2691
                break;
61✔
2692
        }
2693

2694
        lnav::console::print(out_file, iter);
80✔
2695
        if (iter.um_level == lnav::console::user_message::level::error) {
80✔
2696
            retval = EXIT_FAILURE;
60✔
2697
        }
2698
    }
2699

2700
    if (warning_count > 0 && !mf.mf_print_warnings
3✔
2701
        && verbosity != verbosity_t::quiet
×
2702
        && !(lnav_data.ld_flags & LNF_HEADLESS)
×
2703
        && (std::filesystem::file_time_type::clock::now()
17✔
2704
                - lnav_data.ld_last_dot_lnav_time
×
2705
            > 24h))
14✔
2706
    {
2707
        lnav::console::print(
×
2708
            stderr,
2709
            lnav::console::user_message::warning(
×
2710
                attr_line_t()
×
2711
                    .append(lnav::roles::number(fmt::to_string(warning_count)))
×
2712
                    .append(" issues were detected when checking lnav's "
×
2713
                            "configuration"))
2714
                .with_help(
×
2715
                    attr_line_t("pass ")
×
2716
                        .append(lnav::roles::symbol("-W"))
×
2717
                        .append(" on the command line to display the issues\n")
×
2718
                        .append("(this message will only be displayed once a "
×
2719
                                "day)")));
2720
    }
2721

2722
    return retval;
14✔
2723
}
2724

2725
verbosity_t verbosity = verbosity_t::standard;
2726

2727
int
2728
main(int argc, char* argv[])
756✔
2729
{
2730
    std::vector<lnav::console::user_message> config_errors;
756✔
2731
    std::vector<lnav::console::user_message> loader_errors;
756✔
2732
    auto& ec = lnav_data.ld_exec_context;
756✔
2733
    int retval = EXIT_SUCCESS;
756✔
2734

2735
    bool exec_stdin = false, load_stdin = false;
756✔
2736
    mode_flags_t mode_flags;
756✔
2737
    std::string since_time;
756✔
2738
    std::string until_time;
756✔
2739
    const char* LANG = getenv("LANG");
756✔
2740

2741
    if (LANG == nullptr || strcmp(LANG, "C") == 0) {
756✔
2742
        setenv("LANG", "en_US.UTF-8", 1);
×
2743
    }
2744

2745
    {
2746
        const auto* TEMP = getenv("TEMP");
756✔
2747

2748
        if (TEMP != nullptr) {
756✔
2749
            setenv("TMPDIR", TEMP, 0);
×
2750
        } else {
2751
            setenv("TMPDIR",
756✔
2752
                   std::filesystem::temp_directory_path().string().c_str(),
1,512✔
2753
                   0);
2754
        }
2755
    }
2756

2757
    if (lnav::console::only_process_attached_to_win32_console()) {
756✔
2758
        mode_flags.mf_no_default = true;
×
2759
    }
2760

2761
    ec.ec_label_source_stack.push_back(&lnav_data.ld_db_row_source);
756✔
2762

2763
    (void) signal(SIGPIPE, SIG_IGN);
756✔
2764
    (void) signal(SIGCHLD, sigchld);
756✔
2765
    setlocale(LC_ALL, "");
756✔
2766
    try {
2767
        std::locale::global(std::locale(""));
756✔
2768
    } catch (const std::runtime_error& e) {
×
2769
        log_error("unable to set locale to ''");
×
2770
    }
×
2771
    umask(027);
756✔
2772

2773
    /* Disable Lnav from being able to execute external commands if
2774
     * "LNAVSECURE" environment variable is set by the user.
2775
     */
2776
    if (getenv("LNAVSECURE") != nullptr) {
756✔
2777
        lnav_data.ld_flags |= LNF_SECURE_MODE;
5✔
2778
    }
2779

2780
    // Set PAGER so that stuff run from `:sh` will just dump their
2781
    // output for lnav to display.  One example would be `man`, as
2782
    // in `:sh man ls`.
2783
    setenv("PAGER", "cat", 1);
756✔
2784
    setenv("LNAV_HOME_DIR", lnav::paths::dotlnav().c_str(), 1);
756✔
2785
    setenv("LNAV_WORK_DIR", lnav::paths::workdir().c_str(), 1);
756✔
2786

2787
    try {
2788
        auto& safe_options_hier
2789
            = injector::get<lnav::safe_file_options_hier&>();
756✔
2790

2791
        auto opt_path = lnav::paths::dotlnav() / "file-options.json";
756✔
2792
        auto read_res = lnav::filesystem::read_file(opt_path);
756✔
2793
        auto curr_tz = date::get_tzdb().current_zone();
756✔
2794
        auto options_coll = lnav::file_options_collection{};
756✔
2795

2796
        if (read_res.isOk()) {
756✔
2797
            intern_string_t opt_path_src = intern_string::lookup(opt_path);
13✔
2798
            auto parse_res = lnav::file_options_collection::from_json(
2799
                opt_path_src, read_res.unwrap());
13✔
2800
            if (parse_res.isErr()) {
13✔
2801
                for (const auto& um : parse_res.unwrapErr()) {
×
2802
                    lnav::console::print(stderr, um);
×
2803
                }
2804
                return EXIT_FAILURE;
×
2805
            }
2806

2807
            options_coll = parse_res.unwrap();
13✔
2808
        }
13✔
2809

2810
        safe::WriteAccess<lnav::safe_file_options_hier> options_hier(
2811
            safe_options_hier);
756✔
2812

2813
        options_hier->foh_generation += 1;
756✔
2814
        auto_mem<char> var_path;
756✔
2815

2816
        var_path = realpath("/var/log", nullptr);
756✔
2817
        options_coll.foc_pattern_to_options[fmt::format(FMT_STRING("{}/*"),
1,512✔
2818
                                                        var_path.in())]
1,512✔
2819
            = lnav::file_options{
756✔
2820
                {
2821
                    intern_string_t{},
756✔
2822
                    source_location{},
756✔
2823
                    curr_tz,
2824
                },
2825
            };
2826
        options_hier->foh_path_to_collection.emplace(std::filesystem::path("/"),
756✔
2827
                                                     options_coll);
2828
    } catch (const std::runtime_error& e) {
756✔
2829
        log_error("failed to setup tz: %s", e.what());
×
2830
    }
×
2831

2832
    lnav_data.ld_exec_context.ec_sql_callback = sql_callback;
756✔
2833
    lnav_data.ld_exec_context.ec_pipe_callback = pipe_callback;
756✔
2834

2835
    lnav_data.ld_program_name = argv[0];
756✔
2836
    add_ansi_vars(ec.ec_global_vars);
756✔
2837

2838
    lnav_data.ld_db_key_names = DEFAULT_DB_KEY_NAMES;
756✔
2839

2840
    auto dot_lnav_path = lnav::paths::dotlnav();
756✔
2841
    std::error_code last_write_ec;
756✔
2842
    lnav_data.ld_last_dot_lnav_time
2843
        = std::filesystem::last_write_time(dot_lnav_path, last_write_ec);
756✔
2844

2845
    ensure_dotlnav();
756✔
2846

2847
    log_install_handlers();
756✔
2848
    sql_install_logger();
756✔
2849

2850
    log_info("opening main sqlite3 (%s) DB", sqlite3_version);
756✔
2851
    if (sqlite3_open_v2(
756✔
2852
            "file:user_db?mode=memory&cache=shared",
2853
            lnav_data.ld_db.out(),
2854
            SQLITE_OPEN_URI | SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
2855
            nullptr)
2856
        != SQLITE_OK)
756✔
2857
    {
2858
        fprintf(stderr, "error: unable to create sqlite memory database\n");
×
2859
        exit(EXIT_FAILURE);
×
2860
    }
2861

2862
    {
2863
        auto stmt = prepare_stmt(lnav_data.ld_db, LNAV_ATTACH_DB).unwrap();
756✔
2864
        auto exec_res = stmt.execute();
756✔
2865
        if (exec_res.isErr()) {
756✔
2866
            fprintf(stderr,
×
2867
                    "failed to attach memory database: %s\n",
2868
                    exec_res.unwrapErr().c_str());
×
2869
            exit(EXIT_FAILURE);
×
2870
        }
2871
    }
756✔
2872

2873
    {
2874
        int register_collation_functions(sqlite3 * db);
2875

2876
        register_sqlite_funcs(lnav_data.ld_db.in(), sqlite_registration_funcs);
756✔
2877
        register_collation_functions(lnav_data.ld_db.in());
756✔
2878
    }
2879

2880
    register_environ_vtab(lnav_data.ld_db.in());
756✔
2881
    register_static_file_vtab(lnav_data.ld_db.in());
756✔
2882
    {
2883
        static auto vtab_modules
2884
            = injector::get<std::vector<std::shared_ptr<vtab_module_base>>>();
756✔
2885

2886
        for (const auto& mod : vtab_modules) {
7,560✔
2887
            mod->create(lnav_data.ld_db.in());
6,804✔
2888
        }
2889
    }
2890

2891
    register_views_vtab(lnav_data.ld_db.in());
756✔
2892
    register_regexp_vtab(lnav_data.ld_db.in());
756✔
2893
    register_xpath_vtab(lnav_data.ld_db.in());
756✔
2894
    register_fstat_vtab(lnav_data.ld_db.in());
756✔
2895
    lnav::events::register_events_tab(lnav_data.ld_db.in());
756✔
2896
    register_log_stmt_vtab(lnav_data.ld_db.in());
756✔
2897

2898
#ifdef HAVE_RUST_DEPS
2899
    {
2900
        lnav_rs_ext::init_ext();
756✔
2901
    }
2902
#endif
2903

2904
    auto log_fos = std::make_unique<field_overlay_source>(
2905
        lnav_data.ld_log_source, lnav_data.ld_text_source);
756✔
2906

2907
    auto _vtab_cleanup = finally([] {
756✔
2908
        static const char* VIRT_TABLES = R"(
2909
SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%'
2910
)";
2911

2912
        auto op_guard = lnav_opid_guard::once("cleanup");
756✔
2913

2914
        log_info("performing cleanup");
756✔
2915

2916
#ifdef HAVE_RUST_DEPS
2917
        lnav_rs_ext::stop_ext_access();
756✔
2918
#endif
2919

2920
        {
2921
            auto& dls = lnav_data.ld_db_row_source;
756✔
2922
            size_t memory_usage = 0, total_size = 0, cached_chunks = 0;
756✔
2923
            for (auto cc = dls.dls_cell_container.cc_first.get(); cc != nullptr;
1,513✔
2924
                 cc = cc->cc_next.get())
757✔
2925
            {
2926
                total_size += cc->cc_capacity;
757✔
2927
                if (cc->cc_data) {
757✔
2928
                    cached_chunks += 1;
756✔
2929
                    memory_usage += cc->cc_capacity;
756✔
2930
                } else {
2931
                    memory_usage += cc->cc_compressed_size;
1✔
2932
                }
2933
            }
2934
            log_debug(
756✔
2935
                "cell memory footprint: total=%zu; actual=%zu; "
2936
                "cached-chunks=%zu",
2937
                total_size,
2938
                memory_usage,
2939
                cached_chunks);
2940
        }
2941

2942
        if (lnav_data.ld_spectro_source != nullptr) {
756✔
2943
            delete std::exchange(lnav_data.ld_spectro_source->ss_value_source,
632✔
2944
                                 nullptr);
1,264✔
2945
        }
2946

2947
        lnav_data.ld_child_pollers.clear();
756✔
2948

2949
        delete lnav_data.ld_views[LNV_SCHEMA].get_sub_source();
756✔
2950
        delete lnav_data.ld_views[LNV_PRETTY].get_sub_source();
756✔
2951
        for (auto& tc : lnav_data.ld_views) {
7,560✔
2952
            tc.deinit();
6,804✔
2953
        }
2954

2955
        log_info("marking files as closed");
756✔
2956
        for (auto& lf : lnav_data.ld_active_files.fc_files) {
1,365✔
2957
            lf->close();
609✔
2958
        }
2959
        log_info("rebuilding after closures");
756✔
2960
        rebuild_indexes(ui_clock::now());
756✔
2961
        log_info("clearing file collection");
756✔
2962
        lnav_data.ld_active_files.clear();
756✔
2963

2964
        log_info("dropping tables");
756✔
2965
        lnav_data.ld_vtab_manager = nullptr;
756✔
2966

2967
        std::vector<std::string> tables_to_drop;
756✔
2968
        {
2969
            auto prep_res = prepare_stmt(lnav_data.ld_db.in(), VIRT_TABLES);
756✔
2970
            if (prep_res.isErr()) {
756✔
2971
                log_error("unable to prepare VIRT_TABLES: %s",
×
2972
                          prep_res.unwrapErr().c_str());
2973
            } else {
2974
                auto stmt = prep_res.unwrap();
756✔
2975

2976
                stmt.for_each_row<std::string>(
756✔
2977
                    [&tables_to_drop](auto table_name) {
×
2978
                        tables_to_drop.emplace_back(fmt::format(
×
2979
                            FMT_STRING("DROP TABLE {}"), table_name));
×
2980
                        return false;
×
2981
                    });
2982
            }
756✔
2983
        }
756✔
2984

2985
        // XXX
2986
        lnav_data.ld_log_source.set_preview_sql_filter(nullptr);
756✔
2987
        lnav_data.ld_log_source.set_sql_filter("", nullptr);
1,512✔
2988
        lnav_data.ld_log_source.set_sql_marker("", nullptr);
756✔
2989
        lnav_config_listener::unload_all();
756✔
2990

2991
        {
2992
            sqlite3_stmt* stmt_iter = nullptr;
756✔
2993

2994
            do {
2995
                stmt_iter = sqlite3_next_stmt(lnav_data.ld_db.in(), stmt_iter);
756✔
2996
                if (stmt_iter != nullptr) {
756✔
2997
                    const auto* stmt_sql = sqlite3_sql(stmt_iter);
×
2998

2999
                    log_warning("unfinalized SQL statement: %s", stmt_sql);
×
3000
                    ensure(false);
×
3001
                }
3002
            } while (stmt_iter != nullptr);
756✔
3003
        }
3004

3005
        for (auto& drop_stmt : tables_to_drop) {
756✔
3006
            auto prep_res
3007
                = prepare_stmt(lnav_data.ld_db.in(), drop_stmt.c_str());
×
3008
            if (prep_res.isErr()) {
×
3009
                log_error("unable to prepare DROP statement: %s",
×
3010
                          prep_res.unwrapErr().c_str());
3011
                continue;
×
3012
            }
3013

3014
            auto stmt = prep_res.unwrap();
×
3015
            stmt.execute();
×
3016
        }
3017
#if defined(HAVE_SQLITE3_DROP_MODULES)
3018
        sqlite3_drop_modules(lnav_data.ld_db.in(), nullptr);
756✔
3019
#endif
3020

3021
        lnav_data.ld_db.reset();
756✔
3022

3023
        log_info("waiting for cleanup tasks");
756✔
3024
        while (!CLEANUP_TASKS.empty()) {
3,816✔
3025
            auto& [name, task] = CLEANUP_TASKS.back();
3,060✔
3026
            if (task.wait_for(10ms) != std::future_status::ready) {
3,060✔
3027
                log_warning("cleanup task '%s' is not yet ready", name);
×
3028
            }
3029
            CLEANUP_TASKS.pop_back();
3,060✔
3030
        }
3031

3032
        log_info("cleanup finished");
756✔
3033
    });
1,512✔
3034

3035
#ifdef HAVE_LIBCURL
3036
    curl_global_init(CURL_GLOBAL_DEFAULT);
756✔
3037
#endif
3038

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

3041
    // lnav_data.ld_debug_log_name = DEFAULT_DEBUG_LOG;
3042

3043
    std::vector<std::string> file_args;
756✔
3044
    std::vector<lnav::console::user_message> arg_errors;
756✔
3045

3046
    CLI::App app{"The Logfile Navigator"};
3,024✔
3047

3048
    app.add_option("-d",
3049
                   lnav_data.ld_debug_log_name,
3050
                   "Write debug messages to the given file.")
3051
        ->type_name("FILE");
4,536✔
3052
    app.add_option("-I", lnav_data.ld_config_paths, "include paths")
3053
        ->check(CLI::ExistingDirectory)
3054
        ->check([&arg_errors](std::string inc_path) -> std::string {
×
3055
            if (access(inc_path.c_str(), X_OK) != 0) {
53✔
3056
                arg_errors.emplace_back(
×
3057
                    lnav::console::user_message::error(
×
3058
                        attr_line_t("invalid configuration directory: ")
×
3059
                            .append(lnav::roles::file(inc_path)))
×
3060
                        .with_errno_reason());
3061
                return "unreadable";
×
3062
            }
3063

3064
            return std::string();
53✔
3065
        })
3066
        ->allow_extra_args(false);
7,560✔
3067
    app.add_flag("-q{0},-v{2}", verbosity, "Control the verbosity");
3,024✔
3068
    app.set_version_flag("-V,--version");
3,780✔
3069
    app.footer(fmt::format(FMT_STRING("Version: {}"), VCS_PACKAGE_STRING));
3,024✔
3070

3071
    std::shared_ptr<lnav::management::operations> mmode_ops;
756✔
3072

3073
    if (argc < 2 || strcmp(argv[1], "-m") != 0) {
756✔
3074
        app.add_flag("-H", lnav_data.ld_show_help_view, "show help");
2,984✔
3075
        app.add_flag("-C", mode_flags.mf_check_configs, "check");
2,984✔
3076
        auto* install_flag
3077
            = app.add_flag("-i", mode_flags.mf_install, "install");
2,984✔
3078
        app.add_flag("-u", mode_flags.mf_update_formats, "update");
2,984✔
3079
        auto* no_default_flag
3080
            = app.add_flag("-N", mode_flags.mf_no_default, "no def");
2,984✔
3081
        auto* rotated_flag = app.add_flag(
2,984✔
3082
            "-R", lnav_data.ld_active_files.fc_rotated, "rotated");
3083
        auto* recurse_flag = app.add_flag(
2,984✔
3084
            "-r", lnav_data.ld_active_files.fc_recursive, "recurse");
3085
        auto* as_log_flag
3086
            = app.add_flag("-t", lnav_data.ld_treat_stdin_as_log, "as-log");
2,984✔
3087
        app.add_flag("-W", mode_flags.mf_print_warnings);
2,984✔
3088
        auto* headless_flag = app.add_flag(
2,984✔
3089
            "-n",
3090
            [](size_t count) { lnav_data.ld_flags |= LNF_HEADLESS; },
746✔
3091
            "headless");
3092
        auto* file_opt = app.add_option("file", file_args, "files");
2,984✔
3093

3094
        app.add_option("-S,--since", since_time, "since");
2,984✔
3095
        app.add_option("-U,--until", until_time, "until");
2,984✔
3096

3097
        auto wait_cb = [](size_t count) {
×
3098
            fprintf(stderr, "PID %d waiting for attachment\n", getpid());
×
3099
            char b;
3100
            if (isatty(STDIN_FILENO) && read(STDIN_FILENO, &b, 1) == -1) {
×
3101
                perror("Read key from STDIN");
×
3102
            }
3103
        };
3104
        app.add_flag("--stop", wait_cb);
2,238✔
3105

3106
        auto cmd_appender
3107
            = [](std::string cmd) { lnav_data.ld_commands.emplace_back(cmd); };
992✔
3108
        auto cmd_validator = [&arg_errors](std::string cmd) -> std::string {
993✔
3109
            static const auto ARG_SRC
3110
                = intern_string::lookup("command-line argument");
2,043✔
3111

3112
            if (cmd.empty()) {
993✔
3113
                return "empty commands are not allowed";
×
3114
            }
3115

3116
            switch (cmd[0]) {
993✔
3117
                case ':':
992✔
3118
                case '/':
3119
                case ';':
3120
                case '|':
3121
                    break;
992✔
3122
                default:
1✔
3123
                    cmd.push_back(' ');
1✔
3124
                    arg_errors.emplace_back(
1✔
3125
                        lnav::console::user_message::error(
×
3126
                            attr_line_t("invalid value for ")
2✔
3127
                                .append_quoted("-c"_symbol)
1✔
3128
                                .append(" option"))
1✔
3129
                            .with_snippet(lnav::console::snippet::from(
1✔
3130
                                ARG_SRC,
3131
                                attr_line_t()
2✔
3132
                                    .append(" -c "_quoted_code)
1✔
3133
                                    .append(lnav::roles::quoted_code(cmd))
2✔
3134
                                    .append("\n")
1✔
3135
                                    .append(4, ' ')
1✔
3136
                                    .append(lnav::roles::error(
2✔
3137
                                        "^ command type prefix "
3138
                                        "is missing"))))
3139
                            .with_help(command_arg_help()));
2✔
3140
                    return "invalid prefix";
2✔
3141
            }
3142
            return std::string();
992✔
3143
        };
746✔
3144
        auto* cmd_opt = app.add_option("-c")
3145
                            ->check(cmd_validator)
3146
                            ->each(cmd_appender)
3147
                            ->allow_extra_args(false)
3148
                            ->trigger_on_parse(true);
4,476✔
3149

3150
        auto file_appender = [](std::string file_path) {
7✔
3151
            lnav_data.ld_commands.emplace_back(
7✔
3152
                fmt::format(FMT_STRING("|{}"), file_path));
28✔
3153
        };
7✔
3154
        auto* exec_file_opt = app.add_option("-f")
3155
                                  ->trigger_on_parse(true)
3156
                                  ->allow_extra_args(false)
3157
                                  ->each(file_appender);
746✔
3158

3159
        auto shexec_appender = [&mode_flags](std::string cmd) {
4✔
3160
            mode_flags.mf_no_default = true;
4✔
3161
            lnav_data.ld_commands.emplace_back(
4✔
3162
                fmt::format(FMT_STRING(":sh {}"), cmd));
16✔
3163
        };
750✔
3164
        auto* cmdline_opt = app.add_option("-e")
3165
                                ->each(shexec_appender)
3166
                                ->allow_extra_args(false)
3167
                                ->trigger_on_parse(true);
746✔
3168

3169
        install_flag->needs(file_opt);
746✔
3170
        install_flag->excludes(no_default_flag,
746✔
3171
                               as_log_flag,
3172
                               rotated_flag,
3173
                               recurse_flag,
3174
                               headless_flag,
3175
                               cmd_opt,
3176
                               exec_file_opt,
3177
                               cmdline_opt);
3178
    }
3179

3180
    auto is_mmode = argc >= 2 && strcmp(argv[1], "-m") == 0;
756✔
3181
    try {
3182
#if defined(__MSYS__)
3183
        lnav::console::get_command_line_args(&argc, &argv);
3184
#endif
3185
        if (is_mmode) {
756✔
3186
            mmode_ops = lnav::management::describe_cli(app, argc, argv);
10✔
3187
        } else {
3188
            app.parse(argc, argv);
746✔
3189
        }
3190
    } catch (const CLI::CallForHelp& e) {
118✔
3191
        if (is_mmode) {
1✔
3192
            fmt::print(FMT_STRING("{}\n"), app.help());
×
3193
        } else {
3194
            usage();
1✔
3195
        }
3196
        return EXIT_SUCCESS;
1✔
3197
    } catch (const CLI::CallForVersion& e) {
117✔
3198
        fmt::print(FMT_STRING("{}\n"), VCS_PACKAGE_STRING);
348✔
3199
        return EXIT_SUCCESS;
116✔
3200
    } catch (const CLI::ParseError& e) {
117✔
3201
        if (!arg_errors.empty()) {
1✔
3202
            print_user_msgs(arg_errors, mode_flags);
1✔
3203
            return e.get_exit_code();
1✔
3204
        }
3205

3206
        lnav::console::print(
×
3207
            stderr,
3208
            lnav::console::user_message::error("invalid command-line arguments")
×
3209
                .with_reason(e.what()));
×
3210
        return e.get_exit_code();
×
3211
    }
1✔
3212

3213
    lnav_data.ld_config_paths.insert(lnav_data.ld_config_paths.begin(),
638✔
3214
                                     lnav::paths::dotlnav());
1,276✔
3215
    lnav_data.ld_config_paths.insert(lnav_data.ld_config_paths.begin(),
638✔
3216
                                     SYSCONFDIR "/lnav");
3217
    lnav_data.ld_config_paths.insert(lnav_data.ld_config_paths.begin(),
638✔
3218
                                     "/etc/lnav");
3219

3220
    if (!lnav_data.ld_debug_log_name.empty()
638✔
3221
        && lnav_data.ld_debug_log_name != DEFAULT_DEBUG_LOG)
638✔
3222
    {
3223
        lnav_log_level = lnav_log_level_t::TRACE;
46✔
3224
    }
3225

3226
    if (!lnav_data.ld_debug_log_name.empty()) {
638✔
3227
        lnav_log_file = make_optional_from_nullable(
46✔
3228
            fopen(lnav_data.ld_debug_log_name.c_str(), "ae"));
46✔
3229
        lnav_log_file | [](auto* file) {
46✔
3230
            fcntl(fileno(file), F_SETFD, FD_CLOEXEC);
46✔
3231
            log_write_ring_to(fileno(file));
46✔
3232
        };
46✔
3233
    }
3234
    log_info("lnav started %d", lnav_log_file.has_value());
638✔
3235

3236
    {
3237
        static auto builtin_formats
3238
            = injector::get<std::vector<std::shared_ptr<log_format>>>();
638✔
3239
        auto& root_formats = log_format::get_root_formats();
638✔
3240

3241
        log_format::get_root_formats().insert(root_formats.begin(),
638✔
3242
                                              builtin_formats.begin(),
3243
                                              builtin_formats.end());
3244
        builtin_formats.clear();
638✔
3245
    }
3246

3247
    load_config(lnav_data.ld_config_paths, config_errors);
638✔
3248

3249
    if (!config_errors.empty()) {
638✔
3250
        if (print_user_msgs(config_errors, mode_flags) != EXIT_SUCCESS) {
1✔
3251
            return EXIT_FAILURE;
1✔
3252
        }
3253
    }
3254
    add_global_vars(ec);
637✔
3255

3256
    if (mode_flags.mf_update_formats) {
637✔
3257
        if (!update_installs_from_git()) {
×
3258
            return EXIT_FAILURE;
×
3259
        }
3260
        return EXIT_SUCCESS;
×
3261
    }
3262

3263
    if (mode_flags.mf_install) {
637✔
3264
        auto formats_installed_path
3265
            = lnav::paths::dotlnav() / "formats/installed";
5✔
3266
        auto configs_installed_path
3267
            = lnav::paths::dotlnav() / "configs/installed";
5✔
3268

3269
        if (argc == 0) {
5✔
3270
            const auto install_reason
3271
                = attr_line_t("the ")
×
3272
                      .append("-i"_symbol)
×
3273
                      .append(
×
3274
                          " option expects one or more log format "
3275
                          "definition "
3276
                          "files to install in your lnav "
3277
                          "configuration "
3278
                          "directory")
3279
                      .move();
×
3280
            const auto install_help
3281
                = attr_line_t(
×
3282
                      "log format definitions are JSON files that "
3283
                      "tell lnav "
3284
                      "how to understand log files\n")
3285
                      .append(
×
3286
                          "See: "
3287
                          "https://docs.lnav.org/en/latest/"
3288
                          "formats.html")
3289
                      .move();
×
3290

3291
            lnav::console::print(stderr,
×
3292
                                 lnav::console::user_message::error(
×
3293
                                     "missing format files to install")
3294
                                     .with_reason(install_reason)
×
3295
                                     .with_help(install_help));
×
3296
            return EXIT_FAILURE;
×
3297
        }
3298

3299
        for (const auto& file_path : file_args) {
8✔
3300
            if (endswith(file_path, ".git")) {
5✔
3301
                if (!install_from_git(file_path)) {
×
3302
                    return EXIT_FAILURE;
2✔
3303
                }
3304
                continue;
1✔
3305
            }
3306

3307
            if (endswith(file_path, ".lnav")) {
5✔
3308
                auto script_path = std::filesystem::path(file_path);
×
3309
                auto read_res = lnav::filesystem::read_file(script_path);
×
3310
                if (read_res.isErr()) {
×
3311
                    lnav::console::print(
×
3312
                        stderr,
3313
                        lnav::console::user_message::error(
×
3314
                            attr_line_t("unable to read script file: ")
×
3315
                                .append(lnav::roles::file(file_path)))
×
3316
                            .with_reason(read_res.unwrapErr()));
×
3317
                    return EXIT_FAILURE;
×
3318
                }
3319

3320
                auto dst_path = formats_installed_path / script_path.filename();
×
3321
                auto write_res
3322
                    = lnav::filesystem::write_file(dst_path, read_res.unwrap());
×
3323
                if (write_res.isErr()) {
×
3324
                    lnav::console::print(
×
3325
                        stderr,
3326
                        lnav::console::user_message::error(
×
3327
                            attr_line_t("unable to write script file: ")
×
3328
                                .append(lnav::roles::file(file_path)))
×
3329
                            .with_reason(write_res.unwrapErr()));
×
3330
                    return EXIT_FAILURE;
×
3331
                }
3332

3333
                lnav::console::print(
×
3334
                    stderr,
3335
                    lnav::console::user_message::ok(
×
3336
                        attr_line_t("installed -- ")
×
3337
                            .append(lnav::roles::file(dst_path))));
×
3338
                continue;
×
3339
            }
3340

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

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

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

3375
            if (file_path == "extra") {
5✔
3376
                install_extra_formats();
1✔
3377
                continue;
1✔
3378
            }
3379

3380
            auto file_type_result = detect_config_file_type(file_path);
4✔
3381
            if (file_type_result.isErr()) {
4✔
3382
                lnav::console::print(
1✔
3383
                    stderr,
3384
                    lnav::console::user_message::error(
×
3385
                        attr_line_t("unable to open configuration file: ")
1✔
3386
                            .append(lnav::roles::file(file_path)))
2✔
3387
                        .with_reason(file_type_result.unwrapErr()));
1✔
3388
                return EXIT_FAILURE;
1✔
3389
            }
3390
            auto file_type = file_type_result.unwrap();
3✔
3391

3392
            auto src_path = std::filesystem::path(file_path);
3✔
3393
            std::filesystem::path dst_name;
3✔
3394
            if (file_type == config_file_type::CONFIG) {
3✔
3395
                dst_name = src_path.filename();
×
3396
            } else {
3397
                auto format_list = load_format_file(src_path, loader_errors);
3✔
3398

3399
                if (!loader_errors.empty()) {
3✔
3400
                    if (print_user_msgs(loader_errors, mode_flags)
×
3401
                        != EXIT_SUCCESS)
×
3402
                    {
3403
                        return EXIT_FAILURE;
×
3404
                    }
3405
                }
3406
                if (format_list.empty()) {
3✔
3407
                    lnav::console::print(
×
3408
                        stderr,
3409
                        lnav::console::user_message::error(
×
3410
                            attr_line_t("invalid format file: ")
×
3411
                                .append(lnav::roles::file(src_path.string())))
×
3412
                            .with_reason("there must be at least one format "
×
3413
                                         "definition in the file"));
3414
                    return EXIT_FAILURE;
×
3415
                }
3416

3417
                dst_name = format_list[0].to_string() + ".json";
3✔
3418
            }
3✔
3419
            auto dst_path = (file_type == config_file_type::CONFIG
3420
                                 ? configs_installed_path
3421
                                 : formats_installed_path)
3422
                / dst_name;
3✔
3423

3424
            auto read_res = lnav::filesystem::read_file(file_path);
3✔
3425
            if (read_res.isErr()) {
3✔
3426
                auto um = lnav::console::user_message::error(
×
3427
                              attr_line_t("cannot read file to install -- ")
×
3428
                                  .append(lnav::roles::file(file_path)))
×
3429
                              .with_reason(read_res.unwrap())
×
3430
                              .move();
×
3431

3432
                lnav::console::print(stderr, um);
×
3433
                return EXIT_FAILURE;
×
3434
            }
3435

3436
            auto file_content = read_res.unwrap();
3✔
3437

3438
            auto read_dst_res = lnav::filesystem::read_file(dst_path);
3✔
3439
            if (read_dst_res.isOk()) {
3✔
3440
                auto dst_content = read_dst_res.unwrap();
2✔
3441

3442
                if (dst_content == file_content) {
2✔
3443
                    auto um = lnav::console::user_message::info(
3444
                        attr_line_t("file is already installed at -- ")
1✔
3445
                            .append(lnav::roles::file(dst_path)));
1✔
3446

3447
                    lnav::console::print(stdout, um);
1✔
3448

3449
                    return EXIT_SUCCESS;
1✔
3450
                }
1✔
3451
            }
2✔
3452

3453
            auto write_res = lnav::filesystem::write_file(
3454
                dst_path,
3455
                file_content,
3456
                {lnav::filesystem::write_file_options::backup_existing});
2✔
3457
            if (write_res.isErr()) {
2✔
3458
                auto um = lnav::console::user_message::error(
×
3459
                              attr_line_t("failed to install file to -- ")
×
3460
                                  .append(lnav::roles::file(dst_path)))
×
3461
                              .with_reason(write_res.unwrapErr())
×
3462
                              .move();
×
3463

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

3468
            auto write_file_res = write_res.unwrap();
2✔
3469
            auto um = lnav::console::user_message::ok(
3470
                attr_line_t("installed -- ")
2✔
3471
                    .append(lnav::roles::file(dst_path)));
2✔
3472
            if (write_file_res.wfr_backup_path) {
2✔
3473
                um.with_note(
1✔
3474
                    attr_line_t("the previously installed ")
2✔
3475
                        .append_quoted(
1✔
3476
                            lnav::roles::file(dst_path.filename().string()))
2✔
3477
                        .append(" was backed up to -- ")
1✔
3478
                        .append(lnav::roles::file(
1✔
3479
                            write_file_res.wfr_backup_path.value().string())));
2✔
3480
            }
3481

3482
            lnav::console::print(stdout, um);
2✔
3483
        }
10✔
3484
        return EXIT_SUCCESS;
3✔
3485
    }
5✔
3486

3487
    if (lnav_data.ld_flags & LNF_SECURE_MODE) {
632✔
3488
        if (sqlite3_set_authorizer(
5✔
3489
                lnav_data.ld_db.in(), sqlite_authorizer, nullptr)
3490
            != SQLITE_OK)
5✔
3491
        {
3492
            fprintf(stderr, "error: unable to attach sqlite authorizer\n");
×
3493
            exit(EXIT_FAILURE);
×
3494
        }
3495
    }
3496

3497
    /* If we statically linked against an ncurses library that had a
3498
     * non-standard path to the terminfo database, we need to set this
3499
     * variable so that it will try the default path.
3500
     */
3501
    setenv("TERMINFO_DIRS",
632✔
3502
           "/usr/share/terminfo:/lib/terminfo:/usr/share/lib/terminfo",
3503
           0);
3504

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

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

3588
    lnav_data.ld_doc_view.set_sub_source(&lnav_data.ld_doc_source);
632✔
3589
    lnav_data.ld_example_view.set_sub_source(&lnav_data.ld_example_source);
632✔
3590
    lnav_data.ld_preview_view[0].set_sub_source(
632✔
3591
        &lnav_data.ld_preview_source[0]);
3592
    lnav_data.ld_files_view.set_sub_source(&lnav_data.ld_files_source)
632✔
3593
        .add_input_delegate(lnav_data.ld_files_source);
632✔
3594
    lnav_data.ld_file_details_view.set_sub_source(
632✔
3595
        &lnav_data.ld_file_details_source);
3596
    lnav_data.ld_files_source.fss_details_source
3597
        = &lnav_data.ld_file_details_source;
632✔
3598
    lnav_data.ld_progress_view.set_title("progress");
632✔
3599
    lnav_data.ld_progress_view.set_default_role(role_t::VCR_INACTIVE_STATUS);
632✔
3600
    lnav_data.ld_progress_view.set_sub_source(&lnav_data.ld_progress_source);
632✔
3601
    lnav_data.ld_progress_view.set_show_scrollbar(true);
632✔
3602
    lnav_data.ld_progress_view.set_height(2_vl);
632✔
3603
    lnav_data.ld_user_message_view.set_sub_source(
632✔
3604
        &lnav_data.ld_user_message_source);
3605

3606
#if 0
3607
    auto overlay_menu = std::make_shared<text_overlay_menu>();
3608
    lnav_data.ld_file_details_view.set_overlay_source(overlay_menu.get());
3609
#endif
3610

3611
    for (int lpc = 0; lpc < LNV__MAX; lpc++) {
6,320✔
3612
        lnav_data.ld_views[lpc].set_gutter_source(new log_gutter_source());
5,688✔
3613
    }
3614

3615
    {
3616
        auto& hs = lnav_data.ld_hist_source2;
632✔
3617

3618
        lnav_data.ld_log_source.set_index_delegate(new hist_index_delegate(
632✔
3619
            lnav_data.ld_hist_source2, lnav_data.ld_views[LNV_HISTOGRAM]));
632✔
3620
        hs.init();
632✔
3621
        lnav_data.ld_zoom_level = 3;
632✔
3622
        hs.set_time_slice(ZOOM_LEVELS[lnav_data.ld_zoom_level]);
632✔
3623
    }
3624

3625
    for (int lpc = 0; lpc < LNV__MAX; lpc++) {
6,320✔
3626
        lnav_data.ld_views[lpc].set_title(lnav_view_titles[lpc]);
17,064✔
3627
    }
3628

3629
    load_formats(lnav_data.ld_config_paths, loader_errors);
632✔
3630

3631
    {
3632
        auto_mem<char, sqlite3_free> errmsg;
632✔
3633
        auto init_sql_str = init_sql.to_string_fragment_producer()->to_string();
632✔
3634

3635
        if (sqlite3_exec(lnav_data.ld_db.in(),
1,264✔
3636
                         init_sql_str.data(),
632✔
3637
                         nullptr,
3638
                         nullptr,
3639
                         errmsg.out())
3640
            != SQLITE_OK)
632✔
3641
        {
3642
            fprintf(stderr,
×
3643
                    "error: unable to execute DB init -- %s\n",
3644
                    errmsg.in());
3645
        }
3646
    }
632✔
3647

3648
    {
3649
        auto op_guard = lnav_opid_guard::once("register_vtab");
632✔
3650

3651
        lnav_data.ld_vtab_manager->register_vtab(
1,264✔
3652
            std::make_shared<all_logs_vtab>());
1,264✔
3653
        lnav_data.ld_vtab_manager->register_vtab(
1,264✔
3654
            std::make_shared<log_format_vtab_impl>(
1,264✔
3655
                log_format::find_root_format("generic_log")));
1,264✔
3656
        lnav_data.ld_vtab_manager->register_vtab(
1,264✔
3657
            std::make_shared<log_format_vtab_impl>(
1,264✔
3658
                log_format::find_root_format("lnav_piper_log")));
1,264✔
3659

3660
        for (auto& iter : log_format::get_root_formats()) {
46,064✔
3661
            auto lvi = iter->get_vtab_impl();
45,432✔
3662

3663
            if (lvi != nullptr) {
45,432✔
3664
                lnav_data.ld_vtab_manager->register_vtab(lvi);
42,904✔
3665
            }
3666
        }
45,432✔
3667

3668
        load_format_extra(lnav_data.ld_db.in(),
632✔
3669
                          ec.ec_global_vars,
632✔
3670
                          lnav_data.ld_config_paths,
3671
                          loader_errors);
3672
        load_format_vtabs(lnav_data.ld_vtab_manager.get(), loader_errors);
632✔
3673

3674
        if (!loader_errors.empty()) {
632✔
3675
            if (print_user_msgs(loader_errors, mode_flags) != EXIT_SUCCESS) {
2✔
3676
                if (mmode_ops == nullptr) {
2✔
3677
                    return EXIT_FAILURE;
2✔
3678
                }
3679
            }
3680
        }
3681
    }
632✔
3682

3683
    if (mmode_ops) {
630✔
3684
        isc::supervisor root_superv(injector::get<isc::service_list>());
10✔
3685
        auto perform_res = lnav::management::perform(mmode_ops);
10✔
3686

3687
        return print_user_msgs(perform_res, mode_flags);
10✔
3688
    }
10✔
3689

3690
    if (!mode_flags.mf_check_configs && !lnav_data.ld_show_help_view) {
620✔
3691
        DEFAULT_FILES.emplace_back("var/log/messages");
619✔
3692
        DEFAULT_FILES.emplace_back("var/log/system.log");
619✔
3693
        DEFAULT_FILES.emplace_back("var/log/syslog");
619✔
3694
        DEFAULT_FILES.emplace_back("var/log/syslog.log");
619✔
3695
    }
3696

3697
    init_lnav_commands(lnav_commands);
620✔
3698
    init_lnav_bookmark_commands(lnav_commands);
620✔
3699
    init_lnav_breakpoint_commands(lnav_commands);
620✔
3700
    init_lnav_display_commands(lnav_commands);
620✔
3701
    init_lnav_filtering_commands(lnav_commands);
620✔
3702
    init_lnav_io_commands(lnav_commands);
620✔
3703
    init_lnav_metadata_commands(lnav_commands);
620✔
3704
    init_lnav_scripting_commands(lnav_commands);
620✔
3705

3706
    lnav_data.ld_looping = true;
620✔
3707
    set_view_mode(ln_mode_t::PAGING);
620✔
3708

3709
    {
3710
        if (!since_time.empty()) {
620✔
3711
            auto from_res = humanize::time::point::from(since_time);
5✔
3712
            if (from_res.isErr()) {
5✔
3713
                auto um = from_res.unwrapErr();
2✔
3714
                um.um_message = attr_line_t("invalid 'since' time ")
2✔
3715
                                    .append_quoted(since_time);
2✔
3716
                lnav::console::print(stderr, um);
2✔
3717
                return EXIT_FAILURE;
2✔
3718
            }
2✔
3719
            lnav_data.ld_default_time_range.tr_begin
3720
                = to_us(from_res.unwrap().get_point());
3✔
3721
        }
5✔
3722
        if (!until_time.empty()) {
618✔
3723
            auto from_res = humanize::time::point::from(until_time);
1✔
3724
            if (from_res.isErr()) {
1✔
3725
                auto um = from_res.unwrapErr();
×
3726
                um.um_message = attr_line_t("invalid 'until' time ")
×
3727
                                    .append_quoted(until_time);
×
3728
                lnav::console::print(stderr, um);
×
3729
                return EXIT_FAILURE;
×
3730
            }
3731
            lnav_data.ld_default_time_range.tr_end
3732
                = to_us(from_res.unwrap().get_point());
1✔
3733
        }
1✔
3734

3735
        if (lnav_data.ld_default_time_range.tr_end
618✔
3736
            < lnav_data.ld_default_time_range.tr_begin)
618✔
3737
        {
3738
            auto um
3739
                = lnav::console::user_message::error(
×
3740
                      attr_line_t("The low time cutoff ")
1✔
3741
                          .append_quoted(lnav::roles::symbol(since_time))
2✔
3742
                          .append(" is not less than the high time cutoff ")
1✔
3743
                          .append_quoted(lnav::roles::symbol(until_time)))
2✔
3744
                      .with_note(attr_line_t("The resolved low time is ")
2✔
3745
                                     .append_quoted(lnav::roles::symbol(
1✔
3746
                                         lnav::to_rfc3339_string(
2✔
3747
                                             lnav_data.ld_default_time_range
3748
                                                 .tr_begin))))
3749
                      .with_note(
2✔
3750
                          attr_line_t("The resolved high time is ")
2✔
3751
                              .append_quoted(
1✔
3752
                                  lnav::roles::symbol(lnav::to_rfc3339_string(
2✔
3753
                                      lnav_data.ld_default_time_range.tr_end))))
3754
                      .with_help(
2✔
3755
                          "Ensure that the low time cutoff is less than "
3756
                          "the high time cutoff.");
1✔
3757
            lnav::console::print(stderr, um);
1✔
3758
            return EXIT_FAILURE;
1✔
3759
        }
1✔
3760
    }
3761

3762
    if ((isatty(STDIN_FILENO) || is_dev_null(STDIN_FILENO)) && file_args.empty()
1,234✔
3763
        && lnav_data.ld_active_files.fc_file_names.empty()
1✔
3764
        && !mode_flags.mf_no_default)
1,234✔
3765
    {
3766
        char start_dir[FILENAME_MAX];
3767

3768
        if (getcwd(start_dir, sizeof(start_dir)) == nullptr) {
×
3769
            perror("getcwd");
×
3770
        } else {
3771
            do {
3772
                if (!append_default_files()) {
×
3773
                    retval = EXIT_FAILURE;
×
3774
                }
3775
            } while (lnav_data.ld_active_files.fc_file_names.empty()
×
3776
                     && change_to_parent_dir());
×
3777

3778
            if (chdir(start_dir) == -1) {
×
3779
                perror("chdir(start_dir)");
×
3780
            }
3781
        }
3782
    }
3783

3784
    {
3785
        const auto internals_dir_opt = getenv_opt("DUMP_INTERNALS_DIR");
617✔
3786

3787
        if (internals_dir_opt) {
617✔
3788
            lnav::dump_internals(internals_dir_opt.value());
2✔
3789

3790
            return EXIT_SUCCESS;
2✔
3791
        }
3792
    }
3793

3794
    if (file_args.empty() && !mode_flags.mf_no_default) {
615✔
3795
        load_stdin = true;
35✔
3796
    }
3797

3798
    for (const auto& file_path_str : file_args) {
1,162✔
3799
        auto [file_path_without_trailer, file_loc]
547✔
3800
            = lnav::filesystem::split_file_location(file_path_str);
547✔
3801
        auto_mem<char> abspath;
547✔
3802
        struct stat st;
3803

3804
        auto file_path = std::filesystem::path(
3805
            stat(file_path_without_trailer.c_str(), &st) == 0
547✔
3806
                ? file_path_without_trailer
3807
                : file_path_str);
547✔
3808

3809
        auto file_path_type
3810
            = lnav::filesystem::determine_path_type(file_path.string());
547✔
3811

3812
        if (file_path_str == "-") {
547✔
3813
            load_stdin = true;
×
3814
        }
3815
#ifdef HAVE_LIBCURL
3816
        else if (file_path_type == lnav::filesystem::path_type::url)
547✔
3817
        {
3818
            auto ul = std::make_shared<url_loader>(file_path_str);
×
3819

3820
            lnav_data.ld_active_files.fc_file_names[ul->get_path()]
×
3821
                .with_filename(file_path)
×
3822
                .with_time_range(lnav_data.ld_default_time_range);
×
3823
            isc::to<curl_looper&, services::curl_streamer_t>().send(
×
3824
                [ul](auto& clooper) { clooper.add_request(ul); });
×
3825
        } else if (file_path_str.find("://") != std::string::npos) {
547✔
3826
            lnav_data.ld_commands.insert(
3✔
3827
                lnav_data.ld_commands.begin(),
×
3828
                fmt::format(FMT_STRING(":open {}"), file_path_str));
15✔
3829
        }
3830
#endif
3831
        else if (file_path_type == lnav::filesystem::path_type::pattern)
544✔
3832
        {
3833
            lnav_data.ld_active_files.fc_file_names[file_path]
24✔
3834
                .with_follow(!(lnav_data.ld_flags & LNF_HEADLESS))
12✔
3835
                .with_time_range(lnav_data.ld_default_time_range);
12✔
3836
        } else if (lnav::filesystem::statp(file_path, &st) == -1) {
532✔
3837
            if (file_path_type == lnav::filesystem::path_type::remote) {
1✔
3838
                lnav_data.ld_active_files.fc_file_names[file_path]
×
3839
                    .with_follow(!(lnav_data.ld_flags & LNF_HEADLESS))
×
3840
                    .with_time_range(lnav_data.ld_default_time_range);
×
3841
            } else {
3842
                lnav::console::print(
1✔
3843
                    stderr,
3844
                    lnav::console::user_message::error(
1✔
3845
                        attr_line_t("unable to open file: ")
1✔
3846
                            .append(lnav::roles::file(file_path)))
2✔
3847
                        .with_errno_reason());
1✔
3848
                retval = EXIT_FAILURE;
1✔
3849
            }
3850
        } else if (access(file_path.c_str(), R_OK) == -1) {
531✔
3851
            lnav::console::print(
1✔
3852
                stderr,
3853
                lnav::console::user_message::error(
1✔
3854
                    attr_line_t("file exists, but is not readable: ")
1✔
3855
                        .append(lnav::roles::file(file_path)))
2✔
3856
                    .with_errno_reason());
1✔
3857
            retval = EXIT_FAILURE;
1✔
3858
        } else if (S_ISFIFO(st.st_mode)) {
530✔
3859
            auto_fd fifo_fd;
×
3860

3861
            if ((fifo_fd = lnav::filesystem::openp(file_path, O_RDONLY)) == -1)
×
3862
            {
3863
                lnav::console::print(
×
3864
                    stderr,
3865
                    lnav::console::user_message::error(
×
3866
                        attr_line_t("cannot open fifo: ")
×
3867
                            .append(lnav::roles::file(file_path)))
×
3868
                        .with_errno_reason());
×
3869
                retval = EXIT_FAILURE;
×
3870
            } else {
3871
                auto desc = fmt::format(FMT_STRING("FIFO [{}]"),
×
3872
                                        lnav_data.ld_fifo_counter++);
×
3873
                auto create_piper_res = lnav::piper::create_looper(
3874
                    desc, std::move(fifo_fd), auto_fd{});
×
3875

3876
                if (create_piper_res.isOk()) {
×
3877
                    lnav_data.ld_active_files.fc_file_names[desc]
×
3878
                        .with_piper(create_piper_res.unwrap())
×
3879
                        .with_time_range(lnav_data.ld_default_time_range);
×
3880
                }
3881
            }
3882
        } else if ((abspath = realpath(file_path.c_str(), nullptr)) == nullptr)
530✔
3883
        {
3884
            perror("Cannot find file");
×
3885
            retval = EXIT_FAILURE;
×
3886
        } else if (S_ISDIR(st.st_mode)) {
530✔
3887
            std::string dir_wild(abspath.in());
2✔
3888

3889
            if (dir_wild[dir_wild.size() - 1] == '/') {
2✔
3890
                dir_wild.resize(dir_wild.size() - 1);
×
3891
            }
3892
            auto loo = logfile_open_options().with_time_range(
4✔
3893
                lnav_data.ld_default_time_range);
2✔
3894
            lnav_data.ld_active_files.fc_file_names.emplace(dir_wild + "/*",
2✔
3895
                                                            loo);
3896
        } else {
2✔
3897
            lnav_data.ld_active_files.fc_file_names.emplace(
528✔
3898
                abspath.in(),
528✔
3899
                logfile_open_options()
528✔
3900
                    .with_init_location(file_loc)
1,056✔
3901
                    .with_follow(!(lnav_data.ld_flags & LNF_HEADLESS))
528✔
3902
                    .with_time_range(lnav_data.ld_default_time_range));
3903
            if (file_loc.valid()) {
528✔
3904
                lnav_data.ld_files_to_front.emplace_back(abspath.in());
528✔
3905
            }
3906
        }
3907
    }
547✔
3908

3909
    if (mode_flags.mf_check_configs) {
615✔
3910
        isc::supervisor root_superv(injector::get<isc::service_list>());
1✔
3911
        rescan_files(true);
1✔
3912
        for (auto& lf : lnav_data.ld_active_files.fc_files) {
2✔
3913
            logfile::rebuild_result_t rebuild_result;
3914

3915
            do {
3916
                rebuild_result = lf->rebuild_index();
3✔
3917
            } while (rebuild_result == logfile::rebuild_result_t::NEW_LINES
3918
                     || rebuild_result == logfile::rebuild_result_t::NEW_ORDER);
3✔
3919
            auto fmt = lf->get_format();
1✔
3920
            if (fmt == nullptr) {
1✔
3921
                fprintf(stderr,
×
3922
                        "error:%s:no format found for file\n",
3923
                        lf->get_filename().c_str());
×
3924
                retval = EXIT_FAILURE;
×
3925
                continue;
×
3926
            }
3927
            for (auto line_iter = lf->begin(); line_iter != lf->end();
4✔
3928
                 ++line_iter)
3✔
3929
            {
3930
                if (line_iter->get_msg_level() != log_level_t::LEVEL_INVALID) {
3✔
3931
                    continue;
2✔
3932
                }
3933

3934
                size_t partial_len;
3935

3936
                auto read_result = lf->read_line(line_iter);
1✔
3937
                if (read_result.isErr()) {
1✔
3938
                    continue;
×
3939
                }
3940
                auto sbr = read_result.unwrap();
1✔
3941
                auto lffs = lf->get_format_file_state();
1✔
3942
                if (fmt->scan_for_partial(lffs, sbr, partial_len)) {
1✔
3943
                    long line_number = std::distance(lf->begin(), line_iter);
1✔
3944
                    auto full_line = to_string(sbr);
1✔
3945
                    std::string partial_line(sbr.get_data(), partial_len);
1✔
3946

3947
                    fprintf(stderr,
2✔
3948
                            "error:%s:%ld:line did not match format "
3949
                            "%s\n",
3950
                            lf->get_filename().c_str(),
1✔
3951
                            line_number,
3952
                            fmt->get_pattern_path(lffs.lffs_pattern_locks,
2✔
3953
                                                  line_number)
3954
                                .c_str());
3955
                    fprintf(stderr,
2✔
3956
                            "error:%s:%ld:         line -- %s\n",
3957
                            lf->get_filename().c_str(),
1✔
3958
                            line_number,
3959
                            full_line.c_str());
3960
                    if (partial_len > 0) {
1✔
3961
                        fprintf(stderr,
2✔
3962
                                "error:%s:%ld:partial match -- %s\n",
3963
                                lf->get_filename().c_str(),
1✔
3964
                                line_number,
3965
                                partial_line.c_str());
3966
                    } else {
3967
                        fprintf(stderr,
×
3968
                                "error:%s:%ld:no partial match found\n",
3969
                                lf->get_filename().c_str(),
×
3970
                                line_number);
3971
                    }
3972
                    retval = EXIT_FAILURE;
1✔
3973
                }
1✔
3974
            }
1✔
3975
        }
1✔
3976
        return retval;
1✔
3977
    }
1✔
3978

3979
    if (lnav_data.ld_flags & LNF_HEADLESS || mode_flags.mf_check_configs) {
614✔
3980
    } else if (!isatty(STDOUT_FILENO)) {
3✔
3981
        lnav::console::print(
1✔
3982
            stderr,
3983
            lnav::console::user_message::error(
2✔
3984
                "unable to display interactive text UI")
3985
                .with_reason("stdout is not a TTY")
2✔
3986
                .with_help(attr_line_t("pass the ")
2✔
3987
                               .append("-n"_symbol)
1✔
3988
                               .append(" option to run lnav in headless mode "
1✔
3989
                                       "or don't redirect stdout")));
3990
        retval = EXIT_FAILURE;
1✔
3991
    }
3992

3993
    std::optional<std::string> stdin_url;
614✔
3994
    std::filesystem::path stdin_dir;
614✔
3995
    if (load_stdin && !isatty(STDIN_FILENO) && !is_dev_null(STDIN_FILENO)
35✔
3996
        && !exec_stdin)
649✔
3997
    {
3998
        static const std::string STDIN_NAME = "stdin";
105✔
3999
        struct stat stdin_st;
4000

4001
        if (fstat(STDIN_FILENO, &stdin_st) == -1) {
35✔
4002
            lnav::console::print(
×
4003
                stderr,
4004
                lnav::console::user_message::error("unable to stat() stdin")
×
4005
                    .with_errno_reason());
×
4006
            retval = EXIT_FAILURE;
×
4007
        } else if (S_ISFIFO(stdin_st.st_mode)) {
35✔
4008
            static auto op = lnav_operation{"load_stdin"};
25✔
4009
            auto op_guard = lnav_opid_guard::internal(op);
25✔
4010
            pollfd pfd[1];
4011

4012
            log_info("waiting for stdin FIFO");
25✔
4013
            pfd[0].fd = STDIN_FILENO;
25✔
4014
            pfd[0].events = POLLIN;
25✔
4015
            pfd[0].revents = 0;
25✔
4016
            auto prc = poll(pfd, 1, 0);
25✔
4017

4018
            if (prc == 0 || (pfd[0].revents & POLLIN)) {
25✔
4019
                auto stdin_piper_res = lnav::piper::create_looper(
4020
                    STDIN_NAME, auto_fd::dup_of(STDIN_FILENO), auto_fd{});
25✔
4021
                if (stdin_piper_res.isOk()) {
25✔
4022
                    auto stdin_piper = stdin_piper_res.unwrap();
25✔
4023
                    stdin_url = stdin_piper.get_url();
25✔
4024
                    stdin_dir = stdin_piper.get_out_dir();
25✔
4025
                    auto& loo = lnav_data.ld_active_files
4026
                                    .fc_file_names[stdin_piper.get_name()];
25✔
4027
                    loo.with_piper(stdin_piper)
50✔
4028
                        .with_include_in_session(
25✔
4029
                            lnav_data.ld_treat_stdin_as_log)
25✔
4030
                        .with_time_range(lnav_data.ld_default_time_range);
25✔
4031
                    if (lnav_data.ld_treat_stdin_as_log) {
25✔
4032
                        loo.with_text_format(text_format_t::TF_LOG);
1✔
4033
                    }
4034
                }
25✔
4035
            } else if (prc < 0) {
25✔
4036
                log_error("unable to poll() stdin: %s",
×
4037
                          lnav::from_errno().message().c_str());
4038
            }
4039
            log_info("  done waiting for stdin to start");
25✔
4040
        } else if (S_ISREG(stdin_st.st_mode)) {
35✔
4041
            // The shell connected a file directly, just open it up
4042
            // and add it in here.
4043
            auto loo = logfile_open_options{}
20✔
4044
                           .with_filename(STDIN_NAME)
10✔
4045
                           .with_include_in_session(false);
10✔
4046

4047
            auto open_res
4048
                = logfile::open(STDIN_NAME, loo, auto_fd::dup_of(STDIN_FILENO));
10✔
4049

4050
            if (open_res.isErr()) {
10✔
4051
                lnav::console::print(
×
4052
                    stderr,
4053
                    lnav::console::user_message::error("unable to open stdin")
×
4054
                        .with_reason(open_res.unwrapErr()));
×
4055
                retval = EXIT_FAILURE;
×
4056
            } else {
4057
                file_collection fc;
10✔
4058

4059
                fc.fc_files.emplace_back(open_res.unwrap());
10✔
4060
                update_active_files(fc);
10✔
4061
            }
10✔
4062
        }
10✔
4063
    }
4064

4065
    if (!isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)
1,226✔
4066
        && !(lnav_data.ld_flags & LNF_HEADLESS))
1,226✔
4067
    {
4068
        if (dup2(STDOUT_FILENO, STDIN_FILENO) == -1) {
×
4069
            perror("cannot dup stdout to stdin");
×
4070
        }
4071
    }
4072

4073
    if (retval == EXIT_SUCCESS && lnav_data.ld_active_files.fc_files.empty()
612✔
4074
        && lnav_data.ld_active_files.fc_file_names.empty()
602✔
4075
        && lnav_data.ld_commands.empty()
62✔
4076
        && !(lnav_data.ld_show_help_view || mode_flags.mf_no_default))
1,226✔
4077
    {
4078
        lnav::console::print(
×
4079
            stderr,
4080
            lnav::console::user_message::error("nothing to do")
×
4081
                .with_reason("no files given or default files found")
×
4082
                .with_help(attr_line_t("use the ")
×
4083
                               .append_quoted(lnav::roles::keyword("-N"))
×
4084
                               .append(" option to open lnav without "
×
4085
                                       "loading any files")));
4086
        retval = EXIT_FAILURE;
×
4087
    }
4088

4089
    if (retval == EXIT_SUCCESS) {
614✔
4090
        isc::supervisor root_superv(injector::get<isc::service_list>());
612✔
4091

4092
        try {
4093
            char pcre2_version[128];
4094

4095
            pcre2_config(PCRE2_CONFIG_VERSION, pcre2_version);
612✔
4096
            log_info("startup: %s", VCS_PACKAGE_STRING);
612✔
4097
            log_host_info();
612✔
4098
            log_info("Libraries:");
612✔
4099
#ifdef HAVE_BZLIB_H
4100
            log_info("  bzip=%s", BZ2_bzlibVersion());
612✔
4101
#endif
4102
#ifdef HAVE_LIBCURL
4103
            log_info("  curl=%s (%s)", LIBCURL_VERSION, LIBCURL_TIMESTAMP);
612✔
4104
#endif
4105
#ifdef HAVE_ARCHIVE_H
4106
            log_info("  libarchive=%d", ARCHIVE_VERSION_NUMBER);
4107
            log_info("    details=%s", archive_version_details());
4108
#endif
4109
            log_info("  notcurses=%s", notcurses_version());
612✔
4110
            log_info("  pcre2=%s", pcre2_version);
612✔
4111
            log_info("  sqlite=%s", sqlite3_version);
612✔
4112
            log_info("  zlib=%s", zlibVersion());
612✔
4113
            log_info("lnav_data:");
612✔
4114
            log_info("  flags=%lx", lnav_data.ld_flags);
612✔
4115
            log_info("  commands:");
612✔
4116
            for (auto cmd_iter = lnav_data.ld_commands.begin();
612✔
4117
                 cmd_iter != lnav_data.ld_commands.end();
1,618✔
4118
                 ++cmd_iter)
1,006✔
4119
            {
4120
                log_info("    %s", cmd_iter->c_str());
1,006✔
4121
            }
4122
            log_info("  files:");
612✔
4123
            for (auto file_iter
612✔
4124
                 = lnav_data.ld_active_files.fc_file_names.begin();
612✔
4125
                 file_iter != lnav_data.ld_active_files.fc_file_names.end();
1,178✔
4126
                 ++file_iter)
566✔
4127
            {
4128
                log_info("    %s", file_iter->first.c_str());
566✔
4129
            }
4130

4131
            if (!(lnav_data.ld_flags & LNF_HEADLESS)
1,224✔
4132
                && verbosity == verbosity_t::quiet && load_stdin
2✔
4133
                && lnav_data.ld_active_files.fc_file_names.size() == 1)
614✔
4134
            {
4135
                log_info("pager mode, waiting for input to be consumed");
×
4136
                // give the pipers a chance to run to create the files to be
4137
                // scanned.
4138
                wait_for_pipers(ui_clock::now() + 10ms);
×
4139
                rescan_files(true);
×
4140
                // wait for the piper to actually finish running
4141
                wait_for_pipers(ui_clock::now() + 100ms);
×
4142
                auto rebuild_res = rebuild_indexes(ui_clock::now() + 15ms);
×
4143
                if (rebuild_res.rir_completed
×
4144
                    && lnav_data.ld_child_pollers.empty()
×
4145
                    && lnav_data.ld_active_files.active_pipers() == 0)
×
4146
                {
4147
                    log_info("  input fully consumed");
×
4148
                    rebuild_indexes_repeatedly();
×
4149
                    if (lnav_data.ld_active_files.fc_files.empty()
×
4150
                        || lnav_data.ld_active_files.fc_files[0]->size() < 24)
×
4151
                    {
4152
                        log_info("  input is smaller than screen, not paging");
×
4153
                        lnav_data.ld_flags |= LNF_HEADLESS;
×
4154
                        verbosity = verbosity_t::standard;
×
4155
                        lnav_data.ld_views[LNV_LOG].set_top(0_vl);
×
4156
                    }
4157
                    lnav_data.ld_views[LNV_TEXT].set_top(0_vl);
×
4158
                } else {
4159
                    log_info("  input not fully consumed");
×
4160
                }
4161
            }
4162

4163
            if (lnav_data.ld_flags & LNF_HEADLESS) {
612✔
4164
                std::vector<
4165
                    std::pair<Result<std::string, lnav::console::user_message>,
4166
                              std::string>>
4167
                    cmd_results;
610✔
4168
                textview_curses *log_tc, *text_tc, *tc;
4169
                bool output_view = true;
610✔
4170
                auto msg_cb_guard = lnav_data.ld_exec_context.add_msg_callback(
4171
                    [](const auto& um) {
×
4172
                        switch (um.um_level) {
114✔
4173
                            case lnav::console::user_message::level::error:
×
4174
                            case lnav::console::user_message::level::warning:
4175
                                lnav::console::println(stderr,
×
4176
                                                       um.to_attr_line());
4177
                                break;
×
4178
                            default:
114✔
4179
                                break;
114✔
4180
                        }
4181
                    });
724✔
4182

4183
                log_fos->fos_contexts.top().c_show_applicable_annotations
610✔
4184
                    = false;
610✔
4185

4186
                view_colors::init(nullptr);
610✔
4187
                rescan_files(true);
610✔
4188
                wait_for_pipers();
610✔
4189
                rescan_files(true);
610✔
4190
                rebuild_indexes_repeatedly();
610✔
4191
                {
4192
                    safe::WriteAccess<safe_name_to_stubs> errs(
4193
                        *lnav_data.ld_active_files.fc_name_to_stubs);
610✔
4194
                    if (!errs->empty()) {
610✔
4195
                        for (const auto& pair : *errs) {
×
4196
                            lnav::console::print(
×
4197
                                stderr,
4198
                                lnav::console::user_message::error(
×
4199
                                    attr_line_t("unable to open file: ")
×
4200
                                        .append(lnav::roles::file(pair.first)))
×
4201
                                    .with_reason(pair.second.fsi_description));
×
4202
                        }
4203

4204
                        return EXIT_FAILURE;
×
4205
                    }
4206
                }
610✔
4207
                init_session();
610✔
4208
                lnav_data.ld_exec_context.set_output("stdout", stdout, nullptr);
1,220✔
4209
                alerter::singleton().enabled(false);
610✔
4210

4211
                log_tc = &lnav_data.ld_views[LNV_LOG];
610✔
4212
                log_tc->set_height(24_vl);
610✔
4213
                lnav_data.ld_view_stack.push_back(log_tc);
610✔
4214
                // Read all of stdin
4215
                wait_for_pipers();
610✔
4216
                rebuild_indexes_repeatedly();
610✔
4217
                wait_for_children();
610✔
4218

4219
                log_tc->set_top(0_vl);
610✔
4220
                text_tc = &lnav_data.ld_views[LNV_TEXT];
610✔
4221
                if (text_tc->get_inner_height() > 0_vl
610✔
4222
                    && (!text_tc->get_selection().has_value()
1,297✔
4223
                        || lnav_data.ld_text_source.current_file()
764✔
4224
                               ->get_open_options()
77✔
4225
                               .loo_init_location
4226
                               .is<default_for_text_format>()))
77✔
4227
                {
4228
                    text_tc->set_selection(0_vl);
74✔
4229
                }
4230
                if (text_tc->get_selection().has_value()) {
610✔
4231
                    text_tc->set_height(
610✔
4232
                        vis_line_t(text_tc->get_inner_height()
×
4233
                                   - text_tc->get_selection().value()));
1,220✔
4234
                }
4235
                setup_highlights(lnav_data.ld_views[LNV_TEXT].get_highlights());
610✔
4236
                setup_initial_view_stack();
610✔
4237
                for (auto& tview : lnav_data.ld_views) {
6,100✔
4238
                    if (!tview.get_selection().has_value()) {
5,490✔
4239
                        tview.set_selection(0_vl);
1,220✔
4240
                    }
4241
                }
4242
                lnav_data.ld_text_source.tss_apply_default_init_location = true;
610✔
4243

4244
                log_info("Executing initial commands");
610✔
4245
                execute_init_commands(lnav_data.ld_exec_context, cmd_results);
610✔
4246
                run_cleanup_tasks();
610✔
4247
                wait_for_pipers();
610✔
4248
                rescan_files(true);
610✔
4249
                isc::to<curl_looper&, services::curl_streamer_t>()
×
4250
                    .send_and_wait(
610✔
4251
                        [](auto& clooper) { clooper.process_all(); });
610✔
4252
                rebuild_indexes_repeatedly();
610✔
4253
                wait_for_children();
610✔
4254
                {
4255
                    safe::WriteAccess<safe_name_to_stubs> errs(
4256
                        *lnav_data.ld_active_files.fc_name_to_stubs);
610✔
4257
                    if (!errs->empty()) {
610✔
4258
                        for (const auto& pair : *errs) {
×
4259
                            lnav::console::print(
×
4260
                                stderr,
4261
                                lnav::console::user_message::error(
×
4262
                                    attr_line_t("unable to open file: ")
×
4263
                                        .append(lnav::roles::file(pair.first)))
×
4264
                                    .with_reason(pair.second.fsi_description));
×
4265
                        }
4266

4267
                        return EXIT_FAILURE;
×
4268
                    }
4269
                }
610✔
4270

4271
                for (const auto& lf : lnav_data.ld_active_files.fc_files) {
1,217✔
4272
                    auto lf_notes = lf->get_notes();
607✔
4273
                    auto utf_note_opt
4274
                        = lf_notes.value_for(logfile::note_type::not_utf);
607✔
4275
                    if (utf_note_opt.has_value()) {
607✔
4276
                        lnav::console::print(stderr, *utf_note_opt.value());
×
4277
                    }
4278
                }
607✔
4279

4280
                for (auto& pair : cmd_results) {
1,609✔
4281
                    if (pair.first.isErr()) {
999✔
4282
                        lnav::console::print(stderr, pair.first.unwrapErr());
100✔
4283
                        output_view = false;
100✔
4284
                    } else {
4285
                        auto msg = pair.first.unwrap();
899✔
4286

4287
                        if (startswith(msg, "info:")) {
899✔
4288
                            if (verbosity == verbosity_t::verbose) {
292✔
4289
                                printf("%s\n", msg.c_str());
10✔
4290
                            }
4291
                        } else if (!msg.empty()) {
607✔
4292
                            printf("%s\n", msg.c_str());
10✔
4293
                            output_view = false;
10✔
4294
                        }
4295
                    }
899✔
4296
                }
4297

4298
                if (getenv("LNAV_EXTERNAL_URL")) {
610✔
4299
                    auto wakeup_pair = auto_pipe();
×
4300
                    wakeup_pair.open();
×
4301
                    wakeup_pair.read_end().non_blocking();
×
4302

4303
                    static auto& mlooper
4304
                        = injector::get<main_looper&, services::main_t>();
×
4305
                    mlooper.s_wakeup_fd = wakeup_pair.write_end().get();
×
4306
                    while (lnav_data.ld_looping) {
×
4307
                        mlooper.get_port().process_for(50ms);
×
4308
                        rescan_files();
×
4309
                        auto deadline = ui_clock::now() + 1s;
×
4310
                        wait_for_pipers(deadline);
×
4311
                        rebuild_indexes(deadline);
×
4312
                    }
4313
                }
4314

4315
                {
4316
                    auto& bg_service
4317
                        = injector::get<bg_looper&, services::background_t>();
610✔
4318

4319
                    root_superv.stop_child(bg_service.shared_from_this());
610✔
4320
                }
4321

4322
                {
4323
                    auto& pt = lnav::progress_tracker::get_tasks();
610✔
4324

4325
                    for (auto& bt : **pt.readAccess()) {
1,830✔
4326
                        auto tp = bt();
1,220✔
4327
                        if (tp.tp_messages.empty()) {
1,220✔
4328
                            continue;
1,218✔
4329
                        }
4330

4331
                        for (const auto& msg : tp.tp_messages) {
4✔
4332
                            lnav::console::print(stderr, msg);
2✔
4333
                            output_view = false;
2✔
4334
                        }
4335
                    }
1,220✔
4336
                }
4337

4338
                if (output_view && verbosity != verbosity_t::quiet
499✔
4339
                    && !lnav_data.ld_view_stack.empty()
493✔
4340
                    && !lnav_data.ld_stdout_used)
1,109✔
4341
                {
4342
                    bool suppress_empty_lines = false;
369✔
4343
                    unsigned long view_index;
4344
                    vis_line_t y;
369✔
4345

4346
                    tc = *lnav_data.ld_view_stack.top();
369✔
4347
                    // turn off scrollbar since some stuff will resize to
4348
                    // account for it.
4349
                    tc->set_show_scrollbar(false);
369✔
4350
                    view_index = tc - lnav_data.ld_views;
369✔
4351
                    switch (view_index) {
369✔
4352
                        case LNV_DB:
106✔
4353
                        case LNV_HISTOGRAM:
4354
                            suppress_empty_lines = true;
106✔
4355
                            break;
106✔
4356
                        default:
263✔
4357
                            break;
263✔
4358
                    }
4359

4360
                    auto* los = tc->get_overlay_source();
369✔
4361
                    attr_line_t ov_al;
738✔
4362
                    while (los != nullptr && tc->get_inner_height() > 0_vl
828✔
4363
                           && los->list_static_overlay(
940✔
4364
                               *tc,
4365
                               list_overlay_source::media_t::file,
4366
                               y,
4367
                               tc->get_inner_height(),
915✔
4368
                               ov_al))
4369
                    {
4370
                        write_line_to(stdout, ov_al);
112✔
4371
                        ov_al.clear();
112✔
4372
                        ++y;
112✔
4373
                    }
4374

4375
                    vis_line_t vl;
369✔
4376
                    for (vl = tc->get_top(); vl < tc->get_inner_height(); ++vl)
14,782✔
4377
                    {
4378
                        std::vector<attr_line_t> rows(1);
14,414✔
4379
                        tc->listview_value_for_rows(*tc, vl, rows);
14,414✔
4380
                        if (suppress_empty_lines && rows[0].empty()) {
14,414✔
4381
                            continue;
×
4382
                        }
4383

4384
                        write_line_to(stdout, rows[0]);
14,414✔
4385

4386
                        std::vector<attr_line_t> row_overlay_content;
14,413✔
4387
                        if (los != nullptr) {
14,413✔
4388
                            los->list_value_for_overlay(
8,176✔
4389
                                *tc, vl, row_overlay_content);
4390
                            for (const auto& ov_row : row_overlay_content) {
8,261✔
4391
                                write_line_to(stdout, ov_row);
85✔
4392
                            }
4393
                        }
4394
                    }
14,414✔
4395
                }
4396
            } else {
611✔
4397
                init_session();
2✔
4398

4399
                guard_termios gt(STDIN_FILENO);
2✔
4400
                lnav_log_orig_termios = gt.get_termios();
2✔
4401

4402
                looper();
2✔
4403

4404
                dup2(STDOUT_FILENO, STDERR_FILENO);
2✔
4405

4406
                signal(SIGINT, SIG_DFL);
2✔
4407
            }
2✔
4408

4409
            log_info("exiting main loop");
611✔
4410
        } catch (const std::system_error& e) {
1✔
4411
            if (e.code().value() != EPIPE) {
1✔
4412
                fprintf(stderr, "error: %s\n", e.what());
×
4413
            }
4414
        } catch (const line_buffer::error& e) {
1✔
4415
            auto um = lnav::console::user_message::error("internal error")
×
4416
                          .with_reason(strerror(e.e_err));
×
4417
            lnav::console::print(stderr, um);
×
4418
        } catch (const std::exception& e) {
×
4419
            auto um = lnav::console::user_message::error("internal error")
×
4420
                          .with_reason(e.what());
×
4421
            lnav::console::print(stderr, um);
×
4422
        }
×
4423

4424
        // When reading from stdin, tell the user where the capture
4425
        // file is stored so they can look at it later.
4426
        if (stdin_url && !(lnav_data.ld_flags & LNF_HEADLESS)
637✔
4427
            && verbosity != verbosity_t::quiet)
637✔
4428
        {
4429
            file_size_t stdin_size = 0;
×
4430
            for (const auto& ent :
×
4431
                 std::filesystem::directory_iterator(stdin_dir))
×
4432
            {
4433
                stdin_size += ent.file_size();
×
4434
            }
4435

4436
            lnav::console::print(
×
4437
                stderr,
4438
                lnav::console::user_message::info(
×
4439
                    attr_line_t()
×
4440
                        .append(lnav::roles::number(humanize::file_size(
×
4441
                            stdin_size, humanize::alignment::none)))
4442
                        .append(" of data from stdin was captured and "
×
4443
                                "will be saved for one day.  You can "
4444
                                "reopen it by running:\n")
4445
                        .appendf(FMT_STRING("   {} "),
×
4446
                                 lnav_data.ld_program_name)
4447
                        .append(lnav::roles::file(stdin_url.value()))));
×
4448
        }
4449
    }
612✔
4450

4451
    return retval;
614✔
4452
}
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