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