summaryrefslogtreecommitdiffstats
path: root/Python/fileutils.c
diff options
context:
space:
mode:
authorRayyan Ansari <rayyan@ansari.sh>2023-02-16 14:52:24 (GMT)
committerGitHub <noreply@github.com>2023-02-16 14:52:24 (GMT)
commit739c026f4488bd2e37d500a2c3d948aaf929b641 (patch)
tree733937b9fe265101f480e7a2480d427c1909cbac /Python/fileutils.c
parent36b139af638cdeb671cb6b8b0315b254148688f7 (diff)
downloadcpython-739c026f4488bd2e37d500a2c3d948aaf929b641.zip
cpython-739c026f4488bd2e37d500a2c3d948aaf929b641.tar.gz
cpython-739c026f4488bd2e37d500a2c3d948aaf929b641.tar.bz2
gh-101881: Support (non-)blocking read/write functions on Windows pipes (GH-101882)
* fileutils: handle non-blocking pipe IO on Windows Handle erroring operations on non-blocking pipes by reading the _doserrno code. Limit writes on non-blocking pipes that are too large. * Support blocking functions on Windows Use the GetNamedPipeHandleState and SetNamedPipeHandleState Win32 API functions to add support for os.get_blocking and os.set_blocking.
Diffstat (limited to 'Python/fileutils.c')
-rw-r--r--Python/fileutils.c93
1 files changed, 91 insertions, 2 deletions
diff --git a/Python/fileutils.c b/Python/fileutils.c
index 22b2257..897c2f9 100644
--- a/Python/fileutils.c
+++ b/Python/fileutils.c
@@ -1750,7 +1750,15 @@ _Py_read(int fd, void *buf, size_t count)
Py_BEGIN_ALLOW_THREADS
errno = 0;
#ifdef MS_WINDOWS
+ _doserrno = 0;
n = read(fd, buf, (int)count);
+ // read() on a non-blocking empty pipe fails with EINVAL, which is
+ // mapped from the Windows error code ERROR_NO_DATA.
+ if (n < 0 && errno == EINVAL) {
+ if (_doserrno == ERROR_NO_DATA) {
+ errno = EAGAIN;
+ }
+ }
#else
n = read(fd, buf, count);
#endif
@@ -1804,6 +1812,7 @@ _Py_write_impl(int fd, const void *buf, size_t count, int gil_held)
}
}
}
+
#endif
if (count > _PY_WRITE_MAX) {
count = _PY_WRITE_MAX;
@@ -1814,7 +1823,18 @@ _Py_write_impl(int fd, const void *buf, size_t count, int gil_held)
Py_BEGIN_ALLOW_THREADS
errno = 0;
#ifdef MS_WINDOWS
- n = write(fd, buf, (int)count);
+ // write() on a non-blocking pipe fails with ENOSPC on Windows if
+ // the pipe lacks available space for the entire buffer.
+ int c = (int)count;
+ do {
+ _doserrno = 0;
+ n = write(fd, buf, c);
+ if (n >= 0 || errno != ENOSPC || _doserrno != 0) {
+ break;
+ }
+ errno = EAGAIN;
+ c /= 2;
+ } while (c > 0);
#else
n = write(fd, buf, count);
#endif
@@ -1829,7 +1849,18 @@ _Py_write_impl(int fd, const void *buf, size_t count, int gil_held)
do {
errno = 0;
#ifdef MS_WINDOWS
- n = write(fd, buf, (int)count);
+ // write() on a non-blocking pipe fails with ENOSPC on Windows if
+ // the pipe lacks available space for the entire buffer.
+ int c = (int)count;
+ do {
+ _doserrno = 0;
+ n = write(fd, buf, c);
+ if (n >= 0 || errno != ENOSPC || _doserrno != 0) {
+ break;
+ }
+ errno = EAGAIN;
+ c /= 2;
+ } while (c > 0);
#else
n = write(fd, buf, count);
#endif
@@ -2450,6 +2481,64 @@ error:
return -1;
}
#else /* MS_WINDOWS */
+int
+_Py_get_blocking(int fd)
+{
+ HANDLE handle;
+ DWORD mode;
+ BOOL success;
+
+ handle = _Py_get_osfhandle(fd);
+ if (handle == INVALID_HANDLE_VALUE) {
+ return -1;
+ }
+
+ Py_BEGIN_ALLOW_THREADS
+ success = GetNamedPipeHandleStateW(handle, &mode,
+ NULL, NULL, NULL, NULL, 0);
+ Py_END_ALLOW_THREADS
+
+ if (!success) {
+ PyErr_SetFromWindowsErr(0);
+ return -1;
+ }
+
+ return !(mode & PIPE_NOWAIT);
+}
+
+int
+_Py_set_blocking(int fd, int blocking)
+{
+ HANDLE handle;
+ DWORD mode;
+ BOOL success;
+
+ handle = _Py_get_osfhandle(fd);
+ if (handle == INVALID_HANDLE_VALUE) {
+ return -1;
+ }
+
+ Py_BEGIN_ALLOW_THREADS
+ success = GetNamedPipeHandleStateW(handle, &mode,
+ NULL, NULL, NULL, NULL, 0);
+ if (success) {
+ if (blocking) {
+ mode &= ~PIPE_NOWAIT;
+ }
+ else {
+ mode |= PIPE_NOWAIT;
+ }
+ success = SetNamedPipeHandleState(handle, &mode, NULL, NULL);
+ }
+ Py_END_ALLOW_THREADS
+
+ if (!success) {
+ PyErr_SetFromWindowsErr(0);
+ return -1;
+ }
+ return 0;
+}
+
void*
_Py_get_osfhandle_noraise(int fd)
{