Use WriteConsoleW() in priv_fwrite() too (#3607)
jq dumps JSON to the Windows console as UTF-16 with WriteConsoleW(), but priv_fwrite() still writes the raw UTF-8 bytes with WriteFile(), which the console decodes with its output code page. So `jq .k` prints non-ASCII correctly while `jq -r .k` prints mojibake, and the same goes for -j, --raw-output0, --seq, halt_error and the `stderr` builtin. The tty flag is computed once, from stdout, and passed to writes aimed at stderr too, so ask the handle instead: with stdout on a console and stderr redirected, WriteConsoleW() failed on the file handle and the output was dropped. One byte-level change: with stdout on a console and stderr redirected, halt_error and the `stderr` builtin now go through the CRT, so bare LF becomes CRLF and an existing CRLF becomes CR CRLF. Use -b for byte-exact output on that stream.
This commit is contained in:
1 parent
7422e8c914
commit
dd409de28d
2 files changed
+39
-40
No files matched your search
+1
-24
@@ -3,12 +3,6 @@
|
|||||||
#include <float.h>
|
#include <float.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#ifdef WIN32
|
|
||||||
#include <windows.h>
|
|
||||||
#include <io.h>
|
|
||||||
#include <fileapi.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "jv.h"
|
#include "jv.h"
|
||||||
#include "jv_dtoa.h"
|
#include "jv_dtoa.h"
|
||||||
#include "jv_dtoa_tsd.h"
|
#include "jv_dtoa_tsd.h"
|
||||||
@@ -99,26 +93,9 @@ static void put_buf(const char *s, int len, FILE *fout, jv *strout, int is_tty)
|
|||||||
if (strout) {
|
if (strout) {
|
||||||
*strout = jv_string_append_buf(*strout, s, len);
|
*strout = jv_string_append_buf(*strout, s, len);
|
||||||
} else {
|
} else {
|
||||||
#ifdef WIN32
|
|
||||||
/* See util.h */
|
|
||||||
if (is_tty) {
|
|
||||||
wchar_t *ws;
|
|
||||||
size_t wl;
|
|
||||||
if (len == -1)
|
if (len == -1)
|
||||||
len = strlen(s);
|
len = strlen(s);
|
||||||
wl = MultiByteToWideChar(CP_UTF8, 0, s, len, NULL, 0);
|
priv_fwrite(s, len, fout, is_tty); /* see util.h */
|
||||||
ws = jv_mem_calloc(wl + 1, sizeof(*ws));
|
|
||||||
if (!ws)
|
|
||||||
return;
|
|
||||||
wl = MultiByteToWideChar(CP_UTF8, 0, s, len, ws, wl + 1);
|
|
||||||
ws[wl] = 0;
|
|
||||||
WriteConsoleW((HANDLE)_get_osfhandle(fileno(fout)), ws, wl, NULL, NULL);
|
|
||||||
free(ws);
|
|
||||||
} else
|
|
||||||
fwrite(s, 1, len, fout);
|
|
||||||
#else
|
|
||||||
fwrite(s, 1, len, fout);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+38
-16
@@ -2,11 +2,13 @@
|
|||||||
#define UTIL_H
|
#define UTIL_H
|
||||||
|
|
||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
/* For WriteFile() below */
|
/* For WriteConsoleW() below */
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <io.h>
|
#include <io.h>
|
||||||
|
#include <limits.h>
|
||||||
#include <processenv.h>
|
#include <processenv.h>
|
||||||
#include <shellapi.h>
|
#include <shellapi.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include <wchar.h>
|
#include <wchar.h>
|
||||||
#include <wtypes.h>
|
#include <wtypes.h>
|
||||||
#endif
|
#endif
|
||||||
@@ -18,27 +20,47 @@ jv get_home(void);
|
|||||||
jv jq_realpath(jv);
|
jv jq_realpath(jv);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The Windows CRT and console are something else. In order for the
|
* The Windows console does not do UTF-8. Bytes written to it, with
|
||||||
* console to get UTF-8 written to it correctly we have to bypass stdio
|
* fwrite() or with WriteFile() alike, are decoded with the console's
|
||||||
* completely. No amount of fflush()ing helps. If the first byte of a
|
* output code page, so anything but ASCII comes out as mojibake unless
|
||||||
* buffer being written with fwrite() is non-ASCII UTF-8 then the
|
* the user happened to run `chcp 65001` first. The wide character
|
||||||
* console misinterprets the byte sequence. But one must not
|
* console API is code page independent, so convert to UTF-16 and use
|
||||||
* WriteFile() if stdout is a file!1!!
|
* WriteConsoleW() instead. We must not call SetConsoleOutputCP(): the
|
||||||
|
* code page belongs to the console rather than to us, so changing it
|
||||||
|
* also changes the shell we were started from (see #1121 and #1184).
|
||||||
*
|
*
|
||||||
* We carry knowledge of whether the FILE * is a tty everywhere we
|
* Only a console needs this; a file or a pipe wants the UTF-8 bytes.
|
||||||
* output to it just so we can write with WriteFile() if stdout is a
|
* The is_tty flag alone cannot tell the two apart, as it is computed
|
||||||
* console on WIN32.
|
* once, from stdout, and passed along even for writes to stderr, so
|
||||||
|
* ask the handle we are about to write to.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static void priv_fwrite(const char *s, size_t len, FILE *fout, int is_tty) {
|
static void priv_fwrite(const char *s, size_t len, FILE *fout, int is_tty) {
|
||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
if (is_tty)
|
if (is_tty && len > 0 && len <= INT_MAX / sizeof(wchar_t)) {
|
||||||
WriteFile((HANDLE)_get_osfhandle(fileno(fout)), s, len, NULL, NULL);
|
HANDLE h = (HANDLE)_get_osfhandle(fileno(fout));
|
||||||
else
|
DWORD mode;
|
||||||
fwrite(s, 1, len, fout);
|
if (GetConsoleMode(h, &mode)) {
|
||||||
#else
|
int wlen = MultiByteToWideChar(CP_UTF8, 0, s, (int)len, NULL, 0);
|
||||||
fwrite(s, 1, len, fout);
|
wchar_t *ws = wlen > 0 ? malloc(wlen * sizeof(*ws)) : NULL;
|
||||||
|
if (ws != NULL) {
|
||||||
|
wlen = MultiByteToWideChar(CP_UTF8, 0, s, (int)len, ws, wlen);
|
||||||
|
fflush(fout); /* keep our order with stdio writes to this stream */
|
||||||
|
for (int done = 0; done < wlen;) {
|
||||||
|
DWORD written;
|
||||||
|
/* WriteConsoleW() may write fewer characters than asked for. */
|
||||||
|
if (!WriteConsoleW(h, ws + done, wlen - done, &written, NULL) ||
|
||||||
|
written == 0)
|
||||||
|
break;
|
||||||
|
done += written;
|
||||||
|
}
|
||||||
|
free(ws);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
fwrite(s, 1, len, fout);
|
||||||
}
|
}
|
||||||
|
|
||||||
const void *_jq_memmem(const void *haystack, size_t haystacklen,
|
const void *_jq_memmem(const void *haystack, size_t haystacklen,
|
||||||
|
|||||||
Reference in new issue
Block a user