/*****************************************************************************\
 *  proc_args.c - helper functions for command argument processing
 *****************************************************************************
 *  Copyright (C) 2007 Hewlett-Packard Development Company, L.P.
 *  Copyright (C) SchedMD LLC.
 *  Written by Christopher Holmes <cholmes@hp.com>, who borrowed heavily
 *  from existing Slurm source code, particularly src/srun/opt.c
 *
 *  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"

#define _GNU_SOURCE

#include <ctype.h>		/* isdigit    */
#include <fcntl.h>
#include <limits.h>
#include <signal.h>
#include <stdarg.h>		/* va_start   */
#include <stdio.h>
#include <stdlib.h>		/* getenv, strtoll */
#include <string.h>		/* strcpy */
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/utsname.h>
#include <unistd.h>

#include "src/interfaces/gres.h"
#include "src/common/list.h"
#include "src/common/log.h"
#include "src/common/proc_args.h"
#include "src/common/parse_time.h"
#include "src/common/slurm_protocol_api.h"
#include "src/interfaces/acct_gather_profile.h"
#include "src/common/xmalloc.h"
#include "src/common/xstring.h"

enum {
	RESV_NEW, /* It is a new reservation */
	RESV_ADD, /* It is a reservation update with += */
	RESV_REM, /* It is an reservation  update with -= */
};

/* print this version of Slurm */
void print_slurm_version(void)
{
	printf("%s %s\n", PACKAGE_NAME, SLURM_VERSION_STRING);
}

/* print the available gres options */
void print_gres_help(void)
{
	char *msg = gres_help_msg();
	printf("%s", msg);
	xfree(msg);
}

void set_distribution(task_dist_states_t distribution, char **dist)
{
	task_dist_states_t dist_flag = 0;

	if (((int)distribution <= 0) || (distribution == SLURM_DIST_UNKNOWN))
		return; /* distribution not set */

	if ((distribution & SLURM_DIST_STATE_BASE) != SLURM_DIST_UNKNOWN)
		*dist = xstrdup(format_task_dist_states(distribution));
	if ((dist_flag = (distribution & SLURM_DIST_STATE_FLAGS))) {
		if (dist_flag == SLURM_DIST_PACK_NODES)
			xstrfmtcat(*dist, "%spack", *dist ? "," : "");
		else if (dist_flag == SLURM_DIST_NO_PACK_NODES)
			xstrfmtcat(*dist, "%snopack", *dist ? "," : "");
		else
			error("%s: Unknown distribution flag value: 0x%x",
			      __func__, dist_flag);
	}
}

/*
 * Get the size of the plane distribution and put it in plane_size.
 *
 * An invalid plane size is zero, negative, or larger than INT_MAX.
 *
 * Return SLURM_DIST_PLANE for a valid plane size, SLURM_DIST_ERROR otherwise.
 */
static task_dist_states_t _parse_plane_dist(const char *tok,
					    uint32_t *plane_size)
{
	task_dist_states_t rc = SLURM_ERROR;
	long tmp_long;
	char *endptr, *plane_size_str;

	/*
	 * Check for plane size given after '=' sign or in SLURM_DIST_PLANESIZE
	 * environment variable.
	 */
	if ((plane_size_str = xstrchr(tok, '=')))
		plane_size_str++;
	else if (!(plane_size_str = getenv("SLURM_DIST_PLANESIZE")))
		goto fini; /* No plane size given */
	else if (*plane_size_str == '\0')
		goto fini; /* No plane size given */

	tmp_long = strtol(plane_size_str, &endptr, 10);
	if ((plane_size_str == endptr) || (*endptr != '\0')) {
		/* No valid digits or there are characters after plane_size */
		goto fini;
	} else if ((tmp_long > INT_MAX) || (tmp_long <= 0) ||
		   ((errno == ERANGE) && (tmp_long == LONG_MAX)))
		goto fini; /* Number is too high/low */
	*plane_size = (uint32_t)tmp_long;
	rc = SLURM_DIST_PLANE;

fini:
	if (rc == SLURM_ERROR)
		error("Invalid plane size or size not specified");

	return rc;
}

static void _parse_dist_flag(char *flag_str, task_dist_states_t *result)
{
	xassert(result);
	if (!result)
		return;

	if (!*result) {
		*result = SLURM_ERROR;
		return;
	}

	if (xstrcasecmp(flag_str, "nopack") == 0)
		*result |= SLURM_DIST_NO_PACK_NODES;
	else if (xstrcasecmp(flag_str, "pack") == 0)
		*result |= SLURM_DIST_PACK_NODES;
	else
		*result = SLURM_ERROR;
}

