/*
 * Copyright (c) 1980 Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted provided
 * that: (1) source distributions retain this entire copyright notice and
 * comment, and (2) distributions including binaries display the following
 * acknowledgement:  ``This product includes software developed by the
 * University of California, Berkeley and its contributors'' in the
 * documentation or other materials provided with the distribution and in
 * all advertising materials mentioning features or use of this software.
 * Neither the name of the University nor the names of its contributors may
 * be used to endorse or promote products derived from this software without
 * specific prior written permission.
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

#ifndef lint
char copyright[] =
"@(#) Copyright (c) 1980 Regents of the University of California.\n\
 All rights reserved.\n";
#endif /* not lint */

#ifndef lint
/*static char sccsid[] = "@(#)sysline.c	5.16 (Berkeley) 6/24/90";*/
#endif /* not lint */

/*
 *
 * Linux port:
 * v 0.0	May 11th 1994	bjdouma@xs4all.hacktic.nl
 * v 0.2	May 19th 1994	bjdouma@xs4all.hacktic.nl
 * v 0.3	May 26th 1994	bjdouma@xs4all.hacktic.nl
 * v 0.4	Jun  4th 1994	bjdouma@xs4all.hacktic.nl
 * v 0.5	Jun 16th 1994	bjdouma@xs4all.hacktic.nl
 * v 0.6	Aug 30th 1994	bjdouma@xs4all.hacktic.nl
 *
 */

/*
 * sysline - system status display on 25th line of terminal
 * j.k.foderaro
 *
 * Prints a variety of information on the special status line of terminals
 * that have a status display capability.  Cursor motions, status commands,
 * etc. are gleaned from /etc/termcap, or an appropriate terminfo file.
 * By default, ALL information is printed, and flags are given on the command
 * line to disable the printing of information.  The information and
 * disabling flags are:
 *
 *  flag	what
 *  -----	----
 *		time of day
 *		load average and change in load average in the last 5 mins
 *		number of user logged on
 *   -p		# of processes the users owns which are runnable and the
 *		  number which are suspended.  Processes whose parent is 1
 *		  are not counted.
 *   -l		users who've logged on and off.
 *   -m		summarize new mail which has arrived
 *
 *  <other flags>
 *   -r		use non reverse video
 *   -c		turn off 25th line for 5 seconds before redisplaying.
 *   -b		beep once one the half hour, twice on the hour
 *   +N		refresh display every N seconds.
 *   -i		print pid first thing
 *   -e		do simple print designed for an emacs buffer line
 *   -w		do the right things for a window
 *   -h		print hostname between time and load average
 *   -D		print day/date before time of day
 *   -d		debug mode - print status line data in human readable format
 *   -q		quiet mode - don't output diagnostic messages
 *   -s		print Short (left-justified) line if escapes not allowed
 *   -j		Print left Justified line regardless
 */

#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <stdarg.h>
#include <sys/ioctl.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/signal.h>
#include <utmp.h>
#include <ctype.h>
#include <termio.h>
#include <sys/dir.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "config.h"

#define WHO			/* turn this on always */
#define HOSTNAME		/* 4.1a or greater, with hostname() */

#include <sys/time.h>
#include <sys/stat.h>

#ifdef TERMINFO
	#include <curses.h>
	#include <term.h>
#else
	#include <termcap.h>
	#if defined( NCURSES )	
		#include <unctrl.h>
	#else
		#include <curses.h>
	#endif
#endif

#ifdef RWHO
	#include "rwhod.h"
	#define	DOWN_THRESHOLD	(11 * 60)
	struct remotehost 
	{
		char *rh_host;
		int rh_file;
	} 
	 remotehost[10];
	int nremotes = 0;
#endif

#include "pathnames.h"
double _avenrun[3];		/* used for storing load averages */

/*
 * In order to determine how many people are logged on and who has
 * logged in or out, we read in the /etc/utmp file. We also keep track of
 * the previous utmp file.
 */
int ut = -1;			/* the file descriptor */
struct utmp *new, *old;	
char *ttystatus;		/* per tty status bits, see below */
int nentries;			/* number of utmp entries */

	/* string lengths for printing */
#define LINE_SIZE	UT_LINESIZE
#define NAME_SIZE	UT_NAMESIZE

/*
 * Status codes to say what has happened to a particular entry in utmp.
 * NOCH means no change, ON means new person logged on,
 * OFF means person logged off.
 */
#define NOCH	0
#define ON	0x1
#define OFF	0x2

#ifdef WHO
	char whofilename[100];
	char whofilename2[100];
#endif

#ifdef HOSTNAME
	char hostname[MAXHOSTNAMELEN+1];	/* one more for null termination */
#endif

char lockfilename[100];		/* if exists, will prevent us from running */

	/* flags which determine which info is printed */
int mailcheck = 1;	/* m - do biff like checking of mail */
int proccheck = 1;	/* p - give information on processes */
int logcheck = 1; 	/* l - tell who logs in and out */
int hostprint = 0;	/* h - print out hostname */
int dateprint = 0;	/* h - print out day/date */
int quiet = 0;		/* q - hush diagnostic messages */

	/* flags which determine how things are printed */
