Krikri::ControllingTty
Gives the CURRENT (plugin) process a controlling terminal, so that
anything it spawns afterwards can open("/dev/tty").
Why this exists: real ansible-core's ssh connection plugin asks for a
remote pty on essentially every module invocation - see
plugins/connection/ssh.py:
use_tty = self.get_option('use_tty')
...
if not in_data and sudoable and use_tty:
args = ('-tt', self.host, cmd)
and sudoable is True for ordinary module dispatch (it is only
forced False for the internal dd-based put_file/fetch_file
helpers). So under real Ansible the whole remote process tree - the
module, and whatever subprocess a command:/shell: task spawns -
inherits a controlling terminal, and /dev/tty is openable there.
This engine's SSHManager never passes -t/-tt, deliberately:
the same channel carries each plugin's JSON PluginResult back to
the controller, and a pty would merge stderr into stdout, translate
LF to CRLF, and change buffering - i.e. it would risk corrupting the
result protocol for all 100+ plugins, on every path (one-shot exec,
the bash -s batch script, and the length-prefixed persistent
daemon pipe alike). So instead of asking ssh for a tty, the process
that actually needs one manufactures its own, entirely on the target
host and entirely off the transport:
posix_openpt -> setsid -> TIOCSCTTY on the pty slave
Nothing about the ssh channel changes: stdin/stdout/stderr of this
process are untouched (still the pipes the transport handed us), so
the JSON result travels back byte-for-byte as before. The pty is a
side channel that only /dev/tty opens reach.
Found via the Galaxy role imntreal.smallstep_ca, whose
command: step ca init ... task fails under this engine with
"error allocating terminal: open /dev/tty: no such device or
address" while real ansible-playbook succeeds - the step CLI
opens /dev/tty unconditionally to render a banner, even when (as
there) every credential comes from --password-file and it never
actually reads from it.
Constants
Linux O_NOCTTY - opening a terminal with it must never make that terminal the caller's controlling one as a side effect (only the explicit TIOCSCTTY below is allowed to do that).
Linux asm-generic/ioctls.h. arg: 0 = don't steal the tty from
another session (impossible for a pty we just allocated anyway).
Deliberately namespaced here rather than at top level: the
expect plugin defines its own top-level TIOCSCTTY and both land
in the same fat-plugin compilation unit.
Crystal's own LibC bindings define VMIN but not VTIME (termios.cr) - Linux's c_cc index for it.
Class methods
Idempotent: safe to call before every spawn, and in particular
safe in the --persistent-daemon remote half, where one resident
process serves many tasks and must only ever do this once.
Returns true if this process has a controlling terminal when it returns. Never raises and never changes behavior on failure: if anything here is unavailable (no /dev/ptmx, no permission, already a session leader of a session that lost its tty, a non-Linux target), the caller simply proceeds exactly as it did before this existed.
True if this process already has a controlling terminal - the
normal case for ansible_connection=local runs from an
interactive shell, where the plugin binary inherits the user's own
terminal and there is nothing to do.