1/* 2 * tclWinChan.c 3 * 4 * Channel drivers for Windows channels based on files, command pipes and 5 * TCP sockets. 6 * 7 * Copyright (c) 1995-1997 Sun Microsystems, Inc. 8 * 9 * See the file "license.terms" for information on usage and redistribution of 10 * this file, and for a DISCLAIMER OF ALL WARRANTIES. 11 * 12 * RCS: @(#) $Id: tclWinChan.c,v 1.49.4.3 2010/09/08 15:42:13 dgp Exp $ 13 */ 14 15#include "tclWinInt.h" 16#include "tclIO.h" 17 18/* 19 * State flags used in the info structures below. 20 */ 21 22#define FILE_PENDING (1<<0) /* Message is pending in the queue. */ 23#define FILE_ASYNC (1<<1) /* Channel is non-blocking. */ 24#define FILE_APPEND (1<<2) /* File is in append mode. */ 25 26#define FILE_TYPE_SERIAL (FILE_TYPE_PIPE+1) 27#define FILE_TYPE_CONSOLE (FILE_TYPE_PIPE+2) 28 29/* 30 * The following structure contains per-instance data for a file based channel. 31 */ 32 33typedef struct FileInfo { 34 Tcl_Channel channel; /* Pointer to channel structure. */ 35 int validMask; /* OR'ed combination of TCL_READABLE, 36 * TCL_WRITABLE, or TCL_EXCEPTION: indicates 37 * which operations are valid on the file. */ 38 int watchMask; /* OR'ed combination of TCL_READABLE, 39 * TCL_WRITABLE, or TCL_EXCEPTION: indicates 40 * which events should be reported. */ 41 int flags; /* State flags, see above for a list. */ 42 HANDLE handle; /* Input/output file. */ 43 struct FileInfo *nextPtr; /* Pointer to next registered file. */ 44 int dirty; /* Boolean flag. Set if the OS may have data 45 * pending on the channel. */ 46} FileInfo; 47 48typedef struct ThreadSpecificData { 49 /* 50 * List of all file channels currently open. 51 */ 52 53 FileInfo *firstFilePtr; 54} ThreadSpecificData; 55 56static Tcl_ThreadDataKey dataKey; 57 58/* 59 * The following structure is what is added to the Tcl event queue when file 60 * events are generated. 61 */ 62 63typedef struct FileEvent { 64 Tcl_Event header; /* Information that is standard for all 65 * events. */ 66 FileInfo *infoPtr; /* Pointer to file info structure. Note that 67 * we still have to verify that the file 68 * exists before dereferencing this 69 * pointer. */ 70} FileEvent; 71 72/* 73 * Static routines for this file: 74 */ 75 76static int FileBlockProc(ClientData instanceData, int mode); 77static void FileChannelExitHandler(ClientData clientData); 78static void FileCheckProc(ClientData clientData, int flags); 79static int FileCloseProc(ClientData instanceData, 80 Tcl_Interp *interp); 81static int FileEventProc(Tcl_Event *evPtr, int flags); 82static int FileGetHandleProc(ClientData instanceData, 83 int direction, ClientData *handlePtr); 84static ThreadSpecificData *FileInit(void); 85static int FileInputProc(ClientData instanceData, char *buf, 86 int toRead, int *errorCode); 87static int FileOutputProc(ClientData instanceData, 88 CONST char *buf, int toWrite, int *errorCode); 89static int FileSeekProc(ClientData instanceData, long offset, 90 int mode, int *errorCode); 91static Tcl_WideInt FileWideSeekProc(ClientData instanceData, 92 Tcl_WideInt offset, int mode, int *errorCode); 93static void FileSetupProc(ClientData clientData, int flags); 94static void FileWatchProc(ClientData instanceData, int mask); 95static void FileThreadActionProc(ClientData instanceData, 96 int action); 97static int FileTruncateProc(ClientData instanceData, 98 Tcl_WideInt length); 99static DWORD FileGetType(HANDLE handle); 100 101/* 102 * This structure describes the channel type structure for file based IO. 103 */ 104 105static Tcl_ChannelType fileChannelType = { 106 "file", /* Type name. */ 107 TCL_CHANNEL_VERSION_5, /* v5 channel */ 108 FileCloseProc, /* Close proc. */ 109 FileInputProc, /* Input proc. */ 110 FileOutputProc, /* Output proc. */ 111 FileSeekProc, /* Seek proc. */ 112 NULL, /* Set option proc. */ 113 NULL, /* Get option proc. */ 114 FileWatchProc, /* Set up the notifier to watch the channel. */ 115 FileGetHandleProc, /* Get an OS handle from channel. */ 116 NULL, /* close2proc. */ 117 FileBlockProc, /* Set blocking or non-blocking mode.*/ 118 NULL, /* flush proc. */ 119 NULL, /* handler proc. */ 120 FileWideSeekProc, /* Wide seek proc. */ 121 FileThreadActionProc, /* Thread action proc. */ 122 FileTruncateProc, /* Truncate proc. */ 123}; 124 125#ifdef HAVE_NO_SEH 126/* 127 * Unlike Borland and Microsoft, we don't register exception handlers by 128 * pushing registration records onto the runtime stack. Instead, we register 129 * them by creating an EXCEPTION_REGISTRATION within the activation record. 130 */ 131 132typedef struct EXCEPTION_REGISTRATION { 133 struct EXCEPTION_REGISTRATION* link; 134 EXCEPTION_DISPOSITION (*handler)( 135 struct _EXCEPTION_RECORD*, void*, struct _CONTEXT*, void*); 136 void* ebp; 137 void* esp; 138 int status; 139} EXCEPTION_REGISTRATION; 140#endif 141 142/* 143 *---------------------------------------------------------------------- 144 * 145 * FileInit -- 146 * 147 * This function creates the window used to simulate file events. 148 * 149 * Results: 150 * None. 151 * 152 * Side effects: 153 * Creates a new window and creates an exit handler. 154 * 155 *---------------------------------------------------------------------- 156 */ 157 158static ThreadSpecificData * 159FileInit(void) 160{ 161 ThreadSpecificData *tsdPtr = 162 (ThreadSpecificData *)TclThreadDataKeyGet(&dataKey); 163 164 if (tsdPtr == NULL) { 165 tsdPtr = TCL_TSD_INIT(&dataKey); 166 tsdPtr->firstFilePtr = NULL; 167 Tcl_CreateEventSource(FileSetupProc, FileCheckProc, NULL); 168 Tcl_CreateThreadExitHandler(FileChannelExitHandler, NULL); 169 } 170 return tsdPtr; 171} 172 173/* 174 *---------------------------------------------------------------------- 175 * 176 * FileChannelExitHandler -- 177 * 178 * This function is called to cleanup the channel driver before Tcl is 179 * unloaded. 180 * 181 * Results: 182 * None. 183 * 184 * Side effects: 185 * Destroys the communication window. 186 * 187 *---------------------------------------------------------------------- 188 */ 189 190static void 191FileChannelExitHandler( 192 ClientData clientData) /* Old window proc */ 193{ 194 Tcl_DeleteEventSource(FileSetupProc, FileCheckProc, NULL); 195} 196 197/* 198 *---------------------------------------------------------------------- 199 * 200 * FileSetupProc -- 201 * 202 * This function is invoked before Tcl_DoOneEvent blocks waiting for an 203 * event. 204 * 205 * Results: 206 * None. 207 * 208 * Side effects: 209 * Adjusts the block time if needed. 210 * 211 *---------------------------------------------------------------------- 212 */ 213 214void 215FileSetupProc( 216 ClientData data, /* Not used. */ 217 int flags) /* Event flags as passed to Tcl_DoOneEvent. */ 218{ 219 FileInfo *infoPtr; 220 Tcl_Time blockTime = { 0, 0 }; 221 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); 222 223 if (!(flags & TCL_FILE_EVENTS)) { 224 return; 225 } 226 227 /* 228 * Check to see if there is a ready file. If so, poll. 229 */ 230 231 for (infoPtr = tsdPtr->firstFilePtr; infoPtr != NULL; 232 infoPtr = infoPtr->nextPtr) { 233 if (infoPtr->watchMask) { 234 Tcl_SetMaxBlockTime(&blockTime); 235 break; 236 } 237 } 238} 239 240/* 241 *---------------------------------------------------------------------- 242 * 243 * FileCheckProc -- 244 * 245 * This function is called by Tcl_DoOneEvent to check the file event 246 * source for events. 247 * 248 * Results: 249 * None. 250 * 251 * Side effects: 252 * May queue an event. 253 * 254 *---------------------------------------------------------------------- 255 */ 256 257static void 258FileCheckProc( 259 ClientData data, /* Not used. */ 260 int flags) /* Event flags as passed to Tcl_DoOneEvent. */ 261{ 262 FileEvent *evPtr; 263 FileInfo *infoPtr; 264 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); 265 266 if (!(flags & TCL_FILE_EVENTS)) { 267 return; 268 } 269 270 /* 271 * Queue events for any ready files that don't already have events queued 272 * (caused by persistent states that won't generate WinSock events). 273 */ 274 275 for (infoPtr = tsdPtr->firstFilePtr; infoPtr != NULL; 276 infoPtr = infoPtr->nextPtr) { 277 if (infoPtr->watchMask && !(infoPtr->flags & FILE_PENDING)) { 278 infoPtr->flags |= FILE_PENDING; 279 evPtr = (FileEvent *) ckalloc(sizeof(FileEvent)); 280 evPtr->header.proc = FileEventProc; 281 evPtr->infoPtr = infoPtr; 282 Tcl_QueueEvent((Tcl_Event *) evPtr, TCL_QUEUE_TAIL); 283 } 284 } 285} 286 287/* 288 *---------------------------------------------------------------------- 289 * 290 * FileEventProc -- 291 * 292 * This function is invoked by Tcl_ServiceEvent when a file event reaches 293 * the front of the event queue. This function invokes Tcl_NotifyChannel 294 * on the file. 295 * 296 * Results: 297 * Returns 1 if the event was handled, meaning it should be removed from 298 * the queue. Returns 0 if the event was not handled, meaning it should 299 * stay on the queue. The only time the event isn't handled is if the 300 * TCL_FILE_EVENTS flag bit isn't set. 301 * 302 * Side effects: 303 * Whatever the notifier callback does. 304 * 305 *---------------------------------------------------------------------- 306 */ 307 308static int 309FileEventProc( 310 Tcl_Event *evPtr, /* Event to service. */ 311 int flags) /* Flags that indicate what events to handle, 312 * such as TCL_FILE_EVENTS. */ 313{ 314 FileEvent *fileEvPtr = (FileEvent *)evPtr; 315 FileInfo *infoPtr; 316 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); 317 318 if (!(flags & TCL_FILE_EVENTS)) { 319 return 0; 320 } 321 322 /* 323 * Search through the list of watched files for the one whose handle 324 * matches the event. We do this rather than simply dereferencing the 325 * handle in the event so that files can be deleted while the event is in 326 * the queue. 327 */ 328 329 for (infoPtr = tsdPtr->firstFilePtr; infoPtr != NULL; 330 infoPtr = infoPtr->nextPtr) { 331 if (fileEvPtr->infoPtr == infoPtr) { 332 infoPtr->flags &= ~(FILE_PENDING); 333 Tcl_NotifyChannel(infoPtr->channel, infoPtr->watchMask); 334 break; 335 } 336 } 337 return 1; 338} 339 340/* 341 *---------------------------------------------------------------------- 342 * 343 * FileBlockProc -- 344 * 345 * Set blocking or non-blocking mode on channel. 346 * 347 * Results: 348 * 0 if successful, errno when failed. 349 * 350 * Side effects: 351 * Sets the device into blocking or non-blocking mode. 352 * 353 *---------------------------------------------------------------------- 354 */ 355 356static int 357FileBlockProc( 358 ClientData instanceData, /* Instance data for channel. */ 359 int mode) /* TCL_MODE_BLOCKING or 360 * TCL_MODE_NONBLOCKING. */ 361{ 362 FileInfo *infoPtr = (FileInfo *) instanceData; 363 364 /* 365 * Files on Windows can not be switched between blocking and nonblocking, 366 * hence we have to emulate the behavior. This is done in the input 367 * function by checking against a bit in the state. We set or unset the 368 * bit here to cause the input function to emulate the correct behavior. 369 */ 370 371 if (mode == TCL_MODE_NONBLOCKING) { 372 infoPtr->flags |= FILE_ASYNC; 373 } else { 374 infoPtr->flags &= ~(FILE_ASYNC); 375 } 376 return 0; 377} 378 379/* 380 *---------------------------------------------------------------------- 381 * 382 * FileCloseProc -- 383 * 384 * Closes the IO channel. 385 * 386 * Results: 387 * 0 if successful, the value of errno if failed. 388 * 389 * Side effects: 390 * Closes the physical channel 391 * 392 *---------------------------------------------------------------------- 393 */ 394 395static int 396FileCloseProc( 397 ClientData instanceData, /* Pointer to FileInfo structure. */ 398 Tcl_Interp *interp) /* Not used. */ 399{ 400 FileInfo *fileInfoPtr = (FileInfo *) instanceData; 401 FileInfo *infoPtr; 402 ThreadSpecificData *tsdPtr; 403 int errorCode = 0; 404 405 /* 406 * Remove the file from the watch list. 407 */ 408 409 FileWatchProc(instanceData, 0); 410 411 /* 412 * Don't close the Win32 handle if the handle is a standard channel during 413 * the thread exit process. Otherwise, one thread may kill the stdio of 414 * another. 415 */ 416 417 if (!TclInThreadExit() 418 || ((GetStdHandle(STD_INPUT_HANDLE) != fileInfoPtr->handle) 419 && (GetStdHandle(STD_OUTPUT_HANDLE) != fileInfoPtr->handle) 420 && (GetStdHandle(STD_ERROR_HANDLE) != fileInfoPtr->handle))) { 421 if (CloseHandle(fileInfoPtr->handle) == FALSE) { 422 TclWinConvertError(GetLastError()); 423 errorCode = errno; 424 } 425 } 426 427 /* 428 * See if this FileInfo* is still on the thread local list. 429 */ 430 431 tsdPtr = TCL_TSD_INIT(&dataKey); 432 for (infoPtr = tsdPtr->firstFilePtr; infoPtr != NULL; 433 infoPtr = infoPtr->nextPtr) { 434 if (infoPtr == fileInfoPtr) { 435 /* 436 * This channel exists on the thread local list. It should have 437 * been removed by an earlier Threadaction call, but do that now 438 * since just deallocating fileInfoPtr would leave an deallocated 439 * pointer on the thread local list. 440 */ 441 442 FileThreadActionProc(fileInfoPtr,TCL_CHANNEL_THREAD_REMOVE); 443 break; 444 } 445 } 446 ckfree((char *)fileInfoPtr); 447 return errorCode; 448} 449 450/* 451 *---------------------------------------------------------------------- 452 * 453 * FileSeekProc -- 454 * 455 * Seeks on a file-based channel. Returns the new position. 456 * 457 * Results: 458 * -1 if failed, the new position if successful. If failed, it also sets 459 * *errorCodePtr to the error code. 460 * 461 * Side effects: 462 * Moves the location at which the channel will be accessed in future 463 * operations. 464 * 465 *---------------------------------------------------------------------- 466 */ 467 468static int 469FileSeekProc( 470 ClientData instanceData, /* File state. */ 471 long offset, /* Offset to seek to. */ 472 int mode, /* Relative to where should we seek? */ 473 int *errorCodePtr) /* To store error code. */ 474{ 475 FileInfo *infoPtr = (FileInfo *) instanceData; 476 LONG newPos, newPosHigh, oldPos, oldPosHigh; 477 DWORD moveMethod; 478 479 *errorCodePtr = 0; 480 if (mode == SEEK_SET) { 481 moveMethod = FILE_BEGIN; 482 } else if (mode == SEEK_CUR) { 483 moveMethod = FILE_CURRENT; 484 } else { 485 moveMethod = FILE_END; 486 } 487 488 /* 489 * Save our current place in case we need to roll-back the seek. 490 */ 491 492 oldPosHigh = 0; 493 oldPos = SetFilePointer(infoPtr->handle, 0, &oldPosHigh, FILE_CURRENT); 494 if (oldPos == (LONG)INVALID_SET_FILE_POINTER) { 495 DWORD winError = GetLastError(); 496 497 if (winError != NO_ERROR) { 498 TclWinConvertError(winError); 499 *errorCodePtr = errno; 500 return -1; 501 } 502 } 503 504 newPosHigh = (offset < 0 ? -1 : 0); 505 newPos = SetFilePointer(infoPtr->handle, offset, &newPosHigh, moveMethod); 506 if (newPos == (LONG)INVALID_SET_FILE_POINTER) { 507 DWORD winError = GetLastError(); 508 509 if (winError != NO_ERROR) { 510 TclWinConvertError(winError); 511 *errorCodePtr = errno; 512 return -1; 513 } 514 } 515 516 /* 517 * Check for expressability in our return type, and roll-back otherwise. 518 */ 519 520 if (newPosHigh != 0) { 521 *errorCodePtr = EOVERFLOW; 522 SetFilePointer(infoPtr->handle, oldPos, &oldPosHigh, FILE_BEGIN); 523 return -1; 524 } 525 return (int) newPos; 526} 527 528/* 529 *---------------------------------------------------------------------- 530 * 531 * FileWideSeekProc -- 532 * 533 * Seeks on a file-based channel. Returns the new position. 534 * 535 * Results: 536 * -1 if failed, the new position if successful. If failed, it also sets 537 * *errorCodePtr to the error code. 538 * 539 * Side effects: 540 * Moves the location at which the channel will be accessed in future 541 * operations. 542 * 543 *---------------------------------------------------------------------- 544 */ 545 546static Tcl_WideInt 547FileWideSeekProc( 548 ClientData instanceData, /* File state. */ 549 Tcl_WideInt offset, /* Offset to seek to. */ 550 int mode, /* Relative to where should we seek? */ 551 int *errorCodePtr) /* To store error code. */ 552{ 553 FileInfo *infoPtr = (FileInfo *) instanceData; 554 DWORD moveMethod; 555 LONG newPos, newPosHigh; 556 557 *errorCodePtr = 0; 558 if (mode == SEEK_SET) { 559 moveMethod = FILE_BEGIN; 560 } else if (mode == SEEK_CUR) { 561 moveMethod = FILE_CURRENT; 562 } else { 563 moveMethod = FILE_END; 564 } 565 566 newPosHigh = Tcl_WideAsLong(offset >> 32); 567 newPos = SetFilePointer(infoPtr->handle, Tcl_WideAsLong(offset), 568 &newPosHigh, moveMethod); 569 if (newPos == (LONG)INVALID_SET_FILE_POINTER) { 570 DWORD winError = GetLastError(); 571 572 if (winError != NO_ERROR) { 573 TclWinConvertError(winError); 574 *errorCodePtr = errno; 575 return -1; 576 } 577 } 578 return (((Tcl_WideInt)((unsigned)newPos)) | (Tcl_LongAsWide(newPosHigh) << 32)); 579} 580 581/* 582 *---------------------------------------------------------------------- 583 * 584 * FileTruncateProc -- 585 * 586 * Truncates a file-based channel. Returns the error code. 587 * 588 * Results: 589 * 0 if successful, POSIX-y error code if it failed. 590 * 591 * Side effects: 592 * Truncates the file, may move file pointers too. 593 * 594 *---------------------------------------------------------------------- 595 */ 596 597static int 598FileTruncateProc( 599 ClientData instanceData, /* File state. */ 600 Tcl_WideInt length) /* Length to truncate at. */ 601{ 602 FileInfo *infoPtr = (FileInfo *) instanceData; 603 LONG newPos, newPosHigh, oldPos, oldPosHigh; 604 605 /* 606 * Save where we were... 607 */ 608 609 oldPosHigh = 0; 610 oldPos = SetFilePointer(infoPtr->handle, 0, &oldPosHigh, FILE_CURRENT); 611 if (oldPos == (LONG)INVALID_SET_FILE_POINTER) { 612 DWORD winError = GetLastError(); 613 if (winError != NO_ERROR) { 614 TclWinConvertError(winError); 615 return errno; 616 } 617 } 618 619 /* 620 * Move to where we want to truncate 621 */ 622 623 newPosHigh = Tcl_WideAsLong(length >> 32); 624 newPos = SetFilePointer(infoPtr->handle, Tcl_WideAsLong(length), 625 &newPosHigh, FILE_BEGIN); 626 if (newPos == (LONG)INVALID_SET_FILE_POINTER) { 627 DWORD winError = GetLastError(); 628 if (winError != NO_ERROR) { 629 TclWinConvertError(winError); 630 return errno; 631 } 632 } 633 634 /* 635 * Perform the truncation (unlike POSIX ftruncate(), we needed to move to 636 * the location to truncate at first). 637 */ 638 639 if (!SetEndOfFile(infoPtr->handle)) { 640 TclWinConvertError(GetLastError()); 641 return errno; 642 } 643 644 /* 645 * Move back. If this last step fails, we don't care; it's just a "best 646 * effort" attempt to restore our file pointer to where it was. 647 */ 648 649 SetFilePointer(infoPtr->handle, oldPos, &oldPosHigh, FILE_BEGIN); 650 return 0; 651} 652 653/* 654 *---------------------------------------------------------------------- 655 * 656 * FileInputProc -- 657 * 658 * Reads input from the IO channel into the buffer given. Returns count 659 * of how many bytes were actually read, and an error indication. 660 * 661 * Results: 662 * A count of how many bytes were read is returned and an error 663 * indication is returned in an output argument. 664 * 665 * Side effects: 666 * Reads input from the actual channel. 667 * 668 *---------------------------------------------------------------------- 669 */ 670 671static int 672FileInputProc( 673 ClientData instanceData, /* File state. */ 674 char *buf, /* Where to store data read. */ 675 int bufSize, /* Num bytes available in buffer. */ 676 int *errorCode) /* Where to store error code. */ 677{ 678 FileInfo *infoPtr; 679 DWORD bytesRead; 680 681 *errorCode = 0; 682 infoPtr = (FileInfo *) instanceData; 683 684 /* 685 * Note that we will block on reads from a console buffer until a full 686 * line has been entered. The only way I know of to get around this is to 687 * write a console driver. We should probably do this at some point, but 688 * for now, we just block. The same problem exists for files being read 689 * over the network. 690 */ 691 692 if (ReadFile(infoPtr->handle, (LPVOID) buf, (DWORD) bufSize, &bytesRead, 693 (LPOVERLAPPED) NULL) != FALSE) { 694 return bytesRead; 695 } 696 697 TclWinConvertError(GetLastError()); 698 *errorCode = errno; 699 if (errno == EPIPE) { 700 return 0; 701 } 702 return -1; 703} 704 705/* 706 *---------------------------------------------------------------------- 707 * 708 * FileOutputProc -- 709 * 710 * Writes the given output on the IO channel. Returns count of how many 711 * characters were actually written, and an error indication. 712 * 713 * Results: 714 * A count of how many characters were written is returned and an error 715 * indication is returned in an output argument. 716 * 717 * Side effects: 718 * Writes output on the actual channel. 719 * 720 *---------------------------------------------------------------------- 721 */ 722 723static int 724FileOutputProc( 725 ClientData instanceData, /* File state. */ 726 CONST char *buf, /* The data buffer. */ 727 int toWrite, /* How many bytes to write? */ 728 int *errorCode) /* Where to store error code. */ 729{ 730 FileInfo *infoPtr = (FileInfo *) instanceData; 731 DWORD bytesWritten; 732 733 *errorCode = 0; 734 735 /* 736 * If we are writing to a file that was opened with O_APPEND, we need to 737 * seek to the end of the file before writing the current buffer. 738 */ 739 740 if (infoPtr->flags & FILE_APPEND) { 741 SetFilePointer(infoPtr->handle, 0, NULL, FILE_END); 742 } 743 744 if (WriteFile(infoPtr->handle, (LPVOID) buf, (DWORD) toWrite, 745 &bytesWritten, (LPOVERLAPPED) NULL) == FALSE) { 746 TclWinConvertError(GetLastError()); 747 *errorCode = errno; 748 return -1; 749 } 750 infoPtr->dirty = 1; 751 return bytesWritten; 752} 753 754/* 755 *---------------------------------------------------------------------- 756 * 757 * FileWatchProc -- 758 * 759 * Called by the notifier to set up to watch for events on this channel. 760 * 761 * Results: 762 * None. 763 * 764 * Side effects: 765 * None. 766 * 767 *---------------------------------------------------------------------- 768 */ 769 770static void 771FileWatchProc( 772 ClientData instanceData, /* File state. */ 773 int mask) /* What events to watch for; OR-ed combination 774 * of TCL_READABLE, TCL_WRITABLE and 775 * TCL_EXCEPTION. */ 776{ 777 FileInfo *infoPtr = (FileInfo *) instanceData; 778 Tcl_Time blockTime = { 0, 0 }; 779 780 /* 781 * Since the file is always ready for events, we set the block time to 782 * zero so we will poll. 783 */ 784 785 infoPtr->watchMask = mask & infoPtr->validMask; 786 if (infoPtr->watchMask) { 787 Tcl_SetMaxBlockTime(&blockTime); 788 } 789} 790 791/* 792 *---------------------------------------------------------------------- 793 * 794 * FileGetHandleProc -- 795 * 796 * Called from Tcl_GetChannelHandle to retrieve OS handles from a file 797 * based channel. 798 * 799 * Results: 800 * Returns TCL_OK with the fd in handlePtr, or TCL_ERROR if there is no 801 * handle for the specified direction. 802 * 803 * Side effects: 804 * None. 805 * 806 *---------------------------------------------------------------------- 807 */ 808 809static int 810FileGetHandleProc( 811 ClientData instanceData, /* The file state. */ 812 int direction, /* TCL_READABLE or TCL_WRITABLE */ 813 ClientData *handlePtr) /* Where to store the handle. */ 814{ 815 FileInfo *infoPtr = (FileInfo *) instanceData; 816 817 if (direction & infoPtr->validMask) { 818 *handlePtr = (ClientData) infoPtr->handle; 819 return TCL_OK; 820 } else { 821 return TCL_ERROR; 822 } 823} 824 825/* 826 *---------------------------------------------------------------------- 827 * 828 * TclpOpenFileChannel -- 829 * 830 * Open an File based channel on Unix systems. 831 * 832 * Results: 833 * The new channel or NULL. If NULL, the output argument errorCodePtr is 834 * set to a POSIX error. 835 * 836 * Side effects: 837 * May open the channel and may cause creation of a file on the file 838 * system. 839 * 840 *---------------------------------------------------------------------- 841 */ 842 843Tcl_Channel 844TclpOpenFileChannel( 845 Tcl_Interp *interp, /* Interpreter for error reporting; can be 846 * NULL. */ 847 Tcl_Obj *pathPtr, /* Name of file to open. */ 848 int mode, /* POSIX mode. */ 849 int permissions) /* If the open involves creating a file, with 850 * what modes to create it? */ 851{ 852 Tcl_Channel channel = 0; 853 int channelPermissions = 0; 854 DWORD accessMode = 0, createMode, shareMode, flags; 855 CONST TCHAR *nativeName; 856 HANDLE handle; 857 char channelName[16 + TCL_INTEGER_SPACE]; 858 TclFile readFile = NULL, writeFile = NULL; 859 860 nativeName = (TCHAR*) Tcl_FSGetNativePath(pathPtr); 861 if (nativeName == NULL) { 862 return NULL; 863 } 864 865 switch (mode & (O_RDONLY | O_WRONLY | O_RDWR)) { 866 case O_RDONLY: 867 accessMode = GENERIC_READ; 868 channelPermissions = TCL_READABLE; 869 break; 870 case O_WRONLY: 871 accessMode = GENERIC_WRITE; 872 channelPermissions = TCL_WRITABLE; 873 break; 874 case O_RDWR: 875 accessMode = (GENERIC_READ | GENERIC_WRITE); 876 channelPermissions = (TCL_READABLE | TCL_WRITABLE); 877 break; 878 default: 879 Tcl_Panic("TclpOpenFileChannel: invalid mode value"); 880 break; 881 } 882 883 /* 884 * Map the creation flags to the NT create mode. 885 */ 886 887 switch (mode & (O_CREAT | O_EXCL | O_TRUNC)) { 888 case (O_CREAT | O_EXCL): 889 case (O_CREAT | O_EXCL | O_TRUNC): 890 createMode = CREATE_NEW; 891 break; 892 case (O_CREAT | O_TRUNC): 893 createMode = CREATE_ALWAYS; 894 break; 895 case O_CREAT: 896 createMode = OPEN_ALWAYS; 897 break; 898 case O_TRUNC: 899 case (O_TRUNC | O_EXCL): 900 createMode = TRUNCATE_EXISTING; 901 break; 902 default: 903 createMode = OPEN_EXISTING; 904 break; 905 } 906 907 /* 908 * If the file is being created, get the file attributes from the 909 * permissions argument, else use the existing file attributes. 910 */ 911 912 if (mode & O_CREAT) { 913 if (permissions & S_IWRITE) { 914 flags = FILE_ATTRIBUTE_NORMAL; 915 } else { 916 flags = FILE_ATTRIBUTE_READONLY; 917 } 918 } else { 919 flags = (*tclWinProcs->getFileAttributesProc)(nativeName); 920 if (flags == 0xFFFFFFFF) { 921 flags = 0; 922 } 923 } 924 925 /* 926 * Set up the file sharing mode. We want to allow simultaneous access. 927 */ 928 929 shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE; 930 931 /* 932 * Now we get to create the file. 933 */ 934 935 handle = (*tclWinProcs->createFileProc)(nativeName, accessMode, 936 shareMode, NULL, createMode, flags, (HANDLE) NULL); 937 938 if (handle == INVALID_HANDLE_VALUE) { 939 DWORD err = GetLastError(); 940 941 if ((err & 0xffffL) == ERROR_OPEN_FAILED) { 942 err = (mode & O_CREAT) ? ERROR_FILE_EXISTS : ERROR_FILE_NOT_FOUND; 943 } 944 TclWinConvertError(err); 945 if (interp != (Tcl_Interp *) NULL) { 946 Tcl_AppendResult(interp, "couldn't open \"", TclGetString(pathPtr), 947 "\": ", Tcl_PosixError(interp), NULL); 948 } 949 return NULL; 950 } 951 952 channel = NULL; 953 954 switch (FileGetType(handle)) { 955 case FILE_TYPE_SERIAL: 956 /* 957 * Reopen channel for OVERLAPPED operation. Normally this shouldn't 958 * fail, because the channel exists. 959 */ 960 961 handle = TclWinSerialReopen(handle, nativeName, accessMode); 962 if (handle == INVALID_HANDLE_VALUE) { 963 TclWinConvertError(GetLastError()); 964 if (interp != (Tcl_Interp *) NULL) { 965 Tcl_AppendResult(interp, "couldn't reopen serial \"", 966 TclGetString(pathPtr), "\": ", 967 Tcl_PosixError(interp), NULL); 968 } 969 return NULL; 970 } 971 channel = TclWinOpenSerialChannel(handle, channelName, 972 channelPermissions); 973 break; 974 case FILE_TYPE_CONSOLE: 975 channel = TclWinOpenConsoleChannel(handle, channelName, 976 channelPermissions); 977 break; 978 case FILE_TYPE_PIPE: 979 if (channelPermissions & TCL_READABLE) { 980 readFile = TclWinMakeFile(handle); 981 } 982 if (channelPermissions & TCL_WRITABLE) { 983 writeFile = TclWinMakeFile(handle); 984 } 985 channel = TclpCreateCommandChannel(readFile, writeFile, NULL, 0, NULL); 986 break; 987 case FILE_TYPE_CHAR: 988 case FILE_TYPE_DISK: 989 case FILE_TYPE_UNKNOWN: 990 channel = TclWinOpenFileChannel(handle, channelName, 991 channelPermissions, (mode & O_APPEND) ? FILE_APPEND : 0); 992 break; 993 994 default: 995 /* 996 * The handle is of an unknown type, probably /dev/nul equivalent or 997 * possibly a closed handle. 998 */ 999 1000 channel = NULL; 1001 Tcl_AppendResult(interp, "couldn't open \"", TclGetString(pathPtr), 1002 "\": bad file type", NULL); 1003 break; 1004 } 1005 1006 return channel; 1007} 1008 1009/* 1010 *---------------------------------------------------------------------- 1011 * 1012 * Tcl_MakeFileChannel -- 1013 * 1014 * Creates a Tcl_Channel from an existing platform specific file handle. 1015 * 1016 * Results: 1017 * The Tcl_Channel created around the preexisting file. 1018 * 1019 * Side effects: 1020 * None. 1021 * 1022 *---------------------------------------------------------------------- 1023 */ 1024 1025Tcl_Channel 1026Tcl_MakeFileChannel( 1027 ClientData rawHandle, /* OS level handle */ 1028 int mode) /* ORed combination of TCL_READABLE and 1029 * TCL_WRITABLE to indicate file mode. */ 1030{ 1031#ifdef HAVE_NO_SEH 1032 EXCEPTION_REGISTRATION registration; 1033#endif 1034 char channelName[16 + TCL_INTEGER_SPACE]; 1035 Tcl_Channel channel = NULL; 1036 HANDLE handle = (HANDLE) rawHandle; 1037 HANDLE dupedHandle; 1038 TclFile readFile = NULL, writeFile = NULL; 1039 BOOL result; 1040 1041 if (mode == 0) { 1042 return NULL; 1043 } 1044 1045 switch (FileGetType(handle)) { 1046 case FILE_TYPE_SERIAL: 1047 channel = TclWinOpenSerialChannel(handle, channelName, mode); 1048 break; 1049 case FILE_TYPE_CONSOLE: 1050 channel = TclWinOpenConsoleChannel(handle, channelName, mode); 1051 break; 1052 case FILE_TYPE_PIPE: 1053 if (mode & TCL_READABLE) { 1054 readFile = TclWinMakeFile(handle); 1055 } 1056 if (mode & TCL_WRITABLE) { 1057 writeFile = TclWinMakeFile(handle); 1058 } 1059 channel = TclpCreateCommandChannel(readFile, writeFile, NULL, 0, NULL); 1060 break; 1061 1062 case FILE_TYPE_DISK: 1063 case FILE_TYPE_CHAR: 1064 channel = TclWinOpenFileChannel(handle, channelName, mode, 0); 1065 break; 1066 1067 case FILE_TYPE_UNKNOWN: 1068 default: 1069 /* 1070 * The handle is of an unknown type. Test the validity of this OS 1071 * handle by duplicating it, then closing the dupe. The Win32 API 1072 * doesn't provide an IsValidHandle() function, so we have to emulate 1073 * it here. This test will not work on a console handle reliably, 1074 * which is why we can't test every handle that comes into this 1075 * function in this way. 1076 */ 1077 1078 result = DuplicateHandle(GetCurrentProcess(), handle, 1079 GetCurrentProcess(), &dupedHandle, 0, FALSE, 1080 DUPLICATE_SAME_ACCESS); 1081 1082 if (result == 0) { 1083 /* 1084 * Unable to make a duplicate. It's definately invalid at this 1085 * point. 1086 */ 1087 1088 return NULL; 1089 } 1090 1091 /* 1092 * Use structured exception handling (Win32 SEH) to protect the close 1093 * of this duped handle which might throw EXCEPTION_INVALID_HANDLE. 1094 */ 1095 1096 result = 0; 1097#if defined(HAVE_NO_SEH) && !defined(_WIN64) 1098 /* 1099 * Don't have SEH available, do things the hard way. Note that this 1100 * needs to be one block of asm, to avoid stack imbalance; also, it is 1101 * illegal for one asm block to contain a jump to another. 1102 */ 1103 1104 __asm__ __volatile__ ( 1105 1106 /* 1107 * Pick up parameters before messing with the stack 1108 */ 1109 1110 "movl %[dupedHandle], %%ebx" "\n\t" 1111 1112 /* 1113 * Construct an EXCEPTION_REGISTRATION to protect the call to 1114 * CloseHandle. 1115 */ 1116 1117 "leal %[registration], %%edx" "\n\t" 1118 "movl %%fs:0, %%eax" "\n\t" 1119 "movl %%eax, 0x0(%%edx)" "\n\t" /* link */ 1120 "leal 1f, %%eax" "\n\t" 1121 "movl %%eax, 0x4(%%edx)" "\n\t" /* handler */ 1122 "movl %%ebp, 0x8(%%edx)" "\n\t" /* ebp */ 1123 "movl %%esp, 0xc(%%edx)" "\n\t" /* esp */ 1124 "movl $0, 0x10(%%edx)" "\n\t" /* status */ 1125 1126 /* 1127 * Link the EXCEPTION_REGISTRATION on the chain. 1128 */ 1129 1130 "movl %%edx, %%fs:0" "\n\t" 1131 1132 /* 1133 * Call CloseHandle(dupedHandle). 1134 */ 1135 1136 "pushl %%ebx" "\n\t" 1137 "call _CloseHandle@4" "\n\t" 1138 1139 /* 1140 * Come here on normal exit. Recover the EXCEPTION_REGISTRATION 1141 * and put a TRUE status return into it. 1142 */ 1143 1144 "movl %%fs:0, %%edx" "\n\t" 1145 "movl $1, %%eax" "\n\t" 1146 "movl %%eax, 0x10(%%edx)" "\n\t" 1147 "jmp 2f" "\n" 1148 1149 /* 1150 * Come here on an exception. Recover the EXCEPTION_REGISTRATION 1151 */ 1152 1153 "1:" "\t" 1154 "movl %%fs:0, %%edx" "\n\t" 1155 "movl 0x8(%%edx), %%edx" "\n\t" 1156 1157 /* 1158 * Come here however we exited. Restore context from the 1159 * EXCEPTION_REGISTRATION in case the stack is unbalanced. 1160 */ 1161 1162 "2:" "\t" 1163 "movl 0xc(%%edx), %%esp" "\n\t" 1164 "movl 0x8(%%edx), %%ebp" "\n\t" 1165 "movl 0x0(%%edx), %%eax" "\n\t" 1166 "movl %%eax, %%fs:0" "\n\t" 1167 1168 : 1169 /* No outputs */ 1170 : 1171 [registration] "m" (registration), 1172 [dupedHandle] "m" (dupedHandle) 1173 : 1174 "%eax", "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory" 1175 ); 1176 result = registration.status; 1177#else 1178#ifndef HAVE_NO_SEH 1179 __try { 1180#endif 1181 CloseHandle(dupedHandle); 1182 result = 1; 1183#ifndef HAVE_NO_SEH 1184 } __except (EXCEPTION_EXECUTE_HANDLER) {} 1185#endif 1186#endif 1187 if (result == FALSE) { 1188 return NULL; 1189 } 1190 1191 /* 1192 * Fall through, the handle is valid. 1193 * 1194 * Create the undefined channel, anyways, because we know the handle 1195 * is valid to something. 1196 */ 1197 1198 channel = TclWinOpenFileChannel(handle, channelName, mode, 0); 1199 } 1200 1201 return channel; 1202} 1203 1204/* 1205 *---------------------------------------------------------------------- 1206 * 1207 * TclpGetDefaultStdChannel -- 1208 * 1209 * Constructs a channel for the specified standard OS handle. 1210 * 1211 * Results: 1212 * Returns the specified default standard channel, or NULL. 1213 * 1214 * Side effects: 1215 * May cause the creation of a standard channel and the underlying file. 1216 * 1217 *---------------------------------------------------------------------- 1218 */ 1219 1220Tcl_Channel 1221TclpGetDefaultStdChannel( 1222 int type) /* One of TCL_STDIN, TCL_STDOUT, or 1223 * TCL_STDERR. */ 1224{ 1225 Tcl_Channel channel; 1226 HANDLE handle; 1227 int mode = -1; 1228 char *bufMode = NULL; 1229 DWORD handleId = (DWORD)INVALID_HANDLE_VALUE; 1230 /* Standard handle to retrieve. */ 1231 1232 switch (type) { 1233 case TCL_STDIN: 1234 handleId = STD_INPUT_HANDLE; 1235 mode = TCL_READABLE; 1236 bufMode = "line"; 1237 break; 1238 case TCL_STDOUT: 1239 handleId = STD_OUTPUT_HANDLE; 1240 mode = TCL_WRITABLE; 1241 bufMode = "line"; 1242 break; 1243 case TCL_STDERR: 1244 handleId = STD_ERROR_HANDLE; 1245 mode = TCL_WRITABLE; 1246 bufMode = "none"; 1247 break; 1248 default: 1249 Tcl_Panic("TclGetDefaultStdChannel: Unexpected channel type"); 1250 break; 1251 } 1252 1253 handle = GetStdHandle(handleId); 1254 1255 /* 1256 * Note that we need to check for 0 because Windows may return 0 if this 1257 * is not a console mode application, even though this is not a valid 1258 * handle. 1259 */ 1260 1261 if ((handle == INVALID_HANDLE_VALUE) || (handle == 0)) { 1262 return (Tcl_Channel) NULL; 1263 } 1264 1265 channel = Tcl_MakeFileChannel(handle, mode); 1266 1267 if (channel == NULL) { 1268 return (Tcl_Channel) NULL; 1269 } 1270 1271 /* 1272 * Set up the normal channel options for stdio handles. 1273 */ 1274 1275 if (Tcl_SetChannelOption(NULL,channel,"-translation","auto")!=TCL_OK || 1276 Tcl_SetChannelOption(NULL,channel,"-eofchar","\032 {}")!=TCL_OK || 1277 Tcl_SetChannelOption(NULL,channel,"-buffering",bufMode)!=TCL_OK) { 1278 Tcl_Close(NULL, channel); 1279 return (Tcl_Channel) NULL; 1280 } 1281 return channel; 1282} 1283 1284/* 1285 *---------------------------------------------------------------------- 1286 * 1287 * TclWinOpenFileChannel -- 1288 * 1289 * Constructs a File channel for the specified standard OS handle. This 1290 * is a helper function to break up the construction of channels into 1291 * File, Console, or Serial. 1292 * 1293 * Results: 1294 * Returns the new channel, or NULL. 1295 * 1296 * Side effects: 1297 * May open the channel and may cause creation of a file on the file 1298 * system. 1299 * 1300 *---------------------------------------------------------------------- 1301 */ 1302 1303Tcl_Channel 1304TclWinOpenFileChannel( 1305 HANDLE handle, /* Win32 HANDLE to swallow */ 1306 char *channelName, /* Buffer to receive channel name */ 1307 int permissions, /* OR'ed combination of TCL_READABLE, 1308 * TCL_WRITABLE, or TCL_EXCEPTION, indicating 1309 * which operations are valid on the file. */ 1310 int appendMode) /* OR'ed combination of bits indicating what 1311 * additional configuration of the channel is 1312 * present. */ 1313{ 1314 FileInfo *infoPtr; 1315 ThreadSpecificData *tsdPtr = FileInit(); 1316 1317 /* 1318 * See if a channel with this handle already exists. 1319 */ 1320 1321 for (infoPtr = tsdPtr->firstFilePtr; infoPtr != NULL; 1322 infoPtr = infoPtr->nextPtr) { 1323 if (infoPtr->handle == (HANDLE) handle) { 1324 return (permissions==infoPtr->validMask) ? infoPtr->channel : NULL; 1325 } 1326 } 1327 1328 infoPtr = (FileInfo *) ckalloc((unsigned) sizeof(FileInfo)); 1329 1330 /* 1331 * TIP #218. Removed the code inserting the new structure into the global 1332 * list. This is now handled in the thread action callbacks, and only 1333 * there. 1334 */ 1335 1336 infoPtr->nextPtr = NULL; 1337 infoPtr->validMask = permissions; 1338 infoPtr->watchMask = 0; 1339 infoPtr->flags = appendMode; 1340 infoPtr->handle = handle; 1341 infoPtr->dirty = 0; 1342 wsprintfA(channelName, "file%lx", (int) infoPtr); 1343 1344 infoPtr->channel = Tcl_CreateChannel(&fileChannelType, channelName, 1345 (ClientData) infoPtr, permissions); 1346 1347 /* 1348 * Files have default translation of AUTO and ^Z eof char, which means 1349 * that a ^Z will be accepted as EOF when reading. 1350 */ 1351 1352 Tcl_SetChannelOption(NULL, infoPtr->channel, "-translation", "auto"); 1353 Tcl_SetChannelOption(NULL, infoPtr->channel, "-eofchar", "\032 {}"); 1354 1355 return infoPtr->channel; 1356} 1357 1358/* 1359 *---------------------------------------------------------------------- 1360 * 1361 * TclWinFlushDirtyChannels -- 1362 * 1363 * Flush all dirty channels to disk, so that requesting the size of any 1364 * file returns the correct value. 1365 * 1366 * Results: 1367 * None. 1368 * 1369 * Side effects: 1370 * Information is actually written to disk now, rather than later. Don't 1371 * call this too often, or there will be a performance hit (i.e. only 1372 * call when we need to ask for the size of a file). 1373 * 1374 *---------------------------------------------------------------------- 1375 */ 1376 1377void 1378TclWinFlushDirtyChannels(void) 1379{ 1380 FileInfo *infoPtr; 1381 ThreadSpecificData *tsdPtr = FileInit(); 1382 1383 /* 1384 * Flush all channels which are dirty, i.e. may have data pending in the 1385 * OS. 1386 */ 1387 1388 for (infoPtr = tsdPtr->firstFilePtr; infoPtr != NULL; 1389 infoPtr = infoPtr->nextPtr) { 1390 if (infoPtr->dirty) { 1391 FlushFileBuffers(infoPtr->handle); 1392 infoPtr->dirty = 0; 1393 } 1394 } 1395} 1396 1397/* 1398 *---------------------------------------------------------------------- 1399 * 1400 * FileThreadActionProc -- 1401 * 1402 * Insert or remove any thread local refs to this channel. 1403 * 1404 * Results: 1405 * None. 1406 * 1407 * Side effects: 1408 * Changes thread local list of valid channels. 1409 * 1410 *---------------------------------------------------------------------- 1411 */ 1412 1413static void 1414FileThreadActionProc( 1415 ClientData instanceData, 1416 int action) 1417{ 1418 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); 1419 FileInfo *infoPtr = (FileInfo *) instanceData; 1420 1421 if (action == TCL_CHANNEL_THREAD_INSERT) { 1422 infoPtr->nextPtr = tsdPtr->firstFilePtr; 1423 tsdPtr->firstFilePtr = infoPtr; 1424 } else { 1425 FileInfo **nextPtrPtr; 1426 int removed = 0; 1427 1428 for (nextPtrPtr = &(tsdPtr->firstFilePtr); (*nextPtrPtr) != NULL; 1429 nextPtrPtr = &((*nextPtrPtr)->nextPtr)) { 1430 if ((*nextPtrPtr) == infoPtr) { 1431 (*nextPtrPtr) = infoPtr->nextPtr; 1432 removed = 1; 1433 break; 1434 } 1435 } 1436 1437 /* 1438 * This could happen if the channel was created in one thread and then 1439 * moved to another without updating the thread local data in each 1440 * thread. 1441 */ 1442 1443 if (!removed) { 1444 Tcl_Panic("file info ptr not on thread channel list"); 1445 } 1446 } 1447} 1448 1449/* 1450 *---------------------------------------------------------------------- 1451 * 1452 * FileGetType -- 1453 * 1454 * Given a file handle, return its type 1455 * 1456 * Results: 1457 * None. 1458 * 1459 * Side effects: 1460 * None. 1461 * 1462 *---------------------------------------------------------------------- 1463 */ 1464 1465DWORD 1466FileGetType( 1467 HANDLE handle) /* Opened file handle */ 1468{ 1469 DWORD type; 1470 1471 type = GetFileType(handle); 1472 1473 /* 1474 * If the file is a character device, we need to try to figure out whether 1475 * it is a serial port, a console, or something else. We test for the 1476 * console case first because this is more common. 1477 */ 1478 1479 if ((type == FILE_TYPE_CHAR) 1480 || ((type == FILE_TYPE_UNKNOWN) && !GetLastError())) { 1481 DWORD consoleParams; 1482 1483 if (GetConsoleMode(handle, &consoleParams)) { 1484 type = FILE_TYPE_CONSOLE; 1485 } else { 1486 DCB dcb; 1487 1488 dcb.DCBlength = sizeof(DCB); 1489 if (GetCommState(handle, &dcb)) { 1490 type = FILE_TYPE_SERIAL; 1491 } 1492 } 1493 } 1494 1495 return type; 1496} 1497 1498/* 1499 * Local Variables: 1500 * mode: c 1501 * c-basic-offset: 4 1502 * fill-column: 78 1503 * End: 1504 */ 1505