/*
 * Copyright 1999 Silicon Graphics, Inc. All rights reserved.
 */
#include <klib.h>

#define MAX_LONG_LONG	0xffffffffffffffff

#define BUFSZ 128
char tmp[BUFSZ];

#if DUMP_DEBUG
void dump_bp(void) {	/* Debug Breakpoint */
}
#endif

static int
valid_digit(char c, int base)
{
	switch(base) {
		case 2:
			if ((c >= '0') && (c <= '1')) {
				return(1);
			} else {
				return(0);
			}
		case 8:
			if ((c >= '0') && (c <= '7')) {
				return(1);
			} else {
				return(0);
			}
		case 10:
			if ((c >= '0') && (c <= '9')) {
				return(1);
			} else {
				return(0);
			}
		case 16:
			if (((c >= '0') && (c <= '9')) 
					|| ((c >= 'a') && (c <= 'f'))
					|| ((c >= 'A') && (c <= 'F'))) {
				return(1);
			} else {
				return(0);
			}
	}
	return(0);
}

static int
digit_value(char c, int base, int *val)
{
	if (!valid_digit(c, base)) {
		return(1);
	}
	switch (base) {
		case 2:
		case 8:
		case 10:
			*val = (int)((int)(c - 48));
			break;
		case 16:
			if ((c >= 'a') && (c <= 'f')) {
				*val = ((int)(c - 87));
			} else if ((c >= 'A') && (c <= 'F')) {
				*val = ((int)(c - 55));
			} else {
				*val = ((int)(c - 48));
			}
	} 
	return(0);	
}

/*
 * kl_strtoull()
 */
uint64_t 
kl_strtoull(char *str, char **loc, int base)
{
	int dval;
	uint64_t i = 1, v, value = 0;
	char *c, *cp = str;

	*loc = (char *)NULL;
	if (base == 0) {
		if (!strncmp(cp, "0x", 2) || !strncmp(cp, "0X", 2)) {
			base = 16;
		} else if (cp[0] == '0') {
			if (cp[1] == 'b') {
				base = 2;
			} else {
				base = 8;
			}
		} else if (strpbrk(cp, "abcdefABCDEF")) {
			base = 16;
		} else {
			base = 10;
		}
	}
	if ((base == 8) && (*cp == '0')) {
		cp += 1;
	} else if ((base == 2) && !strncmp(cp, "0b", 2)) {
		cp += 2;
	} else if ((base == 16) &&
			(!strncmp(cp, "0x", 2) || !strncmp(cp, "0X", 2))) {
		cp += 2;
	} 
	c = &cp[strlen(cp) - 1];
	while (c >= cp) {
		
		if (digit_value(*c, base, &dval)) {
			if (loc) {
				*loc = c;
			}
			return(value);
		}
		v = dval * i;
		if ((MAX_LONG_LONG - value) < v) {
			errno = ERANGE;	
			return(MAX_LONG_LONG);
		}
		value += v;
		i *= (uint64_t)base;
		c--;
	}
	return(value);
}

/*
 * Definitions for the do_math() routine.
 */
#define M_ADD      '+'
#define M_SUBTRACT '-'
#define M_MULTIPLY '*'
#define M_DIVIDE   '/'

/*
 * do_math() -- Calculate some math values based on a string argument
 *              passed into the function.  For example, if you use:
 *
 *              0xffffc000*2+6/5-3*19-8
 *
 *              And you will get the value 0xffff7fc0 back.  I could
 *              probably optimize this a bit more, but right now, it
 *              works, which is good enough for me.
 */
static uint64_t
do_math(char *str)
{
	int i = 0;
	char *buf, *loc;
	uint64_t value1, value2;
	syment_t *sp;

	buf = (char *)kl_alloc_block((strlen(str) + 1), K_TEMP);
	sprintf(buf, "%s", str);
	for (i = strlen(str); i >= 0; i--) {
		if ((str[i] == M_ADD) || (str[i] == M_SUBTRACT)) {
			buf[i] = '\0';
			value1 = do_math(buf);
			value2 = do_math(&str[i+1]);
			kl_free_block((void *)buf);
			if (str[i] == M_SUBTRACT) {
				return value1 - value2;
			} else {
				return value1 + value2;
			}
		}
	}

	for (i = strlen(str); i >= 0; i--) {
		if ((str[i] == M_MULTIPLY) || (str[i] == M_DIVIDE)) {
			buf[i] = '\0';
			value1 = do_math(buf);
			value2 = do_math(&str[i+1]);
			kl_free_block((void *)buf);
			if (str[i] == M_MULTIPLY) {
				return (value1 * value2);
			} else {
				if (value2 == 0) {
					/* handle divide by zero */
					/* XXX -- set proper error code */
					klib_error = 1;
					return (0);
				} else {
					return (value1 / value2);
				}
			}
		}
	}

	/*
	 * Otherwise, just process the value, and return it.
	 */
	sp = kl_lkup_symname(buf);
	if (KL_ERROR) {
		KL_ERROR = 0;
		value2 = (uint64_t)strtoul(buf, &loc, 10);
		if (((!value2) && (buf[0] != '0')) || (*loc) ||
			(!strncmp(buf, "0x", 2)) || (!strncmp(buf, "0X", 2))) {
			value1 = GET_HEX_VALUE(buf);
		} else {
			value1 = GET_DEC_VALUE(buf);
		}
	} else {
		value1 = (kaddr_t)sp->s_addr;
	}
	kl_free_block((void *)buf);
	return (value1);
}

/*
 * kl_get_value() -- Translate numeric input strings
 *
 *   A generic routine for translating an input string (param) in a
 *   number of dfferent ways. If the input string is an equation
 *   (contains the characters '+', '-', '/', and '*'), then perform
 *   the math evaluation and return one of the following modes (if
 *   mode is passed):
 *
 *   0 -- if the resulting value is <= elements, if elements (number
 *        of elements in a table) is passed.
 *
 *   1 -- if the first character in param is a pound sign ('#').
 *
 *   3 -- the numeric result of an equation.
 *
 *   If the input string is NOT an equation, mode (if passed) will be
 *   set in one of the following ways (depending on the contents of
 *   param and elements).
 *
 *   o When the first character of param is a pound sign ('#'), mode
 *     is set equal to one and the trailing numeric value (assumed to
 *     be decimal) is returned.
 *
 *   o When the first two characters in param are "0x" or "0X," or
 *     when when param contains one of the characers "abcdef," or when
 *     the length of the input value is eight characters. mode is set
 *     equal to two and the numeric value contained in param is
 *     translated as hexadecimal and returned.
 *
 *   o The value contained in param is translated as decimal and mode
 *     is set equal to zero. The resulting value is then tested to see
 *     if it exceeds elements (if passed). If it does, then value is
 *     translated as hexadecimal and mode is set equal to two.
 *
 *   Note that mode is only set when a pointer is passed in the mode
 *   paramater. Also note that when elements is set equal to zero, any 
 *   non-hex (as determined above) value not starting with a pound sign 
 *   will be translated as hexadecimal (mode will be set equal to two) --
 *   IF the length of the string of characters is less than 16 (kaddr_t).
 *
 */
