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

tstack / lnav / 22507085525-2793

27 Feb 2026 10:49PM UTC coverage: 68.948% (-0.02%) from 68.966%
22507085525-2793

push

github

tstack
fix mixup of O_CLOEXEC with FD_CLOEXEC

52007 of 75429 relevant lines covered (68.95%)

440500.48 hits per line

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

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

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

46
#include "config.h"
47

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

62
#include <sqlite3.h>
63

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

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

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

151
#include "curl_looper.hh"
152

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

277
template<>
278
void
279
force_linking(services::curl_streamer_t anno)
623✔
280
{
281
}
623✔
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)
626✔
292
{
293
}
626✔
294
}  // namespace injector
295

296
lnav_data_t lnav_data;
297

298
static auto nc_debug = false;
299

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

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

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

325
    db_key_names = DEFAULT_DB_KEY_NAMES;
512✔
326

327
    for (const auto& iter : *vtab_manager) {
46,069✔
328
        iter.second->get_foreign_keys(db_key_names);
45,557✔
329
    }
330

331
    return retval;
512✔
332
}
333

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

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

364
    return retval;
×
365
}
366

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

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

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

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

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

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

422
readline_context::command_map_t lnav_commands;
423

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

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

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

456
    attr_line_t ex2_term;
1✔
457

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

466
    attr_line_t ex3_term;
1✔
467

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

481
    attr_line_t usage_al;
1✔
482

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

800
    return true;
2✔
801
}
802

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

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

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

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

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

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

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

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

888
    return true;
×
889
}
890

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

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

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

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

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

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

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

937
    return retval;
2,903✔
938
}
939

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

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

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

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

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

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

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

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

1010
            alerter::singleton().new_input(ch);
×
1011

1012
            lnav_data.ld_input_dispatcher.new_input(
×
1013
                current_time, this->rsb_screen->get_notcurses(), ch);
×
1014

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

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

1025
            ncinput_free_paste_content(&ch);
×
1026

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

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

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

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

1082
        return retval;
13✔
1083
    }
1084

1085
    screen_curses* rsb_screen;
1086
    std::shared_ptr<top_status_source> rsb_top_source;
1087
};
1088

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

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

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

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

1163
        lnav_data.ld_exec_context.ec_msg_callback_stack.back()(um);
×
1164
    }
1165
}
1✔
1166

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

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

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

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

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

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

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

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

1243
    auto& ec = lnav_data.ld_exec_context;
2✔
1244
    sig_atomic_t overlay_counter = 0;
2✔
1245

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

1259
    auto& sb = lnav_data.ld_scroll_broadcaster;
2✔
1260
    auto& vsb = lnav_data.ld_view_stack_broadcaster;
2✔
1261

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

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

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

1335
        mouse_note.unwrap().execute();
×
1336
    }
1337

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

1358
    auto_fd errpipe[2];
12✔
1359
    auto_fd::pipe(errpipe);
2✔
1360

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

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

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

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

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

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

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

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

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

1450
    lnav_behavior lb;
2✔
1451

1452
    ui_periodic_timer::singleton();
2✔
1453

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

1460
    lnav_data.ld_window = sc.get_std_plane();
2✔
1461

1462
    view_colors::init(sc.get_notcurses());
2✔
1463

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

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

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

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

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

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

1528
        tc->tc_state_event_handler(*tc);
2✔
1529
    });
2✔
1530

1531
    vsb.push_back(sb);
2✔
1532

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

1550
                      auto op_guard = lnav_opid_guard::internal(op);
×
1551

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

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

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

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

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

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

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

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

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

1670
    lnav_data.ld_progress_view.set_window(lnav_data.ld_window);
2✔
1671
    lnav_data.ld_user_message_view.set_window(lnav_data.ld_window);
2✔
1672

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

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

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

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

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

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

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

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

1739
        execute_command(lnav_data.ld_exec_context, cmd);
×
1740
    };
2✔
1741

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

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

1788
    lnav_data.ld_user_message_view.set_show_bottom_border(true);
2✔
1789

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

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

1797
    sb(*lnav_data.ld_view_stack.top());
2✔
1798
    vsb(*lnav_data.ld_view_stack.top());
2✔
1799

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

