/*****************************************************************************\
 *  update_job.c - update job functions for scontrol.
 *****************************************************************************
 *  Copyright (C) 2002-2007 The Regents of the University of California.
 *  Copyright (C) 2008-2010 Lawrence Livermore National Security.
 *  Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER).
 *  Written by Morris Jette <jette1@llnl.gov>
 *  CODE-OCEC-09-009. All rights reserved.
 *
 *  This file is part of Slurm, a resource management program.
 *  For details, see <https://slurm.schedmd.com/>.
 *  Please also read the included file: DISCLAIMER.
 *
 *  Slurm is free software; you can redistribute it and/or modify it under
 *  the terms of the GNU General Public License as published by the Free
 *  Software Foundation; either version 2 of the License, or (at your option)
 *  any later version.
 *
 *  In addition, as a special exception, the copyright holders give permission
 *  to link the code of portions of this program with the OpenSSL library under
 *  certain conditions as described in each individual source file, and
 *  distribute linked combinations including the two. You must obey the GNU
 *  General Public License in all respects for all of the code used other than
 *  OpenSSL. If you modify file(s) with this exception, you may extend this
 *  exception to your version of the file(s), but you are not obligated to do
 *  so. If you do not wish to do so, delete this exception statement from your
 *  version.  If you delete this exception statement from all source files in
 *  the program, then also delete it here.
 *
 *  Slurm is distributed in the hope that it will be useful, but WITHOUT ANY
 *  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 *  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 *  details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with Slurm; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA.
\*****************************************************************************/

#include "scontrol.h"
#include "src/common/env.h"
#include "src/common/proc_args.h"
#include "src/common/sluid.h"
#include "src/common/uid.h"
#include "src/interfaces/gres.h"

typedef struct job_ids {
	uint32_t job_id;
	uint32_t array_job_id;
	uint32_t array_task_id;
	char *   array_task_str;
} job_ids_t;

static uint32_t	_get_job_time(const char *job_id_str);
static bool	_is_job_id(char *job_str);
static bool	_is_single_job(char *job_id_str);
static char *	_job_name2id(char *job_name, uint32_t job_uid);
static char *	_next_job_id(void);
static void _update_job_size(slurm_step_id_t step_id);

/* Local variables for managing job IDs */
static char *local_job_str = NULL;

/* Confirm that contents of job_str is valid comma delimited list of job IDs */
static bool _is_job_id(char *job_str)
{
	bool have_plus = false, have_under = false;
	int bracket_cnt = 0;
	int i;

	if (!job_str)
		return false;

	local_job_str = xstrdup(job_str);
	for (i = 0; local_job_str[i]; i++) {
		if (local_job_str[i] == 's') {
			int end_sluid = i + SLUID_STR_BYTES - 1;
			char c = local_job_str[end_sluid];
			local_job_str[end_sluid] = '\0';
			if (!str2sluid(&local_job_str[i]))
				goto fail;
			local_job_str[end_sluid] = c;
			i += SLUID_STR_BYTES - 2;
		} else if (local_job_str[i] == '+') {
			if (have_plus)
				goto fail;	/* multiple '+' in name */
			have_plus = true;
		} else if (local_job_str[i] == '_') {
			if (have_under)
				goto fail;	/* multiple '_' in name */
			have_under = true;
		} else if (local_job_str[i] == '[') {
			bracket_cnt++;
		} else if (local_job_str[i] == ']') {
			bracket_cnt--;
		} else if (local_job_str[i] == '-') {
			if ((bracket_cnt == 0) && !have_under)
				goto fail;	/* stray '-' */
		} else if ((local_job_str[i] == ',') ||
			   (local_job_str[i] == ' ')) {
			if (bracket_cnt == 0) {
				local_job_str[i] = '^';	/* New separator */
				have_plus  = false;
				have_under = false;
			}
		} else if ((local_job_str[i] < '0') || (local_job_str[i] > '9'))
			goto fail;	/* Unexpected character */
	}

	if (bracket_cnt != 0)
		goto fail;	/* Unbalanced brackets */
	return true;

fail:	xfree(local_job_str);
	debug("Character %d in %s is invalid job ID", i, job_str);
	return false;
}

/* Get the next job ID from local variables set up by _is_job_id() */
static char *_next_job_id(void)
{
	static hostlist_t *hl = NULL;
	static char *save_ptr = NULL;
	static char *next_job_id = NULL;
	static char *task_id_spec = NULL;
	char *job_id_str = NULL, *bracket_ptr, *under_ptr;
	char *tmp_str, *end_job_str;
	int i;

	/* Clean up from previous calls */
	xfree(next_job_id);

	if (hl) {
		/* Process job ID regular expression using previously
		 * established hostlist data structure */
		tmp_str = hostlist_shift(hl);
		if (tmp_str) {
			next_job_id = xstrdup(tmp_str);
			free(tmp_str);
			if (task_id_spec) {
				xstrcat(next_job_id, "_");
				xstrcat(next_job_id, task_id_spec);
			}
			return next_job_id;
		}
		hostlist_destroy(hl);
		hl = NULL;
	}

	/* Get next token */
	xfree(task_id_spec);
	if (local_job_str && !save_ptr)	/* Get first token */
		job_id_str = strtok_r(local_job_str, "^", &save_ptr);
	else if (save_ptr)		/* Get next token */
		job_id_str = strtok_r(NULL, "^", &save_ptr);

	if (!job_id_str)	/* No more tokens */
		goto fini;

	under_ptr = strchr(job_id_str, '_');
	if (under_ptr) {
		if (under_ptr[1] == '[') {
			/* Strip brackets from job array task ID spec */
			task_id_spec = xstrdup(under_ptr + 2);
			for (i = 0; task_id_spec[i]; i++) {
				if (task_id_spec[i] == ']') {
					task_id_spec[i] = '\0';
					break;
				}
			}
		} else {
			task_id_spec = xstrdup(under_ptr + 1);
		}
	}

	bracket_ptr = strchr(job_id_str, '[');
	if (bracket_ptr && (!under_ptr || (bracket_ptr < under_ptr))) {
		/* Job ID specification uses regular expression */
		tmp_str = xstrdup(job_id_str);
		if ((end_job_str = strchr(tmp_str, '_')))
			end_job_str[0] = '\0';
		hl = hostlist_create(tmp_str);
		if (!hl) {
			error("Invalid job id: %s", job_id_str);
			xfree(tmp_str);
			goto fini;
		}
		xfree(tmp_str);
		tmp_str = hostlist_shift(hl);
		if (!tmp_str) {
			error("Invalid job id: %s", job_id_str);
			hostlist_destroy(hl);
			goto fini;
		}
		next_job_id = xstrdup(tmp_str);
		free(tmp_str);
	} else if (under_ptr) {
		under_ptr[0] = '\0';
		next_job_id = xstrdup(job_id_str);
		under_ptr[0] = '_';
	} else {
		next_job_id = xstrdup(job_id_str);
	}

	if (task_id_spec) {
		xstrcat(next_job_id, "_");
		xstrcat(next_job_id, task_id_spec);
	}

	return next_job_id;

fini:	xfree(local_job_str);
	save_ptr = NULL;
	return NULL;
}

