无种族目录漫游(C++)

race-free directory walk (C++)

本文关键字:C++ 漫游      更新时间:2023-10-16

我需要遍历目录树并获取每个文件的stat值。我希望在修改文件系统时安全地执行此操作。

在Python中,最好的选项是os.fwalk,它允许访问要遍历的目录的fd;然后,我可以用dir_fd(fstatat(os.stat并获得当前的stat值。这是在Linux上可以做到的无种族歧视(如果这个目录的内容被修改,我可能不得不重新扫描它(。在C中,有类似实现的nftwfts,后者在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抑制输出并强制findstat所有文件,而不仅仅是DT_DIR候选文件(。