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(¤t_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(¤t_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