/*
 * scontrol_hold - perform some job hold/release operation
 * IN op	- hold/release operation
 * IN job_str	- a job ID or job name
 * RET 0 if no slurm error, errno otherwise. parsing error prints
 *		error message and returns 0
 */
extern int
scontrol_hold(char *op, char *job_str)
{
	static uint32_t last_job_id = NO_VAL;
	static job_info_msg_t *jobs = NULL;
	job_array_resp_msg_t *resp = NULL;
	int i, rc = SLURM_SUCCESS, rc2;
	int j;
	job_desc_msg_t job_msg;
	uint32_t job_id = 0;
	char *job_name = NULL;
	char *job_id_str = NULL;
	slurm_job_info_t *job_ptr;
	slurm_step_id_t step_id = SLURM_STEP_ID_INITIALIZER;

	if (job_str && !xstrncasecmp(job_str, "JobID=", 6))
		job_str += 6;
	if (job_str && !xstrncasecmp(job_str, "Job=", 4))
		job_str += 4;

	slurm_init_job_desc_msg (&job_msg);
	if (!xstrncasecmp(op, "holdu", 5) ||
	    !xstrncasecmp(op, "uhold", 5)) {
		job_msg.priority = 0;
		job_msg.alloc_sid = ALLOC_SID_USER_HOLD;
	} else if (!xstrncasecmp(op, "hold", 4)) {
		job_msg.priority = 0;
		job_msg.alloc_sid = 0;
	} else
		job_msg.priority = INFINITE;

	if (_is_job_id(job_str)) {
		while ((job_msg.job_id_str = _next_job_id())) {
			rc2 = slurm_update_job2(&job_msg, &resp);
			if (rc2 != SLURM_SUCCESS) {
				rc2 = errno;
				rc = MAX(rc, rc2);
				exit_code = 1;
				if (quiet_flag != 1) {
					fprintf(stderr, "%s for job %s\n",
						slurm_strerror(rc2),
						job_msg.job_id_str);
				}
			} else if (resp) {
				for (i = 0; i < resp->job_array_count; i++) {
					if ((resp->error_code[i]==SLURM_SUCCESS)
					    && (resp->job_array_count == 1))
						continue;
					exit_code = 1;
					if (quiet_flag == 1)
						continue;
					fprintf(stderr, "%s: %s\n",
						resp->job_array_id[i],
						slurm_strerror(resp->
							       error_code[i]));
				}
				slurm_free_job_array_resp(resp);
				resp = NULL;
			}
		}
		return rc;
	} else if (job_str) {
		if (!xstrncasecmp(job_str, "Name=", 5)) {
			job_str += 5;
			job_id = 0;
			job_name = job_str;
			last_job_id = NO_VAL;
		} else if (!xstrncasecmp(job_str, "JobName=", 8)) {
			job_str += 8;
			job_id = 0;
			job_name = job_str;
			last_job_id = NO_VAL;
		} else {
			exit_code = 1;
			rc = ESLURM_INVALID_JOB_ID;
			errno = rc;
			if (quiet_flag != 1) {
				fprintf(stderr, "%s for job %s\n",
					slurm_strerror(rc), job_str);
			}
			return rc;
		}
	} else {
		last_job_id = NO_VAL;	/* Refresh cache on next call */
		return 0;
	}

	if (last_job_id != job_id) {
		step_id.job_id = job_id;
		step_id.sluid = 0;

		if (scontrol_load_job(&jobs, step_id)) {
			if (quiet_flag == -1)
				slurm_perror ("slurm_load_job error");
			return 1;
		}
		last_job_id = job_id;
	}

	/* set current user, needed e.g., for AllowGroups checks */
	for (i = 0, job_ptr = jobs->job_array; i < jobs->record_count;
	     i++, job_ptr++) {
		if (xstrcmp(job_name, job_ptr->name))
			continue;

		if (!IS_JOB_PENDING(job_ptr)) {
			if (job_ptr->array_task_id != NO_VAL)
				continue;
			errno = ESLURM_JOB_NOT_PENDING;
			rc = MAX(rc, ESLURM_JOB_NOT_PENDING);
		}

		if (job_ptr->array_task_str) {
			xstrfmtcat(job_id_str, "%u_%s",
				   job_ptr->array_job_id,
				   job_ptr->array_task_str);
		} else if (job_ptr->array_task_id != NO_VAL) {
			xstrfmtcat(job_id_str, "%u_%u",
				   job_ptr->array_job_id,
				   job_ptr->array_task_id);
		} else {
			xstrfmtcat(job_id_str, "%u", job_ptr->step_id.job_id);
		}
		job_msg.job_id_str = job_id_str;
		rc2 = slurm_update_job2(&job_msg, &resp);
		if (rc2 != SLURM_SUCCESS) {
			rc2 = errno;
			rc = MAX(rc, rc2);
			exit_code = 1;
			if (quiet_flag != 1) {
				fprintf(stderr, "%s for job %s\n",
					slurm_strerror(rc2),
					job_msg.job_id_str);
			}
		} else if (resp) {
			for (j = 0; j < resp->job_array_count; j++) {
				if ((resp->error_code[j] == SLURM_SUCCESS) &&
				    (resp->job_array_count == 1))
					continue;
				exit_code = 1;
				if (quiet_flag == 1)
					continue;
				fprintf(stderr, "%s: %s\n",
					resp->job_array_id[j],
					slurm_strerror(resp->error_code[j]));
			}
			slurm_free_job_array_resp(resp);
			resp = NULL;
		}
		xfree(job_id_str);
	}

	return rc;
}


/*
 * scontrol_suspend - perform some suspend/resume operation
 * IN op - suspend/resume operation
 * IN job_str - a job id
 * RET 0 if no slurm error, errno otherwise. parsing error prints
 *		error message and returns 0
 */
extern void
scontrol_suspend(char *op, char *job_str)
{
	int rc, i;
	job_array_resp_msg_t *resp = NULL;
	char *job_id_str;

	if (!xstrncasecmp(job_str, "jobid=", 6))
		job_str += 6;
	if (!xstrncasecmp(job_str, "job=", 4))
		job_str += 4;

	if (_is_job_id(job_str)) {
		job_id_str = _next_job_id();
		while (job_id_str) {
			if (!xstrncasecmp(op, "suspend", MAX(strlen(op), 2)))
				rc = slurm_suspend2(job_id_str, &resp);
			else
				rc = slurm_resume2(job_id_str, &resp);
			if (rc != SLURM_SUCCESS) {
				exit_code = 1;
				if (quiet_flag != 1) {
					fprintf(stderr, "%s for job %s\n",
						slurm_strerror(errno),
						job_id_str);
				}
			} else if (resp) {
				for (i = 0; i < resp->job_array_count; i++) {
					if ((resp->error_code[i] == SLURM_SUCCESS)
					    && (resp->job_array_count == 1))
						continue;
					exit_code = 1;
					if (quiet_flag == 1)
						continue;
					fprintf(stderr, "%s: %s\n",
						resp->job_array_id[i],
						slurm_strerror(resp->
							       error_code[i]));
				}
				slurm_free_job_array_resp(resp);
				resp = NULL;
			}
			job_id_str = _next_job_id();
		}
	} else {
		exit_code = 1;
		rc = ESLURM_INVALID_JOB_ID;
		errno = rc;
		if (quiet_flag != 1) {
			fprintf(stderr, "%s for job %s\n",
				slurm_strerror(rc), job_str);
		}
	}
}

