Win32: Unified interface of `Win32OperatingSystem` and `UnixOperatingSystem` w.r.t. executing external programs jv
authorJan Vrany <jan.vrany@fit.cvut.cz>
Thu, 11 Jan 2018 23:40:14 +0000
branchjv
changeset 23097 d619cf99365a
parent 23096 36e1c7a471ab
child 23098 078e5d40fb7f
Win32: Unified interface of `Win32OperatingSystem` and `UnixOperatingSystem` w.r.t. executing external programs Historically the second parameter to `#exec:commandPath withArguments:env...` must have been an array of arrguments on Unix and (properly quoted) command string on Windows. This required every direct or indirect user of this method to check for operating system and, on Windows, care for quoting herself. Quoting is notoriously difficult to do that lead to various problems. This commit unifies the interface by allowing second argument to be an array of arguments (just like on Unix) and the `Win32OperatingSystem` itself performs the quoting (hopefully right). For backward compatibility, String argument is also supported. In that case it's assumet the user did the quoting.
Win32OperatingSystem.st
--- a/Win32OperatingSystem.st	Tue Dec 12 08:57:23 2017 +0000
+++ b/Win32OperatingSystem.st	Thu Jan 11 23:40:14 2018 +0000
@@ -1609,7 +1609,7 @@
 getTTYAttributes: fd
 	OsError raiseErrorString: 'TTYs / PTYs not supported on Windows'
 
-!	
+!
 
 isTTY: fd                                                                                                     
     "Return true, if given filedescriptor refers to a character device (console). See _isatty()                                  
@@ -3783,66 +3783,77 @@
     "Modified: / 11-02-2007 / 20:51:08 / cg"
 !
 
-exec:aCommandPath withArguments:argString environment:environment fileDescriptors:fdArray fork:doFork
-	newPgrp:newPgrp inDirectory:aDirectory
-	showWindow:showWindowBooleanOrNil
+exec:commandPath withArguments:argumentsOrCommandLine environment:environment fileDescriptors:fdArray fork:doFork
+        newPgrp:newPgrp inDirectory:aDirectory
+        showWindow:showWindowBooleanOrNil
 
     "Internal lowLevel entry for combined fork & exec for WIN32
 
      If fork is false (chain a command):
-	 execute the OS command specified by the argument, aCommandPath, with
-	 arguments in argArray (no arguments, if nil).
-	 If successful, this method does not return and smalltalk is gone.
-	 If not successful, it does return.
-	 Normal use is with forkForCommand.
+         execute the OS command specified by the argument, aCommandPath, with
+         arguments in argArray (no arguments, if nil).
+         If successful, this method does not return and smalltalk is gone.
+         If not successful, it does return.
+         Normal use is with forkForCommand.
 
      If fork is true (subprocess command execution):
-	fork a child to do the above.
-	The Win32ProcessHandle of the child process is returned; nil if the fork failed.
+        fork a child to do the above.
+        The Win32ProcessHandle of the child process is returned; nil if the fork failed.
 
      fdArray contains the filedescriptors, to be used for the child (if fork is true).
-	fdArray[1] = 15 -> use fd 15 as stdin.
-	If an element of the array is set to nil, the corresponding filedescriptor
-	will be closed for the child.
-	fdArray[0] == StdIn for child
-	fdArray[1] == StdOut for child
-	fdArray[2] == StdErr for child
+        fdArray[1] = 15 -> use fd 15 as stdin.
+        If an element of the array is set to nil, the corresponding filedescriptor
+        will be closed for the child.
+        fdArray[0] == StdIn for child
+        fdArray[1] == StdOut for child
+        fdArray[2] == StdErr for child
 
      NOTE that in WIN32 the fds are HANDLES.
 
      If newPgrp is true, the subprocess will be established in a new process group.
-	The processgroup will be equal to id.
-	newPgrp is not used on WIN32 and VMS systems.
+        The processgroup will be equal to id.
+        newPgrp is not used on WIN32 and VMS systems.
 
      showWindowOrBoolean may be:
