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