diff --git a/src/cli/cli-report.c b/src/cli/cli-report.c index 2598a7a..784b37e 100644 --- a/src/cli/cli-report.c +++ b/src/cli/cli-report.c @@ -18,6 +18,7 @@ #include "internal_libreport.h" #include "run-command.h" #include "cli-report.h" +#include "client.h" /* Field separator for the crash report file that is edited by user. */ #define FIELD_SEP "%----" @@ -404,24 +405,6 @@ static bool ask_yesno(const char *question) return 0 == strncmp(answer, yes, strlen(yes)); } -/* Returns true if echo has been changed from another state. */ -static bool set_echo(bool enable) -{ - struct termios t; - if (tcgetattr(STDIN_FILENO, &t) < 0) - return false; - - /* No change needed? */ - if ((bool)(t.c_lflag & ECHO) == enable) - return false; - - t.c_lflag ^= ECHO; - if (tcsetattr(STDIN_FILENO, TCSANOW, &t) < 0) - perror_msg_and_die("tcsetattr"); - - return true; -} - /* Returns true if the string contains the specified number. */ static bool is_number_in_string(unsigned number, const char *str) { diff --git a/src/gui-wizard-gtk/wizard.c b/src/gui-wizard-gtk/wizard.c index 6ed6713..7a0e7e5 100644 --- a/src/gui-wizard-gtk/wizard.c +++ b/src/gui-wizard-gtk/wizard.c @@ -17,6 +17,7 @@ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include +#include "client.h" #include "internal_libreport_gtk.h" #include "wizard.h" @@ -1045,14 +1046,186 @@ static gboolean consume_cmd_output(GIOChannel *source, GIOCondition condition, g /* Read and insert the output into the log pane */ char buf[257]; /* usually we get one line, no need to have big buf */ + char *msg; /* one line */ + char *newline; + char *raw; int r; - while ((r = read(evd->fd, buf, sizeof(buf)-1)) > 0) + struct strbuf *line = strbuf_new(); + + 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); + + /* read buffered and split lines */ + while ((r = read(evd->fd, buf, sizeof(buf) - 1)) > 0) { buf[r] = '\0'; - append_to_textview(evd->tv_log, buf); - save_to_event_log(evd, buf); + raw = buf; + + /* split lines in the current buffer */ + while ((newline = strchr(raw, '\n')) != NULL) + { + *newline = '\0'; + /* finish line */ + strbuf_append_str(line, raw); + strbuf_append_char(line, '\n'); + + msg = line->buf; + + /* alert dialog */ + if (strncmp(REPORT_PREFIX_ALERT, msg, alert_prefix_len) == 0) + { + msg += alert_prefix_len; + + GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(g_assistant), + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_WARNING, + GTK_BUTTONS_CLOSE, + msg); + + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + } + /* ask dialog with textbox */ + else if (strncmp(REPORT_PREFIX_ASK, msg, ask_prefix_len) == 0) + { + msg += ask_prefix_len; + + GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(g_assistant), + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_QUESTION, + GTK_BUTTONS_OK_CANCEL, + msg); + + GtkWidget *vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); + GtkWidget *textbox = gtk_entry_new(); + gtk_entry_set_editable(GTK_ENTRY(textbox), TRUE); + gtk_box_pack_start(GTK_BOX(vbox), textbox, TRUE, TRUE, 0); + gtk_widget_show(textbox); + + if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_OK) + { + const char *text = gtk_entry_get_text(GTK_ENTRY(textbox)); + char *response = xasprintf("%s\n", text); + if (write(evd->run_state->command_in_fd, response, strlen(response)) < 0) + { + free(response); + VERB1 perror_msg("Unable to write %s\\n to child's stdin", text); + return FALSE; + } + + free(response); + } + else + { + if (write(evd->run_state->command_in_fd, "\n", strlen("\n")) < 0) + { + VERB1 perror_msg("Unable to write \\n to child's stdin"); + return FALSE; + } + } + + gtk_widget_destroy(textbox); + gtk_widget_destroy(dialog); + } + /* ask dialog with passwordbox */ + else if (strncmp(REPORT_PREFIX_ASK_PASSWORD, msg, ask_password_prefix_len) == 0) + { + msg += ask_password_prefix_len; + + GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(g_assistant), + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_QUESTION, + GTK_BUTTONS_OK_CANCEL, + msg); + + GtkWidget *vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); + GtkWidget *textbox = gtk_entry_new(); + gtk_entry_set_editable(GTK_ENTRY(textbox), TRUE); + gtk_entry_set_visibility(GTK_ENTRY(textbox), FALSE); + gtk_box_pack_start(GTK_BOX(vbox), textbox, TRUE, TRUE, 0); + gtk_widget_show(textbox); + + if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_OK) + { + const char *text = gtk_entry_get_text(GTK_ENTRY(textbox)); + char *response = xasprintf("%s\n", text); + if (write(evd->run_state->command_in_fd, response, strlen(response)) < 0) + { + free(response); + VERB1 perror_msg("Unable to write %s\\n to child's stdin", text); + return FALSE; + } + + free(response); + } + else + { + if (write(evd->run_state->command_in_fd, "\n", strlen("\n")) < 0) + { + VERB1 perror_msg("Unable to write \\n to child's stdin"); + return FALSE; + } + } + + gtk_widget_destroy(textbox); + gtk_widget_destroy(dialog); + } + /* yes/no dialog */ + else if (strncmp(REPORT_PREFIX_ASK_YES_NO, msg, ask_yes_no_prefix_len) == 0) + { + msg += ask_yes_no_prefix_len; + + GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(g_assistant), + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_QUESTION, + GTK_BUTTONS_YES_NO, + msg); + + if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_YES) + { + char *yes = _("y"); + char *response = xasprintf("%s\n", yes); + if (write(evd->run_state->command_in_fd, response, strlen(response)) < 0) + { + free(response); + VERB1 perror_msg("Unable to write %s\\n to child's stdin", yes); + return FALSE; + } + + free(response); + } + else + { + if (write(evd->run_state->command_in_fd, "\n", strlen("\n")) < 0) + { + VERB1 perror_msg("Unable to write \\n to child's stdin"); + return FALSE; + } + } + + gtk_widget_destroy(dialog); + } + /* no special prefix - forward to log */ + else + { + append_to_textview(evd->tv_log, msg); + save_to_event_log(evd, msg); + } + + strbuf_clear(line); + + /* jump to next line */ + raw = newline + 1; + } + + /* beginning of next line. the line continues by next read() */ + strbuf_append_str(line, raw); } + strbuf_free(line); + if (r < 0 && errno == EAGAIN) /* We got all buffered data, but fd is still open. Done for now */ return TRUE; /* "please don't remove this event (yet)" */ diff --git a/src/include/client.h b/src/include/client.h index bbd2f10..8074145 100644 --- a/src/include/client.h +++ b/src/include/client.h @@ -22,18 +22,25 @@ #define REPORT_PREFIX_ASK_YES_NO "ASK_YES_NO " #define REPORT_PREFIX_ASK "ASK " +#define REPORT_PREFIX_ASK_PASSWORD "ASK_PASSWORD " #define REPORT_PREFIX_ALERT "ALERT " #ifdef __cplusplus extern "C" { #endif +#define set_echo libreport_set_echo +int set_echo(int enable); + #define ask_yes_no libreport_ask_yes_no int ask_yes_no(const char *question); #define ask libreport_ask char *ask(const char *question, char *response, int response_len); +#define ask_password libreport_ask_password +char *ask_password(const char *question, char *response, int response_len); + #define alert libreport_alert void alert(const char *message); diff --git a/src/include/run_event.h b/src/include/run_event.h index f7ae9ed..43730ce 100644 --- a/src/include/run_event.h +++ b/src/include/run_event.h @@ -44,6 +44,7 @@ struct run_event_state { GList *rule_list; pid_t command_pid; int command_out_fd; + int command_in_fd; }; struct run_event_state *new_run_event_state(void); void free_run_event_state(struct run_event_state *state); diff --git a/src/lib/client.c b/src/lib/client.c index 88a995b..103828b 100644 --- a/src/lib/client.c +++ b/src/lib/client.c @@ -25,6 +25,24 @@ static int is_slave_mode() return getenv("REPORT_CLIENT_SLAVE") != NULL; } +/* Returns 1 if echo has been changed from another state. */ +int set_echo(int enable) +{ + struct termios t; + if (tcgetattr(STDIN_FILENO, &t) < 0) + return 0; + + /* No change needed? */ + if ((t.c_lflag & ECHO) == enable) + return 0; + + t.c_lflag ^= ECHO; + if (tcsetattr(STDIN_FILENO, TCSANOW, &t) < 0) + perror_msg_and_die("tcsetattr"); + + return 1; +} + int ask_yes_no(const char *question) { const char *yes = _("y"); @@ -32,12 +50,7 @@ int ask_yes_no(const char *question) char *env_response = getenv("REPORT_CLIENT_RESPONSE"); if (env_response) - { - if (strncasecmp(yes, env_response, strlen(yes)) == 0) - return true; - if (strncasecmp(no, env_response, strlen(no)) == 0) - return false; - } + return strncasecmp(yes, env_response, strlen(yes)) == 0; if (is_slave_mode()) printf(REPORT_PREFIX_ASK_YES_NO "%s\n", question); @@ -48,7 +61,7 @@ int ask_yes_no(const char *question) char response[16]; if (NULL == fgets(response, sizeof(response), stdin)) - return false; + return 0; return strncasecmp(yes, response, strlen(yes)) == 0; } @@ -65,6 +78,22 @@ char *ask(const char *question, char *response, int response_len) return fgets(response, response_len, stdin); } +char *ask_password(const char *question, char *response, int response_len) +{ + if (is_slave_mode()) + printf(REPORT_PREFIX_ASK_PASSWORD "%s\n", question); + else + printf("%s ", question); + + fflush(stdout); + + set_echo(false); + char *result = fgets(response, response_len, stdin); + set_echo(true); + + return result; +} + void alert(const char *message) { if (is_slave_mode()) diff --git a/src/lib/run_event.c b/src/lib/run_event.c index 0594bde..ba9920c 100644 --- a/src/lib/run_event.c +++ b/src/lib/run_event.c @@ -18,6 +18,7 @@ */ #include #include +#include "client.h" #include "internal_libreport.h" struct run_event_state *new_run_event_state() @@ -393,7 +394,7 @@ int spawn_next_command(struct run_event_state *state, VERB1 log("Executing '%s'", cmd); /* Export some useful environment variables for children */ - char *env_vec[3]; + char *env_vec[4]; /* 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... @@ -402,7 +403,8 @@ int spawn_next_command(struct run_event_state *state, 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; + env_vec[2] = xasprintf("REPORT_CLIENT_SLAVE=1"); + env_vec[3] = NULL; char *argv[4]; argv[0] = (char*)"/bin/sh"; // TODO: honor $SHELL? @@ -412,7 +414,7 @@ int spawn_next_command(struct run_event_state *state, int pipefds[2]; state->command_pid = fork_execv_on_steroids( - EXECFLG_INPUT_NUL + EXECFLG_OUTPUT + EXECFLG_ERR2OUT, + EXECFLG_INPUT + EXECFLG_OUTPUT + EXECFLG_ERR2OUT, argv, pipefds, /* env_vec: */ env_vec, @@ -420,9 +422,11 @@ int spawn_next_command(struct run_event_state *state, /* uid(unused): */ 0 ); state->command_out_fd = pipefds[0]; + state->command_in_fd = pipefds[1]; free(env_vec[0]); free(env_vec[1]); + free(env_vec[2]); free(cmd); return 0; @@ -447,10 +451,68 @@ int run_event_on_dir_name(struct run_event_state *state, 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) { - if (state->logging_callback) - buf = state->logging_callback(buf, state->logging_param); + msg = 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) + { + msg += ask_password_prefix_len; + printf("%s ", msg); + fflush(stdout); + char buf[256]; + set_echo(false); + if (!fgets(buf, sizeof(buf), stdin)) + buf[0] = '\0'; + set_echo(true); + + if (write(state->command_in_fd, buf, strlen(buf)) < 0) + perror_msg_and_die("write"); + } + /* no special prefix -> forward to log if applicable */ + else if (state->logging_callback) + msg = state->logging_callback(msg, state->logging_param); free(buf); } fclose(fp); /* Got EOF, close. This also closes state->command_out_fd */