2 * w - show what logged in users are doing.
4 * Almost entirely rewritten from scratch by Charles Blake circa
5 * June 1996. Some vestigal traces of the original may exist.
6 * That was done in 1993 by Larry Greenfield with some fixes by
9 * Changes by Albert Cahalan, 2002.
11 * This library is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU Lesser General Public
13 * License as published by the Free Software Foundation; either
14 * version 2.1 of the License, or (at your option) any later version.
16 * This library is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 * Lesser General Public License for more details.
21 * You should have received a copy of the GNU Lesser General Public
22 * License along with this library; if not, write to the Free Software
23 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
39 #include <sys/ioctl.h>
42 #include <sys/types.h>
51 #include <arpa/inet.h>
54 #include "fileutils.h"
60 static int ignoreuser = 0; /* for '-u' */
61 static int oldstyle = 0; /* for '-o' */
64 typedef struct utmpx utmp_t;
66 typedef struct utmp utmp_t;
69 #if !defined(UT_HOSTSIZE) || defined(__UT_HOSTSIZE)
70 # define UT_HOSTSIZE __UT_HOSTSIZE
71 # define UT_LINESIZE __UT_LINESIZE
72 # define UT_NAMESIZE __UT_NAMESIZE
76 # define FROM_STRING "on"
78 # define FROM_STRING "off"
81 #define MAX_CMD_WIDTH 512
82 #define MIN_CMD_WIDTH 7
85 * This routine is careful since some programs leave utmp strings
86 * unprintable. Always outputs at least 16 chars padded with
87 * spaces on the right if necessary.
89 static void print_host(const char *restrict host, int len, const int fromlen)
97 for (; host < last; host++) {
98 if (*host == '\0') break;
99 if (isprint(*host) && *host != ' ') {
100 fputc(*host, stdout);
110 * space-fill, and a '-' too if needed to ensure the
117 while (width++ < fromlen)
122 /* This routine prints the display part of the host or IPv6 link address interface */
123 static void print_display_or_interface(const char *restrict host, int len, int restlen)
125 const char *const end = host + (len > 0 ? len : 0);
126 const char *disp, *tmp;
128 if (restlen <= 0) return; /* not enough space for printing anything */
130 /* search for a collon (might be a display) */
132 while ( (disp < end) && (*disp != ':') && isprint(*disp) ) disp++;
135 if (disp < end && *disp == ':') {
136 /* detect multiple colons -> IPv6 in the host (not a display) */
138 while ( (tmp < end) && (*tmp != ':') && isprint(*tmp) ) tmp++;
140 if (tmp >= end || *tmp != ':') { /* multiple colons not found - it's a display */
142 /* number of chars till the end of the input field */
143 len -= (disp - host);
145 /* if it is still longer than the rest of the output field, then cut it */
146 if (len > restlen) len = restlen;
148 /* print the display */
149 while ((len > 0) && isprint(*disp) && (*disp != ' ')) {
151 fputc(*disp, stdout);
155 if ((len > 0) && (*disp != '\0')) { /* space or nonprintable found - replace with dash and stop printing */
159 } else { /* multiple colons found - it's an IPv6 address */
161 /* search for % (interface separator in case of IPv6 link address) */
162 while ( (tmp < end) && (*tmp != '%') && isprint(*tmp) ) tmp++;
164 if (tmp < end && *tmp == '%') { /* interface separator found */
166 /* number of chars till the end of the input field */
169 /* if it is still longer than the rest of the output field, then cut it */
170 if (len > restlen) len = restlen;
172 /* print the interface */
173 while ((len > 0) && isprint(*tmp) && (*tmp != ' ')) {
178 if ((len > 0) && (*tmp != '\0')) { /* space or nonprintable found - replace with dash and stop printing */
186 /* padding with spaces */
187 while (restlen > 0) {
194 /* This routine prints either the hostname or the IP address of the remote */
195 static void print_from(const utmp_t *restrict const u, const int ip_addresses, const int fromlen) {
196 char buf[fromlen + 1];
197 char buf_ipv6[INET6_ADDRSTRLEN];
200 int32_t ut_addr_v6[4]; /* IP address of the remote host */
202 if (ip_addresses) { /* -i switch used */
203 memcpy(&ut_addr_v6, &u->ut_addr_v6, sizeof(ut_addr_v6));
204 if (IN6_IS_ADDR_V4MAPPED(&ut_addr_v6)) {
206 ut_addr_v6[0] = ut_addr_v6[3];
211 if (ut_addr_v6[1] || ut_addr_v6[2] || ut_addr_v6[3]) {
213 if (!inet_ntop(AF_INET6, &ut_addr_v6, buf_ipv6, sizeof(buf_ipv6))) {
214 strcpy(buf, ""); /* invalid address, clean the buffer */
216 strncpy(buf, buf_ipv6, fromlen); /* address valid, copy to buffer */
220 if (!(ut_addr_v6[0] && inet_ntop(AF_INET, &ut_addr_v6[0], buf, sizeof(buf)))) {
221 strcpy(buf, ""); /* invalid address, clean the buffer */
227 if (len) { /* IP address is non-empty, print it (and concatenate with display, if present) */
229 /* show the display part of the host or IPv6 link addr. interface, if present */
230 print_display_or_interface(u->ut_host, UT_HOSTSIZE, fromlen - len);
231 } else { /* IP address is empty, print the host instead */
232 print_host(u->ut_host, UT_HOSTSIZE, fromlen);
234 } else { /* -i switch NOT used */
235 print_host(u->ut_host, UT_HOSTSIZE, fromlen);
238 print_host(u->ut_host, UT_HOSTSIZE, fromlen);
243 /* compact 7 char format for time intervals (belongs in libproc?) */
244 static void print_time_ival7(time_t t, int centi_sec, FILE * fout)
246 if ((long)t < (long)0) {
247 /* system clock changed? */
252 if (t >= 48 * 60 * 60)
254 fprintf(fout, _(" %2lludays"), (unsigned long long)t / (24 * 60 * 60));
255 else if (t >= 60 * 60)
257 /* Translation Hint: Hours:Minutes */
258 fprintf(fout, " %2llu:%02u ", (unsigned long long)t / (60 * 60),
259 (unsigned)((t / 60) % 60));
262 /* Translation Hint: Minutes:Seconds */
263 fprintf(fout, _(" %2llu:%02um"), (unsigned long long)t / 60, (unsigned)t % 60);
267 if (t >= 48 * 60 * 60)
269 fprintf(fout, _(" %2lludays"), (unsigned long long)t / (24 * 60 * 60));
270 else if (t >= 60 * 60)
272 /* Translation Hint: Hours:Minutes */
273 fprintf(fout, _(" %2llu:%02um"), (unsigned long long)t / (60 * 60),
274 (unsigned)((t / 60) % 60));
276 /* 1 minute or more */
277 /* Translation Hint: Minutes:Seconds */
278 fprintf(fout, " %2llu:%02u ", (unsigned long long)t / 60, (unsigned)t % 60);
280 /* Translation Hint: Seconds:Centiseconds */
281 fprintf(fout, _(" %2llu.%02us"), (unsigned long long)t, centi_sec);
285 /* stat the device file to get an idle time */
286 static time_t idletime(const char *restrict const tty)
289 if (stat(tty, &sbuf) != 0)
291 return time(NULL) - sbuf.st_atime;
294 /* 7 character formatted login time */
296 static void print_logintime(time_t logt, FILE * fout)
299 /* Abbreviated of weekday can be longer than 3 characters,
300 * see for instance hu_HU. Using 16 is few bytes more than
304 struct tm *logtm, *curtm;
308 curtm = localtime(&curt);
309 /* localtime returns a pointer to static memory */
310 today = curtm->tm_yday;
311 logtm = localtime(&logt);
312 if (curt - logt > 12 * 60 * 60 && logtm->tm_yday != today) {
313 if (curt - logt > 6 * 24 * 60 * 60) {
314 strftime(time_str, sizeof(time_str), "%b", logtm);
315 fprintf(fout, " %02d%3s%02d", logtm->tm_mday,
316 time_str, logtm->tm_year % 100);
318 strftime(time_str, sizeof(time_str), "%a", logtm);
319 fprintf(fout, " %3s%02d ", time_str,
323 fprintf(fout, " %02d:%02d ", logtm->tm_hour, logtm->tm_min);
328 * Get the Device ID of the given TTY
330 static int get_tty_device(const char *restrict const name)
334 char *dev_paths[] = { "/dev/%s", "/dev/tty%s", "/dev/pts/%s", NULL};
337 if (name[0] == '/' && stat(name, &st) == 0)
340 for (i=0; dev_paths[i] != NULL; i++) {
341 snprintf(buf, 32, dev_paths[i], name);
342 if (stat(buf, &st) == 0)
349 * This function scans the process table accumulating total cpu
350 * times for any processes "associated" with this login session.
351 * It also searches for the "best" process to report as "(w)hat"
352 * the user for that login session is doing currently. This the
353 * essential core of 'w'.
355 static int find_best_proc(
356 const utmp_t * restrict const u,
357 const char *restrict const tty,
358 unsigned long long *restrict const jcpu,
359 unsigned long long *restrict const pcpu,
363 #define PIDS_GETINT(e) PIDS_VAL(EU_ ## e, s_int, reap->stacks[i], info)
364 #define PIDS_GETUNT(e) PIDS_VAL(EU_ ## e, u_int, reap->stacks[i], info)
365 #define PIDS_GETULL(e) PIDS_VAL(EU_ ## e, ull_int, reap->stacks[i], info)
366 #define PIDS_GETSTR(e) PIDS_VAL(EU_ ## e, str, reap->stacks[i], info)
369 int i, total_procs, line;
370 unsigned long long best_time = 0;
371 unsigned long long secondbest_time = 0;
373 struct pids_info *info=NULL;
374 struct pids_fetch *reap;
375 enum pids_item items[] = {
387 EU_PID, EU_TGID, EU_START, EU_EUID, EU_RUID, EU_TPGID, EU_PGRP, EU_TTY,
388 EU_TICS_ALL, EU_CMDLINE};
393 char buf[UT_NAMESIZE + 1];
394 struct passwd *passwd_data;
395 strncpy(buf, u->ut_user, UT_NAMESIZE);
396 buf[UT_NAMESIZE] = '\0';
397 if ((passwd_data = getpwnam(buf)) == NULL)
399 uid = passwd_data->pw_uid;
400 /* OK to have passwd_data go out of scope here */
403 line = get_tty_device(tty);
405 if (procps_pids_new(&info, items, 10) < 0)
407 _("Unable to create pid info structure"));
408 if ((reap = procps_pids_reap(info, PIDS_FETCH_TASKS_ONLY)) == NULL)
410 _("Unable to load process information"));
411 total_procs = reap->counts->total;
413 for (i=0; i < total_procs; i++) {
414 /* is this the login process? */
415 if (PIDS_GETINT(TGID) == u->ut_pid) {
418 best_time = PIDS_GETULL(START);
419 strncpy(cmdline, PIDS_GETSTR(CMDLINE), MAX_CMD_WIDTH);
420 *pid = PIDS_GETULL(PID);
421 *pcpu = PIDS_GETULL(TICS_ALL);
425 if (PIDS_GETINT(TTY) != line)
427 (*jcpu) += PIDS_VAL(EU_TICS_ALL, ull_int, reap->stacks[i], info);
428 if (!(secondbest_time && PIDS_GETULL(START) <= secondbest_time)) {
429 secondbest_time = PIDS_GETULL(START);
430 if (cmdline[0] == '-' && cmdline[1] == '\0') {
431 strncpy(cmdline, PIDS_GETSTR(CMDLINE), MAX_CMD_WIDTH);
432 *pid = PIDS_GETULL(PID);
433 *pcpu = PIDS_GETULL(TICS_ALL);
437 (!ignoreuser && uid != PIDS_GETUNT(EUID)
438 && uid != PIDS_GETUNT(RUID))
439 || (PIDS_GETINT(PGRP) != PIDS_GETINT(TPGID))
440 || (PIDS_GETULL(START) <= best_time)
443 best_time = PIDS_GETULL(START);
444 strncpy(cmdline, PIDS_GETSTR(CMDLINE), MAX_CMD_WIDTH);
445 *pid = PIDS_GETULL(PID);
446 *pcpu = PIDS_GETULL(TICS_ALL);
448 procps_pids_unref(&info);
456 static void showinfo(
457 utmp_t * u, int formtype, int maxcmd, int from,
458 const int userlen, const int fromlen, const int ip_addresses,
461 unsigned long long jcpu, pcpu;
463 char uname[UT_NAMESIZE + 1] = "", tty[5 + UT_LINESIZE + 1] = "/dev/";
465 char cmdline[MAX_CMD_WIDTH + 1];
469 strcpy(cmdline, "-");
471 hertz = procps_hertz_get();
472 for (i = 0; i < UT_LINESIZE; i++)
473 /* clean up tty if garbled */
474 if (isalnum(u->ut_line[i]) || (u->ut_line[i] == '/'))
475 tty[i + 5] = u->ut_line[i];
479 if (find_best_proc(u, tty + 5, &jcpu, &pcpu, cmdline, &best_pid) == 0)
481 * just skip if stale utmp entry (i.e. login proc doesn't
482 * exist). If there is a desire a cmdline flag could be
483 * added to optionally show it with a prefix of (stale)
484 * in front of cmd or something like that.
488 /* force NUL term for printf */
489 strncpy(uname, u->ut_user, UT_NAMESIZE);
492 printf("%-*.*s%-9.8s", userlen + 1, userlen, uname, u->ut_line);
494 print_from(u, ip_addresses, fromlen);
496 print_logintime(u->ut_tv.tv_sec, stdout);
498 print_logintime(u->ut_time, stdout);
500 if (*u->ut_line == ':')
501 /* idle unknown for xdm logins */
504 print_time_ival7(idletime(tty), 0, stdout);
505 print_time_ival7(jcpu / hertz, (jcpu % hertz) * (100. / hertz),
508 print_time_ival7(pcpu / hertz,
509 (pcpu % hertz) * (100. / hertz),
514 printf("%-*.*s%-9.8s", userlen + 1, userlen, u->ut_user,
517 print_from(u, ip_addresses, fromlen);
518 if (*u->ut_line == ':')
519 /* idle unknown for xdm logins */
522 print_time_ival7(idletime(tty), 0, stdout);
525 pids_length = printf(" %d/%d", u->ut_pid, best_pid);
526 if (pids_length > maxcmd) {
528 } else if (pids_length > 0) {
529 maxcmd -= pids_length;
532 printf(" %.*s\n", maxcmd, cmdline);
535 static void __attribute__ ((__noreturn__))
538 fputs(USAGE_HEADER, out);
540 _(" %s [options] [user]\n"), program_invocation_short_name);
541 fputs(USAGE_OPTIONS, out);
542 fputs(_(" -h, --no-header do not print header\n"),out);
543 fputs(_(" -u, --no-current ignore current process username\n"),out);
544 fputs(_(" -s, --short short format\n"),out);
545 fputs(_(" -f, --from show remote hostname field\n"),out);
546 fputs(_(" -o, --old-style old style output\n"),out);
547 fputs(_(" -i, --ip-addr display IP address instead of hostname (if possible)\n"), out);
548 fputs(_(" -p, --pids show the PID(s) of processes in WHAT\n"), out);
549 fputs(USAGE_SEPARATOR, out);
550 fputs(_(" --help display this help and exit\n"), out);
551 fputs(USAGE_VERSION, out);
552 fprintf(out, USAGE_MAN_TAIL("w(1)"));
554 exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
557 int main(int argc, char **argv)
559 char *user = NULL, *p;
568 /* switches (defaults) */
572 int ip_addresses = 0;
576 HELP_OPTION = CHAR_MAX + 1
579 static const struct option longopts[] = {
580 {"no-header", no_argument, NULL, 'h'},
581 {"no-current", no_argument, NULL, 'u'},
582 {"short", no_argument, NULL, 's'},
583 {"from", no_argument, NULL, 'f'},
584 {"old-style", no_argument, NULL, 'o'},
585 {"ip-addr", no_argument, NULL, 'i'},
586 {"pids", no_argument, NULL, 'p'},
587 {"help", no_argument, NULL, HELP_OPTION},
588 {"version", no_argument, NULL, 'V'},
592 #ifdef HAVE_PROGRAM_INVOCATION_NAME
593 program_invocation_name = program_invocation_short_name;
595 setlocale (LC_ALL, "");
596 bindtextdomain(PACKAGE, LOCALEDIR);
598 atexit(close_stdout);
605 getopt_long(argc, argv, "husfoVip", longopts, NULL)) != -1)
617 printf(PROCPS_NG_VERSION);
639 user = (argv[optind]);
641 /* Get user field length from environment */
642 if ((env_var = getenv("PROCPS_USERLEN")) != NULL) {
643 int ut_namesize = UT_NAMESIZE;
644 userlen = atoi(env_var);
645 if (userlen < 8 || ut_namesize < userlen) {
647 (_("User length environment PROCPS_USERLEN must be between 8 and %i, ignoring.\n"),
652 /* Get from field length from environment */
653 if ((env_var = getenv("PROCPS_FROMLEN")) != NULL) {
654 fromlen = atoi(env_var);
655 if (fromlen < 8 || UT_HOSTSIZE < fromlen) {
657 (_("from length environment PROCPS_FROMLEN must be between 8 and %d, ignoring\n"),
662 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) != -1 && win.ws_col > 0)
664 else if ((p = getenv("COLUMNS")))
667 maxcmd = MAX_CMD_WIDTH;
668 #define CLAMP_CMD_WIDTH(cw) do { \
669 if ((cw) < MIN_CMD_WIDTH) (cw) = MIN_CMD_WIDTH; \
670 if ((cw) > MAX_CMD_WIDTH) (cw) = MAX_CMD_WIDTH; \
672 CLAMP_CMD_WIDTH(maxcmd);
673 maxcmd -= 21 + userlen + (from ? fromlen : 0) + (longform ? 20 : 0);
674 CLAMP_CMD_WIDTH(maxcmd);
675 #undef CLAMP_CMD_WIDTH
679 /* print uptime and headers */
680 printf("%s\n", procps_uptime_sprint());
681 /* Translation Hint: Following five uppercase messages are
682 * headers. Try to keep alignment intact. */
683 printf(_("%-*s TTY "), userlen, _("USER"));
685 printf("%-*s", fromlen - 1, _("FROM"));
687 printf(_(" LOGIN@ IDLE JCPU PCPU WHAT\n"));
689 printf(_(" IDLE WHAT\n"));
707 if (u->ut_type != USER_PROCESS)
709 if (!strncmp(u->ut_user, user, UT_NAMESIZE))
710 showinfo(u, longform, maxcmd, from, userlen,
711 fromlen, ip_addresses, pids);
722 if (u->ut_type != USER_PROCESS)
725 showinfo(u, longform, maxcmd, from, userlen,
726 fromlen, ip_addresses, pids);