/*
 * Copyright (c) 1984 through 2008, William LeFebvre
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * 
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 * 
 *     * Neither the name of William LeFebvre nor the names of other
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

char *copyright =
    "Copyright (c) 1984 through 2008, William LeFebvre";

/*
 * Changes to other files that we can do at the same time:
 * screen.c:init_termcap: get rid of the "interactive" argument and have it
 *      pass back something meaningful (such as success/failure/error).
 */

#include "os.h"
#include <signal.h>
#include <setjmp.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>

#ifdef HAVE_SYS_UTSNAME_H
#include <sys/utsname.h>
#endif

#ifdef HAVE_GETOPT_H
#include <getopt.h>
#endif

/* definitions */
#ifndef STDIN_FILENO
#define STDIN_FILENO 0
#endif

/* determine which type of signal functions to use */
/* cant have sigaction without sigprocmask */
#if defined(HAVE_SIGACTION) && !defined(HAVE_SIGPROCMASK)
#undef HAVE_SIGACTION
#endif
/* always use sigaction when it is available */
#ifdef HAVE_SIGACTION
#undef HAVE_SIGHOLD
#else
/* use sighold/sigrelse, otherwise use old fashioned BSD signals */
#if !defined(HAVE_SIGHOLD) || !defined(HAVE_SIGRELSE)
#define BSD_SIGNALS
#endif
#endif

/* if FD_SET and friends aren't present, then fake something up */
#ifndef FD_SET
typedef int fd_set;
#define FD_ZERO(x)     (*(x) = 0)
#define FD_SET(f, x)   (*(x) = 1<<f)
#endif

/* includes specific to top */

#include "top.h"
#include "machine.h"
#include "globalstate.h"
#include "commands.h"
#include "display.h"
#include "screen.h"
#include "boolean.h"
#include "username.h"
#include "utils.h"
#include "version.h"
#ifdef ENABLE_COLOR
#include "color.h"
#endif

/* definitions */
#define BUFFERSIZE 4096
#define JMP_RESUME 1
#define JMP_RESIZE 2

/* externs for getopt: */
extern int  optind;
extern char *optarg;

/* statics */
static char stdoutbuf[BUFFERSIZE];
static jmp_buf jmp_int;

/* globals */
char *myname = "top";

void
quit(int status)

{
    screen_end();
    chdir("/tmp");
    exit(status);
    /* NOTREACHED */
}

/*
 *  signal handlers
 */

void
set_signal(int sig, RETSIGTYPE (*handler)(int))

{
#ifdef HAVE_SIGACTION
    struct sigaction action;

    action.sa_handler = handler;
    action.sa_flags = 0;
    (void) sigaction(sig, &action, NULL);
#else
    (void) signal(sig, handler);
#endif
}

void
release_signal(int sig)

{
#ifdef HAVE_SIGACTION
    sigset_t set;
    sigemptyset(&set);
    sigaddset(&set, sig);
    sigprocmask(SIG_UNBLOCK, &set, NULL);
#endif

#ifdef HAVE_SIGHOLD
    sigrelse(sig);
#endif

#ifdef BSD_SIGNALS
    (void) sigsetmask(sigblock(0) & ~(sigmask(sig)));
#endif
}

RETSIGTYPE
sig_leave(int i)	/* exit under normal conditions -- INT handler */

{
    screen_end();
    exit(EX_OK);
}

RETSIGTYPE
sig_tstop(int i)	/* SIGTSTP handler */

{
    /* move to the lower left */
    screen_end();
    fflush(stdout);

    /* default the signal handler action */
    set_signal(SIGTSTP, SIG_DFL);

    /* unblock the TSTP signal */
    release_signal(SIGTSTP);

    /* send ourselves a TSTP to stop the process */
    (void) kill(0, SIGTSTP);

    /* reset the signal handler */
    set_signal(SIGTSTP, sig_tstop);

    /* reinit screen */
    screen_reinit();

    /* jump back to a known place in the main loop */
    longjmp(jmp_int, JMP_RESUME);

    /* NOTREACHED */
}

