/* ** Copyright (C) 2001-2024 Zabbix SIA ** ** This program is free software: you can redistribute it and/or modify it under the terms of ** the GNU Affero General Public License as published by the Free Software Foundation, version 3. ** ** This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; ** without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ** See the GNU Affero General Public License for more details. ** ** You should have received a copy of the GNU Affero General Public License along with this program. ** If not, see . **/ #include "zbxdb.h" #include "zbxstr.h" #include "zbxtime.h" #include "zbxjson.h" #include "zbx_dbversion_constants.h" #if defined(HAVE_MYSQL) # include "mysql.h" # include "errmsg.h" # include "mysqld_error.h" #elif defined(HAVE_POSTGRESQL) # include #elif defined(HAVE_SQLITE3) # include #endif #if defined(HAVE_SQLITE3) # include "zbxmutexs.h" #endif struct zbx_db_result { #if defined(HAVE_MYSQL) MYSQL_RES *result; #elif defined(HAVE_POSTGRESQL) PGresult *pg_result; int row_num; int fld_num; int cursor; zbx_db_row_t values; #elif defined(HAVE_SQLITE3) int curow; char **data; int nrow; int ncolumn; zbx_db_row_t values; #endif }; static int txn_level = 0; /* transaction level, nested transactions are not supported */ static int txn_error = ZBX_DB_OK; /* failed transaction */ static int txn_end_error = ZBX_DB_OK; /* transaction result */ static char *last_db_strerror = NULL; /* last database error message */ static int config_log_slow_queries; static int db_auto_increment; #if defined(HAVE_MYSQL) static MYSQL *conn = NULL; static int mysql_err_cnt = 0; static zbx_uint32_t ZBX_MYSQL_SVERSION = ZBX_DBVERSION_UNDEFINED; static int ZBX_MARIADB_SFORK = OFF; static int txn_begin = 0; /* transaction begin statement is executed */ #elif defined(HAVE_POSTGRESQL) #define ZBX_PG_READ_ONLY "25006" #define ZBX_PG_UNIQUE_VIOLATION "23505" #define ZBX_PG_DEADLOCK "40P01" static PGconn *conn = NULL; static int ZBX_TSDB_VERSION = -1; static zbx_uint32_t ZBX_PG_SVERSION = ZBX_DBVERSION_UNDEFINED; char ZBX_PG_ESCAPE_BACKSLASH = 1; static int ZBX_TIMESCALE_COMPRESSION_AVAILABLE = OFF; static int ZBX_PG_READ_ONLY_RECOVERABLE; #elif defined(HAVE_SQLITE3) static sqlite3 *conn = NULL; static zbx_mutex_t sqlite_access = ZBX_MUTEX_NULL; #endif static zbx_err_codes_t last_db_errcode; static void zbx_db_errlog(zbx_err_codes_t zbx_errno, int db_errno, const char *db_error, const char *context) { char *s; last_db_errcode = zbx_errno; if (NULL != db_error) last_db_strerror = zbx_strdup(last_db_strerror, db_error); else last_db_strerror = zbx_strdup(last_db_strerror, ""); switch (zbx_errno) { case ERR_Z3001: s = zbx_dsprintf(NULL, "connection to database '%s' failed: [%d] %s", context, db_errno, last_db_strerror); break; case ERR_Z3002: s = zbx_dsprintf(NULL, "cannot create database '%s': [%d] %s", context, db_errno, last_db_strerror); break; case ERR_Z3003: s = zbx_strdup(NULL, "no connection to the database"); break; case ERR_Z3004: s = zbx_dsprintf(NULL, "cannot close database: [%d] %s", db_errno, last_db_strerror); break; case ERR_Z3005: s = zbx_dsprintf(NULL, "query failed: [%d] %s [%s]", db_errno, last_db_strerror, context); break; case ERR_Z3006: s = zbx_dsprintf(NULL, "fetch failed: [%d] %s", db_errno, last_db_strerror); break; case ERR_Z3007: s = zbx_dsprintf(NULL, "query failed: [%d] %s", db_errno, last_db_strerror); break; case ERR_Z3008: s = zbx_dsprintf(NULL, "query failed due to primary key constraint: [%d] %s", db_errno, last_db_strerror); break; case ERR_Z3009: s = zbx_dsprintf(NULL, "query failed due to read-only transaction: [%d] %s", db_errno, last_db_strerror); break; default: s = zbx_strdup(NULL, "unknown error"); } zabbix_log(LOG_LEVEL_ERR, "[Z%04d] %s", (int)zbx_errno, s); zbx_free(s); } /****************************************************************************** * * * Purpose: get last error set by database * * * * Return value: last database error message * * * ******************************************************************************/ const char *zbx_db_last_strerr(void) { return last_db_strerror; } /****************************************************************************** * * * Purpose: get last error code returned by database * * * * Return value: last database error code * * * ******************************************************************************/ zbx_err_codes_t zbx_db_last_errcode(void) { return last_db_errcode; } #ifdef HAVE_POSTGRESQL static void zbx_postgresql_error(char **error, const PGresult *pg_result) { char *result_error_msg; size_t error_alloc = 0, error_offset = 0; zbx_snprintf_alloc(error, &error_alloc, &error_offset, "%s", PQresStatus(PQresultStatus(pg_result))); result_error_msg = PQresultErrorMessage(pg_result); if ('\0' != *result_error_msg) zbx_snprintf_alloc(error, &error_alloc, &error_offset, ":%s", result_error_msg); } #endif /*HAVE_POSTGRESQL*/ __zbx_attr_format_printf(1, 2) static int zbx_db_execute_basic(const char *fmt, ...) { va_list args; int ret; va_start(args, fmt); ret = zbx_db_vexecute(fmt, args); va_end(args); return ret; } __zbx_attr_format_printf(1, 2) static zbx_db_result_t zbx_db_select_basic(const char *fmt, ...) { va_list args; zbx_db_result_t result; va_start(args, fmt); result = zbx_db_vselect(fmt, args); va_end(args); return result; } #if defined(HAVE_MYSQL) static int is_recoverable_mysql_error(int err_no) { if (0 == err_no) err_no = (int)mysql_errno(conn); switch (err_no) { case CR_CONN_HOST_ERROR: case CR_SERVER_GONE_ERROR: case CR_CONNECTION_ERROR: case CR_SERVER_LOST: case CR_UNKNOWN_HOST: case CR_COMMANDS_OUT_OF_SYNC: case ER_SERVER_SHUTDOWN: case ER_ACCESS_DENIED_ERROR: /* wrong user or password */ case ER_ILLEGAL_GRANT_FOR_TABLE: /* user without any privileges */ case ER_TABLEACCESS_DENIED_ERROR: /* user without some privilege */ case ER_UNKNOWN_ERROR: case ER_UNKNOWN_COM_ERROR: case ER_LOCK_DEADLOCK: case ER_LOCK_WAIT_TIMEOUT: #ifdef ER_CLIENT_INTERACTION_TIMEOUT case ER_CLIENT_INTERACTION_TIMEOUT: #endif #ifdef CR_SSL_CONNECTION_ERROR case CR_SSL_CONNECTION_ERROR: #endif #ifdef ER_CONNECTION_KILLED case ER_CONNECTION_KILLED: #endif return SUCCEED; } return FAIL; } static int is_inhibited_mysql_error(int err_no) { if (1 < mysql_err_cnt) return FAIL; if (0 < txn_level && 0 == txn_begin) return FAIL; switch (err_no) { case CR_SERVER_GONE_ERROR: case CR_SERVER_LOST: #ifdef ER_CLIENT_INTERACTION_TIMEOUT case ER_CLIENT_INTERACTION_TIMEOUT: #endif return SUCCEED; } return FAIL; } #elif defined(HAVE_POSTGRESQL) static int is_recoverable_postgresql_error(const PGconn *pg_conn, const PGresult *pg_result) { if (CONNECTION_OK != PQstatus(pg_conn)) return SUCCEED; if (0 == zbx_strcmp_null(PQresultErrorField(pg_result, PG_DIAG_SQLSTATE), ZBX_PG_DEADLOCK)) return SUCCEED; if (1 == ZBX_PG_READ_ONLY_RECOVERABLE && 0 == zbx_strcmp_null(PQresultErrorField(pg_result, PG_DIAG_SQLSTATE), ZBX_PG_READ_ONLY)) { return SUCCEED; } return FAIL; } #endif /****************************************************************************** * * * Purpose: specify the autoincrement options during db connect * * * ******************************************************************************/ void zbx_db_init_autoincrement_options_basic(void) { db_auto_increment = 1; } /****************************************************************************** * * * Purpose: connect to the database * * * * Return value: ZBX_DB_OK - successfully connected * * ZBX_DB_DOWN - database is down * * ZBX_DB_FAIL - failed to connect * * * ******************************************************************************/ int zbx_db_connect_basic(const zbx_config_dbhigh_t *cfg) { int ret = ZBX_DB_OK, last_txn_error, last_txn_level; #if defined(HAVE_MYSQL) int err_no = 0; #elif defined(HAVE_POSTGRESQL) # define ZBX_DB_MAX_PARAMS 9 int rc; char *cport = NULL; zbx_db_result_t result; zbx_db_row_t row; const char *keywords[ZBX_DB_MAX_PARAMS + 1]; const char *values[ZBX_DB_MAX_PARAMS + 1]; unsigned int i = 0; #elif defined(HAVE_SQLITE3) char *p, *path = NULL; #endif /* Allow executing statements during a connection initialization. Make sure to mark transaction as failed. */ if (0 != txn_level) txn_error = ZBX_DB_DOWN; last_txn_error = txn_error; last_txn_level = txn_level; txn_error = ZBX_DB_OK; txn_level = 0; #if defined(HAVE_MYSQL) if (NULL == (conn = mysql_init(NULL))) { zabbix_log(LOG_LEVEL_CRIT, "cannot allocate or initialize MYSQL database connection object"); exit(EXIT_FAILURE); } if (1 == db_auto_increment) { /* Shadow global auto_increment variables. */ /* Setting session variables requires special permissions in MySQL 8.0.14-8.0.17. */ if (0 != MYSQL_OPTIONS(conn, MYSQL_INIT_COMMAND, MYSQL_OPTIONS_ARGS_VOID_CAST "set @@session.auto_increment_increment=1")) { zabbix_log(LOG_LEVEL_ERR, "Cannot set auto_increment_increment option."); ret = ZBX_DB_FAIL; } if (ZBX_DB_OK == ret && 0 != MYSQL_OPTIONS(conn, MYSQL_INIT_COMMAND, MYSQL_OPTIONS_ARGS_VOID_CAST "set @@session.auto_increment_offset=1")) { zabbix_log(LOG_LEVEL_ERR, "Cannot set auto_increment_offset option."); ret = ZBX_DB_FAIL; } } #if defined(HAVE_MYSQL_TLS) if (ZBX_DB_OK == ret && NULL != cfg->config_db_tls_connect) { unsigned int mysql_tls_mode; if (0 == strcmp(cfg->config_db_tls_connect, ZBX_DB_TLS_CONNECT_REQUIRED_TXT)) mysql_tls_mode = SSL_MODE_REQUIRED; else if (0 == strcmp(cfg->config_db_tls_connect, ZBX_DB_TLS_CONNECT_VERIFY_CA_TXT)) mysql_tls_mode = SSL_MODE_VERIFY_CA; else mysql_tls_mode = SSL_MODE_VERIFY_IDENTITY; if (0 != mysql_options(conn, MYSQL_OPT_SSL_MODE, &mysql_tls_mode)) { zabbix_log(LOG_LEVEL_ERR, "Cannot set MYSQL_OPT_SSL_MODE option."); ret = ZBX_DB_FAIL; } } if (ZBX_DB_OK == ret && NULL != cfg->config_db_tls_ca_file && 0 != mysql_options(conn, MYSQL_OPT_SSL_CA, cfg->config_db_tls_ca_file)) { zabbix_log(LOG_LEVEL_ERR, "Cannot set MYSQL_OPT_SSL_CA option."); ret = ZBX_DB_FAIL; } if (ZBX_DB_OK == ret && NULL != cfg->config_db_tls_key_file && 0 != mysql_options(conn, MYSQL_OPT_SSL_KEY, cfg->config_db_tls_key_file)) { zabbix_log(LOG_LEVEL_ERR, "Cannot set MYSQL_OPT_SSL_KEY option."); ret = ZBX_DB_FAIL; } if (ZBX_DB_OK == ret && NULL != cfg->config_db_tls_cert_file && 0 != mysql_options(conn, MYSQL_OPT_SSL_CERT, cfg->config_db_tls_cert_file)) { zabbix_log(LOG_LEVEL_ERR, "Cannot set MYSQL_OPT_SSL_CERT option."); ret = ZBX_DB_FAIL; } if (ZBX_DB_OK == ret && NULL != cfg->config_db_tls_cipher && 0 != mysql_options(conn, MYSQL_OPT_SSL_CIPHER, cfg->config_db_tls_cipher)) { zabbix_log(LOG_LEVEL_ERR, "Cannot set MYSQL_OPT_SSL_CIPHER option."); ret = ZBX_DB_FAIL; } #if defined(HAVE_MYSQL_TLS_CIPHERSUITES) if (ZBX_DB_OK == ret && NULL != cfg->config_db_tls_cipher_13 && 0 != mysql_options(conn, MYSQL_OPT_TLS_CIPHERSUITES, cfg->config_db_tls_cipher_13)) { zabbix_log(LOG_LEVEL_ERR, "Cannot set MYSQL_OPT_TLS_CIPHERSUITES option."); ret = ZBX_DB_FAIL; } #endif #elif defined(HAVE_MARIADB_TLS) if (ZBX_DB_OK == ret && NULL != cfg->config_db_tls_connect) { if (0 == strcmp(cfg->config_db_tls_connect, ZBX_DB_TLS_CONNECT_REQUIRED_TXT)) { my_bool enforce_tls = 1; if (0 != mysql_optionsv(conn, MYSQL_OPT_SSL_ENFORCE, (void *)&enforce_tls)) { zabbix_log(LOG_LEVEL_ERR, "Cannot set MYSQL_OPT_SSL_ENFORCE option."); ret = ZBX_DB_FAIL; } } else { my_bool verify = 1; if (0 != mysql_optionsv(conn, MYSQL_OPT_SSL_VERIFY_SERVER_CERT, (void *)&verify)) { zabbix_log(LOG_LEVEL_ERR, "Cannot set MYSQL_OPT_SSL_VERIFY_SERVER_CERT option."); ret = ZBX_DB_FAIL; } } } if (ZBX_DB_OK == ret && NULL != cfg->config_db_tls_ca_file && 0 != mysql_optionsv(conn, MYSQL_OPT_SSL_CA, cfg->config_db_tls_ca_file)) { zabbix_log(LOG_LEVEL_ERR, "Cannot set MYSQL_OPT_SSL_CA option."); ret = ZBX_DB_FAIL; } if (ZBX_DB_OK == ret && NULL != cfg->config_db_tls_key_file && 0 != mysql_optionsv(conn, MYSQL_OPT_SSL_KEY, cfg->config_db_tls_key_file)) { zabbix_log(LOG_LEVEL_ERR, "Cannot set MYSQL_OPT_SSL_KEY option."); ret = ZBX_DB_FAIL; } if (ZBX_DB_OK == ret && NULL != cfg->config_db_tls_cert_file && 0 != mysql_optionsv(conn, MYSQL_OPT_SSL_CERT, cfg->config_db_tls_cert_file)) { zabbix_log(LOG_LEVEL_ERR, "Cannot set MYSQL_OPT_SSL_CERT option."); ret = ZBX_DB_FAIL; } if (ZBX_DB_OK == ret && NULL != cfg->config_db_tls_cipher && 0 != mysql_optionsv(conn, MYSQL_OPT_SSL_CIPHER, cfg->config_db_tls_cipher)) { zabbix_log(LOG_LEVEL_ERR, "Cannot set MYSQL_OPT_SSL_CIPHER option."); ret = ZBX_DB_FAIL; } #endif if (ZBX_DB_OK == ret && NULL == mysql_real_connect(conn, cfg->config_dbhost, cfg->config_dbuser, cfg->config_dbpassword, cfg->config_dbname, cfg->config_dbport, cfg->config_dbsocket, CLIENT_MULTI_STATEMENTS)) { err_no = (int)mysql_errno(conn); zbx_db_errlog(ERR_Z3001, err_no, mysql_error(conn), cfg->config_dbname); ret = ZBX_DB_FAIL; } if (ZBX_DB_OK == ret) { /* in contrast to "set names utf8" results of this call will survive auto-reconnects */ /* utf8mb3 is deprecated and it's superset utf8mb4 should be used instead if available */ if (0 != mysql_set_character_set(conn, ZBX_SUPPORTED_DB_CHARACTER_SET_UTF8MB4) && 0 != mysql_set_character_set(conn, ZBX_SUPPORTED_DB_CHARACTER_SET_UTF8) && 0 != mysql_set_character_set(conn, ZBX_SUPPORTED_DB_CHARACTER_SET_UTF8MB3)) { zabbix_log(LOG_LEVEL_WARNING, "cannot set MySQL character set"); } } if (ZBX_DB_OK == ret && 0 != mysql_autocommit(conn, 1)) { err_no = (int)mysql_errno(conn); zbx_db_errlog(ERR_Z3001, err_no, mysql_error(conn), cfg->config_dbname); ret = ZBX_DB_FAIL; } if (ZBX_DB_OK == ret && 0 != mysql_select_db(conn, cfg->config_dbname)) { err_no = (int)mysql_errno(conn); zbx_db_errlog(ERR_Z3001, err_no, mysql_error(conn), cfg->config_dbname); ret = ZBX_DB_FAIL; } if (ZBX_DB_FAIL == ret && SUCCEED == is_recoverable_mysql_error(err_no)) ret = ZBX_DB_DOWN; mysql_err_cnt = ZBX_DB_OK == ret ? 0 : mysql_err_cnt + 1; #elif defined(HAVE_POSTGRESQL) if (NULL != cfg->config_db_tls_connect) { keywords[i] = "sslmode"; if (0 == strcmp(cfg->config_db_tls_connect, ZBX_DB_TLS_CONNECT_REQUIRED_TXT)) values[i++] = "require"; else if (0 == strcmp(cfg->config_db_tls_connect, ZBX_DB_TLS_CONNECT_VERIFY_CA_TXT)) values[i++] = "verify-ca"; else values[i++] = "verify-full"; } if (NULL != cfg->config_db_tls_cert_file) { keywords[i] = "sslcert"; values[i++] = cfg->config_db_tls_cert_file; } if (NULL != cfg->config_db_tls_key_file) { keywords[i] = "sslkey"; values[i++] = cfg->config_db_tls_key_file; } if (NULL != cfg->config_db_tls_ca_file) { keywords[i] = "sslrootcert"; values[i++] = cfg->config_db_tls_ca_file; } if (NULL != cfg->config_dbhost) { keywords[i] = "host"; values[i++] = cfg->config_dbhost; } if (NULL != cfg->config_dbname) { keywords[i] = "dbname"; values[i++] = cfg->config_dbname; } if (NULL != cfg->config_dbuser) { keywords[i] = "user"; values[i++] = cfg->config_dbuser; } if (NULL != cfg->config_dbpassword) { keywords[i] = "password"; values[i++] = cfg->config_dbpassword; } if (0 != cfg->config_dbport) { keywords[i] = "port"; values[i++] = cport = zbx_dsprintf(cport, "%d", cfg->config_dbport); } keywords[i] = NULL; values[i] = NULL; conn = PQconnectdbParams(keywords, values, 0); zbx_free(cport); /* check to see that the backend connection was successfully made */ if (CONNECTION_OK != PQstatus(conn)) { zbx_db_errlog(ERR_Z3001, 0, PQerrorMessage(conn), cfg->config_dbname); ret = ZBX_DB_DOWN; goto out; } if (NULL != cfg->config_dbschema && '\0' != *cfg->config_dbschema) { char *dbschema_esc; dbschema_esc = zbx_db_dyn_escape_string_basic(cfg->config_dbschema, ZBX_SIZE_T_MAX, ZBX_SIZE_T_MAX, ESCAPE_SEQUENCE_ON); if (ZBX_DB_DOWN == (rc = zbx_db_execute_basic("set schema '%s'", dbschema_esc)) || ZBX_DB_FAIL == rc) ret = rc; zbx_free(dbschema_esc); } if (ZBX_DB_FAIL == ret || ZBX_DB_DOWN == ret) goto out; /* disable "nonstandard use of \' in a string literal" warning */ if (0 < (ret = zbx_db_execute_basic("set escape_string_warning to off"))) ret = ZBX_DB_OK; if (ZBX_DB_OK != ret) goto out; /* increase float precision */ if (0 < (ret = zbx_db_execute_basic("set extra_float_digits to 3"))) ret = ZBX_DB_OK; if (ZBX_DB_OK != ret) goto out; result = zbx_db_select_basic("show standard_conforming_strings"); if ((zbx_db_result_t)ZBX_DB_DOWN == result || NULL == result) { ret = (NULL == result) ? ZBX_DB_FAIL : ZBX_DB_DOWN; goto out; } if (NULL != (row = zbx_db_fetch_basic(result))) ZBX_PG_ESCAPE_BACKSLASH = (0 == strcmp(row[0], "off")); zbx_db_free_result(result); if (90000 <= ZBX_PG_SVERSION) { /* change the output format for values of type bytea from hex (the default) to escape */ if (0 < (ret = zbx_db_execute_basic("set bytea_output=escape"))) ret = ZBX_DB_OK; } ZBX_PG_READ_ONLY_RECOVERABLE = cfg->read_only_recoverable; out: #elif defined(HAVE_SQLITE3) #ifdef HAVE_FUNCTION_SQLITE3_OPEN_V2 if (SQLITE_OK != sqlite3_open_v2(cfg->config_dbname, &conn, SQLITE_OPEN_READWRITE, NULL)) #else if (SQLITE_OK != sqlite3_open(cfg->config_dbname, &conn)) #endif { zbx_db_errlog(ERR_Z3001, 0, sqlite3_errmsg(conn), cfg->config_dbname); ret = ZBX_DB_DOWN; goto out; } /* do not return SQLITE_BUSY immediately, wait for N ms */ sqlite3_busy_timeout(conn, SEC_PER_MIN * 1000); if (0 < (ret = zbx_db_execute_basic("pragma synchronous=0"))) ret = ZBX_DB_OK; if (ZBX_DB_OK != ret) goto out; if (0 < (ret = zbx_db_execute_basic("pragma foreign_keys=on"))) ret = ZBX_DB_OK; if (ZBX_DB_OK != ret) goto out; if (0 < (ret = zbx_db_execute_basic("pragma temp_store=2"))) ret = ZBX_DB_OK; if (ZBX_DB_OK != ret) goto out; path = zbx_strdup(NULL, cfg->config_dbname); if (NULL != (p = strrchr(path, '/'))) *++p = '\0'; else *path = '\0'; if (0 < (ret = zbx_db_execute_basic("pragma temp_store_directory='%s'", path))) ret = ZBX_DB_OK; zbx_free(path); out: #endif /* HAVE_SQLITE3 */ if (ZBX_DB_OK != ret) zbx_db_close_basic(); txn_error = last_txn_error; txn_level = last_txn_level; return ret; } int zbx_db_init_basic(const char *dbname, const char *const dbschema, int log_slow_queries, char **error) { #ifdef HAVE_SQLITE3 zbx_stat_t buf; #endif config_log_slow_queries = log_slow_queries; #ifdef HAVE_SQLITE3 if (0 != zbx_stat(dbname, &buf)) { zabbix_log(LOG_LEVEL_WARNING, "cannot open database file \"%s\": %s", dbname, zbx_strerror(errno)); zabbix_log(LOG_LEVEL_WARNING, "creating database ..."); if (SQLITE_OK != sqlite3_open(dbname, &conn)) { zbx_db_errlog(ERR_Z3002, 0, sqlite3_errmsg(conn), dbname); *error = zbx_strdup(*error, "cannot open database"); return FAIL; } if (SUCCEED != zbx_mutex_create(&sqlite_access, ZBX_MUTEX_SQLITE3, error)) return FAIL; zbx_db_execute_basic("%s", dbschema); zbx_db_close_basic(); return SUCCEED; } return zbx_mutex_create(&sqlite_access, ZBX_MUTEX_SQLITE3, error); #else /* not HAVE_SQLITE3 */ ZBX_UNUSED(dbname); ZBX_UNUSED(dbschema); ZBX_UNUSED(error); return SUCCEED; #endif /* HAVE_SQLITE3 */ } void zbx_db_deinit_basic(void) { #ifdef HAVE_SQLITE3 zbx_mutex_destroy(&sqlite_access); #endif } void zbx_db_close_basic(void) { #if defined(HAVE_MYSQL) if (NULL != conn) { mysql_close(conn); conn = NULL; } #elif defined(HAVE_POSTGRESQL) if (NULL != conn) { PQfinish(conn); conn = NULL; } #elif defined(HAVE_SQLITE3) if (NULL != conn) { sqlite3_close(conn); conn = NULL; } #endif } /****************************************************************************** * * * Purpose: start transaction * * * * Comments: do nothing if DB does not support transactions * * * ******************************************************************************/ int zbx_db_begin_basic(void) { int rc = ZBX_DB_OK; if (txn_level > 0) { zabbix_log(LOG_LEVEL_CRIT, "ERROR: nested transaction detected. Please report it to Zabbix Team."); assert(0); } #if defined(HAVE_MYSQL) txn_begin = 1; #endif txn_level++; #if defined(HAVE_MYSQL) || defined(HAVE_POSTGRESQL) rc = zbx_db_execute_basic("begin;"); #elif defined(HAVE_SQLITE3) zbx_mutex_lock(sqlite_access); rc = zbx_db_execute_basic("begin;"); #endif if (ZBX_DB_DOWN == rc) txn_level--; #if defined(HAVE_MYSQL) txn_begin = 0; #endif return rc; } /****************************************************************************** * * * Purpose: commit transaction * * * * Comments: do nothing if DB does not support transactions * * * ******************************************************************************/ int zbx_db_commit_basic(void) { int rc = ZBX_DB_OK; if (0 == txn_level) { zabbix_log(LOG_LEVEL_CRIT, "ERROR: commit without transaction." " Please report it to Zabbix Team."); assert(0); } if (ZBX_DB_OK != txn_error) return ZBX_DB_FAIL; /* commit called on failed transaction */ #if defined(HAVE_MYSQL) || defined(HAVE_POSTGRESQL) || defined(HAVE_SQLITE3) rc = zbx_db_execute_basic("commit;"); #endif if (ZBX_DB_OK > rc) { /* commit failed */ txn_error = rc; return rc; } #ifdef HAVE_SQLITE3 zbx_mutex_unlock(sqlite_access); #endif txn_level--; txn_end_error = ZBX_DB_OK; return rc; } /****************************************************************************** * * * Purpose: rollback transaction * * * * Comments: do nothing if DB does not support transactions * * * ******************************************************************************/ int zbx_db_rollback_basic(void) { int rc = ZBX_DB_OK, last_txn_error; if (0 == txn_level) { zabbix_log(LOG_LEVEL_CRIT, "ERROR: rollback without transaction." " Please report it to Zabbix Team."); assert(0); } last_txn_error = txn_error; /* allow rollback of failed transaction */ txn_error = ZBX_DB_OK; #if defined(HAVE_MYSQL) || defined(HAVE_POSTGRESQL) rc = zbx_db_execute_basic("rollback;"); #elif defined(HAVE_SQLITE3) rc = zbx_db_execute_basic("rollback;"); zbx_mutex_unlock(sqlite_access); #endif /* There is no way to recover from rollback errors, so there is no need to preserve transaction level / error. */ txn_level = 0; txn_error = ZBX_DB_OK; if (ZBX_DB_FAIL == rc) txn_end_error = ZBX_DB_FAIL; else txn_end_error = last_txn_error; /* error that caused rollback */ return rc; } int zbx_db_txn_level(void) { return txn_level; } int zbx_db_txn_error(void) { return txn_error; } int zbx_db_txn_end_error(void) { return txn_end_error; } #if defined(HAVE_MYSQL) void zbx_mysql_escape_bin(const char *src, char *dst, size_t size) { mysql_real_escape_string(conn, dst, src, size); } #elif defined(HAVE_POSTGRESQL) void zbx_postgresql_escape_bin(const char *src, char **dst, size_t size) { size_t dst_size; *dst = (char*)PQescapeByteaConn(conn, (const unsigned char*)src, size, &dst_size); } #endif static char *db_replace_nonprintable_chars(const char *sql, char **sql_printable) { if (NULL == *sql_printable) { *sql_printable = zbx_strdup(NULL, sql); zbx_replace_invalid_utf8_and_nonprintable(*sql_printable); } return *sql_printable; } /****************************************************************************** * * * Purpose: Execute SQL statement. For non-select statements only. * * * * Return value: ZBX_DB_FAIL (on error) or ZBX_DB_DOWN (on recoverable error) * * or number of rows affected (on success) * * * ******************************************************************************/ int zbx_db_vexecute(const char *fmt, va_list args) { char *sql = NULL, *sql_printable = NULL; int ret = ZBX_DB_OK; double sec = 0; #if defined(HAVE_POSTGRESQL) PGresult *result; char *error = NULL; #elif defined(HAVE_SQLITE3) int err; char *error = NULL; #endif if (0 != config_log_slow_queries) sec = zbx_time(); sql = zbx_dvsprintf(sql, fmt, args); if (0 == txn_level) zabbix_log(LOG_LEVEL_DEBUG, "query without transaction detected"); if (ZBX_DB_OK != txn_error) { zabbix_log(LOG_LEVEL_DEBUG, "ignoring query [txnlev:%d] [%s] within failed transaction", txn_level, db_replace_nonprintable_chars(sql, &sql_printable)); ret = ZBX_DB_FAIL; goto clean; } zabbix_log(LOG_LEVEL_DEBUG, "query [txnlev:%d] [%s]", txn_level, db_replace_nonprintable_chars(sql, &sql_printable)); #if defined(HAVE_MYSQL) if (NULL == conn) { zbx_db_errlog(ERR_Z3003, 0, NULL, NULL); ret = ZBX_DB_FAIL; } else { zbx_err_codes_t errcode; int err_no; if (0 != mysql_query(conn, sql)) { err_no = (int)mysql_errno(conn); errcode = (ER_DUP_ENTRY == err_no ? ERR_Z3008 : ERR_Z3005); mysql_err_cnt++; if (FAIL == is_inhibited_mysql_error(err_no)) zbx_db_errlog(errcode, err_no, mysql_error(conn), sql); ret = (SUCCEED == is_recoverable_mysql_error(err_no) ? ZBX_DB_DOWN : ZBX_DB_FAIL); } else { int status; do { if (0 != mysql_field_count(conn)) { zabbix_log(LOG_LEVEL_DEBUG, "cannot retrieve result set"); break; } ret += (int)mysql_affected_rows(conn); /* more results? 0 = yes (keep looping), -1 = no, >0 = error */ if (0 < (status = mysql_next_result(conn))) { err_no = (int)mysql_errno(conn); errcode = (ER_DUP_ENTRY == err_no ? ERR_Z3008 : ERR_Z3005); mysql_err_cnt++; if (FAIL == is_inhibited_mysql_error(err_no)) zbx_db_errlog(errcode, err_no, mysql_error(conn), sql); ret = (SUCCEED == is_recoverable_mysql_error(err_no) ? ZBX_DB_DOWN : ZBX_DB_FAIL); } } while (0 == status); } } #elif defined(HAVE_POSTGRESQL) result = PQexec(conn,sql); if (NULL == result) { zbx_db_errlog(ERR_Z3005, 0, "result is NULL", sql); ret = (CONNECTION_OK == PQstatus(conn) ? ZBX_DB_FAIL : ZBX_DB_DOWN); } else if (PGRES_COMMAND_OK != PQresultStatus(result)) { zbx_err_codes_t errcode; zbx_postgresql_error(&error, result); if (0 == zbx_strcmp_null(PQresultErrorField(result, PG_DIAG_SQLSTATE), ZBX_PG_UNIQUE_VIOLATION)) errcode = ERR_Z3008; else if (0 == zbx_strcmp_null(PQresultErrorField(result, PG_DIAG_SQLSTATE), ZBX_PG_READ_ONLY)) errcode = ERR_Z3009; else errcode = ERR_Z3005; zbx_db_errlog(errcode, 0, error, sql); zbx_free(error); ret = (SUCCEED == is_recoverable_postgresql_error(conn, result) ? ZBX_DB_DOWN : ZBX_DB_FAIL); } if (ZBX_DB_OK == ret) ret = atoi(PQcmdTuples(result)); PQclear(result); #elif defined(HAVE_SQLITE3) if (0 == txn_level) zbx_mutex_lock(sqlite_access); lbl_exec: if (SQLITE_OK != (err = sqlite3_exec(conn, sql, NULL, 0, &error))) { if (SQLITE_BUSY == err) goto lbl_exec; zbx_db_errlog(ERR_Z3005, 0, error, sql); sqlite3_free(error); switch (err) { case SQLITE_ERROR: /* SQL error or missing database; assuming SQL error, because if we are this far into execution, zbx_db_connect_basic() was successful */ case SQLITE_NOMEM: /* A malloc() failed */ case SQLITE_TOOBIG: /* String or BLOB exceeds size limit */ case SQLITE_CONSTRAINT: /* Abort due to constraint violation */ case SQLITE_MISMATCH: /* Data type mismatch */ ret = ZBX_DB_FAIL; break; default: ret = ZBX_DB_DOWN; break; } } if (ZBX_DB_OK == ret) ret = sqlite3_changes(conn); if (0 == txn_level) zbx_mutex_unlock(sqlite_access); #endif /* HAVE_SQLITE3 */ if (0 != config_log_slow_queries) { sec = zbx_time() - sec; if (sec > (double)config_log_slow_queries / 1000.0) { zabbix_log(LOG_LEVEL_WARNING, "slow query: " ZBX_FS_DBL " sec, \"%s\"", sec, db_replace_nonprintable_chars(sql, &sql_printable)); } } if (ZBX_DB_FAIL == ret && 0 < txn_level) { zabbix_log(LOG_LEVEL_DEBUG, "query [%s] failed, setting transaction as failed", db_replace_nonprintable_chars(sql, &sql_printable)); txn_error = ZBX_DB_FAIL; } clean: zbx_free(sql_printable); zbx_free(sql); return ret; } /****************************************************************************** * * * Purpose: execute a select statement * * * * Return value: data, NULL (on error) or (zbx_db_result_t)ZBX_DB_DOWN * * * ******************************************************************************/ zbx_db_result_t zbx_db_vselect(const char *fmt, va_list args) { char *sql = NULL; zbx_db_result_t result = NULL; double sec = 0; #if defined(HAVE_POSTGRESQL) char *error = NULL; #elif defined(HAVE_SQLITE3) int ret = FAIL; char *error = NULL; #endif if (0 != config_log_slow_queries) sec = zbx_time(); sql = zbx_dvsprintf(sql, fmt, args); if (ZBX_DB_OK != txn_error) { zabbix_log(LOG_LEVEL_DEBUG, "ignoring query [txnlev:%d] [%s] within failed transaction", txn_level, sql); goto clean; } zabbix_log(LOG_LEVEL_DEBUG, "query [txnlev:%d] [%s]", txn_level, sql); #if defined(HAVE_MYSQL) result = (zbx_db_result_t)zbx_malloc(NULL, sizeof(struct zbx_db_result)); result->result = NULL; if (NULL == conn) { zbx_db_errlog(ERR_Z3003, 0, NULL, NULL); zbx_db_free_result(result); result = NULL; } else { if (0 != mysql_query(conn, sql) || NULL == (result->result = mysql_store_result(conn))) { int err_no = (int)mysql_errno(conn); mysql_err_cnt++; if (FAIL == is_inhibited_mysql_error(err_no)) zbx_db_errlog(ERR_Z3005, err_no, mysql_error(conn), sql); zbx_db_free_result(result); result = (SUCCEED == is_recoverable_mysql_error(err_no) ? (zbx_db_result_t)ZBX_DB_DOWN : NULL); } } #elif defined(HAVE_POSTGRESQL) result = zbx_malloc(NULL, sizeof(struct zbx_db_result)); result->pg_result = PQexec(conn, sql); result->values = NULL; result->cursor = 0; result->row_num = 0; if (NULL == result->pg_result) zbx_db_errlog(ERR_Z3005, 0, "result is NULL", sql); if (PGRES_TUPLES_OK != PQresultStatus(result->pg_result)) { zbx_postgresql_error(&error, result->pg_result); zbx_db_errlog(ERR_Z3005, 0, error, sql); zbx_free(error); if (SUCCEED == is_recoverable_postgresql_error(conn, result->pg_result)) { zbx_db_free_result(result); result = (zbx_db_result_t)ZBX_DB_DOWN; } else { zbx_db_free_result(result); result = NULL; } } else /* init rownum */ result->row_num = PQntuples(result->pg_result); #elif defined(HAVE_SQLITE3) if (0 == txn_level) zbx_mutex_lock(sqlite_access); result = zbx_malloc(NULL, sizeof(struct zbx_db_result)); result->curow = 0; lbl_get_table: if (SQLITE_OK != (ret = sqlite3_get_table(conn,sql, &result->data, &result->nrow, &result->ncolumn, &error))) { if (SQLITE_BUSY == ret) goto lbl_get_table; zbx_db_errlog(ERR_Z3005, 0, error, sql); sqlite3_free(error); zbx_db_free_result(result); switch (ret) { case SQLITE_ERROR: /* SQL error or missing database; assuming SQL error, because if we are this far into execution, zbx_db_connect_basic() was successful */ case SQLITE_NOMEM: /* a malloc() failed */ case SQLITE_MISMATCH: /* data type mismatch */ result = NULL; break; default: result = (zbx_db_result_t)ZBX_DB_DOWN; break; } } if (0 == txn_level) zbx_mutex_unlock(sqlite_access); #endif /* HAVE_SQLITE3 */ if (0 != config_log_slow_queries) { sec = zbx_time() - sec; if (sec > (double)config_log_slow_queries / 1000.0) zabbix_log(LOG_LEVEL_WARNING, "slow query: " ZBX_FS_DBL " sec, \"%s\"", sec, sql); } if (NULL == result && 0 < txn_level) { zabbix_log(LOG_LEVEL_DEBUG, "query [%s] failed, setting transaction as failed", sql); txn_error = ZBX_DB_FAIL; } clean: zbx_free(sql); return result; } /* * Execute SQL statement. For select statements only. */ zbx_db_result_t zbx_db_select_n_basic(const char *query, int n) { #if defined(HAVE_MYSQL) || defined(HAVE_POSTGRESQL) || defined(HAVE_SQLITE3) return zbx_db_select_basic("%s limit %d", query, n); #endif } int zbx_db_get_row_num(zbx_db_result_t result) { #if defined(HAVE_POSTGRESQL) return result->row_num; #elif defined(HAVE_MYSQL) return (int)mysql_num_rows(result->result); #else ZBX_UNUSED(result); return 0; #endif } zbx_db_row_t zbx_db_fetch_basic(zbx_db_result_t result) { if (NULL == result) return NULL; #if defined(HAVE_MYSQL) if (NULL == result->result) return NULL; return (zbx_db_row_t)mysql_fetch_row(result->result); #elif defined(HAVE_POSTGRESQL) /* EOF */ if (result->cursor == result->row_num) return NULL; /* init result */ if (0 == result->cursor) result->fld_num = PQnfields(result->pg_result); if (result->fld_num > 0) { int i; if (NULL == result->values) result->values = zbx_malloc(result->values, sizeof(char *) * result->fld_num); for (i = 0; i < result->fld_num; i++) { result->values[i] = PQgetvalue(result->pg_result, result->cursor, i); if ('\0' == *result->values[i] && PQgetisnull(result->pg_result, result->cursor, i)) result->values[i] = NULL; } } result->cursor++; return result->values; #elif defined(HAVE_SQLITE3) /* EOF */ if (result->curow >= result->nrow) return NULL; if (NULL == result->data) return NULL; result->curow++; /* NOTE: first row == header row */ return &(result->data[result->curow * result->ncolumn]); #endif } int zbx_db_is_null_basic(const char *field) { if (NULL == field) return SUCCEED; return FAIL; } void zbx_db_free_result(zbx_db_result_t result) { #if defined(HAVE_MYSQL) if (NULL == result) return; mysql_free_result(result->result); zbx_free(result); #elif defined(HAVE_POSTGRESQL) if (NULL == result) return; if (NULL != result->values) { result->fld_num = 0; zbx_free(result->values); result->values = NULL; } PQclear(result->pg_result); zbx_free(result); #elif defined(HAVE_SQLITE3) if (NULL == result) return; if (NULL != result->data) { sqlite3_free_table(result->data); } zbx_free(result); #endif /* HAVE_SQLITE3 */ } static int zbx_db_is_escape_sequence(char c) { #if defined(HAVE_MYSQL) if ('\'' == c || '\\' == c) #elif defined(HAVE_POSTGRESQL) if ('\'' == c || ('\\' == c && 1 == ZBX_PG_ESCAPE_BACKSLASH)) #else if ('\'' == c) #endif return SUCCEED; return FAIL; } /****************************************************************************** * * * Return value: escaped string * * * * Comments: sync changes with 'zbx_db_get_escape_string_len' * * and 'zbx_db_dyn_escape_string_basic * * * ******************************************************************************/ static void zbx_db_escape_string(const char *src, char *dst, size_t len, zbx_escape_sequence_t flag) { const char *s; char *d; assert(dst); len--; /* '\0' */ for (s = src, d = dst; NULL != s && '\0' != *s && 0 < len; s++) { if (ESCAPE_SEQUENCE_ON == flag && SUCCEED == zbx_db_is_escape_sequence(*s)) { if (2 > len) break; #if defined(HAVE_MYSQL) *d++ = '\\'; #elif defined(HAVE_POSTGRESQL) *d++ = *s; #else *d++ = '\''; #endif len--; } *d++ = *s; len--; } *d = '\0'; } /****************************************************************************** * * * Purpose: to calculate escaped string length limited by bytes or characters * * whichever is reached first. * * * * Parameters: s - [IN] string to escape * * max_bytes - [IN] limit in bytes * * max_chars - [IN] limit in characters * * flag - [IN] sequences need to be escaped on/off * * * * Return value: return length in bytes of escaped string * * with terminating '\0' * * * ******************************************************************************/ static size_t zbx_db_get_escape_string_len(const char *s, size_t max_bytes, size_t max_chars, zbx_escape_sequence_t flag) { size_t csize, len = 1; /* '\0' */ if (NULL == s) return len; while ('\0' != *s && 0 < max_chars) { csize = zbx_utf8_char_len(s); /* process non-UTF-8 characters as single byte characters */ if (0 == csize) csize = 1; if (max_bytes < csize) break; if (ESCAPE_SEQUENCE_ON == flag && SUCCEED == zbx_db_is_escape_sequence(*s)) len++; s += csize; len += csize; max_bytes -= csize; max_chars--; } return len; } /****************************************************************************** * * * Purpose: to escape string limited by bytes or characters, whichever limit * * is reached first. * * * * Parameters: src - [IN] string to escape * * max_bytes - [IN] limit in bytes * * max_chars - [IN] limit in characters * * flag - [IN] sequences need to be escaped on/off * * * * Return value: escaped string * * * ******************************************************************************/ char *zbx_db_dyn_escape_string_basic(const char *src, size_t max_bytes, size_t max_chars, zbx_escape_sequence_t flag) { char *dst = NULL; size_t len; len = zbx_db_get_escape_string_len(src, max_bytes, max_chars, flag); dst = (char *)zbx_malloc(dst, len); zbx_db_escape_string(src, dst, len, flag); return dst; } /****************************************************************************** * * * Return value: return length of escaped LIKE pattern with terminating '\0' * * * * Comments: sync changes with 'zbx_db_escape_like_pattern' * * * ******************************************************************************/ static int zbx_db_get_escape_like_pattern_len(const char *src) { int len; const char *s; len = zbx_db_get_escape_string_len(src, ZBX_SIZE_T_MAX, ZBX_SIZE_T_MAX, ESCAPE_SEQUENCE_ON) - 1; /* minus '\0' */ for (s = src; s && *s; s++) { len += (*s == '_' || *s == '%' || *s == ZBX_SQL_LIKE_ESCAPE_CHAR); len += 1; } len++; /* '\0' */ return len; } /****************************************************************************** * * * Return value: escaped string to be used as pattern in LIKE * * * * Comments: sync changes with 'zbx_db_get_escape_like_pattern_len' * * * * For instance, we wish to find string a_b%c\d'e!f in our database * * using '!' as escape character. Our queries then become: * * * * ... LIKE 'a!_b!%c\\d\'e!!f' ESCAPE '!' (MySQL, PostgreSQL) * * ... LIKE 'a!_b!%c\d''e!!f' ESCAPE '!' (SQLite3) * * * * Using backslash as escape character in LIKE would be too much * * trouble, because escaping backslashes would have to be escaped * * as well, like so: * * * * ... LIKE 'a\\_b\\%c\\\\d\'e!f' ESCAPE '\\' or * * ... LIKE 'a\\_b\\%c\\\\d\\\'e!f' ESCAPE '\\' (MySQL, PostgreSQL) * * ... LIKE 'a\_b\%c\\d''e!f' ESCAPE '\' (SQLite3) * * * * Hence '!' instead of backslash. * * * ******************************************************************************/ static void zbx_db_escape_like_pattern(const char *src, char *dst, int len) { char *d; char *tmp = NULL; const char *t; assert(dst); tmp = (char *)zbx_malloc(tmp, len); zbx_db_escape_string(src, tmp, len, ESCAPE_SEQUENCE_ON); len--; /* '\0' */ for (t = tmp, d = dst; t && *t && len; t++) { if (*t == '_' || *t == '%' || *t == ZBX_SQL_LIKE_ESCAPE_CHAR) { if (len <= 1) break; *d++ = ZBX_SQL_LIKE_ESCAPE_CHAR; len--; } *d++ = *t; len--; } *d = '\0'; zbx_free(tmp); } /****************************************************************************** * * * Return value: escaped string to be used as pattern in LIKE * * * ******************************************************************************/ char *zbx_db_dyn_escape_like_pattern_basic(const char *src) { int len; char *dst = NULL; len = zbx_db_get_escape_like_pattern_len(src); dst = (char *)zbx_malloc(dst, len); zbx_db_escape_like_pattern(src, dst, len); return dst; } /****************************************************************************** * * * Purpose: return the string length to fit into a database field of the * * specified size * * * * Return value: the string length in bytes * * * ******************************************************************************/ int zbx_db_strlen_n(const char *text_loc, size_t maxlen) { return zbx_strlen_utf8_nchars(text_loc, maxlen); } /********************************************************************************* * * * Purpose: determine if a vendor database(MySQL, MariaDB, PostgreSQL, * * ElasticDB) version satisfies Zabbix requirements * * * * Parameters: database - [IN] database name * * current_version - [IN] detected numeric version * * min_version - [IN] minimum required numeric version * * max_version - [IN] maximum required numeric version * * min_supported_version - [IN] minimum supported numeric version * * * * Return value: resulting status flag * * * *********************************************************************************/ int zbx_db_version_check(const char *database, zbx_uint32_t current_version, zbx_uint32_t min_version, zbx_uint32_t max_version, zbx_uint32_t min_supported_version) { int flag; if (ZBX_DBVERSION_UNDEFINED == current_version) { flag = DB_VERSION_FAILED_TO_RETRIEVE; zabbix_log(LOG_LEVEL_WARNING, "Failed to retrieve %s version", database); } else if (min_version > current_version && ZBX_DBVERSION_UNDEFINED != min_version) { flag = DB_VERSION_LOWER_THAN_MINIMUM; zabbix_log(LOG_LEVEL_WARNING, "Unsupported DB! %s version %lu is older than %lu", database, (unsigned long)current_version, (unsigned long)min_version); } else if (max_version < current_version && ZBX_DBVERSION_UNDEFINED != max_version) { flag = DB_VERSION_HIGHER_THAN_MAXIMUM; zabbix_log(LOG_LEVEL_WARNING, "Unsupported DB! %s version %lu is newer than %lu", database, (unsigned long)current_version, (unsigned long)max_version); } else if (min_supported_version > current_version && ZBX_DBVERSION_UNDEFINED != min_supported_version) { flag = DB_VERSION_NOT_SUPPORTED_ERROR; /* log message must be handled by server or proxy */ } else flag = DB_VERSION_SUPPORTED; return flag; } /****************************************************************************** * * * Purpose: prepare json for front-end with the DB current, minimum and * * maximum versions and a flag that indicates if the version * * satisfies the requirements, and other information as well as * * information about DB extension in a similar way * * * * Parameters: json - [IN/OUT] json data * * info - [IN] info to serialize * * * ******************************************************************************/ void zbx_db_version_json_create(struct zbx_json *json, struct zbx_db_version_info_t *info) { zbx_json_addobject(json, NULL); zbx_json_addstring(json, "database", info->database, ZBX_JSON_TYPE_STRING); if (DB_VERSION_FAILED_TO_RETRIEVE != info->flag) zbx_json_addstring(json, "current_version", info->friendly_current_version, ZBX_JSON_TYPE_STRING); zbx_json_addstring(json, "min_version", info->friendly_min_version, ZBX_JSON_TYPE_STRING); zbx_json_addstring(json, "max_version", info->friendly_max_version, ZBX_JSON_TYPE_STRING); zbx_json_addint64(json, "history_pk", info->history_pk); if (NULL != info->friendly_min_supported_version) { zbx_json_addstring(json, "min_supported_version", info->friendly_min_supported_version, ZBX_JSON_TYPE_STRING); } zbx_json_addint64(json, "flag", info->flag); zbx_json_close(json); if (NULL != info->extension) { zbx_json_addobject(json, NULL); zbx_json_addstring(json, "database", info->extension, ZBX_JSON_TYPE_STRING); if (DB_VERSION_FAILED_TO_RETRIEVE != info->ext_flag) { zbx_json_addstring(json, "current_version", info->ext_friendly_current_version, ZBX_JSON_TYPE_STRING); } zbx_json_addstring(json, "min_version", info->ext_friendly_min_version, ZBX_JSON_TYPE_STRING); zbx_json_addstring(json, "max_version", info->ext_friendly_max_version, ZBX_JSON_TYPE_STRING); zbx_json_addstring(json, "min_supported_version", info->ext_friendly_min_supported_version, ZBX_JSON_TYPE_STRING); zbx_json_addint64(json, "flag", info->ext_flag); zbx_json_addint64(json, "extension_err_code", info->ext_err_code); #ifdef HAVE_POSTGRESQL if (0 == zbx_strcmp_null(info->extension, ZBX_DB_EXTENSION_TIMESCALEDB)) { if (ON == zbx_tsdb_get_compression_availability()) { zbx_json_addstring(json, "compression_availability", "true", ZBX_JSON_TYPE_INT); } else { zbx_json_addstring(json, "compression_availability", "false", ZBX_JSON_TYPE_INT); } zbx_json_addint64(json, "compressed_chunks_history", info->history_compressed_chunks); zbx_json_addint64(json, "compressed_chunks_trends", info->trends_compressed_chunks); } #endif zbx_json_close(json); } } /****************************************************************************** * * * Purpose: For PostgreSQL, MySQL and MariaDB: * * returns DBMS version as integer: MMmmuu * * M = major version part * * m = minor version part * * u = patch version part * * * * Example: if the original DB version was 1.2.34 then 10234 gets returned * * * * Return value: DBMS version or DBVERSION_UNDEFINED if unknown * * * ******************************************************************************/ static zbx_uint32_t zbx_dbms_version_get(void) { #if defined(HAVE_MYSQL) return ZBX_MYSQL_SVERSION; #elif defined(HAVE_POSTGRESQL) return ZBX_PG_SVERSION; #else return ZBX_DBVERSION_UNDEFINED; #endif } /*************************************************************************************************************** * * * Purpose: retrieves the DB version info, including numeric version value * * * * For PostgreSQL: * * numeric version is available from the API * * * * For MySQL and MariaDB: * * numeric version is available from the API, but also the additional processing is required * * to determine if it is a MySQL or MariaDB and save this result as well * * * * * **************************************************************************************************************/ void zbx_dbms_version_info_extract(struct zbx_db_version_info_t *version_info) { #define RIGHT2(x) ((int)((zbx_uint32_t)(x) - ((zbx_uint32_t)((x)/100))*100)) #if defined(HAVE_MYSQL) int client_major_version, client_minor_version, client_release_version, server_major_version, server_minor_version, server_release_version; const char *info; zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__); if (NULL != (info = mysql_get_server_info(conn)) && NULL != strstr(info, "MariaDB")) { zabbix_log(LOG_LEVEL_DEBUG, "MariaDB fork detected"); ZBX_MARIADB_SFORK = ON; } if (ON == ZBX_MARIADB_SFORK && 6 == sscanf(info, "%d.%d.%d-%d.%d.%d-MariaDB", &client_major_version, &client_minor_version, &client_release_version, &server_major_version, &server_minor_version, &server_release_version)) { ZBX_MYSQL_SVERSION = server_major_version * 10000 + server_minor_version * 100 + server_release_version; zabbix_log(LOG_LEVEL_DEBUG, "MariaDB subversion detected"); } else ZBX_MYSQL_SVERSION = (zbx_uint32_t)mysql_get_server_version(conn); version_info->current_version = ZBX_MYSQL_SVERSION; version_info->friendly_current_version = zbx_dsprintf(NULL, "%d.%.2d.%.2d", RIGHT2(ZBX_MYSQL_SVERSION/10000), RIGHT2(ZBX_MYSQL_SVERSION/100), RIGHT2(ZBX_MYSQL_SVERSION)); if (ON == ZBX_MARIADB_SFORK) { version_info->database = "MariaDB"; version_info->min_version = ZBX_MARIADB_MIN_VERSION; version_info->max_version = ZBX_MARIADB_MAX_VERSION; version_info->min_supported_version = ZBX_MARIADB_MIN_SUPPORTED_VERSION; version_info->friendly_min_version = ZBX_MARIADB_MIN_VERSION_STR; version_info->friendly_max_version = ZBX_MARIADB_MAX_VERSION_STR; version_info->friendly_min_supported_version = ZBX_MARIADB_MIN_SUPPORTED_VERSION_STR; } else { version_info->database = "MySQL"; version_info->min_version = ZBX_MYSQL_MIN_VERSION; version_info->max_version = ZBX_MYSQL_MAX_VERSION; version_info->min_supported_version = ZBX_MYSQL_MIN_SUPPORTED_VERSION; version_info->friendly_min_version = ZBX_MYSQL_MIN_VERSION_STR; version_info->friendly_max_version = ZBX_MYSQL_MAX_VERSION_STR; version_info->friendly_min_supported_version = ZBX_MYSQL_MIN_SUPPORTED_VERSION_STR; } version_info->flag = zbx_db_version_check(version_info->database, version_info->current_version, version_info->min_version, version_info->max_version, version_info->min_supported_version); #elif defined(HAVE_POSTGRESQL) zbx_uint32_t major; zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__); ZBX_PG_SVERSION = (zbx_uint32_t)PQserverVersion(conn); major = ZBX_PG_SVERSION/10000; version_info->database = "PostgreSQL"; version_info->current_version = ZBX_PG_SVERSION; version_info->min_version = ZBX_POSTGRESQL_MIN_VERSION; version_info->max_version = ZBX_POSTGRESQL_MAX_VERSION; version_info->min_supported_version = ZBX_POSTGRESQL_MIN_SUPPORTED_VERSION; if (10 > major) { version_info->friendly_current_version = zbx_dsprintf(NULL, "%" PRIu32 ".%d.%d", major, RIGHT2(ZBX_PG_SVERSION/100), RIGHT2(ZBX_PG_SVERSION)); } else { version_info->friendly_current_version = zbx_dsprintf(NULL, "%" PRIu32 ".%d", major, RIGHT2(ZBX_PG_SVERSION)); } version_info->friendly_min_version = ZBX_POSTGRESQL_MIN_VERSION_STR; version_info->friendly_max_version = ZBX_POSTGRESQL_MAX_VERSION_STR; version_info->friendly_min_supported_version = ZBX_POSTGRESQL_MIN_SUPPORTED_VERSION_STR; version_info->flag = zbx_db_version_check(version_info->database, version_info->current_version, version_info->min_version, version_info->max_version, version_info->min_supported_version); #else zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__); version_info->flag = DB_VERSION_SUPPORTED; version_info->friendly_current_version = NULL; #endif zabbix_log(LOG_LEVEL_DEBUG, "End of %s() version:%lu", __func__, (unsigned long)zbx_dbms_version_get()); } #ifdef HAVE_POSTGRESQL static int zbx_tsdb_table_has_compressed_chunks(const char *table_names) { zbx_db_result_t result; int ret; result = zbx_db_select_basic("select null from timescaledb_information.chunks" " where hypertable_name in (%s) and is_compressed='t'", table_names); if ((zbx_db_result_t)ZBX_DB_DOWN == result) { ret = FAIL; goto out; } if (NULL != zbx_db_fetch_basic(result)) ret = SUCCEED; else ret = FAIL; out: zbx_db_free_result(result); return ret; } void zbx_tsdb_extract_compressed_chunk_flags(struct zbx_db_version_info_t *version_info) { #define ZBX_TSDB_HISTORY_TABLES "'history_bin','history_uint','history_log','history_str','history_text','history'" #define ZBX_TSDB_TRENDS_TABLES "'trends','trends_uint'" version_info->history_compressed_chunks = (SUCCEED == zbx_tsdb_table_has_compressed_chunks(ZBX_TSDB_HISTORY_TABLES)) ? 1 : 0; version_info->trends_compressed_chunks = (SUCCEED == zbx_tsdb_table_has_compressed_chunks(ZBX_TSDB_TRENDS_TABLES)) ? 1 : 0; #undef ZBX_TSDB_HISTORY_TABLES #undef ZBX_TSDB_TRENDS_TABLES } /*************************************************************************************************************** * * * Purpose: retrieves TimescaleDB extension info, including license string and numeric version value * * * **************************************************************************************************************/ void zbx_tsdb_info_extract(struct zbx_db_version_info_t *version_info) { int tsdb_ver; if (0 != zbx_strcmp_null(version_info->extension, ZBX_DB_EXTENSION_TIMESCALEDB)) return; tsdb_ver = zbx_tsdb_get_version(); version_info->ext_current_version = (zbx_uint32_t)tsdb_ver; version_info->ext_min_version = ZBX_TIMESCALE_MIN_VERSION; version_info->ext_max_version = ZBX_TIMESCALE_MAX_VERSION; version_info->ext_min_supported_version = ZBX_TIMESCALE_MIN_SUPPORTED_VERSION; version_info->ext_friendly_current_version = zbx_dsprintf(NULL, "%d.%d.%d", RIGHT2(tsdb_ver/10000), RIGHT2(tsdb_ver/100), RIGHT2(tsdb_ver)); version_info->ext_friendly_min_version = ZBX_TIMESCALE_MIN_VERSION_STR; version_info->ext_friendly_max_version = ZBX_TIMESCALE_MAX_VERSION_STR; version_info->ext_friendly_min_supported_version = ZBX_TIMESCALE_MIN_SUPPORTED_VERSION_STR; version_info->ext_flag = zbx_db_version_check(version_info->extension, version_info->ext_current_version, version_info->ext_min_version, version_info->ext_max_version, version_info->ext_min_supported_version); zabbix_log(LOG_LEVEL_DEBUG, "TimescaleDB version: [%d]", tsdb_ver); } /****************************************************************************** * * * Purpose: returns TimescaleDB (TSDB) version as integer: MMmmuu * * M = major version part * * m = minor version part * * u = patch version part * * * * Example: TSDB 1.5.1 version will be returned as 10501 * * * * Return value: TSDB version or 0 if unknown or the extension not installed * * * ******************************************************************************/ int zbx_tsdb_get_version(void) { int ver, major, minor, patch; zbx_db_result_t result; zbx_db_row_t row; if (-1 == ZBX_TSDB_VERSION) { /* catalog pg_extension not available */ if (90001 > ZBX_PG_SVERSION) { ver = ZBX_TSDB_VERSION = 0; goto out; } result = zbx_db_select_basic("select extversion from pg_extension where extname = 'timescaledb'"); /* database down, can re-query in the next call */ if ((zbx_db_result_t)ZBX_DB_DOWN == result) { ver = 0; goto out; } /* extension is not installed */ if (NULL == result) { ver = ZBX_TSDB_VERSION = 0; goto out; } if (NULL != (row = zbx_db_fetch_basic(result)) && 3 == sscanf((const char*)row[0], "%d.%d.%d", &major, &minor, &patch)) { ver = major * 10000; ver += minor * 100; ver += patch; ZBX_TSDB_VERSION = ver; } else ver = ZBX_TSDB_VERSION = 0; zbx_db_free_result(result); } else ver = ZBX_TSDB_VERSION; out: return ver; } /****************************************************************************** * * * Purpose: sets TimescaleDB (TSDB) compression availability * * * * Parameters: compression_availabile - [IN] compression availability * * 0 (OFF): compression is not available * * 1 (ON): compression is available * * * ******************************************************************************/ void zbx_tsdb_set_compression_availability(int compression_availabile) { ZBX_TIMESCALE_COMPRESSION_AVAILABLE = compression_availabile; } /****************************************************************************** * * * Purpose: retrieves TimescaleDB (TSDB) compression availability * * * * Return value: compression availability as as integer * * 0 (OFF): compression is not available * * 1 (ON): compression is available * * * ******************************************************************************/ int zbx_tsdb_get_compression_availability(void) { return ZBX_TIMESCALE_COMPRESSION_AVAILABLE; } #endif