Robin Mueller
371ff931bf
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.
214 lines
6.3 KiB
C++
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;
|
|
}
|