[net.sources] car.c, REVISED a little

colonel@sunybcs.UUCP (George Sicherman) (03/29/84)

/*
 *	car - auto maintenance reminder service.
 *	Col. Sicherman - 9 Nov 1982.
 */

/*
 *	car.log consists of line images in the following form:
 *
 *	V 811231
 *	B 820101
 *	? 831225 112900
 *
 *	Mileages are always in miles, not kilometers.
 *	Fields are separated by white space.
 */

/*
 *	form of .carrc:
 *
 *	B=replace brakes
 *	V=check veeblefetzer
 *	B 20000 365d
 *	O 6m
 *	V 4O
 *
 *	I.e., replace brakes every 20,000 miles or 365 days, whichever
 *	comes first; change oil every 6 months; check veeblefetzer every
 *	4 oil changes.
 */

#define MAXDEPEND 10
#define PASSWORD "/etc/passwd"
#define MAIL "/usr/ucb/mail"
#define NNSIZE 512
#define TOMILES 0.62137
#define TOGALLONS 0.2641721

#include <ctype.h>
#include <pwd.h>
#include <stdio.h>
#include <time.h>

typedef long int datetype;

FILE *Specs, *Log;
FILE *Temp;
FILE *Mail;
char *strcat(), *strcpy();
char tempname[] = "/usr/tmp/carXXXXXX";
char *servicename[26] = {"change air filter","<B>","<C>","<D>",
	"<E>","change oil filter","<G>","<H>","<I>",
	"<J>","check oil","<L>","<M>","<N>",
	"change oil","<P>","<Q>","<R>","<S>",
	"<T>","<U>","<V>","<W>","<X>",
	"<Y>","<Z>"};
char newnamespace[NNSIZE], *nnptr = newnamespace;
int line;		/* INPUT LINE COUNTER */
double odometer;	/* MILEAGE COUNTER FOR PHASE 2 */
double fuel;		/* FUEL CONSUMED */
int metric = 0;		/* FLAG FOR PRINT FUEL CONSUMPTION IN METRIC */
datetype calendar;	/* DATE COUNTER FOR PHASE 3 */
datetype today;		/* TODAY'S DATE */
int changes;		/* FLAG FOR CHANGES IN PHASE 4 */
int moff[12] = {0,31,59,90,120,151,181,212,243,273,304,334};

struct dencytype {
	char key;
	long int cycle, count;
};

struct servicetype {
	long int dents;
	int ndencies;	/* NUMBER OF DEPENDENCIES */
	int isdue;	/* 0 IF NOT DUE */
	struct dencytype dency[MAXDEPEND];
	datetype lastdate;
	long int lastmile;
} service[27], *sp;

main(argc,argv)
int argc;
char **argv;
{
	if (argc>2) bomb();
	if (argc>1) switch (**++argv) {
	case '-':
		switch(*++*argv) {
		case '\0':
			talk();
			break;
		case 'm':
			alltalk();
			break;
		case 'f':
			switch (*++*argv) {
			case 'm':
				metric++;
			case '\0':
				fueltalk();
				break;
			default:
				bomb();
			}
			break;
		default:
			bomb();
		}
		break;
	case '%':	/* Not suitable for crontab! */
		alltalk();
		break;
	default:
		bomb();
	}
	else listen();
}

talk()
{
	if (NULL==(Specs=fopen(".carrc","r"))) {
		fprintf(stderr,"car: cannot read .carrc\n");
		exit(1);
	}
	if (NULL==(Log=fopen("car.log","r"))) {
		fprintf(stderr,"car: cannot read car.log\n");
		exit(1);
	}
	talk1(Specs,Log,stdout);
}

