/*
 * Copyright (C) 1994-2018 Altair Engineering, Inc.
 * For more information, contact Altair at www.altair.com.
 *
 * This file is part of the PBS Professional ("PBS Pro") software.
 *
 * Open Source License Information:
 *
 * PBS Pro is free software. You can redistribute it and/or modify it under the
 * terms of the GNU Affero General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any
 * later version.
 *
 * PBS Pro 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Commercial License Information:
 *
 * For a copy of the commercial license terms and conditions,
 * go to: (http://www.pbspro.com/UserArea/agreement.html)
 * or contact the Altair Legal Department.
 *
 * Altair’s dual-license business model allows companies, individuals, and
 * organizations to create proprietary derivative works of PBS Pro and
 * distribute them - whether embedded or bundled with other software -
 * under a commercial license agreement.
 *
 * Use of Altair’s trademarks, including but not limited to "PBS™",
 * "PBS Professional®", and "PBS Pro™" and Altair’s logos is subject to Altair's
 * trademark licensing policies.
 *
 */
/**
 * @file    svr_jobfunc.c
 *
 * @brief
 * 		svr_jobfunc.c - contains server functions dealing with jobs
 *
 * 	Included public functions are:
 *		svr_enquejob()     - place job in a queue
 *		svr_dequejob()     - remove job from queue
 *		svr_setjobstate()  - set the state/substate of a job
 *		svr_evaljobstate() - evaluate the state of a job based on attributes
 *		chk_resc_limits()  - check job resources vs queue/server limits
 *		svr_chkque()	   - check if job can enter queue
 *		job_set_wait()	   - set event for when job's wait time ends
 *		get_variable()	   - get value of a single environ variable of a job
 *		check_block()      - respond to blocked qsub if JOB_ATR_block is set
 *		prefix_std_file()  - build the fully prefixed default name for std e/o
 *		cat_default_std () - concatenates default std e/o name to input string

 *		get_jobowner()	   - get job owner name without @host suffix
 *		set_resc_deflt()   - set unspecified resource_limit to default values
 *		set_statechar()	   - set the job state attribute character value
 *		get_wall ()		   - get the "walltime" for a job if it has one set
 *		get_used_wall ()   - get the "walltime" resourse used for a job
 *      state_char2int()   - returns the state from char form to int form.
 *		uniq_nameANDfile() - creates a unique filename and file for an object
 *		remove_deleted_resvs() - remove reservations marked RESV_FINISHED
 *		set_cpu_licenses_need()- set # of cpu licenses needed by a job
 *		allocate_cpu_licenses()- assign cpu licenses to a job
 *		deallocate_cpu_licenses()     - unassign cpu licenses from a job
 *		clear_and_populate_svr_unlicensedjobs() - empties then adds entries
 *												to svr_unlicensedjobs.
 *		relicense_svr_unlicensedjobs()- relicense jobs in svr_unlicensedjobs
 *      update_eligible_time() - calc eligible time and modify accrue_type
 *		determine_accruetype() - determines accruetype
 *		alter_eligibletime() - resets sampletime of job
 *		eval_chkpnt()	   - insure job checkpoint .ge. queues min. time
 *
 * Private functions
 *		chk_svr_resc_limit() - check job requirements againt queue/server limits
 *		default_std()	   - make the default name for standard out/error
 *		set_deflt_resc()   - set unspecified resource_limit to default values
 *		job_wait_over()	   - event handler for job_set_wait()
 */
#include <pbs_config.h>   /* the master config generated by configure */

#ifndef WIN32
#include <unistd.h>
#endif
#include <fcntl.h>
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <math.h>
#include <netdb.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "pbs_ifl.h"
#include "libutil.h"
#include "server_limits.h"
#include "list_link.h"
#include "attribute.h"
#include "libpbs.h"
#include "credential.h"
#include "batch_request.h"
#include "resource.h"
#include "server.h"
#include "work_task.h"
#include "resv_node.h"
#include "queue.h"
#ifdef WIN32
#include <io.h>
#include <windows.h>
#include "win.h"
#endif
#include "job.h"
#include "pbs_sched.h"
#include "reservation.h"
#include "pbs_error.h"
#include "log.h"
#include "acct.h"
#include "avltree.h"
#include "pbs_nodes.h"
#include "svrfunc.h"
#include "sched_cmds.h"
#include "dis.h"
#include "libsec.h"
#include "pbs_license.h"
#include "pbs_reliable.h"
#ifndef WIN32
#include <sys/wait.h>
#endif

#define MIN_WALLTIME_LIMIT 0
#define MAX_WALLTIME_LIMIT 1




char statechars[] = "TQHWREXBMF";

/* Private Functions */

static void default_std(job *, int key, char * to);
static void Time4reply(struct work_task  *);
static void Time4resv(struct work_task*);
static void Time4resv1(struct work_task*);
static void resvFinishReply(struct work_task *);
int  change_enableORstart(resc_resv *, int, char *);
static void handle_qmgr_reply_to_startORenable(struct work_task *);
static void delete_occurrence_jobs(resc_resv *presv);
static void Time4occurrenceFinish(resc_resv *);
static void running_jobs_count(struct work_task *);


/** For faster job lookup through AVL tree */
static void svr_avljob_oper(job *pjob, int delkey);

/* Global Data Items: */
extern char *msg_noloopbackif;
extern char *msg_mombadmodify;

extern struct server server;
extern int  pbs_mom_port;
extern pbs_list_head svr_alljobs;
extern pbs_list_head svr_unlicensedjobs;
extern char  *msg_badwait;		/* error message */
extern char  *msg_daemonname;
extern char  *msg_also_deleted_job_history;
extern char   server_name[];
extern char  *pbs_server_name;
extern char   server_host[];
extern pbs_list_head svr_queues;
extern int    comp_resc_lt;
extern int    comp_resc_gt;
extern time_t time_now;
extern char  *resc_in_err;

extern struct   license_used  usedlicenses;

/* For history jobs only */
extern long 	svr_history_enable;
extern long 	svr_history_duration;

/* Work Task Handlers */

extern void resv_retry_handler(struct work_task *);

/* Global Data Items */

/* Private Functions */

#ifndef NDEBUG
static void correct_ct(pbs_queue *);
#endif 	/* NDEBUG */

/**
 * @brief
 * 		clear the default resource from structures
 *
 * @param[in]	pjob	-	The job to be enqueued.
 */
static void
clear_default_resc(job *pjob)
{
	attribute *pattr;
	resource  *presc;

	pattr = &pjob->ji_wattr[(int)JOB_ATR_resource];
	if (pattr->at_flags & ATR_VFLAG_SET) {
		presc = (resource *)GET_NEXT(pattr->at_val.at_list);
		while (presc) {
			if (presc->rs_value.at_flags & ATR_VFLAG_DEFLT)
				presc->rs_defin->rs_free(&presc->rs_value);
			presc = (resource *)GET_NEXT(presc->rs_link);
		}
	}
}

/**
 * @brief
 * 		tickle_for_reply ()
 * 		For internally generated requests to the server we would like
 * 		processing of the reply from the particular server subsystem
 * 		to happen as "soon" as the server get back to its main loop -
 * 		see server's main loop and "next_task()" and variable, "waittime".
 * 		By placing a do nothing task on the "timed_task_list" whose time
 * 		is now (or already passed), we can get next_task() to look at the
 * 		"task_list_immed" tasks now rather than wait for a while
 */
void
tickle_for_reply(void)
{
	(void)set_task(WORK_Timed, time_now + 10, 0, NULL);
}

/**
 * @brief
 * 		svr_enquejob	-	Enqueue the job into specified queue.
 *
 * @param[in]	pjob	-	The job to be enqueued.
 *
 * @return	int
 * @retavl	0	: on success
 * @retval	PBSE	: specified error number.
 *
 * @par MT-Safe:	no
 *
 * @par Note:
 *		Enqueue the job to the specific queue and update the queue state.
 *		Updated default attributes and resources specific to job type.
 */
int
svr_enquejob(job *pjob)
{
	attribute      *pattrjb;
	attribute_def  *pdef;
	job	       *pjcur;
	pbs_queue      *pque;
	int		rc;
	pbs_sched	*psched;

	/* make sure queue is still there, there exist a small window ... */

	pque = find_queuebyname(pjob->ji_qs.ji_queue);
	if (pque == NULL) {
		/*
		 * If it is a history job, then don't return PBSE_UNKQUE
		 * error but link the job to SERVER job list and update
		 * job history timestamp and subjob state table and return
		 * 0 (SUCCESS). INFO: The job is not associated with any
		 * queue as the queue has been already purged.
		 */
		if ((pjob->ji_qs.ji_state == JOB_STATE_MOVED) ||
			(pjob->ji_qs.ji_state == JOB_STATE_FINISHED)) {

			if (is_linked(&svr_alljobs, &pjob->ji_alljobs) == 0) {
				append_link(&svr_alljobs, &pjob->ji_alljobs, pjob);
				/**
				 * Add to AVL tree so that find_job() can return
				 * faster compared to linked list traverse.
				 */
				svr_avljob_oper(pjob, 0);
			}
			server.sv_qs.sv_numjobs++;
			server.sv_jobstates[pjob->ji_qs.ji_state]++;
			if (pjob->ji_qs.ji_svrflags & JOB_SVFLG_ArrayJob) {
				struct ajtrkhd *ptbl = pjob->ji_ajtrk;
				if (ptbl) {
					int indx;

					for (indx = 0; indx < ptbl->tkm_ct; ++indx)
						set_subjob_tblstate(pjob, indx, pjob->ji_qs.ji_state);
				}
			}
			return (0);
		} else {
			return (PBSE_UNKQUE);
		}
	}

	/* add job to server's all job list and update server counts */

#ifndef NDEBUG
	(void)sprintf(log_buffer, "enqueuing into %s, state %x hop %ld",
		pque->qu_qs.qu_name, pjob->ji_qs.ji_state,
		pjob->ji_wattr[(int)JOB_ATR_hopcount].at_val.at_long);
	log_event(PBSEVENT_DEBUG2, PBS_EVENTCLASS_JOB, LOG_DEBUG,
		pjob->ji_qs.ji_jobid, log_buffer);
#endif	/* NDEBUG */

	pjcur = (job *)GET_PRIOR(svr_alljobs);
	while (pjcur) {
		if ((unsigned long)pjob->ji_wattr[(int)JOB_ATR_qrank].
			at_val.at_long >=
			(unsigned long)pjcur->ji_wattr[(int)JOB_ATR_qrank].
			at_val.at_long)
			break;
		pjcur = (job *)GET_PRIOR(pjcur->ji_alljobs);
	}
	if (pjcur == 0) {
		/* link first in server's list */
		insert_link(&svr_alljobs, &pjob->ji_alljobs, pjob,
			LINK_INSET_AFTER);
	} else {
		/* link after 'current' job in server's list */
		insert_link(&pjcur->ji_alljobs, &pjob->ji_alljobs, pjob,
			LINK_INSET_AFTER);
	}

	/**
	 * Add to AVL tree so that find_job() can return
	 * faster compared to linked list traverse.
	 */
	svr_avljob_oper(pjob, 0);

	server.sv_qs.sv_numjobs++;
	server.sv_jobstates[pjob->ji_qs.ji_state]++;

	/* place into queue in order of queue rank starting at end */

	pjob->ji_qhdr = pque;

	pjcur = (job *)GET_PRIOR(pque->qu_jobs);
	while (pjcur) {
		if ((unsigned long)pjob->ji_wattr[(int)JOB_ATR_qrank].
			at_val.at_long >=
			(unsigned long)pjcur->ji_wattr[(int)JOB_ATR_qrank].
			at_val.at_long)
			break;
		pjcur = (job *)GET_PRIOR(pjcur->ji_jobque);
	}
	if (pjcur == 0) {
		/* link first in list */
		insert_link(&pque->qu_jobs, &pjob->ji_jobque, pjob,
			LINK_INSET_AFTER);
	} else {
		/* link after 'current' job in list */
		insert_link(&pjcur->ji_jobque, &pjob->ji_jobque, pjob,
			LINK_INSET_AFTER);
	}

	/* update counts: queue and queue by state */

	pque->qu_numjobs++;
	pque->qu_njstate[pjob->ji_qs.ji_state]++;

	if ((pjob->ji_qs.ji_state == JOB_STATE_MOVED) ||
		(pjob->ji_qs.ji_state == JOB_STATE_FINISHED)) {
		if (pjob->ji_qs.ji_svrflags & JOB_SVFLG_ArrayJob) {
			int indx;
			struct ajtrkhd *ptbl = pjob->ji_ajtrk;
			if (ptbl) {
				for (indx = 0; indx < ptbl->tkm_ct; ++indx)
					set_subjob_tblstate(pjob,
						indx,
						pjob->ji_qs.ji_state);
			}
		}
		return (0);
	}

	/* update the current location and type attribute */

	pdef    = &job_attr_def[(int)JOB_ATR_in_queue];
	pattrjb = &pjob->ji_wattr[(int)JOB_ATR_in_queue];
	pdef->at_free(pattrjb);
	pdef->at_decode(pattrjb, NULL, NULL, pque->qu_qs.qu_name);

	if (pque->qu_attr[(int)QA_ATR_QType].at_val.at_str == NULL) {
		sprintf(log_buffer, "queue type must be set for queue `%s`",
			pque->qu_qs.qu_name);
		log_event(PBSEVENT_ADMIN, PBS_EVENTCLASS_QUEUE, LOG_ERR,
			pjob->ji_qs.ji_jobid, log_buffer);
		return PBSE_NEEDQUET;
	}
	pjob->ji_wattr[(int)JOB_ATR_queuetype].at_val.at_char =
		*pque->qu_attr[(int)QA_ATR_QType].at_val.at_str;
	pjob->ji_wattr[(int)JOB_ATR_queuetype].at_flags |=
		ATR_VFLAG_SET | ATR_VFLAG_MODCACHE;

	if ((pjob->ji_wattr[(int)JOB_ATR_qtime].at_flags &
		ATR_VFLAG_SET) == 0) {
		pjob->ji_wattr[(int)JOB_ATR_qtime].at_val.at_long = time_now;
		pjob->ji_wattr[(int)JOB_ATR_qtime].at_flags |=
			ATR_VFLAG_SET | ATR_VFLAG_MODCACHE;

		/* issue enqueued accounting record */

		(void)sprintf(log_buffer, "queue=%s", pque->qu_qs.qu_name);
		account_record(PBS_ACCT_QUEUE, pjob, log_buffer);
	}

	/*
	 * set any "unspecified" resources which have default values,
	 * first with queue defaults, then with server defaults
	 */

	rc = set_resc_deflt((void *)pjob, JOB_OBJECT, NULL);
	if (rc)
		return rc;

	/*
	 * Ensure that all jobs has JOB_ATR_project set.
	 * It could be unset if coming from an overlay upgrade.
	 */
	if ( (pjob->ji_wattr[(int)JOB_ATR_project].at_flags & \
							ATR_VFLAG_SET) == 0 ) {
		job_attr_def[(int)JOB_ATR_project].at_decode(
			&pjob->ji_wattr[(int)JOB_ATR_project],
			NULL, NULL, PBS_DEFAULT_PROJECT);
	}

	/* update any entity count and entity resources usage for the queue */

	if (!(pjob->ji_qs.ji_svrflags & JOB_SVFLG_SubJob) || (server.sv_attr[(int)SRV_ATR_State].at_val.at_long == SV_STATE_INIT)) {
		account_entity_limit_usages(pjob, pque, NULL, INCR, ETLIM_ACC_ALL);
	}

	/*
	 * See if we need to do anything special based on type of queue
	 */

	if (pque->qu_qs.qu_type == QTYPE_Execution) {

		/* set union to "EXEC" and clear mom's address */

		if (pjob->ji_qs.ji_un_type != JOB_UNION_TYPE_EXEC) {
			pjob->ji_qs.ji_un_type = JOB_UNION_TYPE_EXEC;
			pjob->ji_qs.ji_un.ji_exect.ji_momaddr = 0;
			pjob->ji_qs.ji_un.ji_exect.ji_momport = 0;
			pjob->ji_qs.ji_un.ji_exect.ji_exitstat = 0;
		}

		/* check the job checkpoint against the queue's  min */

		eval_chkpnt(&pjob->ji_wattr[(int)JOB_ATR_chkpnt],
			&pque->qu_attr[(int)QE_ATR_ChkptMim]);

		/*
		 * do anything needed doing regarding job dependencies,
		 * ignore this during Server recovery as the dependency
		 * was registered when the job was first enqueued.
		 */

		if (server.sv_attr[(int)SRV_ATR_State].at_val.at_long != SV_STATE_INIT) {
			if (pjob->ji_wattr[(int)JOB_ATR_depend].at_flags&ATR_VFLAG_SET) {
				rc = depend_on_que(&pjob->ji_wattr[(int)JOB_ATR_depend], pjob, ATR_ACTION_NOOP);
				if (rc)
					return rc;
			}
		}

		/* set eligible time */

		if (((pjob->ji_wattr[(int)JOB_ATR_etime].at_flags &
			ATR_VFLAG_SET) == 0) &&
			(pjob->ji_qs.ji_state == JOB_STATE_QUEUED)) {
			pjob->ji_wattr[(int)JOB_ATR_etime].at_val.at_long =
				time_now;
			pjob->ji_wattr[(int)JOB_ATR_etime].at_flags |=
				ATR_VFLAG_SET | ATR_VFLAG_MODCACHE;

			/* better notify the Scheduler we have a new job */

			if (find_assoc_sched_jid(pjob->ji_qs.ji_jobid, &psched))
				set_scheduler_flag(SCH_SCHEDULE_NEW, psched);
			else {
				sprintf(log_buffer, "Unable to reach scheduler associated with job %s", pjob->ji_qs.ji_jobid);
				log_err(-1, __func__, log_buffer);
			}
		} else if (server.sv_attr[SRV_ATR_EligibleTimeEnable].at_val.at_long &&
			server.sv_attr[SRV_ATR_scheduling].at_val.at_long) {

			/* notify the Scheduler we have moved a job here */

			if (find_assoc_sched_jid(pjob->ji_qs.ji_jobid, &psched))
				set_scheduler_flag(SCH_SCHEDULE_MVLOCAL, psched);
			else {
				sprintf(log_buffer, "Unable to reach scheduler associated with job %s", pjob->ji_qs.ji_jobid);
				log_err(-1, __func__, log_buffer);
			}
		}


	} else if (pque->qu_qs.qu_type == QTYPE_RoutePush) {

		/* start attempts to route job */

		pjob->ji_qs.ji_un_type = JOB_UNION_TYPE_ROUTE;
		pjob->ji_qs.ji_un.ji_routet.ji_quetime = time_now;
		pjob->ji_qs.ji_un.ji_routet.ji_rteretry = 0;
	}
	return (0);
}

/**
 * @brief
 * 		svr_dequejob() - remove job from whatever queue its in and reduce counts
 *
 * @param[in]	pjob	-	The job to be enqueued.
 */

void
svr_dequejob(job *pjob)
{
	int	   bad_ct = 0;
	pbs_queue *pque;

	/* remove job from server's all job list and reduce server counts */

	if (is_linked(&svr_alljobs, &pjob->ji_alljobs)) {
		delete_link(&pjob->ji_alljobs);
		delete_link(&pjob->ji_unlicjobs);

		/**
		 * Remove the key from the AVL tree which was
		 * added for faster job search i.e. find_job().
		 */
		svr_avljob_oper(pjob, 1);

		if (--server.sv_qs.sv_numjobs < 0)
			bad_ct = 1;

		if (--server.sv_jobstates[pjob->ji_qs.ji_state] < 0)
			bad_ct = 1;
	}

	if ((pque = pjob->ji_qhdr) != NULL) {

		/* update any entity count and entity resources usage at que */

		account_entity_limit_usages(pjob, pque, NULL, DECR,
				pjob->ji_etlimit_decr_queued ? ETLIM_ACC_ALL_MAX : ETLIM_ACC_ALL);


		if (is_linked(&pque->qu_jobs, &pjob->ji_jobque)) {
			delete_link(&pjob->ji_jobque);
			if (--pque->qu_numjobs < 0)
				bad_ct = 1;
			if (--pque->qu_njstate[pjob->ji_qs.ji_state] < 0)
				bad_ct = 1;
		}
		pjob->ji_qhdr = NULL;
	}

#ifndef NDEBUG
	(void)sprintf(log_buffer, "dequeuing from %s, state %x",
		pque ? pque->qu_qs.qu_name : "", pjob->ji_qs.ji_state);
	log_event(PBSEVENT_DEBUG2, PBS_EVENTCLASS_JOB, LOG_DEBUG,
		pjob->ji_qs.ji_jobid, log_buffer);
	if (bad_ct) 		/* state counts are all messed up */
		correct_ct(pque);
#endif	/* NDEBUG */

	pjob->ji_wattr[(int)JOB_ATR_qtime].at_flags &= ~ATR_VFLAG_SET;

	/* clear any default resource values.		*/

	clear_default_resc(pjob);
}

/**
 * @brief
 * 		svr_setjobstate - set the job state, update the server/queue state counts,
 *		and save the job
 *
 * @param[in,out]	pjob	-	The job to be operated on.
 * @param[in]	newstate	-	new job state
 * @param[in]	newsubstate	-	new sub state of the job.
 *
 * @return	int
 * @retval	0	: success
 * @retval	!=0	: failure
 */

int
svr_setjobstate(job *pjob, int newstate, int newsubstate)
{
	int    changed = 0;
	pbs_queue *pque = pjob->ji_qhdr;
	pbs_sched *psched;

	/*
	 * If the job has already finished, then do not make any new changes
	 * to job state or substate.
	 */
	if (pjob->ji_qs.ji_state == JOB_STATE_FINISHED)
		return (0);

	/*
	 * if its is a new job, then don't update counts, svr_enquejob() will
	 * take care of that, also req_commit() will see that the job is saved.
	 */

	if (pjob->ji_qs.ji_substate != JOB_SUBSTATE_TRANSICM) {
		int oldstate;

		/* Not a new job, update the counts and save if needed */

		if (pjob->ji_qs.ji_substate != newsubstate)
			changed = 1;

		/* if the state is changing, also update the state counts */

		if ((oldstate = pjob->ji_qs.ji_state) != (long)newstate) {

			changed = 1;
			server.sv_jobstates[oldstate]--;
			server.sv_jobstates[newstate]++;
			if (pque != NULL) {

				pque->qu_njstate[oldstate]--;
				pque->qu_njstate[newstate]++;

				/*
				 * if execution queue, and eligability to run
				 * has improved, kick the scheduler.
				 */

				if ((pque->qu_qs.qu_type == QTYPE_Execution) &&
					(newstate == JOB_STATE_QUEUED)) {
					attribute *etime = &pjob->
						ji_wattr[(int)JOB_ATR_etime];

					if (find_assoc_sched_jid(pjob->ji_qs.ji_jobid, &psched))
						set_scheduler_flag(SCH_SCHEDULE_NEW, psched);
					else {
						sprintf(log_buffer, "Unable to reach scheduler associated with job %s", pjob->ji_qs.ji_jobid);
						log_err(-1, __func__, log_buffer);
					}

					if ((etime->at_flags & ATR_VFLAG_SET)
						== 0) {
						etime->at_val.at_long = time_now;
						etime->at_flags |=
							ATR_VFLAG_SET|
						ATR_VFLAG_MODCACHE;
					}
					/* clear start time (stime) */
					job_attr_def[(int)JOB_ATR_stime].
					at_free(&pjob->
						ji_wattr[(int)JOB_ATR_stime]);

				} else if ((newstate == JOB_STATE_HELD) ||
					(newstate == JOB_STATE_WAITING)) {
					/* on hold or wait, clear etime */
					job_attr_def[(int)JOB_ATR_etime].
					at_free(&pjob->
						ji_wattr[(int)JOB_ATR_etime]);
				}
			}
			/* if subjob, update parent Array Job */
			if (pjob->ji_qs.ji_svrflags & JOB_SVFLG_SubJob) {
				update_subjob_state(pjob, newstate);
			}
		}
	}

	/* set the states accordingly */

	pjob->ji_qs.ji_state = newstate;
	pjob->ji_qs.ji_substate = newsubstate;
	pjob->ji_wattr[(int)JOB_ATR_substate].at_val.at_long = newsubstate;
	pjob->ji_wattr[(int)JOB_ATR_substate].at_flags |= ATR_VFLAG_MODCACHE;

	set_statechar(pjob);
	Update_Resvstate_if_resv(pjob);

	/* eligible_time_enable */
	if (server.sv_attr[SRV_ATR_EligibleTimeEnable].at_val.at_long == 1) {
		long newaccruetype;

		/* determine accrue type */
		newaccruetype = determine_accruetype(pjob);

		/* calculate eligible time */
		(void)update_eligible_time(newaccruetype, pjob);
	}

	/* update the job file */

	if (newstate == JOB_STATE_RUNNING) {
		if (pjob->ji_etlimit_decr_queued == FALSE) {
			account_entity_limit_usages(pjob, NULL, NULL, DECR, ETLIM_ACC_ALL_QUEUED);
			account_entity_limit_usages(pjob, pjob->ji_qhdr, NULL, DECR, ETLIM_ACC_ALL_QUEUED);
			pjob->ji_etlimit_decr_queued = TRUE;
		}
	}

	if (pjob->ji_modified) {
		if (pjob->ji_newjob)	/* Hack to use ji_newjob to mean SAVEJOB_NEW for subjobs */
			return (job_save(pjob, SAVEJOB_NEW));
		else
			return (job_save(pjob, SAVEJOB_FULL));
	} else if (changed)
		return (job_save(pjob, SAVEJOB_QUICK));

	return (0);
}

/**
 * @brief
 * 		svr_evaljobstate - evaluate and return the job state and substate
 *		according to the the values of the hold, execution time, and
 *		dependency attributes.  This is typically called after the job has been
 *		enqueued or the (hold, execution-time) attributes have been modified.
 * @par
 *		IF the job is a history job i.e. job state is JOB_STATE_MOVED
 *		or JOB_STATE_FINISHED, then just return state/substate without
 *		any change, irrespective of the value of "forceeval".
 *
 * @param[in]	pjob	-	pointer to the job structure
 * @param[out]	newstate	-	recommended new state for job
 * @param[out]	newsub	-	recommended new substate for job
 * @param[in]	forceeval	-	whether to forcefully change the value or not.
 *
 * @return	void
 */
