没有检查close()的返回值:问题有多严重?

9

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

不检查close()的返回值是一种常见但严重的编程错误。很可能在最终的close()中首先报告以前的write(2)操作中的错误。关闭文件时不检查返回值可能导致数据的静默丢失。这在NFS和磁盘配额中尤其明显。

我可以相信这种错误是普遍存在的(至少在应用程序中;我不是内核黑客)。但它有多么严重,无论是今天还是过去三十年?特别是:

是否有一个简单、可重复的示例,说明这种数据的静默丢失?即使像在close()期间发送SIGKILL这样的人为制造的例子?

如果存在这样的例子,那么数据丢失能否比仅仅

printf("对不起,伙计,你丢失了一些数据。\n");更加优雅地处理?


尽管我通常会检查结果,但经过多年,似乎一无所获。期待这个答案。 - chux - Reinstate Monica
1
我通常不关心“close”操作的结果或失败。如果您想开发非常健壮的服务器软件,我猜您会关心它。但还有更多其他可能的错误情况 :-) 顺便说一句,很少有自由软件关心“close”操作的失败。 - Basile Starynkevitch
@BasileStarynkevitch:“*…还有许多其他错误的可能性...*” 你说得太对了! :-)) - alk
1
这是一篇有趣的LWN文章,关于在Linux上检查close()的返回值。根据Torvalds本人的说法,_"想要听到IO错误的'小心'用户必须真正执行fsync(),因此任何IO错误都应该在那里显示。当然,除了fsync()之外,还检查'close()'的返回值总是一个好主意"_。 - user986730
2个回答

9
今天或过去30年的任何时候,它有多严重?
典型应用程序处理数据。它们消耗一些输入并产生结果。因此,在关闭只读文件和关闭刚生成或修改的文件时,可能会出现close()返回错误的两种情况。
已知的close()返回错误的情况都特定于将数据写入/刷新到永久存储器中。特别是,在实际写入永久存储器之前,操作系统通常会在本地缓存数据(在close()fsync()fdatasync()处);这在远程文件系统中非常普遍,这就是为什么NFS在man页面中提到的原因。
我从未遇到过关闭只读输入文件时出错的情况。我能想到的所有情况,即使使用任何常见的文件系统,在现实生活中可能发生的情况都是灾难性的失败,例如内核数据结构损坏。如果发生了这种情况,我认为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-)将是相当容易和安全的。它实际上不会破坏数据,但它可以方便地检查应用程序在内核报告(风险的)数据损坏时的行为。


USB闪存驱动器的情况肯定算是简单和日常的。 而报告数据丢失虽然不如恢复丢失的数据令人高兴,但比默默承受数据丢失要好。 - Camille Goudeseune
朋友的话的转述: POSIX曾经禁止close()返回I / O错误;现在仍然不需要。来自Linux内核源代码:ext2,ext3,ext4,NTFS和FAT无法返回错误;NFS可以;其他文件系统可能无法。 (尽管如此,NFS从未真正尊重过POSIX。)因此,检查close()可能无法检测到提前拔出的thumbdrive。 - Camille Goudeseune
2
@CamilleGoudeseune:在Linux中,当内核文件系统特定的struct file_operations中的->flush处理程序返回错误时,会发生close()错误。在3.11版本中,只有exofs、fuse、nfs和cifs指定了一个(ecryptfs也是如此,但它只调用底层文件系统处理程序),因此目前它们是唯一可能在close()期间返回错误的文件系统。这并不意味着它们永远不会;进步是不可避免的。在所有其他文件系统上,需要进行fsync()/fdatasync()以确保数据实际上成功存储到存储设备上,即使在这些文件系统上也不会有任何损失。 - Nominal Animal
@CamilleGoudeseune:也就是说,你是对的:除非使用fuse挂载USB存储设备,否则如果你在未安全移除USB存储设备的情况下将其拔出,你将不会收到close()错误。我以为这个问题已经被解决了。实际上,这可能需要向LKML提交RFC补丁。 - Nominal Animal

7

调用POSIX的close()可能导致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 只有在特定条件下才会出现,就像 man 手册中提到的那样。但是至少因为这个原因,应该追踪此错误,因为如果它发生了,很可能出现了真正的问题。

总之,这些错误中的每一个都有至少一个被捕获的好理由,所以做吧! ;-)

可能的具体反应:

  1. 忽略EBADF可能是可以接受的,但这个错误不应该发生。正如所述,请修复您的代码,因为程序似乎并不知道它在做什么。

  2. 遇到EINTR可能表明信号正在狂奔。这不好。一定要查找根本原因。由于不确定描述符是否已关闭,因此尽快重启系统。

  3. 遇到EIO肯定会表明硬件*1发生了严重故障。但在强烈建议关闭系统之前,重试操作可能是值得的,尽管与EINTR相同的担忧也适用,即不确定描述符是否真的已关闭。如果确实关闭了,再次关闭它是一个坏主意,因为它可能已经被另一个线程使用。尽快关闭和更换硬件*1


*1 在此,硬件应该在更广泛的意义上被视为:NFS服务器充当磁盘,因此 EIO 可能只是由于未正确配置的服务器、网络或与NFS连接有关的任何因素导致的。


当“关闭”失败时,您应该做什么?终止?重试?取消?忽略? - Jongware
1
@Jongware:无论如何,将其记录为严重事件,找出根本原因并修复!"中止、重试、忽略"取决于应用程序的重要性,例如是飞机还是游戏,是国家安全局还是脚本小子。 - alk
至少EBADF不会导致数据丢失。EINTR和EIO可能会,但我寻找的“简单可重现的情况”可能涉及硬件的物理破坏... - Camille Goudeseune
"EBADF" 不能发生在没有程序错误的情况下。而且,如果没有安装中断信号处理程序,"EINTR" 也不会发生。 - R.. GitHub STOP HELPING ICE

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接