.
[coreutils.git] / src / who-users.c
blob4cc9206abe16a1634fddd132fce988e4aeeb8973
1 /* GNU's users/who.
2 Copyright (C) 92, 93, 94, 95, 1996 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 nonzero, display usage information and exit. */
96 static int show_help;
98 /* If nonzero, 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 (time_t when)
168 static time_t now = 0;
169 static char idle[10];
170 time_t seconds_idle;
172 if (now == 0)
173 time (&now);
175 seconds_idle = now - when;
176 if (seconds_idle < 60) /* One minute. */
177 return " . ";
178 if (seconds_idle < (24 * 60 * 60)) /* One day. */
180 sprintf (idle, "%02d:%02d",
181 (int) (seconds_idle / (60 * 60)),
182 (int) ((seconds_idle % (60 * 60)) / 60));
183 return (const char *) idle;
185 return _(" old ");
188 /* Display a line of information about entry THIS. */
190 static void
191 print_entry (STRUCT_UTMP *this)
193 struct stat stats;
194 time_t last_change;
195 char mesg;
197 #define DEV_DIR_WITH_TRAILING_SLASH "/dev/"
198 #define DEV_DIR_LEN (sizeof (DEV_DIR_WITH_TRAILING_SLASH) - 1)
200 char line[sizeof (this->ut_line) + DEV_DIR_LEN + 1];
202 /* Copy ut_line into LINE, prepending `/dev/' if ut_line is not
203 already an absolute pathname. Some system may put the full,
204 absolute pathname in ut_line. */
205 if (this->ut_line[0] == '/')
207 strncpy (line, this->ut_line, sizeof (this->ut_line));
208 line[sizeof (this->ut_line)] = '\0';
210 else
212 strcpy (line, DEV_DIR_WITH_TRAILING_SLASH);
213 strncpy (line + DEV_DIR_LEN, this->ut_line, sizeof (this->ut_line));
214 line[DEV_DIR_LEN + sizeof (this->ut_line)] = '\0';
217 if (stat (line, &stats) == 0)
219 mesg = (stats.st_mode & S_IWGRP) ? '+' : '-';
220 last_change = stats.st_atime;
222 else
224 mesg = '?';
225 last_change = 0;
228 printf ("%-8.*s", (int) sizeof (this->ut_name), this->ut_name);
229 if (include_mesg)
230 printf (" %c ", mesg);
231 printf (" %-8.*s", (int) sizeof (this->ut_line), this->ut_line);
233 #ifdef HAVE_UTMPX_H
234 printf (" %-12.12s", ctime (&this->ut_tv.tv_sec) + 4);
235 #else
236 printf (" %-12.12s", ctime (&this->ut_time) + 4);
237 #endif
239 if (include_idle)
241 if (last_change)
242 printf (" %s", idle_string (last_change));
243 else
244 printf (" . ");
246 #ifdef HAVE_UT_HOST
247 if (this->ut_host[0])
249 extern char *canon_host ();
250 char ut_host[sizeof (this->ut_host) + 1];
251 char *host = 0, *display = 0;
253 /* Copy the host name into UT_HOST, and ensure it's nul terminated. */
254 strncpy (ut_host, this->ut_host, (int) sizeof (this->ut_host));
255 ut_host[sizeof (this->ut_host)] = '\0';
257 /* Look for an X display. */
258 display = strrchr (ut_host, ':');
259 if (display)
260 *display++ = '\0';
262 if (*ut_host)
263 /* See if we can canonicalize it. */
264 host = canon_host (ut_host);
265 if (! host)
266 host = ut_host;
268 if (display)
269 printf (" (%s:%s)", host, display);
270 else
271 printf (" (%s)", host);
273 #endif
275 putchar ('\n');
278 /* Print the username of each valid entry and the number of valid entries
279 in `utmp_contents', which should have N elements. */
281 static void
282 list_entries_who (int n)
284 register STRUCT_UTMP *this = utmp_contents;
285 int entries;
287 entries = 0;
288 while (n--)
290 if (this->ut_name[0]
291 #ifdef USER_PROCESS
292 && this->ut_type == USER_PROCESS
293 #endif
296 char *trimmed_name;
298 trimmed_name = extract_trimmed_name (this);
300 printf ("%s ", trimmed_name);
301 free (trimmed_name);
302 entries++;
304 this++;
306 printf (_("\n# users=%u\n"), entries);
309 #endif /* WHO */
311 #ifdef USERS
313 static int
314 userid_compare (const void *v_a, const void *v_b)
316 char **a = (char **) v_a;
317 char **b = (char **) v_b;
318 return strcmp (*a, *b);
321 static void
322 list_entries_users (int n)
324 register STRUCT_UTMP *this = utmp_contents;
325 char **u;
326 int i;
327 int n_entries;
329 n_entries = 0;
330 u = (char **) xmalloc (n * sizeof (u[0]));
331 for (i=0; i<n; i++)
333 if (this->ut_name[0]
334 #ifdef USER_PROCESS
335 && this->ut_type == USER_PROCESS
336 #endif
339 char *trimmed_name;
341 trimmed_name = extract_trimmed_name (this);
343 u[n_entries] = trimmed_name;
344 ++n_entries;
346 this++;
349 qsort (u, n_entries, sizeof (u[0]), userid_compare);
351 for (i=0; i<n_entries; i++)
353 int c;
354 fputs (u[i], stdout);
355 c = (i < n_entries-1 ? ' ' : '\n');
356 putchar (c);
359 for (i=0; i<n_entries; i++)
360 free (u[i]);
361 free (u);
364 #endif /* USERS */
366 #ifdef WHO
368 static void
369 print_heading (void)
371 printf ("%-8s ", "USER");
372 if (include_mesg)
373 printf (_("MESG "));
374 printf ("%-8s ", _("LINE"));
375 printf (_("LOGIN-TIME "));
376 if (include_idle)
377 printf (_("IDLE "));
378 printf (_("FROM\n"));
381 /* Display `utmp_contents', which should have N entries. */
383 static void
384 scan_entries (int n)
386 register STRUCT_UTMP *this = utmp_contents;
388 if (include_heading)
389 print_heading ();
391 while (n--)
393 if (this->ut_name[0]
394 #ifdef USER_PROCESS
395 && this->ut_type == USER_PROCESS
396 #endif
398 print_entry (this);
399 this++;
403 #endif /* WHO */
405 /* Read the utmp file FILENAME into UTMP_CONTENTS and return the
406 number of entries it contains. */
408 static int
409 read_utmp (char *filename)
411 FILE *utmp;
412 struct stat file_stats;
413 int n_read;
414 size_t size;
416 utmp = fopen (filename, "r");
417 if (utmp == NULL)
418 error (1, errno, "%s", filename);
420 fstat (fileno (utmp), &file_stats);
421 size = file_stats.st_size;
422 if (size > 0)
423 utmp_contents = (STRUCT_UTMP *) xmalloc (size);
424 else
426 fclose (utmp);
427 return 0;
430 /* Use < instead of != in case the utmp just grew. */
431 n_read = fread (utmp_contents, 1, size, utmp);
432 if (ferror (utmp) || fclose (utmp) == EOF
433 || n_read < size)
434 error (1, errno, "%s", filename);
436 return size / sizeof (STRUCT_UTMP);
439 /* Display a list of who is on the system, according to utmp file FILENAME. */
441 static void
442 who (char *filename)
444 int users;
446 users = read_utmp (filename);
447 #ifdef WHO
448 if (short_list)
449 list_entries_who (users);
450 else
451 scan_entries (users);
452 #else
453 #ifdef USERS
454 list_entries_users (users);
455 #endif /* USERS */
456 #endif /* WHO */
459 #ifdef WHO
461 /* Search `utmp_contents', which should have N entries, for
462 an entry with a `ut_line' field identical to LINE.
463 Return the first matching entry found, or NULL if there
464 is no matching entry. */
466 static STRUCT_UTMP *
467 search_entries (int n, char *line)
469 register STRUCT_UTMP *this = utmp_contents;
471 while (n--)
473 if (this->ut_name[0]
474 #ifdef USER_PROCESS
475 && this->ut_type == USER_PROCESS
476 #endif
477 && !strncmp (line, this->ut_line, sizeof (this->ut_line)))
478 return this;
479 this++;
481 return NULL;
484 /* Display the entry in utmp file FILENAME for this tty on standard input,
485 or nothing if there is no entry for it. */
487 static void
488 who_am_i (char *filename)
490 register STRUCT_UTMP *utmp_entry;
491 char hostname[MAXHOSTNAMELEN + 1];
492 char *tty;
494 if (gethostname (hostname, MAXHOSTNAMELEN + 1))
495 *hostname = 0;
497 if (include_heading)
499 printf ("%*s ", (int) strlen (hostname), " ");
500 print_heading ();
503 tty = ttyname (0);
504 if (tty == NULL)
505 return;
506 tty += 5; /* Remove "/dev/". */
508 utmp_entry = search_entries (read_utmp (filename), tty);
509 if (utmp_entry == NULL)
510 return;
512 printf ("%s!", hostname);
513 print_entry (utmp_entry);
516 static void
517 usage (int status)
519 if (status != 0)
520 fprintf (stderr, _("Try `%s --help' for more information.\n"),
521 program_name);
522 else
524 printf (_("Usage: %s [OPTION]... [ FILE | ARG1 ARG2 ]\n"), program_name);
525 printf (_("\
527 -H, --heading print line of column headings\n\
528 -T, -w, --mesg add user's message status as +, - or ?\n\
529 -i, -u, --idle add user idle time as HOURS:MINUTES, . or old\n\
530 -m only hostname and user associated with stdin\n\
531 -q, --count all login names and number of users logged on\n\
532 -s (ignored)\n\
533 --help display this help and exit\n\
534 --message same as -T\n\
535 --version output version information and exit\n\
536 --writeable same as -T\n\
538 If FILE not given, uses /etc/utmp. /etc/wtmp as FILE is common.\n\
539 If ARG1 ARG2 given, -m presumed: `am i' or `mom likes' are usual.\n\
540 "));
542 exit (status);
544 #endif /* WHO */
546 #ifdef USERS
547 static void
548 usage (int status)
550 if (status != 0)
551 fprintf (stderr, _("Try `%s --help' for more information.\n"),
552 program_name);
553 else
555 printf (_("Usage: %s [OPTION]... [ FILE ]\n"), program_name);
556 printf (_("\
557 Output who is currently logged in according to FILE.\n\
558 If FILE not given, uses /etc/utmp. /etc/wtmp as FILE is common.\n\
560 --help display this help and exit\n\
561 --version output version information and exit\n"));
563 exit (status);
565 #endif /* USERS */
567 void
568 main (int argc, char **argv)
570 int optc, longind;
571 #ifdef WHO
572 int my_line_only = 0;
573 #endif /* WHO */
575 program_name = argv[0];
576 setlocale (LC_ALL, "");
577 bindtextdomain (PACKAGE, LOCALEDIR);
578 textdomain (PACKAGE);
580 #ifdef WHO
581 while ((optc = getopt_long (argc, argv, "imqsuwHT", longopts, &longind))
582 #else
583 while ((optc = getopt_long (argc, argv, "", longopts, &longind))
584 #endif /* WHO */
585 != EOF)
587 switch (optc)
589 case 0:
590 break;
592 #ifdef WHO
593 case 'm':
594 my_line_only = 1;
595 break;
597 case 'q':
598 short_list = 1;
599 break;
601 case 's':
602 break;
604 case 'i':
605 case 'u':
606 include_idle = 1;
607 break;
609 case 'H':
610 include_heading = 1;
611 break;
613 case 'w':
614 case 'T':
615 include_mesg = 1;
616 break;
617 #endif /* WHO */
619 default:
620 usage (1);
624 if (show_version)
626 printf ("%s - %s\n", COMMAND_NAME, version_string);
627 exit (0);
630 if (show_help)
631 usage (0);
633 switch (argc - optind)
635 case 0: /* who */
636 #ifdef WHO
637 if (my_line_only)
638 who_am_i (UTMP_FILE);
639 else
640 #endif /* WHO */
641 who (UTMP_FILE);
642 break;
644 case 1: /* who <utmp file> */
645 #ifdef WHO
646 if (my_line_only)
647 who_am_i (argv[optind]);
648 else
649 #endif /* WHO */
650 who (argv[optind]);
651 break;
653 #ifdef WHO
654 case 2: /* who <blurf> <glop> */
655 who_am_i (UTMP_FILE);
656 break;
657 #endif /* WHO */
659 default: /* lose */
660 error (0, 0, _("too many arguments"));
661 usage (1);
664 exit (0);