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