/* cmservice.c: AFS Cache Manager Service
 *
 * Copyright (C) 2002 Red Hat, Inc. All Rights Reserved.
 * Written by David Howells (dhowells@redhat.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.
 */

#include <linux/version.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/completion.h>
#include "server.h"
#include "cell.h"
#include "transport.h"
#include <rxrpc/rxrpc.h>
#include <rxrpc/transport.h>
#include <rxrpc/connection.h>
#include <rxrpc/call.h>
#include "cmservice.h"
#include "internal.h"

static unsigned afscm_usage;		/* AFS cache manager usage count */
static struct rw_semaphore afscm_sem;	/* AFS cache manager start/stop semaphore */

static int afscm_new_call(struct rxrpc_call *call);
static void afscm_attention(struct rxrpc_call *call);
static void afscm_error(struct rxrpc_call *call);
static void afscm_aemap(struct rxrpc_call *call);

static void _SRXAFSCM_CallBack(struct rxrpc_call *call);
static void _SRXAFSCM_InitCallBackState(struct rxrpc_call *call);
static void _SRXAFSCM_Probe(struct rxrpc_call *call);

typedef void (*_SRXAFSCM_xxxx_t)(struct rxrpc_call *call);

static const struct rxrpc_operation AFSCM_ops[] = {
	{
		.id	= 204,
		.asize	= RXRPC_APP_MARK_EOF,
		.name	= "CallBack",
		.user	= _SRXAFSCM_CallBack,
	},
	{
		.id	= 205,
		.asize	= RXRPC_APP_MARK_EOF,
		.name	= "InitCallBackState",
		.user	= _SRXAFSCM_InitCallBackState,
	},
	{
		.id	= 206,
		.asize	= RXRPC_APP_MARK_EOF,
		.name	= "Probe",
		.user	= _SRXAFSCM_Probe,
	},
#if 0
	{
		.id	= 207,
		.asize	= RXRPC_APP_MARK_EOF,
		.name	= "GetLock",
		.user	= _SRXAFSCM_GetLock,
	},
	{
		.id	= 208,
		.asize	= RXRPC_APP_MARK_EOF,
		.name	= "GetCE",
		.user	= _SRXAFSCM_GetCE,
	},
	{
		.id	= 209,
		.asize	= RXRPC_APP_MARK_EOF,
		.name	= "GetXStatsVersion",
		.user	= _SRXAFSCM_GetXStatsVersion,
	},
	{
		.id	= 210,
		.asize	= RXRPC_APP_MARK_EOF,
		.name	= "GetXStats",
		.user	= _SRXAFSCM_GetXStats,
	}
#endif
};

static struct rxrpc_service AFSCM_service = {
	.name		= "AFS/CM",
	.owner		= THIS_MODULE,
	.link		= LIST_HEAD_INIT(AFSCM_service.link),
	.new_call	= afscm_new_call,
	.service_id	= 1,
	.attn_func	= afscm_attention,
	.error_func	= afscm_error,
	.aemap_func	= afscm_aemap,
	.ops_begin	= &AFSCM_ops[0],
	.ops_end	= &AFSCM_ops[sizeof(AFSCM_ops)/sizeof(AFSCM_ops[0])],
};

static DECLARE_COMPLETION(kafscmd_alive);
static DECLARE_COMPLETION(kafscmd_dead);
static DECLARE_WAIT_QUEUE_HEAD(kafscmd_sleepq);
static LIST_HEAD(kafscmd_attention_list);
static LIST_HEAD(afscm_calls);
static spinlock_t afscm_calls_lock = SPIN_LOCK_UNLOCKED;
static spinlock_t kafscmd_attention_lock = SPIN_LOCK_UNLOCKED;
static int kafscmd_die;

/*****************************************************************************/
/*
 * AFS Cache Manager kernel thread
 */
