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