void
svr_evaljobstate(job *pjob, int *newstate, int *newsub, int forceeval)
{
	int	resvstate;

	/*
	 * A value MUST be assigned to newstate and newsub because
	 * they may have been passed in uninitialized. We MUST put
	 * the job in a valid state or the scheduler will bail out
	 * on subsequent cycles and not schedule ANY work. The
	 * safest thing to do is to hold the job by default.
	 */
	*newstate = JOB_STATE_HELD;
	*newsub = JOB_SUBSTATE_HELD;

	if ((pjob->ji_qs.ji_state == JOB_STATE_MOVED) ||
		(pjob->ji_qs.ji_state == JOB_STATE_FINISHED)) {

		/* History job, just return state/sub-state. */
		*newstate = pjob->ji_qs.ji_state;
		*newsub   = pjob->ji_qs.ji_substate;

	} else if ((forceeval == 0) &&
		((pjob->ji_qs.ji_state == JOB_STATE_RUNNING) ||
		(pjob->ji_qs.ji_state == JOB_STATE_TRANSIT))) {

		/* Leave as is. */
		*newstate = pjob->ji_qs.ji_state;
		*newsub   = pjob->ji_qs.ji_substate;

	} else if (pjob->ji_wattr[(int)JOB_ATR_hold].at_val.at_long) {

		*newstate = JOB_STATE_HELD;
		/* is the hold due to a dependency? */
		if ((pjob->ji_qs.ji_substate == JOB_SUBSTATE_SYNCHOLD) ||
			(pjob->ji_qs.ji_substate == JOB_SUBSTATE_DEPNHOLD)) {
			/* Retain substate. */
			*newsub   = pjob->ji_qs.ji_substate;
		} else {
			*newsub   = JOB_SUBSTATE_HELD;
		}

	} else if (pjob->ji_wattr[(int)JOB_ATR_exectime].at_val.at_long > (long)time_now) {

		*newstate = JOB_STATE_WAITING;
		*newsub   = JOB_SUBSTATE_WAITING;

	} else if (pjob->ji_resvp &&
		pjob->ji_resvp->ri_qs.ri_type == RESV_JOB_OBJECT) {

		resvstate = pjob->ji_resvp->ri_qs.ri_state;
		if (resvstate == RESV_UNCONFIRMED) {
			*newstate = JOB_STATE_HELD;
			*newsub   = JOB_SUBSTATE_HELD;
		} else if (pjob->ji_resvp->ri_qs.ri_stime > time_now) {
			*newstate = JOB_STATE_WAITING;
			*newsub   = JOB_SUBSTATE_WAITING;
		} else if (pjob->ji_resvp->ri_qs.ri_etime > time_now) {
			if (resvstate == RESV_RUNNING ||
				resvstate == RESV_TIME_TO_RUN) {
				*newstate = JOB_STATE_QUEUED;
				if (pjob->ji_wattr[(int)JOB_ATR_stagein]
					.at_flags & ATR_VFLAG_SET) {
					if (pjob->ji_qs.ji_svrflags & JOB_SVFLG_StagedIn) {
						*newsub = JOB_SUBSTATE_STAGECMP;
					} else {
						*newsub = JOB_SUBSTATE_PRESTAGEIN;
					}
				} else
					*newsub = JOB_SUBSTATE_QUEUED;
			} else {
				*newstate = pjob->ji_qs.ji_state;
				*newsub   = pjob->ji_qs.ji_substate;
			}
		} else {
			/*
			 * Just keep current job state and substate.
			 * Note, reservation state should be one of:
			 * RESV_BEING_DELETED, RESV_DELETED,
			 * RESV_FINISHED, RESV_DELETING_JOBS and,
			 * job state should be JOB_STATE_EXITING with
			 * substate one of the "job exit processing"
			 * steps.
			 */
			*newstate = pjob->ji_qs.ji_state;
			*newsub   = pjob->ji_qs.ji_substate;
		}

	} else if (pjob->ji_wattr[(int)JOB_ATR_stagein].at_flags & ATR_VFLAG_SET) {

		*newstate = JOB_STATE_QUEUED;
		if (pjob->ji_qs.ji_svrflags & JOB_SVFLG_StagedIn) {
			*newsub = JOB_SUBSTATE_STAGECMP;
		} else {
			*newsub = JOB_SUBSTATE_PRESTAGEIN;
		}

	} else {

		if (pjob->ji_qs.ji_svrflags&JOB_SVFLG_ArrayJob) {
			/* This is an array job. */
			struct ajtrkhd  *ptbl = pjob->ji_ajtrk;
			if (ptbl) {
				if  (ptbl->tkm_subjsct[JOB_STATE_QUEUED] +
				       ptbl->tkm_dsubjsct < ptbl->tkm_ct) {
					*newstate = JOB_STATE_BEGUN;
					*newsub   = JOB_SUBSTATE_BEGUN;
				} else {
					/* All subjobs are queued. */
					*newstate = JOB_STATE_QUEUED;
					*newsub   = JOB_SUBSTATE_QUEUED;
				}
			} else {
				sprintf(log_buffer, "Array job has no tracking table!");
				log_event(PBSEVENT_JOB, PBS_EVENTCLASS_JOB, LOG_ERR,
					pjob->ji_qs.ji_jobid, log_buffer);
				*newstate = JOB_STATE_HELD;
				*newsub = JOB_SUBSTATE_HELD;
			}
		} else {
			*newstate = JOB_STATE_QUEUED;
			*newsub   = JOB_SUBSTATE_QUEUED;
		}

	}
}


/**
 * @brief
 * 		cmp_resvStateRelated_attrs - for the object in question,
 * 		compute and set those attributes whose value or existence
 * 		and value can depend on the state of the reservation or
 * 		whether the object belongs to a reservation - e.g. one
 * 		such attribute is the JOB_ATR_exectime on a job
 *
 * @param[in]	pobj	-	pointer to the object based on the type
 * @param[out]	objtype	-	type of the object - job/reservation.
 *
 * @return	None
 */
void
cmp_resvStateRelated_attrs(void *pobj, int objtype)
{
	job	   *pjob;
	resc_resv  *presv;
	attribute  *pats;	/*"ptr to attribute to set"*/
	attribute  *patu;	/*"ptr to attribute to use"*/
	attribute  hold;	/*a temporary*/

	int	  (*pf)(attribute *, attribute *, enum batch_op);


	if (pobj == NULL)
		return;

	if (objtype == JOB_OBJECT) {
		pjob = (job *)pobj;
		presv = pjob->ji_resvp;
		if (presv) {
			pats = &pjob->ji_wattr[JOB_ATR_exectime];
			patu = &presv->ri_wattr[RESV_ATR_start];
			pf = resv_attr_def[RESV_ATR_start].at_set;
			(void)pf(pats, patu, SET);
		}

	} else if (objtype == RESV_JOB_OBJECT) {
		presv = (resc_resv *)pobj;
		pjob = presv->ri_jbp;
		if (pjob) {
			pats = &pjob->ji_wattr[JOB_ATR_exectime];
			patu = &presv->ri_wattr[RESV_ATR_start];
			pf = resv_attr_def[RESV_ATR_start].at_set;
			(void)pf(pats, patu, SET);

			hold.at_flags = ATR_VFLAG_SET;
			hold.at_val.at_long = HOLD_s;
			pats = &pjob->ji_wattr[JOB_ATR_hold];
			patu = &hold;
			pf = job_attr_def[JOB_ATR_hold].at_set;
			if (presv->ri_qs.ri_state == RESV_UNCONFIRMED)
				(void)pf(pats, patu, INCR);
			else if (presv->ri_qs.ri_state == RESV_CONFIRMED)
				(void)pf(pats, patu, DECR);
		}
	} else if (objtype == RESC_RESV_OBJECT) {
		return;
	}
}


/**
 * @brief
 * 		get_variable - get the value associated with a specified environment
 *		variable of a job
 *
 * @param[in]	pjob	-	pointer to the job object
 * @param[in]	variable	-	string variable which needs to be searched in object attribute.
 *
 * @return	pointer to the start of the value
 * @retval	NULL	: if the variable is not found in the variable_list attribute.
 */

char *
get_variable(job *pjob, char *variable)
{
	char *pc;

	pc = arst_string(variable, &pjob->ji_wattr[(int)JOB_ATR_variables]);
	if (pc) {
		if ((pc = strchr(pc, (int)'=')) != 0)
			pc++;
	}
	return (pc);
}


/**
 * @brief
 * 		lookup_variable - lookup the value of a particular environment variable
 *		associated with the object.
 *
 * @param[in]	pobj	-	pointer to the object structure
 * @param[in]	objtype	-	object type
 * @param[in]	variable	-	string variable which needs to be searched in object attribute.
 *
 * @return	a pointer to the beginning of the value string
 * @retval	NULL	: pointer if the variable isn't found in the object's "variable_list"
 */

char *
lookup_variable(void *pobj, int objtype, char *variable)
{
	char *pc;
	int  idx_var;
	attribute *objattrs;

	if (objtype == JOB_OBJECT) {
		idx_var = (int)JOB_ATR_variables;
		objattrs = ((job *)pobj)->ji_wattr;
	} else {
		idx_var = (int)RESV_ATR_variables;
		objattrs = ((resc_resv *)pobj)->ri_wattr;
	}

	pc = arst_string(variable, &objattrs[idx_var]);
	if (pc) {
		if ((pc = strchr(pc, (int)'=')) != 0)
			pc++;
	}
	return (pc);
}

/**
 * @brief
 * 		compare the job resource limit against the system limit
 * 		unless a queue limit exists, it take priority
 *
 * @param[in]	jobatr	-	job attribute
 * @param[in]	queatr	-	resource (value) entry
 * @param[in]	svratr	-	server attribute.
 * @param[in]	qtype	-	type of queue (not used here)
 *
 * @return	number of .gt. and .lt. comparison in comp_resc_gt and comp_resc_lt
 * 			does not make use of comp_resc_eq or comp_resc_nc
 */

static void
chk_svr_resc_limit(attribute *jobatr, attribute *queatr,
	attribute *svratr, int qtype)
{
	int       rc;
	resource *jbrc;
	resource *qurc;
	resource *svrc;
	resource *cmpwith;
	static resource_def *noderesc = NULL;

	if (noderesc == NULL) {
		noderesc = find_resc_def(svr_resc_def, "nodes", svr_resc_size);
	}
	comp_resc_gt = 0;
	comp_resc_lt = 0;

	jbrc = (resource *)GET_NEXT(jobatr->at_val.at_list);
	while (jbrc) {
		cmpwith = 0;
		if (jbrc->rs_value.at_flags & ATR_VFLAG_SET) {
			qurc = find_resc_entry(queatr, jbrc->rs_defin);
			if ((qurc == 0) ||
				((qurc->rs_value.at_flags & ATR_VFLAG_SET)==0)) {
				/* queue limit not set, check server's */

				svrc = find_resc_entry(svratr, jbrc->rs_defin);
				if ((svrc != 0) &&
					(svrc->rs_value.at_flags & ATR_VFLAG_SET)) {
					cmpwith = svrc;
				}

			} else {
				/* queue limit is set, use it */
				cmpwith = qurc;
			}

			if ((jbrc->rs_defin != noderesc) && cmpwith) {
				rc = jbrc->rs_defin->rs_comp(&cmpwith->rs_value,
					&jbrc->rs_value);
				if (rc > 0)
					comp_resc_gt++;
				else if (rc < 0)
					comp_resc_lt++;
			}
		}
		jbrc = (resource *)GET_NEXT(jbrc->rs_link);
	}
}
/**
 * @brief
 * 		get_wt_limit - get limit set on walltime from the list of resource limits.
 *
 * @param[in]	plimit_attr	-	list of resource limits
 * @param[out]	wt_attr	-	A pointer to walltime limit
 *
 * @return	int
 * @retval	0	: if walltime limit is set
 * @retval 	1	: no walltime limit is set
 */

int
get_wt_limit(attribute *plimit_attr, attribute *wt_attr)
{
	resource *wiresc = NULL;
	if (plimit_attr == NULL || wt_attr == NULL)
		return 1;
	/* Check min_walltime if min_or_max == MIN_WALLTIME_LIMIT */
	wiresc = (resource *)GET_NEXT(plimit_attr->at_val.at_list);
	while (wiresc != NULL) {
		if ((strcasecmp(wiresc->rs_defin->rs_name, WALLTIME) == 0)
			&& (wiresc->rs_value.at_flags & ATR_VFLAG_SET)) {
			*wt_attr = wiresc->rs_value;
			return 0;
		}
		wiresc = (resource *)GET_NEXT(wiresc->rs_link);
	}
	return 1;
}
/**
 * @brief
 * 		comp_wt_limits_STF - check a job's min_walltime OR max_walltime against
 * 		configured "walltime" limits.
 *
 * @param[in]	resc_minmaxwt	-	resource(min_walltime or max_walltime) to be compared
 * @param[in] 	limit_attr	-	attribute containing resource limit
 * @param[in] 	min_or_max	-	check aginst minimum walltime limit if MIN_WALLTIME_LIMIT,
 * 								else check against maximum walltime limit
 * @return	int
 * @retval 	0	: within limits OR if resc_minmaxwt == NULL OR is unset.
 * @retval	PBSE_EXCQRESC	: not within limits
 */
int
comp_wt_limits_STF(resource *resc_minmaxwt, attribute limit_attr, int min_or_max)
{
	int rc = 0;

	if (resc_minmaxwt == NULL || !(resc_minmaxwt->rs_value.at_flags & ATR_VFLAG_SET))
		return 0;

	/* Check minimum walltime limit if min_or_max == MIN_WALLTIME_LIMIT */
	if (min_or_max == MIN_WALLTIME_LIMIT) {
		if ((rc = resc_minmaxwt->rs_defin->rs_comp(&(resc_minmaxwt->rs_value), &limit_attr)) < 0)
			return (PBSE_EXCQRESC);
	}
	else {
		/* Check maximum walltime limit*/
		if ((rc = resc_minmaxwt->rs_defin->rs_comp(&(resc_minmaxwt->rs_value), &limit_attr)) > 0)
			return (PBSE_EXCQRESC);
	}
	return 0;
}

/**
 * @brief
 * 		chk_wt_limits_STF - check a STF job's min and max wlltime against the queue
 * 		and server maximum and mininum "walltime" limits.
 * 		max_walltime will be set to resources_max.walltime(if set), if resc_maxwt == NULL
 *
 * @param[in]	resc_minwt	-	resource_list.min_walltime
 * @param[in]	resc_maxwt	-	resource_list.max_walltime
 * @param[in]	pque	-	queue
 * @param[out]	pattr	-	resource_list, resource_list.max_walltime will be set if NULL
 * @return	int
 * @retval 	0	: within limits OR if resc_minwt == NULL
 * @retval	PBSE_EXCQRESC	: not within limits
 */
int
chk_wt_limits_STF(resource *resc_minwt, resource *resc_maxwt, pbs_queue *pque, attribute *pattr)
{
	attribute wt_min_queue_limit;
	attribute wt_max_queue_limit;
	attribute wt_max_server_limit;
	resource *new_res = NULL;
	resource_def *rscdef = NULL;
	int have_max_queue_limit = 0;
	int have_max_server_limit = 0;

	if (resc_minwt == NULL)
		return 0;
	/* The following should be true:
	 min_walltime >= resources_min.walltime
	 min_walltime <= resources_max.walltime
	 max_walltime >= resources_min.walltime
	 max_walltime <= resources_max.walltime
	 */
	/* Check against queue maximum */
	if (pque && get_wt_limit(&(pque->qu_attr[QA_ATR_ResourceMax]), &wt_max_queue_limit) == 0)
		have_max_queue_limit = 1;
	/* Check server maximum limit only if queue maximum limit is not present */
	if (!have_max_queue_limit && pque && get_wt_limit(&(server.sv_attr[SRV_ATR_ResourceMax]), &wt_max_server_limit) == 0)
		have_max_server_limit = 1;

#ifndef NAS /* localmod 026 */
	/* If res_maxwt is NULL and resources_max.walltime is set on either server/queue,
	 * set resource_list.max_walltime on the server/queue to value of resources_max.walltime.
	 * If resources_max.walltime is set on both server and queue, set resource_list.max_walltime
	 * to queue's resources_max.walltime. */
	if (resc_maxwt == NULL && pattr != NULL
		&& (have_max_queue_limit || have_max_server_limit)) {
		rscdef = find_resc_def(svr_resc_def, MAX_WALLTIME, svr_resc_size);
		new_res = add_resource_entry(pattr , rscdef);
		if (have_max_queue_limit)
			new_res->rs_defin->rs_set(&new_res->rs_value, &wt_max_queue_limit, SET);
		else if (have_max_server_limit)
			new_res->rs_defin->rs_set(&new_res->rs_value, &wt_max_server_limit, SET);
		new_res->rs_value.at_flags |= ATR_VFLAG_SET;
	}
#endif /* localmod 026 */
	/* Check against queue maximum */
	if (have_max_queue_limit) {
		if (PBSE_EXCQRESC == comp_wt_limits_STF(resc_minwt,
			wt_max_queue_limit, MAX_WALLTIME_LIMIT)
			|| PBSE_EXCQRESC == comp_wt_limits_STF(resc_maxwt,
			wt_max_queue_limit, MAX_WALLTIME_LIMIT))
			return (PBSE_EXCQRESC);
	}
	/* Queue limit not present, check against server maximum */
	else if (have_max_server_limit) {
		if ((PBSE_EXCQRESC == comp_wt_limits_STF(resc_maxwt,
			wt_max_server_limit, MAX_WALLTIME_LIMIT)
			|| PBSE_EXCQRESC == comp_wt_limits_STF(resc_minwt,
			wt_max_server_limit, MAX_WALLTIME_LIMIT)))
			return (PBSE_EXCQRESC);
	}
	/* Check against queue minimum */
	if (pque && (get_wt_limit(&(pque->qu_attr[QA_ATR_ResourceMin]), &wt_min_queue_limit) == 0)) {
		if (PBSE_EXCQRESC == comp_wt_limits_STF(resc_minwt,
			wt_min_queue_limit, MIN_WALLTIME_LIMIT)
			|| PBSE_EXCQRESC == comp_wt_limits_STF(resc_maxwt,
			wt_min_queue_limit, MIN_WALLTIME_LIMIT))
			return (PBSE_EXCQRESC);
	}
	return 0;
}
/**
 * @brief
 * 		chk_resc_limits - check job Resource_Limits attribute against the queue
 *		and server maximum and minimum values.
 *
 * @param[in]	pattr	-	attribute list containing resource request of the job
 * @param[in]	pque	-	queue
 *
 * @return	int
 * @retval 	0	: within limits
 * @retval 	PBSE_EXCQRESC	: not within limits
 */
int
chk_resc_limits(attribute *pattr, pbs_queue *pque)
{
	resource 	*atresc;
	resource 	*resc_maxwt = NULL;
	resource 	*resc_minwt = NULL;

	/* Get resource_list.min_walltime and resource_list.max_walltime if it is a STF job */
	atresc = (resource *)GET_NEXT(pattr->at_val.at_list);
	while (atresc != NULL) {
		if ((strcasecmp(atresc->rs_defin->rs_name, MIN_WALLTIME) == 0)) {
			resc_minwt = atresc;
		}
		else if ((strcasecmp(atresc->rs_defin->rs_name, MAX_WALLTIME) == 0)) {
			resc_maxwt = atresc;
		}
		/* No need to traverse further if both min_walltime and max_walltime are set */
		if (resc_minwt && resc_maxwt)
			break;
		atresc = (resource *)GET_NEXT(atresc->rs_link);
	}

	/* Check min and max walltime of a STF job against "walltime" resource limit on queue and server */
	if (resc_minwt != NULL && PBSE_EXCQRESC == chk_wt_limits_STF(resc_minwt, resc_maxwt, pque, pattr))
		return (PBSE_EXCQRESC);
	if ((comp_resc(&pque->qu_attr[QA_ATR_ResourceMin], pattr) == -1) ||
		comp_resc_gt)
		return (PBSE_EXCQRESC);

	/* now check individual resources against queue or server maximum */
	chk_svr_resc_limit(pattr,
		&pque->qu_attr[QA_ATR_ResourceMax],
		&server.sv_attr[SRV_ATR_ResourceMax],
		pque->qu_qs.qu_type);

	if (comp_resc_lt > 0)
		return (PBSE_EXCQRESC);
	return (0);
}

/**
 * @brief
 * 		svr_chkque - check if job can enter a queue
 *
 * @note
 * 		Note: the following fields must be set in the job structure before
 *	 	calling svr_chkque(): 	ji_wattr[JOB_ATR_job_owner]
 * 		set_objexid() will be called to set a uid/gid/name if not already set
 *
 * @param[in]	pjob	-	job structure
 * @param[in]	hostname	-	host machine
 * @param[in]	mtype	-	MOVE_TYPE_* type;  see server_limits.h
 *
 * @return	int
 * @retval	0	: all ok, job can enter queue
 * @retval	PBSE Number	: error code
 */

int
svr_chkque(job *pjob, pbs_queue *pque, char *hostname, int mtype)
{
	int i;

	/* if not already set, set up a uid/gid/name */

	if (!(pjob->ji_wattr[(int)JOB_ATR_euser].at_flags &
		ATR_VFLAG_SET) ||
		!(pjob->ji_wattr[(int)JOB_ATR_egroup].at_flags &
		ATR_VFLAG_SET)) {
		if ((i = set_objexid((void*)pjob, JOB_OBJECT, pjob->ji_wattr)) != 0)
			return (i);  /* PBSE_BADUSER or GRP */
	}

	/*
	 * 1. If the queue is an Execution queue ...
	 *    These are checked first because 1b - 1d are more damaging
	 *    (see local_move() in svr_movejob.c)
	 */

	if (pque->qu_qs.qu_type == QTYPE_Execution) {

		/* 1b. Check site restrictions */

		if (site_acl_check(pjob, pque))
			return (PBSE_PERM);

		/* 1c. cannot have an unknown resource */

		if (find_resc_entry(&pjob->ji_wattr[(int)JOB_ATR_resource],
			svr_resc_def+svr_resc_unk))
			return (PBSE_UNKRESC);

		/* 1d. cannot have an unknown attribute */

		if (pjob->ji_wattr[(int)JOB_ATR_UNKN].at_flags & ATR_VFLAG_SET)
			return (PBSE_NOATTR);

	}

	/* checks 2, 2a, and 3 are bypassed for a move by manager or qorder */

	if ((mtype != MOVE_TYPE_MgrMv) && (mtype != MOVE_TYPE_Order)) {

		/* 2. the queue must be enabled and the job limit not exceeded */

		if (pque->qu_attr[QA_ATR_Enabled].at_val.at_long == 0)
			return (PBSE_QUNOENB);

		if (pque->qu_attr[QA_ATR_MaxJobs].at_flags & ATR_VFLAG_SET) {
			int histjobs = 0;
			if (svr_chk_history_conf()) {
				/* calculate number of finished and moved jobs */
				histjobs = pque->qu_njstate[JOB_STATE_MOVED] +
						pque->qu_njstate[JOB_STATE_FINISHED] +
						pque->qu_njstate[JOB_STATE_EXPIRED];
			}
			/*
			 * check number of jobs in queue excluding
			 * finished and moved jobs
			 */
			if ((pque->qu_numjobs - histjobs) >=
				(pque->qu_attr[QA_ATR_MaxJobs].at_val.at_long))
				return (PBSE_MAXQUED);
		}

		/* 2a. if job array, check for queue max_array_size */

		if (pque->qu_attr[QA_ATR_maxarraysize].at_flags & ATR_VFLAG_SET) {
			if ((pjob->ji_qs.ji_svrflags & JOB_SVFLG_ArrayJob) &&
				(pjob->ji_ajtrk != NULL)) {
				if (pjob->ji_ajtrk->tkm_ct > pque->qu_attr[QA_ATR_maxarraysize].at_val.at_long)
					return (PBSE_MaxArraySize);
			}

		}

		/* 3. If "from_route_only" is true, only local route allowed */

		if ((pque->qu_attr[QA_ATR_FromRouteOnly].at_flags&ATR_VFLAG_SET) &&
			(pque->qu_attr[QA_ATR_FromRouteOnly].at_val.at_long == 1))
			if (mtype == MOVE_TYPE_Move)  /* ok if not plain user */
				return (PBSE_QACESS);
	}

	/* 4. If enabled, check the queue's host ACL */

	if (pque->qu_attr[QA_ATR_AclHostEnabled].at_val.at_long)
		if (acl_check(&pque->qu_attr[QA_ATR_AclHost],
			hostname, ACL_Host) == 0)
			if (mtype != MOVE_TYPE_MgrMv) /* ok if mgr */
				return (PBSE_BADHOST);

	/* 5a. If enabled, check the queue's user ACL */

	if (pque->qu_attr[QA_ATR_AclUserEnabled].at_val.at_long)
		if (acl_check(&pque->qu_attr[QA_ATR_AclUsers],
			pjob->ji_wattr[(int)JOB_ATR_job_owner].
			at_val.at_str, ACL_User) == 0)
			if (mtype != MOVE_TYPE_MgrMv) /* ok if mgr */
				return (PBSE_PERM);

	/* 5b. If enabled, check the queue's group ACL */

	if (pque->qu_attr[QE_ATR_AclGroupEnabled].at_val.at_long)
		if (acl_check(&pque->qu_attr[QE_ATR_AclGroup],
#ifdef WIN32
			pjob->ji_wattr[(int)JOB_ATR_egroup].at_val.at_str,
#else
			pjob->ji_wattr[(int)JOB_ATR_euser].at_val.at_str,
#endif
			ACL_Group) == 0)
			if (mtype != MOVE_TYPE_MgrMv) /* ok if mgr */
				return (PBSE_PERM);

	/* 6. If enabled, check the queue's required cred type */

	if ((pque->qu_attr[QA_ATR_ReqCredEnable].at_flags & ATR_VFLAG_SET) &&
		pque->qu_attr[QA_ATR_ReqCredEnable].at_val.at_long &&
		(pque->qu_attr[QA_ATR_ReqCred].at_flags & ATR_VFLAG_SET)) {
		char	*reqc = pque->qu_attr[QA_ATR_ReqCred].at_val.at_str;
		char	*jobc = pjob->ji_wattr[(int)JOB_ATR_cred].at_val.at_str;
		/*
		 **	The queue requires a cred, if job has none, or
		 **	it is the wrong one, reject.
		 */
		if (((pjob->ji_wattr[(int)JOB_ATR_cred].at_flags &
			ATR_VFLAG_SET) == 0 ||
			strcmp(reqc, jobc) != 0) &&
			(mtype != MOVE_TYPE_MgrMv))	/* ok if mgr */
			return PBSE_BADCRED;
	}

	/* checks 7 and 7a are bypassed for a move by manager or qorder */
	if (mtype != MOVE_TYPE_MgrMv) {
		/* 7. resources of the job must be in the limits of the queue */

		/* 7a. Check limit on number of jobs per entity in queue */

		i = check_entity_ct_limit_max(pjob, pque);
		if (i != 0)
			return i;

		i = check_entity_ct_limit_queued(pjob, pque);
		if (i != 0)
			return i;

		/* 7b. Check limit on number of jobs per entity in server only if */
		/*     this is a new job defined by state == JOB_STATE_TRANSIT    */
		if (pjob->ji_qs.ji_state == JOB_STATE_TRANSIT) {
			i = check_entity_ct_limit_max(pjob, NULL);
			if (i != 0)
				return i;
			i = check_entity_ct_limit_queued(pjob, NULL);
			if (i != 0)
				return i;
		}
	}

	/* Need to unset current default resources and reset them */
	/* from new queue before check if can enter that queue    */

	clear_default_resc(pjob);
	i = set_resc_deflt(pjob, JOB_OBJECT, pque);
	if (i == 0) {

		/* checks 7c and 7d are bypassed for a move by manager or qorder */
		if (mtype != MOVE_TYPE_MgrMv) {
			/* 7c. Check resource limits per entity in queue */
			i = check_entity_resc_limit_max(pjob, pque, NULL);
			if (i == 0)
				i = check_entity_resc_limit_queued(pjob, pque, NULL);

			if (i == 0) {
				/* 7d. Check resource limits per entity in server if this */
				/*     is a new job defined by state == JOB_STATE_TRANSIT */
				i = check_entity_resc_limit_max(pjob, NULL, NULL);
				if (i == 0)
					i = check_entity_resc_limit_queued(pjob, NULL, NULL);

				if (i == 0) {
					/* 7e.  test old gateing limits */
					i = chk_resc_limits(&pjob->ji_wattr[(int)JOB_ATR_resource], pque);
				}
			}
		}
	}

	/* after check unset defaults & reset based on current queue, if one */
	if (pjob->ji_qhdr) {
		clear_default_resc(pjob);
		(void)set_resc_deflt(pjob, JOB_OBJECT, NULL);
	}

	if (i != 0)
		if (mtype != MOVE_TYPE_MgrMv) /* ok if mgr */
			return (i);

	return (0);	/* all ok, job can enter queue */
}

/**
 * @brief
 *		check_block	-	See if "block" is set and send reply.
 *
 * @param[in,out]	pjob	-	job structure
 * @param[in]	message	-	message needs to be send to the port.
 */
