// // 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 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(&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 //