/*
 * 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	misc_utils.c
 * @brief
 *  Utility functions to condense and unroll a sequence of execvnodes that are
 *  returned by the scheduler for standing reservations.
 *  The objective is to condense in a human-readable format the execvnodes
 *  of each occurrence of a standing reservation, and be able to retrieve each
 *  such occurrence easily.
 *
 *  Example usage (also refer to the debug function int test_execvnode_seq
 *  for a code snippet):
 *
 *  Assume str points to some string.
 *  char *condensed_str;
 *  char **unrolled_str;
 *  char **tofree;
 *
 *  condensed_str = condense_execvnode_seq(str);
 *  unrolled_str = unroll_execvnode_seq(condensed_str, &tofree);
 *  ...access an arbitrary, say 2nd occurrence, index via unrolled_str[2]
 *  free_execvnode_seq(tofree);
 */
#define _MISC_UTILS_C
#include <pbs_config.h> /* the master config generated by configure */

#include <libutil.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <libpbs.h>
#include <limits.h>
#include <pbs_idx.h>
#include <pbs_ifl.h>
#include <pbs_internal.h>
#include <pbs_sched.h>
#include <pbs_share.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <pwd.h>
#include <assert.h>
#include <netinet/in.h>
#include <dlfcn.h>
#include <grp.h>
#include <time.h>
#include <sys/time.h>
#include <sys/resource.h>

#include "pbs_error.h"
#include "job.h"

#ifdef HAVE_MALLOC_INFO
#include <malloc.h>
#endif

#define ISESCAPED(ch) (ch == '\'' || ch == '\"' || ch == ',')

/** @brief conversion array for vnode sharing attribute between str and enum */
struct {
	char *vn_str;
	enum vnode_sharing vns;
} str2vns[] = {
	{ND_Default_Shared, VNS_DFLT_SHARED},
	{ND_Ignore_Excl, VNS_IGNORE_EXCL},
	{ND_Ignore_Excl, VNS_FORCE_SHARED},
	{ND_Default_Excl, VNS_DFLT_EXCL},
	{ND_Force_Excl, VNS_FORCE_EXCL},
	{ND_Default_Exclhost, VNS_DFLT_EXCLHOST},
	{ND_Force_Exclhost, VNS_FORCE_EXCLHOST}};

/* Used for collecting performance stats */
typedef struct perf_stat {
	char instance[MAXBUFLEN + 1];
	double walltime;
	double cputime;
	pbs_list_link pi_allstats;
} perf_stat_t;

static int perf_stats_initialized = 0;
static pbs_list_head perf_stats;

/**
 * @brief
 * 	char_in_set - is the char c in the tokenset
 *
 * @param[in] c - the char
 * @param[in] tokset - string tokenset
 *
 * @return	int
 * @retval	1 	if c is in tokset
 * @retval	0 	if c is not in tokset
 */
int
char_in_set(char c, const char *tokset)
{

	int i;

	for (i = 0; tokset[i] != '\0'; i++)
		if (c == tokset[i])
			return 1;

	return 0;
}

/**
 * @brief
 * 	string_token - strtok() without an an internal state pointer
 *
 * @param[in]      str - the string to tokenize
 * @param[in] 	   tokset - the tokenset to look for
 * @param[in/out]  ret_str - the char ptr where we left off after the tokens
 *		             ** ret_str is opaque to the caller
 *
 * @par	call:
 *	string_token( string, tokenset, &tokptr)
 *	2nd call: string_token( NULL, tokenset2, &tokptr)
 *
 * @par	tokenset can differ between the two calls (as per strtok())
 *	tokptr is an opaque ptr, just keep passing &tokptr into all calls
 *	to string_token()
 *
 * @return	char pointer
 * @retval	returns ptr to front of string segment (as per strtok())
 *
 */
char *
string_token(char *str, const char *tokset, char **ret_str)
{
	char *tok;
	char *search_string;

	if (str != NULL)
		search_string = str;
	else if (ret_str != NULL && *ret_str != NULL)
		search_string = *ret_str;
	else
		return NULL;

	tok = strstr(search_string, tokset);

	if (tok != NULL) {
		while (char_in_set(*tok, tokset) && *tok != '\0') {
			*tok = '\0';
			tok++;
		}

		if (ret_str != NULL)
			*ret_str = tok;
	} else
		*ret_str = NULL;

	return search_string;
}

/**
 *	@brief convert vnode sharing enum into string form

 * 	@par Note:
 * 		Do not free the return value - it's a statically allocated
 *		string.
 *
 *	@param[in] vns - vnode sharing value
 *
 *	@retval string form of sharing value
 *	@retval NULL on error
 */
char *
vnode_sharing_to_str(enum vnode_sharing vns)
{
	int i;
	int size = sizeof(str2vns) / sizeof(str2vns[0]);

	for (i = 0; i < size && str2vns[i].vns != vns; i++)
		;

	if (i == size)
		return NULL;

	return str2vns[i].vn_str;
}

/**
 *	@brief convert string form of vnode sharing to enum
 *
 *	@param[in] vn_str - vnode sharing  string
 *
 *	@return	enum
 *	@retval vnode sharing value
 *	@retval VNS_UNSET if not found
 */
enum vnode_sharing
str_to_vnode_sharing(char *vn_str)
{
	int i;
	int size = sizeof(str2vns) / sizeof(str2vns[0]);

	if (vn_str == NULL)
		return VNS_UNSET;

	for (i = 0; i < size && strcmp(vn_str, str2vns[i].vn_str) != 0; i++)
		;

	if (i == size)
		return VNS_UNSET;

	return str2vns[i].vns;
}

/**
 *
 * @brief concatenate two strings by expanding target string as needed.
 * 	  Operation: strbuf += str
 *
 *	@param[in, out] strbuf - string that will expand to accommodate the
 *			        concatenation of 'str' - if null, new buffer allocated
 *	@param[in, out] ssize - if not NULL, allocated size of strbuf
 *	@param[in]      str   - string to concatenate to 'strbuf'
 *
 *	@return char *
 *	@retval pointer to the resulting string on success (*strbuf)
 *	@retval NULL on failure
 */
char *
pbs_strcat(char **strbuf, int *ssize, const char *str)
{
	int len;
	int rbuf_len;
	char *tmp;
	char *rbuf;
	int size;

	if (str == NULL)
		return *strbuf;

	rbuf = *strbuf;
	size = ssize == NULL ? 0 : *ssize;

	len = strlen(str);
	rbuf_len = rbuf == NULL ? 0 : strlen(rbuf);

	if (rbuf_len + len >= size) {
		if (len > size)
			size = len * 2;
		else
			size *= 2;

		tmp = realloc(rbuf, size + 1);
		if (tmp == NULL)
			return NULL;
		if (ssize)
			*ssize = size;
		*strbuf = tmp;
		rbuf = tmp;
		/* first allocate */
		if (rbuf_len == 0)
			rbuf[0] = '\0';
	}

	return strcat(rbuf, str);
}

/**
 *
 * @brief special purpose strcpy for chain copying strings
 *        primary difference with normal strcpy is that it
 *        returns the destination buffer position just past
 *        the copied data. Thus the next string can be just
 *        added to the returned pointer.
 *
 * @param[in] dest - pointer to the destination buffer
 * @param[in] src  - pointer to the source buffer
 *
 * @return char *
 * @retval pointer to the end of the resulting string
 *
 * @note: Caller needs to ensure space and non-NULL pointers
 *        This function is created for performance so does not
 *        verify any paramaters
 */
char *
pbs_strcpy(char *dest, const char *src)
{
	while (*src)
		*dest++ = *src++;

	*dest = '\0';

	return dest;
}
/**
 *
 * @brief general purpose strncpy function that will make sure to
 *        copy '\0' at the end of the buffer.
 *
 * @param[in] dest - pointer to the destination buffer
 * @param[in] src  - pointer to the source string
 * @param[in] n    - size of destination buffer
 *
 * @return char *
 * @retval pointer to the destination string
 *
 * @note: Caller needs ensure non-NULL pointers
 */
char *
pbs_strncpy(char *dest, const char *src, size_t n)
{
	if (strlen(src) < n - 1)
		strcpy(dest, src);
	else {
		memcpy(dest, src, n - 1);
		dest[n - 1] = '\0';
	}
	return dest;
}

/**
 * @brief
 *	get a line from a file of any length.  Extend string via realloc
 *	if necessary
 *
 * @param fp[in] - open file
 * @param pbuf[in,out] - pointer to buffer to fill (may change ala realloc)
 * @param pbuf_size[in,out] - size of buf (may increase ala realloc)
 *
 * @return char *
 * @retval pointer to *pbuf(the string pbuf points at) on successful read
 * @retval NULL on EOF or error
 */
