From d09af1b80697c4ed02e420ca89f2675b14b0f47b Mon Sep 17 00:00:00 2001 From: Nikola Pajkovsky Date: Mon, 13 Jun 2011 13:33:30 +0200 Subject: move libreport into its own git ssh://git.fedorahosted.org/git/libreport.git --- libreport/src/lib/run_event.c | 551 ------------------------------------------ 1 file changed, 551 deletions(-) delete mode 100644 libreport/src/lib/run_event.c (limited to 'libreport/src/lib/run_event.c') diff --git a/libreport/src/lib/run_event.c b/libreport/src/lib/run_event.c deleted file mode 100644 index 0457364a..00000000 --- a/libreport/src/lib/run_event.c +++ /dev/null @@ -1,551 +0,0 @@ -/* - Copyright (C) 2009 Zdenek Prikryl (zprikryl@redhat.com) - Copyright (C) 2009 RedHat inc. - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ -#include -#include -#include "libreport.h" - -struct run_event_state *new_run_event_state() -{ - return xzalloc(sizeof(struct run_event_state)); -} - -void free_run_event_state(struct run_event_state *state) -{ - if (state) - { - free_commands(state); - free(state); - } -} - - -/* Asynchronous command execution */ - -/* It is not yet clear whether we need to re-parse event config file - * and re-check the elements in dump dir after each command. - * - * Consider this config file: - * - * EVENT=e cmd1 - * EVENT=e foo=bar cmd2 - * EVENT=e foo=baz cmd3 - * - * Imagine that element foo existed and was equal to bar at the beginning. - * After cmd1, should we execute cmd2 if element foo disappeared? - * After cmd1/2, should we execute cmd3 if element foo changed value to baz? - * - * We used to read entire config file and select a list of commands to execute, - * checking all conditions in the beginning. It is a bit more simple to code up. - * - * This proved to be bad for use cases where, for example, post-create rule - * for a specified package needs to run: - * - * EVENT=post-create - * abrt-action-save-package-data - * EVENT=post-create component=mypkg - * my_handling - * - * Problem here is that "component" element is created by - * abrt-action-save-package-data! Pre-selecting rules excludes second rule. - * - * Now we read entire config but do NOT select commands to execute, - * we check conditions of every next rule *directly before its execution*. - * - * It's possible we'd want to switch to an algorightm which makes in unnecessary - * to properly order rules in config files(s). Now these two rules must not - * be reordered, or else second one won't work: - * EVENT=post-create echo foo >bar - * EVENT=post-create foo=bar do_something - * but this might be not so easy to ensure when include files are involved... - * - * Anyway, list of commands machinery is encapsulated in struct run_event_state, - * and public async API: - * prepare_commands(state, dir, event); - * spawn_next_command(state, dir, event); - * free_commands(state); - * does not expose the way we select rules to execute. - */ -struct rule { - GList *conditions; - char *command; /* never NULL */ -}; - -static void free_rule_list(GList *rule_list) -{ - while (rule_list) - { - struct rule *cur_rule = rule_list->data; - list_free_with_free(cur_rule->conditions); - free(cur_rule->command); - free(cur_rule); - - GList *next = rule_list->next; - g_list_free_1(rule_list); - rule_list = next; - } -} - -/* Stop-gap measure against infinite recursion */ -#define MAX_recursion_depth 32 - -static GList *load_rule_list(GList *rule_list, - const char *conf_file_name, - unsigned recursion_depth -) { - FILE *conffile = fopen(conf_file_name, "r"); - if (!conffile) - { - error_msg("Can't open '%s'", conf_file_name); - return rule_list; - } - - /* Read and remember rules */ - char *next_line = xmalloc_fgetline(conffile); - while (next_line) - { - /* Read and concatenate all lines in a rule */ - char *line = next_line; - while (1) - { - next_line = xmalloc_fgetline(conffile); - if (!next_line || !isblank(next_line[0])) - break; - char *old_line = line; - line = xasprintf("%s\n%s", line, next_line); - free(old_line); - free(next_line); - } - - char *p = skip_whitespace(line); - if (*p == '\0' || *p == '#') - goto next_line; /* empty or comment line, skip */ - - //VERB3 log("%s: line '%s'", __func__, p); - - /* Handle "include" directive */ - if (recursion_depth < MAX_recursion_depth - && strncmp(p, "include", strlen("include")) == 0 - && isblank(p[strlen("include")]) - ) { - /* "include GLOB_PATTERN" */ - p = skip_whitespace(p + strlen("include")); - - const char *last_slash; - char *name_to_glob; - if (*p != '/' - && (last_slash = strrchr(conf_file_name, '/')) != NULL - ) - /* GLOB_PATTERN is relative, and this include is in path/to/file.conf - * Construct path/to/GLOB_PATTERN: - */ - name_to_glob = xasprintf("%.*s%s", (int)(last_slash - conf_file_name + 1), conf_file_name, p); - else - /* Either GLOB_PATTERN is absolute, or this include is in file.conf - * (no slashes in its name). Use unchanged GLOB_PATTERN: - */ - name_to_glob = xstrdup(p); - - glob_t globbuf; - memset(&globbuf, 0, sizeof(globbuf)); - //VERB3 log("%s: globbing '%s'", __func__, name_to_glob); - glob(name_to_glob, 0, NULL, &globbuf); - free(name_to_glob); - char **name = globbuf.gl_pathv; - if (name) while (*name) - { - //VERB3 log("%s: recursing into '%s'", __func__, *name); - rule_list = load_rule_list(rule_list, *name, recursion_depth + 1); - //VERB3 log("%s: returned from '%s'", __func__, *name); - name++; - } - globfree(&globbuf); - goto next_line; - } - - /* Rule has form: [VAR=VAL]... PROG [ARGS] */ - struct rule *cur_rule = xzalloc(sizeof(*cur_rule)); - - while (1) /* word loop */ - { - char *end_word = skip_non_whitespace(p); - - /* If there is no '=' in this word... */ - char *line_val = strchr(p, '='); - if (!line_val || line_val >= end_word) - break; /* ...we found the start of a command */ - - cur_rule->conditions = g_list_append(cur_rule->conditions, xstrndup(p, end_word - p)); - - /* Go to next word */ - p = skip_whitespace(end_word); - } /* end of word loop */ - - VERB1 log("Adding '%s'", p); - cur_rule->command = xstrdup(p); - - rule_list = g_list_append(rule_list, cur_rule); - - next_line: - free(line); - } /* end of line loop */ - - fclose(conffile); - - return rule_list; -} - -static int regcmp_lines(char *val, const char *regex) -{ - regex_t rx; - int r = regcomp(&rx, regex, REG_NOSUB); //TODO: and REG_EXTENDED? - //log("REGEX:'%s':%d", regex, r); - if (r) - { - //char errbuf[256]; - //size_t needsz = regerror(r, &rx, errbuf, sizeof(errbuf)); - error_msg("Bad regexp '%s'", regex); // TODO: use errbuf? - return r; - } - - /* Check every line */ - while (1) - { - char *eol = strchr(val, '\n'); - if (eol) - *eol = '\0'; - r = regexec(&rx, val, 0, NULL, /*eflags:*/ 0); - //log("REGCMP:'%s':%d", val, r); - if (eol) - *eol = '\n'; - if (r == 0 || !eol) - break; - val = eol + 1; - } - /* Here, r == 0 if match was found */ - regfree(&rx); - return r; -} - -/* Deletes rules in *pp_rule_list, starting from first (remaining) rule, - * until it finds a rule with all conditions satisfied. - * In this case, it deletes this rule and returns this rule's cmd. - * Else (if it didn't find such rule), it deletes all rules and returns NULL. - * In case of error (dump_dir can't be opened), deletes all rules and returns NULL. - * - * Intended usage: - * list = load_rule_list(...); - * while ((cmd = pop_next_command(&list, ...)) != NULL) - * run(cmd); - */ -static char* pop_next_command(GList **pp_rule_list, - char **pp_event_name, /* reports EVENT value thru this, if not NULL on entry */ - struct dump_dir **pp_dd, /* use *pp_dd for access to dump dir, if non-NULL */ - const char *dump_dir_name, - const char *pfx, - unsigned pfx_len -) -{ - char *command = NULL; - struct dump_dir *dd = pp_dd ? *pp_dd : NULL; - - GList *rule_list = *pp_rule_list; - while (rule_list) - { - struct rule *cur_rule = rule_list->data; - - GList *condition = cur_rule->conditions; - while (condition) - { - const char *cond_str = condition->data; - const char *eq_sign = strchr(cond_str, '='); - - /* Is it "EVENT=foo"? */ - if (strncmp(cond_str, "EVENT=", 6) == 0) - { - if (strncmp(eq_sign + 1, pfx, pfx_len) != 0) - goto next_rule; /* prefix doesn't match */ - if (pp_event_name) - { - free(*pp_event_name); - *pp_event_name = xstrdup(eq_sign + 1); - } - } - else - { - /* Read from dump dir and compare */ - if (!dd) - { - /* Without dir to match, we assume match for all conditions */ - if (!dump_dir_name) - goto next_cond; - dd = dd_opendir(dump_dir_name, /*flags:*/ 0); - if (!dd) - { - free_rule_list(rule_list); - *pp_rule_list = NULL; - goto ret; /* error (note: dd_opendir logged error msg) */ - } - } - /* Is it "VAR~=REGEX"? */ - int regex = (eq_sign > cond_str && eq_sign[-1] == '~'); - char *var_name = xstrndup(cond_str, eq_sign - cond_str - regex); - char *real_val = dd_load_text_ext(dd, var_name, DD_FAIL_QUIETLY_ENOENT); - free(var_name); - int vals_differ = regex ? regcmp_lines(real_val, eq_sign + 1) : strcmp(real_val, eq_sign + 1); - free(real_val); - - /* Do values match? */ - if (vals_differ) /* no */ - { - //VERB3 log("var '%s': '%.*s'!='%s', skipping line", - // p, - // (int)(strchrnul(real_val, '\n') - real_val), real_val, - // eq_sign); - goto next_rule; - } - } - next_cond: - /* We are here if current condition is satisfied */ - - condition = condition->next; - } - /* We are here if all conditions are satisfied */ - - command = cur_rule->command; - - next_rule: - *pp_rule_list = rule_list->next; - g_list_free_1(rule_list); - - list_free_with_free(cur_rule->conditions); - /*free(cur_rule->command); - WRONG! we might be returning it! */ - if (command) - { - /* We found rule to run, return it */ - free(cur_rule); - break; - } - free(cur_rule->command); /* _now_ it is ok */ - free(cur_rule); - - rule_list = *pp_rule_list; - } /* while (rule_list) */ - - ret: - if (pp_dd) - *pp_dd = dd; - else - dd_close(dd); - return command; -} - -void free_commands(struct run_event_state *state) -{ - free_rule_list(state->rule_list); - state->rule_list = NULL; - state->command_out_fd = -1; - state->command_pid = 0; -} - -int prepare_commands(struct run_event_state *state, - const char *dump_dir_name, - const char *event -) { - free_commands(state); - - state->children_count = 0; - - GList *rule_list = load_rule_list(NULL, CONF_DIR"/report_event.conf", /*recursion_depth:*/ 0); - state->rule_list = rule_list; - return rule_list != NULL; -} - -int spawn_next_command(struct run_event_state *state, - const char *dump_dir_name, - const char *event -) { - char *cmd = pop_next_command(&state->rule_list, - NULL, /* don't return event_name */ - NULL, /* NULL &dd: we match by... */ - dump_dir_name, /* ...dirname */ - event, strlen(event)+1 /* for this event name exactly (not prefix) */ - ); - if (!cmd) - return -1; - - /* We count it even if fork fails. The counter isn't meant - * to count *successful* forks, it is meant to let caller know - * whether the event we run has *any* handlers configured, or not. - */ - state->children_count++; - - VERB1 log("Executing '%s'", cmd); - - /* Export some useful environment variables for children */ - char *env_vec[3]; - /* Just exporting dump_dir_name isn't always ok: it can be "." - * and some children want to cd to other directory but still - * be able to find dump directory by using $DUMP_DIR... - */ - char *full_name = realpath(dump_dir_name, NULL); - env_vec[0] = xasprintf("DUMP_DIR=%s", (full_name ? full_name : dump_dir_name)); - free(full_name); - env_vec[1] = xasprintf("EVENT=%s", event); - env_vec[2] = NULL; - - char *argv[4]; - argv[0] = (char*)"/bin/sh"; // TODO: honor $SHELL? - argv[1] = (char*)"-c"; - argv[2] = cmd; - argv[3] = NULL; - - int pipefds[2]; - state->command_pid = fork_execv_on_steroids( - EXECFLG_INPUT_NUL + EXECFLG_OUTPUT + EXECFLG_ERR2OUT, - argv, - pipefds, - /* env_vec: */ env_vec, - /* dir: */ dump_dir_name, - /* uid(unused): */ 0 - ); - state->command_out_fd = pipefds[0]; - - free(env_vec[0]); - free(env_vec[1]); - free(cmd); - - return 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 */ - - 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; - while ((buf = xmalloc_fgetline(fp)) != NULL) - { - if (state->logging_callback) - buf = state->logging_callback(buf, state->logging_param); - free(buf); - } - fclose(fp); /* Got EOF, close. This also closes state->command_out_fd */ - - /* Wait for child to actually exit, collect status */ - int status; - waitpid(state->command_pid, &status, 0); - - retval = WEXITSTATUS(status); - if (WIFSIGNALED(status)) - retval = WTERMSIG(status) + 128; - if (retval != 0) - break; - - if (state->post_run_callback) - { - retval = state->post_run_callback(dump_dir_name, state->post_run_param); - if (retval != 0) - break; - } - } - - free_commands(state); - - return retval; -} - -int run_event_on_problem_data(struct run_event_state *state, problem_data_t *data, const char *event) -{ - state->children_count = 0; - - struct dump_dir *dd = create_dump_dir_from_problem_data(data, NULL); - if (!dd) - return -1; - char *dir_name = xstrdup(dd->dd_dirname); - dd_close(dd); - - int r = run_event_on_dir_name(state, dir_name, event); - - g_hash_table_remove_all(data); - dd = dd_opendir(dir_name, /*flags:*/ 0); - free(dir_name); - if (dd) - { - load_problem_data_from_dump_dir(data, dd, NULL); - dd_delete(dd); - } - - return r; -} - -char *list_possible_events(struct dump_dir *dd, const char *dump_dir_name, const char *pfx) -{ - struct strbuf *result = strbuf_new(); - - GList *rule_list = load_rule_list(NULL, CONF_DIR"/report_event.conf", /*recursion_depth:*/ 0); - - unsigned pfx_len = strlen(pfx); - for (;;) - { - /* Retrieve each cmd, and fetch its EVENT=foo value */ - char *event_name = NULL; - char *cmd = pop_next_command(&rule_list, - &event_name, /* return event_name */ - (dd ? &dd : NULL), /* match this dd... */ - dump_dir_name, /* ...or if NULL, this dirname */ - pfx, pfx_len /* for events with this prefix */ - ); - if (!cmd) - break; - free(cmd); - - if (event_name) - { - /* Append "EVENT\n" - only if it is not there yet */ - unsigned e_len = strlen(event_name); - char *p = result->buf; - while (p && *p) - { - if (strncmp(p, event_name, e_len) == 0 && p[e_len] == '\n') - goto skip; /* This event is already in the result */ - p = strchr(p, '\n'); - if (p) - p++; - } - strbuf_append_strf(result, "%s\n", event_name); - skip: - free(event_name); - } - } - - return strbuf_free_nobuf(result); -} -- cgit