/* $Id: compat.c 1555 2006-08-17 20:10:28Z mipsator $ */

/*
 * Copyright (c) 2003-2005 Damien Couderc
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *    - Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *    - 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.
 *    - Neither the name of the copyright holder(s) 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 COPYRIGHT HOLDERS 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
 * COPYRIGHT HOLDERS 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.
 *
 */


#include <sys/param.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <fcntl.h>
#include <math.h>
#include <stdlib.h>

#include "compat/config.h"

#ifdef HAVE_INTMAX_T
#include <stdint.h>
#endif

#if defined(HAVE_WCHAR_T) || defined (HAVE_WINT_T)
#include <wchar.h>
#endif

#ifdef HAVE_PTRDIFF_T
#include <stddef.h>
#endif

#include "compat.h"
#include "compat/pmk_ctype.h"
#include "compat/pmk_libgen.h"
#include "compat/pmk_stdbool.h"
#include "compat/pmk_stdio.h"
#include "compat/pmk_string.h"
#include "compat/pmk_sys_types.h"
#include "compat/pmk_unistd.h"


/**********
 * macros *
 ***********************************************************************/

/* macros for structure */
#define	SET_FLAG(f)		data.flags = data.flags | f
#define	UNSET_FLAG(f)	data.flags = data.flags & (FLAG_ALL ^ f);
#define	F_IS_SET(f)		(data.flags & f) != 0
#define	F_IS_UNSET(f)	(data.flags & f) == 0

/* macros for pointer of structure */
#define	SET_PFLAG(f)	pdt->flags = pdt->flags | f
#define	UNSET_PFLAG(f)	pdt->flags = pdt->flags & (FLAG_ALL ^ f);
#define	PF_IS_SET(f)	(pdt->flags & f) != 0
#define	PF_IS_UNSET(f)	(pdt->flags & f) == 0

/*#define DEBUG_VSNPRINTF 1*/


/*************
 * functions *
 ***********************************************************************/

/*****************
 * fill_buffer() *
 ***********************************************************************
 DESCR
	Append a character in the buffer if it does not override the
	buffer's length

 IN
 	pdt :		vsnprintf common data structure
	chr :		char to append

 OUT
	NONE
 ***********************************************************************/

static void fill_buffer(vsnp_t *pdt, char chr) {
#ifdef DEBUG_VSNPRINTF
	printf("cursor = %d, char = '%c'\n", pdt->cur, chr);
#endif

	/* if buffer is not full */
	if (pdt->cur < pdt->len) {
		/* insert character */
		pdt->buf[pdt->cur] = chr;
	}

	/* increment cursor position */
	pdt->cur++;
}


/******************
 * build_number() *
 ***********************************************************************
 DESCR
	Convert an unsigned integer into a string. The resulting string is
	in the reverse order without terminating null character.

 IN
	buffer :	buffer location
	len :		buffer size
	value :		unsigned integer to convert
 	pdt :		vsnprintf common data structure

 OUT
	size of the resulting string
 ***********************************************************************/

static size_t build_number(char *buf, size_t len, unsigned_t value,
													base_t base, flag_t flags) {
	char	*basestr;
	size_t	 vlen = 0;

	/* if value is equal to zero */
	if (value == 0) {
		buf[0] = '0';
		return(1);
	}

	/* select reference string relative to the case */
	if ((flags & FLAG_UPPERCASE) != 0) {
		basestr = UPPER_BASE;
	} else {
		basestr = LOWER_BASE;
	}

	/* create number string in reverse order */
	while ((value > 0) && (vlen < len)) {
		buf[vlen] = basestr[value % base];
		value = value / base;
		vlen++;
	}

	return(vlen);
}


/*****************
 * convert_int() *
 ***********************************************************************
 DESCR
	Convert an integer value

 IN
 	pdt :		vsnprintf common data structure
	value :		unsigned integer to convert

 OUT
	NONE
 ***********************************************************************/

static void convert_int(vsnp_t *pdt, unsigned_t value) {
	char	nbuf[MAXINTLEN+1],
			sign = ' ';
	int		vlen,
			zplen,
			splen,
			have_sign = 0,
			have_hex_prefix = 0;

	if (PF_IS_SET(FLAG_NEGATIVE_VALUE)) {
		/* on negative value set the sign */
		have_sign = 1;
		sign = '-';
	} else {
		/* else if + flag is set display the positive sign */
		if (PF_IS_SET(FLAG_SIGNED)) {
			have_sign = 1;
			sign = '+';
		}
	}

	vlen = build_number(nbuf, sizeof(nbuf), value, pdt->base, pdt->flags);

	/* compute zero padding */
	zplen = pdt->prec - vlen;
	if (zplen < 0) {
		zplen = 0;
	}

	/* compute space padding */
	if (pdt->prec > vlen) {
		splen = pdt->fwidth - pdt->prec;
	} else {
		splen = pdt->fwidth - vlen;
	}
	if (have_sign != 0) {
		splen--;
	}
	if (PF_IS_SET(FLAG_ALTERNATIVE_FORM)) {
		switch (pdt->base) {
			case BASE_OCT :
				/* octal alternative form */
				if ((value == 0) && (pdt->prec == 0)) {
					pdt->prec = 1;
				}
				break;

			case BASE_HEX :
				/* hex alternative form */
				if (value > 0) {
					have_hex_prefix = 1;
					splen = splen - 2; /* length of '0x' */
				}
				break;
		}
	}
	if (splen < 0) {
		splen = 0;
	}

	/* right justify space padding */
	if (PF_IS_UNSET(FLAG_LEFT_JUSTIFIED)) {
		while (splen > 0) {
			fill_buffer(pdt, ' ');
			splen--;
		}
	}

	/* if we have a sign */
	if (have_sign != 0) {
		fill_buffer(pdt, sign);
	}

	/* hex alternative form */
	if (have_hex_prefix != 0) {
		fill_buffer(pdt, '0');
		if (PF_IS_UNSET(FLAG_UPPERCASE)) {
			fill_buffer(pdt, 'x');
		} else {
			fill_buffer(pdt, 'X');
		}
	}

	/* zero padding */
	while (zplen > 0) {
		fill_buffer(pdt, '0');
		zplen--;
	}

	/* converted integer */
	while (vlen > 0) {
		fill_buffer(pdt, nbuf[vlen - 1]);
		vlen--;
	}

	/* left justify space padding */
	if (PF_IS_SET(FLAG_LEFT_JUSTIFIED)) {
		while (splen > 0) {
			fill_buffer(pdt, ' ');
			splen--;
		}
	}
}


