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