/*
 * 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    accounting.c
 *
 * @brief
 * accounting.c - contains functions to record accounting information
 *
 * Functions included are:
 *	acct_open()
 *	acct_record()
 *	acct_close()
 */

#include <pbs_config.h> /* the master config generated by configure */
#include "portability.h"
#include <sys/param.h>
#include <sys/types.h>
#include <string.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "list_link.h"
#include "attribute.h"
#include "resource.h"
#include "server_limits.h"
#include "job.h"
#include "reservation.h"
#include "queue.h"
#include "pbs_nodes.h"
#include "log.h"
#include "acct.h"
#include "pbs_license.h"
#include "server.h"
#include "svrfunc.h"
#include "libutil.h"

/* Local Data */

static FILE *acctfile; /* open stream for log file */
static volatile int acct_opened = 0;
static int acct_opened_day;
static int acct_auto_switch = 0;
static char *acct_buf = 0;
static int acct_bufsize = PBS_ACCT_MAX_RCD;
static const char *do_not_emit_alter[] = {ATTR_estimated, ATTR_used, NULL};

/* Global Data */

extern char *acctlog_spacechar;
extern attribute_def job_attr_def[];
extern char *path_acct;
extern int resc_access_perm;
extern time_t time_now;
extern struct resc_sum *svr_resc_sum;
extern struct server server;
extern char *msg_job_end_stat;

/**
 * @brief
 * grow_acct_buf - called when need to grow the account buffer
 *
 * @param[out]	pb - New address in the account buffer after the reallocation
 * @param[out]	avail - Remaining size in the returned variable
 * @param[in]	need - Required extra size for reallocation
 *
 * @return      Error code
 * @retval	 0  - Success
 * @retval	-1  - Failure
 *
 * @par Side Effects:
 *     the accounting buffer (acct_buf) is grown
 *
 * @par MT-safe: No
 */
static int
grow_acct_buf(char **pb, int *avail, int need)
{
	size_t ln;
	char *new;

	ln = acct_bufsize + need + need + PBS_ACCT_LEAVE_EXTRA;
	new = realloc(acct_buf, (size_t) (ln + 1));
	if (new == NULL) {
		log_err(errno, __func__, "realloc failure");
		return (-1);
	}
	acct_buf = new;
	acct_bufsize = ln;
	ln = strlen(acct_buf);
	*pb = acct_buf + ln;
	*avail = acct_bufsize - ln;
	return 0;
}

/**
 * @brief
 * sum_resc_alloc() - sums up the consumable resources listed in
 *	the exec_vnode for accounting.  The caller is responsible
 *	for taking the sums in svr_resc_sum[] and formating the
 *	data into a buffer for logging.
 *
 * @param[in]	pjob - pointer to job
 * @param[in]	list - pbs list head
 *
 * @return      Error code
 * @retval	0			- if ok and data in svr_resc_sum[]
 * @retval  non zero	- on error and data is not valid
 *
 * @par MT-safe: No
 */

static void
sum_resc_alloc(const job *pjob, pbs_list_head *list)
{
	char *chunk;
	char *exechost;
	int i;
	int j;
	int nelem;
	char *noden;
	struct key_value_pair *pkvp;
	resource *presc;
	struct pbsnode *pnode;
	int rc;

	static attribute tmpatr;

	if ((pjob == NULL) ||
	    !(is_jattr_set(pjob, JOB_ATR_exec_vnode)))
		return;

	/* if a vnode was allocated "excl",  we need to charge all of its   */
	/* resources, but only once.   So we need to mark the vnode as seen */
	/* To do that, we first need to unmark them...			    */

	for (i = 0; i < svr_totnodes; i++)
		pbsndlist[i]->nd_svrflags &= ~NODE_ACCTED;

	exechost = get_jattr_str(pjob, JOB_ATR_exec_vnode);

	/* 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, go through the exec_vnode specified for the job, for any       */
	/* resource that matches an entry in the table, set the pointer and set flag */

	chunk = parse_plus_spec(exechost, &rc);
	if (rc != 0)
		return;
	while (chunk) {
		if (parse_node_resc(chunk, &noden, &nelem, &pkvp) == 0) {

			/* find if node is shared or excl */

			pnode = find_nodebyname(noden);
			if (pnode) {
				if ((pnode->nd_state & INUSE_JOBEXCL) == 0) {

					/* shared, record only what was requested from the vnode */

					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) {
								/* incr sum by amount requested by user */
								rc = svr_resc_sum[i].rs_def->rs_decode(&tmpatr,
												       0, 0, pkvp[j].kv_val);
								if (rc != 0)
									return;
								(void) svr_resc_sum[i].rs_def->rs_set(&svr_resc_sum[i].rs_attr, &tmpatr, INCR);

								svr_resc_sum[i].rs_set = 1;
							}
						}
					}

				} else if (!(pnode->nd_svrflags & NODE_ACCTED)) {

					/* vnode used exclusively and not already accounted, */
					/* so incr sum by amount in whole vnode              */

					pnode->nd_svrflags |= NODE_ACCTED; /* mark that it has been recorded */
					for (i = 0; svr_resc_sum[i].rs_def; ++i) {
						presc = find_resc_entry(get_nattr(pnode, ND_ATR_ResourceAvail), svr_resc_sum[i].rs_def);
						if (presc && (is_attr_set(&presc->rs_value))) {
							(void) svr_resc_sum[i].rs_def->rs_set(&svr_resc_sum[i].rs_attr, &presc->rs_value, INCR);
							svr_resc_sum[i].rs_set = 1;
						}
					}
				}
			}

		} else {
			return;
		}
		chunk = parse_plus_spec(NULL, &rc);
		if (rc != 0)
			return;
	}

	for (i = 0; svr_resc_sum[i].rs_def != NULL; ++i) {
		if (svr_resc_sum[i].rs_set) {
			(void) svr_resc_sum[i].rs_def->rs_encode(
				&svr_resc_sum[i].rs_attr,
				list,
				"resource_assigned",
				svr_resc_sum[i].rs_def->rs_name,
				ATR_ENCODE_CLIENT, NULL);
		}
	}

	return;
}

/**
 * @brief
 * cpy_quote_value - append the value to the buffer
 *	If the string contains no spaces,  it is appended as is.
 *	If the string contains spaces, and contains a ", then quote the string with ' characters,
 *	else quote the string with " characters
 *
 * @param[in,out]	pb - Source string and stores the result after appending.
 * @param[in]	value - value which needs to be appended
 *
 * @return      void
 */
static void
cpy_quote_value(char *pb, char *value)
{
	char *quotechar;

	if (strchr(value, (int) ' ') != 0) {
		if (strchr(value, (int) '"') != 0)
			quotechar = "'";
		else
			quotechar = "\"";
		(void) strcat(pb, quotechar);
		(void) strcat(pb, value);
		(void) strcat(pb, quotechar);
	} else {
		(void) strcat(pb, value);
	}
}

/* These are various printing formats used in acct_job() */
#define GRIDNAME_FMT "gridname=\"%s\" "
#define USER_FMT "user=%s "
#define GROUP_FMT "group=%s "
#define ACCOUNT_FMT "account=\"%s\" "
#define PROJECT_FMT1 "project=\"%s\" "
#define PROJECT_FMT2 "project=%s "
#define ACCOUNTING_ID_FMT "accounting_id=\"%s\" "
#define JOBNAME_FMT "jobname=%s "
#define QUEUE_FMT "queue=%s "
#define RESVNAME_FMT "resvname=%s "
#define RESVID_FMT "resvID=%s "
#define RESVJOBID_FMT "resvjobID=%s "
#define ARRAY_INDICES_FMT "array_indices=%s "
#define EXEC_HOST_FMT "exec_host=%s "
#define EXEC_VNODE_FMT "exec_vnode=%s "
#define DEPEND_FMT "depend=%s "

/* Amount of space needed in account log buffer for the ctime, qtime, etime, */
/* start attributes */
#define ACCTBUF_TIMES_NEED 72

/**
 * @brief
 * Get the resources_used job attribute
 *
 * @param[in]	pjob	- pointer to job structure
 * @param[in]	resc_used - pointer to resources used string
 * @param[in]	resc_used_size - size of resources used string
 *
 * @return	int
 * @retval	0 upon success
 * @retval	-1	if error encountered.
 *
 */
static int
get_resc_used(job *pjob, char **resc_used, int *resc_used_size)
{
	struct svrattrl *patlist = NULL;
	pbs_list_head temp_head;
	CLEAR_HEAD(temp_head);

	if (get_jattr_usr_encoded(pjob, JOB_ATR_resc_used) != NULL)
		patlist = get_jattr_usr_encoded(pjob, JOB_ATR_resc_used);
	else if (get_jattr_priv_encoded(pjob, JOB_ATR_resc_used) != NULL)
		patlist = get_jattr_priv_encoded(pjob, JOB_ATR_resc_used);
	else
		encode_resc(get_jattr(pjob, JOB_ATR_resc_used),
			    &temp_head, job_attr_def[JOB_ATR_resc_used].at_name,
			    NULL, ATR_ENCODE_CLIENT, &patlist);
	/*
	 * NOTE:
	 * Following code for constructing resources used information is same as job_obit()
	 * with minor different that to traverse patlist in this code
	 * we have to use patlist->al_sister since it is encoded information in job struct
	 * where in job_obit() we are using GET_NEXT(patlist->al_link) which is part of batch
	 * request.
	 * ji_acctrec is lost on server restart.  Recreate it here if needed.
	 */
	while (patlist) {
		/* log to accounting_logs only if there's a value */
		if (strlen(patlist->al_value) > 0) {
			if (concat_rescused_to_buffer(resc_used, resc_used_size, patlist, " ", NULL) != 0) {
				return -1;
			}
		}
		patlist = patlist->al_sister;
	}
	free_attrlist(&temp_head);
	return 0;
}

