(usage): Include one- or two-line synopsis in --help output.
[platform/upstream/coreutils.git] / src / who.c
1 /* GNU's users/who.
2    Copyright (C) 92, 93, 94, 1995 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
16    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
17
18 /* Written by jla; revised by djm */
19
20 /* Output format:
21    name [state] line time [idle] host
22    state: -T
23    name, line, time: not -q
24    idle: -u
25
26    Options:
27    -m           Same as 'who am i', for POSIX.
28    -q           Only user names and # logged on; overrides all other options.
29    -s           Name, line, time (default).
30    -i, -u       Idle hours and minutes; '.' means active in last minute;
31                 'old' means idle for >24 hours.
32    -H           Print column headings at top.
33    -w, -T       -s plus mesg (+ or -, or ? if bad line). */
34
35 #include <config.h>
36 #include <stdio.h>
37 #include <sys/types.h>
38
39 #ifdef HAVE_UTMPX_H
40 #include <utmpx.h>
41 #define STRUCT_UTMP struct utmpx
42 #else
43 #include <utmp.h>
44 #define STRUCT_UTMP struct utmp
45 #endif
46
47 #include <time.h>
48 #include <getopt.h>
49 #ifdef HAVE_SYS_PARAM_H
50 #include <sys/param.h>
51 #endif
52
53 #include "system.h"
54 #include "version.h"
55 #include "error.h"
56
57 #if !defined (UTMP_FILE) && defined (_PATH_UTMP)        /* 4.4BSD.  */
58 #define UTMP_FILE _PATH_UTMP
59 #endif
60
61 #if defined (UTMPX_FILE)        /* Solaris, SysVr4 */
62 #undef  UTMP_FILE
63 #define UTMP_FILE UTMPX_FILE
64 #endif
65
66 #ifndef UTMP_FILE
67 #define UTMP_FILE "/etc/utmp"
68 #endif
69
70 #ifndef MAXHOSTNAMELEN
71 #define MAXHOSTNAMELEN 64
72 #endif
73
74 #ifndef S_IWGRP
75 #define S_IWGRP 020
76 #endif
77
78 #ifdef WHO
79 #define COMMAND_NAME "who"
80 #else
81 #ifdef USERS
82 #define COMMAND_NAME "users"
83 #else
84  error You must define one of WHO and USERS.
85 #endif /* USERS */
86 #endif /* WHO */
87
88 int gethostname ();
89 char *ttyname ();
90 char *xmalloc ();
91
92 /* The name this program was run with. */
93 char *program_name;
94
95 /* If non-zero, display usage information and exit.  */
96 static int show_help;
97
98 /* If non-zero, print the version on standard output and exit.  */
99 static int show_version;
100
101 #ifdef WHO
102 /* If nonzero, display only a list of usernames and count of
103    the users logged on.
104    Ignored for `who am i'. */
105 static int short_list;
106
107 /* If nonzero, display the hours:minutes since each user has touched
108    the keyboard, or "." if within the last minute, or "old" if
109    not within the last day. */
110 static int include_idle;
111
112 /* If nonzero, display a line at the top describing each field. */
113 static int include_heading;
114
115 /* If nonzero, display a `+' for each user if mesg y, a `-' if mesg n,
116    or a `?' if their tty cannot be statted. */
117 static int include_mesg;
118 #endif /* WHO */
119
120 static struct option const longopts[] =
121 {
122 #ifdef WHO
123   {"count", no_argument, NULL, 'q'},
124   {"idle", no_argument, NULL, 'u'},
125   {"heading", no_argument, NULL, 'H'},
126   {"message", no_argument, NULL, 'T'},
127   {"mesg", no_argument, NULL, 'T'},
128   {"writable", no_argument, NULL, 'T'},
129 #endif /* WHO */
130   {"help", no_argument, &show_help, 1},
131   {"version", no_argument, &show_version, 1},
132   {NULL, 0, NULL, 0}
133 };
134
135 static STRUCT_UTMP *utmp_contents;
136
137 #if defined (WHO) || defined (USERS)
138
139 /* Copy UT->ut_name into storage obtained from malloc.  Then remove any
140    trailing spaces from the copy, NUL terminate it, and return the copy.  */
141
142 static char *
143 extract_trimmed_name (const STRUCT_UTMP *ut)
144 {
145   char *p, *trimmed_name;
146
147   trimmed_name = xmalloc (sizeof (ut->ut_name) + 1);
148   strncpy (trimmed_name, ut->ut_name, sizeof (ut->ut_name));
149   /* Append a trailing space character.  Some systems pad names shorter than
150      the maximum with spaces, others pad with NULs.  Remove any spaces.  */
151   trimmed_name[sizeof (ut->ut_name)] = ' ';
152   p = strchr (trimmed_name, ' ');
153   if (p != NULL)
154     *p = '\0';
155   return trimmed_name;
156 }
157
158 #endif /* WHO || USERS */
159
160 #if WHO
161
162 /* Return a string representing the time between WHEN and the time
163    that this function is first run. */
164
165 static const char *
166 idle_string (when)
167      time_t when;
168 {
169   static time_t now = 0;
170   static char idle[10];
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, "%02d:%02d",
182                (int) (seconds_idle / (60 * 60)),
183                (int) ((seconds_idle % (60 * 60)) / 60));
184       return (const char *) idle;
185     }
186   return " old ";
187 }
188
189 /* Display a line of information about entry THIS. */
190
191 static void
192 print_entry (this)
193      STRUCT_UTMP *this;
194 {
195   struct stat stats;
196   time_t last_change;
197   char mesg;
198
199 #define DEV_DIR_WITH_TRAILING_SLASH "/dev/"
200 #define DEV_DIR_LEN (sizeof (DEV_DIR_WITH_TRAILING_SLASH) - 1)
201
202   char line[sizeof (this->ut_line) + DEV_DIR_LEN + 1];
203
204   /* Copy ut_line into LINE, prepending `/dev/' if ut_line is not
205      already an absolute pathname.  Some system may put the full,
206      absolute pathname in ut_line.  */
207   if (this->ut_line[0] == '/')
208     {
209       strncpy (line, this->ut_line, sizeof (this->ut_line));
210       line[sizeof (this->ut_line)] = '\0';
211     }
212   else
213     {
214       strcpy (line, DEV_DIR_WITH_TRAILING_SLASH);
215       strncpy (line + DEV_DIR_LEN, this->ut_line, sizeof (this->ut_line));
216       line[DEV_DIR_LEN + sizeof (this->ut_line)] = '\0';
217     }
218
219   if (stat (line, &stats) == 0)
220     {
221       mesg = (stats.st_mode & S_IWGRP) ? '+' : '-';
222       last_change = stats.st_atime;
223     }
224   else
225     {
226       mesg = '?';
227       last_change = 0;
228     }
229   
230   printf ("%-8.*s", (int) sizeof (this->ut_name), this->ut_name);
231   if (include_mesg)
232     printf ("  %c  ", mesg);
233   printf (" %-8.*s", (int) sizeof (this->ut_line), this->ut_line);
234
235 #ifdef HAVE_UTMPX_H
236   printf (" %-12.12s", ctime (&this->ut_tv.tv_sec) + 4);
237 #else
238   printf (" %-12.12s", ctime (&this->ut_time) + 4);
239 #endif
240
241   if (include_idle)
242     {
243       if (last_change)
244         printf (" %s", idle_string (last_change));
245       else
246         printf ("   .  ");
247     }
248 #ifdef HAVE_UT_HOST
249   if (this->ut_host[0])
250     printf (" (%-.*s)", (int) sizeof (this->ut_host), this->ut_host);
251 #endif
252
253   putchar ('\n');
254 }
255
256 /* Print the username of each valid entry and the number of valid entries
257    in `utmp_contents', which should have N elements. */
258
259 static void
260 list_entries_who (n)
261      int n;
262 {
263   register STRUCT_UTMP *this = utmp_contents;
264   int entries;
265
266   entries = 0;
267   while (n--)
268     {
269       if (this->ut_name[0]
270 #ifdef USER_PROCESS
271           && this->ut_type == USER_PROCESS
272 #endif
273          )
274         {
275           char *trimmed_name;
276
277           trimmed_name = extract_trimmed_name (this);
278
279           printf ("%s ", trimmed_name);
280           free (trimmed_name);
281           entries++;
282         }
283       this++;
284     }
285   printf ("\n# users=%u\n", entries);
286 }
287
288 #endif /* WHO */
289
290 #ifdef USERS
291
292 static int
293 userid_compare (v_a, v_b)
294      const void *v_a;
295      const void *v_b;
296 {
297   char **a = (char **) v_a;
298   char **b = (char **) v_b;
299   return strcmp (*a, *b);
300 }
301
302 static void
303 list_entries_users (n)
304      int n;
305 {
306   register STRUCT_UTMP *this = utmp_contents;
307   char **u;
308   int i;
309   int n_entries;
310
311   n_entries = 0;
312   u = (char **) xmalloc (n * sizeof (u[0]));
313   for (i=0; i<n; i++)
314     {
315       if (this->ut_name[0]
316 #ifdef USER_PROCESS
317           && this->ut_type == USER_PROCESS
318 #endif
319          )
320         {
321           char *trimmed_name;
322
323           trimmed_name = extract_trimmed_name (this);
324
325           u[n_entries] = trimmed_name;
326           ++n_entries;
327         }
328       this++;
329     }
330
331   qsort (u, n_entries, sizeof (u[0]), userid_compare);
332
333   for (i=0; i<n_entries; i++)
334     {
335       int c;
336       fputs (u[i], stdout);
337       c = (i < n_entries-1 ? ' ' : '\n');
338       putchar (c);
339     }
340
341   for (i=0; i<n_entries; i++)
342     free (u[i]);
343   free (u);
344 }
345
346 #endif /* USERS */
347
348 #ifdef WHO
349
350 static void
351 print_heading ()
352 {
353   printf ("%-8s ", "USER");
354   if (include_mesg)
355     printf ("MESG ");
356   printf ("%-8s ", "LINE");
357   printf ("LOGIN-TIME   ");
358   if (include_idle)
359     printf ("IDLE  ");
360   printf ("FROM\n");
361 }
362
363 /* Display `utmp_contents', which should have N entries. */
364
365 static void
366 scan_entries (n)
367      int n;
368 {
369   register STRUCT_UTMP *this = utmp_contents;
370
371   if (include_heading)
372     print_heading ();
373
374   while (n--)
375     {
376       if (this->ut_name[0]
377 #ifdef USER_PROCESS
378           && this->ut_type == USER_PROCESS
379 #endif
380          )
381         print_entry (this);
382       this++;
383     }
384 }
385
386 #endif /* WHO */
387
388 /* Read the utmp file FILENAME into UTMP_CONTENTS and return the
389    number of entries it contains. */
390
391 static int
392 read_utmp (filename)
393      char *filename;
394 {
395   FILE *utmp;
396   struct stat file_stats;
397   int n_read;
398   size_t size;
399
400   utmp = fopen (filename, "r");
401   if (utmp == NULL)
402     error (1, errno, "%s", filename);
403
404   fstat (fileno (utmp), &file_stats);
405   size = file_stats.st_size;
406   if (size > 0)
407     utmp_contents = (STRUCT_UTMP *) xmalloc (size);
408   else
409     {
410       fclose (utmp);
411       return 0;
412     }
413
414   /* Use < instead of != in case the utmp just grew.  */
415   n_read = fread (utmp_contents, 1, size, utmp);
416   if (ferror (utmp) || fclose (utmp) == EOF
417       || n_read < size)
418     error (1, errno, "%s", filename);
419
420   return size / sizeof (STRUCT_UTMP);
421 }
422
423 /* Display a list of who is on the system, according to utmp file FILENAME. */
424
425 static void
426 who (filename)
427      char *filename;
428 {
429   int users;
430
431   users = read_utmp (filename);
432 #ifdef WHO
433   if (short_list)
434     list_entries_who (users);
435   else
436     scan_entries (users);
437 #else
438 #ifdef USERS
439   list_entries_users (users);
440 #endif /* USERS */
441 #endif /* WHO */
442 }
443
444 #ifdef WHO
445
446 /* Search `utmp_contents', which should have N entries, for
447    an entry with a `ut_line' field identical to LINE.
448    Return the first matching entry found, or NULL if there
449    is no matching entry. */
450
451 static STRUCT_UTMP *
452 search_entries (n, line)
453      int n;
454      char *line;
455 {
456   register STRUCT_UTMP *this = utmp_contents;
457
458   while (n--)
459     {
460       if (this->ut_name[0]
461 #ifdef USER_PROCESS
462           && this->ut_type == USER_PROCESS
463 #endif
464           && !strncmp (line, this->ut_line, sizeof (this->ut_line)))
465         return this;
466       this++;
467     }
468   return NULL;
469 }
470
471 /* Display the entry in utmp file FILENAME for this tty on standard input,
472    or nothing if there is no entry for it. */
473
474 static void
475 who_am_i (filename)
476      char *filename;
477 {
478   register STRUCT_UTMP *utmp_entry;
479   char hostname[MAXHOSTNAMELEN + 1];
480   char *tty;
481
482   if (gethostname (hostname, MAXHOSTNAMELEN + 1))
483     *hostname = 0;
484
485   if (include_heading)
486     {
487       printf ("%*s ", (int) strlen (hostname), " ");
488       print_heading ();
489     }
490
491   tty = ttyname (0);
492   if (tty == NULL)
493     return;
494   tty += 5;                     /* Remove "/dev/".  */
495   
496   utmp_entry = search_entries (read_utmp (filename), tty);
497   if (utmp_entry == NULL)
498     return;
499
500   printf ("%s!", hostname);
501   print_entry (utmp_entry);
502 }
503
504 static void
505 usage (status)
506      int status;
507 {
508   if (status != 0)
509     fprintf (stderr, "Try `%s --help' for more information.\n",
510              program_name);
511   else
512     {
513       printf ("Usage: %s [OPTION]... [ FILE | ARG1 ARG2 ]\n", program_name);
514       printf ("\
515 \n\
516   -H, --heading     print line of column headings\n\
517   -T, -w, --mesg    add user's message status as +, - or ?\n\
518   -i, -u, --idle    add user idle time as HOURS:MINUTES, . or old\n\
519   -m                only hostname and user associated with stdin\n\
520   -q, --count       all login names and number of users logged on\n\
521   -s                (ignored)\n\
522       --help        display this help and exit\n\
523       --message     same as -T\n\
524       --version     output version information and exit\n\
525       --writeable   same as -T\n\
526 \n\
527 If FILE not given, uses /etc/utmp.  /etc/wtmp as FILE is common.\n\
528 If ARG1 ARG2 given, -m presumed: `am i' or `mom likes' are usual.\n\
529 ");
530     }
531   exit (status);
532 }
533 #endif /* WHO */
534
535 #ifdef USERS
536 static void
537 usage (status)
538      int status;
539 {
540   if (status != 0)
541     fprintf (stderr, "Try `%s --help' for more information.\n",
542              program_name);
543   else
544     {
545       printf ("Usage: %s [OPTION]... [ FILE ]\n", program_name);
546       printf ("\
547 \n\
548       --help        display this help and exit\n\
549       --version     output version information and exit\n\
550 \n\
551 If FILE not given, uses /etc/utmp.  /etc/wtmp as FILE is common.\n\
552 ");
553     }
554   exit (status);
555 }
556 #endif /* USERS */
557
558 void
559 main (argc, argv)
560      int argc;
561      char **argv;
562 {
563   int optc, longind;
564 #ifdef WHO
565   int my_line_only = 0;
566 #endif /* WHO */
567
568   program_name = argv[0];
569
570 #ifdef WHO
571   while ((optc = getopt_long (argc, argv, "imqsuwHT", longopts, &longind))
572 #else
573   while ((optc = getopt_long (argc, argv, "", longopts, &longind))
574 #endif /* WHO */
575          != EOF)
576     {
577       switch (optc)
578         {
579         case 0:
580           break;
581
582 #ifdef WHO
583         case 'm':
584           my_line_only = 1;
585           break;
586
587         case 'q':
588           short_list = 1;
589           break;
590
591         case 's':
592           break;
593
594         case 'i':
595         case 'u':
596           include_idle = 1;
597           break;
598
599         case 'H':
600           include_heading = 1;
601           break;
602
603         case 'w':
604         case 'T':
605           include_mesg = 1;
606           break;
607 #endif /* WHO */
608
609         default:
610           error (0, 0, "too many arguments");
611           usage (1);
612         }
613     }
614
615   if (show_version)
616     {
617       printf ("%s - %s\n", COMMAND_NAME, version_string);
618       exit (0);
619     }
620
621   if (show_help)
622     usage (0);
623
624   switch (argc - optind)
625     {
626     case 0:                     /* who */
627 #ifdef WHO
628       if (my_line_only)
629         who_am_i (UTMP_FILE);
630       else
631 #endif /* WHO */
632         who (UTMP_FILE);
633       break;
634
635     case 1:                     /* who <utmp file> */
636 #ifdef WHO
637       if (my_line_only)
638         who_am_i (argv[optind]);
639       else
640 #endif /* WHO */
641         who (argv[optind]);
642       break;
643
644 #ifdef WHO
645     case 2:                     /* who <blurf> <glop> */
646       who_am_i (UTMP_FILE);
647       break;
648 #endif /* WHO */
649
650     default:                    /* lose */
651       usage (1);
652     }
653
654   exit (0);
655 }