| // |
| // 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 <cerrno> // errno, error constants |
| #include <climits> // PATH_MAX |
| #include <cstdlib> // abort, _splitpath, _makepath |
| #include <cstring> // strdup, strerror_r |
| #include <sstream> |
| |
| #include <vector> |
| |
| #if defined(__ANDROID__) |
| #include <android/api-level.h> |
| #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 <mach-o/dyld.h> // _NSGetExecutablePath |
| #include <libgen.h> // 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 <cerrno> // errno |
| #include <libgen.h> // dirname |
| #include <unistd.h> // 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 <windows.h> |
| #if defined( max ) |
| #undef max |
| #endif |
| |
| #include <cctype> |
| #include <algorithm> |
| |
| |
| 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 // |