in source/jobs/JobEngine.cpp [230:340]
int JobEngine::exec_cmd(string operation, PlainJobDocument::JobAction action)
{
// Establish some file descriptors which we'll use to redirect stdout and
// stderr from the child process back into our logger
int stdout[] = {0, 0};
int stderr[] = {0, 0};
if (pipe(stdout) < 0)
{
LOG_ERROR(TAG, "failed allocating pipe for child STDOUT redirect");
return CMD_FAILURE;
}
if (pipe(stderr) < 0)
{
close(stdout[PIPE_READ]);
close(stdout[PIPE_WRITE]);
LOG_ERROR(TAG, "failed allocating pipe for child STDERR redirect");
return CMD_FAILURE;
}
/**
* \brief Create char array argv[] storing arguments to pass to execvp() function.
* argv[0] executable path
* argv[1] Linux user name
* argv[2:] arguments required for executing the executable file..
*/
size_t argSize = 0;
if (action.input.args.has_value())
{
argSize = action.input.args->size();
}
std::unique_ptr<const char *[]> argv(new const char *[argSize + 3]);
argv[0] = operation.c_str();
argv[1] = action.runAsUser->c_str();
argv[argSize + 2] = nullptr;
for (size_t i = 0; i < argSize; i++)
{
argv[i + 2] = action.input.args->at(i).c_str();
}
int execResult;
int returnCode;
int pid = vfork();
if (pid < 0)
{
LOGM_ERROR(TAG, "Failed to create child process, fork returned %d", pid);
return CMD_FAILURE;
}
else if (pid == 0)
{
// Child process
LOG_DEBUG(TAG, "Child process now running");
// redirect stdout
if (dup2(stdout[PIPE_WRITE], STDOUT_FILENO) == -1)
{
LOGM_WARN(TAG, "Failed to duplicate STDOUT pipe, errno {%d}, stdout will likely be unavailable", errno);
}
// redirect stderr
if (dup2(stderr[PIPE_WRITE], STDERR_FILENO) == -1)
{
LOGM_WARN(TAG, "Failed to duplicate STDERR pipe, errno {%d}, stderr will likely be unavailable", errno);
}
// all these are for use by parent only
// TODO we need to make sure ALL file handles get closed, including those within the MQTTConnectionManager
close(stdout[PIPE_READ]);
close(stdout[PIPE_WRITE]);
close(stderr[PIPE_READ]);
close(stderr[PIPE_WRITE]);
LOG_DEBUG(TAG, "Child process about to call execvp");
if (execvp(operation.c_str(), const_cast<char *const *>(argv.get())) == -1)
{
LOGM_DEBUG(TAG, "Failed to invoke execvp system call to execute action step: %s ", strerror(errno));
}
// If the exec fails we need to exit the child process
_exit(1);
}
else
{
// parent process
LOGM_DEBUG(TAG, "Parent process now running, child PID is %d", pid);
// close unused file descriptors
close(stdout[PIPE_WRITE]);
close(stderr[PIPE_WRITE]);
// Set up some threads to process the output from the child process
thread stdOutProcessor(&JobEngine::processCmdOutput, this, stdout[PIPE_READ], false, pid);
stdOutProcessor.join();
thread stdErrProcessor(&JobEngine::processCmdOutput, this, stderr[PIPE_READ], true, pid);
stdErrProcessor.join();
do
{
// TODO: do not wait for infinite time for child process to complete
int waitReturn = waitpid(pid, &execResult, 0);
if (waitReturn == -1)
{
LOG_WARN(TAG, "Failed to wait for child process");
}
LOGM_DEBUG(TAG, "JobEngine finished waiting for child process, returning %d", execResult);
returnCode = execResult;
} while (!WIFEXITED(execResult) && !WIFSIGNALED(execResult));
}
return returnCode;
}