static task_dist_states_t _parse_dist_base(const char *str)
{
	task_dist_states_t result = SLURM_DIST_UNKNOWN;
	int i;
	char *tmp, *dist_base, *flag_str = NULL, *outstr = NULL;
	char *token, *save_ptr = NULL;
	char *sock_dist = NULL;

	if (!str || !*str || (*str == ',')) {
		return SLURM_ERROR;
	}

	tmp = xstrdup(str);
	if (!(dist_base = strtok_r(tmp, ",", &flag_str))) {
		xfree(tmp);
		return SLURM_ERROR;
	}

	i = 0;
	token = strtok_r(dist_base, ":", &save_ptr);
	while (token) {
		if (i > 2)
			return SLURM_ERROR;
		if (i > 0)
			xstrcat(outstr, ":");

		if (!xstrcmp(token, "*")) {
			switch (i) {
			case 0:
				/* default node distribution is block */
				xstrcat(outstr, "block");
				break;
			case 1:
				/* default socket distribution is cyclic */
				sock_dist = "cyclic";
				xstrcat(outstr, sock_dist);
				break;
			case 2:
				/* default core dist is inherited socket dist */
				xstrcat(outstr, sock_dist);
				break;
			}
		} else {
			xstrcat(outstr, token);
			if (i == 1)
				sock_dist = token;
		}

		token = strtok_r(NULL, ":", &save_ptr);
		i++;
	}

	if (xstrcasecmp(outstr, "cyclic") == 0) {
		result = SLURM_DIST_CYCLIC;
	} else if (xstrcasecmp(outstr, "block") == 0) {
		result = SLURM_DIST_BLOCK;
	} else if ((xstrcasecmp(outstr, "arbitrary") == 0) ||
		   (xstrcasecmp(outstr, "hostfile") == 0)) {
		result = SLURM_DIST_ARBITRARY;
	} else if (xstrcasecmp(outstr, "cyclic:cyclic") == 0) {
		result = SLURM_DIST_CYCLIC_CYCLIC;
	} else if (xstrcasecmp(outstr, "cyclic:block") == 0) {
		result = SLURM_DIST_CYCLIC_BLOCK;
	} else if (xstrcasecmp(outstr, "block:block") == 0) {
		result = SLURM_DIST_BLOCK_BLOCK;
	} else if (xstrcasecmp(outstr, "block:cyclic") == 0) {
		result = SLURM_DIST_BLOCK_CYCLIC;
	} else if (xstrcasecmp(outstr, "block:fcyclic") == 0) {
		result = SLURM_DIST_BLOCK_CFULL;
	} else if (xstrcasecmp(outstr, "cyclic:fcyclic") == 0) {
		result = SLURM_DIST_CYCLIC_CFULL;
	} else if (xstrcasecmp(outstr, "cyclic:cyclic:cyclic") == 0) {
		result = SLURM_DIST_CYCLIC_CYCLIC_CYCLIC;
	} else if (xstrcasecmp(outstr, "cyclic:cyclic:block") == 0) {
		result = SLURM_DIST_CYCLIC_CYCLIC_BLOCK;
	} else if (xstrcasecmp(outstr, "cyclic:cyclic:fcyclic") == 0) {
		result = SLURM_DIST_CYCLIC_CYCLIC_CFULL;
	} else if (xstrcasecmp(outstr, "cyclic:block:cyclic") == 0) {
		result = SLURM_DIST_CYCLIC_BLOCK_CYCLIC;
	} else if (xstrcasecmp(outstr, "cyclic:block:block") == 0) {
		result = SLURM_DIST_CYCLIC_BLOCK_BLOCK;
	} else if (xstrcasecmp(outstr, "cyclic:block:fcyclic") == 0) {
		result = SLURM_DIST_CYCLIC_BLOCK_CFULL;
	} else if (xstrcasecmp(outstr, "cyclic:fcyclic:cyclic") == 0) {
		result = SLURM_DIST_CYCLIC_CFULL_CYCLIC;
	} else if (xstrcasecmp(outstr, "cyclic:fcyclic:block") == 0) {
		result = SLURM_DIST_CYCLIC_CFULL_BLOCK;
	} else if (xstrcasecmp(outstr, "cyclic:fcyclic:fcyclic") == 0) {
		result = SLURM_DIST_CYCLIC_CFULL_CFULL;
	} else if (xstrcasecmp(outstr, "block:cyclic:cyclic") == 0) {
		result = SLURM_DIST_BLOCK_CYCLIC_CYCLIC;
	} else if (xstrcasecmp(outstr, "block:cyclic:block") == 0) {
		result = SLURM_DIST_BLOCK_CYCLIC_BLOCK;
	} else if (xstrcasecmp(outstr, "block:cyclic:fcyclic") == 0) {
		result = SLURM_DIST_BLOCK_CYCLIC_CFULL;
	} else if (xstrcasecmp(outstr, "block:block:cyclic") == 0) {
		result = SLURM_DIST_BLOCK_BLOCK_CYCLIC;
	} else if (xstrcasecmp(outstr, "block:block:block") == 0) {
		result = SLURM_DIST_BLOCK_BLOCK_BLOCK;
	} else if (xstrcasecmp(outstr, "block:block:fcyclic") == 0) {
		result = SLURM_DIST_BLOCK_BLOCK_CFULL;
	} else if (xstrcasecmp(outstr, "block:fcyclic:cyclic") == 0) {
		result = SLURM_DIST_BLOCK_CFULL_CYCLIC;
	} else if (xstrcasecmp(outstr, "block:fcyclic:block") == 0) {
		result = SLURM_DIST_BLOCK_CFULL_BLOCK;
	} else if (xstrcasecmp(outstr, "block:fcyclic:fcyclic") == 0) {
		result = SLURM_DIST_BLOCK_CFULL_CFULL;
	} else {
		_parse_dist_flag(outstr, &result);
	}

	if ((result != SLURM_ERROR) && flag_str && *flag_str)
		_parse_dist_flag(flag_str, &result);

	xfree(tmp);
	xfree(outstr);

	return result;
}

/*
 * verify that a distribution type in arg is of a known form
 *
 * It is valid to use pack or nopack alone. For example:
 *   srun --distribution=pack
 * In this case, we return SLURM_DIST_UNKNOWN bitwise OR'd with
 * SLURM_DIST_PACK_NODES or SLURM_DIST_NO_PACK_NODES. slurmctld treats
 * SLURM_DIST_UNKNOWN as the default distribution methods and also handles
 * SLURM_DIST_PACK_NODES or SLURM_DIST_NO_PACK_NODES appropriately.
 *
 * Returns the distribution type for a valid argument, SLURM_DIST_UNKNOWN if
 * arg is NULL, or SLURM_ERROR (-1) if arg is invalid.
 */
task_dist_states_t verify_dist_type(const char *arg, uint32_t *plane_size)
{
	task_dist_states_t result = SLURM_DIST_UNKNOWN;

	if (!arg)
		return result;

	if (!xstrncasecmp(arg, "plane", 5)) {
		/*
		 * plane distribution can't be with any other type,
		 * so just parse plane distribution and then break
		 */
		return _parse_plane_dist(arg, plane_size);
	}

	return _parse_dist_base(arg);
}

extern char *format_task_dist_states(task_dist_states_t t)
{
	switch (t & SLURM_DIST_STATE_BASE) {
	case SLURM_DIST_BLOCK:
		return "block";
	case SLURM_DIST_CYCLIC:
		return "cyclic";
	case SLURM_DIST_PLANE:
		return "plane";
	case SLURM_DIST_ARBITRARY:
		return "arbitrary";
	case SLURM_DIST_CYCLIC_CYCLIC:
		return "cyclic:cyclic";
	case SLURM_DIST_CYCLIC_BLOCK:
		return "cyclic:block";
	case SLURM_DIST_CYCLIC_CFULL:
		return "cyclic:fcyclic";
	case SLURM_DIST_BLOCK_CYCLIC:
		return "block:cyclic";
	case SLURM_DIST_BLOCK_BLOCK:
		return "block:block";
	case SLURM_DIST_BLOCK_CFULL:
		return "block:fcyclic";
	case SLURM_DIST_CYCLIC_CYCLIC_CYCLIC:
		return "cyclic:cyclic:cyclic";
	case SLURM_DIST_CYCLIC_CYCLIC_BLOCK:
		return "cyclic:cyclic:block";
	case SLURM_DIST_CYCLIC_CYCLIC_CFULL:
		return "cyclic:cyclic:fcyclic";
	case SLURM_DIST_CYCLIC_BLOCK_CYCLIC:
		return "cyclic:block:cyclic";
	case SLURM_DIST_CYCLIC_BLOCK_BLOCK:
		return "cyclic:block:block";
	case SLURM_DIST_CYCLIC_BLOCK_CFULL:
		return "cyclic:block:fcyclic";
	case SLURM_DIST_CYCLIC_CFULL_CYCLIC:
		return "cyclic:fcyclic:cyclic" ;
	case SLURM_DIST_CYCLIC_CFULL_BLOCK:
		return "cyclic:fcyclic:block";
	case SLURM_DIST_CYCLIC_CFULL_CFULL:
		return "cyclic:fcyclic:fcyclic";
	case SLURM_DIST_BLOCK_CYCLIC_CYCLIC:
		return "block:cyclic:cyclic";
	case SLURM_DIST_BLOCK_CYCLIC_BLOCK:
		return "block:cyclic:block";
	case SLURM_DIST_BLOCK_CYCLIC_CFULL:
		return "block:cyclic:fcyclic";
	case SLURM_DIST_BLOCK_BLOCK_CYCLIC:
		return "block:block:cyclic";
	case SLURM_DIST_BLOCK_BLOCK_BLOCK:
		return "block:block:block";
	case SLURM_DIST_BLOCK_BLOCK_CFULL:
		return "block:block:fcyclic";
	case SLURM_DIST_BLOCK_CFULL_CYCLIC:
		return "block:fcyclic:cyclic";
	case SLURM_DIST_BLOCK_CFULL_BLOCK:
		return "block:fcyclic:block";
	case SLURM_DIST_BLOCK_CFULL_CFULL:
		return "block:fcyclic:fcyclic";
	default:
		return "unknown";
	}
}

/* return command name from its full path name */
char *base_name(const char *command)
{
	const char *char_ptr;

	if (command == NULL)
		return NULL;

	char_ptr = strrchr(command, (int)'/');
	if (char_ptr == NULL)
		char_ptr = command;
	else
		char_ptr++;

	return xstrdup(char_ptr);
}