/**
 * @brief
 *	Get the value of "walltime" resource for the job's given
 *	resource index 'res'.
 *
 *
 * @param[in]	pjob	- pointer to job structure
 * @param[in]	res	- resource entity index (e.g. JOB_ATR_resource)
 *
 * @return	long
 * @retval	<n>	walltime value
 * @retval	-1	if error encountered.
 *
 */
long
get_walltime(const job *jp, int res)
{
	resource_def *rscdef;
	resource *pres;

	rscdef = &svr_resc_def[RESC_WALLTIME];
	pres = find_resc_entry(get_jattr(jp, res), rscdef);
	if (pres == NULL)
		return (-1);
	else if (!is_attr_set(&pres->rs_value))
		return (-1);
	else
		return pres->rs_value.at_val.at_long; /*wall time value*/
}

/**
 * @brief
 *	Form and write a job termination/rerun record with resource usage.
 * 	Build common data for queue/start/end job accounting record
 *
 * @par	Functionality:
 *	Used by account_jobstr() and account_jobend()
 *
 *
 * @param[in]	pjob	- pointer to job structure
 * @param[in]	type	- account record type
 * @param[in]	buf	- buffer holding the data that will be stored in
 *			  accounting logs.
 * @param[in]	len	- number of characters in 'buf' still available to
 *			  store data.
 * @return	char *
 * @retval	pointer to 'buf' containing new data.
 *
 */
static char *
acct_job(const job *pjob, int type, char *buf, int len)
{
	pbs_list_head attrlist;
	int i, k;
	int nd;
	svrattrl *pal;
	char *pb;
	int att_index;
	int len_orig;
	char save_char;
	int old_perm;

	pb = buf;
	CLEAR_HEAD(attrlist);

	/* gridname */
	if (is_jattr_set(pjob, JOB_ATR_gridname)) {
		nd = strlen(get_jattr_str(pjob, JOB_ATR_gridname)) + sizeof(GRIDNAME_FMT);
		if (nd > len)
			if (grow_acct_buf(&pb, &len, nd) == -1)
				return (pb);

		snprintf(pb, len, GRIDNAME_FMT,
			 get_jattr_str(pjob, JOB_ATR_gridname));
		i = strlen(pb);
		pb += i;
		len -= i;
	}

	/* user */
	nd = sizeof(USER_FMT) + strlen(get_jattr_str(pjob, JOB_ATR_euser));
	if (nd > len)
		if (grow_acct_buf(&pb, &len, nd) == -1)
			return (pb);
	snprintf(pb, len, USER_FMT, get_jattr_str(pjob, JOB_ATR_euser));

	i = strlen(pb);
	pb += i;
	len -= i;

	/* group */
	nd = sizeof(GROUP_FMT) + strlen(get_jattr_str(pjob, JOB_ATR_egroup));
	if (nd > len)
		if (grow_acct_buf(&pb, &len, nd) == -1)
			return (pb);

	snprintf(pb, len, GROUP_FMT, get_jattr_str(pjob, JOB_ATR_egroup));

	i = strlen(pb);
	pb += i;
	len -= i;

	/* account */
	if (is_jattr_set(pjob, JOB_ATR_account)) {
		nd = sizeof(ACCOUNT_FMT) + strlen(get_jattr_str(pjob, JOB_ATR_account));
		if (nd > len)
			if (grow_acct_buf(&pb, &len, nd) == -1)
				return (pb);

		snprintf(pb, len, ACCOUNT_FMT, get_jattr_str(pjob, JOB_ATR_account));

		i = strlen(pb);
		pb += i;
		len -= i;
	}

	/* project */
	if (is_jattr_set(pjob, JOB_ATR_project)) {
		char *projstr;

		projstr = get_jattr_str(pjob, JOB_ATR_project);
		/* using PROJECT_FMT1 if projstr needs to be quoted; otherwise, PROJECT_FMT2 */
		nd = sizeof(PROJECT_FMT1) + strlen(projstr);
		if (nd > len)
			if (grow_acct_buf(&pb, &len, nd) == -1)
				return (pb);
		if (strchr(projstr, ' ') != NULL) {
			snprintf(pb, len, PROJECT_FMT1, projstr);
		} else {
			snprintf(pb, len, PROJECT_FMT2, projstr);
		}
		i = strlen(pb);
		pb += i;
		len -= i;
	}

	/* accounting_id */
	if (is_jattr_set(pjob, JOB_ATR_acct_id)) {
		nd = sizeof(ACCOUNTING_ID_FMT) + strlen(get_jattr_str(pjob, JOB_ATR_acct_id));
		if (nd > len)
			if (grow_acct_buf(&pb, &len, nd) == -1)
				return (pb);
		snprintf(pb, len, ACCOUNTING_ID_FMT, get_jattr_str(pjob, JOB_ATR_acct_id));
		i = strlen(pb);
		pb += i;
		len -= i;
	}

	/* job name */
	nd = sizeof(JOBNAME_FMT) + strlen(get_jattr_str(pjob, JOB_ATR_jobname));
	if (nd > len)
		if (grow_acct_buf(&pb, &len, nd) == -1)
			return (pb);
	snprintf(pb, len, JOBNAME_FMT, get_jattr_str(pjob, JOB_ATR_jobname));
	i = strlen(pb);
	pb += i;
	len -= i;

	/* queue name */
	nd = sizeof(QUEUE_FMT) + strlen(pjob->ji_qhdr->qu_qs.qu_name);
	if (nd > len)
		if (grow_acct_buf(&pb, &len, nd) == -1)
			return (pb);
	snprintf(pb, len, QUEUE_FMT, pjob->ji_qhdr->qu_qs.qu_name);
	i = strlen(pb);
	pb += i;
	len -= i;

	if (pjob->ji_myResv) {
		nd = sizeof(RESVID_FMT) + strlen(pjob->ji_myResv->ri_qs.ri_resvID);
		if (is_rattr_set(pjob->ji_myResv, RESV_ATR_resv_name))
			nd += sizeof(RESVNAME_FMT) + strlen(get_rattr_str(pjob->ji_myResv, RESV_ATR_resv_name));
		if (nd > len)
			if (grow_acct_buf(&pb, &len, nd) == -1)
				return (pb);
		/* reservation name */
		if (is_rattr_set(pjob->ji_myResv, RESV_ATR_resv_name)) {
			snprintf(pb, len, RESVNAME_FMT, get_rattr_str(pjob->ji_myResv, RESV_ATR_resv_name));
			i = strlen(pb);
			pb += i;
			len -= i;
		}

		/* reservation ID */
		snprintf(pb, len, RESVID_FMT, pjob->ji_myResv->ri_qs.ri_resvID);
		i = strlen(pb);
		pb += i;
		len -= i;
	}

	/* insure space for all *times */
	nd = ACCTBUF_TIMES_NEED;
	if (nd > len)
		if (grow_acct_buf(&pb, &len, nd) == -1)
			return (pb);

	/* create time */
	sprintf(pb, "ctime=%ld ", get_jattr_long(pjob, JOB_ATR_ctime));
	i = strlen(pb);
	pb += i;
	len -= i;

	/* queued time */
	sprintf(pb, "qtime=%ld ", get_jattr_long(pjob, JOB_ATR_qtime));
	i = strlen(pb);
	pb += i;
	len -= i;

	/* eligible time, how long ready to run */
	sprintf(pb, "etime=%ld ", get_jattr_long(pjob, JOB_ATR_etime));
	i = strlen(pb);
	pb += i;
	len -= i;

	if (type != PBS_ACCT_QUEUE) {
		/* start time */
		sprintf(pb, "start=%ld ", (long) pjob->ji_qs.ji_stime);
		i = strlen(pb);
		pb += i;
		len -= i;
	} else if (is_jattr_set(pjob, JOB_ATR_depend)) {
		pbs_list_head phead;
		svrattrl *svrattrl_list = NULL;
		CLEAR_HEAD(phead);
		job_attr_def[JOB_ATR_depend].at_encode(get_jattr(pjob, JOB_ATR_depend),
						       &phead, job_attr_def[JOB_ATR_depend].at_name, NULL, ATR_ENCODE_CLIENT, &svrattrl_list);
		if (svrattrl_list != NULL) {
			nd = sizeof(DEPEND_FMT) + strlen(svrattrl_list->al_value);
			if (nd > len)
				if (grow_acct_buf(&pb, &len, nd) == -1)
					return (pb);
			snprintf(pb, len, DEPEND_FMT, svrattrl_list->al_value);
			i = strlen(pb);
			pb += i;
			len -= i;
			free_svrattrl(svrattrl_list);
		}
	}

	if ((is_jattr_set(pjob, JOB_ATR_array_indices_submitted)) &&
	    (check_job_state(pjob, JOB_STATE_LTR_BEGUN) || type == PBS_ACCT_QUEUE)) {

		/* for an Array Job in Begun state,  record index range */

		nd = sizeof(ARRAY_INDICES_FMT) + strlen(get_jattr_str(pjob, JOB_ATR_array_indices_submitted));
		if (nd > len)
			if (grow_acct_buf(&pb, &len, nd) == -1)
				return (pb);
		snprintf(pb, len, ARRAY_INDICES_FMT, get_jattr_str(pjob, JOB_ATR_array_indices_submitted));
		i = strlen(pb);
		pb += i;
		len -= i;

	} else {

		/* regular job */
		if ((type == PBS_ACCT_END) &&
		    (is_jattr_set(pjob, JOB_ATR_exec_host_orig)))
			att_index = JOB_ATR_exec_host_orig;
		else
			att_index = JOB_ATR_exec_host;

		if (is_jattr_set(pjob, att_index)) {
			/* execution host list, may be loooong */
			nd = sizeof(EXEC_HOST_FMT) + strlen(get_jattr_str(pjob, att_index));
			if (nd > len)
				if (grow_acct_buf(&pb, &len, nd) == -1)
					return (pb);
			snprintf(pb, len, EXEC_HOST_FMT, get_jattr_str(pjob, att_index));
			i = strlen(pb);
			pb += i;
			len -= i;
		}
		if ((type == PBS_ACCT_END) &&
		    (is_jattr_set(pjob, JOB_ATR_exec_vnode_orig)))
			att_index = JOB_ATR_exec_vnode_orig;
		else
			att_index = JOB_ATR_exec_vnode;

		if (is_jattr_set(pjob, att_index)) {
			/* execution vnode list, will be even longer */
			nd = sizeof(EXEC_VNODE_FMT) + strlen(get_jattr_str(pjob, att_index));
			if (nd > len)
				if (grow_acct_buf(&pb, &len, nd) == -1)
					return (pb);
			snprintf(pb, len, EXEC_VNODE_FMT, get_jattr_str(pjob, att_index));
			i = strlen(pb);
			pb += i;
			len -= i;
		}
	}

	/* now encode the job's resource_list attribute */
	if ((type == PBS_ACCT_END) &&
	    (is_jattr_set(pjob, JOB_ATR_resource_orig))) {
		att_index = JOB_ATR_resource_orig;
		len_orig = 5; /* length of "_orig" */
	} else {
		att_index = JOB_ATR_resource;
		len_orig = 0;
	}

	old_perm = resc_access_perm;
	resc_access_perm = READ_ONLY;
	(void) job_attr_def[att_index].at_encode(
		get_jattr(pjob, att_index),
		&attrlist,
		job_attr_def[att_index].at_name,
		NULL,
		ATR_ENCODE_CLIENT, NULL);
	resc_access_perm = old_perm;

	nd = 0; /* compute total size needed in buf */
	pal = GET_NEXT(attrlist);
	while (pal != NULL) {
		/* +5 in count is for '=', ' ', start and end quotes, and \0 */
		nd += strlen(pal->al_name) + strlen(pal->al_value) + 5;
		if (pal->al_resc)
			nd += 1 + strlen(pal->al_resc);
		pal = GET_NEXT(pal->al_link);
	}
	if (nd > len)
		if (grow_acct_buf(&pb, &len, nd) == -1)
			return (pb);

	while ((pal = GET_NEXT(attrlist)) != NULL) {
		/* strip off the '_orig' suffix */
		if (len_orig > 0) {
			k = strlen(pal->al_name);
			if (k > len_orig) {
				save_char = pal->al_name[k - len_orig];
				pal->al_name[k - len_orig] = '\0';
			}
		}
		(void) strcat(pb, pal->al_name);
		if (len_orig > 0) {
			if (k > len_orig) {
				pal->al_name[k - len_orig] = save_char;
			}
		}
		if (pal->al_resc) {
			(void) strcat(pb, ".");
			(void) strcat(pb, pal->al_resc);
		}
		(void) strcat(pb, "=");
		cpy_quote_value(pb, pal->al_value);
		(void) strcat(pb, " ");
		delete_link(&pal->al_link);
		(void) free(pal);
		pb += strlen(pb);
	}
	return (pb);
}

