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 w 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  * This program also implements 'uptime'.
  47  *
  48  * Maintenance note:
  49  *
  50  * Much of this code is replicated in whodo.c.  If you're
  51  * fixing bugs here, then you should probably fix 'em there too.
  52  */
  53 
  54 #include <stdio.h>
  55 #include <string.h>
  56 #include <stdarg.h>
  57 #include <stdlib.h>
  58 #include <ctype.h>
  59 #include <fcntl.h>
  60 #include <time.h>
  61 #include <errno.h>
  62 #include <sys/types.h>
  63 #include <utmpx.h>
  64 #include <sys/stat.h>
  65 #include <dirent.h>
  66 #include <procfs.h>               /* /proc header file */
  67 #include <locale.h>
  68 #include <unistd.h>
  69 #include <sys/loadavg.h>
  70 #include <limits.h>
  71 #include <priv_utils.h>
  72 
  73 /*
  74  * Use the full lengths from utmpx for user and line.
  75  */
  76 static struct utmpx dummy;
  77 #define NMAX            (sizeof (dummy.ut_user))
  78 #define LMAX            (sizeof (dummy.ut_line))
  79 
  80 /* Print minimum field widths. */
  81 #define LOGIN_WIDTH     8
  82 #define LINE_WIDTH      8
  83 
  84 #define DIV60(t)        ((t+30)/60)     /* x/60 rounded */
  85 
  86 #ifdef ERR
  87 #undef ERR
  88 #endif
  89 #define ERR             (-1)
  90 
  91 #define HSIZE           256             /* size of process hash table   */
  92 #define PROCDIR         "/proc"
  93 #define INITPROCESS     (pid_t)1        /* init process pid */
  94 #define NONE            'n'             /* no state */
  95 #define RUNNING         'r'             /* runnable process */
  96 #define ZOMBIE          'z'             /* zombie process */
  97 #define VISITED         'v'             /* marked node as visited */
  98 #define PRINTF(a)       if (printf a < 0) { \
  99                 perror((gettext("%s: printf failed"), prog)); \
 100                 exit(1); }
 101 
 102 struct uproc {
 103         pid_t   p_upid;                 /* process id */
 104         char    p_state;                /* numeric value of process state */
 105         dev_t   p_ttyd;                 /* controlling tty of process */
 106         time_t  p_time;                 /* seconds of user & system time */
 107         time_t  p_ctime;                /* seconds of child user & sys time */
 108         int     p_igintr;               /* 1 = ignores SIGQUIT and SIGINT */
 109         char    p_comm[PRARGSZ+1];      /* command */
 110         char    p_args[PRARGSZ+1];      /* command line arguments */
 111         struct uproc    *p_child,       /* first child pointer */
 112                         *p_sibling,     /* sibling pointer */
 113                         *p_pgrpl,       /* pgrp link */
 114                         *p_link;        /* hash table chain pointer */
 115 };
 116 
 117 /*
 118  *      define hash table for struct uproc
 119  *      Hash function uses process id
 120  *      and the size of the hash table(HSIZE)
 121  *      to determine process index into the table.
 122  */
 123 static struct uproc     pr_htbl[HSIZE];
 124 
 125 static struct   uproc   *findhash(pid_t);
 126 static time_t   findidle(char *);
 127 static void     clnarglist(char *);
 128 static void     showtotals(struct uproc *);
 129 static void     calctotals(struct uproc *);
 130 static void     prttime(time_t, int);
 131 static void     prtat(time_t *time);
 132 
 133 static char     *prog;          /* pointer to invocation name */
 134 static int      header = 1;     /* true if -h flag: don't print heading */
 135 static int      lflag = 1;      /* set if -l flag; 0 for -s flag: short form */
 136 static char     *sel_user;      /* login of particular user selected */
 137 static char     firstchar;      /* first char of name of prog invoked as */
 138 static int      login;          /* true if invoked as login shell */
 139 static time_t   now;            /* current time of day */
 140 static time_t   uptime;         /* time of last reboot & elapsed time since */
 141 static int      nusers;         /* number of users logged in now */
 142 static time_t   idle;           /* number of minutes user is idle */
 143 static time_t   jobtime;        /* total cpu time visible */
 144 static char     doing[520];     /* process attached to terminal */
 145 static time_t   proctime;       /* cpu time of process in doing */
 146 static pid_t    curpid, empty;
 147 static int      add_times;      /* boolean: add the cpu times or not */
 148 
 149 #if SIGQUIT > SIGINT
 150 #define ACTSIZE SIGQUIT
 151 #else
 152 #define ACTSIZE SIGINT
 153 #endif
 154 
 155 int
 156 main(int argc, char *argv[])
 157 {
 158         struct utmpx    *ut;
 159         struct utmpx    *utmpbegin;
 160         struct utmpx    *utmpend;
 161         struct utmpx    *utp;
 162         struct uproc    *up, *parent, *pgrp;
 163         struct psinfo   info;
 164         struct sigaction actinfo[ACTSIZE];
 165         struct pstatus  statinfo;
 166         size_t          size;
 167         struct stat     sbuf;
 168         DIR             *dirp;
 169         struct  dirent  *dp;
 170         char            pname[64];
 171         char            *fname;
 172         int             procfd;
 173         char            *cp;
 174         int             i;
 175         int             days, hrs, mins;
 176         int             entries;
 177         double          loadavg[3];
 178 
 179         /*
 180          * This program needs the proc_owner privilege
 181          */
 182         (void) __init_suid_priv(PU_CLEARLIMITSET, PRIV_PROC_OWNER,
 183             (char *)NULL);
 184 
 185         (void) setlocale(LC_ALL, "");
 186 #if !defined(TEXT_DOMAIN)
 187 #define TEXT_DOMAIN "SYS_TEST"
 188 #endif
 189         (void) textdomain(TEXT_DOMAIN);
 190 
 191         login = (argv[0][0] == '-');
 192         cp = strrchr(argv[0], '/');
 193         firstchar = login ? argv[0][1] : (cp == 0) ? argv[0][0] : cp[1];
 194         prog = argv[0];
 195 
 196         while (argc > 1) {
 197                 if (argv[1][0] == '-') {
 198                         for (i = 1; argv[1][i]; i++) {
 199                                 switch (argv[1][i]) {
 200 
 201                                 case 'h':
 202                                         header = 0;
 203                                         break;
 204 
 205                                 case 'l':
 206                                         lflag++;
 207                                         break;
 208                                 case 's':
 209                                         lflag = 0;
 210                                         break;
 211 
 212                                 case 'u':
 213                                 case 'w':
 214                                         firstchar = argv[1][i];
 215                                         break;
 216 
 217                                 default:
 218                                         (void) fprintf(stderr, gettext(
 219                                             "%s: bad flag %s\n"),
 220                                             prog, argv[1]);
 221                                         exit(1);
 222                                 }
 223                         }
 224                 } else {
 225                         if (!isalnum(argv[1][0]) || argc > 2) {
 226                                 (void) fprintf(stderr, gettext(
 227                                     "usage: %s [ -hlsuw ] [ user ]\n"), prog);
 228                                 exit(1);
 229                         } else
 230                                 sel_user = argv[1];
 231                 }
 232                 argc--; argv++;
 233         }
 234 
 235         /*
 236          * read the UTMP_FILE (contains information about each logged in user)
 237          */
 238         if (stat(UTMPX_FILE, &sbuf) == ERR) {
 239                 (void) fprintf(stderr, gettext("%s: stat error of %s: %s\n"),
 240                     prog, UTMPX_FILE, strerror(errno));
 241                 exit(1);
 242         }
 243         entries = sbuf.st_size / sizeof (struct futmpx);
 244         size = sizeof (struct utmpx) * entries;
 245         if ((ut = malloc(size)) == NULL) {
 246                 (void) fprintf(stderr, gettext("%s: malloc error of %s: %s\n"),
 247                     prog, UTMPX_FILE, strerror(errno));
 248                 exit(1);
 249         }
 250 
 251         (void) utmpxname(UTMPX_FILE);
 252 
 253         utmpbegin = ut;
 254         utmpend = (struct utmpx *)((char *)utmpbegin + size);
 255 
 256         setutxent();
 257         while ((ut < utmpend) && ((utp = getutxent()) != NULL))
 258                 (void) memcpy(ut++, utp, sizeof (*ut));
 259         endutxent();
 260 
 261         (void) time(&now);  /* get current time */
 262 
 263         if (header) {   /* print a header */
 264                 prtat(&now);
 265                 for (ut = utmpbegin; ut < utmpend; ut++) {
 266                         if (ut->ut_type == USER_PROCESS) {
 267                                 if (!nonuser(*ut))
 268                                         nusers++;
 269                         } else if (ut->ut_type == BOOT_TIME) {
 270                                 uptime = now - ut->ut_xtime;
 271                                 uptime += 30;
 272                                 days = uptime / (60*60*24);
 273                                 uptime %= (60*60*24);
 274                                 hrs = uptime / (60*60);
 275                                 uptime %= (60*60);
 276                                 mins = uptime / 60;
 277 
 278                                 PRINTF((gettext("up")));
 279                                 if (days > 0)
 280                                         PRINTF((gettext(
 281                                             " %d day(s),"), days));
 282                                 if (hrs > 0 && mins > 0) {
 283                                         PRINTF((" %2d:%02d,", hrs, mins));
 284                                 } else {
 285                                         if (hrs > 0)
 286                                                 PRINTF((gettext(
 287                                                     " %d hr(s),"), hrs));
 288                                         if (mins > 0)
 289                                                 PRINTF((gettext(
 290                                                     " %d min(s),"), mins));
 291                                 }
 292                         }
 293                 }
 294 
 295                 ut = utmpbegin; /* rewind utmp data */
 296                 PRINTF((((nusers == 1) ?
 297                     gettext("  %d user") : gettext("  %d users")), nusers));
 298                 /*
 299                  * Print 1, 5, and 15 minute load averages.
 300                  */
 301                 (void) getloadavg(loadavg, 3);
 302                 PRINTF((gettext(",  load average: %.2f, %.2f, %.2f\n"),
 303                     loadavg[LOADAVG_1MIN], loadavg[LOADAVG_5MIN],
 304                     loadavg[LOADAVG_15MIN]));
 305 
 306                 if (firstchar == 'u')   /* uptime command */
 307                         exit(0);
 308 
 309                 if (lflag) {
 310                         PRINTF((dcgettext(NULL, "User     tty      "
 311                             "login@         idle    JCPU    PCPU what\n",
 312                             LC_TIME)));
 313                 } else {
 314                         PRINTF((dcgettext(NULL,
 315                             "User     tty         idle what\n",
 316                             LC_TIME)));
 317                 }
 318 
 319                 if (fflush(stdout) == EOF) {
 320                         perror((gettext("%s: fflush failed\n"), prog));
 321                         exit(1);
 322                 }
 323         }
 324 
 325         /*
 326          * loop through /proc, reading info about each process
 327          * and build the parent/child tree
 328          */
 329         if (!(dirp = opendir(PROCDIR))) {
 330                 (void) fprintf(stderr, gettext("%s: could not open %s: %s\n"),
 331                     prog, PROCDIR, strerror(errno));
 332                 exit(1);
 333         }
 334 
 335         while ((dp = readdir(dirp)) != NULL) {
 336                 if (dp->d_name[0] == '.')
 337                         continue;
 338 retry:
 339                 (void) sprintf(pname, "%s/%s/", PROCDIR, dp->d_name);
 340                 fname = pname + strlen(pname);
 341                 (void) strcpy(fname, "psinfo");
 342                 if ((procfd = open(pname, O_RDONLY)) < 0)
 343                         continue;
 344                 if (read(procfd, &info, sizeof (info)) != sizeof (info)) {
 345                         int err = errno;
 346                         (void) close(procfd);
 347                         if (err == EAGAIN)
 348                                 goto retry;
 349                         if (err != ENOENT)
 350                                 (void) fprintf(stderr, gettext(
 351                                     "%s: read() failed on %s: %s \n"),
 352                                     prog, pname, strerror(err));
 353                         continue;
 354                 }
 355                 (void) close(procfd);
 356 
 357                 up = findhash(info.pr_pid);
 358                 up->p_ttyd = info.pr_ttydev;
 359                 up->p_state = (info.pr_nlwp == 0? ZOMBIE : RUNNING);
 360                 up->p_time = 0;
 361                 up->p_ctime = 0;
 362                 up->p_igintr = 0;
 363                 (void) strncpy(up->p_comm, info.pr_fname,
 364                     sizeof (info.pr_fname));
 365                 up->p_args[0] = 0;
 366 
 367                 if (up->p_state != NONE && up->p_state != ZOMBIE) {
 368                         (void) strcpy(fname, "status");
 369 
 370                         /* now we need the proc_owner privilege */
 371                         (void) __priv_bracket(PRIV_ON);
 372 
 373                         procfd = open(pname, O_RDONLY);
 374 
 375                         /* drop proc_owner privilege after open */
 376                         (void) __priv_bracket(PRIV_OFF);
 377 
 378                         if (procfd < 0)
 379                                 continue;
 380 
 381                         if (read(procfd, &statinfo, sizeof (statinfo))
 382                             != sizeof (statinfo)) {
 383                                 int err = errno;
 384                                 (void) close(procfd);
 385                                 if (err == EAGAIN)
 386                                         goto retry;
 387                                 if (err != ENOENT)
 388                                         (void) fprintf(stderr, gettext(
 389                                             "%s: read() failed on %s: %s \n"),
 390                                             prog, pname, strerror(err));
 391                                 continue;
 392                         }
 393                         (void) close(procfd);
 394 
 395                         up->p_time = statinfo.pr_utime.tv_sec +
 396                             statinfo.pr_stime.tv_sec;   /* seconds */
 397                         up->p_ctime = statinfo.pr_cutime.tv_sec +
 398                             statinfo.pr_cstime.tv_sec;
 399 
 400                         (void) strcpy(fname, "sigact");
 401 
 402                         /* now we need the proc_owner privilege */
 403                         (void) __priv_bracket(PRIV_ON);
 404 
 405                         procfd = open(pname, O_RDONLY);
 406 
 407                         /* drop proc_owner privilege after open */
 408                         (void) __priv_bracket(PRIV_OFF);
 409 
 410                         if (procfd < 0)
 411                                 continue;
 412 
 413                         if (read(procfd, actinfo, sizeof (actinfo))
 414                             != sizeof (actinfo)) {
 415                                 int err = errno;
 416                                 (void) close(procfd);
 417                                 if (err == EAGAIN)
 418                                         goto retry;
 419                                 if (err != ENOENT)
 420                                         (void) fprintf(stderr, gettext(
 421                                             "%s: read() failed on %s: %s \n"),
 422                                             prog, pname, strerror(err));
 423                                 continue;
 424                         }
 425                         (void) close(procfd);
 426 
 427                         up->p_igintr =
 428                             actinfo[SIGINT-1].sa_handler == SIG_IGN &&
 429                             actinfo[SIGQUIT-1].sa_handler == SIG_IGN;
 430 
 431                         /*
 432                          * Process args.
 433                          */
 434                         up->p_args[0] = 0;
 435                         clnarglist(info.pr_psargs);
 436                         (void) strcat(up->p_args, info.pr_psargs);
 437                         if (up->p_args[0] == 0 ||
 438                             up->p_args[0] == '-' && up->p_args[1] <= ' ' ||
 439                             up->p_args[0] == '?') {
 440                                 (void) strcat(up->p_args, " (");
 441                                 (void) strcat(up->p_args, up->p_comm);
 442                                 (void) strcat(up->p_args, ")");
 443                         }
 444                 }
 445 
 446                 /*
 447                  * link pgrp together in case parents go away
 448                  * Pgrp chain is a single linked list originating
 449                  * from the pgrp leader to its group member.
 450                  */
 451                 if (info.pr_pgid != info.pr_pid) {      /* not pgrp leader */
 452                         pgrp = findhash(info.pr_pgid);
 453                         up->p_pgrpl = pgrp->p_pgrpl;
 454                         pgrp->p_pgrpl = up;
 455                 }
 456                 parent = findhash(info.pr_ppid);
 457 
 458                 /* if this is the new member, link it in */
 459                 if (parent->p_upid != INITPROCESS) {
 460                         if (parent->p_child) {
 461                                 up->p_sibling = parent->p_child;
 462                                 up->p_child = 0;
 463                         }
 464                         parent->p_child = up;
 465                 }
 466         }
 467 
 468         /* revert to non-privileged user after opening */
 469         (void) __priv_relinquish();
 470 
 471         (void) closedir(dirp);
 472         (void) time(&now);  /* get current time */
 473 
 474         /*
 475          * loop through utmpx file, printing process info
 476          * about each logged in user
 477          */
 478         for (ut = utmpbegin; ut < utmpend; ut++) {
 479                 if (ut->ut_type != USER_PROCESS)
 480                         continue;
 481                 if (sel_user && strncmp(ut->ut_name, sel_user, NMAX) != 0)
 482                         continue;       /* we're looking for somebody else */
 483 
 484                 /* print login name of the user */
 485                 PRINTF(("%-*.*s ", LOGIN_WIDTH, NMAX, ut->ut_name));
 486 
 487                 /* print tty user is on */
 488                 if (lflag) {
 489                         PRINTF(("%-*.*s ", LINE_WIDTH, LMAX, ut->ut_line));
 490                 } else {
 491                         if (ut->ut_line[0] == 'p' && ut->ut_line[1] == 't' &&
 492                             ut->ut_line[2] == 's' && ut->ut_line[3] == '/') {
 493                                 PRINTF(("%-*.*s ", LINE_WIDTH, LMAX,
 494                                     &ut->ut_line[4]));
 495                         } else {
 496                                 PRINTF(("%-*.*s ", LINE_WIDTH, LMAX,
 497                                     ut->ut_line));
 498                         }
 499                 }
 500 
 501                 /* print when the user logged in */
 502                 if (lflag) {
 503                         time_t tim = ut->ut_xtime;
 504                         prtat(&tim);
 505                 }
 506 
 507                 /* print idle time */
 508                 idle = findidle(ut->ut_line);
 509                 prttime(idle, 8);
 510                 showtotals(findhash(ut->ut_pid));
 511         }
 512         if (fclose(stdout) == EOF) {
 513                 perror((gettext("%s: fclose failed"), prog));
 514                 exit(1);
 515         }
 516         return (0);
 517 }
 518 
 519 /*
 520  *  Prints the CPU time for all processes & children,
 521  *  and the cpu time for interesting process,
 522  *  and what the user is doing.
 523  */
 524 static void
 525 showtotals(struct uproc *up)
 526 {
 527         jobtime = 0;
 528         proctime = 0;
 529         empty = 1;
 530         curpid = -1;
 531         add_times = 1;
 532 
 533         calctotals(up);
 534 
 535         if (lflag) {
 536                 /* print CPU time for all processes & children */
 537                 /* and need to convert clock ticks to seconds first */
 538                 prttime((time_t)jobtime, 8);
 539 
 540                 /* print cpu time for interesting process */
 541                 /* and need to convert clock ticks to seconds first */
 542                 prttime((time_t)proctime, 8);
 543         }
 544         /* what user is doing, current process */
 545         PRINTF(("%-.32s\n", doing));
 546 }
 547 
 548 /*
 549  *  This recursive routine descends the process
 550  *  tree starting from the given process pointer(up).
 551  *  It used depth-first search strategy and also marked
 552  *  each node as visited as it traversed down the tree.
 553  *  It calculates the process time for all processes &
 554  *  children.  It also finds the interesting process
 555  *  and determines its cpu time and command.
 556  */
 557 static void
 558 calctotals(struct uproc *up)
 559 {
 560         struct uproc   *zp;
 561 
 562         /*
 563          * Once a node has been visited, stop adding cpu times
 564          * for its children so they don't get totalled twice.
 565          * Still look for the interesting job for this utmp
 566          * entry, however.
 567          */
 568         if (up->p_state == VISITED)
 569                 add_times = 0;
 570         up->p_state = VISITED;
 571         if (up->p_state == NONE || up->p_state == ZOMBIE)
 572                 return;
 573 
 574         if (empty && !up->p_igintr) {
 575                 empty = 0;
 576                 curpid = -1;
 577         }
 578 
 579         if (up->p_upid > curpid && (!up->p_igintr || empty)) {
 580                 curpid = up->p_upid;
 581                 if (lflag)
 582                         (void) strcpy(doing, up->p_args);
 583                 else
 584                         (void) strcpy(doing, up->p_comm);
 585         }
 586 
 587         if (add_times == 1) {
 588                 jobtime += up->p_time + up->p_ctime;
 589                 proctime += up->p_time;
 590         }
 591 
 592         /* descend for its children */
 593         if (up->p_child) {
 594                 calctotals(up->p_child);
 595                 for (zp = up->p_child->p_sibling; zp; zp = zp->p_sibling)
 596                         calctotals(zp);
 597         }
 598 }
 599 
 600 /*
 601  *   Findhash  finds the appropriate entry in the process
 602  *   hash table (pr_htbl) for the given pid in case that
 603  *   pid exists on the hash chain. It returns back a pointer
 604  *   to that uproc structure. If this is a new pid, it allocates
 605  *   a new node, initializes it, links it into the chain (after
 606  *   head) and returns a structure pointer.
 607  */
 608 static struct uproc *
 609 findhash(pid_t pid)
 610 {
 611         struct uproc *up, *tp;
 612 
 613         tp = up = &pr_htbl[pid % HSIZE];
 614         if (up->p_upid == 0) {                       /* empty slot */
 615                 up->p_upid = pid;
 616                 up->p_state = NONE;
 617                 up->p_child = up->p_sibling = up->p_pgrpl = up->p_link = 0;
 618                 return (up);
 619         }
 620         if (up->p_upid == pid) {             /* found in hash table */
 621                 return (up);
 622         }
 623         for (tp = up->p_link; tp; tp = tp->p_link) {      /* follow chain */
 624                 if (tp->p_upid == pid)
 625                         return (tp);
 626         }
 627         tp = malloc(sizeof (*tp));              /* add new node */
 628         if (!tp) {
 629                 (void) fprintf(stderr, gettext("%s: out of memory!: %s\n"),
 630                     prog, strerror(errno));
 631                 exit(1);
 632         }
 633         (void) memset(tp, 0, sizeof (*tp));
 634         tp->p_upid = pid;
 635         tp->p_state = NONE;
 636         tp->p_child = tp->p_sibling = tp->p_pgrpl = 0;
 637         tp->p_link = up->p_link;          /* insert after head */
 638         up->p_link = tp;
 639         return (tp);
 640 }
 641 
 642 #define HR      (60 * 60)
 643 #define DAY     (24 * HR)
 644 #define MON     (30 * DAY)
 645 
 646 /*
 647  * Prttime prints an elapsed time in hours, minutes, or seconds,
 648  * right-justified with the rightmost column always blank.
 649  * The second argument is the minimum field width.
 650  */
 651 static void
 652 prttime(time_t tim, int width)
 653 {
 654         char value[36];
 655 
 656         if (tim >= 36 * 60) {
 657                 (void) snprintf(value, sizeof (value), "%d:%02d:%02d",
 658                     (int)tim / HR, (int)(tim % HR) / 60, (int)tim % 60);
 659         } else if (tim >= 60) {
 660                 (void) snprintf(value, sizeof (value), "%d:%02d",
 661                     (int)tim / 60, (int)tim % 60);
 662         } else if (tim > 0) {
 663                 (void) snprintf(value, sizeof (value), "%d", (int)tim);
 664         } else {
 665                 (void) strcpy(value, "0");
 666         }
 667         width = (width > 2) ? width - 1 : 1;
 668         PRINTF(("%*s ", width, value));
 669 }
 670 
 671 /*
 672  * Prints the ISO date or time given a pointer to a time of day,
 673  * left-justfied in a 12-character expanding field with the
 674  * rightmost column always blank.
 675  * Includes a dcgettext() override in case a message catalog is needed.
 676  */
 677 static void
 678 prtat(time_t *time)
 679 {
 680         struct tm       *p;
 681 
 682         p = localtime(time);
 683         if (now - *time <= 18 * HR) {
 684                 char timestr[50];
 685 
 686                 (void) strftime(timestr, sizeof (timestr),
 687                     dcgettext(NULL, "%T", LC_TIME), p);
 688                 PRINTF(("%-11s ", timestr));
 689         } else if (now - *time <= 7 * DAY) {
 690                 char weekdaytime[20];
 691 
 692                 (void) strftime(weekdaytime, sizeof (weekdaytime),
 693                     dcgettext(NULL, "%a %H:%M", LC_TIME), p);
 694                 PRINTF(("%-11s ", weekdaytime));
 695         } else {
 696                 char monthtime[20];
 697 
 698                 (void) strftime(monthtime, sizeof (monthtime),
 699                     dcgettext(NULL, "%F", LC_TIME), p);
 700                 PRINTF(("%-11s ", monthtime));
 701         }
 702 }
 703 
 704 /*
 705  * find & return number of minutes current tty has been idle
 706  */
 707 static time_t
 708 findidle(char *devname)
 709 {
 710         struct stat stbuf;
 711         time_t lastaction, diff;
 712         char ttyname[64];
 713 
 714         (void) strcpy(ttyname, "/dev/");
 715         (void) strcat(ttyname, devname);
 716         if (stat(ttyname, &stbuf) != -1) {
 717                 lastaction = stbuf.st_atime;
 718                 diff = now - lastaction;
 719                 diff = DIV60(diff);
 720                 if (diff < 0)
 721                         diff = 0;
 722         } else
 723                 diff = 0;
 724         return (diff);
 725 }
 726 
 727 /*
 728  * given a pointer to the argument string get rid of unsavory characters.
 729  */
 730 static void
 731 clnarglist(char *arglist)
 732 {
 733         char    *c;
 734         int     err = 0;
 735 
 736         /* get rid of unsavory characters */
 737         for (c = arglist; *c != NULL; c++) {
 738                 if ((*c < ' ') || (*c > 0176)) {
 739                         if (err++ > 5) {
 740                                 *arglist = NULL;
 741                                 break;
 742                         }
 743                         *c = '?';
 744                 }
 745         }
 746 }