int clr_bet_ref = 0;	/* c - clear line between refeshes */
int reverse = 1;	/* r - use reverse video */
int shortline = 0;	/* s - short (left-justified) if escapes not allowed */
int leftline = 0;	/* j - left-justified even if escapes allowed */

	/* flags which have terminal do random things	*/
int do_beep = 0;	/* b - beep every half hour and twice every hour */
int printid = 0;	/* i - print pid of this process at startup */
int synch = 1;		/* synchronize with clock */

	/* select output device (status display or straight output) */
int emacs = 0;		/* e - assume status display */
int window = 0;		/* w - window mode */
int dbug = 0;		/* d - debug */

int revtime = 1;

	/* used by mail checker */
off_t mailsize = 0;
off_t linebeg = 0;		/* place where we last left off reading */

	/* things used by the string routines */
int chars;			/* number of printable characters */
char *sp;
char strarr[512];		/* big enough now? */
	/* flags to stringdump() */
char sawmail;			/* remember mail was seen to print bells */
char mustclear;			/* status line messed up */

	/* strings which control status line display */
#ifdef TERMINFO
	char	*bel;
	char	*rev_out;
	char	*rev_end;
	char	*tparm();
#else
	char	bel[64];
	char	rev_out[20];
	char	rev_end[20];
	char	clr_eol[64];
	int	eslok;		/* escapes on status line okay (reverse, cursor addressing) */
	#define tparm(cap,parm)	tgoto((cap),0,(parm))
	char	*tgoto();
#endif

char	*arrows;
char	tsl[64];
char	fsl[64];
char	dsl[64];
int	cols;
int	hasws = 0;	/* is "ws" explicitly defined? */

	/* to deal with window size changes */
#ifdef SIGWINCH
	void sigwinch();
	char winchanged;	/* window size has changed since last update */
#endif

	/* random globals */
char *username;
char *ourtty;			/* keep track of what tty we're on */
struct stat stbuf, mstbuf;	/* mstbuf for mail check only */
unsigned delay = DEFDELAY;
uid_t uid;
double cur_loadavg = 0.0;		/* current load average */
int users = 0;
int procrun, procstop;

void initterm();
void getwinsize();
int isloggedin();
void prtinfo();
void stringinit();
void stringspace();
void stringcat();
void stringdump();
void stringprt(const char *format, ...);
int readline();
void whocheck();
int mailseen();
void timeprint();
void ttyprint();
void touch();
char *getenv();
char *ttyname();
char *strcpy1();
char *sysrup();
char *calloc();
char *malloc();
#ifdef TERMINFO
	int outc(char);
	int erroutc(char);
#else
	int outc();
	int erroutc();
#endif
int readutmp();
void readproctab( uid_t uid );
void setsignal( int sig, void (*func)(int) );
extern int getloadavg();

