【发布时间】:2015-02-14 15:41:42
【问题描述】:
POSIX 以允许进程重命名和取消链接文件条目而著称,而不考虑对使用它们的其他人的影响,而 Windows 默认情况下,如果您尝试触摸在某个深处打开了文件句柄的目录的时间戳,则会引发错误里面里面。
不过,Windows 不需要这么保守。如果您使用 FILE_FLAG_BACKUP_SEMANTICS 和 FILE_SHARE_DELETE 打开所有文件句柄,并注意在标记删除之前将文件重命名为随机名称,您将获得 POSIX 语义,包括对操作包含打开文件句柄的文件路径的限制。
Windows 可以做的一件非常漂亮的事情是仅使用打开的文件描述符执行重命名和删除以及硬链接,因此您可以删除文件而不必担心是否有另一个进程重命名了它或其中的任何目录文件位置之前的路径。此工具可让您执行完全无竞争的文件删除 - 一旦您拥有正确文件的打开文件句柄,您就可以停止关心其他进程对文件系统所做的事情,至少对于删除(这是最重要的,因为它隐含地涉及破坏数据)。
这提出了关于 POSIX 的问题?在 POSIX 上 unlink() 采用路径,并且在使用 /proc/self/fd/x 或 F_GETPATH 检索文件描述符的当前路径和调用 unlink() 之间,有人可能已经更改了该路径,因此可能导致错误的文件被取消链接并丢失数据。
一个相当安全的解决方案是这样的:
- 使用 /proc/self/fd/x 或 F_GETPATH 等获取打开文件描述符的当前路径之一。
- 打开其包含的目录。
- 对打开文件描述符的叶子名称的包含目录执行 statat(),检查设备 ID 和 inode 是否匹配。
- 如果它们匹配,则执行 unlinkat() 以删除叶子名称。
这是从父目录向上竞争安全的,尽管您删除的硬链接可能不是预期的。但是,如果在包含目录中第三方进程将您的文件重命名为其他文件并将另一个文件重命名为您的叶子名称,则在您检查 inode 等效性和调用 unlinkat() 之间是不安全的。这里可能会删除错误的文件,并丢失数据。
因此我提出一个问题:POSIX 或任何特定的 POSIX 实现(例如 Linux)是否允许程序完全取消链接文件条目的链接?一种解决方案可能是通过打开文件描述符取消链接文件条目,另一种可能是通过 inode 取消链接文件条目,但谷歌尚未针对其中任何一个提供解决方案。有趣的是,除了通过打开文件句柄进行删除之外,NTFS 确实允许您通过选择 inode 或 GUID 来删除(是的,NTFS 确实提供了 inode,您可以从 NT 内核中获取它们),但那不是在这里帮助不大。
如果这个问题看起来太深奥,这个问题会影响proposed Boost.AFIO,我需要确定哪些文件系统竞争可以缓解,哪些不能作为其记录的硬行为保证的一部分。
编辑: 澄清了打开文件描述符没有规范的当前路径,在这个用例中我们不在乎 - 我们只想取消链接文件。
【问题讨论】:
-
“打开文件描述符的当前路径”没有意义。文件描述符是无状态的,因为它不跟踪用于获取文件的路径。如果你在一个有三扇门的房间里,关上你用来进入房间的门不会让其他人无法进入房间,就像取消链接你用来获取文件描述符的路径可能不会删除任何数据一样.
-
@WilliamPursell:抱歉,刚刚意识到您指的是硬链接。我们不在乎删除文件是否实际上并没有删除它。删除文件的代码只会取消它们的链接,如果其他硬链接仍然存在的话。
-
Niall 那么您到底关心什么?如果您想取消链接,请执行此操作。没有竞争条件。
-
如果您想确保某个进程在打开链接之前不会更改链接所引用的文件,请使用建议锁定。
-
正如我在 OP 中解释的那样,在比较来自 statat() 和 unlinkat() 的 inode 的等价性时存在竞争。对于奥斯汀工作组来说,这似乎太明显了,不能错过。我担心我错过了什么。
标签: linux file-io path posix race-condition