#ifndef __PROXYSQL_UTILS_H #define __PROXYSQL_UTILS_H #include #include #include #include #include #include #include #include #include #include #include #include #include "../deps/json/json.hpp" #ifndef ProxySQL_Checksum_Value_LENGTH #define ProxySQL_Checksum_Value_LENGTH 20 #endif #ifndef ETIME // ETIME is not defined on FreeBSD // ETIME is used internaly to report API timer expired // replace with ETIMEDOUT as closest alternative #define ETIME ETIMEDOUT #endif #if defined(__APPLE__) using std::conjunction; #elif defined(CXX17) template struct conjunction : std::true_type { }; template struct std::conjunction : B1 { }; template struct std::conjunction : std::conditional, B1>::type {}; #else template struct conjunction : std::true_type { }; template struct conjunction : B1 { }; template struct conjunction : std::conditional, B1>::type {}; #endif // CXX17 /** * @brief Stores the result of formatting the first parameter with the provided * arguments, into the std::string reference provided in the second parameter. * * @param str The string to be formatted. * @param result A std::string reference in which store the formatted result. * @param args The additional arguments to be formatted into the string. * @return int In case of success 0 is returned, otherwise, the formatting error provided * by 'snprintf' is provided. */ template< typename... Args, #ifdef CXX17 typename std::enable_if...>::value,int>::type = 0 #else typename std::enable_if...>::value,int>::type = 0 #endif // CXX17 > int string_format(const std::string& str, std::string& result, Args... args) { int size = snprintf(nullptr, 0, str.c_str(), args...); if (size <= 0) { return size; } else { size += 1; std::unique_ptr buf(new char[size]); size = snprintf(buf.get(), size, str.c_str(), args...); result = std::string(buf.get(), buf.get() + size); } return size; } /** * @brief Output struct of 'cstr_format' functions family. */ struct cfmt_t { // @brief If negative, the error returned from 'snprintf' while formatting. Otherwise the number of bytes // copied into the resulting formatted string. int size; // @brief In case of success the resulting formatted string, empty otherwise. std::string str; }; /** * @brief Formats the provided string literal with the extra variadic arguments. * @details This is an improved version on 'string_format' function. When used against an string literal, * allows the compiler to issue the proper warnings in case the format parameters are ill-formed. * @param fmt The string literal to be formatted with variadic arguments. * @param ... The variadic arguments to use for formatting. * @return An 'cfmt_t' holding the number of bytes copied to the resulting string and the formatted string * itself. In case of error the 'size' field will hold 'snprintf' returned error and 'str' will be empty. */ __attribute__((__format__ (__printf__, 1, 2))) cfmt_t cstr_format(const char* fmt, ...); /** * @brief Formats the provided string literal with the extra variadic arguments, and place the formatted * string either in the returned 'cfmt_t::string' or in the supplied buffer. * @details This is an improved version on 'string_format' function. When used against an string literal, * allows the compiler to issue the proper warnings in case the format parameters are ill-formed. * @param out_buf The output buffer in which to place the resulting formatted string in case it fits. * @param fmt The string literal to be formatted with variadic arguments. * @param ... The variadic arguments to use for formatting. * @return On success, an 'cfmt_t' holding the number of bytes copied to the resulting string, in case this * result fits in the provided buffer, this buffer is directly written and the returned 'cfmt_t::str' will * be empty. In case of error the 'size' field will hold 'snprintf' returned error and 'str' will be empty. */ template __attribute__((__format__ (__printf__, 2, 3))) cfmt_t cstr_format(char (&out_buf)[N], const char* fmt, ...) { va_list args; va_start(args, fmt); int size = vsnprintf(nullptr, 0, fmt, args); va_end(args); if (size <= 0) { return { size, {} }; } else { size += 1; if (size <= N) { va_start(args, fmt); size = vsnprintf(out_buf, size, fmt, args); va_end(args); return { size, {} }; } else { std::unique_ptr buf(new char[size]); va_start(args, fmt); size = vsnprintf(buf.get(), size, fmt, args); va_end(args); if (size <= 0) { return { size, {} }; } else { return { size, std::string(buf.get(), buf.get() + size) }; } } } } /** * @brief Simple struct that holds the 'timeout options' for 'wexecvp'. */ struct to_opts_t { /** * @brief Timeout for the script execution to be completed, in case of being * exceeded, the script will be terminated. */ unsigned int timeout_us; /** * @brief Timeout used for 'poll()' non blocking calls. */ suseconds_t poll_to_us; /** * @brief The duration of the sleeps between the checks being performed * on the child process waiting it to exit after being signaled to terminate, * before issuing 'SIGKILL'. */ unsigned int waitpid_delay_us; /** * @brief The timeout to be waited on the child process after being signaled * with 'SIGTERM' before being forcely terminated by 'SIGKILL'. */ unsigned int sigkill_to_us; }; /** * @brief Helper function to launch an executable in a child process through 'fork()' and * 'execvp()' and retrieve their 'stderr' and 'stdout' to the caller toguether with * the result of it's execution. * * @param file The file to be executed. * @param argv The arguments to be supplied to the file being executed. * @param opts Struct holding timeout options to consider for the launched process. * @param s_stdout Output string to hold the output 'stdout' of the child process. * @param s_stderr Output string to hold the output 'stderr' of the child process. * * @return 0 In case of success or one of the following error codes: * - '-1' in case any 'pipe()' creation failed. * - '-2' in case 'fork()' call failed. * - '-3' in case 'fcntl()' call failed. * - '-4' in case 'poll()' call failed. * - '-5' in case 'read()' from pipes failed with a non-expected error. * - 'ETIME' in case the executable has exceeded the timeout supplied in 'opts.timeout_us'. * In all this cases 'errno' is set to the error reported by the failing 'system call'. */ int wexecvp( const std::string& file, const std::vector& argv, const to_opts_t& opts, std::string& s_stdout, std::string& s_stderr ); /** * @brief Returns the current timestamp in microseconds. * @return The current timestamp in microseconds. */ uint64_t get_timestamp_us(); /** * @brief Converts a string to its hexadecimal representation. * @param str The input string to convert. * @return The hexadecimal representation of the input string. Empty for empty input. */ std::string hex(const std::string_view& str); /** * @brief Converts a hexadecimal string to its original string representation. * @param hex The hexadecimal string to convert. * @return The original string representation of the input hexadecimal string. Empty for empty input. */ std::string unhex(const std::string_view& hex); /** * @brief Helper function to replace all the occurrences in a string of a matching substring in favor * of another string. * * @param str The string which copy is going to be searched for matches to be replaced. * @param match The substring to be matched inside the string. * @param repl The string for which matches are going to be replaced. * * @return A string in which all the matches of 'match' within 'str' has been replaced by 'repl'. */ std::string replace_str(const std::string& str, const std::string& match, const std::string& repl); /** * @brief Split a string into a vector of strings with the provided 'char' delimiter. * @param s String to be split. * @param delimiter Delimiter to be used. * @return Vector with the string splits. Empty if none is found. */ std::vector split_str(const std::string& s, char delimiter); std::string generate_multi_rows_query(int rows, int params); /** * @brief Generates a random string of the length of the provider 'strSize' parameter. * @param strSize The size of the string to be generated. * @return A random string. */ std::string rand_str(std::size_t strSize); /** * @brief Helper function used to replace spaces and zeros by '0' char in the supplied checksum buffer. * @param checksum Input buffer containing the checksum. */ inline void replace_checksum_zeros(char* checksum) { for (int i=2; i<18; i++) { if (checksum[i]==' ' || checksum[i]==0) { checksum[i]='0'; } } } /** * @brief Generates a ProxySQL checksum as a string from the supplied integer hash. * @param hash The integer hash to be formated as a string. * @return String representation of the supplied hash. */ std::string get_checksum_from_hash(uint64_t hash); /** * @brief Closes all open file descriptors except stdin (0), stdout (1), stderr (2), and a specified exclusion list * * This function is typically called after fork() in the child process before exec() to ensure that * the child process does not inherit unintended file descriptors from the parent. * * CRITICAL: This function is designed to be called between fork() and execve() in the child process. * To avoid deadlocks in multi-threaded programs, it must NOT allocate on the heap. * * @param excludeFDs Vector of file descriptors to preserve (in addition to 0, 1, 2) * Passed by const reference to avoid heap allocation during copy. * * Thread-safety: Safe to call in child process after fork() before execve() */ void close_all_non_term_fd(const std::vector& excludeFDs); /** * @brief Returns the expected error for query 'SELECT $$'. * @param version The 'server_version' for which the error should match. * @return A pair of the shape '{err_code,err_msg}'. */ std::pair get_dollar_quote_error(const char* version); /** * @brief Extracts a nested JSON element from the supplied path. * @param j The JSON from which to extract the element. * @param p The path to trasverse to find the element. * @return Pointer to the element if found, 'nullptr' otherwise. */ const nlohmann::json* get_nested_elem(const nlohmann::json& j, const std::vector& p); /** * @brief Retrieves the value of a nested JSON element given a path of keys. * * @tparam T The type of the value to retrieve from the JSON element. * * @param j The JSON object to search through. * @param p A vector of strings representing the path to the nested element. * @param def_val The default value to return if the nested element is not found, * is null, or cannot be converted to type `T`. * * @return The value of the nested JSON element as type `T` if it exists and is * of the correct type, otherwise returns `def_val`. */ template T get_nested_elem_val(const nlohmann::json& j, const std::vector& p, const T def_val) { const nlohmann::json* next_step = get_nested_elem(j, p); try { if (next_step != nullptr && !next_step->is_null()) { return next_step->get(); } else { return def_val; } } catch (std::exception&) { return def_val; } } /** * @brief Helper type for freeing memory allocated by 'malloc' managed by smart pointers. */ struct free_deleter { void operator()(void* x) { free(x); } void operator()(const void* x) { free(const_cast(x)); } }; template using mf_unique_ptr = std::unique_ptr; template static inline void set_thread_name(const char(&name)[LEN], const bool en = true) { static_assert(LEN < 17, "Thread name must not exceed 16 characters"); if (en == false) { return; } #if defined(__linux__) || defined(__FreeBSD__) int rc; rc = pthread_setname_np(pthread_self(), name); assert(!rc); #endif } /** * @brief Gets the client address stored in 'client_addr' member as * an string if available. If member 'client_addr' is NULL, returns an * empty string. * * @return Either an string holding the string representation of internal * member 'client_addr', or empty string if this member is NULL. */ std::string get_client_addr(struct sockaddr* client_addr); /** * @brief Check if a port is available for binding * * Creates a temporary socket and attempts to bind to the specified port * to verify availability. The socket is closed immediately after the test. * Sets SO_REUSEADDR to allow rebinding to recently used ports. * * @param port_num Port number to check * @param port_free Output parameter - set to true if port is available, false if in use * @return int Error code (0 = success, -1 = setsockopt failed, -2 = invalid parameters) */ int check_port_availability(int port_num, bool* port_free); // Forward declaration for GloMTH wait helper class MySQL_Threads_Handler; extern MySQL_Threads_Handler *GloMTH; /** * @brief Wait for GloMTH to be initialized with a bounded timeout * * Waits up to 10 seconds for GloMTH initialization. * Returns true if GloMTH is initialized, false if timeout. * * @return bool true if GloMTH is ready, false otherwise */ static inline bool wait_for_glo_mth() { for (int i = 0; i < 200; ++i) { // ~10s total if (GloMTH) return true; usleep(50000); } return false; } #endif