12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241 |
- /*
- An implementation of Windows console I/O
- Classes defined here: _WindowsConsoleIO
- Written by Steve Dower
- */
- #define PY_SSIZE_T_CLEAN
- #include "Python.h"
- #include "pycore_fileutils.h" // _Py_BEGIN_SUPPRESS_IPH
- #include "pycore_object.h" // _PyObject_GC_UNTRACK()
- #ifdef HAVE_WINDOWS_CONSOLE_IO
- #include "structmember.h" // PyMemberDef
- #ifdef HAVE_SYS_TYPES_H
- #include <sys/types.h>
- #endif
- #ifdef HAVE_SYS_STAT_H
- #include <sys/stat.h>
- #endif
- #include <stddef.h> /* For offsetof */
- #ifndef WIN32_LEAN_AND_MEAN
- #define WIN32_LEAN_AND_MEAN
- #endif
- #include <windows.h>
- #include <fcntl.h>
- #include "_iomodule.h"
- /* BUFSIZ determines how many characters can be typed at the console
- before it starts blocking. */
- #if BUFSIZ < (16*1024)
- #define SMALLCHUNK (2*1024)
- #elif (BUFSIZ >= (2 << 25))
- #error "unreasonable BUFSIZ > 64 MiB defined"
- #else
- #define SMALLCHUNK BUFSIZ
- #endif
- /* BUFMAX determines how many bytes can be read in one go. */
- #define BUFMAX (32*1024*1024)
- /* SMALLBUF determines how many utf-8 characters will be
- buffered within the stream, in order to support reads
- of less than one character */
- #define SMALLBUF 4
- 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 (GetNumberOfConsoleInputEvents(handle, &peek_count))
- return 'r';
- return 'w';
- }
- char _PyIO_get_console_type(PyObject *path_or_fd) {
- int fd = PyLong_AsLong(path_or_fd);
- PyErr_Clear();
- if (fd >= 0) {
- HANDLE handle = _Py_get_osfhandle_noraise(fd);
- if (handle == INVALID_HANDLE_VALUE)
- return '\0';
- return _get_console_type(handle);
- }
- PyObject *decoded;
- wchar_t *decoded_wstr;
- if (!PyUnicode_FSDecoder(path_or_fd, &decoded)) {
- PyErr_Clear();
- return '\0';
- }
- decoded_wstr = PyUnicode_AsWideCharString(decoded, NULL);
- Py_CLEAR(decoded);
- if (!decoded_wstr) {
- PyErr_Clear();
- return '\0';
- }
- char m = '\0';
- if (!_wcsicmp(decoded_wstr, L"CONIN$")) {
- m = 'r';
- } else if (!_wcsicmp(decoded_wstr, L"CONOUT$")) {
- m = 'w';
- } else if (!_wcsicmp(decoded_wstr, L"CON")) {
- m = 'x';
- }
- if (m) {
- PyMem_Free(decoded_wstr);
- return m;
- }
- DWORD length;
- wchar_t name_buf[MAX_PATH], *pname_buf = name_buf;
- length = GetFullPathNameW(decoded_wstr, MAX_PATH, pname_buf, NULL);
- if (length > MAX_PATH) {
- pname_buf = PyMem_New(wchar_t, length);
- if (pname_buf)
- length = GetFullPathNameW(decoded_wstr, length, pname_buf, NULL);
- else
- length = 0;
- }
- PyMem_Free(decoded_wstr);
- if (length) {
- wchar_t *name = pname_buf;
- if (length >= 4 && name[3] == L'\\' &&
- (name[2] == L'.' || name[2] == L'?') &&
- name[1] == L'\\' && name[0] == L'\\') {
- name += 4;
- }
- if (!_wcsicmp(name, L"CONIN$")) {
- m = 'r';
- } else if (!_wcsicmp(name, L"CONOUT$")) {
- m = 'w';
- } else if (!_wcsicmp(name, L"CON")) {
- m = 'x';
- }
- }
- if (pname_buf != name_buf)
- PyMem_Free(pname_buf);
- return m;
- }
- static DWORD
- _find_last_utf8_boundary(const unsigned char *buf, DWORD len)
- {
- for (DWORD count = 1; count < 4 && count <= len; count++) {
- unsigned char c = buf[len - count];
- if (c < 0x80) {
- /* No starting byte found. */
- return len;
- }
- if (c >= 0xc0) {
- if (c < 0xe0 /* 2-bytes sequence */ ? count < 2 :
- c < 0xf0 /* 3-bytes sequence */ ? count < 3 :
- c < 0xf8 /* 4-bytes sequence */)
- {
- /* Incomplete multibyte sequence. */
- return len - count;
- }
- /* Either complete or invalid sequence. */
- return len;
- }
- }
- /* Either complete 4-bytes sequence or invalid sequence. */
- return len;
- }
- /* Find the number of UTF-8 bytes that corresponds to the specified number of
- * wchars.
- * I.e. find x <= len so that MultiByteToWideChar(CP_UTF8, 0, s, x, NULL, 0) == n.
- *
- * WideCharToMultiByte() cannot be used for this, because the UTF-8 -> wchar
- * conversion is not reversible (invalid UTF-8 byte produces \ufffd which
- * will be converted back to 3-bytes UTF-8 sequence \xef\xbf\xbd).
- * So we need to use binary search.
- */
- static DWORD
- _wchar_to_utf8_count(const unsigned char *s, DWORD len, DWORD n)
- {
- DWORD start = 0;
- while (1) {
- DWORD mid = 0;
- for (DWORD i = len / 2; i <= len; i++) {
- mid = _find_last_utf8_boundary(s, i);
- if (mid != 0) {
- break;
- }
- /* The middle could split the first multibytes sequence. */
- }
- if (mid == len) {
- return start + len;
- }
- if (mid == 0) {
- mid = len > 1 ? len - 1 : 1;
- }
- DWORD wlen = MultiByteToWideChar(CP_UTF8, 0, s, mid, NULL, 0);
- if (wlen <= n) {
- s += mid;
- start += mid;
- len -= mid;
- n -= wlen;
- }
- else {
- len = mid;
- }
- }
- }
- /*[clinic input]
- module _io
- class _io._WindowsConsoleIO "winconsoleio *" "clinic_state()->PyWindowsConsoleIO_Type"
- [clinic start generated code]*/
- /*[clinic end generated code: output=da39a3ee5e6b4b0d input=05526e723011ab36]*/
- typedef struct {
- PyObject_HEAD
- int fd;
- unsigned int created : 1;
- unsigned int readable : 1;
- unsigned int writable : 1;
- unsigned int closefd : 1;
- char finalizing;
- unsigned int blksize;
- PyObject *weakreflist;
- PyObject *dict;
- char buf[SMALLBUF];
- wchar_t wbuf;
- } winconsoleio;
- int
- _PyWindowsConsoleIO_closed(PyObject *self)
- {
- return ((winconsoleio *)self)->fd == -1;
- }
- /* Returns 0 on success, -1 with exception set on failure. */
- static int
- internal_close(winconsoleio *self)
- {
- if (self->fd != -1) {
- if (self->closefd) {
- _Py_BEGIN_SUPPRESS_IPH
- close(self->fd);
- _Py_END_SUPPRESS_IPH
- }
- self->fd = -1;
- }
- return 0;
- }
- /*[clinic input]
- _io._WindowsConsoleIO.close
- cls: defining_class
- /
- Close the console object.
- A closed console object 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, PyTypeObject *cls)
- /*[clinic end generated code: output=e50c1808c063e1e2 input=161001bd2a649a4b]*/
- {
- PyObject *res;
- PyObject *exc;
- int rc;
- _PyIO_State *state = get_io_state_by_cls(cls);
- res = PyObject_CallMethodOneArg((PyObject*)state->PyRawIOBase_Type,
- &_Py_ID(close), (PyObject*)self);
- if (!self->closefd) {
- self->fd = -1;
- return res;
- }
- if (res == NULL) {
- exc = PyErr_GetRaisedException();
- }
- rc = internal_close(self);
- if (res == NULL) {
- _PyErr_ChainExceptions1(exc);
- }
- 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->fd = -1;
- self->created = 0;
- self->readable = 0;
- self->writable = 0;
- self->closefd = 0;
- self->blksize = 0;
- self->weakreflist = NULL;
- }
- return (PyObject *) self;
- }
- /*[clinic input]
- _io._WindowsConsoleIO.__init__
- file as nameobj: object
- mode: str = "r"
- closefd: bool = True
- opener: object = None
- Open a console buffer by file descriptor.
- The mode can be 'rb' (default), or 'wb' for reading or writing bytes. All
- other mode characters will be ignored. Mode 'b' will be assumed if it is
- omitted. 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=3fd9cbcdd8d95429 input=7a3eed6bbe998fd9]*/
- {
- const char *s;
- wchar_t *name = NULL;
- char console_type = '\0';
- int ret = 0;
- int rwa = 0;
- int fd = -1;
- int fd_is_own = 0;
- HANDLE handle = NULL;
- #ifndef NDEBUG
- _PyIO_State *state = find_io_state_by_def(Py_TYPE(self));
- assert(PyObject_TypeCheck(self, state->PyWindowsConsoleIO_Type));
- #endif
- if (self->fd >= 0) {
- if (self->closefd) {
- /* Have to close the existing file first. */
- if (internal_close(self) < 0)
- return -1;
- }
- else
- self->fd = -1;
- }
- fd = _PyLong_AsInt(nameobj);
- if (fd < 0) {
- if (!PyErr_Occurred()) {
- PyErr_SetString(PyExc_ValueError,
- "negative file descriptor");
- return -1;
- }
- PyErr_Clear();
- }
- self->fd = fd;
- if (fd < 0) {
- PyObject *decodedname;
- int d = PyUnicode_FSDecoder(nameobj, (void*)&decodedname);
- if (!d)
- return -1;
- name = PyUnicode_AsWideCharString(decodedname, NULL);
- console_type = _PyIO_get_console_type(decodedname);
- Py_CLEAR(decodedname);
- if (name == NULL)
- return -1;
- }
- 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;
- if (console_type == 'x')
- console_type = 'r';
- break;
- case 'w':
- if (rwa)
- goto bad_mode;
- rwa = 1;
- self->writable = 1;
- if (console_type == 'x')
- console_type = 'w';
- break;
- default:
- PyErr_Format(PyExc_ValueError,
- "invalid mode: %.200s", mode);
- goto error;
- }
- }
- if (!rwa)
- goto bad_mode;
- if (fd >= 0) {
- handle = _Py_get_osfhandle_noraise(fd);
- self->closefd = 0;
- } else {
- DWORD access = GENERIC_READ;
- self->closefd = 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
- /* Attempt to open for read/write initially, then fall back
- on the specific access. This is required for modern names
- CONIN$ and CONOUT$, which allow reading/writing state as
- well as reading/writing content. */
- handle = CreateFileW(name, GENERIC_READ | GENERIC_WRITE,
- FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
- if (handle == INVALID_HANDLE_VALUE)
- handle = CreateFileW(name, access,
- FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
- Py_END_ALLOW_THREADS
- if (handle == INVALID_HANDLE_VALUE) {
- PyErr_SetExcFromWindowsErrWithFilenameObject(PyExc_OSError, GetLastError(), nameobj);
- goto error;
- }
- if (self->writable)
- self->fd = _Py_open_osfhandle_noraise(handle, _O_WRONLY | _O_BINARY);
- else
- self->fd = _Py_open_osfhandle_noraise(handle, _O_RDONLY | _O_BINARY);
- if (self->fd < 0) {
- PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError, nameobj);
- CloseHandle(handle);
- goto error;
- }
- }
- if (console_type == '\0')
- console_type = _get_console_type(handle);
- if (self->writable && console_type != 'w') {
- PyErr_SetString(PyExc_ValueError,
- "Cannot open console input buffer for writing");
- goto error;
- }
- if (self->readable && console_type != 'r') {
- PyErr_SetString(PyExc_ValueError,
- "Cannot open console output buffer for reading");
- goto error;
- }
- self->blksize = DEFAULT_BUFFER_SIZE;
- memset(self->buf, 0, 4);
- if (PyObject_SetAttr((PyObject *)self, &_Py_ID(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:
- if (name)
- PyMem_Free(name);
- return ret;
- }
- static int
- winconsoleio_traverse(winconsoleio *self, visitproc visit, void *arg)
- {
- Py_VISIT(Py_TYPE(self));
- Py_VISIT(self->dict);
- return 0;
- }
- static int
- winconsoleio_clear(winconsoleio *self)
- {
- Py_CLEAR(self->dict);
- return 0;
- }
- static void
- winconsoleio_dealloc(winconsoleio *self)
- {
- PyTypeObject *tp = Py_TYPE(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);
- tp->tp_free((PyObject *)self);
- Py_DECREF(tp);
- }
- static PyObject *
- err_closed(void)
- {
- PyErr_SetString(PyExc_ValueError, "I/O operation on closed file");
- return NULL;
- }
- static PyObject *
- err_mode(_PyIO_State *state, const char *action)
- {
- return PyErr_Format(state->unsupported_operation,
- "Console buffer does not support %s", action);
- }
- /*[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=006fa74ce3b5cfbf input=845c47ebbc3a2f67]*/
- {
- if (self->fd < 0)
- return err_closed();
- return PyLong_FromLong(self->fd);
- }
- /*[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=daf9cef2743becf0 input=6be9defb5302daae]*/
- {
- if (self->fd == -1)
- 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=e0a2ad7eae5abf67 input=cefbd8abc24df6a0]*/
- {
- if (self->fd == -1)
- return err_closed();
- return PyBool_FromLong((long) self->writable);
- }
- static DWORD
- _buflen(winconsoleio *self)
- {
- for (DWORD i = 0; i < SMALLBUF; ++i) {
- if (!self->buf[i])
- return i;
- }
- return SMALLBUF;
- }
- static DWORD
- _copyfrombuf(winconsoleio *self, char *buf, DWORD len)
- {
- DWORD n = 0;
- while (self->buf[0] && len--) {
- buf[n++] = self->buf[0];
- for (int i = 1; i < SMALLBUF; ++i)
- self->buf[i - 1] = self->buf[i];
- self->buf[SMALLBUF - 1] = 0;
- }
- return n;
- }
- static wchar_t *
- read_console_w(HANDLE handle, DWORD maxlen, DWORD *readlen) {
- int err = 0, sig = 0;
- wchar_t *buf = (wchar_t*)PyMem_Malloc(maxlen * sizeof(wchar_t));
- if (!buf) {
- PyErr_NoMemory();
- goto error;
- }
- *readlen = 0;
- //DebugBreak();
- Py_BEGIN_ALLOW_THREADS
- DWORD off = 0;
- while (off < maxlen) {
- DWORD n = (DWORD)-1;
- DWORD len = min(maxlen - off, BUFSIZ);
- SetLastError(0);
- BOOL res = ReadConsoleW(handle, &buf[off], len, &n, NULL);
- if (!res) {
- err = GetLastError();
- break;
- }
- if (n == (DWORD)-1 && (err = GetLastError()) == ERROR_OPERATION_ABORTED) {
- break;
- }
- if (n == 0) {
- err = GetLastError();
- if (err != ERROR_OPERATION_ABORTED)
- break;
- err = 0;
- HANDLE hInterruptEvent = _PyOS_SigintEvent();
- if (WaitForSingleObjectEx(hInterruptEvent, 100, FALSE)
- == WAIT_OBJECT_0) {
- ResetEvent(hInterruptEvent);
- Py_BLOCK_THREADS
- sig = PyErr_CheckSignals();
- Py_UNBLOCK_THREADS
- if (sig < 0)
- break;
- }
- }
- *readlen += n;
- /* If we didn't read a full buffer that time, don't try
- again or we will block a second time. */
- if (n < len)
- break;
- /* If the buffer ended with a newline, break out */
- if (buf[*readlen - 1] == '\n')
- break;
- /* If the buffer ends with a high surrogate, expand the
- buffer and read an extra character. */
- WORD char_type;
- if (off + BUFSIZ >= maxlen &&
- GetStringTypeW(CT_CTYPE3, &buf[*readlen - 1], 1, &char_type) &&
- char_type == C3_HIGHSURROGATE) {
- wchar_t *newbuf;
- maxlen += 1;
- Py_BLOCK_THREADS
- newbuf = (wchar_t*)PyMem_Realloc(buf, maxlen * sizeof(wchar_t));
- Py_UNBLOCK_THREADS
- if (!newbuf) {
- sig = -1;
- PyErr_NoMemory();
- break;
- }
- buf = newbuf;
- /* Only advance by n and not BUFSIZ in this case */
- off += n;
- continue;
- }
- off += BUFSIZ;
- }
- Py_END_ALLOW_THREADS
- if (sig)
- goto error;
- if (err) {
- PyErr_SetFromWindowsErr(err);
- goto error;
- }
- if (*readlen > 0 && buf[0] == L'\x1a') {
- PyMem_Free(buf);
- buf = (wchar_t *)PyMem_Malloc(sizeof(wchar_t));
- if (!buf) {
- PyErr_NoMemory();
- goto error;
- }
- buf[0] = L'\0';
- *readlen = 0;
- }
- return buf;
- error:
- if (buf)
- PyMem_Free(buf);
- return NULL;
- }
- static Py_ssize_t
- readinto(_PyIO_State *state, winconsoleio *self, char *buf, Py_ssize_t len)
- {
- if (self->fd == -1) {
- err_closed();
- return -1;
- }
- if (!self->readable) {
- err_mode(state, "reading");
- return -1;
- }
- if (len == 0)
- return 0;
- if (len > BUFMAX) {
- PyErr_Format(PyExc_ValueError, "cannot read more than %d bytes", BUFMAX);
- return -1;
- }
- HANDLE handle = _Py_get_osfhandle(self->fd);
- if (handle == INVALID_HANDLE_VALUE)
- return -1;
- /* Each character may take up to 4 bytes in the final buffer.
- This is highly conservative, but necessary to avoid
- failure for any given Unicode input (e.g. \U0010ffff).
- If the caller requests fewer than 4 bytes, we buffer one
- character.
- */
- DWORD wlen = (DWORD)(len / 4);
- if (wlen == 0) {
- wlen = 1;
- }
- DWORD read_len = _copyfrombuf(self, buf, (DWORD)len);
- if (read_len) {
- buf = &buf[read_len];
- len -= read_len;
- wlen -= 1;
- }
- if (len == read_len || wlen == 0)
- return read_len;
- DWORD n;
- wchar_t *wbuf = read_console_w(handle, wlen, &n);
- if (wbuf == NULL)
- return -1;
- if (n == 0) {
- PyMem_Free(wbuf);
- return read_len;
- }
- int err = 0;
- DWORD u8n = 0;
- Py_BEGIN_ALLOW_THREADS
- if (len < 4) {
- if (WideCharToMultiByte(CP_UTF8, 0, wbuf, n,
- self->buf, sizeof(self->buf) / sizeof(self->buf[0]),
- NULL, NULL))
- u8n = _copyfrombuf(self, buf, (DWORD)len);
- } else {
- u8n = WideCharToMultiByte(CP_UTF8, 0, wbuf, n,
- buf, (DWORD)len, NULL, NULL);
- }
- if (u8n) {
- read_len += u8n;
- u8n = 0;
- } else {
- err = GetLastError();
- if (err == ERROR_INSUFFICIENT_BUFFER) {
- /* Calculate the needed buffer for a more useful error, as this
- means our "/ 4" logic above is insufficient for some input.
- */
- u8n = WideCharToMultiByte(CP_UTF8, 0, wbuf, n,
- NULL, 0, NULL, NULL);
- }
- }
- Py_END_ALLOW_THREADS
- PyMem_Free(wbuf);
- if (u8n) {
- PyErr_Format(PyExc_SystemError,
- "Buffer had room for %zd bytes but %u bytes required",
- len, u8n);
- return -1;
- }
- if (err) {
- PyErr_SetFromWindowsErr(err);
- return -1;
- }
- return read_len;
- }
- /*[clinic input]
- _io._WindowsConsoleIO.readinto
- cls: defining_class
- buffer: Py_buffer(accept={rwbuffer})
- /
- Same as RawIOBase.readinto().
- [clinic start generated code]*/
- static PyObject *
- _io__WindowsConsoleIO_readinto_impl(winconsoleio *self, PyTypeObject *cls,
- Py_buffer *buffer)
- /*[clinic end generated code: output=96717c74f6204b79 input=4b0627c3b1645f78]*/
- {
- _PyIO_State *state = get_io_state_by_cls(cls);
- Py_ssize_t len = readinto(state, self, buffer->buf, buffer->len);
- if (len < 0)
- return NULL;
- return PyLong_FromSsize_t(len);
- }
- static DWORD
- new_buffersize(winconsoleio *self, DWORD currentsize)
- {
- DWORD 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. */
- 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 console, returned as bytes.
- Return an empty bytes object at EOF.
- [clinic start generated code]*/
- static PyObject *
- _io__WindowsConsoleIO_readall_impl(winconsoleio *self)
- /*[clinic end generated code: output=e6d312c684f6e23b input=4024d649a1006e69]*/
- {
- wchar_t *buf;
- DWORD bufsize, n, len = 0;
- PyObject *bytes;
- DWORD bytes_size, rn;
- HANDLE handle;
- if (self->fd == -1)
- return err_closed();
- handle = _Py_get_osfhandle(self->fd);
- if (handle == INVALID_HANDLE_VALUE)
- return NULL;
- bufsize = BUFSIZ;
- buf = (wchar_t*)PyMem_Malloc((bufsize + 1) * sizeof(wchar_t));
- if (buf == NULL) {
- PyErr_NoMemory();
- return NULL;
- }
- while (1) {
- wchar_t *subbuf;
- if (len >= (Py_ssize_t)bufsize) {
- DWORD newsize = new_buffersize(self, len);
- if (newsize > BUFMAX)
- break;
- if (newsize < bufsize) {
- PyErr_SetString(PyExc_OverflowError,
- "unbounded read returned more bytes "
- "than a Python bytes object can hold");
- PyMem_Free(buf);
- return NULL;
- }
- bufsize = newsize;
- wchar_t *tmp = PyMem_Realloc(buf,
- (bufsize + 1) * sizeof(wchar_t));
- if (tmp == NULL) {
- PyMem_Free(buf);
- PyErr_NoMemory();
- return NULL;
- }
- buf = tmp;
- }
- subbuf = read_console_w(handle, bufsize - len, &n);
- if (subbuf == NULL) {
- PyMem_Free(buf);
- return NULL;
- }
- if (n > 0)
- wcsncpy_s(&buf[len], bufsize - len + 1, subbuf, n);
- PyMem_Free(subbuf);
- /* when the read is empty we break */
- if (n == 0)
- break;
- len += n;
- }
- if (len == 0 && _buflen(self) == 0) {
- /* when the result starts with ^Z we return an empty buffer */
- PyMem_Free(buf);
- return PyBytes_FromStringAndSize(NULL, 0);
- }
- if (len) {
- Py_BEGIN_ALLOW_THREADS
- bytes_size = WideCharToMultiByte(CP_UTF8, 0, buf, len,
- NULL, 0, NULL, NULL);
- Py_END_ALLOW_THREADS
- if (!bytes_size) {
- DWORD err = GetLastError();
- PyMem_Free(buf);
- return PyErr_SetFromWindowsErr(err);
- }
- } else {
- bytes_size = 0;
- }
- bytes_size += _buflen(self);
- bytes = PyBytes_FromStringAndSize(NULL, bytes_size);
- rn = _copyfrombuf(self, PyBytes_AS_STRING(bytes), bytes_size);
- if (len) {
- Py_BEGIN_ALLOW_THREADS
- bytes_size = WideCharToMultiByte(CP_UTF8, 0, buf, len,
- &PyBytes_AS_STRING(bytes)[rn], bytes_size - rn, NULL, NULL);
- Py_END_ALLOW_THREADS
- if (!bytes_size) {
- DWORD err = GetLastError();
- PyMem_Free(buf);
- Py_CLEAR(bytes);
- return PyErr_SetFromWindowsErr(err);
- }
- /* add back the number of preserved bytes */
- bytes_size += rn;
- }
- PyMem_Free(buf);
- if (bytes_size < (size_t)PyBytes_GET_SIZE(bytes)) {
- if (_PyBytes_Resize(&bytes, n * sizeof(wchar_t)) < 0) {
- Py_CLEAR(bytes);
- return NULL;
- }
- }
- return bytes;
- }
- /*[clinic input]
- _io._WindowsConsoleIO.read
- cls: defining_class
- size: Py_ssize_t(accept={int, NoneType}) = -1
- /
- Read at most size bytes, returned as bytes.
- Only makes one system call when size is a positive integer,
- so less data may be returned than requested.
- Return an empty bytes object at EOF.
- [clinic start generated code]*/
- static PyObject *
- _io__WindowsConsoleIO_read_impl(winconsoleio *self, PyTypeObject *cls,
- Py_ssize_t size)
- /*[clinic end generated code: output=7e569a586537c0ae input=a14570a5da273365]*/
- {
- PyObject *bytes;
- Py_ssize_t bytes_size;
- if (self->fd == -1)
- return err_closed();
- if (!self->readable) {
- _PyIO_State *state = get_io_state_by_cls(cls);
- return err_mode(state, "reading");
- }
- if (size < 0)
- return _io__WindowsConsoleIO_readall_impl(self);
- if (size > BUFMAX) {
- PyErr_Format(PyExc_ValueError, "cannot read more than %d bytes", BUFMAX);
- return NULL;
- }
- bytes = PyBytes_FromStringAndSize(NULL, size);
- if (bytes == NULL)
- return NULL;
- _PyIO_State *state = get_io_state_by_cls(cls);
- bytes_size = readinto(state, self, PyBytes_AS_STRING(bytes),
- PyBytes_GET_SIZE(bytes));
- if (bytes_size < 0) {
- Py_CLEAR(bytes);
- return NULL;
- }
- if (bytes_size < PyBytes_GET_SIZE(bytes)) {
- if (_PyBytes_Resize(&bytes, bytes_size) < 0) {
- Py_CLEAR(bytes);
- return NULL;
- }
- }
- return bytes;
- }
- /*[clinic input]
- _io._WindowsConsoleIO.write
- cls: defining_class
- 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.
- [clinic start generated code]*/
- static PyObject *
- _io__WindowsConsoleIO_write_impl(winconsoleio *self, PyTypeObject *cls,
- Py_buffer *b)
- /*[clinic end generated code: output=e8019f480243cb29 input=10ac37c19339dfbe]*/
- {
- BOOL res = TRUE;
- wchar_t *wbuf;
- DWORD len, wlen, n = 0;
- HANDLE handle;
- if (self->fd == -1)
- return err_closed();
- if (!self->writable) {
- _PyIO_State *state = get_io_state_by_cls(cls);
- return err_mode(state, "writing");
- }
- handle = _Py_get_osfhandle(self->fd);
- if (handle == INVALID_HANDLE_VALUE)
- return NULL;
- if (!b->len) {
- return PyLong_FromLong(0);
- }
- if (b->len > BUFMAX)
- len = BUFMAX;
- else
- len = (DWORD)b->len;
- Py_BEGIN_ALLOW_THREADS
- /* issue11395 there is an unspecified upper bound on how many bytes
- can be written at once. We cap at 32k - the caller will have to
- handle partial writes.
- Since we don't know how many input bytes are being ignored, we
- have to reduce and recalculate. */
- const DWORD max_wlen = 32766 / sizeof(wchar_t);
- /* UTF-8 to wchar ratio is at most 3:1. */
- len = Py_MIN(len, max_wlen * 3);
- while (1) {
- /* Fix for github issues gh-110913 and gh-82052. */
- len = _find_last_utf8_boundary(b->buf, len);
- wlen = MultiByteToWideChar(CP_UTF8, 0, b->buf, len, NULL, 0);
- if (wlen <= max_wlen) {
- break;
- }
- len /= 2;
- }
- Py_END_ALLOW_THREADS
- if (!wlen) {
- return PyLong_FromLong(0);
- }
- wbuf = (wchar_t*)PyMem_Malloc(wlen * sizeof(wchar_t));
- if (!wbuf) {
- PyErr_NoMemory();
- return NULL;
- }
- Py_BEGIN_ALLOW_THREADS
- wlen = MultiByteToWideChar(CP_UTF8, 0, b->buf, len, wbuf, wlen);
- if (wlen) {
- res = WriteConsoleW(handle, wbuf, wlen, &n, NULL);
- #ifdef Py_DEBUG
- if (res) {
- #else
- if (res && n < wlen) {
- #endif
- /* Wrote fewer characters than expected, which means our
- * len value may be wrong. So recalculate it from the
- * characters that were written.
- */
- len = _wchar_to_utf8_count(b->buf, len, n);
- }
- } else
- res = 0;
- Py_END_ALLOW_THREADS
- if (!res) {
- DWORD err = GetLastError();
- PyMem_Free(wbuf);
- return PyErr_SetFromWindowsErr(err);
- }
- PyMem_Free(wbuf);
- return PyLong_FromSsize_t(len);
- }
- static PyObject *
- winconsoleio_repr(winconsoleio *self)
- {
- if (self->fd == -1)
- return PyUnicode_FromFormat("<_io._WindowsConsoleIO [closed]>");
- if (self->readable)
- return PyUnicode_FromFormat("<_io._WindowsConsoleIO mode='rb' closefd=%s>",
- self->closefd ? "True" : "False");
- if (self->writable)
- return PyUnicode_FromFormat("<_io._WindowsConsoleIO mode='wb' closefd=%s>",
- self->closefd ? "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=9eac09d287c11bd7 input=9b91591dbe356f86]*/
- {
- if (self->fd == -1)
- return err_closed();
- Py_RETURN_TRUE;
- }
- #define clinic_state() (find_io_state_by_def(Py_TYPE(self)))
- #include "clinic/winconsoleio.c.h"
- #undef clinic_state
- static PyMethodDef winconsoleio_methods[] = {
- _IO__WINDOWSCONSOLEIO_READ_METHODDEF
- _IO__WINDOWSCONSOLEIO_READALL_METHODDEF
- _IO__WINDOWSCONSOLEIO_READINTO_METHODDEF
- _IO__WINDOWSCONSOLEIO_WRITE_METHODDEF
- _IO__WINDOWSCONSOLEIO_CLOSE_METHODDEF
- _IO__WINDOWSCONSOLEIO_READABLE_METHODDEF
- _IO__WINDOWSCONSOLEIO_WRITABLE_METHODDEF
- _IO__WINDOWSCONSOLEIO_FILENO_METHODDEF
- _IO__WINDOWSCONSOLEIO_ISATTY_METHODDEF
- {NULL, NULL} /* sentinel */
- };
- /* 'closed' and 'mode' are attributes for compatibility with FileIO. */
- static PyObject *
- get_closed(winconsoleio *self, void *closure)
- {
- return PyBool_FromLong((long)(self->fd == -1));
- }
- static PyObject *
- get_closefd(winconsoleio *self, void *closure)
- {
- return PyBool_FromLong((long)(self->closefd));
- }
- 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},
- {"__weaklistoffset__", T_PYSSIZET, offsetof(winconsoleio, weakreflist), READONLY},
- {"__dictoffset__", T_PYSSIZET, offsetof(winconsoleio, dict), READONLY},
- {NULL}
- };
- static PyType_Slot winconsoleio_slots[] = {
- {Py_tp_dealloc, winconsoleio_dealloc},
- {Py_tp_repr, winconsoleio_repr},
- {Py_tp_getattro, PyObject_GenericGetAttr},
- {Py_tp_doc, (void *)_io__WindowsConsoleIO___init____doc__},
- {Py_tp_traverse, winconsoleio_traverse},
- {Py_tp_clear, winconsoleio_clear},
- {Py_tp_methods, winconsoleio_methods},
- {Py_tp_members, winconsoleio_members},
- {Py_tp_getset, winconsoleio_getsetlist},
- {Py_tp_init, _io__WindowsConsoleIO___init__},
- {Py_tp_new, winconsoleio_new},
- {0, NULL},
- };
- PyType_Spec winconsoleio_spec = {
- .name = "_io._WindowsConsoleIO",
- .basicsize = sizeof(winconsoleio),
- .flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC |
- Py_TPFLAGS_IMMUTABLETYPE),
- .slots = winconsoleio_slots,
- };
- #endif /* HAVE_WINDOWS_CONSOLE_IO */
|