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>
39 /* The official name of this program (e.g., no `g' prefix). */
40 #define PROGRAM_NAME "who"
42 #define AUTHORS N_ ("Joseph Arceneaux, David MacKenzie, and Michael Stone")
44 #ifndef MAXHOSTNAMELEN
45 # define MAXHOSTNAMELEN 64
53 # define USER_PROCESS INT_MAX
57 # define RUN_LVL INT_MAX
61 # define INIT_PROCESS INT_MAX
65 # define LOGIN_PROCESS INT_MAX
69 # define DEAD_PROCESS INT_MAX
73 # define NEW_TIME INT_MAX
82 /* The name this program was run with. */
85 /* If nonzero, attempt to canonicalize hostnames via a DNS lookup. */
88 /* If nonzero, display only a list of usernames and count of
90 Ignored for `who am i'. */
91 static int short_list;
93 /* If nonzero, display only name, line, and time fields */
94 static int short_output;
96 /* If nonzero, display the hours:minutes since each user has touched
97 the keyboard, or "." if within the last minute, or "old" if
98 not within the last day. */
99 static int include_idle;
101 /* If nonzero, display a line at the top describing each field. */
102 static int include_heading;
104 /* If nonzero, display a `+' for each user if mesg y, a `-' if mesg n,
105 or a `?' if their tty cannot be statted. */
106 static int include_mesg;
108 /* If nonzero, display process termination & exit status */
109 static int include_exit;
111 /* If nonzero, display the last boot time */
112 static int need_boottime;
114 /* If nonzero, display dead processes */
115 static int need_deadprocs;
117 /* If nonzero, display processes waiting for user login */
118 static int need_login;
120 /* If nonzero, display processes started by init */
121 static int need_initspawn;
123 /* If nonzero, display the last clock change */
124 static int need_clockchange;
126 /* If nonzero, display the current runlevel */
127 static int need_runlevel;
129 /* If nonzero, display user processes */
130 static int need_users;
132 /* If nonzero, display info only for the controlling tty */
133 static int my_line_only;
135 /* for long options with no corresponding short option, use enum */
138 LOOKUP_OPTION = CHAR_MAX + 1,
142 static struct option const longopts[] = {
143 {"all", no_argument, NULL, 'a'},
144 {"boot", no_argument, NULL, 'b'},
145 {"count", no_argument, NULL, 'q'},
146 {"dead", no_argument, NULL, 'd'},
147 {"heading", no_argument, NULL, 'H'},
148 {"idle", no_argument, NULL, 'i'},
149 {"login", no_argument, NULL, LOGIN_OPTION},
150 {"lookup", no_argument, NULL, LOOKUP_OPTION},
151 {"message", no_argument, NULL, 'T'},
152 {"mesg", no_argument, NULL, 'T'},
153 {"process", no_argument, NULL, 'p'},
154 {"runlevel", no_argument, NULL, 'r'},
155 {"short", no_argument, NULL, 's'},
156 {"time", no_argument, NULL, 't'},
157 {"users", no_argument, NULL, 'u'},
158 {"writable", no_argument, NULL, 'T'},
159 {GETOPT_HELP_OPTION_DECL},
160 {GETOPT_VERSION_OPTION_DECL},
164 /* Return a string representing the time between WHEN and the time
165 that this function is first run.
168 idle_string (time_t when)
170 static time_t now = 0;
171 static char idle_hhmm[IDLESTR_LEN];
177 seconds_idle = now - when;
178 if (seconds_idle < 60) /* One minute. */
180 if (seconds_idle < (24 * 60 * 60)) /* One day. */
182 sprintf (idle_hhmm, "%02d:%02d",
183 (int) (seconds_idle / (60 * 60)),
184 (int) ((seconds_idle % (60 * 60)) / 60));
185 return (const char *) idle_hhmm;
190 /* Return a standard time string, "mon dd hh:mm"
191 FIXME: handle localization */
193 time_string (const STRUCT_UTMP *utmp_ent)
195 /* Don't take the address of UT_TIME_MEMBER directly.
196 Ulrich Drepper wrote:
197 ``... GNU libc (and perhaps other libcs as well) have extended
198 utmp file formats which do not use a simple time_t ut_time field.
199 In glibc, ut_time is a macro which selects for backward compatibility
200 the tv_sec member of a struct timeval value.'' */
201 time_t tm = UT_TIME_MEMBER (utmp_ent);
203 char *ptr = ctime (&tm) + 4;
208 /* Print formatted output line. Uses mostly arbitrary field sizes, probably
209 will need tweaking if any of the localization stuff is done, or for 64 bit
212 print_line (const char *user, const char state, const char *line,
213 const char *time_str, const char *idle, const char *pid,
214 const char *comment, const char *exitstr)
216 printf ("%-8s", user ? user : " .");
218 printf (" %c", state);
219 printf (" %-12s", line);
220 printf (" %-12s", time_str);
221 if (include_idle && !short_output)
222 printf (" %-6s", idle);
224 printf (" %10s", pid);
225 /* FIXME: it's not really clear whether the following should be in short_output.
226 a strict reading of SUSv2 would suggest not, but I haven't seen any
227 implementations that actually work that way... */
228 printf (" %-8s", comment);
229 if (include_exit && exitstr && *exitstr)
230 printf (" %-12s", exitstr);
234 #if HAVE_STRUCT_XTMP_UT_PID
235 # define PIDSTR_DECL_AND_INIT(Var) \
236 char Var[INT_STRLEN_BOUND (utmp_ent->ut_pid) + 1]; \
237 sprintf (Var, "%d", utmp_ent->ut_pid)
239 # define PIDSTR_DECL_AND_INIT(Var) \
243 /* Send properly parsed USER_PROCESS info to print_line */
245 print_user (const STRUCT_UTMP *utmp_ent)
250 char idlestr[IDLESTR_LEN];
251 static char *hoststr;
254 #define DEV_DIR_WITH_TRAILING_SLASH "/dev/"
255 #define DEV_DIR_LEN (sizeof (DEV_DIR_WITH_TRAILING_SLASH) - 1)
257 char line[sizeof (utmp_ent->ut_line) + DEV_DIR_LEN + 1];
258 PIDSTR_DECL_AND_INIT (pidstr);
260 /* Copy ut_line into LINE, prepending `/dev/' if ut_line is not
261 already an absolute pathname. Some system may put the full,
262 absolute pathname in ut_line. */
263 if (utmp_ent->ut_line[0] == '/')
265 strncpy (line, utmp_ent->ut_line, sizeof (utmp_ent->ut_line));
266 line[sizeof (utmp_ent->ut_line)] = '\0';
270 strcpy (line, DEV_DIR_WITH_TRAILING_SLASH);
271 strncpy (line + DEV_DIR_LEN, utmp_ent->ut_line,
272 sizeof (utmp_ent->ut_line));
273 line[DEV_DIR_LEN + sizeof (utmp_ent->ut_line)] = '\0';
276 if (stat (line, &stats) == 0)
278 mesg = (stats.st_mode & S_IWGRP) ? '+' : '-';
279 last_change = stats.st_atime;
288 sprintf (idlestr, "%.6s", idle_string (last_change));
290 sprintf (idlestr, " ?");
293 if (utmp_ent->ut_host[0])
295 char ut_host[sizeof (utmp_ent->ut_host) + 1];
296 char *host = 0, *display = 0;
298 /* Copy the host name into UT_HOST, and ensure it's nul terminated. */
299 strncpy (ut_host, utmp_ent->ut_host, (int) sizeof (utmp_ent->ut_host));
300 ut_host[sizeof (utmp_ent->ut_host)] = '\0';
302 /* Look for an X display. */
303 display = strrchr (ut_host, ':');
307 if (*ut_host && do_lookup)
309 /* See if we can canonicalize it. */
310 host = canon_host (ut_host);
318 if (hostlen < strlen (host) + strlen (display) + 4)
320 hostlen = strlen (host) + strlen (display) + 4;
321 hoststr = (char *) realloc (hoststr, hostlen);
323 sprintf (hoststr, "(%s:%s)", host, display);
327 if (hostlen < strlen (host) + 3)
329 hostlen = strlen (host) + 3;
330 hoststr = (char *) realloc (hoststr, hostlen);
332 sprintf (hoststr, "(%s)", host);
340 hoststr = (char *) realloc (hoststr, hostlen);
342 stpcpy (hoststr, "");
346 print_line (UT_USER (utmp_ent), mesg, utmp_ent->ut_line,
347 time_string (utmp_ent), idlestr, pidstr,
348 hoststr ? hoststr : "", "");
352 print_boottime (const STRUCT_UTMP *utmp_ent)
354 print_line ("", ' ', "system boot", time_string (utmp_ent), "", "", "", "");
358 print_deadprocs (const STRUCT_UTMP *utmp_ent)
360 static char *comment, *exitstr;
361 PIDSTR_DECL_AND_INIT (pidstr);
364 comment = xmalloc (sizeof (_("id=")) + sizeof (utmp_ent->ut_id) + 1);
365 sprintf (comment, "%s%.*s", _("id="), sizeof utmp_ent->ut_id,
369 exitstr = xmalloc (sizeof (_("term="))
370 + INT_STRLEN_BOUND (utmp_ent->ut_exit.e_termination) + 1
371 + sizeof (_("exit="))
372 + INT_STRLEN_BOUND (utmp_ent->ut_exit.e_exit)
374 sprintf (exitstr, "%s%d %s%d", _("term="), utmp_ent->ut_exit.e_termination,
375 _("exit="), utmp_ent->ut_exit.e_exit);
377 /* FIXME: add idle time? */
379 print_line ("", ' ', utmp_ent->ut_line,
380 time_string (utmp_ent), "", pidstr, comment, exitstr);
384 print_login (const STRUCT_UTMP *utmp_ent)
386 static char *comment;
387 PIDSTR_DECL_AND_INIT (pidstr);
390 comment = xmalloc (sizeof (_("id=")) + sizeof (utmp_ent->ut_id) + 1);
391 sprintf (comment, "%s%s", _("id="), utmp_ent->ut_id);
393 /* FIXME: add idle time? */
395 print_line ("LOGIN", ' ', utmp_ent->ut_line,
396 time_string (utmp_ent), "", pidstr, comment, "");
400 print_initspawn (const STRUCT_UTMP *utmp_ent)
402 static char *comment;
403 PIDSTR_DECL_AND_INIT (pidstr);
406 comment = xmalloc (sizeof (_("id=")) + sizeof (utmp_ent->ut_id) + 1);
407 sprintf (comment, "%s%s", _("id="), utmp_ent->ut_id);
409 print_line ("", ' ', utmp_ent->ut_line,
410 time_string (utmp_ent), "", pidstr, comment, "");
414 print_clockchange (const STRUCT_UTMP *utmp_ent)
416 /* FIXME: handle NEW_TIME & OLD_TIME both */
417 print_line ("", ' ', _("clock change"),
418 time_string (utmp_ent), "", "", "", "");
422 print_runlevel (const STRUCT_UTMP *utmp_ent)
424 static char *runlevline, *comment;
426 /* FIXME: The following is correct for linux, may need help
427 on other platforms */
428 #if 1 || HAVE_STRUCT_XTMP_UT_PID
429 int last = utmp_ent->ut_pid / 256;
430 int curr = utmp_ent->ut_pid % 256;
434 runlevline = xmalloc (sizeof (_("run-level")) + 3);
435 sprintf (runlevline, "%s %c", _("run-level"), curr);
438 comment = xmalloc (sizeof (_("last=")) + 2);
439 sprintf (comment, "%s%c", _("last="), (last == 'N') ? 'S' : last);
441 print_line ("", ' ', runlevline, time_string (utmp_ent),
442 "", "", comment, "");
447 /* Print the username of each valid entry and the number of valid entries
448 in UTMP_BUF, which should have N elements. */
450 list_entries_who (int n, const STRUCT_UTMP *utmp_buf)
456 if (UT_USER (utmp_buf)[0] && UT_TYPE (utmp_buf) == USER_PROCESS)
460 trimmed_name = extract_trimmed_name (utmp_buf);
462 printf ("%s ", trimmed_name);
468 printf (_("\n# users=%u\n"), entries);
474 print_line (_("NAME"), ' ', _("LINE"), _("TIME"), _("IDLE"), _("PID"),
475 _("COMMENT"), _("EXIT"));
478 /* Display UTMP_BUF, which should have N entries. */
480 scan_entries (int n, const STRUCT_UTMP *utmp_buf)
482 char *ttyname_b IF_LINT ( = NULL);
489 ttyname_b = ttyname (0);
492 if (strncmp (ttyname_b, DEV_DIR_WITH_TRAILING_SLASH, DEV_DIR_LEN) == 0)
493 ttyname_b += DEV_DIR_LEN; /* Discard /dev/ prefix. */
499 strncmp (ttyname_b, utmp_buf->ut_line,
500 sizeof (utmp_buf->ut_line)) == 0)
502 if (need_users && UT_USER (utmp_buf)[0]
503 && UT_TYPE (utmp_buf) == USER_PROCESS)
504 print_user (utmp_buf);
505 else if (need_runlevel && UT_TYPE (utmp_buf) == RUN_LVL)
506 print_runlevel (utmp_buf);
507 else if (need_boottime && UT_TYPE (utmp_buf) == BOOT_TIME)
508 print_boottime (utmp_buf);
509 /* I've never seen one of these, so I don't know what it should
511 FIXME: handle OLD_TIME also, perhaps show the delta? */
512 else if (need_clockchange && UT_TYPE (utmp_buf) == NEW_TIME)
513 print_clockchange (utmp_buf);
514 else if (need_initspawn && UT_TYPE (utmp_buf) == INIT_PROCESS)
515 print_initspawn (utmp_buf);
516 else if (need_login && UT_TYPE (utmp_buf) == LOGIN_PROCESS)
517 print_login (utmp_buf);
518 else if (need_deadprocs && UT_TYPE (utmp_buf) == DEAD_PROCESS)
519 print_deadprocs (utmp_buf);
526 /* Display a list of who is on the system, according to utmp file filename. */
528 who (const char *filename)
531 STRUCT_UTMP *utmp_buf;
532 int fail = read_utmp (filename, &n_users, &utmp_buf);
535 error (1, errno, "%s", filename);
538 list_entries_who (n_users, utmp_buf);
540 scan_entries (n_users, utmp_buf);
547 fprintf (stderr, _("Try `%s --help' for more information.\n"),
551 printf (_("Usage: %s [OPTION]... [ FILE | ARG1 ARG2 ]\n"), program_name);
554 -a, --all same as -b -d --login -p -r -t -T -u\n\
555 -b, --boot time of last system boot\n\
556 -d, --dead print dead processes\n\
557 -H, --heading print line of column headings\n\
560 -i, --idle add idle time as HOURS:MINUTES, . or old\n\
561 (deprecated, use -u)\n\
562 --login print system login processes\n\
563 (equivalent to SUS -l)\n\
566 -l, --lookup attempt to canonicalize hostnames via DNS\n\
567 (-l is deprecated, use --lookup)\n\
568 -m only hostname and user associated with stdin\n\
569 -p, --process print active processes spawned by init\n\
572 -q, --count all login names and number of users logged on\n\
573 -r, --runlevel print current runlevel\n\
574 -s, --short print only name, line, and time (default)\n\
575 -t, --time print last system clock change\n\
578 -T, -w, --mesg add user's message status as +, - or ?\n\
579 -u, --users lists users logged in\n\
580 --message same as -T\n\
581 --writable same as -T\n\
583 fputs (HELP_OPTION_DESCRIPTION, stdout);
584 fputs (VERSION_OPTION_DESCRIPTION, stdout);
587 If FILE is not specified, use %s. %s as FILE is common.\n\
588 If ARG1 ARG2 given, -m presumed: `am i' or `mom likes' are usual.\n\
589 "), UTMP_FILE, WTMP_FILE);
590 puts (_("\nReport bugs to <bug-sh-utils@gnu.org>."));
596 main (int argc, char **argv)
601 program_name = argv[0];
602 setlocale (LC_ALL, "");
603 bindtextdomain (PACKAGE, LOCALEDIR);
604 textdomain (PACKAGE);
606 atexit (close_stdout);
608 while ((optc = getopt_long (argc, argv, "abdilmpqrstuwHT", longopts,
622 need_clockchange = 1;
646 /* FIXME: This should be -l in a future version */
677 need_clockchange = 1;
688 _("Warning: -i will be removed in a future release; \
699 _("Warning: the meaning of '-l' will change in a future\
700 release to conform to POSIX"));
705 case_GETOPT_HELP_CHAR;
707 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
725 switch (argc - optind)
731 case 1: /* who <utmp file> */
735 case 2: /* who <blurf> <glop> */
741 error (0, 0, _("too many arguments"));