/* * * Copyright (C) 2021, OFFIS e.V. * All rights reserved. See COPYRIGHT file for details. * * This software and supporting documentation were developed by * * OFFIS e.V. * R&D Division Health * Escherweg 2 * D-26121 Oldenburg, Germany * * * Module: ofstd * * Author: Jan Schlamelcher * * Purpose: Implementing the currently used subset of C++17' std::filesystem * */ #include "dcmtk/config/osconfig.h" #include #include #include #include "dcmtk/ofstd/offilsys.h" #ifdef _WIN32 #include #else // _WIN32 BEGIN_EXTERN_C #ifdef HAVE_SYS_TYPES_H #include #endif #ifdef HAVE_SYS_FILE_H #include // for struct DIR, opendir() #endif #ifdef HAVE_DIRENT_H #include // for struct DIR, opendir() #endif END_EXTERN_C /* Check if we are using glibc in a version where readdir() is known to be * thread-safe and where readdir_r() is deprecated. */ #if defined(__GLIBC__) && (((__GLIBC__ == 2) && (__GLIBC_MINOR__ >= 24)) || (__GLIBC__ >= 3)) #define READDIR_IS_THREADSAFE #endif #endif // _WIN32 OFpath::OFpath() : m_NativeString() { } OFpath::OFpath( const OFpath& rhs ) : m_NativeString( rhs.native() ) { } OFpath::OFpath( OFrvalue_ref(OFpath) rhs ) : m_NativeString() { m_NativeString.swap( OFrvalue_access( rhs ).m_NativeString ); } OFpath::OFpath( const char* const cstr, format fmt ) : m_NativeString( cstr ) { #ifdef _WIN32 convertSeparator(fmt); #endif } OFpath::OFpath( const OFString& string, format fmt ) : m_NativeString( string ) { #ifdef _WIN32 convertSeparator(fmt); #endif } #ifdef _WIN32 void OFpath::convertSeparator( format fmt ) { size_t pos = OFString_npos; switch( fmt ) { case native_format: return; case auto_format: pos = m_NativeString.find_first_of( "\\/" ); if( pos == OFString_npos || m_NativeString[pos] == preferred_separator ) return; break; default: case generic_format: pos = m_NativeString.find( '/' ); if( pos == OFString_npos ) return; break; } m_NativeString[pos] = preferred_separator; for( pos = m_NativeString.find( '/', pos + 1 ); pos != OFString_npos; pos = m_NativeString.find( '/', pos + 1 ) ) m_NativeString[pos] = preferred_separator; } #endif OFpath& OFpath::operator=( const OFpath& rhs ) { m_NativeString = rhs.native(); return *this; } OFpath& OFpath::operator=( OFrvalue_ref(OFpath) rhs ) { if( this != &OFrvalue_access( rhs ) ) { m_NativeString.clear(); m_NativeString.swap( OFrvalue_access( rhs ).m_NativeString ); } return *this; } OFBool OFpath::empty() const { return m_NativeString.empty(); } OFBool OFpath::is_absolute() const { #if _WIN32 const std::size_t pos = findRootName(); return OFString_npos != pos && ( ( pos + 1 ) == m_NativeString.size() || preferred_separator == m_NativeString[pos+1] ); #else return has_root_directory(); #endif } OFBool OFpath::is_relative() const { return !is_absolute(); } OFBool OFpath::has_root_name() const { return OFString_npos != findRootName(); } OFBool OFpath::has_root_directory() const { #if _WIN32 size_t pos = findRootName(); pos = ( OFString_npos != pos ? pos + 1 : 0 ); return pos < m_NativeString.size() && preferred_separator == m_NativeString[pos]; #else return !empty() && preferred_separator == *m_NativeString.begin(); #endif } OFBool OFpath::has_filename() const { #if _WIN32 OFString::const_iterator it = m_NativeString.end(); if( it == m_NativeString.begin() ) return OFFalse; --it; if( *it == preferred_separator ) return OFFalse; if( *it != ':' ) return OFTrue; while( it != m_NativeString.begin() ) if( *--it == preferred_separator ) return OFTrue; return OFFalse; #else return !empty() && preferred_separator != *(m_NativeString.end() - 1); #endif } OFBool OFpath::has_extension() const { return OFString_npos != findExtension(); } const OFString& OFpath::native() const { return m_NativeString; } const char* OFpath::c_str() const { return m_NativeString.c_str(); } OFrvalue OFpath::root_name() const { #if _WIN32 const size_t pos = findRootName(); if( OFString_npos != pos ) return OFpath( m_NativeString.substr( 0, pos + 1 ) ); #endif return OFpath(); } OFrvalue OFpath::filename() const { const size_t pos = findFilename(); if( OFString_npos != pos ) return OFpath( m_NativeString.substr( pos ) ); return OFpath(); } OFrvalue OFpath::extension() const { const size_t pos = findExtension(); if( OFString_npos != pos ) return OFpath( m_NativeString.substr( pos ) ); return OFpath(); } OFpath& OFpath::operator/=( const OFpath& rhs ) { // self append if( this == &rhs ) return *this /= OFpath( rhs ); #if _WIN32 // Comments are the descriptions from en.cppreference.com, put to whatever code segment handles it: // If p.is_absolute() || (p.has_root_name() && p.root_name() != root_name()), // then replaces the current path with p as if by operator=(p) and finishes. std::size_t pos = rhs.findRootName(); if( OFString_npos != pos ) // .. p.has_root_name() { ++pos; if ( // .. p.is_absolute() ( pos < rhs.m_NativeString.size() && rhs.m_NativeString[pos] == preferred_separator ) || // .. p.root_name() != root_name() ( pos > m_NativeString.size() || 0 != m_NativeString.compare( 0, pos - 1, rhs.m_NativeString, 0, pos - 1 ) ) ) { // then replaces the current path with p as if by operator=(p) and finishes. m_NativeString = rhs.m_NativeString; return *this; } } else pos = 0; // Otherwise, if p.has_root_directory(), then removes any root directory and the // entire relative path from the generic format pathname of *this, then appends the native format // pathname of p, omitting any root-name from its generic format, to the native format of *this. if( pos < rhs.m_NativeString.size() && rhs.m_NativeString[pos] == '\\' ) // .. p.has_root_directory() { // we shall remove ONLY the root directory and relative path, not the root name // so find it and, if it exists, keep it const size_t root = findRootName(); if( OFString_npos == root ) { // no root name, so replace the entire string m_NativeString = rhs.m_NativeString; } else { // removes any root directory and the entire relative path from the generic format pathname of *this // appends the native format pathname of p, omitting any root-name from its generic format m_NativeString.replace( root + 1, OFString_npos, rhs.m_NativeString, pos, OFString_npos ); } } else { // If has_filename() || (!has_root_directory() && is_absolute()) if( !empty() && preferred_separator != *(m_NativeString.end() - 1) ) { // then appends path::preferred_separator to the generic format of *this m_NativeString.reserve( m_NativeString.size() + rhs.m_NativeString.size() - pos + 1 ); m_NativeString += preferred_separator; } // appends the native format pathname of p, omitting any root-name from its generic format m_NativeString += rhs.m_NativeString.substr( pos ); } #else // version for filesystems without root names, pretty straight forward if( !rhs.is_absolute() ) { if( has_filename() ) { m_NativeString.reserve( m_NativeString.size() + rhs.m_NativeString.size() + 1 ); m_NativeString += preferred_separator; } m_NativeString += rhs.m_NativeString; } else m_NativeString = rhs.m_NativeString; #endif return *this; } size_t OFpath::findRootName() const { #if _WIN32 const size_t pos = m_NativeString.find_first_of( ":\\" ); if( OFString_npos != pos && m_NativeString[pos] == ':' ) return pos; #endif return OFString_npos; } size_t OFpath::findFilename() const { #if _WIN32 const size_t pos = m_NativeString.find_last_of( ":\\" ); #else const size_t pos = m_NativeString.find_last_of( "/" ); #endif if( pos == OFString_npos ) return empty() ? OFString_npos : 0; else return pos < m_NativeString.size() ? pos + 1 : OFString_npos; } size_t OFpath::findExtension() const { #if _WIN32 const size_t pos = m_NativeString.find_last_of( ".:\\" ); #else const size_t pos = m_NativeString.find_last_of( "./" ); #endif if ( pos && OFString_npos != pos && m_NativeString[pos] == '.' ) { switch( m_NativeString[pos-1] ) { #if _WIN32 case '\\': case ':': #else case '/': #endif return OFString_npos; case '.': if( pos < m_NativeString.size() - 1 ) return pos; if( pos == 1 ) return OFString_npos; switch( m_NativeString[pos-2] ) { #if _WIN32 case '\\': case ':': #else case '/': #endif return OFString_npos; default: break; } default: break; } return pos; } return OFString_npos; } const OFpath& OFdirectory_entry::path() const { return m_Path; } OFdirectory_entry::operator const OFpath&() const { return OFdirectory_entry::path(); } OFdirectory_iterator_proxy::OFdirectory_iterator_proxy( const OFdirectory_entry& rhs ) : m_Entry( rhs ) { } class OFdirectory_iterator::NativeDirectoryEntry : public OFdirectory_entry { public: NativeDirectoryEntry( const OFpath& path ); ~NativeDirectoryEntry(); OFBool skipInvalidFiles(); OFBool next(); private: OFBool good() const; const char* filename() const; const OFpath m_Parent; #ifdef HAVE__FINDFIRST // HAVE__FINDFIRST ::_finddata_t m_FileData; OFintptr_t m_hFile; #else // HAVE__FINDFIRST ::DIR* m_pDIR; ::dirent* m_pDirent; #if defined(HAVE_READDIR_R) && !defined(READDIR_IS_THREADSAFE) Uint8 m_Buffer[sizeof(::dirent) + _POSIX_PATH_MAX + 1]; #endif #endif // HAVE__FINDFIRST }; OFdirectory_iterator::NativeDirectoryEntry::NativeDirectoryEntry( const OFpath& path ) : OFdirectory_entry() , m_Parent( path ) #ifdef HAVE__FINDFIRST , m_FileData() , m_hFile( _findfirst( OFconst_cast( char*, (path / "*").c_str() ), &m_FileData ) ) #else // HAVE__FINDFIRST , m_pDIR( ::opendir( path.c_str() ) ) , m_pDirent() #if defined(HAVE_READDIR_R) && !defined(READDIR_IS_THREADSAFE) , m_Buffer() #endif #endif // HAVE__FINDFIRST { #ifndef HAVE__FINDFIRST if( m_pDIR && !next() ) { ::closedir( m_pDIR ); m_pDIR = OFnullptr; } #endif } OFdirectory_iterator::NativeDirectoryEntry::~NativeDirectoryEntry() { #ifdef HAVE__FINDFIRST ::_findclose( m_hFile ); #else if( m_pDIR ) ::closedir( m_pDIR ); #endif } OFBool OFdirectory_iterator::NativeDirectoryEntry::good() const { #ifdef HAVE__FINDFIRST return -1 != m_hFile; #else return m_pDIR; #endif } const char* OFdirectory_iterator::NativeDirectoryEntry::filename() const { #ifdef HAVE__FINDFIRST return m_FileData.name; #else return m_pDirent->d_name; #endif } OFBool OFdirectory_iterator::NativeDirectoryEntry::next() { #ifdef HAVE__FINDFIRST // HAVE__FINDFIRST return !::_findnext( m_hFile, &m_FileData ); #else // HAVE__FINDFIRST #if defined(HAVE_READDIR_R) && !defined(READDIR_IS_THREADSAFE) #ifdef HAVE_OLD_READDIR_R return (m_pDirent = ::readdir_r( m_pDIR, OFreinterpret_cast(dirent*, m_Buffer ) ) ); #else // HAVE_OLD_READDIR_R return !::readdir_r( m_pDIR, OFreinterpret_cast(dirent*, m_Buffer ), &m_pDirent ) && m_pDirent; #endif // HAVE_OLD_READDIR_R #else // HAVE_READDIR_R && !READDIR_IS_THREADSAFE return (m_pDirent = ::readdir( m_pDIR ) ); #endif // HAVE_READDIR_R && !READDIR_IS_THREADSAFE #endif // HAVE__FINDFIRST } OFBool OFdirectory_iterator::NativeDirectoryEntry::skipInvalidFiles() { while( good() && ( !::strcmp( filename(), "." ) || !::strcmp( filename(), ".." ) ) ) { if( !next() ) return OFFalse; } if( good() ) { OFdirectory_entry::m_Path = m_Parent / filename(); return OFTrue; } return OFFalse; } OFdirectory_iterator::OFdirectory_iterator() : m_pEntry() { } OFdirectory_iterator::OFdirectory_iterator( const OFpath& path ) : m_pEntry( new NativeDirectoryEntry( path ) ) { assert( m_pEntry ); if( !m_pEntry->skipInvalidFiles() ) m_pEntry.reset(); } OFdirectory_iterator::OFdirectory_iterator( const OFdirectory_iterator& rhs ) : m_pEntry( rhs.m_pEntry ) { } OFdirectory_iterator& OFdirectory_iterator::operator=( const OFdirectory_iterator& rhs ) { m_pEntry = rhs.m_pEntry; return *this; } OFdirectory_iterator::~OFdirectory_iterator() { } OFBool OFdirectory_iterator::operator==( const OFdirectory_iterator& rhs ) const { return m_pEntry == rhs.m_pEntry; } OFBool OFdirectory_iterator::operator!=( const OFdirectory_iterator& rhs ) const { return m_pEntry != rhs.m_pEntry; } OFdirectory_iterator& OFdirectory_iterator::operator++() { assert( m_pEntry ); if( !m_pEntry->next() || !m_pEntry->skipInvalidFiles() ) m_pEntry.reset(); return *this; } OFrvalue OFdirectory_iterator::operator++(int) { assert( m_pEntry ); OFdirectory_iterator_proxy proxy( *m_pEntry ); ++(*this); return proxy; } OFdirectory_iterator::pointer OFdirectory_iterator::operator->() const { assert( m_pEntry ); return m_pEntry.get(); } OFdirectory_iterator::reference OFdirectory_iterator::operator*() const { assert( m_pEntry ); return *m_pEntry; }