Subject: win/tclWinChan.c Tcl_InitNotifier: Bug [2413550] Avoid reopening of serial channels which causes issues with Bluetooth virtual com. Patch by Rolf Schroedter.

From: oehhar <[email-masked]>
Date: Thu, 30 Jan 2014 11:02:51 +0000

---
 ChangeLog          |   6 +++
 win/tclWinChan.c   | 151 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
 win/tclWinInt.h    |   2 +-
 win/tclWinSerial.c |  25 +++++----
 4 files changed, 171 insertions(+), 13 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index fd8c7c7..aec77d9 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -7,6 +7,12 @@ this log file.  You may still find useful things in it, but the Timeline is
 a better first place to look now.
 ============================================================================
 
+2014-01-30  Harald Oehlmann  <[email-masked]>
+
+	* win/tclWinChan.c Tcl_InitNotifier: Bug [2413550]
+	  Avoid reopening of serial channels which causes issues with
+          Bluetooth virtual com. Patch by Rolf Schroedter.
+
 2013-08-30  Don Porter  <[email-masked]>
 
 	* generic/tcl.h:	Bump to 8.5.15 for release.
diff --git a/win/tclWinChan.c b/win/tclWinChan.c
index 89d898f..19e3655 100644
--- a/win/tclWinChan.c
+++ b/win/tclWinChan.c
@@ -95,7 +95,7 @@ static void		FileThreadActionProc(ClientData instanceData,
 static int		FileTruncateProc(ClientData instanceData,
 			    Tcl_WideInt length);
 static DWORD		FileGetType(HANDLE handle);
-
+static int		NativeIsComPort(CONST TCHAR *nativeName);
 /*
  * This structure describes the channel type structure for file based IO.
  */
@@ -904,6 +904,33 @@ TclpOpenFileChannel(
     }
 
     /*
+     * [2413550] Avoid double-open of serial ports on Windows
+     * Special handling for Windows serial ports by a "name-hint"
+     * to directly open it with the OVERLAPPED flag set.
+     */
+
+    if( NativeIsComPort(nativeName) ) {
+
+	handle = TclWinSerialOpen(INVALID_HANDLE_VALUE, nativeName, accessMode);
+	if (handle == INVALID_HANDLE_VALUE) {
+	    TclWinConvertError(GetLastError());
+	    if (interp != (Tcl_Interp *) NULL) {
+		Tcl_AppendResult(interp, "couldn't open serial \"",
+			TclGetString(pathPtr), "\": ",
+			Tcl_PosixError(interp), NULL);
+	    }
+	    return NULL;
+	}
+
+	/*
+	* For natively named Windows serial ports we are done.
+	*/
+	channel = TclWinOpenSerialChannel(handle, channelName,
+		channelPermissions);
+
+	return channel;
+    }
+    /*
      * Set up the file sharing mode.  We want to allow simultaneous access.
      */
 
@@ -935,11 +962,15 @@ TclpOpenFileChannel(
     switch (FileGetType(handle)) {
     case FILE_TYPE_SERIAL:
 	/*
+	 * Natively named serial ports "com1-9", "\\\\.\\comXX" are 
+	 * already done with the code above.
+	 * Here we handle all other serial port names.
+	 *
 	 * Reopen channel for OVERLAPPED operation. Normally this shouldn't
 	 * fail, because the channel exists.
 	 */
 
-	handle = TclWinSerialReopen(handle, nativeName, accessMode);
+	handle = TclWinSerialOpen(handle, nativeName, accessMode);
 	if (handle == INVALID_HANDLE_VALUE) {
 	    TclWinConvertError(GetLastError());
 	    if (interp != (Tcl_Interp *) NULL) {
@@ -1476,6 +1507,122 @@ FileGetType(
     return type;
 }
 
+ /*
+ *----------------------------------------------------------------------
+ *
+ * NativeIsComPort --
+ *
+ *	Determines if a path refers to a Windows serial port.
+ *	A simple and efficient solution is to use a "name hint" to detect 
+ *      COM ports by their filename instead of resorting to a syscall 
+ *	to detect serialness after the fact.
+ *	The following patterns cover common serial port names:
+ *	    COM[1-9]:?
+ *	    //./COM[0-9]+
+ *	    \\.\COM[0-9]+
+ *
+ * Results:
+ *	1 = serial port, 0 = not.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+NativeIsComPort(
+    const TCHAR *nativePath)	/* Path of file to access, native encoding. */
+{
+    /*
+     * Use wide-char or plain character case-insensitive comparison
+     */
+    if (tclWinProcs->useWide) {
+	const WCHAR *p = (const WCHAR *) nativePath;
+	int i, len = wcslen(p);
+
+	/*
+	 * 1. Look for com[1-9]:?
+	 */
+
+	if ( (len >= 4) && (len <= 5) 
+		&& (_wcsnicmp(p, L"com", 3) == 0) ) {
+	    /*
+	    * The 4th character must be a digit 1..9 optionally followed by a ":"
+	    */
+
+	    if ( (p[3] < L'1') || (p[3] > L'9') ) {
+		return 0;
+	    }
+	    if ( (len == 5) && (p[4] != L':') ) {
+		return 0;
+	    }
+	    return 1;
+	}
+
+	/*
+	 * 2. Look for //./com[0-9]+ or \\.\com[0-9]+
+	 */
+
+	if ( (len >= 8) && ( 
+		   (_wcsnicmp(p, L"//./com", 7) == 0)
+		|| (_wcsnicmp(p, L"\\\\.\\com", 7) == 0) ) )
+	{
+	    /*
+	    * Charaters 8..end must be a digits 0..9
+	    */
+
+	    for ( i=7; i<len; i++ ) {
+		if ( (p[i] < '0') || (p[i] > '9') ) {
+		    return 0;
+		}
+	    }
+	    return 1;
+	}
+
+    } else {
+	const char *p = (const char *) nativePath;
+	int   i, len = strlen(p);
+
+	/*
+	 * 1. Look for com[1-9]:?
+	 */
+
+	if ( (len >= 4) && (len <= 5) 
+		&& (strnicmp(p, "com", 3) == 0) ) {
+	    /*
+	    * The 4th character must be a digit 1..9 optionally followed by a ":"
+	    */
+
+	    if ( (p[3] < '1') || (p[3] > '9') ) {
+		return 0;
+	    }
+	    if ( (len == 5) && (p[4] != ':') ) {
+		return 0;
+	    }
+	    return 1;
+	}
+
+	/*
+	 * 2. Look for //./com[0-9]+ or \\.\com[0-9]+
+	 */
+
+	if ( (len >= 8) && ( 
+		   (strnicmp(p, "//./com", 7) == 0)
+		|| (strnicmp(p, "\\\\.\\com", 7) == 0) ) )
+	{
+	    /*
+	    * Charaters 8..end must be a digits 0..9
+	    */
+
+	    for ( i=7; i<len; i++ ) {
+		if ( (p[i] < '0') || (p[i] > '9') ) {
+		    return 0;
+		}
+	    }
+	    return 1;
+	}
+    }
+    return 0;
+}
+
 /*
  * Local Variables:
  * mode: c
diff --git a/win/tclWinInt.h b/win/tclWinInt.h
index 3d5e275..ccf48bb 100644
--- a/win/tclWinInt.h
+++ b/win/tclWinInt.h
@@ -187,7 +187,7 @@ MODULE_SCOPE Tcl_Channel TclWinOpenFileChannel(HANDLE handle, char *channelName,
 MODULE_SCOPE Tcl_Channel TclWinOpenSerialChannel(HANDLE handle,
 			    char *channelName, int permissions);
 MODULE_SCOPE void	TclWinResetInterfaceEncodings();
-MODULE_SCOPE HANDLE	TclWinSerialReopen(HANDLE handle, CONST TCHAR *name,
+MODULE_SCOPE HANDLE	TclWinSerialOpen(HANDLE handle, CONST TCHAR *name,
 			    DWORD access);
 MODULE_SCOPE int	TclWinSymLinkCopyDirectory(CONST TCHAR* LinkOriginal,
 			    CONST TCHAR* LinkCopy);
diff --git a/win/tclWinSerial.c b/win/tclWinSerial.c
index d5244ac..312a2f8 100644
--- a/win/tclWinSerial.c
+++ b/win/tclWinSerial.c
@@ -1410,23 +1410,22 @@ SerialWriterThread(
 /*
  *----------------------------------------------------------------------
  *
- * TclWinSerialReopen --
+ * TclWinSerialOpen --
  *
- *	Reopens the serial port with the OVERLAPPED FLAG set
+ *	Opens or Reopens the serial port with the OVERLAPPED FLAG set
  *
  * Results:
- *	Returns the new handle, or INVALID_HANDLE_VALUE. Normally there
- *	shouldn't be any error, because the same channel has previously been
- *	succeesfully opened.
+ *	Returns the new handle, or INVALID_HANDLE_VALUE. 
+ *	I an existing channel is specified it is closed and reopened.
  *
  * Side effects:
- *	May close the original handle
+ *	May close/reopen the original handle
  *
  *----------------------------------------------------------------------
  */
 
 HANDLE
-TclWinSerialReopen(
+TclWinSerialOpen(
     HANDLE handle,
     CONST TCHAR *name,
     DWORD access)
@@ -1434,16 +1433,22 @@ TclWinSerialReopen(
     SerialInit();
 
     /*
+     * If an open channel is specified, close it
+     */
+
+    if ( handle != INVALID_HANDLE_VALUE && CloseHandle(handle) == FALSE) {
+	return INVALID_HANDLE_VALUE;
+    }
+
+    /*
      * Multithreaded I/O needs the overlapped flag set otherwise
      * ClearCommError blocks under Windows NT/2000 until serial output is
      * finished
      */
 
-    if (CloseHandle(handle) == FALSE) {
-	return INVALID_HANDLE_VALUE;
-    }
     handle = (*tclWinProcs->createFileProc)(name, access, 0, 0,
 	    OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0);
+
     return handle;
 }
 
-- 
cgit v0.12