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