static int kafscmd(void *arg)
{
	DECLARE_WAITQUEUE(myself,current);

	struct rxrpc_call *call;
	_SRXAFSCM_xxxx_t func;
	int die;

	printk("kAFS: Started kafscmd %d\n",current->pid);
	daemonize("kafscmd");

	complete(&kafscmd_alive);

	/* loop around looking for things to attend to */
	do {
		if (list_empty(&kafscmd_attention_list)) {
			set_current_state(TASK_INTERRUPTIBLE);
			add_wait_queue(&kafscmd_sleepq,&myself);

			for (;;) {
				set_current_state(TASK_INTERRUPTIBLE);
				if (!list_empty(&kafscmd_attention_list) ||
				    signal_pending(current) ||
				    kafscmd_die)
					break;

				schedule();
			}

			remove_wait_queue(&kafscmd_sleepq,&myself);
			set_current_state(TASK_RUNNING);
		}

		die = kafscmd_die;

		/* dequeue the next call requiring attention */
		call = NULL;
		spin_lock(&kafscmd_attention_lock);

		if (!list_empty(&kafscmd_attention_list)) {
			call = list_entry(kafscmd_attention_list.next,
					  struct rxrpc_call,
					  app_attn_link);
			list_del_init(&call->app_attn_link);
			die = 0;
		}

		spin_unlock(&kafscmd_attention_lock);

		if (call) {
			/* act upon it */
			_debug("@@@ Begin Attend Call %p",call);

			func = call->app_user;
			if (func)
				func(call);

			rxrpc_put_call(call);

			_debug("@@@ End Attend Call %p",call);
		}

	} while(!die);

	/* and that's all */
	complete_and_exit(&kafscmd_dead,0);

} /* end kafscmd() */

/*****************************************************************************/
/*
 * handle a call coming in to the cache manager
 * - if I want to keep the call, I must increment its usage count
 * - the return value will be negated and passed back in an abort packet if non-zero
 * - serialised by virtue of there only being one krxiod
 */
static int afscm_new_call(struct rxrpc_call *call)
{
	_enter("%p{cid=%u u=%d}",call,ntohl(call->call_id),atomic_read(&call->usage));

	rxrpc_get_call(call);

	/* add to my current call list */
	spin_lock(&afscm_calls_lock);
	list_add(&call->app_link,&afscm_calls);
	spin_unlock(&afscm_calls_lock);

	_leave(" = 0");
	return 0;

} /* end afscm_new_call() */

/*****************************************************************************/
/*
 * queue on the kafscmd queue for attention
 */
static void afscm_attention(struct rxrpc_call *call)
{
	_enter("%p{cid=%u u=%d}",call,ntohl(call->call_id),atomic_read(&call->usage));

	spin_lock(&kafscmd_attention_lock);

	if (list_empty(&call->app_attn_link)) {
		list_add_tail(&call->app_attn_link,&kafscmd_attention_list);
		rxrpc_get_call(call);
	}

	spin_unlock(&kafscmd_attention_lock);

	wake_up(&kafscmd_sleepq);

	_leave(" {u=%d}",atomic_read(&call->usage));
} /* end afscm_attention() */

/*****************************************************************************/
/*
 * handle my call being aborted
 * - clean up, dequeue and put my ref to the call
 */
static void afscm_error(struct rxrpc_call *call)
{
	int removed;

	_enter("%p{est=%s ac=%u er=%d}",
	       call,
	       rxrpc_call_error_states[call->app_err_state],
	       call->app_abort_code,
	       call->app_errno);

	spin_lock(&kafscmd_attention_lock);

	if (list_empty(&call->app_attn_link)) {
		list_add_tail(&call->app_attn_link,&kafscmd_attention_list);
		rxrpc_get_call(call);
	}

	spin_unlock(&kafscmd_attention_lock);

	removed = 0;
	spin_lock(&afscm_calls_lock);
	if (!list_empty(&call->app_link)) {
		list_del_init(&call->app_link);
		removed = 1;
	}
	spin_unlock(&afscm_calls_lock);

	if (removed)
		rxrpc_put_call(call);

	wake_up(&kafscmd_sleepq);

	_leave("");
} /* end afscm_error() */

/*****************************************************************************/
/*
 * map afs abort codes to/from Linux error codes
 * - called with call->lock held
 */
static void afscm_aemap(struct rxrpc_call *call)
{
	switch (call->app_err_state) {
	case RXRPC_ESTATE_LOCAL_ABORT:
		call->app_abort_code = -call->app_errno;
		break;
	case RXRPC_ESTATE_PEER_ABORT:
		call->app_errno = -ECONNABORTED;
		break;
	default:
		break;
	}
} /* end afscm_aemap() */

/*****************************************************************************/
/*
 * start the cache manager service if not already started
 */
int afscm_start(void)
{
	int ret;

	down_write(&afscm_sem);
	if (!afscm_usage) {
		ret = kernel_thread(kafscmd,NULL,0);
		if (ret<0)
			goto out;

		wait_for_completion(&kafscmd_alive);

		ret = rxrpc_add_service(afs_transport,&AFSCM_service);
		if (ret<0)
			goto kill;
	}

	afscm_usage++;
	up_write(&afscm_sem);

	return 0;

 kill:
	kafscmd_die = 1;
	wake_up(&kafscmd_sleepq);
	wait_for_completion(&kafscmd_dead);

 out:
	up_write(&afscm_sem);
	return ret;

} /* end afscm_start() */

/*****************************************************************************/
/*
 * stop the cache manager service
 */
void afscm_stop(void)
{
	struct rxrpc_call *call;

	down_write(&afscm_sem);

	if (afscm_usage==0) BUG();
	afscm_usage--;

	if (afscm_usage==0) {
		/* don't want more incoming calls */
		rxrpc_del_service(afs_transport,&AFSCM_service);

		/* abort any calls I've still got open (the afscm_error() will dequeue them) */
		spin_lock(&afscm_calls_lock);
		while (!list_empty(&afscm_calls)) {
			call = list_entry(afscm_calls.next,struct rxrpc_call,app_link);
			list_del_init(&call->app_link);
			rxrpc_get_call(call);
			spin_unlock(&afscm_calls_lock);

			rxrpc_call_abort(call,-ESRCH); /* abort, dequeue and put */

			_debug("nuking active call %08x.%d",
			       ntohl(call->conn->conn_id),ntohl(call->call_id));
			rxrpc_put_call(call);
			rxrpc_put_call(call);

			spin_lock(&afscm_calls_lock);
		}
		spin_unlock(&afscm_calls_lock);

		/* get rid of my daemon */
		kafscmd_die = 1;
		wake_up(&kafscmd_sleepq);
		wait_for_completion(&kafscmd_dead);

		/* dispose of any calls waiting for attention */
		spin_lock(&kafscmd_attention_lock);
		while (!list_empty(&kafscmd_attention_list)) {
			call = list_entry(kafscmd_attention_list.next,
					  struct rxrpc_call,
					  app_attn_link);

			list_del_init(&call->app_attn_link);
			spin_unlock(&kafscmd_attention_lock);

			rxrpc_put_call(call);

			spin_lock(&kafscmd_attention_lock);
		}
		spin_unlock(&kafscmd_attention_lock);
	}

	up_write(&afscm_sem);

} /* end afscm_stop() */

/*****************************************************************************/
/*
 * handle the fileserver breaking a set of callbacks
 */
static void _SRXAFSCM_CallBack(struct rxrpc_call *call)
{
	afs_server_t *server;
	size_t count, qty, tmp;
	int ret = 0, removed;

	_enter("%p{acs=%s}",call,rxrpc_call_states[call->app_call_state]);

	server = afs_server_get_from_peer(call->conn->peer);

	switch (call->app_call_state) {
		/* we've received the last packet
		 * - drain all the data from the call and send the reply
		 */
	case RXRPC_CSTATE_SRVR_GOT_ARGS:
		ret = -EBADMSG;
		qty = call->app_ready_qty;
		if (qty<8 || qty>50*(6*4)+8)
			break;

		{
			afs_callback_t *cb, *pcb;
			int loop;
			u32 *fp, *bp;

			fp = rxrpc_call_alloc_scratch(call,qty);

			/* drag the entire argument block out to the scratch space */
			ret = rxrpc_call_read_data(call,fp,qty,0);
			if (ret<0)
				break;

			/* and unmarshall the parameter block */
			ret = -EBADMSG;
			count = ntohl(*fp++);
			if (count>AFSCBMAX ||
			    (count*(3*4)+8 != qty && count*(6*4)+8 != qty))
				break;

			bp = fp + count*3;
			tmp = ntohl(*bp++);
			if (tmp>0 && tmp!=count)
				break;
			if (tmp==0)
				bp = NULL;

			pcb = cb = rxrpc_call_alloc_scratch_s(call,afs_callback_t);

			for (loop=count-1; loop>=0; loop--) {
				pcb->fid.vid	= ntohl(*fp++);
				pcb->fid.vnode	= ntohl(*fp++);
				pcb->fid.unique	= ntohl(*fp++);
				if (bp) {
					pcb->version	= ntohl(*bp++);
					pcb->expiry	= ntohl(*bp++);
					pcb->type	= ntohl(*bp++);
				}
				else {
					pcb->version	= 0;
					pcb->expiry	= 0;
					pcb->type	= AFSCM_CB_UNTYPED;
				}
				pcb++;
			}

			/* invoke the actual service routine */
			ret = SRXAFSCM_CallBack(server,count,cb);
			if (ret<0)
				break;
		}

		/* send the reply */
		ret = rxrpc_call_write_data(call,0,NULL,RXRPC_LAST_PACKET,GFP_KERNEL,0,&count);
		if (ret<0)
			break;
		break;

		/* operation complete */
	case RXRPC_CSTATE_COMPLETE:
		call->app_user = NULL;
		removed = 0;
		spin_lock(&afscm_calls_lock);
		if (!list_empty(&call->app_link)) {
			list_del_init(&call->app_link);
			removed = 1;
		}
		spin_unlock(&afscm_calls_lock);

		if (removed)
			rxrpc_put_call(call);
		break;

		/* operation terminated on error */
	case RXRPC_CSTATE_ERROR:
		call->app_user = NULL;
		break;

	default:
		break;
	}

	if (ret<0)
		rxrpc_call_abort(call,ret);

	if (server) afs_put_server(server);

	_leave(" = %d",ret);

} /* end _SRXAFSCM_CallBack() */

