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