/* bcontrol.c -- decide when to run the butler.
David Nichols
July, 1986

The butler is controled via a butler configuration file that provides a boolean expression to control butler use.  The expression consists of logical and comparison operators, along with the following primitive values:
    users -- number of logged in users
    load -- load factor (not any more)
    time -- time of day (compare to hh.mm in 24 hour time)
    idletime -- time in minutes and seconds (mm.ss) since console was last used.
For example, this expression
    users == 0 || (users <= 1 && load <= 1)
says to run the butler when noone is logged in or when one person is logged in and the load is less than 1.  There is no precedence.

There should be hysterisis stuff as well for enabling and disabling.  For now, they'll be wired in.
 */
#ifdef sun
#include <sys/param.h>		/* for FSCALE */
#else
#include <sys/types.h>
#endif

#include <stdio.h>
#ifdef LOADF
#include <nlist.h>
#endif
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/file.h>
#include <utmp.h>
#include <ctype.h>

extern char *malloc();
extern double atof();
struct Config *ParseExpr();
struct Config *ParseTerm();
struct Config *ParsePart();
struct Config *ParseRelation();
struct Config *NewConfig();

#define TRUE		1
#define FALSE	0

enum ConfigToken {
    andOp, orOp, notOp,
    ltOp, leOp, gtOp, geOp, eqOp, neOp,
    trueTok, falseTok,
    usersTok,
#ifdef LOADF
    loadTok,
#endif
    timeTok, idleTok, numTok,
    lparenTok, rparenTok, endTok, badTok
};
#define IsRelOp(op)	((int)ltOp <= (int)(op) && (int)(op) <= (int)neOp)
#define IsId(t)			((int)usersTok <= (int)(t) && (int)(t) <= (int)idleTok)

enum ConfigToken LookupToken();

struct Config {
    enum ConfigToken op;		/* must be an operator */
    union {
	struct {
	    struct Config *Arg1, *Arg2;
	} p;
	struct {
	    enum ConfigToken Tok1, Tok2;
	    int Val1, Val2;
	} n;
    } u;
};
#define c_p1	u.p.Arg1
#define c_p2	u.p.Arg2
#define c_t1	u.n.Tok1
#define c_t2	u.n.Tok2
#define c_v1	u.n.Val1
#define c_v2	u.n.Val2

static char *configFileName;

struct Config *conf = NULL;		/* current configuration */
struct Config *freeConfigs = NULL;	/* free list */
enum ConfigToken tok;			/* current token */
int tokVal;					/* numeric value if any */
FILE *configFile;				/* config file when it's open */

struct TokenTable {
    char *name;
    enum ConfigToken token;
} tokTable[] = {
    "true", trueTok,
    "false", falseTok,
    "users", usersTok,
#ifdef LOADF
    "load", loadTok,
#endif
    "time", timeTok,
    "idletime", idleTok,
    "and", andOp,
    "&&", andOp,
    "or", orOp,
    "||", orOp,
    "not", notOp,
    "!", notOp,
    "<", ltOp,
    "<=", leOp,
    ">", gtOp,
    ">=", geOp,
    "=", eqOp,
    "==", eqOp,
    "!=", neOp,
    "(", lparenTok,
    ")", rparenTok,
    NULL, badTok
};

struct ValueTable {
    enum ConfigToken token;
    int value;
} values[] = {
    usersTok, 0,
#ifdef LOADF
    loadTok, 0,
#endif
    timeTok, 0,
    idleTok, 0,
    badTok, 0
};

#ifdef LOADF
struct nlist nl[] = {
    {"_avenrun", },
#define X_AVENRUN		0
    { "" },
};
static kernelFD;			/* descriptor for /dev/kmem */
static kernelOk = TRUE;		/* true if all kernel junk worked */
#endif

