/***********************************************************************
*                                                                      *
*               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 miscellaneous support
 */

#include "service.h"

#include <hashkey.h>

#define STATS(a,b)	do{STAT1(a);STAT1(b);}while(0)
#define STAT1(p)	do{if((p)->update<=cs.time+UPDATE)update(p);}while(0)

typedef struct
{
	char*		data;
	int		size;
} String_t;

/*
 * create a remote service connect stream
 * malloc'd stream pathname is returned
 */

char*
stream(int type, register char* name)
{
	register int	fd;
	register char*	path;

	if ((fd = csopen("/dev/tcp/local/normal", CS_OPEN_CREATE)) < 0)
		error(ERROR_SYSTEM|3, "%s: cannot create connect stream", name);
	if (!(path = strdup(cspath(fd, 0))))
		error(3, "out of space [%s]", name);
	state.con[fd].type = type;
	csfd(fd, CS_POLL_READ);
	message((-1, "%s connect stream path is %s", name, path));
	return(path);
}

/*
 * copy string v length n into s max length m
 * v==0 || v=="-" converted to "*"
 */

static void
copystring(char* s, int m, const char* v, int n)
{
	if (n <= 0) n = strlen(v);
	if (n <= 0 || n == 1 && *v == '-')
	{
		n = 1;
		s[0] = '*';
	}
	else
	{
		if (n >= m) n = m - 1;
		memcpy(s, v, n);
	}
	s[n] = 0;
}

/*
 * save string s length n into p
 * v==0 || v=="-" converted to 0
 */

static char*
savestring(String_t* p, const char* v, int n)
{
	if (n <= 0 || n == 1 && *v == '-')
		return(0);
	if (n >= p->size)
	{
		p->size = roundof(n + 1, 32);
		if (!(p->data = newof(p->data, char, p->size, 0)))
			error(3, "out of space [savestring]");
	}
	memcpy(p->data, v, n);
	p->data[n] = 0;
	return(p->data);
}

#define LT	(1<<0)
#define EQ	(1<<1)
#define GT	(1<<2)
#define NOT	(1<<3)

#define NE	(NOT|EQ)
#define LE	(LT|EQ)
#define GE	(GT|EQ)

/*
 * evaluate n1 op n2
 */

static int
miscop(unsigned long n1, int op, unsigned long n2)
{
	switch (op)
	{
	case EQ:
		return(n1 == n2);
	case NE:
		return(n1 != n2);
	case LT:
		return(n1 < n2);
	case LE:
		return(n1 <= n2);
	case GE:
		return(n1 >= n2);
	case GT:
		return(n1 > n2);
	}
	return(0);
}

/*
 * compare the string value vp with the string (sb,se-1)
 */

static int
misccmp(char* vp, int op, char* sb, char* se)
{
	int	c;
	int	r;

	c = *se;
	*se = 0;
	switch (op)
	{
	case EQ:
	case NE:
		r = (strmatch(vp, sb) || strchr(vp, '|') && strmatch(sb, vp)) == (op == EQ);
		break;
	case LT:
		r = strcoll(vp, sb) < 0;
		break;
	case LE:
		r = strcoll(vp, sb) <= 0;
		break;
	case GE:
		r = strcoll(vp, sb) >= 0;
		break;
	case GT:
		r = strcoll(vp, sb) > 0;
		break;
	default:
		r = 0;
		break;
	}
	*se = c;
	return(r);
}

/*
 * return attribute value for s in sp
 * if set!=0 then non-zero returned if s is set in sp and e points to
 * the matched attribute in sp
 * otherwise value is returned and e is set to point to after the
 * end of the attribute id in s
 * if set!=0 && rep!=0 then replace value in sp with s up to rep
 */

