【发布时间】:2019-06-02 11:18:18
【问题描述】:
fgets() 用于读取某些字符串,直到出现EOF 或\n。例如读取文本配置文件非常方便,但存在一些问题。
首先,在信号传递的情况下,它可能会返回EINTR,因此应该用循环检查来包装它。
第二个问题更糟糕:至少在 glibc 中,它会返回 EINTR 并丢失所有已经读取的数据,以防它在中间传递。这不太可能发生,但我认为这可能是某些守护进程中一些复杂漏洞的来源。
在信号上设置SA_RESTART 标志似乎有助于避免这个问题,但我不确定它是否涵盖所有平台上的所有可能情况。是吗?
如果没有,有没有办法完全避免这个问题?
如果不是,fgets() 似乎不能用于在守护进程中读取文件,因为它可能会导致随机数据丢失。
测试示例代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
static char buf[1000000];
static volatile int do_exit = 0;
static void int_sig_handle(int signum) { do_exit = 1; }
void try(void) {
char * r;
int err1, err2;
size_t len;
memset(buf,1,20); buf[20]=0;
r = fgets(buf, sizeof(buf), stdin);
if(!r) {
err1 = errno;
err2 = ferror(stdin);
printf("\n\nfgets()=NULL, errno=%d(%s), ferror()=%d\n", err1, strerror(err1), err2);
len = strlen(buf);
printf("strlen()=%u, buf=[[[%s]]]\n", (unsigned)len, buf);
} else if(r==buf) {
err1 = errno;
err2 = ferror(stdin);
len = strlen(buf);
if(!len) {
printf("\n\nfgets()=buf, strlen()=0, errno=%d(%s), ferror()=%d\n", err1, strerror(err1), err2);
} else {
printf("\n\nfgets()=buf, strlen()=%u, [len-1]=0x%02X, errno=%d(%s), ferror()=%d\n",
(unsigned)len, (unsigned char)(buf[len-1]), err1, strerror(err1), err2);
}
} else {
printf("\n\nerr\n");
}
}
int main(int argc, char * * argv) {
struct sigaction sa;
sa.sa_flags = 0; sigemptyset(&sa.sa_mask); sa.sa_handler = int_sig_handle;
sigaction(SIGINT, &sa, NULL);
printf("attempt 1\n");
try();
printf("\nattempt 2\n");
try();
printf("\nend\n");
return 0;
}
此代码可用于测试“尝试 1”中间的信号传递,并确保其部分读取的数据在此之后完全丢失。
如何测试:
- 使用 strace 运行程序
- 输入某行(不要按 Enter),按 Ctrl+D,请参阅
read()syscall completed with some data - 发送
SIGINT - 查看
fread()返回NULL,“尝试2”并输入一些数据并按Enter - 它将打印第二个输入的数据,但不会在任何地方先打印
FreeBSD 11 libc:相同的行为
FreeBSD 8 libc:第一次尝试返回部分读取的数据并设置 ferror() 和 errno
编辑: 根据@John Bollinger 的建议,我在返回 NULL 后添加了缓冲区转储。结果:
glibc 和 FreeBSD 11 libc:缓冲区包含部分读取的数据,但不是 NULL-TERM,因此获取其长度的唯一方法是在调用 fgets() 之前清除整个缓冲区,这看起来不像预期用途
FreeBSD 8 libc:仍然返回正确的以 null 结尾的部分读取数据
【问题讨论】:
-
您可以在循环中使用 getc() 而不是 fgets()
-
是的,我知道还有其他方法可以做到这一点,但问题是关于 fgets,因为与其他任务相比,它是非常方便的功能。
-
@firk 我认为这是 stdio 的主要问题之一。我有自己的缓冲 IO 实现的原因之一。
-
我通常不会对此发表评论,但由于提供的代码包含所有其他需要的
#include指令,我会发现它未能包含stdio.h。 -
那是格式错误
标签: c signals posix stdio libc