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 #define UT_TYPE_UNDEF 255
102 #if HAVE_STRUCT_XTMP_UT_TYPE
103 # define UT_TYPE(U) ((U)->ut_type)
105 # define UT_TYPE(U) UT_TYPE_UNDEF
108 #define IS_USER_PROCESS(U) \
109 (UT_USER (utmp_buf)[0] \
110 && (UT_TYPE (utmp_buf) == USER_PROCESS \
111 || (UT_TYPE (utmp_buf) == UT_TYPE_UNDEF \
112 && UT_TIME_MEMBER (utmp_buf) != 0)))
118 /* The name this program was run with. */
121 /* If nonzero, attempt to canonicalize hostnames via a DNS lookup. */
122 static int do_lookup
;
124 /* If nonzero, display only a list of usernames and count of
126 Ignored for `who am i'. */
127 static int short_list
;
129 /* If nonzero, display only name, line, and time fields */
130 static int short_output
;
132 /* If nonzero, display the hours:minutes since each user has touched
133 the keyboard, or "." if within the last minute, or "old" if
134 not within the last day. */
135 static int include_idle
;
137 /* If nonzero, display a line at the top describing each field. */
138 static int include_heading
;
140 /* If nonzero, display a `+' for each user if mesg y, a `-' if mesg n,
141 or a `?' if their tty cannot be statted. */
142 static int include_mesg
;
144 /* If nonzero, display process termination & exit status */
145 static int include_exit
;
147 /* If nonzero, display the last boot time */
148 static int need_boottime
;
150 /* If nonzero, display dead processes */
151 static int need_deadprocs
;
153 /* If nonzero, display processes waiting for user login */
154 static int need_login
;
156 /* If nonzero, display processes started by init */
157 static int need_initspawn
;
159 /* If nonzero, display the last clock change */
160 static int need_clockchange
;
162 /* If nonzero, display the current runlevel */
163 static int need_runlevel
;
165 /* If nonzero, display user processes */
166 static int need_users
;
168 /* If nonzero, display info only for the controlling tty */
169 static int my_line_only
;
171 /* for long options with no corresponding short option, use enum */
174 LOOKUP_OPTION
= CHAR_MAX
+ 1,
178 static struct option
const longopts
[] = {
179 {"all", no_argument
, NULL
, 'a'},
180 {"boot", no_argument
, NULL
, 'b'},
181 {"count", no_argument
, NULL
, 'q'},
182 {"dead", no_argument
, NULL
, 'd'},
183 {"heading", no_argument
, NULL
, 'H'},
184 {"idle", no_argument
, NULL
, 'i'},
185 {"login", no_argument
, NULL
, LOGIN_OPTION
},
186 {"lookup", no_argument
, NULL
, LOOKUP_OPTION
},
187 {"message", no_argument
, NULL
, 'T'},
188 {"mesg", no_argument
, NULL
, 'T'},
189 {"process", no_argument
, NULL
, 'p'},
190 {"runlevel", no_argument
, NULL
, 'r'},
191 {"short", no_argument
, NULL
, 's'},
192 {"time", no_argument
, NULL
, 't'},
193 {"users", no_argument
, NULL
, 'u'},
194 {"writable", no_argument
, NULL
, 'T'},
195 {GETOPT_HELP_OPTION_DECL
},
196 {GETOPT_VERSION_OPTION_DECL
},
200 /* Return a string representing the time between WHEN and the time
201 that this function is first run.
204 idle_string (time_t when
)
206 static time_t now
= 0;
207 static char idle_hhmm
[IDLESTR_LEN
];
213 seconds_idle
= now
- when
;
214 if (seconds_idle
< 60) /* One minute. */
216 if (seconds_idle
< (24 * 60 * 60)) /* One day. */
218 sprintf (idle_hhmm
, "%02d:%02d",
219 (int) (seconds_idle
/ (60 * 60)),
220 (int) ((seconds_idle
% (60 * 60)) / 60));
221 return (const char *) idle_hhmm
;
226 /* Return a standard time string, "mon dd hh:mm"
227 FIXME: handle localization */
229 time_string (const STRUCT_UTMP
*utmp_ent
)
231 /* Don't take the address of UT_TIME_MEMBER directly.
232 Ulrich Drepper wrote:
233 ``... GNU libc (and perhaps other libcs as well) have extended
234 utmp file formats which do not use a simple time_t ut_time field.
235 In glibc, ut_time is a macro which selects for backward compatibility
236 the tv_sec member of a struct timeval value.'' */
237 time_t tm
= UT_TIME_MEMBER (utmp_ent
);
239 char *ptr
= ctime (&tm
) + 4;
244 /* Print formatted output line. Uses mostly arbitrary field sizes, probably
245 will need tweaking if any of the localization stuff is done, or for 64 bit
248 print_line (const char *user
, const char state
, const char *line
,
249 const char *time_str
, const char *idle
, const char *pid
,
250 const char *comment
, const char *exitstr
)
252 printf ("%-8.8s", user
? user
: " .");
254 printf (" %c", state
);
255 printf (" %-12s", line
);
256 printf (" %-12s", time_str
);
257 if (include_idle
&& !short_output
)
258 printf (" %-6s", idle
);
260 printf (" %10s", pid
);
261 /* FIXME: it's not really clear whether the following should be in short_output.
262 a strict reading of SUSv2 would suggest not, but I haven't seen any
263 implementations that actually work that way... */
264 printf (" %-8s", comment
);
265 if (include_exit
&& exitstr
&& *exitstr
)
266 printf (" %-12s", exitstr
);
270 /* Send properly parsed USER_PROCESS info to print_line */
272 print_user (const STRUCT_UTMP
*utmp_ent
)
277 char idlestr
[IDLESTR_LEN
];
278 static char *hoststr
;
281 #define DEV_DIR_WITH_TRAILING_SLASH "/dev/"
282 #define DEV_DIR_LEN (sizeof (DEV_DIR_WITH_TRAILING_SLASH) - 1)
284 char line
[sizeof (utmp_ent
->ut_line
) + DEV_DIR_LEN
+ 1];
285 PIDSTR_DECL_AND_INIT (pidstr
, utmp_ent
);
287 /* Copy ut_line into LINE, prepending `/dev/' if ut_line is not
288 already an absolute pathname. Some system may put the full,
289 absolute pathname in ut_line. */
290 if (utmp_ent
->ut_line
[0] == '/')
292 strncpy (line
, utmp_ent
->ut_line
, sizeof (utmp_ent
->ut_line
));
293 line
[sizeof (utmp_ent
->ut_line
)] = '\0';
297 strcpy (line
, DEV_DIR_WITH_TRAILING_SLASH
);
298 strncpy (line
+ DEV_DIR_LEN
, utmp_ent
->ut_line
,
299 sizeof (utmp_ent
->ut_line
));
300 line
[DEV_DIR_LEN
+ sizeof (utmp_ent
->ut_line
)] = '\0';
303 if (stat (line
, &stats
) == 0)
305 mesg
= (stats
.st_mode
& S_IWGRP
) ? '+' : '-';
306 last_change
= stats
.st_atime
;
315 sprintf (idlestr
, "%.6s", idle_string (last_change
));
317 sprintf (idlestr
, " ?");
320 if (utmp_ent
->ut_host
[0])
322 char ut_host
[sizeof (utmp_ent
->ut_host
) + 1];
323 char *host
= 0, *display
= 0;
325 /* Copy the host name into UT_HOST, and ensure it's nul terminated. */
326 strncpy (ut_host
, utmp_ent
->ut_host
, (int) sizeof (utmp_ent
->ut_host
));
327 ut_host
[sizeof (utmp_ent
->ut_host
)] = '\0';
329 /* Look for an X display. */
330 display
= strrchr (ut_host
, ':');
334 if (*ut_host
&& do_lookup
)
336 /* See if we can canonicalize it. */
337 host
= canon_host (ut_host
);
345 if (hostlen
< strlen (host
) + strlen (display
) + 4)
347 hostlen
= strlen (host
) + strlen (display
) + 4;
348 hoststr
= (char *) realloc (hoststr
, hostlen
);
350 sprintf (hoststr
, "(%s:%s)", host
, display
);
354 if (hostlen
< strlen (host
) + 3)
356 hostlen
= strlen (host
) + 3;
357 hoststr
= (char *) realloc (hoststr
, hostlen
);
359 sprintf (hoststr
, "(%s)", host
);
367 hoststr
= (char *) realloc (hoststr
, hostlen
);
369 stpcpy (hoststr
, "");
373 print_line (UT_USER (utmp_ent
), mesg
, utmp_ent
->ut_line
,
374 time_string (utmp_ent
), idlestr
, pidstr
,
375 hoststr
? hoststr
: "", "");
379 print_boottime (const STRUCT_UTMP
*utmp_ent
)
381 print_line ("", ' ', "system boot", time_string (utmp_ent
), "", "", "", "");
385 make_id_equals_comment (STRUCT_UTMP
const *utmp_ent
)
387 char *comment
= xmalloc (sizeof (_("id=")) + sizeof UT_ID (utmp_ent
) + 1);
389 /* Cast field width argument to `int' to avoid warning from gcc. */
390 sprintf (comment
, "%s%.*s", _("id="), (int) sizeof UT_ID (utmp_ent
),
396 print_deadprocs (const STRUCT_UTMP
*utmp_ent
)
398 static char *exitstr
;
399 char *comment
= make_id_equals_comment (utmp_ent
);
400 PIDSTR_DECL_AND_INIT (pidstr
, utmp_ent
);
403 exitstr
= xmalloc (sizeof (_("term="))
404 + INT_STRLEN_BOUND (UT_EXIT_E_TERMINATION (utmp_ent
)) + 1
405 + sizeof (_("exit="))
406 + INT_STRLEN_BOUND (UT_EXIT_E_EXIT (utmp_ent
))
408 sprintf (exitstr
, "%s%d %s%d", _("term="), UT_EXIT_E_TERMINATION (utmp_ent
),
409 _("exit="), UT_EXIT_E_EXIT (utmp_ent
));
411 /* FIXME: add idle time? */
413 print_line ("", ' ', utmp_ent
->ut_line
,
414 time_string (utmp_ent
), "", pidstr
, comment
, exitstr
);
419 print_login (const STRUCT_UTMP
*utmp_ent
)
421 char *comment
= make_id_equals_comment (utmp_ent
);
422 PIDSTR_DECL_AND_INIT (pidstr
, utmp_ent
);
424 /* FIXME: add idle time? */
426 print_line ("LOGIN", ' ', utmp_ent
->ut_line
,
427 time_string (utmp_ent
), "", pidstr
, comment
, "");
432 print_initspawn (const STRUCT_UTMP
*utmp_ent
)
434 char *comment
= make_id_equals_comment (utmp_ent
);
435 PIDSTR_DECL_AND_INIT (pidstr
, utmp_ent
);
437 print_line ("", ' ', utmp_ent
->ut_line
,
438 time_string (utmp_ent
), "", pidstr
, comment
, "");
443 print_clockchange (const STRUCT_UTMP
*utmp_ent
)
445 /* FIXME: handle NEW_TIME & OLD_TIME both */
446 print_line ("", ' ', _("clock change"),
447 time_string (utmp_ent
), "", "", "", "");
451 print_runlevel (const STRUCT_UTMP
*utmp_ent
)
453 static char *runlevline
, *comment
;
454 int last
= UT_PID (utmp_ent
) / 256;
455 int curr
= UT_PID (utmp_ent
) % 256;
458 runlevline
= xmalloc (sizeof (_("run-level")) + 3);
459 sprintf (runlevline
, "%s %c", _("run-level"), curr
);
462 comment
= xmalloc (sizeof (_("last=")) + 2);
463 sprintf (comment
, "%s%c", _("last="), (last
== 'N') ? 'S' : last
);
465 print_line ("", ' ', runlevline
, time_string (utmp_ent
),
466 "", "", comment
, "");
471 /* Print the username of each valid entry and the number of valid entries
472 in UTMP_BUF, which should have N elements. */
474 list_entries_who (int n
, const STRUCT_UTMP
*utmp_buf
)
480 if (UT_USER (utmp_buf
)[0] && UT_TYPE (utmp_buf
) == USER_PROCESS
)
484 trimmed_name
= extract_trimmed_name (utmp_buf
);
486 printf ("%s ", trimmed_name
);
492 printf (_("\n# users=%u\n"), entries
);
498 print_line (_("NAME"), ' ', _("LINE"), _("TIME"), _("IDLE"), _("PID"),
499 _("COMMENT"), _("EXIT"));
502 /* Display UTMP_BUF, which should have N entries. */
504 scan_entries (int n
, const STRUCT_UTMP
*utmp_buf
)
506 char *ttyname_b
IF_LINT ( = NULL
);
513 ttyname_b
= ttyname (0);
516 if (strncmp (ttyname_b
, DEV_DIR_WITH_TRAILING_SLASH
, DEV_DIR_LEN
) == 0)
517 ttyname_b
+= DEV_DIR_LEN
; /* Discard /dev/ prefix. */
523 strncmp (ttyname_b
, utmp_buf
->ut_line
,
524 sizeof (utmp_buf
->ut_line
)) == 0)
526 if (need_users
&& IS_USER_PROCESS (utmp_buf
))
527 print_user (utmp_buf
);
528 else if (need_runlevel
&& UT_TYPE (utmp_buf
) == RUN_LVL
)
529 print_runlevel (utmp_buf
);
530 else if (need_boottime
&& UT_TYPE (utmp_buf
) == BOOT_TIME
)
531 print_boottime (utmp_buf
);
532 /* I've never seen one of these, so I don't know what it should
534 FIXME: handle OLD_TIME also, perhaps show the delta? */
535 else if (need_clockchange
&& UT_TYPE (utmp_buf
) == NEW_TIME
)
536 print_clockchange (utmp_buf
);
537 else if (need_initspawn
&& UT_TYPE (utmp_buf
) == INIT_PROCESS
)
538 print_initspawn (utmp_buf
);
539 else if (need_login
&& UT_TYPE (utmp_buf
) == LOGIN_PROCESS
)
540 print_login (utmp_buf
);
541 else if (need_deadprocs
&& UT_TYPE (utmp_buf
) == DEAD_PROCESS
)
542 print_deadprocs (utmp_buf
);
549 /* Display a list of who is on the system, according to utmp file filename. */
551 who (const char *filename
)
554 STRUCT_UTMP
*utmp_buf
;
555 int fail
= read_utmp (filename
, &n_users
, &utmp_buf
);
558 error (1, errno
, "%s", filename
);
561 list_entries_who (n_users
, utmp_buf
);
563 scan_entries (n_users
, utmp_buf
);
570 fprintf (stderr
, _("Try `%s --help' for more information.\n"),
574 printf (_("Usage: %s [OPTION]... [ FILE | ARG1 ARG2 ]\n"), program_name
);
577 -a, --all same as -b -d --login -p -r -t -T -u\n\
578 -b, --boot time of last system boot\n\
579 -d, --dead print dead processes\n\
580 -H, --heading print line of column headings\n\
583 -i, --idle add idle time as HOURS:MINUTES, . or old\n\
584 (deprecated, use -u)\n\
585 --login print system login processes\n\
586 (equivalent to SUS -l)\n\
589 -l, --lookup attempt to canonicalize hostnames via DNS\n\
590 (-l is deprecated, use --lookup)\n\
591 -m only hostname and user associated with stdin\n\
592 -p, --process print active processes spawned by init\n\
595 -q, --count all login names and number of users logged on\n\
596 -r, --runlevel print current runlevel\n\
597 -s, --short print only name, line, and time (default)\n\
598 -t, --time print last system clock change\n\
601 -T, -w, --mesg add user's message status as +, - or ?\n\
602 -u, --users list users logged in\n\
603 --message same as -T\n\
604 --writable same as -T\n\
606 fputs (HELP_OPTION_DESCRIPTION
, stdout
);
607 fputs (VERSION_OPTION_DESCRIPTION
, stdout
);
610 If FILE is not specified, use %s. %s as FILE is common.\n\
611 If ARG1 ARG2 given, -m presumed: `am i' or `mom likes' are usual.\n\
612 "), UTMP_FILE
, WTMP_FILE
);
613 printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT
);
619 main (int argc
, char **argv
)
624 program_name
= argv
[0];
625 setlocale (LC_ALL
, "");
626 bindtextdomain (PACKAGE
, LOCALEDIR
);
627 textdomain (PACKAGE
);
629 atexit (close_stdout
);
631 while ((optc
= getopt_long (argc
, argv
, "abdilmpqrstuwHT", longopts
,
645 need_clockchange
= 1;
669 /* FIXME: This should be -l in a future version */
700 need_clockchange
= 1;
711 _("Warning: -i will be removed in a future release; \
722 _("Warning: the meaning of '-l' will change in a future\
723 release to conform to POSIX"));
728 case_GETOPT_HELP_CHAR
;
730 case_GETOPT_VERSION_CHAR (PROGRAM_NAME
, AUTHORS
);
748 switch (argc
- optind
)
754 case 1: /* who <utmp file> */
758 case 2: /* who <blurf> <glop> */
764 error (0, 0, _("too many arguments"));