xref: /original-bsd/old/sysline/sysline.c (revision eafa6506)
1 /*
2  * Copyright (c) 1980 Regents of the University of California.
3  * All rights reserved.
4  *
5  * %sccs.include.redist.c%
6  */
7 
8 #ifndef lint
9 char copyright[] =
10 "@(#) Copyright (c) 1980 Regents of the University of California.\n\
11  All rights reserved.\n";
12 #endif /* not lint */
13 
14 #ifndef lint
15 static char sccsid[] = "@(#)sysline.c	5.17 (Berkeley) 02/25/91";
16 #endif /* not lint */
17 
18 /*
19  * sysline - system status display on 25th line of terminal
20  * j.k.foderaro
21  *
22  * Prints a variety of information on the special status line of terminals
23  * that have a status display capability.  Cursor motions, status commands,
24  * etc. are gleamed from /etc/termcap.
25  * By default, all information is printed, and flags are given on the command
26  * line to disable the printing of information.  The information and
27  * disabling flags are:
28  *
29  *  flag	what
30  *  -----	----
31  *		time of day
32  *		load average and change in load average in the last 5 mins
33  *		number of user logged on
34  *   -p		# of processes the users owns which are runnable and the
35  *		  number which are suspended.  Processes whose parent is 1
36  *		  are not counted.
37  *   -l		users who've logged on and off.
38  *   -m		summarize new mail which has arrived
39  *
40  *  <other flags>
41  *   -r		use non reverse video
42  *   -c		turn off 25th line for 5 seconds before redisplaying.
43  *   -b		beep once one the half hour, twice on the hour
44  *   +N		refresh display every N seconds.
45  *   -i		print pid first thing
46  *   -e		do simple print designed for an emacs buffer line
47  *   -w		do the right things for a window
48  *   -h		print hostname between time and load average
49  *   -D		print day/date before time of day
50  *   -d		debug mode - print status line data in human readable format
51  *   -q		quiet mode - don't output diagnostic messages
52  *   -s		print Short (left-justified) line if escapes not allowed
53  *   -j		Print left Justified line regardless
54  */
55 
56 #define BSD4_2			/* for 4.2 BSD */
57 #define WHO			/* turn this on always */
58 #define RWHO			/* 4.1a or greater, with rwho */
59 #define NEW_BOOTTIME		/* 4.1c or greater */
60 
61 #define NETPREFIX "ucb"
62 #define DEFDELAY 60		/* update status once per minute */
63 /*
64  * if MAXLOAD is defined, then if the load average exceeded MAXLOAD
65  * then the process table will not be scanned and the log in/out data
66  * will not be checked.   The purpose of this is to reduced the load
67  * on the system when it is loaded.
68  */
69 #define MAXLOAD 6.0
70 
71 #include <sys/param.h>
72 #include <sys/time.h>
73 #include <sys/stat.h>
74 #include <sys/vtimes.h>
75 #include <sys/proc.h>
76 #include <sys/ioctl.h>
77 #include <signal.h>
78 #include <fcntl.h>
79 #include <utmp.h>
80 #include <nlist.h>
81 #include <curses.h>
82 #undef nl
83 #ifdef TERMINFO
84 #include <term.h>
85 #endif
86 #include <stdarg.h>
87 #include <unistd.h>
88 #include <stdlib.h>
89 #include <string.h>
90 #include <stdio.h>
91 #include <ctype.h>
92 
93 #ifdef RWHO
94 #include <protocols/rwhod.h>
95 
96 #define	DOWN_THRESHOLD	(11 * 60)
97 
98 struct remotehost {
99 	char *rh_host;
100 	int rh_file;
101 } remotehost[10];
102 int nremotes = 0;
103 #endif RWHO
104 
105 #include "pathnames.h"
106 
107 struct nlist nl[] = {
108 	{ "_boottime" },
109 #define	NL_BOOT 0
110 	{ "_proc" },
111 #define NL_PROC 1
112 	{ "_nproc" },
113 #define NL_NPROC 2
114 	0
115 };
116 
117 	/* stuff for the kernel */
118 int kmem;			/* file descriptor for _PATH_KMEM */
119 struct proc *proc, *procNPROC;
120 int nproc;
121 int procadr;
122 double avenrun[3];		/* used for storing load averages */
123 
124 /*
125  * In order to determine how many people are logged on and who has
126  * logged in or out, we read in the /etc/utmp file. We also keep track of
127  * the previous utmp file.
128  */
129 int ut = -1;			/* the file descriptor */
130 struct utmp *new, *old;
131 char *status;			/* per tty status bits, see below */
132 int nentries;			/* number of utmp entries */
133 	/* string lengths for printing */
134 #define LINESIZE (sizeof old->ut_line)
135 #define NAMESIZE (sizeof old->ut_name)
136 /*
137  * Status codes to say what has happened to a particular entry in utmp.
138  * NOCH means no change, ON means new person logged on,
139  * OFF means person logged off.
140  */
141 #define NOCH	0
142 #define ON	0x1
143 #define OFF	0x2
144 
145 #ifdef WHO
146 char whofilename[100];
147 char whofilename2[100];
148 #endif
149 
150 char hostname[MAXHOSTNAMELEN+1];	/* one more for null termination */
151 char lockfilename[100];		/* if exists, will prevent us from running */
152 
153 	/* flags which determine which info is printed */
154 int mailcheck = 1;	/* m - do biff like checking of mail */
155 int proccheck = 1;	/* p - give information on processes */
156 int logcheck = 1; 	/* l - tell who logs in and out */
157 int hostprint = 0;	/* h - print out hostname */
158 int dateprint = 0;	/* h - print out day/date */
159 int quiet = 0;		/* q - hush diagnostic messages */
160 
161 	/* flags which determine how things are printed */
162 int clr_bet_ref = 0;	/* c - clear line between refeshes */
163 int reverse = 1;	/* r - use reverse video */
164 int shortline = 0;	/* s - short (left-justified) if escapes not allowed */
165 int leftline = 0;	/* j - left-justified even if escapes allowed */
166 
167 	/* flags which have terminal do random things	*/
168 int beep = 0;		/* b - beep every half hour and twice every hour */
169 int printid = 0;	/* i - print pid of this process at startup */
170 int synch = 1;		/* synchronize with clock */
171 
172 	/* select output device (status display or straight output) */
173 int emacs = 0;		/* e - assume status display */
174 int window = 0;		/* w - window mode */
175 int dbug = 0;		/* d - debug */
176 
177 /*
178  * used to turn off reverse video every REVOFF times
179  * in an attempt to not wear out the phospher.
180  */
181 #define REVOFF 5
182 int revtime = 1;
183 
184 	/* used by mail checker */
185 off_t mailsize = 0;
186 off_t linebeg = 0;		/* place where we last left off reading */
187 
188 	/* things used by the string routines */
189 int chars;			/* number of printable characters */
190 char *sp;
191 char strarr[512];		/* big enough now? */
192 	/* flags to stringdump() */
193 char sawmail;			/* remember mail was seen to print bells */
194 char mustclear;			/* status line messed up */
195 
196 	/* strings which control status line display */
197 #ifdef TERMINFO
198 char	*rev_out, *rev_end, *arrows;
199 char	*tparm();
200 #else
201 char	to_status_line[64];
202 char	from_status_line[64];
203 char	dis_status_line[64];
204 char	clr_eol[64];
205 char	rev_out[20], rev_end[20];
206 char	*arrows, *bell = "\007";
207 int	eslok;	/* escapes on status line okay (reverse, cursor addressing) */
208 int	hasws = 0;	/* is "ws" explicitly defined? */
209 int	columns;
210 #define tparm(cap, parm) tgoto((cap), 0, (parm))
211 char	*tgoto();
212 #endif
213 
214 	/* to deal with window size changes */
215 #ifdef SIGWINCH
216 void sigwinch();
217 char winchanged;	/* window size has changed since last update */
218 #endif
219 
220 	/* random globals */
221 char *username;
222 char *ourtty;			/* keep track of what tty we're on */
223 struct stat stbuf, mstbuf;	/* mstbuf for mail check only */
224 unsigned delay = DEFDELAY;
225 uid_t uid;
226 double loadavg = 0.0;		/* current load average */
227 int users = 0;
228 
229 char *strcpy1();
230 char *sysrup();
231 int outc();
232 int erroutc();
233 
234 main(argc, argv)
235 	int argc;
236 	register char **argv;
237 {
238 	register char *cp;
239 	char *home;
240 	void clearbotl();
241 
242 	gethostname(hostname, sizeof hostname - 1);
243 	if ((cp = index(hostname, '.')) != NULL)
244 		*cp = '\0';
245 	(void)setvbuf(stdout, (char *)NULL, _IOFBF, 0);
246 
247 	for (argv++; *argv != 0; argv++)
248 		switch (**argv) {
249 		case '-':
250 			for (cp = *argv + 1; *cp; cp++) {
251 				switch(*cp) {
252 				case 'r' :	/* turn off reverse video */
253 					reverse = 0;
254 					break;
255 				case 'c':
256 					clr_bet_ref = 1;
257 					break;
258 				case 'h':
259 					hostprint = 1;
260 					break;
261 				case 'D':
262 					dateprint = 1;
263 					break;
264 #ifdef RWHO
265 				case 'H':
266 					if (argv[1] == 0)
267 						break;
268 					argv++;
269 					if (strcmp(hostname, *argv) &&
270 					    strcmp(&hostname[sizeof NETPREFIX - 1], *argv))
271 						remotehost[nremotes++].rh_host = *argv;
272 					break;
273 #endif RWHO
274 				case 'm':
275 					mailcheck = 0;
276 					break;
277 				case 'p':
278 					proccheck = 0;
279 					break;
280 				case 'l':
281 					logcheck = 0;
282 					break;
283 				case 'b':
284 					beep = 1;
285 					break;
286 				case 'i':
287 					printid = 1;
288 					break;
289 				case 'w':
290 					window = 1;
291 					break;
292 				case 'e':
293 					emacs = 1;
294 					break;
295 				case 'd':
296 					dbug = 1;
297 					break;
298 				case 'q':
299 					quiet = 1;
300 					break;
301 				case 's':
302 					shortline = 1;
303 					break;
304 				case 'j':
305 					leftline = 1;
306 					break;
307 				default:
308 					fprintf(stderr,
309 						"sysline: bad flag: %c\n", *cp);
310 				}
311 			}
312 			break;
313 		case '+':
314 			delay = atoi(*argv + 1);
315 			if (delay < 10)
316 				delay = 10;
317 			else if (delay > 500)
318 				delay = 500;
319 			synch = 0;	/* no more sync */
320 			break;
321 		default:
322 			fprintf(stderr, "sysline: illegal argument %s\n",
323 				argv[0]);
324 		}
325 	if (emacs) {
326 		reverse = 0;
327 		columns = 79;
328 	} else	/* if not to emacs window, initialize terminal dependent info */
329 		initterm();
330 #ifdef SIGWINCH
331 	/*
332 	 * When the window size changes and we are the foreground
333 	 * process (true if -w), we get this signal.
334 	 */
335 	signal(SIGWINCH, sigwinch);
336 #endif
337 	getwinsize();		/* get window size from ioctl */
338 
339 	/* immediately fork and let the parent die if not emacs mode */
340 	if (!emacs && !window && !dbug) {
341 		if (fork())
342 			exit(0);
343 		/* pgrp should take care of things, but ignore them anyway */
344 		signal(SIGINT, SIG_IGN);
345 		signal(SIGQUIT, SIG_IGN);
346 		signal(SIGTTOU, SIG_IGN);
347 	}
348 	/*
349 	 * When we logoff, init will do a "vhangup()" on this
350 	 * tty which turns off I/O access and sends a SIGHUP
351 	 * signal.  We catch this and thereby clear the status
352 	 * display.  Note that a bug in 4.1bsd caused the SIGHUP
353 	 * signal to be sent to the wrong process, so you had to
354 	 * `kill -HUP' yourself in your .logout file.
355 	 * Do the same thing for SIGTERM, which is the default kill
356 	 * signal.
357 	 */
358 	signal(SIGHUP, clearbotl);
359 	signal(SIGTERM, clearbotl);
360 	/*
361 	 * This is so kill -ALRM to force update won't screw us up..
362 	 */
363 	signal(SIGALRM, SIG_IGN);
364 
365 	uid = getuid();
366 	ourtty = ttyname(2);	/* remember what tty we are on */
367 	if (printid) {
368 		printf("%d\n", getpid());
369 		fflush(stdout);
370 	}
371 	dup2(2, 1);
372 
373 	if ((home = getenv("HOME")) == 0)
374 		home = "";
375 	strcpy1(strcpy1(whofilename, home), "/.who");
376 	strcpy1(strcpy1(whofilename2, home), "/.sysline");
377 	strcpy1(strcpy1(lockfilename, home), "/.syslinelock");
378 
379 	if ((kmem = open(_PATH_KMEM,0)) < 0) {
380 		fprintf(stderr, "Can't open %s\n", _PATH_KMEM);
381 		exit(1);
382 	}
383 	readnamelist();
384 	if (proccheck)
385 		initprocread();
386 	if (mailcheck)
387 		if ((username = getenv("USER")) == 0)
388 			mailcheck = 0;
389 		else {
390 			chdir(_PATH_MAILDIR);
391 			if (stat(username, &mstbuf) >= 0)
392 				mailsize = mstbuf.st_size;
393 			else
394 				mailsize = 0;
395 		}
396 
397 	while (emacs || window || isloggedin())
398 		if (access(lockfilename, 0) >= 0)
399 			sleep(60);
400 		else {
401 			prtinfo();
402 			sleep(delay);
403 			if (clr_bet_ref) {
404 				tputs(dis_status_line, 1, outc);
405 				fflush(stdout);
406 				sleep(5);
407 			}
408 			revtime = (1 + revtime) % REVOFF;
409 		}
410 	clearbotl();
411 	/*NOTREACHED*/
412 }
413 
414 isloggedin()
415 {
416 	/*
417 	 * you can tell if a person has logged out if the owner of
418 	 * the tty has changed
419 	 */
420 	struct stat statbuf;
421 
422 	return fstat(2, &statbuf) == 0 && statbuf.st_uid == uid;
423 }
424 
425 readnamelist()
426 {
427 	time_t bootime, clock, nintv, time();
428 
429 	nlist(_PATH_UNIX, nl);
430 	if (nl[0].n_value == 0) {
431 		if (!quiet)
432 			fprintf(stderr, "No namelist\n");
433 		return;
434 	}
435 	lseek(kmem, (long)nl[NL_BOOT].n_value, 0);
436 	read(kmem, &bootime, sizeof(bootime));
437 	(void) time(&clock);
438 	nintv = clock - bootime;
439 	if (nintv <= 0L || nintv > 60L*60L*24L*365L) {
440 		if (!quiet)
441 			fprintf(stderr,
442 			"Time makes no sense... namelist must be wrong\n");
443 		nl[NL_PROC].n_value = 0;
444 	}
445 }
446 
447 readutmp(nflag)
448 	char nflag;
449 {
450 	static time_t lastmod;		/* initially zero */
451 	static off_t utmpsize;		/* ditto */
452 	struct stat st;
453 
454 	if (ut < 0 && (ut = open(_PATH_UTMP, 0)) < 0) {
455 		fprintf(stderr, "sysline: Can't open %s.\n", _PATH_UTMP);
456 		exit(1);
457 	}
458 	if (fstat(ut, &st) < 0 || st.st_mtime == lastmod)
459 		return 0;
460 	lastmod = st.st_mtime;
461 	if (utmpsize != st.st_size) {
462 		utmpsize = st.st_size;
463 		nentries = utmpsize / sizeof (struct utmp);
464 		if (old == 0) {
465 			old = (struct utmp *)calloc(utmpsize, 1);
466 			new = (struct utmp *)calloc(utmpsize, 1);
467 		} else {
468 			old = (struct utmp *)realloc((char *)old, utmpsize);
469 			new = (struct utmp *)realloc((char *)new, utmpsize);
470 			free(status);
471 		}
472 		status = malloc(nentries * sizeof *status);
473 		if (old == 0 || new == 0 || status == 0) {
474 			fprintf(stderr, "sysline: Out of memory.\n");
475 			exit(1);
476 		}
477 	}
478 	lseek(ut, 0L, 0);
479 	(void) read(ut, (char *) (nflag ? new : old), utmpsize);
480 	return 1;
481 }
482 
483 /*
484  * read in the process table locations and sizes, and allocate space
485  * for storing the process table.  This is done only once.
486  */
487 initprocread()
488 {
489 
490 	if (nl[NL_PROC].n_value == 0)
491 		return;
492 	lseek(kmem, (long)nl[NL_PROC].n_value, 0);
493 	read(kmem, &procadr, sizeof procadr);
494 	lseek(kmem, (long)nl[NL_NPROC].n_value, 0);
495 	read(kmem, &nproc, sizeof nproc);
496 	if ((proc = (struct proc *) calloc(nproc, sizeof (struct proc))) == 0) {
497 		fprintf(stderr, "Out of memory.\n");
498 		exit(1);
499 	}
500 	procNPROC = proc + nproc;
501 }
502 
503 /*
504  * read in the process table.  This assumes that initprocread has alread been
505  * called to set up storage.
506  */
507 readproctab()
508 {
509 
510 	if (nl[NL_PROC].n_value == 0)
511 		return (0);
512 	lseek(kmem, (long)procadr, 0);
513 	read(kmem, (char *)proc, nproc * sizeof (struct proc));
514 	return (1);
515 }
516 
517 prtinfo()
518 {
519 	int on, off;
520 	register i;
521 	char fullprocess;
522 
523 	stringinit();
524 #ifdef SIGWINCH
525 	if (winchanged) {
526 		winchanged = 0;
527 		getwinsize();
528 		mustclear = 1;
529 	}
530 #endif
531 #ifdef WHO
532 	/* check for file named .who in the home directory */
533 	whocheck();
534 #endif
535 	timeprint();
536 	/*
537 	 * if mail is seen, don't print rest of info, just the mail
538 	 * reverse new and old so that next time we run, we won't lose log
539 	 * in and out information
540 	 */
541 	if (mailcheck && (sawmail = mailseen()))
542 		goto bottom;
543 #ifdef RWHO
544 	for (i = 0; i < nremotes; i++) {
545 		char *tmp;
546 
547 		stringspace();
548 		tmp = sysrup(remotehost + i);
549 		stringcat(tmp, strlen(tmp));
550 	}
551 #endif
552 	/*
553 	 * print hostname info if requested
554 	 */
555 	if (hostprint) {
556 		stringspace();
557 		stringcat(hostname, -1);
558 	}
559 	/*
560 	 * print load average and difference between current load average
561 	 * and the load average 5 minutes ago
562 	 */
563 	if (getloadavg(avenrun, 3) > 0) {
564 		double diff;
565 
566 		stringspace();
567 
568 		if ((diff = avenrun[0] - avenrun[1]) < 0.0)
569 			stringprt("%.1f %.1f", avenrun[0],  diff);
570 		else
571 			stringprt("%.1f +%.1f", avenrun[0], diff);
572 		loadavg = avenrun[0];		/* remember load average */
573 	}
574 	/*
575 	 * print log on and off information
576 	 */
577 	stringspace();
578 	fullprocess = 1;
579 #ifdef MAXLOAD
580 	if (loadavg > MAXLOAD)
581 		fullprocess = 0;	/* too loaded to run */
582 #endif
583 	/*
584 	 * Read utmp file (logged in data) only if we are doing a full
585 	 * process, or if this is the first time and we are calculating
586 	 * the number of users.
587 	 */
588 	on = off = 0;
589 	if (users == 0) {		/* first time */
590 		if (readutmp(0))
591 			for (i = 0; i < nentries; i++)
592 				if (old[i].ut_name[0])
593 					users++;
594 	} else if (fullprocess && readutmp(1)) {
595 		struct utmp *tmp;
596 
597 		users = 0;
598 		for (i = 0; i < nentries; i++) {
599 			if (strncmp(old[i].ut_name,
600 			    new[i].ut_name, NAMESIZE) == 0)
601 				status[i] = NOCH;
602 			else if (old[i].ut_name[0] == '\0') {
603 				status[i] = ON;
604 				on++;
605 			} else if (new[i].ut_name[0] == '\0') {
606 				status[i] = OFF;
607 				off++;
608 			} else {
609 				status[i] = ON | OFF;
610 				on++;
611 				off++;
612 			}
613 			if (new[i].ut_name[0])
614 				users++;
615 		}
616 		tmp = new;
617 		new = old;
618 		old = tmp;
619 	}
620 	/*
621 	 * Print:
622 	 * 	1.  number of users
623 	 *	2.  a * for unread mail
624 	 *	3.  a - if load is too high
625 	 *	4.  number of processes running and stopped
626 	 */
627 	stringprt("%du", users);
628 	if (mailsize > 0 && mstbuf.st_mtime >= mstbuf.st_atime)
629 		stringcat("*", -1);
630 	if (!fullprocess && (proccheck || logcheck))
631 		stringcat("-", -1);
632 	if (fullprocess && proccheck && readproctab()) {
633 		register struct proc *p;
634 		int procrun, procstop;
635 
636 		/*
637 		 * We are only interested in processes which have the same
638 		 * uid as us, and whose parent process id is not 1.
639 		 */
640 		procrun = procstop = 0;
641 		for (p = proc; p < procNPROC; p++) {
642 			if (p->p_stat == 0 || p->p_pgrp == 0 ||
643 			    p->p_uid != uid || p->p_ppid == 1)
644 				continue;
645 			switch (p->p_stat) {
646 			case SSTOP:
647 				procstop++;
648 				break;
649 			case SSLEEP:
650 				/*
651 				 * Sleep can mean waiting for a signal or just
652 				 * in a disk or page wait queue ready to run.
653 				 * We can tell if it is the later by the pri
654 				 * being negative.
655 				 */
656 				if (p->p_pri < PZERO)
657 					procrun++;
658 				break;
659 			case SWAIT:
660 			case SRUN:
661 			case SIDL:
662 				procrun++;
663 			}
664 		}
665 		if (procrun > 0 || procstop > 0) {
666 			stringspace();
667 			if (procrun > 0 && procstop > 0)
668 				stringprt("%dr %ds", procrun, procstop);
669 			else if (procrun > 0)
670 				stringprt("%dr", procrun);
671 			else
672 				stringprt("%ds", procstop);
673 		}
674 	}
675 	/*
676 	 * If anyone has logged on or off, and we are interested in it,
677 	 * print it out.
678 	 */
679 	if (logcheck) {
680 		/* old and new have already been swapped */
681 		if (on) {
682 			stringspace();
683 			stringcat("on:", -1);
684 			for (i = 0; i < nentries; i++)
685 				if (status[i] & ON) {
686 					stringprt(" %.8s", old[i].ut_name);
687 					ttyprint(old[i].ut_line);
688 				}
689 		}
690 		if (off) {
691 			stringspace();
692 			stringcat("off:", -1);
693 			for (i = 0; i < nentries; i++)
694 				if (status[i] & OFF) {
695 					stringprt(" %.8s", new[i].ut_name);
696 					ttyprint(new[i].ut_line);
697 				}
698 		}
699 	}
700 bottom:
701 		/* dump out what we know */
702 	stringdump();
703 }
704 
705 timeprint()
706 {
707 	long curtime;
708 	struct tm *tp, *localtime();
709 	static int beepable = 1;
710 
711 		/* always print time */
712 	time(&curtime);
713 	tp = localtime(&curtime);
714 	if (dateprint)
715 		stringprt("%.11s", ctime(&curtime));
716 	stringprt("%d:%02d", tp->tm_hour > 12 ? tp->tm_hour - 12 :
717 		(tp->tm_hour == 0 ? 12 : tp->tm_hour), tp->tm_min);
718 	if (synch)			/* sync with clock */
719 		delay = 60 - tp->tm_sec;
720 	/*
721 	 * Beepable is used to insure that we get at most one set of beeps
722 	 * every half hour.
723 	 */
724 	if (beep)
725 		if (beepable) {
726 			if (tp->tm_min == 30) {
727 				tputs(bell, 1, outc);
728 				fflush(stdout);
729 				beepable = 0;
730 			} else if (tp->tm_min == 0) {
731 				tputs(bell, 1, outc);
732 				fflush(stdout);
733 				sleep(2);
734 				tputs(bell, 1, outc);
735 				fflush(stdout);
736 				beepable = 0;
737 			}
738 		} else
739 			if (tp->tm_min != 0 && tp->tm_min != 30)
740 				beepable = 1;
741 }
742 
743 /*
744  * whocheck -- check for file named .who and print it on the who line first
745  */
746 whocheck()
747 {
748 	int chss;
749 	register char *p;
750 	char buff[81];
751 	int whofile;
752 
753 	if ((whofile = open(whofilename, 0)) < 0 &&
754 	    (whofile = open(whofilename2, 0)) < 0)
755 		return;
756 	chss = read(whofile, buff, sizeof buff - 1);
757 	close(whofile);
758 	if (chss <= 0)
759 		return;
760 	buff[chss] = '\0';
761 	/*
762 	 * Remove all line feeds, and replace by spaces if they are within
763 	 * the message, else replace them by nulls.
764 	 */
765 	for (p = buff; *p;)
766 		if (*p == '\n')
767 			if (p[1])
768 				*p++ = ' ';
769 			else
770 				*p = '\0';
771 		else
772 			p++;
773 	stringcat(buff, p - buff);
774 	stringspace();
775 }
776 
777 /*
778  * ttyprint -- given the name of a tty, print in the string buffer its
779  * short name surrounded by parenthesis.
780  * ttyxx is printed as (xx)
781  * console is printed as (cty)
782  */
783 ttyprint(name)
784 	char *name;
785 {
786 	char buff[11];
787 
788 	if (strncmp(name, "tty", 3) == 0)
789 		stringprt("(%.*s)", LINESIZE - 3, name + 3);
790 	else if (strcmp(name, "console") == 0)
791 		stringcat("(cty)", -1);
792 	else
793 		stringprt("(%.*s)", LINESIZE, name);
794 }
795 
796 /*
797  * mail checking function
798  * returns 0 if no mail seen
799  */
800 mailseen()
801 {
802 	FILE *mfd;
803 	register n;
804 	register char *cp;
805 	char lbuf[100], sendbuf[100], *bufend;
806 	char seenspace;
807 	int retval = 0;
808 
809 	if (stat(username, &mstbuf) < 0) {
810 		mailsize = 0;
811 		return 0;
812 	}
813 	if (mstbuf.st_size <= mailsize || (mfd = fopen(username,"r")) == NULL) {
814 		mailsize = mstbuf.st_size;
815 		return 0;
816 	}
817 	fseek(mfd, mailsize, 0);
818 	while ((n = readline(mfd, lbuf, sizeof lbuf)) >= 0 &&
819 	       strncmp(lbuf, "From ", 5) != 0)
820 		;
821 	if (n < 0) {
822 		stringcat("Mail has just arrived", -1);
823 		goto out;
824 	}
825 	retval = 1;
826 	/*
827 	 * Found a From line, get second word, which is the sender,
828 	 * and print it.
829 	 */
830 	for (cp = lbuf + 5; *cp && *cp != ' '; cp++)	/* skip to blank */
831 		;
832 	*cp = '\0';					/* terminate name */
833 	stringspace();
834 	stringprt("Mail from %s ", lbuf + 5);
835 	/*
836 	 * Print subject, and skip over header.
837 	 */
838 	while ((n = readline(mfd, lbuf, sizeof lbuf)) > 0)
839 		if (strncmp(lbuf, "Subject:", 8) == 0)
840 			stringprt("on %s ", lbuf + 9);
841 	if (!emacs)
842 		stringcat(arrows, 2);
843 	else
844 		stringcat(": ", 2);
845 	if (n < 0)			/* already at eof */
846 		goto out;
847 	/*
848 	 * Print as much of the letter as we can.
849 	 */
850 	cp = sendbuf;
851 	if ((n = columns - chars) > sizeof sendbuf - 1)
852 		n = sizeof sendbuf - 1;
853 	bufend = cp + n;
854 	seenspace = 0;
855 	while ((n = readline(mfd, lbuf, sizeof lbuf)) >= 0) {
856 		register char *rp;
857 
858 		if (strncmp(lbuf, "From ", 5) == 0)
859 			break;
860 		if (cp >= bufend)
861 			continue;
862 		if (!seenspace) {
863 			*cp++ = ' ';		/* space before lines */
864 			seenspace = 1;
865 		}
866 		rp = lbuf;
867 		while (*rp && cp < bufend)
868 			if (isspace(*rp)) {
869 				if (!seenspace) {
870 					*cp++ = ' ';
871 					seenspace = 1;
872 				}
873 				rp++;
874 			} else {
875 				*cp++ = *rp++;
876 				seenspace = 0;
877 			}
878 	}
879 	*cp = 0;
880 	stringcat(sendbuf, -1);
881 	/*
882 	 * Want to update write time so a star will
883 	 * appear after the number of users until the
884 	 * user reads his mail.
885 	 */
886 out:
887 	mailsize = linebeg;
888 	fclose(mfd);
889 	touch(username);
890 	return retval;
891 }
892 
893 /*
894  * readline -- read a line from fp and store it in buf.
895  * return the number of characters read.
896  */
897 readline(fp, buf, n)
898 	register FILE *fp;
899 	char *buf;
900 	register n;
901 {
902 	register c;
903 	register char *cp = buf;
904 
905 	linebeg = ftell(fp);		/* remember loc where line begins */
906 	cp = buf;
907 	while (--n > 0 && (c = getc(fp)) != EOF && c != '\n')
908 		*cp++ = c;
909 	*cp = 0;
910 	if (c == EOF && cp - buf == 0)
911 		return -1;
912 	return cp - buf;
913 }
914 
915 
916 /*
917  * string hacking functions
918  */
919 
920 stringinit()
921 {
922 	sp = strarr;
923 	chars = 0;
924 }
925 
926 /*VARARGS1*/
927 stringprt(fmt)
928 	char *fmt;
929 {
930 	va_list ap;
931 	char tempbuf[150];
932 
933 	va_start(ap, fmt);
934 	(void)vsnprintf(tempbuf, sizeof(tempbuf), fmt, ap);
935 	va_end(ap);
936 	stringcat(tempbuf, -1);
937 }
938 
939 stringdump()
940 {
941 	char bigbuf[sizeof strarr + 200];
942 	register char *bp = bigbuf;
943 	register int i;
944 
945 	if (!emacs) {
946 		if (sawmail)
947 			bp = strcpy1(bp, bell);
948 		if (eslok)
949 			bp = strcpy1(bp, tparm(to_status_line,
950 				leftline ? 0 : columns - chars));
951 		else {
952 			bp = strcpy1(bp, to_status_line);
953 			if (!shortline && !leftline)
954 				for (i = columns - chars; --i >= 0;)
955 					*bp++ = ' ';
956 		}
957 		if (reverse && revtime != 0)
958 			bp = strcpy1(bp, rev_out);
959 	}
960 	*sp = 0;
961 	bp = strcpy1(bp, strarr);
962 	if (!emacs) {
963 		if (reverse)
964 			bp = strcpy1(bp, rev_end);
965 		bp = strcpy1(bp, from_status_line);
966 		if (sawmail)
967 			bp = strcpy1(strcpy1(bp, bell), bell);
968 		*bp = 0;
969 		tputs(bigbuf, 1, outc);
970 		if (mustclear) {
971 			mustclear = 0;
972 			tputs(clr_eol, 1, outc);
973 		}
974 		if (dbug)
975 			putchar('\n');
976 		fflush(stdout);
977 	} else
978 		write(2, bigbuf, bp - bigbuf);
979 }
980 
981 stringspace()
982 {
983 	if (reverse && revtime != 0) {
984 #ifdef TERMINFO
985 		stringcat(rev_end,
986 			magic_cookie_glitch <= 0 ? 0 : magic_cookie_glitch);
987 		stringcat(" ", 1);
988 		stringcat(rev_out,
989 			magic_cookie_glitch <= 0 ? 0 : magic_cookie_glitch);
990 #else
991 		stringcat(rev_end, 0);
992 		stringcat(" ", 1);
993 		stringcat(rev_out, 0);
994 #endif TERMINFO
995 	} else
996 		stringcat(" ", 1);
997 }
998 
999 /*
1000  * stringcat :: concatenate the characters in string str to the list we are
1001  * 	        building to send out.
1002  * str - the string to print. may contain funny (terminal control) chars.
1003  * n  - the number of printable characters in the string
1004  *	or if -1 then str is all printable so we can truncate it,
1005  *	otherwise don't print only half a string.
1006  */
1007 stringcat(str, n)
1008 	register char *str;
1009 	register n;
1010 {
1011 	register char *p = sp;
1012 
1013 	if (n < 0) {				/* truncate */
1014 		n = columns - chars;
1015 		while ((*p++ = *str++) && --n >= 0)
1016 			;
1017 		p--;
1018 		chars += p - sp;
1019 		sp = p;
1020 	} else if (chars + n <= columns) {	/* don't truncate */
1021 		while (*p++ = *str++)
1022 			;
1023 		chars += n;
1024 		sp = p - 1;
1025 	}
1026 }
1027 
1028 /*
1029  * touch :: update the modify time of a file.
1030  */
1031 touch(name)
1032 	char *name;		/* name of file */
1033 {
1034 	register fd;
1035 	char buf;
1036 
1037 	if ((fd = open(name, 2)) >= 0) {
1038 		read(fd, &buf, 1);		/* get first byte */
1039 		lseek(fd, 0L, 0);		/* go to beginning */
1040 		write(fd, &buf, 1);		/* and rewrite first byte */
1041 		close(fd);
1042 	}
1043 }
1044 
1045 
1046 /*
1047  * clearbotl :: clear bottom line.
1048  * called when process quits or is killed.
1049  * it clears the bottom line of the terminal.
1050  */
1051 clearbotl()
1052 {
1053 	register int fd;
1054 	void sigexit();
1055 
1056 	signal(SIGALRM, sigexit);
1057 	alarm(30);	/* if can't open in 30 secs, just die */
1058 	if (!emacs && (fd = open(ourtty, 1)) >= 0) {
1059 		write(fd, dis_status_line, strlen(dis_status_line));
1060 		close(fd);
1061 	}
1062 #ifdef PROF
1063 	if (chdir(_PATH_SYSLINE) < 0)
1064 		(void) chdir(_PATH_TMP);
1065 #endif
1066 	exit(0);
1067 }
1068 
1069 #ifdef TERMINFO
1070 initterm()
1071 {
1072 	static char standbuf[40];
1073 
1074 	setupterm(0, 1, 0);
1075 	if (!window && !has_status_line) {
1076 		/* not an appropriate terminal */
1077 		if (!quiet)
1078 		   fprintf(stderr, "sysline: no status capability for %s\n",
1079 			getenv("TERM"));
1080 		exit(1);
1081 	}
1082 	if (window || status_line_esc_ok) {
1083 		if (set_attributes) {
1084 			/* reverse video mode */
1085 			strcpy(standbuf,
1086 				tparm(set_attributes,0,0,1,0,0,0,0,0,0));
1087 			rev_out = standbuf;
1088 			rev_end = exit_attribute_mode;
1089 		} else if (enter_standout_mode && exit_standout_mode) {
1090 			rev_out = enter_standout_mode;
1091 			rev_end = exit_standout_mode;
1092 		} else
1093 			rev_out = rev_end = "";
1094 	} else
1095 		rev_out = rev_end = "";
1096 	columns--;	/* avoid cursor wraparound */
1097 }
1098 
1099 #else	/* TERMCAP */
1100 
1101 initterm()
1102 {
1103 	char *term, *cp;
1104 	static char tbuf[1024];
1105 	char is2[40];
1106 	extern char *UP;
1107 
1108 	if ((term = getenv("TERM")) == NULL) {
1109 		if (!quiet)
1110 			fprintf(stderr,
1111 				"sysline: No TERM variable in enviroment\n");
1112 		exit(1);
1113 	}
1114 	if (tgetent(tbuf, term) <= 0) {
1115 		if (!quiet)
1116 			fprintf(stderr,
1117 				"sysline: Unknown terminal type: %s\n", term);
1118 		exit(1);
1119 	}
1120 	if (!window && tgetflag("hs") <= 0) {
1121 		if (!strncmp(term, "h19", 3)) {
1122 			/* for upward compatability with h19sys */
1123 			strcpy(to_status_line,
1124 				"\033j\033x5\033x1\033Y8%+ \033o");
1125 			strcpy(from_status_line, "\033k\033y5");
1126 			strcpy(dis_status_line, "\033y1");
1127 			strcpy(rev_out, "\033p");
1128 			strcpy(rev_end, "\033q");
1129 			arrows = "\033Fhh\033G";
1130 			columns = 80;
1131 			UP = "\b";
1132 			return;
1133 		}
1134 		if (!quiet)
1135 			fprintf(stderr,
1136 				"sysline: No status capability for %s\n", term);
1137 		exit(1);
1138 	}
1139 	cp = is2;
1140 	if (tgetstr("i2", &cp) != NULL) {
1141 		/* someday tset will do this */
1142 		tputs(is2, 1, erroutc);
1143 		fflush(stdout);
1144 	}
1145 
1146 	/* the "-1" below is to avoid cursor wraparound problems */
1147 	columns = tgetnum("ws");
1148 	hasws = columns >= 0;
1149 	if (!hasws)
1150 		columns = tgetnum("co");
1151 	columns -= 1;
1152 	if (window) {
1153 		strcpy(to_status_line, "\r");
1154 		cp = dis_status_line;	/* use the clear line sequence */
1155 		*cp++ = '\r';
1156 		tgetstr("ce", &cp);
1157 		if (leftline)
1158 			strcpy(from_status_line, dis_status_line + 1);
1159 		else
1160 			strcpy(from_status_line, "");
1161 	} else {
1162 		cp = to_status_line;
1163 		tgetstr("ts", &cp);
1164 		cp = from_status_line;
1165 		tgetstr("fs", &cp);
1166 		cp = dis_status_line;
1167 		tgetstr("ds", &cp);
1168 		eslok = tgetflag("es");
1169 	}
1170 	if (eslok || window) {
1171 		cp = rev_out;
1172 		tgetstr("so", &cp);
1173 		cp = rev_end;
1174 		tgetstr("se", &cp);
1175 		cp = clr_eol;
1176 		tgetstr("ce", &cp);
1177 	} else
1178 		reverse = 0;	/* turn off reverse video */
1179 	UP = "\b";
1180 	if (!strncmp(term, "h19", 3))
1181 		arrows = "\033Fhh\033G";	/* "two tiny graphic arrows" */
1182 	else
1183 		arrows = "->";
1184 }
1185 #endif TERMINFO
1186 
1187 #ifdef RWHO
1188 char *
1189 sysrup(hp)
1190 	register struct remotehost *hp;
1191 {
1192 	char filename[100];
1193 	struct whod wd;
1194 #define WHOD_HDR_SIZE (sizeof (wd) - sizeof (wd.wd_we))
1195 	static char buffer[50];
1196 	time_t now;
1197 
1198 	/*
1199 	 * rh_file is initially 0.
1200 	 * This is ok since standard input is assumed to exist.
1201 	 */
1202 	if (hp->rh_file == 0) {
1203 		/*
1204 		 * Try rwho hostname file, and if that fails try ucbhostname.
1205 		 */
1206 		(void) strcpy1(strcpy1(filename, _PATH_RWHO), hp->rh_host);
1207 		if ((hp->rh_file = open(filename, 0)) < 0) {
1208 			(void) strcpy1(strcpy1(strcpy1(filename, _PATH_RWHO),
1209 				NETPREFIX), hp->rh_host);
1210 			hp->rh_file = open(filename, 0);
1211 		}
1212 	}
1213 	if (hp->rh_file < 0) {
1214 		(void) sprintf(buffer, "%s?", hp->rh_host);
1215 		return(buffer);
1216 	}
1217 	(void) lseek(hp->rh_file, (off_t)0, 0);
1218 	if (read(hp->rh_file, (char *)&wd, WHOD_HDR_SIZE) != WHOD_HDR_SIZE) {
1219 		(void) sprintf(buffer, "%s ?", hp->rh_host);
1220 		return(buffer);
1221 	}
1222 	(void) time(&now);
1223 	if (now - wd.wd_recvtime > DOWN_THRESHOLD) {
1224 		long interval;
1225 		long days, hours, minutes;
1226 
1227 		interval = now - wd.wd_recvtime;
1228 		minutes = (interval + 59) / 60;	/* round to minutes */
1229 		hours = minutes / 60;		/* extract hours from minutes */
1230 		minutes %= 60;			/* remove hours from minutes */
1231 		days = hours / 24;		/* extract days from hours */
1232 		hours %= 24;			/* remove days from hours */
1233 		if (days > 7 || days < 0)
1234 			(void) sprintf(buffer, "%s down", hp->rh_host);
1235 		else if (days > 0)
1236 			(void) sprintf(buffer, "%s %d+%d:%02d",
1237 				hp->rh_host, days, hours, minutes);
1238 		else
1239 			(void) sprintf(buffer, "%s %d:%02d",
1240 				hp->rh_host, hours, minutes);
1241 	} else
1242 		(void) sprintf(buffer, "%s %.1f",
1243 			hp->rh_host, wd.wd_loadav[0]/100.0);
1244 	return buffer;
1245 }
1246 #endif RWHO
1247 
1248 getwinsize()
1249 {
1250 #ifdef TIOCGWINSZ
1251 	struct winsize winsize;
1252 
1253 	/* the "-1" below is to avoid cursor wraparound problems */
1254 	if (!hasws && ioctl(2, TIOCGWINSZ, (char *)&winsize) >= 0 &&
1255 		winsize.ws_col != 0)
1256 		columns = winsize.ws_col - 1;
1257 #endif
1258 }
1259 
1260 #ifdef SIGWINCH
1261 void
1262 sigwinch()
1263 {
1264 	winchanged++;
1265 }
1266 #endif
1267 
1268 void
1269 sigexit()
1270 {
1271 	exit(1);
1272 }
1273 
1274 char *
1275 strcpy1(p, q)
1276 	register char *p, *q;
1277 {
1278 
1279 	while (*p++ = *q++)
1280 		;
1281 	return p - 1;
1282 }
1283 
1284 outc(c)
1285 	char c;
1286 {
1287 	if (dbug)
1288 		printf("%s", unctrl(c));
1289 	else
1290 		putchar(c);
1291 }
1292 
1293 erroutc(c)
1294 	char c;
1295 {
1296 	if (dbug)
1297 		fprintf(stderr, "%s", unctrl(c));
1298 	else
1299 		putc(c, stderr);
1300 }
1301