1
/*-------------------------------------------------------------------------
5
* Contains Tcl "channel" interface routines, plus useful routines
6
* to convert between strings and pointers. These are needed because
7
* everything in Tcl is a string, but in C, pointers to data structures
10
* ASSUMPTION: sizeof(long) >= sizeof(void*)
12
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
13
* Portions Copyright (c) 1994, Regents of the University of California
16
* $Header: /cvsroot/pgsql/src/interfaces/libpgtcl/Attic/pgtclId.c,v 1.43 2003/08/04 02:40:16 momjian Exp $
18
*-------------------------------------------------------------------------
20
#include "postgres_fe.h"
24
#include "pgtclCmds.h"
29
PgEndCopy(Pg_ConnectionId * connid, int *errorCodePtr)
31
connid->res_copyStatus = RES_COPY_NONE;
32
if (PQendcopy(connid->conn))
34
PQclear(connid->results[connid->res_copy]);
35
connid->results[connid->res_copy] =
36
PQmakeEmptyPGresult(connid->conn, PGRES_BAD_RESPONSE);
37
connid->res_copy = -1;
43
PQclear(connid->results[connid->res_copy]);
44
connid->results[connid->res_copy] =
45
PQmakeEmptyPGresult(connid->conn, PGRES_COMMAND_OK);
46
connid->res_copy = -1;
52
* Called when reading data (via gets) for a copy <rel> to stdout.
55
PgInputProc(DRIVER_INPUT_PROTO)
57
Pg_ConnectionId *connid;
61
connid = (Pg_ConnectionId *) cData;
64
if (connid->res_copy < 0 ||
65
PQresultStatus(connid->results[connid->res_copy]) != PGRES_COPY_OUT)
67
*errorCodePtr = EBUSY;
72
* Read any newly arrived data into libpq's buffer, thereby clearing
73
* the socket's read-ready condition.
75
if (!PQconsumeInput(conn))
81
/* Move data from libpq's buffer to Tcl's. */
83
avail = PQgetlineAsync(conn, buf, bufSize);
87
/* Endmarker detected, change state and return 0 */
88
return PgEndCopy(connid, errorCodePtr);
95
* Called when writing data (via puts) for a copy <rel> from stdin
98
PgOutputProc(DRIVER_OUTPUT_PROTO)
100
Pg_ConnectionId *connid;
103
connid = (Pg_ConnectionId *) cData;
106
if (connid->res_copy < 0 ||
107
PQresultStatus(connid->results[connid->res_copy]) != PGRES_COPY_IN)
109
*errorCodePtr = EBUSY;
113
if (PQputnbytes(conn, buf, bufSize))
120
* This assumes Tcl script will write the terminator line in a single
121
* operation; maybe not such a good assumption?
123
if (bufSize >= 3 && strncmp(&buf[bufSize - 3], "\\.\n", 3) == 0)
125
if (PgEndCopy(connid, errorCodePtr) == -1)
131
#if HAVE_TCL_GETFILEPROC
134
PgGetFileProc(ClientData cData, int direction)
136
return (Tcl_File) NULL;
141
* The WatchProc and GetHandleProc are no-ops but must be present.
144
PgWatchProc(ClientData instanceData, int mask)
149
PgGetHandleProc(ClientData instanceData, int direction,
150
ClientData *handlePtr)
155
Tcl_ChannelType Pg_ConnType = {
156
"pgsql", /* channel type */
157
NULL, /* blockmodeproc */
158
PgDelConnectionId, /* closeproc */
159
PgInputProc, /* inputproc */
160
PgOutputProc, /* outputproc */
161
NULL, /* SeekProc, Not used */
162
NULL, /* SetOptionProc, Not used */
163
NULL, /* GetOptionProc, Not used */
164
PgWatchProc, /* WatchProc, must be defined */
165
PgGetHandleProc, /* GetHandleProc, must be defined */
166
NULL /* Close2Proc, Not used */
170
* Create and register a new channel for the connection
173
PgSetConnectionId(Tcl_Interp *interp, PGconn *conn)
175
Tcl_Channel conn_chan;
176
Pg_ConnectionId *connid;
179
connid = (Pg_ConnectionId *) ckalloc(sizeof(Pg_ConnectionId));
181
connid->res_count = 0;
182
connid->res_last = -1;
183
connid->res_max = RES_START;
184
connid->res_hardmax = RES_HARD_MAX;
185
connid->res_copy = -1;
186
connid->res_copyStatus = RES_COPY_NONE;
187
connid->results = (PGresult **) ckalloc(sizeof(PGresult *) * RES_START);
188
for (i = 0; i < RES_START; i++)
189
connid->results[i] = NULL;
190
connid->notify_list = NULL;
191
connid->notifier_running = 0;
193
sprintf(connid->id, "pgsql%d", PQsocket(conn));
195
#if TCL_MAJOR_VERSION >= 8
196
connid->notifier_channel = Tcl_MakeTcpClientChannel((ClientData) PQsocket(conn));
197
Tcl_RegisterChannel(NULL, connid->notifier_channel);
199
connid->notifier_socket = -1;
202
#if TCL_MAJOR_VERSION == 7 && TCL_MINOR_VERSION == 5
203
/* Original signature (only seen in Tcl 7.5) */
204
conn_chan = Tcl_CreateChannel(&Pg_ConnType, connid->id, NULL, NULL, (ClientData) connid);
206
/* Tcl 7.6 and later use this */
207
conn_chan = Tcl_CreateChannel(&Pg_ConnType, connid->id, (ClientData) connid,
208
TCL_READABLE | TCL_WRITABLE);
211
Tcl_SetChannelOption(interp, conn_chan, "-buffering", "line");
212
Tcl_SetResult(interp, connid->id, TCL_VOLATILE);
213
Tcl_RegisterChannel(interp, conn_chan);
218
* Get back the connection from the Id
221
PgGetConnectionId(Tcl_Interp *interp, CONST84 char *id,
222
Pg_ConnectionId ** connid_p)
224
Tcl_Channel conn_chan;
225
Pg_ConnectionId *connid;
227
conn_chan = Tcl_GetChannel(interp, id, 0);
228
if (conn_chan == NULL || Tcl_GetChannelType(conn_chan) != &Pg_ConnType)
230
Tcl_ResetResult(interp);
231
Tcl_AppendResult(interp, id, " is not a valid postgresql connection", 0);
234
return (PGconn *) NULL;
237
connid = (Pg_ConnectionId *) Tcl_GetChannelInstanceData(conn_chan);
245
* Remove a connection Id from the hash table and
246
* close all portals the user forgot.
249
PgDelConnectionId(DRIVER_DEL_PROTO)
251
Tcl_HashEntry *entry;
252
Tcl_HashSearch hsearch;
253
Pg_ConnectionId *connid;
254
Pg_TclNotifies *notifies;
257
connid = (Pg_ConnectionId *) cData;
259
for (i = 0; i < connid->res_max; i++)
261
if (connid->results[i])
262
PQclear(connid->results[i]);
264
ckfree((void *) connid->results);
266
/* Release associated notify info */
267
while ((notifies = connid->notify_list) != NULL)
269
connid->notify_list = notifies->next;
270
for (entry = Tcl_FirstHashEntry(¬ifies->notify_hash, &hsearch);
272
entry = Tcl_NextHashEntry(&hsearch))
273
ckfree((char *) Tcl_GetHashValue(entry));
274
Tcl_DeleteHashTable(¬ifies->notify_hash);
275
if (notifies->conn_loss_cmd)
276
ckfree((void *) notifies->conn_loss_cmd);
277
if (notifies->interp)
278
Tcl_DontCallWhenDeleted(notifies->interp, PgNotifyInterpDelete,
279
(ClientData) notifies);
280
ckfree((void *) notifies);
284
* Turn off the Tcl event source for this connection, and delete any
285
* pending notify and connection-loss events.
287
PgStopNotifyEventSource(connid, true);
289
/* Close the libpq connection too */
290
PQfinish(connid->conn);
294
* Kill the notifier channel, too. We must not do this until after
295
* we've closed the libpq connection, because Tcl will try to close
298
* XXX Unfortunately, while this works fine if we are closing due to
299
* explicit pg_disconnect, all Tcl versions through 8.4.1 dump core if
300
* we try to do it during interpreter shutdown. Not clear why. For
301
* now, we kill the channel during pg_disconnect, but during interp
302
* shutdown we just accept leakage of the (fairly small) amount of
303
* memory taken for the channel state representation. (Note we are not
304
* leaking a socket, since libpq closed that already.) We tell the
305
* difference between pg_disconnect and interpreter shutdown by
306
* testing for interp != NULL, which is an undocumented but apparently
309
#if TCL_MAJOR_VERSION >= 8
310
if (connid->notifier_channel != NULL && interp != NULL)
311
Tcl_UnregisterChannel(NULL, connid->notifier_channel);
315
* We must use Tcl_EventuallyFree because we don't want the connid
316
* struct to vanish instantly if Pg_Notify_EventProc is active for it.
317
* (Otherwise, closing the connection from inside a pg_listen callback
318
* could lead to coredump.) Pg_Notify_EventProc can detect that the
319
* connection has been deleted from under it by checking connid->conn.
321
Tcl_EventuallyFree((ClientData) connid, TCL_DYNAMIC);
328
* Find a slot for a new result id. If the table is full, expand it by
329
* a factor of 2. However, do not expand past the hard max, as the client
330
* is probably just not clearing result handles like they should.
333
PgSetResultId(Tcl_Interp *interp, CONST84 char *connid_c, PGresult *res)
335
Tcl_Channel conn_chan;
336
Pg_ConnectionId *connid;
342
conn_chan = Tcl_GetChannel(interp, connid_c, 0);
343
if (conn_chan == NULL)
345
connid = (Pg_ConnectionId *) Tcl_GetChannelInstanceData(conn_chan);
347
/* search, starting at slot after the last one used */
348
resid = connid->res_last;
351
/* advance, with wraparound */
352
if (++resid >= connid->res_max)
354
/* this slot empty? */
355
if (!connid->results[resid])
357
connid->res_last = resid;
358
break; /* success exit */
360
/* checked all slots? */
361
if (resid == connid->res_last)
362
break; /* failure exit */
365
if (connid->results[resid])
367
/* no free slot found, so try to enlarge array */
368
if (connid->res_max >= connid->res_hardmax)
370
Tcl_SetResult(interp, "hard limit on result handles reached",
374
connid->res_last = resid = connid->res_max;
375
connid->res_max *= 2;
376
if (connid->res_max > connid->res_hardmax)
377
connid->res_max = connid->res_hardmax;
378
connid->results = (PGresult **) ckrealloc((void *) connid->results,
379
sizeof(PGresult *) * connid->res_max);
380
for (i = connid->res_last; i < connid->res_max; i++)
381
connid->results[i] = NULL;
384
connid->results[resid] = res;
385
sprintf(buf, "%s.%d", connid_c, resid);
386
Tcl_SetResult(interp, buf, TCL_VOLATILE);
391
getresid(Tcl_Interp *interp, CONST84 char *id, Pg_ConnectionId ** connid_p)
393
Tcl_Channel conn_chan;
396
Pg_ConnectionId *connid;
398
if (!(mark = strchr(id, '.')))
401
conn_chan = Tcl_GetChannel(interp, id, 0);
403
if (conn_chan == NULL || Tcl_GetChannelType(conn_chan) != &Pg_ConnType)
405
Tcl_SetResult(interp, "Invalid connection handle", TCL_STATIC);
409
if (Tcl_GetInt(interp, mark + 1, &resid) == TCL_ERROR)
411
Tcl_SetResult(interp, "Poorly formated result handle", TCL_STATIC);
415
connid = (Pg_ConnectionId *) Tcl_GetChannelInstanceData(conn_chan);
417
if (resid < 0 || resid >= connid->res_max || connid->results[resid] == NULL)
419
Tcl_SetResult(interp, "Invalid result handle", TCL_STATIC);
430
* Get back the result pointer from the Id
433
PgGetResultId(Tcl_Interp *interp, CONST84 char *id)
435
Pg_ConnectionId *connid;
440
resid = getresid(interp, id, &connid);
443
return connid->results[resid];
448
* Remove a result Id from the hash tables
451
PgDelResultId(Tcl_Interp *interp, CONST84 char *id)
453
Pg_ConnectionId *connid;
456
resid = getresid(interp, id, &connid);
459
connid->results[resid] = 0;
464
* Get the connection Id from the result Id
467
PgGetConnByResultId(Tcl_Interp *interp, CONST84 char *resid_c)
470
Tcl_Channel conn_chan;
472
if (!(mark = strchr(resid_c, '.')))
475
conn_chan = Tcl_GetChannel(interp, resid_c, 0);
477
if (conn_chan && Tcl_GetChannelType(conn_chan) == &Pg_ConnType)
479
Tcl_SetResult(interp, (char *) Tcl_GetChannelName(conn_chan),
485
Tcl_ResetResult(interp);
486
Tcl_AppendResult(interp, resid_c, " is not a valid connection\n", 0);
493
/*-------------------------------------------
496
These functions allow asynchronous notify messages arriving from
497
the SQL server to be dispatched as Tcl events. See the Tcl
498
Notifier(3) man page for more info.
500
The main trick in this code is that we have to cope with status changes
501
between the queueing and the execution of a Tcl event. For example,
502
if the user changes or cancels the pg_listen callback command, we should
503
use the new setting; we do that by not resolving the notify relation
504
name until the last possible moment.
505
We also have to handle closure of the channel or deletion of the interpreter
506
to be used for the callback (note that with multiple interpreters,
507
the channel can outlive the interpreter it was created by!)
508
Upon closure of the channel, we immediately delete the file event handler
509
for it, which has the effect of disabling any file-ready events that might
510
be hanging about in the Tcl event queue. But for interpreter deletion,
511
we just set any matching interp pointers in the Pg_TclNotifies list to NULL.
512
The list item stays around until the connection is deleted. (This avoids
513
trouble with walking through a list whose members may get deleted under us.)
515
Another headache is that Ousterhout keeps changing the Tcl I/O interfaces.
516
libpgtcl currently claims to work with Tcl 7.5, 7.6, and 8.0, and each of
517
'em is different. Worse, the Tcl_File type went away in 8.0, which means
518
there is no longer any platform-independent way of waiting for file ready.
519
So we now have to use a Unix-specific interface. Grumble.
521
In the current design, Pg_Notify_FileHandler is a file handler that
522
we establish by calling Tcl_CreateFileHandler(). It gets invoked from
523
the Tcl event loop whenever the underlying PGconn's socket is read-ready.
524
We suck up any available data (to clear the OS-level read-ready condition)
525
and then transfer any available PGnotify events into the Tcl event queue.
526
Eventually these events will be dispatched to Pg_Notify_EventProc. When
527
we do an ordinary PQexec, we must also transfer PGnotify events into Tcl's
528
event queue, since libpq might have read them when we weren't looking.
529
------------------------------------------*/
533
Tcl_Event header; /* Standard Tcl event info */
534
PGnotify *notify; /* Notify event from libpq, or NULL */
535
/* We use a NULL notify pointer to denote a connection-loss event */
536
Pg_ConnectionId *connid; /* Connection for server */
539
/* Dispatch a NotifyEvent that has reached the front of the event queue */
542
Pg_Notify_EventProc(Tcl_Event *evPtr, int flags)
544
NotifyEvent *event = (NotifyEvent *) evPtr;
545
Pg_TclNotifies *notifies;
549
/* We classify SQL notifies as Tcl file events. */
550
if (!(flags & TCL_FILE_EVENTS))
553
/* If connection's been closed, just forget the whole thing. */
554
if (event->connid == NULL)
557
PQfreemem(event->notify);
562
* Preserve/Release to ensure the connection struct doesn't disappear
565
Tcl_Preserve((ClientData) event->connid);
568
* Loop for each interpreter that has ever registered on the
569
* connection. Each one can get a callback.
572
for (notifies = event->connid->notify_list;
574
notifies = notifies->next)
576
Tcl_Interp *interp = notifies->interp;
579
continue; /* ignore deleted interpreter */
582
* Find the callback to be executed for this interpreter, if any.
586
/* Ordinary NOTIFY event */
587
Tcl_HashEntry *entry;
589
entry = Tcl_FindHashEntry(¬ifies->notify_hash,
590
event->notify->relname);
592
continue; /* no pg_listen in this interpreter */
593
callback = (char *) Tcl_GetHashValue(entry);
597
/* Connection-loss event */
598
callback = notifies->conn_loss_cmd;
601
if (callback == NULL)
602
continue; /* nothing to do for this interpreter */
605
* We have to copy the callback string in case the user executes a
606
* new pg_listen or pg_on_connection_loss during the callback.
608
svcallback = (char *) ckalloc((unsigned) (strlen(callback) + 1));
609
strcpy(svcallback, callback);
612
* Execute the callback.
614
Tcl_Preserve((ClientData) interp);
615
if (Tcl_GlobalEval(interp, svcallback) != TCL_OK)
618
Tcl_AddErrorInfo(interp, "\n (\"pg_listen\" script)");
620
Tcl_AddErrorInfo(interp, "\n (\"pg_on_connection_loss\" script)");
621
Tcl_BackgroundError(interp);
623
Tcl_Release((ClientData) interp);
627
* Check for the possibility that the callback closed the
630
if (event->connid->conn == NULL)
634
Tcl_Release((ClientData) event->connid);
637
PQfreemem(event->notify);
643
* Transfer any notify events available from libpq into the Tcl event queue.
644
* Note that this must be called after each PQexec (to capture notifies
645
* that arrive during command execution) as well as in Pg_Notify_FileHandler
646
* (to capture notifies that arrive when we're idle).
650
PgNotifyTransferEvents(Pg_ConnectionId * connid)
654
while ((notify = PQnotifies(connid->conn)) != NULL)
656
NotifyEvent *event = (NotifyEvent *) ckalloc(sizeof(NotifyEvent));
658
event->header.proc = Pg_Notify_EventProc;
659
event->notify = notify;
660
event->connid = connid;
661
Tcl_QueueEvent((Tcl_Event *) event, TCL_QUEUE_TAIL);
665
* This is also a good place to check for unexpected closure of the
666
* connection (ie, backend crash), in which case we must shut down the
667
* notify event source to keep Tcl from trying to select() on the now-
668
* closed socket descriptor. But don't kill on-connection-loss
669
* events; in fact, register one.
671
if (PQsocket(connid->conn) < 0)
672
PgConnLossTransferEvents(connid);
676
* Handle a connection-loss event
679
PgConnLossTransferEvents(Pg_ConnectionId * connid)
681
if (connid->notifier_running)
683
/* Put the on-connection-loss event in the Tcl queue */
684
NotifyEvent *event = (NotifyEvent *) ckalloc(sizeof(NotifyEvent));
686
event->header.proc = Pg_Notify_EventProc;
687
event->notify = NULL;
688
event->connid = connid;
689
Tcl_QueueEvent((Tcl_Event *) event, TCL_QUEUE_TAIL);
693
* Shut down the notify event source to keep Tcl from trying to
694
* select() on the now-closed socket descriptor. And zap any
695
* unprocessed notify events ... but not, of course, the
696
* connection-loss event.
698
PgStopNotifyEventSource(connid, false);
702
* Cleanup code for coping when an interpreter or a channel is deleted.
704
* PgNotifyInterpDelete is registered as an interpreter deletion callback
705
* for each extant Pg_TclNotifies structure.
706
* NotifyEventDeleteProc is used by PgStopNotifyEventSource to cancel
707
* pending Tcl NotifyEvents that reference a dying connection.
711
PgNotifyInterpDelete(ClientData clientData, Tcl_Interp *interp)
713
/* Mark the interpreter dead, but don't do anything else yet */
714
Pg_TclNotifies *notifies = (Pg_TclNotifies *) clientData;
716
notifies->interp = NULL;
720
* Comparison routines for detecting events to be removed by Tcl_DeleteEvents.
721
* NB: In (at least) Tcl versions 7.6 through 8.0.3, there is a serious
722
* bug in Tcl_DeleteEvents: if there are multiple events on the queue and
723
* you tell it to delete the last one, the event list pointers get corrupted,
724
* with the result that events queued immediately thereafter get lost.
725
* Therefore we daren't tell Tcl_DeleteEvents to actually delete anything!
726
* We simply use it as a way of scanning the event queue. Events matching
727
* the about-to-be-deleted connid are marked dead by setting their connid
728
* fields to NULL. Then Pg_Notify_EventProc will do nothing when those
729
* events are executed.
732
NotifyEventDeleteProc(Tcl_Event *evPtr, ClientData clientData)
734
Pg_ConnectionId *connid = (Pg_ConnectionId *) clientData;
736
if (evPtr->proc == Pg_Notify_EventProc)
738
NotifyEvent *event = (NotifyEvent *) evPtr;
740
if (event->connid == connid && event->notify != NULL)
741
event->connid = NULL;
746
/* This version deletes on-connection-loss events too */
748
AllNotifyEventDeleteProc(Tcl_Event *evPtr, ClientData clientData)
750
Pg_ConnectionId *connid = (Pg_ConnectionId *) clientData;
752
if (evPtr->proc == Pg_Notify_EventProc)
754
NotifyEvent *event = (NotifyEvent *) evPtr;
756
if (event->connid == connid)
757
event->connid = NULL;
763
* File handler callback: called when Tcl has detected read-ready on socket.
764
* The clientData is a pointer to the associated connection.
765
* We can ignore the condition mask since we only ever ask about read-ready.
769
Pg_Notify_FileHandler(ClientData clientData, int mask)
771
Pg_ConnectionId *connid = (Pg_ConnectionId *) clientData;
774
* Consume any data available from the SQL server (this just buffers
775
* it internally to libpq; but it will clear the read-ready
778
if (PQconsumeInput(connid->conn))
780
/* Transfer notify events from libpq to Tcl event queue. */
781
PgNotifyTransferEvents(connid);
786
* If there is no input but we have read-ready, assume this means
787
* we lost the connection.
789
PgConnLossTransferEvents(connid);
795
* Start and stop the notify event source for a connection.
797
* We do not bother to run the notifier unless at least one pg_listen
798
* or pg_on_connection_loss has been executed on the connection. Currently,
799
* once started the notifier is run until the connection is closed.
801
* FIXME: if PQreset is executed on the underlying PGconn, the active
802
* socket number could change. How and when should we test for this
803
* and update the Tcl file handler linkage? (For that matter, we'd
804
* also have to reissue LISTEN commands for active LISTENs, since the
805
* new backend won't know about 'em. I'm leaving this problem for
810
PgStartNotifyEventSource(Pg_ConnectionId * connid)
812
/* Start the notify event source if it isn't already running */
813
if (!connid->notifier_running)
815
int pqsock = PQsocket(connid->conn);
819
#if TCL_MAJOR_VERSION >= 8
820
Tcl_CreateChannelHandler(connid->notifier_channel,
822
Pg_Notify_FileHandler,
823
(ClientData) connid);
825
/* In Tcl 7.5 and 7.6, we need to gin up a Tcl_File. */
826
Tcl_File tclfile = Tcl_GetFile((ClientData) pqsock, TCL_UNIX_FD);
828
Tcl_CreateFileHandler(tclfile, TCL_READABLE,
829
Pg_Notify_FileHandler, (ClientData) connid);
830
connid->notifier_socket = pqsock;
832
connid->notifier_running = 1;
838
PgStopNotifyEventSource(Pg_ConnectionId * connid, bool allevents)
840
/* Remove the event source */
841
if (connid->notifier_running)
843
#if TCL_MAJOR_VERSION >= 8
844
Tcl_DeleteChannelHandler(connid->notifier_channel,
845
Pg_Notify_FileHandler,
846
(ClientData) connid);
848
/* In Tcl 7.5 and 7.6, we need to gin up a Tcl_File. */
849
Tcl_File tclfile = Tcl_GetFile((ClientData) connid->notifier_socket,
852
Tcl_DeleteFileHandler(tclfile);
854
connid->notifier_running = 0;
857
/* Kill queued Tcl events that reference this channel */
859
Tcl_DeleteEvents(AllNotifyEventDeleteProc, (ClientData) connid);
861
Tcl_DeleteEvents(NotifyEventDeleteProc, (ClientData) connid);