// SPDX-License-Identifier: LGPL-3.0-or-later
/*
 * vim:noexpandtab:shiftwidth=8:tabstop=8:
 *
 * Copyright 2017 Red Hat, Inc. and/or its affiliates.
 * Author: Jeff Layton <jlayton@redhat.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * rados-grace: tool for managing coordinated grace period database
 *
 * This tool allows an administrator to make direct changes to the rados_grace
 * database. See the rados_grace support library sources for more info about
 * the internals.
 */
#include "config.h"
#include <stdio.h>
#include <stdint.h>
#include <endian.h>
#include <rados/librados.h>
#include <errno.h>
#include <getopt.h>
#include <stdlib.h>
#include <limits.h>
#include <stdbool.h>
#include <getopt.h>
#include <rados_grace.h>

static int cluster_connect(rados_ioctx_t *io_ctx, const char *id,
			   const char *cephconf, const char *pool,
			   const char *ns, bool create)
{
	int ret;
	rados_t clnt;

	ret = rados_create(&clnt, id);
	if (ret < 0) {
		fprintf(stderr, "rados_create: %d\n", ret);
		return ret;
	}

	ret = rados_conf_read_file(clnt, cephconf);
	if (ret < 0) {
		fprintf(stderr, "rados_conf_read_file: %d\n", ret);
		return ret;
	}

	ret = rados_connect(clnt);
	if (ret < 0) {
		fprintf(stderr, "rados_connect: %d\n", ret);
		return ret;
	}

	if (create) {
		ret = rados_pool_create(clnt, pool);
		if (ret < 0 && ret != -EEXIST) {
			fprintf(stderr, "rados_pool_create: %d\n", ret);
			return ret;
		}
	}

	ret = rados_ioctx_create(clnt, pool, io_ctx);
	if (ret < 0) {
		fprintf(stderr, "rados_ioctx_create: %d\n", ret);
		return ret;
	}

	rados_ioctx_set_namespace(*io_ctx, ns);
	return 0;
}

static const struct option long_options[] = {
	{ "cephconf", 1, NULL, 'c' }, { "ns", 1, NULL, 'n' },
	{ "oid", 1, NULL, 'o' },      { "pool", 1, NULL, 'p' },
	{ "userid", 1, NULL, 'u' },   { NULL, 0, NULL, 0 }
};

static void usage(char *const *argv)
{
	fprintf(stderr,
		"Usage:\n%s [ --userid ceph_user ] [ --cephconf /path/to/ceph.conf ] [ --ns namespace ] [ --oid obj_id ] [ --pool pool_id ] dump|add|start|join|lift|remove|enforce|noenforce|member [ nodeid ... ]\n",
		argv[0]);
}

int main(int argc, char *const *argv)
{
	int ret, nodes = 0;
	rados_ioctx_t io_ctx;
	const char *cmd = "dump";
	uint64_t cur, rec;
	char *userid = NULL;
	char *cephconf = NULL;
	char *pool = DEFAULT_RADOS_GRACE_POOL;
	char *oid = DEFAULT_RADOS_GRACE_OID;
	char *ns = NULL;
	int c;
	char **node_names;
	bool do_add;

	while ((c = getopt_long(argc, argv, "c:n:o:p:u:", long_options,
				NULL)) != -1) {
		switch (c) {
		case 'c':
			cephconf = optarg;
			break;
		case 'n':
			ns = optarg;
			break;
		case 'o':
			oid = optarg;
			break;
		case 'p':
			pool = optarg;
			break;
		case 'u':
			userid = optarg;
			break;
		default:
			usage(argv);
			return 1;
		}
	}

	if (argc > optind) {
		cmd = argv[optind];
		++optind;
		nodes = argc - optind;
		if (nodes > 0) {
			int i;
			int len = 0;
			char *endptr;
			bool prepend;

			node_names = calloc(nodes, sizeof(char *));
			if (!node_names) {
				fprintf(stderr,
					"Allocation failed for node_names\n");
				ret = -ENOMEM;
				/* ensures we don't do node_names[i]
				 * when it's NULL */
				goto out;
			}
			/* create the node names */
			for (i = 0; i < nodes; ++i) {
				prepend = true;
				errno = 0;

				/* check if node name is numeric */
				(void)strtol(argv[optind + i], &endptr, 10);
				if (errno) {
					/* non-numeric */
					len = 1;
					prepend = false;
				} else {
					if (*endptr != '\0') {
						/* non-numeric */
						len = 1;
						prepend = false;
					} else /* numeric */
						len = 6;
				}
				len += strlen(argv[optind + i]);
				node_names[i] = calloc(1, len);
				if (prepend)
					snprintf(node_names[i], len, "node%s",
						 argv[optind + i]);
				else
					snprintf(node_names[i], len, "%s",
						 argv[optind + i]);
			}
		}
	}

	do_add = !strcmp(cmd, "add");
	ret = cluster_connect(&io_ctx, userid, cephconf, pool, ns, do_add);
	if (ret) {
		fprintf(stderr, "Can't connect to cluster: %d\n", ret);
		return 1;
	}

	if (!strcmp(cmd, "dump")) {
		ret = rados_grace_dump(io_ctx, oid, stdout);
		goto out;
	}

	if (!nodes) {
		fprintf(stderr, "Need at least one nodeid.\n");
		ret = -EINVAL;
		goto out;
	}

	if (do_add) {
		ret = rados_grace_create(io_ctx, oid);
		if (ret < 0 && ret != -EEXIST) {
			fprintf(stderr, "Can't create grace db: %d\n", ret);
			return 1;
		}
		ret = rados_grace_add(io_ctx, oid, nodes,
				      (const char *const *)node_names);
	} else if (!strcmp(cmd, "start")) {
		ret = rados_grace_join_bulk(io_ctx, oid, nodes,
					    (const char *const *)node_names,
					    &cur, &rec, true);
	} else if (!strcmp(cmd, "join")) {
		uint64_t cur, rec;

		ret = rados_grace_join_bulk(io_ctx, oid, nodes,
					    (const char *const *)node_names,
					    &cur, &rec, false);
	} else if (!strcmp(cmd, "lift")) {
		ret = rados_grace_lift_bulk(io_ctx, oid, nodes,
					    (const char *const *)node_names,
					    &cur, &rec, false);
	} else if (!strcmp(cmd, "remove")) {
		ret = rados_grace_lift_bulk(io_ctx, oid, nodes,
					    (const char *const *)node_names,
					    &cur, &rec, true);
	} else if (!strcmp(cmd, "enforce")) {
		ret = rados_grace_enforcing_toggle(
			io_ctx, oid, nodes, (const char *const *)node_names,
			&cur, &rec, true);
	} else if (!strcmp(cmd, "noenforce")) {
		ret = rados_grace_enforcing_toggle(
			io_ctx, oid, nodes, (const char *const *)node_names,
			&cur, &rec, false);
	} else if (!strcmp(cmd, "member")) {
		ret = rados_grace_member_bulk(io_ctx, oid, nodes,
					      (const char *const *)node_names);
	} else {
		usage(argv);
		ret = -EINVAL;
	}
out:
	if (ret) {
		fprintf(stderr, "Failure: %d\n", ret);
		return 1;
	}
	return 0;
}
