【问题标题】:read(fd, buf, N>0) == 0, but fd not at EOF?读取(fd,buf,N>0)== 0,但 fd 不在 EOF 处?
【发布时间】: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多年,所以解决方案不一定是完美的.....

标签: c posix aix rlwrap


【解决方案1】:

Per POSIX:

尝试从空管道或 FIFO 中读取时:

  • 如果没有进程打开管道进行写入,read() 将返回 0 以指示文件结束。"

因此“读取零字节意味着 EOF”符合 POSIX。

the write() side(我的粗体字):

在采取下述任何操作之前,如果nbyte 为零且文件是常规文件,则write() 函数可能会检测并返回如下所述的错误。在没有错误的情况下,或者如果没有执行错误检测,write() 函数将返回零并且没有其他结果。 如果nbyte 为零且文件不是常规文件,则结果未指定。

不幸的是,这意味着您不能可移植地依赖零字节的 write() 无效,因为 AIX 在此处符合 write() 的 POSIX 标准。

你可能不得不依赖SIGCHLD

【讨论】:

  • 这是重点。不要用nbytes==0 调用write,除非您想处理这样做的未指定的、可能特定于实现的后果。
  • 重点是,作为rlwrap 的维护者,我遇到的程序会诸如用nbytes = 0 调用write 之类的事情。当然,我只能说那些程序做错了,但它们存在,所以我必须处理它们......
  • @HansLub isastream() 可能很有用。
【解决方案2】:

来自linux man page

如果 count 为零且 fd 引用常规文件,则 write() 如果以下错误之一是,可能会返回失败状态 检测到。如果未检测到错误,或未检测到错误 执行时,将返回 0 而不会引起任何其他影响。 如果 count 为零并且 fd 引用的文件不是常规文件 文件,未指定结果。

所以,由于它是未指定的,它可以根据你的情况做任何事情。

【讨论】:

  • 您的 IBM 链接适用于 z/OS。
  • 是的。但在 POSIX 中,您所依赖的未定义行为。所以它可以在一种情况下工作,但不能在另一种情况下工作,正如你所经历的那样。
  • z/OS 链接声明“如果 N 为零,则 write() 仅返回 0 而不尝试任何其他操作。”这不是 AIX 上正在发生的事情。
  • 未定义未定义。我删除了指向 IBM 的链接,因为它只是为了表明您有不同的行为,具体取决于操作系统,因为它是未定义的。
  • 嗯,“未指定”与“未定义”不同。
猜你喜欢
  • 1970-01-01
  • 2015-06-11
  • 1970-01-01
  • 2022-07-25
  • 1970-01-01
  • 1970-01-01
  • 2021-09-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多