1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License (the "License").
   6  * You may not use this file except in compliance with the License.
   7  *
   8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   9  * or http://www.opensolaris.org/os/licensing.
  10  * See the License for the specific language governing permissions
  11  * and limitations under the License.
  12  *
  13  * When distributing Covered Code, include this CDDL HEADER in each
  14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15  * If applicable, add the following below this CDDL HEADER, with the
  16  * fields enclosed by brackets "[]" replaced with your own identifying
  17  * information: Portions Copyright [yyyy] [name of copyright owner]
  18  *
  19  * CDDL HEADER END
  20  */
  21 /*
  22  * Copyright (c) 2013 Gary Mills
  23  *
  24  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
  25  * Use is subject to license terms.
  26  */
  27 
  28 /*      Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
  29 /*        All Rights Reserved   */
  30 
  31 /*
  32  * University Copyright- Copyright (c) 1982, 1986, 1988
  33  * The Regents of the University of California
  34  * All Rights Reserved
  35  *
  36  * University Acknowledgment- Portions of this document are derived from
  37  * software developed by the University of California, Berkeley, and its
  38  * contributors.
  39  */
  40 
  41 /*
  42  * This is the new whodo command which takes advantage of
  43  * the /proc interface to gain access to the information
  44  * of all the processes currently on the system.
  45  *
  46  * Maintenance note:
  47  *
  48  * Much of this code is replicated in w.c.  If you're
  49  * fixing bugs here, then you should probably fix 'em there too.
  50  */
  51 
  52 #include <stdio.h>
  53 #include <string.h>
  54 #include <stdlib.h>
  55 #include <ctype.h>
  56 #include <fcntl.h>
  57 #include <time.h>
  58 #include <errno.h>
  59 #include <sys/types.h>
  60 #include <utmpx.h>
  61 #include <sys/utsname.h>
  62 #include <sys/stat.h>
  63 #include <sys/mkdev.h>
  64 #include <dirent.h>
  65 #include <procfs.h>               /* /proc header file */
  66 #include <sys/wait.h>
  67 #include <locale.h>
  68 #include <unistd.h>
  69 #include <limits.h>
  70 #include <priv_utils.h>
  71 
  72 /*
  73  * Use the full lengths from utmpx for user and line.
  74  */
  75 #define NMAX    (sizeof (((struct utmpx *)0)->ut_user))
  76 #define LMAX    (sizeof (((struct utmpx *)0)->ut_line))
  77 
  78 /* Print minimum field widths. */
  79 #define LOGIN_WIDTH     8
  80 #define LINE_WIDTH      8
  81 
  82 #define DIV60(t)        ((t+30)/60)    /* x/60 rounded */
  83 
  84 #ifdef ERR
  85 #undef ERR
  86 #endif
  87 #define ERR             (-1)
  88 
  89 #define DEVNAMELEN      14
  90 #define HSIZE           256             /* size of process hash table */
  91 #define PROCDIR         "/proc"
  92 #define INITPROCESS     (pid_t)1        /* init process pid */
  93 #define NONE            'n'             /* no state */
  94 #define RUNNING         'r'             /* runnable process */
  95 #define ZOMBIE          'z'             /* zombie process */
  96 #define VISITED         'v'             /* marked node as visited */
  97 
  98 static int      ndevs;                  /* number of configured devices */
  99 static int      maxdev;                 /* slots for configured devices */
 100 #define DNINCR  100
 101 static struct devl {                    /* device list   */
 102         char    dname[DEVNAMELEN];      /* device name   */
 103         dev_t   ddev;                   /* device number */
 104 } *devl;
 105 
 106 struct uproc {
 107         pid_t   p_upid;                 /* user process id */
 108         char    p_state;                /* numeric value of process state */
 109         dev_t   p_ttyd;                 /* controlling tty of process */
 110         time_t  p_time;                 /* ticks of user & system time */
 111         time_t  p_ctime;                /* ticks of child user & system time */
 112         int     p_igintr;               /* 1=ignores SIGQUIT and SIGINT */
 113         char    p_comm[PRARGSZ+1];      /* command */
 114         char    p_args[PRARGSZ+1];      /* command line arguments */
 115         struct uproc    *p_child,       /* first child pointer */
 116                         *p_sibling,     /* sibling pointer */
 117                         *p_pgrplink,    /* pgrp link */
 118                         *p_link;        /* hash table chain pointer */
 119 };
 120 
 121 /*
 122  *      define  hash table for struct uproc
 123  *      Hash function uses process id
 124  *      and the size of the hash table(HSIZE)
 125  *      to determine process index into the table.
 126  */
 127 static struct uproc     pr_htbl[HSIZE];
 128 
 129 static struct   uproc   *findhash(pid_t);
 130 static time_t   findidle(char *);
 131 static void     clnarglist(char *);
 132 static void     showproc(struct uproc *);
 133 static void     showtotals(struct uproc *);
 134 static void     calctotals(struct uproc *);
 135 static char     *getty(dev_t);
 136 static void     prttime(time_t, int);
 137 static void     prtat(time_t *);
 138 
 139 static char     *prog;
 140 static int      header = 1;     /* true if -h flag: don't print heading */
 141 static int      lflag = 0;      /* true if -l flag: w command format */
 142 static char     *sel_user;      /* login of particular user selected */
 143 static time_t   now;            /* current time of day */
 144 static time_t   uptime;         /* time of last reboot & elapsed time since */
 145 static int      nusers;         /* number of users logged in now */
 146 static time_t   idle;           /* number of minutes user is idle */
 147 static time_t   jobtime;        /* total cpu time visible */
 148 static char     doing[520];     /* process attached to terminal */
 149 static time_t   proctime;       /* cpu time of process in doing */
 150 static int      empty;
 151 static pid_t    curpid;
 152 
 153 #if SIGQUIT > SIGINT
 154 #define ACTSIZE SIGQUIT
 155 #else
 156 #define ACTSIZE SIGINT
 157 #endif
 158 
 159 int
 160 main(int argc, char *argv[])
 161 {
 162         struct utmpx    *ut;
 163         struct utmpx    *utmpbegin;
 164         struct utmpx    *utmpend;
 165         struct utmpx    *utp;
 166         struct tm               *tm;
 167         struct uproc    *up, *parent, *pgrp;
 168         struct psinfo   info;
 169         struct sigaction actinfo[ACTSIZE];
 170         struct pstatus  statinfo;
 171         size_t          size;
 172         struct stat     sbuf;
 173         struct utsname  uts;
 174         DIR             *dirp;
 175         struct  dirent  *dp;
 176         char            pname[64];
 177         char            *fname;
 178         int             procfd;
 179         int             i;
 180         int             days, hrs, mins;
 181         int             entries;
 182 
 183         /*
 184          * This program needs the proc_owner privilege
 185          */
 186         (void) __init_suid_priv(PU_CLEARLIMITSET, PRIV_PROC_OWNER,
 187             (char *)NULL);
 188 
 189         (void) setlocale(LC_ALL, "");
 190 #if !defined(TEXT_DOMAIN)
 191 #define TEXT_DOMAIN "SYS_TEST"
 192 #endif
 193         (void) textdomain(TEXT_DOMAIN);
 194 
 195         prog = argv[0];
 196 
 197         while (argc > 1) {
 198                 if (argv[1][0] == '-') {
 199                         for (i = 1; argv[1][i]; i++) {
 200                                 switch (argv[1][i]) {
 201 
 202                                 case 'h':
 203                                         header = 0;
 204                                         break;
 205 
 206                                 case 'l':
 207                                         lflag++;
 208                                         break;
 209 
 210                                 default:
 211                                         (void) printf(gettext(
 212                                             "usage: %s [ -hl ] [ user ]\n"),
 213                                             prog);
 214                                         exit(1);
 215                                 }
 216                         }
 217                 } else {
 218                         if (!isalnum(argv[1][0]) || argc > 2) {
 219                                 (void) printf(gettext(
 220                                     "usage: %s [ -hl ] [ user ]\n"), prog);
 221                                 exit(1);
 222                         } else
 223                                 sel_user = argv[1];
 224                 }
 225                 argc--; argv++;
 226         }
 227 
 228         /*
 229          * read the UTMPX_FILE (contains information about
 230          * each logged in user)
 231          */
 232         if (stat(UTMPX_FILE, &sbuf) == ERR) {
 233                 (void) fprintf(stderr, gettext("%s: stat error of %s: %s\n"),
 234                     prog, UTMPX_FILE, strerror(errno));
 235                 exit(1);
 236         }
 237         entries = sbuf.st_size / sizeof (struct futmpx);
 238         size = sizeof (struct utmpx) * entries;
 239 
 240         if ((ut = malloc(size)) == NULL) {
 241                 (void) fprintf(stderr, gettext("%s: malloc error of %s: %s\n"),
 242                     prog, UTMPX_FILE, strerror(errno));
 243                 exit(1);
 244         }
 245 
 246         (void) utmpxname(UTMPX_FILE);
 247 
 248         utmpbegin = ut;
 249         /* LINTED pointer cast may result in improper alignment */
 250         utmpend = (struct utmpx *)((char *)utmpbegin + size);
 251 
 252         setutxent();
 253         while ((ut < utmpend) && ((utp = getutxent()) != NULL))
 254                 (void) memcpy(ut++, utp, sizeof (*ut));
 255         endutxent();
 256 
 257         (void) time(&now);  /* get current time */
 258 
 259         if (header) {   /* print a header */
 260                 if (lflag) {    /* w command format header */
 261                         prtat(&now);
 262                         for (ut = utmpbegin; ut < utmpend; ut++) {
 263                                 if (ut->ut_type == USER_PROCESS) {
 264                                         nusers++;
 265                                 } else if (ut->ut_type == BOOT_TIME) {
 266                                         uptime = now - ut->ut_xtime;
 267                                         uptime += 30;
 268                                         days = uptime / (60*60*24);
 269                                         uptime %= (60*60*24);
 270                                         hrs = uptime / (60*60);
 271                                         uptime %= (60*60);
 272                                         mins = uptime / 60;
 273 
 274                                         (void) printf(dcgettext(NULL,
 275                                             "  up %d day(s), %d hr(s), "
 276                                             "%d min(s)", LC_TIME),
 277                                             days, hrs, mins);
 278                                 }
 279                         }
 280 
 281                         ut = utmpbegin; /* rewind utmp data */
 282                         (void) printf(dcgettext(NULL,
 283                             "  %d user(s)\n", LC_TIME), nusers);
 284                         (void) printf(dcgettext(NULL, "User     tty      "
 285                             "login@      idle    JCPU    PCPU    what\n",
 286                             LC_TIME));
 287                 } else {        /* standard whodo header */
 288                         char date_buf[100];
 289 
 290                         /*
 291                          * print current time and date
 292                          */
 293                         (void) strftime(date_buf, sizeof (date_buf),
 294                             "%c", localtime(&now));
 295                         (void) printf("%s\n", date_buf);
 296 
 297                         /*
 298                          * print system name
 299                          */
 300                         (void) uname(&uts);
 301                         (void) printf("%s\n", uts.nodename);
 302                 }
 303         }
 304 
 305         /*
 306          * loop through /proc, reading info about each process
 307          * and build the parent/child tree
 308          */
 309         if (!(dirp = opendir(PROCDIR))) {
 310                 (void) fprintf(stderr, gettext("%s: could not open %s: %s\n"),
 311                     prog, PROCDIR, strerror(errno));
 312                 exit(1);
 313         }
 314 
 315         while ((dp = readdir(dirp)) != NULL) {
 316                 if (dp->d_name[0] == '.')
 317                         continue;
 318 retry:
 319                 (void) snprintf(pname, sizeof (pname),
 320                     "%s/%s/", PROCDIR, dp->d_name);
 321                 fname = pname + strlen(pname);
 322                 (void) strcpy(fname, "psinfo");
 323                 if ((procfd = open(pname, O_RDONLY)) < 0)
 324                         continue;
 325                 if (read(procfd, &info, sizeof (info)) != sizeof (info)) {
 326                         int err = errno;
 327                         (void) close(procfd);
 328                         if (err == EAGAIN)
 329                                 goto retry;
 330                         if (err != ENOENT)
 331                                 (void) fprintf(stderr, gettext(
 332                                     "%s: read() failed on %s: %s\n"),
 333                                     prog, pname, strerror(err));
 334                         continue;
 335                 }
 336                 (void) close(procfd);
 337 
 338                 up = findhash(info.pr_pid);
 339                 up->p_ttyd = info.pr_ttydev;
 340                 up->p_state = (info.pr_nlwp == 0? ZOMBIE : RUNNING);
 341                 up->p_time = 0;
 342                 up->p_ctime = 0;
 343                 up->p_igintr = 0;
 344                 (void) strncpy(up->p_comm, info.pr_fname,
 345                     sizeof (info.pr_fname));
 346                 up->p_args[0] = 0;
 347 
 348                 if (up->p_state != NONE && up->p_state != ZOMBIE) {
 349                         (void) strcpy(fname, "status");
 350 
 351                         /* now we need the proc_owner privilege */
 352                         (void) __priv_bracket(PRIV_ON);
 353 
 354                         procfd = open(pname, O_RDONLY);
 355 
 356                         /* drop proc_owner privilege after open */
 357                         (void) __priv_bracket(PRIV_OFF);
 358 
 359                         if (procfd  < 0)
 360                                 continue;
 361 
 362                         if (read(procfd, &statinfo, sizeof (statinfo))
 363                             != sizeof (statinfo)) {
 364                                 int err = errno;
 365                                 (void) close(procfd);
 366                                 if (err == EAGAIN)
 367                                         goto retry;
 368                                 if (err != ENOENT)
 369                                         (void) fprintf(stderr, gettext(
 370                                             "%s: read() failed on %s: %s \n"),
 371                                             prog, pname, strerror(err));
 372                                 continue;
 373                         }
 374                         (void) close(procfd);
 375 
 376                         up->p_time = statinfo.pr_utime.tv_sec +
 377                             statinfo.pr_stime.tv_sec;
 378                         up->p_ctime = statinfo.pr_cutime.tv_sec +
 379                             statinfo.pr_cstime.tv_sec;
 380 
 381                         (void) strcpy(fname, "sigact");
 382 
 383                         /* now we need the proc_owner privilege */
 384                         (void) __priv_bracket(PRIV_ON);
 385 
 386                         procfd = open(pname, O_RDONLY);
 387 
 388                         /* drop proc_owner privilege after open */
 389                         (void) __priv_bracket(PRIV_OFF);
 390 
 391                         if (procfd  < 0)
 392                                 continue;
 393                         if (read(procfd, actinfo, sizeof (actinfo))
 394                             != sizeof (actinfo)) {
 395                                 int err = errno;
 396                                 (void) close(procfd);
 397                                 if (err == EAGAIN)
 398                                         goto retry;
 399                                 if (err != ENOENT)
 400                                         (void) fprintf(stderr, gettext(
 401                                             "%s: read() failed on %s: %s \n"),
 402                                             prog, pname, strerror(err));
 403                                 continue;
 404                         }
 405                         (void) close(procfd);
 406 
 407                         up->p_igintr =
 408                             actinfo[SIGINT-1].sa_handler == SIG_IGN &&
 409                             actinfo[SIGQUIT-1].sa_handler == SIG_IGN;
 410 
 411                         up->p_args[0] = 0;
 412 
 413                         /*
 414                          * Process args if there's a chance we'll print it.
 415                          */
 416                         if (lflag) { /* w command needs args */
 417                                 clnarglist(info.pr_psargs);
 418                                 (void) strcpy(up->p_args, info.pr_psargs);
 419                                 if (up->p_args[0] == 0 ||
 420                                     up->p_args[0] == '-' &&
 421                                     up->p_args[1] <= ' ' ||
 422                                     up->p_args[0] == '?') {
 423                                         (void) strcat(up->p_args, " (");
 424                                         (void) strcat(up->p_args, up->p_comm);
 425                                         (void) strcat(up->p_args, ")");
 426                                 }
 427                         }
 428 
 429                 }
 430 
 431                 /*
 432                  * link pgrp together in case parents go away
 433                  * Pgrp chain is a single linked list originating
 434                  * from the pgrp leader to its group member.
 435                  */
 436                 if (info.pr_pgid != info.pr_pid) {      /* not pgrp leader */
 437                         pgrp = findhash(info.pr_pgid);
 438                         up->p_pgrplink = pgrp->p_pgrplink;
 439                         pgrp->p_pgrplink = up;
 440                 }
 441                 parent = findhash(info.pr_ppid);
 442 
 443                 /* if this is the new member, link it in */
 444                 if (parent->p_upid != INITPROCESS) {
 445                         if (parent->p_child) {
 446                                 up->p_sibling = parent->p_child;
 447                                 up->p_child = 0;
 448                         }
 449                         parent->p_child = up;
 450                 }
 451 
 452         }
 453 
 454         /* revert to non-privileged user */
 455         (void) __priv_relinquish();
 456 
 457         (void) closedir(dirp);
 458         (void) time(&now);  /* get current time */
 459 
 460         /*
 461          * loop through utmpx file, printing process info
 462          * about each logged in user
 463          */
 464         for (ut = utmpbegin; ut < utmpend; ut++) {
 465                 time_t tim;
 466 
 467                 if (ut->ut_type != USER_PROCESS)
 468                         continue;
 469                 if (sel_user && strncmp(ut->ut_name, sel_user, NMAX) != 0)
 470                         continue;       /* we're looking for somebody else */
 471                 if (lflag) {    /* -l flag format (w command) */
 472                         /* print login name of the user */
 473                         (void) printf("%-*.*s ", LOGIN_WIDTH, (int)NMAX,
 474                             ut->ut_name);
 475 
 476                         /* print tty user is on */
 477                         (void) printf("%-*.*s ", LINE_WIDTH, (int)LMAX,
 478                             ut->ut_line);
 479 
 480                         /* print when the user logged in */
 481                         tim = ut->ut_xtime;
 482                         (void) prtat(&tim);
 483 
 484                         /* print idle time */
 485                         idle = findidle(ut->ut_line);
 486                         prttime(idle, 8);
 487                         showtotals(findhash((pid_t)ut->ut_pid));
 488                 } else {        /* standard whodo format */
 489                         tim = ut->ut_xtime;
 490                         tm = localtime(&tim);
 491                         (void) printf("\n%-*.*s %-*.*s %2.1d:%2.2d\n",
 492                             LINE_WIDTH, (int)LMAX, ut->ut_line,
 493                             LOGIN_WIDTH, (int)NMAX, ut->ut_name, tm->tm_hour,
 494                             tm->tm_min);
 495                         showproc(findhash((pid_t)ut->ut_pid));
 496                 }
 497         }
 498 
 499         return (0);
 500 }
 501 
 502 /*
 503  * Used for standard whodo format.
 504  * This is the recursive routine descending the process
 505  * tree starting from the given process pointer(up).
 506  * It used depth-first search strategy and also marked
 507  * each node as printed as it traversed down the tree.
 508  */
 509 static void
 510 showproc(struct uproc *up)
 511 {
 512         struct  uproc   *zp;
 513 
 514         if (up->p_state == VISITED) /* we already been here */
 515                 return;
 516         /* print the data for this process */
 517         if (up->p_state == ZOMBIE)
 518                 (void) printf("    %-*.*s %5d %4.1ld:%2.2ld %s\n",
 519                     LINE_WIDTH, (int)LMAX, "  ?", (int)up->p_upid, 0L, 0L,
 520                     "<defunct>");
 521         else if (up->p_state != NONE) {
 522                 (void) printf("    %-*.*s %5d %4.1ld:%2.2ld %s\n",
 523                     LINE_WIDTH, (int)LMAX, getty(up->p_ttyd), (int)up->p_upid,
 524                     up->p_time / 60L, up->p_time % 60L,
 525                     up->p_comm);
 526         }
 527         up->p_state = VISITED;
 528 
 529         /* descend for its children */
 530         if (up->p_child) {
 531                 showproc(up->p_child);
 532                 for (zp = up->p_child->p_sibling; zp; zp = zp->p_sibling) {
 533                         showproc(zp);
 534                 }
 535         }
 536 
 537         /* print the pgrp relation */
 538         if (up->p_pgrplink)
 539                 showproc(up->p_pgrplink);
 540 }
 541 
 542 
 543 /*
 544  * Used for -l flag (w command) format.
 545  * Prints the CPU time for all processes & children,
 546  * and the cpu time for interesting process,
 547  * and what the user is doing.
 548  */
 549 static void
 550 showtotals(struct uproc *up)
 551 {
 552         jobtime = 0;
 553         proctime = 0;
 554         empty = 1;
 555         curpid = -1;
 556         (void) strcpy(doing, "-"); /* default act: normally never prints */
 557         calctotals(up);
 558 
 559         /* print CPU time for all processes & children */
 560         /* and need to convert clock ticks to seconds first */
 561         prttime((time_t)jobtime, 8);
 562 
 563         /* print cpu time for interesting process */
 564         /* and need to convert clock ticks to seconds first */
 565         prttime((time_t)proctime, 8);
 566 
 567         /* what user is doing, current process */
 568         (void) printf("%-.32s\n", doing);
 569 }
 570 
 571 /*
 572  *  Used for -l flag (w command) format.
 573  *  This recursive routine descends the process
 574  *  tree starting from the given process pointer(up).
 575  *  It used depth-first search strategy and also marked
 576  *  each node as visited as it traversed down the tree.
 577  *  It calculates the process time for all processes &
 578  *  children.  It also finds the "interesting" process
 579  *  and determines its cpu time and command.
 580  */
 581 static void
 582 calctotals(struct uproc *up)
 583 {
 584         struct uproc    *zp;
 585 
 586         if (up->p_state == VISITED)
 587                 return;
 588         up->p_state = VISITED;
 589         if (up->p_state == NONE || up->p_state == ZOMBIE)
 590                 return;
 591         jobtime += up->p_time + up->p_ctime;
 592         proctime += up->p_time;
 593 
 594         if (empty && !up->p_igintr) {
 595                 empty = 0;
 596                 curpid = -1;
 597         }
 598 
 599         if (up->p_upid > curpid && (!up->p_igintr || empty)) {
 600                 curpid = up->p_upid;
 601                 (void) strcpy(doing, up->p_args);
 602         }
 603 
 604         /* descend for its children */
 605         if (up->p_child) {
 606                 calctotals(up->p_child);
 607                 for (zp = up->p_child->p_sibling; zp; zp = zp->p_sibling)
 608                         calctotals(zp);
 609         }
 610 }
 611 
 612 static char *
 613 devadd(char *name, dev_t ddev)
 614 {
 615         struct devl *dp;
 616         int leng, start, i;
 617 
 618         if (ndevs == maxdev) {
 619                 maxdev += DNINCR;
 620                 dp = realloc(devl, maxdev * sizeof (struct devl));
 621                 if (!dp) {
 622                         (void) fprintf(stderr,
 623                             gettext("%s: out of memory!: %s\n"),
 624                             prog, strerror(errno));
 625                         exit(1);
 626                 }
 627                 devl = dp;
 628         }
 629         dp = &devl[ndevs++];
 630 
 631         dp->ddev = ddev;
 632         if (name == NULL) {
 633                 (void) strcpy(dp->dname, "  ?  ");
 634                 return (dp->dname);
 635         }
 636 
 637         leng = strlen(name);
 638         if (leng < DEVNAMELEN + 4) {
 639                 /* strip off "/dev/" */
 640                 (void) strcpy(dp->dname, &name[5]);
 641         } else {
 642                 /* strip enough off the front to fit */
 643                 start = leng - DEVNAMELEN - 1;
 644 
 645                 for (i = start; i < leng && name[i] != '/'; i++)
 646                                 ;
 647                 if (i == leng)
 648                         (void) strncpy(dp->dname, &name[start], DEVNAMELEN);
 649                 else
 650                         (void) strncpy(dp->dname, &name[i+1], DEVNAMELEN);
 651         }
 652         return (dp->dname);
 653 }
 654 
 655 static char *
 656 devlookup(dev_t ddev)
 657 {
 658         struct devl *dp;
 659         int i;
 660 
 661         for (dp = devl, i = 0; i < ndevs; dp++, i++) {
 662                 if (dp->ddev == ddev)
 663                         return (dp->dname);
 664         }
 665         return (NULL);
 666 }
 667 
 668 /*
 669  * This routine gives back a corresponding device name
 670  * from the device number given.
 671  */
 672 static char *
 673 getty(dev_t dev)
 674 {
 675         extern char *_ttyname_dev(dev_t, char *, size_t);
 676         char devname[TTYNAME_MAX];
 677         char *retval;
 678 
 679         if (dev == PRNODEV)
 680                 return ("  ?  ");
 681 
 682         if ((retval = devlookup(dev)) != NULL)
 683                 return (retval);
 684 
 685         retval = _ttyname_dev(dev, devname, sizeof (devname));
 686         return (devadd(retval, dev));
 687 }
 688 
 689 /*
 690  * Findhash  finds the appropriate entry in the process
 691  * hash table (pr_htbl) for the given pid in case that
 692  * pid exists on the hash chain. It returns back a pointer
 693  * to that uproc structure. If this is a new pid, it allocates
 694  * a new node, initializes it, links it into the chain (after
 695  * head) and returns a structure pointer.
 696  */
 697 static struct uproc *
 698 findhash(pid_t pid)
 699 {
 700         struct uproc *up, *tp;
 701 
 702         tp = up = &pr_htbl[(int)pid % HSIZE];
 703         if (up->p_upid == 0) {                       /* empty slot */
 704                 up->p_upid = pid;
 705                 up->p_state = NONE;
 706                 up->p_child = up->p_sibling = up->p_pgrplink = up->p_link = 0;
 707                 return (up);
 708         }
 709         if (up->p_upid == pid) {             /* found in hash table */
 710                 return (up);
 711         }
 712         for (tp = up->p_link; tp; tp = tp->p_link) {      /* follow chain */
 713                 if (tp->p_upid == pid) {
 714                         return (tp);
 715                 }
 716         }
 717         tp = malloc(sizeof (*tp));              /* add new node */
 718         if (!tp) {
 719                 (void) fprintf(stderr, gettext("%s: out of memory!: %s\n"),
 720                     prog, strerror(errno));
 721                 exit(1);
 722         }
 723         (void) memset((char *)tp, 0, sizeof (*tp));
 724         tp->p_upid = pid;
 725         tp->p_state = NONE;
 726         tp->p_child = tp->p_sibling = tp->p_pgrplink = (pid_t)0;
 727         tp->p_link = up->p_link;          /* insert after head */
 728         up->p_link = tp;
 729         return (tp);
 730 }
 731 
 732 #define HR      (60 * 60)
 733 #define DAY     (24 * HR)
 734 #define MON     (30 * DAY)
 735 
 736 /*
 737  * prttime prints a time in days, hours, minutes, or seconds.
 738  * The second argument is the field width.
 739  */
 740 static void
 741 prttime(time_t tim, int width)
 742 {
 743         char value[12];
 744         char *unit;
 745 
 746         if (tim >= 36 * HR) {
 747                 (void) snprintf(value, sizeof (value), "%d",
 748                     (tim + (DAY / 2)) / (DAY));
 749                 unit = dcgettext(NULL, "days", LC_TIME);
 750         } else if (tim >= 36 * 60) {
 751                 (void) snprintf(value, sizeof (value), "%d",
 752                     (tim + (HR / 2)) / (HR));
 753                 unit = dcgettext(NULL, "hours", LC_TIME);
 754         } else if (tim >= 60) {
 755                 (void) snprintf(value, sizeof (value), "%d",
 756                     (tim + 30) / 60);
 757                 unit = dcgettext(NULL, "mins", LC_TIME);
 758         } else if (tim > 0) {
 759                 (void) snprintf(value, sizeof (value), "%d", (int)tim);
 760                 unit = dcgettext(NULL, "secs", LC_TIME);
 761         } else {
 762                 (void) strcpy(value, "0");
 763                 unit = " ";
 764         }
 765         width -= 2 + strlen(value);
 766         width = (width > 1) ? width : 1;
 767         printf("%s %-*s ", value, width, unit);
 768 }
 769 
 770 
 771 /*
 772  * prints a locale-specific time given a pointer to a time of day
 773  */
 774 static void
 775 prtat(time_t *time)
 776 {
 777         struct tm       *p;
 778 
 779         p = localtime(time);
 780         if (now - *time <= 18 * HR) {
 781                 char timestr[50];
 782 
 783                 (void) strftime(timestr, sizeof (timestr),
 784                     "%X", p);
 785                 printf("%-11s ", timestr);
 786         } else if (now - *time <= 7 * DAY) {
 787                 char weekdaytime[20];
 788 
 789                 (void) strftime(weekdaytime, sizeof (weekdaytime),
 790                     "%d %b", p);
 791                 printf("%-11s ", weekdaytime);
 792         } else {
 793                 char monthtime[20];
 794 
 795                 (void) strftime(monthtime, sizeof (monthtime),
 796                     "%b %Y", p);
 797                 printf("%-11s ", monthtime);
 798         }
 799 }
 800 
 801 /*
 802  * find & return number of minutes current tty has been idle
 803  */
 804 static time_t
 805 findidle(char *devname)
 806 {
 807         struct stat stbuf;
 808         time_t lastaction, diff;
 809         char ttyname[64];
 810 
 811         (void) strcpy(ttyname, "/dev/");
 812         (void) strcat(ttyname, devname);
 813         if (stat(ttyname, &stbuf) != -1) {
 814                 lastaction = stbuf.st_atime;
 815                 diff = now - lastaction;
 816                 diff = DIV60(diff);
 817                 if (diff < 0)
 818                         diff = 0;
 819         } else
 820                 diff = 0;
 821         return (diff);
 822 }
 823 
 824 /*
 825  * given a pointer to the argument string clean out unsavory characters.
 826  */
 827 static void
 828 clnarglist(char *arglist)
 829 {
 830         char    *c;
 831         int     err = 0;
 832 
 833         /* get rid of unsavory characters */
 834         for (c = arglist; *c == NULL; c++) {
 835                 if ((*c < ' ') || (*c > 0176)) {
 836                         if (err++ > 5) {
 837                                 *arglist = NULL;
 838                                 break;
 839                         }
 840                         *c = '?';
 841                 }
 842         }
 843 }