Imported Upstream version 3.3.11
[platform/upstream/procps-ng.git] / w.c
1 /*
2  * w - show what logged in users are doing.
3  *
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
7  * Michael K. Johnson.
8  *
9  * Changes by Albert Cahalan, 2002.
10  *
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.
15  *
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.
20  *
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
24  */
25
26 #include "c.h"
27 #include "fileutils.h"
28 #include "nls.h"
29 #include "proc/devname.h"
30 #include "proc/escape.h"
31 #include "proc/procps.h"
32 #include "proc/readproc.h"
33 #include "proc/sysinfo.h"
34 #include "proc/version.h"
35 #include "proc/whattime.h"
36
37 #include <ctype.h>
38 #include <errno.h>
39 #include <fcntl.h>
40 #include <getopt.h>
41 #include <limits.h>
42 #include <locale.h>
43 #include <locale.h>
44 #include <pwd.h>
45 #include <pwd.h>
46 #include <signal.h>
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <string.h>
50 #include <sys/ioctl.h>
51 #include <sys/mman.h>
52 #include <sys/stat.h>
53 #include <sys/types.h>
54 #include <termios.h>
55 #include <time.h>
56 #include <unistd.h>
57 #include <utmp.h>
58 #include <arpa/inet.h>
59
60 static int ignoreuser = 0;      /* for '-u' */
61 static int oldstyle = 0;        /* for '-o' */
62 static proc_t **procs;          /* our snapshot of the process table */
63
64 typedef struct utmp utmp_t;
65
66 #ifdef W_SHOWFROM
67 # define FROM_STRING "on"
68 #else
69 # define FROM_STRING "off"
70 #endif
71
72 #define MAX_CMD_WIDTH   512
73 #define MIN_CMD_WIDTH   7
74
75 /*
76  * This routine is careful since some programs leave utmp strings
77  * unprintable. Always outputs at least 16 chars padded with
78  * spaces on the right if necessary.
79  */
80 static void print_host(const char *restrict host, int len, const int fromlen)
81 {
82         const char *last;
83         int width = 0;
84
85         if (len > fromlen)
86                 len = fromlen;
87         last = host + len;
88         for (; host < last; host++) {
89                 if (*host == '\0') break;
90                 if (isprint(*host) && *host != ' ') {
91                         fputc(*host, stdout);
92                         ++width;
93                 } else {
94                         fputc('-', stdout);
95                         ++width;
96                         break;
97                 }
98         }
99
100         /*
101          * space-fill, and a '-' too if needed to ensure the
102          * column exists
103          */
104         if (!width) {
105                 fputc('-', stdout);
106                 ++width;
107         }
108         while (width++ < fromlen)
109                 fputc(' ', stdout);
110 }
111
112
113 /* This routine prints the display part of the host or IPv6 link address interface */
114 static void print_display_or_interface(const char *restrict host, int len, int restlen)
115 {
116         char *disp,*tmp;
117
118         if (restlen <= 0) return; /* not enough space for printing anything */
119
120         /* search for a collon (might be a display) */
121         disp = (char *)host;
122         while ( (disp < (host + len)) && (*disp != ':') && isprint(*disp) ) disp++;
123
124         /* colon found */
125         if (*disp == ':') {
126                 /* detect multiple colons -> IPv6 in the host (not a display) */
127                 tmp = disp+1;
128                 while ( (tmp < (host + len)) && (*tmp != ':') && isprint(*tmp) ) tmp++;
129
130                 if (*tmp != ':') { /* multiple colons not found - it's a display */
131
132                         /* number of chars till the end of the input field */
133                         len -= (disp - host);
134
135                         /* if it is still longer than the rest of the output field, then cut it */
136                         if (len > restlen) len = restlen;
137
138                         /* print the display */
139                         while ((len > 0) && isprint(*disp) && (*disp != ' ')) {
140                                 len--; restlen--;
141                                 fputc(*disp, stdout);
142                                 disp++;
143                         }
144
145                         if ((len > 0) && (*disp != '\0')) { /* space or nonprintable found - replace with dash and stop printing */
146                                 restlen--;
147                                 fputc('-', stdout);
148                         }
149                 } else { /* multiple colons found - it's an IPv6 address */
150
151                         /* search for % (interface separator in case of IPv6 link address) */
152                         while ( (tmp < (host + len)) && (*tmp != '%') && isprint(*tmp) ) tmp++;
153
154                         if (*tmp == '%') { /* interface separator found */
155
156                                 /* number of chars till the end of the input field */
157                                 len -= (tmp - host);
158
159                                 /* if it is still longer than the rest of the output field, then cut it */
160                                 if (len > restlen) len = restlen;
161
162                                 /* print the interface */
163                                 while ((len > 0) && isprint(*tmp) && (*tmp != ' ')) {
164                                         len--; restlen--;
165                                         fputc(*tmp, stdout);
166                                         tmp++;
167                                 }
168                                 if ((len > 0) && (*tmp != '\0')) {  /* space or nonprintable found - replace with dash and stop printing */
169                                         restlen--;
170                                         fputc('-', stdout);
171                                 }
172                         }
173
174                 }
175         }
176
177         /* padding with spaces */
178         while (restlen > 0) {
179                 fputc(' ', stdout);
180                 restlen--;
181         }
182 }
183
184
185 /* This routine prints either the hostname or the IP address of the remote */
186 static void print_from(const utmp_t *restrict const u, const int ip_addresses, const int fromlen) {
187         char buf[fromlen + 1];
188         char buf_ipv6[INET6_ADDRSTRLEN];
189         int len;
190         int32_t ut_addr_v6[4];      /* IP address of the remote host */
191
192         if (ip_addresses) { /* -i switch used */
193                 memcpy(&ut_addr_v6, &u->ut_addr_v6, sizeof(ut_addr_v6));
194                 if (IN6_IS_ADDR_V4MAPPED(&ut_addr_v6)) {
195                         /* map back */
196                         ut_addr_v6[0] = ut_addr_v6[3];
197                         ut_addr_v6[1] = 0;
198                         ut_addr_v6[2] = 0;
199                         ut_addr_v6[3] = 0;
200                 }
201                 if (ut_addr_v6[1] || ut_addr_v6[2] || ut_addr_v6[3]) {
202                         /* IPv6 */
203                         if (!inet_ntop(AF_INET6, &ut_addr_v6, buf_ipv6, sizeof(buf_ipv6))) {
204                                 strcpy(buf, ""); /* invalid address, clean the buffer */
205                         } else {
206                                 strncpy(buf, buf_ipv6, fromlen); /* address valid, copy to buffer */
207                         }
208                 } else {
209                         /* IPv4 */
210                         if (!(ut_addr_v6[0] && inet_ntop(AF_INET, &ut_addr_v6[0], buf, sizeof(buf)))) {
211                                 strcpy(buf, ""); /* invalid address, clean the buffer */
212                         }
213                 }
214                 buf[fromlen] = '\0';
215
216                 len = strlen(buf);
217                 if (len) { /* IP address is non-empty, print it (and concatenate with display, if present) */
218                         fputs(buf, stdout);
219                         /* show the display part of the host or IPv6 link addr. interface, if present */
220                         print_display_or_interface(u->ut_host, UT_HOSTSIZE, fromlen - len);
221                 } else { /* IP address is empty, print the host instead */
222                         print_host(u->ut_host, UT_HOSTSIZE, fromlen);
223                 }
224         } else {  /* -i switch NOT used */
225                 print_host(u->ut_host, UT_HOSTSIZE, fromlen);
226         }
227 }
228
229
230 /* compact 7 char format for time intervals (belongs in libproc?) */
231 static void print_time_ival7(time_t t, int centi_sec, FILE * fout)
232 {
233         if ((long)t < (long)0) {
234                 /* system clock changed? */
235                 printf("   ?   ");
236                 return;
237         }
238         if (oldstyle) {
239                 if (t >= 48 * 60 * 60)
240                         /* > 2 days */
241                         fprintf(fout, _(" %2ludays"), t / (24 * 60 * 60));
242                 else if (t >= 60 * 60)
243                         /* > 1 hour */
244                         /* Translation Hint: Hours:Minutes */
245                         fprintf(fout, " %2lu:%02u ", t / (60 * 60),
246                                 (unsigned)((t / 60) % 60));
247                 else if (t > 60)
248                         /* > 1 minute */
249                         /* Translation Hint: Minutes:Seconds */
250                         fprintf(fout, _(" %2lu:%02um"), t / 60, (unsigned)t % 60);
251                 else
252                         fprintf(fout, "       ");
253         } else {
254                 if (t >= 48 * 60 * 60)
255                         /* 2 days or more */
256                         fprintf(fout, _(" %2ludays"), t / (24 * 60 * 60));
257                 else if (t >= 60 * 60)
258                         /* 1 hour or more */
259                         /* Translation Hint: Hours:Minutes */
260                         fprintf(fout, _(" %2lu:%02um"), t / (60 * 60),
261                                 (unsigned)((t / 60) % 60));
262                 else if (t > 60)
263                         /* 1 minute or more */
264                         /* Translation Hint: Minutes:Seconds */
265                         fprintf(fout, " %2lu:%02u ", t / 60, (unsigned)t % 60);
266                 else
267                         /* Translation Hint: Seconds:Centiseconds */
268                         fprintf(fout, _(" %2lu.%02us"), t, centi_sec);
269         }
270 }
271
272 /* stat the device file to get an idle time */
273 static time_t idletime(const char *restrict const tty)
274 {
275         struct stat sbuf;
276         if (stat(tty, &sbuf) != 0)
277                 return 0;
278         return time(NULL) - sbuf.st_atime;
279 }
280
281 /* 7 character formatted login time */
282
283 static void print_logintime(time_t logt, FILE * fout)
284 {
285
286         /* Abbreviated of weekday can be longer than 3 characters,
287          * see for instance hu_HU.  Using 16 is few bytes more than
288          * enough.  */
289         char time_str[16];
290         time_t curt;
291         struct tm *logtm, *curtm;
292         int today;
293
294         curt = time(NULL);
295         curtm = localtime(&curt);
296         /* localtime returns a pointer to static memory */
297         today = curtm->tm_yday;
298         logtm = localtime(&logt);
299         if (curt - logt > 12 * 60 * 60 && logtm->tm_yday != today) {
300                 if (curt - logt > 6 * 24 * 60 * 60) {
301                         strftime(time_str, sizeof(time_str), "%b", logtm);
302                         fprintf(fout, " %02d%3s%02d", logtm->tm_mday,
303                                 time_str, logtm->tm_year % 100);
304                 } else {
305                         strftime(time_str, sizeof(time_str), "%a", logtm);
306                         fprintf(fout, " %3s%02d  ", time_str,
307                                 logtm->tm_hour);
308                 }
309         } else {
310                 fprintf(fout, " %02d:%02d  ", logtm->tm_hour, logtm->tm_min);
311         }
312 }
313
314 /*
315  * This function scans the process table accumulating total cpu
316  * times for any processes "associated" with this login session.
317  * It also searches for the "best" process to report as "(w)hat"
318  * the user for that login session is doing currently. This the
319  * essential core of 'w'.
320  */
321 static const proc_t *getproc(const utmp_t * restrict const u,
322                              const char *restrict const tty,
323                              unsigned long long *restrict const jcpu,
324                              int *restrict const found_utpid)
325 {
326         int line;
327         proc_t **pptr = procs;
328         const proc_t *best = NULL;
329         const proc_t *secondbest = NULL;
330         unsigned uid = ~0U;
331
332         *found_utpid = 0;
333         if (!ignoreuser) {
334                 char buf[UT_NAMESIZE + 1];
335                 /* pointer to static data */
336                 struct passwd *passwd_data;
337                 strncpy(buf, u->ut_user, UT_NAMESIZE);
338                 buf[UT_NAMESIZE] = '\0';
339                 passwd_data = getpwnam(buf);
340                 if (!passwd_data)
341                         return NULL;
342                 uid = passwd_data->pw_uid;
343                 /* OK to have passwd_data go out of scope here */
344         }
345         line = tty_to_dev(tty);
346         *jcpu = 0;
347         for (; *pptr; pptr++) {
348                 const proc_t *restrict const tmp = *pptr;
349                 if (unlikely(tmp->tgid == u->ut_pid)) {
350                         *found_utpid = 1;
351             if (!best)
352                 best = tmp;
353                 }
354                 if (tmp->tty != line)
355                         continue;
356                 (*jcpu) += tmp->utime + tmp->stime;
357                 secondbest = tmp;
358                 /* same time-logic here as for "best" below */
359                 if (!(secondbest && tmp->start_time <= secondbest->start_time)) {
360                         secondbest = tmp;
361                 }
362                 if (!ignoreuser && uid != tmp->euid && uid != tmp->ruid)
363                         continue;
364                 if (tmp->pgrp != tmp->tpgid)
365                         continue;
366                 if (best && tmp->start_time <= best->start_time)
367                         continue;
368                 best = tmp;
369         }
370         return best ? best : secondbest;
371 }
372
373 static void showinfo(utmp_t * u, int formtype, int maxcmd, int from,
374                      const int userlen, const int fromlen, const int ip_addresses)
375 {
376         unsigned long long jcpu;
377         int ut_pid_found;
378         unsigned i;
379         char uname[UT_NAMESIZE + 1] = "", tty[5 + UT_LINESIZE + 1] = "/dev/";
380         const proc_t *best;
381
382         for (i = 0; i < UT_LINESIZE; i++)
383                 /* clean up tty if garbled */
384                 if (isalnum(u->ut_line[i]) || (u->ut_line[i] == '/'))
385                         tty[i + 5] = u->ut_line[i];
386                 else
387                         tty[i + 5] = '\0';
388
389         best = getproc(u, tty + 5, &jcpu, &ut_pid_found);
390
391         /*
392          * just skip if stale utmp entry (i.e. login proc doesn't
393          * exist). If there is a desire a cmdline flag could be
394          * added to optionally show it with a prefix of (stale)
395          * in front of cmd or something like that.
396          */
397         if (!ut_pid_found)
398                 return;
399
400         /* force NUL term for printf */
401         strncpy(uname, u->ut_user, UT_NAMESIZE);
402
403         if (formtype) {
404                 printf("%-*.*s%-9.8s", userlen + 1, userlen, uname, u->ut_line);
405                 if (from)
406                         print_from(u, ip_addresses, fromlen);
407                 print_logintime(u->ut_time, stdout);
408                 if (*u->ut_line == ':')
409                         /* idle unknown for xdm logins */
410                         printf(" ?xdm? ");
411                 else
412                         print_time_ival7(idletime(tty), 0, stdout);
413                 print_time_ival7(jcpu / Hertz, (jcpu % Hertz) * (100. / Hertz),
414                                  stdout);
415                 if (best) {
416                         unsigned long long pcpu = best->utime + best->stime;
417                         print_time_ival7(pcpu / Hertz,
418                                          (pcpu % Hertz) * (100. / Hertz),
419                                          stdout);
420                 } else
421                         printf("   ?   ");
422         } else {
423                 printf("%-*.*s%-9.8s", userlen + 1, userlen, u->ut_user,
424                        u->ut_line);
425                 if (from)
426                         print_from(u, ip_addresses, fromlen);
427                 if (*u->ut_line == ':')
428                         /* idle unknown for xdm logins */
429                         printf(" ?xdm? ");
430                 else
431                         print_time_ival7(idletime(tty), 0, stdout);
432         }
433         fputs(" ", stdout);
434         if (likely(best)) {
435                 char cmdbuf[MAX_CMD_WIDTH];
436                 escape_command(cmdbuf, best, sizeof cmdbuf, &maxcmd, ESC_ARGS);
437                 fputs(cmdbuf, stdout);
438         } else {
439                 printf("-");
440         }
441         fputc('\n', stdout);
442 }
443
444 static void __attribute__ ((__noreturn__))
445     usage(FILE * out)
446 {
447         fputs(USAGE_HEADER, out);
448         fprintf(out,
449               _(" %s [options]\n"), program_invocation_short_name);
450         fputs(USAGE_OPTIONS, out);
451         fputs(_(" -h, --no-header     do not print header\n"),out);
452         fputs(_(" -u, --no-current    ignore current process username\n"),out);
453         fputs(_(" -s, --short         short format\n"),out);
454         fputs(_(" -f, --from          show remote hostname field\n"),out);
455         fputs(_(" -o, --old-style     old style output\n"),out);
456         fputs(_(" -i, --ip-addr       display IP address instead of hostname (if possible)\n"), out);
457         fputs(USAGE_SEPARATOR, out);
458         fputs(_("     --help     display this help and exit\n"), out);
459         fputs(USAGE_VERSION, out);
460         fprintf(out, USAGE_MAN_TAIL("w(1)"));
461
462         exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
463 }
464
465 int main(int argc, char **argv)
466 {
467         char *user = NULL, *p;
468         utmp_t *u;
469         struct winsize win;
470         int ch;
471         int maxcmd = 80;
472         int userlen = 8;
473         int fromlen = 16;
474         char *env_var;
475
476         /* switches (defaults) */
477         int header = 1;
478         int longform = 1;
479         int from = 1;
480         int ip_addresses = 0;
481
482         enum {
483                 HELP_OPTION = CHAR_MAX + 1
484         };
485
486         static const struct option longopts[] = {
487                 {"no-header", no_argument, NULL, 'h'},
488                 {"no-current", no_argument, NULL, 'u'},
489                 {"short", no_argument, NULL, 's'},
490                 {"from", no_argument, NULL, 'f'},
491                 {"old-style", no_argument, NULL, 'o'},
492                 {"ip-addr", no_argument, NULL, 'i'},
493                 {"help", no_argument, NULL, HELP_OPTION},
494                 {"version", no_argument, NULL, 'V'},
495                 {NULL, 0, NULL, 0}
496         };
497
498 #ifdef HAVE_PROGRAM_INVOCATION_NAME
499         program_invocation_name = program_invocation_short_name;
500 #endif
501         setlocale (LC_ALL, "");
502         bindtextdomain(PACKAGE, LOCALEDIR);
503         textdomain(PACKAGE);
504         atexit(close_stdout);
505
506 #ifndef W_SHOWFROM
507         from = 0;
508 #endif
509
510         while ((ch =
511                 getopt_long(argc, argv, "husfoVi", longopts, NULL)) != -1)
512                 switch (ch) {
513                 case 'h':
514                         header = 0;
515                         break;
516                 case 'l':
517                         longform = 1;
518                         break;
519                 case 's':
520                         longform = 0;
521                         break;
522                 case 'f':
523                         from = !from;
524                         break;
525                 case 'V':
526                         printf(PROCPS_NG_VERSION);
527                         exit(0);
528                 case 'u':
529                         ignoreuser = 1;
530                         break;
531                 case 'o':
532                         oldstyle = 1;
533                         break;
534                 case 'i':
535                         ip_addresses = 1;
536                         from = 1;
537                         break;
538                 case HELP_OPTION:
539                         usage(stdout);
540                 default:
541                         usage(stderr);
542                 }
543
544         if ((argv[optind]))
545                 user = (argv[optind]);
546
547         /* Get user field length from environment */
548         if ((env_var = getenv("PROCPS_USERLEN")) != NULL) {
549                 int ut_namesize = UT_NAMESIZE;
550                 userlen = atoi(env_var);
551                 if (userlen < 8 || ut_namesize < userlen) {
552                         xwarnx
553                             (_("User length environment PROCPS_USERLEN must be between 8 and %i, ignoring.\n"),
554                              ut_namesize);
555                         userlen = 8;
556                 }
557         }
558         /* Get from field length from environment */
559         if ((env_var = getenv("PROCPS_FROMLEN")) != NULL) {
560                 fromlen = atoi(env_var);
561                 if (fromlen < 8 || UT_HOSTSIZE < fromlen) {
562                         xwarnx
563                             (_("from length environment PROCPS_FROMLEN must be between 8 and %d, ignoring\n"),
564                              UT_HOSTSIZE);
565                         fromlen = 16;
566                 }
567         }
568         if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) != -1 && win.ws_col > 0)
569                 maxcmd = win.ws_col;
570         else if ((p = getenv("COLUMNS")))
571                 maxcmd = atoi(p);
572         else
573                 maxcmd = MAX_CMD_WIDTH;
574         if (MAX_CMD_WIDTH < maxcmd)
575                 maxcmd = MAX_CMD_WIDTH;
576         maxcmd -= 21 + userlen + (from ? fromlen : 0) + (longform ? 20 : 0);
577         if (maxcmd < MIN_CMD_WIDTH)
578         maxcmd = MIN_CMD_WIDTH;
579
580         procs = readproctab(PROC_FILLCOM | PROC_FILLUSR | PROC_FILLSTAT);
581
582         if (header) {
583                 /* print uptime and headers */
584                 print_uptime(0);
585                 /* Translation Hint: Following five uppercase messages are
586                  * headers. Try to keep alignment intact.  */
587                 printf(_("%-*s TTY      "), userlen, _("USER"));
588                 if (from)
589                         printf("%-*s", fromlen - 1, _("FROM"));
590                 if (longform)
591                         printf(_("  LOGIN@   IDLE   JCPU   PCPU WHAT\n"));
592                 else
593                         printf(_("   IDLE WHAT\n"));
594         }
595
596         utmpname(UTMP_FILE);
597         setutent();
598         if (user) {
599                 for (;;) {
600                         u = getutent();
601                         if (unlikely(!u))
602                                 break;
603                         if (u->ut_type != USER_PROCESS)
604                                 continue;
605                         if (!strncmp(u->ut_user, user, UT_NAMESIZE))
606                                 showinfo(u, longform, maxcmd, from, userlen,
607                                          fromlen, ip_addresses);
608                 }
609         } else {
610                 for (;;) {
611                         u = getutent();
612                         if (unlikely(!u))
613                                 break;
614                         if (u->ut_type != USER_PROCESS)
615                                 continue;
616                         if (*u->ut_user)
617                                 showinfo(u, longform, maxcmd, from, userlen,
618                                          fromlen, ip_addresses);
619                 }
620         }
621         endutent();
622
623         return EXIT_SUCCESS;
624 }