/*
 * Copyright (c) 1992-93 by Mark Boyns (boyns@sdsu.edu)
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted, provided
 * that the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation.  This software is provided "as is" without express or
 * implied warranty.
 */

#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/ttold.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include "rshell.h"

#define DEFAULT_TERM "vt100"

char	*home;
char	fatalbuf[256];
char	*local_rshellrc = NULL, *global_rshellrc = NULL;
char	*local_motd = NULL, *global_motd = NULL;
char	*local_problems = NULL, *global_problems = NULL;
int	badsig(), sigalrm();
int	ncommands = 0;
int	has_automatic = 0;
int	has_default = 0;
Command	*commands = NULL;
Command	*curr, *tail;

extern char	yytext[];
extern FILE	*yyin;

main(argc, argv)
int	argc;
char	**argv;
{
	go_home();
	check_files();
	set_signals();
	set_limits();
	check_winsize();
	check_term();

	/*
	 * print the motd files
	 */
	if (global_motd) {
		print(global_motd);
	}
	if (local_motd) {
		print(local_motd);
	}

	/*
	 * if a local rshellrc exists read it otherwise
	 * read the global one
	 */
	if (local_rshellrc) {
		read_rc(local_rshellrc);
	}
	else {
		read_rc(global_rshellrc);
	}

/*
	press_return();
*/

#ifdef LIBRARY
	sleep(2);
#else
	sleep(1);
#endif

	ui();
	done();
}

/*
 * the user interface
 */
ui()
{
	Command		*c;
	char		buf[128], *p;
	int		num, pid, error;

	if (has_automatic) {
		for(c = commands; c != NULL; c = c->next) {
			if (c->num == has_automatic) break;
		}
		if (c != NULL) {
			execute(c);
			/* not reached now? */
			if (ncommands == 1) return;
		}
	}

	for(;;) {
		printf("\nChoose a number or letter from the following menu:\n");
		for(c = commands; c != NULL; c = c->next) {
			printf("%5d)  %s\n", c->num, c->name);
		}

		printf("    Q)  Quit\n");
		printf("\n");

		if (has_default) {
			printf("Choice [%d]: ", has_default);
		}
		else {
			printf("Choice: ");
		}
		fflush(stdout);

		error = 0;
		alarm(IDLE_TIMEOUT*60);
		p = fgets(buf, sizeof(buf), stdin);
		alarm(0);
		printf("\n");

		/*
		 * check for EOF on stdin
		 */
		if (p == NULL) {
#if 0
			clearerr(stdin);
			error = 1;
#else
			break;
#endif
		}
		/*
		 * check for a default command
		 */
		else if (buf[0] == '\n' || buf[0] == '\0') {
			if (has_default) {
				for(c = commands; c != NULL; c = c->next) {
					if (c->num == has_default) break;
				}
				if (c != NULL) {
					execute(c);		
				}
			}
			else {
				error = 1;
			}
		}
		/*
		 * check for quit
		 */
		else if (buf[0] == 'q' || buf[0] == 'Q' || buf[0] == 'x' || buf[0] == 'X') {
			break;
		}
		/*
		 * otherwise check for a command number
		 */
		else {
			num = atoi(buf);
			for(c = commands; c != NULL; c = c->next) {
				if (num == c->num) break;
			}
			if (c != NULL) {
				execute(c);
			}
			else {
				error = 1;
			}
		}

		if (error) {
			printf("*** please choose a number between 1 and %d or Q followed by return ***\n", ncommands);
		}
	}
}

/*
 * wait for the user to press return
 */
press_return()
{
	int	c;

	printf("press RETURN to continue...");
	fflush(stdout);

	for(;;) {
		c = getc(stdin);
		if (c == EOF) {
			clearerr(stdin);
		}
		else if (c == '\n') {
			break;
		}
	}
}

