1 /**
2  * Copyright (c) 2015, Timothy Stack
3  *
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are met:
8  *
9  * * Redistributions of source code must retain the above copyright notice, this
10  * list of conditions and the following disclaimer.
11  * * Redistributions in binary form must reproduce the above copyright notice,
12  * this list of conditions and the following disclaimer in the documentation
13  * and/or other materials provided with the distribution.
14  * * Neither the name of Timothy Stack nor the names of its contributors
15  * may be used to endorse or promote products derived from this software
16  * without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
19  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21  * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
22  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25  * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29 
30 #include "config.h"
31 
32 #include <vector>
33 
34 #include "base/string_util.hh"
35 #include "base/injector.hh"
36 #include "yajlpp/json_ptr.hh"
37 #include "lnav.hh"
38 #include "log_format_loader.hh"
39 #include "shlex.hh"
40 #include "lnav_util.hh"
41 #include "sql_util.hh"
42 #include "lnav_config.hh"
43 #include "service_tags.hh"
44 #include "bound_tags.hh"
45 
46 #include "command_executor.hh"
47 #include "db_sub_source.hh"
48 #include "papertrail_proc.hh"
49 
50 using namespace std;
51 
52 exec_context INIT_EXEC_CONTEXT;
53 
54 static const string MSG_FORMAT_STMT = R"(
55 SELECT count(*) AS total, min(log_line) AS log_line, log_msg_format
56     FROM all_logs
57     GROUP BY log_msg_format
58     ORDER BY total DESC
59 )";
60 
sql_progress(const struct log_cursor & lc)61 int sql_progress(const struct log_cursor &lc)
62 {
63     static sig_atomic_t sql_counter = 0;
64 
65     size_t total = lnav_data.ld_log_source.text_line_count();
66     off_t  off   = lc.lc_curr_line;
67 
68     if (off < 0) {
69         return 0;
70     }
71 
72     if (lnav_data.ld_window == nullptr) {
73         return 0;
74     }
75 
76     if (!lnav_data.ld_looping) {
77         return 1;
78     }
79 
80     if (ui_periodic_timer::singleton().time_to_update(sql_counter)) {
81         lnav_data.ld_bottom_source.update_loading(off, total);
82         lnav_data.ld_top_source.update_time();
83         lnav_data.ld_status[LNS_TOP].do_update();
84         lnav_data.ld_status[LNS_BOTTOM].do_update();
85         refresh();
86     }
87 
88     return 0;
89 }
90 
sql_progress_finished()91 void sql_progress_finished()
92 {
93     if (lnav_data.ld_window == nullptr) {
94         return;
95     }
96 
97     lnav_data.ld_bottom_source.update_loading(0, 0);
98     lnav_data.ld_top_source.update_time();
99     lnav_data.ld_status[LNS_TOP].do_update();
100     lnav_data.ld_status[LNS_BOTTOM].do_update();
101     lnav_data.ld_views[LNV_DB].redo_search();
102 }
103 
104 Result<string, string> execute_from_file(exec_context &ec, const ghc::filesystem::path &path, int line_number, char mode, const string &cmdline);
105 
execute_command(exec_context & ec,const string & cmdline)106 Result<string, string> execute_command(exec_context &ec, const string &cmdline)
107 {
108     vector<string> args;
109 
110     log_info("Executing: %s", cmdline.c_str());
111 
112     split_ws(cmdline, args);
113 
114     if (!args.empty()) {
115         readline_context::command_map_t::iterator iter;
116 
117         if ((iter = lnav_commands.find(args[0])) == lnav_commands.end()) {
118             return ec.make_error("unknown command - {}", args[0]);
119         }
120         else {
121             return iter->second->c_func(ec, cmdline, args);
122         }
123     }
124 
125     return ec.make_error("no command to execute");
126 }
127 
execute_sql(exec_context & ec,const string & sql,string & alt_msg)128 Result<string, string> execute_sql(exec_context &ec, const string &sql, string &alt_msg)
129 {
130     db_label_source &dls = lnav_data.ld_db_row_source;
131     auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
132     struct timeval start_tv, end_tv;
133     string stmt_str = trim(sql);
134     string retval;
135     int retcode;
136 
137     log_info("Executing SQL: %s", sql.c_str());
138 
139     lnav_data.ld_bottom_source.grep_error("");
140 
141     if (startswith(stmt_str, ".")) {
142         vector<string> args;
143         split_ws(stmt_str, args);
144 
145         auto sql_cmd_map = injector::get<
146             readline_context::command_map_t *, sql_cmd_map_tag>();
147         auto cmd_iter = sql_cmd_map->find(args[0]);
148 
149         if (cmd_iter != sql_cmd_map->end()) {
150             return cmd_iter->second->c_func(ec, stmt_str, args);
151         }
152     }
153 
154     if (stmt_str == ".msgformats") {
155         stmt_str = MSG_FORMAT_STMT;
156     }
157 
158     ec.ec_accumulator.clear();
159 
160     pair<string, int> source = ec.ec_source.top();
161     sql_progress_guard progress_guard(sql_progress,
162                                       sql_progress_finished,
163                                       source.first,
164                                       source.second);
165     gettimeofday(&start_tv, nullptr);
166     retcode = sqlite3_prepare_v2(lnav_data.ld_db.in(),
167        stmt_str.c_str(),
168        -1,
169        stmt.out(),
170        nullptr);
171     if (retcode != SQLITE_OK) {
172         const char *errmsg = sqlite3_errmsg(lnav_data.ld_db);
173 
174         alt_msg = "";
175         return ec.make_error("{}", errmsg);
176     }
177     else if (stmt == nullptr) {
178         alt_msg = "";
179         return ec.make_error("No statement given");
180     }
181 #ifdef HAVE_SQLITE3_STMT_READONLY
182     else if (ec.is_read_only() && !sqlite3_stmt_readonly(stmt.in())) {
183         return ec.make_error(
184             "modifying statements are not allowed in this context: {}", sql);
185     }
186 #endif
187     else {
188         bool done = false;
189         int param_count;
190 
191         param_count = sqlite3_bind_parameter_count(stmt.in());
192         for (int lpc = 0; lpc < param_count; lpc++) {
193             map<string, string>::iterator ov_iter;
194             const char *name;
195 
196             name = sqlite3_bind_parameter_name(stmt.in(), lpc + 1);
197             ov_iter = ec.ec_override.find(name);
198             if (ov_iter != ec.ec_override.end()) {
199                 sqlite3_bind_text(stmt.in(),
200                                   lpc,
201                                   ov_iter->second.c_str(),
202                                   ov_iter->second.length(),
203                                   SQLITE_TRANSIENT);
204             }
205             else if (name[0] == '$') {
206                 const auto &lvars = ec.ec_local_vars.top();
207                 const auto &gvars = ec.ec_global_vars;
208                 map<string, string>::const_iterator local_var, global_var;
209                 const char *env_value;
210 
211                 if (lnav_data.ld_window) {
212                     char buf[32];
213                     int lines, cols;
214 
215                     getmaxyx(lnav_data.ld_window, lines, cols);
216                     if (strcmp(name, "$LINES") == 0) {
217                         snprintf(buf, sizeof(buf), "%d", lines);
218                         sqlite3_bind_text(stmt.in(), lpc + 1,
219                                           buf, -1,
220                                           SQLITE_TRANSIENT);
221                     } else if (strcmp(name, "$COLS") == 0) {
222                         snprintf(buf, sizeof(buf), "%d", cols);
223                         sqlite3_bind_text(stmt.in(), lpc + 1,
224                                           buf, -1,
225                                           SQLITE_TRANSIENT);
226                     }
227                 }
228 
229                 if ((local_var = lvars.find(&name[1])) != lvars.end()) {
230                     sqlite3_bind_text(stmt.in(), lpc + 1,
231                                       local_var->second.c_str(), -1,
232                                       SQLITE_TRANSIENT);
233                 }
234                 else if ((global_var = gvars.find(&name[1])) != gvars.end()) {
235                     sqlite3_bind_text(stmt.in(), lpc + 1,
236                                       global_var->second.c_str(), -1,
237                                       SQLITE_TRANSIENT);
238                 }
239                 else if ((env_value = getenv(&name[1])) != nullptr) {
240                     sqlite3_bind_text(stmt.in(), lpc + 1, env_value, -1, SQLITE_STATIC);
241                 }
242             }
243             else if (name[0] == ':' && ec.ec_line_values != nullptr) {
244                 for (auto& lv : *ec.ec_line_values) {
245                     if (lv.lv_meta.lvm_name != &name[1]) {
246                         continue;
247                     }
248                     switch (lv.lv_meta.lvm_kind) {
249                         case value_kind_t::VALUE_BOOLEAN:
250                             sqlite3_bind_int64(stmt.in(), lpc + 1, lv.lv_value.i);
251                             break;
252                         case value_kind_t::VALUE_FLOAT:
253                             sqlite3_bind_double(stmt.in(), lpc + 1, lv.lv_value.d);
254                             break;
255                         case value_kind_t::VALUE_INTEGER:
256                             sqlite3_bind_int64(stmt.in(), lpc + 1, lv.lv_value.i);
257                             break;
258                         case value_kind_t::VALUE_NULL:
259                             sqlite3_bind_null(stmt.in(), lpc + 1);
260                             break;
261                         default:
262                             sqlite3_bind_text(stmt.in(),
263                                               lpc + 1,
264                                               lv.text_value(),
265                                               lv.text_length(),
266                                               SQLITE_TRANSIENT);
267                             break;
268                     }
269                 }
270             }
271             else {
272                 sqlite3_bind_null(stmt.in(), lpc + 1);
273                 log_warning("Could not bind variable: %s", name);
274             }
275         }
276 
277         if (lnav_data.ld_rl_view != nullptr) {
278             lnav_data.ld_rl_view->set_value("Executing query: " + sql + " ...");
279         }
280 
281         ec.ec_sql_callback(ec, stmt.in());
282         while (!done) {
283             retcode = sqlite3_step(stmt.in());
284 
285             switch (retcode) {
286                 case SQLITE_OK:
287                 case SQLITE_DONE:
288                     done = true;
289                     break;
290 
291                 case SQLITE_ROW:
292                     ec.ec_sql_callback(ec, stmt.in());
293                     break;
294 
295                 default: {
296                     const char *errmsg;
297 
298                     log_error("sqlite3_step error code: %d", retcode);
299                     errmsg = sqlite3_errmsg(lnav_data.ld_db);
300                     return ec.make_error("{}", errmsg);
301                     break;
302                 }
303             }
304         }
305 
306         if (!dls.dls_rows.empty() && !ec.ec_local_vars.empty() &&
307             !ec.ec_dry_run) {
308             auto &vars = ec.ec_local_vars.top();
309 
310             for (unsigned int lpc = 0; lpc < dls.dls_headers.size(); lpc++) {
311                 const auto &column_name = dls.dls_headers[lpc].hm_name;
312 
313                 if (sql_ident_needs_quote(column_name.c_str())) {
314                     continue;
315                 }
316 
317                 const auto* value = dls.dls_rows[0][lpc];
318                 if (value == nullptr) {
319                     continue;
320                 }
321 
322                 vars[column_name] = value;
323             }
324         }
325 
326         if (lnav_data.ld_rl_view != nullptr) {
327             lnav_data.ld_rl_view->set_value("");
328         }
329     }
330 
331     gettimeofday(&end_tv, nullptr);
332     if (retcode == SQLITE_DONE) {
333         if (lnav_data.ld_log_source.is_line_meta_changed()) {
334             lnav_data.ld_log_source.text_filters_changed();
335             lnav_data.ld_views[LNV_LOG].reload_data();
336         }
337         lnav_data.ld_filter_view.reload_data();
338         lnav_data.ld_files_view.reload_data();
339         lnav_data.ld_views[LNV_DB].reload_data();
340         lnav_data.ld_views[LNV_DB].set_left(0);
341 
342         if (!ec.ec_accumulator.empty()) {
343             retval = ec.ec_accumulator.get_string();
344         }
345         else if (!dls.dls_rows.empty()) {
346             if (lnav_data.ld_flags & LNF_HEADLESS) {
347                 if (ec.ec_local_vars.size() == 1) {
348                     ensure_view(&lnav_data.ld_views[LNV_DB]);
349                 }
350 
351                 retval = "";
352                 alt_msg = "";
353             }
354             else if (dls.dls_rows.size() == 1) {
355                 auto &row = dls.dls_rows[0];
356 
357                 if (dls.dls_headers.size() == 1) {
358                     retval = row[0];
359                 } else {
360                     for (unsigned int lpc = 0; lpc < dls.dls_headers.size(); lpc++) {
361                         if (lpc > 0) {
362                             retval.append("; ");
363                         }
364                         retval.append(dls.dls_headers[lpc].hm_name);
365                         retval.push_back('=');
366                         retval.append(row[lpc]);
367                     }
368                 }
369             }
370             else {
371                 int row_count = dls.dls_rows.size();
372                 char row_count_buf[128];
373                 struct timeval diff_tv;
374 
375                 timersub(&end_tv, &start_tv, &diff_tv);
376                 snprintf(row_count_buf, sizeof(row_count_buf),
377                          ANSI_BOLD("%'d") " row%s matched in "
378                          ANSI_BOLD("%ld.%03ld") " seconds",
379                          row_count,
380                          row_count == 1 ? "" : "s",
381                          diff_tv.tv_sec,
382                          std::max((long) diff_tv.tv_usec / 1000, 1L));
383                 retval = row_count_buf;
384                 alt_msg = HELP_MSG_2(
385                     y, Y,
386                     "to move forward/backward through query results "
387                         "in the log view");
388             }
389         }
390 #ifdef HAVE_SQLITE3_STMT_READONLY
391         else if (sqlite3_stmt_readonly(stmt.in())) {
392             retval = "info: No rows matched";
393             alt_msg = "";
394 
395             if (lnav_data.ld_flags & LNF_HEADLESS) {
396                 if (ec.ec_local_vars.size() == 1) {
397                     ensure_view(&lnav_data.ld_views[LNV_DB]);
398                 }
399             }
400         }
401 #endif
402     }
403 
404     return Ok(retval);
405 }
406 
execute_file_contents(exec_context & ec,const ghc::filesystem::path & path,bool multiline)407 static Result<string, string> execute_file_contents(exec_context &ec, const ghc::filesystem::path &path, bool multiline)
408 {
409     static ghc::filesystem::path stdin_path("-");
410     static ghc::filesystem::path dev_stdin_path("/dev/stdin");
411 
412     string retval;
413     FILE *file;
414 
415     if (path == stdin_path || path == dev_stdin_path) {
416         if (isatty(STDIN_FILENO)) {
417             return ec.make_error("stdin has already been consumed");
418         }
419         file = stdin;
420     }
421     else if ((file = fopen(path.c_str(), "r")) == nullptr) {
422         return ec.make_error("unable to open file");
423     }
424 
425     int    line_number = 0, starting_line_number = 0;
426     auto_mem<char> line;
427     size_t line_max_size;
428     ssize_t line_size;
429     string cmdline;
430     char mode = '\0';
431 
432     ec.ec_path_stack.emplace_back(path.parent_path());
433     exec_context::output_guard og(ec);
434     while ((line_size = getline(line.out(), &line_max_size, file)) != -1) {
435         line_number += 1;
436 
437         if (trim(line.in()).empty()) {
438             continue;
439         }
440         if (line[0] == '#') {
441             continue;
442         }
443 
444         switch (line[0]) {
445             case ':':
446             case '/':
447             case ';':
448             case '|':
449                 if (mode) {
450                     retval = TRY(execute_from_file(ec, path, starting_line_number, mode, trim(cmdline)));
451                 }
452 
453                 starting_line_number = line_number;
454                 mode = line[0];
455                 cmdline = string(&line[1]);
456                 break;
457             default:
458                 if (multiline) {
459                     cmdline += line;
460                 }
461                 else {
462                     retval = TRY(execute_from_file(ec, path, line_number, ':', line.in()));
463                 }
464                 break;
465         }
466 
467     }
468 
469     if (mode) {
470         retval = TRY(execute_from_file(ec, path, starting_line_number, mode, trim(cmdline)));
471     }
472 
473     if (file == stdin) {
474         if (isatty(STDOUT_FILENO)) {
475             log_perror(dup2(STDOUT_FILENO, STDIN_FILENO));
476         }
477     } else {
478         fclose(file);
479     }
480     ec.ec_path_stack.pop_back();
481 
482     return Ok(retval);
483 }
484 
execute_file(exec_context & ec,const string & path_and_args,bool multiline)485 Result<string, string> execute_file(exec_context &ec, const string &path_and_args, bool multiline)
486 {
487     available_scripts scripts;
488     vector<string> split_args;
489     string retval, msg;
490     shlex lexer(path_and_args);
491 
492     log_info("Executing file: %s", path_and_args.c_str());
493 
494     if (!lexer.split(split_args, ec.ec_local_vars.top())) {
495         return ec.make_error("unable to parse path");
496     }
497     if (split_args.empty()) {
498         return ec.make_error("no script specified");
499     }
500 
501     ec.ec_local_vars.push({});
502 
503     auto script_name = split_args[0];
504     auto& vars = ec.ec_local_vars.top();
505     char env_arg_name[32];
506     string star, open_error = "file not found";
507 
508     add_ansi_vars(vars);
509 
510     snprintf(env_arg_name, sizeof(env_arg_name), "%d",
511              (int) split_args.size() - 1);
512 
513     vars["#"] = env_arg_name;
514     for (size_t lpc = 0; lpc < split_args.size(); lpc++) {
515         snprintf(env_arg_name, sizeof(env_arg_name), "%lu", lpc);
516         vars[env_arg_name] = split_args[lpc];
517     }
518     for (size_t lpc = 1; lpc < split_args.size(); lpc++) {
519         if (lpc > 1) {
520             star.append(" ");
521         }
522         star.append(split_args[lpc]);
523     }
524     vars["__all__"] = star;
525 
526     vector<script_metadata> paths_to_exec;
527 
528     find_format_scripts(lnav_data.ld_config_paths, scripts);
529     auto iter = scripts.as_scripts.find(script_name);
530     if (iter != scripts.as_scripts.end()) {
531         paths_to_exec = iter->second;
532     }
533     if (script_name == "-" || script_name == "/dev/stdin") {
534         paths_to_exec.push_back({script_name, "", "", ""});
535     } else if (access(script_name.c_str(), R_OK) == 0) {
536         struct script_metadata meta;
537 
538         meta.sm_path = script_name;
539         extract_metadata_from_file(meta);
540         paths_to_exec.push_back(meta);
541     } else if (errno != ENOENT) {
542         open_error = strerror(errno);
543     } else {
544         auto script_path = ghc::filesystem::path(script_name);
545 
546         if (!script_path.is_absolute()) {
547             script_path = ec.ec_path_stack.back() / script_path;
548         }
549 
550         if (ghc::filesystem::is_regular_file(script_path)) {
551             struct script_metadata meta;
552 
553             meta.sm_path = script_path;
554             extract_metadata_from_file(meta);
555             paths_to_exec.push_back(meta);
556         } else if (errno != ENOENT) {
557             open_error = strerror(errno);
558         }
559     }
560 
561     if (!paths_to_exec.empty()) {
562         for (auto &path_iter : paths_to_exec) {
563             retval = TRY(
564                 execute_file_contents(ec, path_iter.sm_path, multiline));
565         }
566     }
567     ec.ec_local_vars.pop();
568 
569     if (paths_to_exec.empty()) {
570         return ec.make_error("unknown script -- {} -- {}",
571                              script_name, open_error);
572     }
573 
574     return Ok(retval);
575 }
576 
execute_from_file(exec_context & ec,const ghc::filesystem::path & path,int line_number,char mode,const string & cmdline)577 Result<string, string> execute_from_file(exec_context &ec, const ghc::filesystem::path &path, int line_number, char mode, const string &cmdline)
578 {
579     string retval, alt_msg;
580     auto _sg = ec.enter_source(path.string(), line_number);
581 
582     switch (mode) {
583         case ':':
584             retval = TRY(execute_command(ec, cmdline));
585             break;
586         case '/':
587             lnav_data.ld_view_stack.top() | [cmdline] (auto tc) {
588                 tc->execute_search(cmdline.substr(1));
589             };
590             break;
591         case ';':
592             setup_logline_table(ec);
593             retval = TRY(execute_sql(ec, cmdline, alt_msg));
594             break;
595         case '|':
596             retval = TRY(execute_file(ec, cmdline));
597             break;
598         default:
599             retval = TRY(execute_command(ec, cmdline));
600             break;
601     }
602 
603     log_info("%s:%d:execute result -- %s",
604              path.c_str(),
605              line_number,
606              retval.c_str());
607 
608     return Ok(retval);
609 }
610 
execute_any(exec_context & ec,const string & cmdline_with_mode)611 Result<string, string> execute_any(exec_context &ec, const string &cmdline_with_mode)
612 {
613     string retval, alt_msg, cmdline = cmdline_with_mode.substr(1);
614     auto _cleanup = finally([&ec] {
615         if (ec.is_read_write() &&
616             // only rebuild in a script or non-interactive mode so we don't
617             // block the UI.
618             (lnav_data.ld_flags & LNF_HEADLESS ||
619              ec.ec_path_stack.size() > 1)) {
620             rescan_files();
621             rebuild_indexes_repeatedly();
622         }
623     });
624 
625     switch (cmdline_with_mode[0]) {
626         case ':':
627             retval = TRY(execute_command(ec, cmdline));
628             break;
629         case '/':
630             lnav_data.ld_view_stack.top() | [cmdline] (auto tc) {
631                 tc->execute_search(cmdline.substr(1));
632             };
633             break;
634         case ';':
635             setup_logline_table(ec);
636             retval = TRY(execute_sql(ec, cmdline, alt_msg));
637             break;
638         case '|': {
639             retval = TRY(execute_file(ec, cmdline));
640             break;
641         }
642         default:
643             retval = TRY(execute_command(ec, cmdline));
644             break;
645     }
646 
647     return Ok(retval);
648 }
649 
execute_init_commands(exec_context & ec,vector<pair<Result<string,string>,string>> & msgs)650 void execute_init_commands(exec_context &ec, vector<pair<Result<string, string>, string> > &msgs)
651 {
652     if (lnav_data.ld_cmd_init_done) {
653         return;
654     }
655 
656     db_label_source &dls = lnav_data.ld_db_row_source;
657     int option_index = 1;
658 
659     log_info("Executing initial commands");
660     for (auto &cmd : lnav_data.ld_commands) {
661         string alt_msg;
662 
663         wait_for_children();
664 
665         ec.ec_source.emplace("command-option", option_index++);
666         switch (cmd.at(0)) {
667         case ':':
668             msgs.emplace_back(execute_command(ec, cmd.substr(1)), alt_msg);
669             break;
670         case '/':
671             lnav_data.ld_view_stack.top() | [cmd] (auto tc) {
672                 tc->execute_search(cmd.substr(1));
673             };
674             break;
675         case ';':
676             setup_logline_table(ec);
677             msgs.emplace_back(execute_sql(ec, cmd.substr(1), alt_msg), alt_msg);
678             break;
679         case '|':
680             msgs.emplace_back(execute_file(ec, cmd.substr(1)), alt_msg);
681             break;
682         }
683 
684         rescan_files();
685         rebuild_indexes_repeatedly();
686 
687         ec.ec_source.pop();
688     }
689     lnav_data.ld_commands.clear();
690 
691     if (!lnav_data.ld_pt_search.empty()) {
692 #ifdef HAVE_LIBCURL
693         auto pt = make_shared<papertrail_proc>(
694             lnav_data.ld_pt_search.substr(3),
695             lnav_data.ld_pt_min_time,
696             lnav_data.ld_pt_max_time);
697         lnav_data.ld_active_files.fc_file_names[lnav_data.ld_pt_search]
698             .with_fd(pt->copy_fd());
699         isc::to<curl_looper&, services::curl_streamer_t>()
700             .send([pt](auto& clooper) {
701                 clooper.add_request(pt);
702             });
703 #endif
704     }
705 
706     if (dls.dls_rows.size() > 1) {
707         ensure_view(&lnav_data.ld_views[LNV_DB]);
708     }
709 
710     lnav_data.ld_cmd_init_done = true;
711 }
712 
sql_callback(exec_context & ec,sqlite3_stmt * stmt)713 int sql_callback(exec_context &ec, sqlite3_stmt *stmt)
714 {
715     auto &dls = lnav_data.ld_db_row_source;
716 
717     if (!sqlite3_stmt_busy(stmt)) {
718         dls.clear();
719 
720         return 0;
721     }
722 
723     stacked_bar_chart<std::string> &chart = dls.dls_chart;
724     view_colors &vc = view_colors::singleton();
725     int ncols = sqlite3_column_count(stmt);
726     int row_number;
727     int lpc, retval = 0;
728 
729     row_number = dls.dls_rows.size();
730     dls.dls_rows.resize(row_number + 1);
731     if (dls.dls_headers.empty()) {
732         for (lpc = 0; lpc < ncols; lpc++) {
733             int    type    = sqlite3_column_type(stmt, lpc);
734             string colname = sqlite3_column_name(stmt, lpc);
735             bool   graphable;
736 
737             graphable = ((type == SQLITE_INTEGER || type == SQLITE_FLOAT) &&
738                          !binary_search(lnav_data.ld_db_key_names.begin(),
739                                         lnav_data.ld_db_key_names.end(),
740                                         colname));
741 
742             dls.push_header(colname, type, graphable);
743             if (graphable) {
744                 int attrs = vc.attrs_for_ident(colname);
745                 chart.with_attrs_for_ident(colname, attrs);
746             }
747         }
748     }
749     for (lpc = 0; lpc < ncols; lpc++) {
750         const char *value = (const char *)sqlite3_column_text(stmt, lpc);
751         db_label_source::header_meta &hm = dls.dls_headers[lpc];
752 
753         dls.push_column(value);
754         if ((hm.hm_column_type == SQLITE_TEXT ||
755              hm.hm_column_type == SQLITE_NULL) && hm.hm_sub_type == 0) {
756             sqlite3_value *raw_value = sqlite3_column_value(stmt, lpc);
757 
758             switch (sqlite3_value_type(raw_value)) {
759                 case SQLITE_TEXT:
760                     hm.hm_column_type = SQLITE_TEXT;
761                     hm.hm_sub_type = sqlite3_value_subtype(raw_value);
762                     break;
763             }
764         }
765     }
766 
767     return retval;
768 }
769 
pipe_callback(exec_context & ec,const string & cmdline,auto_fd & fd)770 future<string> pipe_callback(exec_context &ec, const string &cmdline, auto_fd &fd)
771 {
772     auto out = ec.get_output();
773 
774     if (out) {
775         FILE *file = *out;
776 
777         return std::async(std::launch::async, [&fd, file]() {
778             char buffer[1024];
779             ssize_t rc;
780 
781             if (file == stdout) {
782                 lnav_data.ld_stdout_used = true;
783             }
784 
785             while ((rc = read(fd, buffer, sizeof(buffer))) > 0) {
786                 fwrite(buffer, rc, 1, file);
787             }
788 
789             return string();
790         });
791     } else {
792         auto pp = make_shared<piper_proc>(
793             fd, false, open_temp_file(ghc::filesystem::temp_directory_path() /
794             "lnav.out.XXXXXX")
795                 .map([](auto pair) {
796                     ghc::filesystem::remove(pair.first);
797 
798                     return pair;
799                 })
800                 .expect("Cannot create temporary file for callback")
801                 .second);
802         static int exec_count = 0;
803         char desc[128];
804 
805         lnav_data.ld_pipers.push_back(pp);
806         snprintf(desc,
807                  sizeof(desc), "[%d] Output of %s",
808                  exec_count++,
809                  cmdline.c_str());
810         lnav_data.ld_active_files.fc_file_names[desc]
811             .with_fd(pp->get_fd())
812             .with_include_in_session(false)
813             .with_detect_format(false);
814         lnav_data.ld_files_to_front.emplace_back(desc, 0);
815         if (lnav_data.ld_rl_view != nullptr) {
816             lnav_data.ld_rl_view->set_alt_value(
817                 HELP_MSG_1(X, "to close the file"));
818         }
819 
820         return lnav::futures::make_ready_future(std::string());
821     }
822 }
823 
add_global_vars(exec_context & ec)824 void add_global_vars(exec_context &ec)
825 {
826     for (const auto &iter : lnav_config.lc_global_vars) {
827         shlex subber(iter.second);
828         string str;
829 
830         if (!subber.eval(str, ec.ec_global_vars)) {
831             log_error("Unable to evaluate global variable value: %s",
832                       iter.second.c_str());
833             continue;
834         }
835 
836         ec.ec_global_vars[iter.first] = str;
837     }
838 }
839 
get_error_prefix()840 std::string exec_context::get_error_prefix()
841 {
842     if (this->ec_source.size() <= 1) {
843         return "error: ";
844     }
845 
846     std::pair<std::string, int> source = this->ec_source.top();
847 
848     return fmt::format("{}:{}: error: ", source.first, source.second);
849 }
850 
set_output(const string & name,FILE * file,int (* closer)(FILE *))851 void exec_context::set_output(const string &name, FILE *file, int (*closer)(FILE *))
852 {
853     log_info("redirecting command output to: %s", name.c_str());
854     this->ec_output_stack.back().second | [](auto out) {
855         if (out.second != nullptr) {
856             out.second(out.first);
857         }
858     };
859     this->ec_output_stack.back() = std::make_pair(name, std::make_pair(file, closer));
860 }
861 
clear_output()862 void exec_context::clear_output()
863 {
864     log_info("redirecting command output to screen");
865     this->ec_output_stack.back().second | [](auto out) {
866         if (out.second != nullptr) {
867             out.second(out.first);
868         }
869     };
870     this->ec_output_stack.back() = std::make_pair("default", nonstd::nullopt);
871 }
872 
output_guard(exec_context & context,std::string name,const nonstd::optional<output_t> & file)873 exec_context::output_guard::output_guard(exec_context &context,
874                                          std::string name,
875                                          const nonstd::optional<output_t> &file)
876     : sg_context(context) {
877     if (file) {
878         log_info("redirecting command output to: %s", name.c_str());
879     }
880     context.ec_output_stack.emplace_back(std::move(name), file);
881 }
882 
~output_guard()883 exec_context::output_guard::~output_guard()
884 {
885     this->sg_context.clear_output();
886     this->sg_context.ec_output_stack.pop_back();
887 }
888