#include <sys/types.h>
#include <sys/loadavg.h>
#include <sys/queue.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <ctype.h>
#include <dirent.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <locale.h>
#include <priv_utils.h>
#include <procfs.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <utmpx.h>
static struct utmpx dummy;
#define NMAX (sizeof (dummy.ut_user))
#define LMAX (sizeof (dummy.ut_line))
#define LOGIN_WIDTH 8
#define LINE_WIDTH 8
#define DIV60(t) ((t+30)/60)
#define PROCDIR "/proc"
#define PRINTF(a) if (printf a < 0) { \
perror((gettext("%s: printf failed"), prog)); \
exit(1); }
struct uproc {
pid_t p_upid;
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];
STAILQ_ENTRY(uproc) uprocs;
};
STAILQ_HEAD(uprochead, uproc) uphead;
static time_t findidle(char *);
static void clnarglist(char *);
static void prttime(time_t, int);
static void prtat(time_t *time);
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 = 1;
static char *sel_user;
static char firstchar;
static int login;
static time_t now;
static time_t uptime;
static int nusers;
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 uproc *up;
struct psinfo info;
struct sigaction actinfo[ACTSIZE];
struct pstatus statinfo;
struct stat sbuf;
DIR *dirp;
struct dirent *dp;
char pname[PATH_MAX];
int procfd;
int dirfd;
char *cp;
int i;
int days, hrs, mins;
int entries;
double loadavg[3];
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);
login = (argv[0][0] == '-');
cp = strrchr(argv[0], '/');
firstchar = login ? argv[0][1] : (cp == 0) ? argv[0][0] : cp[1];
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;
case 's':
lflag = 0;
break;
case 'u':
case 'w':
firstchar = argv[1][i];
break;
default:
(void) fprintf(stderr, gettext(
"%s: bad flag %s\n"),
prog, argv[1]);
exit(1);
}
}
} else {
if (!isalnum(argv[1][0]) || argc > 2) {
(void) fprintf(stderr, gettext(
"usage: %s [ -hlsuw ] [ 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) {
prtat(&now);
for (ut = utmpbegin; ut < utmpend; ut++) {
if (ut->ut_type == USER_PROCESS) {
if (!nonuserx(*ut))
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;
PRINTF((gettext("up")));
if (days > 0)
PRINTF((gettext(
" %d day(s),"), days));
if (hrs > 0 && mins > 0) {
PRINTF((" %2d:%02d,", hrs, mins));
} else {
if (hrs > 0) {
PRINTF((gettext(
" %d hr(s),"), hrs));
} else {
PRINTF((gettext(
" %d min(s),"), mins));
}
}
}
}
ut = utmpbegin;
PRINTF((((nusers == 1) ?
gettext(" %d user") : gettext(" %d users")), nusers));
(void) getloadavg(loadavg, 3);
PRINTF((gettext(", load average: %.2f, %.2f, %.2f\n"),
loadavg[LOADAVG_1MIN], loadavg[LOADAVG_5MIN],
loadavg[LOADAVG_15MIN]));
if (firstchar == 'u')
exit(0);
if (lflag) {
PRINTF((dcgettext(NULL, "User tty "
"login@ idle JCPU PCPU what\n",
LC_TIME)));
} else {
PRINTF((dcgettext(NULL,
"User tty idle what\n",
LC_TIME)));
}
if (fflush(stdout) == EOF) {
err(EXIT_FAILURE, "fflush failed");
}
}
if ((dirp = opendir(PROCDIR)) == NULL)
err(EXIT_FAILURE, gettext("could not open %s"), PROCDIR);
STAILQ_INIT(&uphead);
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("failed to read %s"), pname);
(void) close(dirfd);
continue;
}
(void) close(procfd);
if (info.pr_nlwp == 0)
continue;
if (info.pr_ttydev == NODEV)
continue;
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("failed to read %s/status"), pname);
(void) close(procfd);
(void) close(dirfd);
continue;
}
(void) close(procfd);
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("failed to read %s/sigact"), pname);
(void) close(procfd);
(void) close(dirfd);
continue;
}
(void) close(procfd);
(void) close(dirfd);
up = calloc(1, sizeof (*up));
if (up == NULL)
err(EXIT_FAILURE, "calloc");
up->p_upid = info.pr_pid;
up->p_ttyd = info.pr_ttydev;
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;
up->p_igintr =
actinfo[SIGINT-1].sa_handler == SIG_IGN &&
actinfo[SIGQUIT-1].sa_handler == SIG_IGN;
(void) strlcpy(up->p_comm, info.pr_fname, sizeof (up->p_comm));
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[0] == '-' && up->p_args[1] <= ' ')) {
(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));
}
STAILQ_INSERT_TAIL(&uphead, up, uprocs);
}
__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++) {
struct uproc *upt;
char linedev[PATH_MAX];
char what[1024];
time_t idle, jobtime, proctime;
pid_t curpid;
if (ut->ut_type != USER_PROCESS)
continue;
if (sel_user != NULL &&
strncmp(ut->ut_name, sel_user, NMAX) != 0)
continue;
PRINTF(("%-*.*s ", LOGIN_WIDTH, NMAX, ut->ut_name));
if (lflag) {
PRINTF(("%-*.*s ", LINE_WIDTH, LMAX, ut->ut_line));
} else {
if (strncmp(ut->ut_line, "pts/", strlen("pts/")) == 0) {
PRINTF(("%-*.*s ", LINE_WIDTH, LMAX,
&ut->ut_line[4]));
} else {
PRINTF(("%-*.*s ", LINE_WIDTH, LMAX,
ut->ut_line));
}
}
if (lflag) {
time_t tim = ut->ut_xtime;
prtat(&tim);
}
idle = findidle(ut->ut_line);
prttime(idle, 8);
jobtime = 0;
proctime = 0;
curpid = -1;
(void) strlcpy(what, "-", sizeof (what));
(void) snprintf(linedev, sizeof (linedev), "/dev/%s",
ut->ut_line);
if (stat(linedev, &sbuf) == -1 ||
(sbuf.st_mode & S_IFMT) != S_IFCHR ||
sbuf.st_rdev == NODEV)
goto skip;
STAILQ_FOREACH_SAFE(up, &uphead, uprocs, upt) {
if (up->p_ttyd != sbuf.st_rdev)
continue;
jobtime += up->p_time + up->p_ctime;
proctime += up->p_time;
if (up->p_upid > curpid && !up->p_igintr) {
curpid = up->p_upid;
if (lflag) {
(void) strlcpy(what, up->p_args,
sizeof (what));
} else {
(void) strlcpy(what, up->p_comm,
sizeof (what));
}
}
STAILQ_REMOVE(&uphead, up, uproc, uprocs);
free(up);
}
skip:
if (lflag) {
prttime(jobtime, 8);
prttime(proctime, 8);
}
PRINTF(("%-.32s\n", what));
}
if (fclose(stdout) == EOF)
err(EXIT_FAILURE, gettext("fclose failed"));
return (0);
}
#define HR (60 * 60)
#define DAY (24 * HR)
#define MON (30 * DAY)
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) strlcpy(value, "0", sizeof (value));
}
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);
}