execute(c)
Command	*c;
{
	int	pid;

	if (ncommands == 1 && has_automatic) {
		unset_signals();
		execv(c->path, c->argv);
		perror(c->path);
		exit(1);
	}
	else {
		pid = fork();
		if (pid == 0) {
			unset_signals();
			execv(c->path, c->argv);
			perror(c->path);
			exit(1);
		}
		else if (pid < 0) {
			fatal("fork failed");
		}
		else {
			waitpid(pid, NULL, 0);
		}
	}
}

/*
 * check for users local files, if they are not found
 * then use the global ones
 */
check_files()
{
	struct stat	st;

	local_rshellrc = (stat(LOCAL_RSHELLRC, &st) < 0) ? NULL : LOCAL_RSHELLRC;
	global_rshellrc = GLOBAL_RSHELLRC;
	local_motd = (stat(LOCAL_MOTD, &st) < 0) ? NULL : LOCAL_MOTD;
	global_motd = GLOBAL_MOTD;
	local_problems = (stat(LOCAL_PROBLEMS, &st) < 0) ? NULL : LOCAL_PROBLEMS;
	global_problems = GLOBAL_PROBLEMS;
}

/*
 * check the window size
 * if rows and cols equals zero set them to 24 and 80
 */
check_winsize()
{
	struct winsize	ws;

	if (ioctl(0, TIOCGWINSZ, &ws) < 0) {
		fatal("get window size failed");
	}

	if (ws.ws_row == 0 && ws.ws_col == 0) {
		ws.ws_col = 80;
		ws.ws_row = 24;
		if (ioctl(0, TIOCSWINSZ, &ws) < 0) {
			fatal("set window size failed");
		}
	}
}

default_term()
{
	static char buf[256]; /* XXX */

	sprintf(buf, "TERM=%s", DEFAULT_TERM);
	putenv(buf);
}

check_term()
{
	char	*p;
	char	bp[1024];

	p = getenv("TERM");

	/*
	 * check for an unset terminal type
	 */
	if (p == NULL || *p == '\0' || strcmp(p, "network") == 0 || strcmp(p, "unknown") == 0)
	{
		default_term();
	}
	/*
	 * check for a bogus terminal type
	 */
	else
	{
		int t = tgetent(bp, p);
		if (t == 0)
		{
			printf("\n\n*** Unknown terminal type '%s' -- using %s ***\n\n", p, DEFAULT_TERM);
			default_term();
			sleep(2);
		}
	}
}

/*
 * cd to the users home directory using HOME
 */
go_home()
{
	home = getenv("HOME");
	if (home == NULL) {
		fatal("getenv HOME failed");
	}
	if (chdir(home) < 0) {
		sprintf(fatalbuf, "chdir %s failed", home);
		fatal(fatalbuf);
	}
}

/*
 * set user resource limits
 */
set_limits()
{
	struct rlimit	rl;

	rl.rlim_cur = 0;
	rl.rlim_max = 0;

	if (setrlimit(RLIMIT_CORE, &rl) < 0) { /* no core dumps */
		fatal("setrlimit core failed");
	}
}

/*
 * setup signals to be handled/ignored
 */
set_signals()
{
	signal(SIGHUP, badsig);
	signal(SIGILL, badsig);
	signal(SIGBUS, badsig);
	signal(SIGSEGV, badsig);
	signal(SIGTERM, badsig);

	signal(SIGTSTP, SIG_IGN);
	signal(SIGINT, SIG_IGN);
	signal(SIGQUIT, SIG_IGN);

	signal(SIGALRM, sigalrm);
}

/*
 * unset signals that were ignored
 */
unset_signals()
{
	signal(SIGTSTP, SIG_DFL);
	signal(SIGINT, SIG_DFL);
	signal(SIGQUIT, SIG_DFL);
}

/*
 * read the rc file using yyparse
 */
read_rc(filename)
char	*filename;
{
	FILE		*fp;

	fp = fopen(filename, "r");
	if (fp == NULL) {
		sprintf(fatalbuf, "cannot open %s", filename);
		fatal(fatalbuf);
	}

	yyin = fp;

	if (yyparse() != 0) {
		fatal("yyparse failed");
	}
}

/*
 * dump the given file to the screen
 */
print(filename)
char	*filename;
{
	FILE	*fp;
        char	buf[BUFSIZ];

	fp = fopen(filename, "r");
	if (fp != NULL) {
		while(fgets(buf, sizeof(buf), fp)) {
			printf("%s\r", buf);
		}
		close(fp);
	}
}

/*
 * yacc semantic action to parse 2 states of the setenv command
 */
setenv()
{
	static char	*p;
	static int	state;

	switch(state) {
	case 0:
		p = (char *)malloc(strlen(yytext)+2);
		if (p == NULL) {
			fatal("out of memory");
		}
		strcpy(p, yytext);
		strcat(p, "=");
		state = 1;
		break;

	case 1:
		p = (char *)realloc(p, strlen(p)+strlen(yytext)+1);
		if (p == NULL) {
			fatal("out of memory");
		}
		strcat(p, yytext);
		if (putenv(p) != 0) {
			fatal("putenv failed");
		}
		state = 0;
		break;
	}
}

/*
 * yacc semantic action to create a command structure
 */
command_create()
{
	curr = (Command *)malloc(sizeof(Command));
	if (curr == NULL) {
		fatal("out of memory");
	}

	bzero((char *)curr, sizeof(Command));
	ncommands++;
}

/*
 * yacc semantic action to mark a command as auto
 */
command_auto()
{
	if (has_automatic) {
		fatal("only one auto command can be specified");
	}
	else {
		has_automatic = ncommands;
	}
}

/*
 * yacc semantic action to mark a command as default
 */
command_default()
{
	if (has_default) {
		fatal("only one default command can be specified");
	}
	else {
		has_default = ncommands;
	}
}

/*
 * yacc semantic action to insert the command name
 */
command_name()
{
	curr->num = ncommands;
	curr->name = (char *)malloc(strlen(yytext)+1);
	if (curr->name == NULL) {
		fatal("out of memory");
	}
	strcpy(curr->name, yytext);
}

/*
 * yacc semantic action to insert the command path
 */
command_path()
{
	curr->num = ncommands;
	curr->path = (char *)malloc(strlen(yytext)+1);
	if (curr->path == NULL) {
		fatal("out of memory");
	}
	strcpy(curr->path, yytext);
}

/*
 * yacc semantic action to insert the command arg into the argv
 */
command_arg()
{
	curr->argv[curr->argc] = (char *)malloc(strlen(yytext)+1);
	if (curr->argv[curr->argc] == NULL) {
		fatal("out of memory");
	}
	strcpy(curr->argv[curr->argc], yytext);
	curr->argc++;
}

/*
 * yacc semantic action to insert the command into the command list
 */
command_insert()
{
	curr->next = NULL;
	if (commands == NULL) 
		commands = curr;
	else
		tail->next = curr;
	tail = curr;
}

/*
 * yacc routine to remove quotes from quoted strings
 */
rmquotes()
{
	yytext[strlen(yytext)-1] = '\0';
	strcpy(yytext, yytext+1);
}

/*
 * yacc routine called for yacc errors
 */
yyerror(s)
char	*s;
{
	sprintf(fatalbuf, "%s at or before '%s'", s, yytext);
	fatal(fatalbuf);
}

/*
 * SIGALRM signal handler used to timeout after IDLE_TIMEOUT minutes
 */
sigalrm()
{
	fprintf(stderr, "\nTimeout after %d minutes of idle time\n", IDLE_TIMEOUT);
	done();
}

/*
 * signal handler used to report that the given signal was received
 */
badsig(signum)
int	signum;
{
	sprintf(fatalbuf, "received %d signal", signum);
	fatal(fatalbuf);
}

/*
 * used to exit rshell cleanly
 */
done()
{
	fflush(stdout);
	exit(0);
}

/*
 * report the given error message, print the problems file,
 * and exit
 */
fatal(s)
char	*s;
{
	fprintf(stderr, "\n\nfatal error: %s\n\n", s);
	if (local_problems) {
		print(local_problems);
	}
	if (global_problems) {
		print(global_problems);
	}
	fflush(stdout);
	exit(1);
}

