# `Forcola.Shim`
[🔗](https://github.com/joshrotenberg/forcola/blob/v0.4.0/lib/forcola/shim.ex#L1)

Locates and speaks to the `forcola_shim` binary.

The shim is a Rust port program (source under `native/forcola_shim`).
Release builds are published per target to GitHub Releases and fetched
at compile time with SHA256 verification, so consumers need no Rust
toolchain; a locally built binary in `priv/` takes precedence during
development.

Wire protocol (BEAM <-> shim), v0, documented in full in
`native/forcola_shim/src/main.rs`:

Frames are `{:packet, 4}`-style: a 4-byte big-endian length prefix
(handled by the Erlang port itself), then a 1-byte tag, then the
payload. This module owns the tag constants and the JSON payload
shapes for SPAWN/EXIT/ERROR; `Forcola.run/2` drives the protocol.

# `close`

```elixir
@spec close(port()) :: :ok | {:error, :guard_close_timeout}
```

Closes the port and drains its remaining relayed messages.

Call once from the process that opened the port, after consuming its result.
The OS port is closed synchronously; draining normally finishes immediately.
If the guard itself fails to finish, draining is bounded by five seconds and
returns `{:error, :guard_close_timeout}`.

# `decode_contained`

```elixir
@spec decode_contained(binary()) :: boolean()
```

Decodes the `contained` flag from an EXIT frame payload.

`true` when Linux cgroup v2 containment was actually active for the run;
`false` on the default path, on fallback (macOS, no cgroup v2, or no
delegated subtree), and whenever the field is absent (older shim). Reports
which kill mechanism was used.

# `decode_error`

```elixir
@spec decode_error(binary()) :: String.t()
```

Decodes an ERROR frame payload into its reason string.

# `decode_exit`

```elixir
@spec decode_exit(binary()) ::
  {non_neg_integer() | {:signal, atom() | non_neg_integer()}, boolean()}
```

Decodes an EXIT frame payload into `{status_or_signal, timed_out}`.

A new shim reports `confirmed: false` when its bounded cleanup probes still
observed a live process group or cgroup. That maps to
`{:signal, :unconfirmed}` in every execution mode. The field defaults to true
when absent for compatibility with older shims.

# `decode_exit_report`

```elixir
@spec decode_exit_report(binary()) :: map()
```

Decodes the native EXIT report without discarding the observed child status
when cleanup was unconfirmed. `decode_exit/1` retains its legacy status shape.

# `encode_credit`

```elixir
@spec encode_credit(non_neg_integer()) :: binary()
```

Encodes a CREDIT frame payload: an 8-byte big-endian byte count.

Grants the pump selected by the frame tag that many more bytes of read
budget. `Forcola.Stream` uses stdout credit; bounded `Forcola.Duplex`
grants separate stdout and stderr credit.

# `encode_spawn`

```elixir
@spec encode_spawn(term(), keyword()) :: binary()
```

Encodes a SPAWN frame payload from `Forcola.run/2` options.

Shared by all four modes. Besides `:cd`/`:env`/`:merge_stderr`/`:timeout_ms`/
`:kill_grace_ms` and the pty options, it threads `:user` and `:group` (each a
string name or an integer id) through to the shim so the child can be run as a
different user; see `Forcola.run/2` for the semantics.

`:cgroup` opts into Linux cgroup v2 containment. Only added to the payload
when truthy, so the default SPAWN payload is unchanged; the shim defaults it
to false when the key is absent. See `Forcola.run/2` for the Linux-only,
delegation-required, graceful-fallback semantics.

`:window_bytes` opts into demand-driven backpressure on the child's stdout
(see `Forcola.Stream.lines/2`). Only added to the payload when present, so
the default SPAWN payload is unchanged; the shim gates its stdout pump when
the field is present and reads eagerly otherwise. Duplex pull mode also
supplies `:stderr_window_bytes` and `:strict_output` so both pumps stay
gated until output is consumed or explicitly discarded.

# `open`

```elixir
@spec open(keyword()) :: {:ok, port()} | {:error, term()}
```

Opens the shim binary as a port, framed with `{:packet, 4}`.

The caller controls the returned port: send it SPAWN/STDIN/EOF/KILL
frames via `send_frame/2` and receive `{port, {:data, <<tag, payload::binary>>}}`
messages for STDOUT/STDERR/EXIT/ERROR frames.

A guard process owns the port and relays its messages in order. It monitors
the caller and closes the port if the caller dies. This retains EOF-driven
process cleanup without letting a broken shim's port errors (such as
`:epipe`) kill the caller or change the caller's exit-trapping behavior.
When finished, the opening process should call `close/1` to close the port
and drain the guard's remaining messages.

Accepts the trusted `:shim_path` override documented in `path/1`.
Synchronous operating-system errors opening the port return
`{:error, {:shim_start_failed, reason}}`.

# `path`

```elixir
@spec path(keyword()) ::
  {:ok, Path.t()} | {:error, :not_found | {:invalid_shim_path, atom()}}
```

Absolute path to the shim binary for the current target.

Returns `{:error, :not_found}` if no binary has been built or
downloaded yet. An explicit `:shim_path` overrides discovery and must
name an absolute path to a regular file with executable permissions.
Invalid overrides return `{:error, {:invalid_shim_path, reason}}`.

The override is trusted executable code. The caller must supply the
matching Forcola shim for this version and target, protect the file and
its ancestor directories from replacement, and own its lifetime and
cleanup. Symlinks are followed and must satisfy the same trust contract.
Validation does not authenticate the binary or prevent path replacement.

# `send_frame`

```elixir
@spec send_frame(port(), non_neg_integer(), iodata()) :: boolean()
```

Sends a tagged frame to the shim port.

Returns `false` if the port has already closed. The caller still receives
the terminal port event and handles it through its ordinary shim-death path.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
