• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tstack / lnav / 11922751379-1767

19 Nov 2024 10:13PM UTC coverage: 70.233% (+0.01%) from 70.222%
11922751379-1767

push

github

tstack
[log_format] reduce piper match quality

Sigh, I forget why I set the quality to 100...  Reducing
to 1 for now.

Related to #1339

47 of 61 new or added lines in 6 files covered. (77.05%)

18 existing lines in 2 files now uncovered.

46320 of 65952 relevant lines covered (70.23%)

467543.4 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

78.77
/src/command_executor.cc
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
25
 * ON 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 <vector>
31

32
#include "command_executor.hh"
33

34
#include "base/ansi_scrubber.hh"
35
#include "base/ansi_vars.hh"
36
#include "base/fs_util.hh"
37
#include "base/injector.hh"
38
#include "base/itertools.hh"
39
#include "base/paths.hh"
40
#include "base/string_util.hh"
41
#include "bound_tags.hh"
42
#include "config.h"
43
#include "curl_looper.hh"
44
#include "db_sub_source.hh"
45
#include "help_text_formatter.hh"
46
#include "lnav.hh"
47
#include "lnav.indexing.hh"
48
#include "lnav_config.hh"
49
#include "lnav_util.hh"
50
#include "log_format_loader.hh"
51
#include "prql-modules.h"
52
#include "readline_highlighters.hh"
53
#include "service_tags.hh"
54
#include "shlex.hh"
55
#include "sql_help.hh"
56
#include "sql_util.hh"
57
#include "vtab_module.hh"
58

59
#ifdef HAVE_RUST_DEPS
60
#    include "prqlc.cxx.hh"
61
#endif
62

63
using namespace std::literals::chrono_literals;
64
using namespace lnav::roles::literals;
65

66
exec_context INIT_EXEC_CONTEXT;
67

68
int
69
sql_progress(const struct log_cursor& lc)
4,018✔
70
{
71
    ssize_t total = lnav_data.ld_log_source.text_line_count();
4,018✔
72
    off_t off = lc.lc_curr_line;
4,018✔
73

74
    if (off < 0 || off >= total) {
4,018✔
75
        return 0;
889✔
76
    }
77

78
    if (lnav_data.ld_window == nullptr) {
3,129✔
79
        return 0;
3,120✔
80
    }
81

82
    if (!lnav_data.ld_looping) {
9✔
83
        return 1;
×
84
    }
85

86
    static sig_atomic_t sql_counter = 0;
87

88
    if (ui_periodic_timer::singleton().time_to_update(sql_counter)) {
9✔
89
        lnav_data.ld_bottom_source.update_loading(off, total);
1✔
90
        lnav_data.ld_status_refresher();
1✔
91
    }
92

93
    return 0;
9✔
94
}
95

96
void
97
sql_progress_finished()
2,414✔
98
{
99
    if (lnav_data.ld_window == nullptr) {
2,414✔
100
        return;
853✔
101
    }
102

103
    lnav_data.ld_bottom_source.update_loading(0, 0);
1,561✔
104
    lnav_data.ld_status_refresher();
1,561✔
105
    lnav_data.ld_views[LNV_DB].redo_search();
1,561✔
106
}
107

108
static Result<std::string, lnav::console::user_message> execute_from_file(
109
    exec_context& ec,
110
    const std::string& src,
111
    int line_number,
112
    const std::string& cmdline);
113

114
Result<std::string, lnav::console::user_message>
115
execute_command(exec_context& ec, const std::string& cmdline)
595✔
116
{
117
    std::vector<std::string> args;
595✔
118

119
    log_info("Executing: %s", cmdline.c_str());
595✔
120

121
    split_ws(cmdline, args);
595✔
122

123
    if (!args.empty()) {
595✔
124
        readline_context::command_map_t::iterator iter;
595✔
125

126
        if ((iter = lnav_commands.find(args[0])) == lnav_commands.end()) {
595✔
127
            return ec.make_error("unknown command - {}", args[0]);
×
128
        }
129

130
        ec.ec_current_help = &iter->second->c_help;
595✔
131
        auto retval = iter->second->c_func(ec, cmdline, args);
595✔
132
        if (retval.isErr()) {
595✔
133
            auto um = retval.unwrapErr();
38✔
134

135
            ec.add_error_context(um);
38✔
136
            ec.ec_current_help = nullptr;
38✔
137
            return Err(um);
76✔
138
        }
38✔
139
        ec.ec_current_help = nullptr;
557✔
140

141
        return retval;
557✔
142
    }
595✔
143

144
    return ec.make_error("no command to execute");
×
145
}
595✔
146

147
static Result<std::map<std::string, scoped_value_t>,
148
              lnav::console::user_message>
149
bind_sql_parameters(exec_context& ec, sqlite3_stmt* stmt)
2,406✔
150
{
151
    std::map<std::string, scoped_value_t> retval;
2,406✔
152
    auto param_count = sqlite3_bind_parameter_count(stmt);
2,406✔
153
    for (int lpc = 0; lpc < param_count; lpc++) {
2,525✔
154
        std::map<std::string, std::string>::iterator ov_iter;
120✔
155
        const auto* name = sqlite3_bind_parameter_name(stmt, lpc + 1);
120✔
156
        if (name == nullptr) {
120✔
157
            auto um
158
                = lnav::console::user_message::error("invalid SQL statement")
2✔
159
                      .with_reason(
2✔
160
                          "using a question-mark (?) for bound variables "
161
                          "is not supported, only named bound parameters "
162
                          "are supported")
163
                      .with_help(
2✔
164
                          "named parameters start with a dollar-sign "
165
                          "($) or colon (:) followed by the variable name")
166
                      .move();
1✔
167
            ec.add_error_context(um);
1✔
168

169
            return Err(um);
2✔
170
        }
1✔
171

172
        if (name[0] == '$') {
119✔
173
            const auto& lvars = ec.ec_local_vars.top();
90✔
174
            const auto& gvars = ec.ec_global_vars;
90✔
175
            std::map<std::string, scoped_value_t>::const_iterator local_var,
90✔
176
                global_var;
90✔
177
            const char* env_value;
178

179
            if (lnav_data.ld_window) {
90✔
180
                char buf[32];
181
                int lines, cols;
182

183
                getmaxyx(lnav_data.ld_window, lines, cols);
60✔
184
                if (strcmp(name, "$LINES") == 0) {
60✔
185
                    snprintf(buf, sizeof(buf), "%d", lines);
×
186
                    sqlite3_bind_text(stmt, lpc + 1, buf, -1, SQLITE_TRANSIENT);
×
187
                } else if (strcmp(name, "$COLS") == 0) {
60✔
188
                    snprintf(buf, sizeof(buf), "%d", cols);
×
189
                    sqlite3_bind_text(stmt, lpc + 1, buf, -1, SQLITE_TRANSIENT);
×
190
                }
191
            }
192

193
            if ((local_var = lvars.find(&name[1])) != lvars.end()) {
90✔
194
                mapbox::util::apply_visitor(
52✔
195
                    sqlitepp::bind_visitor(stmt, lpc + 1), local_var->second);
52✔
196
                retval[name] = local_var->second;
52✔
197
            } else if ((global_var = gvars.find(&name[1])) != gvars.end()) {
38✔
198
                mapbox::util::apply_visitor(
18✔
199
                    sqlitepp::bind_visitor(stmt, lpc + 1), global_var->second);
18✔
200
                retval[name] = global_var->second;
18✔
201
            } else if ((env_value = getenv(&name[1])) != nullptr) {
20✔
202
                sqlite3_bind_text(stmt, lpc + 1, env_value, -1, SQLITE_STATIC);
3✔
203
                retval[name] = env_value;
3✔
204
            }
205
        } else if (name[0] == ':' && ec.ec_line_values != nullptr) {
29✔
206
            for (auto& lv : ec.ec_line_values->lvv_values) {
159✔
207
                if (lv.lv_meta.lvm_name != &name[1]) {
130✔
208
                    continue;
101✔
209
                }
210
                switch (lv.lv_meta.lvm_kind) {
29✔
211
                    case value_kind_t::VALUE_BOOLEAN:
×
212
                        sqlite3_bind_int64(stmt, lpc + 1, lv.lv_value.i);
×
213
                        retval[name] = fmt::to_string(lv.lv_value.i);
×
214
                        break;
×
215
                    case value_kind_t::VALUE_FLOAT:
×
216
                        sqlite3_bind_double(stmt, lpc + 1, lv.lv_value.d);
×
217
                        retval[name] = fmt::to_string(lv.lv_value.d);
×
218
                        break;
×
219
                    case value_kind_t::VALUE_INTEGER:
×
220
                        sqlite3_bind_int64(stmt, lpc + 1, lv.lv_value.i);
×
221
                        retval[name] = fmt::to_string(lv.lv_value.i);
×
222
                        break;
×
223
                    case value_kind_t::VALUE_NULL:
×
224
                        sqlite3_bind_null(stmt, lpc + 1);
×
225
                        retval[name] = db_label_source::NULL_STR;
×
226
                        break;
×
227
                    default:
29✔
228
                        sqlite3_bind_text(stmt,
29✔
229
                                          lpc + 1,
230
                                          lv.text_value(),
231
                                          lv.text_length(),
29✔
232
                                          SQLITE_TRANSIENT);
233
                        retval[name] = lv.to_string();
29✔
234
                        break;
29✔
235
                }
236
            }
237
        } else {
29✔
238
            sqlite3_bind_null(stmt, lpc + 1);
×
239
            log_warning("Could not bind variable: %s", name);
×
240
            retval[name] = db_label_source::NULL_STR;
×
241
        }
242
    }
243

244
    return Ok(retval);
4,810✔
245
}
2,406✔
246