void
check_block(job *pjob, char *message)
{
	int			port;
	int			ret;
	char			*phost;
	char			*jobid = pjob->ji_qs.ji_jobid;
	struct hostent		*hp;
#ifdef WIN32
	struct in_addr		addr;
#endif
	int			sock;
	struct sockaddr_in	remote;
	short			remote_sin_family;

	if ((pjob->ji_wattr[(int)JOB_ATR_block].at_flags & ATR_VFLAG_SET) == 0)
		return;
	if ((pjob->ji_wattr[(int) JOB_ATR_block].at_val.at_long) == -1)
		return;

	port = (int)pjob->ji_wattr[(int)JOB_ATR_block].at_val.at_long;
	/*
	 * The blocking attribute of the job needs to be unset . This contains the port number on which the job
	 * submission host is waiting for the exit status of the job . This is done here i.e check_block() as it is the
	 * final function in processing of a blocking job .
	 *
	 * Since for posterity it would be useful to record the fact that a job was a blocking job we set the
	 * port number to an impossible value instead of clearing it so that the database only contains
	 * a reference to the fact that a history job was a blocking job . Port number need not be recorded .
	 */
	pjob->ji_wattr[(int) JOB_ATR_block].at_val.at_long = -1;
	pjob->ji_wattr[(int) JOB_ATR_block].at_flags |= ATR_VFLAG_MODCACHE;
	pjob->ji_modified = 1;

	phost = get_hostPart(pjob->ji_wattr[(int)JOB_ATR_job_owner].at_val.at_str);
	if (port == 0 || phost == NULL) {
		sprintf(log_buffer, "%s: cannot reply %s:%d", __func__,
			phost == NULL ? "<no host>" : phost, port);
		log_event(PBSEVENT_ERROR, PBS_EVENTCLASS_JOB, LOG_NOTICE,
			jobid, log_buffer);
		return;
	}
	if ((hp = gethostbyname(phost)) == NULL) {
		sprintf(log_buffer, "%s: host %s not found", __func__, phost);
		log_event(PBSEVENT_ERROR, PBS_EVENTCLASS_JOB, LOG_NOTICE,
			jobid, log_buffer);
		return;
	}
	remote_sin_family = hp->h_addrtype;
	if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
		sprintf(log_buffer, "%s: socket %s", __func__, strerror(errno));
		log_event(PBSEVENT_ERROR, PBS_EVENTCLASS_JOB, LOG_NOTICE,
			jobid, log_buffer);
		return;
	}
	memset(&remote, 0, sizeof(remote));
	memcpy(&remote.sin_addr, hp->h_addr, hp->h_length);

	remote.sin_port = htons((unsigned short)port);
	remote.sin_family = remote_sin_family;
	if (connect(sock, (struct sockaddr *)&remote, sizeof(remote)) == -1) {
		sprintf(log_buffer, "%s: connect %s(%s:%d) %s", __func__, phost,
			inet_ntoa(remote.sin_addr), port, strerror(errno));
		log_event(PBSEVENT_ERROR, PBS_EVENTCLASS_JOB, LOG_NOTICE,
			jobid, log_buffer);
#ifdef WIN32
		closesocket(sock);
#else
		close(sock);
#endif
		return;
	}

	ret = CS_server_auth(sock);
	if ((ret != CS_SUCCESS) && (ret != CS_AUTH_CHECK_PORT)) {

		sprintf(log_buffer,
			"Unable to authenticate with , %s (%s:%d)", phost,
			inet_ntoa(remote.sin_addr), remote.sin_port);

		log_joberr(-1, __func__, log_buffer, jobid);
		goto done;
	}

	/*
	 **	All ready to talk... now send the info.
	 */

	DIS_tcp_setup(sock);
	ret = diswsi(sock, 1);		/* version */
	if (ret != DIS_SUCCESS)
		goto err;
	ret = diswst(sock, jobid);
	if (ret != DIS_SUCCESS)
		goto err;
	ret = diswst(sock, message);
	if (ret != DIS_SUCCESS)
		goto err;
	ret = diswsi(sock, pjob->ji_qs.ji_un.ji_exect.ji_exitstat);
	if (ret != DIS_SUCCESS)
		goto err;
	(void)DIS_tcp_wflush(sock);

	goto done;

err:
	sprintf(log_buffer, "%s: write %s(%s:%d) %s", __func__, phost,
		inet_ntoa(remote.sin_addr), port, dis_emsg[ret]);
	log_event(PBSEVENT_ERROR, PBS_EVENTCLASS_JOB, LOG_NOTICE,
		jobid, log_buffer);

done:
	if ((ret = CS_close_socket(sock)) != CS_SUCCESS) {

		sprintf(log_buffer, "Problem closing security context, %s (%s:%d)",
			phost, inet_ntoa(remote.sin_addr), port);

		log_joberr(-1, __func__, log_buffer, jobid);
	}

#ifdef WIN32
	closesocket(sock);
#else
	close(sock);
#endif

	return;
}

/**
 * @brief
 * 		job_wait_over - The execution wait time of a job has been reached, at
 *		least according to a work_task entry via which we were invoked.
 * @par
 *		IMP: Should not invoke/create any such work task(s) for history
 *	     	jobs (job with state JOB_STATE_MOVED or JOB_STATE_FINISHED).
 * @par
 *		If indeed the case, re-evaluate and set the job state.
 *
 * @param[in]	pwt	-	work task structure
 */

static void
job_wait_over(struct work_task *pwt)
{
	int	 newstate;
	int	 newsub;
	job     *pjob;

	pjob = (job *)pwt->wt_parm1;

	/* If history job, just return from here */
	if ((pjob->ji_qs.ji_state == JOB_STATE_MOVED) ||
		(pjob->ji_qs.ji_state == JOB_STATE_FINISHED))
		return;

#ifndef NDEBUG
	{
		time_t now = time(NULL);
		time_t when = ((job *)pjob)->ji_wattr[(int)JOB_ATR_exectime].
			at_val.at_long;
		struct work_task *ptask;

		if (when > now) {
			sprintf(log_buffer, msg_badwait, ((job *)pjob)->ji_qs.ji_jobid);
			log_err(-1, "job_wait_over", log_buffer);

			/* recreate the work task entry */

			ptask = set_task(WORK_Timed, when, job_wait_over, pjob);
			if (ptask) {
				append_link(&pjob->ji_svrtask,
					&ptask->wt_linkobj, ptask);
			}
			return;
		}
	}
#endif
	pjob->ji_qs.ji_svrflags &= ~JOB_SVFLG_HASWAIT;

	/* clear the exectime attribute */
	job_attr_def[(int)JOB_ATR_exectime].
	at_free(&pjob->ji_wattr[(int)JOB_ATR_exectime]);
	pjob->ji_modified = 1;
	svr_evaljobstate(pjob, &newstate, &newsub, 0);
	(void)svr_setjobstate(pjob, newstate, newsub);
}

/**
 * @brief
 * 		job_set_wait - set up a work task entry that will trigger at the execution
 *		wait time of the job.
 * @par
 *		IMP: History jobs:
 *	     If the SERVER is configured for history jobs and the job is
 *	     in state JOB_STATE_MOVED or JOB_STATE_FINISHED, then do not
 *	     create/schedule any further work task on this job which may
 *	     modify the HISTORY jobs.
 * @par
 *		This is called as the at_action (see attribute.h) function associated
 *		with the execution-time job attribute.
 * 		parameter pjob is a job * cast to a void *
 * 		parameter mode is unused;  do it for all action modes
 *
 * @param[in]	pattr	-	execution-time job attribute.
 * @param[in]	pjob	-	pjob is a job * cast to a void *
 * @param[in]	pattr	-	mode is unused;  do it for all action modes
 */

int
job_set_wait(attribute *pattr, void *pjob, int mode)
{
	struct work_task *ptask;
	long		  when;

	/* Return 0 if it is history job */
	if ((((job *)pjob)->ji_qs.ji_state == JOB_STATE_MOVED) ||
		(((job *)pjob)->ji_qs.ji_state == JOB_STATE_FINISHED))
		return (0);

	if ((pattr->at_flags & ATR_VFLAG_SET) == 0)
		return (0);
	when  = pattr->at_val.at_long;
	ptask = (struct work_task *)GET_NEXT(((job *)pjob)->ji_svrtask);

	/* Is there already an entry for this job?  Then reuse it */

	if (((job *)pjob)->ji_qs.ji_svrflags & JOB_SVFLG_HASWAIT) {
		while (ptask) {
			if ((ptask->wt_event == WORK_Timed) &&
				(ptask->wt_func == job_wait_over) &&
				(ptask->wt_parm1 == pjob)) {
				ptask->wt_event = when;
				return (0);
			}
			ptask = (struct work_task *)GET_NEXT(ptask->wt_linkobj);
		}
	}

	ptask = set_task(WORK_Timed, when, job_wait_over, pjob);
	if (ptask == NULL)
		return (-1);
	append_link(&((job *)pjob)->ji_svrtask, &ptask->wt_linkobj, ptask);

	/* set JOB_SVFLG_HASWAIT to show job has work task entry */

	((job *)pjob)->ji_qs.ji_svrflags |= JOB_SVFLG_HASWAIT;
	return (0);
}


/**
 * @brief
 * 		default_std - make the default name for standard output or error
 *		"job_name".[e|o]job_sequence_number
 *		or
 *		"job_name".[e|o]job_sequence_number^index^ for an Array Job
 * 		parameter key is 'e' for stderr, 'o' for stdout
 *	 	parameter to points to a buffer into which the name is returned; callers
 *		are responsible for ensuring that the buffer is of sufficient size
 *
 * @param[in]	pjob	-	pointer to job structure
 * @param[in]	key	-	the letter before the sequence number
 * @param[out]	to	-	output name
 */

static void
default_std(job *pjob, int key, char *to)
{
	int   len;
	char *pd;


	pd = strrchr(pjob->ji_wattr[(int)JOB_ATR_jobname].at_val.at_str, '/');
	if (pd)
		++pd;
	else
		pd = pjob->ji_wattr[(int)JOB_ATR_jobname].at_val.at_str;
	len = strlen(pd);

	(void)strcpy(to, pd);		/* start with the job name */
	*(to + len++) = '.';            /* the dot        */
	*(to + len++) = (char)key;	/* the letter     */
	pd = pjob->ji_qs.ji_jobid;      /* the seq_number */
	while (isdigit((int)*pd))
		*(to + len++) = *pd++;
	*(to + len) = '\0';
	if (pjob->ji_qs.ji_svrflags & JOB_SVFLG_ArrayJob) {
		/* Array Job - append special substituation string for index */
		strcat(to, ".");
		strcat(to, PBS_FILE_ARRAY_INDEX_TAG);
	}
}


/**
 * @brief
 * 		prefix_std_file - build the absolute pathname for the job's standard
 * 		output or error:
 *		outputhost:$PBS_O_WORKDIR/job_name.[eo]job_sequence_number
 *
 * @param[in]	pjob	-	pointer to job structure
 * @param[in]	key	-	integer that is either 'e' or 'o'
 *
 * @return	char *
 * @retval	NULL	-	Failed to construct prefix
 * @retval	!NULL	-	Pointer to the prefix string
 */
char *
prefix_std_file(job *pjob, int key)
{
	char	*name = NULL;
	char	*outputhost;
	char	*wdir;

	if (pbs_conf.pbs_output_host_name)
		outputhost = pbs_conf.pbs_output_host_name;
	else
		outputhost = get_hostPart(pjob->ji_wattr[(int)JOB_ATR_job_owner].at_val.at_str);
	wdir     = get_variable(pjob, "PBS_O_WORKDIR");
	if (outputhost) {
		int len;

		len = strlen(outputhost) +
			strlen(pjob->ji_wattr[(int)JOB_ATR_jobname].at_val.at_str)
		+ PBS_MAXSEQNUM + strlen(PBS_FILE_ARRAY_INDEX_TAG) + 6;
		if (wdir)
			len += strlen(wdir);
		name = malloc(len);
		if (name) {
			strcpy(name, outputhost);	/* the qsub host name	*/
			strcat(name, ":");	/* the :		*/
			if (wdir) {
#ifdef WIN32
				if (IS_UNCPATH(wdir)) {
					/*
					 * wdir is UNC path so no need to add
					 * <hostname:> into std output or error file
					 */
					memset(name, 0, len);
					strncpy(name, wdir, strlen(wdir));
				} else {
					strncat(name, wdir, strlen(wdir)); /* the qsub cwd */
				}
				 /* add the final / if not there*/
				if (name[strlen(name) - 1] != '/')
					strncat(name, "/", 1);
#else
				strcat(name, wdir);	/* the qsub cwd		*/
				strcat(name, "/");	/* the final /		*/
#endif
			}
			/* now add the rest	*/
			default_std(pjob, key, name+strlen(name));
		}
	}
	return (name);
}

/**
 * @brief
 * 		cat_default_std  - function concatenates the default name for the job's
 * 		stdout/err filename to the string residing in buffer "in".  Space for the
 * 		newly created string is dynamically acquired and returned to the caller
 * 		via the argument "out".  It is the responsibility of some other function
 * 		to free this heap memory when it is no longer needed.
 * @par
 * 		parameter key is 'e' for stderr, 'o' for stdout
 * 		parameter in is the location of the input buffer
 * 		parameter out is the return location of the result string
 *
 * @param[in]	pjob	-	job structure
 * @param[in]	key	-	the letter before the sequence number
 * @param[in]	in	-	the string residing in buffer "in".
 * @param[in]	out	-	Space for the newly created string.
 */
void
cat_default_std(job *pjob, int key, char *in, char **out)
{
	char *result;
	int  len;
	len = strlen(in) +
		strlen(pjob->ji_wattr[(int)JOB_ATR_jobname].at_val.at_str) +
	PBS_MAXSEQNUM + 5 + strlen(PBS_FILE_ARRAY_INDEX_TAG) + 1;
	if ((result = malloc(len))) {
		strcpy(result, in);
		default_std(pjob, key, &result[strlen(result)]);
	}
	*out = result;
}


/**
 * @brief
 * 		get_jobowner - copy the basic job owner's name, without the @host suffix.
 *		The "to" buffer must be large enough (PBS_MAXUSER+1).
 *
 * @param[in]	from	-	 basic job owner's name
 * @param[out]	to	-	"to" buffer where name is copied.
 */
void
get_jobowner(char *from, char *to)
{
	int i;

	for (i=0; i<PBS_MAXUSER; ++i) {
		if ((*(from+i) == '@') || (*(from+i) == '\0'))
			break;
		*(to+i) = *(from+i);
	}
	*(to+i) = '\0';
}

/**
 * @brief
 * 		cvrt_fqn_to_name - copy the name only (no @host suffix) to the "to" buffer.
 *		"to" buffer is (PBS_MAXUSER+1) characters long. Null terminate string.
 *
 * @param[in]	from	-	 basic job owner's name
 * @param[out]	to	-	"to" buffer where name is copied.
 */
void
cvrt_fqn_to_name(char *from, char *to)
{
	int i;

	for (i=0; i<PBS_MAXUSER; ++i) {
		if ((*(from+i) == '@') || (*(from+i) == '\0'))
			break;
		*(to+i) = *(from+i);
	}
	*(to+i) = '\0';
}

/**
 * @brief
 *		get_hostPart - return a pointer to the "host" part of a "name@host"
 *		string.
 *
 * @param[in]	from	-	pointer to a string of the form user@host
 *
 * @return	char *
 * @retval	pointer to host part of the input string
 * @retval 	NULL	: if no '@' in input or host part following the '@' is null
 *
 * @par MT-safe:	yes
 */
char *
get_hostPart(char *from)
{
	char *pc;

	if ((pc = strchr(from, '@')) == NULL)
		return NULL;
	else if (*(++pc) == '\0')
		return NULL;
	return pc;
}

#define CVT_SIZE 1500

/**
 * @brief
 * 		set_select_and_place - create a select and place resource for the job
 *
 * @param[in]	objtype	-	type of the object
 * @param[in]	pobj	-	pointer to void * object, not used here.
 * @param[in]	patr	-	pointer to attriute structure which contains the resources.
 *
 * @return	int
 * @retval	0	: success
 * @retval	PBSE_Error	: Error Code
 *
 * @par MT-safe:	No.
 */
static int
set_select_and_place(int objtype, void *pobj, attribute *patr)
{
	pbs_list_head     collectresc;
	static char  *cvt = NULL;
	static size_t cvt_len;
	char	     *ndspec;
	resource     *presc;
	resource     *prescsl;
	resource     *prescpc;
	resource_def *prdefnd;
	resource_def *prdefpc;
	resource_def *prdefsl;
	int	      rc;
	extern  int   resc_access_perm;

	if (cvt == NULL) {
		cvt = malloc(CVT_SIZE);
		if (cvt == NULL)
			return PBSE_SYSTEM;
		else
			cvt_len = CVT_SIZE;
	}

	prdefpc = find_resc_def(svr_resc_def, "place", svr_resc_size);
	prdefnd = find_resc_def(svr_resc_def, "nodes", svr_resc_size);
	prdefsl = find_resc_def(svr_resc_def, "select", svr_resc_size);
	presc   = find_resc_entry(patr, prdefnd);

	/* add "select" and "place" resource */

	prescsl = add_resource_entry(patr, prdefsl);
	if (prescsl == NULL)
		return PBSE_SYSTEM;

	prescpc = find_resc_entry(patr, prdefpc);
	if (prescpc == NULL) {
		prescpc = add_resource_entry(patr, prdefpc);
		if (prescpc == NULL)
			return PBSE_SYSTEM;
	}

	if (presc && ((ndspec = presc->rs_value.at_val.at_str) != NULL)) {

		/* Have a nodes spec, use it  to make select and place */

		if ((rc=cvt_nodespec_to_select(ndspec, &cvt, &cvt_len, patr)) != 0)
			return rc;

		if ((rc = prdefsl->rs_decode(&prescsl->rs_value, NULL, "select", cvt)) != 0)
			return rc;
		prescsl->rs_value.at_flags |= ATR_VFLAG_DEFLT;


		if (strstr(ndspec, "#excl") != NULL) {
			prdefpc->rs_decode(&prescpc->rs_value, NULL, "place", "scatter:excl");
		} else if (strstr(ndspec, "#shared") != NULL) {
			prdefpc->rs_decode(&prescpc->rs_value, NULL, "place", "scatter:share");
		} else {
			prdefpc->rs_decode(&prescpc->rs_value, NULL, "place", "scatter");
		}

	} else {
		attribute_def *objatrdef;

		/* No nodes spec, use ncpus/mem/arch/host/software	*/
		/* from the resource_List attribute			*/

		if (objtype == JOB_OBJECT)
			objatrdef = &job_attr_def[(int)JOB_ATR_resource];
		else
			objatrdef = &resv_attr_def[(int)RESV_ATR_resource];

		CLEAR_HEAD(collectresc);
		resc_access_perm = READ_ONLY;
		if (objatrdef->at_encode(patr, &collectresc, objatrdef->at_name, NULL, ATR_ENCODE_CLIENT, NULL) > 0) {
			svrattrl *psvrl;

			*cvt = '1';
			*(cvt+1) = '\0';
			psvrl = (svrattrl *)GET_NEXT(collectresc);
			while (psvrl) {
				resource_def *prdefcopy;

				prdefcopy = find_resc_def(svr_resc_def, psvrl->al_resc,
					svr_resc_size);
				if (prdefcopy && (prdefcopy->rs_flags & ATR_DFLAG_CVTSLT)) {
					size_t cvtneed;

					/* how much space is needed in cvt buffer, 	 */
					/* +5 = one for : = possible quotes and null */
					cvtneed = strlen(psvrl->al_resc) +
						strlen(psvrl->al_value) + 5;
					if ((strlen(cvt) + cvtneed) > cvt_len) {
						/* double cvt buffer */
						char *tcvt;
						tcvt = realloc(cvt, 2*cvt_len);
						if (tcvt) {
							cvt = tcvt;
							cvt_len *= 2;
						} else {
							log_event(PBSEVENT_ERROR,
								PBS_EVENTCLASS_SERVER, LOG_ALERT,
								msg_daemonname,
								"unable to malloc space");
							return PBSE_SYSTEM;
						}
					}
					strcat(cvt, ":");
					strcat(cvt, psvrl->al_resc);
					strcat(cvt, "=");
					if (strpbrk(psvrl->al_value, "\"'+:=()")) {
						char *quotec;
						if (strchr(psvrl->al_value, (int)'"'))
							quotec = "'";
						else
							quotec = "\"";
						strcat(cvt, quotec);
						strcat(cvt, psvrl->al_value);
						strcat(cvt, quotec);
					} else {
						strcat(cvt, psvrl->al_value);
					}
				}
				psvrl = (svrattrl *)GET_NEXT(psvrl->al_link);
			}
			free_attrlist(&collectresc);
		} else {
			strcpy(cvt, "ncpus=1");
		}
		if (prdefsl->rs_decode(&prescsl->rs_value, NULL, "select", cvt) == 0) {

			if (objtype == JOB_OBJECT) { /* set default flg only on jobs */
				prescsl->rs_value.at_flags |= ATR_VFLAG_DEFLT;
				if ((prescpc->rs_value.at_flags & (ATR_VFLAG_SET|ATR_VFLAG_DEFLT)) != ATR_VFLAG_SET)
					if (prdefpc->rs_decode(&prescpc->rs_value, NULL, "place", "pack") == 0)

						if (objtype==JOB_OBJECT) /* set default flg only on jobs */
							prescpc->rs_value.at_flags |= ATR_VFLAG_DEFLT;
			}
		}
	}
	return 0;
}

/**
 * @brief
 * 		set_chunk_sums() - set the sums of consumable resources listed
 *		in the chunks as job limits if not already set.
 *
 * @param[in]	pselectattr	-	attribute structure from which we parse the select directive
 * @param[in]	pattr	-	attribute structure where resource limit is set.
 *
 * @return	int
 * @retval	0	: success
 * @retval	PBSE_Error	: Error Code
 *
 * @par MT-safe:	No.
 */

int
set_chunk_sum(attribute  *pselectattr, attribute *pattr)
{
	char     *chunk;
	int       i;
	int       j;
	int       nchk;
	int       nelem;
	int	  rc;
	int	  default_flag;
	int	  total_chunks = 0;
	struct key_value_pair *pkvp;
	resource	  *presc;
	resource_def	  *pdef;
	static attribute   tmpatr;

	if ((pselectattr == NULL) || (pattr == NULL))
		return 0;

	/* first clear the summation table used later */

	for (i=0; svr_resc_sum[i].rs_def; ++i) {
		(void)memset((char *)&svr_resc_sum[i].rs_attr, 0, sizeof(struct attribute));

		svr_resc_sum[i].rs_set = 0;
		svr_resc_sum[i].rs_prs = NULL;
	}

	/* now, look through the resource limits specified for the job        */
	/* if any matches an entry in the table, set the pointer and set flag */

	presc = (resource *)GET_NEXT(pattr->at_val.at_list);
	while (presc) {
		for (i=0; svr_resc_sum[i].rs_def; ++i) {
			if (strcmp(presc->rs_defin->rs_name, svr_resc_sum[i].rs_def->rs_name) == 0) {
				/* found one, save the resource ptr in sum table */
				svr_resc_sum[i].rs_prs = presc;

			}
		}
		presc = (resource *)GET_NEXT(presc->rs_link);
	}

	/* now, parse the select directive */

	chunk = parse_plus_spec(pselectattr->at_val.at_str, &rc);
	if (rc != 0)
		return rc;
	while (chunk) {
#ifdef NAS /* localmod 082 */
		if (parse_chunk(chunk, 0, &nchk, &nelem, &pkvp, NULL) == 0)
#else
		if (parse_chunk(chunk, &nchk, &nelem, &pkvp, NULL) == 0)
#endif /* localmod 082 */
		{
			total_chunks += nchk;
			for (j=0; j<nelem; ++j) {
				for (i=0; svr_resc_sum[i].rs_def; ++i) {
					if (strcmp(svr_resc_sum[i].rs_def->rs_name, pkvp[j].kv_keyw) == 0) {
						rc = svr_resc_sum[i].rs_def->rs_decode(&tmpatr, 0,
							0, pkvp[j].kv_val);
						if (rc != 0)
							return rc;
						else if ((tmpatr.at_flags & ATR_VFLAG_SET) == 0)
							return PBSE_BADATVAL;	/* illegal null value */
						if (svr_resc_sum[i].rs_def->rs_type == ATR_TYPE_SIZE)
							tmpatr.at_val.at_size.atsv_num *= nchk;
						else
							tmpatr.at_val.at_long *= nchk;

						(void)svr_resc_sum[i].rs_def->rs_set(&svr_resc_sum[i].rs_attr, &tmpatr, INCR);
						svr_resc_sum[i].rs_set = 1;
						break;
					}
				}
			}
		} else {
			return (PBSE_BADATVAL);
		}
		chunk = parse_plus_spec(NULL, &rc);
		if (rc != 0)
			return rc;
	}

	/* check that the user asked for at least one chunk total */

	if (total_chunks <= 0)
		return (PBSE_BADATVAL);

	/*
	 * now that we have summed up the chunks, for each one summed (set) ...
	 * set or reset the corresponding job wide limit
	 */
	for (i=0; svr_resc_sum[i].rs_def; ++i) {
		if (svr_resc_sum[i].rs_set) {

			if (svr_resc_sum[i].rs_prs) {
				presc = svr_resc_sum[i].rs_prs;
				default_flag = presc->rs_value.at_flags & ATR_VFLAG_DEFLT;
			} else {
				default_flag = ATR_VFLAG_DEFLT;
				presc = add_resource_entry(pattr, svr_resc_sum[i].rs_def);
				if (presc == NULL)
					return PBSE_SYSTEM;
			}
			(void)svr_resc_sum[i].rs_def->rs_set(&presc->rs_value, &svr_resc_sum[i].rs_attr, SET);
			presc->rs_value.at_flags |= default_flag;

		}
	}

	/* set pseudo-resource "nodect" to the number of chunks */

	pdef = find_resc_def(svr_resc_def, "nodect", svr_resc_size);
	if (pdef) {
		presc = find_resc_entry(pattr, pdef);
		if (presc == NULL)
			presc = add_resource_entry(pattr, pdef);
		if (presc) {
			presc->rs_value.at_val.at_long = total_chunks;
			presc->rs_value.at_flags |= ATR_VFLAG_SET | ATR_VFLAG_DEFLT | ATR_VFLAG_MODCACHE;
		}
	}
	return 0;
}

/**
 * @par
 * 		make_schedselect - decode a selection specification,  and produce the
 *		the "schedselect" attribute which contains any default resources
 *		missing from the chunks in the select spec.
 *		Also translates the value of any boolean resource to the "formal"
 *		value of "True" or "False" for the Scheduler who needs to know it
 *		is a boolean and not a string or number.
 *
 *	@param[in]	patrl	-	(not used)
 * 	@param[in]	pselect -	pointer to the select specification
 * 	@param[in]	pque	-	used to obtain queue defaults
 *	@param[in,out]	psched	-	scheduler attribute
 *
 *	@return	int
 *	@retval	0	: success
 *	@retval	PBSE_Error	: Error Code.
 *
 *	@par MT-safe:	No.
 */

extern int resc_access_perm;

static int
make_schedselect(attribute *patrl, resource *pselect,
	pbs_queue *pque, attribute *psched)
{
	int	     rc;
	char	     *sched_select_out = NULL;

	if ((pselect == NULL) || (psched == NULL)) {
		return (PBSE_SYSTEM);
	}

	rc = do_schedselect(pselect->rs_value.at_val.at_str, (struct server *)&server, (pbs_queue *)pque, &resc_in_err, &sched_select_out);

	if (rc == 0) {
		free_str(psched);
		(void)decode_str(psched, NULL, NULL, sched_select_out);
		psched->at_flags |= ATR_VFLAG_DEFLT;
	}
	return (rc);
}

/**
 * @brief
 * 		set_deflt_resc - set resource attributes based on a set of defaults provided
 *
 *	@param[in,out]	jb	-	is the job resource list attribute
 *	@param[in]	dflt	-	is the parent object (queue, server, ...) list of defaults
 *	@param[in]	selflg	-	if set means set select/place from the defaults
 */

