From 02af7e788f3fef0dbba0ba49442054fb451f5bdc Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Thu, 9 Jul 2015 12:25:47 +0200 Subject: Implement directory iteration support --- butl/filesystem | 119 +++++++++++++++++++++++++++++++++++++ butl/filesystem.cxx | 133 ++++++++++++++++++++++++++++++++++++++++-- butl/filesystem.ixx | 65 +++++++++++++++++++++ tests/buildfile | 2 +- tests/dir-iterator/buildfile | 7 +++ tests/dir-iterator/driver.cxx | 55 +++++++++++++++++ 6 files changed, 376 insertions(+), 5 deletions(-) create mode 100644 butl/filesystem.ixx create mode 100644 tests/dir-iterator/buildfile create mode 100644 tests/dir-iterator/driver.cxx diff --git a/butl/filesystem b/butl/filesystem index 18ba48e..4d0b7ef 100644 --- a/butl/filesystem +++ b/butl/filesystem @@ -7,6 +7,14 @@ #include // mode_t +#ifndef _WIN32 +# include // DIR, struct dirent, *dir() +#endif + +#include // ptrdiff_t +#include // move() +#include + #include #include @@ -65,6 +73,117 @@ namespace butl rmfile_status try_rmfile (const path&); + + // Directory entry iteration. + // + enum class entry_type + { + unknown, + regular, + directory, + symlink, + other + }; + + class dir_entry + { + public: + typedef butl::path path_type; + + entry_type + type () const; + + // Symlink target type in case of the symlink, type() otherwise. + // + entry_type + ltype () const; + + // Entry path (excluding the base). To get the full path, do + // base () / path (). + // + const path_type& + path () const {return p_;} + + const dir_path& + base () const {return b_;} + + dir_entry () = default; + dir_entry (entry_type t, path_type p, dir_path b) + : t_ (t), p_ (std::move (p)), b_ (std::move (b)) {} + + private: + entry_type + type (bool link) const; + + private: + friend class dir_iterator; + + mutable entry_type t_ = entry_type::unknown; // Lazy evaluation. + mutable entry_type lt_ = entry_type::unknown; // Lazy evaluation. + path_type p_; + dir_path b_; + }; + + class dir_iterator + { + public: + typedef dir_entry value_type; + typedef const dir_entry* pointer; + typedef const dir_entry& reference; + typedef std::ptrdiff_t difference_type; + typedef std::input_iterator_tag iterator_category; + + ~dir_iterator (); + dir_iterator () = default; + + explicit + dir_iterator (const dir_path&); + + dir_iterator (const dir_iterator&) = delete; + dir_iterator& operator= (const dir_iterator&) = delete; + + dir_iterator (dir_iterator&& x); + dir_iterator& operator= (dir_iterator&&); + + dir_iterator& operator++ () {next (); return *this;} + + reference operator* () const {return e_;} + pointer operator-> () const {return &e_;} + + friend bool operator== (const dir_iterator&, const dir_iterator&); + friend bool operator!= (const dir_iterator&, const dir_iterator&); + + private: + void + next (); + + private: + dir_entry e_; + +#ifndef _WIN32 + DIR* h_ {nullptr}; +#else + // Check move implementation. + // +# error Win32 implementation lacking +#endif + }; + + // Range-based for loop support. + // + // for (const auto& de: dir_iterator (dir_path ("/tmp"))) ... + // + // Note that the "range" (which is the "begin" iterator), is no + // longer usable. In other words, don't do this: + // + // dir_iterator i (...); + // for (...: i) ... + // ++i; // Invalid. + // + inline dir_iterator begin (dir_iterator& i) {return std::move (i);} + inline dir_iterator end (const dir_iterator&) {return dir_iterator ();} } +#include + #endif // BUTL_FILESYSTEM diff --git a/butl/filesystem.cxx b/butl/filesystem.cxx index 7c8893e..4cf60b2 100644 --- a/butl/filesystem.cxx +++ b/butl/filesystem.cxx @@ -8,6 +8,7 @@ #include // stat #include // stat, lstat(), S_IS*, mkdir() +#include // unique_ptr #include using namespace std; @@ -17,25 +18,25 @@ namespace butl // Figuring out whether we have the nanoseconds in some form. // template - constexpr auto nsec (const S* s) -> decltype(s->st_mtim.tv_nsec) + inline constexpr auto nsec (const S* s) -> decltype(s->st_mtim.tv_nsec) { return s->st_mtim.tv_nsec; // POSIX (GNU/Linux, Solaris). } template - constexpr auto nsec (const S* s) -> decltype(s->st_mtimespec.tv_nsec) + inline constexpr auto nsec (const S* s) -> decltype(s->st_mtimespec.tv_nsec) { return s->st_mtimespec.tv_nsec; // MacOS X. } template - constexpr auto nsec (const S* s) -> decltype(s->st_mtime_n) + inline constexpr auto nsec (const S* s) -> decltype(s->st_mtime_n) { return s->st_mtime_n; // AIX 5.2 and later. } template - constexpr int nsec (...) {return 0;} + inline constexpr int nsec (...) {return 0;} timestamp file_mtime (const path& p) @@ -140,4 +141,128 @@ namespace butl return r; } + +#ifndef _WIN32 + + // dir_entry + // + entry_type dir_entry:: + type (bool link) const + { + path_type p (b_ / p_); + struct stat s; + if ((link + ? ::stat (p.string ().c_str (), &s) + : ::lstat (p.string ().c_str (), &s)) != 0) + { + throw system_error (errno, system_category ()); + } + + entry_type r; + + if (S_ISREG (s.st_mode)) + r = entry_type::regular; + else if (S_ISDIR (s.st_mode)) + r = entry_type::directory; + else if (S_ISLNK (s.st_mode)) + r = entry_type::symlink; + else + r = entry_type::other; + + return r; + } + + // dir_iterator + // + struct dir_deleter + { + void operator() (DIR* p) const {if (p != nullptr) ::closedir (p);} + }; + + dir_iterator:: + dir_iterator (const dir_path& d) + { + unique_ptr h (::opendir (d.string ().c_str ())); + h_ = h.get (); + + if (h_ == nullptr) + throw system_error (errno, system_category ()); + + next (); + + if (h_ != nullptr) + e_.b_ = d; + + h.release (); + } + + template + inline /*constexpr*/ entry_type d_type (const D* d, decltype(d->d_type)*) + { + switch (d->d_type) + { +#ifdef DT_DIR + case DT_DIR: return entry_type::directory; +#endif +#ifdef DT_REG + case DT_REG: return entry_type::regular; +#endif +#ifdef DT_LNK + case DT_LNK: return entry_type::symlink; +#endif +#ifdef DT_BLK + case DT_BLK: +#endif +#ifdef DT_CHR + case DT_CHR: +#endif +#ifdef DT_FIFO + case DT_FIFO: +#endif +#ifdef DT_SOCK + case DT_SOCK: +#endif + return entry_type::other; + + default: return entry_type::unknown; + } + } + + template + inline constexpr entry_type d_type (...) {return entry_type::unknown;} + + void dir_iterator:: + next () + { + for (;;) + { + errno = 0; + if (struct dirent* de = ::readdir (h_)) + { + const char* n (de->d_name); + + // Skip '.' and '..'. + // + if (n[0] == '.' && (n[1] == '\0' || (n[1] == '.' && n[2] == '\0'))) + continue; + + e_.p_ = path (n); + e_.t_ = d_type (de, nullptr); + e_.lt_ = entry_type::unknown; + } + else if (errno == 0) + { + // End of stream. + // + ::closedir (h_); + h_ = nullptr; + } + else + throw system_error (errno, system_category ()); + + break; + } + } +#else +#endif } diff --git a/butl/filesystem.ixx b/butl/filesystem.ixx new file mode 100644 index 0000000..7aa3622 --- /dev/null +++ b/butl/filesystem.ixx @@ -0,0 +1,65 @@ +// file : butl/filesystem.ixx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +namespace butl +{ + // dir_entry + // + inline entry_type dir_entry:: + type () const + { + return t_ != entry_type::unknown ? t_ : (t_ = type (false)); + } + + inline entry_type dir_entry:: + ltype () const + { + entry_type t (type ()); + return t != entry_type::symlink + ? t + : lt_ != entry_type::unknown ? lt_ : (lt_ = type (true)); + } + + // dir_iterator + // + inline dir_iterator:: + dir_iterator (dir_iterator&& x): e_ (std::move (x.e_)), h_ (x.h_) + { + x.h_ = nullptr; + } + + inline dir_iterator& dir_iterator:: + operator= (dir_iterator&& x) + { + if (this != &x) + { + e_ = std::move (x.e_); + h_ = x.h_; + x.h_ = nullptr; + } + return *this; + } + + inline bool + operator== (const dir_iterator& x, const dir_iterator& y) + { + return x.h_ == y.h_; + } + + inline bool + operator!= (const dir_iterator& x, const dir_iterator& y) + { + return !(x == y); + } + +#ifndef _WIN32 + inline dir_iterator:: + ~dir_iterator () + { + if (h_ != nullptr) + ::closedir (h_); // Ignore any errors. + } +#else +#endif +} diff --git a/tests/buildfile b/tests/buildfile index ae4bbb3..684221b 100644 --- a/tests/buildfile +++ b/tests/buildfile @@ -2,6 +2,6 @@ # copyright : Copyright (c) 2014-2015 Code Synthesis Ltd # license : MIT; see accompanying LICENSE file -d = path/ prefix-map/ +d = dir-iterator/ path/ prefix-map/ .: $d include $d diff --git a/tests/dir-iterator/buildfile b/tests/dir-iterator/buildfile new file mode 100644 index 0000000..fabea4d --- /dev/null +++ b/tests/dir-iterator/buildfile @@ -0,0 +1,7 @@ +# file : tests/dir-iterator/buildfile +# copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +# license : MIT; see accompanying LICENSE file + +exe{driver}: cxx{driver} ../../butl/lib{butl} + +include ../../butl/ diff --git a/tests/dir-iterator/driver.cxx b/tests/dir-iterator/driver.cxx new file mode 100644 index 0000000..09a259f --- /dev/null +++ b/tests/dir-iterator/driver.cxx @@ -0,0 +1,55 @@ +// file : tests/dir-iterator/driver.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include // size_t +#include +#include + +#include +#include +#include + +using namespace std; +using namespace butl; + +static const char* entry_type_string[] = +{ + "unk", "reg", "dir", "sym", "oth" +}; + +inline ostream& +operator<< (ostream& os, entry_type e) +{ + return os << entry_type_string[static_cast (e)]; +} + +int +main (int argc, const char* argv[]) +{ + if (argc != 2) + { + cerr << "usage: " << argv[0] << " " << endl; + return 1; + } + + try + { + for (const dir_entry& de: dir_iterator (dir_path (argv[1]))) + { + cerr << de.type () << " "; + + if (de.type () == entry_type::symlink) + cerr << de.ltype (); + else + cerr << " "; + + cerr << " " << de.path () << endl; + } + } + catch (const exception& e) + { + cerr << argv[1] << ": " << e.what () << endl; + return 1; + } +} -- cgit v1.1