/* Set up the package. */
BC_Init(name)
    char *name;
{
    configFileName = name;
    if (BC_ReadConfig() < 0)
	return (-1);
#ifdef LOADF
    nlist("/vmunix", nl);
    if (nl[0].n_type == 0)
	kernelOk = FALSE;
    if (kernelOk && (kernelFD = open("/dev/kmem", O_RDONLY)) < 0)
	kernelOk = FALSE;
    return kernelOk ? 0 : -1;
#else
    return 0;
#endif
}

/* Read the configuration file. */
BC_ReadConfig()
{
    struct Config *myconf;

    configFile = fopen(configFileName, "r");
    if (configFile == NULL)
	return -1;
    GetToken();
    myconf = ParseExpr();
    if (tok != endTok) {
	FreeConfig(myconf);
	myconf = NULL;
    }
    if (myconf != NULL)
	conf = myconf;
    fclose(configFile);
    return myconf == NULL ? -1 : 0;
}

/* Get a single token from the file; uses getstr routine from netd. */
static int GetToken ()
{
    char token[100];
    register char *p;
    register int c;
    int seenDot;

    SkipBlanks();
    c = getc(configFile);
    if (c == EOF) {
	tok = endTok;
	return;
    }
    p = token;
    if (isdigit(c) || c == '.') {
	seenDot = FALSE;
	do {
	    *p++ = c;
	    if (c == '.')
		seenDot = TRUE;
	    c = getc(configFile);
	} while (isdigit(c) || (!seenDot && c == '.'));
	ungetc(c, configFile);
	*p = 0;
	tok = numTok;
	tokVal = (int) (100.0 * atof(token));
    }
    else if (isalpha(c)) {
	do {
	    *p++ = c;
	    c = getc(configFile);
	} while (isalpha(c));
	ungetc(c, configFile);
	*p = 0;
	tok = LookupToken(token);
    }
    else {
	/* We try looking up the two-char token first.  If that fails, we push back the second char and use the one-char token. */
	*p++ = c;
	c = getc(configFile);
	if (c != EOF) {
	    *p++ = c;
	    *p = 0;
	    tok = LookupToken(token);
	    if (tok == badTok) {
		ungetc(c, configFile);
		--p;
	    }
	    else
		return;
	}
	*p = 0;
	tok = LookupToken(token);
    }
}

/* Skip leading blanks and comments in the input. */
static SkipBlanks ()
{
    register int c;

    for (;;) {
	/* Skip blanks. */
	while ((c=getc(configFile)) != EOF && isspace(c))
	    ;
	/* If we have a hash mark, skip the rest of the line. */
	if (c == '#')
	    while((c=getc(configFile)) != EOF && c != '\n')
		;
	else {
	    /* Else we have a token.  Push back the char and return. */
	    ungetc(c, configFile);
	    return;
	}
    }
}

/* Look up a token string in the table. */
static enum ConfigToken LookupToken(token)
    char *token;
{
    int i;

    for (i = 0; tokTable[i].name != NULL; ++i)
	if (strcmp(tokTable[i].name, token) == 0)
	    return tokTable[i].token;
    return badTok;
}

/* Ok, here's a syntax:
    expr ::=term { "or" term }
    term ::= part { "and" part }
    part ::= relation | "not" relation
    relation ::= "false" | "true" | "(" expr ")" | tok relop tok
    tok ::= "users" | "load" | "time" | number
    relop ::= "<" | "<=" | ">" | ">=" | "=" | "!="
There are puns for some of the symbols. */

/* Parse the expression.  Returns NULL if unsuccesful, else returns the new configuration. */
static struct Config *ParseExpr()
{
    struct Config *cf, *cf2;

    cf = ParseTerm();
    if (cf == NULL)
	return NULL;
    while (tok == orOp) {
	GetToken();
	cf2 = ParseTerm();
	if (cf2 == NULL) {
	    FreeConfig(cf);
	    return NULL;
	}
	cf = NewConfig(orOp, cf, cf2);
    }
    return cf;
}

static struct Config *ParseTerm()
{
    struct Config *cf, *cf2;