1803
    {
1804
        auto& id = lnav_data.ld_input_dispatcher;
2✔
1805

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

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

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

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

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

1858
    auto& timer = ui_periodic_timer::singleton();
2✔
1859
    timeval current_time;
1860

1861
    static sig_atomic_t index_counter;
1862

1863
    timer.start_fade(index_counter, 1);
2✔
1864

1865
    std::future<file_collection> rescan_future;
2✔
1866

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

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

1884
    auto wakeup_pair = auto_pipe();
2✔
1885
    wakeup_pair.open();
2✔
1886
    wakeup_pair.read_end().non_blocking();
2✔
1887

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

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

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

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

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

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

1911
        layout_views();
10✔
1912

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

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

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

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

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

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

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

2002
        mlooper.get_port().process_for(0s);
10✔
2003

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

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

2128
                {
2129
                    hasher h;
9✔
2130

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

2150
                {
2151
                    hasher h;
9✔
2152

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

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

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

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

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

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

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

2274
        gettimeofday(&current_time, nullptr);
10✔
2275
        ui_now = ui_clock::now();
10✔
2276
        lb.tick(current_time);
10✔
2277

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

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

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

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

2311
                    alerter::singleton().new_input(nci);
10✔
2312

2313
                    lnav_data.ld_input_dispatcher.new_input(
10✔
2314
                        current_time, sc.get_notcurses(), nci);
2315

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

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

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

2362
            auto old_mode = lnav_data.ld_mode;
10✔
2363

2364
            ps->check_poll_set(pollfds);
10✔
2365
            lnav_data.ld_view_stack.top() | [](auto tc) { update_hits(tc); };
18✔
2366

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

2392
        if (prompt.p_editor.is_enabled()) {
10✔
2393
            prompt.p_editor.tick(ui_now);
×
2394
        }
2395

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

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

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

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

2435
            if (!cmd_results.empty()) {
2✔
2436
                auto& prompt = lnav::prompt::get();
1✔
2437
                auto last_cmd_result = cmd_results.back();
1✔
2438

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

2449
            if (!ran_cleanup) {
2✔
2450
                run_cleanup_tasks();
2✔
2451
                ran_cleanup = true;
2✔
2452
            }
2453

2454
            exec_phase.completed(lnav::phase_t::commands);
2✔
2455
            check_for_enough_colors(sc);
2✔
2456
        }
2✔
2457

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

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

2487
            if (!lnav_data.ld_view_stack.empty()) {
2✔
2488
                auto* top_view = lnav_data.ld_view_stack.top().value();
2✔
2489
                std::string alt_value;
2✔
2490

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

2506
        if (handle_winch(&sc)) {
10✔
2507
            got_user_input = true;
×
2508
            next_status_update_time = ui_now;
×
2509
            layout_views();
×
2510
        }
2511

2512
        if (lnav_data.ld_child_terminated) {
10✔
2513
            lnav_data.ld_child_terminated = false;
×
2514

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

2522
                rc = waitpid(*iter, &child_stat, WNOHANG);
×
2523
                if (rc == -1 || rc == 0) {
×
2524
                    continue;
×
2525
                }
2526

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

2541
            if (gather_pipers()) {
×
2542
                breadcrumb_view->set_needs_update();
×
2543
            }
2544

2545
            next_rescan_time = ui_clock::now();
×
2546
            next_rebuild_time = next_rescan_time;
×
2547
            next_status_update_time = next_rescan_time;
×
2548
        }
2549

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

2563
        if (lnav_data.ld_sigint_count > 0) {
10✔
2564
            auto found_piper = false;
×
2565

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

2571
                if (tc->get_inner_height() > 0_vl && sel) {
×
2572
                    std::vector<attr_line_t> rows(1);
×
2573

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

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

2586
                            if (!cp_name) {
×
2587
                                log_debug("no child_poller");
×
2588
                                continue;
×
2589
                            }
2590

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

2607
    if (rescan_future.valid()) {
2✔
2608
        rescan_future.get();
×
2609
    }
2610

2611
    save_session();
2✔
2612
}
10✔
2613

2614
void
2615
wait_for_children()
2,273✔
2616
{
2617
    std::vector<pollfd> pollfds;
2,273✔
2618
    auto to = timeval{0, 333000};
2,273✔
2619
    static auto* ps = injector::get<pollable_supervisor*>();
2,273✔
2620

2621
    for (auto iter = lnav_data.ld_children.begin();
2,273✔
2622
         iter != lnav_data.ld_children.end();
2,280✔
2623
         ++iter)
7✔
2624
    {
2625
        int rc, child_stat;
2626

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

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

2646
    do {
2647
        pollfds.clear();
2,290✔
2648

2649
        auto update_res = ps->update_poll_set(pollfds);
2,290✔
2650

2651
        if (update_res.ur_background == 0) {
2,290✔
2652
            break;
2,273✔
2653
        }
2654

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

2657
        if (rc < 0) {
17✔
2658
            switch (errno) {
×
2659
                case 0:
×
2660
                case EINTR:
2661
                    break;
×
2662
                default:
×
2663
                    return;
×
2664
            }
2665
        }
2666

2667
        ps->check_poll_set(pollfds);
17✔
2668
        lnav_data.ld_view_stack.top() | [](auto tc) { update_hits(tc); };
34✔
2669
    } while (lnav_data.ld_looping);
17✔
2670
}
2,273✔
2671

2672
struct mode_flags_t {
2673
    bool mf_check_configs{false};
2674
    bool mf_install{false};
2675
    bool mf_update_formats{false};
2676
    bool mf_no_default{false};
2677
    bool mf_print_warnings{false};
2678
};
2679

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

2687
    for (auto& iter : error_list) {
94✔
2688
        FILE* out_file;
2689

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

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

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

2735
    return retval;
14✔
2736
}
2737

2738
verbosity_t verbosity = verbosity_t::standard;
2739

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

2748
    auto exec_stdin = false;
769✔
2749
    auto load_stdin = false;
769✔
2750
    mode_flags_t mode_flags;
769✔
2751
    std::string since_time;
769✔
2752
    std::string until_time;
769✔
2753
    const char* LANG = getenv("LANG");
769✔
2754
    winsize term_size{};
769✔
2755

2756
    if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &term_size) != 0) {
769✔
2757
        term_size.ws_col = 80;
767✔
2758
        term_size.ws_row = 24;
767✔
2759
    }
2760

2761
    if (LANG == nullptr || strcmp(LANG, "C") == 0) {
769✔
2762
        setenv("LANG", "en_US.UTF-8", 1);
×
2763
    }
2764

2765
    {
2766
        const auto* TEMP = getenv("TEMP");
769✔
2767

2768
        if (TEMP != nullptr) {
769✔
2769
            setenv("TMPDIR", TEMP, 0);
×
2770
        } else {
2771
            setenv("TMPDIR",
769✔
2772
                   std::filesystem::temp_directory_path().string().c_str(),
1,538✔
2773
                   0);
2774
        }
2775
    }
2776

2777
    if (lnav::console::only_process_attached_to_win32_console()) {
769✔
2778
        mode_flags.mf_no_default = true;
×
2779
    }
2780

2781
    ec.ec_label_source_stack.push_back(&lnav_data.ld_db_row_source);
769✔
2782

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

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

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

2807
    try {
2808
        auto& safe_options_hier
2809
            = injector::get<lnav::safe_file_options_hier&>();
769✔
2810

2811
        auto opt_path = lnav::paths::dotlnav() / "file-options.json";
769✔
2812
        auto read_res = lnav::filesystem::read_file(opt_path);
769✔
2813
        auto curr_tz = date::get_tzdb().current_zone();
769✔
2814
        auto options_coll = lnav::file_options_collection{};
769✔
2815

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

2827
            options_coll = parse_res.unwrap();
13✔
2828
        }
13✔
2829

2830
        safe::WriteAccess<lnav::safe_file_options_hier> options_hier(
2831
            safe_options_hier);
769✔
2832

2833
        options_hier->foh_generation += 1;
769✔
2834
        auto_mem<char> var_path;
769✔
2835

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

2851
    lnav_data.ld_exec_context.ec_sql_callback = sql_callback;
769✔
2852
    lnav_data.ld_exec_context.ec_pipe_callback = pipe_callback;
769✔
2853

2854
    lnav_data.ld_program_name = argv[0];
769✔
2855
    add_ansi_vars(ec.ec_global_vars);
769✔
2856

2857
    lnav_data.ld_db_key_names = DEFAULT_DB_KEY_NAMES;
769✔
2858

2859
    auto dot_lnav_path = lnav::paths::dotlnav();
769✔
2860
    std::error_code last_write_ec;
769✔
2861
    lnav_data.ld_last_dot_lnav_time
2862
        = std::filesystem::last_write_time(dot_lnav_path, last_write_ec);
769✔
2863

2864
    ensure_dotlnav();
769✔
2865

2866
    log_install_handlers();
769✔
2867
    sql_install_logger();
769✔
2868

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

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

2892
    {
2893
        int register_collation_functions(sqlite3 * db);
2894

2895
        register_sqlite_funcs(lnav_data.ld_db.in(), sqlite_registration_funcs);
769✔
2896
        register_collation_functions(lnav_data.ld_db.in());
769✔
2897
    }
2898

2899
    register_environ_vtab(lnav_data.ld_db.in());
769✔
2900
    register_static_file_vtab(lnav_data.ld_db.in());
769✔
2901
    {
2902
        static auto vtab_modules
2903
            = injector::get<std::vector<std::shared_ptr<vtab_module_base>>>();
769✔
2904

2905
        for (const auto& mod : vtab_modules) {
7,690✔
2906
            mod->create(lnav_data.ld_db.in());
6,921✔
2907
        }
2908
    }
2909

2910
    register_views_vtab(lnav_data.ld_db.in());
769✔
2911
    register_regexp_vtab(lnav_data.ld_db.in());
769✔
2912
    register_xpath_vtab(lnav_data.ld_db.in());
769✔
2913
    register_fstat_vtab(lnav_data.ld_db.in());
769✔
2914
    lnav::events::register_events_tab(lnav_data.ld_db.in());
769✔
2915
    register_log_stmt_vtab(lnav_data.ld_db.in());
769✔
2916

2917
#ifdef HAVE_RUST_DEPS
2918
    {
2919
        lnav_rs_ext::init_ext();
769✔
2920
    }
2921
#endif
2922

2923
    auto log_fos = std::make_unique<field_overlay_source>(
2924
        lnav_data.ld_log_source, lnav_data.ld_text_source);
769✔
2925

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

2933
        auto op_guard = lnav_opid_guard::once("cleanup");
769✔
2934

2935
        log_info("performing cleanup");
769✔
2936

2937
#ifdef HAVE_RUST_DEPS
2938
        lnav_rs_ext::stop_ext_access();
769✔
2939
#endif
2940

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

2963
        if (lnav_data.ld_spectro_source != nullptr) {
769✔
2964
            delete std::exchange(lnav_data.ld_spectro_source->ss_value_source,
645✔
2965
                                 nullptr);
1,290✔
2966
        }
2967

2968
        lnav_data.ld_child_pollers.clear();
769✔
2969

2970
        delete lnav_data.ld_views[LNV_SCHEMA].get_sub_source();
769✔
2971
        delete lnav_data.ld_views[LNV_PRETTY].get_sub_source();
769✔
2972
        for (auto& tc : lnav_data.ld_views) {
7,690✔
2973
            tc.deinit();
6,921✔
2974
        }
2975

2976
        log_info("marking files as closed");
769✔
2977
        for (auto& lf : lnav_data.ld_active_files.fc_files) {
1,392✔
2978
            lf->close();
623✔
2979
        }
2980
        log_info("rebuilding after closures");
769✔
2981
        rebuild_indexes(ui_clock::now());
769✔
2982
        log_info("clearing file collection");
769✔
2983
        lnav_data.ld_active_files.clear();
769✔
2984

2985
        log_info("dropping tables");
769✔
2986
        vtab_man_life = {};
769✔
2987

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

2997
                stmt.for_each_row<std::string>(
769✔
2998
                    [&tables_to_drop](auto table_name) {
×
2999
                        tables_to_drop.emplace_back(fmt::format(
×
3000
                            FMT_STRING("DROP TABLE {}"), table_name));
×
3001
                        return false;
×
3002
                    });
3003
            }
769✔
3004
        }
769✔
3005

3006
        // XXX
3007
        lnav_data.ld_log_source.set_preview_sql_filter(nullptr);
769✔
3008
        lnav_data.ld_log_source.set_sql_filter("", nullptr);
1,538✔
3009
        lnav_data.ld_log_source.set_sql_marker("", nullptr);
769✔
3010
        lnav_config_listener::unload_all();
769✔
3011

3012
        {
3013
            sqlite3_stmt* stmt_iter = nullptr;
769✔
3014

3015
            do {
3016
                stmt_iter = sqlite3_next_stmt(lnav_data.ld_db.in(), stmt_iter);
769✔
3017
                if (stmt_iter != nullptr) {
769✔
3018
                    const auto* stmt_sql = sqlite3_sql(stmt_iter);
×
3019

3020
                    log_warning("unfinalized SQL statement: %s", stmt_sql);
×
3021
                    ensure(false);
×
3022
                }
3023
            } while (stmt_iter != nullptr);
769✔
3024
        }
3025

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

3035
            auto stmt = prep_res.unwrap();
×
3036
            stmt.execute();
×
3037
        }
3038
#if defined(HAVE_SQLITE3_DROP_MODULES)
3039
        sqlite3_drop_modules(lnav_data.ld_db.in(), nullptr);
769✔
3040
#endif
3041

3042
        lnav_data.ld_db.reset();
769✔
3043

3044
        log_info("waiting for cleanup tasks");
769✔
3045
        while (!CLEANUP_TASKS.empty()) {
3,894✔
3046
            auto& [name, task] = CLEANUP_TASKS.back();
3,125✔
3047
            if (task.wait_for(10ms) != std::future_status::ready) {
3,125✔
3048
                log_warning("cleanup task '%s' is not yet ready", name);
×
3049
            }
3050
            CLEANUP_TASKS.pop_back();
3,125✔
3051
        }
3052

3053
        log_info("cleanup finished");
769✔
3054
    });
