libreport/0006-support-interactive-plugins-in-GUI-and-CLI.patch
2011-07-21 19:35:08 +02:00

497 lines
17 KiB
Diff

From da506c2b0f396ccb5a3f7c83b8fd976a763dd1ed Mon Sep 17 00:00:00 2001
From: Michal Toman <mtoman@redhat.com>
Date: Tue, 19 Jul 2011 13:43:15 +0200
Subject: [PATCH 06/12] support interactive plugins in GUI and CLI
---
src/cli/cli-report.c | 19 +-----
src/gui-wizard-gtk/wizard.c | 179 ++++++++++++++++++++++++++++++++++++++++++-
src/include/client.h | 7 ++
src/include/run_event.h | 1 +
src/lib/client.c | 43 +++++++++--
src/lib/run_event.c | 72 ++++++++++++++++-
6 files changed, 288 insertions(+), 33 deletions(-)
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 847e84c..b100c13 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 <gtk/gtk.h>
+#include "client.h"
#include "internal_libreport_gtk.h"
#include "wizard.h"
@@ -1051,14 +1052,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 <glob.h>
#include <regex.h>
+#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 */
--
1.7.6