/*
 * COPYRIGHT (c) 2006
 * THE REGENTS OF THE UNIVERSITY OF MICHIGAN
 * ALL RIGHTS RESERVED
 *
 * Permission is granted to use, copy, create derivative works
 * and redistribute this software and such derivative works
 * for any purpose, so long as the name of The University of
 * Michigan is not used in any advertising or publicity
 * pertaining to the use of distribution of this software
 * without specific, written prior authorization.  If the
 * above copyright notice or any other identification of the
 * University of Michigan is included in any copy of any
 * portion of this software, then the disclaimer below must
 * also be included.
 *
 * THIS SOFTWARE IS PROVIDED AS IS, WITHOUT REPRESENTATION
 * FROM THE UNIVERSITY OF MICHIGAN AS TO ITS FITNESS FOR ANY
 * PURPOSE, AND WITHOUT WARRANTY BY THE UNIVERSITY OF
 * MICHIGAN OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
 * WITHOUT LIMITATION THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
 * REGENTS OF THE UNIVERSITY OF MICHIGAN SHALL NOT BE LIABLE
 * FOR ANY DAMAGES, INCLUDING SPECIAL, INDIRECT, INCIDENTAL, OR
 * CONSEQUENTIAL DAMAGES, WITH RESPECT TO ANY CLAIM ARISING
 * OUT OF OR IN CONNECTION WITH THE USE OF THE SOFTWARE, EVEN
 * IF IT HAS BEEN OR IS HEREAFTER ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGES.
 */

#include <stdlib.h>
#include <string.h>
#include <syslog.h>

#include "fsloc.h"
#include "exportfs.h"

/* Debugging tool: prints out @servers info to syslog */
static void replicas_print(struct servers *sp)
{
	int i;
	if (!sp) {
		xlog(L_NOTICE, "NULL replicas pointer");
		return;
	}
	xlog(L_NOTICE, "replicas listsize=%i", sp->h_num);
	for (i=0; i<sp->h_num; i++) {
		xlog(L_NOTICE, "    %s:%s",
		       sp->h_mp[i]->h_host, sp->h_mp[i]->h_path);
	}
}

#ifdef DEBUG
/* Called by setting 'Method = stub' in config file.  Just returns
 * some syntactically correct gibberish for testing purposes.
 */
static struct servers *method_stub(char *key)
{
	struct servers *sp;
	struct mount_point *mp;

	xlog(L_NOTICE, "called method_stub\n");
	sp = malloc(sizeof(struct servers));
	if (!sp)
		return NULL;
	mp = calloc(1, sizeof(struct mount_point));
	if (!mp) {
		free(sp);
		return NULL;
	}
	sp->h_num = 1;
	sp->h_mp[0] = mp;
	mp->h_host = strdup("stub_server");
	mp->h_path = strdup("/my/test/path");
	sp->h_referral = 1;
	return sp;
}
#endif	/* DEBUG */

/* Scan @list, which is a NULL-terminated array of strings of the
 * form path@host[+host], and return corresponding servers structure.
 */
static struct servers *parse_list(char **list)
{
	int i;
	struct servers *res;
	struct mount_point *mp;
	char *cp;

	res = malloc(sizeof(struct servers));
	if (!res)
		return NULL;
	res->h_num = 0;

	/* parse each of the answers in sucession. */
	for (i=0; list[i] && i<FSLOC_MAX_LIST; i++) {
		mp = calloc(1, sizeof(struct mount_point));
		if (!mp) {
			release_replicas(res);
			return NULL;
		}
		cp = strchr(list[i], '@');
		if ((!cp) || list[i][0] != '/') {
			xlog(L_WARNING, "invalid entry '%s'", list[i]);
			free(mp);
			continue; /* XXX Need better error handling */
		}
		res->h_mp[i] = mp;
		res->h_num++;
		mp->h_path = strndup(list[i], cp - list[i]);
		cp++;
		mp->h_host = strdup(cp);
		/* hosts are '+' separated, kernel expects ':' separated */
		while ( (cp = strchr(mp->h_host, '+')) )
		       *cp = ':';
	}
	return res;
}

/* @data is a string of form path@host[+host][:path@host[+host]]
 */
static struct servers *method_list(char *data)
{
	char *copy, *ptr=data, *p;
	char **list;
	int i, listsize;
	struct servers *rv=NULL;
	bool v6esc = false;

	xlog(L_NOTICE, "method_list(%s)", data);
	for (ptr--, listsize=1; ptr; ptr=strchr(ptr, ':'), listsize++)
		ptr++;
	list = malloc(listsize * sizeof(char *));
	copy = strdup(data);
	if (copy)
		xlog(L_NOTICE, "converted to %s", copy);
	if (list && copy) {
		ptr = copy;
		for (p = ptr, i = 0; *p && i < listsize; p++) {
			if (*p == '[')
				v6esc = true;
			else if (*p == ']')
				v6esc = false;

			if (!v6esc && *p == ':') {
				*p = '\0';
				if (*ptr)
					list[i++] = ptr;
				ptr = p + 1;
			}
		}
		if (*ptr)
			list[i++] = ptr;
		list[i] = NULL;
		rv = parse_list(list);
	}
	free(copy);
	free(list);
	replicas_print(rv);
	return rv;
}

/* Returns appropriately filled struct servers, or NULL if had a problem */
struct servers *replicas_lookup(int method, char *data)
{
	struct servers *sp=NULL;
	switch(method) {
	case FSLOC_NONE:
		break;
	case FSLOC_REFER:
		sp = method_list(data);
		if (sp)
			sp->h_referral = 1;
		break;
	case FSLOC_REPLICA:
		sp = method_list(data);
		if (sp)
			sp->h_referral = 0;
		break;
#ifdef DEBUG
	case FSLOC_STUB:
		sp = method_stub(data);
		break;
#endif
	default:
		xlog(L_WARNING, "Unknown method = %i", method);
	}
	replicas_print(sp);
	return sp;
}

void release_replicas(struct servers *server)
{
	int i;

	if (!server) return;
	for (i = 0; i < server->h_num; i++) {
		free(server->h_mp[i]->h_host);
		free(server->h_mp[i]->h_path);
		free(server->h_mp[i]);
	}
	free(server);
}
