Merge Irssi 0.8.16-rc1
[silc.git] / apps / irssi / src / fe-common / core / fe-exec.c
1 /*
2  fe-exec.c : irssi
3
4     Copyright (C) 2000-2001 Timo Sirainen
5
6     This program is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     This program is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License along
17     with this program; if not, write to the Free Software Foundation, Inc.,
18     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20
21 #include "module.h"
22 #include "modules.h"
23 #include "signals.h"
24 #include "commands.h"
25 #include "pidwait.h"
26 #include "line-split.h"
27 #include "network.h"
28 #include "net-sendbuffer.h"
29 #include "misc.h"
30 #include "levels.h"
31
32 #include "servers.h"
33 #include "channels.h"
34 #include "queries.h"
35
36 #include "printtext.h"
37 #include "fe-exec.h"
38 #include "fe-windows.h"
39 #include "window-items.h"
40
41 #include <signal.h>
42 #include <sys/wait.h>
43
44 GSList *processes;
45 static int signal_exec_input;
46
47 static void exec_wi_destroy(EXEC_WI_REC *rec)
48 {
49         g_return_if_fail(rec != NULL);
50
51         if (rec->destroying) return;
52         rec->destroying = TRUE;
53
54         rec->process->target_item = NULL;
55         if (window_item_window((WI_ITEM_REC *) rec) != NULL)
56                 window_item_destroy((WI_ITEM_REC *) rec);
57
58         MODULE_DATA_DEINIT(rec);
59         g_free(rec->visible_name);
60         g_free(rec);
61 }
62
63 static const char *exec_get_target(WI_ITEM_REC *item)
64 {
65         return ((EXEC_WI_REC *) item)->visible_name;
66 }
67
68 static EXEC_WI_REC *exec_wi_create(WINDOW_REC *window, PROCESS_REC *rec)
69 {
70         EXEC_WI_REC *item;
71
72         g_return_val_if_fail(window != NULL, NULL);
73         g_return_val_if_fail(rec != NULL, NULL);
74
75         item = g_new0(EXEC_WI_REC, 1);
76         item->type = module_get_uniq_id_str("WINDOW ITEM TYPE", "EXEC");
77         item->destroy = (void (*) (WI_ITEM_REC *)) exec_wi_destroy;
78         item->get_target = exec_get_target;
79         item->visible_name = rec->name != NULL ? g_strdup(rec->name) :
80                 g_strdup_printf("%%%d", rec->id);
81
82         item->createtime = time(NULL);
83         item->process = rec;
84
85         MODULE_DATA_INIT(item);
86         window_item_add(window, (WI_ITEM_REC *) item, FALSE);
87         return item;
88 }
89
90 static int process_get_new_id(void)
91 {
92         PROCESS_REC *rec;
93         GSList *tmp;
94         int id;
95
96         id = 0;
97         tmp = processes;
98         while (tmp != NULL) {
99                 rec = tmp->data;
100
101                 if (id != rec->id) {
102                         tmp = tmp->next;
103                         continue;
104                 }
105
106                 id++;
107                 tmp = processes;
108         }
109
110         return id;
111 }
112
113 static PROCESS_REC *process_find_pid(int pid)
114 {
115         GSList *tmp;
116
117         g_return_val_if_fail(pid > 0, NULL);
118
119         for (tmp = processes; tmp != NULL; tmp = tmp->next) {
120                 PROCESS_REC *rec = tmp->data;
121
122                 if (rec->pid == pid)
123                         return rec;
124         }
125
126         return NULL;
127 }
128
129 static PROCESS_REC *process_find_id(int id, int verbose)
130 {
131         GSList *tmp;
132
133         g_return_val_if_fail(id != -1, NULL);
134
135         for (tmp = processes; tmp != NULL; tmp = tmp->next) {
136                 PROCESS_REC *rec = tmp->data;
137
138                 if (rec->id == id)
139                         return rec;
140         }
141
142         if (verbose) {
143                 printtext(NULL, NULL, MSGLEVEL_CLIENTERROR,
144                           "Unknown process id: %d", id);
145         }
146
147         return NULL;
148 }
149
150 static PROCESS_REC *process_find(const char *name, int verbose)
151 {
152         GSList *tmp;
153
154         g_return_val_if_fail(name != NULL, NULL);
155
156         if (*name == '%' && is_numeric(name+1, 0))
157                 return process_find_id(atoi(name+1), verbose);
158
159         for (tmp = processes; tmp != NULL; tmp = tmp->next) {
160                 PROCESS_REC *rec = tmp->data;
161
162                 if (rec->name != NULL && strcmp(rec->name, name) == 0)
163                         return rec;
164         }
165
166         if (verbose) {
167                 printtext(NULL, NULL, MSGLEVEL_CLIENTERROR,
168                           "Unknown process name: %s", name);
169         }
170
171         return NULL;
172 }
173
174 static void process_destroy(PROCESS_REC *rec, int status)
175 {
176         processes = g_slist_remove(processes, rec);
177
178         signal_emit("exec remove", 2, rec, GINT_TO_POINTER(status));
179
180         if (rec->read_tag != -1)
181                 g_source_remove(rec->read_tag);
182         if (rec->target_item != NULL)
183                 exec_wi_destroy(rec->target_item);
184
185         line_split_free(rec->databuf);
186         g_io_channel_close(rec->in);
187         g_io_channel_unref(rec->in);
188         net_sendbuffer_destroy(rec->out, TRUE);
189
190         g_free_not_null(rec->name);
191         g_free_not_null(rec->target);
192         g_free_not_null(rec->target_server);
193         g_free(rec->args);
194         g_free(rec);
195 }
196
197 static void processes_killall(int signum)
198 {
199         GSList *tmp;
200         int kill_ret;
201
202         for (tmp = processes; tmp != NULL; tmp = tmp->next) {
203                 PROCESS_REC *rec = tmp->data;
204
205                 kill_ret = kill(-rec->pid, signum);
206                 if (kill_ret != 0)
207                         printtext(NULL, NULL, MSGLEVEL_CLIENTERROR,
208                                   "Error sending signal %d to pid %d: %s",
209                                   signum, rec->pid, g_strerror(errno));
210         }
211 }
212
213 static int signal_name_to_id(const char *name)
214 {
215         /* check only the few most common signals, too much job to check
216            them all. if we sometimes want more, procps-sources/proc/sig.c
217            would be useful for copypasting */
218         if (g_ascii_strcasecmp(name, "hup") == 0)
219                 return SIGHUP;
220         if (g_ascii_strcasecmp(name, "int") == 0)
221                 return SIGINT;
222         if (g_ascii_strcasecmp(name, "term") == 0)
223                 return SIGTERM;
224         if (g_ascii_strcasecmp(name, "kill") == 0)
225                 return SIGKILL;
226         if (g_ascii_strcasecmp(name, "usr1") == 0)
227                 return SIGUSR1;
228         if (g_ascii_strcasecmp(name, "usr2") == 0)
229                 return SIGUSR2;
230         return -1;
231 }
232
233 /* `optlist' should contain only one unknown key - the server tag.
234    returns NULL if there was unknown -option */
235 static int cmd_options_get_signal(const char *cmd,
236                                   GHashTable *optlist)
237 {
238         GSList *list, *tmp, *next;
239         char *signame;
240         int signum;
241
242         /* get all the options, then remove the known ones. there should
243            be only one left - the signal */
244         list = hashtable_get_keys(optlist);
245         if (cmd != NULL) {
246                 for (tmp = list; tmp != NULL; tmp = next) {
247                         char *option = tmp->data;
248                         next = tmp->next;
249
250                         if (command_have_option(cmd, option))
251                                 list = g_slist_remove(list, option);
252                 }
253         }
254
255         if (list == NULL)
256                 return -1;
257
258         signame = list->data;
259         signum = -1;
260
261         signum = is_numeric(signame, 0) ? atol(signame) :
262                 signal_name_to_id(signame);
263
264         if (signum == -1 || list->next != NULL) {
265                 /* unknown option (not a signal) */
266                 signal_emit("error command", 2,
267                             GINT_TO_POINTER(CMDERR_OPTION_UNKNOWN),
268                             signum == -1 ? list->data : list->next->data);
269                 signal_stop();
270                 return -2;
271         }
272
273         g_slist_free(list);
274         return signum;
275 }
276
277 static void exec_show_list(void)
278 {
279         GSList *tmp;
280
281         for (tmp = processes; tmp != NULL; tmp = tmp->next) {
282                 PROCESS_REC *rec = tmp->data;
283
284                 printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP,
285                           "%d (%s): %s", rec->id, rec->name, rec->args);
286         }
287 }
288
289 static void process_exec(PROCESS_REC *rec, const char *cmd)
290 {
291         const char *shell_args[4] = { "/bin/sh", "-c", NULL, NULL };
292         char **args;
293         int in[2], out[2];
294         int n;
295
296         if (pipe(in) == -1)
297                 return;
298         if (pipe(out) == -1)
299                 return;
300
301         shell_args[2] = cmd;
302         rec->pid = fork();
303         if (rec->pid == -1) {
304                 /* error */
305                 close(in[0]); close(in[1]);
306                 close(out[0]); close(out[1]);
307                 return;
308         }
309
310         if (rec->pid != 0) {
311                 /* parent process */
312                 GIOChannel *outio = g_io_channel_new(in[1]);
313
314                 rec->in = g_io_channel_new(out[0]);
315                 rec->out = net_sendbuffer_create(outio, 0);
316
317                 close(out[1]);
318                 close(in[0]);
319                 pidwait_add(rec->pid);
320                 return;
321         }
322
323         /* child process, try to clean up everything */
324         setsid();
325         setuid(getuid());
326         setgid(getgid());
327         signal(SIGINT, SIG_IGN);
328         signal(SIGQUIT, SIG_DFL);
329
330         putenv("TERM=tty");
331
332         /* set stdin, stdout and stderr */
333         dup2(in[0], STDIN_FILENO);
334         dup2(out[1], STDOUT_FILENO);
335         dup2(out[1], STDERR_FILENO);
336
337         /* don't let child see our files */
338         for (n = 3; n < 256; n++)
339                 close(n);
340
341         if (rec->shell) {
342                 execvp(shell_args[0], (char **) shell_args);
343
344                 fprintf(stderr, "Exec: /bin/sh: %s\n", g_strerror(errno));
345         } else {
346                 args = g_strsplit(cmd, " ", -1);
347                 execvp(args[0], args);
348
349                 fprintf(stderr, "Exec: %s: %s\n", args[0], g_strerror(errno));
350         }
351
352         _exit(-1);
353 }
354
355 static void sig_exec_input_reader(PROCESS_REC *rec)
356 {
357         char tmpbuf[512], *str;
358         int recvlen;
359         int ret;
360
361         g_return_if_fail(rec != NULL);
362
363         recvlen = net_receive(rec->in, tmpbuf, sizeof(tmpbuf));
364         do {
365                 ret = line_split(tmpbuf, recvlen, &str, &rec->databuf);
366                 if (ret == -1) {
367                         /* link to terminal closed? */
368                         g_source_remove(rec->read_tag);
369                         rec->read_tag = -1;
370                         break;
371                 }
372
373                 if (ret > 0) {
374                         signal_emit_id(signal_exec_input, 2, rec, str);
375                         if (recvlen > 0) recvlen = 0;
376                 }
377         } while (ret > 0);
378 }
379
380 static void handle_exec(const char *args, GHashTable *optlist,
381                         SERVER_REC *server, WI_ITEM_REC *item)
382 {
383         PROCESS_REC *rec;
384         SERVER_REC *target_server;
385         char *target, *level;
386         int notice, signum, interactive, target_nick, target_channel, kill_ret;
387
388         /* check that there's no unknown options. we allowed them
389            because signals can be used as options, but there should be
390            only one unknown option: the signal name/number. */
391         signum = cmd_options_get_signal("exec", optlist);
392         if (signum == -2)
393                 return;
394
395         if (*args == '\0') {
396                 exec_show_list();
397                 return;
398         }
399
400         target = NULL;
401         target_server = NULL;
402         notice = FALSE;
403
404         if (g_hash_table_lookup(optlist, "in") != NULL) {
405                 rec = process_find(g_hash_table_lookup(optlist, "in"), TRUE);
406                 if (rec != NULL) {
407                         net_sendbuffer_send(rec->out, args, strlen(args));
408                         net_sendbuffer_send(rec->out, "\n", 1);
409                 }
410                 return;
411         }
412
413         /* check if args is a process ID or name. if it's ID but not found,
414            complain about it and fail immediately */
415         rec = process_find(args, *args == '%');
416         if (*args == '%' && rec == NULL)
417                 return;
418
419         /* common options */
420         target_channel = target_nick = FALSE;
421         if (g_hash_table_lookup(optlist, "out") != NULL) {
422                 /* redirect output to active channel/query */
423                 if (item == NULL)
424                         cmd_return_error(CMDERR_NOT_JOINED);
425                 target = (char *) window_item_get_target(item);
426                 target_server = item->server;
427                 target_channel = IS_CHANNEL(item);
428                 target_nick = IS_QUERY(item);
429         } else if (g_hash_table_lookup(optlist, "msg") != NULL) {
430                 /* redirect output to /msg <nick> */
431                 target = g_hash_table_lookup(optlist, "msg");
432                 target_server = server;
433         } else if (g_hash_table_lookup(optlist, "notice") != NULL) {
434                 target = g_hash_table_lookup(optlist, "notice");
435                 target_server = server;
436                 notice = TRUE;
437         }
438
439         /* options that require process ID/name as argument */
440         if (rec == NULL &&
441             (signum != -1 || g_hash_table_lookup(optlist, "close") != NULL)) {
442                 printtext(NULL, NULL, MSGLEVEL_CLIENTERROR,
443                           "Unknown process name: %s", args);
444                 return;
445         }
446         if (g_hash_table_lookup(optlist, "close") != NULL) {
447                 /* forcibly close the process */
448                 process_destroy(rec, -1);
449                 return;
450         }
451
452         if (signum != -1) {
453                 /* send a signal to process group */
454                 kill_ret = kill(-rec->pid, signum);
455                 if (kill_ret != 0)
456                         printtext(NULL, NULL, MSGLEVEL_CLIENTERROR,
457                                   "Error sending signal %d to pid %d: %s",
458                                   signum, rec->pid, g_strerror(errno));
459                 return;
460         }
461
462         interactive = g_hash_table_lookup(optlist, "interactive") != NULL;
463         if (*args == '%') {
464                 /* do something to already existing process */
465                 char *name;
466
467                 if (target != NULL) {
468                         /* redirect output to target */
469                         g_free_and_null(rec->target);
470                         rec->target = g_strdup(target);
471                         rec->target_server = target_server == NULL ? NULL :
472                                 g_strdup(target_server->tag);
473                         rec->notice = notice;
474                 }
475
476                 name = g_hash_table_lookup(optlist, "name");
477                 if (name != NULL) {
478                         /* change window name */
479                         g_free_not_null(rec->name);
480                         rec->name = *name == '\0' ? NULL : g_strdup(name);
481                 } else if (target == NULL &&
482                            (rec->target_item == NULL || interactive)) {
483                         /* no parameters given,
484                            redirect output to the active window */
485                         g_free_and_null(rec->target);
486                         rec->target_win = active_win;
487
488                         if (rec->target_item != NULL)
489                                 exec_wi_destroy(rec->target_item);
490
491                         if (interactive) {
492                                 rec->target_item =
493                                         exec_wi_create(active_win, rec);
494                         }
495                 }
496                 return;
497         }
498
499         /* starting a new process */
500         rec = g_new0(PROCESS_REC, 1);
501         rec->pid = -1;
502         rec->shell = g_hash_table_lookup(optlist, "nosh") == NULL;
503
504         process_exec(rec, args);
505         if (rec->pid == -1) {
506                 /* pipe() or fork() failed */
507                 g_free(rec);
508                 cmd_return_error(CMDERR_ERRNO);
509         }
510
511         rec->id = process_get_new_id();
512         rec->target = g_strdup(target);
513         rec->target_server = target_server == NULL ? NULL :
514                 g_strdup(target_server->tag);
515         rec->target_win = active_win;
516         rec->target_channel = target_channel;
517         rec->target_nick = target_nick;
518         rec->args = g_strdup(args);
519         rec->notice = notice;
520         rec->silent = g_hash_table_lookup(optlist, "-") != NULL;
521         rec->quiet = g_hash_table_lookup(optlist, "quiet") != NULL;
522         rec->name = g_strdup(g_hash_table_lookup(optlist, "name"));
523
524         level = g_hash_table_lookup(optlist, "level");
525         rec->level = level == NULL ? MSGLEVEL_CLIENTCRAP : level2bits(level, NULL);
526
527         rec->read_tag = g_input_add(rec->in, G_INPUT_READ,
528                                     (GInputFunction) sig_exec_input_reader,
529                                     rec);
530         processes = g_slist_append(processes, rec);
531
532         if (rec->target == NULL && interactive)
533                 rec->target_item = exec_wi_create(active_win, rec);
534
535         signal_emit("exec new", 1, rec);
536 }
537
538 /* SYNTAX: EXEC [-] [-nosh] [-out | -msg <target> | -notice <target>]
539                 [-name <name>] <cmd line>
540            EXEC -out | -window | -msg <target> | -notice <target> |
541                 -close | -<signal> %<id>
542            EXEC -in %<id> <text to send to process> */
543 static void cmd_exec(const char *data, SERVER_REC *server, WI_ITEM_REC *item)
544 {
545         GHashTable *optlist;
546         char *args;
547         void *free_arg;
548
549         g_return_if_fail(data != NULL);
550
551         if (cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS |
552                            PARAM_FLAG_UNKNOWN_OPTIONS | PARAM_FLAG_GETREST,
553                            "exec", &optlist, &args)) {
554                 handle_exec(args, optlist, server, item);
555                 cmd_params_free(free_arg);
556         }
557 }
558
559 static void sig_pidwait(void *pid, void *statusp)
560 {
561         PROCESS_REC *rec;
562         char *str;
563         int status = GPOINTER_TO_INT(statusp);
564
565         rec = process_find_pid(GPOINTER_TO_INT(pid));
566         if (rec == NULL) return;
567
568         /* process exited - print the last line if
569            there wasn't a newline at end. */
570         if (line_split("\n", 1, &str, &rec->databuf) > 0 && *str != '\0')
571                 signal_emit_id(signal_exec_input, 2, rec, str);
572
573         if (!rec->silent) {
574                 if (WIFSIGNALED(status)) {
575                         status = WTERMSIG(status);
576                         printtext(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
577                                   "process %d (%s) terminated with signal %d (%s)",
578                                   rec->id, rec->args,
579                                   status, g_strsignal(status));
580                 } else {
581                         status = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
582                         printtext(NULL, NULL, MSGLEVEL_CLIENTNOTICE,
583                                   "process %d (%s) terminated with return code %d",
584                                   rec->id, rec->args, status);
585                 }
586         }
587         process_destroy(rec, status);
588 }
589
590 static void sig_exec_input(PROCESS_REC *rec, const char *text)
591 {
592         WI_ITEM_REC *item;
593         SERVER_REC *server;
594         char *str;
595
596         if (rec->quiet)
597                 return;
598
599         item = NULL;
600         server = NULL;
601
602         if (rec->target != NULL) {
603                 if (rec->target_server != NULL) {
604                         server = server_find_tag(rec->target_server);
605                         if (server == NULL) {
606                                 /* disconnected - target is lost */
607                                 return;
608                         }
609                         item = NULL;
610                 } else {
611                         item = window_item_find(NULL, rec->target);
612                         server = item != NULL ? item->server :
613                                 active_win->active_server;
614                 }
615
616                 str = g_strconcat(rec->target_nick ? "-nick " :
617                                   rec->target_channel ? "-channel " : "",
618                                   rec->target, " ", text, NULL);
619                 signal_emit(rec->notice ? "command notice" : "command msg",
620                             3, str, server, item);
621                 g_free(str);
622         } else if (rec->target_item != NULL) {
623                 printtext(NULL, rec->target_item->visible_name,
624                           rec->level, "%s", text);
625         } else {
626                 printtext_window(rec->target_win, rec->level, "%s", text);
627         }
628 }
629
630 static void sig_window_destroyed(WINDOW_REC *window)
631 {
632         GSList *tmp;
633
634         /* window is being closed, if there's any /exec targets for it,
635            change them to active window. */
636         for (tmp = processes; tmp != NULL; tmp = tmp->next) {
637                 PROCESS_REC *rec = tmp->data;
638
639                 if (rec->target_win == window)
640                         rec->target_win = active_win;
641         }
642 }
643
644 static void event_text(const char *data, SERVER_REC *server, EXEC_WI_REC *item)
645 {
646         if (!IS_EXEC_WI(item))
647                 return;
648
649         net_sendbuffer_send(item->process->out, data, strlen(data));
650         net_sendbuffer_send(item->process->out, "\n", 1);
651         signal_stop();
652 }
653
654 void fe_exec_init(void)
655 {
656         command_bind("exec", NULL, (SIGNAL_FUNC) cmd_exec);
657         command_set_options("exec", "!- interactive nosh +name out +msg +notice +in window close +level quiet");
658
659         signal_exec_input = signal_get_uniq_id("exec input");
660         signal_add("pidwait", (SIGNAL_FUNC) sig_pidwait);
661         signal_add("exec input", (SIGNAL_FUNC) sig_exec_input);
662         signal_add("window destroyed", (SIGNAL_FUNC) sig_window_destroyed);
663         signal_add_first("send text", (SIGNAL_FUNC) event_text);
664 }
665
666 void fe_exec_deinit(void)
667 {
668         if (processes != NULL) {
669                 processes_killall(SIGTERM);
670                 sleep(1);
671                 processes_killall(SIGKILL);
672
673                 while (processes != NULL)
674                         process_destroy(processes->data, -1);
675         }
676
677         command_unbind("exec", (SIGNAL_FUNC) cmd_exec);
678
679         signal_remove("pidwait", (SIGNAL_FUNC) sig_pidwait);
680         signal_remove("exec input", (SIGNAL_FUNC) sig_exec_input);
681         signal_remove("window destroyed", (SIGNAL_FUNC) sig_window_destroyed);
682         signal_remove("send text", (SIGNAL_FUNC) event_text);
683 }