void main(int argc,register char **argv)
{
	void clearbotl();
	register char *cp;
	char *home;
	extern char *index();
	char *tty;

#ifdef HOSTNAME
	gethostname(hostname, sizeof hostname - 1);
	if ((cp = index(hostname, '.')) != NULL)
		*cp = '\0';
#endif

	for (argv++; *argv != 0; argv++)
		switch (**argv) {
		case '-':
			for (cp = *argv + 1; *cp; cp++) {
				switch(*cp) {
				case 'r' :	/* turn off reverse video */
					reverse = 0;
					break;
				case 'c':
					clr_bet_ref = 1;
					break;
				case 'h':
					hostprint = 1;
					break;
				case 'D':
					dateprint = 1;
					break;
#ifdef RWHO
				case 'H':
					if (argv[1] == 0)
						break;
					argv++;
					/* we exclude ourselves */
					if (strcmp(hostname, *argv) &&
					    strcmp(&hostname[sizeof NETPREFIX - 1], *argv))
						remotehost[nremotes++].rh_host = *argv;
					break;
#endif RWHO
				case 'm':
					mailcheck = 0;
					break;
				case 'p':
					proccheck = 0;
					break;
				case 'l':
					logcheck = 0;
					break;
				case 'b':
					do_beep = 1;
					break;
				case 'i':
					printid = 1;
					break;
				case 'w':
					window = 1;
					break;
				case 'e':
					emacs = 1;
					break;
				case 'd':
					dbug = 1;
					break;
				case 'q':
					quiet = 1;
					break;
				case 's':
					shortline = 1;
					break;
				case 'j':
					leftline = 1;
					break;
				default:
					fprintf(stderr,
						MYNAME": bad flag: %c\n", *cp);
				}
			}
			break;
		case '+':
			delay = atoi(*argv + 1);
			if (delay < 10)
				delay = 10;
			else if (delay > 500)
				delay = 500;
			synch = 0;	/* no more sync */
			break;
		default:
			fprintf(stderr, MYNAME": illegal argument %s\n",
				argv[0]);
		}
	if (emacs) {
		reverse = 0;
		cols = 79;
	} else		/* if not to emacs window, initialize terminal dependent info */
		initterm();

#ifdef SIGWINCH
	/*
	 * When the window size changes and we are the foreground
	 * process (true if -w), we get this signal.
	 */
	setsignal(SIGWINCH, sigwinch);
#endif
	getwinsize();		/* get window size from ioctl */

	/* immediately fork and let the parent die if not emacs mode */
	if (!emacs && !window && !dbug) {
		if (fork())
			exit(0);
		/* pgrp should take care of things, but ignore them anyway */
		setsignal(SIGINT, SIG_IGN);
		setsignal(SIGQUIT, SIG_IGN);
		setsignal(SIGTTOU, SIG_IGN);
	}
	/*
	 * When we logoff, init will do a "vhangup()" on this
	 * tty which turns off I/O access and sends a SIGHUP
	 * signal.  We catch this and thereby clear the status
	 * display.  Note that a bug in 4.1bsd caused the SIGHUP
	 * signal to be sent to the wrong process, so you had to
	 * `kill -HUP' yourself in your .logout file.
	 * Do the same thing for SIGTERM, which is the default kill
	 * signal.
	 */
	setsignal(SIGHUP,  clearbotl);
	setsignal(SIGTERM, clearbotl);
	/*
	 * This is so kill -ALRM to force update won't screw us up..
	 */
	setsignal(SIGALRM, SIG_IGN);

	uid = getuid();
	ourtty = ttyname(2);	/* remember what tty we are on */
	if (printid) {
		printf("%d\n", getpid());
		fflush(stdout);
	}
	dup2(2, 1);

	if ((home = getenv("HOME")) == 0)
		home = "";
#ifdef TTY_SUFFIX
	#define DOT "."
	tty = strrchr( ourtty, '/' );
	if (tty)
		tty++;
#else
	#define DOT ""
	tty = "";
#endif
	strcpy1(strcpy1(strcpy1(whofilename,  home), "/.who"DOT), tty );
	strcpy1(strcpy1(strcpy1(whofilename2, home), "/.sysline"DOT), tty );
	strcpy1(strcpy1(strcpy1(lockfilename, home), "/.syslinelock"DOT ), tty );

	if (mailcheck)
		if ((username = getenv("USER")) == 0)
			mailcheck = 0;
		else {
			chdir(_PATH_MAILDIR);
			if (stat(username, &mstbuf) >= 0)
				mailsize = mstbuf.st_size;
			else
				mailsize = 0;
		}

	while (emacs || window || isloggedin())
		if (access(lockfilename, 0) >= 0)
			sleep(60);
		else {
			prtinfo();
			sleep(delay);
			if (clr_bet_ref) {
				tputs(dsl, 1, outc);
				fflush(stdout);
				sleep(5);
			}
			revtime = (1 + revtime) % REVOFF;
		}
	clearbotl();
	/*NOTREACHED*/
}

int isloggedin()
{
	/*
	 * you can tell if a person has logged out if the owner of
	 * the tty has changed
	 */
	struct stat statbuf;

	return fstat(2, &statbuf) == 0 && statbuf.st_uid == uid;
}

int readutmp(nflag)
	char nflag;
{
	static time_t lastmod;		/* initially zero */
	static off_t utmpsize;		/* ditto */
	struct stat st;

	if (ut < 0 && (ut = open(_PATH_UTMP, 0)) < 0) {
		fprintf(stderr, MYNAME": can't open %s\n", _PATH_UTMP);
		exit(1);
	}
	if (fstat(ut, &st) < 0 || st.st_mtime == lastmod)
		return 0;
	lastmod = st.st_mtime;
	if (utmpsize != st.st_size) {
		utmpsize = st.st_size;
		nentries = utmpsize / sizeof (struct utmp);
		if (old == 0) {
			old = (struct utmp *)calloc(utmpsize, 1);
			new = (struct utmp *)calloc(utmpsize, 1);
		} else {
			old = (struct utmp *)realloc((char *)old, utmpsize);
			new = (struct utmp *)realloc((char *)new, utmpsize);
			free(ttystatus);
		}
		ttystatus = malloc(nentries * sizeof *ttystatus);
		if (old == 0 || new == 0 || ttystatus == 0) {
			fprintf(stderr, MYNAME": out of memory\n");
			exit(1);
		}
	}
	lseek(ut, 0L, 0);
	(void) read(ut, (char *) (nflag ? new : old), utmpsize);
	return 1;
}

