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