Fork me on GitHub

File Descriptors

File descriptors are identifiers (handles) for files, sockets, pipes or other resources.

To work with file descriptors, Java provides us with java.io.FileDescriptor. junixsocket elevates the use of java.io.FileDescriptor by

  1. allowing to send and receive them between processes (via UNIX domain sockets), and
  2. providing a sophisticated casting mechanism to access the referenced resource via Java classes such as Socket, InputStream, etc.
  3. passing file descriptors to new processes via ProcessBuilder.Redirect.

Getting a FileDescriptor reference

Anything that implements the junixsocket interface FileDescriptorAccess (all AFSockets, for example), can return the corresponding FileDescriptor:

AFSocket socket = ...;
FileDescriptor fd = socket.getFileDescriptor();

You can also get the file descriptor from some classes in the Java API, for example:

FileInputStream in = ...;
FileDescriptor fd = in.getFD();

RandomAccessFile raf = ...;
FileDescriptor fd = in.getFD();

// Standard file descriptors
FileDescriptor stdin = FileDescriptor.in;
FileDescriptor stdout = FileDescriptor.out;
FileDescriptor stderr = FileDescriptor.err;

Getting a FileDescriptor from a native fd integer value

You may use a somewhat “unsafe” operation to convert a system-native file descriptor, described as an integer value, to a FileDescriptor (or other types, see below), via FileDescriptorCast.unsafeUsing(fdVal).as(FileDescriptor.class).

This functionality may not be available in all environments (e.g., on Windows, or when manually disabled by setting the system property -Dorg.newsclub.net.unix.library.disable.CAPABILITY_UNSAFE=true).

Sending and receiving File Descriptors

A very useful feature of Unix Domain Sockets is the ability to send and receive file descriptors between processes.

This, for example, enables non-privileged processes to access otherwise restricted files: A privileged process (e.g., running as root, or as a UID with special access rights) opens the restricted file, and then exposes the file handle to another process via AF_UNIX sockets.

File descriptors are sent as so-called “ancillary messages” along with regulary payload. They cannot be sent alone, so make sure that you send at least some data, even if it's just one byte.

Sending file descriptors

AFUNIXSocket socket = ...

FileInputStream fin = new FileInputStream(file);
socket.setOutboundFileDescriptors(fin.getFD());
// you can also send more than one FD at the same time, just make sure they're all part of the same call to setOutboundFileDescriptors. 

// Ancillary messages are sent _along_ regular in-band messages, so we have to send something here.
os.write("Some message".getBytes("UTF-8"));

Receiving file descriptors

AFUNIXSocket socket = ...

// set ancillary receive buffer to a reasonable size (disabled by default!)
socket.setAncillaryReceiveBufferSize(1024);

InputStream in = socket.getInputStream();
// do you regular socket IO here
in.read(...)

// If there were any file descriptors sent as ancillary messages, check them right after a call to read
FileDescriptor[] descriptors = socket.getReceivedFileDescriptors();
if (descriptors != null) {
  for (FileDescriptor fd : descriptors) {
    FileInputStream fin = new FileInputStream(fd);
    // do something with the stream
  }
}

Also see FileDescriptorCast below for how to better handle file descriptors that describe sockets and other resources.

Even easier with RMI

If you're using junixsocket-rmi for inter-process communication, you can simply wrap streams with RemoteFileInput/RemoteFileOutput (or the generic RemoteFileDescriptor) and not worry about the technicalities:

FileInputStream fin = ...;

RemoteFileInput rfi = new RemoteFileInput(socketFactory, fin);
// rfi can now be used for inter-process communication via RMI:
someRMIService.someMethod(rfi);

// on the receiving side:
public void someMethod(RemoteFileInput rfi) throws IOException {
    FileInputStream fin = rfi.asFileInputStream();
    // ...
    fin.close(); // closes the stream for this process only (different file handle).
} 

Due diligence

It is recommended to authenticate the connection, since you certainly don't want unauthorized callers to obtain access to potentially secret information.

You may either use your own SSL/TLS authentication atop the Socket, or simply use peer credentials.

Not supported by all platforms

Not all platforms support file descriptors over AF_UNIX. Make sure they're available using

AFSocket.supports(AFSocketCapability.CAPABILITY_FILE_DESCRIPTORS).

