/* kafsasyncd.c: AFS asynchronous operation daemon
 *
 * 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.
 *
 *
 * The AFS async daemon is used to the following:
 * - probe "dead" servers to see whether they've come back to life yet.
 * - probe "live" servers that we haven't talked to for a while to see if they are better
 *   candidates for serving than what we're currently using
 * - poll volume location servers to keep up to date volume location lists
 */

#include <linux/module.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/completion.h>
#include "cell.h"
#include "server.h"
#include "volume.h"
#include "kafsasyncd.h"
#include "kafstimod.h"
#include <rxrpc/call.h>
#include <asm/errno.h>
#include "internal.h"

static DECLARE_COMPLETION(kafsasyncd_alive);
static DECLARE_COMPLETION(kafsasyncd_dead);
static DECLARE_WAIT_QUEUE_HEAD(kafsasyncd_sleepq);
static struct task_struct *kafsasyncd_task;
static int kafsasyncd_die;

static int kafsasyncd(void *arg);

static LIST_HEAD(kafsasyncd_async_attnq);
static LIST_HEAD(kafsasyncd_async_busyq);
static DEFINE_SPINLOCK(kafsasyncd_async_lock);

static void kafsasyncd_null_call_attn_func(struct rxrpc_call *call)
{
}

static void kafsasyncd_null_call_error_func(struct rxrpc_call *call)
{
}

/*****************************************************************************/
/*
 * start the async daemon
 */
int afs_kafsasyncd_start(void)
{
	int ret;

	ret = kernel_thread(kafsasyncd, NULL, 0);
	if (ret < 0)
		return ret;

	wait_for_completion(&kafsasyncd_alive);

	return ret;
} /* end afs_kafsasyncd_start() */

/*****************************************************************************/
/*
 * stop the async daemon
 */
void afs_kafsasyncd_stop(void)
{
	/* get rid of my daemon */
	kafsasyncd_die = 1;
	wake_up(&kafsasyncd_sleepq);
	wait_for_completion(&kafsasyncd_dead);

} /* end afs_kafsasyncd_stop() */

/*****************************************************************************/
/*
 * probing daemon
 */
static int kafsasyncd(void *arg)
{
	struct afs_async_op *op;
	int die;

	DECLARE_WAITQUEUE(myself, current);

	kafsasyncd_task = current;

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

	daemonize("kafsasyncd");

	complete(&kafsasyncd_alive);

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

		for (;;) {
			if (!list_empty(&kafsasyncd_async_attnq) ||
			    signal_pending(current) ||
			    kafsasyncd_die)
				break;

			schedule();
			set_current_state(TASK_INTERRUPTIBLE);
		}

		remove_wait_queue(&kafsasyncd_sleepq, &myself);
		set_current_state(TASK_RUNNING);

		try_to_freeze();

		/* discard pending signals */
		afs_discard_my_signals();

		die = kafsasyncd_die;

		/* deal with the next asynchronous operation requiring
		 * attention */
		if (!list_empty(&kafsasyncd_async_attnq)) {
			struct afs_async_op *op;

			_debug("@@@ Begin Asynchronous Operation");

			op = NULL;
			spin_lock(&kafsasyncd_async_lock);

			if (!list_empty(&kafsasyncd_async_attnq)) {
				op = list_entry(kafsasyncd_async_attnq.next,
						struct afs_async_op, link);
				list_move_tail(&op->link,
					      &kafsasyncd_async_busyq);
			}

			spin_unlock(&kafsasyncd_async_lock);

			_debug("@@@ Operation %p {%p}\n",
			       op, op ? op->ops : NULL);

			if (op)
				op->ops->attend(op);

			_debug("@@@ End Asynchronous Operation");
		}

	} while(!die);

	/* need to kill all outstanding asynchronous operations before
	 * exiting */
	kafsasyncd_task = NULL;
	spin_lock(&kafsasyncd_async_lock);

	/* fold the busy and attention queues together */
	list_splice_init(&kafsasyncd_async_busyq,
			 &kafsasyncd_async_attnq);

	/* dequeue kafsasyncd from all their wait queues */
	list_for_each_entry(op, &kafsasyncd_async_attnq, link) {
		op->call->app_attn_func = kafsasyncd_null_call_attn_func;
		op->call->app_error_func = kafsasyncd_null_call_error_func;
		remove_wait_queue(&op->call->waitq, &op->waiter);
	}

	spin_unlock(&kafsasyncd_async_lock);

	/* abort all the operations */
	while (!list_empty(&kafsasyncd_async_attnq)) {
		op = list_entry(kafsasyncd_async_attnq.next, struct afs_async_op, link);
		list_del_init(&op->link);

		rxrpc_call_abort(op->call, -EIO);
		rxrpc_put_call(op->call);
		op->call = NULL;

		op->ops->discard(op);
	}

	/* and that's all */
	_leave("");
	complete_and_exit(&kafsasyncd_dead, 0);

} /* end kafsasyncd() */

/*****************************************************************************/
/*
 * begin an operation
 * - place operation on busy queue
 */
void afs_kafsasyncd_begin_op(struct afs_async_op *op)
{
	_enter("");

	spin_lock(&kafsasyncd_async_lock);

	init_waitqueue_entry(&op->waiter, kafsasyncd_task);
	add_wait_queue(&op->call->waitq, &op->waiter);

	list_move_tail(&op->link, &kafsasyncd_async_busyq);

	spin_unlock(&kafsasyncd_async_lock);

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

/*****************************************************************************/
/*
 * request attention for an operation
 * - move to attention queue
 */
void afs_kafsasyncd_attend_op(struct afs_async_op *op)
{
	_enter("");

	spin_lock(&kafsasyncd_async_lock);

	list_move_tail(&op->link, &kafsasyncd_async_attnq);

	spin_unlock(&kafsasyncd_async_lock);

	wake_up(&kafsasyncd_sleepq);

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

/*****************************************************************************/
/*
 * terminate an operation
 * - remove from either queue
 */
void afs_kafsasyncd_terminate_op(struct afs_async_op *op)
{
	_enter("");

	spin_lock(&kafsasyncd_async_lock);

	if (!list_empty(&op->link)) {
		list_del_init(&op->link);
		remove_wait_queue(&op->call->waitq, &op->waiter);
	}

	spin_unlock(&kafsasyncd_async_lock);

	wake_up(&kafsasyncd_sleepq);

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