You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
562 lines
14 KiB
562 lines
14 KiB
//
|
|
// 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 //
|