Question: I'm trying to test some binary library with ctypes and some of my tests involve errno. I'm therefore trying to retrieve it to check the error cases handling but when trying to use ctypes.get_errno() I weirdly get 0 as errno ("Success") which isn't what I was expecting. Why does this occur? Is ctypes.get_errno() actually reliable?  test.py:  #!/usr/bin/env python3 import os import ctypes import errno libc = ctypes.cdll.LoadLibrary("libc.so.6") libc.write.restype = ctypes.c_ssize_t libc.write.argtypes = ctypes.c_int, ctypes.c_void_p, ctypes.c_size_t TMP_FILE = "/tmp/foo" def main(): fd: int errno: int = 0 fd = os.open(TMP_FILE, os.O_RDONLY | os.O_CREAT) if fd == -1: errno = ctypes.get_errno() print(strerror(errno)) if (not errno and libc.write(fd, "foo", 3) == -1): errno = ctypes.get_errno() print(f"ERRNO: {errno}") print(os.strerror(errno)) os.close(fd); os.remove(TMP_FILE) if errno: raise OSError(errno, os.strerror(errno)) if __name__ == "__main__": main()   output:  $ ./test.py ERRNO: 0 Success   NB: I already have a workaround from an answer under an other post (see MRE below) but I'd like to understand what's going on with ctypes.get_errno().  test_with_workaround.py:  #!/usr/bin/env python3 import os import ctypes libc = ctypes.cdll.LoadLibrary("libc.so.6") libc.write.restype = ctypes.c_ssize_t libc.write.argtypes = ctypes.c_int, ctypes.c_void_p, ctypes.c_size_t TMP_FILE = "/tmp/foo" _get_errno_loc = libc.__errno_location _get_errno_loc.restype = ctypes.POINTER(ctypes.c_int) def get_errno() -> int: return _get_errno_loc()[0] def main(): fd: int errno: int = 0 fd = os.open(TMP_FILE, os.O_RDONLY | os.O_CREAT) if fd == -1: errno = get_errno() print(strerror(errno)) if (not errno and libc.write(fd, "foo", 3) == -1): errno = get_errno() print(f"ERRNO: {errno}") print(os.strerror(errno)) os.close(fd); os.remove(TMP_FILE) if errno: raise OSError(errno, os.strerror(errno)) if __name__ == "__main__": main()   output:  $ ./test_with_workaround.py ERRNO: 9 Bad file descriptor Traceback (most recent call last): File "/mnt/nfs/homes/vmonteco/Code/MREs/MRE_python_fdopen_cause_errno/simple_python_test/./test_with_workaround.py", line 41, in <module> main() File "/mnt/nfs/homes/vmonteco/Code/MREs/MRE_python_fdopen_cause_errno/simple_python_test/./test_with_workaround.py", line 37, in main raise OSError(errno, os.strerror(errno)) OSError: [Errno 9] Bad file descriptor 

Answer: Unlike your handwritten get_errno, ctypes get_errno does not access the os errno value, but a private copy that is filled after certain function calls. The docs state:  [...] a ctypes mechanism that allows accessing the system errno error number in a safe way. ctypes maintains a thread-local copy of the systems errno variable; if you call foreign functions created with use_errno=True then the errno value before the function call is swapped with the ctypes private copy, the same happens immediately after the function call.  Thus ctypes.get_errno() will never work for getting errno that might have been set as a side-effect of os.open. But you can (and probably should) use it for your function that you call via ctypes. But there you need to set use_errno to True. use_errno is a parameter of function definition creation. When you use the mylib.myfunc syntax as you did with libc.write, the function creation is implicit and inherits some defaults from the library loader. Here you use ctypes.cdll, which sets use_errno to False. You an change that by loading the Library more explicitly: libc = ctypes.CDll("libc.so.6", use_errno=True)  Note that this will apply use_errno (and the associated overhead) to all functions. If you want to use use_errno for single functions, you can use function prototypes instead.