/**
 * Copyright (C) 2006 International Business Machines Corp.
 *   Author(s): Michael A. Halcrow <mahalcro@us.ibm.com>
 *              Trevor Highland <trevor.highland@gmail.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 */

#include <errno.h>
#include <stdint.h>
#ifndef S_SPLINT_S
#include <stdio.h>
#include <syslog.h>
#endif
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "../include/ecryptfs.h"
#include "../include/decision_graph.h"

int stack_push(struct val_node **head, void *val)
{
	struct val_node *node = malloc(sizeof(struct val_node));
	int rc = 0;

	if (!node) {
		rc = -ENOMEM;
		goto out;
	}
	node->val = val;
	node->prev = *head;
	*head = node;
out:
	return rc;
}

int stack_pop(struct val_node **head)
{
	struct val_node *tmp = (*head)->prev;

	free((*head)->val);
	free(*head);
	*head = tmp;
	return 0;
}

int stack_pop_val(struct val_node **head, void **val)
{
	if (*head && val) {
		struct val_node *tmp = (*head)->prev;

		*val = (*head)->val;
		free(*head);
		*head = tmp;
		return 0;
	}
	return -1;
}

int free_name_val_pairs(struct ecryptfs_name_val_pair *pair)
{
	struct ecryptfs_name_val_pair *next;

	while (pair) {
		if (pair->value)
			free(pair->value);
		next = pair->next;
		free(pair);
		pair = next;
	}
	return 0;
}

int add_transition_node_to_param_node(struct param_node *param_node,
				      struct transition_node *trans_node)
{
	int rc;

	if (param_node->num_transitions >= MAX_NUM_TRANSITIONS) {
		syslog(LOG_ERR, "Too many transitions on node with primary "
		       "alias [%s]\n", param_node->mnt_opt_names[0]);
		rc = -ENOMEM;
		goto out;
	}
	memcpy(&(param_node->tl[param_node->num_transitions++]),
	       trans_node, sizeof(*trans_node));
	rc = 0;
out:
	return rc;
}

int set_exit_param_node_for_node(struct param_node *param_node,
				 struct param_node *exit_param_node,
				 int recursive)
{
	int i;
	int rc = 0;

	for (i = 0; i < param_node->num_transitions; i++)
		if (param_node->tl[i].next_token == NULL) {
			param_node->tl[i].val = "default";
			param_node->tl[i].pretty_val = "default";
			param_node->tl[i].next_token = exit_param_node;
		} else if (recursive) {
			rc = set_exit_param_node_for_node(
				param_node->tl[i].next_token,
				exit_param_node, 1);
			if (rc)
				goto out;
		}
out:
	return rc;
}

/**
 * Sets the exist param node for all NULL transitions throughout an
 * entire graph.
 */
int ecryptfs_set_exit_param_on_graph(struct param_node *param_node,
				     struct param_node *exit_param_node)
{
	return set_exit_param_node_for_node(param_node, exit_param_node, 1);
}

int set_exit_param_node_for_arr(struct param_node param_node_arr[],
				struct param_node *exit_param_node)
{
	int arr_len = sizeof(param_node_arr) / sizeof(param_node_arr[0]);
	int i;

	for (i = 0; i < arr_len; i++)
		set_exit_param_node_for_node(&param_node_arr[i],
					     exit_param_node, 0);
	return 0;
}

/* This function needs to compare transition nodes to options.
 * It is currently comparing them to values provided to options.
 * i.e., each transition is an option; this is incorrect.
 */
int do_transition(struct ecryptfs_ctx *ctx, struct param_node **next,
		  struct param_node *current,
		  struct ecryptfs_name_val_pair *nvp_head,
		  struct val_node **val_stack_head, void **foo)
{
	int i, rc;

	for (i = 0; i < current->num_transitions; i++) {
		struct transition_node *tn = &current->tl[i];
		struct ecryptfs_name_val_pair *nvp = nvp_head->next;

		if (tn->val && current->val && strcmp(current->val,tn->val)==0){
			rc = 0;
			if (tn->trans_func)
				rc = tn->trans_func(ctx, current,
						    val_stack_head, foo);
			if ((*next = tn->next_token))
				return rc;
			else return -EINVAL;
		}
		while (nvp) {
			int trans_func_tok_id = NULL_TOK;

			if (tn->val && strcmp(nvp->name, tn->val)) {
				nvp = nvp->next;
				continue;
			}
			if (tn->trans_func)
				trans_func_tok_id =
					tn->trans_func(ctx, current,
						       val_stack_head, foo);
			if (trans_func_tok_id == MOUNT_ERROR) {
				return trans_func_tok_id;
			}
			if (trans_func_tok_id == DEFAULT_TOK) {
				if ((*next = tn->next_token))
					return 0;
				else
					return -EINVAL;
			} else if (trans_func_tok_id == NULL_TOK) {
				if ((*next = tn->next_token))
					return 0;
				else
					return -EINVAL;
			}
			nvp = nvp->next;
		}
	}
	for (i = 0; i < current->num_transitions; i++) {
		struct transition_node *tn = &current->tl[i];

		if (tn->val && strcmp("default", tn->val) == 0){
			int trans_func_tok_id = NULL_TOK;

			if (tn->trans_func)
				trans_func_tok_id =
					tn->trans_func(ctx, current,
						       val_stack_head, foo);
			if (trans_func_tok_id == MOUNT_ERROR)
				return trans_func_tok_id;
			if ((*next = tn->next_token))
				return 0;
			else return -EINVAL;
		}
	}
	return MOUNT_ERROR;
}

