/****************************************************************************\
 *  opts.c - sinfo command line option processing functions
 *****************************************************************************
 *  Copyright (C) 2002-2007 The Regents of the University of California.
 *  Copyright (C) 2008-2010 Lawrence Livermore National Security.
 *  Portions Copyright (C) 2010-2017 SchedMD <https://www.schedmd.com>.
 *  Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER).
 *  Written by Joey Ekstrom <ekstrom1@llnl.gov>, 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.
\*****************************************************************************/

#define _GNU_SOURCE

#include <getopt.h>
#include <stdlib.h>
#include <unistd.h>

#include "slurm/slurmdb.h"
#include "src/common/read_config.h"
#include "src/common/xstring.h"
#include "src/common/proc_args.h"

#include "src/sinfo/print.h"
#include "src/sinfo/sinfo.h"

/* getopt_long options, integers but not characters */
#define OPT_LONG_HELP      0x100
#define OPT_LONG_USAGE     0x101
#define OPT_LONG_HIDE      0x102
#define OPT_LONG_LOCAL     0x103
#define OPT_LONG_NOCONVERT 0x104
#define OPT_LONG_FEDR      0x105

/* FUNCTIONS */
static List  _build_state_list( char* str );
static List  _build_all_states_list( void );
static List  _build_part_list( char *parts );
static char *_get_prefix(char *token);
static void  _help( void );
static int   _parse_format(char *);
static int   _parse_long_format(char *);
static bool  _node_state_equal(int state_id, const char *state_str);
static int   _node_state_id(char *str);
static const char * _node_state_list(void);
static void  _parse_token(char *token, char *field, int *field_size,
			  bool *right_justify, char **suffix);
static void  _parse_long_token(char *token, char *sep, int *field_size,
			       bool *right_justify, char **suffix);
static void  _print_options(void);
static void  _usage(void);

/*
 * parse_command_line, fill in params data structure with data
 */
extern void parse_command_line(int argc, char **argv)
{
	char *env_val = NULL;
	int opt_char;
	int option_index;
	hostlist_t host_list;
	bool long_form = false;
	bool opt_a_set = false, opt_p_set = false;
	bool env_a_set = false, env_p_set = false;
	static struct option long_options[] = {
		{"all",       no_argument,       0, 'a'},
		{"dead",      no_argument,       0, 'd'},
		{"exact",     no_argument,       0, 'e'},
		{"federation",no_argument,       0, OPT_LONG_FEDR},
		{"help",      no_argument,       0, OPT_LONG_HELP},
		{"hide",      no_argument,       0, OPT_LONG_HIDE},
		{"iterate",   required_argument, 0, 'i'},
		{"local",     no_argument,       0, OPT_LONG_LOCAL},
		{"long",      no_argument,       0, 'l'},
		{"cluster",   required_argument, 0, 'M'},
		{"clusters",  required_argument, 0, 'M'},
		{"nodes",     required_argument, 0, 'n'},
		{"noconvert", no_argument,       0, OPT_LONG_NOCONVERT},
		{"noheader",  no_argument,       0, 'h'},
		{"Node",      no_argument,       0, 'N'},
		{"format",    required_argument, 0, 'o'},
		{"Format",    required_argument, 0, 'O'},
		{"partition", required_argument, 0, 'p'},
		{"responding",no_argument,       0, 'r'},
		{"list-reasons", no_argument,    0, 'R'},
		{"summarize", no_argument,       0, 's'},
		{"sort",      required_argument, 0, 'S'},
		{"states",    required_argument, 0, 't'},
		{"reservation",no_argument,      0, 'T'},
		{"usage",     no_argument,       0, OPT_LONG_USAGE},
		{"verbose",   no_argument,       0, 'v'},
		{"version",   no_argument,       0, 'V'},
		{NULL,        0,                 0, 0}
	};

	params.convert_flags = CONVERT_NUM_UNIT_EXACT;

	if (slurmctld_conf.fed_params &&
	    strstr(slurmctld_conf.fed_params, "fed_display"))
		params.federation_flag = true;

	if (getenv("SINFO_ALL")) {
		env_a_set = true;
		params.all_flag = true;
	}
	if (getenv("SINFO_FEDERATION"))
		params.federation_flag = true;
	if (getenv("SINFO_LOCAL"))
		params.local = true;
	if ( ( env_val = getenv("SINFO_PARTITION") ) ) {
		env_p_set = true;
		params.partition = xstrdup(env_val);
		params.part_list = _build_part_list(env_val);
		params.all_flag = true;
	}
	if (env_a_set && env_p_set) {
		error("Conflicting options, SINFO_ALL and SINFO_PARTITION, specified. "
		      "Please choose one or the other.");
		exit(1);
	}
	if ( ( env_val = getenv("SINFO_SORT") ) )
		params.sort = xstrdup(env_val);
	if ( ( env_val = getenv("SLURM_CLUSTERS") ) ) {
		if (!(params.clusters = slurmdb_get_info_cluster(env_val))) {
			print_db_notok(env_val, 1);
			exit(1);
		}
		working_cluster_rec = list_peek(params.clusters);
		params.local = true;
	}

	while ((opt_char = getopt_long(argc, argv,
				       "adehi:lM:n:No:O:p:rRsS:t:TvV",
				       long_options, &option_index)) != -1) {
		switch (opt_char) {
		case (int)'?':
			fprintf(stderr,
				"Try \"sinfo --help\" for more information\n");
			exit(1);
			break;
		case (int)'a':
			opt_a_set = true;
			xfree(params.partition);
			FREE_NULL_LIST(params.part_list);
			params.all_flag = true;
			break;
		case (int)'d':
			params.dead_nodes = true;
			break;
		case (int)'e':
			params.exact_match = true;
			break;
		case (int)'h':
			params.no_header = true;
			break;
		case (int) 'i':
			params.iterate= atoi(optarg);
			if (params.iterate <= 0) {
				error ("Error: invalid entry for "
				       "--iterate=%s", optarg);
				exit(1);
			}
			break;
		case (int) 'l':
			params.long_output = true;
			break;
		case (int) 'M':
			FREE_NULL_LIST(params.clusters);
			if (!(params.clusters =
			      slurmdb_get_info_cluster(optarg))) {
				print_db_notok(optarg, 0);
				exit(1);
			}
			working_cluster_rec = list_peek(params.clusters);
			params.local = true;
			break;
		case OPT_LONG_NOCONVERT:
			params.convert_flags |= CONVERT_NUM_UNIT_NO;
			break;
		case (int) 'n':
			xfree(params.nodes);
			params.nodes = xstrdup(optarg);
			/*
			 * confirm valid nodelist entry
			 */
			host_list = hostlist_create(params.nodes);
			if (!host_list) {
				error("'%s' invalid entry for --nodes",
				      optarg);
				exit(1);
			}
			if (hostlist_count(host_list) == 1) {
				params.node_name_single = true;
				xfree(params.nodes);
				params.nodes =
				    hostlist_deranged_string_xmalloc(host_list);
			} else
				params.node_name_single = false;
			hostlist_destroy(host_list);
			break;
		case (int) 'N':
			params.node_flag = true;
			break;
		case (int) 'o':
			xfree(params.format);
			params.format = xstrdup(optarg);
			break;
		case (int) 'O':
			long_form = true;
			xfree(params.format);
			params.format = xstrdup(optarg);
			break;
		case (int) 'p':
			opt_p_set = true;
			xfree(params.partition);
			FREE_NULL_LIST(params.part_list);
			params.partition = xstrdup(optarg);
			params.part_list = _build_part_list(optarg);
			params.all_flag = true;
			break;
		case (int) 'r':
			params.responding_nodes = true;
			break;
		case (int) 'R':
			params.list_reasons = true;
			break;
		case (int) 's':
			params.summarize = true;
			break;
		case (int) 'S':
			xfree(params.sort);
			params.sort = xstrdup(optarg);
			break;
		case (int) 't':
			xfree(params.states);
			params.states = xstrdup(optarg);
			if (!(params.state_list = _build_state_list(optarg))) {
				error ("valid states: %s", _node_state_list ());
				exit (1);
			}
			break;
		case (int) 'T':
			params.reservation_flag = true;
			break;
		case (int) 'v':
			params.verbose++;
			break;
		case (int) 'V':
			print_slurm_version ();
			exit(0);
		case (int) OPT_LONG_FEDR:
			params.federation_flag = true;
			break;
		case (int) OPT_LONG_HELP:
			_help();
			exit(0);
		case (int) OPT_LONG_USAGE:
			_usage();
			exit(0);
		case OPT_LONG_HIDE:
			params.all_flag = false;
			break;
		case OPT_LONG_LOCAL:
			params.local = true;
			break;
		}
	}

	if (opt_a_set && opt_p_set) {
		error("Conflicting options, -a and -p, specified. "
		      "Please choose one or the other.");
		exit(1);
	}

	params.cluster_flags = slurmdb_setup_cluster_flags();

	if (params.federation_flag && !params.clusters && !params.local) {
		void *ptr = NULL;
		char *cluster_name = slurm_get_cluster_name();
		if (slurm_load_federation(&ptr) ||
		    !cluster_in_federation(ptr, cluster_name)) {
			/* Not in federation */
			params.local = true;
			slurm_destroy_federation_rec(ptr);
		} else {
			params.fed = (slurmdb_federation_rec_t *) ptr;
		}
		xfree(cluster_name);
	}

	if ( params.format == NULL ) {
		if ( params.summarize ) {
			params.part_field_flag = true;	/* compute size later */
			params.format = "%9P %.5a %.10l %.16F  %N";
		} else if ( params.node_flag ) {
			params.node_field_flag = true;	/* compute size later */
			params.part_field_flag = true;	/* compute size later */
			params.format = params.long_output ?
			  "%N %.6D %.9P %.11T %.4c %.8z %.6m %.8d %.6w %.8f %20E" :
			  "%N %.6D %.9P %6t";

		} else if (params.list_reasons) {
			params.format = params.long_output ?
			  "%20E %12U %19H %6t %N" :
			  "%20E %9u %19H %N";

		} else if ((env_val = getenv ("SINFO_FORMAT"))) {
			params.format = xstrdup(env_val);


		} else if (params.fed) {
			params.part_field_flag = true;	/* compute size later */
			params.format = params.long_output ?
			  "%9P %8V %.5a %.10l %.10s %.4r %.8h %.10g %.6D %.11T %N" :
			  "%9P %8V %.5a %.10l %.6D %.6t %N";
		} else {
			params.part_field_flag = true;	/* compute size later */
			params.format = params.long_output ?
			  "%9P %.5a %.10l %.10s %.4r %.8h %.10g %.6D %.11T %N" :
			  "%9P %.5a %.10l %.6D %.6t %N";
		}
	}

	if (long_form)
		_parse_long_format(params.format);
	else
		_parse_format(params.format);

	if (params.list_reasons && (params.state_list == NULL)) {
		params.states = xstrdup("down,fail,drain");
		if (!(params.state_list = _build_state_list (params.states)))
			fatal ("Unable to build state list for -R!");
	}

	if (params.dead_nodes || params.nodes || params.partition ||
			params.responding_nodes ||params.state_list)
		params.filtering = true;

	if (params.verbose)
		_print_options();
}

