/*
 * Copyright (C) 1994-2021 Altair Engineering, Inc.
 * For more information, contact Altair at www.altair.com.
 *
 * This file is part of both the OpenPBS software ("OpenPBS")
 * and the PBS Professional ("PBS Pro") software.
 *
 * Open Source License Information:
 *
 * OpenPBS 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.
 *
 * OpenPBS 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:
 *
 * PBS Pro is commercially licensed software that shares a common core with
 * the OpenPBS software.  For a copy of the commercial license terms and
 * conditions, go to: (http://www.pbspro.com/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 OpenPBS 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™",
 * "OpenPBS®", "PBS Professional®", and "PBS Pro™" and Altair's logos is
 * subject to Altair's trademark licensing policies.
 */

/**
 * @file	req_stat.c
 *
 * @brief
 * 		req_stat.c - Functions relating to the Status Job, Status Queue, and
 * 		Status Server Batch Requests.
 *
 * Functions included are:
 * 	do_stat_of_a_job()
 * 	stat_a_jobidname()
 * 	req_stat_job()
 * 	req_stat_que()
 * 	status_que()
 * 	req_stat_node()
 * 	status_node()
 * 	req_stat_svr()
 * 	req_stat_sched()
 * 	update_state_ct()
 * 	update_license_ct()
 * 	req_stat_resv()
 * 	status_resv()
 * 	status_resc()
 * 	req_stat_resc()
 *
 */
#include <pbs_config.h> /* the master config generated by configure */

#define STAT_CNTL 1

#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
#include "libpbs.h"
#include <ctype.h>
#include "server_limits.h"
#include "list_link.h"
#include "attribute.h"
#include "server.h"
#include "credential.h"
#include "batch_request.h"
#include "job.h"
#include "reservation.h"
#include "queue.h"
#include "work_task.h"
#include "pbs_entlim.h"
#include "pbs_error.h"
#include "pbs_nodes.h"
#include "svrfunc.h"
#include "net_connect.h"
#include "pbs_license.h"
#include "resource.h"
#include "pbs_sched.h"
#include "liblicense.h"
#include "ifl_internal.h"

/* Global Data Items: */

extern struct server server;
extern pbs_list_head svr_alljobs;
extern pbs_list_head svr_queues;
extern char server_name[];
extern attribute_def svr_attr_def[];
extern attribute_def que_attr_def[];
extern attribute_def job_attr_def[];
extern time_t time_now;
extern char *msg_init_norerun;
extern int resc_access_perm;
extern long svr_history_enable;
extern pbs_list_head svr_runjob_hooks;

extern pbs_license_counts license_counts;

/* Extern Functions */

extern int status_attrib(svrattrl *, void *, attribute_def *, attribute *, int, int, pbs_list_head *, int *);
extern int status_nodeattrib(svrattrl *, struct pbsnode *, int, int, pbs_list_head *, int *);

extern int svr_chk_histjob(job *);

/* Private Data Definitions */

static int bad;

/* The following private support functions are included */

static int status_que(pbs_queue *, struct batch_request *, pbs_list_head *);
static int status_node(struct pbsnode *, struct batch_request *, pbs_list_head *);
static int status_resv(resc_resv *, struct batch_request *, pbs_list_head *);

/**
 * @brief
 * 	Support function for req_stat_job() and stat_a_jobidname().
 * 	Builds status reply for normal job, Array job, and if requested all of the
 * 	subjobs of the array (but not a single or range of subjobs).
 *
 * @note
 * 	If dohistjobs is not set and the job is history, no status or error
 * 	is returned. If an error return is needed, the caller must make that check.
 *
 * @param[in,out] preq       - pointer to the stat job batch request, reply updated
 * @param[in]     pjob       - pointer to the job to be statused
 * @param[in]     dohistjobs - flag to include job if it is a history job
 * @param[in]     dosubjobs  - flag to expand a Array job to include all subjobs
 *
 * @return int
 * @retval PBSE_NONE  - no error
 * @retval !PBSE_NONE - PBS error code to return to client
 */
static int
do_stat_of_a_job(struct batch_request *preq, job *pjob, int dohistjobs, int dosubjobs)
{
	int i;
	svrattrl *pal;
	int rc;
	struct batch_reply *preply = &preq->rq_reply;

	/* if history job and not asking for them, just return */
	if (!dohistjobs && (check_job_state(pjob, JOB_STATE_LTR_FINISHED) || check_job_state(pjob, JOB_STATE_LTR_MOVED))) {
		return (PBSE_NONE); /* just return nothing */
	}

	if ((pjob->ji_qs.ji_svrflags & JOB_SVFLG_SubJob) == 0) {
		/* this is not a subjob, go ahead and build the status reply for this job */
		pal = (svrattrl *) GET_NEXT(preq->rq_ind.rq_status.rq_attr);
		rc = status_job(pjob, preq, pal, &preply->brp_un.brp_status, &bad, dosubjobs);
		if (dosubjobs && (pjob->ji_qs.ji_svrflags & JOB_SVFLG_ArrayJob) && (rc == PBSE_NONE || rc != PBSE_PERM) && pjob->ji_ajinfo != NULL && pjob->ji_ajinfo->tkm_ct != pjob->ji_ajinfo->tkm_subjsct[JOB_STATE_QUEUED]) {
			for (i = pjob->ji_ajinfo->tkm_start; i <= pjob->ji_ajinfo->tkm_end; i += pjob->ji_ajinfo->tkm_step) {
				if (range_contains(pjob->ji_ajinfo->trm_quelist, i))
					continue;
				rc = status_subjob(pjob, preq, pal, i, &preply->brp_un.brp_status, &bad, 1);
				if (rc && rc != PBSE_PERM)
					break;
			}
		}
		if (rc && rc != PBSE_PERM)
			return (rc);
	}
	return PBSE_NONE;
}