    cf = ParsePart();
    if (cf == NULL)
	return NULL;
    while (tok == andOp) {
	GetToken();
	cf2 = ParsePart();
	if (cf2 == NULL) {
	    FreeConfig(cf);
	    return NULL;
	}
	cf = NewConfig(andOp, cf, cf2);
    }
    return cf;
}

static struct Config *ParsePart()
{
    struct Config *cf;
    int sawNot = FALSE;

    if (tok == notOp) {
	GetToken();
	sawNot = TRUE;
    }
    cf = ParseRelation();
    if (cf == NULL)
	return NULL;
    if (sawNot)
	return NewConfig(notOp, cf, NULL);
    else
	return cf;
}

static struct Config *ParseRelation()
{
    struct Config *cf;
    enum ConfigToken t1, op;
    int v1;

    cf = NULL;
    if (tok == lparenTok) {
	GetToken();
	cf = ParseExpr();
	if (tok == rparenTok)
	    GetToken();
	else {
	    FreeConfig(cf);
	    return NULL;
	}
    }
    else if (tok == trueTok || tok == falseTok) {
	cf = NewConfig(tok, NULL, NULL);
	GetToken();
    }
    else if (tok == numTok || IsId(tok)) {
	t1 = tok;
	v1 = tokVal;
	GetToken();
	if (!IsRelOp(tok))
	    return NULL;
	op = tok;
	GetToken();
	if (!IsId(tok) && tok != numTok)
	    return NULL;
	cf = NewConfig(op, NULL, NULL);
	cf->c_t1 = t1;
	cf->c_v1 = v1;
	cf->c_t2 = tok;
	cf->c_v2 = tokVal;
	GetToken();
    }
    return cf;
}

/* Get a new one.  Uses a free list. */
static struct Config *NewConfig(op, a1, a2)
    enum ConfigToken op;
    struct Config *a1, *a2;
{
    struct Config *cf;

    if (freeConfigs == NULL)
    	cf = (struct Config *) malloc(sizeof(struct Config));
    else {
	cf = freeConfigs;
	freeConfigs = freeConfigs->c_p1;	/* aha! the next pointer */
    }
    cf->op = op;
    cf->c_p1 = a1;
    cf->c_p2 = a2;
    return cf;
}

/* Free the entire subtree. */
static FreeConfig(cf)
    struct Config *cf;
{
    if (cf == NULL)
	return;
    if (cf->op == notOp)
	FreeConfig(cf->c_p1);
    else if (cf->op == andOp || cf->op == orOp) {
	FreeConfig(cf->c_p1);
	FreeConfig(cf->c_p2);
    }
    cf->c_p1 = freeConfigs;
    freeConfigs = cf;
}

/* Tell if the butler should run. */
int BC_ButlerOk ()
{
    if (EvalVars() < 0)
	return FALSE;
    return EvalConf(conf);
}

static EvalVars ()
{
#ifdef LOADF
#ifdef sun
    long avenrun[3];
#else
    double avenrun[3];
#endif
#endif LOADF
    struct timeval tv;
    struct tm *tm;
    int nusers;
    int f;
    struct utmp utmp;
    struct stat s;

#ifdef LOADF
    if (!kernelOk)
	return (-1);
    lseek(kernelFD, (long)nl[X_AVENRUN].n_value, 0);
    read(kernelFD, avenrun, sizeof(avenrun));
#ifdef sun
    SetVal(loadTok, (avenrun[2]*100)/FSCALE);
#else
    SetVal(loadTok, (int) (avenrun[2] * 100.0));
#endif
#endif LOADF
    nusers = 0;
    if ((f = open("/etc/utmp", O_RDONLY)) < 0)
	return (-1);
    while (read(f, &utmp, sizeof(utmp)) == sizeof(utmp)) {
	if (utmp.ut_name[0] != 0)
	    ++nusers;
    }
    close(f);
    SetVal(usersTok, nusers*100);
    gettimeofday(&tv, 0);
    tm = localtime(&tv.tv_sec);
    SetVal(timeTok, tm->tm_hour * 100 + tm->tm_min);
    if (stat("/dev/console", &s) < 0)
	SetVal(idleTok, 0);
    else
	SetVal(idleTok, (tv.tv_sec - s.st_atime) / 60 * 100 + (tv.tv_sec - s.st_atime) % 60);
    return 0;
}