alltalk()
{
	char c;
	char user[9];
	char home[40];
	char assem[50];
	char *mktemp();
	struct passwd *g, *getpwent();
	FILE *popen();
	mktemp(tempname);
	if (NULL==(Temp=fopen(tempname,"w+"))) {
		fprintf(stderr,"car: cannot write temp file\n");
		exit(1);
	}
	unlink(tempname);	/* SO IT'S SURE TO DISAPPEAR */
	while (g=getpwent()) {
		strcpy(assem,g->pw_dir);
		strcat(assem,"/.carrc");
		if (NULL==(Specs=fopen(assem,"r"))) continue;
		strcpy(assem,g->pw_dir);
		strcat(assem,"/car.log");
		if (NULL==(Log=fopen(assem,"r"))) {
			fprintf(Temp,"car: cannot read %s\n",assem);
			goto mailit;
		}
		talk1(Specs,Log,Temp);
		fclose(Log);
mailit:
		fclose(Specs);
		rewind(Temp);
		strcpy(assem,MAIL);
		strcat(assem," ");
		strcat(assem,g->pw_name);
		if (NULL==(Mail=popen(assem,"w"))) {
			fprintf(stderr,"car: cannot mail to %s\n",g->pw_name);
			continue;
		}
		while (EOF!=(c=getc(Temp))) putc(c,Mail);
		pclose(Mail);
		rewind(Temp);
	}
}

listen()
{
	long int clock, time();
	char hold[20];
	char k;
	char *ctime();
	int r;
	double m;
	Specs=fopen(".carrc","r");
	if (NULL==(Log=fopen("car.log","a"))) {
		fprintf(stderr,"car: cannot open car.log\n");
		exit(1);
	}
	time(&clock);
	if (Specs) if (phase1(Specs,stderr)) exit(1);
	whatday();
	printf("\ncar \t%s\n\n",ctime(&clock));
	for (;;) {
		printf("Report (type h for help): ");
		if (EOF==scanf(" %s",hold)) break;
		while (isupper(hold[0])) {
			fprintf(Log,"%c %D\n",hold[0],today);
			printf("\tReported: %s\n",servicename[indx(hold[0])]);
			strcpy(hold,&hold[1]);
		}
		if ('0'<=hold[0] && '9'>=hold[0]) {
			r=sscanf(hold,"%lf%c",&m,&k);
			if (r<2) {
				printf("\tReported: %lf miles\n",m);
				fprintf(Log,"? %D %lf\n",today,m);
			}
			else if ('k'==k) {
				printf("\tReported: %lf kilometers\n",m);
				m*=TOMILES;
				fprintf(Log,"? %D %lf\n",today,m);
			}
			else if ('g'==k) {
				printf("\tReported: %lf gallons\n",m);
				fprintf(Log,"f %D %lf\n",today,m);
			}
			else if ('l'==k) {
				printf("\tReported: %lf liters\n",m);
				m*=TOGALLONS;
				fprintf(Log,"f %D %lf\n",today,m);
			}
			else printf("\tError in suffix\n");
		}
		else if (hold[0]=='h') help();
		else if (hold[0]=='q') break;
		else if (hold[0]) printf("\tBad command - %c\n",hold[0]);
	}
}

#define SVCNAMELENGTH 80

talk1(S,L,O)
FILE *S, *L, *O;
{
	char s;
	char *malloc();
	int i;

	if (phase1(S,O)) return;
/*
 *	PHASE 2 - FIRST PASS THROUGH LOG FILE.
 */
	line=0;
	while (EOF!=(s=getc(L))) {
		line++;
		if ((s<'A'||s>'Z')&&s!='?'&&s!='f') {
			badlog(O);
			return;
		}
		if (1!=fscanf(L," %D",&calendar)) {
			badlog(O);
			return;
		}
		if ('?'==s) if (1!=fscanf(L," %lf",&odometer)) {
			badlog(O);
			return;
		}
		if ('f'==s) if (1!=fscanf(L," %lf",&fuel)) {
			badlog(O);
			return;
		}
		/* FLUSH THE LINE */
		while ('\n'!=getc(L));
		sp = &service[indx(s)];
		sp->lastdate=calendar;
		sp->lastmile=odometer;
	}
/*
 *	PHASE 3 - SECOND PASS THROUGH LOG FILE.
 */
	rewind(L);
	while (EOF!=(s=getc(L))) {
		fscanf(L," %D",&calendar);
		while ('\n'!=getc(L));
		if ('f'!=s && '?'!=s) xref(s);
	}
/*
 *	PHASE 4 - COMPUTATION.
 */
	whatday();
	do phase4();
	while (changes);
	/* PRINT MESSAGES */
	for (i=0; i<26; i++) if (service[i].isdue)
	fprintf(O,"car - %s\n",servicename[i]);
	if (service[26].isdue) fprintf(O,"car - please report mileage\n");
}