#define PBS_FGETS_LINE_LEN 8192
char *
pbs_fgets(char **pbuf, int *pbuf_size, FILE *fp)
{
	char fbuf[PBS_FGETS_LINE_LEN];
	char *buf;
	char *p;

	if (fp == NULL || pbuf == NULL || pbuf_size == NULL)
		return NULL;

	if (*pbuf_size == 0) {
		if ((*pbuf = malloc(PBS_FGETS_LINE_LEN)) == NULL)
			return NULL;
		*pbuf_size = PBS_FGETS_LINE_LEN;
	}
	buf = *pbuf;

	buf[0] = '\0';
	while ((p = fgets(fbuf, PBS_FGETS_LINE_LEN, fp)) != NULL) {
		buf = pbs_strcat(pbuf, pbuf_size, fbuf);
		if (buf == NULL)
			return NULL;

		if (buf[strlen(buf) - 1] == '\n') /* we've reached the end of the line */
			break;
	}
	if (p == NULL && buf[0] == '\0')
		return NULL;

	return *pbuf;
}

/**
 * @brief
 * 	Helper function for pbs_fgets_extend() and callers to determine if string requires extending
 *
 * @param[in] buf - line to check for extendable ending
 *
 * @return int
 * @retval offset to extendable location, -1 if not extendable
 */
int
pbs_extendable_line(char *buf)
{
	int len = 0;

	if (buf == NULL)
		return 0;

	len = strlen(buf);

	/* we have two options:
	 * 1) We extend: string ends in a '\' and 0 or more whitespace
	 * 2) we do not extend: Not #1
	 * In the case of #1, we want the string to end just before the '\'
	 * In the case of #2 we want to leave the string alone.
	 */
	while (len > 0 && isspace(buf[len - 1]))
		len--;

	if (len > 0 && buf[len - 1] == '\\')
		return len - 1;
	else /* We're at the end of a non-extended line */
		return -1;
}

/**
 * @brief get a line from a file pointed at by fp.  The line can be extended
 *	  onto the next line if it ends in a backslash (\).  If the string is
 *	  extended, the lines will be combined and the backslash will be
 *        stripped.
 *
 * @param[in] fp pointer to file to read from
 * @param[in, out] pbuf_size - pointer to size of buffer
 * @param[in, out] pbuf - pointer to buffer
 *
 * @return char *
 * @retval string read from file
 * @retval NULL - EOF or error
 * @par MT-Safe: no
 */
char *
pbs_fgets_extend(char **pbuf, int *pbuf_size, FILE *fp)
{
	static char *locbuf = NULL;
	static int locbuf_size = 0;
	char *buf;
	char *p;
	int len;

	if (pbuf == NULL || pbuf_size == NULL || fp == NULL)
		return NULL;

	if (locbuf == NULL) {
		if ((locbuf = malloc(PBS_FGETS_LINE_LEN)) == NULL)
			return NULL;
		locbuf_size = PBS_FGETS_LINE_LEN;
	}

	if (*pbuf_size == 0 || *pbuf == NULL) {
		if ((*pbuf = malloc(PBS_FGETS_LINE_LEN)) == NULL)
			return NULL;
		*pbuf_size = PBS_FGETS_LINE_LEN;
	}

	buf = *pbuf;
	locbuf[0] = '\0';
	buf[0] = '\0';

	while ((p = pbs_fgets(&locbuf, &locbuf_size, fp)) != NULL) {
		if (pbs_strcat(pbuf, pbuf_size, locbuf) == NULL)
			return NULL;

		buf = *pbuf;
		len = pbs_extendable_line(buf);
		if (len >= 0)
			buf[len] = '\0'; /* remove the backslash (\) */
		else
			break;
	}

	/* if we read just EOF */
	if (p == NULL && buf[0] == '\0')
		return NULL;

	return buf;
}

/**
 * @brief
 * 	Internal helper function for pbs_asprintf() to determine the length of post-formatted string
 *
 * @param[in] fmt - printf format string
 * @param[in] args - va_list arguments from pbs_asprintf()
 *
 * @return int
 * @retval length of post-formatted string
 */
int
pbs_asprintf_len(const char *fmt, va_list args)
{
	int len;
#ifdef WIN32
	len = _vscprintf(fmt, args);
#else
	{
		va_list dupargs;
		char c;

		va_copy(dupargs, args);
		len = vsnprintf(&c, 0, fmt, dupargs);
		va_end(dupargs);
	}
#endif
	return len;
}

/**
 * @brief
 * 	Internal helper function for pbs_asprintf() to allocate memory and format the string
 *
 * @param[in] len - length of post-formatted string
 * @param[in] fmt - format for printed string
 * @param[in] args - va_list arguments from pbs_asprintf()
 *
 * @return char *
 * @retval formatted string in allocated buffer
 */

char *
pbs_asprintf_format(int len, const char *fmt, va_list args)
{
	char *buf;
	int rc;
	buf = malloc(len + 1);
	if (!buf)
		return NULL;
	rc = vsnprintf(buf, len + 1, fmt, args);
	if (rc != len) {
		free(buf);
		return NULL;
	}
	return buf;
}

/**
 * @brief
 *	Internal asprintf() implementation for use on all platforms
 *
 * @param[in, out] dest - character pointer that will point to allocated
 *			  space ** must be freed by caller **
 * @param[in] fmt - format for printed string
 * @param[in] ... - arguments to format string
 *
 * @return int
 * @retval -1 - Error
 * @retval >=0 - Length of new string, not including terminator
 */
int
pbs_asprintf(char **dest, const char *fmt, ...)
{
	va_list args;
	int len;
	char *buf = NULL;

	if (!dest)
		return -1;
	*dest = NULL;
	if (!fmt)
		return -1;
	va_start(args, fmt);
	len = pbs_asprintf_len(fmt, args);
	if (len < 0)
		goto pbs_asprintf_exit;

	buf = pbs_asprintf_format(len, fmt, args);
	if (buf == NULL)
		goto pbs_asprintf_exit;
	*dest = buf;
pbs_asprintf_exit:
	va_end(args);
	if (buf == NULL) {
		buf = malloc(1);
		if (buf) {
			*buf = '\0';
			*dest = buf;
			return -1;
		}
	}
	return len;
}

/**

 * @brief
 *	Copies 'src' file  to 'dst' file.
 *
 * @param[in] src - file1
 * @param[in] dst - file2
 *
 * @return int
 * @retval 0	- success
 * @retval COPY_FILE_BAD_INPUT	- dst or src is NULL.
 * @retval COPY_FILE_BAD_SOURCE	- failed to open 'src' file.
 * @retval COPY_FILE_BAD_DEST - failed to open 'dst' file.
 * @retval COPY_FILE_BAD_WRITE	- incomplete write
 */
int
copy_file_internal(char *src, char *dst)
{
	FILE *fp_orig = NULL;
	FILE *fp_copy = NULL;
	char in_data[BUFSIZ + 1];

	if ((src == NULL) || (dst == NULL)) {
		return (COPY_FILE_BAD_INPUT);
	}

	fp_orig = fopen(src, "r");

	if (fp_orig == NULL) {
		return (COPY_FILE_BAD_SOURCE);
	}

	fp_copy = fopen(dst, "w");

	if (fp_copy == NULL) {
		fclose(fp_orig);
		return (COPY_FILE_BAD_DEST);
	}

	while (fgets(in_data, sizeof(in_data),
		     fp_orig) != NULL) {
		if (fputs(in_data, fp_copy) < 0) {
			fclose(fp_orig);
			fclose(fp_copy);
			(void) unlink(dst);
			return (COPY_FILE_BAD_WRITE);
		}
	}

	fclose(fp_orig);
	if (fclose(fp_copy) != 0) {
		return (COPY_FILE_BAD_WRITE);
	}

	return (0);
}

/**
 * @brief
 * 	Puts an advisory lock of type 'op' to the file whose descriptor
 *	is 'fd'.
 *
 * @param[in]	fd - descriptor of file being locked.
 * @param[in]	op - type of lock: F_WRLCK, F_RDLCK, F_UNLCK
 * @param[in]	filename -  corresonding name to 'fp' for logging purposes.
 * @param[in]	lock_retry - number of attempts to retry lock if there's a
 *			     failure to lock.
 * @param[out]	err_msg - filled with the error message string if there's a
 *			  failure to lock. (can be NULL if error message need
 *			  not be saved).
 * @param[in]	err_msg_len - size of the err_msg buffer.
 *
 * @return 	int
 * @retval 	0	for success
 * @reval	1	for failure to lock
 *
 */

