/*
** FreeWare! by John Edward Mosley, jem3@engr.uark.edu
** BootFlag.c - Allows manipulation of an 32-bit flag register in a file on a
** given disk (usually a Workbench disk) so that bit pattern comparisons of
** flags can set the Shell script condition to Warn of the patterns match.
*/
#include <stdio.h>
#include <ctype.h>

#define DEF_STAT_FILE "sys:s/BootFlags"
#define VERSION_INFO "BootFlag version 2.1 for the Amiga (FreeWare) by John Mosley 3-23-1992\n"

typedef char *CHARPTR;

void parse_flags(char *, unsigned int *, unsigned int *);
int return_status(unsigned int, unsigned int);
void print_flags(char *);
void set_status(char *, unsigned int);
unsigned int atoui(CHARPTR *, int);
void print_usage(void);

char STAT_FILE[41];

void main(int argc, char *argv[]) {
	unsigned int ONflags, OFFflags;

	if(*argv[argc - 1] == '-' && *(argv[argc - 1] + 1) == 'f') {
		strcpy(STAT_FILE, argv[argc - 1] + 2);
		argc--;
	} /* if */
	else strcpy(STAT_FILE, DEF_STAT_FILE);
	switch(argc) {
		case 3:
			if(strcmp(argv[1], "check") == 0) {
				parse_flags(argv[2], &ONflags, &OFFflags);
				if(return_status(ONflags, OFFflags))
					exit(5);
				else
					exit(0);
			} /* if */
			else if(*argv[1] == 'p' || *argv[1] == 'P')
				print_flags(argv[2]);
			else {
				parse_flags(argv[2], &ONflags, &OFFflags);
				set_status(argv[1], ONflags);
			} /* else */
			break;
		case 2:
			if(*argv[1] == 'p' || *argv[1] == 'P')
				print_flags(NULL);
			break;
		default:
			printf("BootFlag: Wrong number of arguments\n");
			print_usage();
	} /* switch */
} /* main() */

void parse_flags(char *strptr, unsigned int *ONflags, unsigned int *OFFflags) {
	int sign;
	unsigned int temp;
	char num[41], *numptr;

	*ONflags = *OFFflags = 0;
	while(*strptr) {
		sign = 1;
		/* check for a sign on the flags */
		if(*strptr == '+')
			strptr++;
		else if(*strptr == '-') {
			sign = 0;
			strptr++;
		} /* else if */
		/* check to see if it's just a decimal value */
		if(isdigit(*strptr)) {
			numptr = num;
			while(isdigit(*strptr)) *(numptr++) = *(strptr++);
			*numptr = NULL;
			temp = (unsigned int)atol(num);
		} /* if */
		/* check for a $hex, &oct, or %binary number & convert */	
		else switch(*strptr) {
			case '$':	/* hexadecimal number */
				strptr++;
				temp = atoui(&strptr, 4);
				break;
			case '&':	/* octal number */
				strptr++;
				temp = atoui(&strptr, 3);
				break;
			case '%':	/* binary number */
				strptr++;
				temp = atoui(&strptr, 1);
				break;
			default:
				printf("BootFlag: Unknown number system \"%c\"\n",
					*strptr);
				print_usage();
		} /* else switch */
		if(sign) *ONflags = temp;
		else *OFFflags = temp;
	} /* while */
} /* parse_flags() */

int return_status(unsigned int ONflags, unsigned int OFFflags) {
	unsigned int status;
	FILE *fptr;
	int check_result;

	if((fptr = fopen(STAT_FILE, "rb")) == NULL) {
		printf("BootFlag: Cannot access status file\n");
		exit(10);
	} /* if */
	if(fread((char *)&status, sizeof(unsigned int), 1, fptr) == EOF) {
		fclose(fptr);
		printf("BootFlag: Status file corrupt?\n");
		exit(10);
	} /* if */
	fclose(fptr);
	check_result = 1;
	if(ONflags) check_result = ((status & ONflags) == ONflags);
	if(OFFflags) check_result &= ((~status & OFFflags) == OFFflags);
	return(check_result);
} /* return_status() */

void print_flags(char *format) {
	unsigned int status, bit;
	FILE *fptr;
	char printing_zeros = 0;

	if((fptr = fopen(STAT_FILE, "rb")) == NULL) {
		printf("BootFlag: Cannot access status file\n");
		exit(10);
	} /* if */
	if(fread((char *)&status, sizeof(unsigned int), 1, fptr) == EOF) {
		fclose(fptr);
		printf("BootFlag: Status file corrupt?\n");
		exit(10);
	} /* if */
	fclose(fptr);
	if(format) {	/* determine format specification */
		switch(*format) {
			case '$':
				printf("Boot flags are set at $%x.\n", status);
				break;
			case '&':
				printf("Boot flags are set at &%o.\n", status);
				break;
			case '%':
				printf("Boot flags are set at %%");
				for(bit = 0x80000000; bit; bit >>= 1)
					if(status & bit) {
						printf("1");
						printing_zeros = 1;
					} /* if */
					else if(printing_zeros)
						printf("0");
				printf(".\n");
				break;
			default:
				printf("pmod: Unknown display format \"%c\"\n", *format);
		} /* switch */
	} /* if */
	else
		printf("Boot flags are set at %u.\n", status);
} /* print_flags() */

