.
[coreutils.git] / src / who.c
blobd10d014ea67c6e3bc74950f73e15bae08eaa3850
1 /* GNU's users/who.
2 Copyright (C) 92, 93, 94, 1995 Free Software Foundation, Inc.
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.
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.
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. */
18 /* Written by jla; revised by djm */
20 /* Output format:
21 name [state] line time [idle] host
22 state: -T
23 name, line, time: not -q
24 idle: -u
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). */
35 #include <config.h>
36 #include <stdio.h>
37 #include <sys/types.h>
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
47 #include <time.h>
48 #include <getopt.h>
49 #ifdef HAVE_SYS_PARAM_H
50 #include <sys/param.h>
51 #endif
53 #include "system.h"
54 #include "version.h"
55 #include "error.h"
57 #if !defined (UTMP_FILE) && defined (_PATH_UTMP) /* 4.4BSD. */
58 #define UTMP_FILE _PATH_UTMP
59 #endif
61 #if defined (UTMPX_FILE) /* Solaris, SysVr4 */
62 #undef UTMP_FILE
63 #define UTMP_FILE UTMPX_FILE
64 #endif
66 #ifndef UTMP_FILE
67 #define UTMP_FILE "/etc/utmp"
68 #endif
70 #ifndef MAXHOSTNAMELEN
71 #define MAXHOSTNAMELEN 64
72 #endif
74 #ifndef S_IWGRP
75 #define S_IWGRP 020
76 #endif
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 */
88 int gethostname ();
89 char *ttyname ();
90 char *xmalloc ();
92 /* The name this program was run with. */
93 char *program_name;
95 /* If non-zero, display usage information and exit. */
96 static int show_help;
98 /* If non-zero, print the version on standard output and exit. */
99 static int show_version;
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;
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;
112 /* If nonzero, display a line at the top describing each field. */
113 static int include_heading;
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 */
120 static struct option const longopts[] =
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}
135 static STRUCT_UTMP *utmp_contents;
137 #if defined (WHO) || defined (USERS)
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. */
142 static char *
143 extract_trimmed_name (const STRUCT_UTMP *ut)
145 char *p, *trimmed_name;
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;
158 #endif /* WHO || USERS */
160 #if WHO
162 /* Return a string representing the time between WHEN and the time
163 that this function is first run. */
165 static const char *
166 idle_string (when)
167 time_t when;
169 static time_t now = 0;
170 static char idle[10];
171 time_t seconds_idle;
173 if (now == 0)
174 time (&now);
176 seconds_idle = now - when;
177 if (seconds_idle < 60) /* One minute. */
178 return " . ";
179 if (seconds_idle < (24 * 60 * 60)) /* One day. */
181 sprintf (idle, "%02d:%02d",
182 (int) (seconds_idle / (60 * 60)),
183 (int) ((seconds_idle % (60 * 60)) / 60));
184 return (const char *) idle;
186 return " old ";
189 /* Display a line of information about entry THIS. */
191 static void
192 print_entry (this)
193 STRUCT_UTMP *this;
195 struct stat stats;
196 time_t last_change;
197 char mesg;
199 #define DEV_DIR_WITH_TRAILING_SLASH "/dev/"
200 #define DEV_DIR_LEN (sizeof (DEV_DIR_WITH_TRAILING_SLASH) - 1)
202 char line[sizeof (this->ut_line) + DEV_DIR_LEN + 1];
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] == '/')
209 strncpy (line, this->ut_line, sizeof (this->ut_line));
210 line[sizeof (this->ut_line)] = '\0';
212 else
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';
219 if (stat (line, &stats) == 0)
221 mesg = (stats.st_mode & S_IWGRP) ? '+' : '-';
222 last_change = stats.st_atime;
224 else
226 mesg = '?';
227 last_change = 0;
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);
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
241 if (include_idle)
243 if (last_change)
244 printf (" %s", idle_string (last_change));
245 else
246 printf (" . ");
248 #ifdef HAVE_UT_HOST
249 if (this->ut_host[0])
250 printf (" (%-.*s)", (int) sizeof (this->ut_host), this->ut_host);
251 #endif
253 putchar ('\n');
256 /* Print the username of each valid entry and the number of valid entries
257 in `utmp_contents', which should have N elements. */
259 static void
260 list_entries_who (n)
261 int n;
263 register STRUCT_UTMP *this = utmp_contents;
264 int entries;
266 entries = 0;
267 while (n--)
269 if (this->ut_name[0]
270 #ifdef USER_PROCESS
271 && this->ut_type == USER_PROCESS
272 #endif
275 char *trimmed_name;
277 trimmed_name = extract_trimmed_name (this);
279 printf ("%s ", trimmed_name);
280 free (trimmed_name);
281 entries++;
283 this++;
285 printf ("\n# users=%u\n", entries);
288 #endif /* WHO */
290 #ifdef USERS
292 static int
293 userid_compare (v_a, v_b)
294 const void *v_a;
295 const void *v_b;
297 char **a = (char **) v_a;
298 char **b = (char **) v_b;
299 return strcmp (*a, *b);
302 static void
303 list_entries_users (n)
304 int n;
306 register STRUCT_UTMP *this = utmp_contents;
307 char **u;
308 int i;
309 int n_entries;
311 n_entries = 0;
312 u = (char **) xmalloc (n * sizeof (u[0]));
313 for (i=0; i<n; i++)
315 if (this->ut_name[0]
316 #ifdef USER_PROCESS
317 && this->ut_type == USER_PROCESS
318 #endif
321 char *trimmed_name;
323 trimmed_name = extract_trimmed_name (this);
325 u[n_entries] = trimmed_name;
326 ++n_entries;
328 this++;
331 qsort (u, n_entries, sizeof (u[0]), userid_compare);
333 for (i=0; i<n_entries; i++)
335 int c;
336 fputs (u[i], stdout);
337 c = (i < n_entries-1 ? ' ' : '\n');
338 putchar (c);
341 for (i=0; i<n_entries; i++)
342 free (u[i]);
343 free (u);
346 #endif /* USERS */
348 #ifdef WHO
350 static void
351 print_heading ()
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");
363 /* Display `utmp_contents', which should have N entries. */
365 static void
366 scan_entries (n)
367 int n;
369 register STRUCT_UTMP *this = utmp_contents;
371 if (include_heading)
372 print_heading ();
374 while (n--)
376 if (this->ut_name[0]
377 #ifdef USER_PROCESS
378 && this->ut_type == USER_PROCESS
379 #endif
381 print_entry (this);
382 this++;
386 #endif /* WHO */
388 /* Read the utmp file FILENAME into UTMP_CONTENTS and return the
389 number of entries it contains. */
391 static int
392 read_utmp (filename)
393 char *filename;
395 FILE *utmp;
396 struct stat file_stats;
397 int n_read;
398 size_t size;
400 utmp = fopen (filename, "r");
401 if (utmp == NULL)
402 error (1, errno, "%s", filename);
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
410 fclose (utmp);
411 return 0;
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);
420 return size / sizeof (STRUCT_UTMP);
423 /* Display a list of who is on the system, according to utmp file FILENAME. */
425 static void
426 who (filename)
427 char *filename;
429 int users;
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 */
444 #ifdef WHO
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. */
451 static STRUCT_UTMP *
452 search_entries (n, line)
453 int n;
454 char *line;
456 register STRUCT_UTMP *this = utmp_contents;
458 while (n--)
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++;
468 return NULL;
471 /* Display the entry in utmp file FILENAME for this tty on standard input,
472 or nothing if there is no entry for it. */
474 static void
475 who_am_i (filename)
476 char *filename;
478 register STRUCT_UTMP *utmp_entry;
479 char hostname[MAXHOSTNAMELEN + 1];
480 char *tty;
482 if (gethostname (hostname, MAXHOSTNAMELEN + 1))
483 *hostname = 0;
485 if (include_heading)
487 printf ("%*s ", (int) strlen (hostname), " ");
488 print_heading ();
491 tty = ttyname (0);
492 if (tty == NULL)
493 return;
494 tty += 5; /* Remove "/dev/". */
496 utmp_entry = search_entries (read_utmp (filename), tty);
497 if (utmp_entry == NULL)
498 return;
500 printf ("%s!", hostname);
501 print_entry (utmp_entry);
504 static void
505 usage (status)
506 int status;
508 if (status != 0)
509 fprintf (stderr, "Try `%s --help' for more information.\n",
510 program_name);
511 else
513 printf ("Usage: %s [OPTION]... [ FILE | ARG1 ARG2 ]\n", program_name);
514 printf ("\
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\
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\
531 exit (status);
533 #endif /* WHO */
535 #ifdef USERS
536 static void
537 usage (status)
538 int status;
540 if (status != 0)
541 fprintf (stderr, "Try `%s --help' for more information.\n",
542 program_name);
543 else
545 printf ("Usage: %s [OPTION]... [ FILE ]\n", program_name);
546 printf ("\
547 Output who is currently logged in according to FILE.\n\
548 If FILE not given, uses /etc/utmp. /etc/wtmp as FILE is common.\n\
550 --help display this help and exit\n\
551 --version output version information and exit\n");
553 exit (status);
555 #endif /* USERS */
557 void
558 main (argc, argv)
559 int argc;
560 char **argv;
562 int optc, longind;
563 #ifdef WHO
564 int my_line_only = 0;
565 #endif /* WHO */
567 program_name = argv[0];
569 #ifdef WHO
570 while ((optc = getopt_long (argc, argv, "imqsuwHT", longopts, &longind))
571 #else
572 while ((optc = getopt_long (argc, argv, "", longopts, &longind))
573 #endif /* WHO */
574 != EOF)
576 switch (optc)
578 case 0:
579 break;
581 #ifdef WHO
582 case 'm':
583 my_line_only = 1;
584 break;
586 case 'q':
587 short_list = 1;
588 break;
590 case 's':
591 break;
593 case 'i':
594 case 'u':
595 include_idle = 1;
596 break;
598 case 'H':
599 include_heading = 1;
600 break;
602 case 'w':
603 case 'T':
604 include_mesg = 1;
605 break;
606 #endif /* WHO */
608 default:
609 error (0, 0, "too many arguments");
610 usage (1);
614 if (show_version)
616 printf ("%s - %s\n", COMMAND_NAME, version_string);
617 exit (0);
620 if (show_help)
621 usage (0);
623 switch (argc - optind)
625 case 0: /* who */
626 #ifdef WHO
627 if (my_line_only)
628 who_am_i (UTMP_FILE);
629 else
630 #endif /* WHO */
631 who (UTMP_FILE);
632 break;
634 case 1: /* who <utmp file> */
635 #ifdef WHO
636 if (my_line_only)
637 who_am_i (argv[optind]);
638 else
639 #endif /* WHO */
640 who (argv[optind]);
641 break;
643 #ifdef WHO
644 case 2: /* who <blurf> <glop> */
645 who_am_i (UTMP_FILE);
646 break;
647 #endif /* WHO */
649 default: /* lose */
650 usage (1);
653 exit (0);