| /* src/interfaces/ecpg/ecpglib/misc.c */ |
| |
| #define POSTGRES_ECPG_INTERNAL |
| #include "postgres_fe.h" |
| |
| #include <limits.h> |
| #include <unistd.h> |
| |
| #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 volatile 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 |
| /* Interlock against concurrent executions of ECPGdebug() */ |
| pthread_mutex_lock(&debug_init_mutex); |
| |
| /* Prevent ecpg_log() from printing while we change settings */ |
| pthread_mutex_lock(&debug_mutex); |
| #endif |
| |
| if (n > 100) |
| { |
| ecpg_internal_regression_mode = true; |
| simple_debug = n - 100; |
| } |
| else |
| simple_debug = n; |
| |
| debugstream = dbgs; |
| |
| /* We must release debug_mutex before invoking ecpg_log() ... */ |
| #ifdef ENABLE_THREAD_SAFETY |
| pthread_mutex_unlock(&debug_mutex); |
| #endif |
| |
| /* ... but keep holding debug_init_mutex to avoid racy printout */ |
| 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; |
| |
| /* |
| * For performance reasons, inspect simple_debug without taking the mutex. |
| * This could be problematic if fetching an int isn't atomic, but we |
| * assume that it is in many other places too. |
| */ |
| 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 |
| |
| /* Now that we hold the mutex, recheck simple_debug */ |
| if (simple_debug) |
| { |
| 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; |
| } |