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