/*
 * scontrol_requeue - requeue a pending or running batch job
 * IN job_id_str - a job id
 */
extern void
scontrol_requeue(uint32_t flags, char *job_str)
{
	char *job_id_str;
	int rc, i;
	job_array_resp_msg_t *resp = NULL;

	if (!job_str[0]) {
		exit_code = 1;
		return;
	}

	if (!xstrncasecmp(job_str, "jobid=", 6))
		job_str += 6;
	if (!xstrncasecmp(job_str, "job=", 4))
		job_str += 4;

	if (_is_job_id(job_str)) {
		job_id_str = _next_job_id();
		while (job_id_str) {
			rc = slurm_requeue2(job_id_str, flags, &resp);
			if (rc != SLURM_SUCCESS) {
				exit_code = 1;
				if (quiet_flag != 1) {
					fprintf(stderr, "%s for job %s\n",
						slurm_strerror(errno),
						job_id_str);
				}
			} else if (resp) {
				for (i = 0; i < resp->job_array_count; i++) {
					if ((resp->error_code[i] == SLURM_SUCCESS)
					    && (resp->job_array_count == 1))
						continue;
					exit_code = 1;
					if (quiet_flag == 1)
						continue;
					fprintf(stderr, "%s: %s\n",
						resp->job_array_id[i],
						slurm_strerror(resp->
							       error_code[i]));
				}
				slurm_free_job_array_resp(resp);
				resp = NULL;
			}
			job_id_str = _next_job_id();
		}
	} else {
		exit_code = 1;
		rc = ESLURM_INVALID_JOB_ID;
		errno = rc;
		if (quiet_flag != 1) {
			fprintf(stderr, "%s for job %s\n",
				slurm_strerror(rc), job_str);
		}
	}
}

extern void
scontrol_requeue_hold(uint32_t flags, char *job_str)
{
	int rc, i;
	char *job_id_str;
	job_array_resp_msg_t *resp = NULL;

	flags |= JOB_REQUEUE_HOLD;

	if (_is_job_id(job_str)) {
		job_id_str = _next_job_id();
		while (job_id_str) {
			rc = slurm_requeue2(job_id_str, flags, &resp);
			if (rc != SLURM_SUCCESS) {
				exit_code = 1;
				if (quiet_flag != 1) {
					fprintf(stderr, "%s for job %s\n",
						slurm_strerror(errno),
						job_id_str);
				}
			} else if (resp) {
				for (i = 0; i < resp->job_array_count; i++) {
					if ((resp->error_code[i] == SLURM_SUCCESS)
					    && (resp->job_array_count == 1))
						continue;
					exit_code = 1;
					if (quiet_flag == 1)
						continue;
					fprintf(stderr, "%s: %s\n",
						resp->job_array_id[i],
						slurm_strerror(resp->
							       error_code[i]));
				}
				slurm_free_job_array_resp(resp);
				resp = NULL;
			}
			job_id_str = _next_job_id();
		}
	} else {
		exit_code = 1;
		rc = ESLURM_INVALID_JOB_ID;
		errno = rc;
		if (quiet_flag != 1) {
			fprintf(stderr, "%s for job %s\n",
				slurm_strerror(rc), job_str);
		}
	}
}

/*
 * scontrol_top_job - Move the specified job ID to the top of the queue for
 *	a given user ID, partition, account, and QOS.
 * IN job_str - a job id
 */
extern void
scontrol_top_job(char *job_id_str)
{
	int rc;

	if (!xstrncasecmp(job_id_str, "jobid=", 6))
		job_id_str += 6;
	if (!xstrncasecmp(job_id_str, "job=", 4))
		job_id_str += 4;

	rc = slurm_top_job(job_id_str);
	if (rc != SLURM_SUCCESS) {
		exit_code = 1;
		if (quiet_flag != 1) {
			fprintf(stderr, "%s for job %s\n",
				slurm_strerror(errno), job_id_str);
		}
	}
}

/*
 * scontrol_update_job - update the slurm job configuration per the supplied
 *	arguments
 * IN argc - count of arguments
 * IN argv - list of arguments
 * RET 0 if no slurm error, errno otherwise. parsing error prints
 *			error message and returns 0
 */
