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