1,538✔
3055

3056
#ifdef HAVE_LIBCURL
3057
    curl_global_init(CURL_GLOBAL_DEFAULT);
769✔
3058
#endif
3059

3060
    static const std::string DEFAULT_DEBUG_LOG = "/dev/null";
2,307✔
3061

3062
    // lnav_data.ld_debug_log_name = DEFAULT_DEBUG_LOG;
3063

3064
    std::vector<std::string> file_args;
769✔
3065
    std::vector<lnav::console::user_message> arg_errors;
769✔
3066

3067
    CLI::App app{"The Logfile Navigator"};
3,076✔
3068

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

3086
            return std::string();
53✔
3087
        })
3088
        ->allow_extra_args(false);
7,690✔
3089
    app.add_flag("-q{0},-v{2}", verbosity, "Control the verbosity");
3,076✔
3090
    app.set_version_flag("-V,--version");
3,845✔
3091
    app.footer(fmt::format(FMT_STRING("Version: {}"), VCS_PACKAGE_STRING));
3,076✔
3092

3093
    std::shared_ptr<lnav::management::operations> mmode_ops;
769✔
3094

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

3118
        app.add_option("-S,--since", since_time, "since");
3,036✔
3119
        app.add_option("-U,--until", until_time, "until");
3,036✔
3120

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