void prtinfo()
{
	int on, off;
	register i;
	char fullprocess;

	stringinit();
#ifdef SIGWINCH
	if (winchanged) {
		winchanged = 0;
		getwinsize();
		mustclear = 1;
	}
#endif
#ifdef WHO
	/* 
	 * check for file named .who in the home directory, or, 
	 * if it does not exist, .sysline 
	 */
	whocheck();
#endif
	timeprint();
	/*
	 * if mail is seen, don't print rest of info, just the mail
	 * reverse new and old so that next time we run, we won't lose log
	 * in and out information
	 */
	if (mailcheck && (sawmail = mailseen()))
		goto bottom;
#ifdef HOSTNAME
#ifdef RWHO
	for (i = 0; i < nremotes; i++) {
		char *tmp;

		stringspace();
		tmp = sysrup(remotehost + i);
		stringcat(tmp, strlen(tmp));
	}
#endif
	/*
	 * print hostname info if requested
	 */
	if (hostprint) {
		stringspace();
		stringcat(hostname, -1);
	}
#endif
	/*
	 * print load average and difference between current load average
	 * and the load average 5 minutes ago
	 */
	 
	if (getloadavg(_avenrun, 3) > 0) {
		double diff;

		stringspace();

		if ((diff = _avenrun[0] - _avenrun[1]) < 0.0)
			stringprt("%.1f %.1f", _avenrun[0],  diff);
		else
			stringprt("%.1f +%.1f", _avenrun[0], diff);
		cur_loadavg = _avenrun[0];
	}
	
	/*
	 * print log on and off information
	 */
	stringspace();
	fullprocess = 1;
#ifdef MAXLOAD
	if (cur_loadavg > MAXLOAD)
		fullprocess = 0;	/* too loaded to run */
#endif
	/*
	 * Read utmp file (logged in data) only if we are doing a full
	 * process, or if this is the first time and we are calculating
	 * the number of users.
	 */
	on = off = 0;
	if (users == 0) {		/* first time */
		if (readutmp(0))
			for (i = 0; i < nentries; i++)
				if (old[i].ut_name[0])
					if (old[i].ut_type==USER_PROCESS)
						users++;
	} else if (fullprocess && readutmp(1)) {
		struct utmp *tmp;

		users = 0;

		for (i = 0; i < nentries; i++) 
		{
			if (old[i].ut_name[0] == '\0') 
			{
				if (new[i].ut_type==USER_PROCESS)
				{
					ttystatus[i] = ON;
					on++;
				}
				else 
					ttystatus[i] = NOCH;
			} 
			else 
			if (new[i].ut_name[0] == '\0') 
			{
				if (old[i].ut_type==USER_PROCESS)
				{
					ttystatus[i] = OFF;
					off++;
				}
				else 
					ttystatus[i] = NOCH;
			}
			else
			if (strncmp(old[i].ut_name, new[i].ut_name, NAME_SIZE) == 0)
			{
				if (old[i].ut_type==new[i].ut_type)
					ttystatus[i] = NOCH;
				else
				{
					if (old[i].ut_type==USER_PROCESS)
					{
						ttystatus[i] = OFF;
						off++;
					}
					else
					if (new[i].ut_type==USER_PROCESS)
					{
						ttystatus[i] = ON;
						on++;
					}
					else 
						ttystatus[i] = NOCH;
				}
			} 
			else 
			{
				ttystatus[i] = ON | OFF;
				on++;
				off++;
			}
			if (new[i].ut_name[0])
				if (new[i].ut_type==USER_PROCESS)
					users++;
		}
		tmp = new;
		new = old;
		old = tmp;
	}
	/*
	 * Print:
	 * 	1.  number of users
	 *	2.  a * for unread mail
	 *	3.  a - if load is too high
	 *	4.  number of processes running and stopped
	 */
	stringprt("%du", users);
	if (mailsize > 0 && mstbuf.st_mtime >= mstbuf.st_atime)
		stringcat("*", -1);
	if (!fullprocess && (proccheck || logcheck))
		stringcat("-", -1);

	if (fullprocess && proccheck) {
		readproctab( uid );
		if (procrun > 0 || procstop > 0) {
			stringspace();
			if (procrun > 0 && procstop > 0)
				stringprt("%dr %ds", procrun, procstop);
			else if (procrun > 0)
				stringprt("%dr", procrun);
			else
				stringprt("%ds", procstop);
		}
	}
	/*
	 * If anyone has logged on or off, and we are interested in it,
	 * print it out.
	 */
	if (logcheck) {
		/* old and new have already been swapped */
		if (on) {
			stringspace();
			stringcat("on:", -1);
			for (i = 0; i < nentries; i++)
				if (ttystatus[i] & ON) {
					stringprt(" %.8s", old[i].ut_name);
					ttyprint(old[i].ut_line);
				}
		}
		if (off) {
			stringspace();
			stringcat("off:", -1);
			for (i = 0; i < nentries; i++)
				if (ttystatus[i] & OFF) {
					stringprt(" %.8s", new[i].ut_name);
					ttyprint(new[i].ut_line);
				}
		}
	}
bottom:
		/* dump out what we know */
	stringdump();
}

