/* -*- mode: c; c-basic-offset: 8; -*-
 * vim: noexpandtab sw=8 ts=8 sts=0:
 *
 * Copyright (C) 2004, 2005, 2012 Oracle.  All rights reserved.
 *
 * 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 021110-1307, USA.
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/configfs.h>

#include "heartbeat.h"
#include "tcp.h"
#include "nodemanager.h"

#include "masklog.h"

/*
 * The first heartbeat pass had one global thread that would serialize all hb
 * callback calls.  This global serializing sem should only be removed once
 * we've made sure that all callees can deal with being called concurrently
 * from multiple hb region threads.
 */
static DECLARE_RWSEM(r2hb_callback_sem);

/*
 * multiple hb threads are watching multiple regions.  A node is live
 * whenever any of the threads sees activity from the node in its region.
 */
static DEFINE_SPINLOCK(r2hb_live_lock);
static unsigned long r2hb_live_node_bitmap[BITS_TO_LONGS(R2NM_MAX_NODES)];

static struct r2hb_callback {
	struct list_head list;
} r2hb_callbacks[R2HB_NUM_CB];

enum r2hb_heartbeat_modes {
	R2HB_HEARTBEAT_LOCAL		= 0,
	R2HB_HEARTBEAT_GLOBAL,
	R2HB_HEARTBEAT_NUM_MODES,
};

char *r2hb_heartbeat_mode_desc[R2HB_HEARTBEAT_NUM_MODES] = {
		"local",	/* R2HB_HEARTBEAT_LOCAL */
		"global",	/* R2HB_HEARTBEAT_GLOBAL */
};

unsigned int r2hb_dead_threshold = R2HB_DEFAULT_DEAD_THRESHOLD;
unsigned int r2hb_heartbeat_mode = R2HB_HEARTBEAT_LOCAL;

/* Only sets a new threshold if there are no active regions.
 *
 * No locking or otherwise interesting code is required for reading
 * r2hb_dead_threshold as it can't change once regions are active and
 * it's not interesting to anyone until then anyway. */
static void r2hb_dead_threshold_set(unsigned int threshold)
{
	if (threshold > R2HB_MIN_DEAD_THRESHOLD) {
		spin_lock(&r2hb_live_lock);
		r2hb_dead_threshold = threshold;
		spin_unlock(&r2hb_live_lock);
	}
}

static int r2hb_global_hearbeat_mode_set(unsigned int hb_mode)
{
	int ret = -1;

	if (hb_mode < R2HB_HEARTBEAT_NUM_MODES) {
		spin_lock(&r2hb_live_lock);
		r2hb_heartbeat_mode = hb_mode;
		ret = 0;
		spin_unlock(&r2hb_live_lock);
	}

	return ret;
}

void r2hb_exit(void)
{
}

int r2hb_init(void)
{
	int i;

	for (i = 0; i < ARRAY_SIZE(r2hb_callbacks); i++)
		INIT_LIST_HEAD(&r2hb_callbacks[i].list);

	memset(r2hb_live_node_bitmap, 0, sizeof(r2hb_live_node_bitmap));

	return 0;
}

/* if we're already in a callback then we're already serialized by the sem */
static void r2hb_fill_node_map_from_callback(unsigned long *map,
					     unsigned bytes)
{
	BUG_ON(bytes < (BITS_TO_LONGS(R2NM_MAX_NODES) * sizeof(unsigned long)));

	memcpy(map, &r2hb_live_node_bitmap, bytes);
}

/*
 * get a map of all nodes that are heartbeating in any regions
 */
void r2hb_fill_node_map(unsigned long *map, unsigned bytes)
{
	/* callers want to serialize this map and callbacks so that they
	 * can trust that they don't miss nodes coming to the party */
	down_read(&r2hb_callback_sem);
	spin_lock(&r2hb_live_lock);
	r2hb_fill_node_map_from_callback(map, bytes);
	spin_unlock(&r2hb_live_lock);
	up_read(&r2hb_callback_sem);
}
EXPORT_SYMBOL_GPL(r2hb_fill_node_map);

/*
 * heartbeat configfs bits.  The heartbeat set is a default set under
 * the cluster set in nodemanager.c.
 */

/* heartbeat set */

struct r2hb_hb_group {
	struct config_group hs_group;
	/* some stuff? */
};

static struct r2hb_hb_group *to_r2hb_hb_group(struct config_group *group)
{
	return group ?
		container_of(group, struct r2hb_hb_group, hs_group)
		: NULL;
}

static struct config_item r2hb_config_item;

static struct config_item *r2hb_hb_group_make_item(struct config_group *group,
							  const char *name)
{
	int ret;

	if (strlen(name) > R2HB_MAX_REGION_NAME_LEN) {
		ret = -ENAMETOOLONG;
		goto free;
	}

	config_item_put(&r2hb_config_item);

	return &r2hb_config_item;
free:
	return ERR_PTR(ret);
}