extern int scontrol_update_job(int argc, char **argv)
{
	bool update_size = false;
	int i, update_cnt = 0, rc = SLURM_SUCCESS, rc2;
	char *tag, *val;
	int taglen, vallen;
	job_desc_msg_t job_msg;
	job_array_resp_msg_t *resp = NULL;
	uint32_t job_uid = NO_VAL;

	slurm_init_job_desc_msg (&job_msg);
	for (i = 0; i < argc; i++) {
		char *add_info = NULL;
		tag = argv[i];
		val = strchr(argv[i], '=');
		if (val) {
			taglen = val - argv[i];
			if ((taglen > 0) && ((val[-1] == '+') ||
					     (val[-1] == '-'))) {
				add_info = val - 1;
				taglen--;
			}
			val++;
			vallen = strlen(val);
		/* Handle any tags that might not have an equal sign here */
		} else if (!xstrncasecmp(tag, "Nice", MAX(strlen(tag), 2))) {
			job_msg.nice = NICE_OFFSET + 100;
			update_cnt++;
			continue;
		} else if (!xstrncasecmp(tag, "ResetAccrueTime",
					 MAX(strlen(tag), 3))) {
			job_msg.bitflags |= RESET_ACCRUE_TIME;
			update_cnt++;
			continue;
		} else if (!val && argv[i + 1]) {
			tag = argv[i];
			taglen = strlen(tag);
			val = argv[++i];
			vallen = strlen(val);
		} else {
			exit_code = 1;
			fprintf (stderr, "Invalid input: %s\n", argv[i]);
			fprintf (stderr, "Request aborted\n");
			return -1;
		}

		if (!xstrncasecmp(tag, "JobId", MAX(taglen, 3))) {
			job_msg.job_id_str = val;
		} else if (!xstrncasecmp(tag, "AdminComment", MAX(taglen, 6))) {
			if (add_info) {
				error("Invalid syntax, AdminComment can not be added/subtracted from.");
				exit_code = 1;
				return 0;
			} else
				job_msg.admin_comment = val;
			update_cnt++;
		} else if (!xstrncasecmp(tag, "SiteFactor", MAX(taglen, 5))) {
			long long tmp_prio;
			tmp_prio = strtoll(val, (char **)NULL, 10);
			if (llabs(tmp_prio) > (NICE_OFFSET - 3)) {
				error("SiteFactor value out of range (+/- %u). Value ignored",
				      NICE_OFFSET - 3);
				exit_code = 1;
				return 0;
			}
			job_msg.site_factor = NICE_OFFSET + tmp_prio;
			update_cnt++;
		} else if (!xstrncasecmp(tag, "ArrayTaskThrottle",
					 MAX(taglen, 10))) {
			int throttle;
			throttle = strtoll(val, (char **) NULL, 10);
			if (throttle < 0) {
				error("Invalid ArrayTaskThrottle value");
				exit_code = 1;
				return 0;
			}
			job_msg.array_inx = val;
			update_cnt++;
		} else if (!xstrncasecmp(tag, "Comment", MAX(taglen, 3))) {
			job_msg.comment = val;
			update_cnt++;
		} else if (!xstrncasecmp(tag, "Clusters", MAX(taglen, 8))) {
			job_msg.clusters = val;
			update_cnt++;
		} else if (!xstrncasecmp(tag, "ClusterFeatures",
					 MAX(taglen, 8))) {
			job_msg.cluster_features = val;
			update_cnt++;
		} else if (!xstrncasecmp(tag, "DelayBoot", MAX(taglen, 5))) {
			int time_sec = time_str2secs(val);
			if (time_sec == NO_VAL) {
				error("Invalid DelayBoot value");
				exit_code = 1;
				return 0;
			}
			job_msg.delay_boot = time_sec;
			update_cnt++;
		} else if (!xstrncasecmp(tag, "Extra", MAX(taglen, 3))) {
			job_msg.extra = val;
			update_cnt++;
		} else if (!xstrncasecmp(tag, "TimeLimit", MAX(taglen, 5))) {
			uint32_t job_current_time, time_limit;

			if (val && ((val[0] == '+') || (val[0] == '-'))) {
				if (add_info) {
					error("Invalid syntax, variations of +=- are not accepted.");
					exit_code = 1;
					return 0;
				}
				add_info = val;
				val++;
			}

			time_limit = time_str2mins(val);
			if (time_limit == NO_VAL) {
				error("Invalid TimeLimit value");
				exit_code = 1;
				return 0;
			}
			if (add_info) {
				if (!job_msg.job_id_str) {
					error("JobId must precede TimeLimit "
					      "increment or decrement");
					exit_code = 1;
					return 0;
				}

				job_current_time = _get_job_time(job_msg.
								 job_id_str);
				if (job_current_time == NO_VAL) {
					exit_code = 1;
					return 0;
				} else if (job_current_time == INFINITE) {
					error("Cannot increment/decrement TimeLimit when set to UNLIMITED.");
					exit_code = 1;
					return 0;
				}

				if (add_info[0] == '+') {
					time_limit += job_current_time;
				} else if (time_limit > job_current_time) {
					error("TimeLimit decrement larger than"
					      " current time limit (%u > %u)",
					      time_limit, job_current_time);
					exit_code = 1;
					return 0;
				} else {
					time_limit = job_current_time -
						     time_limit;
				}
				/*
				 * Mark as unset so we know we handled this
				 * correctly as there is a check later to make
				 * sure we know we got a +-.
				 */
				add_info = NULL;
			}
			job_msg.time_limit = time_limit;
			update_cnt++;
		} else if (!xstrncasecmp(tag, "TimeMin", MAX(taglen, 5))) {
			int time_min = time_str2mins(val);
			if ((time_min < 0) && (time_min != INFINITE)) {
				error("Invalid TimeMin value");
				exit_code = 1;
				return 0;
			}
			job_msg.time_min = time_min;
			update_cnt++;
		} else if (!xstrncasecmp(tag, "Priority", MAX(taglen, 2))) {
			if (parse_uint32(val, &job_msg.priority)) {
				error ("Invalid Priority value: %s", val);
				exit_code = 1;
				return 0;
			}
			update_cnt++;
		} else if (!xstrncasecmp(tag, "Nice", MAX(taglen, 2))) {
			long long tmp_nice;
			tmp_nice = strtoll(val, (char **)NULL, 10);
			if (llabs(tmp_nice) > (NICE_OFFSET - 3)) {
				error("Nice value out of range (+/- %u). Value "
				      "ignored", NICE_OFFSET - 3);
				exit_code = 1;
				return 0;
			}
			job_msg.nice = NICE_OFFSET + tmp_nice;
			update_cnt++;
		} else if (!xstrncasecmp(tag, "CPUsPerTask", MAX(taglen, 9))) {
			if (parse_uint16(val, &job_msg.cpus_per_task)) {
				error("Invalid CPUsPerTask value: %s", val);
				exit_code = 1;
				return 0;
			}
			update_cnt++;
		} else if (!xstrncasecmp(tag, "CpusPerTres", MAX(taglen, 9))) {
			job_msg.cpus_per_tres = val;
			update_cnt++;
		} else if (!xstrncasecmp(tag, "NumCPUs", MAX(taglen, 6))) {
			int min_cpus, max_cpus=0;
			if (!get_resource_arg_range(val, "NumCPUs", &min_cpus,
						   &max_cpus, false) ||
			    (min_cpus <= 0) ||
			    (max_cpus && (max_cpus < min_cpus))) {
				error ("Invalid NumCPUs value: %s", val);
				exit_code = 1;
				return 0;
			}
			job_msg.min_cpus = min_cpus;
			if (max_cpus)
				job_msg.max_cpus = max_cpus;
			update_cnt++;
		} else if (!xstrncasecmp(tag, "NumTasks", MAX(taglen, 8)) ||
			   !xstrncasecmp(tag, "ReqProcs", MAX(taglen, 8))) {
			/* ReqProcs was removed in Slurm version 2.1 */
			if (parse_uint32(val, &job_msg.num_tasks)) {
				error ("Invalid NumTasks value: %s", val);
				exit_code = 1;
				return 0;
			}
			update_cnt++;
		} else if (!xstrncasecmp(tag, "Requeue", MAX(taglen, 4))) {
			if (parse_uint16(val, &job_msg.requeue)) {
				error ("Invalid Requeue value: %s", val);
				exit_code = 1;
				return 0;
			}
			update_cnt++;
		} else if (!xstrncasecmp(tag, "ReqNodes", MAX(taglen, 8)) ||
			   !xstrncasecmp(tag, "NumNodes", MAX(taglen, 8))) {
			/* ReqNodes was replaced by NumNodes in Slurm version 2.1 */
			int min_nodes, max_nodes, rc;
			if (!xstrcmp(val, "0")) {
				job_msg.min_nodes = 0;
			} else if (!xstrcasecmp(val, "ALL")) {
				job_msg.min_nodes = INFINITE;
			} else {
				char *job_size_str = NULL;
				min_nodes = (int) job_msg.min_nodes;
				max_nodes = (int) job_msg.max_nodes;
				rc = verify_node_count(val, &min_nodes,
						       &max_nodes,
						       &job_size_str);
				if (!rc)
					return rc;
				job_msg.min_nodes = (uint32_t) min_nodes;
				job_msg.max_nodes = (uint32_t) max_nodes;
				job_msg.job_size_str = job_size_str;
			}
			update_size = true;
			update_cnt++;
		} else if (!xstrncasecmp(tag, "ReqSockets", MAX(taglen, 4))) {
			if (parse_uint16(val, &job_msg.sockets_per_node)) {
				error ("Invalid ReqSockets value: %s", val);
				exit_code = 1;
				return 0;
			}
			update_cnt++;
		} else if (!xstrncasecmp(tag, "ReqCores", MAX(taglen, 4))) {
			if (parse_uint16(val, &job_msg.cores_per_socket)) {
				error ("Invalid ReqCores value: %s", val);
				exit_code = 1;
				return 0;
			}
			update_cnt++;
		} else if (!xstrncasecmp(tag, "TasksPerNode", MAX(taglen, 2))) {
			if (parse_uint16(val, &job_msg.ntasks_per_node)) {
				error ("Invalid TasksPerNode value: %s", val);
				exit_code = 1;
				return 0;
			}
                        update_cnt++;
                } else if (!xstrncasecmp(tag, "ReqThreads", MAX(taglen, 4))) {
			if (parse_uint16(val, &job_msg.threads_per_core)) {
				error ("Invalid ReqThreads value: %s", val);
				exit_code = 1;
				return 0;
			}
			update_cnt++;
		} else if (!xstrncasecmp(tag, "MinCPUsNode", MAX(taglen, 4))) {
			if (parse_uint16(val, &job_msg.pn_min_cpus)) {
				error ("Invalid MinCPUsNode value: %s", val);
				exit_code = 1;
				return 0;
			}
			update_cnt++;
		} else if (!xstrncasecmp(tag, "MinMemoryNode",
					 MAX(taglen, 10))) {
			if (parse_uint64(val, &job_msg.pn_min_memory)) {
				error ("Invalid MinMemoryNode value: %s", val);
				exit_code = 1;
				return 0;
			}
			update_cnt++;
		} else if (!xstrncasecmp(tag, "MinMemoryCPU",
					 MAX(taglen, 10))) {
			if (parse_uint64(val, &job_msg.pn_min_memory)) {
				error ("Invalid MinMemoryCPU value: %s", val);
				exit_code = 1;
				return 0;
			}
			job_msg.pn_min_memory |= MEM_PER_CPU;
			update_cnt++;
		} else if (!xstrncasecmp(tag, "MinTmpDiskNode",
					 MAX(taglen, 5))) {
			if (parse_uint32(val, &job_msg.pn_min_tmp_disk)) {
				error ("Invalid MinTmpDiskNode value: %s", val);
				exit_code = 1;
				return 0;
			}
			update_cnt++;
		} else if (!xstrncasecmp(tag, "Partition", MAX(taglen, 2))) {
			job_msg.partition = val;
			update_cnt++;
		} else if (!xstrncasecmp(tag, "QOS", MAX(taglen, 2))) {
			job_msg.qos = val;
			update_cnt++;
		} else if (!xstrncasecmp(tag, "ReservationName",
					 MAX(taglen, 3))) {
			job_msg.reservation = val;
			update_cnt++;
		} else if (!xstrncasecmp(tag, "Name", MAX(taglen, 2)) ||
			 !xstrncasecmp(tag, "JobName", MAX(taglen, 4))) {
			job_msg.name = val;
			update_cnt++;
		} else if (!xstrncasecmp(tag, "WCKey", MAX(taglen, 1))) {
			job_msg.wckey = val;
			update_cnt++;
		} else if (!xstrncasecmp(tag, "StdErr", MAX(taglen, 6))) {
			job_msg.std_err = val;
			update_cnt++;
		} else if (!xstrncasecmp(tag, "StdIn", MAX(taglen, 5))) {
			job_msg.std_in = val;
			update_cnt++;
		} else if (!xstrncasecmp(tag, "StdOut", MAX(taglen, 6))) {
			job_msg.std_out = val;
			update_cnt++;
		} else if (!xstrncasecmp(tag, "Switches", MAX(taglen, 5))) {
			char *sep_char;
			job_msg.req_switch =
				(uint32_t) strtol(val, &sep_char, 10);
			update_cnt++;
			if (sep_char && sep_char[0] == '@') {
				job_msg.wait4switch = time_str2mins(sep_char+1)
						      * 60;
			}
		} else if (!xstrncasecmp(tag, "wait-for-switch",
					 MAX(taglen, 5))) {
			if (parse_uint32(val, &job_msg.wait4switch)) {
				error ("Invalid wait-for-switch value: %s", val);
				exit_code = 1;
				return 0;
			}
			update_cnt++;
		} else if (!xstrncasecmp(tag, "OverSubscribe", MAX(taglen, 2)) ||
			   !xstrncasecmp(tag, "Shared", MAX(taglen, 2))) {
			if (!xstrncasecmp(val, "YES", MAX(vallen, 1)))
				job_msg.shared = JOB_SHARED_OK;
			else if (!xstrncasecmp(val, "NO", MAX(vallen, 1)))
				job_msg.shared = JOB_SHARED_NONE;
			else if (parse_uint16(val, &job_msg.shared)) {
				error("Invalid OverSubscribe value: %s", val);
				exit_code = 1;
				return 0;
			}
			update_cnt++;
		} else if (!xstrncasecmp(tag, "Contiguous", MAX(taglen, 3))) {
			if (!xstrncasecmp(val, "YES", MAX(vallen, 1)))
				job_msg.contiguous = 1;
			else if (!xstrncasecmp(val, "NO", MAX(vallen, 1)))
				job_msg.contiguous = 0;
			else if (parse_uint16(val, &job_msg.contiguous)) {
				error ("Invalid Contiguous value: %s", val);
				exit_code = 1;
				return 0;
			}
			update_cnt++;
		} else if (!xstrncasecmp(tag, "CoreSpec", MAX(taglen, 4))) {
			if (!xstrcmp(val, "-1") || !xstrcmp(val, "*"))
				job_msg.core_spec = INFINITE16;
			else if (parse_uint16(val, &job_msg.core_spec)) {
				error ("Invalid CoreSpec value: %s", val);
				exit_code = 1;
				return 0;
			}
			update_cnt++;
		} else if (!xstrncasecmp(tag, "MemPerTres", MAX(taglen, 5))) {
			job_msg.mem_per_tres = val;
			update_cnt++;
		} else if (!xstrncasecmp(tag, "ThreadSpec", MAX(taglen, 4))) {
			if (!xstrcmp(val, "-1") || !xstrcmp(val, "*"))
				job_msg.core_spec = INFINITE16;
			else if (parse_uint16(val, &job_msg.core_spec)) {
				error ("Invalid ThreadSpec value: %s", val);
				exit_code = 1;
				return 0;
			} else
				job_msg.core_spec |= CORE_SPEC_THREAD;
			update_cnt++;
		} else if (!xstrncasecmp(tag, "TresBind", MAX(taglen, 5))) {
			job_msg.tres_bind = val;
			update_cnt++;
		} else if (!xstrncasecmp(tag, "TresFreq", MAX(taglen, 5))) {
			job_msg.tres_freq = val;
			update_cnt++;
		} else if (!xstrncasecmp(tag, "TresPerJob", MAX(taglen, 8))) {
			job_msg.tres_per_job = val;
			update_cnt++;
		} else if (!xstrncasecmp(tag, "TresPerNode", MAX(taglen, 8))) {
			/* "gres" replaced by "tres_per_node" in v18.08 */
			if (job_msg.tres_per_node)
				xstrfmtcat(job_msg.tres_per_node, ",%s", val);
			else
				job_msg.tres_per_node = xstrdup(val);
			update_cnt++;
		} else if (!xstrncasecmp(tag, "TresPerSocket", MAX(taglen, 8))) {
			job_msg.tres_per_socket = val;
			update_cnt++;
		} else if (!xstrncasecmp(tag, "TresPerTask", MAX(taglen, 8))) {
			job_msg.tres_per_task = val;
			update_cnt++;
		} else if (!xstrncasecmp(tag, "ExcNodeList", MAX(taglen, 3))) {
			job_msg.exc_nodes = val;
			update_cnt++;
		} else if (!xstrncasecmp(tag, "NodeList",    MAX(taglen, 8)) ||
			   !xstrncasecmp(tag, "ReqNodeList", MAX(taglen, 8))) {
			job_msg.req_nodes = val;
			update_size = true;
			update_cnt++;
		} else if (!xstrncasecmp(tag, "Features", MAX(taglen, 1))) {
			job_msg.features = val;
			update_cnt++;
		} else if (!xstrncasecmp(tag, "Prefer", MAX(taglen, 3))) {
			job_msg.prefer = val;
			update_cnt++;
		} else if (!xstrncasecmp(tag, "Gres", MAX(taglen, 2))) {
			/* "gres" replaced by "tres_per_node" in v18.08 */
			if (!xstrcasecmp(val, "help") ||
			    !xstrcasecmp(val, "list")) {
				print_gres_help();
			} else {
				char *tmp = gres_prepend_tres_type(val);
				if (job_msg.tres_per_node) {
					xstrfmtcat(job_msg.tres_per_node, ",%s",
						   tmp);
					xfree(tmp);
				} else {
					job_msg.tres_per_node = tmp;
				}
				update_cnt++;
			}
		} else if (!xstrncasecmp(tag, "Account", MAX(taglen, 1))) {
			job_msg.account = val;
			update_cnt++;
		} else if (!xstrncasecmp(tag, "BurstBuffer", MAX(taglen, 1))) {
			job_msg.burst_buffer = val;
			update_cnt++;
		} else if (!xstrncasecmp(tag, "Dependency", MAX(taglen, 1))) {
			job_msg.dependency = val;
			update_cnt++;
		} else if (!xstrncasecmp(tag, "Licenses", MAX(taglen, 1))) {
			job_msg.licenses = val;
			update_cnt++;
		} else if (!xstrncasecmp(tag, "EligibleTime", MAX(taglen, 2)) ||
			   !xstrncasecmp(tag, "StartTime", MAX(taglen, 2))) {
			if ((job_msg.begin_time = parse_time(val, 0))) {
				if (job_msg.begin_time < time(NULL))
					job_msg.begin_time = time(NULL);
				update_cnt++;
			}
		} else if (!xstrncasecmp(tag, "EndTime", MAX(taglen, 2))) {
			job_msg.end_time = parse_time(val, 0);
			update_cnt++;
		} else if (!xstrncasecmp(tag, "Reboot", MAX(taglen, 3))) {
			if (!xstrncasecmp(val, "YES", MAX(vallen, 1)))
				job_msg.reboot = 1;
			else if (!xstrncasecmp(val, "NO", MAX(vallen, 1)))
				job_msg.reboot = 0;
			else if (parse_uint16(val, &job_msg.reboot)) {
				error ("Invalid reboot value: %s", val);
				exit_code = 1;
				return 0;
			}
			update_cnt++;
		} else if (!xstrncasecmp(tag, "UserID", MAX(taglen, 3))) {
			uid_t user_id = SLURM_AUTH_NOBODY;
			if (uid_from_string(val, &user_id) != SLURM_SUCCESS) {
				exit_code = 1;
				fprintf (stderr, "Invalid UserID: %s\n", val);
				fprintf (stderr, "Request aborted\n");
				return 0;
			}
			job_uid = (uint32_t) user_id;
		} else if (!xstrncasecmp(tag, "Deadline", MAX(taglen, 3))) {
			if ((job_msg.deadline = parse_time(val, 0))) {
				update_cnt++;
			}
		} else if (!xstrncasecmp(tag, "WorkDir", MAX(taglen, 2))) {
			job_msg.work_dir = val;
			update_cnt++;
		} else if (!xstrncasecmp(tag, "MailType", MAX(taglen, 5))) {
			job_msg.mail_type = parse_mail_type(val);
			if (job_msg.mail_type == INFINITE16) {
				fprintf(stderr, "Invalid MailType: %s\n", val);
				exit_code = 1;
				return 0;
			}
			update_cnt++;
		} else if (!xstrncasecmp(tag, "MailUser", MAX(taglen, 5))) {
			job_msg.mail_user = val;
			update_cnt++;
		} else if (!xstrncasecmp(tag, "MCSLabel", MAX(taglen, 4))) {
			job_msg.mcs_label = val;
			update_cnt++;
		} else {
			exit_code = 1;
			fprintf (stderr, "Update of this parameter is not "
				 "supported: %s\n", argv[i]);
			fprintf (stderr, "Request aborted\n");
			return 0;
		}

		if (add_info) {
			error("Option %s does not accept [+|-]= syntax", tag);
			exit_code = 1;
			return 0;
		}
	}

	if (update_cnt == 0) {
		exit_code = 1;
		fprintf (stderr, "No changes specified\n");
		return 0;
	}

	/* If specified, override uid with effective uid provided by
	 * -u <uid> or --uid=<uid> */
	if (euid != SLURM_AUTH_NOBODY)
		job_msg.user_id = euid;

	if (!job_msg.job_id_str && job_msg.name) {
		/* Translate name to job ID string */
		job_msg.job_id_str = _job_name2id(job_msg.name, job_uid);
		if (!job_msg.job_id_str) {
			exit_code = 1;
			return 0;
		}
	}

	if (!job_msg.job_id_str) {
		error("No job ID specified");
		exit_code = 1;
		return 0;
	}

	if (update_size && !_is_single_job(job_msg.job_id_str)) {
		exit_code = 1;
		return 0;
	}

	if (_is_job_id(job_msg.job_id_str)) {
		job_msg.job_id_str = _next_job_id();
		while (job_msg.job_id_str) {
			rc2 = slurm_update_job2(&job_msg, &resp);
			if (update_size && (rc2 == SLURM_SUCCESS)) {
				/* See check above for one job ID */
				job_msg.step_id.job_id =
					slurm_atoul(job_msg.job_id_str);
				_update_job_size(job_msg.step_id);
			}
			if (rc2 != SLURM_SUCCESS) {
				rc2 = errno;
				rc = MAX(rc, rc2);
				exit_code = 1;
				if (quiet_flag != 1) {
					fprintf(stderr, "%s for job %s\n",
						slurm_strerror(errno),
						job_msg.job_id_str);
				}
			} else if (resp) {
				for (i = 0; i < resp->job_array_count; i++) {
					if (!resp->error_code[i] &&
					    !resp->err_msg[i])
						continue;
					else if (!resp->error_code[i] &&
						 resp->err_msg[i] &&
						 !quiet_flag) {
						fprintf(stdout, "%s: %s\n",
							resp->job_array_id[i],
							resp->err_msg[i]);
						continue;
					}
					exit_code = 1;
					if (quiet_flag == 1)
						continue;
					fprintf(stderr, "%s: %s",
						resp->job_array_id[i],
						slurm_strerror(resp->
							       error_code[i]));
					if (resp->err_msg[i])
						fprintf(stderr, " (%s)\n",
							resp->err_msg[i]);
					else
						fprintf(stderr, "\n");
				}
				slurm_free_job_array_resp(resp);
				resp = NULL;
			}
			job_msg.job_id_str = _next_job_id();
		}
	} else if (job_msg.job_id_str) {
		exit_code = 1;
		rc = ESLURM_INVALID_JOB_ID;
		errno = rc;
		if (quiet_flag != 1) {
			fprintf(stderr, "%s for job %s\n",
				slurm_strerror(rc), job_msg.job_id_str);
		}
	}

	return rc;
}

