procps/w.c

671 lines
17 KiB
C
Raw Normal View History

/*
* w - show what logged in users are doing.
*
* Almost entirely rewritten from scratch by Charles Blake circa
* June 1996. Some vestigal traces of the original may exist.
* That was done in 1993 by Larry Greenfield with some fixes by
* Michael K. Johnson.
*
* Changes by Albert Cahalan, 2002.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
2002-02-02 04:17:29 +05:30
*/
#include "config.h"
#include "c.h"
#include "fileutils.h"
#include "nls.h"
2002-02-02 04:17:29 +05:30
#include "proc/devname.h"
#include "proc/escape.h"
2002-02-02 04:17:29 +05:30
#include "proc/procps.h"
#include "proc/readproc.h"
2002-02-02 04:17:29 +05:30
#include "proc/sysinfo.h"
#include "proc/version.h"
#include "proc/whattime.h"
2002-02-02 04:17:29 +05:30
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <limits.h>
#include <locale.h>
#include <locale.h>
#include <pwd.h>
#include <pwd.h>
#include <signal.h>
2002-02-02 04:17:29 +05:30
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <termios.h>
2002-02-02 04:17:29 +05:30
#include <time.h>
#include <unistd.h>
#ifdef HAVE_UTMPX_H
# include <utmpx.h>
#else
# include <utmp.h>
#endif
#include <arpa/inet.h>
2002-02-02 04:17:29 +05:30
static int ignoreuser = 0; /* for '-u' */
static int oldstyle = 0; /* for '-o' */
2002-02-02 04:17:29 +05:30
static proc_t **procs; /* our snapshot of the process table */
#ifdef HAVE_UTMPX_H
typedef struct utmpx utmp_t;
#else
2002-02-02 04:17:29 +05:30
typedef struct utmp utmp_t;
#endif
#if !defined(UT_HOSTSIZE) || defined(__UT_HOSTSIZE)
# define UT_HOSTSIZE __UT_HOSTSIZE
# define UT_LINESIZE __UT_LINESIZE
# define UT_NAMESIZE __UT_NAMESIZE
#endif
2002-02-02 04:17:29 +05:30
#ifdef W_SHOWFROM
# define FROM_STRING "on"
2002-02-02 04:17:29 +05:30
#else
# define FROM_STRING "off"
2002-02-02 04:17:29 +05:30
#endif
#define MAX_CMD_WIDTH 512
#define MIN_CMD_WIDTH 7
/*
* This routine is careful since some programs leave utmp strings
* unprintable. Always outputs at least 16 chars padded with
* spaces on the right if necessary.
2002-02-02 04:17:29 +05:30
*/
static void print_host(const char *restrict host, int len, const int fromlen)
{
const char *last;
int width = 0;
if (len > fromlen)
len = fromlen;
last = host + len;
for (; host < last; host++) {
if (*host == '\0') break;
if (isprint(*host) && *host != ' ') {
fputc(*host, stdout);
++width;
} else {
fputc('-', stdout);
++width;
break;
}
2002-02-02 04:17:29 +05:30
}
/*
* space-fill, and a '-' too if needed to ensure the
* column exists
*/
if (!width) {
fputc('-', stdout);
++width;
}
while (width++ < fromlen)
fputc(' ', stdout);
2002-02-02 04:17:29 +05:30
}
/* This routine prints the display part of the host or IPv6 link address interface */
static void print_display_or_interface(const char *restrict host, int len, int restlen)
{
const char *const end = host + (len > 0 ? len : 0);
const char *disp, *tmp;
if (restlen <= 0) return; /* not enough space for printing anything */
/* search for a collon (might be a display) */
disp = host;
while ( (disp < end) && (*disp != ':') && isprint(*disp) ) disp++;
/* colon found */
if (disp < end && *disp == ':') {
/* detect multiple colons -> IPv6 in the host (not a display) */
tmp = disp+1;
while ( (tmp < end) && (*tmp != ':') && isprint(*tmp) ) tmp++;
if (tmp >= end || *tmp != ':') { /* multiple colons not found - it's a display */
/* number of chars till the end of the input field */
len -= (disp - host);
/* if it is still longer than the rest of the output field, then cut it */
if (len > restlen) len = restlen;
/* print the display */
while ((len > 0) && isprint(*disp) && (*disp != ' ')) {
len--; restlen--;
fputc(*disp, stdout);
disp++;
}
if ((len > 0) && (*disp != '\0')) { /* space or nonprintable found - replace with dash and stop printing */
restlen--;
fputc('-', stdout);
}
} else { /* multiple colons found - it's an IPv6 address */
/* search for % (interface separator in case of IPv6 link address) */
while ( (tmp < end) && (*tmp != '%') && isprint(*tmp) ) tmp++;
if (tmp < end && *tmp == '%') { /* interface separator found */
/* number of chars till the end of the input field */
len -= (tmp - host);
/* if it is still longer than the rest of the output field, then cut it */
if (len > restlen) len = restlen;
/* print the interface */
while ((len > 0) && isprint(*tmp) && (*tmp != ' ')) {
len--; restlen--;
fputc(*tmp, stdout);
tmp++;
}
if ((len > 0) && (*tmp != '\0')) { /* space or nonprintable found - replace with dash and stop printing */
restlen--;
fputc('-', stdout);
}
}
}
}
/* padding with spaces */
while (restlen > 0) {
fputc(' ', stdout);
restlen--;
}
}
/* This routine prints either the hostname or the IP address of the remote */
static void print_from(const utmp_t *restrict const u, const int ip_addresses, const int fromlen) {
char buf[fromlen + 1];
char buf_ipv6[INET6_ADDRSTRLEN];
int len;
#ifndef __CYGWIN__
int32_t ut_addr_v6[4]; /* IP address of the remote host */
if (ip_addresses) { /* -i switch used */
memcpy(&ut_addr_v6, &u->ut_addr_v6, sizeof(ut_addr_v6));
if (IN6_IS_ADDR_V4MAPPED(&ut_addr_v6)) {
/* map back */
ut_addr_v6[0] = ut_addr_v6[3];
ut_addr_v6[1] = 0;
ut_addr_v6[2] = 0;
ut_addr_v6[3] = 0;
}
if (ut_addr_v6[1] || ut_addr_v6[2] || ut_addr_v6[3]) {
/* IPv6 */
if (!inet_ntop(AF_INET6, &ut_addr_v6, buf_ipv6, sizeof(buf_ipv6))) {
strcpy(buf, ""); /* invalid address, clean the buffer */
} else {
strncpy(buf, buf_ipv6, fromlen); /* address valid, copy to buffer */
}
} else {
/* IPv4 */
if (!(ut_addr_v6[0] && inet_ntop(AF_INET, &ut_addr_v6[0], buf, sizeof(buf)))) {
strcpy(buf, ""); /* invalid address, clean the buffer */
}
}
buf[fromlen] = '\0';
len = strlen(buf);
if (len) { /* IP address is non-empty, print it (and concatenate with display, if present) */
fputs(buf, stdout);
/* show the display part of the host or IPv6 link addr. interface, if present */
print_display_or_interface(u->ut_host, UT_HOSTSIZE, fromlen - len);
} else { /* IP address is empty, print the host instead */
print_host(u->ut_host, UT_HOSTSIZE, fromlen);
}
} else { /* -i switch NOT used */
print_host(u->ut_host, UT_HOSTSIZE, fromlen);
}
#else
print_host(u->ut_host, UT_HOSTSIZE, fromlen);
#endif
}
/* compact 7 char format for time intervals (belongs in libproc?) */
static void print_time_ival7(time_t t, int centi_sec, FILE * fout)
{
if ((long)t < (long)0) {
/* system clock changed? */
fprintf(fout, " ? ");
return;
}
if (oldstyle) {
if (t >= 48 * 60 * 60)
/* > 2 days */
fprintf(fout, _(" %2ludays"), t / (24 * 60 * 60));
else if (t >= 60 * 60)
/* > 1 hour */
/* Translation Hint: Hours:Minutes */
fprintf(fout, " %2lu:%02u ", t / (60 * 60),
(unsigned)((t / 60) % 60));
else if (t > 60)
/* > 1 minute */
/* Translation Hint: Minutes:Seconds */
fprintf(fout, _(" %2lu:%02um"), t / 60, (unsigned)t % 60);
else
fprintf(fout, " ");
} else {
if (t >= 48 * 60 * 60)
/* 2 days or more */
fprintf(fout, _(" %2ludays"), t / (24 * 60 * 60));
else if (t >= 60 * 60)
/* 1 hour or more */
/* Translation Hint: Hours:Minutes */
fprintf(fout, _(" %2lu:%02um"), t / (60 * 60),
(unsigned)((t / 60) % 60));
else if (t > 60)
/* 1 minute or more */
/* Translation Hint: Minutes:Seconds */
fprintf(fout, " %2lu:%02u ", t / 60, (unsigned)t % 60);
else
/* Translation Hint: Seconds:Centiseconds */
fprintf(fout, _(" %2lu.%02us"), t, centi_sec);
}
2002-02-02 04:17:29 +05:30
}
/* stat the device file to get an idle time */
static time_t idletime(const char *restrict const tty)
{
struct stat sbuf;
if (stat(tty, &sbuf) != 0)
return 0;
return time(NULL) - sbuf.st_atime;
2002-02-02 04:17:29 +05:30
}
/* 7 character formatted login time */
static void print_logintime(time_t logt, FILE * fout)
{
/* Abbreviated of weekday can be longer than 3 characters,
* see for instance hu_HU. Using 16 is few bytes more than
* enough. */
char time_str[16];
time_t curt;
struct tm *logtm, *curtm;
int today;
2002-02-02 04:17:29 +05:30
curt = time(NULL);
if (curt == (time_t)(-1)) goto error;
curtm = localtime(&curt);
if (!curtm) goto error;
/* localtime returns a pointer to static memory */
today = curtm->tm_yday;
logtm = localtime(&logt);
if (!logtm) goto error;
if (curt - logt > 12 * 60 * 60 && logtm->tm_yday != today) {
if (curt - logt > 6 * 24 * 60 * 60) {
if (!strftime(time_str, sizeof(time_str), "%b", logtm)) goto error;
fprintf(fout, " %02d%3s%02d", logtm->tm_mday,
time_str, logtm->tm_year % 100);
} else {
if (!strftime(time_str, sizeof(time_str), "%a", logtm)) goto error;
fprintf(fout, " %3s%02d ", time_str,
logtm->tm_hour);
}
} else {
fprintf(fout, " %02d:%02d ", logtm->tm_hour, logtm->tm_min);
}
return;
error:
fprintf(fout, " ???????");
}
/*
* This function scans the process table accumulating total cpu
* times for any processes "associated" with this login session.
* It also searches for the "best" process to report as "(w)hat"
* the user for that login session is doing currently. This the
* essential core of 'w'.
2002-02-02 04:17:29 +05:30
*/
static const proc_t *getproc(const utmp_t * restrict const u,
const char *restrict const tty,
unsigned long long *restrict const jcpu,
int *restrict const found_utpid)
{
int line;
proc_t **pptr = procs;
const proc_t *best = NULL;
const proc_t *secondbest = NULL;
unsigned uid = ~0U;
*found_utpid = 0;
if (!ignoreuser) {
char buf[UT_NAMESIZE + 1];
/* pointer to static data */
struct passwd *passwd_data;
strncpy(buf, u->ut_user, UT_NAMESIZE);
buf[UT_NAMESIZE] = '\0';
passwd_data = getpwnam(buf);
if (!passwd_data)
return NULL;
uid = passwd_data->pw_uid;
/* OK to have passwd_data go out of scope here */
}
line = tty_to_dev(tty);
*jcpu = 0;
for (; *pptr; pptr++) {
const proc_t *restrict const tmp = *pptr;
if (unlikely(tmp->tgid == u->ut_pid)) {
*found_utpid = 1;
if (!best)
best = tmp;
}
if (tmp->tty != line)
continue;
(*jcpu) += tmp->utime + tmp->stime;
secondbest = tmp;
/* same time-logic here as for "best" below */
if (!(secondbest && tmp->start_time <= secondbest->start_time)) {
secondbest = tmp;
}
if (!ignoreuser && uid != tmp->euid && uid != tmp->ruid)
continue;
if (tmp->pgrp != tmp->tpgid)
continue;
if (best && tmp->start_time <= best->start_time)
continue;
best = tmp;
}
return best ? best : secondbest;
2002-02-02 04:17:29 +05:30
}
static void showinfo(utmp_t * u, int formtype, int maxcmd, int from,
const int userlen, const int fromlen, const int ip_addresses)
{
unsigned long long jcpu;
int ut_pid_found;
unsigned i;
char uname[UT_NAMESIZE + 1] = "", tty[5 + UT_LINESIZE + 1] = "/dev/";
const proc_t *best;
2002-02-02 04:17:29 +05:30
for (i = 0; i < UT_LINESIZE; i++)
/* clean up tty if garbled */
if (isalnum(u->ut_line[i]) || (u->ut_line[i] == '/'))
tty[i + 5] = u->ut_line[i];
else
tty[i + 5] = '\0';
2002-02-02 04:17:29 +05:30
best = getproc(u, tty + 5, &jcpu, &ut_pid_found);
/*
* just skip if stale utmp entry (i.e. login proc doesn't
* exist). If there is a desire a cmdline flag could be
* added to optionally show it with a prefix of (stale)
* in front of cmd or something like that.
*/
if (!ut_pid_found)
return;
/* force NUL term for printf */
strncpy(uname, u->ut_user, UT_NAMESIZE);
if (formtype) {
printf("%-*.*s%-9.8s", userlen + 1, userlen, uname, u->ut_line);
if (from)
print_from(u, ip_addresses, fromlen);
#ifdef HAVE_UTMPX_H
print_logintime(u->ut_tv.tv_sec, stdout);
#else
print_logintime(u->ut_time, stdout);
#endif
if (*u->ut_line == ':')
/* idle unknown for xdm logins */
printf(" ?xdm? ");
else
print_time_ival7(idletime(tty), 0, stdout);
print_time_ival7(jcpu / Hertz, (jcpu % Hertz) * (100. / Hertz),
stdout);
if (best) {
unsigned long long pcpu = best->utime + best->stime;
print_time_ival7(pcpu / Hertz,
(pcpu % Hertz) * (100. / Hertz),
stdout);
} else
printf(" ? ");
} else {
printf("%-*.*s%-9.8s", userlen + 1, userlen, u->ut_user,
u->ut_line);
if (from)
print_from(u, ip_addresses, fromlen);
if (*u->ut_line == ':')
/* idle unknown for xdm logins */
printf(" ?xdm? ");
else
print_time_ival7(idletime(tty), 0, stdout);
}
fputs(" ", stdout);
if (likely(best)) {
char cmdbuf[MAX_CMD_WIDTH];
escape_command(cmdbuf, best, sizeof cmdbuf, &maxcmd, ESC_ARGS);
fputs(cmdbuf, stdout);
} else {
printf("-");
}
fputc('\n', stdout);
}
static void __attribute__ ((__noreturn__))
usage(FILE * out)
{
fputs(USAGE_HEADER, out);
fprintf(out,
_(" %s [options]\n"), program_invocation_short_name);
fputs(USAGE_OPTIONS, out);
fputs(_(" -h, --no-header do not print header\n"),out);
fputs(_(" -u, --no-current ignore current process username\n"),out);
fputs(_(" -s, --short short format\n"),out);
fputs(_(" -f, --from show remote hostname field\n"),out);
fputs(_(" -o, --old-style old style output\n"),out);
fputs(_(" -i, --ip-addr display IP address instead of hostname (if possible)\n"), out);
fputs(USAGE_SEPARATOR, out);
fputs(_(" --help display this help and exit\n"), out);
fputs(USAGE_VERSION, out);
fprintf(out, USAGE_MAN_TAIL("w(1)"));
exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
2002-02-02 04:17:29 +05:30
}
int main(int argc, char **argv)
{
char *user = NULL, *p;
utmp_t *u;
struct winsize win;
int ch;
int maxcmd = 80;
int userlen = 8;
int fromlen = 16;
char *env_var;
/* switches (defaults) */
int header = 1;
int longform = 1;
int from = 1;
int ip_addresses = 0;
enum {
HELP_OPTION = CHAR_MAX + 1
};
static const struct option longopts[] = {
{"no-header", no_argument, NULL, 'h'},
{"no-current", no_argument, NULL, 'u'},
{"short", no_argument, NULL, 's'},
{"from", no_argument, NULL, 'f'},
{"old-style", no_argument, NULL, 'o'},
{"ip-addr", no_argument, NULL, 'i'},
{"help", no_argument, NULL, HELP_OPTION},
{"version", no_argument, NULL, 'V'},
{NULL, 0, NULL, 0}
};
2002-02-02 04:17:29 +05:30
#ifdef HAVE_PROGRAM_INVOCATION_NAME
program_invocation_name = program_invocation_short_name;
#endif
setlocale (LC_ALL, "");
bindtextdomain(PACKAGE, LOCALEDIR);
textdomain(PACKAGE);
atexit(close_stdout);
2002-02-02 04:17:29 +05:30
#ifndef W_SHOWFROM
from = 0;
2002-02-02 04:17:29 +05:30
#endif
while ((ch =
getopt_long(argc, argv, "husfoVi", longopts, NULL)) != -1)
switch (ch) {
case 'h':
header = 0;
break;
case 'l':
longform = 1;
break;
case 's':
longform = 0;
break;
case 'f':
from = !from;
break;
case 'V':
printf(PROCPS_NG_VERSION);
exit(0);
case 'u':
ignoreuser = 1;
break;
case 'o':
oldstyle = 1;
break;
case 'i':
ip_addresses = 1;
from = 1;
break;
case HELP_OPTION:
usage(stdout);
default:
usage(stderr);
}
2002-02-02 04:17:29 +05:30
if ((argv[optind]))
user = (argv[optind]);
2002-02-02 04:17:29 +05:30
/* Get user field length from environment */
if ((env_var = getenv("PROCPS_USERLEN")) != NULL) {
int ut_namesize = UT_NAMESIZE;
userlen = atoi(env_var);
if (userlen < 8 || ut_namesize < userlen) {
xwarnx
(_("User length environment PROCPS_USERLEN must be between 8 and %i, ignoring.\n"),
ut_namesize);
userlen = 8;
}
}
/* Get from field length from environment */
if ((env_var = getenv("PROCPS_FROMLEN")) != NULL) {
fromlen = atoi(env_var);
if (fromlen < 8 || UT_HOSTSIZE < fromlen) {
xwarnx
(_("from length environment PROCPS_FROMLEN must be between 8 and %d, ignoring\n"),
UT_HOSTSIZE);
fromlen = 16;
}
}
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) != -1 && win.ws_col > 0)
maxcmd = win.ws_col;
else if ((p = getenv("COLUMNS")))
maxcmd = atoi(p);
2002-02-02 04:17:29 +05:30
else
maxcmd = MAX_CMD_WIDTH;
#define CLAMP_CMD_WIDTH(cw) do { \
if ((cw) < MIN_CMD_WIDTH) (cw) = MIN_CMD_WIDTH; \
if ((cw) > MAX_CMD_WIDTH) (cw) = MAX_CMD_WIDTH; \
} while (0)
CLAMP_CMD_WIDTH(maxcmd);
maxcmd -= 21 + userlen + (from ? fromlen : 0) + (longform ? 20 : 0);
CLAMP_CMD_WIDTH(maxcmd);
#undef CLAMP_CMD_WIDTH
procs = readproctab(PROC_FILLCOM | PROC_FILLUSR | PROC_FILLSTAT);
if (header) {
/* print uptime and headers */
print_uptime(0);
/* Translation Hint: Following five uppercase messages are
* headers. Try to keep alignment intact. */
printf(_("%-*s TTY "), userlen, _("USER"));
if (from)
printf("%-*s", fromlen - 1, _("FROM"));
if (longform)
printf(_(" LOGIN@ IDLE JCPU PCPU WHAT\n"));
else
printf(_(" IDLE WHAT\n"));
2002-12-09 12:30:07 +05:30
}
#ifdef HAVE_UTMPX_H
setutxent();
#else
utmpname(UTMP_FILE);
setutent();
#endif
if (user) {
for (;;) {
#ifdef HAVE_UTMPX_H
u = getutxent();
#else
u = getutent();
#endif
if (unlikely(!u))
break;
if (u->ut_type != USER_PROCESS)
continue;
if (!strncmp(u->ut_user, user, UT_NAMESIZE))
showinfo(u, longform, maxcmd, from, userlen,
fromlen, ip_addresses);
}
} else {
for (;;) {
#ifdef HAVE_UTMPX_H
u = getutxent();
#else
u = getutent();
#endif
if (unlikely(!u))
break;
if (u->ut_type != USER_PROCESS)
continue;
if (*u->ut_user)
showinfo(u, longform, maxcmd, from, userlen,
fromlen, ip_addresses);
}
2002-12-09 12:30:07 +05:30
}
#ifdef HAVE_UTMPX_H
endutxent();
#else
endutent();
#endif
2002-02-02 04:17:29 +05:30
return EXIT_SUCCESS;
2002-02-02 04:17:29 +05:30
}