#ifdef SIGWINCH
RETSIGTYPE
sig_winch(int i)		/* SIGWINCH handler */

{
    /* reascertain the screen dimensions */
    screen_getsize();

    /* jump back to a known place in the main loop */
    longjmp(jmp_int, JMP_RESIZE);
}
#endif

#ifdef HAVE_SIGACTION
static sigset_t signalset;
#endif

void *
hold_signals()

{
#ifdef HAVE_SIGACTION
    sigemptyset(&signalset);
    sigaddset(&signalset, SIGINT);
    sigaddset(&signalset, SIGQUIT);
    sigaddset(&signalset, SIGTSTP);
#ifdef SIGWINCH
    sigaddset(&signalset, SIGWINCH);
#endif
    sigprocmask(SIG_BLOCK, &signalset, NULL);
    return (void *)(&signalset);
#endif

#ifdef HAVE_SIGHOLD
    sighold(SIGINT);
    sighold(SIGQUIT);
    sighold(SIGTSTP);
#ifdef SIGWINCH
    sighold(SIGWINCH);
    return NULL;
#endif
#endif

#ifdef BSD_SIGNALS
    int mask;
#ifdef SIGWINCH
    mask = sigblock(sigmask(SIGINT) | sigmask(SIGQUIT) |
		    sigmask(SIGTSTP) | sigmask(SIGWINCH));
#else
    mask = sigblock(sigmask(SIGINT) | sigmask(SIGQUIT) | sigmask(SIGTSTP));
    return (void *)mask;
#endif
#endif

}

void
set_signals()

{
    (void) set_signal(SIGINT, sig_leave);
    (void) set_signal(SIGQUIT, sig_leave);
    (void) set_signal(SIGTSTP, sig_tstop);
#ifdef SIGWINCH
    (void) set_signal(SIGWINCH, sig_winch);
#endif
}

void
release_signals(void *parm)

{
#ifdef HAVE_SIGACTION
    sigprocmask(SIG_UNBLOCK, (sigset_t *)parm, NULL);
#endif

#ifdef HAVE_SIGHOLD
    sigrelse(SIGINT);
    sigrelse(SIGQUIT);
    sigrelse(SIGTSTP);
#ifdef SIGWINCH
    sigrelse(SIGWINCH);
#endif
#endif

#ifdef BSD_SIGNALS
    (void) sigsetmask((int)parm);
#endif
}

/*
 * void do_arguments(globalstate *gstate, int ac, char **av)
 *
 * Arguments processing.  gstate points to the global state,
 * ac and av are the arguments to process.  This can be called
 * multiple times with different sets of arguments.
 */

#ifdef HAVE_GETOPT_LONG
static struct option longopts[] = {
    { "color", no_argument, NULL, 'C' },
    { "debug", no_argument, NULL, 'D' },
    { "system-procs", no_argument, NULL, 'S' },
    { "idle-procs", no_argument, NULL, 'I' },
    { "tag-names", no_argument, NULL, 'T' },
    { "all", no_argument, NULL, 'a' },
    { "batch", no_argument, NULL, 'b' },
    { "full-commands", no_argument, NULL, 'c' },
    { "interactive", no_argument, NULL, 'i' },
    { "quick", no_argument, NULL, 'q' },
    { "threads", no_argument, NULL, 't' },
    { "uids", no_argument, NULL, 'u' },
    { "version", no_argument, NULL, 'v' },
    { "delay", required_argument, NULL, 's' },
    { "displays", required_argument, NULL, 'd' },
    { "user", required_argument, NULL, 'U' },
    { "sort-order", required_argument, NULL, 'o' },
    { "display-mode", required_argument, NULL, 'm' },
    { NULL, 0, NULL, 0 },
};
#endif


void
do_arguments(globalstate *gstate, int ac, char **av)

