Skip to content

Commit f2da324

Browse files
committed
fix: 问「这是不是终端」的那个请求,不是端口层认得的那一个
musl 的 `isatty` 用 **TIOCGWINSZ** 问,而端口层只答 **TCGETS** —— TCGETS 是**读**终端设置用的请求,不是**问它是不是终端**用的那个: int isatty(int fd) { struct winsize wsz; unsigned long r = syscall(SYS_ioctl, fd, TIOCGWINSZ, &wsz); if (r == 0) return 1; ... } ⇒ 这个端口之上的每一次 `isatty` 都返回 0 —— 对一个真终端和对一个管道一样。 ## ⚠️ 什么都没有失败,这正是它的形状 `std::print` 只是从不走终端那一支;任何靠「问一下」来决定用不用颜色、 用什么缓冲策略的程序,都在无声地决定错。 ## 判据(实测,2026-08-27,同一套 harness 下带原生对照) 管道 伪终端 原生 glibc 0 1 本端口(修前) 0 0 本端口(修后) 0 1 ← 与原生逐字一致 ⭐ **判据是一个关系,不是一个值。** 只断言「管道下为 0」在整个缺陷期间都会通过; 必须断言管道与伪终端**不同**,且与系统自己的 C 库**同样地**不同。CI 里带了原生 对照:若 `script` 分配不出伪终端,这条检查会说自己什么也证明不了,而不是把端口 判成坏的。 ## ⭐ 尺寸报「不知道」,不编一个出来 openkal 没有回答窗口尺寸的操作,而 `winsize` 已被调用方清零。 编一个 80x24 出来会是这份文件唯一明令禁止的形状 ——「报成功而什么也没做」。 一个要尺寸的调用方读到零,而一条串口线报的也是零。
1 parent d9fb0dd commit f2da324

2 files changed

Lines changed: 86 additions & 2 deletions

File tree

.github/workflows/ci.yml

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,61 @@ jobs:
190190
grep kal_random syms.txt; exit 1; }
191191
echo " ok kal_random_fill is weak, kal_time_sleep is strong"
192192
193+
# ⭐⭐ ASKING WHETHER A STREAM IS A TERMINAL GETS THE RIGHT ANSWER.
194+
#
195+
# musl's `isatty' asks with TIOCGWINSZ; this port answered only TCGETS,
196+
# so every `isatty' returned 0 — for a real terminal as readily as for a
197+
# pipe. Nothing failed: `std::print' simply never took its terminal path,
198+
# and a program deciding on colour or on line buffering decided wrongly
199+
# and in silence.
200+
#
201+
# ⭐ THE CRITERION IS A RELATION, NOT A VALUE. `isatty` under a pipe and
202+
# under a pseudo-terminal must DIFFER, and must differ the same way the
203+
# system's own C library does. A test asserting "0 in a pipe" alone would
204+
# have passed throughout the defect.
205+
- name: Asking whether a stream is a terminal is answered, not refused
206+
if: runner.os == 'Linux' && matrix.target == ''
207+
run: |
208+
d="$(mktemp -d)"; mkdir -p "$d/src"
209+
cat > "$d/mcpp.toml" <<TOML
210+
[package]
211+
name = "isattyprobe"
212+
version = "0.1.0"
213+
214+
[dependencies]
215+
openkal-musl = { path = "$PWD" }
216+
217+
[targets.isattyprobe]
218+
kind = "bin"
219+
main = "src/main.c"
220+
221+
[build]
222+
cxx_runtime = "host-coupled"
223+
TOML
224+
sed -i 's/^ //' "$d/mcpp.toml"
225+
printf '#include <unistd.h>\n#include <stdio.h>\nint main(void){ printf("%%d\\n", isatty(1)); return 0; }\n' > "$d/src/main.c"
226+
( cd "$d" && "$MCPP" build --toolchain '${{ matrix.toolchain }}' )
227+
bin="$(find "$d/target" -name isattyprobe -type f | head -1)"
228+
test -n "$bin" || { echo "::error::the probe did not build"; exit 1; }
229+
230+
# The control: the system's own C library, through the same harness.
231+
# Without it a `script` that fails to allocate a pty would make the
232+
# port look broken.
233+
printf '#include <unistd.h>\n#include <stdio.h>\nint main(void){ printf("%%d\\n", isatty(1)); return 0; }\n' > "$d/ctrl.c"
234+
cc "$d/ctrl.c" -o "$d/ctrl"
235+
ctrl_pipe="$("$d/ctrl" | cat | tr -d '\r')"
236+
ctrl_tty="$(script -qec "$d/ctrl" /dev/null | tr -d '\r' | head -1)"
237+
[ "$ctrl_pipe" = 0 ] && [ "$ctrl_tty" = 1 ] \
238+
|| { echo "::error::the harness cannot tell a pty from a pipe (control gave $ctrl_pipe/$ctrl_tty) — this check would prove nothing"
239+
exit 1; }
240+
241+
port_pipe="$("$bin" | cat | tr -d '\r')"
242+
port_tty="$(script -qec "$bin" /dev/null | tr -d '\r' | head -1)"
243+
echo " control: pipe=$ctrl_pipe tty=$ctrl_tty port: pipe=$port_pipe tty=$port_tty"
244+
[ "$port_pipe" = "$ctrl_pipe" ] && [ "$port_tty" = "$ctrl_tty" ] \
245+
|| { echo "::error::isatty over this port disagrees with the system's own C library"; exit 1; }
246+
echo " ok isatty answers the same as the system's own C library"
247+
193248
# ⭐⭐ THE INTERNAL OVERLAY STOPS AT THIS PACKAGE'S BOUNDARY.
194249
#
195250
# musl reaches its own declarations through `src/include`, whose headers