int
kl_get_value(char *param, int *mode, int elements, uint64_t *value)
{
	char *loc;
	uint64_t v;

	kl_reset_error();

	/* Check to see if we are going to need to do any math
	 */
	if (strpbrk(param, "+-/*")) {
		if (!strncmp(param, "#", 1)) {
			v = do_math(&param[1]);
			if (mode) {
				*mode = 1;
			}
		} else {
			v = do_math(param);
			if (mode) {
				if (elements && (*value <= elements)) {
					*mode = 0;
				} else {
					*mode = 3;
				}
			}
		}
	} else {
		if (!strncmp(param, "#", 1)) {
			if (!strncmp(param, "0x", 2) 
					|| !strncmp(param, "0X", 2) 
					|| strpbrk(param, "abcdef")) {
				v = kl_strtoull(&param[1], &loc, 16);
			} else {
				v = kl_strtoull(&param[1], &loc, 10);
			}
			if (loc) {
				KL_ERROR = KLE_INVALID_VALUE;
				return (1);
			}
			if (mode) {
				*mode = 1;
			}
		} else if (!strncmp(param, "0x", 2) || !strncmp(param, "0X", 2) 
					|| strpbrk(param, "abcdef")) {
			v = kl_strtoull(param, &loc, 16);
			if (loc) {
				KL_ERROR = KLE_INVALID_VALUE;
				return (1);
			}
			if (mode) {
				*mode = 2; /* HEX VALUE */
			}
		} else if (elements || (strlen(param) < 16) || 
				(strlen(param) > 16)) {
			v = kl_strtoull(param, &loc, 10);
			if (loc) {
				KL_ERROR = KLE_INVALID_VALUE;
				return (1);
			}
			if (elements && (v >= elements)) {
				v = GET_HEX_VALUE(param);
				if (mode) {
					*mode = 2; /* HEX VALUE */
				}
			} else if (mode) {
				*mode = 0;
			}
		} else {
			v = kl_strtoull(param, &loc, 16);
			if (loc) {
				KL_ERROR = KLE_INVALID_VALUE;
				return (1);
			}
			if (mode) {
				*mode = 2; /* ASSUME HEX VALUE */
			}
		}
	}
	*value = v;
	return (0);
}

extern int __kl_dump_retrieve(char *dumpdev, char *dumpdir, int progress, int local_debug);
extern int __kl_dump_erase(char *dumpdev);

/*
 * kl_dump_retrieve() -- Retrieve and save a crash dump from <dumpdev>
 *                       into <dumpdir>.  Returns 0 on success, 1 on
 *                       failure.  The return codes are this way to handle
 *                       exit codes in 'lcrash'.
 */
int
kl_dump_retrieve(char *dumpdev, char *dumpdir, int progress, int local_debug)
{
	int ret;

	ret = __kl_dump_retrieve(dumpdev, dumpdir, progress, local_debug);
	
	switch (ret) {
		case 0:	/* failed to retrieve dump */

			/* fundamental error.  return 1 */
			return (1);

		case 1:	/* Truncated Dump Retrieved */
		case 2:	/* Complete  Dump Retrieved */
		default:
			/* erase the dump and fall through */
			if (local_debug == 0) {
				(void)__kl_dump_erase(dumpdev);
			}
			/* return success */
			return (0);
	}

	/* default */
	return (1);
}

int
kl_dump_erase(char *dumpdev)
{
	int ret;

	/* since this is destructive, ask before proceeding */
	printf("Do you really want to erase the dump on %s? (yes/no): ", dumpdev);
	fgets(tmp, BUFSZ, stdin);
	if (tmp[strlen(tmp) - 1] == '\n')	/* remove newline left by fgets */
		tmp[strlen(tmp) - 1] = '\0';
	
	if (strlen(tmp) == 0 || strstr("yes", tmp) == 0) {
		printf("Dump not erased\n");
		return (1);
	}
	
	/* erase the dump and fall through */
	ret = __kl_dump_erase(dumpdev);
	
	/* return success */
	if (ret)
		printf("Dump erased\n");
	else
		printf("Failed to erase dump\n");
	return (0);
}

/*
 * kl_get_bit_value()
 *
 * x = byte_size, y = bit_size, z = bit_offset
 */
uint64_t
kl_get_bit_value(void *ptr, int x, int y, int z)
{
        int right_shift;
        uint64_t value, mask;

        right_shift = (x * 8) - (y + z);
        if (right_shift < 0) {
                /* XXX -- set error value?
                 */
                return(0);
        }
        if (y >= 32) {
                uint64_t upper_bits = (y - 32);
                mask = (uint64_t)(((uint64_t)1 << upper_bits) - 1);
                mask = (mask << 32) | 0xffffffff;
        } else {
                mask = (uint64_t)((1 << y) - 1);
        }
        switch (x) {
                case 8:
                        value = *(uint64_t*)ptr;
                        break;
                case 4:
                        value = *(uint32_t*)ptr;
                        break;
                case 2:
                        value = *(unsigned short*)ptr;
                        break;

                case 1:
                        value = *(unsigned char*)ptr;
                        break;
        }
        value = value >> right_shift;
        return (value & mask);
}