247
static void
248
execute_search(const std::string& search_cmd)
6✔
249
{
250
    textview_curses* tc = get_textview_for_mode(lnav_data.ld_mode);
6✔
251
    auto search_term = string_fragment(search_cmd)
6✔
252
                           .find_right_boundary(0, string_fragment::tag1{'\n'})
6✔
253
                           .to_string();
6✔
254
    tc->execute_search(search_term);
6✔
255
}
6✔
256

257
Result<std::string, lnav::console::user_message>
258
execute_sql(exec_context& ec, const std::string& sql, std::string& alt_msg)
2,419✔
259
{
260
    db_label_source& dls = *(ec.ec_label_source_stack.back());
2,419✔
261
    auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
2,419✔
262
    struct timeval start_tv, end_tv;
263
    std::string stmt_str = trim(sql);
2,419✔
264
    std::string retval;
2,419✔
265
    int retcode = SQLITE_OK;
2,419✔
266

267
    if (lnav::sql::is_prql(stmt_str)) {
2,419✔
268
        log_info("compiling PRQL: %s", stmt_str.c_str());
259✔
269

270
#if HAVE_RUST_DEPS
271
        auto opts = prqlc::Options{true, "sql.sqlite", true};
259✔
272

273
        auto tree = sqlite_extension_prql;
259✔
274
        for (const auto& mod : lnav_prql_modules) {
777✔
275
            tree.emplace_back(prqlc::SourceTreeElement{
1,036✔
276
                mod.get_name(),
277
                mod.to_string_fragment().to_string(),
1,036✔
278
            });
279
        }
280
        tree.emplace_back(prqlc::SourceTreeElement{"", stmt_str});
259✔
281
        auto cr = prqlc::compile_tree(tree, opts);
259✔
282

283
        for (const auto& msg : cr.messages) {
259✔
284
            if (msg.kind != prqlc::MessageKind::Error) {
1✔
285
                continue;
×
286
            }
287

288
            auto stmt_al = attr_line_t(stmt_str);
1✔
289
            readline_sqlite_highlighter(stmt_al, 0);
1✔
290
            auto um
291
                = lnav::console::user_message::error(
1✔
292
                      attr_line_t("unable to compile PRQL: ").append(stmt_al))
1✔
293
                      .with_reason(
1✔
294
                          attr_line_t::from_ansi_str((std::string) msg.reason));
3✔
295
            if (!msg.display.empty()) {
1✔
296
                um.with_note(
1✔
297
                    attr_line_t::from_ansi_str((std::string) msg.display));
2✔
298
            }
299
            for (const auto& hint : msg.hints) {
1✔
300
                um.with_help(attr_line_t::from_ansi_str((std::string) hint));
×
301
                break;
×
302
            }
303
            return Err(um);
2✔
304
        }
1✔
305
        stmt_str = (std::string) cr.output;
258✔
306
#else
307
        auto um = lnav::console::user_message::error(
308
            attr_line_t("PRQL is not supported in this build"));
309
        return Err(um);
310
#endif
311
    }
261✔
312

313
    log_info("Executing SQL: %s", stmt_str.c_str());
2,418✔
314

315
    auto old_mode = lnav_data.ld_mode;
2,418✔
316
    lnav_data.ld_mode = ln_mode_t::BUSY;
2,418✔
317
    auto mode_fin = finally([old_mode]() { lnav_data.ld_mode = old_mode; });
2,418✔
318
    lnav_data.ld_bottom_source.grep_error("");
2,418✔
319

320
    if (startswith(stmt_str, ".")) {
2,418✔
321
        std::vector<std::string> args;
5✔
322
        split_ws(stmt_str, args);
5✔
323

324
        const auto* sql_cmd_map
325
            = injector::get<readline_context::command_map_t*,
326
                            sql_cmd_map_tag>();
5✔
327
        auto cmd_iter = sql_cmd_map->find(args[0]);
5✔
328

329
        if (cmd_iter != sql_cmd_map->end()) {
5✔
330
            ec.ec_current_help = &cmd_iter->second->c_help;
5✔
331
            auto retval = cmd_iter->second->c_func(ec, stmt_str, args);
5✔
332
            ec.ec_current_help = nullptr;
5✔
333

334
            return retval;
5✔
335
        }
5✔
336
    }
5✔
337

338
    ec.ec_accumulator->clear();
2,413✔
339

340
    const auto& source = ec.ec_source.back();
2,413✔
341
    sql_progress_guard progress_guard(sql_progress,
342
                                      sql_progress_finished,
343
                                      source.s_location,
344
                                      source.s_content);
2,413✔
345
    gettimeofday(&start_tv, nullptr);
2,413✔
346

347
    const auto* curr_stmt = stmt_str.c_str();
2,413✔
348
    auto last_is_readonly = false;
2,413✔
349
    while (curr_stmt != nullptr) {
4,751✔
350
        const char* tail = nullptr;
4,751✔
351
        while (isspace(*curr_stmt)) {
4,751✔
352
            curr_stmt += 1;
×
353
        }
354
        retcode = sqlite3_prepare_v2(
4,751✔
355
            lnav_data.ld_db.in(), curr_stmt, -1, stmt.out(), &tail);
356
        if (retcode != SQLITE_OK) {
4,751✔
357
            const char* errmsg = sqlite3_errmsg(lnav_data.ld_db);
7✔
358

359
            alt_msg = "";
7✔
360

361
            auto um = lnav::console::user_message::error(
14✔
362
                          "failed to compile SQL statement")
363
                          .with_reason(errmsg)
14✔
364
                          .with_snippets(ec.ec_source)
14✔
365
                          .move();
7✔
366

367
            auto annotated_sql = annotate_sql_with_error(
368
                lnav_data.ld_db.in(), curr_stmt, tail);
7✔
369
            auto loc = um.um_snippets.back().s_location;
7✔
370
            if (curr_stmt == stmt_str.c_str()) {
7✔
371
                um.um_snippets.pop_back();
7✔
372
            } else {
373
                auto tail_iter = stmt_str.begin();
×
374

375
                std::advance(tail_iter, (curr_stmt - stmt_str.c_str()));
×
376
                loc.sl_line_number
377
                    += std::count(stmt_str.begin(), tail_iter, '\n');
×
378
            }
379

380
            um.with_snippet(lnav::console::snippet::from(loc, annotated_sql));
7✔
381

382
            return Err(um);
14✔
383
        }
7✔
384
        if (stmt == nullptr) {
4,744✔
385
            retcode = SQLITE_DONE;
2,338✔
386
            break;
2,338✔
387
        }
388
#ifdef HAVE_SQLITE3_STMT_READONLY
389
        last_is_readonly = sqlite3_stmt_readonly(stmt.in());
2,406✔
390
        if (ec.is_read_only() && !last_is_readonly) {
2,406✔
391
            return ec.make_error(
392
                "modifying statements are not allowed in this context: {}",
393
                sql);
×
394
        }
395
#endif
396
        bool done = false;
2,406✔
397

398
        auto bound_values = TRY(bind_sql_parameters(ec, stmt.in()));
2,407✔
399
        ec.ec_sql_callback(ec, stmt.in());
2,405✔
400
        while (!done) {
7,926✔
401
            retcode = sqlite3_step(stmt.in());
5,588✔
402

403
            switch (retcode) {
5,588✔
404
                case SQLITE_OK:
2,338✔
405
                case SQLITE_DONE: {
406
                    auto changes = sqlite3_changes(lnav_data.ld_db.in());
2,338✔
407

408
                    log_info("sqlite3_changes() -> %d", changes);
2,338✔
409
                    done = true;
2,338✔
410
                    break;
2,338✔
411
                }
412

413
                case SQLITE_ROW:
3,183✔
414
                    ec.ec_sql_callback(ec, stmt.in());
3,183✔
415
                    break;
3,183✔
416

417
                default: {
67✔
418
                    attr_line_t bound_note;
67✔
419

420
                    if (!bound_values.empty()) {
67✔
421
                        bound_note.append(
1✔
422
                            "the bound parameters are set as follows:\n");
423
                        for (const auto& bval : bound_values) {
5✔
424
                            auto val_as_str = fmt::to_string(bval.second);
4✔
425
                            auto sql_type = bval.second.match(
4✔
426
                                [](const std::string&) { return SQLITE_TEXT; },
1✔
427
                                [](const string_fragment&) {
×
428
                                    return SQLITE_TEXT;
×
429
                                },
430
                                [](int64_t) { return SQLITE_INTEGER; },
1✔
431
                                [](null_value_t) { return SQLITE_NULL; },
1✔
432
                                [](double) { return SQLITE_FLOAT; });
1✔
433
                            auto scrubbed_val = scrub_ws(val_as_str.c_str());
4✔
434
                            truncate_to(scrubbed_val, 40);
4✔
435
                            bound_note.append("  ")
4✔
436
                                .append(lnav::roles::variable(bval.first))
8✔
437
                                .append(":")
4✔
438
                                .append(sqlite3_type_to_string(sql_type))
4✔
439
                                .append(" = ")
4✔
440
                                .append_quoted(scrubbed_val)
8✔
441
                                .append("\n");
4✔
442
                        }
4✔
443
                    }
444

445
                    log_error("sqlite3_step error code: %d", retcode);
67✔
446
                    auto um = sqlite3_error_to_user_message(lnav_data.ld_db)
67✔
447
                                  .with_context_snippets(ec.ec_source)
134✔
448
                                  .with_note(bound_note)
67✔
449
                                  .move();
67✔
450

451
                    return Err(um);
134✔
452
                }
67✔
453
            }
454
        }
455

456
        curr_stmt = tail;
2,338✔
457
    }
2,405✔
458

459
    if (lnav_data.ld_rl_view != nullptr) {
2,338✔
460
        lnav_data.ld_rl_view->clear_value();
1,543✔
461
    }
462

463
    gettimeofday(&end_tv, nullptr);
2,338✔
464
    if (retcode == SQLITE_DONE) {
2,338✔
465
        if (lnav_data.ld_log_source.is_line_meta_changed()) {
2,338✔
466
            lnav_data.ld_log_source.text_filters_changed();
8✔
467
            lnav_data.ld_views[LNV_LOG].reload_data();
8✔
468
        }
469
        lnav_data.ld_filter_view.reload_data();
2,338✔
470
        lnav_data.ld_files_view.reload_data();
2,338✔
471

472
        lnav_data.ld_active_files.fc_files
473
            | lnav::itertools::for_each(&logfile::dump_stats);
2,338✔
474
        if (ec.ec_sql_callback != sql_callback) {
2,338✔
475
            retval = ec.ec_accumulator->get_string();
72✔
476
        } else if (!dls.dls_rows.empty()) {
2,266✔
477
            lnav_data.ld_views[LNV_DB].reload_data();
2,159✔
478
            lnav_data.ld_views[LNV_DB].set_left(0);
2,159✔
479
            if (lnav_data.ld_flags & LNF_HEADLESS) {
2,159✔
480
                if (ec.ec_local_vars.size() == 1) {
175✔
481
                    ensure_view(&lnav_data.ld_views[LNV_DB]);
170✔
482
                }
483

484
                retval = "";
175✔
485
                alt_msg = "";
175✔
486
            } else if (dls.dls_rows.size() == 1) {
1,984✔
487
                auto& row = dls.dls_rows[0];
1,800✔
488

489
                if (dls.dls_headers.size() == 1) {
1,800✔
490
                    retval = row[0];
1,768✔
491
                } else {
492
                    for (unsigned int lpc = 0; lpc < dls.dls_headers.size();
128✔
493
                         lpc++)
494
                    {
495
                        if (lpc > 0) {
96✔
496
                            retval.append("; ");
64✔
497
                        }
498
                        retval.append(dls.dls_headers[lpc].hm_name);
96✔
499
                        retval.push_back('=');
96✔
500
                        retval.append(row[lpc]);
96✔
501
                    }
502
                }
503
            } else {
504
                int row_count = dls.dls_rows.size();
184✔
505
                char row_count_buf[128];
506
                struct timeval diff_tv;
507

508
                timersub(&end_tv, &start_tv, &diff_tv);
184✔
509
                snprintf(row_count_buf,
184✔
510
                         sizeof(row_count_buf),
511
                         ANSI_BOLD("%'d") " row%s matched in " ANSI_BOLD(
512
                             "%ld.%03ld") " seconds",
513
                         row_count,
514
                         row_count == 1 ? "" : "s",
515
                         diff_tv.tv_sec,
516
                         std::max((long) diff_tv.tv_usec / 1000, 1L));
184✔
517
                retval = row_count_buf;
184✔
518
                if (dls.has_log_time_column()) {
184✔
519
                    alt_msg = HELP_MSG_1(Q,
120✔
520
                                         "to switch back to the previous view "
521
                                         "at the matching 'log_time' value");
522
                } else {
523
                    alt_msg = "";
64✔
524
                }
525
            }
526
        }
527
#ifdef HAVE_SQLITE3_STMT_READONLY
528
        else if (last_is_readonly)
107✔
529
        {
530
            retval = "info: No rows matched";
28✔
531
            alt_msg = "";
28✔
532

533
            if (lnav_data.ld_flags & LNF_HEADLESS) {
28✔
534
                if (ec.ec_local_vars.size() == 1) {
20✔
535
                    lnav_data.ld_views[LNV_DB].reload_data();
14✔
536
                    ensure_view(&lnav_data.ld_views[LNV_DB]);
14✔
537
                }
538
            }
539
        }
540
#endif
541
    }
542

543
    return Ok(retval);
4,676✔
544
}
2,419✔
545