static void r2hb_hb_group_drop_item(struct config_group *group,
					   struct config_item *item)
{
	if (r2hb_global_heartbeat_active()) {
		printk(KERN_NOTICE "ramster: Heartbeat %s "
			"on region %s (%s)\n",
			"stopped/aborted", config_item_name(item),
			"no region");
	}

	config_item_put(item);
}

struct r2hb_hb_group_attribute {
	struct configfs_attribute attr;
	ssize_t (*show)(struct r2hb_hb_group *, char *);
	ssize_t (*store)(struct r2hb_hb_group *, const char *, size_t);
};

static ssize_t r2hb_hb_group_show(struct config_item *item,
					 struct configfs_attribute *attr,
					 char *page)
{
	struct r2hb_hb_group *reg = to_r2hb_hb_group(to_config_group(item));
	struct r2hb_hb_group_attribute *r2hb_hb_group_attr =
		container_of(attr, struct r2hb_hb_group_attribute, attr);
	ssize_t ret = 0;

	if (r2hb_hb_group_attr->show)
		ret = r2hb_hb_group_attr->show(reg, page);
	return ret;
}

static ssize_t r2hb_hb_group_store(struct config_item *item,
					  struct configfs_attribute *attr,
					  const char *page, size_t count)
{
	struct r2hb_hb_group *reg = to_r2hb_hb_group(to_config_group(item));
	struct r2hb_hb_group_attribute *r2hb_hb_group_attr =
		container_of(attr, struct r2hb_hb_group_attribute, attr);
	ssize_t ret = -EINVAL;

	if (r2hb_hb_group_attr->store)
		ret = r2hb_hb_group_attr->store(reg, page, count);
	return ret;
}

static ssize_t r2hb_hb_group_threshold_show(struct r2hb_hb_group *group,
						     char *page)
{
	return sprintf(page, "%u\n", r2hb_dead_threshold);
}

static ssize_t r2hb_hb_group_threshold_store(struct r2hb_hb_group *group,
						    const char *page,
						    size_t count)
{
	unsigned long tmp;
	char *p = (char *)page;
	int err;

	err = kstrtoul(p, 10, &tmp);
	if (err)
		return err;

	/* this will validate ranges for us. */
	r2hb_dead_threshold_set((unsigned int) tmp);

	return count;
}

static
ssize_t r2hb_hb_group_mode_show(struct r2hb_hb_group *group,
				       char *page)
{
	return sprintf(page, "%s\n",
		       r2hb_heartbeat_mode_desc[r2hb_heartbeat_mode]);
}

static
ssize_t r2hb_hb_group_mode_store(struct r2hb_hb_group *group,
					const char *page, size_t count)
{
	unsigned int i;
	int ret;
	size_t len;

	len = (page[count - 1] == '\n') ? count - 1 : count;
	if (!len)
		return -EINVAL;

	for (i = 0; i < R2HB_HEARTBEAT_NUM_MODES; ++i) {
		if (strnicmp(page, r2hb_heartbeat_mode_desc[i], len))
			continue;

		ret = r2hb_global_hearbeat_mode_set(i);
		if (!ret)
			printk(KERN_NOTICE "ramster: Heartbeat mode "
						"set to %s\n",
			       r2hb_heartbeat_mode_desc[i]);
		return count;
	}

	return -EINVAL;

}

static struct r2hb_hb_group_attribute r2hb_hb_group_attr_threshold = {
	.attr	= { .ca_owner = THIS_MODULE,
		    .ca_name = "dead_threshold",
		    .ca_mode = S_IRUGO | S_IWUSR },
	.show	= r2hb_hb_group_threshold_show,
	.store	= r2hb_hb_group_threshold_store,
};

static struct r2hb_hb_group_attribute r2hb_hb_group_attr_mode = {
	.attr   = { .ca_owner = THIS_MODULE,
		.ca_name = "mode",
		.ca_mode = S_IRUGO | S_IWUSR },
	.show   = r2hb_hb_group_mode_show,
	.store  = r2hb_hb_group_mode_store,
};

static struct configfs_attribute *r2hb_hb_group_attrs[] = {
	&r2hb_hb_group_attr_threshold.attr,
	&r2hb_hb_group_attr_mode.attr,
	NULL,
};

static struct configfs_item_operations r2hb_hearbeat_group_item_ops = {
	.show_attribute		= r2hb_hb_group_show,
	.store_attribute	= r2hb_hb_group_store,
};

static struct configfs_group_operations r2hb_hb_group_group_ops = {
	.make_item	= r2hb_hb_group_make_item,
	.drop_item	= r2hb_hb_group_drop_item,
};

static struct config_item_type r2hb_hb_group_type = {
	.ct_group_ops	= &r2hb_hb_group_group_ops,
	.ct_item_ops	= &r2hb_hearbeat_group_item_ops,
	.ct_attrs	= r2hb_hb_group_attrs,
	.ct_owner	= THIS_MODULE,
};

