static char rcsid[] = "@(#)$Id: syscall.c,v 1.19 2006/04/09 07:37:06 hurtta Exp $";

/******************************************************************************
 *  The Elm (ME+) Mail System  -  $Revision: 1.19 $   $State: Exp $
 *
 *  Modified by: Kari Hurtta <hurtta+elm@posti.FMI.FI> 
 *                           (was hurtta+elm@ozone.FMI.FI)
 ******************************************************************************
 *
 * Some code based on ../src/syscall.c. It have following copyright:
 *
 *  The Elm Mail System 
 *
 *			Copyright (c) 1988-1992 USENET Community Trust
 *			Copyright (c) 1986,1987 Dave Taylor
 *****************************************************************************/

#include "headers.h"
#include "s_elm.h"

DEBUG_VAR(Debug,__FILE__,"system");

#include <errno.h>

static void no_SR_print_status P_((struct run_state *rs,int sig,
				   int exit_code));
static void no_SR_print_status(rs,sig,exit_code)
     struct run_state *rs;
     int sig;
     int exit_code;
{
    DPRINT(Debug,5,(&Debug, "No print_status_hook ...\n"));
}

static int  no_SR_RawState     P_((void));
static int  no_SR_RawState()
{
    DPRINT(Debug,5,(&Debug, "No RawState_hook ...\n"));
    return 0;
}

static void no_SR_Raw          P_((int state));
static void no_SR_Raw(state)
     int state;
{
    DPRINT(Debug,5,(&Debug, "No Raw_hook ...\n"));
}

static void no_SR_tty_init     P_((void));
static void no_SR_tty_init()
{
    DPRINT(Debug,5,(&Debug, "No run_tty_init_hook ...\n"));
}

static void no_SR_ClearScreen  P_((void));
static void no_SR_ClearScreen()
{
    DPRINT(Debug,5,(&Debug, "No ClearScreen_hook ...\n"));
}

static char *us2s P_((unsigned char *str));
static char *us2s(str) 
     unsigned char *str;
{
    return (char *)str;
}

static void no_SR_PutLineS P_((struct string *S));
static void no_SR_PutLineS(S)
     struct string *S;
{
    struct string * R1 = convert_string(display_charset,S,1);
    char * store1;

    DPRINT(Debug,5,(&Debug, "No PutLineS_hook ...\n"));
       	
    store1 = us2s(stream_from_string(R1,0,NULL));

    fprintf(stderr,"%s",store1);
    
    free(store1);
    free_string(&R1);
}

static SR_print_status * print_status_hook = no_SR_print_status;
static SR_RawState     * RawState_hook     = no_SR_RawState;
static SR_Raw          * Raw_hook          = no_SR_Raw;
static SR_tty_init     * run_tty_init_hook = no_SR_tty_init;
static SR_ClearScreen  * ClearScreen_hook  = no_SR_ClearScreen;
static SR_PutLineS     * PutLineS_hook     = no_SR_PutLineS;
static SR_print_status * print_status_cooked_hook = no_SR_print_status;

void set_start_run_hooks(print_status_func,raw_query_func,raw_set_func,
			 run_tty_init,clear_screen_func,put_line_function,
			 print_status_cooked_func)
     SR_print_status * print_status_func;
     SR_RawState     * raw_query_func;
     SR_Raw          * raw_set_func;
     SR_tty_init     * run_tty_init;
     SR_ClearScreen  * clear_screen_func;
     SR_PutLineS     * put_line_function;
     SR_print_status * print_status_cooked_func;
{
    print_status_hook = print_status_func;
    RawState_hook     = raw_query_func;
    Raw_hook          = raw_set_func;
    run_tty_init_hook = run_tty_init;
    ClearScreen_hook  = clear_screen_func;
    PutLineS_hook     = put_line_function;
    print_status_cooked_hook = print_status_cooked_func;
}