int
lock_file(int fd, int op, char *filename, int lock_retry,
	  char *err_msg, size_t err_msg_len)
{
	int i;
	struct flock flock;

	lseek(fd, (off_t) 0, SEEK_SET);
	flock.l_type = op;
	flock.l_whence = SEEK_SET;
	flock.l_start = 0;
	flock.l_len = 0;

	for (i = 0; i < lock_retry; i++) {
		if ((fcntl(fd, F_SETLK, &flock) == -1) &&
		    ((errno == EACCES) || (errno == EAGAIN))) {
			if (err_msg != NULL)
				snprintf(err_msg, err_msg_len,
					 "Failed to lock file %s, retrying", filename);
		} else {
			return 0; /* locked */
		}
		if (i < (lock_retry - 1))
			sleep(2);
	}
	if (err_msg != NULL)
		snprintf(err_msg, err_msg_len,
			 "Failed to lock file %s, giving up", filename);
	return 1;
}

/**
 * @brief
 *	calculate the number of digits to the right of the decimal point in
 *	a floating point number.  This can be used in conjunction with
 *	printf() to not print trailing zeros.
 *
 * @param[in] fl - the float point number
 * @param[in] digits - the max number of digits to check.  Can be -1 for max
 *            number of digits for 32/64 bit numbers.
 *
 * @par	Use: int x = float_digits(fl, 8);
 * 	printf("%0.*f\n", x, fl);
 *
 * @note It may be unwise to use use a large value for digits (or -1) due to
 * that the precision of a double will decrease after the first handful of
 * digits.
 *
 * @return int
 * @retval number of digits to the right of the decimal point in fl.
 *         in the range of 0..digits
 *
 * @par MT-Safe: Yes
 */

#define FLOAT_DIGITS_ERROR_FACTOR 1000.0
/* To be more generic, we should use a signed integer type.
 * This is fine for our current use and gives us 1 more digit.
 */
#define TRUNCATE(x) (((x) > (double) ULONG_MAX) ? ULONG_MAX : (unsigned long) (x))

int
float_digits(double fl, int digits)
{
	unsigned long num;
	int i;

	/* 2^64 = 18446744073709551616 (18 useful)
	 * 2^32 = 4294967296 (9 useful)
	 */
	if (digits == -1)
		digits = (sizeof(unsigned long) >= 8) ? 18 : 9;

	fl = ((fl < 0) ? -fl : fl);

	/* The main part of the algorithm: Floating point numbers are not very exact.
	 * We need to do something to determine how close we are to the right number
	 * We multiply our floating point value by an error factor.  If we see a
	 * string of 9's or 0's in a row, we stop.  For example, if the error factor
	 * is 1000, if we see 3 9's or 0's we stop.  Every time through the loop we
	 * multiply by 10 to shift over one digit and repeat.
	 */
	for (i = 0; i < digits; i++) {
		num = TRUNCATE((fl - TRUNCATE(fl)) * FLOAT_DIGITS_ERROR_FACTOR);
		if ((num < 1) || (num >= (long) (FLOAT_DIGITS_ERROR_FACTOR - 1.0)))
			break;
		fl *= 10.0;
	}
	return i;
}

/**
 *
 * @brief
 *	Returns 1 for path is a full path; otherwise, 0 if
 * 	relative path.
 *
 * @param[in]	path	- the filename path being checked.
 *
 * @return int
 * @retval	1	if 'path' is a full path.
 * @retval	0	if 'path' is  relative path
 */
int
is_full_path(char *path)
{
	char *cp = path;

	if (*cp == '"')
		++cp;

#ifdef WIN32
	if ((cp[0] == '/') || (cp[0] == '\\') ||
	    (strlen(cp) >= 3 &&
	     isalpha(cp[0]) && cp[1] == ':' &&
	     ((cp[2] == '\\') || (cp[2] == '/'))))
	/* matches c:\ or c:/ */
#else
	if (cp[0] == '/')
#endif
		return (1);
	return (0);
}

/**
 * @brief
 *	Replace sub with repl in str.
 *
 * @par Note
 *	same as replace_space except the matching character to replace
 *      is not necessarily a space but the supplied 'sub' string, plus leaving
 *      alone existing 'repl' sub strings and no quoting if 'repl' is ""
 *
 * @param[in]	str  - input buffer having patter sub
 * @param[in]	sub  - pattern to be replaced
 * @param[in]   repl - pattern to be replaced with
 * @param[out]  retstr : string replaced with given pattern.
 *
 */

void
replace(char *str, char *sub, char *repl, char *retstr)
{
	char rstr[MAXPATHLEN + 1];
	int i, j;
	int repl_len;
	int has_match = 0;
	int sub_len;

	if (str == NULL || repl == NULL || sub == NULL)
		return;

	if (*str == '\0') {
		retstr[0] = '\0';
		return;
	}

	if (*sub == '\0') {
		strcpy(retstr, str);
		return;
	}

	repl_len = strlen(repl);
	sub_len = strlen(sub);

	i = 0;
	while (*str != '\0') {
		if (strncmp(str, sub, sub_len) == 0 &&
		    repl_len > 0) {
			for (j = 0; (j < repl_len && i <= MAXPATHLEN); j++, i++) {
				rstr[i] = repl[j];
			}
			has_match = 1;
		} else if (strncmp(str, sub, sub_len) == 0) {
			for (j = 0; (j < sub_len && i <= MAXPATHLEN); j++, i++) {
				rstr[i] = sub[j];
			}
			has_match = 1;
		} else {
			rstr[i] = *str;
			i++;
			has_match = 0;
		}

		if (i > MAXPATHLEN) {
			retstr[0] = '\0';
			return;
		}

		if (has_match) {
			str += sub_len;
		} else {
			str++;
		}
	}
	rstr[i] = '\0';

	strncpy(retstr, rstr, i + 1);
}

/**
 * @brief
 *	Escape every occurrence of 'delim' in 'str' with 'esc'
 *
 * @param[in]	str     - input string
 * @param[in]	delim   - delimiter to be searched in str
 * @param[in]	esc     - escape character to be added if delim found in str
 *
 * @return	string
 * @retval	NULL	- memory allocation failed or str is NULL
 * @retval	retstr	- output string, with every occurrence of delim escaped with 'esc'
 *
 * @note
 * 	The string returned should be freed by the caller.
 */

char *
escape_delimiter(char *str, char *delim, char esc)
{
	int i = 0;
	int j = 0;
	int delim_len = 0;
	int retstrlen = 0;
	char *retstr = NULL;
	char *temp = NULL;

	if (str == NULL)
		return NULL;

	if (*str == '\0' || (delim == NULL || *delim == '\0') || esc == '\0') {
		return strdup((char *) str);
	}
	delim_len = strlen(delim);
	retstr = (char *) malloc(MAXBUFLEN);
	if (retstr == NULL)
		return NULL;
	retstrlen = MAXBUFLEN;

	while (*str != '\0') {
		/* We dont want to use strncmp function if delimiter is a character. */
		if ((*str == esc && !ISESCAPED(*(str + 1))) || (delim_len == 1 && *str == *delim)) {
			retstr[i++] = esc;
			retstr[i++] = *str++;
		} else if (strncmp(str, delim, delim_len) == 0 && ((i + 1 + delim_len) < retstrlen)) {
			retstr[i++] = esc;
			for (j = 0; j < delim_len; j++, i++)
				retstr[i] = *str++;
		} else if ((i + 1 + delim_len) < retstrlen)
			retstr[i++] = *str++;

		if (i >= (retstrlen - (1 + delim_len))) {
			retstrlen *= BUFFER_GROWTH_RATE;
			temp = (char *) realloc(retstr, retstrlen);
			if (temp == NULL) {
				free(retstr);
				return NULL;
			}
			retstr = temp;
		}
	}
	retstr[i] = '\0';
	return retstr;
}

/**
 *
 * @brief
 * 	file_exists: returns 1 if file exists; 0 otherwise.
 *
 * @param[in]	path - file pathname being checked.
 *
 * @return 	int
 * @retval	1	if 'path' exists.
 * @retval	0	if 'path'does not exist.
 */
int
file_exists(char *path)
{
	struct stat sbuf;

#ifdef WIN32
	if (lstat(path, &sbuf) == -1) {
		int ret = GetLastError();
		if (ret == ERROR_FILE_NOT_FOUND ||
		    ret == ERROR_PATH_NOT_FOUND) {
			return 0;
		}
	}
#else
	if ((stat(path, &sbuf) == -1) &&
	    (errno == ENOENT))
		return (0);
#endif
	return (1);
}

/**
 * @brief
 *	Given the two hostnames, compare their short names and full names to make sure if those
 *      point to same host.
 *
 * @param[in]	        host1 - first host
 * @param[in]           host2 - second host
 *
 * @return              int
 * @retval	        0 - hostnames point to different hosts
 * @retval	        1 - hostnames point to same host
 *
 */