static bool _end_on_byte(const char * endptr) {
	if ((endptr[1] == '\0') ||
	    (((endptr[1] == 'B') || (endptr[1] == 'b')) && endptr[2] == '\0'))
		return true;
	return false;
}

/*
 * str_to_mbytes(): verify that arg is numeric with optional "K", "M", "G"
 * or "T" at end and return the number in mega-bytes. Default units are MB.
 */
uint64_t str_to_mbytes(const char *arg)
{
	long long result;
	char *endptr;

	errno = 0;
	result = strtoll(arg, &endptr, 10);
	if ((errno != 0) && ((result == LLONG_MIN) || (result == LLONG_MAX)))
		return NO_VAL64;
	if (arg == endptr)
		return NO_VAL64;

	if (result < 0)
		return NO_VAL64;
	else if (endptr[0] == '\0')	/* MB default */
		;
	else if (((endptr[0] == 'k') || (endptr[0] == 'K')) &&
		 _end_on_byte(endptr))
		result = ROUNDUP(result, 1024);	/* round up */
	else if (((endptr[0] == 'm') || (endptr[0] == 'M')) &&
	         _end_on_byte(endptr))
		;
	else if (((endptr[0] == 'g') || (endptr[0] == 'G')) &&
	         _end_on_byte(endptr))
		result *= 1024;
	else if (((endptr[0] == 't') || (endptr[0] == 'T')) &&
	         _end_on_byte(endptr))
		result *= (1024 * 1024);
	else
		return NO_VAL64;

	return (uint64_t) result;
}

extern char *mbytes_to_str(uint64_t mbytes)
{
	int i = 0;
	char *unit = "MGTP?";

	if (mbytes == NO_VAL64)
		return NULL;

	for (i = 0; unit[i] != '?'; i++) {
		if (mbytes && (mbytes % 1024))
			break;
		mbytes /= 1024;
	}

	/* no need to display the default unit */
	if (unit[i] == 'M')
		return xstrdup_printf("%"PRIu64, mbytes);

	return xstrdup_printf("%"PRIu64"%c", mbytes, unit[i]);
}

/* Convert a string into a node count */
extern int str_to_nodes(const char *num_str, char **leftover)
{
	long int num;
	char *endptr;

	num = strtol(num_str, &endptr, 10);
	if (endptr == num_str) { /* no valid digits */
		*leftover = (char *)num_str;
		return -1;
	}
	if (*endptr != '\0' && (*endptr == 'k' || *endptr == 'K')) {
		num *= 1024;
		endptr++;
	}
	if (*endptr != '\0' && (*endptr == 'm' || *endptr == 'M')) {
		num *= (1024 * 1024);
		endptr++;
	}
	*leftover = endptr;

	if ((num < 0) || (num > INT_MAX))
		return -1;

	return (int)num;
}

/*
 * verify that a node count in arg is of a known form (count or min-max or list)
 * OUT min, max specified minimum and maximum node counts
 * OUT job_size_str
 * RET true if valid
 */
bool verify_node_count(const char *arg, int *min_nodes, int *max_nodes,
		       char **job_size_str)
{
	char *ptr, *min_str, *max_str;
	char *leftover;

	/*
	 * Does the string contain a "-" character?  If so, treat as a range.
	 * otherwise treat as an absolute node count.
	 */
	if (job_size_str)
		xfree(*job_size_str);

	if ((ptr = xstrchr(arg, ',')) || (ptr = xstrchr(arg, ':'))) {
		bitstr_t *job_size_bitmap;
		char *tok, *tmp_str, *save_ptr = NULL;
		long int max = 0;

		tmp_str = xstrdup(arg);

		tok = strtok_r(tmp_str, ",-:", &save_ptr);
		while (tok) {
			char *endptr;
			long int num = strtol(tok, &endptr, 10);
			if ((endptr == tok) ||
			    ((*endptr != '\0') && (*endptr != ',') &&
			     (*endptr != '-') && (*endptr != ':')) ||
			    (num >= MAX_JOB_SIZE_BITMAP)) {
				error("\"%s\" is not a valid node count", tok);
				xfree(tmp_str);
				return false;
			}
			if (num > max)
				max = num;
			tok = strtok_r(NULL, ",-:", &save_ptr);
		}
		xfree(tmp_str);
		tmp_str = xstrdup(arg);
		job_size_bitmap = bit_alloc(max + 1);
		if (bit_unfmt(job_size_bitmap, tmp_str)) {
			error("\"%s\" is not a valid node count", arg);
			FREE_NULL_BITMAP(job_size_bitmap);
			xfree(tmp_str);
			return false;
		}
		*min_nodes = bit_ffs(job_size_bitmap);
		*max_nodes = bit_fls(job_size_bitmap);
		if (job_size_str)
			*job_size_str = bit_fmt_full(job_size_bitmap);
		FREE_NULL_BITMAP(job_size_bitmap);
		xfree(tmp_str);
	} else if ((ptr = xstrchr(arg, '-')) != NULL) {
		min_str = xstrndup(arg, ptr-arg);
		*min_nodes = str_to_nodes(min_str, &leftover);
		if (!xstring_is_whitespace(leftover)) {
			error("\"%s\" is not a valid node count", min_str);
			xfree(min_str);
			return false;
		}
		xfree(min_str);
		if (*min_nodes < 0)
			*min_nodes = 1;

		max_str = xstrndup(ptr+1, strlen(arg)-((ptr+1)-arg));
		*max_nodes = str_to_nodes(max_str, &leftover);
		if (!xstring_is_whitespace(leftover)) {
			error("\"%s\" is not a valid node count", max_str);
			xfree(max_str);
			return false;
		}
		xfree(max_str);
	} else {
		*min_nodes = *max_nodes = str_to_nodes(arg, &leftover);
		if (!xstring_is_whitespace(leftover)) {
			error("\"%s\" is not a valid node count", arg);
			return false;
		}
		if (*min_nodes < 0) {
			error("\"%s\" is not a valid node count", arg);
			return false;
		}
	}

	if ((*max_nodes != 0) && (*max_nodes < *min_nodes)) {
		error("Maximum node count %d is less than minimum node count %d",
		      *max_nodes, *min_nodes);
		return false;
	}

	return true;
}

/*
 * If the node list supplied is a file name, translate that into
 *	a list of nodes, we orphan the data pointed to
 * RET true if the node list is a valid one
 */
bool verify_node_list(char **node_list_pptr, enum task_dist_states dist,
		      int task_count)
{
	char *nodelist = NULL;

	xassert (node_list_pptr);
	xassert (*node_list_pptr);

	if (strchr(*node_list_pptr, '/') == NULL)
		return true;	/* not a file name */

	/* If we are using Arbitrary grab count out of the hostfile
	   using them exactly the way we read it in since we are
	   saying, lay it out this way! */
	if ((dist & SLURM_DIST_STATE_BASE) == SLURM_DIST_ARBITRARY)
		nodelist = slurm_read_hostfile(*node_list_pptr, task_count);
	else
		nodelist = slurm_read_hostfile(*node_list_pptr, NO_VAL);

	if (!nodelist)
		return false;

	xfree(*node_list_pptr);
	*node_list_pptr = xstrdup(nodelist);
	free(nodelist);

	return true;
}

/*
 * get either 1 or 2 integers for a resource count in the form of either
 * (count, min-max, or '*')
 * A partial error message is passed in via the 'what' param.
 * IN arg - argument
 * IN what - variable name (for errors)
 * OUT min - first number
 * OUT max - maximum value if specified, NULL if don't care
 * IN isFatal - if set, exit on error
 * RET true if valid
 */