3130
        auto cmd_appender
3131
            = [](std::string cmd) { lnav_data.ld_commands.emplace_back(cmd); };
1,013✔
3132
        auto cmd_validator = [&arg_errors](std::string cmd) -> std::string {
1,014✔
3133
            static const auto ARG_SRC
3134
                = intern_string::lookup("command-line argument");
2,090✔
3135

3136
            if (cmd.empty()) {
1,014✔
3137
                return "empty commands are not allowed";
×
3138
            }
3139

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

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

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

3193
        install_flag->needs(file_opt);
759✔
3194
        install_flag->excludes(no_default_flag,
759✔
3195
                               as_log_flag,
3196
                               rotated_flag,
3197
                               recurse_flag,
3198
                               headless_flag,
3199
                               cmd_opt,
3200
                               exec_file_opt,
3201
                               cmdline_opt);
3202
    }
3203

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

3230
        lnav::console::print(
×
3231
            stderr,
3232
            lnav::console::user_message::error("invalid command-line arguments")
×
3233
                .with_reason(e.what()));
×
3234
        return e.get_exit_code();
×
3235
    }
1✔
3236

3237
    lnav_data.ld_config_paths.insert(lnav_data.ld_config_paths.begin(),
651✔
3238
                                     lnav::paths::dotlnav());
1,302✔
3239
    lnav_data.ld_config_paths.insert(lnav_data.ld_config_paths.begin(),
651✔
3240
                                     SYSCONFDIR "/lnav");
3241
    lnav_data.ld_config_paths.insert(lnav_data.ld_config_paths.begin(),
651✔
3242
                                     "/etc/lnav");
3243

3244
    if (!lnav_data.ld_debug_log_name.empty()
651✔
3245
        && lnav_data.ld_debug_log_name != DEFAULT_DEBUG_LOG)
651✔
3246
    {
3247
        lnav_log_level = lnav_log_level_t::TRACE;
46✔
3248
    }
3249