546
Result<void, lnav::console::user_message>
547
multiline_executor::push_back(string_fragment line)
280✔
548
{
549
    this->me_line_number += 1;
280✔
550

551
    if (line.trim().empty()) {
280✔
552
        if (this->me_cmdline) {
43✔
553
            this->me_cmdline = this->me_cmdline.value() + "\n";
25✔
554
        }
555
        return Ok();
43✔
556
    }
557
    if (line[0] == '#') {
237✔
558
        return Ok();
60✔
559
    }
560

561
    switch (line[0]) {
177✔
562
        case ':':
114✔
563
        case '/':
564
        case ';':
565
        case '|':
566
            if (this->me_cmdline) {
114✔
567
                this->me_last_result
568
                    = TRY(execute_from_file(this->me_exec_context,
249✔
569
                                            this->me_source,
570
                                            this->me_starting_line_number,
571
                                            trim(this->me_cmdline.value())));
572
            }
573

574
            this->me_starting_line_number = this->me_line_number;
112✔
575
            this->me_cmdline = line.to_string();
112✔
576
            break;
112✔
577
        default:
63✔
578
            if (this->me_cmdline) {
63✔
579
                this->me_cmdline = fmt::format(
126✔
580
                    FMT_STRING("{}{}"), this->me_cmdline.value(), line);
315✔
581
            } else {
63✔
582
                this->me_last_result = TRY(
63✔
583
                    execute_from_file(this->me_exec_context,
584
                                      this->me_source,
×
585
                                      this->me_line_number,
×
586
                                      fmt::format(FMT_STRING(":{}"), line)));
×
587
            }
588
            break;
589
    }
590

591
    return Ok();
592
}
63✔
593

