gpt4 book ai didi

c - fgets()、信号 (EINTR) 和输入数据完整性

转载 作者:太空狗 更新时间:2023-10-29 15:01:42 27 4
gpt4 key购买 nike

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”中间的信号传递,并确保其部分读取的数据在此之后完全丢失。

如何测试:

  1. 用strace运行程序
  2. 输入一些行(不要按 Enter),按 Ctrl+D,看到 read() 系统调用完成了一些数据
  3. 发送SIGINT
  4. 查看 fread() 返回 NULL,“attempt 2”并输入一些数据并按 Enter 键
  5. 它将打印第二个输入的数据,但不会在任何地方首先打印

FreeBSD 11 libc:相同的行为

FreeBSD 8 libc:第一次尝试返回部分读取的数据并设置 ferror() 和 errno

编辑:根据@John Bollinger 的建议,我在 NULL 返回后添加了缓冲区转储。结果:

glibc 和 FreeBSD 11 libc:缓冲区包含部分读取的数据但不是 NULL-TERM,因此获取其长度的唯一方法是在调用 fgets() 之前清除整个缓冲区,这看起来不像预期的用途

FreeBSD 8 libc:仍然返回正确的空终止部分读取数据

最佳答案

对于中断信号处理程序,stdio 确实不能合理地使用

根据 ISO C 11 7.21.7.2 fgets 函数,第 3 段:

The fgets function returns s if successful. If end-of-file is encountered and no characters have been read into the array, the contents of the array remain unchanged and a null pointer is returned. If a read error occurs during the operation, the array contents are indeterminate and a null pointer is returned.

EINTR 是一个读取错误,因此返回后数组内容不确定。

理论上,可以为 fgets 指定行为,您可以通过在操作之前适本地设置缓冲区来有意义地从操作中间的错误中恢复调用,因为您知道 fgets 不会写入 '\n' 除非作为空终止前的最后一个字符(类似于使用 fgets 的技术> 带有嵌入式 NUL)。但是,它没有那样指定,并且没有类似的方法来处理其他 stdio 函数,例如 scanf,它们无处存储状态以在 EINTR 之后恢复它们。

实际上,信号只是一种非常落后的做事方式,而中断信号是一种更加落后的工具,充满了竞争条件和其他令人不快且无法修复的极端情况。如果你想以一种安全和现代的方式做这种事情,你可能需要一个通过管道或套接字转发标准输入的线程,并关闭信号处理程序中管道或套接字的写入端,以便主要从中读取的部分程序会收到 EOF。

关于c - fgets()、信号 (EINTR) 和输入数据完整性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56414520/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com