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
28 #include "fileutils.h"
30 #include "proc/devname.h"
31 #include "proc/escape.h"
32 #include "proc/procps.h"
33 #include "proc/readproc.h"
34 #include "proc/sysinfo.h"
35 #include "proc/version.h"
36 #include "proc/whattime.h"
51 #include <sys/ioctl.h>
54 #include <sys/types.h>
63 #include <arpa/inet.h>
65 static int ignoreuser = 0; /* for '-u' */
66 static int oldstyle = 0; /* for '-o' */
67 static proc_t **procs; /* our snapshot of the process table */
70 typedef struct utmpx utmp_t;
72 typedef struct utmp utmp_t;
75 #if !defined(UT_HOSTSIZE) || defined(__UT_HOSTSIZE)
76 # define UT_HOSTSIZE __UT_HOSTSIZE
77 # define UT_LINESIZE __UT_LINESIZE
78 # define UT_NAMESIZE __UT_NAMESIZE
82 # define FROM_STRING "on"
84 # define FROM_STRING "off"
87 #define MAX_CMD_WIDTH 512
88 #define MIN_CMD_WIDTH 7
91 * This routine is careful since some programs leave utmp strings
92 * unprintable. Always outputs at least 16 chars padded with
93 * spaces on the right if necessary.
95 static void print_host(const char *restrict host, int len, const int fromlen)
103 for (; host < last; host++) {
104 if (*host == '\0') break;
105 if (isprint(*host) && *host != ' ') {
106 fputc(*host, stdout);
116 * space-fill, and a '-' too if needed to ensure the
123 while (width++ < fromlen)
128 /* This routine prints the display part of the host or IPv6 link address interface */
129 static void print_display_or_interface(const char *restrict host, int len, int restlen)
131 const char *const end = host + (len > 0 ? len : 0);
132 const char *disp, *tmp;
134 if (restlen <= 0) return; /* not enough space for printing anything */
136 /* search for a collon (might be a display) */
138 while ( (disp < end) && (*disp != ':') && isprint(*disp) ) disp++;
141 if (disp < end && *disp == ':') {
142 /* detect multiple colons -> IPv6 in the host (not a display) */
144 while ( (tmp < end) && (*tmp != ':') && isprint(*tmp) ) tmp++;
146 if (tmp >= end || *tmp != ':') { /* multiple colons not found - it's a display */
148 /* number of chars till the end of the input field */
149 len -= (disp - host);
151 /* if it is still longer than the rest of the output field, then cut it */
152 if (len > restlen) len = restlen;
154 /* print the display */
155 while ((len > 0) && isprint(*disp) && (*disp != ' ')) {
157 fputc(*disp, stdout);
161 if ((len > 0) && (*disp != '\0')) { /* space or nonprintable found - replace with dash and stop printing */
165 } else { /* multiple colons found - it's an IPv6 address */
167 /* search for % (interface separator in case of IPv6 link address) */
168 while ( (tmp < end) && (*tmp != '%') && isprint(*tmp) ) tmp++;
170 if (tmp < end && *tmp == '%') { /* interface separator found */
172 /* number of chars till the end of the input field */
175 /* if it is still longer than the rest of the output field, then cut it */
176 if (len > restlen) len = restlen;
178 /* print the interface */
179 while ((len > 0) && isprint(*tmp) && (*tmp != ' ')) {
184 if ((len > 0) && (*tmp != '\0')) { /* space or nonprintable found - replace with dash and stop printing */
192 /* padding with spaces */
193 while (restlen > 0) {
200 /* This routine prints either the hostname or the IP address of the remote */
201 static void print_from(const utmp_t *restrict const u, const int ip_addresses, const int fromlen) {
202 char buf[fromlen + 1];
203 char buf_ipv6[INET6_ADDRSTRLEN];
206 int32_t ut_addr_v6[4]; /* IP address of the remote host */
208 if (ip_addresses) { /* -i switch used */
209 memcpy(&ut_addr_v6, &u->ut_addr_v6, sizeof(ut_addr_v6));
210 if (IN6_IS_ADDR_V4MAPPED(&ut_addr_v6)) {
212 ut_addr_v6[0] = ut_addr_v6[3];
217 if (ut_addr_v6[1] || ut_addr_v6[2] || ut_addr_v6[3]) {
219 if (!inet_ntop(AF_INET6, &ut_addr_v6, buf_ipv6, sizeof(buf_ipv6))) {
220 strcpy(buf, ""); /* invalid address, clean the buffer */
222 strncpy(buf, buf_ipv6, fromlen); /* address valid, copy to buffer */
226 if (!(ut_addr_v6[0] && inet_ntop(AF_INET, &ut_addr_v6[0], buf, sizeof(buf)))) {
227 strcpy(buf, ""); /* invalid address, clean the buffer */
233 if (len) { /* IP address is non-empty, print it (and concatenate with display, if present) */
235 /* show the display part of the host or IPv6 link addr. interface, if present */
236 print_display_or_interface(u->ut_host, UT_HOSTSIZE, fromlen - len);
237 } else { /* IP address is empty, print the host instead */
238 print_host(u->ut_host, UT_HOSTSIZE, fromlen);
240 } else { /* -i switch NOT used */
241 print_host(u->ut_host, UT_HOSTSIZE, fromlen);
244 print_host(u->ut_host, UT_HOSTSIZE, fromlen);
249 /* compact 7 char format for time intervals (belongs in libproc?) */
250 static void print_time_ival7(time_t t, int centi_sec, FILE * fout)
252 if ((long)t < (long)0) {
253 /* system clock changed? */
254 fprintf(fout, " ? ");
258 if (t >= 48 * 60 * 60)
260 fprintf(fout, _(" %2ludays"), t / (24 * 60 * 60));
261 else if (t >= 60 * 60)
263 /* Translation Hint: Hours:Minutes */
264 fprintf(fout, " %2lu:%02u ", t / (60 * 60),
265 (unsigned)((t / 60) % 60));
268 /* Translation Hint: Minutes:Seconds */
269 fprintf(fout, _(" %2lu:%02um"), t / 60, (unsigned)t % 60);
273 if (t >= 48 * 60 * 60)
275 fprintf(fout, _(" %2ludays"), t / (24 * 60 * 60));
276 else if (t >= 60 * 60)
278 /* Translation Hint: Hours:Minutes */
279 fprintf(fout, _(" %2lu:%02um"), t / (60 * 60),
280 (unsigned)((t / 60) % 60));
282 /* 1 minute or more */
283 /* Translation Hint: Minutes:Seconds */
284 fprintf(fout, " %2lu:%02u ", t / 60, (unsigned)t % 60);
286 /* Translation Hint: Seconds:Centiseconds */
287 fprintf(fout, _(" %2lu.%02us"), t, centi_sec);
291 /* stat the device file to get an idle time */
292 static time_t idletime(const char *restrict const tty)
295 if (stat(tty, &sbuf) != 0)
297 return time(NULL) - sbuf.st_atime;
300 /* 7 character formatted login time */
301 static void print_logintime(time_t logt, FILE * fout)
303 /* Abbreviated of weekday can be longer than 3 characters,
304 * see for instance hu_HU. Using 16 is few bytes more than
308 struct tm *logtm, *curtm;
312 if (curt == (time_t)(-1)) goto error;
313 curtm = localtime(&curt);
314 if (!curtm) goto error;
315 /* localtime returns a pointer to static memory */
316 today = curtm->tm_yday;
317 logtm = localtime(&logt);
318 if (!logtm) goto error;
319 if (curt - logt > 12 * 60 * 60 && logtm->tm_yday != today) {
320 if (curt - logt > 6 * 24 * 60 * 60) {
321 if (!strftime(time_str, sizeof(time_str), "%b", logtm)) goto error;
322 fprintf(fout, " %02d%3s%02d", logtm->tm_mday,
323 time_str, logtm->tm_year % 100);
325 if (!strftime(time_str, sizeof(time_str), "%a", logtm)) goto error;
326 fprintf(fout, " %3s%02d ", time_str,
330 fprintf(fout, " %02d:%02d ", logtm->tm_hour, logtm->tm_min);
334 fprintf(fout, " ???????");
338 * This function scans the process table accumulating total cpu
339 * times for any processes "associated" with this login session.
340 * It also searches for the "best" process to report as "(w)hat"
341 * the user for that login session is doing currently. This the
342 * essential core of 'w'.
344 static const proc_t *getproc(const utmp_t * restrict const u,
345 const char *restrict const tty,
346 unsigned long long *restrict const jcpu,
347 int *restrict const found_utpid)
350 proc_t **pptr = procs;
351 const proc_t *best = NULL;
352 const proc_t *secondbest = NULL;
357 char buf[UT_NAMESIZE + 1];
358 /* pointer to static data */
359 struct passwd *passwd_data;
360 strncpy(buf, u->ut_user, UT_NAMESIZE);
361 buf[UT_NAMESIZE] = '\0';
362 passwd_data = getpwnam(buf);
365 uid = passwd_data->pw_uid;
366 /* OK to have passwd_data go out of scope here */
368 line = tty_to_dev(tty);
370 for (; *pptr; pptr++) {
371 const proc_t *restrict const tmp = *pptr;
372 if (unlikely(tmp->tgid == u->ut_pid)) {
377 if (tmp->tty != line)
379 (*jcpu) += tmp->utime + tmp->stime;
381 /* same time-logic here as for "best" below */
382 if (!(secondbest && tmp->start_time <= secondbest->start_time)) {
385 if (!ignoreuser && uid != tmp->euid && uid != tmp->ruid)
387 if (tmp->pgrp != tmp->tpgid)
389 if (best && tmp->start_time <= best->start_time)
393 return best ? best : secondbest;
396 static void showinfo(utmp_t * u, int formtype, int maxcmd, int from,
397 const int userlen, const int fromlen, const int ip_addresses)
399 unsigned long long jcpu;
402 char uname[UT_NAMESIZE + 1] = "", tty[5 + UT_LINESIZE + 1] = "/dev/";
405 for (i = 0; i < UT_LINESIZE; i++)
406 /* clean up tty if garbled */
407 if (isalnum(u->ut_line[i]) || (u->ut_line[i] == '/'))
408 tty[i + 5] = u->ut_line[i];
412 best = getproc(u, tty + 5, &jcpu, &ut_pid_found);
415 * just skip if stale utmp entry (i.e. login proc doesn't
416 * exist). If there is a desire a cmdline flag could be
417 * added to optionally show it with a prefix of (stale)
418 * in front of cmd or something like that.
423 /* force NUL term for printf */
424 strncpy(uname, u->ut_user, UT_NAMESIZE);
427 printf("%-*.*s%-9.8s", userlen + 1, userlen, uname, u->ut_line);
429 print_from(u, ip_addresses, fromlen);
431 print_logintime(u->ut_tv.tv_sec, stdout);
433 print_logintime(u->ut_time, stdout);
435 if (*u->ut_line == ':')
436 /* idle unknown for xdm logins */
439 print_time_ival7(idletime(tty), 0, stdout);
440 print_time_ival7(jcpu / Hertz, (jcpu % Hertz) * (100. / Hertz),
443 unsigned long long pcpu = best->utime + best->stime;
444 print_time_ival7(pcpu / Hertz,
445 (pcpu % Hertz) * (100. / Hertz),
450 printf("%-*.*s%-9.8s", userlen + 1, userlen, u->ut_user,
453 print_from(u, ip_addresses, fromlen);
454 if (*u->ut_line == ':')
455 /* idle unknown for xdm logins */
458 print_time_ival7(idletime(tty), 0, stdout);
462 char cmdbuf[MAX_CMD_WIDTH];
463 escape_command(cmdbuf, best, sizeof cmdbuf, &maxcmd, ESC_ARGS);
464 fputs(cmdbuf, stdout);
471 static void __attribute__ ((__noreturn__))
474 fputs(USAGE_HEADER, out);
476 _(" %s [options]\n"), program_invocation_short_name);
477 fputs(USAGE_OPTIONS, out);
478 fputs(_(" -h, --no-header do not print header\n"),out);
479 fputs(_(" -u, --no-current ignore current process username\n"),out);
480 fputs(_(" -s, --short short format\n"),out);
481 fputs(_(" -f, --from show remote hostname field\n"),out);
482 fputs(_(" -o, --old-style old style output\n"),out);
483 fputs(_(" -i, --ip-addr display IP address instead of hostname (if possible)\n"), out);
484 fputs(USAGE_SEPARATOR, out);
485 fputs(_(" --help display this help and exit\n"), out);
486 fputs(USAGE_VERSION, out);
487 fprintf(out, USAGE_MAN_TAIL("w(1)"));
489 exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
492 int main(int argc, char **argv)
494 char *user = NULL, *p;
503 /* switches (defaults) */
507 int ip_addresses = 0;
510 HELP_OPTION = CHAR_MAX + 1
513 static const struct option longopts[] = {
514 {"no-header", no_argument, NULL, 'h'},
515 {"no-current", no_argument, NULL, 'u'},
516 {"short", no_argument, NULL, 's'},
517 {"from", no_argument, NULL, 'f'},
518 {"old-style", no_argument, NULL, 'o'},
519 {"ip-addr", no_argument, NULL, 'i'},
520 {"help", no_argument, NULL, HELP_OPTION},
521 {"version", no_argument, NULL, 'V'},
525 #ifdef HAVE_PROGRAM_INVOCATION_NAME
526 program_invocation_name = program_invocation_short_name;
528 setlocale (LC_ALL, "");
529 bindtextdomain(PACKAGE, LOCALEDIR);
531 atexit(close_stdout);
538 getopt_long(argc, argv, "husfoVi", longopts, NULL)) != -1)
553 printf(PROCPS_NG_VERSION);
572 user = (argv[optind]);
574 /* Get user field length from environment */
575 if ((env_var = getenv("PROCPS_USERLEN")) != NULL) {
576 int ut_namesize = UT_NAMESIZE;
577 userlen = atoi(env_var);
578 if (userlen < 8 || ut_namesize < userlen) {
580 (_("User length environment PROCPS_USERLEN must be between 8 and %i, ignoring.\n"),
585 /* Get from field length from environment */
586 if ((env_var = getenv("PROCPS_FROMLEN")) != NULL) {
587 fromlen = atoi(env_var);
588 if (fromlen < 8 || UT_HOSTSIZE < fromlen) {
590 (_("from length environment PROCPS_FROMLEN must be between 8 and %d, ignoring\n"),
595 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) != -1 && win.ws_col > 0)
597 else if ((p = getenv("COLUMNS")))
600 maxcmd = MAX_CMD_WIDTH;
601 #define CLAMP_CMD_WIDTH(cw) do { \
602 if ((cw) < MIN_CMD_WIDTH) (cw) = MIN_CMD_WIDTH; \
603 if ((cw) > MAX_CMD_WIDTH) (cw) = MAX_CMD_WIDTH; \
605 CLAMP_CMD_WIDTH(maxcmd);
606 maxcmd -= 21 + userlen + (from ? fromlen : 0) + (longform ? 20 : 0);
607 CLAMP_CMD_WIDTH(maxcmd);
608 #undef CLAMP_CMD_WIDTH
610 procs = readproctab(PROC_FILLCOM | PROC_FILLUSR | PROC_FILLSTAT);
613 /* print uptime and headers */
615 /* Translation Hint: Following five uppercase messages are
616 * headers. Try to keep alignment intact. */
617 printf(_("%-*s TTY "), userlen, _("USER"));
619 printf("%-*s", fromlen - 1, _("FROM"));
621 printf(_(" LOGIN@ IDLE JCPU PCPU WHAT\n"));
623 printf(_(" IDLE WHAT\n"));
641 if (u->ut_type != USER_PROCESS)
643 if (!strncmp(u->ut_user, user, UT_NAMESIZE))
644 showinfo(u, longform, maxcmd, from, userlen,
645 fromlen, ip_addresses);
656 if (u->ut_type != USER_PROCESS)
659 showinfo(u, longform, maxcmd, from, userlen,
660 fromlen, ip_addresses);