Drafter.Pty (drafter v0.3.1)

Copy Markdown View Source

Runs a program on a pseudoterminal and hands its byte stream to the caller.

spawn/2 allocates a pty, runs program on it in a new session with the pty as its controlling terminal, and returns a handle holding two ports. No relaying process stands between the caller and the program; os_pid/1 is the program's own pid.

{:ok, pty} = Drafter.Pty.spawn("/bin/sh", args: ["-i"], cols: 100, rows: 30)
Drafter.Pty.write(pty, "echo hi\n")

The process that called spawn/2 owns both ports and receives:

  • {port, {:data, bytes}} — output, where port is the handle's io port
  • {port, {:exit_status, status}} — exit, where port is the handle's control port

Bytes written with write/2 go to the program's standard input. resize/3 sets the terminal size, which makes the kernel deliver SIGWINCH to the foreground process group.

The slave descriptor stays open for the lifetime of the handle. Exit is therefore reported only through :exit_status on the control port; the io port does not reach end-of-file when the program exits. close/1 releases both descriptors.

Setup failures are reported as an exit status rather than written to the host's error output:

  • 64 — too few arguments
  • 71 — the pseudoterminal could not be opened or redirected
  • 127 — the program could not be executed; the reason is written to the pseudoterminal and so appears in the terminal output

Summary

Functions

Shut the pseudoterminal down.

The operating system pid of the running program.

Set the terminal size to cols by rows cells.

Run program on a new pseudoterminal.

Send bytes to the program's standard input.

Types

t()

@type t() :: %Drafter.Pty{
  control: port(),
  io: port(),
  master: integer(),
  slave: integer(),
  slave_path: binary()
}

Functions

close(pty)

@spec close(t()) :: :ok

Shut the pseudoterminal down.

Closes both ports and releases both descriptors. Releasing the last descriptor makes the kernel send SIGHUP to the session, ending the program if it is still running. Safe to call more than once; the handle is unusable afterwards.

os_pid(pty)

@spec os_pid(t()) :: {:ok, non_neg_integer()} | :error

The operating system pid of the running program.

Returns :error once the control port has closed, which is the case after the program exits or close/1 runs.

resize(pty, cols, rows)

@spec resize(t(), pos_integer(), pos_integer()) :: :ok | {:error, :ioctl_failed}

Set the terminal size to cols by rows cells.

The kernel then reports the new size to the program and delivers SIGWINCH to the foreground process group. {:error, :ioctl_failed} means the master descriptor is no longer a pty, which is the case once close/1 has run.

spawn(program, opts \\ [])

@spec spawn(
  String.t(),
  keyword()
) :: {:ok, t()} | {:error, term()}

Run program on a new pseudoterminal.

program is an executable, resolved with execvp: a name without a slash is looked up on the PATH the emulator was started with, and a name with one is used as given.

Options:

  • :args — argument list, default []
  • :cols, :rows — initial terminal size in cells, default 80 by 24
  • :env — extra environment as a list of {name, value} string pairs, default []. It is added to TERM rather than to the caller's environment: the program inherits the emulator's environment with these entries applied on top.
  • :term — value for TERM, default "xterm-256color"
  • :cd — working directory for the program, default the caller's

Returns {:ok, handle}. {:error, :helper_missing} means the pty_spawn helper is not in priv; {:error, reason} from the pty allocation means no pseudoterminal was available, and carries the strerror text as a binary. The calling process becomes the owner of both ports and must call close/1 when finished.

A program that cannot be executed is still reported as a successful spawn; the failure arrives as an exit status on the control port.

write(pty, data)

@spec write(t(), iodata()) :: :ok

Send bytes to the program's standard input.