【问题标题】:What are the reasons to check for error on close()?在 close() 上检查错误的原因是什么?
【发布时间】:2014-08-20 02:14:42
【问题描述】:

注意:请在将其标记为重复之前阅读到最后。虽然相似,但我在答案中寻找的范围超出了上一个问题所要求的范围。

我倾向于同意的广泛实践倾向于将close 纯粹视为文件描述符的资源分配函数,而不是具有有意义的失败案例的潜在 IO 操作。事实上,在the resolution of issue 529 之前,POSIX 在错误发生后未指定文件描述符的状态(即是否仍被分配),因此无法以任何有意义的方式对错误做出可移植的响应。

然而,许多 GNU 软件不遗余力地检查来自 close 的错误,而 Linux man page for close 称未能这样做是“常见但仍然严重的编程错误”。 NFS 和配额被引用为close 可能产生错误但未提供详细信息的情况。

在现实世界的系统中,close 可能在哪些情况下失败,它们与今天是否相关?我特别想知道是否有任何现代系统close 因任何非 NFS、非设备节点特定原因以及 NFS 或设备相关故障在什么条件下(例如配置)失败他们可能会被看到。

【问题讨论】:

  • 也许这个问题与它被标记为重复的问题相似,但另一个问题没有足够的答案来解决我在这个问题末尾要求的细节,而且似乎没有要求这样的细节。所以我认为至少有一些不同。如果这个问题要保持关闭状态,我应该如何为它“重复”的问题获得更可接受的答案?
  • 无论如何,通常无法在写入时检测到物理介质错误。我的意思是 corrupted 文件系统,即底层元数据被损坏的地方。比如说,不正确的片段/块索引,仅在尝试写入时捕获,在close() 时间刷新最终缓存的数据。如果你没有write() 错误并且close() 没有错误,你就知道它被正确写入了,尽管它可能还没有长期存储。 fsync() 一直等到数据到达媒体,这是一个更强大的要求——而且它可能非常慢,尤其是使用 fuse:考虑 sshfs 等。close() 检查几乎没有成本。
  • @NominalAnimal:“正确写入,但尚未长期存储”是什么意思?我无法在close 成功返回后立即拔出书呆子/关闭 NFS 服务器---我必须等待同步---所以close-checking 究竟添加了什么保证?如果你有内核错误,它只是一个会发出噪音的金丝雀吗?)
  • @NominalAnimal:我的印象——这可能是错误的,这就是我想要确定的——检查close 绝对不会提供有关未能将文件提交到存储的信息,由于物理设备故障或逻辑故障(损坏的文件系统)。所以从某种意义上说,检查close 的唯一用处似乎是试图为 NFS-with-caching 提供比您为本地文件提供的更强数据一致性保证。这让我觉得很可疑。如果您关心一致性,则应该同时关心两者,并使用fsync
  • @tmyklebu, R..:Canary 可能是描述我对它的看法的最佳术语。没有“更强”的保证,我只是不想错过 fuse 的内核/nfsd/用户空间组件检测到的问题。损坏的 fs 处理中的错误、NFSv4 在不稳定连接上的委派问题、熔断文件系统中的错误处理错误,都是我正在考虑的——人为错误。您似乎假设/断言close() 永远不会以任何有意义的方式失败。基于什么?相信?希望? 标准?我对待close() 就像对待read()write() 一样。我可能错了,但为了安全起见,我想犯错。

标签: c linux posix


【解决方案1】:

考虑您的问题的反面:“在什么情况下我们可以保证close 会成功?”答案是:

  • 当你正确调用它时,并且
  • 当您知道该文件所在的文件系统在此操作系统和内核版本中不会从 close 返回错误时

如果您确信您的程序没有任何逻辑错误并且您可以完全控制内核和文件系统,那么您不需要检查close 的返回值。

否则,您必须问问自己,您对诊断close 的问题有多关心。我认为出于诊断目的检查和记录错误是有价值的:

  • 如果编码器出现逻辑错误并将无效的 fd 传递给 close,那么您将能够快速找到它。这可能有助于在导致问题之前及早发现错误。
  • 如果用户在close 确实在(例如)数据未刷新时返回错误的环境中运行程序,那么您将能够快速诊断出数据损坏的原因。这是一个简单的危险信号,因为您知道错误不应该发生。

【讨论】:

  • 将无效的 fd 传递给 close 是无法捕获的;更有可能的是,fd 是有效的但属于代码的另一部分,并且通过关闭它,您会触发灾难性的文件损坏或信息泄漏。所以我认为试图抓住EBADF 没有任何价值。您只需要确保它不会发生(如果您在调试时遇到问题,可能会在调试时使用assert)。无论如何,EBADF 超出了我打算询问的范围。
  • 至于第二种情况,close为普通本地文件刷新数据。除了取消分配与打开文件描述相关的 fd 和资源(如果这是最后一个引用此打开文件描述的 fd),它本质上是一个无操作。所以我看不到检查来自close 的错误是如何做的,除了给你一些(仍然很弱的)不寻常的(NFS-with-caching、奇怪的设备、保险丝,...?)文件的一致性保证,你不会有普通的本地文件。看起来如果你真的需要一致性,你必须使用fsync。如果没有,为什么要麻烦?
  • @R..: fuse 文件系统确实有一个文件系统特定的file_operations->flush 处理程序,在close 时间调用,这是唯一可能发生错误的点。因此,您关于 "close 刷新普通本地文件的数据" 的断言对于 fuse 文件系统是不正确的。 ->flush 的确切含义完全取决于文件系统——我不知道——但我不想假设它不会失败,而且我还没有看到它永远不会失败的任何理由。 (除了很多应用程序不检查close() 错误。)
  • @NominalAnimal:你是说 fuse 在write 时间没有提供任何报告错误的方法吗?这也许会让这个问题更有趣。
  • @R..: 不,我的意思是可能有 fuse 文件系统可以在 close 检测到错误,而它们在最后一次 write 期间无法检测到,即使它们是理智的并且否则有用。例如,考虑一个 fuse 文件系统,它在关闭时将文件提交给修订控制,但当前用户在最后一个 writeclose 之间撤销了访问。
