javamacosnativeerrnoproject-panama

Obtain the native value of errno from Java in macOS


I'm porting my JNA-based library to "pure" Java using the Foreign Function and Memory API (JEP 424) in JDK 19.

I've successfully implemented the sysctl() function and am fetching values using:

public static MethodHandle sysctl = Linker.nativeLinker().downcallHandle(
        SYSTEM_LIBRARY.lookup("sysctl").orElseThrow(), FunctionDescriptor.of(ValueLayout.JAVA_INT,
                ValueLayout.ADDRESS, ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS,
                ValueLayout.ADDRESS, ValueLayout.JAVA_LONG));

In the process of debugging before getting this working, I was occasionally getting a -1 return value:

Upon successful completion, the value 0 is returned; otherwise the value -1 is returned and the global variable errno is set to indicate the error.

In JNA, this is easily obtainable using Native.getLastError(). This answer shows how I could write code in JNI to fetch it, but the whole point of JEP 424 is to remove the need for JNA or JNI.

The Windows API has a helpful GetLastError function that can be mapped, as demonstrated in this test in the JDK

However, I was unable to find any native function on macOS that I could map to return errno. The closest I have found is perror() which prints the error to STDERR:

public static MethodHandle perror = Linker.nativeLinker()
        .downcallHandle(SYSTEM_LIBRARY.lookup("perror").orElseThrow(),
                FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS));

While output to STDERR was helpful in the near term, I'd like to use this value for exception handling/error messages.

This seems a common enough use case that there should be a method in the JDK for it, but I can't find it.

Any ideas other than temporarily redirecting STDERR into a string I can parse?


Solution

  • I don't have access to a mac to test this, but if this source is accurate: https://opensource.apple.com/source/xnu/xnu-4570.41.2/bsd/sys/errno.h.auto.html

    Then errno is a macro defined like so:

    __BEGIN_DECLS
    extern int * __error(void);
    #define errno (*__error())
    __END_DECLS
    

    It's a call to a function that returns an int* that points to the errno value.

    So, to access it from Java this should work:

    static final MethodHandle __error = Linker.nativeLinker()
        .downcallHandle(SYSTEM_LIBRARY.lookup("__error").orElseThrow(),
                FunctionDescriptor.of(ValueLayout.ADDRESS));
    
    static int errno() {
        try {
            MemoryAddress addr = (MemoryAddress) __error.invokeExact();
            return addr.get(ValueLayout.JAVA_INT, 0);
        } catch (Throwable t) {
            throw new RuntimeException(t);
        }
    }
    

    Be aware that at the moment (JDK 19) this is not a 100% reliable way to access errno, since the VM might block somewhere and override errno with its own system calls before the value can be read.

    We've been discussing ways to make it more reliable to access errno and other thread-locals that the VM can overwrite. At the same time, that should also make it much easier to access the value.