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)
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 */
21 name [state] line time [idle] host
23 name, line, time: not -q
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). */
37 #include <sys/types.h>
41 #define STRUCT_UTMP struct utmpx
44 #define STRUCT_UTMP struct utmp
49 #ifdef HAVE_SYS_PARAM_H
50 #include <sys/param.h>
55 #include "safe-stat.h"
58 #if !defined (UTMP_FILE) && defined (_PATH_UTMP) /* 4.4BSD. */
59 #define UTMP_FILE _PATH_UTMP
62 #if defined (UTMPX_FILE) /* Solaris, SysVr4 */
64 #define UTMP_FILE UTMPX_FILE
68 #define UTMP_FILE "/etc/utmp"
71 #ifndef MAXHOSTNAMELEN
72 #define MAXHOSTNAMELEN 64
80 #define COMMAND_NAME "who"
83 #define COMMAND_NAME "users"
85 error You must define one of WHO and USERS.
93 /* The name this program was run with. */
96 /* If non-zero, display usage information and exit. */
99 /* If non-zero, print the version on standard output and exit. */
100 static int show_version;
103 /* If nonzero, display only a list of usernames and count of
105 Ignored for `who am i'. */
106 static int short_list;
108 /* If nonzero, display the hours:minutes since each user has touched
109 the keyboard, or "." if within the last minute, or "old" if
110 not within the last day. */
111 static int include_idle;
113 /* If nonzero, display a line at the top describing each field. */
114 static int include_heading;
116 /* If nonzero, display a `+' for each user if mesg y, a `-' if mesg n,
117 or a `?' if their tty cannot be statted. */
118 static int include_mesg;
121 static struct option const longopts[] =
124 {"count", no_argument, NULL, 'q'},
125 {"idle", no_argument, NULL, 'u'},
126 {"heading", no_argument, NULL, 'H'},
127 {"message", no_argument, NULL, 'T'},
128 {"mesg", no_argument, NULL, 'T'},
129 {"writable", no_argument, NULL, 'T'},
131 {"help", no_argument, &show_help, 1},
132 {"version", no_argument, &show_version, 1},
136 static STRUCT_UTMP *utmp_contents;
138 #if defined (WHO) || defined (USERS)
140 /* Copy UT->ut_name into storage obtained from malloc. Then remove any
141 trailing spaces from the copy, NUL terminate it, and return the copy. */
144 extract_trimmed_name (const STRUCT_UTMP *ut)
146 char *p, *trimmed_name;
148 trimmed_name = xmalloc (sizeof (ut->ut_name) + 1);
149 strncpy (trimmed_name, ut->ut_name, sizeof (ut->ut_name));
150 /* Append a trailing space character. Some systems pad names shorter than
151 the maximum with spaces, others pad with NULs. Remove any spaces. */
152 trimmed_name[sizeof (ut->ut_name)] = ' ';
153 p = strchr (trimmed_name, ' ');
159 #endif /* WHO || USERS */
163 /* Return a string representing the time between WHEN and the time
164 that this function is first run. */
170 static time_t now = 0;
171 static char idle[10];
177 seconds_idle = now - when;
178 if (seconds_idle < 60) /* One minute. */
180 if (seconds_idle < (24 * 60 * 60)) /* One day. */
182 sprintf (idle, "%02d:%02d",
183 (int) (seconds_idle / (60 * 60)),
184 (int) ((seconds_idle % (60 * 60)) / 60));
185 return (const char *) idle;
190 /* Display a line of information about entry THIS. */
200 #define DEV_DIR_WITH_TRAILING_SLASH "/dev/"
201 #define DEV_DIR_LEN (sizeof (DEV_DIR_WITH_TRAILING_SLASH) - 1)
203 char line[sizeof (this->ut_line) + DEV_DIR_LEN + 1];
205 /* Copy ut_line into LINE, prepending `/dev/' if ut_line is not
206 already an absolute pathname. Some system may put the full,
207 absolute pathname in ut_line. */
208 if (this->ut_line[0] == '/')
210 strncpy (line, this->ut_line, sizeof (this->ut_line));
211 line[sizeof (this->ut_line)] = '\0';
215 strcpy (line, DEV_DIR_WITH_TRAILING_SLASH);
216 strncpy (line + DEV_DIR_LEN, this->ut_line, sizeof (this->ut_line));
217 line[DEV_DIR_LEN + sizeof (this->ut_line)] = '\0';
220 if (safe_stat (line, &stats) == 0)
222 mesg = (stats.st_mode & S_IWGRP) ? '+' : '-';
223 last_change = stats.st_atime;
231 printf ("%-8.*s", (int) sizeof (this->ut_name), this->ut_name);
233 printf (" %c ", mesg);
234 printf (" %-8.*s", (int) sizeof (this->ut_line), this->ut_line);
237 printf (" %-12.12s", ctime (&this->ut_tv.tv_sec) + 4);
239 printf (" %-12.12s", ctime (&this->ut_time) + 4);
245 printf (" %s", idle_string (last_change));
250 if (this->ut_host[0])
251 printf (" (%-.*s)", (int) sizeof (this->ut_host), this->ut_host);
257 /* Print the username of each valid entry and the number of valid entries
258 in `utmp_contents', which should have N elements. */
264 register STRUCT_UTMP *this = utmp_contents;
272 && this->ut_type == USER_PROCESS
278 trimmed_name = extract_trimmed_name (this);
280 printf ("%s ", trimmed_name);
286 printf ("\n# users=%u\n", entries);
294 userid_compare (v_a, v_b)
298 char **a = (char **) v_a;
299 char **b = (char **) v_b;
300 return strcmp (*a, *b);
304 list_entries_users (n)
307 register STRUCT_UTMP *this = utmp_contents;
313 u = (char **) xmalloc (n * sizeof (u[0]));
318 && this->ut_type == USER_PROCESS
324 trimmed_name = extract_trimmed_name (this);
326 u[n_entries] = trimmed_name;
332 qsort (u, n_entries, sizeof (u[0]), userid_compare);
334 for (i=0; i<n_entries; i++)
337 fputs (u[i], stdout);
338 c = (i < n_entries-1 ? ' ' : '\n');
342 for (i=0; i<n_entries; i++)
354 printf ("%-8s ", "USER");
357 printf ("%-8s ", "LINE");
358 printf ("LOGIN-TIME ");
364 /* Display `utmp_contents', which should have N entries. */
370 register STRUCT_UTMP *this = utmp_contents;
379 && this->ut_type == USER_PROCESS
389 /* Read the utmp file FILENAME into UTMP_CONTENTS and return the
390 number of entries it contains. */
397 struct stat file_stats;
401 utmp = fopen (filename, "r");
403 error (1, errno, "%s", filename);
405 fstat (fileno (utmp), &file_stats);
406 size = file_stats.st_size;
408 utmp_contents = (STRUCT_UTMP *) xmalloc (size);
415 /* Use < instead of != in case the utmp just grew. */
416 n_read = fread (utmp_contents, 1, size, utmp);
417 if (ferror (utmp) || fclose (utmp) == EOF
419 error (1, errno, "%s", filename);
421 return size / sizeof (STRUCT_UTMP);
424 /* Display a list of who is on the system, according to utmp file FILENAME. */
432 users = read_utmp (filename);
435 list_entries_who (users);
437 scan_entries (users);
440 list_entries_users (users);
447 /* Search `utmp_contents', which should have N entries, for
448 an entry with a `ut_line' field identical to LINE.
449 Return the first matching entry found, or NULL if there
450 is no matching entry. */
453 search_entries (n, line)
457 register STRUCT_UTMP *this = utmp_contents;
463 && this->ut_type == USER_PROCESS
465 && !strncmp (line, this->ut_line, sizeof (this->ut_line)))
472 /* Display the entry in utmp file FILENAME for this tty on standard input,
473 or nothing if there is no entry for it. */
479 register STRUCT_UTMP *utmp_entry;
480 char hostname[MAXHOSTNAMELEN + 1];
483 if (gethostname (hostname, MAXHOSTNAMELEN + 1))
488 printf ("%*s ", (int) strlen (hostname), " ");
495 tty += 5; /* Remove "/dev/". */
497 utmp_entry = search_entries (read_utmp (filename), tty);
498 if (utmp_entry == NULL)
501 printf ("%s!", hostname);
502 print_entry (utmp_entry);
510 fprintf (stderr, "Try `%s --help' for more information.\n",
514 printf ("Usage: %s [OPTION]... [ FILE | ARG1 ARG2 ]\n", program_name);
517 -H, --heading print line of column headings\n\
518 -T, -w, --mesg add user's message status as +, - or ?\n\
519 -i, -u, --idle add user idle time as HOURS:MINUTES, . or old\n\
520 -m only hostname and user associated with stdin\n\
521 -q, --count all login names and number of users logged on\n\
523 --help display this help and exit\n\
524 --message same as -T\n\
525 --version output version information and exit\n\
526 --writeable same as -T\n\
528 If FILE not given, uses /etc/utmp. /etc/wtmp as FILE is common.\n\
529 If ARG1 ARG2 given, -m presumed: `am i' or `mom likes' are usual.\n\
542 fprintf (stderr, "Try `%s --help' for more information.\n",
546 printf ("Usage: %s [OPTION]... [ FILE ]\n", program_name);
549 --help display this help and exit\n\
550 --version output version information and exit\n\
552 If FILE not given, uses /etc/utmp. /etc/wtmp as FILE is common.\n\
566 int my_line_only = 0;
569 program_name = argv[0];
572 while ((optc = getopt_long (argc, argv, "imqsuwHT", longopts, &longind))
574 while ((optc = getopt_long (argc, argv, "", longopts, &longind))
611 error (0, 0, "too many arguments");
618 printf ("%s - %s\n", COMMAND_NAME, version_string);
625 switch (argc - optind)
630 who_am_i (UTMP_FILE);
636 case 1: /* who <utmp file> */
639 who_am_i (argv[optind]);
646 case 2: /* who <blurf> <glop> */
647 who_am_i (UTMP_FILE);