/*
 * Copyright (c) 1986, 1987, 1988 by The Trustees of Columbia University
 * in the City of New York.  Permission is granted to any individual or
 * institution to use, copy, or redistribute this software so long as it
 * is not sold for profit, provided this copyright notice is retained.
 */

#ifndef lint
static char *rcsid = "$Header: exit.c,v 1.63 88/07/13 17:16:49 chris Exp $";
#endif

/*
 * exit.c - various ways to exit mm
 */

#include "mm.h"
#include "parse.h"
#include "cmds.h"

#ifdef notdef
#define mask(sig)   (1 << ((sig)-1))
static int TSTP, HUP, INT, XCPU;	/* which signals occurred */
/* previous signal handlers */
static signalhandler (*oldtstp)(), (*oldhup)(), (*oldint)(), (*oldxcpu)();
#endif

int
cmd_quit (n)
int n;
{
    confirm ();
    if (mode & MM_SEND) {		/* if sending, quit that mode */
	mode &= ~(MM_SEND|MM_ANSWER);
	return true;
    }
    if (mode & MM_READ) {		/* if reading, quit that mode */
	mode &= ~MM_READ;
	return true;
    }
    if (suspend_on_quit && (n == CMD_QUIT))  {
	suspend(UPD_SAVEMOD);
    }
    else {
	cmtend ();			/* let go of the terminal */
	if (cf != NULL)			/* save the file, don't expunge */
	    if (!update (&cf,UPD_SAVEMOD) && (cf->flags & MF_WRITERR)) {
		fprintf (stderr, "Cannot save file, not quitting.\n");
		fprintf (stderr,
"Use the SHELL or SUSPEND command to temporarily exit MM, free up some\n\
space, and then try again.\n");
		cmtset ();		/* set CCMD up again */
		cmcsb._cmwrp = autowrap_column;	/* put that back */
		return (false);
	    }
#ifdef MDEBUG
	m_done();
#endif /* MDEBUG */
#ifdef USAGEFILE
	usage_stop(USAGEFILE);
#endif
	exit (0);			/* quit entirely */
    }
}

int
cmd_exit (n)
int n;
{
    confirm ();
    if (!suspend_on_exit) {
	cmtend ();
	if (cf != NULL)			/* expunge while saving */
	    if (!update (&cf,UPD_EXPUNGE|UPD_SAVEMOD) &&
		(cf->flags & MF_WRITERR)) {
		fprintf (stderr, "Cannot save file, not exiting.\n\
Try suspending MM, and making space by deleting some files.\n");
		cmtset ();		/* set CCMD up again */
		cmcsb._cmwrp = autowrap_column;	/* put that back */
		return (false);
	    }

#ifdef MDEBUG	
	m_done();
#endif /* MDEBUG */
#ifdef USAGEFILE
	usage_stop(USAGEFILE);
#endif
	exit (0);
    }
    else
	suspend(UPD_EXPUNGE|UPD_SAVEMOD); /* squish on return */
	if (cf != NULL)			/* is it empty? */
	    squish (cf);		/* no, free deleted messages */
}

/*
 * cmd_expunge:
 * Write out the mail file without the deleted messages.  Adjust the
 * msgvec structure to remove these messages internally as well.
 */
int
cmd_expunge (n)
int n;
{
    confirm ();
    if (mode & MM_READ)	{		/* read mode? */
	fprintf (stderr,
		 "You cannot run expunge while in read mode, try write.\n");
	return;
    }
    if (!check_cf(true))		/* fails if no file or unwriteable */
	return;
    if (!update (&cf,UPD_EXPUNGE|UPD_SAVEMOD)) /* do the expunge */
	return;
    if (cf != nil)			/* is it empty? */
	squish (cf);			/* no, free deleted messages */
}

/*
 * cmd_write:
 * Write out the mail file with the deleted messages.
 */
int
cmd_write (n)
int n;
{
    char *savefile;			/* where to write it to */
    char *parse_output_file();

    noise ("out mail file to");
    if (!check_cf(false))		/* error return if no file */
	return;
    savefile = parse_output_file("confirm with carriage return\n\
  or alternate filename", cf->filename);
    confirm ();				/* XXX memory leak */
    if (strcmp (savefile, cf->filename) == 0) { /* same file, save work */
	if (cf->flags & MF_RDONLY)
	    cmerr ("cannot save read-only (examined) file\n");
	update (&cf,UPD_ALWAYS);	/* do the write */
	return;
    }
    else {
	char realfile[MAXPATHLEN];
	/* XXX This is bogus */
	strcpy (realfile,cf->filename);		/* remember real filename */
	strcpy (cf->filename, savefile);	/* pretend this is the file */
	update (&cf, UPD_ALWAYS|UPD_ALTFILE);
	strcpy (cf->filename, realfile);	/* put that back */
    }
    (void) free (savefile);
    return;
}


/*
 * squish:
 * Remove deleted messages from the msgvec structure passed, and
 * squish remaining messages together.
 */
squish (mail)
msgvec *mail;
{
    int oldpos,newpos;			/* to move remaining messages */
    char *text;				/* text to free */
    message *msgs;			/* gonna use it a lot */

    if ((msgs = mail->msgs) == NULL)	/* easy access! */
	return;				/* empty, nothing to squish */
    for (oldpos = newpos = 1; oldpos <= mail->count; oldpos++) {
	if (msgs[oldpos].flags & M_DELETED) {
	    if ((text = msgs[oldpos].text) != NULL)
		free (text);
	}
	else {
	    if (newpos != oldpos)	/* past deleted messages? */
		msgs[newpos] = msgs[oldpos]; /* move it down */
	    newpos++;
	}
    }
    mail->current = mail->count = newpos-1; /* didn't write to final pos'n */
    mail->msgs = (message *) realloc (mail->msgs, 
				      (mail->count+1)*sizeof (message));
}

int
cmd_take (n)
int n;
{
    extern top_level_parser();

    cmtake(top_level_parser);
    cmcsb._cmwrp = autowrap_column;	/* put that back! */
}

panic (s)
char *s;
{
    printf ("%s: panic: %s\n", progname, s);
    cmtend ();
    abort();
}

char *
errstr(err)
{
    static char temp[16];

    switch (err) {
      case 0:
	return (char *) "no error";
      case -1:
	err = errno;
      default:
	if ((err < 0) || (err > sys_nerr)) {
	    sprintf(temp,"Error %d", err);
	    return (char *) temp;
	}
	return (char *) sys_errlist[err];
    }
}

int
cmd_push (n)
int n;
{
    confirm ();
    shell (nil);
    return true;
}

shell (cmd)
char *cmd;
{
    int pid, status;
    char *getenv (), *cp;

    /*
     * put tty back the way we found it
     */
    cmtend ();

    /*
     * update the user's file, just in case
     */
    if (cf)
	update(&cf,0);

    /*
     * make sure user knows how to get back
     */
    if (!cmd)
	printf("Pushing to subshell, use \"exit\" to return to MM\n");

    if (!(cp = getenv ("SHELL")))
	cp = "/bin/sh";

    fix_signals_for_fork (true);

    pid = vfork ();
    if (pid == 0) {
	if (cmd)
	    /* one-shot command */
	    execl (cp, cp, "-c", cmd, 0);
	else
	    /* interactive shell */
	    execl (cp, cp, 0);
	perror (cp);
	_exit (1);
    }
    if (pid > 0)
	status = wait_for_process (pid);
    else
	perror ("mm: fork");

    fix_signals_for_fork (false);	/* restore SIGCHLD, tty pgrp */

    cmtend ();				/* clean up after child */
    cmtset ();				/* set up terminal again */
    cmcsb._cmwrp = autowrap_column;	/* put that back */
    return status;
}

/*
 * mm_execute:
 * fork and execvp using given arguments and wait for child to terminate.
 * based on shell() command above.
 */

mm_execute (file, argv)
char *file;
char **argv;
{
    int pid, status;

    cmtend ();
    if (cf)				/* update, since who knows what may */
	update(&cf,0);			/* happen, and we turn of sig hndlrs */

    fix_signals_for_fork (true);

    pid = vfork ();
    if (pid == 0) {
	execvp (file, argv);
	perror (file);
	_exit (1);
    }
    if (pid > 0)
	status = wait_for_process (pid);
    else
	perror ("mm: fork");

    fix_signals_for_fork (false);

    cmtend ();				/* clean up after child */
    cmtset ();				/* set up terminal again */
    cmcsb._cmwrp = autowrap_column;	/* put that back */

    return status;
}


/* fancy exit */
done(n)
int n;
{
    cmtend ();
#ifdef MDEBUG
    m_done();
#endif
#ifdef USAGEFILE
    usage_stop(USAGEFILE);
#endif
    exit(n);
}


/*
 * CMD_SUSPEND:
 * suspend MM.
 */

cmd_suspend (n) 
int n; 
{
  confirm();
  suspend(0);
}

/*
 * suspend:
 * handle the suspend -- clean up, then send ourselves the ^Z
 * when we get back (after suspend), replace everything
 */
suspend (updflags)
int updflags;				/* flags for update */
{
#ifdef SIGTSTP
    long mask = block_signals ();

    cmtend ();				/* turn off ccmd's control of tty */

    if (cf)
	update (&cf, updflags);		/* make sure file is saved */

#ifdef USAGEFILE
    usage_stop (USAGEFILE);
#endif

    (void) kill (0, SIGSTOP);		/* stop ourself */

#ifdef USAGEFILE
    usage_start();
#endif

    cmtset ();				/* fix up the terminal */
    cmcsb._cmwrp = autowrap_column;	/* cmtset resets this */

    release_signals (mask);

#else	/* !SIGTSTP */
    printf ("The SUSPEND command isn't supported on this system.\n");
#endif	/* SIGTSTP */  
}

#ifdef SIGTSTP
signalhandler
sigtstp_handler()
{
  cmnl(stdout);				/* move to new line for looks */
  suspend(0);
}
#endif

signalhandler
sighup_handler()
{
    cmtend ();				/* we're done */
    if (cf != NULL)
	update (&cf, UPD_SAVEMOD);	/* make sure file is saved */
    signal (SIGHUP, SIG_DFL);		/* default action */
    kill (PID, SIGHUP);
}


#ifdef SIGXCPU
signalhandler
sigxcpu_handler() {
  printf ("\
WARNING: This process has received an XCPU signal!\n\
This MM process has exceeded the per process CPU limit and will soon be\n\
killed.  Please save your work and exit MM.\n");
  if (cf != NULL)			/* update file just in case */
    update (&cf, UPD_SAVEMOD);
}
#endif /* SIGXCPU */

/*
 * askem:
 * ask them the question and encourage them to give us a good answer
 */
askem (tty, pr, prlen)
int tty;
char *pr;
int prlen;
{
    char ans[BUFSIZ];
    int i;
    static char hint[] = "Please answer 'y' or 'n'\n";

    while (TRUE) {
	i = 0;
	write (tty, pr, prlen);
	do {				/* get a line */
	    read (tty, &ans[i], 1);
	} while ((ans[i++] != '\n') && (i < BUFSIZ));
	if (i < BUFSIZ)
	    ans[i] = '\0';
	if (i == 2) {
	    if ((ans[0] == 'n') || (ans[0] == 'N'))
		return (FALSE);
	    if ((ans[0] == 'y') || (ans[0] == 'Y'))
		return (TRUE);
	}
	if (ustrcmp (ans, "no\n") == 0)
	    return (FALSE);
	if (ustrcmp (ans, "yes\n") == 0)
	    return (TRUE);

	/* failed, try again */
	write (tty, hint, sizeof (hint));
    }
}


/*
 * sigint_handler:
 * handle sigint (^C) by saving the file and then exiting
 * ask them if they really want to exit
 */
signalhandler
sigint_handler()
{
    int tty;
    static char ask[] = "\nDo you really want to exit MM? [y/n] ";
    static char ask2[] = "\nSave mail file before exiting? [y/n] ";
    static char warn[] = "Cannot save file, not exiting.\n\
Try suspending MM, and making space by deleting some files.\n";
    static char cont[] = "Continuing...\n";
    char ans[3];
    
    cmtend ();				/* restore sane tty settings */
    tty = open ("/dev/tty", O_RDWR, 0);	/* read and write at them */
    if (!askem(tty, ask, sizeof (ask))) {
	write (tty, cont, sizeof(cont));
	close (tty);
	cmtset ();			/* give tty back to CCMD */
	return;				/* that's it, no exit */
    }

    if ((cf != NULL) &&
	(cf->flags & (MF_DIRTY|MF_MODIFIED))) { /* save the file */
	if (askem (tty, ask2, sizeof (ask2))) {
	    if (!update (&cf, UPD_SAVEMOD|UPD_QUIET) 
		&& (cf->flags & MF_WRITERR)) {
		write (tty, warn, sizeof(warn));
		close (tty);
		cmtset ();		/* give tty back to CCMD */
		return;			/* keep going */
	    }
	}
    }
#ifdef USAGEFILE
    usage_stop(USAGEFILE);
#endif
    signal (SIGINT, SIG_DFL);
    kill (PID, SIGINT);
}


#ifdef notdef

