From 7cbdd3ab8ef97edc17846f8b7cbb9e63e7a855c4 Mon Sep 17 00:00:00 2001 From: "Kirill Lukonin (EvilWirelessMan)" Date: Sat, 17 Oct 2026 18:45:03 +0300 Subject: [PATCH] python-stdlib/errno: Use the values of the built-in errno module. This module replaced the built-in errno module rather than extending it, and the values it defines are the Linux/x86 ones. errno is an extensible built-in, so a file on sys.path wins outright: installing this package left errno.ETIMEDOUT at 110 on a port whose headers say 145 (Linux/mips) or 60 (macOS), while the number OSError carries still came from those headers. errno.errorcode disappeared with it. Found on an OpenWrt mips target, where the package is installed into /usr/lib/micropython, which is on the default sys.path. Import the built-in module at the end of the file, under its u-prefixed name so that the import does not find this file again. Placed last it overrides the table above, which stays as the fallback for the names the built-in module does not provide. Keep only codes 1 to 34 in the table, which glibc, musl, newlib and the MSVC CRT agree on, and note in the file that they are more or less portable. Everything above 34 differs between platforms (EAFNOSUPPORT is 97 on Linux/x86, 106 in newlib, 124 on Linux/mips, 47 on macOS), so it is dropped and comes from the built-in module only. Providing a wrong value is worse than not providing one. The test checks that no name outside the built-in module has a value above 34. Signed-off-by: Kirill Lukonin (Evil Wireless Man) --- python-stdlib/errno/errno.py | 18 +++++++++---- python-stdlib/errno/manifest.py | 2 +- python-stdlib/errno/test_errno.py | 45 +++++++++++++++++++++++++++++++ tools/ci.sh | 1 + 4 files changed, 60 insertions(+), 6 deletions(-) create mode 100644 python-stdlib/errno/test_errno.py diff --git a/python-stdlib/errno/errno.py b/python-stdlib/errno/errno.py index c513a7f14..9201cb1e9 100644 --- a/python-stdlib/errno/errno.py +++ b/python-stdlib/errno/errno.py @@ -1,3 +1,8 @@ +# Codes 1 to 34 are the same in glibc, musl, newlib and the MSVC CRT, so they +# are more or less portable and this table provides them as a fallback. Codes +# above 34 differ between operating systems and architectures (ETIMEDOUT is 110 +# on Linux/x86, 145 on Linux/mips and 60 on macOS), so they are deliberately +# not defined here and come only from the built-in module. EPERM = 1 # Operation not permitted ENOENT = 2 # No such file or directory ESRCH = 3 # No such process @@ -32,8 +37,11 @@ EPIPE = 32 # Broken pipe EDOM = 33 # Math argument out of domain of func ERANGE = 34 # Math result not representable -EAFNOSUPPORT = 97 # Address family not supported by protocol -ECONNRESET = 104 # Connection timed out -ENOTCONN = 107 # Not connected -ETIMEDOUT = 110 # Connection timed out -EINPROGRESS = 115 # Operation now in progress + +# Override the table above with the built-in module's values, which come from +# this port's system headers. The u-prefix reaches the built-in module. +try: + from uerrno import * +except ImportError: + # Built without MICROPY_PY_ERRNO. + pass diff --git a/python-stdlib/errno/manifest.py b/python-stdlib/errno/manifest.py index 075d3403d..3a71c2d82 100644 --- a/python-stdlib/errno/manifest.py +++ b/python-stdlib/errno/manifest.py @@ -1,3 +1,3 @@ -metadata(version="0.2.0") +metadata(version="0.3.0") module("errno.py") diff --git a/python-stdlib/errno/test_errno.py b/python-stdlib/errno/test_errno.py new file mode 100644 index 000000000..61d59e1d7 --- /dev/null +++ b/python-stdlib/errno/test_errno.py @@ -0,0 +1,45 @@ +# The values the built-in errno module provides come from the system headers +# the port was built against, and they differ between operating systems and +# between architectures: ETIMEDOUT is 110 on Linux/x86, 145 on Linux/mips and +# 60 on macOS. This module must not override them with a table of its own. + +import errno + +try: + import uerrno +except ImportError: + # Built without MICROPY_PY_ERRNO, so there is nothing to compare against. + print("SKIP") + raise SystemExit + + +# Every name the built-in module provides has to come through unchanged. +names = [name for name in dir(uerrno) if name.startswith("E")] +assert names, dir(uerrno) +for name in names: + theirs = getattr(uerrno, name) + ours = getattr(errno, name, None) + assert ours is not None, "errno.%s is missing, uerrno.%s is %d" % (name, name, theirs) + assert ours == theirs, "errno.%s is %d, uerrno.%s is %d" % (name, ours, name, theirs) + +# errorcode has to survive too: mp_errno_to_str() looks names up in it, and it +# is what makes str(OSError(errno.ENOENT)) readable. +if hasattr(uerrno, "errorcode"): + assert isinstance(errno.errorcode, dict), errno.errorcode + for code, name in errno.errorcode.items(): + assert getattr(errno, name) == code, "errorcode[%d] is %s" % (code, name) + +# The names the built-in module does not provide are filled in by this module. +# Codes 1 to 34 are the same on every platform MicroPython runs on. +assert errno.EPERM == 1, errno.EPERM +assert errno.ESRCH == 3, errno.ESRCH +assert errno.ENOTBLK == 15, errno.ENOTBLK +assert errno.EDOM == 33, errno.EDOM +assert errno.ERANGE == 34, errno.ERANGE + +# Nothing above 34 may come from this module's own table: those codes are not +# portable, so they must either come from the built-in module or be absent. +for name in dir(errno): + if name.startswith("E") and not hasattr(uerrno, name): + value = getattr(errno, name) + assert 1 <= value <= 34, "errno.%s is %d, not from uerrno" % (name, value) diff --git a/tools/ci.sh b/tools/ci.sh index 4533fe204..eee4521d2 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -66,6 +66,7 @@ function ci_package_tests_run { python-stdlib/binascii/test_binascii.py \ python-stdlib/collections-defaultdict/test_defaultdict.py \ python-stdlib/curses.ascii/test_curses_ascii.py \ + python-stdlib/errno/test_errno.py \ python-stdlib/functools/test_partial.py \ python-stdlib/functools/test_reduce.py \ python-stdlib/functools/test_cache.py \