void timeprint()
{
	long curtime;
	struct tm *tp, *localtime();
	static int beepable = 1;

		/* always print time */
	time(&curtime);
	tp = localtime(&curtime);
	if (dateprint)
		stringprt("%.11s", ctime(&curtime));
#if defined( CLOCK24 )
	stringprt("%02d:%02d", tp->tm_hour, tp->tm_min);
#else	
	stringprt("%02d:%02d", tp->tm_hour > 12 ? tp->tm_hour - 12 :
		(tp->tm_hour == 0 ? 12 : tp->tm_hour), tp->tm_min);
#endif
	if (synch)			/* sync with clock */
		delay = 60 - tp->tm_sec;
	/*
	 * Beepable is used to insure that we get at most one set of beeps
	 * every half hour.
	 */
	if (do_beep)
		if (beepable) {
			if (tp->tm_min == 30) {
				tputs(bel, 1, outc);
				fflush(stdout);
				beepable = 0;
			} else if (tp->tm_min == 0) {
				tputs(bel, 1, outc);
				fflush(stdout);
				sleep(2);
				tputs(bel, 1, outc);
				fflush(stdout);
				beepable = 0;
			}
		} else
			if (tp->tm_min != 0 && tp->tm_min != 30)
				beepable = 1;
}

/*
 * whocheck -- check for file named .who or .sysline and print it on the who line first
 */
void whocheck()
{
	int chss;
	register char *p;
	char buff[81];
	int whofile;

	if ((whofile = open(whofilename, 0)) < 0 &&
	    (whofile = open(whofilename2, 0)) < 0)
		return;
	chss = read(whofile, buff, sizeof buff - 1);
	close(whofile);
	if (chss <= 0)
		return;
	buff[chss] = '\0';
	/*
	 * Remove all line feeds, and replace by spaces if they are within
	 * the message, else replace them by nulls.
	 */
	for (p = buff; *p;)
		if (*p == '\n')
			if (p[1])
				*p++ = ' ';
			else
				*p = '\0';
		else
			p++;
	stringcat(buff, p - buff);
	stringspace();
}

/*
 * ttyprint -- given the name of a tty, print in the string buffer its
 * short name surrounded by parenthesis.
 * ttyxx is printed as (xx)
 * console is printed as (cty)
 */
void ttyprint(name)
	char *name;
{
	if (strncmp(name, "tty", 3) == 0)
		stringprt("(%.*s)", LINE_SIZE - 3, name + 3);
	else if (strcmp(name, "console") == 0)
		stringcat("(cty)", -1);
	else
		stringprt("(%.*s)", LINE_SIZE, name);
}

/*
 * mail checking function
 * returns 0 if no mail seen
 */
int mailseen()
{
	FILE *mfd;
	register n;
	register char *cp;
	char lbuf[100], sendbuf[100], *bufend;
	char seenspace;
	int retval = 0;

	if (stat(username, &mstbuf) < 0) {
		mailsize = 0;
		return 0;
	}
	if (mstbuf.st_size <= mailsize || (mfd = fopen(username,"r")) == NULL) {
		mailsize = mstbuf.st_size;
		return 0;
	}
	fseek(mfd, mailsize, 0);
	while ((n = readline(mfd, lbuf, sizeof lbuf)) >= 0 &&
	       strncmp(lbuf, "From ", 5) != 0)
		;
	if (n < 0) {
		stringspace();
		stringcat("Mail has just arrived", -1);
		goto out;
	}
	retval = 1;
	/*
	 * Found a From line, get second word, which is the sender,
	 * and print it.
	 */
	for (cp = lbuf + 5; *cp && *cp != ' '; cp++)	/* skip to blank */
		;
	*cp = '\0';					/* terminate name */
	stringspace();
	stringprt("Mail from %s ", lbuf + 5);
	/*
	 * Print subject, and skip over header.
	 */
	while ((n = readline(mfd, lbuf, sizeof lbuf)) > 0)
		if (strncmp(lbuf, "Subject:", 8) == 0)
			stringprt("on %s ", lbuf + 9);
	if (!emacs)
		stringcat(arrows, 2);
	else
		stringcat(": ", 2);
	if (n < 0)					/* already at eof */
		goto out;
	/*
	 * Print as much of the letter as we can.
	 */
	cp = sendbuf;
	if ((n = cols - chars) > sizeof sendbuf - 1)
		n = sizeof sendbuf - 1;
	bufend = cp + n;
	seenspace = 0;
	while ((n = readline(mfd, lbuf, sizeof lbuf)) >= 0) {
		register char *rp;

		if (strncmp(lbuf, "From ", 5) == 0)
			break;
		if (cp >= bufend)
			continue;
		if (!seenspace) {
			*cp++ = ' ';		/* space before lines */
			seenspace = 1;
		}
		rp = lbuf;
		while (*rp && cp < bufend)
			if (isspace(*rp)) {
				if (!seenspace) {
					*cp++ = ' ';
					seenspace = 1;
				}
				rp++;
			} else {
				*cp++ = *rp++;
				seenspace = 0;
			}
	}
	*cp = 0;
	stringcat(sendbuf, -1);
	/*
	 * Want to update write time so a star will
	 * appear after the number of users until the
	 * user reads his mail.
	 */
out:
	mailsize = linebeg;
	fclose(mfd);
	touch(username);
	return retval;
}

