不检查close()的返回值:多严重,真的吗?

Cam*_*une 8 c linux bsd posix

Linux的"man close"警告(SVr4,4.3BSD,POSIX.1-2001):

不检查close()的返回值是一个常见严重的编程错误.很可能首先在最后的close()上报告先前write(2)操作的错误.关闭文件时不检查返回值可能会导致数据无声丢失.使用NFS和磁盘配额尤其可以观察到这种情况.

我可以相信这个错误是常见的(至少在应用程序中;我不是内核黑客).但是,在今天或过去三十年的任何时候,它有多严重?特别是:

是否存在这种静默数据丢失的简单,可重现的示例?即使是一个人为的人,比如在close()期间发送SIGKILL?

如果存在这样的示例,则可以比仅仅更优雅地处理数据丢失

printf("Sorry, dude, you lost some data.\n");

Nom*_*mal 7

[H]严重的是,今天还是过去三十年的任何一点?

典型应用处理数据.他们消耗一些输入,并产生结果.因此,有两种情况close()可能会返回错误:关闭输入(只读?)文件时,以及关闭刚刚生成或修改的文件时.

close()返回错误的已知情况特定于将数据写入/刷新到永久存储器.特别是,它是常见的操作系统来缓存数据在本地,实际写入永久存储之前(在close(),fsync()fdatasync()); 这在远程文件系统中非常常见,这也是手册页中提到NFS的原因.

关闭只读输入文件时,我从未遇到过错误.我可以想到使用任何常见文件系统在现实生活中可能发生的所有情况都是发生灾难性故障的情况,例如内核数据结构损坏.如果发生这种情况,我认为close()错误不是唯一一个非常错误的迹象.

当写入远程文件系统上的文件时,close()如果本地网络容易出现故障或者丢弃大量数据包,则时间错误很常见.作为最终用户,我希望我的应用程序告诉我写入文件时是否有错误.通常,与远程文件系统的连接完全被破坏,并且写入新文件失败的事实是用户的第一个指示.

如果您不检查close()返回值,则应用程序将由用户负责.它将指示(由于缺少错误消息,否则),文件被正确写入,而事实上它不是,并且应用程序被告知; 应用程序只是忽略了指示.如果用户和我一样,他们会对应用程序非常不满意.

问题是,用户数据对您有多重要?大多数当前的应用程序员根本不关心.Basile Starynkevitch(在对原始问题的评论中)是绝对正确的; 检查close()错误并不是大多数程序员都在做的事情.

我相信这种态度是应该受到谴责的; 骑士无视用户数据.

但这很自然,因为用户没有明确指出哪个应用程序损坏了他们的数据.根据我的经验,最终用户最终会指责操作系统,硬件,开源或免费软件或本地IT支持; 因此,程序员无需承担社交或其他方面的压力.因为只有程序员知道这样的细节,并且大多数程序员都不在乎,所以没有改变现状的压力.

(我知道说上面会让很多程序员讨厌我的胆量,但至少我是诚实的.我指出这样的事情的典型反应是,这是一种罕见的情况,它会是浪费资源来检查这一点.这可能是真的..但我愿意花费更多的CPU周期并向程序员多付几个百分点,如果这意味着我的机器实际上工作更加可预测,并告诉我是否它失去了情节,而不是默默地破坏我的数据.)

是否存在这种静默数据丢失的简单,可重现的示例?

我知道三种方法:

  1. 使用USB记忆棒,在决赛之后write()但在之前将其拉出来close().不幸的是,大多数USB记忆棒的硬件都不是为了能够存活而设计的,因此最终可能会使USB记忆棒变硬.根据文件系统的不同,您的内核也可能会出现恐慌,因为大多数文件系统都是在假设永远不会发生的情况下编写的.

  2. 设置NFS服务器,并通过使用iptables丢弃NFS服务器和客户端之间的所有数据包来模拟间歇性数据包丢弃.具体方案取决于服务器和客户端,装入选项以及使用的版本.但是,使用两个或三个虚拟机应该相对容易地设置测试台.

  3. 使用自定义文件系统来模拟写入错误close().当前内核不允许强制卸载tmpfs或环回挂载,只允许NFS挂载,否则通过在最终写入之后强制卸载文件系统但在之前更新,可以很容易地进行模拟close().(如果该文件系统上存在打开的文件,则当前内核只是拒绝umount.)对于应用程序测试,创建tmpfs的变体,close()如果文件模式指示它是合意的,则返回错误(例如,其他可写但不是其他 -可读或其他可执行的,即.-??????-w-)将是非常容易和安全的.它实际上不会破坏数据,但如果内核在关闭时报告(风险)数据损坏,它将使检查应用程序的行为变得容易.

  • @CamilleGoudeseune:在Linux中,当内核文件系统特定的`struct file_operations`中的` - > flush`处理程序返回错误时,会发生Linux中的close()错误.在3.11上,只有exofs,fuse,nfs和cifs指定一个(ecryptfs也可以,但它只调用底层文件系统处理程序),所以*目前*它们是唯一可以在`close()`期间返回错误的.这并不意味着他们永远不会; 进步发生了.在所有其他文件系统上,需要`fsync()`/`fdatasync()`(*for now*)以确保数据实际成功地访问存储,并且即使在这些上也不会受到影响. (2认同)

alk*_*alk 6

调用POSIXclose()可能会导致errno设置为:

  1. EBADF:错误的文件编号
  2. EINTR:系统调用中断
  3. EIO:I/O错误(来自POSIX规范问题6)

不同的错误表明不同的问题

  1. EBADF指示编程错误,因为程序应该跟踪哪些文件/套接字描述符仍然打开.我会考虑将此错误作为质量管理操作进行测试.

  2. EINTR似乎是最难处理的,因为不清楚函数返回后传递的文件/套接字描述符是否有效(在Linux下它可能不是:http://lkml.org/lkml/2002/7/ 17/165).观察此错误,您应该检查程序处理信号的方式.

  3. EIO预计只会出现在特殊条件下,如手册中所述.但是至少只是因为这个应该跟踪这个错误,好像它很可能发生了一些真正的错误.

总而言之,这些错误中至少有一个被抓住的好理由,所以就这样做吧!;-)

可能的具体反应:

  1. 在稳定性方面,忽略EBADF可能是可以接受的,但是错误不会发生.如上所述修复你的代码,因为程序似乎并不真正知道它在做什么.

  2. 观察EINTR可能表明信号正在疯狂.这不好.绝对是根本原因.由于尚不清楚描述符是否已关闭,因此请尽快重新启动系统.

  3. 运行到EIOdefinitly可以inicate在硬件严重故障*1介入.但是,在强烈建议关闭系统之前,简单地重试操作可能是值得的,尽管同样的问题适用于EINTR不确定描述符是否真的关闭.如果它确实关闭了,再次关闭它是一个坏主意,因为它可能已被另一个线程使用.尽快关机和硬件*1更换.


*1硬件在这里可以看出:NFS服务器充当磁盘,因此EIO可能仅仅是由于配置错误的服务器或网络或NFS连接中涉及的任何内容.