Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

errno.EINVAL != errno.WSAEINVAL (and others) on windows #107537

Open
2 tasks done
howeaj opened this issue Aug 1, 2023 · 2 comments
Open
2 tasks done

errno.EINVAL != errno.WSAEINVAL (and others) on windows #107537

howeaj opened this issue Aug 1, 2023 · 2 comments
Labels
OS-windows type-bug An unexpected behavior, bug, or error

Comments

@howeaj
Copy link

howeaj commented Aug 1, 2023

Bug report

errnomodule.c contains the following list of error codes which are #undefd on windows in favour of the WSA version;

/* The following constants were added to errno.h in VS2010 but have
   preferred WSA equivalents. */
#undef EADDRINUSE
#undef EADDRNOTAVAIL
#undef EAFNOSUPPORT
#undef EALREADY
#undef ECONNABORTED
#undef ECONNREFUSED
#undef ECONNRESET
#undef EDESTADDRREQ
#undef EHOSTUNREACH
#undef EINPROGRESS
#undef EISCONN
#undef ELOOP
#undef EMSGSIZE
#undef ENETDOWN
#undef ENETRESET
#undef ENETUNREACH
#undef ENOBUFS
#undef ENOPROTOOPT
#undef ENOTCONN
#undef ENOTSOCK
#undef EOPNOTSUPP
#undef EPROTONOSUPPORT
#undef EPROTOTYPE
#undef ETIMEDOUT
#undef EWOULDBLOCK

This means that, for example, errno.EADDRINUSE is assigned a value of 10048 instead of 48 on windows, and so errno.EADDRINUSE can be used when writing platform-agnostic socket error handling code.

The problem is that the list is missing some error codes which also have preferred WSA equivalents:

EACCES, EBADF, EFAULT, EINTR, EINVAL and EMFILE have WSA equivalents that are equal to the original + 10000.
ENAMETOOLONG (38) and ENOTEMPTY (41) have WSA equivalents WSAENAMETOOLONG (10063) and WSANOTEMPTY (10066).

This problem means that, for example, errno.EINVAL cannot be used in the same way as errno.EADDRINUSE to write platform-agnostic code because on windows errno still gives it a value of 22, while windows will only ever raise errno.WSAEINVAL which has a value of 10022.

As far as I can tell from searching online, it is true that windows only uses WSAEINVAL (10022) and never uses EINVAL (22), and so it would be correct for errnomodule.c to treat it the same way as EADDRINUSE and others. I haven't checked the rest of them.

My guess would be that a later version of VS added these additional error codes and the #undef list was not updated accordingly, but I'm afraid I don't know how to find the history of VS to confirm whether or not that is true.

If there is a real reason to exclude them from the list, it should be documented in the comment.

Checklist

  • I am confident this is a bug in CPython, not a bug in a third-party project
  • I have searched the CPython issue tracker, and am confident this bug has not been reported before

Reproduction

  Python 3.11.4 (tags/v3.11.4:d2340ef, Jun  7 2023, 05:45:37) [MSC v.1934 64 bit (AMD64)] on win32
  >>> import errno
  >>> errno.EADDRINUSE, errno.WSAEADDRINUSE
  (10048, 10048)
  >>> errno.EINVAL, errno.WSAEINVAL
  (22, 10022)

Your environment

  • CPython versions tested on:

    • Python 3.8.16 (default, Dec 16 2022, 06:55:50) [MSC v.1900 64 bit (AMD64)] on win32
    • Python 3.11.4 (tags/v3.11.4:d2340ef, Jun 7 2023, 05:45:37) [MSC v.1934 64 bit (AMD64)] on win32
  • Operating system and architecture: Windows 10 22H2

@howeaj howeaj added the type-bug An unexpected behavior, bug, or error label Aug 1, 2023
@eryksun
Copy link
Contributor

eryksun commented Aug 1, 2023

Please refer to "PC/errmap.h":

cpython/PC/errmap.h

Lines 9 to 23 in 97a6a41

// Winsock error codes (10000-11999) are errno values.
if (winerror >= 10000 && winerror < 12000) {
switch (winerror) {
case WSAEINTR:
case WSAEBADF:
case WSAEACCES:
case WSAEFAULT:
case WSAEINVAL:
case WSAEMFILE:
// Winsock definitions of errno values. See WinSock2.h
return winerror - 10000;
default:
return winerror;
}
}

If a Winsock function (the WSA prefix is a Winsock error code) sets the last error to WSAEINVAL, Python maps it to C EINVAL by subtracting 10000. The C runtime on Windows actually uses EINVAL all over the place. It also uses it as the default mapping for thousands of Windows error codes that aren't mapped explicitly to C errno values. Here's an example of Python mapping Winsock WSAEINVAL (10022) to C EINVAL (22):

>>> import ctypes
>>> try: ctypes.pythonapi.PyErr_SetFromWindowsErr(10022)
... except OSError as e: err = e
...
>>> err.errno
22

You also mention ENAMETOOLONG (38) versus WSAENAMETOOLONG (10063), and ENOTEMPTY (41) versus WSAENOTEMPTY (10066). Do you have an example where Winsock returns WSAENAMETOOLONG or WSAENOTEMPTY? These two Winsock error codes could be special cased in "PC/errmap.h". It would be a breaking change for sockets code that relies on handling e.errno == WSAENAMETOOLONG or e.errno == WSAENOTEMPTY, if that's actually a problem for any real-world code.

We can't change the value of ENOTEMPTY (41) to that of WSAENOTEMPTY (10066). The C runtime maps the WinAPI error code ERROR_DIR_NOT_EMPTY to the errno value ENOTEMPTY (41), and Python has always done the same in "PC/errmap.h". ENAMETOOLONG is less of a problem because the C runtime doesn't actually use it anywhere. But in principle a new version of the CRT could start mapping the Windows error code ERROR_FILENAME_EXCED_RANGE to ENAMETOOLONG instead of ENOENT.

@howeaj
Copy link
Author

howeaj commented Aug 1, 2023

I see, thank you for explaining; so the difference in how python deals with the 6 Winsock error codes in errmap.h (forces all OsError to the non-Winsock value, but leaves both values available in errno) and the 25 Winsock error codes in errnomodule.c (leaves all OsError with the Winsock value, and hides the non-Winsock value in errno) is deliberate? I suppose there is something that delineates these two categories of error?

No, I don't have an example of returning WSAENAMETOOLONG or WSAENOTEMPT; I found those just by looking at the contents of errno.

The one example I actually ran into was socket.sendto raising a socket.error with an errno of WSAEINVAL (and not EINVAL) in python 3.8. Looks like there's been a recent change (#90931) for python 3.12 (and backported to 3.9+) which fixes the call to winerror_to_errno to always occur as you're suggesting it should and stops WSAEINVAL from ever actually reaching the user; so it looks like the original issue of code portability I'm talking about has already been fixed somewhat.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
OS-windows type-bug An unexpected behavior, bug, or error
Projects
None yet
Development

No branches or pull requests

2 participants