mirror of https://github.com/tstack/lnav
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3174 lines
106 KiB
C++
3174 lines
106 KiB
C++
/**
|
|
* 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 <libgen.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 <vector>
|
|
|
|
#include <sqlite3.h>
|
|
|
|
#ifdef HAVE_BZLIB_H
|
|
# include <bzlib.h>
|
|
#endif
|
|
|
|
#include "all_logs_vtab.hh"
|
|
#include "ansi_scrubber.hh"
|
|
#include "base/fs_util.hh"
|
|
#include "base/func_util.hh"
|
|
#include "base/future_util.hh"
|
|
#include "base/humanize.network.hh"
|
|
#include "base/humanize.time.hh"
|
|
#include "base/injector.bind.hh"
|
|
#include "base/isc.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 "column_namer.hh"
|
|
#include "environ_vtab.hh"
|
|
#include "fstat_vtab.hh"
|
|
#include "grep_proc.hh"
|
|
#include "help-txt.h"
|
|
#include "hist_source.hh"
|
|
#include "init-sql.h"
|
|
#include "listview_curses.hh"
|
|
#include "lnav.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 "service_tags.hh"
|
|
#include "session_data.hh"
|
|
#include "sql_help.hh"
|
|
#include "sql_util.hh"
|
|
#include "sqlite-extension-func.hh"
|
|
#include "tailer/tailer.looper.hh"
|
|
#include "term_extra.hh"
|
|
#include "termios_guard.hh"
|
|
#include "textfile_highlighters.hh"
|
|
#include "textfile_sub_source.hh"
|
|
#include "textview_curses.hh"
|
|
#include "top_status_source.hh"
|
|
#include "view_helpers.examples.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;
|
|
using namespace std::literals::chrono_literals;
|
|
|
|
static bool initial_build = false;
|
|
static multimap<lnav_flags_t, string> DEFAULT_FILES;
|
|
static auto intern_lifetime = intern_string::get_table_lifetime();
|
|
|
|
struct lnav_data_t lnav_data;
|
|
|
|
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 char* lnav_zoom_strings[] = {"1-second",
|
|
"30-second",
|
|
"1-minute",
|
|
"5-minute",
|
|
"15-minute",
|
|
"1-hour",
|
|
"4-hour",
|
|
"8-hour",
|
|
"1-day",
|
|
"1-week",
|
|
|
|
nullptr};
|
|
|
|
static const char* view_titles[LNV__MAX] = {
|
|
"LOG",
|
|
"TEXT",
|
|
"HELP",
|
|
"HIST",
|
|
"DB",
|
|
"SCHEMA",
|
|
"PRETTY",
|
|
"SPECTRO",
|
|
};
|
|
|
|
static 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_active_files = injector::bind<file_collection>::to_instance(
|
|
+[]() { return &lnav_data.ld_active_files; });
|
|
|
|
static auto bound_sqlite_db
|
|
= injector::bind<auto_mem<sqlite3, sqlite_close_wrapper>,
|
|
sqlite_db_tag>::to_instance(&lnav_data.ld_db);
|
|
|
|
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(sqlite_db_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
|
|
|
|
void
|
|
add_recent_netlocs_possibilities()
|
|
{
|
|
readline_curses* rc = lnav_data.ld_rl_view;
|
|
|
|
rc->clear_possibilities(LNM_COMMAND, "recent-netlocs");
|
|
std::set<std::string> netlocs;
|
|
|
|
isc::to<tailer::looper&, services::remote_tailer_t>().send_and_wait(
|
|
[&netlocs](auto& tlooper) { netlocs = tlooper.active_netlocs(); });
|
|
netlocs.insert(session_data.sd_recent_netlocs.begin(),
|
|
session_data.sd_recent_netlocs.end());
|
|
rc->add_possibility(LNM_COMMAND, "recent-netlocs", netlocs);
|
|
}
|
|
|
|
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(LNM_SQL, "*");
|
|
add_view_text_possibilities(
|
|
lnav_data.ld_rl_view, LNM_SQL, "*", &log_view);
|
|
}
|
|
|
|
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(
|
|
LNM_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(LNM_SQL);
|
|
|
|
lnav_data.ld_rl_view->add_possibility(
|
|
LNM_SQL, "*", std::begin(sql_keywords), std::end(sql_keywords));
|
|
lnav_data.ld_rl_view->add_possibility(LNM_SQL, "*", sql_function_names);
|
|
lnav_data.ld_rl_view->add_possibility(
|
|
LNM_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(
|
|
LNM_SQL,
|
|
"*",
|
|
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(
|
|
LNM_SQL,
|
|
"*",
|
|
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: {
|
|
string poss = pair.first
|
|
+ (pair.second->ht_parameters.empty() ? "()" : ("("));
|
|
|
|
lnav_data.ld_rl_view->add_possibility(LNM_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;
|
|
}
|
|
|
|
/**
|
|
* Observer for loading progress that updates the bottom status bar.
|
|
*/
|
|
class loading_observer : public logfile_observer {
|
|
public:
|
|
loading_observer() : lo_last_offset(0){};
|
|
|
|
indexing_result logfile_indexing(const shared_ptr<logfile>& lf,
|
|
file_off_t off,
|
|
file_size_t total) override
|
|
{
|
|
static sig_atomic_t index_counter = 0;
|
|
|
|
if (lnav_data.ld_flags & (LNF_HEADLESS | LNF_CHECK_CONFIG)) {
|
|
return indexing_result::CONTINUE;
|
|
}
|
|
|
|
/* XXX require(off <= total); */
|
|
if (off > (off_t) total) {
|
|
off = total;
|
|
}
|
|
|
|
if ((((size_t) off == total) && (this->lo_last_offset != off))
|
|
|| ui_periodic_timer::singleton().time_to_update(index_counter))
|
|
{
|
|
lnav_data.ld_bottom_source.update_loading(off, total);
|
|
do_update(lf);
|
|
this->lo_last_offset = off;
|
|
}
|
|
|
|
if (!lnav_data.ld_looping) {
|
|
return indexing_result::BREAK;
|
|
}
|
|
return indexing_result::CONTINUE;
|
|
};
|
|
|
|
static void do_update(const shared_ptr<logfile>& lf)
|
|
{
|
|
if (isendwin()) {
|
|
return;
|
|
}
|
|
lnav_data.ld_top_source.update_time();
|
|
for (auto& sc : lnav_data.ld_status) {
|
|
sc.do_update();
|
|
}
|
|
if (lf && lnav_data.ld_mode == LNM_FILES && !initial_build) {
|
|
auto& fc = lnav_data.ld_active_files;
|
|
auto iter = std::find(fc.fc_files.begin(), fc.fc_files.end(), lf);
|
|
|
|
if (iter != fc.fc_files.end()) {
|
|
auto index = std::distance(fc.fc_files.begin(), iter);
|
|
lnav_data.ld_files_view.set_selection(
|
|
vis_line_t(fc.fc_other_files.size() + index));
|
|
lnav_data.ld_files_view.reload_data();
|
|
lnav_data.ld_files_view.do_update();
|
|
}
|
|
}
|
|
refresh();
|
|
};
|
|
|
|
off_t lo_last_offset;
|
|
};
|
|
|
|
class hist_index_delegate : public index_delegate {
|
|
public:
|
|
hist_index_delegate(hist_source2& hs, textview_curses& tc)
|
|
: hid_source(hs), hid_view(tc){
|
|
|
|
};
|
|
|
|
void index_start(logfile_sub_source& lss) override
|
|
{
|
|
this->hid_source.clear();
|
|
};
|
|
|
|
void index_line(logfile_sub_source& lss,
|
|
logfile* lf,
|
|
logfile::iterator ll) override
|
|
{
|
|
if (ll->is_continued() || ll->get_time() == 0) {
|
|
return;
|
|
}
|
|
|
|
hist_source2::hist_type_t ht;
|
|
|
|
switch (ll->get_msg_level()) {
|
|
case LEVEL_FATAL:
|
|
case LEVEL_CRITICAL:
|
|
case LEVEL_ERROR:
|
|
ht = hist_source2::HT_ERROR;
|
|
break;
|
|
case LEVEL_WARNING:
|
|
ht = hist_source2::HT_WARNING;
|
|
break;
|
|
default:
|
|
ht = hist_source2::HT_NORMAL;
|
|
break;
|
|
}
|
|
|
|
this->hid_source.add_value(ll->get_time(), ht);
|
|
if (ll->is_marked() || ll->is_expr_marked()) {
|
|
this->hid_source.add_value(ll->get_time(), hist_source2::HT_MARK);
|
|
}
|
|
};
|
|
|
|
void index_complete(logfile_sub_source& lss) override
|
|
{
|
|
this->hid_view.reload_data();
|
|
};
|
|
|
|
private:
|
|
hist_source2& hid_source;
|
|
textview_curses& hid_view;
|
|
};
|
|
|
|
void
|
|
rebuild_hist()
|
|
{
|
|
logfile_sub_source& lss = lnav_data.ld_log_source;
|
|
hist_source2& hs = lnav_data.ld_hist_source2;
|
|
int zoom = lnav_data.ld_zoom_level;
|
|
|
|
hs.set_time_slice(ZOOM_LEVELS[zoom]);
|
|
lss.reload_index_delegate();
|
|
}
|
|
|
|
class textfile_callback {
|
|
public:
|
|
textfile_callback() : front_file(nullptr), front_top(-1){};
|
|
|
|
void closed_files(const std::vector<shared_ptr<logfile>>& files)
|
|
{
|
|
for (const auto& lf : files) {
|
|
log_info("closed text files: %s", lf->get_filename().c_str());
|
|
}
|
|
lnav_data.ld_active_files.close_files(files);
|
|
};
|
|
|
|
void promote_file(const shared_ptr<logfile>& lf)
|
|
{
|
|
if (lnav_data.ld_log_source.insert_file(lf)) {
|
|
this->did_promotion = true;
|
|
log_info("promoting text file to log file: %s (%s)",
|
|
lf->get_filename().c_str(),
|
|
lf->get_content_id().c_str());
|
|
auto format = lf->get_format();
|
|
if (format->lf_is_self_describing) {
|
|
auto vt = format->get_vtab_impl();
|
|
|
|
if (vt != nullptr) {
|
|
lnav_data.ld_vtab_manager->register_vtab(vt);
|
|
}
|
|
}
|
|
|
|
auto iter = session_data.sd_file_states.find(lf->get_filename());
|
|
if (iter != session_data.sd_file_states.end()) {
|
|
log_debug("found state for log file %d",
|
|
iter->second.fs_is_visible);
|
|
|
|
lnav_data.ld_log_source.find_data(lf) | [&iter](auto ld) {
|
|
ld->set_visibility(iter->second.fs_is_visible);
|
|
};
|
|
}
|
|
} else {
|
|
this->closed_files({lf});
|
|
}
|
|
};
|
|
|
|
void scanned_file(const shared_ptr<logfile>& lf)
|
|
{
|
|
if (!lnav_data.ld_files_to_front.empty()
|
|
&& lnav_data.ld_files_to_front.front().first == lf->get_filename())
|
|
{
|
|
this->front_file = lf;
|
|
this->front_top = lnav_data.ld_files_to_front.front().second;
|
|
|
|
lnav_data.ld_files_to_front.pop_front();
|
|
}
|
|
};
|
|
|
|
shared_ptr<logfile> front_file;
|
|
int front_top;
|
|
bool did_promotion{false};
|
|
};
|
|
|
|
size_t
|
|
rebuild_indexes(nonstd::optional<ui_clock::time_point> deadline)
|
|
{
|
|
logfile_sub_source& lss = lnav_data.ld_log_source;
|
|
textview_curses& log_view = lnav_data.ld_views[LNV_LOG];
|
|
textview_curses& text_view = lnav_data.ld_views[LNV_TEXT];
|
|
vis_line_t old_bottoms[LNV__MAX];
|
|
bool scroll_downs[LNV__MAX];
|
|
size_t retval = 0;
|
|
|
|
for (int lpc = 0; lpc < LNV__MAX; lpc++) {
|
|
old_bottoms[lpc] = lnav_data.ld_views[lpc].get_top_for_last_row();
|
|
scroll_downs[lpc]
|
|
= (lnav_data.ld_views[lpc].get_top() >= old_bottoms[lpc])
|
|
&& !(lnav_data.ld_flags & LNF_HEADLESS);
|
|
}
|
|
|
|
{
|
|
textfile_sub_source* tss = &lnav_data.ld_text_source;
|
|
textfile_callback cb;
|
|
|
|
if (tss->rescan_files(cb, deadline)) {
|
|
text_view.reload_data();
|
|
retval += 1;
|
|
}
|
|
|
|
if (cb.front_file != nullptr) {
|
|
ensure_view(&text_view);
|
|
|
|
if (tss->current_file() != cb.front_file) {
|
|
tss->to_front(cb.front_file);
|
|
old_bottoms[LNV_TEXT] = -1_vl;
|
|
}
|
|
|
|
if (cb.front_top < 0) {
|
|
cb.front_top += text_view.get_inner_height();
|
|
}
|
|
if (cb.front_top < text_view.get_inner_height()) {
|
|
text_view.set_top(vis_line_t(cb.front_top));
|
|
scroll_downs[LNV_TEXT] = false;
|
|
}
|
|
}
|
|
if (cb.did_promotion && deadline) {
|
|
// If there's a new log file, extend the deadline so it can be
|
|
// indexed quickly.
|
|
deadline = deadline.value() + 500ms;
|
|
}
|
|
}
|
|
|
|
std::vector<std::shared_ptr<logfile>> closed_files;
|
|
for (auto& lf : lnav_data.ld_active_files.fc_files) {
|
|
if ((!lf->exists() || lf->is_closed())) {
|
|
log_info("closed log file: %s", lf->get_filename().c_str());
|
|
lnav_data.ld_text_source.remove(lf);
|
|
lnav_data.ld_log_source.remove_file(lf);
|
|
closed_files.emplace_back(lf);
|
|
}
|
|
}
|
|
if (!closed_files.empty()) {
|
|
lnav_data.ld_active_files.close_files(closed_files);
|
|
}
|
|
|
|
auto result = lss.rebuild_index(deadline);
|
|
if (result != logfile_sub_source::rebuild_result::rr_no_change) {
|
|
size_t new_count = lss.text_line_count();
|
|
bool force
|
|
= result == logfile_sub_source::rebuild_result::rr_full_rebuild;
|
|
|
|
if ((!scroll_downs[LNV_LOG]
|
|
|| log_view.get_top() > vis_line_t(new_count))
|
|
&& force)
|
|
{
|
|
scroll_downs[LNV_LOG] = false;
|
|
}
|
|
|
|
log_view.reload_data();
|
|
|
|
{
|
|
unordered_map<string, list<shared_ptr<logfile>>> id_to_files;
|
|
bool reload = false;
|
|
|
|
for (const auto& lf : lnav_data.ld_active_files.fc_files) {
|
|
id_to_files[lf->get_content_id()].push_back(lf);
|
|
}
|
|
|
|
for (auto& pair : id_to_files) {
|
|
if (pair.second.size() == 1) {
|
|
continue;
|
|
}
|
|
|
|
pair.second.sort([](const auto& left, const auto& right) {
|
|
return right->get_stat().st_size < left->get_stat().st_size;
|
|
});
|
|
|
|
auto dupe_name = pair.second.front()->get_unique_path();
|
|
pair.second.pop_front();
|
|
for_each(pair.second.begin(),
|
|
pair.second.end(),
|
|
[&dupe_name](auto& lf) {
|
|
log_info("Hiding duplicate file: %s",
|
|
lf->get_filename().c_str());
|
|
lf->mark_as_duplicate(dupe_name);
|
|
lnav_data.ld_log_source.find_data(lf) |
|
|
[](auto ld) { ld->set_visibility(false); };
|
|
});
|
|
reload = true;
|
|
}
|
|
|
|
if (reload) {
|
|
lss.text_filters_changed();
|
|
}
|
|
}
|
|
|
|
retval += 1;
|
|
}
|
|
|
|
for (int lpc = 0; lpc < LNV__MAX; lpc++) {
|
|
textview_curses& scroll_view = lnav_data.ld_views[lpc];
|
|
|
|
if (scroll_downs[lpc]
|
|
&& scroll_view.get_top_for_last_row() > scroll_view.get_top())
|
|
{
|
|
scroll_view.set_top(scroll_view.get_top_for_last_row());
|
|
}
|
|
}
|
|
|
|
lnav_data.ld_view_stack.top() | [](auto tc) {
|
|
lnav_data.ld_filter_status_source.update_filtered(tc->get_sub_source());
|
|
lnav_data.ld_scroll_broadcaster(tc);
|
|
};
|
|
|
|
return retval;
|
|
}
|
|
|
|
void
|
|
rebuild_indexes_repeatedly()
|
|
{
|
|
for (size_t attempt = 0; attempt < 10 && rebuild_indexes() > 0; attempt++) {
|
|
log_info("continuing to rebuild indexes...");
|
|
}
|
|
}
|
|
|
|
static bool
|
|
append_default_files(lnav_flags_t flag)
|
|
{
|
|
bool retval = true;
|
|
|
|
if (lnav_data.ld_flags & flag) {
|
|
auto cwd = ghc::filesystem::current_path();
|
|
|
|
pair<multimap<lnav_flags_t, string>::iterator,
|
|
multimap<lnav_flags_t, string>::iterator>
|
|
range;
|
|
for (range = DEFAULT_FILES.equal_range(flag);
|
|
range.first != range.second;
|
|
range.first++)
|
|
{
|
|
string path = range.first->second;
|
|
struct stat st;
|
|
|
|
if (access(path.c_str(), R_OK) == 0) {
|
|
auto_mem<char> abspath;
|
|
|
|
path = cwd / range.first->second;
|
|
if ((abspath = realpath(path.c_str(), nullptr)) == nullptr) {
|
|
perror("Unable to resolve path");
|
|
} else {
|
|
lnav_data.ld_active_files.fc_file_names[abspath.in()];
|
|
}
|
|
} else if (stat(path.c_str(), &st) == 0) {
|
|
fprintf(stderr,
|
|
"error: cannot read -- %s%s\n",
|
|
cwd.c_str(),
|
|
path.c_str());
|
|
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;
|
|
}
|
|
}
|
|
|
|
void
|
|
rl_focus(readline_curses* rc)
|
|
{
|
|
auto fos = (field_overlay_source*) lnav_data.ld_views[LNV_LOG]
|
|
.get_overlay_source();
|
|
|
|
fos->fos_contexts.emplace("", false, true);
|
|
}
|
|
|
|
void
|
|
rl_blur(readline_curses* rc)
|
|
{
|
|
auto fos = (field_overlay_source*) lnav_data.ld_views[LNV_LOG]
|
|
.get_overlay_source();
|
|
|
|
fos->fos_contexts.pop();
|
|
lnav_data.ld_preview_generation += 1;
|
|
}
|
|
|
|
readline_context::command_map_t lnav_commands;
|
|
|
|
static void
|
|
usage()
|
|
{
|
|
const char* usage_msg = R"(usage: %s [options] [logfile1 logfile2 ...]
|
|
|
|
A curses-based log file viewer that indexes log messages by type
|
|
and time to make it easier to navigate through files quickly.
|
|
|
|
Key bindings:
|
|
? View/leave the online help text.
|
|
q Quit the program.
|
|
|
|
Options:
|
|
-h Print this message, then exit.
|
|
-H Display the internal help text.
|
|
-I path An additional configuration directory.
|
|
-i Install the given format files and exit. Pass 'extra'
|
|
to install the default set of third-party formats.
|
|
-u Update formats installed from git repositories.
|
|
-C Check configuration and then exit.
|
|
-d path Write debug messages to the given file.
|
|
-V Print version information.
|
|
|
|
-a Load all of the most recent log file types.
|
|
-r Recursively load files from the given directory hierarchies.
|
|
-R Load older rotated log files as well.
|
|
-t Prepend timestamps to the lines of data being read in
|
|
on the standard input.
|
|
-w file Write the contents of the standard input to this file.
|
|
|
|
-c cmd Execute a command after the files have been loaded.
|
|
-f path Execute the commands in the given file.
|
|
-n Run without the curses UI. (headless mode)
|
|
-N Do not open the default syslog file if no files are given.
|
|
-q Do not print the log messages after executing all
|
|
of the commands.
|
|
|
|
Optional arguments:
|
|
logfileN The log files, directories, or remote paths to view.
|
|
If a directory is given, all of the files in the
|
|
directory will be loaded.
|
|
|
|
Examples:
|
|
To load and follow the syslog file:
|
|
$ lnav
|
|
|
|
To load all of the files in /var/log:
|
|
$ lnav /var/log
|
|
|
|
To watch the output of make with timestamps prepended:
|
|
$ make 2>&1 | lnav -t
|
|
|
|
Paths:
|
|
Configuration, session, and format files are stored in:
|
|
%s %s
|
|
|
|
Local copies of remote files and files extracted from
|
|
archives are stored in:
|
|
%s %s
|
|
|
|
Documentation: https://docs.lnav.org
|
|
Contact:
|
|
%s https://github.com/tstack/lnav/discussions
|
|
%s %s
|
|
Version: %s
|
|
)";
|
|
|
|
fprintf(stderr,
|
|
usage_msg,
|
|
lnav_data.ld_program_name,
|
|
"\U0001F4C2",
|
|
lnav::paths::dotlnav().c_str(),
|
|
"\U0001F4C2",
|
|
lnav::paths::workdir().c_str(),
|
|
"\U0001F4AC",
|
|
"\U0001F4EB",
|
|
PACKAGE_BUGREPORT,
|
|
VCS_PACKAGE_STRING);
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
bool
|
|
update_active_files(file_collection& new_files)
|
|
{
|
|
static loading_observer obs;
|
|
|
|
if (lnav_data.ld_active_files.fc_invalidate_merge) {
|
|
lnav_data.ld_active_files.fc_invalidate_merge = false;
|
|
|
|
return true;
|
|
}
|
|
|
|
for (const auto& lf : new_files.fc_files) {
|
|
lf->set_logfile_observer(&obs);
|
|
lnav_data.ld_text_source.push_back(lf);
|
|
}
|
|
for (const auto& other_pair : new_files.fc_other_files) {
|
|
switch (other_pair.second.ofd_format) {
|
|
case file_format_t::SQLITE_DB:
|
|
attach_sqlite_db(lnav_data.ld_db.in(), other_pair.first);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
lnav_data.ld_active_files.merge(new_files);
|
|
if (!new_files.fc_files.empty() || !new_files.fc_other_files.empty()
|
|
|| !new_files.fc_name_to_errors.empty())
|
|
{
|
|
lnav_data.ld_active_files.regenerate_unique_file_names();
|
|
}
|
|
lnav_data.ld_child_pollers.insert(
|
|
lnav_data.ld_child_pollers.begin(),
|
|
std::make_move_iterator(
|
|
lnav_data.ld_active_files.fc_child_pollers.begin()),
|
|
std::make_move_iterator(
|
|
lnav_data.ld_active_files.fc_child_pollers.end()));
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
rescan_files(bool req)
|
|
{
|
|
auto& mlooper = injector::get<main_looper&, services::main_t>();
|
|
bool done = false;
|
|
auto delay = 0ms;
|
|
|
|
do {
|
|
auto fc = lnav_data.ld_active_files.rescan_files(req);
|
|
bool all_synced = true;
|
|
|
|
update_active_files(fc);
|
|
mlooper.get_port().process_for(delay);
|
|
if (lnav_data.ld_flags & LNF_HEADLESS) {
|
|
for (const auto& pair : lnav_data.ld_active_files.fc_other_files) {
|
|
if (pair.second.ofd_format != file_format_t::REMOTE) {
|
|
continue;
|
|
}
|
|
|
|
if (lnav_data.ld_active_files.fc_synced_files.count(pair.first)
|
|
== 0) {
|
|
all_synced = false;
|
|
}
|
|
}
|
|
if (!all_synced) {
|
|
delay = 30ms;
|
|
}
|
|
}
|
|
done = fc.fc_file_names.empty() && all_synced;
|
|
} while (!done);
|
|
return true;
|
|
}
|
|
|
|
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);
|
|
};
|
|
|
|
private:
|
|
};
|
|
|
|
static bool
|
|
handle_config_ui_key(int ch)
|
|
{
|
|
bool retval = false;
|
|
|
|
switch (lnav_data.ld_mode) {
|
|
case LNM_FILES:
|
|
retval = lnav_data.ld_files_view.handle_key(ch);
|
|
break;
|
|
case LNM_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 = LNM_FILES;
|
|
} else if (ch == 'T') {
|
|
new_mode = LNM_FILTER;
|
|
} else if (ch == '\t' || ch == KEY_BTAB) {
|
|
if (lnav_data.ld_mode == LNM_FILES) {
|
|
new_mode = LNM_FILTER;
|
|
} else {
|
|
new_mode = LNM_FILES;
|
|
}
|
|
} else if (ch == 'q') {
|
|
new_mode = LNM_PAGING;
|
|
}
|
|
|
|
if (new_mode) {
|
|
if (new_mode.value() == LNM_FILES || new_mode.value() == LNM_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 CTRL('d'):
|
|
case KEY_RESIZE:
|
|
break;
|
|
default: {
|
|
switch (lnav_data.ld_mode) {
|
|
case LNM_PAGING:
|
|
return handle_paging_key(ch);
|
|
|
|
case LNM_FILTER:
|
|
case LNM_FILES:
|
|
return handle_config_ui_key(ch);
|
|
|
|
case LNM_COMMAND:
|
|
case LNM_SEARCH:
|
|
case LNM_SEARCH_FILTERS:
|
|
case LNM_SEARCH_FILES:
|
|
case LNM_CAPTURE:
|
|
case LNM_SQL:
|
|
case LNM_EXEC:
|
|
case LNM_USER:
|
|
handle_rl_key(ch);
|
|
break;
|
|
|
|
default:
|
|
require(0);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static input_dispatcher::escape_match_t
|
|
match_escape_seq(const char* keyseq)
|
|
{
|
|
if (lnav_data.ld_mode != LNM_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;
|
|
}
|
|
|
|
void
|
|
update_hits(textview_curses* tc)
|
|
{
|
|
if (isendwin()) {
|
|
return;
|
|
}
|
|
|
|
auto top_tc = lnav_data.ld_view_stack.top();
|
|
|
|
if (top_tc && tc == *top_tc) {
|
|
lnav_data.ld_bottom_source.update_hits(tc);
|
|
|
|
if (lnav_data.ld_mode == LNM_SEARCH) {
|
|
const auto MAX_MATCH_COUNT = 10_vl;
|
|
const auto PREVIEW_SIZE = MAX_MATCH_COUNT + 1_vl;
|
|
|
|
int preview_count = 0;
|
|
|
|
vis_bookmarks& bm = tc->get_bookmarks();
|
|
const auto& bv = bm[&textview_curses::BM_SEARCH];
|
|
auto vl = tc->get_top();
|
|
unsigned long width;
|
|
vis_line_t height;
|
|
attr_line_t all_matches;
|
|
char linebuf[64];
|
|
int last_line = tc->get_inner_height();
|
|
int max_line_width;
|
|
|
|
snprintf(linebuf, sizeof(linebuf), "%d", last_line);
|
|
max_line_width = strlen(linebuf);
|
|
|
|
tc->get_dimensions(height, width);
|
|
vl += height;
|
|
if (vl > PREVIEW_SIZE) {
|
|
vl -= PREVIEW_SIZE;
|
|
}
|
|
|
|
auto prev_vl = bv.prev(tc->get_top());
|
|
|
|
if (prev_vl != -1_vl) {
|
|
attr_line_t al;
|
|
|
|
tc->textview_value_for_row(prev_vl, al);
|
|
if (preview_count > 0) {
|
|
all_matches.append("\n");
|
|
}
|
|
snprintf(linebuf,
|
|
sizeof(linebuf),
|
|
"L%*d: ",
|
|
max_line_width,
|
|
(int) prev_vl);
|
|
all_matches.append(linebuf).append(al);
|
|
preview_count += 1;
|
|
}
|
|
|
|
while ((vl = bv.next(vl)) != -1_vl
|
|
&& preview_count < MAX_MATCH_COUNT) {
|
|
attr_line_t al;
|
|
|
|
tc->textview_value_for_row(vl, al);
|
|
if (preview_count > 0) {
|
|
all_matches.append("\n");
|
|
}
|
|
snprintf(linebuf,
|
|
sizeof(linebuf),
|
|
"L%*d: ",
|
|
max_line_width,
|
|
(int) vl);
|
|
all_matches.append(linebuf).append(al);
|
|
preview_count += 1;
|
|
}
|
|
|
|
if (preview_count > 0) {
|
|
lnav_data.ld_preview_status_source.get_description().set_value(
|
|
"Matching lines for search");
|
|
lnav_data.ld_preview_source.replace_with(all_matches)
|
|
.set_text_format(text_format_t::TF_UNKNOWN);
|
|
lnav_data.ld_preview_view.set_needs_update();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
} else {
|
|
usleep(10000);
|
|
rebuild_indexes();
|
|
}
|
|
log_debug("%d pipers and %d children still active",
|
|
lnav_data.ld_pipers.size(),
|
|
lnav_data.ld_child_pollers.size());
|
|
}
|
|
}
|
|
|
|
static void
|
|
looper()
|
|
{
|
|
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 index_context("capture");
|
|
readline_context sql_context("sql", sql_cmd_map, false);
|
|
readline_context exec_context("exec");
|
|
readline_context user_context("user");
|
|
readline_curses rlc;
|
|
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);
|
|
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) {
|
|
lnav_data.ld_bottom_source.update_loading(off, size);
|
|
loading_observer::do_update(nullptr);
|
|
};
|
|
|
|
auto& sb = lnav_data.ld_scroll_broadcaster;
|
|
auto& vsb = lnav_data.ld_view_stack_broadcaster;
|
|
|
|
rlc.add_context(LNM_COMMAND, command_context);
|
|
rlc.add_context(LNM_SEARCH, search_context);
|
|
rlc.add_context(LNM_SEARCH_FILTERS, search_filters_context);
|
|
rlc.add_context(LNM_SEARCH_FILES, search_files_context);
|
|
rlc.add_context(LNM_CAPTURE, index_context);
|
|
rlc.add_context(LNM_SQL, sql_context);
|
|
rlc.add_context(LNM_EXEC, exec_context);
|
|
rlc.add_context(LNM_USER, user_context);
|
|
rlc.start();
|
|
|
|
lnav_data.ld_filter_source.fss_editor.start();
|
|
|
|
lnav_data.ld_rl_view = &rlc;
|
|
|
|
lnav_data.ld_rl_view->add_possibility(
|
|
LNM_COMMAND, "viewname", lnav_view_strings);
|
|
|
|
lnav_data.ld_rl_view->add_possibility(
|
|
LNM_COMMAND, "zoomlevel", lnav_zoom_strings);
|
|
|
|
lnav_data.ld_rl_view->add_possibility(
|
|
LNM_COMMAND, "levelname", level_names);
|
|
|
|
(void) signal(SIGINT, sigint);
|
|
(void) signal(SIGTERM, sigint);
|
|
(void) signal(SIGWINCH, sigwinch);
|
|
(void) signal(SIGCHLD, sigchld);
|
|
|
|
screen_curses sc;
|
|
lnav_behavior lb;
|
|
|
|
auto_fd errpipe[2];
|
|
auto_fd::pipe(errpipe);
|
|
|
|
dup2(errpipe[1], STDERR_FILENO);
|
|
errpipe[1].reset();
|
|
log_pipe_err(errpipe[0]);
|
|
|
|
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();
|
|
|
|
{
|
|
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.with_attrs(hl.h_attrs
|
|
| vc.attrs_for_ident(hl.h_pattern));
|
|
}
|
|
|
|
lnav_data.ld_views[LNV_LOG].get_highlights()[{
|
|
highlight_source_t::CONFIGURATION,
|
|
format->get_name().to_string() + "-" + hl.h_pattern}]
|
|
= 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 error 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(&top_status_source::update_filename,
|
|
&lnav_data.ld_top_source));
|
|
vsb.push_back(bind_mem(&top_status_source::update_view_name,
|
|
&lnav_data.ld_top_source));
|
|
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));
|
|
sb.push_back(
|
|
bind_mem(&term_extra::update_title, injector::get<term_extra*>()));
|
|
vsb.push_back([](listview_curses* lv) {
|
|
auto tc = static_cast<textview_curses*>(lv);
|
|
|
|
tc->tc_state_event_handler(*tc);
|
|
});
|
|
|
|
vsb.push_back(sb);
|
|
|
|
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(1);
|
|
lnav_data.ld_views[lpc].set_height(
|
|
vis_line_t(-(rlc.get_height() + 1)));
|
|
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 = [](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);
|
|
}
|
|
};
|
|
}
|
|
|
|
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_status[LNS_TOP].set_top(0);
|
|
lnav_data.ld_status[LNS_BOTTOM].set_top(-(rlc.get_height() + 1));
|
|
for (auto& sc : lnav_data.ld_status) {
|
|
sc.set_window(lnav_data.ld_window);
|
|
}
|
|
lnav_data.ld_status[LNS_TOP].set_data_source(&lnav_data.ld_top_source);
|
|
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_match_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);
|
|
};
|
|
|
|
{
|
|
input_dispatcher& 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 = 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();
|
|
};
|
|
}
|
|
|
|
ui_periodic_timer& timer = ui_periodic_timer::singleton();
|
|
struct timeval current_time;
|
|
|
|
static sig_atomic_t index_counter;
|
|
|
|
lnav_data.ld_mode = LNM_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;
|
|
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);
|
|
|
|
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);
|
|
|
|
lnav_data.ld_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();
|
|
lnav_data.ld_rl_view->set_value(
|
|
("restored session from " ANSI_BOLD_START) + ago
|
|
+ (ANSI_NORM "; press Ctrl-R to reset session"));
|
|
}
|
|
|
|
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();
|
|
active_copy.merge(lnav_data.ld_active_files);
|
|
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))
|
|
{
|
|
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) {
|
|
changes += rebuild_indexes(loop_deadline);
|
|
if (!changes && ui_clock::now() < loop_deadline) {
|
|
next_rebuild_time = ui_clock::now() + 333ms;
|
|
}
|
|
}
|
|
} else {
|
|
lnav_data.ld_files_view.set_overlay_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();
|
|
if (ui_clock::now() >= next_status_update_time) {
|
|
for (auto& sc : lnav_data.ld_status) {
|
|
sc.do_update();
|
|
}
|
|
next_status_update_time = ui_clock::now() + 100ms;
|
|
}
|
|
if (lnav_data.ld_filter_source.fss_editing) {
|
|
lnav_data.ld_filter_source.fss_match_view.set_needs_update();
|
|
}
|
|
switch (lnav_data.ld_mode) {
|
|
case LNM_FILTER:
|
|
case LNM_SEARCH_FILTERS:
|
|
lnav_data.ld_filter_view.set_needs_update();
|
|
lnav_data.ld_filter_view.do_update();
|
|
break;
|
|
case LNM_SEARCH_FILES:
|
|
case LNM_FILES:
|
|
lnav_data.ld_files_view.set_needs_update();
|
|
lnav_data.ld_files_view.do_update();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (lnav_data.ld_mode != LNM_FILTER
|
|
&& lnav_data.ld_mode != LNM_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 (initial_build) {
|
|
switch (lnav_data.ld_mode) {
|
|
case LNM_COMMAND:
|
|
case LNM_SEARCH:
|
|
case LNM_SEARCH_FILTERS:
|
|
case LNM_SEARCH_FILES:
|
|
case LNM_SQL:
|
|
case LNM_EXEC:
|
|
case LNM_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;
|
|
}
|
|
}
|
|
}
|
|
rlc.update_poll_set(pollfds);
|
|
lnav_data.ld_filter_source.fss_editor.update_poll_set(pollfds);
|
|
|
|
for (auto& tc : lnav_data.ld_views) {
|
|
tc.update_poll_set(pollfds);
|
|
}
|
|
lnav_data.ld_filter_view.update_poll_set(pollfds);
|
|
lnav_data.ld_files_view.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;
|
|
|
|
while ((ch = getch()) != ERR) {
|
|
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 LNM_PAGING:
|
|
case LNM_FILTER:
|
|
case LNM_FILES:
|
|
next_rescan_time = next_status_update_time + 1s;
|
|
break;
|
|
case LNM_COMMAND:
|
|
case LNM_SEARCH:
|
|
case LNM_SEARCH_FILTERS:
|
|
case LNM_SEARCH_FILES:
|
|
case LNM_CAPTURE:
|
|
case LNM_SQL:
|
|
case LNM_EXEC:
|
|
case LNM_USER:
|
|
next_rescan_time = next_status_update_time + 1min;
|
|
break;
|
|
}
|
|
next_rebuild_time = next_rescan_time;
|
|
}
|
|
|
|
for (auto& tc : lnav_data.ld_views) {
|
|
tc.check_poll_set(pollfds);
|
|
}
|
|
|
|
lnav_data.ld_view_stack.top() |
|
|
[](auto tc) { lnav_data.ld_bottom_source.update_hits(tc); };
|
|
|
|
auto old_mode = lnav_data.ld_mode;
|
|
rlc.check_poll_set(pollfds);
|
|
lnav_data.ld_filter_source.fss_editor.check_poll_set(pollfds);
|
|
lnav_data.ld_filter_view.check_poll_set(pollfds);
|
|
lnav_data.ld_files_view.check_poll_set(pollfds);
|
|
|
|
if (lnav_data.ld_mode != old_mode) {
|
|
switch (lnav_data.ld_mode) {
|
|
case LNM_PAGING:
|
|
case LNM_FILTER:
|
|
case LNM_FILES:
|
|
next_rescan_time = next_status_update_time + 1s;
|
|
next_rebuild_time = next_rescan_time;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
&& (!initial_build || timer.fade_diff(index_counter) == 0))
|
|
{
|
|
if (lnav_data.ld_mode == LNM_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 (!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_views[LNV_TEXT].set_top(0_vl);
|
|
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 (!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 (!initial_build && lnav_data.ld_flags & LNF_HELP) {
|
|
toggle_view(&lnav_data.ld_views[LNV_HELP]);
|
|
initial_build = true;
|
|
}
|
|
if (!initial_build && lnav_data.ld_flags & LNF_NO_DEFAULT) {
|
|
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())
|
|
{
|
|
initial_build = true;
|
|
}
|
|
|
|
if (initial_build) {
|
|
static bool ran_cleanup = false;
|
|
vector<pair<Result<string, string>, string>> cmd_results;
|
|
|
|
execute_init_commands(ec, cmd_results);
|
|
|
|
if (!cmd_results.empty()) {
|
|
auto last_cmd_result = cmd_results.back();
|
|
|
|
lnav_data.ld_rl_view->set_value(
|
|
last_cmd_result.first.orElse(err_to_ok).unwrap());
|
|
lnav_data.ld_rl_view->set_alt_value(
|
|
last_cmd_result.second);
|
|
}
|
|
|
|
if (!ran_cleanup) {
|
|
archive_manager::cleanup_cache();
|
|
tailer::cleanup_cache();
|
|
ran_cleanup = true;
|
|
}
|
|
}
|
|
|
|
if (session_stage == 1
|
|
&& (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];
|
|
|
|
if (vs.vs_top > 0) {
|
|
lnav_data.ld_views[view_index].set_top(
|
|
vis_line_t(vs.vs_top));
|
|
}
|
|
}
|
|
if (lnav_data.ld_mode == LNM_FILES) {
|
|
if (lnav_data.ld_active_files.fc_name_to_errors.empty())
|
|
{
|
|
log_debug("switching to paging!");
|
|
lnav_data.ld_mode = LNM_PAGING;
|
|
} else {
|
|
lnav_data.ld_files_view.set_selection(0_vl);
|
|
}
|
|
}
|
|
session_stage += 1;
|
|
load_time_bookmarks();
|
|
}
|
|
}
|
|
|
|
if (lnav_data.ld_winched) {
|
|
struct winsize size;
|
|
|
|
lnav_data.ld_winched = false;
|
|
|
|
if (ioctl(fileno(stdout), TIOCGWINSZ, &size) == 0) {
|
|
resizeterm(size.ws_row, size.ws_col);
|
|
}
|
|
rlc.do_update();
|
|
rlc.window_change();
|
|
lnav_data.ld_filter_source.fss_editor.window_change();
|
|
for (auto& sc : lnav_data.ld_status) {
|
|
sc.window_change();
|
|
}
|
|
lnav_data.ld_view_stack.set_needs_update();
|
|
lnav_data.ld_doc_view.set_needs_update();
|
|
lnav_data.ld_example_view.set_needs_update();
|
|
lnav_data.ld_match_view.set_needs_update();
|
|
lnav_data.ld_filter_view.set_needs_update();
|
|
lnav_data.ld_files_view.set_needs_update();
|
|
}
|
|
|
|
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_meta_search) {
|
|
lnav_data.ld_meta_search->start();
|
|
}
|
|
|
|
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()
|
|
{
|
|
vector<struct pollfd> pollfds;
|
|
struct timeval to = {0, 333000};
|
|
|
|
if (lnav_data.ld_meta_search) {
|
|
lnav_data.ld_meta_search->start();
|
|
}
|
|
|
|
do {
|
|
pollfds.clear();
|
|
|
|
for (auto& tc : lnav_data.ld_views) {
|
|
tc.update_poll_set(pollfds);
|
|
}
|
|
lnav_data.ld_filter_view.update_poll_set(pollfds);
|
|
lnav_data.ld_files_view.update_poll_set(pollfds);
|
|
|
|
if (pollfds.empty()) {
|
|
return;
|
|
}
|
|
|
|
int rc = poll(&pollfds[0], pollfds.size(), to.tv_usec / 1000);
|
|
|
|
if (rc < 0) {
|
|
switch (errno) {
|
|
case 0:
|
|
case EINTR:
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
|
|
for (auto& tc : lnav_data.ld_views) {
|
|
tc.check_poll_set(pollfds);
|
|
|
|
lnav_data.ld_view_stack.top() |
|
|
[](auto tc) { lnav_data.ld_bottom_source.update_hits(tc); };
|
|
}
|
|
lnav_data.ld_filter_view.check_poll_set(pollfds);
|
|
lnav_data.ld_files_view.check_poll_set(pollfds);
|
|
} while (true);
|
|
}
|
|
|
|
textview_curses*
|
|
get_textview_for_mode(ln_mode_t mode)
|
|
{
|
|
switch (mode) {
|
|
case LNM_SEARCH_FILTERS:
|
|
case LNM_FILTER:
|
|
return &lnav_data.ld_filter_view;
|
|
case LNM_SEARCH_FILES:
|
|
case LNM_FILES:
|
|
return &lnav_data.ld_files_view;
|
|
default:
|
|
return *lnav_data.ld_view_stack.top();
|
|
}
|
|
}
|
|
|
|
static void
|
|
print_errors(vector<string> error_list)
|
|
{
|
|
for (auto& iter : error_list) {
|
|
fprintf(stderr,
|
|
"%s%s",
|
|
iter.c_str(),
|
|
iter[iter.size() - 1] == '\n' ? "" : "\n");
|
|
}
|
|
}
|
|
|
|
int
|
|
main(int argc, char* argv[])
|
|
{
|
|
std::vector<std::string> config_errors, loader_errors;
|
|
exec_context& ec = lnav_data.ld_exec_context;
|
|
int lpc, c, retval = EXIT_SUCCESS;
|
|
|
|
shared_ptr<piper_proc> stdin_reader;
|
|
const char* stdin_out = nullptr;
|
|
int stdin_out_fd = -1;
|
|
bool exec_stdin = false, load_stdin = false;
|
|
const char* LANG = getenv("LANG");
|
|
ghc::filesystem::path stdin_tmp_path;
|
|
|
|
if (LANG == nullptr || strcmp(LANG, "C") == 0) {
|
|
setenv("LANG", "en_US.utf-8", 1);
|
|
}
|
|
|
|
(void) signal(SIGPIPE, SIG_IGN);
|
|
std::locale::global(std::locale(""));
|
|
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());
|
|
|
|
ensure_dotlnav();
|
|
|
|
log_install_handlers();
|
|
sql_install_logger();
|
|
|
|
#ifdef HAVE_LIBCURL
|
|
curl_global_init(CURL_GLOBAL_DEFAULT);
|
|
#endif
|
|
|
|
lnav_data.ld_debug_log_name = "/dev/null";
|
|
lnav_data.ld_config_paths.emplace_back("/etc/lnav");
|
|
lnav_data.ld_config_paths.emplace_back(SYSCONFDIR "/lnav");
|
|
lnav_data.ld_config_paths.emplace_back(lnav::paths::dotlnav());
|
|
while ((c = getopt(argc, argv, "hHarRCc:I:iuf:d:nNqtw:vVW")) != -1) {
|
|
switch (c) {
|
|
case 'h':
|
|
usage();
|
|
exit(retval);
|
|
break;
|
|
|
|
case 'H':
|
|
lnav_data.ld_flags |= LNF_HELP;
|
|
break;
|
|
|
|
case 'C':
|
|
lnav_data.ld_flags |= LNF_CHECK_CONFIG;
|
|
break;
|
|
|
|
case 'c':
|
|
switch (optarg[0]) {
|
|
case ':':
|
|
case '/':
|
|
case ';':
|
|
break;
|
|
case '|':
|
|
if (strcmp("|-", optarg) == 0
|
|
|| strcmp("|/dev/stdin", optarg) == 0) {
|
|
exec_stdin = true;
|
|
}
|
|
break;
|
|
default:
|
|
fprintf(
|
|
stderr,
|
|
"error: command arguments should start with a "
|
|
"colon, semi-colon, or pipe-symbol to denote:\n");
|
|
fprintf(stderr,
|
|
"error: a built-in command, SQL query, "
|
|
"or a file path that contains commands to "
|
|
"execute\n");
|
|
usage();
|
|
exit(EXIT_FAILURE);
|
|
break;
|
|
}
|
|
lnav_data.ld_commands.emplace_back(optarg);
|
|
break;
|
|
|
|
case 'f':
|
|
// XXX Not the best way to check for stdin.
|
|
if (strcmp("-", optarg) == 0
|
|
|| strcmp("/dev/stdin", optarg) == 0) {
|
|
exec_stdin = true;
|
|
}
|
|
lnav_data.ld_commands.push_back("|" + string(optarg));
|
|
break;
|
|
|
|
case 'I':
|
|
if (access(optarg, X_OK) != 0) {
|
|
perror("invalid config path");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
lnav_data.ld_config_paths.emplace_back(optarg);
|
|
break;
|
|
|
|
case 'i':
|
|
lnav_data.ld_flags |= LNF_INSTALL;
|
|
break;
|
|
|
|
case 'u':
|
|
lnav_data.ld_flags |= LNF_UPDATE_FORMATS;
|
|
break;
|
|
|
|
case 'd':
|
|
lnav_data.ld_debug_log_name = optarg;
|
|
lnav_log_level = lnav_log_level_t::TRACE;
|
|
break;
|
|
|
|
case 'a':
|
|
lnav_data.ld_flags |= LNF__ALL;
|
|
break;
|
|
|
|
case 'n':
|
|
lnav_data.ld_flags |= LNF_HEADLESS;
|
|
break;
|
|
|
|
case 'N':
|
|
lnav_data.ld_flags |= LNF_NO_DEFAULT;
|
|
break;
|
|
|
|
case 'q':
|
|
lnav_data.ld_flags |= LNF_QUIET;
|
|
break;
|
|
|
|
case 'R':
|
|
lnav_data.ld_active_files.fc_rotated = true;
|
|
break;
|
|
|
|
case 'r':
|
|
lnav_data.ld_active_files.fc_recursive = true;
|
|
break;
|
|
|
|
case 't':
|
|
lnav_data.ld_flags |= LNF_TIMESTAMP;
|
|
break;
|
|
|
|
case 'w':
|
|
stdin_out = optarg;
|
|
break;
|
|
|
|
case 'W': {
|
|
char b;
|
|
if (isatty(STDIN_FILENO) && read(STDIN_FILENO, &b, 1) == -1) {
|
|
perror("Read key from STDIN");
|
|
}
|
|
} break;
|
|
|
|
case 'v':
|
|
lnav_data.ld_flags |= LNF_VERBOSE;
|
|
break;
|
|
|
|
case 'V':
|
|
printf("%s\n", VCS_PACKAGE_STRING);
|
|
exit(0);
|
|
break;
|
|
|
|
default:
|
|
retval = EXIT_FAILURE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
argc -= optind;
|
|
argv += optind;
|
|
|
|
lnav_log_file
|
|
= make_optional_from_nullable(fopen(lnav_data.ld_debug_log_name, "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()) {
|
|
print_errors(config_errors);
|
|
return EXIT_FAILURE;
|
|
}
|
|
add_global_vars(ec);
|
|
|
|
if (lnav_data.ld_flags & LNF_UPDATE_FORMATS) {
|
|
if (!update_installs_from_git()) {
|
|
return EXIT_FAILURE;
|
|
}
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|
|
if (lnav_data.ld_flags & LNF_INSTALL) {
|
|
auto formats_installed_path
|
|
= lnav::paths::dotlnav() / "formats/installed";
|
|
auto configs_installed_path
|
|
= lnav::paths::dotlnav() / "configs/installed";
|
|
|
|
if (argc == 0) {
|
|
fprintf(stderr, "error: expecting file format paths\n");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
for (lpc = 0; lpc < argc; lpc++) {
|
|
if (endswith(argv[lpc], ".git")) {
|
|
if (!install_from_git(argv[lpc])) {
|
|
return EXIT_FAILURE;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (strcmp(argv[lpc], "extra") == 0) {
|
|
install_extra_formats();
|
|
continue;
|
|
}
|
|
|
|
auto file_type_result = detect_config_file_type(argv[lpc]);
|
|
if (file_type_result.isErr()) {
|
|
fprintf(stderr,
|
|
"error: %s\n",
|
|
file_type_result.unwrapErr().c_str());
|
|
return EXIT_FAILURE;
|
|
}
|
|
auto file_type = file_type_result.unwrap();
|
|
|
|
string dst_name;
|
|
if (file_type == config_file_type::CONFIG) {
|
|
dst_name = basename(argv[lpc]);
|
|
} else {
|
|
vector<intern_string_t> format_list
|
|
= load_format_file(argv[lpc], loader_errors);
|
|
|
|
if (!loader_errors.empty()) {
|
|
print_errors(loader_errors);
|
|
return EXIT_FAILURE;
|
|
}
|
|
if (format_list.empty()) {
|
|
fprintf(
|
|
stderr, "error: format file is empty: %s\n", argv[lpc]);
|
|
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(argv[lpc], 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;
|
|
}
|
|
}
|
|
|
|
fprintf(stderr, "info: installed: %s\n", dst_path.c_str());
|
|
}
|
|
}
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|
|
if (sqlite3_open(":memory:", lnav_data.ld_db.out()) != SQLITE_OK) {
|
|
fprintf(stderr, "error: unable to create sqlite memory database\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
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);
|
|
|
|
{
|
|
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());
|
|
{
|
|
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_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_views[LNV_HELP]
|
|
.set_sub_source(&lnav_data.ld_help_source)
|
|
.set_word_wrap(true);
|
|
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);
|
|
}))
|
|
.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_views[LNV_SPECTRO]
|
|
.set_sub_source(&lnav_data.ld_spectro_source)
|
|
.set_overlay_source(&lnav_data.ld_spectro_source)
|
|
.add_input_delegate(lnav_data.ld_spectro_source)
|
|
.set_tail_space(2_vl);
|
|
|
|
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(&lnav_data.ld_filter_source)
|
|
.add_input_delegate(lnav_data.ld_filter_source)
|
|
.add_child_view(&lnav_data.ld_filter_source.fss_match_view)
|
|
.add_child_view(&lnav_data.ld_filter_source.fss_editor);
|
|
lnav_data.ld_files_view.set_sub_source(&lnav_data.ld_files_source)
|
|
.add_input_delegate(lnav_data.ld_files_source);
|
|
|
|
for (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 (lpc = 0; lpc < LNV__MAX; lpc++) {
|
|
lnav_data.ld_views[lpc].set_title(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(), lnav_data.ld_config_paths, loader_errors);
|
|
load_format_vtabs(lnav_data.ld_vtab_manager.get(), loader_errors);
|
|
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;
|
|
|
|
auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
|
|
std::vector<std::string> tables_to_drop;
|
|
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(
|
|
"DROP TABLE {}", sqlite3_column_text(stmt.in(), 0)));
|
|
break;
|
|
}
|
|
} while (!done);
|
|
|
|
for (auto& drop_stmt : tables_to_drop) {
|
|
sqlite3_exec(lnav_data.ld_db.in(),
|
|
drop_stmt.c_str(),
|
|
nullptr,
|
|
nullptr,
|
|
nullptr);
|
|
}
|
|
});
|
|
|
|
if (!loader_errors.empty()) {
|
|
print_errors(loader_errors);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
if (!(lnav_data.ld_flags & LNF_CHECK_CONFIG)) {
|
|
DEFAULT_FILES.insert(make_pair(LNF_SYSLOG, string("var/log/messages")));
|
|
DEFAULT_FILES.insert(
|
|
make_pair(LNF_SYSLOG, string("var/log/system.log")));
|
|
DEFAULT_FILES.insert(make_pair(LNF_SYSLOG, string("var/log/syslog")));
|
|
DEFAULT_FILES.insert(
|
|
make_pair(LNF_SYSLOG, string("var/log/syslog.log")));
|
|
}
|
|
|
|
init_lnav_commands(lnav_commands);
|
|
|
|
lnav_data.ld_looping = true;
|
|
lnav_data.ld_mode = LNM_PAGING;
|
|
|
|
if ((isatty(STDIN_FILENO) || is_dev_null(STDIN_FILENO)) && argc == 0
|
|
&& !(lnav_data.ld_flags & LNF__ALL)
|
|
&& !(lnav_data.ld_flags & LNF_NO_DEFAULT))
|
|
{
|
|
lnav_data.ld_flags |= LNF_SYSLOG;
|
|
}
|
|
if (lnav_data.ld_flags != 0) {
|
|
char start_dir[FILENAME_MAX];
|
|
|
|
if (getcwd(start_dir, sizeof(start_dir)) == nullptr) {
|
|
perror("getcwd");
|
|
} else {
|
|
do {
|
|
for (lpc = 0; lpc < LNB__MAX; lpc++) {
|
|
if (!append_default_files((lnav_flags_t) (1L << lpc))) {
|
|
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 = getenv("DUMP_INTERNALS_DIR");
|
|
|
|
if (internals_dir) {
|
|
dump_schema_to(
|
|
lnav_config_handlers, internals_dir, "config-v1.schema.json");
|
|
dump_schema_to(
|
|
root_format_handler, internals_dir, "format-v1.schema.json");
|
|
|
|
execute_examples();
|
|
|
|
auto cmd_ref_path
|
|
= ghc::filesystem::path(internals_dir) / "cmd-ref.rst";
|
|
auto cmd_file = unique_ptr<FILE, decltype(&fclose)>(
|
|
fopen(cmd_ref_path.c_str(), "w+"), fclose);
|
|
|
|
if (cmd_file.get()) {
|
|
set<readline_context::command_t*> unique_cmds;
|
|
|
|
for (auto& cmd : lnav_commands) {
|
|
if (unique_cmds.find(cmd.second) != unique_cmds.end()) {
|
|
continue;
|
|
}
|
|
unique_cmds.insert(cmd.second);
|
|
format_help_text_for_rst(
|
|
cmd.second->c_help, eval_example, cmd_file.get());
|
|
}
|
|
}
|
|
|
|
auto sql_ref_path
|
|
= ghc::filesystem::path(internals_dir) / "sql-ref.rst";
|
|
auto sql_file = unique_ptr<FILE, decltype(&fclose)>(
|
|
fopen(sql_ref_path.c_str(), "w+"), fclose);
|
|
set<help_text*> unique_sql_help;
|
|
|
|
if (sql_file.get()) {
|
|
for (auto& sql : sqlite_function_help) {
|
|
if (unique_sql_help.find(sql.second)
|
|
!= unique_sql_help.end()) {
|
|
continue;
|
|
}
|
|
unique_sql_help.insert(sql.second);
|
|
format_help_text_for_rst(
|
|
*sql.second, eval_example, sql_file.get());
|
|
}
|
|
}
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|
|
}
|
|
|
|
if (argc == 0) {
|
|
load_stdin = true;
|
|
}
|
|
|
|
for (lpc = 0; lpc < argc; lpc++) {
|
|
auto_mem<char> abspath;
|
|
struct stat st;
|
|
|
|
if (strcmp(argv[lpc], "-") == 0) {
|
|
load_stdin = true;
|
|
} else if (startswith(argv[lpc], "pt:")) {
|
|
#ifdef HAVE_LIBCURL
|
|
lnav_data.ld_pt_search = argv[lpc];
|
|
#else
|
|
fprintf(stderr, "error: lnav is not compiled with libcurl\n");
|
|
retval = EXIT_FAILURE;
|
|
#endif
|
|
}
|
|
#ifdef HAVE_LIBCURL
|
|
else if (is_url(argv[lpc]))
|
|
{
|
|
auto ul = std::make_shared<url_loader>(argv[lpc]);
|
|
|
|
lnav_data.ld_active_files.fc_file_names[argv[lpc]].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(argv[lpc]))
|
|
{
|
|
lnav_data.ld_active_files.fc_file_names[argv[lpc]].with_tail(
|
|
!(lnav_data.ld_flags & LNF_HEADLESS));
|
|
} else if (stat(argv[lpc], &st) == -1) {
|
|
if (strchr(argv[lpc], ':') != nullptr) {
|
|
lnav_data.ld_active_files.fc_file_names[argv[lpc]].with_tail(
|
|
!(lnav_data.ld_flags & LNF_HEADLESS));
|
|
} else {
|
|
fprintf(stderr,
|
|
"Cannot stat file: %s -- %s\n",
|
|
argv[lpc],
|
|
strerror(errno));
|
|
retval = EXIT_FAILURE;
|
|
}
|
|
} else if (access(argv[lpc], R_OK) == -1) {
|
|
fprintf(stderr,
|
|
"Cannot read file: %s -- %s\n",
|
|
argv[lpc],
|
|
strerror(errno));
|
|
retval = EXIT_FAILURE;
|
|
} else if (S_ISFIFO(st.st_mode)) {
|
|
auto_fd fifo_fd;
|
|
|
|
if ((fifo_fd = open(argv[lpc], O_RDONLY)) == -1) {
|
|
fprintf(stderr,
|
|
"Cannot open fifo: %s -- %s\n",
|
|
argv[lpc],
|
|
strerror(errno));
|
|
retval = EXIT_FAILURE;
|
|
} else {
|
|
auto fifo_piper = make_shared<piper_proc>(
|
|
fifo_fd.release(),
|
|
false,
|
|
lnav::filesystem::open_temp_file(
|
|
ghc::filesystem::temp_directory_path()
|
|
/ "lnav.fifo.XXXXXX")
|
|
.map([](auto pair) {
|
|
ghc::filesystem::remove(pair.first);
|
|
|
|
return pair;
|
|
})
|
|
.expect("Cannot create temporary file for FIFO")
|
|
.second);
|
|
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(
|
|
fifo_out_fd);
|
|
lnav_data.ld_pipers.push_back(fifo_piper);
|
|
}
|
|
} else if ((abspath = realpath(argv[lpc], nullptr)) == nullptr) {
|
|
perror("Cannot find file");
|
|
retval = EXIT_FAILURE;
|
|
} else if (S_ISDIR(st.st_mode)) {
|
|
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 (lnav_data.ld_flags & LNF_CHECK_CONFIG) {
|
|
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);
|
|
string full_line(sbr.get_data(), sbr.length());
|
|
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_name(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 | LNF_CHECK_CONFIG))
|
|
&& !isatty(STDOUT_FILENO))
|
|
{
|
|
fprintf(stderr, "error: stdout is not a tty.\n");
|
|
retval = EXIT_FAILURE;
|
|
}
|
|
|
|
if (load_stdin && !isatty(STDIN_FILENO) && !is_dev_null(STDIN_FILENO)
|
|
&& !exec_stdin)
|
|
{
|
|
if (stdin_out == nullptr) {
|
|
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_out_fd = temp_pair.second;
|
|
} else {
|
|
if ((stdin_out_fd
|
|
= open(stdin_out, O_RDWR | O_CREAT | O_TRUNC, 0600))
|
|
== -1) {
|
|
perror("Unable to open output file for stdin");
|
|
return EXIT_FAILURE;
|
|
}
|
|
}
|
|
|
|
stdin_reader = make_shared<piper_proc>(
|
|
STDIN_FILENO, lnav_data.ld_flags & LNF_TIMESTAMP, stdin_out_fd);
|
|
lnav_data.ld_active_files.fc_file_names["stdin"]
|
|
.with_fd(auto_fd(stdin_out_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 (lnav_data.ld_active_files.fc_file_names.empty()
|
|
&& lnav_data.ld_commands.empty() && lnav_data.ld_pt_search.empty()
|
|
&& !(lnav_data.ld_flags & (LNF_HELP | LNF_NO_DEFAULT)))
|
|
{
|
|
fprintf(stderr, "error: no log files given/found.\n");
|
|
retval = EXIT_FAILURE;
|
|
}
|
|
|
|
if (retval != EXIT_SUCCESS) {
|
|
usage();
|
|
} else {
|
|
isc::supervisor root_superv(injector::get<isc::service_list>());
|
|
|
|
try {
|
|
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(" pcre=%s", pcre_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<pair<Result<string, string>, string>> cmd_results;
|
|
textview_curses *log_tc, *text_tc, *tc;
|
|
bool output_view = true;
|
|
|
|
view_colors::init();
|
|
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) {
|
|
fprintf(stderr,
|
|
"error: unable to open file: %s -- %s\n",
|
|
pair.first.c_str(),
|
|
pair.second.fei_description.c_str());
|
|
}
|
|
|
|
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();
|
|
|
|
log_tc->set_top(0_vl);
|
|
text_tc = &lnav_data.ld_views[LNV_TEXT];
|
|
text_tc->set_top(0_vl);
|
|
text_tc->set_height(vis_line_t(text_tc->get_inner_height()));
|
|
if (lnav_data.ld_log_source.text_line_count() == 0
|
|
&& lnav_data.ld_text_source.text_line_count() > 0)
|
|
{
|
|
toggle_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();
|
|
wait_for_pipers();
|
|
isc::to<curl_looper&, services::curl_streamer_t>()
|
|
.send_and_wait(
|
|
[](auto& clooper) { clooper.process_all(); });
|
|
rebuild_indexes_repeatedly();
|
|
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()) {
|
|
fprintf(stderr, "%s\n", pair.first.unwrapErr().c_str());
|
|
output_view = false;
|
|
} else {
|
|
auto msg = pair.first.unwrap();
|
|
|
|
if (startswith(msg, "info:")) {
|
|
if (lnav_data.ld_flags & LNF_VERBOSE) {
|
|
printf("%s\n", msg.c_str());
|
|
}
|
|
} else if (!msg.empty()) {
|
|
printf("%s\n", msg.c_str());
|
|
output_view = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (output_view && !(lnav_data.ld_flags & LNF_QUIET)
|
|
&& !lnav_data.ld_view_stack.empty()
|
|
&& !lnav_data.ld_stdout_used)
|
|
{
|
|
bool suppress_empty_lines = false;
|
|
list_overlay_source* los;
|
|
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;
|
|
}
|
|
|
|
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;
|
|
string& line = al.get_string();
|
|
while (los != nullptr
|
|
&& los->list_value_for_overlay(
|
|
*tc, y, tc->get_inner_height(), vl, al))
|
|
{
|
|
if (write(
|
|
STDOUT_FILENO, line.c_str(), line.length())
|
|
== -1
|
|
|| write(STDOUT_FILENO, "\n", 1) == -1)
|
|
{
|
|
perror("1 write to STDOUT");
|
|
}
|
|
++y;
|
|
}
|
|
|
|
vector<attr_line_t> rows(1);
|
|
tc->listview_value_for_rows(*tc, vl, rows);
|
|
if (suppress_empty_lines && rows[0].empty()) {
|
|
continue;
|
|
}
|
|
|
|
struct line_range lr = find_string_attr_range(
|
|
rows[0].get_attrs(), &SA_ORIGINAL_LINE);
|
|
if (write(STDOUT_FILENO,
|
|
lr.substr(rows[0].get_string()),
|
|
lr.sublen(rows[0].get_string()))
|
|
== -1
|
|
|| write(STDOUT_FILENO, "\n", 1) == -1)
|
|
{
|
|
perror("2 write to STDOUT");
|
|
}
|
|
}
|
|
{
|
|
attr_line_t al;
|
|
string& line = al.get_string();
|
|
|
|
while (los != nullptr
|
|
&& los->list_value_for_overlay(
|
|
*tc, y, tc->get_inner_height(), vl, al)
|
|
&& !al.empty())
|
|
{
|
|
if (write(
|
|
STDOUT_FILENO, line.c_str(), line.length())
|
|
== -1
|
|
|| write(STDOUT_FILENO, "\n", 1) == -1)
|
|
{
|
|
perror("1 write to STDOUT");
|
|
}
|
|
++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 (line_buffer::error& e) {
|
|
fprintf(stderr, "error: %s\n", strerror(e.e_err));
|
|
}
|
|
|
|
// When reading from stdin, tell the user where the capture file is
|
|
// stored so they can look at it later.
|
|
if (stdin_out_fd != -1 && stdin_out == nullptr
|
|
&& !(lnav_data.ld_flags & LNF_QUIET)
|
|
&& !(lnav_data.ld_flags & LNF_HEADLESS))
|
|
{
|
|
if (ghc::filesystem::file_size(stdin_tmp_path)
|
|
> MAX_STDIN_CAPTURE_SIZE) {
|
|
log_info("not saving large stdin capture -- %s",
|
|
stdin_tmp_path.c_str());
|
|
ghc::filesystem::remove(stdin_tmp_path);
|
|
} else {
|
|
auto home = getenv("HOME");
|
|
auto path_str = stdin_tmp_path.string();
|
|
|
|
if (home != nullptr && startswith(path_str, home)) {
|
|
path_str = path_str.substr(strlen(home));
|
|
if (path_str[0] != '/') {
|
|
path_str.insert(0, 1, '/');
|
|
}
|
|
path_str.insert(0, 1, '~');
|
|
}
|
|
|
|
fprintf(stderr,
|
|
"info: stdin was captured, you can reopen it using -- "
|
|
"lnav %s\n",
|
|
path_str.c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
return retval;
|
|
}
|