Skip to content

fix(flatpak): allocate PTY on host via rio-pty-host helper#1523

Draft
daniloaguiarbr wants to merge 3 commits intoraphamorim:mainfrom
daniloaguiarbr:feat/flatpak-autodetect
Draft

fix(flatpak): allocate PTY on host via rio-pty-host helper#1523
daniloaguiarbr wants to merge 3 commits intoraphamorim:mainfrom
daniloaguiarbr:feat/flatpak-autodetect

Conversation

@daniloaguiarbr
Copy link
Copy Markdown

Summary

Fix tty: not a tty / no such device warning seen on the first shell line when Rio is launched from the Flatpak build (Fedora 44, runtime org.freedesktop.Platform/x86_64/25.08).

The existing flatpak-spawn integration in teletypewriter/src/unix/mod.rs (added in #1116 and earlier work referenced in #198) successfully launches the host shell, but the PTY itself is still allocated inside the sandbox via openpty(). Bash login init scripts that call ttyname(0) — most notably /etc/profile.d/gnupg2.sh (export GPG_TTY=$(tty)) on Fedora — therefore fail with ENODEV, because the sandbox /dev/pts/N does not exist in the host's mount namespace. GPG_TTY ends up empty as a side effect.

This PR introduces a small helper binary, rio-pty-host, that runs on the host and creates the PTY there. Rio then spawns the user's shell through that helper, so ttyname() resolves correctly and GPG_TTY is populated as expected.

Approach

  • New crate rio-pty-host (added to the workspace) — uses only libc (workspace) plus thiserror = \"2.0.1\" (promoted to [workspace.dependencies]). No additional transitive dependencies.
  • The helper does the standard openptyfork → child runs setsid + ioctl(TIOCSCTTY) + dup2 + execvp → parent relays I/O between its stdio (the sandbox PTY slave that arrived through flatpak-spawn) and the host PTY master.
  • All unsafe blocks carry a // SAFETY: comment explaining the maintained invariant.
  • Shell paths are validated as absolute (starts_with('/')) before being passed to execvp to avoid PATH manipulation; CString failures map to a dedicated ShellInvalido variant.
  • Signal handling on the parent uses a static AtomicBool (no Arc::into_raw leak); SIGCHLD wakes up the relay loop, after which waitpid returns the child exit status.

On the Rio side:

  • New module teletypewriter::unix::flatpak caches the /.flatpak-info probe and the resolved path of ~/.local/bin/rio-pty-host via OnceLock.
  • On first invocation inside Flatpak, it checks for an existing host-side helper; if missing, it installs the bundled /app/bin/rio-pty-host via flatpak-spawn --host using a stdin pipe and an atomic mktempchmod +xmv -f sequence (atomicity comes from rename(2) on the same filesystem, so concurrent Rio instances are safe).
  • create_pty_with_spawn now prefers the helper when available and falls back to the existing direct-shell invocation when it isn't, emitting a tracing::warn that names the residual cosmetic limitation.
  • \$SHELL retrieved via flatpak-spawn is defensively trimmed and falls back to /bin/sh if empty.
  • Behaviour is unchanged on macOS, FreeBSD, and any non-Flatpak Linux launch.

Distribution Note

This PR adds the source crate and the runtime auto-install plumbing inside Rio. The Flathub manifest at flathub/com.rioterm.Rio will need a follow-up to actually build rio-pty-host and ship it at /app/bin/rio-pty-host; the Rio code degrades gracefully (legacy behaviour with the warn message) if that binary is absent.

Tests

  • rio-pty-host: 7 unit tests covering argv parsing, openpty validity, absolute-path enforcement (relative shell name and ../foo both rejected), and a happy-path spawn that runs /bin/echo and waits for it.
  • teletypewriter: 3 unit tests around the new module — installation no-op when /app/bin/rio-pty-host is absent, obter_home_host doesn't panic, and a documented #[ignore]-d Flatpak detection test (the OnceLock cannot be reset between tests in the same process; the recommended invocation is via assert_cmd in a subprocess).

Validation

cargo check  -p rio-pty-host -p teletypewriter             # PASS, 0 errors
cargo clippy -p rio-pty-host -p teletypewriter -- -D warnings  # PASS, 0 warnings
cargo fmt    -p rio-pty-host -p teletypewriter --check     # PASS, 0 diffs
cargo test   -p rio-pty-host                                # 7/7 PASS
cargo test   -p teletypewriter --lib                       # 2/2 PASS, 1 ignored
cargo build  -p rio-pty-host --release                     # 328 KB binary

Known Limitation (v1)

SIGWINCH is not yet propagated by rio-pty-host to the child shell. The initial winsize is set from <COLS> <ROWS> arguments, but dynamic resize after Rio's window changes will not reach the host PTY. A v2 follow-up should monitor TIOCGWINSZ on the helper's stdin and forward via TIOCSWINSZ to the host master.

Test plan

  • cargo check / clippy / fmt --check / test clean
  • Manual: launch the Flatpak build with the bundled rio-pty-host and confirm:
    • First shell line no longer prints tty: ttyname: ... / not a tty
    • tty returns /dev/pts/N and echo \$GPG_TTY prints the same
    • Multiple terminal tabs each get distinct /dev/pts/N and behave normally
  • Verify legacy fallback (warn message) by removing ~/.local/bin/rio-pty-host after first install

Related

When Rio runs inside a Flatpak sandbox, the PTY created via openpty()
lives in the sandbox's mount namespace and is invisible on the host.
Scripts that call ttyname(0) on those FDs (e.g. /etc/profile.d/gnupg2.sh
on Fedora: `export GPG_TTY=$(tty)`) fail with ENODEV and print
"tty: not a tty / no such device" on first shell line.

rio-pty-host is a small helper binary meant to be spawned on the host
via `flatpak-spawn --host`. It allocates a new PTY in the host's mount
namespace, forks the real shell as controlling-terminal owner of that
PTY, and relays I/O between its stdio (the sandbox PTY slave) and the
host PTY master. Since the shell now sees a /dev/pts/N that exists on
the host, ttyname() resolves correctly and GPG_TTY is set as expected.

The crate uses only `libc` (workspace) and `thiserror` (promoted to
workspace.dependencies = "2.0.1") with no extra transitive deps. All
unsafe blocks carry a `// SAFETY:` comment. Shell paths are validated
as absolute to prevent PATH manipulation. Signal handling uses a
static AtomicBool so there is no Arc::into_raw leak.

Adds workspace member `rio-pty-host` and README describing the bundled
distribution plus `~/.local/bin/` auto-install mechanism used by the
teletypewriter integration (separate commit).

7 unit tests cover argv parsing, openpty validity, absolute-path
enforcement, and shell spawn happy path.

Known limitation (v1): SIGWINCH is not propagated to the host shell;
dynamic resize is deferred to a follow-up.
The existing flatpak-spawn block in create_pty_with_spawn passes the
sandbox PTY slave directly to the host bash. That works for the basic
shell, but bash login init scripts (notably /etc/profile.d/gnupg2.sh on
Fedora) call ttyname(0) which fails with ENODEV because /dev/pts/N
created in the sandbox does not exist in the host's mount namespace.

When a Flatpak sandbox is detected, this change now prefers to invoke
the rio-pty-host helper on the host (added in the previous commit). The
helper creates a fresh PTY in the host's namespace and runs the shell
there, so ttyname() resolves correctly and GPG_TTY is populated.

Behaviour:

- New module `unix::flatpak` caches both the `/.flatpak-info` probe and
  the resolved path of `~/.host/.local/bin/rio-pty-host` via OnceLock.
- On first call inside Flatpak, `caminho_rio_pty_host()` checks for an
  existing `~/.local/bin/rio-pty-host` on the host; if absent, it
  installs the bundled `/app/bin/rio-pty-host` by piping it through
  `flatpak-spawn --host` and atomically renaming
  `mktemp` -> chmod +x -> mv -f. Atomicity is provided by `rename(2)`
  on the same filesystem, so concurrent Rio instances are safe.
- create_pty_with_spawn now branches:
    1. If rio-pty-host is available, spawn it on the host with
       `<COLS> <ROWS> <SHELL> -l`. is_controling_terminal stays false
       because the helper sets up TIOCSCTTY itself.
    2. Otherwise it falls back to the legacy direct shell invocation
       and emits a tracing::warn explaining the cosmetic GPG_TTY
       limitation.
- `$SHELL` retrieved via flatpak-spawn is now defensively trimmed and
  falls back to /bin/sh when empty.

3 unit tests cover the OnceLock-bound detection path, the missing
`/app/bin` source path, and the home-host probe being panic-free. The
Flatpak detection test is `#[ignore]`-d because OnceLock cannot be
reset between tests in the same process; it should be exercised via
`assert_cmd` subprocesses.

This change is no-op outside of Linux Flatpak; macOS and FreeBSD code
paths are untouched.
Add right-click context menu overlay with 7 actions (Copy/Paste/SelectAll/
SplitRight/SplitDown/NewTab/CloseTab) rendered via sugarloaf at z-order 21
above CommandPalette. Menu items are hardcoded in English for upstream
compatibility; internal struct fields use Portuguese naming.

Add visual highlight for active split pane rendered as 4 border rectangles
at z-order 4, configurable via new TOML fields in [navigation] section:
  highlight-active-split = true|false   (default: false)
  active-split-color = [r, g, b, a]     (default: blue accent)

Add ConfiguracaoMenuContexto struct in rio-backend with serde defaults for
future TOML customization (currently unused, cores hardcoded in renderer).

Add rio/CHANGELOG.md in Keep a Changelog format.

Test coverage:
  frontends/rioterm/src/renderer/context_menu.rs: 80.39% lines / 88.89% fns
  rio-backend/src/config/context_menu.rs:         94.57% lines / 100% fns
  Total: 829/829 tests passing, 0 failing
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant