From 70a5655461f964fd1eb798083cbc4e91997eda56 Mon Sep 17 00:00:00 2001 From: Andrey Astafyev Date: Fri, 4 Oct 2019 12:48:12 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=BE=D0=B8=D1=81=D0=BA=20=D1=81=D1=82?= =?UTF-8?q?=D0=B0=D0=BD=D0=B4=D0=B0=D1=80=D1=82=D0=BD=D1=8B=D1=85=20=D0=BA?= =?UTF-8?q?=D0=B0=D1=82=D0=B0=D0=BB=D0=BE=D0=B3=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmake/cmlib | 2 +- examples/filesystem/01_minimal/CMakeLists.txt | 45 ++ examples/filesystem/01_minimal/minimal.cpp | 17 + examples/filesystem/CMakeLists.txt | 2 + src/base/CMakeLists.txt | 2 +- src/filesystem/CMakeLists.txt | 9 +- src/filesystem/paths.cpp | 220 +++++- src/filesystem/paths.hpp | 40 +- src/log/CMakeLists.txt | 2 +- thirdparty/whereami/README.md | 132 ++++ thirdparty/whereami/whereami.c | 679 ++++++++++++++++++ thirdparty/whereami/whereami.h | 67 ++ 12 files changed, 1207 insertions(+), 10 deletions(-) create mode 100644 examples/filesystem/01_minimal/CMakeLists.txt create mode 100644 examples/filesystem/01_minimal/minimal.cpp create mode 100644 thirdparty/whereami/README.md create mode 100644 thirdparty/whereami/whereami.c create mode 100644 thirdparty/whereami/whereami.h diff --git a/cmake/cmlib b/cmake/cmlib index aba632a..d28f78d 160000 --- a/cmake/cmlib +++ b/cmake/cmlib @@ -1 +1 @@ -Subproject commit aba632a1d7d673fb17c85284327b123061f04bc0 +Subproject commit d28f78d52115566a8a2be22a77be44f928d8e621 diff --git a/examples/filesystem/01_minimal/CMakeLists.txt b/examples/filesystem/01_minimal/CMakeLists.txt new file mode 100644 index 0000000..1e714bd --- /dev/null +++ b/examples/filesystem/01_minimal/CMakeLists.txt @@ -0,0 +1,45 @@ +# Название основной цели в текущем каталоге +set(current_target example-filesystem-minimal) + +# Список файлов исходных текстов +set(current_target_sources + ${CMAKE_CURRENT_SOURCE_DIR}/minimal.cpp + ) + +# Путь поиска библиотек внутри проекта +link_directories(${CMAKE_INSTALL_LIBDIR}) +link_directories(${CMAKE_BINARY_DIR}/src/filesystem/lib) + +# Цель для создания исполняемого файла +add_executable(${current_target} ${current_target_sources}) +common_target_properties(${current_target}) +add_clang_tidy_check(${current_target} ${current_target_sources}) +add_clang_analyze_check(${current_target} ${current_target_sources}) +add_clazy_check(${current_target} ${current_target_sources}) +add_uncrustify_format(${current_target} ${current_target_sources}) +add_pvs_check(${current_target}) + +# Qt5 +# qt_translation(TARGET ${current_target} TS_DIR ${CMAKE_SOURCE_DIR}/l10n LANGUAGES ru_RU) +target_include_directories(${current_target} PRIVATE ${CMAKE_SOURCE_DIR}/src) +target_include_directories(${current_target} SYSTEM PUBLIC ${FMT_INCLUDE_DIRS}) +target_include_directories(${current_target} SYSTEM PUBLIC ${SPDLOG_INCLUDE_DIRS}) +target_include_directories(${current_target} SYSTEM PUBLIC ${Qt5Core_INCLUDE_DIRS}) +target_compile_options(${current_target} PUBLIC "${Qt5Core_EXECUTABLE_COMPILE_FLAGS}") + +target_link_libraries(${current_target} filesystem_static) +target_link_libraries(${current_target} Qt5::Core) + +# Имя выходного файла для цели +set_target_properties(${current_target} + PROPERTIES + OUTPUT_NAME filesystem-minimal + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR} + ) + +add_sanitizers(${current_target}) + +# cotire(${current_target}) + +# Правила для установки +install(TARGETS ${current_target} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/examples/filesystem/01_minimal/minimal.cpp b/examples/filesystem/01_minimal/minimal.cpp new file mode 100644 index 0000000..279fd9b --- /dev/null +++ b/examples/filesystem/01_minimal/minimal.cpp @@ -0,0 +1,17 @@ +#include "filesystem/paths.hpp" + +namespace MF = myx::filesystem; + + +int main( int argc, char** argv ) +{ + (void)argc; + (void)argv; + MF::Paths paths; + + paths.updatePaths(); + paths.makePaths(); + paths.findConfigFile( "test" ); + + return( 0 ); +} diff --git a/examples/filesystem/CMakeLists.txt b/examples/filesystem/CMakeLists.txt index e69de29..d86db10 100644 --- a/examples/filesystem/CMakeLists.txt +++ b/examples/filesystem/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(01_minimal) + diff --git a/src/base/CMakeLists.txt b/src/base/CMakeLists.txt index e9fa3c0..a36331c 100644 --- a/src/base/CMakeLists.txt +++ b/src/base/CMakeLists.txt @@ -35,6 +35,6 @@ install(FILES ${CMAKE_BINARY_DIR}/include/cmlib_config.hpp ${CMAKE_BINARY_DIR}/include/config_flags.hpp ${current_target_headers} COMPONENT headers - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${current_target}) + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/${current_target}) install(FILES ${CMAKE_BINARY_DIR}/${current_target}.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) diff --git a/src/filesystem/CMakeLists.txt b/src/filesystem/CMakeLists.txt index dd707d6..ffb527c 100644 --- a/src/filesystem/CMakeLists.txt +++ b/src/filesystem/CMakeLists.txt @@ -11,7 +11,8 @@ set(current_target_headers ${CMAKE_CURRENT_SOURCE_DIR}/paths.hpp ) -add_common_library(TARGET ${current_target} SOURCES ${current_target_sources}) +add_common_library(TARGET ${current_target} SOURCES ${current_target_sources} + ${CMAKE_SOURCE_DIR}/thirdparty/whereami/whereami.c) common_target_properties(${current_target}) add_clang_tidy_check(${current_target} ${current_target_sources}) add_clang_analyze_check(${current_target} ${current_target_sources}) @@ -20,6 +21,10 @@ add_pvs_check(${current_target}) add_uncrustify_format(${current_target} ${current_target_sources} ${current_target_headers}) target_include_directories(${current_target} SYSTEM PUBLIC ${Qt5Core_INCLUDE_DIRS}) +target_include_directories(${current_target} SYSTEM PUBLIC ${FMT_INCLUDE_DIRS}) +target_include_directories(${current_target} SYSTEM PUBLIC ${SPDLOG_INCLUDE_DIRS}) +target_include_directories(${current_target} PRIVATE ${CMAKE_SOURCE_DIR}/src/base) +target_include_directories(${current_target} PRIVATE ${CMAKE_SOURCE_DIR}/thirdparty/whereami) # Цель, используемая только для установки заголовочных файлов без компиляции проекта add_custom_target(${current_target}-install-headers @@ -34,6 +39,6 @@ if(BUILD_SHARED_LIBS) endif() install(FILES ${current_target_headers} COMPONENT headers - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${current_target}) + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/${current_target}) install(FILES ${CMAKE_BINARY_DIR}/${current_target}.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) diff --git a/src/filesystem/paths.cpp b/src/filesystem/paths.cpp index f42acfd..ed9431a 100644 --- a/src/filesystem/paths.cpp +++ b/src/filesystem/paths.cpp @@ -1,9 +1,223 @@ #include "paths.hpp" +#include "config.hpp" +#include "whereami.h" -namespace MF = myx::filesystem; +#include +namespace myx { -int32_t MF::init( int32_t i = 0 ) +namespace filesystem { + +Paths::Paths() : + m_prefixDirectory( "/opt/" ORGANIZATION_NAME_LOWER "/" PROJECT_NAME_LOWER ), + m_binaryDirectory( m_prefixDirectory.absolutePath() + "/bin" ), + m_configDirectory( m_prefixDirectory.absolutePath() + "/etc" ), + m_cacheDirectory( m_prefixDirectory.absolutePath() + "/var" ), + m_tempDirectory( QString::fromLocal8Bit( qgetenv( qPrintable( "TMPDIR" ) ) ) ), + m_dataDirectory( m_prefixDirectory.absolutePath() + "/share" ), + m_homeDirectory( QString::fromLocal8Bit( qgetenv( qPrintable( "HOME" ) ) ) ), + m_executableFile( m_binaryDirectory.absolutePath() + "/" + PROJECT_NAME_LOWER ), + m_configFile( m_configDirectory.absolutePath() + "/" + PROJECT_NAME_LOWER + ".conf" ) { - return( i ); + if ( m_tempDirectory.isEmpty() || ( m_tempDirectory.path() == "." ) ) + { + m_tempDirectory = _PATH_TMP; + } } + + +QDir Paths::prefixDirectory() const +{ + return( m_prefixDirectory ); +} + + +void Paths::setPrefixDirectory( const QString& prefixDirectory ) +{ + m_prefixDirectory = prefixDirectory; +} + + +QDir Paths::binaryDirectory() const +{ + return( m_binaryDirectory ); +} + + +void Paths::setBinaryDirectory( const QString& binaryDirectory ) +{ + m_binaryDirectory = binaryDirectory; +} + + +QDir Paths::configDirectory() const +{ + return( m_configDirectory ); +} + + +void Paths::setConfigDirectory( const QString& configDirectory ) +{ + m_configDirectory = configDirectory; +} + + +QDir Paths::cacheDirectory() const +{ + return( m_cacheDirectory ); +} + + +void Paths::setCacheDirectory( const QString& cacheDirectory ) +{ + m_cacheDirectory = cacheDirectory; +} + + +QDir Paths::tempDirectory() const +{ + return( m_tempDirectory ); +} + + +void Paths::setTempDirectory( const QString& tempDirectory ) +{ + m_tempDirectory = tempDirectory; +} + + +QDir Paths::dataDirectory() const +{ + return( m_dataDirectory ); +} + + +void Paths::setDataDirectory( const QString& dataDirectory ) +{ + m_dataDirectory = dataDirectory; +} + + +QFileInfo Paths::executableFile() const +{ + return( m_executableFile ); +} + + +void Paths::setExecutableFile( const QString& executableFile ) +{ + m_executableFile = executableFile; +} + + +QFileInfo Paths::configFile() const +{ + return( m_configFile ); +} + + +void Paths::setConfigFile( const QString& configFile ) +{ + m_configFile = configFile; +} + + +bool Paths::updatePaths() +{ + char* path = nullptr; + int length, dirname_length; + + length = wai_getExecutablePath( nullptr, 0, &dirname_length ); + if ( length > 0 ) + { + path = static_cast< char* >( malloc( static_cast< size_t >( length ) + 1 ) ); + if ( !path ) + { + return( false ); + } + wai_getExecutablePath( path, length, &dirname_length ); + path[length] = '\0'; + } + else + { + return( false ); + } + + m_executableFile = QFile( path ); + path[dirname_length] = '\0'; + m_binaryDirectory = path; + free( path ); + + if ( m_binaryDirectory.absolutePath().endsWith( "/bin" ) ) + { + m_prefixDirectory = m_binaryDirectory.absolutePath().remove( QRegExp( "/bin$" ) ); + m_configDirectory = m_prefixDirectory.absolutePath() + "/etc"; + m_cacheDirectory = m_prefixDirectory.absolutePath() + "/var"; + m_dataDirectory = m_prefixDirectory.absolutePath() + "/share"; + m_configFile = m_configDirectory.absolutePath() + "/" + PROJECT_NAME_LOWER + ".conf"; + } + + if ( m_prefixDirectory.absolutePath().startsWith( "/opt" ) || + m_prefixDirectory.absolutePath().startsWith( "/usr" ) ) + { + QString dataDirectory = QString::fromLocal8Bit( qgetenv( qPrintable( "XDG_DATA_HOME" ) ) ); + if ( dataDirectory.isEmpty() ) + { + dataDirectory = m_homeDirectory.absolutePath() + ".local/share/"; + } + m_dataDirectory = dataDirectory + ORGANIZATION_NAME_LOWER + "/" + PROJECT_NAME_LOWER; + + QString configDirectory = QString::fromLocal8Bit( qgetenv( qPrintable( "XDG_CONFIG_HOME" ) ) ); + if ( configDirectory.isEmpty() ) + { + configDirectory = m_homeDirectory.absolutePath() + ".config/"; + } + m_configDirectory = configDirectory + ORGANIZATION_NAME_LOWER + "/" + PROJECT_NAME_LOWER; + + QString cacheDirectory = QString::fromLocal8Bit( qgetenv( qPrintable( "XDG_CACHE_HOME" ) ) ); + if ( cacheDirectory.isEmpty() ) + { + cacheDirectory = m_homeDirectory.absolutePath() + ".cache/"; + } + m_cacheDirectory = cacheDirectory + ORGANIZATION_NAME_LOWER + "/" + PROJECT_NAME_LOWER; + } + + return( true ); +} // Paths::updatePaths + + +bool Paths::makePaths() +{ + bool status = true; + + if ( !m_dataDirectory.mkpath( m_dataDirectory.absolutePath() ) ) { status = false; } + if ( !m_configDirectory.mkpath( m_configDirectory.absolutePath() ) ) { status = false; } + if ( !m_cacheDirectory.mkpath( m_cacheDirectory.absolutePath() ) ) { status = false; } + return( status ); +} + + +QString Paths::findConfigFile( const QString& defaultConfigFile ) +{ + if ( QFileInfo( defaultConfigFile ).isReadable() ) + { + return( defaultConfigFile ); + } + + auto fileName = QString::fromLocal8Bit( qgetenv( qPrintable( PROJECT_NAME_UPPER "_CONFIG" ) ) ); + if ( QFileInfo( fileName ).isReadable() ) + { + return( fileName ); + } + + if ( QFileInfo( m_configFile ).isReadable() ) + { + return( m_configFile.absoluteFilePath() ); + } + + return( QString() ); +} + +} // namespace filesystem + +} // namespace myx diff --git a/src/filesystem/paths.hpp b/src/filesystem/paths.hpp index fdf35f9..71f9b65 100644 --- a/src/filesystem/paths.hpp +++ b/src/filesystem/paths.hpp @@ -1,13 +1,49 @@ #ifndef MYX_FILESYSTEM_PATHS_HPP_ #define MYX_FILESYSTEM_PATHS_HPP_ -#include +#include +#include +#include namespace myx { namespace filesystem { -int32_t init( int32_t i ); +class Paths +{ + QDir m_prefixDirectory; + QDir m_binaryDirectory; + QDir m_configDirectory; + QDir m_cacheDirectory; + QDir m_tempDirectory; + QDir m_dataDirectory; + QDir m_homeDirectory; + QFileInfo m_executableFile; + QFileInfo m_configFile; + +public: + Paths(); + QDir prefixDirectory() const; + void setPrefixDirectory( const QString& prefixDirectory ); + QDir binaryDirectory() const; + void setBinaryDirectory( const QString& binaryDirectory ); + QDir configDirectory() const; + void setConfigDirectory( const QString& configDirectory ); + QDir cacheDirectory() const; + void setCacheDirectory( const QString& cacheDirectory ); + QDir tempDirectory() const; + void setTempDirectory( const QString& tempDirectory ); + QDir dataDirectory() const; + void setDataDirectory( const QString& dataDirectory ); + QFileInfo executableFile() const; + void setExecutableFile( const QString& executableFile ); + QFileInfo configFile() const; + void setConfigFile( const QString& configFile ); + + bool updatePaths(); + bool makePaths(); + QString findConfigFile( const QString& defaultConfigFile = "" ); +}; // class Paths } // namespace filesystem diff --git a/src/log/CMakeLists.txt b/src/log/CMakeLists.txt index 800f7e8..8d1e0c0 100644 --- a/src/log/CMakeLists.txt +++ b/src/log/CMakeLists.txt @@ -39,6 +39,6 @@ if(BUILD_SHARED_LIBS) endif() install(FILES ${current_target_headers} COMPONENT headers - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${current_target}) + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/${current_target}) install(FILES ${CMAKE_BINARY_DIR}/${current_target}.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) diff --git a/thirdparty/whereami/README.md b/thirdparty/whereami/README.md new file mode 100644 index 0000000..98c7a3b --- /dev/null +++ b/thirdparty/whereami/README.md @@ -0,0 +1,132 @@ +# Where Am I? + +A drop-in two files library to locate the current executable and the current +module on the file system. + +Supported platforms: + +- Windows +- Linux +- Mac +- iOS +- Android +- QNX Neutrino +- FreeBSD +- NetBSD +- DragonFly BSD +- SunOS + +Just drop `whereami.h` and `whereami.c` into your build and get started. (see +also [customizing compilation]) + +[customizing compilation]: #customizing-compilation + +-------------------------------------------------------------------------------- + +## Usage + +- `wai_getExecutablePath()` returns the path of the enclosing executable +- `wai_getModulePath()` returns the path of the enclosing module + +Example usage: + +- first call `int length = wai_getExecutablePath(NULL, 0, NULL);` to retrieve + the length of the path +- allocate the destination buffer with `path = (char*)malloc(length + 1);` +- call `wai_getExecutablePath(path, length, &dirname_length)` again to retrieve + the path +- add a terminal `NUL` character with `path[length] = '\0';` + +Here is the output of the example: + + $ make -j -C _gnu-make + $ cp ./bin/mac-x86_64/library.dylib /tmp/ + $ ./bin/mac-x86_64/executable --load-library=/tmp/library.dylib + + executable path: /Users/gregory/Projects/whereami/bin/mac-x86_64/executable + dirname: /Users/gregory/Projects/whereami/bin/mac-x86_64 + basename: executable + module path: /Users/gregory/Projects/whereami/bin/mac-x86_64/executable + dirname: /Users/gregory/Projects/whereami/bin/mac-x86_64 + basename: executable + + library loaded + executable path: /Users/gregory/Projects/whereami/bin/mac-x86_64/executable + dirname: /Users/gregory/Projects/whereami/bin/mac-x86_64 + basename: executable + module path: /private/tmp/library.dylib + dirname: /private/tmp + basename: library.dylib + library unloaded + +-------------------------------------------------------------------------------- + +## Customizing compilation + +You can customize the library's behavior by defining the following macros: + +- `WAI_FUNCSPEC` +- `WAI_PREFIX` +- `WAI_MALLOC` +- `WAI_REALLOC` +- `WAI_FREE` + +## Compiling for Windows + +There is a Visual Studio 2015 solution in the `_win-vs14/` folder. + +## Compiling for Linux or Mac + +There is a GNU Make 3.81 `MakeFile` in the `_gnu-make/` folder: + + $ make -j -C _gnu-make/ + +## Compiling for Mac + +See above if you want to compile from command line. Otherwise there is an Xcode +project located in the `_mac-xcode/` folder. + +## Compiling for iOS + +There is an Xcode project located in the `_ios-xcode/` folder. + +If you prefer compiling from command line and deploying to a jailbroken device +through SSH, use: + + $ make -j -C _gnu-make/ binsubdir=ios CC="$(xcrun --sdk iphoneos --find clang) -isysroot $(xcrun --sdk iphoneos --show-sdk-path) -arch armv7 -arch armv7s -arch arm64" postbuild="codesign -s 'iPhone Developer'" + +## Compiling for Android + +You will have to install the Android NDK, and point the `$NDK_ROOT` environment +variable to the NDK path: e.g. `export NDK_ROOT=/opt/android-ndk` (without a +trailing `/` character). + +Next, the easy way is to make a standalone Android toolchain with the following +command: + + $ $NDK_ROOT/build/tools/make_standalone_toolchain.py --arch=arm --install-dir=/tmp/android-toolchain + +Now you can compile the example by running: + + $ make -j -C _gnu-make/ libsuffix=.so binsubdir=android CC=/tmp/android-toolchain/bin/arm-linux-androideabi-gcc CXX=/tmp/android-toolchain/bin/arm-linux-androideabi-g++ + +Depending on whether you compile for an Android version that requires PIE +executables, add `CFLAGS='-fpie' CXXFLAGS='-fpie' LDFLAGS='-pie'` accordingly. + +Loading page aligned library straight from APKs is supported. To test, use the +following: + + $ zip -Z store app bin/android/library.so + $ zipalign -v -f -p 4 ./app.zip ./app.apk + +Then copy `bin/android/executable` and `app.apk` to your Android device and +there launch: + + $ ./executable --load-library=app.apk!/bin/android/library.so + +-------------------------------------------------------------------------------- + +If you find this library useful and decide to use it in your own projects please +drop me a line [@gpakosz]. + +[@gpakosz]: https://twitter.com/gpakosz diff --git a/thirdparty/whereami/whereami.c b/thirdparty/whereami/whereami.c new file mode 100644 index 0000000..1ba83a6 --- /dev/null +++ b/thirdparty/whereami/whereami.c @@ -0,0 +1,679 @@ +// (‑●‑●)> dual licensed under the WTFPL v2 and MIT licenses +// without any warranty. +// by Gregory Pakosz (@gpakosz) +// https://github.com/gpakosz/whereami + +// in case you want to #include "whereami.c" in a larger compilation unit +#if !defined(WHEREAMI_H) +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#if !defined(WAI_MALLOC) || !defined(WAI_FREE) || !defined(WAI_REALLOC) +#include +#endif + +#if !defined(WAI_MALLOC) +#define WAI_MALLOC(size) malloc(size) +#endif + +#if !defined(WAI_FREE) +#define WAI_FREE(p) free(p) +#endif + +#if !defined(WAI_REALLOC) +#define WAI_REALLOC(p, size) realloc(p, size) +#endif + +#ifndef WAI_NOINLINE +#if defined(_MSC_VER) +#define WAI_NOINLINE __declspec(noinline) +#elif defined(__GNUC__) +#define WAI_NOINLINE __attribute__((noinline)) +#else +#error unsupported compiler +#endif +#endif + +#if defined(_MSC_VER) +#define WAI_RETURN_ADDRESS() _ReturnAddress() +#elif defined(__GNUC__) +#define WAI_RETURN_ADDRESS() __builtin_extract_return_addr(__builtin_return_address(0)) +#else +#error unsupported compiler +#endif + +#if defined(_WIN32) + +#define WIN32_LEAN_AND_MEAN +#if defined(_MSC_VER) +#pragma warning(push, 3) +#endif +#include +#include +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +static int WAI_PREFIX(getModulePath_)(HMODULE module, char* out, int capacity, int* dirname_length) +{ + wchar_t buffer1[MAX_PATH]; + wchar_t buffer2[MAX_PATH]; + wchar_t* path = NULL; + int length = -1; + + for (;;) + { + DWORD size; + int length_, length__; + + size = GetModuleFileNameW(module, buffer1, sizeof(buffer1) / sizeof(buffer1[0])); + + if (size == 0) + break; + else if (size == (DWORD)(sizeof(buffer1) / sizeof(buffer1[0]))) + { + DWORD size_ = size; + do + { + wchar_t* path_; + + path_ = (wchar_t*)WAI_REALLOC(path, sizeof(wchar_t) * size_ * 2); + if (!path_) + break; + size_ *= 2; + path = path_; + size = GetModuleFileNameW(module, path, size_); + } + while (size == size_); + + if (size == size_) + break; + } + else + path = buffer1; + + if (!_wfullpath(buffer2, path, MAX_PATH)) + break; + length_ = (int)wcslen(buffer2); + length__ = WideCharToMultiByte(CP_UTF8, 0, buffer2, length_ , out, capacity, NULL, NULL); + + if (length__ == 0) + length__ = WideCharToMultiByte(CP_UTF8, 0, buffer2, length_, NULL, 0, NULL, NULL); + if (length__ == 0) + break; + + if (length__ <= capacity && dirname_length) + { + int i; + + for (i = length__ - 1; i >= 0; --i) + { + if (out[i] == '\\') + { + *dirname_length = i; + break; + } + } + } + + length = length__; + + break; + } + + if (path != buffer1) + WAI_FREE(path); + + return length; +} + +WAI_NOINLINE WAI_FUNCSPEC +int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) +{ + return WAI_PREFIX(getModulePath_)(NULL, out, capacity, dirname_length); +} + +WAI_NOINLINE WAI_FUNCSPEC +int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) +{ + HMODULE module; + int length = -1; + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable: 4054) +#endif + if (GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCTSTR)WAI_RETURN_ADDRESS(), &module)) +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + { + length = WAI_PREFIX(getModulePath_)(module, out, capacity, dirname_length); + } + + return length; +} + +#elif defined(__linux__) || defined(__CYGWIN__) || defined(__sun) || defined(WAI_USE_PROC_SELF_EXE) + +#include +#include +#include +#if defined(__linux__) +#include +#else +#include +#endif +#ifndef __STDC_FORMAT_MACROS +#define __STDC_FORMAT_MACROS +#endif +#include + +#if !defined(WAI_PROC_SELF_EXE) +#if defined(__sun) +#define WAI_PROC_SELF_EXE "/proc/self/path/a.out" +#else +#define WAI_PROC_SELF_EXE "/proc/self/exe" +#endif +#endif + +WAI_FUNCSPEC +int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) +{ + char buffer[PATH_MAX]; + char* resolved = NULL; + int length = -1; + + for (;;) + { + resolved = realpath(WAI_PROC_SELF_EXE, buffer); + if (!resolved) + break; + + length = (int)strlen(resolved); + if (length <= capacity) + { + memcpy(out, resolved, (size_t)length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + + break; + } + + return length; +} + +#if !defined(WAI_PROC_SELF_MAPS_RETRY) +#define WAI_PROC_SELF_MAPS_RETRY 5 +#endif + +#if !defined(WAI_PROC_SELF_MAPS) +#if defined(__sun) +#define WAI_PROC_SELF_MAPS "/proc/self/map" +#else +#define WAI_PROC_SELF_MAPS "/proc/self/maps" +#endif +#endif + +#if defined(__ANDROID__) || defined(ANDROID) +#include +#include +#include +#endif + +WAI_NOINLINE WAI_FUNCSPEC +int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) +{ + int length = -1; + FILE* maps = NULL; + + for (int r = 0; r < WAI_PROC_SELF_MAPS_RETRY; ++r) + { + maps = fopen(WAI_PROC_SELF_MAPS, "r"); + if (!maps) + break; + + for (;;) + { + char buffer[PATH_MAX < 1024 ? 1024 : PATH_MAX]; + uint64_t low, high; + char perms[5]; + uint64_t offset; + uint32_t major, minor; + char path[PATH_MAX]; + uint32_t inode; + + if (!fgets(buffer, sizeof(buffer), maps)) + break; + + if (sscanf(buffer, "%" PRIx64 "-%" PRIx64 " %s %" PRIx64 " %x:%x %u %s\n", &low, &high, perms, &offset, &major, &minor, &inode, path) == 8) + { + uint64_t addr = (uintptr_t)WAI_RETURN_ADDRESS(); + if (low <= addr && addr <= high) + { + char* resolved; + + resolved = realpath(path, buffer); + if (!resolved) + break; + + length = (int)strlen(resolved); +#if defined(__ANDROID__) || defined(ANDROID) + if (length > 4 + &&buffer[length - 1] == 'k' + &&buffer[length - 2] == 'p' + &&buffer[length - 3] == 'a' + &&buffer[length - 4] == '.') + { + int fd = open(path, O_RDONLY); + char* begin; + char* p; + + begin = (char*)mmap(0, offset, PROT_READ, MAP_SHARED, fd, 0); + p = begin + offset; + + while (p >= begin) // scan backwards + { + if (*((uint32_t*)p) == 0x04034b50UL) // local file header found + { + uint16_t length_ = *((uint16_t*)(p + 26)); + + if (length + 2 + length_ < (int)sizeof(buffer)) + { + memcpy(&buffer[length], "!/", 2); + memcpy(&buffer[length + 2], p + 30, length_); + length += 2 + length_; + } + + break; + } + + p -= 4; + } + + munmap(begin, offset); + close(fd); + } +#endif + if (length <= capacity) + { + memcpy(out, resolved, (size_t)length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + + break; + } + } + } + + fclose(maps); + maps = NULL; + + if (length != -1) + break; + } + + if (maps) + fclose(maps); + + return length; +} + +#elif defined(__APPLE__) + +#define _DARWIN_BETTER_REALPATH +#include +#include +#include +#include +#include + +WAI_FUNCSPEC +int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) +{ + char buffer1[PATH_MAX]; + char buffer2[PATH_MAX]; + char* path = buffer1; + char* resolved = NULL; + int length = -1; + + for (;;) + { + uint32_t size = (uint32_t)sizeof(buffer1); + if (_NSGetExecutablePath(path, &size) == -1) + { + path = (char*)WAI_MALLOC(size); + if (!_NSGetExecutablePath(path, &size)) + break; + } + + resolved = realpath(path, buffer2); + if (!resolved) + break; + + length = (int)strlen(resolved); + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + + break; + } + + if (path != buffer1) + WAI_FREE(path); + + return length; +} + +WAI_NOINLINE WAI_FUNCSPEC +int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) +{ + char buffer[PATH_MAX]; + char* resolved = NULL; + int length = -1; + + for(;;) + { + Dl_info info; + + if (dladdr(WAI_RETURN_ADDRESS(), &info)) + { + resolved = realpath(info.dli_fname, buffer); + if (!resolved) + break; + + length = (int)strlen(resolved); + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + } + + break; + } + + return length; +} + +#elif defined(__QNXNTO__) + +#include +#include +#include +#include +#include + +#if !defined(WAI_PROC_SELF_EXE) +#define WAI_PROC_SELF_EXE "/proc/self/exefile" +#endif + +WAI_FUNCSPEC +int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) +{ + char buffer1[PATH_MAX]; + char buffer2[PATH_MAX]; + char* resolved = NULL; + FILE* self_exe = NULL; + int length = -1; + + for (;;) + { + self_exe = fopen(WAI_PROC_SELF_EXE, "r"); + if (!self_exe) + break; + + if (!fgets(buffer1, sizeof(buffer1), self_exe)) + break; + + resolved = realpath(buffer1, buffer2); + if (!resolved) + break; + + length = (int)strlen(resolved); + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + + break; + } + + fclose(self_exe); + + return length; +} + +WAI_FUNCSPEC +int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) +{ + char buffer[PATH_MAX]; + char* resolved = NULL; + int length = -1; + + for(;;) + { + Dl_info info; + + if (dladdr(WAI_RETURN_ADDRESS(), &info)) + { + resolved = realpath(info.dli_fname, buffer); + if (!resolved) + break; + + length = (int)strlen(resolved); + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + } + + break; + } + + return length; +} + +#elif defined(__DragonFly__) || defined(__FreeBSD__) || \ + defined(__FreeBSD_kernel__) || defined(__NetBSD__) + +#include +#include +#include +#include +#include +#include + +WAI_FUNCSPEC +int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) +{ + char buffer1[PATH_MAX]; + char buffer2[PATH_MAX]; + char* path = buffer1; + char* resolved = NULL; + int length = -1; + + for (;;) + { +#if defined(__NetBSD__) + int mib[4] = { CTL_KERN, KERN_PROC_ARGS, -1, KERN_PROC_PATHNAME }; +#else + int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 }; +#endif + size_t size = sizeof(buffer1); + + if (sysctl(mib, (u_int)(sizeof(mib) / sizeof(mib[0])), path, &size, NULL, 0) != 0) + break; + + resolved = realpath(path, buffer2); + if (!resolved) + break; + + length = (int)strlen(resolved); + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + + break; + } + + if (path != buffer1) + WAI_FREE(path); + + return length; +} + +WAI_NOINLINE WAI_FUNCSPEC +int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length) +{ + char buffer[PATH_MAX]; + char* resolved = NULL; + int length = -1; + + for(;;) + { + Dl_info info; + + if (dladdr(WAI_RETURN_ADDRESS(), &info)) + { + resolved = realpath(info.dli_fname, buffer); + if (!resolved) + break; + + length = (int)strlen(resolved); + if (length <= capacity) + { + memcpy(out, resolved, length); + + if (dirname_length) + { + int i; + + for (i = length - 1; i >= 0; --i) + { + if (out[i] == '/') + { + *dirname_length = i; + break; + } + } + } + } + } + + break; + } + + return length; +} + +#else + +#error unsupported platform + +#endif + +#ifdef __cplusplus +} +#endif diff --git a/thirdparty/whereami/whereami.h b/thirdparty/whereami/whereami.h new file mode 100644 index 0000000..670db54 --- /dev/null +++ b/thirdparty/whereami/whereami.h @@ -0,0 +1,67 @@ +// (‑●‑●)> dual licensed under the WTFPL v2 and MIT licenses +// without any warranty. +// by Gregory Pakosz (@gpakosz) +// https://github.com/gpakosz/whereami + +#ifndef WHEREAMI_H +#define WHEREAMI_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef WAI_FUNCSPEC + #define WAI_FUNCSPEC +#endif +#ifndef WAI_PREFIX +#define WAI_PREFIX(function) wai_##function +#endif + +/** + * Returns the path to the current executable. + * + * Usage: + * - first call `int length = wai_getExecutablePath(NULL, 0, NULL);` to + * retrieve the length of the path + * - allocate the destination buffer with `path = (char*)malloc(length + 1);` + * - call `wai_getExecutablePath(path, length, NULL)` again to retrieve the + * path + * - add a terminal NUL character with `path[length] = '\0';` + * + * @param out destination buffer, optional + * @param capacity destination buffer capacity + * @param dirname_length optional recipient for the length of the dirname part + * of the path. + * + * @return the length of the executable path on success (without a terminal NUL + * character), otherwise `-1` + */ +WAI_FUNCSPEC +int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length); + +/** + * Returns the path to the current module + * + * Usage: + * - first call `int length = wai_getModulePath(NULL, 0, NULL);` to retrieve + * the length of the path + * - allocate the destination buffer with `path = (char*)malloc(length + 1);` + * - call `wai_getModulePath(path, length, NULL)` again to retrieve the path + * - add a terminal NUL character with `path[length] = '\0';` + * + * @param out destination buffer, optional + * @param capacity destination buffer capacity + * @param dirname_length optional recipient for the length of the dirname part + * of the path. + * + * @return the length of the module path on success (without a terminal NUL + * character), otherwise `-1` + */ +WAI_FUNCSPEC +int WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length); + +#ifdef __cplusplus +} +#endif + +#endif // #ifndef WHEREAMI_H