(root)/
coreutils-9.4/
src/
who.c
       1  /* GNU's who.
       2     Copyright (C) 1992-2023 Free Software Foundation, Inc.
       3  
       4     This program is free software: you can redistribute it and/or modify
       5     it under the terms of the GNU General Public License as published by
       6     the Free Software Foundation, either version 3 of the License, or
       7     (at your option) any later version.
       8  
       9     This program is distributed in the hope that it will be useful,
      10     but WITHOUT ANY WARRANTY; without even the implied warranty of
      11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12     GNU General Public License for more details.
      13  
      14     You should have received a copy of the GNU General Public License
      15     along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
      16  
      17  /* Written by jla; revised by djm; revised again by mstone */
      18  
      19  /* Output format:
      20     name [state] line time [activity] [pid] [comment] [exit]
      21     state: -T
      22     name, line, time: not -q
      23     idle: -u
      24  */
      25  
      26  #include <config.h>
      27  #include <getopt.h>
      28  #include <stdckdint.h>
      29  #include <stdio.h>
      30  
      31  #include <sys/types.h>
      32  #include "system.h"
      33  
      34  #include "c-ctype.h"
      35  #include "canon-host.h"
      36  #include "readutmp.h"
      37  #include "hard-locale.h"
      38  #include "quote.h"
      39  
      40  #ifdef TTY_GROUP_NAME
      41  # include <grp.h>
      42  #endif
      43  
      44  /* The official name of this program (e.g., no 'g' prefix).  */
      45  #define PROGRAM_NAME "who"
      46  
      47  #define AUTHORS \
      48    proper_name ("Joseph Arceneaux"), \
      49    proper_name ("David MacKenzie"), \
      50    proper_name ("Michael Stone")
      51  
      52  #ifdef RUN_LVL
      53  # define UT_TYPE_RUN_LVL(U) ((U)->ut_type == RUN_LVL)
      54  #else
      55  # define UT_TYPE_RUN_LVL(U) false
      56  #endif
      57  
      58  #ifdef INIT_PROCESS
      59  # define UT_TYPE_INIT_PROCESS(U) ((U)->ut_type == INIT_PROCESS)
      60  #else
      61  # define UT_TYPE_INIT_PROCESS(U) false
      62  #endif
      63  
      64  #ifdef LOGIN_PROCESS
      65  # define UT_TYPE_LOGIN_PROCESS(U) ((U)->ut_type == LOGIN_PROCESS)
      66  #else
      67  # define UT_TYPE_LOGIN_PROCESS(U) false
      68  #endif
      69  
      70  #ifdef DEAD_PROCESS
      71  # define UT_TYPE_DEAD_PROCESS(U) ((U)->ut_type == DEAD_PROCESS)
      72  #else
      73  # define UT_TYPE_DEAD_PROCESS(U) false
      74  #endif
      75  
      76  #ifdef NEW_TIME
      77  # define UT_TYPE_NEW_TIME(U) ((U)->ut_type == NEW_TIME)
      78  #else
      79  # define UT_TYPE_NEW_TIME(U) false
      80  #endif
      81  
      82  #define IDLESTR_LEN 6
      83  
      84  #if HAVE_STRUCT_XTMP_UT_PID
      85  # define PIDSTR_DECL_AND_INIT(Var, Utmp_ent) \
      86    char Var[INT_STRLEN_BOUND (Utmp_ent->ut_pid) + 1]; \
      87    sprintf (Var, "%ld", (long int) (Utmp_ent->ut_pid))
      88  #else
      89  # define PIDSTR_DECL_AND_INIT(Var, Utmp_ent) \
      90    char const *Var = ""
      91  #endif
      92  
      93  #if HAVE_STRUCT_XTMP_UT_ID
      94  # define UT_ID(U) ((U)->ut_id)
      95  #else
      96  # define UT_ID(U) "??"
      97  #endif
      98  
      99  /* If true, attempt to canonicalize hostnames via a DNS lookup. */
     100  static bool do_lookup;
     101  
     102  /* If true, display only a list of usernames and count of
     103     the users logged on.
     104     Ignored for 'who am i'.  */
     105  static bool short_list;
     106  
     107  /* If true, display only name, line, and time fields.  */
     108  static bool short_output;
     109  
     110  /* If true, display the hours:minutes since each user has touched
     111     the keyboard, or "." if within the last minute, or "old" if
     112     not within the last day.  */
     113  static bool include_idle;
     114  
     115  /* If true, display a line at the top describing each field.  */
     116  static bool include_heading;
     117  
     118  /* If true, display a '+' for each user if mesg y, a '-' if mesg n,
     119     or a '?' if their tty cannot be statted. */
     120  static bool include_mesg;
     121  
     122  /* If true, display process termination & exit status.  */
     123  static bool include_exit;
     124  
     125  /* If true, display the last boot time.  */
     126  static bool need_boottime;
     127  
     128  /* If true, display dead processes.  */
     129  static bool need_deadprocs;
     130  
     131  /* If true, display processes waiting for user login.  */
     132  static bool need_login;
     133  
     134  /* If true, display processes started by init.  */
     135  static bool need_initspawn;
     136  
     137  /* If true, display the last clock change.  */
     138  static bool need_clockchange;
     139  
     140  /* If true, display the current runlevel.  */
     141  static bool need_runlevel;
     142  
     143  /* If true, display user processes.  */
     144  static bool need_users;
     145  
     146  /* If true, display info only for the controlling tty.  */
     147  static bool my_line_only;
     148  
     149  /* The strftime format to use for login times, and its expected
     150     output width.  */
     151  static char const *time_format;
     152  static int time_format_width;
     153  
     154  /* for long options with no corresponding short option, use enum */
     155  enum
     156  {
     157    LOOKUP_OPTION = CHAR_MAX + 1
     158  };
     159  
     160  static struct option const longopts[] =
     161  {
     162    {"all", no_argument, nullptr, 'a'},
     163    {"boot", no_argument, nullptr, 'b'},
     164    {"count", no_argument, nullptr, 'q'},
     165    {"dead", no_argument, nullptr, 'd'},
     166    {"heading", no_argument, nullptr, 'H'},
     167    {"login", no_argument, nullptr, 'l'},
     168    {"lookup", no_argument, nullptr, LOOKUP_OPTION},
     169    {"message", no_argument, nullptr, 'T'},
     170    {"mesg", no_argument, nullptr, 'T'},
     171    {"process", no_argument, nullptr, 'p'},
     172    {"runlevel", no_argument, nullptr, 'r'},
     173    {"short", no_argument, nullptr, 's'},
     174    {"time", no_argument, nullptr, 't'},
     175    {"users", no_argument, nullptr, 'u'},
     176    {"writable", no_argument, nullptr, 'T'},
     177    {GETOPT_HELP_OPTION_DECL},
     178    {GETOPT_VERSION_OPTION_DECL},
     179    {nullptr, 0, nullptr, 0}
     180  };
     181  
     182  /* Return a string representing the time between WHEN and now.
     183     BOOTTIME is the time of last reboot.
     184     FIXME: locale? */
     185  static char const *
     186  idle_string (time_t when, time_t boottime)
     187  {
     188    static time_t now = TYPE_MINIMUM (time_t);
     189  
     190    if (now == TYPE_MINIMUM (time_t))
     191      time (&now);
     192  
     193    int seconds_idle;
     194    if (boottime < when && when <= now
     195        && ! ckd_sub (&seconds_idle, now, when)
     196        && seconds_idle < 24 * 60 * 60)
     197      {
     198        if (seconds_idle < 60)
     199          return "  .  ";
     200        else
     201          {
     202            static char idle_hhmm[IDLESTR_LEN];
     203            sprintf (idle_hhmm, "%02d:%02d",
     204                     seconds_idle / (60 * 60),
     205                     (seconds_idle % (60 * 60)) / 60);
     206            return idle_hhmm;
     207          }
     208      }
     209  
     210    return _(" old ");
     211  }
     212  
     213  /* Return a time string.  */
     214  static char const *
     215  time_string (struct gl_utmp const *utmp_ent)
     216  {
     217    static char buf[INT_STRLEN_BOUND (intmax_t) + sizeof "-%m-%d %H:%M"];
     218    struct tm *tmp = localtime (&utmp_ent->ut_ts.tv_sec);
     219  
     220    if (tmp)
     221      {
     222        strftime (buf, sizeof buf, time_format, tmp);
     223        return buf;
     224      }
     225    else
     226      return timetostr (utmp_ent->ut_ts.tv_sec, buf);
     227  }
     228  
     229  /* Print formatted output line. Uses mostly arbitrary field sizes, probably
     230     will need tweaking if any of the localization stuff is done, or for 64 bit
     231     pids, etc. */
     232  static void
     233  print_line (char const *user, const char state,
     234              char const *line,
     235              char const *time_str, char const *idle, char const *pid,
     236              char const *comment, char const *exitstr)
     237  {
     238    static char mesg[3] = { ' ', 'x', '\0' };
     239    char *buf;
     240    char x_idle[1 + IDLESTR_LEN + 1];
     241    char x_pid[1 + INT_STRLEN_BOUND (pid_t) + 1];
     242    char *x_exitstr;
     243    int err;
     244  
     245    mesg[1] = state;
     246  
     247    if (include_idle && !short_output && strlen (idle) < sizeof x_idle - 1)
     248      sprintf (x_idle, " %-6s", idle);
     249    else
     250      *x_idle = '\0';
     251  
     252    if (!short_output && strlen (pid) < sizeof x_pid - 1)
     253      sprintf (x_pid, " %10s", pid);
     254    else
     255      *x_pid = '\0';
     256  
     257    x_exitstr = xmalloc (include_exit ? 1 + MAX (12, strlen (exitstr)) + 1 : 1);
     258    if (include_exit)
     259      sprintf (x_exitstr, " %-12s", exitstr);
     260    else
     261      *x_exitstr = '\0';
     262  
     263    err = asprintf (&buf,
     264                    "%-8s"
     265                    "%s"
     266                    " %-12s"
     267                    " %-*s"
     268                    "%s"
     269                    "%s"
     270                    " %-8s"
     271                    "%s"
     272                    ,
     273                    user ? user : "   .",
     274                    include_mesg ? mesg : "",
     275                    line,
     276                    time_format_width,
     277                    time_str,
     278                    x_idle,
     279                    x_pid,
     280                    /* FIXME: it's not really clear whether the following
     281                       field should be in the short_output.  A strict reading
     282                       of SUSv2 would suggest not, but I haven't seen any
     283                       implementations that actually work that way... */
     284                    comment,
     285                    x_exitstr
     286                    );
     287    if (err == -1)
     288      xalloc_die ();
     289  
     290    {
     291      /* Remove any trailing spaces.  */
     292      char *p = buf + strlen (buf);
     293      while (*--p == ' ')
     294        /* empty */;
     295      *(p + 1) = '\0';
     296    }
     297  
     298    puts (buf);
     299    free (buf);
     300    free (x_exitstr);
     301  }
     302  
     303  /* Return true if a terminal device given as PSTAT allows other users
     304     to send messages to; false otherwise */
     305  static bool
     306  is_tty_writable (struct stat const *pstat)
     307  {
     308  #ifdef TTY_GROUP_NAME
     309    /* Ensure the group of the TTY device matches TTY_GROUP_NAME, more info at
     310       https://bugzilla.redhat.com/454261 */
     311    struct group *ttygr = getgrnam (TTY_GROUP_NAME);
     312    if (!ttygr || (pstat->st_gid != ttygr->gr_gid))
     313      return false;
     314  #endif
     315  
     316    return pstat->st_mode & S_IWGRP;
     317  }
     318  
     319  /* Send properly parsed USER_PROCESS info to print_line.  The most
     320     recent boot time is BOOTTIME. */
     321  static void
     322  print_user (struct gl_utmp const *utmp_ent, time_t boottime)
     323  {
     324    struct stat stats;
     325    time_t last_change;
     326    char mesg;
     327    char idlestr[IDLESTR_LEN + 1];
     328    PIDSTR_DECL_AND_INIT (pidstr, utmp_ent);
     329    static char *hoststr;
     330  #if HAVE_STRUCT_XTMP_UT_HOST
     331    static idx_t hostlen;
     332  #endif
     333  
     334    /* If ut_line contains a space, the device name starts after the space.  */
     335    char *line = utmp_ent->ut_line;
     336    char *space = strchr (line, ' ');
     337    line = space ? space + 1 : line;
     338  
     339    int dirfd;
     340    if (IS_ABSOLUTE_FILE_NAME (line))
     341      dirfd = AT_FDCWD;
     342    else
     343      {
     344        static int dev_dirfd;
     345        if (!dev_dirfd)
     346          {
     347            dev_dirfd = open ("/dev", O_PATHSEARCH | O_DIRECTORY);
     348            if (dev_dirfd < 0)
     349              dev_dirfd = AT_FDCWD - 1;
     350          }
     351        dirfd = dev_dirfd;
     352      }
     353  
     354    if (AT_FDCWD <= dirfd && fstatat (dirfd, line, &stats, 0) == 0)
     355      {
     356        mesg = is_tty_writable (&stats) ? '+' : '-';
     357        last_change = stats.st_atime;
     358      }
     359    else
     360      {
     361        mesg = '?';
     362        last_change = 0;
     363      }
     364  
     365    if (last_change)
     366      sprintf (idlestr, "%.*s", IDLESTR_LEN, idle_string (last_change, boottime));
     367    else
     368      sprintf (idlestr, "  ?");
     369  
     370  #if HAVE_STRUCT_XTMP_UT_HOST
     371    if (utmp_ent->ut_host[0])
     372      {
     373        char *host = nullptr;
     374        char *display = nullptr;
     375        char *ut_host = utmp_ent->ut_host;
     376  
     377        /* Look for an X display.  */
     378        display = strchr (ut_host, ':');
     379        if (display)
     380          *display++ = '\0';
     381  
     382        if (*ut_host && do_lookup)
     383          {
     384            /* See if we can canonicalize it.  */
     385            host = canon_host (ut_host);
     386          }
     387  
     388        if (! host)
     389          host = ut_host;
     390  
     391        if (display)
     392          {
     393            idx_t needed = strlen (host) + strlen (display) + 4;
     394            if (hostlen < needed)
     395              {
     396                free (hoststr);
     397                hoststr = xpalloc (nullptr, &hostlen, needed - hostlen, -1, 1);
     398              }
     399            char *p = hoststr;
     400            *p++ = '(';
     401            p = stpcpy (p, host);
     402            *p++ = ':';
     403            strcpy (stpcpy (p, display), ")");
     404          }
     405        else
     406          {
     407            idx_t needed = strlen (host) + 3;
     408            if (hostlen < needed)
     409              {
     410                free (hoststr);
     411                hoststr = xpalloc (nullptr, &hostlen, needed - hostlen, -1, 1);
     412              }
     413            char *p = hoststr;
     414            *p++ = '(';
     415            strcpy (stpcpy (p, host), ")");
     416          }
     417  
     418        if (host != ut_host)
     419          free (host);
     420      }
     421    else
     422      {
     423        if (hostlen < 1)
     424          hoststr = xpalloc (hoststr, &hostlen, 1, -1, 1);
     425        *hoststr = '\0';
     426      }
     427  #endif
     428  
     429    print_line (utmp_ent->ut_user, mesg,
     430                utmp_ent->ut_line,
     431                time_string (utmp_ent), idlestr, pidstr,
     432                hoststr ? hoststr : "", "");
     433  }
     434  
     435  static void
     436  print_boottime (struct gl_utmp const *utmp_ent)
     437  {
     438    print_line ("", ' ', _("system boot"),
     439                time_string (utmp_ent), "", "", "", "");
     440  }
     441  
     442  static char *
     443  make_id_equals_comment (struct gl_utmp const *utmp_ent)
     444  {
     445    char const *id = UT_ID (utmp_ent);
     446    idx_t idlen = strlen (id);
     447    char const *prefix = _("id=");
     448    idx_t prefixlen = strlen (prefix);
     449    char *comment = xmalloc (prefixlen + idlen + 1);
     450    char *p = mempcpy (comment, prefix, prefixlen);
     451    p = mempcpy (p, id, idlen);
     452    *p = '\0';
     453    return comment;
     454  }
     455  
     456  static void
     457  print_deadprocs (struct gl_utmp const *utmp_ent)
     458  {
     459    static char *exitstr;
     460    char *comment = make_id_equals_comment (utmp_ent);
     461    PIDSTR_DECL_AND_INIT (pidstr, utmp_ent);
     462  
     463    if (!exitstr)
     464      exitstr = xmalloc (strlen (_("term="))
     465                         + INT_STRLEN_BOUND (utmp_ent->ut_exit.e_termination) + 1
     466                         + strlen (_("exit="))
     467                         + INT_STRLEN_BOUND (utmp_ent->ut_exit.e_exit)
     468                         + 1);
     469    sprintf (exitstr, "%s%d %s%d", _("term="), utmp_ent->ut_exit.e_termination,
     470             _("exit="), utmp_ent->ut_exit.e_exit);
     471  
     472    /* FIXME: add idle time? */
     473  
     474    print_line ("", ' ', utmp_ent->ut_line,
     475                time_string (utmp_ent), "", pidstr, comment, exitstr);
     476    free (comment);
     477  }
     478  
     479  static void
     480  print_login (struct gl_utmp const *utmp_ent)
     481  {
     482    char *comment = make_id_equals_comment (utmp_ent);
     483    PIDSTR_DECL_AND_INIT (pidstr, utmp_ent);
     484  
     485    /* FIXME: add idle time? */
     486  
     487    print_line (_("LOGIN"), ' ', utmp_ent->ut_line,
     488                time_string (utmp_ent), "", pidstr, comment, "");
     489    free (comment);
     490  }
     491  
     492  static void
     493  print_initspawn (struct gl_utmp const *utmp_ent)
     494  {
     495    char *comment = make_id_equals_comment (utmp_ent);
     496    PIDSTR_DECL_AND_INIT (pidstr, utmp_ent);
     497  
     498    print_line ("", ' ', utmp_ent->ut_line,
     499                time_string (utmp_ent), "", pidstr, comment, "");
     500    free (comment);
     501  }
     502  
     503  static void
     504  print_clockchange (struct gl_utmp const *utmp_ent)
     505  {
     506    /* FIXME: handle NEW_TIME & OLD_TIME both */
     507    print_line ("", ' ', _("clock change"),
     508                time_string (utmp_ent), "", "", "", "");
     509  }
     510  
     511  static void
     512  print_runlevel (struct gl_utmp const *utmp_ent)
     513  {
     514    static char *runlevline, *comment;
     515    unsigned char last = utmp_ent->ut_pid / 256;
     516    unsigned char curr = utmp_ent->ut_pid % 256;
     517  
     518    if (!runlevline)
     519      runlevline = xmalloc (strlen (_("run-level")) + 3);
     520    sprintf (runlevline, "%s %c", _("run-level"), curr);
     521  
     522    if (!comment)
     523      comment = xmalloc (strlen (_("last=")) + 2);
     524    sprintf (comment, "%s%c", _("last="), (last == 'N') ? 'S' : last);
     525  
     526    print_line ("", ' ', runlevline, time_string (utmp_ent),
     527                "", "", c_isprint (last) ? comment : "", "");
     528  
     529    return;
     530  }
     531  
     532  /* Print the username of each valid entry and the number of valid entries
     533     in UTMP_BUF, which should have N elements. */
     534  static void
     535  list_entries_who (idx_t n, struct gl_utmp const *utmp_buf)
     536  {
     537    idx_t entries = 0;
     538    char const *separator = "";
     539  
     540    while (n--)
     541      {
     542        if (IS_USER_PROCESS (utmp_buf))
     543          {
     544            char *trimmed_name;
     545  
     546            trimmed_name = extract_trimmed_name (utmp_buf);
     547  
     548            printf ("%s%s", separator, trimmed_name);
     549            free (trimmed_name);
     550            separator = " ";
     551            entries++;
     552          }
     553        utmp_buf++;
     554      }
     555    printf (_("\n# users=%td\n"), entries);
     556  }
     557  
     558  static void
     559  print_heading (void)
     560  {
     561    print_line (_("NAME"), ' ', _("LINE"), _("TIME"), _("IDLE"),
     562                _("PID"), _("COMMENT"), _("EXIT"));
     563  }
     564  
     565  /* Display UTMP_BUF, which should have N entries. */
     566  static void
     567  scan_entries (idx_t n, struct gl_utmp const *utmp_buf)
     568  {
     569    char *ttyname_b IF_LINT ( = nullptr);
     570    time_t boottime = TYPE_MINIMUM (time_t);
     571  
     572    if (include_heading)
     573      print_heading ();
     574  
     575    if (my_line_only)
     576      {
     577        ttyname_b = ttyname (STDIN_FILENO);
     578        if (!ttyname_b)
     579          return;
     580        if (STRNCMP_LIT (ttyname_b, "/dev/") == 0)
     581          ttyname_b += sizeof "/dev/" - 1;	/* Discard /dev/ prefix.  */
     582      }
     583  
     584    while (n--)
     585      {
     586        if (!my_line_only
     587            || STREQ (ttyname_b, utmp_buf->ut_line))
     588          {
     589            if (need_users && IS_USER_PROCESS (utmp_buf))
     590              print_user (utmp_buf, boottime);
     591            else if (need_runlevel && UT_TYPE_RUN_LVL (utmp_buf))
     592              print_runlevel (utmp_buf);
     593            else if (need_boottime && UT_TYPE_BOOT_TIME (utmp_buf))
     594              print_boottime (utmp_buf);
     595            /* I've never seen one of these, so I don't know what it should
     596               look like :^)
     597               FIXME: handle OLD_TIME also, perhaps show the delta? */
     598            else if (need_clockchange && UT_TYPE_NEW_TIME (utmp_buf))
     599              print_clockchange (utmp_buf);
     600            else if (need_initspawn && UT_TYPE_INIT_PROCESS (utmp_buf))
     601              print_initspawn (utmp_buf);
     602            else if (need_login && UT_TYPE_LOGIN_PROCESS (utmp_buf))
     603              print_login (utmp_buf);
     604            else if (need_deadprocs && UT_TYPE_DEAD_PROCESS (utmp_buf))
     605              print_deadprocs (utmp_buf);
     606          }
     607  
     608        if (UT_TYPE_BOOT_TIME (utmp_buf))
     609          boottime = utmp_buf->ut_ts.tv_sec;
     610  
     611        utmp_buf++;
     612      }
     613  }
     614  
     615  /* Display a list of who is on the system, according to utmp file FILENAME.
     616     Use read_utmp OPTIONS to read the file.  */
     617  static void
     618  who (char const *filename, int options)
     619  {
     620    idx_t n_users;
     621    struct gl_utmp *utmp_buf;
     622    if (short_list)
     623      options |= READ_UTMP_USER_PROCESS;
     624    if (read_utmp (filename, &n_users, &utmp_buf, options) != 0)
     625      error (EXIT_FAILURE, errno, "%s", quotef (filename));
     626  
     627    if (short_list)
     628      list_entries_who (n_users, utmp_buf);
     629    else
     630      scan_entries (n_users, utmp_buf);
     631  
     632    free (utmp_buf);
     633  }
     634  
     635  void
     636  usage (int status)
     637  {
     638    if (status != EXIT_SUCCESS)
     639      emit_try_help ();
     640    else
     641      {
     642        printf (_("Usage: %s [OPTION]... [ FILE | ARG1 ARG2 ]\n"), program_name);
     643        fputs (_("\
     644  Print information about users who are currently logged in.\n\
     645  "), stdout);
     646        fputs (_("\
     647  \n\
     648    -a, --all         same as -b -d --login -p -r -t -T -u\n\
     649    -b, --boot        time of last system boot\n\
     650    -d, --dead        print dead processes\n\
     651    -H, --heading     print line of column headings\n\
     652  "), stdout);
     653        fputs (_("\
     654    -l, --login       print system login processes\n\
     655  "), stdout);
     656        fputs (_("\
     657        --lookup      attempt to canonicalize hostnames via DNS\n\
     658    -m                only hostname and user associated with stdin\n\
     659    -p, --process     print active processes spawned by init\n\
     660  "), stdout);
     661        fputs (_("\
     662    -q, --count       all login names and number of users logged on\n\
     663    -r, --runlevel    print current runlevel\n\
     664    -s, --short       print only name, line, and time (default)\n\
     665    -t, --time        print last system clock change\n\
     666  "), stdout);
     667        fputs (_("\
     668    -T, -w, --mesg    add user's message status as +, - or ?\n\
     669    -u, --users       list users logged in\n\
     670        --message     same as -T\n\
     671        --writable    same as -T\n\
     672  "), stdout);
     673        fputs (HELP_OPTION_DESCRIPTION, stdout);
     674        fputs (VERSION_OPTION_DESCRIPTION, stdout);
     675        printf (_("\
     676  \n\
     677  If FILE is not specified, use %s.  %s as FILE is common.\n\
     678  If ARG1 ARG2 given, -m presumed: 'am i' or 'mom likes' are usual.\n\
     679  "), UTMP_FILE, WTMP_FILE);
     680        emit_ancillary_info (PROGRAM_NAME);
     681      }
     682    exit (status);
     683  }
     684  
     685  int
     686  main (int argc, char **argv)
     687  {
     688    int optc;
     689    bool assumptions = true;
     690  
     691    initialize_main (&argc, &argv);
     692    set_program_name (argv[0]);
     693    setlocale (LC_ALL, "");
     694    bindtextdomain (PACKAGE, LOCALEDIR);
     695    textdomain (PACKAGE);
     696  
     697    atexit (close_stdout);
     698  
     699    while ((optc = getopt_long (argc, argv, "abdlmpqrstuwHT", longopts, nullptr))
     700           != -1)
     701      {
     702        switch (optc)
     703          {
     704          case 'a':
     705            need_boottime = true;
     706            need_deadprocs = true;
     707            need_login = true;
     708            need_initspawn = true;
     709            need_runlevel = true;
     710            need_clockchange = true;
     711            need_users = true;
     712            include_mesg = true;
     713            include_idle = true;
     714            include_exit = true;
     715            assumptions = false;
     716            break;
     717  
     718          case 'b':
     719            need_boottime = true;
     720            assumptions = false;
     721            break;
     722  
     723          case 'd':
     724            need_deadprocs = true;
     725            include_idle = true;
     726            include_exit = true;
     727            assumptions = false;
     728            break;
     729  
     730          case 'H':
     731            include_heading = true;
     732            break;
     733  
     734          case 'l':
     735            need_login = true;
     736            include_idle = true;
     737            assumptions = false;
     738            break;
     739  
     740          case 'm':
     741            my_line_only = true;
     742            break;
     743  
     744          case 'p':
     745            need_initspawn = true;
     746            assumptions = false;
     747            break;
     748  
     749          case 'q':
     750            short_list = true;
     751            break;
     752  
     753          case 'r':
     754            need_runlevel = true;
     755            include_idle = true;
     756            assumptions = false;
     757            break;
     758  
     759          case 's':
     760            short_output = true;
     761            break;
     762  
     763          case 't':
     764            need_clockchange = true;
     765            assumptions = false;
     766            break;
     767  
     768          case 'T':
     769          case 'w':
     770            include_mesg = true;
     771            break;
     772  
     773          case 'u':
     774            need_users = true;
     775            include_idle = true;
     776            assumptions = false;
     777            break;
     778  
     779          case LOOKUP_OPTION:
     780            do_lookup = true;
     781            break;
     782  
     783          case_GETOPT_HELP_CHAR;
     784  
     785          case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
     786  
     787          default:
     788            usage (EXIT_FAILURE);
     789          }
     790      }
     791  
     792    if (assumptions)
     793      {
     794        need_users = true;
     795        short_output = true;
     796      }
     797  
     798    if (include_exit)
     799      {
     800        short_output = false;
     801      }
     802  
     803    if (hard_locale (LC_TIME))
     804      {
     805        time_format = "%Y-%m-%d %H:%M";
     806        time_format_width = 4 + 1 + 2 + 1 + 2 + 1 + 2 + 1 + 2;
     807      }
     808    else
     809      {
     810        time_format = "%b %e %H:%M";
     811        time_format_width = 3 + 1 + 2 + 1 + 2 + 1 + 2;
     812      }
     813  
     814    switch (argc - optind)
     815      {
     816      case 2:			/* who <blurf> <glop> */
     817        my_line_only = true;
     818        FALLTHROUGH;
     819      case -1:
     820      case 0:			/* who */
     821        who (UTMP_FILE, READ_UTMP_CHECK_PIDS);
     822        break;
     823  
     824      case 1:			/* who <utmp file> */
     825        who (argv[optind], 0);
     826        break;
     827  
     828      default:			/* lose */
     829        error (0, 0, _("extra operand %s"), quote (argv[optind + 2]));
     830        usage (EXIT_FAILURE);
     831      }
     832  
     833    return EXIT_SUCCESS;
     834  }