/*************************************************************************/
/* Copyright (c) 2004 */
/* Daniel Sleator, David Temperley, and John Lafferty */
/* Copyright 2008, 2009, 2013 Linas Vepstas */
/* All rights reserved */
/* */
/* Use of the link grammar parsing system is subject to the terms of the */
/* license set forth in the LICENSE file included with this software. */
/* This license allows free redistribution and use in source and binary */
/* forms, with or without modification, subject to certain conditions. */
/* */
/*************************************************************************/
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#ifdef _WIN32
#define _CRT_RAND_S
#endif /* _WIN32 */
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <stdarg.h>
#include <locale.h>
#ifdef HAVE_XLOCALE_H
#include <xlocale.h>
#endif /* HAVE_XLOCALE_H */
#ifndef _WIN32
// #include <unistd.h>
#include <langinfo.h>
#else
#include <windows.h>
#endif /* _WIN32 */
#include "link-includes.h"
#include "error.h"
#include "utilities.h"
/* This file contains general utilities that fix, enhance OS-provided
* API's, esp ones that the OS forgot to provide, or managed to break.
*/
/* ============================================================= */
/* String utilities */
/* Windows, POSIX and GNU have different ideas about strerror_r(). hence
* use our own function that uses the available system function and is
* consistent. It doesn't try to mimic exactly any version of
* strerror_r(). */
#ifdef _WIN32
void lg_strerror(int err_no, char *buf, size_t len)
{
if (strerror_s(buf, len, err_no) != 0)
strerror_s(buf, len, errno); /* errno got set by previous strerror_s() */
}
#else
#if HAVE_STRERROR_R
#if STRERROR_R_CHAR_P
/* Using the GNU version. */
void lg_strerror(int err_no, char *buf, size_t len)
{
char *errstr = strerror_r(err_no, buf, len);
strncpy(buf, errstr, len);
buf[len-1] = '\0';
}
#else /* !STRERROR_R_CHAR_P */
/* Using the XSI version. */
void lg_strerror(int err_no, char *buf, size_t len)
{
errno = 0;
if ((strerror_r(err_no, buf, len) == EINVAL) || (errno == EINVAL))
snprintf(buf, len, "Unknown error %d", err_no);
}
#endif /* STRERROR_R_CHAR_P */
#else /* !STRERROR_R */
/* No strerror_r()??? No thread-safe error message - use a workaround.
* (FIXME Could check if threads are not supported and use strerror(),
* else protect strerror().) */
void lg_strerror(int err_no, char *buf, size_t len)
{
snprintf(buf, len, "Error %d", err_no);
}
#endif /* STRERROR_R */
#endif /* _WIN32 */
char *safe_strdup(const char *u)
{
if (u)
return strdup(u);
return NULL;
}
/**
* A version of strlcpy, for those systems that don't have it.
*/
/* $OpenBSD: strlcpy.c,v 1.12 2015/01/15 03:54:12 millert Exp $ */
/*
* Copyright (c) 1998, 2015 Todd C. Miller <Todd.Miller@courtesan.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
* Copy string src to buffer dst of size dsize. At most dsize-1
* chars will be copied. Always NUL terminates (unless dsize == 0).
* Returns strlen(src); if retval >= dsize, truncation occurred.
*/
size_t
lg_strlcpy(char * restrict dst, const char * restrict src, size_t dsize)
{
const char *osrc = src;
size_t nleft = dsize;
/* Copy as many bytes as will fit. */
if (nleft != 0) {
while (--nleft != 0) {
if ((*dst++ = *src++) == '\0')
break;
}
}
/* Not enough room in dst, add NUL and traverse rest of src. */
if (nleft == 0) {
if (dsize != 0)
*dst = '\0'; /* NUL-terminate dst */
while (*src++)
;
}
return(src - osrc - 1); /* count does not include NUL */
}
/**
* Catenates as much of v onto u as it can assuming u is of size usize
* guaranteed to terminate u with a '\0'. Assumes u and v are null
* terminated.
*/
void safe_strcat(char *u, const char *v, size_t usize)
{
strncat(u, v, usize-strlen(u)-1);
u[usize-1] = '\0';
}
#ifndef HAVE_STRNDUP
/* Emulates glibc's strndup() */
char *
strndup (const char *str, size_t size)
{
size_t len;
char *result;
len = strlen (str);
if (!len) return strdup ("");
if (size > len) size = len;
result = (char *) malloc ((size + 1) * sizeof (char));
memcpy (result, str, size);
result[size] = 0x0;
return result;
}
#endif /* !HAVE_STRNDUP */
#ifndef HAVE_STRTOK_R
/*
* public domain strtok_r() by Charlie Gordon
* from comp.lang.c 9/14/2007
* http://groups.google.com/group/comp.lang.c/msg/2ab1ecbb86646684
*
* Declaration that it's public domain:
* http://groups.google.com/group/comp.lang.c/msg/7c7b39328fefab9c
*/
char* strtok_r(char *str, const char *delim, char **nextp)
{
char *ret;
if (str == NULL) str = *nextp;
str += strspn(str, delim);
if (*str == '\0') return NULL;
ret = str;
str += strcspn(str, delim);
if (*str) *str++ = '\0';
*nextp = str;
return ret;
}
#endif /* !HAVE_STRTOK_R */
/* ============================================================= */
/* UTF8 utilities */
#ifdef _WIN32
/**
* (Experimental) Implementation of mbrtowc for Windows.
* This is required because the other, commonly available implementations
* seem to not work very well, based on user reports. Someone who is
* really, really good at windows programming needs to review this stuff!
*/
size_t lg_mbrtowc(wchar_t *pwc, const char *s, size_t n, mbstate_t *ps)
{
int nb, nb2;
if (NULL == s) return 0;
if (0 == n) return -2;
if (0 == *s) { *pwc = 0; return 0; }
nb = utf8_charlen(s);
if (0 == nb) return 0;
if (0 > nb) return nb;
nb2 = MultiByteToWideChar(CP_UTF8, 0, s, nb, NULL, 0);
nb2 = MultiByteToWideChar(CP_UTF8, 0, s, nb, pwc, nb2);
if (0 == nb2) return (size_t)-1;
return nb;
}
/**
* Emulate rand_r() using rand_s() in a way that is enough for our needs.
* Windows doesn't have rand_r(), and its rand_s() is different: It
* returns an error indication and not the random number like rand_r().
* The value it returns is through its argument.
*
* Note that "#define _CRT_RAND_S" is needed before "#include <stdlib.h>".
*/
int rand_r(unsigned int *s)
{
rand_s(s);
if (*s > INT_MAX) *s -= INT_MAX;
return *s;
}
#endif /* _WIN32 */
static int wctomb_check(char *s, wchar_t wc)
{
int nr;
#ifdef _WIN32
nr = WideCharToMultiByte(CP_UTF8, 0, &wc, 1, NULL, 0, NULL, NULL);
nr = WideCharToMultiByte(CP_UTF8, 0, &wc, 1, s, nr, NULL, NULL);
if (0 == nr) return -1;
#else
mbstate_t mbss;
memset(&mbss, 0, sizeof(mbss));
nr = wcrtomb(s, wc, &mbss);
if (nr < 0) {
prt_error("Fatal Error: unknown character set %s\n", nl_langinfo(CODESET));
lg_lib_failure();
}
#endif /* _WIN32 */
return nr;
}
/**
* Downcase the first letter of the word.
* XXX FIXME This works 'most of the time', but is not technically correct.
* This is because towlower() and towupper() are locale dependent, and also
* because the byte-counts might not match up, e.g. German ß and SS.
* The correct long-term fix is to use ICU or glib g_utf8_strup(), etc.
*/
void downcase_utf8_str(char *to, const char * from, size_t usize, locale_t locale)
{
wchar_t c;
int i, nbl, nbh;
char low[MB_LEN_MAX];
mbstate_t mbs;
/* Make sure it doesn't contain garbage in case of an error */
if (to != from) strcpy(to, from);
memset(&mbs, 0, sizeof(mbs));
nbh = mbrtowc (&c, from, MB_CUR_MAX, &mbs);
if (nbh < 0)
{
prt_error("Error: Invalid UTF-8 string!\n");
return;
}
c = towlower_l(c, locale);
nbl = wctomb_check(low, c);
/* Check for error on an in-place copy */
if ((nbh < nbl) && (to == from))
{
/* I'm to lazy to fix this */
prt_error("Error: can't downcase UTF-8 string!\n");
return;
}
/* Downcase */
for (i=0; i<nbl; i++) { to[i] = low[i]; }
if ((nbh == nbl) && (to == from)) return;
from += nbh;
to += nbl;
lg_strlcpy(to, from, usize-nbl);
}
#if 0
/**
* Upcase the first letter of the word.
* XXX FIXME This works 'most of the time', but is not technically correct.
* This is because towlower() and towupper() are locale dependent, and also
* because the byte-counts might not match up, e.g. German ß and SS.
* The correct long-term fix is to use ICU or glib g_utf8_strup(), etc.
*/
void upcase_utf8_str(char *to, const char * from, size_t usize, locale_t locale)
{
wchar_t c;
int i, nbl, nbh;
char low[MB_LEN_MAX];
mbstate_t mbs;
memset(&mbs, 0, sizeof(mbs));
nbh = mbrtowc (&c, from, MB_CUR_MAX, &mbs);
if (nbh < 0)
{
prt_error("Error: Invalid UTF-8 string!\n");
return;
}
c = towupper_l(c, locale);
nbl = wctomb_check(low, c);
/* Check for error on an in-place copy */
if ((nbh < nbl) && (to == from))
{
/* I'm to lazy to fix this */
prt_error("Error: can't upcase UTF-8 string!\n");
return;
}
/* Upcase */
for (i=0; i<nbl; i++) { to[i] = low[i]; }
if ((nbh == nbl) && (to == from)) return;
from += nbh;
to += nbl;
lg_strlcpy(to, from, usize-nbl);
}
#endif
#ifdef NO_ALIGNED_MALLOC
#if __GNUC__
#warning No aligned alloc found (using malloc() instead).
#endif
#endif /* NO_ALIGNED_MALLOC */
#ifdef HAVE_POSIX_MEMALIGN
void *aligned_alloc(size_t alignment, size_t size)
{
void *ptr;
errno = posix_memalign(&ptr, alignment, size);
return ptr;
}
#endif /* HAVE_POSIX_MEMALIGN */
/* ============================================================= */
/* Memory alloc routines to keep track of how much space is getting
* used during a parse. This code is obsolescent; it is useful once a
* decade when there is some scare about memory usage. Overall, RAM
* usage is excellent; everything is well-managed.
*/
#ifdef TRACK_SPACE_USAGE
typedef struct
{
size_t max_space_used;
size_t space_in_use;
size_t num_xallocs;
size_t num_xfrees;
size_t max_outstanding_xallocs;
size_t max_external_space_used;
size_t external_space_in_use;
size_t num_exallocs;
size_t num_exfrees;
size_t max_outstanding_exallocs;
} space_t;
static TLS space_t space;
static space_t * do_init_memusage(void)
{
space_t *s = &space;
s->max_space_used = 0;
s->space_in_use = 0;
s->num_xallocs = 0;
s->num_xfrees = 0;
s->max_outstanding_xallocs = 0;
s->max_external_space_used = 0;
s->external_space_in_use = 0;
s->num_exallocs = 0;
s->num_exfrees = 0;
s->max_outstanding_exallocs = 0;
return s;
}
void init_memusage(void)
{
static bool mem_inited = false;
if (mem_inited) return;
mem_inited = true;
do_init_memusage();
}
static inline space_t *getspace(void)
{
return &space;
}
/**
* space used but not yet freed during parse
*/
size_t get_space_in_use(void)
{
return getspace()->space_in_use;
}
/**
* maximum space used during the parse
*/
size_t get_max_space_used(void)
{
return getspace()->max_space_used;
}
#else /* TRACK_SPACE_USAGE */
void init_memusage(void) {}
size_t get_space_in_use(void) { return 0; }
size_t get_max_space_used(void) { return 0; }
#endif /* TRACK_SPACE_USAGE */
/**
* alloc some memory, and keep track of the space allocated.
*/
void * xalloc(size_t size)
{
void * p = malloc(size);
#ifdef TRACK_SPACE_USAGE
space_t *s = getspace();
s->space_in_use += size;
if (s->max_space_used < s->space_in_use) s->max_space_used = s->space_in_use;
s->num_xallocs ++;
if (s->max_outstanding_xallocs < (s->num_xallocs - s->num_xfrees))
s->max_outstanding_xallocs = (s->num_xallocs - s->num_xfrees);
#endif /* TRACK_SPACE_USAGE */
if ((p == NULL) && (size != 0))
{
prt_error("Fatal Error: Ran out of space. (int)\n");
abort();
exit(1);
}
return p;
}
#ifdef TRACK_SPACE_USAGE
void xfree(void * p, size_t size)
{
space_t *s = getspace();
s->space_in_use -= size;
s->num_xfrees ++;
free(p);
}
#endif /* TRACK_SPACE_USAGE */
void * exalloc(size_t size)
{
void * p = malloc(size);
#ifdef TRACK_SPACE_USAGE
space_t *s = getspace();
s->external_space_in_use += size;
if (s->max_external_space_used < s->external_space_in_use)
s->max_external_space_used = s->external_space_in_use;
s->num_exallocs ++;
if (s->max_outstanding_exallocs < (s->num_exallocs - s->num_exfrees))
s->max_outstanding_exallocs = (s->num_exallocs - s->num_exfrees);
#endif /* TRACK_SPACE_USAGE */
if ((p == NULL) && (size != 0))
{
prt_error("Fatal Error: Ran out of space. (ext)\n");
abort();
exit(1);
}
return p;
}
#ifdef TRACK_SPACE_USAGE
void exfree(void * p, size_t size)
{
space_t *s = getspace();
s->external_space_in_use -= size;
s->num_exfrees ++;
free(p);
}
#endif /* TRACK_SPACE_USAGE */
/* =========================================================== */
/* Simple, cheap, easy dynamic string. */
dyn_str* dyn_str_new(void)
{
dyn_str *ds = malloc(sizeof(dyn_str));
ds->len = 250;
ds->end = 0;
ds->str = malloc(ds->len);
ds->str[0] = 0x0;
return ds;
}
void dyn_str_delete(dyn_str* ds)
{
free(ds->str);
free(ds);
}
char * dyn_str_take(dyn_str* ds)
{
char * rv = ds->str;
free(ds);
return rv;
}
void dyn_strcat(dyn_str* ds, const char *str)
{
size_t l = strlen(str);
if (ds->end+l+1 >= ds->len)
{
ds->len = 2 * ds->len + l;
ds->str = realloc(ds->str, ds->len);
}
strcpy (ds->str+ds->end, str);
ds->end += l;
}
/// Trim away trailing whitespace.
void dyn_trimback(dyn_str* ds)
{
size_t tail = ds->end;
while (0 < tail && ' ' == ds->str[--tail]) {}
ds->end = ++tail;
ds->str[tail] = 0x0;
}
const char * dyn_str_value(dyn_str* s)
{
return s->str;
}
size_t dyn_strlen(dyn_str* s)
{
return s->end;
}
/* ======================================================== */
/* Locale routines */
#ifdef HAVE_LOCALE_T
/**
* Create a locale object from the given locale string.
* @param locale Locale string, in the native OS format.
* @return Locale object for the given locale
* Note: It has to be freed by freelocale().
*/
locale_t newlocale_LC_CTYPE(const char *locale)
{
locale_t locobj;
#ifdef _WIN32
locobj = _create_locale(LC_CTYPE, locale);
#else
locobj = newlocale(LC_CTYPE_MASK, locale, (locale_t)0);
#endif /* _WIN32 */
return locobj;
}
#endif /* HAVE_LOCALE_T */
/**
* Check that the given locale known by the system.
* In case we don't have locale_t, actually set the locale
* in order to find out if it is fine. This side effect doesn't cause
* harm, as the locale would be set up to that value anyway shortly.
* @param locale Locale string
* @return True if known, false if unknown.
*/
bool try_locale(const char *locale)
{
#ifdef HAVE_LOCALE_T
locale_t ltmp = newlocale_LC_CTYPE(locale);
if ((locale_t)0 == ltmp) return false;
freelocale(ltmp);
#else
lgdebug(D_USER_FILES, "Debug: Setting program's locale \"%s\"", locale);
if (NULL == setlocale(LC_CTYPE, locale))
{
lgdebug(D_USER_FILES, " failed!\n");
return false;
}
lgdebug(D_USER_FILES, ".\n");
#endif /* HAVE_LOCALE_T */
return true;
}
/**
* Ensure that the program's locale has a UTF-8 codeset.
*/
void set_utf8_program_locale(void)
{
#ifndef _WIN32
/* The LG library doesn't use mbrtowc_l(), since it doesn't exist in
* the dynamic glibc (2.22). mbsrtowcs_l() could also be used, but for
* some reason it exists only in the static glibc.
* In order that mbrtowc() will work for any UTF-8 character, UTF-8
* codeset is ensured. */
const char *codeset = nl_langinfo(CODESET);
if (!strstr(codeset, "UTF") && !strstr(codeset, "utf"))
{
const char *locale = setlocale(LC_CTYPE, NULL);
/* Avoid an initial spurious message. */
if ((0 != strcmp(locale, "C")) && (0 != strcmp(locale, "POSIX")))
{
prt_error("Warning: Program locale \"%s\" (codeset %s) was not UTF-8; "
"force-setting to en_US.UTF-8\n", locale, codeset);
}
locale = setlocale(LC_CTYPE, "en_US.UTF-8");
if (NULL == locale)
{
prt_error("Warning: Program locale en_US.UTF-8 could not be set; "
"force-setting to C.UTF-8\n");
locale = setlocale(LC_CTYPE, "C.UTF-8");
if (NULL == locale)
{
prt_error("Warning: Could not set a UTF-8 program locale; "
"program may malfunction\n");
}
}
}
#endif /* !_WIN32 */
}
#ifdef _WIN32
static char *
win32_getlocale (void)
{
char lbuf[10];
char locale[32];
LCID lcid = GetThreadLocale();
if (0 >= GetLocaleInfoA(lcid, LOCALE_SISO639LANGNAME, lbuf, sizeof(lbuf)))
{
prt_error("Error: GetLocaleInfoA LOCALE_SENGLISHLANGUAGENAME LCID=%d: "
"Error %d\n", (int)lcid, (int)GetLastError());
return NULL;
}
strcpy(locale, lbuf);
strcat(locale, "-");
if (0 >= GetLocaleInfoA(lcid, LOCALE_SISO3166CTRYNAME, lbuf, sizeof(lbuf)))
{
prt_error("Error: GetLocaleInfoA LOCALE_SISO3166CTRYNAME LCID=%d: "
"Error %d\n", (int)lcid, (int)GetLastError());
return NULL;
}
strcat(locale, lbuf);
return strdup(locale);
}
#endif /* _WIN32 */
char * get_default_locale(void)
{
const char *lc_vars[] = {"LC_ALL", "LC_CTYPE", "LANG", NULL};
char *ev = NULL;
const char **evname;
char *locale = NULL;
for(evname = lc_vars; NULL != *evname; evname++)
{
ev = getenv(*evname);
if ((NULL != ev) && ('\0' != ev[0])) break;
}
if (NULL != *evname)
{
locale = ev;
lgdebug(D_USER_FILES, "Debug: Environment locale \"%s=%s\"\n", *evname, ev);
#ifdef _WIN32
/* If compiled with MSVC/MinGW, we still support running under Cygwin. */
const char *ostype = getenv("OSTYPE");
if ((NULL != ostype) && (0 == strcmp(ostype, "cygwin")))
{
/* Convert to Windows style locale */
locale = strdupa(locale);
locale[strcspn(locale, "_")] = '-';
locale[strcspn(locale, ".@")] = '\0';
}
#endif /* _WIN32 */
}
else
{
lgdebug(D_USER_FILES, "Debug: Environment locale not set\n");
#ifdef _WIN32
locale = win32_getlocale();
if (NULL == locale)
lgdebug(D_USER_FILES, "Debug: Cannot find user default locale\n");
else
lgdebug(D_USER_FILES, "Debug: User default locale \"%s\"\n", locale);
return locale; /* Already strdup'ed */
#endif /* _WIN32 */
}
return safe_strdup(locale);
}
/**
* A locale-independent version of strtof(). At this time (2023), the
* C Standards Committee does not offer a locale-independent version of
* strtof(). Trying to set LC_NUMERIC to "C", temporarily, while
* reading the dictionary, has proven painful and non-portable. The
* blob of code below, although large and complex, avoids the locale
* issues. C'est la vie.
*
* Convert to float a scaled integer string in the format:
* ([-+])?0*\d{,1}(\.\d*)?
* The string should contain at least one digit.
*
* Digits which are more than 4 positions after the decimal point are
* validated but are otherwise ignored.
*
* This format is also documented in the comment at the dictionary format
* comment at the start of read-dict.c - change it there if this function
* gets changed.
*
* @param s A string starting with an optional '+' or '-', then at most
* 2 decimal digits (leading zeros are not counted), then an optional
* point and optional decimal digits after it.
* @param[out] r If s is valid, this is its conversion to float.
* @return \c true iff \p s is valid.
*/
#define D_SITOF 5
bool strtofC(const char *s, float *r)
{
#define DFP(n) (1.0f * n)
#define FP_BY_POS(p) \
{ \
p*DFP(0), p*DFP(1), p*DFP(2), p*DFP(3), p*DFP(4), \
p*DFP(5), p*DFP(6), p*DFP(7), p*DFP(8), p*DFP(9) \
}
static float fpconv[][10] =
{
FP_BY_POS(10), FP_BY_POS(1), FP_BY_POS(0.1f), FP_BY_POS(0.01f),
FP_BY_POS(0.001f), FP_BY_POS(0.0001f)
};
static const int max_int_digits = 2;
static const int max_frac_digits = 4;
static const char max_str[] = "99.9999";
const char *si = s;
bool minus = false;
if ((*si == '-') || (*si == '+'))
{
if (*si == '-') minus = true;
si++;
}
while (*si == '0') si++; /* Skip leading zeros. */
const char *decpoint = strchr(si, '.');
const size_t len = strlen(si);
if (decpoint == NULL)
{
decpoint = &si[len];
}
else
{
if (strchr(decpoint+1, '.'))
{
lgdebug(+D_SITOF, "\"%s\": Extra decimal point\n", s);
return false;
}
}
int pos = max_int_digits - (int)(decpoint - si);
if (pos < 0)
{
lgdebug(+D_SITOF, "\"%s\" is too big (max %s)\n", s, max_str);
return false;
}
if ((si[0] == '\0') || ((si[0] == '.') && (si[1] == '\0')))
{
if ((si == s) || (si[-1] != '0'))
{
lgdebug(+D_SITOF, "\"%s\": No decimal digits found\n", s);
return false;
}
*r = 0.0f;
//printf("Converted \"%s\" to: 0\n", s);
return true;
}
float total = 0.0f;
do
{
if (*si == '.')
{
si++;
if (*si == '\0') break;
}
unsigned int d = (unsigned int)(*si - '0');
if (d > 9)
{
lgdebug(+D_SITOF, "\"%s\": Invalid digit \"%c\"\n", s, *si);
return false;
}
if ((int)(decpoint - si) >= -max_frac_digits)
total += fpconv[pos][d];
pos++;
}
while (*++si != '\0');
if (minus)
*r = -total;
else
*r = total;
//printf("Converted \"%s\" to: %.4f\n", s, *r);
return true;
}
#undef D_SITOF
/* ============================================================= */
/* Alternatives utilities */
size_t altlen(const char **arr)
{
size_t len = 0;
if (arr)
while (arr[len] != NULL) len++;
return len;
}
/* ============================================================= */
#ifdef __MINGW32__
/*
* Since _USE_MINGW_ANSI_STDIO=1 is used in order to support C99 STDIO
* including the %z formats, MinGW uses its own *printf() functions (and not
* the Windows ones). However, its printf()/fprintf() functions cannot write
* UTF-8 to the console (to files/pipes they write UTF-8 just fine). It
* turned out the problem is that they use the putchar() of Windows, which
* doesn't support writing UTF-8 only when writing to the console! This
* problem is not fixed even in Windows 10 and the latest MinGW in Cygwin
* 2.5.2.
*
* The workaround implemented here is to reimplement the corresponding MinGW
* internal functions, and use fputs() to write the result.
*
* (Reimplementing printf()/fprintf() this way didn't work even with the
* compilation flag -fno-builtin .)
*/
int __mingw_vfprintf (FILE * __restrict__ stream, const char * __restrict__ fmt,
va_list vl)
{
int n = vsnprintf(NULL, 0, fmt, vl);
if (0 > n) return n;
char *buf = malloc(n+1);
n = vsnprintf(buf, n+1, fmt, vl);
if (0 > n)
{
free(buf);
return n;
}
n = fputs(buf, stdout);
free(buf);
return n;
}
int __mingw_vprintf (const char * __restrict__ fmt, va_list vl)
{
return __mingw_vfprintf(stdout, fmt, vl);
}
#endif /* __MINGW32__ */
/* ============================================================= */