Checking for Native Errors Using errno

Some C standard library functions indicate errors by setting the value of the C standard library macro errno. You can access this value with a FFM API linker option.

The Linker::downcallHandle method contains a varargs parameter that enables you to specify additional linker options. These parameters are of type Linker.Option.

One linker option is Linker.Option.captureCallState(String...), which you use to save portions of the execution state immediately after calling a foreign function associated with a downcall method handle. You can use it to capture certain thread-local variables. When used with the "errno" string, it captures the errno value as defined by the C standard library. Specify this linker option (with the "errno" string) when creating a downcall handle for a native function that sets errno.

An example of a C standard library function that sets errno is log, which computes the natural (base e) logarithm of its argument. If this value is less than zero, then errno is set to the value 33, which represents a domain error. As most users won't recognize 33 as a domain error, you can invoke the C standard library function strerror, which returns a textual description of the errno value.

The following example invokes the log function then uses captureCallState("errno") to obtain error messages set by the log function:

    static double invokeLog(double v) throws Throwable {
        
        double result = Double.NaN;
        
        // Setup handles
        Linker.Option ccs = Linker.Option.captureCallState("errno");
        StructLayout capturedStateLayout = Linker.Option.captureStateLayout();
        VarHandle errnoHandle = capturedStateLayout.varHandle(PathElement.groupElement("errno"));

        // log C Standard Library function
        Linker linker = Linker.nativeLinker();
        SymbolLookup stdLib = linker.defaultLookup();
        MethodHandle log = linker.downcallHandle(
            stdLib.find("log").orElseThrow(),
            FunctionDescriptor.of(ValueLayout.JAVA_DOUBLE, ValueLayout.JAVA_DOUBLE),
            ccs);
        
        // strerror C Standard Library function
        MethodHandle strerror = linker.downcallHandle(
            stdLib.find("strerror").orElseThrow(),
            FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.JAVA_INT));

        // Actual invocation
        try (Arena arena = Arena.ofConfined()) {
            MemorySegment capturedState = arena.allocate(capturedStateLayout);

            result = (double) log.invokeExact(capturedState, v);

            if (Double.isNaN(result)) {
                // Indicates that an error occurred per the documentation of
                // the 'log' command.
                
                // Get more information by consulting the value of errno:
                int errno = (int) errnoHandle.get(capturedState);
                System.out.println("errno: " + errno); // 33
                
                // Convert errno code to a string message:
                String errrorString = ((MemorySegment) strerror.invokeExact(errno))
                    .reinterpret(Long.MAX_VALUE).getUtf8String(0);
                System.out.println("errno string: " + errrorString); // Domain error
            }
        }
        return result;        
    }

In this example, the method captureStateLayout() returns a structure layout of the errno function. See Memory Layouts and Structured Access for more information.

Suppose that you call invokeLog(double) as follows:

        System.out.println("log(2.718): " + invokeLog(2.718));
        System.out.println("log(-1): " + invokeLog(-1));

The example then prints output similar to the following:

log(2.718): 0.999896315728952
errno: 33
errno string: Domain error
log(-1): NaN

Tip:

Use the following code to obtain the names of the supported captured value layouts for the Linker.Option.captureCallState(String...) option for your operating system:
List<String> capturedNames = Linker.Option.captureStateLayout()
        .memberLayouts()
        .stream() 
        .map(MemoryLayout::name)
        .flatMap(Optional::stream)
        .toList();