/******************
 * convert_sint() *
 ***********************************************************************
 DESCR
 	Convert a signed integer

 IN
 	pdt :		vsnprintf common data structure
	value :		unsigned integer to convert

 OUT
	NONE
 ***********************************************************************/

static void convert_sint(vsnp_t *pdt, signed_t value) {

	/* check if value is negative */
	if (value < 0) {
		/* if true set flag */
		SET_PFLAG(FLAG_NEGATIVE_VALUE);
		value = -value;
	}

	/* call generic conversion procedure */
	convert_int(pdt, (unsigned_t) value);
}


/*****************
 * convert_str() *
 ***********************************************************************
 DESCR
	Convert the string. It means copying the string in the place of the
	conversion identifier.

 IN
 	pdt :		vsnprintf common data structure
	ptr :		string to convert

 OUT
	NONE
 ***********************************************************************/

static void convert_str(vsnp_t *pdt, char *ptr) {
	while ((pdt->cur < pdt->len) && (pdt->prec > 0) && (*ptr != '\0')) {
		fill_buffer(pdt, *ptr);
		ptr++;
		pdt->prec--;
	}
}


/*****************
 * conv_to_exp() *
 ***********************************************************************
 DESCR
	Convert to exponent form

 IN
 	pdt :		vsnprintf common data structure
	expnt :		exponent value

 OUT
	exponent presence flag
 ***********************************************************************/

static int conv_to_exp(vsnp_t *pdt, flt_t *pval, int *pexp) {
	flt_t	uval;
	int		expnt,
			have_exp = 1;

#ifdef DEBUG_VSNPRINTF
	printf("conv_to_exp() -> in\n");
#endif

	/* work on local variable to save value in case of g conversion */
	uval = *pval;

	/* init exponent value */
	expnt = 0;

	/* process value to get only one digit before decimal point */
	if (uval >= 0) {
		while (uval > pdt->base) {
			uval = uval / pdt->base;
			expnt++;
		}
	} else {
		while (uval < 0) {
			uval = uval * pdt->base;
			expnt--;
		}
	}

	/* check if exponent is needed for g conversion */
	if (PF_IS_SET(FLAG_G_CONVERSION)) {
		if ((expnt >= -4) && (expnt < pdt->prec)) { /* XXX use define for ISOC99 lower limit */
			/* unset exponent flag */
			have_exp = 0;

			/* get back to original value */
			uval = *pval;
		}
	}

	/* set values */
	*pval = uval;
	*pexp = expnt;

#ifdef DEBUG_VSNPRINTF
	printf("conv_to_exp() -> out\n");
#endif

	return(have_exp);
}


/***************
 * print_exp() *
 ***********************************************************************
 DESCR
	process exponent

 IN
 	pdt :		vsnprintf common data structure
	expnt :		exponent value

 OUT
	NONE
 ***********************************************************************/

static void print_exp(vsnp_t *pdt, int expnt) {
	char	ebuf[MAXINTLEN+1],
			sign= ' ';

#ifdef DEBUG_VSNPRINTF
	printf("print_exp() -> in\n");
#endif

	/* exponent sign */
	if (expnt < 0) {
		sign = '-';
	} else {
		sign = '+';
	}

	/* exponent type may vary */
	ebuf[1] = '0'; /* init */
	switch (pdt->base) {
		case BASE_DEC :
			/* g or G conversion */
			if (PF_IS_SET(FLAG_UPPERCASE)) {
				fill_buffer(pdt, 'E');
			} else {
				fill_buffer(pdt, 'e');
			}

			fill_buffer(pdt, sign);

			/* exponent 2 digits */
			build_number(ebuf, 2, expnt, BASE_DEC, FLAG_NONE);
			fill_buffer(pdt, ebuf[1]);
			fill_buffer(pdt, ebuf[0]);
			break;

		case BASE_HEX :
			/* a or A conversion */
			if (PF_IS_SET(FLAG_UPPERCASE)) {
				fill_buffer(pdt, 'P');
			} else {
				fill_buffer(pdt, 'p');
			}

			fill_buffer(pdt, sign);

			/* exponent 1 digit */
			build_number(ebuf, 1, expnt, BASE_DEC, FLAG_NONE);
			fill_buffer(pdt, ebuf[0]);
			break;
	}

#ifdef DEBUG_VSNPRINTF
	printf("print_exp() -> out\n");
#endif
}


/************************
 * convert_real_float() *
 ***********************************************************************
 DESCR
	Convert a floating point number

 IN
 	pdt :		vsnprintf common data structure
	value :		unsigned integer to convert

 OUT
	NONE

 TODO
 	-inf
 	-NaN
 ***********************************************************************/

