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