/*
 * readline -- read a line from fp and store it in buf.
 * return the number of characters read.
 */
int readline(fp, buf, n)
	register FILE *fp;
	char *buf;
	register n;
{
	register c=0;
	register char *cp = buf;

	linebeg = ftell(fp);		/* remember loc where line begins */
	cp = buf;
	while (--n > 0 && (c = getc(fp)) != EOF && c != '\n')
		*cp++ = c;
	*cp = 0;
	if (c == EOF && cp - buf == 0)
		return -1;
	return cp - buf;
}


/*
 * string hacking functions
 */

void stringinit()
{
	sp = strarr;
	chars = 0;
}

void stringprt(const char *format, ...)
{
	char tempbuf[150];
	va_list ap;

	va_start( ap, format );
	vsprintf(tempbuf, format, ap);
	va_end( ap );
	stringcat( tempbuf, -1 );
}

void stringdump()
{
	char bigbuf[sizeof strarr + 200];
	register char *bp = bigbuf;
	register int i;
	
	if (!emacs) {
		if (sawmail)
			bp = strcpy1(bp, bel);
#ifdef TERMINFO
		if (!window && status_line_esc_ok)
#else
		if (!window && eslok)
#endif
			bp = strcpy1(bp, tparm(tsl,
				leftline ? 0 : cols - chars));
		else {
			bp = strcpy1(bp, tsl);
			if (!shortline && !leftline)
				for (i = cols - chars; --i >= 0;)
					*bp++ = ' ';
		}

		if (reverse && revtime != 0)
			bp = strcpy1(bp, rev_out);
	}
	*sp = 0;
	bp = strcpy1(bp, strarr);
	if (!emacs) {
		if (reverse)
			bp = strcpy1(bp, rev_end);
		bp = strcpy1(bp, fsl);
		if (sawmail)
			bp = strcpy1(strcpy1(bp, bel), bel);
		*bp = 0;
		tputs(bigbuf, 1, outc);
		if (mustclear) {
			mustclear = 0;
			tputs(clr_eol, 1, outc);
		}
		if (dbug)
			putchar('\n');
		fflush(stdout);
	} else
		write(2, bigbuf, bp - bigbuf);
}

void stringspace()
{
	if (reverse && revtime != 0) {
#ifdef TERMINFO
		stringcat(rev_end,
			magic_cookie_glitch <= 0 ? 0 : magic_cookie_glitch);
		stringcat(" ", 1);
		stringcat(rev_out,
			magic_cookie_glitch <= 0 ? 0 : magic_cookie_glitch);
#else
		stringcat(rev_end, 0);
		stringcat(" ", 1);
		stringcat(rev_out, 0);
#endif TERMINFO
	} else
		stringcat(" ", 1);
}

/*
 * stringcat :: concatenate the characters in string str to the list we are
 * 	        building to send out.
 * str - the string to print. may contain funny (terminal control) chars.
 * n  - the number of printable characters in the string
 *	or if -1 then str is all printable so we can truncate it,
 *	otherwise don't print only half a string.
 */
void stringcat(str, n)
	register char *str;
	register n;
{
	register char *p = sp;

	if (n < 0) {				/* truncate */
		n = cols - chars;
		while ((*p++ = *str++) && --n >= 0)
			;
		p--;
		chars += p - sp;
		sp = p;
	} else if (chars + n <= cols) {	/* don't truncate */
		while ((*p++ = *str++))
			;
		chars += n;
		sp = p - 1;
	}
}

/*
 * touch :: update the modify time of a file.
 */
void touch(name)
	char *name;		/* name of file */
{
	register fd;
	char buf;

	if ((fd = open(name, 2)) >= 0) {
		read(fd, &buf, 1);		/* get first byte */
		lseek(fd, 0L, 0);		/* go to beginning */
		write(fd, &buf, 1);		/* and rewrite first byte */
		close(fd);
	}
}


/*
 * clearbotl :: clear bottom line.
 * called when process quits or is killed.
 * it clears the bottom line of the terminal.
 */
void clearbotl()
{
	register int fd;

	setsignal(SIGALRM, exit);
	alarm(30);	/* if can't open in 30 secs, just die */
	if (!emacs && (fd = open(ourtty, 1)) >= 0) {
		write(fd, dsl, strlen(dsl));
		close(fd);
	}
#ifdef PROF
	if (chdir(_PATH_SYSLINE) < 0)
		(void) chdir(_PATH_TMP);
#endif
	exit(0);
}