static void convert_real_float(vsnp_t *pdt, flt_t value) {
	char	ibuf[MAXINTLEN+1],
			fbuf[MAXINTLEN+1],
			sign= ' ';
	flt_t	frac,
			uval;
	int		expnt = 0,
			explen = 0,
			fplen,
			fzplen,
			have_alt_form,
			have_dot = 0,
			have_exp = 0,
			have_g_conv,
			have_sign = 0,
			izplen,
			splen,
			skip_zero = 0,
			t;
	long	intval,
			fracval,
			lt;
	size_t	ilen,
			flen;

#ifdef DEBUG_VSNPRINTF
	printf("convert_real_float() -> in\n");
#endif

	have_g_conv = (int) (pdt->flags & FLAG_G_CONVERSION);
	have_alt_form = (int) (pdt->flags & FLAG_ALTERNATIVE_FORM);

	/* for g conversion without alternative form */
	if ((have_g_conv != 0) && (have_alt_form == 0)) {
		/* we skip ending zeros */
		skip_zero = 1;
	}

	if (value < 0) {
		/* on negative value set the sign */
		have_sign = 1;
		sign = '-';
		uval = -value;
	} else {
		/* else if + flag is set display the positive sign */
		if (PF_IS_SET(FLAG_SIGNED)) {
			have_sign = 1;
			sign = '+';
		}

		uval = value;
	}

	if ((PF_IS_SET(FLAG_EXPONENT)) || (have_g_conv != 0)) {
		/* process eventual exponent */
		have_exp = conv_to_exp(pdt, &uval, &expnt);
	}

	/* get integral part of the value */
	intval = (long) uval;

	/* build integral part number */
	ilen = build_number(ibuf, sizeof(ibuf), intval, pdt->base, pdt->flags);

	if (have_g_conv != 0) {
		pdt->prec = pdt->prec - ilen;
		if (pdt->prec < 0) {
			pdt->prec = 0;
		}
	}

	if (pdt->prec > 0) {
		/* get the fractional part */
		frac = uval - (flt_t) intval;

		/* shift as digits as precision specify */
		t = pdt->prec;

		/* init round limit */
		lt = 1;

		/* multiply successively by base to obtain the desired precision */
		while (t > 0) {
			frac = frac * pdt->base;
			lt = lt * pdt->base;
			t--;
		}

		/* get integral part of the result */
		fracval = (long) frac;

		/* rount value if needed */
		if ((frac - fracval) > 0.5) {
			fracval++;

			/* if the rounded value add a digit (eg 999 -> 1000) */
			if (fracval == lt) {
				/* then adjust fractional value */
				fracval = fracval / pdt->base;
			}
		}

		/* unset signed flag for fractional part */
		UNSET_PFLAG(FLAG_SIGNED);

		/* build fractional part number */
		flen = build_number(fbuf, sizeof(fbuf), fracval, pdt->base, pdt->flags);
	} else {
		flen = 0;
		fracval = 0;
	}

	/* need a dot ? */
	if ((have_alt_form != 0) || (pdt->prec > 0)) {
		have_dot = 1;
	}

	/* specific treatment for g conversion */
	if ((skip_zero != 0) && (fracval == 0)) {
		have_dot = 0;
		flen = 0;
		pdt->prec = 0;
	}

	/* fractal part len */
	if (flen < (size_t) pdt->prec) {
		fplen = pdt->prec;
		fzplen = pdt->prec - flen;
	} else {
		fplen = flen;
		fzplen = 0;
	}

	/* exponent len */
	if (have_exp != 0) {
		switch (pdt->base) {
			case BASE_DEC :
				explen = 4;
				break;

			case BASE_HEX :
				explen = 3;
				break;
		}
	} else {
		explen = 0;
	}

	/* total string len */
	t = ilen + fplen + explen;
	/* sign */
	if (have_sign != 0) {
		t++;
	}
	/* decimal point */
	if (have_dot != 0) {
		t++;
	}
	/* a or A conversion => hex prefix */
	if (pdt->base == BASE_HEX) {
		t= t + 2;
	}

	/* compute space or zero (integer part) padding */
	if (PF_IS_SET(FLAG_ZERO_PADDING)) {
		izplen = pdt->fwidth - t;
		if (izplen < 0) {
			izplen = 0;
		}
		splen = 0;
	} else {
		splen = pdt->fwidth - t;
		if (splen < 0) {
			splen = 0;
		}
		izplen = 0;
	}

	/* right justify space padding */
	if (PF_IS_UNSET(FLAG_LEFT_JUSTIFIED)) {
		while (splen > 0) {
			fill_buffer(pdt, ' ');
			splen--;
		}
	}

	/* if we have a sign */
	if (have_sign != 0) {
		fill_buffer(pdt, sign);
	}

	/* hex alternative form */
	if (pdt->base == BASE_HEX) {
		fill_buffer(pdt, '0');
		if (PF_IS_UNSET(FLAG_UPPERCASE)) {
			fill_buffer(pdt, 'x');
		} else {
			fill_buffer(pdt, 'X');
		}
	}

	/* zero padding */
	while (izplen > 0) {
		fill_buffer(pdt, '0');
		izplen--;
	}

	/* integral part */
	while (ilen > 0) {
		fill_buffer(pdt, ibuf[ilen - 1]);
		ilen--;
	}

	if (have_dot) {
		fill_buffer(pdt, '.');

		if (pdt->prec > 0) {
			/* fractional part */
			while (flen > 0) {
				fill_buffer(pdt, fbuf[flen - 1]);
				flen--;
			}

			if (skip_zero == 0) {
				/* zero padding */
				while (fzplen > 0) {
					fill_buffer(pdt, '0');
					fzplen--;
				}
			}
		}
	}

	if (have_exp != 0) {
		/* exponent part */
		print_exp(pdt, expnt);
	}

	/* left justify space padding */
	if (PF_IS_SET(FLAG_LEFT_JUSTIFIED)) {
		while (splen > 0) {
			fill_buffer(pdt, ' ');
			splen--;
		}
	}

#ifdef DEBUG_VSNPRINTF
	printf("convert_real_float() -> out\n");
#endif
}


/***************************
 * convert_special_float() *
 ***********************************************************************
 DESCR
	Convert a NaN or an infinite float

 IN
 	pdt :		vsnprintf common data structure
	value :		unsigned integer to convert
	type :		type of special float

 OUT
	NONE
 ***********************************************************************/

