diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-07 04:48:35 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-07 04:48:35 +0000 |
commit | 207df6fc406e81bfeebdff7f404bd242ff3f099f (patch) | |
tree | a1a796b056909dd0a04ffec163db9363a8757808 /src/fs-extension-functions.cc | |
parent | Releasing progress-linux version 0.11.2-1~progress7.99u1. (diff) | |
download | lnav-207df6fc406e81bfeebdff7f404bd242ff3f099f.tar.xz lnav-207df6fc406e81bfeebdff7f404bd242ff3f099f.zip |
Merging upstream version 0.12.2.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/fs-extension-functions.cc')
-rw-r--r-- | src/fs-extension-functions.cc | 207 |
1 files changed, 201 insertions, 6 deletions
diff --git a/src/fs-extension-functions.cc b/src/fs-extension-functions.cc index a9d34fc..cbc2366 100644 --- a/src/fs-extension-functions.cc +++ b/src/fs-extension-functions.cc @@ -29,24 +29,30 @@ * @file fs-extension-functions.cc */ +#include <future> #include <string> #include <errno.h> -#include <stddef.h> #include <stdlib.h> #include <string.h> #include <sys/param.h> #include <sys/stat.h> #include <unistd.h> +#include "base/auto_fd.hh" +#include "base/auto_mem.hh" +#include "base/auto_pid.hh" +#include "base/injector.hh" +#include "base/lnav.console.hh" +#include "base/opt_util.hh" +#include "bound_tags.hh" #include "config.h" #include "sqlite-extension-func.hh" #include "sqlite3.h" #include "vtab_module.hh" +#include "yajlpp/yajlpp_def.hh" -using namespace mapbox; - -static util::variant<const char*, string_fragment> +static mapbox::util::variant<const char*, string_fragment> sql_basename(const char* path_in) { int text_end = -1; @@ -72,7 +78,7 @@ sql_basename(const char* path_in) } } -static util::variant<const char*, string_fragment> +static mapbox::util::variant<const char*, string_fragment> sql_dirname(const char* path_in) { ssize_t text_end; @@ -161,6 +167,158 @@ sql_realpath(const char* path) return resolved_path; } +struct shell_exec_options { + std::map<std::string, nonstd::optional<std::string>> po_env; +}; + +static const json_path_container shell_exec_env_handlers = { + yajlpp::pattern_property_handler(R"((?<name>[^=]+))") + .for_field(&shell_exec_options::po_env), +}; + +static const typed_json_path_container<shell_exec_options> + shell_exec_option_handlers = { + yajlpp::property_handler("env").with_children(shell_exec_env_handlers), +}; + +static blob_auto_buffer +sql_shell_exec(const char* cmd, + nonstd::optional<string_fragment> input, + nonstd::optional<string_fragment> opts_json) +{ + static const intern_string_t SRC = intern_string::lookup("options"); + + static auto& lnav_flags = injector::get<unsigned long&, lnav_flags_tag>(); + + if (lnav_flags & LNF_SECURE_MODE) { + throw sqlite_func_error("not available in secure mode"); + } + + shell_exec_options options; + + if (opts_json) { + auto parse_res + = shell_exec_option_handlers.parser_for(SRC).of(opts_json.value()); + + if (parse_res.isErr()) { + throw lnav::console::user_message::error( + "invalid options parameter") + .with_reason(parse_res.unwrapErr()[0]); + } + + options = parse_res.unwrap(); + } + + auto child_fds_res + = auto_pipe::for_child_fds(STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO); + if (child_fds_res.isErr()) { + throw lnav::console::user_message::error("cannot open child pipes") + .with_reason(child_fds_res.unwrapErr()); + } + auto child_pid_res = lnav::pid::from_fork(); + if (child_pid_res.isErr()) { + throw lnav::console::user_message::error("cannot fork()") + .with_reason(child_pid_res.unwrapErr()); + } + + auto child_fds = child_fds_res.unwrap(); + auto child_pid = child_pid_res.unwrap(); + + for (auto& child_fd : child_fds) { + child_fd.after_fork(child_pid.in()); + } + + if (child_pid.in_child()) { + const char* args[] = { + getenv_opt("SHELL").value_or("bash"), + "-c", + cmd, + nullptr, + }; + + for (const auto& epair : options.po_env) { + if (epair.second.has_value()) { + setenv(epair.first.c_str(), epair.second->c_str(), 1); + } else { + unsetenv(epair.first.c_str()); + } + } + + execvp(args[0], (char**) args); + _exit(EXIT_FAILURE); + } + + auto out_reader = std::async( + std::launch::async, [out_fd = std::move(child_fds[1].read_end())]() { + auto buffer = auto_buffer::alloc(4096); + + while (true) { + if (buffer.available() < 4096) { + buffer.expand_by(4096); + } + + auto rc + = read(out_fd, buffer.next_available(), buffer.available()); + if (rc < 0) { + break; + } + if (rc == 0) { + break; + } + buffer.resize_by(rc); + } + + return buffer; + }); + + auto err_reader = std::async( + std::launch::async, [err_fd = std::move(child_fds[2].read_end())]() { + auto buffer = auto_buffer::alloc(4096); + + while (true) { + if (buffer.available() < 4096) { + buffer.expand_by(4096); + } + + auto rc + = read(err_fd, buffer.next_available(), buffer.available()); + if (rc < 0) { + break; + } + if (rc == 0) { + break; + } + buffer.resize_by(rc); + } + + return buffer; + }); + + if (input) { + child_fds[0].write_end().write_fully(input.value()); + } + child_fds[0].close(); + + auto retval = blob_auto_buffer{out_reader.get()}; + + auto finished_child = std::move(child_pid).wait_for_child(); + + if (!finished_child.was_normal_exit()) { + throw sqlite_func_error("child failed with signal {}", + finished_child.term_signal()); + } + + if (finished_child.exit_status() != EXIT_SUCCESS) { + throw lnav::console::user_message::error( + attr_line_t("child failed with exit code ") + .append(lnav::roles::number( + fmt::to_string(finished_child.exit_status())))) + .with_reason(err_reader.get().to_string()); + } + + return retval; +} + int fs_extension_functions(struct FuncDef** basic_funcs, struct FuncDefAgg** agg_funcs) @@ -170,6 +328,7 @@ fs_extension_functions(struct FuncDef** basic_funcs, sqlite_func_adapter<decltype(&sql_basename), sql_basename>::builder( help_text("basename", "Extract the base portion of a pathname.") .sql_function() + .with_prql_path({"fs", "basename"}) .with_parameter({"path", "The path"}) .with_tags({"filename"}) .with_example({"To get the base of a plain file name", @@ -183,12 +342,18 @@ fs_extension_functions(struct FuncDef** basic_funcs, .with_example({"To get the base of a Windows path", "SELECT basename('foo\\bar')"}) .with_example({"To get the base of the root directory", - "SELECT basename('/')"})), + "SELECT basename('/')"}) + .with_example({ + "To get the base of a path", + "from [{p='foo/bar'}] | select { fs.basename p }", + help_example::language::prql, + })), sqlite_func_adapter<decltype(&sql_dirname), sql_dirname>::builder( help_text("dirname", "Extract the directory portion of a pathname.") .sql_function() .with_parameter({"path", "The path"}) + .with_prql_path({"fs", "dirname"}) .with_tags({"filename"}) .with_example({"To get the directory of a relative file path", "SELECT dirname('foo/bar')"}) @@ -205,6 +370,7 @@ fs_extension_functions(struct FuncDef** basic_funcs, sqlite_func_adapter<decltype(&sql_joinpath), sql_joinpath>::builder( help_text("joinpath", "Join components of a path together.") .sql_function() + .with_prql_path({"fs", "join"}) .with_parameter( help_text( "path", @@ -233,6 +399,7 @@ fs_extension_functions(struct FuncDef** basic_funcs, sqlite_func_adapter<decltype(&sql_readlink), sql_readlink>::builder( help_text("readlink", "Read the target of a symbolic link.") .sql_function() + .with_prql_path({"fs", "readlink"}) .with_parameter({"path", "The path to the symbolic link."}) .with_tags({"filename"})), @@ -243,9 +410,37 @@ fs_extension_functions(struct FuncDef** basic_funcs, "symbolic links and " "resolving '.' and '..' references.") .sql_function() + .with_prql_path({"fs", "realpath"}) .with_parameter({"path", "The path to resolve."}) .with_tags({"filename"})), + sqlite_func_adapter<decltype(&sql_shell_exec), sql_shell_exec>::builder( + help_text("shell_exec", + "Executes a shell command and returns its output.") + .sql_function() + .with_prql_path({"shell", "exec"}) + .with_parameter({"cmd", "The command to execute."}) + .with_parameter(help_text{ + "input", + "A blob of data to write to the command's standard input."} + .optional()) + .with_parameter( + help_text{"options", + "A JSON object containing options for the " + "execution with the following properties:"} + .optional() + .with_parameter(help_text{ + "env", + "An object containing the environment variables " + "to set or, if NULL, to unset."} + .optional())) + .with_tags({"shell"})) + .with_flags( +#ifdef SQLITE_DIRECTONLY + SQLITE_DIRECTONLY | +#endif + SQLITE_UTF8), + /* * TODO: add other functions like normpath, ... */ |