Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
41c301551f | ||
|
|
a29b976f13
|
||
|
|
a2edb03a0e |
@@ -21,6 +21,7 @@ defmodule BearCli.Accounts do
|
||||
|
||||
import BearCli.Admin, only: [context: 1, fail: 3, format_api_error: 2]
|
||||
alias BearCli.Api
|
||||
alias BearCli.TTY
|
||||
|
||||
@list_per_page_fetch 100
|
||||
@max_fetch_pages 50
|
||||
@@ -251,8 +252,10 @@ defmodule BearCli.Accounts do
|
||||
|
||||
defp weak_password?(_), do: true
|
||||
|
||||
# stdin 是否為 TTY:優先以 fd 0 實際裝置判定(escript 環境 :io.columns/0
|
||||
# 一律 enotsup,真終端機會被誤判;見 BearCli.TTY,issue #19)。
|
||||
defp tty? do
|
||||
:io.columns() != {:error, :enotsup}
|
||||
TTY.stdin_tty?()
|
||||
end
|
||||
|
||||
defp prompt_password(prompt) do
|
||||
|
||||
@@ -15,7 +15,7 @@ defmodule BearCli.SelfService do
|
||||
3 未登入或 401;6 網路/伺服器錯誤;8 權限不足(403)。
|
||||
"""
|
||||
|
||||
alias BearCli.{Api, Config, Credentials}
|
||||
alias BearCli.{Api, Config, Credentials, TTY}
|
||||
|
||||
@genders ~w(male female other)
|
||||
|
||||
@@ -551,9 +551,10 @@ defmodule BearCli.SelfService do
|
||||
end
|
||||
end
|
||||
|
||||
# :io.rows/0 僅在終端機裝置成功;pipe/檔案重導回 {:error, :enotsup}。
|
||||
# stdin 是否為 TTY:優先以 fd 0 實際裝置判定(escript 環境 :io.rows/0 一律
|
||||
# enotsup,真終端機會被誤判;見 BearCli.TTY,issue #19)。
|
||||
defp tty_stdin? do
|
||||
match?({:ok, _}, :io.rows())
|
||||
TTY.stdin_tty?()
|
||||
end
|
||||
|
||||
# -- 輸出 --
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
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
|
||||
@@ -25,7 +25,7 @@ defmodule BearCli.Vault do
|
||||
錯誤(含非 TTY);3 未登入或 401;6 網路/伺服器錯誤;8 權限不足。
|
||||
"""
|
||||
|
||||
alias BearCli.{Api, Config, Credentials}
|
||||
alias BearCli.{Api, Config, Credentials, TTY}
|
||||
alias BearCli.Vault.{BIP39, Crypto}
|
||||
|
||||
@session_env "BEAR_VAULT_SESSION"
|
||||
@@ -932,7 +932,9 @@ defmodule BearCli.Vault do
|
||||
end
|
||||
end
|
||||
|
||||
defp tty_stdin?, do: match?({:ok, _}, :io.rows())
|
||||
# stdin 是否為 TTY:優先以 fd 0 實際裝置判定(escript 環境 :io.rows/0 一律
|
||||
# enotsup,真終端機會被誤判;見 BearCli.TTY,issue #19)。
|
||||
defp tty_stdin?, do: TTY.stdin_tty?()
|
||||
|
||||
defp require_nonempty(""), do: {:error, "不可空白", 2}
|
||||
defp require_nonempty(value) when is_binary(value), do: :ok
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
defmodule BearCli.TTYTest do
|
||||
@moduledoc """
|
||||
`BearCli.TTY` 單元測試(issue #19):
|
||||
|
||||
- `tty_path?/1`:裝置路徑分類(pts/tty/console=TTY;pipe/檔案/
|
||||
socket//dev/null=非 TTY)。
|
||||
- `stdin_tty?/0`:在本測試環境(ExUnit 捕獲 IO,stdin 非 TTY)應為
|
||||
`false`;`rows_tty?/0` 在 escript/noshell 環境回 `enotsup` → `false`。
|
||||
- fd 0 偵測走 `/proc/self/fd/0`,Linux 上必可用。
|
||||
"""
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias BearCli.TTY
|
||||
|
||||
describe "tty_path?/1" do
|
||||
test "終端機裝置路徑 → true" do
|
||||
assert TTY.tty_path?("/dev/pts/0")
|
||||
assert TTY.tty_path?("/dev/pts/17")
|
||||
assert TTY.tty_path?("/dev/tty1")
|
||||
assert TTY.tty_path?("/dev/tty")
|
||||
assert TTY.tty_path?("/dev/ttyS0")
|
||||
assert TTY.tty_path?("/dev/console")
|
||||
assert TTY.tty_path?("/dev/ptmx")
|
||||
end
|
||||
|
||||
test "pipe/檔案/socket/null → false" do
|
||||
refute TTY.tty_path?("pipe:[12345]")
|
||||
refute TTY.tty_path?("socket:[12345]")
|
||||
refute TTY.tty_path?("/dev/null")
|
||||
refute TTY.tty_path?("/home/user/secret.txt")
|
||||
refute TTY.tty_path?("/proc/self/fd/0")
|
||||
refute TTY.tty_path?("")
|
||||
end
|
||||
end
|
||||
|
||||
describe "stdin_tty?/0" do
|
||||
test "測試環境(stdin 非 TTY)→ false" do
|
||||
# ExUnit 的 group leader 為擷取裝置、測試程序的 stdin 非終端機。
|
||||
refute TTY.stdin_tty?()
|
||||
end
|
||||
end
|
||||
|
||||
describe "fd0 實體判定(Linux /proc)" do
|
||||
test "/proc/self/fd/0 可解析且與路徑分類一致" do
|
||||
# 本測試在 Linux 跑:readlink 一定有結果;stdin 非 TTY → 分類為非 TTY。
|
||||
assert {:ok, path} = call_fd0_path()
|
||||
assert TTY.stdin_tty?() == TTY.tty_path?(path)
|
||||
end
|
||||
end
|
||||
|
||||
defp call_fd0_path do
|
||||
case :file.read_link('/proc/self/fd/0') do
|
||||
{:ok, p} -> {:ok, to_string(p)}
|
||||
{:error, e} -> {:error, e}
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user