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