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 
40 static int	Close _ANSI_ARGS_((ClientData instanceData,
41                                    Tcl_Interp *interp));
42 
43 static int	Input _ANSI_ARGS_((ClientData instanceData,
44                                    char *buf,
45                                    int toRead, int *errorCodePtr));
46 
47 static int	Output _ANSI_ARGS_((ClientData instanceData,
48 				    CONST84 char *buf,
49 				    int toWrite,
50 				    int *errorCodePtr));
51 
52 static void	WatchChannel _ANSI_ARGS_((ClientData instanceData, int mask));
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 static int	GetOption _ANSI_ARGS_((ClientData instanceData,
62 				       Tcl_Interp* interp,
63 				       CONST84 char *optionName,
64 				       Tcl_DString *dsPtr));
65 
66 static 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 
76 static 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 
100 typedef 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 
124 static int
BlockMode(instanceData,mode)125 BlockMode (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 */
152 static int
Close(instanceData,interp)153 Close (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 
194 static int
Input(instanceData,buf,toRead,errorCodePtr)195 Input(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 
228 static int
Output(instanceData,buf,toWrite,errorCodePtr)229 Output (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 */
262 static void
WatchChannel(instanceData,mask)263 WatchChannel (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 
306 static void
ChannelReady(instanceData)307 ChannelReady (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  */
350 static int
GetFile(instanceData,direction,handlePtr)351 GetFile (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 
384 static int
SetOption(instanceData,interp,optionName,newValue)385 SetOption (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 
431 static int
GetOption(instanceData,interp,optionName,dsPtr)432 GetOption (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 
482 Tcl_Channel
Memchan_CreateZeroChannel(interp)483 Memchan_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 */
529 int
MemchanZeroCmd(notUsed,interp,objc,objv)530 MemchanZeroCmd (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