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