.
[coreutils.git] / src / who-users.c
blobf8b6659b6065422a2e48310080733e686e3fef50
1 /* GNU's uptime/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 Foundation,
16 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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 # define UT_TIME_MEMBER(UT_PTR) ((UT_PTR)->ut_tv.tv_sec)
43 #else
44 # include <utmp.h>
45 # define STRUCT_UTMP struct utmp
46 # define UT_TIME_MEMBER(UT_PTR) ((UT_PTR)->ut_time)
47 #endif
49 #include <time.h>
50 #include <getopt.h>
51 #ifdef HAVE_SYS_PARAM_H
52 #include <sys/param.h>
53 #endif
55 #include "system.h"
56 #include "error.h"
58 #if !defined (UTMP_FILE) && defined (_PATH_UTMP) /* 4.4BSD. */
59 #define UTMP_FILE _PATH_UTMP
60 #endif
62 #if defined (UTMPX_FILE) /* Solaris, SysVr4 */
63 #undef UTMP_FILE
64 #define UTMP_FILE UTMPX_FILE
65 #endif
67 #ifndef UTMP_FILE
68 #define UTMP_FILE "/etc/utmp"
69 #endif
71 #ifndef MAXHOSTNAMELEN
72 #define MAXHOSTNAMELEN 64
73 #endif
75 #ifndef S_IWGRP
76 #define S_IWGRP 020
77 #endif
79 #ifdef WHO
80 #define COMMAND_NAME "who"
81 #else
82 #ifdef USERS
83 #define COMMAND_NAME "users"
84 #else
85 #ifdef UPTIME
86 #define COMMAND_NAME "uptime"
87 #else
88 error You must define one of WHO, UPTIME or USERS.
89 #endif /* UPTIME */
90 #endif /* USERS */
91 #endif /* WHO */
93 int gethostname ();
94 char *ttyname ();
95 char *xmalloc ();
97 /* The name this program was run with. */
98 char *program_name;
100 /* If nonzero, display usage information and exit. */
101 static int show_help;
103 /* If nonzero, print the version on standard output and exit. */
104 static int show_version;
106 #ifdef WHO
107 /* If nonzero, display only a list of usernames and count of
108 the users logged on.
109 Ignored for `who am i'. */
110 static int short_list;
112 /* If nonzero, display the hours:minutes since each user has touched
113 the keyboard, or "." if within the last minute, or "old" if
114 not within the last day. */
115 static int include_idle;
117 /* If nonzero, display a line at the top describing each field. */
118 static int include_heading;
120 /* If nonzero, display a `+' for each user if mesg y, a `-' if mesg n,
121 or a `?' if their tty cannot be statted. */
122 static int include_mesg;
123 #endif /* WHO */
125 static struct option const longopts[] =
127 #ifdef WHO
128 {"count", no_argument, NULL, 'q'},
129 {"idle", no_argument, NULL, 'u'},
130 {"heading", no_argument, NULL, 'H'},
131 {"message", no_argument, NULL, 'T'},
132 {"mesg", no_argument, NULL, 'T'},
133 {"writable", no_argument, NULL, 'T'},
134 #endif /* WHO */
135 {"help", no_argument, &show_help, 1},
136 {"version", no_argument, &show_version, 1},
137 {NULL, 0, NULL, 0}
140 static STRUCT_UTMP *utmp_contents;
142 #ifdef UPTIME
143 static void
144 print_uptime (int n)
146 register STRUCT_UTMP *this = utmp_contents;
147 register int entries = 0;
148 time_t boot_time = 0;
149 time_t time_now;
150 time_t uptime;
151 int updays;
152 int uphours;
153 int upmins;
154 struct tm *tmn;
155 double avg[3];
156 int loads;
158 /* Loop through all the utmp entries we just read and count up the valid
159 ones, also in the process possibly gleaning boottime. */
160 while (n--)
162 if (this->ut_name[0]
163 #ifdef USER_PROCESS
164 && this->ut_type == USER_PROCESS
165 #endif
168 ++entries;
170 /* If BOOT_MSG is defined, we can get boottime from utmp. This avoids
171 possibly needing special privs to read /dev/kmem. */
172 #ifdef BOOT_MSG
173 if (!strcmp (this->ut_line, BOOT_MSG))
174 boot_time = UT_TIME_MEMBER (this);
175 #endif /* BOOT_MSG */
176 ++this;
178 if (boot_time == 0)
179 error (1, errno, _("couldn't get boot time"));
180 time_now = time (0);
181 uptime = time_now - boot_time;
182 updays = uptime / 86400;
183 uphours = (uptime - (updays * 86400)) / 3600;
184 upmins = (uptime - (updays * 86400) - (uphours * 3600)) / 60;
185 tmn = localtime (&time_now);
186 printf (_(" %2d:%02d%s up "), ((tmn->tm_hour % 12) == 0
187 ? 12 : tmn->tm_hour % 12),
188 tmn->tm_min, (tmn->tm_hour < 12 ? _("am") : _("pm")));
189 if (updays > 0)
190 printf ("%d %s,", updays, (updays == 1 ? _("days") : _("day")));
191 printf (" %2d:%02d, %d %s", uphours, upmins, entries,
192 (entries == 1) ? _("user") : _("users"));
194 #ifdef HAVE_GETLOADAVG
195 loads = getloadavg (avg, 3);
196 #else
197 loads = -1;
198 #endif
200 if (loads == -1)
201 putchar ('\n');
202 else
204 if (loads > 0)
205 printf (_(", load average: %.2f"), avg[0]);
206 if (loads > 1)
207 printf (", %.2f", avg[1]);
208 if (loads > 2)
209 printf (", %.2f", avg[2]);
210 if (loads > 0)
211 putchar ('\n');
214 #endif /* UPTIME */
218 #if defined (WHO) || defined (USERS)
220 /* Copy UT->ut_name into storage obtained from malloc. Then remove any
221 trailing spaces from the copy, NUL terminate it, and return the copy. */
223 static char *
224 extract_trimmed_name (const STRUCT_UTMP *ut)
226 char *p, *trimmed_name;
228 trimmed_name = xmalloc (sizeof (ut->ut_name) + 1);
229 strncpy (trimmed_name, ut->ut_name, sizeof (ut->ut_name));
230 /* Append a trailing space character. Some systems pad names shorter than
231 the maximum with spaces, others pad with NULs. Remove any spaces. */
232 trimmed_name[sizeof (ut->ut_name)] = ' ';
233 p = strchr (trimmed_name, ' ');
234 if (p != NULL)
235 *p = '\0';
236 return trimmed_name;
239 #endif /* WHO || USERS */
241 #if WHO
243 /* Return a string representing the time between WHEN and the time
244 that this function is first run. */
246 static const char *
247 idle_string (time_t when)
249 static time_t now = 0;
250 static char idle[10];
251 time_t seconds_idle;
253 if (now == 0)
254 time (&now);
256 seconds_idle = now - when;
257 if (seconds_idle < 60) /* One minute. */
258 return " . ";
259 if (seconds_idle < (24 * 60 * 60)) /* One day. */
261 sprintf (idle, "%02d:%02d",
262 (int) (seconds_idle / (60 * 60)),
263 (int) ((seconds_idle % (60 * 60)) / 60));
264 return (const char *) idle;
266 return _(" old ");
269 /* Display a line of information about entry THIS. */
271 static void
272 print_entry (STRUCT_UTMP *this)
274 struct stat stats;
275 time_t last_change;
276 char mesg;
278 #define DEV_DIR_WITH_TRAILING_SLASH "/dev/"
279 #define DEV_DIR_LEN (sizeof (DEV_DIR_WITH_TRAILING_SLASH) - 1)
281 char line[sizeof (this->ut_line) + DEV_DIR_LEN + 1];
283 /* Copy ut_line into LINE, prepending `/dev/' if ut_line is not
284 already an absolute pathname. Some system may put the full,
285 absolute pathname in ut_line. */
286 if (this->ut_line[0] == '/')
288 strncpy (line, this->ut_line, sizeof (this->ut_line));
289 line[sizeof (this->ut_line)] = '\0';
291 else
293 strcpy (line, DEV_DIR_WITH_TRAILING_SLASH);
294 strncpy (line + DEV_DIR_LEN, this->ut_line, sizeof (this->ut_line));
295 line[DEV_DIR_LEN + sizeof (this->ut_line)] = '\0';
298 if (stat (line, &stats) == 0)
300 mesg = (stats.st_mode & S_IWGRP) ? '+' : '-';
301 last_change = stats.st_atime;
303 else
305 mesg = '?';
306 last_change = 0;
309 printf ("%-8.*s", (int) sizeof (this->ut_name), this->ut_name);
310 if (include_mesg)
311 printf (" %c ", mesg);
312 printf (" %-8.*s", (int) sizeof (this->ut_line), this->ut_line);
313 printf (" %-12.12s", ctime (&UT_TIME_MEMBER (this)) + 4);
315 if (include_idle)
317 if (last_change)
318 printf (" %s", idle_string (last_change));
319 else
320 printf (" . ");
322 #ifdef HAVE_UT_HOST
323 if (this->ut_host[0])
325 extern char *canon_host ();
326 char ut_host[sizeof (this->ut_host) + 1];
327 char *host = 0, *display = 0;
329 /* Copy the host name into UT_HOST, and ensure it's nul terminated. */
330 strncpy (ut_host, this->ut_host, (int) sizeof (this->ut_host));
331 ut_host[sizeof (this->ut_host)] = '\0';
333 /* Look for an X display. */
334 display = strrchr (ut_host, ':');
335 if (display)
336 *display++ = '\0';
338 if (*ut_host)
339 /* See if we can canonicalize it. */
340 host = canon_host (ut_host);
341 if (! host)
342 host = ut_host;
344 if (display)
345 printf (" (%s:%s)", host, display);
346 else
347 printf (" (%s)", host);
349 #endif
351 putchar ('\n');
354 /* Print the username of each valid entry and the number of valid entries
355 in `utmp_contents', which should have N elements. */
357 static void
358 list_entries_who (int n)
360 register STRUCT_UTMP *this = utmp_contents;
361 int entries;
363 entries = 0;
364 while (n--)
366 if (this->ut_name[0]
367 #ifdef USER_PROCESS
368 && this->ut_type == USER_PROCESS
369 #endif
372 char *trimmed_name;
374 trimmed_name = extract_trimmed_name (this);
376 printf ("%s ", trimmed_name);
377 free (trimmed_name);
378 entries++;
380 this++;
382 printf (_("\n# users=%u\n"), entries);
385 #endif /* WHO */
387 #ifdef USERS
389 static int
390 userid_compare (const void *v_a, const void *v_b)
392 char **a = (char **) v_a;
393 char **b = (char **) v_b;
394 return strcmp (*a, *b);
397 static void
398 list_entries_users (int n)
400 register STRUCT_UTMP *this = utmp_contents;
401 char **u;
402 int i;
403 int n_entries;
405 n_entries = 0;
406 u = (char **) xmalloc (n * sizeof (u[0]));
407 for (i = 0; i < n; i++)
409 if (this->ut_name[0]
410 #ifdef USER_PROCESS
411 && this->ut_type == USER_PROCESS
412 #endif
415 char *trimmed_name;
417 trimmed_name = extract_trimmed_name (this);
419 u[n_entries] = trimmed_name;
420 ++n_entries;
422 this++;
425 qsort (u, n_entries, sizeof (u[0]), userid_compare);
427 for (i = 0; i < n_entries; i++)
429 int c;
430 fputs (u[i], stdout);
431 c = (i < n_entries-1 ? ' ' : '\n');
432 putchar (c);
435 for (i = 0; i < n_entries; i++)
436 free (u[i]);
437 free (u);
440 #endif /* USERS */
442 #ifdef WHO
444 static void
445 print_heading (void)
447 printf ("%-8s ", _("USER"));
448 if (include_mesg)
449 printf (_("MESG "));
450 printf ("%-8s ", _("LINE"));
451 printf (_("LOGIN-TIME "));
452 if (include_idle)
453 printf (_("IDLE "));
454 printf (_("FROM\n"));
457 /* Display `utmp_contents', which should have N entries. */
459 static void
460 scan_entries (int n)
462 register STRUCT_UTMP *this = utmp_contents;
464 if (include_heading)
465 print_heading ();
467 while (n--)
469 if (this->ut_name[0]
470 #ifdef USER_PROCESS
471 && this->ut_type == USER_PROCESS
472 #endif
474 print_entry (this);
475 this++;
479 #endif /* WHO */
481 /* Read the utmp file FILENAME into UTMP_CONTENTS and return the
482 number of entries it contains. */
484 static int
485 read_utmp (char *filename)
487 FILE *utmp;
488 struct stat file_stats;
489 size_t n_read;
490 size_t size;
492 utmp = fopen (filename, "r");
493 if (utmp == NULL)
494 error (1, errno, "%s", filename);
496 fstat (fileno (utmp), &file_stats);
497 size = file_stats.st_size;
498 if (size > 0)
499 utmp_contents = (STRUCT_UTMP *) xmalloc (size);
500 else
502 fclose (utmp);
503 return 0;
506 /* Use < instead of != in case the utmp just grew. */
507 n_read = fread (utmp_contents, 1, size, utmp);
508 if (ferror (utmp) || fclose (utmp) == EOF
509 || n_read < size)
510 error (1, errno, "%s", filename);
512 return size / sizeof (STRUCT_UTMP);
515 /* Display a list of who is on the system, according to utmp file FILENAME. */
517 static void
518 who (char *filename)
520 int users;
522 users = read_utmp (filename);
523 #ifdef WHO
524 if (short_list)
525 list_entries_who (users);
526 else
527 scan_entries (users);
528 #else
529 #ifdef USERS
530 list_entries_users (users);
531 #else
532 #ifdef UPTIME
533 print_uptime (users);
534 #endif /* UPTIME */
535 #endif /* USERS */
536 #endif /* WHO */
539 #ifdef WHO
541 /* Search `utmp_contents', which should have N entries, for
542 an entry with a `ut_line' field identical to LINE.
543 Return the first matching entry found, or NULL if there
544 is no matching entry. */
546 static STRUCT_UTMP *
547 search_entries (int n, char *line)
549 register STRUCT_UTMP *this = utmp_contents;
551 while (n--)
553 if (this->ut_name[0]
554 #ifdef USER_PROCESS
555 && this->ut_type == USER_PROCESS
556 #endif
557 && !strncmp (line, this->ut_line, sizeof (this->ut_line)))
558 return this;
559 this++;
561 return NULL;
564 /* Display the entry in utmp file FILENAME for this tty on standard input,
565 or nothing if there is no entry for it. */
567 static void
568 who_am_i (char *filename)
570 register STRUCT_UTMP *utmp_entry;
571 char hostname[MAXHOSTNAMELEN + 1];
572 char *tty;
574 if (gethostname (hostname, MAXHOSTNAMELEN + 1))
575 *hostname = 0;
577 if (include_heading)
579 printf ("%*s ", (int) strlen (hostname), " ");
580 print_heading ();
583 tty = ttyname (0);
584 if (tty == NULL)
585 return;
586 tty += 5; /* Remove "/dev/". */
588 utmp_entry = search_entries (read_utmp (filename), tty);
589 if (utmp_entry == NULL)
590 return;
592 printf ("%s!", hostname);
593 print_entry (utmp_entry);
596 static void
597 usage (int status)
599 if (status != 0)
600 fprintf (stderr, _("Try `%s --help' for more information.\n"),
601 program_name);
602 else
604 printf (_("Usage: %s [OPTION]... [ FILE | ARG1 ARG2 ]\n"), program_name);
605 printf (_("\
607 -H, --heading print line of column headings\n\
608 -T, -w, --mesg add user's message status as +, - or ?\n\
609 -i, -u, --idle add user idle time as HOURS:MINUTES, . or old\n\
610 -m only hostname and user associated with stdin\n\
611 -q, --count all login names and number of users logged on\n\
612 -s (ignored)\n\
613 --help display this help and exit\n\
614 --message same as -T\n\
615 --version output version information and exit\n\
616 --writeable same as -T\n\
618 If FILE not given, uses /etc/utmp. /etc/wtmp as FILE is common.\n\
619 If ARG1 ARG2 given, -m presumed: `am i' or `mom likes' are usual.\n\
620 "));
622 exit (status);
624 #endif /* WHO */
626 #if defined(USERS) || defined(UPTIME)
627 static void
628 usage (int status)
630 if (status != 0)
631 fprintf (stderr, _("Try `%s --help' for more information.\n"),
632 program_name);
633 else
635 printf (_("Usage: %s [OPTION]... [ FILE ]\n"), program_name);
636 printf (_("\
637 Output who is currently logged in according to FILE.\n\
638 If FILE not given, uses /etc/utmp. /etc/wtmp as FILE is common.\n\
640 --help display this help and exit\n\
641 --version output version information and exit\n"));
643 exit (status);
645 #endif /* USERS || UPTIME */
648 main (int argc, char **argv)
650 int optc, longind;
651 #ifdef WHO
652 int my_line_only = 0;
653 #endif /* WHO */
655 program_name = argv[0];
656 setlocale (LC_ALL, "");
657 bindtextdomain (PACKAGE, LOCALEDIR);
658 textdomain (PACKAGE);
660 #ifdef WHO
661 while ((optc = getopt_long (argc, argv, "imqsuwHT", longopts, &longind))
662 #else
663 while ((optc = getopt_long (argc, argv, "", longopts, &longind))
664 #endif /* WHO */
665 != EOF)
667 switch (optc)
669 case 0:
670 break;
672 #ifdef WHO
673 case 'm':
674 my_line_only = 1;
675 break;
677 case 'q':
678 short_list = 1;
679 break;
681 case 's':
682 break;
684 case 'i':
685 case 'u':
686 include_idle = 1;
687 break;
689 case 'H':
690 include_heading = 1;
691 break;
693 case 'w':
694 case 'T':
695 include_mesg = 1;
696 break;
697 #endif /* WHO */
699 default:
700 usage (1);
704 if (show_version)
706 printf ("%s - %s\n", COMMAND_NAME, PACKAGE_VERSION);
707 exit (0);
710 if (show_help)
711 usage (0);
713 switch (argc - optind)
715 case 0: /* who */
716 #ifdef WHO
717 if (my_line_only)
718 who_am_i (UTMP_FILE);
719 else
720 #endif /* WHO */
721 who (UTMP_FILE);
722 break;
724 case 1: /* who <utmp file> */
725 #ifdef WHO
726 if (my_line_only)
727 who_am_i (argv[optind]);
728 else
729 #endif /* WHO */
730 who (argv[optind]);
731 break;
733 #ifdef WHO
734 case 2: /* who <blurf> <glop> */
735 who_am_i (UTMP_FILE);
736 break;
737 #endif /* WHO */
739 default: /* lose */
740 error (0, 0, _("too many arguments"));
741 usage (1);
744 exit (0);