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

/* Forward declaration
 */
void walk_usage(command_t *);

/*
 * walk_cmd() -- walk linked lists of kernel data structures 
 */
int
walk_cmd(command_t *cmd)
{
	int count = 0;
	uint64_t size, byte_size, value;
	unsigned offset;
	kaddr_t start_addr, last_addr, next_addr;

	if (cmd->flags & C_LIST) {
		structlist(cmd->ofp);
		return(0);
	}

	/* Make sure we have the right number of paramaters
	 */
	if (cmd->nargs != 3) {
		walk_usage(cmd);
		return(1);
	}

	/* Determine if the first paramater is an address or the name of
	 * a struct. If it is an address, then do a hex dump of a size block
	 * of memory and walk to the next block using the offset value.
	 * Note that the kl_struct_len() function will return a zero
	 * for a valid struct name if a namelist has not been added. The
	 * error will be picked up below...
	 */
	if (kl_struct_len(cmd->args[0]) == 0) {
		GET_VALUE(cmd->args[0], &value);
		if (KL_ERROR) {
			kl_print_error();
			return(1);
		}
		start_addr = (kaddr_t)value;

		GET_VALUE(cmd->args[1], &value);
		if (KL_ERROR) {
			kl_print_error();
			return(1);
		}
		offset = (unsigned)value;

		GET_VALUE(cmd->args[2], &value);
		if (KL_ERROR) {
			kl_print_error();
			return(1);
		}

		/* Set byte_size and size (number of nbpw units)
		 */
		byte_size = value;
		if (byte_size % KL_NBPW) {
			byte_size += (KL_NBPW - (byte_size % KL_NBPW));
		}
		size = (byte_size / KL_NBPW);

		next_addr = start_addr;
		while (next_addr && CHECK_ITER_THRESHOLD(count)) {

			/* Make sure the address is valid
			 */
			kl_is_valid_kaddr(next_addr, 
				(void *)NULL, WORD_ALIGN_FLAG);
			if (KL_ERROR) {
				break;
			}

			fprintf(cmd->ofp, "Dumping %"FMT64"d byte block at "
				"0x%"FMTPTR"x:\n\n", 
				byte_size, next_addr);
			dump_memory(next_addr, size, 0, cmd->ofp);
			count++;
			fprintf(cmd->ofp, "\n");

			/* Get the next pointer (if any) at offset.
			 */
			last_addr = next_addr;
			kl_get_kaddr((next_addr + offset), (void *)&next_addr); 
			if (KL_ERROR) {
				break;
			}

			/* Make sure we haven't looped around to the 
			 * start of our list
			 */
			if ((next_addr == start_addr) 
					|| (next_addr == last_addr)) {
				break;
			}
		}
		ITER_THRESHOLD_MSG(cmd->ofp, count);
		if (count == 1) {
			fprintf(cmd->ofp, "1 block found\n");
		} else {
			fprintf(cmd->ofp, "%d blocks in linked list\n", count);
		}
		return(0);
	}

	/* Determine if a struct offset or field name has been entered
	 */
	GET_VALUE(cmd->args[1], &value);
	if (KL_ERROR) {
		offset = -1;
	} else {
		offset = value;
	}

	/* Get the start address of the structure
	 */
	GET_VALUE(cmd->args[2], &value);
	start_addr = (kaddr_t)value; 
	if (KL_ERROR) {
		kl_print_error();
		return(1);
	}

	walk_structs(cmd->args[0], cmd->args[1], offset, 
				start_addr, cmd->flags, cmd->ofp);
	return(0);
}

#define _WALK_USAGE \
"\n        -l"\
"\n        struct field|offset addr [-f] [-n] [-h n|p]"\
"\n        struct field|offset addr -s [-h n|p]"\
"\n        struct field|offset addr -h n|p -t"\
"\n        address offset size"\
"\n        [-i iteration_threshold]"\
"\n        [-w outfile]"

