(list_entries_who): Don't output a trailing space.
[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 "vasprintf.h"
37
38 /* The official name of this program (e.g., no `g' prefix).  */
39 #define PROGRAM_NAME "who"
40
41 #define AUTHORS "Joseph Arceneaux", "David MacKenzie", "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 };
176
177 static struct option const longopts[] = {
178   {"all", no_argument, NULL, 'a'},
179   {"boot", no_argument, NULL, 'b'},
180   {"count", no_argument, NULL, 'q'},
181   {"dead", no_argument, NULL, 'd'},
182   {"heading", no_argument, NULL, 'H'},
183   {"idle", no_argument, NULL, 'i'},
184   {"login", no_argument, NULL, 'l'},
185   {"lookup", no_argument, NULL, LOOKUP_OPTION},
186   {"message", no_argument, NULL, 'T'},
187   {"mesg", no_argument, NULL, 'T'},
188   {"process", no_argument, NULL, 'p'},
189   {"runlevel", no_argument, NULL, 'r'},
190   {"short", no_argument, NULL, 's'},
191   {"time", no_argument, NULL, 't'},
192   {"users", no_argument, NULL, 'u'},
193   {"writable", no_argument, NULL, 'T'},
194   {GETOPT_HELP_OPTION_DECL},
195   {GETOPT_VERSION_OPTION_DECL},
196   {NULL, 0, NULL, 0}
197 };
198
199 /* Return a string representing the time between WHEN and the time
200    that this function is first run.
201    FIXME: locale? */
202 static const char *
203 idle_string (time_t when)
204 {
205   static time_t now = 0;
206   static char idle_hhmm[IDLESTR_LEN];
207   time_t seconds_idle;
208
209   if (now == 0)
210     time (&now);
211
212   seconds_idle = now - when;
213   if (seconds_idle < 60)        /* One minute. */
214     return "  .  ";
215   if (seconds_idle < (24 * 60 * 60))    /* One day. */
216     {
217       sprintf (idle_hhmm, "%02d:%02d",
218                (int) (seconds_idle / (60 * 60)),
219                (int) ((seconds_idle % (60 * 60)) / 60));
220       return (const char *) idle_hhmm;
221     }
222   return _(" old ");
223 }
224
225 /* Return a standard time string, "mon dd hh:mm"
226    FIXME: handle localization */
227 static const char *
228 time_string (const STRUCT_UTMP *utmp_ent)
229 {
230   /* Don't take the address of UT_TIME_MEMBER directly.
231      Ulrich Drepper wrote:
232      ``... GNU libc (and perhaps other libcs as well) have extended
233      utmp file formats which do not use a simple time_t ut_time field.
234      In glibc, ut_time is a macro which selects for backward compatibility
235      the tv_sec member of a struct timeval value.''  */
236   time_t tm = UT_TIME_MEMBER (utmp_ent);
237
238   char *ptr = ctime (&tm) + 4;
239   ptr[12] = '\0';
240   return ptr;
241 }
242
243 /* Print formatted output line. Uses mostly arbitrary field sizes, probably
244    will need tweaking if any of the localization stuff is done, or for 64 bit
245    pids, etc. */
246 static void
247 print_line (const char *user, const char state, const char *line,
248             const char *time_str, const char *idle, const char *pid,
249             const char *comment, const char *exitstr)
250 {
251   static char mesg[3] = { ' ', 'x', '\0' };
252   char *buf;
253   char x_idle[1 + IDLESTR_LEN + 1];
254   char x_pid[1 + INT_STRLEN_BOUND (pid_t) + 1];
255   char *x_exitstr;
256   int err;
257
258   mesg[1] = state;
259
260   if (include_idle && !short_output)
261     sprintf (x_idle, " %-6s", idle);
262   else
263     *x_idle = '\0';
264
265   if (!short_output)
266     sprintf (x_pid, " %10s", pid);
267   else
268     *x_pid = '\0';
269
270   x_exitstr = xmalloc (include_exit ? 1 + MAX (12, strlen (exitstr)) + 1 : 1);
271   if (include_exit)
272     sprintf (x_exitstr, " %-12s", exitstr);
273   else
274     *x_exitstr = '\0';
275
276   err = asprintf (&buf,
277                   "%-8.8s"
278                   "%s"
279                   " %-12s"
280                   " %-12s"
281                   "%s"
282                   "%s"
283                   " %-8s"
284                   "%s"
285                   ,
286                   user ? user : "   .",
287                   include_mesg ? mesg : "",
288                   line,
289                   time_str,
290                   x_idle,
291                   x_pid,
292                   /* FIXME: it's not really clear whether the following
293                      field should be in the short_output.  A strict reading
294                      of SUSv2 would suggest not, but I haven't seen any
295                      implementations that actually work that way... */
296                   comment,
297                   x_exitstr
298                   );
299   if (err == -1)
300     xalloc_die ();
301
302   {
303     /* Remove any trailing spaces.  */
304     char *p = buf + strlen (buf);
305     while (*--p == ' ')
306       /* empty */;
307     *(p + 1) = '\0';
308   }
309
310   puts (buf);
311   free (buf);
312   free (x_exitstr);
313 }
314
315 /* Send properly parsed USER_PROCESS info to print_line */
316 static void
317 print_user (const STRUCT_UTMP *utmp_ent)
318 {
319   struct stat stats;
320   time_t last_change;
321   char mesg;
322   char idlestr[IDLESTR_LEN];
323   static char *hoststr;
324   static size_t hostlen;
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 pathname.  Some system may put the full,
334      absolute pathname 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, "%.6s", idle_string (last_change));
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 = 0, *display = 0;
369
370       /* Copy the host name into UT_HOST, and ensure it's nul terminated. */
371       strncpy (ut_host, utmp_ent->ut_host, (int) sizeof (utmp_ent->ut_host));
372       ut_host[sizeof (utmp_ent->ut_host)] = '\0';
373
374       /* Look for an X display.  */
375       display = strchr (ut_host, ':');
376       if (display)
377         *display++ = '\0';
378
379       if (*ut_host && do_lookup)
380         {
381           /* See if we can canonicalize it.  */
382           host = canon_host (ut_host);
383         }
384
385       if (! host)
386         host = ut_host;
387
388       if (display)
389         {
390           if (hostlen < strlen (host) + strlen (display) + 4)
391             {
392               hostlen = strlen (host) + strlen (display) + 4;
393               hoststr = xrealloc (hoststr, hostlen);
394             }
395           sprintf (hoststr, "(%s:%s)", host, display);
396         }
397       else
398         {
399           if (hostlen < strlen (host) + 3)
400             {
401               hostlen = strlen (host) + 3;
402               hoststr = xrealloc (hoststr, hostlen);
403             }
404           sprintf (hoststr, "(%s)", host);
405         }
406     }
407   else
408     {
409       if (hostlen < 1)
410         {
411           hostlen = 1;
412           hoststr = xrealloc (hoststr, hostlen);
413         }
414       stpcpy (hoststr, "");
415     }
416 #endif
417
418   print_line (UT_USER (utmp_ent), mesg, utmp_ent->ut_line,
419               time_string (utmp_ent), idlestr, pidstr,
420               hoststr ? hoststr : "", "");
421 }
422
423 static void
424 print_boottime (const STRUCT_UTMP *utmp_ent)
425 {
426   print_line ("", ' ', "system boot", time_string (utmp_ent), "", "", "", "");
427 }
428
429 static char *
430 make_id_equals_comment (STRUCT_UTMP const *utmp_ent)
431 {
432   char *comment = xmalloc (strlen (_("id=")) + sizeof UT_ID (utmp_ent) + 1);
433
434   /* Cast field width argument to `int' to avoid warning from gcc.  */
435   sprintf (comment, "%s%.*s", _("id="), (int) sizeof UT_ID (utmp_ent),
436            UT_ID (utmp_ent));
437   return comment;
438 }
439
440 static void
441 print_deadprocs (const STRUCT_UTMP *utmp_ent)
442 {
443   static char *exitstr;
444   char *comment = make_id_equals_comment (utmp_ent);
445   PIDSTR_DECL_AND_INIT (pidstr, utmp_ent);
446
447   if (!exitstr)
448     exitstr = xmalloc (strlen (_("term="))
449                        + INT_STRLEN_BOUND (UT_EXIT_E_TERMINATION (utmp_ent)) + 1
450                        + strlen (_("exit="))
451                        + INT_STRLEN_BOUND (UT_EXIT_E_EXIT (utmp_ent))
452                        + 1);
453   sprintf (exitstr, "%s%d %s%d", _("term="), UT_EXIT_E_TERMINATION (utmp_ent),
454            _("exit="), UT_EXIT_E_EXIT (utmp_ent));
455
456   /* FIXME: add idle time? */
457
458   print_line ("", ' ', utmp_ent->ut_line,
459               time_string (utmp_ent), "", pidstr, comment, exitstr);
460   free (comment);
461 }
462
463 static void
464 print_login (const STRUCT_UTMP *utmp_ent)
465 {
466   char *comment = make_id_equals_comment (utmp_ent);
467   PIDSTR_DECL_AND_INIT (pidstr, utmp_ent);
468
469   /* FIXME: add idle time? */
470
471   print_line ("LOGIN", ' ', utmp_ent->ut_line,
472               time_string (utmp_ent), "", pidstr, comment, "");
473   free (comment);
474 }
475
476 static void
477 print_initspawn (const STRUCT_UTMP *utmp_ent)
478 {
479   char *comment = make_id_equals_comment (utmp_ent);
480   PIDSTR_DECL_AND_INIT (pidstr, utmp_ent);
481
482   print_line ("", ' ', utmp_ent->ut_line,
483               time_string (utmp_ent), "", pidstr, comment, "");
484   free (comment);
485 }
486
487 static void
488 print_clockchange (const STRUCT_UTMP *utmp_ent)
489 {
490   /* FIXME: handle NEW_TIME & OLD_TIME both */
491   print_line ("", ' ', _("clock change"),
492               time_string (utmp_ent), "", "", "", "");
493 }
494
495 static void
496 print_runlevel (const STRUCT_UTMP *utmp_ent)
497 {
498   static char *runlevline, *comment;
499   int last = UT_PID (utmp_ent) / 256;
500   int curr = UT_PID (utmp_ent) % 256;
501
502   if (!runlevline)
503     runlevline = xmalloc (strlen (_("run-level")) + 3);
504   sprintf (runlevline, "%s %c", _("run-level"), curr);
505
506   if (!comment)
507     comment = xmalloc (strlen (_("last=")) + 2);
508   sprintf (comment, "%s%c", _("last="), (last == 'N') ? 'S' : last);
509
510   print_line ("", ' ', runlevline, time_string (utmp_ent),
511               "", "", comment, "");
512
513   return;
514 }
515
516 /* Print the username of each valid entry and the number of valid entries
517    in UTMP_BUF, which should have N elements. */
518 static void
519 list_entries_who (int n, const STRUCT_UTMP *utmp_buf)
520 {
521   int entries = 0;
522   char const *separator = "";
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%s", separator, trimmed_name);
533           free (trimmed_name);
534           separator = " ";
535           entries++;
536         }
537       utmp_buf++;
538     }
539   printf (_("\n# users=%u\n"), entries);
540 }
541
542 static void
543 print_heading (void)
544 {
545   print_line (_("NAME"), ' ', _("LINE"), _("TIME"), _("IDLE"), _("PID"),
546               _("COMMENT"), _("EXIT"));
547 }
548
549 /* Display UTMP_BUF, which should have N entries. */
550 static void
551 scan_entries (int n, const STRUCT_UTMP *utmp_buf)
552 {
553   char *ttyname_b IF_LINT ( = NULL);
554
555   if (include_heading)
556     print_heading ();
557
558   if (my_line_only)
559     {
560       ttyname_b = ttyname (0);
561       if (!ttyname_b)
562         return;
563       if (strncmp (ttyname_b, DEV_DIR_WITH_TRAILING_SLASH, DEV_DIR_LEN) == 0)
564         ttyname_b += DEV_DIR_LEN;       /* Discard /dev/ prefix.  */
565     }
566
567   while (n--)
568     {
569       if (!my_line_only ||
570           strncmp (ttyname_b, utmp_buf->ut_line,
571                    sizeof (utmp_buf->ut_line)) == 0)
572         {
573           if (need_users && IS_USER_PROCESS (utmp_buf))
574             print_user (utmp_buf);
575           else if (need_runlevel && UT_TYPE (utmp_buf) == RUN_LVL)
576             print_runlevel (utmp_buf);
577           else if (need_boottime && UT_TYPE (utmp_buf) == BOOT_TIME)
578             print_boottime (utmp_buf);
579           /* I've never seen one of these, so I don't know what it should
580              look like :^)
581              FIXME: handle OLD_TIME also, perhaps show the delta? */
582           else if (need_clockchange && UT_TYPE (utmp_buf) == NEW_TIME)
583             print_clockchange (utmp_buf);
584           else if (need_initspawn && UT_TYPE (utmp_buf) == INIT_PROCESS)
585             print_initspawn (utmp_buf);
586           else if (need_login && UT_TYPE (utmp_buf) == LOGIN_PROCESS)
587             print_login (utmp_buf);
588           else if (need_deadprocs && UT_TYPE (utmp_buf) == DEAD_PROCESS)
589             print_deadprocs (utmp_buf);
590         }
591
592       utmp_buf++;
593     }
594 }
595
596 /* Display a list of who is on the system, according to utmp file filename. */
597 static void
598 who (const char *filename)
599 {
600   int n_users;
601   STRUCT_UTMP *utmp_buf;
602   int fail = read_utmp (filename, &n_users, &utmp_buf);
603
604   if (fail)
605     error (EXIT_FAILURE, errno, "%s", filename);
606
607   if (short_list)
608     list_entries_who (n_users, utmp_buf);
609   else
610     scan_entries (n_users, utmp_buf);
611
612   free (utmp_buf);
613 }
614
615 void
616 usage (int status)
617 {
618   if (status != EXIT_SUCCESS)
619     fprintf (stderr, _("Try `%s --help' for more information.\n"),
620              program_name);
621   else
622     {
623       printf (_("Usage: %s [OPTION]... [ FILE | ARG1 ARG2 ]\n"), program_name);
624       fputs (_("\
625 \n\
626   -a, --all         same as -b -d --login -p -r -t -T -u\n\
627   -b, --boot        time of last system boot\n\
628   -d, --dead        print dead processes\n\
629   -H, --heading     print line of column headings\n\
630 "), stdout);
631       fputs (_("\
632   -i, --idle        add idle time as HOURS:MINUTES, . or old\n\
633                     (deprecated, use -u)\n\
634   -l, --login       print system login processes\n\
635 "), stdout);
636       fputs (_("\
637       --lookup      attempt to canonicalize hostnames via DNS\n\
638   -m                only hostname and user associated with stdin\n\
639   -p, --process     print active processes spawned by init\n\
640 "), stdout);
641       fputs (_("\
642   -q, --count       all login names and number of users logged on\n\
643   -r, --runlevel    print current runlevel\n\
644   -s, --short       print only name, line, and time (default)\n\
645   -t, --time        print last system clock change\n\
646 "), stdout);
647       fputs (_("\
648   -T, -w, --mesg    add user's message status as +, - or ?\n\
649   -u, --users       list users logged in\n\
650       --message     same as -T\n\
651       --writable    same as -T\n\
652 "), stdout);
653       fputs (HELP_OPTION_DESCRIPTION, stdout);
654       fputs (VERSION_OPTION_DESCRIPTION, stdout);
655       printf (_("\
656 \n\
657 If FILE is not specified, use %s.  %s as FILE is common.\n\
658 If ARG1 ARG2 given, -m presumed: `am i' or `mom likes' are usual.\n\
659 "), UTMP_FILE, WTMP_FILE);
660       printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
661     }
662   exit (status);
663 }
664
665 int
666 main (int argc, char **argv)
667 {
668   int optc, longind;
669   int assumptions = 1;
670
671   initialize_main (&argc, &argv);
672   program_name = argv[0];
673   setlocale (LC_ALL, "");
674   bindtextdomain (PACKAGE, LOCALEDIR);
675   textdomain (PACKAGE);
676
677   atexit (close_stdout);
678
679   while ((optc = getopt_long (argc, argv, "abdilmpqrstuwHT", longopts,
680                               &longind)) != -1)
681     {
682       switch (optc)
683         {
684         case 0:
685           break;
686
687         case 'a':
688           need_boottime = 1;
689           need_deadprocs = 1;
690           need_login = 1;
691           need_initspawn = 1;
692           need_runlevel = 1;
693           need_clockchange = 1;
694           need_users = 1;
695           include_mesg = 1;
696           include_idle = 1;
697           include_exit = 1;
698           assumptions = 0;
699           break;
700
701         case 'b':
702           need_boottime = 1;
703           assumptions = 0;
704           break;
705
706         case 'd':
707           need_deadprocs = 1;
708           include_idle = 1;
709           include_exit = 1;
710           assumptions = 0;
711           break;
712
713         case 'H':
714           include_heading = 1;
715           break;
716
717         case 'l':
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 LOOKUP_OPTION:
768           do_lookup = 1;
769           break;
770
771           case_GETOPT_HELP_CHAR;
772
773           case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
774
775         default:
776           usage (EXIT_FAILURE);
777         }
778     }
779
780   if (assumptions)
781     {
782       need_users = 1;
783       short_output = 1;
784     }
785
786   if (include_exit)
787     {
788       short_output = 0;
789     }
790
791   switch (argc - optind)
792     {
793     case -1:
794     case 0:                     /* who */
795       who (UTMP_FILE);
796       break;
797
798     case 1:                     /* who <utmp file> */
799       who (argv[optind]);
800       break;
801
802     case 2:                     /* who <blurf> <glop> */
803       my_line_only = 1;
804       who (UTMP_FILE);
805       break;
806
807     default:                    /* lose */
808       error (0, 0, _("too many arguments"));
809       usage (EXIT_FAILURE);
810     }
811
812   exit (EXIT_SUCCESS);
813 }