/*
 * 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    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"
#ifndef  WIN32
#include <sys/param.h>
#endif
#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;

/* 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(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) ||
		!(pjob->ji_wattr[(int)JOB_ATR_exec_vnode].at_flags & ATR_VFLAG_SET))
		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_accted = 0;

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

	/* 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_accted == 0) {

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

					pnode->nd_accted = 1;  /* mark that it has been recorded */
					for (i=0; svr_resc_sum[i].rs_def; ++i) {
						presc = find_resc_entry(&pnode->nd_attr[(int)ND_ATR_ResourceAvail], svr_resc_sum[i].rs_def);
						if (presc && (presc->rs_value.at_flags & ATR_VFLAG_SET)) {
							(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 "

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


/**
 * @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(job *jp, int res)
{
	resource_def	*rscdef;
	resource	*pres;

	rscdef = find_resc_def(svr_resc_def, "walltime", svr_resc_size);
	if (rscdef == NULL)
		return (-1);
	pres = find_resc_entry(&jp->ji_wattr[res], rscdef);
	if (pres == NULL)
		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
 *	Form and write a job termination/rerun record with resource usage.
 * 	Build common data for 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(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 (pjob->ji_wattr[(int)JOB_ATR_gridname].at_flags & ATR_VFLAG_SET) {
		nd = strlen(pjob->ji_wattr[(int)JOB_ATR_gridname].at_val.at_str)
			+ sizeof(GRIDNAME_FMT);
		if (nd > len)
			if (grow_acct_buf(&pb, &len, nd) == -1)
				return (pb);

		(void)snprintf(pb, len, GRIDNAME_FMT,
			pjob->ji_wattr[(int)JOB_ATR_gridname].at_val.at_str);
		i = strlen(pb);
		pb  += i;
		len -= i;
	}

	/* user */
	nd = sizeof(USER_FMT) + strlen(pjob->ji_wattr[(int)JOB_ATR_euser].at_val.at_str);
	if (nd > len)
		if (grow_acct_buf(&pb, &len, nd) == -1)
			return (pb);
#ifdef WIN32
	(void)snprintf(pb, len, USER_FMT,
		replace_space(pjob->ji_wattr[(int)JOB_ATR_euser].at_val.at_str,
		acctlog_spacechar));
#else
	(void)snprintf(pb, len, USER_FMT,
		pjob->ji_wattr[(int)JOB_ATR_euser].at_val.at_str);
#endif
	i = strlen(pb);
	pb  += i;
	len -= i;

	/* group */
	nd = sizeof(GROUP_FMT) + strlen(pjob->ji_wattr[(int)JOB_ATR_egroup].at_val.at_str);
	if (nd > len)
		if (grow_acct_buf(&pb, &len, nd) == -1)
			return (pb);
#ifdef WIN32
	(void)snprintf(pb, len, GROUP_FMT,
		replace_space(pjob->ji_wattr[(int)JOB_ATR_egroup].at_val.at_str,
		acctlog_spacechar));
#else
	(void)snprintf(pb, len, GROUP_FMT,
		pjob->ji_wattr[(int)JOB_ATR_egroup].at_val.at_str);
#endif
	i = strlen(pb);
	pb  += i;
	len -= i;

	/* account */
	if (pjob->ji_wattr[(int)JOB_ATR_account].at_flags & ATR_VFLAG_SET) {
		nd = sizeof(ACCOUNT_FMT) + strlen(pjob->ji_wattr[(int)JOB_ATR_account].at_val.at_str);
		if (nd > len)
			if (grow_acct_buf(&pb, &len, nd) == -1)
				return (pb);
#ifdef WIN32
		(void)snprintf(pb, len, ACCOUNT_FMT,
			replace_space(
			pjob->ji_wattr[(int)JOB_ATR_account].at_val.at_str,
			acctlog_spacechar));
#else
		(void)snprintf(pb, len, ACCOUNT_FMT,
			pjob->ji_wattr[(int)JOB_ATR_account].at_val.at_str);
#endif
		i = strlen(pb);
		pb  += i;
		len -= i;
	}

	/* project */
	if (pjob->ji_wattr[(int)JOB_ATR_project].at_flags & ATR_VFLAG_SET) {
		char *projstr;

		projstr =  pjob->ji_wattr[(int)JOB_ATR_project].at_val.at_str;
		/* 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);
		}
		i = strlen(pb);
		pb  += i;
		len -= i;
	}

	/* accounting_id */
	if (pjob->ji_wattr[(int)JOB_ATR_acct_id].at_flags & ATR_VFLAG_SET) {
		nd = sizeof(ACCOUNTING_ID_FMT) + strlen(pjob->ji_wattr[(int)JOB_ATR_acct_id].at_val.at_str);
		if (nd > len)
			if (grow_acct_buf(&pb, &len, nd) == -1)
				return (pb);
		(void)snprintf(pb, len, ACCOUNTING_ID_FMT,
			pjob->ji_wattr[(int)JOB_ATR_acct_id].at_val.at_str);
		i = strlen(pb);
		pb  += i;
		len -= i;
	}

	/* job name */
	nd = sizeof(JOBNAME_FMT) + strlen(pjob->ji_wattr[(int)JOB_ATR_jobname].at_val.at_str);
	if (nd > len)
		if (grow_acct_buf(&pb, &len, nd) == -1)
			return (pb);
	(void)snprintf(pb, len, JOBNAME_FMT,
		pjob->ji_wattr[(int)JOB_ATR_jobname].at_val.at_str);
	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);
	(void)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 (pjob->ji_myResv->ri_wattr[(int)RESV_ATR_resv_name].at_flags
			& ATR_VFLAG_SET)
			nd += sizeof(RESVNAME_FMT) + strlen(pjob->ji_myResv->ri_wattr[(int)RESV_ATR_resv_name].at_val.at_str);
		if (nd > len)
			if (grow_acct_buf(&pb, &len, nd) == -1)
				return (pb);
		/* reservation name */
		if (pjob->ji_myResv->ri_wattr[(int)RESV_ATR_resv_name].at_flags
			& ATR_VFLAG_SET) {
			(void)snprintf(pb, len, RESVNAME_FMT,
				pjob->ji_myResv->ri_wattr[(int)
				RESV_ATR_resv_name].
				at_val.at_str);
			i = strlen(pb);
			pb  += i;
			len -= i;
		}

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

	/* resvjob ID */
	if (pjob->ji_resvp) {
		nd = sizeof(RESVJOBID_FMT) + strlen(pjob->ji_resvp->ri_qs.ri_resvID);
		if (nd > len)
			if (grow_acct_buf(&pb, &len, nd) == -1)
				return (pb);
		(void)snprintf(pb, len, RESVJOBID_FMT,
			pjob->ji_resvp->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 */
	(void)sprintf(pb, "ctime=%ld ",
		pjob->ji_wattr[(int)JOB_ATR_ctime].at_val.at_long);
	i = strlen(pb);
	pb  += i;
	len -= i;

	/* queued time */
	(void)sprintf(pb, "qtime=%ld ",
		pjob->ji_wattr[(int)JOB_ATR_qtime].at_val.at_long);
	i = strlen(pb);
	pb  += i;
	len -= i;

	/* eligible time, how long ready to run */
	(void)sprintf(pb, "etime=%ld ", pjob->ji_wattr[(int)JOB_ATR_etime].at_val.at_long);
	i = strlen(pb);
	pb  += i;
	len -= i;

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

	if (pjob->ji_wattr[(int)JOB_ATR_array_indices_submitted].at_flags & ATR_VFLAG_SET && (pjob->ji_qs.ji_state == JOB_STATE_BEGUN)) {

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

		nd = sizeof(ARRAY_INDICES_FMT) + strlen(pjob->ji_wattr[(int)JOB_ATR_array_indices_submitted].at_val.at_str);
		if (nd > len)
			if (grow_acct_buf(&pb, &len, nd) == -1)
				return (pb);
		(void)snprintf(pb, len, ARRAY_INDICES_FMT, pjob->ji_wattr[(int)JOB_ATR_array_indices_submitted].at_val.at_str);
		i = strlen(pb);
		pb  += i;
		len -= i;

	} else {

		/* regular job */
		if ((type == PBS_ACCT_END) &&
		    (pjob->ji_wattr[(int)JOB_ATR_exec_host_orig].at_flags & ATR_VFLAG_SET))
			att_index = JOB_ATR_exec_host_orig;
		else
			att_index = JOB_ATR_exec_host;

		if (pjob->ji_wattr[att_index].at_flags & ATR_VFLAG_SET) {
			/* execution host list, may be loooong */
			nd = sizeof(EXEC_HOST_FMT) + strlen(pjob->ji_wattr[att_index].at_val.at_str);
			if (nd > len)
				if (grow_acct_buf(&pb, &len, nd) == -1)
					return (pb);
			(void)snprintf(pb, len, EXEC_HOST_FMT,
				pjob->ji_wattr[att_index].at_val.at_str);
			i = strlen(pb);
			pb  += i;
			len -= i;
		}
		if ((type == PBS_ACCT_END) &&
		    (pjob->ji_wattr[(int)JOB_ATR_exec_vnode_orig].at_flags & ATR_VFLAG_SET))
			att_index = JOB_ATR_exec_vnode_orig;
		else
			att_index = JOB_ATR_exec_vnode;

		if (pjob->ji_wattr[att_index].at_flags & ATR_VFLAG_SET) {
			/* execution vnode list, will be even longer */
			nd = sizeof(EXEC_VNODE_FMT) + strlen(pjob->ji_wattr[att_index].at_val.at_str);
			if (nd > len)
				if (grow_acct_buf(&pb, &len, nd) == -1)
					return (pb);
			(void)snprintf(pb, len, EXEC_VNODE_FMT,
				pjob->ji_wattr[att_index].at_val.at_str);
			i = strlen(pb);
			pb  += i;
			len -= i;
		}
	}

	/* now encode the job's resource_list attribute */
	if ((type == PBS_ACCT_END) &&
	    (pjob->ji_wattr[(int)JOB_ATR_resource_orig].at_flags & ATR_VFLAG_SET)) {
		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(
		&pjob->ji_wattr[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(presv->ri_wattr[(int)RESV_ATR_resv_owner].at_val.at_str);
	if (i > len)
		if (grow_acct_buf(&pb, &len, i) == -1)
			return (pb);
	(void)sprintf(pb, "owner=%s ",
		presv->ri_wattr[(int)RESV_ATR_resv_owner].at_val.at_str);
	i = strlen(pb);
	pb  += i;
	len -= i;

	/* name */
	if (presv->ri_wattr[(int)RESV_ATR_resv_name].at_flags & ATR_VFLAG_SET) {
		i=7+strlen(presv->ri_wattr[(int)RESV_ATR_resv_name].at_val.at_str);
		if (i > len)
			if (grow_acct_buf(&pb, &len, i) == -1)
				return (pb);
		(void)sprintf(pb, "name=%s ",
			presv->ri_wattr[(int)RESV_ATR_resv_name].at_val.at_str);
		i = strlen(pb);
		pb  += i;
		len -= i;
	}

	/* account */
	if (presv->ri_wattr[(int)RESV_ATR_account].at_flags & ATR_VFLAG_SET) {
		i = 10+strlen(presv->ri_wattr[(int)RESV_ATR_account].at_val.at_str);
		if (i > len)
			if (grow_acct_buf(&pb, &len, i) == -1)
				return (pb);
		(void)sprintf(pb, "account=%s ",
			presv->ri_wattr[(int)RESV_ATR_account].at_val.at_str);
		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_qs.ri_type == RESC_RESV_OBJECT) &&
		(presv->ri_qp != NULL))
		(void)sprintf(pb, "queue=%s ", presv->ri_qp->qu_qs.qu_name);
	else if (presv->ri_qs.ri_type == RESV_JOB_OBJECT && presv->ri_jbp)
		(void)sprintf(pb, "queue=%s ", presv->ri_jbp->ji_qhdr->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 ",
		presv->ri_wattr[(int)RESV_ATR_ctime].at_val.at_long);
	i = strlen(pb);
	pb  += i;
	len -= i;

#if 0
	There is no such thing right now but think about it
	/* confirmed time */
	(void)sprintf(pb, "qtime=%ld ",
		pjob->ji_wattr[(int)JOB_ATR_qtime].at_val.at_long);
	i = strlen(pb);
	pb  += i;
	len -= i;
#endif

	/* 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 (presv->ri_wattr[(int)RESV_ATR_resv_nodes].at_flags & ATR_VFLAG_SET) {
		i = 8+strlen(presv->ri_wattr[(int)RESV_ATR_resv_nodes].at_val.at_str);
		if (i > len)
			if (grow_acct_buf(&pb, &len, i) == -1)
				return (pb);
		(void)sprintf(pb, "nodes=%s ",
			presv->ri_wattr[(int)RESV_ATR_resv_nodes].at_val.at_str);
		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[(int)RESV_ATR_auth_u].at_encode(
		&presv->ri_wattr[(int)RESV_ATR_auth_u],
		&attrlist,
		resv_attr_def[(int)RESV_ATR_auth_u].at_name,
		NULL,
		ATR_ENCODE_CLIENT, NULL);

	(void)resv_attr_def[(int)RESV_ATR_auth_g].at_encode(
		&presv->ri_wattr[(int)RESV_ATR_auth_g],
		&attrlist,
		resv_attr_def[(int)RESV_ATR_auth_g].at_name,
		NULL,
		ATR_ENCODE_CLIENT, NULL);

	(void)resv_attr_def[(int)RESV_ATR_auth_h].at_encode(
		&presv->ri_wattr[(int)RESV_ATR_auth_h],
		&attrlist,
		resv_attr_def[(int)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[(int)RESV_ATR_resource].at_encode(
		&presv->ri_wattr[(int)RESV_ATR_resource],
		&attrlist,
		resv_attr_def[(int)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)
{

#ifdef WIN32
	char  filen[_MAX_PATH];
	char  logmsg[_MAX_PATH+80];
#else
	char  filen[_POSIX_PATH_MAX];
	char  logmsg[_POSIX_PATH_MAX+80];
#endif
	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);
	}

#ifdef WIN32
	secure_file(filename, "Administrators", READS_MASK|WRITES_MASK|STANDARD_RIGHTS_REQUIRED);
	(void)setvbuf(newacct, NULL, _IONBF, 0); /* no buffering to get instant
						  log*/
#else
	(void)setvbuf(newacct, NULL, _IOLBF, 0); /* set line buffering */
#endif

	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, 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, 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 job start record.
 *
 * @par	Functionality:
 *	Calls account_jobstr2().
 *
 * @see:
 *	complete_running()
 *
 * @param[in]	pjob	- pointer to job structure
 *
 * @return	void
 *
 * @par	MT-safe: No - uses a global buffer, "acct_buf".
 *
 */
void
account_jobstr(pjob)
job	*pjob;
{
	account_jobstr2(pjob, PBS_ACCT_RUN);
}

/**
 * @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_jobstr2(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 */

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

	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) {
			(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);
		}
	}
	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;
	struct svrattrl *patlist = NULL;
	char *resc_used;
	int resc_used_size = 0;
	pbs_list_head temp_head;

	CLEAR_HEAD(temp_head);
	/* 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",
		pjob->ji_wattr[(int)JOB_ATR_session_id].at_val.at_long);
	i = strlen(pb);
	pb  += i;
	len -= i;

	/* Alternate id if present */

	if (pjob->ji_wattr[(int)JOB_ATR_altid].at_flags & ATR_VFLAG_SET) {
		i = 9 + strlen(pjob->ji_wattr[(int)JOB_ATR_altid].at_val.at_str);
		if (i > len)
			if (grow_acct_buf(&pb, &len, i) == -1)
				goto writeit;
#ifdef WIN32
		(void)sprintf(pb, " alt_id=%s",
			replace_space(
			pjob->ji_wattr[(int)JOB_ATR_altid].at_val.at_str,
			acctlog_spacechar));
#else
		(void)sprintf(pb, " alt_id=%s",
			pjob->ji_wattr[(int)JOB_ATR_altid].at_val.at_str);
#endif
		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.
			 */
			if (pjob->ji_wattr[(int) JOB_ATR_resc_used].at_user_encoded != NULL)
				patlist = pjob->ji_wattr[(int) JOB_ATR_resc_used].at_user_encoded;
			else if (pjob->ji_wattr[(int) JOB_ATR_resc_used].at_priv_encoded != NULL)
				patlist = pjob->ji_wattr[(int) JOB_ATR_resc_used].at_priv_encoded;
			else
				encode_resc(&pjob->ji_wattr[(int) JOB_ATR_resc_used],
					    &temp_head, job_attr_def[(int) JOB_ATR_resc_used].at_name,
					    NULL, ATR_ENCODE_CLIENT, &patlist);

			/* 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);

			/*
			 * 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) {
						free(resc_used);
						goto writeit;
					}

				}

				patlist = patlist->al_sister;
			}

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

	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 (server.sv_attr[(int)SRV_ATR_EligibleTimeEnable].at_val.at_long == 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(pjob->ji_wattr[(int)JOB_ATR_eligible_time].at_val.at_long, 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",
		pjob->ji_wattr[(int)JOB_ATR_runcount].at_val.at_long);
	/* 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 license_used
 *
 * @return      void
 */
void
log_licenses(struct license_used *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(pjob->ji_wattr[(int)JOB_ATR_euser].at_val.at_str);
	if (nd > len)
		if (grow_acct_buf(&pb, &len, nd) == -1)
			return (pb);
#ifdef WIN32
	(void)sprintf(pb, "user=%s ",
		replace_space(pjob->ji_wattr[(int)JOB_ATR_euser].at_val.at_str,
		acctlog_spacechar));
#else
	(void)sprintf(pb, "user=%s ",
		pjob->ji_wattr[(int)JOB_ATR_euser].at_val.at_str);
#endif
	i = strlen(pb);
	pb  += i;
	len -= i;

	/* group */
	nd = 8 + strlen(pjob->ji_wattr[(int)JOB_ATR_egroup].at_val.at_str);
	if (nd > len)
		if (grow_acct_buf(&pb, &len, nd) == -1)
			return (pb);
#ifdef WIN32
	(void)sprintf(pb, "group=%s ",
		replace_space(pjob->ji_wattr[(int)JOB_ATR_egroup].at_val.at_str,
		acctlog_spacechar));
#else
	(void)sprintf(pb, "group=%s ",
		pjob->ji_wattr[(int)JOB_ATR_egroup].at_val.at_str);
#endif
	i = strlen(pb);
	pb  += i;
	len -= i;

	/* job name */
	nd = 10 + strlen(pjob->ji_wattr[(int)JOB_ATR_jobname].at_val.at_str);
	if (nd > len)
		if (grow_acct_buf(&pb, &len, nd) == -1)
			return (pb);
	(void)sprintf(pb, "jobname=%s ",
		pjob->ji_wattr[(int)JOB_ATR_jobname].at_val.at_str);
	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);
	(void)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 (pjob->ji_wattr[(int)JOB_ATR_prov_vnode].at_val.at_str == NULL) {
		char  logmsg[1024];
		(void)sprintf(logmsg, "prov_vnode is NULL for job %s", pjob->ji_wattr[(int)JOB_ATR_hashname].at_val.at_str);
		log_event(PBSEVENT_SYSTEM, PBS_EVENTCLASS_SERVER, LOG_INFO, "Bug", logmsg);

		return;
	}
#endif /* localmod 136 */
	nd = 18 + strlen(pjob->ji_wattr[(int)JOB_ATR_prov_vnode].at_val.at_str);
	if (nd > len)
		if (grow_acct_buf(&pb, &len, nd) == -1)
			return;
	(void)sprintf(pb, "provision_vnode=%s ",
		pjob->ji_wattr[(int)JOB_ATR_prov_vnode].at_val.at_str);
	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(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 (pjob->ji_wattr[(int)JOB_ATR_gridname].at_flags & ATR_VFLAG_SET) {
		nd = strlen(pjob->ji_wattr[(int)JOB_ATR_gridname].at_val.at_str)
			+ sizeof(GRIDNAME_FMT);
		if (nd > len)
			if (grow_acct_buf(&pb, &len, nd) == -1)
				return (pb);

		(void)snprintf(pb, len, GRIDNAME_FMT,
			pjob->ji_wattr[(int)JOB_ATR_gridname].at_val.at_str);
		ct = strlen(pb);
		pb  += ct;
		len -= ct;
	}

	/* user */
	nd = sizeof(USER_FMT) + strlen(pjob->ji_wattr[(int)JOB_ATR_euser].at_val.at_str);
	if (nd > len)
		if (grow_acct_buf(&pb, &len, nd) == -1)
			return (pb);
#ifdef WIN32
	(void)snprintf(pb, len, USER_FMT,
		replace_space(pjob->ji_wattr[(int)JOB_ATR_euser].at_val.at_str,
		acctlog_spacechar));
#else
	(void)snprintf(pb, len, USER_FMT,
		pjob->ji_wattr[(int)JOB_ATR_euser].at_val.at_str);
#endif
	ct = strlen(pb);
	pb  += ct;
	len -= ct;

	/* group */
	nd = sizeof(GROUP_FMT) + strlen(pjob->ji_wattr[(int)JOB_ATR_egroup].at_val.at_str);
	if (nd > len)
		if (grow_acct_buf(&pb, &len, nd) == -1)
			return (pb);
#ifdef WIN32
	(void)snprintf(pb, len, GROUP_FMT,
		replace_space(pjob->ji_wattr[(int)JOB_ATR_egroup].at_val.at_str,
		acctlog_spacechar));
#else
	(void)snprintf(pb, len, GROUP_FMT,
		pjob->ji_wattr[(int)JOB_ATR_egroup].at_val.at_str);
#endif
	ct = strlen(pb);
	pb  += ct;
	len -= ct;

	/* account */
	if (pjob->ji_wattr[(int)JOB_ATR_account].at_flags & ATR_VFLAG_SET) {
		nd = sizeof(ACCOUNT_FMT) + strlen(pjob->ji_wattr[(int)JOB_ATR_account].at_val.at_str);
		if (nd > len)
			if (grow_acct_buf(&pb, &len, nd) == -1)
				return (pb);
#ifdef WIN32
		(void)snprintf(pb, len, ACCOUNT_FMT,
			replace_space(
			pjob->ji_wattr[(int)JOB_ATR_account].at_val.at_str,
			acctlog_spacechar));
#else
		(void)snprintf(pb, len, ACCOUNT_FMT,
			pjob->ji_wattr[(int)JOB_ATR_account].at_val.at_str);
#endif
		ct = strlen(pb);
		pb  += ct;
		len -= ct;
	}

	/* project */
	if (pjob->ji_wattr[(int)JOB_ATR_project].at_flags & ATR_VFLAG_SET) {
		char *projstr;

		projstr =  pjob->ji_wattr[(int)JOB_ATR_project].at_val.at_str;
		/* 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 (pjob->ji_wattr[(int)JOB_ATR_acct_id].at_flags & ATR_VFLAG_SET) {
		nd = sizeof(ACCOUNTING_ID_FMT) + strlen(pjob->ji_wattr[(int)JOB_ATR_acct_id].at_val.at_str);
		if (nd > len)
			if (grow_acct_buf(&pb, &len, nd) == -1)
				return (pb);
		(void)snprintf(pb, len, ACCOUNTING_ID_FMT,
			pjob->ji_wattr[(int)JOB_ATR_acct_id].at_val.at_str);
		ct = strlen(pb);
		pb  += ct;
		len -= ct;
	}

	/* job name */
	nd = sizeof(JOBNAME_FMT) + strlen(pjob->ji_wattr[(int)JOB_ATR_jobname].at_val.at_str);
	if (nd > len)
		if (grow_acct_buf(&pb, &len, nd) == -1)
			return (pb);
	(void)snprintf(pb, len, JOBNAME_FMT,
		pjob->ji_wattr[(int)JOB_ATR_jobname].at_val.at_str);
	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 (pjob->ji_myResv->ri_wattr[(int)RESV_ATR_resv_name].at_flags
			& ATR_VFLAG_SET)
			nd += sizeof(RESVNAME_FMT) + strlen(pjob->ji_myResv->ri_wattr[(int)RESV_ATR_resv_name].at_val.at_str);
		if (nd > len)
			if (grow_acct_buf(&pb, &len, nd) == -1)
				return (pb);
		/* reservation name */
		if (pjob->ji_myResv->ri_wattr[(int)RESV_ATR_resv_name].at_flags
			& ATR_VFLAG_SET) {
			(void)snprintf(pb, len, RESVNAME_FMT,
				pjob->ji_myResv->ri_wattr[(int)
				RESV_ATR_resv_name].
				at_val.at_str);
			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;
	}

	/* resvjob ID */
	if (pjob->ji_resvp) {
		nd = sizeof(RESVJOBID_FMT) + strlen(pjob->ji_resvp->ri_qs.ri_resvID);
		if (nd > len)
			if (grow_acct_buf(&pb, &len, nd) == -1)
				return (pb);
		(void)snprintf(pb, len, RESVJOBID_FMT,
			pjob->ji_resvp->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 */
	(void)sprintf(pb, "ctime=%ld ",
		pjob->ji_wattr[(int)JOB_ATR_ctime].at_val.at_long);
	ct = strlen(pb);
	pb  += ct;
	len -= ct;

	/* queued time */
	(void)sprintf(pb, "qtime=%ld ",
		pjob->ji_wattr[(int)JOB_ATR_qtime].at_val.at_long);
	ct = strlen(pb);
	pb  += ct;
	len -= ct;

	/* eligible time, how long ready to run */
	(void)sprintf(pb, "etime=%ld ", pjob->ji_wattr[(int)JOB_ATR_etime].
		at_val.at_long);
	ct = strlen(pb);
	pb  += ct;
	len -= ct;

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

	if ((pjob->ji_wattr[(int)JOB_ATR_array_indices_submitted].at_flags & ATR_VFLAG_SET) && (pjob->ji_qs.ji_state == JOB_STATE_BEGUN)) {

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

		nd = sizeof(ARRAY_INDICES_FMT) + strlen(pjob->ji_wattr[(int)JOB_ATR_array_indices_submitted].at_val.at_str);
		if (nd > len)
			if (grow_acct_buf(&pb, &len, nd) == -1)
				return (pb);
		(void)snprintf(pb, len, ARRAY_INDICES_FMT, pjob->ji_wattr[(int)JOB_ATR_array_indices_submitted].at_val.at_str);
		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(
			&pjob->ji_wattr[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 (pjob->ji_wattr[att_index].at_flags & ATR_VFLAG_SET) {
			/* execution host list, may be loooong */
			nd = sizeof(EXEC_HOST_FMT) + strlen(pjob->ji_wattr[att_index].at_val.at_str);
			if (nd > len)
				if (grow_acct_buf(&pb, &len, nd) == -1)
					return (pb);
			(void)snprintf(pb, len, EXEC_HOST_FMT,
				pjob->ji_wattr[att_index].at_val.at_str);
			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 (pjob->ji_wattr[att_index].at_flags & ATR_VFLAG_SET) {
			/* execution vnode list, will be even longer */
			nd = sizeof(EXEC_VNODE_FMT) + strlen(pjob->ji_wattr[att_index].at_val.at_str);
			if (nd > len)
				if (grow_acct_buf(&pb, &len, nd) == -1)
					return (pb);
			(void)snprintf(pb, len, EXEC_VNODE_FMT,
				pjob->ji_wattr[att_index].at_val.at_str);
			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(
			&pjob->ji_wattr[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;
	char	save_char = '\0';
	int	old_perm;


	if ((pjob->ji_wattr[(int)JOB_ATR_exec_vnode_acct].at_flags & ATR_VFLAG_SET) == 0) {
		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;
	(void)sprintf(pb, "session=%ld",
		pjob->ji_wattr[(int)JOB_ATR_session_id].at_val.at_long);
	i = strlen(pb);
	pb  += i;
	len -= i;

	/* Alternate id if present */

	if (pjob->ji_wattr[(int)JOB_ATR_altid].at_flags & ATR_VFLAG_SET) {
		/* 9 is for length of " alt_id=" and \0 */
		i = 9 + strlen(pjob->ji_wattr[(int)JOB_ATR_altid].at_val.at_str);
		if (i > len)
			if (grow_acct_buf(&pb, &len, i) == -1)
				goto writeit;
#ifdef WIN32
		(void)sprintf(pb, " alt_id=%s",
			replace_space(
			pjob->ji_wattr[(int)JOB_ATR_altid].at_val.at_str,
			acctlog_spacechar));
#else
		(void)sprintf(pb, " alt_id=%s",
			pjob->ji_wattr[(int)JOB_ATR_altid].at_val.at_str);
#endif
		i = strlen(pb);
		pb  += i;
		len -= i;
	}

	/* Add eligible_time */
	if (server.sv_attr[(int)SRV_ATR_EligibleTimeEnable].at_val.at_long == 1) {
		char timebuf[TIMEBUF_SIZE] = {0};
		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(pjob->ji_wattr[(int)JOB_ATR_eligible_time].at_val.at_long, 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",
		pjob->ji_wattr[(int)JOB_ATR_runcount].at_val.at_long);

	/* now encode the job's resources_used attribute */
	old_perm = resc_access_perm;
	resc_access_perm = READ_ONLY;
	(void)job_attr_def[(int)JOB_ATR_resc_used_update].at_encode(
			&pjob->ji_wattr[(int)JOB_ATR_resc_used_update],
			&attrlist,
			job_attr_def[(int)JOB_ATR_resc_used_update].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);
	len_upd = 7; /* for length of "_update" */
	while (patlist) {
		/* 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 (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 (k > len_upd) {
					patlist->al_name[k-len_upd] = save_char;
				}
				goto writeit;
			}
			if (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] = {0};

					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 != NULL) {
		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;

		if ((pjob->ji_wattr[JOB_ATR_resc_used_acct].at_flags & ATR_VFLAG_SET) != 0) {
			job_attr_def[JOB_ATR_resc_used_acct].at_free(&pjob->ji_wattr[JOB_ATR_resc_used_acct]);
			pjob->ji_wattr[JOB_ATR_resc_used_acct].at_flags &= ~ATR_VFLAG_SET;
		}
		job_attr_def[JOB_ATR_resc_used_acct].at_set( &pjob->ji_wattr[JOB_ATR_resc_used_acct], &pjob->ji_wattr[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);
	}
}