static void
set_deflt_resc(attribute *jb, attribute *dflt, int selflg)
{
	resource       *prescjb;
	resource       *prescdt;
	resource_def   *seldef;
	resource_def   *plcdef;

	seldef = find_resc_def(svr_resc_def, "select", svr_resc_size);
	plcdef = find_resc_def(svr_resc_def, "place",  svr_resc_size);

	if (dflt->at_flags & ATR_VFLAG_SET) {

		/* for each resource in the default value list */

		for (prescdt = (resource *)GET_NEXT(dflt->at_val.at_list);
			prescdt;
			prescdt = (resource *)GET_NEXT(prescdt->rs_link)) {

			if ((prescdt->rs_defin == seldef) ||
				(prescdt->rs_defin == plcdef)) {
				if (!selflg)
					continue; /* dont use select/place */
			}

			if (prescdt->rs_value.at_flags & ATR_VFLAG_SET) {
				/* see if the job already has that resource */
				prescjb = find_resc_entry(jb, prescdt->rs_defin);
				if ((prescjb == NULL) ||
					((prescjb->rs_value.at_flags &
					ATR_VFLAG_SET) == 0)) {

					if (prescjb == NULL)
						prescjb = add_resource_entry(jb,
							prescdt->rs_defin);
					if (prescjb) {
						if (prescdt->rs_defin->rs_set(
							&prescjb->rs_value,
							&prescdt->rs_value,
							SET) == 0)
							prescjb->rs_value.at_flags |=
								(ATR_VFLAG_SET|ATR_VFLAG_DEFLT);
						jb->at_flags |= ATR_VFLAG_MODCACHE;
					}

				}
			}
		}
	}
}

/**
 * @brief
 * 		set_resc_deflt - sets default resource limit values
 * 		on the object pointed to by input "pobj"
 *
 * @param[in]	pobj	-	job/reservation structure
 * @param[in]	objtype	-	type of object - job or reservation.
 * @param[in,out]	pque	-	Queue structure
 *
 * @return	int
 * @retval	0	: success
 * @retval	PBSE_Error	: Error Code.
 *
 * @par MT-safe:	No.
 */
int
set_resc_deflt(void *pobj, int objtype, pbs_queue *pque)
{
	static resc_resv  *presv;
	job	   *pjob;
	attribute  *pdest = NULL;
	attribute  *psched = NULL;
	resource   *presc;
	resource_def *prdefsl;
	resource_def *prdefpc;
	int           rc;

	switch (objtype) {
		case	JOB_OBJECT:
			pjob = (job *)pobj;
			assert(pjob != NULL);
			if (pque == NULL)
				pque = pjob->ji_qhdr;
			assert(pque != NULL);
			pdest = &pjob->ji_wattr[(int)JOB_ATR_resource];
			psched = &pjob->ji_wattr[(int)JOB_ATR_SchedSelect];
			break;

		case	RESC_RESV_OBJECT:
			presv = (resc_resv *)pobj;
			assert(presv != NULL);
			pque = NULL;
			pdest = &presv->ri_wattr[(int)RESV_ATR_resource];
			psched = &presv->ri_wattr[(int)RESV_ATR_SchedSelect];
			break;

		case	RESV_JOB_OBJECT:
			presv = (resc_resv *)pobj;
			assert(presv != NULL);
			pjob = presv->ri_jbp;
			assert(pjob != NULL);
			pque = pjob->ji_qhdr;
			assert(pque != NULL);
			pdest = &presv->ri_wattr[(int)RESV_ATR_resource];
			break;

		default:
			break;
	}


	/* set defaults based on the Queue's resources_default */
	if (pque) {
		set_deflt_resc(pdest,
			&pque->qu_attr[(int)QA_ATR_ResourceDefault], 1);
	}

	/* set defaults based on the Server' resources_default */
	set_deflt_resc(pdest,
		&server.sv_attr[(int)SRV_ATR_resource_deflt], 1);

	/* set defaults based on the Queue's resources_max */
	if (pque) {
		set_deflt_resc(pdest,
			&pque->qu_attr[(int)QA_ATR_ResourceMax], 0);
	}

	/* set defaults based on the Server's resources_max */
	set_deflt_resc(pdest,
		&server.sv_attr[(int)SRV_ATR_ResourceMax], 0);


	/* if needed, set "select" and "place" from the other resources */

	prdefsl = find_resc_def(svr_resc_def, "select", svr_resc_size);
	presc   = find_resc_entry(pdest, prdefsl);
	/* if not set, set select/place */
	if ((presc == NULL) || ((presc->rs_value.at_flags & ATR_VFLAG_SET)==0))
		if ((rc = set_select_and_place(objtype, pobj, pdest)) != 0)
			return rc;

	prdefpc = find_resc_def(svr_resc_def, "place", svr_resc_size);
	presc = find_resc_entry(pdest, prdefpc);
	/* if "place" still not set, force to "free" */
	if ((presc == NULL) || ((presc->rs_value.at_flags&ATR_VFLAG_SET)==0)) {
		presc = add_resource_entry(pdest, prdefpc);
		if (presc == NULL)
			return PBSE_SYSTEM;
		if (prdefpc->rs_decode(&presc->rs_value, NULL, "place", "free") == 0)
			if (objtype == JOB_OBJECT)	/* only for jobs, set DEFLT */
				presc->rs_value.at_flags |= ATR_VFLAG_DEFLT;
	}

	/* now set up the Scheduler's version of select JOB_ATR_SchedSelect */
	presc   = find_resc_entry(pdest, prdefsl);
	if (presc) {
		if ((rc = make_schedselect(pdest, presc , pque, psched)) == 0)
			rc = set_chunk_sum(psched, pdest);

	} else
		rc = PBSE_SYSTEM;
	return rc;
}

/**
 * @brief
 * 		set_statechar - set the job state attribute to the letter that corresponds
 *		to its current state.
 *
 * @param[in,out]	pjob	-	job whose state attribute needs to e set.
 */

void
set_statechar(job *pjob)
{
	if (pjob->ji_qs.ji_state == JOB_STATE_RUNNING) {
		static char suspend = 'S';
		static char useractive = 'U';

		if (pjob->ji_qs.ji_svrflags & JOB_SVFLG_Suspend)
			pjob->ji_wattr[JOB_ATR_state].at_val.at_char = suspend;
		else if (pjob->ji_qs.ji_svrflags & JOB_SVFLG_Actsuspd)
			pjob->ji_wattr[JOB_ATR_state].at_val.at_char = useractive;
		else
			pjob->ji_wattr[JOB_ATR_state].at_val.at_char =
				*(statechars + pjob->ji_qs.ji_state);
	} else
		pjob->ji_wattr[JOB_ATR_state].at_val.at_char =
			*(statechars + pjob->ji_qs.ji_state);
	pjob->ji_wattr[JOB_ATR_state].at_flags |= ATR_VFLAG_MODCACHE;
}

/**
 * @brief
 * 		state_char2int - return the state from character form to int form.
 *
 * @param[in]	stc	-	state in character form
 *
 * @return	state in int form
 * @retval	-1	: failure
 */

int
state_char2int(char stc)
{
	int  i;

	for (i=0; i < strlen(statechars); i++) {
		if (statechars[i] == stc)
			return (i);
	}
	return (-1);
}


/**
 * @brief
 * 		eval_chkpnt - if the job's checkpoint attribute is "c=nnnn" and
 * 		nnnn is less than the queue' minimum checkpoint time, reset
 *		to the queue min time.
 *
 * @param[in]	jobckp	-	the job's checkpoint attribute
 * @param[in]	queckp	-	the queue's checkpoint attribute
 */

void
eval_chkpnt(attribute *jobckp, attribute *queckp)
{
	char *pv;

	if (((jobckp->at_flags & ATR_VFLAG_SET) == 0)  ||
		((queckp->at_flags & ATR_VFLAG_SET) == 0))
		return;		/* need do nothing */

	pv = jobckp->at_val.at_str;
	if ((*pv == 'c') || (*pv == 'w')) {
		int jobs;
		char queues[30];
		char ckt;

		ckt = *pv;
		if (*++pv == '=')
			pv++;
		jobs = atoi(pv);
		if (jobs < queckp->at_val.at_long) {
			(void)sprintf(queues, "%c=%ld", ckt, queckp->at_val.at_long);
			free_str(jobckp);
			(void)decode_str(jobckp, 0, 0, queues);
		}
	}
}


#ifndef NDEBUG
/**
 * @brief
 * 		correct_ct - This is a work-around for an as yet unfound bug where
 *		the counts of jobs in each state sometimes (rarely) become wrong.
 *		When this happens, the count for a state can become negative.
 *		If this is detected (see above), this routine is called to reset
 *		all of the counts and log a message.
 *
 * @param[in]	pqj	-	pbs queue structure
 */

static void
correct_ct(pbs_queue *pqj)
{
	int	   i;
	char	  *pc;
	job	  *pjob;
	pbs_queue *pque;


	(void)sprintf(log_buffer, "Job state counts incorrect, server %d: ",
		server.sv_qs.sv_numjobs);
	server.sv_qs.sv_numjobs = 0;
	for (i=0; i<PBS_NUMJOBSTATE-4; ++i) {
		pc = log_buffer + strlen(log_buffer);
		(void)sprintf(pc, "%d ", server.sv_jobstates[i]);
		server.sv_jobstates[i] = 0;
	}
	if (pqj) {
		pc = log_buffer + strlen(log_buffer);
		(void)sprintf(pc, "; queue %s %d: ", pqj->qu_qs.qu_name,
			pqj->qu_numjobs);
		for (i=0; i<PBS_NUMJOBSTATE-4; ++i) {
			pc = log_buffer + strlen(log_buffer);
			(void)sprintf(pc, "%d ", pqj->qu_njstate[i]);
		}
	}
	log_event(PBSEVENT_ERROR, PBS_EVENTCLASS_SERVER, LOG_DEBUG,
		msg_daemonname, log_buffer);

	for (pque = (pbs_queue *)GET_NEXT(svr_queues); pque;
		pque = (pbs_queue *)GET_NEXT(pque->qu_link)) {
		pque->qu_numjobs = 0;
		for (i=0; i<PBS_NUMJOBSTATE-4; ++i)
			pque->qu_njstate[i] = 0;
	}

	for (pjob = (job *)GET_NEXT(svr_alljobs); pjob;
		pjob = (job *)GET_NEXT(pjob->ji_alljobs)) {
		server.sv_qs.sv_numjobs++;
		server.sv_jobstates[pjob->ji_qs.ji_state]++;
		if (pjob->ji_qhdr) {
			(pjob->ji_qhdr)->qu_numjobs++;
			(pjob->ji_qhdr)->qu_njstate[pjob->ji_qs.ji_state]++;
		}
	}
	return;
}
#endif 	/* NDEBUG */



/**
 * @brief
 * 		Update_Resvstate_if_resv - function checks if the job is
 *      a reservation-job and, if so, the reservation "state"
 *		is computed based on:
 *	    current "job state", "job substate", "reservation state"
 *	    and, the "reserve_start"/"reserve_end" times vs current time.
 * @par
 *		The assumption that's made is that "reserve_start",
 *		and "reserve_end" have been computed prior to the calling
 *		of this function -  typically by making a call to function
 *		"start_end_dur_wall ()".
 *
 * @param[in]	pjob	-	job which needs to be checked
 */

void
Update_Resvstate_if_resv(job *pjob)
{
	attribute *ap;
	resc_resv *presv;
	int	  beyondStart = 0;

	if (pjob == NULL)
		return;

	/* Is this job a reservation job ? */
	if ((presv = pjob->ji_resvp) == NULL)
		return;

	ap = &pjob->ji_wattr[JOB_ATR_reserve_state];

	/* This is a reservation job, compute reservation state */
	/* Remark: one thing that might be worth considering is */
	/* other ways to treat a reservation job if a user has  */
	/* placed a hold on the reservation and the time window */
	/* for the reservation has passed.  Maybe we would want */
	/* the reservation job to transition to an ordinary job */
	/* and remain in the system				*/
	/* OR, what if it is decided to ignore the reservation  */
	/* and qrun the reservation job, ignoring any reservation*/
	/* window.  Should the job in that setting cease to be  */
	/* marked as a reservation job?				*/

	if (presv->ri_wattr[RESV_ATR_end]
		.at_val.at_long > 0) {
		if (presv->ri_wattr[RESV_ATR_end]
			.at_val.at_long <= time_now) {

			if (ap->at_val.at_long != RESV_FINISHED) {
				ap->at_val.at_long = RESV_FINISHED;
				ap->at_flags |= ATR_VFLAG_SET |
					ATR_VFLAG_MODIFY | ATR_VFLAG_MODCACHE;
				pjob->ji_modified = 1;
			}
			return;
		}
	}

	if (presv->ri_wattr[RESV_ATR_start]
		.at_val.at_long <= time_now)
		beyondStart = 1;

	switch (pjob->ji_qs.ji_state) {
		case JOB_STATE_TRANSIT:
			if (pjob->ji_qs.ji_substate == JOB_SUBSTATE_TRANSIN) {
				ap->at_val.at_long = RESV_UNCONFIRMED;
				ap->at_flags |= ATR_VFLAG_SET | ATR_VFLAG_MODIFY
					| ATR_VFLAG_MODCACHE;
				pjob->ji_modified = 1;
			}
			break;
		case JOB_STATE_HELD:
			if (ap->at_val.at_long != presv->ri_qs.ri_state) {
				ap->at_val.at_long = presv->ri_qs.ri_state;
				ap->at_flags |= ATR_VFLAG_SET |
					ATR_VFLAG_MODIFY | ATR_VFLAG_MODCACHE;
				pjob->ji_modified = 1;
			}
			if (beyondStart) {
				if (presv->ri_qs.ri_state == RESV_CONFIRMED) {
					if (ap->at_val.at_long != RESV_TIME_TO_RUN) {
						ap->at_val.at_long = RESV_TIME_TO_RUN;
						ap->at_flags |= ATR_VFLAG_SET |
							ATR_VFLAG_MODIFY | ATR_VFLAG_MODCACHE;
						pjob->ji_modified = 1;
					}
				}
			}
			break;
		case JOB_STATE_WAITING:
			if (beyondStart) {
				if (ap->at_val.at_long != RESV_TIME_TO_RUN) {
					ap->at_val.at_long = RESV_TIME_TO_RUN;
					ap->at_flags |= ATR_VFLAG_SET |
						ATR_VFLAG_MODIFY | ATR_VFLAG_MODCACHE;
					pjob->ji_modified = 1;
				}
			} else if (ap->at_val.at_long != RESV_CONFIRMED) {
				ap->at_val.at_long = RESV_CONFIRMED;
				ap->at_flags |= ATR_VFLAG_SET |
					ATR_VFLAG_MODIFY | ATR_VFLAG_MODCACHE;
				pjob->ji_modified = 1;
			}
			break;
		case JOB_STATE_QUEUED:
			if (beyondStart) {
				if (ap->at_val.at_long != RESV_TIME_TO_RUN) {
					ap->at_val.at_long = RESV_TIME_TO_RUN;
					ap->at_flags |= ATR_VFLAG_SET |
						ATR_VFLAG_MODIFY | ATR_VFLAG_MODCACHE;
					pjob->ji_modified = 1;
				}
			} else {
				if (ap->at_val.at_long != RESV_CONFIRMED) {
					ap->at_val.at_long = RESV_CONFIRMED;
					ap->at_flags |= ATR_VFLAG_SET |
						ATR_VFLAG_MODIFY | ATR_VFLAG_MODCACHE;
					pjob->ji_modified = 1;
				}
			}

			break;
		case JOB_STATE_RUNNING:
			if (beyondStart) {
				/*operator didn't run the job early*/
				if (ap->at_val.at_long != RESV_RUNNING) {
					ap->at_val.at_long = RESV_RUNNING;
					ap->at_flags |= ATR_VFLAG_SET |
						ATR_VFLAG_MODIFY | ATR_VFLAG_MODCACHE;
					pjob->ji_modified = 1;
				}
			}
			break;
		case JOB_STATE_EXITING:
			if (ap->at_val.at_long != RESV_BEING_DELETED) {
				ap->at_val.at_long = RESV_BEING_DELETED;
				ap->at_flags |= ATR_VFLAG_SET | ATR_VFLAG_MODIFY |
					ATR_VFLAG_MODCACHE;
				pjob->ji_modified = 1;
			}
			break;
	}
}


/**
 * @brief
 * 		get_wall - get the value of "walltime" for the job
 *
 * @param[in]	jp	-	jp is a valid job pointer
 *
 * @return	int
 * @retval	-1	: function failed
 * @retval	walltime value	: function succeeded
 *
 * @note
 * 		Assumption: input jp is a valid job pointer
 */
int
get_wall(job *jp)
{
	resource_def	*rscdef;
	resource	*pres;

	rscdef = find_resc_def(svr_resc_def, "walltime", svr_resc_size);
	if (rscdef == 0)
		return (-1);
	pres = find_resc_entry(&jp->ji_wattr[JOB_ATR_resource], rscdef);
	if (pres == 0)
		return (-1);
	else if ((pres->rs_value.at_flags & ATR_VFLAG_SET) == 0)
		return (-1);
	else
		return pres->rs_value.at_val.at_long;   /*wall time value*/
}

/**
 * @brief
 * 		get the amount of "walltime" resource USED for the job
 *
 * @param[in]	jp	- Pointer to a job
 *
 * @return	Success/Failure
 * @retval	-1 		- Function failed
 * @retval	walltime value	- Function succeeded
 * @note
 * 		Assumption: input jp is a valid job pointer
 *
 * @return	wall time value
 * @retval	-1	: failure
 *
 */
int
get_used_wall(job *jp)
{
	resource_def	*rscdef;
	resource	*pres;

	rscdef = find_resc_def(svr_resc_def, "walltime", svr_resc_size);
	if (rscdef == 0)
		return (-1);
	pres = find_resc_entry(&jp->ji_wattr[JOB_ATR_resc_used], rscdef);
	if (pres == 0)
		return (-1);
	else if ((pres->rs_value.at_flags & ATR_VFLAG_SET) == 0)
		return (-1);
	else
		return pres->rs_value.at_val.at_long;   /*wall time value*/
}

/*-------------------------------------------------------------------------------
 Functions for establishing reservation related tasks
 --------------------------------------------------------------------------------*/
/**
 * @brief
 * 		Time4reply	-	reply when reservation becomes unconfirmed.
 *
 * @param[in,out]	ptask	-	work task structure which contains reservation structure.
 */
static	void
Time4reply(struct work_task *ptask)
{
	resc_resv	*presv = ptask->wt_parm1;

	if (presv->ri_brp) {
		char buf[512] = {0};
		if (presv->ri_qs.ri_state == RESV_UNCONFIRMED ||
			presv->ri_qs.ri_state == RESV_BEING_ALTERED)
			snprintf(buf, sizeof(buf), "%s UNCONFIRMED", presv->ri_qs.ri_resvID);
		else if (presv->ri_qs.ri_state == RESV_CONFIRMED) {
			/*Remark: this part of the if is unlikely to happen*/
			/*        reply would happen in req_rescreserve()  */
			snprintf(buf, sizeof(buf), "%s CONFIRMED", presv->ri_qs.ri_resvID);
		}

		(void)reply_text(presv->ri_brp, PBSE_NONE, buf);
		presv->ri_brp = NULL ;
	}
}

/**
 * @brief
 * 		Time4resv - function to execute when the "start time" for a
 *		CONFIRMED reservation finally arrives.
 *		At some prior point in time a task that's to be processed
 *		at "start time" is put onto  the "timed-tasks" list.  This
 *		task's function pointer field points at this function -
 *		see "gen_task_Time4resv" regards the task on the "timed_tasks"
 *		list.
 *		A pointer to the resc_resv structure is put in the task's
 *		"wt_parm1" void* field.
 * @note
 *		Note: function "dispatch_task" unlinks the task structure
 *	      from whatever list(s) it's on and it frees the memory
 *	      consumed by the work_task struct
 *
 * @param[in,out]	ptask	-	work task structure which contains reservation structure.
 *
 *	Returns   none
 */
static	void
Time4resv(struct work_task *ptask)
{
	resc_resv	*presv = ptask->wt_parm1;
	int		pbs_ecode;
	int		state, sub;


	/* cause to have issued to the qmgr subsystem
	 * a request to start the reservation's queue
	 * note: if presv is for a job reservation no
	 * request gets made
	 */

	pbs_ecode = change_enableORstart(presv, Q_CHNG_START, "True");
	if (!pbs_ecode) {
		job *pjob;

		/*
		 *this is really the line  we want once the scheduler
		 *has the capability to say "begin this reservation"
		 */

		eval_resvState(presv, RESVSTATE_Time4resv, 0, &state, &sub);
		resv_setResvState(presv, state, sub);
		cmp_resvStateRelated_attrs((void *)presv,
			presv->ri_qs.ri_type);
		if (presv->ri_qs.ri_type == RESV_JOB_OBJECT &&
			(pjob = presv->ri_jbp)) {

			svr_evaljobstate(pjob, &state, &sub, 0);
			(void)svr_setjobstate(pjob, state, sub);
		}

		/*ok, time for the reservation to be running so adjust
		 *server's/queue's resource accounting to reflect that
		 *their "resources_assigned" values are now higher by the
		 *amounts requested by the reservation.  Also, set a flag to
		 *indicate that in the future resources have to be returned
		 *and, setup so that the scheduler gets notified
		 */
		set_resc_assigned((void *)presv, 1, INCR);
		presv->ri_giveback = 1;

		resv_exclusive_handler(presv);

		set_scheduler_flag(SCH_SCHEDULE_JOBRESV,dflt_scheduler);

		/*notify the relevant persons that the reservation time has arrived*/
		if(presv->ri_qs.ri_tactive == time_now){
			svr_mailownerResv(presv, MAIL_BEGIN, MAIL_NORMAL, "");
			account_resvstart(presv);
		}

		presv->resv_start_task = NULL;
		if ((ptask = set_task(WORK_Timed, time_now + 60,
			Time4resv1, presv)) != 0) {

			ptask->wt_aux = 4;	/*we will attempt up to 5 times*/

			/* set things so that the reservation going away causes */
			/* any "yet to be processed" work tasks also going away */

			append_link(&presv->ri_svrtask, &ptask->wt_linkobj, ptask);
		}
	}
}


/**
 * @brief
 * 		Time4resv1 - function that's executed when a "n-th reminder"
 *		on the timed-tasks list gets dispatched.
 * @par
 *		This function checks if the reservation is still in state
 *		RESV_TIME_TO_RUN and, if it is, it sets into the global
 *		server variable "svr_do_schedule" an appropriate command
 *		for the scheduler, notes how many times it has done this
 *		and, from that, determines whether or not to put itself
 *		back on the task list.
 *
 * @param[in,out]	ptask	-	work task structure which contains reservation structure.
 *
 *	@return   none
 */
static	void
Time4resv1(struct work_task *ptask)
{
	struct work_task   *pwt;
	resc_resv	   *presv = ptask->wt_parm1;



	if (presv->ri_wattr[RESV_ATR_state].at_val.at_long !=
		RESV_TIME_TO_RUN)

		return;    /*no more reminders needed*/

	/*put on another reminder timed for 60 seconds in the future*/
	if (ptask->wt_aux > 0) {
		if ((pwt = set_task(WORK_Timed, time_now + 60,
			Time4resv1, presv)) != 0) {

			pwt->wt_aux = ptask->wt_aux - 1;

			/* set things so that the job going away will result in */
			/* any "yet to be processed" work tasks also going away */

			append_link(&presv->ri_svrtask, &pwt->wt_linkobj, pwt);
		}
	}

#if 0
	For general reservation, where duration can be less than end - start
	time, the scheduler can(eventually) choose when to run the reservation
	so going to want something different than what we currently have for
		reservation jobs
#endif

		/* specify the scheduling command for the scheduler */
		set_scheduler_flag(SCH_SCHEDULE_JOBRESV, dflt_scheduler);
}


/**
 * @brief
 * 		Time4resvFinish - function that's to execute when "time_now" exceeds
 *		the ending time of the reservation
 *
 * @param[in,out]	ptask	-	work task structure which contains reservation structure.
 *
 *	Returns   none
 */
void
Time4resvFinish(struct work_task *ptask)
{
	resc_resv		*presv = ptask->wt_parm1;
	struct  batch_request	*preq;

	/* If more than one occurrence then process the occurrence end. The sequence
	 * of events that are needed for the end of a standing reservation are:
	 *
	 * 1) change the queue state from started True to False
	 * 2) Delete all Running Jobs and Keep Queued Jobs
	 * 3) Once all Obits are received (see running_jobs_count):
	 *    3.a) Determine if occurrences were missed
	 *    3.b) Add the next occurrence start and end event on the work task 	 *
	 */
	presv->resv_end_task = NULL;
	if (presv->ri_wattr[RESV_ATR_resv_count].at_val.at_long > 1) {
		int ridx = presv->ri_wattr[RESV_ATR_resv_idx].at_val.at_long;
		int rcount = presv->ri_wattr[RESV_ATR_resv_count].at_val.at_long;

		DBPRT(("reached end of occurrence %d/%d\n", ridx, rcount))

		/* When recovering past the last occurrence the standing reservation is purged
		 * in a manner similar to an advance reservation  */
		if (ridx < rcount) {
			/*
			 * Invoke the reservation end hook for every occurrence
			 */
			struct batch_request *newreq;
			newreq = alloc_br(PBS_BATCH_ResvOccurEnd);
			if (newreq != NULL)	{
				newreq->rq_perm |= ATR_DFLAG_MGWR;
				strcpy(newreq->rq_user, pbs_current_user);
				strcpy(newreq->rq_host, server_host);
				strcpy(newreq->rq_ind.rq_delete.rq_objname, presv->ri_qs.ri_resvID);
				if (issue_Drequest(PBS_LOCAL_CONNECTION, newreq, resvFinishReply, NULL, 0) == -1) {
					free_br(newreq);
				}
				tickle_for_reply();
			}
			/* 1) Change queue state from started True to False and change
			 * state of the reservation queue
			 */
			change_enableORstart(presv, Q_CHNG_START, "FALSE");
			(void)resv_setResvState(presv, RESV_DELETING_JOBS, RESV_DELETING_JOBS);

			/* 2) Issue delete messages to jobs in running state and keep jobs in
			 * Queued state. Server periodically monitors the reservation queue
			 * to determine if all jobs in run state have been purged.
			 */
			delete_occurrence_jobs(presv);

			/* Done processing the current occurrence. If MOM locks up during cleanup
			 * or stage out for a duration that exceeds the time of the last occurrence
			 * then this handler will be invoked again with an occurrence index (ridx)
			 * equal to the last occurrence (rcount) and be processed as a DeleteReservation
			 * event in the next block.
			 */
			return;
		}
	}
	/* If an advance reservation or last occurrence of a standing reservation
	 * construct a "deleteResv" batch request for the dummy connection
	 * PBS_LOCAL_CONNECTION; Issue that request via "issue_Drequest".
	 * "issue_Drequest" will notice this request is to be handled here
	 * and call upon "dispatch_request", the mechanism for dispatching of
	 * incomming requests.  "issue_Drequest" is passed a function that's
	 * to deal with the reply to the request when it arrives.  A task
	 * having this reply handling function (pointer) is placed on the
	 * global list, "task_list_event".  Request dispatching proceeds
	 * as it normally does and invokes the function "reply_send", which
	 * is to send back a reply to the batch request.  In this instance,
	 * (response recipent local) reply_send  moves the task of dealing
	 * with the "reply to request" on to "task_list_immediate", so it
	 * can get recognized the next time function "next_task" in the
	 * server's main loop gets invoked
	 */
	if ((preq = alloc_br(PBS_BATCH_DeleteResv)) != 0) {
		/*setup field so don't fail a check on perm*/
		preq->rq_perm |= ATR_DFLAG_MGWR;

		strcpy(preq->rq_user, pbs_current_user);
		strcpy(preq->rq_host, server_host);
		strcpy(preq->rq_ind.rq_delete.rq_objname,
			presv->ri_qs.ri_resvID);

		(void)issue_Drequest(PBS_LOCAL_CONNECTION, preq,
			resvFinishReply, NULL, 0);

		/*notify relevant parties that the reservation's
		 *ending time has arrived and reservation is being deleted
		 */
		svr_mailownerResv(presv, MAIL_END, MAIL_NORMAL, "");

		tickle_for_reply();
		set_last_used_time_node(presv, 1);
	}
}