/*
 * Send message to stdout of specified job
 * argv[0] == jobid
 * argv[1]++ the message
 */
extern int scontrol_job_notify(int argc, char **argv)
{
	int i;
	uint32_t job_id;
	char *message = NULL;
	slurm_step_id_t step_id = SLURM_STEP_ID_INITIALIZER;

	job_id = atoi(argv[0]);
	if (job_id <= 0) {
		fprintf(stderr, "Invalid job_id %s", argv[0]);
		return 1;
	}

	for (i=1; i<argc; i++) {
		if (message)
			xstrfmtcat(message, " %s", argv[i]);
		else
			xstrcat(message, argv[i]);
	}

	step_id.job_id = job_id;
	i = slurm_notify_job(step_id, message);
	xfree(message);

	if (i)
		return errno;
	else
		return 0;
}

static void _update_job_size(slurm_step_id_t step_id)
{
	resource_allocation_response_msg_t *alloc_info;
	char *fname_csh = NULL, *fname_sh = NULL;
	FILE *resize_csh = NULL, *resize_sh = NULL;

	if (!getenv("SLURM_JOBID"))
		return;		/* No job environment here to update */

	if (slurm_allocation_lookup(step_id, &alloc_info) != SLURM_SUCCESS) {
		if (errno != ESLURM_ALREADY_DONE) {
			slurm_perror("slurm_allocation_lookup");
			return;
		}
		/* Job size reset to zero, not an error */
		alloc_info = xmalloc(sizeof(resource_allocation_response_msg_t));
		alloc_info->node_list = xstrdup("");
	}

	xstrfmtcat(fname_csh, "slurm_job_%u_resize.csh", step_id.job_id);
	xstrfmtcat(fname_sh, "slurm_job_%u_resize.sh", step_id.job_id);
	(void) unlink(fname_csh);
	(void) unlink(fname_sh);
 	if (!(resize_csh = fopen(fname_csh, "w"))) {
		fprintf(stderr, "Could not create file %s: %s\n", fname_csh,
			strerror(errno));
		goto fini;
	}
 	if (!(resize_sh = fopen(fname_sh, "w"))) {
		fprintf(stderr, "Could not create file %s: %s\n", fname_sh,
			strerror(errno));
		goto fini;

	}
	/*
	 * Make files executable
	 */
	if (chmod(fname_csh, 0700) == -1)
		error("%s: chmod(%s): %m", __func__, fname_csh);
	if (chmod(fname_sh, 0700) == -1)
		error("%s: chmod(%s): %m", __func__, fname_sh);

	if (getenv("SLURM_NODELIST")) {
		fprintf(resize_sh, "export SLURM_NODELIST=\"%s\"\n",
			alloc_info->node_list);
		fprintf(resize_csh, "setenv SLURM_NODELIST \"%s\"\n",
			alloc_info->node_list);
	}
	if (getenv("SLURM_JOB_NODELIST")) {
		fprintf(resize_sh, "export SLURM_JOB_NODELIST=\"%s\"\n",
			alloc_info->node_list);
		fprintf(resize_csh, "setenv SLURM_JOB_NODELIST \"%s\"\n",
			alloc_info->node_list);
	}
	if (getenv("SLURM_NNODES")) {
		fprintf(resize_sh, "export SLURM_NNODES=%u\n",
			alloc_info->node_cnt);
		fprintf(resize_csh, "setenv SLURM_NNODES %u\n",
			alloc_info->node_cnt);
	}
	if (getenv("SLURM_JOB_NUM_NODES")) {
		fprintf(resize_sh, "export SLURM_JOB_NUM_NODES=%u\n",
			alloc_info->node_cnt);
		fprintf(resize_csh, "setenv SLURM_JOB_NUM_NODES %u\n",
			alloc_info->node_cnt);
	}
	if (getenv("SLURM_JOB_CPUS_PER_NODE")) {
		char *tmp;
		tmp = uint32_compressed_to_str(alloc_info->num_cpu_groups,
					       alloc_info->cpus_per_node,
					       alloc_info->cpu_count_reps);
		fprintf(resize_sh, "export SLURM_JOB_CPUS_PER_NODE=\"%s\"\n",
			tmp);
		fprintf(resize_csh, "setenv SLURM_JOB_CPUS_PER_NODE \"%s\"\n",
			tmp);
		xfree(tmp);
	}
	if (getenv("SLURM_TASKS_PER_NODE")) {
		/* We don't have sufficient information to recreate these */
		fprintf(resize_sh, "unset SLURM_NPROCS\n");
		fprintf(resize_csh, "unsetenv SLURM_NPROCS\n");

		fprintf(resize_sh, "unset SLURM_NTASKS\n");
		fprintf(resize_csh, "unsetenv SLURM_NTASKS\n");

		fprintf(resize_sh, "unset SLURM_TASKS_PER_NODE\n");
		fprintf(resize_csh, "unsetenv SLURM_TASKS_PER_NODE\n");
	}

	printf("To reset Slurm environment variables, execute\n");
	printf("  For bash or sh shells:  . ./%s\n", fname_sh);
	printf("  For csh shells:         source ./%s\n", fname_csh);

fini:	slurm_free_resource_allocation_response_msg(alloc_info);
	xfree(fname_csh);
	xfree(fname_sh);
	if (resize_csh)
		fclose(resize_csh);
	if (resize_sh)
		fclose(resize_sh);
}