/**
 * Try to find one of the alises for this node in the list of
 * name-value pairs. If found, set the value from that element in the
 * list.
 */
static int retrieve_val(struct ecryptfs_name_val_pair *nvp_head,
			struct param_node *node)
{
	int i = node->num_mnt_opt_names;

	while (i > 0) {
		struct ecryptfs_name_val_pair *temp = nvp_head->next;

		i--;
		while (temp) {
			if (strcmp(temp->name, node->mnt_opt_names[i]) == 0) {
				if (temp->value
				    && (strcmp(temp->value, "(null)") != 0)) {
					if (asprintf(&node->val, "%s",
						     temp->value) == -1)
						return -ENOMEM;
				} else {
					node->flags |= PARAMETER_SET;
					return -1;
				}
				return 0;
			}
			temp = temp->next;
		}
	}
	if (node->default_val) {
		if (asprintf(&node->val, "%s", node->default_val) == -1)
			return -ENOMEM;
		return 0;
	}
	return -1;
}


/**
 * This function can prompt the user and/or check some list of values
 * to get what it needs. Caller must free node->val if it winds up
 * being non-NULL.
 */
int alloc_and_get_val(struct ecryptfs_ctx *ctx, struct param_node *node,
		      struct ecryptfs_name_val_pair *nvp_head)
{
	char *verify_prompt;
	char *verify;
	int rc = 0;
	int val;

	if (!retrieve_val(nvp_head, node))
		return 0;
	if (node->flags & NO_VALUE)
		return 0;
	if (ctx->verbosity == 0 && !(node->flags & STDIN_REQUIRED))
		return 0;
	if ((node->flags & PARAMETER_SET) && !(node->flags & STDIN_REQUIRED))
		return 0;
	if (ctx->get_string) {
		if (node->flags & DISPLAY_TRANSITION_NODE_VALS) {
			int i, strpos = 0;
			int node_prompt_len = strlen(node->prompt);
			int default_val_len = 0;
			int len;
			char *prompt;

			if (node->default_val)
				default_val_len = strlen(node->default_val);
			len = node_prompt_len + default_val_len + 32;
			if (node->num_transitions == 1) {
				if (asprintf(&(node->val), "%s",
					     node->tl[0].val) == -1)
					return MOUNT_ERROR;
				return 0;
			}
			for (i = 0; i < node->num_transitions; i++)
				len += (strlen(node->tl[i].val) + 5);
			prompt = malloc(len);
			if (!prompt)
				return -ENOMEM;
			memcpy(prompt, node->prompt, node_prompt_len);
			prompt[node_prompt_len] = ':';
			strpos = node_prompt_len + 1;
			prompt[strpos++] = '\n';
			for (i = 0; i < node->num_transitions; i++) {
				prompt[strpos++] = ' ';
				prompt[strpos++] = '0' + (char)(i + 1);
				prompt[strpos++] = ')';
				prompt[strpos++] = ' ';
				memcpy(&prompt[strpos], node->tl[i].val,
				       strlen(node->tl[i].val));
				strpos += strlen(node->tl[i].val);
				prompt[strpos++] = '\n';
			}
			memcpy(&prompt[strpos], "Selection", 9);
			strpos += 9;
			if (node->default_val) {
				memcpy(&prompt[strpos], " [", 2);
				strpos += 2;
				memcpy(&prompt[strpos], node->default_val,
				       default_val_len);
				strpos += default_val_len;
				memcpy(&prompt[strpos], "]", 1);
				strpos += 1;
			}
			prompt[strpos] = '\0';
get_value:
			rc = (ctx->get_string)(&(node->val), prompt,
					       node->flags & ECHO_INPUT);
			val = atoi(node->val);
			if (val > 0 && val <= node->num_transitions) {
				free(node->val);
				asprintf(&(node->val), "%s",
					 node->tl[val - 1].val);
			} else {
				goto get_value;
			}
			free(prompt);
			return rc;
		} else {
obtain_value:
			rc = (ctx->get_string)(&(node->val), node->prompt,
					       node->flags & ECHO_INPUT);
			if (node->flags & VERIFY_VALUE) {
				rc = asprintf(&verify_prompt, "Verify %s",
					      node->prompt);
				if (rc == -1)
					return MOUNT_ERROR;
				rc = (ctx->get_string)(&verify, verify_prompt,
						      node->flags & ECHO_INPUT);
				if (rc)
					return MOUNT_ERROR;
				if (strcmp(verify, node->val))
					goto obtain_value;
			}
			return rc;
		}
	}
	return MOUNT_ERROR;
}

