/*****************************************************************************\
 *  slurm_resource_info.c - Functions to determine number of available resources
 *****************************************************************************
 *  Copyright (C) 2006 Hewlett-Packard Development Company, L.P.
 *  Written by Susanne M. Balle, <susanne.balle@hp.com>
 *  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 <ctype.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>

#include "slurm/slurm.h"
#include "slurm/slurm_errno.h"

#include "src/common/log.h"
#include "src/common/read_config.h"
#include "src/common/slurm_protocol_api.h"
#include "src/common/slurm_resource_info.h"
#include "src/common/xmalloc.h"
#include "src/common/xstring.h"

/*
 * First clear all of the bits in "*data" which are set in "clear_mask".
 * Then set all of the bits in "*data" that are set in "set_mask".
 */
static void _clear_then_set(int *data, int clear_mask, int set_mask)
{
	*data &= ~clear_mask;
	*data |= set_mask;
}

/*
 * _isvalue
 * returns 1 is the argument appears to be a value, 0 otherwise
 */
static int _isvalue(char *arg) {
    	if (isdigit((int)*arg)) { /* decimal values and 0x... hex values */
	    	return 1;
	}

	while (isxdigit((int)*arg)) { /* hex values not preceded by 0x */
		arg++;
	}
	if (*arg == ',' || *arg == '\0') { /* end of field or string */
	    	return 1;
	}

	return 0;	/* not a value */
}

/* Expand a list of CPU/memory maps or masks containing multipliers.
 * For example, change "1*4,2*4" to "1,1,1,1,2,2,2,2"
 * list IN - input mask or map
 * type IN - "mask_cpu", "map_mem", used for error messages
 * error_code - output SLURM_SUCCESS or SLURM_ERROR on failure
 * RET - output mask or map, value must be xfreed */
static char *_expand_mult(char *list, char *type, int *error_code)
{
	char *ast, *end_ptr = NULL, *result = NULL, *save_ptr = NULL;
	char *sep = "", *tmp, *tok;
	int (*isfunc) (int) = isdigit;
	bool mask = false;

	*error_code = SLURM_SUCCESS;

	if (!list)		/* Nothing to convert */
		return NULL;

	tmp = xstrdup(list);

	if (!xstrncmp(type, "mask", 4)) {
		isfunc = isxdigit;
		mask = true;
	}

	tok = strtok_r(tmp, ",", &save_ptr);
	while (tok) {
		long int i;
		long int count = 1;
		if (mask && !xstrncmp(tok, "0x", 2))
			tok+=2;

		ast = strchr(tok, '*');
		if (ast) {
			/* Starting from 1 since we know that ast[0] == '*' */
			for (int i = 1; ast[i]; i++)
				if (!isdigit(ast[i])) {
					error("Failed to validate number: %s, the offending character is %c",
					      ast, ast[i]);
					*error_code = SLURM_ERROR;
					return 0;
				}

			count = strtol(ast + 1, &end_ptr, 10);
			if ((count <= 0) || (end_ptr[0] != '\0') ||
			    (count == LONG_MIN) || (count == LONG_MAX)) {
				error("Invalid %s multiplier: %s",
				      type, ast + 1);
				xfree(result);
				*error_code = SLURM_ERROR;
				break;
			}
			ast[0] = '\0';
		}

		for (i = 0; tok[i]; i++) {
			if (!isfunc(tok[i])) {
				error("Failed to validate number: %s, the offending character is %c",
				      tok, tok[i]);
				*error_code = SLURM_ERROR;
				return 0;
			}
		}

		for (i = 0; i < count; i++) {
			xstrfmtcat(result, "%s%s", sep, tok);
			sep = ",";
		}
		tok = strtok_r(NULL, ",", &save_ptr);
	}
	xfree(tmp);

	if (!result) {
		error("Failed to expand list: '%s'", list);
		*error_code = SLURM_ERROR;
	}

	return result;
}