594
Result<std::string, lnav::console::user_message>
595
multiline_executor::final()
175✔
596
{
597
    if (this->me_cmdline) {
598
        this->me_last_result
599
            = TRY(execute_from_file(this->me_exec_context,
29✔
600
                                    this->me_source,
601
                                    this->me_starting_line_number,
29✔
602
                                    trim(this->me_cmdline.value())));
603
    }
87✔
604

605
    return Ok(this->me_last_result);
606
}
607

608
static Result<std::string, lnav::console::user_message>
609
execute_file_contents(exec_context& ec, const std::filesystem::path& path)
58✔
610
{
611
    static const std::filesystem::path stdin_path("-");
612
    static const std::filesystem::path dev_stdin_path("/dev/stdin");
613

24✔
614
    FILE* file;
615

24✔
616
    if (path == stdin_path || path == dev_stdin_path) {
24✔
617
        if (isatty(STDIN_FILENO)) {
618
            return ec.make_error("stdin has already been consumed");
619
        }
620
        file = stdin;
24✔
621
    } else if ((file = fopen(path.c_str(), "re")) == nullptr) {
2✔
622
        return ec.make_error("unable to open file");
×
623
    }
624

2✔
625
    auto_mem<char> line;
22✔
626
    size_t line_max_size;
×
627
    ssize_t line_size;
628
    multiline_executor me(ec, path.string());
629

24✔
630
    ec.ec_local_vars.top()["0"] = path.string();
631
    ec.ec_path_stack.emplace_back(path.parent_path());
632
    exec_context::output_guard og(ec);
24✔
633
    while ((line_size = getline(line.out(), &line_max_size, file)) != -1) {
634
        TRY(me.push_back(string_fragment::from_bytes(line.in(), line_size)));
24✔
635
    }
24✔
636

48✔
637
    auto retval = TRY(me.final());
294✔
638

274✔
639
    if (file == stdin) {
640
        if (isatty(STDOUT_FILENO)) {
641
            log_perror(dup2(STDOUT_FILENO, STDIN_FILENO));
22✔
642
        }
643
    } else {
22✔
644
        fclose(file);
2✔
645
    }
×
646
    ec.ec_path_stack.pop_back();
647

648
    return Ok(retval);
20✔
649
}
650

