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