/*
 * parse_requeue_args()
 * IN s - string to parse
 * OUT flags - flags to set based upon argument
 * RET 0 on successful parse, -1 otherwise
 */
extern int parse_requeue_flags(char *s, uint32_t *flags)
{
	int len;

	len = strlen(s);
	if (!xstrncasecmp(s, "incomplete", len)) {
		*flags |= JOB_RUNNING;
		return 0;
	}

	if (xstrncasecmp(s, "state=", 6))
		return -1;
	s += 6;
	if (!xstrncasecmp(s, "specialexit", 11) || !xstrncasecmp(s, "se", 2)) {
		*flags |= JOB_SPECIAL_EXIT;
		return 0;
	}

	return -1;
}

/* Return the current time limit of the specified job_id or NO_VAL if the
 * information is not available */
static uint32_t _get_job_time(const char *job_id_str)
{
	uint32_t task_id;
	char *next_str = NULL;
	uint32_t time_limit = NO_VAL;
	int i, rc;
	job_info_msg_t *resp;
	bitstr_t *array_bitmap;
	slurm_step_id_t step_id = SLURM_STEP_ID_INITIALIZER;

	step_id.job_id = (uint32_t) strtol(job_id_str, &next_str, 10);
	if (next_str[0] == '_') {
		task_id = (uint32_t)strtol(next_str+1, &next_str, 10);
		if (next_str[0] != '\0') {
			error("Invalid job ID %s", job_id_str);
			return time_limit;
		}
	} else if (next_str[0] != '\0') {
		error("Invalid job ID %s", job_id_str);
		return time_limit;
	} else {
		task_id = NO_VAL;
	}

	rc = slurm_load_job(&resp, step_id, SHOW_ALL);
	if (rc == SLURM_SUCCESS) {
		if (resp->record_count == 0) {
			error("Job ID %s not found", job_id_str);
			slurm_free_job_info_msg(resp);
			return time_limit;
		} else if (resp->record_count == 1) {
			time_limit = resp->job_array[0].time_limit;
			slurm_free_job_info_msg(resp);
			return time_limit;
		}

		if ((resp->record_count > 1) && (task_id == NO_VAL)) {
			error("TimeLimit increment/decrement not supported "
			      "for job arrays");
			slurm_free_job_info_msg(resp);
			return time_limit;
		}
		for (i = 0; i < resp->record_count; i++) {
			if ((resp->job_array[i].step_id.job_id ==
			     step_id.job_id) &&
			    (resp->job_array[i].array_task_id == NO_VAL) &&
			    (resp->job_array[i].array_bitmap == NULL)) {
				/* Regular job match */
				time_limit = resp->job_array[i].time_limit;
				break;
			}
			if (resp->job_array[i].array_job_id != step_id.job_id)
				continue;
			array_bitmap = resp->job_array[i].array_bitmap;
			if ((task_id == NO_VAL) ||
			    (resp->job_array[i].array_task_id == task_id) ||
			    (array_bitmap &&
			     (task_id < bit_size(array_bitmap)) &&
			     bit_test(array_bitmap, task_id))) {
				/* Array job with task_id match */
				time_limit = resp->job_array[i].time_limit;
				break;
			}
		}
		slurm_free_job_info_msg(resp);
	} else {
		error("Could not load state information for job %s: %m",
		      job_id_str);
	}

	return time_limit;
}