bool get_resource_arg_range(const char *arg, const char *what, int* min,
			    int *max, bool isFatal)
{
	char *p;
	long int result;

	/* wildcard meaning every possible value in range */
	if ((*arg == '\0') || (*arg == '*' )) {
		*min = 1;
		if (max)
			*max = INT_MAX;
		return true;
	}

	result = strtol(arg, &p, 10);
	if (*p == 'k' || *p == 'K') {
		result *= 1024;
		p++;
	} else if (*p == 'm' || *p == 'M') {
		result *= 1048576;
		p++;
	}

	if (((*p != '\0') && (*p != '-')) || (result < 0L)) {
		error ("Invalid numeric value \"%s\" for %s.", arg, what);
		if (isFatal)
			exit(1);
		return false;
	} else if (result > INT_MAX) {
		error("Numeric argument (%ld) too large for %s.", result, what);
		if (isFatal)
			exit(1);
		return false;
	}

	*min = (int) result;

	if (*p == '\0')
		return true;
	if (*p == '-')
		p++;

	result = strtol(p, &p, 10);
	if ((*p == 'k') || (*p == 'K')) {
		result *= 1024;
		p++;
	} else if (*p == 'm' || *p == 'M') {
		result *= 1048576;
		p++;
	}

	if (((*p != '\0') && (*p != '-')) || (result <= 0L)) {
		error ("Invalid numeric value \"%s\" for %s.", arg, what);
		if (isFatal)
			exit(1);
		return false;
	} else if (result > INT_MAX) {
		error("Numeric argument (%ld) too large for %s.", result, what);
		if (isFatal)
			exit(1);
		return false;
	}

	if (max)
		*max = (int) result;

	return true;
}

/*
 * verify that a resource counts in arg are of a known form X, X:X, X:X:X, or
 * X:X:X:X, where X is defined as either (count, min-max, or '*')
 * RET true if valid
 */
bool verify_socket_core_thread_count(const char *arg, int *min_sockets,
				     int *min_cores, int *min_threads,
				     cpu_bind_type_t *cpu_bind_type)
{
	bool tmp_val, ret_val;
	int i, j;
	int max_sockets = 0, max_cores = 0, max_threads = 0;
	const char *cur_ptr = arg;
	char buf[3][48]; /* each can hold INT64_MAX - INT64_MAX */

	if (!arg) {
		error("%s: argument is NULL", __func__);
		return false;
	}
	memset(buf, 0, sizeof(buf));
	for (j = 0; j < 3; j++) {
		for (i = 0; i < 47; i++) {
			if (*cur_ptr == '\0' || *cur_ptr ==':')
				break;
			buf[j][i] = *cur_ptr++;
		}
		if (*cur_ptr == '\0')
			break;
		xassert(*cur_ptr == ':');
		cur_ptr++;
	}
	/* if cpu_bind_type doesn't already have a auto preference, choose
	 * the level based on the level of the -E specification
	 */
	if (cpu_bind_type &&
	    !(*cpu_bind_type & (CPU_BIND_TO_SOCKETS |
				CPU_BIND_TO_CORES |
				CPU_BIND_TO_THREADS))) {
		if (j == 0) {
			*cpu_bind_type |= CPU_BIND_TO_SOCKETS;
		} else if (j == 1) {
			*cpu_bind_type |= CPU_BIND_TO_CORES;
		} else if (j == 2) {
			*cpu_bind_type |= CPU_BIND_TO_THREADS;
		}
	}

	ret_val = true;
	tmp_val = get_resource_arg_range(&buf[0][0], "first arg of -B",
					 min_sockets, &max_sockets, true);
	if ((*min_sockets == 1) && (max_sockets == INT_MAX))
		*min_sockets = NO_VAL;	/* Use full range of values */
	ret_val = ret_val && tmp_val;


	tmp_val = get_resource_arg_range(&buf[1][0], "second arg of -B",
					 min_cores, &max_cores, true);
	if ((*min_cores == 1) && (max_cores == INT_MAX))
		*min_cores = NO_VAL;	/* Use full range of values */
	ret_val = ret_val && tmp_val;

	tmp_val = get_resource_arg_range(&buf[2][0], "third arg of -B",
					 min_threads, &max_threads, true);
	if ((*min_threads == 1) && (max_threads == INT_MAX))
		*min_threads = NO_VAL;	/* Use full range of values */
	ret_val = ret_val && tmp_val;

	return ret_val;
}

/*
 * verify that a hint is valid and convert it into the implied settings
 * RET true if valid
 */
bool verify_hint(const char *arg, int *min_sockets, int *min_cores,
		 int *min_threads, int *ntasks_per_core,
		 cpu_bind_type_t *cpu_bind_type)
{
	char *buf, *p, *tok;

	if (!arg)
		return true;

	buf = xstrdup(arg);
	p = buf;
	/* change all ',' delimiters not followed by a digit to ';'  */
	/* simplifies parsing tokens while keeping map/mask together */
	while (p[0] != '\0') {
		if ((p[0] == ',') && (!isdigit((int)p[1])))
			p[0] = ';';
		p++;
	}

	p = buf;
	while ((tok = strsep(&p, ";"))) {
		if (xstrcasecmp(tok, "help") == 0) {
			printf(
"Application hint options:\n"
"    --hint=             Bind tasks according to application hints\n"
"        compute_bound   use all cores in each socket\n"
"        memory_bound    use only one core in each socket\n"
"        [no]multithread [don't] use extra threads with in-core multi-threading\n"
"        help            show this help message\n");
			xfree(buf);
			return 1;
		} else if (xstrcasecmp(tok, "compute_bound") == 0) {
			*min_sockets = NO_VAL;
			*min_cores   = NO_VAL;
			*min_threads = 1;
			if (cpu_bind_type)
				*cpu_bind_type |= CPU_BIND_TO_CORES;
		} else if (xstrcasecmp(tok, "memory_bound") == 0) {
			*min_cores   = 1;
			*min_threads = 1;
			if (cpu_bind_type)
				*cpu_bind_type |= CPU_BIND_TO_CORES;
		} else if (xstrcasecmp(tok, "multithread") == 0) {
			*min_threads = NO_VAL;
			if (cpu_bind_type) {
				*cpu_bind_type |= CPU_BIND_TO_THREADS;
				*cpu_bind_type &=
					(~CPU_BIND_ONE_THREAD_PER_CORE);
			}
			*ntasks_per_core = INFINITE16;
		} else if (xstrcasecmp(tok, "nomultithread") == 0) {
			*min_threads = 1;
			if (cpu_bind_type) {
				*cpu_bind_type |= CPU_BIND_TO_THREADS;
				*cpu_bind_type |= CPU_BIND_ONE_THREAD_PER_CORE;
			}
		} else {
			error("unrecognized --hint argument \"%s\", "
			      "see --hint=help", tok);
			xfree(buf);
			return 1;
		}
	}

	if (!cpu_bind_type)
		setenvf(NULL, "SLURM_HINT", "%s", arg);

	xfree(buf);
	return 0;
}

