/*****************************************************************************\
 *  scontrol.c - administration tool for slurm.
 *	provides interface to read, write, update, and configurations.
 *****************************************************************************
 *  Copyright (C) 2002-2007 The Regents of the University of California.
 *  Copyright (C) 2008-2010 Lawrence Livermore National Security.
 *  Portions Copyright (C) 2008 Vijay Ramasubramanian.
 *  Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER).
 *  Written by Morris Jette <jette1@llnl.gov>
 *  CODE-OCEC-09-009. All rights reserved.
 *
 *  This file is part of Slurm, a resource management program.
 *  For details, see <https://slurm.schedmd.com/>.
 *  Please also read the included file: DISCLAIMER.
 *
 *  Slurm is free software; you can redistribute it and/or modify it under
 *  the terms of the GNU General Public License as published by the Free
 *  Software Foundation; either version 2 of the License, or (at your option)
 *  any later version.
 *
 *  In addition, as a special exception, the copyright holders give permission
 *  to link the code of portions of this program with the OpenSSL library under
 *  certain conditions as described in each individual source file, and
 *  distribute linked combinations including the two. You must obey the GNU
 *  General Public License in all respects for all of the code used other than
 *  OpenSSL. If you modify file(s) with this exception, you may extend this
 *  exception to your version of the file(s), but you are not obligated to do
 *  so. If you do not wish to do so, delete this exception statement from your
 *  version.  If you delete this exception statement from all source files in
 *  the program, then also delete it here.
 *
 *  Slurm 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 Slurm; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA.
\*****************************************************************************/

#include "config.h"

#include <limits.h>
#include <stdarg.h>

#include "scontrol.h"
#include "src/common/data.h"
#include "src/common/proc_args.h"
#include "src/common/ref.h"
#include "src/common/strlcpy.h"
#include "src/common/uid.h"
#include "src/interfaces/data_parser.h"
#include "src/interfaces/hash.h"
#include "src/interfaces/serializer.h"

#define OPT_LONG_HIDE    0x102
#define OPT_LONG_LOCAL   0x103
#define OPT_LONG_SIBLING 0x104
#define OPT_LONG_FEDR    0x105
#define OPT_LONG_AUTOCOMP 0x106
#define OPT_LONG_JSON 0x107
#define OPT_LONG_YAML 0x108

/* Global externs from scontrol.h */
char *command_name;
list_t *clusters = NULL;
char *cluster_names = NULL;
int all_flag = 0;	/* display even hidden partitions */
int detail_flag = 0;	/* display additional details */
int future_flag = 0;	/* display future nodes */
int exit_code = 0;	/* scontrol's exit code, =1 on any error at any time */
int exit_flag = 0;	/* program to terminate if =1 */
int federation_flag = 0;/* show federated jobs */
int local_flag = 0;     /* show only local jobs -- not remote remote sib jobs */
int one_liner = 0;	/* one record per line if =1 */
int quiet_flag = 0;	/* quiet=1, verbose=-1, normal=0 */
int sibling_flag = 0;   /* show sibling jobs (if any fed job). */
int verbosity = 0;	/* count of "-v" options */
uint32_t euid = SLURM_AUTH_NOBODY; /* proxy request as user */
const char *mime_type = NULL; /* mimetype if we are using data_parser */
const char *data_parser = NULL; /* data_parser args */

job_info_msg_t *old_job_info_ptr = NULL;
node_info_msg_t *old_node_info_ptr = NULL;
partition_info_msg_t *old_part_info_ptr = NULL;
reserve_info_msg_t *old_res_info_ptr = NULL;
slurm_ctl_conf_info_msg_t *old_slurm_ctl_conf_ptr = NULL;

static void	_create_it(int argc, char **argv);
static void	_delete_it(int argc, char **argv);
static void     _show_it(int argc, char **argv);
static void	_fetch_token(int argc, char **argv);
static int _get_command(int *argc, char **argv);
static void _print_config(char *config_param, int argc, char **argv);
static void     _print_daemons(void);
static void     _print_aliases(char* node_hostname);
static void _print_ping(int argc, char **argv);
static void	_print_slurmd(char *hostlist);
static void     _print_version(void);
static int	_process_command(int argc, char **argv);
static void	_update_it(int argc, char **argv);
static int	_update_slurmctld_debug(char *val);
static void	_usage(void);
static void	_write_config(char *file_name);

decl_static_data(usage_txt);

static void _printf_error(const char *format, ...)
{
	va_list args;
	va_start(args, format);
	exit_code = 1;
	if (quiet_flag != 1) {
		vfprintf(stderr, format, args);
		if (errno)
			fprintf(stderr, ":%s\n", slurm_strerror(errno));
		else
			fprintf(stderr, "\n");
	}
	va_end(args);
}

int main(int argc, char **argv)
{
	int error_code = SLURM_SUCCESS, opt_char;
	char *env_val;
	log_options_t opts = LOG_OPTS_STDERR_ONLY ;

	int option_index;
	static struct option long_options[] = {
		{"autocomplete", required_argument, 0, OPT_LONG_AUTOCOMP},
		{"all",      0, 0, 'a'},
		{"cluster",  1, 0, 'M'},
		{"clusters", 1, 0, 'M'},
		{"details",  0, 0, 'd'},
		{"federation",0, 0, OPT_LONG_FEDR},
		{"future",   0, 0, 'F'},
		{"help",     0, 0, 'h'},
		{"hide",     0, 0, OPT_LONG_HIDE},
		{"json", optional_argument, 0, OPT_LONG_JSON},
		{"local",    0, 0, OPT_LONG_LOCAL},
		{"oneliner", 0, 0, 'o'},
		{"quiet",    0, 0, 'Q'},
		{"sibling",  0, 0, OPT_LONG_SIBLING},
		{"uid",	     1, 0, 'u'},
		{"usage",    0, 0, 'h'},
		{"verbose",  0, 0, 'v'},
		{"version",  0, 0, 'V'},
		{"yaml", optional_argument, 0, OPT_LONG_YAML},
		{NULL,       0, 0, 0}
	};

	command_name = argv[0];
	slurm_init(NULL);
	log_init("scontrol", opts, SYSLOG_FACILITY_DAEMON, NULL);

	if (xstrstr(slurm_conf.fed_params, "fed_display"))
		federation_flag = true;

	if (getenv ("SCONTROL_ALL"))
		all_flag = 1;
	if ((env_val = getenv("SLURM_CLUSTERS"))) {
		xfree(cluster_names);
		cluster_names = xstrdup(env_val);
		local_flag = 1;
	}
	if (getenv("SCONTROL_FEDERATION"))
		federation_flag = 1;
	if (getenv("SCONTROL_FUTURE"))
		future_flag = 1;
	if (getenv("SCONTROL_LOCAL"))
		local_flag = 1;
	if (getenv("SCONTROL_SIB") || getenv("SCONTROL_SIBLING"))
		sibling_flag = 1;

	while (1) {
		if ((optind < argc) &&
		    !xstrncasecmp(argv[optind], "setdebugflags", 8))
			break;	/* avoid parsing "-<flagname>" as option */
		if ((opt_char = getopt_long(argc, argv, "adhM:FoQu:vV",
					    long_options, &option_index)) == -1)
			break;
		switch (opt_char) {
		case (int)'?':
			fprintf(stderr, "Try \"scontrol --help\" for "
				"more information\n");
			exit(1);
			break;
		case (int)'a':
			all_flag = 1;
			break;
		case (int)'d':
			detail_flag = 1;
			break;
		case (int)'F':
			future_flag = 1;
			break;
		case (int)'h':
			_usage ();
			exit(exit_code);
			break;
		case OPT_LONG_FEDR:
			federation_flag = 1;
			break;
		case OPT_LONG_HIDE:
			all_flag = 0;
			detail_flag = 0;
			break;
		case OPT_LONG_LOCAL:
			local_flag = 1;
			break;
		case (int)'M':
			xfree(cluster_names);
			cluster_names = xstrdup(optarg);
			local_flag = 1;
			break;
		case (int)'o':
			one_liner = 1;
			break;
		case (int)'Q':
			quiet_flag = 1;
			break;
		case OPT_LONG_SIBLING:
			sibling_flag = 1;
			break;
		case (int)'u':
			if (uid_from_string(optarg, &euid) != SLURM_SUCCESS) {
				error("--uid=\"%s\" invalid", optarg);
				exit(exit_code);
			}
			break;
		case (int)'v':
			quiet_flag = -1;
			verbosity++;
			break;
		case (int)'V':
			_print_version();
			exit(exit_code);
			break;
		case OPT_LONG_AUTOCOMP:
			suggest_completion(long_options, optarg);
			exit(0);
		case OPT_LONG_JSON :
			mime_type = MIME_TYPE_JSON;
			data_parser = optarg;
			detail_flag = 1;
			serializer_required(MIME_TYPE_JSON);
			break;
		case OPT_LONG_YAML :
			mime_type = MIME_TYPE_YAML;
			data_parser = optarg;
			detail_flag = 1;
			serializer_required(MIME_TYPE_YAML);
			break;
		default:
			exit_code = 1;
			fprintf(stderr, "getopt error, returned %c\n",
				opt_char);
			exit(exit_code);
		}
	}

	FREE_NULL_LIST(clusters);
	if (cluster_names) {
		if (slurm_get_cluster_info(&(clusters),
					   cluster_names,
					   (federation_flag ?
					    SHOW_FEDERATION : SHOW_LOCAL))) {

			print_db_notok(cluster_names, 0);
			fatal("Could not get cluster information");
		}
		working_cluster_rec = list_peek(clusters);
		local_flag = true;
	}

	if (clusters && (list_count(clusters) > 1))
		fatal("Only one cluster can be used at a time with scontrol");

	if (verbosity) {
		opts.stderr_level += verbosity;
		log_alter(opts, SYSLOG_FACILITY_USER, NULL);
	}

	/* We are only running a single command and exiting */
	if (optind < argc) {
		error_code = _process_command(argc - optind, argv + optind);
	} else if ((argc == 2) && (optind == argc) && mime_type &&
		   !xstrcmp(data_parser, "list")) {
		/*
		 * We are only listing the available data parser plugins.
		 * Calling DATA_DUMP_CLI_SINGLE() with a dummy type to get to
		 * "list".
		 * TODO: After Bug 18109 is fixed, replace this logic:
		 */
		DATA_DUMP_CLI_SINGLE(OPENAPI_PING_ARRAY_RESP, NULL, argc, argv,
				     NULL, mime_type, data_parser, exit_code);
	} else {
		/* We are running interactively multiple commands */
		int input_field_count = 0;
		char **input_fields = xcalloc(MAX_INPUT_FIELDS, sizeof(char *));
		while (error_code == SLURM_SUCCESS) {
			error_code = _get_command(
				&input_field_count, input_fields);
			if (error_code || exit_flag) {	/* EOF */
				putchar('\n');
				break;
			}

			error_code = _process_command(
				input_field_count, input_fields);
			if (exit_flag)
				break;
		}
		xfree(input_fields);
	}

#ifdef MEMORY_LEAK_DEBUG
	FREE_NULL_LIST(clusters);
	slurm_conf_destroy();
	serializer_g_fini();

	slurm_free_job_info_msg(old_job_info_ptr);
	slurm_free_node_info_msg(old_node_info_ptr);
	slurm_free_partition_info_msg(old_part_info_ptr);
	slurm_free_reservation_info_msg(old_res_info_ptr);
	slurm_free_ctl_conf(old_slurm_ctl_conf_ptr);

#endif /* MEMORY_LEAK_DEBUG */

	exit(exit_code);
}