/**
 * @brief
 * acct_resv - build data for start/end reservation  accounting record
 *
 * @par	Functionality:
 *	Used by account_resvstr() and account_resvend()
 *
 * @param[in]	presv - pointer to reservation structure
 * @param[in]	buf	- buffer holding the data that will be stored in
 *			  accounting logs.
 * @param[in]	len	- number of characters in 'buf' still available to
 *			  store data.
 * @return	char *
 * @retval	pointer to 'buf' containing new data.
 */
static char *
acct_resv(resc_resv *presv, char *buf, int len)
{
	pbs_list_head attrlist; /*retrieved resources list put here*/
	int i;
	svrattrl *pal;
	char *pb;
	int old_perm;

	pb = buf;
	CLEAR_HEAD(attrlist);

	/* owner */
	i = 8 + strlen(get_rattr_str(presv, RESV_ATR_resv_owner));
	if (i > len)
		if (grow_acct_buf(&pb, &len, i) == -1)
			return (pb);
	(void) sprintf(pb, "owner=%s ", get_rattr_str(presv, RESV_ATR_resv_owner));
	i = strlen(pb);
	pb += i;
	len -= i;

	/* name */
	if (is_rattr_set(presv, RESV_ATR_resv_name)) {
		i = 7 + strlen(get_rattr_str(presv, RESV_ATR_resv_name));
		if (i > len)
			if (grow_acct_buf(&pb, &len, i) == -1)
				return (pb);
		(void) sprintf(pb, "name=%s ", get_rattr_str(presv, RESV_ATR_resv_name));
		i = strlen(pb);
		pb += i;
		len -= i;
	}

	/* account */
	if (is_rattr_set(presv, RESV_ATR_account)) {
		i = 10 + strlen(get_rattr_str(presv, RESV_ATR_account));
		if (i > len)
			if (grow_acct_buf(&pb, &len, i) == -1)
				return (pb);
		(void) sprintf(pb, "account=%s ", get_rattr_str(presv, RESV_ATR_account));
		i = strlen(pb);
		pb += i;
		len -= i;
	}

	/* queue name */
	i = 23;
	if (i > len)
		if (grow_acct_buf(&pb, &len, i) == -1)
			return (pb);
	if (presv->ri_qp != NULL)
		sprintf(pb, "queue=%s ", presv->ri_qp->qu_qs.qu_name);

	i = strlen(pb);
	pb += i;
	len -= i;

	/* allow space for all *times */
	i = 90;
	if (i > len)
		if (grow_acct_buf(&pb, &len, i) == -1)
			return (pb);

	/* create time */
	(void) sprintf(pb, "ctime=%ld ", get_rattr_long(presv, RESV_ATR_ctime));
	i = strlen(pb);
	pb += i;
	len -= i;

	/* reservation start time */
	(void) sprintf(pb, "start=%ld ", (long) presv->ri_qs.ri_stime);
	i = strlen(pb);
	pb += i;
	len -= i;

	/* reservation end time */
	(void) sprintf(pb, "end=%ld ", (long) presv->ri_qs.ri_etime);
	i = strlen(pb);
	pb += i;
	len -= i;

	/* reservation duration time */
	(void) sprintf(pb, "duration=%ld ", (long) presv->ri_qs.ri_duration);
	i = strlen(pb);
	pb += i;
	len -= i;

	/* nodes string may be loooong */
	if (is_rattr_set(presv, RESV_ATR_resv_nodes)) {
		i = 8 + strlen(get_rattr_str(presv, RESV_ATR_resv_nodes));
		if (i > len)
			if (grow_acct_buf(&pb, &len, i) == -1)
				return (pb);
		(void) sprintf(pb, "nodes=%s ", get_rattr_str(presv, RESV_ATR_resv_nodes));
		i = strlen(pb);
		pb += i;
		len -= i;
	}

	/* now encode any user, group or host ACL */

	old_perm = resc_access_perm;
	resc_access_perm = READ_ONLY;
	(void) resv_attr_def[RESV_ATR_auth_u].at_encode(
		get_rattr(presv, RESV_ATR_auth_u),
		&attrlist,
		resv_attr_def[RESV_ATR_auth_u].at_name,
		NULL,
		ATR_ENCODE_CLIENT, NULL);

	(void) resv_attr_def[RESV_ATR_auth_g].at_encode(
		get_rattr(presv, RESV_ATR_auth_g),
		&attrlist,
		resv_attr_def[RESV_ATR_auth_g].at_name,
		NULL,
		ATR_ENCODE_CLIENT, NULL);

	(void) resv_attr_def[RESV_ATR_auth_h].at_encode(
		get_rattr(presv, RESV_ATR_auth_h),
		&attrlist,
		resv_attr_def[RESV_ATR_auth_h].at_name,
		NULL,
		ATR_ENCODE_CLIENT, NULL);

	/* now encode the reservation's resource_list attribute */

	resc_access_perm = READ_ONLY;
	(void) resv_attr_def[RESV_ATR_resource].at_encode(
		get_rattr(presv, RESV_ATR_resource),
		&attrlist,
		resv_attr_def[RESV_ATR_resource].at_name,
		NULL,
		ATR_ENCODE_CLIENT, NULL);
	resc_access_perm = old_perm;

	/* compute space need for the encode attributes */

	i = 0;
	pal = GET_NEXT(attrlist);
	while (pal != NULL) {
		/* +5 in count is for '=', ' ', start and end quotes, and \0 */
		i += strlen(pal->al_name) + strlen(pal->al_value) + 5;
		if (pal->al_resc)
			i += 1 + strlen(pal->al_resc);
		pal = GET_NEXT(pal->al_link);
	}
	if (i > len)
		if (grow_acct_buf(&pb, &len, i) == -1)
			return (pb);

	/*write encoded attrlist values into buffer being developed*/

	while ((pal = GET_NEXT(attrlist)) != NULL) {
		(void) strcat(pb, pal->al_name);
		if (pal->al_resc) {
			(void) strcat(pb, ".");
			(void) strcat(pb, pal->al_resc);
		}
		(void) strcat(pb, "=");
		cpy_quote_value(pb, pal->al_value);
		(void) strcat(pb, " ");
		delete_link(&pal->al_link);
		(void) free(pal);
		pb += strlen(pb);
	}
	return (pb);
}