22✔
651
Result<std::string, lnav::console::user_message>
652
execute_file(exec_context& ec, const std::string& path_and_args)
44✔
653
{
24✔
654
    static const intern_string_t SRC = intern_string::lookup("cmdline");
655

656
    available_scripts scripts;
31✔
657
    std::string retval, msg;
658
    shlex lexer(path_and_args);
31✔
659

660
    log_info("Executing file: %s", path_and_args.c_str());
31✔
661

31✔
662
    auto split_args_res = lexer.split(scoped_resolver{&ec.ec_local_vars.top()});
31✔
663
    if (split_args_res.isErr()) {
664
        auto split_err = split_args_res.unwrapErr();
31✔
665
        auto um = lnav::console::user_message::error(
666
                      "unable to parse script command-line")
31✔
667
                      .with_reason(split_err.te_msg)
31✔
668
                      .with_snippet(lnav::console::snippet::from(
×
669
                          SRC, lexer.to_attr_line(split_err)))
×
670
                      .move();
671

×
672
        return Err(um);
×
673
    }
×
674
    auto split_args = split_args_res.unwrap();
×
675
    if (split_args.empty()) {
676
        return ec.make_error("no script specified");
×
677
    }
678

31✔
679
    ec.ec_local_vars.push({});
31✔
680

×
681
    auto script_name = split_args[0].se_value;
682
    auto& vars = ec.ec_local_vars.top();
683
    std::string star, open_error = "file not found";
31✔
684

685
    add_ansi_vars(vars);
31✔
686

31✔
687
    vars["#"] = fmt::to_string(split_args.size() - 1);
31✔
688
    for (size_t lpc = 0; lpc < split_args.size(); lpc++) {
689
        vars[fmt::to_string(lpc)] = split_args[lpc].se_value;
31✔
690
    }
691
    for (size_t lpc = 1; lpc < split_args.size(); lpc++) {
31✔
692
        if (lpc > 1) {
78✔
693
            star.append(" ");
47✔
694
        }
695
        star.append(split_args[lpc].se_value);
47✔
696
    }
16✔
697
    vars["__all__"] = star;
3✔
698

699
    std::vector<script_metadata> paths_to_exec;
16✔
700

701
    find_format_scripts(lnav_data.ld_config_paths, scripts);
31✔
702
    auto iter = scripts.as_scripts.find(script_name);
703
    if (iter != scripts.as_scripts.end()) {
31✔
704
        paths_to_exec = iter->second;
705
    }
31✔
706
    if (is_url(script_name)) {
31✔
707
        auto_mem<CURLU> cu(curl_url_cleanup);
31✔
708
        cu = curl_url();
17✔
709
        auto set_rc = curl_url_set(cu, CURLUPART_URL, script_name.c_str(), 0);
710
        if (set_rc == CURLUE_OK) {
31✔
711
            auto_mem<char> scheme_part(curl_free);
×
712
            auto get_rc
×
713
                = curl_url_get(cu, CURLUPART_SCHEME, scheme_part.out(), 0);
×
714
            if (get_rc == CURLUE_OK
×
715
                && string_fragment::from_c_str(scheme_part.in()) == "file")
×
716
            {
717
                auto_mem<char> path_part;
×
718
                auto get_rc
×
719
                    = curl_url_get(cu, CURLUPART_PATH, path_part.out(), 0);
×
720
                if (get_rc == CURLUE_OK) {
721
                    auto rp_res = lnav::filesystem::realpath(path_part.in());
×
722
                    if (rp_res.isOk()) {
723
                        struct script_metadata meta;
×
724

×
725
                        meta.sm_path = rp_res.unwrap();
×
726
                        extract_metadata_from_file(meta);
×
727
                        paths_to_exec.push_back(meta);
×
728
                    }
729
                }
×
730
            }
×
731
        }
×
732
    }
733
    if (script_name == "-" || script_name == "/dev/stdin") {
734
        paths_to_exec.push_back({script_name, "", "", ""});
735
    } else if (access(script_name.c_str(), R_OK) == 0) {
736
        struct script_metadata meta;
737
        auto rp_res = lnav::filesystem::realpath(script_name);
31✔
738

2✔
739
        if (rp_res.isErr()) {
29✔
740
            log_error("unable to get realpath() of %s -- %s",
5✔
741
                      script_name.c_str(),
5✔
742
                      rp_res.unwrapErr().c_str());
743
            meta.sm_path = script_name;
5✔
744
        } else {
×
745
            meta.sm_path = rp_res.unwrap();
746
        }
747
        extract_metadata_from_file(meta);
×
748
        paths_to_exec.push_back(meta);
749
    } else if (errno != ENOENT) {
5✔
750
        open_error = strerror(errno);
751
    } else {
5✔
752
        auto script_path = std::filesystem::path(script_name);
5✔
753

29✔
754
        if (!script_path.is_absolute()) {
×
755
            script_path = ec.ec_path_stack.back() / script_path;
756
        }
24✔
757

758
        if (std::filesystem::is_regular_file(script_path)) {
24✔
759
            struct script_metadata meta;
24✔
760

761
            meta.sm_path = script_path;
762
            extract_metadata_from_file(meta);
24✔
763
            paths_to_exec.push_back(meta);
×
764
        } else if (errno != ENOENT) {
765
            open_error = strerror(errno);
×
766
        }
×
767
    }
×
768

24✔
769
    if (!paths_to_exec.empty()) {
×
770
        for (auto& path_iter : paths_to_exec) {
771
            retval = TRY(execute_file_contents(ec, path_iter.sm_path));
24✔
772
        }
773
    }
31✔
774
    ec.ec_local_vars.pop();
46✔
775

48✔
776
    if (paths_to_exec.empty()) {
777
        return ec.make_error(
778
            "unknown script -- {} -- {}", script_name, open_error);
29✔
779
    }
780

29✔
781
    return Ok(retval);
782
}
7✔
783

784
Result<std::string, lnav::console::user_message>
785
execute_from_file(exec_context& ec,
44✔
786
                  const std::string& src,
31✔
787
                  int line_number,
788
                  const std::string& cmdline)
789
{
112✔
790
    std::string retval, alt_msg;
791
    auto _sg
792
        = ec.enter_source(intern_string::lookup(src), line_number, cmdline);
793

794
    switch (cmdline[0]) {
112✔
795
        case ':':
796
            retval = TRY(execute_command(ec, cmdline.substr(1)));
112✔
797
            break;
798
        case '/':
112✔
799
            execute_search(cmdline.substr(1));
48✔
800
            break;
144✔
801
        case ';':
48✔
802
            setup_logline_table(ec);
1✔
803
            retval = TRY(execute_sql(ec, cmdline.substr(1), alt_msg));
1✔
804
            break;
1✔
805
        case '|':
61✔
806
            retval = TRY(execute_file(ec, cmdline.substr(1)));
61✔
807
            break;
183✔
808
        default:
59✔
809
            retval = TRY(execute_command(ec, cmdline));
2✔
810
            break;
6✔
811
    }
2✔
812

×
813
    log_info(
×
814
        "%s:%d:execute result -- %s", src.c_str(), line_number, retval.c_str());
×
815

816
    return Ok(retval);
817
}
110✔
818

819
Result<std::string, lnav::console::user_message>
820
execute_any(exec_context& ec, const std::string& cmdline_with_mode)
220✔
821
{
112✔
822
    if (cmdline_with_mode.empty()) {
823
        auto um = lnav::console::user_message::error("empty command")
824
                      .with_help(
45✔
825
                          "a command should start with ':', ';', '/', '|' and "
826
                          "followed by the operation to perform")
45✔
827
                      .move();
×
828
        if (!ec.ec_source.empty()) {
×
829
            um.with_snippet(ec.ec_source.back());
830
        }
831
        return Err(um);
×
832
    }
×
833

×
834
    std::string retval, alt_msg, cmdline = cmdline_with_mode.substr(1);
835
    auto _cleanup = finally([&ec] {
×
836
        if (ec.is_read_write() &&
837
            // only rebuild in a script or non-interactive mode so we don't
838
            // block the UI.
45✔
839
            lnav_data.ld_flags & LNF_HEADLESS)
45✔
840
        {
58✔
841
            rescan_files();
842
            wait_for_pipers(std::nullopt);
843
            rebuild_indexes_repeatedly();
13✔
844
        }
845
    });
×
846

×
847
    switch (cmdline_with_mode[0]) {
×
848
        case ':':
849
            retval = TRY(execute_command(ec, cmdline));
90✔
850
            break;
851
        case '/':
45✔
852
            execute_search(cmdline);
6✔
853
            break;
12✔
854
        case ';':
6✔
855
            setup_logline_table(ec);
×
856
            retval = TRY(execute_sql(ec, cmdline, alt_msg));
×
857
            break;
×
858
        case '|': {
31✔
859
            retval = TRY(execute_file(ec, cmdline));
31✔
860
            break;
62✔
861
        }
31✔
862
        default:
8✔
863
            retval = TRY(execute_command(ec, cmdline));
16✔
864
            break;
8✔
865
    }
866

×
867
    return Ok(retval);
×
868
}
×
869

870
void
871
execute_init_commands(
90✔
872
    exec_context& ec,
45✔
873
    std::vector<std::pair<Result<std::string, lnav::console::user_message>,
874
                          std::string>>& msgs)
