【问题标题】:What could cause an XML file to be filled with null characters?什么可能导致 XML 文件被空字符填充?
【发布时间】:2018-08-21 22:57:14
【问题描述】:

这是一个棘手的问题。我怀疑这需要一些文件系统的高级知识才能回答。

我有一个面向 .NET 框架 4.0 的 WPF 应用程序“App1”。它有一个Settings.settings 文件,该文件生成一个标准的App1.exe.config 文件,其中存储了默认设置。当用户修改设置时,修改进入AppData\Roaming\MyCompany\App1\X.X.0.0\user.config。这都是标准的 .NET 行为。但是,有时我们会发现客户计算机上的 user.config 文件不是应有的文件,这会导致应用程序崩溃。

问题看起来像这样:user.config 的大小与使用 XML 填充时的大小差不多,但不是 XML,它只是一堆 NUL 字符。这是字符 0 一遍又一遍地重复。我们没有关于导致此文件修改的原因的信息。

如果我们删除user.config,我们可以在客户的设备上解决该问题,因为公共语言运行时只会生成一个新的。他们将丢失对设置所做的更改,但可以再次进行更改。

但是,我在另一个 WPF 应用程序“App2”中遇到了这个问题,该应用程序还有另一个 XML 文件 info.xml。这次不同了,因为文件是由我自己的代码生成的,而不是由 CLR 生成的。共同的主题是两者都是 C# WPF 应用程序,都是 XML 文件,并且在这两种情况下,我们都完全无法在测试中重现问题。这可能与 C# 应用程序与 XML 文件或一般文件交互的方式有关吗?

我们不仅无法在当前应用程序中重现问题,而且我什至无法通过编写故意生成错误的自定义代码来重现问题。我找不到导致文件被空值填充的单个 XML 序列化错误或文件访问错误。那么会发生什么?

App1 通过调用Upgrade()Save() 以及获取和设置属性来访问user.config。例如:

if (Settings.Default.UpgradeRequired)
{
    Settings.Default.Upgrade();
    Settings.Default.UpgradeRequired = false;
    Settings.Default.Save();
}

App2 通过序列化和反序列化 XML 来访问info.xml

public Info Deserialize(string xmlFile)
{
    if (File.Exists(xmlFile) == false)
    {
        return null;
    }

    XmlSerializer xmlReadSerializer = new XmlSerializer(typeof(Info));

    Info overview = null;

    using (StreamReader file = new StreamReader(xmlFile))
    {
        overview = (Info)xmlReadSerializer.Deserialize(file);
        file.Close();
    }

    return overview;
}

public void Serialize(Info infoObject, string fileName)
{
    XmlSerializer writer = new XmlSerializer(typeof(Info));

    using (StreamWriter fileWrite = new StreamWriter(fileName))
    {
        writer.Serialize(fileWrite, infoObject);
        fileWrite.Close();
    }
}

我们在 Windows 7 和 Windows 10 上都遇到过这个问题。在研究这个问题时,我发现了这篇文章,在 Windows 8.1 中遇到了同样的 XML 问题:Saved files sometime only contains NUL-characters

我可以在我的代码中进行更改以防止这种情况发生吗,或者问题是否存在于 .NET 的行为中?

在我看来有三种可能:

  1. CLR 正在将空字符写入 XML 文件。
  2. 文件的内存地址指针在不移动文件内容的情况下切换到另一个位置。
  3. 文件系统尝试将文件移动到另一个内存地址并且文件内容被移动但指针没有得到更新。

我觉得 2 和 3 比 1 更有可能。这就是为什么我说这可能需要高级文件系统知识。

如果有任何信息可以帮助我重现、修复或解决问题,我将不胜感激。谢谢!

