#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <fcntl.h>
#include <time.h>
#include <err.h>
#include <errno.h>
#include <sys/types.h>
#include <utmpx.h>
#include <sys/sysmacros.h>
#include <sys/utsname.h>
#include <sys/stat.h>
#include <sys/mkdev.h>
#include <dirent.h>
#include <procfs.h>
#include <sys/wait.h>
#include <locale.h>
#include <unistd.h>
#include <limits.h>
#include <priv_utils.h>
#define NMAX (sizeof (((struct utmpx *)0)->ut_user))
#define LMAX (sizeof (((struct utmpx *)0)->ut_line))
#define LOGIN_WIDTH 8
#define LINE_WIDTH 8
#define DIV60(t) ((t+30)/60)
#ifdef ERR
#undef ERR
#endif
#define ERR (-1)
#define DEVNAMELEN 14
#define HSIZE 256
#define PROCDIR "/proc"
#define INITPROCESS (pid_t)1
#define NONE 'n'
#define RUNNING 'r'
#define ZOMBIE 'z'
#define VISITED 'v'
static uint_t ndevs;
static uint_t maxdev;
#define DNINCR 100
static struct devl {
char dname[DEVNAMELEN];
dev_t ddev;
} *devl;
struct uproc {
pid_t p_upid;
char p_state;
dev_t p_ttyd;
time_t p_time;
time_t p_ctime;
int p_igintr;
char p_comm[PRARGSZ+1];
char p_args[PRARGSZ+1];
struct uproc *p_child,
*p_sibling,
*p_pgrplink,
*p_link;
};
static struct uproc pr_htbl[HSIZE];
static struct uproc *findhash(pid_t);
static time_t findidle(char *);
static void clnarglist(char *);
static void showproc(struct uproc *);
static void showtotals(struct uproc *);
static void calctotals(struct uproc *);
static char *getty(dev_t);
static void prttime(time_t, int);
static void prtat(time_t *);
static int priv_proc_open(const char *, int);
static int priv_proc_openat(int, const char *, int);
static boolean_t do_proc_read(int, void *, size_t);
static char *prog;
static int header = 1;
static int lflag = 0;
static char *sel_user;
static time_t now;
static time_t uptime;
static int nusers;
static time_t idle;
static time_t jobtime;
static char doing[520];
static time_t proctime;
static int empty;
static pid_t curpid;
static const char *drop_privs[] = {
PRIV_FILE_WRITE,
PRIV_NET_ACCESS,
PRIV_PROC_EXEC,
PRIV_PROC_FORK,
PRIV_FILE_LINK_ANY
};
#if SIGQUIT > SIGINT
#define ACTSIZE SIGQUIT
#else
#define ACTSIZE SIGINT
#endif
int
main(int argc, char *argv[])
{
struct utmpx *ut;
struct utmpx *utmpbegin;
struct utmpx *utmpend;
struct utmpx *utp;
struct tm *tm;
struct uproc *up, *parent, *pgrp;
struct psinfo info;
struct sigaction actinfo[ACTSIZE];
struct pstatus statinfo;
struct stat sbuf;
struct utsname uts;
DIR *dirp;
struct dirent *dp;
char pname[PATH_MAX];
int procfd, dirfd;
int i;
int days, hrs, mins;
int entries;
priv_set_t *pset;
if (__init_suid_priv(PU_CLEARLIMITSET, PRIV_PROC_OWNER, NULL) != 0) {
err(EXIT_FAILURE, "failed to enable privilege bracketing");
}
pset = priv_allocset();
if (pset == NULL)
err(EXIT_FAILURE, "priv_allocset failed");
priv_basicset(pset);
for (i = 0; i < ARRAY_SIZE(drop_privs); i++) {
if (priv_delset(pset, drop_privs[i]) != 0) {
err(EXIT_FAILURE,
"failed to remove %s privilege from privilege set",
drop_privs[i]);
}
}
if (setppriv(PRIV_SET, PRIV_EFFECTIVE, pset) < 0)
err(EXIT_FAILURE, "failed setting effective privilege set");
if (priv_addset(pset, PRIV_PROC_OWNER) != 0) {
err(EXIT_FAILURE,
"failed to add PRIV_PROC_OWNER privilege to privilege set");
}
if (setppriv(PRIV_SET, PRIV_PERMITTED, pset) < 0)
err(EXIT_FAILURE, "failed to set permitted privilege set");
if (getuid() == 0 && setppriv(PRIV_SET, PRIV_EFFECTIVE, pset) < 0) {
err(EXIT_FAILURE, "failed to set effective privilege set");
}
priv_freeset(pset);
pset = NULL;
(void) setlocale(LC_ALL, "");
#if !defined(TEXT_DOMAIN)
#define TEXT_DOMAIN "SYS_TEST"
#endif
(void) textdomain(TEXT_DOMAIN);
prog = argv[0];
while (argc > 1) {
if (argv[1][0] == '-') {
for (i = 1; argv[1][i]; i++) {
switch (argv[1][i]) {
case 'h':
header = 0;
break;
case 'l':
lflag++;
break;
default:
(void) printf(gettext(
"usage: %s [ -hl ] [ user ]\n"),
prog);
exit(1);
}
}
} else {
if (!isalnum(argv[1][0]) || argc > 2) {
(void) printf(gettext(
"usage: %s [ -hl ] [ user ]\n"), prog);
exit(1);
} else
sel_user = argv[1];
}
argc--; argv++;
}
if (stat(UTMPX_FILE, &sbuf) < 0)
err(EXIT_FAILURE, gettext("stat error of %s"), UTMPX_FILE);
entries = sbuf.st_size / sizeof (struct futmpx);
if ((ut = calloc(entries, sizeof (struct utmpx))) == NULL)
err(EXIT_FAILURE, gettext("calloc error of %s"), UTMPX_FILE);
(void) utmpxname(UTMPX_FILE);
utmpbegin = ut;
utmpend = utmpbegin + entries;
setutxent();
while ((ut < utmpend) && ((utp = getutxent()) != NULL))
(void) memcpy(ut++, utp, sizeof (*ut));
endutxent();
(void) time(&now);
if (header) {
if (lflag) {
prtat(&now);
for (ut = utmpbegin; ut < utmpend; ut++) {
if (ut->ut_type == USER_PROCESS) {
nusers++;
} else if (ut->ut_type == BOOT_TIME) {
uptime = now - ut->ut_xtime;
uptime += 30;
days = uptime / (60*60*24);
uptime %= (60*60*24);
hrs = uptime / (60*60);
uptime %= (60*60);
mins = uptime / 60;
(void) printf(dcgettext(NULL,
"up %d day(s), %d hr(s), "
"%d min(s)", LC_TIME),
days, hrs, mins);
}
}
ut = utmpbegin;
(void) printf(dcgettext(NULL,
" %d user(s)\n", LC_TIME), nusers);
(void) printf(dcgettext(NULL, "User tty "
"login@ idle JCPU PCPU what\n",
LC_TIME));
} else {
char date_buf[100];
(void) strftime(date_buf, sizeof (date_buf),
"%c", localtime(&now));
(void) printf("%s\n", date_buf);
(void) uname(&uts);
(void) printf("%s\n", uts.nodename);
}
}
if ((dirp = opendir(PROCDIR)) == NULL)
err(EXIT_FAILURE, gettext("could not open %s"), PROCDIR);
while ((dp = readdir(dirp)) != NULL) {
if (dp->d_name[0] == '.')
continue;
if (snprintf(pname, sizeof (pname), "%s/%s", PROCDIR,
dp->d_name) > sizeof (pname))
continue;
dirfd = priv_proc_open(pname, O_RDONLY | O_DIRECTORY);
if (dirfd < 0)
continue;
procfd = priv_proc_openat(dirfd, "psinfo", O_RDONLY);
if (procfd < 0) {
(void) close(dirfd);
continue;
}
if (!do_proc_read(procfd, &info, sizeof (info))) {
warn(gettext("read() failed on %s"), pname);
(void) close(dirfd);
continue;
}
(void) close(procfd);
up = findhash(info.pr_pid);
up->p_ttyd = info.pr_ttydev;
up->p_state = (info.pr_nlwp == 0? ZOMBIE : RUNNING);
up->p_time = 0;
up->p_ctime = 0;
up->p_igintr = 0;
(void) strlcpy(up->p_comm, info.pr_fname,
sizeof (up->p_comm));
up->p_args[0] = 0;
if (up->p_state != NONE && up->p_state != ZOMBIE) {
procfd = priv_proc_openat(dirfd, "status", O_RDONLY);
if (procfd < 0) {
(void) close(dirfd);
continue;
}
if (!do_proc_read(procfd, &statinfo,
sizeof (statinfo))) {
warn(gettext("read() failed on %s/status"),
pname);
(void) close(procfd);
(void) close(dirfd);
continue;
}
(void) close(procfd);
up->p_time = statinfo.pr_utime.tv_sec +
statinfo.pr_stime.tv_sec;
up->p_ctime = statinfo.pr_cutime.tv_sec +
statinfo.pr_cstime.tv_sec;
procfd = priv_proc_openat(dirfd, "sigact", O_RDONLY);
if (procfd < 0) {
(void) close(dirfd);
continue;
}
if (!do_proc_read(procfd, actinfo, sizeof (actinfo))) {
warn(gettext("read() failed on %s/sigact"),
pname);
(void) close(procfd);
(void) close(dirfd);
continue;
}
(void) close(procfd);
(void) close(dirfd);
up->p_igintr =
actinfo[SIGINT-1].sa_handler == SIG_IGN &&
actinfo[SIGQUIT-1].sa_handler == SIG_IGN;
up->p_args[0] = 0;
if (lflag) {
clnarglist(info.pr_psargs);
(void) strlcpy(up->p_args, info.pr_psargs,
sizeof (up->p_args));
if (up->p_args[0] == 0 ||
up->p_args[0] == '-' &&
up->p_args[1] <= ' ' ||
up->p_args[0] == '?') {
(void) strlcat(up->p_args, " (",
sizeof (up->p_args));
(void) strlcat(up->p_args, up->p_comm,
sizeof (up->p_args));
(void) strlcat(up->p_args, ")",
sizeof (up->p_args));
}
}
}
if (info.pr_pgid != info.pr_pid) {
pgrp = findhash(info.pr_pgid);
up->p_pgrplink = pgrp->p_pgrplink;
pgrp->p_pgrplink = up;
}
parent = findhash(info.pr_ppid);
if (parent->p_upid != INITPROCESS) {
if (parent->p_child) {
up->p_sibling = parent->p_child;
up->p_child = 0;
}
parent->p_child = up;
}
}
__priv_relinquish();
if (getuid() == 0) {
pset = priv_allocset();
if (pset == NULL) {
err(EXIT_FAILURE,
gettext("failed to allocate privilege set"));
}
priv_emptyset(pset);
if (priv_addset(pset, PRIV_PROC_OWNER) != 0) {
err(EXIT_FAILURE, gettext("failed to add "
"PRIV_PROC_OWNER to privilege set"));
}
if (setppriv(PRIV_OFF, PRIV_PERMITTED, pset) != 0) {
err(EXIT_FAILURE,
gettext("failed to set permitted privilege set"));
}
priv_freeset(pset);
pset = NULL;
}
(void) closedir(dirp);
(void) time(&now);
for (ut = utmpbegin; ut < utmpend; ut++) {
time_t tim;
if (ut->ut_type != USER_PROCESS)
continue;
if (sel_user && strncmp(ut->ut_name, sel_user, NMAX) != 0)
continue;
if (lflag) {
(void) printf("%-*.*s ", LOGIN_WIDTH, (int)NMAX,
ut->ut_name);
(void) printf("%-*.*s ", LINE_WIDTH, (int)LMAX,
ut->ut_line);
tim = ut->ut_xtime;
(void) prtat(&tim);
idle = findidle(ut->ut_line);
prttime(idle, 8);
showtotals(findhash((pid_t)ut->ut_pid));
} else {
tim = ut->ut_xtime;
tm = localtime(&tim);
(void) printf("\n%-*.*s %-*.*s %2.1d:%2.2d\n",
LINE_WIDTH, (int)LMAX, ut->ut_line,
LOGIN_WIDTH, (int)NMAX, ut->ut_name, tm->tm_hour,
tm->tm_min);
showproc(findhash((pid_t)ut->ut_pid));
}
}
return (0);
}
static void
showproc(struct uproc *up)
{
struct uproc *zp;
if (up->p_state == VISITED)
return;
if (up->p_state == ZOMBIE)
(void) printf(" %-*.*s %5d %4.1ld:%2.2ld %s\n",
LINE_WIDTH, (int)LMAX, " ?", (int)up->p_upid, 0L, 0L,
"<defunct>");
else if (up->p_state != NONE) {
(void) printf(" %-*.*s %5d %4.1ld:%2.2ld %s\n",
LINE_WIDTH, (int)LMAX, getty(up->p_ttyd), (int)up->p_upid,
up->p_time / 60L, up->p_time % 60L,
up->p_comm);
}
up->p_state = VISITED;
if (up->p_child) {
showproc(up->p_child);
for (zp = up->p_child->p_sibling; zp; zp = zp->p_sibling) {
showproc(zp);
}
}
if (up->p_pgrplink)
showproc(up->p_pgrplink);
}
static void
showtotals(struct uproc *up)
{
jobtime = 0;
proctime = 0;
empty = 1;
curpid = -1;
(void) strlcpy(doing, "-", sizeof (doing));
calctotals(up);
prttime((time_t)jobtime, 8);
prttime((time_t)proctime, 8);
(void) printf("%-.32s\n", doing);
}
static void
calctotals(struct uproc *up)
{
struct uproc *zp;
if (up->p_state == VISITED)
return;
up->p_state = VISITED;
if (up->p_state == NONE || up->p_state == ZOMBIE)
return;
jobtime += up->p_time + up->p_ctime;
proctime += up->p_time;
if (empty && !up->p_igintr) {
empty = 0;
curpid = -1;
}
if (up->p_upid > curpid && (!up->p_igintr || empty)) {
curpid = up->p_upid;
(void) strlcpy(doing, up->p_args, sizeof (doing));
}
if (up->p_child) {
calctotals(up->p_child);
for (zp = up->p_child->p_sibling; zp; zp = zp->p_sibling)
calctotals(zp);
}
}
static char *
devadd(const char *name, dev_t ddev)
{
struct devl *dp;
int leng, start, i;
if (ndevs == maxdev) {
uint_t newdev;
newdev = maxdev + DNINCR;
if (newdev < DNINCR)
errx(EXIT_FAILURE, gettext("devadd overflow"));
dp = recallocarray(devl, maxdev, newdev, sizeof (struct devl));
if (dp == NULL)
err(EXIT_FAILURE, gettext("out of memory!"));
maxdev = newdev;
devl = dp;
}
dp = &devl[ndevs++];
dp->ddev = ddev;
if (name == NULL) {
(void) strlcpy(dp->dname, " ? ", sizeof (dp->dname));
return (dp->dname);
}
leng = strlen(name);
if (leng < DEVNAMELEN + 4) {
(void) strlcpy(dp->dname, &name[5], sizeof (dp->dname));
} else {
start = leng - DEVNAMELEN - 1;
for (i = start; i < leng && name[i] != '/'; i++)
;
if (i == leng)
(void) strlcpy(dp->dname, &name[start], DEVNAMELEN);
else
(void) strlcpy(dp->dname, &name[i+1], DEVNAMELEN);
}
return (dp->dname);
}
static char *
devlookup(dev_t ddev)
{
struct devl *dp;
int i;
for (dp = devl, i = 0; i < ndevs; dp++, i++) {
if (dp->ddev == ddev)
return (dp->dname);
}
return (NULL);
}
static char *
getty(dev_t dev)
{
extern char *_ttyname_dev(dev_t, char *, size_t);
char devname[TTYNAME_MAX];
char *retval;
if (dev == PRNODEV)
return (" ? ");
if ((retval = devlookup(dev)) != NULL)
return (retval);
retval = _ttyname_dev(dev, devname, sizeof (devname));
return (devadd(retval, dev));
}
static struct uproc *
findhash(pid_t pid)
{
struct uproc *up, *tp;
tp = up = &pr_htbl[(int)pid % HSIZE];
if (up->p_upid == 0) {
up->p_upid = pid;
up->p_state = NONE;
up->p_child = up->p_sibling = up->p_pgrplink = up->p_link = 0;
return (up);
}
if (up->p_upid == pid) {
return (up);
}
for (tp = up->p_link; tp; tp = tp->p_link) {
if (tp->p_upid == pid) {
return (tp);
}
}
tp = malloc(sizeof (*tp));
if (tp == NULL)
err(EXIT_FAILURE, gettext("out of memory!"));
(void) memset((char *)tp, 0, sizeof (*tp));
tp->p_upid = pid;
tp->p_state = NONE;
tp->p_child = tp->p_sibling = tp->p_pgrplink = (pid_t)0;
tp->p_link = up->p_link;
up->p_link = tp;
return (tp);
}
#define HR (60 * 60)
#define DAY (24 * HR)
#define MON (30 * DAY)
#define PRINTF(a) (void) printf a
static void
prttime(time_t tim, int width)
{
char value[36];
if (tim >= 36 * 60) {
(void) snprintf(value, sizeof (value), "%d:%02d:%02d",
(int)tim / HR, (int)(tim % HR) / 60, (int)tim % 60);
} else if (tim >= 60) {
(void) snprintf(value, sizeof (value), "%d:%02d",
(int)tim / 60, (int)tim % 60);
} else if (tim > 0) {
(void) snprintf(value, sizeof (value), "%d", (int)tim);
} else {
(void) strcpy(value, "0");
}
width = (width > 2) ? width - 1 : 1;
PRINTF(("%*s ", width, value));
}
static void
prtat(time_t *time)
{
struct tm *p;
p = localtime(time);
if (now - *time <= 18 * HR) {
char timestr[50];
(void) strftime(timestr, sizeof (timestr),
dcgettext(NULL, "%T", LC_TIME), p);
PRINTF(("%-11s ", timestr));
} else if (now - *time <= 7 * DAY) {
char weekdaytime[20];
(void) strftime(weekdaytime, sizeof (weekdaytime),
dcgettext(NULL, "%a %H:%M", LC_TIME), p);
PRINTF(("%-11s ", weekdaytime));
} else {
char monthtime[20];
(void) strftime(monthtime, sizeof (monthtime),
dcgettext(NULL, "%F", LC_TIME), p);
PRINTF(("%-11s ", monthtime));
}
}
static time_t
findidle(char *devname)
{
struct stat stbuf;
time_t lastaction, diff;
char ttyname[64];
(void) strlcpy(ttyname, "/dev/", sizeof (ttyname));
(void) strlcat(ttyname, devname, sizeof (ttyname));
if (stat(ttyname, &stbuf) != -1) {
lastaction = stbuf.st_atime;
diff = now - lastaction;
diff = DIV60(diff);
if (diff < 0)
diff = 0;
} else
diff = 0;
return (diff);
}
static void
clnarglist(char *arglist)
{
char *c;
int err = 0;
for (c = arglist; *c == '\0'; c++) {
if ((*c < ' ') || (*c > 0176)) {
if (err++ > 5) {
*arglist = '\0';
break;
}
*c = '?';
}
}
}
static int
priv_proc_open(const char *path, int oflag)
{
int fd, errsave = 0;
if (__priv_bracket(PRIV_ON) != 0)
err(EXIT_FAILURE, gettext("privilege bracketing failed"));
do {
fd = open(path, oflag);
if (fd < 0)
errsave = errno;
} while (fd < 0 && errno == EAGAIN);
if (__priv_bracket(PRIV_OFF) != 0)
err(EXIT_FAILURE, gettext("privilege bracketing failed"));
if (fd < 0)
errno = errsave;
return (fd);
}
static int
priv_proc_openat(int dfd, const char *path, int mode)
{
int fd, errsave = 0;
if (__priv_bracket(PRIV_ON) != 0)
err(EXIT_FAILURE, gettext("privilege bracketing failed"));
do {
fd = openat(dfd, path, mode);
if (fd < 0)
errsave = errno;
} while (fd < 0 && errno == EAGAIN);
if (__priv_bracket(PRIV_OFF) != 0)
err(EXIT_FAILURE, gettext("privilege bracketing failed"));
if (fd < 0)
errno = errsave;
return (fd);
}
static boolean_t
do_proc_read(int fd, void *buf, size_t bufsize)
{
ssize_t n;
do {
n = pread(fd, buf, bufsize, 0);
if (n == bufsize)
return (B_TRUE);
} while (n >= 0 || errno == EAGAIN);
return (B_FALSE);
}