3250
    if (!lnav_data.ld_debug_log_name.empty()) {
651✔
3251
        lnav_log_file = make_optional_from_nullable(
46✔
3252
            fopen(lnav_data.ld_debug_log_name.c_str(), "ae"));
46✔
3253
        lnav_log_file | [](auto* file) {
46✔
3254
            fcntl(fileno(file), F_SETFD, FD_CLOEXEC);
46✔
3255
            log_write_ring_to(fileno(file));
46✔
3256
        };
46✔
3257
    }
3258
    log_info("lnav started %d", lnav_log_file.has_value());
651✔
3259
    for (int argi = 0; argi < argc; argi++) {
4,808✔
3260
        log_info("  arg[%d] = %s", argi, argv[argi]);
4,157✔
3261
    }
3262

3263
    {
3264
        static auto builtin_formats
3265
            = injector::get<std::vector<std::shared_ptr<log_format>>>();
651✔
3266
        auto& root_formats = log_format::get_root_formats();
651✔
3267

3268
        log_format::get_root_formats().insert(root_formats.begin(),
651✔
3269
                                              builtin_formats.begin(),
3270
                                              builtin_formats.end());
3271
        builtin_formats.clear();
651✔
3272
    }
3273

3274
    load_config(lnav_data.ld_config_paths, config_errors);
651✔
3275

3276
    if (!config_errors.empty()) {
651✔
3277
        if (print_user_msgs(config_errors, mode_flags) != EXIT_SUCCESS) {
1✔
3278
            return EXIT_FAILURE;
1✔
3279
        }
3280
    }
3281
    add_global_vars(ec);
650✔
3282

3283
    if (mode_flags.mf_update_formats) {
650✔
3284
        if (!update_installs_from_git()) {
×
3285
            return EXIT_FAILURE;
×
3286
        }
3287
        return EXIT_SUCCESS;
×
3288
    }
3289

3290
    if (mode_flags.mf_install) {
650✔
3291
        auto formats_installed_path
3292
            = lnav::paths::dotlnav() / "formats/installed";
5✔
3293
        auto configs_installed_path
3294
            = lnav::paths::dotlnav() / "configs/installed";
5✔
3295

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

3318
            lnav::console::print(stderr,
×
3319
                                 lnav::console::user_message::error(
×
3320
                                     "missing format files to install")
3321
                                     .with_reason(install_reason)
×
3322
                                     .with_help(install_help));
×
3323
            return EXIT_FAILURE;
×
3324
        }
3325

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

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

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

3360
                lnav::console::print(
×
3361
                    stderr,
3362
                    lnav::console::user_message::ok(
×
3363
                        attr_line_t("installed -- ")
×
3364
                            .append(lnav::roles::file(dst_path))));
×
3365
                continue;
×
3366
            }
3367

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

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

3394
                lnav::console::print(
×
3395
                    stderr,
3396
                    lnav::console::user_message::ok(
×
3397
                        attr_line_t("installed -- ")
×
3398
                            .append(lnav::roles::file(dst_path))));
×
3399
                continue;
×
3400
            }
3401

3402
            if (file_path == "extra") {
5✔
3403
                install_extra_formats();
1✔
3404
                continue;
1✔
3405
            }
3406

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

3419
            auto src_path = std::filesystem::path(file_path);
3✔
3420
            std::filesystem::path dst_name;
3✔
3421
            if (file_type == config_file_type::CONFIG) {
3✔
3422
                dst_name = src_path.filename();
×
3423
            } else {
3424
                auto format_list = load_format_file(src_path, loader_errors);
3✔
3425

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

3444
                dst_name = format_list[0].to_string() + ".json";
3✔
3445
            }
3✔
3446
            auto dst_path = (file_type == config_file_type::CONFIG
3447
                                 ? configs_installed_path
3448
                                 : formats_installed_path)
3449
                / dst_name;
3✔
3450

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

3459
                lnav::console::print(stderr, um);
×
3460
                return EXIT_FAILURE;
×
3461
            }
3462

3463
            auto file_content = read_res.unwrap();
3✔
3464

3465
            auto read_dst_res = lnav::filesystem::read_file(dst_path);
3✔
3466
            if (read_dst_res.isOk()) {
3✔
3467
                auto dst_content = read_dst_res.unwrap();
2✔
3468

3469
                if (dst_content == file_content) {
2✔
3470
                    auto um = lnav::console::user_message::info(
3471
                        attr_line_t("file is already installed at -- ")
1✔
3472
                            .append(lnav::roles::file(dst_path)));
1✔
3473

3474
                    lnav::console::print(stdout, um);
1✔
3475

3476
                    return EXIT_SUCCESS;
1✔
3477
                }
1✔
3478
            }
2✔
3479

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

3491
                lnav::console::print(stderr, um);
×
3492
                return EXIT_FAILURE;
×
3493
            }
3494

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

3509
            lnav::console::print(stdout, um);
2✔
3510
        }
10✔
3511
        return EXIT_SUCCESS;
3✔
3512
    }
5✔
3513

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

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

3532
    auto* vtab_manager = injector::get<log_vtab_manager*>();
645✔
3533

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

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

3632
#if 0
3633
    auto overlay_menu = std::make_shared<text_overlay_menu>();
3634
    lnav_data.ld_file_details_view.set_overlay_source(overlay_menu.get());
3635
#endif
3636

3637
    for (int lpc = 0; lpc < LNV__MAX; lpc++) {
6,450✔
3638
        lnav_data.ld_views[lpc].set_gutter_source(new log_gutter_source());
5,805✔
3639
    }
3640