/**
 * @brief
 * 		If processing a Standing Reservation
 * 		1) Get the occurrence index and the total number of occurrences,
 *    		if this is the last occurrence, an event to purge reservation is added to
 *    		the work task.
 * 		2) If not last, then set the next occurrence's start and end time and
 *    		appropriate execvnodes and add to server's work task
 * 		3) Update state and save reservation
 * @par
 * 		This function is also entered upon reservation recovery to handle skipped
 * 		occurrences.
 *
 * @param[in]	presv	-	Standing Reservation
 */
static void
Time4occurrenceFinish(resc_resv *presv)
{
	time_t			newend;
	time_t			newstart;
	int			state = 0;
	int			sub = 0;
	int			rc = 0;
	int			rcount_adjusted = 0;
	char			*execvnodes = NULL;
	char			*newxc = NULL;
	char			**short_xc = NULL;
	char			**tofree = NULL;
	time_t			dtstart;
	time_t			dtend;
	time_t			next;
	time_t			now;
	struct work_task	*ptask = NULL;
	pbsnode_list_t		*pl = NULL;
	char			start_time[9] = {0};	/* 9 = sizeof("%H:%M:%S")[=8] + 1('\0') */
	resource_def		*rscdef = NULL;
	resource		*prsc = NULL;
	attribute		atemp = {0};
	int			j = 2;
	int			ridx = presv->ri_wattr[RESV_ATR_resv_idx].at_val.at_long;
	int			rcount = presv->ri_wattr[RESV_ATR_resv_count].at_val.at_long;
	char			*rrule = presv->ri_wattr[RESV_ATR_resv_rrule].at_val.at_str;
	char			*tz = presv->ri_wattr[RESV_ATR_resv_timezone].at_val.at_str;

	/* the next occurrence returned by get_occurrence is counted from the current
	 * one which is at index 1. */

	/* If the start time of the reservation was altered, copy from RESV_ATR_start
 	 * will make the next instance to have it's start time altered so take the start
 	 * time from the ri_alter_stime. */
	if (presv->ri_alter_stime) {
		dtstart = presv->ri_alter_stime;
		presv->ri_alter_stime = 0;
	} else
		dtstart = presv->ri_wattr[RESV_ATR_start].at_val.at_long;

	dtend = presv->ri_wattr[RESV_ATR_end].at_val.at_long;
	next = dtstart;
	now = time(NULL);

	/* Add next occurrence and account for missed occurrences..
	 * A missed occurrence is one that had its reservation end time in the past.
	 * next can be -1 if it exceeds the end date of Unix time in 2038.
	 */
	while (dtend <= now && next != -1) {
		/* get occurrence that is "j" numbers away from dtstart. */
		next = get_occurrence(rrule, dtstart, tz, j);
		if (presv->ri_alter_standing_reservation_duration) {
			presv->ri_qs.ri_duration = presv->ri_alter_standing_reservation_duration;
			presv->ri_alter_standing_reservation_duration = 0;
		}
		dtend = next + presv->ri_qs.ri_duration;

		/* Index of next occurrence from dtstart*/
		j++;

		/* Log information notifying of missed occurrences. An occurrence is
		 * "missed" either if it was interrupted, in which case it never was
		 * instructed to "give back" its allocated resources, or if the server
		 * was down for an extended period of time extending over a number of
		 * occurrences.
		 * The first time around j has the value 2 and is incremented to 3 to
		 * account for the next occurrence. Any increments after that characterize
		 * missed occurrences that are noted in the log file. */
		if (j > 3 || presv->ri_giveback == 0) {
			if (strftime(start_time, sizeof(start_time),
				"%H:%M:%S", localtime(&dtstart))) {
				sprintf(log_buffer,
					"reservation occurrence %d/%d "
					"scheduled at %s was skipped because "
					"its end time is in the past",
					ridx, rcount, start_time);
			} else {
				sprintf(log_buffer,
					"reservation occurrence %d/%d was "
					"skipped because its end time is in "
					"the past", ridx, rcount);
			}
			log_event(PBSEVENT_ERROR, PBS_EVENTCLASS_RESV,
				LOG_NOTICE, presv->ri_qs.ri_resvID,
				log_buffer);
		}

		/* The reservation index is incremented */
		ridx++;

		/* If skipped past the last occurrence then return to the
		 * caller which will handle issuing a reservation delete
		 * message
		 */
		if (ridx > rcount) {
			presv->ri_wattr[RESV_ATR_resv_idx].at_val.at_long = rcount;

			if ((ptask = set_task(WORK_Immed, 0, Time4resvFinish, presv)) != 0)
				append_link(&presv->ri_svrtask, &ptask->wt_linkobj, ptask);

			return;
		}

		DBPRT(("stdg_resv: next occurrence start = %s", ctime(&next)))
		DBPRT(("stdg_resv: next occurrence end   = %s", ctime(&dtend)))

	}
	DBPRT(("stdg_resv: execvnodes sequence   = %s\n", presv->ri_wattr[RESV_ATR_resv_execvnodes].at_val.at_str))
	execvnodes = strdup(presv->ri_wattr[RESV_ATR_resv_execvnodes].at_val.at_str);
	short_xc = (char **) unroll_execvnode_seq(execvnodes, &tofree);

	/* when a reservation is reconfirmed, the 'count' of occurrences may differ
	 * from the original 'count', we need to adjust for the actual remaining
	 * count
	 */
	rcount_adjusted = rcount - get_execvnodes_count(execvnodes);

	/* The reservation index starts at 1 but the short_xc array at 0. Occurrence 1
	 * is therefore given by array element 0. */
	newxc = strdup(short_xc[ridx-rcount_adjusted-1]);

	/* clean up helper variables */
	free(short_xc);
	free(execvnodes);
	free_execvnode_seq(tofree);

	/* Decrement resources assigned */
	if (presv->ri_giveback == 1) {
		set_resc_assigned((void *)presv, 1, DECR);
		presv->ri_giveback = 0;
	}

	/* Reservation Nodes are freed and a -possibly- new set assigned */
	free_resvNodes(presv);

	/* Set the new start time, end time, and occurrence index */
	newstart = next;
	newend = (time_t)(newstart + presv->ri_qs.ri_duration);

	presv->ri_wattr[RESV_ATR_start].at_val.at_long = newstart;
	presv->ri_wattr[RESV_ATR_start].at_flags |= ATR_VFLAG_SET | ATR_VFLAG_MODIFY
		| ATR_VFLAG_MODCACHE;
	presv->ri_qs.ri_stime = newstart;

	presv->ri_wattr[RESV_ATR_end].at_val.at_long = newend;
	presv->ri_wattr[RESV_ATR_end].at_flags |= ATR_VFLAG_SET | ATR_VFLAG_MODIFY
		| ATR_VFLAG_MODCACHE;
	presv->ri_qs.ri_etime = newend;

	presv->ri_wattr[RESV_ATR_resv_idx].at_val.at_long = ridx;
	presv->ri_wattr[RESV_ATR_resv_idx].at_flags |= ATR_VFLAG_SET
		| ATR_VFLAG_MODIFY | ATR_VFLAG_MODCACHE;

	presv->ri_wattr[RESV_ATR_duration].at_val.at_long = presv->ri_qs.ri_duration;
	presv->ri_wattr[RESV_ATR_duration].at_flags |= ATR_VFLAG_SET
		| ATR_VFLAG_MODIFY | ATR_VFLAG_MODCACHE;

	rscdef = find_resc_def(svr_resc_def, "walltime", svr_resc_size);
	prsc = find_resc_entry(&presv->ri_wattr[RESV_ATR_resource], rscdef);
	atemp.at_flags = ATR_VFLAG_SET;
	atemp.at_type = ATR_TYPE_LONG;
	atemp.at_val.at_long = presv->ri_qs.ri_duration;
	rscdef->rs_set(&prsc->rs_value, &atemp, SET);
	presv->ri_wattr[RESV_ATR_resource].at_flags |= ATR_VFLAG_SET | ATR_VFLAG_MODIFY | ATR_VFLAG_MODCACHE;

	/* Assign the allocated resources to the reservation
	 * and the reservation to the associated vnodes
	 */
	rc = assign_resv_resc(presv, newxc);
	free(newxc);

	if (rc != PBSE_NONE) {
		sprintf(log_buffer, "problem assigning resource to reservation occurrence (%d)", rc);
		log_event(PBSEVENT_ERROR, PBS_EVENTCLASS_RESV, LOG_NOTICE, presv->ri_qs.ri_resvID, log_buffer);
		return;
	}

	/*place "Time4resv" task on "task_list_timed"*/
	if ((rc = gen_task_Time4resv(presv)) != 0) {
		sprintf(log_buffer, "problem generating task Time for occurrence (%d)", rc);
		log_event(PBSEVENT_ERROR, PBS_EVENTCLASS_RESV, LOG_NOTICE, presv->ri_qs.ri_resvID, log_buffer);
		return;
	}
	/* add task to handle the end of the next occurrence */
	if ((rc = gen_task_EndResvWindow(presv)) != 0) {
		(void)resv_purge(presv);
		sprintf(log_buffer, " problem generating reservation end task for occurrence (%d)", rc);
		log_event(PBSEVENT_ERROR, PBS_EVENTCLASS_RESV, LOG_NOTICE, presv->ri_qs.ri_resvID, log_buffer);
		return;
	}

	/* compute new values for state and substate */
	eval_resvState(presv, RESVSTATE_gen_task_Time4resv, 0, &state, &sub);

	/*
	 * Walk the nodes list associated to this reservation to determine if any
	 * node is unavailable. If so, mark this next occurrence as degraded
	 */
	for (pl = presv->ri_pbsnode_list; pl != NULL; pl = pl->next) {
		if ((pl->vnode->nd_state & (INUSE_OFFLINE | INUSE_OFFLINE_BY_MOM | INUSE_DOWN | INUSE_UNKNOWN)) != 0) {
			DBPRT(("vnode %s unavailable\n", pl->vnode->nd_name))
			state = RESV_DEGRADED;
			sub = RESV_DEGRADED;

			presv->ri_degraded_time = newstart;
			break;
		}
	}

	/* If the reservation already has a retry time set then its substate is
	 * marked degraded
	 */
	if (presv->ri_wattr[RESV_ATR_retry].at_val.at_long > time_now) {
		sub = RESV_DEGRADED;
	}
	/* otherwise, if it has a valid degraded time past the cutoff time then
	 * set the retry time to be the half time to the degraded time
	 */
	else if (presv->ri_degraded_time > (time_now + reserve_retry_cutoff)) {
		set_resv_retry(presv, time_now + ((presv->ri_degraded_time - time_now)/2));
	}
	/* otherwise, if degraded, default to setting a retry time in a
	 * "reasonable" time in the future
	 */
	else if ((presv->ri_wattr[RESV_ATR_retry].at_flags & ATR_VFLAG_SET) &&
		presv->ri_wattr[RESV_ATR_retry].at_val.at_long > 0 &&
		presv->ri_wattr[RESV_ATR_retry].at_val.at_long <= time_now) {
		set_resv_retry(presv, time_now + 120);
	}

	if (sub == RESV_DEGRADED) {
		DBPRT(("degraded_time of %s is %s", presv->ri_qs.ri_resvID, ctime(&presv->ri_degraded_time)))
	}

	/* Set the reservation state and substate */
	resv_setResvState(presv, state, sub);

	if (presv->ri_modified)
		(void)job_or_resv_save((void *)presv, SAVERESV_FULL, RESC_RESV_OBJECT);
}

/**
 * @brief
 * 		Handler to check on number of remaining jobs in RUNNING/EXITING state.
 * 		The jobs asynchronously update their state as they get purged from the system.
 * 		Once all jobs have been purged, the process for adding the next occurrence is
 * 		triggered.
 *
 * @param[in,out]	ptask	-	work task structure which contains reservation
 */
static void
running_jobs_count(struct work_task *ptask)
{
	resc_resv *presv;
	int rj;
	presv = (resc_resv *) ptask->wt_parm1;

	/* Number of remaining jobs in RUNNING/EXITING state */
	rj = presv->ri_qp->qu_njstate[JOB_STATE_RUNNING] + presv->ri_qp->qu_njstate[JOB_STATE_EXITING];

	if (rj == 0)
		/* If none are left then process the next occurrence */
		Time4occurrenceFinish(presv);
	else
		/* If some are left then issue another set of requests to clean up */
		delete_occurrence_jobs(presv);
}

/**
 * @brief
 * 		Delete all Running jobs associated to a standing reservation queue.
 * 		The queued jobs will remain queued
 *
 * @param[in,out]	presv	-	The reservation to obtain queue and jobs from
 *
 */
static void
delete_occurrence_jobs(resc_resv *presv)
{
	job *pjob, *pnxj;
	struct work_task *ptask;

	pjob = (job *)GET_NEXT(presv->ri_qp->qu_jobs);
	while (pjob != NULL) {
		/* Get the next job from the queue before the job is unlinked as a result
		 * of job_abt
		 */
		pnxj = (job *)GET_NEXT(pjob->ji_jobque);
		if (pjob->ji_qs.ji_state == JOB_STATE_RUNNING && pjob->ji_qs.ji_substate != JOB_SUBSTATE_ABORT)
			(void) job_abt(pjob, "Deleting running job at end of reservation occurrence");

		pjob = pnxj;
	}
	/* Check if all running jobs have been cleaned up every 5 seconds.
	 * Link the work task into the server's reservation info work tasks such that
	 * the work task gets deleted when the reservation is deleted.
	 * This can happen if a pbs_rdel is invoked on the reservation while it is
	 * processing the deletion of running jobs. */
	if ((ptask = set_task(WORK_Timed, time_now + 5, running_jobs_count, presv)) != 0)
		append_link(&presv->ri_svrtask, &ptask->wt_linkobj, ptask);
}

/**
 * @brif
 * 		Time4_term - function that's to execute when server wants to delete
 *		a reservation, e.g. when the server needs to delete the reservation
 *		part of a "reservation job".
 *
 * @param[in]	ptask	-	The reservation to be deleted.
 *
 *	@return	none
 */
void
Time4_term(struct work_task *ptask)
{
	resc_resv		*presv = ptask->wt_parm1;
	struct  batch_request	*preq;

	/*construct a "deleteResv" batch request for the dummy connection
	 *PBS_LOCAL_CONNECTION; Issue that request via "issue_Drequest".
	 *"issue_Drequest" will notice this request is to be handled here
	 *and call upon "dispatch_request", the mechanism for dispatching of
	 *incomming requests.  "issue_Drequest" is passed a function that's
	 *to deal with the reply to the request when it arrives.  A task
	 *having this reply handling function (pointer) is placed on the
	 *global list, "task_list_event".  Request dispatching proceeds
	 *as it normally does and invokes the function "reply_send", which
	 *is to send back a reply to the batch request.  In this instance,
	 *(response recipent local) reply_send  moves the task of dealing
	 *with the "reply to request" on to "task_list_immediate", so it
	 *can get recognized the next time function "next_task" in the
	 *server's main loop gets invoked
	 */

	if ((preq = alloc_br(PBS_BATCH_DeleteResv)) != 0) {
		/*setup field so don't fail a check on perm*/
		preq->rq_perm |= ATR_DFLAG_MGWR;

		strcpy(preq->rq_user, pbs_current_user);
		strcpy(preq->rq_host, server_host);
		strcpy(preq->rq_ind.rq_delete.rq_objname,
			presv->ri_qs.ri_resvID);

		(void)issue_Drequest(PBS_LOCAL_CONNECTION, preq,
			resvFinishReply, NULL, 0);

		/*notify relevant parties that the reservation's
		 *ending time has arrived and reservation is being deleted
		 */
		svr_mailownerResv(presv, MAIL_END, MAIL_NORMAL, "");

		tickle_for_reply();
		set_last_used_time_node(presv, 1);
	}
}


/**
 * @brief
 * 		Time4_I_term - function that's to execute when an "interactive"
 *		reservation is submitted with a ngative "I" value.  If the state
 *		on the reservation is UNCONFIRMED a delete request is generated.
 *		If the state is not UNCONFIRMED, this task function does nothing.
 *
 * @param[in]	ptask	-	The reservation submitted with a negative "I" value.
 *
 *	@return	none
 */
void
Time4_I_term(struct work_task *ptask)
{
	resc_resv		*presv = ptask->wt_parm1;
	struct  batch_request	*preq;

	if (presv->ri_qs.ri_state != RESV_UNCONFIRMED)
		return;

	/*construct a "deleteResv" batch request for the dummy connection
	 *PBS_LOCAL_CONNECTION; Issue that request via "issue_Drequest".
	 *"issue_Drequest" will notice this request is to be handled here
	 *and call upon "dispatch_request", the mechanism for dispatching of
	 *incomming requests.  "issue_Drequest" is passed a function that's
	 *to deal with the reply to the request when it arrives.  A task
	 *having this reply handling function (pointer) is placed on the
	 *global list, "task_list_event".  Request dispatching proceeds
	 *as it normally does and invokes the function "reply_send", which
	 *is to send back a reply to the batch request.  In this instance,
	 *(response recipent local) reply_send  moves the task of dealing
	 *with the "reply to request" on to "task_list_immediate", so it
	 *can get recognized the next time function "next_task" in the
	 *server's main loop gets invoked
	 */

	if ((preq = alloc_br(PBS_BATCH_DeleteResv)) != 0) {
		/*setup field so don't fail a check on perm*/
		preq->rq_perm |= ATR_DFLAG_MGWR;

		strcpy(preq->rq_user, pbs_current_user);
		strcpy(preq->rq_host, server_host);
		strcpy(preq->rq_ind.rq_delete.rq_objname,
			presv->ri_qs.ri_resvID);

		(void)issue_Drequest(PBS_LOCAL_CONNECTION, preq,
			resvFinishReply, NULL, 0);

		/*notify relevant parties that the reservation's
		 *ending time has arrived and reservation is being deleted
		 */
		svr_mailownerResv(presv, MAIL_END, MAIL_NORMAL, "");

		tickle_for_reply();
	}
}


/**
 * @brief
 * 		resvFinishReply - function gets executed to dispatch the reply
 *		to an internally generated request to delete a reservation
 *		whose time has passed (it's FINISHED).
 *		Here, we just delete the batch_request structure.  If the
 *		request bombs for some internal reason, mail should go back
 *		to those on the reservation's mail list as well as an error
 *		being entered into the server's logging files.
 * @param[in,out]	ptask	-	wt_param1 holds the address of the batch_request structure,
 *								which needs to be freed
 *
 * @return	none
 */
static void
resvFinishReply(struct work_task *ptask)
{
	if (ptask->wt_event == PBS_LOCAL_CONNECTION) {
		/*we passed the little sanity check so do the free*/
		free_br((struct batch_request *)ptask->wt_parm1);
	}
}


/**
 * @brief
 * 		eval_resvState - does an evaluation to determine
 * 		what should be set for state and substate values on
 * 		the reservation in question.
 * @par
 * 		Evaluation is based on current time, current state
 * 		and substate, pointer to some relevant function and
 * 		possibly it's success or failure return value
 *
 * @param[in]	presv	-	reservation in question.
 * @param[in]	s	-	identifies the caller
 * @param[in]	relVal	-	relVal can have following possible values 0,1,2.
 * @param[out]	pstate	-	internal copy of state
 * @param[out]	psub	-	substate of resv state
 */
void
eval_resvState(resc_resv *presv, enum resvState_discrim s, int relVal,
	int *pstate, int *psub)
{
	/*initialize new values to current settings*/

	*pstate = presv->ri_qs.ri_state;
	*psub = presv->ri_qs.ri_substate;

	if (s == RESVSTATE_gen_task_Time4resv) {
		if (relVal == 0) {
			if (*pstate == RESV_BEING_ALTERED) {
				/*
				 * Altering a reservation's start time after the current time
				 * moves the reservation into the confirmed state.
				 */
				if (presv->ri_qs.ri_stime > time_now) {

					*pstate = RESV_CONFIRMED;
					*psub = RESV_CONFIRMED;
				} else {
					/* Altering a reservation after its start time */
					*pstate = RESV_RUNNING;
					*psub = RESV_RUNNING;
				}
			} else if (presv->ri_qs.ri_etime > time_now) {
				*pstate = RESV_CONFIRMED;
				*psub = RESV_CONFIRMED;
			}
		}
	} else if (s == RESVSTATE_Time4resv) {
		if (relVal == 0) {
			if (presv->ri_qs.ri_stime <= time_now &&
				time_now <= presv->ri_qs.ri_etime) {
				if (*pstate == RESV_DEGRADED || *psub == RESV_DEGRADED)
					*psub = RESV_DEGRADED;
				else
					*psub = RESV_RUNNING;
				*pstate = RESV_RUNNING;
				if (presv->ri_qs.ri_tactive <
					presv->ri_wattr[RESV_ATR_start].at_val.at_long)
					/*Assigning time_now to indicate when reservation become active
 					 *to help in fend off accounting on server restart*/
					presv->ri_qs.ri_tactive = time_now;
			}
		}
	} else if (s == RESVSTATE_req_deleteReservation) {
		if (relVal == 0) {

			*pstate = RESV_BEING_DELETED;
			*psub = RESV_BEING_DELETED;
		} else if (relVal == 1) {

			*pstate = RESV_BEING_DELETED;
			*psub = RESV_DELETING_JOBS;
		} else if (relVal == 2) {

			*pstate = RESV_DELETED;
			*psub = RESV_DELETED;
		}
	} else if (s == RESVSTATE_add_resc_resv_to_job) {
		*pstate = RESV_UNCONFIRMED;
		*psub = RESV_UNCONFIRMED;
	} else if (s == RESVSTATE_is_resv_window_in_future) {
		if (presv->ri_qs.ri_etime < time_now) {
			*pstate = RESV_FINISHED;
			*psub = RESV_FINISHED;
		}
	} else if (s == RESVSTATE_req_resvSub) {
		*pstate = RESV_UNCONFIRMED;
		*psub = RESV_UNCONFIRMED;
	} else if (s == RESVSTATE_alter_failed)
		/* backup only the state, as substate was not modified. */
		*pstate = presv->ri_alter_state;
}



/**
 * @brief
 * 		resv_setResvState - function modifies the state, substate
 * 		and related fields of the resc_resv object and updates
 * 		the local backing store for the object as appropriate -
 * 		either a full save of the structure or a quick save of the
 * 		structure
 *
 * @param[out]	presv	-	resc_resv object
 * @param[in]	state	-	internal copy of state
 * @param[in]	sub	-	substate of resv state
 */
void
resv_setResvState(resc_resv *presv, int state, int sub)
{
	if ((presv->ri_qs.ri_state == state) &&
		(presv->ri_qs.ri_substate == sub))
		return;

	presv->ri_qs.ri_state = state;
	presv->ri_qs.ri_substate = sub;

	presv->ri_wattr[(int)RESV_ATR_state]
	.at_val.at_long = state;
	presv->ri_wattr[(int)RESV_ATR_state]
	.at_flags |= ATR_VFLAG_SET |
		ATR_VFLAG_MODIFY | ATR_VFLAG_MODCACHE;

	presv->ri_wattr[(int)RESV_ATR_substate]
	.at_val.at_long = sub;
	presv->ri_wattr[(int)RESV_ATR_substate]
	.at_flags |= ATR_VFLAG_SET |
		ATR_VFLAG_MODIFY | ATR_VFLAG_MODCACHE;
	presv->ri_modified = 1;
	return;
}

/**
 * @brief
 * 		Set a scheduler flag to initiate a scheduling cycle when a reservation is
 * 		in degraded mode and needs to have nodes replaced.
 *
 * @param[in]	ptask	-	work task structure which contains reservation.
 */
void
resv_retry_handler(struct work_task *ptask)
{
	resc_resv	   *presv = ptask->wt_parm1;

	if (!presv)
		return;

	/* If all nodes associated to this reservation are back to available (due to
	 * a change in the system setup or a recovery) then no action is required as
	 * the handler vnode_available takes care of updating the reservation state
	 */
	if (presv->ri_vnodes_down == 0)
		return;

	/* Notify scheduler that a reservation needs to be reconfirmed */
	set_scheduler_flag(SCH_SCHEDULE_RESV_RECONFIRM, dflt_scheduler);
}

/**
 * @brief
 * 		chk_resvReq_viable - checks if scheduler's request to reserve is viable
 *
 * @param[in]	presv	-	pointer to reservation.
 *
 * @return	int
 * @retval	0	: no problems occur
 * @retval	error code	: if problem detected
 */
int
chk_resvReq_viable(resc_resv *presv)
{
	attribute		*ap;
	int			rc;

	ap = &presv->ri_wattr[RESV_ATR_state];
	if (ap->at_val.at_long == RESV_NONE)
		return    PBSE_INTERNAL;

	rc = 0;		/*assume no problems occur*/

	if (ap->at_val.at_long == RESV_FINISHED ||
		ap->at_val.at_long == RESV_DELETED  ||
		ap->at_val.at_long == RESV_BEING_DELETED)
		rc = PBSE_INTERNAL;

	return  (rc);
}


/**
 * @brief
 * 		gen_task_Time4resv - creates a work_task structure and puts it onto
 * 		the "WORK_Timed" work_task list at the appropriate (time sequential)
 * 		location.
 * @par
 * 		The assumption here is that gen_task_Time4resv () won't be called
 * 		if the "rescreserve" is not viable - see chk_resvReq_viable ()
 *
 * @param[in]	presv	-	pointer to reservation.
 *
 * @return	int
 * @retval	0	: work_task created and put on timed task list
 * @retval	error code	: if problem was detected
 */
int
gen_task_Time4resv(resc_resv *presv)
{
	struct work_task	*ptask;
	attribute		*ap;
	int			rc;
	long			startTime;

	ap = &presv->ri_wattr[RESV_ATR_state];
	if (ap->at_val.at_long == RESV_NONE)
		return    PBSE_INTERNAL;

	if (presv->resv_start_task)
		delete_task(presv->resv_start_task);
	presv->resv_start_task = NULL;
	startTime = presv->ri_wattr[RESV_ATR_start].at_val.at_long;
	if ((ptask = set_task(WORK_Timed, startTime,
		Time4resv, presv)) != 0) {
		/* set things so that the reservation going away causes
		 * any "yet to be processed" work tasks to also go away
		 */

		append_link(&presv->ri_svrtask, &ptask->wt_linkobj, ptask);

		/* cause to have issued to the qmgr subsystem
		 * a request to enable the reservation's queue
		 */
		rc = change_enableORstart(presv, Q_CHNG_ENABLE, "True");
		presv->resv_start_task = ptask;

	} else
		rc = PBSE_SYSTEM;

	return  (rc);
}


/**
 * @brief
 * 		gen_task_EndResvWindow - creates a work_task for deleting a reservation
 * 		whose window has expired and puts it on the "WORK_Timed" work_task list
 * 		at the appropriate (time sequential) location.
 *
 * @param[in]	presv	-	pointer to reservation.
 *
 * @return	int
 * @retval	0	: task was created and put on timed task list
 * @retval	error code	: if a problem was detected
 */
int
gen_task_EndResvWindow(resc_resv *presv)
{
	int		 rc;
	long		 fromNow;

	if (presv == NULL)
		return (PBSE_INTERNAL);

	fromNow = presv->ri_qs.ri_etime - (long)time_now;
	if ((server.sv_attr[(int)SVR_ATR_resv_post_processing].at_flags &
		ATR_VFLAG_SET) != 0)
		fromNow -= server.sv_attr[(int)SVR_ATR_resv_post_processing].at_val.at_long;
	rc = gen_future_deleteResv(presv, fromNow);
	return  (rc);
}


/**
 * @brief
 * 		gen_deleteResv - creates a work_task for deleting a reservation
 * 		Argument "fromNow" needs to be a non-negative value.  It's the number of
 * 		seconds into the future (measured from from global variable "time_now")
 * 		that this task is to be activated.
 *
 * @param[in,out]	presv	-	pointer to reservation.
 * @param[in]	fromNow	-	It's the number of seconds into the future that this task is to be activated.
 *
 * @return	int
 * @retval	0	: task was created and put on timed task list
 * @retval	error code	: if a problem was detected
 */