static void _print_version(void)
{
	print_slurm_version();
	if (quiet_flag == -1) {
		long version = slurm_api_version();
		printf("slurm_api_version: %ld, %ld.%ld.%ld\n", version,
			SLURM_VERSION_MAJOR(version),
			SLURM_VERSION_MINOR(version),
			SLURM_VERSION_MICRO(version));
	}
}


#if !HAVE_READLINE
/*
 * Alternative to readline if readline is not available
 */
static char *_getline(const char *prompt)
{
	char buf[4096];
	char *line;
	int len;

	printf("%s", prompt);

	/* Set "line" here to avoid a warning, discard later */
	line = fgets(buf, 4096, stdin);
	if (line == NULL)
		return NULL;
	len = strlen(buf);
	if ((len == 0) || (len >= 4096))
		return NULL;
	if (buf[len-1] == '\n')
		buf[len-1] = '\0';
	else
		len++;
	line = malloc(len);
	if (!line)
		return NULL;
	strlcpy(line, buf, len);
	return line;
}
#endif

/*
 * _get_command - get a command from the user
 * OUT argc - location to store count of arguments
 * OUT argv - location to store the argument list
 */
static int _get_command (int *argc, char **argv)
{
	char *in_line;
	static char *last_in_line = NULL;
	int i, in_line_size;
	static int last_in_line_size = 0;

	*argc = 0;

#if HAVE_READLINE
	in_line = readline ("scontrol: ");
#else
	in_line = _getline("scontrol: ");
#endif
	if (in_line == NULL) {
		exit_flag = true;
		return 0;
	} else if (xstrcmp (in_line, "!!") == 0) {
		free (in_line);
		in_line = last_in_line;
		in_line_size = last_in_line_size;
	} else {
		if (last_in_line)
			free (last_in_line);
		last_in_line = in_line;
		last_in_line_size = in_line_size = strlen (in_line);
	}

#if HAVE_READLINE
	add_history(in_line);
#endif

	/* break in_line into tokens */
	for (i = 0; i < in_line_size; i++) {
		bool double_quote = false, single_quote = false;
		if (in_line[i] == '\0')
			break;
		if (isspace ((int) in_line[i]))
			continue;
		if (((*argc) + 1) > MAX_INPUT_FIELDS) {	/* bogus input line */
			exit_code = 1;
			fprintf (stderr,
				 "%s: can not process over %d words\n",
				 command_name, MAX_INPUT_FIELDS - 1);
			return E2BIG;
		}
		argv[(*argc)++] = &in_line[i];
		for (i++; i < in_line_size; i++) {
			if (in_line[i] == '\042') {
				double_quote = !double_quote;
				continue;
			}
			if (in_line[i] == '\047') {
				single_quote = !single_quote;
				continue;
			}
			if (in_line[i] == '\0')
				break;
			if (double_quote || single_quote)
				continue;
			if (isspace ((int) in_line[i])) {
				in_line[i] = '\0';
				break;
			}
		}
	}
	return 0;
}

/*
 * _write_config - write the configuration parameters and values to a file.
 */
static void _write_config(char *file_name)
{
	int error_code;
	node_info_msg_t *node_info_ptr = NULL;
	partition_info_msg_t *part_info_ptr = NULL;
	slurm_ctl_conf_info_msg_t  *slurm_ctl_conf_ptr = NULL;

	/* slurm config loading code copied from _print_config() */

	if (old_slurm_ctl_conf_ptr) {
		error_code = slurm_load_ctl_conf (
				old_slurm_ctl_conf_ptr->last_update,
				&slurm_ctl_conf_ptr);
		if (error_code == SLURM_SUCCESS) {
			slurm_free_ctl_conf(old_slurm_ctl_conf_ptr);
		} else if (errno == SLURM_NO_CHANGE_IN_DATA) {
			slurm_ctl_conf_ptr = old_slurm_ctl_conf_ptr;
			error_code = SLURM_SUCCESS;
			if (quiet_flag == -1) {
				printf ("slurm_load_ctl_conf no change "
					"in data\n");
			}
		}
	} else {
		error_code = slurm_load_ctl_conf ((time_t) NULL,
						  &slurm_ctl_conf_ptr);
	}

	if (error_code) {
		_printf_error("slurm_load_ctl_conf error");
	} else
		old_slurm_ctl_conf_ptr = slurm_ctl_conf_ptr;


	if (error_code == SLURM_SUCCESS) {
		int save_all_flag = all_flag;
		all_flag = 1;

		if (file_name)
			setenv("SLURM_CONF_OUT", file_name, 1);

		/* now gather node info */
		error_code = scontrol_load_nodes(&node_info_ptr, SHOW_ALL);

		if (error_code) {
			_printf_error("slurm_load_node error");
			all_flag = save_all_flag;
			return;
		}

		/* now gather partition info */
		error_code = scontrol_load_partitions(&part_info_ptr);
		all_flag = save_all_flag;
		if (error_code) {
			exit_code = 1;
			_printf_error("slurm_load_partitions error");
			return;
		}

		/* send the info off to be written */
		slurm_write_ctl_conf (slurm_ctl_conf_ptr,
				      node_info_ptr,
				      part_info_ptr);
	}
}

/*
 * _print_config - print the specified configuration parameter and value
 * IN config_param - NULL to print all parameters and values
 */
static void _print_config(char *config_param, int argc, char **argv)
{
	int error_code;
	slurm_ctl_conf_info_msg_t  *slurm_ctl_conf_ptr = NULL;

	/*
	 * There isn't a parser for slurm.conf but there is one for ping, which
	 * gets printed as part of this function. So to make sure the output is
	 * not mixing output types disable json/yaml output for ping.
	 */
	mime_type = NULL;

	if (old_slurm_ctl_conf_ptr) {
		error_code = slurm_load_ctl_conf (
				old_slurm_ctl_conf_ptr->last_update,
				&slurm_ctl_conf_ptr);
		if (error_code == SLURM_SUCCESS)
			slurm_free_ctl_conf(old_slurm_ctl_conf_ptr);
		else if (errno == SLURM_NO_CHANGE_IN_DATA) {
			slurm_ctl_conf_ptr = old_slurm_ctl_conf_ptr;
			error_code = SLURM_SUCCESS;
			if (quiet_flag == -1) {
				printf ("slurm_load_ctl_conf no change "
					"in data\n");
			}
		}
	}
	else
		error_code = slurm_load_ctl_conf ((time_t) NULL,
						  &slurm_ctl_conf_ptr);

	if (error_code) {
		_printf_error("slurm_load_ctl_conf error");
	}
	else
		old_slurm_ctl_conf_ptr = slurm_ctl_conf_ptr;


	if (error_code == SLURM_SUCCESS) {
		slurm_print_ctl_conf (stdout, slurm_ctl_conf_ptr) ;
		fprintf(stdout, "\n");
	}
	if (slurm_ctl_conf_ptr)
		_print_ping(argc, argv);
}

/* Print slurmd status on localhost.
 * Parse hostlist in the future */
static void _print_slurmd(char *hostlist)
{
	slurmd_status_t *slurmd_status;

	if (slurm_load_slurmd_status(&slurmd_status))
		_printf_error("slurm_load_slurmd_status");
	else {
		slurm_print_slurmd_status(stdout, slurmd_status);
		slurm_free_slurmd_status(slurmd_status);
	}
}

/* Print state of controllers only */
static void _print_ping(int argc, char **argv)
{
	static const char *state[2] = { "DOWN", "UP" };
	char mode[64];
	bool down_msg = false;
	controller_ping_t *pings = ping_all_controllers();

	if (mime_type) {
		DATA_DUMP_CLI_SINGLE(OPENAPI_PING_ARRAY_RESP, pings, argc, argv,
				     NULL, mime_type, data_parser, exit_code);
		xfree(pings);
		return;
	}

	exit_code = 1;
	for (controller_ping_t *ping = pings; ping && ping->hostname; ping++) {
		if (ping->pinged)
			exit_code = SLURM_SUCCESS;
		else
			down_msg = true;

		if (ping->offset == 0)
			snprintf(mode, sizeof(mode), "primary");
		else if ((ping->offset == 1) && (slurm_conf.control_cnt == 2))
			snprintf(mode, sizeof(mode), "backup");
		else
			snprintf(mode, sizeof(mode), "backup%d", ping->offset);
		fprintf(stdout, "Slurmctld(%s) at %s is %s\n",
			mode, ping->hostname, state[ping->pinged]);
	}
	xfree(pings);

	if (down_msg && (getuid() == 0)) {
		fprintf(stdout, "*****************************************\n");
		fprintf(stdout, "** RESTORE SLURMCTLD DAEMON TO SERVICE **\n");
		fprintf(stdout, "*****************************************\n");
	}
}

/*
 * _print_daemons - report what daemons should be running on this node
 */
static void
_print_daemons (void)
{
	slurm_ctl_conf_info_msg_t *conf;
	char node_name_short[HOST_NAME_MAX];
	char node_name_long[HOST_NAME_MAX];
	char *c, *n, *token, *save_ptr = NULL;
	int actld = 0, ctld = 0, d = 0, i;
	char *daemon_list = NULL;

	conf = slurm_conf_lock();

	gethostname_short(node_name_short, HOST_NAME_MAX);
	gethostname(node_name_long, HOST_NAME_MAX);
	for (i = 0; i < conf->control_cnt; i++) {
		if (!conf->control_machine[i])
			break;
		actld = 1;
		c = xstrdup(conf->control_machine[i]);
		token = strtok_r(c, ",", &save_ptr);
		while (token) {
			if (!xstrcmp(token, node_name_short) ||
			    !xstrcmp(token, node_name_long)  ||
			    !xstrcasecmp(token, "localhost")) {
				ctld = 1;
				break;
			}
			token = strtok_r(NULL, ",", &save_ptr);
		}
		xfree(c);
		if (ctld)
			break;
	}
	slurm_conf_unlock();

	if ((n = slurm_conf_get_nodename(node_name_short))) {
		d = 1;
		xfree(n);
	} else if ((n = slurm_conf_get_aliased_nodename())) {
		d = 1;
		xfree(n);
	} else if ((n = slurm_conf_get_nodename("localhost"))) {
		d = 1;
		xfree(n);
	}

	if (actld && ctld)
		xstrcat(daemon_list, "slurmctld ");
	if (actld && d)
		xstrcat(daemon_list, "slurmd");
	fprintf (stdout, "%s\n", daemon_list) ;
	xfree(daemon_list);
}

/*
 * _print_aliases - report which aliases should be running on this node
 */
static void
_print_aliases (char* node_hostname)
{
	char me[HOST_NAME_MAX], *n = NULL, *a = NULL;
	char *s;

	if (!node_hostname) {
		gethostname_short(me, HOST_NAME_MAX);
		s = me;
	} else
		s = node_hostname;

	if (!(n = slurm_conf_get_aliases(s)) && (s == me)) {

		if (!(a = slurm_conf_get_aliased_nodename()))
			a = slurm_conf_get_nodename("localhost");

		if (a) {
			n = slurm_conf_get_aliases(a);
			xfree(a);
		}
	}

	if (n) {
		fprintf(stdout, "%s\n", n);
		xfree(n);
	}

}

void _process_reboot_command(const char *tag, int argc, char **argv)
{
	int error_code = SLURM_SUCCESS;
	bool asap = false;
	char *reason = NULL;
	uint32_t next_state = NO_VAL;
	int argc_offset = 1;

	if (argc > 1) {
		int i = 1;
		for (; i <= 3 && i < argc; i++) {
			if (!strcasecmp(argv[i], "ASAP")) {
				asap = true;
				argc_offset++;
			} else if (!xstrncasecmp(argv[i], "Reason=",
						 strlen("Reason="))) {
				char *tmp_ptr = strchr(argv[i], '=');
				if (!tmp_ptr || !*(tmp_ptr + 1)) {
					_printf_error("missing reason");
					xfree(reason);
					return;
				}

				xfree(reason);
				reason = xstrdup(tmp_ptr+1);
				argc_offset++;
			} else if (!xstrncasecmp(argv[i], "nextstate=",
						 strlen("nextstate="))) {
				int state_str_len;
				char* state_str;
				char *tmp_ptr = strchr(argv[i], '=');
				if (!tmp_ptr || !*(tmp_ptr + 1)) {
					_printf_error("missing state");
					xfree(reason);
					return;
				}

				state_str = xstrdup(tmp_ptr+1);
				state_str_len = strlen(state_str);
				argc_offset++;

				if (!xstrncasecmp(state_str, "DOWN",
						  MAX(state_str_len, 1)))
					next_state = NODE_STATE_DOWN;
				else if (!xstrncasecmp(state_str, "RESUME",
						       MAX(state_str_len, 1)))
					next_state = NODE_RESUME;
				else {
					_printf_error("Invalid state: %s\n Valid states: DOWN, RESUME",
						      state_str);
					xfree(reason);
					xfree(state_str);
					return;
				}
				xfree(state_str);
			}
		}
	}
	if ((argc - argc_offset) > 1) {
		exit_code = 1;
		fprintf (stderr,
			 "too many arguments for keyword:%s\n",
			 tag);
	} else if ((argc - argc_offset) < 1) {
		exit_code = 1;
		fprintf(stderr, "Missing node list. Specify ALL|<NodeList>");
	} else {
		error_code = scontrol_reboot_nodes(argv[argc_offset], asap,
						   next_state, reason);
	}

	xfree(reason);
	if (error_code)
		_printf_error("scontrol_reboot_nodes error");
}