/**
 * @brief
 * 	Support function for req_stat_job().
 * 	Builds status reply for a single job id, which may be: a normal job,
 * 	an Array job, a single subjob or a range of subjobs.
 * 	Finds the job structure for the job id and calls either do_stat_of_a_job()
 * 	or status_subjob() to build that actual status reply.
 *
 * @param[in,out] preq       - pointer to the stat job batch request, reply updated
 * @param[in]     name       - job id to be statused
 * @param[in]     dohistjobs - flag to include job if it is a history job
 * @param[in]     dosubjobs  - flag to expand a Array job to include all subjobs
 *
 * @return int
 * @retval PBSE_NONE  - no error
 * @retval !PBSE_NONE - PBS error code to return to client
 */
static int
stat_a_jobidname(struct batch_request *preq, char *name, int dohistjobs, int dosubjobs)
{
	int i;
	char *pc;
	char *range;
	int rc;
	job *pjob;
	struct batch_reply *preply = &preq->rq_reply;
	svrattrl *pal;

	i = is_job_array(name);
	if (i == IS_ARRAY_Single) {
		int idx;

		pjob = find_arrayparent(name);
		if (pjob == NULL)
			return PBSE_UNKJOBID;
		else if (!dohistjobs && (rc = svr_chk_histjob(pjob)))
			return rc;
		idx = get_index_from_jid(name);
		if (idx != -1) {
			pal = (svrattrl *) GET_NEXT(preq->rq_ind.rq_status.rq_attr);
			rc = status_subjob(pjob, preq, pal, idx, &preply->brp_un.brp_status, &bad, 0);
		} else
			rc = PBSE_UNKJOBID;
		return rc; /* no job still needs to be stat-ed */

	} else if ((i == IS_ARRAY_NO) || (i == IS_ARRAY_ArrayJob)) {
		pjob = find_job(name);
		if (pjob == NULL)
			return PBSE_UNKJOBID;
		else if (!dohistjobs && (rc = svr_chk_histjob(pjob)) != PBSE_NONE)
			return rc;
		return do_stat_of_a_job(preq, pjob, dohistjobs, dosubjobs);
	} else {
		/* range of sub jobs */
		range = get_range_from_jid(name);
		if (range == NULL)
			return PBSE_IVALREQ;
		pjob = find_arrayparent(name);
		if (pjob == NULL)
			return PBSE_UNKJOBID;
		else if (!dohistjobs && (rc = svr_chk_histjob(pjob)) != PBSE_NONE)
			return rc;
		pal = (svrattrl *) GET_NEXT(preq->rq_ind.rq_status.rq_attr);
		while (1) {
			int start;
			int end;
			int step;
			int unused;

			if ((i = parse_subjob_index(range, &pc, &start, &end, &step, &unused)) == -1)
				return PBSE_IVALREQ;
			else if (i == 1)
				break;
			for (i = start; i <= end; i += step) {
				if (preply->brp_count >= MAX_JOBS_PER_REPLY) {
					rc = reply_send_status_part(preq);
					if (rc != PBSE_NONE)
						return rc;
				}
				rc = status_subjob(pjob, preq, pal, i, &preply->brp_un.brp_status, &bad, 0);
				if (rc && rc != PBSE_PERM)
					return rc;
			}
			range = pc;
		}
		/* stat-ed the range, no more to stat for this id */
		return PBSE_NONE;
	}
}

/**
 * @brief
 * 	Service the Status Job Request
 *
 * 	This processes the request for status of a single job or
 * 	the set of jobs at a destination.  It uses the currently known data
 * 	for resources_used in the case of a running job.  If Mom for that
 * 	job is down, the data is likely stale.
 * 	The requested object may be a job id (either a single regular job, an Array
 * 	job, a subjob or a range of subjobs), a comma separated list of the above,
 * 	a queue name or null (or @...) for all jobs in the Server.
 *
 * @param[in/out] preq - pointer to the stat job batch request, reply updated
 *
 * @return void
 *
 */