static void convert_special_float(vsnp_t *pdt, flt_t value, int type) {
	char	*fstr = NULL,
			 sign = ' ';
	int		 have_sign = 0;
	size_t	 i,
			 len,
			 splen;

#ifdef DEBUG_VSNPRINTF
	printf("convert_special_float() -> in\n");
#endif

	if (value < 0) {
		/* on negative value set the sign */
		have_sign = 1;
		sign = '-';
	} else {
		/* else if + flag is set display the positive sign */
		if (PF_IS_SET(FLAG_SIGNED)) {
			have_sign = 1;
			sign = '+';
		}
	}

	/* set special float type string */
	switch (type) {
		case FLT_IS_NAN :
			/* float is NaN */
			if (PF_IS_SET(FLAG_UPPERCASE)) {
				fstr = UPPER_NAN;
			} else {
				fstr = LOWER_NAN;
			}
			break;

		case FLT_IS_INF :
			/* float is infinite */
			if (PF_IS_UNSET(FLAG_UPPERCASE)) {
				fstr = UPPER_INF;
			} else {
				fstr = LOWER_INF;
			}
			break;
	}

	/* compute space padding length */
	len = strlen(fstr);
	splen = pdt->fwidth - len;
	if (have_sign != 0) {
		splen--;
	}

	/* right justify space padding */
	if (PF_IS_UNSET(FLAG_LEFT_JUSTIFIED)) {
		while (splen > 0) {
			fill_buffer(pdt, ' ');
			splen--;
		}
	}

	/* if we have a sign */
	if (have_sign != 0) {
		fill_buffer(pdt, sign);
	}

	/* special float string */
	for (i = 0 ; i < len ; i++) {
		fill_buffer(pdt, fstr[i]);
	}

	/* left justify space padding */
	if (PF_IS_SET(FLAG_LEFT_JUSTIFIED)) {
		while (splen > 0) {
			fill_buffer(pdt, ' ');
			splen--;
		}
	}

#ifdef DEBUG_VSNPRINTF
	printf("convert_special_float() -> out\n");
#endif
}


/*******************
 * convert_float() *
 ***********************************************************************
 DESCR
	Wrapper of floating point conversion

 IN
 	pdt :		vsnprintf common data structure
	value :		unsigned integer to convert

 OUT
	NONE

 ***********************************************************************/

static void convert_float(vsnp_t *pdt, flt_t value) {
#ifdef DEBUG_VSNPRINTF
	printf("convert_float() -> in\n");
#endif

	if (isnan(value) != 0) {
		/* float is NaN */
		convert_special_float(pdt, value, FLT_IS_NAN);
	} else {
		if (isinf(value) != 0) {
			/* float is infinite */
			convert_special_float(pdt, value, FLT_IS_INF);
		}
	}

	/* process real float */
	convert_real_float(pdt, value);

#ifdef DEBUG_VSNPRINTF
	printf("convert_float() -> out\n");
#endif
}


/*******************
 * pmk_vsnprintf() *
 ***********************************************************************
 DESCR
	String formatting function with fixed bufer size and va_list
	argument type

 IN
	buf :		buffer location
	len :		buffer size
	fmt :		format string
	args :		list of arguments

 OUT
	number of characters that would have been written if the buffer had
	not been size limited

 TODO
	wide char support
 ***********************************************************************/

