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

Reply via email to