
static char rcsid[] = "@(#)$Id: syscall.c,v 4.1.1.4 90/07/12 22:41:55 syd Exp $";

/*******************************************************************************
 *  The Elm Mail System  -  $Revision: 4.1.1.4 $   $State: Exp $
 *
 * 			Copyright (c) 1986, 1987 Dave Taylor
 * 			Copyright (c) 1988, 1989, 1990 USENET Community Trust
 *******************************************************************************
 * Bug reports, patches, comments, suggestions should be sent to:
 *
 *	Syd Weinstein, Elm Coordinator
 *	elm@DSI.COM			dsinc!elm
 *
 *******************************************************************************
 * $Log:	syscall.c,v $
 * Revision 4.1.1.4  90/07/12  22:41:55  syd
 * Make it aware of the fact that we loose the cursor position on
 * some system calls, so set it far enough off an absolute move will
 * be done on the next cursor address, and then place it where we want it.
 * From: Syd, reported by Douglas Lamb
 * 
 * Revision 4.1.1.3  90/06/21  22:48:17  syd
 * patch to fix up the Log headers.
 * From: pdc%lunch.wpd@sgi.com (Paul Close)
 * 
 * Revision 4.1.1.2  90/06/09  22:00:13  syd
 * Use a close-on-exec pipe to diagnose exec() failures.
 * From: tct!chip@uunet.UU.NET (Chip Salzenberg)
 * 
 * Revision 4.1.1.1  90/06/09  21:33:22  syd
 * Some wait(2) system calls return -1 and set errno=EINTR (interrupted system
 * call) when the editor is invoked, suspended, and then resumed.  Loop until
 * wait either returns pid, or returns -1 with errno != EINTR.
 * From: pdc%lunch.wpd@sgi.com (Paul Close)
 * 
 * Revision 4.1  90/04/28  22:44:18  syd
 * checkin of Elm 2.3 as of Release PL0
 * 
 *
 ******************************************************************************/

/** These routines are used for user-level system calls, including the
    '!' command and the '|' commands...

**/

#include "headers.h"

#include <signal.h>
#include <errno.h>

#ifdef BSD
#  include <sys/wait.h>
#endif

char *argv_zero();	
void  _exit();

#ifdef ALLOW_SUBSHELL

int
subshell()
{
	/** spawn a subshell with either the specified command
	    returns non-zero if screen rewrite needed
	**/

	char command[SLEN];
	int  old_raw, helpful, ret;

	helpful = (user_level == 0);

	if (helpful)
	  PutLine0(LINES-3,COLUMNS-40,"(Use the shell name for a shell.)");
	PutLine0(LINES-2,0,"Shell command: ");
	CleartoEOS();
	command[0] = '\0';
	(void) optionally_enter(command, LINES-2, 15, FALSE, FALSE);
	if (command[0] == 0) {
	  if (helpful)
	    MoveCursor(LINES-3,COLUMNS-40);
	  else
	    MoveCursor(LINES-2,0);
	  CleartoEOS();
	  return 0;
	}

	MoveCursor(LINES,0);
	CleartoEOLN();

	if ((old_raw = RawState()) == ON)
	  Raw(OFF);
	softkeys_off();
	if (cursor_control)
	  transmit_functions(OFF);
	
	umask(original_umask);	/* restore original umask so users new files are ok */
	ret = system_call(command, USER_SHELL, TRUE, TRUE);
	umask(077);		/* now put it back to private for mail files */

	SetXYLocation(0, 40);	/* a location not near the next request, so an absolute is used */
	PutLine0(LINES, 0, "\n\nPress any key to return to ELM: ");
	Raw(ON);
	(void) getchar();
	if (old_raw == OFF)
	  Raw(OFF);
	softkeys_on();
	if (cursor_control)
	  transmit_functions(ON);

	if (ret)
	  error1("Return code was %d.", ret);

	return 1;
}

#endif /* ALLOW_SUBSHELL */

system_call(string, shell_type, allow_signals, allow_interrupt)
char *string;
int   shell_type, allow_signals, allow_interrupt;
{
	/** execute 'string', setting uid to userid... **/
	/** if shell-type is "SH" /bin/sh is used regardless of the 
	    users shell setting.  Otherwise, "USER_SHELL" is sent.
	    If allow_signals is TRUE, then allow the executed
	    command handle hangup, and optionally if allow_interrupt
	    is also true handle interrupt in its own way.
	    This is useful for executed programs with
	    user interaction that handle those signals on their
	    own terms. It is especially important for vi, so that
	    a message being edited when a user connection is
	    dropped is recovered by vi's expreserve program **/

	int pfd[2], stat, pid, w, iteration;
	char *sh;
#if defined(BSD) && !defined(WEXITSTATUS)
	union wait status;
#else
	int status;
#endif
#ifdef VOIDSIG
	register void (*istat)(), (*qstat)();
# ifdef SIGTSTP
	register void (*oldstop)(), (*oldstart)();
# endif
#else
	register int (*istat)(), (*qstat)();
# ifdef SIGTSTP
	register int (*oldstop)(), (*oldstart)();
# endif 
#endif
	extern int errno;

	sh = (shell_type == USER_SHELL) ? shell : "/bin/sh";
	dprint(2, (debugfile, "System Call: %s\n\t%s\n", sh, string));

	/*
	 * Note the neat trick with close-on-exec pipes.
	 * If the child's exec() succeeds, then the pipe read returns zero.
	 * Otherwise, it returns the zero byte written by the child
	 * after the exec() is attempted.  This is the cleanest way I know
	 * to discover whether an exec() failed.   --CHS
	 */

	if (pipe(pfd) == -1) {
	  perror("pipe");
	  return -1;
	}
	fcntl(pfd[0], F_SETFD, 1);
	fcntl(pfd[1], F_SETFD, 1);

	istat = signal(SIGINT, SIG_IGN);
	qstat = signal(SIGQUIT, SIG_IGN);
#ifdef SIGTSTP
	oldstop = signal(SIGTSTP, SIG_DFL);
	oldstart = signal(SIGCONT, SIG_DFL);
#endif

	stat = -1;		/* Assume failure. */

	for (iteration = 0; iteration < 5; ++iteration) {
	  if (iteration > 0)
	    sleep(2);

#ifdef VFORK
	  pid = vfork();
#else
	  pid = fork();
#endif

	  if (pid != -1)
	    break;
	}

	if (pid == -1) {
	  perror("fork");
	}
	else if (pid == 0) {
	  /*
	   * Set group and user back to their original values.
	   * Note that group must be set first.
	   */
	  setgid(groupid);
	  setuid(userid);

	  /*
	   * Program to exec may or may not be able to handle
	   * interrupt, quit, hangup and stop signals.
	   */
	  (void) signal(SIGHUP, allow_signals ? SIG_DFL : SIG_IGN);
	  (void) signal(SIGINT, (allow_signals && allow_interrupt)?SIG_DFL:SIG_IGN);
	  (void) signal(SIGQUIT, (allow_signals && allow_interrupt)?SIG_DFL:SIG_IGN);
#ifdef SIGTSTP
	  (void) signal(SIGTSTP, allow_signals ? SIG_DFL : SIG_IGN);
	  (void) signal(SIGCONT, allow_signals ? SIG_DFL : SIG_IGN);
#endif

	  /* Go for it. */
	  execl(sh, argv_zero(sh), "-c", string, (char *) 0);

	  /* If exec fails, we write a byte to the pipe before exiting. */
	  perror(sh);
	  write(pfd[1], "", 1);
	  _exit(127);
	}
	else {
	  int rd;
	  char ch;

	  /* Try to read a byte from the pipe. */
	  close(pfd[1]);
	  rd = read(pfd[0], &ch, 1);
	  close(pfd[0]);

	  while ((w = wait(&status)) != pid)
	      if (w == -1 && errno != EINTR)
		  break;

	  /* If we read a byte from the pipe, the exec failed. */
	  if (rd > 0)
	    stat = -1;
	  else if (w == pid) {
#ifdef	WEXITSTATUS
	    stat = WEXITSTATUS(status);
#else
# ifdef	BSD
	    stat = status.w_retcode;
# else
	    stat = status;
# endif
#endif
	  }
  	}
  
	(void) signal(SIGINT, istat);
	(void) signal(SIGQUIT, qstat);
#ifdef SIGTSTP
	(void) signal(SIGTSTP, oldstop);
	(void) signal(SIGCONT, oldstart);
#endif

	return(stat);
}

int
do_pipe()
{
	/** pipe the current message or tagged messages to
	    the specified sequence.. **/

	char command[SLEN], buffer[SLEN], message_list[SLEN];
	register int  ret, to_pipe;
	int	old_raw;

	to_pipe = make_msg_list(message_list);
	sprintf(buffer, "Pipe message%s to: ", plural(to_pipe));
        PutLine0(LINES-2,0,buffer);

	command[0] = '\0';

	(void) optionally_enter(command, LINES-2, strlen(buffer), FALSE, FALSE);
	if (strlen(command) == 0) {
	  MoveCursor(LINES-2,0);	CleartoEOLN();
	  return(0);
	}

	MoveCursor(LINES,0); 	CleartoEOLN();
	if (( old_raw = RawState()) == ON)
	  Raw(OFF);

	if (cursor_control)  transmit_functions(OFF);
	
	sprintf(buffer, "%s -f %s -h %s | %s",
		readmsg,
		(folder_type == NON_SPOOL ? cur_folder : cur_tempfolder),
		message_list,
		command);
	
	ret = system_call(buffer, USER_SHELL, TRUE, TRUE);

	SetXYLocation(0, 40);	/* a location not near the next request, so an absolute is used */
	PutLine0(LINES, 0, "\n\nPress any key to return to ELM.");
	if (old_raw == ON)
	   Raw(ON);
	(void) getchar();
	if (cursor_control)  transmit_functions(ON);

	if (ret != 0) error1("Return code was %d.", ret);
	return(1);
}

print_msg()
{
	/** Print current message or tagged messages using 'printout' 
	    variable.  Error message iff printout not defined! **/

	char buffer[SLEN], filename[SLEN], printbuffer[SLEN];
	char message_list[SLEN];
	register int  retcode, to_print;

	if (strlen(printout) == 0) {
	  error("Don't know how to print - option \"printmail\" undefined!");
	  return;
	}
	
	to_print = make_msg_list(message_list);

	sprintf(filename,"%s%s%d", temp_dir, temp_print, getpid());

	if (in_string(printout, "%s"))
	  sprintf(printbuffer, printout, filename);
	else
	  sprintf(printbuffer, "%s %s", printout, filename);

	sprintf(buffer,"(%s -p -f %s%s > %s; %s 2>&1) > /dev/null",
		readmsg,
		(folder_type == NON_SPOOL ? cur_folder : cur_tempfolder),
		message_list, 
		filename,
		printbuffer);
	
	dprint(2, (debugfile, "Printing system call...\n"));

  	Centerline(LINES, "Queuing...");

	if ((retcode = system_call(buffer, SH, FALSE, FALSE)) == 0) {
	  sprintf(buffer, "Message%s queued up to print.", plural(to_print));
	  Centerline(LINES, buffer);
	}
	else
	  error1("Printout failed with return code %d.", retcode);

	unlink(filename);	/* remove da temp file! */
}

make_msg_list(message_list)
char *message_list;
{
	/** make a list of the tagged or just the current, if none tagged.
		check for overflow on messsage length
         **/

	int i, msgs_selected = 0;

	*message_list = '\0';	/* start with an empty list */

	for (i=0; i < message_count; i++) 
	  if (headers[i]->status & TAGGED) {
	    if (strlen(message_list) + 6 >= SLEN) {
	      error1("Too many messages selected, messages from %d on not used", i);
	      return(msgs_selected);
	      }
	    sprintf(message_list, "%s %d", message_list, 
		    headers[i]->index_number);
	    msgs_selected++;
	  }

	if (! msgs_selected) {
	  sprintf(message_list," %d", headers[current-1]->index_number);
	  msgs_selected = 1;
	}
	
	return(msgs_selected);
}

list_folders(numlines, helpmsg)
unsigned numlines;
char *helpmsg;
{
	/** list the folders in the users FOLDERHOME directory.  This is
	    simply a call to "ls -C"
	    Numlines is the number of lines to scroll afterwards. This is
	    useful when a portion of the screen needs to be cleared for
	    subsequent prompts, but you don't want to overwrite the
	    list of folders.
	    Helpmsg is what should be printed before the listing if not NULL.
	**/

	char buffer[SLEN];

	Raw(OFF);
	ClearScreen();
	MoveCursor(LINES, 0);
	if(helpmsg)
	  printf(helpmsg);
	sprintf(buffer, "cd %s;ls -C", folders);
	printf("\n\rContents of your folder directory:\n\r\n\r");
	system_call(buffer, SH, FALSE, FALSE); 
	while(numlines--)
	    printf("\n\r");
	Raw(ON);
}
