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