1 /*
2  * fifo.c --
3  *
4  *	Implementation of a memory channel having fifo behaviour.
5  *
6  * Copyright (C) 1996-1999 Andreas Kupries (a.kupries@westend.com)
7  * All rights reserved.
8  *
9  * Permission is hereby granted, without written agreement and without
10  * license or royalty fees, to use, copy, modify, and distribute this
11  * software and its documentation for any purpose, provided that the
12  * above copyright notice and the following two paragraphs appear in
13  * all copies of this software.
14  *
15  * IN NO EVENT SHALL I BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL,
16  * INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS
17  * SOFTWARE AND ITS DOCUMENTATION, EVEN IF I HAVE BEEN ADVISED OF THE
18  * POSSIBILITY OF SUCH DAMAGE.
19  *
20  * I SPECIFICALLY DISCLAIM ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22  * PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND
23  * I HAVE NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
24  * ENHANCEMENTS, OR MODIFICATIONS.
25  *
26  * CVS: $Id: fifo.c,v 1.14 2004/11/09 23:11:00 patthoyts Exp $
27  */
28 
29 
30 #include "memchanInt.h"
31 #include "buf.h"
32 
33 /*
34  * Forward declarations of internal procedures.
35  */
36 
37 static int	Close _ANSI_ARGS_((ClientData instanceData,
38 		   Tcl_Interp *interp));
39 
40 static int	Input _ANSI_ARGS_((ClientData instanceData,
41 		    char *buf, int toRead, int *errorCodePtr));
42 
43 static int	Output _ANSI_ARGS_((ClientData instanceData,
44 	            CONST84 char *buf, int toWrite, int *errorCodePtr));
45 
46 static void	WatchChannel _ANSI_ARGS_((ClientData instanceData, int mask));
47 
48 static int	GetOption _ANSI_ARGS_((ClientData instanceData,
49 				       Tcl_Interp* interp,
50 				       CONST84 char *optionName,
51 				       Tcl_DString *dsPtr));
52 
53 static void	ChannelReady _ANSI_ARGS_((ClientData instanceData));
54 static int      GetFile      _ANSI_ARGS_((ClientData instanceData,
55 					  int direction,
56 					  ClientData* handlePtr));
57 
58 static int	BlockMode _ANSI_ARGS_((ClientData instanceData,
59 				       int mode));
60 /*
61  * This structure describes the channel type structure for in-memory channels:
62  * Fifo are not seekable. They have no writable options, but a readable.
63  */
64 
65 static Tcl_ChannelType channelType = {
66   "memory/fifo",	/* Type name.                                    */
67   (Tcl_ChannelTypeVersion)BlockMode, /* Set blocking behaviour.          */
68   Close,		/* Close channel, clean instance data            */
69   Input,		/* Handle read request                           */
70   Output,		/* Handle write request                          */
71   NULL,			/* Move location of access point.      NULL'able */
72   NULL,			/* Set options.                        NULL'able */
73   GetOption,		/* Get options.                        NULL'able */
74   WatchChannel,		/* Initialize notifier                           */
75 #if GT81
76   GetFile,              /* Get OS handle from the channel.               */
77   NULL                  /* Close2Proc, not available, no partial close
78 			 * possible */
79 #else
80   GetFile               /* Get OS handle from the channel.               */
81 #endif
82 };
83 
84 /*
85  * This structure describes the per-instance state of a in-memory fifo channel.
86  */
87 
88 typedef struct ChannelInstance {
89   Tcl_Channel    chan;   /* Backreference to generic channel information */
90   long int       length; /* Total number of bytes in the channel */
91 
92   Buf_BufferQueue queue;  /* Queue of buffers holding the information in this
93 			   * channel. */
94 
95   Tcl_TimerToken timer;  /* Timer used to link the channel into the
96 			  * notifier. */
97   int          interest; /* Interest in events as signaled by the user of
98 			  * the channel */
99 
100 #if 0
101 #if (GT81) && defined (TCL_THREADS)
102   Tcl_Mutex lock;        /* Semaphor to handle thread-spanning access to this
103 			  * fifo. */
104 #endif
105 #endif /* 0 */
106 
107 } ChannelInstance;
108 
109 /* Macro to check a fifo channel for emptiness.
110  */
111 
112 #define FIFO_EMPTY(c) (c->length == 0)
113 
114 
115 /*
116  *----------------------------------------------------------------------
117  *
118  * BlockMode --
119  *
120  *	Helper procedure to set blocking and nonblocking modes on a
121  *	memory channel. Invoked by generic IO level code.
122  *
123  * Results:
124  *	0 if successful, errno when failed.
125  *
126  * Side effects:
127  *	Sets the device into blocking or non-blocking mode.
128  *
129  *----------------------------------------------------------------------
130  */
131 
132 static int
BlockMode(instanceData,mode)133 BlockMode (instanceData, mode)
134      ClientData instanceData;
135      int mode;
136 {
137     return 0;
138 }
139 
140 /*
141  *------------------------------------------------------*
142  *
143  *	Close --
144  *
145  *	------------------------------------------------*
146  *	This procedure is called from the generic IO
147  *	level to perform channel-type-specific cleanup
148  *	when an in-memory fifo channel is closed.
149  *	------------------------------------------------*
150  *
151  *	Sideeffects:
152  *		Closes the device of the channel.
153  *
154  *	Result:
155  *		0 if successful, errno if failed.
156  *
157  *------------------------------------------------------*
158  */
159 /* ARGSUSED */
160 static int
Close(instanceData,interp)161 Close (instanceData, interp)
162 ClientData  instanceData;    /* The instance information of the channel to
163 			      * close */
164 Tcl_Interp* interp;          /* unused */
165 {
166   ChannelInstance* chan;
167 
168   chan = (ChannelInstance*) instanceData;
169 
170   /* Release the allocated memory. We can be sure that this is done
171    * only after the last user closed the channel, i.e. there will be
172    * no thread-spanning access !
173    */
174 
175   if (chan->timer != (Tcl_TimerToken) NULL) {
176     Tcl_DeleteTimerHandler (chan->timer);
177   }
178   chan->timer = (Tcl_TimerToken) NULL;
179 
180   Buf_FreeQueue (chan->queue);
181   Tcl_Free      ((char*) chan);
182 
183   return 0;
184 }
185 
186 /*
187  *------------------------------------------------------*
188  *
189  *	Input --
190  *
191  *	------------------------------------------------*
192  *	This procedure is invoked from the generic IO
193  *	level to read input from an in-memory fifo channel.
194  *	------------------------------------------------*
195  *
196  *	Sideeffects:
197  *		Reads input from the input device of the
198  *		channel.
199  *
200  *	Result:
201  *		The number of bytes read is returned or
202  *		-1 on error. An output argument contains
203  *		a POSIX error code if an error occurs, or
204  *		zero.
205  *
206  *------------------------------------------------------*
207  */
208 
209 static int
Input(instanceData,buf,toRead,errorCodePtr)210 Input (instanceData, buf, toRead, errorCodePtr)
211 ClientData instanceData;	/* The channel to read from */
212 char*      buf;			/* Buffer to fill */
213 int        toRead;		/* Requested number of bytes */
214 int*       errorCodePtr;	/* Location of error flag */
215 {
216   ChannelInstance* chan;
217 
218   if (toRead == 0) {
219     return 0;
220   }
221 
222   chan = (ChannelInstance*) instanceData;
223 
224   if (chan->length == 0) {
225     /* Signal EOF to higher layer */
226     return 0;
227   }
228 
229   toRead        = Buf_QueueRead (chan->queue, buf, toRead);
230   chan->length -= toRead;
231   *errorCodePtr = 0;
232 
233   return toRead;
234 }
235 
236 /*
237  *------------------------------------------------------*
238  *
239  *	Output --
240  *
241  *	------------------------------------------------*
242  *	This procedure is invoked from the generic IO
243  *	level to write output to a file channel.
244  *	------------------------------------------------*
245  *
246  *	Sideeffects:
247  *		Writes output on the output device of
248  *		the channel.
249  *
250  *	Result:
251  *		The number of bytes written is returned
252  *		or -1 on error. An output argument
253  *		contains a POSIX error code if an error
254  *		occurred, or zero.
255  *
256  *------------------------------------------------------*
257  */
258 
259 static int
Output(instanceData,buf,toWrite,errorCodePtr)260 Output (instanceData, buf, toWrite, errorCodePtr)
261 ClientData instanceData;	/* The channel to write to */
262 CONST84 char* buf;		/* Data to be stored. */
263 int        toWrite;		/* Number of bytes to write. */
264 int*       errorCodePtr;	/* Location of error flag. */
265 {
266   ChannelInstance* chan;
267 
268   if (toWrite == 0) {
269     return 0;
270   }
271 
272   chan          = (ChannelInstance*) instanceData;
273   toWrite       = Buf_QueueWrite (chan->queue, buf, toWrite);
274   chan->length += toWrite;
275 
276   return toWrite;
277 }
278 
279 /*
280  *------------------------------------------------------*
281  *
282  *	GetOption --
283  *
284  *	------------------------------------------------*
285  *	Computes an option value for a in-memory fifo
286  *	channel, or a list of all options and their values.
287  *	------------------------------------------------*
288  *
289  *	Sideeffects:
290  *		None.
291  *
292  *	Result:
293  *		A standard Tcl result. The value of the
294  *		specified option or a list of all options
295  *		and their values is returned in the
296  *		supplied DString.
297  *
298  *------------------------------------------------------*
299  */
300 
301 static int
GetOption(instanceData,interp,optionName,dsPtr)302 GetOption (instanceData, interp, optionName, dsPtr)
303 ClientData   instanceData;	/* Channel to query */
304 Tcl_Interp*  interp;		/* Interpreter to leave error messages in */
305 CONST84 char* optionName;	/* Name of reuqested option */
306 Tcl_DString* dsPtr;		/* String to place the result into */
307 {
308   /*
309    * In-memory fifo channels provide two channel type specific,
310    * read-only, fconfigure options, "length", that obtains
311    * the current number of bytes of data stored in the channel,
312    * and "allocated", that obtains the current number of bytes
313    * really allocated by the system for its buffers.
314    */
315 
316   ChannelInstance* chan;
317   char             buffer [50];
318   /* sufficient even for 64-bit quantities */
319 
320   chan = (ChannelInstance*) instanceData;
321 
322   /* Known options:
323    * -length:    Number of bytes currently used by the buffers.
324    * -allocated: Number of bytes currently allocated by the buffers.
325    */
326 
327   if ((optionName != (char*) NULL) &&
328       (0 != strcmp (optionName, "-length")) &&
329       (0 != strcmp (optionName, "-allocated"))) {
330     Tcl_SetErrno (EINVAL);
331     return Tcl_BadChannelOption (interp, optionName, "length allocated");
332   }
333 
334   if (optionName == (char*) NULL) {
335     /*
336      * optionName == NULL
337      * => a list of options and their values was requested,
338      * so append the optionName before the retrieved value.
339      */
340     Tcl_DStringAppendElement (dsPtr, "-length");
341     LTOA (chan->length, buffer);
342     Tcl_DStringAppendElement (dsPtr, buffer);
343 
344     Tcl_DStringAppendElement (dsPtr, "-allocated");
345     LTOA (chan->length, buffer);
346     Tcl_DStringAppendElement (dsPtr, buffer);
347 
348   } else if (0 == strcmp (optionName, "-length")) {
349     LTOA (chan->length, buffer);
350     Tcl_DStringAppendElement (dsPtr, buffer);
351   } else if (0 == strcmp (optionName, "-allocated")) {
352     LTOA (chan->length, buffer);
353     Tcl_DStringAppendElement (dsPtr, buffer);
354   }
355 
356   return TCL_OK;
357 }
358 
359 /*
360  *------------------------------------------------------*
361  *
362  *	WatchChannel --
363  *
364  *	------------------------------------------------*
365  *	Initialize the notifier to watch Tcl_Files from
366  *	this channel.
367  *	------------------------------------------------*
368  *
369  *	Sideeffects:
370  *		Sets up the notifier so that a future
371  *		event on the channel will be seen by Tcl.
372  *
373  *	Result:
374  *		None.
375  *
376  *------------------------------------------------------*
377  */
378 	/* ARGSUSED */
379 static void
WatchChannel(instanceData,mask)380 WatchChannel (instanceData, mask)
381 ClientData instanceData;	/* Channel to watch */
382 int        mask;		/* Events of interest */
383 {
384   /*
385    * In-memory fifo channels are not based on files.
386    * They are always writable, and almost always readable.
387    * We could call Tcl_NotifyChannel immediately, but this
388    * would starve other sources, so a timer is set up instead.
389    */
390 
391   ChannelInstance* chan = (ChannelInstance*) instanceData;
392 
393   if (mask) {
394     if (chan->timer == (Tcl_TimerToken) NULL) {
395       chan->timer = Tcl_CreateTimerHandler (DELAY, ChannelReady, instanceData);
396     }
397   } else {
398     if (chan->timer != (Tcl_TimerToken) NULL) {
399       Tcl_DeleteTimerHandler (chan->timer);
400     }
401     chan->timer = (Tcl_TimerToken) NULL;
402   }
403 
404   chan->interest = mask;
405 }
406 
407 /*
408  *------------------------------------------------------*
409  *
410  *	ChannelReady --
411  *
412  *	------------------------------------------------*
413  *	Called by the notifier (-> timer) to check whether
414  *	the channel is readable or writable.
415  *	------------------------------------------------*
416  *
417  *	Sideeffects:
418  *		As of 'Tcl_NotifyChannel'.
419  *
420  *	Result:
421  *		None.
422  *
423  *------------------------------------------------------*
424  */
425 
426 static void
ChannelReady(instanceData)427 ChannelReady (instanceData)
428 ClientData instanceData; /* Channel to query */
429 {
430   /*
431    * In-memory fifo channels are always writable (fileevent
432    * writable) and they are readable if they are not empty.
433    */
434 
435   ChannelInstance* chan = (ChannelInstance*) instanceData;
436   int              mask = TCL_READABLE | TCL_WRITABLE;
437 
438   /*
439    * Timer fired, our token is useless now.
440    */
441 
442   chan->timer = (Tcl_TimerToken) NULL;
443 
444   if (!chan->interest) {
445     return;
446   }
447 
448   if (! FIFO_EMPTY (chan)) {
449     mask &= ~TCL_READABLE;
450   }
451 
452   /* Tell Tcl about the possible events.
453    * This will regenerate the timer too, via 'WatchChannel'.
454    */
455 
456   mask &= chan->interest;
457   if (mask) {
458     Tcl_NotifyChannel (chan->chan, mask);
459   } else {
460     chan->timer = Tcl_CreateTimerHandler (DELAY, ChannelReady, instanceData);
461   }
462 }
463 
464 /*
465  *------------------------------------------------------*
466  *
467  *	GetFile --
468  *
469  *	------------------------------------------------*
470  *	Called from Tcl_GetChannelHandle to retrieve
471  *	OS handles from inside a in-memory fifo channel.
472  *	------------------------------------------------*
473  *
474  *	Sideeffects:
475  *		None.
476  *
477  *	Result:
478  *		The appropriate OS handle or NULL if not
479  *		present.
480  *
481  *------------------------------------------------------*
482  */
483 static int
GetFile(instanceData,direction,handlePtr)484 GetFile (instanceData, direction, handlePtr)
485 ClientData  instanceData;	/* Channel to query */
486 int         direction;		/* Direction of interest */
487 ClientData* handlePtr;          /* Space to the handle into */
488 {
489   /*
490    * In-memory fifo channels are not based on files.
491    */
492 
493   /* *handlePtr = (ClientData) NULL; */
494   return TCL_ERROR;
495 }
496 
497 /*
498  * ----------------------------------------------------------------------
499  *
500  * Memchan_CreateFifoChannel -
501  *
502  *	Create a memchan 'fifo' channel.
503  *
504  * Results:
505  *	Returns the newly minted channel
506  *
507  * Side effects:
508  *	A fifo channel is registered in the current interp.
509  *
510  * ----------------------------------------------------------------------
511  */
512 
513 Tcl_Channel
Memchan_CreateFifoChannel(interp)514 Memchan_CreateFifoChannel(interp)
515      Tcl_Interp *interp;	/* the current interp */
516 {
517   Tcl_Obj*         channelHandle;
518   Tcl_Channel      chan;
519   ChannelInstance* instance;
520 
521   instance = (ChannelInstance*) Tcl_Alloc (sizeof (ChannelInstance));
522   instance->length = 0;
523   instance->queue  = Buf_NewQueue ();
524 
525   channelHandle = MemchanGenHandle ("fifo");
526 
527   chan = Tcl_CreateChannel (&channelType,
528 			    Tcl_GetStringFromObj (channelHandle, NULL),
529 			    (ClientData) instance,
530 			    TCL_READABLE | TCL_WRITABLE);
531 
532   instance->chan      = chan;
533   instance->timer     = (Tcl_TimerToken) NULL;
534   instance->interest  = 0;
535 
536   Tcl_RegisterChannel  (interp, chan);
537   Tcl_SetChannelOption (interp, chan, "-buffering", "none");
538   Tcl_SetChannelOption (interp, chan, "-blocking",  "0");
539 
540   return chan;
541 }
542 
543 /*
544  *------------------------------------------------------*
545  *
546  *	MemchanFifoCmd --
547  *
548  *	------------------------------------------------*
549  *	This procedure realizes the 'fifo' command.
550  *	See the manpages for details on what it does.
551  *	------------------------------------------------*
552  *
553  *	Sideeffects:
554  *		See the user documentation.
555  *
556  *	Result:
557  *		A standard Tcl result.
558  *
559  *------------------------------------------------------*
560  */
561 	/* ARGSUSED */
562 int
MemchanFifoCmd(notUsed,interp,objc,objv)563 MemchanFifoCmd (notUsed, interp, objc, objv)
564 ClientData    notUsed;		/* Not used. */
565 Tcl_Interp*   interp;		/* Current interpreter. */
566 int           objc;		/* Number of arguments. */
567 Tcl_Obj*CONST objv[];		/* Argument objects. */
568 {
569     Tcl_Channel chan;
570 
571     if (objc != 1) {
572 	Tcl_AppendResult (interp, "wrong # args: should be \"fifo\"",
573 	    (char*) NULL);
574 	return TCL_ERROR;
575     }
576 
577     chan = Memchan_CreateFifoChannel(interp);
578     Tcl_AppendResult(interp, Tcl_GetChannelName(chan), (char *)NULL);
579     return TCL_OK;
580 }
581 
582