std::filesystem::remove_all 中带有 clang++ 的潜在错误
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 和 Ubuntu 使用 EXT4 和 XFS 复制行为。
这是 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。
不要在家尝试这个
我在使用 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 和 Ubuntu 使用 EXT4 和 XFS 复制行为。
这是 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。