POSIX Native Access

POSIX Native Access

On POSIX systems (Linux and macOS), Æsh Readline needs access to terminal attributes, size, and raw I/O for interactive line editing. Starting with version 3.8, the terminal-tty module ships as a multi-release JAR with two implementations:

Java VersionImplementationHow It Works
8 – 21ExecPty (subprocess)Spawns stty and tty commands
22+FfmPty (FFM)Calls tcgetattr/tcsetattr/ioctl directly

On Java 22+, the Foreign Function & Memory API calls libc directly from pure Java – no subprocess spawning, no dependency on stty being installed.

What Changed

The ExecPty implementation spawns external processes for every terminal operation:

OperationExecPty (stty)FfmPty (FFM)
Get terminal attributesstty -a (~5ms)tcgetattr() (~5us)
Set terminal attributesstty <flags> (~5ms)tcsetattr() (~5us)
Get terminal sizestty size (~5ms)ioctl(TIOCGWINSZ) (~5us)
Discover TTY devicetty command (~5ms)open("/dev/tty") (~5us)

Terminal initialization, which involves 3-5 subprocess spawns, drops from ~15-25ms to ~25us.

Runtime Requirements

Applications running on Java 22+ must enable native access for the FFM API:

java --enable-native-access=ALL-UNNAMED -jar myapp.jar

On Java 8-21, no special flags are needed – ExecPty is used automatically.

How It Works

The multi-release JAR contains FfmPty alongside the existing ExecPty:

terminal-tty.jar
├── org/aesh/terminal/tty/impl/ExecPty.class                (stty, Java 8)
├── META-INF/versions/22/org/aesh/terminal/tty/impl/FfmPty.class  (FFM, Java 22+)
├── META-INF/versions/22/org/aesh/terminal/tty/impl/LibC.class
├── META-INF/versions/22/org/aesh/terminal/tty/impl/PosixConstants.class
└── META-INF/MANIFEST.MF                                    (Multi-Release: true)

TerminalBuilder tries FfmPty first via Class.forName(). If it fails (Java < 22, or FFM initialization error), it falls back to ExecPty. No configuration needed.

FfmPty implements the same Pty interface as ExecPty, so PosixSysTerminal, TerminalConnection, and all downstream code require zero changes.

Platform Differences

The FFM implementation handles Linux and macOS differences:

AspectLinuxmacOS
termios flag size4 bytes (unsigned int)8 bytes (unsigned long)
termios struct size60 bytes72 bytes
c_cc array size32 entries20 entries
VMIN index616
VTIME index517
TTY file descriptoropen("/dev/tty", O_RDWR)STDIN_FILENO (fd 0)
TIOCGWINSZ0x54130x40087468

On macOS, STDIN_FILENO is used instead of opening /dev/tty because poll() does not work reliably with a separately opened TTY on macOS.

Input I/O

FfmPty provides an InputStream that uses poll() + read() for terminal input:

  • poll() with 100ms timeout for responsiveness to close()
  • read() retries on EINTR (signal interruption between poll and read)
  • POLLHUP/POLLERR detection for EOF
  • No reader thread needed for the poll-based path (though the existing TerminalConnection read loop still runs in a thread)

Fallback Behavior

If FfmPty fails to initialize for any reason, TerminalBuilder logs a warning and falls back to ExecPty:

  • ClassNotFoundException (Java < 22) – logged at FINE level
  • IOException or UnsupportedOperationException from FfmPty.current() – logged at FINE level
  • Other exceptions (FFM bugs, platform issues) – logged at WARNING level

This means upgrading the JDK version transparently enables the FFM path with no code changes.

See Also