/**
 * @brief
 * acct_open() - open the acct file for append.
 * Opens a (new) acct file.
 * If a acct file is already open, and the new file is successfully opened,
 * the old file is closed.  Otherwise the old file is left open.
 *
 * @param[in]	filename - abs pathname or NULL
 *
 * @return      Error code
 * @retval	 0  - Success
 * @retval	-1  - Failure
 */
int
acct_open(char *filename)
{
	char filen[_POSIX_PATH_MAX];
	char logmsg[_POSIX_PATH_MAX + 80];
	FILE *newacct;
	time_t now;
	struct tm *ptm;

	if (acct_buf == NULL) { /* malloc buffer space */
		acct_buf = (char *) malloc(acct_bufsize + 1);
		if (acct_buf == NULL)
			return (-1);
	}

	if (filename == NULL) { /* go with default */
		now = time(0);
		ptm = localtime(&now);
		(void) sprintf(filen, "%s%04d%02d%02d",
			       path_acct,
			       ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday);
		filename = filen;
		acct_auto_switch = 1;
		acct_opened_day = ptm->tm_yday;
	} else if (*filename == '\0') { /* a null name is not an error */
		return (0);		/* turns off account logging.  */
	} else if (*filename != '/') {
		return (-1); /* not absolute */
	}
	if ((newacct = fopen(filename, "a")) == NULL) {
		log_err(errno, "acct_open", filename);
		return (-1);
	}

	(void) setvbuf(newacct, NULL, _IOLBF, 0); /* set line buffering */

	if (acct_opened > 0) /* if acct was open, close it */
		(void) fclose(acctfile);

	acctfile = newacct;
	acct_opened = 1; /* note that file is open */
	(void) sprintf(logmsg, "Account file %s opened", filename);
	log_event(PBSEVENT_SYSTEM, PBS_EVENTCLASS_SERVER, LOG_INFO,
		  "Act", logmsg);

	return (0);
}

/**
 * @brief
 * acct_close - close the current open log file
 *
 * @return	void
 */
void
acct_close()
{
	if (acct_opened == 1) {
		(void) fclose(acctfile);
		acct_opened = 0;
	}
}

/**
 * @brief
 * write_account_record - write basic accounting record
 *
 * @param[in]	acctype - accounting record type
 * @param[in]	id - accounting record id
 * @param[in,out]	text - text to log, may be null
 *
 * @return	void
 */
void
write_account_record(int acctype, const char *id, char *text)
{
	struct tm *ptm;

	if (acct_opened == 0)
		return; /* file not open, don't bother */

	ptm = localtime(&time_now);

	/* Do we need to switch files */

	if (acct_auto_switch && (acct_opened_day != ptm->tm_yday)) {
		acct_close();
		acct_open(NULL);
	}
	if (text == NULL)
		text = "";

	(void) fprintf(acctfile,
		       "%02d/%02d/%04d %02d:%02d:%02d;%c;%s;%s\n",
		       ptm->tm_mon + 1, ptm->tm_mday, ptm->tm_year + 1900,
		       ptm->tm_hour, ptm->tm_min, ptm->tm_sec,
		       (char) acctype, id, text);
}

/**
 * @brief
 *account_record - basic job related accounting record
 *
 * @param[in]	acctype - accounting record type
 * @param[in]	pjob - pointer to job
 * @param[in]	text - text to log, may be null
 *
 * @return	void
 */
void
account_record(int acctype, const job *pjob, char *text)
{
	write_account_record(acctype, pjob->ji_qs.ji_jobid, text);
}

/**
 * @brief
 * account_recordResv - write basic accounting record
 *
 * @param[in]	acctype - accounting record type
 * @param[in]	presv - pointer to reservation structure
 * @param[in]	text - text to log, may be null
 *
 * @return	void
 */
void
account_recordResv(int acctype, resc_resv *presv, char *text)
{
	write_account_record(acctype, presv->ri_qs.ri_resvID, text);
}

/**
 * @brief
 *	Form and write a record that contains basic job information and the
 *	assigned consumable resource values for the job.
 *
 * @par	Functionality:
 *	Takes various information from the job structure, start time, owner,
 *	Resource_List, etc., and the resource assigned information (based on
 *	job's exec_vnode value) and formats the record type requested.
 *	Currently, this is used for 'R' (Job run/started).
 *
 *	The record is then written to the accounting log.
 *
 * @see:
 *	complete_running()
 *
 * @param[in]	pjob	- pointer to job structure
 * @param[in]	type	- record type, PBS_ACCT_RUN ('R'),
 *					PBS_ACCT_NEXT ('c').
 * @return	void
 *
 * @par	MT-safe: No - uses a global buffer, "acct_buf".
 */
void
account_jobstr(const job *pjob, int type)
{
	pbs_list_head attrlist;
	int nd;
	int len;
	svrattrl *pal;
	char *pb;

	CLEAR_HEAD(attrlist);

	/* pack in general information about the job */

	acct_job(pjob, type, acct_buf, acct_bufsize);
	acct_buf[acct_bufsize] = '\0';

	if (type != PBS_ACCT_QUEUE) {

		nd = strlen(acct_buf);
		pb = acct_buf + nd;
		len = acct_bufsize - nd;

		sum_resc_alloc(pjob, &attrlist);

		nd = 0; /* compute total size needed in buf */
		pal = GET_NEXT(attrlist);
		while (pal != NULL) {
			/* +5 in count is for '=', ' ', start and end quotes, and \0 */
			nd += strlen(pal->al_name) + strlen(pal->al_value) + 5;
			if (pal->al_resc)
				nd += 1 + strlen(pal->al_resc);
			pal = GET_NEXT(pal->al_link);
		}
		if ((nd <= len) ||
		    (grow_acct_buf(&pb, &len, nd) == 0)) {

			/* have room in buffer, so copy in resources_assigned */

			while ((pal = GET_NEXT(attrlist)) != NULL) {
				strcat(pb, pal->al_name);
				if (pal->al_resc) {
					strcat(pb, ".");
					strcat(pb, pal->al_resc);
				}
				strcat(pb, "=");
				cpy_quote_value(pb, pal->al_value);
				strcat(pb, " ");
				delete_link(&pal->al_link);
				free(pal);
				pb += strlen(pb);
			}
		}
	}
	account_record(type, pjob, acct_buf);
}

/**
 * @brief
 * account_resvstart - write a "reservation start" record
 *
 * @param[in]	presv - pointer to reservation structure
 *
 * @return	void
 */
void
account_resvstart(resc_resv *presv)
{
	/* pack in general information about the reservation */

	(void) acct_resv(presv, acct_buf, acct_bufsize);
	acct_buf[acct_bufsize] = '\0';
	account_recordResv(PBS_ACCT_BR, presv, acct_buf);
}

/**
 * @brief
 *	Form and write a job termination/rerun record with resource usage.
 *
 * @par	Functionality:
 *	Takes various information from the job structure, start time, owner,
 *	Resource_List, etc., and the resource usage information (see
 *	ji_acctrec) if present and formats the record type requested.
 *	Currently, this is used for 'E' and 'R' records.  The record is then
 *	written to the accounting log.
 *
 * @see:
 *	on_job_exit() and on_job_rerun() as well as force_reque().
 *
 * @param[in]	pjob	- pointer to job structure
 * @param[in]	used	- resource usage information from Mom,  this is a string
 *			  consisting of space separated keyword=value pairs,
 *			  may be null pointer
 * @param[in]	type	- record type, PBS_ACCT_END ('E') or
 *			  PBS_ACCT_RERUN ('R')
 * @return	void
 *
 * @par	MT-safe: No - uses a global buffer, "acct_buf".
 *
 */