-	true  - a window is shown on start of the command
-	false - the command window is hidden
-	nil   - the nCmdShown parameter of the commans's winmain function determins,
-		if a window is shown.
-	#default
-	      - same as nil
-    "
-
-    |dirPath rslt|
-
+        true  - a window is shown on start of the command
+        false - the command window is hidden
+        nil   - the nCmdShown parameter of the commans's winmain function determins,
+                if a window is shown.
+        #default
+              - same as nil
+    "
+
+    |commandLine dirPath rslt|
+
+    argumentsOrCommandLine isString ifTrue:[ 
+        commandLine := argumentsOrCommandLine
+    ] ifFalse:[
+        argumentsOrCommandLine isSequenceable ifTrue:[ 
+            commandLine := String streamContents:[:s|
+                argumentsOrCommandLine 
+                    do:[:p |self quoteCommandParameter: p on: s]
+                    separatedBy:[ s space ]
+            ].
+        ].
+    ].
     aDirectory notNil ifTrue:[
-	dirPath := aDirectory asFilename asAbsoluteFilename osNameForDirectory.
-	(dirPath endsWith:':') ifTrue:[
-	    dirPath := dirPath , '\'.
-	].
+        dirPath := aDirectory asFilename asAbsoluteFilename osNameForDirectory.
+        (dirPath endsWith:':') ifTrue:[
+            dirPath := dirPath , '\'.
+        ].
     ].
 
     rslt := self
-	primExec:aCommandPath
-	commandLine:argString
-	environment:environment
-	fileDescriptors:fdArray
-	fork:doFork
-	newPgrp:newPgrp
-	inPath:dirPath
-	createFlags:nil
-	inheritHandles:true
-	showWindow:showWindowBooleanOrNil.
+        primExec:commandPath
+        commandLine:commandLine
+        environment:environment
+        fileDescriptors:fdArray
+        fork:doFork
+        newPgrp:newPgrp
+        inPath:dirPath
+        createFlags:nil
+        inheritHandles:true
+        showWindow:showWindowBooleanOrNil.
 
 "/ 'created ' print. cmdLine print. ' -> ' print. rslt printCR.
     ^ rslt
@@ -3850,6 +3861,7 @@
     "Modified: / 31-01-1998 / 10:54:24 / md"
     "Modified: / 15-05-1999 / 18:07:51 / cg"
     "Modified (comment): / 18-10-2016 / 16:00:26 / cg"
+    "Modified: / 11-01-2018 / 23:31:55 / jv"
 !
 
 getStatusOfProcess:aProcessId
@@ -10653,6 +10665,50 @@
     "
 	self primExpandEnvironmentStringsW:'%ProgramFiles%\test\x' asUnicodeString into:(Unicode16String new:256) inspect size:256
     "
+!
+
+quoteCommandParameter: parameter on: stream
+    "Quotes the parameter as neccesary on Windows"
+
+    "/ Adapted version of ArgvQuote,
+    "/ see http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx
+
+    | parameterS |
+
+    stream nextPut: $".
+    parameterS := parameter readStream.
+    [ parameterS atEnd ] whileFalse:[
+        | numBackSlashes |
+
+        numBackSlashes := 0.
+
+        [ parameterS atEnd not and:[ parameterS peek == $\ ] ] whileTrue:[
+            numBackSlashes := numBackSlashes + 1.
+            parameterS next.
+        ].
+        parameterS atEnd ifTrue:[
+            "/ Escape all backslashes, but let the terminating
+            "/ double quotation mark we add below be interpreted
+            "/ as a metacharacter.
+            stream next: numBackSlashes * 2 put: $\
+        ] ifFalse:[
+            parameterS peek == $" ifTrue:[
+                "/ Escape all backslashes and the following
+                "/ double quotation mark.
+                stream next: numBackSlashes * 2 put: $\.
+                stream nextPut: $\.
+                stream nextPut: $".
+            ] ifFalse:[
+                "/ Backslashes aren't special here.
+                stream next: numBackSlashes put: $\.               
+                stream nextPut: parameterS peek.
+            ].
+            parameterS next.
+        ].
+    ].
+    stream nextPut: $".
+
+    "Created: / 11-01-2018 / 23:24:07 / jv"
 ! !
 
 !Win32OperatingSystem class methodsFor:'regional settings'!