【问题标题】:Potential bug in std::filesystem::remove_all with clang++使用 clang++ 的 std::filesystem::remove_all 中的潜在错误
【发布时间】:2021-07-01 05:49:38
【问题描述】:

不要在家里尝试这个

std::filesystem::remove_all 有一个奇怪的问题。 我编写了一个程序,将N 文件写入单个目录中的磁盘,然后删除所有文件(这是有充分理由的)。 但是,当我使用 std::filesystem::remove_all 时,会出现如下错误:

filesystem error: cannot remove all: Structure needs cleaning [./tmp_storage] [./tmp_storage/2197772]

并且文件夹没有被删除(显然调用失败)并且调用ls之后显示文件系统“损坏”:

$ ls tmp_storage/
ls: cannot access 'tmp_storage/2197772': Structure needs cleaning
ls: cannot access 'tmp_storage/5493417': Structure needs cleaning
...

我必须修复文件系统。完整的程序如下所示:

#include <fmt/core.h>
#include <CLI/CLI.hpp>

#include <filesystem>
#include <fstream>
#include <string>
#include <exception>

int main(int argc, char** argv)
{
  size_t num_files{64000000};

  CLI::App app("Writes N number of files to dir in file system to check the maximum number of files in a directory");
  app.add_option("-c,--count", num_files, fmt::format("How many files generate [Default: {}]", num_files));
  CLI11_PARSE(app, argc, argv);

  std::string base_path = "./tmp_storage";

  if (!std::filesystem::exists(base_path))
  {
    std::filesystem::create_directory(base_path); 
  }

  size_t i;

  for (i = 1; i <= num_files; ++i)
  {
    std::string file_path = fmt::format("{}/{}", base_path, std::to_string(i));
    std::ofstream out(file_path, std::ios::binary);

    if (out.fail())
    {
      break; 
    }

    try
    {
      out << std::to_string(i); 
    }
    catch(const std::exception& e)
    {
      fmt::print("{}\n", e.what());
    }
  }

  fmt::print("Wrote {} out of {} files\n", i, num_files);

  try
  {
    std::filesystem::remove_all(base_path);
  }
  catch(const std::exception& e)
  {
    fmt::print("{}\n", e.what());
  }
  
  fmt::print("Done\n");
  
  return 0; 
}

使用以下 Makefile 编译:

CC = clang++
CXX_FLAGS = -std=c++17
LINK_FLAGS = -lfmt

all:
    $(CC) $(CXX_FLAGS) main.cpp -o main $(LINK_FLAGS)

我已经能够在 Fedora Server 33/34 和 Ubuntu 上复制行为,Fedora 使用 XFS,Ubuntu 使用 EXT4 和 XFS。 这是std::filesystem::remov_all 中的错误还是我做错了什么?

对于 Fedora,内核版本是:Linux 5.12.12-300.fc34.x86_64 x86_64 带有 clang 版本

clang version 12.0.0 (Fedora 12.0.0-2.fc34)
Target: x86_64-unknown-linux-gnu
Thread model: posix
InstalledDir: /usr/bin

【问题讨论】:

  • 您的两次尝试是在同一台物理机/硬盘驱动器上(因此是双启动还是同一主机上的虚拟机)?
  • 即使 C++ 库有错误,通常用户空间代码不应该能够破坏文件系统!听起来更像是操作系统/文件系统本身的代码问题... 考虑到这个程序的目的似乎正是压力测试,也许可以预见。
  • @Scheff'sCat 完成 :)
  • 绝对是一个操作系统错误,该错误可能由 std:: 文件系统中的错误/异常行为触发,但用户代码不应该能够破坏文件系统
  • 通过一些错误检查和递归到子目录,这几乎就是 remove_all 所做的:github.com/gcc-mirror/gcc/blob/…(我假设您使用的是 libstdc++)

标签: c++ std-filesystem


【解决方案1】:

我尝试使用这个修改后的程序在 Fedora 34 上重现这个(删除 fmt 和 cli11 依赖项):

#include <filesystem>
#include <fstream>
#include <string>
#include <exception>

