// -*- C++ -*- (c) 2014 Vladimír Štill #include #include #ifdef _WIN32 #include #include #else #include #include #endif #include #include #include #include #include #include #include #include #include #ifndef BRICK_FS_H #define BRICK_FS_H namespace brick { namespace fs { enum class AutoDelete : bool { No = false, Yes = true }; struct SystemException : std::exception { // make sure we don't override errno accidentaly when constructing std::string explicit SystemException( std::string what ) : SystemException{ errno, what } { } explicit SystemException( const char *what ) : SystemException{ errno, what } { } virtual const char *what() const noexcept override { return _what.c_str(); } private: std::string _what; explicit SystemException( int _errno, std::string what ) { _what = "System error: " + std::string( std::strerror( _errno ) ) + ", when " + what; } }; struct Exception : std::runtime_error { explicit Exception( std::string what ) : std::runtime_error( what ) { } explicit Exception( const char *what ) : std::runtime_error( what ) { } }; #if defined( __unix ) const char pathSeparators[] = { '/' }; #elif defined( _WIN32 ) const char pathSeparators[] = { '\\', '/' }; #endif inline bool isPathSeparator( char c ) { for ( auto sep : pathSeparators ) if ( sep == c ) return true; return false; } inline std::pair< std::string, std::string > splitExtension( std::string path ) { auto pos = path.rfind( '.' ); if ( pos == std::string::npos ) return std::make_pair( path, std::string() ); return std::make_pair( path.substr( 0, pos ), path.substr( pos ) ); } inline std::string takeExtension( std::string path ) { return splitExtension( path ).second; } inline std::string dropExtension( std::string path ) { return splitExtension( path ).first; } inline std::string replaceExtension( std::string path, std::string extension ) { if ( !extension.empty() && extension[0] == '.' ) return dropExtension( path ) + extension; return dropExtension( path ) + "." + extension; } inline std::pair< std::string, std::string > splitFileName( std::string path ) { auto begin = path.rbegin(); while ( isPathSeparator( *begin ) ) ++begin; auto length = &*begin - &path.front() + 1; auto pos = std::find_if( begin, path.rend(), &isPathSeparator ); if ( pos == path.rend() ) return std::make_pair( std::string(), path.substr( 0, length ) ); auto count = &*pos - &path.front(); length -= count + 1; return std::make_pair( path.substr( 0, count ), path.substr( count + 1, length ) ); } inline std::pair< std::string, std::string > absolutePrefix( std::string path ) { #ifdef _WIN32 /* this must go before general case, because \ is prefix of \\ */ if ( path.size() >= 3 && path[ 1 ] == ':' && isPathSeparator( path[ 2 ] ) ) return std::make_pair( path.substr( 0, 3 ), path.substr( 3 ) ); if ( path.size() >= 2 && isPathSeparator( path[ 0 ] ) && isPathSeparator( path[ 1 ] ) ) return std::make_pair( path.substr( 0, 2 ), path.substr( 2 ) ); #endif // this is absolute path in both windows and unix if ( path.size() >= 1 && isPathSeparator( path[ 0 ] ) ) return std::make_pair( path.substr( 0, 1 ), path.substr( 1 ) ); return std::make_pair( std::string(), path ); } inline bool isAbsolute( std::string path ) { return absolutePrefix( path ).first.size() != 0; } inline bool isRelative( std::string path ) { return !isAbsolute( path ); } inline std::string joinPath( std::vector< std::string > paths ) { if ( paths.empty() ) return ""; auto it = ++paths.begin(); std::string out = paths[0]; for ( ; it != paths.end(); ++it ) { if ( isAbsolute( *it ) || out.empty() ) out = *it; else if ( !out.empty() && isPathSeparator( out.back() ) ) out += *it; else out += pathSeparators[0] + *it; } return out; } template< typename... FilePaths > inline std::string joinPath( FilePaths &&...paths ) { return joinPath( std::vector< std::string >{ std::forward< FilePaths >( paths )... } ); } inline std::vector< std::string > splitPath( std::string path ) { std::vector< std::string > out; auto last = path.begin(); while ( true ) { auto next = std::find_if( last, path.end(), &isPathSeparator ); if ( next == path.end() ) { out.emplace_back( last, next ); return out; } if ( last != next ) out.emplace_back( last, next ); last = ++next; } } inline std::string normalize( std::string path ) { auto abs = absolutePrefix( path ); return joinPath( abs.first, joinPath( splitPath( abs.second ) ) ); } inline std::string distinctPaths( const std::string &prefix, const std::string &path ) { auto prefI = prefix.begin(); auto pathI = path.begin(); auto start = pathI; bool wasSlash = false; for ( ; prefI != prefix.end() && pathI != path.end(); ++prefI, ++pathI ) { while ( wasSlash && prefI != prefix.end() && isPathSeparator( *prefI ) ) ++prefI; while ( wasSlash && pathI != path.end() && isPathSeparator( *pathI ) ) ++pathI; if ( wasSlash ) { start = pathI; wasSlash = false; } if ( prefI == prefix.end() ) { ++pathI; break; } if ( pathI == path.end() ) { ++prefI; break; } if ( *pathI != *prefI ) break; if ( isPathSeparator( *prefI ) ) wasSlash = true; } while ( wasSlash && prefI != prefix.end() && isPathSeparator( *prefI ) ) ++prefI; while ( wasSlash && pathI != path.end() && isPathSeparator( *pathI ) ) ++pathI; if ( wasSlash ) start = pathI; if ( prefI == prefix.end() ) { if ( pathI != path.end() && isPathSeparator( *pathI ) ) { while ( pathI != path.end() && isPathSeparator( *pathI ) ) ++pathI; start = pathI; } else if ( pathI == path.end() ) start = pathI; } else if ( prefI != prefix.end() && isPathSeparator( *prefI ) && pathI == path.end() ) { start = pathI; } return std::string( start, path.end() ); } inline std::string getcwd() { std::string buf; #ifdef _WIN32 char *buffer; if ( ( buffer = _getcwd( NULL, 0 ) ) == NULL ) throw SystemException( "getting the current working directory" ); buf = buffer; free( buffer ); #else size_t size = pathconf( ".", _PC_PATH_MAX ); buf.resize( size ); if ( ::getcwd( &buf.front(), size ) == nullptr ) throw SystemException( "getting the current working directory" ); buf.resize( std::strlen( &buf.front() ) ); #endif return buf; } inline void chdir( std::string dir ) { #ifdef _WIN32 if ( ::_chdir( dir.c_str() ) != 0 ) throw SystemException( "changing directory" ); #else if ( ::chdir( dir.c_str() ) != 0 ) throw SystemException( "changing directory" ); #endif } inline std::string mkdtemp( std::string dirTemplate ) { #ifdef _WIN32 if ( ::_mktemp( &dirTemplate.front() ) == nullptr ) throw SystemException( "creating temporary directory" ); #else if ( ::mkdtemp( &dirTemplate.front() ) == nullptr ) throw SystemException( "creating temporary directory" ); #endif return dirTemplate; } #ifdef _WIN32 #define stat _stat64 #endif inline std::unique_ptr< struct stat > stat( std::string pathname ) { #if _WIN32 // from MSDN: // If path contains the location of a directory, it cannot contain // a trailing backslash. If it does, -1 will be returned and errno // will be set to ENOENT. pathname = normalize( pathname ); #endif std::unique_ptr< struct stat > res( new struct stat ); if ( ::stat( pathname.c_str(), res.get() ) == -1 ) { if ( errno == ENOENT ) return std::unique_ptr< struct stat >(); else throw SystemException( "getting file information for " + pathname ); } return res; } #ifndef _WIN32 inline std::unique_ptr< struct stat > lstat( std::string pathname ) { std::unique_ptr< struct stat > res( new struct stat ); if ( ::lstat( pathname.c_str(), res.get() ) == -1 ) { if ( errno == ENOENT ) return std::unique_ptr< struct stat >(); else throw SystemException( "getting file information for " + pathname ); } return res; } #endif #ifdef _WIN32 inline void mkdirIfMissing( std::string dir, int ) { #else inline void mkdirIfMissing( std::string dir, mode_t mode ) { #endif for ( int i = 0; i < 5; ++i ) { // If it does not exist, make it #ifdef _WIN32 if ( ::_mkdir( dir.c_str() ) != -1 ) #else if ( ::mkdir( dir.c_str(), mode ) != -1 ) #endif return; // throw on all errors except EEXIST. Note that EEXIST "includes the case // where pathname is a symbolic link, dangling or not." if ( errno != EEXIST ) throw SystemException( "creating directory " + dir ); // Ensure that, if dir exists, it is a directory auto st = stat( dir ); if ( !st ) { // Either dir has just been deleted, or we hit a dangling // symlink. // // Retry creating a directory: the more we keep failing, the more // the likelyhood of a dangling symlink increases. // // We could lstat here, but it would add yet another case for a // race condition if the broken symlink gets deleted between the // stat and the lstat. continue; } #ifdef _WIN32 else if ( ( st->st_mode & _S_IFDIR ) == 0 ) #else else if ( !S_ISDIR( st->st_mode ) ) #endif // If it exists but it is not a directory, complain throw Exception( "ensuring path: " + dir + " exists but it is not a directory" ); else // If it exists and it is a directory, we're fine return; } throw Exception( "ensuring path: " + dir + " exists and looks like a dangling symlink" ); } inline void mkpath( std::string dir ) { std::pair< std::string, std::string > abs = absolutePrefix( dir ); auto split = splitPath( abs.second ); std::vector< std::string > toDo; for ( auto &x : split ) { toDo.emplace_back( x ); mkdirIfMissing( abs.first + joinPath( toDo ), 0777 ); } } inline void mkFilePath( std::string file ) { auto dir = splitFileName( file ).first; if ( !dir.empty() ) mkpath( dir ); } #if 0 struct Fd { template< typename... Args > Fd( Args... args ) : _fd( ::open( args... ) ) { if ( _fd < 0 ) throw SystemException( "while opening file" ); } ~Fd() { close(); } void close() { if ( _fd >= 0 ) { ::close( _fd ); _fd = -1; } } operator int() const { return _fd; } int fd() const { return _fd; } private: int _fd; }; #endif inline void writeFile( std::string file, std::string data ) { std::ofstream out( file.c_str(), std::ios::binary ); if ( !out.is_open() ) throw SystemException( "writing file " + file ); out << data; if ( !out.good() ) throw SystemException( "writing data to file " + file ); } inline void renameIfExists( std::string src, std::string dst ) { int res = ::rename( src.c_str(), dst.c_str() ); if ( res < 0 && errno != ENOENT ) throw SystemException( "moving " + src + " to " + dst ); } inline void unlink( std::string fname ) { #ifdef _WIN32 if ( ::_unlink( fname.c_str() ) < 0 ) throw SystemException( "cannot delete file" + fname ); #else if ( ::unlink( fname.c_str() ) < 0 ) throw SystemException( "cannot delete file" + fname ); #endif } inline void rmdir( std::string dirname ) { #ifdef _WIN32 if ( ::_rmdir( dirname.c_str() ) < 0 ) throw SystemException( "cannot delete directory " + dirname ); #else if ( ::rmdir( dirname.c_str() ) < 0 ) throw SystemException( "cannot delete directory " + dirname ); #endif } inline bool deleteIfExists( std::string file ) { #ifdef _WIN32 int r = ::_unlink( file.c_str() ); #else int r = ::unlink( file.c_str() ); #endif if ( r != 0 ) { if ( errno != ENOENT ) throw SystemException( "cannot delete file" + file ); else return false; } else return true; } #ifdef _WIN32 } // fs } // brick /* Declaration of POSIX directory browsing functions and types for Win32. Author: Kevlin Henney (kevlin@acm.org, kevlin@curbralan.com) History: Created March 1997. Updated June 2003. Copyright Kevlin Henney, 1997, 2003. All rights reserved. Permission to use, copy, modify, and distribute this software and its documentation for any purpose is hereby granted without fee, provided that this copyright and permissions notice appear in all copies and derivatives. This software is supplied "as is" without express or implied warranty. But that said, if there are any problems please get in touch. */ #ifdef __cplusplus extern "C" { #endif struct dirent { char *d_name; }; struct DIR { intptr_t handle; /* -1 for failed rewind */ struct _finddata_t info; struct dirent result; /* d_name null iff first time */ char *name; /* null-terminated char string */ }; static DIR *opendir( const char *name ) { DIR *dir = 0; if ( name && name[ 0 ] ) { size_t base_length = strlen( name ); const char *all = /* search pattern must end with suitable wildcard */ strchr( "/\\", name[ base_length - 1 ] ) ? "*" : "/*"; if ( ( dir = (DIR *)malloc( sizeof *dir ) ) != 0 && ( dir->name = (char *)malloc( base_length + strlen( all ) + 1 ) ) != 0 ) { strcat( strcpy( dir->name, name ), all ); if ( ( dir->handle = (intptr_t)_findfirst( dir->name, &dir->info ) ) != -1 ) { dir->result.d_name = 0; } else { /* rollback */ free( dir->name ); free( dir ); dir = 0; } } else { /* rollback */ free( dir ); dir = 0; errno = ENOMEM; } } else { errno = EINVAL; } return dir; } static int closedir( DIR *dir ) { int result = -1; if ( dir ) { if ( dir->handle != -1 ) { result = _findclose( dir->handle ); } free( dir->name ); free( dir ); } if ( result == -1 ) {/* map all errors to EBADF */ errno = EBADF; } return result; } static struct dirent *readdir( DIR *dir ) { struct dirent *result = 0; if ( dir && dir->handle != -1 ) { if ( !dir->result.d_name || _findnext( dir->handle, &dir->info ) != -1 ) { result = &dir->result; result->d_name = dir->info.name; } } else { errno = EBADF; } return result; } static void rewinddir( DIR *dir ) { if ( dir && dir->handle != -1 ) { _findclose( dir->handle ); dir->handle = (intptr_t)_findfirst( dir->name, &dir->info ); dir->result.d_name = 0; } else { errno = EBADF; } } #ifdef __cplusplus } #endif namespace brick { namespace fs { #endif template< typename DirPre, typename DirPost, typename File > void traverseDirectoryTree( std::string root, DirPre pre, DirPost post, File file ) { if ( pre( root ) ) { auto dir = std::unique_ptr< DIR, decltype( &::closedir ) >( ::opendir( root.c_str() ), &::closedir ); if ( dir == nullptr ) throw SystemException( "opening directory " + root ); for ( auto de = readdir( dir.get() ); de != nullptr; de = readdir( dir.get() ) ) { std::string name = de->d_name; if ( name == "." || name == ".." ) continue; auto path = joinPath( root, name ); auto st = stat( path ); #ifdef _WIN32 if ( st && ( st->st_mode & _S_IFDIR ) ) #else if ( st && S_ISDIR( st->st_mode ) ) #endif traverseDirectoryTree( path, pre, post, file ); else file( path ); } post( root ); } } template< typename Dir, typename File > void traverseDirectory( std::string root, Dir dir, File file ) { traverseDirectoryTree( root, [&]( std::string d ) -> bool { if ( d == root ) return true; else dir( d ); return false; }, []( std::string ) {}, file ); } template< typename File > void traverseFiles( std::string dir, File file ) { traverseDirectory( dir, []( std::string ) {}, file ); } inline void rmtree( std::string dir ) { traverseDirectoryTree( dir, []( std::string ) { return true; }, []( std::string dir ) { rmdir( dir ); }, []( std::string file ) { unlink( file ); } ); } struct ChangeCwd { ChangeCwd( std::string newcwd ) : oldcwd( getcwd() ) { chdir( newcwd ); } ~ChangeCwd() { chdir( oldcwd ); } const std::string oldcwd; }; struct TempDir { TempDir( std::string nameTemplate, AutoDelete autoDelete ) : path( mkdtemp( nameTemplate ) ), autoDelete( autoDelete ) { } ~TempDir() { if ( bool( autoDelete ) ) rmtree( path ); } const std::string path; AutoDelete autoDelete; }; namespace { std::string readFile( std::ifstream &in ) { if ( !in.is_open() ) throw std::runtime_error( "reading filestream" ); size_t length; in.seekg( 0, std::ios::end ); length = in.tellg(); in.seekg( 0, std::ios::beg ); std::string buffer; buffer.resize( length ); in.read( &buffer[ 0 ], length ); return buffer; } std::string readFile( const std::string &file ) { std::ifstream in( file.c_str(), std::ios::binary ); if ( !in.is_open() ) throw std::runtime_error( "reading file " + file ); return readFile( in ); } } #ifdef _WIN32 #define F_OK 0 #define W_OK 2 #define R_OK 4 //#define X_OK #endif inline bool access(const std::string &s, int m) { #ifdef _WIN32 return ::_access(s.c_str(), m) == 0; #else return ::access(s.c_str(), m) == 0; #endif } inline bool exists(const std::string& file) { return access(file, F_OK); } } } #if 0 inline std::pair< std::string, std::string > splitExtension( std::string path ) { inline std::string replaceExtension( std::string path, std::string extension ) { inline std::pair< std::string, std::string > splitFileName( std::string path ) { inline std::pair< std::string, std::string > absolutePrefix( std::string path ) { #endif #endif // BRICK_FS_H