/* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: nil; -*-
 * vim:expandtab:shiftwidth=8:tabstop=8:
 *
 *  Copyright (C) 1999 Peter J. Braam <braam@clusterfs.com>
 *
 *   This file is part of InterMezzo, http://www.inter-mezzo.org.
 *
 *   InterMezzo is free software; you can redistribute it and/or
 *   modify it under the terms of version 2 of the GNU General Public
 *   License as published by the Free Software Foundation.
 *
 *   InterMezzo 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 InterMezzo; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *  Sysctrl entries for Intermezzo!
 */

#include <linux/config.h> /* for CONFIG_PROC_FS */
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/stat.h>
#include <linux/ctype.h>
#include <linux/init.h>
#include <asm/bitops.h>
#include <asm/segment.h>
#include <asm/uaccess.h>
#include <linux/utsname.h>
#include <linux/blk.h>

#include "intermezzo_fs.h"
#include "intermezzo_psdev.h"

/* /proc entries */

#ifdef CONFIG_PROC_FS
struct proc_dir_entry *proc_fs_intermezzo;
int intermezzo_mount_get_info( char * buffer, char ** start, off_t offset,
			       int length)
{
	int len=0;

	/* this works as long as we are below 1024 characters! */
	*start = buffer + offset;
	len -= offset;

	if ( len < 0 )
		return -EINVAL;

	return len;
}

#endif


/* SYSCTL below */

static struct ctl_table_header *intermezzo_table_header = NULL;
/* 0x100 to avoid any chance of collisions at any point in the tree with
 * non-directories
 */
#define PSDEV_INTERMEZZO  (0x100)

#define PSDEV_DEBUG	   1      /* control debugging */
#define PSDEV_TRACE	   2      /* control enter/leave pattern */
#define PSDEV_TIMEOUT      3      /* timeout on upcalls to become intrble */
#define PSDEV_HARD         4      /* mount type "hard" or "soft" */
#define PSDEV_NO_FILTER    5      /* controls presto_chk */
#define PSDEV_NO_JOURNAL   6      /* controls presto_chk */
#define PSDEV_NO_UPCALL    7      /* controls lento_upcall */
#define PSDEV_ERRORVAL     8      /* controls presto_debug_fail_blkdev */
#define PSDEV_EXCL_GID     9      /* which GID is ignored by presto */
#define PSDEV_BYTES_TO_CLOSE 11   /* bytes to write before close */

/* These are global presto control options */
#define PRESTO_PRIMARY_CTLCNT 2
static struct ctl_table presto_table[ PRESTO_PRIMARY_CTLCNT + MAX_CHANNEL + 1] =
{
	{PSDEV_DEBUG, "debug", &presto_debug, sizeof(int), 0644, NULL, &proc_dointvec},
	{PSDEV_TRACE, "trace", &presto_print_entry, sizeof(int), 0644, NULL, &proc_dointvec},
};

/*
 * Intalling the sysctl entries: strategy
 * - have templates for each /proc/sys/intermezzo/ entry
 *   such an entry exists for each /dev/presto
 *    (proto_channel_entry)
 * - have a template for the contents of such directories
 *    (proto_psdev_table)
 * - have the master table (presto_table)
 *
 * When installing, malloc, memcpy and fix up the pointers to point to
 * the appropriate constants in izo_channels[your_minor]
 */

static ctl_table proto_psdev_table[] = {
	{PSDEV_HARD, "hard", 0, sizeof(int), 0644, NULL, &proc_dointvec},
	{PSDEV_NO_FILTER, "no_filter", 0, sizeof(int), 0644, NULL, &proc_dointvec},
	{PSDEV_NO_JOURNAL, "no_journal", NULL, sizeof(int), 0644, NULL, &proc_dointvec},
	{PSDEV_NO_UPCALL, "no_upcall", NULL, sizeof(int), 0644, NULL, &proc_dointvec},
	{PSDEV_TIMEOUT, "timeout", NULL, sizeof(int), 0644, NULL, &proc_dointvec},
#ifdef PRESTO_DEBUG
	{PSDEV_ERRORVAL, "errorval", NULL, sizeof(int), 0644, NULL, &proc_dointvec},
#endif
	{ 0 }
};

