/***********************************************************************
*                                                                      *
*               This software is part of the ast package               *
*           Copyright (c) 1990-2006 AT&T Knowledge Ventures            *
*                      and is licensed under the                       *
*                  Common Public License, Version 1.0                  *
*                      by AT&T Knowledge Ventures                      *
*                                                                      *
*                A copy of the License is available at                 *
*            http://www.opensource.org/licenses/cpl1.0.txt             *
*         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
*                                                                      *
*              Information and Software Systems Research               *
*                            AT&T Research                             *
*                           Florham Park NJ                            *
*                                                                      *
*                 Glenn Fowler <gsf@research.att.com>                  *
*                                                                      *
***********************************************************************/
#pragma prototyped
/*
 * Glenn Fowler
 * AT&T Bell Laboratories
 *
 * remote coshell server command interface support
 */

#include "service.h"

#define NOARG	(((long)1)<<(CHAR_BIT*sizeof(long)-2))

static struct
{
	int*	pass;
	int	fd;
} svc;

/*
 * send command message to fd and read ack
 * 0 returned after the last command
 */

static int
cmdsend(int fd, int c, register char* s, register int n, int* code)
{
	int	i;
	int	m;
	int	x;
	CSPOLL	fds[5];
	char	cmd[1024];

	if (cswrite(fd, s, n) != n)
		error(ERROR_SYSTEM|3, "message write error");
	if (c == 'Q') return(0);
	s = cmd;
	if (((n = read(fd, s, sizeof(cmd) - 1)) == 4 || n > 6) && s[0] == 'a' && isdigit(s[2]))
	{
		if (n == 4) n = read(fd, s, sizeof(cmd) - 1);
		else
		{
			n -= 4;
			s += 4;
		}
	}
	if (n < 6 || !isdigit(*(s += 4)))
	{
		if (code) *code = EXIT_NOEXEC;
		return(0);
	}
	if (code && (n = (int)strtol(s, NiL, 10))) *code = n;
	if (state.indirect.con)
	{
		n = 0;
		fds[n].fd = state.indirect.con;
		fds[n++].events = CS_POLL_READ|CS_POLL_CONNECT;
		if (fds[n].fd = state.indirect.err)
			fds[n++].events = CS_POLL_READ;
		fds[n].fd = state.indirect.out;
		fds[n++].events = CS_POLL_READ;
		i = n;
		while (cspoll(fds, n, 0) > 0)
			for (m = 0; m < n; m++)
				if (fds[m].status & CS_POLL_CONNECT)
				{
					if (n < elementsof(fds) && csrecv(fds[m].fd, NiL, &x, 1) == 1)
					{
						svc.pass[x] = 0;
						fds[n].fd = x;
						fds[n].status = 0;
						fds[n++].events = CS_POLL_READ;
					}
				}
				else if (fds[m].status & CS_POLL_READ)
				{
					if (x = svc.pass[fds[m].fd])
					{
						if ((c = read(fds[m].fd, cmd, sizeof(cmd))) > 0)
							cswrite(x, cmd, c);
						else
						{
							close(fds[m].fd);
							fds[m].events = 0;
						}
					}
					else if (csread(fds[m].fd, cmd, 7, CS_EXACT) == 7 && cmd[0] == '#')
						svc.pass[fds[m].fd] = (int)strtol(cmd + 1, NiL, 10);
					else
					{
						close(fds[m].fd);
						fds[m].events = 0;
					}
				}
		while (--i > n)
			if (fds[i].events)
				close(fds[i].fd);
	}
	return(1);
}

#ifdef SIGCONT

/*
 * send a job control kill message to fd
 */

static void
cmdkill(int fd, int sig)
{
	int	n;
	char	buf[32];

	n = sfsprintf(buf, sizeof(buf), "#%05d\nk 0 %d\n", 0, sig);
	sfsprintf(buf, 7, "#%05d\n", n - 7);
	cmdsend(fd, 'Q', buf, n, NiL);
}

/*
 * pass job control signals to the server and self
 */

static void
cmdstop(int sig)
{
	cmdkill(svc.fd, sig);
	signal(sig, SIG_DFL);
	kill(getpid(), sig);
	cmdkill(svc.fd, SIGCONT);
	signal(sig, cmdstop);
}