void _process_power_command(const char *tag, int argc, char **argv)
{
	int error_code = SLURM_SUCCESS;
	bool power_up;
	bool asap = false;
	bool force = false;
	int min_argv = 3;
	int max_argv = 5;

	/* at least 'power' should have been supplied */
	xassert(argc);

	if ((argc <= max_argv) && (argc >= min_argv)) {
		int idx = 1;
		char *reason = NULL;

		/* up or down subcommand */
		if (!xstrcasecmp(argv[idx], "UP")) {
			power_up = true;
		} else if (!xstrcasecmp(argv[idx], "DOWN")) {
			power_up = false;
		} else {
			_printf_error("unexpected argument: '%s'", argv[idx]);
			goto done;
		}
		idx++;

		/*
		 * Optional asap|force if powerering down. Silently ignore
		 * asap|force if powering up as there's no such option.
		 */
		if (!xstrcasecmp(argv[idx], "ASAP")) {
			asap = true;
			idx++;
		} else if (!xstrcasecmp(argv[idx], "FORCE")) {
			force = true;
			idx++;
		}

		if ((force || asap) && power_up) {
			_printf_error("The '%s' argument is not valid for power up requests",
				      argv[idx - 1]);
			goto done;
		}

		/* Missing list of nodes */
		if (idx == argc) {
			exit_code = 1;
			_printf_error("Mandatory argument {ALL|<NodeList>|<NodeSet>} missing");
			goto done;
		}

		/* We have one more argument - it may be Reason= */
		if ((idx + 1) < argc) {
			if (!xstrncasecmp(argv[idx + 1],
					  "Reason=", strlen("Reason="))) {
				if (!power_up) {
					char *tmp_ptr =
						strchr(argv[idx + 1], '=');

					if (!tmp_ptr || !*(tmp_ptr + 1)) {
						exit_code = 1;
						_printf_error("missing reason");
						goto done;
					}
					reason = xstrdup(tmp_ptr + 1);
				} else {
					_printf_error("Reason only allowed for scontrol power down operation");
					goto done;
				}
			} else {
				_printf_error("unexpected argument:'%s'",
					      argv[idx + 1]);
				goto done;
			}
		}

		/* call with nodelist */
		error_code = scontrol_power_nodes(argv[idx], power_up, asap,
						  force, reason);
		xfree(reason);

	} else if (argc < min_argv) {
		_printf_error("too few arguments for keyword:%s", argv[0]);
	} else if (argc > max_argv) {
		_printf_error("too many arguments for keyword:%s", argv[0]);
	}

done:
	if (error_code)
		_printf_error("scontrol_power_nodes error");
}

static void _setdebug(int argc, char **argv)
{
	int level = -1, index = 0;
	char *endptr;
	char *levels[] = {
		"quiet", "fatal", "error", "info", "verbose", "debug",
		"debug2", "debug3", "debug4", "debug5", NULL
	};

	if (argc > 3) {
		_printf_error("too many arguments for keyword:%s", argv[0]);
		return;
	} else if (argc < 2) {
		_printf_error("too few arguments for keyword:%s", argv[0]);
		return;
	}

	while (levels[index]) {
		if (!xstrcasecmp(argv[1], levels[index])) {
			level = index;
			break;
		}
		index ++;
	}

	if (level == -1) {
		/* effective levels: 0 - 9 */
		level = (int) strtoul(argv[1], &endptr, 10);
		if (*endptr != '\0' || level > 9) {
			_printf_error("invalid debug level: %s", argv[1]);
			return;
		}
	}

	if (argc == 2) {
		if (slurm_set_debug_level(level))
			_printf_error("slurm_set_debug_level error");
	} else if (argc == 3) {
		/*
		 * scontrol setdebug <level> nodes=<list of nodes>
		 */
		char *nodes;

		if (xstrncasecmp(argv[2], "Nodes=", 6)) {
			_printf_error("Invalid option: %s", argv[1]);
			return;
		}

		if ((nodes = strchr(argv[2], '=')))
			nodes++;

		if (slurm_set_slurmd_debug_level(nodes, level)) {
			_printf_error("Failed to change debug level on one or more nodes.");
		}
	}
}

static void _setdebugflags(int argc, char **argv)
{
	char *tag = argv[0];
	int i, mode = 0;
	uint64_t debug_flags_plus = 0;
	uint64_t debug_flags_minus = 0, flags;

	if (argc < 2) {
		_printf_error("too few arguments for keyword:%s", tag);
		return;
	}

	for (i = 1; i < argc; i++) {
		if (argv[i][0] == '+')
			mode = 1;
		else if (argv[i][0] == '-')
			mode = -1;
		else {
			mode = 0;
			break;
		}

		if (debug_str2flags(&argv[i][1], &flags) != SLURM_SUCCESS)
			break;
		if (mode == 1)
			debug_flags_plus |= flags;
		else
			debug_flags_minus |= flags;
	}

	if ((i == (argc - 1)) && (!xstrncasecmp(argv[i], "node=", 5) ||
				  !xstrncasecmp(argv[i], "nodes=", 6))) {
		char *nodes = strchr(argv[i], '=') + 1;

		if (slurm_set_slurmd_debug_flags(nodes, debug_flags_plus,
						 debug_flags_minus)) {
			_printf_error("Failed to set DebugFlags on one or more nodes.");
		}
	} else if (i < argc) {
		_printf_error("invalid debug flag: %s", argv[i]);
		if ((quiet_flag != 1) && (mode == 0)) {
			fprintf(stderr, "Usage: setdebugflags {+|-}NAME [{+|-}NAME] [nodes=<NODES>]\n");
		}
	} else {
		if (slurm_set_debugflags(debug_flags_plus, debug_flags_minus))
			_printf_error("slurm_set_debug_flags error");
	}
}

static void _fetch_token(int argc, char **argv)
{
	char *username = NULL, *token;
	int lifespan = 0;

	for (int i = 1; i < argc; i++) {
		if (!xstrncasecmp("lifespan=", argv[i], 9)) {
			char *val = argv[i] + 9;
			if ((!xstrcasecmp("infinite", val)) ||
			    (!xstrcasecmp("unlimited", val)))
				lifespan = INT_MAX - 1;
			else
				lifespan = parse_int("lifespan", val, true);
		} else if (!xstrncasecmp("username=", argv[i], 9))
			username = argv[i] + 9;
		else {
			fprintf(stderr, "Invalid option: `%s`\n", argv[i]);
			exit_code = 1;
			return;
		}
	}

	if (!(token = slurm_fetch_token(username, lifespan))) {
		/*
		 * Error message printed slurm_fetch_token().
		 * No need for error message here.
		 */
		exit_code = 1;
		return;
	}

	printf("SLURM_JWT=%s\n", token);

	xfree(token);
}

/*
 * _process_command - process the user's command
 * IN argc - count of arguments
 * IN argv - the arguments
 * RET 0 or errno (only for errors fatal to scontrol)
 */
