From e2c00dbbec9e6d25500daadf5323487e09dab1f6 Mon Sep 17 00:00:00 2001 From: Patrick Gaskin Date: Thu, 23 Apr 2020 05:25:22 -0400 Subject: [PATCH] Implemented cmd_spawn and cmd_output actions --- .gitignore | 2 +- res/doc | 6 ++-- src/action_cc.cc | 84 ++++++++++++++++++++++++++++++++++++++++++++++++ src/action_cc.h | 5 +++ src/config.c | 2 ++ 5 files changed, 96 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index a90dfcb..8e5017a 100644 --- a/.gitignore +++ b/.gitignore @@ -5,11 +5,11 @@ /src/qtplugin.moc /src/qtplugin.o +/src/action_c.o /src/failsafe.o /src/init.o /src/config.o /src/dlhook.o -/src/action_c.o /src/menu.o /src/action_cc.o diff --git a/res/doc b/res/doc index 3bb3669..de61e02 100644 --- a/res/doc +++ b/res/doc @@ -25,8 +25,8 @@ # nickel_setting - toggles a boolean setting # nickel_extras - opens one of the beta features # nickel_misc - other stuff which isn't significant enough for its own category -# cmdline_spawn - starts another process or script in the background (TODO) -# cmdline_output - runs a command and displays the output (TODO) +# cmd_spawn - starts another process or script in the background +# cmd_output - runs a command and displays the output # the argument passed to the action: # dbg_syslog - the text to write # dbg_error - the error message @@ -44,6 +44,8 @@ # misc - one of: # force_usb_connection - forces a usb connection dialog to be shown # rescan_books - forces nickel to rescan books (TODO) +# cmd_spawn - the command line to pass to /bin/sh -c (started in /) +# cmd_output - the timeout in milliseconds (0 < t < 10000), a colon, then the command line to pass to /bin/sh -c (started in /) # # For example, you might have a configuration file in KOBOeReader/.adds/nm/mystuff like: # diff --git a/src/action_cc.cc b/src/action_cc.cc index f147fdc..9179155 100644 --- a/src/action_cc.cc +++ b/src/action_cc.cc @@ -1,3 +1,9 @@ +#include +#include +#include + +#include + #include #include #include @@ -150,3 +156,81 @@ extern "C" int nm_action_nickelmisc(const char *arg, char **err_out) { NM_RETURN_OK(0); #undef NM_ERR_RET } + +// TODO: stop abusing err_out to display messages, maybe add a msg_out arg +// for that kind of thing instead (I've marked those spots by returning 2 for +// now). + +extern "C" int nm_action_cmdspawn(const char *arg, char **err_out) { + #define NM_ERR_RET 1 + + QProcess proc; + uint64_t pid; + bool ok = proc.startDetached( + QStringLiteral("/bin/sh"), + QStringList(std::initializer_list{ + QStringLiteral("-c"), + QString::fromUtf8(arg), + }), + QStringLiteral("/"), + (qint64*)(&pid) + ); + NM_ASSERT(ok, "could not start process"); + + if (*err_out) + asprintf(err_out, "Successfully started process with PID %lu.", pid); + return 2; + + #undef NM_ERR_RET +} + +extern "C" int nm_action_cmdoutput(const char *arg, char **err_out) { + #define NM_ERR_RET 1 + + char *tmp = strdup(arg); + + char *cmd = tmp; + char *tmp1 = strsep(&cmd, ":"), *tmp2; + long timeout = strtol(tmp1, &tmp2, 10); + NM_ASSERT(*tmp1 && !*tmp2 && timeout > 0 && timeout < 10000, "invalid timeout '%s'", tmp1); + + QProcess proc; + proc.setProcessChannelMode(QProcess::MergedChannels); + proc.setWorkingDirectory(QStringLiteral("/")); + proc.start( + QStringLiteral("/bin/sh"), + QStringList(std::initializer_list{ + QStringLiteral("-c"), + QString::fromUtf8(cmd), + }), + QIODevice::ReadOnly + ); + + bool ok = proc.waitForFinished(timeout); + if (!ok) { + switch (proc.error()) { + case QProcess::Timedout: + NM_RETURN_ERR("could not run process: timed out"); + case QProcess::FailedToStart: + NM_RETURN_ERR("could not run process: missing program or wrong permissions"); + case QProcess::Crashed: + NM_RETURN_ERR("could not run process: process crashed"); + default: + NM_RETURN_ERR("could not run process"); + } + } + if (proc.exitStatus() == QProcess::NormalExit && proc.exitCode() != 0) + NM_RETURN_ERR("could not run process: process exited with status %d", proc.exitCode()); + + QString out = proc.readAllStandardOutput(); + if (out.length() > 500) + out = out.left(500) + "..."; + + free(tmp); + + if (err_out) + *err_out = strdup(qPrintable(out)); + return 2; + + #undef NM_ERR_RET +} diff --git a/src/action_cc.h b/src/action_cc.h index 10f1ec9..3d7345a 100644 --- a/src/action_cc.h +++ b/src/action_cc.h @@ -4,9 +4,14 @@ extern "C" { #endif +// This file contains actions which need access to Qt classes, but is still +// mostly C code. + int nm_action_nickelsetting(const char *arg, char **err_out); int nm_action_nickelextras(const char *arg, char **err_out); int nm_action_nickelmisc(const char *arg, char **err_out); +int nm_action_cmdspawn(const char *arg, char **err_out); +int nm_action_cmdoutput(const char *arg, char **err_out); #ifdef __cplusplus } diff --git a/src/config.c b/src/config.c index b0fda39..48aca46 100644 --- a/src/config.c +++ b/src/config.c @@ -132,6 +132,8 @@ nm_config_t *nm_config_parse(char **err_out) { else if (!strcmp(c_act, "nickel_setting")) it->act = nm_action_nickelsetting; else if (!strcmp(c_act, "nickel_extras")) it->act = nm_action_nickelextras; else if (!strcmp(c_act, "nickel_misc")) it->act = nm_action_nickelmisc; + else if (!strcmp(c_act, "cmd_spawn")) it->act = nm_action_cmdspawn; + else if (!strcmp(c_act, "cmd_output")) it->act = nm_action_cmdoutput; else RETERR("file %s: line %d: field 4: unknown action '%s'", fn, line_n, c_act); // type: menu_item - field 5: argument