void
req_stat_job(struct batch_request *preq)
{
	int at_least_one_success = 0;
	int dosubjobs = 0;
	int dohistjobs = 0;
	char *name;
	job *pjob = NULL;
	pbs_queue *pque = NULL;
	struct batch_reply *preply;
	int rc = 0;
	int type = 0;
	char *pnxtjid = NULL;

	/* check for any extended flag in the batch request. 't' for
	 * the sub jobs. If 'x' is there, then check if the server is
	 * configured for history job info. If not set or set to FALSE,
	 * return with PBSE_JOBHISTNOTSET error. Otherwise select history
	 * jobs.
	 */
	if (preq->rq_extend) {
		if (strchr(preq->rq_extend, (int) 't'))
			dosubjobs = 1; /* status sub jobs of an Array Job */
		if (strchr(preq->rq_extend, (int) 'x')) {
			if (svr_history_enable == 0) {
				req_reject(PBSE_JOBHISTNOTSET, 0, preq);
				return;
			}
			dohistjobs = 1; /* status history jobs */
		}
	}

	/*
	 * first, validate the name of the requested object, either
	 * a job, a queue, or the whole server.
	 * type = 1 for a job, Array job, subjob or range of subjobs, or
	 *          a comma separated list of  the above.
	 *        2 for jobs in a queue,
	 *        3 for jobs in the server, or
	 */

	name = preq->rq_ind.rq_status.rq_id;

	if (isdigit((int) *name)) {
		/* a single job id */
		type = 1;
		rc = PBSE_UNKJOBID;

	} else if (isalpha((int) *name)) {
		pque = find_queuebyname(name) /* status jobs in a queue */;
#ifdef NAS /* localmod 075 */
		if (pque == NULL)
			pque = find_resvqueuebyname(name);
#endif /* localmod 075 */
		if (pque)
			type = 2;
		else
			rc = PBSE_UNKQUE;

	} else if ((*name == '\0') || (*name == '@'))
		type = 3; /* status all jobs at server */
	else
		rc = PBSE_IVALREQ;

	if (type == 0) { /* is invalid - an error */
		req_reject(rc, 0, preq);
		return;
	}
	preply = &preq->rq_reply;
	preply->brp_choice = BATCH_REPLY_CHOICE_Status;
	CLEAR_HEAD(preply->brp_un.brp_status);
	preply->brp_count = 0;

	if (dosubjobs && GET_NEXT(preq->rq_ind.rq_status.rq_attr) != NULL) {
		if (find_svrattrl_list_entry(&preq->rq_ind.rq_status.rq_attr, ATTR_array_indices_remaining, NULL) == NULL)
			add_to_svrattrl_list(&preq->rq_ind.rq_status.rq_attr, ATTR_array_indices_remaining, NULL, "", SET, NULL);
	}

	rc = PBSE_NONE;
	if (type == 1) {
		/*
		 * If there is more than one job id, any status for any
		 * one job is returned, then no error is given.
		 * If a single job id is requested and there is an error
		 * the error is returned.
		 */
		pnxtjid = name;
		while ((name = parse_comma_string_r(&pnxtjid)) != NULL) {
			if ((rc = stat_a_jobidname(preq, name, dohistjobs, dosubjobs)) == PBSE_NONE)
				at_least_one_success = 1;
		}
		if (at_least_one_success == 1)
			reply_send(preq);
		else
			req_reject(rc, 0, preq);
		return;

	} else {
		pjob = (job *) GET_NEXT(type == 2 ? pque->qu_jobs : svr_alljobs);
		while (pjob) {
			rc = do_stat_of_a_job(preq, pjob, dohistjobs, dosubjobs);
			if (rc != PBSE_NONE) {
				req_reject(rc, bad, preq);
				return;
			}
			pjob = (job *) GET_NEXT(type == 2 ? pjob->ji_jobque : pjob->ji_alljobs);
			if (preply->brp_count >= MAX_JOBS_PER_REPLY && pjob) {
				rc = reply_send_status_part(preq);
				if (rc != PBSE_NONE)
					return;
			}
		}
	}

	if (rc && rc != PBSE_PERM)
		req_reject(rc, bad, preq);
	else
		reply_send(preq);
}

/**
 * @brief
 * 		req_stat_que - service the Status Queue Request
 *
 *		This request processes the request for status of a single queue or
 *		the set of queues at a destination.
 *
 * @param[in,out]	preq	-	ptr to the decoded request
 */

void
req_stat_que(struct batch_request *preq)
{
	char *name;
	pbs_queue *pque;
	struct batch_reply *preply;
	int rc = 0;
	int type = 0;

	/*
	 * first, validate the name of the requested object, either
	 * a queue, or null for all queues
	 */

	name = preq->rq_ind.rq_status.rq_id;

	if ((*name == '\0') || (*name == '@'))
		type = 1;
	else {
		pque = find_queuebyname(name);
#ifdef NAS /* localmod 075 */
		if (pque == NULL)
			pque = find_resvqueuebyname(name);
#endif /* localmod 075 */
		if (pque == NULL) {
			req_reject(PBSE_UNKQUE, 0, preq);
			return;
		}
	}

	preply = &preq->rq_reply;
	preply->brp_choice = BATCH_REPLY_CHOICE_Status;
	CLEAR_HEAD(preply->brp_un.brp_status);
	preply->brp_count = 0;

	if (type == 0) { /* get status of the one named queue */
		rc = status_que(pque, preq, &preply->brp_un.brp_status);

	} else { /* get status of queues */

		pque = (pbs_queue *) GET_NEXT(svr_queues);
		while (pque) {
			rc = status_que(pque, preq, &preply->brp_un.brp_status);
			if (rc != 0) {
				if (rc == PBSE_PERM)
					rc = 0;
				else
					break;
			}
			pque = (pbs_queue *) GET_NEXT(pque->qu_link);
		}
	}
	if (rc) {
		reply_free(preply);
		req_reject(rc, bad, preq);
	} else {
		reply_send(preq);
	}
}