/* this is just here to avoid touching group in heartbeat.h which the
 * entire damn world #includes */
struct config_group *r2hb_alloc_hb_set(void)
{
	struct r2hb_hb_group *hs = NULL;
	struct config_group *ret = NULL;

	hs = kzalloc(sizeof(struct r2hb_hb_group), GFP_KERNEL);
	if (hs == NULL)
		goto out;

	config_group_init_type_name(&hs->hs_group, "heartbeat",
				    &r2hb_hb_group_type);

	ret = &hs->hs_group;
out:
	if (ret == NULL)
		kfree(hs);
	return ret;
}

void r2hb_free_hb_set(struct config_group *group)
{
	struct r2hb_hb_group *hs = to_r2hb_hb_group(group);
	kfree(hs);
}

/* hb callback registration and issuing */

static struct r2hb_callback *hbcall_from_type(enum r2hb_callback_type type)
{
	if (type == R2HB_NUM_CB)
		return ERR_PTR(-EINVAL);

	return &r2hb_callbacks[type];
}

void r2hb_setup_callback(struct r2hb_callback_func *hc,
			 enum r2hb_callback_type type,
			 r2hb_cb_func *func,
			 void *data,
			 int priority)
{
	INIT_LIST_HEAD(&hc->hc_item);
	hc->hc_func = func;
	hc->hc_data = data;
	hc->hc_priority = priority;
	hc->hc_type = type;
	hc->hc_magic = R2HB_CB_MAGIC;
}
EXPORT_SYMBOL_GPL(r2hb_setup_callback);

int r2hb_register_callback(const char *region_uuid,
			   struct r2hb_callback_func *hc)
{
	struct r2hb_callback_func *tmp;
	struct list_head *iter;
	struct r2hb_callback *hbcall;
	int ret;

	BUG_ON(hc->hc_magic != R2HB_CB_MAGIC);
	BUG_ON(!list_empty(&hc->hc_item));

	hbcall = hbcall_from_type(hc->hc_type);
	if (IS_ERR(hbcall)) {
		ret = PTR_ERR(hbcall);
		goto out;
	}

	down_write(&r2hb_callback_sem);

	list_for_each(iter, &hbcall->list) {
		tmp = list_entry(iter, struct r2hb_callback_func, hc_item);
		if (hc->hc_priority < tmp->hc_priority) {
			list_add_tail(&hc->hc_item, iter);
			break;
		}
	}
	if (list_empty(&hc->hc_item))
		list_add_tail(&hc->hc_item, &hbcall->list);

	up_write(&r2hb_callback_sem);
	ret = 0;
out:
	mlog(ML_CLUSTER, "returning %d on behalf of %p for funcs %p\n",
	     ret, __builtin_return_address(0), hc);
	return ret;
}
EXPORT_SYMBOL_GPL(r2hb_register_callback);

void r2hb_unregister_callback(const char *region_uuid,
			      struct r2hb_callback_func *hc)
{
	BUG_ON(hc->hc_magic != R2HB_CB_MAGIC);

	mlog(ML_CLUSTER, "on behalf of %p for funcs %p\n",
	     __builtin_return_address(0), hc);

	/* XXX Can this happen _with_ a region reference? */
	if (list_empty(&hc->hc_item))
		return;

	down_write(&r2hb_callback_sem);

	list_del_init(&hc->hc_item);

	up_write(&r2hb_callback_sem);
}
EXPORT_SYMBOL_GPL(r2hb_unregister_callback);

int r2hb_check_node_heartbeating_from_callback(u8 node_num)
{
	unsigned long testing_map[BITS_TO_LONGS(R2NM_MAX_NODES)];

	r2hb_fill_node_map_from_callback(testing_map, sizeof(testing_map));
	if (!test_bit(node_num, testing_map)) {
		mlog(ML_HEARTBEAT,
		     "node (%u) does not have heartbeating enabled.\n",
		     node_num);
		return 0;
	}

	return 1;
}
EXPORT_SYMBOL_GPL(r2hb_check_node_heartbeating_from_callback);

void r2hb_stop_all_regions(void)
{
}
EXPORT_SYMBOL_GPL(r2hb_stop_all_regions);

/*
 * this is just a hack until we get the plumbing which flips file systems
 * read only and drops the hb ref instead of killing the node dead.
 */
int r2hb_global_heartbeat_active(void)
{
	return (r2hb_heartbeat_mode == R2HB_HEARTBEAT_GLOBAL);
}
EXPORT_SYMBOL(r2hb_global_heartbeat_active);

/* added for RAMster */
void r2hb_manual_set_node_heartbeating(int node_num)
{
	if (node_num < R2NM_MAX_NODES)
		set_bit(node_num, r2hb_live_node_bitmap);
}
EXPORT_SYMBOL(r2hb_manual_set_node_heartbeating);