/*
 * DEFER_SIGNALS:
 * defer handling of HUP, XCPU, INT and TSTP signals
 */
defer_signals()
{
    int sig_defer();
    int oldmask;

    TSTP = HUP = INT = XCPU = FALSE;	/* don't have any yet */

#ifndef SYSV
    oldmask = sigblock(-1);		/* critical section */
#endif /* SYSV */
#ifdef SIGTSTP
    oldtstp = signal(SIGTSTP, sig_defer);
#endif /* SIGTSTP */
    oldhup = signal(SIGHUP, sig_defer);
    oldint = signal(SIGINT, sig_defer);
#ifdef SIGXCPU
    oldxcpu = signal(SIGXCPU, sig_defer);
#endif /* SIGXCPU */
#ifndef SYSV
    sigsetmask (oldmask);		/* end critical section */
#endif /* SYSV */
}


/*
 * We'll always do the XCPU.  Then we'll do a HUP if we caught that,
 * since it precludes the need of any others.  Then we'll do INT or TSTP, 
 * whichever came first.
 */
do_signals()
{
    int oldmask;

#ifndef SYSV
    oldmask = sigblock(-1);		/* critical section */
#endif /* SYSV */
#ifdef SIGTSTP
    signal(SIGTSTP, oldtstp);
#endif /* SIGTSTP */
    signal(SIGHUP, oldhup);
    signal(SIGINT, oldint);
#ifdef SIGXCPU
    signal(SIGXCPU, oldxcpu);
#endif /* SIGXCPU */
#ifndef SYSV
    sigsetmask (oldmask);		/* end critical section */
#endif /* SYSV */

    /* then call the ones that happened */
#ifdef SIGXCPU
    if (XCPU)				/* we always want the warning */
	kill (PID, SIGXCPU);
#endif /* SIGXCPU */
    if (HUP)				/* if hup, nothing else matters */
	kill (PID, SIGHUP);
#ifdef SIGTSTP
    if (TSTP)
	kill (PID, SIGTSTP);
#endif /* SIGTSTP */
    if (INT)
	kill (PID, SIGINT);
}

/*
 * SIG_DEFER:
 * note we're assuming Vax-11 stuff, which is to say that we know which
 * signal generated a call to this handler.  Otherwise, we need four
 * separate functions.
 */

sig_defer (sig, code, scp) 
int sig,code,scp;
{
    switch (sig) {
#ifdef SIGTSTP
    case SIGTSTP:
	if (!INT)			/* don't catch ^Z after ^C */
	    TSTP = TRUE;
	break;
#endif
    case SIGHUP:
	HUP = TRUE;
	break;
    case SIGINT:
	if (!TSTP)			/* don't catch ^C after ^Z */
	    INT = TRUE;
	break;
#ifdef SIGXCPU
    case SIGXCPU:
	XCPU = TRUE;
	break;
#endif
    }
}
#endif

int
wait_for_process (pid)
int pid;
{
    return do_wait (pid, true);
}

int
collect_process (pid)
int pid;
{
    return do_wait (pid, false);
}

int
do_wait (pid, blocking)
int pid, blocking;
{
    int n;
    unsigned int status;

    do {
#ifdef HAVE_WAIT3
	union wait w;
	int flags = blocking ? WUNTRACED : (WUNTRACED|WNOHANG);

	n = wait3 (&w, flags, 0);
	if (n > 0 && w.w_stopval == WSTOPPED) {
	    (void) kill (pid, SIGCONT);
	    continue;
	}
	status = w.w_status;
#else
	n = wait (&status);
#endif
	if (n > 0)
	    forget_pid (n);
	else if (n == 0 && !blocking)
	    return 0;
	else if (n < 0 && errno != EINTR)
	    return -1;

	if (n == pid)
	    break;

    } while (blocking);

    return status;
}

#define MAXBG 10
static nrun = 0;
static unsigned bg_procs[MAXBG];

maybe_wait_for_process (pid)
int pid;
{
    int i;

    if (nrun < MAXBG) {
	for (i = 0; i < MAXBG; i++)
	    if (bg_procs[i] == 0) {
		bg_procs[i] = pid;
		nrun++;
		return;
	    }
    }
    /*
     * we've exceeded the number of allowed background processes,
     * so we'll just have to wait for this one.
     */
    wait_for_process (pid);
}

forget_pid (n)
unsigned n;
{
    int i;
    for (i = 0; i < MAXBG; i++)
	if (bg_procs[i] == n) {
	    bg_procs[i] = 0;
	    --nrun;
	}
}
