# HG changeset patch # Parent 05120447f2c627e7ad0d996a9ea16a2df3382f2e Issue #1602: Windows console doesn't input or print Unicode diff --git a/Include/pydebug.h b/Include/pydebug.h --- a/Include/pydebug.h +++ b/Include/pydebug.h @@ -24,6 +24,10 @@ PyAPI_DATA(int) Py_HashRandomizationFlag; PyAPI_DATA(int) Py_IsolatedFlag; +#ifdef MS_WINDOWS +PyAPI_DATA(int) Py_LegacyWindowsStdioFlag; +#endif + /* this is a wrapper around getenv() that pays attention to Py_IgnoreEnvironmentFlag. It should be used for getting variables like PYTHONPATH and PYTHONHOME from the environment */ diff --git a/Modules/_io/_iomodule.c b/Modules/_io/_iomodule.c --- a/Modules/_io/_iomodule.c +++ b/Modules/_io/_iomodule.c @@ -20,6 +20,9 @@ #include #endif /* HAVE_SYS_STAT_H */ +#ifdef MS_WINDOWS +#include +#endif /* Various interned strings */ @@ -52,7 +55,6 @@ PyObject *_PyIO_empty_bytes; PyObject *_PyIO_zero; - PyDoc_STRVAR(module_doc, "The io module provides the Python interfaces to stream handling. The\n" "builtin open function is defined in this module.\n" @@ -362,8 +364,18 @@ } /* Create the Raw file stream */ - raw = PyObject_CallFunction((PyObject *)&PyFileIO_Type, - "OsiO", path_or_fd, rawmode, closefd, opener); + { + PyObject *RawIO_class = (PyObject *)&PyFileIO_Type; +#ifdef MS_WINDOWS + if (!Py_LegacyWindowsStdioFlag && _PyIO_get_console_type(path_or_fd) != '\0') { + RawIO_class = (PyObject *)&PyWindowsConsoleIO_Type; + encoding = "utf-16-le"; + } +#endif + raw = PyObject_CallFunction(RawIO_class, + "OsiO", path_or_fd, rawmode, closefd, opener); + } + if (raw == NULL) goto error; result = raw; @@ -425,7 +437,7 @@ { PyObject *Buffered_class; - if (updating) + if (updating) Buffered_class = (PyObject *)&PyBufferedRandom_Type; else if (creating || writing || appending) Buffered_class = (PyObject *)&PyBufferedWriter_Type; @@ -591,7 +603,6 @@ return mod; } - static int iomodule_traverse(PyObject *mod, visitproc visit, void *arg) { _PyIO_State *state = IO_MOD_STATE(mod); @@ -708,6 +719,12 @@ PyStringIO_Type.tp_base = &PyTextIOBase_Type; ADD_TYPE(&PyStringIO_Type, "StringIO"); +#ifdef MS_WINDOWS + /* WindowsConsoleIO */ + PyWindowsConsoleIO_Type.tp_base = &PyRawIOBase_Type; + ADD_TYPE(&PyWindowsConsoleIO_Type, "WindowsConsoleIO"); +#endif + /* BufferedReader */ PyBufferedReader_Type.tp_base = &PyBufferedIOBase_Type; ADD_TYPE(&PyBufferedReader_Type, "BufferedReader"); diff --git a/Modules/_io/_iomodule.h b/Modules/_io/_iomodule.h --- a/Modules/_io/_iomodule.h +++ b/Modules/_io/_iomodule.h @@ -19,6 +19,12 @@ extern PyTypeObject PyTextIOWrapper_Type; extern PyTypeObject PyIncrementalNewlineDecoder_Type; +#ifndef Py_LIMITED_API +#ifdef MS_WINDOWS +extern PyTypeObject PyWindowsConsoleIO_Type; +#define PyWindowsConsoleIO_Check(op) (PyObject_TypeCheck((op), &PyWindowsConsoleIO_Type)) +#endif /* MS_WINDOWS */ +#endif /* Py_LIMITED_API */ extern int _PyIO_ConvertSsize_t(PyObject *, void *); @@ -145,6 +151,10 @@ extern _PyIO_State *_PyIO_get_module_state(void); extern PyObject *_PyIO_get_locale_module(_PyIO_State *); +#ifdef MS_WINDOWS +extern char _PyIO_get_console_type(PyObject *); +#endif + extern PyObject *_PyIO_str_close; extern PyObject *_PyIO_str_closed; extern PyObject *_PyIO_str_decode; diff --git a/Modules/_io/clinic/winconsoleio.c.h b/Modules/_io/clinic/winconsoleio.c.h new file mode 100644 --- /dev/null +++ b/Modules/_io/clinic/winconsoleio.c.h @@ -0,0 +1,417 @@ +/*[clinic input] +preserve +[clinic start generated code]*/ + +#if defined(MS_WINDOWS) + +PyDoc_STRVAR(_io_WindowsConsoleIO_close__doc__, +"close($self, /)\n" +"--\n" +"\n" +"Close the handle.\n" +"\n" +"A closed handle cannot be used for further I/O operations. close() may be\n" +"called more than once without error."); + +#define _IO_WINDOWSCONSOLEIO_CLOSE_METHODDEF \ + {"close", (PyCFunction)_io_WindowsConsoleIO_close, METH_NOARGS, _io_WindowsConsoleIO_close__doc__}, + +static PyObject * +_io_WindowsConsoleIO_close_impl(winconsoleio *self); + +static PyObject * +_io_WindowsConsoleIO_close(winconsoleio *self, PyObject *Py_UNUSED(ignored)) +{ + return _io_WindowsConsoleIO_close_impl(self); +} + +#endif /* defined(MS_WINDOWS) */ + +#if defined(MS_WINDOWS) + +PyDoc_STRVAR(_io_WindowsConsoleIO___init____doc__, +"WindowsConsoleIO(file, mode=\'r\', closefd=True, opener=None)\n" +"--\n" +"\n" +"Open a file.\n" +"\n" +"The mode can be \'r\' (default), or \'w\' reading or writing. All other mode\n" +"characters will be ignored. The *opener* parameter is always ignored."); + +static int +_io_WindowsConsoleIO___init___impl(winconsoleio *self, PyObject *nameobj, + const char *mode, int closefd, + PyObject *opener); + +static int +_io_WindowsConsoleIO___init__(PyObject *self, PyObject *args, PyObject *kwargs) +{ + int return_value = -1; + static char *_keywords[] = {"file", "mode", "closefd", "opener", NULL}; + PyObject *nameobj; + const char *mode = "r"; + int closefd = 1; + PyObject *opener = Py_None; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|siO:WindowsConsoleIO", _keywords, + &nameobj, &mode, &closefd, &opener)) { + goto exit; + } + return_value = _io_WindowsConsoleIO___init___impl((winconsoleio *)self, nameobj, mode, closefd, opener); + +exit: + return return_value; +} + +#endif /* defined(MS_WINDOWS) */ + +#if defined(MS_WINDOWS) + +PyDoc_STRVAR(_io_WindowsConsoleIO_fileno__doc__, +"fileno($self, /)\n" +"--\n" +"\n" +"Return the underlying file descriptor (an integer)."); + +#define _IO_WINDOWSCONSOLEIO_FILENO_METHODDEF \ + {"fileno", (PyCFunction)_io_WindowsConsoleIO_fileno, METH_NOARGS, _io_WindowsConsoleIO_fileno__doc__}, + +static PyObject * +_io_WindowsConsoleIO_fileno_impl(winconsoleio *self); + +static PyObject * +_io_WindowsConsoleIO_fileno(winconsoleio *self, PyObject *Py_UNUSED(ignored)) +{ + return _io_WindowsConsoleIO_fileno_impl(self); +} + +#endif /* defined(MS_WINDOWS) */ + +#if defined(MS_WINDOWS) + +PyDoc_STRVAR(_io_WindowsConsoleIO_readable__doc__, +"readable($self, /)\n" +"--\n" +"\n" +"True if console is an input buffer."); + +#define _IO_WINDOWSCONSOLEIO_READABLE_METHODDEF \ + {"readable", (PyCFunction)_io_WindowsConsoleIO_readable, METH_NOARGS, _io_WindowsConsoleIO_readable__doc__}, + +static PyObject * +_io_WindowsConsoleIO_readable_impl(winconsoleio *self); + +static PyObject * +_io_WindowsConsoleIO_readable(winconsoleio *self, PyObject *Py_UNUSED(ignored)) +{ + return _io_WindowsConsoleIO_readable_impl(self); +} + +#endif /* defined(MS_WINDOWS) */ + +#if defined(MS_WINDOWS) + +PyDoc_STRVAR(_io_WindowsConsoleIO_writable__doc__, +"writable($self, /)\n" +"--\n" +"\n" +"True if console is an output buffer."); + +#define _IO_WINDOWSCONSOLEIO_WRITABLE_METHODDEF \ + {"writable", (PyCFunction)_io_WindowsConsoleIO_writable, METH_NOARGS, _io_WindowsConsoleIO_writable__doc__}, + +static PyObject * +_io_WindowsConsoleIO_writable_impl(winconsoleio *self); + +static PyObject * +_io_WindowsConsoleIO_writable(winconsoleio *self, PyObject *Py_UNUSED(ignored)) +{ + return _io_WindowsConsoleIO_writable_impl(self); +} + +#endif /* defined(MS_WINDOWS) */ + +#if defined(MS_WINDOWS) + +PyDoc_STRVAR(_io_WindowsConsoleIO_seekable__doc__, +"seekable($self, /)\n" +"--\n" +"\n" +"This is always false for consoles."); + +#define _IO_WINDOWSCONSOLEIO_SEEKABLE_METHODDEF \ + {"seekable", (PyCFunction)_io_WindowsConsoleIO_seekable, METH_NOARGS, _io_WindowsConsoleIO_seekable__doc__}, + +static PyObject * +_io_WindowsConsoleIO_seekable_impl(winconsoleio *self); + +static PyObject * +_io_WindowsConsoleIO_seekable(winconsoleio *self, PyObject *Py_UNUSED(ignored)) +{ + return _io_WindowsConsoleIO_seekable_impl(self); +} + +#endif /* defined(MS_WINDOWS) */ + +#if defined(MS_WINDOWS) + +PyDoc_STRVAR(_io_WindowsConsoleIO_readinto__doc__, +"readinto($self, buffer, /)\n" +"--\n" +"\n" +"Same as RawIOBase.readinto()."); + +#define _IO_WINDOWSCONSOLEIO_READINTO_METHODDEF \ + {"readinto", (PyCFunction)_io_WindowsConsoleIO_readinto, METH_O, _io_WindowsConsoleIO_readinto__doc__}, + +static PyObject * +_io_WindowsConsoleIO_readinto_impl(winconsoleio *self, Py_buffer *buffer); + +static PyObject * +_io_WindowsConsoleIO_readinto(winconsoleio *self, PyObject *arg) +{ + PyObject *return_value = NULL; + Py_buffer buffer = {NULL, NULL}; + + if (!PyArg_Parse(arg, "w*:readinto", &buffer)) { + goto exit; + } + return_value = _io_WindowsConsoleIO_readinto_impl(self, &buffer); + +exit: + /* Cleanup for buffer */ + if (buffer.obj) { + PyBuffer_Release(&buffer); + } + + return return_value; +} + +#endif /* defined(MS_WINDOWS) */ + +#if defined(MS_WINDOWS) + +PyDoc_STRVAR(_io_WindowsConsoleIO_readall__doc__, +"readall($self, /)\n" +"--\n" +"\n" +"Read all data from the file, returned as bytes.\n" +"\n" +"In non-blocking mode, returns as much as is immediately available,\n" +"or None if no data is available. Return an empty bytes object at EOF."); + +#define _IO_WINDOWSCONSOLEIO_READALL_METHODDEF \ + {"readall", (PyCFunction)_io_WindowsConsoleIO_readall, METH_NOARGS, _io_WindowsConsoleIO_readall__doc__}, + +static PyObject * +_io_WindowsConsoleIO_readall_impl(winconsoleio *self); + +static PyObject * +_io_WindowsConsoleIO_readall(winconsoleio *self, PyObject *Py_UNUSED(ignored)) +{ + return _io_WindowsConsoleIO_readall_impl(self); +} + +#endif /* defined(MS_WINDOWS) */ + +#if defined(MS_WINDOWS) + +PyDoc_STRVAR(_io_WindowsConsoleIO_read__doc__, +"read($self, size=-1, /)\n" +"--\n" +"\n" +"Read at most size bytes, returned as bytes.\n" +"\n" +"Only makes one system call, so less data may be returned than requested.\n" +"In non-blocking mode, returns None if no data is available.\n" +"Return an empty bytes object at EOF."); + +#define _IO_WINDOWSCONSOLEIO_READ_METHODDEF \ + {"read", (PyCFunction)_io_WindowsConsoleIO_read, METH_VARARGS, _io_WindowsConsoleIO_read__doc__}, + +static PyObject * +_io_WindowsConsoleIO_read_impl(winconsoleio *self, Py_ssize_t size); + +static PyObject * +_io_WindowsConsoleIO_read(winconsoleio *self, PyObject *args) +{ + PyObject *return_value = NULL; + Py_ssize_t size = -1; + + if (!PyArg_ParseTuple(args, "|O&:read", + _PyIO_ConvertSsize_t, &size)) { + goto exit; + } + return_value = _io_WindowsConsoleIO_read_impl(self, size); + +exit: + return return_value; +} + +#endif /* defined(MS_WINDOWS) */ + +#if defined(MS_WINDOWS) + +PyDoc_STRVAR(_io_WindowsConsoleIO_write__doc__, +"write($self, b, /)\n" +"--\n" +"\n" +"Write buffer b to file, return number of bytes written.\n" +"\n" +"Only makes one system call, so not all of the data may be written.\n" +"The number of bytes actually written is returned. In non-blocking mode,\n" +"returns None if the write would block."); + +#define _IO_WINDOWSCONSOLEIO_WRITE_METHODDEF \ + {"write", (PyCFunction)_io_WindowsConsoleIO_write, METH_O, _io_WindowsConsoleIO_write__doc__}, + +static PyObject * +_io_WindowsConsoleIO_write_impl(winconsoleio *self, Py_buffer *b); + +static PyObject * +_io_WindowsConsoleIO_write(winconsoleio *self, PyObject *arg) +{ + PyObject *return_value = NULL; + Py_buffer b = {NULL, NULL}; + + if (!PyArg_Parse(arg, "y*:write", &b)) { + goto exit; + } + return_value = _io_WindowsConsoleIO_write_impl(self, &b); + +exit: + /* Cleanup for b */ + if (b.obj) { + PyBuffer_Release(&b); + } + + return return_value; +} + +#endif /* defined(MS_WINDOWS) */ + +#if defined(MS_WINDOWS) + +PyDoc_STRVAR(_io_WindowsConsoleIO_seek__doc__, +"seek($self, pos, whence=0, /)\n" +"--\n" +"\n" +"Raises IOError as console buffers are not seekable."); + +#define _IO_WINDOWSCONSOLEIO_SEEK_METHODDEF \ + {"seek", (PyCFunction)_io_WindowsConsoleIO_seek, METH_VARARGS, _io_WindowsConsoleIO_seek__doc__}, + +static PyObject * +_io_WindowsConsoleIO_seek_impl(winconsoleio *self, PyObject *pos, int whence); + +static PyObject * +_io_WindowsConsoleIO_seek(winconsoleio *self, PyObject *args) +{ + PyObject *return_value = NULL; + PyObject *pos; + int whence = 0; + + if (!PyArg_ParseTuple(args, "O|i:seek", + &pos, &whence)) { + goto exit; + } + return_value = _io_WindowsConsoleIO_seek_impl(self, pos, whence); + +exit: + return return_value; +} + +#endif /* defined(MS_WINDOWS) */ + +#if defined(MS_WINDOWS) + +PyDoc_STRVAR(_io_WindowsConsoleIO_tell__doc__, +"tell($self, /)\n" +"--\n" +"\n" +"Raises OSError as console buffers are not seekable."); + +#define _IO_WINDOWSCONSOLEIO_TELL_METHODDEF \ + {"tell", (PyCFunction)_io_WindowsConsoleIO_tell, METH_NOARGS, _io_WindowsConsoleIO_tell__doc__}, + +static PyObject * +_io_WindowsConsoleIO_tell_impl(winconsoleio *self); + +static PyObject * +_io_WindowsConsoleIO_tell(winconsoleio *self, PyObject *Py_UNUSED(ignored)) +{ + return _io_WindowsConsoleIO_tell_impl(self); +} + +#endif /* defined(MS_WINDOWS) */ + +#if defined(MS_WINDOWS) + +PyDoc_STRVAR(_io_WindowsConsoleIO_isatty__doc__, +"isatty($self, /)\n" +"--\n" +"\n" +"Always True."); + +#define _IO_WINDOWSCONSOLEIO_ISATTY_METHODDEF \ + {"isatty", (PyCFunction)_io_WindowsConsoleIO_isatty, METH_NOARGS, _io_WindowsConsoleIO_isatty__doc__}, + +static PyObject * +_io_WindowsConsoleIO_isatty_impl(winconsoleio *self); + +static PyObject * +_io_WindowsConsoleIO_isatty(winconsoleio *self, PyObject *Py_UNUSED(ignored)) +{ + return _io_WindowsConsoleIO_isatty_impl(self); +} + +#endif /* defined(MS_WINDOWS) */ + +#ifndef _IO_WINDOWSCONSOLEIO_CLOSE_METHODDEF + #define _IO_WINDOWSCONSOLEIO_CLOSE_METHODDEF +#endif /* !defined(_IO_WINDOWSCONSOLEIO_CLOSE_METHODDEF) */ + +#ifndef _IO_WINDOWSCONSOLEIO_FILENO_METHODDEF + #define _IO_WINDOWSCONSOLEIO_FILENO_METHODDEF +#endif /* !defined(_IO_WINDOWSCONSOLEIO_FILENO_METHODDEF) */ + +#ifndef _IO_WINDOWSCONSOLEIO_READABLE_METHODDEF + #define _IO_WINDOWSCONSOLEIO_READABLE_METHODDEF +#endif /* !defined(_IO_WINDOWSCONSOLEIO_READABLE_METHODDEF) */ + +#ifndef _IO_WINDOWSCONSOLEIO_WRITABLE_METHODDEF + #define _IO_WINDOWSCONSOLEIO_WRITABLE_METHODDEF +#endif /* !defined(_IO_WINDOWSCONSOLEIO_WRITABLE_METHODDEF) */ + +#ifndef _IO_WINDOWSCONSOLEIO_SEEKABLE_METHODDEF + #define _IO_WINDOWSCONSOLEIO_SEEKABLE_METHODDEF +#endif /* !defined(_IO_WINDOWSCONSOLEIO_SEEKABLE_METHODDEF) */ + +#ifndef _IO_WINDOWSCONSOLEIO_READINTO_METHODDEF + #define _IO_WINDOWSCONSOLEIO_READINTO_METHODDEF +#endif /* !defined(_IO_WINDOWSCONSOLEIO_READINTO_METHODDEF) */ + +#ifndef _IO_WINDOWSCONSOLEIO_READALL_METHODDEF + #define _IO_WINDOWSCONSOLEIO_READALL_METHODDEF +#endif /* !defined(_IO_WINDOWSCONSOLEIO_READALL_METHODDEF) */ + +#ifndef _IO_WINDOWSCONSOLEIO_READ_METHODDEF + #define _IO_WINDOWSCONSOLEIO_READ_METHODDEF +#endif /* !defined(_IO_WINDOWSCONSOLEIO_READ_METHODDEF) */ + +#ifndef _IO_WINDOWSCONSOLEIO_WRITE_METHODDEF + #define _IO_WINDOWSCONSOLEIO_WRITE_METHODDEF +#endif /* !defined(_IO_WINDOWSCONSOLEIO_WRITE_METHODDEF) */ + +#ifndef _IO_WINDOWSCONSOLEIO_SEEK_METHODDEF + #define _IO_WINDOWSCONSOLEIO_SEEK_METHODDEF +#endif /* !defined(_IO_WINDOWSCONSOLEIO_SEEK_METHODDEF) */ + +#ifndef _IO_WINDOWSCONSOLEIO_TELL_METHODDEF + #define _IO_WINDOWSCONSOLEIO_TELL_METHODDEF +#endif /* !defined(_IO_WINDOWSCONSOLEIO_TELL_METHODDEF) */ + +#ifndef _IO_WINDOWSCONSOLEIO_ISATTY_METHODDEF + #define _IO_WINDOWSCONSOLEIO_ISATTY_METHODDEF +#endif /* !defined(_IO_WINDOWSCONSOLEIO_ISATTY_METHODDEF) */ +/*[clinic end generated code: output=6330ceedd7d2e529 input=a9049054013a1b77]*/ diff --git a/Modules/_io/winconsoleio.c b/Modules/_io/winconsoleio.c new file mode 100644 --- /dev/null +++ b/Modules/_io/winconsoleio.c @@ -0,0 +1,814 @@ +/* + An implementation of Windows console I/O + + Classes defined here: WinConsoleReader, WinConsoleWriter + + Written by Steve Dower +*/ + +#define PY_SSIZE_T_CLEAN +#include "Python.h" + +#ifdef MS_WINDOWS + +#include "structmember.h" +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_SYS_STAT_H +#include +#endif +#include /* For offsetof */ + +#define WIN32_LEAN_AND_MEAN +#include + +#include "_iomodule.h" + +#if BUFSIZ < (8*1024) +#define SMALLCHUNK (8*1024) +#elif (BUFSIZ >= (2 << 25)) +#error "unreasonable BUFSIZ > 64MB defined" +#else +#define SMALLCHUNK BUFSIZ +#endif + +char _get_console_type(HANDLE handle) { + DWORD mode, peek_count; + + if (handle == INVALID_HANDLE_VALUE) + return '\0'; + + if (!GetConsoleMode(handle, &mode)) + return '\0'; + + /* Peek at the handle to see whether it is an input or output handle */ + if (PeekConsoleInputW(handle, NULL, 0, &peek_count)) + return 'r'; + return 'w'; +} + +char _PyIO_get_console_type(PyObject *path_or_fd) { + int fd; + + fd = PyLong_AsLong(path_or_fd); + PyErr_Clear(); + if (fd >= 0) { + return _get_console_type((HANDLE)_get_osfhandle(fd)); + } + + /* TODO: Support opening console using name */ + return '\0'; +} + +/*[clinic input] +module _io +class _io.WindowsConsoleIO "winconsoleio *" "&PyWindowsConsoleIO_Type" +[clinic start generated code]*/ +/*[clinic end generated code: output=da39a3ee5e6b4b0d input=431722d0fd743878]*/ + +/*[python input] +class io_ssize_t_converter(CConverter): + type = 'Py_ssize_t' + converter = '_PyIO_ConvertSsize_t' +[python start generated code]*/ +/*[python end generated code: output=da39a3ee5e6b4b0d input=d0a811d3cbfd1b33]*/ + +typedef struct { + PyObject_HEAD + HANDLE handle; + unsigned int created : 1; + unsigned int readable : 1; + unsigned int writable : 1; + unsigned int closehandle : 1; + char finalizing; + unsigned int blksize; + PyObject *weakreflist; + PyObject *dict; +} winconsoleio; + +PyTypeObject PyWindowsConsoleIO_Type; + +_Py_IDENTIFIER(name); + +int +_PyWindowsConsoleIO_closed(PyObject *self) +{ + return ((winconsoleio *)self)->handle == INVALID_HANDLE_VALUE; +} + + +/* Returns 0 on success, -1 with exception set on failure. */ +static int +internal_close(winconsoleio *self) +{ + if (self->handle != INVALID_HANDLE_VALUE) { + if (self->closehandle) { + CloseHandle(self->handle); + } + self->handle = INVALID_HANDLE_VALUE; + } + return 0; +} + +/*[clinic input] +_io.WindowsConsoleIO.close + +Close the handle. + +A closed handle cannot be used for further I/O operations. close() may be +called more than once without error. +[clinic start generated code]*/ + +static PyObject * +_io_WindowsConsoleIO_close_impl(winconsoleio *self) +/*[clinic end generated code: output=222098bea11857e5 input=5cdeb454f05e5b9c]*/ +{ + PyObject *res; + PyObject *exc, *val, *tb; + int rc; + _Py_IDENTIFIER(close); + res = _PyObject_CallMethodId((PyObject*)&PyRawIOBase_Type, + &PyId_close, "O", self); + if (!self->closehandle) { + self->handle = INVALID_HANDLE_VALUE; + return res; + } + if (res == NULL) + PyErr_Fetch(&exc, &val, &tb); + rc = internal_close(self); + if (res == NULL) + _PyErr_ChainExceptions(exc, val, tb); + if (rc < 0) + Py_CLEAR(res); + return res; +} + +static PyObject * +winconsoleio_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + winconsoleio *self; + + assert(type != NULL && type->tp_alloc != NULL); + + self = (winconsoleio *) type->tp_alloc(type, 0); + if (self != NULL) { + self->handle = INVALID_HANDLE_VALUE; + self->created = 0; + self->readable = 0; + self->writable = 0; + self->closehandle = 0; + self->blksize = 0; + self->weakreflist = NULL; + } + + return (PyObject *) self; +} + +/*[clinic input] +_io.WindowsConsoleIO.__init__ + file as nameobj: object + mode: str = "r" + closefd: int(c_default="1") = True + opener: object = None + +Open a file. + +The mode can be 'r' (default), or 'w' reading or writing. All other mode +characters will be ignored. The *opener* parameter is always ignored. +[clinic start generated code]*/ + +static int +_io_WindowsConsoleIO___init___impl(winconsoleio *self, PyObject *nameobj, + const char *mode, int closefd, + PyObject *opener) +/*[clinic end generated code: output=d75a8c3247929b4e input=d446236e71cc17ee]*/ +{ + const char *name = NULL; + PyObject *stringobj = NULL; + const char *s; + Py_UNICODE *widename = NULL; + int ret = 0; + int rwa = 0; + int fd = -1; + int fd_is_own = 0; + + assert(PyWindowsConsoleIO_Check(self)); + if (self->handle >= 0) { + if (self->closehandle) { + /* Have to close the existing file first. */ + if (internal_close(self) < 0) + return -1; + } + else + self->handle = INVALID_HANDLE_VALUE; + } + + if (PyFloat_Check(nameobj)) { + PyErr_SetString(PyExc_TypeError, + "integer argument expected, got float"); + return -1; + } + + fd = _PyLong_AsInt(nameobj); + if (fd < 0) { + if (!PyErr_Occurred()) { + PyErr_SetString(PyExc_ValueError, + "negative file descriptor"); + return -1; + } + PyErr_Clear(); + } + + if (PyUnicode_Check(nameobj)) { + Py_ssize_t length; + widename = PyUnicode_AsUnicodeAndSize(nameobj, &length); + if (widename == NULL) + return -1; + if (wcslen(widename) != length) { + PyErr_SetString(PyExc_ValueError, "embedded null character"); + return -1; + } + } else if (fd < 0) { + if (!PyUnicode_FSConverter(nameobj, &stringobj)) { + return -1; + } + name = PyBytes_AS_STRING(stringobj); + } + + s = mode; + while (*s) { + switch (*s++) { + case '+': + case 'a': + case 'b': + case 'x': + break; + case 'r': + if (rwa) + goto bad_mode; + rwa = 1; + self->readable = 1; + break; + case 'w': + if (rwa) + goto bad_mode; + rwa = 1; + self->writable = 1; + break; + default: + PyErr_Format(PyExc_ValueError, + "invalid mode: %.200s", mode); + goto error; + } + } + + if (!rwa) + goto bad_mode; + + if (fd >= 0) { + self->handle = (HANDLE)_get_osfhandle(fd); + self->closehandle = 0; + } + else { + DWORD access = GENERIC_READ; + + self->closehandle = 1; + if (!closefd) { + PyErr_SetString(PyExc_ValueError, + "Cannot use closefd=False with file name"); + goto error; + } + + if (self->writable) + access |= GENERIC_WRITE; + + Py_BEGIN_ALLOW_THREADS + if (widename != NULL) + self->handle = CreateFileW(widename, access, + FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); + else + self->handle = CreateFileA(name, access, + FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); + Py_END_ALLOW_THREADS + + if (self->handle == INVALID_HANDLE_VALUE) { + PyErr_SetExcFromWindowsErrWithFilenameObject(PyExc_OSError, GetLastError(), nameobj); + goto error; + } + } + + if (self->writable && _get_console_type(self->handle) != 'w') { + PyErr_SetString(PyExc_ValueError, + "Cannot open console input buffer for writing"); + goto error; + } + if (self->readable && _get_console_type(self->handle) != 'r') { + PyErr_SetString(PyExc_ValueError, + "Cannot open console output buffer for reading"); + goto error; + } + + self->blksize = DEFAULT_BUFFER_SIZE; + + if (_PyObject_SetAttrId((PyObject *)self, &PyId_name, nameobj) < 0) + goto error; + + goto done; + +bad_mode: + PyErr_SetString(PyExc_ValueError, + "Must have exactly one of read or write mode"); +error: + ret = -1; + internal_close(self); + +done: + Py_CLEAR(stringobj); + return ret; +} + +static int +winconsoleio_traverse(winconsoleio *self, visitproc visit, void *arg) +{ + Py_VISIT(self->dict); + return 0; +} + +static int +winconsoleio_clear(winconsoleio *self) +{ + Py_CLEAR(self->dict); + return 0; +} + +static void +winconsoleio_dealloc(winconsoleio *self) +{ + self->finalizing = 1; + if (_PyIOBase_finalize((PyObject *) self) < 0) + return; + _PyObject_GC_UNTRACK(self); + if (self->weakreflist != NULL) + PyObject_ClearWeakRefs((PyObject *) self); + Py_CLEAR(self->dict); + Py_TYPE(self)->tp_free((PyObject *)self); +} + +static PyObject * +err_closed(void) +{ + PyErr_SetString(PyExc_ValueError, "I/O operation on closed file"); + return NULL; +} + +static PyObject * +err_mode(const char *action) +{ + _PyIO_State *state = IO_STATE(); + if (state != NULL) + PyErr_Format(state->unsupported_operation, + "Console buffer does not support %s", action); + return NULL; +} + +/*[clinic input] +_io.WindowsConsoleIO.fileno + +Return the underlying file descriptor (an integer). +[clinic start generated code]*/ + +static PyObject * +_io_WindowsConsoleIO_fileno_impl(winconsoleio *self) +/*[clinic end generated code: output=8ce2675b8b2e70aa input=9d4498c1de6f6a18]*/ +{ + err_mode("fileno"); + return NULL; +} + +/*[clinic input] +_io.WindowsConsoleIO.readable + +True if console is an input buffer. +[clinic start generated code]*/ + +static PyObject * +_io_WindowsConsoleIO_readable_impl(winconsoleio *self) +/*[clinic end generated code: output=8cc7d865bea4671b input=9cffdf2ab99ca138]*/ +{ + if (self->handle == INVALID_HANDLE_VALUE) + return err_closed(); + return PyBool_FromLong((long) self->readable); +} + +/*[clinic input] +_io.WindowsConsoleIO.writable + +True if console is an output buffer. +[clinic start generated code]*/ + +static PyObject * +_io_WindowsConsoleIO_writable_impl(winconsoleio *self) +/*[clinic end generated code: output=480ef155e305ed74 input=940422ab3d91c8d2]*/ +{ + if (self->handle == INVALID_HANDLE_VALUE) + return err_closed(); + return PyBool_FromLong((long) self->writable); +} + +/*[clinic input] +_io.WindowsConsoleIO.seekable + +This is always false for consoles. +[clinic start generated code]*/ + +static PyObject * +_io_WindowsConsoleIO_seekable_impl(winconsoleio *self) +/*[clinic end generated code: output=3299ed704b401a1c input=8a8bb4a7727e9b67]*/ +{ + Py_RETURN_FALSE; +} + +/*[clinic input] +_io.WindowsConsoleIO.readinto + buffer: Py_buffer(accept={rwbuffer}) + / + +Same as RawIOBase.readinto(). +[clinic start generated code]*/ + +static PyObject * +_io_WindowsConsoleIO_readinto_impl(winconsoleio *self, Py_buffer *buffer) +/*[clinic end generated code: output=ccf1ee4b17df834d input=8c5b9abb7c2dba64]*/ +{ + DWORD n; + + if (self->handle == INVALID_HANDLE_VALUE) + return err_closed(); + if (!self->readable) + return err_mode("reading"); + + if (!ReadConsoleW(self->handle, buffer->buf, buffer->len / sizeof(wchar_t), &n, NULL)) { + /* copy error because PyBuffer_Release() can indirectly modify it */ + return PyErr_SetFromWindowsErr(0); + } + + /* TODO: Retry if not enough read? */ + + return PyLong_FromSsize_t(n * sizeof(wchar_t)); +} + +static size_t +new_buffersize(winconsoleio *self, size_t currentsize) +{ + size_t addend; + + /* Expand the buffer by an amount proportional to the current size, + giving us amortized linear-time behavior. For bigger sizes, use a + less-than-double growth factor to avoid excessive allocation. */ + assert(currentsize <= PY_SSIZE_T_MAX); + if (currentsize > 65536) + addend = currentsize >> 3; + else + addend = 256 + currentsize; + if (addend < SMALLCHUNK) + /* Avoid tiny read() calls. */ + addend = SMALLCHUNK; + return addend + currentsize; +} + +/*[clinic input] +_io.WindowsConsoleIO.readall + +Read all data from the file, returned as bytes. + +In non-blocking mode, returns as much as is immediately available, +or None if no data is available. Return an empty bytes object at EOF. +[clinic start generated code]*/ + +static PyObject * +_io_WindowsConsoleIO_readall_impl(winconsoleio *self) +/*[clinic end generated code: output=524a2ffc0b46dc4e input=538b16912b563010]*/ +{ + PyObject *result; + Py_ssize_t bytes_read = 0; + DWORD n; + size_t bufsize; + + if (self->handle == INVALID_HANDLE_VALUE) + return err_closed(); + + bufsize = SMALLCHUNK; + + result = PyBytes_FromStringAndSize(NULL, bufsize); + if (result == NULL) + return NULL; + + while (1) { + if (bytes_read >= (Py_ssize_t)bufsize) { + bufsize = new_buffersize(self, bytes_read); + if (bufsize > PY_SSIZE_T_MAX || bufsize <= 0) { + PyErr_SetString(PyExc_OverflowError, + "unbounded read returned more bytes " + "than a Python bytes object can hold"); + Py_DECREF(result); + return NULL; + } + + if (PyBytes_GET_SIZE(result) < (Py_ssize_t)bufsize) { + if (_PyBytes_Resize(&result, bufsize) < 0) + return NULL; + } + } + + if (!ReadConsoleW(self->handle, PyBytes_AS_STRING(result) + bytes_read, + (bufsize - bytes_read) / sizeof(wchar_t), &n, NULL)) { + int err = GetLastError(); + Py_DECREF(result); + return PyErr_SetFromWindowsErr(err); + } + + if (n == 0) + break; + bytes_read += n * sizeof(wchar_t); + } + + if (PyBytes_GET_SIZE(result) > bytes_read) { + if (_PyBytes_Resize(&result, bytes_read) < 0) + return NULL; + } + return result; +} + +/*[clinic input] +_io.WindowsConsoleIO.read + size: io_ssize_t = -1 + / + +Read at most size bytes, returned as bytes. + +Only makes one system call, so less data may be returned than requested. +In non-blocking mode, returns None if no data is available. +Return an empty bytes object at EOF. +[clinic start generated code]*/ + +static PyObject * +_io_WindowsConsoleIO_read_impl(winconsoleio *self, Py_ssize_t size) +/*[clinic end generated code: output=8300f301cef9e00c input=599776f55233f916]*/ +{ + DWORD n; + PyObject *bytes; + + if (self->handle == INVALID_HANDLE_VALUE) + return err_closed(); + if (!self->readable) + return err_mode("reading"); + + if (size < 0) + return _io_WindowsConsoleIO_readall_impl(self); + if (size > ULONG_MAX) + size = ULONG_MAX; + + bytes = PyBytes_FromStringAndSize(NULL, size); + if (bytes == NULL) + return NULL; + if (!ReadConsoleW(self->handle, PyBytes_AS_STRING(bytes), + size / sizeof(wchar_t), &n, NULL)) { + int err = GetLastError(); + Py_DECREF(bytes); + return PyErr_SetFromWindowsErr(err); + } + + if (n * sizeof(wchar_t) != size) { + if (_PyBytes_Resize(&bytes, n * sizeof(wchar_t)) < 0) { + Py_CLEAR(bytes); + return NULL; + } + } + + return (PyObject *) bytes; +} + +/*[clinic input] +_io.WindowsConsoleIO.write + b: Py_buffer + / + +Write buffer b to file, return number of bytes written. + +Only makes one system call, so not all of the data may be written. +The number of bytes actually written is returned. In non-blocking mode, +returns None if the write would block. +[clinic start generated code]*/ + +static PyObject * +_io_WindowsConsoleIO_write_impl(winconsoleio *self, Py_buffer *b) +/*[clinic end generated code: output=931b5512f7615d6f input=47508023d8e45472]*/ +{ + DWORD n; + + if (self->handle == INVALID_HANDLE_VALUE) + return err_closed(); + if (!self->writable) + return err_mode("writing"); + + if (!WriteConsoleW(self->handle, b->buf, b->len / sizeof(wchar_t), + &n, NULL)) { + return PyErr_SetFromWindowsErr(0); + } + + return PyLong_FromSsize_t(n * sizeof(wchar_t)); +} + +/*[clinic input] +_io.WindowsConsoleIO.seek + pos: object + whence: int = 0 + / + +Raises IOError as console buffers are not seekable. +[clinic start generated code]*/ + +static PyObject * +_io_WindowsConsoleIO_seek_impl(winconsoleio *self, PyObject *pos, int whence) +/*[clinic end generated code: output=c20f4a858474be69 input=6718d8c6a563ca23]*/ +{ + if (self->handle == INVALID_HANDLE_VALUE) + return err_closed(); + + return err_mode("seeking"); +} + +/*[clinic input] +_io.WindowsConsoleIO.tell + +Raises OSError as console buffers are not seekable. +[clinic start generated code]*/ + +static PyObject * +_io_WindowsConsoleIO_tell_impl(winconsoleio *self) +/*[clinic end generated code: output=e45fd45edf85bacf input=3921089ae32406b6]*/ +{ + if (self->handle == INVALID_HANDLE_VALUE) + return err_closed(); + + return err_mode("seeking"); +} + +static const char * +mode_string(winconsoleio *self) +{ + if (self->readable) + return "rb"; + else if (self->writable) + return "wb"; + return NULL; +} + +static PyObject * +winconsoleio_repr(winconsoleio *self) +{ + if (self->handle == INVALID_HANDLE_VALUE) + return PyUnicode_FromFormat("<_io.WindowsConsoleIO [closed]>"); + + if (self->readable) + return PyUnicode_FromFormat("<_io.WindowsConsoleIO mode='rb' closefd=%s>", + self->closehandle ? "True" : "False"); + if (self->writable) + return PyUnicode_FromFormat("<_io.WindowsConsoleIO mode='wb' closefd=%s>", + self->closehandle ? "True" : "False"); + + PyErr_SetString(PyExc_SystemError, "WindowsConsoleIO has invalid mode"); + return NULL; +} + +/*[clinic input] +_io.WindowsConsoleIO.isatty + +Always True. +[clinic start generated code]*/ + +static PyObject * +_io_WindowsConsoleIO_isatty_impl(winconsoleio *self) +/*[clinic end generated code: output=ee9b776418c2b0dd input=97521b5fd13237ca]*/ +{ + if (self->handle == INVALID_HANDLE_VALUE) + return err_closed(); + + Py_RETURN_TRUE; +} + +static PyObject * +winconsoleio_getstate(winconsoleio *self) +{ + PyErr_Format(PyExc_TypeError, + "cannot serialize '%s' object", Py_TYPE(self)->tp_name); + return NULL; +} + +#include "clinic/winconsoleio.c.h" + +static PyMethodDef winconsoleio_methods[] = { + _IO_WINDOWSCONSOLEIO_READ_METHODDEF + _IO_WINDOWSCONSOLEIO_READALL_METHODDEF + _IO_WINDOWSCONSOLEIO_READINTO_METHODDEF + _IO_WINDOWSCONSOLEIO_WRITE_METHODDEF + _IO_WINDOWSCONSOLEIO_SEEK_METHODDEF + _IO_WINDOWSCONSOLEIO_TELL_METHODDEF + _IO_WINDOWSCONSOLEIO_CLOSE_METHODDEF + _IO_WINDOWSCONSOLEIO_SEEKABLE_METHODDEF + _IO_WINDOWSCONSOLEIO_READABLE_METHODDEF + _IO_WINDOWSCONSOLEIO_WRITABLE_METHODDEF + _IO_WINDOWSCONSOLEIO_FILENO_METHODDEF + _IO_WINDOWSCONSOLEIO_ISATTY_METHODDEF + {"__getstate__", (PyCFunction)winconsoleio_getstate, METH_NOARGS, NULL}, + {NULL, NULL} /* sentinel */ +}; + +/* 'closed' and 'mode' are attributes for backwards compatibility reasons. */ + +static PyObject * +get_closed(winconsoleio *self, void *closure) +{ + return PyBool_FromLong((long)(self->handle == INVALID_HANDLE_VALUE)); +} + +static PyObject * +get_closefd(winconsoleio *self, void *closure) +{ + return PyBool_FromLong((long)(self->closehandle)); +} + +static PyObject * +get_mode(winconsoleio *self, void *closure) +{ + return PyUnicode_FromString(self->readable ? "rb" : "wb"); +} + +static PyGetSetDef winconsoleio_getsetlist[] = { + {"closed", (getter)get_closed, NULL, "True if the file is closed"}, + {"closefd", (getter)get_closefd, NULL, + "True if the file descriptor will be closed by close()."}, + {"mode", (getter)get_mode, NULL, "String giving the file mode"}, + {NULL}, +}; + +static PyMemberDef winconsoleio_members[] = { + {"_blksize", T_UINT, offsetof(winconsoleio, blksize), 0}, + {"_finalizing", T_BOOL, offsetof(winconsoleio, finalizing), 0}, + {NULL} +}; + +PyTypeObject PyWindowsConsoleIO_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "_io.WindowsConsoleIO", + sizeof(winconsoleio), + 0, + (destructor)winconsoleio_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_reserved */ + (reprfunc)winconsoleio_repr, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE + | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_HAVE_FINALIZE, /* tp_flags */ + _io_WindowsConsoleIO___init____doc__, /* tp_doc */ + (traverseproc)winconsoleio_traverse, /* tp_traverse */ + (inquiry)winconsoleio_clear, /* tp_clear */ + 0, /* tp_richcompare */ + offsetof(winconsoleio, weakreflist), /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + winconsoleio_methods, /* tp_methods */ + winconsoleio_members, /* tp_members */ + winconsoleio_getsetlist, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + offsetof(winconsoleio, dict), /* tp_dictoffset */ + _io_WindowsConsoleIO___init__, /* tp_init */ + PyType_GenericAlloc, /* tp_alloc */ + winconsoleio_new, /* tp_new */ + PyObject_GC_Del, /* tp_free */ + 0, /* tp_is_gc */ + 0, /* tp_bases */ + 0, /* tp_mro */ + 0, /* tp_cache */ + 0, /* tp_subclasses */ + 0, /* tp_weaklist */ + 0, /* tp_del */ + 0, /* tp_version_tag */ + 0, /* tp_finalize */ +}; + +#endif /* MS_WINDOWS */ diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -266,6 +266,7 @@ + diff --git a/PCbuild/pythoncore.vcxproj.filters b/PCbuild/pythoncore.vcxproj.filters --- a/PCbuild/pythoncore.vcxproj.filters +++ b/PCbuild/pythoncore.vcxproj.filters @@ -593,6 +593,9 @@ Modules\_io + + Modules\_io + Modules\_io diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -31,6 +31,9 @@ #ifdef MS_WINDOWS #undef BYTE #include "windows.h" + +extern PyTypeObject PyWindowsConsoleIO_Type; +#define PyWindowsConsoleIO_Check(op) (PyObject_TypeCheck((op), &PyWindowsConsoleIO_Type)) #endif _Py_IDENTIFIER(flush); @@ -90,6 +93,9 @@ int Py_UnbufferedStdioFlag = 0; /* Unbuffered binary std{in,out,err} */ int Py_HashRandomizationFlag = 0; /* for -R and PYTHONHASHSEED */ int Py_IsolatedFlag = 0; /* for -I, isolate from user's env */ +#ifdef MS_WINDOWS +int Py_LegacyWindowsStdioFlag = 0; /* Uses FileIO instead of WindowsConsoleIO */ +#endif PyThreadState *_Py_Finalizing = NULL; @@ -321,6 +327,10 @@ check its value further. */ if ((p = Py_GETENV("PYTHONHASHSEED")) && *p != '\0') Py_HashRandomizationFlag = add_flag(Py_HashRandomizationFlag, p); +#ifdef MS_WINDOWS + if ((p = Py_GETENV("PYTHONLEGACYWINDOWSSTDIO")) && *p != '\0') + Py_LegacyWindowsStdioFlag = add_flag(Py_LegacyWindowsStdioFlag, p); +#endif _PyRandom_Init(); @@ -1033,6 +1043,7 @@ _Py_IDENTIFIER(isatty); _Py_IDENTIFIER(TextIOWrapper); _Py_IDENTIFIER(mode); + _Py_IDENTIFIER(raw); if (!is_valid_fd(fd)) Py_RETURN_NONE; @@ -1058,7 +1069,6 @@ goto error; if (buffering) { - _Py_IDENTIFIER(raw); raw = _PyObject_GetAttrId(buf, &PyId_raw); if (raw == NULL) goto error; @@ -1068,6 +1078,12 @@ Py_INCREF(raw); } +#ifdef MS_WINDOWS + /* Windows console IO is always UTF-16-LE encoded */ + if (PyWindowsConsoleIO_Check(raw)) + encoding = "utf-16-le"; +#endif + text = PyUnicode_FromString(name); if (text == NULL || _PyObject_SetAttrId(raw, &PyId_name, text) < 0) goto error;