static char *
_next_tok (char *sep, char **str)
{
	char *tok;

	/* push str past any leading separators */
	while ((**str != '\0') && (strchr(sep, **str) != NULL))
		(*str)++;

	if (**str == '\0')
		return NULL;

	/* assign token ptr */
	tok = *str;

	/* push str past token and leave pointing to first separator */
	while ((**str != '\0') && (strchr(sep, **str) == NULL))
		(*str)++;

	/* nullify consecutive separators and push str beyond them */
	while ((**str != '\0') && (strchr(sep, **str) != NULL))
		*(*str)++ = '\0';

	return (tok);
}

/*
 * _build_state_list - build a list of node states
 * IN str - comma separated list of job states
 * RET List of enum job_states values
 */
static List
_build_state_list (char *state_str)
{
	List state_ids;
	char *orig, *str, *state;

	if (state_str == NULL)
		return NULL;
	if (xstrcasecmp (state_str, "all") == 0 )
		return _build_all_states_list ();

	orig = str = xstrdup (state_str);
	state_ids = list_create (NULL);

	while ((state = _next_tok (",", &str))) {
		int *id = xmalloc (sizeof (*id));
		if ((*id = _node_state_id (state)) < 0) {
			error ("Bad state string: \"%s\"", state);
			return (NULL);
		}
		list_append (state_ids, id);
	}

	xfree (orig);
	return (state_ids);
}

/*
 * _build_all_states_list - build a list containing all possible node states
 * RET List of enum job_states values
 */
static List
_build_all_states_list( void )
{
	List my_list;
	int i;
	uint16_t *state_id;

	my_list = list_create( NULL );
	for (i = 0; i < NODE_STATE_END; i++) {
		state_id = xmalloc( sizeof( uint16_t ) );
		*state_id = (uint16_t) i;
		list_append( my_list, state_id );
	}

	state_id = xmalloc( sizeof( uint16_t ) );
	*state_id = NODE_STATE_DRAIN;
	list_append( my_list, state_id );

	state_id = xmalloc( sizeof( uint16_t ) );
	*state_id = NODE_STATE_COMPLETING;
	list_append( my_list, state_id );

	return my_list;
}

/*
 * _build_part_list - build a list of partition names
 * IN parts - comma separated list of partitions
 * RET List of partition names
 */
static List
_build_part_list(char *parts)
{
	List part_list;
	char *orig, *str, *part;

	orig = str = xstrdup(parts);
	part_list = list_create(NULL);

	while ((part = _next_tok (",", &str))) {
		char *tmp_part = xstrdup(part);
		list_append (part_list, tmp_part);
	}

	xfree(orig);
	return (part_list);
}

static const char *
_node_state_list (void)
{
	int i;
	static char *all_states = NULL;

	if (all_states)
		return (all_states);

	all_states = xstrdup (node_state_string(0));
	for (i = 1; i < NODE_STATE_END; i++) {
		xstrcat (all_states, ",");
		xstrcat (all_states, node_state_string(i));
	}

	xstrcat(all_states,
		",DRAIN,DRAINED,DRAINING,NO_RESPOND,RESERVED,PERFCTRS");
	xstrcat(all_states, ",");
	xstrcat(all_states, node_state_string(NODE_STATE_COMPLETING));
	xstrcat(all_states, ",");
	xstrcat(all_states, node_state_string(NODE_STATE_POWER_SAVE));
	xstrcat(all_states, ",");
	xstrcat(all_states, node_state_string(NODE_STATE_POWER_UP));
	xstrcat(all_states, ",");
	xstrcat(all_states, node_state_string(NODE_STATE_FAIL));
	xstrcat(all_states, ",");
	xstrcat(all_states, node_state_string(NODE_STATE_MAINT));
	xstrcat(all_states, ",");
	xstrcat(all_states, node_state_string(NODE_STATE_REBOOT));

	for (i = 0; i < strlen (all_states); i++)
		all_states[i] = tolower (all_states[i]);

	return (all_states);
}


static bool
_node_state_equal (int i, const char *str)
{
	int len = strlen (str);

	if ((xstrncasecmp(node_state_string_compact(i), str, len) == 0) ||
	    (xstrncasecmp(node_state_string(i),         str, len) == 0))
		return (true);
	return (false);
}

/*
 * _parse_state - convert node state name string to numeric value
 * IN str - state name
 * OUT states - node_state value corresponding to str
 * RET 0 or error code
 */
