(idle_string, print_user): New arg boottime,
[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 (const char *user, const char state, const char *line,
263             const char *time_str, const char *idle, const char *pid,
264             const char *comment, const char *exitstr)
265 {
266   static char mesg[3] = { ' ', 'x', '\0' };
267   char *buf;
268   char x_idle[1 + IDLESTR_LEN + 1];
269   char x_pid[1 + INT_STRLEN_BOUND (pid_t) + 1];
270   char *x_exitstr;
271   int err;
272
273   mesg[1] = state;
274
275   if (include_idle && !short_output && strlen (idle) < sizeof x_idle - 1)
276     sprintf (x_idle, " %-6s", idle);
277   else
278     *x_idle = '\0';
279
280   if (!short_output && strlen (pid) < sizeof x_pid - 1)
281     sprintf (x_pid, " %10s", pid);
282   else
283     *x_pid = '\0';
284
285   x_exitstr = xmalloc (include_exit ? 1 + MAX (12, strlen (exitstr)) + 1 : 1);
286   if (include_exit)
287     sprintf (x_exitstr, " %-12s", exitstr);
288   else
289     *x_exitstr = '\0';
290
291   err = asprintf (&buf,
292                   "%-8s"
293                   "%s"
294                   " %-12s"
295                   " %-*s"
296                   "%s"
297                   "%s"
298                   " %-8s"
299                   "%s"
300                   ,
301                   user ? user : "   .",
302                   include_mesg ? mesg : "",
303                   line,
304                   time_format_width,
305                   time_str,
306                   x_idle,
307                   x_pid,
308                   /* FIXME: it's not really clear whether the following
309                      field should be in the short_output.  A strict reading
310                      of SUSv2 would suggest not, but I haven't seen any
311                      implementations that actually work that way... */
312                   comment,
313                   x_exitstr
314                   );
315   if (err == -1)
316     xalloc_die ();
317
318   {
319     /* Remove any trailing spaces.  */
320     char *p = buf + strlen (buf);
321     while (*--p == ' ')
322       /* empty */;
323     *(p + 1) = '\0';
324   }
325
326   puts (buf);
327   free (buf);
328   free (x_exitstr);
329 }
330
331 /* Send properly parsed USER_PROCESS info to print_line.  The most
332    recent boot time is BOOTTIME. */
333 static void
334 print_user (const STRUCT_UTMP *utmp_ent, time_t boottime)
335 {
336   struct stat stats;
337   time_t last_change;
338   char mesg;
339   char idlestr[IDLESTR_LEN + 1];
340   static char *hoststr;
341   static size_t hostlen;
342
343 #define DEV_DIR_WITH_TRAILING_SLASH "/dev/"
344 #define DEV_DIR_LEN (sizeof (DEV_DIR_WITH_TRAILING_SLASH) - 1)
345
346   char line[sizeof (utmp_ent->ut_line) + DEV_DIR_LEN + 1];
347   PIDSTR_DECL_AND_INIT (pidstr, utmp_ent);
348
349   /* Copy ut_line into LINE, prepending `/dev/' if ut_line is not
350      already an absolute pathname.  Some system may put the full,
351      absolute pathname in ut_line.  */
352   if (utmp_ent->ut_line[0] == '/')
353     {
354       strncpy (line, utmp_ent->ut_line, sizeof (utmp_ent->ut_line));
355       line[sizeof (utmp_ent->ut_line)] = '\0';
356     }
357   else
358     {
359       strcpy (line, DEV_DIR_WITH_TRAILING_SLASH);
360       strncpy (line + DEV_DIR_LEN, utmp_ent->ut_line,
361                sizeof (utmp_ent->ut_line));
362       line[DEV_DIR_LEN + sizeof (utmp_ent->ut_line)] = '\0';
363     }
364
365   if (stat (line, &stats) == 0)
366     {
367       mesg = (stats.st_mode & S_IWGRP) ? '+' : '-';
368       last_change = stats.st_atime;
369     }
370   else
371     {
372       mesg = '?';
373       last_change = 0;
374     }
375
376   if (last_change)
377     sprintf (idlestr, "%.*s", IDLESTR_LEN, idle_string (last_change, boottime));
378   else
379     sprintf (idlestr, "  ?");
380
381 #if HAVE_UT_HOST
382   if (utmp_ent->ut_host[0])
383     {
384       char ut_host[sizeof (utmp_ent->ut_host) + 1];
385       char *host = 0, *display = 0;
386
387       /* Copy the host name into UT_HOST, and ensure it's nul terminated. */
388       strncpy (ut_host, utmp_ent->ut_host, sizeof (utmp_ent->ut_host));
389       ut_host[sizeof (utmp_ent->ut_host)] = '\0';
390
391       /* Look for an X display.  */
392       display = strchr (ut_host, ':');
393       if (display)
394         *display++ = '\0';
395
396       if (*ut_host && do_lookup)
397         {
398           /* See if we can canonicalize it.  */
399           host = canon_host (ut_host);
400         }
401
402       if (! host)
403         host = ut_host;
404
405       if (display)
406         {
407           if (hostlen < strlen (host) + strlen (display) + 4)
408             {
409               hostlen = strlen (host) + strlen (display) + 4;
410               hoststr = xrealloc (hoststr, hostlen);
411             }
412           sprintf (hoststr, "(%s:%s)", host, display);
413         }
414       else
415         {
416           if (hostlen < strlen (host) + 3)
417             {
418               hostlen = strlen (host) + 3;
419               hoststr = xrealloc (hoststr, hostlen);
420             }
421           sprintf (hoststr, "(%s)", host);
422         }
423     }
424   else
425     {
426       if (hostlen < 1)
427         {
428           hostlen = 1;
429           hoststr = xrealloc (hoststr, hostlen);
430         }
431       stpcpy (hoststr, "");
432     }
433 #endif
434
435   print_line (UT_USER (utmp_ent), mesg, utmp_ent->ut_line,
436               time_string (utmp_ent), idlestr, pidstr,
437               hoststr ? hoststr : "", "");
438 }
439
440 static void
441 print_boottime (const STRUCT_UTMP *utmp_ent)
442 {
443   print_line ("", ' ', "system boot", time_string (utmp_ent), "", "", "", "");
444 }
445
446 static char *
447 make_id_equals_comment (STRUCT_UTMP const *utmp_ent)
448 {
449   char *comment = xmalloc (strlen (_("id=")) + sizeof UT_ID (utmp_ent) + 1);
450
451   strcpy (comment, _("id="));
452   strncat (comment, UT_ID (utmp_ent), sizeof UT_ID (utmp_ent));
453   return comment;
454 }
455
456 static void
457 print_deadprocs (const STRUCT_UTMP *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 (UT_EXIT_E_TERMINATION (utmp_ent)) + 1
466                        + strlen (_("exit="))
467                        + INT_STRLEN_BOUND (UT_EXIT_E_EXIT (utmp_ent))
468                        + 1);
469   sprintf (exitstr, "%s%d %s%d", _("term="), UT_EXIT_E_TERMINATION (utmp_ent),
470            _("exit="), UT_EXIT_E_EXIT (utmp_ent));
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 (const STRUCT_UTMP *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 (const STRUCT_UTMP *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 (const STRUCT_UTMP *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 (const STRUCT_UTMP *utmp_ent)
513 {
514   static char *runlevline, *comment;
515   int last = UT_PID (utmp_ent) / 256;
516   int curr = UT_PID (utmp_ent) % 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               "", "", 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 (int n, const STRUCT_UTMP *utmp_buf)
536 {
537   int entries = 0;
538   char const *separator = "";
539
540   while (n--)
541     {
542       if (UT_USER (utmp_buf)[0] && UT_TYPE (utmp_buf) == USER_PROCESS)
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=%u\n"), entries);
556 }
557
558 static void
559 print_heading (void)
560 {
561   print_line (_("NAME"), ' ', _("LINE"), _("TIME"), _("IDLE"), _("PID"),
562               _("COMMENT"), _("EXIT"));
563 }
564
565 /* Display UTMP_BUF, which should have N entries. */
566 static void
567 scan_entries (int n, const STRUCT_UTMP *utmp_buf)
568 {
569   char *ttyname_b IF_LINT ( = NULL);
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 (0);
578       if (!ttyname_b)
579         return;
580       if (strncmp (ttyname_b, DEV_DIR_WITH_TRAILING_SLASH, DEV_DIR_LEN) == 0)
581         ttyname_b += DEV_DIR_LEN;       /* Discard /dev/ prefix.  */
582     }
583
584   while (n--)
585     {
586       if (!my_line_only ||
587           strncmp (ttyname_b, utmp_buf->ut_line,
588                    sizeof (utmp_buf->ut_line)) == 0)
589         {
590           if (need_users && IS_USER_PROCESS (utmp_buf))
591             print_user (utmp_buf, boottime);
592           else if (need_runlevel && UT_TYPE (utmp_buf) == RUN_LVL)
593             print_runlevel (utmp_buf);
594           else if (need_boottime && UT_TYPE (utmp_buf) == BOOT_TIME)
595             print_boottime (utmp_buf);
596           /* I've never seen one of these, so I don't know what it should
597              look like :^)
598              FIXME: handle OLD_TIME also, perhaps show the delta? */
599           else if (need_clockchange && UT_TYPE (utmp_buf) == NEW_TIME)
600             print_clockchange (utmp_buf);
601           else if (need_initspawn && UT_TYPE (utmp_buf) == INIT_PROCESS)
602             print_initspawn (utmp_buf);
603           else if (need_login && UT_TYPE (utmp_buf) == LOGIN_PROCESS)
604             print_login (utmp_buf);
605           else if (need_deadprocs && UT_TYPE (utmp_buf) == DEAD_PROCESS)
606             print_deadprocs (utmp_buf);
607         }
608
609       if (UT_TYPE (utmp_buf) == BOOT_TIME)
610         boottime = UT_TIME_MEMBER (utmp_buf);
611
612       utmp_buf++;
613     }
614 }
615
616 /* Display a list of who is on the system, according to utmp file filename. */
617 static void
618 who (const char *filename)
619 {
620   int n_users;
621   STRUCT_UTMP *utmp_buf;
622   int fail = read_utmp (filename, &n_users, &utmp_buf);
623
624   if (fail)
625     error (EXIT_FAILURE, errno, "%s", 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     fprintf (stderr, _("Try `%s --help' for more information.\n"),
640              program_name);
641   else
642     {
643       printf (_("Usage: %s [OPTION]... [ FILE | ARG1 ARG2 ]\n"), program_name);
644       fputs (_("\
645 \n\
646   -a, --all         same as -b -d --login -p -r -t -T -u\n\
647   -b, --boot        time of last system boot\n\
648   -d, --dead        print dead processes\n\
649   -H, --heading     print line of column headings\n\
650 "), stdout);
651       fputs (_("\
652   -i, --idle        add idle time as HOURS:MINUTES, . or old\n\
653                     (deprecated, use -u)\n\
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       printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
681     }
682   exit (status);
683 }
684
685 int
686 main (int argc, char **argv)
687 {
688   int optc, longind;
689   int assumptions = 1;
690
691   initialize_main (&argc, &argv);
692   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, "abdilmpqrstuwHT", longopts,
700                               &longind)) != -1)
701     {
702       switch (optc)
703         {
704         case 0:
705           break;
706
707         case 'a':
708           need_boottime = 1;
709           need_deadprocs = 1;
710           need_login = 1;
711           need_initspawn = 1;
712           need_runlevel = 1;
713           need_clockchange = 1;
714           need_users = 1;
715           include_mesg = 1;
716           include_idle = 1;
717           include_exit = 1;
718           assumptions = 0;
719           break;
720
721         case 'b':
722           need_boottime = 1;
723           assumptions = 0;
724           break;
725
726         case 'd':
727           need_deadprocs = 1;
728           include_idle = 1;
729           include_exit = 1;
730           assumptions = 0;
731           break;
732
733         case 'H':
734           include_heading = 1;
735           break;
736
737         case 'l':
738           need_login = 1;
739           include_idle = 1;
740           assumptions = 0;
741           break;
742
743         case 'm':
744           my_line_only = 1;
745           break;
746
747         case 'p':
748           need_initspawn = 1;
749           assumptions = 0;
750           break;
751
752         case 'q':
753           short_list = 1;
754           break;
755
756         case 'r':
757           need_runlevel = 1;
758           include_idle = 1;
759           assumptions = 0;
760           break;
761
762         case 's':
763           short_output = 1;
764           break;
765
766         case 't':
767           need_clockchange = 1;
768           assumptions = 0;
769           break;
770
771         case 'T':
772         case 'w':
773           include_mesg = 1;
774           break;
775
776         case 'i':
777           error (0, 0,
778                  _("Warning: -i will be removed in a future release; \
779   use -u instead"));
780           /* Fall through.  */
781         case 'u':
782           need_users = 1;
783           include_idle = 1;
784           assumptions = 0;
785           break;
786
787         case LOOKUP_OPTION:
788           do_lookup = 1;
789           break;
790
791           case_GETOPT_HELP_CHAR;
792
793           case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
794
795         default:
796           usage (EXIT_FAILURE);
797         }
798     }
799
800   if (assumptions)
801     {
802       need_users = 1;
803       short_output = 1;
804     }
805
806   if (include_exit)
807     {
808       short_output = 0;
809     }
810
811   if (hard_locale (LC_TIME))
812     {
813       time_format = "%Y-%m-%d %H:%M";
814       time_format_width = 4 + 1 + 2 + 1 + 2 + 1 + 2 + 1 + 2;
815     }
816   else
817     {
818       time_format = "%b %e %H:%M";
819       time_format_width = 3 + 1 + 2 + 1 + 2 + 1 + 2;
820     }
821
822   switch (argc - optind)
823     {
824     case -1:
825     case 0:                     /* who */
826       who (UTMP_FILE);
827       break;
828
829     case 1:                     /* who <utmp file> */
830       who (argv[optind]);
831       break;
832
833     case 2:                     /* who <blurf> <glop> */
834       my_line_only = 1;
835       who (UTMP_FILE);
836       break;
837
838     default:                    /* lose */
839       error (0, 0, _("extra operand %s"), quote (argv[optind + 2]));
840       usage (EXIT_FAILURE);
841     }
842
843   exit (EXIT_SUCCESS);
844 }