// Copyright (C) 2003 Davis E. King (davis@dlib.net) // License: Boost Software License See LICENSE.txt for the full license. #ifndef DLIB_DIR_NAV_KERNEl_2_ #define DLIB_DIR_NAV_KERNEl_2_ #ifdef DLIB_ISO_CPP_ONLY #error "DLIB_ISO_CPP_ONLY is defined so you can't use this OS dependent code. Turn DLIB_ISO_CPP_ONLY off if you want to use it." #endif #include "dir_nav_kernel_abstract.h" #include <string> #include "../uintn.h" #include "../algs.h" #include <sys/types.h> #include <dirent.h> #include <libgen.h> #include <limits.h> #include <unistd.h> #include <sys/stat.h> #include <errno.h> #include <stdlib.h> #include <chrono> #if !defined(__USE_LARGEFILE64 ) && !defined(_LARGEFILE64_SOURCE) #define stat64 stat #endif #include <vector> #include "../stl_checked.h" #include "../enable_if.h" #include "../queue.h" namespace dlib { // ---------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------- // file object // ---------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------- class file { /*! INITIAL VALUES state.name == name() state.full_name == full_name() state.file_size == size() state.last_modified == last_modified() CONVENTION state.name == name() state.full_name == full_name() state.file_size == size() state.last_modified == last_modified() !*/ friend class directory; struct data { uint64 file_size; std::string name; std::string full_name; std::chrono::time_point<std::chrono::system_clock> last_modified; }; void init(const std::string& name); public: struct private_constructor{}; inline file ( const std::string& name, const std::string& full_name, const uint64 file_size, const std::chrono::time_point<std::chrono::system_clock>& last_modified, private_constructor ) { state.file_size = file_size; state.name = name; state.full_name = full_name; state.last_modified = last_modified; } class file_not_found : public error { public: file_not_found(const std::string& s): error(s){} }; inline file ( ) { state.file_size = 0; } file ( const std::string& name ) { init(name); } file ( const char* name ) { init(name); } inline const std::string& name ( ) const { return state.name; } inline const std::string& full_name ( ) const { return state.full_name; } inline uint64 size ( ) const { return state.file_size; } inline std::chrono::time_point<std::chrono::system_clock> last_modified ( ) const { return state.last_modified; } operator std::string ( ) const { return full_name(); } bool operator == ( const file& rhs ) const; bool operator != ( const file& rhs ) const { return !(*this == rhs); } inline bool operator < ( const file& item ) const { return full_name() < item.full_name(); } inline void swap ( file& item ) { exchange(state,item.state); } private: // member data data state; }; // ---------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------- // directory object // ---------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------- class directory { /*! INITIAL VALUES state.name == name() state.full_name == full_name() CONVENTION state.name == name() state.full_name == full_name() is_root() == state.name.size() == 0 !*/ void init(const std::string& name); public: struct private_constructor{}; inline directory ( const std::string& name, const std::string& full_name, private_constructor ) { state.name = name; state.full_name = full_name; } struct data { std::string name; std::string full_name; }; class dir_not_found : public error { public: dir_not_found(const std::string& s):error(s){} }; class listing_error : public error { public: listing_error(const std::string& s):error(s){} }; inline directory ( ) { } directory ( const std::string& name ) { init(name); } directory ( const char* name ) { init(name); } static char get_separator ( ); template < typename queue_of_files > void get_files ( queue_of_files& files ) const; template < typename queue_of_dirs > void get_dirs ( queue_of_dirs& dirs ) const; std::vector<file> get_files ( ) const { std::vector<file> temp_vector; get_files(temp_vector); return temp_vector; } std::vector<directory> get_dirs ( ) const { std::vector<directory> temp_vector; get_dirs(temp_vector); return temp_vector; } const directory get_parent ( ) const; inline bool is_root ( ) const { return state.name.size() == 0; } inline const std::string& name ( ) const { return state.name; } inline const std::string& full_name ( ) const { return state.full_name; } operator std::string ( ) const { return full_name(); } bool operator == ( const directory& rhs ) const; bool operator != ( const directory& rhs ) const { return !(*this == rhs); } inline bool operator < ( const directory& item ) const { return full_name() < item.full_name(); } inline void swap ( directory& item ) { exchange(state,item.state); } private: // member data data state; bool is_root_path ( const std::string& path ) const; /*! ensures - returns true if path is a root path. Note that this function considers root paths that don't have a trailing separator to also be valid. !*/ }; // ---------------------------------------------------------------------------------------- inline std::ostream& operator<< ( std::ostream& out, const directory& item ) { out << (std::string)item; return out; } inline std::ostream& operator<< ( std::ostream& out, const file& item ) { out << (std::string)item; return out; } // ---------------------------------------------------------------------------------------- inline void swap ( file& a, file& b ) { a.swap(b); } // ---------------------------------------------------------------------------------------- inline void swap ( directory& a, directory& b ) { a.swap(b); } // ---------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------- // templated member function definitions // ---------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------- template < typename queue_of_files > typename disable_if<is_std_vector<queue_of_files>,void>::type directory_helper_get_files ( const directory::data& state, queue_of_files& files ) { using namespace std; files.clear(); if (state.full_name.size() == 0) throw directory::listing_error("This directory object currently doesn't represent any directory."); DIR* ffind = 0; struct dirent* data; struct stat64 buffer; try { string path = state.full_name; // ensure that the path ends with a separator if (path[path.size()-1] != directory::get_separator()) path += directory::get_separator(); // get a handle to something we can search with ffind = opendir(state.full_name.c_str()); if (ffind == 0) { throw directory::listing_error("Unable to list the contents of " + state.full_name); } while(true) { errno = 0; if ( (data = readdir(ffind)) == 0) { // there was an error or no more files if ( errno == 0) { // there are no more files break; } else { // there was an error throw directory::listing_error("Unable to list the contents of " + state.full_name); } } uint64 file_size; // get a stat64 structure so we can see if this is a file if (::stat64((path+data->d_name).c_str(), &buffer) != 0) { // this might be a broken symbolic link. We can check by calling // readlink and seeing if it finds anything. char buf[PATH_MAX]; ssize_t temp = readlink((path+data->d_name).c_str(),buf,sizeof(buf)); if (temp == -1) throw directory::listing_error("Unable to list the contents of " + state.full_name); else file_size = static_cast<uint64>(temp); } else { file_size = static_cast<uint64>(buffer.st_size); } auto last_modified = std::chrono::system_clock::from_time_t(buffer.st_mtime); #ifdef _BSD_SOURCE last_modified += std::chrono::nanoseconds(buffer.st_atim.tv_nsec); #endif if (S_ISDIR(buffer.st_mode) == 0) { // this is actually a file file temp( data->d_name, path+data->d_name, file_size, last_modified, file::private_constructor() ); files.enqueue(temp); } } // while (true) if (ffind != 0) { while (closedir(ffind)) { if (errno != EINTR) break; } ffind = 0; } } catch (...) { if (ffind != 0) { while (closedir(ffind)) { if (errno != EINTR) break; } ffind = 0; } files.clear(); throw; } } // ---------------------------------------------------------------------------------------- template < typename queue_of_files > typename enable_if<is_std_vector<queue_of_files>,void>::type directory_helper_get_files ( const directory::data& state, queue_of_files& files ) { queue<file>::kernel_2a temp_files; directory_helper_get_files(state,temp_files); files.clear(); // copy the queue of files into the vector temp_files.reset(); while (temp_files.move_next()) { files.push_back(temp_files.element()); } } // ---------------------------------------------------------------------------------------- template < typename queue_of_files > void directory:: get_files ( queue_of_files& files ) const { // the reason for this indirection here is because it avoids a bug in // the cygwin version of gcc directory_helper_get_files(state,files); } // ---------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------- template < typename queue_of_dirs > typename disable_if<is_std_vector<queue_of_dirs>,void>::type directory_helper_get_dirs ( const directory::data& state, queue_of_dirs& dirs ) { using namespace std; dirs.clear(); if (state.full_name.size() == 0) throw directory::listing_error("This directory object currently doesn't represent any directory."); DIR* ffind = 0; struct dirent* data; struct stat64 buffer; try { string path = state.full_name; // ensure that the path ends with a separator if (path[path.size()-1] != directory::get_separator()) path += directory::get_separator(); // get a handle to something we can search with ffind = opendir(state.full_name.c_str()); if (ffind == 0) { throw directory::listing_error("Unable to list the contents of " + state.full_name); } while(true) { errno = 0; if ( (data = readdir(ffind)) == 0) { // there was an error or no more files if ( errno == 0) { // there are no more files break; } else { // there was an error throw directory::listing_error("Unable to list the contents of " + state.full_name); } } // get a stat64 structure so we can see if this is a file if (::stat64((path+data->d_name).c_str(), &buffer) != 0) { // just assume this isn't a directory. It is probably a broken // symbolic link. continue; } string dtemp(data->d_name); if (S_ISDIR(buffer.st_mode) && dtemp != "." && dtemp != ".." ) { // this is a directory so add it to dirs directory temp(dtemp,path+dtemp, directory::private_constructor()); dirs.enqueue(temp); } } // while (true) if (ffind != 0) { while (closedir(ffind)) { if (errno != EINTR) break; } ffind = 0; } } catch (...) { if (ffind != 0) { while (closedir(ffind)) { if (errno != EINTR) break; } ffind = 0; } dirs.clear(); throw; } } // ---------------------------------------------------------------------------------------- template < typename queue_of_dirs > typename enable_if<is_std_vector<queue_of_dirs>,void>::type directory_helper_get_dirs ( const directory::data& state, queue_of_dirs& dirs ) { queue<directory>::kernel_2a temp_dirs; directory_helper_get_dirs(state,temp_dirs); dirs.clear(); // copy the queue of dirs into the vector temp_dirs.reset(); while (temp_dirs.move_next()) { dirs.push_back(temp_dirs.element()); } } // ---------------------------------------------------------------------------------------- template < typename queue_of_dirs > void directory:: get_dirs ( queue_of_dirs& dirs ) const { // the reason for this indirection here is because it avoids a bug in // the cygwin version of gcc directory_helper_get_dirs(state,dirs); } // ---------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------- template < typename queue_of_dir > typename disable_if<is_std_vector<queue_of_dir>,void>::type get_filesystem_roots ( queue_of_dir& roots ) { roots.clear(); directory dir("/"); roots.enqueue(dir); } template < typename queue_of_dir > typename enable_if<is_std_vector<queue_of_dir>,void>::type get_filesystem_roots ( std::vector<directory>& roots ) { roots.clear(); directory dir("/"); roots.push_back(dir); } // ---------------------------------------------------------------------------------------- } #ifdef NO_MAKEFILE #include "dir_nav_kernel_2.cpp" #endif #endif // DLIB_DIR_NAV_KERNEl_2_