static int
_node_state_id (char *str)
{
	int i;
	int len = strlen (str);

	for (i = 0; i < NODE_STATE_END; i++) {
		if (_node_state_equal (i, str))
			return (i);
	}

	if (xstrncasecmp("DRAIN", str, len) == 0)
		return NODE_STATE_DRAIN;
	if (xstrncasecmp("DRAINED", str, len) == 0)
		return NODE_STATE_DRAIN | NODE_STATE_IDLE;
	if ((xstrncasecmp("RESV", str, len) == 0) ||
	    (xstrncasecmp("RESERVED", str, len) == 0))
		return NODE_STATE_RES;
	if ((xstrncasecmp("PERFCTRS", str, len) == 0) ||
	    (xstrncasecmp("NPC", str, len) == 0))
		return NODE_STATE_NET;
	if ((xstrncasecmp("DRAINING", str, len) == 0) ||
	    (xstrncasecmp("DRNG", str, len) == 0))
		return NODE_STATE_DRAIN | NODE_STATE_ALLOCATED;
	if (_node_state_equal (NODE_STATE_COMPLETING, str))
		return NODE_STATE_COMPLETING;
	if (xstrncasecmp("NO_RESPOND", str, len) == 0)
		return NODE_STATE_NO_RESPOND;
	if (_node_state_equal (NODE_STATE_POWER_SAVE, str))
		return NODE_STATE_POWER_SAVE;
	if (_node_state_equal (NODE_STATE_POWER_UP, str))
		return NODE_STATE_POWER_UP;
	if (_node_state_equal (NODE_STATE_FAIL, str))
		return NODE_STATE_FAIL;
	if (_node_state_equal (NODE_STATE_MAINT, str))
		return NODE_STATE_MAINT;
	if (_node_state_equal (NODE_STATE_REBOOT, str))
		return NODE_STATE_REBOOT;

	return (-1);
}

/* Take the user's format specification and use it to build build the
 *	format specifications (internalize it to print.c data structures) */