#endif

/*
 * command loop on server stream fd
 * if ap!=0 then it is an argv of commands
 * otherwise the commands are read from stdin
 * exit code returned
 */

int
command(int fd, char** ap)
{
	register char*	s;
	register int	n;
	int		c;
	int		m;
	int		helped = 0;
	int		code = 0;
	int		prompt;
	char*		t;
	char*		arg;
	char*		att;

#ifdef SIGCONT
	svc.fd = fd;
#ifdef SIGTSTP
	signal(SIGTSTP, cmdstop);
#endif
#ifdef SIGTTIN
	signal(SIGTTIN, cmdstop);
#endif
#ifdef SIGTTOU
	signal(SIGTTOU, cmdstop);
#endif
#endif
	if (state.indirect.con)
	{
		if (!(svc.pass = newof(0, int, sysconf(_SC_OPEN_MAX), 0)))
			error(3, "out of space [pass]");
		svc.pass[state.indirect.err] = 2;
		svc.pass[state.indirect.out] = 1;
	}
	prompt = !ap && isatty(0);
	for (;;)
	{
		if (!ap)
		{
			if (prompt) error(ERROR_PROMPT, "%s> ", CO_ID);
			if (!(s = sfgetr(sfstdin, '\n', 1))) break;
			while (isspace(*s)) s++;
			if (c = *s) s++;
		}
		else if (!(s = *ap++)) break;
		else if (*s == '-' && s[1])
		{
			c = *++s;
			s++;
		}
		if (c == 'q') break;
		while (isspace(*s)) s++;
		switch (c)
		{
		case 0:
		case '#':
			continue;
		case 'a':
		case 'c':
		case 'l':
		case 'o':
			if (!*s && ap && *ap) s = *ap++;
			sfprintf(state.string, "#%05d\nS %c - 0 ", 0, c);
			if (!s || !*s) sfprintf(state.string, "%s\n", (char*)0);
			else sfprintf(state.string, "(%d:%s)\n", strlen(s), s);
			break;
		case 'd':
		case 'f':
		case 'g':
		case 'j':
		case 'k':
		case 's':
		case 't':
		case 'u':
		case 'v':
		case 'Q':
			if (!*s && ap && *ap && **ap != '-') s = *ap++;
			while (isspace(*s)) s++;
			m = isalpha(*s) ? *s++ : '-';
			while (isspace(*s)) s++;
			if (isdigit(*s))
			{
				n = strtol(s, &t, 0);
				s = t;
			}
			else n = *s ? *s : NOARG;
			sfprintf(state.string, "#%05d\nS %c %c %d ", 0, c, m, n);
			if (!s || !*s) sfprintf(state.string, "%s\n", (char*)0);
			else sfprintf(state.string, "(%d:%s)\n", strlen(s), s);
			break;
		case 'r':
		case 'R':
			if (!*s && ap && *ap) s = *ap++;
			m = 0;
			att = s;
			for (;;)
			{
				if (!(n = *s++))
				{
					s--;
					break;
				}
				if (n == '\\')
				{
					if (*s) s++;
				}
				else if (!m)
				{
					if (n == '\'' || n == '"') m = n;
					else if (isspace(*s))
					{
						*s++ = 0;
						while (isspace(*s)) s++;
						break;
					}
				}
				else if (n == m) m = 0;
			}
			sfprintf(state.string, "#%05d\ne %d %d %s %s %s",
				0,
				1,
				c == 'r' ? CO_SILENT : 0,
				NiL,
				NiL, 
				NiL);
			if (!att[0] || att[0] == '-' && !att[1]) sfprintf(state.string, " %s", NiL);
			else sfprintf(state.string, " (%d:%s)", strlen(att), att);
			arg = coinit(CO_SERVER);
			sfprintf(state.string, " (%d:%s)", strlen(arg), arg);
			t = state.buf;
			if (!*(arg = s) && (!ap || !*ap)) arg = "hostname 2>/dev/null || uname -n";
			t += sfsprintf(t, state.buf + state.buflen - t, "%s", arg);
			if (ap)
			{
				while (s = *ap++)
					t += sfsprintf(t, state.buf + state.buflen - t, " %s", s);
				ap--;
			}
			sfprintf(state.string, " (%d:%s)\n", t - state.buf, state.buf);
			break;
		default:
			if (ap) error(3, "%c: unknown command -- use h for help", c);
			error(2, "%c: unknown command", c);
			if (helped) continue;
			/*FALLTHROUGH*/
		case 'h':
		case '?':
			helped++;
			error(0, "\
\n\
	a host ...	change host shell attributes\n\
	c host ...	close host shell\n\
	d [level]	set debug trace stderr [and level]\n\
	f [fd]		internal fd status [drop CON fd]\n\
	g		global state\n\
	h		help\n\
	j		job status\n\
	k [ckst] job	terminate [continue|kill|stop|term] job\n\
	l host		list matching host names\n\
	o host ...	open host shell\n\
	q		quit\n\
	r host [cmd]	run cmd [hostname] on host\n\
	s [aelopst]	shell status [attr|every|long|open|pid|sched|temp]\n\
	t		shell and user connection totals\n\
	u		user connection status\n\
	v		server version\n\
	Q		kill server and quit\n\
");
			continue;
		}
		n = sfstrtell(state.string);
		sfstrseek(state.string, 0, SEEK_SET);
		sfprintf(state.string, "#%05d\n", n - 7);
		if (!cmdsend(fd, c, sfstrseek(state.string, 0, SEEK_SET), n, &code)) break;
	}
	return(code);
}

