【发布时间】:2021-05-13 00:31:21
【问题描述】:
以下小 C 程序(我们称之为pointless):
/* pointless.c */
#include <stdio.h>
#include <unistd.h>
void main(){
write(STDOUT_FILENO, "", 0); /* pointless write() of 0 bytes */
sleep(1);
write(STDOUT_FILENO, "still there!\n", 13);
}
将打印“还在那儿!”正如预期的那样,经过一小段延迟。然而,
rlwrap ./pointless 在 AIX 下不打印任何内容并立即退出。
显然,rlwrap 在第一个 write() 之后读取了 0 个字节,并且
(错误地)决定 pointless 已将其退出。
当在没有rlwrap 的情况下运行pointless 时, 在所有情况下运行rlwrap
我可以接触到的其他系统(Linux、OSX、FreeBSD),“仍然
那里!”按预期打印。
相关的rlwrap(伪)代码是这样的:
/* master is the file descriptor of the master end of a pty, while the slave is 'pointless's stdout */
/* master was opened with O_NDELAY */
while(pselect(nfds, &readfds, .....)) {
if (FD_ISSET(master, &readfds)) { /* master is "ready" for reading */
nread = read(master, buf, BUFFSIZE - 1); /* so try to read a buffer's worth */
if (nread == 0) /* 0 bytes read... */
cleanup_and_exit(); /* ... usually means EOF, doens't it? */
显然,在除 AIX 之外的所有系统上,writeing 在
pty 的从端是无操作的,而在 AIX 上它会唤醒
select() 在主端。写 0 个字节似乎毫无意义,但一个
我的测试程序写入随机长度的文本块,这可能
实际上恰好有长度 0。
在 linux 上,man 2 read 声明“成功时,读取的字节数为
返回(零表示文件结束)“(斜体是我的)这个
问题有come up
before
没有提到这种情况。
这引出了一个问题:我如何便携地确定是否
从端已关闭? (在这种情况下,我可能只能等待
SIGCHLD 然后关闭商店,但这可能会打开另一罐
我宁愿避免的蠕虫)
编辑:POSIX 状态:
将长度为零的缓冲区(nbyte 为 0)写入 STREAMS 设备会发送 0 个字节并返回 0。但是,将长度为零的缓冲区写入基于 STREAMS 的管道或 FIFO 不会发送任何消息并返回 0。该进程可能会发出 I_SWROPT ioctl() 以启用通过管道或 FIFO 发送零长度消息。
在 AIX 上,pty 确实是一个 STREAMS 设备,而且,不是管道或 FIFO。 ioctl(STDOUT_FILENO, I_SWROPT, 0) 似乎可以使 pty 符合 Unix 世界的其他部分。可悲的是,这必须从 slave 端调用,在rlwraps 影响范围之外也是如此(即使我们可以在fork() 和@987654345 之间调用ioctl() @ - 这不能保证执行的命令不会变回)
【问题讨论】:
-
Per POSIX: “当尝试从空管道或 FIFO 中读取时:如果没有进程打开管道进行写入,read() 将返回 0 以指示文件结束。”所以“读取零字节意味着 EOF”是 POSIX 兼容的。
-
是的,我也看到了。我的问题是:它声明如果在EOF,则返回0。但是它没有说如果返回0,你可以假设EOF。但是,表示文件结束的短语似乎暗示着确实可以双向阅读。
-
出于好奇,您能否在 AIX 上尝试
ioctl(STDOUT_FILENO, I_SWROPT, 0)开头的pointless.c看看会发生什么? (why I think this may change the behavior) -
@Sergey_Kalinichenko:很好。有时间我再试试……
-
@Sergey_Kalinichenko:
ioctl(STDOUT_FILENO, I_SWROPT, 0)成功了!当我在pointless.c的开头插入它时,rlwrap不会从pselect()返回,而pointless执行0 位write。我想我会在 pty 的从属端的fork()和execute()之间使用它。非常感谢您的提示!理论上,客户端程序可以撤消ioctl(),但是这个错误已经被忽视了20多年,所以解决方案不一定是完美的.....