diff --git a/flight_sql/flight_sql_connection.cc b/flight_sql/flight_sql_connection.cc index cfae4b34..d0b62888 100644 --- a/flight_sql/flight_sql_connection.cc +++ b/flight_sql/flight_sql_connection.cc @@ -114,7 +114,7 @@ inline std::string GetCerts() { #endif -const std::set BUILT_IN_PROPERTIES = { +const std::set BUILT_IN_PROPERTIES = { FlightSqlConnection::HOST, FlightSqlConnection::PORT, FlightSqlConnection::USER, diff --git a/flight_sql/flight_sql_driver.cc b/flight_sql/flight_sql_driver.cc index 8e731d3b..87d4f3a5 100644 --- a/flight_sql/flight_sql_driver.cc +++ b/flight_sql/flight_sql_driver.cc @@ -5,14 +5,41 @@ */ #include "flight_sql_connection.h" +#include "odbcabstraction/utils.h" +#include #include #include + +#define DEFAULT_MAXIMUM_FILE_SIZE 16777216 +#define CONFIG_FILE_NAME "arrow-odbc.ini" + namespace driver { namespace flight_sql { using odbcabstraction::Connection; using odbcabstraction::OdbcVersion; +using odbcabstraction::LogLevel; +using odbcabstraction::SPDLogger; + +namespace { + LogLevel ToLogLevel(int64_t level) { + switch (level) { + case 0: + return LogLevel::LogLevel_TRACE; + case 1: + return LogLevel::LogLevel_DEBUG; + case 2: + return LogLevel::LogLevel_INFO; + case 3: + return LogLevel::LogLevel_WARN; + case 4: + return LogLevel::LogLevel_ERROR; + default: + return LogLevel::LogLevel_OFF; + } + } +} FlightSqlDriver::FlightSqlDriver() : diagnostics_("Apache Arrow", "Flight SQL", OdbcVersion::V_3), @@ -31,5 +58,47 @@ odbcabstraction::Diagnostics &FlightSqlDriver::GetDiagnostics() { void FlightSqlDriver::SetVersion(std::string version) { version_ = std::move(version); } + +void FlightSqlDriver::RegisterLog() { + odbcabstraction::PropertyMap propertyMap; + driver::odbcabstraction::ReadConfigFile(propertyMap, CONFIG_FILE_NAME); + + auto log_enable_iterator = propertyMap.find(SPDLogger::LOG_ENABLED); + auto log_enabled = log_enable_iterator != propertyMap.end() ? + odbcabstraction::AsBool(log_enable_iterator->second) : false; + if (!log_enabled) { + return; + } + + auto log_path_iterator = propertyMap.find(SPDLogger::LOG_PATH); + auto log_path = + log_path_iterator != propertyMap.end() ? log_path_iterator->second : ""; + if (log_path.empty()) { + return; + } + + auto log_level_iterator = propertyMap.find(SPDLogger::LOG_LEVEL); + auto log_level = + ToLogLevel(log_level_iterator != propertyMap.end() ? std::stoi(log_level_iterator->second) : 1); + if (log_level == odbcabstraction::LogLevel_OFF) { + return; + } + + auto maximum_file_size_iterator = propertyMap.find(SPDLogger::MAXIMUM_FILE_SIZE); + auto maximum_file_size = maximum_file_size_iterator != propertyMap.end() ? + std::stoi(maximum_file_size_iterator->second) : DEFAULT_MAXIMUM_FILE_SIZE; + + auto maximum_file_quantity_iterator = propertyMap.find(SPDLogger::FILE_QUANTITY); + auto maximum_file_quantity = + maximum_file_quantity_iterator != propertyMap.end() ? std::stoi( + maximum_file_quantity_iterator->second) : 1; + + std::unique_ptr logger(new odbcabstraction::SPDLogger()); + + logger->init(maximum_file_quantity, maximum_file_size, + log_path, log_level); + odbcabstraction::Logger::SetInstance(std::move(logger)); +} + } // namespace flight_sql } // namespace driver diff --git a/flight_sql/include/flight_sql/flight_sql_driver.h b/flight_sql/include/flight_sql/flight_sql_driver.h index e55a7126..98845230 100644 --- a/flight_sql/include/flight_sql/flight_sql_driver.h +++ b/flight_sql/include/flight_sql/flight_sql_driver.h @@ -26,6 +26,8 @@ class FlightSqlDriver : public odbcabstraction::Driver { odbcabstraction::Diagnostics &GetDiagnostics() override; void SetVersion(std::string version) override; + + void RegisterLog() override; }; }; // namespace flight_sql diff --git a/odbcabstraction/CMakeLists.txt b/odbcabstraction/CMakeLists.txt index 0ffd493a..3ebf5570 100644 --- a/odbcabstraction/CMakeLists.txt +++ b/odbcabstraction/CMakeLists.txt @@ -7,12 +7,17 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON) include_directories(include) +# Ensure fmt is loaded as header only +add_compile_definitions(FMT_HEADER_ONLY) + add_library(odbcabstraction include/odbcabstraction/calendar_utils.h include/odbcabstraction/diagnostics.h include/odbcabstraction/error_codes.h include/odbcabstraction/exceptions.h + include/odbcabstraction/logger.h include/odbcabstraction/platform.h + include/odbcabstraction/spd_logger.h include/odbcabstraction/types.h include/odbcabstraction/utils.h include/odbcabstraction/odbc_impl/AttributeUtils.h @@ -32,7 +37,11 @@ add_library(odbcabstraction diagnostics.cc encoding.cc exceptions.cc + logger.cc + spd_logger.cc utils.cc + whereami.h + whereami.cc odbc_impl/ODBCConnection.cc odbc_impl/ODBCDescriptor.cc odbc_impl/ODBCEnvironment.cc @@ -45,4 +54,21 @@ set_target_properties(odbcabstraction LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/$/lib RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/$/lib ) -target_link_libraries(odbcabstraction) + +include(FetchContent) +FetchContent_Declare( + spdlog + URL https://github.com/gabime/spdlog/archive/76fb40d95455f249bd70824ecfcae7a8f0930fa3.zip + CONFIGURE_COMMAND "" + BUILD_COMMAND "" +) +FetchContent_GetProperties(spdlog) +if(NOT spdlog_POPULATED) + FetchContent_Populate(spdlog) +endif() + +add_library(spdlog INTERFACE) +target_include_directories(spdlog INTERFACE ${spdlog_SOURCE_DIR}/include) + +add_dependencies(odbcabstraction spdlog) +target_include_directories(odbcabstraction PUBLIC ${spdlog_SOURCE_DIR}/include) diff --git a/odbcabstraction/include/odbcabstraction/logger.h b/odbcabstraction/include/odbcabstraction/logger.h new file mode 100644 index 00000000..b085f2bb --- /dev/null +++ b/odbcabstraction/include/odbcabstraction/logger.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2020-2022 Dremio Corporation + * + * See "LICENSE" for license information. + */ + +#pragma once + +#include +#include + +#include + +#define __LAZY_LOG(LEVEL, ...) do { \ + driver::odbcabstraction::Logger *logger = driver::odbcabstraction::Logger::GetInstance(); \ + if (logger) { \ + logger->log(driver::odbcabstraction::LogLevel::LogLevel_##LEVEL, [&]() { \ + return fmt::format(__VA_ARGS__); \ + }); \ + } \ +} while(0) +#define LOG_DEBUG(...) __LAZY_LOG(DEBUG, __VA_ARGS__) +#define LOG_INFO(...) __LAZY_LOG(INFO, __VA_ARGS__) +#define LOG_ERROR(...) __LAZY_LOG(ERROR, __VA_ARGS__) +#define LOG_TRACE(...) __LAZY_LOG(TRACE, __VA_ARGS__) +#define LOG_WARN(...) __LAZY_LOG(WARN, __VA_ARGS__) + +namespace driver { +namespace odbcabstraction { + +enum LogLevel { + LogLevel_TRACE, + LogLevel_DEBUG, + LogLevel_INFO, + LogLevel_WARN, + LogLevel_ERROR, + LogLevel_OFF +}; + +class Logger { +protected: + Logger() = default; + +public: + static Logger *GetInstance(); + static void SetInstance(std::unique_ptr logger); + + virtual ~Logger() = default; + + virtual void log(LogLevel level, const std::function &build_message) = 0; +}; + +} // namespace odbcabstraction +} // namespace driver diff --git a/odbcabstraction/include/odbcabstraction/spd_logger.h b/odbcabstraction/include/odbcabstraction/spd_logger.h new file mode 100644 index 00000000..dfa633ed --- /dev/null +++ b/odbcabstraction/include/odbcabstraction/spd_logger.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2020-2022 Dremio Corporation + * + * See "LICENSE" for license information. + */ + +#pragma once + +#include "odbcabstraction/logger.h" + +#include +#include + +#include + +namespace driver { +namespace odbcabstraction { + +class SPDLogger : public Logger { +protected: + std::shared_ptr logger_; + +public: + static const std::string LOG_LEVEL; + static const std::string LOG_PATH; + static const std::string MAXIMUM_FILE_SIZE; + static const std::string FILE_QUANTITY; + static const std::string LOG_ENABLED; + + SPDLogger() = default; + ~SPDLogger(); + SPDLogger(SPDLogger &other) = delete; + + void operator=(const SPDLogger &) = delete; + void init(int64_t fileQuantity, int64_t maxFileSize, + const std::string &fileNamePrefix, LogLevel level); + + void log(LogLevel level, const std::function &build_message) override; +}; + +} // namespace odbcabstraction +} // namespace driver diff --git a/odbcabstraction/include/odbcabstraction/spi/connection.h b/odbcabstraction/include/odbcabstraction/spi/connection.h index 4c1c4bb2..9fafc44d 100644 --- a/odbcabstraction/include/odbcabstraction/spi/connection.h +++ b/odbcabstraction/include/odbcabstraction/spi/connection.h @@ -18,6 +18,17 @@ namespace driver { namespace odbcabstraction { +/// \brief Case insensitive comparator +struct CaseInsensitiveComparator + : std::binary_function { + bool operator()(const std::string &s1, const std::string &s2) const { + return boost::lexicographical_compare(s1, s2, boost::is_iless()); + } +}; + +// PropertyMap is case-insensitive for keys. +typedef std::map PropertyMap; + class Statement; /// \brief High-level representation of an ODBC connection. @@ -38,20 +49,9 @@ class Connection { PACKET_SIZE, // uint32_t - The Packet Size }; - /// \brief Case insensitive comparator - struct CaseInsensitiveComparator - : std::binary_function { - bool operator()(const std::string &s1, const std::string &s2) const { - return boost::lexicographical_compare(s1, s2, boost::is_iless()); - } - }; - typedef boost::variant Attribute; - typedef std::string Property; typedef boost::variant Info; - // ConnPropertyMap is case-insensitive for keys. - typedef std::map - ConnPropertyMap; + typedef PropertyMap ConnPropertyMap; /// \brief Establish the connection. /// \param properties[in] properties used to establish the connection. diff --git a/odbcabstraction/include/odbcabstraction/spi/driver.h b/odbcabstraction/include/odbcabstraction/spi/driver.h index 974704f7..e9e60b52 100644 --- a/odbcabstraction/include/odbcabstraction/spi/driver.h +++ b/odbcabstraction/include/odbcabstraction/spi/driver.h @@ -35,6 +35,9 @@ class Driver { /// \brief Sets the driver version. virtual void SetVersion(std::string version) = 0; + + /// \brief Register a log to be used by the system. + virtual void RegisterLog() = 0; }; } // namespace odbcabstraction diff --git a/odbcabstraction/include/odbcabstraction/utils.h b/odbcabstraction/include/odbcabstraction/utils.h index a5617ada..138a19bf 100644 --- a/odbcabstraction/include/odbcabstraction/utils.h +++ b/odbcabstraction/include/odbcabstraction/utils.h @@ -6,7 +6,9 @@ #pragma once +#include #include +#include #include namespace driver { @@ -36,5 +38,9 @@ boost::optional AsBool(const Connection::ConnPropertyMap& connPropertyMap, /// \exception std::out_of_range exception from \link std::stoi \endlink boost::optional AsInt32(int32_t min_value, const Connection::ConnPropertyMap& connPropertyMap, const std::string& property_name); + + +void ReadConfigFile(PropertyMap &properties, const std::string &configFileName); + } // namespace odbcabstraction } // namespace driver diff --git a/odbcabstraction/logger.cc b/odbcabstraction/logger.cc new file mode 100644 index 00000000..16ac682d --- /dev/null +++ b/odbcabstraction/logger.cc @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2020-2022 Dremio Corporation + * + * See "LICENSE" for license information. + */ + + +#include + +namespace driver { +namespace odbcabstraction { + +static std::unique_ptr odbc_logger_ = nullptr; + +Logger *Logger::GetInstance() { + return odbc_logger_.get(); +} + +void Logger::SetInstance(std::unique_ptrlogger) { + odbc_logger_ = std::move(logger); +} + +} +} diff --git a/odbcabstraction/spd_logger.cc b/odbcabstraction/spd_logger.cc new file mode 100644 index 00000000..6151dded --- /dev/null +++ b/odbcabstraction/spd_logger.cc @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2020-2022 Dremio Corporation + * + * See "LICENSE" for license information. + */ + +#include "odbcabstraction/spd_logger.h" + +#include "odbcabstraction/logger.h" + +#include +#include +#include + +#include +#include + +namespace driver { +namespace odbcabstraction { + +const std::string SPDLogger::LOG_LEVEL = "LogLevel"; +const std::string SPDLogger::LOG_PATH= "LogPath"; +const std::string SPDLogger::MAXIMUM_FILE_SIZE= "MaximumFileSize"; +const std::string SPDLogger::FILE_QUANTITY= "FileQuantity"; +const std::string SPDLogger::LOG_ENABLED= "LogEnabled"; + +namespace { +std::function shutdown_handler; +void signal_handler(int signal) { + shutdown_handler(signal); +} + +typedef void (*Handler)(int signum); + +Handler old_sigint_handler = SIG_IGN; +Handler old_sigsegv_handler = SIG_IGN; +Handler old_sigabrt_handler = SIG_IGN; +#ifdef SIGKILL +Handler old_sigkill_handler = SIG_IGN; +#endif + +Handler GetHandlerFromSignal(int signum) { + switch (signum) { + case(SIGINT): + return old_sigint_handler; + case(SIGSEGV): + return old_sigsegv_handler; + case(SIGABRT): + return old_sigabrt_handler; +#ifdef SIGKILL + case(SIGKILL): + return old_sigkill_handler; +#endif + } +} + +void SetSignalHandler(int signum) { + Handler old = signal(signum, SIG_IGN); + if (old != SIG_IGN) { + auto old_handler = GetHandlerFromSignal(signum); + old_handler = old; + } + signal(signum, signal_handler); +} + +void ResetSignalHandler(int signum) { + Handler actual_handler = signal(signum, SIG_IGN); + if (actual_handler == signal_handler) { + signal(signum, GetHandlerFromSignal(signum)); + } +} + + +inline spdlog::level::level_enum ToSpdLogLevel(LogLevel level) { + switch (level) { + case LogLevel_TRACE: + return spdlog::level::trace; + case LogLevel_DEBUG: + return spdlog::level::debug; + case LogLevel_INFO: + return spdlog::level::info; + case LogLevel_WARN: + return spdlog::level::warn; + case LogLevel_ERROR: + return spdlog::level::err; + default: + return spdlog::level::off; + } +} +} // namespace + +void SPDLogger::init(int64_t fileQuantity, int64_t maxFileSize, + const std::string &fileNamePrefix, LogLevel level) { + logger_ = spdlog::rotating_logger_mt( + "ODBC Logger", fileNamePrefix, maxFileSize, fileQuantity); + + logger_->set_level(ToSpdLogLevel(level)); + + if (level != LogLevel::LogLevel_OFF) { + SetSignalHandler(SIGINT); + SetSignalHandler(SIGSEGV); + SetSignalHandler(SIGABRT); +#ifdef SIGKILL + SetSignalHandler(SIGKILL); +#endif + shutdown_handler = [&](int signal) { + logger_->flush(); + spdlog::shutdown(); + auto handler = GetHandlerFromSignal(signal); + handler(signal); + }; + } +} + +void SPDLogger::log(LogLevel level, const std::function &build_message) { + auto level_set = logger_->level(); + spdlog::level::level_enum spdlog_level = ToSpdLogLevel(level); + if (level_set == spdlog::level::off || level_set > spdlog_level) { + return; + } + + const std::string &message = build_message(); + logger_->log(spdlog_level, message); +} + +SPDLogger::~SPDLogger() { + ResetSignalHandler(SIGINT); + ResetSignalHandler(SIGSEGV); + ResetSignalHandler(SIGABRT); +#ifdef SIGKILL + ResetSignalHandler(SIGKILL); +#endif +} + +} // namespace odbcabstraction +} // namespace driver diff --git a/odbcabstraction/utils.cc b/odbcabstraction/utils.cc index 8256cb23..23be030d 100644 --- a/odbcabstraction/utils.cc +++ b/odbcabstraction/utils.cc @@ -5,8 +5,15 @@ */ #include +#include "whereami.h" + +#include +#include #include +#include +#include +#include namespace driver { namespace odbcabstraction { @@ -45,5 +52,50 @@ boost::optional AsInt32(int32_t min_value, const Connection::ConnProper return boost::none; } +std::string GetModulePath() { + std::vector path; + int length, dirname_length; + length = wai_getModulePath(NULL, 0, &dirname_length); + + if (length != 0) { + path.resize(length); + wai_getModulePath(path.data(), length, &dirname_length); + } else { + throw DriverException("Could not find module path."); + } + + return std::string(path.begin(), path.begin() + dirname_length); +} + +void ReadConfigFile(PropertyMap &properties, const std::string &config_file_name) { + auto config_path = GetModulePath(); + + std::ifstream config_file; + auto config_file_path = config_path + "/" + config_file_name; + config_file.open(config_file_path); + + if (config_file.fail()) { + auto error_msg = "Arrow Flight SQL ODBC driver config file not found on \"" + config_file_path + "\""; + std::cerr << error_msg << std::endl; + + throw DriverException(error_msg); + } + + std::string temp_config; + + boost::char_separator separator("="); + while(config_file.good()) { + config_file >> temp_config; + boost::tokenizer> tokenizer(temp_config, separator); + + auto iterator = tokenizer.begin(); + + std::string key = *iterator; + std::string value = *++iterator; + + properties[key] = std::move(value); + } +} + } // namespace odbcabstraction } // namespace driver diff --git a/odbcabstraction/whereami.cc b/odbcabstraction/whereami.cc new file mode 100644 index 00000000..39324d16 --- /dev/null +++ b/odbcabstraction/whereami.cc @@ -0,0 +1,804 @@ +// (‑●‑●)> 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 "whereami.h" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(__linux__) || defined(__CYGWIN__) +#undef _DEFAULT_SOURCE +#define _DEFAULT_SOURCE +#elif defined(__APPLE__) +#undef _DARWIN_C_SOURCE +#define _DARWIN_C_SOURCE +#define _DARWIN_BETTER_REALPATH +#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) + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#if defined(_MSC_VER) +#pragma warning(push, 3) +#endif +#include +#include +#if defined(_MSC_VER) +#pragma warning(pop) +#endif +#include + +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; + bool ok; + + for (ok = false; !ok; ok = true) + { + 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__; + } + + if (path != buffer1) + WAI_FREE(path); + + return ok ? length : -1; +} + +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 +#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; + bool ok; + + for (ok = false; !ok; ok = true) + { + resolved = realpath(WAI_PROC_SELF_EXE, 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; + } + } + } + } + } + + return ok ? length : -1; +} + +#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 +#include + +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); + if (fd == -1) + { + length = -1; // retry + break; + } + + char* begin = (char*)mmap(0, offset, PROT_READ, MAP_SHARED, fd, 0); + if (begin == MAP_FAILED) + { + close(fd); + length = -1; // retry + break; + } + + char* p = begin + offset - 30; // minimum size of local file header + while (p >= begin) // scan backwards + { + if (*((uint32_t*)p) == 0x04034b50UL) // local file header signature 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; + } + + munmap(begin, offset); + close(fd); + } +#endif + 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(maps); + maps = NULL; + + if (length != -1) + break; + } + + return length; +} + +#elif defined(__APPLE__) + +#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; + bool ok; + + for (ok = false; !ok; ok = true) + { + 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; + } + } + } + } + } + + if (path != buffer1) + WAI_FREE(path); + + return ok ? length : -1; +} + +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 +#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; + bool ok; + + for (ok = false; !ok; ok = true) + { + 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; + } + } + } + } + } + + fclose(self_exe); + + return ok ? length : -1; +} + +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__) || defined(__OpenBSD__) + +#include +#include +#include +#include +#include +#include +#include + +#if defined(__OpenBSD__) + +#include + +WAI_FUNCSPEC +int WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length) +{ + char buffer1[4096]; + char buffer2[PATH_MAX]; + char buffer3[PATH_MAX]; + char** argv = (char**)buffer1; + char* resolved = NULL; + int length = -1; + bool ok; + + for (ok = false; !ok; ok = true) + { + int mib[4] = { CTL_KERN, KERN_PROC_ARGS, getpid(), KERN_PROC_ARGV }; + size_t size; + + if (sysctl(mib, 4, NULL, &size, NULL, 0) != 0) + break; + + if (size > sizeof(buffer1)) + { + argv = (char**)WAI_MALLOC(size); + if (!argv) + break; + } + + if (sysctl(mib, 4, argv, &size, NULL, 0) != 0) + break; + + if (strchr(argv[0], '/')) + { + resolved = realpath(argv[0], buffer2); + if (!resolved) + break; + } + else + { + const char* PATH = getenv("PATH"); + if (!PATH) + break; + + size_t argv0_length = strlen(argv[0]); + + const char* begin = PATH; + while (1) + { + const char* separator = strchr(begin, ':'); + const char* end = separator ? separator : begin + strlen(begin); + + if (end - begin > 0) + { + if (*(end -1) == '/') + --end; + + if (((end - begin) + 1 + argv0_length + 1) <= sizeof(buffer2)) + { + memcpy(buffer2, begin, end - begin); + buffer2[end - begin] = '/'; + memcpy(buffer2 + (end - begin) + 1, argv[0], argv0_length + 1); + + resolved = realpath(buffer2, buffer3); + if (resolved) + break; + } + } + + if (!separator) + break; + + begin = ++separator; + } + + 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; + } + } + } + } + } + + if (argv != (char**)buffer1) + WAI_FREE(argv); + + return ok ? length : -1; +} + +#else + +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; + bool ok; + + for (ok = false; !ok; ok = true) + { +#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, 4, 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; + } + } + } + } + } + + return ok ? length : -1; +} + +#endif + +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/odbcabstraction/whereami.h b/odbcabstraction/whereami.h new file mode 100644 index 00000000..ca62d674 --- /dev/null +++ b/odbcabstraction/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 diff --git a/vcpkg.json b/vcpkg.json index 88edaa56..519d6441 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -20,6 +20,7 @@ "protobuf", "zlib", "re2", + "spdlog", "grpc", "utf8proc", "zlib",