javalinuxjava-nio

Get Linux file descriptor of FileChannel


Is there a way to get the Linux file descriptor for an opened FileChannel?

I need it to call mount.fuse -o fd=... (for implementing FUSE).

As a hacky workaround, I'm doing:

var pid = ProcessHandle.current().pid();
var fd = Files.list(Path.of("/proc/"+pid+"/fd")).count();
var fc = FileChannel.open(path);
System.out.println("file descriptor: " + fd);

Note that two file descriptors appear. One for path and another socket. I'm using the first one. What is the socket for?


Solution

  • You can use reflection to get the file descriptor from a RandomAccessFile:

    long pid = ProcessHandle.current().pid();
    long guessedFd = Files.list(Path.of("/proc/"+pid+"/fd")).count();
    
    var file = new java.io.RandomAccessFile(FUSE_DEVICE_PATH.toFile(), "rw");
    FileChannel fc = file.getChannel(); // Use FileChannel for fast NIO
    var javaFd = file.getFD();
    try {
        Field f = FileDescriptor.class.getDeclaredField("fd");
        f.setAccessible(true);
        var trueFd = (int) f.get(javaFd);
        return trueFd;
    }
    catch (InaccessibleObjectException | NoSuchFieldException | IllegalAccessException e) {
        return guessedFd;
    }
    

    On Java 11+ you will the jvm option --add-opens=java.base/java.io=ALL-UNNAMED at startup. Tested on Java 18.


    In Java 17 you can use SharedSecrets instead of reflection:

    FileDescriptor javaFd = ...
    try {
        int trueFd = jdk.internal.access.SharedSecrets.getJavaIOFileDescriptorAccess().get(javaFd);
        return trueFd;
    }
    catch (IllegalAccessError e) {
        return guessedFd;
    }
    

    You will need to add --add-exports=java.base/jdk.internal.access=ALL-UNNAMED when compiling and running. See for Maven.