"Fossies" - the Fresh Open Source Software Archive

Member "pstree.c" (13 May 2015, 34264 Bytes) of package /linux/misc/old/pstree-2.39.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "pstree.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 2.38_vs_2.39.

    1 /*  This is pstree written by Fred Hucht (c) 1993-2015  *
    2  *  EMail: fred AT thp.uni-due.de               *
    3  *  Feel free to copy and redistribute in terms of the  *
    4  *  GNU public license.                     *
    5  *
    6  * $Id: pstree.c,v 2.39 2015/05/13 12:24:47 fred Exp $
    7  */
    8 static char *WhatString[]= {
    9   "@(#)pstree $Revision: 2.39 $ by Fred Hucht (C) 1993-2015",
   10   "@(#)EMail: fred AT thp.uni-due.de",
   11   "$Id: pstree.c,v 2.39 2015/05/13 12:24:47 fred Exp $"
   12 };
   13 
   14 #define MAXLINE 8192
   15 
   16 #if defined(_AIX) || defined(___AIX)    /* AIX >= 3.1 */
   17 /* Under AIX, we directly read the process table from the kernel */
   18 # ifndef _AIX50
   19 /* problems with getprocs() under AIX 5L
   20  * workaround contributed by Chris Benesch <chris AT fdbs.com> */
   21 #  define USE_GetProcessesDirect
   22 # endif /*_AIX50*/
   23 #  define HAS_TERMDEF
   24 extern char *termdef(int, char);
   25 #  define _ALL_SOURCE
   26 #  include <procinfo.h>
   27 #  define USE_GETPROCS
   28 
   29 #  ifdef USE_GETPROCS
   30 #    define IFNEW(a,b) a
   31 #    define ProcInfo procsinfo
   32 #    ifndef _AIX61
   33 /* workaround contributed by Michael Staats <michael.staats AT gmx.de> */
   34 extern getprocs(struct procsinfo *, int, struct fdsinfo *, int, pid_t *, int);
   35 #    endif /*_AIX61*/
   36 #  else /*USE_GETPROCS*/
   37 #    define IFNEW(a,b) b
   38 #    define ProcInfo procinfo
   39 extern getproc(struct procinfo *, int, int);
   40 extern getuser(struct procinfo *, int, void *, int);
   41 #  endif /*USE_GETPROCS*/
   42 
   43 #  ifndef _AIX61
   44 /* workaround contributed by Michael Staats <michael.staats AT gmx.de> */
   45 extern getargs(struct ProcInfo *, int, char *, int);
   46 #  endif /*_AIX61*/
   47 
   48 /*#  define PSCMD   "ps -ekf"
   49   #  define PSFORMAT    "%s %ld %ld %*20c %*s %[^\n]"*/
   50 #  define HAS_PGID
   51 #  define UID2USER
   52 #  define PSCMD     "ps -eko uid,pid,ppid,pgid,thcount,args"
   53 #  define PSFORMAT  "%ld %ld %ld %ld %ld %[^\n]"
   54 #  define PSVARS    &P[i].uid, &P[i].pid, &P[i].ppid, &P[i].pgid, &P[i].thcount, P[i].cmd
   55 #  define PSVARSN   6
   56 /************************************************************************/
   57 #elif defined(__linux) || (defined __alpha && defined(_SYSTYPE_BSD) || defined (Tru64))
   58 /* TRU64 contributed by Frank Parkin <fparki AT acxiom.co.uk>
   59  */
   60 #  ifdef __linux
   61 #    define USE_GetProcessesDirect
   62 #    include <glob.h>
   63 #    include <sys/stat.h>
   64 #  endif
   65 #  define UID2USER
   66 #  define HAS_PGID
   67 #  define PSCMD     "ps -eo uid,pid,ppid,pgid,args"
   68 #  define PSFORMAT  "%ld %ld %ld %ld %[^\n]"
   69 #  define PSVARS    &P[i].uid, &P[i].pid, &P[i].ppid, &P[i].pgid, P[i].cmd
   70 #  define PSVARSN   5
   71 /************************************************************************/
   72 #elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__APPLE__)
   73 /* NetBSD contributed by Gary D. Duzan <gary AT wheel.tiac.net>
   74  * FreeBSD contributed by Randall Hopper <rhh AT ct.picker.com> 
   75  * Darwin / Mac OS X patch by Yuji Yamano <yyamano AT kt.rim.or.jp>
   76  * wide output format fix for NetBSD by Jeff Brown <jabrown AT caida.org>
   77  * (Net|Open|Free)BSD & Darwin merged by Ralf Meyer <ralf AT thp.Uni-Duisburg.DE>
   78  */
   79 #  define HAS_PGID
   80 #  define PSCMD     "ps -axwwo user,pid,ppid,pgid,command"
   81 #  define PSFORMAT  "%s %ld %ld %ld %[^\n]"
   82 #  define PSVARS    P[i].name, &P[i].pid, &P[i].ppid, &P[i].pgid, P[i].cmd
   83 #  define PSVARSN   5
   84 #  define ZOMBIES_HAVE_PID_0
   85 /************************************************************************/
   86 #elif defined(sun) && (!defined(__SVR4)) /* Solaris 1.x */
   87 /* contributed by L. Mark Larsen <mlarsen AT ptdcs2.intel.com> */
   88 /* new cpp criteria by Pierre Belanger <belanger AT risq.qc.ca> */
   89 #  define solaris1x
   90 #  define UID2USER
   91 #  ifdef mc68000
   92 /* contributed by Paul Kern <pkern AT utcc.utoronto.ca> */
   93 #    define PSCMD   "ps laxw"
   94 #    define PSFORMAT    "%*7c%ld %ld %ld %*d %*d %*d %*x %*d %*d %*x %*14c %[^\n]"
   95 #    define uid_t   int
   96 #    define NEED_STRSTR
   97 #  else
   98 #    define PSCMD   "ps jaxw"
   99 #    define PSFORMAT    "%ld %ld %*d %*d %*s %*d %*s %ld %*s %[^\n]"
  100 #    define PSVARS  &P[i].ppid, &P[i].pid, &P[i].uid, P[i].cmd
  101 #    define PSVARSN 4
  102 #  endif
  103 /************************************************************************/
  104 #elif defined(sun) && (defined(__SVR4)) /* Solaris 2.x */
  105 /* contributed by Pierre Belanger <belanger AT risq.qc.ca> */
  106 #  define solaris2x
  107 #  define PSCMD         "ps -ef"
  108 #  define PSFORMAT      "%s %ld %ld %*d %*s %*s %*s %[^\n]"
  109 /************************************************************************/
  110 #elif defined(bsdi)
  111 /* contributed by Dean Gaudet <dgaudet AT hotwired.com> */
  112 #  define UID2USER
  113 #  define PSCMD     "ps laxw"
  114 #  define PSFORMAT  "%ld %ld %ld %*d %*d %*d %*d %*d %*s %*s %*s %*s %[^\n]"
  115 /************************************************************************/
  116 #elif defined(_BSD) /* Untested */
  117 #  define UID2USER
  118 #  define PSCMD     "ps laxw"
  119 #  define PSFORMAT  "%*d %*c %ld %ld %ld %*d %*d %*d %*x %*d %d %*15c %*s %[^\n]"
  120 /************************************************************************/
  121 #elif defined(__convex) /* ConvexOS */
  122 #  define UID2USER
  123 #  define PSCMD     "ps laxw"
  124 #  define PSFORMAT  "%*s %ld %ld %ld %*d %*g %*d %*d %*21c %*s %[^\n]"
  125 /************************************************************************/
  126 #else           /* HP-UX, A/UX etc. */
  127 #  define PSCMD     "ps -ef"
  128 #  define PSFORMAT  "%s %ld %ld %*20c %*s %[^\n]"
  129 #endif
  130 /*********************** end of configurable part ***********************/
  131 
  132 #ifndef PSVARS      /* Set default */
  133 # ifdef UID2USER
  134 #  define PSVARS    &P[i].uid, &P[i].pid, &P[i].ppid, P[i].cmd
  135 # else
  136 #  define PSVARS    P[i].name, &P[i].pid, &P[i].ppid, P[i].cmd
  137 # endif
  138 # define PSVARSN    4
  139 #endif
  140 
  141 #include <stdio.h>
  142 #include <stdlib.h>
  143 #include <string.h>     /* For str...() */
  144 #ifdef NEED_SNPRINTF
  145 #include <stdarg.h>
  146 int snprintf(char *, int, char *, ...);
  147 #endif
  148 #include <unistd.h>     /* For getopt() */
  149 #include <pwd.h>        /* For getpwnam() */
  150 
  151 #include <sys/ioctl.h>      /* For TIOCGSIZE/TIOCGWINSZ */
  152 /* #include <termios.h> */
  153 
  154 #ifdef DEBUG
  155 # include <errno.h>
  156 #endif
  157 
  158 #ifdef NEED_STRSTR
  159 static char *strstr(char *, char *);
  160 #endif
  161 
  162 #ifndef TRUE
  163 #define TRUE  1
  164 #define FALSE 0
  165 #endif
  166 
  167 struct TreeChars {
  168   char *s2,         /* SS String between header and pid */
  169     *p,         /* PP dito, when parent of printed childs */
  170     *pgl,       /* G  Process group leader */
  171     *npgl,      /* N  No process group leader */
  172     *barc,      /* C  bar for line with child */
  173     *bar,       /* B  bar for line without child */
  174     *barl,      /* L  bar for last child */
  175     *sg,        /*    Start graphics (alt char set) */
  176     *eg,        /*    End graphics (alt char set) */
  177     *init;      /*    Init string sent at the beginning */
  178 };
  179 
  180 /* Example:
  181  * |-+- 01111 ...        CPPN 01111 ...
  182  * | \-+=   01112 ...    B LPPG 01112 ...
  183  * |   |--= 01113 ...    B   CSSG 01113 ...
  184  * |   \--= 01114 ...    B   LSSG 01114 ...
  185  * \--- 01115 ...        LSSN 01115 ...
  186  */
  187 
  188 enum { G_ASCII = 0, G_PC850 = 1, G_VT100 = 2, G_UTF8 = 3, G_LAST };
  189 
  190 /* VT sequences contributed by Randall Hopper <rhh AT ct.picker.com> */
  191 /* UTF8 sequences contributed by Mark-Andre Hopf <mhopf AT mark13.org> */
  192 static struct TreeChars TreeChars[] = {
  193   /* SS          PP          G       N       C       B       L      sg      eg      init */
  194   { "--",       "-+",       "=",    "-",    "|",    "|",    "\\",   "",     "",     ""             }, /*Ascii*/
  195   { "\304\304", "\304\302", "\372", "\304", "\303", "\263", "\300", "",     "",     ""             }, /*Pc850*/
  196   { "qq",       "qw",       "`",    "q",    "t",    "x",    "m",    "\016", "\017", "\033(B\033)0" }, /*Vt100*/
  197   { "\342\224\200\342\224\200",
  198     /**/        "\342\224\200\342\224\254",
  199     /**/                    "=",
  200     /**/                            "\342\224\200",
  201     /**/                                    "\342\224\234",
  202     /**/                                            "\342\224\202",
  203     /**/                                                    "\342\224\224",
  204     /**/                                                            "",     "",     ""             }  /*UTF8*/
  205 }, *C;
  206 
  207 static int MyPid, NProc, Columns, RootPid;
  208 static short showall = TRUE, soption = FALSE, Uoption = FALSE;
  209 static char *name = "", *str = NULL, *Progname;
  210 static long ipid = -1;
  211 static char *input = NULL;
  212 
  213 static int atLdepth=0;    /* LOPTION - track how deep in the print chain we are */
  214 static int maxLdepth=100; /* LOPTION - will be changed by -l n option */
  215 static int compress = FALSE;
  216 
  217 #ifdef DEBUG
  218 static int debug = FALSE;
  219 #endif
  220 
  221 struct Proc {
  222   long uid, pid, ppid, pgid;
  223   char name[32], cmd[MAXLINE];
  224   int  print;
  225   long parent, child, sister;
  226   unsigned long thcount;
  227 } *P;
  228 
  229 #ifdef UID2USER
  230 static void uid2user(uid_t uid, char *name, int len) {
  231 #define NUMUN 128
  232   static struct un_ {
  233     uid_t uid;
  234     char name[32];
  235   } un[NUMUN];
  236   static short n = 0;
  237   short i;
  238   char uid_name[32];
  239   char *found;
  240 #ifdef DEBUG
  241   if (name == NULL) {
  242     for (i = 0; i < n; i++)
  243       fprintf(stderr, "uid = %3d, name = %s\n", un[i].uid, un[i].name);
  244     return;
  245   }
  246 #endif
  247   for (i = n - 1; i >= 0 && un[i].uid != uid; i--);
  248   if (i >= 0) { /* found locally */
  249     found = un[i].name;
  250   } else {
  251     struct passwd *pw = getpwuid(uid);
  252     if (pw) {
  253       found = pw->pw_name;
  254     } else {
  255       /* fix by Stan Sieler & Philippe Torche */
  256       snprintf(uid_name, sizeof(uid_name), "#%d", uid);
  257       found = uid_name;
  258     }
  259     if (n < NUMUN) {
  260       un[n].uid = uid;
  261       strncpy(un[n].name, found, 9);
  262       un[n].name[8] = '\0';
  263       n++;
  264     }
  265   }
  266   strncpy(name, found, len);
  267   name[len-1] = '\0';
  268 }
  269 #endif
  270 
  271 #if defined(_AIX) || defined(___AIX)    /* AIX 3.x / 4.x */
  272 static int GetProcessesDirect(void) {
  273   int i, nproc, maxnproc = 1024;
  274   
  275   struct ProcInfo *proc;
  276   int idx;
  277 #ifndef USE_GETPROCS
  278   struct userinfo user;
  279 #endif
  280   
  281   do {
  282     proc = malloc(maxnproc * sizeof(struct ProcInfo));
  283     if (proc == NULL) {
  284       fprintf(stderr, "Problems with malloc.\n");
  285       exit(1);
  286     }
  287     
  288     /* Get process table */
  289     idx = 0;
  290     nproc = IFNEW(getprocs(proc, sizeof(struct procsinfo), NULL, 0,
  291                &idx, maxnproc),
  292           getproc(proc, maxnproc, sizeof(struct procinfo))
  293           );
  294 #ifdef DEBUG
  295     idx = errno; /* Don't ask... */
  296     if (debug)
  297       fprintf(stderr,
  298           "nproc = %d maxnproc = %d" IFNEW(" idx = %d ","") "\n",
  299           nproc, maxnproc, idx);
  300     errno = idx;
  301 #endif
  302 #ifdef USE_GETPROCS
  303     if (nproc == -1) {
  304       perror("getprocs");
  305       exit(1);
  306     } else if (nproc == maxnproc) {
  307       nproc = -1;
  308     }
  309 #endif
  310     if (nproc == -1) {
  311       free(proc);
  312       maxnproc *= 2;
  313     } 
  314   } while (nproc == -1);
  315   
  316   P = malloc((nproc+1) * sizeof(struct Proc));
  317   if (P == NULL) {
  318     fprintf(stderr, "Problems with malloc.\n");
  319     exit(1);
  320   }
  321   
  322   for (i = 0; i < nproc; i++) {
  323 #ifndef USE_GETPROCS
  324     getuser(&proc[i],sizeof(struct procinfo),
  325         &user,   sizeof(struct userinfo));
  326 #endif
  327     P[i].uid     = proc[i].pi_uid;
  328     P[i].pid     = proc[i].pi_pid;
  329     P[i].ppid    = proc[i].pi_ppid;
  330     P[i].pgid    = proc[i].pi_pgrp;
  331     P[i].thcount = IFNEW(proc[i].pi_thcount, 1);
  332     
  333     uid2user(P[i].uid, P[i].name, sizeof(P[i].name));
  334     
  335     if (IFNEW(proc[i].pi_state,proc[i].pi_stat) == SZOMB) {
  336       strcpy(P[i].cmd, "<defunct>");
  337     } else {
  338       char *c = P[i].cmd;
  339       int ci = 0;
  340       getargs(&proc[i], sizeof(struct procinfo), c, MAXLINE - 2);
  341       c[MAXLINE-2] = c[MAXLINE-1] = '\0';
  342 
  343       /* Collect args. Stop when we encounter two '\0' */
  344       while (c[ci] != '\0' && (ci += strlen(&c[ci])) < MAXLINE - 2)
  345     c[ci++] = ' ';
  346       
  347       /* Drop trailing blanks */
  348       ci = strlen(c);
  349       while (ci > 0 && c[ci-1] == ' ') ci--;
  350       c[ci] = '\0';
  351       
  352       /* Replace some unprintables with '?' */
  353       for (ci = 0; c[ci] != '\0'; ci++)
  354     if (c[ci] == '\n' || c[ci] == '\t') c[ci] = '?';
  355       
  356       /* Insert [ui_comm] when getargs returns nothing */
  357       if (c[0] == '\0') {
  358     int l = strlen(IFNEW(proc[i].pi_comm,user.ui_comm));
  359     c[0] = '[';
  360     strcpy(c+1, IFNEW(proc[i].pi_comm,user.ui_comm));
  361     c[l+1] = ']';
  362     c[l+2] = '\0';
  363       }
  364     }
  365 #ifdef DEBUG
  366     if (debug)
  367       fprintf(stderr,
  368           "%d: uid=%5ld, name=%8s, pid=%5ld, ppid=%5ld, pgid=%5ld, tsize=%7u, dvm=%4u, "
  369           "thcount=%2d, cmd[%d]='%s'\n",
  370           i, P[i].uid, P[i].name, P[i].pid, P[i].ppid, P[i].pgid,
  371           IFNEW(proc[i].pi_tsize,user.ui_tsize),
  372           IFNEW(proc[i].pi_dvm,user.ui_dvm),
  373           proc[i].pi_thcount,
  374           strlen(P[i].cmd),P[i].cmd);
  375 #endif
  376     P[i].parent = P[i].child = P[i].sister = -1;
  377     P[i].print = FALSE;
  378   }
  379   free(proc);
  380   return nproc;
  381 }
  382 
  383 #endif /* _AIX */
  384 
  385 #ifdef __linux
  386 static int GetProcessesDirect(void) {
  387   glob_t globbuf;
  388   unsigned int i, j;
  389   
  390   glob("/proc/[0-9]*", GLOB_NOSORT, NULL, &globbuf);
  391   
  392   P = calloc(globbuf.gl_pathc, sizeof(struct Proc));
  393   if (P == NULL) {
  394     fprintf(stderr, "Problems with malloc.\n");
  395     exit(1);
  396   }
  397   
  398   for (i = j = 0; i < globbuf.gl_pathc; i++) {
  399     char *pdir, name[32];
  400     int c;
  401     FILE *tn;
  402     int k = 0;
  403     
  404     pdir = globbuf.gl_pathv[globbuf.gl_pathc - i - 1];
  405     
  406     /* if processes change their UID this change is only reflected in the owner of pdir.
  407      * fixed since version 2.36 */
  408     {
  409       struct stat st;
  410       if (stat(pdir, &st) != 0) { /* get uid */
  411     continue; /* process vanished since glob() */
  412       }
  413       P[j].uid = st.st_uid;
  414       uid2user(P[j].uid, P[j].name, sizeof(P[j].name));
  415     }
  416     
  417     snprintf(name, sizeof(name), "%s%s",
  418          globbuf.gl_pathv[globbuf.gl_pathc - i - 1], "/stat");
  419     tn = fopen(name, "r");
  420     if (tn == NULL) continue; /* process vanished since glob() */
  421     fscanf(tn, "%ld %s %*c %ld %ld",
  422        &P[j].pid, P[j].cmd, &P[j].ppid, &P[j].pgid);
  423     fclose(tn);
  424     P[j].thcount = 1;
  425     
  426     snprintf(name, sizeof(name), "%s%s",
  427          globbuf.gl_pathv[globbuf.gl_pathc - i - 1], "/cmdline");
  428     tn = fopen(name, "r");
  429     if (tn == NULL) continue; /* process vanished since glob() */
  430     while (k < MAXLINE - 1 && EOF != (c = fgetc(tn))) {
  431       P[j].cmd[k++] = c == '\0' ? ' ' : c;
  432     }
  433     if (k > 0) P[j].cmd[k] = '\0';
  434     fclose(tn);
  435     
  436 #ifdef DEBUG
  437     if (debug) fprintf(stderr,
  438                "uid=%5ld, name=%8s, pid=%5ld, ppid=%5ld, pgid=%5ld, thcount=%ld, cmd='%s'\n",
  439                P[j].uid, P[j].name, P[j].pid, P[j].ppid, P[j].pgid, P[j].thcount, P[j].cmd);
  440 #endif
  441     P[j].parent = P[j].child = P[j].sister = -1;
  442     P[j].print  = FALSE;
  443     j++;
  444   }
  445   globfree(&globbuf);
  446   return j;
  447 }
  448 #endif /* __linux */
  449 
  450 static int GetProcesses(void) {
  451   FILE *tn;
  452   int i = 0;
  453   char line[MAXLINE], command[] = PSCMD;
  454   
  455   /* file read code contributed by Paul Kern <pkern AT utcc.utoronto.ca> */
  456   if (input != NULL) {
  457     if (strcmp(input, "-") == 0)
  458       tn = stdin;
  459     else if (NULL == (tn = fopen(input,"r"))) {
  460       perror(input);
  461       exit(1);
  462     }
  463   } else {
  464 #ifdef DEBUG
  465     if (debug) fprintf(stderr, "calling '%s'\n", command);
  466 #endif
  467     if (NULL == (tn = (FILE*)popen(command,"r"))) {
  468       perror("Problems with pipe");
  469       exit(1);
  470     }
  471   }
  472 #ifdef DEBUG
  473   if (debug) fprintf(stderr, "popen:errno = %d\n", errno);
  474 #endif
  475   
  476   if (NULL == fgets(line, MAXLINE, tn)) { /* Throw away header line */
  477     fprintf(stderr, "No input.\n");
  478     exit(1);
  479   }
  480   
  481 #ifdef DEBUG
  482   if (debug) fputs(line, stderr);
  483 #endif
  484   
  485   P = malloc(sizeof(struct Proc));
  486   if (P == NULL) {
  487     fprintf(stderr, "Problems with malloc.\n");
  488     exit(1);
  489   }
  490   
  491   while (NULL != fgets(line, MAXLINE, tn)) {
  492     int len, num;
  493     len = strlen(line);
  494 #ifdef DEBUG
  495     if (debug) {
  496       fprintf(stderr, "len=%3d ", len);
  497       fputs(line, stderr);
  498     }
  499 #endif
  500     
  501     if (len == MAXLINE - 1) { /* line too long, drop remaining stuff */
  502       char tmp[MAXLINE];
  503       while (MAXLINE - 1 == strlen(fgets(tmp, MAXLINE, tn)));
  504     }      
  505     
  506     P = realloc(P, (i+1) * sizeof(struct Proc));
  507     if (P == NULL) {
  508       fprintf(stderr, "Problems with realloc.\n");
  509       exit(1);
  510     }
  511     
  512     memset(&P[i], 0, sizeof(*P));
  513     
  514 #ifdef solaris1x
  515     { /* SunOS allows columns to run together.  With the -j option, the CPU
  516        * time used can run into the numeric user id, so make sure there is
  517        * space between these two columns.  Also, the order of the desired
  518        * items is different. (L. Mark Larsen <mlarsen AT ptdcs2.intel.com>)
  519        */
  520       char buf1[45], buf2[MAXLINE];
  521       buf1[44] = '\0';
  522       sscanf(line, "%44c%[^\n]", buf1, buf2);
  523       snprintf(line, sizeof(line), "%s %s", buf1, buf2);
  524     }
  525 #endif
  526     
  527     num = sscanf(line, PSFORMAT, PSVARS);
  528     
  529     if (num != PSVARSN) {
  530 #ifdef DEBUG
  531       if (debug) fprintf(stderr, "dropped line, num=%d != %d\n", num, PSVARSN);
  532 #endif
  533       continue;
  534     }
  535     
  536 #ifdef UID2USER /* get username */
  537     uid2user(P[i].uid, P[i].name, sizeof(P[i].name));
  538 #endif
  539 
  540 #ifdef DEBUG
  541     if (debug) fprintf(stderr,
  542               "uid=%5ld, name=%8s, pid=%5ld, ppid=%5ld, pgid=%5ld, thcount=%ld, cmd='%s'\n",
  543               P[i].uid, P[i].name, P[i].pid, P[i].ppid, P[i].pgid, P[i].thcount, P[i].cmd);
  544 #endif
  545     P[i].parent = P[i].child = P[i].sister = -1;
  546     P[i].print  = FALSE;
  547     i++;
  548   }
  549   if (input != NULL)
  550     fclose(tn);
  551   else
  552     pclose(tn);
  553   return i;
  554 }
  555 
  556 static int GetRootPid(void) {
  557   int me;
  558   for (me = 0; me < NProc; me++) {
  559     if (P[me].pid == 1) return P[me].pid;
  560   }
  561   /* PID == 1 not found, so we'll take process with PPID == 0
  562    * Fix for TRU64 TruCluster with uniq PIDs
  563    * reported by Frank Parkin <fparki AT acxiom.co.uk>
  564    * re-reported by Eric van Doorn <Eric.van.Doorn AT isc.politie.nl>,
  565    * because fix was not published by me :-/ */
  566   for (me = 0; me < NProc; me++) {
  567     if (P[me].ppid == 0) return P[me].pid;
  568   }
  569   /* OK, still nothing found. Maybe it is FreeBSD and won't show foreign
  570    * processes. So we also accept PPID == 1 */
  571   for (me = 0; me < NProc; me++) {
  572     if (P[me].ppid == 1) return P[me].pid;
  573   }
  574   /* Still nothing. Maybe it is something like Solaris Zone. We'll take
  575    * the process with PID == PPID */
  576   for (me = 0; me < NProc; me++) {
  577     if (P[me].pid == P[me].ppid) return P[me].pid;
  578   }
  579   /* Should not happen */
  580   fprintf(stderr,
  581       "%s: No process found with PID == 1 || PPID == 0 || PPID == 1\n"
  582       "          || PID == PPID, contact author.\n",
  583       Progname);
  584   exit(1);
  585 }
  586 
  587 #ifdef ZOMBIES_HAVE_PID_0
  588 void FixZombies(void) {
  589   int me, num = 0;
  590   for (me = 0; me < NProc; me++) {
  591     if (P[me].pid == 0) num++;
  592   }
  593   if (num > 1) for (me = 0; me < NProc; me++) {
  594     if (P[me].pid == 0 && P[me].ppid != 0 && P[me].ppid != -1) {
  595       P[me].pid = -1;
  596 #ifdef DEBUG
  597       if (debug) fprintf(stderr,
  598              "fixed zombie %s with ppid %ld\n",
  599              P[me].cmd, (long)P[me].ppid);
  600 #endif
  601     }
  602   }
  603 }
  604 #endif
  605 
  606 int get_pid_index(long pid) {
  607   int me;
  608   for (me = NProc - 1;me >= 0 && P[me].pid != pid; me--); /* Search process */
  609   return me;
  610 }
  611 
  612 #define EXIST(idx) ((idx) != -1)
  613 
  614 static void MakeTree(void) {
  615   /* Build the process hierarchy. Every process marks itself as first child
  616    * of it's parent or as sister of first child of it's parent */
  617   int me;  
  618   for (me = 0; me < NProc; me++) {
  619     int parent;
  620     parent = get_pid_index(P[me].ppid);
  621     if (parent != me && parent != -1) { /* valid process, not me */
  622       P[me].parent = parent;
  623       if (P[parent].child == -1) /* first child */
  624     P[parent].child = me;
  625       else {
  626     int sister;
  627     for (sister = P[parent].child; EXIST(P[sister].sister); sister = P[sister].sister);
  628     P[sister].sister = me;
  629       }
  630     }
  631   }
  632 }
  633 
  634 static void MarkChildren(int me) {
  635   int child;
  636   P[me].print = TRUE;
  637   for (child = P[me].child; EXIST(child); child = P[child].sister)
  638     MarkChildren(child);
  639 }
  640 
  641 static void MarkProcs(void) {
  642   int me;
  643   for (me = 0; me < NProc; me++) {
  644     if (showall) {
  645       P[me].print = TRUE;
  646     } else {
  647       int parent;
  648       if (0 == strcmp(P[me].name, name)     /* for -u */
  649      || (Uoption &&
  650          0 != strcmp(P[me].name, "root"))   /* for -U */
  651      || P[me].pid == ipid           /* for -p */
  652      || (soption
  653          && NULL != strstr(P[me].cmd, str)
  654          && P[me].pid != MyPid)     /* for -s */
  655      ) {
  656     /* Mark parents */
  657     for (parent = P[me].parent; EXIST(parent); parent = P[parent].parent) {
  658       P[parent].print = TRUE;
  659     }
  660     /* Mark children */
  661     MarkChildren(me);
  662       }
  663     }
  664 #if 0 /* experimental thread compression */
  665     {
  666       int parent = P[me].parent;
  667       int ancestor; /* oldest parent with same cmd */
  668       if (0 == strcmp(P[me].cmd, P[parent].cmd)) {
  669     P[me].print = FALSE;
  670     for (parent = P[me].parent;
  671          EXIST(parent) && (0 == strcmp(P[me].cmd, P[parent].cmd));
  672          parent = P[parent].parent) {
  673       ancestor = parent;
  674     }
  675     fprintf(stderr, "%d: %d\n",
  676         P[me].pid,
  677         P[ancestor].pid);
  678     P[ancestor].thcount++;
  679       }
  680     }
  681 #endif
  682   }
  683 }
  684 
  685 static void DropProcs(void) {
  686   int me;
  687   for (me = 0; me < NProc; me++) if (P[me].print) {
  688     int child, sister;
  689     /* Drop children that won't print */
  690     for (child = P[me].child;
  691      EXIST(child) && !P[child].print; child = P[child].sister);
  692     P[me].child = child;
  693     /* Drop sisters that won't print */
  694     for (sister = P[me].sister;
  695      EXIST(sister) && !P[sister].print; sister = P[sister].sister);
  696     P[me].sister = sister;
  697   }
  698 }
  699 
  700 static void PrintTree(int idx, const char *head) {
  701   char nhead[MAXLINE], out[4 * MAXLINE], thread[16] = {'\0'};
  702   int child;
  703   
  704   if (head[0] == '\0' && !P[idx].print) return;
  705   /*if (!P[idx].print) return;*/
  706   
  707   if (P[idx].thcount > 1) snprintf(thread, sizeof(thread), "[%ld]", P[idx].thcount);
  708  
  709   if(atLdepth == maxLdepth) return;    /* LOPTION */
  710   ++atLdepth;                          /* LOPTION */
  711  
  712   
  713   snprintf(out, sizeof(out),
  714        "%s%s%s%s%s%s %05ld %s %s%s" /*" (ch=%d, si=%d, pr=%d)"*/,
  715        C->sg,
  716        head,
  717        head[0] == '\0' ? "" : EXIST(P[idx].sister) ? C->barc : C->barl,
  718        EXIST(P[idx].child)       ? C->p   : C->s2,
  719        P[idx].pid == P[idx].pgid ? C->pgl : C->npgl,
  720        C->eg,
  721        P[idx].pid, P[idx].name,
  722        thread,
  723        P[idx].cmd
  724        /*,P[idx].child,P[idx].sister,P[idx].print*/);
  725   
  726   out[Columns-1] = '\0';
  727   puts(out);
  728   
  729   /* Process children */
  730   snprintf(nhead, sizeof(nhead), "%s%s ", head,
  731        head[0] == '\0' ? "" : EXIST(P[idx].sister) ? C->bar : " ");
  732 
  733   /*
  734     if ( compress ) {
  735     int c1, c2, flag = 0;
  736     for ( c1 = P[idx].child; EXIST(c1); c1 = P[c1].sister ) {
  737       for ( c2 = P[c1].sister; EXIST(c2); c2 = P[c2].sister ) {
  738     if ( 0 == strcmp(P[c1].cmd, P[c2].cmd) ) {
  739       flag = 1;
  740       printf("%d:%d ", c1, c2);
  741       P[c1].pid = -1;
  742       P[c2].print = FALSE;
  743     }
  744       }
  745     }
  746     if ( flag ) printf("\n");
  747   }
  748   */
  749 
  750   for (child = P[idx].child; EXIST(child); child = P[child].sister) {
  751     PrintTree(child, nhead);
  752   }
  753 
  754   --atLdepth;                          /* LOPTION */
  755 
  756 }
  757 
  758 static void Usage(void) {
  759   fprintf(stderr,
  760       "%s\n"
  761       "%s\n\n"
  762       "Usage: %s "
  763 #ifdef DEBUG
  764       "[-d] "
  765 #endif
  766       "[-f file] [-g n] [-l n] [-u user] [-U] [-s string] [-p pid] [-w] [pid ...]\n"
  767       /*"   -a        align output\n"*/
  768 #ifdef DEBUG
  769       "   -d        print debugging info to stderr\n"
  770 #endif
  771       "   -f file   read input from <file> (- is stdin) instead of running\n"
  772       "             \"%s\"\n"
  773       "   -g n      use graphics chars for tree. n=1: IBM-850, n=2: VT100, n=3: UTF-8\n"
  774       "   -l n      print tree to n level deep\n"
  775       "   -u user   show only branches containing processes of <user>\n"
  776       "   -U        don't show branches containing only root processes\n"
  777           "   -s string show only branches containing process with <string> in commandline\n"
  778           "   -p pid    show only branches containing process <pid>\n"
  779       "   -w        wide output, not truncated to window width\n"
  780       "   pid ...   process ids to start from, default is 1 (probably init)\n"
  781       , WhatString[0] + 4, WhatString[1] + 4, Progname, PSCMD);
  782 #ifdef HAS_PGID
  783   fprintf(stderr, "\n%sProcess group leaders are marked with '%s%s%s'.\n",
  784       C->init, C->sg, C->pgl, C->eg);
  785 #endif
  786   exit(1);
  787 }
  788 
  789 int main(int argc, char **argv) {
  790   extern int optind;
  791   extern char *optarg;
  792   int ch;
  793   long pid;
  794   int graph = G_ASCII, wide = FALSE;
  795   
  796   C = &TreeChars[graph];
  797   
  798   Progname = strrchr(argv[0],'/');
  799   Progname = (NULL == Progname) ? argv[0] : Progname + 1;
  800   
  801   while ((ch = getopt(argc, argv, "cdf:g:hl:p:s:u:Uw?")) != EOF)
  802     switch(ch) {
  803       /*case 'a':
  804     align   = TRUE;
  805     break;*/
  806     case 'c':
  807       compress = TRUE;
  808       break;
  809 #ifdef DEBUG
  810     case 'd':
  811       debug   = TRUE;
  812       break;
  813 #endif
  814     case 'f':
  815       input   = optarg;
  816       break;
  817     case 'g':
  818       graph   = atoi(optarg);
  819       if (graph < 0 || graph >= G_LAST) {
  820     fprintf(stderr, "%s: Invalid graph parameter.\n",
  821         Progname);
  822     exit(1);
  823       }
  824       C = &TreeChars[graph];
  825       break;
  826     case 'l':                                 /* LOPTION */
  827       maxLdepth = atoi(optarg);               /* LOPTION */
  828       if(maxLdepth < 1) maxLdepth = 1;        /* LOPTION */
  829       break;                                  /* LOPTION */
  830     case 'p':
  831       showall = FALSE;
  832       ipid    = atoi(optarg);
  833       break;
  834     case 's':
  835       showall = FALSE;
  836       soption = TRUE;
  837       str     = optarg;
  838       break;
  839     case 'u':
  840       showall = FALSE;
  841       name    = optarg;
  842       if (
  843 #ifdef solaris2x
  844      (int)
  845 #endif
  846      NULL == getpwnam(name)) {
  847     fprintf(stderr, "%s: User '%s' does not exist.\n",
  848         Progname, name);
  849     exit(1);
  850       }
  851       break;
  852     case 'U':
  853       showall = FALSE;
  854       Uoption = TRUE;
  855       break;
  856     case 'w':
  857       wide    = TRUE;
  858       break;
  859     case 'h':
  860     case '?':
  861     default :
  862       Usage();
  863       break;
  864     }
  865   
  866 #ifdef USE_GetProcessesDirect
  867   NProc = input == NULL ? GetProcessesDirect() : GetProcesses();
  868 #else
  869   NProc = GetProcesses();
  870 #endif
  871   
  872 #ifdef ZOMBIES_HAVE_PID_0
  873   FixZombies();
  874 #endif
  875   
  876   if (NProc == 0) {
  877     fprintf(stderr, "%s: No processes read.\n", Progname);
  878     exit(1);
  879   }
  880 
  881 #ifdef DEBUG
  882   if (debug) fprintf(stderr, "NProc = %d processes found.\n", NProc);
  883 #endif
  884   
  885   RootPid = GetRootPid();
  886 
  887 #ifdef DEBUG
  888   if (debug) fprintf(stderr, "RootPid = %d.\n", RootPid);
  889 #endif
  890 
  891 #if defined(UID2USER) && defined(DEBUG)
  892   if (debug) uid2user(0,NULL,0);
  893 #endif
  894   MyPid = getpid();
  895   
  896   if (wide)
  897     Columns = MAXLINE - 1;
  898   else {
  899 #if defined (HAS_TERMDEF)
  900     Columns = atoi((char*)termdef(fileno(stdout),'c'));
  901 #elif defined(TIOCGWINSZ)
  902     struct winsize winsize;
  903     if ( ioctl(fileno(stdout), TIOCGWINSZ, &winsize) != -1 )
  904       Columns = winsize.ws_col;
  905 #elif defined(TIOCGSIZE)
  906     struct ttysize ttysize;
  907     if ( ioctl(fileno(stdout), TIOCGSIZE, &ttysize) != -1 )
  908       Columns = ttysize.ts_cols;
  909 #else
  910     char *env = getenv("COLUMNS");
  911     Columns = env ? atoi(env) : 80;
  912 #endif
  913   }
  914   if (Columns == 0) Columns = MAXLINE - 1;
  915   
  916   printf("%s", C->init);
  917   
  918   Columns += strlen(C->sg) + strlen(C->eg); /* Don't count hidden chars */
  919 
  920   if (Columns >= MAXLINE) Columns = MAXLINE - 1;
  921   
  922 #ifdef DEBUG
  923   if (debug) fprintf(stderr, "Columns = %d\n", Columns);
  924 #endif
  925   
  926   MakeTree();
  927   MarkProcs();
  928   DropProcs();
  929   
  930   if (argc == optind) { /* No pids */
  931     PrintTree(get_pid_index(RootPid), "");
  932   } else while (optind < argc) {
  933     int idx;
  934     pid = (long)atoi(argv[optind]);
  935     idx = get_pid_index(pid);
  936     if (idx > -1) PrintTree(idx, "");
  937     optind++;
  938   }
  939   free(P);
  940   return 0;
  941 }
  942 
  943 #ifdef NEED_STRSTR
  944 /* Contributed by Paul Kern <pkern AT utcc.utoronto.ca> */
  945 static char * strstr(s1, s2)
  946      register char *s1, *s2;
  947 {
  948   register int n1, n2;
  949   
  950   if (n2 = strlen(s2))
  951     for (n1 = strlen(s1); n1 >= n2; s1++, n1--)
  952       if (strncmp(s1, s2, n2) == 0)
  953     return s1;
  954   return NULL;
  955 }
  956 #endif /* NEED_STRSTR */
  957 
  958 #ifdef NEED_SNPRINTF
  959 int snprintf (char *s, int namesiz, char *format, ...) {      
  960   /* Original portable version by Michael E. White.
  961      This version of Stan Sieler (sieler AT allegro.com) */
  962 
  963   int  chars_needed;              /* not including trailing null */
  964   
  965   char bigbuf [1024] = {'\0'};    /* note: 1024 is a guess, and may not be large enough! */
  966   
  967   va_list ap;         /* some systems allow "va_list ap = NULL;", others *do not* (like MACH) */
  968   
  969   va_start (ap, format);
  970   chars_needed = vsprintf (bigbuf, format, ap); /* note: chars_needed does not include trailing null */
  971   va_end (ap);
  972 
  973   /* 0 is documented as "don't write anything" ... while not specifically spelled out
  974      (e.g., does it also mean "don't internally call vsprintf"?), one can imply that it simply means
  975      "don't write to the output buffer 's'.  (Otherwise, if we didn't call vsprintf, we wouldn't
  976      know what value of chars_needed to return!) */
  977 
  978    if (namesiz <= 0)
  979      ;     /* Don't touch 's' buffer at all! Note: on some systems, a negative namesiz
  980           will cause the process to abort. By checking for <= 0, not just 0, we differ
  981           in that area, but it's a reasonable difference. */
  982    
  983    else if (chars_needed >= namesiz)  
  984      {     /* oh oh, output too large for 'name' buffer... */
  985        memcpy (s, bigbuf, namesiz - 1);
  986        s [namesiz - 1] = '\0';
  987      }
  988    
  989    else    /* size is ok */
  990      {
  991        memcpy (s, bigbuf, chars_needed); /* chars_needed < namesiz */
  992        s [chars_needed] = '\0';
  993        /* note: above two could be replaced by strcpy (s, bigbuf)
  994       since we know strlen (bigbuf) is acceptable.  
  995       But, why copy byte at a time, comparing to null, when
  996       we *know* the length? */
  997      }
  998    
  999    return chars_needed;    /* May be larger than namesiz, but that's ok
 1000                   In fact, not just 'ok', it's *useful*! */
 1001 }
 1002 #endif  /* NEED_SNPRINTF */
 1003 
 1004 /*
 1005  * $Log: pstree.c,v $
 1006  * Revision 2.39  2015/05/13 12:24:47  fred
 1007  * Summary: Don't use uninitialized structs when ioctl() fails, e.g. if run with stdout
 1008  * redirected. Problem reported by Jan Stary.
 1009  *
 1010  * Revision 2.38  2015/04/20 14:50:42  fred
 1011  * Summary: Added patch for AIX61 contributed by Michael Staats
 1012  *
 1013  * Revision 2.37  2015/04/20 10:15:29  fred
 1014  * Summary: V2.36
 1015  *
 1016  * Revision 2.36  2013-04-12 11:47:03+02  fred
 1017  * Some processes like apache under a recent Linux were listed with UID
 1018  * root instead of the correct UID, as they use setuid(). We now read the
 1019  * UID from the owner of /proc/PID instead of /proc/PID/stat, as this
 1020  * seems to be updated correctly. Thanks to Tom Schmidt
 1021  * <tschmidt AT micron.com> for pointing out this bug.
 1022  *
 1023  * Revision 2.35  2013-02-28 08:33:02+01  fred
 1024  * Added Stan Sieler's fix to my adaption of snprintf fix by Stan Sieler :-)
 1025  *
 1026  * Revision 2.34  2013-02-27 16:57:25+01  fred
 1027  * Added snprintf fix by Stan Sieler
 1028  *
 1029  * Revision 2.33  2009-11-10 22:12:39+01  fred
 1030  * Added UTF8, enlarged MAXLINE
 1031  *
 1032  * Revision 2.32  2007-10-26 21:39:50+02  fred
 1033  * Added option -l provided by Michael E. White <mewhite AT us.ibm.com>
 1034  *
 1035  * Revision 2.31  2007-06-08 17:45:23+02  fred
 1036  * Fixed problem with users with long login name (Reported by Oleg A. Mamontov)
 1037  *
 1038  * Revision 2.30  2007-05-10 23:13:04+02  fred
 1039  * *** empty log message ***
 1040  *
 1041  * Revision 2.29  2007-05-10 22:37:13+02  fred
 1042  * Added fix for Solaris Zone and bug fix from Philippe Torche
 1043  *
 1044  * Revision 2.28  2007-05-10 22:01:07+02  fred
 1045  * Added new determination of window width
 1046  *
 1047  * Revision 2.27  2005-04-08 22:08:45+02  fred
 1048  * Also accept PPID==1 if nothing else is found. Should fix problem with
 1049  * FreeBSD and security.bsd.see_other_uids=0.
 1050  *
 1051  * Revision 2.26  2004-10-15 13:59:03+02  fred
 1052  * Fixed small bug with char/int variable c
 1053  * reported by Tomas Dvorak <tomas_dvorak AT mailcan.com>
 1054  *
 1055  * Revision 2.25  2004-05-14 16:41:39+02  fred
 1056  * Added workaround for spurious blank lines in ps output under AIX 5.2
 1057  * reported by Dean Rowswell <rowswell AT ca.ibm.com>
 1058  *
 1059  * Revision 2.24  2004-04-14 09:10:29+02  fred
 1060  * *** empty log message ***
 1061  *
 1062  * Revision 2.23  2004-02-16 10:55:20+01  fred
 1063  * Fix for zombies (pid == 0) under FreeBSD
 1064  *
 1065  * Revision 2.22  2003-12-12 10:58:46+01  fred
 1066  * Added support for TRU64 v5.1b TruCluster
 1067  *
 1068  * Revision 2.21  2003-10-06 13:55:47+02  fred
 1069  * Fixed SEGV under Linux when process table changes during run
 1070  *
 1071  * Revision 2.20  2003-07-09 20:07:29+02  fred
 1072  * cosmetic
 1073  *
 1074  * Revision 2.19  2003/05/26 15:33:35  fred
 1075  * Merged FreeBSD, (Open|Net)BSD; added Darwin (APPLE), fixed wide output
 1076  * in FreeBSD
 1077  *
 1078  * Revision 2.18  2003/03/13 18:53:22  fred
 1079  * Added getenv("COLUMNS"), cosmetic changes
 1080  *
 1081  * Revision 2.17  2001/12/17 12:18:02  fred
 1082  * Changed ps call to something like ps -eo uid,pid,ppid,pgid,args under
 1083  * AIX and Linux, workaround for AIX 5L.
 1084  *
 1085  * Revision 2.17  2001-12-13 08:27:00+08  chris
 1086  * Added workaround for AIX Version >= 5
 1087  *
 1088  * Revision 2.16  2000-03-01 10:42:22+01  fred
 1089  * Added support for thread count (thcount) in other OSs than AIX
 1090  *
 1091  * Revision 2.15  2000-03-01 10:18:56+01  fred
 1092  * Added process group support for {Net|Open}BSD following a suggestion
 1093  * by Ralf Meyer <ralf AT thp.Uni-Duisburg.de>
 1094  *
 1095  * Revision 2.14  1999-03-22 20:45:02+01  fred
 1096  * Fixed bug when line longer than MAXLINE, set MAXLINE=512
 1097  *
 1098  * Revision 2.13  1998-12-17 19:31:53+01  fred
 1099  * Fixed problem with option -f when input file is empty
 1100  *
 1101  * Revision 2.12  1998-12-07 17:08:59+01  fred
 1102  * Added -f option and sun 68000 support by Paul Kern
 1103  * <pkern AT utcc.utoronto.ca>
 1104  *
 1105  * Revision 2.11  1998-05-23 13:30:28+02  fred
 1106  * Added vt100 sequences, NetBSD support
 1107  *
 1108  * Revision 2.10  1998-02-02 15:04:57+01  fred
 1109  * Fixed bug in MakeTree()/get_pid_index() when parent doesn't
 1110  * exist. Thanks to Igor Schein <igor AT andrew.air-boston.com> for the bug
 1111  * report.
 1112  *
 1113  * Revision 2.9  1998-01-07 16:55:26+01  fred
 1114  * Added support for getprocs()
 1115  *
 1116  * Revision 2.9  1998-01-06 17:13:19+01  fred
 1117  * Added support for getprocs() under AIX
 1118  *
 1119  * Revision 2.8  1997-10-22 15:09:39+02  fred
 1120  * Cosmetic
 1121  *
 1122  * Revision 2.7  1997-10-22 15:01:40+02  fred
 1123  * Minor changes in getprocs for AIX
 1124  *
 1125  * Revision 2.6  1997/10/16 16:35:19  fred
 1126  * Added uid2name() caching username lookup, added patch for Solaris 2.x
 1127  *
 1128  * Revision 2.5  1997-02-05 14:24:53+01  fred
 1129  * return PrintTree when nothing to do.
 1130  *
 1131  * Revision 2.4  1997/02/05 09:54:08  fred
 1132  * Fixed bug when P[i].cmd is empty
 1133  *
 1134  * Revision 2.3  1997-02-04 18:40:54+01  fred
 1135  * Cosmetic
 1136  *
 1137  * Revision 2.2  1997-02-04 14:11:17+01  fred
 1138  * *** empty log message ***
 1139  *
 1140  * Revision 2.1  1997-02-04 13:55:14+01  fred
 1141  * Rewritten
 1142  *
 1143  * Revision 1.13  1997-02-04 09:01:59+01  fred
 1144  * Start of rewrite
 1145  *
 1146  * Revision 1.12  1996-09-17 21:54:05+02  fred
 1147  * *** empty log message ***
 1148  *
 1149  * Revision 1.11  1996-09-17 21:52:52+02  fred
 1150  * revision added
 1151  *
 1152  * Revision 1.10  1996-09-17 21:45:35+02  fred
 1153  * replace \n and \t with ? in output
 1154  *
 1155  * Revision 1.4  1996-09-17 21:43:14+02  fred
 1156  * Moved under RCS, replace \n and \t with ?
 1157  */