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