void set_status(char *operation, unsigned int flags) {
	unsigned int status;
	FILE *fptr;

	if(flags < 0 || flags > 0xffffffff) {
	  	printf("BootFlag: Given flag out of range\n");
		print_usage();
	} /* if */
	if(*operation == '=') {
		if((fptr = fopen(STAT_FILE, "wb")) == NULL) {
			printf("BootFlag: Cannot access status file\n");
			exit(10);
		} /* if */
	} /* if */
	else {
		if((fptr = fopen(STAT_FILE, "rb+")) == NULL) {
			printf("BootFlag: Cannot access status file\n");
			exit(10);
		} /* if */
		if(fread((char *)&status, sizeof(unsigned int), 1, fptr) == EOF) {
			fclose(fptr);
			printf("BootFlag: Status file corrupt?\n");
			exit(10);
		} /* if */
	} /* else */
	switch(*operation) {
		case 's':
		case 'S':
			status |= flags;
			break;
		case 'c':
		case 'C':
			status &= ~flags;
			break;
		case 't':
		case 'T':
			status ^= flags;
			break;
		case '=':
			status = flags;
			break;
		default:
			printf("BootFlag: Unknown command \"%s\"\n", operation);
			fclose(fptr);
			print_usage();
	} /* switch */
	if(fseek(fptr, 0, 0) != NULL) {
		fclose(fptr);
		printf("BootFlag: Status file corrupt?\n");
		exit(10);
	} /* if */
	if(fwrite((char *)&status, sizeof(unsigned int), 1, fptr) == NULL) {
		fclose(fptr);
		printf("BootFlag: Unable to update flags in status file\n");
		exit(10);
	} /* if */
	fclose(fptr);
} /* set_status() */

unsigned int atoui(CHARPTR *lineptr, int shift_base) {
	int digit, place = 1;
	unsigned int value, sum = 0;
	char num[41], *ptr;

	/* place the current number from the command-line string into num[] */
	ptr = num;
	while(**lineptr && **lineptr != '+' && **lineptr != '-')
		*(ptr++) = *((*lineptr)++);
	*ptr = NULL;
	ptr = num;
	/* reverse string to start conversion from Least Sig digit */
	strrev(ptr);
	/* loop through each digit and convert to integer */
	while(*ptr) {
		*ptr = toupper(*ptr);
		switch(shift_base) {
			case 4:
				if(!isxdigit(*ptr)) {
					printf("BootFlag: Bad hexadecimal digit \"%c\"\n", *ptr);
					print_usage();
				} /* if */
				digit = (isdigit(*ptr) != 0)*(*ptr - '0') + (isalpha(*ptr))*(*ptr - 'A' + 10);
				break;
			case 3:
				if(!isdigit(*ptr) || *ptr < '0' || *ptr > '7') {
					printf("BootFlag: Bad octal digit \"%c\"\n", *ptr);
					print_usage();
				} /* if */
				digit = (*ptr - '0');
				break;
			case 1:
				if(*ptr != '0' && *ptr != '1') {
					printf("BootFlag: Bad binary digit \"%c\"\n", *ptr);
					print_usage();
				} /* if */
				digit = (*ptr - '0');
				break;
			default:
				printf("BootFlag: atoui: Unknown base %d\n", shift_base);
				print_usage();
		} /* switch */
		value = digit*place;
		place <<= shift_base;
		sum += value;
		ptr++;
	} /* while */
	return(sum);
} /* atoui() */

void print_usage(void) {
	printf(VERSION_INFO);
	printf("USAGE: BootFlag <check|print|set|clear|tog|=> <flags> [-f<file>]\n");
	printf("\tflags : specifier representing 32 binary flags to manipulate\n");
	printf("\t\tSyntax: [+|-][$|&|%]<digits>[<+|->[$|&|%]<digits>]\n");
	printf("\t\t+: check if flags are ON, -: check if flags are OFF\n");
	printf("\t\t$hexadecimal#, &octal#, %binary#, or (none)decimal#\n");
	printf("\tcheck : compare flags, return Warn if the given pattern matches\n");
	printf("\tprint : print out current flag settings\n");
	printf("\t\tUse [$|&|%] in place of <flags> for output format.\n");
	printf("\tThe commands below only use the + flag value:\n");
	printf("\tset   : set given flags to ON state\n");
	printf("\tclear : set given flags to OFF state\n");
	printf("\ttog   : toggle given flags\n");
	printf("\t=     : set entire 32-bit register equal to given flags\n");
	printf("\tFile option(s):\n");
	printf("\t-f    : use a different register file (<file> required)\n");
	printf("\tfile  : path and filename of the register file to use\n");
	exit(10);
} /* print_usage() */