void
account_jobend(job *pjob, char *used, int type)
{
	int i = 0;
	int len = 0;
	char *pb = NULL;
	char *resc_used;
	int resc_used_size = 0;

	/* pack in general information about the job */

	pb = acct_job(pjob, type, acct_buf, acct_bufsize);
	len = acct_bufsize - (pb - acct_buf);

	/*
	 * for each keyword=value pair added, the following steps should be
	 * followed:
	 * a. calculate (or over-estimate) the size of the date to be added
	 * b. check that there is sufficient room in the buffer, "len" is the
	 *    current unused amount.
	 * c. If necessary, grow the buffer by calling grow_acct_buf().
	 *    If this function fails,  just write out what we already have.
	 * d. Append the new datum to the buffer at "pb".  Each new item should
	 *    have a single leading space.  The variable "pb" is maintained
	 *    to point to the end to save "strcat" time.
	 * e. Advance "pb" by the length of the datum added and decrement
	 *    "len" by the same amount.
	 */

	/* session */
	i = 30;
	if (i > len)
		if (grow_acct_buf(&pb, &len, i) == -1)
			goto writeit;
	(void) sprintf(pb, "session=%ld",
		       get_jattr_long(pjob, JOB_ATR_session_id));
	i = strlen(pb);
	pb += i;
	len -= i;

	/* Alternate id if present */

	if (is_jattr_set(pjob, JOB_ATR_altid)) {
		i = 9 + strlen(get_jattr_str(pjob, JOB_ATR_altid));
		if (i > len)
			if (grow_acct_buf(&pb, &len, i) == -1)
				goto writeit;

		(void) sprintf(pb, " alt_id=%s",
			       get_jattr_str(pjob, JOB_ATR_altid));

		i = strlen(pb);
		pb += i;
		len -= i;
	}

	/* add the execution ended time */
	i = 18;
	if (i > len)
		if (grow_acct_buf(&pb, &len, i) == -1)
			goto writeit;
	(void) sprintf(pb, " end=%ld", (long) time_now);
	i = strlen(pb);
	pb += i;
	len -= i;

	/* finally add on resources used from req_jobobit() */
	if (type == PBS_ACCT_END || type == PBS_ACCT_RERUN) {
		if ((used == NULL && pjob->ji_acctrec == NULL) || (used != NULL && strstr(used, "resources_used") == NULL)) {
			/* If pbs_server is restarted during the end of job processing then used maybe NULL.
			 * So we try to derive the resource usage information from resources_used attribute of
			 * the job and then reconstruct the resources usage information into resc_used buffer.
			 */

			/* Allocate initial space for resc_used.  Future space will be allocated by pbs_strcat(). */
			resc_used = malloc(RESC_USED_BUF_SIZE);
			if (resc_used == NULL)
				goto writeit;
			resc_used_size = RESC_USED_BUF_SIZE;

			/* strlen(msg_job_end_stat) == 12 characters plus a number.  This should be plenty big */
			(void) snprintf(resc_used, resc_used_size, msg_job_end_stat,
					pjob->ji_qs.ji_un.ji_exect.ji_exitstat);

			if (get_resc_used(pjob, &resc_used, &resc_used_size) == -1) {
				free(resc_used);
				goto writeit;
			}

			used = resc_used;
			free(pjob->ji_acctrec);
			pjob->ji_acctrec = used;
		}
	}

	if (used != NULL) {
		i = strlen(used) + 1;
		if (i > len)
			if (grow_acct_buf(&pb, &len, i) == -1)
				goto writeit;
		(void) strcat(pb, " ");
		(void) strcat(pb, used);
		i = strlen(pb);
		pb += i;
		len -= i;
	}

	/* Add eligible_time */
	if (get_sattr_long(SVR_ATR_EligibleTimeEnable) == 1) {
		char timebuf[TIMEBUF_SIZE] = {0};
		i = 26; /* max size for " eligible_time=<value>" */
		if (i > len)
			if (grow_acct_buf(&pb, &len, i) == -1)
				goto writeit;

		convert_duration_to_str(get_jattr_long(pjob, JOB_ATR_eligible_time), timebuf, TIMEBUF_SIZE);
		(void) sprintf(pb, " eligible_time=%s", timebuf);
		i = strlen(pb);
		pb += i;
		len -= i;
	}

	/* Add in run count */

	i = 34; /* sort of max size for "run_count=<value>" */
	if (i > len)
		if (grow_acct_buf(&pb, &len, i) == -1)
			goto writeit;
	sprintf(pb, " run_count=%ld",
		get_jattr_long(pjob, JOB_ATR_runcount));
	/* if any more is added after this point, */
	/* don't forget to reset pb and len first */

	/* done creating record,  now write it out */

writeit:
	acct_buf[acct_bufsize - 1] = '\0';
	account_record(type, pjob, acct_buf);
}
/**
 * @brief
 *	Log the license used.
 *
 * @see
 *	call_log_license
 *
 * @param[in]   pu	-	pointer to licenses_high_use
 *
 * @return      void
 */
void
log_licenses(pbs_licenses_high_use *pu)
{
	sprintf(acct_buf, "floating license hour:%d day:%d month:%d max:%d",
		pu->lu_max_hr,
		pu->lu_max_day,
		pu->lu_max_month,
		pu->lu_max_forever);
	write_account_record(PBS_ACCT_LIC, "license", acct_buf);
}

/**
 * @brief
 *	Builds job accounting record.
 *
 * @par Functionality:
 *      This function builds basic job data to be printed with provisioning
 *	record.
 *
 * @see
 *	set_job_ProvAcctRcd
 *
 * @param[in]   pjob	-	pointer to job
 * @param[in]   buf	-	pointer to buffer to contain job related data
 * @param[in]   len	-	length of buffer
 *
 * @return      pointer to string
 * @retval       char* : job accounting info
 *
 * @par Side Effects:
 *     the accounting buffer (acct_buf) is grown
 *
 * @par MT-safe: No
 *
 */
static char *
common_acct_job(job *pjob, char *buf, int len)
{
	int i;
	int nd;
	char *pb;

	pb = buf;

	/* user */
	nd = 7 + strlen(get_jattr_str(pjob, JOB_ATR_euser));
	if (nd > len)
		if (grow_acct_buf(&pb, &len, nd) == -1)
			return (pb);

	sprintf(pb, "user=%s ", get_jattr_str(pjob, JOB_ATR_euser));

	i = strlen(pb);
	pb += i;
	len -= i;

	/* group */
	nd = 8 + strlen(get_jattr_str(pjob, JOB_ATR_egroup));
	if (nd > len)
		if (grow_acct_buf(&pb, &len, nd) == -1)
			return (pb);

	sprintf(pb, "group=%s ", get_jattr_str(pjob, JOB_ATR_egroup));

	i = strlen(pb);
	pb += i;
	len -= i;

	/* job name */
	nd = 10 + strlen(get_jattr_str(pjob, JOB_ATR_jobname));
	if (nd > len)
		if (grow_acct_buf(&pb, &len, nd) == -1)
			return (pb);
	sprintf(pb, "jobname=%s ", get_jattr_str(pjob, JOB_ATR_jobname));
	i = strlen(pb);
	pb += i;
	len -= i;

	/* queue name */
	nd = 8 + strlen(pjob->ji_qhdr->qu_qs.qu_name);
	if (nd > len)
		if (grow_acct_buf(&pb, &len, nd) == -1)
			return (pb);
	sprintf(pb, "queue=%s ", pjob->ji_qhdr->qu_qs.qu_name);

	return (pb);
}

/**
 * @brief
 *	Creates start/end provisioning record.
 *
 * @par Functionality:
 *      This function creates start/end provisioning record for a single job.
 *
 * @see
 *
 * @param[in]   pjob	-	pointer to job
 * @param[in]   time_se	-	start or end time stamp depending upon value of type
 * @param[in]   type	-	integer value to select type of record,
 *				1 = start, 2 = end
 *
 * @return	void
 *
 * @par Side Effects:
 *      The accounting buffer (acct_buf) is grown
 *
 * @par MT-safe: No
 *
 */
void
set_job_ProvAcctRcd(job *pjob, long time_se, int type)
{
	int nd;
	int len;
	char *pb;
	int i;

	/* pack in general information about the job */

	(void) common_acct_job(pjob, acct_buf, acct_bufsize);
	acct_buf[acct_bufsize - 1] = '\0';

	nd = strlen(acct_buf);
	pb = acct_buf + nd;
	len = acct_bufsize - nd;

	/* node list that were provisioned */
#ifdef NAS /* localmod 136 */
	if (get_jattr_str(pjob, JOB_ATR_prov_vnode) == NULL) {
		char logmsg[1024];
		sprintf(logmsg, "prov_vnode is NULL for job %s", get_jattr_str(pjob, JOB_ATR_hashname));
		log_event(PBSEVENT_SYSTEM, PBS_EVENTCLASS_SERVER, LOG_INFO, "Bug", logmsg);

		return;
	}
#endif /* localmod 136 */
	nd = 18 + strlen(get_jattr_str(pjob, JOB_ATR_prov_vnode));
	if (nd > len)
		if (grow_acct_buf(&pb, &len, nd) == -1)
			return;
	(void) sprintf(pb, "provision_vnode=%s ",
		       get_jattr_str(pjob, JOB_ATR_prov_vnode));
	i = strlen(pb);
	pb += i;
	len -= i;

	switch (type) {
		case PROVISIONING_STARTED:
			nd = 45;
			if (nd > len)
				if (grow_acct_buf(&pb, &len, nd) == -1)
					return;
			(void) sprintf(pb, "provision_event=START start_time=%ld", time_se);
			acct_buf[acct_bufsize - 1] = '\0';
			account_record(PBS_ACCT_PROV_START, pjob, acct_buf);
			break;
		case PROVISIONING_SUCCESS:
		case PROVISIONING_FAILURE:
			nd = 56;
			if (nd > len)
				if (grow_acct_buf(&pb, &len, nd) == -1)
					return;

			(void) sprintf(pb, "provision_event=END status=%s end_time=%ld",
				       (type == 2) ? "SUCCESS" : "FAILURE", time_se);
			acct_buf[acct_bufsize - 1] = '\0';
			account_record(PBS_ACCT_PROV_END, pjob, acct_buf);
			break;
	}
}

