* Copyright (c) 1980 Regents of the University of California.
* All rights reserved. The Berkeley software License Agreement
* specifies the terms and conditions for redistribution.
"@(#) Copyright (c) 1980 Regents of the University of California.\n\
static char sccsid
[] = "@(#)sysline.c 5.6 (Berkeley) %G%";
* sysline - system status display on 25th line of terminal
* Prints a variety of information on the special status line of terminals
* that have a status display capability. Cursor motions, status commands,
* etc. are gleamed from /etc/termcap.
* By default, all information is printed, and flags are given on the command
* line to disable the printing of information. The information and
* load average and change in load average in the last 5 mins
* number of user logged on
* -p # of processes the users owns which are runnable and the
* number which are suspended. Processes whose parent is 1
* -l users who've logged on and off.
* -m summarize new mail which has arrived
* -r use non reverse video
* -c turn off 25th line for 5 seconds before redisplaying.
* -b beep once one the half hour, twice on the hour
* +N refresh display every N seconds.
* -i print pid first thing
* -e do simple print designed for an emacs buffer line
* -w do the right things for a window
* -h print hostname between time and load average
* -D print day/date before time of day
* -d debug mode - print status line data in human readable format
* -q quiet mode - don't output diagnostic messages
* -s print Short (left-justified) line if escapes not allowed
* -j Print left Justified line regardless
#define BSD4_2 /* for 4.2 BSD */
#define WHO /* turn this on always */
#define HOSTNAME /* 4.1a or greater, with hostname() */
#define RWHO /* 4.1a or greater, with rwho */
#define VMUNIX /* turn this on if you are running on vmunix */
#define NEW_BOOTTIME /* 4.1c or greater */
#define DEFDELAY 60 /* update status once per minute */
#define MAILDIR "/usr/spool/mail"
* if MAXLOAD is defined, then if the load average exceeded MAXLOAD
* then the process table will not be scanned and the log in/out data
* will not be checked. The purpose of this is to reduced the load
* on the system when it is loaded.
#include <protocols/rwhod.h>
#define DOWN_THRESHOLD (11 * 60)
#define RWHOLEADER "/usr/spool/rwho/whod."
{ "_boottime" }, /* After 4.1a the label changed to "boottime" */
{ "_bootime" }, /* Under 4.1a and earlier it is "bootime" */
/* stuff for the kernel */
int kmem
; /* file descriptor for /dev/kmem */
struct proc
*proc
, *procNPROC
;
double avenrun
[3]; /* used for storing load averages */
* In order to determine how many people are logged on and who has
* logged in or out, we read in the /etc/utmp file. We also keep track of
* the previous utmp file.
int ut
= -1; /* the file descriptor */
char *status
; /* per tty status bits, see below */
int nentries
; /* number of utmp entries */
/* string lengths for printing */
#define LINESIZE (sizeof old->ut_line)
#define NAMESIZE (sizeof old->ut_name)
* Status codes to say what has happened to a particular entry in utmp.
* NOCH means no change, ON means new person logged on,
* OFF means person logged off.
char hostname
[MAXHOSTNAMELEN
+1]; /* one more for null termination */
char lockfilename
[100]; /* if exists, will prevent us from running */
/* flags which determine which info is printed */
int mailcheck
= 1; /* m - do biff like checking of mail */
int proccheck
= 1; /* p - give information on processes */
int logcheck
= 1; /* l - tell who logs in and out */
int hostprint
= 0; /* h - print out hostname */
int dateprint
= 0; /* h - print out day/date */
int quiet
= 0; /* q - hush diagnostic messages */
/* flags which determine how things are printed */
int clr_bet_ref
= 0; /* c - clear line between refeshes */
int reverse
= 1; /* r - use reverse video */
int shortline
= 0; /* s - short (left-justified) if escapes not allowed */
int leftline
= 0; /* j - left-justified even if escapes allowed */
/* flags which have terminal do random things */
int beep
= 0; /* b - beep every half hour and twice every hour */
int printid
= 0; /* i - print pid of this process at startup */
int synch
= 1; /* synchronize with clock */
/* select output device (status display or straight output) */
int emacs
= 0; /* e - assume status display */
int window
= 0; /* w - window mode */
int dbug
= 0; /* d - debug */
* used to turn off reverse video every REVOFF times
* in an attempt to not wear out the phospher.
/* used by mail checker */
off_t linebeg
= 0; /* place where we last left off reading */
/* things used by the string routines */
int chars
; /* number of printable characters */
char strarr
[512]; /* big enough now? */
/* flags to stringdump() */
char sawmail
; /* remember mail was seen to print bells */
char mustclear
; /* status line messed up */
/* strings which control status line display */
char *rev_out
, *rev_end
, *arrows
;
char from_status_line
[64];
char dis_status_line
[64];
char rev_out
[20], rev_end
[20];
char *arrows
, *bell
= "\007";
int eslok
; /* escapes on status line okay (reverse, cursor addressing) */
#define tparm(cap, parm) tgoto((cap), 0, (parm))
/* to deal with window size changes */
char winchanged
; /* window size has changed since last update */
char *ourtty
; /* keep track of what tty we're on */
struct stat stbuf
, mstbuf
; /* mstbuf for mail check only */
unsigned delay
= DEFDELAY
;
double loadavg
= 0.0; /* current load average */
gethostname(hostname
, sizeof hostname
- 1);
if ((cp
= index(hostname
, '.')) != NULL
)
for (argv
++; *argv
!= 0; argv
++)
for (cp
= *argv
+ 1; *cp
; cp
++) {
case 'r' : /* turn off reverse video */
if (strcmp(hostname
, *argv
) &&
strcmp(&hostname
[sizeof NETPREFIX
- 1], *argv
))
remotehost
[nremotes
++].rh_host
= *argv
;
"sysline: bad flag: %c\n", *cp
);
synch
= 0; /* no more sync */
fprintf(stderr
, "sysline: illegal argument %s\n",
} else /* if not to emacs window, initialize terminal dependent info */
* When the window size changes and we are the foreground
* process (true if -w), we get this signal.
signal(SIGWINCH
, sigwinch
);
getwinsize(); /* get window size from ioctl */
/* immediately fork and let the parent die if not emacs mode */
if (!emacs
&& !window
&& !dbug
) {
/* pgrp should take care of things, but ignore them anyway */
signal(SIGQUIT
, SIG_IGN
);
signal(SIGTTOU
, SIG_IGN
);
* When we logoff, init will do a "vhangup()" on this
* tty which turns off I/O access and sends a SIGHUP
* signal. We catch this and thereby clear the status
* display. Note that a bug in 4.1bsd caused the SIGHUP
* signal to be sent to the wrong process, so you had to
* `kill -HUP' yourself in your .logout file.
* Do the same thing for SIGTERM, which is the default kill
signal(SIGHUP
, clearbotl
);
signal(SIGTERM
, clearbotl
);
* This is so kill -ALRM to force update won't screw us up..
signal(SIGALRM
, SIG_IGN
);
ourtty
= ttyname(2); /* remember what tty we are on */
printf("%d\n", getpid());
if ((home
= getenv("HOME")) == 0)
strcpy1(strcpy1(whofilename
, home
), "/.who");
strcpy1(strcpy1(whofilename2
, home
), "/.sysline");
strcpy1(strcpy1(lockfilename
, home
), "/.syslinelock");
if ((kmem
= open("/dev/kmem",0)) < 0) {
fprintf(stderr
, "Can't open kmem.\n");
if ((username
= getenv("USER")) == 0)
if (stat(username
, &mstbuf
) >= 0)
mailsize
= mstbuf
.st_size
;
while (emacs
|| window
|| isloggedin())
if (access(lockfilename
, 0) >= 0)
tputs(dis_status_line
, 1, outc
);
revtime
= (1 + revtime
) % REVOFF
;
* you can tell if a person has logged out if the owner of
return fstat(2, &statbuf
) == 0 && statbuf
.st_uid
== uid
;
time_t bootime
, clock
, nintv
, time();
if (nl
[0].n_value
== 0) {
fprintf(stderr
, "No namelist\n");
lseek(kmem
, (long)nl
[NL_BOOT
].n_value
, 0);
read(kmem
, &bootime
, sizeof(bootime
));
if (nintv
<= 0L || nintv
> 60L*60L*24L*365L) {
"Time makes no sense... namelist must be wrong\n");
nl
[NL_PROC
].n_value
= nl
[NL_AVEN
].n_value
= 0;
static time_t lastmod
; /* initially zero */
static off_t utmpsize
; /* ditto */
if (ut
< 0 && (ut
= open("/etc/utmp", 0)) < 0) {
fprintf(stderr
, "sysline: Can't open utmp.\n");
if (fstat(ut
, &st
) < 0 || st
.st_mtime
== lastmod
)
if (utmpsize
!= st
.st_size
) {
nentries
= utmpsize
/ sizeof (struct utmp
);
old
= (struct utmp
*)calloc(utmpsize
, 1);
new = (struct utmp
*)calloc(utmpsize
, 1);
old
= (struct utmp
*)realloc((char *)old
, utmpsize
);
new = (struct utmp
*)realloc((char *)new, utmpsize
);
status
= malloc(nentries
* sizeof *status
);
if (old
== 0 || new == 0 || status
== 0) {
fprintf(stderr
, "sysline: Out of memory.\n");
(void) read(ut
, (char *) (nflag
? new : old
), utmpsize
);
* read in the process table locations and sizes, and allocate space
* for storing the process table. This is done only once.
if (nl
[NL_PROC
].n_value
== 0)
lseek(kmem
, (long)nl
[NL_PROC
].n_value
, 0);
read(kmem
, &procadr
, sizeof procadr
);
lseek(kmem
, (long)nl
[NL_NPROC
].n_value
, 0);
read(kmem
, &nproc
, sizeof nproc
);
procadr
= nl
[NL_PROC
].n_value
;
nproc
= NPROC
; /* from param.h */
if ((proc
= (struct proc
*) calloc(nproc
, sizeof (struct proc
))) == 0) {
fprintf(stderr
, "Out of memory.\n");
procNPROC
= proc
+ nproc
;
* read in the process table. This assumes that initprocread has alread been
* called to set up storage.
if (nl
[NL_PROC
].n_value
== 0)
lseek(kmem
, (long)procadr
, 0);
read(kmem
, (char *)proc
, nproc
* sizeof (struct proc
));
/* check for file named .who in the home directory */
* if mail is seen, don't print rest of info, just the mail
* reverse new and old so that next time we run, we won't lose log
if (mailcheck
&& (sawmail
= mailseen()))
for (i
= 0; i
< nremotes
; i
++) {
tmp
= sysrup(remotehost
+ i
);
stringcat(tmp
, strlen(tmp
));
* print hostname info if requested
* print load average and difference between current load average
* and the load average 5 minutes ago
if (nl
[NL_AVEN
].n_value
!= 0) {
lseek(kmem
, (long)nl
[NL_AVEN
].n_value
, 0);
read(kmem
, avenrun
, sizeof avenrun
);
if ((diff
= avenrun
[0] - avenrun
[1]) < 0.0)
stringprt("%.1f %.1f", avenrun
[0], diff
);
stringprt("%.1f +%.1f", avenrun
[0], diff
);
loadavg
= avenrun
[0]; /* remember load average */
* print log on and off information
fullprocess
= 0; /* too loaded to run */
* Read utmp file (logged in data) only if we are doing a full
* process, or if this is the first time and we are calculating
if (users
== 0) { /* first time */
for (i
= 0; i
< nentries
; i
++)
} else if (fullprocess
&& readutmp(1)) {
for (i
= 0; i
< nentries
; i
++) {
if (strncmp(old
[i
].ut_name
,
new[i
].ut_name
, NAMESIZE
) == 0)
else if (old
[i
].ut_name
[0] == '\0') {
} else if (new[i
].ut_name
[0] == '\0') {
* 3. a - if load is too high
* 4. number of processes running and stopped
if (mailsize
> 0 && mstbuf
.st_mtime
>= mstbuf
.st_atime
)
if (!fullprocess
&& (proccheck
|| logcheck
))
if (fullprocess
&& proccheck
&& readproctab()) {
* We are only interested in processes which have the same
* uid as us, and whose parent process id is not 1.
for (p
= proc
; p
< procNPROC
; p
++) {
if (p
->p_stat
== 0 || p
->p_pgrp
== 0 ||
p
->p_uid
!= uid
|| p
->p_ppid
== 1)
* Sleep can mean waiting for a signal or just
* in a disk or page wait queue ready to run.
* We can tell if it is the later by the pri
if (procrun
> 0 || procstop
> 0) {
if (procrun
> 0 && procstop
> 0)
stringprt("%dr %ds", procrun
, procstop
);
stringprt("%dr", procrun
);
stringprt("%ds", procstop
);
* If anyone has logged on or off, and we are interested in it,
/* old and new have already been swapped */
for (i
= 0; i
< nentries
; i
++)
stringprt(" %.8s", old
[i
].ut_name
);
ttyprint(old
[i
].ut_line
);
for (i
= 0; i
< nentries
; i
++)
stringprt(" %.8s", new[i
].ut_name
);
ttyprint(new[i
].ut_line
);
/* dump out what we know */
struct tm
*tp
, *localtime();
tp
= localtime(&curtime
);
stringprt("%.11s", ctime(&curtime
));
stringprt("%d:%02d", tp
->tm_hour
> 12 ? tp
->tm_hour
- 12 :
(tp
->tm_hour
== 0 ? 12 : tp
->tm_hour
), tp
->tm_min
);
if (synch
) /* sync with clock */
* Beepable is used to insure that we get at most one set of beeps
} else if (tp
->tm_min
== 0) {
if (tp
->tm_min
!= 0 && tp
->tm_min
!= 30)
* whocheck -- check for file named .who and print it on the who line first
if ((whofile
= open(whofilename
, 0)) < 0 &&
(whofile
= open(whofilename2
, 0)) < 0)
chss
= read(whofile
, buff
, sizeof buff
- 1);
* Remove all line feeds, and replace by spaces if they are within
* the message, else replace them by nulls.
stringcat(buff
, p
- buff
);
* ttyprint -- given the name of a tty, print in the string buffer its
* short name surrounded by parenthesis.
* ttyxx is printed as (xx)
* console is printed as (cty)
if (strncmp(name
, "tty", 3) == 0)
stringprt("(%.*s)", LINESIZE
- 3, name
+ 3);
else if (strcmp(name
, "console") == 0)
stringprt("(%.*s)", LINESIZE
, name
);
* returns 0 if no mail seen
char lbuf
[100], sendbuf
[100], *bufend
;
if (stat(username
, &mstbuf
) < 0) {
if (mstbuf
.st_size
<= mailsize
|| (mfd
= fopen(username
,"r")) == NULL
) {
mailsize
= mstbuf
.st_size
;
while ((n
= readline(mfd
, lbuf
, sizeof lbuf
)) >= 0 &&
strncmp(lbuf
, "From ", 5) != 0)
stringcat("Mail has just arrived", 0);
* Found a From line, get second word, which is the sender,
for (cp
= lbuf
+ 5; *cp
&& *cp
!= ' '; cp
++) /* skip to blank */
*cp
= '\0'; /* terminate name */
stringprt("Mail from %s ", lbuf
+ 5);
* Print subject, and skip over header.
while ((n
= readline(mfd
, lbuf
, sizeof lbuf
)) > 0)
if (strncmp(lbuf
, "Subject:", 8) == 0)
stringprt("on %s ", lbuf
+ 9);
if (n
< 0) /* already at eof */
* Print as much of the letter as we can.
if ((n
= columns
- chars
) > sizeof sendbuf
- 1)
while ((n
= readline(mfd
, lbuf
, sizeof lbuf
)) >= 0) {
if (strncmp(lbuf
, "From ", 5) == 0)
*cp
++ = ' '; /* space before lines */
while (*rp
&& cp
< bufend
)
* Want to update write time so a star will
* appear after the number of users until the
* readline -- read a line from fp and store it in buf.
* return the number of characters read.
linebeg
= ftell(fp
); /* remember loc where line begins */
while (--n
> 0 && (c
= getc(fp
)) != EOF
&& c
!= '\n')
if (c
== EOF
&& cp
- buf
== 0)
* string hacking functions
stringprt(format
, a
, b
, c
)
stringcat(sprintf(tempbuf
, format
, a
, b
, c
), -1);
char bigbuf
[sizeof strarr
+ 200];
register char *bp
= bigbuf
;
bp
= strcpy1(bp
, tparm(to_status_line
,
leftline
? 0 : columns
- chars
));
bp
= strcpy1(bp
, to_status_line
);
if (!shortline
&& !leftline
)
for (i
= columns
- chars
; --i
>= 0;)
if (reverse
&& revtime
!= 0)
bp
= strcpy1(bp
, rev_out
);
bp
= strcpy1(bp
, strarr
);
bp
= strcpy1(bp
, rev_end
);
bp
= strcpy1(bp
, from_status_line
);
bp
= strcpy1(strcpy1(bp
, bell
), bell
);
write(2, bigbuf
, bp
- bigbuf
);
if (reverse
&& revtime
!= 0) {
magic_cookie_glitch
<= 0 ? 0 : magic_cookie_glitch
);
magic_cookie_glitch
<= 0 ? 0 : magic_cookie_glitch
);
* stringcat :: concatenate the characters in string str to the list we are
* str - the string to print. may contain funny (terminal control) chars.
* n - the number of printable characters in the string
* or if -1 then str is all printable so we can truncate it,
* otherwise don't print only half a string.
if (n
< 0) { /* truncate */
while ((*p
++ = *str
++) && --n
>= 0)
} else if (chars
+ n
<= columns
) { /* don't truncate */
* touch :: update the modify time of a file.
char *name
; /* name of file */
if ((fd
= open(name
, 2)) >= 0) {
read(fd
, &buf
, 1); /* get first byte */
lseek(fd
, 0L, 0); /* go to beginning */
write(fd
, &buf
, 1); /* and rewrite first byte */
* clearbotl :: clear bottom line.
* called when process quits or is killed.
* it clears the bottom line of the terminal.
alarm(30); /* if can't open in 30 secs, just die */
if (!emacs
&& (fd
= open(ourtty
, 1)) >= 0) {
write(fd
, dis_status_line
, strlen(dis_status_line
));
if (chdir("/usr/src/ucb/sysline") < 0)
static char standbuf
[40];
if (!window
&& !has_status_line
) {
/* not an appropriate terminal */
fprintf(stderr
, "sysline: no status capability for %s\n",
if (window
|| status_line_esc_ok
) {
tparm(set_attributes
,0,0,1,0,0,0,0,0,0));
rev_end
= exit_attribute_mode
;
} else if (enter_standout_mode
&& exit_standout_mode
) {
rev_out
= enter_standout_mode
;
rev_end
= exit_standout_mode
;
columns
--; /* avoid cursor wraparound */
if ((term
= getenv("TERM")) == NULL
) {
"sysline: No TERM variable in enviroment\n");
if (tgetent(tbuf
, term
) <= 0) {
"sysline: Unknown terminal type: %s\n", term
);
if (!window
&& tgetflag("hs") <= 0) {
if (!strncmp(term
, "h19", 3)) {
/* for upward compatability with h19sys */
"\033j\033x5\033x1\033Y8%+ \033o");
strcpy(from_status_line
, "\033k\033y5");
strcpy(dis_status_line
, "\033y1");
strcpy(rev_out
, "\033p");
strcpy(rev_end
, "\033q");
"sysline: No status capability for %s\n", term
);
if (tgetstr("i2", &cp
) != NULL
) {
/* someday tset will do this */
/* the "-1" below is to avoid cursor wraparound problems */
columns
= tgetnum("co") - 1;
strcpy(to_status_line
, "\r");
cp
= dis_status_line
; /* use the clear line sequence */
strcpy(from_status_line
, dis_status_line
+ 1);
strcpy(from_status_line
, "");
reverse
= 0; /* turn off reverse video */
if (!strncmp(term
, "h19", 3))
arrows
= "\033Fhh\033G"; /* "two tiny graphic arrows" */
lseek(kmem
, (long)nl
[NL_AVEN
].n_value
, 0);
read(kmem
, s_avenrun
, sizeof(s_avenrun
));
for (i
=0; i
< (sizeof(s_avenrun
)/sizeof(s_avenrun
[0])); i
++)
ap
[i
] = s_avenrun
[i
] / 256.0;
register struct remotehost
*hp
;
#define WHOD_HDR_SIZE (sizeof (wd) - sizeof (wd.wd_we))
* rh_file is initially 0.
* This is ok since standard input is assumed to exist.
* Try rwho hostname file, and if that fails try ucbhostname.
(void) strcpy1(strcpy1(filename
, RWHOLEADER
), hp
->rh_host
);
if ((hp
->rh_file
= open(filename
, 0)) < 0) {
(void) strcpy1(strcpy1(strcpy1(filename
, RWHOLEADER
),
NETPREFIX
), hp
->rh_host
);
hp
->rh_file
= open(filename
, 0);
return sprintf(buffer
, "%s?", hp
->rh_host
);
(void) lseek(hp
->rh_file
, (off_t
)0, 0);
if (read(hp
->rh_file
, (char *)&wd
, WHOD_HDR_SIZE
) != WHOD_HDR_SIZE
)
return sprintf(buffer
, "%s ?", hp
->rh_host
);
if (now
- wd
.wd_recvtime
> DOWN_THRESHOLD
) {
long days
, hours
, minutes
;
interval
= now
- wd
.wd_recvtime
;
minutes
= (interval
+ 59) / 60; /* round to minutes */
hours
= minutes
/ 60; /* extract hours from minutes */
minutes
%= 60; /* remove hours from minutes */
days
= hours
/ 24; /* extract days from hours */
hours
%= 24; /* remove days from hours */
if (days
> 7 || days
< 0)
(void) sprintf(buffer
, "%s down", hp
->rh_host
);
(void) sprintf(buffer
, "%s %d+%d:%02d",
hp
->rh_host
, days
, hours
, minutes
);
(void) sprintf(buffer
, "%s %d:%02d",
hp
->rh_host
, hours
, minutes
);
(void) sprintf(buffer
, "%s %.1f",
hp
->rh_host
, wd
.wd_loadav
[0]/100.0);
/* the "-1" below is to avoid cursor wraparound problems */
if (ioctl(2, TIOCGWINSZ
, (char *)&winsize
) >= 0 && winsize
.ws_col
!= 0)
columns
= winsize
.ws_col
- 1;
fprintf(stderr
, "%s", unctrl(c
));