/******************************************************************************
*******************************************************************************
**
**  Copyright (C) Sistina Software, Inc.  1997-2003  All rights reserved.
**  Copyright (C) 2004 Red Hat, Inc.  All rights reserved.
**
**  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.
**
*******************************************************************************
******************************************************************************/

#include <inttypes.h>
#include <unistd.h>
#include <signal.h>
#include "copyright.cf"
#include "cnxman-socket.h"
#include "cman_tool.h"

#define OPTION_STRING		("m:n:v:e:2p:c:r:i:N:t:XVwqh?d")
#define OP_JOIN			1
#define OP_LEAVE		2
#define OP_EXPECTED		3
#define OP_VOTES		4
#define OP_KILL			5
#define OP_VERSION		6
#define OP_WAIT			7
#define OP_STATUS		8
#define OP_NODES		9
#define OP_SERVICES		10


static void print_usage(void)
{
	printf("Usage:\n");
	printf("\n");
	printf("%s <join|leave|kill|expected|votes|version|wait|status|nodes|services> [options]\n",
	       prog_name);
	printf("\n");
	printf("Options:\n");
	printf("  -h               Print this help, then exit\n");
	printf("  -V               Print program version information, then exit\n");
	printf("  -d               Enable debug output\n");
	printf("\n");

	printf("join\n");
	printf("  -m <addr>      * Multicast address to use (combines with -i)\n");
	printf("  -i <ifname>    * Interfaces for above multicast addresses\n");
	printf("  -v <votes>       Number of votes this node has (default 1)\n");
	printf("  -e <votes>       Number of expected votes for the cluster (no default)\n");
	printf("  -c <clustername> Name of the cluster to join\n");
	printf("  -2               This is a two node cluster (-e must be 1)\n");
	printf("  -p <port>        UDP port number for cman communications (default %d)\n", DEFAULT_PORT);
	printf("  -n <nodename>  * The name of this node (defaults to hostname)\n");
	printf("  -N <id>          Node id (defaults to automatic)\n");
	printf("  -X               Do not use cluster.conf values from CCS\n");
	printf("  -w               Wait until node has joined a cluster\n");
	printf("  -q               Wait until the cluster is quorate\n");
	printf("  -t               Maximum time (in seconds) to wait\n");
	printf("  options with marked * can be specified multiple times for multi-path systems\n");

	printf("\n");
	printf("wait               Wait until the node is a member of a cluster\n");
	printf("  -q               Wait until the cluster is quorate\n");
	printf("  -t               Maximum time (in seconds) to wait\n");
	printf("\n");

	printf("leave\n");
	printf("  -w               If cluster is in transition, wait and keep trying\n");
	printf("  -t               Maximum time (in seconds) to wait\n");
	printf("  remove           Tell other nodes to ajust quorum downwards if necessary\n");
	printf("  force            Leave even if cluster subsystems are active\n");

	printf("\n");
	printf("kill\n");
	printf("  -n <nodename>    The name of the node to kill (can specify multiple times)\n");

	printf("\n");
	printf("expected\n");
	printf("  -e <votes>       New number of expected votes for the cluster\n");

	printf("\n");
	printf("votes\n");
	printf("  -v <votes>       New number of votes for this node\n");

	printf("\n");
	printf("status             Show local record of cluster status\n");
	printf("\n");
	printf("nodes              Show local record of cluster nodes\n");
	printf("\n");
	printf("services           Show local record of cluster services\n");

	printf("\n");
	printf("version\n");
	printf("  -r <config>      A new config version to set on all members\n");
	printf("\n");
}

static void sigalarm_handler(int sig)
{
	fprintf(stderr, "Timed-out waiting for cluster\n");
	exit(2);
}

static void show_file(char *name)
{
	FILE *file;
	char line[256];

	file = fopen(name, "r");
	if (!file)
		die("can't open %s, cman not running", name);

	while (fgets(line, 256, file))
		printf("%s", line);

	fclose(file);
}

static void show_status(void)
{
	show_file("/proc/cluster/status");
}

static void show_nodes(void)
{
	show_file("/proc/cluster/nodes");
}

static void show_services(void)
{
	show_file("/proc/cluster/services");
}

static int open_cluster_socket()
{
	int cluster_sock;

	cluster_sock = socket(AF_CLUSTER, SOCK_DGRAM, CLPROTO_MASTER);
	if (cluster_sock == -1)
		die("can't open cluster socket, cman kernel module probably not loaded");

	return cluster_sock;
}

char *cman_error(int err)
{
	char *die_error;

	switch (errno) {
	case ENOTCONN:
		die_error = "Cluster software not started";
		break;
	case ENOENT:
		die_error = "Node is not yet a cluster member";
		break;
	case EBUSY:
		die_error = "Cluster is in transition, try later or use -w";
		break;
	default:
		die_error = strerror(errno);
		break;
	}
	return die_error;
}

static void leave(commandline_t *comline)
{
	int cluster_sock;
	int result;
	int flags = CLUSTER_LEAVEFLAG_DOWN;

	cluster_sock = open_cluster_socket();

	/* "cman_tool leave remove" adjusts quorum downward */

	if (comline->remove)
		flags |= CLUSTER_LEAVEFLAG_REMOVED;

	/* If the join count is != 1 then there are other things using
	   the cluster and we need to be forced */

	if ((result = ioctl(cluster_sock, SIOCCLUSTER_GET_JOINCOUNT, 0)) != 0) {
		if (result < 0)
			die("error getting join count: %s", cman_error(errno));

		if (!comline->force) {
	    		die("Can't leave cluster while there are %d active subsystems\n", result);
		}
		flags |= CLUSTER_LEAVEFLAG_FORCE;
	}

	/* Unlikely this will be needed, but no point in leaving it out */
	if (comline->wait_opt && comline->timeout) {
		signal(SIGALRM, sigalarm_handler);
		alarm(comline->timeout);
	}

	do {
		result = ioctl(cluster_sock, SIOCCLUSTER_LEAVE_CLUSTER, flags);
		if (result < 0 && errno == EBUSY && comline->wait_opt)
			sleep(1);

	} while (result < 0 && errno == EBUSY && comline->wait_opt);

	if (result) {
		die("Error leaving cluster: %s", cman_error(errno));
	}

	close(cluster_sock);
}

static void set_expected(commandline_t *comline)
{
	int cluster_sock;
	int result;

	cluster_sock = open_cluster_socket();

	if ((result = ioctl(cluster_sock, SIOCCLUSTER_SETEXPECTED_VOTES,
			    comline->expected_votes)))
		die("can't set expected votes: %s", cman_error(errno));

	close(cluster_sock);
}

static void set_votes(commandline_t *comline)
{
	int cluster_sock;
	int result;

	cluster_sock = open_cluster_socket();

	if ((result = ioctl(cluster_sock, SIOCCLUSTER_SET_VOTES,
			    comline->votes)))
		die("can't set votes: %s", cman_error(errno));

	close(cluster_sock);
}

static void version(commandline_t *comline)
{
	struct cl_version ver;
	int cluster_sock;
	int result;

	cluster_sock = open_cluster_socket();

	if ((result = ioctl(cluster_sock, SIOCCLUSTER_GET_VERSION, &ver)))
		die("can't get version: %s", cman_error(errno));

	if (!comline->config_version) {
		printf("%d.%d.%d config %d\n", ver.major, ver.minor, ver.patch,
		       ver.config);
		goto out;
	}

	ver.config = comline->config_version;

	if ((result = ioctl(cluster_sock, SIOCCLUSTER_SET_VERSION, &ver)))
		die("can't set version: %s", cman_error(errno));
 out:
	close(cluster_sock);
}

static int wait_for_sock(int sock)
{
	int recvbuf[256]; /* Plenty big enough for an OOB message */
	int ret;

	ret = recv(sock, recvbuf, sizeof(recvbuf), MSG_OOB);
	if (ret < 0)
	{
		return errno;
	}

	/* EOF also means we are no longer connected to the cluster */
	if (ret == 0)
		return ENOTCONN;

	return 0;
}

static int cluster_wait(commandline_t *comline)
{
    int cluster_sock;
    int ret = 0;

    cluster_sock = socket(AF_CLUSTER, SOCK_DGRAM, CLPROTO_CLIENT);
    if (cluster_sock == -1)
	    die("can't open cluster socket, cman kernel module probably not loaded");

    if (comline->wait_quorate_opt) {
	    while (ioctl(cluster_sock, SIOCCLUSTER_ISQUORATE, 0) <= 0) {
		    if ((ret = wait_for_sock(cluster_sock)))
			    goto end_wait;
	    }
    }
    else {
	    while (ioctl(cluster_sock, SIOCCLUSTER_GETMEMBERS, 0) < 0) {
		    if ((ret = wait_for_sock(cluster_sock)))
			    goto end_wait;
	    }
    }

 end_wait:
    close(cluster_sock);

    return ret;
}

static void kill_node(commandline_t *comline)
{
	int cluster_sock;
	int i;
	struct cl_cluster_node node;

	if (!comline->num_nodenames) {
	    die("No node name specified\n");
	}

	cluster_sock = open_cluster_socket();

	for (i=0; i<comline->num_nodenames; i++) {

	    /* Resolve node name into a number */
	    node.node_id = 0;
	    strcpy(node.name, comline->nodenames[i]);
	    if (ioctl(cluster_sock, SIOCCLUSTER_GETNODE, &node)) {
		fprintf(stderr, "Can't kill node %s : %s\n", node.name, strerror(errno));
		continue;
	    }


	    if (ioctl(cluster_sock, SIOCCLUSTER_KILLNODE, node.node_id))
		perror("kill node failed");
	}

	close(cluster_sock);
}


static int get_int_arg(char argopt, char *arg)
{
	char *tmp;
	int val;

	val = strtol(arg, &tmp, 10);
	if (tmp == arg || tmp != arg + strlen(arg))
		die("argument to %c (%s) is not an integer", argopt, arg);

	if (val < 0)
		die("argument to %c cannot be negative", argopt);

	return val;
}


static void decode_arguments(int argc, char *argv[], commandline_t *comline)
{
	int cont = TRUE;
	int optchar, i;

	while (cont) {
		optchar = getopt(argc, argv, OPTION_STRING);

		switch (optchar) {

		case 'i':
			i = comline->num_interfaces;
			if (i >= MAX_INTERFACES)
				die("maximum of %d interfaces allowed",
				    MAX_INTERFACES);
			comline->interfaces[i] = strdup(optarg);
			if (!comline->interfaces[i])
				die("no memory");
			comline->num_interfaces++;
			break;

		case 'm':
		        i = comline->num_multicasts;
			if (i >= MAX_INTERFACES)
			        die("maximum of %d multicast addresses allowed",
				    MAX_INTERFACES);
			if (strlen(optarg) > MAX_MCAST_NAME_LEN)
				die("maximum multicast name length is %d",
				    MAX_MCAST_NAME_LEN);
			comline->multicast_names[i] = strdup(optarg);
			comline->num_multicasts++;
			break;

		case 'n':
		        i = comline->num_nodenames;
			if (i >= MAX_INTERFACES)
			        die("maximum of %d node names allowed",
				    MAX_INTERFACES);
			if (strlen(optarg) > MAX_NODE_NAME_LEN)
				die("maximum node name length is %d",
				    MAX_NODE_NAME_LEN);
			comline->nodenames[i] = strdup(optarg);
			comline->num_nodenames++;
			break;

		case 'r':
			comline->config_version = get_int_arg(optchar, optarg);
			comline->config_version_opt = TRUE;
			break;

		case 'v':
			comline->votes = get_int_arg(optchar, optarg);
			comline->votes_opt = TRUE;
			break;

		case 'e':
			comline->expected_votes = get_int_arg(optchar, optarg);
			comline->expected_votes_opt = TRUE;
			break;

		case '2':
			comline->two_node = TRUE;
			break;

		case 'p':
			comline->port = get_int_arg(optchar, optarg);
			comline->port_opt = TRUE;
			break;

		case 'N':
			comline->nodeid = get_int_arg(optchar, optarg);
			comline->nodeid_opt = TRUE;
			break;

		case 'c':
			if (strlen(optarg) > MAX_NODE_NAME_LEN)
				die("maximum cluster name length is %d",
				    MAX_CLUSTER_NAME_LEN);
			strcpy(comline->clustername, optarg);
			comline->clustername_opt = TRUE;
			break;

		case 'X':
			comline->no_ccs = TRUE;
			break;

		case 'V':
			printf("cman_tool %s (built %s %s)\n",
				CMAN_RELEASE_NAME, __DATE__, __TIME__);
			printf("%s\n", REDHAT_COPYRIGHT);
			exit(EXIT_SUCCESS);
			break;

		case 'h':
			print_usage();
			exit(EXIT_SUCCESS);
			break;

		case ':':
		case '?':
			fprintf(stderr, "Please use '-h' for usage.\n");
			exit(EXIT_FAILURE);
			break;

		case 'd':
		        comline->verbose++;
			break;

		case 'w':
			comline->wait_opt = TRUE;
			break;

		case 'q':
			comline->wait_quorate_opt = TRUE;
			break;

		case 't':
			comline->timeout = get_int_arg(optchar, optarg);
			break;

		case EOF:
			cont = FALSE;
			break;

		default:
			die("unknown option: %c", optchar);
			break;
		};
	}

	while (optind < argc) {
		if (strcmp(argv[optind], "join") == 0) {
			if (comline->operation)
				die("can't specify two operations");
			comline->operation = OP_JOIN;
		} else if (strcmp(argv[optind], "leave") == 0) {
			if (comline->operation)
				die("can't specify two operations");
			comline->operation = OP_LEAVE;
		} else if (strcmp(argv[optind], "expected") == 0) {
			if (comline->operation)
				die("can't specify two operations");
			comline->operation = OP_EXPECTED;
		} else if (strcmp(argv[optind], "votes") == 0) {
			if (comline->operation)
				die("can't specify two operations");
			comline->operation = OP_VOTES;
		} else if (strcmp(argv[optind], "kill") == 0) {
			if (comline->operation)
				die("can't specify two operations");
			comline->operation = OP_KILL;
		} else if (strcmp(argv[optind], "version") == 0) {
			if (comline->operation)
				die("can't specify two operations");
			comline->operation = OP_VERSION;
		} else if (strcmp(argv[optind], "wait") == 0) {
			if (comline->operation)
				die("can't specify two operations");
			comline->operation = OP_WAIT;
		} else if (strcmp(argv[optind], "status") == 0) {
			if (comline->operation)
				die("can't specify two operations");
			comline->operation = OP_STATUS;
		} else if (strcmp(argv[optind], "nodes") == 0) {
			if (comline->operation)
				die("can't specify two operations");
			comline->operation = OP_NODES;
		} else if (strcmp(argv[optind], "services") == 0) {
			if (comline->operation)
				die("can't specify two operations");
			comline->operation = OP_SERVICES;
		} else if (strcmp(argv[optind], "remove") == 0) {
			comline->remove = TRUE;
		} else if (strcmp(argv[optind], "force") == 0) {
			comline->force = TRUE;
		} else
			die("unknown option %s", argv[optind]);

		optind++;
	}

	if (!comline->operation)
		die("no operation specified");
}

