/****************************************************************************** * Copyright (C) 2012 Matthew Goff * * * * This software is provided 'as-is', without any express or implied * * warranty. In no event will the authors be held liable for any damages * * arising from the use of this software. * * * * Permission is granted to anyone to use this software for any purpose, * * including commercial applications, and to alter it and redistribute it * * freely, subject to the following restrictions: * * * * 1. The origin of this software must not be misrepresented; you must not * * claim that you wrote the original software. If you use this software * * in a product, an acknowledgment in the product documentation would be * * appreciated but is not required. * * * * 2. Altered source versions must be plainly marked as such, and must not * * be misrepresented as being the original software. * * * * 3. This notice may not be removed or altered from any source distribution. * * * * Matthew Goff (matt@goff.cc) <http://www.ackmud.net/> * ******************************************************************************/ /** * @file utils.cpp * @brief All non-template member functions of the Utils namespace. * * The Utils namespace contains all general purpose, multi-use, and non-class * functions. Classes that are implemented to extend default operators or * designed to be used in a utility sense, rather than actual objects, are also * contained within this namespace. */ #include "utils.h" /** * @brief Returns the current system time. * @retval timeval A timeval struct filled with the current system time. */ const timeval Utils::CurrentTime() { UFLAGS_DE( flags ); timeval now; if ( ::gettimeofday( &now, NULL ) < 0 ) { LOGERRNO( flags, "Utils::CurrentTime()->" ); return timeval(); } return now; } /** * @brief Calculates the different between two timeval variables. * @param[in] prev A timeval of the beginning time. * @param[in] current A timeval of the current, or end time. * @param[in] granularity A #long unsigned int variable specified as a UTILS_TIME value from #UTILS_OPTS. * @retval long unsigned int The difference between prev and current in units granularity. */ const long unsigned int Utils::DiffTime( const timeval& prev, const timeval& current, const long unsigned int& granularity ) { switch ( granularity ) { case UTILS_TIME_S: return ( current.tv_sec - prev.tv_sec ); case UTILS_TIME_MS: return ( current.tv_usec - prev.tv_usec ) / 1000; case UTILS_TIME_US: default: return ( current.tv_usec - prev.tv_usec ); } } /** * @brief Returns a string consisting of directory/file.ext. * @param[in] directory The top level directory build the path from. * @param[in] file The file to build the path from. * @param[in] ext Optionally replaces the file extension with this. * @retval string A string consisting of directory/file.ext. */ const string Utils::DirPath( const string& directory, const string& file, const string& ext ) { string path( directory ); path.append( "/" ); if ( !ext.empty() ) { path.append( file.substr( 0, file.find_last_of( "." ) + 1 ) ); path.append( ext ); } else path.append( file ); return path; } /** * @brief This is a nested wrapper for Utils::__FormatString and should not be called directly. * @param[in] narg A #long unsigned int variable of the total number of arguments passed. Handled automatically. * @param[in] flags Any number of flags from #UTILS_OPTS to control output formatting and options. * @param[in] caller A string value containing the calling function. Handled automatically. * @param[in] fmt A string value containing a printf-style format string. * @param[in] ... A variable arguments list to populate fmt with. * @retval string A printf-style formatted string. */ const string Utils::_FormatString( const long unsigned int& narg, const bitset<CFG_MEM_MAX_BITSET>& flags, const string& caller, const string& fmt, ... ) { UFLAGS_DE( uflags ); va_list args; string output; if ( fmt.empty() ) { LOGSTR( uflags, "Utils::_FormatString()-> called with empty fmt" ); return output; } va_start( args, fmt ); output = __FormatString( narg, flags, caller, fmt, args ); va_end( args ); return output; } /** * @brief This is the printf-style string formatter. It should not be invoked directly, but rather by using Utils::FormatString() to ensure proper argument count and caller passing. * @param[in] narg A #long unsigned int variable of the total number of arguments passed. Handled automatically. * @param[in] flags Any number of flags from #UTILS_OPTS to control output formatting and options. * @param[in] caller A string value containing the calling function. Handled automatically. * @param[in] fmt A string value containing a printf-style format string. * @param[in] val A variable arguments list to populate fmt with. * @retval string A printf-style formatted string. */ const string Utils::__FormatString( const long unsigned int& narg, const bitset<CFG_MEM_MAX_BITSET>& flags, const string& caller, const string& fmt, va_list& val ) // Thanks go to Darien @ MudBytes.net for the start of this { UFLAGS_DE( uflags ); va_list args; vector<string> arguments; vector<string>::iterator si; vector<char> buf; string output, token; long signed int size = 0; if ( fmt.empty() ) { LOGSTR( uflags, "Utils::__FormatString()-> called with empty fmt" ); return output; } arguments = StrTokens( fmt ); for ( si = arguments.begin(); si != arguments.end(); si++ ) { token = *si; if ( token.find( "%" ) != string::npos ) // not foolproof, but it should catch some worst cases by attempting size++; // to ensure a matching narg : format specifier count } if ( narg != 1 && narg != static_cast<long unsigned int>( size ) && narg != NumChar( fmt, "%" ) ) // if narg == 1 invocation was func( flags, string ) { bitset<CFG_MEM_MAX_BITSET> eflags; eflags.set( UTILS_TYPE_ERROR ); Logger( eflags, "Number of arguments (%lu) did not match number of format specifiers (%lu) at: %s", narg, size, CSTR( caller ) ); return output = ""; } va_copy( args, val ); size = vsnprintf( NULL, 0, CSTR( fmt ), args ); va_end( args ); va_copy( args, val ); buf.resize( size + 1 ); vsnprintf( &buf[0], ( size + 1 ), CSTR( fmt ), args ); va_end( args ); return output = &buf[0]; } /** * @brief This is the logging output engine. It should not be invoked directly, but rather by calling Utils::Logger() to ensure proper argument count and caller passing. * @param[in] narg A #long unsigned int variable of the total number of arguments passed. Handled automatically. * @param[in] flags Any number of flags from #UTILS_OPTS to control output formatting and options. * @param[in] caller A string value containing the calling function. Handled automatically. * @param[in] fmt A string value containing a printf-style format string. * @param[in] ... A variable arguments list to populate fmt with. * @retval string A printf-style formatted string. */ const void Utils::_Logger( const long unsigned int& narg, const bitset<CFG_MEM_MAX_BITSET>& flags, const string& caller, const string& fmt, ... ) { UFLAGS_DE( uflags ); va_list args; string pre, post, output; long unsigned int i = 0; if ( fmt.empty() ) { LOGSTR( uflags, "Utils::_Logger()-> called with empty fmt" ); return; } va_start( args, fmt ); output = __FormatString( narg, flags, caller, fmt, args ); va_end( args ); if ( output.empty() ) return; // prepend timestamp pre = StrTime( CurrentTime() ); pre.append( " :: " ); for ( i = 0; i < MAX_UTILS; i++ ) { if ( flags.test( i ) ) { switch( i ) { case UTILS_DEBUG: //output caller post.append( " [" ); post.append( caller ); post.append( "]" ); break; case UTILS_RAW: //no extraneous data applied pre.clear(); post.clear(); i = MAX_UTILS; break; case UTILS_TYPE_ERROR: //so fancy! pre.append( CFG_STR_UTILS_ERROR ); break; case UTILS_TYPE_INFO: //so fancy! pre.append( CFG_STR_UTILS_INFO ); break; case UTILS_TYPE_SOCKET: //so fancy! pre.append( CFG_STR_UTILS_SOCKET ); break; default: break; } } } clog << pre << output << post << endl; /** @todo Add monitor channel support */ return; } /** * @brief Returns the number of a specific character in a given string. * @param[in] input A string value to search. * @param[in] item The character to search for within input. * @retval long unsigned int The total count of item within input. */ const long unsigned int Utils::NumChar( const string& input, const string& item ) { UFLAGS_DE( flags ); long unsigned int amount = 0, i = 0; if ( input.empty() ) { LOGSTR( flags, "Utils::NumChar()-> called with empty input" ); return amount; } for ( i = 0; i < input.length(); i++ ) if ( input[i] == item[0] ) amount++; return amount; } /** * @brief Returns a vector of strings split at linebreaks based on input. * @param[in] input A string to split on newline characters. * @retval vector<string> A vector of strings that were split on the linebreaks detected from input. */ const vector<string> Utils::StrNewlines( const string& input ) { UFLAGS_DE( flags ); if ( input.empty() ) { LOGSTR( flags, "Utils::StrNewlines()-> called with empty input" ); return vector<string>(); } stringstream ss( input ); string line; vector<string> output; while ( getline( ss, line ) ) { // Strip the newline off the end line.resize( line.length() - 1 ); if ( !line.empty() ) output.push_back( line ); } return output; } /** * @brief Returns a given time as a string. * @param[in] now A timeval to be formatted into a string. * @retval string A string value containing the human readable form of the contents of now. */ const string Utils::StrTime( const timeval& now ) { UFLAGS_DE( flags ); string output; if ( ( output = ::ctime( &now.tv_sec ) ).empty() ) { LOGSTR( flags, "Utils::CurrentTime()->ctime()-> returned NULL" ); return output; } // Strip the newline off the end output.resize( output.length() - 1 ); return output; } /** * @brief Returns a vector of strings split at spaces based on input. * @param[in] input A string to split on space characters. * @retval vector<string> A vector of strings that were split on the spaces detected from input. */ const vector<string> Utils::StrTokens( const string& input ) { UFLAGS_DE( flags ); if ( input.empty() ) { LOGSTR( flags, "Utils::StrTokens()-> called with empty input" ); return vector<string>(); } stringstream ss( input ); istream_iterator<string> si( ss ); istream_iterator<string> end; vector<string> output( si, end ); return output; } /** * @brief Determines if a file path is a directory or file on disk. * @param[in] dir A string containing the file path to be checked. * @retval false Returned if the file path received in dir is not of type directory or an error occurs during stat. * @retval true Returned if the file path received in dir is of type directory. */ const bool Utils::iDirectory( const string& dir ) { UFLAGS_DE( flags ); struct stat dir_info; if ( ::stat( CSTR( dir ), &dir_info ) < 0 ) { LOGERRNO( flags, "Utils::iDirectory()->stat()->" ); return false; } if ( !S_ISDIR( dir_info.st_mode ) ) return false; return true; } /** * @brief Determines if a file path is a file or directory on disk. * @param[in] file A string containing the file path to be checked. * @retval false Returned if the file path received in file is not of type file or an error occurs during stat. * @retval true Returned if the file path receievd in file is of type file. */ const bool Utils::iFile( const string& file ) { UFLAGS_DE( flags ); struct stat dir_info; if ( ::stat( CSTR( file ), &dir_info ) < 0 ) { LOGERRNO( flags, "Utils::iFile()->stat()->" ); return false; } if ( !S_ISREG( dir_info.st_mode ) ) return false; return true; } /** * @brief Determines if a string is only a string of numerical values. * @param[in] input A string to check for numerical values. * @retval false Returned if input is empty or the input contains non-numerical values. * @retval true Returned if input contains only numerical values. */ const bool Utils::iNumber( const string& input ) { UFLAGS_DE( flags ); long unsigned int i = 0; if ( input.empty() ) { LOGSTR( flags, "Utils::iNumber()-> called with empty input" ); return false; } for ( i = 0; i < input.length(); i++ ) if ( !isdigit( input[i] ) ) return false; return true; } /** * @brief Determines if a file is readable. * @param[in] file A string containing the file path to be checked. * @retval false Returned if file does not exist or is not readable. * @retval true Returned if file exists and is readable. */ const bool Utils::iReadable( const string& file ) { UFLAGS_DE( flags ); ifstream ifile; bool ret = false; ifile.open( CSTR( file ), ifstream::in ); if ( ifile.fail() ) ret = false; else ret = true; ifile.close(); return ret; } /** * @brief Return a multimap of a specified directory tree on disk. * @param[in] dir The filesystem path to search. * @param[in] recursive If tue, the function will continue to recursively list folders multi-layers deep rather than top level only. * @param[in,out] output A multimap<bool,string> consisting of a boolean value denoting either #UTILS_IS_DIRECTORY or #UTILS_IS_FILE. If recursive, this will be updated on each pass. * @param[in,out] dir_close A #long unsigned int pointing to the total directory opened count on a Server object. * @param[in,out] dir_open A #long unsigned int pointing to the total directory closed count on a Server object. * @retval multimap<bool,string> A multimap<bool,string> consisting of a boolean value denoting either #UTILS_IS_DIRECTORY or #UTILS_IS_FILE. If recursive, this will be updated on each pass. */ const multimap<bool,string> Utils::ListDirectory( const string& dir, const bool& recursive, multimap<bool,string>& output, long unsigned int& dir_close, long unsigned int& dir_open ) { UFLAGS_DE( flags ); DIR* directory = NULL; dirent* entry = NULL; string ifile, idir; if ( ( directory = ::opendir( CSTR( dir ) ) ) == NULL ) { LOGFMT( flags, "Utils::OpenDirectory()->opendir()-> returned NULL for dir: %s", CSTR( dir ) ); return output; } dir_open++; idir = dir; // Ensure a trailing slash is present to properly recurse if ( idir.compare( dir.length() - 1, 1, "/" ) != 0 ) idir.append( "/" ); while ( ( entry = ::readdir( directory ) ) != NULL ) { ifile = entry->d_name; // Skip over the unwanteds if ( ifile.compare( "." ) == 0 || ifile.compare( ".." ) == 0 ) continue; if ( iDirectory( idir + ifile ) ) output.insert( pair<bool,string>( UTILS_IS_DIRECTORY, ifile ) ); else output.insert( pair<bool,string>( UTILS_IS_FILE, ifile ) ); // Only recurse if another directory is found, otherwise a file was found, so skip it if ( iDirectory( idir + ifile ) && recursive ) ListDirectory( idir + ifile, recursive, output, dir_close, dir_open ); } if ( ::closedir( directory ) < 0 ) LOGERRNO( flags, "Utils::OpenDir()->closedir()->" ); else dir_close++; return output; }