875
{
510✔
876
    if (lnav_data.ld_cmd_init_done) {
877
        return;
878
    }
879

880
    std::optional<exec_context::output_t> ec_out;
510✔
881
    auto_fd fd_copy;
×
882

883
    if (!(lnav_data.ld_flags & LNF_HEADLESS)) {
884
        auto_mem<FILE> tmpout(fclose);
510✔
885

510✔
886
        tmpout = std::tmpfile();
887
        if (!tmpout) {
510✔
888
            msgs.emplace_back(Err(lnav::console::user_message::error(
6✔
889
                                      "Unable to open temporary output file")
890
                                      .with_errno_reason()),
6✔
891
                              "");
6✔
892
            return;
×
893
        }
894
        fcntl(fileno(tmpout), F_SETFD, FD_CLOEXEC);
895
        fd_copy = auto_fd::dup_of(fileno(tmpout));
896
        fd_copy.close_on_exec();
×
897
        ec_out = std::make_pair(tmpout.release(), fclose);
898
    }
6✔
899

6✔
900
    auto& dls = *(ec.ec_label_source_stack.back());
6✔
901
    int option_index = 1;
6✔
902

6✔
903
    {
904
        log_info("Executing initial commands");
510✔
905
        exec_context::output_guard og(ec, "tmp", ec_out);
510✔
906

907
        for (auto& cmd : lnav_data.ld_commands) {
908
            static const auto COMMAND_OPTION_SRC
510✔
909
                = intern_string::lookup("command-option");
1,020✔
910

911
            std::string alt_msg;
1,353✔
912

913
            wait_for_children();
843✔
914

915
            lnav_data.ld_view_stack.top() |
843✔
916
                [&ec](auto* tc) { ec.ec_top_line = tc->get_selection(); };
917
            log_debug("init cmd: %s", cmd.c_str());
843✔
918
            {
919
                auto _sg
843✔
920
                    = ec.enter_source(COMMAND_OPTION_SRC, option_index++, cmd);
843✔
921
                switch (cmd.at(0)) {
843✔
922
                    case ':':
923
                        msgs.emplace_back(execute_command(ec, cmd.substr(1)),
924
                                          alt_msg);
843✔
925
                        break;
843✔
926
                    case '/':
534✔
927
                        execute_search(cmd.substr(1));
534✔
928
                        break;
929
                    case ';':
534✔
930
                        setup_logline_table(ec);
5✔
931
                        msgs.emplace_back(
5✔
932
                            execute_sql(ec, cmd.substr(1), alt_msg), alt_msg);
5✔
933
                        break;
292✔
934
                    case '|':
292✔
935
                        msgs.emplace_back(execute_file(ec, cmd.substr(1)),
292✔
936
                                          alt_msg);
584✔
937
                        break;
292✔
938
                }
12✔
939

12✔
940
                rescan_files();
941
                auto deadline = ui_clock::now();
12✔
942
                if (lnav_data.ld_flags & LNF_HEADLESS) {
943
                    deadline += 5s;
944
                } else {
843✔
945
                    deadline += 500ms;
843✔
946
                }
843✔
947
                wait_for_pipers(deadline);
842✔
948
                rebuild_indexes_repeatedly();
949
            }
1✔
950
            if (!dls.dls_headers.empty() && lnav_data.ld_view_stack.size() == 1)
951
            {
843✔
952
                lnav_data.ld_views[LNV_DB].reload_data();
843✔
953
                ensure_view(LNV_DB);
843✔
954
            }
843✔
955
        }
956
    }
12✔
957
    lnav_data.ld_commands.clear();
12✔
958

959
    struct stat st;
843✔
960

510✔
961
    if (ec_out && fstat(fd_copy, &st) != -1 && st.st_size > 0) {
510✔
962
        static const auto OUTPUT_NAME = std::string("Initial command output");
963

964
        auto create_piper_res = lnav::piper::create_looper(
965
            OUTPUT_NAME, std::move(fd_copy), auto_fd{});
510✔
966
        if (create_piper_res.isOk()) {
967
            lnav_data.ld_active_files.fc_file_names[OUTPUT_NAME]
968
                .with_piper(create_piper_res.unwrap())
969
                .with_include_in_session(false)
×
970
                .with_detect_format(false)
×
971
                .with_init_location(0_vl);
×
972
            lnav_data.ld_files_to_front.emplace_back(OUTPUT_NAME, 0_vl);
×
973

×
974
            if (lnav_data.ld_rl_view != nullptr) {
×
975
                lnav_data.ld_rl_view->set_alt_value(
×
976
                    HELP_MSG_1(X, "to close the file"));
×
977
            }
978
        }
×
979
    }
×
980

981
    lnav_data.ld_cmd_init_done = true;
982
}
983

984
int
985
sql_callback(exec_context& ec, sqlite3_stmt* stmt)
510✔
986
{
510✔
987
    const auto& vc = view_colors::singleton();
988
    auto& dls = *(ec.ec_label_source_stack.back());
989
    int ncols = sqlite3_column_count(stmt);
5,458✔
990

991
    if (!sqlite3_stmt_busy(stmt)) {
5,458✔
992
        dls.clear();
5,458✔
993

5,458✔
994
        for (int lpc = 0; lpc < ncols; lpc++) {
995
            const int type = sqlite3_column_type(stmt, lpc);
5,458✔
996
            std::string colname = sqlite3_column_name(stmt, lpc);
2,334✔
997

998
            dls.push_header(colname, type, false);
5,989✔
999
        }
3,655✔
1000
        return 0;
3,655✔
1001
    }
1002

3,655✔
1003
    int retval = 0;
3,655✔
1004
    auto set_vars = dls.dls_rows.empty();
2,334✔
1005

1006
    if (dls.dls_rows.empty()) {
1007
        for (int lpc = 0; lpc < ncols; lpc++) {
3,124✔
1008
            int type = sqlite3_column_type(stmt, lpc);
3,124✔
1009
            std::string colname = sqlite3_column_name(stmt, lpc);
1010

3,124✔
1011
            bool graphable = (type == SQLITE_INTEGER || type == SQLITE_FLOAT)
5,493✔
1012
                && !binary_search(lnav_data.ld_db_key_names.begin(),
3,333✔
1013
                                  lnav_data.ld_db_key_names.end(),
3,333✔
1014
                                  colname);
1015
            auto& hm = dls.dls_headers[lpc];
2,384✔
1016
            hm.hm_column_type = type;
5,717✔
1017
            hm.hm_graphable = graphable;
1018
            if (graphable) {
3,333✔
1019
                auto name_for_ident_attrs = colname;
3,333✔
1020
                auto attrs = vc.attrs_for_ident(name_for_ident_attrs);
3,333✔
1021
                for (size_t attempt = 0;
3,333✔
1022
                     hm.hm_chart.attrs_in_use(attrs) && attempt < 3;
3,333✔
1023
                     attempt++)
954✔
1024
                {
954✔
1025
                    name_for_ident_attrs += " ";
954✔
1026
                    attrs = vc.attrs_for_ident(name_for_ident_attrs);
954✔
1027
                }
1028
                hm.hm_chart.with_attrs_for_ident(colname, attrs);
1029
                hm.hm_title_attrs = attrs;
×
1030
                hm.hm_column_size = std::max(hm.hm_column_size, size_t{10});
×
1031
            }
1032
        }
954✔
1033
    }
954✔
1034

954✔
1035
    auto row_number = dls.dls_rows.size();
954✔
1036
    dls.dls_rows.resize(row_number + 1);
3,333✔
1037
    for (int lpc = 0; lpc < ncols; lpc++) {
1038
        auto* raw_value = sqlite3_column_value(stmt, lpc);
1039
        const auto value_type = sqlite3_value_type(raw_value);
3,124✔
1040
        scoped_value_t value;
3,124✔
1041
        auto& hm = dls.dls_headers[lpc];
11,364✔
1042

8,240✔
1043
        switch (value_type) {
8,240✔
1044
            case SQLITE_INTEGER:
8,240✔
1045
                value = (int64_t) sqlite3_value_int64(raw_value);
8,240✔
1046
                break;
1047
            case SQLITE_FLOAT:
8,240✔
1048
                value = sqlite3_value_double(raw_value);
2,425✔
1049
                break;
2,425✔
1050
            case SQLITE_NULL:
2,425✔
1051
                value = null_value_t{};
317✔
1052
                break;
317✔
1053
            default:
317✔
1054
                value = string_fragment::from_bytes(
1,732✔
1055
                    sqlite3_value_text(raw_value),
1,732✔
1056
                    sqlite3_value_bytes(raw_value));
1,732✔
1057
                break;
3,766✔
1058
        }
3,766✔
1059
        dls.push_column(value);
1060
        if ((hm.hm_column_type == SQLITE_TEXT
7,532✔
1061
             || hm.hm_column_type == SQLITE_NULL)
3,766✔
1062
            && hm.hm_sub_type == 0)
1063
        {
8,240✔
1064
            switch (value_type) {
8,240✔
1065
                case SQLITE_TEXT:
4,382✔
1066
                    hm.hm_column_type = SQLITE_TEXT;
5,570✔
1067
                    hm.hm_sub_type = sqlite3_value_subtype(raw_value);
1068
                    break;
5,427✔
1069
            }
3,654✔
1070
        }
3,654✔
1071
        if (set_vars && !ec.ec_local_vars.empty() && !ec.ec_dry_run) {
3,654✔
1072
            if (sql_ident_needs_quote(hm.hm_name.c_str())) {
3,654✔
1073
                continue;
1074
            }
1075
            auto& vars = ec.ec_local_vars.top();
8,240✔
1076

3,333✔
1077
            if (value.is<string_fragment>()) {
1,729✔
1078
                value = value.get<string_fragment>().to_string();
1079
            }
1,604✔
1080
            vars[hm.hm_name] = value;
1081
        }
1,604✔
1082
    }
717✔
1083

1084
    return retval;
1,604✔
1085
}
1086

