Skip to content

Commit 2f10037

Browse files
authored
Support module errno on Linux and macOS (#1502)
* Generate platform-specific errno codes * Generate platform-specific errno symbols * Add test_errno * Support error symbol aliases * Support error symbol aliases on Linux * Support error symbol aliases on Darwin * Include Posix aliases in Windows aliases * Stable errno order * Let _socket use OS-native error codes * Make tests passing on all platforms * More Mono codes remapped * Complete mapping of Mono codes
1 parent 99c98e5 commit 2f10037

File tree

8 files changed

+1255
-268
lines changed

8 files changed

+1255
-268
lines changed

Src/IronPython.Modules/_socket.cs

Lines changed: 223 additions & 45 deletions
Large diffs are not rendered by default.

Src/IronPython.Modules/errno.cs

Lines changed: 741 additions & 208 deletions
Large diffs are not rendered by default.

Src/IronPython/Lib/iptest/file_util.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def file_exists_in_path(self, file):
1818
return False
1919
else:
2020
full_path = full_path[0]
21-
21+
2222
for path in [os.getcwd()] + full_path.split(os.pathsep):
2323
path = path.strip()
2424
if self.file_exists(os.path.join(path, file)):
@@ -27,7 +27,7 @@ def file_exists_in_path(self, file):
2727
return False
2828

2929
def fullpath(self, path):
30-
if sys.platform == 'win32' and colon not in path:
30+
if sys.implementation.name == 'cpython' or sys.platform == 'win32' and colon not in path:
3131
return os.path.join(os.getcwd(), path)
3232
elif sys.platform != 'win32':
3333
from System.IO.Path import GetFullPath

Src/IronPython/Runtime/PlatformsAttribute.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@
66
using System.Runtime.InteropServices;
77

88
namespace IronPython.Runtime {
9+
/// <summary>
10+
/// Marks a member as platform-specific.
11+
/// </summary>
12+
/// <remarks>
13+
/// PlatformFamily.Unix means Linux and Darwin
14+
/// but PlatformID.Unix means Linux only
15+
/// while PlatformID.MacOSX means Darwin only.
16+
/// </remarks>
917
public class PlatformsAttribute : Attribute {
1018
public enum PlatformFamily {
1119
Windows,

Src/IronPython/Runtime/PythonHiddenAttribute.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ namespace IronPython.Runtime {
99
/// <summary>
1010
/// Marks a member as being hidden from Python code.
1111
/// </summary>
12+
/// <inheritdoc />
1213
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
1314
public sealed class PythonHiddenAttribute : PlatformsAttribute {
1415
public PythonHiddenAttribute(params PlatformID[] hiddenPlatforms) {
@@ -19,6 +20,14 @@ public PythonHiddenAttribute(PlatformsAttribute.PlatformFamily hiddenPlatformFam
1920
SetValidPlatforms(hiddenPlatformFamily);
2021
}
2122

23+
public PythonHiddenAttribute(PlatformsAttribute.PlatformFamily hiddenPlatformFamily, params PlatformID[] hiddenPlatforms)
24+
: this(hiddenPlatformFamily) {
25+
var allHiddenPlatforms = new PlatformID[ValidPlatforms.Length + hiddenPlatforms.Length];
26+
Array.Copy(ValidPlatforms, allHiddenPlatforms, ValidPlatforms.Length);
27+
Array.Copy(hiddenPlatforms, 0, allHiddenPlatforms, ValidPlatforms.Length, hiddenPlatforms.Length);
28+
ValidPlatforms = allHiddenPlatforms;
29+
}
30+
2231
public static bool IsHidden(MemberInfo m, bool inherit = false) {
2332
var hasHiddenAttribute = m.IsDefined(typeof(PythonHiddenAttribute), inherit);
2433
if (hasHiddenAttribute) {

Src/Scripts/generate_errno.py

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
# Licensed to the .NET Foundation under one or more agreements.
2+
# The .NET Foundation licenses this file to you under the Apache 2.0 License.
3+
# See the LICENSE file in the project root for more information.
4+
5+
from generate import generate
6+
7+
num_systems = 3
8+
linux_idx, darwin_idx, windows_idx = range(num_systems)
9+
10+
# python3 -c 'import errno;print(dict(sorted(errno.errorcode.items())))'
11+
errorcode_linux = {1: 'EPERM', 2: 'ENOENT', 3: 'ESRCH', 4: 'EINTR', 5: 'EIO', 6: 'ENXIO', 7: 'E2BIG', 8: 'ENOEXEC', 9: 'EBADF', 10: 'ECHILD', 11: 'EAGAIN', 12: 'ENOMEM', 13: 'EACCES', 14: 'EFAULT', 15: 'ENOTBLK', 16: 'EBUSY', 17: 'EEXIST', 18: 'EXDEV', 19: 'ENODEV', 20: 'ENOTDIR', 21: 'EISDIR', 22: 'EINVAL', 23: 'ENFILE', 24: 'EMFILE', 25: 'ENOTTY', 26: 'ETXTBSY', 27: 'EFBIG', 28: 'ENOSPC', 29: 'ESPIPE', 30: 'EROFS', 31: 'EMLINK', 32: 'EPIPE', 33: 'EDOM', 34: 'ERANGE', 35: 'EDEADLOCK', 36: 'ENAMETOOLONG', 37: 'ENOLCK', 38: 'ENOSYS', 39: 'ENOTEMPTY', 40: 'ELOOP', 42: 'ENOMSG', 43: 'EIDRM', 44: 'ECHRNG', 45: 'EL2NSYNC', 46: 'EL3HLT', 47: 'EL3RST', 48: 'ELNRNG', 49: 'EUNATCH', 50: 'ENOCSI', 51: 'EL2HLT', 52: 'EBADE', 53: 'EBADR', 54: 'EXFULL', 55: 'ENOANO', 56: 'EBADRQC', 57: 'EBADSLT', 59: 'EBFONT', 60: 'ENOSTR', 61: 'ENODATA', 62: 'ETIME', 63: 'ENOSR', 64: 'ENONET', 65: 'ENOPKG', 66: 'EREMOTE', 67: 'ENOLINK', 68: 'EADV', 69: 'ESRMNT', 70: 'ECOMM', 71: 'EPROTO', 72: 'EMULTIHOP', 73: 'EDOTDOT', 74: 'EBADMSG', 75: 'EOVERFLOW', 76: 'ENOTUNIQ', 77: 'EBADFD', 78: 'EREMCHG', 79: 'ELIBACC', 80: 'ELIBBAD', 81: 'ELIBSCN', 82: 'ELIBMAX', 83: 'ELIBEXEC', 84: 'EILSEQ', 85: 'ERESTART', 86: 'ESTRPIPE', 87: 'EUSERS', 88: 'ENOTSOCK', 89: 'EDESTADDRREQ', 90: 'EMSGSIZE', 91: 'EPROTOTYPE', 92: 'ENOPROTOOPT', 93: 'EPROTONOSUPPORT', 94: 'ESOCKTNOSUPPORT', 95: 'ENOTSUP', 96: 'EPFNOSUPPORT', 97: 'EAFNOSUPPORT', 98: 'EADDRINUSE', 99: 'EADDRNOTAVAIL', 100: 'ENETDOWN', 101: 'ENETUNREACH', 102: 'ENETRESET', 103: 'ECONNABORTED', 104: 'ECONNRESET', 105: 'ENOBUFS', 106: 'EISCONN', 107: 'ENOTCONN', 108: 'ESHUTDOWN', 109: 'ETOOMANYREFS', 110: 'ETIMEDOUT', 111: 'ECONNREFUSED', 112: 'EHOSTDOWN', 113: 'EHOSTUNREACH', 114: 'EALREADY', 115: 'EINPROGRESS', 116: 'ESTALE', 117: 'EUCLEAN', 118: 'ENOTNAM', 119: 'ENAVAIL', 120: 'EISNAM', 121: 'EREMOTEIO', 122: 'EDQUOT', 123: 'ENOMEDIUM', 124: 'EMEDIUMTYPE', 125: 'ECANCELED', 126: 'ENOKEY', 127: 'EKEYEXPIRED', 128: 'EKEYREVOKED', 129: 'EKEYREJECTED', 130: 'EOWNERDEAD', 131: 'ENOTRECOVERABLE', 132: 'ERFKILL'}
12+
errorcode_darwin = {1: 'EPERM', 2: 'ENOENT', 3: 'ESRCH', 4: 'EINTR', 5: 'EIO', 6: 'ENXIO', 7: 'E2BIG', 8: 'ENOEXEC', 9: 'EBADF', 10: 'ECHILD', 11: 'EDEADLK', 12: 'ENOMEM', 13: 'EACCES', 14: 'EFAULT', 15: 'ENOTBLK', 16: 'EBUSY', 17: 'EEXIST', 18: 'EXDEV', 19: 'ENODEV', 20: 'ENOTDIR', 21: 'EISDIR', 22: 'EINVAL', 23: 'ENFILE', 24: 'EMFILE', 25: 'ENOTTY', 26: 'ETXTBSY', 27: 'EFBIG', 28: 'ENOSPC', 29: 'ESPIPE', 30: 'EROFS', 31: 'EMLINK', 32: 'EPIPE', 33: 'EDOM', 34: 'ERANGE', 35: 'EAGAIN', 36: 'EINPROGRESS', 37: 'EALREADY', 38: 'ENOTSOCK', 39: 'EDESTADDRREQ', 40: 'EMSGSIZE', 41: 'EPROTOTYPE', 42: 'ENOPROTOOPT', 43: 'EPROTONOSUPPORT', 44: 'ESOCKTNOSUPPORT', 45: 'ENOTSUP', 46: 'EPFNOSUPPORT', 47: 'EAFNOSUPPORT', 48: 'EADDRINUSE', 49: 'EADDRNOTAVAIL', 50: 'ENETDOWN', 51: 'ENETUNREACH', 52: 'ENETRESET', 53: 'ECONNABORTED', 54: 'ECONNRESET', 55: 'ENOBUFS', 56: 'EISCONN', 57: 'ENOTCONN', 58: 'ESHUTDOWN', 59: 'ETOOMANYREFS', 60: 'ETIMEDOUT', 61: 'ECONNREFUSED', 62: 'ELOOP', 63: 'ENAMETOOLONG', 64: 'EHOSTDOWN', 65: 'EHOSTUNREACH', 66: 'ENOTEMPTY', 67: 'EPROCLIM', 68: 'EUSERS', 69: 'EDQUOT', 70: 'ESTALE', 71: 'EREMOTE', 72: 'EBADRPC', 73: 'ERPCMISMATCH', 74: 'EPROGUNAVAIL', 75: 'EPROGMISMATCH', 76: 'EPROCUNAVAIL', 77: 'ENOLCK', 78: 'ENOSYS', 79: 'EFTYPE', 80: 'EAUTH', 81: 'ENEEDAUTH', 82: 'EPWROFF', 83: 'EDEVERR', 84: 'EOVERFLOW', 85: 'EBADEXEC', 86: 'EBADARCH', 87: 'ESHLIBVERS', 88: 'EBADMACHO', 89: 'ECANCELED', 90: 'EIDRM', 91: 'ENOMSG', 92: 'EILSEQ', 93: 'ENOATTR', 94: 'EBADMSG', 95: 'EMULTIHOP', 96: 'ENODATA', 97: 'ENOLINK', 98: 'ENOSR', 99: 'ENOSTR', 100: 'EPROTO', 101: 'ETIME', 102: 'EOPNOTSUPP', 103: 'ENOPOLICY', 104: 'ENOTRECOVERABLE', 105: 'EOWNERDEAD'}
13+
errorcode_windows = {1: 'EPERM', 2: 'ENOENT', 3: 'ESRCH', 4: 'EINTR', 5: 'EIO', 6: 'ENXIO', 7: 'E2BIG', 8: 'ENOEXEC', 9: 'EBADF', 10: 'ECHILD', 11: 'EAGAIN', 12: 'ENOMEM', 13: 'EACCES', 14: 'EFAULT', 16: 'EBUSY', 17: 'EEXIST', 18: 'EXDEV', 19: 'ENODEV', 20: 'ENOTDIR', 21: 'EISDIR', 22: 'EINVAL', 23: 'ENFILE', 24: 'EMFILE', 25: 'ENOTTY', 27: 'EFBIG', 28: 'ENOSPC', 29: 'ESPIPE', 30: 'EROFS', 31: 'EMLINK', 32: 'EPIPE', 33: 'EDOM', 34: 'ERANGE', 36: 'EDEADLOCK', 38: 'ENAMETOOLONG', 39: 'ENOLCK', 40: 'ENOSYS', 41: 'ENOTEMPTY', 42: 'EILSEQ', 104: 'EBADMSG', 105: 'ECANCELED', 111: 'EIDRM', 120: 'ENODATA', 121: 'ENOLINK', 122: 'ENOMSG', 124: 'ENOSR', 125: 'ENOSTR', 127: 'ENOTRECOVERABLE', 129: 'ENOTSUP', 132: 'EOVERFLOW', 133: 'EOWNERDEAD', 134: 'EPROTO', 137: 'ETIME', 139: 'ETXTBSY', 10000: 'WSABASEERR', 10004: 'WSAEINTR', 10009: 'WSAEBADF', 10013: 'WSAEACCES', 10014: 'WSAEFAULT', 10022: 'WSAEINVAL', 10024: 'WSAEMFILE', 10035: 'WSAEWOULDBLOCK', 10036: 'WSAEINPROGRESS', 10037: 'WSAEALREADY', 10038: 'WSAENOTSOCK', 10039: 'WSAEDESTADDRREQ', 10040: 'WSAEMSGSIZE', 10041: 'WSAEPROTOTYPE', 10042: 'WSAENOPROTOOPT', 10043: 'WSAEPROTONOSUPPORT', 10044: 'WSAESOCKTNOSUPPORT', 10045: 'WSAEOPNOTSUPP', 10046: 'WSAEPFNOSUPPORT', 10047: 'WSAEAFNOSUPPORT', 10048: 'WSAEADDRINUSE', 10049: 'WSAEADDRNOTAVAIL', 10050: 'WSAENETDOWN', 10051: 'WSAENETUNREACH', 10052: 'WSAENETRESET', 10053: 'WSAECONNABORTED', 10054: 'WSAECONNRESET', 10055: 'WSAENOBUFS', 10056: 'WSAEISCONN', 10057: 'WSAENOTCONN', 10058: 'WSAESHUTDOWN', 10059: 'WSAETOOMANYREFS', 10060: 'WSAETIMEDOUT', 10061: 'WSAECONNREFUSED', 10062: 'WSAELOOP', 10063: 'WSAENAMETOOLONG', 10064: 'WSAEHOSTDOWN', 10065: 'WSAEHOSTUNREACH', 10066: 'WSAENOTEMPTY', 10067: 'WSAEPROCLIM', 10068: 'WSAEUSERS', 10069: 'WSAEDQUOT', 10070: 'WSAESTALE', 10071: 'WSAEREMOTE', 10091: 'WSASYSNOTREADY', 10092: 'WSAVERNOTSUPPORTED', 10093: 'WSANOTINITIALISED', 10101: 'WSAEDISCON'}
14+
linux_aliases = {'EOPNOTSUPP' : 'ENOTSUP', 'EDEADLK': 'EDEADLOCK', 'EWOULDBLOCK': 'EAGAIN'}
15+
darwin_aliases = {'EWOULDBLOCK': 'EAGAIN'}
16+
aliases = {**linux_aliases, **darwin_aliases}
17+
18+
def set_value(errorval, name, value, index):
19+
if name not in errorval:
20+
errorval[name] = [None] * num_systems
21+
errorval[name][index] = value
22+
23+
def collect_errornames():
24+
errorval = {}
25+
for code in errorcode_linux:
26+
set_value(errorval, errorcode_linux[code], code, linux_idx)
27+
for code in errorcode_darwin:
28+
set_value(errorval, errorcode_darwin[code], code, darwin_idx)
29+
for code in errorcode_windows:
30+
set_value(errorval, errorcode_windows[code], code, windows_idx)
31+
32+
# WSA-codes are also available as E-codes if such code name is in use on Posix systems.
33+
known_symbols = set(errorval) | set(aliases)
34+
for symbol in errorcode_windows.values():
35+
esymbol = symbol[3:]
36+
if symbol.startswith("WSAE") and esymbol in known_symbols and (esymbol not in errorval or not errorval[esymbol][windows_idx]):
37+
set_value(errorval, esymbol, errorval[symbol][windows_idx], windows_idx)
38+
39+
# set aliases
40+
for alias, target in aliases.items():
41+
for idx in range(num_systems):
42+
if errorval[target][idx] and not errorval[alias][idx]:
43+
errorval[alias][idx] = errorval[target][idx]
44+
45+
return errorval
46+
47+
errorvalues = collect_errornames()
48+
49+
def generate_errno_codes(cw):
50+
def priority(error):
51+
codes = errorvalues[error]
52+
shifts = 0
53+
while codes[0] is None:
54+
codes = codes[1:] + [0]
55+
shifts += 1
56+
res = 0
57+
for code in codes:
58+
res *= 100000
59+
res += code if code else 0
60+
return res + shifts * 100000 ** (num_systems - 1) // 10
61+
62+
names = sorted(errorvalues, key = priority)
63+
for name in names:
64+
codes = errorvalues[name]
65+
hidden_on = []
66+
cw.writeline()
67+
if not codes[windows_idx]:
68+
hidden_on = ["PlatformsAttribute.PlatformFamily.Windows"]
69+
if not codes[linux_idx] and not codes[darwin_idx]:
70+
assert not hidden_on, "Cannot hide on both Unix and Windows"
71+
hidden_on = ["PlatformsAttribute.PlatformFamily.Unix"]
72+
else:
73+
if not codes[linux_idx]:
74+
hidden_on += ["PlatformID.Unix"]
75+
if not codes[darwin_idx]:
76+
hidden_on += ["PlatformID.MacOSX"]
77+
if hidden_on:
78+
cw.write(f"[PythonHidden({', '.join(hidden_on)})]")
79+
80+
value = windows_code_expr(codes)
81+
cw.write(f"public static int {name} => {value};")
82+
83+
def windows_code_expr(codes):
84+
if codes[windows_idx]:
85+
if not any(codes[:windows_idx]) or all(map(lambda x: x == codes[windows_idx], codes[:windows_idx])):
86+
return str(codes[windows_idx])
87+
else:
88+
return f"RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? {codes[windows_idx]} : {darwin_code_expr(codes)}"
89+
else:
90+
return darwin_code_expr(codes)
91+
92+
def darwin_code_expr(codes):
93+
if codes[darwin_idx]:
94+
if not any(codes[:darwin_idx]) or all(map(lambda x: x == codes[darwin_idx], codes[:darwin_idx])):
95+
return str(codes[darwin_idx])
96+
else:
97+
return f"RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? {codes[darwin_idx]} : {linux_code_expr(codes)}"
98+
else:
99+
return linux_code_expr(codes)
100+
101+
def linux_code_expr(codes):
102+
return str(codes[linux_idx])
103+
104+
def generate_errno_names(cw):
105+
def is_windows_alias(name):
106+
return "WSA" + name in errorvalues and name in errorvalues and errorvalues["WSA" + name][windows_idx] == errorvalues[name][windows_idx]
107+
108+
cw.write("// names defined on all platforms")
109+
exclusions = set(aliases)
110+
common_names = sorted(name for name in errorvalues if all(errorvalues[name]) and not is_windows_alias(name) and name not in exclusions)
111+
for name in common_names:
112+
cw.write(f'errorcode[{name}] = "{name}";')
113+
114+
cw.write("// names defined on Posix platforms")
115+
exclusions = set(common_names) | set(aliases)
116+
posix_names = sorted(name for name in errorvalues if errorvalues[name][linux_idx] and errorvalues[name][darwin_idx] and name not in exclusions)
117+
cw.enter_block("if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX))")
118+
for name in posix_names:
119+
cw.write(f'errorcode[{name}] = "{name}";')
120+
cw.exit_block()
121+
122+
cw.write("// names defined on Linux")
123+
exclusions = set(common_names) | set(posix_names) | set(linux_aliases)
124+
linux_names = sorted(name for name in errorvalues if errorvalues[name][linux_idx] and name not in exclusions)
125+
cw.enter_block("if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))")
126+
for name in linux_names:
127+
cw.write(f'errorcode[{name}] = "{name}";')
128+
cw.exit_block()
129+
130+
cw.write("// names defined on macOS")
131+
exclusions = set(common_names) | set(posix_names) | set(darwin_aliases)
132+
darwin_names = sorted(name for name in errorvalues if errorvalues[name][darwin_idx] and name not in exclusions)
133+
cw.enter_block("if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))")
134+
for name in darwin_names:
135+
cw.write(f'errorcode[{name}] = "{name}";')
136+
cw.exit_block()
137+
138+
cw.write("// names defined on Windows")
139+
windows_names = sorted(name for name in errorvalues if errorvalues[name][windows_idx] and name not in common_names)
140+
cw.enter_block("if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))")
141+
for name in windows_names:
142+
cw.write(f'errorcode[{name}] = "{name}";')
143+
cw.exit_block()
144+
145+
def main():
146+
return generate(
147+
("Errno Codes", generate_errno_codes),
148+
("Errno Names", generate_errno_names),
149+
)
150+
151+
if __name__ == "__main__":
152+
main()

0 commit comments

Comments
 (0)