/*
 * Author: 	Brian Wood (brian.j.wood@intel.com), Intel Corporation
 * Date: 		8/07-12/07
 * Description: This is a utility that can be used to register/unregister/check status 
 *			of device mapper RAID devices, see the manpage for further details.
 *
 * Copyright (C) 2003-2004 Sistina Software, Inc. All rights reserved.  
 * Copyright (C) 2004 Red Hat, Inc. All rights reserved.
 * Copyright (C) 2007,2009 Intel Corp. All rights reserved.
 *
 * Streamlining by Heinz Mauelshagen <heinzm@redhat.com>
 *
 * Portions of this code (and its underlying architectural idea's)
 * are borrowed from LVM2 and Device Mapper.
 *
 * This copyrighted material is made available to anyone wishing to use,
 * modify, copy, or redistribute it subject to the terms and conditions
 * of the GNU General Public License v.2.
 *
 * 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  02111-1307  USA
 */

/*
 * FIXME before releasing in RHEL5 (Heinz Mauelshagen):
 *
 *
 * Likely after 5.3:
 * o integrate code with dmraid package
 * o support metadata updates
 * o remove any limitations to SATA, because dmraid must be device agnostic;
 *   ie. the devices being registered with dmeventd have to be derived from
 *   libdmraid metadata discovery; this essentially means a rewrite!
 *
 * FIXED:
 * o symbols naming consistency
 * o white space / indenting
 * o removed bogus sysfs access code in favour of "dmraid -s" for the time
 *   being; maby share code with libdmraid-events later ?
 * o memory leaks
 * o remove code duplication
 * o programm exit codes
 * o cover error paths
 * o stdout/stderr in _usage()
 * o any (naming) limitations to Intel Matrix RAID
 * o replace memcpy by s[n]printf/strcpy/cat and check for memory leaks
 * o variable declaration consistency
 * o streamlined in general for better readability
 * o command line processing
 * o avoid displaying slave devices in _dm_all_monitored()
 * o most of the functions transferred to dmraid library
 */

#include <dirent.h>
#include <dlfcn.h>
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include <dmraid/dmreg.h>
#include "libdevmapper.h"
#include "libdevmapper-event.h"

#define ARRAY_SIZE(a)	(sizeof(a) / sizeof(*a))
#define ARRAY_END(a)	(a + ARRAY_SIZE(a))
       
#define DEFAULT_DMRAID_MIRROR_LIB "libdmraid-events.so"
#define DM_DSO_REG_TOOL_VERSION "1.0.0.rc3"
#define SYS_DM_LEN 256
#define MAJOR_MINOR 10
#define SYS_DM_PATH "/sys/block/dm-"
#define SYS_DM_DEV "/dev"
#define SYS_DM_SLAVES_DIR "/slaves"

/* Command line option counters for CLI processing. */
enum option_type { OPT_a, OPT_h, OPT_m, OPT_r, OPT_u, OPT_V, OPT_SUM, OPT_MAX };
static int optc[OPT_MAX];

/* Usage for dm_dso_reg_tool. */
static const char *options = "Vh?amru";
static void _usage(const char *cmd, FILE *file)
{
	fprintf(file,
		"Usage:\n"
		"%s -[%s]\n"
		"\n"
		"   -V      Show version of %s\n"
		"\n"
		"   -{h/?}  Show this help information\n"
		"\n"
		"   -m[r|u] List all currently active device mapper devices\n"
		"           and their current status with dmeventd\n"
		"           for registered (-r)/unregistered (-m) devices\n"
		"             Syntax: %s -m[u|r]\n"
		"\n"
		"   -a[r|u] Same as -m, but for devices with UUID only!\n"
		"             Syntax: %s -a[u|r]\n"
		"\n"
		"   -r      Register a device with dmeventd\n"
		"             Syntax: %s -r <device name> "
		"<path to DSO library>\n"
		"             Example: %s -r isw_abcdeh_Volume0"
		" libdmraid-events.so\n"
		"\n"
		"   -u      Unregister a device with dmeventd\n"
		"             Syntax: %s -u <device name> "
		"[<path to DSO library>]\n"
		"             Example: %s -u isw_abcdefgh_Volume0\n"
		"\n" , cmd, options, cmd, cmd, cmd, cmd, cmd, cmd, cmd);
}

