无种族目录漫游(C++)
race-free directory walk (C++)
我需要遍历目录树并获取每个文件的stat值。我希望在修改文件系统时安全地执行此操作。
在Python中,最好的选项是os.fwalk
,它允许访问要遍历的目录的fd;然后,我可以用dir_fd(fstatat
(os.stat
并获得当前的stat值。这是在Linux上可以做到的无种族歧视(如果这个目录的内容被修改,我可能不得不重新扫描它(。在C中,有类似实现的nftw
和fts
,后者在glibc中使用纯(l(stat,因此是racy(它通过更改目录来减少竞争窗口,这很不方便(。
C++有一个从boost毕业的新文件系统API,它缓存stat
值,但不公开它们(我需要访问st_dev(。这不仅仅是一个头库,所以我不能绕过它。
我是不是错过了一个不错的C++选项,它使用fstatat
,并且不受Boost不公开平台特定调用的理想约束?或者我的最佳选择是包装nftw
(甚至是find
(?
事实证明,它实现起来足够简单。
我使用了dryproject中的libposix。
#include <posix++.h>
class Walker {
public:
void walk(posix::directory dir) {
dir.for_each([this, dir](auto& dirent) {
if (dirent.name == "." or dirent.name == "..")
return;
if (!handle_dirent(dirent))
return;
struct stat stat;
if (dirent.type == DT_DIR || dirent.type == DT_UNKNOWN) {
int fd = openat(
dir.fd(), dirent.name.c_str(), O_DIRECTORY|O_NOFOLLOW|O_NOATIME);
if (fd < 0) {
// ELOOP when O_NOFOLLOW is used on a symlink
if (errno == ENOTDIR || errno == ELOOP)
goto enotdir;
if (errno == ENOENT)
goto enoent;
posix::throw_error(
"openat", "%d, "%s"", dir.fd(), dirent.name);
}
posix::directory dir1(fd);
fstat(fd, &stat);
if (handle_directory(dirent, fd, stat))
walk(dir1);
close(fd);
return;
}
enotdir:
try {
dir.stat(dirent.name.c_str(), stat, AT_SYMLINK_NOFOLLOW);
} catch (const posix::runtime_error &error) {
if (error.number() == ENOENT)
goto enoent;
throw;
}
handle_file(dirent, stat);
return;
enoent:
handle_missing(dirent);
});
}
protected:
/* return value: whether to stat */
virtual bool handle_dirent(const posix::directory::entry&) { return true; }
/* return value: whether to recurse
* stat will refer to a directory, dirent info may be obsolete */
virtual bool handle_directory(
const posix::directory::entry &dirent,
const int fd, const struct stat&) { return true; }
/* stat might refer to a directory in case of a race;
* it still won't be recursed into. dirent may be obsolete. */
virtual void handle_file(
const posix::directory::entry &dirent,
const struct stat&) {}
/* in case of a race */
virtual void handle_missing(
const posix::directory::entry &dirent) {}
};
性能与GNUfind相同(与基类相比,使用-size $RANDOM
抑制输出并强制find
到stat
所有文件,而不仅仅是DT_DIR
候选文件(。