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