/**
 * @brief
 * 	Build common data for update job accounting record
 *
 * @par	Functionality:
 *	Used by account_job_update()
 *
 *
 * @param[in]	pjob	- pointer to job structure
 * @param[in]	type	- type of accounting record: PBS_ACCT_UPDATE,
 *			  PBS_ACCT_LAST.
 * @param[in]	buf	- buffer holding the data that will be stored in
 *			  accounting logs.
 * @param[in]	len	- number of characters in 'buf' still available to
 *			  store data.
 * @return	char *
 * @retval	pointer to 'buf' containing new data.
 *
 */
static char *
build_common_data_for_job_update(const job *pjob, int type, char *buf, int len)
{
	pbs_list_head attrlist;
	int ct;
	int nd;
	svrattrl *pal;
	char *pb;
	int k, len_acct, att_index;
	char save_char;
	int old_perm;

	pb = buf;
	CLEAR_HEAD(attrlist);

	/* gridname */
	if (is_jattr_set(pjob, JOB_ATR_gridname)) {
		nd = strlen(get_jattr_str(pjob, JOB_ATR_gridname)) + sizeof(GRIDNAME_FMT);
		if (nd > len)
			if (grow_acct_buf(&pb, &len, nd) == -1)
				return (pb);

		(void) snprintf(pb, len, GRIDNAME_FMT,
				get_jattr_str(pjob, JOB_ATR_gridname));
		ct = strlen(pb);
		pb += ct;
		len -= ct;
	}

	/* user */
	nd = sizeof(USER_FMT) + strlen(get_jattr_str(pjob, JOB_ATR_euser));
	if (nd > len)
		if (grow_acct_buf(&pb, &len, nd) == -1)
			return (pb);

	(void) snprintf(pb, len, USER_FMT,
			get_jattr_str(pjob, JOB_ATR_euser));

	ct = strlen(pb);
	pb += ct;
	len -= ct;

	/* group */
	nd = sizeof(GROUP_FMT) + strlen(get_jattr_str(pjob, JOB_ATR_egroup));
	if (nd > len)
		if (grow_acct_buf(&pb, &len, nd) == -1)
			return (pb);

	(void) snprintf(pb, len, GROUP_FMT,
			get_jattr_str(pjob, JOB_ATR_egroup));

	ct = strlen(pb);
	pb += ct;
	len -= ct;

	/* account */
	if (is_jattr_set(pjob, JOB_ATR_account)) {
		nd = sizeof(ACCOUNT_FMT) + strlen(get_jattr_str(pjob, JOB_ATR_account));
		if (nd > len)
			if (grow_acct_buf(&pb, &len, nd) == -1)
				return (pb);

		(void) snprintf(pb, len, ACCOUNT_FMT,
				get_jattr_str(pjob, JOB_ATR_account));

		ct = strlen(pb);
		pb += ct;
		len -= ct;
	}

	/* project */
	if (is_jattr_set(pjob, JOB_ATR_project)) {
		char *projstr;

		projstr = get_jattr_str(pjob, JOB_ATR_project);
		/* using PROJECT_FMT1 if projstr needs to be quoted; otherwise, PROJECT_FMT2 */
		nd = sizeof(PROJECT_FMT1) + strlen(projstr);
		if (nd > len)
			if (grow_acct_buf(&pb, &len, nd) == -1)
				return (pb);
		if (strchr(projstr, ' ') != NULL) {
			(void) snprintf(pb, len, PROJECT_FMT1, projstr);
		} else {
			(void) snprintf(pb, len, PROJECT_FMT2, projstr);
		}
		ct = strlen(pb);
		pb += ct;
		len -= ct;
	}

	/* accounting_id */
	if (is_jattr_set(pjob, JOB_ATR_acct_id)) {
		nd = sizeof(ACCOUNTING_ID_FMT) + strlen(get_jattr_str(pjob, JOB_ATR_acct_id));
		if (nd > len)
			if (grow_acct_buf(&pb, &len, nd) == -1)
				return (pb);
		(void) snprintf(pb, len, ACCOUNTING_ID_FMT,
				get_jattr_str(pjob, JOB_ATR_acct_id));
		ct = strlen(pb);
		pb += ct;
		len -= ct;
	}

	/* job name */
	nd = sizeof(JOBNAME_FMT) + strlen(get_jattr_str(pjob, JOB_ATR_jobname));
	if (nd > len)
		if (grow_acct_buf(&pb, &len, nd) == -1)
			return (pb);
	(void) snprintf(pb, len, JOBNAME_FMT,
			get_jattr_str(pjob, JOB_ATR_jobname));
	ct = strlen(pb);
	pb += ct;
	len -= ct;

	/* queue name */
	nd = sizeof(QUEUE_FMT) + strlen(pjob->ji_qhdr->qu_qs.qu_name);
	if (nd > len)
		if (grow_acct_buf(&pb, &len, nd) == -1)
			return (pb);
	(void) snprintf(pb, len, QUEUE_FMT, pjob->ji_qhdr->qu_qs.qu_name);
	ct = strlen(pb);
	pb += ct;
	len -= ct;

	if (pjob->ji_myResv) {
		nd = sizeof(RESVID_FMT) + strlen(pjob->ji_myResv->ri_qs.ri_resvID);
		if (is_rattr_set(pjob->ji_myResv, RESV_ATR_resv_name))
			nd += sizeof(RESVNAME_FMT) + strlen(get_rattr_str(pjob->ji_myResv, RESV_ATR_resv_name));
		if (nd > len)
			if (grow_acct_buf(&pb, &len, nd) == -1)
				return (pb);
		/* reservation name */
		if (is_rattr_set(pjob->ji_myResv, RESV_ATR_resv_name)) {
			(void) snprintf(pb, len, RESVNAME_FMT, get_rattr_str(pjob->ji_myResv, RESV_ATR_resv_name));
			ct = strlen(pb);
			pb += ct;
			len -= ct;
		}

		/* reservation ID */
		(void) snprintf(pb, len, RESVID_FMT,
				pjob->ji_myResv->ri_qs.ri_resvID);
		ct = strlen(pb);
		pb += ct;
		len -= ct;
	}

	/* insure space for all *times */
	nd = ACCTBUF_TIMES_NEED;
	if (nd > len)
		if (grow_acct_buf(&pb, &len, nd) == -1)
			return (pb);

	/* create time */
	sprintf(pb, "ctime=%ld ", get_jattr_long(pjob, JOB_ATR_ctime));
	ct = strlen(pb);
	pb += ct;
	len -= ct;

	/* queued time */
	sprintf(pb, "qtime=%ld ", get_jattr_long(pjob, JOB_ATR_qtime));
	ct = strlen(pb);
	pb += ct;
	len -= ct;

	/* eligible time, how long ready to run */
	sprintf(pb, "etime=%ld ", get_jattr_long(pjob, JOB_ATR_etime));
	ct = strlen(pb);
	pb += ct;
	len -= ct;

	/* start time */
	sprintf(pb, "start=%ld ", (long) pjob->ji_qs.ji_stime);
	ct = strlen(pb);
	pb += ct;
	len -= ct;

	if ((is_jattr_set(pjob, JOB_ATR_array_indices_submitted)) &&
	    check_job_state(pjob, JOB_STATE_LTR_BEGUN)) {

		/* for an Array Job in Begun state,  record index range */

		nd = sizeof(ARRAY_INDICES_FMT) + strlen(get_jattr_str(pjob, JOB_ATR_array_indices_submitted));
		if (nd > len)
			if (grow_acct_buf(&pb, &len, nd) == -1)
				return (pb);
		snprintf(pb, len, ARRAY_INDICES_FMT, get_jattr_str(pjob, JOB_ATR_array_indices_submitted));
		ct = strlen(pb);
		pb += ct;
		len -= ct;

		/* now encode the job's resource_list attribute */
		/* of the just concluded phase */
		old_perm = resc_access_perm;
		resc_access_perm = READ_ONLY;
		if (type == PBS_ACCT_UPDATE)
			att_index = JOB_ATR_resource_acct;
		else
			att_index = JOB_ATR_resource;
		job_attr_def[att_index].at_encode(
			get_jattr(pjob, att_index),
			&attrlist,
			job_attr_def[att_index].at_name,
			NULL,
			ATR_ENCODE_CLIENT, NULL);
		resc_access_perm = old_perm;

		nd = 0; /* compute total size needed in buf */
		pal = GET_NEXT(attrlist);
		while (pal != NULL) {
			/* +5 in count is for '=', ' ', start and end quotes, and \0 */
			nd += strlen(pal->al_name) + strlen(pal->al_value) + 5;
			if (pal->al_resc)
				nd += 1 + strlen(pal->al_resc);
			pal = GET_NEXT(pal->al_link);
		}
		if (nd > len)
			if (grow_acct_buf(&pb, &len, nd) == -1)
				return (pb);

		if (type == PBS_ACCT_UPDATE)
			len_acct = 5; /* for length of "_acct" */
		else
			len_acct = 0;
		while ((pal = GET_NEXT(attrlist)) != NULL) {
			/* strip off the '_acct' suffix */
			if (len_acct > 0) {
				k = strlen(pal->al_name);
				if (k > len_acct) {
					save_char = pal->al_name[k - len_acct];
					pal->al_name[k - len_acct] = '\0';
				}
			}
			(void) strcat(pb, pal->al_name);
			if (len_acct > 0) {
				if (k > len_acct) {
					pal->al_name[k - len_acct] = save_char;
				}
			}
			if (pal->al_resc) {

				(void) strcat(pb, ".");
				(void) strcat(pb, pal->al_resc);
			}
			(void) strcat(pb, "=");
			cpy_quote_value(pb, pal->al_value);
			(void) strcat(pb, " ");
			delete_link(&pal->al_link);
			(void) free(pal);
			pb += strlen(pb);
		}
	} else {

		/* regular job */
		/* record exec_host of a completed phase */
		if (type == PBS_ACCT_UPDATE)
			att_index = JOB_ATR_exec_host_acct;
		else
			att_index = JOB_ATR_exec_host;
		if (is_jattr_set(pjob, att_index)) {
			/* execution host list, may be loooong */
			nd = sizeof(EXEC_HOST_FMT) + strlen(get_jattr_str(pjob, att_index));
			if (nd > len)
				if (grow_acct_buf(&pb, &len, nd) == -1)
					return (pb);
			(void) snprintf(pb, len, EXEC_HOST_FMT,
					get_jattr_str(pjob, att_index));
			ct = strlen(pb);
			pb += ct;
			len -= ct;
		}

		/* record exec_vnode of a just concluded phase */
		if (type == PBS_ACCT_UPDATE)
			att_index = JOB_ATR_exec_vnode_acct;
		else
			att_index = JOB_ATR_exec_vnode;
		if (is_jattr_set(pjob, att_index)) {
			/* execution vnode list, will be even longer */
			nd = sizeof(EXEC_VNODE_FMT) + strlen(get_jattr_str(pjob, att_index));
			if (nd > len)
				if (grow_acct_buf(&pb, &len, nd) == -1)
					return (pb);
			(void) snprintf(pb, len, EXEC_VNODE_FMT,
					get_jattr_str(pjob, att_index));
			ct = strlen(pb);
			pb += ct;
			len -= ct;
		}

		/* now encode the job's resource_list attribute */
		/* of the just concluded phase */
		old_perm = resc_access_perm;
		resc_access_perm = READ_ONLY;
		if (type == PBS_ACCT_UPDATE)
			att_index = JOB_ATR_resource_acct;
		else
			att_index = JOB_ATR_resource;
		(void) job_attr_def[att_index].at_encode(
			get_jattr(pjob, att_index),
			&attrlist,
			job_attr_def[att_index].at_name,
			NULL,
			ATR_ENCODE_CLIENT, NULL);
		resc_access_perm = old_perm;

		nd = 0; /* compute total size needed in buf */
		pal = GET_NEXT(attrlist);
		while (pal != NULL) {
			/* +5 in count is for '=', ' ', start and end quotes, and \0 */
			nd += strlen(pal->al_name) + strlen(pal->al_value) + 5;
			if (pal->al_resc)
				nd += 1 + strlen(pal->al_resc);
			pal = GET_NEXT(pal->al_link);
		}
		if (nd > len)
			if (grow_acct_buf(&pb, &len, nd) == -1)
				return (pb);

		if (type == PBS_ACCT_UPDATE)
			len_acct = strlen("_acct");
		else
			len_acct = 0;
		while ((pal = GET_NEXT(attrlist)) != NULL) {
			/* strip off the '_acct' suffix */
			if (len_acct > 0) {
				k = strlen(pal->al_name);
				if (k > len_acct) {
					save_char = pal->al_name[k - len_acct];
					pal->al_name[k - len_acct] = '\0';
				}
			}
			(void) strcat(pb, pal->al_name);
			if (len_acct > 0) {
				if (k > len_acct) {
					pal->al_name[k - len_acct] = save_char;
				}
			}
			if (pal->al_resc) {

				(void) strcat(pb, ".");
				(void) strcat(pb, pal->al_resc);
			}
			(void) strcat(pb, "=");
			cpy_quote_value(pb, pal->al_value);
			(void) strcat(pb, " ");
			delete_link(&pal->al_link);
			(void) free(pal);
			pb += strlen(pb);
		}
	}

	return (pb);
}

