Issue #1798: Add ctypes calling convention that allows safe access to
authorThomas Heller <theller@ctypes.org>
Wed, 4 Jun 2008 18:59:03 +0000 (18:59 +0000)
committerThomas Heller <theller@ctypes.org>
Wed, 4 Jun 2008 18:59:03 +0000 (18:59 +0000)
errno (and LastError, on Windows).

ctypes maintains a module-global, but thread-local, variable that
contains an error number; called 'ctypes_errno' for this discussion.
This variable is a private copy of the systems 'errno' value; the copy
is swapped with the 'errno' variable on several occasions.

Foreign functions created with CDLL(..., use_errno=True), when called,
swap the values just before the actual function call, and swapped
again immediately afterwards.  The 'use_errno' parameter defaults to
False, in this case 'ctypes_errno' is not touched.

The values are also swapped immeditately before and after ctypes
callback functions are called, if the callbacks are constructed using
the new optional use_errno parameter set to True: CFUNCTYPE(..., use_errno=TRUE)
or WINFUNCTYPE(..., use_errno=True).

Two new ctypes functions are provided to access the 'ctypes_errno'
value from Python:

- ctypes.set_errno(value) sets ctypes_errno to 'value', the previous
  ctypes_errno value is returned.

- ctypes.get_errno() returns the current ctypes_errno value.

---

On Windows, the same scheme is implemented for the error value which
is managed by the GetLastError() and SetLastError() windows api calls.

The ctypes functions are 'ctypes.set_last_error(value)' and
'ctypes.get_last_error()', the CDLL and WinDLL optional parameter is
named 'use_last_error', defaults to False.

---

On Windows, TlsSetValue and TlsGetValue calls are used to provide
thread local storage for the variables; ctypes compiled with __GNUC__
uses __thread variables.

Lib/ctypes/__init__.py
Lib/ctypes/test/test_errno.py [new file with mode: 0644]
Misc/NEWS
Modules/_ctypes/_ctypes.c
Modules/_ctypes/callbacks.c
Modules/_ctypes/callproc.c
Modules/_ctypes/ctypes.h

index 41d39dc93f12ef0c51bdd13c866250856078b880..d3e11dc663ac52d51e019a35fe8cf3ba91ca21fa 100644 (file)
@@ -33,7 +33,9 @@ if _os.name == "posix" and _sys.platform == "darwin":
         DEFAULT_MODE = RTLD_GLOBAL
 
 from _ctypes import FUNCFLAG_CDECL as _FUNCFLAG_CDECL, \
-     FUNCFLAG_PYTHONAPI as _FUNCFLAG_PYTHONAPI
+     FUNCFLAG_PYTHONAPI as _FUNCFLAG_PYTHONAPI, \
+     FUNCFLAG_USE_ERRNO as _FUNCFLAG_USE_ERRNO, \
+     FUNCFLAG_USE_LASTERROR as _FUNCFLAG_USE_LASTERROR
 
 """
 WINOLEAPI -> HRESULT
@@ -73,8 +75,9 @@ def c_buffer(init, size=None):
     return create_string_buffer(init, size)
 
 _c_functype_cache = {}
-def CFUNCTYPE(restype, *argtypes):
-    """CFUNCTYPE(restype, *argtypes) -> function prototype.
+def CFUNCTYPE(restype, *argtypes, **kw):
+    """CFUNCTYPE(restype, *argtypes,
+                 use_errno=False, use_last_error=False) -> function prototype.
 
     restype: the result type
     argtypes: a sequence specifying the argument types
@@ -88,14 +91,21 @@ def CFUNCTYPE(restype, *argtypes):
     prototype((ordinal number, dll object)[, paramflags]) -> foreign function exported by ordinal
     prototype((function name, dll object)[, paramflags]) -> foreign function exported by name
     """
+    flags = _FUNCFLAG_CDECL
+    if kw.pop("use_errno", False):
+        flags |= _FUNCFLAG_USE_ERRNO
+    if kw.pop("use_last_error", False):
+        flags |= _FUNCFLAG_USE_LASTERROR
+    if kw:
+        raise ValueError("unexpected keyword argument(s) %s" % kw.keys())
     try:
-        return _c_functype_cache[(restype, argtypes)]
+        return _c_functype_cache[(restype, argtypes, flags)]
     except KeyError:
         class CFunctionType(_CFuncPtr):
             _argtypes_ = argtypes
             _restype_ = restype
-            _flags_ = _FUNCFLAG_CDECL
-        _c_functype_cache[(restype, argtypes)] = CFunctionType
+            _flags_ = flags
+        _c_functype_cache[(restype, argtypes, flags)] = CFunctionType
         return CFunctionType
 
 if _os.name in ("nt", "ce"):
@@ -106,16 +116,23 @@ if _os.name in ("nt", "ce"):
         _FUNCFLAG_STDCALL = _FUNCFLAG_CDECL
 
     _win_functype_cache = {}
-    def WINFUNCTYPE(restype, *argtypes):
+    def WINFUNCTYPE(restype, *argtypes, **kw):
         # docstring set later (very similar to CFUNCTYPE.__doc__)
+        flags = _FUNCFLAG_STDCALL
+        if kw.pop("use_errno", False):
+            flags |= _FUNCFLAG_USE_ERRNO
+        if kw.pop("use_last_error", False):
+            flags |= _FUNCFLAG_USE_LASTERROR
+        if kw:
+            raise ValueError("unexpected keyword argument(s) %s" % kw.keys())
         try:
-            return _win_functype_cache[(restype, argtypes)]
+            return _win_functype_cache[(restype, argtypes, flags)]
         except KeyError:
             class WinFunctionType(_CFuncPtr):
                 _argtypes_ = argtypes
                 _restype_ = restype
-                _flags_ = _FUNCFLAG_STDCALL
-            _win_functype_cache[(restype, argtypes)] = WinFunctionType
+                _flags_ = flags
+            _win_functype_cache[(restype, argtypes, flags)] = WinFunctionType
             return WinFunctionType
     if WINFUNCTYPE.__doc__:
         WINFUNCTYPE.__doc__ = CFUNCTYPE.__doc__.replace("CFUNCTYPE", "WINFUNCTYPE")
@@ -124,6 +141,7 @@ elif _os.name == "posix":
     from _ctypes import dlopen as _dlopen
 
 from _ctypes import sizeof, byref, addressof, alignment, resize
+from _ctypes import get_errno, set_errno
 from _ctypes import _SimpleCData
 
 def _check_size(typ, typecode=None):
@@ -313,12 +331,24 @@ class CDLL(object):
     Calling the functions releases the Python GIL during the call and
     reacquires it afterwards.
     """
-    class _FuncPtr(_CFuncPtr):
-        _flags_ = _FUNCFLAG_CDECL
-        _restype_ = c_int # default, can be overridden in instances
+    _func_flags_ = _FUNCFLAG_CDECL
+    _func_restype_ = c_int
 
-    def __init__(self, name, mode=DEFAULT_MODE, handle=None):
+    def __init__(self, name, mode=DEFAULT_MODE, handle=None,
+                 use_errno=False,
+                 use_last_error=False):
         self._name = name
+        flags = self._func_flags_
+        if use_errno:
+            flags |= _FUNCFLAG_USE_ERRNO
+        if use_last_error:
+            flags |= _FUNCFLAG_USE_LASTERROR
+
+        class _FuncPtr(_CFuncPtr):
+            _flags_ = flags
+            _restype_ = self._func_restype_
+        self._FuncPtr = _FuncPtr
+
         if handle is None:
             self._handle = _dlopen(self._name, mode)
         else:
@@ -348,9 +378,7 @@ class PyDLL(CDLL):
     access Python API functions.  The GIL is not released, and
     Python exceptions are handled correctly.
     """
-    class _FuncPtr(_CFuncPtr):
-        _flags_ = _FUNCFLAG_CDECL | _FUNCFLAG_PYTHONAPI
-        _restype_ = c_int # default, can be overridden in instances
+    _func_flags_ = _FUNCFLAG_CDECL | _FUNCFLAG_PYTHONAPI
 
 if _os.name in ("nt", "ce"):
 
@@ -358,9 +386,7 @@ if _os.name in ("nt", "ce"):
         """This class represents a dll exporting functions using the
         Windows stdcall calling convention.
         """