static SetVal(tok, val)
    enum ConfigToken tok;
    int val;
{
    int i;

    for (i = 0; values[i].token != badTok; ++i) {
	if (values[i].token == tok) {
	    values[i].value = val;
	    return;
	}
    }
}

static int GetVal(tok, val)
    enum ConfigToken tok;
{
    int i;

    if (tok == numTok)
	return val;
    for (i = 0; values[i].token != badTok; ++i) {
	if (values[i].token == tok)
	    return values[i].value;
    }
    return -1;
}

static int EvalConf (cf)
    struct Config *cf;
{
    switch(cf->op) {
	case notOp:
	    return !EvalConf(cf->c_p1);
	case andOp:
	    return EvalConf(cf->c_p1) && EvalConf(cf->c_p2);
	case orOp:
	    return EvalConf(cf->c_p1) || EvalConf(cf->c_p2);
	case ltOp:
	    return GetVal(cf->c_t1, cf->c_v1) < GetVal(cf->c_t2, cf->c_v2);
	case leOp:
	    return GetVal(cf->c_t1, cf->c_v1) <= GetVal(cf->c_t2, cf->c_v2);
	case gtOp:
	    return GetVal(cf->c_t1, cf->c_v1) > GetVal(cf->c_t2, cf->c_v2);
	case geOp:
	    return GetVal(cf->c_t1, cf->c_v1) >= GetVal(cf->c_t2, cf->c_v2);
	case eqOp:
	    return GetVal(cf->c_t1, cf->c_v1) == GetVal(cf->c_t2, cf->c_v2);
	case neOp:
	    return GetVal(cf->c_t1, cf->c_v1) != GetVal(cf->c_t2, cf->c_v2);
	case falseTok:
	    return FALSE;
	case trueTok:
	    return TRUE;
	default:
	    return FALSE;
    }
}

BC_PrintExpr (f)
    FILE *f;
{
    PrintConf(f, conf);
}

static PrintConf (f, cf)
    FILE *f;
    struct Config *cf;
{
    switch (cf->op) {
	case notOp:
	    PrintToken(f, cf->op);
	    putc(' ', f);
	    PrintConf(f, cf->c_p1);
	    break;
	case andOp:
	case orOp:
	    putc('(', f);
	    PrintConf(f, cf->c_p1);
	    putc(' ', f);
	    PrintToken(f, cf->op);
	    putc(' ', f);
	    PrintConf(f, cf->c_p2);
	    putc(')', f);
	    break;
	case ltOp:
	case leOp:
	case gtOp:
	case geOp:
	case eqOp:
	case neOp:
	    PrintToken(f, cf->c_t1, cf->c_v1);
	    putc(' ', f);
	    PrintToken(f, cf->op, 0);
	    putc(' ', f);
	    PrintToken(f, cf->c_t2, cf->c_v2);
	    break;
	case falseTok:
	case trueTok:
	    PrintToken(f, cf->op, 0);
	    break;
	default:
	    fprintf(f, "unknown");
	    break;
    }
}

static PrintToken(f, tok, val)
    FILE *f;
    enum ConfigToken tok;
    int val;
{
    if (tok == numTok) {
	int i = val / 100;
	int frac = val % 100;
	fprintf(f, "%d", i);
	if (frac != 0)
	    fprintf(f, ".%02d", frac);
    }
    else {
	int i;
	for (i = 0; tokTable[i].token != badTok; ++i)
	    if (tokTable[i].token == tok) {
		fprintf(f, "%s", tokTable[i].name);
		return;
	    }
    }
}