“Casting” FileDescriptor objects to Socket etc.

When you receive a FileDescriptor from another process, you want to use it as if it was created locally. While the Java standard API does not provide us with much more than a FileDescriptor-taking constructor for java.io.FileInputStream/FileOutputStream, junixsocket gives you more control via FileDescriptorCast:

First, you instantiate a FileDescriptorCast instance using the FileDescriptor of your choice, then you specify as what class you want to access it:

FileDescriptor fd = ...;
// NOTE: check `fd.valid()` or an `IOException` may be thrown.
Class<T> desiredClass = ...;
T instance = FileDescriptorCast.using(fd).as(desiredClass);

For example, if you want to simply read from this file descriptor, use:

InputStream in = FileDescriptorCast.using(fd).as(InputStream.class);

If the file descriptor is a Socket, you can use:

Socket sock = FileDescriptorCast.using(fd).as(Socket.class);
// or:
AFSocket sock = FileDescriptorCast.using(fd).as(AFSocket.class);
// or:
AFUNIXSocket sock = FileDescriptorCast.using(fd).as(AFUNIXSocket.class);
// etc.

If you want to access the native file descriptor value as an integer (only where supported), you can use:

int fdVal = FileDescriptorCast.using(fd).as(Integer.class); // won't work for all types on Windows

Note that if the specified FileDescriptor is incompatible with the target class, a ClassCastException is thrown. Also be aware that this technically isn't a cast, since a different object reference is returned.

In order to check if a file descriptor can be “cast” to a particular class, you can use FileDescriptorCast#isAvailable and FileDescriptorCast#availableTypes:

FileDescriptorCast fdc = FileDescriptorCast.using(fd);
if (fdc.isAvailable(Socket.class)) {
	Socket socket = fdc.as(Socket.class);
} else {
	throw new IllegalStateException("Cannot cast to Socket, only to: " + fdc.availableTypes());
}

Sending a FileDescriptor via ProcessBuilder.Redirect

You can pass a FileDescriptor as standard input (“stdin”) to a process launched with ProcessBuilder:

FileDescriptor fd = ...;
ProcessBuilder pb = ...;
pb.redirectInput(FileDescriptorCast.using(fd).as(ProcessBuilder.Redirect.class));
Process p = pb.start();

Not supported by all platforms

Note that this functionality is only available if the environment supports it (Java 9 or newer, and not on Windows). Moreover, the feature currently uses Java SDK internals that may change/disappear.

Be sure to check availability with

if (AFSocket.supports(AFSocketCapability.CAPABILITY_FD_AS_REDIRECT)) {...}

Important considerations

  • On some platforms (e.g., Solaris, Illumos) you may need to re-apply a read timeout (e.g., using Socket#setSoTimeout(int) after obtaining the socket.

  • You may lose Java port information for AFSocketAddress implementations that do not encode this information directly (such as AFUNIXSocketAddress and AFTIPCSocketAddress)

  • The “blocking” state of a socket may be forcibly changed to “blocking” when performing the cast, especially when casting to Socket, DatagramSocket or ServerSocket and any of their subclasses where “blocking” is the expected state.

  • When calling FileDescriptorCast#using(FileDescriptor) for a FileDescriptor obtained from another socket or other resource in the same JVM (i.e., not from another process), especially for sockets provided by junixsocket itself, there is a chance that the garbage collector may clean up the original socket at an opportune moment, thereby closing the resource underlying the shared file descriptor prematurely.

    This is considered an edge-case, and deliberately not handled automatically for performance and portability reasons: We would have to do additional reference counting on all FileDescriptor instances, either through patching FileCleanable or a shared data structure.

    The issue can be prevented by keeping a reference to the original object, such as keeping it in an enclosing try-with-resources block or as a member variable, for example. Alternatively, using a “duplicate” file descriptor (via FileDescriptorCast#duplicating(FileDescriptor) circumvents this problem, at the cost of using additional system resources.

  • As a consequence of the previous point: For FileDescriptorCast#using(FileDescriptor): when casting file descriptors that belong to a junixsocket-controlled socket, the target socket is configured in a way such that garbage collection will not automatically close the target's underlying file descriptor (but still potentially any file descriptors received from other processes via ancillary messages).

  • The same restrictions as for #using(FileDescriptor) apply to `#unsafeUsing(int) as well.