uint16_t parse_mail_type(const char *arg)
{
	char *buf, *tok, *save_ptr = NULL;
	uint16_t rc = 0;
	bool none_set = false;

	if (!arg)
		return INFINITE16;

	buf = xstrdup(arg);
	tok = strtok_r(buf, ",", &save_ptr);
	while (tok) {
		if (xstrcasecmp(tok, "NONE") == 0) {
			rc = 0;
			none_set = true;
			break;
		}
		else if (xstrcasecmp(tok, "ARRAY_TASKS") == 0)
			rc |= MAIL_ARRAY_TASKS;
		else if (xstrcasecmp(tok, "BEGIN") == 0)
			rc |= MAIL_JOB_BEGIN;
		else if  (xstrcasecmp(tok, "END") == 0)
			rc |= MAIL_JOB_END;
		else if (xstrcasecmp(tok, "FAIL") == 0)
			rc |= MAIL_JOB_FAIL;
		else if (xstrcasecmp(tok, "INVALID_DEPEND") == 0)
			rc |= MAIL_INVALID_DEPEND;
		else if (xstrcasecmp(tok, "REQUEUE") == 0)
			rc |= MAIL_JOB_REQUEUE;
		else if (xstrcasecmp(tok, "ALL") == 0)
			rc |= MAIL_INVALID_DEPEND | MAIL_JOB_BEGIN |
			      MAIL_JOB_END | MAIL_JOB_FAIL | MAIL_JOB_REQUEUE |
			      MAIL_JOB_STAGE_OUT;
		else if (!xstrcasecmp(tok, "STAGE_OUT"))
			rc |= MAIL_JOB_STAGE_OUT;
		else if (xstrcasecmp(tok, "TIME_LIMIT") == 0)
			rc |= MAIL_JOB_TIME100;
		else if (xstrcasecmp(tok, "TIME_LIMIT_90") == 0)
			rc |= MAIL_JOB_TIME90;
		else if (xstrcasecmp(tok, "TIME_LIMIT_80") == 0)
			rc |= MAIL_JOB_TIME80;
		else if (xstrcasecmp(tok, "TIME_LIMIT_50") == 0)
			rc |= MAIL_JOB_TIME50;
		tok = strtok_r(NULL, ",", &save_ptr);
	}
	xfree(buf);
	if (!rc && !none_set)
		rc = INFINITE16;

	return rc;
}
char *print_mail_type(const uint16_t type)
{
	static char buf[256];

	buf[0] = '\0';

	if (type == 0)
		return "NONE";

	if (type & MAIL_ARRAY_TASKS) {
		if (buf[0])
			strcat(buf, ",");
		strcat(buf, "ARRAY_TASKS");
	}
	if (type & MAIL_INVALID_DEPEND) {
		if (buf[0])
			strcat(buf, ",");
		strcat(buf, "INVALID_DEPEND");
	}
	if (type & MAIL_JOB_BEGIN) {
		if (buf[0])
			strcat(buf, ",");
		strcat(buf, "BEGIN");
	}
	if (type & MAIL_JOB_END) {
		if (buf[0])
			strcat(buf, ",");
		strcat(buf, "END");
	}
	if (type & MAIL_JOB_FAIL) {
		if (buf[0])
			strcat(buf, ",");
		strcat(buf, "FAIL");
	}
	if (type & MAIL_JOB_REQUEUE) {
		if (buf[0])
			strcat(buf, ",");
		strcat(buf, "REQUEUE");
	}
	if (type & MAIL_JOB_STAGE_OUT) {
		if (buf[0])
			strcat(buf, ",");
		strcat(buf, "STAGE_OUT");
	}
	if (type & MAIL_JOB_TIME50) {
		if (buf[0])
			strcat(buf, ",");
		strcat(buf, "TIME_LIMIT_50");
	}
	if (type & MAIL_JOB_TIME80) {
		if (buf[0])
			strcat(buf, ",");
		strcat(buf, "TIME_LIMIT_80");
	}
	if (type & MAIL_JOB_TIME90) {
		if (buf[0])
			strcat(buf, ",");
		strcat(buf, "TIME_LIMIT_90");
	}
	if (type & MAIL_JOB_TIME100) {
		if (buf[0])
			strcat(buf, ",");
		strcat(buf, "TIME_LIMIT");
	}

	return buf;
}

static list_t *_create_path_list(void)
{
	list_t *l = list_create(xfree_ptr);
	char *path;
	char *c, *lc;

	c = getenv("PATH");
	if (!c) {
		error("No PATH environment variable");
		return l;
	}
	path = xstrdup(c);
	c = lc = path;

	while (*c != '\0') {
		if (*c == ':') {
			/* nullify and push token onto list */
			*c = '\0';
			if (lc != NULL && strlen(lc) > 0)
				list_append(l, xstrdup(lc));
			lc = ++c;
		} else
			c++;
	}

	if (strlen(lc) > 0)
		list_append(l, xstrdup(lc));

	xfree(path);

	return l;
}

/*
 * Check a specific path to see if the executable exists and is not a directory
 * IN path - path of executable to check
 * RET true if path exists and is not a directory; false otherwise
 */
static bool _exists(const char *path)
{
	struct stat st;
        if (stat(path, &st)) {
		debug2("_check_exec: failed to stat path %s", path);
		return false;
	}
	if (S_ISDIR(st.st_mode)) {
		debug2("_check_exec: path %s is a directory", path);
		return false;
	}
	return true;
}

/*
 * Check a specific path to see if the executable is accessible
 * IN path - path of executable to check
 * IN access_mode - determine if executable is accessible to caller with
 *		    specified mode
 * RET true if path is accessible according to access mode, false otherwise
 */
static bool _accessible(const char *path, int access_mode)
{
	if (access(path, access_mode)) {
		debug2("_check_exec: path %s is not accessible", path);
		return false;
	}
	return true;
}

/*
 * search PATH to confirm the location and access mode of the given command
 * IN cwd - current working directory
 * IN cmd - command to execute
 * IN check_cwd_last - if true, search cwd after PATH is checked
 *                   - if false, search cwd for the command first
 * IN access_mode - required access rights of cmd
 * IN test_exec - if false, do not confirm access mode of cmd if full path
 * RET full path of cmd or NULL if not found
 */
char *search_path(char *cwd, char *cmd, bool check_cwd_last, int access_mode,
		  bool test_exec)
{
	list_t *l = NULL;
	list_itr_t *i = NULL;
	char *path, *fullpath = NULL;

	/* Relative path */
	if (cmd[0] == '.') {
		if (test_exec) {
			char *cmd1 = xstrdup_printf("%s/%s", cwd, cmd);
			if (_exists(cmd1) && _accessible(cmd1, access_mode)) {
				fullpath = xstrdup(cmd1);
				debug5("%s: relative path found %s -> %s",
					__func__, cmd, cmd1);
			} else {
				debug5("%s: relative path not found %s -> %s",
					__func__, cmd, cmd1);
			}
			xfree(cmd1);
		}
		return fullpath;
	}
	/* Absolute path */
	if (cmd[0] == '/') {
		if (test_exec && _exists(cmd) && _accessible(cmd, access_mode)) {
			fullpath = xstrdup(cmd);
			debug5("%s: absolute path found %s",
			       __func__, cmd);
		} else {
			debug5("%s: absolute path not found %s",
			       __func__, cmd);
		}
		return fullpath;
	}
	/* Otherwise search in PATH */
	l = _create_path_list();
	if (l == NULL) {
		debug5("%s: empty PATH environment",
			__func__);
		return NULL;
	}

	if (check_cwd_last)
		list_append(l, xstrdup(cwd));
	else
		list_prepend(l, xstrdup(cwd));

	i = list_iterator_create(l);
	while ((path = list_next(i))) {
		if (path[0] == '.')
			xstrfmtcat(fullpath, "%s/%s/%s", cwd, path, cmd);
		else
			xstrfmtcat(fullpath, "%s/%s", path, cmd);
		/* Use first executable found in PATH */
		if (_exists(fullpath)) {
			if (!test_exec) {
				debug5("%s: env PATH found: %s",
					__func__, fullpath);
				break;
			}
			if (_accessible(path, access_mode)) {
				debug5("%s: env PATH found: %s",
					__func__, fullpath);
				break;
			}
		}

		debug5("%s: env PATH not found: %s",
			__func__, fullpath);

		xfree(fullpath);
	}
	list_iterator_destroy(i);
	FREE_NULL_LIST(l);
	return fullpath;
}

