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