• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2     An implementation of Windows console I/O
3 
4     Classes defined here: _WindowsConsoleIO
5 
6     Written by Steve Dower
7 */
8 
9 #include "Python.h"
10 #include "pycore_fileutils.h"     // _Py_BEGIN_SUPPRESS_IPH
11 #include "pycore_object.h"        // _PyObject_GC_UNTRACK()
12 #include "pycore_pyerrors.h"      // _PyErr_ChainExceptions1()
13 
14 #ifdef HAVE_WINDOWS_CONSOLE_IO
15 
16 
17 #ifdef HAVE_SYS_TYPES_H
18 #include <sys/types.h>
19 #endif
20 #ifdef HAVE_SYS_STAT_H
21 #include <sys/stat.h>
22 #endif
23 #include <stddef.h> /* For offsetof */
24 
25 #ifndef WIN32_LEAN_AND_MEAN
26 #define WIN32_LEAN_AND_MEAN
27 #endif
28 #include <windows.h>
29 #include <fcntl.h>
30 
31 #include "_iomodule.h"
32 
33 /* BUFSIZ determines how many characters can be typed at the console
34    before it starts blocking. */
35 #if BUFSIZ < (16*1024)
36 #define SMALLCHUNK (2*1024)
37 #elif (BUFSIZ >= (2 << 25))
38 #error "unreasonable BUFSIZ > 64 MiB defined"
39 #else
40 #define SMALLCHUNK BUFSIZ
41 #endif
42 
43 /* BUFMAX determines how many bytes can be read in one go. */
44 #define BUFMAX (32*1024*1024)
45 
46 /* SMALLBUF determines how many utf-8 characters will be
47    buffered within the stream, in order to support reads
48    of less than one character */
49 #define SMALLBUF 4
50 
_get_console_type(HANDLE handle)51 char _get_console_type(HANDLE handle) {
52     DWORD mode, peek_count;
53 
54     if (handle == INVALID_HANDLE_VALUE)
55         return '\0';
56 
57     if (!GetConsoleMode(handle, &mode))
58         return '\0';
59 
60     /* Peek at the handle to see whether it is an input or output handle */
61     if (GetNumberOfConsoleInputEvents(handle, &peek_count))
62         return 'r';
63     return 'w';
64 }
65 
_PyIO_get_console_type(PyObject * path_or_fd)66 char _PyIO_get_console_type(PyObject *path_or_fd) {
67     int fd = PyLong_AsLong(path_or_fd);
68     PyErr_Clear();
69     if (fd >= 0) {
70         HANDLE handle = _Py_get_osfhandle_noraise(fd);
71         if (handle == INVALID_HANDLE_VALUE)
72             return '\0';
73         return _get_console_type(handle);
74     }
75 
76     PyObject *decoded;
77     wchar_t *decoded_wstr;
78 
79     if (!PyUnicode_FSDecoder(path_or_fd, &decoded)) {
80         PyErr_Clear();
81         return '\0';
82     }
83     decoded_wstr = PyUnicode_AsWideCharString(decoded, NULL);
84     Py_CLEAR(decoded);
85     if (!decoded_wstr) {
86         PyErr_Clear();
87         return '\0';
88     }
89 
90     char m = '\0';
91     if (!_wcsicmp(decoded_wstr, L"CONIN$")) {
92         m = 'r';
93     } else if (!_wcsicmp(decoded_wstr, L"CONOUT$")) {
94         m = 'w';
95     } else if (!_wcsicmp(decoded_wstr, L"CON")) {
96         m = 'x';
97     }
98     if (m) {
99         PyMem_Free(decoded_wstr);
100         return m;
101     }
102 
103     DWORD length;
104     wchar_t name_buf[MAX_PATH], *pname_buf = name_buf;
105 
106     length = GetFullPathNameW(decoded_wstr, MAX_PATH, pname_buf, NULL);
107     if (length > MAX_PATH) {
108         pname_buf = PyMem_New(wchar_t, length);
109         if (pname_buf)
110             length = GetFullPathNameW(decoded_wstr, length, pname_buf, NULL);
111         else
112             length = 0;
113     }
114     PyMem_Free(decoded_wstr);
115 
116     if (length) {
117         wchar_t *name = pname_buf;
118         if (length >= 4 && name[3] == L'\\' &&
119             (name[2] == L'.' || name[2] == L'?') &&
120             name[1] == L'\\' && name[0] == L'\\') {
121             name += 4;
122         }
123         if (!_wcsicmp(name, L"CONIN$")) {
124             m = 'r';
125         } else if (!_wcsicmp(name, L"CONOUT$")) {
126             m = 'w';
127         } else if (!_wcsicmp(name, L"CON")) {
128             m = 'x';
129         }
130     }
131 
132     if (pname_buf != name_buf)
133         PyMem_Free(pname_buf);
134     return m;
135 }
136 
137 static DWORD
_find_last_utf8_boundary(const unsigned char * buf,DWORD len)138 _find_last_utf8_boundary(const unsigned char *buf, DWORD len)
139 {
140     for (DWORD count = 1; count < 4 && count <= len; count++) {
141         unsigned char c = buf[len - count];
142         if (c < 0x80) {
143             /* No starting byte found. */
144             return len;
145         }
146         if (c >= 0xc0) {
147             if (c < 0xe0 /* 2-bytes sequence */ ? count < 2 :
148                 c < 0xf0 /* 3-bytes sequence */ ? count < 3 :
149                 c < 0xf8 /* 4-bytes sequence */)
150             {
151                 /* Incomplete multibyte sequence. */
152                 return len - count;
153             }
154             /* Either complete or invalid sequence. */
155             return len;
156         }
157     }
158     /* Either complete 4-bytes sequence or invalid sequence. */
159     return len;
160 }
161 
162 /* Find the number of UTF-8 bytes that corresponds to the specified number of
163  * wchars.
164  * I.e. find x <= len so that MultiByteToWideChar(CP_UTF8, 0, s, x, NULL, 0) == n.
165  *
166  * WideCharToMultiByte() cannot be used for this, because the UTF-8 -> wchar
167  * conversion is not reversible (invalid UTF-8 byte produces \ufffd which
168  * will be converted back to 3-bytes UTF-8 sequence \xef\xbf\xbd).
169  * So we need to use binary search.
170  */
171 static DWORD
_wchar_to_utf8_count(const unsigned char * s,DWORD len,DWORD n)172 _wchar_to_utf8_count(const unsigned char *s, DWORD len, DWORD n)
173 {
174     DWORD start = 0;
175     while (1) {
176         DWORD mid = 0;
177         for (DWORD i = len / 2; i <= len; i++) {
178             mid = _find_last_utf8_boundary(s, i);
179             if (mid != 0) {
180                 break;
181             }
182             /* The middle could split the first multibytes sequence. */
183         }
184         if (mid == len) {
185             return start + len;
186         }
187         if (mid == 0) {
188             mid = len > 1 ? len - 1 : 1;
189         }
190         DWORD wlen = MultiByteToWideChar(CP_UTF8, 0, s, mid, NULL, 0);
191         if (wlen <= n) {
192             s += mid;
193             start += mid;
194             len -= mid;
195             n -= wlen;
196         }
197         else {
198             len = mid;
199         }
200     }
201 }
202 
203 /*[clinic input]
204 module _io
205 class _io._WindowsConsoleIO "winconsoleio *" "clinic_state()->PyWindowsConsoleIO_Type"
206 [clinic start generated code]*/
207 /*[clinic end generated code: output=da39a3ee5e6b4b0d input=05526e723011ab36]*/
208 
209 typedef struct {
210     PyObject_HEAD
211     int fd;
212     unsigned int created : 1;
213     unsigned int readable : 1;
214     unsigned int writable : 1;
215     unsigned int closefd : 1;
216     char finalizing;
217     unsigned int blksize;
218     PyObject *weakreflist;
219     PyObject *dict;
220     char buf[SMALLBUF];
221     wchar_t wbuf;
222 } winconsoleio;
223 
224 int
_PyWindowsConsoleIO_closed(PyObject * self)225 _PyWindowsConsoleIO_closed(PyObject *self)
226 {
227     return ((winconsoleio *)self)->fd == -1;
228 }
229 
230 
231 /* Returns 0 on success, -1 with exception set on failure. */
232 static int
internal_close(winconsoleio * self)233 internal_close(winconsoleio *self)
234 {
235     if (self->fd != -1) {
236         if (self->closefd) {
237             _Py_BEGIN_SUPPRESS_IPH
238             close(self->fd);
239             _Py_END_SUPPRESS_IPH
240         }
241         self->fd = -1;
242     }
243     return 0;
244 }
245 
246 /*[clinic input]
247 _io._WindowsConsoleIO.close
248     cls: defining_class
249     /
250 
251 Close the console object.
252 
253 A closed console object cannot be used for further I/O operations.
254 close() may be called more than once without error.
255 [clinic start generated code]*/
256 
257 static PyObject *
_io__WindowsConsoleIO_close_impl(winconsoleio * self,PyTypeObject * cls)258 _io__WindowsConsoleIO_close_impl(winconsoleio *self, PyTypeObject *cls)
259 /*[clinic end generated code: output=e50c1808c063e1e2 input=161001bd2a649a4b]*/
260 {
261     PyObject *res;
262     PyObject *exc;
263     int rc;
264 
265     _PyIO_State *state = get_io_state_by_cls(cls);
266     res = PyObject_CallMethodOneArg((PyObject*)state->PyRawIOBase_Type,
267                                     &_Py_ID(close), (PyObject*)self);
268     if (!self->closefd) {
269         self->fd = -1;
270         return res;
271     }
272     if (res == NULL) {
273         exc = PyErr_GetRaisedException();
274     }
275     rc = internal_close(self);
276     if (res == NULL) {
277         _PyErr_ChainExceptions1(exc);
278     }
279     if (rc < 0) {
280         Py_CLEAR(res);
281     }
282     return res;
283 }
284 
285 static PyObject *
winconsoleio_new(PyTypeObject * type,PyObject * args,PyObject * kwds)286 winconsoleio_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
287 {
288     winconsoleio *self;
289 
290     assert(type != NULL && type->tp_alloc != NULL);
291 
292     self = (winconsoleio *) type->tp_alloc(type, 0);
293     if (self != NULL) {
294         self->fd = -1;
295         self->created = 0;
296         self->readable = 0;
297         self->writable = 0;
298         self->closefd = 0;
299         self->blksize = 0;
300         self->weakreflist = NULL;
301     }
302 
303     return (PyObject *) self;
304 }
305 
306 /*[clinic input]
307 _io._WindowsConsoleIO.__init__
308     file as nameobj: object
309     mode: str = "r"
310     closefd: bool = True
311     opener: object = None
312 
313 Open a console buffer by file descriptor.
314 
315 The mode can be 'rb' (default), or 'wb' for reading or writing bytes. All
316 other mode characters will be ignored. Mode 'b' will be assumed if it is
317 omitted. The *opener* parameter is always ignored.
318 [clinic start generated code]*/
319 
320 static int
_io__WindowsConsoleIO___init___impl(winconsoleio * self,PyObject * nameobj,const char * mode,int closefd,PyObject * opener)321 _io__WindowsConsoleIO___init___impl(winconsoleio *self, PyObject *nameobj,
322                                     const char *mode, int closefd,
323                                     PyObject *opener)
324 /*[clinic end generated code: output=3fd9cbcdd8d95429 input=7a3eed6bbe998fd9]*/
325 {
326     const char *s;
327     wchar_t *name = NULL;
328     char console_type = '\0';
329     int ret = 0;
330     int rwa = 0;
331     int fd = -1;
332     int fd_is_own = 0;
333     HANDLE handle = NULL;
334 
335 #ifndef NDEBUG
336     _PyIO_State *state = find_io_state_by_def(Py_TYPE(self));
337     assert(PyObject_TypeCheck(self, state->PyWindowsConsoleIO_Type));
338 #endif
339     if (self->fd >= 0) {
340         if (self->closefd) {
341             /* Have to close the existing file first. */
342             if (internal_close(self) < 0)
343                 return -1;
344         }
345         else
346             self->fd = -1;
347     }
348 
349     if (PyBool_Check(nameobj)) {
350         if (PyErr_WarnEx(PyExc_RuntimeWarning,
351                 "bool is used as a file descriptor", 1))
352         {
353             return -1;
354         }
355     }
356     fd = PyLong_AsInt(nameobj);
357     if (fd < 0) {
358         if (!PyErr_Occurred()) {
359             PyErr_SetString(PyExc_ValueError,
360                             "negative file descriptor");
361             return -1;
362         }
363         PyErr_Clear();
364     }
365     self->fd = fd;
366 
367     if (fd < 0) {
368         PyObject *decodedname;
369 
370         int d = PyUnicode_FSDecoder(nameobj, (void*)&decodedname);
371         if (!d)
372             return -1;
373 
374         name = PyUnicode_AsWideCharString(decodedname, NULL);
375         console_type = _PyIO_get_console_type(decodedname);
376         Py_CLEAR(decodedname);
377         if (name == NULL)
378             return -1;
379     }
380 
381     s = mode;
382     while (*s) {
383         switch (*s++) {
384         case '+':
385         case 'a':
386         case 'b':
387         case 'x':
388             break;
389         case 'r':
390             if (rwa)
391                 goto bad_mode;
392             rwa = 1;
393             self->readable = 1;
394             if (console_type == 'x')
395                 console_type = 'r';
396             break;
397         case 'w':
398             if (rwa)
399                 goto bad_mode;
400             rwa = 1;
401             self->writable = 1;
402             if (console_type == 'x')
403                 console_type = 'w';
404             break;
405         default:
406             PyErr_Format(PyExc_ValueError,
407                          "invalid mode: %.200s", mode);
408             goto error;
409         }
410     }
411 
412     if (!rwa)
413         goto bad_mode;
414 
415     if (fd >= 0) {
416         handle = _Py_get_osfhandle_noraise(fd);
417         self->closefd = 0;
418     } else {
419         DWORD access = GENERIC_READ;
420 
421         self->closefd = 1;
422         if (!closefd) {
423             PyErr_SetString(PyExc_ValueError,
424                 "Cannot use closefd=False with file name");
425             goto error;
426         }
427 
428         if (self->writable)
429             access = GENERIC_WRITE;
430 
431         Py_BEGIN_ALLOW_THREADS
432         /* Attempt to open for read/write initially, then fall back
433            on the specific access. This is required for modern names
434            CONIN$ and CONOUT$, which allow reading/writing state as
435            well as reading/writing content. */
436         handle = CreateFileW(name, GENERIC_READ | GENERIC_WRITE,
437             FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
438         if (handle == INVALID_HANDLE_VALUE)
439             handle = CreateFileW(name, access,
440                 FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
441         Py_END_ALLOW_THREADS
442 
443         if (handle == INVALID_HANDLE_VALUE) {
444             PyErr_SetExcFromWindowsErrWithFilenameObject(PyExc_OSError, GetLastError(), nameobj);
445             goto error;
446         }
447 
448         if (self->writable)
449             self->fd = _Py_open_osfhandle_noraise(handle, _O_WRONLY | _O_BINARY | _O_NOINHERIT);
450         else
451             self->fd = _Py_open_osfhandle_noraise(handle, _O_RDONLY | _O_BINARY | _O_NOINHERIT);
452         if (self->fd < 0) {
453             PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError, nameobj);
454             CloseHandle(handle);
455             goto error;
456         }
457     }
458 
459     if (console_type == '\0')
460         console_type = _get_console_type(handle);
461 
462     if (self->writable && console_type != 'w') {
463         PyErr_SetString(PyExc_ValueError,
464             "Cannot open console input buffer for writing");
465         goto error;
466     }
467     if (self->readable && console_type != 'r') {
468         PyErr_SetString(PyExc_ValueError,
469             "Cannot open console output buffer for reading");
470         goto error;
471     }
472 
473     self->blksize = DEFAULT_BUFFER_SIZE;
474     memset(self->buf, 0, 4);
475 
476     if (PyObject_SetAttr((PyObject *)self, &_Py_ID(name), nameobj) < 0)
477         goto error;
478 
479     goto done;
480 
481 bad_mode:
482     PyErr_SetString(PyExc_ValueError,
483                     "Must have exactly one of read or write mode");
484 error:
485     ret = -1;
486     internal_close(self);
487 
488 done:
489     if (name)
490         PyMem_Free(name);
491     return ret;
492 }
493 
494 static int
winconsoleio_traverse(winconsoleio * self,visitproc visit,void * arg)495 winconsoleio_traverse(winconsoleio *self, visitproc visit, void *arg)
496 {
497     Py_VISIT(Py_TYPE(self));
498     Py_VISIT(self->dict);
499     return 0;
500 }
501 
502 static int
winconsoleio_clear(winconsoleio * self)503 winconsoleio_clear(winconsoleio *self)
504 {
505     Py_CLEAR(self->dict);
506     return 0;
507 }
508 
509 static void
winconsoleio_dealloc(winconsoleio * self)510 winconsoleio_dealloc(winconsoleio *self)
511 {
512     PyTypeObject *tp = Py_TYPE(self);
513     self->finalizing = 1;
514     if (_PyIOBase_finalize((PyObject *) self) < 0)
515         return;
516     _PyObject_GC_UNTRACK(self);
517     if (self->weakreflist != NULL)
518         PyObject_ClearWeakRefs((PyObject *) self);
519     Py_CLEAR(self->dict);
520     tp->tp_free((PyObject *)self);
521     Py_DECREF(tp);
522 }
523 
524 static PyObject *
err_closed(void)525 err_closed(void)
526 {
527     PyErr_SetString(PyExc_ValueError, "I/O operation on closed file");
528     return NULL;
529 }
530 
531 static PyObject *
err_mode(_PyIO_State * state,const char * action)532 err_mode(_PyIO_State *state, const char *action)
533 {
534     return PyErr_Format(state->unsupported_operation,
535                         "Console buffer does not support %s", action);
536 }
537 
538 /*[clinic input]
539 _io._WindowsConsoleIO.fileno
540 
541 Return the underlying file descriptor (an integer).
542 
543 [clinic start generated code]*/
544 
545 static PyObject *
_io__WindowsConsoleIO_fileno_impl(winconsoleio * self)546 _io__WindowsConsoleIO_fileno_impl(winconsoleio *self)
547 /*[clinic end generated code: output=006fa74ce3b5cfbf input=845c47ebbc3a2f67]*/
548 {
549     if (self->fd < 0)
550         return err_closed();
551     return PyLong_FromLong(self->fd);
552 }
553 
554 /*[clinic input]
555 _io._WindowsConsoleIO.readable
556 
557 True if console is an input buffer.
558 [clinic start generated code]*/
559 
560 static PyObject *
_io__WindowsConsoleIO_readable_impl(winconsoleio * self)561 _io__WindowsConsoleIO_readable_impl(winconsoleio *self)
562 /*[clinic end generated code: output=daf9cef2743becf0 input=6be9defb5302daae]*/
563 {
564     if (self->fd == -1)
565         return err_closed();
566     return PyBool_FromLong((long) self->readable);
567 }
568 
569 /*[clinic input]
570 _io._WindowsConsoleIO.writable
571 
572 True if console is an output buffer.
573 [clinic start generated code]*/
574 
575 static PyObject *
_io__WindowsConsoleIO_writable_impl(winconsoleio * self)576 _io__WindowsConsoleIO_writable_impl(winconsoleio *self)
577 /*[clinic end generated code: output=e0a2ad7eae5abf67 input=cefbd8abc24df6a0]*/
578 {
579     if (self->fd == -1)
580         return err_closed();
581     return PyBool_FromLong((long) self->writable);
582 }
583 
584 static DWORD
_buflen(winconsoleio * self)585 _buflen(winconsoleio *self)
586 {
587     for (DWORD i = 0; i < SMALLBUF; ++i) {
588         if (!self->buf[i])
589             return i;
590     }
591     return SMALLBUF;
592 }
593 
594 static DWORD
_copyfrombuf(winconsoleio * self,char * buf,DWORD len)595 _copyfrombuf(winconsoleio *self, char *buf, DWORD len)
596 {
597     DWORD n = 0;
598 
599     while (self->buf[0] && len--) {
600         buf[n++] = self->buf[0];
601         for (int i = 1; i < SMALLBUF; ++i)
602             self->buf[i - 1] = self->buf[i];
603         self->buf[SMALLBUF - 1] = 0;
604     }
605 
606     return n;
607 }
608 
609 static wchar_t *
read_console_w(HANDLE handle,DWORD maxlen,DWORD * readlen)610 read_console_w(HANDLE handle, DWORD maxlen, DWORD *readlen) {
611     int err = 0, sig = 0;
612 
613     wchar_t *buf = (wchar_t*)PyMem_Malloc(maxlen * sizeof(wchar_t));
614     if (!buf) {
615         PyErr_NoMemory();
616         goto error;
617     }
618 
619     *readlen = 0;
620 
621     //DebugBreak();
622     Py_BEGIN_ALLOW_THREADS
623     DWORD off = 0;
624     while (off < maxlen) {
625         DWORD n = (DWORD)-1;
626         DWORD len = min(maxlen - off, BUFSIZ);
627         SetLastError(0);
628         BOOL res = ReadConsoleW(handle, &buf[off], len, &n, NULL);
629 
630         if (!res) {
631             err = GetLastError();
632             break;
633         }
634         if (n == (DWORD)-1 && (err = GetLastError()) == ERROR_OPERATION_ABORTED) {
635             break;
636         }
637         if (n == 0) {
638             err = GetLastError();
639             if (err != ERROR_OPERATION_ABORTED)
640                 break;
641             err = 0;
642             HANDLE hInterruptEvent = _PyOS_SigintEvent();
643             if (WaitForSingleObjectEx(hInterruptEvent, 100, FALSE)
644                     == WAIT_OBJECT_0) {
645                 ResetEvent(hInterruptEvent);
646                 Py_BLOCK_THREADS
647                 sig = PyErr_CheckSignals();
648                 Py_UNBLOCK_THREADS
649                 if (sig < 0)
650                     break;
651             }
652         }
653         *readlen += n;
654 
655         /* If we didn't read a full buffer that time, don't try
656            again or we will block a second time. */
657         if (n < len)
658             break;
659         /* If the buffer ended with a newline, break out */
660         if (buf[*readlen - 1] == '\n')
661             break;
662         /* If the buffer ends with a high surrogate, expand the
663            buffer and read an extra character. */
664         WORD char_type;
665         if (off + BUFSIZ >= maxlen &&
666             GetStringTypeW(CT_CTYPE3, &buf[*readlen - 1], 1, &char_type) &&
667             char_type == C3_HIGHSURROGATE) {
668             wchar_t *newbuf;
669             maxlen += 1;
670             Py_BLOCK_THREADS
671             newbuf = (wchar_t*)PyMem_Realloc(buf, maxlen * sizeof(wchar_t));
672             Py_UNBLOCK_THREADS
673             if (!newbuf) {
674                 sig = -1;
675                 PyErr_NoMemory();
676                 break;
677             }
678             buf = newbuf;
679             /* Only advance by n and not BUFSIZ in this case */
680             off += n;
681             continue;
682         }
683 
684         off += BUFSIZ;
685     }
686 
687     Py_END_ALLOW_THREADS
688 
689     if (sig)
690         goto error;
691     if (err) {
692         PyErr_SetFromWindowsErr(err);
693         goto error;
694     }
695 
696     if (*readlen > 0 && buf[0] == L'\x1a') {
697         PyMem_Free(buf);
698         buf = (wchar_t *)PyMem_Malloc(sizeof(wchar_t));
699         if (!buf) {
700             PyErr_NoMemory();
701             goto error;
702         }
703         buf[0] = L'\0';
704         *readlen = 0;
705     }
706 
707     return buf;
708 
709 error:
710     if (buf)
711         PyMem_Free(buf);
712     return NULL;
713 }
714 
715 
716 static Py_ssize_t
readinto(_PyIO_State * state,winconsoleio * self,char * buf,Py_ssize_t len)717 readinto(_PyIO_State *state, winconsoleio *self, char *buf, Py_ssize_t len)
718 {
719     if (self->fd == -1) {
720         err_closed();
721         return -1;
722     }
723     if (!self->readable) {
724         err_mode(state, "reading");
725         return -1;
726     }
727     if (len == 0)
728         return 0;
729     if (len > BUFMAX) {
730         PyErr_Format(PyExc_ValueError, "cannot read more than %d bytes", BUFMAX);
731         return -1;
732     }
733 
734     HANDLE handle = _Py_get_osfhandle(self->fd);
735     if (handle == INVALID_HANDLE_VALUE)
736         return -1;
737 
738     /* Each character may take up to 4 bytes in the final buffer.
739        This is highly conservative, but necessary to avoid
740        failure for any given Unicode input (e.g. \U0010ffff).
741        If the caller requests fewer than 4 bytes, we buffer one
742        character.
743     */
744     DWORD wlen = (DWORD)(len / 4);
745     if (wlen == 0) {
746         wlen = 1;
747     }
748 
749     DWORD read_len = _copyfrombuf(self, buf, (DWORD)len);
750     if (read_len) {
751         buf = &buf[read_len];
752         len -= read_len;
753         wlen -= 1;
754     }
755     if (len == read_len || wlen == 0)
756         return read_len;
757 
758     DWORD n;
759     wchar_t *wbuf = read_console_w(handle, wlen, &n);
760     if (wbuf == NULL)
761         return -1;
762     if (n == 0) {
763         PyMem_Free(wbuf);
764         return read_len;
765     }
766 
767     int err = 0;
768     DWORD u8n = 0;
769 
770     Py_BEGIN_ALLOW_THREADS
771     if (len < 4) {
772         if (WideCharToMultiByte(CP_UTF8, 0, wbuf, n,
773                 self->buf, sizeof(self->buf) / sizeof(self->buf[0]),
774                 NULL, NULL))
775             u8n = _copyfrombuf(self, buf, (DWORD)len);
776     } else {
777         u8n = WideCharToMultiByte(CP_UTF8, 0, wbuf, n,
778             buf, (DWORD)len, NULL, NULL);
779     }
780 
781     if (u8n) {
782         read_len += u8n;
783         u8n = 0;
784     } else {
785         err = GetLastError();
786         if (err == ERROR_INSUFFICIENT_BUFFER) {
787             /* Calculate the needed buffer for a more useful error, as this
788                 means our "/ 4" logic above is insufficient for some input.
789             */
790             u8n = WideCharToMultiByte(CP_UTF8, 0, wbuf, n,
791                 NULL, 0, NULL, NULL);
792         }
793     }
794     Py_END_ALLOW_THREADS
795 
796     PyMem_Free(wbuf);
797 
798     if (u8n) {
799         PyErr_Format(PyExc_SystemError,
800             "Buffer had room for %zd bytes but %u bytes required",
801             len, u8n);
802         return -1;
803     }
804     if (err) {
805         PyErr_SetFromWindowsErr(err);
806         return -1;
807     }
808 
809     return read_len;
810 }
811 
812 /*[clinic input]
813 _io._WindowsConsoleIO.readinto
814     cls: defining_class
815     buffer: Py_buffer(accept={rwbuffer})
816     /
817 
818 Same as RawIOBase.readinto().
819 [clinic start generated code]*/
820 
821 static PyObject *
_io__WindowsConsoleIO_readinto_impl(winconsoleio * self,PyTypeObject * cls,Py_buffer * buffer)822 _io__WindowsConsoleIO_readinto_impl(winconsoleio *self, PyTypeObject *cls,
823                                     Py_buffer *buffer)
824 /*[clinic end generated code: output=96717c74f6204b79 input=4b0627c3b1645f78]*/
825 {
826     _PyIO_State *state = get_io_state_by_cls(cls);
827     Py_ssize_t len = readinto(state, self, buffer->buf, buffer->len);
828     if (len < 0)
829         return NULL;
830 
831     return PyLong_FromSsize_t(len);
832 }
833 
834 static DWORD
new_buffersize(winconsoleio * self,DWORD currentsize)835 new_buffersize(winconsoleio *self, DWORD currentsize)
836 {
837     DWORD addend;
838 
839     /* Expand the buffer by an amount proportional to the current size,
840        giving us amortized linear-time behavior.  For bigger sizes, use a
841        less-than-double growth factor to avoid excessive allocation. */
842     if (currentsize > 65536)
843         addend = currentsize >> 3;
844     else
845         addend = 256 + currentsize;
846     if (addend < SMALLCHUNK)
847         /* Avoid tiny read() calls. */
848         addend = SMALLCHUNK;
849     return addend + currentsize;
850 }
851 
852 /*[clinic input]
853 _io._WindowsConsoleIO.readall
854 
855 Read all data from the console, returned as bytes.
856 
857 Return an empty bytes object at EOF.
858 [clinic start generated code]*/
859 
860 static PyObject *
_io__WindowsConsoleIO_readall_impl(winconsoleio * self)861 _io__WindowsConsoleIO_readall_impl(winconsoleio *self)
862 /*[clinic end generated code: output=e6d312c684f6e23b input=4024d649a1006e69]*/
863 {
864     wchar_t *buf;
865     DWORD bufsize, n, len = 0;
866     PyObject *bytes;
867     DWORD bytes_size, rn;
868     HANDLE handle;
869 
870     if (self->fd == -1)
871         return err_closed();
872 
873     handle = _Py_get_osfhandle(self->fd);
874     if (handle == INVALID_HANDLE_VALUE)
875         return NULL;
876 
877     bufsize = BUFSIZ;
878 
879     buf = (wchar_t*)PyMem_Malloc((bufsize + 1) * sizeof(wchar_t));
880     if (buf == NULL) {
881         PyErr_NoMemory();
882         return NULL;
883     }
884 
885     while (1) {
886         wchar_t *subbuf;
887 
888         if (len >= (Py_ssize_t)bufsize) {
889             DWORD newsize = new_buffersize(self, len);
890             if (newsize > BUFMAX)
891                 break;
892             if (newsize < bufsize) {
893                 PyErr_SetString(PyExc_OverflowError,
894                                 "unbounded read returned more bytes "
895                                 "than a Python bytes object can hold");
896                 PyMem_Free(buf);
897                 return NULL;
898             }
899             bufsize = newsize;
900 
901             wchar_t *tmp = PyMem_Realloc(buf,
902                                          (bufsize + 1) * sizeof(wchar_t));
903             if (tmp == NULL) {
904                 PyMem_Free(buf);
905                 PyErr_NoMemory();
906                 return NULL;
907             }
908             buf = tmp;
909         }
910 
911         subbuf = read_console_w(handle, bufsize - len, &n);
912 
913         if (subbuf == NULL) {
914             PyMem_Free(buf);
915             return NULL;
916         }
917 
918         if (n > 0)
919             wcsncpy_s(&buf[len], bufsize - len + 1, subbuf, n);
920 
921         PyMem_Free(subbuf);
922 
923         /* when the read is empty we break */
924         if (n == 0)
925             break;
926 
927         len += n;
928     }
929 
930     if (len == 0 && _buflen(self) == 0) {
931         /* when the result starts with ^Z we return an empty buffer */
932         PyMem_Free(buf);
933         return PyBytes_FromStringAndSize(NULL, 0);
934     }
935 
936     if (len) {
937         Py_BEGIN_ALLOW_THREADS
938         bytes_size = WideCharToMultiByte(CP_UTF8, 0, buf, len,
939             NULL, 0, NULL, NULL);
940         Py_END_ALLOW_THREADS
941 
942         if (!bytes_size) {
943             DWORD err = GetLastError();
944             PyMem_Free(buf);
945             return PyErr_SetFromWindowsErr(err);
946         }
947     } else {
948         bytes_size = 0;
949     }
950 
951     bytes_size += _buflen(self);
952     bytes = PyBytes_FromStringAndSize(NULL, bytes_size);
953     rn = _copyfrombuf(self, PyBytes_AS_STRING(bytes), bytes_size);
954 
955     if (len) {
956         Py_BEGIN_ALLOW_THREADS
957         bytes_size = WideCharToMultiByte(CP_UTF8, 0, buf, len,
958             &PyBytes_AS_STRING(bytes)[rn], bytes_size - rn, NULL, NULL);
959         Py_END_ALLOW_THREADS
960 
961         if (!bytes_size) {
962             DWORD err = GetLastError();
963             PyMem_Free(buf);
964             Py_CLEAR(bytes);
965             return PyErr_SetFromWindowsErr(err);
966         }
967 
968         /* add back the number of preserved bytes */
969         bytes_size += rn;
970     }
971 
972     PyMem_Free(buf);
973     if (bytes_size < (size_t)PyBytes_GET_SIZE(bytes)) {
974         if (_PyBytes_Resize(&bytes, n * sizeof(wchar_t)) < 0) {
975             Py_CLEAR(bytes);
976             return NULL;
977         }
978     }
979     return bytes;
980 }
981 
982 /*[clinic input]
983 _io._WindowsConsoleIO.read
984     cls: defining_class
985     size: Py_ssize_t(accept={int, NoneType}) = -1
986     /
987 
988 Read at most size bytes, returned as bytes.
989 
990 Only makes one system call when size is a positive integer,
991 so less data may be returned than requested.
992 Return an empty bytes object at EOF.
993 [clinic start generated code]*/
994 
995 static PyObject *
_io__WindowsConsoleIO_read_impl(winconsoleio * self,PyTypeObject * cls,Py_ssize_t size)996 _io__WindowsConsoleIO_read_impl(winconsoleio *self, PyTypeObject *cls,
997                                 Py_ssize_t size)
998 /*[clinic end generated code: output=7e569a586537c0ae input=a14570a5da273365]*/
999 {
1000     PyObject *bytes;
1001     Py_ssize_t bytes_size;
1002 
1003     if (self->fd == -1)
1004         return err_closed();
1005     if (!self->readable) {
1006         _PyIO_State *state = get_io_state_by_cls(cls);
1007         return err_mode(state, "reading");
1008     }
1009 
1010     if (size < 0)
1011         return _io__WindowsConsoleIO_readall_impl(self);
1012     if (size > BUFMAX) {
1013         PyErr_Format(PyExc_ValueError, "cannot read more than %d bytes", BUFMAX);
1014         return NULL;
1015     }
1016 
1017     bytes = PyBytes_FromStringAndSize(NULL, size);
1018     if (bytes == NULL)
1019         return NULL;
1020 
1021     _PyIO_State *state = get_io_state_by_cls(cls);
1022     bytes_size = readinto(state, self, PyBytes_AS_STRING(bytes),
1023                           PyBytes_GET_SIZE(bytes));
1024     if (bytes_size < 0) {
1025         Py_CLEAR(bytes);
1026         return NULL;
1027     }
1028 
1029     if (bytes_size < PyBytes_GET_SIZE(bytes)) {
1030         if (_PyBytes_Resize(&bytes, bytes_size) < 0) {
1031             Py_CLEAR(bytes);
1032             return NULL;
1033         }
1034     }
1035 
1036     return bytes;
1037 }
1038 
1039 /*[clinic input]
1040 _io._WindowsConsoleIO.write
1041     cls: defining_class
1042     b: Py_buffer
1043     /
1044 
1045 Write buffer b to file, return number of bytes written.
1046 
1047 Only makes one system call, so not all of the data may be written.
1048 The number of bytes actually written is returned.
1049 [clinic start generated code]*/
1050 
1051 static PyObject *
_io__WindowsConsoleIO_write_impl(winconsoleio * self,PyTypeObject * cls,Py_buffer * b)1052 _io__WindowsConsoleIO_write_impl(winconsoleio *self, PyTypeObject *cls,
1053                                  Py_buffer *b)
1054 /*[clinic end generated code: output=e8019f480243cb29 input=10ac37c19339dfbe]*/
1055 {
1056     BOOL res = TRUE;
1057     wchar_t *wbuf;
1058     DWORD len, wlen, n = 0;
1059     HANDLE handle;
1060 
1061     if (self->fd == -1)
1062         return err_closed();
1063     if (!self->writable) {
1064         _PyIO_State *state = get_io_state_by_cls(cls);
1065         return err_mode(state, "writing");
1066     }
1067 
1068     handle = _Py_get_osfhandle(self->fd);
1069     if (handle == INVALID_HANDLE_VALUE)
1070         return NULL;
1071 
1072     if (!b->len) {
1073         return PyLong_FromLong(0);
1074     }
1075     if (b->len > BUFMAX)
1076         len = BUFMAX;
1077     else
1078         len = (DWORD)b->len;
1079 
1080     Py_BEGIN_ALLOW_THREADS
1081     /* issue11395 there is an unspecified upper bound on how many bytes
1082        can be written at once. We cap at 32k - the caller will have to
1083        handle partial writes.
1084        Since we don't know how many input bytes are being ignored, we
1085        have to reduce and recalculate. */
1086     const DWORD max_wlen = 32766 / sizeof(wchar_t);
1087     /* UTF-8 to wchar ratio is at most 3:1. */
1088     len = Py_MIN(len, max_wlen * 3);
1089     while (1) {
1090         /* Fix for github issues gh-110913 and gh-82052. */
1091         len = _find_last_utf8_boundary(b->buf, len);
1092         wlen = MultiByteToWideChar(CP_UTF8, 0, b->buf, len, NULL, 0);
1093         if (wlen <= max_wlen) {
1094             break;
1095         }
1096         len /= 2;
1097     }
1098     Py_END_ALLOW_THREADS
1099 
1100     if (!wlen) {
1101         return PyLong_FromLong(0);
1102     }
1103 
1104     wbuf = (wchar_t*)PyMem_Malloc(wlen * sizeof(wchar_t));
1105     if (!wbuf) {
1106         PyErr_NoMemory();
1107         return NULL;
1108     }
1109 
1110     Py_BEGIN_ALLOW_THREADS
1111     wlen = MultiByteToWideChar(CP_UTF8, 0, b->buf, len, wbuf, wlen);
1112     if (wlen) {
1113         res = WriteConsoleW(handle, wbuf, wlen, &n, NULL);
1114 #ifdef Py_DEBUG
1115         if (res) {
1116 #else
1117         if (res && n < wlen) {
1118 #endif
1119             /* Wrote fewer characters than expected, which means our
1120              * len value may be wrong. So recalculate it from the
1121              * characters that were written.
1122              */
1123             len = _wchar_to_utf8_count(b->buf, len, n);
1124         }
1125     } else
1126         res = 0;
1127     Py_END_ALLOW_THREADS
1128 
1129     if (!res) {
1130         DWORD err = GetLastError();
1131         PyMem_Free(wbuf);
1132         return PyErr_SetFromWindowsErr(err);
1133     }
1134 
1135     PyMem_Free(wbuf);
1136     return PyLong_FromSsize_t(len);
1137 }
1138 
1139 static PyObject *
1140 winconsoleio_repr(winconsoleio *self)
1141 {
1142     const char *type_name = (Py_TYPE((PyObject *)self)->tp_name);
1143 
1144     if (self->fd == -1) {
1145         return PyUnicode_FromFormat("<%.100s [closed]>", type_name);
1146     }
1147 
1148     if (self->readable) {
1149         return PyUnicode_FromFormat("<%.100s mode='rb' closefd=%s>",
1150                                     type_name,
1151                                     self->closefd ? "True" : "False");
1152     }
1153     if (self->writable) {
1154         return PyUnicode_FromFormat("<%.100s mode='wb' closefd=%s>",
1155                                     type_name,
1156                                     self->closefd ? "True" : "False");
1157     }
1158 
1159     PyErr_SetString(PyExc_SystemError, "_WindowsConsoleIO has invalid mode");
1160     return NULL;
1161 }
1162 
1163 /*[clinic input]
1164 _io._WindowsConsoleIO.isatty
1165 
1166 Always True.
1167 [clinic start generated code]*/
1168 
1169 static PyObject *
1170 _io__WindowsConsoleIO_isatty_impl(winconsoleio *self)
1171 /*[clinic end generated code: output=9eac09d287c11bd7 input=9b91591dbe356f86]*/
1172 {
1173     if (self->fd == -1)
1174         return err_closed();
1175 
1176     Py_RETURN_TRUE;
1177 }
1178 
1179 #define clinic_state() (find_io_state_by_def(Py_TYPE(self)))
1180 #include "clinic/winconsoleio.c.h"
1181 #undef clinic_state
1182 
1183 static PyMethodDef winconsoleio_methods[] = {
1184     _IO__WINDOWSCONSOLEIO_READ_METHODDEF
1185     _IO__WINDOWSCONSOLEIO_READALL_METHODDEF
1186     _IO__WINDOWSCONSOLEIO_READINTO_METHODDEF
1187     _IO__WINDOWSCONSOLEIO_WRITE_METHODDEF
1188     _IO__WINDOWSCONSOLEIO_CLOSE_METHODDEF
1189     _IO__WINDOWSCONSOLEIO_READABLE_METHODDEF
1190     _IO__WINDOWSCONSOLEIO_WRITABLE_METHODDEF
1191     _IO__WINDOWSCONSOLEIO_FILENO_METHODDEF
1192     _IO__WINDOWSCONSOLEIO_ISATTY_METHODDEF
1193     {NULL,           NULL}             /* sentinel */
1194 };
1195 
1196 /* 'closed' and 'mode' are attributes for compatibility with FileIO. */
1197 
1198 static PyObject *
1199 get_closed(winconsoleio *self, void *closure)
1200 {
1201     return PyBool_FromLong((long)(self->fd == -1));
1202 }
1203 
1204 static PyObject *
1205 get_closefd(winconsoleio *self, void *closure)
1206 {
1207     return PyBool_FromLong((long)(self->closefd));
1208 }
1209 
1210 static PyObject *
1211 get_mode(winconsoleio *self, void *closure)
1212 {
1213     return PyUnicode_FromString(self->readable ? "rb" : "wb");
1214 }
1215 
1216 static PyGetSetDef winconsoleio_getsetlist[] = {
1217     {"closed", (getter)get_closed, NULL, "True if the file is closed"},
1218     {"closefd", (getter)get_closefd, NULL,
1219         "True if the file descriptor will be closed by close()."},
1220     {"mode", (getter)get_mode, NULL, "String giving the file mode"},
1221     {NULL},
1222 };
1223 
1224 static PyMemberDef winconsoleio_members[] = {
1225     {"_blksize", Py_T_UINT, offsetof(winconsoleio, blksize), 0},
1226     {"_finalizing", Py_T_BOOL, offsetof(winconsoleio, finalizing), 0},
1227     {"__weaklistoffset__", Py_T_PYSSIZET, offsetof(winconsoleio, weakreflist), Py_READONLY},
1228     {"__dictoffset__", Py_T_PYSSIZET, offsetof(winconsoleio, dict), Py_READONLY},
1229     {NULL}
1230 };
1231 
1232 static PyType_Slot winconsoleio_slots[] = {
1233     {Py_tp_dealloc, winconsoleio_dealloc},
1234     {Py_tp_repr, winconsoleio_repr},
1235     {Py_tp_getattro, PyObject_GenericGetAttr},
1236     {Py_tp_doc, (void *)_io__WindowsConsoleIO___init____doc__},
1237     {Py_tp_traverse, winconsoleio_traverse},
1238     {Py_tp_clear, winconsoleio_clear},
1239     {Py_tp_methods, winconsoleio_methods},
1240     {Py_tp_members, winconsoleio_members},
1241     {Py_tp_getset, winconsoleio_getsetlist},
1242     {Py_tp_init, _io__WindowsConsoleIO___init__},
1243     {Py_tp_new, winconsoleio_new},
1244     {0, NULL},
1245 };
1246 
1247 PyType_Spec winconsoleio_spec = {
1248     .name = "_io._WindowsConsoleIO",
1249     .basicsize = sizeof(winconsoleio),
1250     .flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC |
1251               Py_TPFLAGS_IMMUTABLETYPE),
1252     .slots = winconsoleio_slots,
1253 };
1254 
1255 #endif /* HAVE_WINDOWS_CONSOLE_IO */
1256