static long
miscget(Coshell_t* sp, const char* as, int set, char** e, char* rep)
{
	register char*	s = (char*)as;
	register char*	cp;
	register char*	np;
	register int	c;
	unsigned long	x;
	int		no;
	int		op;
	char*		bp;
	char*		sb;
	char*		se;
	char*		st;
	char*		ee;
	Coshell_t*	osp;
	Coshell_t*	tsp;

	if (s && (isalpha(*s) || *s == '_'))
	{
		if (no = *s == 'n' && *(s + 1) == 'o' && (isalnum(*(s + 2)) || *(s + 2) == '_')) s += 2;
		osp = 0;
		bp = s;
		x = 0;
		sb = bp + HASHKEYMAX;
		while (isalnum(c = *s++) || c == '_')
			if (s <= sb)
				x = HASHKEYPART(x, c);
		s--;
		sb = 0;
		if (!set)
		{
			for (cp = s; isspace(*cp); cp++);
			if (*cp == '@')
			{
				osp = sp;
				while (isspace(*++cp));
				se = cp;
				while (isalnum(*cp) || *cp == '_') cp++;
				*e = cp;
				c = *cp;
				*cp = 0;
				sp = search(GET, se, NiL, NiL);
				*cp = c;
				if (!sp) return(0);
				while (isspace(*cp)) cp++;
				*e = cp;
			}
			else *e = (char*)s;
			switch (*cp)
			{
			case '<':
				op = LT;
				break;
			case '>':
				op = GT;
				break;
			case '=':
				op = EQ;
				break;
			case '!':
				op = NOT;
				break;
			default:
				op = 0;
				break;
			}
			if (op)
			{
				if (*++cp == '=')
				{
					cp++;
					op |= EQ;
				}
				while (isspace(*cp)) cp++;
				if ((c = *cp) == '"' || c == '\'')
				{
					sb = ++cp;
					while (*cp && *cp != c) cp++;
					if (*(se = cp)) cp++;
					ee = cp;
				}
				else if (isalpha(*cp) || *cp == '_')
				{
					se = cp;
					while (isalnum(*++se));
					st = se;
					while (isspace(*se)) se++;
					if (*se == '@')
					{
						sb = cp;
						while (isspace(*++se));
						cp = se;
						while (isalnum(*cp) || *cp == '_') cp++;
						c = *cp;
						*cp = 0;
						tsp = search(GET, se, NiL, NiL);
						*cp = c;
						ee = cp;
						if (tsp)
						{
							c = *st;
							*st = 0;
							if ((st - sb) == 4 && (*sb == 'n' && !strncmp(sb, "name", 4) || *sb == 't' && !strncmp(sb, "type", 4)))
							{
								sb = *sb == 'n' ? tsp->name : tsp->type;
								se = sb + strlen(sb);
							}
							else if (miscget(tsp, sb, 1, &sb, NiL))
								for (se = sb; *se && !isspace(*se); se++);
							else sb = se = "";
							*st = c;
						}
						else sb = se = "";
					}
				}
			}
			switch (x)
			{
			case HASHKEY4('b','i','a','s'):
				return(osp ? osp->bias == sp->bias : op ? miscop(sp->bias, op, strton(cp, e, NiL, 100)) : sp->bias);
			case HASHKEY3('c','p','u'):
				return(osp ? osp->cpu == sp->cpu : sp->cpu);
			case HASHKEY3('d','a','y'):
				return(state.tm->tm_wday);
			case HASHKEY3('g','i','d'):
				x = 0;
				if (sb)
				{
					gid_t	gid;

					*e = ee;
					if (c = *se) *se = 0;
					do
					{
						if (ee = strchr(sb, '|')) *ee = 0;
						gid = strgid(sb);
						if (sb = ee) *sb++ = '|';
						for (no = 0; state.gids[no] != (gid_t)-1; no++)
							if (gid == state.gids[no])
							{
								x = 1;
								break;
							}
					} while (!x && sb);
					if (c) *se = c;
				}
				return(miscop(1, op, x));
			case HASHKEY4('h','o','u','r'):
				return(state.tm->tm_hour);
			case HASHKEY4('i','d','l','e'):
				STATS(sp, osp);
				return(osp ? osp->stat.idle == sp->stat.idle : op ? miscop(sp->stat.idle, op, strelapsed(cp, e, 1)) : sp->stat.idle);
			case HASHKEY4('j','o','b','s'):
				return(osp ? osp->running == sp->running : sp->running);
			case HASHKEY4('l','o','a','d'):
				STATS(sp, osp);
				return(osp ? (osp->stat.load / osp->scale) == (sp->stat.load / sp->scale) : op ? miscop(sp->stat.load / sp->scale, op, strton(cp, e, NiL, 100)) : sp->stat.load / sp->scale);
			case HASHKEY3('m','i','n'):
				return(state.tm->tm_min);
			case HASHKEY6('m','i','n','i','d','l'):
				return(osp ? osp->idle == sp->idle : op ? miscop(sp->idle, op, strelapsed(cp, e, 1)) : sp->idle);
			case HASHKEY4('n','a','m','e'):
				if (sb)
				{
					*e = ee;
					return(misccmp(sp->name, op, sb, se));
				}
				if (osp) return(streq(osp->name, sp->name));
				return(*sp->name != 0);
			case HASHKEY4('o','p','e','n'):
				return(osp ? (!osp->fd) == (!sp->fd) : sp->fd);
			case HASHKEY6('r','a','t','i','n','g'):
				return(osp ? osp->rating == sp->rating : op ? miscop(sp->rating, op, strton(cp, e, NiL, 100)) : sp->rating);
			case HASHKEY4('t','y','p','e'):
				if (sb)
				{
					*e = ee;
					return(misccmp(sp->type, op, sb, se));
				}
				if (osp) return(streq(osp->type, sp->type));
				return(*sp->type != 0);
			case HASHKEY3('u','i','d'):
				x = 0;
				if (sb)
				{
					uid_t	uid;

					*e = ee;
					if (c = *se) *se = 0;
					do
					{
						if (ee = strchr(sb, '|')) *ee = 0;
						uid = struid(sb);
						if (sb = ee) *sb++ = '|';
						if (uid == state.uid)
						{
							x = 1;
							break;
						}
					} while (sb);
					if (c) *se = c;
				}
				return(miscop(1, op, x));
			case HASHKEY2('u','p'):
				STATS(sp, osp);
				return(osp ? osp->stat.up == sp->stat.up : sp->stat.up);
			}
		}
		cp = sp->misc;
		for (;;)
		{
			if (set) *e = cp;
			np = bp;
			while (*np++ == *cp++)
				if (np >= s)
				{
					if (*cp == 0 || *cp == ' ')
					{
						if (set && rep)
						{
						replace:
							np = *e;
							if (*cp++)
								while (*np = *cp++) np++;
						add:
							if (!no && np < rep)
							{
								cp = np;
								if (np > sp->misc) *np++ = ' ';
								for (;;)
								{
									if (np >= rep)
									{
										np = cp;
										break;
									}
									if (!(*np = *bp++) || isspace(*np)) break;
									np++;
								}
							}
							*np = 0;
						}
						return(1);
					}
					if (*cp++ == '=')
					{
						if (set)
						{
							if (rep)
							{
								while (*cp && *cp != ' ') cp++;
								goto replace;
							}
							*e = cp;
							return(1);
						}
						if (sb)
						{
							*e = ee;
							for (np = cp; *np && *np != ' '; np++);
							c = *np;
							*np = 0;
							set = misccmp(cp, op, sb, se);
							*np = c;
							return(set);
						}
						if (osp)
						{
							if (!miscget(osp, bp, 1, &bp, NiL)) return(0);
							for (np = cp; *np && *np != ' '; np++);
							return(!strncmp(bp, cp, np - cp));
						}
						set = strton(cp, &bp, NiL, 0);
						if (*bp && *bp != ' ') set = 1;
						return(set);
					}
					break;
				}
			cp--;
			for (;;)
			{
				if (!*cp)
				{
					if (set && rep)
					{
						np = cp;
						goto add;
					}
					if (sb)
					{
						*e = ee;
						return(misccmp("", op, sb, se));
					}
					if (osp) return(!miscget(osp, bp, 1, &bp, NiL));
					return(0);
				}
				if (*cp++ == ' ') break;
			}
		}
	}
	return(0);
}

