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