/*
 * Get or set RPC debug flags.
 *
 * I would have loved to write this without recourse to the sysctl
 * interface, but the only plausible approach (reading and writing
 * /dev/kmem at the offsets indicated by the _debug symbols from
 * /proc/ksyms) didn't work, because /dev/kmem doesn't translate virtual
 * addresses on write. Unfortunately, modules are stuffed into memory
 * allocated via vmalloc.
 *
 * Copyright (C) 1996, 1997, Olaf Kirch <okir@monad.swb.de>
 *           (C) 2004 <frederic.jolly@bull.ext.net>
 *
 * 06/15/2004: updated for NFSv4
 *
 */

/* #include "config.h" */

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <unistd.h>
#include <string.h>
#include <malloc.h>
#include <fcntl.h>
#include <ctype.h>
#include <libgen.h>
/* RPC debug flags
   #include <sunrpc/debug.h> */
/* NFS debug flags
   #include <nfs_fs.h> */
/* NFSD and NLM debug flags
   #include <nfsd/debug.h> */
#include <nfs/debug.h>

static int		verbose = 0;
static char*            cdename;

static unsigned int	find_flag(char **module, char *name);
static unsigned int	get_flags(char *);
static unsigned int	set_flags(char *, unsigned int value);
static void		print_flags(FILE *, char *, unsigned int, int);
static char *		strtolower(char *str);
static void		usage(int excode, char *module);

int
main(int argc, char **argv)
{
	int		opt_s = 0,
			opt_c = 0;
	unsigned int	flags = 0, oflags;
	char *		module = NULL;
	int		c;

	cdename = malloc(strlen(basename(argv[0])));
	if (cdename == NULL) {
	  fprintf(stderr, "failed in malloc\n");
	  exit(1);
        }
	strcpy(cdename, basename(argv[0]));

	if (!strcmp(cdename, "nfsdebug")) {
	  module = "nfs";
	}
	else if (!strcmp(cdename, "nfsddebug")) {
	  module = "nfsd";
	}

	while ((c = getopt(argc, argv, "chm:sv")) != EOF) {
		switch (c) {
		case 'c':
			opt_c = 1;
			break;
		case 'h':
			usage(0, module); /* usage does not return */
			break;
		case 'm':
			module = optarg;
			break;
		case 's':
			opt_s = 1;
			break;
		case 'v':
			verbose++;
			break;
		default:
		        fprintf(stderr, "%s: unknown option -%c\n", cdename, optopt);
			usage(1, module);
		}
	}

	if (opt_c + opt_s > 1) {
		fprintf(stderr, "You can use at most one of -c and -s\n");
		usage(1, module);
	}

	if (!module) {
		fprintf(stderr, "%s: no module name specified.\n", cdename);
		usage(1, module);
	}

	if (strcmp(module, "nfsd") &&
	    strcmp(module, "nfs") &&
	    strcmp(module, "nlm") &&
	    strcmp(module, "rpc")) {
	        fprintf(stderr, "%s: unknown module: %s\n", cdename, module);
		usage(1, module);
	}

	if (argc == optind) {
		flags = ~(unsigned int) 0;
	} else {
		for (; optind < argc; optind++)
			flags |= find_flag(&module, argv[optind]);
		if (flags && !opt_c)
			opt_s = 1;
	}

	oflags = get_flags(module);

	if (opt_c) {
		oflags = set_flags(module, oflags & ~flags);
	} else if (opt_s) {
		oflags = set_flags(module, oflags | flags);
	}
	print_flags(stdout, module, oflags, 0);
	if (verbose) {
	        fprintf(stdout, "\nModule     Valid flags\n");
	        print_flags(stdout, module, ~(unsigned int) 0, 1);
	}

	return 0;
}

