簡體   English   中英

帶有 clang++ 的 std::filesystem::remove_all 中的潛在錯誤

[英]Potential bug in std::filesystem::remove_all with clang++

不要在家嘗試做這個

我對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 上使用 XFS 復制 Fedora 和使用 EXT4 和 XFS 的 Ubuntu 上的行為。 這是std::filesystem::remov_all錯誤還是我做錯了什么?

對於 Fedora,內核版本是: Linux 5.12.12-300.fc34.x86_64 x86_64 with clang version

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

注意:這不是底層和操作系統問題的解決方案,而是在 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; 
}

我嘗試使用這個修改過的程序(刪除 fmt 和 cli11 依賴項)在 Fedora 34 上重現這個:

#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。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM