8b122c85b11c8dd16404f0be0c7be201fda6aa02
[platform/upstream/coreutils.git] / src / who.c
1 /* GNU's who.
2    Copyright (C) 1992 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 #ifdef HAVE_CONFIG_H
36 #if defined (CONFIG_BROKETS)
37 /* We use <config.h> instead of "config.h" so that a compilation
38    using -I. -I$srcdir will use ./config.h rather than $srcdir/config.h
39    (which it would do because it found this file in $srcdir).  */
40 #include <config.h>
41 #else
42 #include "config.h"
43 #endif
44 #endif
45
46 #include <stdio.h>
47 #include <sys/types.h>
48 #include <utmp.h>
49 #include <time.h>
50 #include <getopt.h>
51 #ifndef _POSIX_SOURCE
52 #include <sys/param.h>
53 #endif
54
55 #include "system.h"
56 #include "version.h"
57
58 #ifndef UTMP_FILE
59 #ifdef _PATH_UTMP               /* 4.4BSD.  */
60 #define UTMP_FILE _PATH_UTMP
61 #else                           /* !_PATH_UTMP */
62 #define UTMP_FILE "/etc/utmp"
63 #endif                          /* !_PATH_UTMP */
64 #endif                          /* !UTMP_FILE */
65
66 #ifndef MAXHOSTNAMELEN
67 #define MAXHOSTNAMELEN 64
68 #endif
69
70 #define MESG_BIT 020            /* Group write bit. */
71
72
73 char *xmalloc ();
74 void error ();
75 char *ttyname ();
76
77 static int read_utmp ();
78 static char *idle_string ();
79 static struct utmp *search_entries ();
80 static void list_entries ();
81 static void print_entry ();
82 static void print_heading ();
83 static void scan_entries ();
84 static void usage ();
85 static void who ();
86 static void who_am_i ();
87
88 /* The name this program was run with. */
89 char *program_name;
90
91 /* If non-zero, display usage information and exit.  */
92 static int show_help;
93
94 /* If non-zero, print the version on standard output and exit.  */
95 static int show_version;
96
97 /* If nonzero, display only a list of usernames and count of
98    the users logged on.
99    Ignored for `who am i'. */
100 static int short_list;
101
102 /* If nonzero, display the hours:minutes since each user has touched
103    the keyboard, or "." if within the last minute, or "old" if
104    not within the last day. */
105 static int include_idle;
106
107 /* If nonzero, display a line at the top describing each field. */
108 static int include_heading;
109
110 /* If nonzero, display a `+' for each user if mesg y, a `-' if mesg n,
111    or a `?' if their tty cannot be statted. */
112 static int include_mesg;
113
114 static struct option const longopts[] =
115 {
116   {"count", no_argument, NULL, 'q'},
117   {"help", no_argument, &show_help, 1},
118   {"idle", no_argument, NULL, 'u'},
119   {"heading", no_argument, NULL, 'H'},
120   {"message", no_argument, NULL, 'T'},
121   {"mesg", no_argument, NULL, 'T'},
122   {"version", no_argument, &show_version, 1},
123   {"writable", no_argument, NULL, 'T'},
124   {NULL, 0, NULL, 0}
125 };
126
127 void
128 main (argc, argv)
129      int argc;
130      char **argv;
131 {
132   int optc, longind;
133   int my_line_only = 0;
134
135   program_name = argv[0];
136
137   while ((optc = getopt_long (argc, argv, "imqsuwHT", longopts, &longind))
138          != EOF)
139     {
140       switch (optc)
141         {
142         case 0:
143           break;
144
145         case 'm':
146           my_line_only = 1;
147           break;
148
149         case 'q':
150           short_list = 1;
151           break;
152
153         case 's':
154           break;
155
156         case 'i':
157         case 'u':
158           include_idle = 1;
159           break;
160
161         case 'H':
162           include_heading = 1;
163           break;
164
165         case 'w':
166         case 'T':
167           include_mesg = 1;
168           break;
169
170         default:
171           usage (1);
172         }
173     }
174
175   if (show_version)
176     {
177       printf ("%s\n", version_string);
178       exit (0);
179     }
180
181   if (show_help)
182     usage (0);
183
184   if (chdir ("/dev"))
185     error (1, errno, "cannot change directory to /dev");
186
187   switch (argc - optind)
188     {
189     case 0:                     /* who */
190       if (my_line_only)
191         who_am_i (UTMP_FILE);
192       else
193         who (UTMP_FILE);
194       break;
195
196     case 1:                     /* who <utmp file> */
197       if (my_line_only)
198         who_am_i (argv[optind]);
199       else
200         who (argv[optind]);
201       break;
202
203     case 2:                     /* who <blurf> <glop> */
204       who_am_i (UTMP_FILE);
205       break;
206
207     default:                    /* lose */
208       usage (1);
209     }
210
211   exit (0);
212 }
213
214 static struct utmp *utmp_contents;
215
216 /* Display a list of who is on the system, according to utmp file FILENAME. */
217
218 static void
219 who (filename)
220      char *filename;
221 {
222   int users;
223
224   users = read_utmp (filename);
225   if (short_list)
226     list_entries (users);
227   else
228     scan_entries (users);
229 }
230
231 /* Read the utmp file FILENAME into UTMP_CONTENTS and return the
232    number of entries it contains. */
233
234 static int
235 read_utmp (filename)
236      char *filename;
237 {
238   register int desc;
239   struct stat file_stats;
240
241   desc = open (filename, O_RDONLY, 0);
242   if (desc < 0)
243     error (1, errno, "%s", filename);
244
245   fstat (desc, &file_stats);
246   if (file_stats.st_size > 0)
247     utmp_contents = (struct utmp *) xmalloc ((unsigned) file_stats.st_size);
248   else
249     {
250       close (desc);
251       return 0;
252     }
253
254   /* Use < instead of != in case the utmp just grew.  */
255   if (read (desc, utmp_contents, file_stats.st_size) < file_stats.st_size)
256     error (1, errno, "%s", filename);
257
258   if (close (desc) != 0)
259     error (1, errno, "%s", filename);
260
261   return file_stats.st_size / sizeof (struct utmp);
262 }
263
264 /* Display a line of information about entry THIS. */
265
266 static void
267 print_entry (this)
268      struct utmp *this;
269 {
270   struct stat stats;
271   time_t last_change;
272   char mesg;
273   char line[sizeof (this->ut_line) + 1];
274
275   strncpy (line, this->ut_line, sizeof (this->ut_line));
276   line[sizeof (this->ut_line)] = 0;
277   if (stat (line, &stats) == 0)
278     {
279       mesg = (stats.st_mode & MESG_BIT) ? '+' : '-';
280       last_change = stats.st_atime;
281     }
282   else
283     {
284       mesg = '?';
285       last_change = 0;
286     }
287   
288   printf ("%-*.*s",
289           (int) sizeof (this->ut_name), (int) sizeof (this->ut_name),
290           this->ut_name);
291   if (include_mesg)
292     printf ("  %c  ", mesg);
293   printf (" %-*.*s",
294           (int) sizeof (this->ut_line), (int) sizeof (this->ut_line),
295           this->ut_line);
296   printf (" %-12.12s", ctime (&this->ut_time) + 4);
297   if (include_idle)
298     {
299       if (last_change)
300         printf (" %s", idle_string (last_change));
301       else
302         printf ("   .  ");
303     }
304 #ifdef HAVE_UT_HOST
305   if (this->ut_host[0])
306     printf (" (%-.*s)", (int) sizeof (this->ut_host), this->ut_host);
307 #endif
308
309   putchar ('\n');
310 }
311
312 /* Print the username of each valid entry and the number of valid entries
313    in `utmp_contents', which should have N elements. */
314
315 static void
316 list_entries (n)
317      int n;
318 {
319   register struct utmp *this = utmp_contents;
320   register int entries = 0;
321
322   while (n--)
323     {
324       if (this->ut_name[0]
325 #ifdef USER_PROCESS
326           && this->ut_type == USER_PROCESS
327 #endif
328          )
329         {
330           printf ("%s ", this->ut_name);
331           entries++;
332         }
333       this++;
334     }
335   printf ("\n# users=%u\n", entries);
336 }
337
338 static void
339 print_heading ()
340 {
341   struct utmp *ut;
342
343   printf ("%-*s ", (int) sizeof (ut->ut_name), "USER");
344   if (include_mesg)
345     printf ("MESG ");
346   printf ("%-*s ", (int) sizeof (ut->ut_line), "LINE");
347   printf ("LOGIN-TIME   ");
348   if (include_idle)
349     printf ("IDLE  ");
350   printf ("FROM\n");
351 }
352
353 /* Display `utmp_contents', which should have N entries. */
354
355 static void
356 scan_entries (n)
357      int n;
358 {
359   register struct utmp *this = utmp_contents;
360
361   if (include_heading)
362     print_heading ();
363
364   while (n--)
365     {
366       if (this->ut_name[0]
367 #ifdef USER_PROCESS
368           && this->ut_type == USER_PROCESS
369 #endif
370          )
371         print_entry (this);
372       this++;
373     }
374 }
375
376 /* Search `utmp_contents', which should have N entries, for
377    an entry with a `ut_line' field identical to LINE.
378    Return the first matching entry found, or NULL if there
379    is no matching entry. */
380
381 static struct utmp *
382 search_entries (n, line)
383      int n;
384      char *line;
385 {
386   register struct utmp *this = utmp_contents;
387
388   while (n--)
389     {
390       if (this->ut_name[0]
391 #ifdef USER_PROCESS
392           && this->ut_type == USER_PROCESS
393 #endif
394           && !strncmp (line, this->ut_line, sizeof (this->ut_line)))
395         return this;
396       this++;
397     }
398   return NULL;
399 }
400
401 /* Display the entry in utmp file FILENAME for this tty on standard input,
402    or nothing if there is no entry for it. */
403
404 static void
405 who_am_i (filename)
406      char *filename;
407 {
408   register struct utmp *utmp_entry;
409   char hostname[MAXHOSTNAMELEN + 1];
410   char *tty;
411
412   if (gethostname (hostname, MAXHOSTNAMELEN + 1))
413     *hostname = 0;
414
415   if (include_heading)
416     {
417       printf ("%*s ", (int) strlen (hostname), " ");
418       print_heading ();
419     }
420
421   tty = ttyname (0);
422   if (tty == NULL)
423     return;
424   tty += 5;                     /* Remove "/dev/".  */
425   
426   utmp_entry = search_entries (read_utmp (filename), tty);
427   if (utmp_entry == NULL)
428     return;
429
430   printf ("%s!", hostname);
431   print_entry (utmp_entry);
432 }
433
434 /* Return a string representing the time between WHEN and the time
435    that this function is first run. */
436
437 static char *
438 idle_string (when)
439      time_t when;
440 {
441   static time_t now = 0;
442   static char idle[10];
443   time_t seconds_idle;
444
445   if (now == 0)
446     time (&now);
447
448   seconds_idle = now - when;
449   if (seconds_idle < 60)        /* One minute. */
450     return "  .  ";
451   if (seconds_idle < (24 * 60 * 60)) /* One day. */
452     {
453       sprintf (idle, "%02d:%02d",
454                (int) (seconds_idle / (60 * 60)),
455                (int) ((seconds_idle % (60 * 60)) / 60));
456       return idle;
457     }
458   return " old ";
459 }
460
461 static void
462 usage (status)
463      int status;
464 {
465   fprintf (status == 0 ? stdout : stderr, "\
466 Usage: %s [OPTION]... [ FILE | ARG1 ARG2 ]\n\
467 ",
468            program_name);
469
470   if (status != 0)
471     fprintf (stderr, "Try `%s --help' for more information.\n",
472              program_name);
473   else
474
475     printf ("\
476 \n\
477   -H, --heading     print line of column headings\n\
478   -T, -w, --mesg    add user's message status as +, - or ?\n\
479   -i, -u, --idle    add user idle time as HOURS:MINUTES, . or old\n\
480   -m                only hostname and user associated with stdin\n\
481   -q, --count       all login names and number of users logged on\n\
482   -s                (ignored)\n\
483       --help        display this help and exit\n\
484       --message     same as -T\n\
485       --version     output version information and exit\n\
486       --writeable   same as -T\n\
487 \n\
488 If FILE not given, uses /etc/utmp.  /etc/wtmp as FILE is common.\n\
489 If ARG1 ARG2 given, -m presumed: `am i' or `mom likes' are usual.\n\
490 ");
491
492   exit (status);
493 }