int
is_same_host(char *host1, char *host2)
{
	char *host1_f = NULL;
	char *host2_f = NULL;

	static void *hostmap = NULL;

	if (host1 == NULL || host2 == NULL)
		return 0;

	if (hostmap == NULL)
		hostmap = pbs_idx_create(0, 0);

	if (strcasecmp(host1, host2) == 0)
		return 1;

	pbs_idx_find(hostmap, (void **) &host1, (void **) &host1_f, NULL);
	pbs_idx_find(hostmap, (void **) &host2, (void **) &host2_f, NULL);

	if (host1_f == NULL) {
		char host1_full[PBS_MAXHOSTNAME + 1];

		if (get_fullhostname(host1, host1_full, PBS_MAXHOSTNAME) != 0 || host1_full[0] == '\0')
			return 0;
		host1_f = strdup(host1_full);
		pbs_idx_insert(hostmap, host1, host1_f);
	}
	if (host2_f == NULL) {
		char host2_full[PBS_MAXHOSTNAME + 1];

		if (get_fullhostname(host2, host2_full, PBS_MAXHOSTNAME) != 0 || host2_full[0] == '\0')
			return 0;
		host2_f = strdup(host2_full);
		pbs_idx_insert(hostmap, host2, host2_f);
	}
	if (host1_f == NULL || host2_f == NULL)
		return 0;

	if (strcasecmp(host1_f, host2_f) == 0)
		return 1;

	return 0;
}

/**
 * @brief Determine if place_def is in place_str
 * @see place_sharing_type and getplacesharing
 *
 * @param[in] place_str - The string representation of the place directive
 * @param[in] place_def - The type of place to check
 *
 * @return Whether the place directive contains the type of exclusivity
 * queried for.
 * @retval 1 If the place directive is of the type queried
 * @retval 0 If the place directive is not of the type queried
 *
 *@par MT-Safe: No
 */
int
place_sharing_check(char *place_str, char *place_def)
{
	char *buf;
	char *p;
	char *psave;

	if ((place_str == NULL) || (*place_str == '\0'))
		return 0;

	if ((place_def == NULL) || (*place_def == '\0'))
		return 0;

	buf = strdup(place_str);
	if (buf == NULL)
		return 0;

	for (p = buf; (p = strtok_r(p, ":", &psave)) != NULL; p = NULL) {
		if (strcmp(p, place_def) == 0) {
			free(buf);
			return 1;
		}
	}
	free(buf);
	return 0;
}

/**
 *
 * @brief
 * 	Determines if 'str' is found in 'sep'-separated 'string_list'.
 *
 * @param[in]	str	- the substring to look for.
 * @param[in]	sep	- the separator character in 'string_list'
 * @param[in]	string_list - the list of characters to check for a 'str'
 *				match.
 * @return int
 * @retval	1	- if 'str' is found in 'string_list'.
 * @retval	0	- if 'str' not found.
 *
 * @note
 *	In the absence of a 'sep' value (i.e. empty string ''), then
 *	a white space is the default delimiter.
 *	If there's a 'sep' value, the white space character is also treated
 *	as an additional delimeter, matching only the strings that don't
 *	contain leading/trailing 'sep' char and white space character.
 */
int
in_string_list(char *str, char sep, char *string_list)
{
	char *p = NULL;
	char *p2 = NULL;
	char *p_end = NULL;
	char *ptoken = NULL;
	int found_match = 0;

	if ((str == NULL) || (str[0] == '\0') || (string_list == NULL)) {
		return (0);
	}

	p2 = strdup(string_list);
	if (p2 == NULL) {
		return (0);
	}

	p = p2;
	p_end = p + strlen(string_list);

	while (p < p_end) {

		/* skip past [<sep> ] characters */
		while ((*p != '\0') && ((*p == sep) || (*p == ' '))) {
			p++;
		}

		if (*p == '\0')
			break;

		ptoken = p; /* start of token */

		/* skip past not in [<sep> ] characters  */
		while ((*p != '\0') && ((*p != sep) && (*p != ' '))) {
			p++;
		}
		*p = '\0'; /* delimeter value is nulled */
		if (strcmp(str, ptoken) == 0) {
			found_match = 1;
			break;
		}
		p++;
	}

	if (p2) {
		(void) free(p2);
	}
	return (found_match);
}

/**
 *
 *	@brief break apart a delimited string into an array of strings
 *
 *	@param[in] strlist - the delimited string
 *	@param[in] sep - the separator character
 *
 *	@return char **
 *
 *	@note
 *		The returned array of strings has to be freed by the caller.
 */
char **
break_delimited_str(char *strlist, char delim)
{
	char sep[2] = {0};
	int num_words = 1; /* number of words delimited by commas*/
	char **arr = NULL; /* the array of words */
	char *list;
	char *tok; /* used with strtok() */
	char *end;
	int i;

	sep[0] = delim;

	if (strlist == NULL) {
		pbs_errno = PBSE_BADATVAL;
		return NULL;
	}

	list = strdup(strlist);

	if (list != NULL) {
		char *saveptr = NULL;

		for (i = 0; list[i] != '\0'; i++)
			if (list[i] == delim)
				num_words++;

		if ((arr = (char **) malloc(sizeof(char *) * (num_words + 1))) == NULL) {
			pbs_errno = PBSE_SYSTEM;
			free(list);
			return NULL;
		}

		tok = strtok_r(list, sep, &saveptr);

		for (i = 0; tok != NULL; i++) {
			while (isspace((int) *tok))
				tok++;

			end = &tok[strlen(tok) - 1];

			while (isspace((int) *end)) {
				*end = '\0';
				end--;
			}

			arr[i] = strdup(tok);
			if (arr[i] == NULL) {
				pbs_errno = PBSE_SYSTEM;
				free(list);
				free_string_array(arr);
				return NULL;
			}
			tok = strtok_r(NULL, sep, &saveptr);
		}
		arr[i] = NULL;
	}
	if (list != NULL)
		free(list);

	return arr;
}

/**
 *
 *	@brief break apart a comma delimited string into an array of strings
 *
 *	@param[in] strlist - the comma delimited string
 *
 *	@return char **
 *
 */
char **
break_comma_list(char *strlist)
{
	return (break_delimited_str(strlist, ','));
}

/**
 * @brief
 *		Does a string exist in the given array?
 *
 * @param[in]	strarr	-	the string array to search, should be NULL terminated
 * @param[in]	str	-	the string to find
 *
 * @return	int
 * @retval	1	: if the string is found
 * @retval	0	: the string is not found or on error
 *
 */
int
is_string_in_arr(char **strarr, const char *str)
{
	int ind;

	ind = find_string_idx(strarr, str);

	if (ind >= 0)
		return 1;

	return 0;
}

/**
 * @brief
 *	make copy of string array
 *
 * @param[in] strarr - the string array to make copy
 *
 * @return char **
 * @retval !NULL - copy of string array
 * @retval NULL  - failed to make copy of string array
 *
 */
char **
dup_string_arr(char **strarr)
{
	int i = 0;
	char **retarr = NULL;

	if (strarr == NULL)
		return NULL;

	for (i = 0; strarr[i] != NULL; i++)
		;

	if ((retarr = (char **) malloc((i + 1) * sizeof(char *))) == NULL)
		return NULL;

	for (i = 0; strarr[i] != NULL; i++) {
		retarr[i] = strdup(strarr[i]);
		if (retarr[i] == NULL) {
			for (i = 0; retarr[i] != NULL; i++)
				free(retarr[i]);
			free(retarr);
			return NULL;
		}
	}
	retarr[i] = NULL;
	return retarr;
}

/**
 * @brief
 * 		find index of str in strarr
 *
 * @param[in]	strarr	-	the string array to search
 * @param[in]	str	-	the string to find
 *
 * @return	int
 * @retval	index of string
 * @retval	-1	: if not found
 */
int
find_string_idx(char **strarr, const char *str)
{
	int i;
	if (strarr == NULL || str == NULL)
		return -1;

	for (i = 0; strarr[i] != NULL && strcmp(strarr[i], str); i++)
		;
	if (strarr[i] == NULL)
		return -1;

	return i;
}

/**
 * @brief
 *		free_string_array - free an array of strings with a NULL as a sentinel
 *
 * @param[in,out]	arr	-	the array to free
 *
 * @return	nothing
 *
 */
void
free_string_array(char **arr)
{
	int i;

	if (arr != NULL) {
		for (i = 0; arr[i] != NULL; i++)
			free(arr[i]);

		free(arr);
	}
}

/**
 * @brief
 *	ensure_string_not_null - if string is NULL, allocate an empty string
 *
 * @param[in]	str - pointer to pointer to string (or to NULL)
 *
 * @return	nothing
 *
 */
void
ensure_string_not_null(char **str)
{
	if (*str == NULL)
		*str = strdup("");
}

/**
 * @brief
 *	convert_string_to_lowercase - Convert string to lowercase
 *
 * @param[in]	str - string to be converted
 *
 * @return	char *
 * @retval	!NULL - converted string
 * @retval	NULL - failure
 *
 * @note
 * 	Returned string will be malloced area, so free after use
 *
 */
