[comp.sources.misc] v07i106: pt - show process family tree

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

Posting-number: Volume 7, Issue 106
Submitted-by: bauer@loligo.cc.fsu.edu (Jeff Bauer)
Archive-name: pt

Here's a simple utility that displays the hierarchy of the process
structure on a UNIX system.  I must admit, however, that "ps" output
on System V is cleaner in terms of white space between columns
than bsd systems and "pt" does a better job there.

Just delete everything above and include the "cut here" line and
run it through "sh".

				-- Jeff Bauer

-- cut here -- cut here -- cut here -- cut here -- cut here --
#!/bin/sh
# shar:	Shell Archiver
#	Run the following text with /bin/sh to create:
#	Makefile
#	pt.1
#	pt.1.cat
#	pt.c
sed 's/^X//' << 'SHAR_EOF' > Makefile
X#
X# Comment out the next line if you are building for System V
X#
XDEFINES=-DBSD
XCFLAGS=-O
X
Xpt:	pt.c
X	$(CC) $@.c -o $@ $(CFLAGS) $(DEFINES)
SHAR_EOF
sed 's/^X//' << 'SHAR_EOF' > pt.1
X.TH PT 1
X.SH NAME
X\fBpt\fR \- display process family tree
X.SH USAGE
X.B pt [-a] [user]
X.SH DESCRIPTION
XThe 
X.I pt
Xcommand is a convenient alternative to using ps(1) to display a list
Xof user processes.  Instead of showing a list of processes in the order
Xthat they appear in the system process table,
X.I pt
Xdisplays processes in order of their family hierarchy.  The PID of
Xchildren processes are indented to show the relationship between a
Xparent and child process.
X.sp
XWith no parameters specified,
X.I pt
Xwill display only those processes not owned by "root".  If
X.I \-a
Xis specified,
X.I pt
Xwill display all processes.  If
X.I user
Xis specified,
X.I pt
Xwill display only processes owned by the user name specified.
X.sp
XThe column headings are somewhat self-explanatory; for further
Xdetails see ps(1).
X.SH EXAMPLE
X.sp
X$ pt carr
X.br
XPID                  User     Time    Pages  S  Command
X.br
X 8618                carr     0:00        8 (S) -ksh
X.br
X  8724               carr     0:00        8 (S) -ksh
X.br
X   8756              carr    16:13      479 (R) vlad6
X.SH CAVEATS
XWhen a user name is specified on the command line sometimes
X.I pt
Xcannot determine the correct lineage of a process.  Any of these
Xorphan processes will appear under the heading of "Orphans" on
Xthe output.
X.sp
XSince
X.I pt
Xreally is a filter from a forked-off ps(1) sometimes the parsing of
Xthe output from ps(1) fails and the fields get garbled.
X.SH SEE ALSO
X.BR ps(1).
SHAR_EOF
sed 's/^X//' << 'SHAR_EOF' > pt.1.cat
XPT(1)                     USER COMMANDS                     PT(1)
X
X
X
XNAME
X     pt - display process family tree
X
XUSAGE
X     pt [-a] [user]
X
XDESCRIPTION
X     The _p_t command is a convenient alternative to using ps(1) to
X     display a list of user processes.  Instead of showing a list
X     of processes in the order that they  appear  in  the  system
X     process  table, _p_t displays processes in order of their fam-
X     ily hierarchy.  The PID of children processes  are  indented
X     to show the relationship between a parent and child process.
X
X     With no parameters specified, _p_t  will  display  only  those
X     processes  not owned by "root".  If -_a is specified, _p_t will
X     display all  processes.   If  _u_s_e_r  is  specified,  _p_t  will
X     display only processes owned by the user name specified.
X
X     The  column  headings  are  somewhat  self-explanatory;  for
X     further details see ps(1).
X
XEXAMPLE
X     $ pt carr
X     PID                  User     Time    Pages  S  Command
X      8618                carr     0:00        8 (S) -ksh
X       8724               carr     0:00        8 (S) -ksh
X        8756              carr    16:13      479 (R) vlad6
X
XCAVEATS
X     When a user name is specified on the command line  sometimes
X     _p_t  cannot  determine the correct lineage of a process.  Any
X     of these orphan processes will appear under the  heading  of
X     "Orphans" on the output.
X
X     Since _p_t really is a filter from a  forked-off  ps(1)  some-
X     times  the  parsing  of  the output from ps(1) fails and the
X     fields get garbled.
X
XSEE ALSO
X     ps(1).
SHAR_EOF
sed 's/^X//' << 'SHAR_EOF' > pt.c
X/*
X	pt.c	- show process family tree
X
X	pt [-a] [user]
X
X	Default compilation is for System V UNIX.
X	To compile for a bsd system try :
X
X	cc pt.c -o pt -O -DBSD
X							*/
X
X#include <stdio.h>
X#include <fcntl.h>
X#include <string.h>
X#ifdef	BSD
X#include <pwd.h>
X#include <ctype.h>
X#endif	BSD
X
X#define	MAXLINE	256
X#define	MYBUFSZ	256
X#define	STDIN	0
X#define	STDOUT	1
X#define	STDERR	2
X
Xchar line[MAXLINE], token[MAXLINE];
Xint firstr, cur_char, cur_row, cur_col;
X
X#define	TRUE	1
X#define	FALSE	0
X#define	UNDEF	2
X
Xstruct psl {
X	char state[8];		/* S */
X	char user[9];		/* UID */
X	unsigned int pid;	/* PID */
X	unsigned int ppid;	/* PPID */
X	unsigned int pages;	/* SZ */
X	char time[12];		/* TIME */
X	char verb[32];		/* COMD */
X};
X
Xstruct plist {
X	struct psl cp;
X	struct plist *s;		/* sibling */
X	struct plist *c;		/* child */
X};
X
Xstruct psl cp;
Xstruct plist *proot;
Xstruct plist *orphans;
Xint orphcnt;
Xint showroot = FALSE;
X#ifdef	BSD
Xstruct passwd *pe;
Xextern int errno;
X#endif	BSD
X
Xextern char *malloc();
Xvoid strcpy2(), cp_plist(), pprint();
Xint pinsert();
Xstruct plist *pfind();
X
Xvoid main(argc, argv)
Xint argc;
Xchar **argv;
X{
X	int gls;
X	int pfds[2];
X	char who[9];
X	int slen, wsp;
X	char tbuf[32], tbuf2[32];
X#ifdef	BSD
X	char d1[32], d2[32], d3[32], d4[32], d5[32], d6[32], d7[32];
X#endif
X	struct plist *child;
X	int fostered;
X	int offset;
X	int adopted;
X	int i, j;
X
X	if (argc == 1) who[0] = '\0';
X
X	while (argc--) {
X	  if (argc != 0) {
X	    if (strcmp(argv[argc], "-a") == 0)
X	      showroot = TRUE;
X	    else
X	      strcpy2(who, argv[argc], 8);
X	  }
X	}
X	if (argc > 3) {
X	  fprintf(stderr,"Usage : %s [-a] [user]\n", argv[0]);
X	  exit(1);
X	}
X	if (strcmp(who, "root") == 0)
X	  showroot = TRUE;
X
X	orphcnt = 0;
X
X	if (pipe(pfds) < 0) {
X	    perror("pipe");
X	    exit(1);
X	}
X
X	if (fork() == 0) {		/* BEGIN child processing */
X	  if (close(STDOUT) < 0) {	/* release fd 1 */
X	    perror("close1");
X	    exit(1);
X	  }
X	  if (dup(pfds[1]) < 0) {	/* re-use fd 1 */
X	    perror("dup1");
X	    exit(1);
X	  }
X	  if (close(pfds[1]) < 0) {
X	    perror("close2");
X	    exit(1);
X	  }
X	  if (close(pfds[0]) < 0) {
X	    perror("close3");
X	    exit(1);
X	  }
X	  if (who[0] == '\0')
X#ifdef	BSD
X	    execl("/bin/ps","ps","-axlw",0);
X#else
X	    execl("/bin/ps","ps","-elf",0);
X#endif	BSD
X	  else 
X#ifdef	BSD
X	    execl("/bin/ps","ps","-axlw",0);	/* no single-user filter on bsd "ps" */
X#else
X	    execl("/bin/ps","ps","-u",who,"-lf",0);
X#endif	BSD
X	}				/* END child processing */
X
X	if (close(STDIN) < 0) {		/* release fd 0 */
X	  perror("close4");
X	  exit(1);
X	}
X	if (dup(pfds[0]) < 0) {		/* re-use fd 0 */
X	  perror("dup2");
X	  exit(1);
X	}
X	if (close(pfds[0]) < 0) {
X	  perror("close5");
X	  exit(1);
X	}
X	if (close(pfds[1]) < 0) {
X	  perror("close6");
X	  exit(1);
X	}
X
X	firstr = TRUE;
X	cur_row = 0;
X	while ((gls = getline(STDIN, line, MAXLINE)) >= 0) {
X	  if (gls == -2) break;
X	  cur_char = 0;
X	  cur_col = 0;
X	  offset = FALSE;
X#ifdef	BSD
X	  d7[0] = '\0';
X#endif	BSD
X	  while (get_token(line, token, MAXLINE, &wsp) >= 0) {
X	    if (cur_row > 0) {		/* skip column headings */
X	      switch (cur_col) {
X#ifdef	BSD
X		case 0 :
X		  if (((wsp > 0) && (strlen(token) > 5)) ||
X		    ((wsp == 0) && (strlen(token) > 10))) {
X		    j = 0;
X		    for (i = (7 - wsp); i < strlen(token); i++)
X		      tbuf[j++] = token[i];
X		    tbuf[j+1] = '\0';
X		    strcpy(token, tbuf);
X		    cur_col += 1;
X		    sscanf(token, "%d", &i);
X		    if ((pe = getpwuid(i)) != NULL)
X		      strcpy2(cp.user, pe->pw_name, 8);
X		    else {
X		      strcpy2(cp.user, "(bogus)", 8);
X		      fprintf(stderr,"1: Checking UID %d : ",i);
X		      perror("getpwuid");
X		    }
X		  }
X		  else if ((wsp == 6) && (strlen(token) > 1)) {
X		    cur_col += 1;
X		    sscanf(token, "%d", &i);
X		    if ((pe = getpwuid(i)) != NULL)
X		      strcpy2(cp.user, pe->pw_name, 8);
X		    else {
X		      strcpy2(cp.user, "(bogus)", 8);
X		      fprintf(stderr,"2: Checking UID %d : ",i);
X		      perror("getpwuid");
X		    }
X		  }
X		  break;	  
X		case 1 :
X		  sscanf(token, "%d", &i);
X		  if ((pe = getpwuid(i)) != NULL)
X		    strcpy2(cp.user, pe->pw_name, 8);
X		  else {
X		    strcpy2(cp.user, "(bogus)", 8);
X		    fprintf(stderr,"3: Checking UID %d : ",i);
X		    perror("getpwuid");
X		  }	    
X		  break;
X		case 2 :
X		  sscanf(token, "%d", &cp.pid);
X		  break;
X		case 3 :
X		  sscanf(token, "%d", &cp.ppid);
X		  break;
X		case 7 :
X		  if (strlen(token) > 4)
X		    offset = TRUE;
X		  break;
X		case 8 :
X		  if (offset == TRUE) {
X		    strcpy2(d1, token, 32);
X		  }
X		  break;
X		case 9 :
X		  if (offset == TRUE) {
X		    strcpy2(d2, token, 32);
X		  }
X		  else {
X		    strcpy2(d1, token, 32);
X		  }	    
X		  break;
X		case 10 :
X		  if (offset == TRUE) {
X		    strcpy2(d3,token, 32);
X		  }
X		  else {
X		    strcpy2(d2, token, 32);
X		  }	    
X		  break;
X		case 11 :
X		  if (offset == TRUE) {
X		    strcpy(d4, token, 32);
X		  }
X		  else {
X		    strcpy2(d3, token, 32);
X		  }
X		  break;
X		case 12 :
X		  if (offset == TRUE) {
X		    strcpy2(d5, token, 32);
X		  }
X		  else {
X		    strcpy2(d4, token, 32);
X		  }
X		  break;
X		case 13 :
X		  if (offset == TRUE) { 
X		    strcpy2(d6, token, 32);
X		  }
X		  else {
X		    strcpy2(d5, token, 32);
X		  }
X		  break;
X		case 14 :
X		  if (offset == TRUE) {
X		    strcpy2(d7, token, 32);
X		  }
X		  else {
X		    strcpy2(d6, token, 32);
X		  }
X		  break;
X#else
X		case 1 :
X		  cp.state[0] = token[0];
X		  cp.state[1] = '\0';
X		  if (strlen(token) > 1) {
X		    strcpy2(cp.user, (token+1), 8);
X		    cur_col += 1;
X		  }
X		  if ((cp.state[0] == 'R') || (cp.state[0] == 'O'))
X		    offset = TRUE;	/* "WCHAN" is empty */
X		  break;
X		case 2 :
X		  strcpy2(cp.user, token, 8);
X		  break;
X		case 3 :
X		  sscanf(token, "%d", &cp.pid);
X		  break;
X		case 4 :
X		  sscanf(token, "%d", &cp.ppid);
X		  break;
X		case 9 :
X		  if (cp.state[0] == 'Z') {
X		    cp.pages = 0;
X		    break;
X		  }
X		  strcpy(tbuf,token);
X		  if ((slen = strlen(token)) > 9 ) {
X		    strcpy2(tbuf, token, (slen-9));
X		    cur_col += 1;
X		  }
X		  sscanf(tbuf, "%d", &cp.pages);
X		  break;
X		case 12 :
X		  if (offset == TRUE) {
X		    strcpy2(cp.time, token, 12);
X		  }
X		  break;
X		case 13 :
X		  if (offset == TRUE) {
X		    strcpy2(cp.verb, token, 32);
X		  }
X		  else {
X		    strcpy2(cp.time, token, 12);
X		  }
X		  strcpy2(tbuf2, token, 12);
X		  break;
X		case 14 :
X		  if (offset == FALSE) {
X		    strcpy2(cp.verb, token, 32);
X		  }
X		  break;
X#endif	BSD
X	      }
X	      cur_col += 1;
X	    }
X	  }
X#ifdef	BSD
X	
X	  /* Sigh... */
X
X	  if (isupper(d2[0]) || (d2[0] == 'p')) {
X	    strcpy(cp.state, d2);
X	    strcpy(cp.time, d4);
X	    strcpy(cp.verb, d5);
X	  }
X	  else {
X	    strcpy(cp.state, d3);
X	    strcpy(cp.time, d5);
X	    strcpy(cp.verb, d6);
X	  }
X
X#endif	BSD
X	  if (cur_row > 0) {
X#ifdef	BSD
X	    if (who[0] == '\0')
X	      pinsert(&cp, TRUE);
X	    else
X	      if (strcmp(cp.user, who) == 0)
X	        pinsert(&cp, TRUE);
X#else
X	    pinsert(&cp, TRUE);
X#endif	BSD
X	  }
X	  cur_row += 1;
X	}
X	wait(0);
X
X	/* Place orphans into their foster homes */
X
X	fostered = 0;
X
X	while (orphcnt != fostered) {
X
X	  adopted = FALSE;
X
X	  for (child = orphans; child != NULL; child = child->s) {
X	    if (strcmp(child->cp.state, "HOME") != 0) {
X	      if (pinsert(&child->cp, FALSE) == TRUE) {
X		strcpy(child->cp.state, "HOME");
X	        fostered += 1;
X		adopted = TRUE;
X	      }
X	    }
X	  }
X	  if (adopted == FALSE) {
X	    break;
X	  }
X	}
X	printf("PID\t\t\t    User     Time    Pages  S  Command\n");
X	pprint(proot, 0, FALSE);
X	if (orphcnt != fostered) {
X	  printf("Orphans :\n");
X	  printf("PID (PPID)\t\t    User     Time    Pages  S  Command\n");
X	  pprint(orphans, 0, TRUE);
X	}
X}
X
Xint pinsert(cp, adopt)		/* TRUE = info inserted; FALSE = info orphaned */
Xstruct psl *cp;
Xint adopt;			/* TRUE = add orphans to orphan list */
X{
X	struct plist *parent, *sibling;
X	int iflag;
X
X	iflag = TRUE;
X	if (proot == NULL) {	/* call this one root for now */
X	  if ((proot = (struct plist *) malloc(sizeof(struct plist))) == NULL) {
X	    fprintf(stderr,"malloc failed\n");
X	    exit(1);
X	  }
X	  cp_plist(proot, cp);
X	}
X	else {			/* find parent; traverse sibling list & insert */
X	  if ((parent = pfind(proot, cp->ppid)) == NULL) {	/* no parent yet */
X	    iflag = FALSE;
X	    if (adopt == TRUE) {
X	      orphcnt += 1;
X	      if (orphans == NULL) {
X	        if ((orphans = (struct plist *) malloc(sizeof(struct plist))) == NULL) {
X	          fprintf(stderr,"malloc failed\n");
X	          exit(1);
X	        }
X	        cp_plist(orphans, cp);
X	      }
X	      else {			/* chain orphans together (heartless!) */
X	        for (sibling = orphans; sibling->s != NULL; sibling = sibling->s);
X	        if ((sibling->s = (struct plist *) malloc(sizeof(struct plist))) == NULL) {
X	          fprintf(stderr,"malloc failed\n");
X	          exit(1);
X	        }
X	        cp_plist(sibling->s, cp);
X	      }
X	    }
X	  }
X	  else {			/* parent exists */
X	    if (parent->c == NULL) {	/* first child */
X	      if ((parent->c = (struct plist *) malloc(sizeof(struct plist))) == NULL) {
X	        fprintf(stderr,"malloc failed\n");
X	        exit(1);
X	      }
X	      cp_plist(parent->c, cp);
X	    }
X	    else {			/* siblings exist */
X	      sibling = parent->c;
X	      while (sibling->s != NULL) sibling = sibling->s;
X	      if ((sibling->s = (struct plist *) malloc(sizeof(struct plist))) == NULL) {
X	        fprintf(stderr,"malloc failed\n");
X	        exit(1);
X	      }
X	      cp_plist(sibling->s, cp);
X	    }
X	  }
X	}
X	return(iflag);
X}
X
Xstruct plist *pfind(cp, ppid)
Xstruct plist *cp;
Xint ppid;
X{
X	struct plist *p;
X
X	if (cp == NULL) return(NULL);
X	else if (cp->cp.pid == ppid) return(cp);
X	else {
X	  if ((p = pfind(cp->c, ppid)) == NULL)
X	    return(pfind(cp->s, ppid));
X	  else
X	    return(p);
X	}
X}
X
Xvoid pprint(cp, cnt, orphflg)
Xstruct plist *cp;
Xint cnt, orphflg;
X{
X	int i, j;
X	char buf[16];
X
X	if (cp == NULL) return;
X
X	if (strcmp(cp->cp.state, "HOME") != 0) {
X	  if (!((showroot == FALSE) && (strcmp(cp->cp.user, "root") == 0))) {
X	    if (orphflg == TRUE) {
X#ifdef	BSD
X	      sprintf(buf, "%d (%d)",cp->cp.pid, cp->cp.ppid);
X	      j = strlen(buf);
X#else
X	      j = sprintf(buf, "%d (%d)",cp->cp.pid, cp->cp.ppid);
X#endif	BSD
X	    }
X	    else {
X#ifdef	BSD
X	      sprintf(buf, "%d", cp->cp.pid);
X	      j = strlen(buf);
X#else
X	      j = sprintf(buf, "%d", cp->cp.pid);
X#endif	BSD
X	    }
X	    for (i = 0; i < cnt; i++) printf(" ");
X	    printf(" %s", buf);
X	    for (i = 0; i < (3-(((cnt*1)+j+1)/8)); i++) printf("\t");
X	    printf("%8s %8s %8d (%s) %s\n", cp->cp.user, cp->cp.time,
X	      cp->cp.pages, cp->cp.state, cp->cp.verb);
X	  }
X	}
X
X	pprint(cp->c, cnt+1, orphflg);
X	pprint(cp->s, cnt, orphflg);
X}
X
Xvoid cp_plist(pto, pfrom)
Xstruct plist *pto;
Xstruct psl *pfrom;
X{
X	strcpy(pto->cp.state, pfrom->state);
X	strcpy(pto->cp.user, pfrom->user);
X	pto->cp.pid = pfrom->pid;
X	pto->cp.ppid = pfrom->ppid;
X	pto->cp.pages = pfrom->pages;
X	strcpy(pto->cp.time, pfrom->time);
X	strcpy(pto->cp.verb, pfrom->verb);
X	pto->c = pto->s = NULL;
X}
X
Xint getline(fd,buf,max)
Xint fd;
Xchar *buf;
Xint max;
X
X/*
X	getline(fd, buffer, max)
X
X	Get a line from file *fd* up to *max* bytes into *buffer*.
X	Return 0 if OK, -1 if hit EOF, -2 if first read is NULL,
X	-3 if read failed.
X								*/
X
X{
X	static char mybuf[MYBUFSZ];	/* internal buffer */
X	static int myend = 0;		/* # bytes in mybuf */
X	static int mycnt = 0;		/* # bytes already scanned */
X	static char *curline = NULL;	/* beginning of current line to get */
X	char *p, lastc;
X	int nbytes;
X
X	if (firstr == TRUE) curline = NULL;
X
X	if (curline == NULL) {	/* empty buffer */
X	  if ((myend = read(fd, mybuf, MYBUFSZ)) < 0) {
X	    perror("read");
X	    return(-3);
X	  }
X	  curline = mybuf;		/* new buffer filled */
X	  mycnt = 0;
X	}
X
X	if ((myend == 0) && firstr) {	/* first read hit EOF (empty file) */
X	  *buf = '\0';
X	  return(-2);
X	}
X
X	if (myend == 0) {		/* later read hit EOF */
X	  *buf = '\0';
X	  return(-1);
X	}
X
X	firstr = FALSE;
X
X	p = curline;
X	nbytes = 0;
X
X	read_loop:
X
X	while ((*p != '\n') && (mycnt < myend) && (nbytes <= max)) {
X	  *buf++ = *p++;
X	  mycnt += 1;
X	  nbytes += 1;
X	}
X	lastc = *p;
X	p += 1;
X	mycnt += 1;
X
X	if ((mycnt >= myend) && (lastc != '\n')) {
X	  if ((myend = read(fd, mybuf, MYBUFSZ)) < 0) {
X	    perror("read");
X	    return(-3);
X	  }
X	  p = curline = mybuf;
X	  lastc = *p;
X	  mycnt = 0;
X	}
X	if ((mycnt != myend) && (lastc != '\n')) goto read_loop;
X
X	*buf += 1;
X	*buf = '\0';
X	curline = p;	/* set for next *getline* call */
X
X	if (mycnt >= myend)
X	  curline = NULL;	/* reached end of buffer */
X
X	return(0);
X}
X
Xint get_token(line, token, max, wsp)	/* return 0 = token, -1 = no tokens left on line */
Xchar *line, *token;
Xint *wsp;				/* # of white spaces preceding token */
Xint max;
X{
X	int i, j;
X
X	i = cur_char;
X	j = 0;
X	*wsp = 0;
X
X	if ((i >= (max-1)) || (line[i] == '\0')) {
X	  *token = '\0';
X	  return(-1);
X	}
X
X	while((line[i] == ' ') || (line[i] == '\t')) {
X	  i++;
X	  *(wsp)++;
X	}
X
X	while ((line[i] != ' ') && (line[i] != '\t') && (line[i] != '\n')
X	    && (line[i] != '\r') && (line[i] != '\0'))
X	  token[j++] = line[i++];
X
X	token[j] = '\0';
X
X	if (line[i] != '\0') cur_char = i + 1;
X	else cur_char = i;
X	return(0);
X}
X
Xvoid strcpy2(dst, src, max)
Xchar *dst, *src;
Xint max;
X{
X	int i;
X
X	i = 0;
X	while ((src[i] != ' ') && (i < max) && (src[i] != '\0')) {
X	  dst[i] = src[i];
X	  i += 1;
X	}
X	dst[i] = '\0';
X}
SHAR_EOF
exit