static int _process_command (int argc, char **argv)
{
	int error_code = 0;
	char *tag = argv[0];
	int tag_len = 0;
	int i;

	if (argc < 1) {
		exit_code = 1;
		if (quiet_flag == -1)
			fprintf(stderr, "no input\n");
		return 0;
	} else if (tag)
		tag_len = strlen(tag);
	else {
		if (quiet_flag == -1)
			fprintf(stderr, "input problem\n");
		return 0;
	}

	if (!xstrncasecmp(tag, "all", MAX(tag_len, 2))) {
		all_flag = 1;
	} else if (!xstrncasecmp(tag, "cancel_reboot", MAX(tag_len, 3))) {
		if (argc > 2) {
			exit_code = 1;
			fprintf (stderr,
				 "too many arguments for keyword:%s\n",
				 tag);
		} else if (argc < 2) {
			exit_code = 1;
			fprintf (stderr,
				 "missing argument for keyword:%s\n",
				 tag);
		} else
			scontrol_cancel_reboot(argv[1]);
	} else if (!xstrncasecmp(tag, "completing", MAX(tag_len, 2))) {
		if (argc > 1) {
			exit_code = 1;
			fprintf (stderr,
				 "too many arguments for keyword:%s\n",
				 tag);
		} else
			scontrol_print_completing();
	} else if (!xstrncasecmp(tag, "cluster", MAX(tag_len, 2))) {
		if (clusters) {
			FREE_NULL_LIST(clusters);
			working_cluster_rec = NULL;
		}
		if (argc >= 2) {
			if (slurm_get_cluster_info(&(clusters), argv[1],
						   (federation_flag ?
							    SHOW_FEDERATION :
							    SHOW_LOCAL))) {

				print_db_notok(argv[1], 0);
				fatal("Could not get cluster information");
			}
			working_cluster_rec = list_peek(clusters);
			if (list_count(clusters) > 1) {
				fatal("Only one cluster can be used at a time "
				      "with scontrol");
			}
		}
		slurm_free_job_info_msg(old_job_info_ptr);
		old_job_info_ptr = NULL;
		slurm_free_node_info_msg(old_node_info_ptr);
		old_node_info_ptr = NULL;
		slurm_free_partition_info_msg(old_part_info_ptr);
		old_part_info_ptr = NULL;
		slurm_free_reservation_info_msg(old_res_info_ptr);
		old_res_info_ptr = NULL;
		slurm_free_ctl_conf(old_slurm_ctl_conf_ptr);
		old_slurm_ctl_conf_ptr = NULL;
		/* if (old_job_info_ptr) */
		/* 	old_job_info_ptr->last_update = 0; */
		/* if (old_node_info_ptr) */
		/* 	old_node_info_ptr->last_update = 0; */
		/* if (old_part_info_ptr) */
		/* 	old_part_info_ptr->last_update = 0; */
		/* if (old_res_info_ptr) */
		/* 	old_res_info_ptr->last_update = 0; */
		/* if (old_slurm_ctl_conf_ptr) */
		/* 	old_slurm_ctl_conf_ptr->last_update = 0; */
	} else if (!xstrncasecmp(tag, "create", MAX(tag_len, 2))) {
		if (argc < 2) {
			exit_code = 1;
			fprintf (stderr, "too few arguments for %s keyword\n",
				 tag);
			return 0;
		}
		_create_it ((argc - 1), &argv[1]);
	} else if (!xstrncasecmp(tag, "details", MAX(tag_len, 1))) {
		if (argc > 1) {
			exit_code = 1;
			fprintf (stderr,
				 "too many arguments for keyword:%s\n",
				 tag);
			return 0;
		}
		detail_flag = 1;
	} else if (!xstrncasecmp(tag, "errnumstr", MAX(tag_len, 2)) ||
		   !xstrncasecmp(tag, "errnostr", MAX(tag_len, 2))) {
		if (argc != 2) {
			exit_code = 1;
			fprintf (stderr,
				 "one arguments required for keyword:%s\n",
				 tag);
		} else {
			char *end_ptr;
			int err = strtol(argv[1], &end_ptr, 10);
			if (end_ptr[0] == '\0') {
				printf("%s\n", slurm_strerror(err));
			} else {
				exit_code = 1;
				fprintf (stderr,
					 "numeric arguments required for keyword:%s\n",
					 tag);
			}
		}
	} else if (!xstrncasecmp(tag, "exit", MAX(tag_len, 2))) {
		if (argc > 1) {
			exit_code = 1;
			fprintf (stderr,
				 "too many arguments for keyword:%s\n",
				 tag);
		}
		exit_flag = 1;
	} else if (!xstrncasecmp(tag, "getaddrs", MAX(tag_len, 8))) {
		if (argc == 2)
			scontrol_getaddrs(argv[1]);
		else {
			exit_code = 1;
			fprintf(stderr,
				"one argument required for keyword:%s\n",
				tag);
		}
	} else if (!xstrncasecmp(tag, "gethost", MAX(tag_len, 7))) {
		if (argc == 3)
			scontrol_gethost(argv[1], argv[2]);
		else {
			exit_code = 1;
			fprintf(stderr,
				"two arguments required for keyword:%s\n",
				tag);
		}
	} else if (!xstrncasecmp(tag, "hash_file", MAX(tag_len, 15))) {
		if (argc > 3) {
			exit_code = 1;
			fprintf(stderr, "too many arguments for keyword:%s\n",
				tag);
		} else if (argc < 2) {
			exit_code = 1;
			fprintf(stderr, "missing argument for keyword:%s\n",
				tag);
		} else {
			int hash_len;
			buf_t *buf;
			slurm_hash_t hash = { 0 };

			if (argc > 2)
				hash.type = atoi(argv[2]);

			if (!(buf = create_mmap_buf(argv[1]))) {
				exit_code = 1;
				fprintf(stderr, "Can't open `%s`\n", argv[1]);
			} else {
				hash_len = hash_g_compute(buf->head, buf->size,
							  NULL, 0, &hash);

				FREE_NULL_BUFFER(buf);
				for (int i = 0; i < hash_len; i++)
					printf("%02x", (int)hash.hash[i]);
				printf("\n");
			}
		}
	} else if (!xstrncasecmp(tag, "hash", MAX(tag_len, 9))) {
		if (argc > 3) {
			exit_code = 1;
			fprintf(stderr, "too many arguments for keyword:%s\n",
				tag);
		} else if (argc < 2) {
			exit_code = 1;
			fprintf(stderr, "missing argument for keyword:%s\n",
				tag);
		} else {
			int hash_len;
			slurm_hash_t hash = { 0 };

			if (argc > 2)
				hash.type = atoi(argv[2]);

			hash_len = hash_g_compute(argv[1], strlen(argv[1]),
						  NULL, 0, &hash);
			for (int i = 0; i < hash_len; i++)
				printf("%02x", (int)hash.hash[i]);
			printf("\n");
		}
	} else if (!xstrncasecmp(tag, "help", MAX(tag_len, 2))) {
		if (argc > 1) {
			exit_code = 1;
			fprintf (stderr,
				 "too many arguments for keyword:%s\n",
				 tag);
		}
		_usage ();
	} else if (!xstrncasecmp(tag, "hide", MAX(tag_len, 2))) {
		all_flag = 0;
		detail_flag = 0;
	} else if (!xstrncasecmp(tag, "oneliner", MAX(tag_len, 1))) {
		if (argc > 1) {
			exit_code = 1;
			fprintf (stderr,
				 "too many arguments for keyword:%s\n",
				 tag);
		}
		one_liner = 1;
	} else if (!xstrncasecmp(tag, "pidinfo", MAX(tag_len, 3))) {
		if (argc > 2) {
			exit_code = 1;
			fprintf (stderr,
				 "too many arguments for keyword:%s\n",
				 tag);
		} else if (argc < 2) {
			exit_code = 1;
			fprintf (stderr,
				 "missing argument for keyword:%s\n",
				 tag);
		} else
			scontrol_pid_info ((pid_t) atol (argv[1]) );
	} else if (!xstrncasecmp(tag, "ping", MAX(tag_len, 3))) {
		if (argc > 1) {
			exit_code = 1;
			fprintf (stderr,
				 "too many arguments for keyword:%s\n",
				 tag);
		} else
			_print_ping(argc, argv);
	} else if (!xstrncasecmp(tag, "power", MAX(tag_len, 2))) {
		_process_power_command(tag, argc, argv);
	} else if (!xstrncasecmp(tag, "\\q", 2) ||
		   !xstrncasecmp(tag, "quiet", MAX(tag_len, 4))) {
		if (argc > 1) {
			exit_code = 1;
			fprintf (stderr, "too many arguments for keyword:%s\n",
				 tag);
		}
		quiet_flag = 1;
	} else if (!xstrncasecmp(tag, "quit", MAX(tag_len, 4))) {
		if (argc > 1) {
			exit_code = 1;
			fprintf (stderr,
				 "too many arguments for keyword:%s\n",
				 tag);
		}
		exit_flag = 1;
	} else if (!xstrncasecmp(tag, "reboot_nodes", MAX(tag_len, 3))) {
		_process_reboot_command(tag, argc, argv);
	} else if (!xstrncasecmp(tag, "reconfigure", MAX(tag_len, 3))) {
		if (argc > 2) {
			exit_code = 1;
			fprintf (stderr, "too many arguments for keyword:%s\n",
				 tag);
		}
		error_code = slurm_reconfigure();
		if (error_code)
			_printf_error("slurm_reconfigure error");
	} else if (!xstrncasecmp(tag, "requeue", MAX(tag_len, 3))) {
		if (argc < 2) {
			_printf_error("too few arguments for keyword:%s", tag);
		} else {
			uint32_t i, flags = 0, start_pos = 1;
			for (i = 1; i < argc; i++) {
				if (parse_requeue_flags(argv[i], &flags))
					break;
				start_pos++;
			}
			for (i = start_pos; i < argc; i++) {
				scontrol_requeue(flags, argv[i]);
			}
		}
	} else if (!xstrncasecmp(tag, "requeuehold", 11)) {
		if (argc < 2) {
			_printf_error("too few arguments for keyword:%s", tag);
		} else {
			uint32_t i, flags = 0, start_pos = 1;
			for (i = 1; i < argc; i++) {
				if (parse_requeue_flags(argv[i], &flags))
					break;
				start_pos++;
			}
			for (i = start_pos; i < argc; i++) {
				scontrol_requeue_hold(flags, argv[i]);
			}
		}

	} else if (!xstrncasecmp(tag, "hold",  4) ||
		   !xstrncasecmp(tag, "holdu", 5) ||
		   !xstrncasecmp(tag, "uhold", 5) ||
	           !xstrncasecmp(tag, "release", MAX(tag_len, 3))) {
		if (argc < 2) {
			_printf_error("too few arguments for keyword:%s", tag);
		} else {
			for (i = 1; i < argc; i++) {
				error_code = scontrol_hold(argv[0], argv[i]);
				if (error_code)
					_printf_error("slurm_suspend error");
			}
			(void) scontrol_hold(argv[0], NULL);   /* Clear cache */
		}
	} else if (!xstrncasecmp(tag, "suspend", MAX(tag_len, 2)) ||
		   !xstrncasecmp(tag, "resume", MAX(tag_len, 3))) {
		if (argc < 2) {
			_printf_error("too few arguments for keyword:%s", tag);
		} else {
			for (i = 1; i < argc; i++) {
				scontrol_suspend(argv[0], argv[i]);
			}
		}
	} else if (!xstrncasecmp(tag, "top", MAX(tag_len, 3))) {
		if (argc < 2) {
			_printf_error("too few arguments for keyword:%s", tag);
		} else if (argc > 2) {
			_printf_error("too many arguments for keyword:%s", tag);
		} else {
			scontrol_top_job(argv[1]);
		}
	} else if (!xstrncasecmp(tag, "token", MAX(tag_len, 3))) {
		_fetch_token(argc, argv);
	} else if (!xstrncasecmp(tag, "wait_job", MAX(tag_len, 2))) {
		if (argc > 2) {
			_printf_error("too many arguments for keyword:%s", tag);
		} else if (argc < 2) {
			_printf_error("too few arguments for keyword:%s", tag);
		} else {
			error_code = scontrol_job_ready(argv[1]);
			if (error_code)
				exit_code = 1;
		}
	} else if (!xstrncasecmp(tag, "setdebugflags", MAX(tag_len, 9))) {
		_setdebugflags(argc, argv);
	} else if (!xstrncasecmp(tag, "fsdampeningfactor", MAX(tag_len, 3)) ||
		 !xstrncasecmp(tag, "fairsharedampeningfactor",
			      MAX(tag_len, 3))) {
		if (argc > 2) {
			_printf_error("too many arguments for keyword:%s", tag);
		} else if (argc < 2) {
			_printf_error("too few arguments for keyword:%s", tag);
		} else {
			uint16_t factor = 0;
			char *endptr;
			factor = (uint16_t)strtoul(argv[1], &endptr, 10);
			if (*endptr != '\0' || factor == 0) {
				exit_code = 1;
				_printf_error("invalid dampening factor: %s",
					      argv[1]);
			} else {
				error_code = slurm_set_fs_dampeningfactor(
						factor);
				if (error_code)
					_printf_error("slurm_set_fs_dampeningfactor error");
			}
		}
	} else if (!xstrncasecmp(tag, "setdebug", MAX(tag_len, 2))) {
		_setdebug(argc, argv);
	} else if (!xstrncasecmp(tag, "schedloglevel", MAX(tag_len, 3))) {
		if (argc > 2) {
			_printf_error("too many arguments for keyword:%s", tag);
		} else if (argc < 2) {
			_printf_error("too few arguments for keyword:%s", tag);
		} else {
			int level = -1;
			char *endptr;
			char *levels[] = {
				"disable", "enable", NULL};
			int index = 0;
			while (levels[index]) {
				if (xstrcasecmp(argv[1], levels[index]) == 0) {
					level = index;
					break;
				}
				index ++;
			}
			if (level == -1) {
				/* effective levels: 0 - 1 */
				level = (int)strtoul (argv[1], &endptr, 10);
				if (*endptr != '\0' || level > 1) {
					level = -1;
					_printf_error("invalid schedlog level: %s",
						      argv[1]);
				}
			}
			if (level != -1) {
				error_code = slurm_set_schedlog_level(
					level);
				if (error_code)
					_printf_error("slurm_set_schedlog_level error");
			}
		}
	} else if (!xstrncasecmp(tag, "show", MAX(tag_len, 3))) {
		_show_it (argc, argv);
	} else if (!xstrncasecmp(tag, "write", MAX(tag_len, 5))) {
		if (argc < 2) {
			exit_code = 1;
			fprintf(stderr,
				"too few arguments for keyword:%s\n",
				tag);
		} else if (!xstrncasecmp(argv[1], "batch_script",
					 MAX(strlen(argv[1]), 5))) {
			/* write batch_script <jobid> <optional filename> */
			if (argc > 4) {
				exit_code = 1;
				fprintf(stderr,
					"too many arguments for keyword:%s\n",
					tag);
			} else {
				scontrol_batch_script(argc-2, &argv[2]);
			}
		} else if (!xstrncasecmp(argv[1], "config",
					 MAX(strlen(argv[1]), 6))) {
			/* write config */
			if (argc > 3) {
				exit_code = 1;
				fprintf(stderr,
					"too many arguments for keyword:%s\n",
					tag);
			} else {
				_write_config(argv[2]);
			}
		} else {
			exit_code = 1;
			fprintf(stderr,
				"invalid write argument:%s\n",
				argv[1]);
		}
	} else if (!xstrncasecmp(tag, "takeover", MAX(tag_len, 8))) {
		int backup_inx = 1, control_cnt;
		slurm_ctl_conf_info_msg_t  *slurm_ctl_conf_ptr = NULL;

		slurm_ctl_conf_ptr = slurm_conf_lock();
		control_cnt = slurm_ctl_conf_ptr->control_cnt;
		slurm_conf_unlock();
		if (argc > 2) {
			exit_code = 1;
			fprintf(stderr, "%s: too many arguments\n",
				tag);
			backup_inx = -1;
		} else if (argc == 2) {
			backup_inx = atoi(argv[1]);
			if ((backup_inx < 1) || (backup_inx >= control_cnt)) {
				exit_code = 1;
				fprintf(stderr,
					"%s: invalid backup controller index (%d)\n",
					tag, backup_inx);
				backup_inx = -1;
			}
		} else if (control_cnt < 1) {
			exit_code = 1;
			fprintf(stderr, "%s: no backup controller defined\n",
				tag);
			backup_inx = -1;
		}

		if (backup_inx != -1) {
			error_code = slurm_takeover(backup_inx);
			if (error_code) {
				exit_code = 1;
				if (quiet_flag != 1)
					slurm_perror("slurm_takeover error");
			}
		}
	} else if (!xstrncasecmp(tag, "shutdown", MAX(tag_len, 8))) {
		/* require full command name */
		uint16_t options = SLURMCTLD_SHUTDOWN_ALL;
		if (argc == 2) {
			if (xstrcmp(argv[1], "slurmctld") &&
			    xstrcmp(argv[1], "controller")) {
				error_code = 1;
				exit_code = 1;
				fprintf (stderr,
					 "invalid shutdown argument:%s\n",
					 argv[1]);
			} else
				options= SLURMCTLD_SHUTDOWN_CTLD;
		} else if (argc > 2) {
			error_code = 1;
			exit_code = 1;
			fprintf (stderr,
				 "too many arguments for keyword:%s\n",
				 tag);
		}
		if (error_code == 0) {
			error_code = slurm_shutdown(options);
			if (error_code)
				_printf_error("slurm_shutdown error");
		}
	} else if (!xstrncasecmp(tag, "update", MAX(tag_len, 1))) {
		if (argc < 2) {
			exit_code = 1;
			fprintf (stderr, "too few arguments for %s keyword\n",
				 tag);
			return 0;
		}
		_update_it ((argc - 1), &argv[1]);
	} else if (!xstrncasecmp(tag, "delete", MAX(tag_len, 1))) {
		if (argc < 2) {
			exit_code = 1;
			fprintf (stderr, "too few arguments for %s keyword\n",
				 tag);
			return 0;
		}
		_delete_it ((argc - 1), &argv[1]);
	} else if (!xstrncasecmp(tag, "verbose", MAX(tag_len, 4))) {
		if (argc > 1) {
			exit_code = 1;
			fprintf (stderr,
				 "too many arguments for %s keyword\n",
				 tag);
		}
		quiet_flag = -1;
	} else if (!xstrncasecmp(tag, "version", MAX(tag_len, 4))) {
		if (argc > 1) {
			exit_code = 1;
			fprintf (stderr,
				 "too many arguments for %s keyword\n",
				 tag);
		}
		_print_version();
	} else if (!xstrncasecmp(tag, "listpids", MAX(tag_len, 1))) {
		if (argc > 3) {
			exit_code = 1;
			fprintf (stderr,
				 "too many arguments for keyword:%s\n",
				 tag);
		} else {
			scontrol_list_pids(argc, argv);
		}
	} else if (!xstrncasecmp(tag, "listjobs", MAX(tag_len, 1))) {
		if (argc > 2) {
			exit_code = 1;
			fprintf(stderr,
				"too many arguments for keyword:%s\n",
				tag);
		} else {
			scontrol_list_jobs(argc, argv);
		}
	} else if (!xstrncasecmp(tag, "liststeps", MAX(tag_len, 1))) {
		if (argc > 2) {
			exit_code = 1;
			fprintf(stderr,
				"too many arguments for keyword:%s\n",
				tag);
		} else {
			scontrol_list_steps(argc, argv);
		}
	} else if (!xstrncasecmp(tag, "getent", MAX(tag_len, 6))) {
		scontrol_getent(argc == 1 ? NULL : argv[1]);
	} else if (!xstrncasecmp(tag, "notify", MAX(tag_len, 1))) {
		if (argc < 3) {
			exit_code = 1;
			fprintf (stderr,
				 "too few arguments for keyword:%s\n",
				 tag);
		} else if (scontrol_job_notify(argc-1, &argv[1])) {
			exit_code = 1;
			slurm_perror("job notify failure");
		}
	} else if (!xstrncasecmp(tag, "callerid", MAX(tag_len, 3))) {
		if (argc < 5) {
			exit_code = 1;
			fprintf (stderr,
				 "too few arguments for keyword:%s\n",
				 tag);
		} else if (argc > 6) {
			exit_code = 1;
			fprintf (stderr,
				 "too many arguments for keyword:%s\n",
				 tag);
		} else if (scontrol_callerid(argc-1, &argv[1])) {
			exit_code = 1;
			slurm_perror("callerid failure");
		}
	} else {
		exit_code = 1;
		fprintf (stderr, "invalid keyword: %s\n", tag);
	}

	return 0;
}