char *
convert_string_to_lowercase(char *str)
{
	char *ret = NULL;
	int i = 0;
	int len = 0;

	if (str == NULL || *str == '\0')
		return NULL;

	len = strlen(str);
	if ((ret = calloc(1, len + 1)) == NULL)
		return NULL;

	for (i = 0; i < len; i++)
		ret[i] = tolower(str[i]);

	return ret;
}

/**
 * @brief
 * 		Convert a duration to HH:MM:SS format string
 *
 * @param[in]	duration	-	the duration
 * @param[out]	buf	-	the buffer to be filled
 * @param[in]	bufsize	-	size of the buffer
 *
 * @return	void
 */
void
convert_duration_to_str(time_t duration, char *buf, int bufsize)
{
	long hour, min, sec;
	if (buf == NULL || bufsize == 0)
		return;
	hour = duration / 3600;
	duration = duration % 3600;
	min = duration / 60;
	duration = duration % 60;
	sec = duration;
	snprintf(buf, bufsize, "%02ld:%02ld:%02ld", hour, min, sec);
}

/**
 * @brief
 *	Determines if 'str' ends with three consecutive double quotes,
 *	before a newline (if it exists).
 *
 * @param[in]	str - input string
 * @param[in]	strip_quotes - if set to 1, then modify 'str' so that
 *				the triple quotes are not part of the string.
 *
 * @return int
 * @retval 1 - if string ends with triple quotes.
 * @retval 0 - if string does not end with triple quotes.
 *
 */
int
ends_with_triple_quotes(char *str, int strip_quotes)
{
	int ct;
	char *p = NULL;
	int ll = 0;

	if (str == NULL)
		return (0);

	ll = strlen(str);
	if (ll < 3) {
		return (0);
	}

	p = str + (ll - 1);

	if (*p == '\n') {
		p--;
#ifdef WIN32
		if (*p == '\r') {
			p--;
		}
#endif
	}

	ct = 0;
	while ((p >= str) && (*p == '"')) {
		ct++;
		p--;
		if (ct == 3)
			break;
	}
	if (ct == 3) {
		if (strip_quotes == 1) {
			/* null terminate the first double quote */
			*(p + 1) = '\0';
		}
		return (1);
	}
	return (0);
}

/**
 * @brief
 *	Determines if 'str' begins with three consecutive double quotes.
 *
 * @param[in]	str - input string
 *
 * @return int
 * @retval 1 - if string starts with triple quotes.
 * @retval 0 - if string does not start with triple quotes.
 *
 */
int
starts_with_triple_quotes(char *str)
{
	char *p;
	int ct;

	if (str == NULL)
		return (0);

	p = str;
	ct = 0;
	while ((*p != '\0') && (*p == '"')) {
		ct++;
		p++;
		if (ct == 3)
			break;
	}
	if (ct == 3) {
		return (1);
	}
	return (0);
}

/*
 * @brief
 *	Gets malloc_info and returns as a string
 *
 * @return char *
 * @retval NULL - Error
 * @note
 * The buffer has to be freed by the caller.
 *
 */
#ifndef WIN32
#ifdef HAVE_MALLOC_INFO
char *
get_mem_info(void)
{
	FILE *stream;
	char *buf;
	size_t len;
	int err = 0;

	stream = open_memstream(&buf, &len);
	if (stream == NULL)
		return NULL;
	err = malloc_info(0, stream);
	fclose(stream);
	if (err == -1) {
		free(buf);
		return NULL;
	}
	return buf;
}
#endif /* malloc_info */
#endif /* WIN32 */

/**
 * @brief
 *	Return a copy of 'str' where non-printing characters
 *	(except the ones listed in the local variable 'special_char') are
 *	shown in ^<translated_char> notation.
 *
 * @param[in]	str - input string
 *
 * @return char *
 *
 * @note
 * 	Do not free the return value - it's in a fixed memory area that
 *	will get overwritten the next time the function is called.
 *      So best to use the result immediately or strdup() it.
 *
 *	This will return the original (non-translated) 'str' value if
 *	an error was encounted, like a realloc() error.
 */
char *
show_nonprint_chars(char *str)
{
#ifndef WIN32
	static char *locbuf = NULL;
	static size_t locbuf_size = 0;
	char *buf, *buf2;
	size_t nsize;
	int ch;
	char special_char[] = "\n\t";

	if ((str == NULL) || (str[0] == '\0'))
		return str;

	nsize = (strlen(str) * 2) + 1;
	if (nsize > locbuf_size || locbuf == NULL) {
		char *tmpbuf;
		if ((tmpbuf = realloc(locbuf, nsize)) == NULL)
			return str;
		locbuf = tmpbuf;
		locbuf_size = nsize;
	}

	locbuf[0] = '\0';
	buf = str;
	buf2 = locbuf;
	while ((ch = *buf++) != '\0') {
		if ((ch < 32) && !char_in_set(ch, special_char)) {
			*buf2++ = '^';
			*buf2++ = ch + 64;
		} else {
			*buf2++ = ch;
		}
	}
	*buf2 = '\0';
	return (locbuf);
#else
	return (str);
#endif
}

/**
 * @brief
 *  get_preemption_order - deduce the preemption ordering to be used for a job
 *
 *  @param[in]	porder - static value of preempt order from the sched object
 *  						this array is assumed to be of size PREEMPT_ORDER_MAX
 *  @param[in]	req - amount of requested time for the job
 *  @param[in]	used - amount of used time by the job
 *
 *  @return	struct preempt_ordering *
 *  @retval	preempt ordering for the job
 *  @retval	NULL if error
 */
struct preempt_ordering *
get_preemption_order(struct preempt_ordering *porder, int req, int used)
{
	int i;
	int percent_left = 0;
	struct preempt_ordering *po = NULL;

	if (porder == NULL)
		return NULL;

	po = &porder[0];
	if (req < 0 || used < 0)
		return po;

	/* check if we have more then one range... no need to choose if not */
	if (porder[1].high_range != 0) {
		percent_left = 100 - ((float) used / req) * 100;
		if (percent_left < 0)
			percent_left = 1;

		for (i = 0; i < PREEMPT_ORDER_MAX; i++) {
			if (percent_left <= porder[i].high_range && percent_left >= porder[i].low_range) {
				po = &porder[i];
				break;
			}
		}
	}

	return po;
}

#ifdef WIN32
/**
 * @brief
 *	Returns the current wall clock time.
 *
 * @return double - number of seconds.
 */
static double
get_walltime(void)
{
	LARGE_INTEGER time, freq;

	if (QueryPerformanceFrequency(&freq) == 0)
		return (0);

	if (QueryPerformanceCounter(&time) == 0)
		return (0);

	return ((double) time.QuadPart / freq.QuadPart);
}

/**
 * @brief
 *	Returns the current processor time.
 *
 * @return double - number of seconds.
 **/
static double
get_cputime()
{
	FILETIME create_time;
	FILETIME exit_time;
	FILETIME kernel_time;
	FILETIME user_time;

	/* The times are returned in 100-nanosecond units */
	if (GetProcessTimes(GetCurrentProcess(), &create_time, &exit_time, &kernel_time, &user_time) == 0)
		return (0);

	if (user_time.dwLowDateTime != 0)
		return ((double) user_time.dwLowDateTime * 0.0000001);

	return ((double) (((unsigned long long) user_time.dwHighDateTime << 32)) * 0.0000001);
}

#else

/**
 * @brief
 *	Returns the current wall clock time.
 *
 * @return double - number of seconds.
 */
static double
get_walltime(void)
{

	struct timeval time;

	if (gettimeofday(&time, NULL) == -1)
		return (0);

	return ((double) time.tv_sec + (double) time.tv_usec * .000001);
}

/**
 * @brief
 *	Returns the current processor time.
 *
 * @return double - number of seconds.
 **/
static double
get_cputime()
{
	clock_t clock_cycles;
	clock_cycles = clock();
	if (clock_cycles == -1)
		return (0);
	assert(CLOCKS_PER_SEC != 0);
	return (double) clock_cycles / CLOCKS_PER_SEC;
}
#endif

/**
 * @brief
 *	Allocates memory for a given 'instance' of measurements.
 *
 * @param[in] instance - a description of what is being measured.
 *
 * @return perf_stat_t - an allocated entry.
 */
static perf_stat_t *
perf_stat_alloc(char *instance)
{
	perf_stat_t *p_stat;

	if ((instance == NULL) || (instance[0] == '\0'))
		return NULL;

	p_stat = malloc(sizeof(perf_stat_t));
	if (p_stat == NULL)
		return NULL;

	(void) memset((char *) p_stat, (int) 0, (size_t) sizeof(perf_stat_t));

	strncpy(p_stat->instance, instance, MAXBUFLEN);
	p_stat->instance[MAXBUFLEN] = '\0';
	p_stat->walltime = 0;
	p_stat->cputime = 0;

	delete_link(&p_stat->pi_allstats);
	append_link(&perf_stats, &p_stat->pi_allstats, p_stat);

	return (p_stat);
}

