rsalz@uunet.uu.net (Rich Salz) (10/28/89)
Submitted-by: Jeff Bauer <bauer@loligo.cc.fsu.edu> Posting-number: Volume 20, Issue 78 Archive-name: pt Get frustrated poring through "ps" output trying to figure out which child belongs to what parent? Well, this might help. "Pt" makes a valiant effort to filter "ps" output into a hierarchical process tree. It runs well on System V.3 and makes a good effort on bsd derivatives, where "ps" columns tend to stretch and run together. Note the define in the Makefile for bsd/SysV builds. -- Jeff Bauer PS : If anybody hacks at this and has more luck parsing "ps" output or generates changes to run on different unix flavors, please mail/post me the diffs. -- Cut here, as usual -- #!/bin/sh # shar: Shell Archiver # Run the following text with /bin/sh to create: # Makefile # pt.1 # 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.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 -- Please send comp.sources.unix-related mail to rsalz@uunet.uu.net. Use a domain-based address or give alternate paths, or you may lose out.