{
    int i;

    /* this appears to keep getopt happy */
    optind = 1;

#ifdef HAVE_GETOPT_LONG
    while ((i = getopt_long(ac, av, "CDSITabcinqtuvs:d:U:o:m:", longopts, NULL)) != -1)
#else
    while ((i = getopt(ac, av, "CDSITabcinqtuvs:d:U:o:m:")) != EOF)
#endif
    {
	switch(i)
	{
#ifdef ENABLE_COLOR
	case 'C':
	    gstate->use_color = !gstate->use_color;
	    break;
#endif

	case 'D':
	    debug_set(1);
	    break;

	case 'v':
	    fprintf(stderr, "%s: version %s\n", myname, version_string());
	    exit(EX_OK);
	    break;

	case 'b':
	case 'n':
	    gstate->interactive = No;
	    break;

	case 'a':
	    gstate->displays = Infinity;
	    gstate->topn = Infinity;
	    break;

	case 'i':
	    gstate->interactive = Yes;
	    break;

	case 'o':
	    gstate->order_name = optarg;
	    break;

	case 'd':
	    i = atoiwi(optarg);
	    if (i == Invalid || i == 0)
	    {
		message_error(" Bad display count");
	    }
	    else
	    {
		gstate->displays = i;
	    }
	    break;

	case 's':
	    i = atoi(optarg);
	    if (i < 0 || (i == 0 && getuid() != 0))
	    {
		message_error(" Bad seconds delay");
	    }
	    else
	    {
		gstate->delay = i;
	    }
	    break;

	case 'u':
	    gstate->show_usernames = !gstate->show_usernames;
	    break;

	case 'U':
	    i = userid(optarg);
	    if (i == -1)
	    {
		message_error(" Unknown user '%s'", optarg);
	    }
	    else
	    {
		gstate->pselect.uid = i;
	    }
	    break;

	case 'm':
	    i = atoi(optarg);
	    gstate->pselect.mode = i;
	    break;

	case 'S':
	    gstate->pselect.system = !gstate->pselect.system;
	    break;

	case 'I':
	    gstate->pselect.idle = !gstate->pselect.idle;
	    break;

#ifdef ENABLE_COLOR
	case 'T':
	    gstate->show_tags = 1;
	    break;
#endif

	case 'c':
	    gstate->pselect.fullcmd = !gstate->pselect.fullcmd;
	    break;

	case 't':
	    gstate->pselect.threads = !gstate->pselect.threads;
	    break;

	case 'q':		/* be quick about it */
	    /* only allow this if user is really root */
	    if (getuid() == 0)
	    {
		/* be very un-nice! */
		(void) nice(-20);
	    }
	    else
	    {
		message_error(" Option -q can only be used by root");
	    }
	    break;

	default:
	    fprintf(stderr, "\
Top version %s\n\
Usage: %s [-ISTabcinqu] [-d x] [-s x] [-o field] [-U username] [number]\n",
		    version_string(), myname);
	    exit(EX_USAGE);
	}
    }

    /* get count of top processes to display */
    if (optind < ac && *av[optind])
    {
	if ((i = atoiwi(av[optind])) == Invalid)
	{
	    message_error(" Process count not a number");
	}
	else
	{
	    gstate->topn = i;
	}
    }
}

void
do_display(globalstate *gstate)