int pmk_vsnprintf(char *buf, size_t len, const char *fmt, va_list args) {
	char				*pstr,
						*pc = NULL;
	flt_t				 flt_val;
	int					*pi = NULL,
						 state = PARSE_CHAR,
						 modifier = 0,
						 n_modsave = MDFR_NORMAL,
						 have_n_conv = 0;
#ifdef HAVE_INTMAX_T
	intmax_t			*pimt = NULL;
#endif /* HAVE_INTMAX_T */
	long				*pl = NULL;
#ifdef HAVE_LONG_LONG
	long long			*pll = NULL;
#endif /* HAVE_LONG_LONG */
#ifdef HAVE_PTRDIFF_T
	ptrdiff_t			*ppd = NULL;
#endif /* HAVE_PTRDIFF_T */
	short				*ps = NULL;
	signed_t			 int_val;
	size_t				*pst = NULL;
	unsigned_t			 uint_val;
	void				*ptr;
	vsnp_t				 data;
#ifdef HAVE_WCHAR_T
	wchar_t				*wc;
#endif /* HAVE_WCHAR_T */
#ifdef HAVE_WINT_T
	wint_t				 wi;
#endif /* HAVE_WINT_T */

	/* init common data */
	data.buf = buf;
	data.len = len;
	data.cur = 0;

	/* loop until the end of the format string is reached */
	while (*fmt != '\0') {
		switch (state) {
			case PARSE_CHAR :
				/*
					check if we have a conversion sequence
				*/

#ifdef DEBUG_VSNPRINTF
printf("Enter state PARSE_CHAR\n");
#endif

				if (*fmt == '%') {
					data.flags = FLAG_NONE;
					data.fwidth = 0; /* -1 ? */
					data.prec = -1;
					modifier = MDFR_NORMAL;
					state = PARSE_FLAGS;
				} else {
					/* else copy the character in the buffer */
					fill_buffer(&data, *fmt);
				}

				/* next character */
				fmt++;
				break;

			case PARSE_FLAGS :
				/*
					parse the conversion flags
				*/

#ifdef DEBUG_VSNPRINTF
printf("Enter state PARSE_FLAGS\n");
#endif

				switch (*fmt) {
					case '+' :
						SET_FLAG(FLAG_SIGNED);
						fmt++;
						break;

					case '-' :
						SET_FLAG(FLAG_LEFT_JUSTIFIED);
						fmt++;
						break;

					case ' ' :
						SET_FLAG(FLAG_SPACE_PREFIX);
						fmt++;
						break;

					case '#' :
						SET_FLAG(FLAG_ALTERNATIVE_FORM);
						fmt++;
						break;

					case '0' :
						SET_FLAG(FLAG_ZERO_PADDING);
						fmt++;
						break;

					default :
						state = PARSE_FLD_WIDTH;
				}
				break;

			case PARSE_FLD_WIDTH :
				/*
					parse the field width
				*/

#ifdef DEBUG_VSNPRINTF
printf("Enter state PARSE_FLD_WIDTH\n");
#endif

				/* if we got an asterisk */
				if (*fmt == '*') {
					/* then get the field width from arguments */
					data.fwidth = va_arg(args, int);
					/* if field width is negative */
					if (data.fwidth < 0) {
						/* take absolute value for the width */
						data.fwidth = data.fwidth * (-1);
						/* and set left justify flag */
						SET_FLAG(FLAG_LEFT_JUSTIFIED);
					}
					fmt++;
				} else {
					/* else take the width from format string */
					while ((*fmt >= '0') && (*fmt <= '9')) {
						data.fwidth = (data.fwidth * 10) + (int) (*fmt - '0');
						fmt++;
					}
				}

				/* ignore 0 flag if - flag is provided */
				if (F_IS_SET(FLAG_LEFT_JUSTIFIED)) {
					UNSET_FLAG(FLAG_ZERO_PADDING);
				}

				state = PARSE_DOT;
				break;

			case PARSE_DOT :
				/*
					check if the dot of precision field is given
				*/

#ifdef DEBUG_VSNPRINTF
printf("Enter state PARSE_DOT\n");
#endif

				if (*fmt == '.') {
					/* if yes parse the precision field */
					fmt++;
					data.prec = 0;
					state = PARSE_PRECISION;
					/*flags = flags & (FLAG_ALL ^ FLAG_ZERO_PADDING);*//* XXX meant for something ??? */
				} else {
					/* else go parse the modifier */
					state = PARSE_LEN_MDFR;
				}
				break;

			case PARSE_PRECISION :
				/*
					parse precision field
				*/

				/* if we got an asterisk */
				if (*fmt == '*') {
					/* then get the precision from arguments */
					data.prec = va_arg(args, int);
					fmt++;
				} else {
					/* else take the precision from format string */
					while ((*fmt >= '0') && (*fmt <= '9')) {
						data.prec = (data.prec * 10) + (int) (*fmt - '0');
						fmt++;
					}
				}

				/* go parse the modifier */
				state = PARSE_LEN_MDFR;
				break;

			case PARSE_LEN_MDFR :
				/*
					parse modifier
				*/

#ifdef DEBUG_VSNPRINTF
printf("Enter state PARSE_LEN_MDFR\n");
#endif

				switch (*fmt) {
					case 'h' :
						switch (modifier) {
							case MDFR_NORMAL :
								modifier = MDFR_SHORT;
								fmt++;
								break;

							case MDFR_SHORT :
								modifier = MDFR_CHAR;
								state = PARSE_CONV_SPEC;
								fmt++;
								break;

							default :
								state = PARSE_CONV_SPEC;
						}
						break;

					case 'l' :
						switch (modifier) {
							case MDFR_NORMAL :
								modifier = MDFR_LONG;
								fmt++;
								break;

							case MDFR_LONG :
#if defined(HAVE_LONG_LONG) || defined(HAVE_UNSIGNED_LONG_LONG)
								modifier = MDFR_LONG_LONG;
#endif /* HAVE_LONG_LONG || HAVE_UNSIGNED_LONG_LONG */
								state = PARSE_CONV_SPEC;
								fmt++;
								break;
							default :
								state = PARSE_CONV_SPEC;
						}
						break;

					case 'j' :
						modifier = MDFR_INTMAX;
						state = PARSE_CONV_SPEC;
						fmt++;
						break;

					case 'z' :
						modifier = MDFR_SIZET;
						state = PARSE_CONV_SPEC;
						fmt++;
						break;

					case 't' :
						modifier = MDFR_PTRDIFF;
						state = PARSE_CONV_SPEC;
						fmt++;
						break;

					case 'L' :
						modifier = MDFR_LONG_DBL;
						state = PARSE_CONV_SPEC;
						fmt++;
						break;

					default :
						state = PARSE_CONV_SPEC;
				}
				break;

			case PARSE_CONV_SPEC :
				/*
					parse conversion specifier
				*/

#ifdef DEBUG_VSNPRINTF
printf("Enter state PARSE_CONV_SPEC\n");
#endif

				switch(*fmt) {
					case 'd' :
					case 'i' :
						/*
							signed decimal
						*/

						/* default precision */
						if (data.prec < 0) {
							data.prec = 1;
						}

						/* process modifier */
						switch (modifier) {
							case MDFR_LONG :
								int_val = (signed_t) va_arg(args, long);
								break;

#ifdef HAVE_LONG_LONG
							case MDFR_LONG_LONG :
								int_val = (signed_t) va_arg(args, long long);
								break;
#endif /* HAVE_LONG_LONG */

							case MDFR_SHORT :
							case MDFR_CHAR :
								/* short and char are promoted to int throught ... */
								int_val = (signed_t) va_arg(args, int);
								break;

#ifdef HAVE_INTMAX_T
							case MDFR_INTMAX :
								int_val = (signed_t) va_arg(args, intmax_t);
								break;
#endif /* HAVE_INTMAX_T */

							case MDFR_SIZET :
								int_val = (signed_t) va_arg(args, size_t);
								break;

#ifdef HAVE_PTRDIFF_T
							case MDFR_PTRDIFF :
								int_val = (signed_t) va_arg(args, ptrdiff_t);
								break;
#endif /* HAVE_PTRDIFF_T */
							default :
								int_val = (signed_t) va_arg(args, int);
						}

						data.base = 10;

						convert_sint(&data, int_val);
						break;

					case 'o' :
					case 'u' :
					case 'x' :
					case 'X' :
						/*
							unsigned conversion
						*/

						/* default precision */
						if (data.prec < 0) {
							data.prec = 1;
						}

						/* ignore - flag */
						UNSET_FLAG(FLAG_SIGNED);

						/* process modifier */
						switch (modifier) {
							case MDFR_LONG :
								uint_val = (unsigned_t) va_arg(args, unsigned long);
								break;

#ifdef HAVE_UNSIGNED_LONG_LONG
							case MDFR_LONG_LONG :
								uint_val = (unsigned_t) va_arg(args, unsigned long long);
								break;
#endif /* HAVE_UNSIGNED_LONG_LONG */

							case MDFR_SHORT :
							case MDFR_CHAR :
								/* short and char are promoted to int throught ... */
								uint_val = (unsigned_t) va_arg(args, unsigned int);
								break;

#ifdef HAVE_UINTMAX_T
							case MDFR_INTMAX :
								uint_val = (unsigned_t) va_arg(args, uintmax_t);
								break;
#endif /* HAVE_UINTMAX_T */

							case MDFR_SIZET :
								uint_val = (unsigned_t) va_arg(args, size_t);
								break;

#ifdef HAVE_PTRDIFF_T
							case MDFR_PTRDIFF :
								uint_val = (unsigned_t) va_arg(args, ptrdiff_t);
								break;
#endif /* HAVE_PTRDIFF_T */
							default :
								uint_val = (unsigned_t) va_arg(args, unsigned int);
						}

						/* set base */
						switch (*fmt) {
							case 'o' :
								data.base = BASE_OCT;
								break;

							case 'u' :
								data.base = BASE_DEC;
								break;

							case 'X' :
								SET_FLAG(FLAG_UPPERCASE);
								/* no break */

							case 'x' :
								data.base = BASE_HEX;
								break;
						}

						convert_int(&data, uint_val);
						break;

					case 'F' :
					case 'E' :
					case 'G' :
					case 'A' :
						/*
							float conversion (uppercase)
						*/

						SET_FLAG(FLAG_UPPERCASE);
						/* no break */

					case 'f' :
					case 'e' :
					case 'g' :
					case 'a' :
						/*
							float conversion (common)
						*/

						switch (*fmt) {
							case 'F' :
							case 'f' :
								data.base = BASE_DEC;

								/* default precision */
								if (data.prec < 0) {
									data.prec = 6;
								}
								break;

							case 'E' :
							case 'e' :
								data.base = BASE_DEC;

								SET_FLAG(FLAG_EXPONENT);

								/* default precision */
								if (data.prec < 0) {
									data.prec = 6;
								}
								break;

							case 'G' :
							case 'g' :
								data.base = BASE_DEC;

								SET_FLAG(FLAG_G_CONVERSION);

								/* default precision */
								if (data.prec <= 0) {
									data.prec = 6; /* XXX ISO C99 = 1 */
								}
								break;

							case 'A' :
							case 'a' :
								data.base = BASE_HEX;

								SET_FLAG(FLAG_EXPONENT);

								/* default precision */
								if (data.prec < 0) {
									data.prec = 4; /* XXX real default value is ? */
								}
								break;
						}

#ifdef HAVE_LONG_DOUBLE
						if (modifier == MDFR_LONG_DBL) {
							flt_val = (flt_t) va_arg(args, long double);
						} else {
#endif /* HAVE_LONG_DOUBLE */
							flt_val = (flt_t) va_arg(args, double);
#ifdef HAVE_LONG_DOUBLE
						}
#endif /* HAVE_LONG_DOUBLE */

						convert_float(&data, flt_val);
						break;

					case 'c' :
						/*
							character conversion
						*/

#ifdef HAVE_WINT_T
						if (modifier == MDFR_LONG) {
							/* wide char */
							wi = va_arg(args, wint_t);

							/* XXX TODO wide char support */
						} else {
#endif /* HAVE_WINT_T */
							/* char is promoted to int throught ... */
							int_val = (signed_t) va_arg(args, int);

							fill_buffer(&data, (char) int_val);
#ifdef HAVE_WINT_T
						}
#endif /* HAVE_WINT_T */

						break;

					case 's' :
						/*
							string conversion
						*/

#ifdef HAVE_WCHAR_T
						if (modifier == MDFR_LONG) {
							/* wide char */
							wc = va_arg(args, wchar_t *);

							/* XXX TODO wide char support */
						} else {
#endif /* HAVE_WCHAR_T */
							pstr = va_arg(args, char *);

							if (data.prec <= 0) {
								data.prec = len;
							}

							convert_str(&data, pstr);
#ifdef HAVE_WCHAR_T
						}
#endif /* HAVE_WCHAR_T */

						break;

					case 'p' :
						/*
							pointer conversion
						*/
						ptr = va_arg(args, void *);

						data.base = BASE_HEX;

/* XXX ?
						SET_FLAG(FLAG_ALTERNATIVE_FORM);
*/

						convert_int(&data, (unsigned_t) (size_t) ptr);
						break;

					case 'n' :
						/*
							pointer to the variable that will receive string size
						*/

						/* set flag */
						have_n_conv = 1;

						/* save modifier */
						n_modsave = modifier;

						/* process modifier */
						switch (modifier) {
							case MDFR_LONG :
								pl = va_arg(args, long *);
								break;

#ifdef HAVE_LONG_LONG
							case MDFR_LONG_LONG :
								pll = va_arg(args, long long *);
								break;
#endif /* HAVE_LONG_LONG */

							case MDFR_SHORT :
								ps = va_arg(args, short *);
								break;

							case MDFR_CHAR :
								pc = va_arg(args, char *);
								break;

#ifdef HAVE_INTMAX_T
							case MDFR_INTMAX :
								pimt = va_arg(args, intmax_t *);
								break;
#endif /* HAVE_INTMAX_T */

							case MDFR_SIZET :
								pst = va_arg(args, size_t *);
								break;

#ifdef HAVE_PTRDIFF_T
							case MDFR_PTRDIFF :
								ppd = va_arg(args, ptrdiff_t *);
								break;
#endif /* HAVE_PTRDIFF_T */
							default :
								pi = va_arg(args, int *);
						}
						break;

					case '%' :
						/*
							"escaped" conversion character
						*/

						/* XXX we allow complete specification, != ISO C99 ? */

						fill_buffer(&data, *fmt);
						break;
				}

				/* next char */
				fmt++;

				state = PARSE_CHAR;
				break;
		}
	}

	/* NULL terminate the string */
	if (len > data.cur) {
		buf[data.cur] = '\0';
	} else {
		buf[len - 1] = '\0';
	}

	/* if %n provided, write lenght into the pointed area */
	if (have_n_conv != 0) {
		/* process modifier */
		switch (n_modsave) {
			case MDFR_LONG :
				*pl = (long) data.cur;
				break;

#ifdef HAVE_LONG_LONG
			case MDFR_LONG_LONG :
				*pll = (long long) data.cur;
				break;
#endif /* HAVE_LONG_LONG */

			case MDFR_SHORT :
				*ps = (short) data.cur;
				break;

			case MDFR_CHAR :
				*pc = (char) data.cur;
				break;

#ifdef HAVE_INTMAX_T
			case MDFR_INTMAX :
				*pimt = (intmax_t) data.cur;
				break;
#endif /* HAVE_INTMAX_T */

			case MDFR_SIZET :
				*pst = data.cur;
				break;

#ifdef HAVE_PTRDIFF_T
			case MDFR_PTRDIFF :
				*ppd = (ptrdiff_t) data.cur;
				break;
#endif /* HAVE_PTRDIFF_T */
			default :
				*pi = (int) data.cur;
		}
	}

	/* return what should have been the string lenght if not limited */
	return(data.cur);
}


