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