static void get_verbosity(struct ecryptfs_name_val_pair *nvp_head,
			  int *verbosity)
{
	struct ecryptfs_name_val_pair *temp = nvp_head->next;

	*verbosity = 1;
	while (temp) {
		if (strcmp(temp->name, "verbosity") == 0) {
			*verbosity = atoi(temp->value);
			return ;
		}
		temp = temp->next;
	}
	return;
}

int eval_param_tree(struct ecryptfs_ctx *ctx, struct param_node *node,
		    struct ecryptfs_name_val_pair *nvp_head,
		    struct val_node **val_stack_head)
{
	void *foo = NULL;
	int rc;

	get_verbosity(nvp_head, &(ctx->verbosity));
	do {
		if ((rc = alloc_and_get_val(ctx, node, nvp_head)))
			return rc;
	} while (!(rc = do_transition(ctx, &node, node, nvp_head,
				      val_stack_head, &foo)));
	return rc;
}

int decision_graph_mount(struct ecryptfs_ctx *ctx, struct val_node **head,
			 struct param_node *root_node,
			 struct ecryptfs_name_val_pair *nvp_head) {
	int rc;

	memset(*head, 0, sizeof(struct val_node));
	rc = eval_param_tree(ctx, root_node, nvp_head, head);
	if (rc == MOUNT_ERROR)
		goto out;
	else
		rc = 0;
out:
	return rc;
}


static void print_whitespace(FILE *file_stream, int depth)
{
	int i;

	for (i = 0; i < depth; i++)
		fprintf(file_stream, " ");
}

void ecryptfs_dump_param_node(FILE *file_stream,
			      struct param_node *param_node, int depth,
			      int recursive);

void ecryptfs_dump_transition_node(FILE *file_stream,
				   struct transition_node *trans_node,
				   int depth, int recursive)
{
	print_whitespace(file_stream, depth);
	fprintf(file_stream, "---------------\n");
	print_whitespace(file_stream, depth);
	fprintf(file_stream, "transition_node\n");
	print_whitespace(file_stream, depth);
	fprintf(file_stream, "---------------\n");
	print_whitespace(file_stream, depth);
	fprintf(file_stream, "val = [%s]\n", trans_node->val);
	print_whitespace(file_stream, depth);
	fprintf(file_stream, "next_token = [0x%p]\n", trans_node->next_token);
	if (recursive && trans_node->next_token)
		ecryptfs_dump_param_node(file_stream, trans_node->next_token,
					 depth + 1, recursive);
	print_whitespace(file_stream, depth);
	fprintf(file_stream, "---------------\n");
}

void ecryptfs_dump_param_node(FILE *file_stream,
			      struct param_node *param_node, int depth,
			      int recursive)
{
	int i;

	print_whitespace(file_stream, depth);
	fprintf(file_stream, "----------\n");
	print_whitespace(file_stream, depth);
	fprintf(file_stream, "param_node\n");
	print_whitespace(file_stream, depth);
	fprintf(file_stream, "----------\n");
	print_whitespace(file_stream, depth);
	fprintf(file_stream, "mnt_opt_names[0] = [%s]\n",
		param_node->mnt_opt_names[0]);
	print_whitespace(file_stream, depth);
	fprintf(file_stream, "num_transitions = [%d]\n",
		param_node->num_transitions);
	for (i = 0; i < param_node->num_transitions; i++) {
		print_whitespace(file_stream, depth);
		fprintf(file_stream, "transition node [%d]:\n", i);
		ecryptfs_dump_transition_node(file_stream, &param_node->tl[i],
					      depth + 1, recursive);
	}
	print_whitespace(file_stream, depth);
	fprintf(file_stream, "----------\n");

}

void ecryptfs_dump_decision_graph(FILE *file_stream,
				  struct param_node *param_node, int depth)
{
	ecryptfs_dump_param_node(file_stream, param_node, depth, 1);
}
