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