diff --git a/ble.pp b/ble.pp index 10ecbb50..43595ea6 100644 --- a/ble.pp +++ b/ble.pp @@ -422,11 +422,11 @@ function ble/base/xtrace/adjust { fi set +x - ((level==0)) || return 0 + ((_ble_bash>=40000&&level==0)) || return 0 _ble_bash_xtrace_debug_enabled= if [[ ${bleopt_debug_xtrace:-/dev/null} == /dev/null ]]; then if [[ $_ble_bash_xtrace_debug_fd ]]; then - builtin eval "exec $_ble_bash_xtrace_debug_fd>&-" || return 0 + builtin eval "exec $_ble_bash_xtrace_debug_fd>&-" || return 0 # disable=#D2164 (here bash4+) _ble_bash_xtrace_debug_filename= _ble_bash_xtrace_debug_fd= fi @@ -468,7 +468,7 @@ function ble/base/xtrace/restore { fi builtin unset -v '_ble_bash_xtrace[level]' - ((level==0)) || return 0 + ((_ble_bash>=40000&&level==0)) || return 0 if [[ $_ble_bash_xtrace_debug_enabled ]]; then ble/base/xtrace/.log "$FUNCNAME" _ble_bash_xtrace_debug_enabled= @@ -480,7 +480,7 @@ function ble/base/xtrace/restore { if [[ $_ble_bash_XTRACEFD_dup ]]; then # BASH_XTRACEFD の fd を元の出力先に繋ぎ直す builtin eval "exec $BASH_XTRACEFD>&$_ble_bash_XTRACEFD_dup" && - builtin eval "exec $_ble_bash_XTRACEFD_dup>&-" || ((1)) + builtin eval "exec $_ble_bash_XTRACEFD_dup>&-" || ((1)) # disble=#D2164 (here bash4+) else # BASH_XTRACEFD の fd は新しく割り当てた fd なので値上書きで閉じて良い if [[ $_ble_bash_XTRACEFD_set ]]; then diff --git a/docs/ChangeLog.md b/docs/ChangeLog.md index 0ff97a60..170086a5 100644 --- a/docs/ChangeLog.md +++ b/docs/ChangeLog.md @@ -106,6 +106,7 @@ - util(joblist): fix job detection in Bash 5.3 `#D2157` xxxxxxxx - util,complete: work around regex `/=.../` failing in Solaris nawk `#D2162` xxxxxxxx - main: fix issues in MSYS1 `#D2163` xxxxxxxx +- util: work around bash-3.1 bug that `10>&-` fails to close the fd `#D2164` xxxxxxxx ## Contrib diff --git a/lib/test-bash.sh b/lib/test-bash.sh index d37ed8d7..1818fa0a 100644 --- a/lib/test-bash.sh +++ b/lib/test-bash.sh @@ -392,6 +392,48 @@ ble/test/start-section 'bash' 61 ble/test '(set -H; builtin history -c; builtin history -p "$line")' stdout="$line" fi + # BUG bash-3.1 and 3.2 [Ref #D0857] + # A file descriptor >= 10 cannot be redirected if it is already in use. In + # bash-3.2, we can first close the file descriptor and then perform the + # redirect. In bash-3.1, because of the next bug, one cannot simply close + # the file descriptor. One needs to move the file descriptor to another + # number. + if [[ -d /proc/$$/fd ]]; then + ( + exec 7>/dev/null 77>/dev/null # disable=#D0857 + exec 7>/dev/tty 77>/dev/tty # disable=#D0857 + ble/util/getpid + if ((30100<=_ble_bash&&_ble_bash<40000)); then + # bug + ble/test '[[ -t 7 ]]' + ble/test '[[ ! -t 77 ]]' + else + # expected + ble/test '[[ -t 7 ]]' + ble/test '[[ -t 77 ]]' + fi + ) + fi + + # BUG bash-3.1 [Ref #D2164] + # file descriptor >= 10 cannot be closed by exec 77>&-. + if [[ -d /proc/$$/fd ]]; then + ( + exec 7>/dev/null 77>/dev/null # disable=#D0857 + exec 7>&- 77>&- # disable=#D2164 + ble/util/getpid + if ((30100<=_ble_bash&&_ble_bash<30200)); then + # bug + ble/test '[[ ! -e /proc/$BASHPID/fd/7 ]]' + ble/test '[[ -e /proc/$BASHPID/fd/77 ]]' + else + # expected + ble/test '[[ ! -e /proc/$BASHPID/fd/7 ]]' + ble/test '[[ ! -e /proc/$BASHPID/fd/77 ]]' + fi + ) + fi + # BUG bash-3.0 [Ref #D1956] # 関数定義の一番外側でリダイレクトしてもリダイレクトされない。例えば、 # function func { ls -l /proc/$BASHPID/fd/{0..2}; } <&"$fd0" >&"$fd1" diff --git a/make_command.sh b/make_command.sh index 44f13bb5..6d829ffe 100755 --- a/make_command.sh +++ b/make_command.sh @@ -274,8 +274,16 @@ function sub:scan/bash301bug { # する。 grc ' [0-9]{2}&?[<>]' --exclude=./{test,ext} --exclude=./make_command.sh --exclude=ChangeLog.md --color | sed -E 'h;s/'"$_make_rex_escseq"'//g;s/^[^:]*:[0-9]+:[[:space:]]*// + /^#/d /#D0857/d - / [0-9]{2}[<>]&- [0-9]{2}&?[<>]/d + / [0-9]{2}[<>]&-/d + g' + + # bash-3.1 では 10 以上の fd は >&- 等で閉じる事ができない。 + grc ' ([0-9]{2}|\$[a-zA-Z_0-9]+)&?[<>]&-' --exclude=./{test,ext} --exclude=./make_command.sh --exclude=ChangeLog.md --color | + sed -E 'h;s/'"$_make_rex_escseq"'//g;s/^[^:]*:[0-9]+:[[:space:]]*// + /^#/d + /#D2164/d g' # array-element-length diff --git a/memo/D1779.func-source-and-lineno.sh b/memo/D1779.func-source-and-lineno.sh index 39ac4c06..56119055 100644 --- a/memo/D1779.func-source-and-lineno.sh +++ b/memo/D1779.func-source-and-lineno.sh @@ -27,7 +27,7 @@ function ble/function#get-source-and-lineno { declare -ft "$_ble_util_function_name" builtin trap 'ble/function#get-source-and-lineno/.extract && return 0' DEBUG "$_ble_util_function_name" - ) 11>&- 11>&1 12>&- 12>&2 + ) 11>&- 11>&1 12>&- 12>&2 # disable=#D2164 (we give up bash-3.1) fi } diff --git a/memo/D1956.sh b/memo/D1956.sh index d13de8c0..e91db99f 100644 --- a/memo/D1956.sh +++ b/memo/D1956.sh @@ -16,8 +16,8 @@ function test1 { local fb=$prefix.b.pipe rm -f "$fa" "$fb" mkfifo "$fa" "$fb" - exec 36>&- 36<> "$fa" - exec 37>&- 37<> "$fb" + exec 36>&- 36<> "$fa" # disable=#D2164 (XXX--give up bash-3.1) + exec 37>&- 37<> "$fb" # disable=#D2164 (XXX--give up bash-3.1) _test1_a=36 _test1_b=37 diff --git a/note.txt b/note.txt index 0bb3c319..12519233 100644 --- a/note.txt +++ b/note.txt @@ -7075,6 +7075,171 @@ bash_tips Done (実装ログ) ------------------------------------------------------------------------------- +2024-02-18 + + * util: bash-3.1 でプロンプトが表示されなくなっている [#D2164] + + 元々 #D2159 のテストの過程で見つけて確かに #2159 以降で顕在化した問題だが、 + これは bash-3.1 のバグによって引き起こされていて実際にはもっと影響範囲が広 + い様に思われるので独立した項目とする。 + + x bash-3.1 でプロンプトが表示されない。bash-3.2 では発生しない。何が起こっ + ているのだろうか? これは次の項目にある fd#add-cloexec を呼び出さない様に + しても変化しない。先ずはこれを修正したい。 + + プロンプトは恐らく表示されている。然し、stderr が正しい所に繋がっていない。 + そもそも source ble.sh した直後に stderr に何か出力しても何も出ない。実際 + に ls -l /proc/$$/fd で確認してみると /dev/null に繋がっている。 + + また、沢山の fd が既にこの時点でできている。うーん。ble/fd#add-cloexec を + 完全に潰すと fd が増える問題は発生しなくなるがやはり /dev/null に繋がって + しまう問題は変わっていにない。他に気づくのは 3.1 だと 40, 41 が + /dev/null, /dev/tty に繋がっているという事。/dev/tty が開いているという事 + は -t のテストに失敗しているという事だろうか? + + 調べていくとどうも最初から /dev/null に繋がっている。何故? + _ble_util_fd_null を初期化した時点で /dev/null に繋がっている様だ。 + + ? ok: 一方で、そうだとしても最初のプロンプトの表示までは tui に繋げておくべ + きでは? と思ったが既にそういう実装になっている。 + + | 何故か関数の外ではちゃんとtty に繋がっていて、関数の中に入った途端に + | stderr は /dev/null に繋がっている様だ。謎だ。或いは 3.1 では関数内では必 + | ず /dev/null に繋がるのだろうか? それとも ble.sh という特殊なスクリプトが + | そうさせている? + | + | うーん。調べると別に関数を抜けたら元に戻るというのは再現しなくなった。勘 + | 違いかもしれない。それよりも nextfd を呼び出すと 2 が /dev/null に繋がっ + | た状態になってしまうという事が分かった。 + | + | 更に ble/fd#is-open でも発生する事が分かった。もしかして redir に失敗する + | と関数の実行自体がキャンセルされる? + | + | うーん。辿って行ったらそもそも exec 33>&- で stream を閉じるのに失敗して + | いる。その所為で 33 が /dev/null に繋がった儘になり、また bash-3.1 の別の + | バグにより exec 33>&2 でも 33 が更新されずに /dev/null のままになる。それ + | が 2 に書き戻されて /dev/null に置き換わってしまうという事の様だ。 + | + | 何故 33>&- で閉じないのか? うーん。一応 99>&33- 等としたら閉じはする。然 + | しこれだと更にもう一つの fd 99 が必要になる。と思ったが 99 は置き換わって + | いない様なので実は既存の物でも良いのかもしれない。 + | + | 然し前にテストした時にはちゃんと動いていた気がする。不思議だ。もっと単純 + | な設定で問題が再現するのか確認する → ble.sh なしで再現する。もっと単純化 + | しても発生する。というか 33>&- の形だと常に発生する。以前は bash-3.1 でも + | これを使えば 33>&- 33>&XX とする事ができるのではなかったか? 探してみると + | fd#alloc にコメントが残っていて #D0857 を参照している。然し其処を見ると + | 3.2 と書いてあるし、何だか別の問題について話している様に見える。但し、対 + | 策としては一貫している。 + + まとめると、bash-3.1 では 33>&- 等としても file descriptor が閉じないバグが + あって、更に既知のバグである 33>redir で 33 が閉じていないと失敗する + (#D0857) というバグが組み合わさって面倒な事になっている。#D0857 の + workaround は exec 33>&- 33>redir とする事であったが、実はこれは 3.2 では動 + いても 3.1 では動かないという事が判明してしまった。 + + fd を閉じようと思ったら move_fd 99>&33- を実行するしかない様だ。一方で、移 + 動先の 99 として何を選ぶのかは微妙である。3.1 だけでこの対策をするのだとし + たら、99>/dev/null として置いて、後はこれに対して redir を実行すれば良い様 + に見える。何故なら実験した限りだと 99>&33- としても 99 に対するコピーは失敗 + して単に 33 が閉じられるだけに見えるので。然し、これは他の version だと成功 + してしまって 99 が置き換わってしまうので使えない方法である。 + + うーん。これは設計を改めて考え直す必要がある気がする。また、この微妙な振 + る舞いは ble-0.3 の alloc/openat の実装にも影響があるのではないか。別項目 + で議論するべきの気がする。 + + x fixed: bash-3.2 でエラーが出る様になった。と思ったが今まで出ていた + ~/.bashrc のテスト用コードのエラーが見えていなかったのが見える様になった + だけの気がする。或いは今まで bash-3.2 で ~/.bashrc を実行していなかった。 + + x ok: この修正をしたら 3.2 で cloexec が効かなくなっている。うーん。修正前 + は動いていた筈 + + →と思って結構遡ってみたが別に古い物でも cloexec はちゃんと働いていない様 + だ。#D2158 でも 3.2 についてちゃんと動作を確認した様な事は書かれていない。 + 恐らく単にテストしていなかっただけ。また、様々の環境のテストでも msys1 以 + 外は 4.0 以降だったのだろうという気がする。 + + 4.0 で動いているのは procfs があるからかもしれない。procfs がない場合でも + ちゃんと cloexec が動くかは現在の codebase でも確かめるべき → procfs を + 使わない場合でもちゃんと 4.0 で cloexec は付加できている。つまり、cloexec + のアプローチの問題という訳ではない。 + + bash-3.2 に於ける振る舞いについて確認するべきか? 手元で bash-3.2 --norc + で確認する限りは 3.2 でも cloexec を付加する事は可能の様に思われる。何故 + ble.sh では動かない? 試しに ble/fd#add-cloexec を色々の fd に適用してみた + が全く効果がない。 + + 実際に ble/fd#add-cloexec の実装を抜き出して各箇所で振る舞いを見てみたが、 + undo fd を見つけて exec する所までは良い。然し、undo fd にどうやら + cloexec がついていない様だ。 ls self で見ても残っている。手で clone した + 時との違いは何だろうか。 + + うーん。改めて norc で試してみたが cloexec は付加されていない。付加されて + いる様な気がしたのは勘違いだろうか? そもそもコピー元の undo fd に cloexec + がついていない様に見える。不思議だ。 + + % と思ったが自分でやった限りは undo fd にちゃんと cloexec がついている。 + % という事は .probe で見つけた fd が実は undo fd ではないという可能性? 然 + % し、何れにしても undo fd から exec で移すと cloexec は消滅する様に見え + % る。 + % + % $ exec 50>/dev/null + % $ { ls /proc/$$/fd; ls /proc/self/fd; exec 51>&10; } 50>/dev/tty + % $ ls /proc/$$/fd; ls /proc/self/fd + % + % 以下の様に mv で移したとしても cloexec は消えている。 + % + % $ exec 50>/dev/null + % $ { ls /proc/$$/fd; ls /proc/self/fd; exec 51>&10-; exec 10>&51; } 50>/dev/tty + % $ ls /proc/$$/fd; ls /proc/self/fd + % + % と思ったがよく見ると別に undo fd (10) に cloexec はついていない。改めて + % 自分で確認する。うーん。やはり undo fd が bash-3.2 では子プロセスに継承 + % されている。どうも cloexec がついていると思ったのは勘違いの様である。何 + % 故こんなに勘違いするのかは分からない。 + + これは bash-3.2 特有なのだろうか? それとも 3.0 や 3.1 でも同じ? → 3.0 + や 3.1 でも同じ振る舞いだった。つまり、3.0 や 3.1 では undo fd でも + cloexec がついていないので undo fd を clone する作戦は全く使えない。 + + [結論] bash-3.x では undo fd にも cloexec がついていないので、undo fd を + dup しても当然 cloexec はつかない。従って、この方針はそもそも使えない。 + + * done: m scan test を追加してコードを修正する必要がある + + x reject: そもそもコマンドを実行する度にどんどん使っている fd の数が増えて + いく。overwrite が効いていないのだろうか。これは ble/fd#add-cloexec を実 + 行しない限りは問題にならない。一方で 3.1 に於ける ble/fd#add-cloexec の実 + 装について考える必要がある。 + + ble/fd#add-cloexec 対応は諦める。2つ前の調査の結果 bash-3.2 以下では + O_CLOEXEC に対応する事は不可能なので、そもそも fd#add-cloexec は実装しな + い事にした。ble/fd#add-cloexec を試みない限りは問題は発生しない様なので取 + り敢えず気にしない事にする。 + + ---- + + 2024-02-21 msys1 を再びテストしようと思ったら再び bad fd のエラーが出る様に + なった。やはり fd の取り扱いを大きく書き換えてまたバグができたのだろうか。 + 先ず何処でエラーが発生しているか確認する。 + + どうも /dev/null に繋いだ筈の fd 30 が駄目の様だ。うーん。自分で直接 30 を + 開いたりする時には問題は発生しない。bash-3.1 なら chat でも再現する。.close + で exec 30<&"$1"- を実行している所でエラーになっている。或いは、既に閉じて + いる fd について move を試みると発生する? →確かにエラーが発生する。これは + 潰す必要がある。 + + 然し、疑問は何故 bad fd のメッセージが行き先の 30 についてなのかという事。 + 30<&31- を実行しようとして 31 が存在しないのだったら 31 に対してエラーメッ + セージが発生するべきなのでは? 実際に bash-3.1 で手で実行すると 31 の方でエ + ラーメッセージになる。うーん。分かった。 exec 30<&"$v"- の形式だと fd $v が + 開いていなかった時のエラーメッセージが 30 に対してになる。 + + 取り敢えず対策はした。最新の bash でも似たようなエラーメッセージの問題はあ + るのだろうか → 何と現在の bash でも同じ問題がある。 + 2024-02-17 * msys1 をテストの為に久しぶりに使ってみたが色々と問題が発生する [#D2163] diff --git a/src/util.sh b/src/util.sh index 2e13438d..e8ae4e11 100644 --- a/src/util.sh +++ b/src/util.sh @@ -2805,77 +2805,86 @@ else fi ## @fn ble/fd#is-open fd -## 指定したファイルディスクリプタが開いているかどうか判定します。 +## Test if the specified file descriptor is open. ## _ble_util_fd_is_open_stdout= _ble_util_fd_is_open_stderr= -function ble/fd#is-open/.bootstrap { - if ((_ble_bash>=40000)) && [[ -d /proc/$BASHPID/fd ]]; then - function ble/fd#is-open { [[ $1 && -e /proc/$BASHPID/fd/$1 ]]; } - function ble/fd#is-open/.bootstrap { ((1)); } - elif [[ ${_ble_util_fd_null-} && $_ble_util_fd_is_open_stdout && $_ble_util_fd_is_open_stderr ]] && - { ((1)) >&"$_ble_util_fd_null"; } 2>/dev/null - then - # (3) This is the final version. To reserve the numbers of the file - # descriptors, we duplicate the file descriptor with O_CLOEXEC connected to - # /dev/null stored in $_ble_util_fd_null. After defining this function, - # "ble/fd#is-open" needs to be called at least once to replace the file - # descriptors with the ones with O_CLOEXEC. - local fd1=$_ble_util_fd_is_open_stdout - local fd2=$_ble_util_fd_is_open_stderr - builtin eval -- " - function ble/fd#is-open { - ble/string#match \"\$1\" '^[0-9]+$' || return 1 - [[ \$1 == $fd1 || \$1 == $fd2 || \$1 == $_ble_util_fd_null ]] && return 0 - exec $fd1>&- $fd2>&- $fd2>&2 # disable=#D1835 - exec 2>&$_ble_util_fd_null # disable=#D1835 - builtin : $fd1>&\"\$1\" # disable=#D1835 - local ext=\$? - exec 2>&$fd2 # disable=#D1835 - exec $fd1>&- $fd1>&$_ble_util_fd_null $fd2>&- $fd2>&$fd1 # disable=#D1835 - return \"\$ext\" - } - " - builtin unset -f "$FUNCNAME" - elif ble/is-function ble/fd#alloc/.nextfd; then - # (2) This is the second implementation. We need this implementation to - # prepare "_ble_util_fd_null" with O_CLOEXEC by "ble/fd#add-cloexec". This - # implementation uses "exec" to move file descriptors without creating any - # "undo-redirection" fds. This implementation works as expected, but we - # don't want to leave the file descriptors used by this implementation in - # the child processes. +if ((_ble_bash>=40000)) && [[ -d /proc/$BASHPID/fd ]]; then + # Bash 3 does not have BASHPID + function ble/fd#is-open { [[ $1 && -e /proc/$BASHPID/fd/$1 ]]; } + function ble/fd#is-open/.upgrade { builtin unset -f "$FUNCNAME"; } +else + # This is the most primitive but incomplete implementation and will be + # overwritten later when "ble/fd#alloc/.nextfd" is ready. We need this + # implementation to make "ble/fd#alloc/.nextfd" work. This temporary + # implementation ensures that the number is not used when it fails, but the + # number may not be actually used when it succeeds. This is because this + # function may unexpectedly hit the undo-redirection fd for `2>/dev/null'. + function ble/fd#is-open { builtin : 9>&"$1"; } 2>/dev/null + + function ble/fd#is-open/.upgrade { + if ! { [[ $_ble_util_fd_null ]] && ((1)) >&"$_ble_util_fd_null"; } 2>/dev/null; then + ble/util/print "$FUNCNAME: [FATAL] call this function after \$_ble_util_fd_null is ready" >&2 + return 1 + fi + local fd1 fd2 ble/fd#alloc/.nextfd fd1 ble/fd#alloc/.nextfd fd2 + _ble_util_fd_is_open_stdout=$fd1 + _ble_util_fd_is_open_stderr=$fd2 + + # This is the final version. This implementation uses "exec" to move file + # descriptors without creating any "undo-redirection" fds. To reserve the + # numbers of the file descriptors, we duplicate the file descriptor with + # O_CLOEXEC connected to /dev/null stored in $_ble_util_fd_null. After + # defining this function, "ble/fd#is-open" needs to be called at least once + # to replace the file descriptors with the ones with O_CLOEXEC. builtin eval -- " - exec $fd1>/dev/null $fd2>&$fd1 # disable=#D1835 + ble/fd#alloc/.exec $fd1 '>/dev/null' # disable=#D1835 + ble/fd#alloc/.exec $fd2 '>&$fd1' # disable=#D1835 function ble/fd#is-open { ble/string#match \"\$1\" '^[0-9]+$' || return 1 - [[ \$1 == $fd1 || \$1 == $fd2 ]] && return 0 - exec $fd1>&- $fd2>&- $fd2>&2 # disable=#D1835 - exec 2>/dev/null - builtin : $fd1>&\"\$1\" # disable=#D1835 + [[ \$1 == $fd1 || \$1 == $fd2 || \$1 == $_ble_util_fd_null ]] && return 0 + ble/fd#alloc/.exec $fd2 '>&2' # disable=#D1835 + exec 2>&$_ble_util_fd_null # disable=#D1835 + ble/fd#alloc/.exec $fd1 \">&\$1\" # disable=#D1835 local ext=\$? exec 2>&$fd2 # disable=#D1835 - exec $fd1>&- $fd1>/dev/null $fd2>&- $fd2>&$fd1 # disable=#D1835 + ble/fd#alloc/.exec $fd1 '>&$_ble_util_fd_null' # disable=#D1835 + ble/fd#alloc/.exec $fd2 '>&$fd1' # disable=#D1835 return \"\$ext\" } " - _ble_util_fd_is_open_stdout=$fd1 - _ble_util_fd_is_open_stderr=$fd2 - else - # (1) This is the most primitive implementation and will be overwritten - # later when "ble/fd#alloc/.nextfd" is ready. We need this implementation - # to make "ble/fd#alloc/.nextfd" work. This temporary implementation - # ensures that the number is not used when it fails, but the number may not - # be actually used when it succeeds. This is because this function may - # unexpectedly hit the undo-redirection fd for `2>/dev/null'. - function ble/fd#is-open { builtin : 9>&"$1"; } 2>/dev/null - fi -} -ble/fd#is-open/.bootstrap + builtin unset -f "$FUNCNAME" + } +fi + +function ble/fd#alloc/.close { builtin eval "exec $1<&-"; } # disable=#D2164 +if ((30100<=_ble_bash&&_ble_bash<30200)); then + function ble/fd#alloc/.close/.upgrade { + if ! { [[ $_ble_util_fd_null ]] && ((1)) >&"$_ble_util_fd_null"; } 2>/dev/null; then + ble/util/print "$FUNCNAME: [FATAL] call this function after \$_ble_util_fd_null is ready" >&2 + return 1 + fi + + # Bash 3.1 has a bug that the file descriptor (>= 10) cannot be closed by + # 33>&-. We here utilize another bug in Bash 3.1, where 77>&33- fails + # halfway when 77 is in use and results in just closing 33. We use the + # file descriptor for /dev/null in place of 77. + builtin eval -- " + function ble/fd#alloc/.close { + ((\$1==$_ble_util_fd_null||\$1==2)) && return 1 + exec $_ble_util_fd_null<&\"\$1\"- + } 2>/dev/null" + builtin unset -f "$FUNCNAME" + } +else + function ble/fd#alloc/.close/.upgrade { builtin unset -f "$FUNCNAME"; } +fi function ble/fd/.validate-shared-fds { + local -a close_fd=() # We first check if the exported fds are still valid. If an exported fd is # invalidated by e.g. closing the other end point, the fd becomes invalid and # another stream can be later assigned to the same number. Using the @@ -2890,7 +2899,7 @@ function ble/fd/.validate-shared-fds { fd=${!var} ble/string#match "$fd" '^[0-9]+$' || continue if ! ble/fd#is-open "$fd"; then - builtin eval -- "exec $fd>&-" + ble/array#push close_fd "$fd" builtin unset -v "$var" fi done @@ -2902,10 +2911,11 @@ function ble/fd/.validate-shared-fds { fd=${!var} ble/string#match "$fd" '^[0-9]{2,}$' || continue if ! ble/fd#is-open "$fd"; then - builtin eval -- "exec $fd>&-" + ble/array#push close_fd "$fd" builtin unset -v "$var" fi done + _ble_util_fdvars_export= fi # We also close all the fds marked as "cloexec" that were inherited by the @@ -2914,7 +2924,23 @@ function ble/fd/.validate-shared-fds { local ret fd ble/string#split ret : "$_ble_util_fdlist_cloexit" for fd in "${ret[@]}"; do - [[ $fd && ! ${fd//[0-9]} ]] && builtin eval -- "exec $fd>&-" + [[ $fd && ! ${fd//[0-9]} ]] && + ble/array#push close_fd "$fd" + done + _ble_util_fdlist_cloexit= + fi + + if ((${#close_fd[@]})); then + "${_ble_util_set_declare[@]//NAME/mark}" # disable=#D1570 + local fd + for fd in "${close_fd[@]}"; do + ble/set#contains mark "$fd" && continue + ble/set#add mark "$fd" + + # Note: XXX--At this point, the implementation of ble/fd#alloc/.close + # does not work for Bash 3.1. We give up closing file descriptors in + # Bash 3.1. + ble/fd#alloc/.close "$fd" done fi } @@ -2947,15 +2973,71 @@ function ble/fd#alloc/.nextfd { done if ((_ble_local_nextfd>=_ble_local_limit)); then _ble_local_nextfd=$_ble_local_init - builtin eval "exec $_ble_local_nextfd>&-" + ble/fd#alloc/.close "$_ble_local_nextfd" fi (($1=_ble_local_nextfd++)) [[ ${2-} || :${3-}: == *:no-increment:* ]] || _ble_util_openat_nextfd=$_ble_local_nextfd } -ble/fd#is-open/.bootstrap # use ble/fd#alloc/.nextfd for fd moving +## @var _ble_util_fd_null +## We open a stream connected to /dev/null for read/write and store the +## number in this variable. +## +## @remark We originally initialized this variable using "ble/fd#alloc" after +## we define "ble/fd#alloc". However, because of bash-3.1 bug (#D2164), we +## need the file descriptor associated with /dev/null for ble/fd#add-cloexec, +## and ble/fd#add-cloexec is needed by ble/fd#alloc. We give up initializing +## _ble_util_fd_null using "ble/fd#alloc" and manually initialize it here. +## Some part needs to be performed later after ble/fd#add-cloexec is defined. +if [[ :$bleopt_connect_tty: == *:inherit:* ]]; then + # Initialize the variable as if "ble/fd#alloc _ble_util_fd_null base:inherit" + if [[ ! ${_ble_util_fd_null-} ]] || ! ble/fd#is-open "$_ble_util_fd_null"; then + builtin eval "exec $_ble_util_fd_null<>/dev/null" + ble/opts#append-unique _ble_util_fdvars_export _ble_util_fd_null + export _ble_util_fd_null + ble/fd#alloc/.nextfd _ble_util_fd_null + fi +else + # Initialize the variable as if "ble/fd#alloc _ble_util_fd_null base". + # ble/fd#add-cloexec needs to be performed later. + ble/fd#alloc/.nextfd _ble_util_fd_null + builtin eval "exec $_ble_util_fd_null<>/dev/null" + ble/opts#append-unique _ble_util_fdlist_cloexit "$_ble_util_fd_null" + # later: ble/fd#add-cloexec "$_ble_util_fd_null" +fi + +# We now switch to the complete implementation of "ble/fd#alloc/.close" relying +# on $_ble_util_fd_null. +ble/fd#alloc/.close/.upgrade + +## @fn ble/fd#alloc/.exec fddst redir +## Performs builtin eval "exec $fddst$redir" with sepcial cares for bugs in +## old versions of Bash. +## @param fddst +## The file descriptor that is supposed to be modified +## @param redir +## Redirection operator plus arguments +if ((30100<=_ble_bash&&_ble_bash<40000)); then + function ble/fd#alloc/.exec { + # Note (#D0857): Bash 3.2/3.1 has a bug for the file descriptors (>= 10). + # When a file descriptor is already used, the redirections of the form + # 33>... (disable=#D0857) silently fails. To work around this bug, we + # first close the file descriptor by ble/fd#alloc/.close. + ble/fd#alloc/.close "$1" + builtin eval "exec $1$2" + } +else + function ble/fd#alloc/.exec { + builtin eval "exec $1$2" + } +fi +# We now switch to the complete implementation of "ble/fd#is-open" utlizing +# "ble/fd#alloc/.nextfd", "ble/fd#alloc/.exec", and "$_ble_util_fd_null". +ble/fd#is-open/.upgrade + +## @fn ble/fd#list if [[ -d /proc/$$/fd ]]; then ## @fn ble/fd#list/adjust-glob ## @var[out] set shopt gignore @@ -2982,6 +3064,8 @@ if [[ -d /proc/$$/fd ]]; then [[ :$shopt: == *:failglob:* ]] && shopt -s failglob } ## @fn ble/fd#list [pid] + ## List the file descriptors opend for the specified process. If PID is + ## not specified, this returns the list for the current process. ## @arr[out] ret function ble/fd#list { ret=() @@ -3007,6 +3091,7 @@ if [[ -d /proc/$$/fd ]]; then } else ## @fn ble/fd#list + ## List the file descriptors opend for the current process. ## @arr[out] ret function ble/fd#list { ret=() @@ -3022,7 +3107,15 @@ if ((_ble_bash>=40400)) && ble/util/load-standard-builtin fdflags; then function ble/fd#add-cloexec { builtin fdflags -s +cloexec "$1"; } function ble/fd#remove-cloexec { builtin fdflags -s -cloexec "$1"; } -else +elif ((_ble_bash>=40000)); then + # Implementation of ble/fd#add-cloexec by undo fd. + # + # In bash >= 4.0, the "undo fd" (i.e., the file descriptor that holds the + # original stream of the redirected file descriptor) gets O_CLOEXEC. If we + # can identify the "undo fd", we can duplicate it to another file descriptor + # to get O_CLOEXEC version of the original stream. This can only be used in + # bash >= 4.0, because older versions of bash does not give O_CLOEXEC to the + # undo fds. if [[ -d /proc/$$/fd ]]; then # Implementation of ble/fd#add-cloexec by procfs (1588us) @@ -3107,16 +3200,19 @@ else function ble/fd#add-cloexec { local fd=$1 ret ble/fd#add-cloexec/.dup-undo-redirection-fd "$fd" && - builtin eval -- "exec $fd>&- $fd>&$ret $ret>&-" + builtin eval -- "exec $fd>&- $fd>&$ret $ret>&-" # disable=#D2164 (here bash4+) } 2>/dev/null function ble/fd#remove-cloexec { local fd=$1 if ((fd!=0)); then - builtin eval -- "exec 0<&$fd $fd<&- $fd<&0" &$fd $fd>&- $fd>&1" >/dev/null + builtin eval -- "exec 1>&$fd $fd>&- $fd>&1" >/dev/null # disable=#D2164 (here bash4+) fi } +else + function ble/fd#add-cloexec { false; } + function ble/fd#remove-cloexec { false; } fi ## @fn ble/fd#alloc fdvar redirect [opts] @@ -3183,15 +3279,12 @@ function ble/fd#alloc { # it for the auto-closing. _ble_local_opts=$_ble_local_opts:preserve - builtin eval "exec ${!1}>&- ${!1}$2" + ble/fd#alloc/.exec "${!1}" "$2" elif ((_ble_bash>=40100)) && [[ :$_ble_local_opts: != *:base:* ]]; then builtin eval "exec {$1}$2" else ble/fd#alloc/.nextfd "$1" - # Note (#D0857): Bash 3.2/3.1 のバグを避けるため、>&- を用いて一旦明示的に - # 閉じる必要がある。10 以上の fd は、既に使われている場合、 - # 15>... (disable=#D0857) 等とリダイレクトしても無視される。 - builtin eval "exec ${!1}>&- ${!1}$2" + ble/fd#alloc/.exec "${!1}" "$2" fi; local _ble_local_ext=$? if ((_ble_local_ext==0)); then @@ -3210,7 +3303,7 @@ function ble/fd#finalize { ble/string#split fds : "$_ble_util_fdlist_cloexit" for fd in "${fds[@]}"; do [[ $fd ]] || continue - builtin eval -- "exec $fd>&-" + ble/fd#alloc/.close "$fd" done _ble_util_fdlist_cloexit= } @@ -3222,7 +3315,7 @@ function ble/fd#is-cloexit { function ble/fd#close { set -- "$(($1))" (($1>=3)) || return 1 - builtin eval "exec $1>&-" + ble/fd#alloc/.close "$1" ble/opts#remove _ble_util_fdlist_cloexit "$1" return 0 } @@ -3231,18 +3324,21 @@ bleopt/declare -v connect_tty 1 export bleopt_connect_tty ## @var _ble_util_fd_null -## @var _ble_util_fd_zero -function ble/fd/.initialize-utility-stream { - local alloc_opts=base - [[ :$bleopt_connect_tty: == *:inherit:* ]] && alloc_opts=$alloc_opts:inherit - ble/fd#alloc "$1" "$2" "$alloc_opts" -} -ble/fd/.initialize-utility-stream _ble_util_fd_null '<> /dev/null' -ble/fd#is-open/.bootstrap # use $_ble_util_fd_null for /dev/null with O_CLOEXEC +## The final part of the initialization of _ble_util_fd_null is performed +## here. +ble/fd#add-cloexec "$_ble_util_fd_null" -# Note: MSYS1 somehow does not support duping a file descriptor to /dev/zero -[[ -c /dev/zero ]] && ! ble/base/is-msys1 && - ble/fd/.initialize-utility-stream _ble_util_fd_zero '< /dev/zero' base:inherit +## @var _ble_util_fd_zero +## @remark MSYS1 somehow does not support duping a file descriptor to +## /dev/zero +_ble_util_fd_zero= +if [[ -c /dev/zero ]] && ! ble/base/is-msys1; then + if [[ :$bleopt_connect_tty: == *:inherit:* ]]; then + ble/fd#alloc _ble_util_fd_zero '< /dev/zero' base:inherit + else + ble/fd#alloc _ble_util_fd_zero '< /dev/zero' base + fi +fi ## @var[export,opt] _ble_util_fd_tty_stdin ## @var[export,opt] _ble_util_fd_tty_stdout @@ -3319,7 +3415,7 @@ function ble/fd#close-all-tty { local fd for fd in "${ret[@]}"; do if ble/string#match "$fd" '^[0-9]+$' && [[ -t $fd ]]; then - builtin eval "exec $fd>&- $fd>&$_ble_util_fd_null" + ble/fd#alloc/.exec "$fd" ">&$_ble_util_fd_null" ble/opts#remove _ble_util_fdlist_cloexit "$fd" fi done