/*
 * _create_it - create a slurm configuration per the supplied arguments
 * IN argc - count of arguments
 * IN argv - list of arguments
 */
static void _create_it(int argc, char **argv)
{
	/* Scan for "res" first, anywhere in the args.  When creating
	   a reservation there is a partition= option, which we don't
	   want to mistake for a requestion to create a partition. */
	int i, error_code = SLURM_SUCCESS;
	for (i=0; i<argc; i++) {
		char *tag = argv[i];
		char *val = strchr(argv[i], '=');
		int tag_len;

		if (val) {
			tag_len = val - argv[i];
			val++;
		} else {
			tag_len = strlen(tag);
		}
		if (!xstrncasecmp(tag, "NodeName", MAX(tag_len, 3))) {
			error_code = scontrol_create_node(argc, argv);
			break;
		} else if (!xstrncasecmp(tag, "PartitionName",
					 MAX(tag_len, 3))) {
			error_code = scontrol_create_part(argc, argv);
			break;
		} else if (!xstrncasecmp(tag, "ReservationName",
					 MAX(tag_len, 3))) {
			error_code = scontrol_create_res(argc, argv);
			break;
		}
	}

	if (i >= argc) {
		exit_code = 1;
		error("Invalid creation entity: %s", argv[0]);
	} else if (error_code)
		exit_code = 1;
}




