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