3641
    {
3642
        auto& hs = lnav_data.ld_hist_source2;
645✔
3643

3644
        lnav_data.ld_log_source.set_index_delegate(new hist_index_delegate(
645✔
3645
            lnav_data.ld_hist_source2, lnav_data.ld_views[LNV_HISTOGRAM]));
645✔
3646
        hs.init();
645✔
3647
        lnav_data.ld_zoom_level = 3;
645✔
3648
        hs.set_time_slice(ZOOM_LEVELS[lnav_data.ld_zoom_level]);
645✔
3649
    }
3650

3651
    for (int lpc = 0; lpc < LNV__MAX; lpc++) {
6,450✔
3652
        lnav_data.ld_views[lpc].set_title(lnav_view_titles[lpc]);
17,415✔
3653
    }
3654

3655
    load_formats(lnav_data.ld_config_paths, loader_errors);
645✔
3656

3657
    {
3658
        auto_mem<char, sqlite3_free> errmsg;
645✔
3659
        auto init_sql_str = init_sql.to_string_fragment_producer()->to_string();
645✔
3660

3661
        if (sqlite3_exec(lnav_data.ld_db.in(),
1,290✔
3662
                         init_sql_str.data(),
645✔
3663
                         nullptr,
3664
                         nullptr,
3665
                         errmsg.out())
3666
            != SQLITE_OK)
645✔
3667
        {
3668
            fprintf(stderr,
×
3669
                    "error: unable to execute DB init -- %s\n",
3670
                    errmsg.in());
3671
        }
3672
    }
645✔
3673

3674
    {
3675
        auto op_guard = lnav_opid_guard::once("register_vtab");
645✔
3676

3677
        vtab_manager->register_vtab(std::make_shared<all_logs_vtab>());
645✔
3678
        vtab_manager->register_vtab(std::make_shared<log_format_vtab_impl>(
645✔
3679
            log_format::find_root_format("generic_log")));
1,290✔
3680
        vtab_manager->register_vtab(std::make_shared<log_format_vtab_impl>(
645✔
3681
            log_format::find_root_format("lnav_piper_log")));
1,290✔
3682

3683
        for (auto& iter : log_format::get_root_formats()) {
48,935✔
3684
            auto lvi = iter->get_vtab_impl();
48,290✔
3685

3686
            if (lvi != nullptr) {
48,290✔
3687
                vtab_manager->register_vtab(lvi);
45,710✔
3688
            }
3689
        }
48,290✔
3690

3691
        load_format_extra(lnav_data.ld_db.in(),
645✔
3692
                          ec.ec_global_vars,
645✔
3693
                          lnav_data.ld_config_paths,
3694
                          loader_errors);
3695
        load_format_vtabs(vtab_manager, loader_errors);
645✔
3696

3697
        if (!loader_errors.empty()) {
645✔
3698
            if (print_user_msgs(loader_errors, mode_flags) != EXIT_SUCCESS) {
2✔
3699
                if (mmode_ops == nullptr) {
2✔
3700
                    return EXIT_FAILURE;
2✔
3701
                }
3702
            }
3703
        }
3704
    }
645✔
3705

3706
    if (mmode_ops) {
643✔
3707
        isc::supervisor root_superv(injector::get<isc::service_list>());
10✔
3708
        auto perform_res = lnav::management::perform(mmode_ops);
10✔
3709

3710
        return print_user_msgs(perform_res, mode_flags);
10✔
3711
    }
10✔
3712

3713
    if (!mode_flags.mf_check_configs && !lnav_data.ld_show_help_view) {
633✔
3714
        DEFAULT_FILES.emplace_back("var/log/messages");
632✔
3715
        DEFAULT_FILES.emplace_back("var/log/system.log");
632✔
3716
        DEFAULT_FILES.emplace_back("var/log/syslog");
632✔
3717
        DEFAULT_FILES.emplace_back("var/log/syslog.log");
632✔
3718
    }
3719

3720
    init_lnav_commands(lnav_commands);
633✔
3721
    init_lnav_bookmark_commands(lnav_commands);
633✔
3722
    init_lnav_breakpoint_commands(lnav_commands);
633✔
3723
    init_lnav_display_commands(lnav_commands);
633✔
3724
    init_lnav_filtering_commands(lnav_commands);
633✔
3725
    init_lnav_io_commands(lnav_commands);
633✔
3726
    init_lnav_metadata_commands(lnav_commands);
633✔
3727
    init_lnav_scripting_commands(lnav_commands);
633✔
3728

3729
    lnav_data.ld_looping = true;
633✔
3730
    set_view_mode(ln_mode_t::PAGING);
633✔
3731

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

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

3787
    if ((isatty(STDIN_FILENO) || is_dev_null(STDIN_FILENO)) && file_args.empty()
1,260✔
3788
        && lnav_data.ld_active_files.fc_file_names.empty()
1✔
3789
        && !mode_flags.mf_no_default)
1,260✔
3790
    {
3791
        char start_dir[FILENAME_MAX];
3792

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

3803
            if (chdir(start_dir) == -1) {
×
3804
                perror("chdir(start_dir)");
×
3805
            }
3806
        }
3807
    }
3808

3809
    {
3810
        const auto internals_dir_opt = getenv_opt("DUMP_INTERNALS_DIR");
630✔
3811

3812
        if (internals_dir_opt) {
630✔
3813
            lnav::dump_internals(internals_dir_opt.value());
2✔
3814

3815
            return EXIT_SUCCESS;
2✔
3816
        }
3817
    }
3818

3819
    if (file_args.empty() && !mode_flags.mf_no_default) {
628✔
3820
        load_stdin = true;
35✔
3821
    }
3822