static bool _have_task_affinity(void)
{
	if (!xstrcmp(slurm_conf.task_plugin, "task/none"))
		return false;
	return true;
}

/*
 * slurm_sprint_cpu_bind_type
 *
 * Given a cpu_bind_type, report all flag settings in str
 * IN  - cpu_bind_type
 * OUT - str
 */
void slurm_sprint_cpu_bind_type(char *str, cpu_bind_type_t cpu_bind_type)
{
	if (!str)
		return;

	str[0] = '\0';

	if (cpu_bind_type & CPU_BIND_VERBOSE)
		strcat(str, "verbose,");

	if (cpu_bind_type & CPU_BIND_TO_THREADS)
		strcat(str, "threads,");
	if (cpu_bind_type & CPU_BIND_TO_CORES)
		strcat(str, "cores,");
	if (cpu_bind_type & CPU_BIND_TO_SOCKETS)
		strcat(str, "sockets,");
	if (cpu_bind_type & CPU_BIND_TO_LDOMS)
		strcat(str, "ldoms,");
	if (cpu_bind_type & CPU_BIND_NONE)
		strcat(str, "none,");
	if (cpu_bind_type & CPU_BIND_MAP)
		strcat(str, "map_cpu,");
	if (cpu_bind_type & CPU_BIND_MASK)
		strcat(str, "mask_cpu,");
	if (cpu_bind_type & CPU_BIND_LDRANK)
		strcat(str, "rank_ldom,");
	if (cpu_bind_type & CPU_BIND_LDMAP)
		strcat(str, "map_ldom,");
	if (cpu_bind_type & CPU_BIND_LDMASK)
		strcat(str, "mask_ldom,");
	if (cpu_bind_type & CPU_BIND_ONE_THREAD_PER_CORE)
		strcat(str, "one_thread,");

	if (cpu_bind_type & CPU_AUTO_BIND_TO_THREADS)
		strcat(str, "autobind=threads,");
	if (cpu_bind_type & CPU_AUTO_BIND_TO_CORES)
		strcat(str, "autobind=cores,");
	if (cpu_bind_type & CPU_AUTO_BIND_TO_SOCKETS)
		strcat(str, "autobind=sockets,");

	if (cpu_bind_type & CPU_BIND_OFF)
		strcat(str, "off,");

	if (*str) {
		str[strlen(str)-1] = '\0';	/* remove trailing ',' */
	} else {
	    	strcat(str, "(null type)");	/* no bits set */
	}
}

/*
 * slurm_xstr_mem_bind_type
 *
 * Given a mem_bind_type, report all flag settings in str
 * IN  - mem_bind_type
 * OUT - str
 */
extern char *slurm_xstr_mem_bind_type(mem_bind_type_t mem_bind_type)
{
	char *str = NULL;

	if (mem_bind_type & MEM_BIND_VERBOSE)
		xstrcat(str, "verbose,");
	if (mem_bind_type & MEM_BIND_PREFER)
		xstrcat(str, "prefer,");
	if (mem_bind_type & MEM_BIND_NONE)
		xstrcat(str, "none,");
	if (mem_bind_type & MEM_BIND_RANK)
		xstrcat(str, "rank,");
	if (mem_bind_type & MEM_BIND_LOCAL)
		xstrcat(str, "local,");
	if (mem_bind_type & MEM_BIND_MAP)
		xstrcat(str, "map_mem,");
	if (mem_bind_type & MEM_BIND_MASK)
		xstrcat(str, "mask_mem,");

	if (str) {
		str[strlen(str)-1] = '\0';	/* remove trailing ',' */
	}

	return str;
}

