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