int set_child_signals(options)
     int options;
{
    /*
     * Program to exec may or may not be able to handle
     * interrupt, quit, hangup and stop signals.
     */
    if (options&SY_ENAB_SIGINT)
	options |= SY_ENAB_SIGHUP;
    (void) signal(SIGHUP,  (options&SY_ENAB_SIGHUP) ? SIG_DFL : SIG_IGN);
    (void) signal(SIGINT,  (options&SY_ENAB_SIGINT) ? SIG_DFL : SIG_IGN);
    (void) signal(SIGQUIT, (options&SY_ENAB_SIGINT) ? SIG_DFL : SIG_IGN);
#ifdef SIGTSTP
    (void) signal(SIGTSTP, (options&SY_ENAB_SIGHUP) ? SIG_DFL : SIG_IGN);
    (void) signal(SIGCONT, (options&SY_ENAB_SIGHUP) ? SIG_DFL : SIG_IGN);
#endif
    return 0;
}

int set_child_env(options)
     int options;
{
    /* Optionally override the MM_CHARSET environment variable. */
    if (options&SY_ENV_METAMAIL) {
	char * A = display_charset->MIME_name;
	char mm[] = "MM_CHARSET=";
	char mm1[] = "MAILCAPS=";

	if (A) {
	    int size = sizeof(mm) + strlen(A);
	    
	    /* \0 character is included in size returned by sizeof */
	    char *p = malloc(size);
	    if (p) {
		elm_sfprintf(p, size,
			     FRM("%s%s"), mm , A );
		if (0 == putenv(p)) {
		    DPRINT(Debug,5,(&Debug, "Child: added %s\n",p));
		}
	    }
	}

	A = give_dt_path_as_str(&metamail_mailcaps,"metamail-mailcaps");
	if (A) {
	    int size = sizeof(mm1) + strlen(A);
	    
	    /* \0 character is included in size returned by sizeof */
	    char *p = malloc(size);
	    if (p) {
		elm_sfprintf(p, size,
			     FRM("%s%s"), mm1 , A );
		if (0 == putenv(p)) {
		    DPRINT(Debug,5,(&Debug, "Child: added %s\n",p));
		}
	    }
	}

	/* Used 'h' command */
	if (!elm_filter) {
	    static char mm2[] = "KEYHEADS=*";	
	    static char mm3[] = "KEYIGNHEADS=";	

	    if (0 == putenv(mm2)) {
		DPRINT(Debug,5,(&Debug, "Child: added %s\n",mm2));
	    }
	    if (0 == putenv(mm3)) {
		DPRINT(Debug,5,(&Debug, "Child: added %s\n",mm3));
	    }
	}
    }
    
    /* Optionally override the SHELL environment variable. */
    if (options&SY_ENV_SHELL) {
	static char sheq[] = "SHELL=";
	char * sh =  "/bin/sh";
	int size;
	char *p;

	if (0 != (options & SY_USER_SHELL)) {
	    char * s = give_dt_estr_as_str(&shell_e,"shell");
	    if (s)
		sh = s;
	}

	size = sizeof(sheq) + strlen(sh);

	p = malloc(size);
	if (p) {
	    elm_sfprintf(p, size,
			 FRM("%s%s"), sheq, sh);
	    if (0 == putenv(p)) {
		DPRINT(Debug,5,(&Debug, "Child: added %s\n",p));
	    }
	}
    }
    return 0;
}

#ifdef BACKGROUD_PROCESSES       /* We assume POSIX in here */

VOLATILE int handle_sigchld = 0;       /* got SIGCHLD */

static struct process_list {
    union any_fd fd;
    char * message;
    struct run_state state_information;
    end_handler *handler;
    struct process_list * next;
} * my_processes = NULL;

static void got_sigchld P_((int sig));
static void got_sigchld (sig) 
     int sig;
{
    SIGDPRINT(Debug,2,(&Debug,
			"got_sigchld: Setting handle_sigchld\n"));

    handle_sigchld = 1;
}

void sigchld_handler() {
    
    DPRINT(Debug,2,(&Debug,
		    "sigchld_handler --> ENTER (not on signal)\n"));
    
    do {
	struct process_list *tmp = my_processes, *prev = NULL;
	handle_sigchld = 0;
	
	while (tmp) {
	    struct process_list * this = tmp;
	    int exit_code;
	    int ret = run_already_done(&(tmp->state_information),&exit_code);
	    
	    if (ret != 0) {
		
		tmp->handler(tmp->fd,tmp->message,&(tmp->state_information),
			     ret,exit_code);
		
		if (prev)
		    prev -> next = tmp -> next;
		else
		    my_processes = tmp -> next;
		
		DPRINT(Debug,2,(&Debug,
				"sigchld_handler: deleting %d from list: %s\n",
				tmp->state_information.pid, tmp->message));
		
		free(tmp->message);
		tmp = tmp -> next;
		free(this);
		continue;
	    }
	    
	    prev = tmp;
	    tmp  = tmp -> next;
	}
	
    } while (handle_sigchld);
    
    DPRINT(Debug,2,(&Debug,
		    "sigchld_handler --> LEAVE\n"));
}

void init_backgroud_handling () {
    /* We use POSIX sigaction here,
     * so that we not depend different semantic
     * between SYSV and BSD signal(SIGCHLD, ...)
     */
    
    struct sigaction new_child;
    
    new_child.sa_flags    = 0;
#ifdef SA_INTERRUPT
    new_child.sa_flags |= SA_INTERRUPT;           /* SunOS? */
#endif
    new_child.sa_handler = got_sigchld;
    sigemptyset(& (new_child.sa_mask));
    
    if (-1 == sigaction(SIGCHLD,&new_child,NULL)) {
	int err = errno;
	lib_error(CATGETS(elm_msg_cat, ElmSet, ElmSigaction,"sigaction: %s"),
		  error_description(err));
	exit (1);
    }
}

int maybe_background (rs,exit_code,fd,title,func) 
     struct run_state *rs;
     int *exit_code;
     union any_fd fd;
     char *title; 
     end_handler *func; 
{
    int ret;
    struct process_list *listptr;
    if (!title)
	title = "NO NAME";
    
    if (rs->raw == ON && RawState_hook() == OFF) {
	
	ret = wait_end (rs,exit_code);
	if (ret != 0)
	    return ret;
    }
    
    listptr = safe_malloc(sizeof (struct process_list));
    rs -> listptr = listptr;
    listptr -> fd      = fd;
    listptr -> message = safe_strdup(title);
    listptr -> state_information = *rs;
    listptr -> handler = func;
    listptr -> next = my_processes;
    my_processes = listptr;
    return 0;
} 
#endif

static void raw_exit P_((struct run_state *rs));
static void raw_exit(rs)
     struct run_state *rs;
{
    if (rs->raw == ON && RawState_hook() == OFF) {
	DPRINT(Debug,4,(&Debug,
			"raw_exit: setting RAW on\n"));
	Raw_hook(ON|NO_TITE);
    }  else {
	DPRINT(Debug,4,(&Debug,
			"raw_exit: no state change\n"));
    }
}


int run_already_done (rs,exit_code)
     struct run_state *rs;
     int *exit_code;
{
    S__ status;
    
#ifdef HASWAITPID
    int w;
    /* waitpid is on POSIX */
    
    *exit_code = -1;
    
    w = waitpid(rs->pid,&status,WNOHANG);
    
    if (w == 0) {
	DPRINT(Debug,2,(&Debug,
			"run_already_done=%d (w=%d)\n",0,w));
	return 0;
    }

    if (w == -1) {
	rs->save_errno = errno;
	DPRINT(Debug,2,(&Debug,
			"run_already_done: errno=%d\n", rs->save_errno));
	
	if (rs->save_errno == EINTR) {
	    DPRINT(Debug,2,(&Debug,
			    "run_already_done=%d (w=%d) [EINTR]\n",0,w));
	    return 0;
	}
    }
    
    raw_exit(rs);

    if(rs->pfd) {
	DPRINT(Debug,4,(&Debug,
			"run_already_done: closing rs->pfd=%p\n",
			rs->pfd));
	fclose(rs->pfd);
	rs->pfd = NULL;
    }

    if (w == rs->pid) {
	int sig = convert_status(status,exit_code);
	
	DPRINT(Debug,2,(&Debug,
			"run_already_done: exit_code=%d, sig=%d\n", 
			*exit_code,sig));
	
	print_status_hook(rs,sig,*exit_code) ;
	
	if (sig) {
	    DPRINT(Debug,2,(&Debug,
			    "run_already_done=%d\n",-sig));
	    return -sig;
	}
    }
    DPRINT(Debug,2,(&Debug,
		    "run_already_done=%d\n",w != -1));
    return w != -1;
#else
    DPRINT(Debug,2,(&Debug,
	       "run_already_done=%d (no HASWAITPID)\n",0));
    return 0;      
#endif
}

int wait_end  (rs,exit_code)
     struct run_state *rs;
     int *exit_code;
{
    int w;
    S__ status;
    
    *exit_code = -1;
    
    while ((w = my_wait(rs->pid,&status)) != rs->pid)
	if (w == -1 && errno != EINTR)
	    break;
    
    if (w == -1) {
	rs->save_errno = errno;
	DPRINT(Debug,2,(&Debug,
			"wait_end: errno=%d\n", rs->save_errno));    
    }
    
    raw_exit(rs);
    
    if(rs->pfd) {
	DPRINT(Debug,4,(&Debug,
			"wait_end: closing rs->pfd=%p\n",
			rs->pfd));
	fclose(rs->pfd);
	rs->pfd = NULL;
    }

    if (w == rs->pid) {
	int sig = convert_status(status,exit_code);
	
	DPRINT(Debug,2,(&Debug,
			"wait_end: exit_code=%d, sig=%d\n", 
			*exit_code,sig));
	
	print_status_hook(rs,sig,*exit_code);
	
	if (sig) {
	    DPRINT(Debug,2,(&Debug,
			    "wait_end=%d\n",-sig));
	    return -sig;
	}
    }
      
    DPRINT(Debug,2,(&Debug,
		    "wait_end=%d\n",w != -1));
    return w != -1;
}

CONST char ** join_argv(argv1,argv2)
     char * argv1[];
     char * argv2[];
{
    int count1;
    int count2;
    CONST char ** res;
    int i;
    for (count1 = 0; argv1[count1]; count1++);
    for (count2 = 0; argv2[count2]; count2++);
    
    res = safe_malloc((count1 + count2 + 1)* sizeof (char *));
    
    for (i = 0; i < count1; i++)
	res[i] = argv1[i];
    for (i = 0; i < count2; i++)
	res[i + count1] = argv2[i];
    res[count1 + count2] = NULL;
    
    return res;
}

void sr_call_ClearScreen() 
{
    ClearScreen_hook();
}


void sr_call_Raw(x)
     int x;
{
    Raw_hook(x);
}

int sr_call_RawState()
{
    int x = RawState_hook();
    return x;
}

/* copied from curses.c */
void sr_call_Write_to_screen (
#if ANSI_C
		      CONST char *format, CONST char *msg, ...
#else
		      format, msg, va_alist
#endif
		      )
#if !ANSI_C
     CONST char *format; 
     CONST char *msg;
     va_dcl
#endif
{
    va_list vl;
    struct string *text;

    Va_start(vl, msg);           /* defined in defs.h */
    
    /** This routine writes to the screen at the current location.
	when done, it increments lines & columns accordingly by
	looking for "\n" sequences... **/

    text = elm_smessage(0,format,msg,vl);
    PutLineS_hook(text);
    free_string(&text);
    
    va_end(vl);
    
}

void call_print_status_cooked(rs,sig,exit_code)
     struct run_state *rs;
     int sig;
     int exit_code;
{
    print_status_cooked_hook(rs,sig,exit_code);
}

int start_run(rs, options, argv, infd, outfd)
     struct run_state *rs;
     int options;
     CONST char * argv[];
     int infd, outfd;
{
    int count;
    int pfd[2], opipe[2];
    static int fd = -1;
    int notty    = (options & SY_NOTTY) != 0;
    int outpipe  = (options & SY_RUN_STATE_OPIPE) != 0;
    int errpipe  = (options & SY_RUN_STATE_EPIPE) != 0;
    
    DPRINT(Debug,2,(&Debug,
	       "start_run: [0] %s\n", argv[0]));
    for (count = 1; argv[count]; count++) {
	DPRINT(Debug,2,(&Debug,
			"           [%d] %s\n", 
			count,argv[count]));
    }
    DPRINT(Debug,2,(&Debug,
		    "    infd=%d, outfd=%d\n",infd,outfd));
    
    if (fd == -1)
	fd = open("/dev/null",O_RDWR);
    DPRINT(Debug,2,(&Debug,
		    "    fd=%d \n", fd));
    
    /* flush any pending output */
    fflush(stdout);
    rs->save_errno = 0;
    rs->raw     = RawState_hook();
    rs->options = options;
#ifdef BACKGROUD_PROCESSES      
    rs->listptr = NULL; 
#endif
    rs->pfd     = NULL;

    if (!notty) {
	run_tty_init_hook();

	if (rs->raw == ON) {
	    if (options & SY_CLRWAIT) {
		ClearScreen_hook();
	    }
	    DPRINT(Debug,4,(&Debug,
			    "start_run: setting RAW off\n"));
	    Raw_hook(OFF|NO_TITE);
	}
	if (options & SY_CLRWAIT) {
	    printf("Executing: %s ...\n\n",argv[0]);
	}
    }
    
    if (outpipe || errpipe) {
	if (-1 == pipe(opipe)) {
	    rs->save_errno = errno;
	    
	    raw_exit(rs);
	    return 0;
	}
    }

    if (pipe(pfd) == -1) {
	rs->save_errno = errno;
	
	if (outpipe || errpipe) {
	    close(opipe[0]);
	    close(opipe[1]);
	}

	raw_exit(rs);
	return 0;
    }

#ifdef FD_CLOEXEC
    fcntl(pfd[0], F_SETFD, FD_CLOEXEC);
    fcntl(pfd[1], F_SETFD, FD_CLOEXEC);
#else
    fcntl(pfd[0], F_SETFD, 1);
    fcntl(pfd[1], F_SETFD, 1);
#endif    

    rs->pid = fork();
    
    if (rs->pid == -1) {
	rs->save_errno = errno;

	if (outpipe || errpipe) {
	    close(opipe[0]);
	    close(opipe[1]);
	}
	close(pfd[0]);
	close(pfd[1]);
	
	raw_exit(rs);
	return 0;
    }
    else if (rs->pid == 0) {
	close(pfd[0]);
	if (outpipe || errpipe) 
	    close(opipe[0]);

	/*
	 * Set group and user back to their original values.
	 * Note that group must be set first.
	 */
	if (-1 == setgid(groupid)) {
	    int err = errno;
	    fprintf(stderr,"start_run: setgid(%d) FAILED: %s\n",
		    groupid,error_description(err));
	    fflush(stderr);
	    write(pfd[1],(void *)&err,sizeof err); _exit(127); 
	}
	if (-1 == setuid(userid)) {
	    int err = errno;
	    fprintf(stderr,"start_run: setruid(%d) FAILED: %s\n",
		    userid,error_description(err));
	    fflush(stderr);
	    write(pfd[1],(void *)&err,sizeof err); _exit(127); 
	}
	
	set_child_signals(options);
	
	set_child_env(options);
	
	if (options & SY_RUN_STATE_ENV) {
	    int i;
	    for (i = 0; rs->ext_env[i]; i++) {
		if (0 == putenv(rs->ext_env[i])) {
		    DPRINT(Debug,5,(&Debug, 
				    "Child: added %s\n",
				    rs->ext_env[i]));
		}
	    }
	}

	if (options & SY_RUN_STATE_INIT) {
	    rs->ext_init(rs);
	}

	if (outpipe || errpipe) {
	    if (outpipe) {
		if (opipe[1] != 1) {
		    if (-1 == dup2(opipe[1],1)) {
			write(pfd[1],(void *)&errno,sizeof errno); _exit(127); 
		    }	    
		}
	    }
	    if (errpipe) {
		if (opipe[1] != 2) {
		    if (-1 == dup2(opipe[1],2)) {
			write(pfd[1],(void *)&errno,sizeof errno); _exit(127); 
		    }	    
		}
	    }
	    if (opipe[1] != 1 && opipe[1] != 2) {
		close(opipe[1]);
	    }
	}

	if (notty) {
	    if ((infd != 0  && -1 == dup2(fd,0)) ||
		(!outpipe && outfd != 1 && -1 == dup2(fd,1)) ||
		(!errpipe && outfd != 2 && -1 == dup2(fd,2))) { 
		write(pfd[1],(void *)&errno,sizeof errno); _exit(127); 
	    }

#ifdef SIGTTSTP
	    signal(SIGTTIN, SIG_IGN); 
	    signal(SIGTTOU, SIG_IGN);
	    signal(SIGTSTP, SIG_IGN);
#endif
	}
	
	if (infd >= 0) {
	    if (infd != 0  && -1 == dup2(infd,0)) {
		write(pfd[1],(void *)&errno,sizeof errno); _exit(127); 
	    }
	}
	if (outfd >= 0) {
	    if (outfd != 1  && -1 == dup2(outfd,1)) {
		write(pfd[1],(void *)&errno,sizeof errno); _exit(127); 
	    }
	}



	/*              ... lose const */
	execvp(argv[0],(char **)argv);
	write(pfd[1],(void *)&errno,sizeof errno); _exit(127); 
    }
    else {
	int code, rd;
	close(pfd[1]);
	
	DPRINT(Debug,4,(&Debug,
			"start_run: child pid=%d\n", rs->pid));
	rd = read(pfd[0],(void *)&code, sizeof code);
	close(pfd[0]);
	
	if (outpipe || errpipe) 
	    close(opipe[1]);


	if (rd > 0) {
	    int exitcode;
	    
	    if (outpipe || errpipe) 
		close(opipe[0]);

	    wait_end(rs,&exitcode);
	    if (rd == sizeof code)
		rs->save_errno = code;
	    else
		rs->save_errno = 0;
	    return 0;
	}

	if (outpipe || errpipe) {
	    rs->pfd = fdopen(opipe[0],"r");
	    if (!rs->pfd) {
		DPRINT(Debug,4,(&Debug,
				"start_run: Failed open rs->pfd (fd=%d)\n",
				opipe[0]));
		close(opipe[0]);
	    } else {
		DPRINT(Debug,4,(&Debug,
				"start_run: opened rs->pfd=%p (fd=%d)\n",
				rs->pfd,opipe[0]));
	    }
	}
	return 1;
    }
    raw_exit(rs);

    return 0;
}

/*
 * Local Variables:
 *  mode:c
 *  c-basic-offset:4
 *  buffer-file-coding-system: iso-8859-1
 * End:
 */



syntax highlighted by Code2HTML, v. 0.9.1