static void check_arguments(commandline_t *comline)
{
	int error;

	if (!comline->expected_votes)
	        die("expected votes not set");

	if (!comline->clustername[0])
		die("cluster name not set");

	if (!comline->votes)
		comline->votes = DEFAULT_VOTES;

	if (!comline->port)
		comline->port = DEFAULT_PORT;

	if (comline->two_node && comline->expected_votes != 1)
		die("expected_votes value (%d) invalid in two node mode",
		    comline->expected_votes);

	if (!comline->nodenames[0]) {
		struct utsname utsname;
		error = uname(&utsname);
		if (error)
			die("cannot get node name, uname failed");

		comline->nodenames[0] = strdup(utsname.nodename);
		comline->num_nodenames++;
	}

	if (!comline->num_interfaces) {
	        comline->interfaces[0] = strdup("eth0");
		if (!comline->interfaces[0])
			die("no memory");
	}

	if (comline->num_multicasts != comline->num_interfaces) {
	        die("Number of multicast addresses (%d) must match number of "
		    "interfaces (%d)", comline->num_multicasts,
		    comline->num_interfaces);
	}

	if (comline->num_nodenames && comline->num_multicasts &&
	    comline->num_nodenames != comline->num_multicasts) {
	        die("Number of node names (%d) must match number of multicast "
		    "addresses (%d)", comline->num_nodenames,
		    comline->num_multicasts);
	}

	if (comline->port <= 0 || comline->port > 65535)
		die("Port must be a number between 1 and 65535");

	/* This message looks like it contradicts the condition but
	   a nodeid of zero simply means "assign one for me" and is a
	   perfectly reasonable override */
	if (comline->nodeid < 0 || comline->nodeid > 4096)
	        die("Node id must be between 1 and 4096");

	if (strlen(comline->clustername) > MAX_CLUSTER_NAME_LEN) {
	        die("Cluster name must be <= %d characters long",
		    MAX_CLUSTER_NAME_LEN);
	}

	if (comline->timeout && !comline->wait_opt && !comline->wait_quorate_opt)
		die("timeout is only appropriate with wait");
}

int main(int argc, char *argv[])
{
	commandline_t comline;
	int ret;

	prog_name = argv[0];

	memset(&comline, 0, sizeof(commandline_t));

	decode_arguments(argc, argv, &comline);

	switch (comline.operation) {
	case OP_JOIN:
		if (!comline.no_ccs)
			get_ccs_join_info(&comline);
		check_arguments(&comline);

		if (comline.timeout) {
			signal(SIGALRM, sigalarm_handler);
			alarm(comline.timeout);
		}

		join(&comline);
		if (comline.wait_opt || comline.wait_quorate_opt) {
			do {
				ret = cluster_wait(&comline);
				if (ret == ENOTCONN)
					join(&comline);

			} while (ret == ENOTCONN);
		}
		break;

	case OP_LEAVE:
		leave(&comline);
		break;

	case OP_EXPECTED:
		set_expected(&comline);
		break;

	case OP_VOTES:
		set_votes(&comline);
		break;

	case OP_KILL:
		kill_node(&comline);
		break;

	case OP_VERSION:
		version(&comline);
		break;

	case OP_WAIT:
		if (comline.timeout) {
			signal(SIGALRM, sigalarm_handler);
			alarm(comline.timeout);
		}
		cluster_wait(&comline);
		break;

	case OP_STATUS:
		show_status();
		break;

	case OP_NODES:
		show_nodes();
		break;

	case OP_SERVICES:
		show_services();
		break;

	/* FIXME: support CLU_SET_NODENAME? */
	}

	exit(EXIT_SUCCESS);
}

char *prog_name;