static int
_parse_format( char* format )
{
	int field_size;
	bool right_justify;
	char *prefix = NULL, *suffix = NULL, *token = NULL;
	char *tmp_char = NULL, *tmp_format = NULL;
	char field[1];
	bool format_all = false;
	int i;

	if (format == NULL) {
		fprintf( stderr, "Format option lacks specification\n" );
		exit( 1 );
	}

	params.format_list = list_create( NULL );
	if ((prefix = _get_prefix(format)))
		format_add_prefix( params.format_list, 0, 0, prefix);

	if (!xstrcasecmp(format, "%all")) {
		xstrfmtcat(tmp_format, "%c%c", '%', 'a');
		for (i = 'b'; i <= 'z'; i++)
			xstrfmtcat(tmp_format, "|%c%c", '%', (char) i);
		for (i = 'A'; i <= 'Z'; i++)
			xstrfmtcat(tmp_format, "|%c%c ", '%', (char) i);
		format_all = true;
	} else {
		tmp_format = xstrdup(format);
	}
	token = strtok_r( tmp_format, "%", &tmp_char);
	if (token && (format[0] != '%'))	/* toss header */
		token = strtok_r( NULL, "%", &tmp_char );
	while (token) {
		_parse_token( token, field, &field_size, &right_justify,
			      &suffix);
		if        (field[0] == 'a') {
			params.match_flags.avail_flag = true;
			format_add_avail( params.format_list,
					field_size,
					right_justify,
					suffix );
		} else if (field[0] == 'A') {
			format_add_nodes_ai( params.format_list,
					field_size,
					right_justify,
					suffix );
		} else if (field[0] == 'b') {
			params.match_flags.features_act_flag = true;
			format_add_features_act( params.format_list,
					field_size,
					right_justify,
					suffix );
		} else if (field[0] == 'B') {
			params.match_flags.max_cpus_per_node_flag = true;
			format_add_max_cpus_per_node( params.format_list,
					     field_size,
					     right_justify,
					     suffix );
		} else if (field[0] == 'c') {
			params.match_flags.cpus_flag = true;
			format_add_cpus( params.format_list,
					field_size,
					right_justify,
					suffix );
		} else if (field[0] == 'C') {
			params.match_flags.cpus_flag = true;
			format_add_cpus_aiot( params.format_list,
					field_size,
					right_justify,
					suffix );
		} else if (field[0] == 'd') {
			params.match_flags.disk_flag = true;
			format_add_disk( params.format_list,
					field_size,
					right_justify,
					suffix );
		} else if (field[0] == 'D') {
			format_add_nodes( params.format_list,
					field_size,
					right_justify,
					suffix );
		} else if (field[0] == 'e') {
			params.match_flags.free_mem_flag = true;
			format_add_free_mem( params.format_list,
					field_size,
					right_justify,
					suffix );
		} else if (field[0] == 'E') {
			params.match_flags.reason_flag = true;
			format_add_reason( params.format_list,
					field_size,
					right_justify,
					suffix );
		} else if (field[0] == 'f') {
			params.match_flags.features_flag = true;
			format_add_features( params.format_list,
					field_size,
					right_justify,
					suffix );
		} else if (field[0] == 'F') {
			format_add_nodes_aiot( params.format_list,
					field_size,
					right_justify,
					suffix );
		} else if (field[0] == 'g') {
			params.match_flags.groups_flag = true;
			format_add_groups( params.format_list,
					field_size,
					right_justify,
					suffix );
		} else if (field[0] == 'G') {
			params.match_flags.gres_flag = true;
			format_add_gres( params.format_list,
					field_size,
					right_justify,
					suffix );
		} else if (field[0] == 'h') {
			params.match_flags.oversubscribe_flag = true;
			format_add_oversubscribe( params.format_list,
						  field_size,
						  right_justify,
						  suffix );
		} else if (field[0] == 'H') {
			params.match_flags.reason_timestamp_flag = true;
			format_add_timestamp( params.format_list,
					      field_size,
					      right_justify,
					      suffix );
		} else if (field[0] == 'I') {
			params.match_flags.priority_job_factor_flag = true;
			format_add_priority_job_factor(params.format_list,
					field_size,
					right_justify,
					suffix);
		} else if (field[0] == 'l') {
			params.match_flags.max_time_flag = true;
			format_add_time( params.format_list,
					field_size,
					right_justify,
					suffix );
		} else if (field[0] == 'L') {
			params.match_flags.default_time_flag = true;
			format_add_default_time( params.format_list,
					field_size,
					right_justify,
					suffix );
		} else if (field[0] == 'm') {
			params.match_flags.memory_flag = true;
			format_add_memory( params.format_list,
					field_size,
					right_justify,
					suffix );
		} else if (field[0] == 'M') {
			params.match_flags.preempt_mode_flag = true;
			format_add_preempt_mode( params.format_list,
					field_size,
					right_justify,
					suffix );
		} else if (field[0] == 'n') {
			params.match_flags.hostnames_flag = true;
			format_add_node_hostnames( params.format_list,
					field_size,
					right_justify,
					suffix );
		} else if (field[0] == 'N') {
			format_add_node_list( params.format_list,
					field_size,
					right_justify,
					suffix );
		} else if (field[0] == 'o') {
			params.match_flags.node_addr_flag = true;
			format_add_node_address( params.format_list,
					field_size,
					right_justify,
					suffix );
		} else if (field[0] == 'O') {
			params.match_flags.cpu_load_flag = true;
			format_add_cpu_load( params.format_list,
					field_size,
					right_justify,
					suffix );
		} else if (field[0] == 'p') {
			params.match_flags.priority_tier_flag = true;
			format_add_priority_tier(params.format_list,
					field_size,
					right_justify,
					suffix);
		} else if (field[0] == 'P') {
			params.match_flags.partition_flag = true;
			format_add_partition( params.format_list,
					field_size,
					right_justify,
					suffix );
		} else if (field[0] == 'r') {
			params.match_flags.root_flag = true;
			format_add_root( params.format_list,
					field_size,
					right_justify,
					suffix );
		} else if (field[0] == 'R') {
			params.match_flags.partition_flag = true;
			format_add_partition_name( params.format_list,
					field_size,
					right_justify,
					suffix );
		} else if (field[0] == 's') {
			params.match_flags.job_size_flag = true;
			format_add_size( params.format_list,
					 field_size,
					 right_justify,
					 suffix );
		} else if (field[0] == 'S') {
			format_add_alloc_nodes( params.format_list,
						field_size,
						right_justify,
						suffix );
		} else if (field[0] == 't') {
			params.match_flags.state_flag = true;
			format_add_state_compact( params.format_list,
						  field_size,
						  right_justify,
						  suffix );
		} else if (field[0] == 'T') {
			params.match_flags.state_flag = true;
			format_add_state_long( params.format_list,
					       field_size,
					       right_justify,
					       suffix );
		} else if (field[0] == 'u') {
			params.match_flags.reason_user_flag = true;
			format_add_user( params.format_list,
					field_size,
					right_justify,
					suffix );
		} else if (field[0] == 'U') {
			params.match_flags.reason_user_flag = true;
			format_add_user_long( params.format_list,
					      field_size,
					      right_justify,
					      suffix );
		} else if (field[0] == 'v') {
			params.match_flags.version_flag = true;
			format_add_version( params.format_list,
					    field_size,
					    right_justify,
					    suffix);
		} else if (field[0] == 'V') {
			format_add_cluster_name(params.format_list,
						field_size,
						right_justify,
						suffix);
		} else if (field[0] == 'w') {
			params.match_flags.weight_flag = true;
			format_add_weight( params.format_list,
					field_size,
					right_justify,
					suffix );
		} else if (field[0] == 'X') {
			params.match_flags.sockets_flag = true;
			format_add_sockets( params.format_list,
					field_size,
					right_justify,
					suffix );
		} else if (field[0] == 'Y') {
			params.match_flags.cores_flag = true;
			format_add_cores( params.format_list,
					field_size,
					right_justify,
					suffix );
		} else if (field[0] == 'Z') {
			params.match_flags.threads_flag = true;
			format_add_threads( params.format_list,
					field_size,
					right_justify,
					suffix );
		} else if (field[0] == 'z') {
			params.match_flags.sct_flag = true;
			format_add_sct( params.format_list,
					field_size,
					right_justify,
					suffix );
		} else if (format_all) {
			;	/* ignore */
		} else {
			prefix = xstrdup("%");
			xstrcat(prefix, token);
			xfree(suffix);
			suffix = prefix;
			format_add_invalid( params.format_list,
					    field_size,
					    right_justify,
					    suffix );
			fprintf(stderr, "Invalid node format specification: %c\n",
				field[0] );
		}
		token = strtok_r( NULL, "%", &tmp_char);
	}

	xfree( tmp_format );
	return SLURM_SUCCESS;
}