8,240✔
1087
int
1088
internal_sql_callback(exec_context& ec, sqlite3_stmt* stmt)
3,124✔
1089
{
1090
    if (!sqlite3_stmt_busy(stmt)) {
1091
        return 0;
1092
    }
59✔
1093

1094
    const int ncols = sqlite3_column_count(stmt);
59✔
1095

40✔
1096
    auto& vars = ec.ec_local_vars.top();
1097

1098
    for (int lpc = 0; lpc < ncols; lpc++) {
19✔
1099
        const char* column_name = sqlite3_column_name(stmt, lpc);
1100

19✔
1101
        if (sql_ident_needs_quote(column_name)) {
1102
            continue;
51✔
1103
        }
32✔
1104

1105
        auto* raw_value = sqlite3_column_value(stmt, lpc);
32✔
NEW
1106
        auto value_type = sqlite3_column_type(stmt, lpc);
×
1107
        scoped_value_t value;
1108
        switch (value_type) {
1109
            case SQLITE_INTEGER:
32✔
1110
                value = (int64_t) sqlite3_value_int64(raw_value);
32✔
1111
                break;
32✔
1112
            case SQLITE_FLOAT:
32✔
1113
                value = sqlite3_value_double(raw_value);
6✔
1114
                break;
6✔
1115
            case SQLITE_NULL:
6✔
NEW
1116
                value = null_value_t{};
×
NEW
1117
                break;
×
NEW
1118
            default:
×
1119
                value = std::string((const char*) sqlite3_value_text(raw_value),
7✔
1120
                                    sqlite3_value_bytes(raw_value));
7✔
1121
                break;
7✔
1122
        }
19✔
1123
        vars[column_name] = value;
57✔
1124
    }
38✔
1125

19✔
1126
    return 0;
1127
}
32✔
1128

32✔
1129
std::future<std::string>
1130
pipe_callback(exec_context& ec, const std::string& cmdline, auto_fd& fd)
19✔
1131
{
1132
    auto out = ec.get_output();
1133

1134
    if (out) {
7✔
1135
        FILE* file = *out;
1136

7✔
1137
        return std::async(std::launch::async, [fd = std::move(fd), file]() {
1138
            char buffer[1024];
7✔
1139
            ssize_t rc;
7✔
1140

1141
            if (file == stdout) {
7✔
1142
                lnav_data.ld_stdout_used = true;
1143
            }
1144

1145
            while ((rc = read(fd, buffer, sizeof(buffer))) > 0) {
7✔
1146
                fwrite(buffer, rc, 1, file);
7✔
1147
            }
1148

1149
            return std::string();
14✔
1150
        });
7✔
1151
    }
1152
    std::error_code errc;
1153
    std::filesystem::create_directories(lnav::paths::workdir(), errc);
14✔
1154
    auto open_temp_res = lnav::filesystem::open_temp_file(lnav::paths::workdir()
7✔
1155
                                                          / "exec.XXXXXX");
1156
    if (open_temp_res.isErr()) {
×
1157
        return lnav::futures::make_ready_future(
×
1158
            fmt::format(FMT_STRING("error: cannot open temp file -- {}"),
×
1159
                        open_temp_res.unwrapErr()));
×
1160
    }
×
1161

1162
    auto tmp_pair = open_temp_res.unwrap();
×
1163

×
1164
    auto reader = std::thread(
×
1165
        [in_fd = std::move(fd), out_fd = std::move(tmp_pair.second)]() {
×
1166
            char buffer[1024];
1167
            ssize_t rc;
1168

×
1169
            while ((rc = read(in_fd, buffer, sizeof(buffer))) > 0) {
1170
                write(out_fd, buffer, rc);
1171
            }
×
1172
        });
1173
    reader.detach();
1174

1175
    static int exec_count = 0;
×
1176
    auto desc
×
1177
        = fmt::format(FMT_STRING("exec-{}-output {}"), exec_count++, cmdline);
1178
    lnav_data.ld_active_files.fc_file_names[tmp_pair.first]
×
1179
        .with_filename(desc)
×
1180
        .with_include_in_session(false)
1181
        .with_detect_format(false)
1182
        .with_init_location(0_vl);
1183
    lnav_data.ld_files_to_front.emplace_back(desc, 0_vl);
×
1184
    if (lnav_data.ld_rl_view != nullptr) {
×
1185
        lnav_data.ld_rl_view->set_alt_value(HELP_MSG_1(X, "to close the file"));
×
1186
    }
×
1187

×
1188
    return lnav::futures::make_ready_future(std::string());
×
1189
}
×
1190

