- 新增 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)
60 lines
1.9 KiB
Elixir
60 lines
1.9 KiB
Elixir
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
|