static int _parse_long_format (char* format_long)
{
	int field_size;
	bool right_justify, format_all = false;
	char *tmp_format = NULL, *token = NULL, *str_tmp = NULL;
	char *sep = NULL;
	char* suffix = NULL;

	if (format_long == NULL) {
		error("Format long option lacks specification");
		exit( 1 );
	}

	params.format_list = list_create(NULL);
	tmp_format = xstrdup(format_long);
	token = strtok_r(tmp_format, ",",&str_tmp);

	while (token) {
		_parse_long_token( token, sep, &field_size, &right_justify,
				   &suffix);

		if (!xstrcasecmp(token, "all")) {
			_parse_format ("%all");
		} else if (!xstrcasecmp(token, "allocmem")) {
			params.match_flags.alloc_mem_flag = true;
			format_add_alloc_mem( params.format_list,
						field_size,
						right_justify,
						suffix );
		} else if (!xstrcasecmp(token, "allocnodes")) {
			format_add_alloc_nodes( params.format_list,
						field_size,
						right_justify,
						suffix );
		} else if (!xstrcasecmp(token, "available")) {
			params.match_flags.avail_flag = true;
			format_add_avail( params.format_list,
					  field_size,
					  right_justify,
					  suffix );
		} else if (!xstrcasecmp(token, "cluster")) {
			format_add_cluster_name(params.format_list,
						field_size,
						right_justify,
						suffix);
		} else if (!xstrcasecmp(token, "cpus")) {
			params.match_flags.cpus_flag = true;
			format_add_cpus( params.format_list,
					 field_size,
					 right_justify,
					 suffix );
		} else if (!xstrcasecmp(token, "cpusload")) {
			params.match_flags.cpu_load_flag = true;
			format_add_cpu_load( params.format_list,
					     field_size,
					     right_justify,
					     suffix );
		} else if (!xstrcasecmp(token, "freemem")) {
			params.match_flags.free_mem_flag = true;
			format_add_free_mem( params.format_list,
					     field_size,
					     right_justify,
					     suffix );
		} else if (!xstrcasecmp(token, "cpusstate")) {
			params.match_flags.cpus_flag = true;
			format_add_cpus_aiot( params.format_list,
					      field_size,
					      right_justify,
					      suffix );
		} else if (!xstrcasecmp(token, "cores")) {
			params.match_flags.cores_flag = true;
			format_add_cores( params.format_list,
					  field_size,
					  right_justify,
					  suffix );
		} else if (!xstrcasecmp(token, "defaulttime")) {
			params.match_flags.default_time_flag = true;
			format_add_default_time( params.format_list,
						 field_size,
						 right_justify,
						 suffix );
		} else if (!xstrcasecmp(token, "disk")) {
			params.match_flags.disk_flag = true;
			format_add_disk( params.format_list,
					 field_size,
					 right_justify,
					 suffix );
		} else if (!xstrcasecmp(token, "features")) {
			params.match_flags.features_flag = true;
			format_add_features( params.format_list,
					     field_size,
					     right_justify,
					     suffix );
		} else if (!xstrcasecmp(token, "features_act")) {
			params.match_flags.features_act_flag = true;
			format_add_features_act( params.format_list,
					field_size,
					right_justify,
					suffix );
		} else if (!xstrcasecmp(token, "groups")) {
			params.match_flags.groups_flag = true;
			format_add_groups( params.format_list,
					   field_size,
					   right_justify,
					   suffix );
		} else if (!xstrcasecmp(token, "gres")) {
			params.match_flags.gres_flag = true;
			format_add_gres( params.format_list,
					 field_size,
					 right_justify,
					 suffix );
		} else if (!xstrcasecmp(token, "maxcpuspernode")) {
			params.match_flags.max_cpus_per_node_flag = true;
			format_add_max_cpus_per_node( params.format_list,
						      field_size,
						      right_justify,
						      suffix );
		} else if (!xstrcasecmp(token, "memory")) {
			params.match_flags.memory_flag = true;
			format_add_memory( params.format_list,
					   field_size,
					   right_justify,
					   suffix );
		} else if (!xstrcasecmp(token, "nodes")) {
			format_add_nodes( params.format_list,
					  field_size,
					  right_justify,
					  suffix );
		} else if (!xstrcasecmp(token, "nodeaddr")) {
			params.match_flags.node_addr_flag = true;
			format_add_node_address( params.format_list,
						 field_size,
						 right_justify,
						 suffix );
		} else if (!xstrcasecmp(token, "nodeai")) {
			format_add_nodes_ai( params.format_list,
					     field_size,
					     right_justify,
					     suffix );
		} else if (!xstrcasecmp(token, "nodeaiot")) {
			format_add_nodes_aiot( params.format_list,
					       field_size,
					       right_justify,
					       suffix );
		} else if (!xstrcasecmp(token, "nodehost")) {
			params.match_flags.hostnames_flag = true;
			format_add_node_hostnames( params.format_list,
						   field_size,
						   right_justify,
						   suffix );
		} else if (!xstrcasecmp(token, "nodelist")) {
			format_add_node_list( params.format_list,
					      field_size,
					      right_justify,
					      suffix );
		} else if (!xstrcasecmp(token, "partition")) {
			params.match_flags.partition_flag = true;
			format_add_partition( params.format_list,
					      field_size,
					      right_justify,
					      suffix );
		} else if (!xstrcasecmp(token, "partitionname")) {
			params.match_flags.partition_flag = true;
			format_add_partition_name( params.format_list,
						   field_size,
						   right_justify,
						   suffix );
		} else if (!xstrcasecmp(token, "port")) {
			params.match_flags.port_flag = true;
			format_add_port( params.format_list,
					 field_size,
					 right_justify,
					 suffix );
		} else if (!xstrcasecmp(token, "preemptmode")) {
			params.match_flags.preempt_mode_flag = true;
			format_add_preempt_mode( params.format_list,
						 field_size,
						 right_justify,
						 suffix );
		} else if (!xstrcasecmp(token, "priorityjobfactor")) {
			params.match_flags.priority_job_factor_flag = true;
			format_add_priority_job_factor(params.format_list,
						       field_size,
						       right_justify,
						       suffix );
		} else if (!xstrcasecmp(token, "prioritytier")) {
			params.match_flags.priority_tier_flag = true;
			format_add_priority_tier(params.format_list,
						 field_size,
						 right_justify,
						 suffix );
		} else if (!xstrcasecmp(token, "reason")) {
			params.match_flags.reason_flag = true;
			format_add_reason( params.format_list,
					   field_size,
					   right_justify,
					   suffix );
		} else if (!xstrcasecmp(token, "root")) {
			params.match_flags.root_flag = true;
			format_add_root( params.format_list,
					 field_size,
					 right_justify,
					 suffix );
		} else if (!xstrcasecmp(token, "oversubscribe") ||
			   !xstrcasecmp(token, "share")) {
			params.match_flags.oversubscribe_flag = true;
			format_add_oversubscribe( params.format_list,
						  field_size,
						  right_justify,
						  suffix );
		} else if (!xstrcasecmp(token, "size")) {
			params.match_flags.job_size_flag = true;
			format_add_size( params.format_list,
					 field_size,
					 right_justify,
					 suffix );
		} else if (!xstrcasecmp(token, "statecompact")) {
			params.match_flags.state_flag = true;
			format_add_state_compact( params.format_list,
						  field_size,
						  right_justify,
						  suffix );
		} else if (!xstrcasecmp(token, "statelong")) {
			params.match_flags.state_flag = true;
			format_add_state_long( params.format_list,
					       field_size,
					       right_justify,
					       suffix );
		} else if (!xstrcasecmp(token, "sockets")) {
			params.match_flags.sockets_flag = true;
			format_add_sockets( params.format_list,
					    field_size,
					    right_justify,
					    suffix );
		} else if (!xstrcasecmp(token, "socketcorethread")) {
			params.match_flags.sct_flag = true;
			format_add_sct( params.format_list,
					field_size,
					right_justify,
					suffix );
		} else if (!xstrcasecmp(token, "time")) {
			params.match_flags.max_time_flag = true;
			format_add_time( params.format_list,
					 field_size,
					 right_justify,
					 suffix );
		} else if (!xstrcasecmp(token, "timestamp")) {
			params.match_flags.reason_timestamp_flag = true;
			format_add_timestamp( params.format_list,
					      field_size,
					      right_justify,
					      suffix );
		} else if (!xstrcasecmp(token, "threads")) {
			params.match_flags.threads_flag = true;
			format_add_threads( params.format_list,
					    field_size,
					    right_justify,
					    suffix );
		} else if (!xstrcasecmp(token, "user")) {
			params.match_flags.reason_user_flag = true;
			format_add_user( params.format_list,
					 field_size,
					 right_justify,
					 suffix );
		} else if (!xstrcasecmp(token, "userlong")) {
			params.match_flags.reason_user_flag = true;
			format_add_user_long( params.format_list,
					      field_size,
					      right_justify,
					      suffix );
		} else if (!xstrcasecmp(token, "version")) {
			params.match_flags.version_flag = true;
			format_add_version( params.format_list,
					    field_size,
					    right_justify,
					    suffix);
		} else if (!xstrcasecmp(token, "weight")) {
			params.match_flags.weight_flag = true;
			format_add_weight( params.format_list,
					   field_size,
					   right_justify,
					   suffix );
		} else if (format_all) {
			/* ignore */
		} else {
			format_add_invalid( params.format_list,
					    field_size,
					    right_justify,
					    suffix );
			error( "Invalid job format specification: %s",
			       token );
		}
		token = strtok_r(NULL, ",", &str_tmp);
	}
	xfree(tmp_format);
	return SLURM_SUCCESS;
}

