diff -Nur 3-3/src/Makefile.in 3-3.new/src/Makefile.in --- 3-3/src/Makefile.in 2002-10-17 06:27:03.000000000 +0200 +++ 3-3.new/src/Makefile.in 2002-11-16 00:44:38.000000000 +0100 @@ -74,7 +74,7 @@ interpret.c \ lex.c main.c mapping.c md5.c mempools.c mregex.c mstrings.c object.c \ otable.c\ - parser.c parse.c pkg-alists.c pkg-mysql.c pkg-pcre.c port.c \ + parser.c parse.c pkg-alists.c pkg-mysql.c pkg-pcre.c pkg-pgsql.c port.c \ ptrtable.c \ random.c regexp.c simulate.c simul_efun.c stdstrings.c \ strfuns.c sprintf.c swap.c wiz_list.c xalloc.c @@ -84,7 +84,7 @@ interpret.o \ lex.o main.o mapping.o md5.o mempools.o mregex.o mstrings.o object.o \ otable.o \ - parser.o parse.o pkg-alists.o pkg-mysql.o pkg-pcre.o port.o \ + parser.o parse.o pkg-alists.o pkg-mysql.o pkg-pcre.o pkg-pgsql.o port.o \ ptrtable.o \ random.o regexp.o simulate.o simul_efun.o stdstrings.o \ strfuns.o sprintf.o swap.o wiz_list.o xalloc.o @ALLOCA@ diff -Nur 3-3/src/comm.c 3-3.new/src/comm.c --- 3-3/src/comm.c 2002-11-16 00:44:07.000000000 +0100 +++ 3-3.new/src/comm.c 2002-11-16 00:44:38.000000000 +0100 @@ -2194,7 +2194,7 @@ { /* State information: */ - static fd_set readfds; + static fd_set readfds, writefds; /* List of sockets with pending data. * You can ignore a 'could be used uninitialized' warning. */ @@ -2241,6 +2241,7 @@ /* Set up readfds */ FD_ZERO(&readfds); + FD_ZERO(&writefds); for (i = 0; i < numports; i++) { FD_SET(sos[i], &readfds); } /* for */ @@ -2281,6 +2282,9 @@ FD_SET(udp_s, &readfds); } +#ifdef USE_PGSQL + pg_setfds(&readfds, &writefds, &nfds); +#endif /* select() until time is up or there is data */ for (retries = 6;;) @@ -2288,7 +2292,7 @@ check_alarm(); timeout.tv_sec = twait; timeout.tv_usec = 0; - res = socket_select(nfds, &readfds, 0, 0, &timeout); + res = socket_select(nfds, &readfds, &writefds, 0, &timeout); if (res == -1) { /* BeOS <= PR2 returns errno -1 instead of EINTR :-( */ @@ -2359,6 +2363,9 @@ } } /* if (urgent_data) */ +#ifdef USE_PGSQL + pg_process_all(); +#endif /* Initialise the user scan */ CmdsGiven = 0; NextCmdGiver = StartCmdGiver; diff -Nur 3-3/src/config.h.in 3-3.new/src/config.h.in --- 3-3/src/config.h.in 2002-11-16 00:43:50.000000000 +0100 +++ 3-3.new/src/config.h.in 2002-11-16 00:44:38.000000000 +0100 @@ -294,6 +294,10 @@ */ @cdef_use_pcre@ USE_PCRE +/* Define this if you want PostgreSQL-EFuns. + */ +@cdef_use_pgsql@ USE_PGSQL + /* Define this if you want the obsolete and deprecated efuns. */ @cdef_use_deprecated@ USE_DEPRECATED diff -Nur 3-3/src/configure 3-3.new/src/configure --- 3-3/src/configure 2002-11-08 04:36:30.000000000 +0100 +++ 3-3.new/src/configure 2002-11-16 00:44:38.000000000 +0100 @@ -857,6 +857,8 @@ enable using of threads for socket writes --enable-use-pcre default=enabled Enables PCRE instead of traditional regexps + --enable-use-pgsql default=disabled + Enables PostgreSQL-Efuns --enable-use-deprecated default=enabled Enables obsolete and deprecated efuns --enable-use-structs default=enabled @@ -1466,6 +1468,13 @@ fi; +DEFAULTenable_use_pgsql=yes +# Check whether --enable-use-pgsql or --disable-use-pgsql was given. +if test "${enable_use_pgsql+set}" = set; then + enableval="$enable_use_pgsql" + +fi; + DEFAULTenable_use_deprecated=yes # Check whether --enable-use-deprecated or --disable-use-deprecated was given. if test "${enable_use_deprecated+set}" = set; then @@ -2202,12 +2211,22 @@ enable_use_pcre=$DEFAULTenable_use_pcre fi +if test "x$enable_use_pgsql" = "x" && test "x$DEFAULTenable_use_pgsql" != "x"; then + enable_use_pgsql=$DEFAULTenable_use_pgsql +fi + if test "x$enable_use_pcre" = "xyes"; then cdef_use_pcre="#define" else cdef_use_pcre="#undef" fi +if test "x$enable_use_pgsql" = "xyes"; then + cdef_use_pgsql="#define" +else + cdef_use_pgsql="#undef" +fi + if test "x$enable_use_pthreads" = "x" && test "x$DEFAULTenable_use_pthreads" != "x"; then enable_use_pthreads=$DEFAULTenable_use_pthreads fi @@ -9362,6 +9381,10 @@ # --- +if test $enable_use_pgsql = yes; then + PKGLIBS="$PKGLIBS -lpq" +fi + echo "$as_me:$LINENO: checking if rename handles directories" >&5 echo $ECHO_N "checking if rename handles directories... $ECHO_C" >&6 if test "${lp_cv_sys_rename_handles_directories+set}" = set; then @@ -11636,6 +11659,7 @@ s,@cdef_use_pthreads@,$cdef_use_pthreads,;t t s,@cdef_use_alists@,$cdef_use_alists,;t t s,@cdef_use_pcre@,$cdef_use_pcre,;t t +s,@cdef_use_pgsql@,$cdef_use_pgsql,;t t s,@cdef_use_deprecated@,$cdef_use_deprecated,;t t s,@cdef_use_structs@,$cdef_use_structs,;t t s,@cdef_use_new_inlines@,$cdef_use_new_inlines,;t t diff -Nur 3-3/src/func_spec 3-3.new/src/func_spec --- 3-3/src/func_spec 2002-10-30 03:35:24.000000000 +0100 +++ 3-3.new/src/func_spec 2002-11-16 00:44:38.000000000 +0100 @@ -673,5 +673,10 @@ #endif /* USE_DEPRECATED */ +#ifdef USE_PGSQL +int pgconnect (string, string); +int pgquery (string, void|int); +void pgclose (); +#endif /***************************************************************************/ diff -Nur 3-3/src/pkg-pgsql.c 3-3.new/src/pkg-pgsql.c --- 3-3/src/pkg-pgsql.c 1970-01-01 01:00:00.000000000 +0100 +++ 3-3.new/src/pkg-pgsql.c 2002-11-16 00:44:38.000000000 +0100 @@ -0,0 +1,631 @@ + /*************************************************************************** + * Async Postgres-DB EFUNS * + * * + * Entries in func_spec: * + * int pgconnect (string, string) * + * int pgquery (string) * + * void pgclose (void) * + * * + * pgconnect: params: int pgconnect(string callback, string connstr) * + * returns: 0 (OK); -1 (FAILED) * + * connstr an in pglib-function PQconnectdb() * + * callback-function <callback>(int type, mixed ret, int id) * + * <type> is the type of the return-value: * + * PGRES_TUPLES_OK: ret is a mapping containing the returned * + * rows. The key is the field-name which * + * indexes n values (for n returned tuples) * + * PGRES_COMMAND_OK: ret is a string which contains the * + * server response (e.g. on INSERT * + * or DELETE) * + * PGRES_BAD_RESPONSE or * + * PGRES_NONFATAL_ERROR or * + * PGRES_FATAL_ERROR: ret is the error-string * + * additionally the following non-standard types may be * + * returned (id is ignored): * + * PGRES_NOTICE: ret is a notification-string * + * PGCONN_SUCCESS: The database-connection was established. * + * PGCONN_FAILED: The database-connection failed. * + * One of these two is the first message to the callback * + * after a successful call to pgconnect() * + * PGCONN_ABORTED: If the connection to the backend fails * + * we try to re-establish (reset) it. If the * + * reset fails, the connection is closed and * + * this value is returned. Consider the * + * connection gone and don't try to close or * + * otherwise operate further on it. * + * <id> is the query-id returned by pgquery() * + * pgconnect throws a privilege violation "pgconnect" with the connect- * + * string as argument. * + * * + * pgquery: params: pgquery(string query) * + * returns the query-id, which will be passed to the callback * + * * + * pgclose: params: none * + * returns: nothing * + * closes the connection to the backend. * + ***************************************************************************/ + +#include "driver.h" + +#ifdef USE_PGSQL + +#include "typedefs.h" + +#include "my-alloca.h" +#include <stddef.h> +#include <time.h> +#include <sys/poll.h> +#include <postgresql/libpq-fe.h> + +#include "simulate.h" + +#include "actions.h" +#include "array.h" +#include "backend.h" +#include "interpret.h" +#include "mstrings.h" +#include "mapping.h" +#include "object.h" +#include "smalloc.h" +#include "stdstrings.h" +#include "svalue.h" +#include "xalloc.h" + +#define PG_UNCONNECTED 0 +#define PG_CONNECTING 1 +#define PG_RESETTING 2 +#define PG_RESET_NEXT 3 +#define PG_IDLE 4 +#define PG_SENDQUERY 5 +#define PG_WAITREPLY 6 +#define PG_REPLYREADY 7 + + +#define PG_NOTICE 99 + +#define PGCONN_SUCCESS 100 +#define PGCONN_FAILED 101 +#define PGCONN_ABORTED 102 + +#define RESULT_ARRAY 0x00 +#define RESULT_ASSOC 0x01 + +#define MAX_RESETS 5 // We try x resets, max. one per second + +#define optfree(x) do { \ + if (x) { \ + free(x); \ + x = NULL; \ + } \ + } while (0) + +struct query_queue { + int id; + char *str; + int flags; + struct query_queue *next; +}; + +static struct dbconn { + string_t *callback; + + PGconn *conn; + PostgresPollingStatusType pgstate; + + int state; + int fd; + int resets; + + time_t lastreset; + time_t lastreply; + + object_t *object; + struct query_queue *queue; + + struct dbconn *next; +} *head = NULL; + +static int query_id = 1; + +static void pgnotice (struct dbconn *pgconn, const char *msg); + + +/*************************************************************************/ +/************************* Queue management ****************************/ +/*************************************************************************/ + +struct query_queue *queue(struct dbconn *db, char *query) { + struct query_queue *tmp; + + tmp = db->queue; + if (!tmp) + tmp = db->queue = malloc(sizeof(struct query_queue)); + else { + while (tmp->next) + tmp = tmp->next; + tmp->next = malloc(sizeof(struct query_queue)); + tmp = tmp->next; + } + if (!tmp) { + outofmemory("Queueing PG-Query"); + /* NOTREACHED */ + return NULL; + } + memset(tmp, 0, sizeof(struct query_queue)); + tmp->id = query_id++; + tmp->str = strdup(query); + tmp->flags = 0; + + return tmp; +} + +void dequeue (struct dbconn *db) { + struct query_queue *tmp; + + tmp = db->queue; + db->queue = tmp->next; + optfree(tmp->str); + free(tmp); +} + +static struct dbconn *alloc_dbconn (void) { + struct dbconn *ret; + + ret = malloc(sizeof(struct dbconn)); + if (!ret) { + outofmemory("Allocating new DB-connection"); + /* NOTREACHED */ + return NULL; + } + + memset(ret, 0, sizeof(struct dbconn)); + ret->next = head; + ret->fd = -1; + ret->state = PG_UNCONNECTED; + head = ret; + + return ret; +} + +static void dealloc_dbconn (struct dbconn *del) { + struct dbconn *ptr; + + if (!del) + return; + mstring_free(del->callback); + if (del == head) + head = head->next; + else { + ptr = head; + while (ptr->next && (ptr->next != del)) + ptr = ptr->next; + if (ptr->next) + ptr->next = ptr->next->next; + } + while (del->queue) + dequeue(del); + free(del); +} + +/*************************************************************************/ + + + + +/*************************************************************************/ +/******************* Connection-handling functions *********************/ +/*************************************************************************/ + +static int pgconnect(struct dbconn *db, char *connstr) { + db->conn = PQconnectStart(connstr); + if (!db->conn) + return -1; + if (PQstatus(db->conn) == CONNECTION_BAD) + return -1; + + PQsetNoticeProcessor(db->conn, (void*) pgnotice, db); + db->fd = PQsocket(db->conn); + db->state = PG_CONNECTING; + db->pgstate = PGRES_POLLING_WRITING; + return 0; +} + +static void pgclose (struct dbconn *pgconn) { + pgconn->state = PG_UNCONNECTED; + if (pgconn->conn) + PQfinish(pgconn->conn); + pgconn->conn = NULL; + pgconn->fd = -1; +} + +static void pgreset (struct dbconn *pgconn) { + + if (!PQresetStart(pgconn->conn)) { + pgclose(pgconn); + + push_number(inter_sp, PGCONN_ABORTED); + push_c_string(inter_sp, "Reset failed, connection aborted."); + secure_apply(pgconn->callback, pgconn->object, 2); + + return; + } + + pgconn->state = PG_RESETTING; + pgconn->pgstate = PGRES_POLLING_WRITING; +} + +/*************************************************************************/ + + + + +/*************************************************************************/ +/******************** Database result handling *************************/ +/*************************************************************************/ + +static void pgnotice (struct dbconn *pgconn, const char *msg) { + current_object = pgconn->object; + command_giver = 0; + current_interactive = 0; + + push_number(inter_sp, PG_NOTICE); + push_c_string(inter_sp, msg); + secure_apply(pgconn->callback, pgconn->object, 2); +} + +static void pgresult (struct dbconn *pgconn, PGresult *res) { + int type, nfields, ntuples, i, j; + svalue_t *entry, fname; + mapping_t *map; + vector_t *array; + + current_object = pgconn->object; + command_giver = 0; + current_interactive = 0; + + type = PQresultStatus(res); + + push_number(inter_sp, type); + + switch (type) { + case PGRES_TUPLES_OK: + nfields = PQnfields(res); + ntuples = PQntuples(res); + + if (pgconn->queue->flags & RESULT_ASSOC) { + if ((nfields * (ntuples + 1)) > max_mapping_size) { + PQclear(res); + dequeue(pgconn); + error("Query exceeded mappingsize-limit.\n"); + } + + map = allocate_mapping(nfields, ntuples); + if (!map) { + push_number(inter_sp, 0); + break; + } + + for (i = 0; i < nfields; i++) { + put_c_string(&fname, PQfname(res, i)); + entry = get_map_lvalue(map, &fname); + free_svalue(&fname); + if (!entry) + break; + for (j = 0; j < ntuples; j++) + put_c_string(&entry[j], PQgetvalue(res, j, i)); + } + + push_mapping(inter_sp, map); + } else { + if ((ntuples >= max_array_size) || (nfields >= max_array_size)) { + PQclear(res); + dequeue(pgconn); + error("Query exceeded array-limit.\n"); + } + array = allocate_array(ntuples+1); + if (!array) { + push_number(inter_sp, 0); + break; + } + + entry = &array->item[0]; + put_array(entry, allocate_array(nfields)); + for (j = 0; j < nfields; j++) + put_c_string(&entry->u.vec->item[j], PQfname(res, j)); + + for (i = 0; i < ntuples; i++) { + entry = &array->item[i+1]; + put_array(entry, allocate_array(nfields)); + for (j = 0; j < nfields; j++) + put_c_string(&entry->u.vec->item[j], PQgetvalue(res, i, j)); + } + push_array(inter_sp, array); + } + break; + + case PGRES_COMMAND_OK: +// push_number(inter_sp, atoi(PQcmdTuples(res))); + push_c_string(inter_sp, PQcmdStatus(res)); + break; + + case PGRES_BAD_RESPONSE: + push_c_string(inter_sp, PQerrorMessage(pgconn->conn)); + break; + + case PGRES_FATAL_ERROR: + case PGRES_NONFATAL_ERROR: + push_c_string(inter_sp, PQerrorMessage(pgconn->conn)); + break; + + default: + PQclear(res); + return; + } + + push_number(inter_sp, pgconn->queue->id); + + secure_apply(pgconn->callback, pgconn->object, 3); + dequeue(pgconn); + PQclear(res); +} + +/*************************************************************************/ + + + + +/*************************************************************************/ +/******************* Database Connection-processing ********************/ +/*************************************************************************/ + +static void pg_process_connect_reset (struct dbconn *pgconn) { + struct pollfd ufd; + int reset; + PostgresPollingStatusType (*pqpollhandler)(PGconn *); + + reset = (pgconn->state == PG_RESETTING); + + if (reset) + printf("Connection resetting...\n"); + + pqpollhandler = reset ? PQresetPoll : PQconnectPoll; + + if (pgconn->pgstate != PGRES_POLLING_ACTIVE) { + ufd.fd = pgconn->fd; + switch (pgconn->pgstate) { + case PGRES_POLLING_READING: + ufd.events = POLLIN; + break; + case PGRES_POLLING_WRITING: + ufd.events = POLLOUT; + break; + default: // To keep the compiler happy... + break; + } + if (poll(&ufd, 1, 0) > 0) { + pgconn->pgstate = PGRES_POLLING_ACTIVE; + if (ufd.revents | POLLIN) + pgconn->lastreply = time(NULL); + } + } + + if (pgconn->pgstate == PGRES_POLLING_ACTIVE) + pgconn->pgstate = pqpollhandler(pgconn->conn); + + if (pgconn->pgstate == PGRES_POLLING_FAILED) { + if (reset && (pgconn->resets < MAX_RESETS)) { + pgconn->resets++; + pgconn->state = PG_RESET_NEXT; + pgconn->lastreset = time(NULL); + } else { + push_number(inter_sp, reset ? PGCONN_ABORTED : PGCONN_FAILED); + push_c_string(inter_sp, PQerrorMessage(pgconn->conn)); + secure_apply(pgconn->callback, pgconn->object, 2); + pgclose(pgconn); + } + } else if (pgconn->pgstate == PGRES_POLLING_OK) { + if (!reset) { // The program should not notice a successful reset + push_number(inter_sp, PGCONN_SUCCESS); + push_c_string(inter_sp, "success"); + secure_apply(pgconn->callback, pgconn->object, 2); + } + pgconn->resets = 0; + if (pgconn->queue) + pgconn->state = PG_SENDQUERY; + else + pgconn->state = PG_IDLE; + } +} + +static void pg_process_query (struct dbconn *pgconn) { + struct pollfd ufd; + + ufd.fd = pgconn->fd; + ufd.events = POLLIN; + + PQflush(pgconn->conn); + if (poll(&ufd, 1, 0) > 0) { + pgconn->lastreply = time(NULL); + PQconsumeInput(pgconn->conn); + if (!PQisBusy(pgconn->conn)) + pgconn->state = PG_REPLYREADY; + } +} + +static void pg_process_one(struct dbconn *pgconn) { + PGresult *res; + + switch(pgconn->state) { + case PG_CONNECTING: + case PG_RESETTING: + pg_process_connect_reset(pgconn); + break; + case PG_SENDQUERY: + case PG_IDLE: + if (pgconn->queue) { + pgconn->lastreply = time(NULL); + PQsendQuery(pgconn->conn, pgconn->queue->str); + pgconn->state = PG_WAITREPLY; + } + case PG_WAITREPLY: + pg_process_query(pgconn); + break; + case PG_UNCONNECTED: + dealloc_dbconn(pgconn); + break; + case PG_RESET_NEXT: + if (pgconn->lastreset != time(NULL)) + pgreset(pgconn); + break; + } + + if ((PQstatus(pgconn->conn) != CONNECTION_OK) && + (pgconn->state >= PG_IDLE)) + pgreset(pgconn); + + if (pgconn->state == PG_REPLYREADY) { + do { + res = PQgetResult(pgconn->conn); + if (!res) { + pgconn->state = PG_IDLE; + pg_process_one(pgconn); + break; + } else + pgresult(pgconn, res); + } while (!PQisBusy(pgconn->conn)); + } +} + +void pg_process_all (void) { + struct dbconn *ptr = head; + + while (ptr) { + if (ptr->object && (check_object(ptr->object) == NULL)) { + free_object(ptr->object, "pg_process_all"); + ptr->object = NULL; + pgclose(ptr); + } else + pg_process_one(ptr); + ptr = ptr->next; + } +} + +/*************************************************************************/ + + + + +/*************************************************************************/ +/************************ Helper-functions *******************************/ +/*************************************************************************/ + +void pg_setfds (fd_set *readfds, fd_set *writefds, int *nfds) { + struct dbconn *ptr; + + for (ptr = head; ptr; ptr = ptr->next) { + if (ptr->fd < 0) + continue; + if ((ptr->pgstate == PGRES_POLLING_WRITING) || + (ptr->state == PG_SENDQUERY)) + FD_SET(ptr->fd, writefds); + FD_SET(ptr->fd, readfds); + if (*nfds <= ptr->fd) + *nfds = ptr->fd + 1; + } +} + +static struct dbconn *find_current_connection(void) { + struct dbconn *ptr = head; + + while (ptr && (ptr->object != current_object)) + ptr = ptr->next; + + return ptr; +} + +/*************************************************************************/ + + + +/*************************************************************************/ +/********************************* EFUNS *********************************/ +/*************************************************************************/ + + +/* int pgconnect(string callback, string connstr) */ + +svalue_t * +f_pgconnect(sp, argc) +svalue_t *sp; +int argc; +{ + struct dbconn *db; + int st; + + if (!privilege_violation(STR_PGCONNECT, &const0, sp)) + error("pgconnect(): privilege violation."); + + db = find_current_connection(); + if (db) { + if (db->state == PG_UNCONNECTED) + dealloc_dbconn(db); + else + error("pgconnect(): Already connected\n"); + } + db = alloc_dbconn(); + db->object = current_object; + db->callback = dup_mstring(sp[-1].u.str); + ref_object(current_object, "f_pgconnect"); + + st = pgconnect(db, sp[0].u.str->str->txt); + if (st < 0) + pgclose(db); + free_svalue(sp--); + free_svalue(sp); + put_number(sp, st); + return sp; +} + + +/* int pgquery (string query, void|int flags) */ + +svalue_t *v_pgquery (svalue_t *sp, int argc) { + struct dbconn *db; + struct query_queue *q; + int flags = 0; + + if (argc == 2) + flags = (sp--)->u.number; + + db = find_current_connection(); + if (!db) + error("pgquery(): not connected\n"); + + q = queue(db, sp->u.str->str->txt); + q->flags = flags; + if (db->state == PG_IDLE) + db->state = PG_SENDQUERY; + + free_svalue(sp); + put_number(sp, q->id); + return sp; +} + + +/* void pgclose (void) */ + +svalue_t *f_pgclose (svalue_t *sp, int argc) { + struct dbconn *db; + + db = find_current_connection(); + if (!db) + error("pgclose: not connected\n"); + + pgclose(db); + + return sp; +} + +/*************************************************************************/ + +#endif /* USE_PGSQL */ diff -Nur 3-3/src/string_spec 3-3.new/src/string_spec --- 3-3/src/string_spec 2002-11-16 00:43:50.000000000 +0100 +++ 3-3.new/src/string_spec 2002-11-16 00:44:38.000000000 +0100 @@ -201,4 +201,8 @@ #endif /* SUPPLY_PARSE_COMMAND */ +#ifdef USE_PGSQL +PGCONNECT "pgconnect" +#endif + /***************************************************************************/