Skip to content

python-stdlib/errno: Use the values of the built-in errno module. - #1161

Draft
klukonin wants to merge 1 commit into
micropython:masterfrom
klukonin:fix/errno-use-builtin-values
Draft

klukonin wants to merge 1 commit into
micropython:masterfrom
klukonin:fix/errno-use-builtin-values

Conversation

@klukonin

Copy link
Copy Markdown

python-stdlib/errno replaces the built-in errno module instead of extending
it, and hardcodes the Linux/x86 values.

errno is an extensible built-in, so a file found on sys.path wins outright.
Installing this package leaves errno.ETIMEDOUT at 110 on a port whose system
headers say 145 (Linux/mips) or 60 (macOS), while the number OSError carries
still comes from those headers — so e.args[0] == errno.ETIMEDOUT silently
stops matching. errno.errorcode disappears at the same time, which is what
mp_errno_to_str() looks names up in.

The fix is one import, at the end of the file, which is what makes it work
without per-name guards: the built-in values override the table, and the table
only fills the gaps. The u-prefixed name reaches the built-in module directly
(py/objmodule.c), so the import does not find this file again. os.py,
time.py and binascii.py already use this pattern at the top of the file,
having nothing to fall back to.

EAFNOSUPPORT is removed: it is not in the default MICROPY_PY_ERRNO_LIST, so
the value here is the one used, and 97 is right only for Linux/x86 (newlib 106,
Linux/mips 124, macOS 47). This does mean errno.EAFNOSUPPORT raises
AttributeError on a stock x86 build where it used to be correct — a loud
failure in place of a silent wrong answer. The companion PR below brings it
back from the built-in module on every platform.

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.

Drop EAFNOSUPPORT.  It is not in the default MICROPY_PY_ERRNO_LIST, so
the value here is the one that gets used, and 97 is right only for
Linux/x86: newlib has 106, Linux/mips 124, macOS 47.  What is left in
the table is codes 1 to 34, which glibc, musl, newlib and py/mperrno.h
agree on, and four codes the default list supplies anyway.

Signed-off-by: Kirill Lukonin (Evil Wireless Man) <klukonin@gmail.com>
@Josverl

Josverl commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

I'm just a simple Windows guy, so I'm sure I do not understand all the intricacies of the different error codes for the same error on one platform, but I do wonder if this proposed changes makes it simpler or harder from a MicroPython module perspective ?

How do you intend that this makes it possible for a MicroPython module to know which error to check for?
How should the module "know" on which platform/headers the firmware it runs on was built , and how to adapt to that ?

@klukonin

Copy link
Copy Markdown
Author

@Josverl

Good question from a Windows guy)))
For example, some kind of a module written against this package already does the portable thing:

try:
    s.connect(addr)
except OSError as e:
    if e.args[0] == errno.ETIMEDOUT:
        print("TIMEOUT!!!")

The name is the portable part. The number behind it is not, and supplying it is
the firmware's job — which the interpreter already does: py/mperrno.h takes the
values from the headers the firmware was compiled against. So on a Windows build
the built-in errno has ETIMEDOUT == 138, the MSVC/UCRT value.

What this package does today is overwrite that with the Linux/x86 numbers,
because errno is an extensible module and a file found on sys.path replaces
the built-in one outright:

built-in errno on Windows with this package installed, today
ETIMEDOUT 138 110
ECONNRESET 108 104
ENOTCONN 126 107
EINPROGRESS 112 115

OSError still carries 138, because that comes from the C side. So
e.args[0] == errno.ETIMEDOUT compares 138 against 110 and is quietly never
true. That is the case where the module author is forced to care about the
platform — and it is the status quo, not the proposal.

After this change the package stops overriding those names. errno.ETIMEDOUT
becomes 138 on your Windows build, 145 on the OpenWrt mips box that prompted
this, 60 on macOS, 110 on Linux/x86 — and the module source above is byte for
byte the same on all of them, with no platform detection anywhere. The module
never learns which headers the firmware used, and it does not need to.

The one thing a module must not do is write the number instead of the name.
That was never portable; what this package does today just makes it look like it
is.

@klukonin

Copy link
Copy Markdown
Author

@dpgeorge @agatti FYI

One finding from digging into this, and one question I would rather
have settled before putting more work in.

The finding. The obvious "fix it properly" move is to expose more of
py/mperrno.h through MICROPY_PY_ERRNO_LIST. That cannot be done in the
default list in py/moderrno.c: of the 48 MP_E* names, ENOTBLK is one that
Windows does not have — neither the MSVC CRT nor mingw-w64 defines it.
py/mperrno.h has #define MP_ENOTBLK ENOTBLK unguarded, so it compiles today
only because nothing references it; adding X(ENOTBLK) would expand to an
undeclared identifier and break the Windows build.

Windows is also a fourth distinct numbering, worth recording next to the
Linux/mips and macOS ones above: ETIMEDOUT 138, ECONNRESET 108, ENOTCONN
126, EINPROGRESS 112, EAFNOSUPPORT 102.

So IMHO the workable shape is a per-port list.

The question. Given that, is python-stdlib/errno worth keeping at all?
MicroPython is always built for a specific target and py/mperrno.h already has
that target's values; this file cannot know anything the C build does not, while
the reverse is not true. It exists only because the default list exposes 22 of
the 48, and it fills the rest with numbers it has to guess.
I understand It could be a size win (about 640 bytes of .mpy against roughly 2 KB for the same names in C).

The one case that genuinely needs it is firmware that cannot be rebuilt, where
mip install errno is the only way to get something like errno.ESRCH. This PR
is scoped to serve exactly that case and nothing more: it keeps only codes 1 to
34, which glibc, musl, newlib and the MSVC CRT all agree on, and defers
everything else to the built-in module. (macOS agrees there too, except for 11,
which is EDEADLK rather than EAGAIN — and EAGAIN is in the default list,
so it comes from the built-in module there anyway.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants