如何安全地写入文件?

23 投票
8 回答
11500 浏览
提问于 2025-04-15 16:28

想象一下,你有一个库可以用来处理某种XML文件或配置文件。这个库会把整个文件读到内存中,并提供一些方法来编辑内容。当你完成内容的修改后,可以调用一个write方法把内容保存回文件。问题是,怎么才能安全地做到这一点。

直接覆盖原来的文件(也就是开始写入原文件)显然是不安全的。如果write方法在完成之前出错,你就会得到一个半成品的文件,这样数据就丢失了。

一个更好的选择是先写入一个临时文件,等write方法完成后,再把这个临时文件复制到原文件。

这样,如果复制过程中出现问题,你仍然可以在临时文件中找到正确保存的数据。如果复制成功了,你就可以删除临时文件。

在POSIX系统上,我想你可以使用rename系统调用,这个操作是原子性的(也就是说要么全部成功,要么全部失败)。但在Windows系统上,你该怎么做呢?特别是,使用Python时,怎么处理这个问题比较好?

另外,还有没有其他安全写入文件的方法呢?

8 个回答

6

在Windows的API中,我发现了一个很不错的函数ReplaceFile,这个函数的名字就说明了它的功能,甚至还可以选择备份文件。你总是可以通过DeleteFileMoveFile这两个函数组合来实现类似的效果。

总的来说,你想做的事情是非常好的。我想不出有什么更好的写入方案。

13

如果你想要遵循POSIX标准并且确保数据安全,你需要:

  1. 先写入一个临时文件
  2. 刷新并使用 fsync 这个文件(或者用 fdatasync
  3. 把临时文件重命名为原来的文件名

需要注意的是,调用fsync可能会对性能产生不可预测的影响——在Linux的ext3文件系统上,可能会因为其他的输入输出操作而导致磁盘读写停顿几秒钟。

另外要知道,在POSIX标准中,rename 并不是一个原子操作——至少在你期望的文件数据方面不是。不过,大多数操作系统和文件系统通常会这样工作。但看起来你错过了关于Ext4和文件系统原子性保证的一个很大的讨论。我不太确定具体的链接在哪里,但这里有个起点: ext4和数据丢失

不过要注意,在很多系统上,重命名操作在实际使用中是相对安全的。然而,想要在所有可能的Linux配置中同时获得性能和可靠性是有点困难的!

如果先写入一个临时文件,然后再重命名这个临时文件,大家通常会认为这些操作是相互依赖的,并且会按顺序执行。

问题在于,大多数文件系统将元数据和数据分开。重命名操作只是涉及元数据。虽然听起来可能很糟糕,但文件系统更看重元数据而不是数据(比如HFS+或Ext3、Ext4中的日志记录)!原因是元数据相对轻量,如果元数据损坏,整个文件系统都会损坏——文件系统必须首先保护自己,然后再保护用户的数据。

Ext4在最初发布时打破了对rename的期望,不过后来添加了一些启发式的方法来解决这个问题。问题并不是重命名失败,而是重命名成功。Ext4可能成功注册了重命名,但如果在此之后发生崩溃,可能会导致文件数据未能写入。结果就是生成一个长度为0的文件,既没有原始数据也没有新数据。

所以简而言之,POSIX并没有这样的保证。想了解更多信息,请阅读链接中的Ext4文章!

19

如果你查看Python的文档,会发现它明确提到os.rename()是一个原子操作。这意味着在你的情况下,先把数据写入一个临时文件,然后再把它改名为原来的文件是非常安全的。

还有一种方法可以这样操作:

  • 假设原文件是abc.xml
  • 创建一个abc.xml.tmp文件,并把新数据写入这个文件
  • 把abc.xml改名为abc.xml.bak
  • 把abc.xml.tmp改名为abc.xml
  • 在新的abc.xml放好后,删除abc.xml.bak

这样你就有了abc.xml.bak这个备份文件,如果临时文件或复制过程中出现任何问题,你可以用它来恢复。

撰写回答