/*
 * _delete_it - delete the specified slurm entity
 * IN argc - count of arguments
 * IN argv - list of arguments
 */
static void _delete_it(int argc, char **argv)
{
	char *tag = NULL, *val = NULL;
	int tag_len = 0;


	if (argc == 1) {
		tag = argv[0];
		val = strchr(argv[0], '=');
		if (val) {
			tag_len = val - argv[0];
			val++;
		} else {
			error("Proper format is 'delete <ENTITY>=<ID>' or 'delete <ENTITY> <ID>'");
			exit_code = 1;
			return;
		}
	} else if (argc == 2) {
		tag = argv[0];
		tag_len = strlen(argv[0]);
		val = argv[1];
	} else {
		error("Proper format is 'delete <ENTITY>=<ID>' or 'delete <ENTITY> <ID>'");
		exit_code = 1;
		return;
	}

	/* First identify the entity type to delete */
	if (xstrncasecmp(tag, "NodeName", MAX(tag_len, 3)) == 0) {
		update_node_msg_t node_msg;
		slurm_init_update_node_msg(&node_msg);
		node_msg.node_names = val;
		if (slurm_delete_node(&node_msg)) {
			char errmsg[64];
			snprintf(errmsg, 64, "delete_node %s", argv[0]);
			slurm_perror(errmsg);
			exit_code = 1;
		}
	} else if (xstrncasecmp(tag, "PartitionName", MAX(tag_len, 3)) == 0) {
		delete_part_msg_t part_msg;
		memset(&part_msg, 0, sizeof(part_msg));
		part_msg.name = val;
		if (slurm_delete_partition(&part_msg)) {
			char errmsg[64];
			snprintf(errmsg, 64, "delete_partition %s", argv[0]);
			slurm_perror(errmsg);
			exit_code = 1;
		}
	} else if (xstrncasecmp(tag, "ReservationName", MAX(tag_len, 3)) == 0) {
		reservation_name_msg_t   res_msg;
		memset(&res_msg, 0, sizeof(res_msg));
		res_msg.name = val;
		if (slurm_delete_reservation(&res_msg)) {
			char errmsg[64];
			snprintf(errmsg, 64, "delete_reservation %s", argv[0]);
			slurm_perror(errmsg);
			exit_code = 1;
		}
	} else {
		exit_code = 1;
		fprintf(stderr, "Invalid deletion entity: %s\n", argv[0]);
	}
}


/*
 * _show_it - print a description of the specified slurm entity
 * IN argc - count of arguments
 * IN argv - list of arguments
 */
static void _show_it(int argc, char **argv)
{
	char *tag = NULL, *val = NULL;
	int tag_len = 0;
	bool allow_opt = false;

	if (argc < 2) {
		_printf_error("too few arguments for keyword:%s", argv[0]);
		return;
	}

	if (!xstrncasecmp(argv[1], "assoc_mgr", MAX(tag_len, 2)) ||
	    !xstrncasecmp(argv[1], "bbstat", MAX(tag_len, 2)) ||
	    !xstrncasecmp(argv[1], "dwstat", MAX(tag_len, 2)) ||
	    !xstrncasecmp(argv[1], "topology", MAX(tag_len, 4)))
		allow_opt = true;

	if ((argc > 3) && !allow_opt) {
		_printf_error("too many arguments for keyword:%s", argv[0]);
		return;
	}

	tag = argv[1];
	tag_len = strlen(tag);
	val = strchr(argv[1], '=');
	if (val) {
		tag_len = val - argv[1];
		val++;
	} else if (argc == 3) {
		val = argv[2];
	} else {
		val = NULL;
	}

	if (xstrncasecmp(tag, "aliases", MAX(tag_len, 1)) == 0) {
		if (val)
			_print_aliases (val);
		else
			_print_aliases (NULL);
	} else if (!xstrncasecmp(tag, "bbstat", MAX(tag_len, 2)) ||
		   !xstrncasecmp(tag, "dwstat", MAX(tag_len, 2))) {
		scontrol_print_bbstat(argc - 2, argv + 2);
	} else if (xstrncasecmp(tag, "burstbuffer", MAX(tag_len, 2)) == 0) {
		scontrol_print_burst_buffer ();
	} else if (!xstrncasecmp(tag, "assoc_mgr", MAX(tag_len, 2)) ||
		   !xstrncasecmp(tag, "cache", MAX(tag_len, 2))) {
		scontrol_print_assoc_mgr_info(argc - 2, argv + 2);
	} else if (xstrncasecmp(tag, "config", MAX(tag_len, 1)) == 0) {
		_print_config(val, argc, argv);
	} else if (xstrncasecmp(tag, "daemons", MAX(tag_len, 1)) == 0) {
		if (val) {
			_printf_error("too many arguments for keyword:%s",
				      argv[0]);
		} else
			_print_daemons();
	} else if (xstrncasecmp(tag, "Federations",  MAX(tag_len, 1)) == 0) {
		scontrol_print_federation();
	} else if (xstrncasecmp(tag, "hostnames", MAX(tag_len, 5)) == 0) {
		if (val)
			scontrol_print_hosts(val);
		else
			scontrol_print_hosts(getenv("SLURM_NODELIST"));
	} else if (xstrncasecmp(tag, "hostlist", MAX(tag_len, 5)) == 0) {
		if (!val) {
			exit_code = 1;
			fprintf(stderr, "invalid encode argument\n");
			_usage();
		} else if (scontrol_encode_hostlist(val, 0))
			exit_code = 1;
	} else if (xstrncasecmp(tag, "hostlistsorted", MAX(tag_len, 9)) == 0) {
		if (!val) {
			exit_code = 1;
			fprintf(stderr, "invalid encode argument\n");
			_usage();
		} else if (scontrol_encode_hostlist(val, 1))
			exit_code = 1;
	} else if (xstrncasecmp(tag, "jobs", MAX(tag_len, 1)) == 0 ||
		   xstrncasecmp(tag, "jobid", MAX(tag_len, 1)) == 0 ) {
		scontrol_print_job(val, argc, argv);
	} else if (xstrncasecmp(tag, "licenses", MAX(tag_len, 2)) == 0) {
		scontrol_print_licenses(val, argc, argv);
	} else if (xstrncasecmp(tag, "nodes", MAX(tag_len, 1)) == 0) {
		scontrol_print_node_list(val, argc, argv);
	} else if (xstrncasecmp(tag, "partitions", MAX(tag_len, 2)) == 0 ||
		   xstrncasecmp(tag, "partitionname", MAX(tag_len, 2)) == 0) {
		scontrol_print_part(val, argc, argv);
	} else if (xstrncasecmp(tag, "reservations", MAX(tag_len, 1)) == 0 ||
		   xstrncasecmp(tag, "reservationname", MAX(tag_len, 1)) == 0) {
		scontrol_print_res(val, argc, argv);
	} else if (!xstrncasecmp(tag, "resources", MAX(tag_len, 4))) {
		scontrol_print_resources(argc, argv);
	} else if (xstrncasecmp(tag, "slurmd", MAX(tag_len, 2)) == 0) {
		_print_slurmd (val);
	} else if (xstrncasecmp(tag, "steps", MAX(tag_len, 2)) == 0) {
		scontrol_print_step(val, argc, argv);
	} else if (!xstrncasecmp(tag, "topoconf", MAX(tag_len, 5))) {
		scontrol_print_topo_conf();
	} else if (xstrncasecmp(tag, "topology", MAX(tag_len, 1)) == 0) {
		scontrol_print_topo(argc - 2, argv + 2);
	} else {
		_printf_error("invalid entity:%s for keyword:%s", tag, argv[0]);
	}
}



/*
 * _update_it - update the slurm configuration per the supplied arguments
 * IN argc - count of arguments
 * IN argv - list of arguments
 */
static void _update_it(int argc, char **argv)
{
	char *val = NULL;
	int i, error_code = SLURM_SUCCESS;
	int node_tag = 0, part_tag = 0, job_tag = 0;
	int res_tag = 0;
	int debug_tag = 0, step_tag = 0;
	int suspend_exc_nodes_tag = 0, suspend_exc_parts_tag = 0,
	    suspend_exc_states_tag = 0;
	int jerror_code = SLURM_SUCCESS;
	update_mode_t mode = UPDATE_SET;

	/* First identify the entity to update */
	for (i=0; i<argc; i++) {
		char *tag = argv[i];
		int tag_len = 0;
		val = strchr(argv[i], '=');
		if (!val){
			tag = argv[i];
			tag_len = strlen(tag);
			++i;
		} else {
			tag_len = val - argv[i];
			if (*(val - 1) == '+'){
				mode = UPDATE_ADD;
				tag_len--;
			} else if (*(val - 1) == '-') {
				mode = UPDATE_REMOVE;
				tag_len--;
			}
			val++;
		}
		if (!xstrncasecmp(tag, "NodeName", MAX(tag_len, 3))) {
			node_tag = 1;
		} else if (!xstrncasecmp(tag, "PartitionName",
					MAX(tag_len, 3))) {
			part_tag = 1;
		} else if (!xstrncasecmp(tag, "JobId", MAX(tag_len, 3)) ||
			   !xstrncasecmp(tag, "JobNAME", MAX(tag_len, 3))) {
			job_tag = 1;
		} else if (!xstrncasecmp(tag, "StepId", MAX(tag_len, 4))) {
			step_tag = 1;
		} else if (!xstrncasecmp(tag, "ReservationName",
					 MAX(tag_len, 3))) {
			res_tag = 1;
		} else if (!xstrncasecmp(tag, "SlurmctldDebug",
					 MAX(tag_len, 2))) {
			debug_tag = 1;
		} else if (!xstrncasecmp(tag, "SuspendExcNodes",
					 MAX(tag_len, 11))) {
			suspend_exc_nodes_tag = 1;
		} else if (!xstrncasecmp(tag, "SuspendExcParts",
					 MAX(tag_len, 11))) {
			suspend_exc_parts_tag = 1;
		} else if (!xstrncasecmp(tag, "SuspendExcStates",
					 MAX(tag_len, 11))) {
			suspend_exc_states_tag = 1;
		}
	}
	/* The order of tests matters here.  An update job request can include
	 * partition and reservation tags, possibly before the jobid tag, but
	 * none of the other updates have a jobid tag, so check jobtag first.
	 * Likewise, check restag next, because reservations can have a
	 * partition tag.  The order of the rest doesn't matter because there
	 * aren't any other duplicate tags.  */

	if (job_tag)
		jerror_code = scontrol_update_job (argc, argv);
	else if (step_tag)
		error_code = scontrol_update_step (argc, argv);
	else if (res_tag)
		error_code = scontrol_update_res (argc, argv);
	else if (node_tag)
		error_code = scontrol_update_node (argc, argv);
	else if (part_tag)
		error_code = scontrol_update_part (argc, argv);
	else if (debug_tag)
		error_code = _update_slurmctld_debug(val);
	else if (suspend_exc_nodes_tag)
		error_code = slurm_update_suspend_exc_nodes(val, mode);
	else if (suspend_exc_parts_tag)
		error_code = slurm_update_suspend_exc_parts(val, mode);
	else if (suspend_exc_states_tag)
		error_code = slurm_update_suspend_exc_states(val, mode);
	else {
		exit_code = 1;
		fprintf(stderr, "No valid entity in update command\n");
		fprintf(stderr, "Input line must include \"NodeName\", ");
		fprintf(stderr, "\"PartitionName\", \"Reservation\", "
			"\"JobId\", or \"SlurmctldDebug\"\n");
	}

	if (error_code) {
		exit_code = 1;
		if (errno)
			slurm_perror ("slurm_update error");
	}
	/* The slurm error message is already
	 * printed for each array task in
	 * scontrol_update_job()
	 */
	if (jerror_code)
		exit_code = 1;
}

/*
 * _update_slurmctld_debug - update the slurmctld debug level
 * IN  val - new value
 * RET 0 if no slurm error, errno otherwise. parsing error prints
 *			error message and returns 0
 */
static int _update_slurmctld_debug(char *val)
{
	char *endptr = NULL;
	int error_code = SLURM_SUCCESS;
	uint32_t level;

	if (val)
		level = (uint32_t)strtoul(val, &endptr, 10);

	if ((val == NULL) || (*endptr != '\0') || (level > 9)) {
		_printf_error("invalid debug level: %s", val);
	} else {
		error_code = slurm_set_debug_level(level);
	}

	return error_code;
}

/* _usage - show the valid scontrol commands */
void _usage(void)
{
	char *txt;
	static_ref_to_cstring(txt, usage_txt);
	printf("%s\n", txt);
	xfree(txt);
}
