/*-
 * Copyright (c) 1980, 1991 The Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#ifndef lint
char copyright[] =
"@(#) Copyright (c) 1980, 1991 The Regents of the University of California.\n\
 All rights reserved.\n";
#endif /* not lint */

#ifndef lint
static char sccsid[] = "@(#)w.c	5.29 (Berkeley) 4/23/91";
#endif /* not lint */

/*
 * w - print system status (who and what)
 *
 * This program is similar to the systat command on Tenex/Tops 10/20
 *
 * This version of w is the same as the kmem version in this distribution,
 * but uses the /proc file system instead of /dev/kmem and the psdatabase.
 * Compile with -DNEWFMT to get the original 4.4BSD output format.
 *
 */
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>
#include <utmp.h>
#include <ctype.h>
#include <string.h>
#include <stdio.h>
#include <getopt.h>
#include <stdarg.h>
#include <dirent.h>
#include <fcntl.h>
#include <sys/param.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/sysmacros.h>
#include <sys/stat.h>

#ifndef	UT_NAMESIZE
#define	UT_NAMESIZE	8
#endif
#ifndef	UT_LINESIZE
#define	UT_LINESIZE	12
#endif
#ifndef	UT_HOSTSIZE
#define	UT_HOSTSIZE	16
#endif
#ifndef	_PATH_DEV
#define	_PATH_DEV	"/dev/"
#endif
#ifndef	_PATH_UTMP
#define	_PATH_UTMP	"/var/adm/utmp"
#endif

char	*program;
int	ttywidth;		/* width of tty */
int	argwidth;		/* width of tty */
int	header = 1;		/* true if -h flag: don't print heading */
int	wcmd = 1;		/* true if this is w(1), and not uptime(1) */
int	nusers;			/* number of users logged in now */
char *	sel_user;		/* login of particular user selected */
time_t	now;			/* the current time of day */
struct	timeval boottime;
time_t	uptime;			/* time of last reboot & elapsed time since */
struct	utmp utmp;
struct	winsize ws;
int	sortidle;		/* sort bu idle time */

void error(char *fmt, ...);
struct stat *ttystat(char *line);
void prttime(time_t tim, char *tail);
int get_proc_string(char *name, char *buf);
char *attime(time_t *time);

static char *
skip_fields(char *p, int n)
{
	while (--n >= 0)
		while (*p && *p++ != ' ')
			;
	return p;
}


/*
 * One of these per active utmp entry.  
 */
struct	entry {
	struct	entry *next;
	struct	utmp utmp;
	dev_t	tdev;		/* dev_t of terminal */
	int	idle;		/* idle time of terminal in minutes */
	int	jcpu;		/* joint cpu time */
	int	pcpu;		/* process cpu time */
	int	pid;
	char	comm[32];
	char	*args;		/* arg list of interesting process */
} *ep, *ehead = NULL, **nextp = &ehead;


#define USAGE "[ -hi ] [ user ]"
#define usage()	fprintf(stderr, "usage: %s: %s\n", program, USAGE)