int
gen_deleteResv(resc_resv *presv, long fromNow)
{
	struct work_task	*ptask;
	int			rc = 0;		/*assume success*/
	long			event = (long)time_now + fromNow;

	if ((ptask = set_task(WORK_Timed, event,
		Time4_term, presv)) != 0) {

		/* set things so that the reservation going away results in
		 * any "yet to be processed" work tasks also going away
		 * and set up to notify Scheduler of new reservation-job
		 */

		append_link(&presv->ri_svrtask, &ptask->wt_linkobj, ptask);
		presv->ri_futuredr = 1;
	} else
		rc = PBSE_SYSTEM;

	return  (rc);
}


/**
 * @brief
 * 		gen_negI_deleteResv - creates a work_task for deleting a reservation
 * 		if the reservation was submitted with a negative value for "I" attribute -
 * 		meaning: willing to wait "n" seconds, but after that forget it.
 * 		Argument "fromNow" needs to be a non-negative value.  It's the number of
 * 		seconds into the future (measured from from global variable "time_now")
 * 		that this task is to be activated.
 *
 * @param[in,out]	presv	-	pointer to reservation.
 * @param[in]	fromNow	-	It's the number of seconds into the future that this task is to be activated.
 *
 * @return	int
 * @retval	0	: task was created and put on timed task list
 * @retval	error code	: if a problem was detected
 */
int
gen_negI_deleteResv(resc_resv *presv, long fromNow)
{
	struct work_task	*ptask;
	int			rc = 0;		/*assume success*/
	long			event = (long)time_now + fromNow;

	if ((ptask = set_task(WORK_Timed, event,
		Time4_I_term, presv)) != 0) {

		/* set things so that the reservation going away results in
		 * any "yet to be processed" work tasks also going away
		 * and set up to notify Scheduler of new reservation-job
		 */

		append_link(&presv->ri_svrtask, &ptask->wt_linkobj, ptask);
		presv->ri_futuredr = 1;
	} else
		rc = PBSE_SYSTEM;

	return  (rc);
}


/**
 * @brief
 * 		gen_future_deleteResv - creates a work_task for deleting a reservation
 * 		in the future and puts it on the "WORK_Timed" work_task list at the
 * 		appropriate (time sequential) location.  Argument "fromNow" is to be a
 * 		non-negative value.  It's the number of seconds into the future
 * 		(measured from from global variable "time_now") that the task is to become
 * 		active.
 *
 * @param[in,out]	presv	-	pointer to reservation.
 * @param[in]	fromNow	-	It's the number of seconds into the future that this task is to be activated.
 *
 * @return	int
 * @retval	0	: task was created and put on timed task list
 * @retval	error code	: if a problem was detected
 */
int
gen_future_deleteResv(resc_resv *presv, long fromNow)
{
	struct work_task	*ptask = NULL;
	int			rc = 0;		/*assume success*/
	long			event = (long)time_now + fromNow;

	if (presv->resv_end_task)
		delete_task(presv->resv_end_task);
	presv->resv_end_task = NULL;
	if ((ptask = set_task(WORK_Timed, event,
		Time4resvFinish, presv)) != 0) {

		/* set things so that the reservation going away results in
		 * any "yet to be processed" work tasks also going away
		 * and set up to notify Scheduler of new reservation-job
		 */

		append_link(&presv->ri_svrtask, &ptask->wt_linkobj, ptask);
		presv->ri_futuredr = 1;
		presv->resv_end_task = ptask;
	} else
		rc = PBSE_SYSTEM;

	return  (rc);
}


/**
 * @brief
 * 		gen_future_reply - creates a work_task to reply in the future to a
 * 		reservation request submitted now. Place on the "WORK_Timed" work_task
 * 		list at the appropriate (time sequential) location.  Argument "fromNow"
 * 		is to be a non-negative value.  It's the number of seconds into the future
 * 		(measured from from global variable "time_now") that the task is to become
 * 		active.
 *
 * @param[in,out]	presv	-	pointer to reservation.
 * @param[in]	fromNow	-	It's the number of seconds into the future that this task is to be activated.
 *
 * @return	int
 * @retval	0	: task was created and put on timed task list
 * @retval	error code	: if a problem was detected
 */
int
gen_future_reply(resc_resv *presv, long fromNow)
{
	struct work_task	*ptask;
	int			rc = 0;		/*assume success*/
	long			event = (long)time_now + fromNow;

	if ((ptask = set_task(WORK_Timed, event,
		Time4reply, presv)) != 0) {

		/* set things so that the reservation going away results in
		 * any "yet to be processed" work tasks also going away
		 * and set up to notify Scheduler of new reservation-job
		 */

		append_link(&presv->ri_svrtask, &ptask->wt_linkobj, ptask);
		presv->ri_futuredr = 1;
	} else
		rc = PBSE_SYSTEM;

	return  (rc);
}


/**
 * @brief
 * 		change_enableORstart - call this function to build and issue an internally
 *		generated request to the qmgr subsystem to change the value of either
 *		attributes "start" or "enable" for the queue associated with a general
 *		resources reservation.
 *
 * @note
 *		Notes:  the "issue_Drequest" function called in the body of this
 *		code causes the request to be dispatched to qmgr immediately
 *		(since its local) and the reply from qmgr will get handled
 *		by the "reply handling function" passed to issue_Drequest.
 *		The reply handler gets triggered by invocation of "next_task()"
 *		in the server's main loop, since it's put into a work_task on the
 *		"immediate_tasks" work_list.
 *
 * @param[in]	presv	-	pointer to reservation structure
 * @param[in]	which	-	Q_CHNG_START, Q_CHNG_ENABLE
 * @param[in]	value	-	"True", "False"
 *
 * @return	int
 * @retval	0	: if build and issuance successful
 * @retval	!=0	: error code if function fails
 */
int
change_enableORstart(resc_resv *presv, int which, char *value)
{
	extern char  *msg_internalReqFail;
	struct batch_request	*newreq;
	pbs_list_head		*plhed;
	int			len;
	svrattrl		*psatl;
	struct work_task	*pwt;
	char			*at_name;
	int			index;

	/*General Remark: shouldn't do any queue "enable/start"
	 *changing for reservation jobs since they don't have
	 *a queue especially created for them
	 */
	if (presv->ri_qs.ri_type != RESC_RESV_OBJECT)
		return (0);

	newreq = alloc_br(PBS_BATCH_Manager);
	if (newreq == NULL) {
		(void)sprintf(log_buffer, "batch request allocation failed");
		log_event(PBSEVENT_SYSTEM, PBS_EVENTCLASS_RESV, LOG_NOTICE,
			presv->ri_qs.ri_resvID, log_buffer);
		return  (PBSE_SYSTEM);
	}

	newreq->rq_ind.rq_manager.rq_cmd = MGR_CMD_SET;
	newreq->rq_ind.rq_manager.rq_objtype = MGR_OBJ_QUEUE;
	newreq->rq_perm = ATR_DFLAG_MGWR | ATR_DFLAG_OPWR;
	(void)strcpy(newreq->rq_user, "pbs_server");
	(void)strcpy(newreq->rq_host, pbs_server_name);

	strcpy(newreq->rq_ind.rq_manager.rq_objname,
		presv->ri_wattr[RESV_ATR_queue].at_val.at_str);

	CLEAR_HEAD(newreq->rq_ind.rq_manager.rq_attr);
	plhed = &newreq->rq_ind.rq_manager.rq_attr;

	if (which == Q_CHNG_ENABLE) {
		index = QA_ATR_Enabled;
		at_name = que_attr_def[index].at_name;
	} else if (which == Q_CHNG_START) {
		index = QA_ATR_Started;
		at_name = que_attr_def[index].at_name;
	} else
		return  (PBSE_INTERNAL);

	len = strlen(value) + 1;
	if ((psatl = attrlist_create(at_name, NULL, len)) != NULL) {
		psatl->al_flags = que_attr_def[index].at_flags;
		strcpy(psatl->al_value, value);
		append_link(plhed, &psatl->al_link, psatl);
	} else {
		free_br(newreq);
		return  (PBSE_INTERNAL);
	}

	if (issue_Drequest(PBS_LOCAL_CONNECTION, newreq,
		handle_qmgr_reply_to_startORenable, &pwt, 0) == -1) {
		free_br(newreq);

		(void)sprintf(log_buffer, "%s", msg_internalReqFail);
		log_event(PBSEVENT_RESV, PBS_EVENTCLASS_RESV, LOG_NOTICE,
			presv->ri_qs.ri_resvID, log_buffer);

		return (PBSE_mgrBatchReq);
	}
	tickle_for_reply();
	if (pwt)
		pwt->wt_parm2 = presv;	/*needed to handle qmgr's response*/

	return (0);
}



/**
 * @brief
 * 		handle_qmgr_reply_to_startORenable - this is the function that's to be
 *		called to handle the qmgr's response to the request issued in
 *		"change_enableORstart()".  If not successful log a message.
 * @par
 *		This function should only be called through an INTERNALLY GENERATED
 *		request to another server (including ourself).
 *		It frees the request structure and closes the connection (handle).
 * @par
 *		In the work task entry, wt_event is the connection handle and
 *		wt_parm1 is a pointer to the request structure (that contains the reply).
 *		wt_parm2 should have the address of the reservation structure
 * @par
 *		THIS SHOULD NOT BE USED IF AN EXTERNAL (CLIENT) REQUEST IS "relayed",
 *		because the request/reply structure is still needed to reply back
 *		to the client.
 *
 * @param[in]	pwt	-	work task entry
 */
static void
handle_qmgr_reply_to_startORenable(struct work_task *pwt)
{
	extern char  *msg_qEnabStartFail;
	struct batch_request	*preq = pwt->wt_parm1;
	resc_resv		*presv = pwt->wt_parm2;

	if (preq->rq_reply.brp_code) {

		(void)sprintf(log_buffer, "%s", msg_qEnabStartFail);
		log_event(PBSEVENT_RESV, PBS_EVENTCLASS_RESV, LOG_NOTICE,
			presv->ri_qs.ri_resvID, log_buffer);
	}

	free_br((struct batch_request *)pwt->wt_parm1);
	if (pwt->wt_event != -1)
		svr_disconnect(pwt->wt_event);

	/*I don't know why, except for system error, that a
	 *the server couldn't set "start" or "enable" on one
	 *of its own queues.  However, if this happens it probably
	 *should result in the reservation being deleted by the
	 *server and a message sent back to the owner regarding the
	 *action.  We will pass on that for now.
	 */
}


/**
 * @brief
 * 		remove_deleted_resvs - Walk the server's "svr_allresvs"
 *		list and cause to be removed any reservation whose state
 *		is marked RESV_FINISHED.  Function used in "pbsd_init" code
 *
 *	@return	Nothing
 */
void
remove_deleted_resvs(void)
{
	resc_resv		*presv, *nxresv;
	struct work_task	*ptask;

	presv = (resc_resv *)GET_NEXT(svr_allresvs);
	while (presv) {
		nxresv = (resc_resv *)GET_NEXT(presv->ri_allresvs);

		if (presv->ri_qs.ri_state == RESV_FINISHED) {
			/*put a task on the server's "task_list_timed" that causes
			 *an internal BATCH_REQUEST_DeleteResv to be generated
			 *and issued against this reservation
			 */

			if ((ptask = set_task(WORK_Timed, time_now + 5,
				Time4resvFinish, presv)) != 0) {

				/* set things so that the reservation going away results in
				 * any "yet to be processed" work tasks also going away
				 * and set up to notify Scheduler of new reservation-job
				 */

				append_link(&presv->ri_svrtask, &ptask->wt_linkobj, ptask);
			}
		}
		presv = nxresv;
	}
}


/**
 * @brief
 * 		add_resv_beginEnd_tasks - for each reservation not in state
 *      RESV_FINISHED add to "task_list_timed" the "begin" and
 *      "end" reservation tasks as appropriate.  Function used
 *		in "pbsd_init" code
 *
 * @return	none
 */
void
add_resv_beginEnd_tasks(void)
{
	resc_resv		*presv;
	char			txt[PBS_MAXSVRRESVID + 100];
	int			rc;


	presv = (resc_resv *)GET_NEXT(svr_allresvs);
	while (presv) {

		rc = 0;
		if (presv->ri_qs.ri_state == RESV_CONFIRMED ||
			presv->ri_qs.ri_state == RESV_RUNNING) {

			/*add "begin" and "end" tasks onto "task_list_timed"*/

			if ((rc = gen_task_EndResvWindow(presv)) != 0) {
				sprintf(txt, "%s : EndResvWindow task creation failed",
					presv->ri_qs.ri_resvID);
				log_err(rc, "add_resv_beginEnd_tasks", txt);
			}
			if ((rc = gen_task_Time4resv(presv)) != 0) {
				sprintf(txt, "%s : Time4resv task creation failed",
					presv->ri_qs.ri_resvID);
				log_err(rc, "add_resv_beginEnd_tasks", txt);
			}
		} else if (presv->ri_qs.ri_state == RESV_UNCONFIRMED) {

			/*add "end" task onto "task_list_timed"*/

			if ((rc = gen_task_EndResvWindow(presv)) != 0) {
				sprintf(txt, "%s : EndResvWindow task creation failed",
					presv->ri_qs.ri_resvID);
				log_err(rc, "add_resv_beginEnd_tasks", txt);
			}
		}

		presv = (resc_resv *)GET_NEXT(presv->ri_allresvs);
	}
}



/**
 * @brief
 * 		uniq_nameANDfile - develop a unique name and file in the directory
 *		pointed to by "pdir".  The root name of the file is initially
 *		given by "pname".  The name pointed to by pname may be modified
 *		in place in the process of arriving at a unique filename for
 *		the file.  The file, if generated, will have zero length.
 *
 * @param[in]	pname	-	The root name of the file is initially given by "pname"
 * @param[in]	psuffix	-	suffix of the name
 * @param[in]	pdir	-	points to the directory in which file contains.
 *
 * @return	int
 * @return	0	: on success
 * @retval	PBSE_*	: code on failure
 */
int
uniq_nameANDfile(char *pname, char *psuffix, char *pdir)
{
	int	fds, L1, L2;
	int	rc = 0;
	char	*pc;
	char	namebuf[MAXPATHLEN + 1];


	if (!pname || !psuffix || !pdir ||
		!(L1 = strlen(pname)) ||
		!(L2 = strlen(pdir))  ||
		((L1 + L2 + strlen(psuffix)) >= MAXPATHLEN))
		return  (PBSE_INTERNAL);


	do {
		(void)strcpy(namebuf, pdir);
		(void)strcat(namebuf, pname);
		(void)strcat(namebuf, psuffix);
		fds = open(namebuf, O_CREAT | O_EXCL | O_WRONLY, 0600);
		if (fds < 0) {
			if (errno == EEXIST) {
				pc = pname + strlen(pname) - 1;
				while (! isprint((int)*pc)) {
					pc--;
					if (pc <= pname) {
						rc = PBSE_INTERNAL;
						break;
					}
				}
				(*pc)++;
			} else {
				rc = PBSE_SYSTEM;
				break;
			}
		}
	} while (fds < 0);

	if (fds)
		(void)close(fds);
	return (rc);
}

/**
 * @brief
 *		start_end_dur_wall - This function handles both "resc_resv"
 *		objects or "job" objects.   If it is passed a reservation
 *		of some type, it considers the information specified for
 *		start_time, end_time, duration and walltime.  Using what was
 *		specified, it computes those unspecified values that are
 *		possible to compute. If the initially supplied information
 *		is bogus, or not enough information is specified or results
 *		are inconsistent, the function returns "failure" otherwise,
 *		it returns "success".
 * @par
 * 		reservation attributes dealing with start, end, duration times
 *		can be modified by this function.  In addition, if this is
 *		happens to be a RESC_RESV_OBJECT  its "ri_qs.ri_stime",
 *		"ri_qs.ri_etime", and "ri_qs.ri_duration" fields are subject
 *		to modification.
 *
 * @param[in,out]	pobj	-	it can be "resc_resv" objects or "job" objects.
 * @param[in]	objtype	-	determines the type of object - job/resc_resv.
 *
 * @return	int
 * @retval	0	: Success
 * @retval	!=0	: don't have a complete or consistent set of
 * 					information, or possibly some other error
 * 					occurred - e.g. problem adding the "walltime"
 * 					resource entry if it doesn't exist
 */
int
start_end_dur_wall(void *pobj, int objtype)
{
	job		*pjob = NULL;
	resc_resv	*presv = NULL;
	resource_def	*rscdef = NULL;
	resource	*prsc = NULL;
	attribute	*pstime = NULL;
	attribute	*petime = NULL;
	attribute	*pduration = NULL;
	attribute	*pattr = NULL;
	attribute	atemp = {0};
	attribute_def	*pddef = NULL;
	int		pstate = 0;

	int	swcode = 0;	/*"switch code"*/
	int	rc = 0;		/*return code, assume success*/

	if (pobj == 0)
		return (-1);

	rscdef = find_resc_def(svr_resc_def, "walltime", svr_resc_size);

	if (objtype == JOB_OBJECT) {
		pjob = (job *)pobj;
		if ((pjob->ji_wattr[JOB_ATR_reserve_start]
			.at_flags & ATR_VFLAG_SET) == 0)
			return (0);
		else {
			pjob = (job *)pobj;
			pstime = &pjob->ji_wattr[JOB_ATR_reserve_start];

			petime = &pjob->ji_wattr[JOB_ATR_reserve_end];

			pddef = &job_attr_def[JOB_ATR_reserve_duration];
			pduration = &pjob->ji_wattr[JOB_ATR_reserve_duration];

			pattr = &pjob->ji_wattr[JOB_ATR_resource];
			prsc = find_resc_entry(&pjob->ji_wattr[JOB_ATR_resource],
				rscdef);
		}
	} else if (objtype == RESC_RESV_OBJECT) {
		presv = (resc_resv *)pobj;
		pstime = &presv->ri_wattr[RESV_ATR_start];
		pstate = presv->ri_wattr[RESV_ATR_state].at_val.at_long;

		petime = &presv->ri_wattr[RESV_ATR_end];

		pddef = &resv_attr_def[RESV_ATR_duration];
		pduration = &presv->ri_wattr[RESV_ATR_duration];

		pattr = &presv->ri_wattr[RESV_ATR_resource];
		prsc = find_resc_entry(&presv->ri_wattr[RESV_ATR_resource],
			rscdef);
	} else if (objtype == RESV_JOB_OBJECT) {
		pjob = (job *)pobj;
		presv = pjob->ji_resvp;
		pstime = &pjob->ji_wattr[JOB_ATR_reserve_start];

		petime = &pjob->ji_wattr[JOB_ATR_reserve_end];

		pddef = &job_attr_def[JOB_ATR_reserve_duration];
		pduration = &pjob->ji_wattr[JOB_ATR_reserve_duration];

		pattr = &pjob->ji_wattr[JOB_ATR_resource];
		prsc = find_resc_entry(&pjob->ji_wattr[JOB_ATR_resource],
			rscdef);
	} else
		return (-1);

	if (pstate != RESV_BEING_ALTERED) {
		if (pstime->at_flags & ATR_VFLAG_SET)
			swcode += 1;			/*have start*/
		if (petime->at_flags & ATR_VFLAG_SET)
			swcode += 2;			/*have end  */
		if (pduration->at_flags & ATR_VFLAG_SET)
			swcode += 4;			/*have duration*/
		if (prsc)
			swcode += 8;			/*have walltime*/
		else if (!(prsc = add_resource_entry(pattr, rscdef)))
			return (-1);
	}
	else {
		swcode = 3;
	}

	atemp.at_flags = ATR_VFLAG_SET;
	atemp.at_type = ATR_TYPE_LONG;
	switch (swcode) {
		case  3:	/*start, end*/
			if (((pstime->at_val.at_long < time_now) && (pstate != RESV_BEING_ALTERED)) ||
				(petime->at_val.at_long <= pstime->at_val.at_long))
				rc = -1;
			else {

				atemp.at_val.at_long = (petime->at_val.at_long -
					pstime->at_val.at_long);

				(void)pddef->at_set(pduration, &atemp, SET);
				(void)rscdef->rs_set(&prsc->rs_value, &atemp, SET);
			}
			break;

		case  5:	/*start, duration*/
			if ((pstime->at_val.at_long < time_now) ||
				(pduration->at_val.at_long <= 0))
				rc = -1;
			else {
				petime->at_flags |= ATR_VFLAG_SET |
					ATR_VFLAG_MODIFY | ATR_VFLAG_MODCACHE;
				petime->at_val.at_long = pstime->at_val.at_long +
					presv->ri_qs.ri_duration;
			}
			break;

		case  7:	/*start, end, duration*/
			if ((pstime->at_val.at_long < time_now) ||
				(petime->at_val.at_long < pstime->at_val.at_long) ||
				(pduration->at_val.at_long <= 0) ||
				((petime->at_val.at_long - pstime->at_val.at_long) !=
					pduration->at_val.at_long))
				rc = -1;
			break;

		case  8:	/* end, duration */
			if ((pduration->at_val.at_long <= 0) ||
				(petime->at_val.at_long - pduration->at_val.at_long <
					time_now)) {
				rc = -1;
			}
			else {
				pstime->at_flags |= ATR_VFLAG_SET |
					ATR_VFLAG_MODIFY | ATR_VFLAG_MODCACHE;
				pstime->at_val.at_long = petime->at_val.at_long -
					pduration->at_val.at_long;
			}
			break;

		case  9:	/*start, wall*/
			if ((pstime->at_val.at_long < time_now) ||
				(prsc->rs_value.at_val.at_long <= 0))
				rc = -1;
			else {
				petime->at_flags |= ATR_VFLAG_SET |
					ATR_VFLAG_MODIFY | ATR_VFLAG_MODCACHE;
				petime->at_val.at_long = pstime->at_val.at_long +
					prsc->rs_value.at_val.at_long;
				pduration->at_flags |= ATR_VFLAG_SET |
					ATR_VFLAG_MODIFY | ATR_VFLAG_MODCACHE;
				pduration->at_val.at_long = prsc->rs_value.at_val.at_long;
			}
			break;

		case 10:	/* end, wall */
			if ((prsc->rs_value.at_val.at_long <= 0) ||
				(petime->at_val.at_long - prsc->rs_value.at_val.at_long <
					time_now)) {
				rc = -1;
			}
			else {
				pstime->at_flags |= ATR_VFLAG_SET |
					ATR_VFLAG_MODIFY | ATR_VFLAG_MODCACHE;
				pstime->at_val.at_long = petime->at_val.at_long -
					prsc->rs_value.at_val.at_long;
				pduration->at_flags |= ATR_VFLAG_SET |
					ATR_VFLAG_MODIFY | ATR_VFLAG_MODCACHE;
				pduration->at_val.at_long = prsc->rs_value.at_val.at_long;
			}
			break;

		case 11:	/*start, end, wall*/
			if ((pstime->at_val.at_long < time_now) ||
				(prsc->rs_value.at_val.at_long <= 0) ||
				(petime->at_val.at_long - pstime->at_val.at_long !=
					prsc->rs_value.at_val.at_long))
				rc = -1;
			else {
				pduration->at_flags |= ATR_VFLAG_SET |
					ATR_VFLAG_MODIFY | ATR_VFLAG_MODCACHE;
				pduration->at_val.at_long = prsc->rs_value.at_val.at_long;
			}
			break;

		case 13:	/*start, duration & wall*/
			if ((pstime->at_val.at_long < time_now) ||
				(prsc->rs_value.at_val.at_long != pduration->at_val.at_long) ||
				(pduration->at_val.at_long <= 0))
				rc = -1;
			else {
				petime->at_flags |= ATR_VFLAG_SET |
					ATR_VFLAG_MODIFY | ATR_VFLAG_MODCACHE;
				petime->at_val.at_long = pstime->at_val.at_long +
					presv->ri_qs.ri_duration;
			}
			break;

		case 15:	/*start, end, duration & wall*/
			if ((pstime->at_val.at_long < time_now) ||
				(petime->at_val.at_long < pstime->at_val.at_long) ||
				(pduration->at_val.at_long <= 0) ||
				(prsc->rs_value.at_val.at_long != pduration->at_val.at_long) ||
				((petime->at_val.at_long - pstime->at_val.at_long) !=
					pduration->at_val.at_long))
				rc = -1;
			break;

		default:
			rc = -1;
	}

	if (server.sv_attr[(int)SVR_ATR_resv_post_processing].at_flags &
		ATR_VFLAG_SET) {
		pduration->at_val.at_long += server.sv_attr[(int)SVR_ATR_resv_post_processing].at_val.at_long;
		petime->at_val.at_long += server.sv_attr[(int)SVR_ATR_resv_post_processing].at_val.at_long;
	}

	if (!rc && (objtype == RESC_RESV_OBJECT ||
		objtype == RESV_JOB_OBJECT)) {
		presv->ri_qs.ri_stime = pstime->at_val.at_long;
		presv->ri_qs.ri_etime = petime->at_val.at_long;
		presv->ri_qs.ri_duration = pduration->at_val.at_long;
	}
	return (rc);
}


/**
 * @brief
 * 		is_resv_window_in_future - Updates the reservation's state
 *		to RESV_FINISHED if the current time is already beyond
 *		the start of the reservation's window otherwise, it
 *		just returns.  It's used in pbsd_init.c to decide if
 *		an existing reservation, that's read back into the system
 *		from the disk at sever restart, should continue to remain.
 *
 * @param[in,out]	presv	-	reservation structure
 *
 * @return	Nothing
 */
void
is_resv_window_in_future(resc_resv *presv)
{
	int	state, sub;

	eval_resvState(presv, RESVSTATE_is_resv_window_in_future, 0, &state,
		&sub);
	resv_setResvState(presv, state, sub);
}


/**
 * @brief
 * 		resv_mailAction - Based on what was requested on reservation submission
 * 		and on who is issuing the current *request*, generate (or not) a mail
 * 		message about some aspect of the reservation to the appropriate parties
 * @par
 * 		This function makes use of mail function svr_mailownerResv () but
 * 		it is not intended that this be the only way that svr_mailownerResv
 * 		should be called, for we may not be presented with any related
 * 		batch_request at the point where some message ought to be issued -
 * 		e.g. the case, "reservation start-time has finally arrived."
 *
 * @param[in]	presv	-	reservation structure
 * @param[in]	preq	-	batch_request structure
 */
void
resv_mailAction(resc_resv *presv, struct batch_request *preq)
{
	int	force;
	char	text[PBS_MAXUSER + PBS_MAXHOSTNAME + 64];

	if (preq->rq_type != PBS_BATCH_DeleteResv)
		return;

	snprintf(text, sizeof(text), "Requesting party: %s@%s",
		preq->rq_user, preq->rq_host);
#ifdef NAS /* localmod 028 */
	/*
	 * The extend attribute can contain additional explanation
	 */
	if (preq->rq_extend) {
		size_t len;
		len = strlen(text);
		snprintf(text+len, sizeof(text)-len,
			"\nReason: %s\n", preq->rq_extend);
	}
#endif /* localmod 028 */
	if (preq->rq_fromsvr != 0)
		force = MAIL_FORCE;
	else
		force = MAIL_NORMAL;
	svr_mailownerResv(presv, MAIL_ABORT, force, text);
}


/**
 * @brief
 * 		This function converts long to hh:mm:ss format
 *
 * @param[in]	l	-	time passed as a long number
 *
 * @return	pointer to the converted time string, allocated
 *         	by the function. Memory deallocation rests in
 *         	the hands of caller.
 * @retval	NULL	: failure
 */

char*
convert_long_to_time(long l)
{
	unsigned int h;
	unsigned int temp;
	int m;
	int s;
	int hr_len = 0;
	char *str;

	temp = h = l/3600;
	l = l%3600;
	m = l/60;
	l = l%60;
	s = l;
	while (temp > 0) {
		hr_len++;
		temp = temp/10;
	}
	/* Allocating memory for hours field and other 9 chars which can
	 * accommodate "hh:mm:ss\0"
	 */
	str = (char*)malloc(hr_len + 9);
	if (str == NULL)
		return NULL;

	sprintf(str, "%02u:%02d:%02d", h, m, s);
	return str;

}