×
1191
void
×
1192
add_global_vars(exec_context& ec)
×
1193
{
×
1194
    for (const auto& iter : lnav_config.lc_global_vars) {
1195
        shlex subber(iter.second);
1196
        std::string str;
×
1197

1198
        if (!subber.eval(str, scoped_resolver{&ec.ec_global_vars})) {
1199
            log_error("Unable to evaluate global variable value: %s",
1200
                      iter.second.c_str());
572✔
1201
            continue;
1202
        }
8,586✔
1203

8,014✔
1204
        ec.ec_global_vars[iter.first] = str;
8,014✔
1205
    }
1206
}
8,014✔
1207

×
1208
void
1209
exec_context::set_output(const std::string& name,
×
1210
                         FILE* file,
1211
                         int (*closer)(FILE*))
1212
{
8,014✔
1213
    log_info("redirecting command output to: %s", name.c_str());
8,014✔
1214
    this->ec_output_stack.back().second | [](auto out) {
572✔
1215
        if (out.second != nullptr) {
1216
            out.second(out.first);
1217
        }
506✔
1218
    };
1219
    this->ec_output_stack.back()
1220
        = std::make_pair(name, std::make_pair(file, closer));
1221
}
506✔
1222

506✔
1223
void
×
1224
exec_context::clear_output()
×
1225
{
1226
    log_info("redirecting command output to screen");
1227
    this->ec_output_stack.back().second | [](auto out) {
506✔
1228
        if (out.second != nullptr) {
1,012✔
1229
            out.second(out.first);
506✔
1230
        }
1231
    };
1232
    this->ec_output_stack.back() = std::make_pair("default", std::nullopt);
568✔
1233
}
1234

568✔
1235
exec_context::
568✔
1236
exec_context(logline_value_vector* line_values,
40✔
1237
             sql_callback_t sql_callback,
40✔
1238
             pipe_callback_t pipe_callback)
1239
    : ec_line_values(line_values),
40✔
1240
      ec_accumulator(std::make_unique<attr_line_t>()),
568✔
1241
      ec_sql_callback(sql_callback), ec_pipe_callback(pipe_callback)
568✔
1242
{
1243
    static const auto COMMAND_SRC = intern_string::lookup("command");
4,135✔
1244

1245
    this->ec_local_vars.push(std::map<std::string, scoped_value_t>());
1246
    this->ec_path_stack.emplace_back(".");
4,135✔
1247
    this->ec_source.emplace_back(
4,135✔
1248
        lnav::console::snippet::from(COMMAND_SRC, "").with_line(1));
4,135✔
1249
    this->ec_output_stack.emplace_back("screen", std::nullopt);
8,270✔
1250
    this->ec_error_callback_stack.emplace_back(
1251
        [](const auto& um) { lnav::console::print(stderr, um); });
4,135✔
1252
}
1253

4,135✔
1254
Result<std::string, lnav::console::user_message>
4,135✔
1255
exec_context::execute(const std::string& cmdline)
4,135✔
1256
{
8,270✔
1257
    if (this->get_provenance<mouse_input>()) {
4,135✔
1258
        require(!lnav_data.ld_rl_view->is_active());
4,135✔
1259

×
1260
        int context = 0;
4,135✔
1261
        switch (cmdline[0]) {
1262
            case '/':
1263
                context = lnav::enums::to_underlying(ln_mode_t::SEARCH);
×
1264
                break;
1265
            case ':':
×
1266
                context = lnav::enums::to_underlying(ln_mode_t::COMMAND);
×
1267
                break;
1268
            case ';':
×
1269
                context = lnav::enums::to_underlying(ln_mode_t::SQL);
×
1270
                break;
×
1271
            case '|':
×
1272
                context = lnav::enums::to_underlying(ln_mode_t::EXEC);
×
1273
                break;
×
1274
        }
×
1275

×
1276
        lnav_data.ld_rl_view->append_to_history(context, cmdline.substr(1));
×
1277
    }
×
1278

×
1279
    auto exec_res = execute_any(*this, cmdline);
×
1280
    if (exec_res.isErr()) {
×
1281
        this->ec_error_callback_stack.back()(exec_res.unwrapErr());
×
1282
    }
1283

1284
    return exec_res;
×
1285
}
1286

1287
void
×
1288
exec_context::add_error_context(lnav::console::user_message& um)
×
1289
{
×
1290
    switch (um.um_level) {
1291
        case lnav::console::user_message::level::raw:
1292
        case lnav::console::user_message::level::info:
×
1293
        case lnav::console::user_message::level::ok:
×
1294
            return;
1295
        default:
1296
            break;
68✔
1297
    }
1298

68✔
1299
    if (um.um_snippets.empty()) {
×
1300
        um.with_snippets(this->ec_source);
1301
    }
1302

×
1303
    if (this->ec_current_help != nullptr && um.um_help.empty()) {
68✔
1304
        attr_line_t help;
68✔
1305

1306
        format_help_text_for_term(*this->ec_current_help,
1307
                                  70,
68✔
1308
                                  help,
32✔
1309
                                  help_text_content::synopsis_and_summary);
1310
        um.with_help(help);
1311
    }
68✔
1312
}
27✔
1313

1314
exec_context::sql_callback_guard
27✔
1315
exec_context::push_callback(sql_callback_t cb)
1316
{
1317
    return sql_callback_guard(*this, cb);
1318
}
27✔
1319
exec_context::source_guard
27✔
1320
exec_context::enter_source(intern_string_t path,
1321
                           int line_number,
1322
                           const std::string& content)
1323
{
3✔
1324
    attr_line_t content_al{content};
1325
    content_al.with_attr_for_all(VC_ROLE.value(role_t::VCR_QUOTED_CODE));
3✔
1326
    readline_lnav_highlighter(content_al, -1);
1327
    this->ec_source.emplace_back(
1328
        lnav::console::snippet::from(path, content_al).with_line(line_number));
1,007✔
1329
    return {this};
1330
}
1331

1332
exec_context::output_guard::
1,007✔
1333
output_guard(exec_context& context,
1,007✔
1334
             std::string name,
1,007✔
1335
             const std::optional<output_t>& file)
1,007✔
1336
    : sg_context(context)
2,014✔
1337
{
2,014✔
1338
    if (file) {
1,007✔
1339
        log_info("redirecting command output to: %s", name.c_str());
1340
    }
566✔
1341
    context.ec_output_stack.emplace_back(std::move(name), file);
1342
}
1343

566✔
1344
exec_context::output_guard::~
566✔
1345
output_guard()
1346
{
566✔
1347
    this->sg_context.clear_output();
38✔
1348
    this->sg_context.ec_output_stack.pop_back();
1349
}
566✔
1350
exec_context::sql_callback_guard::
566✔
1351
sql_callback_guard(exec_context& context, sql_callback_t cb)
1352
    : scg_context(context), scg_old_callback(context.ec_sql_callback)
566✔
1353
{
1354
    context.ec_sql_callback = cb;
1355
}
566✔
1356
exec_context::sql_callback_guard::
566✔
1357
sql_callback_guard(sql_callback_guard&& other)
566✔
1358
    : scg_context(other.scg_context),
3✔
1359
      scg_old_callback(std::exchange(other.scg_old_callback, nullptr))
3✔
1360
{
3✔
1361
}
1362
exec_context::sql_callback_guard::~
3✔
1363
sql_callback_guard()
3✔
NEW
1364
{
×
NEW
1365
    if (this->scg_old_callback != nullptr) {
×
NEW
1366
        this->scg_context.ec_sql_callback = this->scg_old_callback;
×
NEW
1367
    }
×
1368
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc