[comp.sources.misc] v07i122: si -- display user process trees

allbery@uunet.UU.NET (Brandon S. Allbery - comp.sources.misc) (08/07/89)

Posting-number: Volume 7, Issue 122
Submitted-by: allbery@hal.CWRU.Edu@nc386.UUCP
Archive-name: si

As one of the folks in charge of NCoast.ORG, I often find it useful to keep
an eye on what people are doing on the system.  This program makes it easy
by displaying genealogical process trees.

++Brandon
#--------------------------------CUT HERE-------------------------------------
#! /bin/sh
#
# This is a shell archive.  Save this into a file, edit it
# and delete all lines above this comment.  Then give this
# file to sh by executing the command "sh file".  The files
# will be extracted into the current directory owned by
# you with default permissions.
#
# The files contained herein are:
#
# -rw-r--r--   1 allbery  20           780 Aug  6 14:53 README
# -rw-r--r--   1 allbery  20         11187 Aug  6 14:06 si.c
# -rw-r--r--   1 allbery  20          2860 Aug  6 14:45 si.man
#
echo 'x - README'
if test -f README; then echo 'shar: not overwriting README'; else
sed 's/^X//' << '________This_Is_The_END________' > README
XThis program displays process trees for processes other than the kernel
Xand init.  The name is historical; the first version did considerably
Xmore than this one does, but subsequent versions were under System III
Xand much of the information was unavailable or inaccessible.
X
XTo compile:  cc -o si si.c -ltinfo -lx
X	     chown sysinfo si
X	     chmod 4711 si
X(you need root permissions to chown and chgrp the program)
X
XTo run:  si [ -m ]
X
XUse -m to get idle time calculations like SCO's version of w; the default is
Xthe idle time calculation used by who and finger.  (I wish more programs
Xallowed the user to choose!)
X
XThe current version only works under SCO Xenix 386, but should be portable to
XSystem III, System V R2/R3, and Xenix 3/5 systems with a little tweaking.
X
X++Brandon
________This_Is_The_END________
if test `wc -c < README` -ne 780; then
	echo 'shar: README was damaged during transit (should have been 780 bytes)'
fi
fi		; : end of overwriting check
echo 'x - si.c'
if test -f si.c; then echo 'shar: not overwriting si.c'; else
sed 's/^X//' << '________This_Is_The_END________' > si.c
X/*
X * System process and user information
X *
X * Slurp the entire process table into memory (discarding system processes)
X * and sort it by process tree and tty.  Then present the information after
X * the fashion of the old "si", using modified strings to represent NLI ttys
X * and null ttys.
X *
X * This version is for SCO Xenix 386.  The original ran under Plexus System III
X * on a Plexus P/35 (68000 Unix).
X */
X
X#define M_TERMINFO		/* d*mn SCO, anyway */
X
X#include <curses.h>
X#include <term.h>
X#include <sys/types.h>
X#include <sys/param.h>
X#include <sys/sysmacros.h>
X#include <sys/seg.h>
X#include <sys/page.h>
X#include <sys/proc.h>
X#include <utmp.h>
X#include <sys/stat.h>
X#include <a.out.h>
X#include <time.h>
X#include <signal.h>
X#include <sys/var.h>
X#include <sys/dir.h>
X#include <sys/user.h>
X#include <sys/tty.h>
X#include <pwd.h>
X#include <grp.h>
X
X#define UTMP		"/etc/utmp"
X#define DEV		"/dev/"
X#define KERNEL		"/xenix"
X#define KMEM		"/dev/kmem"
X#define PMEM		"/dev/mem"
X#define SMEM		"/dev/swap"
X
Xint kfd;
Xint mfd;
Xint sfd;
Xint utmp;
Xint dfd;
Xint idlef;
Xlong nproc;
Xdaddr_t swplo;
X
Xstruct nlist kernel[] =
X{
X    {"_v"},
X    {"_proc"},
X    {"_swplo"},
X    {NULL},
X};
X
Xstruct tm *localtime();
Xstruct passwd *getpwuid(), *getpwnam();
Xstruct group *getgrgid();
Xchar *strrchr(), *malloc();
X
X#define DAY		(60L*60L*24)
X#define HOUR		(60L*60L)
X#define MIN		(60L)
X
X#define static
X
Xstatic void endw(), pcmd();
Xstatic int die(), paint(), grandparent(), pgsort(), rgetc(), showtty();
Xstatic struct proc *ppid();
X
Xstatic
Xvoid
Xendw()
X{
X    move(LINES - 1, 0);
X    clrtoeol();
X    refresh();
X    endwin();
X}
X
Xint dying = 0;
Xint repaint = 0;
X
Xstatic
Xint
Xdie(s)
X{
X    signal(s, die);
X    dying = 1;
X}
X
Xstatic
Xint
Xpaint(s)
X{
X    signal(s, paint);
X    repaint = 1;
X}
X
Xstruct proc *proctab;
Xint numproc;
X
X/*
X * find the ultimate parent of a tree of processes
X */
X
Xstatic
Xstruct proc *
Xppid(p)
X    register struct proc *p;
X{
X    register struct proc *pp;
X
X    if (p->p_ppid == 1)
X	return p;
X    for (;;) {
X	for (pp = proctab; pp < &proctab[numproc]; pp++)
X	    if (pp->p_pid == p->p_ppid)
X		break;
X	if (pp == &proctab[numproc])
X	    return 0;
X	if (pp->p_ppid == 1)
X	    return pp;
X	p = pp;
X    }
X}
X
X/*
X * similar to ppid(), but stop when a parent process is the other specified
X * process; return 1 if it's found, 0 if not
X */
X
Xstatic
Xint
Xgrandparent(p, gp)
X    register struct proc *p, *gp;
X{
X    register struct proc *pp;
X
X    if (p->p_ppid < 2)
X	return 0;
X    for (;;) {
X	for (pp = proctab; pp < &proctab[numproc]; pp++)
X	    if (pp->p_pid == p->p_ppid)
X		break;
X	if (pp == &proctab[numproc])
X	    return 0;
X	if (pp == gp)
X	    return 1;
X	if (pp->p_ppid < 2)
X	    return 0;
X	p = pp;
X    }
X}
X
X/*
X * sort function for qsort() of process table
X * order by process group, then parent of "tree" of processes within group,
X * then relative hierarchy within "tree".
X * if tree parent's pid is same as pgrp, always sorts before other parents
X */
X
Xstatic
Xint
Xpgsort(p1, p2)
X    register struct proc *p1, *p2;
X{
X    register struct proc *p1p, *p2p;
X
X    /* sort by process group first... */
X    if (p1->p_pgrp != p2->p_pgrp)
X	return p1->p_pgrp - p2->p_pgrp;
X    /* sort by great^n-grandparent */
X    if ((p1p = ppid(p1)) != (p2p = ppid(p2))) {
X	if (p1p->p_pgrp == p1p->p_pid)
X	    return -1;
X	if (p2p->p_pgrp == p2p->p_pid)
X	    return 1;
X	return p1p->p_pid - p2p->p_pid;
X    }
X    /* great^n-grandparent comes first, always */
X    if (p1->p_ppid < 2)
X	return -1;
X    if (p2->p_ppid < 2)
X	return 1;
X    /* follow hierarchy up searching for common ancestry */
X    if (grandparent(p1, p2))
X	return 1;
X    if (grandparent(p2, p1))
X	return -1;
X    /* this shouldn't happen... */
X    return 0;
X}
X
Xint
Xmain(argc, argv)
X    char **argv;
X{
X    struct var vars;
X    register struct proc *scanp, *insp;
X    int depth, ptr, pgrp, ruid, rgid;
X    int stack[20];
X    struct user *u;
X    struct passwd *pw;
X    struct group *gr;
X
X    if (argc == 2)
X	idlef = (strcmp(argv[1], "-m") == 0);
X    signal(SIGHUP, die);
X    signal(SIGQUIT, die);
X    signal(SIGINT, paint);
X    signal(SIGTERM, die);
X    initscr();
X    cbreak();
X    noecho();
X    nl();
X    if (!cursor_address || !*cursor_address)
X    {
X	endw();
X	fprintf(stderr, "I can't use your terminal.\n");
X	exit(1);
X    }
X    if (nlist(KERNEL, kernel) == -1)
X    {
X	endw();
X	perror(KERNEL);
X	exit(2);
X    }
X    if ((utmp = open(UTMP, 0)) == -1)
X    {
X	endw();
X	perror(UTMP);
X	exit(1);
X    }
X    if ((kfd = open(KMEM, 0)) == -1)
X    {
X	endw();
X	perror(KMEM);
X	exit(3);
X    }
X    lseek(kfd, kernel[0].n_value, 0);
X    read(kfd, &vars, sizeof vars);
X    nproc = vars.v_proc;
X    lseek(kfd, kernel[2].n_value, 0);
X    read(kfd, &swplo, sizeof swplo);
X    if ((mfd = open(PMEM, 0)) == -1)
X    {
X	endw();
X	perror(PMEM);
X	exit(4);
X    }
X    if ((sfd = open(SMEM, 0)) == -1)
X    {
X	endw();
X	perror(SMEM);
X	exit(5);
X    }
X    if ((dfd = open(DEV, 0)) == -1)
X    {
X	endw();
X	perror(DEV);
X	exit(6);
X    }
X    if ((proctab = (struct proc *) malloc(nproc * sizeof *proctab)) == 0)
X    {
X	endw();
X	fprintf(stderr, "Out of memory\n");
X	exit(6);
X    }
X    for (;;)
X    {
X	/* handle pending signals */
X	if (dying)
X	{
X	    endw();
X	    exit(0);
X	}
X	if (repaint)
X	{
X	    repaint = 0;
X	    clear();
X	}
X	/* prepare for output */
X	erase();
X	/* gobble proc */
X	lseek(kfd, (long) kernel[1].n_value, 0);
X	read(kfd, proctab, nproc * sizeof *proctab);
X	numproc = nproc;
X	/* zero out swapper and init */
X	memset(proctab, 0, 2 * sizeof *proctab);
X	insp = proctab;
X	/* prune proctab[] (and add user structures) */
X	for (scanp = proctab; scanp < &proctab[nproc]; scanp++)
X	{
X	    switch (scanp->p_stat)
X	    {
X	    case 0:
X	    case SWAIT:
X	    case SIDL:
X	    case SZOMB:
X		scanp->p_stat = 0;
X		numproc--;
X		continue;
X	    default:
X		while (insp->p_stat != 0 && insp != scanp)
X		    insp++;
X		if (insp != scanp)
X		{
X		    memcpy(insp, scanp, sizeof *scanp);
X		    scanp->p_stat = 0;
X		}
X		if (!(insp->p_wchan = malloc(sizeof (struct user))))
X		{
X		    endw();
X		    fprintf(stderr, "Out of memory\n");
X		    exit(8);
X		}
X		if (insp->p_flag & SLOAD)
X		{
X		    lseek(mfd, insp->p_addr[0].te_frameno * NBPC, 0);
X		    read(mfd, insp->p_wchan, sizeof (struct user));
X		}
X		else
X		{
X		    lseek(sfd, insp->p_addr[0].te_frameno * NBPC, 0);
X		    read(sfd, insp->p_wchan, sizeof (struct user));
X		}
X		/* zap gettys, we could care less */
X		if (isgetty(insp))
X		{
X		    insp->p_stat = 0;
X		    free(insp->p_wchan);
X		    scanp->p_stat = 0;
X		    numproc--;
X		}
X	    }
X	}
X	/* sort proctab[] by process group */
X	qsort(proctab, numproc, sizeof *proctab, pgsort);
X	/* print proctab[] */
X	pgrp = -1;
X	for (scanp = proctab; scanp < &proctab[numproc]; scanp++)
X	{
X	    u = (struct user *) scanp->p_wchan;
X	    ((struct user *) scanp->p_wchan)->u_procp = scanp;
X	    if (scanp->p_pgrp != pgrp)
X	    {
X		if (pgrp != -1)
X		    addch('\n');
X		pgrp = scanp->p_pgrp;
X		if (!u->u_ttyp)
X		{
X		    addstr("(no tty)");
X		    move(stdscr->_cury, 40);
X		    addstr("Process group detached from terminal\n");
X		    ruid = u->u_ruid;
X		    rgid = u->u_rgid;
X		}
X		else if (!showtty(scanp, u, &ruid, &rgid))
X		{
X		    ruid = u->u_ruid;
X		    rgid = u->u_rgid;
X		    move(stdscr->_cury, 40);
X		    addstr("Process group orphaned\n");
X		}
X		depth = -1;
X	    }
X	    for (ptr = depth; ptr >= 0; ptr--)
X		if (scanp->p_ppid == stack[ptr])
X		    break;
X	    stack[depth = ++ptr] = scanp->p_pid;
X	    printw("  %5d ", scanp->p_pid);
X	    if (u->u_uid == ruid)
X		addstr("         ");
X	    else
X	    {
X		setpwent();
X		if (pw = getpwuid(u->u_uid))
X		    printw("%-8.8s ", pw->pw_name);
X		else
X		    printw("%-8d ", u->u_uid);
X	    }
X	    if (u->u_gid == rgid)
X		addstr("         ");
X	    else
X	    {
X		setgrent();
X		if (gr = getgrgid(u->u_gid))
X		    printw("%-8.8s ", gr->gr_name);
X		else
X		    printw("%-8d ", u->u_gid);
X	    }
X	    printw("%*s ", depth * 2, "");
X	    pcmd(u);
X	    addch('\n');
X	    free(u);
X	}
X	refresh();
X	sleep(5);
X    }
X}
X
X/*
X * print the command name and arguments (if possible) of a process
X * for xenix, this prints u.u_psargs instead of groveling in core
X */
X
Xstatic
Xvoid
Xpcmd(uinfo)
X    struct user *uinfo;
X{
X    int c, d;
X
X    for (c = PSARGSZ; c-- && uinfo->u_psargs[c] != '\0'; )
X	;
X    if (!c)
X	printw("(%.*s)", DIRSIZ, uinfo->u_comm);
X    else
X    {
X	if (c > COLS - stdscr->_curx - 1)
X	    c = COLS - stdscr->_curx - 1;
X	printw("%.*s", c, uinfo->u_psargs);
X	for (d = 0; d < c && uinfo->u_psargs[d] != ' '; d++)
X	    ;
X	uinfo->u_psargs[d] = '\0';
X	for (; d >= 0 && uinfo->u_psargs[d] != '/'; d--)
X	    ;
X	if (strncmp(uinfo->u_psargs + d + 1, uinfo->u_comm, DIRSIZ) != 0)
X	    printw(" (%.*s)", DIRSIZ, uinfo->u_comm);
X    }
X}
X
X/*
X * print tty information for process; if process owner (pgrp) isn't logged in,
X * return FALSE, else set *ruidp and *rgidp and return TRUE
X */
X
Xstatic
Xint
Xshowtty(p, u, ruidp, rgidp)
X    struct proc *p;
X    struct user *u;
X    int *ruidp, *rgidp;
X{
X    int isut;
X    struct tty t;
X    struct direct dev;
X    char *cp;
X    char ttyp[32], login[16];
X    struct stat sb;
X    struct utmp ut;
X    struct tm *tp;
X    struct passwd *pw;
X    long now;
X
X    lseek(kfd, (long) u->u_ttyp, 0);
X    read(kfd, &t, sizeof t);
X    sb.st_rdev = -1;
X    lseek(dfd, 0L, 0);
X    while (read(dfd, &dev, sizeof dev) == sizeof dev)
X    {
X	strcpy(ttyp, DEV);
X	strncat(ttyp, dev.d_name, DIRSIZ);
X	ttyp[DIRSIZ + sizeof DEV] = '\0';
X	if (stat(ttyp, &sb) == -1 || (sb.st_mode & S_IFMT) != S_IFCHR)
X	    continue;
X	if (sb.st_rdev == u->u_ttyd)
X	    break;
X    }
X    if (sb.st_rdev != u->u_ttyd)
X    {
X	printw("(cdev %d/%d not found)", major(u->u_ttyd), minor(u->u_ttyd));
X	return 0;
X    }
X    if (t.t_pgrp != p->p_pgrp)
X    {
X	printw("          %-8.8s          old", dev.d_name);
X	return 0;
X    }
X    lseek(utmp, 0L, 0);
X    while (isut = (read(utmp, &ut, sizeof ut) == sizeof ut))
X	if (ut.ut_type != USER_PROCESS)
X	    continue;
X	else if (strncmp(ut.ut_line, dev.d_name, sizeof ut.ut_line) == 0)
X	    break;
X    if (!isut || ut.ut_name[0] == '\0')
X    {
X	printw("          %-8.8s          old", dev.d_name);
X	return 0;
X    }
X    printw("%-8.8s  %-8.8s  ", ut.ut_name, dev.d_name);
X    tp = localtime(&ut.ut_time);
X    time(&now);
X    if (now - ut.ut_time >= 86400)
X	printw("  %02d/%02d", tp->tm_mon + 1, tp->tm_mday);
X    else
X	printw("  %2d:%02d", tp->tm_hour, tp->tm_min);
X    if ((now -= (idlef? sb.st_mtime: sb.st_atime)) >= 86400)
X	printw("  %3dd", now / 86400);
X    else if (now >= 3600)
X	printw("  %2d:%02d", now / 3600, (now % 3600) / 60);
X    else if (now >= 60)
X	printw("  %3d", now / 60);
X    strncpy(login, ut.ut_name, sizeof ut.ut_name);
X    login[sizeof ut.ut_name] = '\0';
X    move(stdscr->_cury, 40);
X    setpwent();
X    if (!(pw = getpwnam(login)))
X    {
X	*ruidp = u->u_ruid;
X	*rgidp = u->u_rgid;
X	addstr("(user not in passwd file)\n");
X    }
X    else
X    {
X	*ruidp = pw->pw_uid;
X	*rgidp = pw->pw_gid;
X	for (cp = pw->pw_gecos; *cp && *cp != ','; cp++)
X	    ;
X	*cp = '\0';
X	addstr(pw->pw_gecos);
X	addch('\n');
X    }
X    return 1;
X}
X
X/*
X * determine "getty-ness" of a process
X */
X
Xisgetty(u)
X    struct proc *u;
X{
X    struct utmp ut;
X
X    lseek(utmp, 0L, 0);
X    while (read(utmp, &ut, sizeof ut) == sizeof ut)
X	if (ut.ut_pid == u->p_pid)
X	    return ut.ut_type == LOGIN_PROCESS;
X    return 0;
X}
________This_Is_The_END________
if test `wc -c < si.c` -ne 11187; then
	echo 'shar: si.c was damaged during transit (should have been 11187 bytes)'
fi
fi		; : end of overwriting check
echo 'x - si.man'
if test -f si.man; then echo 'shar: not overwriting si.man'; else
sed 's/^X//' << '________This_Is_The_END________' > si.man
X.TH SI 1 "6 August 1989"
X.SH NAME
Xsi \- show system and user process trees
X.SH SYNOPSIS
X.B si
X[
X.B -m
X]
X.SH DESCRIPTION
X.I Si
Xdisplays indented process trees for processes other than the kernel,
X.IR init ,
Xand
X.IR getty .
XThe information displayed is the terminal name or
X.BR "(no tty)" ,
Xthe logged-in user,
Xlogin time,
Xidle time and user name,
Xand for each process the process ID,
Xeffective user and group IDs if different from the logged-in user's IDs,
Xand the process name and arguments (if available).
XIf the process name is not available or differs from the exec name,
Xthe exec name is displayed in parentheses.
XProcesses are displayed in order by process group,
Xand within each process group by parent/child relationship.
XThe only option is
X.BR "-m" ,
Xwhich causes
X.I si
Xto compute the idle time based on the tty's modification time
Xrather than access time.
XThis allows the user to select between the idle time displayed by
X.I who
Xor by
X.IR w .
X.PP
XThe program is full-screen,
Xusing
X.I curses
X(with
X.IR M_TERMINFO )
Xto control screen updates.
XIt has two interactive commands:
Xpress
X.B quit
X(usually
X.BR Control-\\ )
Xto exit,
Xand
X.B intr
X(usually
X.B DEL
Xor
X.BR Control-C )
Xto force the screen to be redrawn after line noise or an
Xunexpected screen message.
X.SH FILES
X.nf
X.ta 2i
X/dev/kmem	system tables
X/dev/mem	processes in core
X/dev/swap	processes on swap
X.fi
X.SH SEE ALSO
Xwho(1), w(1), finger(1)
X.SH AUTHOR
X.nf
XBrandon S. Allbery
Xtdi2!brandon (first version)
Xncoast!allbery (second version)
Xallbery@NCoast.ORG (third version)
Xnc386!allbery (fourth version)
X.fi
X.SH HISTORY
XFirst written at Tridelta Industries (tdi2), for System V Release 2.2.
XThis version displayed quite a bit more information than the current one;
Xit may be resurrected in the future.
X.PP
XA vastly simplified version was implemented on ncoast (nc68k);
Xmuch of the information in the original was not available under
XSystem III,
Xso the program was restricted to process trees of logged-in users.
X.PP
XA third version was implemented much later,
Xalso under System III.
XIt displayed process trees for detached processes as well,
Xand did a much better job of sorting processes.
X.PP
XThe fourth version was written for Xenix V/386,
Xand amounted to a port of the third version to that environment.
X.SH BUGS
XThe program truncates lines that will not fit across the screen;
Xon an 80-column screen under Xenix 386, this rarely causes problems.
XIt also omits lines which will not fit,
Xwhich is somewhat more serious on busy systems.
X.PP
XOccasionally,
Xa race condition will cause
X.I si
Xto hang instead of exiting or redrawing the screen.
XThis is a side effect of the old V7 signal mechanism;
Xif SCO UNIX 386 implements SVR3 (more) reliable signals,
Xthis will go away.
XEnter the command again to retain control of the program.
X(This may cause
X.I si
Xto exit without restoring the terminal state.)
________This_Is_The_END________
if test `wc -c < si.man` -ne 2860; then
	echo 'shar: si.man was damaged during transit (should have been 2860 bytes)'
fi
fi		; : end of overwriting check
exit 0
--
Brandon S. Allbery, moderator of comp.sources.misc	allbery@uunet.uu.net
Please send comp.sources.misc submissions to comp-sources-misc@<backbone>,
related mail to comp-sources-misc-request@<backbone>, and personal mail ONLY
to allbery@NCoast.ORG.  You have only yourself to blame if you don't.