【问题标题】:Not checking close()'s return value: how serious, really?不检查 close() 的返回值:真的有多严重?
【发布时间】:2013-10-04 01:53:04
【问题描述】:

Linux 的“man close”警告(SVr4、4.3BSD、POSIX.1-2001):

不检查 close() 的返回值是一个常见的,但严重 编程错误。上一次 write(2) 操作的错误很有可能在最后的 close() 中首先报告。关闭文件时不检查返回值可能会导致数据无声丢失。这在 NFS 和磁盘配额中尤其明显。

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

有没有一个简单的、可重复的例子来说明这种无声的数据丢失?甚至是在 close() 期间发送 SIGKILL 之类的人为操作?

如果存在这样的例子,数据丢失是否可以比仅仅处理更优雅

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

【问题讨论】:

  • 尽管我通常会检查结果,但多年后,它似乎化为乌有。期待这个答案。
  • 我一般从不关心close的结果或失败。我想如果你想开发一个非常强大的服务器软件,你会关心的。但是还有更多其他可能的错误 :-) 顺便说一句,很少有免费软件会关心 close 失败。
  • @BasileStarynkevitch:“...还有更多其他可能的错误...”您说的对! :-))

标签: c linux posix bsd


【解决方案1】:

调用POSIX's 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 预计仅出现在特殊条件下,如手册页中所述。但是,至少正因为如此,应该跟踪此错误,因为 如果 它很可能发生了 真的 错误。

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

可能的具体反应:

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

  2. 观察到EINTR 可能表明信号正在疯狂运行。这不好。一定要寻找根本原因。由于尚不清楚描述符是否已关闭,因此请尽快重启系统。

  3. 遇到EIO 肯定会导致相关硬件出现严重故障*1。然而,在强烈建议关闭系统之前,可能值得简单地重试该操作,尽管与EINTR 相同的问题适用于不确定描述符是否真的关闭。万一它确实被关闭了,再次关闭它是一个坏主意,因为它可能已经被另一个线程使用。尽快关机并更换硬件*1


*1 硬件在这里可以看到一个更宽泛的意义:NFS 服务器充当磁盘,因此EIO 可能只是由于错误配置的服务器或网络或 NFS 连接中涉及的任何原因。

【讨论】:

  • 嗯......当close 失败时你应该怎么做?中止?重试?取消?忽略?
  • @Jongware: 无论如何都要把它记录为一个严重的事件,找到根本原因并修复它!是否“中止、重试、忽略”取决于应用程序的关键程度,例如无论是飞机还是游戏。无论你是 NSA 还是脚本小子。
  • 至少EBADF不会导致数据丢失。 EINTR 和 EIO 当然可以,但我寻求的“简单可重现案例”可能涉及硬件的物理破坏......
  • EBADF 不可能在您的程序中没有错误的情况下发生。如果不安装中断信号处理程序,EINTR 就不会发生。
【解决方案2】:

[H]现在或过去 30 年的任何时候有多严重?

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

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

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

在远程文件系统上写入文件时,close()-time 错误非常普遍,如果本地网络容易出现故障或只是丢弃大量数据包。作为最终用户,我希望我的应用程序在写入文件时告诉我是否有错误。通常与远程文件系统的连接完全断开,写入新文件失败的事实是用户的第一个指标。

如果不检查close() 返回值,应用程序会欺骗用户。它将指示(如果不是其他情况,则缺少错误消息)该文件被正确写入,而实际上它不是,并且应用程序被告知如此;该应用程序只是忽略了该指示。如果用户像我一样,他们会对应用程序非常不满。

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

我认为这种态度是应受谴责的;无视用户数据。

不过,这很自然,因为用户无法明确指出哪个应用程序损坏了他们的数据。根据我的经验,最终用户通常会归咎于操作系统、硬件、开源或免费软件,或者本地 IT 支持;因此,对于程序员来说,没有压力,社会或其他方面的压力。因为只有程序员知道这样的细节,而大多数程序员并不关心,所以没有改变现状的压力。

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

有没有一个简单的、可重复的例子来说明这种无声的数据丢失?

我知道三种方法:

  1. 使用 U 盘,在最后一个 write() 之后但在 close() 之前将其拉出。 不幸的是,大多数 USB 记忆棒的硬件无法承受这种情况,因此您最终可能会将 USB 记忆棒变砖。 根据文件系统的不同,您的内核也可能会出现崩溃,因为大多数文件系统都是在假设这永远不会发生的情况下编写的。

  2. 设置 NFS 服务器,并通过使用 iptables 丢弃 NFS 服务器和客户端之间的所有数据包来模拟间歇性数据包丢弃。 确切的场景取决于服务器和客户端、挂载选项和使用的版本。不过,使用两个或三个虚拟机设置测试台应该相对容易。

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

【讨论】:

  • USB 记忆棒场景当然算得上简单和日常。并且报告数据丢失,虽然不如恢复丢失的数据那么愉快,但比无声的数据丢失更好。
  • 转述朋友的话:POSIX 曾经禁止 close() 返回 I/O 错误;它仍然不需要它。来自Linux内核源码:ext2、ext3、ext4、NTFS和FAT不能返回错误; NFS 可以;其他文件系统可能不能。 (不过,NFS 从不尊重 POSIX。)因此检查 close() 可能不会检测到过早移除的拇指驱动器。
  • @CamilleGoudeseune:在 Linux 中,当内核文件系统特定 struct file_operations 中的 ->flush 处理程序返回错误时,Linux 中会发生 close() 错误。在 3.11 上,只有 exofs、fuse、nfs 和 cifs 指定了一个(ecryptfs 也这样做,但它只是调用底层文件系统处理程序),因此 当前 它们是唯一可以在 @ 期间返回错误的987654338@。这并不意味着他们永远不会;进展发生。在所有其他文件系统上,需要fsync()/fdatasync()现在)以确保数据实际成功到达存储,并且即使在这些文件系统上也不会受到伤害。
  • @CamilleGoudeseune: IOW,你是对的:除非你使用保险丝安装 USB 记忆棒,否则如果你过早地拔出 USB 记忆棒,你将不会收到 close() 错误。我以为这已经解决了。实际上,这可能需要为 LKML 提供 RFC 补丁..
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-10
  • 2017-08-13
  • 1970-01-01
  • 2020-01-13
相关资源
最近更新 更多