int
main(int argc, char *argv[])
{
	struct stat *stp;
	FILE *ut;
	char *cp;
	int ch;
	struct dirent *d;
	DIR *dp;
	char buf[4097];

	program = argv[0];
	/*
	 * are we w(1) or uptime(1)
	 */
	if ((cp = strrchr(program, '/')) || *(cp = program) == '-')
		cp++;
	if (*cp == 'u')
		wcmd = 0;

	while ((ch = getopt(argc, argv, "hiflsuw")) != EOF)
		switch((char)ch) {
		case 'h':
			header = 0;
			break;
		case 'i':
			sortidle++;
			break;
		case 'f': case 'l': case 's': case 'u': case 'w':
			error("[-flsuw] no longer supported");
			usage();
			exit(1);
		case '?':
		default:
			usage();
			exit(1);
		}
	argc -= optind;
	argv += optind;

	if (*argv)
		sel_user = *argv;

	time(&now);
	ut = fopen(_PATH_UTMP, "r");
	while (fread(&utmp, sizeof(utmp), 1, ut)) {
#ifdef USER_PROCESS
		if (utmp.ut_type != USER_PROCESS || utmp.ut_name[0] == '\0')
#else
		if (utmp.ut_name[0] == '\0')
#endif
			continue;
		nusers++;
		if (wcmd == 0 || (sel_user && 
		    strncmp(utmp.ut_name, sel_user, UT_NAMESIZE) != 0))
			continue;
		if ((ep = (struct entry *)
		     calloc(1, sizeof (struct entry))) == NULL) {
			error("out of memory");
			exit(1);
		}
#ifdef USER_PROCESS	/* Linux utmp is not sorted */
		for (nextp = &ehead; *nextp; nextp = &(**nextp).next)
			if (strncmp(utmp.ut_line, (**nextp).utmp.ut_line, 8) < 0)
				break;
		ep->next = *nextp;
		*nextp = ep;
#else
		*nextp = ep;
		nextp = &(ep->next);
#endif
		memcpy(&(ep->utmp), &utmp, sizeof (struct utmp));
		stp = ttystat(ep->utmp.ut_line);
		ep->tdev = stp->st_rdev;
		ep->idle = ((now - stp->st_atime) + 30) / 60; /* secs->mins */
		if (ep->idle < 0)
			ep->idle = 0;
	}
	fclose(ut);
	if (chdir("/proc") == -1) {
		error("cannot chdir to /proc");
		exit(1);
	}

	if (header || wcmd == 0) {
		int days, hrs, mins;

		/*
		 * Print time of day 
		 */
		putchar(' ');
		fputs(attime(&now), stdout);
		/*
		 * Print how long system has been up.
		 * (Found by looking for "boottime" in kernel)
		 */
		if (get_proc_string("uptime", buf) == -1) {
			error("can't read /proc/uptime");
			exit(1);
		}
		uptime = atoi(buf);
		uptime += 30;
		days = uptime / (60*60*24);
		uptime %= (60*60*24);
		hrs = uptime / (60*60);
		uptime %= (60*60);
		mins = uptime / 60;

		printf("  up");
		if (days > 0)
			printf(" %d day%s,", days, days>1?"s":"");
		if (hrs > 0 && mins > 0) {
			printf(" %2d:%02d,", hrs, mins);
		} else {
			if (hrs > 0)
				printf(" %d hr%s,", hrs, hrs>1?"s":"");
			if (mins > 0)
				printf(" %d min%s,", mins, mins>1?"s":"");
		}

		/* Print number of users logged in to system */
		printf("  %d user%s", nusers, nusers>1?"s":"");

		/*
		 * Print 1, 5, and 15 minute load averages.
		 */
		printf(",  load average: ");
		get_proc_string("loadavg", buf);
		for (cp = buf; *cp != '\n'; ++cp) {
			if (*cp == ' ')
				putchar(',');
			putchar(*cp);
		}
		putchar('\n');
		if (wcmd == 0)		/* if uptime(1) then done */
			exit(0);
#ifdef NEWFMT
#define HEADER	"USER    TTY FROM              LOGIN@  IDLE WHAT\n"
#else
#define HEADER	"User     tty       login@  idle   JCPU   PCPU  what\n"
#endif
#define WUSED	(sizeof (HEADER) - sizeof ("WHAT\n"))
		printf(HEADER);
	}

	if ((dp = opendir(".")) == NULL) {
		error("can't open /proc directory");
		exit(1);
	}
	while ((d = readdir(dp)) != NULL) {
		long tpgid;
		int tty, pcpu, pgrp, pid, i=0;
		char comm[32];

		if (d->d_ino < 1000)
			continue;
		strcpy(buf, d->d_name);
		strcat(buf, "/stat");
		if (get_proc_string(buf, buf) == -1)
			continue;
		pid = atoi(buf);
		cp = skip_fields(buf, 1);
		while (!(*(cp-1) == ')' && *cp == ' ') && i < 31)
			comm[i++] = *cp++;
		comm[i] = '\0';
		++cp;
		if (*cp == 'Z')
			continue;
		cp = skip_fields(cp, 2);
		pgrp = atoi(cp);
		cp = skip_fields(cp, 2);
		if ((tty = atoi(cp)) == -1)
			continue;
		cp = skip_fields(cp, 1);
		tpgid = atoi(cp);

		cp = skip_fields(cp, 6);
		pcpu = atoi(cp);	/* utime */
		cp = skip_fields(cp, 1);
		pcpu += atoi(cp);	/* stime */


		for (ep = ehead; ep != NULL; ep = ep->next) {
			if (minor(ep->tdev) == minor(tty)) {
				ep->jcpu += pcpu;
				cp = skip_fields(cp, 1);
				ep->jcpu += atoi(cp);	/* cutime */
				cp = skip_fields(cp, 1);
				ep->jcpu += atoi(cp);	/* cstime */
				if (pgrp == tpgid) {
					/*
					 * Proc is in foreground of this terminal
					 */
					if (pcpu >= ep->pcpu) {
						strcpy(ep->comm, comm);
						ep->pid = pid;
						ep->pcpu = pcpu;
					}
				}
				break;
			}
		}
	}
	if ((ioctl(1, TIOCGWINSZ, &ws) == -1 &&
	     ioctl(2, TIOCGWINSZ, &ws) == -1 &&
	     ioctl(0, TIOCGWINSZ, &ws) == -1) || ws.ws_col == 0)
	       ttywidth = 79;
        else
	       ttywidth = ws.ws_col - 1;
	argwidth = ttywidth - WUSED;
	if (argwidth < 4)
		argwidth = 8;
	if (argwidth > 200)
		argwidth = 200;
	for (ep = ehead; ep != NULL; ep = ep->next) {
		int n;
		sprintf(buf, "%d/cmdline", ep->pid);
		if ((n = get_proc_string(buf, buf)) == -1) {
			ep->args = "";
			continue;
		}
		if (n > argwidth)
			n = argwidth;
		buf[n] = '\0';
		for (cp = buf + n; --cp >= buf; )
			if (!isprint(*cp))
				*cp = ' ';
		if (!*buf || *buf == '-') {
			strcat(buf, " ");
			strcat(buf, ep->comm);
		}
		ep->args = strdup(buf);
		if (ep->args == NULL) {
			error("out of memory");
			exit(1);
		}
	}
	/* sort by idle time */
	if (sortidle && ehead != NULL) {
		struct entry *from = ehead, *save;
		
		ehead = NULL;
		while (from != NULL) {
			for (nextp = &ehead; 
			    (*nextp) && from->idle >= (*nextp)->idle;
			    nextp = &(*nextp)->next)
				;
			save = from;
			from = from->next;
			save->next = *nextp;
			*nextp = save;
		}
	}

	for (ep = ehead; ep != NULL; ep = ep->next) {
#ifdef	NEWFMT
		printf("%-*.*s %-2.2s %-*.*s %s",
			UT_NAMESIZE, UT_NAMESIZE, ep->utmp.ut_name,
			strncmp(ep->utmp.ut_line, "tty", 3) == 0 ? 
				ep->utmp.ut_line+3 : ep->utmp.ut_line,
			UT_HOSTSIZE, UT_HOSTSIZE, *ep->utmp.ut_host ?
				ep->utmp.ut_host : "-",
			attime(&ep->utmp.ut_time));
		if (ep->idle >= 36 * 60)
			printf(" %ddays ", (ep->idle + 12 * 60) / (24 * 60));
		else
			prttime(ep->idle, " ");
#else
		printf("%-8.8s %-8.8s %s",
			ep->utmp.ut_name,
			ep->utmp.ut_line,
			attime(&ep->utmp.ut_time));
		if (ep->idle >= 36 * 60)
			printf("%2ddays ", (ep->idle + 12 * 60) / (24 * 60));
		else
			prttime(ep->idle, " ");
		prttime(ep->jcpu / HZ, " ");
		prttime(ep->pcpu / HZ, "  ");
		
#endif
		printf("%.*s\n", argwidth, ep->args);
	}
	exit(0);
}