/**
 * @brief
 * 		status_que - Build the status reply for a single queue.
 *
 * @param[in,out]	pque	-	ptr to que to status
 * @param[in]		preq	-	ptr to the decoded request
 * @param[in,out]	pstathd	-	head of list to append status to
 *
 * @return	int
 * @retval	0	: success
 * @retval	!0	: PBSE error code
 */

static int
status_que(pbs_queue *pque, struct batch_request *preq, pbs_list_head *pstathd)
{
	struct brp_status *pstat;
	svrattrl *pal;
	long total_jobs;
	int rc = 0;
	attribute *qattr;

	if ((preq->rq_perm & ATR_DFLAG_RDACC) == 0)
		return (PBSE_PERM);

	/* ok going to do status, update count and state counts from qu_qs */

	if (!svr_chk_history_conf()) {
		total_jobs = pque->qu_numjobs;
	} else {
		total_jobs = pque->qu_numjobs - (pque->qu_njstate[JOB_STATE_MOVED] + pque->qu_njstate[JOB_STATE_FINISHED] + pque->qu_njstate[JOB_STATE_EXPIRED]);
	}
	set_qattr_l_slim(pque, QA_ATR_TotalJobs, total_jobs, SET);

	qattr = get_qattr(pque, QA_ATR_JobsByState);
	update_state_ct(qattr, pque->qu_njstate, &que_attr_def[QA_ATR_JobsByState]);

	/* allocate status sub-structure and fill in header portion */

	pstat = (struct brp_status *) malloc(sizeof(struct brp_status));
	if (pstat == NULL)
		return (PBSE_SYSTEM);
	pstat->brp_objtype = MGR_OBJ_QUEUE;
	strcpy(pstat->brp_objname, pque->qu_qs.qu_name);
	CLEAR_LINK(pstat->brp_stlink);
	CLEAR_HEAD(pstat->brp_attr);
	append_link(pstathd, &pstat->brp_stlink, pstat);
	preq->rq_reply.brp_count++;

	/* add attributes to the status reply */

	bad = 0;
	pal = (svrattrl *) GET_NEXT(preq->rq_ind.rq_status.rq_attr);
	if (status_attrib(pal, que_attr_idx, que_attr_def, pque->qu_attr, QA_ATR_LAST,
			  preq->rq_perm, &pstat->brp_attr, &bad))
		rc = PBSE_NOATTR;

	if (is_attr_set(qattr))
		free_attr(que_attr_def, qattr, QA_ATR_JobsByState);
	return rc;
}

/**
 * @brief
 * 		req_stat_node - service the Status Node Request
 *
 *		This request processes the request for status of a single node or
 *		set of nodes at a destination.
 *
 * @param[in]	preq	-	ptr to the decoded request
 */

void
req_stat_node(struct batch_request *preq)
{
	char *name;
	struct batch_reply *preply;
	svrattrl *pal;
	struct pbsnode *pnode = NULL;
	int rc = 0;
	int type = 0;
	int i;

	/*
	 * first, check that the server indeed has a list of nodes
	 * and if it does, validate the name of the requested object--
	 * either name is that of a spedific node, or name[0] is null/@
	 * meaning request is for all nodes in the server's jurisdiction
	 */

	if (pbsndlist == 0 || svr_totnodes <= 0) {
		req_reject(PBSE_NONODES, 0, preq);
		return;
	}

	resc_access_perm = preq->rq_perm;

	name = preq->rq_ind.rq_status.rq_id;

	if ((*name == '\0') || (*name == '@'))
		type = 1;
	else {
		pnode = find_nodebyname(name);
		if (pnode == NULL) {
			req_reject(PBSE_UNKNODE, 0, preq);
			return;
		}
	}

	preply = &preq->rq_reply;
	preply->brp_choice = BATCH_REPLY_CHOICE_Status;
	CLEAR_HEAD(preply->brp_un.brp_status);
	preply->brp_count = 0;

	if (type == 0) { /* get status of the named node */
		rc = status_node(pnode, preq, &preply->brp_un.brp_status);

	} else { /* get status of all nodes */

		for (i = 0; i < svr_totnodes; i++) {
			pnode = pbsndlist[i];

			rc = status_node(pnode, preq,
					 &preply->brp_un.brp_status);
			if (rc)
				break;
		}
	}

	if (!rc) {
		reply_send(preq);
	} else {
		if (rc != PBSE_UNKNODEATR)
			req_reject(rc, 0, preq);

		else {
			pal = (svrattrl *) GET_NEXT(preq->rq_ind.rq_status.rq_attr);
			reply_badattr(rc, bad, pal, preq);
		}
	}
}

