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
filenamewith the modemode(in this example, the mode isr) 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 objectstreamand stores it in an array pointed to bystr. - int feof(FILE *stream): Returns a non-zero value if the FILE
pointer
streamhas encountered the end-of-file indicator. Otherwise, it returns zero. - int fclose(FILE *stream): Closes the file to which
streampoints.
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 asstatic 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 directoryIn 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();