/**
 * @brief
 *	Find an 'instance' entry among the list of saved performance
 *	stats.
 *
 * @param[in] instance - entity being measured.
 *
 * @return perf_stat_t - found entry.
 */
static perf_stat_t *
perf_stat_find(char *instance)
{
	perf_stat_t *p_stat;

	if ((instance == NULL) || (instance[0] == '\0') || (perf_stats_initialized == 0))
		return (NULL);

	p_stat = (perf_stat_t *) GET_NEXT(perf_stats);
	while (p_stat) {
		if (strcmp(p_stat->instance, instance) == 0) {
			break;
		}
		p_stat = (perf_stat_t *) GET_NEXT(p_stat->pi_allstats);
	}
	return (p_stat); /* may be a null pointer */
}

/**
 * @brief
 *	Remove (deallocate) an 'instance' entry among the list of
 *	saved performance stats.
 *
 * @param[in] instance - entity being measured.
 *
 * @return void
 */
void
perf_stat_remove(char *instance)
{
	perf_stat_t *p_stat;

	if ((instance == NULL) || (instance[0] == '\0') || (perf_stats_initialized == 0))
		return;

	p_stat = (perf_stat_t *) GET_NEXT(perf_stats);
	while (p_stat) {
		if (strcmp(p_stat->instance, instance) == 0) {
			break;
		}
		p_stat = (perf_stat_t *) GET_NEXT(p_stat->pi_allstats);
	}
	if (p_stat != NULL) {
		delete_link(&p_stat->pi_allstats);
		free(p_stat);
	}
}

/**
 * @brief
 *	Record start counters for the 'instance' entry.
 *
 * @param[in] instance - entity being measured
 *
 * @return void
 */
void
perf_stat_start(char *instance)
{
	perf_stat_t *p_stat;

	if ((instance == NULL) || (instance[0] == '\0'))
		return;

	if (perf_stats_initialized == 0) {
		CLEAR_HEAD(perf_stats);
		perf_stats_initialized = 1;
	}

	p_stat = perf_stat_find(instance);
	if (p_stat == NULL) {
		p_stat = perf_stat_alloc(instance);
		if (p_stat == NULL)
			return;
	}

	p_stat->walltime = get_walltime();
	p_stat->cputime = get_cputime();
}

/**
 * @brief
 *	Returns a summary of statistics gathered (e.g.
 *	elapsed walltime) since the perf_stat_start() call on the
 *	same 'instance'.
 *
 * @param[in] instance - entity being measured
 *
 * @return char *  - a string describing stats gathered.
 *		   - this is a statically-allocated buffer that
 *		     will get over-written by the next call to this
 *		     function.
 * @note
 *	This also frees up the memory used by the 'instance' entry
 *      in the list of saved stats.
 */
char *
perf_stat_stop(char *instance)
{
	perf_stat_t *p_stat;
	double now_walltime;
	double now_cputime;
	static char stat_summary[MAXBUFLEN + 1];

	if ((instance == NULL) || (instance[0] == '\0')) {
		return (NULL);
	}

	p_stat = perf_stat_find(instance);
	if (p_stat == NULL)
		return (NULL);

	now_walltime = get_walltime();
	now_cputime = get_cputime();

	snprintf(stat_summary, sizeof(stat_summary), "%s walltime=%f cputime=%f", instance, (now_walltime - p_stat->walltime), (now_cputime - p_stat->cputime));

	delete_link(&p_stat->pi_allstats);
	free(p_stat);

	return (stat_summary);
}

/**
 * @brief
 *	creates an empty file in /tmp/ and saves timestamp of that file
 *
 * @param[in] - void
 *
 * @return - void
 */
void
create_query_file(void)
{
	FILE *f;
	char filename[MAXPATHLEN + 1];
	uid_t usid = getuid();
#ifdef WIN32
	LPSTR win_sid = NULL;
	if (!ConvertSidToStringSid(usid, &win_sid)) {
		fprintf(stderr, "qstat: failed to convert SID to string with error=%d\n", GetLastError());
		return;
	}
	snprintf(filename, sizeof(filename), "%s\\.pbs_last_query_%s", TMP_DIR, win_sid);
	LocalFree(win_sid);
#else
	snprintf(filename, sizeof(filename), "%s/.pbs_last_query_%d", TMP_DIR, usid);
#endif /* WIN32 */
	f = fopen(filename, "w");
	if (f != NULL)
		fclose(f);
}

/**
 * @brief
 *	stats te information of the empty file created in /tmp/ to decide
 *  whether to add sleep for .2 seconds or not
 *
 * @param[in] - void
 *
 * @return - void
 */
void
delay_query(void)
{
	char filename[MAXPATHLEN + 1];
#ifdef WIN32
	struct _stat buf;
#else
	struct stat buf;
#endif

	uid_t usid = getuid();
#ifdef WIN32
	LPSTR win_sid = NULL;
	if (!ConvertSidToStringSid(usid, &win_sid)) {
		fprintf(stderr, "qstat: failed to convert SID to string with error=%d\n", GetLastError());
		return;
	}
	snprintf(filename, sizeof(filename), "%s\\.pbs_last_query_%s", TMP_DIR, win_sid);
	if (_stat(filename, &buf) == 0) {
		if (((time(NULL) * 1000) - (buf.st_mtime * 1000)) < 10) {
			Sleep(200);
		}
	}
	LocalFree(win_sid);
#else
	snprintf(filename, sizeof(filename), "%s/.pbs_last_query_%d", TMP_DIR, usid);
	if (stat(filename, &buf) == 0) {
		if (((time(NULL) * 1000) - (buf.st_mtime * 1000)) < 10) {
			usleep(200000);
		}
	}
#endif /* WIN32 */
	atexit(create_query_file);
}

/**
 * @brief
 *	Put a human readable representation of a network addres into
 *	a staticly allocated string.
 *
 * @param[in] ap - internet address
 *
 * @return	string
 * @retval	static  string		success
 * @retval	"unknown"		error
 *
 */
char *
netaddr(struct sockaddr_in *ap)
{
	static char out[80];
	u_long ipadd;

	if (ap == NULL)
		return "unknown";

	ipadd = ntohl(ap->sin_addr.s_addr);

	sprintf(out, "%ld.%ld.%ld.%ld:%d",
		(ipadd & 0xff000000) >> 24,
		(ipadd & 0x00ff0000) >> 16,
		(ipadd & 0x0000ff00) >> 8,
		(ipadd & 0x000000ff),
		ntohs(ap->sin_port));
	return out;
}

/*
 *	BEGIN included source
 */
/*-
 * Copyright (c) 1991, 1993
 *	The Regents of the University of California.  All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * James W. Williams of NASA Goddard Space Flight Center.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgment:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

static u_long crctab[] = {
	0x0,
	0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b,
	0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6,
	0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
	0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac,
	0x5bd4b01b, 0x569796c2, 0x52568b75, 0x6a1936c8, 0x6ed82b7f,
	0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a,
	0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
	0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58,
	0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033,
	0xa4ad16ea, 0xa06c0b5d, 0xd4326d90, 0xd0f37027, 0xddb056fe,
	0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
	0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4,
	0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0,
	0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5,
	0x2ac12072, 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16,
	0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, 0x7897ab07,
	0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c,
	0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1,
	0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
	0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b,
	0xbb60adfc, 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698,
	0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d,
	0x94ea7b2a, 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e,
	0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, 0xc6bcf05f,
	0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34,
	0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80,
	0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,
	0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a,
	0x58c1663d, 0x558240e4, 0x51435d53, 0x251d3b9e, 0x21dc2629,
	0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c,
	0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,
	0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e,
	0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65,
	0xeba91bbc, 0xef68060b, 0xd727bbb6, 0xd3e6a601, 0xdea580d8,
	0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,
	0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2,
	0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71,
	0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74,
	0x857130c3, 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640,
	0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, 0x7b827d21,
	0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a,
	0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, 0x18197087,
	0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
	0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d,
	0x2056cd3a, 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce,
	0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb,
	0xdbee767c, 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18,
	0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, 0x89b8fd09,
	0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662,
	0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf,
	0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4};

/**
 * @brief
 *	-Compute a POSIX 1003.2 checksum.  This routine has been broken out so that
 * 	other programs can use it.  It takes a char pointer and length.
 * 	It ruturns the crc value for the data in buf.
 *
 * @param[in] buf - input data data for which crc computed
 * @param[in] len - length of input data
 *
 * @return	u_long
 * @retval	crc value	success
 *
 */
static u_long
crc(u_char *buf, u_long clen)
{
	register u_char *p;
	register u_long crc, len;

#define COMPUTE(var, ch) (var) = (((var) << 8) ^                           \
				  crctab[(((var) >> 24) & 0xff) ^ (ch)]) & \
				 0xffffffff

	for (crc = 0, len = clen, p = buf; len--; ++p) {
		COMPUTE(crc, *p);
	}

	/* Include the length of the file. */
	for (; clen != 0; clen >>= 8) {
		COMPUTE(crc, clen & 0xff);
	}

	return (~crc & 0xffffffff);
}
/*
 *	END of included source
 */

