C++复制目录树同步修改时间:Windows权限及文件已存在问题求助
问题描述
编写的C目录树复制程序可同步最后修改时间,在Linux Ubuntu 22.04 LTS(g/gcc 11.4.0)下正常运行,但在Windows 11(g++/gcc 13.1.0)编译成功后运行失败,出现两类报错:
无法设置文件时间:权限拒绝无法复制文件:文件已存在
即使以管理员权限运行CMD/PowerShell,问题仍未解决。
环境
- Linux Ubuntu
22.04LTS, g++/gcc11.4.0 - Windows 11, g++/gcc
13.1.0
编译命令
- Linux:
g++ -std=c++20 main.cpp -o main - Windows:
g++ -std=c++20 main.cpp -o main.exe
Windows错误信息
terminate called after throwing an instance of 'std::filesystem::__cxx11::filesystem_error'
what(): filesystem error: cannot set file time: Permission denied [D:\to\working\path\output\test_folder]
terminate called after throwing an instance of 'std::filesystem::__cxx11::filesystem_error'
what(): filesystem error: cannot copy file: File exists [D:\to\working\path\src\test_folder\a1.txt] [D:\to\working\path\output\test_folder\a1.txt]
源代码
#include <iostream> #include <chrono> #include <filesystem> using namespace std; namespace fs = filesystem; void copy_directory(const fs::path& src, const fs::path& dst) { // directory create if (!fs::exists(dst)) fs::create_directories(dst); // directory walk for (const auto& entry : fs::directory_iterator(src)) { const auto& entry_path = entry.path(); const auto& file_name = entry_path.filename(); const auto& dst_file_path = dst / file_name; if (fs::is_directory(entry_path)) { copy_directory(entry_path, dst_file_path); } else if (fs::is_regular_file(entry_path)) { // files update time to last modification auto modify_time = fs::last_write_time(entry_path); fs::copy_file(entry_path, dst_file_path, fs::copy_options::overwrite_existing); fs::last_write_time(dst_file_path, modify_time); } } // folder update time to last modification auto modify_time = fs::last_write_time(src); fs::last_write_time(dst, modify_time); } int main(int argc, char *argv[]) { auto start = chrono::high_resolution_clock::now(); auto src = argv[1]; auto dst = fs::current_path() / "output"; cout << "current_path : " << fs::current_path() << endl; cout << "src : " << src << endl; cout << "dst : " << dst << endl; copy_directory(src, dst); auto end = chrono::high_resolution_clock::now(); auto duration = chrono::duration_cast<chrono::microseconds>(end - start); cout << "Time spent: " << duration.count() / 1000000.0 << " seconds" << endl; return 0; }
1. 解决"无法复制文件:文件已存在"问题
Linux下fs::copy_options::overwrite_existing可直接覆盖目标文件,但Windows下若目标文件被进程占用(如资源管理器预览、其他程序打开),即使添加该选项也会报错。此外原代码未处理特殊文件/符号链接,也可能引发失败。
修改方式:
方式一:先删除再复制
else if (fs::is_regular_file(entry_path)) { auto modify_time = fs::last_write_time(entry_path); // 先删除已存在的目标文件 if (fs::exists(dst_file_path)) { fs::remove(dst_file_path); } fs::copy_file(entry_path, dst_file_path); fs::last_write_time(dst_file_path, modify_time); }
方式二:扩展复制选项
添加更完整的复制规则,覆盖更多场景:
fs::copy_file(entry_path, dst_file_path, fs::copy_options::overwrite_existing | fs::copy_options::copy_symlinks);
2. 解决"无法设置文件时间:权限拒绝"问题
Windows下设置目录修改时间存在两个核心问题:目录可能被系统/其他进程临时锁定;GCC的std::filesystem对Windows目录时间的处理存在兼容性瑕疵。
修复方案:
方案A:重试机制规避临时锁定
添加重试逻辑,避免因临时锁定导致的失败:
// 需要添加<thread>头文件 #include <thread> // ... 原函数代码 ... // folder update time to last modification auto modify_time = fs::last_write_time(src); bool success = false; int retry = 3; while (retry-- > 0 && !success) { try { fs::last_write_time(dst, modify_time); success = true; } catch (const fs::filesystem_error& e) { this_thread::sleep_for(chrono::milliseconds(100)); } } if (!success) { cerr << "Failed to set directory time after retries: " << dst << endl; }
方案B:调用Windows原生API设置时间
若std::filesystem的接口存在兼容性问题,直接使用Windows APISetFileTime:
#include <windows.h> void set_directory_last_write_time(const fs::path& dir_path, const fs::file_time_type& modify_time) { // 转换时间格式 auto duration = modify_time.time_since_epoch(); auto filetime_val = ULARGE_INTEGER{static_cast<ULONGLONG>(chrono::duration_cast<chrono::microseconds>(duration).count())}; FILETIME ft{}; ft.dwLowDateTime = filetime_val.LowPart; ft.dwHighDateTime = filetime_val.HighPart; // 打开目录(需FILE_FLAG_BACKUP_SEMANTICS标记) HANDLE hDir = CreateFileW(dir_path.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); if (hDir != INVALID_HANDLE_VALUE) { SetFileTime(hDir, nullptr, nullptr, &ft); CloseHandle(hDir); } else { cerr << "Failed to open directory: " << dir_path << endl; } }
替换原代码中的fs::last_write_time(dst, modify_time);为:
set_directory_last_write_time(dst, modify_time);
注意:Windows编译时需链接kernel32.lib,编译命令改为:g++ -std=c++20 main.cpp -o main.exe -lkernel32
3. 额外鲁棒性优化
- 添加异常捕获:在
copy_directory函数中为单个文件/目录操作添加try-catch,避免单个错误导致整个程序终止。 - 跳过符号链接:Windows下创建符号链接需管理员权限,若无需复制可直接跳过:
if (fs::is_symlink(entry_path)) { continue; }
内容的提问来源于stack exchange,提问作者Dr.Dan

