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