MXS-1405: Capture subprocess output

The output by the subprocesses launched by the externcmd system is now
captured and logged.
This commit is contained in:
Markus Mäkelä 2017-09-14 18:09:35 +03:00
parent eab7b7e2a3
commit 7e6e8d3e29
3 changed files with 108 additions and 52 deletions

View File

@ -17,14 +17,14 @@
#include <errno.h>
#include <string.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <string>
#include <maxscale/alloc.h>
#include <maxscale/log_manager.h>
#include <maxscale/pcre2.h>
#include <maxscale/thread.h>
/**
* Tokenize a string into arguments suitable for a `execvp` call
*
@ -159,8 +159,24 @@ void externcmd_free(EXTERNCMD* cmd)
}
}
int externcmd_execute(EXTERNCMD* cmd)
int externcmd_execute(EXTERNCMD* cmd, char** dest)
{
// Always set dest to NULL before starting
*dest = NULL;
// Create a pipe where the command can print output
int fd[2];
if (pipe(fd) == -1)
{
MXS_ERROR("Failed to open pipe: [%d] %s", errno, mxs_strerror(errno));
return -1;
}
// Make them non-blocking
fcntl(fd[0], F_SETFL, O_NONBLOCK);
fcntl(fd[1], F_SETFL, O_NONBLOCK);
int rval = 0;
pid_t pid;
@ -170,14 +186,25 @@ int externcmd_execute(EXTERNCMD* cmd)
if (pid < 0)
{
close(fd[0]);
close(fd[1]);
MXS_ERROR("Failed to execute command '%s', fork failed: [%d] %s",
cmd->argv[0], errno, mxs_strerror(errno));
rval = -1;
}
else if (pid == 0)
{
/** Child process, execute command */
// This is the child process. Close the read end of the pipe and redirect
// both stdout and stderr to the write end of the pipe
close(fd[0]);
dup2(fd[1], STDOUT_FILENO);
dup2(fd[1], STDERR_FILENO);
// Execute the command
execvp(cmd->argv[0], cmd->argv);
// Close the write end of the pipe and exit
close(fd[1]);
_exit(1);
}
else
@ -186,67 +213,83 @@ int externcmd_execute(EXTERNCMD* cmd)
cmd->child = pid;
cmd->n_exec++;
std::string output;
bool first_warning = true;
bool again = true;
uint64_t t = 0;
uint64_t t_max = cmd->timeout * 1000;
// Close the write end of the pipe
close(fd[1]);
while (again)
{
int exit_status;
switch (waitpid(pid, &exit_status, WNOHANG))
{
case -1:
MXS_ERROR("Failed to wait for child process: %d, %s", errno, mxs_strerror(errno));
again = false;
break;
case -1:
MXS_ERROR("Failed to wait for child process: %d, %s", errno, mxs_strerror(errno));
again = false;
break;
case 0:
if (t++ > t_max)
case 0:
if (t++ > t_max)
{
// Command timed out
t = 0;
if (first_warning)
{
// Command timed out
t = 0;
if (first_warning)
{
MXS_WARNING("Soft timeout for command '%s', sending SIGTERM", cmd->argv[0]);
kill(pid, SIGTERM);
first_warning = false;
}
else
{
MXS_ERROR("Hard timeout for command '%s', sending SIGKILL", cmd->argv[0]);
kill(pid, SIGKILL);
}
MXS_WARNING("Soft timeout for command '%s', sending SIGTERM", cmd->argv[0]);
kill(pid, SIGTERM);
first_warning = false;
}
else
{
// Sleep and try again
thread_millisleep(1);
MXS_ERROR("Hard timeout for command '%s', sending SIGKILL", cmd->argv[0]);
kill(pid, SIGKILL);
}
break;
}
else
{
// Sleep and try again
thread_millisleep(1);
}
break;
default:
again = false;
default:
again = false;
if (WIFEXITED(exit_status))
{
rval = WEXITSTATUS(exit_status);
}
else if (WIFSIGNALED(exit_status))
{
rval = WTERMSIG(exit_status);
}
else
{
rval = exit_status;
MXS_ERROR("Command '%s' did not exit normally. Exit status: %d",
cmd->argv[0], exit_status);
}
break;
if (WIFEXITED(exit_status))
{
rval = WEXITSTATUS(exit_status);
}
else if (WIFSIGNALED(exit_status))
{
rval = WTERMSIG(exit_status);
}
else
{
rval = exit_status;
MXS_ERROR("Command '%s' did not exit normally. Exit status: %d",
cmd->argv[0], exit_status);
}
break;
}
int n;
char buf[4096]; // This seems like enough space
while ((n = read(fd[0], buf, sizeof(buf))) > 0)
{
// Read all available output
output.append(buf, n);
}
}
// Close the read end of the pipe and copy the data to the output parameter
close(fd[0]);
*dest = MXS_STRDUP_A(output.c_str());
}
return rval;

View File

@ -55,11 +55,14 @@ void externcmd_free(EXTERNCMD* cmd);
/**
* Execute a command
*
* @param cmd Command to execute
* The output of the command must be freed by the caller by calling MXS_FREE.
*
* @param cmd Command to execute
* @param dest Pointer where to store the output of the command
*
* @return The return value of the executed command or -1 on error
*/
int externcmd_execute(EXTERNCMD* cmd);
int externcmd_execute(EXTERNCMD* cmd, char** dest);
/**
* Substitute all occurrences of @c match with @c replace in the arguments for @c cmd

View File

@ -1209,21 +1209,29 @@ monitor_launch_script(MXS_MONITOR* mon, MXS_MONITOR_SERVERS* ptr, const char* sc
externcmd_substitute_arg(cmd, "[$]SYNCEDLIST", nodelist);
}
int rv = externcmd_execute(cmd);
char* out = NULL;
std::string str;
int rv = externcmd_execute(cmd, &out);
if (out)
{
str = trim(out);
MXS_FREE(out);
}
if (rv)
{
if (rv == -1)
{
// Internal error
MXS_ERROR("Failed to execute script '%s' on server state change event '%s'",
script, mon_get_event_name(ptr));
MXS_ERROR("Failed to execute script '%s' on server state change event '%s': %s",
script, mon_get_event_name(ptr), str.c_str());
}
else
{
// Script returned a non-zero value
MXS_ERROR("Script '%s' returned %d on event '%s'",
script, rv, mon_get_event_name(ptr));
MXS_ERROR("Script '%s' returned %d on event '%s': %s",
script, rv, mon_get_event_name(ptr), str.c_str());
}
}
else
@ -1264,8 +1272,10 @@ monitor_launch_script(MXS_MONITOR* mon, MXS_MONITOR_SERVERS* ptr, const char* sc
memError = true;
scriptStr = cmd->argv[0]; // print at least something
}
MXS_NOTICE("Executed monitor script '%s' on event '%s'.",
scriptStr, mon_get_event_name(ptr));
MXS_NOTICE("Executed monitor script '%s' on event '%s': %s",
scriptStr, mon_get_event_name(ptr), str.c_str());
if (!memError)
{
MXS_FREE(scriptStr);