#define FLAG(mname, fname)	\
      { #mname, #fname, mname##DBG_##fname }

static struct flagmap {
	char *		module;
	char *		name;
	unsigned int	value;
}			flagmap[] = {
	/* rpc */
	FLAG(RPC,	XPRT),
	FLAG(RPC,	CALL),
	FLAG(RPC,	DEBUG),
	FLAG(RPC,	NFS),
	FLAG(RPC,	AUTH),
	FLAG(RPC,	BIND),
	FLAG(RPC,	SCHED),
	FLAG(RPC,	TRANS),
	FLAG(RPC,	SVCSOCK),
	FLAG(RPC,	SVCDSP),
	FLAG(RPC,	MISC),
	FLAG(RPC,	CACHE),
	FLAG(RPC,	ALL),

	/* nfs */
	FLAG(NFS,	VFS),
	FLAG(NFS,	DIRCACHE),
	FLAG(NFS,	LOOKUPCACHE),
	FLAG(NFS,	PAGECACHE),
	FLAG(NFS,	PROC),
	FLAG(NFS,       XDR),
	FLAG(NFS,       FILE),
	FLAG(NFS,       ROOT),
	FLAG(NFS,       CALLBACK),
	FLAG(NFS,       CLIENT),
	FLAG(NFS,       MOUNT),
	FLAG(NFS,       FSCACHE),
	FLAG(NFS,       PNFS),
	FLAG(NFS,       PNFS_LD),
	FLAG(NFS,	STATE),
	FLAG(NFS,	ALL),

	/* nfsd */
	FLAG(NFSD,	SOCK),
	FLAG(NFSD,	FH),
	FLAG(NFSD,	EXPORT),
	FLAG(NFSD,	SVC),
	FLAG(NFSD,	PROC),
	FLAG(NFSD,	FILEOP),
	FLAG(NFSD,	AUTH),
	FLAG(NFSD,	REPCACHE),
	FLAG(NFSD,	XDR),
	FLAG(NFSD,	LOCKD),
	FLAG(NFSD,	ALL),

	/* lockd */
	FLAG(NLM,	SVC),
	FLAG(NLM,	CLIENT),
	FLAG(NLM,	CLNTLOCK),
	FLAG(NLM,	SVCLOCK),
	FLAG(NLM,	MONITOR),
	FLAG(NLM,	CLNTSUBS),
	FLAG(NLM,	SVCSUBS),
	FLAG(NLM,       HOSTCACHE),
	FLAG(NLM,       XDR),
	FLAG(NLM,	ALL),

      { NULL,		NULL,		0 }
};

static unsigned int
find_flag(char **module, char *name)
{
	char		*mod = *module;
	unsigned int	value = 0;
	int		i;

	for (i = 0; flagmap[i].module; i++) {
		if ((mod && strcasecmp(mod, flagmap[i].module))
		 || strcasecmp(name, flagmap[i].name))
			continue;
		if (value) {
			fprintf(stderr,
				"%s: ambiguous symbol name %s.\n"
				"This name is used by more than one module, "
				"please specify the module name using\n"
				"the -m option.\n",
				cdename, name);
			exit(1);
		}
		value = flagmap[i].value;
		if (*module)
			return value;
		mod = flagmap[i].module;
	}

	if (!value) {
		if (*module)
			fprintf(stderr,
				"%s: unknown module or flag %s/%s\n",
				cdename, *module, name);
		else
			fprintf(stderr,
				"%s: unknown flag %s\n",
				cdename, name);
		exit(1);
	}

	*module = mod;
	return value;
}

static unsigned int
get_flags(char *module)
{
	char	buffer[256], filename[256];
	int	sysfd, len;

	snprintf(filename, 256, "/proc/sys/sunrpc/%s_debug", module);

	if ((sysfd = open(filename, O_RDONLY)) < 0) {
		perror(filename);
		exit(1);
	}
	if ((len = read(sysfd, buffer, sizeof(buffer))) <= 0) {
		perror("read");
		exit(1);
	}
	close(sysfd);
	buffer[len - 1] = '\0';

	return strtoul(buffer, NULL, 0);
}

static unsigned int
set_flags(char *module, unsigned int value)
{
	char	buffer[64], filename[256];
	int	sysfd, len, ret;

	snprintf(filename, 256, "/proc/sys/sunrpc/%s_debug", module);

	len = sprintf(buffer, "%d", value);
	if ((sysfd = open(filename, O_WRONLY)) < 0) {
		perror(filename);
		exit(1);
	}
	if ((ret = write(sysfd, buffer, len)) < 0) {
		perror("write");
		exit(1);
	}
	if (ret < len) {
		fprintf(stderr, "error: short write in set_flags!\n");
		exit(1);
	}
	close(sysfd);
	return value;
}


static char *
strtolower(char *str)
{
	static char	temp[64];
	char		*sp;

	strcpy(temp, str);
	for (sp = temp; *sp; sp++)
		*sp = tolower(*sp);
	return temp;
}

static void
print_flags(FILE *ofp, char *module, unsigned int flags, int show_all)
{
	char		*lastmod = NULL;
	unsigned int	shown = 0;
	int		i;

	if (module) {
		fprintf(ofp, "%-10s", strtolower(module));
		if (!flags) {
			fprintf(ofp, "<no flags set>\n");
			return;
		}
	}

	for (i = 0, shown = 0; flagmap[i].module; i++) {
		if (module) {
			if (strcasecmp(flagmap[i].module, module))
				continue;
		} else if (!lastmod || strcmp(lastmod, flagmap[i].module)) {
			if (lastmod) {
				fprintf(ofp, "\n");
				shown = 0;
			}
			fprintf(ofp, "%-10s", strtolower(flagmap[i].module));
			lastmod = flagmap[i].module;
		}
		if (!(flags & flagmap[i].value)
		 || (!show_all && (shown & flagmap[i].value))
		 || (module && !strcasecmp(flagmap[i].name, "all")))
			continue;
		fprintf(ofp, " %s", strtolower(flagmap[i].name));
		shown |= flagmap[i].value;
	}
	fprintf(ofp, "\n");
}

static void
usage(int excode, char *module)
{
        if (module)
	  fprintf(stderr, "usage: %s [-v] [-h] [-s flags...|-c flags...]\n", cdename);
	else
	  fprintf(stderr, "usage: %s [-v] [-h] [-m module] [-s flags...|-c flags...]\n", cdename);
	fprintf(stderr, "       set or cancel debug flags.\n");
	if (verbose) {
	  fprintf(stderr, "\nModule     Valid flags\n");
	  print_flags(stderr, module, ~(unsigned int) 0, 1);
	} else {
	  if (module)
	    fprintf(stderr, "       (use %s -vh to get a list of valid flags)\n", cdename);
	  else
	    fprintf(stderr, "       (use %s -vh to get a list of modules and valid flags)\n", cdename);
	}
	exit (excode);
}