int phase1(S,O)
FILE *S, *O;
{
	int i;
	char c, s, k;
	long int n;
	for (i=0; i<27; i++) {
		sp = &service[i];
		sp->lastmile = 0;
		sp->lastdate = 100;
		sp->isdue = sp->ndencies = sp->dents = 0;
	}
	while (EOF!=(s=getc(S))) {
		c=getc(S);
		switch (c) {
		case '=':
			i=0;
			if (s<'A'||s>'Z') {
				fprintf(O,"car: bad definition '%c'\n",s);
				return 1;
			}
			servicename[s-'A']=nnptr;
			while ('\n'!=(c=getc(S))) *nnptr++ = c;
			*nnptr++ ='\0';
			break;
		case ' ':
		case '\t':
			if ((s<'A'||s>'Z')&&s!='?') {
				fprintf(O,"car: bad rule '%c'\n",s);
				return 1;
			}
			for (;;) {
				if (1!=fscanf(S," %D",&n)) {
					fprintf(O,
					"car: bad rule '%c'\n",s);
					return 1;
				}
				c=getc(S);
				if (c==' '||c=='\t'||c=='\n') k='?';
				else if (c==s) {
					fprintf(O,
					"car: '%c' depends on itself\n",
					s);
					return 1;
				}
				else if (!isupper(c) && !index("dwmyk",c)) {
					fprintf(O,"car: bad rule '%c'\n",
					s);
					return 1;
				}
				else if ('k'==c) {/* KILOMETERS */
					k='?';
					n*=TOMILES;
				}
				else k=c;
				sp = &service[indx(s)];
				sp->dency[sp->ndencies].cycle=n;
				sp->dency[sp->ndencies].key=k;
				sp->dency[sp->ndencies].count=0;
				sp->ndencies++;
				/* CROSS-REFERENCE */
				if (isupper(k))
				service[indx(k)].dents|=1L<<indx(s);
				/* ADVANCE TO NEXT FIELD */
				if ('\n'!=c) do c=getc(S);
				while (' '==c||'\t'==c);
				if ('\n'==c) break;
				else ungetc(c,S);
			}
			break;
		default:
			fprintf(O,
			"car: syntax error in specifications file\n");
			return 1;
		}
	}
	/* DEFAULT DEPENDENCY FOR MILEAGE CHECK */
	sp = &service[26];
	if (!sp->ndencies) {
		sp->ndencies = 1;
		sp->dency[0].key = 'w';
		sp->dency[0].cycle = 1;
		sp->dency[0].count = 0;
	}
	return 0;
}

phase4()
{
	int i, j;
	struct servicetype *sp;
	struct dencytype *u;
	changes = 0;
	for (i=0; i<27; i++) {
		sp = &service[i];
		if (sp->isdue) continue;
		if (!sp->ndencies) continue;
		for (j=0; j<sp->ndencies; j++) {
			u = &sp->dency[j];
			if ('?'==u->key) {
				/* MILEAGE DEPENDENCY */
				if (odometer>=sp->lastmile+u->cycle)
				goto markdue;
			}
			else if ('d'==u->key || 'w'==u->key
			|| 'm'==u->key || 'y'==u->key) {
				/* TIME DEPENDENCY */
				if (expired(u,sp->lastdate))
				goto markdue;
			}
			/* SERVICE DEPENDENCY */
			else if (u->count >= u->cycle) goto markdue;
		}
		/* NOT DUE - GO TO NEXT SERVICE */
		continue;
markdue:
		sp->isdue++;
		if (i<26) xref((char)('A'+i));
		changes++;
	}
}


