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