3823
    for (const auto& file_path_str : file_args) {
1,189✔
3824
        auto [file_path_without_trailer, file_loc]
561✔
3825
            = lnav::filesystem::split_file_location(file_path_str);
561✔
3826
        auto_mem<char> abspath;
561✔
3827
        struct stat st;
3828

3829
        auto file_path = std::filesystem::path(
3830
            stat(file_path_without_trailer.c_str(), &st) == 0
561✔
3831
                ? file_path_without_trailer
3832
                : file_path_str);
561✔
3833

3834
        auto file_path_type
3835
            = lnav::filesystem::determine_path_type(file_path.string());
561✔
3836

3837
        if (file_path_str == "-") {
561✔
3838
            load_stdin = true;
×
3839
        }
3840
#ifdef HAVE_LIBCURL
3841
        else if (file_path_type == lnav::filesystem::path_type::url)
561✔
3842
        {
3843
            auto ul = std::make_shared<url_loader>(file_path_str);
×
3844

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

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

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

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

3936
    if (mode_flags.mf_check_configs) {
628✔
3937
        isc::supervisor root_superv(injector::get<isc::service_list>());
1✔
3938
        rescan_files(true);
1✔
3939
        for (auto& lf : lnav_data.ld_active_files.fc_files) {
2✔
3940
            logfile::rebuild_result_t rebuild_result;
3941

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

3961
                size_t partial_len;
3962

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

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

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

4022
    std::optional<std::string> stdin_url;
627✔
4023
    std::filesystem::path stdin_dir;
627✔
4024
    if (load_stdin && !isatty(STDIN_FILENO) && !is_dev_null(STDIN_FILENO)
35✔
4025
        && !exec_stdin)
662✔
4026
    {
4027
        static const std::string STDIN_NAME = "stdin";
105✔
4028
        struct stat stdin_st;
4029

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

4041
            log_info("waiting for stdin FIFO");
25✔
4042
            pfd[0].fd = STDIN_FILENO;
25✔
4043
            pfd[0].events = POLLIN;
25✔
4044
            pfd[0].revents = 0;
25✔
4045
            auto prc = poll(pfd, 1, 0);
25✔
4046

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

4076
            auto open_res
4077
                = logfile::open(STDIN_NAME, loo, auto_fd::dup_of(STDIN_FILENO));
10✔
4078

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

4088
                fc.fc_files.emplace_back(open_res.unwrap());
10✔
4089
                update_active_files(fc);
10✔
4090
            }
10✔
4091
        }
10✔
4092
    }
4093

4094
    if (!isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)
1,252✔
4095
        && !lnav_data.ld_flags.is_set<lnav_flags::headless>())
1,252✔
4096
    {
4097
        if (dup2(STDOUT_FILENO, STDIN_FILENO) == -1) {
×
4098
            perror("cannot dup stdout to stdin");
×
4099
        }
4100
    }
4101

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

4118
    if (retval == EXIT_SUCCESS) {
627✔
4119
        isc::supervisor root_superv(injector::get<isc::service_list>());
625✔
4120

4121
        try {
4122
            char pcre2_version[128];
4123

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

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

4179
                    auto max_height = lnav::math::vmax(
×
4180
                        lnav_data.ld_log_source.text_line_count(),
4181
                        lnav_data.ld_text_source.text_line_count());
4182

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

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

4215
                log_fos->fos_contexts.top().c_show_applicable_annotations
623✔
4216
                    = false;
623✔
4217

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

4236
                        return EXIT_FAILURE;
×
4237
                    }
4238
                }
623✔
4239
                init_session();
623✔
4240
                lnav_data.ld_exec_context.set_output("stdout", stdout, nullptr);
1,246✔
4241
                alerter::singleton().enabled(false);
623✔
4242

4243
                auto* log_tc = &lnav_data.ld_views[LNV_LOG];
623✔
4244
                log_tc->set_height(24_vl);
623✔
4245
                lnav_data.ld_view_stack.push_back(log_tc);
623✔
4246
                // Read all of stdin
4247
                wait_for_pipers();
623✔
4248
                rebuild_indexes_repeatedly();
623✔
4249
                wait_for_children();
623✔
4250

4251
                log_tc->set_top(0_vl);
623✔
4252
                auto* text_tc = &lnav_data.ld_views[LNV_TEXT];
623✔
4253
                if (text_tc->get_inner_height() > 0_vl
623✔
4254
                    && (!text_tc->get_selection().has_value()
1,325✔
4255
                        || lnav_data.ld_text_source.current_file()
781✔
4256
                               ->get_open_options()
79✔
4257
                               .loo_init_location
4258
                               .is<default_for_text_format>()))
79✔
4259
                {
4260
                    text_tc->set_selection(0_vl);
76✔
4261
                }
4262
                if (text_tc->get_selection().has_value()) {
623✔
4263
                    text_tc->set_height(
623✔
4264
                        vis_line_t(text_tc->get_inner_height()
×
4265
                                   - text_tc->get_selection().value()));
1,246✔
4266
                }
4267
                setup_highlights(lnav_data.ld_views[LNV_TEXT].get_highlights());
623✔
4268
                setup_initial_view_stack();
623✔
4269
                for (auto& tview : lnav_data.ld_views) {
6,230✔
4270
                    if (!tview.get_selection().has_value()) {
5,607✔
4271
                        tview.set_selection(0_vl);
1,246✔
4272
                    }
4273
                }
4274

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

4298
                        return EXIT_FAILURE;
×
4299
                    }
4300
                }
623✔
4301

4302
                for (const auto& lf : lnav_data.ld_active_files.fc_files) {
1,244✔
4303
                    auto lf_notes = lf->get_notes();
621✔
4304
                    auto utf_note_opt
4305
                        = lf_notes.value_for(logfile::note_type::not_utf);
621✔
4306
                    if (utf_note_opt.has_value()) {
621✔
4307
                        lnav::console::print(stderr, *utf_note_opt.value());
×
4308
                    }
4309
                }
621✔
4310

4311
                for (const auto& pair : cmd_results) {
1,643✔
4312
                    if (pair.first.isErr()) {
1,020✔
4313
                        lnav::console::print(stderr, pair.first.unwrapErr());
102✔
4314
                        output_view = false;
102✔
4315
                    } else {
4316
                        auto msg = pair.first.unwrap();
918✔
4317

4318
                        if (startswith(msg, "info:")) {
918✔
4319
                            if (verbosity == verbosity_t::verbose) {
296✔
4320
                                printf("%s\n", msg.c_str());
10✔
4321
                            }
4322
                        } else if (!msg.empty()) {
622✔
4323
                            printf("%s\n", msg.c_str());
10✔
4324
                            output_view = false;
10✔
4325
                        }
4326
                    }
918✔
4327
                }
4328

4329
                if (getenv("LNAV_EXTERNAL_URL")) {
623✔
4330
                    auto wakeup_pair = auto_pipe();
×
4331
                    wakeup_pair.open();
×
4332
                    wakeup_pair.read_end().non_blocking();
×
4333

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

4346
                {
4347
                    auto& bg_service
4348
                        = injector::get<bg_looper&, services::background_t>();
623✔
4349

4350
                    root_superv.stop_child(bg_service.shared_from_this());
623✔
4351
                }
4352

4353
                {
4354
                    auto& pt = lnav::progress_tracker::get_tasks();
623✔
4355

4356
                    for (auto& bt : **pt.readAccess()) {
1,869✔
4357
                        auto tp = bt();
1,246✔
4358
                        if (tp.tp_messages.empty()) {
1,246✔
4359
                            continue;
1,244✔
4360
                        }
4361

4362
                        for (const auto& msg : tp.tp_messages) {
4✔
4363
                            lnav::console::print(stderr, msg);
2✔
4364
                            output_view = false;
2✔
4365
                        }
4366
                    }
1,246✔
4367
                }
4368

4369
                if (output_view && verbosity != verbosity_t::quiet
510✔
4370
                    && !lnav_data.ld_view_stack.empty()
503✔
4371
                    && !lnav_data.ld_stdout_used)
1,133✔
4372
                {
4373
                    bool suppress_empty_lines = false;
379✔
4374
                    unsigned long view_index;
4375
                    vis_line_t y;
379✔
4376

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

4391
                    auto* los = tc->get_overlay_source();
379✔
4392
                    attr_line_t ov_al;
758✔
4393
                    while (los != nullptr && tc->get_inner_height() > 0_vl
846✔
4394
                           && los->list_static_overlay(
956✔
4395
                               *tc,
4396
                               list_overlay_source::media_t::file,
4397
                               y,
4398
                               tc->get_inner_height(),
932✔
4399
                               ov_al))
4400
                    {
4401
                        write_line_to(stdout, ov_al);
110✔
4402
                        ov_al.clear();
110✔
4403
                        ++y;
110✔
4404
                    }
4405

4406
                    vis_line_t vl;
379✔
4407
                    for (vl = tc->get_top(); vl < tc->get_inner_height(); ++vl)
13,579✔
4408
                    {
4409
                        std::vector<attr_line_t> rows(1);
13,201✔
4410
                        tc->listview_value_for_rows(*tc, vl, rows);
13,201✔
4411
                        if (suppress_empty_lines && rows[0].empty()) {
13,201✔
4412
                            continue;
×
4413
                        }
4414

4415
                        write_line_to(stdout, rows[0]);
13,201✔
4416

4417
                        std::vector<attr_line_t> row_overlay_content;
13,200✔
4418
                        if (los != nullptr) {
13,200✔
4419
                            los->list_value_for_overlay(
6,928✔
4420
                                *tc, vl, row_overlay_content);
4421
                            if (!row_overlay_content.empty()) {
6,928✔
4422
                                auto hdr_opt = los->list_header_for_overlay(
27✔
4423
                                    *tc,
4424
                                    list_overlay_source::media_t::file,
4425
                                    vl);
27✔
4426
                                if (hdr_opt) {
27✔
4427
                                    auto hdr = hdr_opt.value();
25✔
4428
                                    hdr.with_attr_for_all(VC_STYLE.value(
25✔
4429
                                        text_attrs::with_underline()));
25✔
4430
                                    write_line_to(stdout, hdr);
25✔
4431
                                }
25✔
4432
                            }
27✔
4433
                            for (const auto& ov_row : row_overlay_content) {
7,035✔
4434
                                write_line_to(stdout, ov_row);
107✔
4435
                            }
4436
                        }
4437
                    }
13,201✔
4438
                }
4439
            } else {
624✔
4440
                init_session();
2✔
4441

4442
                guard_termios gt(STDIN_FILENO);
2✔
4443
                lnav_log_orig_termios = gt.get_termios();
2✔
4444

4445
                looper();
2✔
4446

4447
                dup2(STDOUT_FILENO, STDERR_FILENO);
2✔
4448

4449
                signal(SIGINT, SIG_DFL);
2✔
4450
            }
2✔
4451

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

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

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

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

4499
    return retval;
627✔
4500
}
769✔
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