/*****************************************************************************/
/*
 * handle the fileserver asking us to initialise our callback state
 */
static void _SRXAFSCM_InitCallBackState(struct rxrpc_call *call)
{
	afs_server_t *server;
	size_t count;
	int ret = 0, removed;

	_enter("%p{acs=%s}",call,rxrpc_call_states[call->app_call_state]);

	server = afs_server_get_from_peer(call->conn->peer);

	switch (call->app_call_state) {
		/* we've received the last packet - drain all the data from the call */
	case RXRPC_CSTATE_SRVR_GOT_ARGS:
		/* shouldn't be any args */
		ret = -EBADMSG;
		break;

		/* send the reply when asked for it */
	case RXRPC_CSTATE_SRVR_SND_REPLY:
		/* invoke the actual service routine */
		ret = SRXAFSCM_InitCallBackState(server);
		if (ret<0)
			break;

		ret = rxrpc_call_write_data(call,0,NULL,RXRPC_LAST_PACKET,GFP_KERNEL,0,&count);
		if (ret<0)
			break;
		break;

		/* operation complete */
	case RXRPC_CSTATE_COMPLETE:
		call->app_user = NULL;
		removed = 0;
		spin_lock(&afscm_calls_lock);
		if (!list_empty(&call->app_link)) {
			list_del_init(&call->app_link);
			removed = 1;
		}
		spin_unlock(&afscm_calls_lock);

		if (removed)
			rxrpc_put_call(call);
		break;

		/* operation terminated on error */
	case RXRPC_CSTATE_ERROR:
		call->app_user = NULL;
		break;

	default:
		break;
	}

	if (ret<0)
		rxrpc_call_abort(call,ret);

	if (server) afs_put_server(server);

	_leave(" = %d",ret);

} /* end _SRXAFSCM_InitCallBackState() */

/*****************************************************************************/
/*
 * handle a probe from a fileserver
 */
static void _SRXAFSCM_Probe(struct rxrpc_call *call)
{
	afs_server_t *server;
	size_t count;
	int ret = 0, removed;

	_enter("%p{acs=%s}",call,rxrpc_call_states[call->app_call_state]);

	server = afs_server_get_from_peer(call->conn->peer);

	switch (call->app_call_state) {
		/* we've received the last packet - drain all the data from the call */
	case RXRPC_CSTATE_SRVR_GOT_ARGS:
		/* shouldn't be any args */
		ret = -EBADMSG;
		break;

		/* send the reply when asked for it */
	case RXRPC_CSTATE_SRVR_SND_REPLY:
		/* invoke the actual service routine */
		ret = SRXAFSCM_Probe(server);
		if (ret<0)
			break;

		ret = rxrpc_call_write_data(call,0,NULL,RXRPC_LAST_PACKET,GFP_KERNEL,0,&count);
		if (ret<0)
			break;
		break;

		/* operation complete */
	case RXRPC_CSTATE_COMPLETE:
		call->app_user = NULL;
		removed = 0;
		spin_lock(&afscm_calls_lock);
		if (!list_empty(&call->app_link)) {
			list_del_init(&call->app_link);
			removed = 1;
		}
		spin_unlock(&afscm_calls_lock);

		if (removed)
			rxrpc_put_call(call);
		break;

		/* operation terminated on error */
	case RXRPC_CSTATE_ERROR:
		call->app_user = NULL;
		break;

	default:
		break;
	}

	if (ret<0)
		rxrpc_call_abort(call,ret);

	if (server) afs_put_server(server);

	_leave(" = %d",ret);

} /* end _SRXAFSCM_Probe() */
