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