static bool _is_single_job(char *job_id_str)
{
	int rc;
	job_info_msg_t *resp;
	bool is_single = false;
	slurm_selected_step_t sel = { 0 };

	if (unfmt_job_id_string(job_id_str, &sel, NO_VAL)) {
		error("Invalid job ID %s", job_id_str);
		return is_single;
	}

	/* SLUID always identifies a single job */
	if (sel.step_id.sluid)
		return true;

	rc = slurm_load_job(&resp, sel.step_id, SHOW_ALL);
	if (rc == SLURM_SUCCESS) {
		if (resp->record_count == 0) {
			error("Job ID %s not found", job_id_str);
			slurm_free_job_info_msg(resp);
			return is_single;
		}
		if ((resp->record_count > 1) && (sel.array_task_id == NO_VAL)) {
			error("Job resizing not supported for job arrays");
			slurm_free_job_info_msg(resp);
			return is_single;
		}
		is_single = true;
		slurm_free_job_info_msg(resp);
	} else {
		error("Could not load state information for job %s: %m",
		      job_id_str);
	}

	return is_single;
}

/* Translate a job name to relevant job IDs
 * NOTE: xfree the return value to avoid memory leak */
static char *_job_name2id(char *job_name, uint32_t job_uid)
{
	int i, rc;
	job_info_msg_t *resp;
	slurm_job_info_t *job_ptr;
	char *job_id_str = NULL, *sep = "";
	slurm_step_id_t step_id = SLURM_STEP_ID_INITIALIZER;

	xassert(job_name);

	/* All jobs */
	step_id.sluid = 0;
	step_id.job_id = 0;

	rc = scontrol_load_job(&resp, step_id);
	if (rc == SLURM_SUCCESS) {
		if (resp->record_count == 0) {
			error("JobName %s not found", job_name);
			slurm_free_job_info_msg(resp);
			return job_id_str;
		}
		for (i = 0, job_ptr = resp->job_array; i < resp->record_count;
		     i++, job_ptr++) {
			if ((job_uid != NO_VAL) &&
			    (job_uid != job_ptr->user_id))
				continue;
			if (!job_ptr->name || xstrcmp(job_name, job_ptr->name))
				continue;
			if (job_ptr->array_task_id != NO_VAL) {
				xstrfmtcat(job_id_str, "%s%u_%u", sep,
					   job_ptr->array_job_id,
					   job_ptr->array_task_id);
			} else {
				xstrfmtcat(job_id_str, "%s%u", sep,
					   job_ptr->step_id.job_id);
			}
			sep = ",";
		}
		if (!job_id_str) {
			if (job_uid == NO_VAL) {
				error("No jobs with name \'%s\'", job_name);
			} else {
				error("No jobs with user ID %u and name \'%s\'",
				      job_uid, job_name);
			}
		}
	} else {
		error("Could not load state information: %m");
	}

	return job_id_str;
}
