/* src/interfaces/ecpg/ecpglib/misc.c */ #define POSTGRES_ECPG_INTERNAL #include "postgres_fe.h" #include #include #include "ecpg-pthread-win32.h" #include "ecpgerrno.h" #include "ecpglib.h" #include "ecpglib_extern.h" #include "ecpgtype.h" #include "pg_config_paths.h" #include "pgtypes_date.h" #include "pgtypes_interval.h" #include "pgtypes_numeric.h" #include "pgtypes_timestamp.h" #include "sqlca.h" #ifndef LONG_LONG_MIN #ifdef LLONG_MIN #define LONG_LONG_MIN LLONG_MIN #else #define LONG_LONG_MIN LONGLONG_MIN #endif /* LLONG_MIN */ #endif /* LONG_LONG_MIN */ bool ecpg_internal_regression_mode = false; static struct sqlca_t sqlca_init = { { 'S', 'Q', 'L', 'C', 'A', ' ', ' ', ' ' }, sizeof(struct sqlca_t), 0, { 0, { 0 } }, { 'N', 'O', 'T', ' ', 'S', 'E', 'T', ' ' }, { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0 }, { '0', '0', '0', '0', '0' } }; #ifdef ENABLE_THREAD_SAFETY static pthread_key_t sqlca_key; static pthread_once_t sqlca_key_once = PTHREAD_ONCE_INIT; #else static struct sqlca_t sqlca = { { 'S', 'Q', 'L', 'C', 'A', ' ', ' ', ' ' }, sizeof(struct sqlca_t), 0, { 0, { 0 } }, { 'N', 'O', 'T', ' ', 'S', 'E', 'T', ' ' }, { 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0 }, { '0', '0', '0', '0', '0' } }; #endif #ifdef ENABLE_THREAD_SAFETY static pthread_mutex_t debug_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t debug_init_mutex = PTHREAD_MUTEX_INITIALIZER; #endif static int simple_debug = 0; static FILE *debugstream = NULL; void ecpg_init_sqlca(struct sqlca_t *sqlca) { memcpy((char *) sqlca, (char *) &sqlca_init, sizeof(struct sqlca_t)); } bool ecpg_init(const struct connection *con, const char *connection_name, const int lineno) { struct sqlca_t *sqlca = ECPGget_sqlca(); if (sqlca == NULL) { ecpg_raise(lineno, ECPG_OUT_OF_MEMORY, ECPG_SQLSTATE_ECPG_OUT_OF_MEMORY, NULL); return false; } ecpg_init_sqlca(sqlca); if (con == NULL) { ecpg_raise(lineno, ECPG_NO_CONN, ECPG_SQLSTATE_CONNECTION_DOES_NOT_EXIST, connection_name ? connection_name : ecpg_gettext("NULL")); return false; } return true; } #ifdef ENABLE_THREAD_SAFETY static void ecpg_sqlca_key_destructor(void *arg) { free(arg); /* sqlca structure allocated in ECPGget_sqlca */ } static void ecpg_sqlca_key_init(void) { pthread_key_create(&sqlca_key, ecpg_sqlca_key_destructor); } #endif struct sqlca_t * ECPGget_sqlca(void) { #ifdef ENABLE_THREAD_SAFETY struct sqlca_t *sqlca; pthread_once(&sqlca_key_once, ecpg_sqlca_key_init); sqlca = pthread_getspecific(sqlca_key); if (sqlca == NULL) { sqlca = malloc(sizeof(struct sqlca_t)); if (sqlca == NULL) return NULL; ecpg_init_sqlca(sqlca); pthread_setspecific(sqlca_key, sqlca); } return sqlca; #else return &sqlca; #endif } bool ECPGstatus(int lineno, const char *connection_name) { struct connection *con = ecpg_get_connection(connection_name); if (!ecpg_init(con, connection_name, lineno)) return false; /* are we connected? */ if (con->connection == NULL) { ecpg_raise(lineno, ECPG_NOT_CONN, ECPG_SQLSTATE_ECPG_INTERNAL_ERROR, con->name); return false; } return true; } PGTransactionStatusType ECPGtransactionStatus(const char *connection_name) { const struct connection *con; con = ecpg_get_connection(connection_name); if (con == NULL) { /* transaction status is unknown */ return PQTRANS_UNKNOWN; } return PQtransactionStatus(con->connection); } bool ECPGtrans(int lineno, const char *connection_name, const char *transaction) { PGresult *res; struct connection *con = ecpg_get_connection(connection_name); if (!ecpg_init(con, connection_name, lineno)) return false; ecpg_log("ECPGtrans on line %d: action \"%s\"; connection \"%s\"\n", lineno, transaction, con ? con->name : "null"); /* if we have no connection we just simulate the command */ if (con && con->connection) { /* * If we got a transaction command but have no open transaction, we * have to start one, unless we are in autocommit, where the * developers have to take care themselves. However, if the command is * a begin statement, we just execute it once. And if the command is * commit or rollback prepared, we don't execute it. */ if (PQtransactionStatus(con->connection) == PQTRANS_IDLE && !con->autocommit && strncmp(transaction, "begin", 5) != 0 && strncmp(transaction, "start", 5) != 0 && strncmp(transaction, "commit prepared", 15) != 0 && strncmp(transaction, "rollback prepared", 17) != 0) { res = PQexec(con->connection, "begin transaction"); if (!ecpg_check_PQresult(res, lineno, con->connection, ECPG_COMPAT_PGSQL)) return false; PQclear(res); } res = PQexec(con->connection, transaction); if (!ecpg_check_PQresult(res, lineno, con->connection, ECPG_COMPAT_PGSQL)) return false; PQclear(res); } return true; } void ECPGdebug(int n, FILE *dbgs) { #ifdef ENABLE_THREAD_SAFETY pthread_mutex_lock(&debug_init_mutex); #endif if (n > 100) { ecpg_internal_regression_mode = true; simple_debug = n - 100; } else simple_debug = n; debugstream = dbgs; ecpg_log("ECPGdebug: set to %d\n", simple_debug); #ifdef ENABLE_THREAD_SAFETY pthread_mutex_unlock(&debug_init_mutex); #endif } void ecpg_log(const char *format,...) { va_list ap; struct sqlca_t *sqlca = ECPGget_sqlca(); const char *intl_format; int bufsize; char *fmt; if (!simple_debug) return; /* localize the error message string */ intl_format = ecpg_gettext(format); /* * Insert PID into the format, unless ecpg_internal_regression_mode is set * (regression tests want unchanging output). */ bufsize = strlen(intl_format) + 100; fmt = (char *) malloc(bufsize); if (fmt == NULL) return; if (ecpg_internal_regression_mode) snprintf(fmt, bufsize, "[NO_PID]: %s", intl_format); else snprintf(fmt, bufsize, "[%d]: %s", (int) getpid(), intl_format); #ifdef ENABLE_THREAD_SAFETY pthread_mutex_lock(&debug_mutex); #endif va_start(ap, format); vfprintf(debugstream, fmt, ap); va_end(ap); /* dump out internal sqlca variables */ if (ecpg_internal_regression_mode && sqlca != NULL) { fprintf(debugstream, "[NO_PID]: sqlca: code: %ld, state: %s\n", sqlca->sqlcode, sqlca->sqlstate); } fflush(debugstream); #ifdef ENABLE_THREAD_SAFETY pthread_mutex_unlock(&debug_mutex); #endif free(fmt); } void ECPGset_noind_null(enum ECPGttype type, void *ptr) { switch (type) { case ECPGt_char: case ECPGt_unsigned_char: case ECPGt_string: *((char *) ptr) = '\0'; break; case ECPGt_short: case ECPGt_unsigned_short: *((short int *) ptr) = SHRT_MIN; break; case ECPGt_int: case ECPGt_unsigned_int: *((int *) ptr) = INT_MIN; break; case ECPGt_long: case ECPGt_unsigned_long: case ECPGt_date: *((long *) ptr) = LONG_MIN; break; case ECPGt_long_long: case ECPGt_unsigned_long_long: *((long long *) ptr) = LONG_LONG_MIN; break; case ECPGt_float: memset((char *) ptr, 0xff, sizeof(float)); break; case ECPGt_double: memset((char *) ptr, 0xff, sizeof(double)); break; case ECPGt_varchar: *(((struct ECPGgeneric_varchar *) ptr)->arr) = 0x00; ((struct ECPGgeneric_varchar *) ptr)->len = 0; break; case ECPGt_bytea: ((struct ECPGgeneric_bytea *) ptr)->len = 0; break; case ECPGt_decimal: memset((char *) ptr, 0, sizeof(decimal)); ((decimal *) ptr)->sign = NUMERIC_NULL; break; case ECPGt_numeric: memset((char *) ptr, 0, sizeof(numeric)); ((numeric *) ptr)->sign = NUMERIC_NULL; break; case ECPGt_interval: memset((char *) ptr, 0xff, sizeof(interval)); break; case ECPGt_timestamp: memset((char *) ptr, 0xff, sizeof(timestamp)); break; default: break; } } static bool _check(const unsigned char *ptr, int length) { for (length--; length >= 0; length--) if (ptr[length] != 0xff) return false; return true; } bool ECPGis_noind_null(enum ECPGttype type, const void *ptr) { switch (type) { case ECPGt_char: case ECPGt_unsigned_char: case ECPGt_string: if (*((const char *) ptr) == '\0') return true; break; case ECPGt_short: case ECPGt_unsigned_short: if (*((const short int *) ptr) == SHRT_MIN) return true; break; case ECPGt_int: case ECPGt_unsigned_int: if (*((const int *) ptr) == INT_MIN) return true; break; case ECPGt_long: case ECPGt_unsigned_long: case ECPGt_date: if (*((const long *) ptr) == LONG_MIN) return true; break; case ECPGt_long_long: case ECPGt_unsigned_long_long: if (*((const long long *) ptr) == LONG_LONG_MIN) return true; break; case ECPGt_float: return _check(ptr, sizeof(float)); break; case ECPGt_double: return _check(ptr, sizeof(double)); break; case ECPGt_varchar: if (*(((const struct ECPGgeneric_varchar *) ptr)->arr) == 0x00) return true; break; case ECPGt_bytea: if (((const struct ECPGgeneric_bytea *) ptr)->len == 0) return true; break; case ECPGt_decimal: if (((const decimal *) ptr)->sign == NUMERIC_NULL) return true; break; case ECPGt_numeric: if (((const numeric *) ptr)->sign == NUMERIC_NULL) return true; break; case ECPGt_interval: return _check(ptr, sizeof(interval)); break; case ECPGt_timestamp: return _check(ptr, sizeof(timestamp)); break; default: break; } return false; } #ifdef WIN32 #ifdef ENABLE_THREAD_SAFETY int pthread_mutex_init(pthread_mutex_t *mp, void *attr) { mp->initstate = 0; return 0; } int pthread_mutex_lock(pthread_mutex_t *mp) { /* Initialize the csection if not already done */ if (mp->initstate != 1) { LONG istate; while ((istate = InterlockedExchange(&mp->initstate, 2)) == 2) Sleep(0); /* wait, another thread is doing this */ if (istate != 1) InitializeCriticalSection(&mp->csection); InterlockedExchange(&mp->initstate, 1); } EnterCriticalSection(&mp->csection); return 0; } int pthread_mutex_unlock(pthread_mutex_t *mp) { if (mp->initstate != 1) return EINVAL; LeaveCriticalSection(&mp->csection); return 0; } static pthread_mutex_t win32_pthread_once_lock = PTHREAD_MUTEX_INITIALIZER; void win32_pthread_once(volatile pthread_once_t *once, void (*fn) (void)) { if (!*once) { pthread_mutex_lock(&win32_pthread_once_lock); if (!*once) { fn(); *once = true; } pthread_mutex_unlock(&win32_pthread_once_lock); } } #endif /* ENABLE_THREAD_SAFETY */ #endif /* WIN32 */ #ifdef ENABLE_NLS char * ecpg_gettext(const char *msgid) { /* * At least on Windows, there are gettext implementations that fail if * multiple threads call bindtextdomain() concurrently. Use a mutex and * flag variable to ensure that we call it just once per process. It is * not known that similar bugs exist on non-Windows platforms, but we * might as well do it the same way everywhere. */ static volatile bool already_bound = false; static pthread_mutex_t binddomain_mutex = PTHREAD_MUTEX_INITIALIZER; if (!already_bound) { /* dgettext() preserves errno, but bindtextdomain() doesn't */ #ifdef WIN32 int save_errno = GetLastError(); #else int save_errno = errno; #endif (void) pthread_mutex_lock(&binddomain_mutex); if (!already_bound) { const char *ldir; /* * No relocatable lookup here because the calling executable could * be anywhere */ ldir = getenv("PGLOCALEDIR"); if (!ldir) ldir = LOCALEDIR; bindtextdomain(PG_TEXTDOMAIN("ecpglib"), ldir); already_bound = true; } (void) pthread_mutex_unlock(&binddomain_mutex); #ifdef WIN32 SetLastError(save_errno); #else errno = save_errno; #endif } return dgettext(PG_TEXTDOMAIN("ecpglib"), msgid); } #endif /* ENABLE_NLS */ struct var_list *ivlist = NULL; void ECPGset_var(int number, void *pointer, int lineno) { struct var_list *ptr; struct sqlca_t *sqlca = ECPGget_sqlca(); if (sqlca == NULL) { ecpg_raise(lineno, ECPG_OUT_OF_MEMORY, ECPG_SQLSTATE_ECPG_OUT_OF_MEMORY, NULL); return; } ecpg_init_sqlca(sqlca); for (ptr = ivlist; ptr != NULL; ptr = ptr->next) { if (ptr->number == number) { /* already known => just change pointer value */ ptr->pointer = pointer; return; } } /* a new one has to be added */ ptr = (struct var_list *) calloc(1L, sizeof(struct var_list)); if (!ptr) { sqlca = ECPGget_sqlca(); if (sqlca == NULL) { ecpg_raise(lineno, ECPG_OUT_OF_MEMORY, ECPG_SQLSTATE_ECPG_OUT_OF_MEMORY, NULL); return; } sqlca->sqlcode = ECPG_OUT_OF_MEMORY; strncpy(sqlca->sqlstate, "YE001", sizeof(sqlca->sqlstate)); snprintf(sqlca->sqlerrm.sqlerrmc, sizeof(sqlca->sqlerrm.sqlerrmc), "out of memory on line %d", lineno); sqlca->sqlerrm.sqlerrml = strlen(sqlca->sqlerrm.sqlerrmc); /* free all memory we have allocated for the user */ ECPGfree_auto_mem(); } else { ptr->number = number; ptr->pointer = pointer; ptr->next = ivlist; ivlist = ptr; } } void * ECPGget_var(int number) { struct var_list *ptr; for (ptr = ivlist; ptr != NULL && ptr->number != number; ptr = ptr->next); return (ptr) ? ptr->pointer : NULL; }