/* Take a format specification and copy out it's prefix
 * IN/OUT token - input specification, everything before "%" is removed
 * RET - everything before "%" in the token
 */
static char *
_get_prefix( char *token )
{
	char *pos, *prefix;

	if (token == NULL)
		return NULL;

	pos = strchr(token, (int) '%');
	if (pos == NULL)	/* everything is prefix */
		return xstrdup(token);
	if (pos == token)	/* no prefix */
		return NULL;

	pos[0] = '\0';		/* some prefix */
	prefix = xstrdup(token);
	pos[0] = '%';
	memmove(token, pos, (strlen(pos)+1));
	return prefix;
}

/* Take a format specification and break it into its components
 * IN token - input specification without leading "%", eg. ".5u"
 * OUT field - the letter code for the data type
 * OUT field_size - byte count
 * OUT right_justify - true of field to be right justified
 * OUT suffix - string containing everthing after the field specification
 */
static void
_parse_token( char *token, char *field, int *field_size, bool *right_justify,
	      char **suffix)
{
	int i = 0;

	assert (token != NULL);

	if (token[i] == '.') {
		*right_justify = true;
		i++;
	} else
		*right_justify = false;

	*field_size = 0;
	while ((token[i] >= '0') && (token[i] <= '9'))
		*field_size = (*field_size * 10) + (token[i++] - '0');

	field[0] = token[i++];

	*suffix = xstrdup(&token[i]);
}

static void
_parse_long_token( char *token, char *sep, int *field_size, bool *right_justify,
		   char **suffix)
{
	char *ptr;

	xassert(token);
	ptr = strchr(token, ':');
	if (ptr) {
		ptr[0] = '\0';
		if (ptr[1] == '.') {
			*right_justify = true;
			ptr++;
		} else {
			*right_justify = false;
		}
		*field_size = atoi(ptr + 1);
	} else {
		*right_justify = false;
		*field_size = 20;
	}
}