/**
 * @brief
 *	Form and write a job update record with resource usage.
 *
 * @par	Functionality:
 *	Takes various information from the job structure, start time, owner,
 *	Resource_List, etc., and the resource usage information
 *	if present and formats the record type requested.
 *	Currently, this is used for 'u' and 'e' records.  The record is then
 *	written to the accounting log.
 *
 * @see build_common_data_for_job_update()
 *
 * @param[in]	pjob	- pointer to job structure
 * @param[in]	used	- resource usage information from Mom,
 *			  this is a string consisting of space separated
 *			  keyword=value pairs, may be null pointer
 * @param[in]	type	- record type, PBS_ACCT_UPDATE ('u') or PBS_ACCT_LAST ('e')
 * @return	void
 *
 * @par	MT-safe: No - uses a global buffer, "acct_buf".
 *
 */
void
account_job_update(job *pjob, int type)
{
	int i = 0;
	int len = 0;
	char *pb = NULL;
	pbs_list_head attrlist;
	struct svrattrl *patlist = NULL;
	char *resc_used = NULL;
	int resc_used_size = 0;
	int k, len_upd, attr_index;
	char save_char = '\0';
	int old_perm;

	if (!is_jattr_set(pjob, JOB_ATR_exec_vnode_acct))
		return;

	CLEAR_HEAD(attrlist);
	/* pack in general information about the job */

	pb = build_common_data_for_job_update(pjob, type, acct_buf, acct_bufsize);
	len = acct_bufsize - (pb - acct_buf);

	/* session */
	i = 30;
	if (i > len)
		if (grow_acct_buf(&pb, &len, i) == -1)
			goto writeit;
	sprintf(pb, "session=%ld", get_jattr_long(pjob, JOB_ATR_session_id));
	i = strlen(pb);
	pb += i;
	len -= i;

	/* Alternate id if present */

	if (is_jattr_set(pjob, JOB_ATR_altid)) {
		/* 9 is for length of " alt_id=" and \0 */
		i = 9 + strlen(get_jattr_str(pjob, JOB_ATR_altid));
		if (i > len)
			if (grow_acct_buf(&pb, &len, i) == -1)
				goto writeit;

		sprintf(pb, " alt_id=%s", get_jattr_str(pjob, JOB_ATR_altid));

		i = strlen(pb);
		pb += i;
		len -= i;
	}

	/* Add eligible_time */
	if (get_sattr_long(SVR_ATR_EligibleTimeEnable) == 1) {
		char timebuf[TIMEBUF_SIZE];
		i = 26; /* sort of max size for " eligible_time=<value>" */
		if (i > len)
			if (grow_acct_buf(&pb, &len, i) == -1)
				goto writeit;

		convert_duration_to_str(get_jattr_long(pjob, JOB_ATR_eligible_time), timebuf, TIMEBUF_SIZE);
		(void) sprintf(pb, " eligible_time=%s", timebuf);
		i = strlen(pb);
		pb += i;
		len -= i;
	}

	/* Add in runcount */

	i = 34; /* sort of max size for "run_count=<value>" */
	if (i > len)
		if (grow_acct_buf(&pb, &len, i) == -1)
			goto writeit;
	sprintf(pb, " run_count=%ld", get_jattr_long(pjob, JOB_ATR_runcount));

	/* now encode the job's resources_used attribute */
	old_perm = resc_access_perm;
	resc_access_perm = READ_ONLY;
	if (is_jattr_set(pjob, JOB_ATR_resc_used_update)) {
		len_upd = 7; /* for length of "_update" */
		attr_index = JOB_ATR_resc_used_update;
	} else {
		len_upd = 0;
		attr_index = JOB_ATR_resc_used;
	}
	job_attr_def[attr_index].at_encode(get_jattr(pjob, attr_index),
					   &attrlist,
					   job_attr_def[attr_index].at_name,
					   NULL,
					   ATR_ENCODE_CLIENT, NULL);
	resc_access_perm = old_perm;

	/* Allocate initial space for resc_used.  Future space will be allocated by pbs_strcat(). */
	resc_used = malloc(RESC_USED_BUF_SIZE);
	if (resc_used == NULL)
		goto writeit;
	resc_used_size = RESC_USED_BUF_SIZE;
	resc_used[0] = '\0';

	patlist = GET_NEXT(attrlist);
	while (patlist) {
		k = 0;
		if (len_upd > 0) {
			/* strip off the '_update' suffix */
			k = strlen(patlist->al_name);
			if (k > len_upd) {
				save_char = patlist->al_name[k - len_upd];
				patlist->al_name[k - len_upd] = '\0';
			}
		}
		/*
		 * To calculate length of the string of the form "resources_used.<resource>=<value>".
		 * Additional length of 3 is required to accommodate the characters '.', '=' and ' '.
		 */
		if (strlen(patlist->al_value) > 0) {

			if (pbs_strcat(&resc_used, &resc_used_size, " ") == NULL) {
				log_err(errno, __func__, "Failed to allocate memory.");
				if (len_upd > 0 && k > len_upd) {
					patlist->al_name[k - len_upd] = save_char;
				}
				goto writeit;
			}
			if (pbs_strcat(&resc_used, &resc_used_size, patlist->al_name) == NULL) {
				log_err(errno, __func__, "Failed to allocate memory.");
				if (len_upd > 0 && k > len_upd) {
					patlist->al_name[k - len_upd] = save_char;
				}
				goto writeit;
			}
			if (len_upd > 0 && k > len_upd) {
				patlist->al_name[k - len_upd] = save_char;
			}
			if (patlist->al_resc) {
				if (pbs_strcat(&resc_used, &resc_used_size, ".") == NULL) {
					log_err(errno, __func__, "Failed to allocate memory.");
					goto writeit;
				}
				if (pbs_strcat(&resc_used, &resc_used_size, patlist->al_resc) == NULL) {
					log_err(errno, __func__, "Failed to allocate memory.");
					goto writeit;
				}
			}
			if (pbs_strcat(&resc_used, &resc_used_size, "=") == NULL) {
				log_err(errno, __func__, "Failed to allocate memory.");
				goto writeit;
			}
			if (patlist->al_resc && (strcmp(patlist->al_resc, WALLTIME) == 0)) {
				long j, k;

				k = get_walltime(pjob, JOB_ATR_resc_used_acct);
				j = get_walltime(pjob, JOB_ATR_resc_used);
				if ((k >= 0) && (j >= k)) {
					char timebuf[TIMEBUF_SIZE];

					convert_duration_to_str(j - k, timebuf, TIMEBUF_SIZE);
					if (pbs_strcat(&resc_used, &resc_used_size, timebuf) == NULL) {
						log_err(errno, __func__, "Failed to allocate memory.");
						goto writeit;
					}
				} else {
					if (pbs_strcat(&resc_used, &resc_used_size, patlist->al_value) == NULL) {
						log_err(errno, __func__, "Failed to allocate memory.");
						goto writeit;
					}
				}
			} else {
				if (pbs_strcat(&resc_used, &resc_used_size, patlist->al_value) == NULL) {
					log_err(errno, __func__, "Failed to allocate memory.");
					goto writeit;
				}
			}
		}
		patlist = patlist->al_sister;
	}
	free_attrlist(&attrlist);

	if (resc_used[0] != '\0') {
		i = strlen(resc_used) + 1;
		if (i > len)
			if (grow_acct_buf(&pb, &len, i) == -1)
				goto writeit;
		(void) strcat(pb, " ");
		(void) strcat(pb, resc_used);
		i = strlen(pb);
		pb += i;
		len -= i;

		set_attr_with_attr(&job_attr_def[JOB_ATR_resc_used_acct], get_jattr(pjob, JOB_ATR_resc_used_acct), get_jattr(pjob, JOB_ATR_resc_used), INCR);
	}

writeit:
	acct_buf[acct_bufsize - 1] = '\0';
	account_record(type, pjob, acct_buf);
	if (resc_used != NULL)
		free(resc_used);
}

