1 /**
2  * Copyright (c) 2007-2016, Timothy Stack
3  *
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are met:
8  *
9  * * Redistributions of source code must retain the above copyright notice, this
10  * list of conditions and the following disclaimer.
11  * * Redistributions in binary form must reproduce the above copyright notice,
12  * this list of conditions and the following disclaimer in the documentation
13  * and/or other materials provided with the distribution.
14  * * Neither the name of Timothy Stack nor the names of its contributors
15  * may be used to endorse or promote products derived from this software
16  * without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
19  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21  * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
22  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25  * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  *
29  * @file lnav.cc
30  *
31  * XXX This file has become a dumping ground for code and needs to be broken up
32  * a bit.
33  */
34 
35 #include "config.h"
36 
37 #include <stdio.h>
38 #include <errno.h>
39 
40 #include <time.h>
41 #include <glob.h>
42 #include <locale.h>
43 
44 #include <fcntl.h>
45 #include <signal.h>
46 #include <stdlib.h>
47 #include <string.h>
48 #include <unistd.h>
49 #include <termios.h>
50 #include <libgen.h>
51 
52 #include <sys/types.h>
53 #include <sys/stat.h>
54 #include <sys/ioctl.h>
55 #include <sys/time.h>
56 #include <sys/wait.h>
57 
58 #include <readline/readline.h>
59 
60 #if defined(__OpenBSD__) && defined(__clang__) && \
61     !defined(_WCHAR_H_CPLUSPLUS_98_CONFORMANCE_)
62 #define _WCHAR_H_CPLUSPLUS_98_CONFORMANCE_
63 #endif
64 #include <map>
65 #include <memory>
66 #include <set>
67 #include <vector>
68 #include <algorithm>
69 #include <functional>
70 
71 #include <sqlite3.h>
72 
73 #ifdef HAVE_BZLIB_H
74 #include <bzlib.h>
75 #endif
76 
77 #include "lnav.hh"
78 #include "help-txt.h"
79 #include "init-sql.h"
80 #include "logfile.hh"
81 #include "base/func_util.hh"
82 #include "base/humanize.network.hh"
83 #include "base/humanize.time.hh"
84 #include "base/injector.bind.hh"
85 #include "base/isc.hh"
86 #include "base/string_util.hh"
87 #include "base/lnav_log.hh"
88 #include "base/paths.hh"
89 #include "bound_tags.hh"
90 #include "lnav_util.hh"
91 #include "ansi_scrubber.hh"
92 #include "listview_curses.hh"
93 #include "vt52_curses.hh"
94 #include "readline_curses.hh"
95 #include "textview_curses.hh"
96 #include "logfile_sub_source.hh"
97 #include "textfile_sub_source.hh"
98 #include "grep_proc.hh"
99 #include "bookmarks.hh"
100 #include "hist_source.hh"
101 #include "top_status_source.hh"
102 #include "bottom_status_source.hh"
103 #include "piper_proc.hh"
104 #include "log_vtab_impl.hh"
105 #include "termios_guard.hh"
106 #include "xterm_mouse.hh"
107 #include "lnav_commands.hh"
108 #include "column_namer.hh"
109 #include "log_data_table.hh"
110 #include "log_format_loader.hh"
111 #include "log_gutter_source.hh"
112 #include "session_data.hh"
113 #include "lnav_config.hh"
114 #include "sql_util.hh"
115 #include "sqlite-extension-func.hh"
116 #include "term_extra.hh"
117 #include "log_data_helper.hh"
118 #include "readline_highlighters.hh"
119 #include "environ_vtab.hh"
120 #include "views_vtab.hh"
121 #include "all_logs_vtab.hh"
122 #include "regexp_vtab.hh"
123 #include "fstat_vtab.hh"
124 #include "xpath_vtab.hh"
125 #include "textfile_highlighters.hh"
126 #include "base/future_util.hh"
127 #include "tailer/tailer.looper.hh"
128 #include "service_tags.hh"
129 
130 #ifdef HAVE_LIBCURL
131 #include <curl/curl.h>
132 #endif
133 
134 #include "curl_looper.hh"
135 
136 #if HAVE_ARCHIVE_H
137 #include <archive.h>
138 #endif
139 
140 #include "yajlpp/yajlpp.hh"
141 #include "readline_callbacks.hh"
142 #include "command_executor.hh"
143 #include "hotkeys.hh"
144 #include "readline_possibilities.hh"
145 #include "field_overlay_source.hh"
146 #include "url_loader.hh"
147 #include "shlex.hh"
148 #include "log_actions.hh"
149 #include "archive_manager.hh"
150 
151 #ifndef SYSCONFDIR
152 #define SYSCONFDIR "/usr/etc"
153 #endif
154 
155 using namespace std;
156 using namespace std::literals::chrono_literals;
157 
158 static bool initial_build = false;
159 static multimap<lnav_flags_t, string> DEFAULT_FILES;
160 static auto intern_lifetime = intern_string::get_table_lifetime();
161 
162 struct lnav_data_t lnav_data;
163 
164 const int ZOOM_LEVELS[] = {
165     1,
166     30,
167     60,
168     5 * 60,
169     15 * 60,
170     60 * 60,
171     4 * 60 * 60,
172     8 * 60 * 60,
173     24 * 60 * 60,
174     7 * 24 * 60 * 60,
175 };
176 
177 const ssize_t ZOOM_COUNT = sizeof(ZOOM_LEVELS) / sizeof(int);
178 
179 const char *lnav_zoom_strings[] = {
180     "1-second",
181     "30-second",
182     "1-minute",
183     "5-minute",
184     "15-minute",
185     "1-hour",
186     "4-hour",
187     "8-hour",
188     "1-day",
189     "1-week",
190 
191     nullptr
192 };
193 
194 static const char *view_titles[LNV__MAX] = {
195     "LOG",
196     "TEXT",
197     "HELP",
198     "HIST",
199     "DB",
200     "SCHEMA",
201     "PRETTY",
202     "SPECTRO",
203 };
204 
205 static std::vector<std::string> DEFAULT_DB_KEY_NAMES = {
206     "match_index",
207     "capture_index",
208     "capture_count",
209     "range_start",
210     "range_stop",
211     "inode",
212     "device",
213     "inode",
214     "rowid",
215     "st_dev",
216     "st_ino",
217     "st_mode",
218     "st_rdev",
219     "st_uid",
220     "st_gid",
221 };
222 
223 const static size_t MAX_STDIN_CAPTURE_SIZE = 10 * 1024 * 1024;
224 
225 static auto bound_active_files =
__anonf74016b00102() 226     injector::bind<file_collection>::to_instance(+[]() {
227         return &lnav_data.ld_active_files;
228     });
229 
230 static auto bound_last_rel_time =
231     injector::bind<relative_time, last_relative_time_tag>::to_singleton();
232 
233 static auto bound_term_extra =
234     injector::bind<term_extra>::to_singleton();
235 
236 static auto bound_xterm_mouse =
237     injector::bind<xterm_mouse>::to_singleton();
238 
239 static auto bound_scripts =
240     injector::bind<available_scripts>::to_singleton();
241 
242 static auto bound_curl =
243     injector::bind_multiple<isc::service_base>()
244         .add_singleton<curl_looper, services::curl_streamer_t>();
245 
246 static auto bound_tailer =
247     injector::bind_multiple<isc::service_base>()
248         .add_singleton<tailer::looper, services::remote_tailer_t>();
249 
250 static auto bound_main =
251     injector::bind_multiple<static_service>()
252         .add_singleton<main_looper, services::main_t>();
253 
254 namespace injector {
255 template<>
force_linking(last_relative_time_tag anno)256 void force_linking(last_relative_time_tag anno)
257 {
258 }
259 
260 template<>
force_linking(services::curl_streamer_t anno)261 void force_linking(services::curl_streamer_t anno)
262 {
263 }
264 
265 template<>
force_linking(services::remote_tailer_t anno)266 void force_linking(services::remote_tailer_t anno)
267 {
268 }
269 
270 template<>
force_linking(services::main_t anno)271 void force_linking(services::main_t anno)
272 {
273 }
274 }
275 
add_recent_netlocs_possibilities()276 void add_recent_netlocs_possibilities()
277 {
278     readline_curses *rc = lnav_data.ld_rl_view;
279 
280     rc->clear_possibilities(LNM_COMMAND, "recent-netlocs");
281     std::set<std::string> netlocs;
282 
283     isc::to<tailer::looper&, services::remote_tailer_t>()
284         .send_and_wait([&netlocs](auto& tlooper) {
285             netlocs = tlooper.active_netlocs();
286         });
287     netlocs.insert(session_data.sd_recent_netlocs.begin(),
288                    session_data.sd_recent_netlocs.end());
289     rc->add_possibility(LNM_COMMAND, "recent-netlocs", netlocs);
290 }
291 
setup_logline_table(exec_context & ec)292 bool setup_logline_table(exec_context &ec)
293 {
294     // Hidden columns don't show up in the table_info pragma.
295     static const char *hidden_table_columns[] = {
296         "log_time_msecs",
297         "log_path",
298         "log_text",
299         "log_body",
300 
301         nullptr
302     };
303 
304     textview_curses &log_view = lnav_data.ld_views[LNV_LOG];
305     bool             retval   = false;
306     bool update_possibilities = (
307         lnav_data.ld_rl_view != nullptr &&
308         ec.ec_local_vars.size() == 1);
309 
310     if (update_possibilities) {
311         lnav_data.ld_rl_view->clear_possibilities(LNM_SQL, "*");
312         add_view_text_possibilities(lnav_data.ld_rl_view, LNM_SQL, "*", &log_view);
313     }
314 
315     if (log_view.get_inner_height()) {
316         static intern_string_t logline = intern_string::lookup("logline");
317         vis_line_t     vl = log_view.get_top();
318         content_line_t cl = lnav_data.ld_log_source.at_base(vl);
319 
320         lnav_data.ld_vtab_manager->unregister_vtab(logline);
321         lnav_data.ld_vtab_manager->register_vtab(
322             std::make_shared<log_data_table>(lnav_data.ld_log_source,
323                                              *lnav_data.ld_vtab_manager,
324                                              cl,
325                                              logline));
326 
327         if (update_possibilities) {
328             log_data_helper ldh(lnav_data.ld_log_source);
329 
330             ldh.parse_line(cl);
331 
332             std::map<const intern_string_t, json_ptr_walk::walk_list_t>::const_iterator pair_iter;
333             for (pair_iter = ldh.ldh_json_pairs.begin();
334                  pair_iter != ldh.ldh_json_pairs.end();
335                  ++pair_iter) {
336                 for (size_t lpc = 0; lpc < pair_iter->second.size(); lpc++) {
337                     lnav_data.ld_rl_view->add_possibility(LNM_SQL, "*",
338                         ldh.format_json_getter(pair_iter->first, lpc));
339                 }
340             }
341         }
342 
343         retval = true;
344     }
345 
346     auto &db_key_names = lnav_data.ld_db_key_names;
347 
348     db_key_names = DEFAULT_DB_KEY_NAMES;
349 
350     if (update_possibilities) {
351         add_env_possibilities(LNM_SQL);
352 
353         lnav_data.ld_rl_view->add_possibility(LNM_SQL, "*",
354                                               std::begin(sql_keywords),
355                                               std::end(sql_keywords));
356         lnav_data.ld_rl_view->add_possibility(LNM_SQL, "*", sql_function_names);
357         lnav_data.ld_rl_view->add_possibility(LNM_SQL, "*",
358             hidden_table_columns);
359 
360         for (int lpc = 0; sqlite_registration_funcs[lpc]; lpc++) {
361             struct FuncDef *basic_funcs;
362             struct FuncDefAgg *agg_funcs;
363 
364             sqlite_registration_funcs[lpc](&basic_funcs, &agg_funcs);
365             for (int lpc2 = 0; basic_funcs && basic_funcs[lpc2].zName; lpc2++) {
366                 const FuncDef &func_def = basic_funcs[lpc2];
367 
368                 lnav_data.ld_rl_view->add_possibility(
369                     LNM_SQL,
370                     "*",
371                     string(func_def.zName) + (func_def.nArg ? "(" : "()"));
372             }
373             for (int lpc2 = 0; agg_funcs && agg_funcs[lpc2].zName; lpc2++) {
374                 const FuncDefAgg &func_def = agg_funcs[lpc2];
375 
376                 lnav_data.ld_rl_view->add_possibility(
377                     LNM_SQL,
378                     "*",
379                     string(func_def.zName) + (func_def.nArg ? "(" : "()"));
380             }
381         }
382 
383         for (const auto &pair : sqlite_function_help) {
384             switch (pair.second->ht_context) {
385                 case help_context_t::HC_SQL_FUNCTION:
386                 case help_context_t::HC_SQL_TABLE_VALUED_FUNCTION: {
387                     string poss = pair.first +
388                         (pair.second->ht_parameters.empty() ? "()" : ("("));
389 
390                     lnav_data.ld_rl_view->add_possibility(LNM_SQL, "*", poss);
391                     break;
392                 }
393                 default:
394                     break;
395             }
396         }
397     }
398 
399     walk_sqlite_metadata(lnav_data.ld_db.in(), lnav_sql_meta_callbacks);
400 
401     for (const auto &iter : *lnav_data.ld_vtab_manager) {
402         iter.second->get_foreign_keys(db_key_names);
403     }
404 
405     stable_sort(db_key_names.begin(), db_key_names.end());
406 
407     return retval;
408 }
409 
410 /**
411  * Observer for loading progress that updates the bottom status bar.
412  */
413 class loading_observer
414     : public logfile_observer {
415 public:
loading_observer()416     loading_observer()
417         : lo_last_offset(0) {
418     };
419 
logfile_indexing(const shared_ptr<logfile> & lf,file_off_t off,file_size_t total)420     indexing_result logfile_indexing(const shared_ptr<logfile>& lf,
421                                      file_off_t off,
422                                      file_size_t total) override
423     {
424         static sig_atomic_t index_counter = 0;
425 
426         if (lnav_data.ld_flags & (LNF_HEADLESS|LNF_CHECK_CONFIG)) {
427             return indexing_result::CONTINUE;
428         }
429 
430         /* XXX require(off <= total); */
431         if (off > (off_t)total) {
432             off = total;
433         }
434 
435         if ((((size_t)off == total) && (this->lo_last_offset != off)) ||
436             ui_periodic_timer::singleton().time_to_update(index_counter)) {
437             lnav_data.ld_bottom_source.update_loading(off, total);
438             do_update(lf);
439             this->lo_last_offset = off;
440         }
441 
442         if (!lnav_data.ld_looping) {
443             return indexing_result::BREAK;
444         }
445         return indexing_result::CONTINUE;
446     };
447 
do_update(const shared_ptr<logfile> & lf)448     static void do_update(const shared_ptr<logfile>& lf)
449     {
450         if (isendwin()) {
451             return;
452         }
453         lnav_data.ld_top_source.update_time();
454         for (auto &sc : lnav_data.ld_status) {
455             sc.do_update();
456         }
457         if (lf && lnav_data.ld_mode == LNM_FILES && !initial_build) {
458             auto &fc = lnav_data.ld_active_files;
459             auto iter = std::find(fc.fc_files.begin(),
460                                   fc.fc_files.end(), lf);
461 
462             if (iter != fc.fc_files.end()) {
463                 auto index = std::distance(fc.fc_files.begin(), iter);
464                 lnav_data.ld_files_view.set_selection(
465                     vis_line_t(fc.fc_other_files.size() + index));
466                 lnav_data.ld_files_view.reload_data();
467                 lnav_data.ld_files_view.do_update();
468             }
469         }
470         refresh();
471     };
472 
473     off_t          lo_last_offset;
474 };
475 
476 class hist_index_delegate : public index_delegate {
477 public:
hist_index_delegate(hist_source2 & hs,textview_curses & tc)478     hist_index_delegate(hist_source2 &hs, textview_curses &tc)
479             : hid_source(hs), hid_view(tc) {
480 
481     };
482 
index_start(logfile_sub_source & lss)483     void index_start(logfile_sub_source &lss) override {
484         this->hid_source.clear();
485     };
486 
index_line(logfile_sub_source & lss,logfile * lf,logfile::iterator ll)487     void index_line(logfile_sub_source &lss, logfile *lf, logfile::iterator ll) override {
488         if (ll->is_continued() || ll->get_time() == 0) {
489             return;
490         }
491 
492         hist_source2::hist_type_t ht;
493 
494         switch (ll->get_msg_level()) {
495             case LEVEL_FATAL:
496             case LEVEL_CRITICAL:
497             case LEVEL_ERROR:
498                 ht = hist_source2::HT_ERROR;
499                 break;
500             case LEVEL_WARNING:
501                 ht = hist_source2::HT_WARNING;
502                 break;
503             default:
504                 ht = hist_source2::HT_NORMAL;
505                 break;
506         }
507 
508         this->hid_source.add_value(ll->get_time(), ht);
509         if (ll->is_marked() || ll->is_expr_marked()) {
510             this->hid_source.add_value(ll->get_time(), hist_source2::HT_MARK);
511         }
512     };
513 
index_complete(logfile_sub_source & lss)514     void index_complete(logfile_sub_source &lss) override {
515         this->hid_view.reload_data();
516     };
517 
518 private:
519     hist_source2 &hid_source;
520     textview_curses &hid_view;
521 };
522 
rebuild_hist()523 void rebuild_hist()
524 {
525     logfile_sub_source &lss = lnav_data.ld_log_source;
526     hist_source2 &hs = lnav_data.ld_hist_source2;
527     int zoom = lnav_data.ld_zoom_level;
528 
529     hs.set_time_slice(ZOOM_LEVELS[zoom]);
530     lss.reload_index_delegate();
531 }
532 
533 class textfile_callback {
534 public:
textfile_callback()535     textfile_callback() : front_file(nullptr), front_top(-1) { };
536 
closed_files(const std::vector<shared_ptr<logfile>> & files)537     void closed_files(const std::vector<shared_ptr<logfile>> &files) {
538         for (const auto& lf : files) {
539             log_info("closed text files: %s", lf->get_filename().c_str());
540         }
541         lnav_data.ld_active_files.close_files(files);
542     };
543 
promote_file(const shared_ptr<logfile> & lf)544     void promote_file(const shared_ptr<logfile> &lf) {
545         if (lnav_data.ld_log_source.insert_file(lf)) {
546             this->did_promotion = true;
547             log_info("promoting text file to log file: %s (%s)",
548                      lf->get_filename().c_str(),
549                      lf->get_content_id().c_str());
550             auto format = lf->get_format();
551             if (format->lf_is_self_describing) {
552                 auto vt = format->get_vtab_impl();
553 
554                 if (vt != nullptr) {
555                     lnav_data.ld_vtab_manager->register_vtab(vt);
556                 }
557             }
558 
559             auto iter = session_data.sd_file_states.find(lf->get_filename());
560             if (iter != session_data.sd_file_states.end()) {
561                 log_debug("found state for log file %d",
562                           iter->second.fs_is_visible);
563 
564                 lnav_data.ld_log_source.find_data(lf) | [&iter](auto ld) {
565                     ld->set_visibility(iter->second.fs_is_visible);
566                 };
567             }
568         }
569         else {
570             this->closed_files({lf});
571         }
572     };
573 
scanned_file(const shared_ptr<logfile> & lf)574     void scanned_file(const shared_ptr<logfile> &lf) {
575         if (!lnav_data.ld_files_to_front.empty() &&
576                 lnav_data.ld_files_to_front.front().first ==
577                         lf->get_filename()) {
578             this->front_file = lf;
579             this->front_top = lnav_data.ld_files_to_front.front().second;
580 
581             lnav_data.ld_files_to_front.pop_front();
582         }
583     };
584 
585     shared_ptr<logfile> front_file;
586     int front_top;
587     bool did_promotion{false};
588 };
589 
rebuild_indexes(nonstd::optional<ui_clock::time_point> deadline)590 size_t rebuild_indexes(nonstd::optional<ui_clock::time_point> deadline)
591 {
592     logfile_sub_source &lss = lnav_data.ld_log_source;
593     textview_curses &log_view  = lnav_data.ld_views[LNV_LOG];
594     textview_curses &text_view = lnav_data.ld_views[LNV_TEXT];
595     vis_line_t old_bottoms[LNV__MAX];
596     bool scroll_downs[LNV__MAX];
597     size_t retval = 0;
598 
599     for (int lpc = 0; lpc < LNV__MAX; lpc++) {
600         old_bottoms[lpc] = lnav_data.ld_views[lpc].get_top_for_last_row();
601         scroll_downs[lpc] =
602             (lnav_data.ld_views[lpc].get_top() >= old_bottoms[lpc]) &&
603             !(lnav_data.ld_flags & LNF_HEADLESS);
604     }
605 
606     {
607         textfile_sub_source *tss = &lnav_data.ld_text_source;
608         textfile_callback cb;
609 
610         if (tss->rescan_files(cb, deadline)) {
611             text_view.reload_data();
612             retval += 1;
613         }
614 
615         if (cb.front_file != nullptr) {
616             ensure_view(&text_view);
617 
618             if (tss->current_file() != cb.front_file) {
619                 tss->to_front(cb.front_file);
620                 old_bottoms[LNV_TEXT] = -1_vl;
621             }
622 
623             if (cb.front_top < 0) {
624                 cb.front_top += text_view.get_inner_height();
625             }
626             if (cb.front_top < text_view.get_inner_height()) {
627                 text_view.set_top(vis_line_t(cb.front_top));
628                 scroll_downs[LNV_TEXT] = false;
629             }
630         }
631         if (cb.did_promotion && deadline) {
632             // If there's a new log file, extend the deadline so it can be
633             // indexed quickly.
634             deadline = deadline.value() + 500ms;
635         }
636     }
637 
638     std::vector<std::shared_ptr<logfile>> closed_files;
639     for (auto& lf : lnav_data.ld_active_files.fc_files) {
640         if ((!lf->exists() || lf->is_closed())) {
641             log_info("closed log file: %s", lf->get_filename().c_str());
642             lnav_data.ld_text_source.remove(lf);
643             lnav_data.ld_log_source.remove_file(lf);
644             closed_files.emplace_back(lf);
645         }
646     }
647     if (!closed_files.empty()) {
648         lnav_data.ld_active_files.close_files(closed_files);
649     }
650 
651     auto result = lss.rebuild_index(deadline);
652     if (result != logfile_sub_source::rebuild_result::rr_no_change) {
653         size_t new_count = lss.text_line_count();
654         bool force =
655             result == logfile_sub_source::rebuild_result::rr_full_rebuild;
656 
657         if ((!scroll_downs[LNV_LOG] ||
658              log_view.get_top() > vis_line_t(new_count)) &&
659             force) {
660             scroll_downs[LNV_LOG] = false;
661         }
662 
663         log_view.reload_data();
664 
665         {
666             unordered_map<string, list<shared_ptr<logfile>>> id_to_files;
667             bool reload = false;
668 
669             for (const auto &lf : lnav_data.ld_active_files.fc_files) {
670                 id_to_files[lf->get_content_id()].push_back(lf);
671             }
672 
673             for (auto &pair : id_to_files) {
674                 if (pair.second.size() == 1) {
675                     continue;
676                 }
677 
678                 pair.second.sort([](const auto& left, const auto& right) {
679                     return right->get_stat().st_size <
680                            left->get_stat().st_size;
681                 });
682 
683                 auto dupe_name = pair.second.front()->get_unique_path();
684                 pair.second.pop_front();
685                 for_each(pair.second.begin(),
686                          pair.second.end(),
687                          [&dupe_name](auto& lf) {
688                     log_info("Hiding duplicate file: %s",
689                              lf->get_filename().c_str());
690                     lf->mark_as_duplicate(dupe_name);
691                     lnav_data.ld_log_source.find_data(lf) | [](auto ld) {
692                         ld->set_visibility(false);
693                     };
694                 });
695                 reload = true;
696             }
697 
698             if (reload) {
699                 lss.text_filters_changed();
700             }
701         }
702 
703         retval += 1;
704     }
705 
706     for (int lpc = 0; lpc < LNV__MAX; lpc++) {
707         textview_curses &scroll_view = lnav_data.ld_views[lpc];
708 
709         if (scroll_downs[lpc] && scroll_view.get_top_for_last_row() > scroll_view.get_top()) {
710             scroll_view.set_top(scroll_view.get_top_for_last_row());
711         }
712     }
713 
714     lnav_data.ld_view_stack.top() | [] (auto tc) {
715         lnav_data.ld_filter_status_source.update_filtered(tc->get_sub_source());
716         lnav_data.ld_scroll_broadcaster(tc);
717     };
718 
719     return retval;
720 }
721 
rebuild_indexes_repeatedly()722 void rebuild_indexes_repeatedly()
723 {
724     for (size_t attempt = 0; attempt < 10 && rebuild_indexes() > 0; attempt++) {
725         log_info("continuing to rebuild indexes...");
726     }
727 }
728 
append_default_files(lnav_flags_t flag)729 static bool append_default_files(lnav_flags_t flag)
730 {
731     bool retval = true;
732 
733     if (lnav_data.ld_flags & flag) {
734         auto cwd = ghc::filesystem::current_path();
735 
736         pair<multimap<lnav_flags_t, string>::iterator,
737              multimap<lnav_flags_t, string>::iterator> range;
738         for (range = DEFAULT_FILES.equal_range(flag);
739              range.first != range.second;
740              range.first++) {
741             string path = range.first->second;
742             struct stat st;
743 
744             if (access(path.c_str(), R_OK) == 0) {
745                 auto_mem<char> abspath;
746 
747                 path = cwd / range.first->second;
748                 if ((abspath = realpath(path.c_str(), nullptr)) == nullptr) {
749                     perror("Unable to resolve path");
750                 }
751                 else {
752                     lnav_data.ld_active_files.fc_file_names[abspath.in()];
753                 }
754             }
755             else if (stat(path.c_str(), &st) == 0) {
756                 fprintf(stderr,
757                         "error: cannot read -- %s%s\n",
758                         cwd.c_str(),
759                         path.c_str());
760                 retval = false;
761             }
762         }
763     }
764 
765     return retval;
766 }
767 
sigint(int sig)768 static void sigint(int sig)
769 {
770     lnav_data.ld_looping = false;
771 }
772 
sigwinch(int sig)773 static void sigwinch(int sig)
774 {
775     lnav_data.ld_winched = true;
776 }
777 
sigchld(int sig)778 static void sigchld(int sig)
779 {
780     lnav_data.ld_child_terminated = true;
781 }
782 
handle_rl_key(int ch)783 static void handle_rl_key(int ch)
784 {
785     switch (ch) {
786         case KEY_PPAGE:
787         case KEY_NPAGE:
788         case KEY_CTRL_P:
789             handle_paging_key(ch);
790             break;
791 
792         case KEY_CTRL_RBRACKET:
793             lnav_data.ld_rl_view->abort();
794             break;
795 
796         default:
797             lnav_data.ld_rl_view->handle_key(ch);
798             break;
799     }
800 }
801 
rl_focus(readline_curses * rc)802 void rl_focus(readline_curses *rc)
803 {
804     auto fos = (field_overlay_source *)lnav_data.ld_views[LNV_LOG]
805                         .get_overlay_source();
806 
807     fos->fos_contexts.emplace("", false, true);
808 }
809 
rl_blur(readline_curses * rc)810 void rl_blur(readline_curses *rc)
811 {
812     auto fos = (field_overlay_source *)lnav_data.ld_views[LNV_LOG]
813         .get_overlay_source();
814 
815     fos->fos_contexts.pop();
816     lnav_data.ld_preview_generation += 1;
817 }
818 
819 readline_context::command_map_t lnav_commands;
820 
usage()821 static void usage()
822 {
823     const char *usage_msg = R"(usage: %s [options] [logfile1 logfile2 ...]
824 
825 A curses-based log file viewer that indexes log messages by type
826 and time to make it easier to navigate through files quickly.
827 
828 Key bindings:
829   ?     View/leave the online help text.
830   q     Quit the program.
831 
832 Options:
833   -h         Print this message, then exit.
834   -H         Display the internal help text.
835   -I path    An additional configuration directory.
836   -i         Install the given format files and exit.  Pass 'extra'
837              to install the default set of third-party formats.
838   -u         Update formats installed from git repositories.
839   -C         Check configuration and then exit.
840   -d path    Write debug messages to the given file.
841   -V         Print version information.
842 
843   -a         Load all of the most recent log file types.
844   -r         Recursively load files from the given directory hierarchies.
845   -R         Load older rotated log files as well.
846   -t         Prepend timestamps to the lines of data being read in
847              on the standard input.
848   -w file    Write the contents of the standard input to this file.
849 
850   -c cmd     Execute a command after the files have been loaded.
851   -f path    Execute the commands in the given file.
852   -n         Run without the curses UI. (headless mode)
853   -N         Do not open the default syslog file if no files are given.
854   -q         Do not print the log messages after executing all
855              of the commands.
856 
857 Optional arguments:
858   logfileN          The log files, directories, or remote paths to view.
859                     If a directory is given, all of the files in the
860                     directory will be loaded.
861 
862 Examples:
863   To load and follow the syslog file:
864     $ lnav
865 
866   To load all of the files in /var/log:
867     $ lnav /var/log
868 
869   To watch the output of make with timestamps prepended:
870     $ make 2>&1 | lnav -t
871 
872 Paths:
873   Configuration, session, and format files are stored in:
874     %s %s
875 
876   Local copies of remote files and files extracted from
877   archives are stored in:
878     %s %s
879 
880 Documentation: https://docs.lnav.org
881 Contact:
882   %s https://github.com/tstack/lnav/discussions
883   %s %s
884 Version: %s
885 )";
886 
887     fprintf(stderr,
888             usage_msg,
889             lnav_data.ld_program_name,
890             "\U0001F4C2",
891             lnav::paths::dotlnav().c_str(),
892             "\U0001F4C2",
893             lnav::paths::workdir().c_str(),
894             "\U0001F4AC",
895             "\U0001F4EB",
896             PACKAGE_BUGREPORT,
897             VCS_PACKAGE_STRING);
898 }
899 
clear_last_user_mark(listview_curses * lv)900 static void clear_last_user_mark(listview_curses *lv)
901 {
902     textview_curses *tc = (textview_curses *) lv;
903     if (lnav_data.ld_select_start.find(tc) != lnav_data.ld_select_start.end() &&
904         !tc->is_line_visible(vis_line_t(lnav_data.ld_last_user_mark[tc]))) {
905         lnav_data.ld_select_start.erase(tc);
906         lnav_data.ld_last_user_mark.erase(tc);
907     }
908 }
909 
update_active_files(const file_collection & new_files)910 bool update_active_files(const file_collection& new_files)
911 {
912     static loading_observer obs;
913 
914     if (lnav_data.ld_active_files.fc_invalidate_merge) {
915         lnav_data.ld_active_files.fc_invalidate_merge = false;
916 
917         return true;
918     }
919 
920     for (const auto& lf : new_files.fc_files) {
921         lf->set_logfile_observer(&obs);
922         lnav_data.ld_text_source.push_back(lf);
923     }
924     for (const auto& other_pair : new_files.fc_other_files) {
925         switch (other_pair.second.ofd_format) {
926             case file_format_t::FF_SQLITE_DB:
927                 attach_sqlite_db(lnav_data.ld_db.in(), other_pair.first);
928                 break;
929             default:
930                 break;
931         }
932     }
933     lnav_data.ld_active_files.merge(new_files);
934     if (!new_files.fc_files.empty() ||
935         !new_files.fc_other_files.empty() ||
936         !new_files.fc_name_to_errors.empty()) {
937         lnav_data.ld_active_files.regenerate_unique_file_names();
938     }
939 
940     return true;
941 }
942 
rescan_files(bool req)943 bool rescan_files(bool req)
944 {
945     auto& mlooper = injector::get<main_looper&, services::main_t>();
946     bool done = false;
947     auto delay = 0ms;
948 
949     do {
950         auto fc = lnav_data.ld_active_files.rescan_files(req);
951         bool all_synced = true;
952 
953         update_active_files(fc);
954         mlooper.get_port().process_for(delay);
955         if (lnav_data.ld_flags & LNF_HEADLESS) {
956             for (const auto& pair : lnav_data.ld_active_files.fc_other_files) {
957                 if (pair.second.ofd_format != file_format_t::FF_REMOTE) {
958                     continue;
959                 }
960 
961                 if (lnav_data.ld_active_files.fc_synced_files
962                         .count(pair.first) == 0) {
963                     all_synced = false;
964                 }
965             }
966             if (!all_synced) {
967                 delay = 30ms;
968             }
969         }
970         done = fc.fc_file_names.empty() && all_synced;
971     } while (!done);
972     return true;
973 }
974 
975 class lnav_behavior : public mouse_behavior {
976 public:
mouse_event(int button,bool release,int x,int y)977     void mouse_event(int button, bool release, int x, int y) override
978     {
979         textview_curses *tc = *(lnav_data.ld_view_stack.top());
980         struct mouse_event me;
981 
982         switch (button & xterm_mouse::XT_BUTTON__MASK) {
983         case xterm_mouse::XT_BUTTON1:
984             me.me_button = mouse_button_t::BUTTON_LEFT;
985             break;
986         case xterm_mouse::XT_BUTTON2:
987             me.me_button = mouse_button_t::BUTTON_MIDDLE;
988             break;
989         case xterm_mouse::XT_BUTTON3:
990             me.me_button = mouse_button_t::BUTTON_RIGHT;
991             break;
992         case xterm_mouse::XT_SCROLL_UP:
993             me.me_button = mouse_button_t::BUTTON_SCROLL_UP;
994             break;
995         case xterm_mouse::XT_SCROLL_DOWN:
996             me.me_button = mouse_button_t::BUTTON_SCROLL_DOWN;
997             break;
998         }
999 
1000         if (button & xterm_mouse::XT_DRAG_FLAG) {
1001             me.me_state = mouse_button_state_t::BUTTON_STATE_DRAGGED;
1002         }
1003         else if (release) {
1004             me.me_state = mouse_button_state_t::BUTTON_STATE_RELEASED;
1005         }
1006         else {
1007             me.me_state = mouse_button_state_t::BUTTON_STATE_PRESSED;
1008         }
1009 
1010         gettimeofday(&me.me_time, nullptr);
1011         me.me_x = x - 1;
1012         me.me_y = y - tc->get_y() - 1;
1013 
1014         tc->handle_mouse(me);
1015     };
1016 
1017 private:
1018 };
1019 
handle_config_ui_key(int ch)1020 static bool handle_config_ui_key(int ch)
1021 {
1022     bool retval = false;
1023 
1024     switch (lnav_data.ld_mode) {
1025         case LNM_FILES:
1026             retval = lnav_data.ld_files_view.handle_key(ch);
1027             break;
1028         case LNM_FILTER:
1029             retval = lnav_data.ld_filter_view.handle_key(ch);
1030             break;
1031         default:
1032             ensure(0);
1033     }
1034 
1035     if (retval) {
1036         return retval;
1037     }
1038 
1039     nonstd::optional<ln_mode_t> new_mode;
1040 
1041     lnav_data.ld_filter_help_status_source.fss_error.clear();
1042     if (ch == 'F') {
1043         new_mode = LNM_FILES;
1044     } else if (ch == 'T') {
1045         new_mode = LNM_FILTER;
1046     } else if (ch == '\t' || ch == KEY_BTAB) {
1047         if (lnav_data.ld_mode == LNM_FILES) {
1048             new_mode = LNM_FILTER;
1049         } else {
1050             new_mode = LNM_FILES;
1051         }
1052     } else if (ch == 'q') {
1053         new_mode = LNM_PAGING;
1054     }
1055 
1056     if (new_mode) {
1057         if (new_mode.value() == LNM_FILES ||
1058             new_mode.value() == LNM_FILTER) {
1059             lnav_data.ld_last_config_mode = new_mode.value();
1060         }
1061         lnav_data.ld_mode = new_mode.value();
1062         lnav_data.ld_files_view.reload_data();
1063         lnav_data.ld_filter_view.reload_data();
1064         lnav_data.ld_status[LNS_FILTER].set_needs_update();
1065     } else {
1066         return handle_paging_key(ch);
1067     }
1068 
1069     return true;
1070 }
1071 
handle_key(int ch)1072 static bool handle_key(int ch) {
1073     lnav_data.ld_input_state.push_back(ch);
1074 
1075     switch (ch) {
1076         case CTRL('d'):
1077         case KEY_RESIZE:
1078             break;
1079         default: {
1080             switch (lnav_data.ld_mode) {
1081                 case LNM_PAGING:
1082                     return handle_paging_key(ch);
1083 
1084                 case LNM_FILTER:
1085                 case LNM_FILES:
1086                     return handle_config_ui_key(ch);
1087 
1088                 case LNM_COMMAND:
1089                 case LNM_SEARCH:
1090                 case LNM_SEARCH_FILTERS:
1091                 case LNM_SEARCH_FILES:
1092                 case LNM_CAPTURE:
1093                 case LNM_SQL:
1094                 case LNM_EXEC:
1095                 case LNM_USER:
1096                     handle_rl_key(ch);
1097                     break;
1098 
1099                 default:
1100                     require(0);
1101                     break;
1102             }
1103         }
1104     }
1105 
1106     return true;
1107 }
1108 
match_escape_seq(const char * keyseq)1109 static input_dispatcher::escape_match_t match_escape_seq(const char *keyseq)
1110 {
1111     if (lnav_data.ld_mode != LNM_PAGING) {
1112         return input_dispatcher::escape_match_t::NONE;
1113     }
1114 
1115     auto &km = lnav_config.lc_active_keymap;
1116     auto iter = km.km_seq_to_cmd.find(keyseq);
1117     if (iter != km.km_seq_to_cmd.end()) {
1118         return input_dispatcher::escape_match_t::FULL;
1119     }
1120 
1121     auto lb = km.km_seq_to_cmd.lower_bound(keyseq);
1122     if (lb == km.km_seq_to_cmd.end()) {
1123         return input_dispatcher::escape_match_t::NONE;
1124     }
1125 
1126     auto ub = km.km_seq_to_cmd.upper_bound(keyseq);
1127     auto longest = max_element(lb, ub, [] (auto l, auto r) {
1128         return l.first.size() < r.first.size();
1129     });
1130 
1131     if (strlen(keyseq) < longest->first.size()) {
1132         return input_dispatcher::escape_match_t::PARTIAL;
1133     }
1134 
1135     return input_dispatcher::escape_match_t::NONE;
1136 }
1137 
update_hits(textview_curses * tc)1138 void update_hits(textview_curses *tc)
1139 {
1140     if (isendwin()) {
1141         return;
1142     }
1143 
1144     auto top_tc = lnav_data.ld_view_stack.top();
1145 
1146     if (top_tc && tc == *top_tc) {
1147         lnav_data.ld_bottom_source.update_hits(tc);
1148 
1149         if (lnav_data.ld_mode == LNM_SEARCH) {
1150             const auto MAX_MATCH_COUNT = 10_vl;
1151             const auto PREVIEW_SIZE = MAX_MATCH_COUNT + 1_vl;
1152 
1153             int preview_count = 0;
1154 
1155             vis_bookmarks &bm = tc->get_bookmarks();
1156             const auto &bv = bm[&textview_curses::BM_SEARCH];
1157             auto vl = tc->get_top();
1158             unsigned long width;
1159             vis_line_t height;
1160             attr_line_t all_matches;
1161             char linebuf[64];
1162             int last_line = tc->get_inner_height();
1163             int max_line_width;
1164 
1165             snprintf(linebuf, sizeof(linebuf), "%d", last_line);
1166             max_line_width = strlen(linebuf);
1167 
1168             tc->get_dimensions(height, width);
1169             vl += height;
1170             if (vl > PREVIEW_SIZE) {
1171                 vl -= PREVIEW_SIZE;
1172             }
1173 
1174             auto prev_vl = bv.prev(tc->get_top());
1175 
1176             if (prev_vl != -1_vl) {
1177                 attr_line_t al;
1178 
1179                 tc->textview_value_for_row(prev_vl, al);
1180                 if (preview_count > 0) {
1181                     all_matches.append("\n");
1182                 }
1183                 snprintf(linebuf, sizeof(linebuf),
1184                          "L%*d: ",
1185                          max_line_width, (int) prev_vl);
1186                 all_matches
1187                     .append(linebuf)
1188                     .append(al);
1189                 preview_count += 1;
1190             }
1191 
1192             while ((vl = bv.next(vl)) != -1_vl &&
1193                    preview_count < MAX_MATCH_COUNT) {
1194                 attr_line_t al;
1195 
1196                 tc->textview_value_for_row(vl, al);
1197                 if (preview_count > 0) {
1198                     all_matches.append("\n");
1199                 }
1200                 snprintf(linebuf, sizeof(linebuf),
1201                          "L%*d: ",
1202                          max_line_width, (int) vl);
1203                 all_matches
1204                     .append(linebuf)
1205                     .append(al);
1206                 preview_count += 1;
1207             }
1208 
1209             if (preview_count > 0) {
1210                 lnav_data.ld_preview_status_source.get_description()
1211                          .set_value("Matching lines for search");
1212                 lnav_data.ld_preview_source
1213                          .replace_with(all_matches)
1214                          .set_text_format(text_format_t::TF_UNKNOWN);
1215                 lnav_data.ld_preview_view.set_needs_update();
1216             }
1217         }
1218     }
1219 }
1220 
gather_pipers()1221 static void gather_pipers()
1222 {
1223     for (auto iter = lnav_data.ld_pipers.begin();
1224          iter != lnav_data.ld_pipers.end(); ) {
1225         pid_t child_pid = (*iter)->get_child_pid();
1226         if ((*iter)->has_exited()) {
1227             log_info("child piper has exited -- %d", child_pid);
1228             iter = lnav_data.ld_pipers.erase(iter);
1229         } else {
1230             ++iter;
1231         }
1232     }
1233 }
1234 
wait_for_pipers()1235 static void wait_for_pipers()
1236 {
1237     for (;;) {
1238         gather_pipers();
1239         if (lnav_data.ld_pipers.empty()) {
1240             log_debug("all pipers finished");
1241             break;
1242         }
1243         else {
1244             usleep(10000);
1245             rebuild_indexes();
1246         }
1247         log_debug("%d pipers still active",
1248                 lnav_data.ld_pipers.size());
1249     }
1250 }
1251 
looper()1252 static void looper()
1253 {
1254     try {
1255         auto sql_cmd_map = injector::get<
1256             readline_context::command_map_t*, sql_cmd_map_tag>();
1257         auto& ec = lnav_data.ld_exec_context;
1258 
1259         readline_context command_context("cmd", &lnav_commands);
1260 
1261         readline_context search_context("search", nullptr, false);
1262         readline_context search_filters_context("search-filters", nullptr, false);
1263         readline_context search_files_context("search-files", nullptr, false);
1264         readline_context index_context("capture");
1265         readline_context sql_context("sql", sql_cmd_map, false);
1266         readline_context exec_context("exec");
1267         readline_context user_context("user");
1268         readline_curses  rlc;
1269         sig_atomic_t overlay_counter = 0;
1270         int lpc;
1271 
1272         command_context.set_highlighter(readline_command_highlighter);
1273         search_context
1274             .set_append_character(0)
1275             .set_highlighter(readline_regex_highlighter);
1276         search_filters_context
1277             .set_append_character(0)
1278             .set_highlighter(readline_regex_highlighter);
1279         search_files_context
1280             .set_append_character(0)
1281             .set_highlighter(readline_regex_highlighter);
1282         sql_context
1283             .set_highlighter(readline_sqlite_highlighter)
1284             .set_quote_chars("\"")
1285             .with_readline_var((char **)&rl_completer_word_break_characters,
1286                                " \t\n(),");
1287         exec_context.set_highlighter(readline_shlex_highlighter);
1288 
1289         lnav_data.ld_log_source.lss_sorting_observer = [](auto& lss, auto off, auto size) {
1290             lnav_data.ld_bottom_source.update_loading(off, size);
1291             loading_observer::do_update(nullptr);
1292         };
1293 
1294         auto &sb = lnav_data.ld_scroll_broadcaster;
1295         auto &vsb = lnav_data.ld_view_stack_broadcaster;
1296 
1297         rlc.add_context(LNM_COMMAND, command_context);
1298         rlc.add_context(LNM_SEARCH, search_context);
1299         rlc.add_context(LNM_SEARCH_FILTERS, search_filters_context);
1300         rlc.add_context(LNM_SEARCH_FILES, search_files_context);
1301         rlc.add_context(LNM_CAPTURE, index_context);
1302         rlc.add_context(LNM_SQL, sql_context);
1303         rlc.add_context(LNM_EXEC, exec_context);
1304         rlc.add_context(LNM_USER, user_context);
1305         rlc.start();
1306 
1307         lnav_data.ld_filter_source.fss_editor.start();
1308 
1309         lnav_data.ld_rl_view = &rlc;
1310 
1311         lnav_data.ld_rl_view->add_possibility(
1312             LNM_COMMAND, "viewname", lnav_view_strings);
1313 
1314         lnav_data.ld_rl_view->add_possibility(
1315             LNM_COMMAND, "zoomlevel", lnav_zoom_strings);
1316 
1317         lnav_data.ld_rl_view->add_possibility(
1318             LNM_COMMAND, "levelname", level_names);
1319 
1320         (void)signal(SIGINT, sigint);
1321         (void)signal(SIGTERM, sigint);
1322         (void)signal(SIGWINCH, sigwinch);
1323         (void)signal(SIGCHLD, sigchld);
1324 
1325         screen_curses sc;
1326         lnav_behavior lb;
1327 
1328         auto_fd errpipe[2];
1329         auto_fd::pipe(errpipe);
1330 
1331         dup2(errpipe[1], STDERR_FILENO);
1332         errpipe[1].reset();
1333         log_pipe_err(errpipe[0]);
1334 
1335         ui_periodic_timer::singleton();
1336 
1337         auto mouse_i = injector::get<xterm_mouse&>();
1338 
1339         mouse_i.set_behavior(&lb);
1340         mouse_i.set_enabled(check_experimental("mouse"));
1341 
1342         lnav_data.ld_window = sc.get_window();
1343         keypad(stdscr, TRUE);
1344         (void)nonl();
1345         (void)cbreak();
1346         (void)noecho();
1347         (void)nodelay(lnav_data.ld_window, 1);
1348 
1349 #ifdef VDSUSP
1350         {
1351             struct termios tio;
1352 
1353             tcgetattr(STDIN_FILENO, &tio);
1354             tio.c_cc[VDSUSP] = 0;
1355             tcsetattr(STDIN_FILENO, TCSANOW, &tio);
1356         }
1357 #endif
1358 
1359         define_key("\033Od", KEY_BEG);
1360         define_key("\033Oc", KEY_END);
1361 
1362         view_colors &vc = view_colors::singleton();
1363         view_colors::init();
1364 
1365         {
1366             setup_highlights(lnav_data.ld_views[LNV_LOG].get_highlights());
1367             setup_highlights(lnav_data.ld_views[LNV_TEXT].get_highlights());
1368             setup_highlights(lnav_data.ld_views[LNV_SCHEMA].get_highlights());
1369             setup_highlights(lnav_data.ld_views[LNV_PRETTY].get_highlights());
1370             setup_highlights(lnav_data.ld_preview_view.get_highlights());
1371 
1372             for (const auto& format : log_format::get_root_formats()) {
1373                 for (auto &hl : format->lf_highlighters) {
1374                     if (hl.h_fg.empty()) {
1375                         hl.with_attrs(hl.h_attrs | vc.attrs_for_ident(hl.h_pattern));
1376                     }
1377 
1378                     lnav_data.ld_views[LNV_LOG].get_highlights()[{
1379                         highlight_source_t::CONFIGURATION,
1380                         format->get_name().to_string() + "-" + hl.h_pattern
1381                     }] = hl;
1382                 }
1383             }
1384         }
1385 
1386         execute_examples();
1387 
1388         rlc.set_window(lnav_data.ld_window);
1389         rlc.set_y(-1);
1390         rlc.set_focus_action(rl_focus);
1391         rlc.set_change_action(rl_change);
1392         rlc.set_perform_action(rl_callback);
1393         rlc.set_alt_perform_action(rl_alt_callback);
1394         rlc.set_timeout_action(rl_search);
1395         rlc.set_abort_action(lnav_rl_abort);
1396         rlc.set_display_match_action(rl_display_matches);
1397         rlc.set_display_next_action(rl_display_next);
1398         rlc.set_blur_action(rl_blur);
1399         rlc.set_completion_request_action(rl_completion_request);
1400         rlc.set_alt_value(HELP_MSG_2(
1401             e, E, "to move forward/backward through error messages"));
1402 
1403         (void)curs_set(0);
1404 
1405         lnav_data.ld_view_stack.push_back(&lnav_data.ld_views[LNV_LOG]);
1406 
1407         sb.push_back(clear_last_user_mark);
1408         sb.push_back(bind_mem(&top_status_source::update_filename, &lnav_data.ld_top_source));
1409         vsb.push_back(bind_mem(&top_status_source::update_view_name, &lnav_data.ld_top_source));
1410         sb.push_back(bind_mem(&bottom_status_source::update_line_number, &lnav_data.ld_bottom_source));
1411         sb.push_back(bind_mem(&bottom_status_source::update_percent, &lnav_data.ld_bottom_source));
1412         sb.push_back(bind_mem(&bottom_status_source::update_marks, &lnav_data.ld_bottom_source));
1413         sb.push_back(bind_mem(&term_extra::update_title, injector::get<term_extra*>()));
1414         vsb.push_back([](listview_curses *lv) {
1415             auto tc = static_cast<textview_curses *>(lv);
1416 
1417             tc->tc_state_event_handler(*tc);
1418         });
1419 
1420         vsb.push_back(sb);
1421 
1422         for (lpc = 0; lpc < LNV__MAX; lpc++) {
1423             lnav_data.ld_views[lpc].set_window(lnav_data.ld_window);
1424             lnav_data.ld_views[lpc].set_y(1);
1425             lnav_data.ld_views[lpc].
1426             set_height(vis_line_t(-(rlc.get_height() + 1)));
1427             lnav_data.ld_views[lpc].set_scroll_action(sb);
1428             lnav_data.ld_views[lpc].set_search_action(update_hits);
1429             lnav_data.ld_views[lpc].tc_state_event_handler = [](auto &&tc) {
1430                 auto top_view = lnav_data.ld_view_stack.top();
1431 
1432                 if (top_view && *top_view == &tc) {
1433                     lnav_data.ld_bottom_source.update_search_term(tc);
1434                 }
1435             };
1436         }
1437 
1438         lnav_data.ld_doc_view.set_window(lnav_data.ld_window);
1439         lnav_data.ld_doc_view.set_show_scrollbar(false);
1440 
1441         lnav_data.ld_example_view.set_window(lnav_data.ld_window);
1442         lnav_data.ld_example_view.set_show_scrollbar(false);
1443 
1444         lnav_data.ld_match_view.set_window(lnav_data.ld_window);
1445 
1446         lnav_data.ld_preview_view.set_window(lnav_data.ld_window);
1447         lnav_data.ld_preview_view.set_show_scrollbar(false);
1448 
1449         lnav_data.ld_filter_view.set_selectable(true);
1450         lnav_data.ld_filter_view.set_window(lnav_data.ld_window);
1451         lnav_data.ld_filter_view.set_show_scrollbar(true);
1452 
1453         lnav_data.ld_files_view.set_selectable(true);
1454         lnav_data.ld_files_view.set_window(lnav_data.ld_window);
1455         lnav_data.ld_files_view.set_show_scrollbar(true);
1456         lnav_data.ld_files_view.get_disabled_highlights()
1457             .insert(highlight_source_t::THEME);
1458         lnav_data.ld_files_view.set_overlay_source(&lnav_data.ld_files_overlay);
1459 
1460         lnav_data.ld_status[LNS_TOP].set_top(0);
1461         lnav_data.ld_status[LNS_BOTTOM].set_top(-(rlc.get_height() + 1));
1462         for (auto &sc : lnav_data.ld_status) {
1463             sc.set_window(lnav_data.ld_window);
1464         }
1465         lnav_data.ld_status[LNS_TOP].set_data_source(
1466             &lnav_data.ld_top_source);
1467         lnav_data.ld_status[LNS_BOTTOM].set_data_source(
1468             &lnav_data.ld_bottom_source);
1469         lnav_data.ld_status[LNS_FILTER].set_data_source(
1470             &lnav_data.ld_filter_status_source);
1471         lnav_data.ld_status[LNS_FILTER_HELP].set_data_source(
1472             &lnav_data.ld_filter_help_status_source);
1473         lnav_data.ld_status[LNS_DOC].set_data_source(
1474             &lnav_data.ld_doc_status_source);
1475         lnav_data.ld_status[LNS_PREVIEW].set_data_source(
1476             &lnav_data.ld_preview_status_source);
1477 
1478         lnav_data.ld_match_view.set_show_bottom_border(true);
1479 
1480         for (auto &sc : lnav_data.ld_status) {
1481             sc.window_change();
1482         }
1483 
1484         auto session_path = lnav::paths::dotlnav() / "session";
1485         execute_file(ec, session_path.string());
1486 
1487         sb(*lnav_data.ld_view_stack.top());
1488         vsb(*lnav_data.ld_view_stack.top());
1489 
1490         lnav_data.ld_view_stack.vs_change_handler = [](textview_curses *tc) {
1491             lnav_data.ld_view_stack_broadcaster(tc);
1492         };
1493 
1494         {
1495             input_dispatcher &id = lnav_data.ld_input_dispatcher;
1496 
1497             id.id_escape_matcher = match_escape_seq;
1498             id.id_escape_handler = handle_keyseq;
1499             id.id_key_handler = handle_key;
1500             id.id_mouse_handler = bind(&xterm_mouse::handle_mouse, &mouse_i);
1501             id.id_unhandled_handler = [](const char *keyseq) {
1502                 auto enc_len = lnav_config.lc_ui_keymap.size() * 2;
1503                 auto encoded_name = (char *) alloca(enc_len);
1504 
1505                 log_info("unbound keyseq: %s", keyseq);
1506                 json_ptr::encode(encoded_name, enc_len,
1507                                  lnav_config.lc_ui_keymap.c_str());
1508                 // XXX we should have a hotkey for opening a prompt that is
1509                 // pre-filled with a suggestion that the user can complete.
1510                 // This quick-fix key could be used for other stuff as well
1511                 lnav_data.ld_rl_view->set_value(fmt::format(
1512                     ANSI_CSI ANSI_COLOR_PARAM(COLOR_YELLOW)
1513                     ";" ANSI_BOLD_PARAM ANSI_CHAR_ATTR
1514                     "Unrecognized key"
1515                     ANSI_NORM
1516                     ", bind to a command using \u2014 "
1517                     ANSI_BOLD(":config")
1518                     " /ui/keymap-defs/{}/{}/command <cmd>",
1519                     encoded_name, keyseq));
1520                 alerter::singleton().chime();
1521             };
1522         }
1523 
1524         ui_periodic_timer &timer = ui_periodic_timer::singleton();
1525         struct timeval current_time;
1526 
1527         static sig_atomic_t index_counter;
1528 
1529         lnav_data.ld_mode = LNM_FILES;
1530 
1531         timer.start_fade(index_counter, 1);
1532 
1533         file_collection active_copy;
1534         log_debug("rescan started %p", &active_copy);
1535         active_copy.merge(lnav_data.ld_active_files);
1536         active_copy.fc_progress = lnav_data.ld_active_files.fc_progress;
1537         future<file_collection> rescan_future =
1538             std::async(std::launch::async,
1539                        &file_collection::rescan_files,
1540                        active_copy,
1541                        false);
1542         bool initial_rescan_completed = false;
1543         int session_stage = 0;
1544 
1545         // rlc.do_update();
1546 
1547         auto next_rebuild_time = ui_clock::now();
1548         auto next_status_update_time = next_rebuild_time;
1549         auto next_rescan_time = next_rebuild_time;
1550 
1551         while (lnav_data.ld_looping) {
1552             auto loop_deadline = ui_clock::now() +
1553                 (session_stage == 0 ? 3s : 50ms);
1554 
1555             vector<struct pollfd> pollfds;
1556             size_t starting_view_stack_size = lnav_data.ld_view_stack.size();
1557             size_t changes = 0;
1558             int rc;
1559 
1560             gettimeofday(&current_time, nullptr);
1561 
1562             lnav_data.ld_top_source.update_time(current_time);
1563             lnav_data.ld_preview_view.set_needs_update();
1564 
1565             layout_views();
1566 
1567             auto scan_timeout = initial_rescan_completed ? 0s : 10ms;
1568             if (rescan_future.valid() &&
1569                 rescan_future.wait_for(scan_timeout) ==
1570                 std::future_status::ready) {
1571                 auto new_files = rescan_future.get();
1572                 if (!initial_rescan_completed &&
1573                     new_files.fc_file_names.empty() &&
1574                     new_files.fc_files.empty() &&
1575                     lnav_data.ld_active_files.fc_progress->readAccess()->
1576                     sp_tailers.empty()) {
1577                     initial_rescan_completed = true;
1578 
1579                     log_debug("initial rescan rebuild");
1580                     changes += rebuild_indexes(loop_deadline);
1581                     load_session();
1582                     if (session_data.sd_save_time) {
1583                         std::string ago;
1584 
1585                         ago = humanize::time::point::from_tv({
1586                             (time_t) session_data.sd_save_time, 0})
1587                             .as_time_ago();
1588                         lnav_data.ld_rl_view->set_value(
1589                             ("restored session from " ANSI_BOLD_START) +
1590                             ago +
1591                             (ANSI_NORM "; press Ctrl-R to reset session"));
1592                     }
1593 
1594                     lnav_data.ld_session_loaded = true;
1595                     session_stage += 1;
1596                     loop_deadline = ui_clock::now();
1597                     log_debug("file count %d",
1598                               lnav_data.ld_active_files.fc_files.size())
1599                 }
1600                 update_active_files(new_files);
1601                 if (!initial_rescan_completed) {
1602                     auto &fview = lnav_data.ld_files_view;
1603                     auto height = fview.get_inner_height();
1604 
1605                     if (height > 0_vl) {
1606                         fview.set_selection(height - 1_vl);
1607                     }
1608                 }
1609 
1610                 active_copy.clear();
1611                 active_copy.merge(lnav_data.ld_active_files);
1612                 rescan_future = std::future<file_collection>{};
1613                 next_rescan_time = ui_clock::now() + 333ms;
1614             }
1615 
1616             if (!rescan_future.valid() &&
1617                 (session_stage < 2 || ui_clock::now() >= next_rescan_time)) {
1618                 rescan_future = std::async(std::launch::async,
1619                                            &file_collection::rescan_files,
1620                                            active_copy,
1621                                            false);
1622             }
1623 
1624             {
1625                 auto& mlooper = injector::get<main_looper&, services::main_t>();
1626 
1627                 mlooper.get_port().process_for(0s);
1628             }
1629 
1630             auto ui_now = ui_clock::now();
1631             if (initial_rescan_completed) {
1632                 if (ui_now >= next_rebuild_time) {
1633                     changes += rebuild_indexes(loop_deadline);
1634                     if (!changes && ui_clock::now() < loop_deadline) {
1635                         next_rebuild_time = ui_clock::now() + 333ms;
1636                     }
1637                 }
1638             } else {
1639                 lnav_data.ld_files_view.set_overlay_needs_update();
1640             }
1641 
1642             lnav_data.ld_view_stack.do_update();
1643             lnav_data.ld_doc_view.do_update();
1644             lnav_data.ld_example_view.do_update();
1645             lnav_data.ld_match_view.do_update();
1646             lnav_data.ld_preview_view.do_update();
1647             if (ui_clock::now() >= next_status_update_time) {
1648                 for (auto &sc : lnav_data.ld_status) {
1649                     sc.do_update();
1650                 }
1651                 next_status_update_time = ui_clock::now() + 100ms;
1652             }
1653             if (lnav_data.ld_filter_source.fss_editing) {
1654                 lnav_data.ld_filter_source.fss_match_view.set_needs_update();
1655             }
1656             switch (lnav_data.ld_mode) {
1657                 case LNM_FILTER:
1658                 case LNM_SEARCH_FILTERS:
1659                     lnav_data.ld_filter_view.set_needs_update();
1660                     lnav_data.ld_filter_view.do_update();
1661                     break;
1662                 case LNM_SEARCH_FILES:
1663                 case LNM_FILES:
1664                     lnav_data.ld_files_view.set_needs_update();
1665                     lnav_data.ld_files_view.do_update();
1666                     break;
1667                 default:
1668                     break;
1669             }
1670             if (lnav_data.ld_mode != LNM_FILTER &&
1671                 lnav_data.ld_mode != LNM_FILES) {
1672                 rlc.do_update();
1673             }
1674             refresh();
1675 
1676             if (lnav_data.ld_session_loaded) {
1677                 // Only take input from the user after everything has loaded.
1678                 pollfds.push_back((struct pollfd) {
1679                     STDIN_FILENO,
1680                     POLLIN,
1681                     0
1682                 });
1683                 if (initial_build) {
1684                     switch (lnav_data.ld_mode) {
1685                         case LNM_COMMAND:
1686                         case LNM_SEARCH:
1687                         case LNM_SEARCH_FILTERS:
1688                         case LNM_SEARCH_FILES:
1689                         case LNM_SQL:
1690                         case LNM_EXEC:
1691                         case LNM_USER:
1692                             if (rlc.consume_ready_for_input()) {
1693                                 // log_debug("waiting for readline input")
1694                                 view_curses::awaiting_user_input();
1695                             }
1696                             break;
1697                         default:
1698                             // log_debug("waiting for paging input");
1699                             view_curses::awaiting_user_input();
1700                             break;
1701                     }
1702                 }
1703             }
1704             rlc.update_poll_set(pollfds);
1705             lnav_data.ld_filter_source.fss_editor.update_poll_set(pollfds);
1706 
1707             for (auto &tc : lnav_data.ld_views) {
1708                 tc.update_poll_set(pollfds);
1709             }
1710             lnav_data.ld_filter_view.update_poll_set(pollfds);
1711             lnav_data.ld_files_view.update_poll_set(pollfds);
1712 
1713             ui_now = ui_clock::now();
1714             auto poll_to =
1715                 (!changes && ui_now < loop_deadline && session_stage >= 1) ?
1716                 std::chrono::duration_cast<std::chrono::milliseconds>(loop_deadline - ui_now) :
1717                 0ms;
1718 
1719             if (initial_rescan_completed &&
1720                 lnav_data.ld_input_dispatcher.in_escape() &&
1721                 poll_to > 15ms) {
1722                 poll_to = 15ms;
1723             }
1724             // log_debug("poll %d %d", changes, poll_to.count());
1725             rc = poll(&pollfds[0], pollfds.size(), poll_to.count());
1726 
1727             gettimeofday(&current_time, nullptr);
1728             lnav_data.ld_input_dispatcher.poll(current_time);
1729 
1730             if (rc < 0) {
1731                 switch (errno) {
1732                 case 0:
1733                 case EINTR:
1734                     break;
1735 
1736                 default:
1737                     log_error("select %s", strerror(errno));
1738                     lnav_data.ld_looping = false;
1739                     break;
1740                 }
1741             }
1742             else {
1743                 auto in_revents = pollfd_revents(pollfds, STDIN_FILENO);
1744 
1745                 if (in_revents & (POLLHUP|POLLNVAL)) {
1746                     log_info("stdin has been closed, exiting...");
1747                     lnav_data.ld_looping = false;
1748                 } else if (in_revents & POLLIN) {
1749                     int ch;
1750 
1751                     while ((ch = getch()) != ERR) {
1752                         alerter::singleton().new_input(ch);
1753 
1754                         lnav_data.ld_input_dispatcher.new_input(current_time, ch);
1755 
1756                         lnav_data.ld_view_stack.top() | [ch] (auto tc) {
1757                             lnav_data.ld_key_repeat_history.update(ch, tc->get_top());
1758                         };
1759 
1760                         if (!lnav_data.ld_looping) {
1761                             // No reason to keep processing input after the
1762                             // user has quit.  The view stack will also be
1763                             // empty, which will cause issues.
1764                             break;
1765                         }
1766                     }
1767 
1768                     next_status_update_time = ui_clock::now();
1769                     switch (lnav_data.ld_mode) {
1770                         case LNM_PAGING:
1771                         case LNM_FILTER:
1772                         case LNM_FILES:
1773                             next_rescan_time = next_status_update_time + 1s;
1774                             break;
1775                         case LNM_COMMAND:
1776                         case LNM_SEARCH:
1777                         case LNM_SEARCH_FILTERS:
1778                         case LNM_SEARCH_FILES:
1779                         case LNM_CAPTURE:
1780                         case LNM_SQL:
1781                         case LNM_EXEC:
1782                         case LNM_USER:
1783                             next_rescan_time = next_status_update_time + 1min;
1784                             break;
1785                     }
1786                     next_rebuild_time = next_rescan_time;
1787                 }
1788 
1789                 for (auto &tc : lnav_data.ld_views) {
1790                     tc.check_poll_set(pollfds);
1791                 }
1792 
1793                 lnav_data.ld_view_stack.top() | [] (auto tc) {
1794                     lnav_data.ld_bottom_source.update_hits(tc);
1795                 };
1796 
1797                 auto old_mode = lnav_data.ld_mode;
1798                 rlc.check_poll_set(pollfds);
1799                 lnav_data.ld_filter_source.fss_editor.check_poll_set(pollfds);
1800                 lnav_data.ld_filter_view.check_poll_set(pollfds);
1801                 lnav_data.ld_files_view.check_poll_set(pollfds);
1802 
1803                 if (lnav_data.ld_mode != old_mode) {
1804                     switch (lnav_data.ld_mode) {
1805                         case LNM_PAGING:
1806                         case LNM_FILTER:
1807                         case LNM_FILES:
1808                             next_rescan_time = next_status_update_time + 1s;
1809                             next_rebuild_time = next_rescan_time;
1810                             break;
1811                         default:
1812                             break;
1813                     }
1814                 }
1815             }
1816 
1817             if (timer.time_to_update(overlay_counter)) {
1818                 lnav_data.ld_view_stack.top() | [] (auto tc) {
1819                     tc->set_overlay_needs_update();
1820                 };
1821             }
1822 
1823             if (initial_rescan_completed && session_stage < 2 &&
1824                 (!initial_build || timer.fade_diff(index_counter) == 0)) {
1825                 if (lnav_data.ld_mode == LNM_PAGING) {
1826                     timer.start_fade(index_counter, 1);
1827                 }
1828                 else {
1829                     timer.start_fade(index_counter, 3);
1830                 }
1831                 log_debug("initial build rebuild");
1832                 changes += rebuild_indexes(loop_deadline);
1833                 if (!initial_build &&
1834                     lnav_data.ld_log_source.text_line_count() == 0 &&
1835                     lnav_data.ld_text_source.text_line_count() > 0) {
1836                     ensure_view(&lnav_data.ld_views[LNV_TEXT]);
1837                     lnav_data.ld_views[LNV_TEXT].set_top(0_vl);
1838                     lnav_data.ld_rl_view->set_alt_value(
1839                             HELP_MSG_2(f, F,
1840                                     "to switch to the next/previous file"));
1841                 }
1842                 if (lnav_data.ld_view_stack.top().value_or(nullptr) ==
1843                     &lnav_data.ld_views[LNV_TEXT] &&
1844                     lnav_data.ld_text_source.empty() &&
1845                     lnav_data.ld_log_source.text_line_count() > 0) {
1846                     textview_curses *tc_log = &lnav_data.ld_views[LNV_LOG];
1847                     lnav_data.ld_view_stack.pop_back();
1848 
1849                     lnav_data.ld_views[LNV_LOG].set_top(tc_log->get_top_for_last_row());
1850                 }
1851                 if (!initial_build &&
1852                     lnav_data.ld_log_source.text_line_count() == 0 &&
1853                     !lnav_data.ld_active_files.fc_other_files.empty() &&
1854                     std::any_of(lnav_data.ld_active_files.fc_other_files.begin(),
1855                                 lnav_data.ld_active_files.fc_other_files.end(),
1856                                 [](const auto& pair) {
1857                                     return pair.second.ofd_format ==
1858                                            file_format_t::FF_SQLITE_DB;
1859                                 })) {
1860                     ensure_view(&lnav_data.ld_views[LNV_SCHEMA]);
1861                 }
1862 
1863                 if (!initial_build && lnav_data.ld_flags & LNF_HELP) {
1864                     toggle_view(&lnav_data.ld_views[LNV_HELP]);
1865                     initial_build = true;
1866                 }
1867                 if (!initial_build && lnav_data.ld_flags & LNF_NO_DEFAULT) {
1868                     initial_build = true;
1869                 }
1870                 if (lnav_data.ld_log_source.text_line_count() > 0 ||
1871                     lnav_data.ld_text_source.text_line_count() > 0 ||
1872                     !lnav_data.ld_active_files.fc_other_files.empty()) {
1873                     initial_build = true;
1874                 }
1875 
1876                 if (initial_build) {
1877                     static bool ran_cleanup = false;
1878                     vector<pair<Result<string, string>, string>> cmd_results;
1879 
1880                     execute_init_commands(ec, cmd_results);
1881 
1882                     if (!cmd_results.empty()) {
1883                         auto last_cmd_result = cmd_results.back();
1884 
1885                         lnav_data.ld_rl_view->set_value(
1886                             last_cmd_result.first.orElse(err_to_ok).unwrap());
1887                         lnav_data.ld_rl_view->set_alt_value(
1888                             last_cmd_result.second);
1889                     }
1890 
1891                     if (!ran_cleanup) {
1892                         archive_manager::cleanup_cache();
1893                         tailer::cleanup_cache();
1894                         ran_cleanup = true;
1895                     }
1896                 }
1897 
1898                 if (session_stage == 1 &&
1899                     (lnav_data.ld_log_source.text_line_count() > 0 ||
1900                      lnav_data.ld_text_source.text_line_count() > 0 ||
1901                      !lnav_data.ld_active_files.fc_other_files.empty())) {
1902                     for (size_t view_index = 0;
1903                          view_index < LNV__MAX;
1904                          view_index++) {
1905                         const auto &vs = session_data.sd_view_states[view_index];
1906 
1907                         if (vs.vs_top > 0) {
1908                             lnav_data.ld_views[view_index]
1909                                 .set_top(vis_line_t(vs.vs_top));
1910                         }
1911                     }
1912                     if (lnav_data.ld_mode == LNM_FILES) {
1913                         if (lnav_data.ld_active_files.fc_name_to_errors.empty()) {
1914                             log_debug("switching to paging!");
1915                             lnav_data.ld_mode = LNM_PAGING;
1916                         } else {
1917                             lnav_data.ld_files_view.set_selection(0_vl);
1918                         }
1919                     }
1920                     session_stage += 1;
1921                     load_time_bookmarks();
1922                 }
1923             }
1924 
1925             if (lnav_data.ld_winched) {
1926                 struct winsize size;
1927 
1928                 lnav_data.ld_winched = false;
1929 
1930                 if (ioctl(fileno(stdout), TIOCGWINSZ, &size) == 0) {
1931                     resizeterm(size.ws_row, size.ws_col);
1932                 }
1933                 rlc.do_update();
1934                 rlc.window_change();
1935                 lnav_data.ld_filter_source.fss_editor.window_change();
1936                 for (auto &sc : lnav_data.ld_status) {
1937                     sc.window_change();
1938                 }
1939                 lnav_data.ld_view_stack.set_needs_update();
1940                 lnav_data.ld_doc_view.set_needs_update();
1941                 lnav_data.ld_example_view.set_needs_update();
1942                 lnav_data.ld_match_view.set_needs_update();
1943                 lnav_data.ld_filter_view.set_needs_update();
1944                 lnav_data.ld_files_view.set_needs_update();
1945             }
1946 
1947             if (lnav_data.ld_child_terminated) {
1948                 lnav_data.ld_child_terminated = false;
1949 
1950                 for (auto iter = lnav_data.ld_children.begin();
1951                     iter != lnav_data.ld_children.end();
1952                     ++iter) {
1953                     int rc, child_stat;
1954 
1955                     rc = waitpid(*iter, &child_stat, WNOHANG);
1956                     if (rc == -1 || rc == 0)
1957                         continue;
1958 
1959                     iter = lnav_data.ld_children.erase(iter);
1960                 }
1961 
1962                 gather_pipers();
1963             }
1964 
1965             if (lnav_data.ld_meta_search) {
1966                 lnav_data.ld_meta_search->start();
1967             }
1968 
1969             if (lnav_data.ld_view_stack.empty() ||
1970                 (lnav_data.ld_view_stack.size() == 1 &&
1971                  starting_view_stack_size == 2 &&
1972                  lnav_data.ld_active_files.fc_file_names.size() ==
1973                  lnav_data.ld_text_source.size())) {
1974                 lnav_data.ld_looping = false;
1975             }
1976         }
1977     }
1978     catch (readline_curses::error & e) {
1979         log_error("error: %s", strerror(e.e_err));
1980     }
1981 }
1982 
wait_for_children()1983 void wait_for_children()
1984 {
1985     vector<struct pollfd> pollfds;
1986     struct timeval to = { 0, 333000 };
1987 
1988     if (lnav_data.ld_meta_search) {
1989         lnav_data.ld_meta_search->start();
1990     }
1991 
1992     do {
1993         pollfds.clear();
1994 
1995         for (auto &tc : lnav_data.ld_views) {
1996             tc.update_poll_set(pollfds);
1997         }
1998         lnav_data.ld_filter_view.update_poll_set(pollfds);
1999         lnav_data.ld_files_view.update_poll_set(pollfds);
2000 
2001         if (pollfds.empty()) {
2002             return;
2003         }
2004 
2005         int rc = poll(&pollfds[0], pollfds.size(), to.tv_usec / 1000);
2006 
2007         if (rc < 0) {
2008             switch (errno) {
2009                 case 0:
2010                 case EINTR:
2011                     break;
2012                 default:
2013                     return;
2014             }
2015         }
2016 
2017         for (auto &tc : lnav_data.ld_views) {
2018             tc.check_poll_set(pollfds);
2019 
2020             lnav_data.ld_view_stack.top() | [] (auto tc) {
2021                 lnav_data.ld_bottom_source.update_hits(tc);
2022             };
2023         }
2024         lnav_data.ld_filter_view.check_poll_set(pollfds);
2025         lnav_data.ld_files_view.check_poll_set(pollfds);
2026     } while (true);
2027 }
2028 
get_textview_for_mode(ln_mode_t mode)2029 textview_curses *get_textview_for_mode(ln_mode_t mode)
2030 {
2031     switch (mode) {
2032         case LNM_SEARCH_FILTERS:
2033         case LNM_FILTER:
2034             return &lnav_data.ld_filter_view;
2035         case LNM_SEARCH_FILES:
2036         case LNM_FILES:
2037             return &lnav_data.ld_files_view;
2038         default:
2039             return *lnav_data.ld_view_stack.top();
2040     }
2041 }
2042 
print_errors(vector<string> error_list)2043 static void print_errors(vector<string> error_list)
2044 {
2045     for (auto &iter : error_list) {
2046         fprintf(stderr, "%s%s", iter.c_str(),
2047                 iter[iter.size() - 1] == '\n' ? "" : "\n");
2048     }
2049 }
2050 
main(int argc,char * argv[])2051 int main(int argc, char *argv[])
2052 {
2053     std::vector<std::string> config_errors, loader_errors;
2054     exec_context &ec = lnav_data.ld_exec_context;
2055     int lpc, c, retval = EXIT_SUCCESS;
2056 
2057     shared_ptr<piper_proc> stdin_reader;
2058     const char *stdin_out = nullptr;
2059     int stdin_out_fd = -1;
2060     bool exec_stdin = false, load_stdin = false;
2061     const char *LANG = getenv("LANG");
2062     ghc::filesystem::path stdin_tmp_path;
2063 
2064     if (LANG == nullptr || strcmp(LANG, "C") == 0) {
2065         setenv("LANG", "en_US.utf-8", 1);
2066     }
2067 
2068     (void)signal(SIGPIPE, SIG_IGN);
2069     setlocale(LC_ALL, "");
2070     umask(027);
2071 
2072     /* Disable Lnav from being able to execute external commands if
2073      * "LNAVSECURE" environment variable is set by the user.
2074      */
2075     if (getenv("LNAVSECURE") != nullptr) {
2076         lnav_data.ld_flags |= LNF_SECURE_MODE;
2077     }
2078 
2079     lnav_data.ld_exec_context.ec_sql_callback = sql_callback;
2080     lnav_data.ld_exec_context.ec_pipe_callback = pipe_callback;
2081 
2082     lnav_data.ld_program_name = argv[0];
2083     add_ansi_vars(ec.ec_global_vars);
2084 
2085     rl_readline_name = "lnav";
2086     lnav_data.ld_db_key_names = DEFAULT_DB_KEY_NAMES;
2087 
2088     stable_sort(lnav_data.ld_db_key_names.begin(),
2089                 lnav_data.ld_db_key_names.end());
2090 
2091     ensure_dotlnav();
2092 
2093     log_install_handlers();
2094     sql_install_logger();
2095 
2096 #ifdef HAVE_LIBCURL
2097     curl_global_init(CURL_GLOBAL_DEFAULT);
2098 #endif
2099 
2100     lnav_data.ld_debug_log_name = "/dev/null";
2101     lnav_data.ld_config_paths.emplace_back("/etc/lnav");
2102     lnav_data.ld_config_paths.emplace_back(SYSCONFDIR "/lnav");
2103     lnav_data.ld_config_paths.emplace_back(lnav::paths::dotlnav());
2104     while ((c = getopt(argc, argv, "hHarRCc:I:iuf:d:nNqtw:vVW")) != -1) {
2105         switch (c) {
2106         case 'h':
2107             usage();
2108             exit(retval);
2109             break;
2110 
2111         case 'H':
2112             lnav_data.ld_flags |= LNF_HELP;
2113             break;
2114 
2115         case 'C':
2116             lnav_data.ld_flags |= LNF_CHECK_CONFIG;
2117             break;
2118 
2119         case 'c':
2120             switch (optarg[0]) {
2121             case ':':
2122             case '/':
2123             case ';':
2124                 break;
2125             case '|':
2126                 if (strcmp("|-", optarg) == 0 ||
2127                     strcmp("|/dev/stdin", optarg) == 0) {
2128                     exec_stdin = true;
2129                 }
2130                 break;
2131             default:
2132                 fprintf(stderr, "error: command arguments should start with a "
2133                     "colon, semi-colon, or pipe-symbol to denote:\n");
2134                 fprintf(stderr, "error: a built-in command, SQL query, "
2135                     "or a file path that contains commands to execute\n");
2136                 usage();
2137                 exit(EXIT_FAILURE);
2138                 break;
2139             }
2140             lnav_data.ld_commands.emplace_back(optarg);
2141             break;
2142 
2143         case 'f':
2144             // XXX Not the best way to check for stdin.
2145             if (strcmp("-", optarg) == 0 ||
2146                 strcmp("/dev/stdin", optarg) == 0) {
2147                 exec_stdin = true;
2148             }
2149             lnav_data.ld_commands.push_back("|" + string(optarg));
2150             break;
2151 
2152         case 'I':
2153             if (access(optarg, X_OK) != 0) {
2154                 perror("invalid config path");
2155                 exit(EXIT_FAILURE);
2156             }
2157             lnav_data.ld_config_paths.emplace_back(optarg);
2158             break;
2159 
2160         case 'i':
2161             lnav_data.ld_flags |= LNF_INSTALL;
2162             break;
2163 
2164         case 'u':
2165             lnav_data.ld_flags |= LNF_UPDATE_FORMATS;
2166             break;
2167 
2168         case 'd':
2169             lnav_data.ld_debug_log_name = optarg;
2170             lnav_log_level = lnav_log_level_t::TRACE;
2171             break;
2172 
2173         case 'a':
2174             lnav_data.ld_flags |= LNF__ALL;
2175             break;
2176 
2177         case 'n':
2178             lnav_data.ld_flags |= LNF_HEADLESS;
2179             break;
2180 
2181         case 'N':
2182             lnav_data.ld_flags |= LNF_NO_DEFAULT;
2183             break;
2184 
2185         case 'q':
2186             lnav_data.ld_flags |= LNF_QUIET;
2187             break;
2188 
2189         case 'R':
2190             lnav_data.ld_active_files.fc_rotated = true;
2191             break;
2192 
2193         case 'r':
2194             lnav_data.ld_active_files.fc_recursive = true;
2195             break;
2196 
2197         case 't':
2198             lnav_data.ld_flags |= LNF_TIMESTAMP;
2199             break;
2200 
2201         case 'w':
2202             stdin_out = optarg;
2203             break;
2204 
2205         case 'W':
2206         {
2207             char b;
2208             if (isatty(STDIN_FILENO) && read(STDIN_FILENO, &b, 1) == -1) {
2209                 perror("Read key from STDIN");
2210             }
2211         }
2212             break;
2213 
2214         case 'v':
2215             lnav_data.ld_flags |= LNF_VERBOSE;
2216             break;
2217 
2218         case 'V':
2219             printf("%s\n", VCS_PACKAGE_STRING);
2220             exit(0);
2221             break;
2222 
2223         default:
2224             retval = EXIT_FAILURE;
2225             break;
2226         }
2227     }
2228 
2229     argc -= optind;
2230     argv += optind;
2231 
2232     lnav_log_file = make_optional_from_nullable(fopen(lnav_data.ld_debug_log_name, "a"));
2233     log_info("lnav started");
2234 
2235     {
2236         static auto builtin_formats =
2237             injector::get<std::vector<std::shared_ptr<log_format>>>();
2238         auto& root_formats = log_format::get_root_formats();
2239 
2240         log_format::get_root_formats().insert(
2241             root_formats.begin(), builtin_formats.begin(), builtin_formats.end());
2242         builtin_formats.clear();
2243     }
2244 
2245     load_config(lnav_data.ld_config_paths, config_errors);
2246     if (!config_errors.empty()) {
2247         print_errors(config_errors);
2248         return EXIT_FAILURE;
2249     }
2250     add_global_vars(ec);
2251 
2252     if (lnav_data.ld_flags & LNF_UPDATE_FORMATS) {
2253         if (!update_installs_from_git()) {
2254             return EXIT_FAILURE;
2255         }
2256         return EXIT_SUCCESS;
2257     }
2258 
2259     if (lnav_data.ld_flags & LNF_INSTALL) {
2260         auto formats_installed_path = lnav::paths::dotlnav() / "formats/installed";
2261         auto configs_installed_path = lnav::paths::dotlnav() / "configs/installed";
2262 
2263         if (argc == 0) {
2264             fprintf(stderr, "error: expecting file format paths\n");
2265             return EXIT_FAILURE;
2266         }
2267 
2268         for (lpc = 0; lpc < argc; lpc++) {
2269             if (endswith(argv[lpc], ".git")) {
2270                 if (!install_from_git(argv[lpc])) {
2271                     return EXIT_FAILURE;
2272                 }
2273                 continue;
2274             }
2275 
2276             if (strcmp(argv[lpc], "extra") == 0) {
2277                 install_extra_formats();
2278                 continue;
2279             }
2280 
2281             auto file_type_result = detect_config_file_type(argv[lpc]);
2282             if (file_type_result.isErr()) {
2283                 fprintf(stderr, "error: %s\n", file_type_result.unwrapErr().c_str());
2284                 return EXIT_FAILURE;
2285             }
2286             auto file_type = file_type_result.unwrap();
2287 
2288             string dst_name;
2289             if (file_type == config_file_type::CONFIG) {
2290                 dst_name = basename(argv[lpc]);
2291             } else {
2292                 vector<intern_string_t> format_list = load_format_file(
2293                     argv[lpc], loader_errors);
2294 
2295                 if (!loader_errors.empty()) {
2296                     print_errors(loader_errors);
2297                     return EXIT_FAILURE;
2298                 }
2299                 if (format_list.empty()) {
2300                     fprintf(stderr, "error: format file is empty: %s\n",
2301                             argv[lpc]);
2302                     return EXIT_FAILURE;
2303                 }
2304 
2305                 dst_name = format_list[0].to_string() + ".json";
2306             }
2307             auto dst_path = (file_type == config_file_type::CONFIG ?
2308                              configs_installed_path :
2309                              formats_installed_path) /
2310                             dst_name;
2311             auto_fd in_fd, out_fd;
2312 
2313             if ((in_fd = open(argv[lpc], O_RDONLY)) == -1) {
2314                 perror("unable to open file to install");
2315             }
2316             else if ((out_fd = openp(dst_path,
2317                                      O_WRONLY | O_CREAT | O_TRUNC,
2318                                      0644)) == -1) {
2319                 fprintf(stderr, "error: unable to open destination: %s -- %s\n",
2320                         dst_path.c_str(), strerror(errno));
2321             }
2322             else {
2323                 char buffer[2048];
2324                 ssize_t rc;
2325 
2326                 while ((rc = read(in_fd, buffer, sizeof(buffer))) > 0) {
2327                     ssize_t remaining = rc, written;
2328 
2329                     while (remaining > 0) {
2330                         written = write(out_fd, buffer, rc);
2331                         if (written == -1) {
2332                             fprintf(stderr,
2333                                     "error: unable to install file -- %s\n",
2334                                     strerror(errno));
2335                             exit(EXIT_FAILURE);
2336                         }
2337 
2338                         remaining -= written;
2339                     }
2340                 }
2341 
2342                 fprintf(stderr, "info: installed: %s\n", dst_path.c_str());
2343             }
2344         }
2345         return EXIT_SUCCESS;
2346     }
2347 
2348     if (sqlite3_open(":memory:", lnav_data.ld_db.out()) != SQLITE_OK) {
2349         fprintf(stderr, "error: unable to create sqlite memory database\n");
2350         exit(EXIT_FAILURE);
2351     }
2352 
2353     if (lnav_data.ld_flags & LNF_SECURE_MODE) {
2354         if ((sqlite3_set_authorizer(lnav_data.ld_db.in(),
2355                                     sqlite_authorizer, NULL)) != SQLITE_OK) {
2356             fprintf(stderr, "error: unable to attach sqlite authorizer\n");
2357             exit(EXIT_FAILURE);
2358         }
2359     }
2360 
2361     /* If we statically linked against an ncurses library that had a non-
2362      * standard path to the terminfo database, we need to set this variable
2363      * so that it will try the default path.
2364      */
2365     setenv("TERMINFO_DIRS",
2366            "/usr/share/terminfo:/lib/terminfo:/usr/share/lib/terminfo",
2367            0);
2368 
2369     {
2370         int register_collation_functions(sqlite3 * db);
2371 
2372         register_sqlite_funcs(lnav_data.ld_db.in(), sqlite_registration_funcs);
2373         register_collation_functions(lnav_data.ld_db.in());
2374     }
2375 
2376     register_environ_vtab(lnav_data.ld_db.in());
2377     {
2378         static auto vtab_modules =
2379             injector::get<std::vector<std::shared_ptr<vtab_module_base>>>();
2380 
2381         for (const auto& mod : vtab_modules) {
2382             mod->create(lnav_data.ld_db.in());
2383         }
2384     }
2385 
2386     register_views_vtab(lnav_data.ld_db.in());
2387     register_regexp_vtab(lnav_data.ld_db.in());
2388     register_xpath_vtab(lnav_data.ld_db.in());
2389     register_fstat_vtab(lnav_data.ld_db.in());
2390 
2391     lnav_data.ld_vtab_manager = std::make_unique<log_vtab_manager>(
2392         lnav_data.ld_db, lnav_data.ld_views[LNV_LOG], lnav_data.ld_log_source);
2393 
2394     lnav_data.ld_views[LNV_HELP]
2395         .set_sub_source(&lnav_data.ld_help_source)
2396         .set_word_wrap(true);
2397     auto log_fos = new field_overlay_source(lnav_data.ld_log_source,
2398                                             lnav_data.ld_text_source);
2399     if (lnav_data.ld_flags & LNF_HEADLESS) {
2400         log_fos->fos_show_status = false;
2401     }
2402     log_fos->fos_contexts.emplace("", false, true);
2403     lnav_data.ld_views[LNV_LOG]
2404         .set_sub_source(&lnav_data.ld_log_source)
2405         .set_delegate(new action_delegate(lnav_data.ld_log_source))
2406         .add_input_delegate(lnav_data.ld_log_source)
2407         .set_tail_space(2_vl)
2408         .set_overlay_source(log_fos);
2409     lnav_data.ld_views[LNV_TEXT]
2410         .set_sub_source(&lnav_data.ld_text_source);
2411     lnav_data.ld_views[LNV_HISTOGRAM]
2412         .set_sub_source(&lnav_data.ld_hist_source2);
2413     lnav_data.ld_views[LNV_DB]
2414         .set_sub_source(&lnav_data.ld_db_row_source);
2415     lnav_data.ld_db_overlay.dos_labels = &lnav_data.ld_db_row_source;
2416     lnav_data.ld_views[LNV_DB]
2417         .set_overlay_source(&lnav_data.ld_db_overlay);
2418     lnav_data.ld_views[LNV_SPECTRO]
2419         .set_sub_source(&lnav_data.ld_spectro_source)
2420         .set_overlay_source(&lnav_data.ld_spectro_source)
2421         .add_input_delegate(lnav_data.ld_spectro_source)
2422         .set_tail_space(2_vl);
2423 
2424     lnav_data.ld_doc_view.set_sub_source(&lnav_data.ld_doc_source);
2425     lnav_data.ld_example_view.set_sub_source(&lnav_data.ld_example_source);
2426     lnav_data.ld_match_view.set_sub_source(&lnav_data.ld_match_source);
2427     lnav_data.ld_preview_view.set_sub_source(&lnav_data.ld_preview_source);
2428     lnav_data.ld_filter_view
2429         .set_sub_source(&lnav_data.ld_filter_source)
2430         .add_input_delegate(lnav_data.ld_filter_source)
2431         .add_child_view(&lnav_data.ld_filter_source.fss_match_view)
2432         .add_child_view(&lnav_data.ld_filter_source.fss_editor);
2433     lnav_data.ld_files_view
2434         .set_sub_source(&lnav_data.ld_files_source)
2435         .add_input_delegate(lnav_data.ld_files_source);
2436 
2437     for (lpc = 0; lpc < LNV__MAX; lpc++) {
2438         lnav_data.ld_views[lpc].set_gutter_source(new log_gutter_source());
2439     }
2440 
2441     {
2442         hist_source2 &hs = lnav_data.ld_hist_source2;
2443 
2444         lnav_data.ld_log_source.set_index_delegate(
2445             new hist_index_delegate(lnav_data.ld_hist_source2,
2446                                     lnav_data.ld_views[LNV_HISTOGRAM]));
2447         hs.init();
2448         lnav_data.ld_zoom_level = 3;
2449         hs.set_time_slice(ZOOM_LEVELS[lnav_data.ld_zoom_level]);
2450     }
2451 
2452     for (lpc = 0; lpc < LNV__MAX; lpc++) {
2453         lnav_data.ld_views[lpc].set_title(view_titles[lpc]);
2454     }
2455 
2456     load_formats(lnav_data.ld_config_paths, loader_errors);
2457 
2458     {
2459         auto_mem<char, sqlite3_free> errmsg;
2460 
2461         if (sqlite3_exec(lnav_data.ld_db.in(),
2462                          init_sql.to_string_fragment().data(),
2463                          nullptr,
2464                          nullptr,
2465                          errmsg.out()) != SQLITE_OK) {
2466             fprintf(stderr,
2467                     "error: unable to execute DB init -- %s\n",
2468                     errmsg.in());
2469         }
2470     }
2471 
2472     lnav_data.ld_vtab_manager->register_vtab(std::make_shared<all_logs_vtab>());
2473     lnav_data.ld_vtab_manager->register_vtab(std::make_shared<log_format_vtab_impl>(
2474             *log_format::find_root_format("generic_log")));
2475 
2476     for (auto &iter : log_format::get_root_formats()) {
2477         auto lvi = iter->get_vtab_impl();
2478 
2479         if (lvi != nullptr) {
2480             lnav_data.ld_vtab_manager->register_vtab(lvi);
2481         }
2482     }
2483 
2484     load_format_extra(lnav_data.ld_db.in(), lnav_data.ld_config_paths, loader_errors);
2485     load_format_vtabs(lnav_data.ld_vtab_manager.get(), loader_errors);
2486     if (!loader_errors.empty()) {
2487         print_errors(loader_errors);
2488         return EXIT_FAILURE;
2489     }
2490 
2491     auto _vtab_cleanup = finally([] {
2492         static const char *VIRT_TABLES = R"(
2493 SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%'
2494 )";
2495 
2496         for (auto& lf : lnav_data.ld_active_files.fc_files) {
2497             lf->close();
2498         }
2499         rebuild_indexes(ui_clock::now());
2500 
2501         lnav_data.ld_vtab_manager = nullptr;
2502 
2503         auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
2504         std::vector<std::string> tables_to_drop;
2505         bool done = false;
2506 
2507         sqlite3_prepare_v2(lnav_data.ld_db.in(),
2508                            VIRT_TABLES,
2509                            -1,
2510                            stmt.out(),
2511                            nullptr);
2512         do {
2513             auto ret = sqlite3_step(stmt.in());
2514 
2515             switch (ret) {
2516                 case SQLITE_OK:
2517                 case SQLITE_DONE:
2518                     done = true;
2519                     break;
2520                 case SQLITE_ROW:
2521                     tables_to_drop.emplace_back(fmt::format(
2522                         "DROP TABLE {}", sqlite3_column_text(stmt.in(), 0)));
2523                     break;
2524             }
2525         } while (!done);
2526 
2527         for (auto &drop_stmt : tables_to_drop) {
2528             sqlite3_exec(lnav_data.ld_db.in(),
2529                          drop_stmt.c_str(),
2530                          nullptr,
2531                          nullptr,
2532                          nullptr);
2533         }
2534     });
2535 
2536     if (!(lnav_data.ld_flags & LNF_CHECK_CONFIG)) {
2537         DEFAULT_FILES.insert(make_pair(LNF_SYSLOG, string("var/log/messages")));
2538         DEFAULT_FILES.insert(
2539                 make_pair(LNF_SYSLOG, string("var/log/system.log")));
2540         DEFAULT_FILES.insert(make_pair(LNF_SYSLOG, string("var/log/syslog")));
2541         DEFAULT_FILES.insert(
2542                 make_pair(LNF_SYSLOG, string("var/log/syslog.log")));
2543     }
2544 
2545     init_lnav_commands(lnav_commands);
2546 
2547     lnav_data.ld_looping        = true;
2548     lnav_data.ld_mode           = LNM_PAGING;
2549 
2550     if ((isatty(STDIN_FILENO) || is_dev_null(STDIN_FILENO)) && argc == 0 &&
2551         !(lnav_data.ld_flags & LNF__ALL) &&
2552         !(lnav_data.ld_flags & LNF_NO_DEFAULT)) {
2553         lnav_data.ld_flags |= LNF_SYSLOG;
2554     }
2555     if (lnav_data.ld_flags != 0) {
2556         char start_dir[FILENAME_MAX];
2557 
2558         if (getcwd(start_dir, sizeof(start_dir)) == nullptr) {
2559             perror("getcwd");
2560         }
2561         else {
2562             do {
2563                 for (lpc = 0; lpc < LNB__MAX; lpc++) {
2564                     if (!append_default_files((lnav_flags_t)(1L << lpc))) {
2565                         retval = EXIT_FAILURE;
2566                     }
2567                 }
2568             } while (lnav_data.ld_active_files.fc_file_names.empty() &&
2569                      change_to_parent_dir());
2570 
2571             if (chdir(start_dir) == -1) {
2572                 perror("chdir(start_dir)");
2573             }
2574         }
2575     }
2576 
2577     {
2578         const auto internals_dir = getenv("DUMP_INTERNALS_DIR");
2579 
2580         if (internals_dir) {
2581             dump_schema_to(lnav_config_handlers, internals_dir, "config-v1.schema.json");
2582             dump_schema_to(root_format_handler, internals_dir, "format-v1.schema.json");
2583 
2584             execute_examples();
2585 
2586             auto cmd_ref_path = ghc::filesystem::path(internals_dir) / "cmd-ref.rst";
2587             auto cmd_file = unique_ptr<FILE, decltype(&fclose)>(fopen(cmd_ref_path.c_str(), "w+"), fclose);
2588 
2589             if (cmd_file.get()) {
2590                 set<readline_context::command_t *> unique_cmds;
2591 
2592                 for (auto &cmd : lnav_commands) {
2593                     if (unique_cmds.find(cmd.second) != unique_cmds.end()) {
2594                         continue;
2595                     }
2596                     unique_cmds.insert(cmd.second);
2597                     format_help_text_for_rst(cmd.second->c_help, eval_example,
2598                                              cmd_file.get());
2599                 }
2600             }
2601 
2602             auto sql_ref_path = ghc::filesystem::path(internals_dir) / "sql-ref.rst";
2603             auto sql_file = unique_ptr<FILE, decltype(&fclose)>(fopen(sql_ref_path.c_str(), "w+"), fclose);
2604             set<help_text *> unique_sql_help;
2605 
2606             if (sql_file.get()) {
2607                 for (auto &sql : sqlite_function_help) {
2608                     if (unique_sql_help.find(sql.second) !=
2609                         unique_sql_help.end()) {
2610                         continue;
2611                     }
2612                     unique_sql_help.insert(sql.second);
2613                     format_help_text_for_rst(*sql.second, eval_example,
2614                                              sql_file.get());
2615                 }
2616             }
2617 
2618             return EXIT_SUCCESS;
2619         }
2620     }
2621 
2622     if (argc == 0) {
2623         load_stdin = true;
2624     }
2625 
2626     for (lpc = 0; lpc < argc; lpc++) {
2627         auto_mem<char> abspath;
2628         struct stat    st;
2629 
2630         if (strcmp(argv[lpc], "-") == 0) {
2631             load_stdin = true;
2632         }
2633         else if (startswith(argv[lpc], "pt:")) {
2634 #ifdef HAVE_LIBCURL
2635             lnav_data.ld_pt_search = argv[lpc];
2636 #else
2637             fprintf(stderr, "error: lnav is not compiled with libcurl\n");
2638             retval = EXIT_FAILURE;
2639 #endif
2640         }
2641 #ifdef HAVE_LIBCURL
2642         else if (is_url(argv[lpc])) {
2643             auto ul = std::make_shared<url_loader>(argv[lpc]);
2644 
2645             lnav_data.ld_active_files.fc_file_names[argv[lpc]]
2646                 .with_fd(ul->copy_fd());
2647             isc::to<curl_looper&, services::curl_streamer_t>()
2648                 .send([ul](auto& clooper) {
2649                     clooper.add_request(ul);
2650                 });
2651         }
2652 #endif
2653         else if (is_glob(argv[lpc])) {
2654             lnav_data.ld_active_files.fc_file_names[argv[lpc]]
2655                 .with_tail(!(lnav_data.ld_flags & LNF_HEADLESS));
2656         }
2657         else if (stat(argv[lpc], &st) == -1) {
2658             if (strchr(argv[lpc], ':') != nullptr) {
2659                 lnav_data.ld_active_files.fc_file_names[argv[lpc]]
2660                     .with_tail(!(lnav_data.ld_flags & LNF_HEADLESS));
2661             } else {
2662                 fprintf(stderr,
2663                         "Cannot stat file: %s -- %s\n",
2664                         argv[lpc],
2665                         strerror(errno));
2666                 retval = EXIT_FAILURE;
2667             }
2668         }
2669         else if (access(argv[lpc], R_OK) == -1) {
2670             fprintf(stderr,
2671                     "Cannot read file: %s -- %s\n",
2672                     argv[lpc],
2673                     strerror(errno));
2674             retval = EXIT_FAILURE;
2675         }
2676         else if (S_ISFIFO(st.st_mode)) {
2677             auto_fd fifo_fd;
2678 
2679             if ((fifo_fd = open(argv[lpc], O_RDONLY)) == -1) {
2680                 fprintf(stderr,
2681                         "Cannot open fifo: %s -- %s\n",
2682                         argv[lpc],
2683                         strerror(errno));
2684                 retval = EXIT_FAILURE;
2685             } else {
2686                 auto fifo_piper = make_shared<piper_proc>(
2687                     fifo_fd.release(),
2688                     false,
2689                     open_temp_file(ghc::filesystem::temp_directory_path() /
2690                                    "lnav.fifo.XXXXXX")
2691                         .map([](auto pair) {
2692                             ghc::filesystem::remove(pair.first);
2693 
2694                             return pair;
2695                         })
2696                         .expect("Cannot create temporary file for FIFO")
2697                         .second);
2698                 auto fifo_out_fd = fifo_piper->get_fd();
2699                 char desc[128];
2700 
2701                 snprintf(desc, sizeof(desc),
2702                          "FIFO [%d]",
2703                          lnav_data.ld_fifo_counter++);
2704                 lnav_data.ld_active_files.fc_file_names[desc]
2705                     .with_fd(fifo_out_fd);
2706                 lnav_data.ld_pipers.push_back(fifo_piper);
2707             }
2708         }
2709         else if ((abspath = realpath(argv[lpc], nullptr)) == nullptr) {
2710             perror("Cannot find file");
2711             retval = EXIT_FAILURE;
2712         }
2713         else if (S_ISDIR(st.st_mode)) {
2714             string dir_wild(abspath.in());
2715 
2716             if (dir_wild[dir_wild.size() - 1] == '/') {
2717                 dir_wild.resize(dir_wild.size() - 1);
2718             }
2719             lnav_data.ld_active_files.fc_file_names
2720                 .emplace(dir_wild + "/*", logfile_open_options());
2721         }
2722         else {
2723             lnav_data.ld_active_files.fc_file_names
2724                 .emplace(abspath.in(), logfile_open_options());
2725         }
2726     }
2727 
2728     if (lnav_data.ld_flags & LNF_CHECK_CONFIG) {
2729         rescan_files(true);
2730         for (auto &lf : lnav_data.ld_active_files.fc_files) {
2731             logfile::rebuild_result_t rebuild_result;
2732 
2733             do {
2734                 rebuild_result = lf->rebuild_index();
2735             } while (rebuild_result == logfile::rebuild_result_t::NEW_LINES ||
2736                      rebuild_result == logfile::rebuild_result_t::NEW_ORDER);
2737             auto fmt = lf->get_format();
2738             if (fmt == nullptr) {
2739                 fprintf(stderr, "error:%s:no format found for file\n",
2740                         lf->get_filename().c_str());
2741                 retval = EXIT_FAILURE;
2742                 continue;
2743             }
2744             for (auto line_iter = lf->begin(); line_iter != lf->end(); ++line_iter) {
2745                 if (line_iter->get_msg_level() != log_level_t::LEVEL_INVALID) {
2746                     continue;
2747                 }
2748 
2749                 size_t partial_len;
2750 
2751                 auto read_result = lf->read_line(line_iter);
2752                 if (read_result.isErr()) {
2753                     continue;
2754                 }
2755                 shared_buffer_ref sbr = read_result.unwrap();
2756                 if (fmt->scan_for_partial(sbr, partial_len)) {
2757                     long line_number = distance(lf->begin(), line_iter);
2758                     string full_line(sbr.get_data(), sbr.length());
2759                     string partial_line(sbr.get_data(), partial_len);
2760 
2761                     fprintf(stderr,
2762                             "error:%s:%ld:line did not match format %s\n",
2763                             lf->get_filename().c_str(), line_number,
2764                             fmt->get_pattern_name(line_number).c_str());
2765                     fprintf(stderr,
2766                             "error:%s:%ld:         line -- %s\n",
2767                             lf->get_filename().c_str(), line_number,
2768                             full_line.c_str());
2769                     if (partial_len > 0) {
2770                         fprintf(stderr,
2771                                 "error:%s:%ld:partial match -- %s\n",
2772                                 lf->get_filename().c_str(), line_number,
2773                                 partial_line.c_str());
2774                     }
2775                     else {
2776                         fprintf(stderr,
2777                                 "error:%s:%ld:no partial match found\n",
2778                                 lf->get_filename().c_str(), line_number);
2779                     }
2780                     retval = EXIT_FAILURE;
2781                 }
2782             }
2783         }
2784         return retval;
2785     }
2786 
2787     if (!(lnav_data.ld_flags & (LNF_HEADLESS|LNF_CHECK_CONFIG)) && !isatty(STDOUT_FILENO)) {
2788         fprintf(stderr, "error: stdout is not a tty.\n");
2789         retval = EXIT_FAILURE;
2790     }
2791 
2792     if (load_stdin && !isatty(STDIN_FILENO) && !is_dev_null(STDIN_FILENO) && !exec_stdin) {
2793         if (stdin_out == nullptr) {
2794             auto pattern = lnav::paths::dotlnav() / "stdin-captures/stdin.XXXXXX";
2795 
2796             auto open_result = open_temp_file(pattern);
2797             if (open_result.isErr()) {
2798                 fprintf(stderr,
2799                         "Unable to open temporary file for stdin: %s",
2800                         open_result.unwrapErr().c_str());
2801                 return EXIT_FAILURE;
2802             }
2803 
2804             auto temp_pair = open_result.unwrap();
2805             stdin_tmp_path = temp_pair.first;
2806             stdin_out_fd = temp_pair.second;
2807         } else {
2808             if ((stdin_out_fd = open(stdin_out, O_RDWR | O_CREAT | O_TRUNC, 0600)) == -1) {
2809                 perror("Unable to open output file for stdin");
2810                 return EXIT_FAILURE;
2811             }
2812         }
2813 
2814         stdin_reader = make_shared<piper_proc>(
2815             STDIN_FILENO, lnav_data.ld_flags & LNF_TIMESTAMP, stdin_out_fd);
2816         lnav_data.ld_active_files.fc_file_names["stdin"]
2817             .with_fd(auto_fd(stdin_out_fd))
2818             .with_include_in_session(false);
2819         lnav_data.ld_pipers.push_back(stdin_reader);
2820     }
2821 
2822     if (!isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)) {
2823         if (dup2(STDOUT_FILENO, STDIN_FILENO) == -1) {
2824             perror("cannot dup stdout to stdin");
2825         }
2826     }
2827 
2828     if (lnav_data.ld_active_files.fc_file_names.empty() &&
2829         lnav_data.ld_commands.empty() &&
2830         lnav_data.ld_pt_search.empty() &&
2831         !(lnav_data.ld_flags & (LNF_HELP|LNF_NO_DEFAULT))) {
2832         fprintf(stderr, "error: no log files given/found.\n");
2833         retval = EXIT_FAILURE;
2834     }
2835 
2836     if (retval != EXIT_SUCCESS) {
2837         usage();
2838     }
2839     else {
2840         isc::supervisor root_superv(injector::get<isc::service_list>());
2841 
2842         try {
2843             log_info("startup: %s", VCS_PACKAGE_STRING);
2844             log_host_info();
2845             log_info("Libraries:");
2846 #ifdef HAVE_BZLIB_H
2847             log_info("  bzip=%s", BZ2_bzlibVersion());
2848 #endif
2849 #ifdef HAVE_LIBCURL
2850             log_info("  curl=%s (%s)", LIBCURL_VERSION, LIBCURL_TIMESTAMP);
2851 #endif
2852 #ifdef HAVE_ARCHIVE_H
2853             log_info("  libarchive=%d", ARCHIVE_VERSION_NUMBER);
2854 #endif
2855             log_info("  ncurses=%s", NCURSES_VERSION);
2856             log_info("  pcre=%s", pcre_version());
2857             log_info("  readline=%s", rl_library_version);
2858             log_info("  sqlite=%s", sqlite3_version);
2859             log_info("  zlib=%s", zlibVersion());
2860             log_info("lnav_data:");
2861             log_info("  flags=%x", lnav_data.ld_flags);
2862             log_info("  commands:");
2863             for (auto cmd_iter = lnav_data.ld_commands.begin();
2864                  cmd_iter != lnav_data.ld_commands.end();
2865                  ++cmd_iter) {
2866                 log_info("    %s", cmd_iter->c_str());
2867             }
2868             log_info("  files:");
2869             for (auto file_iter = lnav_data.ld_active_files.fc_file_names.begin();
2870                  file_iter != lnav_data.ld_active_files.fc_file_names.end();
2871                  ++file_iter) {
2872                 log_info("    %s", file_iter->first.c_str());
2873             }
2874 
2875             if (lnav_data.ld_flags & LNF_HEADLESS) {
2876                 std::vector<pair<Result<string, string>, string>> cmd_results;
2877                 textview_curses *log_tc, *text_tc, *tc;
2878                 bool output_view = true;
2879 
2880                 view_colors::init();
2881                 rescan_files(true);
2882                 if (!lnav_data.ld_active_files.fc_name_to_errors.empty()) {
2883                     for (const auto& pair : lnav_data.ld_active_files.fc_name_to_errors) {
2884                         fprintf(stderr,
2885                                 "error: unable to open file: %s -- %s\n",
2886                                 pair.first.c_str(),
2887                                 pair.second.fei_description.c_str());
2888                     }
2889 
2890                     return EXIT_FAILURE;
2891                 }
2892                 init_session();
2893                 lnav_data.ld_exec_context.set_output("stdout", stdout, nullptr);
2894                 alerter::singleton().enabled(false);
2895 
2896                 log_tc = &lnav_data.ld_views[LNV_LOG];
2897                 log_tc->set_height(24_vl);
2898                 lnav_data.ld_view_stack.push_back(log_tc);
2899                 // Read all of stdin
2900                 wait_for_pipers();
2901                 rebuild_indexes_repeatedly();
2902 
2903                 log_tc->set_top(0_vl);
2904                 text_tc = &lnav_data.ld_views[LNV_TEXT];
2905                 text_tc->set_top(0_vl);
2906                 text_tc->set_height(vis_line_t(text_tc->get_inner_height()));
2907                 if (lnav_data.ld_log_source.text_line_count() == 0 &&
2908                     lnav_data.ld_text_source.text_line_count() > 0) {
2909                     toggle_view(&lnav_data.ld_views[LNV_TEXT]);
2910                 }
2911 
2912                 log_info("Executing initial commands");
2913                 execute_init_commands(lnav_data.ld_exec_context, cmd_results);
2914                 archive_manager::cleanup_cache();
2915                 tailer::cleanup_cache();
2916                 wait_for_pipers();
2917                 isc::to<curl_looper&, services::curl_streamer_t>()
2918                     .send_and_wait([](auto& clooper) {
2919                         clooper.process_all();
2920                     });
2921                 rebuild_indexes_repeatedly();
2922 
2923                 for (auto &pair : cmd_results) {
2924                     if (pair.first.isErr()) {
2925                         fprintf(stderr, "%s\n", pair.first.unwrapErr().c_str());
2926                         output_view = false;
2927                     }
2928                     else {
2929                         auto msg = pair.first.unwrap();
2930 
2931                         if (startswith(msg, "info:")) {
2932                             if (lnav_data.ld_flags & LNF_VERBOSE) {
2933                                 printf("%s\n", msg.c_str());
2934                             }
2935                         } else if (!msg.empty()) {
2936                             printf("%s\n", msg.c_str());
2937                             output_view = false;
2938                         }
2939                     }
2940                 }
2941 
2942                 if (output_view &&
2943                     !(lnav_data.ld_flags & LNF_QUIET) &&
2944                     !lnav_data.ld_view_stack.empty() &&
2945                     !lnav_data.ld_stdout_used) {
2946                     bool suppress_empty_lines = false;
2947                     list_overlay_source *los;
2948                     unsigned long view_index;
2949                     vis_line_t y;
2950 
2951                     tc = *lnav_data.ld_view_stack.top();
2952                     view_index = tc - lnav_data.ld_views;
2953                     switch (view_index) {
2954                         case LNV_DB:
2955                         case LNV_HISTOGRAM:
2956                             suppress_empty_lines = true;
2957                             break;
2958                         default:
2959                             break;
2960                     }
2961 
2962                     los = tc->get_overlay_source();
2963 
2964                     vis_line_t vl;
2965                     for (vl = tc->get_top();
2966                          vl < tc->get_inner_height();
2967                          ++vl, ++y) {
2968                         attr_line_t al;
2969                         string &line = al.get_string();
2970                         while (los != nullptr &&
2971                                los->list_value_for_overlay(*tc, y, tc->get_inner_height(), vl, al)) {
2972                             if (write(STDOUT_FILENO, line.c_str(),
2973                                       line.length()) == -1 ||
2974                                 write(STDOUT_FILENO, "\n", 1) == -1) {
2975                                 perror("1 write to STDOUT");
2976                             }
2977                             ++y;
2978                         }
2979 
2980                         vector<attr_line_t> rows(1);
2981                         tc->listview_value_for_rows(*tc, vl, rows);
2982                         if (suppress_empty_lines && rows[0].empty()) {
2983                             continue;
2984                         }
2985 
2986                         struct line_range lr = find_string_attr_range(
2987                                 rows[0].get_attrs(), &SA_ORIGINAL_LINE);
2988                         if (write(STDOUT_FILENO, lr.substr(rows[0].get_string()),
2989                                   lr.sublen(rows[0].get_string())) == -1 ||
2990                             write(STDOUT_FILENO, "\n", 1) == -1) {
2991                             perror("2 write to STDOUT");
2992                         }
2993 
2994                     }
2995                     {
2996                         attr_line_t al;
2997                         string &line = al.get_string();
2998 
2999                         while (los != nullptr &&
3000                                los->list_value_for_overlay(*tc, y, tc->get_inner_height(), vl, al) &&
3001                                !al.empty()) {
3002                             if (write(STDOUT_FILENO, line.c_str(),
3003                                       line.length()) == -1 ||
3004                                 write(STDOUT_FILENO, "\n", 1) == -1) {
3005                                 perror("1 write to STDOUT");
3006                             }
3007                             ++y;
3008                         }
3009                     }
3010                 }
3011             }
3012             else {
3013                 init_session();
3014 
3015                 guard_termios gt(STDIN_FILENO);
3016                 lnav_log_orig_termios = gt.get_termios();
3017 
3018                 looper();
3019 
3020                 dup2(STDOUT_FILENO, STDERR_FILENO);
3021 
3022                 signal(SIGINT, SIG_DFL);
3023 
3024                 save_session();
3025             }
3026         }
3027         catch (line_buffer::error & e) {
3028             fprintf(stderr, "error: %s\n", strerror(e.e_err));
3029         }
3030 
3031         // When reading from stdin, tell the user where the capture file is
3032         // stored so they can look at it later.
3033         if (stdin_out_fd != -1 &&
3034             stdin_out == nullptr &&
3035             !(lnav_data.ld_flags & LNF_QUIET) &&
3036             !(lnav_data.ld_flags & LNF_HEADLESS)) {
3037             if (ghc::filesystem::file_size(stdin_tmp_path) > MAX_STDIN_CAPTURE_SIZE) {
3038                 log_info("not saving large stdin capture -- %s",
3039                          stdin_tmp_path.c_str());
3040                 ghc::filesystem::remove(stdin_tmp_path);
3041             } else {
3042                 auto home = getenv("HOME");
3043                 auto path_str = stdin_tmp_path.string();
3044 
3045                 if (home != nullptr && startswith(path_str, home)) {
3046                     path_str = path_str.substr(strlen(home));
3047                     if (path_str[0] != '/') {
3048                         path_str.insert(0, 1, '/');
3049                     }
3050                     path_str.insert(0, 1, '~');
3051                 }
3052 
3053                 fprintf(stderr,
3054                         "info: stdin was captured, you can reopen it using -- "
3055                         "lnav %s\n",
3056                         path_str.c_str());
3057             }
3058         }
3059     }
3060 
3061     return retval;
3062 }
3063