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

tstack / lnav / 17955647695-2537

23 Sep 2025 06:33PM UTC coverage: 64.851% (-0.1%) from 64.974%
17955647695-2537

push

github

tstack
[build] left local path in cargo

45984 of 70907 relevant lines covered (64.85%)

406352.74 hits per line

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

44.84
/src/cmds.scripting.cc
1
/**
2
 * Copyright (c) 2025, 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 <map>
31
#include <memory>
32
#include <string>
33
#include <vector>
34

35
#include "base/itertools.hh"
36
#include "base/lnav.console.hh"
37
#include "base/result.h"
38
#include "bound_tags.hh"
39
#include "command_executor.hh"
40
#include "config.h"
41
#include "libbase64.h"
42
#include "lnav.hh"
43
#include "lnav.indexing.hh"
44
#include "lnav.prompt.hh"
45
#include "lnav_commands.hh"
46
#include "readline_context.hh"
47
#include "scn/scan.h"
48
#include "service_tags.hh"
49
#include "session.export.hh"
50
#include "shlex.hh"
51
#include "sysclip.hh"
52
#include "yajlpp/yajlpp.hh"
53

54
#ifdef HAVE_RUST_DEPS
55
#    include "lnav_rs_ext.cxx.hh"
56
#endif
57

58
static Result<std::string, lnav::console::user_message>
59
com_export_session_to(exec_context& ec,
5✔
60
                      std::string cmdline,
61
                      std::vector<std::string>& args)
62
{
63
    std::string retval;
5✔
64

65
    if (!ec.ec_dry_run) {
5✔
66
        auto_mem<FILE> outfile(fclose);
5✔
67
        auto fn = trim(remaining_args(cmdline, args));
5✔
68
        auto to_term = false;
5✔
69

70
        if (fn == "-" || fn == "/dev/stdout") {
5✔
71
            auto ec_out = ec.get_output();
2✔
72

73
            if (!ec_out) {
2✔
74
                outfile = auto_mem<FILE>::leak(stdout);
×
75

76
                if (ec.ec_ui_callbacks.uc_pre_stdout_write) {
×
77
                    ec.ec_ui_callbacks.uc_pre_stdout_write();
×
78
                }
79
                setvbuf(stdout, nullptr, _IONBF, 0);
×
80
                to_term = true;
×
81
                fprintf(outfile,
×
82
                        "\n---------------- Press any key to exit "
83
                        "lo-fi "
84
                        "display "
85
                        "----------------\n\n");
86
            } else {
87
                outfile = auto_mem<FILE>::leak(ec_out.value());
2✔
88
            }
89
            if (outfile.in() == stdout) {
2✔
90
                lnav_data.ld_stdout_used = true;
2✔
91
            }
92
        } else if (fn == "/dev/clipboard") {
3✔
93
            auto open_res = sysclip::open(sysclip::type_t::GENERAL);
×
94
            if (open_res.isErr()) {
×
95
                alerter::singleton().chime("cannot open clipboard");
×
96
                return ec.make_error("Unable to copy to clipboard: {}",
97
                                     open_res.unwrapErr());
×
98
            }
99
            outfile = open_res.unwrap();
×
100
        } else if (lnav_data.ld_flags & LNF_SECURE_MODE) {
3✔
101
            return ec.make_error("{} -- unavailable in secure mode", args[0]);
×
102
        } else {
103
            if ((outfile = fopen(fn.c_str(), "we")) == nullptr) {
3✔
104
                return ec.make_error("unable to open file -- {}", fn);
×
105
            }
106
            fchmod(fileno(outfile.in()), S_IRWXU);
3✔
107
        }
108

109
        auto export_res = lnav::session::export_to(outfile.in());
5✔
110

111
        fflush(outfile.in());
5✔
112
        if (to_term) {
5✔
113
            if (ec.ec_ui_callbacks.uc_post_stdout_write) {
×
114
                ec.ec_ui_callbacks.uc_post_stdout_write();
×
115
            }
116
        }
117
        if (export_res.isErr()) {
5✔
118
            return Err(export_res.unwrapErr());
×
119
        }
120

121
        retval = fmt::format(
5✔
122
            FMT_STRING("info: wrote session commands to -- {}"), fn);
20✔
123
    }
5✔
124

125
    return Ok(retval);
5✔
126
}
5✔
127

128
static Result<std::string, lnav::console::user_message>
129
com_rebuild(exec_context& ec,
9✔
130
            std::string cmdline,
131
            std::vector<std::string>& args)
132
{
133
    if (!ec.ec_dry_run) {
9✔
134
        rescan_files(true);
9✔
135
        rebuild_indexes_repeatedly();
9✔
136
    }
137

138
    return Ok(std::string());
9✔
139
}
140

141
static Result<std::string, lnav::console::user_message>
142
com_echo(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
45✔
143
{
144
    std::string retval = "error: expecting a message";
45✔
145

146
    if (args.size() >= 1) {
45✔
147
        bool lf = true;
45✔
148
        std::string src;
45✔
149

150
        if (args.size() > 2 && args[1] == "-n") {
45✔
151
            std::string::size_type index_in_cmdline = cmdline.find(args[1]);
1✔
152

153
            lf = false;
1✔
154
            src = cmdline.substr(index_in_cmdline + args[1].length() + 1);
1✔
155
        } else if (args.size() >= 2) {
44✔
156
            src = cmdline.substr(args[0].length() + 1);
30✔
157
        } else {
158
            src = "";
14✔
159
        }
160

161
        auto lexer = shlex(src);
45✔
162
        lexer.eval(retval, ec.create_resolver());
45✔
163

164
        auto ec_out = ec.get_output();
45✔
165
        if (ec.ec_dry_run) {
45✔
166
            lnav_data.ld_preview_status_source[0].get_description().set_value(
×
167
                "The text to output:"_frag);
×
168
            lnav_data.ld_status[LNS_PREVIEW0].set_needs_update();
×
169
            lnav_data.ld_preview_view[0].set_sub_source(
×
170
                &lnav_data.ld_preview_source[0]);
171
            lnav_data.ld_preview_source[0].replace_with(attr_line_t(retval));
×
172
            retval = "";
×
173
        } else if (ec_out) {
45✔
174
            FILE* outfile = *ec_out;
43✔
175

176
            if (outfile == stdout) {
43✔
177
                lnav_data.ld_stdout_used = true;
37✔
178
            }
179

180
            fprintf(outfile, "%s", retval.c_str());
43✔
181
            if (lf) {
43✔
182
                putc('\n', outfile);
42✔
183
            }
184
            fflush(outfile);
43✔
185

186
            retval = "";
43✔
187
        }
188
    }
45✔
189

190
    return Ok(retval);
90✔
191
}
45✔
192

193
static Result<std::string, lnav::console::user_message>
194
com_alt_msg(exec_context& ec,
×
195
            std::string cmdline,
196
            std::vector<std::string>& args)
197
{
198
    static auto& prompt = lnav::prompt::get();
199

200
    std::string retval;
×
201

202
    if (ec.ec_dry_run) {
×
203
        retval = "";
×
204
    } else if (args.size() == 1) {
×
205
        prompt.p_editor.clear_alt_value();
×
206
        retval = "";
×
207
    } else {
208
        std::string msg = remaining_args(cmdline, args);
×
209

210
        prompt.p_editor.set_alt_value(msg);
×
211
        retval = "";
×
212
    }
213

214
    return Ok(retval);
×
215
}
216

217
static Result<std::string, lnav::console::user_message>
218
com_eval(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
22✔
219
{
220
    std::string retval;
22✔
221

222
    if (args.size() > 1) {
22✔
223
        static intern_string_t EVAL_SRC = intern_string::lookup(":eval");
32✔
224

225
        std::string all_args = remaining_args(cmdline, args);
22✔
226
        std::string expanded_cmd;
22✔
227
        shlex lexer(all_args.c_str(), all_args.size());
22✔
228

229
        log_debug("Evaluating: %s", all_args.c_str());
22✔
230
        if (!lexer.eval(expanded_cmd,
22✔
231
                        {
232
                            &ec.ec_local_vars.top(),
22✔
233
                            &ec.ec_global_vars,
22✔
234
                        }))
235
        {
236
            return ec.make_error("invalid arguments");
×
237
        }
238
        log_debug("Expanded command to evaluate: %s", expanded_cmd.c_str());
22✔
239

240
        if (expanded_cmd.empty()) {
22✔
241
            return ec.make_error("empty result after evaluation");
×
242
        }
243

244
        if (ec.ec_dry_run) {
22✔
245
            attr_line_t al(expanded_cmd);
×
246

247
            lnav_data.ld_preview_status_source[0].get_description().set_value(
×
248
                "The command to be executed:"_frag);
×
249
            lnav_data.ld_status[LNS_PREVIEW0].set_needs_update();
×
250

251
            lnav_data.ld_preview_view[0].set_sub_source(
×
252
                &lnav_data.ld_preview_source[0]);
253
            lnav_data.ld_preview_source[0].replace_with(al);
×
254

255
            return Ok(std::string());
×
256
        }
257

258
        auto src_guard = ec.enter_source(EVAL_SRC, 1, expanded_cmd);
22✔
259
        auto content = string_fragment::from_str(expanded_cmd);
22✔
260
        multiline_executor me(ec, ":eval");
22✔
261
        for (auto line : content.split_lines()) {
106✔
262
            TRY(me.push_back(line));
84✔
263
        }
22✔
264
        TRY(me.final());
22✔
265
        retval = std::move(me.me_last_result);
22✔
266
    } else {
22✔
267
        return ec.make_error("expecting a command or query to evaluate");
×
268
    }
269

270
    return Ok(retval);
22✔
271
}
22✔
272

273
static Result<std::string, lnav::console::user_message>
274
com_cd(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
4✔
275
{
276
    static const intern_string_t SRC = intern_string::lookup("path");
12✔
277

278
    if (lnav_data.ld_flags & LNF_SECURE_MODE) {
4✔
279
        return ec.make_error("{} -- unavailable in secure mode", args[0]);
×
280
    }
281

282
    std::vector<std::string> word_exp;
4✔
283
    std::string pat;
4✔
284

285
    pat = trim(remaining_args(cmdline, args));
4✔
286

287
    shlex lexer(pat);
4✔
288
    auto split_args_res = lexer.split(ec.create_resolver());
4✔
289
    if (split_args_res.isErr()) {
4✔
290
        auto split_err = split_args_res.unwrapErr();
×
291
        auto um
292
            = lnav::console::user_message::error("unable to parse file name")
×
293
                  .with_reason(split_err.se_error.te_msg)
×
294
                  .with_snippet(lnav::console::snippet::from(
×
295
                      SRC, lexer.to_attr_line(split_err.se_error)))
×
296
                  .move();
×
297

298
        return Err(um);
×
299
    }
300

301
    auto split_args = split_args_res.unwrap()
8✔
302
        | lnav::itertools::map([](const auto& elem) { return elem.se_value; });
12✔
303

304
    if (split_args.size() != 1) {
4✔
305
        return ec.make_error("expecting a single argument");
×
306
    }
307

308
    struct stat st;
309

310
    if (stat(split_args[0].c_str(), &st) != 0) {
4✔
311
        return Err(ec.make_error_msg("cannot access -- {}", split_args[0])
3✔
312
                       .with_errno_reason());
1✔
313
    }
314

315
    if (!S_ISDIR(st.st_mode)) {
3✔
316
        return ec.make_error("{} is not a directory", split_args[0]);
2✔
317
    }
318

319
    if (!ec.ec_dry_run) {
2✔
320
        chdir(split_args[0].c_str());
2✔
321
        setenv("PWD", split_args[0].c_str(), 1);
2✔
322
    }
323

324
    return Ok(std::string());
2✔
325
}
4✔
326

327
static Result<std::string, lnav::console::user_message>
328
com_sh(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
5✔
329
{
330
    if (lnav_data.ld_flags & LNF_SECURE_MODE) {
5✔
331
        return ec.make_error("{} -- unavailable in secure mode", args[0]);
×
332
    }
333

334
    static size_t EXEC_COUNT = 0;
335

336
    if (!ec.ec_dry_run) {
5✔
337
        std::optional<std::string> name_flag;
5✔
338

339
        shlex lexer(cmdline);
5✔
340
        auto cmd_start = args[0].size();
5✔
341
        auto split_res = lexer.split(ec.create_resolver());
5✔
342
        if (split_res.isOk()) {
5✔
343
            auto flags = split_res.unwrap();
5✔
344
            if (flags.size() >= 2) {
5✔
345
                static const char* NAME_FLAG = "--name=";
346

347
                if (startswith(flags[1].se_value, NAME_FLAG)) {
5✔
348
                    name_flag = flags[1].se_value.substr(strlen(NAME_FLAG));
×
349
                    cmd_start = flags[1].se_origin.sf_end;
×
350
                }
351
            }
352
        }
5✔
353

354
        auto carg = trim(cmdline.substr(cmd_start));
5✔
355

356
        log_info("executing: %s", carg.c_str());
5✔
357

358
        auto child_fds_res
359
            = auto_pipe::for_child_fds(STDOUT_FILENO, STDERR_FILENO);
5✔
360
        if (child_fds_res.isErr()) {
5✔
361
            auto um = lnav::console::user_message::error(
×
362
                          "unable to create child pipes")
363
                          .with_reason(child_fds_res.unwrapErr())
×
364
                          .move();
×
365
            ec.add_error_context(um);
×
366
            return Err(um);
×
367
        }
368
        auto child_res = lnav::pid::from_fork();
5✔
369
        if (child_res.isErr()) {
5✔
370
            auto um
371
                = lnav::console::user_message::error("unable to fork() child")
×
372
                      .with_reason(child_res.unwrapErr())
×
373
                      .move();
×
374
            ec.add_error_context(um);
×
375
            return Err(um);
×
376
        }
377

378
        auto child_fds = child_fds_res.unwrap();
5✔
379
        auto child = child_res.unwrap();
5✔
380
        for (auto& child_fd : child_fds) {
15✔
381
            child_fd.after_fork(child.in());
10✔
382
        }
383
        if (child.in_child()) {
5✔
384
            auto dev_null = open("/dev/null", O_RDONLY | O_CLOEXEC);
×
385

386
            dup2(dev_null, STDIN_FILENO);
×
387
            const char* exec_args[] = {
×
388
                getenv_opt("SHELL").value_or("bash"),
×
389
                "-c",
390
                carg.c_str(),
×
391
                nullptr,
392
            };
393

394
            for (const auto& pair : ec.ec_local_vars.top()) {
×
395
                pair.second.match(
×
396
                    [&pair](const std::string& val) {
×
397
                        setenv(pair.first.c_str(), val.c_str(), 1);
×
398
                    },
×
399
                    [&pair](const string_fragment& sf) {
×
400
                        setenv(pair.first.c_str(), sf.to_string().c_str(), 1);
×
401
                    },
×
402
                    [](null_value_t) {},
×
403
                    [&pair](int64_t val) {
×
404
                        setenv(
×
405
                            pair.first.c_str(), fmt::to_string(val).c_str(), 1);
×
406
                    },
×
407
                    [&pair](double val) {
×
408
                        setenv(
×
409
                            pair.first.c_str(), fmt::to_string(val).c_str(), 1);
×
410
                    },
×
411
                    [&pair](bool val) {
×
412
                        setenv(pair.first.c_str(), val ? "1" : "0", 1);
×
413
                    });
×
414
            }
415

416
            execvp(exec_args[0], (char**) exec_args);
×
417
            _exit(EXIT_FAILURE);
×
418
        }
419

420
        std::string display_name;
5✔
421
        auto open_prov = ec.get_provenance<exec_context::file_open>();
5✔
422
        if (open_prov) {
5✔
423
            if (name_flag) {
1✔
424
                display_name = fmt::format(
×
425
                    FMT_STRING("{}/{}"), open_prov->fo_name, name_flag.value());
×
426
            } else {
427
                display_name = open_prov->fo_name;
1✔
428
            }
429
        } else if (name_flag) {
4✔
430
            display_name = name_flag.value();
×
431
        } else {
432
            display_name
433
                = fmt::format(FMT_STRING("sh-{} {}"), EXEC_COUNT++, carg);
16✔
434
        }
435

436
        auto name_base = display_name;
5✔
437
        size_t name_counter = 0;
5✔
438

439
        while (true) {
440
            auto fn_iter
441
                = lnav_data.ld_active_files.fc_file_names.find(display_name);
5✔
442
            if (fn_iter == lnav_data.ld_active_files.fc_file_names.end()) {
5✔
443
                break;
5✔
444
            }
445
            name_counter += 1;
×
446
            display_name
447
                = fmt::format(FMT_STRING("{} [{}]"), name_base, name_counter);
×
448
        }
449

450
        auto create_piper_res
451
            = lnav::piper::create_looper(display_name,
452
                                         std::move(child_fds[0].read_end()),
5✔
453
                                         std::move(child_fds[1].read_end()));
10✔
454

455
        if (create_piper_res.isErr()) {
5✔
456
            auto um
457
                = lnav::console::user_message::error("unable to create piper")
×
458
                      .with_reason(create_piper_res.unwrapErr())
×
459
                      .move();
×
460
            ec.add_error_context(um);
×
461
            return Err(um);
×
462
        }
463

464
        lnav_data.ld_active_files.fc_file_names[display_name].with_piper(
10✔
465
            create_piper_res.unwrap());
10✔
466
        lnav_data.ld_child_pollers.emplace_back(child_poller{
15✔
467
            display_name,
468
            std::move(child),
5✔
469
            [](auto& fc, auto& child) {},
5✔
470
        });
471
        lnav_data.ld_files_to_front.emplace_back(display_name);
5✔
472

473
        return Ok(fmt::format(FMT_STRING("info: executing -- {}"), carg));
20✔
474
    }
5✔
475

476
    return Ok(std::string());
×
477
}
478

479
#ifdef HAVE_RUST_DEPS
480

481
static lnav::task_progress
482
ext_prog_rep()
×
483
{
484
    auto ext = lnav_rs_ext::get_status();
×
485
    auto status = ext.status == lnav_rs_ext::Status::idle
×
486
        ? lnav::progress_status_t::idle
×
487
        : lnav::progress_status_t::working;
488
    std::vector<lnav::console::user_message> msgs_out;
×
489
    for (const auto& err : ext.messages) {
×
490
        auto um = lnav::console::user_message::error((std::string) err.error)
×
491
                      .with_reason((std::string) err.source)
×
492
                      .with_help((std::string) err.help);
×
493
        msgs_out.emplace_back(um);
×
494
    }
495
    auto retval = lnav::task_progress{
496
        (std::string) ext.id,
497
        status,
498
        ext.version,
×
499
        (std::string) ext.current_step,
500
        ext.completed,
×
501
        ext.total,
×
502
        std::move(msgs_out),
×
503
    };
504

505
    return retval;
×
506
}
507

508
DIST_SLICE(prog_reps) lnav::progress_reporter_t EXT_PROG_REP = ext_prog_rep;
509

510
namespace lnav_rs_ext {
511

512
LnavLogLevel
513
get_lnav_log_level()
6,446✔
514
{
515
    switch (lnav_log_level) {
6,446✔
516
        case lnav_log_level_t::TRACE:
×
517
            return LnavLogLevel::trace;
×
518
        case lnav_log_level_t::DEBUG:
6,446✔
519
            return LnavLogLevel::debug;
6,446✔
520
        case lnav_log_level_t::INFO:
×
521
            return LnavLogLevel::info;
×
522
        case lnav_log_level_t::WARNING:
×
523
            return LnavLogLevel::warning;
×
524
        case lnav_log_level_t::ERROR:
×
525
            return LnavLogLevel::error;
×
526
    }
527

528
    return LnavLogLevel::info;
×
529
}
530

531
void
532
log_msg(LnavLogLevel level, ::rust::Str file, uint32_t line, ::rust::Str msg)
5,495✔
533
{
534
    auto ln_level = static_cast<lnav_log_level_t>(level);
5,495✔
535

536
    ::log_msg(ln_level,
10,990✔
537
              ((std::string) file).c_str(),
10,990✔
538
              line,
539
              "%.*s",
540
              msg.size(),
541
              msg.data());
542
}
5,495✔
543

544
::rust::String
545
version_info()
×
546
{
547
    yajlpp_gen gen;
×
548
    {
549
        yajlpp_map root(gen);
×
550

551
        root.gen("product");
×
552
        root.gen(PACKAGE);
×
553
        root.gen("version");
×
554
        root.gen(PACKAGE_VERSION);
×
555
    }
556

557
    return gen.to_string_fragment().to_string();
×
558
}
559

560
ExecResult
561
execute_external_command(::rust::String rs_src,
×
562
                         ::rust::String rs_script,
563
                         ::rust::String hdrs)
564
{
565
    auto src = (std::string) rs_src;
×
566
    auto script = (std::string) rs_script;
×
567
    auto retval = std::make_shared<ExecResult>();
×
568

569
    log_debug("sending remote command to main looper");
×
570
    isc::to<main_looper&, services::main_t>().send_and_wait(
×
571
        [src, script, hdrs, &retval](auto& mlooper) {
×
572
            log_debug("executing remote command from: %s", src.c_str());
×
573
            db_label_source ext_db_source;
×
574
            auto& ec = lnav_data.ld_exec_context;
×
575
            // XXX we should still allow an external command to update the
576
            // regular DB view.
577
            auto dsg = ec.enter_db_source(&ext_db_source);
×
578
            auto* outfile = tmpfile();
×
579
            auto ec_out = exec_context::output_t{outfile, fclose};
×
580
            auto og = exec_context::output_guard{ec, "default", ec_out};
×
581
            auto me = multiline_executor{ec, src};
×
582
            auto pg = ec.with_provenance(exec_context::external_access{src});
×
583
            ec.ec_local_vars.push(std::map<std::string, scoped_value_t>{
×
584
                {"headers", scoped_value_t{(std::string) hdrs}}});
×
585
            auto script_frag = string_fragment::from_str(script);
×
586
            for (const auto& line : script_frag.split_lines()) {
×
587
                auto res = me.push_back(line);
×
588
                if (res.isErr()) {
×
589
                    auto um = res.unwrapErr();
×
590
                    retval->error.msg = um.um_message.al_string;
×
591
                    retval->error.reason = um.um_reason.al_string;
×
592
                    retval->error.help = um.um_help.al_string;
×
593
                    ec.ec_local_vars.pop();
×
594
                    return;
×
595
                }
596
            }
597
            auto res = me.final();
×
598
            if (res.isErr()) {
×
599
                auto um = res.unwrapErr();
×
600
                retval->error.msg = um.um_message.al_string;
×
601
                retval->error.reason = um.um_reason.al_string;
×
602
                retval->error.help = um.um_help.al_string;
×
603
            } else {
×
604
                fseek(ec_out.first, 0, SEEK_SET);
×
605
                retval->status = me.me_last_result;
×
606
                retval->content_type = fmt::to_string(ec.get_output_format());
×
607
                retval->content_fd = dup(fileno(ec_out.first));
×
608
            }
609
            ec.ec_local_vars.pop();
×
610
        });
×
611

612
    return *retval;
×
613
}
614

615
}  // namespace lnav_rs_ext
616
#endif
617

618
static Result<std::string, lnav::console::user_message>
619
com_external_access(exec_context& ec,
×
620
                    std::string cmdline,
621
                    std::vector<std::string>& args)
622
{
623
#ifdef HAVE_RUST_DEPS
624
    if (args.size() != 3) {
×
625
        return ec.make_error("Expecting port number and API key");
×
626
    }
627

628
    if (lnav_data.ld_flags & LNF_SECURE_MODE) {
×
629
        return ec.make_error("External access is not available in secure mode");
×
630
    }
631

632
    std::string retval;
×
633
    if (ec.ec_dry_run) {
×
634
        return Ok(retval);
×
635
    }
636

637
    auto scan_res = scn::scan_int<uint16_t>(args[1]);
×
638
    if (!scan_res || !scan_res.value().range().empty()) {
×
639
        return ec.make_error(FMT_STRING("port value is not a number: {}"),
×
640
                             args[1]);
×
641
    }
642
    auto port = scan_res->value();
×
643

644
    auto buf = auto_buffer::alloc((args[2].size() * 5) / 3);
×
645
    auto outlen = buf.capacity();
×
646
    base64_encode(args[2].data(), args[2].size(), buf.in(), &outlen, 0);
×
647
    auto start_res
648
        = lnav_rs_ext::start_ext_access(port, ::rust::String(buf.in(), outlen));
×
649
    if (start_res.port == 0) {
×
650
        return ec.make_error(FMT_STRING("unable to start external access: {}"),
×
651
                             (std::string) start_res.error);
×
652
    }
653

654
    retval = fmt::format(FMT_STRING("info: started external access on port {}"),
×
655
                         start_res.port);
×
656
    setenv("LNAV_EXTERNAL_PORT", fmt::to_string(start_res.port).c_str(), 1);
×
657
    auto url = fmt::format(FMT_STRING("http://127.0.0.1:{}"), start_res.port);
×
658
    setenv("LNAV_EXTERNAL_URL", url.c_str(), 1);
×
659

660
    return Ok(retval);
×
661
#else
662
    return ec.make_error("lnav was compiled without Rust extensions");
663
#endif
664
}
665

666
static readline_context::command_t SCRIPTING_COMMANDS[] = {
667
    {
668
        "export-session-to",
669
        com_export_session_to,
670

671
        help_text(":export-session-to")
672
            .with_summary("Export the current lnav state to an executable lnav "
673
                          "script file that contains the commands needed to "
674
                          "restore the current session")
675
            .with_parameter(
676
                help_text("path", "The path to the file to write")
677
                    .with_format(help_parameter_format_t::HPF_LOCAL_FILENAME))
678
            .with_tags({"io", "scripting"}),
679
    },
680
    {
681
        "rebuild",
682
        com_rebuild,
683
        help_text(":rebuild")
684
            .with_summary("Forcefully rebuild file indexes")
685
            .with_tags({"scripting"}),
686
    },
687
    {
688
        "echo",
689
        com_echo,
690

691
        help_text(":echo")
692
            .with_summary(
693
                "Echo the given message to the screen or, if "
694
                ":redirect-to has "
695
                "been called, to output file specified in the "
696
                "redirect.  "
697
                "Variable substitution is performed on the message.  "
698
                "Use a "
699
                "backslash to escape any special characters, like '$'")
700
            .with_parameter(help_text("-n",
701
                                      "Do not print a line-feed at "
702
                                      "the end of the output")
703
                                .optional()
704
                                .with_format(help_parameter_format_t::HPF_TEXT))
705
            .with_parameter(help_text("msg", "The message to display"))
706
            .with_tags({"io", "scripting"})
707
            .with_example({"To output 'Hello, World!'", "Hello, World!"}),
708
    },
709
    {
710
        "alt-msg",
711
        com_alt_msg,
712

713
        help_text(":alt-msg")
714
            .with_summary("Display a message in the alternate command position")
715
            .with_parameter(help_text("msg", "The message to display")
716
                                .with_format(help_parameter_format_t::HPF_TEXT))
717
            .with_tags({"scripting"})
718
            .with_example({"To display 'Press t to switch to the text view' on "
719
                           "the bottom right",
720
                           "Press t to switch to the text view"}),
721
    },
722
    {
723
        "eval",
724
        com_eval,
725

726
        help_text(":eval")
727
            .with_summary("Evaluate the given command/query after doing "
728
                          "environment variable substitution")
729
            .with_parameter(help_text(
730
                "command", "The command or query to perform substitution on."))
731
            .with_tags({"scripting"})
732
            .with_examples({{"To substitute the table name from a variable",
733
                             ";SELECT * FROM ${table}"}}),
734
    },
735
    {
736
        "sh",
737
        com_sh,
738

739
        help_text(":sh")
740
            .with_summary("Execute the given command-line and display the "
741
                          "captured output")
742
            .with_parameter(help_text(
743
                "--name=<name>", "The name to give to the captured output"))
744
            .with_parameter(
745
                help_text("cmdline", "The command-line to execute."))
746
            .with_tags({"scripting"}),
747
    },
748
    {
749
        "cd",
750
        com_cd,
751

752
        help_text(":cd")
753
            .with_summary("Change the current directory")
754
            .with_parameter(
755
                help_text("dir", "The new current directory")
756
                    .with_format(help_parameter_format_t::HPF_DIRECTORY))
757
            .with_tags({"scripting"}),
758
    },
759
    {
760
        "external-access",
761
        com_external_access,
762
        help_text(":external-access")
763
            .with_summary(
764
                "Open a port to give remote access to this lnav instance")
765
            .with_parameter(
766
                help_text("port", "The port number to listen on")
767
                    .with_format(help_parameter_format_t::HPF_NUMBER))
768
            .with_parameter(
769
                help_text("api-key", "The API key")
770
                    .with_format(help_parameter_format_t::HPF_STRING))
771
            .with_tags({"scripting"}),
772
    },
773
};
774

775
void
776
init_lnav_scripting_commands(readline_context::command_map_t& cmd_map)
573✔
777
{
778
    for (auto& cmd : SCRIPTING_COMMANDS) {
5,157✔
779
        cmd.c_help.index_tags();
4,584✔
780
        cmd_map[cmd.c_name] = &cmd;
13,752✔
781
    }
782
}
573✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc