/*
* exec.c: handles exec'd process for IRCII
*
* Copyright 1990 Michael Sandrof
* Copyright 1997 EPIC Software Labs
* See the COPYRIGHT file, or do a HELP IRCII COPYRIGHT
*/
#include "irc.h"
static char cvsrevision[] = "$Id: exec.c,v 1.1.1.1 2003/04/11 01:09:07 dan Exp $";
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);
/* Process: the structure that has all the info needed for each process */
typedef struct
{
int index; /* Where in the proc array it is */
char *name; /* full process name */
char *logical;
pid_t pid; /* process-id of process */
int p_stdin; /* stdin description for process */
int p_stdout; /* stdout descriptor for process */
int p_stderr; /* stderr descriptor for process */
int counter; /* output line counter for process */
char *redirect; /* redirection command (MSG, NOTICE) */
unsigned refnum; /* a window for output to go to */
int server; /* the server to use for output */
char *who; /* nickname used for redirection */
int exited; /* true if process has exited */
int termsig; /* The signal that terminated
* the process */
int retcode; /* return code of process */
List *waitcmds; /* commands queued by WAIT -CMD */
int dumb; /* 0 if input still going, 1 if not */
int disowned; /* 1 if we let it loose */
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);
/*
* A nice array of the possible signals. Used by the coredump trapping
* routines and in the exec.c package
*/
#if !defined(SYS_SIGLIST_DECLARED) && !defined(_SYS_SIGLIST_DECLARED) && !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 defined(__GLIBC__)
#define _GNU_SOURCE
/* use strsignal() from <string.h> */
#include <string.h>
#else
#include "sig.inc"
#endif
#endif
/*
* exec: the /EXEC command. Does the whole shebang for ircII sub procceses
* I melded in about half a dozen external functions, since they were only
* ever used here. Perhaps at some point theyll be broken out again, but
* not until i regain control of this module.
*/
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;
/*
* Protect the user against themselves.
* XXX This is stupid.
*/
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 no args are given, list all the processes.
*/
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;
}
/*
* Walk through and parse out all the args
*/
while ((*args == '-') && (flag = next_arg(args, &args)))
{
flag++;
if (!*flag)
break;
len = strlen(flag);
/*
* /EXEC -OUT redirects all output from the /exec to the
* current channel for the current window.
*/
if (my_strnicmp(flag, "OUT", len) == 0)
{
if (doing_privmsg)
redirect = "NOTICE";
else
redirect = "PRIVMSG";
/* Now we can `/exec -o' to queries too. - djw */
if (!(who = get_target_by_refnum(0)))
{
say("No target for this window for -OUT");
return;
}
}
/*
* /EXEC -NAME gives the /exec a logical name that can be
* refered to as %name
*/
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;
}
}
/*
* /EXEC -WINDOW forces all output for an /exec to go to
* the window that is current at this time
*/
else if (my_strnicmp(flag, "WINDOW", len) == 0)
{
refnum_flag = 1;
refnum = current_refnum();
}
/*
* /EXEC -MSG <target> redirects the output of an /exec
* to the given target.
*/
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;
}
}
/*
* /EXEC -LINE specifies the stdout callback
*/
else if (my_strnicmp(flag, "LINE", len) == 0)
{
if ((stdoutc = next_expr(&args, '{')) == NULL)
say("Need {...} argument for -LINE flag");
}
/*
* /EXEC -LINEPART specifies the stdout partial line callback
*/
else if (my_strnicmp(flag, "LINEPART", len) == 0)
{
if ((stdoutpc = next_expr(&args, '{')) == NULL)
say("Need {...} argument for -LINEPART flag");
}
/*
* /EXEC -ERROR specifies the stderr callback
*/
else if (my_strnicmp(flag, "ERROR", len) == 0)
{
if ((stderrc = next_expr(&args, '{')) == NULL)
say("Need {...} argument for -ERROR flag");
}
/*
* /EXEC -ERRORPART specifies the stderr part line callback
*/
else if (my_strnicmp(flag, "ERRORPART", len) == 0)
{
if ((stderrpc = next_expr(&args, '{')) == NULL)
say("Need {...} argument for -ERRORPART flag");
}
/*
* /EXEC -END specifies the final collection callback
*/
else if (my_strnicmp(flag, "END", len) == 0)
{
if ((endc = next_expr(&args, '{')) == NULL)
say("Need {...} argument for -END flag");
}
/*
* /EXEC -CLOSE forcibly closes all the fd's to a process,
* in the hope that it will take the hint.
*/
else if (my_strnicmp(flag, "CLOSE", len) == 0)
{
if ((i = get_process_index(&args)) == -1)
return;
ignore_process(i);
return;
}
/*
* /EXEC -NOTICE <target> redirects the output of an /exec
* to a specified target
*/
else if (my_strnicmp(flag, "NOTICE", len) == 0)
{
redirect = "NOTICE";
if (!(who = next_arg(args, &args)))
{
say("No nicknames specified");
return;
}
}
/*
* /EXEC -start <command> starts a command with no association
* to the main BitchX process.
*/
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;
}
/*
* /EXEC -IN sends a line of text to a process
*/
else if (my_strnicmp(flag, "IN", len) == 0)
{
if ((i = get_process_index(&args)) == -1)
return;
text_to_process(i, args, 1);
return;
}
/*
* /EXEC -INQUIET quietly sends a line of text to a process
*/
else if (my_strnicmp(flag, "INQUIET", len) == 0)
{
if ((i = get_process_index(&args)) == -1)
return;
text_to_process(i, args, 0);
return;
}
/*
* /EXEC -DIRECT suppresses the use of a shell
*/
else if (my_strnicmp(flag, "DIRECT", len) == 0)
direct = 1;
/*
* All other - arguments are implied KILLs
*/
else
{
if (*args != '%')
{
say("%s is not a valid process", args);
return;
}
/*
* Check for a process to kill
*/
if ((i = get_process_index(&args)) == -1)
return;
/*
* Handle /exec -<num> %<process>
*/
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;
}
/*
* Handle /exec -<SIGNAME> %<process>
*/
for (sig = 1; sig < NSIG-1; sig++)
{
#if defined(_GNU_SOURCE)
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;
}
}
/*
* Give up! =)
*/
say("No such signal: %s", flag);
return;
}
}
/*
* This handles the form:
*
* /EXEC <flags> %process
*
* Where the user wants to redefine some options for %process.
*/
if (*args == '%')
{
int i;
/*
* Make sure the process is actually running
*/
if ((i = get_process_index(&args)) == -1)
return;
proc = process_list[i];
message_to(refnum);
/*
* Check to see if the user wants to change windows
*/
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);
}
/*
* Check to see if the user is changing the default target
*/
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);
/*
* Check to see if the user changed the NAME of %proc.
*/
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);
}
/*
* The user is trying to fire up a new /exec, so pass the buck.
*/
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;
/*
* Open up the communication pipes
*/
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;
/*
* CHILD: set up and exec the process
*/
case 0:
{
int i;
/*
* Fire up a new job control session,
* Sever all ties we had with the parent ircII process
*/
#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__
/* prevent inheritance */
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
/*
* Pretend to be just a dumb terminal
*/
/*
bsd_setenv("TERM", "tty", 1);
*/
/*
* Figure out what shell (if any) we're using
*/
shell = get_string_var(SHELL_VAR);
/*
* If we're not using a shell, doovie up the exec args
* array and pass it off to execvp
*/
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
}
/*
* If we're using a shell, let them have all the fun
*/
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__
/*
* Something really died if we got here
*/
printf("*** Error starting shell \"%s\": %s\n",
shell, strerror(errno));
_exit(-1);
break;
}
/*
* PARENT: add the new process to the process table list
*/
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]);
/*
* Init the proc list if neccesary
*/
if (!process_list)
{
process_list = new_malloc(sizeof(Process *));
process_list_size = 1;
process_list[0] = NULL;
}
/*
* Find the first empty proc entry
*/
for (i = 0; i < process_list_size; i++)
{
if (!process_list[i])
{
process_list[i] = proc;
break;
}
}
/*
* If there are no empty proc entries, make a new one.
*/
if (i == process_list_size)
{
process_list_size++;
RESIZE(process_list, Process *, process_list_size);
process_list[i] = proc;
}
/*
* Fill in the new proc entry
*/
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
}
/*
* do_processes: This is called from the main io() loop to handle any
* pending /exec'd events. All this does is call handle_filedesc() on
* the two reading descriptors. If an EOF is asserted on either, then they
* are closed. If EOF has been asserted on both, then we mark the process
* as being "dumb". Once it is reaped (exited), it is expunged.
*/
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);
}
/* Clean up any (now) dead processes */
cleanup_dead_processes();
}
/*
* This is the back end to do_processes, saves some repeated code
*/
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))) /* No buffering! */
{
case -1: /* Something died */
{
*fd = new_close(*fd);
if (proc->p_stdout == -1 && proc->p_stderr == -1)
proc->dumb = 1;
break;
}
case 0: /* We didnt get a full line */
{
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: /* We got a full line */
{
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);
}
}
}
/*
* get_child_exit: This looks for dead child processes of the client.
* There are two main sources of dead children: Either an /exec'd process
* has exited, or the client has attempted to fork() off a helper process
* (such as wserv or gzip) and that process has choked on itself.
*
* When SIGCHLD is recieved, the global variable 'dead_children_processes'
* is incremented. When this function is called, we go through and call
* waitpid() on all of the outstanding zombies, conditionally stopping when
* we reach a specific wanted sub-process.
*
* If you want to stop reaping children when a specific subprocess is
* reached, specify the process in 'wanted'. If all youre doing is cleaning
* up after zombies and /exec's, then 'wanted' should be -1.
*/
extern SIGNAL_HANDLER(child_reap);
int get_child_exit (pid_t wanted)
{
Process *proc;
pid_t pid;
int status, i;
/*
* Iterate until we've reaped all of the dead processes
* or we've found the one asked for.
*/
if (dead_children_processes)
while ((pid = waitpid(wanted, &status, WNOHANG)) > 0)
{
/*
* Ideally, this should never be < 0.
*/
dead_children_processes--;
#ifdef __EMX__
my_signal(SIGCHLD, child_reap, 0);
#endif
/*
* First thing we do is look to see if the process we're
* working on is the one that was asked for. If it is,
* then we get its exit status information and return it.
*/
if (wanted != -1 && pid == wanted)
{
if (WIFEXITED(status))
return WEXITSTATUS(status);
if (WIFSTOPPED(status))
return -(WSTOPSIG(status));
if (WIFSIGNALED(status))
return -(WTERMSIG(status));
}
/*
* If it wasnt the process asked for, then we've probably
* stumbled across a dead /exec'd process. Look for the
* corresponding child process, and mark it as being dead.
*/
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;
}
}
}
}
/*
* Now we may have reaped some /exec'd processes that were previously
* dumb and have now exited. So we call cleanup_dead_processes() to
* find and delete any such processes.
*/
cleanup_dead_processes();
/*
* If 'wanted' is not -1, then we didnt find that process, and
* if 'wanted' is -1, then you should ignore the retval anyhow. ;-)
*/
return -1;
}
/*
* clean_up_processes: In effect, we want to tell all of our sub processes
* that we're going away. We cant be 100% sure that theyre all dead by
* the time this function returns, but we can be 100% sure that they will
* be killed off next time they come up to run. This is the only thing that
* can be guaranteed, and is in fact all we really need to know.
*/
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);
}
}
/*
* text_to_process: sends the given text to the given process. If the given
* process index is not valid, an error is reported and 1 is returned.
* Otherwise 0 is returned.
* Added show, to remove some bad recursion, phone, april 1993
*/
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);
}
/*
* When a server goes away, re-assign all of the /exec's that are
* current bound to that server.
*/
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--;
}
/*
* This adds a new /wait %proc -cmd entry to a running process.
*/
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;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - */
/*
* A process must go through two stages to be completely obliterated from
* the client. Either stage may happen first, but until both are completed
* we keep the process around.
*
* 1) We must recieve an EOF on both stdin and stderr, or we must
* have closed stdin and stderr already (handled by do_processes)
* 2) The process must have died (handled by get_child_exit)
*
* The reason why both must happen is becuase the process can die (and
* we would get an async signal) before we read all of its output on the
* pipe, and if we simply deleted the process when it dies, we could lose
* some of its output. The reason why we cant delete a process that has
* asserted EOF on its output is because it could still be running (duh! ;-)
* So we wait for both to happen.
*/
/*
* This function is called by the three places that can effect a change
* on the state of a running process:
* 1) get_child_exit, which can mark a process as exited
* 2) do_processes, which can mark a child as being done with I/O
* 3) execcmd, which cacn mark a child as being done with I/O
*
* Any processes that are found to have both exited and having completed
* their I/O will be summarily destroyed.
*/
static void cleanup_dead_processes (void)
{
int i, flag;
List *cmd,
*next;
Process *dead, *proc;
if (!process_list)
return; /* Nothing to do */
for (i = 0; i < process_list_size; i++)
{
if (!(proc = process_list[i]))
continue;
/*
* We do not parse the process if it has not
* both exited and finished its io, UNLESS
* it has been disowned.
*/
if ((!proc->exited || !proc->dumb) && !proc->disowned)
continue; /* Not really dead yet */
dead = process_list[i];
process_list[i] = NULL;
/*
* First thing we do is run any /wait %proc -cmd commands
*/
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);
/*
* Flag /on exec_exit
*/
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);
/*
* Tell the user, if they care
*/
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(_GNU_SOURCE)
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);
}
}
/*
* Clean up after the process
*/
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);
}
/*
* Resize away any dead processes at the end
*/
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);
}
}
/*
* ignore_process: When we no longer want to communicate with the process
* any longer, we call here. It continues execution until it is done, but
* we are oblivious to any output it sends. Now, it will get an EOF
* condition on its output fd, so it will probably either take the hint, or
* its output will go the bit bucket (which we want to happen)
*/
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;
}
/*
* kill_process: sends the given signal to the specified process. It does
* not delete the process from the process table or anything like that, it
* only is for sending a signal to a sub process (most likely in an attempt
* to kill it.) The actual reaping of the children will take place async
* on the next parsing run.
*/
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(_GNU_SOURCE)
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
/* The exec'd process shouldn't be in our process group */
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);
}
/*
* This kills (sends a signal, *NOT* ``make it stop running'') all of the
* currently running subprocesses with the given signal. Presumably this
* is because you want them to die.
*
* Remember that UNIX signals are asynchornous. At best, you should assume
* that they have an advisory effect. You can tell a process that it should
* die, but you cannot tell it *when* it will die -- that is up to the system.
* That means that it is pointless to assume the condition of any of the
* kill()ed processes after the kill(). They may indeed be dead, or they may
* be ``sleeping but runnable'', or they might even be waiting for a hardware
* condition (such as a swap in). You do not know when the process will
* actually die. It could be 15 ns, it could be 15 minutes, it could be
* 15 years. Its also useful to note that we, as the parent process, will not
* recieve the SIGCHLD signal until after the child dies. That means it is
* pointless to try to reap any children processes here. The main io()
* loop handles reaping children (by calling get_child_exit()).
*/
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;
}
/* * * * * * logical stuff * * * * * * */
/*
* valid_process_index: checks to see if index refers to a valid running
* process and returns true if this is the case. Returns false otherwise
*/
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;
}
/*
* logical_to_index: converts a logical process name to it's approriate index
* in the process list, or -1 if not found
*/
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;
}
/*
* get_process_index: parses out a process index or logical name from the
* given string
*/
int get_process_index (char **args)
{
char *s = next_arg(*args, args);
return is_valid_process(s);
}
/*
* is_valid_process: tells me if the spec is a process that is either
* running or still has not closed its pipes, or both.
*/
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;
}
/*
* set_process_bits: This will set the bits in a fd_set map for each of the
* process descriptors.
*/
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);
}
}
}
}
syntax highlighted by Code2HTML, v. 0.9.1