void _test_user_id(void)
{
	if (geteuid()) {
		fprintf(stderr, "This utility needs to be run as root.\n");
		exit(1);
	}
}

/* Increment option counters. */
static _process_opt(int opt, const char *cmd)
{
	struct opt_def_struct {
		const char opt;		/* Option character. */
		enum option_type type;	/* The option type. */
		int root;		/* Flag to require root crdentials. */
	};
	static struct opt_def_struct optdefs[] = {
		{ 'V', OPT_V, 0 }, /* Display tool version. */
		{ 'm', OPT_m, 1 }, /* List (all) devices. */
		{ 'a', OPT_a, 1 }, /* List (all) devices with UUID only. */
		{ 'r', OPT_r, 1 }, /* Register a device. */
		{ 'u', OPT_u, 1 }, /* Unregister a device. */
		{ 'h', OPT_h, 0 }, /* Help. */
		{ '?', OPT_h, 0 }, /* Help. */
	};
	struct opt_def_struct *o;

	for (o = optdefs; o < ARRAY_END(optdefs); o++) {
		if (opt == o->opt) {
			if (o->root)
				_test_user_id();

			optc[o->type]++;
			optc[OPT_SUM]++;
			return;
		}
	}

	_usage(cmd, stderr);
	exit(1);
}

/*
 * Process command line options and do an initial argument check.
 * Covers help request and command line error.
 *
 * Return 1 for failure, 0 for success.
 */
static void _process_options(int argc, char **argv, const char *cmd)
{
	int err = 0, opt;

	memset(optc, 0, sizeof(optc));

	/* Walk command line options. */
	while ((opt = getopt(argc, argv, options)) != EOF)
		_process_opt(opt, cmd);

	/* No options specified -> request help. */
	if (!optc[OPT_SUM])
		optc[OPT_h]++;

	/* Help may be the only option. */
	if (optc[OPT_h]) {
		if (optc[OPT_SUM] > 1)
			err++;

	/* Only one of -r or -u. */
	} else if (optc[OPT_r] + optc[OPT_u] > 1)
		err++;

	/* Only one of -r or -u. */
	else if (optc[OPT_a] + optc[OPT_m] > 1)
		err++;

	/* With -{a|m}, no additional arguments, only {-r/-u}. */
	else if (optc[OPT_a] || optc[OPT_m]) {
		if (argc != 2)
			err++;

	/* With -r, we need a device name and a DSO path. */
	} else if (optc[OPT_r]) {
		if (argc != 4)
			err++;

	/* With -u, we need a device name and optionally a DSO path. */
	} else if (optc[OPT_u]) {
		if (argc < 3 || argc > 4)
			err++;
	}

	if (err || optc[OPT_h]) {
		_usage(cmd, err ? stderr : stdout);
		exit(!!err);
	}
}

/* main: Process command line options + arguments. */
int main(int argc, char **argv)
{
	int opt, ret = 0;
	char *cmd = basename(argv[0]);
	enum display_opt display_option;

	/* Process command line option (covers help and error). */
	_process_options(argc, argv, cmd);

	if (optc[OPT_a] || optc[OPT_m]) {
		display_option = ALL;
		if (!optc[OPT_r] && optc[OPT_u])
			display_option = UNREGISTERED;
		else if (optc[OPT_r] && !optc[OPT_u])
			if (optc[OPT_a])
				display_option = REGISTERED_WITH_UUID;
			else if (optc[OPT_m])
				display_option = REGISTERED_NO_UUID;
		dm_all_monitored(display_option);
	}
	else if (optc[OPT_r])
		ret = dm_register_device(argv[2], argv[3]);
	else if (optc[OPT_u])
		ret = dm_unregister_device(argv[2], argc > 3 ? argv[3] : NULL);
	else if (optc[OPT_V])
		printf("%s version: %s\n", cmd, DM_DSO_REG_TOOL_VERSION);

	return ret;
} /* End main. */