#ifdef WIN32
/**
 * @brief
 * 	Given some bytes of data in 'buf' of size 'buf_sz', return a
 *	copy of the data but with <carriage return> <linefeed> combination
 *	entries replaced by a single <linefeed>. The returned buffer
 *	size is returned in 'new_buf_sz'.
 *
 * @param[in]		buf	- some bytes of data.
 * @param[in]		buf_sz	- size of 'buf'.
 * @param[in/out]	new_buf_sz - holds the buffer size of the returned buf.
 *
 * @return char *
 * @retval <copy of filtered 'buf'>
 */
static char *
dos2unix(char *buf, unsigned long buf_sz, int *new_buf_sz)
{
	static char *buf2 = NULL;
	static unsigned long buf2_sz = 0;
	char *tmp_str = NULL;
	int i, j;

	if (buf_sz > buf2_sz) {
		tmp_str = realloc(buf2, buf_sz);
		if (tmp_str == NULL) {
			*new_buf_sz = buf_sz;
			return (buf); /* return original */
		}
		buf2 = tmp_str;
		buf2_sz = buf_sz;
	}

	memset(buf2, '\0', buf2_sz);
	j = 0;
	for (i = 0; i < buf_sz; i++) {
		if ((i < (buf_sz - 1)) && (buf[i] == '\r') &&
		    (buf[i + 1] == '\n')) {
			buf2[j++] = '\n';
			i++; /* skip the next linefeed */
		} else {
			buf2[j++] = buf[i];
		}
	}

	*new_buf_sz = j;
	return (buf2);
}
#endif

/**
 * @brief
 * 	Given a file represented by 'filepath', return its crc value.
 *
 * @param[in]	filepath	- file being crc-ed.
 *
 * @return u_long
 * @retval	> 0	- crc (checksum) value of the file.
 * @retval	0	- if file is non-existent, or file is empty, or if an
 * 			  error encountered while opening or reading the
 * 			  file.
 */
unsigned long
crc_file(char *filepath)
{
	int fd;
	struct stat sb;
	static u_char *buf = NULL;
	static int buf_sz = 0;
	u_char *tmp_str = NULL;
	int nread = 0;
	int count;
	u_char *tmpbuf;
#ifdef WIN32
	u_char *tr_buf = NULL;
	int tr_buf_sz = 0;
#endif

	if (filepath == NULL)
		return (0);

	if (stat(filepath, &sb) == -1) {
		return (0);
	}

	if (sb.st_size <= 0) {
		return (0);
	}

	if ((fd = open(filepath, O_RDONLY)) <= 0) {
		return (0);
	}

#ifdef WIN32
	setmode(fd, O_BINARY);
#endif

	if (sb.st_size > buf_sz) {
		tmp_str = realloc(buf, sb.st_size);
		if (tmp_str == NULL) {
			close(fd);
			return (0);
		}
		buf = tmp_str;
		buf[0] = '\0';
		buf_sz = sb.st_size;
	}

	tmpbuf = buf;
	count = sb.st_size;

	while (((nread = read(fd, tmpbuf, count)) > 0) &&
	       (nread <= sb.st_size)) {

		count -= nread;
		tmpbuf += nread;

		if (count == 0) {
			break;
		}
	}

	if (nread < 0) {
		close(fd);
		return (0);
	}

	close(fd);
#ifdef WIN32
	tr_buf = dos2unix(buf, sb.st_size, &tr_buf_sz);
	return (crc(tr_buf, tr_buf_sz));
#else
	return (crc(buf, sb.st_size));
#endif
}

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

