1/* 2 * tclIOGT.c -- 3 * 4 * Implements a generic transformation exposing the underlying API at the 5 * script level. Contributed by Andreas Kupries. 6 * 7 * Copyright (c) 2000 Ajuba Solutions 8 * Copyright (c) 1999-2000 Andreas Kupries (a.kupries@westend.com) 9 * 10 * See the file "license.terms" for information on usage and redistribution of 11 * this file, and for a DISCLAIMER OF ALL WARRANTIES. 12 * 13 * CVS: $Id: tclIOGT.c,v 1.20 2007/12/13 15:23:18 dgp Exp $ 14 */ 15 16#include "tclInt.h" 17#include "tclIO.h" 18 19/* 20 * Forward declarations of internal procedures. First the driver procedures of 21 * the transformation. 22 */ 23 24static int TransformBlockModeProc(ClientData instanceData, 25 int mode); 26static int TransformCloseProc(ClientData instanceData, 27 Tcl_Interp *interp); 28static int TransformInputProc(ClientData instanceData, char *buf, 29 int toRead, int *errorCodePtr); 30static int TransformOutputProc(ClientData instanceData, 31 const char *buf, int toWrite, int *errorCodePtr); 32static int TransformSeekProc(ClientData instanceData, long offset, 33 int mode, int *errorCodePtr); 34static int TransformSetOptionProc(ClientData instanceData, 35 Tcl_Interp *interp, const char *optionName, 36 const char *value); 37static int TransformGetOptionProc(ClientData instanceData, 38 Tcl_Interp *interp, const char *optionName, 39 Tcl_DString *dsPtr); 40static void TransformWatchProc(ClientData instanceData, int mask); 41static int TransformGetFileHandleProc(ClientData instanceData, 42 int direction, ClientData *handlePtr); 43static int TransformNotifyProc(ClientData instanceData, int mask); 44static Tcl_WideInt TransformWideSeekProc(ClientData instanceData, 45 Tcl_WideInt offset, int mode, int *errorCodePtr); 46 47/* 48 * Forward declarations of internal procedures. Secondly the procedures for 49 * handling and generating fileeevents. 50 */ 51 52static void TransformChannelHandlerTimer(ClientData clientData); 53 54/* 55 * Forward declarations of internal procedures. Third, helper procedures 56 * encapsulating essential tasks. 57 */ 58 59typedef struct TransformChannelData TransformChannelData; 60 61static int ExecuteCallback(TransformChannelData *ctrl, 62 Tcl_Interp *interp, unsigned char *op, 63 unsigned char *buf, int bufLen, int transmit, 64 int preserve); 65 66/* 67 * Action codes to give to 'ExecuteCallback' (argument 'transmit'), telling 68 * the procedure what to do with the result of the script it calls. 69 */ 70 71#define TRANSMIT_DONT 0 /* No transfer to do. */ 72#define TRANSMIT_DOWN 1 /* Transfer to the underlying channel. */ 73#define TRANSMIT_SELF 2 /* Transfer into our channel. */ 74#define TRANSMIT_IBUF 3 /* Transfer to internal input buffer. */ 75#define TRANSMIT_NUM 4 /* Transfer number to 'maxRead'. */ 76 77/* 78 * Codes for 'preserve' of 'ExecuteCallback'. 79 */ 80 81#define P_PRESERVE 1 82#define P_NO_PRESERVE 0 83 84/* 85 * Strings for the action codes delivered to the script implementing a 86 * transformation. Argument 'op' of 'ExecuteCallback'. 87 */ 88 89#define A_CREATE_WRITE (UCHARP("create/write")) 90#define A_DELETE_WRITE (UCHARP("delete/write")) 91#define A_FLUSH_WRITE (UCHARP("flush/write")) 92#define A_WRITE (UCHARP("write")) 93 94#define A_CREATE_READ (UCHARP("create/read")) 95#define A_DELETE_READ (UCHARP("delete/read")) 96#define A_FLUSH_READ (UCHARP("flush/read")) 97#define A_READ (UCHARP("read")) 98 99#define A_QUERY_MAXREAD (UCHARP("query/maxRead")) 100#define A_CLEAR_READ (UCHARP("clear/read")) 101 102/* 103 * Management of a simple buffer. 104 */ 105 106typedef struct ResultBuffer ResultBuffer; 107 108static inline void ResultClear(ResultBuffer *r); 109static inline void ResultInit(ResultBuffer *r); 110static inline int ResultEmpty(ResultBuffer *r); 111static inline int ResultCopy(ResultBuffer *r, unsigned char *buf, 112 size_t toRead); 113static inline void ResultAdd(ResultBuffer *r, unsigned char *buf, 114 size_t toWrite); 115 116/* 117 * This structure describes the channel type structure for Tcl-based 118 * transformations. 119 */ 120 121static Tcl_ChannelType transformChannelType = { 122 "transform", /* Type name. */ 123 TCL_CHANNEL_VERSION_5, /* v5 channel */ 124 TransformCloseProc, /* Close proc. */ 125 TransformInputProc, /* Input proc. */ 126 TransformOutputProc, /* Output proc. */ 127 TransformSeekProc, /* Seek proc. */ 128 TransformSetOptionProc, /* Set option proc. */ 129 TransformGetOptionProc, /* Get option proc. */ 130 TransformWatchProc, /* Initialize notifier. */ 131 TransformGetFileHandleProc, /* Get OS handles out of channel. */ 132 NULL, /* close2proc */ 133 TransformBlockModeProc, /* Set blocking/nonblocking mode.*/ 134 NULL, /* Flush proc. */ 135 TransformNotifyProc, /* Handling of events bubbling up. */ 136 TransformWideSeekProc, /* Wide seek proc. */ 137 NULL, /* Thread action. */ 138 NULL, /* Truncate. */ 139}; 140 141/* 142 * Possible values for 'flags' field in control structure, see below. 143 */ 144 145#define CHANNEL_ASYNC (1<<0) /* Non-blocking mode. */ 146 147/* 148 * Definition of the structure containing the information about the internal 149 * input buffer. 150 */ 151 152struct ResultBuffer { 153 unsigned char *buf; /* Reference to the buffer area. */ 154 size_t allocated; /* Allocated size of the buffer area. */ 155 size_t used; /* Number of bytes in the buffer, no more than 156 * number allocated. */ 157}; 158 159/* 160 * Additional bytes to allocate during buffer expansion. 161 */ 162 163#define INCREMENT 512 164 165/* 166 * Number of milliseconds to wait before firing an event to flush out 167 * information waiting in buffers (fileevent support). 168 */ 169 170#define FLUSH_DELAY 5 171 172/* 173 * Convenience macro to make some casts easier to use. 174 */ 175 176#define UCHARP(x) ((unsigned char *) (x)) 177 178/* 179 * Definition of a structure used by all transformations generated here to 180 * maintain their local state. 181 */ 182 183struct TransformChannelData { 184 /* 185 * General section. Data to integrate the transformation into the channel 186 * system. 187 */ 188 189 Tcl_Channel self; /* Our own Channel handle. */ 190 int readIsFlushed; /* Flag to note whether in.flushProc was 191 * called or not. */ 192 int flags; /* Currently CHANNEL_ASYNC or zero. */ 193 int watchMask; /* Current watch/event/interest mask. */ 194 int mode; /* Mode of parent channel, OR'ed combination 195 * of TCL_READABLE, TCL_WRITABLE. */ 196 Tcl_TimerToken timer; /* Timer for automatic flushing of information 197 * sitting in an internal buffer. Required for 198 * full fileevent support. */ 199 200 /* 201 * Transformation specific data. 202 */ 203 204 int maxRead; /* Maximum allowed number of bytes to read, as 205 * given to us by the Tcl script implementing 206 * the transformation. */ 207 Tcl_Interp *interp; /* Reference to the interpreter which created 208 * the transformation. Used to execute the 209 * code below. */ 210 Tcl_Obj *command; /* Tcl code to execute for a buffer */ 211 ResultBuffer result; /* Internal buffer used to store the result of 212 * a transformation of incoming data. Also 213 * serves as buffer of all data not yet 214 * consumed by the reader. */ 215}; 216 217/* 218 *---------------------------------------------------------------------- 219 * 220 * TclChannelTransform -- 221 * 222 * Implements the Tcl "testchannel transform" debugging command. This is 223 * part of the testing environment. This sets up a tcl script (cmdObjPtr) 224 * to be used as a transform on the channel. 225 * 226 * Results: 227 * A standard Tcl result. 228 * 229 * Side effects: 230 * None. 231 * 232 *---------------------------------------------------------------------- 233 */ 234 235 /* ARGSUSED */ 236int 237TclChannelTransform( 238 Tcl_Interp *interp, /* Interpreter for result. */ 239 Tcl_Channel chan, /* Channel to transform. */ 240 Tcl_Obj *cmdObjPtr) /* Script to use for transform. */ 241{ 242 Channel *chanPtr; /* The actual channel. */ 243 ChannelState *statePtr; /* State info for channel. */ 244 int mode; /* Read/write mode of the channel. */ 245 TransformChannelData *dataPtr; 246 Tcl_DString ds; 247 248 if (chan == NULL) { 249 return TCL_ERROR; 250 } 251 252 chanPtr = (Channel *) chan; 253 statePtr = chanPtr->state; 254 chanPtr = statePtr->topChanPtr; 255 chan = (Tcl_Channel) chanPtr; 256 mode = (statePtr->flags & (TCL_READABLE|TCL_WRITABLE)); 257 258 /* 259 * Now initialize the transformation state and stack it upon the specified 260 * channel. One of the necessary things to do is to retrieve the blocking 261 * regime of the underlying channel and to use the same for us too. 262 */ 263 264 dataPtr = (TransformChannelData *) ckalloc(sizeof(TransformChannelData)); 265 266 Tcl_DStringInit(&ds); 267 Tcl_GetChannelOption(interp, chan, "-blocking", &ds); 268 dataPtr->readIsFlushed = 0; 269 dataPtr->flags = 0; 270 if (ds.string[0] == '0') { 271 dataPtr->flags |= CHANNEL_ASYNC; 272 } 273 Tcl_DStringFree(&ds); 274 275 dataPtr->self = chan; 276 dataPtr->watchMask = 0; 277 dataPtr->mode = mode; 278 dataPtr->timer = NULL; 279 dataPtr->maxRead = 4096; /* Initial value not relevant. */ 280 dataPtr->interp = interp; 281 dataPtr->command = cmdObjPtr; 282 Tcl_IncrRefCount(dataPtr->command); 283 284 ResultInit(&dataPtr->result); 285 286 dataPtr->self = Tcl_StackChannel(interp, &transformChannelType, dataPtr, 287 mode, chan); 288 if (dataPtr->self == NULL) { 289 Tcl_AppendResult(interp, "\nfailed to stack channel \"", 290 Tcl_GetChannelName(chan), "\"", NULL); 291 Tcl_DecrRefCount(dataPtr->command); 292 ResultClear(&dataPtr->result); 293 ckfree((char *) dataPtr); 294 return TCL_ERROR; 295 } 296 297 /* 298 * At last initialize the transformation at the script level. 299 */ 300 301 if ((dataPtr->mode & TCL_WRITABLE) && ExecuteCallback(dataPtr, NULL, 302 A_CREATE_WRITE, NULL, 0, TRANSMIT_DONT, P_NO_PRESERVE) != TCL_OK){ 303 Tcl_UnstackChannel(interp, chan); 304 return TCL_ERROR; 305 } 306 307 if ((dataPtr->mode & TCL_READABLE) && ExecuteCallback(dataPtr, NULL, 308 A_CREATE_READ, NULL, 0, TRANSMIT_DONT, P_NO_PRESERVE) != TCL_OK) { 309 ExecuteCallback(dataPtr, NULL, A_DELETE_WRITE, NULL, 0, TRANSMIT_DONT, 310 P_NO_PRESERVE); 311 Tcl_UnstackChannel(interp, chan); 312 return TCL_ERROR; 313 } 314 315 return TCL_OK; 316} 317 318/* 319 *---------------------------------------------------------------------- 320 * 321 * ExecuteCallback -- 322 * 323 * Executes the defined callback for buffer and operation. 324 * 325 * Side effects: 326 * As of the executed tcl script. 327 * 328 * Result: 329 * A standard TCL error code. In case of an error a message is left in 330 * the result area of the specified interpreter. 331 * 332 *---------------------------------------------------------------------- 333 */ 334 335static int 336ExecuteCallback( 337 TransformChannelData *dataPtr, 338 /* Transformation with the callback. */ 339 Tcl_Interp *interp, /* Current interpreter, possibly NULL. */ 340 unsigned char *op, /* Operation invoking the callback. */ 341 unsigned char *buf, /* Buffer to give to the script. */ 342 int bufLen, /* And its length. */ 343 int transmit, /* Flag, determines whether the result of the 344 * callback is sent to the underlying channel 345 * or not. */ 346 int preserve) /* Flag. If true the procedure will preserve 347 * the result state of all accessed 348 * interpreters. */ 349{ 350 Tcl_Obj *resObj; /* See below, switch (transmit). */ 351 int resLen; 352 unsigned char *resBuf; 353 Tcl_InterpState state = NULL; 354 int res = TCL_OK; 355 Tcl_Obj *command = Tcl_DuplicateObj(dataPtr->command); 356 357 /* 358 * Step 1, create the complete command to execute. Do this by appending 359 * operation and buffer to operate upon to a copy of the callback 360 * definition. We *cannot* create a list containing 3 objects and then use 361 * 'Tcl_EvalObjv', because the command may contain additional prefixed 362 * arguments. Feather's curried commands would come in handy here. 363 */ 364 365 if (preserve == P_PRESERVE) { 366 state = Tcl_SaveInterpState(dataPtr->interp, res); 367 } 368 369 Tcl_IncrRefCount(command); 370 res = Tcl_ListObjAppendElement(dataPtr->interp, command, 371 Tcl_NewStringObj((char *) op, -1)); 372 if (res != TCL_OK) { 373 goto cleanup; 374 } 375 376 /* 377 * Use a byte-array to prevent the misinterpretation of binary data coming 378 * through as UTF while at the tcl level. 379 */ 380 381 res = Tcl_ListObjAppendElement(dataPtr->interp, command, 382 Tcl_NewByteArrayObj(buf, bufLen)); 383 if (res != TCL_OK) { 384 goto cleanup; 385 } 386 387 /* 388 * Step 2, execute the command at the global level of the interpreter used 389 * to create the transformation. Destroy the command afterward. If an 390 * error occured and the current interpreter is defined and not equal to 391 * the interpreter for the callback, then copy the error message into 392 * current interpreter. Don't copy if in preservation mode. 393 */ 394 395 res = Tcl_EvalObjEx(dataPtr->interp, command, TCL_EVAL_GLOBAL); 396 Tcl_DecrRefCount(command); 397 command = NULL; 398 399 if ((res != TCL_OK) && (interp != NULL) && (dataPtr->interp != interp) 400 && (preserve == P_NO_PRESERVE)) { 401 Tcl_SetObjResult(interp, Tcl_GetObjResult(dataPtr->interp)); 402 return res; 403 } 404 405 /* 406 * Step 3, transmit a possible conversion result to the underlying 407 * channel, or ourselves. 408 */ 409 410 switch (transmit) { 411 case TRANSMIT_DONT: 412 /* nothing to do */ 413 break; 414 415 case TRANSMIT_DOWN: 416 resObj = Tcl_GetObjResult(dataPtr->interp); 417 resBuf = Tcl_GetByteArrayFromObj(resObj, &resLen); 418 Tcl_WriteRaw(Tcl_GetStackedChannel(dataPtr->self), (char *) resBuf, 419 resLen); 420 break; 421 422 case TRANSMIT_SELF: 423 resObj = Tcl_GetObjResult(dataPtr->interp); 424 resBuf = Tcl_GetByteArrayFromObj(resObj, &resLen); 425 Tcl_WriteRaw(dataPtr->self, (char *) resBuf, resLen); 426 break; 427 428 case TRANSMIT_IBUF: 429 resObj = Tcl_GetObjResult(dataPtr->interp); 430 resBuf = Tcl_GetByteArrayFromObj(resObj, &resLen); 431 ResultAdd(&dataPtr->result, resBuf, resLen); 432 break; 433 434 case TRANSMIT_NUM: 435 /* 436 * Interpret result as integer number. 437 */ 438 439 resObj = Tcl_GetObjResult(dataPtr->interp); 440 TclGetIntFromObj(dataPtr->interp, resObj, &dataPtr->maxRead); 441 break; 442 } 443 444 Tcl_ResetResult(dataPtr->interp); 445 if (preserve == P_PRESERVE) { 446 (void) Tcl_RestoreInterpState(dataPtr->interp, state); 447 } 448 return res; 449 450 cleanup: 451 if (preserve == P_PRESERVE) { 452 (void) Tcl_RestoreInterpState(dataPtr->interp, state); 453 } 454 if (command != NULL) { 455 Tcl_DecrRefCount(command); 456 } 457 return res; 458} 459 460/* 461 *---------------------------------------------------------------------- 462 * 463 * TransformBlockModeProc -- 464 * 465 * Trap handler. Called by the generic IO system during option processing 466 * to change the blocking mode of the channel. 467 * 468 * Side effects: 469 * Forwards the request to the underlying channel. 470 * 471 * Result: 472 * 0 if successful, errno when failed. 473 * 474 *---------------------------------------------------------------------- 475 */ 476 477static int 478TransformBlockModeProc( 479 ClientData instanceData, /* State of transformation. */ 480 int mode) /* New blocking mode. */ 481{ 482 TransformChannelData *dataPtr = instanceData; 483 484 if (mode == TCL_MODE_NONBLOCKING) { 485 dataPtr->flags |= CHANNEL_ASYNC; 486 } else { 487 dataPtr->flags &= ~CHANNEL_ASYNC; 488 } 489 return 0; 490} 491 492/* 493 *---------------------------------------------------------------------- 494 * 495 * TransformCloseProc -- 496 * 497 * Trap handler. Called by the generic IO system during destruction of 498 * the transformation channel. 499 * 500 * Side effects: 501 * Releases the memory allocated in 'Tcl_TransformObjCmd'. 502 * 503 * Result: 504 * None. 505 * 506 *---------------------------------------------------------------------- 507 */ 508 509static int 510TransformCloseProc( 511 ClientData instanceData, 512 Tcl_Interp *interp) 513{ 514 TransformChannelData *dataPtr = instanceData; 515 516 /* 517 * Important: In this procedure 'dataPtr->self' already points to the 518 * underlying channel. 519 * 520 * There is no need to cancel an existing channel handler, this is already 521 * done. Either by 'Tcl_UnstackChannel' or by the general cleanup in 522 * 'Tcl_Close'. 523 * 524 * But we have to cancel an active timer to prevent it from firing on the 525 * removed channel. 526 */ 527 528 if (dataPtr->timer != NULL) { 529 Tcl_DeleteTimerHandler(dataPtr->timer); 530 dataPtr->timer = NULL; 531 } 532 533 /* 534 * Now flush data waiting in internal buffers to output and input. The 535 * input must be done despite the fact that there is no real receiver for 536 * it anymore. But the scripts might have sideeffects other parts of the 537 * system rely on (f.e. signaling the close to interested parties). 538 */ 539 540 if (dataPtr->mode & TCL_WRITABLE) { 541 ExecuteCallback(dataPtr, interp, A_FLUSH_WRITE, NULL, 0, 542 TRANSMIT_DOWN, P_PRESERVE); 543 } 544 545 if ((dataPtr->mode & TCL_READABLE) && !dataPtr->readIsFlushed) { 546 dataPtr->readIsFlushed = 1; 547 ExecuteCallback(dataPtr, interp, A_FLUSH_READ, NULL, 0, TRANSMIT_IBUF, 548 P_PRESERVE); 549 } 550 551 if (dataPtr->mode & TCL_WRITABLE) { 552 ExecuteCallback(dataPtr, interp, A_DELETE_WRITE, NULL, 0, 553 TRANSMIT_DONT, P_PRESERVE); 554 } 555 if (dataPtr->mode & TCL_READABLE) { 556 ExecuteCallback(dataPtr, interp, A_DELETE_READ, NULL, 0, 557 TRANSMIT_DONT, P_PRESERVE); 558 } 559 560 /* 561 * General cleanup. 562 */ 563 564 ResultClear(&dataPtr->result); 565 Tcl_DecrRefCount(dataPtr->command); 566 ckfree((char *) dataPtr); 567 return TCL_OK; 568} 569 570/* 571 *---------------------------------------------------------------------- 572 * 573 * TransformInputProc -- 574 * 575 * Called by the generic IO system to convert read data. 576 * 577 * Side effects: 578 * As defined by the conversion. 579 * 580 * Result: 581 * A transformed buffer. 582 * 583 *---------------------------------------------------------------------- 584 */ 585 586static int 587TransformInputProc( 588 ClientData instanceData, 589 char *buf, 590 int toRead, 591 int *errorCodePtr) 592{ 593 TransformChannelData *dataPtr = instanceData; 594 int gotBytes, read, copied; 595 Tcl_Channel downChan; 596 597 /* 598 * Should assert(dataPtr->mode & TCL_READABLE); 599 */ 600 601 if (toRead == 0) { 602 /* 603 * Catch a no-op. 604 */ 605 return 0; 606 } 607 608 gotBytes = 0; 609 downChan = Tcl_GetStackedChannel(dataPtr->self); 610 611 while (toRead > 0) { 612 /* 613 * Loop until the request is satisfied (or no data is available from 614 * below, possibly EOF). 615 */ 616 617 copied = ResultCopy(&dataPtr->result, UCHARP(buf), toRead); 618 toRead -= copied; 619 buf += copied; 620 gotBytes += copied; 621 622 if (toRead == 0) { 623 /* 624 * The request was completely satisfied from our buffers. We can 625 * break out of the loop and return to the caller. 626 */ 627 628 return gotBytes; 629 } 630 631 /* 632 * Length (dataPtr->result) == 0, toRead > 0 here. Use the incoming 633 * 'buf'! as target to store the intermediary information read from 634 * the underlying channel. 635 * 636 * Ask the tcl level how much data it allows us to read from the 637 * underlying channel. This feature allows the transform to signal EOF 638 * upstream although there is none downstream. Useful to control an 639 * unbounded 'fcopy', either through counting bytes, or by pattern 640 * matching. 641 */ 642 643 ExecuteCallback(dataPtr, NULL, A_QUERY_MAXREAD, NULL, 0, 644 TRANSMIT_NUM /* -> maxRead */, P_PRESERVE); 645 646 if (dataPtr->maxRead >= 0) { 647 if (dataPtr->maxRead < toRead) { 648 toRead = dataPtr->maxRead; 649 } 650 } /* else: 'maxRead < 0' == Accept the current value of toRead. */ 651 if (toRead <= 0) { 652 return gotBytes; 653 } 654 655 /* 656 * Get bytes from the underlying channel. 657 */ 658 659 read = Tcl_ReadRaw(downChan, buf, toRead); 660 if (read < 0) { 661 /* 662 * Report errors to caller. EAGAIN is a special situation. If we 663 * had some data before we report that instead of the request to 664 * re-try. 665 */ 666 667 if ((Tcl_GetErrno() == EAGAIN) && (gotBytes > 0)) { 668 return gotBytes; 669 } 670 671 *errorCodePtr = Tcl_GetErrno(); 672 return -1; 673 } else if (read == 0) { 674 /* 675 * Check wether we hit on EOF in the underlying channel or not. If 676 * not differentiate between blocking and non-blocking modes. In 677 * non-blocking mode we ran temporarily out of data. Signal this 678 * to the caller via EWOULDBLOCK and error return (-1). In the 679 * other cases we simply return what we got and let the caller 680 * wait for more. On the other hand, if we got an EOF we have to 681 * convert and flush all waiting partial data. 682 */ 683 684 if (!Tcl_Eof(downChan)) { 685 if ((gotBytes == 0) && (dataPtr->flags & CHANNEL_ASYNC)) { 686 *errorCodePtr = EWOULDBLOCK; 687 return -1; 688 } 689 return gotBytes; 690 } 691 692 if (dataPtr->readIsFlushed) { 693 /* 694 * Already flushed, nothing to do anymore. 695 */ 696 697 return gotBytes; 698 } 699 700 dataPtr->readIsFlushed = 1; 701 ExecuteCallback(dataPtr, NULL, A_FLUSH_READ, NULL, 0, 702 TRANSMIT_IBUF, P_PRESERVE); 703 704 if (ResultEmpty(&dataPtr->result)) { 705 /* 706 * We had nothing to flush. 707 */ 708 709 return gotBytes; 710 } 711 712 continue; /* at: while (toRead > 0) */ 713 } /* read == 0 */ 714 715 /* 716 * Transform the read chunk and add the result to our read buffer 717 * (dataPtr->result). 718 */ 719 720 if (ExecuteCallback(dataPtr, NULL, A_READ, UCHARP(buf), read, 721 TRANSMIT_IBUF, P_PRESERVE) != TCL_OK) { 722 *errorCodePtr = EINVAL; 723 return -1; 724 } 725 } /* while toRead > 0 */ 726 727 return gotBytes; 728} 729 730/* 731 *---------------------------------------------------------------------- 732 * 733 * TransformOutputProc -- 734 * 735 * Called by the generic IO system to convert data waiting to be written. 736 * 737 * Side effects: 738 * As defined by the transformation. 739 * 740 * Result: 741 * A transformed buffer. 742 * 743 *---------------------------------------------------------------------- 744 */ 745 746static int 747TransformOutputProc( 748 ClientData instanceData, 749 const char *buf, 750 int toWrite, 751 int *errorCodePtr) 752{ 753 TransformChannelData *dataPtr = instanceData; 754 755 /* 756 * Should assert(dataPtr->mode & TCL_WRITABLE); 757 */ 758 759 if (toWrite == 0) { 760 /* 761 * Catch a no-op. 762 */ 763 764 return 0; 765 } 766 767 if (ExecuteCallback(dataPtr, NULL, A_WRITE, UCHARP(buf), toWrite, 768 TRANSMIT_DOWN, P_NO_PRESERVE) != TCL_OK) { 769 *errorCodePtr = EINVAL; 770 return -1; 771 } 772 773 return toWrite; 774} 775 776/* 777 *---------------------------------------------------------------------- 778 * 779 * TransformSeekProc -- 780 * 781 * This procedure is called by the generic IO level to move the access 782 * point in a channel. 783 * 784 * Side effects: 785 * Moves the location at which the channel will be accessed in future 786 * operations. Flushes all transformation buffers, then forwards it to 787 * the underlying channel. 788 * 789 * Result: 790 * -1 if failed, the new position if successful. An output argument 791 * contains the POSIX error code if an error occurred, or zero. 792 * 793 *---------------------------------------------------------------------- 794 */ 795 796static int 797TransformSeekProc( 798 ClientData instanceData, /* The channel to manipulate. */ 799 long offset, /* Size of movement. */ 800 int mode, /* How to move. */ 801 int *errorCodePtr) /* Location of error flag. */ 802{ 803 TransformChannelData *dataPtr = instanceData; 804 Tcl_Channel parent = Tcl_GetStackedChannel(dataPtr->self); 805 Tcl_ChannelType *parentType = Tcl_GetChannelType(parent); 806 Tcl_DriverSeekProc *parentSeekProc = Tcl_ChannelSeekProc(parentType); 807 808 if ((offset == 0) && (mode == SEEK_CUR)) { 809 /* 810 * This is no seek but a request to tell the caller the current 811 * location. Simply pass the request down. 812 */ 813 814 return parentSeekProc(Tcl_GetChannelInstanceData(parent), offset, 815 mode, errorCodePtr); 816 } 817 818 /* 819 * It is a real request to change the position. Flush all data waiting for 820 * output and discard everything in the input buffers. Then pass the 821 * request down, unchanged. 822 */ 823 824 if (dataPtr->mode & TCL_WRITABLE) { 825 ExecuteCallback(dataPtr, NULL, A_FLUSH_WRITE, NULL, 0, TRANSMIT_DOWN, 826 P_NO_PRESERVE); 827 } 828 829 if (dataPtr->mode & TCL_READABLE) { 830 ExecuteCallback(dataPtr, NULL, A_CLEAR_READ, NULL, 0, TRANSMIT_DONT, 831 P_NO_PRESERVE); 832 ResultClear(&dataPtr->result); 833 dataPtr->readIsFlushed = 0; 834 } 835 836 return parentSeekProc(Tcl_GetChannelInstanceData(parent), offset, mode, 837 errorCodePtr); 838} 839 840/* 841 *---------------------------------------------------------------------- 842 * 843 * TransformWideSeekProc -- 844 * 845 * This procedure is called by the generic IO level to move the access 846 * point in a channel, with a (potentially) 64-bit offset. 847 * 848 * Side effects: 849 * Moves the location at which the channel will be accessed in future 850 * operations. Flushes all transformation buffers, then forwards it to 851 * the underlying channel. 852 * 853 * Result: 854 * -1 if failed, the new position if successful. An output argument 855 * contains the POSIX error code if an error occurred, or zero. 856 * 857 *---------------------------------------------------------------------- 858 */ 859 860static Tcl_WideInt 861TransformWideSeekProc( 862 ClientData instanceData, /* The channel to manipulate. */ 863 Tcl_WideInt offset, /* Size of movement. */ 864 int mode, /* How to move. */ 865 int *errorCodePtr) /* Location of error flag. */ 866{ 867 TransformChannelData *dataPtr = instanceData; 868 Tcl_Channel parent = Tcl_GetStackedChannel(dataPtr->self); 869 Tcl_ChannelType *parentType = Tcl_GetChannelType(parent); 870 Tcl_DriverSeekProc *parentSeekProc = Tcl_ChannelSeekProc(parentType); 871 Tcl_DriverWideSeekProc *parentWideSeekProc = 872 Tcl_ChannelWideSeekProc(parentType); 873 ClientData parentData = Tcl_GetChannelInstanceData(parent); 874 875 if ((offset == Tcl_LongAsWide(0)) && (mode == SEEK_CUR)) { 876 /* 877 * This is no seek but a request to tell the caller the current 878 * location. Simply pass the request down. 879 */ 880 881 if (parentWideSeekProc != NULL) { 882 return parentWideSeekProc(parentData, offset, mode, errorCodePtr); 883 } 884 885 return Tcl_LongAsWide(parentSeekProc(parentData, 0, mode, 886 errorCodePtr)); 887 } 888 889 /* 890 * It is a real request to change the position. Flush all data waiting for 891 * output and discard everything in the input buffers. Then pass the 892 * request down, unchanged. 893 */ 894 895 if (dataPtr->mode & TCL_WRITABLE) { 896 ExecuteCallback(dataPtr, NULL, A_FLUSH_WRITE, NULL, 0, TRANSMIT_DOWN, 897 P_NO_PRESERVE); 898 } 899 900 if (dataPtr->mode & TCL_READABLE) { 901 ExecuteCallback(dataPtr, NULL, A_CLEAR_READ, NULL, 0, TRANSMIT_DONT, 902 P_NO_PRESERVE); 903 ResultClear(&dataPtr->result); 904 dataPtr->readIsFlushed = 0; 905 } 906 907 /* 908 * If we have a wide seek capability, we should stick with that. 909 */ 910 911 if (parentWideSeekProc != NULL) { 912 return parentWideSeekProc(parentData, offset, mode, errorCodePtr); 913 } 914 915 /* 916 * We're transferring to narrow seeks at this point; this is a bit complex 917 * because we have to check whether the seek is possible first (i.e. 918 * whether we are losing information in truncating the bits of the 919 * offset). Luckily, there's a defined error for what happens when trying 920 * to go out of the representable range. 921 */ 922 923 if (offset<Tcl_LongAsWide(LONG_MIN) || offset>Tcl_LongAsWide(LONG_MAX)) { 924 *errorCodePtr = EOVERFLOW; 925 return Tcl_LongAsWide(-1); 926 } 927 928 return Tcl_LongAsWide(parentSeekProc(parentData, Tcl_WideAsLong(offset), 929 mode, errorCodePtr)); 930} 931 932/* 933 *---------------------------------------------------------------------- 934 * 935 * TransformSetOptionProc -- 936 * 937 * Called by generic layer to handle the reconfiguration of channel 938 * specific options. As this channel type does not have such, it simply 939 * passes all requests downstream. 940 * 941 * Side effects: 942 * As defined by the channel downstream. 943 * 944 * Result: 945 * A standard TCL error code. 946 * 947 *---------------------------------------------------------------------- 948 */ 949 950static int 951TransformSetOptionProc( 952 ClientData instanceData, 953 Tcl_Interp *interp, 954 const char *optionName, 955 const char *value) 956{ 957 TransformChannelData *dataPtr = instanceData; 958 Tcl_Channel downChan = Tcl_GetStackedChannel(dataPtr->self); 959 Tcl_DriverSetOptionProc *setOptionProc; 960 961 setOptionProc = Tcl_ChannelSetOptionProc(Tcl_GetChannelType(downChan)); 962 if (setOptionProc == NULL) { 963 return TCL_ERROR; 964 } 965 966 return setOptionProc(Tcl_GetChannelInstanceData(downChan), interp, 967 optionName, value); 968} 969 970/* 971 *---------------------------------------------------------------------- 972 * 973 * TransformGetOptionProc -- 974 * 975 * Called by generic layer to handle requests for the values of channel 976 * specific options. As this channel type does not have such, it simply 977 * passes all requests downstream. 978 * 979 * Side effects: 980 * As defined by the channel downstream. 981 * 982 * Result: 983 * A standard TCL error code. 984 * 985 *---------------------------------------------------------------------- 986 */ 987 988static int 989TransformGetOptionProc( 990 ClientData instanceData, 991 Tcl_Interp *interp, 992 const char *optionName, 993 Tcl_DString *dsPtr) 994{ 995 TransformChannelData *dataPtr = instanceData; 996 Tcl_Channel downChan = Tcl_GetStackedChannel(dataPtr->self); 997 Tcl_DriverGetOptionProc *getOptionProc; 998 999 getOptionProc = Tcl_ChannelGetOptionProc(Tcl_GetChannelType(downChan)); 1000 if (getOptionProc != NULL) { 1001 return getOptionProc(Tcl_GetChannelInstanceData(downChan), interp, 1002 optionName, dsPtr); 1003 } else if (optionName == NULL) { 1004 /* 1005 * Request is query for all options, this is ok. 1006 */ 1007 1008 return TCL_OK; 1009 } 1010 1011 /* 1012 * Request for a specific option has to fail, since we don't have any. 1013 */ 1014 1015 return TCL_ERROR; 1016} 1017 1018/* 1019 *---------------------------------------------------------------------- 1020 * 1021 * TransformWatchProc -- 1022 * 1023 * Initialize the notifier to watch for events from this channel. 1024 * 1025 * Side effects: 1026 * Sets up the notifier so that a future event on the channel will be 1027 * seen by Tcl. 1028 * 1029 * Result: 1030 * None. 1031 * 1032 *---------------------------------------------------------------------- 1033 */ 1034 1035 /* ARGSUSED */ 1036static void 1037TransformWatchProc( 1038 ClientData instanceData, /* Channel to watch. */ 1039 int mask) /* Events of interest. */ 1040{ 1041 TransformChannelData *dataPtr = instanceData; 1042 Tcl_Channel downChan; 1043 1044 /* 1045 * The caller expressed interest in events occuring for this channel. We 1046 * are forwarding the call to the underlying channel now. 1047 */ 1048 1049 dataPtr->watchMask = mask; 1050 1051 /* 1052 * No channel handlers any more. We will be notified automatically about 1053 * events on the channel below via a call to our 'TransformNotifyProc'. 1054 * But we have to pass the interest down now. We are allowed to add 1055 * additional 'interest' to the mask if we want to. But this 1056 * transformation has no such interest. It just passes the request down, 1057 * unchanged. 1058 */ 1059 1060 downChan = Tcl_GetStackedChannel(dataPtr->self); 1061 1062 Tcl_GetChannelType(downChan)->watchProc( 1063 Tcl_GetChannelInstanceData(downChan), mask); 1064 1065 /* 1066 * Management of the internal timer. 1067 */ 1068 1069 if ((dataPtr->timer != NULL) && 1070 (!(mask & TCL_READABLE) || ResultEmpty(&dataPtr->result))) { 1071 /* 1072 * A pending timer exists, but either is there no (more) interest in 1073 * the events it generates or nothing is available for reading, so 1074 * remove it. 1075 */ 1076 1077 Tcl_DeleteTimerHandler(dataPtr->timer); 1078 dataPtr->timer = NULL; 1079 } 1080 1081 if ((dataPtr->timer == NULL) && (mask & TCL_READABLE) 1082 && !ResultEmpty(&dataPtr->result)) { 1083 /* 1084 * There is no pending timer, but there is interest in readable events 1085 * and we actually have data waiting, so generate a timer to flush 1086 * that. 1087 */ 1088 1089 dataPtr->timer = Tcl_CreateTimerHandler(FLUSH_DELAY, 1090 TransformChannelHandlerTimer, dataPtr); 1091 } 1092} 1093 1094/* 1095 *---------------------------------------------------------------------- 1096 * 1097 * TransformGetFileHandleProc -- 1098 * 1099 * Called from Tcl_GetChannelHandle to retrieve OS specific file handle 1100 * from inside this channel. 1101 * 1102 * Side effects: 1103 * None. 1104 * 1105 * Result: 1106 * The appropriate Tcl_File or NULL if not present. 1107 * 1108 *---------------------------------------------------------------------- 1109 */ 1110 1111static int 1112TransformGetFileHandleProc( 1113 ClientData instanceData, /* Channel to query. */ 1114 int direction, /* Direction of interest. */ 1115 ClientData *handlePtr) /* Place to store the handle into. */ 1116{ 1117 TransformChannelData *dataPtr = instanceData; 1118 1119 /* 1120 * Return the handle belonging to parent channel. IOW, pass the request 1121 * down and the result up. 1122 */ 1123 1124 return Tcl_GetChannelHandle(Tcl_GetStackedChannel(dataPtr->self), 1125 direction, handlePtr); 1126} 1127 1128/* 1129 *---------------------------------------------------------------------- 1130 * 1131 * TransformNotifyProc -- 1132 * 1133 * Handler called by Tcl to inform us of activity on the underlying 1134 * channel. 1135 * 1136 * Side effects: 1137 * May process the incoming event by itself. 1138 * 1139 * Result: 1140 * None. 1141 * 1142 *---------------------------------------------------------------------- 1143 */ 1144 1145static int 1146TransformNotifyProc( 1147 ClientData clientData, /* The state of the notified 1148 * transformation. */ 1149 int mask) /* The mask of occuring events. */ 1150{ 1151 TransformChannelData *dataPtr = clientData; 1152 1153 /* 1154 * An event occured in the underlying channel. This transformation doesn't 1155 * process such events thus returns the incoming mask unchanged. 1156 */ 1157 1158 if (dataPtr->timer != NULL) { 1159 /* 1160 * Delete an existing timer. It was not fired, yet we are here, so the 1161 * channel below generated such an event and we don't have to. The 1162 * renewal of the interest after the execution of channel handlers 1163 * will eventually cause us to recreate the timer (in 1164 * TransformWatchProc). 1165 */ 1166 1167 Tcl_DeleteTimerHandler(dataPtr->timer); 1168 dataPtr->timer = NULL; 1169 } 1170 return mask; 1171} 1172 1173/* 1174 *---------------------------------------------------------------------- 1175 * 1176 * TransformChannelHandlerTimer -- 1177 * 1178 * Called by the notifier (-> timer) to flush out information waiting in 1179 * the input buffer. 1180 * 1181 * Side effects: 1182 * As of 'Tcl_NotifyChannel'. 1183 * 1184 * Result: 1185 * None. 1186 * 1187 *---------------------------------------------------------------------- 1188 */ 1189 1190static void 1191TransformChannelHandlerTimer( 1192 ClientData clientData) /* Transformation to query. */ 1193{ 1194 TransformChannelData *dataPtr = clientData; 1195 1196 dataPtr->timer = NULL; 1197 if (!(dataPtr->watchMask&TCL_READABLE) || ResultEmpty(&dataPtr->result)) { 1198 /* 1199 * The timer fired, but either is there no (more) interest in the 1200 * events it generates or nothing is available for reading, so ignore 1201 * it and don't recreate it. 1202 */ 1203 1204 return; 1205 } 1206 Tcl_NotifyChannel(dataPtr->self, TCL_READABLE); 1207} 1208 1209/* 1210 *---------------------------------------------------------------------- 1211 * 1212 * ResultClear -- 1213 * 1214 * Deallocates any memory allocated by 'ResultAdd'. 1215 * 1216 * Side effects: 1217 * See above. 1218 * 1219 * Result: 1220 * None. 1221 * 1222 *---------------------------------------------------------------------- 1223 */ 1224 1225static inline void 1226ResultClear( 1227 ResultBuffer *r) /* Reference to the buffer to clear out. */ 1228{ 1229 r->used = 0; 1230 1231 if (r->allocated) { 1232 ckfree((char *) r->buf); 1233 r->buf = NULL; 1234 r->allocated = 0; 1235 } 1236} 1237 1238/* 1239 *---------------------------------------------------------------------- 1240 * 1241 * ResultInit -- 1242 * 1243 * Initializes the specified buffer structure. The structure will contain 1244 * valid information for an emtpy buffer. 1245 * 1246 * Side effects: 1247 * See above. 1248 * 1249 * Result: 1250 * None. 1251 * 1252 *---------------------------------------------------------------------- 1253 */ 1254 1255static inline void 1256ResultInit( 1257 ResultBuffer *r) /* Reference to the structure to 1258 * initialize. */ 1259{ 1260 r->used = 0; 1261 r->allocated = 0; 1262 r->buf = NULL; 1263} 1264 1265/* 1266 *---------------------------------------------------------------------- 1267 * 1268 * ResultEmpty -- 1269 * 1270 * Returns whether the number of bytes stored in the buffer is zero. 1271 * 1272 * Side effects: 1273 * None. 1274 * 1275 * Result: 1276 * A boolean. 1277 * 1278 *---------------------------------------------------------------------- 1279 */ 1280 1281static inline int 1282ResultEmpty( 1283 ResultBuffer *r) /* The structure to query. */ 1284{ 1285 return r->used == 0; 1286} 1287 1288/* 1289 *---------------------------------------------------------------------- 1290 * 1291 * ResultCopy -- 1292 * 1293 * Copies the requested number of bytes from the buffer into the 1294 * specified array and removes them from the buffer afterward. Copies 1295 * less if there is not enough data in the buffer. 1296 * 1297 * Side effects: 1298 * See above. 1299 * 1300 * Result: 1301 * The number of actually copied bytes, possibly less than 'toRead'. 1302 * 1303 *---------------------------------------------------------------------- 1304 */ 1305 1306static inline int 1307ResultCopy( 1308 ResultBuffer *r, /* The buffer to read from. */ 1309 unsigned char *buf, /* The buffer to copy into. */ 1310 size_t toRead) /* Number of requested bytes. */ 1311{ 1312 if (r->used == 0) { 1313 /* 1314 * Nothing to copy in the case of an empty buffer. 1315 */ 1316 1317 return 0; 1318 } else if (r->used == toRead) { 1319 /* 1320 * We have just enough. Copy everything to the caller. 1321 */ 1322 1323 memcpy(buf, r->buf, toRead); 1324 r->used = 0; 1325 } else if (r->used > toRead) { 1326 /* 1327 * The internal buffer contains more than requested. Copy the 1328 * requested subset to the caller, and shift the remaining bytes down. 1329 */ 1330 1331 memcpy(buf, r->buf, toRead); 1332 memmove(r->buf, r->buf + toRead, r->used - toRead); 1333 r->used -= toRead; 1334 } else { 1335 /* 1336 * There is not enough in the buffer to satisfy the caller, so take 1337 * everything. 1338 */ 1339 1340 memcpy(buf, r->buf, r->used); 1341 toRead = r->used; 1342 r->used = 0; 1343 } 1344 return toRead; 1345} 1346 1347/* 1348 *---------------------------------------------------------------------- 1349 * 1350 * ResultAdd -- 1351 * 1352 * Adds the bytes in the specified array to the buffer, by appending it. 1353 * 1354 * Side effects: 1355 * See above. 1356 * 1357 * Result: 1358 * None. 1359 * 1360 *---------------------------------------------------------------------- 1361 */ 1362 1363static inline void 1364ResultAdd( 1365 ResultBuffer *r, /* The buffer to extend. */ 1366 unsigned char *buf, /* The buffer to read from. */ 1367 size_t toWrite) /* The number of bytes in 'buf'. */ 1368{ 1369 if (r->used + toWrite > r->allocated) { 1370 /* 1371 * Extension of the internal buffer is required. 1372 */ 1373 1374 if (r->allocated == 0) { 1375 r->allocated = toWrite + INCREMENT; 1376 r->buf = UCHARP(ckalloc(r->allocated)); 1377 } else { 1378 r->allocated += toWrite + INCREMENT; 1379 r->buf = UCHARP(ckrealloc((char *) r->buf, r->allocated)); 1380 } 1381 } 1382 1383 /* 1384 * Now we may copy the data. 1385 */ 1386 1387 memcpy(r->buf + r->used, buf, toWrite); 1388 r->used += toWrite; 1389} 1390 1391/* 1392 * Local Variables: 1393 * mode: c 1394 * c-basic-offset: 4 1395 * fill-column: 78 1396 * End: 1397 */ 1398