blob: 30d1c767afcf74d99bd47e3c2da7b909975e63a8 [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))
Victor Stinner8c663fd2017-11-08 14:44:44 -080034#error "unreasonable BUFSIZ > 64 MiB defined"
Steve Dower39294992016-08-30 21:22:36 -070035#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;
Victor Stinner29adc132017-06-08 18:19:25 +0200104
Steve Dower722e3e22017-02-04 15:07:46 -0800105 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) {
Victor Stinner29adc132017-06-08 18:19:25 +0200301 PyObject *decodedname;
Steve Dower39294992016-08-30 21:22:36 -0700302
303 int d = PyUnicode_FSDecoder(nameobj, (void*)&decodedname);
304 if (!d)
305 return -1;
306
Serhiy Storchakae613e6a2017-06-27 16:03:14 +0300307 name = PyUnicode_AsWideCharString(decodedname, NULL);
Steve Dower27f26ad2016-09-17 13:51:23 -0700308 console_type = _PyIO_get_console_type(decodedname);
Steve Dower39294992016-08-30 21:22:36 -0700309 Py_CLEAR(decodedname);
310 if (name == NULL)
311 return -1;
Steve Dower39294992016-08-30 21:22:36 -0700312 }
313
314 s = mode;
315 while (*s) {
316 switch (*s++) {
317 case '+':
318 case 'a':
319 case 'b':
320 case 'x':
321 break;
322 case 'r':
323 if (rwa)
324 goto bad_mode;
325 rwa = 1;
326 self->readable = 1;
Steve Dower27f26ad2016-09-17 13:51:23 -0700327 if (console_type == 'x')
328 console_type = 'r';
Steve Dower39294992016-08-30 21:22:36 -0700329 break;
330 case 'w':
331 if (rwa)
332 goto bad_mode;
333 rwa = 1;
334 self->writable = 1;
Steve Dower27f26ad2016-09-17 13:51:23 -0700335 if (console_type == 'x')
336 console_type = 'w';
Steve Dower39294992016-08-30 21:22:36 -0700337 break;
338 default:
339 PyErr_Format(PyExc_ValueError,
340 "invalid mode: %.200s", mode);
341 goto error;
342 }
343 }
344
345 if (!rwa)
346 goto bad_mode;
347
348 if (fd >= 0) {
349 _Py_BEGIN_SUPPRESS_IPH
350 self->handle = (HANDLE)_get_osfhandle(fd);
351 _Py_END_SUPPRESS_IPH
352 self->closehandle = 0;
353 } else {
354 DWORD access = GENERIC_READ;
355
356 self->closehandle = 1;
357 if (!closefd) {
358 PyErr_SetString(PyExc_ValueError,
359 "Cannot use closefd=False with file name");
360 goto error;
361 }
362
363 if (self->writable)
Steve Dower27f26ad2016-09-17 13:51:23 -0700364 access = GENERIC_WRITE;
Steve Dower39294992016-08-30 21:22:36 -0700365
366 Py_BEGIN_ALLOW_THREADS
367 /* Attempt to open for read/write initially, then fall back
368 on the specific access. This is required for modern names
369 CONIN$ and CONOUT$, which allow reading/writing state as
370 well as reading/writing content. */
371 self->handle = CreateFileW(name, GENERIC_READ | GENERIC_WRITE,
372 FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
373 if (self->handle == INVALID_HANDLE_VALUE)
374 self->handle = CreateFileW(name, access,
375 FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
376 Py_END_ALLOW_THREADS
377
378 if (self->handle == INVALID_HANDLE_VALUE) {
379 PyErr_SetExcFromWindowsErrWithFilenameObject(PyExc_OSError, GetLastError(), nameobj);
380 goto error;
381 }
382 }
383
Steve Dower27f26ad2016-09-17 13:51:23 -0700384 if (console_type == '\0')
385 console_type = _get_console_type(self->handle);
386
387 if (self->writable && console_type != 'w') {
Steve Dower39294992016-08-30 21:22:36 -0700388 PyErr_SetString(PyExc_ValueError,
389 "Cannot open console input buffer for writing");
390 goto error;
391 }
Steve Dower27f26ad2016-09-17 13:51:23 -0700392 if (self->readable && console_type != 'r') {
Steve Dower39294992016-08-30 21:22:36 -0700393 PyErr_SetString(PyExc_ValueError,
394 "Cannot open console output buffer for reading");
395 goto error;
396 }
397
398 self->blksize = DEFAULT_BUFFER_SIZE;
399 memset(self->buf, 0, 4);
400
401 if (_PyObject_SetAttrId((PyObject *)self, &PyId_name, nameobj) < 0)
402 goto error;
403
404 goto done;
405
406bad_mode:
407 PyErr_SetString(PyExc_ValueError,
408 "Must have exactly one of read or write mode");
409error:
410 ret = -1;
411 internal_close(self);
412
413done:
414 if (name)
415 PyMem_Free(name);
416 return ret;
417}
418
419static int
420winconsoleio_traverse(winconsoleio *self, visitproc visit, void *arg)
421{
422 Py_VISIT(self->dict);
423 return 0;
424}
425
426static int
427winconsoleio_clear(winconsoleio *self)
428{
429 Py_CLEAR(self->dict);
430 return 0;
431}
432
433static void
434winconsoleio_dealloc(winconsoleio *self)
435{
436 self->finalizing = 1;
437 if (_PyIOBase_finalize((PyObject *) self) < 0)
438 return;
439 _PyObject_GC_UNTRACK(self);
440 if (self->weakreflist != NULL)
441 PyObject_ClearWeakRefs((PyObject *) self);
442 Py_CLEAR(self->dict);
443 Py_TYPE(self)->tp_free((PyObject *)self);
444}
445
446static PyObject *
447err_closed(void)
448{
449 PyErr_SetString(PyExc_ValueError, "I/O operation on closed file");
450 return NULL;
451}
452
453static PyObject *
454err_mode(const char *action)
455{
456 _PyIO_State *state = IO_STATE();
457 if (state != NULL)
458 PyErr_Format(state->unsupported_operation,
459 "Console buffer does not support %s", action);
460 return NULL;
461}
462
463/*[clinic input]
464_io._WindowsConsoleIO.fileno
465
466Return the underlying file descriptor (an integer).
467
468fileno is only set when a file descriptor is used to open
469one of the standard streams.
470
471[clinic start generated code]*/
472
473static PyObject *
474_io__WindowsConsoleIO_fileno_impl(winconsoleio *self)
475/*[clinic end generated code: output=006fa74ce3b5cfbf input=079adc330ddaabe6]*/
476{
477 if (self->fd < 0 && self->handle != INVALID_HANDLE_VALUE) {
478 _Py_BEGIN_SUPPRESS_IPH
479 if (self->writable)
Steve Dower27f26ad2016-09-17 13:51:23 -0700480 self->fd = _open_osfhandle((intptr_t)self->handle, _O_WRONLY | _O_BINARY);
Steve Dower39294992016-08-30 21:22:36 -0700481 else
Steve Dower27f26ad2016-09-17 13:51:23 -0700482 self->fd = _open_osfhandle((intptr_t)self->handle, _O_RDONLY | _O_BINARY);
Steve Dower39294992016-08-30 21:22:36 -0700483 _Py_END_SUPPRESS_IPH
484 }
485 if (self->fd < 0)
486 return err_mode("fileno");
487 return PyLong_FromLong(self->fd);
488}
489
490/*[clinic input]
491_io._WindowsConsoleIO.readable
492
493True if console is an input buffer.
494[clinic start generated code]*/
495
496static PyObject *
497_io__WindowsConsoleIO_readable_impl(winconsoleio *self)
498/*[clinic end generated code: output=daf9cef2743becf0 input=6be9defb5302daae]*/
499{
500 if (self->handle == INVALID_HANDLE_VALUE)
501 return err_closed();
502 return PyBool_FromLong((long) self->readable);
503}
504
505/*[clinic input]
506_io._WindowsConsoleIO.writable
507
508True if console is an output buffer.
509[clinic start generated code]*/
510
511static PyObject *
512_io__WindowsConsoleIO_writable_impl(winconsoleio *self)
513/*[clinic end generated code: output=e0a2ad7eae5abf67 input=cefbd8abc24df6a0]*/
514{
515 if (self->handle == INVALID_HANDLE_VALUE)
516 return err_closed();
517 return PyBool_FromLong((long) self->writable);
518}
519
520static DWORD
521_buflen(winconsoleio *self)
522{
Steve Dower312cef72016-10-03 09:04:58 -0700523 for (DWORD i = 0; i < SMALLBUF; ++i) {
Steve Dower39294992016-08-30 21:22:36 -0700524 if (!self->buf[i])
525 return i;
526 }
Steve Dower312cef72016-10-03 09:04:58 -0700527 return SMALLBUF;
Steve Dower39294992016-08-30 21:22:36 -0700528}
529
530static DWORD
531_copyfrombuf(winconsoleio *self, char *buf, DWORD len)
532{
533 DWORD n = 0;
534
535 while (self->buf[0] && len--) {
Steve Dower312cef72016-10-03 09:04:58 -0700536 buf[n++] = self->buf[0];
537 for (int i = 1; i < SMALLBUF; ++i)
538 self->buf[i - 1] = self->buf[i];
539 self->buf[SMALLBUF - 1] = 0;
Steve Dower39294992016-08-30 21:22:36 -0700540 }
541
542 return n;
543}
544
545static wchar_t *
546read_console_w(HANDLE handle, DWORD maxlen, DWORD *readlen) {
547 int err = 0, sig = 0;
548
549 wchar_t *buf = (wchar_t*)PyMem_Malloc(maxlen * sizeof(wchar_t));
550 if (!buf)
551 goto error;
Steve Dower312cef72016-10-03 09:04:58 -0700552
Steve Dower39294992016-08-30 21:22:36 -0700553 *readlen = 0;
554
Steve Dower312cef72016-10-03 09:04:58 -0700555 //DebugBreak();
Steve Dower39294992016-08-30 21:22:36 -0700556 Py_BEGIN_ALLOW_THREADS
Steve Dower312cef72016-10-03 09:04:58 -0700557 DWORD off = 0;
558 while (off < maxlen) {
Steve Dower39294992016-08-30 21:22:36 -0700559 DWORD n, len = min(maxlen - off, BUFSIZ);
560 SetLastError(0);
561 BOOL res = ReadConsoleW(handle, &buf[off], len, &n, NULL);
562
563 if (!res) {
564 err = GetLastError();
565 break;
566 }
567 if (n == 0) {
568 err = GetLastError();
569 if (err != ERROR_OPERATION_ABORTED)
570 break;
571 err = 0;
572 HANDLE hInterruptEvent = _PyOS_SigintEvent();
573 if (WaitForSingleObjectEx(hInterruptEvent, 100, FALSE)
Steve Dower312cef72016-10-03 09:04:58 -0700574 == WAIT_OBJECT_0) {
Steve Dower39294992016-08-30 21:22:36 -0700575 ResetEvent(hInterruptEvent);
576 Py_BLOCK_THREADS
577 sig = PyErr_CheckSignals();
578 Py_UNBLOCK_THREADS
579 if (sig < 0)
580 break;
581 }
582 }
583 *readlen += n;
584
585 /* If we didn't read a full buffer that time, don't try
586 again or we will block a second time. */
587 if (n < len)
588 break;
589 /* If the buffer ended with a newline, break out */
590 if (buf[*readlen - 1] == '\n')
591 break;
Steve Dower312cef72016-10-03 09:04:58 -0700592 /* If the buffer ends with a high surrogate, expand the
593 buffer and read an extra character. */
594 WORD char_type;
595 if (off + BUFSIZ >= maxlen &&
596 GetStringTypeW(CT_CTYPE3, &buf[*readlen - 1], 1, &char_type) &&
597 char_type == C3_HIGHSURROGATE) {
598 wchar_t *newbuf;
599 maxlen += 1;
600 Py_BLOCK_THREADS
601 newbuf = (wchar_t*)PyMem_Realloc(buf, maxlen * sizeof(wchar_t));
602 Py_UNBLOCK_THREADS
603 if (!newbuf) {
604 sig = -1;
605 break;
606 }
607 buf = newbuf;
608 /* Only advance by n and not BUFSIZ in this case */
609 off += n;
610 continue;
611 }
612
613 off += BUFSIZ;
Steve Dower39294992016-08-30 21:22:36 -0700614 }
Steve Dower312cef72016-10-03 09:04:58 -0700615
Steve Dower39294992016-08-30 21:22:36 -0700616 Py_END_ALLOW_THREADS
617
618 if (sig)
619 goto error;
620 if (err) {
621 PyErr_SetFromWindowsErr(err);
622 goto error;
623 }
624
625 if (*readlen > 0 && buf[0] == L'\x1a') {
626 PyMem_Free(buf);
627 buf = (wchar_t *)PyMem_Malloc(sizeof(wchar_t));
628 if (!buf)
629 goto error;
630 buf[0] = L'\0';
631 *readlen = 0;
632 }
633
634 return buf;
635
636error:
637 if (buf)
638 PyMem_Free(buf);
639 return NULL;
640}
641
642
643static Py_ssize_t
644readinto(winconsoleio *self, char *buf, Py_ssize_t len)
645{
646 if (self->handle == INVALID_HANDLE_VALUE) {
647 err_closed();
648 return -1;
649 }
650 if (!self->readable) {
651 err_mode("reading");
652 return -1;
653 }
654 if (len == 0)
655 return 0;
656 if (len > BUFMAX) {
657 PyErr_Format(PyExc_ValueError, "cannot read more than %d bytes", BUFMAX);
658 return -1;
659 }
660
661 /* Each character may take up to 4 bytes in the final buffer.
662 This is highly conservative, but necessary to avoid
663 failure for any given Unicode input (e.g. \U0010ffff).
664 If the caller requests fewer than 4 bytes, we buffer one
665 character.
666 */
667 DWORD wlen = (DWORD)(len / 4);
668 if (wlen == 0) {
669 wlen = 1;
670 }
671
672 DWORD read_len = _copyfrombuf(self, buf, (DWORD)len);
673 if (read_len) {
674 buf = &buf[read_len];
675 len -= read_len;
676 wlen -= 1;
677 }
678 if (len == read_len || wlen == 0)
679 return read_len;
680
681 DWORD n;
682 wchar_t *wbuf = read_console_w(self->handle, wlen, &n);
683 if (wbuf == NULL)
684 return -1;
685 if (n == 0) {
686 PyMem_Free(wbuf);
687 return read_len;
688 }
689
690 int err = 0;
691 DWORD u8n = 0;
692
693 Py_BEGIN_ALLOW_THREADS
694 if (len < 4) {
695 if (WideCharToMultiByte(CP_UTF8, 0, wbuf, n,
696 self->buf, sizeof(self->buf) / sizeof(self->buf[0]),
697 NULL, NULL))
698 u8n = _copyfrombuf(self, buf, (DWORD)len);
699 } else {
700 u8n = WideCharToMultiByte(CP_UTF8, 0, wbuf, n,
701 buf, (DWORD)len, NULL, NULL);
702 }
703
704 if (u8n) {
705 read_len += u8n;
706 u8n = 0;
707 } else {
708 err = GetLastError();
709 if (err == ERROR_INSUFFICIENT_BUFFER) {
710 /* Calculate the needed buffer for a more useful error, as this
711 means our "/ 4" logic above is insufficient for some input.
712 */
713 u8n = WideCharToMultiByte(CP_UTF8, 0, wbuf, n,
714 NULL, 0, NULL, NULL);
715 }
716 }
717 Py_END_ALLOW_THREADS
718
719 PyMem_Free(wbuf);
720
721 if (u8n) {
722 PyErr_Format(PyExc_SystemError,
723 "Buffer had room for %d bytes but %d bytes required",
724 len, u8n);
725 return -1;
726 }
727 if (err) {
728 PyErr_SetFromWindowsErr(err);
729 return -1;
730 }
731
732 return read_len;
733}
734
735/*[clinic input]
736_io._WindowsConsoleIO.readinto
737 buffer: Py_buffer(accept={rwbuffer})
738 /
739
740Same as RawIOBase.readinto().
741[clinic start generated code]*/
742
743static PyObject *
744_io__WindowsConsoleIO_readinto_impl(winconsoleio *self, Py_buffer *buffer)
745/*[clinic end generated code: output=66d1bdfa3f20af39 input=4ed68da48a6baffe]*/
746{
747 Py_ssize_t len = readinto(self, buffer->buf, buffer->len);
748 if (len < 0)
749 return NULL;
750
751 return PyLong_FromSsize_t(len);
752}
753
754static DWORD
755new_buffersize(winconsoleio *self, DWORD currentsize)
756{
757 DWORD addend;
758
759 /* Expand the buffer by an amount proportional to the current size,
760 giving us amortized linear-time behavior. For bigger sizes, use a
761 less-than-double growth factor to avoid excessive allocation. */
762 if (currentsize > 65536)
763 addend = currentsize >> 3;
764 else
765 addend = 256 + currentsize;
766 if (addend < SMALLCHUNK)
767 /* Avoid tiny read() calls. */
768 addend = SMALLCHUNK;
769 return addend + currentsize;
770}
771
772/*[clinic input]
773_io._WindowsConsoleIO.readall
774
775Read all data from the console, returned as bytes.
776
777Return an empty bytes object at EOF.
778[clinic start generated code]*/
779
780static PyObject *
781_io__WindowsConsoleIO_readall_impl(winconsoleio *self)
782/*[clinic end generated code: output=e6d312c684f6e23b input=4024d649a1006e69]*/
783{
784 wchar_t *buf;
785 DWORD bufsize, n, len = 0;
786 PyObject *bytes;
787 DWORD bytes_size, rn;
788
789 if (self->handle == INVALID_HANDLE_VALUE)
790 return err_closed();
791
792 bufsize = BUFSIZ;
793
794 buf = (wchar_t*)PyMem_Malloc((bufsize + 1) * sizeof(wchar_t));
795 if (buf == NULL)
796 return NULL;
797
798 while (1) {
799 wchar_t *subbuf;
800
801 if (len >= (Py_ssize_t)bufsize) {
802 DWORD newsize = new_buffersize(self, len);
803 if (newsize > BUFMAX)
804 break;
805 if (newsize < bufsize) {
806 PyErr_SetString(PyExc_OverflowError,
807 "unbounded read returned more bytes "
808 "than a Python bytes object can hold");
809 PyMem_Free(buf);
810 return NULL;
811 }
812 bufsize = newsize;
813
814 buf = PyMem_Realloc(buf, (bufsize + 1) * sizeof(wchar_t));
815 if (!buf) {
816 PyMem_Free(buf);
817 return NULL;
818 }
819 }
820
821 subbuf = read_console_w(self->handle, bufsize - len, &n);
822
823 if (subbuf == NULL) {
824 PyMem_Free(buf);
825 return NULL;
826 }
827
828 if (n > 0)
829 wcsncpy_s(&buf[len], bufsize - len + 1, subbuf, n);
830
831 PyMem_Free(subbuf);
832
Steve Dowerc6f9b2b2016-10-08 12:37:33 -0700833 /* when the read is empty we break */
834 if (n == 0)
Steve Dower39294992016-08-30 21:22:36 -0700835 break;
836
837 len += n;
838 }
839
Steve Dowerc6f9b2b2016-10-08 12:37:33 -0700840 if (len == 0 && _buflen(self) == 0) {
Steve Dower39294992016-08-30 21:22:36 -0700841 /* when the result starts with ^Z we return an empty buffer */
842 PyMem_Free(buf);
843 return PyBytes_FromStringAndSize(NULL, 0);
844 }
845
Steve Dowerc6f9b2b2016-10-08 12:37:33 -0700846 if (len) {
847 Py_BEGIN_ALLOW_THREADS
848 bytes_size = WideCharToMultiByte(CP_UTF8, 0, buf, len,
849 NULL, 0, NULL, NULL);
850 Py_END_ALLOW_THREADS
Benjamin Petersone2e792d2016-09-19 22:17:16 -0700851
Steve Dowerc6f9b2b2016-10-08 12:37:33 -0700852 if (!bytes_size) {
853 DWORD err = GetLastError();
854 PyMem_Free(buf);
855 return PyErr_SetFromWindowsErr(err);
856 }
857 } else {
858 bytes_size = 0;
Steve Dower39294992016-08-30 21:22:36 -0700859 }
860
861 bytes_size += _buflen(self);
862 bytes = PyBytes_FromStringAndSize(NULL, bytes_size);
863 rn = _copyfrombuf(self, PyBytes_AS_STRING(bytes), bytes_size);
864
Steve Dowerc6f9b2b2016-10-08 12:37:33 -0700865 if (len) {
866 Py_BEGIN_ALLOW_THREADS
867 bytes_size = WideCharToMultiByte(CP_UTF8, 0, buf, len,
868 &PyBytes_AS_STRING(bytes)[rn], bytes_size - rn, NULL, NULL);
869 Py_END_ALLOW_THREADS
Steve Dower39294992016-08-30 21:22:36 -0700870
Steve Dowerc6f9b2b2016-10-08 12:37:33 -0700871 if (!bytes_size) {
872 DWORD err = GetLastError();
873 PyMem_Free(buf);
874 Py_CLEAR(bytes);
875 return PyErr_SetFromWindowsErr(err);
876 }
877
878 /* add back the number of preserved bytes */
879 bytes_size += rn;
Steve Dower39294992016-08-30 21:22:36 -0700880 }
881
882 PyMem_Free(buf);
883 if (bytes_size < (size_t)PyBytes_GET_SIZE(bytes)) {
884 if (_PyBytes_Resize(&bytes, n * sizeof(wchar_t)) < 0) {
885 Py_CLEAR(bytes);
886 return NULL;
887 }
888 }
889 return bytes;
890}
891
892/*[clinic input]
893_io._WindowsConsoleIO.read
Serhiy Storchaka762bf402017-03-30 09:15:31 +0300894 size: Py_ssize_t(accept={int, NoneType}) = -1
Steve Dower39294992016-08-30 21:22:36 -0700895 /
896
897Read at most size bytes, returned as bytes.
898
899Only makes one system call when size is a positive integer,
900so less data may be returned than requested.
901Return an empty bytes object at EOF.
902[clinic start generated code]*/
903
904static PyObject *
905_io__WindowsConsoleIO_read_impl(winconsoleio *self, Py_ssize_t size)
Serhiy Storchaka762bf402017-03-30 09:15:31 +0300906/*[clinic end generated code: output=57df68af9f4b22d0 input=8bc73bc15d0fa072]*/
Steve Dower39294992016-08-30 21:22:36 -0700907{
908 PyObject *bytes;
909 Py_ssize_t bytes_size;
Benjamin Petersone2e792d2016-09-19 22:17:16 -0700910
Steve Dower39294992016-08-30 21:22:36 -0700911 if (self->handle == INVALID_HANDLE_VALUE)
912 return err_closed();
913 if (!self->readable)
914 return err_mode("reading");
915
916 if (size < 0)
917 return _io__WindowsConsoleIO_readall_impl(self);
918 if (size > BUFMAX) {
919 PyErr_Format(PyExc_ValueError, "cannot read more than %d bytes", BUFMAX);
920 return NULL;
921 }
922
923 bytes = PyBytes_FromStringAndSize(NULL, size);
924 if (bytes == NULL)
925 return NULL;
926
927 bytes_size = readinto(self, PyBytes_AS_STRING(bytes), PyBytes_GET_SIZE(bytes));
928 if (bytes_size < 0) {
929 Py_CLEAR(bytes);
930 return NULL;
931 }
932
933 if (bytes_size < PyBytes_GET_SIZE(bytes)) {
934 if (_PyBytes_Resize(&bytes, bytes_size) < 0) {
935 Py_CLEAR(bytes);
936 return NULL;
937 }
938 }
939
940 return bytes;
941}
942
943/*[clinic input]
944_io._WindowsConsoleIO.write
945 b: Py_buffer
946 /
947
948Write buffer b to file, return number of bytes written.
949
950Only makes one system call, so not all of the data may be written.
951The number of bytes actually written is returned.
952[clinic start generated code]*/
953
954static PyObject *
955_io__WindowsConsoleIO_write_impl(winconsoleio *self, Py_buffer *b)
956/*[clinic end generated code: output=775bdb16fbf9137b input=be35fb624f97c941]*/
957{
958 BOOL res = TRUE;
959 wchar_t *wbuf;
960 DWORD len, wlen, n = 0;
961
962 if (self->handle == INVALID_HANDLE_VALUE)
963 return err_closed();
964 if (!self->writable)
965 return err_mode("writing");
966
967 if (b->len > BUFMAX)
968 len = BUFMAX;
969 else
970 len = (DWORD)b->len;
971
972 Py_BEGIN_ALLOW_THREADS
973 wlen = MultiByteToWideChar(CP_UTF8, 0, b->buf, len, NULL, 0);
974
975 /* issue11395 there is an unspecified upper bound on how many bytes
976 can be written at once. We cap at 32k - the caller will have to
977 handle partial writes.
978 Since we don't know how many input bytes are being ignored, we
979 have to reduce and recalculate. */
980 while (wlen > 32766 / sizeof(wchar_t)) {
981 len /= 2;
982 wlen = MultiByteToWideChar(CP_UTF8, 0, b->buf, len, NULL, 0);
983 }
984 Py_END_ALLOW_THREADS
Benjamin Petersone2e792d2016-09-19 22:17:16 -0700985
Steve Dower39294992016-08-30 21:22:36 -0700986 if (!wlen)
987 return PyErr_SetFromWindowsErr(0);
988
989 wbuf = (wchar_t*)PyMem_Malloc(wlen * sizeof(wchar_t));
990
991 Py_BEGIN_ALLOW_THREADS
992 wlen = MultiByteToWideChar(CP_UTF8, 0, b->buf, len, wbuf, wlen);
993 if (wlen) {
994 res = WriteConsoleW(self->handle, wbuf, wlen, &n, NULL);
Segev Finer523776c2017-06-02 19:26:01 +0300995 if (res && n < wlen) {
Steve Dower39294992016-08-30 21:22:36 -0700996 /* Wrote fewer characters than expected, which means our
997 * len value may be wrong. So recalculate it from the
998 * characters that were written. As this could potentially
999 * result in a different value, we also validate that value.
1000 */
1001 len = WideCharToMultiByte(CP_UTF8, 0, wbuf, n,
1002 NULL, 0, NULL, NULL);
1003 if (len) {
1004 wlen = MultiByteToWideChar(CP_UTF8, 0, b->buf, len,
1005 NULL, 0);
1006 assert(wlen == len);
1007 }
1008 }
1009 } else
1010 res = 0;
1011 Py_END_ALLOW_THREADS
Benjamin Petersone2e792d2016-09-19 22:17:16 -07001012
Steve Dower39294992016-08-30 21:22:36 -07001013 if (!res) {
1014 DWORD err = GetLastError();
1015 PyMem_Free(wbuf);
1016 return PyErr_SetFromWindowsErr(err);
1017 }
1018
1019 PyMem_Free(wbuf);
1020 return PyLong_FromSsize_t(len);
1021}
1022
1023static PyObject *
1024winconsoleio_repr(winconsoleio *self)
1025{
1026 if (self->handle == INVALID_HANDLE_VALUE)
1027 return PyUnicode_FromFormat("<_io._WindowsConsoleIO [closed]>");
1028
1029 if (self->readable)
1030 return PyUnicode_FromFormat("<_io._WindowsConsoleIO mode='rb' closefd=%s>",
1031 self->closehandle ? "True" : "False");
1032 if (self->writable)
1033 return PyUnicode_FromFormat("<_io._WindowsConsoleIO mode='wb' closefd=%s>",
1034 self->closehandle ? "True" : "False");
1035
1036 PyErr_SetString(PyExc_SystemError, "_WindowsConsoleIO has invalid mode");
1037 return NULL;
1038}
1039
1040/*[clinic input]
1041_io._WindowsConsoleIO.isatty
1042
1043Always True.
1044[clinic start generated code]*/
1045
1046static PyObject *
1047_io__WindowsConsoleIO_isatty_impl(winconsoleio *self)
1048/*[clinic end generated code: output=9eac09d287c11bd7 input=9b91591dbe356f86]*/
1049{
1050 if (self->handle == INVALID_HANDLE_VALUE)
1051 return err_closed();
Benjamin Petersone2e792d2016-09-19 22:17:16 -07001052
Steve Dower39294992016-08-30 21:22:36 -07001053 Py_RETURN_TRUE;
1054}
1055
1056static PyObject *
1057winconsoleio_getstate(winconsoleio *self)
1058{
1059 PyErr_Format(PyExc_TypeError,
1060 "cannot serialize '%s' object", Py_TYPE(self)->tp_name);
1061 return NULL;
1062}
1063
1064#include "clinic/winconsoleio.c.h"
1065
1066static PyMethodDef winconsoleio_methods[] = {
1067 _IO__WINDOWSCONSOLEIO_READ_METHODDEF
1068 _IO__WINDOWSCONSOLEIO_READALL_METHODDEF
1069 _IO__WINDOWSCONSOLEIO_READINTO_METHODDEF
1070 _IO__WINDOWSCONSOLEIO_WRITE_METHODDEF
1071 _IO__WINDOWSCONSOLEIO_CLOSE_METHODDEF
1072 _IO__WINDOWSCONSOLEIO_READABLE_METHODDEF
1073 _IO__WINDOWSCONSOLEIO_WRITABLE_METHODDEF
1074 _IO__WINDOWSCONSOLEIO_FILENO_METHODDEF
1075 _IO__WINDOWSCONSOLEIO_ISATTY_METHODDEF
1076 {"__getstate__", (PyCFunction)winconsoleio_getstate, METH_NOARGS, NULL},
1077 {NULL, NULL} /* sentinel */
1078};
1079
1080/* 'closed' and 'mode' are attributes for compatibility with FileIO. */
1081
1082static PyObject *
1083get_closed(winconsoleio *self, void *closure)
1084{
1085 return PyBool_FromLong((long)(self->handle == INVALID_HANDLE_VALUE));
1086}
1087
1088static PyObject *
1089get_closefd(winconsoleio *self, void *closure)
1090{
1091 return PyBool_FromLong((long)(self->closehandle));
1092}
1093
1094static PyObject *
1095get_mode(winconsoleio *self, void *closure)
1096{
1097 return PyUnicode_FromString(self->readable ? "rb" : "wb");
1098}
1099
1100static PyGetSetDef winconsoleio_getsetlist[] = {
1101 {"closed", (getter)get_closed, NULL, "True if the file is closed"},
1102 {"closefd", (getter)get_closefd, NULL,
1103 "True if the file descriptor will be closed by close()."},
1104 {"mode", (getter)get_mode, NULL, "String giving the file mode"},
1105 {NULL},
1106};
1107
1108static PyMemberDef winconsoleio_members[] = {
1109 {"_blksize", T_UINT, offsetof(winconsoleio, blksize), 0},
1110 {"_finalizing", T_BOOL, offsetof(winconsoleio, finalizing), 0},
1111 {NULL}
1112};
1113
1114PyTypeObject PyWindowsConsoleIO_Type = {
1115 PyVarObject_HEAD_INIT(NULL, 0)
1116 "_io._WindowsConsoleIO",
1117 sizeof(winconsoleio),
1118 0,
1119 (destructor)winconsoleio_dealloc, /* tp_dealloc */
1120 0, /* tp_print */
1121 0, /* tp_getattr */
1122 0, /* tp_setattr */
1123 0, /* tp_reserved */
1124 (reprfunc)winconsoleio_repr, /* tp_repr */
1125 0, /* tp_as_number */
1126 0, /* tp_as_sequence */
1127 0, /* tp_as_mapping */
1128 0, /* tp_hash */
1129 0, /* tp_call */
1130 0, /* tp_str */
1131 PyObject_GenericGetAttr, /* tp_getattro */
1132 0, /* tp_setattro */
1133 0, /* tp_as_buffer */
1134 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE
1135 | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_HAVE_FINALIZE, /* tp_flags */
1136 _io__WindowsConsoleIO___init____doc__, /* tp_doc */
1137 (traverseproc)winconsoleio_traverse, /* tp_traverse */
1138 (inquiry)winconsoleio_clear, /* tp_clear */
1139 0, /* tp_richcompare */
1140 offsetof(winconsoleio, weakreflist), /* tp_weaklistoffset */
1141 0, /* tp_iter */
1142 0, /* tp_iternext */
1143 winconsoleio_methods, /* tp_methods */
1144 winconsoleio_members, /* tp_members */
1145 winconsoleio_getsetlist, /* tp_getset */
1146 0, /* tp_base */
1147 0, /* tp_dict */
1148 0, /* tp_descr_get */
1149 0, /* tp_descr_set */
1150 offsetof(winconsoleio, dict), /* tp_dictoffset */
1151 _io__WindowsConsoleIO___init__, /* tp_init */
1152 PyType_GenericAlloc, /* tp_alloc */
1153 winconsoleio_new, /* tp_new */
1154 PyObject_GC_Del, /* tp_free */
1155 0, /* tp_is_gc */
1156 0, /* tp_bases */
1157 0, /* tp_mro */
1158 0, /* tp_cache */
1159 0, /* tp_subclasses */
1160 0, /* tp_weaklist */
1161 0, /* tp_del */
1162 0, /* tp_version_tag */
1163 0, /* tp_finalize */
1164};
1165
Steve Dower312cef72016-10-03 09:04:58 -07001166PyAPI_DATA(PyObject *) _PyWindowsConsoleIO_Type = (PyObject*)&PyWindowsConsoleIO_Type;
1167
Steve Dower39294992016-08-30 21:22:36 -07001168#endif /* MS_WINDOWS */