2 Copyright (C) 1992-2002 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 Foundation,
16 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
18 /* Written by jla; revised by djm; revised again by mstone */
21 name [state] line time [activity] [pid] [comment] [exit]
23 name, line, time: not -q
31 #include <sys/types.h>
38 /* The official name of this program (e.g., no `g' prefix). */
39 #define PROGRAM_NAME "who"
41 #define AUTHORS N_ ("Joseph Arceneaux, David MacKenzie, and Michael Stone")
43 #ifndef MAXHOSTNAMELEN
44 # define MAXHOSTNAMELEN 64
52 # define USER_PROCESS INT_MAX
56 # define RUN_LVL INT_MAX
60 # define INIT_PROCESS INT_MAX
64 # define LOGIN_PROCESS INT_MAX
68 # define DEAD_PROCESS INT_MAX
81 #if HAVE_STRUCT_XTMP_UT_PID
82 # define UT_PID(U) ((U)->ut_pid)
83 # define PIDSTR_DECL_AND_INIT(Var, Utmp_ent) \
84 char Var[INT_STRLEN_BOUND (Utmp_ent->ut_pid) + 1]; \
85 sprintf (Var, "%d", (int) (Utmp_ent->ut_pid))
88 # define PIDSTR_DECL_AND_INIT(Var, Utmp_ent) \
92 #if HAVE_STRUCT_XTMP_UT_ID
93 # define UT_ID(U) ((U)->ut_id)
95 /* Of course, sizeof "whatever" is the size of a pointer (often 4),
96 but that's ok, since the actual string has a length of only 2. */
97 # define UT_ID(U) "??"
100 #if HAVE_STRUCT_XTMP_UT_EXIT
101 # define UT_EXIT_E_TERMINATION(U) ((U)->ut_exit.e_termination)
102 # define UT_EXIT_E_EXIT(U) ((U)->ut_exit.e_exit)
104 # define UT_EXIT_E_TERMINATION(U) 0
105 # define UT_EXIT_E_EXIT(U) 0
108 #define UT_TYPE_UNDEF 255
110 #if HAVE_STRUCT_XTMP_UT_TYPE
111 # define UT_TYPE(U) ((U)->ut_type)
113 # define UT_TYPE(U) UT_TYPE_UNDEF
116 #define IS_USER_PROCESS(U) \
117 (UT_USER (utmp_buf)[0] \
118 && (UT_TYPE (utmp_buf) == USER_PROCESS \
119 || (UT_TYPE (utmp_buf) == UT_TYPE_UNDEF \
120 && UT_TIME_MEMBER (utmp_buf) != 0)))
126 /* The name this program was run with. */
129 /* If nonzero, attempt to canonicalize hostnames via a DNS lookup. */
130 static int do_lookup;
132 /* If nonzero, display only a list of usernames and count of
134 Ignored for `who am i'. */
135 static int short_list;
137 /* If nonzero, display only name, line, and time fields */
138 static int short_output;
140 /* If nonzero, display the hours:minutes since each user has touched
141 the keyboard, or "." if within the last minute, or "old" if
142 not within the last day. */
143 static int include_idle;
145 /* If nonzero, display a line at the top describing each field. */
146 static int include_heading;
148 /* If nonzero, display a `+' for each user if mesg y, a `-' if mesg n,
149 or a `?' if their tty cannot be statted. */
150 static int include_mesg;
152 /* If nonzero, display process termination & exit status */
153 static int include_exit;
155 /* If nonzero, display the last boot time */
156 static int need_boottime;
158 /* If nonzero, display dead processes */
159 static int need_deadprocs;
161 /* If nonzero, display processes waiting for user login */
162 static int need_login;
164 /* If nonzero, display processes started by init */
165 static int need_initspawn;
167 /* If nonzero, display the last clock change */
168 static int need_clockchange;
170 /* If nonzero, display the current runlevel */
171 static int need_runlevel;
173 /* If nonzero, display user processes */
174 static int need_users;
176 /* If nonzero, display info only for the controlling tty */
177 static int my_line_only;
179 /* for long options with no corresponding short option, use enum */
182 LOOKUP_OPTION = CHAR_MAX + 1,
186 static struct option const longopts[] = {
187 {"all", no_argument, NULL, 'a'},
188 {"boot", no_argument, NULL, 'b'},
189 {"count", no_argument, NULL, 'q'},
190 {"dead", no_argument, NULL, 'd'},
191 {"heading", no_argument, NULL, 'H'},
192 {"idle", no_argument, NULL, 'i'},
193 {"login", no_argument, NULL, LOGIN_OPTION},
194 {"lookup", no_argument, NULL, LOOKUP_OPTION},
195 {"message", no_argument, NULL, 'T'},
196 {"mesg", no_argument, NULL, 'T'},
197 {"process", no_argument, NULL, 'p'},
198 {"runlevel", no_argument, NULL, 'r'},
199 {"short", no_argument, NULL, 's'},
200 {"time", no_argument, NULL, 't'},
201 {"users", no_argument, NULL, 'u'},
202 {"writable", no_argument, NULL, 'T'},
203 {GETOPT_HELP_OPTION_DECL},
204 {GETOPT_VERSION_OPTION_DECL},
208 /* Return a string representing the time between WHEN and the time
209 that this function is first run.
212 idle_string (time_t when)
214 static time_t now = 0;
215 static char idle_hhmm[IDLESTR_LEN];
221 seconds_idle = now - when;
222 if (seconds_idle < 60) /* One minute. */
224 if (seconds_idle < (24 * 60 * 60)) /* One day. */
226 sprintf (idle_hhmm, "%02d:%02d",
227 (int) (seconds_idle / (60 * 60)),
228 (int) ((seconds_idle % (60 * 60)) / 60));
229 return (const char *) idle_hhmm;
234 /* Return a standard time string, "mon dd hh:mm"
235 FIXME: handle localization */
237 time_string (const STRUCT_UTMP *utmp_ent)
239 /* Don't take the address of UT_TIME_MEMBER directly.
240 Ulrich Drepper wrote:
241 ``... GNU libc (and perhaps other libcs as well) have extended
242 utmp file formats which do not use a simple time_t ut_time field.
243 In glibc, ut_time is a macro which selects for backward compatibility
244 the tv_sec member of a struct timeval value.'' */
245 time_t tm = UT_TIME_MEMBER (utmp_ent);
247 char *ptr = ctime (&tm) + 4;
252 /* Print formatted output line. Uses mostly arbitrary field sizes, probably
253 will need tweaking if any of the localization stuff is done, or for 64 bit
256 print_line (const char *user, const char state, const char *line,
257 const char *time_str, const char *idle, const char *pid,
258 const char *comment, const char *exitstr)
260 printf ("%-8.8s", user ? user : " .");
262 printf (" %c", state);
263 printf (" %-12s", line);
264 printf (" %-12s", time_str);
265 if (include_idle && !short_output)
266 printf (" %-6s", idle);
268 printf (" %10s", pid);
269 /* FIXME: it's not really clear whether the following should be in short_output.
270 a strict reading of SUSv2 would suggest not, but I haven't seen any
271 implementations that actually work that way... */
272 printf (" %-8s", comment);
273 if (include_exit && exitstr && *exitstr)
274 printf (" %-12s", exitstr);
278 /* Send properly parsed USER_PROCESS info to print_line */
280 print_user (const STRUCT_UTMP *utmp_ent)
285 char idlestr[IDLESTR_LEN];
286 static char *hoststr;
289 #define DEV_DIR_WITH_TRAILING_SLASH "/dev/"
290 #define DEV_DIR_LEN (sizeof (DEV_DIR_WITH_TRAILING_SLASH) - 1)
292 char line[sizeof (utmp_ent->ut_line) + DEV_DIR_LEN + 1];
293 PIDSTR_DECL_AND_INIT (pidstr, utmp_ent);
295 /* Copy ut_line into LINE, prepending `/dev/' if ut_line is not
296 already an absolute pathname. Some system may put the full,
297 absolute pathname in ut_line. */
298 if (utmp_ent->ut_line[0] == '/')
300 strncpy (line, utmp_ent->ut_line, sizeof (utmp_ent->ut_line));
301 line[sizeof (utmp_ent->ut_line)] = '\0';
305 strcpy (line, DEV_DIR_WITH_TRAILING_SLASH);
306 strncpy (line + DEV_DIR_LEN, utmp_ent->ut_line,
307 sizeof (utmp_ent->ut_line));
308 line[DEV_DIR_LEN + sizeof (utmp_ent->ut_line)] = '\0';
311 if (stat (line, &stats) == 0)
313 mesg = (stats.st_mode & S_IWGRP) ? '+' : '-';
314 last_change = stats.st_atime;
323 sprintf (idlestr, "%.6s", idle_string (last_change));
325 sprintf (idlestr, " ?");
328 if (utmp_ent->ut_host[0])
330 char ut_host[sizeof (utmp_ent->ut_host) + 1];
331 char *host = 0, *display = 0;
333 /* Copy the host name into UT_HOST, and ensure it's nul terminated. */
334 strncpy (ut_host, utmp_ent->ut_host, (int) sizeof (utmp_ent->ut_host));
335 ut_host[sizeof (utmp_ent->ut_host)] = '\0';
337 /* Look for an X display. */
338 display = strrchr (ut_host, ':');
342 if (*ut_host && do_lookup)
344 /* See if we can canonicalize it. */
345 host = canon_host (ut_host);
353 if (hostlen < strlen (host) + strlen (display) + 4)
355 hostlen = strlen (host) + strlen (display) + 4;
356 hoststr = (char *) realloc (hoststr, hostlen);
358 sprintf (hoststr, "(%s:%s)", host, display);
362 if (hostlen < strlen (host) + 3)
364 hostlen = strlen (host) + 3;
365 hoststr = (char *) realloc (hoststr, hostlen);
367 sprintf (hoststr, "(%s)", host);
375 hoststr = (char *) realloc (hoststr, hostlen);
377 stpcpy (hoststr, "");
381 print_line (UT_USER (utmp_ent), mesg, utmp_ent->ut_line,
382 time_string (utmp_ent), idlestr, pidstr,
383 hoststr ? hoststr : "", "");
387 print_boottime (const STRUCT_UTMP *utmp_ent)
389 print_line ("", ' ', "system boot", time_string (utmp_ent), "", "", "", "");
393 make_id_equals_comment (STRUCT_UTMP const *utmp_ent)
395 char *comment = xmalloc (sizeof (_("id=")) + sizeof UT_ID (utmp_ent) + 1);
397 /* Cast field width argument to `int' to avoid warning from gcc. */
398 sprintf (comment, "%s%.*s", _("id="), (int) sizeof UT_ID (utmp_ent),
404 print_deadprocs (const STRUCT_UTMP *utmp_ent)
406 static char *exitstr;
407 char *comment = make_id_equals_comment (utmp_ent);
408 PIDSTR_DECL_AND_INIT (pidstr, utmp_ent);
410 /* FIXME: ut_exit works with GNU/Linux but is probably not portable. */
412 exitstr = xmalloc (sizeof (_("term="))
413 + INT_STRLEN_BOUND (UT_EXIT_E_TERMINATION (utmp_ent)) + 1
414 + sizeof (_("exit="))
415 + INT_STRLEN_BOUND (UT_EXIT_E_EXIT (utmp_ent))
417 sprintf (exitstr, "%s%d %s%d", _("term="), UT_EXIT_E_TERMINATION (utmp_ent),
418 _("exit="), UT_EXIT_E_EXIT (utmp_ent));
420 /* FIXME: add idle time? */
422 print_line ("", ' ', utmp_ent->ut_line,
423 time_string (utmp_ent), "", pidstr, comment, exitstr);
428 print_login (const STRUCT_UTMP *utmp_ent)
430 char *comment = make_id_equals_comment (utmp_ent);
431 PIDSTR_DECL_AND_INIT (pidstr, utmp_ent);
433 /* FIXME: add idle time? */
435 print_line ("LOGIN", ' ', utmp_ent->ut_line,
436 time_string (utmp_ent), "", pidstr, comment, "");
441 print_initspawn (const STRUCT_UTMP *utmp_ent)
443 char *comment = make_id_equals_comment (utmp_ent);
444 PIDSTR_DECL_AND_INIT (pidstr, utmp_ent);
446 print_line ("", ' ', utmp_ent->ut_line,
447 time_string (utmp_ent), "", pidstr, comment, "");
452 print_clockchange (const STRUCT_UTMP *utmp_ent)
454 /* FIXME: handle NEW_TIME & OLD_TIME both */
455 print_line ("", ' ', _("clock change"),
456 time_string (utmp_ent), "", "", "", "");
460 print_runlevel (const STRUCT_UTMP *utmp_ent)
462 static char *runlevline, *comment;
463 int last = UT_PID (utmp_ent) / 256;
464 int curr = UT_PID (utmp_ent) % 256;
467 runlevline = xmalloc (sizeof (_("run-level")) + 3);
468 sprintf (runlevline, "%s %c", _("run-level"), curr);
471 comment = xmalloc (sizeof (_("last=")) + 2);
472 sprintf (comment, "%s%c", _("last="), (last == 'N') ? 'S' : last);
474 print_line ("", ' ', runlevline, time_string (utmp_ent),
475 "", "", comment, "");
480 /* Print the username of each valid entry and the number of valid entries
481 in UTMP_BUF, which should have N elements. */
483 list_entries_who (int n, const STRUCT_UTMP *utmp_buf)
489 if (UT_USER (utmp_buf)[0] && UT_TYPE (utmp_buf) == USER_PROCESS)
493 trimmed_name = extract_trimmed_name (utmp_buf);
495 printf ("%s ", trimmed_name);
501 printf (_("\n# users=%u\n"), entries);
507 print_line (_("NAME"), ' ', _("LINE"), _("TIME"), _("IDLE"), _("PID"),
508 _("COMMENT"), _("EXIT"));
511 /* Display UTMP_BUF, which should have N entries. */
513 scan_entries (int n, const STRUCT_UTMP *utmp_buf)
515 char *ttyname_b IF_LINT ( = NULL);
522 ttyname_b = ttyname (0);
525 if (strncmp (ttyname_b, DEV_DIR_WITH_TRAILING_SLASH, DEV_DIR_LEN) == 0)
526 ttyname_b += DEV_DIR_LEN; /* Discard /dev/ prefix. */
532 strncmp (ttyname_b, utmp_buf->ut_line,
533 sizeof (utmp_buf->ut_line)) == 0)
535 if (need_users && IS_USER_PROCESS (utmp_buf))
536 print_user (utmp_buf);
537 else if (need_runlevel && UT_TYPE (utmp_buf) == RUN_LVL)
538 print_runlevel (utmp_buf);
539 else if (need_boottime && UT_TYPE (utmp_buf) == BOOT_TIME)
540 print_boottime (utmp_buf);
541 /* I've never seen one of these, so I don't know what it should
543 FIXME: handle OLD_TIME also, perhaps show the delta? */
544 else if (need_clockchange && UT_TYPE (utmp_buf) == NEW_TIME)
545 print_clockchange (utmp_buf);
546 else if (need_initspawn && UT_TYPE (utmp_buf) == INIT_PROCESS)
547 print_initspawn (utmp_buf);
548 else if (need_login && UT_TYPE (utmp_buf) == LOGIN_PROCESS)
549 print_login (utmp_buf);
550 else if (need_deadprocs && UT_TYPE (utmp_buf) == DEAD_PROCESS)
551 print_deadprocs (utmp_buf);
558 /* Display a list of who is on the system, according to utmp file filename. */
560 who (const char *filename)
563 STRUCT_UTMP *utmp_buf;
564 int fail = read_utmp (filename, &n_users, &utmp_buf);
567 error (1, errno, "%s", filename);
570 list_entries_who (n_users, utmp_buf);
572 scan_entries (n_users, utmp_buf);
579 fprintf (stderr, _("Try `%s --help' for more information.\n"),
583 printf (_("Usage: %s [OPTION]... [ FILE | ARG1 ARG2 ]\n"), program_name);
586 -a, --all same as -b -d --login -p -r -t -T -u\n\
587 -b, --boot time of last system boot\n\
588 -d, --dead print dead processes\n\
589 -H, --heading print line of column headings\n\
592 -i, --idle add idle time as HOURS:MINUTES, . or old\n\
593 (deprecated, use -u)\n\
594 --login print system login processes\n\
595 (equivalent to SUS -l)\n\
598 -l, --lookup attempt to canonicalize hostnames via DNS\n\
599 (-l is deprecated, use --lookup)\n\
600 -m only hostname and user associated with stdin\n\
601 -p, --process print active processes spawned by init\n\
604 -q, --count all login names and number of users logged on\n\
605 -r, --runlevel print current runlevel\n\
606 -s, --short print only name, line, and time (default)\n\
607 -t, --time print last system clock change\n\
610 -T, -w, --mesg add user's message status as +, - or ?\n\
611 -u, --users lists users logged in\n\
612 --message same as -T\n\
613 --writable same as -T\n\
615 fputs (HELP_OPTION_DESCRIPTION, stdout);
616 fputs (VERSION_OPTION_DESCRIPTION, stdout);
619 If FILE is not specified, use %s. %s as FILE is common.\n\
620 If ARG1 ARG2 given, -m presumed: `am i' or `mom likes' are usual.\n\
621 "), UTMP_FILE, WTMP_FILE);
622 puts (_("\nReport bugs to <bug-sh-utils@gnu.org>."));
628 main (int argc, char **argv)
633 program_name = argv[0];
634 setlocale (LC_ALL, "");
635 bindtextdomain (PACKAGE, LOCALEDIR);
636 textdomain (PACKAGE);
638 atexit (close_stdout);
640 while ((optc = getopt_long (argc, argv, "abdilmpqrstuwHT", longopts,
654 need_clockchange = 1;
678 /* FIXME: This should be -l in a future version */
709 need_clockchange = 1;
720 _("Warning: -i will be removed in a future release; \
731 _("Warning: the meaning of '-l' will change in a future\
732 release to conform to POSIX"));
737 case_GETOPT_HELP_CHAR;
739 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
757 switch (argc - optind)
763 case 1: /* who <utmp file> */
767 case 2: /* who <blurf> <glop> */
773 error (0, 0, _("too many arguments"));