【解决方案2】:

曾几何时(2007 年 3 月 24 日),Eric Sosmancomp.lang.c 新闻组中分享了以下故事:

(首先让我承认一个小小的善意谎言:这不是 fclose() 其失败未被检测到,但 POSIX close() 功能;这部分应用程序使用了 POSIX I/O。谎言 但是,它是无害的,因为 C I/O 设施将具有 以完全相同的方式失败,并且未检测到的失败将 产生了同样的后果。我将描述发生的事情 C 的 I/O 条款,以避免过多地关注 POSIX。)

情况与 Richard Tobin 所描述的非常相似。 该应用程序是一个文档管理系统,它加载了一个 文档文件到内存中,将用户的编辑应用到in- 内存复制,然后在被告知时将所有内容写入新文件 保存编辑。它还保持了一个级别的“旧版本” 为安全起见备份:保存操作写入临时 文件,然后如果成功,它会删除旧的备份, 将旧文档文件重命名为备份名称,并将 临时文件到文档。 bak -> 垃圾,doc -> bak,tmp -> doc。

write-to-temp-file 步骤检查了几乎所有内容。这 fopen(),很明显,还有所有的 fwrite(),甚至是 final 检查 fflush() 是否有错误指示——但 fclose() 不是。在一个系统上,最后几个磁盘发生 直到 fclose() 才真正分配块——I/O 系统位于 VMS 的低级文件访问机制之上,以及一个 安排中固有的一点点异步性。

客户的系统启用了磁盘配额,并且 受害者正接近他的极限。他打开一个文件, 编辑了一段时间,保存了他迄今为止的工作,并超越了他的 配额——因为没有出现错误而未被检测到 直到未选中的 fclose()。以为保存成功, 应用程序丢弃了旧备份,重命名了原始备份 文档成为备份,并将截断的临时文件重命名 文件成为新文件。用户工作时间长了一点 并再次保存——同样的事情,只是这次你会注意到 唯一幸存的完整文件被删除,并且 备份和主文档文件被截断。结果: 整个文档文件都变成了垃圾,而不仅仅是最新的会话 工作,但一切都过去了。

正如墨菲所说,受害者是该公司的老板 为我们购买了数百个许可证的部门 软件,我有幸飞往圣路易斯成为 扔给狮子。

[...]

在这种情况下,fclose() 的失败(如果检测到)会 停止删除和重命名序列。用户本来是 告诉“嘿,保存文档时出现问题;做点什么 关于它,然后再试一次。与此同时,磁盘上没有任何变化。” 即使他无法保存他最新的一批作品,他也会 至少没有失去之前的一切。

【讨论】:

  • 我的问题包含短语“在真实世界的系统上,它们今天是否相关”并被标记为posixlinux .因此,与 VMS 相关的轶事并不真正适用。任何现代系统上的配额是否以这种荒谬的方式实施(检查刷新到物理介质而不是逻辑写入)?
  • 区分相关的部分原因是 POSIX 对常规文件的文件操作提出了某些相当严格的要求:它们彼此之间是原子的,并且对其他进程立即可见。所以如果write成功了,数据实际上必须提交到逻辑文件(这样其他访问该文件的进程才能看到);唯一的问题是它是否在物理媒体上,这仅在电源出现故障的情况下才重要。而对于后者,只有fsync,而不是close 检查,会告诉你答案。
  • @R..:我认为这仍然是一个有趣的故事,可以告诉那些想知道为什么要检查 (f)close() 的返回值的人,但我很乐意删除它如果它无关紧要。
  • @R..: fuse 文件系统允许缓存 logical 文件,因为所有访问它的进程都会看到相同的缓存状态。在任何时候都不需要刷新缓存来说明下划线文件系统;如果它在关闭时进行最终刷新,则这是报告写入错误的完全有效点(源自底层存储文件系统,而不是逻辑级别)。 Nisse Engström:fuse 文件系统与 VMS 文件系统没有什么不同,所以在我看来,这个轶事是有效且有用的。
  • 关于fflush 的部分在这里似乎是一个红鲱鱼。据我所知,fflush 不会导致fsync。所有的轶事似乎都在说,write 调用可以在随后的close 失败时成功。
猜你喜欢
  • 2012-01-11
  • 1970-01-01
  • 1970-01-01
  • 2013-06-16
  • 2020-01-29
  • 2019-05-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多