【问题讨论】:

  • 在写入该文件时可能出现断电(例如当您强行关闭计算机时)?在这种情况下,我认为可能会出现像您这样的情况。
  • 我会将 using 语句替换为 Try/Catch 并将结果保存到日志文件中。 using 语句隐藏了异常,因此您不知道发生了异常,并且代码将继续执行,就像没有出错一样。
  • @jdweng 虽然我当然应该尝试使用 try/catch 收集诊断数据,但我不相信 using 语句会抑制异常。我可以在 using 块内生成异常就好了。
  • 虽然 CLR 中可能有一些可怕的 bug 导致了这个问题,但所涉及的逻辑非常简单,而且影响非常大,以至于你希望这样的 bug 被发现并且现在已经修复(尽管这当然不是硬性保证)。我的钱用于文件系统过滤器驱动程序损坏导致的文件损坏。询问您的客户安装了哪种防病毒/反恶意软件。另外,询问他们是否使用了真正的漫游配置文件,即上传到网络并跨机器传输的那种——这显然是另一个失败点。
  • 我也有类似的问题,我已经把问题贴在这里stackoverflow.com/questions/49269579/…希望我们能得到解决方案

标签: c# xml filesystems ntfs null-character


【解决方案1】:

这种行为没有记录在案的原因,因为这种情况发生在用户身上,但没有人能说出这种奇怪情况的起源。

这可能是 CLR 问题,尽管这不太可能发生,但如果没有为节点定义 xsi:nil,CLR 不仅会写入空字符,而且 XML 文档不能包含空字符。

无论如何,解决此问题的唯一记录方法是使用以下代码行删除损坏的文件:

try
{
     ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal);
}
catch (ConfigurationErrorsException ex)
{
    string filename = ex.Filename;
    _logger.Error(ex, "Cannot open config file");

    if (File.Exists(filename) == true)
    {
        _logger.Error("Config file {0} content:\n{1}", filename, File.ReadAllText(filename));
        File.Delete(filename);
        _logger.Error("Config file deleted");
        Properties.Settings.Default.Upgrade();
        // Properties.Settings.Default.Reload();
        // you could optionally restart the app instead
    }
    else
    {
        _logger.Error("Config file {0} does not exist", filename);
    }
}

它将使用 Properties.Settings.Default.Upgrade(); 恢复 user.config 再次没有空值。

【讨论】:

  • 我使用相同的方法,但我每隔 x 小时保存一次配置备份,然后在遇到损坏的 xml 文件时使用它。
  • 我想我会将您的答案标记为正确并给 Julo 赏金,因为他似乎能够重现该问题。而且他的名声也比较低。 :)
  • 那么这是同意的:)
【解决方案2】:

我遇到了类似的问题,我能够将问题追溯到损坏的 HDD。

我的问题描述(所有相关信息)

  • 连接到主板 (SATA) 的磁盘:

    • SSD (系统),

    • 3 * 硬盘。

      其中一个硬盘有坏块,甚至在读取磁盘结构时出现问题(目录和文件列表)

  • 操作系统:Windows 7 x64

  • 文件系统(在所有磁盘上):NTFS

当系统尝试读取或写入损坏的磁盘(用户请求或自动扫描或任何其他原因)并且尝试失败时,所有写入操作(对其他磁盘的) 不正确。在系统磁盘上创建的文件(主要是其他应用程序的配置文件)已写入并且在直接检查文件内容时是有效的(可能是因为这些文件已在 RAM 中兑现)

不幸的是,重启后,所有文件(在损坏的驱动器上写入/读取访问失败后写入)具有正确的大小,但文件的内容是“零字节”(和你的情况完全一样).

尝试排除硬件相关问题。您可以尝试检查“复制”文件(更改后)到另一台机器(上传到 web/ftp)。或者尝试将特定内容保存到固定文件。当不同的检查文件正确时,或者当固定内容文件为“空”时,原因可能是在本地计算机上。尝试更换硬件组件,或重装系统。

