* Copyright (c) 1983, 1995, 1996 Eric P. Allman
  * Copyright (c) 1988, 1993
  *     The Regents of the University of California.  All rights reserved.
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  *     SNPRINTF, VSNPRINTF and friends
  *
  * These versions have been grabbed off the net.  They have been
- * cleaned up to compile properly and support for most of the Single Unix
- * Specification has been added.  Remaining unimplemented features are:
+ * cleaned up to compile properly and support for most of the C99
+ * specification has been added.  Remaining unimplemented features are:
  *
  * 1. No locale support: the radix character is always '.' and the '
  * (single quote) format flag is ignored.
  * 5. Space and '#' flags are not implemented.
  *
  *
- * The result values of these functions are not the same across different
- * platforms.  This implementation is compatible with the Single Unix Spec:
+ * Historically the result values of sprintf/snprintf varied across platforms.
+ * This implementation now follows the C99 standard:
  *
- * 1. -1 is returned only if processing is abandoned due to an invalid
- * parameter, such as incorrect format string.  (Although not required by
- * the spec, this happens only when no characters have yet been transmitted
- * to the destination.)
+ * 1. -1 is returned if an error is detected in the format string, or if
+ * a write to the target stream fails (as reported by fwrite).  Note that
+ * overrunning snprintf's target buffer is *not* an error.
  *
- * 2. For snprintf and sprintf, 0 is returned if str == NULL or count == 0;
- * no data has been stored.
+ * 2. For successful writes to streams, the actual number of bytes written
+ * to the stream is returned.
  *
- * 3. Otherwise, the number of bytes actually transmitted to the destination
- * is returned (excluding the trailing '\0' for snprintf and sprintf).
+ * 3. For successful sprintf/snprintf, the number of bytes that would have
+ * been written to an infinite-size buffer (excluding the trailing '\0')
+ * is returned.  snprintf will truncate its output to fit in the buffer
+ * (ensuring a trailing '\0' unless count == 0), but this is not reflected
+ * in the function result.
  *
- * For snprintf with nonzero count, the result cannot be more than count-1
- * (a trailing '\0' is always stored); it is not possible to distinguish
- * buffer overrun from exact fit.  This is unlike some implementations that
- * return the number of bytes that would have been needed for the complete
- * result string.
+ * snprintf buffer overrun can be detected by checking for function result
+ * greater than or equal to the supplied count.
  */
 
 /**************************************************************
 #undef fprintf
 #undef printf
 
-/* Info about where the formatted output is going */
+/*
+ * Info about where the formatted output is going.
+ *
+ * dopr and subroutines will not write at/past bufend, but snprintf
+ * reserves one byte, ensuring it may place the trailing '\0' there.
+ *
+ * In snprintf, we use nchars to count the number of bytes dropped on the
+ * floor due to buffer overrun.  The correct result of snprintf is thus
+ * (bufptr - bufstart) + nchars.  (This isn't as inconsistent as it might
+ * seem: nchars is the number of emitted bytes that are not in the buffer now,
+ * either because we sent them to the stream or because we couldn't fit them
+ * into the buffer to begin with.)
+ */
 typedef struct
 {
        char       *bufptr;                     /* next buffer output position */
        char       *bufstart;           /* first buffer element */
-       char       *bufend;                     /* last buffer element, or NULL */
+       char       *bufend;                     /* last+1 buffer element, or NULL */
        /* bufend == NULL is for sprintf, where we assume buf is big enough */
        FILE       *stream;                     /* eventual output destination, or NULL */
-       int                     nchars;                 /* # chars already sent to stream */
+       int                     nchars;                 /* # chars sent to stream, or dropped */
        bool            failed;                 /* call is a failure; errno is set */
 } PrintfTarget;
 
 pg_vsnprintf(char *str, size_t count, const char *fmt, va_list args)
 {
        PrintfTarget target;
+       char            onebyte[1];
 
-       if (str == NULL || count == 0)
-               return 0;
+       /*
+        * C99 allows the case str == NULL when count == 0.  Rather than
+        * special-casing this situation further down, we substitute a one-byte
+        * local buffer.  Callers cannot tell, since the function result doesn't
+        * depend on count.
+        */
+       if (count == 0)
+       {
+               str = onebyte;
+               count = 1;
+       }
        target.bufstart = target.bufptr = str;
        target.bufend = str + count - 1;
        target.stream = NULL;
-       /* target.nchars is unused in this case */
+       target.nchars = 0;
        target.failed = false;
        dopr(&target, fmt, args);
        *(target.bufptr) = '\0';
-       return target.failed ? -1 : (target.bufptr - target.bufstart);
+       return target.failed ? -1 : (target.bufptr - target.bufstart
+                                                                + target.nchars);
 }
 
 int
 {
        PrintfTarget target;
 
-       if (str == NULL)
-               return 0;
        target.bufstart = target.bufptr = str;
        target.bufend = NULL;
        target.stream = NULL;
-       /* target.nchars is unused in this case */
+       target.nchars = 0;                      /* not really used in this case */
        target.failed = false;
        dopr(&target, fmt, args);
        *(target.bufptr) = '\0';
-       return target.failed ? -1 : (target.bufptr - target.bufstart);
+       return target.failed ? -1 : (target.bufptr - target.bufstart
+                                                                + target.nchars);
 }
 
 int
                return -1;
        }
        target.bufstart = target.bufptr = buffer;
-       target.bufend = buffer + sizeof(buffer) - 1;
+       target.bufend = buffer + sizeof(buffer);        /* use the whole buffer */
        target.stream = stream;
        target.nchars = 0;
        target.failed = false;
 {
        size_t          nc = target->bufptr - target->bufstart;
 
+       /*
+        * Don't write anything if we already failed; this is to ensure we
+        * preserve the original failure's errno.
+        */
        if (!target->failed && nc > 0)
        {
                size_t          written;
                {
                        /* buffer full, can we dump to stream? */
                        if (target->stream == NULL)
-                               return;                 /* no, lose the data */
+                       {
+                               target->nchars += slen; /* no, lose the data */
+                               return;
+                       }
                        flushbuffer(target);
                        continue;
                }
        {
                /* buffer full, can we dump to stream? */
                if (target->stream == NULL)
-                       return;                         /* no, lose the data */
+               {
+                       target->nchars++;       /* no, lose the data */
+                       return;
+               }
                flushbuffer(target);
        }
        *(target->bufptr++) = c;