text: /**
   * Copyright (c) 2007-2016, Timothy Stack
   *
   * All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions are met:
   *
   * * Redistributions of source code must retain the above copyright notice, this
   * list of conditions and the following disclaimer.
   * * Redistributions in binary form must reproduce the above copyright notice,
   * this list of conditions and the following disclaimer in the documentation
   * and/or other materials provided with the distribution.
   * * Neither the name of Timothy Stack nor the names of its contributors
   * may be used to endorse or promote products derived from this software
   * without specific prior written permission.
   *
   * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
   * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
   * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
   * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   *
   * @file lnav.cc
   *
   * XXX This file has become a dumping ground for code and needs to be broken up
   * a bit.
   */
  
  #include "config.h"
  
  #include <stdio.h>
  #include <errno.h>
  
  #include <time.h>
  #include <glob.h>
  #include <locale.h>
  
  #include <fcntl.h>
  #include <signal.h>
  #include <stdlib.h>
  #include <string.h>
  #include <unistd.h>
  #include <termios.h>
  #include <libgen.h>
  
  #include <sys/types.h>
  #include <sys/stat.h>
  #include <sys/ioctl.h>
  #include <sys/time.h>
  #include <sys/wait.h>
  
  #include <readline/readline.h>
  
  #if defined(__OpenBSD__) && defined(__clang__) && \
      !defined(_WCHAR_H_CPLUSPLUS_98_CONFORMANCE_)
  #define _WCHAR_H_CPLUSPLUS_98_CONFORMANCE_
  #endif
  #include <map>
  #include <memory>
  #include <set>
  #include <vector>
  #include <algorithm>
  #include <functional>
  
  #include <sqlite3.h>
  
  #ifdef HAVE_BZLIB_H
  #include <bzlib.h>
  #endif
  
  #include "lnav.hh"
  #include "help-txt.h"
  #include "init-sql.h"
  #include "logfile.hh"
  #include "base/func_util.hh"
  #include "base/humanize.network.hh"
  #include "base/humanize.time.hh"
  #include "base/injector.bind.hh"
  #include "base/isc.hh"
  #include "base/string_util.hh"
  #include "base/lnav_log.hh"
  #include "base/paths.hh"
  #include "bound_tags.hh"
  #include "lnav_util.hh"
  #include "ansi_scrubber.hh"
  #include "listview_curses.hh"
  #include "vt52_curses.hh"
  #include "readline_curses.hh"
  #include "textview_curses.hh"
  #include "logfile_sub_source.hh"
  #include "textfile_sub_source.hh"
  #include "grep_proc.hh"
  #include "bookmarks.hh"
  #include "hist_source.hh"
  #include "top_status_source.hh"
  #include "bottom_status_source.hh"
  #include "piper_proc.hh"
  #include "log_vtab_impl.hh"
  #include "termios_guard.hh"
  #include "xterm_mouse.hh"
  #include "lnav_commands.hh"
  #include "column_namer.hh"
  #include "log_data_table.hh"
  #include "log_format_loader.hh"
  #include "log_gutter_source.hh"
  #include "session_data.hh"
  #include "lnav_config.hh"
  #include "sql_util.hh"
  #include "sqlite-extension-func.hh"
  #include "term_extra.hh"
  #include "log_data_helper.hh"
  #include "readline_highlighters.hh"
  #include "environ_vtab.hh"
  #include "views_vtab.hh"
  #include "all_logs_vtab.hh"
  #include "regexp_vtab.hh"
  #include "fstat_vtab.hh"
  #include "xpath_vtab.hh"
  #include "textfile_highlighters.hh"
  #include "base/future_util.hh"
  #include "tailer/tailer.looper.hh"
  #include "service_tags.hh"
  
  #ifdef HAVE_LIBCURL
  #include <curl/curl.h>
  #endif
  
  #include "curl_looper.hh"
  
  #if HAVE_ARCHIVE_H
  #include <archive.h>
  #endif
  
  #include "yajlpp/yajlpp.hh"
  #include "readline_callbacks.hh"
  #include "command_executor.hh"
  #include "hotkeys.hh"
  #include "readline_possibilities.hh"
  #include "field_overlay_source.hh"
  #include "url_loader.hh"
  #include "shlex.hh"
  #include "log_actions.hh"
  #include "archive_manager.hh"
  
  #ifndef SYSCONFDIR
  #define SYSCONFDIR "/usr/etc"
  #endif
  
  using namespace std;
  using namespace std::literals::chrono_literals;
  
  static bool initial_build = false;
  static multimap<lnav_flags_t, string> DEFAULT_FILES;
  static auto intern_lifetime = intern_string::get_table_lifetime();
  
  struct lnav_data_t lnav_data;
  
  const int ZOOM_LEVELS[] = {
      1,
      30,
      60,
      5 * 60,
      15 * 60,
      60 * 60,
      4 * 60 * 60,
      8 * 60 * 60,
      24 * 60 * 60,
      7 * 24 * 60 * 60,
  };
  
  const ssize_t ZOOM_COUNT = sizeof(ZOOM_LEVELS) / sizeof(int);
  
  const char *lnav_zoom_strings[] = {
      "1-second",
      "30-second",
      "1-minute",
      "5-minute",
      "15-minute",
      "1-hour",
      "4-hour",
      "8-hour",
      "1-day",
      "1-week",
  
      nullptr
  };
  
  static const char *view_titles[LNV__MAX] = {
      "LOG",
      "TEXT",
      "HELP",
      "HIST",
      "DB",
      "SCHEMA",
      "PRETTY",
      "SPECTRO",
  };
  
  static std::vector<std::string> DEFAULT_DB_KEY_NAMES = {
      "match_index",
      "capture_index",
      "capture_count",
      "range_start",
      "range_stop",
      "inode",
      "device",
      "inode",
      "rowid",
      "st_dev",
      "st_ino",
      "st_mode",
      "st_rdev",
      "st_uid",
      "st_gid",
  };
  
  const static size_t MAX_STDIN_CAPTURE_SIZE = 10 * 1024 * 1024;
  
  static auto bound_active_files =
      injector::bind<file_collection>::to_instance(+[]() {
          return &lnav_data.ld_active_files;
      });
  
  static auto bound_last_rel_time =
      injector::bind<relative_time, last_relative_time_tag>::to_singleton();
  
  static auto bound_term_extra =
      injector::bind<term_extra>::to_singleton();
  
  static auto bound_xterm_mouse =
      injector::bind<xterm_mouse>::to_singleton();
  
  static auto bound_scripts =
      injector::bind<available_scripts>::to_singleton();
  
  static auto bound_curl =
      injector::bind_multiple<isc::service_base>()
          .add_singleton<curl_looper, services::curl_streamer_t>();
  
  static auto bound_tailer =
      injector::bind_multiple<isc::service_base>()
          .add_singleton<tailer::looper, services::remote_tailer_t>();
  
  static auto bound_main =
      injector::bind_multiple<static_service>()
          .add_singleton<main_looper, services::main_t>();
  
  namespace injector {
  template<>
  void force_linking(last_relative_time_tag anno)
  {
  }
  
  template<>
  void force_linking(services::curl_streamer_t anno)
  {
  }
  
  template<>
  void force_linking(services::remote_tailer_t anno)
  {
  }
  
  template<>
  void force_linking(services::main_t anno)
  {
  }
  }
  
  void add_recent_netlocs_possibilities()
  {
      readline_curses *rc = lnav_data.ld_rl_view;
  
      rc->clear_possibilities(LNM_COMMAND, "recent-netlocs");
      std::set<std::string> netlocs;
  
      isc::to<tailer::looper&, services::remote_tailer_t>()
          .send_and_wait([&netlocs](auto& tlooper) {
              netlocs = tlooper.active_netlocs();
          });
      netlocs.insert(session_data.sd_recent_netlocs.begin(),
                     session_data.sd_recent_netlocs.end());
      rc->add_possibility(LNM_COMMAND, "recent-netlocs", netlocs);
  }
  
  bool setup_logline_table(exec_context &ec)
  {
      // Hidden columns don't show up in the table_info pragma.
      static const char *hidden_table_columns[] = {
          "log_time_msecs",
          "log_path",
          "log_text",
          "log_body",
  
          nullptr
      };
  
      textview_curses &log_view = lnav_data.ld_views[LNV_LOG];
      bool             retval   = false;
      bool update_possibilities = (
          lnav_data.ld_rl_view != nullptr &&
          ec.ec_local_vars.size() == 1);
  
      if (update_possibilities) {
          lnav_data.ld_rl_view->clear_possibilities(LNM_SQL, "*");
          add_view_text_possibilities(lnav_data.ld_rl_view, LNM_SQL, "*", &log_view);
      }
  
      if (log_view.get_inner_height()) {
          static intern_string_t logline = intern_string::lookup("logline");
          vis_line_t     vl = log_view.get_top();
          content_line_t cl = lnav_data.ld_log_source.at_base(vl);
  
          lnav_data.ld_vtab_manager->unregister_vtab(logline);
          lnav_data.ld_vtab_manager->register_vtab(
              std::make_shared<log_data_table>(lnav_data.ld_log_source,
                                               *lnav_data.ld_vtab_manager,
                                               cl,
                                               logline));
  
          if (update_possibilities) {
              log_data_helper ldh(lnav_data.ld_log_source);
  
              ldh.parse_line(cl);
  
              std::map<const intern_string_t, json_ptr_walk::walk_list_t>::const_iterator pair_iter;
              for (pair_iter = ldh.ldh_json_pairs.begin();
                   pair_iter != ldh.ldh_json_pairs.end();
                   ++pair_iter) {
                  for (size_t lpc = 0; lpc < pair_iter->second.size(); lpc++) {
                      lnav_data.ld_rl_view->add_possibility(LNM_SQL, "*",
                          ldh.format_json_getter(pair_iter->first, lpc));
                  }
              }
          }
  
          retval = true;
      }
  
      auto &db_key_names = lnav_data.ld_db_key_names;
  
      db_key_names = DEFAULT_DB_KEY_NAMES;
  
      if (update_possibilities) {
          add_env_possibilities(LNM_SQL);
  
          lnav_data.ld_rl_view->add_possibility(LNM_SQL, "*",
                                                std::begin(sql_keywords),
                                                std::end(sql_keywords));
          lnav_data.ld_rl_view->add_possibility(LNM_SQL, "*", sql_function_names);
          lnav_data.ld_rl_view->add_possibility(LNM_SQL, "*",
              hidden_table_columns);
  
          for (int lpc = 0; sqlite_registration_funcs[lpc]; lpc++) {
              struct FuncDef *basic_funcs;
              struct FuncDefAgg *agg_funcs;
  
              sqlite_registration_funcs[lpc](&basic_funcs, &agg_funcs);
              for (int lpc2 = 0; basic_funcs && basic_funcs[lpc2].zName; lpc2++) {
                  const FuncDef &func_def = basic_funcs[lpc2];
  
                  lnav_data.ld_rl_view->add_possibility(
                      LNM_SQL,
                      "*",
                      string(func_def.zName) + (func_def.nArg ? "(" : "()"));
              }
              for (int lpc2 = 0; agg_funcs && agg_funcs[lpc2].zName; lpc2++) {
                  const FuncDefAgg &func_def = agg_funcs[lpc2];
  
                  lnav_data.ld_rl_view->add_possibility(
                      LNM_SQL,
                      "*",
                      string(func_def.zName) + (func_def.nArg ? "(" : "()"));
              }
          }
  
          for (const auto &pair : sqlite_function_help) {
              switch (pair.second->ht_context) {
                  case help_context_t::HC_SQL_FUNCTION:
                  case help_context_t::HC_SQL_TABLE_VALUED_FUNCTION: {
                      string poss = pair.first +
                          (pair.second->ht_parameters.empty() ? "()" : ("("));
  
                      lnav_data.ld_rl_view->add_possibility(LNM_SQL, "*", poss);
                      break;
                  }
                  default:
                      break;
              }
          }
      }
  
      walk_sqlite_metadata(lnav_data.ld_db.in(), lnav_sql_meta_callbacks);
  
      for (const auto &iter : *lnav_data.ld_vtab_manager) {
          iter.second->get_foreign_keys(db_key_names);
      }
  
      stable_sort(db_key_names.begin(), db_key_names.end());
  
      return retval;
  }
  
  /**
   * Observer for loading progress that updates the bottom status bar.
   */
  class loading_observer
      : public logfile_observer {
  public:
      loading_observer()
          : lo_last_offset(0) {
      };
  
      indexing_result logfile_indexing(const shared_ptr<logfile>& lf,
                                       file_off_t off,
                                       file_size_t total) override
      {
          static sig_atomic_t index_counter = 0;
  
          if (lnav_data.ld_flags & (LNF_HEADLESS|LNF_CHECK_CONFIG)) {
              return indexing_result::CONTINUE;
          }
  
          /* XXX require(off <= total); */
          if (off > (off_t)total) {
              off = total;
          }
  
          if ((((size_t)off == total) && (this->lo_last_offset != off)) ||
              ui_periodic_timer::singleton().time_to_update(index_counter)) {
              lnav_data.ld_bottom_source.update_loading(off, total);
              do_update(lf);
              this->lo_last_offset = off;
          }
  
          if (!lnav_data.ld_looping) {
              return indexing_result::BREAK;
          }
          return indexing_result::CONTINUE;
      };
  
      static void do_update(const shared_ptr<logfile>& lf)
      {
          if (isendwin()) {
              return;
          }
          lnav_data.ld_top_source.update_time();
          for (auto &sc : lnav_data.ld_status) {
              sc.do_update();
          }
          if (lf && lnav_data.ld_mode == LNM_FILES && !initial_build) {
              auto &fc = lnav_data.ld_active_files;
              auto iter = std::find(fc.fc_files.begin(),
                                    fc.fc_files.end(), lf);
  
              if (iter != fc.fc_files.end()) {
                  auto index = std::distance(fc.fc_files.begin(), iter);
                  lnav_data.ld_files_view.set_selection(
                      vis_line_t(fc.fc_other_files.size() + index));
                  lnav_data.ld_files_view.reload_data();
                  lnav_data.ld_files_view.do_update();
              }
          }
          refresh();
      };
  
      off_t          lo_last_offset;
  };
  
  class hist_index_delegate : public index_delegate {
  public:
      hist_index_delegate(hist_source2 &hs, textview_curses &tc)
              : hid_source(hs), hid_view(tc) {
  
      };
  
      void index_start(logfile_sub_source &lss) override {
          this->hid_source.clear();
      };
  
      void index_line(logfile_sub_source &lss, logfile *lf, logfile::iterator ll) override {
          if (ll->is_continued() || ll->get_time() == 0) {
              return;
          }
  
          hist_source2::hist_type_t ht;
  
          switch (ll->get_msg_level()) {
              case LEVEL_FATAL:
              case LEVEL_CRITICAL:
              case LEVEL_ERROR:
                  ht = hist_source2::HT_ERROR;
                  break;
              case LEVEL_WARNING:
                  ht = hist_source2::HT_WARNING;
                  break;
              default:
                  ht = hist_source2::HT_NORMAL;
                  break;
          }
  
          this->hid_source.add_value(ll->get_time(), ht);
          if (ll->is_marked() || ll->is_expr_marked()) {
              this->hid_source.add_value(ll->get_time(), hist_source2::HT_MARK);
          }
      };
  
      void index_complete(logfile_sub_source &lss) override {
          this->hid_view.reload_data();
      };
  
  private:
      hist_source2 &hid_source;
      textview_curses &hid_view;
  };
  
  void rebuild_hist()
  {
      logfile_sub_source &lss = lnav_data.ld_log_source;
      hist_source2 &hs = lnav_data.ld_hist_source2;
      int zoom = lnav_data.ld_zoom_level;
  
      hs.set_time_slice(ZOOM_LEVELS[zoom]);
      lss.reload_index_delegate();
  }
  
  class textfile_callback {
  public:
      textfile_callback() : front_file(nullptr), front_top(-1) { };
  
      void closed_files(const std::vector<shared_ptr<logfile>> &files) {
          for (const auto& lf : files) {
              log_info("closed text files: %s", lf->get_filename().c_str());
          }
          lnav_data.ld_active_files.close_files(files);
      };
  
      void promote_file(const shared_ptr<logfile> &lf) {
          if (lnav_data.ld_log_source.insert_file(lf)) {
              this->did_promotion = true;
              log_info("promoting text file to log file: %s (%s)",
                       lf->get_filename().c_str(),
                       lf->get_content_id().c_str());
              auto format = lf->get_format();
              if (format->lf_is_self_describing) {
                  auto vt = format->get_vtab_impl();
  
                  if (vt != nullptr) {
                      lnav_data.ld_vtab_manager->register_vtab(vt);
                  }
              }
  
              auto iter = session_data.sd_file_states.find(lf->get_filename());
              if (iter != session_data.sd_file_states.end()) {
                  log_debug("found state for log file %d",
                            iter->second.fs_is_visible);
  
                  lnav_data.ld_log_source.find_data(lf) | [&iter](auto ld) {
                      ld->set_visibility(iter->second.fs_is_visible);
                  };
              }
          }
          else {
              this->closed_files({lf});
          }
      };
  
      void scanned_file(const shared_ptr<logfile> &lf) {
          if (!lnav_data.ld_files_to_front.empty() &&
                  lnav_data.ld_files_to_front.front().first ==
                          lf->get_filename()) {
              this->front_file = lf;
              this->front_top = lnav_data.ld_files_to_front.front().second;
  
              lnav_data.ld_files_to_front.pop_front();
          }
      };
  
      shared_ptr<logfile> front_file;
      int front_top;
      bool did_promotion{false};
  };
  
  size_t rebuild_indexes(nonstd::optional<ui_clock::time_point> deadline)
  {
      logfile_sub_source &lss = lnav_data.ld_log_source;
      textview_curses &log_view  = lnav_data.ld_views[LNV_LOG];
      textview_curses &text_view = lnav_data.ld_views[LNV_TEXT];
      vis_line_t old_bottoms[LNV__MAX];
      bool scroll_downs[LNV__MAX];
      size_t retval = 0;
  
      for (int lpc = 0; lpc < LNV__MAX; lpc++) {
          old_bottoms[lpc] = lnav_data.ld_views[lpc].get_top_for_last_row();
          scroll_downs[lpc] =
              (lnav_data.ld_views[lpc].get_top() >= old_bottoms[lpc]) &&
              !(lnav_data.ld_flags & LNF_HEADLESS);
      }
  
      {
          textfile_sub_source *tss = &lnav_data.ld_text_source;
          textfile_callback cb;
  
          if (tss->rescan_files(cb, deadline)) {
              text_view.reload_data();
              retval += 1;
          }
  
          if (cb.front_file != nullptr) {
              ensure_view(&text_view);
  
              if (tss->current_file() != cb.front_file) {
                  tss->to_front(cb.front_file);
                  old_bottoms[LNV_TEXT] = -1_vl;
              }
  
              if (cb.front_top < 0) {
                  cb.front_top += text_view.get_inner_height();
              }
              if (cb.front_top < text_view.get_inner_height()) {
                  text_view.set_top(vis_line_t(cb.front_top));
                  scroll_downs[LNV_TEXT] = false;
              }
          }
          if (cb.did_promotion && deadline) {
              // If there's a new log file, extend the deadline so it can be
              // indexed quickly.
              deadline = deadline.value() + 500ms;
          }
      }
  
      std::vector<std::shared_ptr<logfile>> closed_files;
      for (auto& lf : lnav_data.ld_active_files.fc_files) {
          if ((!lf->exists() || lf->is_closed())) {
              log_info("closed log file: %s", lf->get_filename().c_str());
              lnav_data.ld_text_source.remove(lf);
              lnav_data.ld_log_source.remove_file(lf);
              closed_files.emplace_back(lf);
          }
      }
      if (!closed_files.empty()) {
          lnav_data.ld_active_files.close_files(closed_files);
      }
  
      auto result = lss.rebuild_index(deadline);
      if (result != logfile_sub_source::rebuild_result::rr_no_change) {
          size_t new_count = lss.text_line_count();
          bool force =
              result == logfile_sub_source::rebuild_result::rr_full_rebuild;
  
          if ((!scroll_downs[LNV_LOG] ||
               log_view.get_top() > vis_line_t(new_count)) &&
              force) {
              scroll_downs[LNV_LOG] = false;
          }
  
          log_view.reload_data();
  
          {
              unordered_map<string, list<shared_ptr<logfile>>> id_to_files;
              bool reload = false;
  
              for (const auto &lf : lnav_data.ld_active_files.fc_files) {
                  id_to_files[lf->get_content_id()].push_back(lf);
              }
  
              for (auto &pair : id_to_files) {
                  if (pair.second.size() == 1) {
                      continue;
                  }
  
                  pair.second.sort([](const auto& left, const auto& right) {
                      return right->get_stat().st_size <
                             left->get_stat().st_size;
                  });
  
                  auto dupe_name = pair.second.front()->get_unique_path();
                  pair.second.pop_front();
                  for_each(pair.second.begin(),
                           pair.second.end(),
                           [&dupe_name](auto& lf) {
                      log_info("Hiding duplicate file: %s",
                               lf->get_filename().c_str());
                      lf->mark_as_duplicate(dupe_name);
                      lnav_data.ld_log_source.find_data(lf) | [](auto ld) {
                          ld->set_visibility(false);
                      };
                  });
                  reload = true;
              }
  
              if (reload) {
                  lss.text_filters_changed();
              }
          }
  
          retval += 1;
      }
  
      for (int lpc = 0; lpc < LNV__MAX; lpc++) {
          textview_curses &scroll_view = lnav_data.ld_views[lpc];
  
          if (scroll_downs[lpc] && scroll_view.get_top_for_last_row() > scroll_view.get_top()) {
              scroll_view.set_top(scroll_view.get_top_for_last_row());
          }
      }
  
      lnav_data.ld_view_stack.top() | [] (auto tc) {
          lnav_data.ld_filter_status_source.update_filtered(tc->get_sub_source());
          lnav_data.ld_scroll_broadcaster(tc);
      };
  
      return retval;
  }
  
  void rebuild_indexes_repeatedly()
  {
      for (size_t attempt = 0; attempt < 10 && rebuild_indexes() > 0; attempt++) {
          log_info("continuing to rebuild indexes...");
      }
  }
  
  static bool append_default_files(lnav_flags_t flag)
  {
      bool retval = true;
  
      if (lnav_data.ld_flags & flag) {
          auto cwd = ghc::filesystem::current_path();
  
          pair<multimap<lnav_flags_t, string>::iterator,
               multimap<lnav_flags_t, string>::iterator> range;
          for (range = DEFAULT_FILES.equal_range(flag);
               range.first != range.second;
               range.first++) {
              string path = range.first->second;
              struct stat st;
  
              if (access(path.c_str(), R_OK) == 0) {
                  auto_mem<char> abspath;
  
                  path = cwd / range.first->second;
                  if ((abspath = realpath(path.c_str(), nullptr)) == nullptr) {
                      perror("Unable to resolve path");
                  }
                  else {
                      lnav_data.ld_active_files.fc_file_names[abspath.in()];
                  }
              }
              else if (stat(path.c_str(), &st) == 0) {
                  fprintf(stderr,
                          "error: cannot read -- %s%s\n",
                          cwd.c_str(),
                          path.c_str());
                  retval = false;
              }
          }
      }
  
      return retval;
  }
  
  static void sigint(int sig)
  {
      lnav_data.ld_looping = false;
  }
  
  static void sigwinch(int sig)
  {
      lnav_data.ld_winched = true;
  }
  
  static void sigchld(int sig)
  {
      lnav_data.ld_child_terminated = true;
  }
  
  static void handle_rl_key(int ch)
  {
      switch (ch) {
          case KEY_PPAGE:
          case KEY_NPAGE:
          case KEY_CTRL_P:
              handle_paging_key(ch);
              break;
  
          case KEY_CTRL_RBRACKET:
              lnav_data.ld_rl_view->abort();
              break;
  
          default:
              lnav_data.ld_rl_view->handle_key(ch);
              break;
      }
  }
  
  void rl_focus(readline_curses *rc)
  {
      auto fos = (field_overlay_source *)lnav_data.ld_views[LNV_LOG]
                          .get_overlay_source();
  
      fos->fos_contexts.emplace("", false, true);
  }
  
  void rl_blur(readline_curses *rc)
  {
      auto fos = (field_overlay_source *)lnav_data.ld_views[LNV_LOG]
          .get_overlay_source();
  
      fos->fos_contexts.pop();
      lnav_data.ld_preview_generation += 1;
  }
  
  readline_context::command_map_t lnav_commands;
  
  static void usage()
  {
      const char *usage_msg = R"(usage: %s [options] [logfile1 logfile2 ...]
  
  A curses-based log file viewer that indexes log messages by type
  and time to make it easier to navigate through files quickly.
  
  Key bindings:
    ?     View/leave the online help text.
    q     Quit the program.
  
  Options:
    -h         Print this message, then exit.
    -H         Display the internal help text.
    -I path    An additional configuration directory.
    -i         Install the given format files and exit.  Pass 'extra'
               to install the default set of third-party formats.
    -u         Update formats installed from git repositories.
    -C         Check configuration and then exit.
    -d path    Write debug messages to the given file.
    -V         Print version information.
  
    -a         Load all of the most recent log file types.
    -r         Recursively load files from the given directory hierarchies.
    -R         Load older rotated log files as well.
    -t         Prepend timestamps to the lines of data being read in
               on the standard input.
    -w file    Write the contents of the standard input to this file.
  
    -c cmd     Execute a command after the files have been loaded.
    -f path    Execute the commands in the given file.
    -n         Run without the curses UI. (headless mode)
    -N         Do not open the default syslog file if no files are given.
    -q         Do not print the log messages after executing all
               of the commands.
  
  Optional arguments:
    logfileN          The log files, directories, or remote paths to view.
                      If a directory is given, all of the files in the
                      directory will be loaded.
  
  Examples:
    To load and follow the syslog file:
      $ lnav
  
    To load all of the files in /var/log:
      $ lnav /var/log
  
    To watch the output of make with timestamps prepended:
      $ make 2>&1 | lnav -t
  
  Paths:
    Configuration, session, and format files are stored in:
      %s %s
  
    Local copies of remote files and files extracted from
    archives are stored in:
      %s %s
  
  Documentation: https://docs.lnav.org
  Contact:
    %s https://github.com/tstack/lnav/discussions
    %s %s
  Version: %s
  )";
  
      fprintf(stderr,
              usage_msg,
              lnav_data.ld_program_name,
              "\U0001F4C2",
              lnav::paths::dotlnav().c_str(),
              "\U0001F4C2",
              lnav::paths::workdir().c_str(),
              "\U0001F4AC",
              "\U0001F4EB",
              PACKAGE_BUGREPORT,
              VCS_PACKAGE_STRING);
  }
  
  static void clear_last_user_mark(listview_curses *lv)
  {
      textview_curses *tc = (textview_curses *) lv;
      if (lnav_data.ld_select_start.find(tc) != lnav_data.ld_select_start.end() &&
          !tc->is_line_visible(vis_line_t(lnav_data.ld_last_user_mark[tc]))) {
          lnav_data.ld_select_start.erase(tc);
          lnav_data.ld_last_user_mark.erase(tc);
      }
  }
  
  bool update_active_files(file_collection& new_files)
  {
      static loading_observer obs;
  
      if (lnav_data.ld_active_files.fc_invalidate_merge) {
          lnav_data.ld_active_files.fc_invalidate_merge = false;
  
          return true;
      }
  
      for (const auto& lf : new_files.fc_files) {
          lf->set_logfile_observer(&obs);
          lnav_data.ld_text_source.push_back(lf);
      }
      for (const auto& other_pair : new_files.fc_other_files) {
          switch (other_pair.second.ofd_format) {
              case file_format_t::FF_SQLITE_DB:
                  attach_sqlite_db(lnav_data.ld_db.in(), other_pair.first);
                  break;
              default:
                  break;
          }
      }
      lnav_data.ld_active_files.merge(new_files);
      if (!new_files.fc_files.empty() ||
          !new_files.fc_other_files.empty() ||
          !new_files.fc_name_to_errors.empty()) {
          lnav_data.ld_active_files.regenerate_unique_file_names();
      }
      lnav_data.ld_child_pollers.insert(
          lnav_data.ld_child_pollers.begin(),
          std::make_move_iterator(lnav_data.ld_active_files.fc_child_pollers.begin()),
          std::make_move_iterator(lnav_data.ld_active_files.fc_child_pollers.end()));
  
      return true;
  }
  
  bool rescan_files(bool req)
  {
      auto& mlooper = injector::get<main_looper&, services::main_t>();
      bool done = false;
      auto delay = 0ms;
  
      do {
          auto fc = lnav_data.ld_active_files.rescan_files(req);
          bool all_synced = true;
  
          update_active_files(fc);
          mlooper.get_port().process_for(delay);
          if (lnav_data.ld_flags & LNF_HEADLESS) {
              for (const auto& pair : lnav_data.ld_active_files.fc_other_files) {
                  if (pair.second.ofd_format != file_format_t::FF_REMOTE) {
                      continue;
                  }
  
                  if (lnav_data.ld_active_files.fc_synced_files
                          .count(pair.first) == 0) {
                      all_synced = false;
                  }
              }
              if (!all_synced) {
                  delay = 30ms;
              }
          }
          done = fc.fc_file_names.empty() && all_synced;
      } while (!done);
      return true;
  }
  
  class lnav_behavior : public mouse_behavior {
  public:
      void mouse_event(int button, bool release, int x, int y) override
      {
          textview_curses *tc = *(lnav_data.ld_view_stack.top());
          struct mouse_event me;
  
          switch (button & xterm_mouse::XT_BUTTON__MASK) {
          case xterm_mouse::XT_BUTTON1:
              me.me_button = mouse_button_t::BUTTON_LEFT;
              break;
          case xterm_mouse::XT_BUTTON2:
              me.me_button = mouse_button_t::BUTTON_MIDDLE;
              break;
          case xterm_mouse::XT_BUTTON3:
              me.me_button = mouse_button_t::BUTTON_RIGHT;
              break;
          case xterm_mouse::XT_SCROLL_UP:
              me.me_button = mouse_button_t::BUTTON_SCROLL_UP;
              break;
          case xterm_mouse::XT_SCROLL_DOWN:
              me.me_button = mouse_button_t::BUTTON_SCROLL_DOWN;
              break;
          }
  
          if (button & xterm_mouse::XT_DRAG_FLAG) {
              me.me_state = mouse_button_state_t::BUTTON_STATE_DRAGGED;
          }
          else if (release) {
              me.me_state = mouse_button_state_t::BUTTON_STATE_RELEASED;
          }
          else {
              me.me_state = mouse_button_state_t::BUTTON_STATE_PRESSED;
          }
  
          gettimeofday(&me.me_time, nullptr);
          me.me_x = x - 1;
          me.me_y = y - tc->get_y() - 1;
  
          tc->handle_mouse(me);
      };
  
  private:
  };
  
  static bool handle_config_ui_key(int ch)
  {
      bool retval = false;
  
      switch (lnav_data.ld_mode) {
          case LNM_FILES:
              retval = lnav_data.ld_files_view.handle_key(ch);
              break;
          case LNM_FILTER:
              retval = lnav_data.ld_filter_view.handle_key(ch);
              break;
          default:
              ensure(0);
      }
  
      if (retval) {
          return retval;
      }
  
      nonstd::optional<ln_mode_t> new_mode;
  
      lnav_data.ld_filter_help_status_source.fss_error.clear();
      if (ch == 'F') {
          new_mode = LNM_FILES;
      } else if (ch == 'T') {
          new_mode = LNM_FILTER;
      } else if (ch == '\t' || ch == KEY_BTAB) {
          if (lnav_data.ld_mode == LNM_FILES) {
              new_mode = LNM_FILTER;
          } else {
              new_mode = LNM_FILES;
          }
      } else if (ch == 'q') {
          new_mode = LNM_PAGING;
      }
  
      if (new_mode) {
          if (new_mode.value() == LNM_FILES ||
              new_mode.value() == LNM_FILTER) {
              lnav_data.ld_last_config_mode = new_mode.value();
          }
          lnav_data.ld_mode = new_mode.value();
          lnav_data.ld_files_view.reload_data();
          lnav_data.ld_filter_view.reload_data();
          lnav_data.ld_status[LNS_FILTER].set_needs_update();
      } else {
          return handle_paging_key(ch);
      }
  
      return true;
  }
  
  static bool handle_key(int ch) {
      lnav_data.ld_input_state.push_back(ch);
  
      switch (ch) {
          case CTRL('d'):
          case KEY_RESIZE:
              break;
          default: {
              switch (lnav_data.ld_mode) {
                  case LNM_PAGING:
                      return handle_paging_key(ch);
  
                  case LNM_FILTER:
                  case LNM_FILES:
                      return handle_config_ui_key(ch);
  
                  case LNM_COMMAND:
                  case LNM_SEARCH:
                  case LNM_SEARCH_FILTERS:
                  case LNM_SEARCH_FILES:
                  case LNM_CAPTURE:
                  case LNM_SQL:
                  case LNM_EXEC:
                  case LNM_USER:
                      handle_rl_key(ch);
                      break;
  
                  default:
                      require(0);
                      break;
              }
          }
      }
  
      return true;
  }
  
  static input_dispatcher::escape_match_t match_escape_seq(const char *keyseq)
  {
      if (lnav_data.ld_mode != LNM_PAGING) {
          return input_dispatcher::escape_match_t::NONE;
      }
  
      auto &km = lnav_config.lc_active_keymap;
      auto iter = km.km_seq_to_cmd.find(keyseq);
      if (iter != km.km_seq_to_cmd.end()) {
          return input_dispatcher::escape_match_t::FULL;
      }
  
      auto lb = km.km_seq_to_cmd.lower_bound(keyseq);
      if (lb == km.km_seq_to_cmd.end()) {
          return input_dispatcher::escape_match_t::NONE;
      }
  
      auto ub = km.km_seq_to_cmd.upper_bound(keyseq);
      auto longest = max_element(lb, ub, [] (auto l, auto r) {
          return l.first.size() < r.first.size();
      });
  
      if (strlen(keyseq) < longest->first.size()) {
          return input_dispatcher::escape_match_t::PARTIAL;
      }
  
      return input_dispatcher::escape_match_t::NONE;
  }
  
  void update_hits(textview_curses *tc)
  {
      if (isendwin()) {
          return;
      }
  
      auto top_tc = lnav_data.ld_view_stack.top();
  
      if (top_tc && tc == *top_tc) {
          lnav_data.ld_bottom_source.update_hits(tc);
  
          if (lnav_data.ld_mode == LNM_SEARCH) {
              const auto MAX_MATCH_COUNT = 10_vl;
              const auto PREVIEW_SIZE = MAX_MATCH_COUNT + 1_vl;
  
              int preview_count = 0;
  
              vis_bookmarks &bm = tc->get_bookmarks();
              const auto &bv = bm[&textview_curses::BM_SEARCH];
              auto vl = tc->get_top();
              unsigned long width;
              vis_line_t height;
              attr_line_t all_matches;
              char linebuf[64];
              int last_line = tc->get_inner_height();
              int max_line_width;
  
              snprintf(linebuf, sizeof(linebuf), "%d", last_line);
              max_line_width = strlen(linebuf);
  
              tc->get_dimensions(height, width);
              vl += height;
              if (vl > PREVIEW_SIZE) {
                  vl -= PREVIEW_SIZE;
              }
  
              auto prev_vl = bv.prev(tc->get_top());
  
              if (prev_vl != -1_vl) {
                  attr_line_t al;
  
                  tc->textview_value_for_row(prev_vl, al);
                  if (preview_count > 0) {
                      all_matches.append("\n");
                  }
                  snprintf(linebuf, sizeof(linebuf),
                           "L%*d: ",
                           max_line_width, (int) prev_vl);
                  all_matches
                      .append(linebuf)
                      .append(al);
                  preview_count += 1;
              }
  
              while ((vl = bv.next(vl)) != -1_vl &&
                     preview_count < MAX_MATCH_COUNT) {
                  attr_line_t al;
  
                  tc->textview_value_for_row(vl, al);
                  if (preview_count > 0) {
                      all_matches.append("\n");
                  }
                  snprintf(linebuf, sizeof(linebuf),
                           "L%*d: ",
                           max_line_width, (int) vl);
                  all_matches
                      .append(linebuf)
                      .append(al);
                  preview_count += 1;
              }
  
              if (preview_count > 0) {
                  lnav_data.ld_preview_status_source.get_description()
                           .set_value("Matching lines for search");
                  lnav_data.ld_preview_source
                           .replace_with(all_matches)
                           .set_text_format(text_format_t::TF_UNKNOWN);
                  lnav_data.ld_preview_view.set_needs_update();
              }
          }
      }
  }
  
  static void gather_pipers()
  {
      for (auto iter = lnav_data.ld_pipers.begin();
           iter != lnav_data.ld_pipers.end(); ) {
          pid_t child_pid = (*iter)->get_child_pid();
          if ((*iter)->has_exited()) {
              log_info("child piper has exited -- %d", child_pid);
              iter = lnav_data.ld_pipers.erase(iter);
          } else {
              ++iter;
          }
      }
  
      for (auto iter = lnav_data.ld_child_pollers.begin();
           iter != lnav_data.ld_child_pollers.end();) {
          if (iter->poll(lnav_data.ld_active_files) == child_poll_result_t::FINISHED) {
              iter = lnav_data.ld_child_pollers.erase(iter);
          } else {
              ++iter;
          }
      }
  }
  
  static void wait_for_pipers()
  {
      for (;;) {
          gather_pipers();
          if (lnav_data.ld_pipers.empty() && lnav_data.ld_child_pollers.empty()) {
              log_debug("all pipers finished");
              break;
          }
          else {
              usleep(10000);
              rebuild_indexes();
          }
          log_debug("%d pipers and %d children still active",
                  lnav_data.ld_pipers.size(),
                  lnav_data.ld_child_pollers.size());
      }
  }
  
  static void looper()
  {
      try {
          auto sql_cmd_map = injector::get<
              readline_context::command_map_t*, sql_cmd_map_tag>();
          auto& ec = lnav_data.ld_exec_context;
  
          readline_context command_context("cmd", &lnav_commands);
  
          readline_context search_context("search", nullptr, false);
          readline_context search_filters_context("search-filters", nullptr, false);
          readline_context search_files_context("search-files", nullptr, false);
          readline_context index_context("capture");
          readline_context sql_context("sql", sql_cmd_map, false);
          readline_context exec_context("exec");
          readline_context user_context("user");
          readline_curses  rlc;
          sig_atomic_t overlay_counter = 0;
          int lpc;
  
          command_context.set_highlighter(readline_command_highlighter);
          search_context
              .set_append_character(0)
              .set_highlighter(readline_regex_highlighter);
          search_filters_context
              .set_append_character(0)
              .set_highlighter(readline_regex_highlighter);
          search_files_context
              .set_append_character(0)
              .set_highlighter(readline_regex_highlighter);
          sql_context
              .set_highlighter(readline_sqlite_highlighter)
              .set_quote_chars("\"")
              .with_readline_var((char **)&rl_completer_word_break_characters,
                                 " \t\n(),");
          exec_context.set_highlighter(readline_shlex_highlighter);
  
          lnav_data.ld_log_source.lss_sorting_observer = [](auto& lss, auto off, auto size) {
              lnav_data.ld_bottom_source.update_loading(off, size);
              loading_observer::do_update(nullptr);
          };
  
          auto &sb = lnav_data.ld_scroll_broadcaster;
          auto &vsb = lnav_data.ld_view_stack_broadcaster;
  
          rlc.add_context(LNM_COMMAND, command_context);
          rlc.add_context(LNM_SEARCH, search_context);
          rlc.add_context(LNM_SEARCH_FILTERS, search_filters_context);
          rlc.add_context(LNM_SEARCH_FILES, search_files_context);
          rlc.add_context(LNM_CAPTURE, index_context);
          rlc.add_context(LNM_SQL, sql_context);
          rlc.add_context(LNM_EXEC, exec_context);
          rlc.add_context(LNM_USER, user_context);
          rlc.start();
  
          lnav_data.ld_filter_source.fss_editor.start();
  
          lnav_data.ld_rl_view = &rlc;
  
          lnav_data.ld_rl_view->add_possibility(
              LNM_COMMAND, "viewname", lnav_view_strings);
  
          lnav_data.ld_rl_view->add_possibility(
              LNM_COMMAND, "zoomlevel", lnav_zoom_strings);
  
          lnav_data.ld_rl_view->add_possibility(
              LNM_COMMAND, "levelname", level_names);
  
          (void)signal(SIGINT, sigint);
          (void)signal(SIGTERM, sigint);
          (void)signal(SIGWINCH, sigwinch);
          (void)signal(SIGCHLD, sigchld);
  
          screen_curses sc;
          lnav_behavior lb;
  
          auto_fd errpipe[2];
          auto_fd::pipe(errpipe);
  
          dup2(errpipe[1], STDERR_FILENO);
          errpipe[1].reset();
          log_pipe_err(errpipe[0]);
  
          ui_periodic_timer::singleton();
  
          auto mouse_i = injector::get<xterm_mouse&>();
  
          mouse_i.set_behavior(&lb);
          mouse_i.set_enabled(check_experimental("mouse"));
  
          lnav_data.ld_window = sc.get_window();
          keypad(stdscr, TRUE);
          (void)nonl();
          (void)cbreak();
          (void)noecho();
          (void)nodelay(lnav_data.ld_window, 1);
  
  #ifdef VDSUSP
          {
              struct termios tio;
  
              tcgetattr(STDIN_FILENO, &tio);
              tio.c_cc[VDSUSP] = 0;
              tcsetattr(STDIN_FILENO, TCSANOW, &tio);
          }
  #endif
  
          define_key("\033Od", KEY_BEG);
          define_key("\033Oc", KEY_END);
  
          view_colors &vc = view_colors::singleton();
          view_colors::init();
  
          {
              setup_highlights(lnav_data.ld_views[LNV_LOG].get_highlights());
              setup_highlights(lnav_data.ld_views[LNV_TEXT].get_highlights());
              setup_highlights(lnav_data.ld_views[LNV_SCHEMA].get_highlights());
              setup_highlights(lnav_data.ld_views[LNV_PRETTY].get_highlights());
              setup_highlights(lnav_data.ld_preview_view.get_highlights());
  
              for (const auto& format : log_format::get_root_formats()) {
                  for (auto &hl : format->lf_highlighters) {
                      if (hl.h_fg.empty()) {
                          hl.with_attrs(hl.h_attrs | vc.attrs_for_ident(hl.h_pattern));
                      }
  
                      lnav_data.ld_views[LNV_LOG].get_highlights()[{
                          highlight_source_t::CONFIGURATION,
                          format->get_name().to_string() + "-" + hl.h_pattern
                      }] = hl;
                  }
              }
          }
  
          execute_examples();
  
          rlc.set_window(lnav_data.ld_window);
          rlc.set_y(-1);
          rlc.set_focus_action(rl_focus);
          rlc.set_change_action(rl_change);
          rlc.set_perform_action(rl_callback);
          rlc.set_alt_perform_action(rl_alt_callback);
          rlc.set_timeout_action(rl_search);
          rlc.set_abort_action(lnav_rl_abort);
          rlc.set_display_match_action(rl_display_matches);
          rlc.set_display_next_action(rl_display_next);
          rlc.set_blur_action(rl_blur);
          rlc.set_completion_request_action(rl_completion_request);
          rlc.set_alt_value(HELP_MSG_2(
              e, E, "to move forward/backward through error messages"));
  
          (void)curs_set(0);
  
          lnav_data.ld_view_stack.push_back(&lnav_data.ld_views[LNV_LOG]);
  
          sb.push_back(clear_last_user_mark);
          sb.push_back(bind_mem(&top_status_source::update_filename, &lnav_data.ld_top_source));
          vsb.push_back(bind_mem(&top_status_source::update_view_name, &lnav_data.ld_top_source));
          sb.push_back(bind_mem(&bottom_status_source::update_line_number, &lnav_data.ld_bottom_source));
          sb.push_back(bind_mem(&bottom_status_source::update_percent, &lnav_data.ld_bottom_source));
          sb.push_back(bind_mem(&bottom_status_source::update_marks, &lnav_data.ld_bottom_source));
          sb.push_back(bind_mem(&term_extra::update_title, injector::get<term_extra*>()));
          vsb.push_back([](listview_curses *lv) {
              auto tc = static_cast<textview_curses *>(lv);
  
              tc->tc_state_event_handler(*tc);
          });
  
          vsb.push_back(sb);
  
          for (lpc = 0; lpc < LNV__MAX; lpc++) {
              lnav_data.ld_views[lpc].set_window(lnav_data.ld_window);
              lnav_data.ld_views[lpc].set_y(1);
              lnav_data.ld_views[lpc].
              set_height(vis_line_t(-(rlc.get_height() + 1)));
              lnav_data.ld_views[lpc].set_scroll_action(sb);
              lnav_data.ld_views[lpc].set_search_action(update_hits);
              lnav_data.ld_views[lpc].tc_state_event_handler = [](auto &&tc) {
                  auto top_view = lnav_data.ld_view_stack.top();
  
                  if (top_view && *top_view == &tc) {
                      lnav_data.ld_bottom_source.update_search_term(tc);
                  }
              };
          }
  
          lnav_data.ld_doc_view.set_window(lnav_data.ld_window);
          lnav_data.ld_doc_view.set_show_scrollbar(false);
  
          lnav_data.ld_example_view.set_window(lnav_data.ld_window);
          lnav_data.ld_example_view.set_show_scrollbar(false);
  
          lnav_data.ld_match_view.set_window(lnav_data.ld_window);
  
          lnav_data.ld_preview_view.set_window(lnav_data.ld_window);
          lnav_data.ld_preview_view.set_show_scrollbar(false);
  
          lnav_data.ld_filter_view.set_selectable(true);
          lnav_data.ld_filter_view.set_window(lnav_data.ld_window);
          lnav_data.ld_filter_view.set_show_scrollbar(true);
  
          lnav_data.ld_files_view.set_selectable(true);
          lnav_data.ld_files_view.set_window(lnav_data.ld_window);
          lnav_data.ld_files_view.set_show_scrollbar(true);
          lnav_data.ld_files_view.get_disabled_highlights()
              .insert(highlight_source_t::THEME);
          lnav_data.ld_files_view.set_overlay_source(&lnav_data.ld_files_overlay);
  
          lnav_data.ld_status[LNS_TOP].set_top(0);
          lnav_data.ld_status[LNS_BOTTOM].set_top(-(rlc.get_height() + 1));
          for (auto &sc : lnav_data.ld_status) {
              sc.set_window(lnav_data.ld_window);
          }
          lnav_data.ld_status[LNS_TOP].set_data_source(
              &lnav_data.ld_top_source);
          lnav_data.ld_status[LNS_BOTTOM].set_data_source(
              &lnav_data.ld_bottom_source);
          lnav_data.ld_status[LNS_FILTER].set_data_source(
              &lnav_data.ld_filter_status_source);
          lnav_data.ld_status[LNS_FILTER_HELP].set_data_source(
              &lnav_data.ld_filter_help_status_source);
          lnav_data.ld_status[LNS_DOC].set_data_source(
              &lnav_data.ld_doc_status_source);
          lnav_data.ld_status[LNS_PREVIEW].set_data_source(
              &lnav_data.ld_preview_status_source);
  
          lnav_data.ld_match_view.set_show_bottom_border(true);
  
          for (auto &sc : lnav_data.ld_status) {
              sc.window_change();
          }
  
          auto session_path = lnav::paths::dotlnav() / "session";
          execute_file(ec, session_path.string());
  
          sb(*lnav_data.ld_view_stack.top());
          vsb(*lnav_data.ld_view_stack.top());
  
          lnav_data.ld_view_stack.vs_change_handler = [](textview_curses *tc) {
              lnav_data.ld_view_stack_broadcaster(tc);
          };
  
          {
              input_dispatcher &id = lnav_data.ld_input_dispatcher;
  
              id.id_escape_matcher = match_escape_seq;
              id.id_escape_handler = handle_keyseq;
              id.id_key_handler = handle_key;
              id.id_mouse_handler = bind(&xterm_mouse::handle_mouse, &mouse_i);
              id.id_unhandled_handler = [](const char *keyseq) {
                  auto enc_len = lnav_config.lc_ui_keymap.size() * 2;
                  auto encoded_name = (char *) alloca(enc_len);
  
                  log_info("unbound keyseq: %s", keyseq);
                  json_ptr::encode(encoded_name, enc_len,
                                   lnav_config.lc_ui_keymap.c_str());
                  // XXX we should have a hotkey for opening a prompt that is
                  // pre-filled with a suggestion that the user can complete.
                  // This quick-fix key could be used for other stuff as well
                  lnav_data.ld_rl_view->set_value(fmt::format(
                      ANSI_CSI ANSI_COLOR_PARAM(COLOR_YELLOW)
                      ";" ANSI_BOLD_PARAM ANSI_CHAR_ATTR
                      "Unrecognized key"
                      ANSI_NORM
                      ", bind to a command using \u2014 "
                      ANSI_BOLD(":config")
                      " /ui/keymap-defs/{}/{}/command <cmd>",
                      encoded_name, keyseq));
                  alerter::singleton().chime();
              };
          }
  
          ui_periodic_timer &timer = ui_periodic_timer::singleton();
          struct timeval current_time;
  
          static sig_atomic_t index_counter;
  
          lnav_data.ld_mode = LNM_FILES;
  
          timer.start_fade(index_counter, 1);
  
          file_collection active_copy;
          log_debug("rescan started %p", &active_copy);
          active_copy.merge(lnav_data.ld_active_files);
          active_copy.fc_progress = lnav_data.ld_active_files.fc_progress;
          future<file_collection> rescan_future =
              std::async(std::launch::async,
                         &file_collection::rescan_files,
                         std::move(active_copy),
                         false);
          bool initial_rescan_completed = false;
          int session_stage = 0;
  
          // rlc.do_update();
  
          auto next_rebuild_time = ui_clock::now();
          auto next_status_update_time = next_rebuild_time;
          auto next_rescan_time = next_rebuild_time;
  
          while (lnav_data.ld_looping) {
              auto loop_deadline = ui_clock::now() +
                  (session_stage == 0 ? 3s : 50ms);
  
              vector<struct pollfd> pollfds;
              size_t starting_view_stack_size = lnav_data.ld_view_stack.size();
              size_t changes = 0;
              int rc;
  
              gettimeofday(&current_time, nullptr);
  
              lnav_data.ld_top_source.update_time(current_time);
              lnav_data.ld_preview_view.set_needs_update();
  
              layout_views();
  
              auto scan_timeout = initial_rescan_completed ? 0s : 10ms;
              if (rescan_future.valid() &&
                  rescan_future.wait_for(scan_timeout) ==
                  std::future_status::ready) {
                  auto new_files = rescan_future.get();
                  if (!initial_rescan_completed &&
                      new_files.fc_file_names.empty() &&
                      new_files.fc_files.empty() &&
                      lnav_data.ld_active_files.fc_progress->readAccess()->
                      sp_tailers.empty()) {
                      initial_rescan_completed = true;
  
                      log_debug("initial rescan rebuild");
                      changes += rebuild_indexes(loop_deadline);
                      load_session();
                      if (session_data.sd_save_time) {
                          std::string ago;
  
                          ago = humanize::time::point::from_tv({
                              (time_t) session_data.sd_save_time, 0})
                              .as_time_ago();
                          lnav_data.ld_rl_view->set_value(
                              ("restored session from " ANSI_BOLD_START) +
                              ago +
                              (ANSI_NORM "; press Ctrl-R to reset session"));
                      }
  
                      lnav_data.ld_session_loaded = true;
                      session_stage += 1;
                      loop_deadline = ui_clock::now();
                      log_debug("file count %d",
                                lnav_data.ld_active_files.fc_files.size())
                  }
                  update_active_files(new_files);
                  if (!initial_rescan_completed) {
                      auto &fview = lnav_data.ld_files_view;
                      auto height = fview.get_inner_height();
  
                      if (height > 0_vl) {
                          fview.set_selection(height - 1_vl);
                      }
                  }
  
                  active_copy.clear();
                  active_copy.merge(lnav_data.ld_active_files);
                  rescan_future = std::future<file_collection>{};
                  next_rescan_time = ui_clock::now() + 333ms;
              }
  
              if (!rescan_future.valid() &&
                  (session_stage < 2 || ui_clock::now() >= next_rescan_time)) {
                  rescan_future = std::async(std::launch::async,
                                             &file_collection::rescan_files,
                                             std::move(active_copy),
                                             false);
              }
  
              {
                  auto& mlooper = injector::get<main_looper&, services::main_t>();
  
                  mlooper.get_port().process_for(0s);
              }
  
              auto ui_now = ui_clock::now();
              if (initial_rescan_completed) {
                  if (ui_now >= next_rebuild_time) {
                      changes += rebuild_indexes(loop_deadline);
                      if (!changes && ui_clock::now() < loop_deadline) {
                          next_rebuild_time = ui_clock::now() + 333ms;
                      }
                  }
              } else {
                  lnav_data.ld_files_view.set_overlay_needs_update();
              }
  
              lnav_data.ld_view_stack.do_update();
              lnav_data.ld_doc_view.do_update();
              lnav_data.ld_example_view.do_update();
              lnav_data.ld_match_view.do_update();
              lnav_data.ld_preview_view.do_update();
              if (ui_clock::now() >= next_status_update_time) {
                  for (auto &sc : lnav_data.ld_status) {
                      sc.do_update();
                  }
                  next_status_update_time = ui_clock::now() + 100ms;
              }
              if (lnav_data.ld_filter_source.fss_editing) {
                  lnav_data.ld_filter_source.fss_match_view.set_needs_update();
              }
              switch (lnav_data.ld_mode) {
                  case LNM_FILTER:
                  case LNM_SEARCH_FILTERS:
                      lnav_data.ld_filter_view.set_needs_update();
                      lnav_data.ld_filter_view.do_update();
                      break;
                  case LNM_SEARCH_FILES:
                  case LNM_FILES:
                      lnav_data.ld_files_view.set_needs_update();
                      lnav_data.ld_files_view.do_update();
                      break;
                  default:
                      break;
              }
              if (lnav_data.ld_mode != LNM_FILTER &&
                  lnav_data.ld_mode != LNM_FILES) {
                  rlc.do_update();
              }
              refresh();
  
              if (lnav_data.ld_session_loaded) {
                  // Only take input from the user after everything has loaded.
                  pollfds.push_back((struct pollfd) {
                      STDIN_FILENO,
                      POLLIN,
                      0
                  });
                  if (initial_build) {
                      switch (lnav_data.ld_mode) {
                          case LNM_COMMAND:
                          case LNM_SEARCH:
                          case LNM_SEARCH_FILTERS:
                          case LNM_SEARCH_FILES:
                          case LNM_SQL:
                          case LNM_EXEC:
                          case LNM_USER:
                              if (rlc.consume_ready_for_input()) {
                                  // log_debug("waiting for readline input")
                                  view_curses::awaiting_user_input();
                              }
                              break;
                          default:
                              // log_debug("waiting for paging input");
                              view_curses::awaiting_user_input();
                              break;
                      }
                  }
              }
              rlc.update_poll_set(pollfds);
              lnav_data.ld_filter_source.fss_editor.update_poll_set(pollfds);
  
              for (auto &tc : lnav_data.ld_views) {
                  tc.update_poll_set(pollfds);
              }
              lnav_data.ld_filter_view.update_poll_set(pollfds);
              lnav_data.ld_files_view.update_poll_set(pollfds);
  
              ui_now = ui_clock::now();
              auto poll_to =
                  (!changes && ui_now < loop_deadline && session_stage >= 1) ?
                  std::chrono::duration_cast<std::chrono::milliseconds>(loop_deadline - ui_now) :
                  0ms;
  
              if (initial_rescan_completed &&
                  lnav_data.ld_input_dispatcher.in_escape() &&
                  poll_to > 15ms) {
                  poll_to = 15ms;
              }
              // log_debug("poll %d %d", changes, poll_to.count());
              rc = poll(&pollfds[0], pollfds.size(), poll_to.count());
  
              gettimeofday(&current_time, nullptr);
              lnav_data.ld_input_dispatcher.poll(current_time);
  
              if (rc < 0) {
                  switch (errno) {
                  case 0:
                  case EINTR:
                      break;
  
                  default:
                      log_error("select %s", strerror(errno));
                      lnav_data.ld_looping = false;
                      break;
                  }
              }
              else {
                  auto in_revents = pollfd_revents(pollfds, STDIN_FILENO);
  
                  if (in_revents & (POLLHUP|POLLNVAL)) {
                      log_info("stdin has been closed, exiting...");
                      lnav_data.ld_looping = false;
                  } else if (in_revents & POLLIN) {
                      int ch;
  
                      while ((ch = getch()) != ERR) {
                          alerter::singleton().new_input(ch);
  
                          lnav_data.ld_input_dispatcher.new_input(current_time, ch);
  
                          lnav_data.ld_view_stack.top() | [ch] (auto tc) {
                              lnav_data.ld_key_repeat_history.update(ch, tc->get_top());
                          };
  
                          if (!lnav_data.ld_looping) {
                              // No reason to keep processing input after the
                              // user has quit.  The view stack will also be
                              // empty, which will cause issues.
                              break;
                          }
                      }
  
                      next_status_update_time = ui_clock::now();
                      switch (lnav_data.ld_mode) {
                          case LNM_PAGING:
                          case LNM_FILTER:
                          case LNM_FILES:
                              next_rescan_time = next_status_update_time + 1s;
                              break;
                          case LNM_COMMAND:
                          case LNM_SEARCH:
                          case LNM_SEARCH_FILTERS:
                          case LNM_SEARCH_FILES:
                          case LNM_CAPTURE:
                          case LNM_SQL:
                          case LNM_EXEC:
                          case LNM_USER:
                              next_rescan_time = next_status_update_time + 1min;
                              break;
                      }
                      next_rebuild_time = next_rescan_time;
                  }
  
                  for (auto &tc : lnav_data.ld_views) {
                      tc.check_poll_set(pollfds);
                  }
  
                  lnav_data.ld_view_stack.top() | [] (auto tc) {
                      lnav_data.ld_bottom_source.update_hits(tc);
                  };
  
                  auto old_mode = lnav_data.ld_mode;
                  rlc.check_poll_set(pollfds);
                  lnav_data.ld_filter_source.fss_editor.check_poll_set(pollfds);
                  lnav_data.ld_filter_view.check_poll_set(pollfds);
                  lnav_data.ld_files_view.check_poll_set(pollfds);
  
                  if (lnav_data.ld_mode != old_mode) {
                      switch (lnav_data.ld_mode) {
                          case LNM_PAGING:
                          case LNM_FILTER:
                          case LNM_FILES:
                              next_rescan_time = next_status_update_time + 1s;
                              next_rebuild_time = next_rescan_time;
                              break;
                          default:
                              break;
                      }
                  }
              }
  
              if (timer.time_to_update(overlay_counter)) {
                  lnav_data.ld_view_stack.top() | [] (auto tc) {
                      tc->set_overlay_needs_update();
                  };
              }
  
              if (initial_rescan_completed && session_stage < 2 &&
                  (!initial_build || timer.fade_diff(index_counter) == 0)) {
                  if (lnav_data.ld_mode == LNM_PAGING) {
                      timer.start_fade(index_counter, 1);
                  }
                  else {
                      timer.start_fade(index_counter, 3);
                  }
                  // log_debug("initial build rebuild");
                  changes += rebuild_indexes(loop_deadline);
                  if (!initial_build &&
                      lnav_data.ld_log_source.text_line_count() == 0 &&
                      lnav_data.ld_text_source.text_line_count() > 0) {
                      ensure_view(&lnav_data.ld_views[LNV_TEXT]);
                      lnav_data.ld_views[LNV_TEXT].set_top(0_vl);
                      lnav_data.ld_rl_view->set_alt_value(
                              HELP_MSG_2(f, F,
                                      "to switch to the next/previous file"));
                  }
                  if (lnav_data.ld_view_stack.top().value_or(nullptr) ==
                      &lnav_data.ld_views[LNV_TEXT] &&
                      lnav_data.ld_text_source.empty() &&
                      lnav_data.ld_log_source.text_line_count() > 0) {
                      textview_curses *tc_log = &lnav_data.ld_views[LNV_LOG];
                      lnav_data.ld_view_stack.pop_back();
  
                      lnav_data.ld_views[LNV_LOG].set_top(tc_log->get_top_for_last_row());
                  }
                  if (!initial_build &&
                      lnav_data.ld_log_source.text_line_count() == 0 &&
                      !lnav_data.ld_active_files.fc_other_files.empty() &&
                      std::any_of(lnav_data.ld_active_files.fc_other_files.begin(),
                                  lnav_data.ld_active_files.fc_other_files.end(),
                                  [](const auto& pair) {
                                      return pair.second.ofd_format ==
                                             file_format_t::FF_SQLITE_DB;
                                  })) {
                      ensure_view(&lnav_data.ld_views[LNV_SCHEMA]);
                  }
  
                  if (!initial_build && lnav_data.ld_flags & LNF_HELP) {
                      toggle_view(&lnav_data.ld_views[LNV_HELP]);
                      initial_build = true;
                  }
                  if (!initial_build && lnav_data.ld_flags & LNF_NO_DEFAULT) {
                      initial_build = true;
                  }
                  if (lnav_data.ld_log_source.text_line_count() > 0 ||
                      lnav_data.ld_text_source.text_line_count() > 0 ||
                      !lnav_data.ld_active_files.fc_other_files.empty()) {
                      initial_build = true;
                  }
  
                  if (initial_build) {
                      static bool ran_cleanup = false;
                      vector<pair<Result<string, string>, string>> cmd_results;
  
                      execute_init_commands(ec, cmd_results);
  
                      if (!cmd_results.empty()) {
                          auto last_cmd_result = cmd_results.back();
  
                          lnav_data.ld_rl_view->set_value(
                              last_cmd_result.first.orElse(err_to_ok).unwrap());
                          lnav_data.ld_rl_view->set_alt_value(
                              last_cmd_result.second);
                      }
  
                      if (!ran_cleanup) {
                          archive_manager::cleanup_cache();
                          tailer::cleanup_cache();
                          ran_cleanup = true;
                      }
                  }
  
                  if (session_stage == 1 &&
                      (lnav_data.ld_log_source.text_line_count() > 0 ||
                       lnav_data.ld_text_source.text_line_count() > 0 ||
                       !lnav_data.ld_active_files.fc_other_files.empty())) {
                      for (size_t view_index = 0;
                           view_index < LNV__MAX;
                           view_index++) {
                          const auto &vs = session_data.sd_view_states[view_index];
  
                          if (vs.vs_top > 0) {
                              lnav_data.ld_views[view_index]
                                  .set_top(vis_line_t(vs.vs_top));
                          }
                      }
                      if (lnav_data.ld_mode == LNM_FILES) {
                          if (lnav_data.ld_active_files.fc_name_to_errors.empty()) {
                              log_debug("switching to paging!");
                              lnav_data.ld_mode = LNM_PAGING;
                          } else {
                              lnav_data.ld_files_view.set_selection(0_vl);
                          }
                      }
                      session_stage += 1;
                      load_time_bookmarks();
                  }
              }
  
              if (lnav_data.ld_winched) {
                  struct winsize size;
  
                  lnav_data.ld_winched = false;
  
                  if (ioctl(fileno(stdout), TIOCGWINSZ, &size) == 0) {
                      resizeterm(size.ws_row, size.ws_col);
                  }
                  rlc.do_update();
                  rlc.window_change();
                  lnav_data.ld_filter_source.fss_editor.window_change();
                  for (auto &sc : lnav_data.ld_status) {
                      sc.window_change();
                  }
                  lnav_data.ld_view_stack.set_needs_update();
                  lnav_data.ld_doc_view.set_needs_update();
                  lnav_data.ld_example_view.set_needs_update();
                  lnav_data.ld_match_view.set_needs_update();
                  lnav_data.ld_filter_view.set_needs_update();
                  lnav_data.ld_files_view.set_needs_update();
              }
  
              if (lnav_data.ld_child_terminated) {
                  lnav_data.ld_child_terminated = false;
  
                  log_info("checking for terminated child processes");
                  for (auto iter = lnav_data.ld_children.begin();
                      iter != lnav_data.ld_children.end();
                      ++iter) {
                      int rc, child_stat;
  
                      rc = waitpid(*iter, &child_stat, WNOHANG);
                      if (rc == -1 || rc == 0)
                          continue;
  
                      iter = lnav_data.ld_children.erase(iter);
                  }
  
                  gather_pipers();
              }
  
              if (lnav_data.ld_meta_search) {
                  lnav_data.ld_meta_search->start();
              }
  
              if (lnav_data.ld_view_stack.empty() ||
                  (lnav_data.ld_view_stack.size() == 1 &&
                   starting_view_stack_size == 2 &&
                   lnav_data.ld_active_files.fc_file_names.size() ==
                   lnav_data.ld_text_source.size())) {
                  lnav_data.ld_looping = false;
              }
          }
      }
      catch (readline_curses::error & e) {
          log_error("error: %s", strerror(e.e_err));
      }
  }
  
  void wait_for_children()
  {
      vector<struct pollfd> pollfds;
      struct timeval to = { 0, 333000 };
  
      if (lnav_data.ld_meta_search) {
          lnav_data.ld_meta_search->start();
      }
  
      do {
          pollfds.clear();
  
          for (auto &tc : lnav_data.ld_views) {
              tc.update_poll_set(pollfds);
          }
          lnav_data.ld_filter_view.update_poll_set(pollfds);
          lnav_data.ld_files_view.update_poll_set(pollfds);
  
          if (pollfds.empty()) {
              return;
          }
  
          int rc = poll(&pollfds[0], pollfds.size(), to.tv_usec / 1000);
  
          if (rc < 0) {
              switch (errno) {
                  case 0:
                  case EINTR:
                      break;
                  default:
                      return;
              }
          }
  
          for (auto &tc : lnav_data.ld_views) {
              tc.check_poll_set(pollfds);
  
              lnav_data.ld_view_stack.top() | [] (auto tc) {
                  lnav_data.ld_bottom_source.update_hits(tc);
              };
          }
          lnav_data.ld_filter_view.check_poll_set(pollfds);
          lnav_data.ld_files_view.check_poll_set(pollfds);
      } while (true);
  }
  
  textview_curses *get_textview_for_mode(ln_mode_t mode)
  {
      switch (mode) {
          case LNM_SEARCH_FILTERS:
          case LNM_FILTER:
              return &lnav_data.ld_filter_view;
          case LNM_SEARCH_FILES:
          case LNM_FILES:
              return &lnav_data.ld_files_view;
          default:
              return *lnav_data.ld_view_stack.top();
      }
  }
  
  static void print_errors(vector<string> error_list)
  {
      for (auto &iter : error_list) {
          fprintf(stderr, "%s%s", iter.c_str(),
                  iter[iter.size() - 1] == '\n' ? "" : "\n");
      }
  }
  
  int main(int argc, char *argv[])
  {
      std::vector<std::string> config_errors, loader_errors;
      exec_context &ec = lnav_data.ld_exec_context;
      int lpc, c, retval = EXIT_SUCCESS;
  
      shared_ptr<piper_proc> stdin_reader;
      const char *stdin_out = nullptr;
      int stdin_out_fd = -1;
      bool exec_stdin = false, load_stdin = false;
      const char *LANG = getenv("LANG");
      ghc::filesystem::path stdin_tmp_path;
  
      if (LANG == nullptr || strcmp(LANG, "C") == 0) {
          setenv("LANG", "en_US.utf-8", 1);
      }
  
      (void)signal(SIGPIPE, SIG_IGN);
      setlocale(LC_ALL, "");
      umask(027);
  
      /* Disable Lnav from being able to execute external commands if
       * "LNAVSECURE" environment variable is set by the user.
       */
      if (getenv("LNAVSECURE") != nullptr) {
          lnav_data.ld_flags |= LNF_SECURE_MODE;
      }
  
      lnav_data.ld_exec_context.ec_sql_callback = sql_callback;
      lnav_data.ld_exec_context.ec_pipe_callback = pipe_callback;
  
      lnav_data.ld_program_name = argv[0];
      add_ansi_vars(ec.ec_global_vars);
  
      rl_readline_name = "lnav";
      lnav_data.ld_db_key_names = DEFAULT_DB_KEY_NAMES;
  
      stable_sort(lnav_data.ld_db_key_names.begin(),
                  lnav_data.ld_db_key_names.end());
  
      ensure_dotlnav();
  
      log_install_handlers();
      sql_install_logger();
  
  #ifdef HAVE_LIBCURL
      curl_global_init(CURL_GLOBAL_DEFAULT);
  #endif
  
      lnav_data.ld_debug_log_name = "/dev/null";
      lnav_data.ld_config_paths.emplace_back("/etc/lnav");
      lnav_data.ld_config_paths.emplace_back(SYSCONFDIR "/lnav");
      lnav_data.ld_config_paths.emplace_back(lnav::paths::dotlnav());
      while ((c = getopt(argc, argv, "hHarRCc:I:iuf:d:nNqtw:vVW")) != -1) {
          switch (c) {
          case 'h':
              usage();
              exit(retval);
              break;
  
          case 'H':
              lnav_data.ld_flags |= LNF_HELP;
              break;
  
          case 'C':
              lnav_data.ld_flags |= LNF_CHECK_CONFIG;
              break;
  
          case 'c':
              switch (optarg[0]) {
              case ':':
              case '/':
              case ';':
                  break;
              case '|':
                  if (strcmp("|-", optarg) == 0 ||
                      strcmp("|/dev/stdin", optarg) == 0) {
                      exec_stdin = true;
                  }
                  break;
              default:
                  fprintf(stderr, "error: command arguments should start with a "
                      "colon, semi-colon, or pipe-symbol to denote:\n");
                  fprintf(stderr, "error: a built-in command, SQL query, "
                      "or a file path that contains commands to execute\n");
                  usage();
                  exit(EXIT_FAILURE);
                  break;
              }
              lnav_data.ld_commands.emplace_back(optarg);
              break;
  
          case 'f':
              // XXX Not the best way to check for stdin.
              if (strcmp("-", optarg) == 0 ||
                  strcmp("/dev/stdin", optarg) == 0) {
                  exec_stdin = true;
              }
              lnav_data.ld_commands.push_back("|" + string(optarg));
              break;
  
          case 'I':
              if (access(optarg, X_OK) != 0) {
                  perror("invalid config path");
                  exit(EXIT_FAILURE);
              }
              lnav_data.ld_config_paths.emplace_back(optarg);
              break;
  
          case 'i':
              lnav_data.ld_flags |= LNF_INSTALL;
              break;
  
          case 'u':
              lnav_data.ld_flags |= LNF_UPDATE_FORMATS;
              break;
  
          case 'd':
              lnav_data.ld_debug_log_name = optarg;
              lnav_log_level = lnav_log_level_t::TRACE;
              break;
  
          case 'a':
              lnav_data.ld_flags |= LNF__ALL;
              break;
  
          case 'n':
              lnav_data.ld_flags |= LNF_HEADLESS;
              break;
  
          case 'N':
              lnav_data.ld_flags |= LNF_NO_DEFAULT;
              break;
  
          case 'q':
              lnav_data.ld_flags |= LNF_QUIET;
              break;
  
          case 'R':
              lnav_data.ld_active_files.fc_rotated = true;
              break;
  
          case 'r':
              lnav_data.ld_active_files.fc_recursive = true;
              break;
  
          case 't':
              lnav_data.ld_flags |= LNF_TIMESTAMP;
              break;
  
          case 'w':
              stdin_out = optarg;
              break;
  
          case 'W':
          {
              char b;
              if (isatty(STDIN_FILENO) && read(STDIN_FILENO, &b, 1) == -1) {
                  perror("Read key from STDIN");
              }
          }
              break;
  
          case 'v':
              lnav_data.ld_flags |= LNF_VERBOSE;
              break;
  
          case 'V':
              printf("%s\n", VCS_PACKAGE_STRING);
              exit(0);
              break;
  
          default:
              retval = EXIT_FAILURE;
              break;
          }
      }
  
      argc -= optind;
      argv += optind;
  
      lnav_log_file = make_optional_from_nullable(fopen(lnav_data.ld_debug_log_name, "a"));
      log_info("lnav started");
  
      {
          static auto builtin_formats =
              injector::get<std::vector<std::shared_ptr<log_format>>>();
          auto& root_formats = log_format::get_root_formats();
  
          log_format::get_root_formats().insert(
              root_formats.begin(), builtin_formats.begin(), builtin_formats.end());
          builtin_formats.clear();
      }
  
      load_config(lnav_data.ld_config_paths, config_errors);
      if (!config_errors.empty()) {
          print_errors(config_errors);
          return EXIT_FAILURE;
      }
      add_global_vars(ec);
  
      if (lnav_data.ld_flags & LNF_UPDATE_FORMATS) {
          if (!update_installs_from_git()) {
              return EXIT_FAILURE;
          }
          return EXIT_SUCCESS;
      }
  
      if (lnav_data.ld_flags & LNF_INSTALL) {
          auto formats_installed_path = lnav::paths::dotlnav() / "formats/installed";
          auto configs_installed_path = lnav::paths::dotlnav() / "configs/installed";
  
          if (argc == 0) {
              fprintf(stderr, "error: expecting file format paths\n");
              return EXIT_FAILURE;
          }
  
          for (lpc = 0; lpc < argc; lpc++) {
              if (endswith(argv[lpc], ".git")) {
                  if (!install_from_git(argv[lpc])) {
                      return EXIT_FAILURE;
                  }
                  continue;
              }
  
              if (strcmp(argv[lpc], "extra") == 0) {
                  install_extra_formats();
                  continue;
              }
  
              auto file_type_result = detect_config_file_type(argv[lpc]);
              if (file_type_result.isErr()) {
                  fprintf(stderr, "error: %s\n", file_type_result.unwrapErr().c_str());
                  return EXIT_FAILURE;
              }
              auto file_type = file_type_result.unwrap();
  
              string dst_name;
              if (file_type == config_file_type::CONFIG) {
                  dst_name = basename(argv[lpc]);
              } else {
                  vector<intern_string_t> format_list = load_format_file(
                      argv[lpc], loader_errors);
  
                  if (!loader_errors.empty()) {
                      print_errors(loader_errors);
                      return EXIT_FAILURE;
                  }
                  if (format_list.empty()) {
                      fprintf(stderr, "error: format file is empty: %s\n",
                              argv[lpc]);
                      return EXIT_FAILURE;
                  }
  
                  dst_name = format_list[0].to_string() + ".json";
              }
              auto dst_path = (file_type == config_file_type::CONFIG ?
                               configs_installed_path :
                               formats_installed_path) /
                              dst_name;
              auto_fd in_fd, out_fd;
  
              if ((in_fd = open(argv[lpc], O_RDONLY)) == -1) {
                  perror("unable to open file to install");
              }
              else if ((out_fd = openp(dst_path,
                                       O_WRONLY | O_CREAT | O_TRUNC,
                                       0644)) == -1) {
                  fprintf(stderr, "error: unable to open destination: %s -- %s\n",
                          dst_path.c_str(), strerror(errno));
              }
              else {
                  char buffer[2048];
                  ssize_t rc;
  
                  while ((rc = read(in_fd, buffer, sizeof(buffer))) > 0) {
                      ssize_t remaining = rc, written;
  
                      while (remaining > 0) {
                          written = write(out_fd, buffer, rc);
                          if (written == -1) {
                              fprintf(stderr,
                                      "error: unable to install file -- %s\n",
                                      strerror(errno));
                              exit(EXIT_FAILURE);
                          }
  
                          remaining -= written;
                      }
                  }
  
                  fprintf(stderr, "info: installed: %s\n", dst_path.c_str());
              }
          }
          return EXIT_SUCCESS;
      }
  
      if (sqlite3_open(":memory:", lnav_data.ld_db.out()) != SQLITE_OK) {
          fprintf(stderr, "error: unable to create sqlite memory database\n");
          exit(EXIT_FAILURE);
      }
  
      if (lnav_data.ld_flags & LNF_SECURE_MODE) {
          if ((sqlite3_set_authorizer(lnav_data.ld_db.in(),
                                      sqlite_authorizer, nullptr)) != SQLITE_OK) {
              fprintf(stderr, "error: unable to attach sqlite authorizer\n");
              exit(EXIT_FAILURE);
          }
      }
  
      /* If we statically linked against an ncurses library that had a non-
       * standard path to the terminfo database, we need to set this variable
       * so that it will try the default path.
       */
      setenv("TERMINFO_DIRS",
             "/usr/share/terminfo:/lib/terminfo:/usr/share/lib/terminfo",
             0);
  
      {
          int register_collation_functions(sqlite3 * db);
  
          register_sqlite_funcs(lnav_data.ld_db.in(), sqlite_registration_funcs);
          register_collation_functions(lnav_data.ld_db.in());
      }
  
      register_environ_vtab(lnav_data.ld_db.in());
      {
          static auto vtab_modules =
              injector::get<std::vector<std::shared_ptr<vtab_module_base>>>();
  
          for (const auto& mod : vtab_modules) {
              mod->create(lnav_data.ld_db.in());
          }
      }
  
      register_views_vtab(lnav_data.ld_db.in());
      register_regexp_vtab(lnav_data.ld_db.in());
      register_xpath_vtab(lnav_data.ld_db.in());
      register_fstat_vtab(lnav_data.ld_db.in());
  
      lnav_data.ld_vtab_manager = std::make_unique<log_vtab_manager>(
          lnav_data.ld_db, lnav_data.ld_views[LNV_LOG], lnav_data.ld_log_source);
  
      lnav_data.ld_views[LNV_HELP]
          .set_sub_source(&lnav_data.ld_help_source)
          .set_word_wrap(true);
      auto log_fos = new field_overlay_source(lnav_data.ld_log_source,
                                              lnav_data.ld_text_source);
      if (lnav_data.ld_flags & LNF_HEADLESS) {
          log_fos->fos_show_status = false;
      }
      log_fos->fos_contexts.emplace("", false, true);
      lnav_data.ld_views[LNV_LOG]
          .set_sub_source(&lnav_data.ld_log_source)
          .set_delegate(new action_delegate(lnav_data.ld_log_source))
          .add_input_delegate(lnav_data.ld_log_source)
          .set_tail_space(2_vl)
          .set_overlay_source(log_fos);
      lnav_data.ld_views[LNV_TEXT]
          .set_sub_source(&lnav_data.ld_text_source);
      lnav_data.ld_views[LNV_HISTOGRAM]
          .set_sub_source(&lnav_data.ld_hist_source2);
      lnav_data.ld_views[LNV_DB]
          .set_sub_source(&lnav_data.ld_db_row_source);
      lnav_data.ld_db_overlay.dos_labels = &lnav_data.ld_db_row_source;
      lnav_data.ld_views[LNV_DB]
          .set_overlay_source(&lnav_data.ld_db_overlay);
      lnav_data.ld_views[LNV_SPECTRO]
          .set_sub_source(&lnav_data.ld_spectro_source)
          .set_overlay_source(&lnav_data.ld_spectro_source)
          .add_input_delegate(lnav_data.ld_spectro_source)
          .set_tail_space(2_vl);
  
      lnav_data.ld_doc_view.set_sub_source(&lnav_data.ld_doc_source);
      lnav_data.ld_example_view.set_sub_source(&lnav_data.ld_example_source);
      lnav_data.ld_match_view.set_sub_source(&lnav_data.ld_match_source);
      lnav_data.ld_preview_view.set_sub_source(&lnav_data.ld_preview_source);
      lnav_data.ld_filter_view
          .set_sub_source(&lnav_data.ld_filter_source)
          .add_input_delegate(lnav_data.ld_filter_source)
          .add_child_view(&lnav_data.ld_filter_source.fss_match_view)
          .add_child_view(&lnav_data.ld_filter_source.fss_editor);
      lnav_data.ld_files_view
          .set_sub_source(&lnav_data.ld_files_source)
          .add_input_delegate(lnav_data.ld_files_source);
  
      for (lpc = 0; lpc < LNV__MAX; lpc++) {
          lnav_data.ld_views[lpc].set_gutter_source(new log_gutter_source());
      }
  
      {
          hist_source2 &hs = lnav_data.ld_hist_source2;
  
          lnav_data.ld_log_source.set_index_delegate(
              new hist_index_delegate(lnav_data.ld_hist_source2,
                                      lnav_data.ld_views[LNV_HISTOGRAM]));
          hs.init();
          lnav_data.ld_zoom_level = 3;
          hs.set_time_slice(ZOOM_LEVELS[lnav_data.ld_zoom_level]);
      }
  
      for (lpc = 0; lpc < LNV__MAX; lpc++) {
          lnav_data.ld_views[lpc].set_title(view_titles[lpc]);
      }
  
      load_formats(lnav_data.ld_config_paths, loader_errors);
  
      {
          auto_mem<char, sqlite3_free> errmsg;
  
          if (sqlite3_exec(lnav_data.ld_db.in(),
                           init_sql.to_string_fragment().data(),
                           nullptr,
                           nullptr,
                           errmsg.out()) != SQLITE_OK) {
              fprintf(stderr,
                      "error: unable to execute DB init -- %s\n",
                      errmsg.in());
          }
      }
  
      lnav_data.ld_vtab_manager->register_vtab(std::make_shared<all_logs_vtab>());
      lnav_data.ld_vtab_manager->register_vtab(std::make_shared<log_format_vtab_impl>(
              *log_format::find_root_format("generic_log")));
  
      for (auto &iter : log_format::get_root_formats()) {
          auto lvi = iter->get_vtab_impl();
  
          if (lvi != nullptr) {
              lnav_data.ld_vtab_manager->register_vtab(lvi);
          }
      }
  
      load_format_extra(lnav_data.ld_db.in(), lnav_data.ld_config_paths, loader_errors);
      load_format_vtabs(lnav_data.ld_vtab_manager.get(), loader_errors);
      auto _vtab_cleanup = finally([] {
          static const char *VIRT_TABLES = R"(
  SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%'
  )";
  
          for (auto& lf : lnav_data.ld_active_files.fc_files) {
              lf->close();
          }
          rebuild_indexes(ui_clock::now());
  
          lnav_data.ld_vtab_manager = nullptr;
  
          auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
          std::vector<std::string> tables_to_drop;
          bool done = false;
  
          sqlite3_prepare_v2(lnav_data.ld_db.in(),
                             VIRT_TABLES,
                             -1,
                             stmt.out(),
                             nullptr);
          do {
              auto ret = sqlite3_step(stmt.in());
  
              switch (ret) {
                  case SQLITE_OK:
                  case SQLITE_DONE:
                      done = true;
                      break;
                  case SQLITE_ROW:
                      tables_to_drop.emplace_back(fmt::format(
                          "DROP TABLE {}", sqlite3_column_text(stmt.in(), 0)));
                      break;
              }
          } while (!done);
  
          for (auto &drop_stmt : tables_to_drop) {
              sqlite3_exec(lnav_data.ld_db.in(),
                           drop_stmt.c_str(),
                           nullptr,
                           nullptr,
                           nullptr);
          }
      });
  
      if (!loader_errors.empty()) {
          print_errors(loader_errors);
          return EXIT_FAILURE;
      }
  
      if (!(lnav_data.ld_flags & LNF_CHECK_CONFIG)) {
          DEFAULT_FILES.insert(make_pair(LNF_SYSLOG, string("var/log/messages")));
          DEFAULT_FILES.insert(
                  make_pair(LNF_SYSLOG, string("var/log/system.log")));
          DEFAULT_FILES.insert(make_pair(LNF_SYSLOG, string("var/log/syslog")));
          DEFAULT_FILES.insert(
                  make_pair(LNF_SYSLOG, string("var/log/syslog.log")));
      }
  
      init_lnav_commands(lnav_commands);
  
      lnav_data.ld_looping        = true;
      lnav_data.ld_mode           = LNM_PAGING;
  
      if ((isatty(STDIN_FILENO) || is_dev_null(STDIN_FILENO)) && argc == 0 &&
          !(lnav_data.ld_flags & LNF__ALL) &&
          !(lnav_data.ld_flags & LNF_NO_DEFAULT)) {
          lnav_data.ld_flags |= LNF_SYSLOG;
      }
      if (lnav_data.ld_flags != 0) {
          char start_dir[FILENAME_MAX];
  
          if (getcwd(start_dir, sizeof(start_dir)) == nullptr) {
              perror("getcwd");
          }
          else {
              do {
                  for (lpc = 0; lpc < LNB__MAX; lpc++) {
                      if (!append_default_files((lnav_flags_t)(1L << lpc))) {
                          retval = EXIT_FAILURE;
                      }
                  }
              } while (lnav_data.ld_active_files.fc_file_names.empty() &&
                       change_to_parent_dir());
  
              if (chdir(start_dir) == -1) {
                  perror("chdir(start_dir)");
              }
          }
      }
  
      {
          const auto internals_dir = getenv("DUMP_INTERNALS_DIR");
  
          if (internals_dir) {
              dump_schema_to(lnav_config_handlers, internals_dir, "config-v1.schema.json");
              dump_schema_to(root_format_handler, internals_dir, "format-v1.schema.json");
  
              execute_examples();
  
              auto cmd_ref_path = ghc::filesystem::path(internals_dir) / "cmd-ref.rst";
              auto cmd_file = unique_ptr<FILE, decltype(&fclose)>(fopen(cmd_ref_path.c_str(), "w+"), fclose);
  
              if (cmd_file.get()) {
                  set<readline_context::command_t *> unique_cmds;
  
                  for (auto &cmd : lnav_commands) {
                      if (unique_cmds.find(cmd.second) != unique_cmds.end()) {
                          continue;
                      }
                      unique_cmds.insert(cmd.second);
                      format_help_text_for_rst(cmd.second->c_help, eval_example,
                                               cmd_file.get());
                  }
              }
  
              auto sql_ref_path = ghc::filesystem::path(internals_dir) / "sql-ref.rst";
              auto sql_file = unique_ptr<FILE, decltype(&fclose)>(fopen(sql_ref_path.c_str(), "w+"), fclose);
              set<help_text *> unique_sql_help;
  
              if (sql_file.get()) {
                  for (auto &sql : sqlite_function_help) {
                      if (unique_sql_help.find(sql.second) !=
                          unique_sql_help.end()) {
                          continue;
                      }
                      unique_sql_help.insert(sql.second);
                      format_help_text_for_rst(*sql.second, eval_example,
                                               sql_file.get());
                  }
              }
  
              return EXIT_SUCCESS;
          }
      }
  
      if (argc == 0) {
          load_stdin = true;
      }
  
      for (lpc = 0; lpc < argc; lpc++) {
          auto_mem<char> abspath;
          struct stat    st;
  
          if (strcmp(argv[lpc], "-") == 0) {
              load_stdin = true;
          }
          else if (startswith(argv[lpc], "pt:")) {
  #ifdef HAVE_LIBCURL
              lnav_data.ld_pt_search = argv[lpc];
  #else
              fprintf(stderr, "error: lnav is not compiled with libcurl\n");
              retval = EXIT_FAILURE;
  #endif
          }
  #ifdef HAVE_LIBCURL
          else if (is_url(argv[lpc])) {
              auto ul = std::make_shared<url_loader>(argv[lpc]);
  
              lnav_data.ld_active_files.fc_file_names[argv[lpc]]
                  .with_fd(ul->copy_fd());
              isc::to<curl_looper&, services::curl_streamer_t>()
                  .send([ul](auto& clooper) {
                      clooper.add_request(ul);
                  });
          }
  #endif
          else if (is_glob(argv[lpc])) {
              lnav_data.ld_active_files.fc_file_names[argv[lpc]]
                  .with_tail(!(lnav_data.ld_flags & LNF_HEADLESS));
          }
          else if (stat(argv[lpc], &st) == -1) {
              if (strchr(argv[lpc], ':') != nullptr) {
                  lnav_data.ld_active_files.fc_file_names[argv[lpc]]
                      .with_tail(!(lnav_data.ld_flags & LNF_HEADLESS));
              } else {
                  fprintf(stderr,
                          "Cannot stat file: %s -- %s\n",
                          argv[lpc],
                          strerror(errno));
                  retval = EXIT_FAILURE;
              }
          }
          else if (access(argv[lpc], R_OK) == -1) {
              fprintf(stderr,
                      "Cannot read file: %s -- %s\n",
                      argv[lpc],
                      strerror(errno));
              retval = EXIT_FAILURE;
          }
          else if (S_ISFIFO(st.st_mode)) {
              auto_fd fifo_fd;
  
              if ((fifo_fd = open(argv[lpc], O_RDONLY)) == -1) {
                  fprintf(stderr,
                          "Cannot open fifo: %s -- %s\n",
                          argv[lpc],
                          strerror(errno));
                  retval = EXIT_FAILURE;
              } else {
                  auto fifo_piper = make_shared<piper_proc>(
                      fifo_fd.release(),
                      false,
                      open_temp_file(ghc::filesystem::temp_directory_path() /
                                     "lnav.fifo.XXXXXX")
                          .map([](auto pair) {
                              ghc::filesystem::remove(pair.first);
  
                              return pair;
                          })
                          .expect("Cannot create temporary file for FIFO")
                          .second);
                  auto fifo_out_fd = fifo_piper->get_fd();
                  char desc[128];
  
                  snprintf(desc, sizeof(desc),
                           "FIFO [%d]",
                           lnav_data.ld_fifo_counter++);
                  lnav_data.ld_active_files.fc_file_names[desc]
                      .with_fd(fifo_out_fd);
                  lnav_data.ld_pipers.push_back(fifo_piper);
              }
          }
          else if ((abspath = realpath(argv[lpc], nullptr)) == nullptr) {
              perror("Cannot find file");
              retval = EXIT_FAILURE;
          }
          else if (S_ISDIR(st.st_mode)) {
              string dir_wild(abspath.in());
  
              if (dir_wild[dir_wild.size() - 1] == '/') {
                  dir_wild.resize(dir_wild.size() - 1);
              }
              lnav_data.ld_active_files.fc_file_names
                  .emplace(dir_wild + "/*", logfile_open_options());
          }
          else {
              lnav_data.ld_active_files.fc_file_names
                  .emplace(abspath.in(), logfile_open_options());
          }
      }
  
      if (lnav_data.ld_flags & LNF_CHECK_CONFIG) {
          rescan_files(true);
          for (auto &lf : lnav_data.ld_active_files.fc_files) {
              logfile::rebuild_result_t rebuild_result;
  
              do {
                  rebuild_result = lf->rebuild_index();
              } while (rebuild_result == logfile::rebuild_result_t::NEW_LINES ||
                       rebuild_result == logfile::rebuild_result_t::NEW_ORDER);
              auto fmt = lf->get_format();
              if (fmt == nullptr) {
                  fprintf(stderr, "error:%s:no format found for file\n",
                          lf->get_filename().c_str());
                  retval = EXIT_FAILURE;
                  continue;
              }
              for (auto line_iter = lf->begin(); line_iter != lf->end(); ++line_iter) {
                  if (line_iter->get_msg_level() != log_level_t::LEVEL_INVALID) {
                      continue;
                  }
  
                  size_t partial_len;
  
                  auto read_result = lf->read_line(line_iter);
                  if (read_result.isErr()) {
                      continue;
                  }
                  shared_buffer_ref sbr = read_result.unwrap();
                  if (fmt->scan_for_partial(sbr, partial_len)) {
                      long line_number = distance(lf->begin(), line_iter);
                      string full_line(sbr.get_data(), sbr.length());
                      string partial_line(sbr.get_data(), partial_len);
  
                      fprintf(stderr,
                              "error:%s:%ld:line did not match format %s\n",
                              lf->get_filename().c_str(), line_number,
                              fmt->get_pattern_name(line_number).c_str());
                      fprintf(stderr,
                              "error:%s:%ld:         line -- %s\n",
                              lf->get_filename().c_str(), line_number,
                              full_line.c_str());
                      if (partial_len > 0) {
                          fprintf(stderr,
                                  "error:%s:%ld:partial match -- %s\n",
                                  lf->get_filename().c_str(), line_number,
                                  partial_line.c_str());
                      }
                      else {
                          fprintf(stderr,
                                  "error:%s:%ld:no partial match found\n",
                                  lf->get_filename().c_str(), line_number);
                      }
                      retval = EXIT_FAILURE;
                  }
              }
          }
          return retval;
      }
  
      if (!(lnav_data.ld_flags & (LNF_HEADLESS|LNF_CHECK_CONFIG)) && !isatty(STDOUT_FILENO)) {
          fprintf(stderr, "error: stdout is not a tty.\n");
          retval = EXIT_FAILURE;
      }
  
      if (load_stdin && !isatty(STDIN_FILENO) && !is_dev_null(STDIN_FILENO) && !exec_stdin) {
          if (stdin_out == nullptr) {
              auto pattern = lnav::paths::dotlnav() / "stdin-captures/stdin.XXXXXX";
  
              auto open_result = open_temp_file(pattern);
              if (open_result.isErr()) {
                  fprintf(stderr,
                          "Unable to open temporary file for stdin: %s",
                          open_result.unwrapErr().c_str());
                  return EXIT_FAILURE;
              }
  
              auto temp_pair = open_result.unwrap();
              stdin_tmp_path = temp_pair.first;
              stdin_out_fd = temp_pair.second;
          } else {
              if ((stdin_out_fd = open(stdin_out, O_RDWR | O_CREAT | O_TRUNC, 0600)) == -1) {
                  perror("Unable to open output file for stdin");
                  return EXIT_FAILURE;
              }
          }
  
          stdin_reader = make_shared<piper_proc>(
              STDIN_FILENO, lnav_data.ld_flags & LNF_TIMESTAMP, stdin_out_fd);
          lnav_data.ld_active_files.fc_file_names["stdin"]
              .with_fd(auto_fd(stdin_out_fd))
              .with_include_in_session(false);
          lnav_data.ld_pipers.push_back(stdin_reader);
      }
  
      if (!isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)) {
          if (dup2(STDOUT_FILENO, STDIN_FILENO) == -1) {
              perror("cannot dup stdout to stdin");
          }
      }
  
      if (lnav_data.ld_active_files.fc_file_names.empty() &&
          lnav_data.ld_commands.empty() &&
          lnav_data.ld_pt_search.empty() &&
          !(lnav_data.ld_flags & (LNF_HELP|LNF_NO_DEFAULT))) {
          fprintf(stderr, "error: no log files given/found.\n");
          retval = EXIT_FAILURE;
      }
  
      if (retval != EXIT_SUCCESS) {
          usage();
      }
      else {
          isc::supervisor root_superv(injector::get<isc::service_list>());
  
          try {
              log_info("startup: %s", VCS_PACKAGE_STRING);
              log_host_info();
              log_info("Libraries:");
  #ifdef HAVE_BZLIB_H
              log_info("  bzip=%s", BZ2_bzlibVersion());
  #endif
  #ifdef HAVE_LIBCURL
              log_info("  curl=%s (%s)", LIBCURL_VERSION, LIBCURL_TIMESTAMP);
  #endif
  #ifdef HAVE_ARCHIVE_H
              log_info("  libarchive=%d", ARCHIVE_VERSION_NUMBER);
  #endif
              log_info("  ncurses=%s", NCURSES_VERSION);
              log_info("  pcre=%s", pcre_version());
              log_info("  readline=%s", rl_library_version);
              log_info("  sqlite=%s", sqlite3_version);
              log_info("  zlib=%s", zlibVersion());
              log_info("lnav_data:");
              log_info("  flags=%x", lnav_data.ld_flags);
              log_info("  commands:");
              for (auto cmd_iter = lnav_data.ld_commands.begin();
                   cmd_iter != lnav_data.ld_commands.end();
                   ++cmd_iter) {
                  log_info("    %s", cmd_iter->c_str());
              }
              log_info("  files:");
              for (auto file_iter = lnav_data.ld_active_files.fc_file_names.begin();
                   file_iter != lnav_data.ld_active_files.fc_file_names.end();
                   ++file_iter) {
                  log_info("    %s", file_iter->first.c_str());
              }
  
              if (lnav_data.ld_flags & LNF_HEADLESS) {
                  std::vector<pair<Result<string, string>, string>> cmd_results;
                  textview_curses *log_tc, *text_tc, *tc;
                  bool output_view = true;
  
                  view_colors::init();
                  rescan_files(true);
                  if (!lnav_data.ld_active_files.fc_name_to_errors.empty()) {
                      for (const auto& pair : lnav_data.ld_active_files.fc_name_to_errors) {
                          fprintf(stderr,
                                  "error: unable to open file: %s -- %s\n",
                                  pair.first.c_str(),
                                  pair.second.fei_description.c_str());
                      }
  
                      return EXIT_FAILURE;
                  }
                  init_session();
                  lnav_data.ld_exec_context.set_output("stdout", stdout, nullptr);
                  alerter::singleton().enabled(false);
  
                  log_tc = &lnav_data.ld_views[LNV_LOG];
                  log_tc->set_height(24_vl);
                  lnav_data.ld_view_stack.push_back(log_tc);
                  // Read all of stdin
                  wait_for_pipers();
                  rebuild_indexes_repeatedly();
  
                  log_tc->set_top(0_vl);
                  text_tc = &lnav_data.ld_views[LNV_TEXT];
                  text_tc->set_top(0_vl);
                  text_tc->set_height(vis_line_t(text_tc->get_inner_height()));
                  if (lnav_data.ld_log_source.text_line_count() == 0 &&
                      lnav_data.ld_text_source.text_line_count() > 0) {
                      toggle_view(&lnav_data.ld_views[LNV_TEXT]);
                  }
  
                  log_info("Executing initial commands");
                  execute_init_commands(lnav_data.ld_exec_context, cmd_results);
                  archive_manager::cleanup_cache();
                  tailer::cleanup_cache();
                  wait_for_pipers();
                  isc::to<curl_looper&, services::curl_streamer_t>()
                      .send_and_wait([](auto& clooper) {
                          clooper.process_all();
                      });
                  rebuild_indexes_repeatedly();
                  if (!lnav_data.ld_active_files.fc_name_to_errors.empty()) {
                      for (const auto& pair : lnav_data.ld_active_files.fc_name_to_errors) {
                          fprintf(stderr,
                                  "error: unable to open file: %s -- %s\n",
                                  pair.first.c_str(),
                                  pair.second.fei_description.c_str());
                      }
  
                      return EXIT_FAILURE;
                  }
  
                  for (auto &pair : cmd_results) {
                      if (pair.first.isErr()) {
                          fprintf(stderr, "%s\n", pair.first.unwrapErr().c_str());
                          output_view = false;
                      }
                      else {
                          auto msg = pair.first.unwrap();
  
                          if (startswith(msg, "info:")) {
                              if (lnav_data.ld_flags & LNF_VERBOSE) {
                                  printf("%s\n", msg.c_str());
                              }
                          } else if (!msg.empty()) {
                              printf("%s\n", msg.c_str());
                              output_view = false;
                          }
                      }
                  }
  
                  if (output_view &&
                      !(lnav_data.ld_flags & LNF_QUIET) &&
                      !lnav_data.ld_view_stack.empty() &&
                      !lnav_data.ld_stdout_used) {
                      bool suppress_empty_lines = false;
                      list_overlay_source *los;
                      unsigned long view_index;
                      vis_line_t y;
  
                      tc = *lnav_data.ld_view_stack.top();
                      view_index = tc - lnav_data.ld_views;
                      switch (view_index) {
                          case LNV_DB:
                          case LNV_HISTOGRAM:
                              suppress_empty_lines = true;
                              break;
                          default:
                              break;
                      }
  
                      los = tc->get_overlay_source();
  
                      vis_line_t vl;
                      for (vl = tc->get_top();
                           vl < tc->get_inner_height();
                           ++vl, ++y) {
                          attr_line_t al;
                          string &line = al.get_string();
                          while (los != nullptr &&
                                 los->list_value_for_overlay(*tc, y, tc->get_inner_height(), vl, al)) {
                              if (write(STDOUT_FILENO, line.c_str(),
                                        line.length()) == -1 ||
                                  write(STDOUT_FILENO, "\n", 1) == -1) {
                                  perror("1 write to STDOUT");
                              }
                              ++y;
                          }
  
                          vector<attr_line_t> rows(1);
                          tc->listview_value_for_rows(*tc, vl, rows);
                          if (suppress_empty_lines && rows[0].empty()) {
                              continue;
                          }
  
                          struct line_range lr = find_string_attr_range(
                                  rows[0].get_attrs(), &SA_ORIGINAL_LINE);
                          if (write(STDOUT_FILENO, lr.substr(rows[0].get_string()),
                                    lr.sublen(rows[0].get_string())) == -1 ||
                              write(STDOUT_FILENO, "\n", 1) == -1) {
                              perror("2 write to STDOUT");
                          }
  
                      }
                      {
                          attr_line_t al;
                          string &line = al.get_string();
  
                          while (los != nullptr &&
                                 los->list_value_for_overlay(*tc, y, tc->get_inner_height(), vl, al) &&
                                 !al.empty()) {
                              if (write(STDOUT_FILENO, line.c_str(),
                                        line.length()) == -1 ||
                                  write(STDOUT_FILENO, "\n", 1) == -1) {
                                  perror("1 write to STDOUT");
                              }
                              ++y;
                          }
                      }
                  }
              }
              else {
                  init_session();
  
                  guard_termios gt(STDIN_FILENO);
                  lnav_log_orig_termios = gt.get_termios();
  
                  looper();
  
                  dup2(STDOUT_FILENO, STDERR_FILENO);
  
                  signal(SIGINT, SIG_DFL);
  
                  save_session();
              }
          }
          catch (line_buffer::error & e) {
              fprintf(stderr, "error: %s\n", strerror(e.e_err));
          }
  
          // When reading from stdin, tell the user where the capture file is
          // stored so they can look at it later.
          if (stdin_out_fd != -1 &&
              stdin_out == nullptr &&
              !(lnav_data.ld_flags & LNF_QUIET) &&
              !(lnav_data.ld_flags & LNF_HEADLESS)) {
              if (ghc::filesystem::file_size(stdin_tmp_path) > MAX_STDIN_CAPTURE_SIZE) {
                  log_info("not saving large stdin capture -- %s",
                           stdin_tmp_path.c_str());
                  ghc::filesystem::remove(stdin_tmp_path);
              } else {
                  auto home = getenv("HOME");
                  auto path_str = stdin_tmp_path.string();
  
                  if (home != nullptr && startswith(path_str, home)) {
                      path_str = path_str.substr(strlen(home));
                      if (path_str[0] != '/') {
                          path_str.insert(0, 1, '/');
                      }
                      path_str.insert(0, 1, '~');
                  }
  
                  fprintf(stderr,
                          "info: stdin was captured, you can reopen it using -- "
                          "lnav %s\n",
                          path_str.c_str());
              }
          }
      }
  
      return retval;
  }
  