【讨论】:

  • “重装系统”是指操作系统吗?
  • 问题是,他们不能告诉每个遇到问题的客户“更换硬件组件或重新安装系统”。如果我是一个被告知这样事情的客户,我会生气的。这就是为什么最好删除而不是告诉客户他们需要修复损坏的计算机...
  • Kyle Delaney:是的,系统重装;在我的情况下,这是必要的(当损坏的磁盘仍然是 Windows XP 的不同计算机中的系统磁盘时)。但是驱动引起的问题是不同的。 @BarrJ:我知道,但是当存在无法掩盖的真正硬件问题时(除非在不同计算机上的 SQL 上使用某种外部存储,例如 FTP),这将仍然是唯一的选择。然后将仅针对单个应用程序屏蔽该问题。当然,只有当问题是由HW引起的(可以检查,看我的帖子)
  • 仍然,试图向客户解释,他们需要重新安装整个系统,或者更糟糕的是,需要为他们的计算机购买新的硬件,只是因为您的程序生成的文件没有空闲。如果 OP 说删除文件有帮助,最好删除它并调用升级,这将恢复最近更新的文件。
  • 当问题出在损坏的硬件(或操作系统)中时,只有两个选项。掩盖单个应用程序的问题(例如,使用 web/FTP/SMB),或接受在本地存储数据的每个应用程序的数据丢失(设置)。当客户接受这种数据丢失没有问题时,没有理由重新安装系统或购买新的硬件。使用任何可用的方法简单地掩盖错误。
【解决方案3】:

我遇到了类似的问题,但它是在服务器上。服务器在程序写入文件时重新启动,导致文件包含所有空字符,并且对程序写入/读取文件变得不可用。

所以文件看起来像这样:

日志显示服务器重新启动:

损坏的文件显示上次更新是在重启时:

【讨论】:

  • 感谢您提供 get-eventLog 示例
【解决方案4】:

众所周知,如果断电,就会发生这种情况。这发生在扩展文件(它可以是新文件或现有文件)的缓存写入之后,并且此后不久发生断电。在这种情况下,当机器重新启动时,文件有 3 种预期的可能状态:

1) 文件根本不存在或具有其原始长度,就好像写入从未发生过一样。

2) 文件的长度与写入发生时一样,但数据为零。

3) 文件具有预期的长度和写入的正确数据。

状态 2 就是您所描述的。发生这种情况是因为当您执行缓存写入时,NTFS 最初只是相应地扩展了文件大小,但保持 VDL(有效数据长度)不变。超出 VDL 的数据总是读回为零。您打算写入的数据位于文件缓存的内存中。它最终会被写入磁盘,通常在几秒钟内,然后 VDL 将在磁盘上进行高级以反映写入的数据。如果在数据写入之前或 VDL 增加之前发生断电,您将最终处于状态 2。

这很容易复制,例如复制文件(复制引擎使用缓存写入),然后立即拔掉计算机上的电源插头。

【讨论】:

  • 感谢您:我很感兴趣。请问您可以链接到此信息的任何来源吗? (显然,此信息与 Beastwood 的回答和 dsdel 的评论相对应。)
  • 优秀的答案。感谢您对文件系统的了解。
  • 对不起,我不知道我可以链接到什么来源来描述这些交互。我是 Microsoft 的一名 NTFS 开发人员,所以我只是根据第一手知识来描述它是如何工作的。
  • 计划重启也会发生这种情况,例如对于 Windows 更新?查看我的调查:superuser.com/a/1402396/13089
  • 这可能就是我们在生产中看到类似问题的原因。以及为什么新创建的备份文件和新创建的文件都会发生这种情况。
【解决方案5】:

我有同样的问题,序列化的 xml 文件末尾有一个额外的“NUL”字符:

我正在使用这样的 XMLWriter:

using (var stringWriter = new Utf8StringWriter())
        {
            using (var xmlWriter = XmlWriter.Create(stringWriter, new XmlWriterSettings { Indent = true, IndentChars = "\t", NewLineChars = "\r\n", NewLineHandling = NewLineHandling.Replace }))
            {                    
                xmlSerializer.Serialize(xmlWriter, data, nameSpaces);
                xml =  stringWriter.ToString();
                var xmlDocument = new XmlDocument();
                xmlDocument.LoadXml(xml);
                if (removeEmptyNodes)
                {
                    RemoveEmptyNodes(xmlDocument);
                }
                xml = xmlDocument.InnerXml;
            }
        }

【讨论】:

  • 我认为在 using 块结束之前避免尝试使用 writer 的结果是明智的。从 sn-p 中也不清楚屏幕截图中的文件是哪一个;但如果该文件是从 xml = xmlDocument.InnerXml; 创建的,那么我认为您错过了 XmlWriter 的重点。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多