int main(int argc, char** argv)
{
  size_t num_files{64000000};

  if (argc > 1)
    num_files = std::stol(argv[1]);

  std::string base_path = "./tmp_storage";

  try
  {
    if (!std::filesystem::exists(base_path))
    {
      std::filesystem::create_directory(base_path); 
    }

    size_t i;

    for (i = 1; i <= num_files; ++i)
    {
      auto si = std::to_string(i);
      std::string file_path = base_path + '/' + si;
      std::ofstream out(file_path, std::ios::binary);

      if (out.fail())
        throw std::system_error(errno, std::generic_category(), "ofstream failed: " + file_path);

      try
      {
        out << si;
      }
      catch(const std::exception& e)
      {
        std::puts(e.what());
      }
    }

    std::printf("Wrote %zu out of %zu files\n", i - 1, num_files);

    std::filesystem::remove_all(base_path);
  }
  catch(const std::exception& e)
  {
    std::puts(e.what());
  }
  
  std::puts("Done");
  
  return 0; 
}

我无法重现 F34 中的错误,使用 ext4 或 xfs 或使用默认安装选项 btrfs。我也无法在使用 xfs、clang 13.0.0 和 libstdc++-11.2.1 以及内核 5.14.0 的另一台服务器上重现它。这意味着我无法调试我的 std::filesystem 实现损坏文件系统的位置,也无法向内核团队报告。

我不确定代码是否遇到内核错误,或者您的硬件是否有问题。您是否在文件系统损坏时检查了系统日志所说的内容?内核哪里出错了?

编辑:另外,你的磁盘使用 LVM 吗?我认为我所有的测试都没有使用 LVM。

【讨论】:

    【解决方案2】:

    注意:这不是解决底层和操作系统问题的方法,而是在 C++ 中避免它的方法。

    我们需要对原始代码进行的更改是“最小的”。 对 try 块进行所有更改

     try
      {
        std::filesystem::remove_all(base_path);
      }
      catch(const std::exception& e)
      {
        fmt::print("{}\n", e.what());
      }
    

    并用顺序删除替换:std::filesystem::remove_all(base_path);

    for (auto& path : std::filesystem::directory_iterator(base_path))
    {
        std::filesystem::remove(path);
    }
    

    把原来的代码改成

    #include <fmt/core.h>
    #include <CLI/CLI.hpp>
    
    #include <filesystem>
    #include <fstream>
    #include <string>
    #include <exception>
    
    int main(int argc, char** argv)
    {
        size_t num_files{64000000};
        
        CLI::App app("Writes N number of files to dir in file system to check the maximum number of files in a directory");
        app.add_option("-c,--count", num_files, fmt::format("How many files generate [Default: {}]", num_files));
        CLI11_PARSE(app, argc, argv);
    
        std::string base_path = "./tmp_storage";
    
        if (!std::filesystem::exists(base_path))
        {
            std::filesystem::create_directory(base_path); 
        }
    
        size_t i;
    
        for (i = 1; i <= num_files; ++i)
        {
            std::string file_path = fmt::format("{}/{}", base_path, std::to_string(i));
            std::ofstream out(file_path, std::ios::binary);
    
            if (out.fail())
            {
                break; 
            }
    
            try
            {
                out << std::to_string(i); 
            }
            catch(const std::exception& e)
            {
                fmt::print("{}\n", e.what());
            }
        }
    
        fmt::print("Wrote {} out of {} files\n", i, num_files);
    
        try
        {
            for (auto& path : std::filesystem::directory_iterator(base_path))
            {
                std::filesystem::remove(path); 
            }
        }
        catch(const std::exception& e)
        {
            fmt::print("{}\n", e.what());
        }
      
        fmt::print("Done\n");
      
        return 0; 
    }
    

    【讨论】:

    • 这不是一个正确的答案,很可能你的 RAM/SSD 有问题。
    • @NoSenseEtAl 我可以在多台机器和操作系统上重现它,所以除非 +10 台机器的内存、SSD 和 HDD 有故障,否则我怀疑是这种情况。
    • 您可以提供对另一个答案的作者的远程访问,或者如果他不感兴趣,至少提供这些机器的详细信息。这对我来说仍然很可疑。
    • @NoSenseEtAl 我目前正在对此进行更详细的调查。确定实际发生的故障。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-25
    • 2022-06-14
    • 2015-12-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多