I've dug more into the code and came up with something a bit nicer than what I originally described. Feel free to look at the
changes (comments and suggestions are always welcome).
My basic assumption is that the underlying CFFI-SYS functions like %foreign-funcall and defcfun-helper-forms would return two values whenever defcfun or foreign-funcall is configured to return errno. The first value is the foreign call return value, and the second is errno. errno is then captured to be passed to the user's structure, and the foreign call return value is propagated further.
Here's a sample of how it looks:
CL-USER> (defparameter errno-object (cffi:make-errno :value 0))
ERRNO-OBJECT
CL-USER> (cffi:defcfun ("socket" :errno t) :int
(a :int)
(b :int)
(c :int))
Warning: :call-direct ignored because :error-value specified.
SOCKET
CL-USER> (socket errno-object -1 -1 -1)
-1
CL-USER> errno-object
#S(CFFI::ERRNO :VALUE 22)
More:
CL-USER> (defparameter errno-object (cffi:make-errno :value 0))
ERRNO-OBJECT
CL-USER> (cffi:foreign-funcall ("getenv" :errno errno-object) :string "SHELL" :string+ptr)
("/bin/bash" 140735646882874)
CL-USER> errno-object
#S(CFFI::ERRNO :VALUE 0)
Last one:
CL-USER> (defparameter errno-object (cffi:make-errno :value 0))
ERRNO-OBJECT
CL-USER> (cffi:defcfun ("printf" :errno t) :int
(control :string)
&rest)
PRINTF
CL-USER> (printf errno-object "%s %s" :string "hi" :string "bob")
6
CL-USER> errno-object
#S(CFFI::ERRNO :VALUE 0)