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