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?
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.