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