From deb4d954eafc4fc65f04c00a08e08c3e69df32d0 Mon Sep 17 00:00:00 2001 From: John Howard Date: Wed, 23 Jan 2019 13:57:37 -0800 Subject: [PATCH] Windows:Have native CommandLine in Process Signed-off-by: John Howard This adds a new field `CommandLine` in the `Process` structure for use on Windows. A Windows runtime will check this first and use it as-is when launching WCOW processes in a container. If omitted, the Windows runtime will fall back to the previous behaviour of escaping (eg Windows.EscapeArg in golang) each of the `args` array elements, and space-concatenating them. The reason for this change is to avoid loss of fidelity for launching processes. One such example is the following: `cmd /S /C mkdir "c:/foo"` When parsed into a JSON array such as `Args`, it becomes 5 elements - cmd - /S - /C - mkdir - c:/foo Here, note the lost information being the double-quotes around `c:/foo`. When using the required contenation, space separation required on Windows, (https://github.com/golang/sys/blob/c4afb3effaa53fd9a06ca61262dc7ce8df4c081b/windows/exec_windows.go#L9-L18), this becomes the following command line: `cmd /S /C mkdir c:/foo` When the double-quotes are missing, mkdir would fail, but with the double-quotes, it succeeds as expected: ``` C:\>cmd /s /c mkdir c:/foo The syntax of the command is incorrect. C:\>cmd /s /c mkdir "c:/foo" C:\> ``` The addition of a full `commandLine` in Process for use on Windows alleviates issues where fidelity can be lost. Some further background: For historical reasons, Windows only has native support for launching processes using a command line. It does not support an argument array as per Linux. See the `CreateProcess` API documentation on MSDN. What happens under the covers is that prior to invoking a programs main, the language runtime will convert the command line to a set of argv[] suach as in the C-style `int main(int argc, char* argv), or the golang `os.Args` prior to the programs `main` being invoked, using Windows- specific rules. In go, that's the `commandLineToArgv` function which is called in the init() function of the os package https://github.com/golang/go/blob/ff7b245a31394b700a252fd547cf16ad0ad838b6/src/os/exec_windows.go#L100, In the Microsoft C++ startup code, it does exactly the same processing as documented here prior to invoking main itself: https://docs.microsoft.com/en-us/cpp/cpp/parsing-cpp-command-line-arguments?view=vs-2017. The processing it describes is documented at https://msdn.microsoft.com/en-us/library/windows/desktop/ms683156(v=vs.85).aspx and https://docs.microsoft.com/en-us/windows/desktop/api/shellapi/nf-shellapi-commandlinetoargvw Some related links which provide a lot more information about the very specific (and unique to Windows) command line escaping rules, and handling of them are below: https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/ https://stackoverflow.com/questions/31838469/how-do-i-convert-argv-to-lpcommandline-parameter-of-createprocess https://docs.microsoft.com/en-us/cpp/cpp/parsing-cpp-command-line-arguments?view=vs-2017 --- config.md | 7 +++++-- schema/config-schema.json | 6 ++++-- specs-go/config.go | 4 +++- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/config.md b/config.md index 01fb120..357f319 100644 --- a/config.md +++ b/config.md @@ -155,8 +155,11 @@ For POSIX platforms the `mounts` structure has the following fields: * **`cwd`** (string, REQUIRED) is the working directory that will be set for the executable. This value MUST be an absolute path. * **`env`** (array of strings, OPTIONAL) with the same semantics as [IEEE Std 1003.1-2008's `environ`][ieee-1003.1-2008-xbd-c8.1]. -* **`args`** (array of strings, REQUIRED) with similar semantics to [IEEE Std 1003.1-2008 `execvp`'s *argv*][ieee-1003.1-2008-functions-exec]. - This specification extends the IEEE standard in that at least one entry is REQUIRED, and that entry is used with the same semantics as `execvp`'s *file*. +* **`args`** (array of strings, OPTIONAL) with similar semantics to [IEEE Std 1003.1-2008 `execvp`'s *argv*][ieee-1003.1-2008-functions-exec]. + This specification extends the IEEE standard in that at least one entry is REQUIRED (non-Windows), and that entry is used with the same semantics as `execvp`'s *file*. This field is OPTIONAL on Windows, and `commandLine` is REQUIRED if this field is omitted. +* **`commandLine`** (string, OPTIONAL) specifies the full command line to be executed on Windows. + This is the preferred means of supplying the command line on Windows. If omitted, the runtime will fall back to escaping and concatenating fields from `args` before making the system call into Windows. + ### POSIX process diff --git a/schema/config-schema.json b/schema/config-schema.json index f90bd4b..e3d6096 100644 --- a/schema/config-schema.json +++ b/schema/config-schema.json @@ -50,13 +50,15 @@ "process": { "type": "object", "required": [ - "cwd", - "args" + "cwd" ], "properties": { "args": { "$ref": "defs.json#/definitions/ArrayOfStrings" }, + "commandLine": { + "type": "string" + }, "consoleSize": { "type": "object", "required": [ diff --git a/specs-go/config.go b/specs-go/config.go index 6d791e7..27268f9 100644 --- a/specs-go/config.go +++ b/specs-go/config.go @@ -38,7 +38,9 @@ type Process struct { // User specifies user information for the process. User User `json:"user"` // Args specifies the binary and arguments for the application to execute. - Args []string `json:"args"` + Args []string `json:"args,omitempty"` + // CommandLine specifies the full command line for the application to execute on Windows. + CommandLine string `json:"commandLine,omitempty" platform:"windows"` // Env populates the process environment for the process. Env []string `json:"env,omitempty"` // Cwd is the current working directory for the process and must be