char *print_commandline(const int script_argc, char **script_argv)
{
	int i;
	char *out_buf = NULL, *prefix = "";

	for (i = 0; i < script_argc; i++) {
		xstrfmtcat(out_buf,  "%s%s", prefix, script_argv[i]);
		prefix = " ";
	}
	return out_buf;
}

/* Translate a signal option string "--signal=<int>[@<time>]" into
 * it's warn_signal and warn_time components.
 * RET 0 on success, -1 on failure */
int get_signal_opts(char *optarg, uint16_t *warn_signal, uint16_t *warn_time,
		    uint16_t *warn_flags)
{
	char *endptr;
	long num;

	if (optarg == NULL)
		return -1;

	if (!xstrncasecmp(optarg, "R", 1)) {
		*warn_flags |= KILL_JOB_RESV;
		optarg++;
	}

		if (!xstrncasecmp(optarg, "B", 1)) {
			*warn_flags |= KILL_JOB_BATCH;
			optarg++;
		}

		/* easiest way to handle BR and RB */
		if (!xstrncasecmp(optarg, "R", 1)) {
			*warn_flags |= KILL_JOB_RESV;
			optarg++;
		}

	if (*optarg == ':')
		optarg++;

	endptr = strchr(optarg, '@');
	if (endptr)
		endptr[0] = '\0';
	num = (uint16_t) sig_name2num(optarg);
	if (endptr)
		endptr[0] = '@';
	if ((num < 1) || (num > 0x0ffff))
		return -1;
	*warn_signal = (uint16_t) num;

	if (!endptr) {
		*warn_time = 60;
		return 0;
	}

	num = strtol(endptr+1, &endptr, 10);
	if ((num < 0) || (num > 0x0ffff))
		return -1;
	*warn_time = (uint16_t) num;
	if (endptr[0] == '\0')
		return 0;
	return -1;
}

extern char *signal_opts_to_cmdline(uint16_t warn_signal, uint16_t warn_time,
				    uint16_t warn_flags)
{
	char *cmdline = NULL, *sig_name;

	if (warn_flags & KILL_JOB_RESV)
		xstrcat(cmdline, "R");
	if (warn_flags & KILL_JOB_BATCH)
		xstrcat(cmdline, "B");

	if ((warn_flags & KILL_JOB_RESV) || (warn_flags & KILL_JOB_BATCH))
		xstrcat(cmdline, ":");

	sig_name = sig_num2name(warn_signal);
	xstrcat(cmdline, sig_name);
	xfree(sig_name);

	if (warn_time != 60) /* default value above, don't print */
		xstrfmtcat(cmdline, "@%u", warn_time);

	return cmdline;
}

static struct {
	char *name;
	uint16_t val;
} signals_mapping[] = {
	{ "HUP",	SIGHUP	},
	{ "INT",	SIGINT	},
	{ "QUIT",	SIGQUIT	},
	{ "ABRT",	SIGABRT	},
	{ "KILL",	SIGKILL	},
	{ "ALRM",	SIGALRM	},
	{ "TERM",	SIGTERM	},
	{ "CHLD",	SIGCHLD	},
	{ "USR1",	SIGUSR1	},
	{ "USR2",	SIGUSR2	},
	{ "PIPE",	SIGPIPE	},
	{ "URG",	SIGURG	},
	{ "CONT",	SIGCONT	},
	{ "STOP",	SIGSTOP	},
	{ "TSTP",	SIGTSTP	},
	{ "TTIN",	SIGTTIN	},
	{ "TTOU",	SIGTTOU	},
	{ "XCPU",	SIGXCPU	},
	{ NULL,		0	}	/* terminate array */
};

/* Convert a signal name to it's numeric equivalent.
 * Return 0 on failure */
int sig_name2num(const char *signal_name)
{
	char *ptr;
	long tmp;
	int i;

	tmp = strtol(signal_name, &ptr, 10);
	if (ptr != signal_name) { /* found a number */
		if (xstring_is_whitespace(ptr))
			return (int)tmp;
		else
			return 0;
	}

	/* search the array */
	ptr = (char *) signal_name;
	while (isspace((int)*ptr))
		ptr++;
	if (xstrncasecmp(ptr, "SIG", 3) == 0)
		ptr += 3;
	for (i = 0; ; i++) {
		int siglen;
		if (signals_mapping[i].name == NULL)
			return 0;
		siglen = strlen(signals_mapping[i].name);
		if ((!xstrncasecmp(ptr, signals_mapping[i].name, siglen)
		    && xstring_is_whitespace(ptr + siglen))) {
			/* found the signal name */
			return signals_mapping[i].val;
		}
	}

	return 0;	/* not found */
}

extern char *sig_num2name(int signal)
{
	for (int i = 0; signals_mapping[i].name; i++) {
		if (signal == signals_mapping[i].val)
			return xstrdup(signals_mapping[i].name);
	}

	return xstrdup_printf("%d", signal);
}

/*
 * parse_uint16 - Convert ascii string to a 16 bit unsigned int.
 * IN      aval - ascii string.
 * IN/OUT  ival - 16 bit pointer.
 * RET     0 if no error, 1 otherwise.
 */
extern int parse_uint16(char *aval, uint16_t *ival)
{
	/*
	 * First,  convert the ascii value it to a
	 * long long int. If the result is greater then
	 * or equal to 0 and less than NO_VAL16
	 * set the value and return. Otherwise
	 * return an error.
	 */
	uint16_t max16uint = NO_VAL16;
	long long tval;
	char *p;

	/*
	 * Return error for invalid value.
	 */
	tval = strtoll(aval, &p, 10);
	if (p[0] || (tval == LLONG_MIN) || (tval == LLONG_MAX) ||
	    (tval < 0) || (tval >= max16uint))
		return 1;

	*ival = (uint16_t) tval;

	return 0;
}

/*
 * parse_uint32 - Convert ascii string to a 32 bit unsigned int.
 * IN      aval - ascii string.
 * IN/OUT  ival - 32 bit pointer.
 * RET     0 if no error, 1 otherwise.
 */
extern int parse_uint32(char *aval, uint32_t *ival)
{
	/*
	 * First,  convert the ascii value it to a
	 * long long int. If the result is greater
	 * than or equal to 0 and less than NO_VAL
	 * set the value and return. Otherwise return
	 * an error.
	 */
	uint32_t max32uint = NO_VAL;
	long long tval;
	char *p;

	/*
	 * Return error for invalid value.
	 */
	tval = strtoll(aval, &p, 10);
	if (p[0] || (tval == LLONG_MIN) || (tval == LLONG_MAX) ||
	    (tval < 0) || (tval >= max32uint))
		return 1;

	*ival = (uint32_t) tval;

	return 0;
}

/*
 * parse_uint64 - Convert ascii string to a 64 bit unsigned int.
 * IN      aval - ascii string.
 * IN/OUT  ival - 64 bit pointer.
 * RET     0 if no error, 1 otherwise.
 */
