Skip to content

Commit 4e62a8a

Browse files
[3.15] gh-86427: Use the console code page for the stdio encoding on Windows (GH-155415) (GH-156602)
In the legacy Windows stdio mode PyConfig.stdio_encoding is now initialized with the console output code page instead of the ANSI code page. Only one encoding can be specified, so sys.stdin.encoding is still wrong if the input code page differs from the output one. (cherry picked from commit 0bb312a) Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
1 parent 7e5774b commit 4e62a8a

3 files changed

Lines changed: 62 additions & 2 deletions

File tree

‎Lib/test/test_cmd_line.py‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1067,6 +1067,41 @@ def test_python_legacy_windows_stdio(self):
10671067
support.skip_on_low_desktop_heap_memory_subprocess(p.returncode)
10681068
self.assertEqual(p.returncode, 0)
10691069

1070+
@unittest.skipUnless(support.MS_WINDOWS, 'Test only applicable on Windows')
1071+
def test_python_legacy_windows_stdio_encoding(self):
1072+
# gh-86427: In the legacy mode the encoding of the standard streams
1073+
# is the encoding of the console.
1074+
import ctypes
1075+
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
1076+
try:
1077+
fin = open('CONIN$')
1078+
except OSError:
1079+
self.skipTest('no console')
1080+
# We cannot use PIPE, because the standard streams should be
1081+
# connected to the console. So we use the exit code.
1082+
code = ("import sys; sys.exit(sys.stdin.encoding != 'cp850' or "
1083+
"sys.stdout.encoding != 'cp850')")
1084+
env = os.environ.copy()
1085+
env['PYTHONLEGACYWINDOWSSTDIO'] = '1'
1086+
env['PYTHONUTF8'] = '0'
1087+
env.pop('PYTHONIOENCODING', None)
1088+
old_cp = kernel32.GetConsoleCP()
1089+
old_output_cp = kernel32.GetConsoleOutputCP()
1090+
with fin, open('CONOUT$', 'w') as fout:
1091+
try:
1092+
if not kernel32.SetConsoleCP(850):
1093+
self.skipTest('cannot set the console input code page')
1094+
if not kernel32.SetConsoleOutputCP(850):
1095+
self.skipTest('cannot set the console output code page')
1096+
proc = subprocess.run([sys.executable, '-c', code], env=env,
1097+
stdin=fin, stdout=fout,
1098+
stderr=subprocess.DEVNULL)
1099+
finally:
1100+
kernel32.SetConsoleCP(old_cp)
1101+
kernel32.SetConsoleOutputCP(old_output_cp)
1102+
support.skip_on_low_desktop_heap_memory_subprocess(proc.returncode)
1103+
self.assertEqual(proc.returncode, 0)
1104+
10701105
@unittest.skipIf("-fsanitize" in sysconfig.get_config_vars().get('PY_CFLAGS', ()),
10711106
"PYTHONMALLOCSTATS doesn't work with ASAN")
10721107
def test_python_malloc_stats(self):
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix the encoding of the standard streams in the legacy Windows stdio mode
2+
(:envvar:`PYTHONLEGACYWINDOWSSTDIO`). It is now the code page of the
3+
console, as in Python 3.7, not the ANSI code page.

‎Python/initconfig.c‎

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2737,8 +2737,30 @@ config_init_stdio_encoding(PyConfig *config,
27372737

27382738
/* Choose the default error handler based on the current locale. */
27392739
if (config->stdio_encoding == NULL) {
2740-
status = config_get_locale_encoding(config, preconfig,
2741-
&config->stdio_encoding);
2740+
#ifdef MS_WINDOWS
2741+
/* gh-86427: use the console code page. Only one encoding can be
2742+
specified, so the output code page is used: it affects two
2743+
streams of three. */
2744+
UINT cp = config->legacy_windows_stdio ? GetConsoleOutputCP() : 0;
2745+
if (cp != 0) {
2746+
if (cp == CP_UTF8) {
2747+
status = PyConfig_SetString(config, &config->stdio_encoding,
2748+
L"utf-8");
2749+
}
2750+
else {
2751+
wchar_t encoding[20];
2752+
swprintf(encoding, Py_ARRAY_LENGTH(encoding), L"cp%u",
2753+
(unsigned int)cp);
2754+
status = PyConfig_SetString(config, &config->stdio_encoding,
2755+
encoding);
2756+
}
2757+
}
2758+
else
2759+
#endif
2760+
{
2761+
status = config_get_locale_encoding(config, preconfig,
2762+
&config->stdio_encoding);
2763+
}
27422764
if (_PyStatus_EXCEPTION(status)) {
27432765
return status;
27442766
}

0 commit comments

Comments
 (0)