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