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