覆盖保存文件的非常安全的方法

Adequetely safe method of overwriting a save file?

本文关键字:方法 安全 非常 覆盖 保存文件      更新时间:2023-10-16

使用cstdio,覆盖文件的最安全方法是什么?'"安全"在这种情况下意味着文件不可能变得不完整或损坏;该文件将被完全覆盖,或者如果出现问题,它将是旧文件。

我想最好的方法是创建一个临时中间文件,然后在该中间文件完成后覆盖旧文件。如果这真的是最好的方法,那么还有一些其他问题似乎是可能的,尽管很罕见。

  • 如果程序在重写时退出,我怎么知道该使用其他文件
  • 如果程序在创建过程中退出,我怎么知道不使用其他文件
  • 我怎么知道原始文件或中间体处于未定义状态(因为它可能在某种程度上失败它仍然可读,但它包含的数据是微妙的错误)

我想有一个很好的练习,但我没能找到。这是为保存的游戏数据;只有一个文件,而且每次都会覆盖整个文件,不需要担心部分覆盖或追加。

正如其他人所说,保留现有文件,并写入新文件。如果这是非常重要的(即,用户不可能恢复信息),请确保存在";备份";文件(例如,如果您的程序保存了abc.config,则保留abc.old.configabc.backup[如果您希望确保名称在任何地方都有效,则.cfg.bak可能是更好的选择])。

编写文件时,请在文件中放置某种结束标记,这样可以确保文件是完整的。如果你想避免";用户编辑";对于该文件,您可能还需要内容的校验和(sha1、md5或类似)。如果结束标记不存在,或者校验和错误,那么您就知道该文件是";坏";,所以不要用它来备份。

  1. 将新内容写入临时文件(例如fstream fout("abc.tmp");
  2. 删除备份文件(如果存在)(例如remove("abc.bak");
  3. 将现在旧的文件重命名为备份名称(例如rename("abc.cfg", "abc.bak");
  4. 将新文件重命名为旧文件(例如rename("abc.tmp", "abc.cfg"

对于所有步骤(尤其是写入实际数据),请检查是否存在错误。您需要决定哪里可以出现错误,哪里不可以(例如,不存在的文件的remove可以,但如果rename不起作用,您可能应该停止,否则可能会出现不好的结果)。

加载文件时,请检查所有步骤,如果出现错误,请返回备份文件。

您应该使用一个数据库管理系统来保证ACID。如果您坚持使用平面文件,则应该写入临时文件,在写入完成时复制并替换实际文件,并且只有在复制成功时才删除临时文件。此外,在每次写入文件时调用flush()

这是一个简单而有限的答案,它的实现提供了更多的清晰度。

它与Mats Petersson的答案相似,但只使用了两个文件,因为rename()损坏文件的可能性似乎低于随着代码变得越来越复杂,我实现了一些错误。(应该是更改inode或主文件表条目与否触摸文件内容。如果您的应用程序可能部署在FAT文件系统上。)

步骤大致相同:

  • 将临时文件写入与目标文件相同的目录中。如果此步骤失败,则中止
  • 删除目标文件
  • 将临时文件重命名为目标文件

下面是一个将char*写入文件的实现。我希望你有一个围绕文件函数的unicode包装器,所以一定要使用它!

// Replace unicode:: with your unicode-aware stdio functions.
#include <stdio.h>
static const char* k_TempExtension = ".tmp";
bool Storage::SaveFile_Safe(std::string& fullpath, const char* data, unsigned int len) {
    std::string tempfile = fullpath + k_TempExtension;
    bool success = false;
    FILE* f = unicode::fopen(tempfile.c_str(), "wb");
    if (f) {
        const size_t count = 1;
        size_t written = fwrite(data, len, count, f);
        if (len == 0 && written == 0) {
            // Close enough to writing everything.
            written = count;
        }
        if (fclose(f) != 0) {
            written = 0;
        }
        if (written == count) {
            const char* tempfile_c = tempfile.c_str();
            const char* fullpath_c = fullpath.c_str();
            unicode::remove(fullpath_c);
            // If we fail between these two function calls, we still have our
            // temp file that we should attempt to load.
            success = 0 == unicode::rename(tempfile_c, fullpath_c);
        }
    }
    return success;
}
const char* Storage::LoadFile_Safe(std::string& fullpath) {
    FILE* f = unicode::fopen(fullpath.c_str(), "rb");
    if (f == 0) {
        // If we failed during save, load the temp file.
        std::string tempfile = fullpath + k_TempExtension;
        f = unicode::fopen(tempfile.c_str(), "rb");
    }
    if (f == 0) {
        return false;
    }
    // ... do loading code here
}

在将数据保存到文件中时,应防止关闭应用程序。你应该做的是加载旧文件,将其保存在一个变量中——覆盖变量的数据(在你的应用程序中),并将其写在旧文件上。所有操作都需要不到1秒的时间,因此您不必担心在保存时关闭应用程序。此外,只有在检查操作是否可行以及数据的完整性是否正确后,才能覆盖。