/* ** Zabbix ** Copyright (C) 2001-2023 Zabbix SIA ** ** This program is free software; you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 2 of the License, or ** (at your option) any later version. ** ** 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 General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. **/ #include "zbxdb.h" #include "zbxstr.h" #include "zbxtime.h" #include "zbx_dbversion_constants.h" #if defined(HAVE_MYSQL) # include "mysql.h" # include "errmsg.h" # include "mysqld_error.h" #elif defined(HAVE_ORACLE) # include "zbxcrypto.h" # include "zbxdbschema.h" # include "oci.h" #elif defined(HAVE_POSTGRESQL) # include <libpq-fe.h> #elif defined(HAVE_SQLITE3) # include <sqlite3.h> #endif #if defined(HAVE_SQLITE3) # include "zbxmutexs.h" #endif struct zbx_db_result { #if defined(HAVE_MYSQL) MYSQL_RES *result; #elif defined(HAVE_ORACLE) OCIStmt *stmthp; /* the statement handle for select operations */ int ncolumn; zbx_db_row_t values; ub4 *values_alloc; OCILobLocator **clobs; #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 zbx_uint32_t ZBX_MYSQL_SVERSION = ZBX_DBVERSION_UNDEFINED; static int ZBX_MARIADB_SFORK = OFF; #elif defined(HAVE_ORACLE) #include "zbxalgo.h" typedef struct { OCIEnv *envhp; OCIError *errhp; OCISvcCtx *svchp; OCIServer *srvhp; OCIStmt *stmthp; /* the statement handle for execute operations */ zbx_vector_ptr_t db_results; } zbx_oracle_db_handle_t; static zbx_uint32_t ZBX_ORACLE_SVERSION = ZBX_DBVERSION_UNDEFINED; static zbx_oracle_db_handle_t oracle; static ub4 OCI_DBserver_status(void); #define ORA_ERR_UNIQ_CONSTRAINT -1 #elif defined(HAVE_POSTGRESQL) static PGconn *conn = NULL; static unsigned int ZBX_PG_BYTEAOID = 0; 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; #elif defined(HAVE_SQLITE3) static sqlite3 *conn = NULL; static zbx_mutex_t sqlite_access = ZBX_MUTEX_NULL; #endif #if defined(HAVE_ORACLE) static void OCI_DBclean_result_handle(zbx_db_result_t result); static void OCI_DBclean_result(zbx_db_result_t result); #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; 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; } #if defined(HAVE_ORACLE) static const char *zbx_oci_error(sword status, sb4 *err) { static char errbuf[512]; sb4 errcode, *perrcode; perrcode = (NULL == err ? &errcode : err); errbuf[0] = '\0'; *perrcode = 0; switch (status) { case OCI_SUCCESS_WITH_INFO: OCIErrorGet((dvoid *)oracle.errhp, (ub4)1, (text *)NULL, perrcode, (text *)errbuf, (ub4)sizeof(errbuf), OCI_HTYPE_ERROR); break; case OCI_NEED_DATA: zbx_snprintf(errbuf, sizeof(errbuf), "%s", "OCI_NEED_DATA"); break; case OCI_NO_DATA: zbx_snprintf(errbuf, sizeof(errbuf), "%s", "OCI_NODATA"); break; case OCI_ERROR: OCIErrorGet((dvoid *)oracle.errhp, (ub4)1, (text *)NULL, perrcode, (text *)errbuf, (ub4)sizeof(errbuf), OCI_HTYPE_ERROR); break; case OCI_INVALID_HANDLE: zbx_snprintf(errbuf, sizeof(errbuf), "%s", "OCI_INVALID_HANDLE"); break; case OCI_STILL_EXECUTING: zbx_snprintf(errbuf, sizeof(errbuf), "%s", "OCI_STILL_EXECUTING"); break; case OCI_CONTINUE: zbx_snprintf(errbuf, sizeof(errbuf), "%s", "OCI_CONTINUE"); break; } zbx_rtrim(errbuf, ZBX_WHITESPACE); return errbuf; } /****************************************************************************** * * * Purpose: handles Oracle prepare/bind/execute/select operation error * * * * Parameters: zerrcode - [IN] the Zabbix errorcode for the failed database * * operation * * oci_error - [IN] the return code from failed Oracle operation * * sql - [IN] the failed sql statement (can be NULL) * * * * Return value: ZBX_DB_DOWN - database connection is down * * ZBX_DB_FAIL - otherwise * * * * Comments: This function logs the error description and checks the * * database connection status. * * * ******************************************************************************/ static int OCI_handle_sql_error(int zerrcode, sword oci_error, const char *sql) { sb4 errcode; int ret = ZBX_DB_DOWN; zbx_db_errlog(zerrcode, oci_error, zbx_oci_error(oci_error, &errcode), sql); /* after ORA-02396 (and consequent ORA-01012) errors the OCI_SERVER_NORMAL server status is still returned */ switch (errcode) { case 1012: /* ORA-01012: not logged on */ case 2396: /* ORA-02396: exceeded maximum idle time */ goto out; } if (OCI_SERVER_NORMAL == OCI_DBserver_status()) ret = ZBX_DB_FAIL; out: return ret; } #endif /* HAVE_ORACLE */ #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 CR_SSL_CONNECTION_ERROR case CR_SSL_CONNECTION_ERROR: #endif #ifdef ER_CONNECTION_KILLED case ER_CONNECTION_KILLED: #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), "40P01")) 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) #if LIBMYSQL_VERSION_ID >= 80000 /* my_bool type is removed in MySQL 8.0 */ bool mysql_reconnect = 1; #else my_bool mysql_reconnect = 1; #endif int err_no = 0; #elif defined(HAVE_ORACLE) char *connect = NULL; sword err = OCI_SUCCESS; static ub2 csid = 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; } /* The RECONNECT option setting is placed here, AFTER the connection */ /* is made, due to a bug in MySQL versions prior to 5.1.6 where it */ /* reset the options value to the default, regardless of what it was */ /* set to prior to the connection. MySQL allows changing connection */ /* options on an open connection, so setting it here is safe. */ if (ZBX_DB_OK == ret && 0 != mysql_options(conn, MYSQL_OPT_RECONNECT, &mysql_reconnect)) zabbix_log(LOG_LEVEL_WARNING, "Cannot set MySQL reconnect option."); 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; #elif defined(HAVE_ORACLE) memset(&oracle, 0, sizeof(oracle)); zbx_vector_ptr_create(&oracle.db_results); /* connection string format: [//]host[:port][/service name] */ if ('\0' != *cfg->config_dbhost) { /* Easy Connect method */ connect = zbx_strdcatf(connect, "//%s", cfg->config_dbhost); if (0 != cfg->config_dbport) connect = zbx_strdcatf(connect, ":%d", cfg->config_dbport); if ('\0' != *cfg->config_dbname) connect = zbx_strdcatf(connect, "/%s", cfg->config_dbname); } else { /* Net Service Name method */ connect = zbx_strdup(connect, cfg->config_dbname); } while (ZBX_DB_OK == ret) { /* initialize environment */ if (OCI_SUCCESS == (err = OCIEnvNlsCreate((OCIEnv **)&oracle.envhp, (ub4)OCI_DEFAULT, (dvoid *)0, (dvoid * (*)(dvoid *,size_t))0, (dvoid * (*)(dvoid *, dvoid *, size_t))0, (void (*)(dvoid *, dvoid *))0, (size_t)0, (dvoid **)0, csid, csid))) { if (0 != csid) break; /* environment with UTF8 character set successfully created */ /* try to find out the id of UTF8 character set */ if (0 == (csid = OCINlsCharSetNameToId(oracle.envhp, (const oratext *)"UTF8"))) { zabbix_log(LOG_LEVEL_WARNING, "Cannot find out the ID of \"UTF8\" character set." " Relying on current \"NLS_LANG\" settings."); break; /* use default environment with character set derived from NLS_LANG */ } /* get rid of this environment to create a better one on the next iteration */ OCIHandleFree((dvoid *)oracle.envhp, OCI_HTYPE_ENV); oracle.envhp = NULL; } else { zbx_db_errlog(ERR_Z3001, err, zbx_oci_error(err, NULL), connect); ret = ZBX_DB_FAIL; } } if (ZBX_DB_OK == ret) { /* allocate an error handle */ (void)OCIHandleAlloc((dvoid *)oracle.envhp, (dvoid **)&oracle.errhp, OCI_HTYPE_ERROR, (size_t)0, (dvoid **)0); /* get the session */ err = OCILogon2(oracle.envhp, oracle.errhp, &oracle.svchp, (text *)cfg->config_dbuser, (ub4)(NULL != cfg->config_dbuser ? strlen(cfg->config_dbuser) : 0), (text *)cfg->config_dbpassword, (ub4)(NULL != cfg->config_dbpassword ? strlen(cfg->config_dbpassword) : 0), (text *)connect, (ub4)strlen(connect), OCI_DEFAULT); switch (err) { case OCI_SUCCESS_WITH_INFO: zabbix_log(LOG_LEVEL_WARNING, "%s", zbx_oci_error(err, NULL)); /* break; is not missing here */ ZBX_FALLTHROUGH; case OCI_SUCCESS: err = OCIAttrGet((void *)oracle.svchp, OCI_HTYPE_SVCCTX, (void *)&oracle.srvhp, (ub4 *)0, OCI_ATTR_SERVER, oracle.errhp); } if (OCI_SUCCESS != err) { zbx_db_errlog(ERR_Z3001, err, zbx_oci_error(err, NULL), connect); ret = ZBX_DB_DOWN; } } if (ZBX_DB_OK == ret) { /* initialize statement handle */ err = OCIHandleAlloc((dvoid *)oracle.envhp, (dvoid **)&oracle.stmthp, OCI_HTYPE_STMT, (size_t)0, (dvoid **)0); if (OCI_SUCCESS != err) { zbx_db_errlog(ERR_Z3001, err, zbx_oci_error(err, NULL), connect); ret = ZBX_DB_DOWN; } } if (ZBX_DB_OK == ret) { if (0 < (ret = zbx_db_execute_basic("alter session set nls_numeric_characters='. '"))) ret = ZBX_DB_OK; } zbx_free(connect); #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; result = zbx_db_select_basic("select oid from pg_type where typname='bytea'"); 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_BYTEAOID = atoi(row[0]); zbx_db_free_result(result); /* 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; } 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_ORACLE) if (0 != oracle.db_results.values_num) { int i; zabbix_log(LOG_LEVEL_WARNING, "cannot process queries: database is closed"); for (i = 0; i < oracle.db_results.values_num; i++) { /* deallocate all handles before environment is deallocated */ OCI_DBclean_result_handle(oracle.db_results.values[i]); } } /* deallocate statement handle */ if (NULL != oracle.stmthp) { OCIHandleFree((dvoid *)oracle.stmthp, OCI_HTYPE_STMT); oracle.stmthp = NULL; } if (NULL != oracle.svchp) { OCILogoff(oracle.svchp, oracle.errhp); oracle.svchp = NULL; } if (NULL != oracle.errhp) { OCIHandleFree(oracle.errhp, OCI_HTYPE_ERROR); oracle.errhp = NULL; } if (NULL != oracle.srvhp) { OCIHandleFree(oracle.srvhp, OCI_HTYPE_SERVER); oracle.srvhp = NULL; } if (NULL != oracle.envhp) { /* delete the environment handle, which deallocates all other handles associated with it */ OCIHandleFree((dvoid *)oracle.envhp, OCI_HTYPE_ENV); oracle.envhp = NULL; } zbx_vector_ptr_destroy(&oracle.db_results); #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); } 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--; 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; #ifdef HAVE_ORACLE sword err; #endif 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_ORACLE) if (OCI_SUCCESS != (err = OCITransCommit(oracle.svchp, oracle.errhp, OCI_DEFAULT))) rc = OCI_handle_sql_error(ERR_Z3005, err, "commit failed"); #elif 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; #ifdef HAVE_ORACLE sword err; #endif 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_ORACLE) if (OCI_SUCCESS != (err = OCITransRollback(oracle.svchp, oracle.errhp, OCI_DEFAULT))) rc = OCI_handle_sql_error(ERR_Z3005, err, "rollback failed"); #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; } #ifdef HAVE_ORACLE static sword zbx_oracle_statement_prepare(const char *sql) { return OCIStmtPrepare(oracle.stmthp, oracle.errhp, (text *)sql, (ub4)strlen((char *)sql), (ub4)OCI_NTV_SYNTAX, (ub4)OCI_DEFAULT); } static sword zbx_oracle_statement_execute(ub4 iters, ub4 *nrows) { sword err; if (OCI_SUCCESS == (err = OCIStmtExecute(oracle.svchp, oracle.stmthp, oracle.errhp, iters, (ub4)0, (CONST OCISnapshot *)NULL, (OCISnapshot *)NULL, 0 == txn_level ? OCI_COMMIT_ON_SUCCESS : OCI_DEFAULT))) { err = OCIAttrGet((void *)oracle.stmthp, OCI_HTYPE_STMT, nrows, (ub4 *)0, OCI_ATTR_ROW_COUNT, oracle.errhp); } return err; } #endif #ifdef HAVE_ORACLE int zbx_db_statement_prepare_basic(const char *sql) { sword err; int ret = ZBX_DB_OK; 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] within failed transaction", txn_level); return ZBX_DB_FAIL; } zabbix_log(LOG_LEVEL_DEBUG, "query [txnlev:%d] [%s]", txn_level, sql); if (OCI_SUCCESS != (err = zbx_oracle_statement_prepare(sql))) ret = OCI_handle_sql_error(ERR_Z3005, err, sql); if (ZBX_DB_FAIL == ret && 0 < txn_level) { zabbix_log(LOG_LEVEL_DEBUG, "query [%s] failed, setting transaction as failed", sql); txn_error = ZBX_DB_FAIL; } return ret; } /****************************************************************************** * * * Purpose: callback function used by dynamic parameter binding * * * ******************************************************************************/ static sb4 db_bind_dynamic_cb(dvoid *ctxp, OCIBind *bindp, ub4 iter, ub4 index, dvoid **bufpp, ub4 *alenp, ub1 *piecep, dvoid **indpp) { zbx_db_bind_context_t *context = (zbx_db_bind_context_t *)ctxp; ZBX_UNUSED(bindp); ZBX_UNUSED(index); switch (context->type) { case ZBX_TYPE_ID: /* handle 0 -> NULL conversion */ if (0 == context->rows[iter][context->position].ui64) { *bufpp = NULL; *alenp = 0; break; } ZBX_FALLTHROUGH; case ZBX_TYPE_UINT: *bufpp = &((OCINumber *)context->data)[iter]; *alenp = sizeof(OCINumber); break; case ZBX_TYPE_INT: *bufpp = &context->rows[iter][context->position].i32; *alenp = sizeof(int); break; case ZBX_TYPE_FLOAT: *bufpp = &context->rows[iter][context->position].dbl; *alenp = sizeof(double); break; case ZBX_TYPE_CHAR: case ZBX_TYPE_TEXT: case ZBX_TYPE_SHORTTEXT: case ZBX_TYPE_LONGTEXT: case ZBX_TYPE_CUID: case ZBX_TYPE_BLOB: *bufpp = context->rows[iter][context->position].str; *alenp = ((size_t *)context->data)[iter]; break; default: return FAIL; } *indpp = NULL; *piecep = OCI_ONE_PIECE; return OCI_CONTINUE; } /****************************************************************************** * * * Purpose: performs dynamic parameter binding, converting value if necessary * * * * Parameters: context - [OUT] the bind context * * position - [IN] the parameter position * * type - [IN] the parameter type (ZBX_TYPE_* ) * * rows - [IN] the data to bind - array of rows, * * each row being an array of columns * * rows_num - [IN] the number of rows in the data * * * ******************************************************************************/ int zbx_db_bind_parameter_dyn(zbx_db_bind_context_t *context, int position, unsigned char type, zbx_db_value_t **rows, int rows_num) { int i, ret = ZBX_DB_OK; size_t *sizes; sword err; OCINumber *values; ub2 data_type; OCIBind *bindhp = NULL; context->position = position; context->rows = rows; context->data = NULL; context->type = type; switch (type) { case ZBX_TYPE_ID: case ZBX_TYPE_UINT: values = (OCINumber *)zbx_malloc(NULL, sizeof(OCINumber) * rows_num); for (i = 0; i < rows_num; i++) { err = OCINumberFromInt(oracle.errhp, &rows[i][position].ui64, sizeof(zbx_uint64_t), OCI_NUMBER_UNSIGNED, &values[i]); if (OCI_SUCCESS != err) { ret = OCI_handle_sql_error(ERR_Z3007, err, NULL); goto out; } } context->data = (OCINumber *)values; context->size_max = sizeof(OCINumber); data_type = SQLT_VNU; break; case ZBX_TYPE_INT: context->size_max = sizeof(int); data_type = SQLT_INT; break; case ZBX_TYPE_FLOAT: context->size_max = sizeof(double); data_type = SQLT_BDOUBLE; break; case ZBX_TYPE_CHAR: case ZBX_TYPE_TEXT: case ZBX_TYPE_SHORTTEXT: case ZBX_TYPE_LONGTEXT: case ZBX_TYPE_CUID: sizes = (size_t *)zbx_malloc(NULL, sizeof(size_t) * rows_num); context->size_max = 0; for (i = 0; i < rows_num; i++) { sizes[i] = strlen(rows[i][position].str); if (sizes[i] > context->size_max) context->size_max = sizes[i]; } context->data = sizes; data_type = SQLT_LNG; break; case ZBX_TYPE_BLOB: sizes = (size_t *)zbx_malloc(NULL, sizeof(size_t) * rows_num); context->size_max = 0; for (i = 0; i < rows_num; i++) { size_t dst_len; size_t src_len = strlen(rows[i][position].str) * 3 / 4 + 1; char *dst = (char*)zbx_malloc(NULL, src_len); zbx_base64_decode(rows[i][position].str, (char *)dst, src_len, &dst_len); sizes[i] = dst_len; zbx_free(rows[i][position].str); rows[i][position].str = dst; if (sizes[i] > context->size_max) context->size_max = sizes[i]; } context->data = sizes; data_type = SQLT_BIN; break; default: THIS_SHOULD_NEVER_HAPPEN; exit(EXIT_FAILURE); } err = OCIBindByPos(oracle.stmthp, &bindhp, oracle.errhp, context->position + 1, NULL, context->size_max, data_type, NULL, NULL, NULL, 0, NULL, (ub4)OCI_DATA_AT_EXEC); if (OCI_SUCCESS != err) { ret = OCI_handle_sql_error(ERR_Z3007, err, NULL); if (ZBX_DB_FAIL == ret && 0 < txn_level) { zabbix_log(LOG_LEVEL_DEBUG, "query failed, setting transaction as failed"); txn_error = ZBX_DB_FAIL; } goto out; } err = OCIBindDynamic(bindhp, oracle.errhp, (dvoid *)context, db_bind_dynamic_cb, NULL, NULL); if (OCI_SUCCESS != err) { ret = OCI_handle_sql_error(ERR_Z3007, err, NULL); if (ZBX_DB_FAIL == ret && 0 < txn_level) { zabbix_log(LOG_LEVEL_DEBUG, "query failed, setting transaction as failed"); txn_error = ZBX_DB_FAIL; } goto out; } out: if (ret != ZBX_DB_OK) zbx_db_clean_bind_context(context); return ret; } void zbx_db_clean_bind_context(zbx_db_bind_context_t *context) { zbx_free(context->data); } int zbx_db_statement_execute(int iters) { sword err; ub4 nrows; int ret; if (ZBX_DB_OK != txn_error) { zabbix_log(LOG_LEVEL_DEBUG, "ignoring query [txnlev:%d] within failed transaction", txn_level); ret = ZBX_DB_FAIL; goto out; } if (OCI_SUCCESS != (err = zbx_oracle_statement_execute(iters, &nrows))) ret = OCI_handle_sql_error((ORA_ERR_UNIQ_CONSTRAINT == err ? ERR_Z3008 : ERR_Z3007), err, NULL); else ret = (int)nrows; if (ZBX_DB_FAIL == ret && 0 < txn_level) { zabbix_log(LOG_LEVEL_DEBUG, "query failed, setting transaction as failed"); txn_error = ZBX_DB_FAIL; } out: zabbix_log(LOG_LEVEL_DEBUG, "%s():%d", __func__, ret); return ret; } #endif #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 /****************************************************************************** * * * 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; int ret = ZBX_DB_OK; double sec = 0; #if defined(HAVE_MYSQL) #elif defined(HAVE_ORACLE) sword err = OCI_SUCCESS; #elif 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, sql); ret = ZBX_DB_FAIL; goto clean; } zabbix_log(LOG_LEVEL_DEBUG, "query [txnlev:%d] [%s]", txn_level, sql); #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); 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); 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_ORACLE) if (OCI_SUCCESS == (err = zbx_oracle_statement_prepare(sql))) { ub4 nrows = 0; if (OCI_SUCCESS == (err = zbx_oracle_statement_execute(1, &nrows))) ret = (int)nrows; } if (OCI_SUCCESS != err) ret = OCI_handle_sql_error((err == ORA_ERR_UNIQ_CONSTRAINT ? ERR_Z3008 : ERR_Z3005), err, sql); #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), "23505")) errcode = ERR_Z3008; 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, sql); } if (ZBX_DB_FAIL == ret && 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 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_ORACLE) sword err = OCI_SUCCESS; ub4 prefetch_rows = 200, counter; ZBX_UNUSED(counter); #elif 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; err_no = (int)mysql_errno(conn); 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_ORACLE) result = zbx_malloc(NULL, sizeof(struct zbx_db_result)); memset(result, 0, sizeof(struct zbx_db_result)); zbx_vector_ptr_append(&oracle.db_results, result); err = OCIHandleAlloc((dvoid *)oracle.envhp, (dvoid **)&result->stmthp, OCI_HTYPE_STMT, (size_t)0, (dvoid **)0); /* Prefetching when working with Oracle is needed because otherwise it fetches only 1 row at a time when doing */ /* selects (default behavior). There are 2 ways to do prefetching: memory based and rows based. Based on the */ /* study optimal (speed-wise) memory based prefetch is 2 MB. But in case of many subsequent selects CPU usage */ /* jumps up to 100 %. Using rows prefetch with up to 200 rows does not affect CPU usage, it is the same as */ /* without prefetching at all. See ZBX-5920, ZBX-6493 for details. */ /* */ /* Tested on Oracle 11gR2. */ /* */ /* Oracle info: docs.oracle.com/cd/B28359_01/appdev.111/b28395/oci04sql.htm */ if (OCI_SUCCESS == err) { err = OCIAttrSet(result->stmthp, OCI_HTYPE_STMT, &prefetch_rows, sizeof(prefetch_rows), OCI_ATTR_PREFETCH_ROWS, oracle.errhp); } if (OCI_SUCCESS == err) { err = OCIStmtPrepare(result->stmthp, oracle.errhp, (text *)sql, (ub4)strlen((char *)sql), (ub4)OCI_NTV_SYNTAX, (ub4)OCI_DEFAULT); } if (OCI_SUCCESS == err) { err = OCIStmtExecute(oracle.svchp, result->stmthp, oracle.errhp, (ub4)0, (ub4)0, (CONST OCISnapshot *)NULL, (OCISnapshot *)NULL, 0 == txn_level ? OCI_COMMIT_ON_SUCCESS : OCI_DEFAULT); } if (OCI_SUCCESS == err) { /* get the number of columns in the query */ err = OCIAttrGet((void *)result->stmthp, OCI_HTYPE_STMT, (void *)&result->ncolumn, (ub4 *)0, OCI_ATTR_PARAM_COUNT, oracle.errhp); } if (OCI_SUCCESS != err) goto error; assert(0 < result->ncolumn); result->values = zbx_malloc(NULL, result->ncolumn * sizeof(char *)); result->clobs = zbx_malloc(NULL, result->ncolumn * sizeof(OCILobLocator *)); result->values_alloc = zbx_malloc(NULL, result->ncolumn * sizeof(ub4)); memset(result->values, 0, result->ncolumn * sizeof(char *)); memset(result->clobs, 0, result->ncolumn * sizeof(OCILobLocator *)); memset(result->values_alloc, 0, result->ncolumn * sizeof(ub4)); for (counter = 1; OCI_SUCCESS == err && counter <= (ub4)result->ncolumn; counter++) { OCIParam *parmdp = NULL; OCIDefine *defnp = NULL; ub4 char_semantics; ub2 col_width = 0, data_type = 0; /* request a parameter descriptor in the select-list */ err = OCIParamGet((void *)result->stmthp, OCI_HTYPE_STMT, oracle.errhp, (void **)&parmdp, (ub4)counter); if (OCI_SUCCESS == err) { /* retrieve the data type for the column */ err = OCIAttrGet((void *)parmdp, OCI_DTYPE_PARAM, (dvoid *)&data_type, (ub4 *)NULL, (ub4)OCI_ATTR_DATA_TYPE, (OCIError *)oracle.errhp); } if (SQLT_CLOB == data_type) { if (OCI_SUCCESS == err) { /* allocate the lob locator variable */ err = OCIDescriptorAlloc((dvoid *)oracle.envhp, (dvoid **)&result->clobs[counter - 1], OCI_DTYPE_LOB, (size_t)0, (dvoid **)0); } if (OCI_SUCCESS == err) { /* associate clob var with its define handle */ err = OCIDefineByPos((void *)result->stmthp, &defnp, (OCIError *)oracle.errhp, (ub4)counter, (dvoid *)&result->clobs[counter - 1], (sb4)-1, data_type, (dvoid *)0, (ub2 *)0, (ub2 *)0, (ub4)OCI_DEFAULT); } } else { if (SQLT_IBDOUBLE != data_type && SQLT_BDOUBLE != data_type) { if (OCI_SUCCESS == err) { /* retrieve the length semantics for the column */ char_semantics = 0; err = OCIAttrGet((void *)parmdp, (ub4)OCI_DTYPE_PARAM, (void *)&char_semantics, (ub4 *)NULL, (ub4)OCI_ATTR_CHAR_USED, (OCIError *)oracle.errhp); } if (OCI_SUCCESS == err) { if (0 != char_semantics) { /* retrieve the column width in characters */ err = OCIAttrGet((void *)parmdp, (ub4)OCI_DTYPE_PARAM, (void *)&col_width, (ub4 *)NULL, (ub4)OCI_ATTR_CHAR_SIZE, (OCIError *)oracle.errhp); /* adjust for UTF-8 */ col_width *= 4; } else { #define ZBX_MIN_OCI_NUMBER_WIDTH 22 /* retrieve the column width in bytes */ err = OCIAttrGet((void *)parmdp, (ub4)OCI_DTYPE_PARAM, (void *)&col_width, (ub4 *)NULL, (ub4)OCI_ATTR_DATA_SIZE, (OCIError *)oracle.errhp); if (ZBX_MIN_OCI_NUMBER_WIDTH > col_width && SQLT_NUM == data_type) { col_width = ZBX_MIN_OCI_NUMBER_WIDTH; } #undef ZBX_MIN_OCI_NUMBER_WIDTH } } col_width++; /* add 1 byte for terminating '\0' */ } else col_width = ZBX_MAX_DOUBLE_LEN + 1; result->values_alloc[counter - 1] = col_width; result->values[counter - 1] = zbx_malloc(NULL, col_width); *result->values[counter - 1] = '\0'; if (OCI_SUCCESS == err) { /* represent any data as characters */ err = OCIDefineByPos(result->stmthp, &defnp, oracle.errhp, counter, (dvoid *)result->values[counter - 1], col_width, SQLT_STR, (dvoid *)0, (ub2 *)0, (ub2 *)0, OCI_DEFAULT); } } /* free cell descriptor */ OCIDescriptorFree(parmdp, OCI_DTYPE_PARAM); parmdp = NULL; } error: if (OCI_SUCCESS != err) { int server_status; server_status = OCI_handle_sql_error(ERR_Z3005, err, sql); zbx_db_free_result(result); result = (ZBX_DB_DOWN == server_status ? (zbx_db_result_t)(intptr_t)server_status : 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_ORACLE) return zbx_db_select_basic("select * from (%s) where rownum<=%d", query, n); #elif defined(HAVE_MYSQL) || defined(HAVE_POSTGRESQL) || defined(HAVE_SQLITE3) return zbx_db_select_basic("%s limit %d", query, n); #endif } #ifdef HAVE_POSTGRESQL /****************************************************************************** * * * Purpose: converts the null terminated string into binary buffer * * * * Transformations: * * \ooo == a byte whose value = ooo (ooo is an octal number) * * \\ == \ * * * * Parameters: * * io - [IN/OUT] null terminated string / binary data * * * * Return value: length of the binary buffer * * * ******************************************************************************/ static size_t zbx_db_bytea_unescape(u_char *io) { const u_char *i = io; u_char *o = io; while ('\0' != *i) { switch (*i) { case '\\': i++; if ('\\' == *i) { *o++ = *i++; } else { if (0 != isdigit(i[0]) && 0 != isdigit(i[1]) && 0 != isdigit(i[2])) { *o = (*i++ - 0x30) << 6; *o += (*i++ - 0x30) << 3; *o++ += *i++ - 0x30; } } break; default: *o++ = *i++; } } return o - io; } #endif #if defined(HAVE_ORACLE) static void db_set_fetch_error(int dberr) { if (0 < txn_level) { zabbix_log(LOG_LEVEL_DEBUG, "fetch failed, setting transaction as failed"); txn_error = dberr; } } #endif zbx_db_row_t zbx_db_fetch_basic(zbx_db_result_t result) { #if defined(HAVE_ORACLE) int i; sword rc; static char errbuf[512]; sb4 errcode; #endif 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_ORACLE) if (NULL == result->stmthp) return NULL; if (OCI_NO_DATA == (rc = OCIStmtFetch2(result->stmthp, oracle.errhp, 1, OCI_FETCH_NEXT, 0, OCI_DEFAULT))) return NULL; if (OCI_SUCCESS != rc) { ub4 rows_fetched; ub4 sizep = sizeof(ub4); if (OCI_SUCCESS != (rc = OCIErrorGet((dvoid *)oracle.errhp, (ub4)1, (text *)NULL, &errcode, (text *)errbuf, (ub4)sizeof(errbuf), OCI_HTYPE_ERROR))) { zbx_db_errlog(ERR_Z3006, rc, zbx_oci_error(rc, NULL), NULL); db_set_fetch_error(ZBX_DB_FAIL); return NULL; } switch (errcode) { case 1012: /* ORA-01012: not logged on */ case 2396: /* ORA-02396: exceeded maximum idle time */ case 3113: /* ORA-03113: end-of-file on communication channel */ case 3114: /* ORA-03114: not connected to ORACLE */ zbx_db_errlog(ERR_Z3006, errcode, errbuf, NULL); db_set_fetch_error(ZBX_DB_DOWN); return NULL; default: rc = OCIAttrGet((void *)result->stmthp, (ub4)OCI_HTYPE_STMT, (void *)&rows_fetched, (ub4 *)&sizep, (ub4)OCI_ATTR_ROWS_FETCHED, (OCIError *)oracle.errhp); if (OCI_SUCCESS != rc || 1 != rows_fetched) { zbx_db_errlog(ERR_Z3006, errcode, errbuf, NULL); db_set_fetch_error(ZBX_DB_FAIL); return NULL; } } } for (i = 0; i < result->ncolumn; i++) { ub4 alloc, amount; ub1 csfrm; sword rc2; if (NULL == result->clobs[i]) continue; if (OCI_SUCCESS != (rc2 = OCILobGetLength(oracle.svchp, oracle.errhp, result->clobs[i], &amount))) { /* If the LOB is NULL, the length is undefined. */ /* In this case the function returns OCI_INVALID_HANDLE. */ if (OCI_INVALID_HANDLE != rc2) { zbx_db_errlog(ERR_Z3006, rc2, zbx_oci_error(rc2, NULL), NULL); db_set_fetch_error(ZBX_DB_FAIL); return NULL; } else amount = 0; } else if (OCI_SUCCESS != (rc2 = OCILobCharSetForm(oracle.envhp, oracle.errhp, result->clobs[i], &csfrm))) { zbx_db_errlog(ERR_Z3006, rc2, zbx_oci_error(rc2, NULL), NULL); db_set_fetch_error(ZBX_DB_FAIL); return NULL; } if (result->values_alloc[i] < (alloc = amount * ZBX_MAX_BYTES_IN_UTF8_CHAR + 1)) { result->values_alloc[i] = alloc; result->values[i] = zbx_realloc(result->values[i], result->values_alloc[i]); } if (OCI_SUCCESS == rc2) { if (OCI_SUCCESS != (rc2 = OCILobRead(oracle.svchp, oracle.errhp, result->clobs[i], &amount, (ub4)1, (dvoid *)result->values[i], (ub4)(result->values_alloc[i] - 1), (dvoid *)NULL, (OCICallbackLobRead)NULL, (ub2)0, csfrm))) { zbx_db_errlog(ERR_Z3006, rc2, zbx_oci_error(rc2, NULL), NULL); db_set_fetch_error(ZBX_DB_FAIL); return NULL; } } result->values[i][amount] = '\0'; } return result->values; #elif defined(HAVE_POSTGRESQL) /* free old data */ if (NULL != result->values) zbx_free(result->values); /* EOF */ if (result->cursor == result->row_num) return NULL; /* init result */ result->fld_num = PQnfields(result->pg_result); if (result->fld_num > 0) { int i; result->values = zbx_malloc(result->values, sizeof(char *) * result->fld_num); for (i = 0; i < result->fld_num; i++) { if (PQgetisnull(result->pg_result, result->cursor, i)) { result->values[i] = NULL; } else { result->values[i] = PQgetvalue(result->pg_result, result->cursor, i); if (PQftype(result->pg_result, i) == ZBX_PG_BYTEAOID) /* binary data type BYTEAOID */ zbx_db_bytea_unescape((u_char *)result->values[i]); } } } 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; #ifdef HAVE_ORACLE if ('\0' == *field) return SUCCEED; #endif return FAIL; } #ifdef HAVE_ORACLE static void OCI_DBclean_result_handle(zbx_db_result_t result) { if (NULL != result->values) { int i; for (i = 0; i < result->ncolumn; i++) { /* deallocate the lob locator variable */ if (NULL != result->clobs[i]) { OCIDescriptorFree((dvoid *)result->clobs[i], OCI_DTYPE_LOB); result->clobs[i] = NULL; } } } if (result->stmthp) { OCIHandleFree((dvoid *)result->stmthp, OCI_HTYPE_STMT); result->stmthp = NULL; } } static void OCI_DBclean_result(zbx_db_result_t result) { if (NULL == result) return; OCI_DBclean_result_handle(result); if (NULL != result->values) { int i; for (i = 0; i < result->ncolumn; i++) zbx_free(result->values[i]); zbx_free(result->values); zbx_free(result->clobs); zbx_free(result->values_alloc); } } #endif 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_ORACLE) int i; if (NULL == result) return; OCI_DBclean_result(result); for (i = 0; i < oracle.db_results.values_num; i++) { if (oracle.db_results.values[i] == result) { zbx_vector_ptr_remove_noorder(&oracle.db_results, i); break; } } 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 */ } #ifdef HAVE_ORACLE /* server status: OCI_SERVER_NORMAL or OCI_SERVER_NOT_CONNECTED */ static ub4 OCI_DBserver_status(void) { sword err; ub4 server_status = OCI_SERVER_NOT_CONNECTED; err = OCIAttrGet((void *)oracle.srvhp, OCI_HTYPE_SERVER, (void *)&server_status, (ub4 *)0, OCI_ATTR_SERVER_STATUS, (OCIError *)oracle.errhp); if (OCI_SUCCESS != err) zabbix_log(LOG_LEVEL_WARNING, "cannot determine Oracle server status, assuming not connected"); return server_status; } #endif /* HAVE_ORACLE */ 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 '!' (Oracle, 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 '\' (Oracle, 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, * * Oracle, 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); #ifdef HAVE_ORACLE if (0 != info->tables_json.buffer_offset) { zbx_json_addobject(json, "schema_diff"); if (0 != strcmp(info->tables_json.buffer, "{}")) zbx_json_addraw(json, "tables", info->tables_json.buffer); zbx_json_close(json); } #endif 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 * * * * Purpose: For OracleDB: * * returns DBMS version as integer: MRruRRivUU * * MR = major release version part * * ru = release update version part * * RR = release update version revision part * * iv = increment version part * * UU = unused, reserved for future use * * * * Example: if the OracleDB version was 18.1.0.0.7 then 1801000007 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; #elif defined(HAVE_ORACLE) return ZBX_ORACLE_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 * * * * For Oracle: * * numeric version needs to be manually parsed from the string result * * Oracle DB format is like 18.1.2.3.0 where * * 18 - major release version * * 1 - release update version * * 2 - release update version revision * * 3 - increment version * * 0 - unused, reserved for future use * * * * Oracle Examples: * * For "Oracle Database 18c Express Edition Release 1.0.0.0.0 - Production" => 100000000 * * For "Oracle Database 18c Express Edition Release 18.2.0.0.7 - Production" => 1802000007 * * For "Oracle Database 18c Express Edition Release 0.0.34.123.7 - Production" => DBVERSION_UNDEFINED * * For "Oracle Database 18c Express Edition Release 1.0.3.x.7 - Production" => DBVERISON_UNDEFINED * * For "<anything else>" => DBVERSION_UNDEFINED * * * **************************************************************************************************************/ 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); #elif defined(HAVE_ORACLE) # ifdef HAVE_OCI_SERVER_RELEASE2 char *version_str = "Version "; ub4 oci_ver = 0; # endif char *start, *release_str = "Release "; char version_friendly[MAX_STRING_LEN / 8]; int major_release_version, release_update_version, release_update_version_revision, increment_version, reserved_for_future_use, overall_status = SUCCEED; zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__); # ifdef HAVE_OCI_SERVER_RELEASE2 if (OCI_SUCCESS != OCIServerRelease2(oracle.svchp, oracle.errhp, (OraText *) version_friendly, (ub4)sizeof(version_friendly), OCI_HTYPE_SVCCTX, &oci_ver, OCI_DEFAULT)) # else if (OCI_SUCCESS != OCIServerVersion(oracle.svchp, oracle.errhp, (OraText *) version_friendly, (ub4)sizeof(version_friendly), OCI_HTYPE_SVCCTX)) # endif { overall_status = FAIL; goto out; } zabbix_log(LOG_LEVEL_DEBUG, "OracleDB version retrieved unparsed: %s", version_friendly); if ( # ifdef HAVE_OCI_SERVER_RELEASE2 NULL != (start = strstr(version_friendly, version_str)) || # endif NULL != (start = strstr(version_friendly, release_str))) { size_t next_start_index; next_start_index = start - version_friendly + strlen(release_str); /* same length for version_str */ if (5 != sscanf(version_friendly + next_start_index, "%d.%d.%d.%d.%d", &major_release_version, &release_update_version, &release_update_version_revision, &increment_version, &reserved_for_future_use) || major_release_version >= 100 || major_release_version <= 0 || release_update_version >= 100 || release_update_version < 0 || release_update_version_revision >= 100 || release_update_version_revision < 0 || increment_version >= 100 || increment_version < 0) { zabbix_log(LOG_LEVEL_WARNING, "Unexpected Oracle DB version format: %s", version_friendly); overall_status = FAIL; } } else { zabbix_log(LOG_LEVEL_WARNING, "Cannot find Release keyword in Oracle DB version."); overall_status = FAIL; } out: if (FAIL == overall_status) { zabbix_log(LOG_LEVEL_WARNING, "Failed to detect OracleDB version"); ZBX_ORACLE_SVERSION = ZBX_DBVERSION_UNDEFINED; } else { ZBX_ORACLE_SVERSION = major_release_version * 100000000 + release_update_version * 1000000 + release_update_version_revision * 10000 + increment_version * 100 + reserved_for_future_use; # ifndef HAVE_OCI_SERVER_RELEASE2 if (18 <= major_release_version) { zabbix_log(LOG_LEVEL_WARNING, "Unable to determine the accurate Oracle DB version " "(possibly there is a DB driver - DB version mismatch, " "only the major Oracle DB version can be established): %s", version_friendly); } # endif } version_info->database = "Oracle"; version_info->current_version = ZBX_ORACLE_SVERSION; version_info->min_version = ZBX_ORACLE_MIN_VERSION; version_info->max_version = ZBX_ORACLE_MAX_VERSION; version_info->min_supported_version = ZBX_ORACLE_MIN_SUPPORTED_VERSION; version_info->friendly_current_version = zbx_strdup(NULL, version_friendly); version_info->friendly_min_version = ZBX_ORACLE_MIN_VERSION_STR; version_info->friendly_max_version = ZBX_ORACLE_MAX_VERSION_STR; version_info->friendly_min_supported_version = ZBX_ORACLE_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_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