1
0
Fork 0
mirror of https://git.rwth-aachen.de/acs/public/villas/node/ synced 2025-03-09 00:00:00 +01:00
VILLASnode/lib/nodes/exec.cpp

212 lines
4.7 KiB
C++
Raw Permalink Normal View History

2019-06-05 18:59:45 +02:00
/** Node-type for subprocess node-types.
*
* @author Steffen Vogel <post@steffenvogel.de>
2022-03-15 09:28:57 -04:00
* @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC
2022-07-04 18:20:03 +02:00
* @license Apache 2.0
2019-06-05 18:59:45 +02:00
*********************************************************************************/
#include <string>
#include <unistd.h>
2019-06-05 18:59:45 +02:00
#include <villas/node/config.hpp>
2019-06-05 18:59:45 +02:00
#include <villas/nodes/exec.hpp>
2021-06-21 16:11:42 -04:00
#include <villas/utils.hpp>
#include <villas/node/exceptions.hpp>
#include <villas/format.hpp>
2019-06-05 18:59:45 +02:00
using namespace villas;
2021-05-10 00:12:30 +02:00
using namespace villas::node;
2019-06-05 18:59:45 +02:00
using namespace villas::utils;
ExecNode::~ExecNode()
2019-06-05 18:59:45 +02:00
{
if (stream_in)
fclose(stream_in);
if (stream_out)
fclose(stream_out);
}
int ExecNode::parse(json_t *json)
{
int ret = Node::parse(json);
if (ret)
return ret;
2019-06-05 18:59:45 +02:00
json_error_t err;
int f = 1, s = -1;
2019-06-05 18:59:45 +02:00
json_t *json_exec;
json_t *json_env = nullptr;
2021-05-10 00:12:30 +02:00
json_t *json_format = nullptr;
const char *wd = nullptr;
2019-06-05 18:59:45 +02:00
2021-05-10 00:12:30 +02:00
ret = json_unpack_ex(json, &err, 0, "{ s: o, s?: o, s?: b, s?: o, s?: b, s?: s }",
"exec", &json_exec,
2021-05-10 00:12:30 +02:00
"format", &json_format,
"flush", &f,
"environment", &json_env,
"shell", &s,
"working_directory", &wd
2019-06-05 18:59:45 +02:00
);
if (ret)
2021-02-16 14:15:14 +01:00
throw ConfigError(json, err, "node-config-node-exec");
2019-06-05 18:59:45 +02:00
flush = f != 0;
shell = s < 0 ? json_is_string(json_exec) : s != 0;
arguments.clear();
environment.clear();
if (json_is_string(json_exec)) {
if (!shell)
throw ConfigError(json_exec, "node-config-node-exec-shell", "The exec setting must be an array if shell mode is disabled.");
command = json_string_value(json_exec);
}
else if (json_is_array(json_exec)) {
if (shell)
throw ConfigError(json_exec, "node-config-node-exec-shell", "The exec setting must be a string if shell mode is enabled.");
if (json_array_size(json_exec) < 1)
throw ConfigError(json_exec, "node-config-node-exec-exec", "At least one argument must be given");
size_t i;
json_t *json_arg;
json_array_foreach(json_exec, i, json_arg) {
if (!json_is_string(json_arg))
throw ConfigError(json_arg, "node-config-node-exec-exec", "All arguments must be of string type");
if (i == 0)
command = json_string_value(json_arg);
2019-06-23 17:35:28 +02:00
arguments.push_back(json_string_value(json_arg));
}
}
if (json_env) {
/* obj is a JSON object */
const char *key;
json_t *json_value;
json_object_foreach(json_env, key, json_value) {
if (!json_is_string(json_value))
throw ConfigError(json_value, "node-config-node-exec-environment", "Environment variables must be of string type");
environment[key] = json_string_value(json_value);
}
}
2019-06-05 18:59:45 +02:00
2021-05-10 00:12:30 +02:00
/* Format */
auto *fmt = json_format
2021-05-10 00:12:30 +02:00
? FormatFactory::make(json_format)
: FormatFactory::make("villas.human");
formatter = Format::Ptr(fmt);
if (!formatter)
2021-05-10 00:12:30 +02:00
throw ConfigError(json_format, "node-config-node-exec-format", "Invalid format configuration");
2019-06-05 18:59:45 +02:00
state = State::PARSED;
2019-06-05 18:59:45 +02:00
return 0;
}
int ExecNode::prepare()
2019-06-05 18:59:45 +02:00
{
assert(state == State::CHECKED);
2019-06-05 18:59:45 +02:00
/* Initialize IO */
formatter->start(getInputSignals(false));
return Node::prepare();
2019-06-05 18:59:45 +02:00
}
int ExecNode::start()
2020-06-16 02:35:34 +02:00
{
/* Start subprocess */
proc = std::make_unique<Popen>(command, arguments, environment, working_dir, shell);
logger->debug("Started sub-process with pid={}", proc->getPid());
2019-06-05 18:59:45 +02:00
stream_in = fdopen(proc->getFdIn(), "r");
if (!stream_in)
return -1;
2019-06-05 18:59:45 +02:00
stream_out = fdopen(proc->getFdOut(), "w");
if (!stream_out)
return -1;
2020-06-16 02:35:34 +02:00
int ret = Node::start();
if (!ret)
state = State::STARTED;
2020-06-16 02:35:34 +02:00
2019-06-05 18:59:45 +02:00
return 0;
}
int ExecNode::stop()
2019-06-05 18:59:45 +02:00
{
int ret = Node::stop();
if (ret)
return ret;
2019-06-05 18:59:45 +02:00
/* Stop subprocess */
logger->debug("Killing sub-process with pid={}", proc->getPid());
proc->kill(SIGINT);
2019-06-05 18:59:45 +02:00
logger->debug("Waiting for sub-process with pid={} to terminate", proc->getPid());
proc->close();
2019-06-05 18:59:45 +02:00
2021-07-09 19:04:46 +02:00
/** @todo Check exit code of subprocess? */
2019-06-05 18:59:45 +02:00
return 0;
}
int ExecNode::_read(struct Sample * smps[], unsigned cnt)
2019-06-05 18:59:45 +02:00
{
return formatter->scan(stream_in, smps, cnt);
2019-06-05 18:59:45 +02:00
}
int ExecNode::_write(struct Sample * smps[], unsigned cnt)
2019-06-05 18:59:45 +02:00
{
int ret;
ret = formatter->print(stream_out, smps, cnt);
2019-06-05 18:59:45 +02:00
if (ret < 0)
return ret;
if (flush)
fflush(stream_out);
2019-06-05 18:59:45 +02:00
return cnt;
}
const std::string & ExecNode::getDetails()
2019-06-05 18:59:45 +02:00
{
if (details.empty()) {
std::string wd = working_dir;
if (wd.empty()) {
char buf[128];
wd = getcwd(buf, sizeof(buf));
}
details = fmt::format("exec={}, shell={}, flush={}, #environment={}, #arguments={}, working_dir={}",
command,
shell ? "yes" : "no",
flush ? "yes" : "no",
environment.size(),
arguments.size(),
wd
);
}
return details;
2019-06-05 18:59:45 +02:00
}
std::vector<int> ExecNode::getPollFDs()
2019-06-05 18:59:45 +02:00
{
return { proc->getFdIn() };
2019-06-05 18:59:45 +02:00
}
static char n[] = "exec";
static char d[] = "run subprocesses with stdin/stdout communication";
static NodePlugin<ExecNode, n , d, (int) NodeFactory::Flags::SUPPORTS_READ | (int) NodeFactory::Flags::SUPPORTS_WRITE | (int) NodeFactory::Flags::SUPPORTS_POLL> p;