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 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 objectstream
and stores it in an array pointed to bystr
. - 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 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 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();