fix crash when performing an action on a mime part
[claws.git] / src / action.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2003 Hiroyuki Yamamoto
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23
24 #include "defs.h"
25
26 #include <glib.h>
27 #include <gtk/gtk.h>
28 #include <gdk/gdkkeysyms.h>
29 #include <gdk/gdkx.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <errno.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <signal.h>
37 #include <unistd.h>
38
39 #include "intl.h"
40 #include "utils.h"
41 #include "gtkutils.h"
42 #include "manage_window.h"
43 #include "mainwindow.h"
44 #include "prefs_common.h"
45 #include "alertpanel.h"
46 #include "inputdialog.h"
47 #include "action.h"
48 #include "compose.h"
49 #include "procmsg.h"
50 #include "gtkstext.h"
51 #include "textview.h"
52
53 typedef struct _Children                Children;
54 typedef struct _ChildInfo               ChildInfo;
55 typedef struct _UserStringDialog        UserStringDialog;
56
57 struct _Children
58 {
59         GtkWidget       *dialog;
60         GtkWidget       *text;
61         GtkWidget       *input_entry;
62         GtkWidget       *input_hbox;
63         GtkWidget       *abort_btn;
64         GtkWidget       *close_btn;
65         GtkWidget       *scrolledwin;
66
67         gchar           *action;
68         GSList          *list;
69         gint             nb;
70         gint             open_in;
71         gboolean         output;
72
73         GtkWidget       *msg_text;
74         GdkFont         *msgfont;
75 };
76
77 struct _ChildInfo
78 {
79         Children        *children;
80         gchar           *cmd;
81         guint            type;
82         pid_t            pid;
83         gint             chld_in;
84         gint             chld_out;
85         gint             chld_err;
86         gint             chld_status;
87         gint             tag_in;
88         gint             tag_out;
89         gint             tag_err;
90         gint             tag_status;
91         gint             new_out;
92
93         GString         *output;
94 };
95
96 static void action_update_menu          (GtkItemFactory *ifactory,
97                                          gchar          *branch_path,
98                                          gpointer        callback,
99                                          gpointer        data);
100 static void compose_actions_execute_cb  (Compose        *compose,
101                                          guint           action_nb,
102                                          GtkWidget      *widget);
103 static void mainwin_actions_execute_cb  (MainWindow     *mainwin,
104                                          guint           action_nb,
105                                          GtkWidget      *widget);
106 static void msgview_actions_execute_cb  (MessageView    *msgview,
107                                          guint           action_nb,
108                                          GtkWidget      *widget);
109 static void message_actions_execute     (MessageView    *msgview,
110                                          guint           action_nb,
111                                          GSList         *msg_list);
112
113 static gboolean execute_actions         (gchar          *action, 
114                                          GSList         *msg_list, 
115                                          GtkWidget      *text,
116                                          GdkFont        *msgfont,
117                                          gint            body_pos,
118                                          MimeInfo       *partinfo);
119
120 static gchar *parse_action_cmd          (gchar          *action,
121                                          MsgInfo        *msginfo,
122                                          GSList         *msg_list,
123                                          MimeInfo       *partinfo,
124                                          const gchar    *user_str,
125                                          const gchar    *user_hidden_str,
126                                          const gchar    *sel_str);
127 static gboolean parse_append_filename   (GString        *cmd,
128                                          MsgInfo        *msginfo);
129
130 static gboolean parse_append_msgpart    (GString        *cmd,
131                                          MsgInfo        *msginfo,
132                                          MimeInfo       *partinfo);
133
134 static ChildInfo *fork_child            (gchar          *cmd,
135                                          gint            action_type,
136                                          const gchar    *msg_str,
137                                          Children       *children);
138
139 static gint wait_for_children           (Children       *children);
140
141 static void free_children               (Children       *children);
142
143 static void childinfo_close_pipes       (ChildInfo      *child_info);
144
145 static void create_io_dialog            (Children       *children);
146 static void update_io_dialog            (Children       *children);
147
148 static void hide_io_dialog_cb           (GtkWidget      *widget,
149                                          gpointer        data);
150 static gint io_dialog_key_pressed_cb    (GtkWidget      *widget,
151                                          GdkEventKey    *event,
152                                          gpointer        data);
153
154 static void catch_output                (gpointer                data,
155                                          gint                    source,
156                                          GdkInputCondition       cond);
157 static void catch_input                 (gpointer                data, 
158                                          gint                    source,
159                                          GdkInputCondition       cond);
160 static void catch_status                (gpointer                data,
161                                          gint                    source,
162                                          GdkInputCondition       cond);
163
164 static gchar *get_user_string           (const gchar    *action,
165                                          ActionType      type);
166
167
168 ActionType action_get_type(const gchar *action_str)
169 {
170         const gchar *p;
171         ActionType action_type = ACTION_NONE;
172
173         g_return_val_if_fail(action_str,  ACTION_ERROR);
174         g_return_val_if_fail(*action_str, ACTION_ERROR);
175
176         p = action_str;
177
178         if (p[0] == '|') {
179                 action_type |= ACTION_PIPE_IN;
180                 p++;
181         } else if (p[0] == '>') {
182                 action_type |= ACTION_USER_IN;
183                 p++;
184         } else if (p[0] == '*') {
185                 action_type |= ACTION_USER_HIDDEN_IN;
186                 p++;
187         }
188
189         if (p[0] == '\0')
190                 return ACTION_ERROR;
191
192         while (*p && action_type != ACTION_ERROR) {
193                 if (p[0] == '%') {
194                         switch (p[1]) {
195                         case 'f':
196                                 action_type |= ACTION_SINGLE;
197                                 break;
198                         case 'F':
199                                 action_type |= ACTION_MULTIPLE;
200                                 break;
201                         case 'p':
202                                 action_type |= ACTION_SINGLE;
203                                 break;
204                         case 's':
205                                 action_type |= ACTION_SELECTION_STR;
206                                 break;
207                         case 'u':
208                                 action_type |= ACTION_USER_STR;
209                                 break;
210                         case 'h':
211                                 action_type |= ACTION_USER_HIDDEN_STR;
212                                 break;
213                         default:
214                                 action_type = ACTION_ERROR;
215                                 break;
216                         }
217                 } else if (p[0] == '|') {
218                         if (p[1] == '\0')
219                                 action_type |= ACTION_PIPE_OUT;
220                 } else if (p[0] == '>') {
221                         if (p[1] == '\0')
222                                 action_type |= ACTION_INSERT;
223                 } else if (p[0] == '&') {
224                         if (p[1] == '\0')
225                                 action_type |= ACTION_ASYNC;
226                 }
227                 p++;
228         }
229
230         return action_type;
231 }
232
233 static gchar *parse_action_cmd(gchar *action, MsgInfo *msginfo,
234                                GSList *msg_list, MimeInfo *partinfo,
235                                const gchar *user_str,
236                                const gchar *user_hidden_str,
237                                const gchar *sel_str)
238 {
239         GString *cmd;
240         gchar *p;
241         GSList *cur;
242         
243         p = action;
244         
245         if (p[0] == '|' || p[0] == '>' || p[0] == '*')
246                 p++;
247
248         cmd = g_string_sized_new(strlen(action));
249
250         while (p[0] &&
251                !((p[0] == '|' || p[0] == '>' || p[0] == '&') && !p[1])) {
252                 if (p[0] == '%' && p[1]) {
253                         switch (p[1]) {
254                         case 'f':
255                                 if (!parse_append_filename(cmd, msginfo)) {
256                                         g_string_free(cmd, TRUE);
257                                         return NULL;
258                                 }
259                                 p++;
260                                 break;
261                         case 'F':
262                                 for (cur = msg_list; cur != NULL;
263                                      cur = cur->next) {
264                                         MsgInfo *msg = (MsgInfo *)cur->data;
265
266                                         if (!parse_append_filename(cmd, msg)) {
267                                                 g_string_free(cmd, TRUE);
268                                                 return NULL;
269                                         }
270                                         if (cur->next)
271                                                 g_string_append_c(cmd, ' ');
272                                 }
273                                 p++;
274                                 break;
275                         case 'p':
276                                 if (!parse_append_msgpart(cmd, msginfo,
277                                                           partinfo)) {
278                                         g_string_free(cmd, TRUE);
279                                         return NULL;
280                                 }
281                                 p++;
282                                 break;
283                         case 's':
284                                 if (sel_str)
285                                         g_string_append(cmd, sel_str);
286                                 p++;
287                                 break;
288                         case 'u':
289                                 if (user_str)
290                                         g_string_append(cmd, user_str);
291                                 p++;
292                                 break;
293                         case 'h':
294                                 if (user_hidden_str)
295                                         g_string_append(cmd, user_hidden_str);
296                                 p++;
297                                 break;
298                         default:
299                                 g_string_append_c(cmd, p[0]);
300                                 g_string_append_c(cmd, p[1]);
301                                 p++;
302                         }
303                 } else {
304                         g_string_append_c(cmd, p[0]);
305                 }
306                 p++;
307         }
308         if (cmd->len == 0) {
309                 g_string_free(cmd, TRUE);
310                 return NULL;
311         }
312
313         p = cmd->str;
314         g_string_free(cmd, FALSE);
315         return p;
316 }
317
318 static gboolean parse_append_filename(GString *cmd, MsgInfo *msginfo)
319 {
320         gchar *filename;
321
322         g_return_val_if_fail(msginfo, FALSE);
323
324         filename = procmsg_get_message_file(msginfo);
325
326         if (filename) {
327                 g_string_append(cmd, filename);
328                 g_free(filename);
329         } else {
330                 alertpanel_error(_("Could not get message file %d"),
331                                  msginfo->msgnum);
332                 return FALSE;
333         }
334
335         return TRUE;
336 }
337
338 static gboolean parse_append_msgpart(GString *cmd, MsgInfo *msginfo,
339                                      MimeInfo *partinfo)
340 {
341         gboolean single_part = FALSE;
342         gchar *filename;
343         gchar *part_filename;
344         gint ret;
345
346         if (!partinfo) {
347                 FILE *fp;
348 #if USE_GPGME
349                 if ((fp = procmsg_open_message_decrypted(msginfo, &partinfo))
350                     == NULL) {
351                         alertpanel_error(_("Could not get message file."));
352                         return FALSE;
353                 }
354 #else
355                 if ((fp = procmsg_open_message(msginfo)) == NULL) {
356                         alertpanel_error(_("Could not get message file."));
357                         return FALSE;
358                 }
359                 partinfo = procmime_scan_mime_header(fp);
360 #endif
361                 fclose(fp);
362                 if (!partinfo) {
363                         procmime_mimeinfo_free_all(partinfo);
364                         alertpanel_error(_("Could not get message part."));
365                         return FALSE;
366                 }
367
368                 single_part = TRUE;
369         }
370
371         filename = procmsg_get_message_file_path(msginfo);
372         part_filename = procmime_get_tmp_file_name(partinfo);
373
374         ret = procmime_get_part(part_filename, filename, partinfo); 
375
376         if (single_part)
377                 procmime_mimeinfo_free_all(partinfo);
378         g_free(filename);
379
380         if (ret < 0) {
381                 alertpanel_error(_("Can't get part of multipart message"));
382                 g_free(part_filename);
383                 return FALSE;
384         }
385
386         g_string_append(cmd, part_filename);
387
388         g_free(part_filename);
389
390         return TRUE;
391 }
392
393 void actions_execute(gpointer data, 
394                      guint action_nb,
395                      GtkWidget *widget,
396                      gint source)
397 {
398         if (source == TOOLBAR_MAIN) 
399                 mainwin_actions_execute_cb((MainWindow*)data, action_nb, widget);
400         else if (source == TOOLBAR_COMPOSE)
401                 compose_actions_execute_cb((Compose*)data, action_nb, widget);
402         else if (source == TOOLBAR_MSGVIEW)
403                 msgview_actions_execute_cb((MessageView*)data, action_nb, widget);      
404 }
405
406 void action_update_mainwin_menu(GtkItemFactory *ifactory, MainWindow *mainwin)
407 {
408         action_update_menu(ifactory, "/Tools/Actions",
409                            mainwin_actions_execute_cb, mainwin);
410 }
411
412 void action_update_msgview_menu(GtkItemFactory *ifactory, MessageView *msgview)
413 {
414         action_update_menu(ifactory, "/Tools/Actions",
415                            msgview_actions_execute_cb, msgview);
416 }
417
418 void action_update_compose_menu(GtkItemFactory *ifactory, Compose *compose)
419 {
420         action_update_menu(ifactory, "/Tools/Actions",
421                            compose_actions_execute_cb, compose);
422 }
423
424 static void action_update_menu(GtkItemFactory *ifactory, gchar *branch_path,
425                                gpointer callback, gpointer data)
426 {
427         GtkWidget *menuitem;
428         gchar *menu_path;
429         GSList *cur;
430         gchar *action, *action_p;
431         GList *amenu;
432         GtkItemFactoryEntry ifentry = {NULL, NULL, NULL, 0, "<Branch>"};
433
434         ifentry.path = branch_path;
435         menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
436         g_return_if_fail(menuitem != NULL);
437
438         amenu = GTK_MENU_SHELL(menuitem)->children;
439         while (amenu != NULL) {
440                 GList *alist = amenu->next;
441                 gtk_widget_destroy(GTK_WIDGET(amenu->data));
442                 amenu = alist;
443         }
444
445         ifentry.accelerator     = NULL;
446         ifentry.callback_action = 0;
447         ifentry.callback        = callback;
448         ifentry.item_type       = NULL;
449
450         for (cur = prefs_common.actions_list; cur; cur = cur->next) {
451                 action   = g_strdup((gchar *)cur->data);
452                 action_p = strstr(action, ": ");
453                 if (action_p && action_p[2] &&
454                     action_get_type(&action_p[2]) != ACTION_ERROR) {
455                         action_p[0] = 0x00;
456                         menu_path = g_strdup_printf("%s/%s", branch_path,
457                                                     action);
458                         ifentry.path = menu_path;
459                         gtk_item_factory_create_item(ifactory, &ifentry, data,
460                                                      1);
461                         g_free(menu_path);
462                 }
463                 g_free(action);
464                 ifentry.callback_action++;
465         }
466 }
467
468 static void compose_actions_execute_cb(Compose *compose, guint action_nb,
469                                        GtkWidget *widget)
470 {
471         gchar *buf, *action;
472         guint action_type;
473
474         g_return_if_fail(action_nb < g_slist_length(prefs_common.actions_list));
475
476         buf = (gchar *)g_slist_nth_data(prefs_common.actions_list, action_nb);
477         g_return_if_fail(buf != NULL);
478         action = strstr(buf, ": ");
479         g_return_if_fail(action != NULL);
480
481         /* Point to the beginning of the command-line */
482         action += 2;
483
484         action_type = action_get_type(action);
485         if (action_type & (ACTION_SINGLE | ACTION_MULTIPLE)) {
486                 alertpanel_warning
487                         (_("The selected action cannot be used in the compose window\n"
488                            "because it contains %%f, %%F or %%p."));
489                 return;
490         }
491
492         execute_actions(action, NULL, compose->text, NULL, 0, NULL);
493 }
494
495 static void mainwin_actions_execute_cb(MainWindow *mainwin, guint action_nb,
496                                        GtkWidget *widget)
497 {
498         GSList *msg_list;
499
500         msg_list = summary_get_selected_msg_list(mainwin->summaryview);
501         message_actions_execute(mainwin->messageview, action_nb, msg_list);
502         g_slist_free(msg_list);
503 }
504
505 static void msgview_actions_execute_cb(MessageView *msgview, guint action_nb,
506                                        GtkWidget *widget)
507 {
508         GSList *msg_list = NULL;
509
510         if (msgview->msginfo)
511                 msg_list = g_slist_append(msg_list, msgview->msginfo);
512         message_actions_execute(msgview, action_nb, msg_list);
513         g_slist_free(msg_list);
514 }
515
516 static void message_actions_execute(MessageView *msgview, guint action_nb,
517                                     GSList *msg_list)
518 {
519         TextView *textview;
520         MimeInfo *partinfo;
521         gchar *buf;
522         gchar *action;
523         GtkWidget *text = NULL;
524         GdkFont *msgfont = NULL;
525         guint body_pos = 0;
526
527         g_return_if_fail(action_nb < g_slist_length(prefs_common.actions_list));
528
529         buf = (gchar *)g_slist_nth_data(prefs_common.actions_list, action_nb);
530
531         g_return_if_fail(buf);
532         g_return_if_fail(action = strstr(buf, ": "));
533
534         /* Point to the beginning of the command-line */
535         action += 2;
536
537         textview = messageview_get_current_textview(msgview);
538         if (textview) {
539                 text     = textview->text;
540                 msgfont  = textview->msgfont;
541                 body_pos = textview->body_pos;
542         }
543         partinfo = messageview_get_selected_mime_part(msgview);
544
545         execute_actions(action, msg_list, text, msgfont, body_pos, partinfo);
546 }
547
548 static gboolean execute_actions(gchar *action, GSList *msg_list,
549                                 GtkWidget *text, GdkFont *msgfont,
550                                 gint body_pos, MimeInfo *partinfo)
551 {
552         GSList *children_list = NULL;
553         gint is_ok  = TRUE;
554         gint msg_list_len;
555         Children *children;
556         ChildInfo *child_info;
557         gint action_type;
558         MsgInfo *msginfo;
559         gchar *cmd;
560         guint start = 0, end = 0;
561         gchar *sel_str = NULL;
562         gchar *msg_str = NULL;
563         gchar *user_str = NULL;
564         gchar *user_hidden_str = NULL;
565
566         g_return_val_if_fail(action && *action, FALSE);
567
568         action_type = action_get_type(action);
569
570         if (action_type == ACTION_ERROR)
571                 return FALSE;         /* ERR: syntax error */
572
573         if (action_type & (ACTION_SINGLE | ACTION_MULTIPLE) && !msg_list)
574                 return FALSE;         /* ERR: file command without selection */
575
576         msg_list_len = g_slist_length(msg_list);
577
578         if (action_type & (ACTION_PIPE_OUT | ACTION_PIPE_IN | ACTION_INSERT)) {
579                 if (msg_list_len > 1)
580                         return FALSE; /* ERR: pipe + multiple selection */
581                 if (!text)
582                         return FALSE; /* ERR: pipe and no displayed text */
583         }
584
585         if (action_type & ACTION_SELECTION_STR) {
586                 if (!text)
587                         return FALSE; /* ERR: selection string but no text */
588         }
589
590         if (text) {
591                 start = GTK_EDITABLE(text)->selection_start_pos;
592                 end   = GTK_EDITABLE(text)->selection_end_pos;
593                 if (start > end) {
594                         guint tmp;
595                         tmp = start;
596                         start = end;
597                         end = tmp;
598                 }
599
600                 if (start == end) {
601                         start = body_pos;
602                         end = gtk_stext_get_length(GTK_STEXT(text));
603                         msg_str = gtk_editable_get_chars(GTK_EDITABLE(text),
604                                                          start, end);
605                 } else {
606                         sel_str = gtk_editable_get_chars(GTK_EDITABLE(text),
607                                                          start, end);
608                         msg_str = g_strdup(sel_str);
609                 }
610         } else {
611                 start = body_pos;
612                 end = gtk_stext_get_length(GTK_STEXT(text));
613                 msg_str = gtk_editable_get_chars(GTK_EDITABLE(text),
614                                                  start, end);
615         }
616
617         if (action_type & ACTION_USER_STR) {
618                 if (!(user_str = get_user_string(action, ACTION_USER_STR))) {
619                         g_free(msg_str);
620                         g_free(sel_str);
621                         return FALSE;
622                 }
623         }
624
625         if (action_type & ACTION_USER_HIDDEN_STR) {
626                 if (!(user_hidden_str =
627                         get_user_string(action, ACTION_USER_HIDDEN_STR))) {
628                         g_free(msg_str);
629                         g_free(sel_str);
630                         g_free(user_str);
631                         return FALSE;
632                 }
633         }
634
635         if (action_type & ACTION_PIPE_OUT) {
636                 gtk_stext_freeze(GTK_STEXT(text));
637                 gtk_stext_set_point(GTK_STEXT(text), start);
638                 gtk_stext_forward_delete(GTK_STEXT(text), end - start);
639                 gtk_stext_thaw(GTK_STEXT(text));
640         }
641
642         children = g_new0(Children, 1);
643
644         children->msg_text = text;
645         children->msgfont = msgfont;
646
647         if ((action_type & (ACTION_USER_IN | ACTION_USER_HIDDEN_IN)) &&
648             ((action_type & ACTION_SINGLE) == 0 || msg_list_len == 1))
649                 children->open_in = 1;
650
651         if (action_type & ACTION_SINGLE) {
652                 GSList *cur;
653
654                 for (cur = msg_list; cur && is_ok == TRUE; cur = cur->next) {
655                         msginfo = (MsgInfo *)cur->data;
656                         if (!msginfo) {
657                                 is_ok  = FALSE; /* ERR: msginfo missing */
658                                 break;
659                         }
660                         cmd = parse_action_cmd(action, msginfo, msg_list,
661                                                partinfo, user_str,
662                                                user_hidden_str, sel_str);
663                         if (!cmd) {
664                                 debug_print("Action command error\n");
665                                 is_ok  = FALSE; /* ERR: incorrect command */
666                                 break;
667                         }
668                         if ((child_info = fork_child(cmd, action_type, msg_str,
669                                                      children))) {
670                                 children_list = g_slist_append(children_list,
671                                                                child_info);
672                         }
673                         g_free(cmd);
674                 }
675         } else {
676                 cmd = parse_action_cmd(action, NULL, msg_list, partinfo,
677                                        user_str, user_hidden_str, sel_str);
678                 if (cmd) {
679                         if ((child_info = fork_child(cmd, action_type, msg_str,
680                                                      children))) {
681                                 children_list = g_slist_append(children_list,
682                                                                child_info);
683                         }
684                         g_free(cmd);
685                 } else
686                         is_ok  = FALSE;         /* ERR: incorrect command */
687         }
688
689         g_free(msg_str);
690         g_free(sel_str);
691         g_free(user_str);
692         g_free(user_hidden_str);
693
694         if (!children_list) {
695                  /* If not waiting for children, return */
696                 g_free(children);
697         } else {
698                 GSList *cur;
699
700                 children->action  = g_strdup(action);
701                 children->dialog  = NULL;
702                 children->list    = children_list;
703                 children->nb      = g_slist_length(children_list);
704
705                 for (cur = children_list; cur; cur = cur->next) {
706                         child_info = (ChildInfo *) cur->data;
707                         child_info->tag_status = 
708                                 gdk_input_add(child_info->chld_status,
709                                               GDK_INPUT_READ,
710                                               catch_status, child_info);
711                 }
712
713                 create_io_dialog(children);
714         }
715
716         return is_ok;
717 }
718
719 static ChildInfo *fork_child(gchar *cmd, gint action_type,
720                              const gchar *msg_str, Children *children)
721 {
722         gint chld_in[2], chld_out[2], chld_err[2], chld_status[2];
723         gchar *cmdline[4];
724         pid_t pid, gch_pid;
725         ChildInfo *child_info;
726         gint sync;
727
728         sync = !(action_type & ACTION_ASYNC);
729
730         chld_in[0] = chld_in[1] = chld_out[0] = chld_out[1] = chld_err[0]
731                 = chld_err[1] = chld_status[0] = chld_status[1] = -1;
732
733         if (sync) {
734                 if (pipe(chld_status) || pipe(chld_in) || pipe(chld_out) ||
735                     pipe(chld_err)) {
736                         alertpanel_error(_("Command could not be started. "
737                                            "Pipe creation failed.\n%s"),
738                                         g_strerror(errno));
739                         /* Closing fd = -1 fails silently */
740                         close(chld_in[0]);
741                         close(chld_in[1]);
742                         close(chld_out[0]);
743                         close(chld_out[1]);
744                         close(chld_err[0]);
745                         close(chld_err[1]);
746                         close(chld_status[0]);
747                         close(chld_status[1]);
748                         return NULL; /* Pipe error */
749                 }
750         }
751
752         debug_print("Forking child and grandchild.\n");
753
754         pid = fork();
755         if (pid == 0) { /* Child */
756                 if (setpgid(0, 0))
757                         perror("setpgid");
758
759                 close(ConnectionNumber(gdk_display));
760
761                 gch_pid = fork();
762
763                 if (gch_pid == 0) {
764                         if (setpgid(0, getppid()))
765                                 perror("setpgid");
766
767                         if (sync) {
768                                 if (action_type &
769                                     (ACTION_PIPE_IN |
770                                      ACTION_USER_IN |
771                                      ACTION_USER_HIDDEN_IN)) {
772                                         close(fileno(stdin));
773                                         dup  (chld_in[0]);
774                                 }
775                                 close(chld_in[0]);
776                                 close(chld_in[1]);
777
778                                 close(fileno(stdout));
779                                 dup  (chld_out[1]);
780                                 close(chld_out[0]);
781                                 close(chld_out[1]);
782
783                                 close(fileno(stderr));
784                                 dup  (chld_err[1]);
785                                 close(chld_err[0]);
786                                 close(chld_err[1]);
787                         }
788
789                         cmdline[0] = "sh";
790                         cmdline[1] = "-c";
791                         cmdline[2] = cmd;
792                         cmdline[3] = 0;
793                         execvp("/bin/sh", cmdline);
794
795                         perror("execvp");
796                         _exit(1);
797                 } else if (gch_pid < (pid_t) 0) { /* Fork error */
798                         if (sync)
799                                 write(chld_status[1], "1\n", 2);
800                         perror("fork");
801                         _exit(1);
802                 } else { /* Child */
803                         if (sync) {
804                                 close(chld_in[0]);
805                                 close(chld_in[1]);
806                                 close(chld_out[0]);
807                                 close(chld_out[1]);
808                                 close(chld_err[0]);
809                                 close(chld_err[1]);
810                                 close(chld_status[0]);
811
812                                 debug_print("Child: Waiting for grandchild\n");
813                                 waitpid(gch_pid, NULL, 0);
814                                 debug_print("Child: grandchild ended\n");
815                                 write(chld_status[1], "0\n", 2);
816                                 close(chld_status[1]);
817                         }
818                         _exit(0);
819                 }
820         } else if (pid < 0) { /* Fork error */
821                 alertpanel_error(_("Could not fork to execute the following "
822                                    "command:\n%s\n%s"),
823                                  cmd, g_strerror(errno));
824                 return NULL; 
825         }
826
827         /* Parent */
828
829         if (!sync) {
830                 waitpid(pid, NULL, 0);
831                 return NULL;
832         }
833
834         close(chld_in[0]);
835         if (!(action_type &
836               (ACTION_PIPE_IN | ACTION_USER_IN | ACTION_USER_HIDDEN_IN)))
837                 close(chld_in[1]);
838         close(chld_out[1]);
839         close(chld_err[1]);
840         close(chld_status[1]);
841
842         child_info = g_new0(ChildInfo, 1);
843
844         child_info->children    = children;
845
846         child_info->pid         = pid;
847         child_info->cmd         = g_strdup(cmd);
848         child_info->type        = action_type;
849         child_info->new_out     = FALSE;
850         child_info->output      = g_string_new(NULL);
851         child_info->chld_in     =
852                 (action_type &
853                  (ACTION_PIPE_IN | ACTION_USER_IN | ACTION_USER_HIDDEN_IN))
854                         ? chld_in [1] : -1;
855         child_info->chld_out    = chld_out[0];
856         child_info->chld_err    = chld_err[0];
857         child_info->chld_status = chld_status[0];
858         child_info->tag_in      = -1;
859         child_info->tag_out     = gdk_input_add(chld_out[0], GDK_INPUT_READ,
860                                                 catch_output, child_info);
861         child_info->tag_err     = gdk_input_add(chld_err[0], GDK_INPUT_READ,
862                                                 catch_output, child_info);
863
864         if (!(action_type & (ACTION_PIPE_IN | ACTION_PIPE_OUT | ACTION_INSERT)))
865                 return child_info;
866
867         if ((action_type & ACTION_PIPE_IN) && msg_str) {
868                 write(chld_in[1], msg_str, strlen(msg_str));
869                 if (!(action_type & (ACTION_USER_IN | ACTION_USER_HIDDEN_IN)))
870                         close(chld_in[1]);
871                 child_info->chld_in = -1; /* No more input */
872         }
873
874         return child_info;
875 }
876
877 static void kill_children_cb(GtkWidget *widget, gpointer data)
878 {
879         GSList *cur;
880         Children *children = (Children *) data;
881         ChildInfo *child_info;
882
883         for (cur = children->list; cur; cur = cur->next) {
884                 child_info = (ChildInfo *)(cur->data);
885                 debug_print("Killing child group id %d\n", child_info->pid);
886                 if (child_info->pid && kill(-child_info->pid, SIGTERM) < 0)
887                         perror("kill");
888         }
889 }
890
891 static gint wait_for_children(Children *children)
892 {
893         gboolean new_output;
894         ChildInfo *child_info;
895         GSList *cur;
896         gint nb = children->nb;
897
898         children->nb = 0;
899
900         cur = children->list;
901         new_output = FALSE;
902         while (cur) {
903                 child_info = (ChildInfo *)cur->data;
904                 if (child_info->pid)
905                         children->nb++;
906                 new_output |= child_info->new_out;
907                 cur = cur->next;
908         }
909
910         children->output |= new_output;
911
912         if (new_output || (children->dialog && (nb != children->nb)))
913                 update_io_dialog(children);
914
915         if (children->nb)
916                 return FALSE;
917
918         if (!children->dialog) {
919                 free_children(children);
920         } else if (!children->output) {
921                 gtk_widget_destroy(children->dialog);
922         }
923
924         return FALSE;
925 }
926
927 static void send_input(GtkWidget *w, gpointer data)
928 {
929         Children *children = (Children *) data;
930         ChildInfo *child_info = (ChildInfo *) children->list->data;
931
932         child_info->tag_in = gdk_input_add(child_info->chld_in,
933                                            GDK_INPUT_WRITE,
934                                            catch_input, children);
935         gtk_widget_set_sensitive(children->input_hbox, FALSE);
936 }
937
938 static gint delete_io_dialog_cb(GtkWidget *w, GdkEvent *e, gpointer data)
939 {
940         hide_io_dialog_cb(w, data);
941         return TRUE;
942 }
943
944 static void hide_io_dialog_cb(GtkWidget *w, gpointer data)
945 {
946
947         Children *children = (Children *)data;
948
949         if (!children->nb) {
950                 gtk_signal_disconnect_by_data(GTK_OBJECT(children->dialog),
951                                               children);
952                 gtk_widget_destroy(children->dialog);
953                 free_children(children);
954         }
955 }
956
957 static gint io_dialog_key_pressed_cb(GtkWidget *widget, GdkEventKey *event,
958                                      gpointer data)
959 {
960         if (event && event->keyval == GDK_Escape)
961                 hide_io_dialog_cb(widget, data);
962         return TRUE;
963 }
964
965 static void childinfo_close_pipes(ChildInfo *child_info)
966 {
967         if (child_info->tag_in > 0)
968                 gdk_input_remove(child_info->tag_in);
969         gdk_input_remove(child_info->tag_out);
970         gdk_input_remove(child_info->tag_err);
971
972         if (child_info->chld_in >= 0)
973                 close(child_info->chld_in);
974         close(child_info->chld_out);
975         close(child_info->chld_err);
976         close(child_info->chld_status);
977 }
978
979 static void free_children(Children *children)
980 {
981         GSList *cur;
982         ChildInfo *child_info;
983
984         debug_print("Freeing children data %p\n", children);
985
986         g_free(children->action);
987         for (cur = children->list; cur;) {
988                 child_info = (ChildInfo *)cur->data;
989                 g_free(child_info->cmd);
990                 g_string_free(child_info->output, TRUE);
991                 children->list = g_slist_remove(children->list, child_info);
992                 g_free(child_info);
993                 cur = children->list;
994         }
995         g_free(children);
996 }
997
998 static void update_io_dialog(Children *children)
999 {
1000         GSList *cur;
1001
1002         debug_print("Updating actions input/output dialog.\n");
1003
1004         if (!children->nb) {
1005                 gtk_widget_set_sensitive(children->abort_btn, FALSE);
1006                 gtk_widget_set_sensitive(children->close_btn, TRUE);
1007                 if (children->input_hbox)
1008                         gtk_widget_set_sensitive(children->input_hbox, FALSE);
1009                 gtk_widget_grab_focus(children->close_btn);
1010                 gtk_signal_connect(GTK_OBJECT(children->dialog),
1011                                    "key_press_event",
1012                                    GTK_SIGNAL_FUNC(io_dialog_key_pressed_cb),
1013                                    children);
1014         }
1015
1016         if (children->output) {
1017                 GtkWidget *text = children->text;
1018                 gchar *caption;
1019                 ChildInfo *child_info;
1020
1021                 gtk_widget_show(children->scrolledwin);
1022                 gtk_text_freeze(GTK_TEXT(text));
1023                 gtk_text_set_point(GTK_TEXT(text), 0);
1024                 gtk_text_forward_delete(GTK_TEXT(text), 
1025                                         gtk_text_get_length(GTK_TEXT(text)));
1026                 for (cur = children->list; cur; cur = cur->next) {
1027                         child_info = (ChildInfo *)cur->data;
1028                         if (child_info->pid)
1029                                 caption = g_strdup_printf
1030                                         (_("--- Running: %s\n"),
1031                                          child_info->cmd);
1032                         else
1033                                 caption = g_strdup_printf
1034                                         (_("--- Ended: %s\n"),
1035                                          child_info->cmd);
1036
1037                         gtk_text_insert(GTK_TEXT(text), NULL, NULL, NULL,
1038                                         caption, -1);
1039                         gtk_text_insert(GTK_TEXT(text), NULL, NULL, NULL,
1040                                         child_info->output->str, -1);
1041                         g_free(caption);
1042                         child_info->new_out = FALSE;
1043                 }
1044                 gtk_text_thaw(GTK_TEXT(text));
1045         }
1046 }
1047
1048 static void create_io_dialog(Children *children)
1049 {
1050         GtkWidget *dialog;
1051         GtkWidget *vbox;
1052         GtkWidget *entry = NULL;
1053         GtkWidget *input_hbox = NULL;
1054         GtkWidget *send_button;
1055         GtkWidget *label;
1056         GtkWidget *text;
1057         GtkWidget *scrolledwin;
1058         GtkWidget *hbox;
1059         GtkWidget *abort_button;
1060         GtkWidget *close_button;
1061
1062         debug_print("Creating action IO dialog\n");
1063
1064         dialog = gtk_dialog_new();
1065         gtk_container_set_border_width
1066                 (GTK_CONTAINER(GTK_DIALOG(dialog)->action_area), 5);
1067         gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
1068         gtk_window_set_title(GTK_WINDOW(dialog), _("Action's input/output"));
1069         gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
1070         manage_window_set_transient(GTK_WINDOW(dialog));
1071         gtk_signal_connect(GTK_OBJECT(dialog), "delete_event",
1072                         GTK_SIGNAL_FUNC(delete_io_dialog_cb), children);
1073         gtk_signal_connect(GTK_OBJECT(dialog), "destroy",
1074                         GTK_SIGNAL_FUNC(hide_io_dialog_cb),
1075                         children);
1076
1077         vbox = gtk_vbox_new(FALSE, 8);
1078         gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), vbox);
1079         gtk_container_set_border_width(GTK_CONTAINER(vbox), 8);
1080         gtk_widget_show(vbox);
1081
1082         label = gtk_label_new(children->action);
1083         gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
1084         gtk_widget_show(label);
1085
1086         scrolledwin = gtk_scrolled_window_new(NULL, NULL);
1087         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
1088                                        GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
1089         gtk_box_pack_start(GTK_BOX(vbox), scrolledwin, TRUE, TRUE, 0);
1090         gtk_widget_set_usize(scrolledwin, 480, 200);
1091         gtk_widget_hide(scrolledwin);
1092
1093         text = gtk_text_new(gtk_scrolled_window_get_hadjustment
1094                             (GTK_SCROLLED_WINDOW(scrolledwin)),
1095                             gtk_scrolled_window_get_vadjustment
1096                             (GTK_SCROLLED_WINDOW(scrolledwin)));
1097         gtk_text_set_editable(GTK_TEXT(text), FALSE);
1098         gtk_container_add(GTK_CONTAINER(scrolledwin), text);
1099         gtk_widget_show(text);
1100
1101         if (children->open_in) {
1102                 input_hbox = gtk_hbox_new(FALSE, 8);
1103                 gtk_widget_show(input_hbox);
1104
1105                 entry = gtk_entry_new();
1106                 gtk_widget_set_usize(entry, 320, -1);
1107                 gtk_signal_connect(GTK_OBJECT(entry), "activate",
1108                                    GTK_SIGNAL_FUNC(send_input), children);
1109                 gtk_box_pack_start(GTK_BOX(input_hbox), entry, TRUE, TRUE, 0);
1110                 if (children->open_in & ACTION_USER_HIDDEN_IN)
1111                         gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE);
1112                 gtk_widget_show(entry);
1113
1114                 send_button = gtk_button_new_with_label(_(" Send "));
1115                 gtk_signal_connect(GTK_OBJECT(send_button), "clicked",
1116                                    GTK_SIGNAL_FUNC(send_input), children);
1117                 gtk_box_pack_start(GTK_BOX(input_hbox), send_button, FALSE,
1118                                    FALSE, 0);
1119                 gtk_widget_show(send_button);
1120
1121                 gtk_box_pack_start(GTK_BOX(vbox), input_hbox, FALSE, FALSE, 0);
1122                 gtk_widget_grab_focus(entry);
1123         }
1124
1125         gtkut_button_set_create(&hbox, &abort_button, _("Abort"),
1126                                 &close_button, _("Close"), NULL, NULL);
1127         gtk_signal_connect(GTK_OBJECT(abort_button), "clicked",
1128                         GTK_SIGNAL_FUNC(kill_children_cb), children);
1129         gtk_signal_connect(GTK_OBJECT(close_button), "clicked",
1130                         GTK_SIGNAL_FUNC(hide_io_dialog_cb), children);
1131         gtk_widget_show(hbox);
1132
1133         if (children->nb)
1134                 gtk_widget_set_sensitive(close_button, FALSE);
1135
1136         gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->action_area), hbox);
1137
1138         children->dialog      = dialog;
1139         children->scrolledwin = scrolledwin;
1140         children->text        = text;
1141         children->input_hbox  = children->open_in ? input_hbox : NULL;
1142         children->input_entry = children->open_in ? entry : NULL;
1143         children->abort_btn   = abort_button;
1144         children->close_btn   = close_button;
1145
1146         gtk_widget_show(dialog);
1147 }
1148
1149 static void catch_status(gpointer data, gint source, GdkInputCondition cond)
1150 {
1151         ChildInfo *child_info = (ChildInfo *)data;
1152         gchar buf;
1153         gint c;
1154
1155         gdk_input_remove(child_info->tag_status);
1156
1157         c = read(source, &buf, 1);
1158         debug_print("Child returned %c\n", buf);
1159
1160         waitpid(-child_info->pid, NULL, 0);
1161         childinfo_close_pipes(child_info);
1162         child_info->pid = 0;
1163
1164         wait_for_children(child_info->children);
1165 }
1166         
1167 static void catch_input(gpointer data, gint source, GdkInputCondition cond)
1168 {
1169         Children *children = (Children *)data;
1170         ChildInfo *child_info = (ChildInfo *)children->list->data;
1171         gchar *input;
1172         gint c, count, len;
1173
1174         debug_print("Sending input to grand child.\n");
1175         if (!(cond && GDK_INPUT_WRITE))
1176                 return;
1177
1178         gdk_input_remove(child_info->tag_in);
1179         child_info->tag_in = -1;
1180
1181         input = gtk_editable_get_chars(GTK_EDITABLE(children->input_entry),
1182                                        0, -1);
1183         len = strlen(input);
1184         count = 0;
1185
1186         do {
1187                 c = write(child_info->chld_in, input + count, len - count);
1188                 if (c >= 0)
1189                         count += c;
1190         } while (c >= 0 && count < len);
1191
1192         if (c >= 0)
1193                 write(child_info->chld_in, "\n", 2);
1194
1195         g_free(input);
1196
1197         gtk_entry_set_text(GTK_ENTRY(children->input_entry), "");
1198         gtk_widget_set_sensitive(children->input_hbox, TRUE);
1199         close(child_info->chld_in);
1200         child_info->chld_in = -1;
1201         debug_print("Input to grand child sent.\n");
1202 }
1203
1204 static void catch_output(gpointer data, gint source, GdkInputCondition cond)
1205 {
1206         ChildInfo *child_info = (ChildInfo *)data;
1207         gint c, i;
1208         gchar buf[BUFFSIZE];
1209
1210         debug_print("Catching grand child's output.\n");
1211         if (child_info->type & (ACTION_PIPE_OUT | ACTION_INSERT)
1212             && source == child_info->chld_out) {
1213                 gboolean is_selection = FALSE;
1214                 GtkWidget *text = child_info->children->msg_text;
1215
1216                 if (GTK_EDITABLE(text)->has_selection)
1217                         is_selection = TRUE;
1218                 gtk_stext_freeze(GTK_STEXT(text));
1219                 while (TRUE) {
1220                         c = read(source, buf, sizeof(buf) - 1);
1221                         if (c == 0)
1222                                 break;
1223                         gtk_stext_insert(GTK_STEXT(text),
1224                                          child_info->children->msgfont,
1225                                          NULL, NULL, buf, c);
1226                 }
1227                 if (is_selection) {
1228                         /* Using the select_region draws things. Should not.
1229                          * so we just change selection position and 
1230                          * defere drawing when thawing. Hack?
1231                          */
1232                         GTK_EDITABLE(text)->selection_end_pos =
1233                                         gtk_stext_get_point(GTK_STEXT(text));
1234                 }
1235                 gtk_stext_thaw(GTK_STEXT(text));
1236         } else {
1237                 c = read(source, buf, sizeof(buf) - 1);
1238                 for (i = 0; i < c; i++)
1239                         g_string_append_c(child_info->output, buf[i]);
1240                 if (c > 0)
1241                         child_info->new_out = TRUE;
1242         }
1243         wait_for_children(child_info->children);
1244 }
1245
1246 static gchar *get_user_string(const gchar *action, ActionType type)
1247 {
1248         gchar *message;
1249         gchar *user_str = NULL;
1250
1251         switch (type) {
1252         case ACTION_USER_HIDDEN_STR:
1253                 message = g_strdup_printf
1254                         (_("Enter the argument for the following action:\n"
1255                            "(`%%h' will be replaced with the argument)\n"
1256                            "  %s"),
1257                          action);
1258                 user_str = input_dialog_with_invisible
1259                         (_("Action's hidden user argument"), message, NULL);
1260                 break;
1261         case ACTION_USER_STR:
1262                 message = g_strdup_printf
1263                         (_("Enter the argument for the following action:\n"
1264                            "(`%%u' will be replaced with the argument)\n"
1265                            "  %s"),
1266                          action);
1267                 user_str = input_dialog
1268                         (_("Action's user argument"), message, NULL);
1269                 break;
1270         default:
1271                 g_warning("Unsupported action type %d", type);
1272         }
1273
1274         return user_str;
1275 }