/*
 * return file mode given fd
 * if must!=0 then 0 returned if fstat fails
 */

static char*
ffmtmode(int fd, int must)
{
	char*		p;
	struct stat	st;

	if (fstat(fd, &st)) return(must ? 0 : "**ERROR** ");
	p = fmtmode(st.st_mode ? st.st_mode : (S_IRUSR|S_IWUSR), 0);
	if (!st.st_mode)
#ifdef S_ISSOCK
		*p = 's';
#else
		*p = 'p';
#endif
	return(p);
}

/*
 * do CO_server op for user fd
 */

void
server(int fd, int op, int sub, int arg, char* dat)
{
	register char*		s;
	register int		n;
	register Connection_t*	con;
	int			u;
	int			hdr;
	char*			t;
	char*			v;
	Cojob_t*		jp;
	Coshell_t*		sp;
	Coshell_t**		sv;
	Coattr_t		attr;
	Coshell_t		tot;
	char			num[4][12];

	con = state.con;
	hdr = 0;
	switch (op)
	{
	case 0:
		break;
	case 'a':
		if (dat)
		{
			t = tokopen(dat, 1);
			while (s = tokread(t))
				if (!search(SET, s, NiL, NiL))
					error(ERROR_OUTPUT|2, con[fd].info.user.fds[2], "%s: invalid host name", s);
			tokclose(t);
		}
		break;
	case 'c':
		if (dat)
		{
			t = tokopen(dat, 1);
			while (s = tokread(t))
			{
				if (!(sp = search(GET, s, NiL, NiL))) error(ERROR_OUTPUT|2, con[fd].info.user.fds[2], "%s: not found", s);
				else if (!sp->fd) error(ERROR_OUTPUT|2, con[fd].info.user.fds[2], "%s: host not open", s);
				else shellclose(sp, con[fd].info.user.fds[2]);
			}
			tokclose(t);
		}
		break;
	case 'd':
		if (arg == 't')
		{
			cs.flags |= CS_TEST;
			break;
		}
		else if (arg == 'T')
		{
			cs.flags &= ~CS_TEST;
			break;
		}
		arg = (arg == NOARG) ? error_info.trace : -arg;
		message((error_info.trace, "level=%d output=%s", -arg, dat ? dat : cspath(state.indirect.con ? 2 : con[fd].info.user.fds[2], 0)));
		error_info.trace = arg;
		if (dat)
		{
			if ((n = open(dat, O_WRONLY|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)) >= 0)
			{
				close(2);
				dup(n);
				close(n);
			}
		}
		else if (!state.indirect.con)
		{
			close(2);
			dup(con[fd].info.user.fds[2]);
		}
		break;
	case 'f':
		if (arg == NOARG)
		{
			sfprintf(state.string, "CON  TYPE  INFO\n");
			for (n = 0; n <= state.fdtotal; n++)
				switch (con[n].type)
				{
				case ANON:
					sfprintf(state.string, "%3d  anon\n", n);
					break;
				case DEST:
					sfprintf(state.string, "%3d  dest\n", n);
					break;
				case IDENT:
					sfprintf(state.string, "%3d  ident\n", n);
					break;
				case MESG:
					sfprintf(state.string, "%3d  mesg  %s %s\n", n, ffmtmode(n, 0), state.mesg);
					break;
				case PASS:
					if (jp = con[n].info.pass.job) sfprintf(state.string, "%3d  pass  %-3d %s %d\n", n, con[n].info.pass.fd, jp->shell->name, jp->pid);
					else sfprintf(state.string, "%3d  pass  %-3d zombie\n", n, con[n].info.pass.fd);
					break;
				case POLL:
					sfprintf(state.string, "%3d  poll  %s %s\n", n, ffmtmode(n, 0), state.service);
					break;
				case PUMP:
					sfprintf(state.string, "%3d  pump  %s %s\n", n, ffmtmode(n, 0), state.pump);
					break;
				case SHELL:
					sfprintf(state.string, "%3d  shell %s\n", n, con[n].info.shell->name);
					break;
				case USER:
					if (con[n].info.user.fds[0] == n) sfprintf(state.string, "%3d  query %-3d %-3d %s\n", n, con[n].info.user.fds[1], con[n].info.user.fds[2], (sp = con[n].info.user.home) ? sp->name : "");
					else sfprintf(state.string, "%3d  user  %-3d %-3d %-3d %s\n", n, con[n].info.user.fds[0], con[n].info.user.fds[1], con[n].info.user.fds[2], (sp = con[n].info.user.home) ? sp->name : "");
					break;
				case UCMD:
					t = "ucmd";
					goto path;
				case UERR:
					t = "uerr";
					goto path;
				case UOUT:
					t = "uout";
					goto path;
				case 0:
					t = "open";
				path:
					if (v = ffmtmode(n, 1)) sfprintf(state.string, "%3d  %s  %s %s\n", n, t, v, cspath(n, 0));
					else if (con[n].type) sfprintf(state.string, "%3d  %s  error\n", n, t);
					break;
				}
		}
		else if (arg >= state.fdtotal || !con[arg].type) error(ERROR_OUTPUT|2, con[fd].info.user.fds[2], "cannot drop CON %d", arg);
		else drop(arg);
		break;
	case 'g':
		/*
		 * string values
		 */

		sfprintf(state.string, "\n");
		sfprintf(state.string, "   version  %-.*s\n", strlen(version) - 1, version);
		sfprintf(state.string, "      mesg  %s\n", state.mesg);
		if (state.identify)
			sfprintf(state.string, "  identify  %s\n", fmtesc(state.identify));
		if (state.migrate)
			sfprintf(state.string, "   migrate  %s\n", state.migrate);
		if (state.profile && *state.profile)
			sfprintf(state.string, "   profile  %s\n", state.profile);
		sfprintf(state.string, "      pump  %s\n", state.pump);
		sfprintf(state.string, "    remote  %s\n", state.remote);
		sfprintf(state.string, "   service  %s\n", state.service);
		sfprintf(state.string, "     shell  %s\n", state.sh);

		/*
		 * readonly values
		 */

		sfprintf(state.string, "\n");

		sfprintf(state.string, "    access  %-7s", state.access < cs.time ? "EXPIRED" : fmtelapsed(state.access - cs.time, 1));
		sfprintf(state.string, "  joblimit  %-6d ", state.joblimit);
		sfprintf(state.string, "       pid  %-6d ", getpid());
		sfprintf(state.string, "       sys  %-6s\n", fmtelapsed(state.sys, CO_QUANT));


		sfprintf(state.string, "     clock  %-6s ", fmtelapsed(cs.time - state.clock, 1));
		sfprintf(state.string, "      jobs  %-6d ", state.jobs);
		sfprintf(state.string, "      real  %-6s ", fmtelapsed(state.real, 1));
		sfprintf(state.string, "      user  %-6s\n", fmtelapsed(state.user, CO_QUANT));

		sfprintf(state.string, "      cmds  %-6d ", state.cmds);
		sfprintf(state.string, "   jobwait  %-6d ", state.jobwait);
		sfprintf(state.string, "   running  %-6d ", state.running);
		sfprintf(state.string, "     users  %-6d\n", state.users);

		sfprintf(state.string, "   connect  %-6d ", state.connect);
		sfprintf(state.string, "      open  %-6d ", state.open);
		sfprintf(state.string, "    shells  %-6d ", state.shells);
		sfprintf(state.string, "    wakeup  %-6s\n", fmtelapsed(state.wakeup, 1000));

		sfprintf(state.string, "   fdtotal  %-6d ", state.fdtotal);
		sfprintf(state.string, "  override  %-6d ", state.override);
		sfprintf(state.string, " shellwait  %-6d\n", state.shellwait);

		/*
		 * global values
		 */

		sfprintf(state.string, "\n");

		sfprintf(state.string, "      busy  %-6s ", fmtelapsed(state.busy, 1));
		sfprintf(state.string, "     grace  %-6s ", fmtelapsed(state.grace, 1));
		sfprintf(state.string, "    percpu  %-6d ", state.percpu);
		sfprintf(state.string, "   peruser  %-6d\n", state.peruser);

		sfprintf(state.string, "     debug  %-6d ", -error_info.trace);
		sfprintf(state.string, "   maxidle  %-6s ", fmtelapsed(state.maxidle, 1));
		sfprintf(state.string, "   perhost  %-6d ", state.perhost);
		sfprintf(state.string, "      pool  %-6d\n", state.pool);

		sfprintf(state.string, "   disable  %-6s ", fmtelapsed(state.disable, 1));
		sfprintf(state.string, "   maxload  %-6s ", fmtfloat(state.maxload));
		sfprintf(state.string, " perserver  %-6d\n", state.perserver);

		/*
		 * done
		 */

		sfprintf(state.string, "\n");
		break;
	case 'j':
		for (jp = state.job; jp <= state.jobmax; jp++)
			if (jp->pid)
			{
				switch (jp->pid)
				{
				case QUEUE:
					t = "QUEUE";
					break;
				case START:
					t = "START";
					break;
				case WARP:
					t = "WARP";
					break;
				default:
					sfsprintf(t = num[0], sizeof(num[0]), "%d", jp->pid);
					break;
				}
				if (!hdr)
				{
					hdr = 1;
					sfprintf(state.string, "JOB USR RID   PID   TIME HOST             LABEL\n");
				}
				v = con[jp->fd].info.user.attr.label;
				sfprintf(state.string, "%3d%4d%4d%6s%7s%c%-16s %s%s%s", jp - state.job, jp->fd, jp->rid, t, fmtelapsed(cs.time - jp->start, 1), jp->ref <= 0 ? '*' : jp->lost ? (jp->lost > cs.time ? '?' : '!') : ' ', jp->shell->name, v, *v ? " " : "", jp->label);
				if (jp->sig) sfprintf(state.string, " [%s]", fmtsignal(jp->sig));
				sfputc(state.string, '\n');
			}
		break;
	case 'k':
		if (arg == NOARG)
			error(ERROR_OUTPUT|2, con[fd].info.user.fds[2], "job id required");
		else if ((jp = state.job + arg) > state.jobmax || !jp->pid)
			error(ERROR_OUTPUT|2, con[fd].info.user.fds[2], "%d: invalid job id", arg);
		else switch (sub)
		{
#ifdef SIGCONT
		case 'c':
			jobkill(jp, SIGCONT);
			break;
#endif
		case 'k':
			jobkill(jp, SIGKILL);
			break;
#ifdef SIGSTOP
		case 's':
			jobkill(jp, SIGSTOP);
			break;
#endif
		case 't':
		case '-':
			jobkill(jp, SIGTERM);
			break;
		default:
			error(ERROR_OUTPUT|2, con[fd].info.user.fds[2], "%c: invalid signal id", sub);
			break;
		}
		break;
	case 'l':
		sv = state.shellv;
		sp = state.shell;
		if (dat && *dat) attributes(dat, &attr, NiL);
		else attr = con[fd].info.user.attr;
		do
		{
			if (match(sp, &attr, 0))
				*sv++ = sp;
		} while ((sp = sp->next) != state.shell);
		if (n = sv - state.shellv)
		{
			*sv = 0;
			strsort((char**)(sv = state.shellv), n, byrank);
			if (dat && (attr.global.set & SETPOOL) && attr.global.pool > 0 && attr.global.pool < n) sv[attr.global.pool] = 0;
			while (sp = *sv++)
				sfprintf(state.string, "%s\n", sp->name);
		}
		break;
	case 'o':
		if (dat)
		{
			t = tokopen(dat, 1);
			while (s = tokread(t))
			{
				if (!(sp = search(NEW, s, NiL, NiL))) error(ERROR_OUTPUT|2, con[fd].info.user.fds[2], "%s: invalid host name", s);
				else if (sp->fd) error(ERROR_OUTPUT|2, con[fd].info.user.fds[2], "%s: host already open", s);
				else shellopen(sp, con[fd].info.user.fds[2]);
			}
			tokclose(t);
		}
		break;
	case 's':
		sv = state.shellv;
		sp = state.shell;
		do
		{
			if (sub == 'a' || sub == 'e' || sp->fd || (sub == 'l' || sub == 's') && sp->temp && !(sp->flags & IGN)) *sv++ = sp;
		} while ((sp = sp->next) != state.shell);
		if (n = sv - state.shellv)
		{
			*sv = 0;
			strsort((char**)(sv = state.shellv), n, sub == 'a' || sub == 'e' || sub == 'p' ? byname : sub == 't' ? bytemp : byrank);
			switch (sub)
			{
			case 'a':
				sfprintf(state.string, "HOST         CONF\n%-12.12s busy=%s grace=%s name=%s percpu=%d perserver=%d peruser=%d pool=%d\n", CS_HOST_LOCAL, fmtelapsed(state.busy, 1), fmtelapsed(state.grace, 1), state.shell->name, state.percpu, state.perserver, state.peruser, state.pool);
				break;
			case 'o':
			case 's':
			case 't':
				sfprintf(state.string, "CON OPEN USERS     UP CONNECT UPDATE OVERRIDE   IDLE   TEMP     RANK HOST\n");
				break;
			case 'p':
				sfprintf(state.string, "PID    HOST\n%-6d %s\n", getpid(), cs.cs);
				break;
			default:
				sfprintf(state.string, "CON JOBS TOTAL   USER    SYS   IDLE CPU  LOAD RATING  BIAS TYPE     HOST\n");
				break;
			}
			while (sp = *sv++)
			{
				switch (sub)
				{
				case 'a':
					sfprintf(state.string, "%-12.12s rating=%s", sp->name, fmtfloat(sp->rating));
					if (sp->flags & IGN)
						sfprintf(state.string, " ignore=1");
					if (sp->bias != BIAS)
						sfprintf(state.string, " bias=%s", fmtfloat(sp->bias));
					if (sp->cpu != 1)
						sfprintf(state.string, " cpu=%d", sp->cpu);
					if (sp->scale != sp->cpu)
						sfprintf(state.string, " scale=%d", sp->scale);
					if (sp->idle)
						sfprintf(state.string, " idle=%s", fmtelapsed(sp->idle, 1));
					if (sp->type[0] != '*' || sp->type[1])
						sfprintf(state.string, " type=%s", sp->type);
					if (sp->access)
						sfprintf(state.string, " access=%s", sp->access);
					if (sp->bypass)
						sfprintf(state.string, " bypass=%s", sp->bypass);
					if (sp->shell[0])
						sfprintf(state.string, " shell=%s", sp->shell);
					sfprintf(state.string, " %s\n", sp->misc);
					break;
				case 'p':
					if (sp->fd > 0) sfprintf(state.string, "%-6d %s\n", sp->pid, sp->name);
					break;
				default:
					if (sp->fd < 0) t = "+";
					else if (sp->fd > 0) sfsprintf(t = num[0], sizeof(num[0]), "%d", sp->fd);
					else t = (sp->temp && !(sp->flags & IGN)) ? "-" : "@";
					if (sub == 'o' || sub == 's' || sub == 't') sfprintf(state.string, "%3s%c%4d%6d%7s%8s%7s%7s/%d%7s%7s%9s %s\n"
					, t
					, sp->fd > 0 && state.con[sp->fd].error ? '?' : ' '
					, sp->open
					, sp->stat.users
					, sp->stat.up < 0 ? "DOWN" : fmtelapsed(sp->stat.up, 1)
					, sp->start ? fmtelapsed(cs.time - sp->start, 1) : ""
					, fmtelapsed(sp->update > cs.time ? sp->update - cs.time : 0, 1)
					, fmtelapsed(sp->override > cs.time ? sp->override - cs.time : 0, 1)
					, sp->home
					, fmtelapsed(sp->idle, 1)
					, fmtfloat(sp->temp >> (CHAR_BIT * sizeof(sp->temp) / 2))
					, fmtfloat(sp->rank)
					, sp->name
					);
					else sfprintf(state.string, "%3s %4d%6d%7s%7s%7s%c%3d%6s%7s%6s %-8.8s %s\n"
					, t
					, sp->running
					, sp->total
					, fmtelapsed(sp->user, CO_QUANT)
					, fmtelapsed(sp->sys, CO_QUANT)
					, fmtelapsed(sp->stat.up < 0 ? -sp->stat.up : sp->stat.idle, 1)
					, sp->stat.up < 0 ? '-' : (sp->mode & SHELL_DISABLE) ? '$' : (sp->mode & SHELL_DENIED) ? '!' : sp->idle && sp->stat.idle < sp->idle ? (sp->home ? '~' : '*') : ' '
					, sp->cpu
					, fmtfloat(sp->stat.load / sp->scale)
					, fmtfloat(sp->rating)
					, fmtfloat(sp->bias)
					, sp->type
					, sp->name
					);
					break;
				}
			}
		}
		break;
	case 't':
		memzero(&tot, sizeof(tot));
		sfprintf(state.string, "SHELLS  USERS    JOBS  CMDS     UP   REAL   USER    SYS     CPU  LOAD RATING\n");
		tot.running = state.running;
		sp = state.shell;
		do
		{
			if (sp->fd)
			{
				tot.fd++;
				tot.cpu += sp->cpu;
				tot.stat.load += sp->cpu * sp->stat.load / sp->scale;
				tot.rating += sp->cpu * sp->rating;
			}
		} while ((sp = sp->next) != state.shell);
		if (tot.cpu)
		{
			tot.stat.load /= tot.cpu;
			tot.rating /= tot.cpu;
		}
		for (n = u = 0; n <= state.fdtotal; n++)
			if (con[n].type == USER) u++;
		if (state.running)
		{
			state.real += cs.time - state.clock;
			state.clock = cs.time;
		}
		sfsprintf(num[0], sizeof(num[0]), "%d/%d", tot.fd, state.shells);
		sfsprintf(num[1], sizeof(num[1]), "%d/%d", u, state.users);
		sfsprintf(num[2], sizeof(num[2]), "%d/%d", tot.running, state.jobs);
		sfsprintf(num[3], sizeof(num[3]), "%d/%d+%d", tot.cpu, state.pool, state.override);
		sfprintf(state.string, "%6s%7s%8s%6d%7s%7s%7s%7s%8s%6s%7s\n", num[0], num[1], num[2], state.cmds, fmtelapsed(cs.time - state.start, 1), fmtelapsed(state.real, 1), fmtelapsed(state.user, CO_QUANT), fmtelapsed(state.sys, CO_QUANT), num[3], fmtfloat(tot.stat.load), fmtfloat(tot.rating));
		break;
	case 'u':
		for (n = 0; n <= state.fdtotal; n++)
			if (con[n].type == USER)
			{
				if (!hdr)
				{
					hdr = 1;
					sfprintf(state.string, "CON   PID JOBS TOTAL TTY                          LABEL\n");
				}
				sfprintf(state.string, "%3d%6d%5d%6d %-28s %s\n", n, con[n].info.user.pid, con[n].info.user.running, con[n].info.user.total, cspath(con[n].info.user.fds[2], 0), con[n].info.user.attr.label);
			}
		break;
	case 'v':
		sfprintf(state.string, "%-.*s\n", strlen(version) - 1, version);
		break;
	case 'Q':
		exit(arg);
	default:
		error(ERROR_OUTPUT|2, con[fd].info.user.fds[2], "%c: unknown server op", op);
		break;
	}
	if ((n = sfstrtell(state.string)) > 0)
		cswrite(con[fd].info.user.fds[1], sfstrseek(state.string, 0, SEEK_SET), n);
}