/**
 * @brief
 * 		status_node - Build the status reply for a single node.
 *
 * @param[in,out]	pnode	-	ptr to node receiving status query
 * @param[in]	preq	-	ptr to the decoded request
 * @param[in,out]	pstathd	-	head of list to append status to
 *
 * @return	int
 * @retval	0	: success
 * @retval	!0	: PBSE error code
 */

static int
status_node(struct pbsnode *pnode, struct batch_request *preq, pbs_list_head *pstathd)
{
	int rc = 0;
	struct brp_status *pstat;
	svrattrl *pal;
	unsigned long old_nd_state = VNODE_UNAVAILABLE;

	if (pnode->nd_state & INUSE_DELETED) /*node no longer valid*/
		return (0);

	if ((preq->rq_perm & ATR_DFLAG_RDACC) == 0)
		return (PBSE_PERM);

	/* sync state attribute with nd_state */

	if (pnode->nd_state != get_nattr_long(pnode, ND_ATR_state))
		set_nattr_l_slim(pnode, ND_ATR_state, pnode->nd_state, SET);

	/*node is provisioning - mask out the DOWN/UNKNOWN flags while prov is on*/
	if (get_nattr_long(pnode, ND_ATR_state) & (INUSE_PROV | INUSE_WAIT_PROV)) {
		old_nd_state = get_nattr_long(pnode, ND_ATR_state);

		/* don't want to show job-busy, job/resv-excl while provisioning */
		set_nattr_l_slim(pnode, ND_ATR_state,
				 old_nd_state & ~(INUSE_DOWN | INUSE_UNKNOWN | INUSE_JOB | INUSE_JOBEXCL | INUSE_RESVEXCL),
				 SET);
	}

	/*allocate status sub-structure and fill in header portion*/

	pstat = (struct brp_status *) malloc(sizeof(struct brp_status));
	if (pstat == NULL)
		return (PBSE_SYSTEM);

	pstat->brp_objtype = MGR_OBJ_NODE;
	strcpy(pstat->brp_objname, pnode->nd_name);
	CLEAR_LINK(pstat->brp_stlink);
	CLEAR_HEAD(pstat->brp_attr);

	/*add this new brp_status structure to the list hanging off*/
	/*the request's reply substructure                         */

	append_link(pstathd, &pstat->brp_stlink, pstat);
	preq->rq_reply.brp_count++;

	/*point to the list of node-attributes about which we want status*/
	/*hang that status information from the brp_attr field for this  */
	/*brp_status structure                                           */
	bad = 0; /*global variable*/
	pal = (svrattrl *) GET_NEXT(preq->rq_ind.rq_status.rq_attr);

	rc = status_nodeattrib(pal, pnode, ND_ATR_LAST, preq->rq_perm, &pstat->brp_attr, &bad);

	/*reverting back the state*/

	if (get_nattr_long(pnode, ND_ATR_state) & INUSE_PROV)
		set_nattr_l_slim(pnode, ND_ATR_state, old_nd_state, SET);

	return (rc);
}

/**
 * @brief
 * 	update_isrunhook - update the value is has_runjob_hook
 *
 * @param[in]	pattr - ptr to the server attribute object
 *
 * @return	void
 */
static void
update_isrunhook(attribute *pattr)
{
	hook *phook = NULL;
	long old_val = pattr->at_val.at_long;
	long new_val = 0;

	/* Check if there are any valid runjob hooks */
	for (phook = (hook *) GET_NEXT(svr_runjob_hooks);
	     phook != NULL;
	     phook = (hook *) GET_NEXT(phook->hi_runjob_hooks)) {
		if (phook->enabled) {
			new_val = 1;
			break;
		}
	}

	if (new_val != old_val) {
		pattr->at_val.at_long = new_val;
		post_attr_set(pattr);
	}
}

/**
 * @brief
 * 		req_stat_svr - service the Status Server Request
 * @par
 *		This request processes the request for status of the Server
 *
 * @param[in]	preq	-	ptr to the decoded request
 */

