diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 17:44:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 17:44:55 +0000 |
commit | 5068d34c08f951a7ea6257d305a1627b09a95817 (patch) | |
tree | 08213e2be853396a3b07ce15dbe222644dcd9a89 /src/lnav.cc | |
parent | Initial commit. (diff) | |
download | lnav-upstream.tar.xz lnav-upstream.zip |
Adding upstream version 0.11.1.upstream/0.11.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | src/lnav.cc | 3325 |
1 files changed, 3325 insertions, 0 deletions
diff --git a/src/lnav.cc b/src/lnav.cc new file mode 100644 index 0000000..5cfd31f --- /dev/null +++ b/src/lnav.cc @@ -0,0 +1,3325 @@ +/** + * Copyright (c) 2007-2016, Timothy Stack + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Timothy Stack nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @file lnav.cc + * + * XXX This file has become a dumping ground for code and needs to be broken up + * a bit. + */ + +#ifdef __CYGWIN__ +# include <alloca.h> +#endif + +#include <errno.h> +#include <fcntl.h> +#include <glob.h> +#include <locale.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <termios.h> +#include <time.h> +#include <unistd.h> + +#include "config.h" + +#if defined(__OpenBSD__) && defined(__clang__) \ + && !defined(_WCHAR_H_CPLUSPLUS_98_CONFORMANCE_) +# define _WCHAR_H_CPLUSPLUS_98_CONFORMANCE_ +#endif +#include <algorithm> +#include <functional> +#include <map> +#include <memory> +#include <set> +#include <utility> +#include <vector> + +#include <sqlite3.h> + +#ifdef HAVE_BZLIB_H +# include <bzlib.h> +#endif + +#include "all_logs_vtab.hh" +#include "base/ansi_scrubber.hh" +#include "base/fs_util.hh" +#include "base/func_util.hh" +#include "base/future_util.hh" +#include "base/humanize.hh" +#include "base/humanize.time.hh" +#include "base/injector.bind.hh" +#include "base/isc.hh" +#include "base/itertools.hh" +#include "base/lnav.console.hh" +#include "base/lnav_log.hh" +#include "base/paths.hh" +#include "base/string_util.hh" +#include "bookmarks.hh" +#include "bottom_status_source.hh" +#include "bound_tags.hh" +#include "breadcrumb_curses.hh" +#include "CLI/CLI.hpp" +#include "dump_internals.hh" +#include "environ_vtab.hh" +#include "filter_sub_source.hh" +#include "fstat_vtab.hh" +#include "grep_proc.hh" +#include "hist_source.hh" +#include "init-sql.h" +#include "listview_curses.hh" +#include "lnav.events.hh" +#include "lnav.hh" +#include "lnav.indexing.hh" +#include "lnav.management_cli.hh" +#include "lnav_commands.hh" +#include "lnav_config.hh" +#include "lnav_util.hh" +#include "log_data_helper.hh" +#include "log_data_table.hh" +#include "log_format_loader.hh" +#include "log_gutter_source.hh" +#include "log_vtab_impl.hh" +#include "logfile.hh" +#include "logfile_sub_source.hh" +#include "piper_proc.hh" +#include "readline_curses.hh" +#include "readline_highlighters.hh" +#include "regexp_vtab.hh" +#include "scn/scn.h" +#include "service_tags.hh" +#include "session_data.hh" +#include "spectro_source.hh" +#include "sql_help.hh" +#include "sql_util.hh" +#include "sqlite-extension-func.hh" +#include "sqlitepp.client.hh" +#include "static_file_vtab.hh" +#include "tailer/tailer.looper.hh" +#include "term_extra.hh" +#include "termios_guard.hh" +#include "textfile_highlighters.hh" +#include "textview_curses.hh" +#include "top_status_source.hh" +#include "view_helpers.crumbs.hh" +#include "view_helpers.examples.hh" +#include "view_helpers.hist.hh" +#include "views_vtab.hh" +#include "vt52_curses.hh" +#include "xpath_vtab.hh" +#include "xterm_mouse.hh" + +#ifdef HAVE_LIBCURL +# include <curl/curl.h> +#endif + +#include "curl_looper.hh" + +#if HAVE_ARCHIVE_H +# include <archive.h> +#endif + +#include "archive_manager.hh" +#include "command_executor.hh" +#include "field_overlay_source.hh" +#include "hotkeys.hh" +#include "log_actions.hh" +#include "readline_callbacks.hh" +#include "readline_possibilities.hh" +#include "shlex.hh" +#include "url_loader.hh" +#include "yajlpp/yajlpp.hh" + +#ifndef SYSCONFDIR +# define SYSCONFDIR "/usr/etc" +#endif + +using namespace std::literals::chrono_literals; +using namespace lnav::roles::literals; + +static std::vector<std::string> DEFAULT_FILES; +static auto intern_lifetime = intern_string::get_table_lifetime(); + +const int ZOOM_LEVELS[] = { + 1, + 30, + 60, + 5 * 60, + 15 * 60, + 60 * 60, + 4 * 60 * 60, + 8 * 60 * 60, + 24 * 60 * 60, + 7 * 24 * 60 * 60, +}; + +const ssize_t ZOOM_COUNT = sizeof(ZOOM_LEVELS) / sizeof(int); + +const std::vector<std::string> lnav_zoom_strings = { + "1-second", + "30-second", + "1-minute", + "5-minute", + "15-minute", + "1-hour", + "4-hour", + "8-hour", + "1-day", + "1-week", +}; + +static const std::vector<std::string> DEFAULT_DB_KEY_NAMES = { + "match_index", + "capture_index", + "capture_count", + "range_start", + "range_stop", + "inode", + "device", + "inode", + "rowid", + "st_dev", + "st_ino", + "st_mode", + "st_rdev", + "st_uid", + "st_gid", +}; + +const static size_t MAX_STDIN_CAPTURE_SIZE = 10 * 1024 * 1024; + +static auto bound_pollable_supervisor + = injector::bind<pollable_supervisor>::to_singleton(); + +static auto bound_filter_sub_source + = injector::bind<filter_sub_source>::to_singleton(); + +static auto bound_active_files = injector::bind<file_collection>::to_instance( + +[]() { return &lnav_data.ld_active_files; }); + +static auto bound_sqlite_db + = injector::bind<auto_sqlite3>::to_instance(&lnav_data.ld_db); + +static auto bound_lnav_flags + = injector::bind<unsigned long, lnav_flags_tag>::to_instance( + &lnav_data.ld_flags); + +static auto bound_last_rel_time + = injector::bind<relative_time, last_relative_time_tag>::to_singleton(); + +static auto bound_term_extra = injector::bind<term_extra>::to_singleton(); + +static auto bound_xterm_mouse = injector::bind<xterm_mouse>::to_singleton(); + +static auto bound_scripts = injector::bind<available_scripts>::to_singleton(); + +static auto bound_curl + = injector::bind_multiple<isc::service_base>() + .add_singleton<curl_looper, services::curl_streamer_t>(); + +static auto bound_tailer + = injector::bind_multiple<isc::service_base>() + .add_singleton<tailer::looper, services::remote_tailer_t>(); + +static auto bound_main = injector::bind_multiple<static_service>() + .add_singleton<main_looper, services::main_t>(); + +namespace injector { +template<> +void +force_linking(last_relative_time_tag anno) +{ +} + +template<> +void +force_linking(lnav_flags_tag anno) +{ +} + +template<> +void +force_linking(services::curl_streamer_t anno) +{ +} + +template<> +void +force_linking(services::remote_tailer_t anno) +{ +} + +template<> +void +force_linking(services::main_t anno) +{ +} +} // namespace injector + +static breadcrumb_curses breadcrumb_view; + +struct lnav_data_t lnav_data; + +bool +setup_logline_table(exec_context& ec) +{ + // Hidden columns don't show up in the table_info pragma. + static const char* hidden_table_columns[] = { + "log_time_msecs", + "log_path", + "log_text", + "log_body", + + nullptr, + }; + + textview_curses& log_view = lnav_data.ld_views[LNV_LOG]; + bool retval = false; + bool update_possibilities + = (lnav_data.ld_rl_view != nullptr && ec.ec_local_vars.size() == 1); + + if (update_possibilities) { + lnav_data.ld_rl_view->clear_possibilities(ln_mode_t::SQL, "*"); + add_view_text_possibilities(lnav_data.ld_rl_view, + ln_mode_t::SQL, + "*", + &log_view, + text_quoting::sql); + } + + if (log_view.get_inner_height()) { + static intern_string_t logline = intern_string::lookup("logline"); + vis_line_t vl = log_view.get_top(); + content_line_t cl = lnav_data.ld_log_source.at_base(vl); + + lnav_data.ld_vtab_manager->unregister_vtab(logline); + lnav_data.ld_vtab_manager->register_vtab( + std::make_shared<log_data_table>(lnav_data.ld_log_source, + *lnav_data.ld_vtab_manager, + cl, + logline)); + + if (update_possibilities) { + log_data_helper ldh(lnav_data.ld_log_source); + + ldh.parse_line(cl); + + std::map<const intern_string_t, + json_ptr_walk::walk_list_t>::const_iterator pair_iter; + for (pair_iter = ldh.ldh_json_pairs.begin(); + pair_iter != ldh.ldh_json_pairs.end(); + ++pair_iter) + { + for (size_t lpc = 0; lpc < pair_iter->second.size(); lpc++) { + lnav_data.ld_rl_view->add_possibility( + ln_mode_t::SQL, + "*", + ldh.format_json_getter(pair_iter->first, lpc)); + } + } + } + + retval = true; + } + + auto& db_key_names = lnav_data.ld_db_key_names; + + db_key_names = DEFAULT_DB_KEY_NAMES; + + if (update_possibilities) { + add_env_possibilities(ln_mode_t::SQL); + + lnav_data.ld_rl_view->add_possibility(ln_mode_t::SQL, + "*", + std::begin(sql_keywords), + std::end(sql_keywords)); + lnav_data.ld_rl_view->add_possibility( + ln_mode_t::SQL, "*", sql_function_names); + lnav_data.ld_rl_view->add_possibility( + ln_mode_t::SQL, "*", hidden_table_columns); + + for (int lpc = 0; sqlite_registration_funcs[lpc]; lpc++) { + struct FuncDef* basic_funcs; + struct FuncDefAgg* agg_funcs; + + sqlite_registration_funcs[lpc](&basic_funcs, &agg_funcs); + for (int lpc2 = 0; basic_funcs && basic_funcs[lpc2].zName; lpc2++) { + const FuncDef& func_def = basic_funcs[lpc2]; + + lnav_data.ld_rl_view->add_possibility( + ln_mode_t::SQL, + "*", + std::string(func_def.zName) + (func_def.nArg ? "(" : "()")); + } + for (int lpc2 = 0; agg_funcs && agg_funcs[lpc2].zName; lpc2++) { + const FuncDefAgg& func_def = agg_funcs[lpc2]; + + lnav_data.ld_rl_view->add_possibility( + ln_mode_t::SQL, + "*", + std::string(func_def.zName) + (func_def.nArg ? "(" : "()")); + } + } + + for (const auto& pair : sqlite_function_help) { + switch (pair.second->ht_context) { + case help_context_t::HC_SQL_FUNCTION: + case help_context_t::HC_SQL_TABLE_VALUED_FUNCTION: { + std::string poss = pair.first + + (pair.second->ht_parameters.empty() ? "()" : ("(")); + + lnav_data.ld_rl_view->add_possibility( + ln_mode_t::SQL, "*", poss); + break; + } + default: + break; + } + } + } + + walk_sqlite_metadata(lnav_data.ld_db.in(), lnav_sql_meta_callbacks); + + for (const auto& iter : *lnav_data.ld_vtab_manager) { + iter.second->get_foreign_keys(db_key_names); + } + + stable_sort(db_key_names.begin(), db_key_names.end()); + + return retval; +} + +static bool +append_default_files() +{ + bool retval = true; + auto cwd = ghc::filesystem::current_path(); + + for (const auto& path : DEFAULT_FILES) { + if (access(path.c_str(), R_OK) == 0) { + auto_mem<char> abspath; + + auto full_path = cwd / path; + if ((abspath = realpath(full_path.c_str(), nullptr)) == nullptr) { + perror("Unable to resolve path"); + } else { + lnav_data.ld_active_files.fc_file_names[abspath.in()]; + } + } else if (lnav::filesystem::stat_file(path).isOk()) { + lnav::console::print( + stderr, + lnav::console::user_message::error( + attr_line_t("default syslog file is not readable -- ") + .append(lnav::roles::file(cwd)) + .append(lnav::roles::file(path)))); + retval = false; + } + } + + return retval; +} + +static void +sigint(int sig) +{ + lnav_data.ld_looping = false; +} + +static void +sigwinch(int sig) +{ + lnav_data.ld_winched = true; +} + +static void +sigchld(int sig) +{ + lnav_data.ld_child_terminated = true; +} + +static void +handle_rl_key(int ch) +{ + switch (ch) { + case KEY_PPAGE: + case KEY_NPAGE: + case KEY_CTRL_P: + handle_paging_key(ch); + break; + + case KEY_CTRL_RBRACKET: + lnav_data.ld_rl_view->abort(); + break; + + default: + lnav_data.ld_rl_view->handle_key(ch); + break; + } +} + +readline_context::command_map_t lnav_commands; + +static attr_line_t +command_arg_help() +{ + return attr_line_t() + .append( + "command arguments must start with one of the following symbols " + "to denote the type of command:\n") + .append(" ") + .append(":"_symbol) + .append(" - ") + .append("an lnav command (e.g. :goto 42)\n") + .append(" ") + .append(";"_symbol) + .append(" - ") + .append("an SQL statement (e.g. ;SELECT * FROM syslog_log)\n") + .append(" ") + .append("|"_symbol) + .append(" - ") + .append("an lnav script (e.g. |rename-stdin foo)\n"); +} + +static void +usage() +{ + attr_line_t ex1_term; + + ex1_term.append(lnav::roles::ok("$")) + .append(" ") + .append(lnav::roles::file("lnav")) + .pad_to(40) + .with_attr_for_all(VC_ROLE.value(role_t::VCR_QUOTED_CODE)); + + attr_line_t ex2_term; + + ex2_term.append(lnav::roles::ok("$")) + .append(" ") + .append(lnav::roles::file("lnav")) + .append(" ") + .append(lnav::roles::file("/var/log")) + .pad_to(40) + .with_attr_for_all(VC_ROLE.value(role_t::VCR_QUOTED_CODE)); + + attr_line_t ex3_term; + + ex3_term.append(lnav::roles::ok("$")) + .append(" ") + .append(lnav::roles::file("make")) + .append(" 2>&1 | ") + .append(lnav::roles::file("lnav")) + .append(" ") + .append("-t"_symbol) + .pad_to(40) + .with_attr_for_all(VC_ROLE.value(role_t::VCR_QUOTED_CODE)); + + attr_line_t usage_al; + + usage_al.append("usage"_h1) + .append(": ") + .append(lnav::roles::file(lnav_data.ld_program_name)) + .append(" [") + .append("options"_variable) + .append("] [") + .append("logfile1"_variable) + .append(" ") + .append("logfile2"_variable) + .append(" ") + .append("..."_variable) + .append("]\n") + .append(R"( +A log file viewer for the terminal that indexes log messages to +make it easier to navigate through files quickly. + +)") + .append("Key Bindings"_h2) + .append("\n") + .append(" ?"_symbol) + .append(" View/leave the online help text.\n") + .append(" q"_symbol) + .append(" Quit the program.\n") + .append("\n") + .append("Options"_h2) + .append("\n") + .append(" ") + .append("-h"_symbol) + .append(" ") + .append("Print this message, then exit.\n") + .append(" ") + .append("-H"_symbol) + .append(" ") + .append("Display the internal help text.\n") + .append("\n") + .append(" ") + .append("-I"_symbol) + .append(" ") + .append("dir"_variable) + .append(" ") + .append("An additional configuration directory.\n") + .append(" ") + .append("-W"_symbol) + .append(" ") + .append("Print warnings related to lnav's configuration.\n") + .append(" ") + .append("-u"_symbol) + .append(" ") + .append("Update formats installed from git repositories.\n") + .append(" ") + .append("-d"_symbol) + .append(" ") + .append("file"_variable) + .append(" ") + .append("Write debug messages to the given file.\n") + .append(" ") + .append("-V"_symbol) + .append(" ") + .append("Print version information.\n") + .append("\n") + .append(" ") + .append("-r"_symbol) + .append(" ") + .append( + "Recursively load files from the given directory hierarchies.\n") + .append(" ") + .append("-R"_symbol) + .append(" ") + .append("Load older rotated log files as well.\n") + .append(" ") + .append("-t"_symbol) + .append(" ") + .append(R"(Prepend timestamps to the lines of data being read in + from the standard input. +)") + .append(" ") + .append("-w"_symbol) + .append(" ") + .append("file"_variable) + .append(" ") + .append("Write the contents of the standard input to this file.\n") + .append("\n") + .append(" ") + .append("-c"_symbol) + .append(" ") + .append("cmd"_variable) + .append(" ") + .append("Execute a command after the files have been loaded.\n") + .append(" ") + .append("-f"_symbol) + .append(" ") + .append("file"_variable) + .append(" ") + .append("Execute the commands in the given file.\n") + .append(" ") + .append("-n"_symbol) + .append(" ") + .append("Run without the curses UI. (headless mode)\n") + .append(" ") + .append("-N"_symbol) + .append(" ") + .append("Do not open the default syslog file if no files are given.\n") + .append(" ") + .append("-q"_symbol) + .append(" ") + .append( + R"(Do not print the log messages after executing all + of the commands. +)") + .append("\n") + .append("Optional arguments"_h2) + .append("\n") + .append(" ") + .append("logfileN"_variable) + .append(R"( The log files, directories, or remote paths to view. + If a directory is given, all of the files in the + directory will be loaded. +)") + .append("\n") + .append("Management-Mode Options"_h2) + .append("\n") + .append(" ") + .append("-i"_symbol) + .append(" ") + .append(R"(Install the given format files and exit. Pass 'extra' + to install the default set of third-party formats. +)") + .append(" ") + .append("-m"_symbol) + .append(" ") + .append(R"(Switch to the management command-line mode. This mode + is used to work with lnav's configuration. +)") + .append("\n") + .append("Examples"_h2) + .append("\n ") + .append("\u2022"_list_glyph) + .append(" To load and follow the syslog file:\n") + .append(" ") + .append(ex1_term) + .append("\n\n ") + .append("\u2022"_list_glyph) + .append(" To load all of the files in ") + .append(lnav::roles::file("/var/log")) + .append(":\n") + .append(" ") + .append(ex2_term) + .append("\n\n ") + .append("\u2022"_list_glyph) + .append(" To watch the output of ") + .append(lnav::roles::file("make")) + .append(" with timestamps prepended:\n") + .append(" ") + .append(ex3_term) + .append("\n\n") + .append("Paths"_h2) + .append("\n ") + .append("\u2022"_list_glyph) + .append(" Configuration, session, and format files are stored in:\n") + .append(" \U0001F4C2 ") + .append(lnav::roles::file(lnav::paths::dotlnav().string())) + .append("\n\n ") + .append("\u2022"_list_glyph) + .append(" Local copies of remote files and files extracted from\n") + .append(" archives are stored in:\n") + .append(" \U0001F4C2 ") + .append(lnav::roles::file(lnav::paths::workdir().string())) + .append("\n\n") + .append("Documentation"_h1) + .append(": https://docs.lnav.org\n") + .append("Contact"_h1) + .append("\n") + .append(" \U0001F4AC https://github.com/tstack/lnav/discussions\n") + .appendf(FMT_STRING(" \U0001F4EB {}\n"), PACKAGE_BUGREPORT) + .append("Version"_h1) + .appendf(FMT_STRING(": {}"), VCS_PACKAGE_STRING); + + lnav::console::println(stderr, usage_al); +} + +static void +clear_last_user_mark(listview_curses* lv) +{ + textview_curses* tc = (textview_curses*) lv; + if (lnav_data.ld_select_start.find(tc) != lnav_data.ld_select_start.end() + && !tc->is_line_visible(vis_line_t(lnav_data.ld_last_user_mark[tc]))) + { + lnav_data.ld_select_start.erase(tc); + lnav_data.ld_last_user_mark.erase(tc); + } +} + +class lnav_behavior : public mouse_behavior { +public: + void mouse_event(int button, bool release, int x, int y) override + { + textview_curses* tc = *(lnav_data.ld_view_stack.top()); + struct mouse_event me; + + switch (button & xterm_mouse::XT_BUTTON__MASK) { + case xterm_mouse::XT_BUTTON1: + me.me_button = mouse_button_t::BUTTON_LEFT; + break; + case xterm_mouse::XT_BUTTON2: + me.me_button = mouse_button_t::BUTTON_MIDDLE; + break; + case xterm_mouse::XT_BUTTON3: + me.me_button = mouse_button_t::BUTTON_RIGHT; + break; + case xterm_mouse::XT_SCROLL_UP: + me.me_button = mouse_button_t::BUTTON_SCROLL_UP; + break; + case xterm_mouse::XT_SCROLL_DOWN: + me.me_button = mouse_button_t::BUTTON_SCROLL_DOWN; + break; + } + + if (button & xterm_mouse::XT_DRAG_FLAG) { + me.me_state = mouse_button_state_t::BUTTON_STATE_DRAGGED; + } else if (release) { + me.me_state = mouse_button_state_t::BUTTON_STATE_RELEASED; + } else { + me.me_state = mouse_button_state_t::BUTTON_STATE_PRESSED; + } + + gettimeofday(&me.me_time, nullptr); + me.me_x = x - 1; + me.me_y = y - tc->get_y() - 1; + + tc->handle_mouse(me); + } +}; + +static bool +handle_config_ui_key(int ch) +{ + bool retval = false; + + switch (lnav_data.ld_mode) { + case ln_mode_t::FILES: + retval = lnav_data.ld_files_view.handle_key(ch); + break; + case ln_mode_t::FILTER: + retval = lnav_data.ld_filter_view.handle_key(ch); + break; + default: + ensure(0); + } + + if (retval) { + return retval; + } + + nonstd::optional<ln_mode_t> new_mode; + + lnav_data.ld_filter_help_status_source.fss_error.clear(); + if (ch == 'F') { + new_mode = ln_mode_t::FILES; + } else if (ch == 'T') { + new_mode = ln_mode_t::FILTER; + } else if (ch == '\t' || ch == KEY_BTAB) { + if (lnav_data.ld_mode == ln_mode_t::FILES) { + new_mode = ln_mode_t::FILTER; + } else { + new_mode = ln_mode_t::FILES; + } + } else if (ch == 'q') { + new_mode = ln_mode_t::PAGING; + } + + if (new_mode) { + if (new_mode.value() == ln_mode_t::FILES + || new_mode.value() == ln_mode_t::FILTER) + { + lnav_data.ld_last_config_mode = new_mode.value(); + } + lnav_data.ld_mode = new_mode.value(); + lnav_data.ld_files_view.reload_data(); + lnav_data.ld_filter_view.reload_data(); + lnav_data.ld_status[LNS_FILTER].set_needs_update(); + } else { + return handle_paging_key(ch); + } + + return true; +} + +static bool +handle_key(int ch) +{ + lnav_data.ld_input_state.push_back(ch); + + switch (ch) { + case KEY_RESIZE: + break; + default: { + switch (lnav_data.ld_mode) { + case ln_mode_t::PAGING: + if (ch == KEY_ENTER || ch == '\n' || ch == '\r') { + breadcrumb_view.focus(); + lnav_data.ld_mode = ln_mode_t::BREADCRUMBS; + return true; + } + + return handle_paging_key(ch); + + case ln_mode_t::BREADCRUMBS: + if (!breadcrumb_view.handle_key(ch)) { + lnav_data.ld_mode = ln_mode_t::PAGING; + lnav_data.ld_view_stack.set_needs_update(); + return true; + } + return true; + + case ln_mode_t::FILTER: + case ln_mode_t::FILES: + return handle_config_ui_key(ch); + + case ln_mode_t::SPECTRO_DETAILS: { + if (ch == '\t' || ch == 'q') { + lnav_data.ld_mode = ln_mode_t::PAGING; + return true; + } + if (lnav_data.ld_spectro_details_view.handle_key(ch)) { + return true; + } + switch (ch) { + case 'n': { + execute_command(lnav_data.ld_exec_context, + "next-mark search"); + return true; + } + case 'N': { + execute_command(lnav_data.ld_exec_context, + "prev-mark search"); + return true; + } + case '/': { + execute_command(lnav_data.ld_exec_context, + "prompt search-spectro-details"); + return true; + } + } + return false; + } + + case ln_mode_t::COMMAND: + case ln_mode_t::SEARCH: + case ln_mode_t::SEARCH_FILTERS: + case ln_mode_t::SEARCH_FILES: + case ln_mode_t::SEARCH_SPECTRO_DETAILS: + case ln_mode_t::CAPTURE: + case ln_mode_t::SQL: + case ln_mode_t::EXEC: + case ln_mode_t::USER: + handle_rl_key(ch); + break; + + case ln_mode_t::BUSY: + switch (ch) { + case KEY_CTRL_RBRACKET: + log_vtab_data.lvd_looping = false; + break; + } + break; + + default: + require(0); + break; + } + } + } + + return true; +} + +static input_dispatcher::escape_match_t +match_escape_seq(const char* keyseq) +{ + if (lnav_data.ld_mode != ln_mode_t::PAGING) { + return input_dispatcher::escape_match_t::NONE; + } + + auto& km = lnav_config.lc_active_keymap; + auto iter = km.km_seq_to_cmd.find(keyseq); + if (iter != km.km_seq_to_cmd.end()) { + return input_dispatcher::escape_match_t::FULL; + } + + auto lb = km.km_seq_to_cmd.lower_bound(keyseq); + if (lb == km.km_seq_to_cmd.end()) { + return input_dispatcher::escape_match_t::NONE; + } + + auto ub = km.km_seq_to_cmd.upper_bound(keyseq); + auto longest = max_element( + lb, ub, [](auto l, auto r) { return l.first.size() < r.first.size(); }); + + if (strlen(keyseq) < longest->first.size()) { + return input_dispatcher::escape_match_t::PARTIAL; + } + + return input_dispatcher::escape_match_t::NONE; +} + +static void +gather_pipers() +{ + for (auto iter = lnav_data.ld_pipers.begin(); + iter != lnav_data.ld_pipers.end();) + { + pid_t child_pid = (*iter)->get_child_pid(); + if ((*iter)->has_exited()) { + log_info("child piper has exited -- %d", child_pid); + iter = lnav_data.ld_pipers.erase(iter); + } else { + ++iter; + } + } + + for (auto iter = lnav_data.ld_child_pollers.begin(); + iter != lnav_data.ld_child_pollers.end();) + { + if (iter->poll(lnav_data.ld_active_files) + == child_poll_result_t::FINISHED) + { + iter = lnav_data.ld_child_pollers.erase(iter); + } else { + ++iter; + } + } +} + +static void +wait_for_pipers() +{ + for (;;) { + gather_pipers(); + if (lnav_data.ld_pipers.empty() && lnav_data.ld_child_pollers.empty()) { + log_debug("all pipers finished"); + break; + } + usleep(10000); + rebuild_indexes(); + + log_debug("%d pipers and %d children still active", + lnav_data.ld_pipers.size(), + lnav_data.ld_child_pollers.size()); + } +} + +struct refresh_status_bars { + refresh_status_bars(std::shared_ptr<top_status_source> top_source) + : rsb_top_source(std::move(top_source)) + { + } + + using injectable + = refresh_status_bars(std::shared_ptr<top_status_source> top_source); + + void doit() const + { + struct timeval current_time {}; + int ch; + + gettimeofday(¤t_time, nullptr); + while ((ch = getch()) != ERR) { + lnav_data.ld_user_message_source.clear(); + + alerter::singleton().new_input(ch); + + lnav_data.ld_input_dispatcher.new_input(current_time, ch); + + lnav_data.ld_view_stack.top() | [ch](auto tc) { + lnav_data.ld_key_repeat_history.update(ch, tc->get_top()); + }; + + if (!lnav_data.ld_looping) { + // No reason to keep processing input after the + // user has quit. The view stack will also be + // empty, which will cause issues. + break; + } + } + + this->rsb_top_source->update_time(current_time); + for (auto& sc : lnav_data.ld_status) { + sc.do_update(); + } + lnav_data.ld_rl_view->do_update(); + if (handle_winch()) { + layout_views(); + lnav_data.ld_view_stack.do_update(); + } + refresh(); + } + + std::shared_ptr<top_status_source> rsb_top_source; +}; + +static void +looper() +{ + static auto* ps = injector::get<pollable_supervisor*>(); + static auto* filter_source = injector::get<filter_sub_source*>(); + + try { + auto* sql_cmd_map = injector::get<readline_context::command_map_t*, + sql_cmd_map_tag>(); + auto& ec = lnav_data.ld_exec_context; + + readline_context command_context("cmd", &lnav_commands); + + readline_context search_context("search", nullptr, false); + readline_context search_filters_context( + "search-filters", nullptr, false); + readline_context search_files_context("search-files", nullptr, false); + readline_context search_spectro_details_context( + "search-spectro-details", nullptr, false); + readline_context index_context("capture"); + readline_context sql_context("sql", sql_cmd_map, false); + readline_context exec_context("exec"); + readline_context user_context("user"); + auto rlc = injector::get<std::shared_ptr<readline_curses>>(); + sig_atomic_t overlay_counter = 0; + int lpc; + + command_context.set_highlighter(readline_command_highlighter); + search_context.set_append_character(0).set_highlighter( + readline_regex_highlighter); + search_filters_context.set_append_character(0).set_highlighter( + readline_regex_highlighter); + search_files_context.set_append_character(0).set_highlighter( + readline_regex_highlighter); + search_spectro_details_context.set_append_character(0).set_highlighter( + readline_regex_highlighter); + sql_context.set_highlighter(readline_sqlite_highlighter) + .set_quote_chars("\"") + .with_readline_var((char**) &rl_completer_word_break_characters, + " \t\n(),"); + exec_context.set_highlighter(readline_shlex_highlighter); + + lnav_data.ld_log_source.lss_sorting_observer + = [](auto& lss, auto off, auto size) { + if (off == size) { + lnav_data.ld_bottom_source.update_loading(0, 0); + } else { + lnav_data.ld_bottom_source.update_loading(off, size); + } + do_observer_update(nullptr); + }; + + auto& sb = lnav_data.ld_scroll_broadcaster; + auto& vsb = lnav_data.ld_view_stack_broadcaster; + + rlc->add_context(ln_mode_t::COMMAND, command_context); + rlc->add_context(ln_mode_t::SEARCH, search_context); + rlc->add_context(ln_mode_t::SEARCH_FILTERS, search_filters_context); + rlc->add_context(ln_mode_t::SEARCH_FILES, search_files_context); + rlc->add_context(ln_mode_t::SEARCH_SPECTRO_DETAILS, + search_spectro_details_context); + rlc->add_context(ln_mode_t::CAPTURE, index_context); + rlc->add_context(ln_mode_t::SQL, sql_context); + rlc->add_context(ln_mode_t::EXEC, exec_context); + rlc->add_context(ln_mode_t::USER, user_context); + rlc->set_save_history(!(lnav_data.ld_flags & LNF_SECURE_MODE)); + rlc->start(); + + filter_source->fss_editor->start(); + + lnav_data.ld_rl_view = rlc.get(); + + lnav_data.ld_rl_view->add_possibility( + ln_mode_t::COMMAND, "viewname", lnav_view_strings); + + lnav_data.ld_rl_view->add_possibility( + ln_mode_t::COMMAND, "zoomlevel", lnav_zoom_strings); + + lnav_data.ld_rl_view->add_possibility( + ln_mode_t::COMMAND, "levelname", level_names); + + auto echo_views_stmt_res = prepare_stmt(lnav_data.ld_db, +#if SQLITE_VERSION_NUMBER < 3033000 + R"( + UPDATE lnav_views_echo + SET top = (SELECT top FROM lnav_views WHERE lnav_views.name = lnav_views_echo.name), + left = (SELECT left FROM lnav_views WHERE lnav_views.name = lnav_views_echo.name), + height = (SELECT height FROM lnav_views WHERE lnav_views.name = lnav_views_echo.name), + inner_height = (SELECT inner_height FROM lnav_views WHERE lnav_views.name = lnav_views_echo.name), + top_time = (SELECT top_time FROM lnav_views WHERE lnav_views.name = lnav_views_echo.name), + search = (SELECT search FROM lnav_views WHERE lnav_views.name = lnav_views_echo.name) + WHERE EXISTS (SELECT * FROM lnav_views WHERE name = lnav_views_echo.name AND + ( + lnav_views.top != lnav_views_echo.top OR + lnav_views.left != lnav_views_echo.left OR + lnav_views.height != lnav_views_echo.height OR + lnav_views.inner_height != lnav_views_echo.inner_height OR + lnav_views.top_time != lnav_views_echo.top_time OR + lnav_views.search != lnav_views_echo.search + )) + )" +#else + R"( + UPDATE lnav_views_echo + SET top = orig.top, + left = orig.left, + height = orig.height, + inner_height = orig.inner_height, + top_time = orig.top_time, + search = orig.search + FROM (SELECT * FROM lnav_views) AS orig + WHERE orig.name = lnav_views_echo.name AND + ( + orig.top != lnav_views_echo.top OR + orig.left != lnav_views_echo.left OR + orig.height != lnav_views_echo.height OR + orig.inner_height != lnav_views_echo.inner_height OR + orig.top_time != lnav_views_echo.top_time OR + orig.search != lnav_views_echo.search + ) + )" +#endif + ); + + if (echo_views_stmt_res.isErr()) { + lnav::console::print( + stderr, + lnav::console::user_message::error( + "unable to prepare UPDATE statement for lnav_views_echo " + "table") + .with_reason(echo_views_stmt_res.unwrapErr())); + return; + } + auto echo_views_stmt = echo_views_stmt_res.unwrap(); + + (void) signal(SIGINT, sigint); + (void) signal(SIGTERM, sigint); + (void) signal(SIGWINCH, sigwinch); + (void) signal(SIGCHLD, sigchld); + + auto create_screen_res = screen_curses::create(); + + if (create_screen_res.isErr()) { + log_error("create screen failed with: %s", + create_screen_res.unwrapErr().c_str()); + lnav::console::print( + stderr, + lnav::console::user_message::error("unable to open TUI") + .with_reason(create_screen_res.unwrapErr())); + return; + } + + auto sc = create_screen_res.unwrap(); + + auto_fd errpipe[2]; + auto_fd::pipe(errpipe); + + dup2(errpipe[1], STDERR_FILENO); + errpipe[1].reset(); + log_pipe_err(errpipe[0]); + lnav_behavior lb; + + ui_periodic_timer::singleton(); + + auto mouse_i = injector::get<xterm_mouse&>(); + + mouse_i.set_behavior(&lb); + mouse_i.set_enabled(check_experimental("mouse")); + + lnav_data.ld_window = sc.get_window(); + keypad(stdscr, TRUE); + (void) nonl(); + (void) cbreak(); + (void) noecho(); + (void) nodelay(lnav_data.ld_window, 1); + +#ifdef VDSUSP + { + struct termios tio; + + tcgetattr(STDIN_FILENO, &tio); + tio.c_cc[VDSUSP] = 0; + tcsetattr(STDIN_FILENO, TCSANOW, &tio); + } +#endif + + define_key("\033Od", KEY_BEG); + define_key("\033Oc", KEY_END); + + view_colors& vc = view_colors::singleton(); + view_colors::init(false); + + auto ecb_guard + = lnav_data.ld_exec_context.add_error_callback([](const auto& um) { + auto al = um.to_attr_line().rtrim(); + + if (al.get_string().find('\n') == std::string::npos) { + if (lnav_data.ld_rl_view) { + lnav_data.ld_rl_view->set_attr_value(al); + } + } else { + lnav_data.ld_user_message_source.replace_with(al); + lnav_data.ld_user_message_view.reload_data(); + lnav_data.ld_user_message_expiration + = std::chrono::steady_clock::now() + 20s; + } + }); + + { + setup_highlights(lnav_data.ld_views[LNV_LOG].get_highlights()); + setup_highlights(lnav_data.ld_views[LNV_TEXT].get_highlights()); + setup_highlights(lnav_data.ld_views[LNV_SCHEMA].get_highlights()); + setup_highlights(lnav_data.ld_views[LNV_PRETTY].get_highlights()); + setup_highlights(lnav_data.ld_preview_view.get_highlights()); + + for (const auto& format : log_format::get_root_formats()) { + for (auto& hl : format->lf_highlighters) { + if (hl.h_fg.empty() && hl.h_bg.empty() + && hl.h_attrs.empty()) + { + hl.with_attrs(hl.h_attrs + | vc.attrs_for_ident(hl.h_name)); + } + + lnav_data.ld_views[LNV_LOG].get_highlights()[{ + highlight_source_t::CONFIGURATION, + format->get_name().to_string() + "-" + hl.h_name}] + = hl; + } + } + } + + execute_examples(); + + rlc->set_window(lnav_data.ld_window); + rlc->set_y(-1); + rlc->set_focus_action(rl_focus); + rlc->set_change_action(rl_change); + rlc->set_perform_action(rl_callback); + rlc->set_alt_perform_action(rl_alt_callback); + rlc->set_timeout_action(rl_search); + rlc->set_abort_action(lnav_rl_abort); + rlc->set_display_match_action(rl_display_matches); + rlc->set_display_next_action(rl_display_next); + rlc->set_blur_action(rl_blur); + rlc->set_completion_request_action(rl_completion_request); + rlc->set_alt_value( + HELP_MSG_2(e, + E, + "to move forward/backward through " ANSI_COLOR( + COLOR_RED) "error" ANSI_NORM " messages")); + + (void) curs_set(0); + + lnav_data.ld_view_stack.push_back(&lnav_data.ld_views[LNV_LOG]); + + sb.push_back(clear_last_user_mark); + sb.push_back(bind_mem(&bottom_status_source::update_line_number, + &lnav_data.ld_bottom_source)); + sb.push_back(bind_mem(&bottom_status_source::update_percent, + &lnav_data.ld_bottom_source)); + sb.push_back(bind_mem(&bottom_status_source::update_marks, + &lnav_data.ld_bottom_source)); + vsb.push_back( + bind_mem(&term_extra::update_title, injector::get<term_extra*>())); + vsb.push_back([](listview_curses* lv) { + auto* tc = dynamic_cast<textview_curses*>(lv); + + tc->tc_state_event_handler(*tc); + }); + + vsb.push_back(sb); + + breadcrumb_view.set_y(1); + breadcrumb_view.set_window(lnav_data.ld_window); + breadcrumb_view.set_line_source(lnav_crumb_source); + auto event_handler = [](auto&& tc) { + auto top_view = lnav_data.ld_view_stack.top(); + + if (top_view && *top_view == &tc) { + lnav_data.ld_bottom_source.update_search_term(tc); + } + }; + for (lpc = 0; lpc < LNV__MAX; lpc++) { + lnav_data.ld_views[lpc].set_window(lnav_data.ld_window); + lnav_data.ld_views[lpc].set_y(2); + lnav_data.ld_views[lpc].set_height( + vis_line_t(-(rlc->get_height() + 3))); + lnav_data.ld_views[lpc].set_scroll_action(sb); + lnav_data.ld_views[lpc].set_search_action(update_hits); + lnav_data.ld_views[lpc].tc_state_event_handler = event_handler; + } + + lnav_data.ld_doc_view.set_window(lnav_data.ld_window); + lnav_data.ld_doc_view.set_show_scrollbar(false); + + lnav_data.ld_example_view.set_window(lnav_data.ld_window); + lnav_data.ld_example_view.set_show_scrollbar(false); + + lnav_data.ld_match_view.set_window(lnav_data.ld_window); + + lnav_data.ld_preview_view.set_window(lnav_data.ld_window); + lnav_data.ld_preview_view.set_show_scrollbar(false); + + lnav_data.ld_filter_view.set_selectable(true); + lnav_data.ld_filter_view.set_window(lnav_data.ld_window); + lnav_data.ld_filter_view.set_show_scrollbar(true); + + lnav_data.ld_files_view.set_selectable(true); + lnav_data.ld_files_view.set_window(lnav_data.ld_window); + lnav_data.ld_files_view.set_show_scrollbar(true); + lnav_data.ld_files_view.get_disabled_highlights().insert( + highlight_source_t::THEME); + lnav_data.ld_files_view.set_overlay_source(&lnav_data.ld_files_overlay); + + lnav_data.ld_user_message_view.set_window(lnav_data.ld_window); + + lnav_data.ld_spectro_details_view.set_window(lnav_data.ld_window); + lnav_data.ld_spectro_details_view.set_show_scrollbar(true); + lnav_data.ld_spectro_details_view.set_height(5_vl); + lnav_data.ld_spectro_details_view.set_sub_source( + &lnav_data.ld_spectro_no_details_source); + lnav_data.ld_spectro_details_view.tc_state_event_handler + = event_handler; + lnav_data.ld_spectro_details_view.set_scroll_action(sb); + lnav_data.ld_spectro_no_details_source.replace_with( + attr_line_t().append( + lnav::roles::comment(" No details available"))); + lnav_data.ld_spectro_source->ss_details_view + = &lnav_data.ld_spectro_details_view; + lnav_data.ld_spectro_source->ss_no_details_source + = &lnav_data.ld_spectro_no_details_source; + lnav_data.ld_spectro_source->ss_exec_context + = &lnav_data.ld_exec_context; + + auto top_status_lifetime + = injector::bind<top_status_source>::to_scoped_singleton(); + + auto top_source = injector::get<std::shared_ptr<top_status_source>>(); + + lnav_data.ld_status[LNS_TOP].set_top(0); + lnav_data.ld_status[LNS_TOP].set_default_role( + role_t::VCR_INACTIVE_STATUS); + lnav_data.ld_status[LNS_TOP].set_data_source(top_source.get()); + lnav_data.ld_status[LNS_BOTTOM].set_top(-(rlc->get_height() + 1)); + for (auto& stat_bar : lnav_data.ld_status) { + stat_bar.set_window(lnav_data.ld_window); + } + lnav_data.ld_status[LNS_BOTTOM].set_data_source( + &lnav_data.ld_bottom_source); + lnav_data.ld_status[LNS_FILTER].set_data_source( + &lnav_data.ld_filter_status_source); + lnav_data.ld_status[LNS_FILTER_HELP].set_data_source( + &lnav_data.ld_filter_help_status_source); + lnav_data.ld_status[LNS_DOC].set_data_source( + &lnav_data.ld_doc_status_source); + lnav_data.ld_status[LNS_PREVIEW].set_data_source( + &lnav_data.ld_preview_status_source); + lnav_data.ld_spectro_status_source + = std::make_unique<spectro_status_source>(); + lnav_data.ld_status[LNS_SPECTRO].set_data_source( + lnav_data.ld_spectro_status_source.get()); + + lnav_data.ld_match_view.set_show_bottom_border(true); + lnav_data.ld_user_message_view.set_show_bottom_border(true); + + for (auto& sc : lnav_data.ld_status) { + sc.window_change(); + } + + auto session_path = lnav::paths::dotlnav() / "session"; + execute_file(ec, session_path.string()); + + sb(*lnav_data.ld_view_stack.top()); + vsb(*lnav_data.ld_view_stack.top()); + + lnav_data.ld_view_stack.vs_change_handler = [](textview_curses* tc) { + lnav_data.ld_view_stack_broadcaster(tc); + }; + + { + auto& id = lnav_data.ld_input_dispatcher; + + id.id_escape_matcher = match_escape_seq; + id.id_escape_handler = handle_keyseq; + id.id_key_handler = handle_key; + id.id_mouse_handler + = std::bind(&xterm_mouse::handle_mouse, &mouse_i); + id.id_unhandled_handler = [](const char* keyseq) { + auto enc_len = lnav_config.lc_ui_keymap.size() * 2; + auto encoded_name = (char*) alloca(enc_len); + + log_info("unbound keyseq: %s", keyseq); + json_ptr::encode( + encoded_name, enc_len, lnav_config.lc_ui_keymap.c_str()); + // XXX we should have a hotkey for opening a prompt that is + // pre-filled with a suggestion that the user can complete. + // This quick-fix key could be used for other stuff as well + lnav_data.ld_rl_view->set_value(fmt::format( + ANSI_CSI ANSI_COLOR_PARAM( + COLOR_YELLOW) ";" ANSI_BOLD_PARAM ANSI_CHAR_ATTR + "Unrecognized key" ANSI_NORM + ", bind to a command using " + "\u2014 " ANSI_BOLD( + ":config") " /ui/keymap-defs/{}/{}/" + "command <cmd>", + encoded_name, + keyseq)); + alerter::singleton().chime("unrecognized key"); + }; + } + + auto refresher_lifetime + = injector::bind<refresh_status_bars>::to_scoped_singleton(); + + auto refresher = injector::get<std::shared_ptr<refresh_status_bars>>(); + + auto refresh_guard = lnav_data.ld_status_refresher.install( + [refresher]() { refresher->doit(); }); + + auto& timer = ui_periodic_timer::singleton(); + struct timeval current_time; + + static sig_atomic_t index_counter; + + lnav_data.ld_mode = ln_mode_t::FILES; + + timer.start_fade(index_counter, 1); + + file_collection active_copy; + log_debug("rescan started %p", &active_copy); + active_copy.merge(lnav_data.ld_active_files); + active_copy.fc_progress = lnav_data.ld_active_files.fc_progress; + std::future<file_collection> rescan_future + = std::async(std::launch::async, + &file_collection::rescan_files, + std::move(active_copy), + false); + bool initial_rescan_completed = false; + int session_stage = 0; + + // rlc.do_update(); + + auto next_rebuild_time = ui_clock::now(); + auto next_status_update_time = next_rebuild_time; + auto next_rescan_time = next_rebuild_time; + + while (lnav_data.ld_looping) { + auto loop_deadline + = ui_clock::now() + (session_stage == 0 ? 3s : 50ms); + + std::vector<struct pollfd> pollfds; + size_t starting_view_stack_size = lnav_data.ld_view_stack.size(); + size_t changes = 0; + int rc; + + gettimeofday(¤t_time, nullptr); + + top_source->update_time(current_time); + lnav_data.ld_preview_view.set_needs_update(); + + layout_views(); + + auto scan_timeout = initial_rescan_completed ? 0s : 10ms; + if (rescan_future.valid() + && rescan_future.wait_for(scan_timeout) + == std::future_status::ready) + { + auto new_files = rescan_future.get(); + if (!initial_rescan_completed && new_files.fc_file_names.empty() + && new_files.fc_files.empty() + && lnav_data.ld_active_files.fc_progress->readAccess() + ->sp_tailers.empty()) + { + initial_rescan_completed = true; + + log_debug("initial rescan rebuild"); + changes += rebuild_indexes(loop_deadline); + load_session(); + if (session_data.sd_save_time) { + std::string ago; + + ago = humanize::time::point::from_tv( + {(time_t) session_data.sd_save_time, 0}) + .as_time_ago(); + auto um = lnav::console::user_message::ok( + attr_line_t("restored session from ") + .append(lnav::roles::number(ago)) + .append("; press ") + .append("CTRL-R"_hotkey) + .append(" to reset session")); + lnav_data.ld_rl_view->set_attr_value(um.to_attr_line()); + } + + lnav_data.ld_session_loaded = true; + session_stage += 1; + loop_deadline = ui_clock::now(); + log_debug("file count %d", + lnav_data.ld_active_files.fc_files.size()) + } + update_active_files(new_files); + if (!initial_rescan_completed) { + auto& fview = lnav_data.ld_files_view; + auto height = fview.get_inner_height(); + + if (height > 0_vl) { + fview.set_selection(height - 1_vl); + } + } + + active_copy.clear(); + rescan_future = std::future<file_collection>{}; + next_rescan_time = ui_clock::now() + 333ms; + } + + if (!rescan_future.valid() + && (session_stage < 2 || ui_clock::now() >= next_rescan_time)) + { + active_copy.clear(); + active_copy.merge(lnav_data.ld_active_files); + active_copy.fc_progress = lnav_data.ld_active_files.fc_progress; + rescan_future = std::async(std::launch::async, + &file_collection::rescan_files, + std::move(active_copy), + false); + } + + { + auto& mlooper = injector::get<main_looper&, services::main_t>(); + + mlooper.get_port().process_for(0s); + } + + auto ui_now = ui_clock::now(); + if (initial_rescan_completed) { + if (ui_now >= next_rebuild_time) { + auto text_file_count = lnav_data.ld_text_source.size(); + changes += rebuild_indexes(loop_deadline); + if (!changes && ui_clock::now() < loop_deadline) { + next_rebuild_time = ui_clock::now() + 333ms; + } + if (changes && text_file_count + && lnav_data.ld_text_source.empty() + && lnav_data.ld_view_stack.top().value_or(nullptr) + == &lnav_data.ld_views[LNV_TEXT]) + { + do { + lnav_data.ld_view_stack.pop_back(); + } while (lnav_data.ld_view_stack.top().value_or(nullptr) + != &lnav_data.ld_views[LNV_LOG]); + } + } + } else { + lnav_data.ld_files_view.set_overlay_needs_update(); + } + + if (lnav_data.ld_mode == ln_mode_t::BREADCRUMBS + && breadcrumb_view.get_needs_update()) + { + lnav_data.ld_view_stack.set_needs_update(); + } + lnav_data.ld_view_stack.do_update(); + lnav_data.ld_doc_view.do_update(); + lnav_data.ld_example_view.do_update(); + lnav_data.ld_match_view.do_update(); + lnav_data.ld_preview_view.do_update(); + lnav_data.ld_spectro_details_view.do_update(); + lnav_data.ld_user_message_view.do_update(); + if (ui_clock::now() >= next_status_update_time) { + echo_views_stmt.execute(); + top_source->update_user_msg(); + for (auto& sc : lnav_data.ld_status) { + sc.do_update(); + } + next_status_update_time = ui_clock::now() + 100ms; + } + if (filter_source->fss_editing) { + filter_source->fss_match_view.set_needs_update(); + } + breadcrumb_view.do_update(); + // These updates need to be done last so their readline views can + // put the cursor in the right place. + switch (lnav_data.ld_mode) { + case ln_mode_t::FILTER: + case ln_mode_t::SEARCH_FILTERS: + lnav_data.ld_filter_view.set_needs_update(); + lnav_data.ld_filter_view.do_update(); + break; + case ln_mode_t::SEARCH_FILES: + case ln_mode_t::FILES: + lnav_data.ld_files_view.set_needs_update(); + lnav_data.ld_files_view.do_update(); + break; + default: + break; + } + if (lnav_data.ld_mode != ln_mode_t::FILTER + && lnav_data.ld_mode != ln_mode_t::FILES) + { + rlc->do_update(); + } + refresh(); + + if (lnav_data.ld_session_loaded) { + // Only take input from the user after everything has loaded. + pollfds.push_back((struct pollfd){STDIN_FILENO, POLLIN, 0}); + if (lnav_data.ld_initial_build) { + switch (lnav_data.ld_mode) { + case ln_mode_t::COMMAND: + case ln_mode_t::SEARCH: + case ln_mode_t::SEARCH_FILTERS: + case ln_mode_t::SEARCH_FILES: + case ln_mode_t::SEARCH_SPECTRO_DETAILS: + case ln_mode_t::SQL: + case ln_mode_t::EXEC: + case ln_mode_t::USER: + if (rlc->consume_ready_for_input()) { + // log_debug("waiting for readline input") + view_curses::awaiting_user_input(); + } + break; + default: + // log_debug("waiting for paging input"); + view_curses::awaiting_user_input(); + break; + } + } + } + + ps->update_poll_set(pollfds); + ui_now = ui_clock::now(); + auto poll_to + = (!changes && ui_now < loop_deadline && session_stage >= 1) + ? std::chrono::duration_cast<std::chrono::milliseconds>( + loop_deadline - ui_now) + : 0ms; + + if (initial_rescan_completed + && lnav_data.ld_input_dispatcher.in_escape() && poll_to > 15ms) + { + poll_to = 15ms; + } + // log_debug("poll %d %d", changes, poll_to.count()); + rc = poll(&pollfds[0], pollfds.size(), poll_to.count()); + + gettimeofday(¤t_time, nullptr); + lnav_data.ld_input_dispatcher.poll(current_time); + + if (rc < 0) { + switch (errno) { + case 0: + case EINTR: + break; + + default: + log_error("select %s", strerror(errno)); + lnav_data.ld_looping = false; + break; + } + } else { + auto in_revents = pollfd_revents(pollfds, STDIN_FILENO); + + if (in_revents & (POLLHUP | POLLNVAL)) { + log_info("stdin has been closed, exiting..."); + lnav_data.ld_looping = false; + } else if (in_revents & POLLIN) { + int ch; + + auto old_gen + = lnav_data.ld_active_files.fc_files_generation; + while ((ch = getch()) != ERR) { + lnav_data.ld_user_message_source.clear(); + + alerter::singleton().new_input(ch); + + lnav_data.ld_input_dispatcher.new_input(current_time, + ch); + + lnav_data.ld_view_stack.top() | [ch](auto tc) { + lnav_data.ld_key_repeat_history.update( + ch, tc->get_top()); + }; + + if (!lnav_data.ld_looping) { + // No reason to keep processing input after the + // user has quit. The view stack will also be + // empty, which will cause issues. + break; + } + } + + next_status_update_time = ui_clock::now(); + switch (lnav_data.ld_mode) { + case ln_mode_t::PAGING: + case ln_mode_t::FILTER: + case ln_mode_t::FILES: + case ln_mode_t::SPECTRO_DETAILS: + case ln_mode_t::BUSY: + if (old_gen + == lnav_data.ld_active_files + .fc_files_generation) + { + next_rescan_time = next_status_update_time + 1s; + } else { + next_rescan_time = next_status_update_time; + } + break; + case ln_mode_t::BREADCRUMBS: + case ln_mode_t::COMMAND: + case ln_mode_t::SEARCH: + case ln_mode_t::SEARCH_FILTERS: + case ln_mode_t::SEARCH_FILES: + case ln_mode_t::SEARCH_SPECTRO_DETAILS: + case ln_mode_t::CAPTURE: + case ln_mode_t::SQL: + case ln_mode_t::EXEC: + case ln_mode_t::USER: + next_rescan_time = next_status_update_time + 1min; + break; + } + next_rebuild_time = next_rescan_time; + } + + auto old_mode = lnav_data.ld_mode; + auto old_file_names_size + = lnav_data.ld_active_files.fc_file_names.size(); + + ps->check_poll_set(pollfds); + lnav_data.ld_view_stack.top() | + [](auto tc) { lnav_data.ld_bottom_source.update_hits(tc); }; + + if (lnav_data.ld_mode != old_mode) { + switch (lnav_data.ld_mode) { + case ln_mode_t::PAGING: + case ln_mode_t::FILTER: + case ln_mode_t::FILES: + next_rescan_time = next_status_update_time + 1s; + next_rebuild_time = next_rescan_time; + break; + default: + break; + } + } + if (old_file_names_size + != lnav_data.ld_active_files.fc_file_names.size()) + { + next_rescan_time = ui_clock::now(); + next_rebuild_time = next_rescan_time; + next_status_update_time = next_rescan_time; + } + } + + if (timer.time_to_update(overlay_counter)) { + lnav_data.ld_view_stack.top() | + [](auto tc) { tc->set_overlay_needs_update(); }; + } + + if (initial_rescan_completed && session_stage < 2 + && (!lnav_data.ld_initial_build + || timer.fade_diff(index_counter) == 0)) + { + if (lnav_data.ld_mode == ln_mode_t::PAGING) { + timer.start_fade(index_counter, 1); + } else { + timer.start_fade(index_counter, 3); + } + // log_debug("initial build rebuild"); + changes += rebuild_indexes(loop_deadline); + if (!lnav_data.ld_initial_build + && lnav_data.ld_log_source.text_line_count() == 0 + && lnav_data.ld_text_source.text_line_count() > 0) + { + ensure_view(&lnav_data.ld_views[LNV_TEXT]); + lnav_data.ld_rl_view->set_alt_value(HELP_MSG_2( + f, F, "to switch to the next/previous file")); + } + if (lnav_data.ld_view_stack.top().value_or(nullptr) + == &lnav_data.ld_views[LNV_TEXT] + && lnav_data.ld_text_source.empty() + && lnav_data.ld_log_source.text_line_count() > 0) + { + textview_curses* tc_log = &lnav_data.ld_views[LNV_LOG]; + lnav_data.ld_view_stack.pop_back(); + + lnav_data.ld_views[LNV_LOG].set_top( + tc_log->get_top_for_last_row()); + } + if (!lnav_data.ld_initial_build + && lnav_data.ld_log_source.text_line_count() == 0 + && !lnav_data.ld_active_files.fc_other_files.empty() + && std::any_of( + lnav_data.ld_active_files.fc_other_files.begin(), + lnav_data.ld_active_files.fc_other_files.end(), + [](const auto& pair) { + return pair.second.ofd_format + == file_format_t::SQLITE_DB; + })) + { + ensure_view(&lnav_data.ld_views[LNV_SCHEMA]); + } + + if (!lnav_data.ld_initial_build && lnav_data.ld_show_help_view) + { + toggle_view(&lnav_data.ld_views[LNV_HELP]); + lnav_data.ld_initial_build = true; + } + if (!lnav_data.ld_initial_build + && lnav_data.ld_active_files.fc_file_names.empty()) + { + lnav_data.ld_initial_build = true; + } + if (lnav_data.ld_log_source.text_line_count() > 0 + || lnav_data.ld_text_source.text_line_count() > 0 + || !lnav_data.ld_active_files.fc_other_files.empty()) + { + lnav_data.ld_initial_build = true; + } + + if (lnav_data.ld_initial_build) { + static bool ran_cleanup = false; + std::vector<std::pair< + Result<std::string, lnav::console::user_message>, + std::string>> + cmd_results; + + execute_init_commands(ec, cmd_results); + + if (!cmd_results.empty()) { + auto last_cmd_result = cmd_results.back(); + + if (last_cmd_result.first.isOk()) { + lnav_data.ld_rl_view->set_value( + last_cmd_result.first.unwrap()); + } else { + ec.ec_error_callback_stack.back()( + last_cmd_result.first.unwrapErr()); + } + lnav_data.ld_rl_view->set_alt_value( + last_cmd_result.second); + } + + if (!ran_cleanup) { + line_buffer::cleanup_cache(); + archive_manager::cleanup_cache(); + tailer::cleanup_cache(); + ran_cleanup = true; + } + } + + if (session_stage == 1 + && (lnav_data.ld_active_files.fc_file_names.empty() + || lnav_data.ld_log_source.text_line_count() > 0 + || lnav_data.ld_text_source.text_line_count() > 0 + || !lnav_data.ld_active_files.fc_other_files.empty())) + { + for (size_t view_index = 0; view_index < LNV__MAX; + view_index++) + { + const auto& vs + = session_data.sd_view_states[view_index]; + auto& tview = lnav_data.ld_views[view_index]; + + if (vs.vs_top > 0 && tview.get_top() == 0_vl) { + log_info("restoring %s view top: %d", + lnav_view_strings[view_index], + vs.vs_top); + lnav_data.ld_views[view_index].set_top( + vis_line_t(vs.vs_top)); + } + } + if (lnav_data.ld_mode == ln_mode_t::FILES) { + if (lnav_data.ld_active_files.fc_name_to_errors.empty()) + { + log_info("switching to paging!"); + lnav_data.ld_mode = ln_mode_t::PAGING; + lnav_data.ld_active_files.fc_files + | lnav::itertools::for_each( + &logfile::dump_stats); + } else { + lnav_data.ld_files_view.set_selection(0_vl); + } + } + session_stage += 1; + load_time_bookmarks(); + } + } + + handle_winch(); + + if (lnav_data.ld_child_terminated) { + lnav_data.ld_child_terminated = false; + + log_info("checking for terminated child processes"); + for (auto iter = lnav_data.ld_children.begin(); + iter != lnav_data.ld_children.end(); + ++iter) + { + int rc, child_stat; + + rc = waitpid(*iter, &child_stat, WNOHANG); + if (rc == -1 || rc == 0) { + continue; + } + + iter = lnav_data.ld_children.erase(iter); + } + + gather_pipers(); + } + + if (lnav_data.ld_view_stack.empty() + || (lnav_data.ld_view_stack.size() == 1 + && starting_view_stack_size == 2 + && lnav_data.ld_active_files.fc_file_names.size() + == lnav_data.ld_text_source.size())) + { + lnav_data.ld_looping = false; + } + } + } catch (readline_curses::error& e) { + log_error("error: %s", strerror(e.e_err)); + } +} + +void +wait_for_children() +{ + std::vector<struct pollfd> pollfds; + struct timeval to = {0, 333000}; + static auto* ps = injector::get<pollable_supervisor*>(); + + do { + pollfds.clear(); + + auto update_res = ps->update_poll_set(pollfds); + + if (update_res.ur_background == 0) { + break; + } + + int rc = poll(&pollfds[0], pollfds.size(), to.tv_usec / 1000); + + if (rc < 0) { + switch (errno) { + case 0: + case EINTR: + break; + default: + return; + } + } + + ps->check_poll_set(pollfds); + lnav_data.ld_view_stack.top() | + [](auto tc) { lnav_data.ld_bottom_source.update_hits(tc); }; + } while (lnav_data.ld_looping); +} + +struct mode_flags_t { + bool mf_check_configs{false}; + bool mf_install{false}; + bool mf_update_formats{false}; + bool mf_no_default{false}; + bool mf_print_warnings{false}; +}; + +static int +print_user_msgs(std::vector<lnav::console::user_message> error_list, + mode_flags_t mf) +{ + size_t warning_count = 0; + int retval = EXIT_SUCCESS; + + for (auto& iter : error_list) { + FILE* out_file; + + switch (iter.um_level) { + case lnav::console::user_message::level::raw: + case lnav::console::user_message::level::ok: + out_file = stdout; + break; + case lnav::console::user_message::level::warning: + warning_count += 1; + if (!mf.mf_print_warnings) { + continue; + } + out_file = stderr; + break; + default: + out_file = stderr; + break; + } + + lnav::console::print(out_file, iter); + if (iter.um_level == lnav::console::user_message::level::error) { + retval = EXIT_FAILURE; + } + } + + if (warning_count > 0 && !mf.mf_print_warnings + && !(lnav_data.ld_flags & LNF_HEADLESS) + && (std::chrono::system_clock::now() - lnav_data.ld_last_dot_lnav_time + > 24h)) + { + lnav::console::print( + stderr, + lnav::console::user_message::warning( + attr_line_t() + .append(lnav::roles::number(fmt::to_string(warning_count))) + .append(" issues were detected when checking lnav's " + "configuration")) + .with_help( + attr_line_t("pass ") + .append(lnav::roles::symbol("-W")) + .append(" on the command line to display the issues\n") + .append("(this message will only be displayed once a " + "day)"))); + } + + return retval; +} + +enum class verbosity_t : int { + quiet, + standard, + verbose, +}; + +struct stdin_options_t { + ghc::filesystem::path so_out; + bool so_timestamp{false}; + auto_fd so_out_fd; +}; + +int +main(int argc, char* argv[]) +{ + std::vector<lnav::console::user_message> config_errors; + std::vector<lnav::console::user_message> loader_errors; + exec_context& ec = lnav_data.ld_exec_context; + int retval = EXIT_SUCCESS; + + std::shared_ptr<piper_proc> stdin_reader; + stdin_options_t stdin_opts; + bool exec_stdin = false, load_stdin = false, stdin_captured = false; + mode_flags_t mode_flags; + const char* LANG = getenv("LANG"); + ghc::filesystem::path stdin_tmp_path; + verbosity_t verbosity = verbosity_t::standard; + + if (LANG == nullptr || strcmp(LANG, "C") == 0) { + setenv("LANG", "en_US.utf-8", 1); + } + + (void) signal(SIGPIPE, SIG_IGN); + setlocale(LC_ALL, ""); + try { + std::locale::global(std::locale("")); + } catch (const std::runtime_error& e) { + log_error("unable to set locale to ''"); + } + umask(027); + + /* Disable Lnav from being able to execute external commands if + * "LNAVSECURE" environment variable is set by the user. + */ + if (getenv("LNAVSECURE") != nullptr) { + lnav_data.ld_flags |= LNF_SECURE_MODE; + } + + lnav_data.ld_exec_context.ec_sql_callback = sql_callback; + lnav_data.ld_exec_context.ec_pipe_callback = pipe_callback; + + lnav_data.ld_program_name = argv[0]; + add_ansi_vars(ec.ec_global_vars); + + rl_readline_name = "lnav"; + lnav_data.ld_db_key_names = DEFAULT_DB_KEY_NAMES; + + stable_sort(lnav_data.ld_db_key_names.begin(), + lnav_data.ld_db_key_names.end()); + + auto dot_lnav_path = lnav::paths::dotlnav(); + std::error_code last_write_ec; + lnav_data.ld_last_dot_lnav_time + = ghc::filesystem::last_write_time(dot_lnav_path, last_write_ec); + + ensure_dotlnav(); + + log_install_handlers(); + sql_install_logger(); + + if (sqlite3_open(":memory:", lnav_data.ld_db.out()) != SQLITE_OK) { + fprintf(stderr, "error: unable to create sqlite memory database\n"); + exit(EXIT_FAILURE); + } + + { + int register_collation_functions(sqlite3 * db); + + register_sqlite_funcs(lnav_data.ld_db.in(), sqlite_registration_funcs); + register_collation_functions(lnav_data.ld_db.in()); + } + + register_environ_vtab(lnav_data.ld_db.in()); + register_static_file_vtab(lnav_data.ld_db.in()); + { + static auto vtab_modules + = injector::get<std::vector<std::shared_ptr<vtab_module_base>>>(); + + for (const auto& mod : vtab_modules) { + mod->create(lnav_data.ld_db.in()); + } + } + + register_views_vtab(lnav_data.ld_db.in()); + register_regexp_vtab(lnav_data.ld_db.in()); + register_xpath_vtab(lnav_data.ld_db.in()); + register_fstat_vtab(lnav_data.ld_db.in()); + lnav::events::register_events_tab(lnav_data.ld_db.in()); + + auto _vtab_cleanup = finally([] { + static const char* VIRT_TABLES = R"( +SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%' +)"; + + for (auto& lf : lnav_data.ld_active_files.fc_files) { + lf->close(); + } + rebuild_indexes(ui_clock::now()); + + lnav_data.ld_vtab_manager = nullptr; + + std::vector<std::string> tables_to_drop; + { + auto_mem<sqlite3_stmt> stmt(sqlite3_finalize); + bool done = false; + + sqlite3_prepare_v2( + lnav_data.ld_db.in(), VIRT_TABLES, -1, stmt.out(), nullptr); + do { + auto ret = sqlite3_step(stmt.in()); + + switch (ret) { + case SQLITE_OK: + case SQLITE_DONE: + done = true; + break; + case SQLITE_ROW: + tables_to_drop.emplace_back(fmt::format( + FMT_STRING("DROP TABLE {}"), + reinterpret_cast<const char*>( + sqlite3_column_text(stmt.in(), 0)))); + break; + } + } while (!done); + } + + // XXX + lnav_data.ld_log_source.set_preview_sql_filter(nullptr); + lnav_data.ld_log_source.set_sql_filter("", nullptr); + lnav_data.ld_log_source.set_sql_marker("", nullptr); + lnav_config_listener::unload_all(); + + { + sqlite3_stmt* stmt_iter = nullptr; + + do { + stmt_iter = sqlite3_next_stmt(lnav_data.ld_db.in(), stmt_iter); + if (stmt_iter != nullptr) { + const auto* stmt_sql = sqlite3_sql(stmt_iter); + + log_warning("unfinalized SQL statement: %s", stmt_sql); + ensure(false); + } + } while (stmt_iter != nullptr); + } + + for (auto& drop_stmt : tables_to_drop) { + sqlite3_exec(lnav_data.ld_db.in(), + drop_stmt.c_str(), + nullptr, + nullptr, + nullptr); + } +#if defined(HAVE_SQLITE3_DROP_MODULES) + sqlite3_drop_modules(lnav_data.ld_db.in(), nullptr); +#endif + + lnav_data.ld_db.reset(); + }); + +#ifdef HAVE_LIBCURL + curl_global_init(CURL_GLOBAL_DEFAULT); +#endif + + static const std::string DEFAULT_DEBUG_LOG = "/dev/null"; + + lnav_data.ld_debug_log_name = DEFAULT_DEBUG_LOG; + + std::vector<std::string> file_args; + std::vector<lnav::console::user_message> arg_errors; + + CLI::App app{"The Logfile Navigator"}; + + app.add_option("-d", + lnav_data.ld_debug_log_name, + "Write debug messages to the given file.") + ->type_name("FILE"); + app.add_flag("-q{0},-v{2}", verbosity, "Control the verbosity"); + app.set_version_flag("-V,--version"); + app.footer(fmt::format(FMT_STRING("Version: {}"), VCS_PACKAGE_STRING)); + + std::shared_ptr<lnav::management::operations> mmode_ops; + + if (argc < 2 || strcmp(argv[1], "-m") != 0) { + app.add_flag("-H", lnav_data.ld_show_help_view, "show help"); + app.add_option("-I", lnav_data.ld_config_paths, "include paths") + ->check(CLI::ExistingDirectory) + ->check([&arg_errors](std::string inc_path) -> std::string { + if (access(inc_path.c_str(), X_OK) != 0) { + arg_errors.emplace_back( + lnav::console::user_message::error( + attr_line_t("invalid configuration directory: ") + .append(lnav::roles::file(inc_path))) + .with_errno_reason()); + return "unreadable"; + } + + return std::string(); + }) + ->allow_extra_args(false); + app.add_flag("-C", mode_flags.mf_check_configs, "check"); + auto* install_flag + = app.add_flag("-i", mode_flags.mf_install, "install"); + app.add_flag("-u", mode_flags.mf_update_formats, "update"); + auto* write_flag = app.add_option("-w", stdin_opts.so_out, "write"); + auto* ts_flag + = app.add_flag("-t", stdin_opts.so_timestamp, "timestamp"); + auto* no_default_flag + = app.add_flag("-N", mode_flags.mf_no_default, "no def"); + auto* rotated_flag = app.add_flag( + "-R", lnav_data.ld_active_files.fc_rotated, "rotated"); + auto* recurse_flag = app.add_flag( + "-r", lnav_data.ld_active_files.fc_recursive, "recurse"); + app.add_flag("-W", mode_flags.mf_print_warnings); + auto* headless_flag = app.add_flag( + "-n", + [](size_t count) { lnav_data.ld_flags |= LNF_HEADLESS; }, + "headless"); + auto* file_opt = app.add_option("file", file_args, "files"); + + auto wait_cb = [](size_t count) { + char b; + if (isatty(STDIN_FILENO) && read(STDIN_FILENO, &b, 1) == -1) { + perror("Read key from STDIN"); + } + }; + app.add_flag("-S", wait_cb); + + auto cmd_appender + = [](std::string cmd) { lnav_data.ld_commands.emplace_back(cmd); }; + auto cmd_validator = [&arg_errors](std::string cmd) -> std::string { + static const auto ARG_SRC + = intern_string::lookup("command-line argument"); + + if (cmd.empty()) { + return "empty commands are not allowed"; + } + + switch (cmd[0]) { + case ':': + case '/': + case ';': + case '|': + break; + default: + cmd.push_back(' '); + arg_errors.emplace_back( + lnav::console::user_message::error( + attr_line_t("invalid value for ") + .append_quoted("-c"_symbol) + .append(" option")) + .with_snippet(lnav::console::snippet::from( + ARG_SRC, + attr_line_t() + .append(" -c "_quoted_code) + .append(lnav::roles::quoted_code(cmd)) + .append("\n") + .append(4, ' ') + .append(lnav::roles::error( + "^ command type prefix " + "is missing")))) + .with_help(command_arg_help())); + return "invalid prefix"; + } + return std::string(); + }; + auto* cmd_opt = app.add_option("-c") + ->check(cmd_validator) + ->each(cmd_appender) + ->allow_extra_args(false) + ->trigger_on_parse(true); + + auto file_appender = [](std::string file_path) { + lnav_data.ld_commands.emplace_back( + fmt::format(FMT_STRING("|{}"), file_path)); + }; + auto* exec_file_opt = app.add_option("-f") + ->trigger_on_parse(true) + ->allow_extra_args(false) + ->each(file_appender); + + install_flag->needs(file_opt); + install_flag->excludes(write_flag, + ts_flag, + no_default_flag, + rotated_flag, + recurse_flag, + headless_flag, + cmd_opt, + exec_file_opt); + } + + auto is_mmode = argc >= 2 && strcmp(argv[1], "-m") == 0; + try { + if (is_mmode) { + mmode_ops = lnav::management::describe_cli(app, argc, argv); + } else { + app.parse(argc, argv); + } + } catch (const CLI::CallForHelp& e) { + if (is_mmode) { + fmt::print("{}\n", app.help()); + } else { + usage(); + } + return EXIT_SUCCESS; + } catch (const CLI::CallForVersion& e) { + fmt::print("{}\n", VCS_PACKAGE_STRING); + return EXIT_SUCCESS; + } catch (const CLI::ParseError& e) { + if (!arg_errors.empty()) { + print_user_msgs(arg_errors, mode_flags); + return e.get_exit_code(); + } + + lnav::console::print( + stderr, + lnav::console::user_message::error("invalid command-line arguments") + .with_reason(e.what())); + return e.get_exit_code(); + } + + lnav_data.ld_config_paths.insert(lnav_data.ld_config_paths.begin(), + lnav::paths::dotlnav()); + lnav_data.ld_config_paths.insert(lnav_data.ld_config_paths.begin(), + SYSCONFDIR "/lnav"); + lnav_data.ld_config_paths.insert(lnav_data.ld_config_paths.begin(), + "/etc/lnav"); + + if (lnav_data.ld_debug_log_name != DEFAULT_DEBUG_LOG) { + lnav_log_level = lnav_log_level_t::TRACE; + } + + lnav_log_file = make_optional_from_nullable( + fopen(lnav_data.ld_debug_log_name.c_str(), "a")); + log_info("lnav started"); + + { + static auto builtin_formats + = injector::get<std::vector<std::shared_ptr<log_format>>>(); + auto& root_formats = log_format::get_root_formats(); + + log_format::get_root_formats().insert(root_formats.begin(), + builtin_formats.begin(), + builtin_formats.end()); + builtin_formats.clear(); + } + + load_config(lnav_data.ld_config_paths, config_errors); + if (!config_errors.empty()) { + if (print_user_msgs(config_errors, mode_flags) != EXIT_SUCCESS) { + return EXIT_FAILURE; + } + } + add_global_vars(ec); + + if (mode_flags.mf_update_formats) { + if (!update_installs_from_git()) { + return EXIT_FAILURE; + } + return EXIT_SUCCESS; + } + + if (mode_flags.mf_install) { + auto formats_installed_path + = lnav::paths::dotlnav() / "formats/installed"; + auto configs_installed_path + = lnav::paths::dotlnav() / "configs/installed"; + + if (argc == 0) { + const auto install_reason + = attr_line_t("the ") + .append("-i"_symbol) + .append( + " option expects one or more log format definition " + "files to install in your lnav configuration " + "directory"); + const auto install_help + = attr_line_t( + "log format definitions are JSON files that tell lnav " + "how to understand log files\n") + .append( + "See: https://docs.lnav.org/en/latest/formats.html"); + + lnav::console::print(stderr, + lnav::console::user_message::error( + "missing format files to install") + .with_reason(install_reason) + .with_help(install_help)); + return EXIT_FAILURE; + } + + for (auto& file_path : file_args) { + if (endswith(file_path, ".git")) { + if (!install_from_git(file_path)) { + return EXIT_FAILURE; + } + continue; + } + + if (endswith(file_path, ".sql")) { + auto sql_path = ghc::filesystem::path(file_path); + auto read_res = lnav::filesystem::read_file(sql_path); + if (read_res.isErr()) { + lnav::console::print( + stderr, + lnav::console::user_message::error( + attr_line_t("unable to read SQL file: ") + .append(lnav::roles::file(file_path))) + .with_reason(read_res.unwrapErr())); + return EXIT_FAILURE; + } + + auto dst_path = formats_installed_path / sql_path.filename(); + auto write_res + = lnav::filesystem::write_file(dst_path, read_res.unwrap()); + if (write_res.isErr()) { + lnav::console::print( + stderr, + lnav::console::user_message::error( + attr_line_t("unable to write SQL file: ") + .append(lnav::roles::file(file_path))) + .with_reason(write_res.unwrapErr())); + return EXIT_FAILURE; + } + + lnav::console::print( + stderr, + lnav::console::user_message::ok( + attr_line_t("installed -- ") + .append(lnav::roles::file(dst_path)))); + continue; + } + + if (file_path == "extra") { + install_extra_formats(); + continue; + } + + auto file_type_result = detect_config_file_type(file_path); + if (file_type_result.isErr()) { + lnav::console::print( + stderr, + lnav::console::user_message::error( + attr_line_t("unable to open configuration file: ") + .append(lnav::roles::file(file_path))) + .with_reason(file_type_result.unwrapErr())); + return EXIT_FAILURE; + } + auto file_type = file_type_result.unwrap(); + + auto src_path = ghc::filesystem::path(file_path); + ghc::filesystem::path dst_name; + if (file_type == config_file_type::CONFIG) { + dst_name = src_path.filename(); + } else { + auto format_list = load_format_file(src_path, loader_errors); + + if (!loader_errors.empty()) { + if (print_user_msgs(loader_errors, mode_flags) + != EXIT_SUCCESS) + { + return EXIT_FAILURE; + } + } + if (format_list.empty()) { + lnav::console::print( + stderr, + lnav::console::user_message::error( + attr_line_t("invalid format file: ") + .append(lnav::roles::file(src_path.string()))) + .with_reason("there must be at least one format " + "definition in the file")); + return EXIT_FAILURE; + } + + dst_name = format_list[0].to_string() + ".json"; + } + auto dst_path = (file_type == config_file_type::CONFIG + ? configs_installed_path + : formats_installed_path) + / dst_name; + auto_fd in_fd, out_fd; + + if ((in_fd = open(file_path.c_str(), O_RDONLY)) == -1) { + perror("unable to open file to install"); + } else if ((out_fd = lnav::filesystem::openp( + dst_path, O_WRONLY | O_CREAT | O_TRUNC, 0644)) + == -1) + { + fprintf(stderr, + "error: unable to open destination: %s -- %s\n", + dst_path.c_str(), + strerror(errno)); + } else { + char buffer[2048]; + ssize_t rc; + + while ((rc = read(in_fd, buffer, sizeof(buffer))) > 0) { + ssize_t remaining = rc, written; + + while (remaining > 0) { + written = write(out_fd, buffer, rc); + if (written == -1) { + fprintf(stderr, + "error: unable to install file -- %s\n", + strerror(errno)); + exit(EXIT_FAILURE); + } + + remaining -= written; + } + } + + lnav::console::print( + stderr, + lnav::console::user_message::ok( + attr_line_t("installed -- ") + .append(lnav::roles::file(dst_path)))); + } + } + return EXIT_SUCCESS; + } + + if (lnav_data.ld_flags & LNF_SECURE_MODE) { + if ((sqlite3_set_authorizer( + lnav_data.ld_db.in(), sqlite_authorizer, nullptr)) + != SQLITE_OK) + { + fprintf(stderr, "error: unable to attach sqlite authorizer\n"); + exit(EXIT_FAILURE); + } + } + + /* If we statically linked against an ncurses library that had a non- + * standard path to the terminfo database, we need to set this variable + * so that it will try the default path. + */ + setenv("TERMINFO_DIRS", + "/usr/share/terminfo:/lib/terminfo:/usr/share/lib/terminfo", + 0); + + auto* filter_source = injector::get<filter_sub_source*>(); + lnav_data.ld_vtab_manager = std::make_unique<log_vtab_manager>( + lnav_data.ld_db, lnav_data.ld_views[LNV_LOG], lnav_data.ld_log_source); + + lnav_data.ld_log_source.set_exec_context(&lnav_data.ld_exec_context); + lnav_data.ld_views[LNV_HELP] + .set_sub_source(&lnav_data.ld_help_source) + .set_word_wrap(false); + auto log_fos = new field_overlay_source(lnav_data.ld_log_source, + lnav_data.ld_text_source); + if (lnav_data.ld_flags & LNF_HEADLESS) { + log_fos->fos_show_status = false; + } + log_fos->fos_contexts.emplace("", false, true); + lnav_data.ld_views[LNV_LOG] + .set_sub_source(&lnav_data.ld_log_source) + .set_delegate(std::make_shared<action_delegate>( + lnav_data.ld_log_source, + [](auto child_pid) { lnav_data.ld_children.push_back(child_pid); }, + [](const auto& desc, auto pp) { + lnav_data.ld_pipers.push_back(pp); + lnav_data.ld_active_files.fc_file_names[desc].with_fd( + pp->get_fd()); + lnav_data.ld_files_to_front.template emplace_back(desc, 0_vl); + })) + .add_input_delegate(lnav_data.ld_log_source) + .set_tail_space(2_vl) + .set_overlay_source(log_fos); + lnav_data.ld_views[LNV_TEXT].set_sub_source(&lnav_data.ld_text_source); + lnav_data.ld_views[LNV_HISTOGRAM].set_sub_source( + &lnav_data.ld_hist_source2); + lnav_data.ld_views[LNV_DB].set_sub_source(&lnav_data.ld_db_row_source); + lnav_data.ld_db_overlay.dos_labels = &lnav_data.ld_db_row_source; + lnav_data.ld_views[LNV_DB].set_overlay_source(&lnav_data.ld_db_overlay); + lnav_data.ld_spectro_source = std::make_unique<spectrogram_source>(); + lnav_data.ld_views[LNV_SPECTRO] + .set_sub_source(lnav_data.ld_spectro_source.get()) + .set_overlay_source(lnav_data.ld_spectro_source.get()) + .add_input_delegate(*lnav_data.ld_spectro_source) + .set_tail_space(4_vl); + lnav_data.ld_views[LNV_SPECTRO].set_selectable(true); + + lnav_data.ld_doc_view.set_sub_source(&lnav_data.ld_doc_source); + lnav_data.ld_example_view.set_sub_source(&lnav_data.ld_example_source); + lnav_data.ld_match_view.set_sub_source(&lnav_data.ld_match_source); + lnav_data.ld_preview_view.set_sub_source(&lnav_data.ld_preview_source); + lnav_data.ld_filter_view.set_sub_source(filter_source) + .add_input_delegate(*filter_source) + .add_child_view(&filter_source->fss_match_view) + .add_child_view(filter_source->fss_editor.get()); + lnav_data.ld_files_view.set_sub_source(&lnav_data.ld_files_source) + .add_input_delegate(lnav_data.ld_files_source); + lnav_data.ld_user_message_view.set_sub_source( + &lnav_data.ld_user_message_source); + + for (int lpc = 0; lpc < LNV__MAX; lpc++) { + lnav_data.ld_views[lpc].set_gutter_source(new log_gutter_source()); + } + + { + hist_source2& hs = lnav_data.ld_hist_source2; + + lnav_data.ld_log_source.set_index_delegate(new hist_index_delegate( + lnav_data.ld_hist_source2, lnav_data.ld_views[LNV_HISTOGRAM])); + hs.init(); + lnav_data.ld_zoom_level = 3; + hs.set_time_slice(ZOOM_LEVELS[lnav_data.ld_zoom_level]); + } + + for (int lpc = 0; lpc < LNV__MAX; lpc++) { + lnav_data.ld_views[lpc].set_title(lnav_view_titles[lpc]); + } + + load_formats(lnav_data.ld_config_paths, loader_errors); + + { + auto_mem<char, sqlite3_free> errmsg; + + if (sqlite3_exec(lnav_data.ld_db.in(), + init_sql.to_string_fragment().data(), + nullptr, + nullptr, + errmsg.out()) + != SQLITE_OK) + { + fprintf(stderr, + "error: unable to execute DB init -- %s\n", + errmsg.in()); + } + } + + lnav_data.ld_vtab_manager->register_vtab(std::make_shared<all_logs_vtab>()); + lnav_data.ld_vtab_manager->register_vtab( + std::make_shared<log_format_vtab_impl>( + *log_format::find_root_format("generic_log"))); + + for (auto& iter : log_format::get_root_formats()) { + auto lvi = iter->get_vtab_impl(); + + if (lvi != nullptr) { + lnav_data.ld_vtab_manager->register_vtab(lvi); + } + } + + load_format_extra(lnav_data.ld_db.in(), + ec.ec_global_vars, + lnav_data.ld_config_paths, + loader_errors); + load_format_vtabs(lnav_data.ld_vtab_manager.get(), loader_errors); + + if (!loader_errors.empty()) { + if (print_user_msgs(loader_errors, mode_flags) != EXIT_SUCCESS) { + if (mmode_ops == nullptr) { + return EXIT_FAILURE; + } + } + } + + if (mmode_ops) { + auto perform_res = lnav::management::perform(mmode_ops); + + return print_user_msgs(perform_res, mode_flags); + } + + if (!mode_flags.mf_check_configs && !lnav_data.ld_show_help_view) { + DEFAULT_FILES.emplace_back("var/log/messages"); + DEFAULT_FILES.emplace_back("var/log/system.log"); + DEFAULT_FILES.emplace_back("var/log/syslog"); + DEFAULT_FILES.emplace_back("var/log/syslog.log"); + } + + init_lnav_commands(lnav_commands); + + lnav_data.ld_looping = true; + lnav_data.ld_mode = ln_mode_t::PAGING; + + if ((isatty(STDIN_FILENO) || is_dev_null(STDIN_FILENO)) && file_args.empty() + && !mode_flags.mf_no_default) + { + char start_dir[FILENAME_MAX]; + + if (getcwd(start_dir, sizeof(start_dir)) == nullptr) { + perror("getcwd"); + } else { + do { + if (!append_default_files()) { + retval = EXIT_FAILURE; + } + } while (lnav_data.ld_active_files.fc_file_names.empty() + && change_to_parent_dir()); + + if (chdir(start_dir) == -1) { + perror("chdir(start_dir)"); + } + } + } + + { + const auto internals_dir_opt = getenv_opt("DUMP_INTERNALS_DIR"); + + if (internals_dir_opt) { + lnav::dump_internals(internals_dir_opt.value()); + + return EXIT_SUCCESS; + } + } + + if (file_args.empty()) { + load_stdin = true; + } + + for (auto& file_path : file_args) { + auto file_path_without_trailer = file_path; + auto file_loc = file_location_t{mapbox::util::no_init{}}; + auto_mem<char> abspath; + struct stat st; + + auto colon_index = file_path.rfind(':'); + if (colon_index != std::string::npos) { + file_path_without_trailer = file_path.substr(0, colon_index); + auto top_range = scn::string_view{&file_path[colon_index + 1], + &(*file_path.cend())}; + auto scan_res = scn::scan_value<int>(top_range); + + if (scan_res) { + file_path_without_trailer = file_path.substr(0, colon_index); + file_loc = vis_line_t(scan_res.value()); + } else { + log_warning( + "failed to parse line number from file path with colon: %s", + file_path.c_str()); + } + } + auto hash_index = file_path.rfind('#'); + if (hash_index != std::string::npos) { + file_loc = file_path.substr(hash_index); + file_path_without_trailer = file_path.substr(0, hash_index); + } + if (stat(file_path_without_trailer.c_str(), &st) == 0) { + file_path = file_path_without_trailer; + } + + if (file_path == "-") { + load_stdin = true; + } +#ifdef HAVE_LIBCURL + else if (is_url(file_path)) + { + auto ul = std::make_shared<url_loader>(file_path); + + lnav_data.ld_active_files.fc_file_names[file_path].with_fd( + ul->copy_fd()); + isc::to<curl_looper&, services::curl_streamer_t>().send( + [ul](auto& clooper) { clooper.add_request(ul); }); + } +#endif + else if (is_glob(file_path)) + { + lnav_data.ld_active_files.fc_file_names[file_path].with_tail( + !(lnav_data.ld_flags & LNF_HEADLESS)); + } else if (stat(file_path.c_str(), &st) == -1) { + if (file_path.find(':') != std::string::npos) { + lnav_data.ld_active_files.fc_file_names[file_path].with_tail( + !(lnav_data.ld_flags & LNF_HEADLESS)); + } else { + lnav::console::print( + stderr, + lnav::console::user_message::error( + attr_line_t("unable to open file: ") + .append(lnav::roles::file(file_path))) + .with_errno_reason()); + retval = EXIT_FAILURE; + } + } else if (access(file_path.c_str(), R_OK) == -1) { + lnav::console::print( + stderr, + lnav::console::user_message::error( + attr_line_t("file exists, but is not readable: ") + .append(lnav::roles::file(file_path))) + .with_errno_reason()); + retval = EXIT_FAILURE; + } else if (S_ISFIFO(st.st_mode)) { + auto_fd fifo_fd; + + if ((fifo_fd = open(file_path.c_str(), O_RDONLY)) == -1) { + lnav::console::print( + stderr, + lnav::console::user_message::error( + attr_line_t("cannot open fifo: ") + .append(lnav::roles::file(file_path))) + .with_errno_reason()); + retval = EXIT_FAILURE; + } else { + auto fifo_tmp_fd + = lnav::filesystem::open_temp_file( + ghc::filesystem::temp_directory_path() + / "lnav.fifo.XXXXXX") + .map([](auto&& pair) { + ghc::filesystem::remove(pair.first); + + return std::move(pair.second); + }) + .expect("Cannot create temporary file for FIFO"); + auto fifo_piper = std::make_shared<piper_proc>( + std::move(fifo_fd), false, std::move(fifo_tmp_fd)); + auto fifo_out_fd = fifo_piper->get_fd(); + auto desc = fmt::format(FMT_STRING("FIFO [{}]"), + lnav_data.ld_fifo_counter++); + + lnav_data.ld_active_files.fc_file_names[desc].with_fd( + std::move(fifo_out_fd)); + lnav_data.ld_pipers.push_back(fifo_piper); + } + } else if ((abspath = realpath(file_path.c_str(), nullptr)) == nullptr) + { + perror("Cannot find file"); + retval = EXIT_FAILURE; + } else if (S_ISDIR(st.st_mode)) { + std::string dir_wild(abspath.in()); + + if (dir_wild[dir_wild.size() - 1] == '/') { + dir_wild.resize(dir_wild.size() - 1); + } + lnav_data.ld_active_files.fc_file_names.emplace( + dir_wild + "/*", logfile_open_options()); + } else { + lnav_data.ld_active_files.fc_file_names.emplace( + abspath.in(), logfile_open_options()); + if (file_loc.valid()) { + lnav_data.ld_files_to_front.emplace_back(abspath.in(), + file_loc); + } + } + } + + if (mode_flags.mf_check_configs) { + rescan_files(true); + for (auto& lf : lnav_data.ld_active_files.fc_files) { + logfile::rebuild_result_t rebuild_result; + + do { + rebuild_result = lf->rebuild_index(); + } while (rebuild_result == logfile::rebuild_result_t::NEW_LINES + || rebuild_result == logfile::rebuild_result_t::NEW_ORDER); + auto fmt = lf->get_format(); + if (fmt == nullptr) { + fprintf(stderr, + "error:%s:no format found for file\n", + lf->get_filename().c_str()); + retval = EXIT_FAILURE; + continue; + } + for (auto line_iter = lf->begin(); line_iter != lf->end(); + ++line_iter) + { + if (line_iter->get_msg_level() != log_level_t::LEVEL_INVALID) { + continue; + } + + size_t partial_len; + + auto read_result = lf->read_line(line_iter); + if (read_result.isErr()) { + continue; + } + shared_buffer_ref sbr = read_result.unwrap(); + if (fmt->scan_for_partial(sbr, partial_len)) { + long line_number = distance(lf->begin(), line_iter); + std::string full_line(sbr.get_data(), sbr.length()); + std::string partial_line(sbr.get_data(), partial_len); + + fprintf(stderr, + "error:%s:%ld:line did not match format %s\n", + lf->get_filename().c_str(), + line_number, + fmt->get_pattern_path(line_number).c_str()); + fprintf(stderr, + "error:%s:%ld: line -- %s\n", + lf->get_filename().c_str(), + line_number, + full_line.c_str()); + if (partial_len > 0) { + fprintf(stderr, + "error:%s:%ld:partial match -- %s\n", + lf->get_filename().c_str(), + line_number, + partial_line.c_str()); + } else { + fprintf(stderr, + "error:%s:%ld:no partial match found\n", + lf->get_filename().c_str(), + line_number); + } + retval = EXIT_FAILURE; + } + } + } + return retval; + } + + if (lnav_data.ld_flags & LNF_HEADLESS || mode_flags.mf_check_configs) { + } else if (!isatty(STDOUT_FILENO)) { + lnav::console::print( + stderr, + lnav::console::user_message::error( + "unable to display interactive text UI") + .with_reason("stdout is not a TTY") + .with_help(attr_line_t("pass the ") + .append("-n"_symbol) + .append(" option to run lnav in headless mode " + "or don't redirect stdout"))); + retval = EXIT_FAILURE; + } + + if (load_stdin && !isatty(STDIN_FILENO) && !is_dev_null(STDIN_FILENO) + && !exec_stdin) + { + if (stdin_opts.so_out.empty()) { + auto pattern + = lnav::paths::dotlnav() / "stdin-captures/stdin.XXXXXX"; + + auto open_result = lnav::filesystem::open_temp_file(pattern); + if (open_result.isErr()) { + fprintf(stderr, + "Unable to open temporary file for stdin: %s", + open_result.unwrapErr().c_str()); + return EXIT_FAILURE; + } + + auto temp_pair = open_result.unwrap(); + stdin_tmp_path = temp_pair.first; + stdin_opts.so_out_fd = std::move(temp_pair.second); + } else { + auto open_res = lnav::filesystem::create_file( + stdin_opts.so_out, O_RDWR | O_TRUNC, 0600); + if (open_res.isErr()) { + fmt::print(stderr, "error: {}\n", open_res.unwrapErr()); + return EXIT_FAILURE; + } + + stdin_opts.so_out_fd = open_res.unwrap(); + } + + stdin_captured = true; + stdin_reader + = std::make_shared<piper_proc>(auto_fd(STDIN_FILENO), + stdin_opts.so_timestamp, + std::move(stdin_opts.so_out_fd)); + lnav_data.ld_active_files.fc_file_names["stdin"] + .with_fd(stdin_reader->get_fd()) + .with_include_in_session(false); + lnav_data.ld_pipers.push_back(stdin_reader); + } + + if (!isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)) { + if (dup2(STDOUT_FILENO, STDIN_FILENO) == -1) { + perror("cannot dup stdout to stdin"); + } + } + + if (retval == EXIT_SUCCESS + && lnav_data.ld_active_files.fc_file_names.empty() + && lnav_data.ld_commands.empty() + && !(lnav_data.ld_show_help_view || mode_flags.mf_no_default)) + { + lnav::console::print( + stderr, + lnav::console::user_message::error("nothing to do") + .with_reason("no files given or default files found") + .with_help( + attr_line_t("use the ") + .append_quoted(lnav::roles::keyword("-N")) + .append( + " option to open lnav without loading any files"))); + retval = EXIT_FAILURE; + } + + if (retval == EXIT_SUCCESS) { + isc::supervisor root_superv(injector::get<isc::service_list>()); + + try { + char pcre2_version[128]; + + pcre2_config(PCRE2_CONFIG_VERSION, pcre2_version); + log_info("startup: %s", VCS_PACKAGE_STRING); + log_host_info(); + log_info("Libraries:"); +#ifdef HAVE_BZLIB_H + log_info(" bzip=%s", BZ2_bzlibVersion()); +#endif +#ifdef HAVE_LIBCURL + log_info(" curl=%s (%s)", LIBCURL_VERSION, LIBCURL_TIMESTAMP); +#endif +#ifdef HAVE_ARCHIVE_H + log_info(" libarchive=%d", ARCHIVE_VERSION_NUMBER); +#endif + log_info(" ncurses=%s", NCURSES_VERSION); + log_info(" pcre2=%s", pcre2_version); + log_info(" readline=%s", rl_library_version); + log_info(" sqlite=%s", sqlite3_version); + log_info(" zlib=%s", zlibVersion()); + log_info("lnav_data:"); + log_info(" flags=%x", lnav_data.ld_flags); + log_info(" commands:"); + for (auto cmd_iter = lnav_data.ld_commands.begin(); + cmd_iter != lnav_data.ld_commands.end(); + ++cmd_iter) + { + log_info(" %s", cmd_iter->c_str()); + } + log_info(" files:"); + for (auto file_iter + = lnav_data.ld_active_files.fc_file_names.begin(); + file_iter != lnav_data.ld_active_files.fc_file_names.end(); + ++file_iter) + { + log_info(" %s", file_iter->first.c_str()); + } + + if (lnav_data.ld_flags & LNF_HEADLESS) { + std::vector< + std::pair<Result<std::string, lnav::console::user_message>, + std::string>> + cmd_results; + textview_curses *log_tc, *text_tc, *tc; + bool output_view = true; + + view_colors::init(true); + rescan_files(true); + if (!lnav_data.ld_active_files.fc_name_to_errors.empty()) { + for (const auto& pair : + lnav_data.ld_active_files.fc_name_to_errors) + { + lnav::console::print( + stderr, + lnav::console::user_message::error( + attr_line_t("unable to open file: ") + .append(lnav::roles::file(pair.first))) + .with_reason(pair.second.fei_description)); + } + + return EXIT_FAILURE; + } + init_session(); + lnav_data.ld_exec_context.set_output("stdout", stdout, nullptr); + alerter::singleton().enabled(false); + + log_tc = &lnav_data.ld_views[LNV_LOG]; + log_tc->set_height(24_vl); + lnav_data.ld_view_stack.push_back(log_tc); + // Read all of stdin + wait_for_pipers(); + rebuild_indexes_repeatedly(); + wait_for_children(); + + log_tc->set_top(0_vl); + text_tc = &lnav_data.ld_views[LNV_TEXT]; + text_tc->set_height(vis_line_t(text_tc->get_inner_height() + - text_tc->get_top())); + setup_highlights(lnav_data.ld_views[LNV_TEXT].get_highlights()); + if (lnav_data.ld_log_source.text_line_count() == 0 + && lnav_data.ld_text_source.text_line_count() > 0) + { + ensure_view(&lnav_data.ld_views[LNV_TEXT]); + } + + log_info("Executing initial commands"); + execute_init_commands(lnav_data.ld_exec_context, cmd_results); + archive_manager::cleanup_cache(); + tailer::cleanup_cache(); + line_buffer::cleanup_cache(); + wait_for_pipers(); + isc::to<curl_looper&, services::curl_streamer_t>() + .send_and_wait( + [](auto& clooper) { clooper.process_all(); }); + rebuild_indexes_repeatedly(); + wait_for_children(); + if (!lnav_data.ld_active_files.fc_name_to_errors.empty()) { + for (const auto& pair : + lnav_data.ld_active_files.fc_name_to_errors) + { + fprintf(stderr, + "error: unable to open file: %s -- %s\n", + pair.first.c_str(), + pair.second.fei_description.c_str()); + } + + return EXIT_FAILURE; + } + + for (auto& pair : cmd_results) { + if (pair.first.isErr()) { + lnav::console::print(stderr, pair.first.unwrapErr()); + output_view = false; + } else { + auto msg = pair.first.unwrap(); + + if (startswith(msg, "info:")) { + if (verbosity == verbosity_t::verbose) { + printf("%s\n", msg.c_str()); + } + } else if (!msg.empty()) { + printf("%s\n", msg.c_str()); + output_view = false; + } + } + } + + if (output_view && verbosity != verbosity_t::quiet + && !lnav_data.ld_view_stack.empty() + && !lnav_data.ld_stdout_used) + { + bool suppress_empty_lines = false; + unsigned long view_index; + vis_line_t y; + + tc = *lnav_data.ld_view_stack.top(); + view_index = tc - lnav_data.ld_views; + switch (view_index) { + case LNV_DB: + case LNV_HISTOGRAM: + suppress_empty_lines = true; + break; + default: + break; + } + + auto* los = tc->get_overlay_source(); + + vis_line_t vl; + for (vl = tc->get_top(); vl < tc->get_inner_height(); + ++vl, ++y) + { + attr_line_t al; + + while (los != nullptr + && los->list_value_for_overlay( + *tc, y, tc->get_inner_height(), vl, al)) + { + write_line_to(stdout, al); + ++y; + } + + std::vector<attr_line_t> rows(1); + tc->listview_value_for_rows(*tc, vl, rows); + if (suppress_empty_lines && rows[0].empty()) { + continue; + } + + write_line_to(stdout, rows[0]); + } + { + attr_line_t al; + + while (los != nullptr + && los->list_value_for_overlay( + *tc, y, tc->get_inner_height(), vl, al) + && !al.empty()) + { + write_line_to(stdout, al); + ++y; + } + } + } + } else { + init_session(); + + guard_termios gt(STDIN_FILENO); + lnav_log_orig_termios = gt.get_termios(); + + looper(); + + dup2(STDOUT_FILENO, STDERR_FILENO); + + signal(SIGINT, SIG_DFL); + + save_session(); + } + } catch (const std::system_error& e) { + if (e.code().value() != EPIPE) { + fprintf(stderr, "error: %s\n", e.what()); + } + } catch (const line_buffer::error& e) { + fprintf(stderr, "error: %s\n", strerror(e.e_err)); + } catch (const std::exception& e) { + fprintf(stderr, "error: %s\n", e.what()); + } + + // When reading from stdin, tell the user where the capture file is + // stored so they can look at it later. + if (stdin_captured && stdin_opts.so_out.empty() + && !(lnav_data.ld_flags & LNF_HEADLESS)) + { + auto stdin_fd = stdin_reader->get_fd(); + struct stat stdin_stat; + nonstd::optional<file_ssize_t> stdin_size; + + // NB: the file can be deleted by the time we get here + fchmod(stdin_fd.get(), S_IRUSR); + if (fstat(stdin_fd.get(), &stdin_stat) != -1) { + stdin_size = stdin_stat.st_size; + } + if (!ghc::filesystem::exists(stdin_tmp_path) + || verbosity == verbosity_t::quiet || !stdin_size + || stdin_size.value() > MAX_STDIN_CAPTURE_SIZE) + { + std::error_code rm_err_code; + + log_info("not saving stdin capture -- %s (size=%d)", + stdin_tmp_path.c_str(), + stdin_size.value_or(-1)); + ghc::filesystem::remove(stdin_tmp_path, rm_err_code); + } else { + auto home = getenv_opt("HOME"); + auto path_str = stdin_tmp_path.string(); + + if (home && startswith(path_str, home.value())) { + path_str = path_str.substr(strlen(home.value())); + if (path_str[0] != '/') { + path_str.insert(0, 1, '/'); + } + path_str.insert(0, 1, '~'); + } + + lnav::console::print( + stderr, + lnav::console::user_message::info( + attr_line_t() + .append(lnav::roles::number(humanize::file_size( + stdin_size.value(), humanize::alignment::none))) + .append(" of data from stdin was captured and " + "will be saved for one day. You can " + "reopen it by running:\n") + .appendf(FMT_STRING(" {} "), + lnav_data.ld_program_name) + .append(lnav::roles::file(path_str)))); + } + } + } + + return retval; +} |