#define _GNU_SOURCE
#include "irc.h"
static char cvsrevision[] = "$Id: exec.c 156 2012-02-17 12:30:55Z keaston $";
CVS_REVISION(exec_c)
#include "struct.h"
#include "dcc.h"
#include "exec.h"
#include "vars.h"
#include "ircaux.h"
#include "commands.h"
#include "window.h"
#include "screen.h"
#include "hook.h"
#include "input.h"
#include "list.h"
#include "server.h"
#include "output.h"
#include "parse.h"
#include "newio.h"
#include "gui.h"
#define MAIN_SOURCE
#include "modval.h"
#include <unistd.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#ifdef HAVE_SYS_FILIO_H
#include <sys/filio.h>
#endif
#ifdef __EMX__
#include <process.h>
#endif
pid_t getpid(void);
typedef struct
{
int index;
char *name;
char *logical;
pid_t pid;
int p_stdin;
int p_stdout;
int p_stderr;
int counter;
char *redirect;
unsigned refnum;
int server;
char *who;
int exited;
int termsig;
int retcode;
List *waitcmds;
int dumb;
int disowned;
char *stdoutc;
char *stdoutpc;
char *stderrc;
char *stderrpc;
} Process;
static Process **process_list = NULL;
static int process_list_size = 0;
static void handle_filedesc (Process *, int *, int, int);
static void cleanup_dead_processes (void);
static void ignore_process (int index);
void kill_process (int, int);
static void kill_all_processes (int signo);
static int valid_process_index (int proccess);
static int is_logical_unique (char *logical);
int logical_to_index (const char *logical);
extern int dead_children_processes;
extern int in_cparse;
extern char *next_expr (char **, char);
#if !HAVE_DECL_SYS_SIGLIST && !HAVE_DECL__SYS_SIGLIST && !defined(__QNX__)
#if defined(WINNT) || defined(__EMX__)
char *sys_siglist[] = { "ZERO", "SIGINT", "SIGKILL", "SIGPIPE", "SIGFPE",
"SIGHUP", "SIGTERM", "SIGSEGV", "SIGTSTP",
"SIGQUIT", "SIGTRAP", "SIGILL", "SIGEMT", "SIGALRM",
"SIGBUS", "SIGLOST", "SIGSTOP", "SIGABRT", "SIGUSR1",
"SIGUSR2", "SIGCHLD", "SIGTTOU", "SIGTTIN", "SIGCONT" };
#elif HAVE_DECL_STRSIGNAL
#define USING_STRSIGNAL
#include <string.h>
#else
#include "sig.inc"
#endif
#endif
BUILT_IN_COMMAND(execcmd)
{
#if !defined(PUBLIC_ACCESS)
#if defined(EXEC_COMMAND)
char *who = NULL,
*logical = NULL,
*redirect = NULL,
*flag;
unsigned refnum = 0;
int sig,
len,
i,
refnum_flag = 0,
logical_flag = 0;
int direct = 0;
Process *proc;
char *stdoutc = NULL,
*stderrc = NULL,
*stderrpc = NULL,
*stdoutpc = NULL,
*endc = NULL;
if (get_int_var(EXEC_PROTECTION_VAR) && (current_on_hook != -1))
{
say("Attempt to use EXEC from within an ON function!");
say("Command \"%s\" not executed!", args);
say("Please read /HELP SET EXEC_PROTECTION");
say("for important details about this!");
return;
}
if (in_cparse)
{
yell("Something very bad just happened. an $exec() was issued");
yell("from within a format. Please notify panasync about this immediately");
yell("with what version of BitchX as well the scripts loaded when this happened");
return;
}
if (!*args)
{
int i;
if (!process_list)
{
say("No processes are running");
return;
}
say("Process List:");
for (i = 0; i < process_list_size; i++)
{
Process *proc = process_list[i];
if (!proc)
continue;
if (proc->logical)
say("\t%d (%s) (pid %d): %s",
i, proc->logical, proc->pid, proc->name);
else
say("\t%d (pid %d): %s", i, proc->pid, proc->name);
}
return;
}
while ((*args == '-') && (flag = next_arg(args, &args)))
{
flag++;
if (!*flag)
break;
len = strlen(flag);
if (my_strnicmp(flag, "OUT", len) == 0)
{
if (doing_privmsg)
redirect = "NOTICE";
else
redirect = "PRIVMSG";
if (!(who = get_target_by_refnum(0)))
{
say("No target for this window for -OUT");
return;
}
}
else if (my_strnicmp(flag, "NAME", len) == 0)
{
logical_flag = 1;
if (!(logical = next_arg(args, &args)))
{
say("You must specify a logical name");
return;
}
}
else if (my_strnicmp(flag, "WINDOW", len) == 0)
{
refnum_flag = 1;
refnum = current_refnum();
}
else if (my_strnicmp(flag, "MSG", len) == 0)
{
if (doing_privmsg)
redirect = "NOTICE";
else
redirect = "PRIVMSG";
if (!(who = next_arg(args, &args)))
{
say("No nicknames specified");
return;
}
}
else if (my_strnicmp(flag, "LINE", len) == 0)
{
if ((stdoutc = next_expr(&args, '{')) == NULL)
say("Need {...} argument for -LINE flag");
}
else if (my_strnicmp(flag, "LINEPART", len) == 0)
{
if ((stdoutpc = next_expr(&args, '{')) == NULL)
say("Need {...} argument for -LINEPART flag");
}
else if (my_strnicmp(flag, "ERROR", len) == 0)
{
if ((stderrc = next_expr(&args, '{')) == NULL)
say("Need {...} argument for -ERROR flag");
}
else if (my_strnicmp(flag, "ERRORPART", len) == 0)
{
if ((stderrpc = next_expr(&args, '{')) == NULL)
say("Need {...} argument for -ERRORPART flag");
}
else if (my_strnicmp(flag, "END", len) == 0)
{
if ((endc = next_expr(&args, '{')) == NULL)
say("Need {...} argument for -END flag");
}
else if (my_strnicmp(flag, "CLOSE", len) == 0)
{
if ((i = get_process_index(&args)) == -1)
return;
ignore_process(i);
return;
}
else if (my_strnicmp(flag, "NOTICE", len) == 0)
{
redirect = "NOTICE";
if (!(who = next_arg(args, &args)))
{
say("No nicknames specified");
return;
}
}
else if (my_strnicmp(flag, "START", len) == 0)
{
#ifndef __EMX__
if(fork() == 0)
{
int i;
#endif
char *name = args;
char **args, *arg;
int max, cnt;
cnt = 0;
max = 5;
args = new_malloc(sizeof(char *) * max);
while ((arg = new_next_arg(name, &name)))
{
if (!arg || !*arg)
break;
if (cnt == max)
{
max += 5;
RESIZE(args, char *, max);
}
args[cnt++] = arg;
}
args[cnt] = NULL;
#ifndef __EMX__
for (i = 3; i < 256; i++)
close(i);
setsid();
execvp(args[0], args);
_exit(-1);
}
#else
spawnvp(P_SESSION, args[0], args);
#endif
return;
}
else if (my_strnicmp(flag, "IN", len) == 0)
{
if ((i = get_process_index(&args)) == -1)
return;
text_to_process(i, args, 1);
return;
}
else if (my_strnicmp(flag, "INQUIET", len) == 0)
{
if ((i = get_process_index(&args)) == -1)
return;
text_to_process(i, args, 0);
return;
}
else if (my_strnicmp(flag, "DIRECT", len) == 0)
direct = 1;
else
{
if (*args != '%')
{
say("%s is not a valid process", args);
return;
}
if ((i = get_process_index(&args)) == -1)
return;
if ((sig = my_atol(flag)) > 0)
{
if ((sig > 0) && (sig < NSIG-1))
kill_process(i, sig);
else
say("Signal number can be from 1 to %d", NSIG-1);
return;
}
for (sig = 1; sig < NSIG-1; sig++)
{
#if defined(USING_STRSIGNAL)
if (strsignal(sig) && !my_strnicmp(strsignal(sig), flag, len))
#else
if (sys_siglist[sig] && !my_strnicmp(sys_siglist[sig], flag, len))
#endif
{
kill_process(i, sig);
return;
}
}
say("No such signal: %s", flag);
return;
}
}
if (*args == '%')
{
int i;
if ((i = get_process_index(&args)) == -1)
return;
proc = process_list[i];
message_to(refnum);
if (refnum_flag)
{
proc->refnum = refnum;
if (refnum)
say("Output from process %d (%s) now going to this window", i, proc->name);
else
say("Output from process %d (%s) not going to any window", i, proc->name);
}
malloc_strcpy(&(proc->redirect), redirect);
malloc_strcpy(&(proc->who), who);
if (redirect)
say("Output from process %d (%s) now going to %s", i, proc->name, who);
else
say("Output from process %d (%s) now going to you", i, proc->name);
if (logical_flag)
{
if (is_logical_unique(logical))
{
malloc_strcpy(&proc->logical, logical);
say("Process %d (%s) is now called %s",
i, proc->name, proc->logical);
}
else
say("The name %s is not unique!", logical);
}
message_to(0);
}
else
{
int p0[2], p1[2], p2[2],
pid, cnt;
char *shell,
*flag,
*arg;
char *name = args;
if (!is_logical_unique(logical))
{
say("The name %s is not unique!", logical);
return;
}
p0[0] = p1[0] = p2[0] = -1;
p0[1] = p1[1] = p2[1] = -1;
if (pipe(p0) || pipe(p1) || pipe(p2))
{
say("Unable to start new process: %s", strerror(errno));
new_close(p0[0]);
new_close(p0[1]);
new_close(p1[0]);
new_close(p1[1]);
new_close(p2[0]);
new_close(p2[1]);
return;
}
#ifndef __EMXPM__
switch ((pid = fork()))
{
case -1:
say("Couldn't start new process!");
break;
case 0:
{
int i;
#if !defined(WINNT) && !defined(__EMX__)
setsid();
#endif
setuid(getuid());
setgid(getgid());
my_signal(SIGINT, SIG_IGN, 0);
my_signal(SIGQUIT, SIG_DFL, 0);
my_signal(SIGSEGV, SIG_DFL, 0);
my_signal(SIGBUS, SIG_DFL, 0);
#endif
dup2(p0[0], 0);
dup2(p1[1], 1);
dup2(p2[1], 2);
#ifdef __EMXPM__
fcntl(p0[1], F_SETFD, FD_CLOEXEC);
fcntl(p1[0], F_SETFD, FD_CLOEXEC);
fcntl(p2[0], F_SETFD, FD_CLOEXEC);
#else
close(p0[1]);
close(p1[0]);
close(p2[0]);
p0[1] = p1[0] = p2[0] = -1;
for (i = 3; i < 256; i++)
close(i);
#endif
shell = get_string_var(SHELL_VAR);
if (direct || !shell)
{
char **args;
int max;
cnt = 0;
max = 5;
args = new_malloc(sizeof(char *) * max);
while ((arg = new_next_arg(name, &name)))
{
if (!arg || !*arg)
break;
if (cnt == max)
{
max += 5;
RESIZE(args, char *, max);
}
args[cnt++] = arg;
}
args[cnt] = NULL;
#ifndef __EMXPM__
execvp(args[0], args);
#else
pid = spawnvp(P_NOWAIT, args[0], args);
#endif
}
else
{
if (!(flag = get_string_var(SHELL_FLAGS_VAR)))
flag = empty_string;
#ifndef __EMXPM__
execl(shell, shell, flag, name, NULL);
#else
pid = spawnl(P_NOWAIT, shell, shell, flag, name, NULL);
#endif
}
#ifndef __EMXPM__
printf("*** Error starting shell \"%s\": %s\n",
shell, strerror(errno));
_exit(-1);
break;
}
default:
#else
if(pid == -1)
say("Couldn't start new process!");
else
#endif
{
int i;
Process *proc = new_malloc(sizeof(Process));
new_close(p0[0]);
new_close(p1[1]);
new_close(p2[1]);
if (!process_list)
{
process_list = new_malloc(sizeof(Process *));
process_list_size = 1;
process_list[0] = NULL;
}
for (i = 0; i < process_list_size; i++)
{
if (!process_list[i])
{
process_list[i] = proc;
break;
}
}
if (i == process_list_size)
{
process_list_size++;
RESIZE(process_list, Process *, process_list_size);
process_list[i] = proc;
}
proc->name = m_strdup(name);
proc->logical = logical ? m_strdup(logical) : NULL;
proc->index = i;
proc->pid = pid;
proc->p_stdin = p0[1];
proc->p_stdout = p1[0];
proc->p_stderr = p2[0];
proc->refnum = refnum;
proc->redirect = NULL;
if (redirect)
proc->redirect = m_strdup(redirect);
proc->server = current_window->server;
proc->counter = 0;
proc->exited = 0;
proc->termsig = 0;
proc->retcode = 0;
proc->waitcmds = NULL;
proc->who = NULL;
proc->disowned = 0;
if (who)
proc->who = m_strdup(who);
proc->dumb = 0;
if (stdoutc)
proc->stdoutc = m_strdup(stdoutc);
else
proc->stdoutc = NULL;
if (stdoutpc)
proc->stdoutpc = m_strdup(stdoutpc);
else
proc->stdoutpc = NULL;
if (stderrc)
proc->stderrc = m_strdup(stderrc);
else
proc->stderrc = NULL;
if (stderrpc)
proc->stderrpc = m_strdup(stderrpc);
else
proc->stderrpc = NULL;
if (endc)
add_process_wait(proc->index, endc);
new_open(proc->p_stdout);
new_open(proc->p_stderr);
#ifndef __EMXPM__
break;
}
#endif
}
}
cleanup_dead_processes();
#endif
#endif
}
void do_processes (fd_set *rd)
{
int i;
int limit;
if (!process_list)
return;
limit = get_int_var(SHELL_LIMIT_VAR);
for (i = 0; i < process_list_size; i++)
{
Process *proc = process_list[i];
if (!proc)
continue;
if (proc->p_stdout != -1 && FD_ISSET(proc->p_stdout, rd))
handle_filedesc(proc, &proc->p_stdout,
EXEC_PROMPT_LIST, EXEC_LIST);
if (proc->p_stderr != -1 && FD_ISSET(proc->p_stderr, rd))
handle_filedesc(proc, &proc->p_stderr,
EXEC_PROMPT_LIST, EXEC_ERRORS_LIST);
if (limit && proc->counter >= limit)
ignore_process(proc->index);
}
cleanup_dead_processes();
}
static void handle_filedesc (Process *proc, int *fd, int hook_nonl, int hook_nl)
{
char exec_buffer[IO_BUFFER_SIZE + 1];
int len;
switch ((len = dgets(exec_buffer, *fd, 0, IO_BUFFER_SIZE, NULL)))
{
case -1:
{
*fd = new_close(*fd);
if (proc->p_stdout == -1 && proc->p_stderr == -1)
proc->dumb = 1;
break;
}
case 0:
{
if (hook_nl == EXEC_LIST && proc->stdoutpc)
parse_line("EXEC", proc->stdoutpc, exec_buffer, 0, 0, 1);
else if (hook_nl == EXEC_ERRORS_LIST && proc->stderrpc)
parse_line("EXEC", proc->stderrpc, exec_buffer, 0, 0, 1);
else if (proc->logical)
do_hook(hook_nonl, "%s %s",
proc->logical, exec_buffer);
else
do_hook(hook_nonl, "%d %s",
proc->index, exec_buffer);
set_prompt_by_refnum(proc->refnum, exec_buffer);
update_input(UPDATE_ALL);
break;
}
default:
{
message_to(proc->refnum);
proc->counter++;
while (len > 0 && (exec_buffer[len] == '\n' ||
exec_buffer[len] == '\r'))
{
exec_buffer[len--] = 0;
}
if (proc->redirect)
redirect_text(proc->server, proc->who,
exec_buffer, proc->redirect, 1, 0);
if (hook_nl == EXEC_LIST && proc->stdoutc)
parse_line("EXEC", proc->stdoutc, exec_buffer, 0, 0, 1);
else if (hook_nl == EXEC_ERRORS_LIST && proc->stderrc)
parse_line("EXEC", proc->stderrc, exec_buffer, 0, 0,1);
else if (proc->logical)
{
if ((do_hook(hook_nl, "%s %s",
proc->logical, exec_buffer)))
if (!proc->redirect)
put_it("%s", exec_buffer);
}
else
{
if ((do_hook(hook_nl, "%d %s",
proc->index, exec_buffer)))
if (!proc->redirect)
put_it("%s", exec_buffer);
}
message_to(0);
}
}
}
extern SIGNAL_HANDLER(child_reap);
int get_child_exit (pid_t wanted)
{
Process *proc;
pid_t pid;
int status, i;
if (dead_children_processes)
while ((pid = waitpid(wanted, &status, WNOHANG)) > 0)
{
dead_children_processes--;
#ifdef __EMX__
my_signal(SIGCHLD, child_reap, 0);
#endif
if (wanted != -1 && pid == wanted)
{
if (WIFEXITED(status))
return WEXITSTATUS(status);
if (WIFSTOPPED(status))
return -(WSTOPSIG(status));
if (WIFSIGNALED(status))
return -(WTERMSIG(status));
}
else
{
for (i = 0; i < process_list_size; i++)
{
proc = process_list[i];
if (proc && proc->pid == pid)
{
proc->exited = 1;
#ifdef __EMX__
proc->disowned = 1;
#endif
proc->termsig = WTERMSIG(status);
proc->retcode = WEXITSTATUS(status);
#ifdef __EMXPM__
pm_seticon(last_input_screen);
#endif
break;
}
}
}
}
cleanup_dead_processes();
return -1;
}
void clean_up_processes (void)
{
if (process_list_size)
{
say("Closing all left over exec's");
kill_all_processes(SIGTERM);
sleep(2);
kill_all_processes(SIGKILL);
}
}
int text_to_process (int proc_index, const char *text, int show)
{
Process * proc;
char * my_buffer;
if (valid_process_index(proc_index) == 0)
return 1;
proc = process_list[proc_index];
message_to(proc->refnum);
if (show)
put_it("%s%s", get_prompt_by_refnum(proc->refnum), text);
message_to(0);
my_buffer = alloca(strlen(text) + 2);
strcpy(my_buffer, text);
strcat(my_buffer, "\n");
write(proc->p_stdin, my_buffer, strlen(my_buffer));
set_prompt_by_refnum(proc->refnum, empty_string);
return (0);
}
void exec_server_delete (int i)
{
int j;
for (j = 0; j < process_list_size; j++)
if (process_list[j] && process_list[j]->server >= i)
process_list[j]->server--;
}
void add_process_wait (int proc_index, const char *cmd)
{
Process *proc = process_list[proc_index];
List *new, *posn;
new = new_malloc(sizeof(List));
new->next = NULL;
new->name = m_strdup(cmd);
if ((posn = proc->waitcmds))
{
while (posn->next)
posn = posn->next;
posn->next = new;
}
else
proc->waitcmds = new;
}
static void cleanup_dead_processes (void)
{
int i, flag;
List *cmd,
*next;
Process *dead, *proc;
if (!process_list)
return;
for (i = 0; i < process_list_size; i++)
{
if (!(proc = process_list[i]))
continue;
if ((!proc->exited || !proc->dumb) && !proc->disowned)
continue;
dead = process_list[i];
process_list[i] = NULL;
next = dead->waitcmds;
dead->waitcmds = NULL;
while ((cmd = next))
{
next = cmd->next;
parse_line(NULL, cmd->name, empty_string, 0, 0, 1);
new_free(&cmd->name);
new_free((char **)&cmd);
}
if (dead->p_stdin != -1)
close(dead->p_stdin);
dead->p_stdout = new_close(dead->p_stdout);
dead->p_stderr = new_close(dead->p_stderr);
if (dead->logical && *dead->logical)
flag = do_hook(EXEC_EXIT_LIST, "%s %d %d",
dead->logical, dead->termsig,
dead->retcode);
else
flag = do_hook(EXEC_EXIT_LIST, "%d %d %d",
dead->index, dead->termsig, dead->retcode);
if (flag)
{
if (get_int_var(NOTIFY_ON_TERMINATION_VAR))
{
if (dead->termsig > 0 && dead->termsig < NSIG)
say("Process %d (%s) terminated with signal %s (%d)",
dead->index, dead->name,
#if defined(USING_STRSIGNAL)
strsignal(dead->termsig),
#else
sys_siglist[dead->termsig],
#endif
dead->termsig);
else if (dead->disowned)
say("Process %d (%s) disowned", dead->index, dead->name);
else
say("Process %d (%s) terminated with return code %d",
dead->index, dead->name, dead->retcode);
}
}
new_free(&dead->name);
new_free(&dead->logical);
new_free(&dead->who);
new_free(&dead->redirect);
new_free(&dead->stdoutc);
new_free(&dead->stdoutpc);
new_free(&dead->stderrc);
new_free(&dead->stderrpc);
new_free((char **)&dead);
}
for (i = process_list_size - 1; i >= 0; i--)
{
if (process_list[i])
break;
}
if (process_list_size != i + 1)
{
process_list_size = i + 1;
RESIZE(process_list, Process, process_list_size);
}
}
static void ignore_process (int index)
{
Process *proc;
if (valid_process_index(index) == 0)
return;
proc = process_list[index];
if (proc->p_stdin != -1)
proc->p_stdin = new_close(proc->p_stdin);
if (proc->p_stdout != -1)
proc->p_stdout = new_close(proc->p_stdout);
if (proc->p_stderr != -1)
proc->p_stderr = new_close(proc->p_stderr);
proc->dumb = 1;
}
void kill_process (int kill_index, int sig)
{
pid_t pgid;
if (!process_list || kill_index > process_list_size ||
!process_list[kill_index])
{
say("There is no such process %d", kill_index);
return;
}
say("Sending signal %s (%d) to process %d: %s",
#if defined(USING_STRSIGNAL)
strsignal(sig),
#else
sys_siglist[sig],
#endif
sig, kill_index, process_list[kill_index]->name);
#ifdef HAVE_GETPGID
pgid = getpgid(process_list[kill_index]->pid);
#else
# ifndef GETPGRP_VOID
pgid = getpgrp(process_list[kill_index]->pid);
# else
pgid = process_list[kill_index]->pid;
# endif
#endif
#ifndef HAVE_KILLPG
# define killpg(pg, sig) kill(-(pg), (sig))
#endif
if (pgid == getpid())
{
yell("--- exec'd process is in my job control session! Something is hosed ---");
return;
}
killpg(pgid, sig);
kill(process_list[kill_index]->pid, sig);
}
static void kill_all_processes (int signo)
{
int i;
int tmp;
tmp = window_display;
window_display = 0;
for (i = 0; i < process_list_size; i++)
{
if (process_list[i])
{
ignore_process(i);
kill_process(i, signo);
}
}
window_display = tmp;
}
static int valid_process_index (int process)
{
if ((process < 0) || (process >= process_list_size) || !process_list[process])
{
say("No such process number %d", process);
return (0);
}
return (1);
}
static int is_logical_unique (char *logical)
{
Process *proc;
int i;
if (!logical)
return 1;
for (i = 0; i < process_list_size; i++)
{
if (!(proc = process_list[i]) || !proc->logical)
continue;
if (!my_stricmp(proc->logical, logical))
return 0;
}
return 1;
}
int logical_to_index (const char *logical)
{
Process *proc;
int i;
for (i = 0; i < process_list_size; i++)
{
if (!(proc = process_list[i]) || !proc->logical)
continue;
if (!my_stricmp(proc->logical, logical))
return i;
}
return -1;
}
int get_process_index (char **args)
{
char *s = next_arg(*args, args);
return is_valid_process(s);
}
int is_valid_process (const char *arg)
{
if (!arg || *arg != '%')
return -1;
arg++;
if (is_number((char *)arg) && valid_process_index(my_atol((char *)arg)))
return my_atol((char *)arg);
else
return logical_to_index(arg);
return -1;
}
int process_is_running (char *arg)
{
int idx = is_valid_process(arg);
if (idx == -1)
return 0;
else
return 1;
}
void set_process_bits(fd_set *rd)
{
int i;
Process *proc;
if (process_list)
{
for (i = 0; i < process_list_size; i++)
{
if ((proc = process_list[i]) != NULL)
{
if (proc->p_stdout != -1)
FD_SET(proc->p_stdout, rd);
if (proc->p_stderr != -1)
FD_SET(proc->p_stderr, rd);
}
}
}
}