/**
 * @brief
 * 		  determine_accruetype
 *        determine accrue_type for new job or after overlay upgrade
 *        or after recovery.
 *        If coming after an overlay upgrade or enabling accrual after a long time,
 *        jobs which entered system when accrual was false or those before the upgrade,
 *        accrual will begin from start time of the scheduling cycle.
 *        if, scheduler cannot determine accrual type, then server determines it and
 *        accrual begins from the time job was created in the server.
 *
 *        precedence of accrual :
 *	        1) run time, exit time 2) ineligible time 3) eligible time
 * @param[in]	pjob	-	Job whose accrue type needs to be determined
 * @return	long
 * @retval	JOB_ELIGIBLE	-	when job is eligible to accrue eligible_time
 * @retval	JOB_INELIGIBLE	-	when job is ineligible to accrue eligible_time
 * @retval	JOB_RUNNING	-	when job is running or provisioning
 * @retval	JOB_EXIT	-	when job is exiting
 * @retval	-1	-	when this function is not able to determine accruetype
 */
long
determine_accruetype(job* pjob)
{
	struct pbs_queue *pque;
	long	temphold;
	long newaccruetype = -1;


	/* have to determine accrue type */

	/* if job is truely running or provisioning */
	if (pjob->ji_qs.ji_state == JOB_STATE_RUNNING &&
		(pjob->ji_qs.ji_substate == JOB_SUBSTATE_RUNNING ||
		pjob->ji_qs.ji_substate == JOB_SUBSTATE_PROVISION)) {
		newaccruetype = (long)JOB_RUNNING;
		return newaccruetype;
	}

	/* if job exit */
	if (pjob->ji_qs.ji_state == JOB_STATE_EXITING) {
		newaccruetype = (long)JOB_EXIT;
		return newaccruetype;
	}

	/* handling qsub -a, waiting with substate 30 ; accrue ineligible time */
	if (pjob->ji_wattr[(int)JOB_ATR_exectime].at_val.at_long) {
		newaccruetype = (long)JOB_INELIGIBLE;
		return newaccruetype;
	}

	/* 'user' hold applied ; accrue ineligible time */
	if (pjob->ji_wattr[(int)JOB_ATR_hold].at_val.at_long & HOLD_u) {
		newaccruetype = (long)JOB_INELIGIBLE;
		return newaccruetype;
	}

	/* other than 'user' hold applied */
	/* accrue type is set to JOB_INELIGIBLE incase a job has dependency */
	/* on another job and hold type is set to system hold. */
	/* For all other cases accrue type is set to JOB_ELIGIBLE. */
	temphold = pjob->ji_wattr[(int)JOB_ATR_hold].at_val.at_long;
	if (temphold & HOLD_o || temphold & HOLD_bad_password || temphold & HOLD_s) {
		if ((pjob->ji_qs.ji_substate == JOB_SUBSTATE_DEPNHOLD)
			&& (temphold & HOLD_s)) {
			newaccruetype = (long)JOB_INELIGIBLE;
		} else {
			newaccruetype = (long)JOB_ELIGIBLE;
		}
		return newaccruetype;
	}

	/* scheduler suspend job ; accrue eligible time */
	if (pjob->ji_qs.ji_substate == JOB_SUBSTATE_SCHSUSP) {
		newaccruetype = (long)JOB_ELIGIBLE;
		return newaccruetype;
	}

	/* qsig suspended job ; accrue eligible time */
	if (pjob->ji_qs.ji_substate == JOB_SUBSTATE_SUSPEND) {
		newaccruetype = (long)JOB_ELIGIBLE;
		return newaccruetype;
	}

	/* check for stopped queue: routing and execute ; accrue eligible time */
	pque = find_queuebyname(pjob->ji_qs.ji_queue);
	if (pque != NULL)
		if (pque->qu_attr[(int)QA_ATR_Started].at_val.at_long == 0) {
			newaccruetype = (long)JOB_ELIGIBLE;
			return newaccruetype;
		}

	/* handling qsub -Wdepend, state H with substate 22 ; accrue eligible_time */
	if (pjob->ji_wattr[(int)JOB_ATR_depend].at_flags & ATR_VFLAG_SET) {
		newaccruetype = (long)JOB_ELIGIBLE;
		return newaccruetype;
	}

	/* return */
	return newaccruetype;
}

/**
 * @brief
 * 		update_eligible_time - this function is responsible for calculating eligible time accrued
 *			  for a job. it also updates the accrue type and sample start time
 *
 * @param[in]	newaccruetype	-	new accrue type to be set
 * @param[in,out]	pjob	-	pointer to job
 *
 * @return	int
 * @retval	0	: success
 * @retval	1	: if updating same accrue type or do nothing
 *
 * @par MT-Safe: No
 */

int
update_eligible_time(long newaccruetype, job *pjob)
{
	static char *msg[] = { "initial_time", "ineligible_time", "eligible_time", "run_time", "exiting" };
	char *strtime;
	static char errtime[] = "00:00:00";
	char str[256];
	long accrued_time;			/* accrued time */
	long oldaccruetype = pjob->ji_wattr[(int)JOB_ATR_accrue_type].at_val.at_long;
	long timestamp = (long)time_now; 	/* time since accrual begins */

	/* check if updating same accrue type or do nothing */
	if (newaccruetype == oldaccruetype || newaccruetype == -1)
		return 1;

	/* time since accrue type last changed  */
	accrued_time = timestamp - pjob->ji_wattr[(int) JOB_ATR_sample_starttime].at_val.at_long;

	/* time since accrue type last changed is accrued */
	/* change type to new accrue type,  update start time to mark */
	/* change of accrue type */
	switch ((int)oldaccruetype) {
		case JOB_ELIGIBLE:
			pjob->ji_wattr[(int)JOB_ATR_eligible_time].at_val.at_long += accrued_time;
			pjob->ji_wattr[(int)JOB_ATR_accrue_type].at_val.at_long = newaccruetype;
			pjob->ji_wattr[(int)JOB_ATR_sample_starttime].at_val.at_long = timestamp;

			pjob->ji_wattr[(int)JOB_ATR_sample_starttime].at_flags |=
				(ATR_VFLAG_MODIFY | ATR_VFLAG_MODCACHE);
			pjob->ji_wattr[(int)JOB_ATR_accrue_type].at_flags |=
				(ATR_VFLAG_MODIFY | ATR_VFLAG_MODCACHE);
			pjob->ji_wattr[(int)JOB_ATR_eligible_time].at_flags |=
				(ATR_VFLAG_MODIFY | ATR_VFLAG_MODCACHE);
			break;
		case JOB_INELIGIBLE:
		case JOB_RUNNING:
		case JOB_INITIAL:
		case JOB_EXIT:
			pjob->ji_wattr[(int)JOB_ATR_accrue_type].at_val.at_long = newaccruetype;
			pjob->ji_wattr[(int)JOB_ATR_sample_starttime].at_val.at_long = timestamp;

			pjob->ji_wattr[(int)JOB_ATR_sample_starttime].at_flags |=
				(ATR_VFLAG_MODIFY | ATR_VFLAG_MODCACHE);
			pjob->ji_wattr[(int)JOB_ATR_accrue_type].at_flags |=
				(ATR_VFLAG_MODIFY | ATR_VFLAG_MODCACHE);
			pjob->ji_wattr[(int)JOB_ATR_eligible_time].at_flags |=
				(ATR_VFLAG_MODIFY | ATR_VFLAG_MODCACHE);
			break;

		default:
			break;

	}  /*switch end*/

	/* Prepare and print log message */
	strtime = convert_long_to_time(pjob->ji_wattr[(int)JOB_ATR_eligible_time].at_val.at_long);
	if (strtime == NULL)
		strtime = errtime;

	sprintf(str, "Accrue type has changed to %s, previous accrue type was %s for %ld secs, total eligible_time=%s",
		msg[newaccruetype], msg[oldaccruetype], accrued_time, strtime);
	log_event(PBSEVENT_DEBUG3, PBS_EVENTCLASS_JOB, LOG_DEBUG, pjob->ji_qs.ji_jobid, str);

	if (strtime != NULL && strtime != errtime)
		free(strtime);

	return 0;
}

/**
 * @brief
 * 		alter_eligibletime 	this is action function for eligible_time.
 *			qalter will alter the value of eligible_time,
 *			hence need to set sample_starttime to now so that
 *			accrual begins with this change. log message is
 *			printed to mark the change. the time accrued while
 *			in present accruetype from last change is printed.
 *			Accrual continues from now.
 *
 * @param[in]	pattr	-	attribute structure
 * @param[in,out]	pobject	-	object which will be later typecasted into job type.
 * @param[in]	actmode	-	action mode
 *
 * @par MT-Safe: No
 */
int
alter_eligibletime(attribute *pattr, void *pobject, int actmode)
{
	static char errtime[] = "00:00:00";
	long timestamp = (long)time_now; /* accrual begins from here */
	job * pjob = (job*)pobject;
	long oldaccruetype = pjob->ji_wattr[(int)JOB_ATR_accrue_type].at_val.at_long;
	long newaccruetype = oldaccruetype; /* We are not changing accrue type */

	/* distinguish between genuine qalter and call by action */
	if (actmode == ATR_ACTION_ALTER) {

		/* eligible_time_enable is OFF, then error */
		if (!server.sv_attr[SRV_ATR_EligibleTimeEnable].at_val.at_long) {
			return PBSE_ETEERROR;
		} else {
			long accrued_time;
			char *strtime;
			char logstr[256];
			static char *msg[] = {
				"initial_time",
				"ineligible_time",
				"eligible_time",
			 	"run_time",
				"exiting"
			};

			accrued_time = (long)time_now -
				pjob->ji_wattr[(int)JOB_ATR_sample_starttime].at_val.at_long;

			/* Sample time accrual continues with this time .... */
			pjob->ji_wattr[(int)JOB_ATR_sample_starttime].at_val.at_long = timestamp;

			/* eligible_time is set to new value again in modify_job_attr.
			 * this is for log message, we have the new value anyways.
			 */
			strtime = convert_long_to_time(pattr->at_val.at_long);
			if (strtime == NULL)
				strtime = errtime;

			sprintf(logstr, "Accrue type is %s, previous accrue type was %s for %ld secs, due to qalter total eligible_time=%s",
				msg[newaccruetype], msg[oldaccruetype], accrued_time, strtime);
			log_event(PBSEVENT_DEBUG3, PBS_EVENTCLASS_JOB, LOG_DEBUG,
				pjob->ji_qs.ji_jobid, logstr);

			return PBSE_NONE;
		}
	}
	return PBSE_NONE;
}
/**
 * @brief
 *		Check if the history of the  finished job needs to be saved or purged .
 *		If it needs to be saved and history management is ON then call svr_setjob_histinfo() to
 *		store the data in the server's history . Else if the history management is OFF or the
 *		request is to not store the jobs history then call job_purge()
 *
 * @param[in]	pjob	-	Pointer to the job structure
 *
 *
 */
void
svr_saveorpurge_finjobhist(job *pjob)
{
	int flag = 0;

	flag = svr_chk_history_conf();
	if (flag && !pjob->ji_deletehistory) {
		svr_setjob_histinfo(pjob, T_FIN_JOB);
		if (pjob->ji_ajtrk)
			pjob->ji_ajtrk->tkm_flags &= ~TKMFLG_CHK_ARRAY;
	} else {
		if (pjob->ji_deletehistory && flag) {
			log_event(PBSEVENT_DEBUG, PBS_EVENTCLASS_JOB,
				LOG_INFO, pjob->ji_qs.ji_jobid,
				msg_also_deleted_job_history);
		}
		/* For an array subjob if exit status is non-zero mark sub state
		 * as JOB_SUBSTATE_FAILED. Otherwise set to JOB_SUBSTATE_FINISHED
		 * when current sub state is JOB_SUBSTATE_EXITED.
		 */
		if (pjob->ji_qs.ji_svrflags & JOB_SVFLG_SubJob) {
			if (pjob->ji_terminated)
				pjob->ji_qs.ji_substate = JOB_SUBSTATE_TERMINATED;
			else if ((pjob->ji_wattr[(int)JOB_ATR_exit_status].at_flags) &
				ATR_VFLAG_SET) {
				if (pjob->ji_wattr[(int)JOB_ATR_exit_status].at_val.at_long)
					pjob->ji_qs.ji_substate = JOB_SUBSTATE_FAILED;
				else if (pjob->ji_qs.ji_substate == JOB_SUBSTATE_EXITED)
					pjob->ji_qs.ji_substate = JOB_SUBSTATE_FINISHED;
			}
		}
		job_purge(pjob);
	}
}
/**
 * @brief
 *		Function name: svr_clean_job_history
 * @par Purpose: Periodically checks for the history jobs in the server and
 *		 purge the history jobs whose history duration exceeds the
 *		 configured job_history_duration server attribute.
 * @par Functionality: It is a work_task and reschedule itself after 2 mins if
 *		 and only if job_history_enable is set.
 *		Output: None
 *
 * @param[in]	pwt	-	work_task structure
 */
void
svr_clean_job_history(struct work_task *pwt)
{
	job 	*pjob;
	job 	*nxpjob = NULL;
	int 	walltime_used = 0;

	/*
	 * Keep track of time spent purging jobs, interrupts purge if necessary.
	 * Timed task in nearby future set if purge took too long.
	 * Timed task in far future only set if this task completes.
	 * Autotunes time between job history purges:
	 * - raised if last purge was short
	 * - lowered if this purge needs to be interrupted
	 */

	time_t	begin_time;
	time_t	end_time;
	static time_t time_between_tasks = SVR_CLEAN_JOBHIST_TM;

	begin_time = time(NULL);
	/* Initialize end_time, in case we do not get into the while loop */
	end_time = begin_time;

	/*
	 * Traverse through the SERVER job list and find the history
	 * jobs (job with state JOB_STATE_MOVED and JOB_STATE_FINISHED)
	 * which exceed the configured job_history_duration value and
	 * purge them immediately.
	 */
	pjob = (job *)GET_NEXT(svr_alljobs);

	while (pjob != NULL) {
		/* save the next job */
		nxpjob = (job *)GET_NEXT(pjob->ji_alljobs);

		if ((pjob->ji_qs.ji_state == JOB_STATE_MOVED && pjob->ji_qs.ji_substate == JOB_SUBSTATE_FINISHED) ||
			(pjob->ji_qs.ji_state == JOB_STATE_FINISHED) ||
			(pjob->ji_qs.ji_state == JOB_STATE_EXPIRED)) {

			if (!(pjob->ji_wattr[(int) JOB_ATR_history_timestamp].at_flags & ATR_VFLAG_SET)) {
				if (pjob->ji_qs.ji_state == JOB_STATE_MOVED)
					pjob->ji_wattr[(int) JOB_ATR_history_timestamp].at_val.at_long = time_now;
				else {
					if (((walltime_used = get_used_wall(pjob)) == -1) ||
						!(pjob->ji_wattr[(int) JOB_ATR_stime].at_flags & ATR_VFLAG_SET)) {
						log_err(-1, "svr_clean_job_history",
							"Finished job missing start-time/walltime used, cannot clean history");
						pjob = nxpjob;
						continue;
					}
					pjob->ji_wattr[(int) JOB_ATR_history_timestamp].at_val.at_long =
						pjob->ji_wattr[(int) JOB_ATR_stime].at_val.at_long + walltime_used;
				}
				pjob->ji_wattr[(int) JOB_ATR_history_timestamp].at_flags |= ATR_VFLAG_SET | ATR_VFLAG_MODCACHE;
				pjob->ji_modified = 1;
				/* save the full job */
				(void)job_save(pjob, SAVEJOB_FULL);
			}

			if (time_now >= (pjob->ji_wattr[(int) JOB_ATR_history_timestamp].at_val.at_long
				+ svr_history_duration)) {
				job_purge(pjob);
				pjob = NULL;
			}
		}
		/* restore the saved next in pjob */
		pjob = nxpjob;

		/* check if we spent too long hogging the pbs_server process here */
		end_time = time(NULL);
		if ((end_time - begin_time) > SVR_CLEAN_JOBHIST_SECS) {
			/* Apparently the interval between history purges is too long.
			 * reduce it using factor 0.7
			 */
			time_between_tasks = (floor((double)time_between_tasks * 0.7));

			/* no use reducing to less than 4 * SVR_CLEAN_JOBHIST_SECS
			 * since we'll already schedule a continuation task here
			 */
			if (time_between_tasks < (4 * SVR_CLEAN_JOBHIST_SECS))
				time_between_tasks = 4 * SVR_CLEAN_JOBHIST_SECS;

			/* set up another work task in near future,
			 * but leave as much time as we spent in this routine for other work first
			 */
			if (!set_task(WORK_Timed,
				(end_time + SVR_CLEAN_JOBHIST_SECS),
				svr_clean_job_history, NULL)) {
				log_err(errno,
					"svr_clean_job_history",
					"Unable to set task for clean job history");
					/* on error to set task
					 * just continue purging the history
					 */
			} else
				/* but if we managed to set a task in near future, return;
				 * that task will continue where we left off
				 */
				return;
		}
	} /* end of while loop through jobs */

	/* We purged everything necessary in this task if we get here.
	 * set up another work task for next time period.
	 */
	if (pwt && svr_history_enable) {
		if (!set_task(WORK_Timed,
			(time_now + time_between_tasks),
			svr_clean_job_history, NULL)) {
			log_err(errno,
				"svr_clean_job_history",
				"Unable to set task for clean job history");
		}
	}

	/* try to move the time between tasks up again
	 * but only if we spent less than 2/3rds of what we were allowed to spend
	 * Note the last purge of a chain of purges will often tend to undo part of the lowering
	 * in the earlier incomplete purges -- that's OK: 0.7*1.1 is still smaller than 1
	 */

	if ((time_between_tasks < SVR_CLEAN_JOBHIST_TM) &&
	    ((end_time - begin_time) < floor((double)SVR_CLEAN_JOBHIST_SECS * 2/3))) {
		time_between_tasks = ceil((double) time_between_tasks * 1.1);
		if (time_between_tasks > SVR_CLEAN_JOBHIST_TM)
			time_between_tasks = SVR_CLEAN_JOBHIST_TM;
	}
}

/**
 * @brief
 * 		Function name: svr_histjob_update()
 * 		Description: Update the state/substate of the history job and save
 *		the job structure to the disk.
 * 		Input: 1) pjob 2) newstate 3) newsubstate
 *
 * @param[in,out]	pjob	-	job which needs to be updated.
 * @param[in]	newstate	-	internal copy of state
 * @param[in]	newsubstate	-	job sub-state
 *
 * @return	Nothing
 */
void
svr_histjob_update(job * pjob, int newstate, int newsubstate)
{
	int oldstate = pjob->ji_qs.ji_state;
	pbs_queue *pque = pjob->ji_qhdr;

	/* update the state count in queue and server */
	if (oldstate != newstate) {
		server.sv_jobstates[oldstate]--;
		server.sv_jobstates[newstate]++;
		if (pque != NULL) {
			pque->qu_njstate[oldstate]--;
			pque->qu_njstate[newstate]++;
		}
	}
	/* set the job state and state char */
	pjob->ji_qs.ji_state = newstate;
	pjob->ji_qs.ji_substate = newsubstate;
	set_statechar(pjob);

	/* For subjob update the state */
	if (pjob->ji_qs.ji_svrflags & JOB_SVFLG_SubJob) {
		update_subjob_state(pjob, newstate);
	}

	/* set the status of each subjob if it is an array job */
	if (pjob->ji_qs.ji_svrflags & JOB_SVFLG_ArrayJob) {
		int indx;
		struct ajtrkhd *ptbl = pjob->ji_ajtrk;
		if (ptbl) {
			/* update the subjob state table */
			for (indx = 0; indx < ptbl->tkm_ct; ++indx) {
				job *psubj = ptbl->tkm_tbl[indx].trk_psubjob;
				if (psubj)
					svr_histjob_update(psubj, newstate, newsubstate);
				else
					set_subjob_tblstate(pjob, indx, newstate);
			}
		}
	}
	/* set the substate attr and cache it */
	pjob->ji_wattr[(int)JOB_ATR_substate].at_val.at_long = newsubstate;
	pjob->ji_wattr[(int)JOB_ATR_substate].at_flags |= ATR_VFLAG_MODCACHE;

	/* save the full job */
	(void)job_save(pjob, SAVEJOB_FULL);
}

/**
 * @brief
 * 		 svr_chk_history_conf - Check if server is configured to keep job history info.
 *
 * @return	Boolen value
 * @retval	1	: if the server is configured for job history info
 * @retval	0	: otherwise. i.e. onei/both of svr_history_enable and
 * 					 svr_history_duration is/are zero.
 */
int
svr_chk_history_conf()
{
	return (svr_history_enable && svr_history_duration);
}

/**
 * @brief:
 *        update_job_finish_comment	- Append job comment on exit (finished/ terminated/ failed) of job.
 *
 * @param[in]  *pjob       -	job structure
 * @param[in]  newsubstate -	new substate of the job
 * @param[in]  user        -	username who invoked job termination
 *
 * @return void
 *
 */
void
update_job_finish_comment(job *pjob, int newsubstate, char *user)
{
	char buffer[LOG_BUF_SIZE + 1] = {'\0'};
	if ((pjob->ji_wattr[(int)JOB_ATR_Comment].at_flags & ATR_VFLAG_SET) == 0) {
		return;
	}

	if (newsubstate == JOB_SUBSTATE_FINISHED) {
		snprintf(buffer, LOG_BUF_SIZE, "%s and finished",
			pjob->ji_wattr[(int)JOB_ATR_Comment].at_val.at_str);
	} else if (newsubstate == JOB_SUBSTATE_FAILED) {
		snprintf(buffer, LOG_BUF_SIZE, "%s and failed",
			pjob->ji_wattr[(int)JOB_ATR_Comment].at_val.at_str);
	} else if (newsubstate == JOB_SUBSTATE_TERMINATED) {
		/* Don't overwrite the comment; if already set by req_deletejob2 */
		if (strstr(pjob->ji_wattr[(int)JOB_ATR_Comment].at_val.at_str, "terminated") == NULL) {
			if (user != NULL) {
				snprintf(buffer, LOG_BUF_SIZE, "%s and terminated by %s",
					pjob->ji_wattr[(int)JOB_ATR_Comment].at_val.at_str,
					user);
			} else {
				snprintf(buffer, LOG_BUF_SIZE, "%s and terminated",
					pjob->ji_wattr[(int)JOB_ATR_Comment].at_val.at_str);
			}
		}
	}
	if (buffer[0] != '\0') {
		(void)job_attr_def[(int)JOB_ATR_Comment].at_decode(
			&pjob->ji_wattr[(int)JOB_ATR_Comment],
			NULL,
			NULL,
			buffer);
	}
}

/**
 * @brief
 *		Set the history info for the job and keep until cleaned up by the
 *		server after the svr_history_duration period.
 *		Called on the execution completion of jobs i.e. normal job (non-array)
 *		or sub jobs; and when an array job is done (all subjobs complete).
 *		Also called when locally created normal jobs and array jobs are
 *		moved/routed to a new server.
 *
 * @param[in]	*pjob	-	job structure
 * @param[in]	type	-	type of history
 *	  						type = T_FIN_JOB for FINISHED jobs
 *	  						type = T_MOV_JOB for MOVED jobs
 *	  						type = T_MOM_DOWN for non-rerunnable jobs FAILED
 *	  						because MOM went down.
 *
 * @return	void
 */

void
svr_setjob_histinfo(job *pjob, histjob_type type)
{
	int newstate = 0;
	int newsubstate = 0;
	struct ajtrkhd *ptbl = NULL;
	struct work_task *pwt = NULL;

	if (type == T_MOV_JOB) { /* MOVED job */
		char *destination = pjob->ji_qs.ji_destin;
		char *tmpstr = NULL;
		char qname[PBS_MAXROUTEDEST + 1];

		if (destination == NULL || *destination == '\0') {
			return;
		}

		/*
		 * If the move_job request comes from the scheduler because of
		 * peer-2-peer scheduling, then destin will have port number
		 * (format: "[<queue>]<server>:<portno>")which is not required,
		 * so strip the string after ':'.
		 */
		tmpstr = strchr(destination, ':');
		if (tmpstr != NULL) {
			*tmpstr = '\0';
		}

		sprintf(log_buffer,
			"Job Moved to destination: \"%s\"", destination);
		log_event(PBSEVENT_JOB, PBS_EVENTCLASS_JOB, LOG_INFO,
			pjob->ji_qs.ji_jobid, log_buffer);

		/* put the accounting log for MOVED job */
		sprintf(log_buffer, "destination=%s", destination);
		account_record(PBS_ACCT_MOVED, pjob, log_buffer);

		/*
		 * parse the queue name from the destination
		 * set the queue attribute to the destination
		 * set the server's ji_queue info to just the queue name
		 */
		snprintf(qname, sizeof(qname), "%s", destination);

		/* strip off the portion that isn't the queue name */
		tmpstr = strchr(qname,'@');
		if (tmpstr != NULL) {
			*tmpstr = '\0';
		}
		snprintf(pjob->ji_qs.ji_queue, sizeof(pjob->ji_qs.ji_queue),
			"%.*s", PBS_MAXQUEUENAME, qname);

		/* Set the queue attribute to destination */
		(void)job_attr_def[(int)JOB_ATR_in_queue].at_decode(
			&pjob->ji_wattr[(int)JOB_ATR_in_queue],
			NULL,
			NULL,
			destination);

		/* set the job comment attr with destination */
		sprintf(log_buffer, "Job has been moved to \"%s\"",
			destination);
		(void)job_attr_def[(int)JOB_ATR_Comment].at_decode(
			&pjob->ji_wattr[(int)JOB_ATR_Comment],
			NULL,
			NULL,
			log_buffer);
		/*
		 * SET the NEW STATE/SUB-STATE for the job (which is moved).
		 * New STATE for the job will be JOB_STATE_MOVED and new
		 * SUBSTATE will be JOB_SUBSTATE_MOVED.
		 */
		newstate = JOB_STATE_MOVED;
		newsubstate = JOB_SUBSTATE_MOVED;

	} else if (type == T_FIN_JOB) {
		/*
		 * FINISHED job:
		 * set the OGSA-BES compliant substate for the job.
		 * If it is terminated by deljob batch request, then
		 * set it as TERMINATED. Else, if the job has run and
		 * exited with non-zero exit status, then it is FAILED,
		 * otherwise FINISHED.
		 */
		newstate = (pjob->ji_qs.ji_svrflags & JOB_SVFLG_SubJob) ?
				JOB_STATE_EXPIRED : JOB_STATE_FINISHED; /* default X for subjob, F for other jobs */
		newsubstate = JOB_SUBSTATE_FINISHED; /* default */

		/* If Array job, handle here */
		if ((pjob->ji_qs.ji_svrflags & JOB_SVFLG_ArrayJob) &&
			(ptbl = pjob->ji_ajtrk)) {
			int i = 0;
			int stgout_status = -1;

			for (i=0; i<ptbl->tkm_ct; i++) {
				if (ptbl->tkm_tbl[i].trk_stgout >= 0) {
					stgout_status = ptbl->tkm_tbl[i].trk_stgout;
					if (stgout_status == 0)
						break;
				}
			}
			if (stgout_status != -1) {
				pjob->ji_wattr[(int)JOB_ATR_stageout_status].at_val.at_long =
					stgout_status;
				pjob->ji_wattr[(int)JOB_ATR_stageout_status].at_flags =
					ATR_VFLAG_SET | ATR_VFLAG_MODCACHE;
			}
			for (i=0; i<ptbl->tkm_ct; i++) {
				if (ptbl->tkm_tbl[i].trk_exitstat) {
					pjob->ji_wattr[(int)JOB_ATR_exit_status].at_val.at_long =
						pjob->ji_qs.ji_un.ji_exect.ji_exitstat;
					pjob->ji_wattr[(int)JOB_ATR_exit_status].at_flags =
						ATR_VFLAG_SET | ATR_VFLAG_MODCACHE;
					break;
				}
			}
			if (pjob->ji_terminated)
				newsubstate = JOB_SUBSTATE_TERMINATED;
			else {
				for (i=0; i<ptbl->tkm_ct; i++) {
					if (ptbl->tkm_tbl[i].trk_substate != JOB_SUBSTATE_FINISHED) {
						if ((ptbl->tkm_tbl[i].trk_substate == JOB_SUBSTATE_FAILED) ||
							(ptbl->tkm_tbl[i].trk_substate == JOB_SUBSTATE_TERMINATED)) {
							newsubstate = ptbl->tkm_tbl[i].trk_substate;
							break;
						}
					}
				}
			}
		} else { /* Non-Array job */
			if (pjob->ji_terminated) {
				newsubstate = JOB_SUBSTATE_TERMINATED;
			} else if ((pjob->ji_wattr[(int)JOB_ATR_exit_status].at_flags) &
				ATR_VFLAG_SET) {
				if (pjob->ji_wattr[(int)JOB_ATR_exit_status].at_val.at_long)
					newsubstate = JOB_SUBSTATE_FAILED;
			}
		}
		update_job_finish_comment(pjob, newsubstate, NULL);
	} else if (type == T_MOM_DOWN) {
		newstate = JOB_STATE_FINISHED;
		newsubstate = JOB_SUBSTATE_FAILED;
	}


	/* if the job is not already in MOVED or FINISHED state, then */
	/* decrement the entity job counts and entity resource sums   */

	if ((pjob->ji_qs.ji_state != JOB_STATE_MOVED) &&
		(pjob->ji_qs.ji_state != JOB_STATE_EXPIRED) &&
		(pjob->ji_qs.ji_state != JOB_STATE_FINISHED)) {
		account_entity_limit_usages(pjob, NULL, NULL, DECR,
				pjob->ji_etlimit_decr_queued ? ETLIM_ACC_ALL_MAX : ETLIM_ACC_ALL);
		account_entity_limit_usages(pjob, pjob->ji_qhdr, NULL, DECR,
				pjob->ji_etlimit_decr_queued ? ETLIM_ACC_ALL_MAX : ETLIM_ACC_ALL);
	}

	/* set the history timestamp */
	pjob->ji_wattr[(int) JOB_ATR_history_timestamp].at_val.at_long = time_now;
	pjob->ji_wattr[(int) JOB_ATR_history_timestamp].at_flags |= ATR_VFLAG_SET | ATR_VFLAG_MODCACHE;
	pjob->ji_modified = 1;
	/* update the history job state and substate */
	svr_histjob_update(pjob, newstate, newsubstate);

	/*
	 * Work tasks on history jobs are not required and may change the
	 * history info which is dangerous, so better delete them. Walk
	 * through the work task list of the job and delete them using
	 * delete_task().
	 */
	while ((pwt = (struct work_task *)GET_NEXT(pjob->ji_svrtask)) != NULL)
		delete_task(pwt);

}