int expired(u,d)
struct dencytype *u;
datetype d;
{
	long int now, then;
	switch (u->key) {
	case 'y':
		return (today>=d+10000*u->cycle);
	case 'm':
		now = today/10000 * 12 + (today/100)%100;
		then = d/10000 * 12 + (d/100)%100;
		if (today%100 < d%100) now--;
		return (now >= then+u->cycle);
	case 'w':
		u->key='d';
		u->cycle*=7;
		/* SO MUCH FOR THAT! */
	case 'd':
		now = today/10000 * 365 + moff[(today/100)%100-1] + today%100;
		then = d/10000 * 365 + moff[(d/100)%100-1] + d%100;
		return (now >= then+u->cycle);
	}
}

update(i,s)
int i;
char s;
{
	struct servicetype *rp;
	int j;
	rp = &service[i];
	for (j=0; j<rp->ndencies; j++)
	if (rp->dency[j].key == s)
	if (calendar>rp->lastdate) rp->dency[j].count++;
}

whatday()
{
	long int clock, time();
	struct tm *l, *localtime();
	time(&clock);
	l=localtime(&clock);
	today=10000*l->tm_year+100*l->tm_mon+l->tm_mday+100;
}

int indx(c)
char c;
{
	return ('?'==c ? 26 : c-'A');
}

xref(s)
char s;
{
	int i;
	sp = &service[indx(s)];
	for (i=0; i<26; i++) if ((1L<<i) & sp->dents) update(i,s);
}

fueltalk()
{
	double fuel0, mile0, fueltot;
	int isfuel;
	char c;
	if (NULL==(Log=fopen("car.log","r"))) {
		fprintf(stderr,"car: cannot read car.log\n");
		exit(1);
	}
	mile0 = fuel0 = -1.0;
	fueltot = 0.0;
	line = 0;
	while (EOF!=(c=getc(Log))) {
		line++;
		switch (c) {
		case 'f':
			isfuel=1;
			if (1!=fscanf(Log," %*ld %lf",&fuel)) {
				badlog(stderr);
				exit(1);
			}
			if (fuel0<0) fuel0=fuel;
			fueltot += fuel;
			break;
		case '?':
			isfuel=0;
			if (1!=fscanf(Log," %*ld %lf",&odometer)) {
				badlog(stderr);
				exit(1);
			}
			if (fuel0<0) break;
			if (mile0<0) mile0=odometer;
		}
		while ('\n'!=getc(Log));
	}
	if (isfuel) fueltot -= fuel;
	if (fueltot==0.0 || odometer==mile0) {
		fprintf(stdout,"insufficient data\n");
		exit(0);
	}
	odometer -= mile0;
	if (metric) {
		odometer /= TOMILES;
		fueltot /= TOGALLONS;
	}
	printf("\t%.1lf %s / %.1lf %s = %.2lf\n",
		odometer, metric?"km":"mi", fueltot, metric?"l":"gal",
		odometer/fueltot);
}

help()
{
	int i;
	putchar('\n');
	for (i=0; i<26; i++) if ('<'!=*servicename[i])
	printf("\t%c\t%s\n",(char)(i+'A'),servicename[i]);
	printf("\t1234\tcurrent mileage\n");
	printf("\t1234k\tcurrent mileage in kilometers\n");
	printf("\t12.3g\tfuel consumed in US gallons\n");
	printf("\t12.3l\tfuel consumed in liters\n");
	printf("\th\thelp\n");
	printf("\tq\tquit\n\n");
}

bomb()
{
	fprintf(stderr,"usage: car [-] [-m] [-f[m]]\n");
	exit(1);
}

badlog(stm)
FILE *stm;
{
	fprintf(stm,"car: bad log entry, line %d\n",line);
	return;
}