extern int parse_uint64(char *aval, uint64_t *ival)
{
	/*
	 * First,  convert the ascii value it to an
	 * unsigned long long. If the result is greater
	 * than or equal to 0 and less than NO_VAL
	 * set the value and return. Otherwise return
	 * an error.
	 */
	uint64_t max64uint = NO_VAL64;
	long long tval;
	char *p;

	/*
 	 * Return error for invalid value.
	 */
	tval = strtoll(aval, &p, 10);
	if (p[0] || (tval == LLONG_MIN) || (tval == LLONG_MAX) ||
	    (tval < 0) || (tval >= max64uint))
		return 1;

	*ival = (uint64_t) tval;

	return 0;
}

/*
 *  Get a decimal integer from arg.
 *
 *  Returns the integer on success, exits program on failure.
 */
extern int parse_int(const char *name, const char *val, bool positive)
{
	char *p = NULL;
	long int result = 0;

	if (val)
		result = strtol(val, &p, 10);

	if ((p == NULL) || (p[0] != '\0') || (result < 0L) ||
	    (positive && (result <= 0L))) {
		error ("Invalid numeric value \"%s\" for %s.", val, name);
		exit(1);
	} else if (result >= INT_MAX) {
		error("Numeric argument (%ld) too large for %s.", result, name);
		exit(1);
	} else if (result <= INT_MIN) {
		error ("Numeric argument (%ld) to small for %s.", result, name);
		exit(1);
	}

	return (int) result;
}

/* print_db_notok() - Print an error message about slurmdbd
 *                    is unreachable or wrong cluster name.
 * IN  cname - char * cluster name
 * IN  isenv - bool  cluster name from env or from command line option.
 */
void print_db_notok(const char *cname, bool isenv)
{
	if (errno)
		error("There is a problem talking to the database: %m.  "
		      "Only local cluster communication is available, remove "
		      "%s or contact your admin to resolve the problem.",
		      isenv ? "SLURM_CLUSTERS from your environment" :
		      "--cluster from your command line");
	else if (!xstrcasecmp("all", cname))
		error("No clusters can be reached now. "
		      "Contact your admin to resolve the problem.");
	else
		error("'%s' can't be reached now, "
		      "or it is an invalid entry for %s.  "
		      "Use 'sacctmgr list clusters' to see available clusters.",
		      cname, isenv ? "SLURM_CLUSTERS" : "--cluster");
}

/*
 * parse_resv_flags() used to parse the Flags= option.  It handles
 * daily, weekly, static_alloc, part_nodes, and maint, optionally
 * preceded by + or -, separated by a comma but no spaces.
 *
 * flagstr IN - reservation flag string
 * msg IN - string to append to error message (e.g. function name)
 * resv_msg_ptr IN/OUT - sets flags and times in ptr.
 * RET equivalent reservation flag bits
 */
extern uint64_t parse_resv_flags(const char *flagstr, const char *msg,
				 resv_desc_msg_t  *resv_msg_ptr)
{
	int op = RESV_NEW;
	uint64_t outflags = 0;
	char *curr = xstrdup(flagstr), *start = curr;
	int taglen = 0;

	while (*curr != '\0') {
		if (*curr == '+') {
			op = RESV_ADD;
			curr++;
		} else if (*curr == '-') {
			op = RESV_REM;
			curr++;
		}
		taglen = 0;
		while (curr[taglen] != ',' && curr[taglen] != '\0'
		       && curr[taglen] != '=')
			taglen++;

		if (xstrncasecmp(curr, "Maintenance", MAX(taglen,3)) == 0) {
			curr += taglen;
			if (op == RESV_REM)
				outflags |= RESERVE_FLAG_NO_MAINT;
			else
				outflags |= RESERVE_FLAG_MAINT;
		} else if ((xstrncasecmp(curr, "Overlap", MAX(taglen,1))
			    == 0) && (op != RESV_REM)) {
			curr += taglen;
			outflags |= RESERVE_FLAG_OVERLAP;
			/*
			 * "-OVERLAP" is not supported since that's the
			 * default behavior and the option only applies
			 * for reservation creation, not updates
			 */
		} else if (xstrncasecmp(curr, "Flex", MAX(taglen,1)) == 0) {
			curr += taglen;
			if (op == RESV_REM)
				outflags |= RESERVE_FLAG_NO_FLEX;
			else
				outflags |= RESERVE_FLAG_FLEX;
		} else if (xstrncasecmp(curr, "Ignore_Jobs", MAX(taglen,1))
			   == 0) {
			curr += taglen;
			if (op == RESV_REM)
				outflags |= RESERVE_FLAG_NO_IGN_JOB;
			else
				outflags |= RESERVE_FLAG_IGN_JOBS;
		} else if (xstrncasecmp(curr, "Hourly", MAX(taglen, 1)) == 0) {
			curr += taglen;
			if (op == RESV_REM)
				outflags |= RESERVE_FLAG_NO_HOURLY;
			else
				outflags |= RESERVE_FLAG_HOURLY;
		} else if (xstrncasecmp(curr, "Daily", MAX(taglen,1)) == 0) {
			curr += taglen;
			if (op == RESV_REM)
				outflags |= RESERVE_FLAG_NO_DAILY;
			else
				outflags |= RESERVE_FLAG_DAILY;
		} else if (xstrncasecmp(curr, "Weekday", MAX(taglen,1)) == 0) {
			curr += taglen;
			if (op == RESV_REM)
				outflags |= RESERVE_FLAG_NO_WEEKDAY;
			else
				outflags |= RESERVE_FLAG_WEEKDAY;
		} else if (xstrncasecmp(curr, "Weekend", MAX(taglen,1)) == 0) {
			curr += taglen;
			if (op == RESV_REM)
				outflags |= RESERVE_FLAG_NO_WEEKEND;
			else
				outflags |= RESERVE_FLAG_WEEKEND;
		} else if (xstrncasecmp(curr, "Weekly", MAX(taglen,1)) == 0) {
			curr += taglen;
			if (op == RESV_REM)
				outflags |= RESERVE_FLAG_NO_WEEKLY;
			else
				outflags |= RESERVE_FLAG_WEEKLY;
		} else if (!xstrncasecmp(curr, "Any_Nodes", MAX(taglen,1)) ||
			   !xstrncasecmp(curr, "License_Only", MAX(taglen,1))) {
			curr += taglen;
			if (op == RESV_REM)
				outflags |= RESERVE_FLAG_NO_ANY_NODES;
			else
				outflags |= RESERVE_FLAG_ANY_NODES;
		} else if (xstrncasecmp(curr, "Static_Alloc", MAX(taglen,1))
			   == 0) {
			curr += taglen;
			if (op == RESV_REM)
				outflags |= RESERVE_FLAG_NO_STATIC;
			else
				outflags |= RESERVE_FLAG_STATIC;
		} else if (xstrncasecmp(curr, "Part_Nodes", MAX(taglen, 2))
			   == 0) {
			curr += taglen;
			if (op == RESV_REM)
				outflags |= RESERVE_FLAG_NO_PART_NODES;
			else
				outflags |= RESERVE_FLAG_PART_NODES;
		} else if (!xstrncasecmp(curr, "magnetic", MAX(taglen, 3)) ||
			   !xstrncasecmp(curr, "promiscuous", MAX(taglen, 2))) {
			curr += taglen;

			if (op == RESV_REM)
				outflags |= RESERVE_FLAG_NO_MAGNETIC;
			else
				outflags |= RESERVE_FLAG_MAGNETIC;
		} else if (!xstrncasecmp(curr, "PURGE_COMP", MAX(taglen, 2))) {
			if (curr[taglen] == '=') {
				int num_end;
				taglen++;

				num_end = taglen;
				while (curr[num_end] != ',' &&
				       curr[num_end] != '\0')
					num_end++;
				if (curr[num_end] == ',') {
					curr[num_end] = '\0';
					num_end++;
				}
				if (resv_msg_ptr)
					resv_msg_ptr->purge_comp_time =
						time_str2secs(curr+taglen);
				taglen = num_end;
			}
			curr += taglen;
			if (op == RESV_REM)
				outflags |= RESERVE_FLAG_NO_PURGE_COMP;
			else
				outflags |= RESERVE_FLAG_PURGE_COMP;
		} else if (!xstrncasecmp(curr, "Time_Float", MAX(taglen,1)) &&
			   op == RESV_NEW) {
			curr += taglen;
			outflags |= RESERVE_FLAG_TIME_FLOAT;
		} else if (!xstrncasecmp(curr, "Replace", MAX(taglen, 1)) &&
			   op != RESV_REM) {
			curr += taglen;
			outflags |= RESERVE_FLAG_REPLACE;
		} else if (!xstrncasecmp(curr, "Replace_Down", MAX(taglen, 8))
			   && op != RESV_REM) {
			curr += taglen;
			outflags |= RESERVE_FLAG_REPLACE_DOWN;
		} else if (!xstrncasecmp(curr, "NO_HOLD_JOBS_AFTER_END",
					 MAX(taglen, 1)) && op != RESV_REM) {
			curr += taglen;
			outflags |= RESERVE_FLAG_NO_HOLD_JOBS;
		} else if (!xstrncasecmp(curr, "User_Delete", MAX(taglen, 1))) {
			curr += taglen;
			if (op == RESV_REM)
				outflags |= RESERVE_FLAG_NO_USER_DEL;
			else
				outflags |= RESERVE_FLAG_USER_DEL;
		} else if (!xstrncasecmp(curr, "Force_Start", MAX(taglen, 1)) &&
			   op == RESV_NEW) {
			curr += taglen;
			outflags |= RESERVE_FLAG_FORCE_START;
		} else {
			error("Error parsing flags %s.  %s", flagstr, msg);
			return INFINITE64;
		}

		if (*curr == ',') {
			curr++;
		}
	}

	if (resv_msg_ptr && (outflags != INFINITE64)) {
		if (resv_msg_ptr->flags == NO_VAL64)
			resv_msg_ptr->flags = outflags;
		else
			resv_msg_ptr->flags |= outflags;
	}
	xfree(start);
	return outflags;
}

