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