#include <sys/cdefs.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <err.h>
#include <errno.h>
#include <langinfo.h>
#include <limits.h>
#include <locale.h>
#include <paths.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <timeconv.h>
#include <unistd.h>
#include <utmpx.h>
#define UT_NAMESIZE 8
#define UT_LINESIZE 8
#define UT_HOSTSIZE 16
static void heading(void);
static void process_utmp(void);
static void process_wtmp();
static void quick(void);
static void row(const struct utmpx *);
static int ttywidth(void);
static void usage(void);
static void whoami(void);
static int bflag;
static int dflag;
static int Hflag;
static int lflag;
static int mflag;
static int pflag;
static int qflag;
static int rflag;
static int sflag;
static int tflag;
static int Tflag;
static int uflag;
#ifdef __APPLE__
#include <get_compat.h>
#else
#define COMPAT_MODE(a,b) (1)
#endif
static int unix2003_std;
int
main(int argc, char *argv[])
{
int ch;
setlocale(LC_TIME, "");
unix2003_std = COMPAT_MODE("bin/who", "unix2003");
while ((ch = getopt(argc, argv, "abdHlmpqrstTu")) != -1) {
switch (ch) {
case 'a':
bflag = dflag = lflag = pflag = 1;
rflag = tflag = Tflag = uflag = 1;
break;
case 'b':
bflag = 1;
break;
case 'd':
dflag = 1;
break;
case 'H':
Hflag = 1;
break;
case 'l':
lflag = 1;
break;
case 'm':
mflag = 1;
break;
case 'p':
pflag = 1;
break;
case 'q':
qflag = 1;
break;
case 'r':
rflag = 1;
break;
case 's':
sflag = 1;
break;
case 't':
tflag = 1;
break;
case 'T':
Tflag = 1;
break;
case 'u':
uflag = 1;
break;
default:
usage();
}
}
argc -= optind;
argv += optind;
if (argc >= 2 && strcmp(argv[0], "am") == 0 &&
(strcmp(argv[1], "i") == 0 || strcmp(argv[1], "I") == 0)) {
mflag = 1;
argc -= 2;
argv += 2;
}
if (argc > 1)
usage();
if (*argv != NULL) {
if (!utmpxname(*argv) || !wtmpxname(*argv))
usage();
}
if (qflag)
quick();
else {
if (sflag)
Tflag = uflag = 0;
if (Hflag)
heading();
if (mflag)
whoami();
else
if( Tflag || uflag || !(bflag || dflag || lflag || pflag || rflag) )
process_utmp();
}
if (bflag || dflag || lflag || pflag || rflag ) {
process_wtmp();
}
endutxent();
exit(0);
}
static void
usage(void)
{
fprintf(stderr, "usage: who [-abdHlmpqrstTu] [am I] [file]\n");
exit(1);
}
static void
heading(void)
{
printf("%-*s ", UT_NAMESIZE, "NAME");
if (Tflag)
printf("S ");
printf("%-*s ", UT_LINESIZE, "LINE");
printf("%-*s ", 12, "TIME");
if (uflag)
printf("IDLE ");
if (unix2003_std && uflag && !Tflag)
printf(" PID ");
printf("%-*s", UT_HOSTSIZE, "FROM");
putchar('\n');
}
static void
row(const struct utmpx *ut)
{
char buf[80], tty[sizeof(_PATH_DEV) + _UTX_LINESIZE];
struct stat sb;
time_t idle, t;
static int d_first = -1;
struct tm *tm;
char state;
char login_pidstr[20];
if (d_first < 0)
d_first = (*nl_langinfo(D_MD_ORDER) == 'd');
if (Tflag || uflag) {
snprintf(tty, sizeof(tty), "%s%.*s", _PATH_DEV,
_UTX_LINESIZE, ut->ut_line);
state = '?';
idle = 0;
if (stat(tty, &sb) == 0) {
state = sb.st_mode & (S_IWOTH|S_IWGRP) ?
'+' : '-';
idle = time(NULL) - sb.st_mtime;
}
if (unix2003_std && !Tflag) {
if (ut->ut_pid) {
snprintf(login_pidstr,sizeof(login_pidstr),
"%8d",ut->ut_pid);
} else {
strcpy(login_pidstr," ?");
}
}
}
printf("%-*.*s ", UT_NAMESIZE, _UTX_USERSIZE, ut->ut_user);
if (Tflag)
printf("%c ", state);
printf("%-*.*s ", UT_LINESIZE, _UTX_LINESIZE, ut->ut_line);
t = _time32_to_time(ut->ut_tv.tv_sec);
tm = localtime(&t);
strftime(buf, sizeof(buf), d_first ? "%e %b %R" : "%b %e %R", tm);
printf("%-*s ", 12, buf);
if (uflag) {
if (idle < 60)
printf(" . ");
else if (idle < 24 * 60 * 60)
printf("%02d:%02d ", (int)(idle / 60 / 60),
(int)(idle / 60 % 60));
else
printf(" old ");
if (unix2003_std && !Tflag) {
printf("%s ", login_pidstr);
}
}
if (*ut->ut_host != '\0')
printf("(%.*s)", _UTX_HOSTSIZE, ut->ut_host);
putchar('\n');
}
static void
process_utmp(void)
{
struct utmpx *ut;
while ((ut = getutxent()) != NULL)
if (*ut->ut_user != '\0' && ut->ut_type == USER_PROCESS) {
row(ut);
}
}
static void
process_wtmp(void)
{
struct utmpx *ut;
struct utmpx lboot_ut;
int num = 0;
setutxent_wtmp(0);
lboot_ut.ut_type = 0;
while (!lboot_ut.ut_type && (ut = getutxent_wtmp()) != NULL) {
switch(ut->ut_type) {
case BOOT_TIME:
lboot_ut = *ut;
strcpy(lboot_ut.ut_user, "reboot");
strcpy(lboot_ut.ut_line, "~");
break;
case INIT_PROCESS:
case LOGIN_PROCESS:
case USER_PROCESS:
case DEAD_PROCESS:
num++;
break;
}
}
endutxent_wtmp();
if (bflag && lboot_ut.ut_type)
row(&lboot_ut);
if (rflag && (num > 1))
printf(" . run-level 3\n");
}
static void
quick(void)
{
struct utmpx *ut;
int col, ncols, num;
ncols = ttywidth();
col = num = 0;
while ((ut = getutxent()) != NULL) {
if (*ut->ut_user == '\0' || ut->ut_type != USER_PROCESS)
continue;
printf("%-*.*s", UT_NAMESIZE, _UTX_USERSIZE, ut->ut_user);
if (++col < ncols / (UT_NAMESIZE + 1))
putchar(' ');
else {
col = 0;
putchar('\n');
}
num++;
}
if (col != 0)
putchar('\n');
printf("# users = %d\n", num);
}
static void
whoami(void)
{
struct utmpx ut;
struct utmpx *u;
struct passwd *pwd;
const char *name, *p, *tty;
if ((tty = ttyname(STDIN_FILENO)) == NULL)
tty = "tty??";
else if ((p = strrchr(tty, '/')) != NULL)
tty = p + 1;
memset(&ut, 0, sizeof(ut));
strncpy(ut.ut_line, tty, sizeof(ut.ut_line));
memcpy(ut.ut_id, tty + (strlen(tty) - sizeof(ut.ut_id)), sizeof(ut.ut_id));
ut.ut_type = USER_PROCESS;
u = getutxid(&ut);
if (u) {
row(u);
return;
}
if ((pwd = getpwuid(getuid())) != NULL)
name = pwd->pw_name;
else
name = "?";
strncpy(ut.ut_user, name, _UTX_USERSIZE);
ut.ut_tv.tv_sec = _time_to_time32(time(NULL));
row(&ut);
}
static int
ttywidth(void)
{
struct winsize ws;
long width;
char *cols, *ep;
if ((cols = getenv("COLUMNS")) != NULL && *cols != '\0') {
errno = 0;
width = strtol(cols, &ep, 10);
if (errno || width <= 0 || width > INT_MAX || ep == cols ||
*ep != '\0')
warnx("invalid COLUMNS environment variable ignored");
else
return (width);
}
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) != -1)
return (ws.ws_col);
return (80);
}