/* 
 * kl_get_struct()
 */
int
kl_get_struct(kaddr_t addr, int size, void *ptr, char *name)
{
	GET_BLOCK(addr, size, ptr);
	return(KL_ERROR);
}

/*
 * kl_get_module()
 *
 * reads struct module from dump, returns either (in the given priority)
 *    - struct for module named modname (modname!=NULL)
 *    - struct for module where vaddr points to (modname==NULL, vaddr!=0)
 *    - struct for first module in module_list (modname==NULL, vaddr==0)
 * function allocates memory if *ptr_module==NULL,
 * this memory has to be released in the calling function using kl_free_block()
 */
int
kl_get_module(char *modname, kaddr_t *vaddr, void **ptr_module)
{
	syment_t *sym_module_list = NULL;
	void *dump_page = NULL;
	int mod_found = 0;
	kaddr_t dump_modname = 0;
	kaddr_t addr_mod = 0;
	size_t size=0;

	kl_reset_error();


	if(!*vaddr) {
		if(!(sym_module_list = kl_lkup_symname("module_list"))){
			KL_ERROR = KLE_NO_MODULE_LIST;
			return(1);
		}
		GET_BLOCK(sym_module_list->s_addr, KL_NBPW, &addr_mod);
	} else {
		addr_mod = *vaddr;
	}

	dump_page = kl_alloc_block(dump_page_size, K_TEMP);
	if (KL_ERROR) {
		return(1);
	}

	if(modname){
		if(!strcmp(modname, KL_KERNEL_MODULE)){
			sym_module_list = kl_lkup_symname(KL_KERNEL_MODULE);
			addr_mod = sym_module_list->s_addr;
			modname=NULL;
		} else {
			sym_module_list = kl_lkup_symname("module_list");
			GET_BLOCK(sym_module_list->s_addr, KL_NBPW, &addr_mod);
		}
	}

	if(modname) {
		while(addr_mod){
			if(kl_get_structure(addr_mod, "module", 
					    &size, ptr_module)){
				kl_free_block(dump_page);
				return(1);
			}
			*vaddr= addr_mod;
			dump_modname = kl_kaddr(*ptr_module, "module", "name");
			GET_BLOCK(dump_modname, dump_page_size, dump_page);
			if(!strcmp(modname, (char *)dump_page)) {
				mod_found = 1;
				break;
			}
			addr_mod = kl_kaddr(*ptr_module, "module", "next");
		}
	} else {
		if(kl_get_structure(addr_mod, "module",
				    &size, ptr_module)){
			kl_free_block(dump_page);
			return(1);
		}
		mod_found = 1;
		*vaddr= addr_mod;
	}

	kl_free_block(dump_page);
	if(!mod_found){
		return(1);
	}
	return(0);
}

/*
 * kl_get_structure()
 *
 * return structure name from address vaddr of dump
 * function allocates memory if *ptr==NULL,
 * this memory has to be released in the calling function using kl_free_block()
 */

int
kl_get_structure(kaddr_t vaddr, char * name, size_t * size, void **ptr)
{
	int  free_ptr = 0;

	if(!*size){
		*size = kl_struct_len(name);
		if(KL_ERROR){
			return(1);
		}
	}

	if(*ptr == NULL){
		*ptr = kl_alloc_block(*size, K_TEMP);
		if (KL_ERROR) {
			return(1);
		}
		free_ptr = 1;
	}

	if(vaddr){
		GET_BLOCK(vaddr, *size, *ptr);
		if (KL_ERROR) {
			if(free_ptr){
				kl_free_block(*ptr);
				*ptr = NULL;
			}
			return(1);
		}
	} else {
		return(1);
	}

	return(0);
}

