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 fopen(const char *filename, const char *mode), which opens a file using the give mode. Examples of modes include r, which opens a file for reading and w, which opens the file for writing. If fopen attempts to open a file that doesn't exist, then errno is set to the value 2, which means that the file doesn't exist. As most users won't know this, you can invoke the C standard library function strerror, which returns a textual description of the errno value.

The following example opens and reads a file with the following C standard library functions:

  • FILE *fopen(const char *filename, const char *mode): As mentioned previously, opens the file filename with the mode mode (in this example, the mode is r) and returns a pointer to a FILE object, which is represented by a MemorySegment.
  • char *strerror(int errnum): As mentioned previously, returns a pointer to an error message string that corresponds to the value of errnum.
  • char *fgets(char *str, int n, FILE *stream): Reads n−1 characters from a pointer to a FILE object stream and stores it in an array pointed to by str.
  • int feof(FILE *stream): Returns a non-zero value if the FILE pointer stream has encountered the end-of-file indicator. Otherwise, it returns zero.
  • int fclose(FILE *stream): Closes the file to which stream points.

The example reads a file that should exist, ReadFileWithFopen.java, and a file that shouldn't exist, file-doesnot-exist.txt. When the example invokes the fopen function, it uses captureCallState("errno") to obtain error messages set by it:

public class ReadFileWithFopen {
    
    static int BUFFER_SIZE = 1024;
    
    // Setup handles
    static final Linker.Option ccs = Linker.Option.captureCallState("errno");
    static final StructLayout capturedStateLayout = Linker.Option.captureStateLayout();
    static final VarHandle errnoHandle = capturedStateLayout.varHandle(PathElement.groupElement("errno"));
    
    // Linker and symbol lookup for C Standard Library functions
    static final Linker linker = Linker.nativeLinker();
    static final SymbolLookup stdLib = linker.defaultLookup();
    
    // char *strerror(int errnum)
    static final MethodHandle strerror = linker.downcallHandle(
        stdLib.find("strerror").orElseThrow(),
            FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.JAVA_INT));     
        
    // FILE *fopen(const char *filename, const char *mode)
    static final MethodHandle fopen = linker.downcallHandle(stdLib.find("fopen").orElseThrow(),
        FunctionDescriptor.of(
            ValueLayout.ADDRESS,
            ValueLayout.ADDRESS,
            ValueLayout.ADDRESS),
        ccs);
            
    // char *fgets(char *str, int n, FILE *stream)
    static final MethodHandle fgets = linker.downcallHandle(stdLib.find("fgets").orElseThrow(),
        FunctionDescriptor.of(
            ValueLayout.ADDRESS,
            ValueLayout.ADDRESS,
            ValueLayout.JAVA_INT,
            ValueLayout.ADDRESS));
                
    // int feof(FILE *stream)
    static final MethodHandle feof = linker.downcallHandle(stdLib.find("feof").orElseThrow(),
        FunctionDescriptor.of(
            ValueLayout.JAVA_INT,
            ValueLayout.ADDRESS));
    
    // int fclose(FILE *stream)
    static final MethodHandle fclose = linker.downcallHandle(stdLib.find("fclose").orElseThrow(),
        FunctionDescriptor.of(
            ValueLayout.JAVA_INT,
            ValueLayout.ADDRESS));    
    
    static void readFile(String path) throws Throwable {
        
        System.out.println("Reading " + path);
            
        // Actual invocation
        try (Arena arena = Arena.ofConfined()) {
            MemorySegment capturedState = arena.allocate(capturedStateLayout);
            MemorySegment location = arena.allocateFrom(path);
            MemorySegment openMode =  arena.allocateFrom("r");

            var filePointer = (MemorySegment) fopen.invokeExact(capturedState, location, openMode);

            if (filePointer.address() == 0) {
                printErrnoCode(errnoHandle, capturedState, strerror);
                return;
            }
            
            var buffer = arena.allocate(ValueLayout.JAVA_BYTE, BUFFER_SIZE);
            var eof = (int) feof.invokeExact(filePointer);
            
            while (eof == 0) {
                System.out.print(buffer.getString(0));
                var read = (MemorySegment) fgets.invokeExact(buffer, BUFFER_SIZE, filePointer);
                eof = (int) feof.invokeExact(filePointer);
            }
            
            var close = (int) fclose.invokeExact(filePointer);
            
        }
    }
    
    private static void printErrnoCode(
        VarHandle errnoHandle,
        MemorySegment capturedState,
        MethodHandle strerror) throws Throwable {
        
        // Get more information by consulting the value of errno:
        int errno = (int) errnoHandle.get(capturedState, 0);
                
        // An errno value of 2 (ENOENT) is "No such file or directory"
        System.out.println("errno: " + errno);

        // Convert errno code to a string message:
        String errrorString = ((MemorySegment) strerror.invokeExact(errno))
            .reinterpret(Long.MAX_VALUE).getString(0);
        System.out.println("errno string: " + errrorString);
    }
    
    public static void main(String[] args) {
        try {
            readFile("ReadFileWithFopen.java");
            readFile("file-does-not-exist.txt");
        } catch (Throwable t) {
            System.out.println(t.getMessage());
        }
    }
}

Tip:

It is recommend that you declare VarHandles and method handles as static final for performance reasons.

The example prints the following output:

Reading ReadFileWithFopen.java
import java.lang.foreign.*;
import java.lang.foreign.MemoryLayout.*;
import java.lang.invoke.*;
...
Reading file-does-not-exist.txt
errno: 2
errno string: No such file or directory

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

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();