#ifdef TERMINFO
void initterm()
{
	char *term;
	static char standbuf[40];

	term = getenv("TERM");
	setupterm(0, 1, 0);
	if (!window && !has_status_line) {
		/* not an appropriate terminal */
		if (!quiet)
		   fprintf(stderr, MYNAME": no status capability for terminal type `%s'\n", term);
		exit(1);
	}

	if (!window && init_2string != NULL) 
	{
		tputs(init_2string, 1, erroutc);
		fflush(stdout);
	}

	if (window || status_line_esc_ok) {
		if (set_attributes) {
			/* reverse video mode */
			strcpy(standbuf,
				tparm(set_attributes,0,0,1,0,0,0,0,0,0));
			rev_out = standbuf;
			rev_end = exit_attribute_mode;
		} else if (enter_standout_mode && exit_standout_mode) {
			rev_out = enter_standout_mode;
			rev_end = exit_standout_mode;
		} else
			rev_out = rev_end = "";
	} else
		rev_out = rev_end = "";
	cols = width_status_line;
	hasws = cols>=0;
	if (!hasws)
		cols = columns;		
	cols--;		/* avoid cursor wraparound */
	if (!strncmp(term, "h19", 3))
		arrows = "\033Fhh\033G";	/* "two tiny graphic arrows" */
	else
		arrows = ARROWS;
	if (bell!=NULL)
		bel = bell;
	else
		bel = "\007";
	if (window) 
	{
		strcpy( tsl,   "\r" );
		strcpy( dsl,   "\r" );
		strcpy( dsl+1, clr_eol );
		if (leftline)
			strcpy( fsl, clr_eol );
		else
			strcpy( fsl, "" );
	}
	else
	{
		strcpy( tsl, to_status_line   );
		strcpy( fsl, from_status_line );
		strcpy( dsl, dis_status_line  );
	}
}

#else	/* TERMCAP */
void initterm()
{
	char *term, *cp;
	static char tbuf[1024];
	char is2[40];
	extern char *UP;

	if ((term = getenv("TERM")) == NULL) {
		if (!quiet)
			fprintf(stderr,
				MYNAME": no TERM variable in enviroment\n");
		exit(1);
	}
	if (tgetent(tbuf, term) <= 0) {
		if (!quiet)
			fprintf(stderr,
				MYNAME": unknown terminal type `%s'\n", term);
		exit(1);
	}
	if (!window && tgetflag("hs") <= 0) {
		if (!strncmp(term, "h19", 3)) {
			/* for upward compatability with h19sys */
			strcpy(tsl,
				"\033j\033x5\033x1\033Y8%+ \033o");
			strcpy(fsl, "\033k\033y5");
			strcpy(dsl, "\033y1");
			strcpy(rev_out, "\033p");
			strcpy(rev_end, "\033q");
			arrows = "\033Fhh\033G";
			cols = 80;
			UP = "\b";
			return;
		}
		if (!quiet)
			fprintf(stderr,
				MYNAME": no status capability for terminal type `%s'\n", term);
		exit(1);
	}
	cp = is2;
	if (!window && tgetstr("i2", &cp)) {
		/* someday tset will do this */
		tputs(is2, 1, erroutc);
		fflush(stdout);
	}

	/* the "-1" below is to avoid cursor wraparound problems */
	cols = tgetnum("ws");
	hasws = cols >= 0;
	if (!hasws)
		cols = tgetnum("co");
	cols -= 1;
	cp = bel;
	if (!tgetstr("bl", &cp))
		strcpy( bel, "\007" );
	if (window) {
		strcpy(tsl, "\r");
		cp = dsl;		/* use the clear line sequence */
		*cp++ = '\r';
		tgetstr("ce", &cp);
		if (leftline)
			strcpy(fsl, dsl + 1);
		else
			strcpy(fsl, "");
	} else {
		cp = tsl;
		tgetstr("ts", &cp);
		cp = fsl;
		tgetstr("fs", &cp);
		cp = dsl;
		tgetstr("ds", &cp);
		eslok = tgetflag("es");
	}
	if (eslok || window) {
		cp = rev_out;
		tgetstr("so", &cp);
		cp = rev_end;
		tgetstr("se", &cp);
		cp = clr_eol;
		tgetstr("ce", &cp);
	} else
		reverse = 0;			/* turn off reverse video */
	UP = "\b";
	if (!strncmp(term, "h19", 3))
		arrows = "\033Fhh\033G";	/* "two tiny graphic arrows" */
	else
		arrows = ARROWS;
}
#endif TERMINFO