struct stat *
ttystat(char *line)
{
	static struct stat statbuf;
	char ttybuf[sizeof (_PATH_DEV) + UT_LINESIZE + 1];

	sprintf(ttybuf, "%s/%.*s", _PATH_DEV, UT_LINESIZE, line);
	(void) stat(ttybuf, &statbuf);

	return (&statbuf);
}

/*
 * prttime prints a time in hours and minutes or minutes and seconds.
 * The character string tail is printed at the end, obvious
 * strings to pass are "", " ", or "am".
 */
void
prttime(time_t tim, char *tail)
{

	if (tim >= 60) {
		printf("%3ld:", (long) tim/60);
		tim %= 60;
		printf("%02ld", (long) tim);
	} else if (tim > 0)
		printf("    %2ld", (long) tim);
	else
		printf("      ");
	printf("%s", tail);
}

int
get_proc_string(char *name, char *buf)
{
	int fd, n;

    	if ((fd = open(name, 0)) == -1)
		return -1;
	n = read(fd, buf, 4096);
	close(fd);
	return n;
}

void
error(char *fmt, ...)
{
	va_list ap;

	fprintf(stderr, "%s: ", program);
	va_start(ap, fmt);
	vfprintf(stderr, fmt, ap);
	va_end(ap);
	fprintf(stderr, "\n");
}
