fsfw/hal/src/fsfw_hal/linux/CommandExecutor.cpp
Robin Mueller 371ff931bf
Linux CommandExecutor
The CommandExecutor helper class can execute shell commands in blocking and non-blocking mode
This class is able to execute processes by using the Linux popen call. It also has the capability of writing
the read output of a process into a provided ring buffer.

The executor works by first loading the command which should be executed and specifying whether
it should be executed blocking or non-blocking. After that, execution can be started with the execute call.

Using non-blocking mode allows to execute commands which might take a longer time in the background,
and allowing the user thread to check completion status with the check function

Moved to HAL like requested in code review and unit tested with failing commands as well.
Also, Linux HAL components are compiled by default now unless explicitely disabled.
2022-01-26 12:11:52 +01:00

214 lines
6.3 KiB
C++

#include "CommandExecutor.h"
#include "fsfw/serviceinterface.h"
#include "fsfw/container/SimpleRingBuffer.h"
#include "fsfw/container/DynamicFIFO.h"
#include <unistd.h>
#include <cstring>
CommandExecutor::CommandExecutor(const size_t maxSize):
readVec(maxSize) {
waiter.events = POLLIN;
}
ReturnValue_t CommandExecutor::load(std::string command, bool blocking, bool printOutput) {
if(state == States::PENDING) {
return COMMAND_PENDING;
}
currentCmd = command;
this->blocking = blocking;
this->printOutput = printOutput;
if(state == States::IDLE) {
state = States::COMMAND_LOADED;
}
return HasReturnvaluesIF::RETURN_OK;
}
ReturnValue_t CommandExecutor::execute() {
if(state == States::IDLE) {
return NO_COMMAND_LOADED_OR_PENDING;
}
else if(state == States::PENDING) {
return COMMAND_PENDING;
}
currentCmdFile = popen(currentCmd.c_str(), "r");
if(currentCmdFile == nullptr) {
lastError = errno;
return HasReturnvaluesIF::RETURN_FAILED;
}
if(blocking) {
ReturnValue_t result = executeBlocking();
state = States::IDLE;
return result;
}
else {
currentFd = fileno(currentCmdFile);
waiter.fd = currentFd;
}
state = States::PENDING;
return HasReturnvaluesIF::RETURN_OK;
}
ReturnValue_t CommandExecutor::close() {
if(state == States::PENDING) {
// Attempt to close process, irrespective of if it is running or not
if(currentCmdFile != nullptr) {
pclose(currentCmdFile);
}
}
return HasReturnvaluesIF::RETURN_OK;
}
void CommandExecutor::printLastError(std::string funcName) const {
if(lastError != 0) {
#if FSFW_CPP_OSTREAM_ENABLED == 1
sif::warning << funcName << " pclose failed with code " << lastError << ": " <<
strerror(lastError) << std::endl;
#else
sif::printError("%s pclose failed with code %d: %s\n",
funcName, lastError, strerror(lastError));
#endif
}
}
void CommandExecutor::setRingBuffer(SimpleRingBuffer *ringBuffer,
DynamicFIFO<uint16_t>* sizesFifo) {
this->ringBuffer = ringBuffer;
this->sizesFifo = sizesFifo;
}
ReturnValue_t CommandExecutor::check(bool& replyReceived) {
if(blocking) {
return HasReturnvaluesIF::RETURN_OK;
}
switch(state) {
case(States::IDLE):
case(States::COMMAND_LOADED): {
return NO_COMMAND_LOADED_OR_PENDING;
}
case(States::PENDING): {
break;
}
}
int result = poll(&waiter, 1, 0);
switch(result) {
case(0): {
return HasReturnvaluesIF::RETURN_OK;
break;
}
case(1): {
if (waiter.revents & POLLIN) {
ssize_t readBytes = read(currentFd, readVec.data(), readVec.size());
if(readBytes == 0) {
// Should not happen
#if FSFW_CPP_OSTREAM_ENABLED == 1
sif::warning << "CommandExecutor::check: No bytes read "
"after poll event.." << std::endl;
#else
sif::printWarning("CommandExecutor::check: No bytes read after poll event..\n");
#endif
break;
}
else if(readBytes > 0) {
replyReceived = true;
if(printOutput) {
// It is assumed the command output is line terminated
#if FSFW_CPP_OSTREAM_ENABLED == 1
sif::info << currentCmd << " | " << readVec.data();
#else
sif::printInfo("%s | %s", currentCmd, readVec.data());
#endif
}
if(ringBuffer != nullptr) {
ringBuffer->writeData(reinterpret_cast<const uint8_t*>(
readVec.data()), readBytes);
}
if(sizesFifo != nullptr) {
if(not sizesFifo->full()) {
sizesFifo->insert(readBytes);
}
}
}
else {
// Should also not happen
#if FSFW_CPP_OSTREAM_ENABLED == 1
sif::warning << "CommandExecutor::check: Error " << errno << ": " <<
strerror(errno) << std::endl;
#else
sif::printWarning("CommandExecutor::check: Error %d: %s\n", errno, strerror(errno));
#endif
}
}
if(waiter.revents & POLLERR) {
#if FSFW_CPP_OSTREAM_ENABLED == 1
sif::warning << "CommandExecuter::check: Poll error" << std::endl;
#else
sif::printWarning("CommandExecuter::check: Poll error\n");
#endif
return COMMAND_ERROR;
}
if(waiter.revents & POLLHUP) {
result = pclose(currentCmdFile);
ReturnValue_t retval = EXECUTION_FINISHED;
if(result != 0) {
lastError = result;
retval = HasReturnvaluesIF::RETURN_FAILED;
}
state = States::IDLE;
currentCmdFile = nullptr;
currentFd = 0;
return retval;
}
break;
}
}
return HasReturnvaluesIF::RETURN_OK;
}
void CommandExecutor::reset() {
CommandExecutor::close();
currentCmdFile = nullptr;
currentFd = 0;
state = States::IDLE;
}
int CommandExecutor::getLastError() const {
// See: https://stackoverflow.com/questions/808541/any-benefit-in-using-wexitstatus-macro-in-c-over-division-by-256-on-exit-statu
return WEXITSTATUS(this->lastError);
}
CommandExecutor::States CommandExecutor::getCurrentState() const {
return state;
}
ReturnValue_t CommandExecutor::executeBlocking() {
while(fgets(readVec.data(), readVec.size(), currentCmdFile) != nullptr) {
std::string output(readVec.data());
if(printOutput) {
#if FSFW_CPP_OSTREAM_ENABLED == 1
sif::info << currentCmd << " | " << output;
#else
sif::printInfo("%s | %s", currentCmd, output);
#endif
}
if(ringBuffer != nullptr) {
ringBuffer->writeData(reinterpret_cast<const uint8_t*>(output.data()), output.size());
}
if(sizesFifo != nullptr) {
if(not sizesFifo->full()) {
sizesFifo->insert(output.size());
}
}
}
int result = pclose(currentCmdFile);
if(result != 0) {
lastError = result;
return HasReturnvaluesIF::RETURN_FAILED;
}
return HasReturnvaluesIF::RETURN_OK;
}