Потенциальная ошибка в std::файловая система::удалить все с помощью clang

#c #std-filesystem

Вопрос:

НЕ ПЫТАЙТЕСЬ ДЕЛАТЬ ЭТО ДОМА

У меня странная проблема с 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::exceptionamp; e)
    {
      fmt::print("{}n", e.what());
    }
  }

  fmt::print("Wrote {} out of {} filesn", i, num_files);

  try
  {
    std::filesystem::remove_all(base_path);
  }
  catch(const std::exceptionamp; e)
  {
    fmt::print("{}n", e.what());
  }
  
  fmt::print("Donen");
  
  return 0; 
}
 

Скомпилирован со следующим файлом Makefile:

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

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

 

Я смог воспроизвести поведение на сервере Fedora 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
 

Комментарии:

1. Были ли обе ваши попытки на одной и той же физической машине/жестком диске (так что либо двойная загрузка, либо виртуальные машины на одном хосте)?

2. Даже если в библиотеке C есть ошибка, обычно код пользовательского пространства не должен быть способен повредить файловые системы! Звучит больше похоже на проблему в коде самой операционной системы/файловой системы… возможно, предсказуемую, учитывая, что цель этой программы, похоже, заключается именно в стресс-тестировании.

3. @Scheff’Scat готово 🙂

4. Определенно ошибка операционной системы, эта ошибка может быть вызвана ошибкой/необычным поведением в файловой системе std::, но пользовательский код не должен быть в состоянии повредить файловую систему

5. С некоторой проверкой ошибок и рекурсией в подкаталоги это в значительной степени то, что remove_all происходит в любом случае: github.com/gcc-mirror/gcc/blob/… (Я предполагаю, что вы используете libstdc )

Ответ №1:

ПРИМЕЧАНИЕ: Это не решение базовых проблем и проблем операционной системы, а способ избежать их в C .

Изменение, которое нам нужно внести в исходный код, является «минимальным». Все изменения вносятся в блок try

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

и замените: std::filesystem::remove_all(base_path); последовательным удалением.

 for (autoamp; 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::exceptionamp; e)
        {
            fmt::print("{}n", e.what());
        }
    }

    fmt::print("Wrote {} out of {} filesn", i, num_files);

    try
    {
        for (autoamp; path : std::filesystem::directory_iterator(base_path))
        {
            std::filesystem::remove(path); 
        }
    }
    catch(const std::exceptionamp; e)
    {
        fmt::print("{}n", e.what());
    }
  
    fmt::print("Donen");
  
    return 0; 
}
 

Ответ №2:

Я попытался воспроизвести это в 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::exceptionamp; e)
      {
        std::puts(e.what());
      }
    }

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

    std::filesystem::remove_all(base_path);
  }
  catch(const std::exceptionamp; 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.