static ctl_table proto_channel_entry = {
	PSDEV_INTERMEZZO, 0,  NULL, 0, 0555, 0,
};

static ctl_table intermezzo_table[2] = {
	{PSDEV_INTERMEZZO, "intermezzo",    NULL, 0, 0555, presto_table},
	{0}
};

/* support for external setting and getting of opts. */
/* particularly via ioctl. The Right way to do this is via sysctl,
 * but that will have to wait until intermezzo gets its own nice set of
 * sysctl IDs
 */
/* we made these separate as setting may in future be more restricted
 * than getting
 */
#ifdef RON_MINNICH
int dosetopt(int minor, struct psdev_opt *opt)
{
	int retval = 0;
	int newval = opt->optval;

	ENTRY;

	switch(opt->optname) {

	case PSDEV_TIMEOUT:
		izo_channels[minor].uc_timeout = newval;
		break;

	case PSDEV_HARD:
		izo_channels[minor].uc_hard = newval;
		break;

	case PSDEV_NO_FILTER:
		izo_channels[minor].uc_no_filter = newval;
		break;

	case PSDEV_NO_JOURNAL:
		izo_channels[minor].uc_no_journal = newval;
		break;

	case PSDEV_NO_UPCALL:
		izo_channels[minor].uc_no_upcall = newval;
		break;

#ifdef PRESTO_DEBUG
	case PSDEV_ERRORVAL: {
		/* If we have a positive arg, set a breakpoint for that
		 * value.  If we have a negative arg, make that device
		 * read-only.  FIXME  It would be much better to only
		 * allow setting the underlying device read-only for the
		 * current presto cache.
		 */
		int errorval = izo_channels[minor].uc_errorval;
		if (errorval < 0) {
			if (newval == 0)
				set_device_ro(-errorval, 0);
			else
				CERROR("device %s already read only\n",
				       kdevname(-errorval));
		} else {
			if (newval < 0)
				set_device_ro(-newval, 1);
			izo_channels[minor].uc_errorval = newval;
			CDEBUG(D_PSDEV, "setting errorval to %d\n", newval);
		}

		break;
	}
#endif

	case PSDEV_TRACE:
	case PSDEV_DEBUG:
	case PSDEV_BYTES_TO_CLOSE:
	default:
		CDEBUG(D_PSDEV,
		       "ioctl: dosetopt: minor %d, bad optname 0x%x, \n",
		       minor, opt->optname);

		retval = -EINVAL;
	}

	EXIT;
	return retval;
}

int dogetopt(int minor, struct psdev_opt *opt)
{
	int retval = 0;

	ENTRY;

	switch(opt->optname) {

	case PSDEV_TIMEOUT:
		opt->optval = izo_channels[minor].uc_timeout;
		break;

	case PSDEV_HARD:
		opt->optval = izo_channels[minor].uc_hard;
		break;

	case PSDEV_NO_FILTER:
		opt->optval = izo_channels[minor].uc_no_filter;
		break;

	case PSDEV_NO_JOURNAL:
		opt->optval = izo_channels[minor].uc_no_journal;
		break;

	case PSDEV_NO_UPCALL:
		opt->optval = izo_channels[minor].uc_no_upcall;
		break;

#ifdef PSDEV_DEBUG
	case PSDEV_ERRORVAL: {
		int errorval = izo_channels[minor].uc_errorval;
		if (errorval < 0 && is_read_only(-errorval))
			CERROR("device %s has been set read-only\n",
			       kdevname(-errorval));
		opt->optval = izo_channels[minor].uc_errorval;
		break;
	}
#endif

	case PSDEV_TRACE:
	case PSDEV_DEBUG:
	case PSDEV_BYTES_TO_CLOSE:
	default:
		CDEBUG(D_PSDEV,
		       "ioctl: dogetopt: minor %d, bad optval 0x%x, \n",
		       minor, opt->optname);

		retval = -EINVAL;
	}

	EXIT;
	return retval;
}
#endif


/* allocate the tables for the presto devices. We need
 * sizeof(proto_channel_table)/sizeof(proto_channel_table[0])
 * entries for each dev
 */