/*
 * parse host attributes from s into p
 * d points to optional default attributes
 */

void
attributes(register char* s, register Coattr_t* p, Coattr_t* d)
{
	register char*	b;
	register char*	v;
	char*		e;
	char*		r;
	char*		u;
	int		n;
	int		m;
	int		c;
	int		t;
	int		q;
	int		expr;
	unsigned long	x;
	Coshell_t*	sp;

	if (d) *p = *d;
	else p->set = p->global.set = 0;
	if (s)
	{
		expr = 0;
		for (;;)
		{
			while (isspace(*s) || *s == ',') s++;
			if (!(t = *(b = s)) || t == '#') break;
			r = v = 0;
			m = 0;
			x = 0;
			for (;;)
			{
				switch (c = *s++)
				{
				case '=':
					if (*s != '=')
					{
						if (!v)
						{
							v = s;
							if ((q = *s) == '"' || q == '\'')
							{
								v++;
								while (*++s && *s != q)
									if (*s == '\\' && *(s + 1)) s++;
								n = s - v;
								if (*s) s++;
								break;
							}
						}
						continue;
					}
					/*FALLTHROUGH*/
				case '|':
				case '&':
				case '<':
				case '>':
				case '!':
				case '(':
				case ')':
					if (!v)
					{
						v = s;
						t = '*';
						if (!expr)
						{
							expr = 2;
							if (p->set & SETMISC) u = p->misc;
						}
					}
					continue;
				case '@':
					if (!r) r = s;
					continue;
				case 0:
				case ',':
				case ' ':
				case '\t':
				case '\n':
					if (!v)
					{
						v = b;
						t = (p->set & SETNAME) ? '*' : 'n';
					}
					n = s - v - 1;
					if (!c) s--;
					break;
				default:
					if (m++ < HASHKEYMAX)
					{
						if (m == (s - b))
						{
							if (islower(c))
								x = HASHKEYPART(x, c);
							else
							{
								if (isalnum(c) || c == '_')
									x = 0;
								m = HASHKEYMAX;
							}
						}
						else m = 6;
					}
					continue;
				}
				break;
			}
			if (r && t != '*')
			{
				v = r;
				m = v[n];
				v[n] = 0;
				sp = search(GET, v, NiL, NiL);
				v[n] = m;
				if (!sp) continue;
			}
			else sp = 0;
			if (v == b) x = (p->set & SETNAME) ? 0 : HASHKEY4('n','a','m','e');
			switch (x)
			{
			case HASHKEY6('a','c','c','e','s','s'):
				p->set |= SETACCESS;
				if (sp) p->access = sp->access;
				else
				{
					static String_t	save;

					p->access = savestring(&save, v, n);
				}
				continue;
			case HASHKEY4('b','i','a','s'):
				p->set |= SETBIAS;
				p->bias = sp ? sp->bias : strton(v, NiL, NiL, 100);
				continue;
			case HASHKEY4('b','u','s','y'):
				if (!sp)
				{
					p->global.set |= SETBUSY;
					p->global.busy = strelapsed(v, NiL, 1);
				}
				continue;
			case HASHKEY6('b','y','p','a','s','s'):
				p->set |= SETBYPASS;
				if (sp) p->bypass = sp->bypass;
				else
				{
					static String_t	save;

					p->bypass = savestring(&save, v, n);
				}
				continue;
			case HASHKEY3('c','p','u'):
				if (p->cpu = sp ? sp->cpu : (int)strtol(v, NiL, 0)) p->set |= SETCPU;
				continue;
			case HASHKEY5('d','e','b','u','g'):
				if (!sp)
				{
					p->global.set |= SETDEBUG;
					p->global.debug = -strton(v, NiL, NiL, 0);
				}
				continue;
			case HASHKEY6('d','i','s','a','b','l'):
				if (!sp)
				{
					p->global.set |= SETDISABLE;
					p->global.disable = strelapsed(v, NiL, 1);
				}
				continue;
			case HASHKEY4('f','i','l','e'):
				if (!sp)
				{
					static String_t	save;

					p->global.set |= SETFILE;
					p->global.file = savestring(&save, v, n);
				}
				continue;
			case HASHKEY5('g','r','a','c','e'):
				if (!sp)
				{
					p->global.set |= SETGRACE;
					p->global.grace = strelapsed(v, NiL, 1);
				}
				continue;
			case HASHKEY6('i','d','e','n','t','i'):
				if (!sp)
				{
					static String_t	save;

					if (streq(v, "0")) n = strlen(v = "-");
					else if (streq(v, "1")) n = strlen(v = "[ %s ]\n");
					p->global.set |= SETIDENTIFY;
					if (p->global.identify = savestring(&save, v, n))
						stresc(p->global.identify);
				}
				continue;
			case HASHKEY4('i','d','l','e'):
			case HASHKEY6('m','i','n','i','d','l'):
				p->set |= SETIDLE;
				p->idle = sp ? sp->idle : strelapsed(v, NiL, 1);
				continue;
			case HASHKEY6('i','g','n','o','r','e'):
				p->set |= SETIGNORE;
				p->ignore = sp ? (sp->flags & IGN) : (int)strtol(v, NiL, 0);
				continue;
			case HASHKEY5('l','a','b','e','l'):
				if (!sp && n)
				{
					p->set |= SETLABEL;
					if (n >= sizeof(p->label))
					{
						v += n - sizeof(p->label); 
						n = sizeof(p->label) - 1;
					}
					memcpy(p->label, v, n);
					p->label[n] = 0;
				}
				continue;
			case HASHKEY6('m','a','x','i','d','l'):
				if (!sp)
				{
					p->global.maxidle = (int)strtol(v, NiL, 0);
					p->global.set |= SETMAXIDLE;
				}
				continue;
			case HASHKEY6('m','a','x','l','o','a'):
				if (!sp && (p->global.maxload = strton(v, NiL, NiL, 100)) >= 0)
					p->global.set |= SETMAXLOAD;
				continue;
			case HASHKEY6('m','i','g','r','a','t'):
				if (!sp)
				{
					static String_t	save;

					p->global.set |= SETMIGRATE;
					p->global.migrate = savestring(&save, v, n);
				}
				continue;
			case HASHKEY4('n','a','m','e'):
				p->set |= SETNAME;
				copystring(p->name, sizeof(p->name), sp ? sp->name : v, sp ? 0 : n);
				continue;
			case HASHKEY6('p','e','r','c','p','u'):
				if (!sp && (p->global.percpu = (int)strtol(v, NiL, 0)) > 0)
				{
					if (p->global.percpu > (state.jobmax - state.job + 1))
						p->global.percpu = state.jobmax - state.job + 1;
					p->global.set |= SETPERCPU;
				}
				continue;
			case HASHKEY6('p','e','r','h','o','s'):
				if (!sp && (p->global.perhost = (int)strtol(v, NiL, 0)) > 0)
				{
					if (p->global.perhost > (state.jobmax - state.job + 1))
						p->global.perhost = state.jobmax - state.job + 1;
					p->global.set |= SETPERHOST;
				}
				continue;
			case HASHKEY6('p','e','r','s','e','r'):
				if (!sp && (p->global.perserver = (int)strtol(v, NiL, 0)) >= 0)
				{
					if (p->global.perserver > (state.jobmax - state.job + 1))
						p->global.perserver = state.jobmax - state.job + 1;
					p->global.set |= SETPERSERVER;
				}
				continue;
			case HASHKEY6('p','e','r','u','s','e'):
				if (!sp && (p->global.peruser = (int)strtol(v, NiL, 0)) > 0)
				{
					if (p->global.peruser > (state.jobmax - state.job + 1))
						p->global.peruser = state.jobmax - state.job + 1;
					p->global.set |= SETPERUSER;
				}
				continue;
			case HASHKEY4('p','o','o','l'):
				if (!sp && (p->global.pool = (int)strtol(v, NiL, 0)) > 0)
					p->global.set |= SETPOOL;
				continue;
			case HASHKEY6('p','r','o','f','i','l'):
				if (!sp)
				{
					static String_t	save;

					p->global.set |= SETPROFILE;
					p->global.profile = savestring(&save, v, n);
				}
				continue;
			case HASHKEY6('r','a','t','i','n','g'):
				if (p->rating = sp ? sp->rating : strton(v, NiL, NiL, 100)) p->set |= SETRATING;
				continue;
			case HASHKEY6('r','e','m','o','t','e'):
				if (p->set & SETNAME)
				{
					p->set |= SETREMOTE;
					copystring(p->remote, sizeof(p->remote), v, n);
				}
				else
				{
					static String_t	save;

					p->global.set |= SETREMOTE;
					p->global.remote = savestring(&save, v, n);
				}
				continue;
			case HASHKEY5('s','c','a','l','e'):
				if (p->scale = sp ? sp->scale : (int)strtol(v, NiL, 0)) p->set |= SETSCALE;
				continue;
			case HASHKEY6('s','c','h','e','d','u'):
				if (!sp)
				{
					static String_t	save;

					p->global.set |= SETSCHEDULE;
					p->global.schedule = savestring(&save, v, n);
				}
				continue;
			case HASHKEY5('s','h','e','l','l'):
				if (p->set & SETNAME)
				{
					p->set |= SETSHELL;
					copystring(p->shell, sizeof(p->shell), v, n);
				}
				else
				{
					static String_t	save;

					p->global.set |= SETSHELL;
					p->global.shell = savestring(&save, v, n);
				}
				continue;
			case HASHKEY4('s','t','a','t'):
				if ((v - b) > 6 && *(b + 4) == '.')
				{
					if ((--v - (b += HASHKEYMAX - 1)) > HASHKEYMAX)
						v = b + HASHKEYMAX;
					while (b < v)
					{
						if (islower(q = *b++))
							x = HASHKEYPART(x, q);
						else
						{
							if (isalnum(q) || q == '_')
								x = 0;
							break;
						}
					}
					switch (x)
					{
					case HASHKEY4('i','d','l','e'):
						p->global.set |= SETIDLE;
						p->stat.idle = strelapsed(v, NiL, 1);
						break;
					case HASHKEY4('l','o','a','d'):
						p->global.set |= SETLOAD;
						p->stat.load = strton(v, NiL, NiL, 100);
						break;
					case HASHKEY6('u','p','d','a','t','e'):
						p->global.set |= SETUPDATE;
						p->stat.up = cs.time + strelapsed(v, NiL, 1);
						break;
					case HASHKEY5('u','s','e','r','s'):
						p->global.set |= SETUSERS;
						p->stat.users = strton(v, NiL, NiL, 0);
						break;
					}
					continue;
				}
				break;
			case HASHKEY4('t','y','p','e'):
				p->set |= SETTYPE;
				copystring(p->type, sizeof(p->type), sp ? sp->type : v, sp ? 0 : n);
				continue;
			}
			if (!(p->set & SETMISC))
			{
				p->set |= SETMISC;
				u = p->misc;
			}
			if (!sp) n = s - b - (*s != 0);
			else if (miscget(sp, b, 1, &e, NiL))
			{
				for (v = b = e; *b && *b != ' '; b++);
				n = v - b;
			}
			else n = 0;
			if (n > 0 && u + n + expr < p->misc + sizeof(p->misc) - 1)
			{
				if (expr)
				{
					if (expr == 4)
					{
						*u++ = '&';
						*u++ = '&';
					}
					else expr = 4;
					*u++ = '(';
				}
				else if (u != p->misc) *u++ = ' ';
				memcpy(u, b, n);
				u += n;
				if (expr) *u++ = ')';
			}
			*u = 0;
		}
	}
}

/*
 * add misc attributes in s to sp
 */

void
miscadd(Coshell_t* sp, register char* s)
{
	char*	tp;

	for (;;)
	{
		while (isspace(*s)) s++;
		if (!*s) return;
		miscget(sp, s, 1, &tp, sp->misc + sizeof(sp->misc) - 1);
		while (!isspace(*s)) if (!*s++) return;
	}
}

/*
 * evaluate miscmatch() expression operand
 */

static long
misceval(const char* s, char** e, void* handle)
{
	if (!s)
	{
		message((-3, "attribute: %s", *e));
		return(0);
	}
	return(miscget((Coshell_t*)handle, s, 0, e, NiL));
}

/*
 * evaluate misc attribute expression p on shell info sp
 */

int
miscmatch(Coshell_t* sp, char* p)
{
	return(strexpr(p, NiL, misceval, sp) != 0);
}

/*
 * format pseudo-float
 */

#define PFSIZE		9

char*
fmtfloat(int n)
{
	static char	buf[4][PFSIZE];
	static int	inx;

	if (++inx >= elementsof(buf)) inx = 0;
	sfsprintf(buf[inx], PFSIZE, "%d.%02d", n / 100, n % 100);
	return(buf[inx]);
}
