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