/*****************
 * pmk_strlcpy() *
 ***********************************************************************
 DESCR
	This function duplicate a string.

 IN
	ostr :	char pointer of source string

 OUT
	pointer to duplicated string or NULL
 ***********************************************************************/

char *pmk_strdup(const char *ostr) {
	char	*pstr;
	size_t	 len;

	len = strlen(ostr) + 1;

	pstr = (char *) malloc(len);
	if (pstr != NULL) {
		memcpy(pstr, ostr, len);
	}

	return(pstr);
}


/*****************
 * pmk_strlcpy() *
 ***********************************************************************
 DESCR
	This function copy a given number of characters from a source
	string to a destination buffer. It also grants that this buffer
	will be null terminated.

 IN
	dst :	char pointer of destination buffer
	src :	char pointer of source string
	s :		number of characters to copy

 OUT
	size of the source string
 ***********************************************************************/

size_t pmk_strlcpy(char *dst, const char *src, size_t s) {
	size_t	len = 0;

	/* loop until we reach the end of the src string */
	while (*src != '\0') {
		/* if buffer is not full */
		if (s > 0) {
			if (s == 1) {
				/* last character, null terminate */
				*dst = '\0';
			} else {
				/* copy character */
				*dst = *src;
			}

			/* adjust remaining size */
			s--;

			/* update src string length */
			len++;
			/* and dst pointer */
			dst++;
		}

		/* increment src pointer */
		src++;
	}

	/* if the end of the buffer has not been reached */
	if (s > 0) {
		/* last character, null terminate */
		*dst = '\0';
	}

	return(len);
}


