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