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 *
idle_string(time_t when,time_t boottime)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 *
time_string(struct gl_utmp const * utmp_ent)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
print_line(char const * user,const char state,char const * line,char const * time_str,char const * idle,char const * pid,char const * comment,char const * exitstr)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
is_tty_writable(struct stat const * pstat)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
print_user(struct gl_utmp const * utmp_ent,time_t boottime)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
print_boottime(struct gl_utmp const * utmp_ent)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 *
make_id_equals_comment(struct gl_utmp const * utmp_ent)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
print_deadprocs(struct gl_utmp const * utmp_ent)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
print_login(struct gl_utmp const * utmp_ent)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
print_initspawn(struct gl_utmp const * utmp_ent)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
print_clockchange(struct gl_utmp const * utmp_ent)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
print_runlevel(struct gl_utmp const * utmp_ent)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
list_entries_who(idx_t n,struct gl_utmp const * utmp_buf)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
print_heading(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
scan_entries(idx_t n,struct gl_utmp const * utmp_buf)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
who(char const * filename,int options)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
usage(int status)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
main(int argc,char ** argv)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 }
835