Files
alex a29b976f13 fix: stdin TTY 偵測改以 fd 0 實際裝置判定,escript 真終端機不再誤判(issue #19)
- 新增 BearCli.TTY:readlink /proc/self/fd/0 判定 pts/tty/console/ptmx;
  無 /proc 平台退回 :io.rows/0
- vault/self_service 的 tty_stdin?、accounts 的 tty? 改用 TTY.stdin_tty?
- 根因::io.rows/0/:io.columns/0 走 io 協定問 group leader,escript
  (noshell standard_io)不支援行列查詢,一律回 enotsup——真終端機被
  誤判成非 TTY,互動指令(vault unlock 等)無法讀 stdin 密碼
  (bear #52 驗收建議 2)
- 非 TTY(pipe/redirect)行為不變:明確提示+退出碼 2
- 新增 tty_test(4 測試);mix precommit 全綠(200 passed)
2026-09-14 19:26:23 +08:00

60 lines
1.9 KiB
Elixir
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
defmodule BearCli.TTY do
@moduledoc """
stdin 是否為終端機(TTY)的偵測(issue #19;來源 bear #52 驗收建議 2)。
不再只依賴 `:io.rows/0`/`:io.columns/0`:兩者走 io 協定詢問 group
leader,escript(`erl -noshell` 的 `standard_io`)不支援行列查詢,
即使 fd 0 是真終端機也回 `{:error, :enotsup}`,互動環境會被誤判成
非 TTY(無法讀 stdin 密碼,只能靠測試注入介面繞過)。
本模組改為直接判定 fd 0 指向的裝置:
- Linux:readlink `/proc/self/fd/0`,指向 `/dev/pts/*`、`/dev/tty*`、
`/dev/console`、`/dev/ptmx` 視為 TTY;pipe/socket/一般檔案(含
`/dev/null`)視為非 TTY。
- 無 `/proc` 的平台(如 macOS、Windows):退回 `:io.rows/0`(僅在
終端機裝置成功)。
"""
@tty_prefixes ["/dev/pts/", "/dev/tty", "/dev/console", "/dev/ptmx"]
@doc "stdin(fd 0)是否為終端機。"
@spec stdin_tty?() :: boolean()
def stdin_tty? do
case fd0_path() do
{:ok, path} -> tty_path?(path)
:error -> rows_tty?()
end
end
@doc """
裝置路徑是否為終端機(公開供測試與診斷)。
iex> BearCli.TTY.tty_path?("/dev/pts/0")
true
iex> BearCli.TTY.tty_path?("pipe:[42]")
false
"""
@spec tty_path?(String.t()) :: boolean()
def tty_path?(path) when is_binary(path) do
String.starts_with?(path, @tty_prefixes)
end
@doc """
舊式偵測(`:io.rows/0`):終端機 io 裝置回 `{:ok, rows}`;escript 的
`standard_io` 一律回 `{:error, :enotsup}`。僅作為無 `/proc` 平台的退路。
"""
@spec rows_tty?() :: boolean()
def rows_tty? do
match?({:ok, _}, :io.rows())
end
# fd 0 實際指向的路徑(readlink /proc/self/fd/0);無 /proc 或讀取失敗 → :error
defp fd0_path do
case :file.read_link(~c"/proc/self/fd/0") do
{:ok, path} -> {:ok, to_string(path)}
{:error, _} -> :error
end
end
end