/*
 * libsal/io/vsnprintf.c
 * Print a variable-length message to a buffer of fixed width
 * 
 * DESCRIPTION:
 * 
 * 	sal_io_vsnprintf() is a poor man's implementation of the GNU libio's
 * 	vsnprintf() function, which takes a variable-length string, and
 * 	ensures that it fits into a fixed-width buffer.
 * 
 * 	The technique is derived from GNU's libio, where vsnprintf prints
 * 	into a fixed-width file buffer.  Here, we print to one side of a
 * 	pipe, and use read() to read the string back from the other side
 * 	of the pipe.
 * 
 * 	I make no guarentees about the performance, or (most likely) lack
 * 	of performance, of this routine.  At this time, it should NOT be
 * 	used as a general string printing routine like sprintf().  If
 * 	demand for this function prevails, it's time to look at linking
 * 	against GNU libio, I reckon.
 *
 * 	This routine is essential to ensure that nasty system cracker
 * 	types (I object to them being called hackers) don't overwrite
 * 	the system stack by passing us a string far longer than the buffer
 * 	it is intended for.
 * 
 * 	Original Author:
 * 	================
 * 	Stuart Herbert
 * 	Academic Computing Services, University of Sheffield
 */

#include <libsal/license.h>
#include <libsal/proto.h>
#include <libsal/defines.h>
#include <stdarg.h>
#include <unistd.h>
#include <stdio.h>

int sal_io_vsnprintf
(
  char    *szBuffer, 
  int      iMaxLen, 
  const char    *szFormat,
  va_list  vTmp)
{

  FILE * pstFp0;
    /* file pointer for writing into the pipe */
  
  int iFp[2];
    /* file handles for the pipe */
  int iResult;
    /* holds value returned by read() */
  
  /* first, ensure we've not been passed garbage */

  TEST_ARG(szBuffer != NULL, 1);
  TEST_ARG(szFormat != NULL, 3);
  
  if ((pipe(iFp) != 0))
  {
    /* !! FIXME:	Failed to open the pipe
     * !! DESC:		This devious trick relies on being able to open
     * !! DESC:		up a temporary pipe.  As we've just failed to
     * !! DESC:		open the pipe, what is our fallback plan?
     * !! DESC:
     * !! DESC:		Current implementation is to abort the process.
     * !! DESC:		Better ideas are welcome!!
     * !! END_FIXME:
     */
    
    perror("Failed to open temporary pipe in sal_io_vsnprintf!\n");
    abort();
  }
  
  if ((pstFp0 = fdopen(iFp[1], "w")) == NULL)
  {
    /*
     * !! FIXME:	Failed to duplicate the file handle
     * !! DESC:		Our attempts to duplicate the open write pipe handle
     * !! DESC:		to produce a FILE handle has been a miserable
     * !! DESC:		failure.
     * !! DESC:
     * !! DESC:		We continue with the strategy above, and log a
     * !! DESC:		process error and quit.
     * !! END_FIXME:
     */
    
    perror("Failed to duplicate file handles in sal_io_vsnprintf!\n");
    abort();
  }
  
  /*
   * now, we write the string out to this temporary pipe, and use read()
   * to read it back without going over the size of our buffer.
   * 
   * Yes, this isn't exactly elegant, but the alternative is to link
   * against GNU's libio, or write our own printf() format string parser.
   * For now, this will do nicely.
   */

  fflush(pstFp0);
  vfprintf(pstFp0, szFormat, vTmp);
  fflush(pstFp0);

  iResult = read(iFp[0], szBuffer, iMaxLen);
  if (iResult == iMaxLen)
    iResult--;
  szBuffer[iResult] = '\0';
    
  /*
   * we've got our nicely cropped string - now we close the pipe to get
   * rid of any excess.
   * 
   * NOTE:
   * 	We specifically do not leave the pipes open, as this would be
   * 	an important, and unexpected side-effect for anyone looking to
   * 	use this routine.
   */

  fclose(pstFp0);
  close(iFp[0]);
  close(iFp[1]);
  
  return (strlen(szBuffer));
}
