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