int
state_char2int(char stc)
{
	int i;
	char statechars[] = "TQHWREXBMF";

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

/**
 * @brief
 * 		state_int2char - return the state from int form to char form.
 *
 * @param[in]	sti	-	state in int form
 *
 * @return	state in char form
 * @retval	'0'	: failure
 */

char
state_int2char(int sti)
{
	switch (sti) {
		case JOB_STATE_TRANSIT:
			return JOB_STATE_LTR_TRANSIT;
		case JOB_STATE_QUEUED:
			return JOB_STATE_LTR_QUEUED;
		case JOB_STATE_HELD:
			return JOB_STATE_LTR_HELD;
		case JOB_STATE_WAITING:
			return JOB_STATE_LTR_WAITING;
		case JOB_STATE_RUNNING:
			return JOB_STATE_LTR_RUNNING;
		case JOB_STATE_EXITING:
			return JOB_STATE_LTR_EXITING;
		case JOB_STATE_EXPIRED:
			return JOB_STATE_LTR_EXPIRED;
		case JOB_STATE_BEGUN:
			return JOB_STATE_LTR_BEGUN;
		case JOB_STATE_MOVED:
			return JOB_STATE_LTR_MOVED;
		case JOB_STATE_FINISHED:
			return JOB_STATE_LTR_FINISHED;
		default:
			return JOB_STATE_LTR_UNKNOWN;
	}

	return JOB_STATE_LTR_UNKNOWN;
}

/**
 * @brief
 * 		parse_servername - parse a server/vnode name in the form:
 *		[(]name[:service_port][:resc=value[:...]][+name...]
 *		from exec_vnode or from exec_hostname
 *		name[:service_port]/NUMBER[*NUMBER][+...]
 *		or basic servername:port string
 *
 *		Returns ptr to the node name as the function value and the service_port
 *		number (int) into service if :port is found, otherwise port is unchanged
 *		host name is also terminated by a ':', '+' or '/' in string
 *
 * @param[in]	name	- server/node/exec_vnode string
 * @param[out]	service	-  RETURN: service_port if :port
 *
 * @return	 ptr to the node name
 *
 * @par MT-safe: No
 */

char *
parse_servername(char *name, unsigned int *service)
{
	static char buf[PBS_MAXSERVERNAME + PBS_MAXPORTNUM + 2];
	int i = 0;
	char *pc;

	if ((name == NULL) || (*name == '\0'))
		return NULL;
	if (*name == '(') /* skip leading open paren found in exec_vnode */
		name++;

	/* look for a ':', '+' or '/' in the string */

	pc = name;
	while (*pc && (i < PBS_MAXSERVERNAME + PBS_MAXPORTNUM + 2)) {
		if ((*pc == '+') || (*pc == '/')) {
			break;
		} else if (*pc == ':') {
			if (isdigit((int) *(pc + 1)) && (service != NULL))
				*service = (unsigned int) atoi(pc + 1);
			break;
		} else {
			buf[i++] = *pc++;
		}
	}
	buf[i] = '\0';
	return (buf);
}

#ifndef WIN32
/**
 * @brief
 * 	set limits for the current process
 *
 * @param[in] core_limit - core limit in string (this is usally pbs_conf.pbs_core_limit)
 * @param[in] fdlimit - max open fd limit (can be 0 to not to change limit)
 *
 * @return void
 *
 */
void
set_proc_limits(char *core_limit, int fdlimit)
{
#ifdef RLIMIT_CORE
	int char_in_cname = 0;
	extern char *msg_corelimit;

	if (core_limit) {
		char *pc = core_limit;
		while (*pc != '\0') {
			if (!isdigit(*pc)) {
				/* there is a character in core limit */
				char_in_cname = 1;
				break;
			}
			pc++;
		}
	}
#endif /* RLIMIT_CORE */

#if defined(RLIM64_INFINITY)
	{
		struct rlimit64 rlimit;

		if (fdlimit) {
			rlimit.rlim_cur = fdlimit;
			rlimit.rlim_max = fdlimit;
			if (setrlimit64(RLIMIT_NOFILE, &rlimit) == -1) {
				log_err(errno, __func__, "could not set max open files limit");
			}
		}

		rlimit.rlim_cur = RLIM64_INFINITY;
		rlimit.rlim_max = RLIM64_INFINITY;
		(void) setrlimit64(RLIMIT_CPU, &rlimit);
		(void) setrlimit64(RLIMIT_FSIZE, &rlimit);
		(void) setrlimit64(RLIMIT_DATA, &rlimit);
		(void) setrlimit64(RLIMIT_STACK, &rlimit);
#ifdef RLIMIT_RSS
		(void) setrlimit64(RLIMIT_RSS, &rlimit);
#endif /* RLIMIT_RSS */
#ifdef RLIMIT_VMEM
		(void) setrlimit64(RLIMIT_VMEM, &rlimit);
#endif /* RLIMIT_VMEM */
#ifdef RLIMIT_CORE
		if (core_limit) {
			struct rlimit64 corelimit;
			corelimit.rlim_max = RLIM64_INFINITY;
			if (strcmp("unlimited", core_limit) == 0)
				corelimit.rlim_cur = RLIM64_INFINITY;
			else if (char_in_cname == 1) {
				log_record(PBSEVENT_ERROR, PBS_EVENTCLASS_SERVER, LOG_WARNING,
					   __func__, msg_corelimit);
				corelimit.rlim_cur = RLIM64_INFINITY;
			} else
				corelimit.rlim_cur = (rlim64_t) atol(core_limit);
			(void) setrlimit64(RLIMIT_CORE, &corelimit);
		}
#endif /* RLIMIT_CORE */
	}

#else /* setrlimit 32 bit */
	{
		struct rlimit rlimit;

		if (fdlimit) {
			rlimit.rlim_cur = fdlimit;
			rlimit.rlim_max = fdlimit;
			if (setrlimit(RLIMIT_NOFILE, &rlimit) == -1) {
				log_err(errno, __func__, "could not set max open files limit");
			}
		}
		rlimit.rlim_cur = RLIM_INFINITY;
		rlimit.rlim_max = RLIM_INFINITY;
		(void) setrlimit(RLIMIT_CPU, &rlimit);
#ifdef RLIMIT_RSS
		(void) setrlimit(RLIMIT_RSS, &rlimit);
#endif /* RLIMIT_RSS */
#ifdef RLIMIT_VMEM
		(void) setrlimit(RLIMIT_VMEM, &rlimit);
#endif /* RLIMIT_VMEM */
#ifdef RLIMIT_CORE
		if (core_limit) {
			struct rlimit corelimit;
			corelimit.rlim_max = RLIM_INFINITY;
			if (strcmp("unlimited", core_limit) == 0)
				corelimit.rlim_cur = RLIM_INFINITY;
			else if (char_in_cname == 1) {
				log_record(PBSEVENT_ERROR, PBS_EVENTCLASS_SERVER, LOG_WARNING,
					   (char *) __func__, msg_corelimit);
				corelimit.rlim_cur = RLIM_INFINITY;
			} else
				corelimit.rlim_cur =
					(rlim_t) atol(core_limit);

			(void) setrlimit(RLIMIT_CORE, &corelimit);
		}
#endif /* RLIMIT_CORE */
#ifndef linux
		(void) setrlimit(RLIMIT_FSIZE, &rlimit);
		(void) setrlimit(RLIMIT_DATA, &rlimit);
		(void) setrlimit(RLIMIT_STACK, &rlimit);
#else
		if (getrlimit(RLIMIT_STACK, &rlimit) != -1) {
			if ((rlimit.rlim_cur != RLIM_INFINITY) && (rlimit.rlim_cur < MIN_STACK_LIMIT)) {
				rlimit.rlim_cur = MIN_STACK_LIMIT;
				rlimit.rlim_max = MIN_STACK_LIMIT;
				if (setrlimit(RLIMIT_STACK, &rlimit) == -1) {
					log_err(errno, __func__, "setting stack limit failed");
					exit(1);
				}
			}
		} else {
			log_err(errno, __func__, "getting current stack limit failed");
			exit(1);
		}
#endif /* not linux */
	}
#endif /* !RLIM64_INFINITY */
}
#endif

/**
 * @brief
 *	rand_num - returns a random number.
 * 	This function will seed using micro second if already not seeded
 *
 */
int
rand_num(void)
{
	static int seeded = 0;
	struct timeval tv;

	if (!seeded) {
		gettimeofday(&tv, NULL);
		srand(1000000 * tv.tv_sec + tv.tv_usec); /* seed the random generator */
		seeded = 1;
	}

	return rand();
}

/**
 * @brief
 * 	get subjob index from given jobid
 *
 * @param[in] jid - jobid
 *
 * @return int
 * @retval -1  - fail to determine index of subjob
 * @retval !-1 - index of subjob
 */
int
get_index_from_jid(char *jid)
{
	char *range = get_range_from_jid(jid);

	if (range != NULL) {
		char *endptr = NULL;
		int idx = strtoul(range, &endptr, 10);

		if (endptr == NULL || *endptr != '\0' || idx < 0)
			return -1;
		else
			return idx;
	} else
		return -1;
}
/**
 * @brief
 * 	get range string of arrayjob from given jobid
 *
 * @param[in] jid - job id
 *
 * @return char *
 * @retval NULL - on error
 * @retval ptr - ptr to static char array containing range string if found
 *
 * @par
 * 	MT-safe: No - uses static variables - index, indexlen.
 */
char *
get_range_from_jid(char *jid)
{
	int i;
	char *pcb;
	char *pce;
	static char index[BUF_SIZE];

	if ((pcb = strchr(jid, (int) '[')) == NULL)
		return NULL;
	if ((pce = strchr(jid, (int) ']')) == NULL)
		return NULL;
	if (pce <= pcb)
		return NULL;

	i = 0;
	while (++pcb < pce)
		index[i++] = *pcb;
	index[i] = '\0';
	return index;
}

/**
 * @brief
 * 	create and return (in a static array) a jobid for a subjob based on
 * 	the parent jobid and the subjob index
 *
 * @param[in] parent_jid - parent jobid
 * @param[in] sjidx -  subjob index.
 *
 * @return char *
 * @return !NULL - jobid of subjob
 * @return NULL - failure
 *
 * @par
 * 	MT-safe: No - uses a static buffer, "jid".
 */
char *
create_subjob_id(char *parent_jid, int sjidx)
{
	static char jid[PBS_MAXSVRJOBID + 1];
	char *pcb;
	char *pce;

	if ((pcb = strchr(parent_jid, (int) '[')) == NULL)
		return NULL;
	if ((pce = strchr(parent_jid, (int) ']')) == NULL)
		return NULL;
	if (pce <= pcb)
		return NULL;

	*pcb = '\0';
	snprintf(jid, sizeof(jid), "%s[%d]%s", parent_jid, sjidx, pce + 1);
	*pcb = '[';
	return jid;
}

/**
 * @brief
 * 		read attributes from file descriptor of a job file
 *
 * @param[in]	fd	-	file descriptor
 * @param[out]	errbuf	-	buffer to return messages for any errors
 *
 * @return	svrattrl *
 * @retval	svrattrl object for the attribute read
 * @retval	NULL for error
 */
static svrattrl *
read_attr(int fd, char **errbuf)
{
	int amt;
	int i;
	svrattrl *pal;
	svrattrl tempal;

	i = read(fd, (char *) &tempal, sizeof(tempal));
	if (i != sizeof(tempal)) {
		if (errbuf != NULL)
			sprintf(*errbuf, "bad read of attribute");
		return NULL;
	}
	if (tempal.al_tsize == ENDATTRIBUTES)
		return NULL;

	pal = (svrattrl *) malloc(tempal.al_tsize);
	if (pal == NULL) {
		if (errbuf != NULL)
			sprintf(*errbuf, "malloc failed");
		return NULL;
	}
	*pal = tempal;

	/* read in the actual attribute data */

	amt = pal->al_tsize - sizeof(svrattrl);
	i = read(fd, (char *) pal + sizeof(svrattrl), amt);
	if (i != amt) {
		if (errbuf != NULL)
			sprintf(*errbuf, "short read of attribute");
		return NULL;
	}
	pal->al_name = (char *) pal + sizeof(svrattrl);
	if (pal->al_rescln)
		pal->al_resc = pal->al_name + pal->al_nameln;
	else
		pal->al_resc = NULL;
	if (pal->al_valln)
		pal->al_value = pal->al_name + pal->al_nameln + pal->al_rescln;
	else
		pal->al_value = NULL;

	return pal;
}

/**
 * @brief	Read all job attribute values from a job file
 *
 * @param[in]	fd - fd of job file
 * @param[out]	state - return pointer to state value
 * @param[out]	substate - return pointer for substate value
 * @param[out]	errbuf	-	buffer to return messages for any errors
 *
 * @return	svrattrl*
 * @retval	list of attributes read from a job file
 * @retval	NULL for error
 */
svrattrl *
read_all_attrs_from_jbfile(int fd, char **state, char **substate, char **errbuf)
{
	svrattrl *pal = NULL;
	svrattrl *pali = NULL;

	while ((pali = read_attr(fd, errbuf)) != NULL) {
		if (pal == NULL) {
			pal = pali;
			(&pal->al_link)->ll_struct = (void *) (&pal->al_link);
			(&pal->al_link)->ll_next = NULL;
			(&pal->al_link)->ll_prior = NULL;
		} else {
			pbs_list_link *head = &pal->al_link;
			pbs_list_link *newp = &pali->al_link;
			newp->ll_prior = NULL;
			newp->ll_next = head;
			newp->ll_struct = pali;
			pal = pali;
		}
		/* Check if the attribute read is state/substate and store it separately */
		if (state && strcmp(pali->al_name, ATTR_state) == 0)
			*state = pali->al_value;
		else if (substate && strcmp(pali->al_name, ATTR_substate) == 0)
			*substate = pali->al_value;
	}

	return pal;
}