#ifdef RWHO
char *
sysrup(hp)
	register struct remotehost *hp;
{
	char filename[100];
	struct whod wd;
#define WHOD_HDR_SIZE (sizeof (wd) - sizeof (wd.wd_we))
	static char buffer[50];
	time_t now;

	/*
	 * rh_file is initially 0.
	 * This is ok since standard input is assumed to exist.
	 */
	if (hp->rh_file == 0) {
		/*
		 * Try rwho hostname file, and if that fails try ucbhostname.
		 */
		(void) strcpy1(strcpy1(filename, _PATH_RWHO), hp->rh_host);
		if ((hp->rh_file = open(filename, 0)) < 0) {
			(void) strcpy1(strcpy1(strcpy1(filename, _PATH_RWHO),
				NETPREFIX), hp->rh_host);
			hp->rh_file = open(filename, 0);
		}
	}
	if (hp->rh_file < 0) {
		(void) sprintf(buffer, "%s?", hp->rh_host);
		return(buffer);
	}
	(void) lseek(hp->rh_file, (off_t)0, 0);
	if (read(hp->rh_file, (char *)&wd, WHOD_HDR_SIZE) != WHOD_HDR_SIZE) {
		(void) sprintf(buffer, "%s ?", hp->rh_host);
		return(buffer);
	}
	(void) time(&now);
	if (now - wd.wd_recvtime > DOWN_THRESHOLD) {
		long interval;
		long days, hours, minutes;

		interval = now - wd.wd_recvtime;
		minutes = (interval + 59) / 60;	/* round to minutes */
		hours = minutes / 60;		/* extract hours from minutes */
		minutes %= 60;			/* remove hours from minutes */
		days = hours / 24;		/* extract days from hours */
		hours %= 24;			/* remove days from hours */
		if (days > 7 || days < 0)
			(void) sprintf(buffer, "%s down", hp->rh_host);
		else if (days > 0)
			(void) sprintf(buffer, "%s %d+%d:%02d",
				hp->rh_host, (int)days, (int)hours, (int)minutes);
		else
			(void) sprintf(buffer, "%s %d:%02d",
				hp->rh_host, (int)hours, (int)minutes);
	} else
		(void) sprintf(buffer, "%s %.1f",
			hp->rh_host, wd.wd_loadav[0]/100.0);
	return buffer;
}
#endif RWHO

void getwinsize()
{
#ifdef TIOCGWINSZ
	struct winsize winsize;

	/* the "-1" below is to avoid cursor wraparound problems */
	if (!hasws
	 && ioctl(2, TIOCGWINSZ, (char *)&winsize) >= 0 && winsize.ws_col != 0)
		cols = winsize.ws_col - 1;
#endif
}

#ifdef SIGWINCH
void sigwinch()
{
	winchanged++;
}
#endif

char *
strcpy1(p, q)
	register char *p, *q;
{
	while ((*p++ = *q++))
		;
	return p - 1;
}

int outc(c)
	char c;
{
	if (dbug)
		printf("%s", unctrl(c));
	else
		putchar(c);
	return( 1 );
}

int erroutc(c)
	char c;
{
	if (dbug)
		fprintf(stderr, "%s", unctrl(c));
	else
		putc(c, stderr);
	return( 1 );
}


 /* 
  * following readproctab() is derived from Michael K. Johnson's function
  * take_snapshot in his procps package, stripped of redundancy and
  * customized to fit our purposes - bjd 
  */

 #define PZERO	15

 void readproctab( uid_t uid )
 {
	DIR *proc;
	static struct direct *ent;
	static char filename[80];
	static char stat_str[4096];
	char process_state;
	int  process_ppid, process_pgrp, process_priority;
	struct stat sb;
	int fd, nr_read;

	procrun = procstop = 0;
	proc = opendir("/proc");

	while ((ent = readdir(proc)))
	{
		 /*
		  * I'm assuming anything that starts with a digit is a
		  * valid process directory.  Since the kernel has absolute
		  * control over the /proc directory, this is a valid 
		  * assumption, I think.			- bjd
		  */
		if (!(*ent->d_name>='0' && *ent->d_name<='9'))
			continue;
		sprintf(filename, "/proc/%s", ent->d_name);
		stat(filename, &sb);
		if(sb.st_uid != uid) 
    			continue;
		strcat(filename, "/stat");
		fd = open(filename, O_RDONLY, 0);
		if (fd != -1) 
		{
		   	nr_read = read(fd, stat_str, sizeof(stat_str));
    			stat_str[nr_read]='\0';
	    	}
		else 
    			continue;
		close(fd);
    
		sscanf(stat_str, "%*d %*s %c %d %d %*d %*d %*d %*u %*u \
			     %*u %*u %*u %*d %*d %*d %*d %*d %d",
			    &process_state, 
			    &process_ppid, 
			    &process_pgrp, 
			    &process_priority );
		if (process_pgrp==0 || process_ppid==1)
    			continue;
		switch (process_state) 
		{
			case 'T':procstop++;
				 break;
			case 'S':
				 /*
				  * Sleep can mean waiting for a signal or just
				  * in a disk or page wait queue ready to run.
				  * We can tell if it is the later by the pri
				  * being negative.
				  */
				 if (process_priority > PZERO)
					procrun++;
				 break;
			case 'D':
			case 'R':procrun++;
		}
	} /* end of the while loop */
	closedir(proc);
 }

 void setsignal( int sig, void (*func)(int) )
 {
	struct sigaction sa;

	sa.sa_handler = func;
	sigemptyset( &sa.sa_mask );
	sa.sa_flags = 0;
	if (sig!=SIGALRM) 
	{
#ifdef	SA_RESTART
		sa.sa_flags |= SA_RESTART;
#endif
	}
	sigaction( sig, &sa, NULL );
 }