port/src/okm_syscall.c

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -749,9 +749,38 @@ syscall_arg_t __okm_syscall(syscall_arg_t n, syscall_arg_t a1, syscall_arg_t a2,
749749
/* The only question a C library asks through this call is whether
750750
* the stream is a terminal, and openkal answers it. Everything a
751751
* terminal can be asked to do beyond that is not an operation
752-
* openkal has. */
752+
* openkal has.
753+
*
754+
* ⚠️⚠️ AND THE REQUEST IT ASKS IT WITH IS NOT THE ONE THIS BRANCH
755+
* FIRST RECOGNISED. `TCGETS' is the request a C library uses to
756+
* READ a terminal's settings; the one it uses to ASK WHETHER
757+
* something is a terminal is musl's own `isatty':
758+
*
759+
* struct winsize wsz;
760+
* r = syscall(SYS_ioctl, fd, TIOCGWINSZ, &wsz);
761+
* if (r == 0) return 1;
762+
*
763+
* So every `isatty' over this port answered 0 --- for a real
764+
* terminal as readily as for a pipe. Measured 2026-08-27 against a
765+
* native control under one harness:
766+
*
767+
* pipe pseudo-terminal
768+
* native glibc 0 1
769+
* this port 0 0
770+
*
771+
* ⇒ `std::print' never took its terminal path, and any program
772+
* that decides on colour or on line buffering by asking decided
773+
* wrongly and in silence.
774+
*
775+
* ⭐ THE SIZE IS REPORTED AS UNKNOWN RATHER THAN GUESSED. openkal
776+
* has no operation that answers it, and `winsize' is already
777+
* zeroed by the caller; a fabricated 80x24 would be this file's one
778+
* forbidden shape --- reporting success having done nothing.
779+
* A caller that wants the size reads zero, which is what a serial
780+
* line reports too. */
753781
if (!interactive) return -ENOTTY;
754-
if ((unsigned long)a2 == TCGETS) return 0;
782+
if ((unsigned long)a2 == TCGETS) return 0;
783+
if ((unsigned long)a2 == TIOCGWINSZ) return 0;
755784
return -ENOTTY;
756785
}
757786
return -ENOTTY;

0 commit comments

Comments
 (0)