Added new helper function 'wexecvp' to simplify command execution

pull/2833/head
Javier Jaramago Fernández 6 years ago
parent 2bd91dcca7
commit f6e401c023

@ -1,8 +1,14 @@
#include <string>
#include <mysql.h>
#include "tap.h"
#include "utils.h"
#include <unistd.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <iostream>
int show_variable(MYSQL *mysql, const std::string& var_name, std::string& var_value) {
char query[128];
@ -102,3 +108,200 @@ int get_server_version(MYSQL *mysql, std::string& version) {
return 0;
}
// Pipes definition
constexpr uint8_t NUM_PIPES = 3;
constexpr uint8_t PARENT_WRITE_PIPE = 0;
constexpr uint8_t PARENT_READ_PIPE = 1;
constexpr uint8_t PARENT_ERR_PIPE = 2;
int pipes[NUM_PIPES][2];
// Pipe selection
constexpr uint8_t READ_FD = 0;
constexpr uint8_t WRITE_FD = 1;
// Parent pipes
const auto& PARENT_READ_FD = pipes[PARENT_READ_PIPE][READ_FD];
const auto& PARENT_READ_ERR = pipes[PARENT_ERR_PIPE][READ_FD];
const auto& PARENT_WRITE_FD = pipes[PARENT_WRITE_PIPE][WRITE_FD];
// Child pipes
const auto& CHILD_READ_FD = pipes[PARENT_WRITE_PIPE][READ_FD];
const auto& CHILD_WRITE_FD = pipes[PARENT_READ_PIPE][WRITE_FD];
const auto& CHILD_WRITE_ERR = pipes[PARENT_ERR_PIPE][WRITE_FD];
int kill_child_proc(pid_t child_pid, const uint timeout_us, const uint it_sleep_us) {
uint err = 0;
uint waited = 0;
int child_status = 0;
err = kill(child_pid, SIGTERM);
while (waitpid(child_pid, &child_status, WNOHANG) > 0) {
if (waited >= timeout_us) {
kill(child_pid, SIGKILL);
waited = 0;
} else {
waited += it_sleep_us;
}
usleep(it_sleep_us);
}
return err;
}
int read_pipe(int pipe_fd, std::string& sbuffer) {
char buffer[128];
char stderr_buffer[128];
ssize_t count = 0;
int res = 1;
for (;;) {
count = read(pipe_fd, buffer, sizeof(buffer));
if (count > 0) {
buffer[count] = 0;
sbuffer += buffer;
} else if (count == 0){
res = 0;
break;
} else {
if (errno != EWOULDBLOCK && errno != EINTR) {
res = -1;
}
break;
}
}
return res;
}
int wexecvp(const std::string& file, const std::vector<const char*>& argv, const to_opts* opts, std::string& s_stdout, std::string& s_stderr) {
int err = 0;
int pipe_err = 0;
std::string stdout_ = "";
std::string stderr_ = "";
std::vector<const char*> _argv = argv;
to_opts to_opts { 1000*1000, 1000*100 };
// Append null to end of _argv for extra safety
_argv.push_back(nullptr);
// Duplicate file argument to avoid manual duplication
_argv.insert(_argv.begin(), file.c_str());
if (opts) {
to_opts.timeout_us = opts->timeout_us;
to_opts.it_delay_us = opts->it_delay_us;
}
int outfd[2];
int infd[2];
// Pipes for parent to write and read
pipe(pipes[PARENT_READ_PIPE]);
pipe(pipes[PARENT_WRITE_PIPE]);
pipe(pipes[PARENT_ERR_PIPE]);
pid_t child_pid = fork();
if(child_pid == 0) {
// Copy the pipe descriptors
dup2(CHILD_READ_FD, STDIN_FILENO);
dup2(CHILD_WRITE_FD, STDOUT_FILENO);
dup2(CHILD_WRITE_ERR, STDERR_FILENO);
// Close no longer needed pipes
close(CHILD_READ_FD);
close(CHILD_WRITE_FD);
close(CHILD_WRITE_ERR);
close(PARENT_READ_FD);
close(PARENT_READ_ERR);
close(PARENT_WRITE_FD);
char** args = const_cast<char**>(_argv.data());
err = execvp(file.c_str(), args);
if (err) {
exit(errno);
} else {
exit(0);
}
} else {
int errno_cpy = 0;
char stdout_buffer[128];
char stderr_buffer[128];
ssize_t stdout_count = 0;
ssize_t stderr_count = 0;
// Close no longer needed pipes
close(CHILD_READ_FD);
close(CHILD_WRITE_FD);
close(CHILD_WRITE_ERR);
// Set the pipes in non-blocking mode
fcntl(PARENT_READ_FD, F_SETFL, fcntl(PARENT_READ_FD, F_GETFL) | O_NONBLOCK);
fcntl(PARENT_READ_ERR, F_SETFL, fcntl(PARENT_READ_ERR, F_GETFL) | O_NONBLOCK);
fd_set read_fds;
uint read_fds_sz = 2;
int maxfd = PARENT_READ_FD > PARENT_READ_ERR ? PARENT_READ_FD : PARENT_READ_ERR;
bool stdout_eof = false;
bool stderr_eof = false;
while (!stdout_eof && !stderr_eof) {
FD_ZERO(&read_fds);
FD_SET(PARENT_READ_FD, &read_fds);
FD_SET(PARENT_READ_ERR, &read_fds);
// Wait for the pipes to be ready
select(maxfd + 1, &read_fds, NULL, NULL, NULL);
if (FD_ISSET(PARENT_READ_FD, &read_fds)) {
int read_res = read_pipe(PARENT_READ_FD, stdout_);
if (read_res == 0) {
stdout_eof = true;
}
// Unexpected error while reading pipe
if (read_res < 0) {
pipe_err = -1;
// Backup read errno
errno_cpy = errno;
// Kill child and return error
kill_child_proc(child_pid, to_opts.timeout_us, to_opts.it_delay_us);
// Recover errno before return
errno = errno_cpy;
}
}
if(FD_ISSET(PARENT_READ_ERR, &read_fds)) {
int read_res = read_pipe(PARENT_READ_ERR, stderr_);
if (read_res == 0) {
stderr_eof = true;
}
// Unexpected error while reading pipe
if (read_res < 0) {
pipe_err = -1;
// Backup read errno
errno_cpy = errno;
// Kill child and return error
kill_child_proc(child_pid, to_opts.timeout_us, to_opts.it_delay_us);
// Recover errno before return
errno = errno_cpy;
}
}
}
if (pipe_err == 0) {
waitpid(child_pid, &err, 0);
}
}
if (pipe_err == 0) {
s_stdout = stdout_;
s_stderr = stderr_;
} else {
err = pipe_err;
}
return err;
}

@ -3,6 +3,7 @@
#include <mysql.h>
#include <string>
#include <vector>
#define MYSQL_QUERY(mysql, query) \
do { \
@ -27,5 +28,27 @@ int select_config_file(MYSQL* mysql, std::string& resultset);
}
#endif
/**
* @brief Simple struct that holds the 'timeout options' for 'wexecvp'.
*/
struct to_opts {
uint timeout_us;
uint it_delay_us;
};
/**
* @brief Execute the given comand, and stores it's output.
*
* @param file File to be executed.
* @param argv Arguments to be given to the executable.
* @param result The output of the file execution. If the execution succeed it contains `stdout` output,
* in case of failure `stderr` contents are returned.
* @param opts In case of pipe readin error, this timeout options are used for trying to terminate
* the child process nicely, before seding a SIGKILL to it:
* - timeout_us: Member specifies the total timeout to wait for the child to exit.
* - it_delay_us: Member specifies the waiting delay between checks.
* @return int Zero in case of success, or the errno returned by `execvp` in case of failure.
*/
int wexecvp(const std::string& file, const std::vector<const char*>& argv, const to_opts* opts, std::string& s_stdout, std::string& s_stderr);
#endif // #define UTILS_H

Loading…
Cancel
Save