{
    int active_procs;
    int i;
    time_t curr_time;
    caddr_t processes;
    struct system_info system_info;
    char *hdr;

    /* get the time */
    time_mark(&(gstate->now));
    curr_time = (time_t)(gstate->now.tv_sec);

    /* get the current stats */
    get_system_info(&system_info);

    /* get the current processes */
    processes = get_process_info(&system_info, &(gstate->pselect), gstate->order_index);

    /* determine number of processes to actually display */
    if (gstate->topn > 0)
    {
	/* this number will be the smallest of:  active processes,
	   number user requested, number current screen accomodates */
	active_procs = system_info.P_ACTIVE;
	if (active_procs > gstate->topn)
	{
	    active_procs = gstate->topn;
	}
	if (active_procs > gstate->max_topn)
	{
	    active_procs = gstate->max_topn;
	}
    }
    else
    {
	/* dont show any */
	active_procs = 0;
    }

#ifdef HAVE_FORMAT_PROCESS_HEADER
    /* get the process header to use */
    hdr = format_process_header(&(gstate->pselect), processes, active_procs);
#else
    hdr = gstate->header_text;
#endif

    /* full screen or update? */
    if (gstate->fulldraw)
    {
	display_clear();
	i_loadave(system_info.last_pid, system_info.load_avg);
	i_uptime(&(gstate->statics->boottime), &curr_time);
	i_timeofday(&curr_time);
	i_procstates(system_info.p_total, system_info.procstates, gstate->pselect.threads);
	if (gstate->show_cpustates)
	{
	    i_cpustates(system_info.cpustates);
	}
	else
	{
	    if (smart_terminal)
	    {
		z_cpustates();
	    }
	    gstate->show_cpustates = Yes;
	}
	i_kernel(system_info.kernel);
	i_memory(system_info.memory);
	i_swap(system_info.swap);
	i_message(&(gstate->now));
	i_header(hdr);
	for (i = 0; i < active_procs; i++)
	{
	    i_process(i, format_next_process(processes, gstate->get_userid));
	}
	i_endscreen();
	if (gstate->smart_terminal)
	{
	    gstate->fulldraw = No;
	}
    }
    else
    {
	u_loadave(system_info.last_pid, system_info.load_avg);
	u_uptime(&(gstate->statics->boottime), &curr_time);
	i_timeofday(&curr_time);
	u_procstates(system_info.p_total, system_info.procstates, gstate->pselect.threads);
	u_cpustates(system_info.cpustates);
	u_kernel(system_info.kernel);
	u_memory(system_info.memory);
	u_swap(system_info.swap);
	u_message(&(gstate->now));
	u_header(hdr);
	for (i = 0; i < active_procs; i++)
	{
	    u_process(i, format_next_process(processes, gstate->get_userid));
	}
	u_endscreen();
    }
}

#ifdef DEBUG
void
timeval_xdprint(char *s, struct timeval tv)

{
    xdprintf("%s %d.%06d\n", s, tv.tv_sec, tv.tv_usec);
}
#endif

void
do_wait(globalstate *gstate)

{
    struct timeval wait;

    wait.tv_sec = gstate->delay;
    wait.tv_usec = 0;
    select(0, NULL, NULL, NULL, &wait);
}

void
do_command(globalstate *gstate)

{
    int status;
    struct timeval wait = {0, 0};
    struct timeval now;
    fd_set readfds;
    unsigned char ch;

    /* calculate new refresh time */
    gstate->refresh = gstate->now;
    gstate->refresh.tv_sec += gstate->delay;
    time_get(&now);

    /* loop waiting for time to expire */
    do {
	/* calculate time to wait */
	if (gstate->delay > 0)
	{
	    wait = gstate->refresh;
	    wait.tv_usec -= now.tv_usec;
	    if (wait.tv_usec < 0)
	    {
		wait.tv_usec += 1000000;
		wait.tv_sec--;
	    }
	    wait.tv_sec -= now.tv_sec;
	}

	/* set up arguments for select on stdin (0) */
	FD_ZERO(&readfds);
	FD_SET(STDIN_FILENO, &readfds);

	/* wait for something to read or time out */
	if (select(32, &readfds, NULL, NULL, &wait) > 0)
	{
	    /* read it */
	    if (read(STDIN_FILENO, &ch, 1) != 1)
	    {
		/* read error */
		message_error(" Read error on stdin");
		quit(EX_DATAERR);
		/*NOTREACHED*/
	    }

	    /* mark pending messages as old */
	    message_mark();

	    /* dispatch */
	    status = command_process(gstate, (int)ch);
	    switch(status)
	    {
	    case CMD_ERROR:
		quit(EX_SOFTWARE);
		/*NOTREACHED*/
		
	    case CMD_REFRESH:
		return;

	    case CMD_UNKNOWN:
		message_error(" Unknown command");
		break;

	    case CMD_NA:
		message_error(" Command not available");
	    }
	}

	/* get new time */
	time_get(&now);
    } while (timercmp(&now, &(gstate->refresh), < ));
}

