2015-09-28 3 views
4

Мне нужно пройти дерево каталогов и получить значения stat для каждого файла. Я хочу сделать это безопасно, пока файловая система будет изменена.Прогулка по каталогу без гонок (C++)

В Python лучшим вариантом является os.fwalk, который дает доступ к fd для проходящего каталога; Затем я смогу os.stat с dir_fd (fstatat) и получить текущие значения stat. Это как без гонок, так как это может быть сделано в Linux (если содержимое этого каталога изменяется, возможно, мне придется его повторно просмотреть). В C есть nftw, который реализован аналогично, и fts, который в glibc использует простой (l) stat и поэтому является racy (он уменьшает окно гонки, меняя каталоги, что неудобно).

C++ имеет новый filesystem APIgraduated from boost, который кэширует stat значения, но doesn't expose them (и мне нужен доступ к st_dev). Это не просто библиотека заголовков, поэтому я не могу обойти это.

Не хватает ли у меня подходящей опции C++, которая использует fstatat и не связана идеей Boost о том, что вы не разоблачаете вызовы на платформе? Или мой лучший вариант обернуть nftw (или даже find)?

ответ

1

Получается, что это было достаточно просто, чтобы реализовать.

Я использовал libposix от dryproject.

#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) {} 
}; 

Performance идентичен GNU найти (при сравнении с базовым классом, используя -size $RANDOM для подавления вывода и сил find к stat всем файлам, а не только DT_DIR кандидатов).

Смежные вопросы