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