void
req_stat_svr(struct batch_request *preq)
{
	svrattrl *pal;
	struct batch_reply *preply;
	struct brp_status *pstat;
	conn_t *conn;

	/* update count and state counts from sv_numjobs and sv_jobstates */
	set_sattr_l_slim(SVR_ATR_TotalJobs, server.sv_qs.sv_numjobs, SET);
	update_state_ct(get_sattr(SVR_ATR_JobsByState), server.sv_jobstates, &svr_attr_def[SVR_ATR_JobsByState]);

	update_license_ct();

	conn = get_conn(preq->rq_conn);
	if (!conn) {
		req_reject(PBSE_SYSTEM, 0, preq);
		return;
	}
	if (conn->cn_origin == CONN_SCHED_PRIMARY) {
		/* Request is from sched so update "has_runjob_hook" */
		update_isrunhook(get_sattr(SVR_ATR_has_runjob_hook));
	}

	/* allocate a reply structure and a status sub-structure */

	preply = &preq->rq_reply;
	preply->brp_choice = BATCH_REPLY_CHOICE_Status;
	CLEAR_HEAD(preply->brp_un.brp_status);
	preply->brp_count = 0;

	pstat = (struct brp_status *) malloc(sizeof(struct brp_status));
	if (pstat == NULL) {
		reply_free(preply);
		req_reject(PBSE_SYSTEM, 0, preq);
		return;
	}
	CLEAR_LINK(pstat->brp_stlink);
	strcpy(pstat->brp_objname, server_name);
	pstat->brp_objtype = MGR_OBJ_SERVER;
	CLEAR_HEAD(pstat->brp_attr);
	append_link(&preply->brp_un.brp_status, &pstat->brp_stlink, pstat);
	preply->brp_count++;

	/* add attributes to the status reply */

	bad = 0;
	pal = (svrattrl *) GET_NEXT(preq->rq_ind.rq_status.rq_attr);
	if (status_attrib(pal, svr_attr_idx, svr_attr_def, server.sv_attr, SVR_ATR_LAST,
			  preq->rq_perm, &pstat->brp_attr, &bad))
		reply_badattr(PBSE_NOATTR, bad, pal, preq);
	else
		reply_send(preq);
}

/**
 * @brief
 * 		status_sched - Build the status reply for single scheduler
 *
 * @param[in]	psched	-	ptr to sched receiving status query
 * @param[in]	preq	-	ptr to the decoded request
 * @param[out]	pstathd	-	head of list to append status to
 *
 * @return	int
 * @retval	0	: success
 * @retval	!0	: PBSE error code
 */
static int
status_sched(pbs_sched *psched, struct batch_request *preq, pbs_list_head *pstathd)
{
	int rc = 0;
	struct brp_status *pstat;
	svrattrl *pal;

	pstat = (struct brp_status *) malloc(sizeof(struct brp_status));
	if (pstat == NULL)
		return (PBSE_SYSTEM);

	pstat->brp_objtype = MGR_OBJ_SCHED;
	strncpy(pstat->brp_objname, psched->sc_name, (PBS_MAXSVRJOBID > PBS_MAXDEST ? PBS_MAXSVRJOBID : PBS_MAXDEST) - 1);
	pstat->brp_objname[(PBS_MAXSVRJOBID > PBS_MAXDEST ? PBS_MAXSVRJOBID : PBS_MAXDEST) - 1] = '\0';

	CLEAR_LINK(pstat->brp_stlink);
	CLEAR_HEAD(pstat->brp_attr);
	append_link(pstathd, &pstat->brp_stlink, pstat);
	preq->rq_reply.brp_count++;

	bad = 0;
	pal = (svrattrl *) GET_NEXT(preq->rq_ind.rq_status.rq_attr);
	if (status_attrib(pal, sched_attr_idx, sched_attr_def, psched->sch_attr, SCHED_ATR_LAST,
			  preq->rq_perm, &pstat->brp_attr, &bad))
		reply_badattr(PBSE_NOATTR, bad, pal, preq);

	return (rc);
}

/**
 * @brief
 * 		req_stat_sched - service a PBS_BATCH_StatusSched request
 * @par
 *		This function processes a request regarding scheduler status
 *
 * @param[in]	preq	-	ptr to the decoded request
 *
 * @par MT-safe: No
 */

void
req_stat_sched(struct batch_request *preq)
{
	svrattrl *pal;
	struct batch_reply *preply;
	int rc = 0;
	pbs_sched *psched;

	/* allocate a reply structure and a status sub-structure */

	preply = &preq->rq_reply;
	preply->brp_choice = BATCH_REPLY_CHOICE_Status;
	CLEAR_HEAD(preply->brp_un.brp_status);
	preply->brp_count = 0;

	for (psched = (pbs_sched *) GET_NEXT(svr_allscheds);
	     (psched != NULL);
	     psched = (pbs_sched *) GET_NEXT(psched->sc_link)) {
		rc = status_sched(psched, preq, &preply->brp_un.brp_status);
		if (rc != 0) {
			break;
		}
	}

	if (!rc) {
		reply_send(preq);
	} else {
		if (rc != PBSE_NOATTR)
			req_reject(rc, 0, preq);
		else {
			pal = (svrattrl *) GET_NEXT(preq->rq_ind.rq_status.rq_attr);
			reply_badattr(rc, bad, pal, preq);
		}
	}
}

/**
 * @brief
 * 		update-state_ct - update the count of jobs per state (in queue and server
 *		attributes.
 *
 * @param[out]	pattr	-	queue or server attribute
 * @param[in]	ct_array	-	number of jobs per state
 * @param[in] attr_def - attribute def of pattr
 *
 * @par MT-safe: No
 */