void
do_minidisplay(globalstate *gstate)

{
    int real_delay;
    struct system_info si;

    /* save the real delay and substitute 1 second */
    real_delay = gstate->delay;
    gstate->delay = 1;

    /* wait 1 second for a command */
    time_mark(&(gstate->now));
    do_command(gstate);

    /* do a mini update that only updates the cpustates */
    get_system_info(&si);
    u_cpustates(si.cpustates);

    /* restore the delay time */
    gstate->delay = real_delay;

    /* done */
    i_endscreen();
}

int
main(int argc, char *argv[])

{
    char *env_top;
    char **preset_argv;
    int preset_argc = 0;
    void *mask;
    int need_mini = 1;

    struct statics statics;
    globalstate *gstate;

    /* get our name */
    if (argc > 0)
    {
	if ((myname = strrchr(argv[0], '/')) == 0)
	{
	    myname = argv[0];
	}
	else
	{
	    myname++;
	}
    }

    /* binary compatibility check */
#ifdef HAVE_UNAME
    {
	struct utsname uts;

	if (uname(&uts) == 0)
	{
	    if (strcmp(uts.machine, UNAME_HARDWARE) != 0)
	    {
		fprintf(stderr, "%s: incompatible hardware platform\n",
			myname);
		exit(EX_UNAVAILABLE);
	    }
	}
    }
#endif

    /* initialization */
    gstate = (globalstate *)calloc(1, sizeof(globalstate));
    gstate->statics = &statics;
    time_mark(NULL);

    /* preset defaults for various options */
    gstate->show_usernames = Yes;
    gstate->topn = DEFAULT_TOPN;
    gstate->delay = DEFAULT_DELAY;
    gstate->fulldraw = Yes;
    gstate->use_color = Yes;
    gstate->interactive = Maybe;

    /* preset defaults for process selection */
    gstate->pselect.idle = Yes;
    gstate->pselect.system = No;
    gstate->pselect.fullcmd = No;
    gstate->pselect.command = NULL;
    gstate->pselect.uid = -1;
    gstate->pselect.mode = 0;

    /* use a large buffer for stdout */
#ifdef HAVE_SETVBUF
    setvbuf(stdout, stdoutbuf, _IOFBF, BUFFERSIZE);
#else
#ifdef HAVE_SETBUFFER
    setbuffer(stdout, stdoutbuf, BUFFERSIZE);
#endif
#endif

    /* get preset options from the environment */
    if ((env_top = getenv("TOP")) != NULL)
    {
	preset_argv = argparse(env_top, &preset_argc);
	preset_argv[0] = myname;
	do_arguments(gstate, preset_argc, preset_argv);
    }

    /* process arguments */
    do_arguments(gstate, argc, argv);

#ifdef ENABLE_COLOR
    /* If colour has been turned on read in the settings. */
    env_top = getenv("TOPCOLOURS");
    if (!env_top)
    {
	env_top = getenv("TOPCOLORS");
    }
    /* must do something about error messages */
    color_env_parse(env_top);
    color_activate(gstate->use_color);
#endif

    /* in order to support forward compatability, we have to ensure that
       the entire statics structure is set to a known value before we call
       machine_init.  This way fields that a module does not know about
       will retain their default values */
    memzero((void *)&statics, sizeof(statics));
    statics.boottime = -1;

    /* call the platform-specific init */
    if (machine_init(&statics) == -1)
    {
	exit(EX_SOFTWARE);
    }

    /* create a helper list of sort order names */
    gstate->order_namelist = string_list(statics.order_names);

    /* look up chosen sorting order */
    if (gstate->order_name != NULL)
    {
	int i;

	if (statics.order_names == NULL)
	{
	    message_error(" This platform does not support arbitrary ordering");
	}
	else if ((i = string_index(gstate->order_name,
				   statics.order_names)) == -1)
	{
	    message_error(" Sort order `%s' not recognized", gstate->order_name);
	    message_error(" Recognized sort orders: %s", gstate->order_namelist);
	}
	else
	{
	    gstate->order_index = i;
	}
    }

    /* initialize extensions */
    init_username();

    /* initialize termcap */
    gstate->smart_terminal = screen_readtermcap(gstate->interactive);

    /* determine interactive state */
    if (gstate->interactive == Maybe)
    {
	gstate->interactive = smart_terminal;
    }

    /* if displays were not specified, choose an appropriate default */
    if (gstate->displays == 0)
    {
	gstate->displays = gstate->smart_terminal ? Infinity: 1;
    }

    /* we don't need a mini display when delay is less than 2
       seconds or when we are not on a smart terminal */
    if (gstate->delay <= 1 || !smart_terminal)
    {
	need_mini = 0;
    }

#ifndef HAVE_FORMAT_PROCESS_HEADER
    /* set constants for username/uid display */
    if (gstate->show_usernames)
    {
	gstate->header_text = format_header("USERNAME");
	gstate->get_userid = username;
    }
    else
    {
	gstate->header_text = format_header("   UID  ");
	gstate->get_userid = itoa7;
    }
#endif
    gstate->pselect.usernames = gstate->show_usernames;

    /* initialize display */
    if ((gstate->max_topn = display_init(&statics)) == -1)
    {
	fprintf(stderr, "%s: can't allocate sufficient memory\n", myname);
	exit(EX_OSERR);
    }

    /* check for infinity and for overflowed screen */
    if (gstate->topn == Infinity)
    {
	gstate->topn = INT_MAX;
    }
    else if (gstate->topn > gstate->max_topn)
    {
	message_error(" This terminal can only display %d processes",
		      gstate->max_topn);
    }

#ifdef ENABLE_COLOR
    /* producing a list of color tags is easy */
    if (gstate->show_tags)
    {
	color_dump(stdout);
	exit(EX_OK);
    }
#endif

    /* hold all signals while we initialize the screen */
    mask = hold_signals();
    screen_init();

    /* set the signal handlers */
    set_signals();

    /* longjmp re-entry point */
    /* set the jump buffer for long jumps out of signal handlers */
    if (setjmp(jmp_int) != 0)
    {
	/* this is where we end up after processing sigwinch or sigtstp */

	/* tell display to resize its buffers, and get the new length */
	if ((gstate->max_topn = display_resize()) == -1)
	{
	    /* thats bad */
	    quit(EX_OSERR);
	    /*NOTREACHED*/
	}

	/* set up for a full redraw, and get the current line count */
	gstate->fulldraw = Yes;

	/* safe to release the signals now */
	release_signals(mask);
    }
    else
    {
	/* release the signals */
	release_signals(mask);

	/* some systems require a warmup */
	/* always do a warmup for batch mode */
	if (gstate->interactive == 0 || statics.flags.warmup)
	{
	    struct system_info system_info;
	    struct timeval timeout;

	    time_mark(&(gstate->now));
	    get_system_info(&system_info);
	    (void)get_process_info(&system_info, &gstate->pselect, 0);
	    timeout.tv_sec = 1;
	    timeout.tv_usec = 0;
	    select(0, NULL, NULL, NULL, &timeout);

	    /* if we've warmed up, then we can show good states too */
	    gstate->show_cpustates = Yes;
	    need_mini = 0;
	}
    }

    /* main loop */
    while ((gstate->displays == -1) || (--gstate->displays > 0))
    {
	do_display(gstate);
	if (gstate->interactive)
	{
	    if (need_mini)
	    {
		do_minidisplay(gstate);
		need_mini = 0;
	    }
	    do_command(gstate);
	}
	else
	{
	    do_wait(gstate);
	}
    }

    /* do one last display */
    do_display(gstate);

    quit(EX_OK);
    /* NOTREACHED */
    return 1; /* Keep compiler quiet. */
}