int /* __init */ init_intermezzo_sysctl(void)
{
	int i;
	int total_dev = MAX_CHANNEL;
	int entries_per_dev = sizeof(proto_psdev_table) /
		sizeof(proto_psdev_table[0]);
	int total_entries = entries_per_dev * total_dev;
	ctl_table *dev_ctl_table;

	PRESTO_ALLOC(dev_ctl_table, sizeof(ctl_table) * total_entries);

	if (! dev_ctl_table) {
		CERROR("WARNING: presto couldn't allocate dev_ctl_table\n");
		EXIT;
		return -ENOMEM;
	}

	/* now fill in the entries ... we put the individual presto<x>
	 * entries at the end of the table, and the per-presto stuff
	 * starting at the front.  We assume that the compiler makes
	 * this code more efficient, but really, who cares ... it
	 * happens once per reboot.
	 */
	for(i = 0; i < total_dev; i++) {
		/* entry for this /proc/sys/intermezzo/intermezzo"i" */
		ctl_table *psdev = &presto_table[i + PRESTO_PRIMARY_CTLCNT];
		/* entries for the individual "files" in this "directory" */
		ctl_table *psdev_entries = &dev_ctl_table[i * entries_per_dev];
		/* init the psdev and psdev_entries with the prototypes */
		*psdev = proto_channel_entry;
		memcpy(psdev_entries, proto_psdev_table,
		       sizeof(proto_psdev_table));
		/* now specialize them ... */
		/* the psdev has to point to psdev_entries, and fix the number */
		psdev->ctl_name = psdev->ctl_name + i + 1; /* sorry */

		PRESTO_ALLOC((void*)psdev->procname, PROCNAME_SIZE);
		if (!psdev->procname) {
			PRESTO_FREE(dev_ctl_table,
				    sizeof(ctl_table) * total_entries);
			return -ENOMEM;
		}
		sprintf((char *) psdev->procname, "intermezzo%d", i);
		/* hook presto into */
		psdev->child = psdev_entries;

		/* now for each psdev entry ... */
		psdev_entries[0].data = &(izo_channels[i].uc_hard);
		psdev_entries[1].data = &(izo_channels[i].uc_no_filter);
		psdev_entries[2].data = &(izo_channels[i].uc_no_journal);
		psdev_entries[3].data = &(izo_channels[i].uc_no_upcall);
		psdev_entries[4].data = &(izo_channels[i].uc_timeout);
#ifdef PRESTO_DEBUG
		psdev_entries[5].data = &(izo_channels[i].uc_errorval);
#endif
	}


#ifdef CONFIG_SYSCTL
	if ( !intermezzo_table_header )
		intermezzo_table_header =
			register_sysctl_table(intermezzo_table, 0);
#endif
#ifdef CONFIG_PROC_FS
	proc_fs_intermezzo = proc_mkdir("intermezzo", proc_root_fs);
	proc_fs_intermezzo->owner = THIS_MODULE;
	create_proc_info_entry("mounts", 0, proc_fs_intermezzo, 
			       intermezzo_mount_get_info);
#endif
	return 0;
}

void cleanup_intermezzo_sysctl(void)
{
	int total_dev = MAX_CHANNEL;
	int entries_per_dev = sizeof(proto_psdev_table) /
		sizeof(proto_psdev_table[0]);
	int total_entries = entries_per_dev * total_dev;
	int i;

#ifdef CONFIG_SYSCTL
	if ( intermezzo_table_header )
		unregister_sysctl_table(intermezzo_table_header);
	intermezzo_table_header = NULL;
#endif
	for(i = 0; i < total_dev; i++) {
		/* entry for this /proc/sys/intermezzo/intermezzo"i" */
		ctl_table *psdev = &presto_table[i + PRESTO_PRIMARY_CTLCNT];
		PRESTO_FREE(psdev->procname, PROCNAME_SIZE);
	}
	/* presto_table[PRESTO_PRIMARY_CTLCNT].child points to the
	 * dev_ctl_table previously allocated in init_intermezzo_psdev()
	 */
	PRESTO_FREE(presto_table[PRESTO_PRIMARY_CTLCNT].child, sizeof(ctl_table) * total_entries);

#if CONFIG_PROC_FS
	remove_proc_entry("mounts", proc_fs_intermezzo);
	remove_proc_entry("intermezzo", proc_root_fs);
#endif
}