void
update_state_ct(attribute *pattr, int *ct_array, attribute_def *attr_def)
{
	static char *statename[] = {"Transit", "Queued", "Held", "Waiting",
				    "Running", "Exiting", "Expired", "Begun",
				    "Moved", "Finished"};
	int index;
	char buf[BUF_SIZE];

	buf[0] = '\0';
	for (index = 0; index < (PBS_NUMJOBSTATE); index++) {
		if ((index == JOB_STATE_EXPIRED) ||
		    (index == JOB_STATE_MOVED) ||
		    (index == JOB_STATE_FINISHED))
			continue; /* skip over Expired/Moved/Finished */
		sprintf(buf + strlen(buf), "%s:%d ", statename[index],
			*(ct_array + index));
	}
	set_attr_generic(pattr, attr_def, buf, NULL, INTERNAL);
}

/**
 * @brief
 * 	update_license_ct - update the # of licenses (counters) in 'license_count' server attribute.
 */
void
update_license_ct(void)
{
	char buf[BUF_SIZE];

	buf[0] = '\0';
	snprintf(buf, sizeof(buf), "Avail_Global:%ld Avail_Local:%ld Used:%ld High_Use:%d",
		 license_counts.licenses_global,
		 license_counts.licenses_local,
		 license_counts.licenses_used,
		 license_counts.licenses_high_use.lu_max_forever);
	set_sattr_str_slim(SVR_ATR_license_count, buf, NULL);
}

/**
 * @brief
 * 		req_stat_resv - service the Status Reservation Request
 * @par
 *		This request processes the request for status of a single
 *		reservation or the set of reservations at a destination.
 *
 * @param[in,out]	preq	-	ptr to the decoded request
 */

void
req_stat_resv(struct batch_request *preq)
{
	char *name;
	struct batch_reply *preply;
	resc_resv *presv = NULL;
	int rc = 0;
	int type = 0;

	/*
	 * first, validate the name sent in the request.
	 * This is either the ID of a specific reservation
	 * or a '\0' or "@..." for all reservations.
	 */

	name = preq->rq_ind.rq_status.rq_id;

	if ((*name == '\0') || (*name == '@'))
		type = 1;
	else {
		presv = find_resv(name);
		if (presv == NULL) {
			req_reject(PBSE_UNKRESVID, 0, preq);
			return;
		}
	}

	preply = &preq->rq_reply;
	preply->brp_choice = BATCH_REPLY_CHOICE_Status;
	CLEAR_HEAD(preply->brp_un.brp_status);
	preply->brp_count = 0;

	if (type == 0) {
		/* get status of the specifically named reservation */
		rc = status_resv(presv, preq, &preply->brp_un.brp_status);

	} else {
		/* get status of all the reservations */

		presv = (resc_resv *) GET_NEXT(svr_allresvs);
		while (presv) {
			rc = status_resv(presv, preq, &preply->brp_un.brp_status);
			if (rc == PBSE_PERM)
				rc = 0;
			if (rc)
				break;
			presv = (resc_resv *) GET_NEXT(presv->ri_allresvs);
		}
	}

	if (rc == 0)
		reply_send(preq);
	else
		req_reject(rc, bad, preq);
}

/**
 * @brief
 * 		status_resv - Build the status reply for a single resv.
 *
 * @param[in]	presv	-	get status for this reservation
 * @param[in]	preq	-	ptr to the decoded request
 * @param[in,out]	pstathd	-	append retrieved status to list
 *
 * @return	int
 * @retval	0	: success
 * @retval	!0	: PBSE error
 */

static int
status_resv(resc_resv *presv, struct batch_request *preq, pbs_list_head *pstathd)
{
	struct brp_status *pstat;
	svrattrl *pal;

	if ((preq->rq_perm & ATR_DFLAG_RDACC) == 0)
		return (PBSE_PERM);

	/*first do any need update to attributes from
	 *"quick save" area of the resc_resv structure
	 */

	/*now allocate status sub-structure and fill header portion*/

	pstat = (struct brp_status *) malloc(sizeof(struct brp_status));
	if (pstat == NULL)
		return (PBSE_SYSTEM);

	pstat->brp_objtype = MGR_OBJ_RESV;
	strcpy(pstat->brp_objname, presv->ri_qs.ri_resvID);
	CLEAR_LINK(pstat->brp_stlink);
	CLEAR_HEAD(pstat->brp_attr);
	append_link(pstathd, &pstat->brp_stlink, pstat);
	preq->rq_reply.brp_count++;

	/*finally, add the requested attributes to the status reply*/

	bad = 0; /*global: record ordinal position where got error*/
	pal = (svrattrl *) GET_NEXT(preq->rq_ind.rq_status.rq_attr);

	if (status_attrib(pal, resv_attr_idx, resv_attr_def, presv->ri_wattr,
			  RESV_ATR_LAST, preq->rq_perm, &pstat->brp_attr, &bad) == 0)
		return (0);
	else
		return (PBSE_NOATTR);
}

