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>
57 #if !defined (UTMP_FILE) && defined (_PATH_UTMP) /* 4.4BSD. */
58 #define UTMP_FILE _PATH_UTMP
61 #if defined (UTMPX_FILE) /* Solaris, SysVr4 */
63 #define UTMP_FILE UTMPX_FILE
67 #define UTMP_FILE "/etc/utmp"
70 #ifndef MAXHOSTNAMELEN
71 #define MAXHOSTNAMELEN 64
79 #define COMMAND_NAME "who"
82 #define COMMAND_NAME "users"
84 error You must define one of WHO
and USERS
.
92 /* The name this program was run with. */
95 /* If non-zero, display usage information and exit. */
98 /* If non-zero, print the version on standard output and exit. */
99 static int show_version
;
102 /* If nonzero, display only a list of usernames and count of
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
;
120 static struct option
const longopts
[] =
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'},
130 {"help", no_argument
, &show_help
, 1},
131 {"version", no_argument
, &show_version
, 1},
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. */
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
, ' ');
158 #endif /* WHO || USERS */
162 /* Return a string representing the time between WHEN and the time
163 that this function is first run. */
169 static time_t now
= 0;
170 static char idle
[10];
176 seconds_idle
= now
- when
;
177 if (seconds_idle
< 60) /* One minute. */
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
;
189 /* Display a line of information about entry THIS. */
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';
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
;
230 printf ("%-8.*s", (int) sizeof (this->ut_name
), this->ut_name
);
232 printf (" %c ", mesg
);
233 printf (" %-8.*s", (int) sizeof (this->ut_line
), this->ut_line
);
236 printf (" %-12.12s", ctime (&this->ut_tv
.tv_sec
) + 4);
238 printf (" %-12.12s", ctime (&this->ut_time
) + 4);
244 printf (" %s", idle_string (last_change
));
249 if (this->ut_host
[0])
250 printf (" (%-.*s)", (int) sizeof (this->ut_host
), this->ut_host
);
256 /* Print the username of each valid entry and the number of valid entries
257 in `utmp_contents', which should have N elements. */
263 register STRUCT_UTMP
*this = utmp_contents
;
271 && this->ut_type
== USER_PROCESS
277 trimmed_name
= extract_trimmed_name (this);
279 printf ("%s ", trimmed_name
);
285 printf ("\n# users=%u\n", entries
);
293 userid_compare (v_a
, v_b
)
297 char **a
= (char **) v_a
;
298 char **b
= (char **) v_b
;
299 return strcmp (*a
, *b
);
303 list_entries_users (n
)
306 register STRUCT_UTMP
*this = utmp_contents
;
312 u
= (char **) xmalloc (n
* sizeof (u
[0]));
317 && this->ut_type
== USER_PROCESS
323 trimmed_name
= extract_trimmed_name (this);
325 u
[n_entries
] = trimmed_name
;
331 qsort (u
, n_entries
, sizeof (u
[0]), userid_compare
);
333 for (i
=0; i
<n_entries
; i
++)
336 fputs (u
[i
], stdout
);
337 c
= (i
< n_entries
-1 ? ' ' : '\n');
341 for (i
=0; i
<n_entries
; i
++)
353 printf ("%-8s ", "USER");
356 printf ("%-8s ", "LINE");
357 printf ("LOGIN-TIME ");
363 /* Display `utmp_contents', which should have N entries. */
369 register STRUCT_UTMP
*this = utmp_contents
;
378 && this->ut_type
== USER_PROCESS
388 /* Read the utmp file FILENAME into UTMP_CONTENTS and return the
389 number of entries it contains. */
396 struct stat file_stats
;
400 utmp
= fopen (filename
, "r");
402 error (1, errno
, "%s", filename
);
404 fstat (fileno (utmp
), &file_stats
);
405 size
= file_stats
.st_size
;
407 utmp_contents
= (STRUCT_UTMP
*) xmalloc (size
);
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
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. */
431 users
= read_utmp (filename
);
434 list_entries_who (users
);
436 scan_entries (users
);
439 list_entries_users (users
);
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. */
452 search_entries (n
, line
)
456 register STRUCT_UTMP
*this = utmp_contents
;
462 && this->ut_type
== USER_PROCESS
464 && !strncmp (line
, this->ut_line
, sizeof (this->ut_line
)))
471 /* Display the entry in utmp file FILENAME for this tty on standard input,
472 or nothing if there is no entry for it. */
478 register STRUCT_UTMP
*utmp_entry
;
479 char hostname
[MAXHOSTNAMELEN
+ 1];
482 if (gethostname (hostname
, MAXHOSTNAMELEN
+ 1))
487 printf ("%*s ", (int) strlen (hostname
), " ");
494 tty
+= 5; /* Remove "/dev/". */
496 utmp_entry
= search_entries (read_utmp (filename
), tty
);
497 if (utmp_entry
== NULL
)
500 printf ("%s!", hostname
);
501 print_entry (utmp_entry
);
509 fprintf (stderr
, "Try `%s --help' for more information.\n",
513 printf ("Usage: %s [OPTION]... [ FILE | ARG1 ARG2 ]\n", program_name
);
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\
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\
541 fprintf (stderr
, "Try `%s --help' for more information.\n",
545 printf ("Usage: %s [OPTION]... [ FILE ]\n", program_name
);
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");
564 int my_line_only
= 0;
567 program_name
= argv
[0];
570 while ((optc
= getopt_long (argc
, argv
, "imqsuwHT", longopts
, &longind
))
572 while ((optc
= getopt_long (argc
, argv
, "", longopts
, &longind
))
609 error (0, 0, "too many arguments");
616 printf ("%s - %s\n", COMMAND_NAME
, version_string
);
623 switch (argc
- optind
)
628 who_am_i (UTMP_FILE
);
634 case 1: /* who <utmp file> */
637 who_am_i (argv
[optind
]);
644 case 2: /* who <blurf> <glop> */
645 who_am_i (UTMP_FILE
);