/**
 * @brief
 * 	log an alter record for modified jobs
 * 	plist contains the attributes and resources requested to be modified.
 * 	We only modify those because the ATTR_l encode function will encode
 * 	all resources, not just the ones we want.
 *
 * @param[in] pjob - job to log records for.
 * @param[in] plist - list of attributes and resources to log
 *
 * @returns void
 */
void
log_alter_records_for_attrs(job *pjob, svrattrl *plist)
{
	svrattrl *cur_svr;
	pbs_list_head phead;
	svrattrl *cur_plist;
	char *per_attr_buf = NULL;
	static char *entire_record = NULL;
	static int entire_record_len = 0;
	int error = 0;
	int i;

	if (entire_record == NULL) {
		entire_record = malloc(1024 * sizeof(char));
		if (entire_record == NULL)
			return;
		entire_record_len = 1024;
	}
	entire_record[0] = '\0';

	CLEAR_HEAD(phead);
	for (i = 0; i < JOB_ATR_LAST; i++) {
		attribute *pattr = get_jattr(pjob, i);
		if (pattr->at_flags & ATR_VFLAG_MODIFY) {
			svrattrl *svrattrl_list = NULL;
			job_attr_def[i].at_encode(pattr, &phead, job_attr_def[i].at_name, NULL, ATR_ENCODE_CLIENT, &svrattrl_list);
			for (cur_plist = plist; cur_plist != NULL; cur_plist = (svrattrl *) GET_NEXT(cur_plist->al_link)) {
				int j;
				int ignore = 0;
				for (j = 0; do_not_emit_alter[j] != NULL; j++)
					if (strcmp(do_not_emit_alter[j], cur_plist->al_name) == 0) {
						ignore = 1;
						break;
					}
				if (ignore || strcmp(cur_plist->al_name, job_attr_def[i].at_name) != 0)
					continue;
				else {
					for (cur_svr = svrattrl_list; cur_svr != NULL; cur_svr = (svrattrl *) GET_NEXT(cur_svr->al_link)) {
						if (pattr->at_type == ATR_TYPE_RESC) {
							if (cur_plist->al_resc != NULL) {
								if (strcmp(cur_plist->al_resc, cur_svr->al_resc) == 0) {
									char *fmt;
									if (strchr(cur_svr->al_value, ' ') == NULL)
										fmt = "%s.%s=%s";
									else
										fmt = "%s.%s=\"%s\"";
									pbs_asprintf(&per_attr_buf, fmt, cur_svr->al_name, cur_svr->al_resc, cur_svr->al_value);
									break;
								}
							}
						} else {
							char *fmt;
							if (strchr(cur_svr->al_value, ' ') == NULL)
								fmt = "%s=%s";
							else
								fmt = "%s=\"%s\"";
							pbs_asprintf(&per_attr_buf, fmt, cur_svr->al_name, cur_svr->al_value);
							break;
						}
					}
				}
				if (per_attr_buf == NULL && cur_plist->al_value[0] == '\0') { /* unset */
					pbs_asprintf(&per_attr_buf, "%s%s%s=UNSET", cur_plist->al_name, cur_plist->al_resc ? "." : "", cur_plist->al_resc ? cur_plist->al_resc : "");
				}

				if (entire_record[0] != '\0')
					if (pbs_strcat(&entire_record, &entire_record_len, " ") == NULL)
						error = 1;

				if (error == 0)
					if (pbs_strcat(&entire_record, &entire_record_len, per_attr_buf) == NULL)
						error = 1;

				free(per_attr_buf);
				per_attr_buf = NULL;
				if (error)
					return;
			}
			free_svrattrl(svrattrl_list);
		}
	}
	if (entire_record[0] != '\0')
		account_record(PBS_ACCT_ALTER, pjob, entire_record);
}

/**
 * @brief
 * Common function to log a suspend/resume record
 * for suspend/resume job events respectively.
 *
 * @param[in] pjob - job to log records for.
 * @param[in] acct_type - Accounting type flag
 *
 * @returns void
 */
void
log_suspend_resume_record(job *pjob, int acct_type)
{
	if (acct_type == PBS_ACCT_SUSPEND) {
		char *resc_buf;
		int resc_buf_size = RESC_USED_BUF_SIZE;

		/* Allocating initial space as required by resc_used. Future space will be allocated by pbs_strcat(). */
		resc_buf = malloc(RESC_USED_BUF_SIZE);
		if (resc_buf == NULL)
			return;

		resc_buf[0] = '\0';

		if (get_resc_used(pjob, &resc_buf, &resc_buf_size) == -1) {
			write_account_record(acct_type, pjob->ji_qs.ji_jobid, NULL);
			free(resc_buf);
			return;
		}

		if (is_jattr_set(pjob, JOB_ATR_resc_released)) {
			char *ret;
			ret = pbs_strcat(&resc_buf, &resc_buf_size, " resources_released=");
			if (ret == NULL) {
				free(resc_buf);
				return;
			}

			ret = pbs_strcat(&resc_buf, &resc_buf_size, get_jattr_str(pjob, JOB_ATR_resc_released));
			if (ret == NULL) {
				free(resc_buf);
				return;
			}
		}
		write_account_record(acct_type, pjob->ji_qs.ji_jobid, resc_buf + 1);
		free(resc_buf);
		return;
	}

	write_account_record(acct_type, pjob->ji_qs.ji_jobid, NULL);
}