/**
 * @brief
 * 		status_resc - Build the status reply for a single resource.
 *
 * @param[in]	prd	-	pointer to resource def to status
 * @param[in]	preq	-	pointer to the batch request to service
 * @param[in]	pstathd	-	pointer to head of list to append status to
 * @param[in]	private	-	if a pbs private request, the status returns numeric
 * 							values for type and flags. Otherwise it returns strings
 *
 * @par
 * 		At the current time, the only things returned in the reply are
 *		the resource type and the flags, both as "integers".
 *
 * @return	whether the operation was successful or not
 * @retval	0	: on success
 * @retval	PBSE_SYSTEM	: on error
 */

static int
status_resc(struct resource_def *prd, struct batch_request *preq, pbs_list_head *pstathd, int private)
{
	attribute attr;
	struct brp_status *pstat;

	if (((prd->rs_flags & ATR_DFLAG_USRD) == 0) &&
	    (preq->rq_perm & (ATR_DFLAG_MGRD | ATR_DFLAG_OPRD)) == 0)
		return (PBSE_PERM);

	/* allocate status sub-structure and fill in header portion */

	pstat = (struct brp_status *) malloc(sizeof(struct brp_status));
	if (pstat == NULL)
		return (PBSE_SYSTEM);
	pstat->brp_objtype = MGR_OBJ_RSC;
	strcpy(pstat->brp_objname, prd->rs_name);
	CLEAR_LINK(pstat->brp_stlink);
	CLEAR_HEAD(pstat->brp_attr);

	/* add attributes to the status reply */
	if (private) {
		attr.at_val.at_long = prd->rs_type;
		attr.at_flags = ATR_VFLAG_SET;
		if (encode_l(&attr, &pstat->brp_attr, ATTR_RESC_TYPE, NULL, 0, NULL) == -1)
			return PBSE_SYSTEM;

		attr.at_val.at_long = prd->rs_flags;
		attr.at_flags = ATR_VFLAG_SET;
		if (encode_l(&attr, &pstat->brp_attr, ATTR_RESC_FLAG, NULL, 0, NULL) == -1)
			return PBSE_SYSTEM;
	} else {
		struct resc_type_map *p_resc_type_map;

		p_resc_type_map = find_resc_type_map_by_typev(prd->rs_type);
		if (p_resc_type_map == NULL) {
			return PBSE_SYSTEM;
		}

		attr.at_val.at_str = p_resc_type_map->rtm_rname;
		attr.at_flags = ATR_VFLAG_SET;
		if (encode_str(&attr, &pstat->brp_attr, ATTR_RESC_TYPE, NULL, 0, NULL) == -1)
			return PBSE_SYSTEM;

		attr.at_val.at_str = find_resc_flag_map(prd->rs_flags);
		attr.at_flags = ATR_VFLAG_SET;
		if (encode_str(&attr, &pstat->brp_attr, ATTR_RESC_FLAG, NULL, 0, NULL) == -1)
			return PBSE_SYSTEM;
	}
	append_link(pstathd, &pstat->brp_stlink, pstat);
	preq->rq_reply.brp_count++;
	return 0;
}

/**
 * @brief
 * 		req_stat_resc - service the Status Resource Request
 *
 *		This request processes the request for status of (information on)
 *		a set of resources
 *
 * @param[in]	preq	-	ptr to the decoded request
 */

void
req_stat_resc(struct batch_request *preq)
{
	int i;
	char *name;
	char *extend;
	struct resource_def *prd = NULL;
	struct batch_reply *preply;
	int rc = 0;
	int type;
	int private = 0;

	if (preq == NULL)
		return;
	/*
	 * first, validate the name of the requested object, either
	 * a resource name, or null for all resources
	 */

	name = preq->rq_ind.rq_status.rq_id;

	if ((*name == '\0') || (*name == '@'))
		type = 1;
	else {
		type = 0;
		prd = find_resc_def(svr_resc_def, name);
		if (prd == NULL) {
			req_reject(PBSE_UNKRESC, 0, preq);
			return;
		}
	}

	extend = preq->rq_extend;
	if (extend != NULL) {
		if (strchr(preq->rq_extend, (int) 'p'))
		      private
		= 1;
	}

	preply = &preq->rq_reply;
	preply->brp_choice = BATCH_REPLY_CHOICE_Status;
	CLEAR_HEAD(preply->brp_un.brp_status);
	preply->brp_count = 0;

	if (type == 0) { /* get status of the one named resource */
		rc = status_resc(prd, preq, &preply->brp_un.brp_status, private);

	} else { /* get status of all resources */

		i = svr_resc_size;
		prd = &svr_resc_def[0];
		while (i--) {
			/* skip the unknown resource because it would fail
			 * to pass the string encoding routine
			 */
			if (!private && (strcmp(prd->rs_name, RESOURCE_UNKNOWN) == 0)) {
				prd = prd->rs_next;
				continue;
			}
			rc = status_resc(prd, preq, &preply->brp_un.brp_status, private);
			if (rc == PBSE_PERM) {
				/* we skip resources that are disallowed to be
				 * stat'ed by this user
				 */
				rc = 0;
			}
			prd = prd->rs_next;
		}
	}
	if (rc) {
		reply_free(preply);
		req_reject(rc, bad, preq);
	} else {
		reply_send(preq);
	}
}