/**
 * @brief
 * 		svr_chk_histjob - check whether job is a history job: called from
 * 		       req_stat_job() if type = 1;
 *
 * @param[in]	pjob	-	job structure to be checked
 *
 * @return	PBSE_*
 * @retval	PBSE_NONE	: if it is not a history job or feature is not enabled.
 * @retval	PBSE_UNKJOBID	: if it is a moved job but active at remote server
 * @retval	PBSE_HISTJOBID	: if it is a Finished job or moved and finished
 *	    		    			at remote server.
 */

int
svr_chk_histjob(job *pjob)
{
	int rc = PBSE_NONE;

	/*
	 * If server is not configured for history job information,
	 * no need to check further, return PBSE_NONE.
	 */
	if (svr_history_enable == 0)
		return PBSE_NONE;

	/*
	 * If it is FINISHED or MOVED with substate FINISHED, then
	 * return PBSE_HISTJOBID otherwise PBSE_NONE.
	 */
	if (pjob) {
		switch (pjob->ji_qs.ji_state) {
			case JOB_STATE_FINISHED:
				rc = PBSE_HISTJOBID;
				break;
			case JOB_STATE_MOVED:
				if (pjob->ji_qs.ji_substate == JOB_SUBSTATE_FINISHED)
					rc = PBSE_HISTJOBID;
				else /* other than JOB_SUBSTATE_FINISHED */
					rc = PBSE_UNKJOBID;
				break;
		}
	}
	return rc;
}


/**
 * @brief
 *		Creates the avl key from jobid string.
 *
 * @param[in]	keystr	-	jobid string
 *
 * @see
 * 		svr_enquejob()
 *		svr_dequejob()
 *		find_job()
 *
 * @return	Pointer to AVL_IX_REC record for success.
 * @retval	NULL	: failure.
 *
 * @par	Reentrancy:
 *		MT-unsafe
 *
 */
AVL_IX_REC *
svr_avlkey_create(const char *keystr)
{
	size_t keylen;
	AVL_IX_REC *pkey;

	if (keystr == NULL)
		return NULL;

	keylen = sizeof(AVL_IX_REC) + strlen(keystr) + 1;
	pkey = malloc(keylen);
	if (pkey == NULL)
		return NULL;

	memset((void *)pkey, 0, keylen);
	(void)strcpy(pkey->key, keystr);
	return (pkey);

}

/**
 * @brief
 *		Add/Delete the job to/from the AVL tree for faster lookup based
 *		on the boolean value of "delkey" parameter.
 *
 * @par Functionality:
 *		Create the key for the avl record using svr_avlkey_create() and call
 *		avl_add_key()/avl_delete_key() based on the boolean value parameter
 *		i.e. "delkey" to add/delete the job to the AVL tree for faster lookup.
 *		If it fails in the AVL operation, then it destroys the AVL tree and
 *		turns off the global avl switch AVL_jctx, so that SERVER falls back
 *		to regular doubly linked list for lookup.
 *
 * @param[in]	pjob	-	job structure to be operated on.
 * @param[in]	delkey	-	0 to add the key.
 *							1 to delete the key.
 *
 * @par	Linkage scope:
 *		static (local)
 *
 * @see	svr_enquejob()
 *		svr_dequejob()
 *
 * @return	void
 *
 * @par	Reentrancy:
 *		MT-unsafe
 *
 */
static void
svr_avljob_oper(job *pjob, int delkey)
{
	int rc = AVL_IX_OK;
	AVL_IX_REC *pkey;

	if ((AVL_jctx == NULL) || (pjob == NULL))
		return;

	/** create the avl key using jobid */
	pkey = svr_avlkey_create(pjob->ji_qs.ji_jobid);
	if (pkey == NULL) { /** key creation failed */
		(void) sprintf(log_buffer, "AVL: failed to create job key.");
		log_event(PBSEVENT_DEBUG4, PBS_EVENTCLASS_JOB, LOG_DEBUG,
			pjob->ji_qs.ji_jobid, log_buffer);
		goto AVL_OP_FAIL;
	}

	/** call avl interface based on the delkey */
	if (delkey == 0) {
		pkey->recptr = pjob;
		rc = avl_add_key(pkey, AVL_jctx);
	} else {
		rc = avl_delete_key(pkey, AVL_jctx);
	}
	if (rc != AVL_IX_OK) /** avl operation failed */
		goto AVL_OP_FAIL;

	/** everything went fine, free() the pkey and return */
	free(pkey);
	return;

AVL_OP_FAIL:
	if (pkey) /** free the pkey if valid */
		free(pkey);
	/**
	 * AVL operation failed, free the AVL tree context which was created
	 * by avl_create_index(), and turn off the AVL context i.e. AVL_jctx
	 * [global switch] so that SERVER will fall back to use linked list
	 * for job lookup.
	 */
	if (AVL_jctx != NULL) {
		(void) sprintf(log_buffer,
			"AVL: %s failed, using LinkedList.",
			delkey ? "delete" : "insert");
		log_event(PBSEVENT_DEBUG4, PBS_EVENTCLASS_SERVER, LOG_DEBUG,
			msg_daemonname, log_buffer);
		avl_destroy_index(AVL_jctx);
		free(AVL_jctx);
		AVL_jctx = NULL;
	}
}

/**
 * @brief
 * 	Finish the request to mom to update a job's exec_* values.
 *	Both the mom request and the originating client request are acknowledged.
 *
 * @param[in,out]	pwt -	work_task structure, containing info
 *				about the mom request and the client request.
 * @return none
 */
static void
post_send_job_exec_update_req(struct work_task *pwt)
{
	struct batch_request *mom_preq = NULL;
	struct batch_request *cli_preq = NULL;
	int bcode = 0;

	if (pwt == NULL)
		return;

	if (pwt->wt_aux2 != 1) /* not rpp */
		svr_disconnect(pwt->wt_event);  /* close connection to MOM */
	mom_preq = pwt->wt_parm1;
	mom_preq->rq_conn = mom_preq->rq_orgconn;  /* restore socket to client */
	bcode = mom_preq->rq_reply.brp_code;

	cli_preq = pwt->wt_parm2;

	if (bcode) {
		char err_msg[LOG_BUF_SIZE];

		/* also take note of the reject msg if any */
		if (mom_preq->rq_reply.brp_choice == BATCH_REPLY_CHOICE_Text) {
			(void)snprintf(err_msg, sizeof(err_msg), "%s", mom_preq->rq_reply.brp_un.brp_txt.brp_str);
		} else {
			(void)snprintf(err_msg, sizeof(err_msg), msg_mombadmodify, bcode);
		}
		log_event(PBSEVENT_JOB, PBS_EVENTCLASS_JOB, LOG_INFO, mom_preq->rq_ind.rq_modify.rq_objname, err_msg);
		req_reject(bcode, 0, mom_preq);
		reply_text(cli_preq, bcode, err_msg);
	} else {
		reply_ack(mom_preq);
		if (cli_preq != NULL) {
			if (cli_preq->rq_extend == NULL) {
				reply_ack(cli_preq);
			} else {
				reply_text(cli_preq, PBSE_NONE, cli_preq->rq_extend);
			}
		}
	}
}

/**
 * @brief
 *
 * Communicate to the MS mom pjob's exec_vnode, exec_host,
 * exec_host2, and schedselect attributes.
 *
 * @param[in]	pjob - job structure
 * @param[out]  err_msg - a buffer of size 'err_msg_sz' supplied by the
 *       		  caller and upon a failure will contain an appropriate
 *       		  error message
 * @param[in]	err_msg_sz - size of 'err_msg' buf
 * @param[in]	reply_req - the batch request to reply to if any
 *
 * @return int
 * @retrval	0	- sucess
 * @retrval	1	- fail
 */

int
send_job_exec_update_to_mom(job *pjob, char *err_msg, int err_msg_sz,
				struct batch_request *reply_req)
{
	struct batch_request *newreq;
	char		*new_exec_vnode = NULL;
	char		*new_exec_host = NULL;
	char		*new_exec_host2 = NULL;
	attribute	*psched = NULL;
	int		rc = 1;
	int		num_updates = 0;
	struct	work_task	*pwt = NULL;

	if (pjob == NULL) {
		log_err(-1, __func__, "bad job parameter");
		return (1);
	}

	if ((err_msg[0] != '\0') && (reply_req != NULL)) {
		/*
		 * be sure to save/send this extra info in
		 * 'err_msg' buf
		 */
		reply_req->rq_extend = strdup(err_msg);
		if (reply_req->rq_extend == NULL) {
			log_err(-1, __func__, "strdup failed");
			return (1);
		}

	}

	newreq = alloc_br(PBS_BATCH_ModifyJob);

	if (newreq == (struct batch_request *) 0) {
		log_err(-1, __func__, "failed to alloc_br for PBS_MATCH_modifyjob");
		return (1);
	}
	CLEAR_HEAD(newreq->rq_ind.rq_modify.rq_attr);

	(void)strcpy(newreq->rq_ind.rq_modify.rq_objname,
                                        pjob->ji_qs.ji_jobid);

	if (pjob->ji_wattr[(int)JOB_ATR_exec_vnode].at_flags & ATR_VFLAG_SET) {
	 	new_exec_vnode =
		  pjob->ji_wattr[(int)JOB_ATR_exec_vnode].at_val.at_str;

		if (add_to_svrattrl_list(
			&(newreq->rq_ind.rq_modify.rq_attr),
			ATTR_execvnode, NULL, new_exec_vnode, 0,
							NULL) == -1) {
			if ((err_msg != NULL) && (err_msg_sz > 0)) {
				snprintf(err_msg, err_msg_sz, "failed to add_to_svrattrl_list(%s,%s,%s)", ATTR_execvnode, "", new_exec_vnode);
        			log_event(PBSEVENT_DEBUG2, PBS_EVENTCLASS_JOB, LOG_DEBUG, pjob->ji_qs.ji_jobid, err_msg);
			}
			goto send_job_exec_update_exit;
		}
		num_updates++;
	}

	if (pjob->ji_wattr[(int)JOB_ATR_exec_host].at_flags & ATR_VFLAG_SET) {
		new_exec_host =
		   pjob->ji_wattr[(int)JOB_ATR_exec_host].at_val.at_str;

		if (add_to_svrattrl_list(
			&(newreq->rq_ind.rq_modify.rq_attr),
			ATTR_exechost, NULL, new_exec_host, 0, NULL) == -1) {
			if ((err_msg != NULL) && (err_msg_sz > 0)) {
				snprintf(err_msg, err_msg_sz, "failed to add_to_svrattrl_list(%s,%s,%s)", ATTR_exechost, "", new_exec_host);
        			log_event(PBSEVENT_DEBUG2, PBS_EVENTCLASS_JOB, LOG_DEBUG, pjob->ji_qs.ji_jobid, err_msg);
			}
			goto send_job_exec_update_exit;
		}
		num_updates++;
	}

	if (pjob->ji_wattr[(int)JOB_ATR_exec_host2].at_flags & ATR_VFLAG_SET) {
		new_exec_host2 =
		  pjob->ji_wattr[(int)JOB_ATR_exec_host2].at_val.at_str;

		if (add_to_svrattrl_list(
			&(newreq->rq_ind.rq_modify.rq_attr),
			ATTR_exechost2, NULL, new_exec_host2, 0, NULL) == -1) {
			if ((err_msg != NULL) && (err_msg_sz > 0)) {
				snprintf(err_msg, err_msg_sz, "failed to add_to_svrattrl_list(%s,%s,%s)", ATTR_exechost2, "", new_exec_host2);
        			log_event(PBSEVENT_DEBUG2, PBS_EVENTCLASS_JOB, LOG_DEBUG, pjob->ji_qs.ji_jobid, err_msg);
			}
			goto send_job_exec_update_exit;
		}
		num_updates++;
	}

	psched = &pjob->ji_wattr[(int)JOB_ATR_SchedSelect];
	if ((psched->at_flags & ATR_VFLAG_SET) != 0) {
		if (add_to_svrattrl_list(
			&(newreq->rq_ind.rq_modify.rq_attr),
			ATTR_SchedSelect, NULL,
			psched->at_val.at_str, 0, NULL) == -1) {
			if ((err_msg != NULL) && (err_msg_sz > 0)) {
				snprintf(err_msg, err_msg_sz, "failed to add_to_svrattrl_list(%s,%s,%s)", ATTR_SchedSelect, "", psched->at_val.at_str);
        			log_event(PBSEVENT_DEBUG2, PBS_EVENTCLASS_JOB, LOG_DEBUG, pjob->ji_qs.ji_jobid, err_msg);
			}
			goto send_job_exec_update_exit;
		}
		num_updates++;
	}


	if ((pjob->ji_wattr[JOB_ATR_resource].at_flags & ATR_VFLAG_SET) != 0) {
		pbs_list_head    collectresc;
		svrattrl 	*psvrl;
		attribute_def	*objatrdef;
		extern  int	resc_access_perm;

		objatrdef = &job_attr_def[(int)JOB_ATR_resource];
		CLEAR_HEAD(collectresc);
		resc_access_perm = READ_ONLY;
		if (objatrdef->at_encode(&pjob->ji_wattr[(int)JOB_ATR_resource], &collectresc, objatrdef->at_name, NULL, ATR_ENCODE_CLIENT, NULL) > 0) {

			psvrl = (svrattrl *)GET_NEXT(collectresc);
			while (psvrl) {
				if (add_to_svrattrl_list(
				    &(newreq->rq_ind.rq_modify.rq_attr),
				    objatrdef->at_name, psvrl->al_resc,
				      psvrl->al_value, 0, NULL) == -1) {
					free_attrlist(&collectresc);
					if ((err_msg != NULL) && (err_msg_sz > 0)) {
						snprintf(err_msg, err_msg_sz, "failed to add_to_svrattrl_list(%s,%s,%s)", objatrdef->at_name, psvrl->al_resc, psvrl->al_value);
        					log_event(PBSEVENT_DEBUG2, PBS_EVENTCLASS_JOB, LOG_DEBUG, pjob->ji_qs.ji_jobid, err_msg);
					}
					goto send_job_exec_update_exit;
				}
				num_updates++;
				psvrl = (svrattrl *)GET_NEXT(psvrl->al_link);
			}
			free_attrlist(&collectresc);
		}
	}

	/* pass the request on to MOM */

	if (num_updates > 0) {
		rc = relay_to_mom2(pjob, newreq,
				post_send_job_exec_update_req, &pwt);
		if (rc != 0) {
			log_err(-1, __func__, "failed telling mom of the request");
		} else {
			pwt->wt_parm2 = reply_req;
		}
	} else {
		/* no updates, ok */
		rc = 0;
	}

send_job_exec_update_exit:

	if ((rc != 0) || (num_updates == 0)) {
		free_br(newreq);
	}

	return (rc);
}

/**
 * @brief
 *	Recreates the pjob's exec_vnode, updating at the same time
 *	its corresponding exec_host and exec_host2 attributes
 *	by taking out the vnodes managed by sister moms.
 *
 * @param[in,out]	pjob - job structure
 * @param[in]		vnodelist - if non-NULL, lists the vnodes to be
 *				freed whose parent mom is a sister mom.
 *				if NULL, releases all the sister
 *				vnodes assigned to 'pjob'
 * @param[out]  err_msg - if function returns != 0 (failure), return
 *			  any error message in this buffer.
 * @param[int]	err_msg_sz - size of 'err_msg' buf.
 * @return int
 * @retval 0	for success
 * @reval 1	for error
*/
int
recreate_exec_vnode(job *pjob, char *vnodelist, char *err_msg,
						int err_msg_sz)
{
	char	*exec_vnode = NULL;
	char	*exec_host = NULL;
	char	*exec_host2 = NULL;
	char	*new_exec_vnode = NULL;
	char	*new_exec_host = NULL;
	char	*new_exec_host2 = NULL;
	char	*new_select = NULL;
	char	*schedselect = NULL;
	char	*deallocated_execvnode = NULL;
	char	*new_deallocated_execvnode = NULL;
	resource_def	*prdefsl = NULL;
	resource	*presc;
	attribute	deallocated_execvnode_attr;
	int		rc = 1;
	relnodes_input_t		r_input;
	relnodes_input_vnodelist_t	r_input_vnlist;

	if (pjob == NULL) {
		log_err(-1, __func__, "bad job parameter");
		return (1);
	}

	if ((pjob->ji_qs.ji_state != JOB_STATE_RUNNING) &&
	    (pjob->ji_qs.ji_state != JOB_STATE_EXITING)) {
		log_err(-1, __func__, "job not in running or exiting state");
		return (1);

	}

	if ((pjob->ji_wattr[(int)JOB_ATR_exec_vnode].at_flags & ATR_VFLAG_SET) == 0) {
		log_err(-1, __func__, "exec_vnode is not set");
		return (1);
	}

	if ((pjob->ji_wattr[(int)JOB_ATR_exec_host].at_flags & ATR_VFLAG_SET) == 0) {
		log_err(-1, __func__, "exec_host is not set");
		return (1);
	}

	if ((pjob->ji_wattr[(int)JOB_ATR_exec_host2].at_flags & ATR_VFLAG_SET) == 0) {
		log_err(-1, __func__, "exec_host2 is not set");
		return (1);
	}

	if ((pjob->ji_wattr[(int)JOB_ATR_SchedSelect].at_flags & ATR_VFLAG_SET) == 0) {
		log_err(-1, __func__, "schedselect is not set");
		return (1);
	}

	exec_vnode = pjob->ji_wattr[(int) JOB_ATR_exec_vnode].at_val.at_str;

	exec_host = pjob->ji_wattr[(int)JOB_ATR_exec_host].at_val.at_str;

	exec_host2 = pjob->ji_wattr[(int)JOB_ATR_exec_host2].at_val.at_str;

	schedselect = pjob->ji_wattr[JOB_ATR_SchedSelect].at_val.at_str;

	deallocated_execvnode_attr = pjob->ji_wattr[(int)JOB_ATR_exec_vnode_deallocated];
	if (deallocated_execvnode_attr.at_flags & ATR_VFLAG_SET) {
		deallocated_execvnode = deallocated_execvnode_attr.at_val.at_str;
	}

	relnodes_input_init(&r_input);
	r_input.jobid = pjob->ji_qs.ji_jobid;
	r_input.execvnode = exec_vnode;
	r_input.exechost = exec_host;
	r_input.exechost2 = exec_host2;
	r_input.p_new_exec_vnode = &new_exec_vnode;
	r_input.p_new_exec_host[0] = &new_exec_host;
	r_input.p_new_exec_host[1] = &new_exec_host2;
	r_input.p_new_schedselect = &new_select;

	relnodes_input_vnodelist_init(&r_input_vnlist);
	r_input_vnlist.vnodelist = vnodelist;
	r_input_vnlist.schedselect = schedselect;
	r_input_vnlist.deallocated_nodes_orig = deallocated_execvnode;
	r_input_vnlist.p_new_deallocated_execvnode = &new_deallocated_execvnode;

	rc = pbs_release_nodes_given_nodelist(&r_input, &r_input_vnlist, err_msg, err_msg_sz);

	if (rc != 0) {
		goto recreate_exec_vnode_exit;
	}

	if (new_exec_vnode[0] != '\0') {

		if (strcmp(pjob->ji_wattr[(int) JOB_ATR_exec_vnode].at_val.at_str,
						 new_exec_vnode) == 0) {
			/* no change */

			if ((err_msg != NULL) && (err_msg_sz > 0)) {
				snprintf(err_msg, err_msg_sz, "node(s) requested to be released not part of the job: %s", vnodelist?vnodelist:"");
        			log_event(PBSEVENT_DEBUG2, PBS_EVENTCLASS_JOB, LOG_DEBUG, pjob->ji_qs.ji_jobid, err_msg);
			}
			goto recreate_exec_vnode_exit;
		}
		(void)job_attr_def[(int)JOB_ATR_exec_vnode_acct].at_decode(
			&pjob->ji_wattr[(int)JOB_ATR_exec_vnode_acct],
			NULL,
			NULL,
		pjob->ji_wattr[(int) JOB_ATR_exec_vnode].at_val.at_str);

		/* save original value which will be used later in the accounting end record */
		if ((pjob->ji_wattr[JOB_ATR_exec_vnode_orig].at_flags & ATR_VFLAG_SET) == 0) {
			(void)job_attr_def[(int)JOB_ATR_exec_vnode_orig].at_decode(
				&pjob->ji_wattr[(int)JOB_ATR_exec_vnode_orig],
				NULL,
				NULL,
				pjob->ji_wattr[(int) JOB_ATR_exec_vnode].at_val.at_str);
		}

		if ((pjob->ji_wattr[JOB_ATR_resource_acct].at_flags & ATR_VFLAG_SET) != 0) {
			job_attr_def[JOB_ATR_resource_acct].at_free(&pjob->ji_wattr[JOB_ATR_resource_acct]);
			pjob->ji_wattr[JOB_ATR_resource_acct].at_flags &= ~ATR_VFLAG_SET;
		}
		job_attr_def[JOB_ATR_resource_acct].at_set(&pjob->ji_wattr[JOB_ATR_resource_acct], &pjob->ji_wattr[JOB_ATR_resource], INCR);


		(void)job_attr_def[(int)JOB_ATR_exec_vnode].at_decode(
			&pjob->ji_wattr[(int)JOB_ATR_exec_vnode],
			NULL,
			NULL,
			new_exec_vnode);
		pjob->ji_modified = 1;

		(void)update_resources_list(pjob, ATTR_l,
			JOB_ATR_resource, new_exec_vnode, INCR, 0,
				JOB_ATR_resource_orig);
	}

	if (new_deallocated_execvnode[0] != '\0') {
		(void)job_attr_def[(int)JOB_ATR_exec_vnode_deallocated].at_decode(
			&pjob->ji_wattr[(int)JOB_ATR_exec_vnode_deallocated],
			NULL,
			NULL,
			new_deallocated_execvnode);
		pjob->ji_modified = 1;
	}

	if (new_exec_host[0] != '\0') {

		(void)job_attr_def[(int)JOB_ATR_exec_host_acct].at_decode(
			&pjob->ji_wattr[(int)JOB_ATR_exec_host_acct],
			NULL,
			NULL,
		  pjob->ji_wattr[(int)JOB_ATR_exec_host].at_val.at_str);

		/* save original value which will be used later in the accounting end record */
		if ((pjob->ji_wattr[JOB_ATR_exec_host_orig].at_flags & ATR_VFLAG_SET) == 0) {
			(void)job_attr_def[(int)JOB_ATR_exec_host_orig].at_decode(
				&pjob->ji_wattr[(int)JOB_ATR_exec_host_orig],
				NULL,
				NULL,
		  	pjob->ji_wattr[(int)JOB_ATR_exec_host].at_val.at_str);
		}

		(void)job_attr_def[(int)JOB_ATR_exec_host].at_decode(
			&pjob->ji_wattr[(int)JOB_ATR_exec_host],
			NULL,
			NULL,
			new_exec_host);
		pjob->ji_modified = 1;
	}

	if (new_exec_host2[0] != '\0') {

		(void)job_attr_def[(int)JOB_ATR_exec_host2].at_decode(
			&pjob->ji_wattr[(int)JOB_ATR_exec_host2],
			NULL,
			NULL,
			new_exec_host2);
		pjob->ji_modified = 1;
	}

	if (new_select[0] != '\0') {
		prdefsl = find_resc_def(svr_resc_def, "select",
							svr_resc_size);
		/* re-generate "select" resource */
		if (prdefsl != NULL) {
			presc = find_resc_entry(
			  	&pjob->ji_wattr[(int)JOB_ATR_resource], prdefsl);
			if (presc == NULL) {
				presc = add_resource_entry(
			  	 &pjob->ji_wattr[(int)JOB_ATR_resource], prdefsl);
			}
			if (presc != NULL) {
				(void)prdefsl->rs_decode(
					&presc->rs_value, NULL, "select", new_select);
			}
		}
		/* re-generate "schedselect" attribute */

		if ((pjob->ji_wattr[JOB_ATR_SchedSelect].\
				at_flags & ATR_VFLAG_SET) != 0) {
			/* Save current SchedSelect value if not */
			/* already saved in *_orig */
			if ((pjob->ji_wattr[JOB_ATR_SchedSelect_orig].at_flags & ATR_VFLAG_SET) == 0) {
				(void)decode_str(
                         		&pjob->ji_wattr[(int)JOB_ATR_SchedSelect_orig],
					NULL,
                                        NULL,
					pjob->ji_wattr[JOB_ATR_SchedSelect].at_val.at_str);

			}
		}
		(void)decode_str(
			&pjob->ji_wattr[(int)JOB_ATR_SchedSelect], NULL,
						NULL, new_select);
		/* re-generate nodect */
		(void)set_chunk_sum(&pjob->ji_wattr[(int)JOB_ATR_SchedSelect],
					&pjob->ji_wattr[(int)JOB_ATR_resource]);

	}
recreate_exec_vnode_exit:
	free(new_exec_vnode);
	free(new_exec_host);
	free(new_exec_host2);
	free(new_select);
	free(new_deallocated_execvnode);

	return (rc);
}
