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