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