#define _WALK_HELP \
"Walk a linked list of kernel structures or memory blocks.\n"\
"\nOPTIONS:"\
"\n -l"\
"\n       Show a list of special structures, which can be displayed in a"\
"\n       predefined formatted manner."\
"\n       Currently there is support for a handful special structures."\
"\n struct field|offset addr [-f] [-n] [-h n|p]"\
"\n       Display  each entry of a linked list of special structures in"\
"\n       a predefined formatted way."\
"\n       By default, the output consists of one line for each structure."\
"\n       Using '-f' and/or '-n' a more detailed output is given."\
"\n       '-f' can be used for all special structures. '-n' works for"\
"\n       special structures \"mm_struct\" and \"task_struct\"."\
"\n struct field|offset addr -s [-h n|p]"\
"\n       Each structure of a linked list is displayed in its entirety -"\
"\n       in a C-like format. All structures for which type information is"\
"\n       available can be displayed in this manner."\
"\n -h n|p"\
"\n       A linked list is constructed by following \"list_head\" structures"\
"\n       instead of next pointers. The argument specifies wether to follow"\
"\n       the next pointers of struct list_head (using 'n') or to follow"\
"\n       the prev pointers of struct list_head (by using 'p')."\
"\n       'field' or 'offset' is regarded as a member of type \"list_head\""\
"\n       instead of a next pointer within the 'struct'. 'addr' is"\
"\n       interpreted as a pointer to an anchor of a linked list of"\
"\n       \"struct list_head\" structures."\
"\n struct field|offset addr -h n|p -t"\
"\n       Display each entry of a linked \"list_head\"-list in one line."\
"\n       For each entry the address to the 'struct' structure, the"\
"\n       address to the \"list_head\" member within 'struct', and previous"\
"\n       and next pointer of the embedded \"list_head\" are given."\
"\n address offset size"\
"\n       Do a hex memory dump of each structure in a list."\
"\n       A start address ('address') of a structure, a byte offset"\
"\n       ('offset') for the next pointer in the structure, and a"\
"\n       structure size ('size') are required. 'size' bytes will be"\
"\n       dumped for each entry in the constructed list."\
"\n -i iteration_threshold"\
"\n       By default, certain loops are interrupted after 10'000 iterations"\
"\n       to avoid endless loops while following invalid pointers. Using"\
"\n       this option you can change the threshold for the current command."\
"\n       A value '0' means infinite iteration threshold, i.e. no"\
"\n       interruption of the loop is caused by reaching any threshold.\n"\
"\nWhile using \"struct field|offset addr\" without '-h', a structure name"\
"\n('struct'), a field name ('field') or byte offset ('offset') for the next"\
"\npointer within the structure, and a pointer ('addr') to the first entry"\
"\nof the linked list must be given.\n"\
"\nNote: Using '-h' the anchor is not displayed as a structure 'struct'."

/*
 * walk_usage() -- Walk a linked list of structures
 */
void
walk_usage(command_t *cmd)
{
	CMD_USAGE(cmd, _WALK_USAGE);
}

/*
 * walk_help() -- Print the help information for the 'walk' command.
 */
void
walk_help(command_t *cmd)
{
	CMD_HELP(cmd, _WALK_USAGE, _WALK_HELP);
}

/*
 * walk_parse() -- Parse the command line arguments for 'walk'.
 */
int
walk_parse(command_t *cmd)
{
	option_t *op;

	if (set_cmd_flags(cmd, (C_FULL|C_NEXT|C_WRITE|C_LIST), "sh:ti:")) {
		return(1);
	}
	op = cmd->options;
	while (op) {
		switch(op->op_char) {
			case 's':
				cmd->flags |= C_STRUCT;
				break;
			case 'h':
				if(strlen(op->op_arg) != 1){
					fprintf(KL_ERRORFP, "Argument invalid "
						"for command line option "
						"'h'.\n");
					return(1);
				}
				if(op->op_arg[0] == 'n'){
					cmd->flags |= C_LISTHEAD_N;
				} else if(op->op_arg[0] == 'p'){
					cmd->flags |= C_LISTHEAD_P;
				} else {
					fprintf(KL_ERRORFP, "Argument invalid "
						"for command line option "
						"'h'.\n");
					return(1);
				}
				break;
			case 't':
				cmd->flags |= C_TABLE;
				break;
			case 'i':
				cmd->flags |= C_ITER;
				kl_get_value(op->op_arg, 0, 0, &iter_threshold);
				if(KL_ERROR){
					fprintf(KL_ERRORFP, "Invalid value for "
						"iter_threshold.\n");
					return(1);
				}
				break;
		}
		op = op->op_next;
	}
	if((cmd->flags & C_TABLE) && !(cmd->flags & C_LISTHEAD)){
		fprintf(KL_ERRORFP, "'t' can only be used together with "
			"command line option 'h'.\n");
		return(1);
	} else if((cmd->flags & C_TABLE) && (cmd->flags & C_STRUCT)){
		fprintf(KL_ERRORFP, "Command line options 't' and 's' can not "
			"be specified together.\n");
		return(1);
	}
	return(0);
}

/*
 * walk_complete() -- Complete arguments of 'walk' command.
 */
char *
walk_complete(command_t *cmd)
{
	char *ret;

	/* complete standard options (for example, -w option) arguments
	 */
	if ((ret = complete_standard_options(cmd)) != NOT_COMPLETED) {
		return(ret);
	}
	fprintf(cmd->ofp, "\n");
	walk_usage(cmd);
	return(DRAW_NEW_ENTIRE_LINE);
}
