/* Copyright (C) 2000-2003 Markus Lausser (sgop@users.sf.net) This is free software distributed under the terms of the GNU Public License. See the file COPYING for details. */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "lopster.h" #include "connection.h" #include "global.h" #include "search.h" #include "transfer.h" #include "resume.h" #include "interface.h" #include "support.h" #include "callbacks.h" #include "browse.h" #include "hotlist.h" #include "commands.h" #include "chat.h" #include "dirselect.h" #include "scheme.h" #include "handler.h" #include "resume.h" #include "server.h" #include "preferences.h" #include "log.h" #include "exec.h" #include "utils.h" #define MAX_PROCESS 5 #define O_NONE 0 #define O_WINDOW 1 #define O_CHANNEL 2 #define O_PRIVATE 3 #define O_OPERATOR 4 #define O_GLOBAL 5 #define O_WALLOP 6 #define O_EMOTE 7 static void exec_destroy(exec_t * exec); static void get_exec_input(gpointer data, gint source, GdkInputCondition condition); static void exec_print_line(exec_t * exec, char *line); static void exec_kill_all(void); static int process_cnt; static GList* process_queue; static GList* process_list; static int exec_find_free_id() { int id; GList *dlist; exec_t *exec; for (id = 1; id < 1000; id++) { for (dlist = process_list; dlist; dlist = dlist->next) { exec = (exec_t *)dlist->data; if (exec->id == id) break; } if (!dlist) return id; } return -1; } static exec_t *exec_new(char *command, int output, char *who, net_t *net, chat_page_t *page, void (*exit_cb)(void *, int), void *exit_cb_data, int verbose) { exec_t *exec; exec = g_malloc(sizeof(exec_t)); exec->command = g_strdup(command); exec->fd_watch = -1; exec->output = output; exec->who = who ? g_strdup(who) : 0; exec->id = exec_find_free_id(); exec->pid = 0; exec->net = net; exec->page = page; exec->timerid = -1; exec->exit_cb = exit_cb; exec->exit_cb_data = exit_cb_data; exec->verbose = verbose; exec->buffer = buffer_new(2048); process_list = g_list_append(process_list, exec); if (exec->verbose) client_message("Exec", "Created process %d: %s", exec->id, exec->command); return exec; } static int exec_timerid = -1; static int exec_fire_up(exec_t* exec); static void exec_try_dequeue() { while (process_queue && process_cnt < MAX_PROCESS) { exec_t *exec = process_queue->data; process_queue = g_list_remove(process_queue, exec); if (!exec_fire_up(exec)) break; } } static void exec_destroy(exec_t * exec) { if (exec->who) g_free(exec->who); g_free(exec->command); process_list = g_list_remove(process_list, exec); if (exec->fd_watch >= 0) gdk_input_remove(exec->fd_watch); if (exec->fd >= 0) close(exec->fd); buffer_destroy(exec->buffer); g_free(exec); } static void print_exec(exec_t * exec) { const char *output, *who, *wdl; switch (exec->output) { case O_NONE: output = "No Output"; wdl = who = ""; break; case O_WINDOW: default: output = "Window"; wdl = ":"; if (!exec->page || !g_list_find(global.chat_pages, exec->page)) { exec->page = NULL; who = "closed"; } else { who = exec->page->vname; } break; case O_CHANNEL: output = "Channel"; wdl = ":"; who = exec->who; break; case O_PRIVATE: output = "Private"; wdl = ":"; who = exec->who; break; case O_OPERATOR: output = "Channel op"; wdl = ":"; who = exec->who; break; case O_GLOBAL: output = "Global"; wdl = who = ""; break; case O_WALLOP: output = "Wallop"; wdl = who = ""; break; } if (exec->pid) client_message(NULL, "%2d [pid %d] [%s%s%s] %s", exec->id, exec->pid, output, wdl, who, exec->command); else client_message(NULL, "%2d [queued] [%s%s%s] %s", exec->id, output, wdl, who, exec->command); } static void exec_list() { GList *dlist; exec_t *exec; if (process_list) { client_message(NULL, "Process List:"); for (dlist = process_list; dlist; dlist = dlist->next) { exec = dlist->data; print_exec(exec); } } else { client_message(NULL, "No Processes"); } } struct exitdata { pid_t pid; int status; }; static int exitpipe[2]; static void sigchld(int n ATTR_UNUSED) { struct exitdata ed; while ((ed.pid = waitpid(-1, &ed.status, WNOHANG)) > 0) write(exitpipe[1], &ed, sizeof(ed)); } static exec_t *exec_find_pid(pid_t pid) { GList *dlist; for (dlist = process_list; dlist; dlist = dlist->next) { exec_t *exec = (exec_t *)dlist->data; if (exec->pid == pid) return exec; } return NULL; } static void exec_reap(void *data ATTR_UNUSED, int source ATTR_UNUSED, GdkInputCondition condition ATTR_UNUSED) { exec_t *exec; struct exitdata ed; while (read(exitpipe[0], &ed, sizeof(ed)) < 0); if (!(exec = exec_find_pid(ed.pid))) { // printf("unrecognized process %d reaped\n", ed.pid); return; } if (exec->timerid != -1) { // printf("[EXEC] removing timer %d in exec_reap\n", exec->timerid); g_source_remove(exec->timerid); } while (exec->fd != -1) get_exec_input(exec, 0, 0); if (exec->verbose) { if (WIFEXITED(ed.status)) client_message("Exec", "Process %d [pid %d] exited (status %d)", exec->id, exec->pid, WEXITSTATUS(ed.status)); else client_message("Exec", "Process %d [pid %d] exited (killed by signal %d)", exec->id, exec->pid, WTERMSIG(ed.status)); } if (exec->exit_cb) exec->exit_cb(exec->exit_cb_data, ed.status); process_cnt--; exec_destroy(exec); if (exec_timerid != -1) { // printf("[EXEC] removing timer %d in exec_dequeue\n", exec_timerid); g_source_remove(exec_timerid); exec_timerid = -1; } exec_try_dequeue(); } /* int exitpipe_watch; */ void exec_init(void) { struct sigaction sa; if (pipe(exitpipe)) return; /* XXX ooops */ /* exitpipe_watch = */ gdk_input_add(exitpipe[0], GDK_INPUT_READ, GTK_SIGNAL_FUNC(exec_reap), 0); sa.sa_handler = sigchld; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sigaction(SIGCHLD, &sa, 0); } void exec_shutdown(void) { exec_kill_all(); /* signal(SIGCHLD, SIGDFL); close(exitpipe[0]); close(exitpipe[1]); gdk_input_remove(exitpipe_watch); */ } static gboolean exec_kill_finish(void *data) { exec_t *exec = (exec_t *)data; killpg(exec->pid, SIGKILL); // printf("[EXEC] removing timer %d in exec_kill_finish\n", exec->timerid); exec->timerid = -1; return FALSE; } static int exec_kill_proc(exec_t *exec) { if (!exec->pid) { client_message("Exec", "Removing queued process %d ...", exec->id); process_queue = g_list_remove(process_queue, exec); exec_destroy(exec); return 1; } else { client_message("Exec", "Killing process %d [pid %d] ...", exec->id, exec->pid); kill(exec->pid, SIGTERM); exec->timerid = g_timeout_add(2000, exec_kill_finish, exec); // printf("[EXEC] added timer %d in exec_kill_proc\n", exec->timerid); return 0; } } static exec_t *exec_find_id(int id) { GList *dlist; exec_t *exec; for (dlist = process_list; dlist; dlist = dlist->next) { exec = (exec_t *)dlist->data; if (exec->id == id) return exec; } return NULL; } static void exec_kill(int id) { exec_t *exec; exec = exec_find_id(id); if (!exec) { client_message("Error", "Could not kill proccess: not found"); return; } exec_kill_proc(exec); } static void exec_kill_all() { GList *dlist, *nxt; for (dlist = process_list; dlist; dlist = nxt) { nxt = dlist->next; exec_kill_proc((exec_t *)dlist->data); } } static gboolean exec_retry_fire_up(void *data ATTR_UNUSED) { // printf("[EXEC] removing timer %d in exec_retry_fire_up\n", exec_timerid); exec_timerid = -1; exec_try_dequeue(); return FALSE; } static void exec_failed(exec_t* exec) { if (exec->verbose) client_message("Warning", "Unable to start process %d: %s. Retry in 20 secs.", exec->id, strerror(errno)); if (exec_timerid != -1) { // printf("[EXEC] removing timer %d in exec_failed\n", exec_timerid); g_source_remove(exec_timerid); } exec_timerid = g_timeout_add(20000, exec_retry_fire_up, 0); // printf("[EXEC] added timer %d in exec_failed\n", exec_timerid); process_queue = g_list_prepend(process_queue, exec); } static int exec_fire_up(exec_t* exec) { int p1[2]; pid_t pid; unsigned i; if (pipe(p1)) { exec_failed(exec); return 0; } switch ((pid = fork())) { case -1: exec_failed(exec); close(p1[0]); close(p1[1]); return 0; case 0: // child setsid(); dup2(p1[1], 1); // copy stdout dup2(p1[1], 2); // and stderr for (i = 3; i < 1024; i++) close(i); execl("/bin/sh", "sh", "-c", exec->command, 0); _exit(-1); default: // parent exec->pid = pid; close(p1[1]); fcntl(p1[0], F_SETFL, O_NONBLOCK); exec->fd = p1[0]; exec->fd_watch = gdk_input_add(exec->fd, GDK_INPUT_READ, GTK_SIGNAL_FUNC(get_exec_input), exec); // printf("[EXEC] added watch %d for fd %d\n", exec->fd_watch, exec->fd); if (exec->verbose) client_message("Exec", "Started process %d [pid %d]", exec->id, exec->pid); process_cnt++; return 1; } } static void exec_queue(exec_t* exec) { if (!process_queue && process_cnt < MAX_PROCESS) { exec_fire_up(exec); } else { process_queue = g_list_append(process_queue, exec); if (exec->verbose) client_message("Exec", "Queued process %d", exec->id); } } /* executes a command basic implementation by r00t added non blocking output read and more. */ void exec_command(net_t* net, char *cmd) { char *line; char *command; int output = O_WINDOW; char *who = NULL; chat_page_t* page = NULL; line = arg(cmd, 1); if (!line) { exec_list(); return; } if (!strncasecmp(line, "-out", 4)) { if (!net) { client_message("Error", "No Network specified"); return; } command = arg(line, 0); command = arg(NULL, 1); if (global.current_page->type == P_PUBLIC) { output = O_CHANNEL; who = global.current_page->name; page = global.current_page; } else if (global.current_page->type == P_PRIVATE) { output = O_PRIVATE; who = global.current_page->name; } else if (!strcmp("Wallop", global.current_page->name)) { output = O_WALLOP; } else if (!strcmp("Global", global.current_page->name)) { output = O_GLOBAL; } else { output = O_WINDOW; page = chat_page_get_printable(); } } else if (!strncasecmp(line, "-msg", 4) || !strncasecmp(line, "-whisper", 8)) { output = O_PRIVATE; command = arg(line, 0); who = arg(NULL, 0); command = arg(NULL, 1); } else if (!strncasecmp(line, "-wallop", 7)) { output = O_WALLOP; command = arg(line, 0); command = arg(NULL, 1); } else if (!strncasecmp(line, "-chop", 5)) { command = arg(line, 0); command = arg(NULL, 1); if (global.current_page->type == P_PUBLIC) { output = O_OPERATOR; who = global.current_page->name; page = global.current_page; } else { client_message("Error", "Switch to a channel"); return; } } else if (!strncasecmp(line, "-me", 3) || !strncasecmp(line, "-emote", 6)) { command = arg(line, 0); command = arg(NULL, 1); if (global.current_page->type == P_PUBLIC) { output = O_EMOTE; who = global.current_page->name; page = global.current_page; } else { client_message("Error", "Switch to a channel"); return; } } else if (!strncasecmp(line, "-quiet", 6)) { output = O_NONE; command = arg(line, 0); command = arg(NULL, 1); } else if (!strncasecmp(line, "-global", 7)) { output = O_GLOBAL; command = arg(line, 0); command = arg(NULL, 1); } else if (!strncasecmp(line, "-kill", 5)) { command = arg(line, 0); command = arg(NULL, 1); if (!command) { client_message("Error", "Kill which process?"); return; } if (!g_strcasecmp(command, "all")) exec_kill_all(); else exec_kill(atoi(command)); return; } else if (*line == '-') { client_message("Error", "Unknown option!"); return; } else { output = O_WINDOW; page = chat_page_get_printable(); command = line; } if (!command) { client_message("Error", "Execute what?"); return; } exec_queue(exec_new(command, output, who, net, page, 0, 0, 1)); } void exec_command_safe(void (*exit_cb)(void *, int), void *exit_cb_data, char *cmd, ...) { va_list args; char *cline, *targ; int clen, nlen, idx, quo; clen = nlen = strlen(cmd); va_start(args, cmd); while ((targ = va_arg (args, char *))) { quo = 0; for (idx = 0; targ[idx]; idx++) if (targ[idx] == '\'') { if (quo) { nlen++; quo = 0; } nlen++; } else if (!quo) { nlen++; quo = 1; } if (quo) nlen++; nlen += idx + 1; } nlen++; va_end(args); cline = g_malloc(nlen); memcpy(cline, cmd, clen); va_start(args, cmd); while ((targ = va_arg (args, char *))) { cline[clen++] = ' '; quo = 0; for (idx = 0; targ[idx]; idx++) { if (targ[idx] == '\'') { if (quo) { cline[clen++] = '\''; quo = 0; } cline[clen++] = '\\'; } else if (!quo) { cline[clen++] = '\''; quo = 1; } cline[clen++] = targ[idx]; } if (quo) cline[clen++] = '\''; } cline[clen] = 0; va_end(args); // printf("[EXEC] [%s]\n", cline); exec_queue(exec_new(cline, O_NONE, 0, 0, 0, exit_cb, exit_cb_data, 0)); g_free(cline); } static void get_exec_input(gpointer data, gint source ATTR_UNUSED, GdkInputCondition condition ATTR_UNUSED) { exec_t *exec = (exec_t *) data; int res; char *pos1; char *pos2; buffer_t* buffer = exec->buffer; // printf("reading %d [%d] ...\n", exec->fd, exec->id); switch ((res = read(exec->fd, buffer->data + buffer->datasize, buffer->datamax - buffer->datasize - 1))) { case -1: printf("[EXEC] error: %s\n", strerror(errno)); if (errno == EINTR) return; if (errno == EAGAIN) /* if this happens, a grandchild is holding the pipe open */ killpg(exec->pid, SIGKILL); else if (exec_kill_proc(exec)) return; case 0: // printf("[EXEC] finished\n"); // printf("[EXEC] removing watch %d for fd %d\n", exec->fd_watch, exec->fd); gdk_input_remove(exec->fd_watch); exec->fd_watch = -1; close(exec->fd); exec->fd = -1; } // update position buffer->datasize += res; buffer->data[buffer->datasize] = 0; pos1 = buffer->data; while ((pos2 = strchr(pos1, '\n')) || (pos2 = strchr(pos1, '\r'))) { *pos2 = 0; exec_print_line(exec, pos1); pos1 = pos2 + 1; } /* handle oversized lines! */ if (pos1 == buffer->data && buffer->datasize == buffer->datamax-1) { /* no newline and buffer is full */ exec_print_line(exec, buffer->data); buffer_consume(buffer, buffer->datasize); } else { buffer_consume(buffer, pos1 - buffer->data); } } static void exec_print_line(exec_t * exec, char *line) { char* prefix; // printf("printing: %s\n", line); if (exec->page && !g_list_find(global.chat_pages, exec->page)) { exec->page = NULL; printf("[EXEC] oops - page gone\n"); return; } switch (exec->output) { case O_NONE: break; case O_CHANNEL: if (exec->page) send_public(exec->page, line); break; case O_PRIVATE: send_notice(exec->net, exec->who, line, 1); break; case O_WALLOP: send_wallop(exec->net, line); break; case O_OPERATOR: if (exec->page) send_chwallop(exec->page, line); break; case O_EMOTE: if (exec->page) send_public_emote(exec->page, line); break; case O_GLOBAL: send_global(exec->net, line); break; case O_WINDOW: default: if (!exec->page) break; chat_print_time_stamp(exec->page, M_PUBLIC); prefix = cparse(global.scheme->client_prefix); chat_print_colored(exec->page, M_PUBLIC, "message", prefix); chat_print_colored(exec->page, M_PUBLIC, "message", line); chat_print_text(exec->page, M_PUBLIC, "message", "\n"); break; } }