In article <
68669c25$0$690$14726298@news.sunsite.dk>,
Arne Vajhøj <
arne@vajhoej.dk> wrote:
On 7/1/2025 3:57 PM, Arne Vajhøj wrote:
On 6/25/2025 8:15 AM, Craig A. Berry wrote:
feel free to take a crack at it and submit a PR. To
work against current sources, you either need to use autoconf with a git
checkout or get a nightly snapshot from <https://www.freetds.org>.
I may take a look.
Submitting a PR may be a problem. I have never had a personal
Github account and I suspect that my work Github account has
been deactivated. But I will worry about that when/if I have
a good fix.
>
First take:
>
https://www.vajhoej.dk/arne/temp/tds_vasprintf.c
Instead of putting this on a web page on your private sever, you
should consider putting it on github or similar in a place where
people can comment directly on the code.
Total rewrite. I did not like the old code at all.
Your code is incorrect.
Note that `vsnprintf` et al do not include space for the NUL
terminator in their return value, and you do not account for
this when you malloc your destination buffer. Since you then
use the (unsafe, unchecked) `vsprintf` function to actually
format, you're writing off the end of the allocated buffer,
resulting in undefined behavior. I get that you use `vsprintf`
here for maximum portability, but it's a poor substitute for
`vsnprintf`. Sufficiently so, that I think you're better off
using it if you can.
As the main logic is the same in each case (find out the buffer
size, allocate, and then assign and return), and the only real
difference is in finding out the buffer length, your code would
be more readable with some helper functions that you delegated
to.
You refer to `_PATH_DEVNULL` but do not `#include <paths.h>`, as
required by POSIX.
Your standards matrix includes C90, but note that C90 is not
(generally) going to be supported by your code, unless the
system already supports `vasprintf` or `va_copy` as an
extension. In particular, every option that does not include
`vsnprintf` requires `va_copy`, so you may as well test for that
once and error immediately.
An obvious optimization, in the cases where you try `vsnprintf`,
would be to observe that most strings are going to be fairly
small (say, a K or less) and speculatively write into a
stack-allocated buffer when determining the size; if the result
fits, then simply `strdup` the result and return. This would
avoid having to call into the (comparatively expensive)
formatting code twice, but it hardly seems worth it; I didn't
bother in my implementation (which is below).
- Dan C.
/*
* SPDX-License-Identifier: LGPL 2.1 OR Apache 2.0
*
* vasprintf implementation
*/
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
//#include "config.h"
static const size_t FAIL = ~(size_t)0;
#ifdef HAVE_VASPRINTF
int
tds_vasprintf(char **outp, const char *fmt, va_list ap)
{
return vasprintf(outp, fmt, ap);
}
#else
#ifndef va_copy
#error "va_copy macro required"
#endif
#ifdef HAVE_VSNPRINTF
static inline int
tds_vsnprintf(char *dst, size_t len, const char *fmt, va_list ap)
{
return vsnprintf(dst, len, fmt, ap);
}
static inline size_t
tds_vasprintf_bufsize(const char *fmt, va_list ap)
{
int ret = vsnprintf(NULL, 0, fmt, ap);
if (ret < 0)
return FAIL;
return ret;
}
#else
// Note: this is extremely dangerous, as `len` is mostly ignored.
static inline int
tds_vsnprintf(char *dst, size_t len, const char *fmt, va_list ap)
{
if (dst == NULL || len == 0)
return -1;
return vsprintf(dst, fmt, ap);
}
#include <paths.h>
#ifdef REENTRANT
#define neednull(fp) 1
#define putnull(fp) fclose(fp)
#else
#define neednull(fp) ((fp) == NULL)
#define putnull(fp)
#endif
static inline size_t
tds_vasprintf_bufsize(const char *fmt, va_list ap)
{
#ifdef HAVE_VSCPRINTF
return _vscprintf(fmt, ap);
#endif
static FILE *fp = NULL;
if (neednull(fp))
fp = fopen(_PATH_DEVNULL, "w");
if (fp == NULL)
return FAIL;
int len = vfprintf(fp, fmt, ap);
if(len < 0)
return FAIL;
putnull(fp);
return (size_t)len;
}
#endif
int tds_vasprintf(char **outp, const char *fmt, va_list ap)
{
va_list save_ap;
va_copy(save_ap, ap);
size_t len = tds_vasprintf_bufsize(fmt, ap);
if (len == ~(size_t)0)
return -1;
char *dst = malloc(len);
if (dst == NULL)
return -1;
vsprintf(dst, fmt, save_ap);
*outp = dst;
return (int)len;
}
#endif
#ifdef TEST
#include <assert.h>
#include <string.h>
char *
dovasprintf(const char *fmt, ...)
{
char *out = NULL;
int rv = 0;
va_list ap;
va_start(ap, fmt);
out = NULL;
rv = tds_vasprintf(&out, fmt, ap);
va_end(ap);
if (rv < 0)
return NULL;
return out;
}
int
main()
{
char *p = dovasprintf("This is the %dth test: %s", 10, "test string");
assert(strcmp(p, "This is the 10th test: test string") == 0);
return EXIT_SUCCESS;
}
#endif