/*****************
 * pmk_strlcat() *
 ***********************************************************************
 DESCR
	This function append a given number of characters from a source
	string to the end of a destination buffer. It also grants that
	this buffer will be null terminated.

 IN
	dst :	char pointer of destination buffer
	src :	char pointer of source string
	s :		number of characters to copy

 OUT
	size of the source string
 ***********************************************************************/

size_t pmk_strlcat(char *dst, const char *src, size_t s) {
	size_t	len;

	/* get size of actual string */
	len = strlen(dst);

	/* start right after the last character */
	dst = dst + len;

	/* update size */
	s = s -len;

	/* update len with the result of the string copy */
	len = len + pmk_strlcpy(dst, src, s);

	return(len);
}


/***************************
 * find_parent_separator() *
 ***********************************************************************
 DESCR
	try to find the last separator between directory name and file name

 IN
	path :	path string
	psep :	separator location
	plen :	path string len

 OUT
	NONE
 ***********************************************************************/

static void find_parent_separator(char *path, char **psep, size_t *plen) {
	char		*psave = NULL;
	int			 have_sep = 0;

	/* initialize length */
	*plen = 0;

	/* initialize separator position */
	*psep = NULL;

	while (*path != '\0') {
		if (*path == '/') {
			/* set flag */
			have_sep = 1;

			/* save position */
			psave = path;
		} else {
			if (have_sep != 0) {
				/* had a separator before -> save last position */
				*psep = psave;
			}
		}

		/* next character */
		path++;
		(*plen)++;
	}
}


/*****************
 * pmk_dirname() *
 ***********************************************************************
 DESCR
	XXX

 IN
	XXX

 OUT
	XXX
 ***********************************************************************/

char *pmk_dirname(char *path) {
	static char	 buffer[MAXPATHLEN];
	char		*pstr,
				*psep;
	size_t		 len;

	/* start at the begining of the buffer */
	pstr = buffer;

	if ((path == NULL) || (*path == '\0')) {
		*pstr = '.';
		pstr++;
	} else {
		/* look for base/dir separator */
		find_parent_separator(path, &psep, &len);

		if (psep == NULL) {
			/* no valid separator found */
			if (*path == '/') {
				*pstr = '/';
			} else {
				*pstr = '.';
			}
			pstr++;
		} else {
			while ((psep > path) && (*psep == '/')) {
				/* skip trailing slashes */
				psep--;
			}

			/* take care of buffer overflow */
			if ((size_t) (psep - path) > sizeof(buffer)) {
				return(NULL);
			}

			/* copy characters until separator position */
			while (path <= psep) {
				*pstr = *path;
				pstr++;
				path++;
			}
		}
	}

	/* NULL terminate the buffer */
	*pstr = '\0';

	return(buffer);
}


/******************
 * pmk_basename() *
 ***********************************************************************
 DESCR
	XXX

 IN
	XXX

 OUT
	XXX
 ***********************************************************************/

char *pmk_basename(char *path) {
	static char	 buffer[MAXPATHLEN];
	char		*pstr,
				*psep,
				*pbeg,
				*pend;
	size_t		 len;

	/* start at the begining of the buffer */
	pstr = buffer;

	/* if path is a null pointer or empty string */
	if ((path == NULL) || (*path == '\0')) {
		*pstr = '.';
		pstr++;
	} else {
		/* else look for base/dir separator */
		find_parent_separator(path, &psep, &len);

		/* if no valid separator is found */
		if (psep == NULL) {
			/* return the whole string */
			pbeg = path;
		} else {
			/* else return the string starting right after separator */
			pbeg = psep + 1;
		}

		/* compute end of string pointer */
		pend = path + len - 1;

		while ((pend > pbeg) && (*pend == '/')) {
			/* skip trailing slashes */
			pend--;
		}

		/* take care of buffer overflow */
		if ((size_t) (pend - pbeg) > sizeof(buffer)) {
			return(NULL);
		}

		/* copy characters until separator position */
		while (pbeg <= pend) {
			*pstr = *pbeg;
			pstr++;
			pbeg++;
		}
	}

	/* NULL terminate the buffer */
	*pstr = '\0';

	return(buffer);
}


/*****************
 * pmk_isblank() *
 ***********************************************************************
 DESCR
	XXX

 IN
	XXX

 OUT
	XXX
 ***********************************************************************/

int pmk_isblank(int c) {
	if (c == ' ' || c == '\t')
		return (1);
	else
		return(0);
}


/******************
 * pmk_mkstemps() *
 ***********************************************************************
 DESCR
	XXX

 IN
	XXX

 OUT
	XXX
 ***********************************************************************/

int pmk_mkstemps(char *template, int suffixlen) {
	struct timeval	 tv;
	char			 subst[] = "aZbY0cXdWe1VfUgT2hSiRj3QkPlO4mNnMo5LpKqJ6rIsHt7GuFvE8wDxCy9BzA",
					*start,
					*end,
					*p;
	int				 fd,
					 len,
					 i;

	/* forwarding to end of template */
	for (p = template ; *p != '\0' ; p++);

	/* increment len to also count end of file character */
	suffixlen++;

	/* compute (supposed) position of last character to replace */
	p = p - suffixlen;

	/* check it baby ;) */
	if (p < template)
		return(-1);

	/* set last character position */
	end = p;

	/* step back until we found the starting character */
	for ( ; *p == MKSTEMPS_REPLACE_CHAR && p > template; p--);

	/* set fisrt character position */
	start = ++p;

	/* intialise random() */
	len = strlen(subst);
	gettimeofday(&tv, NULL);
	srandom(tv.tv_sec * tv.tv_usec);

	/* lets go replacing the stuff */
	for (p = start ; p <= end ; p++) {
		/* get random value */
		i = (int) random() % len;
		/* replace */
		*p = subst[i];
	}

	/* open file */
	fd = open(template, O_CREAT|O_EXCL|O_RDWR, S_IRUSR | S_IWUSR);

	return(fd);
}


/************************
 *	compatibility stuff *
 ***********************************************************************/

/********************
 *	pmk_stdio.h	*
 ********************/

#ifndef HAVE_VSNPRINTF
int vsnprintf(char *buf, size_t len, const char *fmt, va_list args) {
	return(pmk_vsnprintf(buf, len, fmt, args));
}

#endif /* HAVE_VSNPRINTF */


#ifndef HAVE_SNPRINTF
int snprintf(char *str, size_t size, const char *format, ...) {
	int		rslt;
	va_list	args;

	va_start(args, format);
	rslt = vsnprintf(str, size, format, args);
	va_end(args);

	return(rslt);
}
#endif /* HAVE_SNPRINTF */


/********************
 *	pmk_string.h	*
 ********************/

#ifndef HAVE_STRDUP
char *strdup(const char *str) {
	return(pmk_strdup(str));
}
#endif /* HAVE_STRDUP */


#ifndef HAVE_STRLCAT
size_t strlcat(char *dst, const char *src, size_t s) {
	return(pmk_strlcat(dst, src, s));
}
#endif /* HAVE_STRLCAT */


#ifndef HAVE_STRLCPY
size_t strlcpy(char *dst, const char *src, size_t s) {
	return(pmk_strlcpy(dst, src, s));
}
#endif /* HAVE_STRLCPY */


/********************
 *	pmk_libgen.h	*
 ********************/

#ifndef HAVE_LIBGEN_H
char *dirname(char *path) {
	return(pmk_dirname(path));
}

char *basename(char *path) {
	return(pmk_basename(path));
}
#endif /* HAVE_LIBGEN_H */


/****************
 *	pmk_ctype.h	*
 ****************/

#ifndef HAVE_ISBLANK
int isblank(int c) {
	return(pmk_isblank(c));
}
#endif /* HAVE_ISBLANK */


/********************
 *	pmk_unistd.h	*
 ********************/

#ifndef HAVE_MKSTEMPS
int mkstemps(char *template, int suffixlen) {
	return(pmk_mkstemps(template, suffixlen));
}
#endif /* HAVE_MKSTEMPS */


/********************************
 *	boolean string functions	*
 ***********************************************************************/

/****************
 * snprintf_b() *
 ***********************************************************************
 DESCR
	boolean snprintf

 IN
	buf :	buffer location
	siz :	buffer size
	fmt :	format string
	... :	argument list

 OUT
	boolean value
 ***********************************************************************/

bool snprintf_b(char *buf, size_t siz, const char *fmt, ...) {
	bool	rslt;
	va_list	ap;

	va_start(ap, fmt);

	if (vsnprintf(buf, siz, fmt, ap) >= (int) siz)
		rslt = false;
	else
		rslt = true;

	va_end(ap);

	return(rslt);
}


/***************
 * strlcat_b() *
 ***********************************************************************
 DESCR
	boolean strlcat

 IN
	dst :	destination buffer
	src :	source string
	siz :	size of destination buffer

 OUT
	boolean
 ***********************************************************************/

bool strlcat_b(char *dst, const char *src, size_t siz) {
	if (strlcat(dst, src, siz) >= siz)
		return(false);
	else
		return(true);
}


/***************
 * strlcpy_b() *
 ***********************************************************************
 DESCR
	boolean strlcpy

 IN
	dst :	destination buffer
	src :	source string
	siz :	size of destination buffer

 OUT
	boolean
 ***********************************************************************/

bool strlcpy_b(char *dst, const char *src, size_t siz) {
	if (strlcpy(dst, src, siz) >= siz)
		return(false);
	else
		return(true);
}

/* vim: set noexpandtab tabstop=4 softtabstop=4 shiftwidth=4: */


syntax highlighted by Code2HTML, v. 0.9.1