覆盖文件而不会有损坏文件的风险

Posted

技术标签:

【中文标题】覆盖文件而不会有损坏文件的风险【英文标题】:Overwriting a file without the risk of a corrupt file 【发布时间】:2013-07-26 14:33:28 【问题描述】:

我的应用程序经常想要保存文件以便稍后再次加载。最近因为崩溃而倒霉,我想以这样一种方式编写操作,以保证我要么拥有新数据,要么拥有原始数据,但没有损坏的混乱。

我的第一个想法是按照以下方式做一些事情(保存名为 example.dat 的文件):

    为目标目录指定一个唯一的文件名,例如例子.dat.tmp 创建该文件并将我的数据写入其中。 删除原始文件(example.dat) 将临时文件重命名(“移动”)到原来的位置(example.dat.tmp -> example.dat)。

那么在加载时应用程序可以遵循以下规则:

如果没有“example.dat”也没有“example.dat.tmp”,首先运行/新建项目,所以加载默认值/创建新文件。 如果“example.dat”且没有“example.dat.tmp”,则加载example.dat(正常负载情况) 如果存在“example.dat.tmp”,则为用户提供恢复数据的机会。如果“example.dat”也存在,请不要在没有明确用户常量的情况下覆盖它。

但是,经过一些研究,我发现除了可以使用文件刷新方法覆盖的操作系统缓存之外,一些磁盘驱动器仍然在内部缓存,甚至可能欺骗操作系统说它们已经完成, 所以 4. 可以完成,写入实际上并没有写入,如果系统宕机我已经丢失了我的数据...

我不确定磁盘问题实际上是否可以通过应用程序解决,但以上一般规则是否正确?我是否应该将文件的旧恢复副本保留更长时间,以确保有关这些事情的指导方针是什么(例如,可接受的磁盘使用情况、用户应选择的位置、放置此类文件的位置等)。

另外我应该如何避免用户和其他程序对“example.dat.tmp”的潜在冲突。我记得有时会从其他一些软件中看到“~example.dat”,这是一个更好的约定吗?

【问题讨论】:

重命名前无需删除旧文件。而且这种方式很常见(写入临时文件,然后将临时文件重命名为原始文件)。 在文件本身中有一些检查数据可以让您验证其完整性。在这种情况下,您可以自动处理“两个文件都存在”的情况——如果检查 tmp 文件表明它没有损坏,则使用它,否则使用 dat 文件。可以在打开文件时使用FILE_FLAG_WRITE_THROUGH标志绕过写缓存。 @JoachimPileborg 但您通常需要先用".bak" 重命名目标。 @cdhowie 够了吗?该文档似乎表明它不是,但没有表明还需要什么。 (Windows 文档似乎表明它相当于 Unix 下的 O_DSYNC,但为了完全完整,您需要 O_DSYNC | O_SYNC。) @JamesKanze This page 可能会有所帮助。 【参考方案1】:

如果磁盘驱动器向操作系统报告数据是 物理上在磁盘上,它不是,那么你就没有多少 可以做的。许多磁盘确实缓存了一定数量的 写入并报告它们已完成,但此类磁盘应该 备用电池,无论如何完成物理写入 (并且在系统崩溃的情况下它们不会丢失数据,因为它们 甚至不会看到它)。

剩下的,你说你已经做了一些研究,所以你毫无疑问 知道您不能为此使用std::ofstream(也不能使用FILE*); 您必须在系统级别进行实际写入,然后打开 具有特殊属性的文件,以确保完整 同步。否则,操作可能会持续存在 操作系统缓冲了一段时间。据我所知, 没有办法确保rename 的这种同步。 (但我不确定是否有必要,如果你总是保留两个 版本:在这种情况下,我通常的约定是写信给 一个文件"example.dat.new",然后当我写完后,删除 任何名为 "example.dat.bak" 的文件,将 "example.dat" 重命名为 "example.dat.bak",然后将"example.dat.new"重命名为 "example.dat"。鉴于此,您应该能够弄清楚 发生了什么或没有发生什么,并找到正确的文件 (如果需要,以交互方式,或插入一个带有 时间戳)。

【讨论】:

可以肯定大多数磁盘缓存并且没有电池。因此,有关“关闭设备上的 Windows 写入缓存缓冲区刷新”的警告返回到某些磁盘驱动器可能实际上是在这样做。 我认为 .bak 的想法有助于在恢复情况下更加清晰。所以我现在真正想要的是“你真的完成了将新的 example.dat 放到磁盘上,而不是在某个内部缓存中吗?”在删除 bak 文件之前。我想等待几分钟对于基本上每个磁盘来说应该是安全的,甚至可能没有操作系统刷新命令。这就为文件系统和数据库类型的事情留下了真正复杂的解决方案(因为在插入/更新/等上重写多 GB 文件可能不是一件实际的事情)。 @WillNewbery 我知道缓存的磁盘 do 有电池,并且 do 保证所有已被磁盘确认的写入都是持久的.如果不是这种情况,您根本无法在专业环境中使用该磁盘。 @WillNewbery 是的,数据物理上在磁盘上。这是由 Posix 保证的,并且适用于我工作过的所有专业系统。 @WillNewbery 当然,您不要在数据集的中间编写;您的写入内容将写入日志文件,如果出现任何问题,这将允许从备份中重建数据文件。【参考方案2】:

您应该在编写其替代文件时锁定实际数据文件,如果有可能不同的进程可能会通过您描述的相同协议。

您可以使用flock 来锁定文件。

至于您的临时文件名,您可以将您的进程 ID 作为其中的一部分,例如“example.dat.3124”,其他同时运行的进程不会生成相同的名称。

【讨论】:

这并没有解决所提出的问题。 事实上,确实如此。 OP 的标题是“覆盖文件而不会有损坏文件的风险”。发帖人提出的协议可以工作,除了可能会同时执行重叠的进程。 其实不然。 OP 很清楚他的问题不是同时写入,而是操作系统在写入过程中崩溃。 获得对某些文件的独占写访问权不是这样的问题。至于使用进程ID来获取文件名,如果它确实崩溃了,我是否打算在加载时搜索“example.dat.*”以查看是否有可能是我的应用程序? @WillNewbery 那是假设并行访问您要保护的数据文件是一个问题(看起来不是,但您会问这个问题)。因为 Linux 托管数据库和大型关键数据库,所以 必须 有一个系统调用来禁用对磁盘的直写缓存:否则操作系统将不适合托管事务性数据库。如果没有某种方法可以保证您的“原映像”实际上在磁盘上是完整的,就没有绝对安全的方法。我正在谷歌搜索答案,但运气不佳......

以上是关于覆盖文件而不会有损坏文件的风险的主要内容,如果未能解决你的问题,请参考以下文章

写入文本文件而不覆盖它

VBA:保存而不覆盖现有文件

Xcode 7.0 和 7.1,代码覆盖打开,单元测试崩溃“无法合并以前的 GCDA 文件:损坏的弧标签”

文件编写器覆盖文件而不是附加到末尾的问题

Linux CentOS 复制文件替换文件 cp 复制文件cp 覆盖文件 cp替换文件

Webpack 和 Angular 的单元测试覆盖映射损坏了吗?