-        class _FuncPtr(_CFuncPtr):
-            _flags_ = _FUNCFLAG_STDCALL
-            _restype_ = c_int # default, can be overridden in instances
+        _func_flags_ = _FUNCFLAG_STDCALL
 
     # XXX Hm, what about HRESULT as normal parameter?
     # Mustn't it derive from c_long then?
@@ -384,9 +410,8 @@ if _os.name in ("nt", "ce"):
         HRESULT error values are automatically raised as WindowsError
         exceptions.
         """
-        class _FuncPtr(_CFuncPtr):
-            _flags_ = _FUNCFLAG_STDCALL
-            _restype_ = HRESULT
+        _func_flags_ = _FUNCFLAG_STDCALL
+        _func_restype_ = HRESULT
 
 class LibraryLoader(object):
     def __init__(self, dlltype):
@@ -424,6 +449,7 @@ if _os.name in ("nt", "ce"):
         GetLastError = windll.kernel32.GetLastError
     else:
         GetLastError = windll.coredll.GetLastError
+    from _ctypes import get_last_error, set_last_error
 
     def WinError(code=None, descr=None):
         if code is None:
diff --git a/Lib/ctypes/test/test_errno.py b/Lib/ctypes/test/test_errno.py
new file mode 100644 (file)
index 0000000..b80656b
--- /dev/null
@@ -0,0 +1,76 @@
+import unittest, os, errno
+from ctypes import *
+from ctypes.util import find_library
+import threading
+
+class Test(unittest.TestCase):
+    def test_open(self):
+        libc_name = find_library("c")
+        if libc_name is not None:
+            libc = CDLL(libc_name, use_errno=True)
+            if os.name == "nt":
+                libc_open = libc._open
+            else:
+                libc_open = libc.open
+
+            libc_open.argtypes = c_char_p, c_int
+
+            self.failUnlessEqual(libc_open("", 0), -1)
+            self.failUnlessEqual(get_errno(), errno.ENOENT)
+
+            self.failUnlessEqual(set_errno(32), errno.ENOENT)
+            self.failUnlessEqual(get_errno(), 32)
+
+
+            def _worker():
+                set_errno(0)
+
+                libc = CDLL(libc_name, use_errno=False)
+                if os.name == "nt":
+                    libc_open = libc._open
+                else:
+                    libc_open = libc.open
+                libc_open.argtypes = c_char_p, c_int
+                self.failUnlessEqual(libc_open("", 0), -1)
+                self.failUnlessEqual(get_errno(), 0)
+
+            t = threading.Thread(target=_worker)
+            t.start()
+            t.join()
+
+            self.failUnlessEqual(get_errno(), 32)
+            set_errno(0)
+
+    if os.name == "nt":
+
+        def test_GetLastError(self):
+            dll = WinDLL("kernel32", use_last_error=True)
+            GetModuleHandle = dll.GetModuleHandleA
+            GetModuleHandle.argtypes = [c_wchar_p]
+
+            self.failUnlessEqual(0, GetModuleHandle("foo"))
+            self.failUnlessEqual(get_last_error(), 126)
+
+            self.failUnlessEqual(set_last_error(32), 126)
+            self.failUnlessEqual(get_last_error(), 32)
+
+            def _worker():
+                set_last_error(0)
+
+                dll = WinDLL("kernel32", use_last_error=False)
+                GetModuleHandle = dll.GetModuleHandleW
+                GetModuleHandle.argtypes = [c_wchar_p]
+                GetModuleHandle("bar")
+
+                self.failUnlessEqual(get_last_error(), 0)
+
+            t = threading.Thread(target=_worker)
+            t.start()
+            t.join()
+
+            self.failUnlessEqual(get_last_error(), 32)
+
+            set_last_error(0)
+
+if __name__ == "__main__":
+    unittest.main()
index bec09804c374044c90d6f12bc37a9bee1b867904..830f6681e821b34757fae03b18ea782e70ee46b0 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -72,6 +72,9 @@ Extension Modules
 Library
 -------
 
+- Issue #1798: Add ctypes calling convention that allows safe access
+  to errno.
+
 - Patch #2125: Add GetInteger and GetString methods for 
   msilib.Record objects.
 
index 740b7f6e15f1865a3012a84da3bf0eb9f83b2c39..7deb3f37a54a28500f182addb8a6db610be634f9 100644 (file)
@@ -3271,7 +3271,7 @@ CFuncPtr_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
        thunk = AllocFunctionCallback(callable,
                                      dict->argtypes,
                                      dict->restype,
-                                     dict->flags & FUNCFLAG_CDECL);
+                                     dict->flags);
        if (!thunk)
                return NULL;
 
@@ -5273,6 +5273,17 @@ init_ctypes(void)
        if (!m)
                return;
 
+#ifdef MS_WIN32
+       dwTlsIndex_LastError = TlsAlloc();
+       dwTlsIndex_errno = TlsAlloc();
+       if (dwTlsIndex_LastError == TLS_OUT_OF_INDEXES
+           || dwTlsIndex_errno == TLS_OUT_OF_INDEXES) {
+               PyErr_SetString(PyExc_MemoryError,
+                               "Could not allocate TLSIndex for LastError value");
+               return;
+       }
+#endif
+
        _pointer_type_cache = PyDict_New();
        if (_pointer_type_cache == NULL)
                return;
@@ -5394,6 +5405,8 @@ init_ctypes(void)
        PyModule_AddObject(m, "FUNCFLAG_STDCALL", PyInt_FromLong(FUNCFLAG_STDCALL));
 #endif
        PyModule_AddObject(m, "FUNCFLAG_CDECL", PyInt_FromLong(FUNCFLAG_CDECL));
+       PyModule_AddObject(m, "FUNCFLAG_USE_ERRNO", PyInt_FromLong(FUNCFLAG_USE_ERRNO));
+       PyModule_AddObject(m, "FUNCFLAG_USE_LASTERROR", PyInt_FromLong(FUNCFLAG_USE_LASTERROR));
        PyModule_AddObject(m, "FUNCFLAG_PYTHONAPI", PyInt_FromLong(FUNCFLAG_PYTHONAPI));
        PyModule_AddStringConstant(m, "__version__", "1.1.0");
 
index b78c5282c88891e3c5b58a053b76bc9baca88d1c..78c7419ce44ddbaebbb5dcb1b4ab67deee474d67 100644 (file)
@@ -189,6 +189,7 @@ static void _CallPythonObject(void *mem,
                              SETFUNC setfunc,
                              PyObject *callable,
                              PyObject *converters,
+                             int flags,
                              void **pArgs)
 {
        Py_ssize_t i;
@@ -271,8 +272,22 @@ static void _CallPythonObject(void *mem,
 #define CHECK(what, x) \
 if (x == NULL) _AddTraceback(what, "_ctypes/callbacks.c", __LINE__ - 1), PyErr_Print()
 
+       if (flags & FUNCFLAG_USE_ERRNO)
+               _swap_errno();
+#ifdef MS_WIN32
+       if (flags & FUNCFLAG_USE_LASTERROR)
+               _swap_last_error();
+#endif
+
        result = PyObject_CallObject(callable, arglist);
        CHECK("'calling callback function'", result);
+
+#ifdef MS_WIN32
+       if (flags & FUNCFLAG_USE_LASTERROR)
+               _swap_last_error();
+#endif
+       if (flags & FUNCFLAG_USE_ERRNO)
+               _swap_errno();
        if ((restype != &ffi_type_void) && result) {
                PyObject *keep;
                assert(setfunc);
@@ -322,6 +337,7 @@ static void closure_fcn(ffi_cif *cif,
                          p->setfunc,
                          p->callable,
                          p->converters,
+                         p->flags,
                          args);
 }
 
@@ -351,7 +367,7 @@ static CThunkObject* CThunkObject_new(Py_ssize_t nArgs)
 CThunkObject *AllocFunctionCallback(PyObject *callable,
                                    PyObject *converters,
                                    PyObject *restype,
-                                   int is_cdecl)
+                                   int flags)
 {
        int result;
        CThunkObject *p;
@@ -371,6 +387,7 @@ CThunkObject *AllocFunctionCallback(PyObject *callable,
                goto error;
        }
 
+       p->flags = flags;
        for (i = 0; i < nArgs; ++i) {
                PyObject *cnv = PySequence_GetItem(converters, i);
                if (cnv == NULL)
@@ -398,7 +415,7 @@ CThunkObject *AllocFunctionCallback(PyObject *callable,
 
        cc = FFI_DEFAULT_ABI;
 #if defined(MS_WIN32) && !defined(_WIN32_WCE) && !defined(MS_WIN64)
-       if (is_cdecl == 0)
+       if ((flags & FUNCFLAG_CDECL) == 0)
                cc = FFI_STDCALL;
 #endif
        result = ffi_prep_cif(&p->cif, cc,
index 95b2709e7982c2bba9c9abc4c0a535c4c2b54c4e..70ca73f10a03e3d2b485754702364587376d63b0 100644 (file)
 #define DONT_USE_SEH
 #endif
 
+/*
+  ctypes maintains a module-global, but thread-local, variable that contains
+  an error number; called 'ctypes_errno' for this discussion.  This variable
+  is a private copy of the systems 'errno' value; the copy is swapped with the
+  'errno' variable on several occasions.
+
+  Foreign functions created with CDLL(..., use_errno=True), when called, swap
+  the values just before the actual function call, and swapped again
+  immediately afterwards.  The 'use_errno' parameter defaults to False, in
+  this case 'ctypes_errno' is not touched.
+
+  The values are also swapped immeditately before and after ctypes callback
+  functions are called, if the callbacks are constructed using the new
+  optional use_errno parameter set to True: CFUNCTYPE(..., use_errno=TRUE) or
+  WINFUNCTYPE(..., use_errno=True).
+
+  Two new ctypes functions are provided to access the 'ctypes_errno' value
+  from Python:
+
+  - ctypes.set_errno(value) sets ctypes_errno to 'value', the previous
+    ctypes_errno value is returned.
+
+  - ctypes.get_errno() returns the current ctypes_errno value.
+
+  ---
+
+  On Windows, the same scheme is implemented for the error value which is
+  managed by the GetLastError() and SetLastError() windows api calls.
+
+  The ctypes functions are 'ctypes.set_last_error(value)' and
+  'ctypes.get_last_error()', the CDLL and WinDLL optional parameter is named
+  'use_last_error', defaults to False.
+
+  ---
+
+  On Windows, TlsSetValue and TlsGetValue calls are used to provide thread
+  local storage for the variables; ctypes compiled with __GNUC__ uses __thread
+  variables.
+*/
+
+#if defined(MS_WIN32)
+DWORD dwTlsIndex_LastError;
+DWORD dwTlsIndex_errno;
+
+void
+_swap_last_error(void)
+{
+       DWORD temp = GetLastError();
+       SetLastError((DWORD)TlsGetValue(dwTlsIndex_LastError));
+       TlsSetValue(dwTlsIndex_LastError, (void *)temp);
+}
+
+static PyObject *
+get_last_error(PyObject *self, PyObject *args)
+{
+       return PyInt_FromLong((DWORD)TlsGetValue(dwTlsIndex_LastError));
+}
+
+static PyObject *
+set_last_error(PyObject *self, PyObject *args)
+{
+       DWORD new_value, prev_value;
+       if (!PyArg_ParseTuple(args, "i", &new_value))
+               return NULL;
+       prev_value = (DWORD)TlsGetValue(dwTlsIndex_LastError);
+       TlsSetValue(dwTlsIndex_LastError, (void *)new_value);
+       return PyInt_FromLong(prev_value);
+}
+
+void
+_swap_errno(void)
+{
+       int temp = errno;
+       errno = (int)TlsGetValue(dwTlsIndex_errno);
+       TlsSetValue(dwTlsIndex_errno, (void *)temp);
+}
+
+static PyObject *
+get_errno(PyObject *self, PyObject *args)
+{
+       return PyInt_FromLong((int)TlsGetValue(dwTlsIndex_errno));
+}
+
+static PyObject *
+set_errno(PyObject *self, PyObject *args)
+{
+       int new_value, prev_value;
+       if (!PyArg_ParseTuple(args, "i", &new_value))
+               return NULL;
+       prev_value = (int)TlsGetValue(dwTlsIndex_errno);
+       TlsSetValue(dwTlsIndex_errno, (void *)new_value);
+       return PyInt_FromLong(prev_value);
+}
+
+#elif defined(__GNUC__)
+static __thread int ctypes_errno;
+
+void
+_swap_errno(void)
+{
+       int temp = errno;
+       errno = ctypes_errno;
+       ctypes_errno = temp;
+}
+
+static PyObject *
+get_errno(PyObject *self, PyObject *args)
+{
+       return PyInt_FromLong(ctypes_errno);
+}
+
+static PyObject *
+set_errno(PyObject *self, PyObject *args)
+{
+       int new_errno;
+       if (!PyArg_ParseTuple(args, "i", &new_errno))
+               return NULL;
+       return PyInt_FromLong(_save_errno(new_errno));
+}
+#else
+
+#error "TLS not implemented in this configuration"
+
+#endif
+
 #ifdef MS_WIN32
 PyObject *ComError;
 
@@ -660,7 +785,11 @@ static int _call_function_pointer(int flags,
        if ((flags & FUNCFLAG_PYTHONAPI) == 0)
                Py_UNBLOCK_THREADS
 #endif
+       if (flags & FUNCFLAG_USE_ERRNO)
+               _swap_errno();
 #ifdef MS_WIN32
+       if (flags & FUNCFLAG_USE_LASTERROR)
+               _swap_last_error();
 #ifndef DONT_USE_SEH
        __try {
 #endif
@@ -675,7 +804,11 @@ static int _call_function_pointer(int flags,
                ;
        }
 #endif
+       if (flags & FUNCFLAG_USE_LASTERROR)
+               _swap_last_error();
 #endif
+       if (flags & FUNCFLAG_USE_ERRNO)
+               _swap_errno();
 #ifdef WITH_THREAD
        if ((flags & FUNCFLAG_PYTHONAPI) == 0)
                Py_BLOCK_THREADS
@@ -1667,6 +1800,8 @@ pointer(PyObject *self, PyObject *arg)
 }
 
 PyMethodDef module_methods[] = {
+       {"get_errno", get_errno, METH_NOARGS},
+       {"set_errno", set_errno, METH_VARARGS},
        {"POINTER", POINTER, METH_O },
        {"pointer", pointer, METH_O },
        {"_unpickle", unpickle, METH_VARARGS },
@@ -1675,6 +1810,8 @@ PyMethodDef module_methods[] = {
        {"set_conversion_mode", set_conversion_mode, METH_VARARGS, set_conversion_mode_doc},
 #endif
 #ifdef MS_WIN32
+       {"get_last_error", get_last_error, METH_NOARGS},
+       {"set_last_error", set_last_error, METH_VARARGS},
        {"CopyComPointer", copy_com_pointer, METH_VARARGS, copy_com_pointer_doc},
        {"FormatError", format_error, METH_VARARGS, format_error_doc},
        {"LoadLibrary", load_library, METH_VARARGS, load_library_doc},
index 1a104cfb2c2fceb17949c2fd991ca5ccb6022608..a7c35628e1b327bec9a217a11bf7451348444f2c 100644 (file)
@@ -87,6 +87,7 @@ typedef struct {
        PyObject_VAR_HEAD
        ffi_closure *pcl; /* the C callable */
        ffi_cif cif;
+       int flags;
        PyObject *converters;
        PyObject *callable;
        PyObject *restype;
@@ -185,7 +186,7 @@ extern PyMethodDef module_methods[];
 extern CThunkObject *AllocFunctionCallback(PyObject *callable,
                                           PyObject *converters,
                                           PyObject *restype,
-                                          int stdcall);
+                                          int flags);
 /* a table entry describing a predefined ctypes type */
 struct fielddesc {
        char code;
@@ -303,6 +304,8 @@ PyObject *_CallProc(PPROC pProc,
 #define FUNCFLAG_CDECL   0x1
 #define FUNCFLAG_HRESULT 0x2
 #define FUNCFLAG_PYTHONAPI 0x4
+#define FUNCFLAG_USE_ERRNO 0x8
+#define FUNCFLAG_USE_LASTERROR 0x10
 
 #define TYPEFLAG_ISPOINTER 0x100
 #define TYPEFLAG_HASPOINTER 0x200
@@ -421,8 +424,16 @@ extern int IsSimpleSubType(PyObject *obj);
 
 extern PyObject *_pointer_type_cache;
 
+extern void _swap_errno(void);
+
 #ifdef MS_WIN32
+
+extern void _swap_last_error(void);
+
 extern PyObject *ComError;
+
+extern DWORD dwTlsIndex_LastError;
+extern DWORD dwTlsIndex_errno;
 #endif
 
 /*