/* print the parameters specified */
void _print_options( void )
{
	printf("-----------------------------\n");
	printf("dead        = %s\n", params.dead_nodes ? "true" : "false");
	printf("exact       = %d\n", params.exact_match);
	printf("filtering   = %s\n", params.filtering ? "true" : "false");
	printf("format      = %s\n", params.format);
	printf("iterate     = %d\n", params.iterate );
	printf("long        = %s\n", params.long_output ? "true" : "false");
	printf("no_header   = %s\n", params.no_header   ? "true" : "false");
	printf("node_field  = %s\n", params.node_field_flag ?
					"true" : "false");
	printf("node_format = %s\n", params.node_flag   ? "true" : "false");
	printf("nodes       = %s\n", params.nodes ? params.nodes : "n/a");
	printf("part_field  = %s\n", params.part_field_flag ?
					"true" : "false");
	printf("partition   = %s\n", params.partition ?
					params.partition: "n/a");
	printf("responding  = %s\n", params.responding_nodes ?
					"true" : "false");
	printf("states      = %s\n", params.states);
	printf("sort        = %s\n", params.sort);
	printf("summarize   = %s\n", params.summarize   ? "true" : "false");
	printf("verbose     = %d\n", params.verbose);
	printf("-----------------------------\n");
	printf("all_flag        = %s\n", params.all_flag ? "true" : "false");
	printf("alloc_mem_flag  = %s\n", params.match_flags.alloc_mem_flag ?
			"true" : "false");
	printf("avail_flag      = %s\n", params.match_flags.avail_flag ?
			"true" : "false");
	printf("cpus_flag       = %s\n", params.match_flags.cpus_flag ?
			"true" : "false");
	printf("default_time_flag =%s\n", params.match_flags.default_time_flag ?
					"true" : "false");
	printf("disk_flag       = %s\n", params.match_flags.disk_flag ?
			"true" : "false");
	printf("features_flag   = %s\n", params.match_flags.features_flag ?
			"true" : "false");
	printf("features_flag_act = %s\n", params.match_flags.features_act_flag?
			"true" : "false");
	printf("groups_flag     = %s\n", params.match_flags.groups_flag ?
					"true" : "false");
	printf("gres_flag       = %s\n", params.match_flags.gres_flag ?
			"true" : "false");
	printf("job_size_flag   = %s\n", params.match_flags.job_size_flag ?
					"true" : "false");
	printf("max_time_flag   = %s\n", params.match_flags.max_time_flag ?
					"true" : "false");
	printf("memory_flag     = %s\n", params.match_flags.memory_flag ?
			"true" : "false");
	printf("partition_flag  = %s\n", params.match_flags.partition_flag ?
			"true" : "false");
	printf("port_flag       = %s\n", params.match_flags.port_flag ?
			"true" : "false");
	printf("priority_job_factor_flag   = %s\n",
			params.match_flags.priority_job_factor_flag ?
			"true" : "false");
	printf("priority_tier_flag   = %s\n",
			params.match_flags.priority_tier_flag ?
			"true" : "false");
	printf("reason_flag     = %s\n", params.match_flags.reason_flag ?
			"true" : "false");
	printf("reason_timestamp_flag = %s\n",
			params.match_flags.reason_timestamp_flag ?
			"true" : "false");
	printf("reason_user_flag = %s\n",
			params.match_flags.reason_user_flag ?  "true" : "false");
	printf("reservation_flag = %s\n", params.reservation_flag ?
			"true" : "false");
	printf("root_flag       = %s\n", params.match_flags.root_flag ?
			"true" : "false");
	printf("oversubscribe_flag      = %s\n",
			params.match_flags.oversubscribe_flag ?
			"true" : "false");
	printf("state_flag      = %s\n", params.match_flags.state_flag ?
			"true" : "false");
	printf("weight_flag     = %s\n", params.match_flags.weight_flag ?
			"true" : "false");
	printf("-----------------------------\n\n");
}

static void _usage( void )
{
	printf("\
Usage: sinfo [-abdelNRrsTv] [-i seconds] [-t states] [-p partition] [-n nodes]\n\
             [-S fields] [-o format] [-O Format] [--federation] [--local]\n");
}

static void _help( void )
{
	printf ("\
Usage: sinfo [OPTIONS]\n\
  -a, --all                  show all partitions (including hidden and those\n\
			     not accessible)\n\
  -d, --dead                 show only non-responding nodes\n\
  -e, --exact                group nodes only on exact match of configuration\n\
      --federation           Report federated information if a member of one\n\
  -h, --noheader             no headers on output\n\
  --hide                     do not show hidden or non-accessible partitions\n\
  -i, --iterate=seconds      specify an iteration period\n\
      --local                show only local cluster in a federation.\n\
                             Overrides --federation.\n\
  -l, --long                 long output - displays more information\n\
  -M, --clusters=names       clusters to issue commands to. Implies --local.\n\
                             NOTE: SlurmDBD must be up.\n\
  -n, --nodes=NODES          report on specific node(s)\n\
  --noconvert                don't convert units from their original type\n\
			     (e.g. 2048M won't be converted to 2G).\n\
  -N, --Node                 Node-centric format\n\
  -o, --format=format        format specification\n\
  -O, --Format=format        long format specification\n\
  -p, --partition=PARTITION  report on specific partition\n\
  -r, --responding           report only responding nodes\n\
  -R, --list-reasons         list reason nodes are down or drained\n\
  -s, --summarize            report state summary only\n\
  -S, --sort=fields          comma separated list of fields to sort on\n\
  -t, --states=node_state    specify the what states of nodes to view\n\
  -T, --reservation          show only reservation information\n\
  -v, --verbose              verbosity level\n\
  -V, --version              output version information and exit\n\
\nHelp options:\n\
  --help                     show this help message\n\
  --usage                    display brief usage message\n");
}
