/* ** ZABBIX ** Copyright (C) 2000-2005 SIA Zabbix ** ** 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., 675 Mass Ave, Cambridge, MA 02139, USA. **/ #include "common.h" #include "db.h" #include "zbxdb.h" #include "log.h" #include "zlog.h" #ifdef HAVE_SQLITE3 int sqlite_transaction_started = 0; sqlite3 *conn = NULL; PHP_MUTEX sqlite_access; #endif #ifdef HAVE_MYSQL MYSQL *conn = NULL; #endif #ifdef HAVE_POSTGRESQL PGconn *conn = NULL; int ZBX_PG_BYTEAOID = 0; #endif #ifdef HAVE_ORACLE sqlo_db_handle_t oracle; #endif void zbx_db_close(void) { #ifdef HAVE_MYSQL mysql_close(conn); conn = NULL; #endif #ifdef HAVE_POSTGRESQL PQfinish(conn); conn = NULL; #endif #ifdef HAVE_ORACLE if (SQLO_SUCCESS != sqlo_finish(oracle)) zabbix_log(LOG_LEVEL_ERR, "Cannot finish ORACLE session"); #endif #ifdef HAVE_SQLITE3 sqlite_transaction_started = 0; sqlite3_close(conn); conn = NULL; /* php_sem_remove(&sqlite_access);*/ #endif } /* * Connect to the database. * If fails, program terminates. */ int zbx_db_connect(char *host, char *user, char *password, char *dbname, char *dbsocket, int port) { int ret = ZBX_DB_OK; #ifdef HAVE_SQLITE3 char *p, *path; #endif /* HAVE_SQLITE3 */ /* zabbix_log(LOG_LEVEL_ERR, "[%s] [%s] [%s]\n",dbname, dbuser, dbpassword ); */ #ifdef HAVE_MYSQL /* For MySQL >3.22.00 */ /* if( ! mysql_connect( conn, NULL, dbuser, dbpassword ) )*/ conn = mysql_init(NULL); if( ! mysql_real_connect( conn, host, user, password, dbname, port, dbsocket, CLIENT_MULTI_STATEMENTS) ) { zabbix_log(LOG_LEVEL_ERR, "Failed to connect to database: Error: %s [%d]", mysql_error(conn), mysql_errno(conn)); ret = ZBX_DB_FAIL; } if(ZBX_DB_OK == ret) { if( mysql_select_db( conn, dbname ) != 0 ) { zabbix_log(LOG_LEVEL_ERR, "Failed to select database: Error: %s [%d]", mysql_error(conn), mysql_errno(conn)); ret = ZBX_DB_FAIL; } } if(ZBX_DB_OK == ret) { #ifdef HAVE_MYSQL_AUTOCOMMIT if(mysql_autocommit(conn, 1) != 0) { zabbix_log(LOG_LEVEL_ERR, "Failed to set autocommit to 1: Error: %s [%d]", mysql_error(conn), mysql_errno(conn)); ret = ZBX_DB_FAIL; } #endif /* HAVE_MYSQL_AUTOCOMMIT */ DBexecute("SET CHARACTER SET utf8"); } if(ZBX_DB_FAIL == ret) { switch(mysql_errno(conn)) { case CR_SERVER_GONE_ERROR: case CR_CONNECTION_ERROR: case CR_SERVER_LOST: case ER_SERVER_SHUTDOWN: case ER_UNKNOWN_ERROR: ret = ZBX_DB_DOWN; break; default: break; } } return ret; #endif #ifdef HAVE_POSTGRESQL char *cport = NULL; DB_RESULT result; DB_ROW row; if( port ) cport = zbx_dsprintf(cport, "%i", port); conn = PQsetdbLogin(host, cport, NULL, NULL, dbname, user, password ); zbx_free(cport); /* check to see that the backend connection was successfully made */ if (PQstatus(conn) != CONNECTION_OK) { zabbix_log(LOG_LEVEL_ERR, "Connection to database '%s' failed: %s", dbname, PQerrorMessage(conn)); ret = ZBX_DB_FAIL; } result = DBselect("select oid from pg_type where typname = 'bytea'" ); row = DBfetch(result); if(row) { ZBX_PG_BYTEAOID = atoi(row[0]); } DBfree_result(result); return ret; #endif #ifdef HAVE_ORACLE char connect[MAX_STRING_LEN]; if (SQLO_SUCCESS != sqlo_init(SQLO_OFF, 1, 100)) { zabbix_log(LOG_LEVEL_ERR, "Failed to init libsqlora8"); ret = ZBX_DB_FAIL; } if (ZBX_DB_OK == ret) { zbx_strlcpy(connect, user, sizeof(connect)); if (password && *password) { zbx_strlcat(connect, "/", sizeof(connect)); zbx_strlcat(connect, password, sizeof(connect)); if (dbname && *dbname) { zbx_strlcat(connect, "@", sizeof(connect)); zbx_strlcat(connect, dbname, sizeof(connect)); } } /* login */ /* TODO: how to use port??? */ if (SQLO_SUCCESS != sqlo_connect(&oracle, connect)) { zabbix_log(LOG_LEVEL_ERR, "Cannot login with %s", connect); ret = ZBX_DB_FAIL; } } if (ZBX_DB_OK == ret) sqlo_autocommit_off(oracle); return ret; #endif #ifdef HAVE_SQLITE3 ret = sqlite3_open(dbname, &conn); /* check to see that the backend connection was successfully made */ if (ret) { zabbix_log(LOG_LEVEL_ERR, "Can't open database [%s]: %s\n", dbname, sqlite3_errmsg(conn)); DBclose(); exit(FAIL); } /* Do not return SQLITE_BUSY immediately, wait for N ms */ sqlite3_busy_timeout(conn, 60*1000); sqlite_transaction_started = 0; path = strdup(dbname); if (NULL != (p = strrchr(path, '/'))) *++p = '\0'; else *path = '\0'; DBexecute("PRAGMA synchronous = 0"); /* OFF */ DBexecute("PRAGMA temp_store = 2"); /* MEMORY */ DBexecute("PRAGMA temp_store_directory = '%s'", path); zbx_free(path); return ret; #endif } void zbx_db_init(char *host, char *user, char *password, char *dbname, char *dbsocket, int port) { #ifdef HAVE_SQLITE3 int ret; struct stat buf; #endif #ifdef HAVE_SQLITE3 if (0 != stat(dbname, &buf)) { zabbix_log(LOG_LEVEL_WARNING, "Cannot open database file \"%s\": %s", dbname, strerror(errno)); zabbix_log(LOG_LEVEL_WARNING, "Creating database ..."); ret = sqlite3_open(dbname, &conn); if (SQLITE_OK != ret) { zabbix_log(LOG_LEVEL_ERR, "Can't create database \"%s\": %s", dbname, sqlite3_errmsg(conn)); exit(FAIL); } DBexecute("%s", db_schema); DBclose(); } #endif } int __zbx_zbx_db_execute(const char *fmt, ...) { va_list args; int ret; va_start(args, fmt); ret = zbx_db_vexecute(fmt, args); va_end(args); return ret; } #ifdef HAVE___VA_ARGS__ # define zbx_db_select(fmt, ...) __zbx_zbx_db_select(ZBX_CONST_STRING(fmt), ##__VA_ARGS__) #else # define zbx_db_select __zbx_zbx_db_select #endif /* HAVE___VA_ARGS__ */ static DB_RESULT __zbx_zbx_db_select(const char *fmt, ...) { va_list args; DB_RESULT result; va_start(args, fmt); result = zbx_db_vselect(fmt, args); va_end(args); return result; } /****************************************************************************** * * * Function: DBbegin * * * * Purpose: Start transaction * * * * Parameters: - * * * * Return value: - * * * * Author: Eugene Grigorjev * * * * Comments: Do nothing of DB does not support transactions * * * ******************************************************************************/ void zbx_db_begin(void) { #ifdef HAVE_MYSQL zbx_db_execute("%s","begin;"); #endif #ifdef HAVE_POSTGRESQL zbx_db_execute("%s","begin;"); #endif #ifdef HAVE_SQLITE3 sqlite_transaction_started++; if(sqlite_transaction_started == 1) { php_sem_acquire(&sqlite_access); zbx_db_execute("%s","begin;"); } else { zabbix_log( LOG_LEVEL_DEBUG, "POSSIBLE ERROR: Used incorect logic in database processing started subtransaction!"); } #endif } /****************************************************************************** * * * Function: DBcommit * * * * Purpose: Commit transaction * * * * Parameters: - * * * * Return value: - * * * * Author: Eugene Grigorjev * * * * Comments: Do nothing of DB does not support transactions * * * ******************************************************************************/ void zbx_db_commit(void) { #ifdef HAVE_MYSQL zbx_db_execute("%s","commit;"); #endif #ifdef HAVE_POSTGRESQL zbx_db_execute("%s","commit;"); #endif #ifdef HAVE_ORACLE zbx_db_execute("%s","commit"); #endif #ifdef HAVE_SQLITE3 if(sqlite_transaction_started > 1) { sqlite_transaction_started--; } if(sqlite_transaction_started == 1) { zbx_db_execute("%s","commit;"); sqlite_transaction_started = 0; php_sem_release(&sqlite_access); } #endif } /****************************************************************************** * * * Function: DBrollback * * * * Purpose: Rollback transaction * * * * Parameters: - * * * * Return value: - * * * * Author: Eugene Grigorjev * * * * Comments: Do nothing of DB does not support transactions * * * ******************************************************************************/ void zbx_db_rollback(void) { #ifdef HAVE_MYSQL zbx_db_execute("rollback;"); #endif #ifdef HAVE_POSTGRESQL zbx_db_execute("rollback;"); #endif #ifdef HAVE_ORACLE zbx_db_execute("rollback"); #endif #ifdef HAVE_SQLITE3 if(sqlite_transaction_started > 1) { sqlite_transaction_started--; } if(sqlite_transaction_started == 1) { zbx_db_execute("rollback;"); sqlite_transaction_started = 0; php_sem_release(&sqlite_access); } #endif } /* * Execute SQL statement. For non-select statements only. * If fails, program terminates. */ int zbx_db_vexecute(const char *fmt, va_list args) { char *sql = NULL; int ret = ZBX_DB_OK; /* double sec;*/ #ifdef HAVE_POSTGRESQL PGresult *result; #endif #ifdef HAVE_SQLITE3 char *error=0; #endif #ifdef HAVE_MYSQL int status; #endif /* sec = zbx_time();*/ sql = zbx_dvsprintf(sql, fmt, args); zabbix_log( LOG_LEVEL_DEBUG, "Query [%s]", sql); #ifdef HAVE_MYSQL if(!conn) { zabbix_log( LOG_LEVEL_ERR, "Query executing require connection to database!"); ret = ZBX_DB_FAIL; } else { if ((status = mysql_query(conn,sql)) != 0) { zabbix_log(LOG_LEVEL_ERR, "Query failed: [%s] %s [%d]", sql, mysql_error(conn), mysql_errno(conn) ); switch(mysql_errno(conn)) { case CR_SERVER_GONE_ERROR: case CR_CONNECTION_ERROR: case CR_SERVER_LOST: case ER_SERVER_SHUTDOWN: case ER_UNKNOWN_ERROR: ret = ZBX_DB_DOWN; break; default: ret = ZBX_DB_FAIL; break; } } else { do { if (mysql_field_count(conn) == 0) { /* zabbix_log(LOG_LEVEL_DEBUG, ZBX_FS_UI64 " rows affected", (zbx_uint64_t)mysql_affected_rows(conn));*/ ret += (int)mysql_affected_rows(conn); } else /* some error occurred */ { zabbix_log(LOG_LEVEL_DEBUG, "Could not retrieve result set"); break; } /* more results? -1 = no, >0 = error, 0 = yes (keep looping) */ if ((status = mysql_next_result(conn)) > 0) zabbix_log(LOG_LEVEL_ERR, "Query failed: [%s] %s [%d]", sql, mysql_error(conn), mysql_errno(conn)); } while (status == 0); } } #endif #ifdef HAVE_POSTGRESQL result = PQexec(conn,sql); if( result==NULL) { zabbix_log( LOG_LEVEL_ERR, "Query::%s",sql); zabbix_log(LOG_LEVEL_ERR, "Query failed:%s", "Result is NULL" ); ret = ZBX_DB_FAIL; } else if( PQresultStatus(result) != PGRES_COMMAND_OK) { zabbix_log( LOG_LEVEL_ERR, "Query::%s",sql); zabbix_log(LOG_LEVEL_ERR, "Query failed:%s:%s", PQresStatus(PQresultStatus(result)), PQresultErrorMessage(result)); ret = ZBX_DB_FAIL; } if(ret == ZBX_DB_OK) { ret = PQntuples(result); } PQclear(result); #endif #ifdef HAVE_ORACLE if ((ret = sqlo_exec(oracle, sql))<0) { zabbix_log( LOG_LEVEL_ERR, "Query::%s",sql); zabbix_log(LOG_LEVEL_ERR, "Query failed:%s", sqlo_geterror(oracle) ); zbx_error("Query::%s.",sql); zbx_error("Query failed:%s.", sqlo_geterror(oracle) ); ret = ZBX_DB_FAIL; } #endif #ifdef HAVE_SQLITE3 if(!sqlite_transaction_started) { php_sem_acquire(&sqlite_access); } lbl_exec: if(SQLITE_OK != (ret = sqlite3_exec(conn, sql, NULL, 0, &error))) { if(ret == SQLITE_BUSY) goto lbl_exec; /* attention deadlock!!! */ zabbix_log(LOG_LEVEL_ERR, "Query::%s",sql); zabbix_log(LOG_LEVEL_ERR, "Query failed [%i]:%s", ret, error); sqlite3_free(error); ret = ZBX_DB_FAIL; } if(ret == ZBX_DB_OK) { ret = sqlite3_changes(conn); } if(!sqlite_transaction_started) { php_sem_release(&sqlite_access); } #endif /* sec = zbx_time() - sec; if(sec > 0.1) zabbix_log( LOG_LEVEL_WARNING, "Long query: " ZBX_FS_DBL " sec, \"%s\"", sec, sql);*/ zbx_free(sql); return ret; } int zbx_db_is_null(char *field) { int ret = FAIL; if(field == NULL) ret = SUCCEED; #ifdef HAVE_ORACLE else if(field[0] == 0) ret = SUCCEED; #endif return ret; } #ifdef HAVE_POSTGRESQL /* in db.h - #define DBfree_result PG_DBfree_result */ void PG_DBfree_result(DB_RESULT result) { if(!result) return; /* free old data */ if(result->values) { result->fld_num = 0; zbx_free(result->values); result->values = NULL; } PQclear(result->pg_result); zbx_free(result); } #endif #ifdef HAVE_SQLITE3 /* in db.h - #define DBfree_result SQ_DBfree_result */ void SQ_DBfree_result(DB_RESULT result) { if(!result) return; if(result->data) { sqlite3_free_table(result->data); } zbx_free(result); } #endif DB_ROW zbx_db_fetch(DB_RESULT result) { #ifdef HAVE_MYSQL if(!result) return NULL; return mysql_fetch_row(result); #endif #ifdef HAVE_POSTGRESQL int i; /* EOF */ if(!result) return NULL; /* free old data */ if(result->values) { zbx_free(result->values); result->values = NULL; } /* EOF */ if(result->cursor == result->row_num) return NULL; /* init result */ result->fld_num = PQnfields(result->pg_result); if(result->fld_num > 0) { 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_pg_unescape_bytea((u_char *)result->values[i]); } } } result->cursor++; return result->values; #endif #ifdef HAVE_ORACLE int res; res = sqlo_fetch(result, 1); if(SQLO_SUCCESS == res) { return (DB_ROW)sqlo_values(result, NULL, 1); } else if(SQLO_NO_DATA == res) { return NULL; } zabbix_log( LOG_LEVEL_ERR, "Fetch failed:%s\n", sqlo_geterror(oracle) ); exit(FAIL); return NULL; #endif #ifdef HAVE_SQLITE3 /* EOF */ if(!result) return NULL; /* EOF */ if(result->curow >= result->nrow) return NULL; if(!result->data) return NULL; result->curow++; /* NOTE: First row == header row */ return &(result->data[result->curow * result->ncolumn]); #endif return NULL; } /* * Execute SQL statement. For select statements only. * If fails, program terminates. */ DB_RESULT zbx_db_vselect(const char *fmt, va_list args) { char *sql = NULL; DB_RESULT result; /* double sec;*/ #ifdef HAVE_ORACLE sqlo_stmt_handle_t sth; #endif #ifdef HAVE_SQLITE3 int ret = FAIL; char *error=NULL; #endif /* sec = zbx_time();*/ sql = zbx_dvsprintf(sql, fmt, args); zabbix_log( LOG_LEVEL_DEBUG, "Query [%s]", sql); #ifdef HAVE_MYSQL if(!conn) { zabbix_log( LOG_LEVEL_ERR, "Query executing require connection to database!"); result = NULL; } else { if(mysql_query(conn,sql) != 0) { zabbix_log( LOG_LEVEL_ERR, "Query::%s",sql); zabbix_log(LOG_LEVEL_ERR, "Query failed:%s [%d]", mysql_error(conn), mysql_errno(conn) ); switch(mysql_errno(conn)) { case CR_SERVER_GONE_ERROR: case CR_CONNECTION_ERROR: case CR_SERVER_LOST: case ER_SERVER_SHUTDOWN: case ER_UNKNOWN_ERROR: result = (DB_RESULT)ZBX_DB_DOWN; break; default: result = NULL; break; } } else { result = mysql_store_result(conn); } } #endif #ifdef HAVE_POSTGRESQL result = zbx_malloc(NULL, sizeof(ZBX_PG_DB_RESULT)); result->pg_result = PQexec(conn,sql); result->values = NULL; result->cursor = 0; if(result->pg_result==NULL) { zabbix_log(LOG_LEVEL_ERR, "Query::%s",sql); zabbix_log(LOG_LEVEL_ERR, "Query failed:%s", "Result is NULL" ); exit( FAIL ); } if( PQresultStatus(result->pg_result) != PGRES_TUPLES_OK) { zabbix_log(LOG_LEVEL_ERR, "Query::%s",sql); zabbix_log(LOG_LEVEL_ERR, "Query failed:%s:%s", PQresStatus(PQresultStatus(result->pg_result)), PQresultErrorMessage(result->pg_result)); exit( FAIL ); } /* init rownum */ result->row_num = PQntuples(result->pg_result); #endif #ifdef HAVE_ORACLE if(0 > (sth = (sqlo_open(oracle, sql,0,NULL)))) { zabbix_log(LOG_LEVEL_ERR, "Query::%s",sql); zabbix_log(LOG_LEVEL_ERR, "Query failed:%s", sqlo_geterror(oracle)); exit(FAIL); } result = sth; #endif #ifdef HAVE_SQLITE3 if(!sqlite_transaction_started) { php_sem_acquire(&sqlite_access); } result = zbx_malloc(NULL, sizeof(ZBX_SQ_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(ret == SQLITE_BUSY) goto lbl_get_table; /* attention deadlock!!! */ zabbix_log( LOG_LEVEL_ERR, "Query::%s",sql); zabbix_log(LOG_LEVEL_ERR, "Query failed [%i]:%s", ret, error); sqlite3_free(error); if(!sqlite_transaction_started) { php_sem_release(&sqlite_access); } exit(FAIL); } if(!sqlite_transaction_started) { php_sem_release(&sqlite_access); } #endif /* sec = zbx_time() - sec; if(sec > 0.1) zabbix_log( LOG_LEVEL_WARNING, "Long query: " ZBX_FS_DBL " sec, \"%s\"", sec, sql);*/ zbx_free(sql); return result; } /* * Get value of autoincrement field for last insert or update statement */ zbx_uint64_t zbx_db_insert_id(int exec_result, const char *table, const char *field) { #ifdef HAVE_MYSQL zabbix_log(LOG_LEVEL_DEBUG, "In DBinsert_id()" ); if(exec_result == FAIL) return 0; return mysql_insert_id(conn); #endif #ifdef HAVE_POSTGRESQL DB_RESULT tmp_res; zbx_uint64_t id_res = FAIL; zabbix_log(LOG_LEVEL_DEBUG, "In DBinsert_id()" ); if(exec_result < 0) return 0; if(exec_result == FAIL) return 0; if((Oid)exec_result == InvalidOid) return 0; tmp_res = zbx_db_select("select %s from %s where oid=%i", field, table, exec_result); ZBX_STR2UINT64(id_res, PQgetvalue(tmp_res->pg_result, 0, 0)); /* id_res = atoi(PQgetvalue(tmp_res->pg_result, 0, 0));*/ DBfree_result(tmp_res); return id_res; #endif #ifdef HAVE_ORACLE DB_ROW row; char sql[MAX_STRING_LEN]; DB_RESULT result; zbx_uint64_t id; zabbix_log(LOG_LEVEL_DEBUG, "In DBinsert_id()" ); if(exec_result == FAIL) return 0; zbx_snprintf(sql, sizeof(sql), "select %s_%s.currval from dual", table, field); result=DBselect("%s", sql); row = DBfetch(result); ZBX_STR2UINT64(id, row[0]); /* id = atoi(row[0]);*/ DBfree_result(result); return id; #endif #ifdef HAVE_SQLITE3 return (zbx_uint64_t)sqlite3_last_insert_rowid(conn); #endif } /* * Execute SQL statement. For select statements only. * If fails, program terminates. */ DB_RESULT zbx_db_select_n(char *query, int n) { #ifdef HAVE_MYSQL return zbx_db_select("%s limit %d", query, n); #endif #ifdef HAVE_POSTGRESQL return zbx_db_select("%s limit %d", query, n); #endif #ifdef HAVE_ORACLE return zbx_db_select("select * from (%s) where rownum<=%d", query, n); #endif #ifdef HAVE_SQLITE3 return zbx_db_select("%s limit %d", query, n); #endif }