void slurm_print_cpu_bind_help(void)
{
	if (!_have_task_affinity()) {
		printf("CPU bind options not supported with current "
		       "configuration\n");
	} else {
		printf(
"CPU bind options:\n"
"    --cpu-bind=         Bind tasks to CPUs\n"
"        q[uiet]         quietly bind before task runs (default)\n"
"        v[erbose]       verbosely report binding before task runs\n"
"        no[ne]          don't bind tasks to CPUs (default)\n"
"        map_cpu:<list>  specify a CPU ID binding for each task\n"
"                        where <list> is <cpuid1>,<cpuid2>,...<cpuidN>\n"
"        mask_cpu:<list> specify a CPU ID binding mask for each task\n"
"                        where <list> is <mask1>,<mask2>,...<maskN>\n"
"        rank_ldom       bind task by rank to CPUs in a NUMA locality domain\n"
"        map_ldom:<list> specify a NUMA locality domain ID for each task\n"
"                        where <list> is <ldom1>,<ldom2>,...<ldomN>\n"
"        mask_ldom:<list>specify a NUMA locality domain ID mask for each task\n"
"                        where <list> is <mask1>,<mask2>,...<maskN>\n"
"        sockets         auto-generated masks bind to sockets\n"
"        cores           auto-generated masks bind to cores\n"
"        threads         auto-generated masks bind to threads\n"
"        ldoms           auto-generated masks bind to NUMA locality domains\n"
"        help            show this help message\n");
	}
}

/*
 * verify cpu_bind arguments
 *
 * we support different launch policy names
 * we also allow a verbose setting to be specified
 *     --cpu-bind=threads
 *     --cpu-bind=cores
 *     --cpu-bind=sockets
 *     --cpu-bind=v
 *     --cpu-bind=rank,v
 *     --cpu-bind=rank
 *     --cpu-bind={MAP_CPU|MASK_CPU}:0,1,2,3,4
 *
 * arg IN - user task binding option
 * cpu_bind OUT - task binding string
 * flags OUT OUT - task binding flags
 * RET SLURM_SUCCESS, SLURM_ERROR (-1) on failure, 1 for return for "help" arg
 */
extern int slurm_verify_cpu_bind(const char *arg, char **cpu_bind,
				 cpu_bind_type_t *flags)
{
	char *buf, *p, *tok;
	int bind_bits, bind_to_bits;
	bool have_binding = _have_task_affinity();
	bool log_binding = true;
	int rc = SLURM_SUCCESS;

	bind_bits = CPU_BIND_NONE | CPU_BIND_MAP | CPU_BIND_MASK |
		    CPU_BIND_LDRANK | CPU_BIND_LDMAP | CPU_BIND_LDMASK;
	bind_to_bits = CPU_BIND_TO_SOCKETS | CPU_BIND_TO_CORES |
		       CPU_BIND_TO_THREADS | CPU_BIND_TO_LDOMS;

    	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] == ',') && (!_isvalue(&(p[1]))))
			p[0] = ';';
		p++;
	}

	p = buf;
	while ((rc == SLURM_SUCCESS) && (tok = strsep(&p, ";"))) {
		if (xstrcasecmp(tok, "help") == 0) {
			slurm_print_cpu_bind_help();
			xfree(buf);
			return 1;
		}
		if (!have_binding && log_binding) {
			info("cluster configuration lacks support for cpu binding");
			log_binding = false;
		}
		if ((xstrcasecmp(tok, "q") == 0) ||
			   (xstrcasecmp(tok, "quiet") == 0)) {
		        *flags &= ~CPU_BIND_VERBOSE;
		} else if ((xstrcasecmp(tok, "v") == 0) ||
			   (xstrcasecmp(tok, "verbose") == 0)) {
		        *flags |= CPU_BIND_VERBOSE;
		} else if ((xstrcasecmp(tok, "one_thread") == 0)) {
		        *flags |= CPU_BIND_ONE_THREAD_PER_CORE;
		} else if ((xstrcasecmp(tok, "no") == 0) ||
			   (xstrcasecmp(tok, "none") == 0)) {
			_clear_then_set((int *)flags, bind_bits, CPU_BIND_NONE);
			xfree(*cpu_bind);
		} else if (xstrcasecmp(tok, "rank") == 0) {
			info("Ignoring --cpu-bind=rank. Rank binding is obsolete.");
			xfree(*cpu_bind);
		} else if ((xstrncasecmp(tok, "map_cpu", 7) == 0) ||
		           (xstrncasecmp(tok, "mapcpu", 6) == 0)) {
			char *list;
			(void) strsep(&tok, ":=");
			list = strsep(&tok, ":=");  /* THIS IS NOT REDUNDANT */
			_clear_then_set((int *)flags, bind_bits, CPU_BIND_MAP);
			xfree(*cpu_bind);
			if (list && *list) {
				*cpu_bind = _expand_mult(list, "map_cpu", &rc);
			} else {
				error("missing list for \"--cpu-bind=map_cpu:<list>\"");
				rc = SLURM_ERROR;
			}
		} else if ((xstrncasecmp(tok, "mask_cpu", 8) == 0) ||
		           (xstrncasecmp(tok, "maskcpu", 7) == 0)) {
			char *list;
			(void) strsep(&tok, ":=");
			list = strsep(&tok, ":=");  /* THIS IS NOT REDUNDANT */
			_clear_then_set((int *)flags, bind_bits, CPU_BIND_MASK);
			xfree(*cpu_bind);
			if (list && *list) {
				*cpu_bind = _expand_mult(list, "mask_cpu", &rc);
			} else {
				error("missing list for \"--cpu-bind=mask_cpu:<list>\"");
				rc = SLURM_ERROR;
			}
		} else if (xstrcasecmp(tok, "rank_ldom") == 0) {
			_clear_then_set((int *)flags, bind_bits,
					CPU_BIND_LDRANK);
			xfree(*cpu_bind);
		} else if ((xstrncasecmp(tok, "map_ldom", 8) == 0) ||
		           (xstrncasecmp(tok, "mapldom", 7) == 0)) {
			char *list;
			(void) strsep(&tok, ":=");
			list = strsep(&tok, ":=");  /* THIS IS NOT REDUNDANT */
			_clear_then_set((int *)flags, bind_bits,
					CPU_BIND_LDMAP);
			xfree(*cpu_bind);
			if (list && *list) {
				*cpu_bind = _expand_mult(list, "map_ldom", &rc);
			} else {
				error("missing list for \"--cpu-bind=map_ldom:<list>\"");
				rc = SLURM_ERROR;
			}
		} else if ((xstrncasecmp(tok, "mask_ldom", 9) == 0) ||
		           (xstrncasecmp(tok, "maskldom", 8) == 0)) {
			char *list;
			(void) strsep(&tok, ":=");
			list = strsep(&tok, ":=");  /* THIS IS NOT REDUNDANT */
			_clear_then_set((int *)flags, bind_bits,
					CPU_BIND_LDMASK);
			xfree(*cpu_bind);
			if (list && *list) {
				*cpu_bind = _expand_mult(list, "mask_ldom",&rc);
			} else {
				error("missing list for \"--cpu-bind="
				      "mask_ldom:<list>\"");
				rc = SLURM_ERROR;
			}
		} else if ((xstrcasecmp(tok, "socket") == 0) ||
		           (xstrcasecmp(tok, "sockets") == 0)) {
			_clear_then_set((int *)flags, bind_to_bits,
				       CPU_BIND_TO_SOCKETS);
		} else if ((xstrcasecmp(tok, "core") == 0) ||
		           (xstrcasecmp(tok, "cores") == 0)) {
			_clear_then_set((int *)flags, bind_to_bits,
				       CPU_BIND_TO_CORES);
		} else if ((xstrcasecmp(tok, "thread") == 0) ||
		           (xstrcasecmp(tok, "threads") == 0)) {
			_clear_then_set((int *)flags, bind_to_bits,
				       CPU_BIND_TO_THREADS);
		} else if ((xstrcasecmp(tok, "ldom") == 0) ||
		           (xstrcasecmp(tok, "ldoms") == 0)) {
			_clear_then_set((int *)flags, bind_to_bits,
				       CPU_BIND_TO_LDOMS);
		} else {
			error("unrecognized --cpu-bind argument \"%s\"", tok);
			rc = SLURM_ERROR;
		}
	}
	xfree(buf);
	if (rc)
		fatal("Failed to parse --cpu-bind= values.");

	return rc;
}

/*
 * Translate a CPU bind string to its equivalent numeric value
 * cpu_bind_str IN - string to translate
 * flags OUT - equlvalent numeric value
 * RET SLURM_SUCCESS or SLURM_ERROR
 */
extern int xlate_cpu_bind_str(char *cpu_bind_str, uint32_t *flags)
{
	int rc = SLURM_SUCCESS;
	char *save_ptr = NULL, *tok, *tmp;
	bool have_bind_type = false;

	*flags = 0;
	if (!cpu_bind_str)
		return rc;

	tmp = xstrdup(cpu_bind_str);
	tok = strtok_r(tmp, ",;", &save_ptr);
	while (tok) {
		if ((xstrcasecmp(tok, "no") == 0) ||
		    (xstrcasecmp(tok, "none") == 0)) {
			if (have_bind_type) {
				rc = SLURM_ERROR;
				break;
			} else {
				*flags |= CPU_BIND_NONE;
				have_bind_type = true;
			}
		} else if ((xstrcasecmp(tok, "socket") == 0) ||
			   (xstrcasecmp(tok, "sockets") == 0)) {
			if (have_bind_type) {
				rc = SLURM_ERROR;
				break;
			} else {
				*flags |= CPU_BIND_TO_SOCKETS;
				have_bind_type = true;
			}
		} else if ((xstrcasecmp(tok, "ldom") == 0) ||
			   (xstrcasecmp(tok, "ldoms") == 0)) {
			if (have_bind_type) {
				rc = SLURM_ERROR;
				break;
			} else {
				*flags |= CPU_BIND_TO_LDOMS;
				have_bind_type = true;
			}
		} else if ((xstrcasecmp(tok, "core") == 0) ||
			   (xstrcasecmp(tok, "cores") == 0)) {
			if (have_bind_type) {
				rc = SLURM_ERROR;
				break;
			} else {
				*flags |= CPU_BIND_TO_CORES;
				have_bind_type = true;
			}
		} else if ((xstrcasecmp(tok, "thread") == 0) ||
			   (xstrcasecmp(tok, "threads") == 0)) {
			if (have_bind_type) {
				rc = SLURM_ERROR;
				break;
			} else {
				*flags |= CPU_BIND_TO_THREADS;
				have_bind_type = true;
			}
		} else if (xstrcasecmp(tok, "off") == 0) {
			if (have_bind_type) {
				rc = SLURM_ERROR;
				break;
			} else {
				*flags |= CPU_BIND_OFF;
				have_bind_type = true;
			}
		} else if ((xstrcasecmp(tok, "v") == 0) ||
			   (xstrcasecmp(tok, "verbose") == 0)) {
		        *flags |= CPU_BIND_VERBOSE;
		} else {
			/* Other options probably not desirable to support */
			rc = SLURM_ERROR;
			break;
		}
		tok = strtok_r(NULL, ",;", &save_ptr);
	}
	xfree(tmp);

	return rc;
}

void slurm_print_mem_bind_help(void)
{
			printf(
"Memory bind options:\n"
"    --mem-bind=         Bind memory to locality domains (ldom)\n"
"        nosort          avoid sorting pages at startup\n"
"        sort            sort pages at startup\n"
"        q[uiet]         quietly bind before task runs (default)\n"
"        v[erbose]       verbosely report binding before task runs\n"
"        no[ne]          don't bind tasks to memory (default)\n"
"        rank            bind by task rank\n"
"        local           bind to memory local to processor\n"
"        map_mem:<list>  specify a memory binding for each task\n"
"                        where <list> is <cpuid1>,<cpuid2>,...<cpuidN>\n"
"        mask_mem:<list> specify a memory binding mask for each tasks\n"
"                        where <list> is <mask1>,<mask2>,...<maskN>\n"
"        help            show this help message\n");
}

/*
 * verify mem_bind arguments
 *
 * we support different memory binding names
 * we also allow a verbose setting to be specified
 *     --mem-bind=v
 *     --mem-bind=rank,v
 *     --mem-bind=rank
 *     --mem-bind={MAP_MEM|MASK_MEM}:0,1,2,3,4
 *
 * returns -1 on error, 0 otherwise
 */
int slurm_verify_mem_bind(const char *arg, char **mem_bind,
			  mem_bind_type_t *flags)
{
	char *buf, *p, *tok;
	int bind_bits = MEM_BIND_NONE|MEM_BIND_RANK|MEM_BIND_LOCAL|
		MEM_BIND_MAP|MEM_BIND_MASK;
	int rc = SLURM_SUCCESS;

	if (arg == NULL) {
	    	return 0;
	}

    	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] == ',') && (!_isvalue(&(p[1]))))
			p[0] = ';';
		p++;
	}

	p = buf;
	while ((rc == SLURM_SUCCESS) && (tok = strsep(&p, ";"))) {
		if (xstrcasecmp(tok, "help") == 0) {
			slurm_print_mem_bind_help();
			xfree(buf);
			return 1;
		} else if ((xstrcasecmp(tok, "p") == 0) ||
			   (xstrcasecmp(tok, "prefer") == 0)) {
		        *flags |= MEM_BIND_PREFER;
		} else if ((xstrcasecmp(tok, "q") == 0) ||
			   (xstrcasecmp(tok, "quiet") == 0)) {
		        *flags &= ~MEM_BIND_VERBOSE;
		} else if ((xstrcasecmp(tok, "v") == 0) ||
			   (xstrcasecmp(tok, "verbose") == 0)) {
		        *flags |= MEM_BIND_VERBOSE;
		} else if ((xstrcasecmp(tok, "no") == 0) ||
			   (xstrcasecmp(tok, "none") == 0)) {
			_clear_then_set((int *)flags, bind_bits, MEM_BIND_NONE);
			xfree(*mem_bind);
		} else if (xstrcasecmp(tok, "rank") == 0) {
			_clear_then_set((int *)flags, bind_bits, MEM_BIND_RANK);
			xfree(*mem_bind);
		} else if (xstrcasecmp(tok, "local") == 0) {
			_clear_then_set((int *)flags, bind_bits,MEM_BIND_LOCAL);
			xfree(*mem_bind);
		} else if ((xstrncasecmp(tok, "map_mem", 7) == 0) ||
		           (xstrncasecmp(tok, "mapmem", 6) == 0)) {
			char *list;
			(void) strsep(&tok, ":=");
			list = strsep(&tok, ":=");  /* THIS IS NOT REDUNDANT */
			_clear_then_set((int *)flags, bind_bits, MEM_BIND_MAP);
			xfree(*mem_bind);
			if (list && *list) {
				*mem_bind = _expand_mult(list, "map_mem", &rc);
			} else {
				error("missing list for \"--mem-bind=map_mem:<list>\"");
				rc = SLURM_ERROR;
			}
		} else if ((xstrncasecmp(tok, "mask_mem", 8) == 0) ||
		           (xstrncasecmp(tok, "maskmem", 7) == 0)) {
			char *list;
			(void) strsep(&tok, ":=");
			list = strsep(&tok, ":=");  /* THIS IS NOT REDUNDANT */
			_clear_then_set((int *)flags, bind_bits, MEM_BIND_MASK);
			xfree(*mem_bind);
			if (list && *list) {
				*mem_bind = _expand_mult(list, "mask_mem", &rc);
			} else {
				error("missing list for \"--mem-bind=mask_mem:<list>\"");
				rc = SLURM_ERROR;
			}
		} else {
			error("unrecognized --mem-bind argument \"%s\"", tok);
			rc = SLURM_ERROR;
		}
	}

	xfree(buf);
	return rc;
}
