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