/* parse --compress for a compression type, set to default type if not found */
uint16_t parse_compress_type(const char *arg)
{
	/* if called with null string return default compression type */
	if (!arg) {
#if HAVE_LZ4
		return COMPRESS_LZ4;
#else
		error("No compression library available, compression disabled.");
		return COMPRESS_OFF;
#endif
	}

	if (!strcasecmp(arg, "lz4"))
		return COMPRESS_LZ4;
	else if (!strcasecmp(arg, "none"))
		return COMPRESS_OFF;

	error("Compression type '%s' unknown, disabling compression support.",
	      arg);
	return COMPRESS_OFF;
}

/*
 * IN: option argument value to interpret.
 * RET: 1 if enabled, 0 if disabled, -1 if error
 */
int parse_send_libs(const char *arg)
{
	if (!arg || !xstrcasecmp(arg, "yes") || !xstrcasecmp(arg, "y"))
		return 1;

	if (!xstrcasecmp(arg, "no") || !xstrcasecmp(arg, "n"))
		return 0;

	return -1;
}

/*
 * IN: char pointer to path1
 * IN: char pointer to path2
 *
 * RET: true if path2 is a subpath of path1; false otherwise
 *
 * Examples:
 *
 * path1	path2		ret
 * ---------------------------------
 * NULL		NULL		true
 * NULL		/foo		false
 * /foo		NULL		true
 * /foo/bar	/foo		true
 * /foo/bar	/bar		false
 * /foo/bar	/foo/b		false
 * /foo		/foo/bar	false
 * /foo		/foo/		true
 */
extern bool subpath(char *path1, char *path2)
{
	bool ret = true;
	char *p1 = NULL, *p2 = NULL;
	char *tok1 = NULL, *tok2 = NULL;
	char *save_ptr1 = NULL, *save_ptr2 = NULL;

	if (!path2)
		return true;
	else if (!path1)
		return false;

	/* Both non-NULL. */
	p1 = xstrdup(path1);
	p2 = xstrdup(path2);
	tok1 = strtok_r(p1, "/", &save_ptr1);
	tok2 = strtok_r(p2, "/", &save_ptr2);

	while (tok1 && tok2) {
		if (xstrcmp(tok1, tok2)) {
			ret = false;
			break;
		}
		tok1 = strtok_r(NULL, "/", &save_ptr1);
		tok2 = strtok_r(NULL, "/", &save_ptr2);
	}

	if (tok2 && !tok1)
		ret = false;

	xfree(p1);
	xfree(p2);
	return ret;
}

extern int validate_acctg_freq(char *acctg_freq)
{
	int i;
	char *save_ptr = NULL, *tok, *tmp;
	bool valid;
	int rc = SLURM_SUCCESS;

	if (!acctg_freq)
		return rc;

	tmp = xstrdup(acctg_freq);
	tok = strtok_r(tmp, ",", &save_ptr);
	while (tok) {
		valid = false;
		for (i = 0; i < PROFILE_CNT; i++)
			if (acct_gather_parse_freq(i, tok) != -1) {
				valid = true;
				break;
			}

		if (!valid) {
			error("Invalid --acctg-freq specification: %s", tok);
			rc = SLURM_ERROR;
		}
		tok = strtok_r(NULL, ",", &save_ptr);
	}
	xfree(tmp);

	return rc;
}

/*
 * Format a tres_per_* argument
 * dest OUT - resulting string
 * prefix IN - TRES type (e.g. "gres/gpu")
 * src IN - user input, can include multiple comma-separated specifications
 */
extern void xfmt_tres(char **dest, char *prefix, char *src)
{
	char *result = NULL, *save_ptr = NULL, *sep = "", *tmp, *tok;

	if (!src || (src[0] == '\0'))
		return;
	if (*dest) {
		result = xstrdup(*dest);
		sep = ",";
	}
	tmp = xstrdup(src);
	tok = strtok_r(tmp, ",", &save_ptr);
	while (tok) {
		xstrfmtcat(result, "%s%s:%s", sep, prefix, tok);
		sep = ",";
		tok = strtok_r(NULL, ",", &save_ptr);
	}
	xfree(tmp);
	*dest = result;
}

/*
 * Format a tres_freq argument
 * dest OUT - resulting string
 * prefix IN - TRES type (e.g. "gres/gpu")
 * src IN - user input
 */
extern void xfmt_tres_freq(char **dest, char *prefix, char *src)
{
	char *result = NULL, *sep = "";

	if (!src || (src[0] == '\0'))
		return;
	if (*dest) {
		result = xstrdup(*dest);
		sep = ";";
	}
	xstrfmtcat(result, "%s%s:%s", sep, prefix, src);
	*dest = result;
}

extern bool valid_runtime_directory(char *runtime_dir)
{
	/*
	 * systemd >= v240 prepends "/run" to generate RUNTIME_DIRECTORY
	 * environment variable off of RuntimeDirectory unit option.
	 *
	 * Example:
	 * RuntimeDirectory=foo/bar results in RUNTIME_DIRECTORY=/run/foo/bar.
	 */
	if (xstrncmp(runtime_dir, "/run/", 5) || (strlen(runtime_dir) < 6))
		return false;

	return true;
}
