Rename lots of `this' locals.
[platform/upstream/coreutils.git] / src / who.c
1 /* GNU's who.
2    Copyright (C) 92, 93, 94, 95, 96, 1997 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 */
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
27 #include <config.h>
28 #include <getopt.h>
29
30 #include "system.h"
31 #include "error.h"
32 #include "readutmp.h"
33
34 #ifndef MAXHOSTNAMELEN
35 # define MAXHOSTNAMELEN 64
36 #endif
37
38 #ifndef S_IWGRP
39 # define S_IWGRP 020
40 #endif
41
42 int gethostname ();
43 char *ttyname ();
44 char *xmalloc ();
45
46 /* The name this program was run with. */
47 char *program_name;
48
49 /* If nonzero, display usage information and exit.  */
50 static int show_help;
51
52 /* If nonzero, print the version on standard output and exit.  */
53 static int show_version;
54
55 /* If nonzero, display only a list of usernames and count of
56    the users logged on.
57    Ignored for `who am i'. */
58 static int short_list;
59
60 /* If nonzero, display the hours:minutes since each user has touched
61    the keyboard, or "." if within the last minute, or "old" if
62    not within the last day. */
63 static int include_idle;
64
65 /* If nonzero, display a line at the top describing each field. */
66 static int include_heading;
67
68 /* If nonzero, display a `+' for each user if mesg y, a `-' if mesg n,
69    or a `?' if their tty cannot be statted. */
70 static int include_mesg;
71
72 static struct option const longopts[] =
73 {
74   {"count", no_argument, NULL, 'q'},
75   {"idle", no_argument, NULL, 'u'},
76   {"heading", no_argument, NULL, 'H'},
77   {"message", no_argument, NULL, 'T'},
78   {"mesg", no_argument, NULL, 'T'},
79   {"writable", no_argument, NULL, 'T'},
80   {"help", no_argument, &show_help, 1},
81   {"version", no_argument, &show_version, 1},
82   {NULL, 0, NULL, 0}
83 };
84
85 /* Return a string representing the time between WHEN and the time
86    that this function is first run. */
87
88 static const char *
89 idle_string (time_t when)
90 {
91   static time_t now = 0;
92   static char idle_hhmm[10];
93   time_t seconds_idle;
94
95   if (now == 0)
96     time (&now);
97
98   seconds_idle = now - when;
99   if (seconds_idle < 60)        /* One minute. */
100     return "  .  ";
101   if (seconds_idle < (24 * 60 * 60)) /* One day. */
102     {
103       sprintf (idle_hhmm, "%02d:%02d",
104                (int) (seconds_idle / (60 * 60)),
105                (int) ((seconds_idle % (60 * 60)) / 60));
106       return (const char *) idle_hhmm;
107     }
108   return _(" old ");
109 }
110
111 /* Display a line of information about UTMP_ENT. */
112
113 static void
114 print_entry (const STRUCT_UTMP *utmp_ent)
115 {
116   struct stat stats;
117   time_t last_change;
118   char mesg;
119
120 #define DEV_DIR_WITH_TRAILING_SLASH "/dev/"
121 #define DEV_DIR_LEN (sizeof (DEV_DIR_WITH_TRAILING_SLASH) - 1)
122
123   char line[sizeof (utmp_ent->ut_line) + DEV_DIR_LEN + 1];
124   time_t tm;
125
126   /* Copy ut_line into LINE, prepending `/dev/' if ut_line is not
127      already an absolute pathname.  Some system may put the full,
128      absolute pathname in ut_line.  */
129   if (utmp_ent->ut_line[0] == '/')
130     {
131       strncpy (line, utmp_ent->ut_line, sizeof (utmp_ent->ut_line));
132       line[sizeof (utmp_ent->ut_line)] = '\0';
133     }
134   else
135     {
136       strcpy (line, DEV_DIR_WITH_TRAILING_SLASH);
137       strncpy (line + DEV_DIR_LEN, utmp_ent->ut_line, sizeof (utmp_ent->ut_line));
138       line[DEV_DIR_LEN + sizeof (utmp_ent->ut_line)] = '\0';
139     }
140
141   if (stat (line, &stats) == 0)
142     {
143       mesg = (stats.st_mode & S_IWGRP) ? '+' : '-';
144       last_change = stats.st_atime;
145     }
146   else
147     {
148       mesg = '?';
149       last_change = 0;
150     }
151
152   printf ("%-8.*s", (int) sizeof (utmp_ent->ut_name), utmp_ent->ut_name);
153   if (include_mesg)
154     printf ("  %c  ", mesg);
155   printf (" %-8.*s", (int) sizeof (utmp_ent->ut_line), utmp_ent->ut_line);
156
157   /* Don't take the address of UT_TIME_MEMBER directly.
158      Ulrich Drepper wrote:
159      ``... GNU libc (and perhaps other libcs as well) have extended
160      utmp file formats which do not use a simple time_t ut_time field.
161      In glibc, ut_time is a macro which selects for backward compatibility
162      the tv_sec member of a struct timeval value.''  */
163   tm = UT_TIME_MEMBER (utmp_ent);
164   printf (" %-12.12s", ctime (&tm) + 4);
165
166   if (include_idle)
167     {
168       if (last_change)
169         printf (" %s", idle_string (last_change));
170       else
171         printf ("   .  ");
172     }
173 #ifdef HAVE_UT_HOST
174   if (utmp_ent->ut_host[0])
175     {
176       extern char *canon_host ();
177       char ut_host[sizeof (utmp_ent->ut_host) + 1];
178       char *host = 0, *display = 0;
179
180       /* Copy the host name into UT_HOST, and ensure it's nul terminated. */
181       strncpy (ut_host, utmp_ent->ut_host, (int) sizeof (utmp_ent->ut_host));
182       ut_host[sizeof (utmp_ent->ut_host)] = '\0';
183
184       /* Look for an X display.  */
185       display = strrchr (ut_host, ':');
186       if (display)
187         *display++ = '\0';
188
189       if (*ut_host)
190         /* See if we can canonicalize it.  */
191         host = canon_host (ut_host);
192       if (! host)
193         host = ut_host;
194
195       if (display)
196         printf (" (%s:%s)", host, display);
197       else
198         printf (" (%s)", host);
199     }
200 #endif
201
202   putchar ('\n');
203 }
204
205 /* Print the username of each valid entry and the number of valid entries
206    in UTMP_BUF, which should have N elements. */
207
208 static void
209 list_entries_who (int n, const STRUCT_UTMP *utmp_buf)
210 {
211   int entries;
212
213   entries = 0;
214   while (n--)
215     {
216       if (utmp_buf->ut_name[0]
217 #ifdef USER_PROCESS
218           && utmp_buf->ut_type == USER_PROCESS
219 #endif
220          )
221         {
222           char *trimmed_name;
223
224           trimmed_name = extract_trimmed_name (utmp_buf);
225
226           printf ("%s ", trimmed_name);
227           free (trimmed_name);
228           entries++;
229         }
230       utmp_buf++;
231     }
232   printf (_("\n# users=%u\n"), entries);
233 }
234
235 static void
236 print_heading (void)
237 {
238   printf ("%-8s ", _("USER"));
239   if (include_mesg)
240     printf (_("MESG "));
241   printf ("%-8s ", _("LINE"));
242   printf (_("LOGIN-TIME   "));
243   if (include_idle)
244     printf (_("IDLE  "));
245   printf (_("FROM\n"));
246 }
247
248 /* Display UTMP_BUF, which should have N entries. */
249
250 static void
251 scan_entries (int n, const STRUCT_UTMP *utmp_buf)
252 {
253   if (include_heading)
254     print_heading ();
255
256   while (n--)
257     {
258       if (utmp_buf->ut_name[0]
259 #ifdef USER_PROCESS
260           && utmp_buf->ut_type == USER_PROCESS
261 #endif
262          )
263         print_entry (utmp_buf);
264       utmp_buf++;
265     }
266 }
267
268 /* Display a list of who is on the system, according to utmp file FILENAME. */
269
270 static void
271 who (const char *filename)
272 {
273   int n_users;
274   STRUCT_UTMP *utmp_buf;
275   int fail = read_utmp (filename, &n_users, &utmp_buf);
276
277   if (fail)
278     error (1, errno, "%s", filename);
279
280   if (short_list)
281     list_entries_who (n_users, utmp_buf);
282   else
283     scan_entries (n_users, utmp_buf);
284 }
285
286 /* Search UTMP_CONTENTS, which should have N entries, for
287    an entry with a `ut_line' field identical to LINE.
288    Return the first matching entry found, or NULL if there
289    is no matching entry. */
290
291 static const STRUCT_UTMP *
292 search_entries (int n, const STRUCT_UTMP *utmp_buf, const char *line)
293 {
294   while (n--)
295     {
296       if (utmp_buf->ut_name[0]
297 #ifdef USER_PROCESS
298           && utmp_buf->ut_type == USER_PROCESS
299 #endif
300           && !strncmp (line, utmp_buf->ut_line, sizeof (utmp_buf->ut_line)))
301         return utmp_buf;
302       utmp_buf++;
303     }
304   return NULL;
305 }
306
307 /* Display the entry in utmp file FILENAME for this tty on standard input,
308    or nothing if there is no entry for it. */
309
310 static void
311 who_am_i (const char *filename)
312 {
313   const STRUCT_UTMP *utmp_entry;
314   STRUCT_UTMP *utmp_buf;
315   char hostname[MAXHOSTNAMELEN + 1];
316   char *tty;
317   int fail;
318   int n_users;
319
320   if (gethostname (hostname, MAXHOSTNAMELEN + 1))
321     *hostname = 0;
322
323   if (include_heading)
324     {
325       printf ("%*s ", (int) strlen (hostname), " ");
326       print_heading ();
327     }
328
329   tty = ttyname (0);
330   if (tty == NULL)
331     return;
332   tty += 5;                     /* Remove "/dev/".  */
333
334   fail = read_utmp (filename, &n_users, &utmp_buf);
335
336   if (fail)
337     error (1, errno, "%s", filename);
338
339   utmp_entry = search_entries (n_users, utmp_buf, tty);
340   if (utmp_entry == NULL)
341     return;
342
343   printf ("%s!", hostname);
344   print_entry (utmp_entry);
345 }
346
347 static void
348 usage (int status)
349 {
350   if (status != 0)
351     fprintf (stderr, _("Try `%s --help' for more information.\n"),
352              program_name);
353   else
354     {
355       printf (_("Usage: %s [OPTION]... [ FILE | ARG1 ARG2 ]\n"), program_name);
356       printf (_("\
357 \n\
358   -H, --heading     print line of column headings\n\
359   -i, -u, --idle    add user idle time as HOURS:MINUTES, . or old\n\
360   -m                only hostname and user associated with stdin\n\
361   -q, --count       all login names and number of users logged on\n\
362   -s                (ignored)\n\
363   -T, -w, --mesg    add user's message status as +, - or ?\n\
364       --message     same as -T\n\
365       --writable    same as -T\n\
366       --help        display this help and exit\n\
367       --version     output version information and exit\n\
368 \n\
369 If FILE is not specified, use %s.  %s as FILE is common.\n\
370 If ARG1 ARG2 given, -m presumed: `am i' or `mom likes' are usual.\n\
371 "), UTMP_FILE, WTMP_FILE);
372       puts (_("\nReport bugs to <sh-utils-bugs@gnu.ai.mit.edu>."));
373     }
374   exit (status);
375 }
376
377 int
378 main (int argc, char **argv)
379 {
380   int optc, longind;
381   int my_line_only = 0;
382
383   program_name = argv[0];
384   setlocale (LC_ALL, "");
385   bindtextdomain (PACKAGE, LOCALEDIR);
386   textdomain (PACKAGE);
387
388   while ((optc = getopt_long (argc, argv, "imqsuwHT", longopts, &longind)) != -1)
389     {
390       switch (optc)
391         {
392         case 0:
393           break;
394
395         case 'm':
396           my_line_only = 1;
397           break;
398
399         case 'q':
400           short_list = 1;
401           break;
402
403         case 's':
404           break;
405
406         case 'i':
407         case 'u':
408           include_idle = 1;
409           break;
410
411         case 'H':
412           include_heading = 1;
413           break;
414
415         case 'w':
416         case 'T':
417           include_mesg = 1;
418           break;
419
420         default:
421           usage (1);
422         }
423     }
424
425   if (show_version)
426     {
427       printf ("who (%s) %s\n", GNU_PACKAGE, VERSION);
428       exit (0);
429     }
430
431   if (show_help)
432     usage (0);
433
434   switch (argc - optind)
435     {
436     case 0:                     /* who */
437       if (my_line_only)
438         who_am_i (UTMP_FILE);
439       else
440         who (UTMP_FILE);
441       break;
442
443     case 1:                     /* who <utmp file> */
444       if (my_line_only)
445         who_am_i (argv[optind]);
446       else
447         who (argv[optind]);
448       break;
449
450     case 2:                     /* who <blurf> <glop> */
451       who_am_i (UTMP_FILE);
452       break;
453
454     default:                    /* lose */
455       error (0, 0, _("too many arguments"));
456       usage (1);
457     }
458
459   exit (0);
460 }