// // Copyright (c) 2017 The Khronos Group Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #include "os_helpers.h" #include "errorHelpers.h" // ================================================================================================= // C++ interface. // ================================================================================================= #include // errno, error constants #include // PATH_MAX #include // abort, _splitpath, _makepath #include // strdup, strerror_r #include #include #if defined(__ANDROID__) #include #endif #define CHECK_PTR( ptr ) \ if ( (ptr) == NULL ) { \ abort(); \ } typedef std::vector< char > buffer_t; #if ! defined( PATH_MAX ) #define PATH_MAX 1000 #endif int const _size = PATH_MAX + 1; // Initial buffer size for path. int const _count = 8; // How many times we will try to double buffer size. // ------------------------------------------------------------------------------------------------- // MacOS X // ------------------------------------------------------------------------------------------------- #if defined( __APPLE__ ) #include // _NSGetExecutablePath #include // dirname static std::string _err_msg( int err, // Error number (e. g. errno). int level // Nesting level, for avoiding infinite recursion. ) { /* There are 3 incompatible versions of strerror_r: char * strerror_r( int, char *, size_t ); // GNU version int strerror_r( int, char *, size_t ); // BSD version int strerror_r( int, char *, size_t ); // XSI version BSD version returns error code, while XSI version returns 0 or -1 and sets errno. */ // BSD version of strerror_r. buffer_t buffer( 100 ); int count = _count; for ( ; ; ) { int rc = strerror_r( err, & buffer.front(), buffer.size() ); if ( rc == EINVAL ) { // Error code is not recognized, but anyway we got the message. return & buffer.front(); } else if ( rc == ERANGE ) { // Buffer is not enough. if ( count > 0 ) { // Enlarge the buffer. -- count; buffer.resize( buffer.size() * 2 ); } else { std::stringstream ostr; ostr << "Error " << err << " " << "(Getting error message failed: " << "Buffer of " << buffer.size() << " bytes is still too small" << ")"; return ostr.str(); }; // if } else if ( rc == 0 ) { // We got the message. return & buffer.front(); } else { std::stringstream ostr; ostr << "Error " << err << " " << "(Getting error message failed: " << ( level < 2 ? _err_msg( rc, level + 1 ) : "Oops" ) << ")"; return ostr.str(); }; // if }; // forever } // _err_msg std::string dir_sep( ) { return "/"; } // dir_sep std::string exe_path( ) { buffer_t path( _size ); int count = _count; for ( ; ; ) { uint32_t size = path.size(); int rc = _NSGetExecutablePath( & path.front(), & size ); if ( rc == 0 ) { break; }; // if if ( count > 0 ) { -- count; path.resize( size ); } else { log_error( "ERROR: Getting executable path failed: " "_NSGetExecutablePath failed: Buffer of %lu bytes is still too small\n", (unsigned long) path.size() ); exit( 2 ); }; // if }; // forever return & path.front(); } // exe_path std::string exe_dir( ) { std::string path = exe_path(); // We cannot pass path.c_str() to `dirname' bacause `dirname' modifies its argument. buffer_t buffer( path.c_str(), path.c_str() + path.size() + 1 ); // Copy with trailing zero. return dirname( & buffer.front() ); } // exe_dir #endif // __APPLE__ // ------------------------------------------------------------------------------------------------- // Linux // ------------------------------------------------------------------------------------------------- #if defined( __linux__ ) #include // errno #include // dirname #include // readlink static std::string _err_msg( int err, int level ) { /* There are 3 incompatible versions of strerror_r: char * strerror_r( int, char *, size_t ); // GNU version int strerror_r( int, char *, size_t ); // BSD version int strerror_r( int, char *, size_t ); // XSI version BSD version returns error code, while XSI version returns 0 or -1 and sets errno. */ #if (defined(__ANDROID__) && __ANDROID_API__ < 23) || ( ( _POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600 ) && ! _GNU_SOURCE ) // XSI version of strerror_r. #warning Not tested! buffer_t buffer( 200 ); int count = _count; for ( ; ; ) { int rc = strerror_r( err, & buffer.front(), buffer.size() ); if ( rc == -1 ) { int _err = errno; if ( _err == ERANGE ) { if ( count > 0 ) { // Enlarge the buffer. -- count; buffer.resize( buffer.size() * 2 ); } else { std::stringstream ostr; ostr << "Error " << err << " " << "(Getting error message failed: " << "Buffer of " << buffer.size() << " bytes is still too small" << ")"; return ostr.str(); }; // if } else { std::stringstream ostr; ostr << "Error " << err << " " << "(Getting error message failed: " << ( level < 2 ? _err_msg( _err, level + 1 ) : "Oops" ) << ")"; return ostr.str(); }; // if } else { // We got the message. return & buffer.front(); }; // if }; // forever #else // GNU version of strerror_r. char buffer[ 2000 ]; return strerror_r( err, buffer, sizeof( buffer ) ); #endif } // _err_msg std::string dir_sep( ) { return "/"; } // dir_sep std::string exe_path( ) { static std::string const exe = "/proc/self/exe"; buffer_t path( _size ); int count = _count; // Max number of iterations. for ( ; ; ) { ssize_t len = readlink( exe.c_str(), & path.front(), path.size() ); if ( len < 0 ) { // Oops. int err = errno; log_error( "ERROR: Getting executable path failed: " "Reading symlink `%s' failed: %s\n", exe.c_str(), err_msg( err ).c_str() ); exit( 2 ); }; // if if ( len < path.size() ) { // We got the path. path.resize( len ); break; }; // if // Oops, buffer is too small. if ( count > 0 ) { -- count; // Enlarge the buffer. path.resize( path.size() * 2 ); } else { log_error( "ERROR: Getting executable path failed: " "Reading symlink `%s' failed: Buffer of %lu bytes is still too small\n", exe.c_str(), (unsigned long) path.size() ); exit( 2 ); }; // if }; // forever return std::string( & path.front(), path.size() ); } // exe_path std::string exe_dir( ) { std::string path = exe_path(); // We cannot pass path.c_str() to `dirname' bacause `dirname' modifies its argument. buffer_t buffer( path.c_str(), path.c_str() + path.size() + 1 ); // Copy with trailing zero. return dirname( & buffer.front() ); } // exe_dir #endif // __linux__ // ------------------------------------------------------------------------------------------------- // MS Windows // ------------------------------------------------------------------------------------------------- #if defined( _WIN32 ) #include #if defined( max ) #undef max #endif #include #include static std::string _err_msg( int err, int level ) { std::string msg; LPSTR buffer = NULL; DWORD flags = FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS; DWORD len = FormatMessageA( flags, NULL, err, LANG_USER_DEFAULT, reinterpret_cast< LPSTR >( & buffer ), 0, NULL ); if ( buffer == NULL || len == 0 ) { int _err = GetLastError(); char str[1024] = { 0 }; snprintf(str, sizeof(str), "Error 0x%08x (Getting error message failed: %s )", err, ( level < 2 ? _err_msg( _err, level + 1 ).c_str() : "Oops" )); msg = std::string(str); } else { // Trim trailing whitespace (including `\r' and `\n'). while ( len > 0 && isspace( buffer[ len - 1 ] ) ) { -- len; }; // while // Drop trailing full stop. if ( len > 0 && buffer[ len - 1 ] == '.' ) { -- len; }; // if msg.assign( buffer, len ); }; //if if ( buffer != NULL ) { LocalFree( buffer ); }; // if return msg; } // _get_err_msg std::string dir_sep( ) { return "\\"; } // dir_sep std::string exe_path( ) { buffer_t path( _size ); int count = _count; for ( ; ; ) { DWORD len = GetModuleFileNameA( NULL, & path.front(), path.size() ); if ( len == 0 ) { int err = GetLastError(); log_error( "ERROR: Getting executable path failed: %s\n", err_msg( err ).c_str() ); exit( 2 ); }; // if if ( len < path.size() ) { path.resize( len ); break; }; // if // Buffer too small. if ( count > 0 ) { -- count; path.resize( path.size() * 2 ); } else { log_error( "ERROR: Getting executable path failed: " "Buffer of %lu bytes is still too small\n", (unsigned long) path.size() ); exit( 2 ); }; // if }; // forever return std::string( & path.front(), path.size() ); } // exe_path std::string exe_dir( ) { std::string exe = exe_path(); int count = 0; // Splitting path into components. buffer_t drv( _MAX_DRIVE ); buffer_t dir( _MAX_DIR ); count = _count; #if defined(_MSC_VER) for ( ; ; ) { int rc = _splitpath_s( exe.c_str(), & drv.front(), drv.size(), & dir.front(), dir.size(), NULL, 0, // We need neither name NULL, 0 // nor extension ); if ( rc == 0 ) { break; } else if ( rc == ERANGE ) { if ( count > 0 ) { -- count; // Buffer is too small, but it is not clear which one. // So we have to enlarge all. drv.resize( drv.size() * 2 ); dir.resize( dir.size() * 2 ); } else { log_error( "ERROR: Getting executable path failed: " "Splitting path `%s' to components failed: " "Buffers of %lu and %lu bytes are still too small\n", exe.c_str(), (unsigned long) drv.size(), (unsigned long) dir.size() ); exit( 2 ); }; // if } else { log_error( "ERROR: Getting executable path failed: " "Splitting path `%s' to components failed: %s\n", exe.c_str(), err_msg( rc ).c_str() ); exit( 2 ); }; // if }; // forever #else // __MINGW32__ // MinGW does not have the "secure" _splitpath_s, use the insecure version instead. _splitpath( exe.c_str(), & drv.front(), & dir.front(), NULL, // We need neither name NULL // nor extension ); #endif // __MINGW32__ // Combining components back to path. // I failed with "secure" `_makepath_s'. If buffer is too small, instead of returning // ERANGE, `_makepath_s' pops up dialog box and offers to debug the program. D'oh! // So let us try to guess the size of result and go with insecure `_makepath'. buffer_t path( std::max( drv.size() + dir.size(), size_t( _MAX_PATH ) ) + 10 ); _makepath( & path.front(), & drv.front(), & dir.front(), NULL, NULL ); return & path.front(); } // exe_dir #endif // _WIN32 std::string err_msg( int err ) { return _err_msg( err, 0 ); } // err_msg // ================================================================================================= // C interface. // ================================================================================================= char * get_err_msg( int err ) { char * msg = strdup( err_msg( err ).c_str() ); CHECK_PTR( msg ); return msg; } // get_err_msg char * get_dir_sep( ) { char * sep = strdup( dir_sep().c_str() ); CHECK_PTR( sep ); return sep; } // get_dir_sep char * get_exe_path( ) { char * path = strdup( exe_path().c_str() ); CHECK_PTR( path ); return path; } // get_exe_path char * get_exe_dir( ) { char * dir = strdup( exe_dir().c_str() ); CHECK_PTR( dir ); return dir; } // get_exe_dir // end of file //