run event on dir uses callbacks for processing messages produced by
event command process. logging_callback and post_run_callback are still
in run_event_state because this callbacks are used in Python API. run
event on dir uses "observer" in order to allow better logging perforemed
by run event on dir callers
---
src/include/run_event.h | 134 ++++++++++++++-
src/lib/run_event.c | 440 +++++++++++++++++++++++++++++++++++++++--------
2 files changed, 496 insertions(+), 78 deletions(-)
diff --git a/src/include/run_event.h b/src/include/run_event.h
index 43730ce..32ad089 100644
--- a/src/include/run_event.h
+++ b/src/include/run_event.h
@@ -27,17 +27,20 @@ extern "C" {
struct dump_dir;
+typedef int (*res_post_run_callback)(const char *dump_dir_name, void *param);
+typedef char* (*res_logging_callback)(char *log_line, void *param);
+
struct run_event_state {
int children_count;
/* Used only for post-create dup detection. TODO: document its API */
- int (*post_run_callback)(const char *dump_dir_name, void *param);
+ res_post_run_callback post_run_callback;
void *post_run_param;
/* Can take ownership of log_line, which is malloced. In this case, return
NULL.
* Otherwise should return log_line (it will be freed by caller)
*/
- char* (*logging_callback)(char *log_line, void *param);
+ res_logging_callback logging_callback;
void *logging_param;
/* Internal data for async command execution */
@@ -49,6 +52,130 @@ struct run_event_state {
struct run_event_state *new_run_event_state(void);
void free_run_event_state(struct run_event_state *state);
+/*
+ * Kill child's command process if is running
+ *
+ * @param A not NULL pointer to run_event_state
+ */
+void run_event_state_kill_command(struct run_event_state *state);
+
+/*
+ * This structured holds a callbacks used during process of event run.
+ */
+struct run_event_impl
+{
+ /*
+ * Called when child command proced an alert
+ *
+ * Must not be NULL
+ *
+ * @param msg An alert message produced byt child command
+ * @param args An implementor args
+ */
+ void (* alert)(const char *msg, void *args);
+
+ /*
+ * Called when child ask for some input. A callee
+ * should return a text whithout any new line character.
+ *
+ * Must not be NULL
+ *
+ * @param msg An ask message produced by child command
+ * @param args An implementor args
+ * @return Must allways return string without new lines.
+ */
+ char *(* ask)(const char *msg, void *args);
+
+ /*
+ * Called when child wants to know yes/no decision.
+ *
+ * Must not be NULL
+ *
+ * @param msg An ask message produced by child command
+ * @param args An implementor args
+ * @return Return 0 if an answer is NO, otherwise return nonzero value.
+ */
+ int (* ask_yes_no)(const char *msg, void *args);
+
+ /*
+ * Called when child wants to know yes/no decision.
+ *
+ * Must not be NULL
+ *
+ * @param msg An ask message produced by child command
+ * @param password A password value, can be NULL if nobody is interested
in.
+ * @param args An implementor args
+ * @return Return nonzero if a password was obtained, otherwise return 0.
+ */
+ int (* ask_password)(const char *msg, char **password, void *args);
+
+ /*
+ * Implementor's custom data.
+ */
+ void *args;
+};
+
+
+struct run_event_observer
+{
+ /*
+ * Push observer method called after child command starts
+ *
+ * @param pid Child's command process pid
+ * @param args An implementor args
+ */
+ void (* command_started)(pid_t pid, void *args);
+
+ /*
+ * Push observer method called after processing of output line from command
+ *
+ * @param msg A line without formatting defined by communication protocol
+ * @param raw_output Whole line read from command output
+ * @param response A response sent back to a command
+ * @param args An implementor args
+ */
+ void (* command_msg_processed)(const char *msg, const char *raw_output,
const char *response, void *args);
+
+ /*
+ * Push observer method called after child command finish
+ *
+ * @param retval Child's command process return value
+ * @param status Child's command process status
+ * @param err_msg An error message generated by runtime (not OS nor child
command).
+ * @param args An implementor args
+ */
+ void (* command_finished)(int retval, int status, const char *err_msg,
void *args);
+
+ /*
+ * Implementor's custom data.
+ */
+ void *args;
+};
+
+
+/*
+ * Initializes and returns a text implementation of run event callbacks.
+ * The implementation communicates with user over standard input and output.
+ */
+struct run_event_impl *new_text_run_event_impl();
+/*
+ * Frees run_event callbacks text implementation
+ */
+void free_text_run_event_impl(struct run_event_impl *impl);
+
+
+/*
+ * Initializes and returns a implementation of run event observer.
+ * All function calls are passed to a wrapped implementation.
+ * This implementation stores communication between command process and
+ * parent process in problem event log.
+ */
+struct run_event_observer *new_command_log_run_event_observer(char
*dump_dir_name, const struct run_event_observer *wrapped_impl);
+/*
+ * Frees run_event callbacks event log implementation
+ */
+void free_command_log_run_event_observer(struct run_event_observer *impl);
+
/* Asynchronous command execution */
/* Returns 0 if no commands found for this dump_dir_name+event, else >0 */
@@ -65,6 +192,9 @@ void free_commands(struct run_event_state *state);
/* Returns exit code of first failed action, or first nonzero return value
* of post_run_callback. If all actions are successful, returns 0.
*/
+int run_event_state_on_dir_name(struct run_event_state *state, const char
*dump_dir_name,
+ const char *event, const struct run_event_impl
*impl,
+ const struct run_event_observer *obs);
int run_event_on_dir_name(struct run_event_state *state, const char
*dump_dir_name, const char *event);
int run_event_on_problem_data(struct run_event_state *state, problem_data_t
*data, const char *event);
diff --git a/src/lib/run_event.c b/src/lib/run_event.c
index e45ec16..44a6fc2 100644
--- a/src/lib/run_event.c
+++ b/src/lib/run_event.c
@@ -26,6 +26,12 @@ struct run_event_state *new_run_event_state()
return xzalloc(sizeof(struct run_event_state));
}
+void run_event_state_kill_command(struct run_event_state *state)
+{
+ if (state && state->command_pid > 0)
+ kill(state->command_pid, SIGKILL);
+}
+
void free_run_event_state(struct run_event_state *state)
{
if (state)
@@ -432,101 +438,145 @@ int spawn_next_command(struct run_event_state *state,
return 0;
}
+int run_event_state_on_dir_name(struct run_event_state *state,
+ const char *dump_dir_name,
+ const char *event,
+ const struct run_event_impl *impl,
+ const struct run_event_observer *obs)
+{
+#define CALL_OBSERVER(var, method, args) do { if (var && var->method )
var->method args; }while(0)
-/* Synchronous command execution:
- */
-int run_event_on_dir_name(struct run_event_state *state,
- const char *dump_dir_name,
- const char *event
-) {
- prepare_commands(state, dump_dir_name, event);
-
- /* Execute every command in shell */
+ static const int alert_prefix_len = sizeof(REPORT_PREFIX_ALERT)-1;
+ static const int ask_prefix_len = sizeof(REPORT_PREFIX_ASK)-1;
+ static const int ask_yes_no_prefix_len =
sizeof(REPORT_PREFIX_ASK_YES_NO)-1;
+ static const int ask_password_prefix_len =
sizeof(REPORT_PREFIX_ASK_PASSWORD)-1;
+ char buf[257]; /* usually we get one line, no need to have big buf */
+ int r;
int retval = 0;
while (spawn_next_command(state, dump_dir_name, event) >= 0)
{
- /* Consume log from stdout */
- FILE *fp = fdopen(state->command_out_fd, "r");
- if (!fp)
- die_out_of_memory();
- char *buf;
- char *msg;
-
- int alert_prefix_len = strlen(REPORT_PREFIX_ALERT);
- int ask_prefix_len = strlen(REPORT_PREFIX_ASK);
- int ask_yes_no_prefix_len = strlen(REPORT_PREFIX_ASK_YES_NO);
- int ask_password_prefix_len = strlen(REPORT_PREFIX_ASK_PASSWORD);
-
- while ((buf = xmalloc_fgetline(fp)) != NULL)
+ CALL_OBSERVER(obs, command_started, (state->command_pid, obs->args));
+ struct strbuf *cmd_output = strbuf_new();
+
+ while ((r = read(state->command_out_fd, buf, sizeof(buf) - 1)) > 0)
{
- msg = buf;
+ char *newline;
+ char *raw;
+ buf[r] = '\0';
+ raw = buf;
- /* just cut off prefix, no waiting */
- if (strncmp(REPORT_PREFIX_ALERT, msg, alert_prefix_len) == 0)
- {
- msg += alert_prefix_len;
- printf("%s\n", msg);
- fflush(stdout);
- }
- /* wait for y/N response on the same line */
- else if (strncmp(REPORT_PREFIX_ASK_YES_NO, msg,
ask_yes_no_prefix_len) == 0)
- {
- msg += ask_yes_no_prefix_len;
- printf("%s [%s/%s] ", msg, _("y"), _("N"));
- fflush(stdout);
- char buf[16];
- if (!fgets(buf, sizeof(buf), stdin))
- buf[0] = '\0';
-
- if (write(state->command_in_fd, buf, strlen(buf)) < 0)
- perror_msg_and_die("write");
- }
- /* wait for the string on the same line */
- else if (strncmp(REPORT_PREFIX_ASK, msg, ask_prefix_len) == 0)
- {
- msg += ask_prefix_len;
- printf("%s ", msg);
- fflush(stdout);
- char buf[256];
- if (!fgets(buf, sizeof(buf), stdin))
- buf[0] = '\0';
-
- if (write(state->command_in_fd, buf, strlen(buf)) < 0)
- perror_msg_and_die("write");
- }
- /* set echo off and wait for password on the same line */
- else if (strncmp(REPORT_PREFIX_ASK_PASSWORD, msg,
ask_password_prefix_len) == 0)
+ while ((newline = strchr(raw, '\n')) != NULL)
{
- msg += ask_password_prefix_len;
- printf("%s ", msg);
- fflush(stdout);
- char buf[256];
- bool changed = set_echo(false);
- if (!fgets(buf, sizeof(buf), stdin))
- buf[0] = '\0';
- if (changed)
- set_echo(true);
-
- if (write(state->command_in_fd, buf, strlen(buf)) < 0)
- perror_msg_and_die("write");
+ *newline = '\0';
+ strbuf_append_str(cmd_output, raw);
+ char *msg = cmd_output->buf;
+ int skipped_chars = 0;
+
+
+ /* In the code below:
+ * response is always malloced,
+ * log_response is always set to response
+ * or to constant string.
+ */
+ char *response = NULL;
+
+ /* just cut off prefix, no waiting */
+ if (strncmp(REPORT_PREFIX_ALERT, msg, alert_prefix_len) == 0)
+ {
+ skipped_chars = alert_prefix_len;
+ impl->alert(msg + skipped_chars, impl->args);
+ }
+ /* wait for the string on the same line */
+ else if (strncmp(REPORT_PREFIX_ASK, msg, ask_prefix_len) == 0)
+ {
+ skipped_chars = ask_prefix_len;
+ response = impl->ask(msg + skipped_chars, impl->args);
+ }
+ /* wait for y/N response on the same line */
+ else if (strncmp(REPORT_PREFIX_ASK_YES_NO, msg,
ask_yes_no_prefix_len) == 0)
+ {
+ skipped_chars = ask_yes_no_prefix_len;
+ if (impl->ask_yes_no(msg + skipped_chars, impl->args))
+ response = xstrdup("y");
+ else
+ response = xstrdup("n");
+ }
+ /* set echo off and wait for password on the same line */
+ else if (strncmp(REPORT_PREFIX_ASK_PASSWORD, msg,
ask_password_prefix_len) == 0)
+ {
+ skipped_chars = ask_password_prefix_len;
+ if (!impl->ask_password(msg + skipped_chars, &response,
impl->args))
+ response = xstrdup("");
+ }
+ /* no special prefix -> forward to log if applicable */
+ /* note that callback may take ownership of buf by returning
NULL */
+ else if (state->logging_callback)
+ {
+ char *log = state->logging_callback(xstrdup(msg),
state->logging_param);
+ free(log);
+ }
+
+ if (response)
+ {
+ unsigned len = strlen(response);
+ response[len++] = '\n';
+
+ if (full_write(state->command_in_fd, response, len) != len)
+ {
+ VERB1 perror_msg("Can't write %u bytes to child's
stdin", len);
+ free(response);
+ response = xstrdup("<WRITE ERROR>");
+ }
+ }
+
+ CALL_OBSERVER(obs, command_msg_processed, (msg +
skipped_chars, msg, response, obs->args));
+
+ free(response);
+ strbuf_clear(cmd_output);
+
+ /* jump to next line */
+ raw = newline + 1;
}
- /* no special prefix -> forward to log if applicable
- * note that callback may take ownership of buf by returning NULL
*/
- else if (state->logging_callback)
- buf = state->logging_callback(buf, state->logging_param);
- free(buf);
+ /* beginning of next line. the line continues by next read() */
+ strbuf_append_str(cmd_output, raw);
}
- fclose(fp); /* Got EOF, close. This also closes state->command_out_fd
*/
/* Wait for child to actually exit, collect status */
int status;
safe_waitpid(state->command_pid, &status, 0);
+ state->command_pid = 0;
retval = WEXITSTATUS(status);
if (WIFSIGNALED(status))
retval = WTERMSIG(status) + 128;
+
+ char *err_msg = NULL;
+ if (retval != 0)
+ {
+ /* Localize these messages? */
+ if (WIFSIGNALED(status))
+ err_msg = xasprintf("killed by signal %u", WTERMSIG(status));
+ else
+ err_msg = xasprintf("exited with %u", retval);
+ }
+
+ CALL_OBSERVER(obs, command_finished, (retval, status, err_msg,
obs->args));
+
+ free(err_msg);
+
+ if (geteuid() == 0)
+ {
+ /* Reset mode/uig/gid to correct values for all files created by
event run */
+ struct dump_dir *dd = dd_opendir(dump_dir_name, 0);
+ if (dd)
+ {
+ dd_sanitize_mode_and_owner(dd);
+ dd_close(dd);
+ }
+ }
+
if (retval != 0)
break;
@@ -543,6 +593,244 @@ int run_event_on_dir_name(struct run_event_state *state,
return retval;
}
+static void text_alert(const char *msg, void *args)
+{
+ printf("%s\n", msg);
+ fflush(stdout);
+}
+
+static char *text_ask(const char *msg, void *args)
+{
+ printf("%s ", msg);
+ fflush(stdout);
+ char buf[256];
+ if (!fgets(buf, sizeof(buf), stdin))
+ buf[0] = '\0';
+ else
+ strtrimch(buf,'\n');
+
+ return xstrdup(buf);
+}
+
+static int text_ask_yes_no(const char *msg, void *args)
+{
+ printf("%s [%s/%s] ", msg, _("y"), _("N"));
+ fflush(stdout);
+ char buf[16];
+ if (!fgets(buf, sizeof(buf), stdin))
+ return false;
+
+ return buf[0] == 'y' && (buf[1] == '\n' || buf[1] == '\0');
+}
+
+static int text_ask_password(const char *msg, char **password, void *args)
+{
+ printf("%s ", msg);
+ fflush(stdout);
+ char buf[256];
+ bool changed = set_echo(false);
+ bool got = fgets(buf, sizeof(buf), stdin);
+
+ if (changed)
+ set_echo(true);
+
+ if (got && password)
+ {
+ strtrimch(buf,'\n');
+ *password = xstrdup(buf);
+ }
+
+ return got;
+}
+
+enum event_log_state {
+ ELS_LOGSTATE_FIRSTLINE = 0,
+ ELS_LOGSTATE_BEGLINE,
+ ELS_LOGSTATE_ERRLINE,
+ ELS_LOGSTATE_MIDLINE,
+};
+
+struct log_event_data
+{
+ char *dump_dir_name;
+ struct strbuf *event_log;
+ enum event_log_state event_log_state;
+ const struct run_event_observer *wrapped_obs;
+};
+
+static void save_to_event_log(struct log_event_data *led, const char *str)
+{
+ static const char delim[] = {
+ [ELS_LOGSTATE_FIRSTLINE] = '>',
+ [ELS_LOGSTATE_BEGLINE] = ' ',
+ [ELS_LOGSTATE_ERRLINE] = '*',
+ };
+
+ while (str[0])
+ {
+ char *end = strchrnul(str, '\n');
+ char end_char = *end;
+ if (end_char == '\n')
+ end++;
+ switch (led->event_log_state)
+ {
+ case ELS_LOGSTATE_FIRSTLINE:
+ case ELS_LOGSTATE_BEGLINE:
+ case ELS_LOGSTATE_ERRLINE:
+ /* skip empty lines */
+ if (str[0] == '\n')
+ goto next_save_to_event_log;
+ strbuf_append_strf(led->event_log, "%s%c %.*s",
+ iso_date_string(NULL),
+ delim[led->event_log_state],
+ (int)(end - str), str
+ );
+ break;
+ case ELS_LOGSTATE_MIDLINE:
+ strbuf_append_strf(led->event_log, "%.*s", (int)(end - str),
str);
+ break;
+ }
+ led->event_log_state = ELS_LOGSTATE_MIDLINE;
+ if (end_char != '\n')
+ break;
+ led->event_log_state = ELS_LOGSTATE_BEGLINE;
+next_save_to_event_log:
+ str = end;
+ }
+}
+
+static void update_event_log_on_disk(const char *str, const char
*dump_dir_name)
+{
+ /* Load existing log */
+ struct dump_dir *dd = dd_opendir(dump_dir_name, 0);
+ if (!dd)
+ return;
+ char *event_log = dd_load_text_ext(dd, FILENAME_EVENT_LOG,
DD_FAIL_QUIETLY_ENOENT);
+
+ /* Append new log part to existing log */
+ unsigned len = strlen(event_log);
+ if (len != 0 && event_log[len - 1] != '\n')
+ event_log = append_to_malloced_string(event_log, "\n");
+ event_log = append_to_malloced_string(event_log, str);
+
+ /* Trim log according to size watermarks */
+ len = strlen(event_log);
+ char *new_log = event_log;
+ if (len > EVENT_LOG_HIGH_WATERMARK)
+ {
+ new_log += len - EVENT_LOG_LOW_WATERMARK;
+ new_log = strchrnul(new_log, '\n');
+ if (new_log[0])
+ new_log++;
+ }
+
+ /* Save */
+ dd_save_text(dd, FILENAME_EVENT_LOG, new_log);
+ free(event_log);
+ dd_close(dd);
+}
+
+static void event_log_command_started(pid_t pid, void *args)
+{
+ struct log_event_data *led = (struct log_event_data *)args;
+
+ if (led->wrapped_obs->command_started)
+ led->wrapped_obs->command_started(pid, led->wrapped_obs->args);
+}
+
+static void event_log_command_msg_processed(const char *msg, const char
*raw_input, const char *rsp, void *args)
+{
+ struct log_event_data *led = (struct log_event_data *)args;
+ save_to_event_log(led, msg);
+
+ if (led->wrapped_obs->command_msg_processed)
+ led->wrapped_obs->command_msg_processed(msg, raw_input, rsp,
led->wrapped_obs->args);
+}
+
+static void event_log_command_finished(int retval, int status, const char
*err_msg, void *args)
+{
+ struct log_event_data *led = (struct log_event_data *)args;
+
+ if (retval != 0 || led->event_log_state == ELS_LOGSTATE_FIRSTLINE)
+ {
+ if (retval != 0) /* If program failed, emit error line */
+ led->event_log_state = ELS_LOGSTATE_ERRLINE;
+
+ save_to_event_log(led, err_msg);
+ }
+
+ /* Append log to FILENAME_EVENT_LOG */
+ update_event_log_on_disk(led->event_log->buf, led->dump_dir_name);
+ strbuf_clear(led->event_log);
+ led->event_log_state = ELS_LOGSTATE_FIRSTLINE;
+
+ if (led->wrapped_obs->command_finished)
+ led->wrapped_obs->command_finished(retval, status, err_msg,
led->wrapped_obs->args);
+}
+
+struct run_event_observer *new_command_log_run_event_observer(char
*dump_dir_name, const struct run_event_observer *wrapped_obs)
+{
+ struct log_event_data *led = xmalloc(sizeof(*led));
+ led->dump_dir_name = xstrdup(dump_dir_name);
+ led->event_log = strbuf_new();
+ led->event_log_state = ELS_LOGSTATE_FIRSTLINE;
+ led->wrapped_obs = wrapped_obs;
+
+ struct run_event_observer *impl = xmalloc(sizeof(*impl));
+ /* cannot use callbacks from wrapped because of args */
+ impl->command_started = event_log_command_started;
+ impl->command_msg_processed = event_log_command_msg_processed;
+ impl->command_finished = event_log_command_finished;
+ impl->args = led;
+
+ return impl;
+}
+
+void free_command_log_run_event_observer(struct run_event_observer *impl)
+{
+ struct log_event_data *led = (struct log_event_data *)impl->args;
+ free(led->dump_dir_name);
+ strbuf_free(led->event_log);
+ free(led);
+ free(impl);
+}
+
+struct run_event_impl *new_text_run_event_impl()
+{
+ struct run_event_impl *impl = xmalloc(sizeof(*impl));
+
+ impl->alert = text_alert;
+ impl->ask = text_ask;
+ impl->ask_yes_no = text_ask_yes_no;
+ impl->ask_password = text_ask_password;
+ impl->args = NULL;
+
+ return impl;
+}
+
+void free_text_run_event_impl(struct run_event_impl *impl)
+{
+ free(impl);
+}
+
+/* Synchronous command execution:
+ */
+int run_event_on_dir_name(struct run_event_state *state,
+ const char *dump_dir_name,
+ const char *event)
+{
+ if (prepare_commands(state, dump_dir_name, event) == 0)
+ return -1;
+
+ struct run_event_impl *impl = new_text_run_event_impl();
+
+ const int ret = run_event_state_on_dir_name(state, dump_dir_name, event,
impl, NULL);
+
+ free_text_run_event_impl(impl);
+
+ return ret;
+}
+
int run_event_on_problem_data(struct run_event_state *state, problem_data_t
*data, const char *event)
{
state->children_count = 0;
--
1.7.10.2