Browse code

Windows: Exec driver

Signed-off-by: John Howard <jhoward@microsoft.com>

John Howard authored on 2015/05/28 05:15:14
Showing 51 changed files
... ...
@@ -5,6 +5,7 @@ import (
5 5
 	"io"
6 6
 	"net/url"
7 7
 	"os"
8
+	"runtime"
8 9
 
9 10
 	"github.com/Sirupsen/logrus"
10 11
 	"github.com/docker/docker/opts"
... ...
@@ -103,6 +104,13 @@ func (cli *DockerCli) CmdRun(args ...string) error {
103 103
 		sigProxy = false
104 104
 	}
105 105
 
106
+	// Telling the Windows daemon the initial size of the tty during start makes
107
+	// a far better user experience rather than relying on subsequent resizes
108
+	// to cause things to catch up.
109
+	if runtime.GOOS == "windows" {
110
+		hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = cli.getTtySize()
111
+	}
112
+
106 113
 	createResponse, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName)
107 114
 	if err != nil {
108 115
 		return err
... ...
@@ -74,6 +74,7 @@ type CommonContainer struct {
74 74
 	MountLabel, ProcessLabel string
75 75
 	RestartCount             int
76 76
 	UpdateDns                bool
77
+	HasBeenStartedBefore     bool
77 78
 
78 79
 	MountPoints map[string]*mountPoint
79 80
 	Volumes     map[string]string // Deprecated since 1.7, kept for backwards compatibility
... ...
@@ -286,6 +287,7 @@ func (container *Container) Run() error {
286 286
 	if err := container.Start(); err != nil {
287 287
 		return err
288 288
 	}
289
+	container.HasBeenStartedBefore = true
289 290
 	container.WaitStop(-1 * time.Second)
290 291
 	return nil
291 292
 }
... ...
@@ -92,11 +92,12 @@ func populateCommand(c *Container, env []string) error {
92 92
 
93 93
 	// TODO Windows. Further refactoring required (privileged/user)
94 94
 	processConfig := execdriver.ProcessConfig{
95
-		Privileged: c.hostConfig.Privileged,
96
-		Entrypoint: c.Path,
97
-		Arguments:  c.Args,
98
-		Tty:        c.Config.Tty,
99
-		User:       c.Config.User,
95
+		Privileged:  c.hostConfig.Privileged,
96
+		Entrypoint:  c.Path,
97
+		Arguments:   c.Args,
98
+		Tty:         c.Config.Tty,
99
+		User:        c.Config.User,
100
+		ConsoleSize: c.hostConfig.ConsoleSize,
100 101
 	}
101 102
 
102 103
 	processConfig.Env = env
... ...
@@ -116,6 +117,7 @@ func populateCommand(c *Container, env []string) error {
116 116
 		ProcessConfig:  processConfig,
117 117
 		ProcessLabel:   c.GetProcessLabel(),
118 118
 		MountLabel:     c.GetMountLabel(),
119
+		FirstStart:     !c.HasBeenStartedBefore,
119 120
 	}
120 121
 
121 122
 	return nil
... ...
@@ -138,13 +138,14 @@ type Mount struct {
138 138
 type ProcessConfig struct {
139 139
 	exec.Cmd `json:"-"`
140 140
 
141
-	Privileged bool     `json:"privileged"`
142
-	User       string   `json:"user"`
143
-	Tty        bool     `json:"tty"`
144
-	Entrypoint string   `json:"entrypoint"`
145
-	Arguments  []string `json:"arguments"`
146
-	Terminal   Terminal `json:"-"` // standard or tty terminal
147
-	Console    string   `json:"-"` // dev/console path
141
+	Privileged  bool     `json:"privileged"`
142
+	User        string   `json:"user"`
143
+	Tty         bool     `json:"tty"`
144
+	Entrypoint  string   `json:"entrypoint"`
145
+	Arguments   []string `json:"arguments"`
146
+	Terminal    Terminal `json:"-"` // standard or tty terminal
147
+	Console     string   `json:"-"` // dev/console path
148
+	ConsoleSize [2]int   `json:"-"` // h,w of initial console size
148 149
 }
149 150
 
150 151
 // TODO Windows: Factor out unused fields such as LxcConfig, AppArmorProfile,
... ...
@@ -175,4 +176,7 @@ type Command struct {
175 175
 	LxcConfig          []string          `json:"lxc_config"`
176 176
 	AppArmorProfile    string            `json:"apparmor_profile"`
177 177
 	CgroupParent       string            `json:"cgroup_parent"` // The parent cgroup for this command.
178
+	FirstStart         bool              `json:"first_start"`
179
+	LayerPaths         []string          `json:"layer_paths"` // Windows needs to know the layer paths and folder for a command
180
+	LayerFolder        string            `json:"layer_folder"`
178 181
 }
... ...
@@ -13,7 +13,7 @@ import (
13 13
 func NewDriver(name string, options []string, root, libPath, initPath string, sysInfo *sysinfo.SysInfo) (execdriver.Driver, error) {
14 14
 	switch name {
15 15
 	case "windows":
16
-		return windows.NewDriver(root, initPath)
16
+		return windows.NewDriver(root, initPath, options)
17 17
 	}
18 18
 	return nil, fmt.Errorf("unknown exec driver %s", name)
19 19
 }
20 20
new file mode 100644
... ...
@@ -0,0 +1,36 @@
0
+// +build windows
1
+
2
+package windows
3
+
4
+import (
5
+	"errors"
6
+
7
+	"github.com/docker/docker/daemon/execdriver"
8
+)
9
+
10
+func checkSupportedOptions(c *execdriver.Command) error {
11
+	// Windows doesn't support read-only root filesystem
12
+	if c.ReadonlyRootfs {
13
+		return errors.New("Windows does not support the read-only root filesystem option")
14
+	}
15
+
16
+	// Windows doesn't support username
17
+	if c.ProcessConfig.User != "" {
18
+		return errors.New("Windows does not support the username option")
19
+	}
20
+
21
+	// Windows doesn't support custom lxc options
22
+	if c.LxcConfig != nil {
23
+		return errors.New("Windows does not support lxc options")
24
+	}
25
+
26
+	// Windows doesn't support ulimit
27
+	if c.Resources.Rlimits != nil {
28
+		return errors.New("Windows does not support ulimit options")
29
+	}
30
+
31
+	// TODO Windows: Validate other fields which Windows doesn't support, factor
32
+	// out where applicable per platform.
33
+
34
+	return nil
35
+}
0 36
new file mode 100644
... ...
@@ -0,0 +1,7 @@
0
+// +build windows
1
+
2
+package windows
3
+
4
+func (d *driver) Clean(id string) error {
5
+	return nil
6
+}
0 7
new file mode 100644
... ...
@@ -0,0 +1,144 @@
0
+// +build windows
1
+
2
+package windows
3
+
4
+import (
5
+	"errors"
6
+	"fmt"
7
+
8
+	"github.com/Sirupsen/logrus"
9
+	"github.com/docker/docker/daemon/execdriver"
10
+	"github.com/docker/docker/pkg/stringid"
11
+	"github.com/microsoft/hcsshim"
12
+	"github.com/natefinch/npipe"
13
+)
14
+
15
+func (d *driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
16
+
17
+	var (
18
+		inListen, outListen, errListen     *npipe.PipeListener
19
+		term                               execdriver.Terminal
20
+		err                                error
21
+		randomID                           string = stringid.GenerateRandomID()
22
+		serverPipeFormat, clientPipeFormat string
23
+		pid                                uint32
24
+		exitCode                           int32
25
+	)
26
+
27
+	active := d.activeContainers[c.ID]
28
+	if active == nil {
29
+		return -1, fmt.Errorf("Exec - No active container exists with ID %s", c.ID)
30
+	}
31
+
32
+	createProcessParms := hcsshim.CreateProcessParams{
33
+		EmulateConsole:   processConfig.Tty, // Note NOT c.ProcessConfig.Tty
34
+		WorkingDirectory: c.WorkingDir,
35
+	}
36
+
37
+	// Configure the environment for the process // Note NOT c.ProcessConfig.Tty
38
+	createProcessParms.Environment = setupEnvironmentVariables(processConfig.Env)
39
+
40
+	// We use another unique ID here for each exec instance otherwise it
41
+	// may conflict with the pipe name being used by RUN.
42
+
43
+	// We use a different pipe name between real and dummy mode in the HCS
44
+	if dummyMode {
45
+		clientPipeFormat = `\\.\pipe\docker-exec-%[1]s-%[2]s-%[3]s`
46
+		serverPipeFormat = clientPipeFormat
47
+	} else {
48
+		clientPipeFormat = `\\.\pipe\docker-exec-%[2]s-%[3]s`
49
+		serverPipeFormat = `\\.\Containers\%[1]s\Device\NamedPipe\docker-exec-%[2]s-%[3]s`
50
+	}
51
+
52
+	// Connect stdin
53
+	if pipes.Stdin != nil {
54
+		stdInPipe := fmt.Sprintf(serverPipeFormat, c.ID, randomID, "stdin")
55
+		createProcessParms.StdInPipe = fmt.Sprintf(clientPipeFormat, c.ID, randomID, "stdin")
56
+
57
+		// Listen on the named pipe
58
+		inListen, err = npipe.Listen(stdInPipe)
59
+		if err != nil {
60
+			logrus.Errorf("stdin failed to listen on %s %s ", stdInPipe, err)
61
+			return -1, err
62
+		}
63
+		defer inListen.Close()
64
+
65
+		// Launch a goroutine to do the accept. We do this so that we can
66
+		// cause an otherwise blocking goroutine to gracefully close when
67
+		// the caller (us) closes the listener
68
+		go stdinAccept(inListen, stdInPipe, pipes.Stdin)
69
+	}
70
+
71
+	// Connect stdout
72
+	stdOutPipe := fmt.Sprintf(serverPipeFormat, c.ID, randomID, "stdout")
73
+	createProcessParms.StdOutPipe = fmt.Sprintf(clientPipeFormat, c.ID, randomID, "stdout")
74
+
75
+	outListen, err = npipe.Listen(stdOutPipe)
76
+	if err != nil {
77
+		logrus.Errorf("stdout failed to listen on %s %s", stdOutPipe, err)
78
+		return -1, err
79
+	}
80
+	defer outListen.Close()
81
+	go stdouterrAccept(outListen, stdOutPipe, pipes.Stdout)
82
+
83
+	// No stderr on TTY. Note NOT c.ProcessConfig.Tty
84
+	if !processConfig.Tty {
85
+		// Connect stderr
86
+		stdErrPipe := fmt.Sprintf(serverPipeFormat, c.ID, randomID, "stderr")
87
+		createProcessParms.StdErrPipe = fmt.Sprintf(clientPipeFormat, c.ID, randomID, "stderr")
88
+
89
+		errListen, err = npipe.Listen(stdErrPipe)
90
+		if err != nil {
91
+			logrus.Errorf("Stderr failed to listen on %s %s", stdErrPipe, err)
92
+			return -1, err
93
+		}
94
+		defer errListen.Close()
95
+		go stdouterrAccept(errListen, stdErrPipe, pipes.Stderr)
96
+	}
97
+
98
+	// While this should get caught earlier, just in case, validate that we
99
+	// have something to run.
100
+	if processConfig.Entrypoint == "" {
101
+		err = errors.New("No entrypoint specified")
102
+		logrus.Error(err)
103
+		return -1, err
104
+	}
105
+
106
+	// Build the command line of the process
107
+	createProcessParms.CommandLine = processConfig.Entrypoint
108
+	for _, arg := range processConfig.Arguments {
109
+		logrus.Debugln("appending ", arg)
110
+		createProcessParms.CommandLine += " " + arg
111
+	}
112
+	logrus.Debugln("commandLine: ", createProcessParms.CommandLine)
113
+
114
+	// Start the command running in the container.
115
+	pid, err = hcsshim.CreateProcessInComputeSystem(c.ID, createProcessParms)
116
+
117
+	if err != nil {
118
+		logrus.Errorf("CreateProcessInComputeSystem() failed %s", err)
119
+		return -1, err
120
+	}
121
+
122
+	// Note NOT c.ProcessConfig.Tty
123
+	if processConfig.Tty {
124
+		term = NewTtyConsole(c.ID, pid)
125
+	} else {
126
+		term = NewStdConsole()
127
+	}
128
+	processConfig.Terminal = term
129
+
130
+	// Invoke the start callback
131
+	if startCallback != nil {
132
+		startCallback(&c.ProcessConfig, int(pid))
133
+	}
134
+
135
+	if exitCode, err = hcsshim.WaitForProcessInComputeSystem(c.ID, pid); err != nil {
136
+		logrus.Errorf("Failed to WaitForProcessInComputeSystem %s", err)
137
+		return -1, err
138
+	}
139
+
140
+	// TODO Windows - Do something with this exit code
141
+	logrus.Debugln("Exiting Run() with ExitCode 0", c.ID)
142
+	return int(exitCode), nil
143
+}
0 144
new file mode 100644
... ...
@@ -0,0 +1,10 @@
0
+// +build windows
1
+
2
+package windows
3
+
4
+import "fmt"
5
+
6
+func (d *driver) GetPidsForContainer(id string) ([]int, error) {
7
+	// TODO Windows: Implementation required.
8
+	return nil, fmt.Errorf("GetPidsForContainer: GetPidsForContainer() not implemented")
9
+}
0 10
new file mode 100644
... ...
@@ -0,0 +1,23 @@
0
+// +build windows
1
+
2
+package windows
3
+
4
+import "github.com/docker/docker/daemon/execdriver"
5
+
6
+type info struct {
7
+	ID     string
8
+	driver *driver
9
+}
10
+
11
+func (d *driver) Info(id string) execdriver.Info {
12
+	return &info{
13
+		ID:     id,
14
+		driver: d,
15
+	}
16
+}
17
+
18
+func (i *info) IsRunning() bool {
19
+	var running bool
20
+	running = true // TODO Need an HCS API
21
+	return running
22
+}
0 23
new file mode 100644
... ...
@@ -0,0 +1,82 @@
0
+// +build windows
1
+
2
+package windows
3
+
4
+import (
5
+	"io"
6
+
7
+	"github.com/Sirupsen/logrus"
8
+	"github.com/natefinch/npipe"
9
+)
10
+
11
+// stdinAccept runs as a go function. It waits for the container system
12
+// to accept our offer of a named pipe for stdin. Once accepted, if we are
13
+// running "attached" to the container (eg docker run -i), then we spin up
14
+// a further thread to copy anything from the client into the container.
15
+//
16
+// Important design note. This function is run as a go function for a very
17
+// good reason. The named pipe Accept call is blocking until one of two things
18
+// happen. Either someone connects to it, or it is forcibly closed. Let's
19
+// assume that no-one connects to it, the only way otherwise the Run()
20
+// method would continue is by closing it. However, as that would be the same
21
+// thread, it can't close it. Hence we run as another thread allowing Run()
22
+// to close the named pipe.
23
+func stdinAccept(inListen *npipe.PipeListener, pipeName string, copyfrom io.ReadCloser) {
24
+
25
+	// Wait for the pipe to be connected to by the shim
26
+	logrus.Debugln("stdinAccept: Waiting on ", pipeName)
27
+	stdinConn, err := inListen.Accept()
28
+	if err != nil {
29
+		logrus.Errorf("Failed to accept on pipe %s %s", pipeName, err)
30
+		return
31
+	}
32
+	logrus.Debugln("Connected to ", stdinConn.RemoteAddr())
33
+
34
+	// Anything that comes from the client stdin should be copied
35
+	// across to the stdin named pipe of the container.
36
+	if copyfrom != nil {
37
+		go func() {
38
+			defer stdinConn.Close()
39
+			logrus.Debugln("Calling io.Copy on stdin")
40
+			bytes, err := io.Copy(stdinConn, copyfrom)
41
+			logrus.Debugf("Finished io.Copy on stdin bytes=%d err=%s pipe=%s", bytes, err, stdinConn.RemoteAddr())
42
+		}()
43
+	} else {
44
+		defer stdinConn.Close()
45
+	}
46
+}
47
+
48
+// stdouterrAccept runs as a go function. It waits for the container system to
49
+// accept our offer of a named pipe - in fact two of them - one for stdout
50
+// and one for stderr (we are called twice). Once the named pipe is accepted,
51
+// if we are running "attached" to the container (eg docker run -i), then we
52
+// spin up a further thread to copy anything from the containers output channels
53
+// to the client.
54
+func stdouterrAccept(outerrListen *npipe.PipeListener, pipeName string, copyto io.Writer) {
55
+
56
+	// Wait for the pipe to be connected to by the shim
57
+	logrus.Debugln("out/err: Waiting on ", pipeName)
58
+	outerrConn, err := outerrListen.Accept()
59
+	if err != nil {
60
+		logrus.Errorf("Failed to accept on pipe %s %s", pipeName, err)
61
+		return
62
+	}
63
+	logrus.Debugln("Connected to ", outerrConn.RemoteAddr())
64
+
65
+	// Anything that comes from the container named pipe stdout/err should be copied
66
+	// across to the stdout/err of the client
67
+	if copyto != nil {
68
+		go func() {
69
+			defer outerrConn.Close()
70
+			logrus.Debugln("Calling io.Copy on ", pipeName)
71
+			bytes, err := io.Copy(copyto, outerrConn)
72
+			logrus.Debugf("Copied %d bytes from pipe=%s", bytes, outerrConn.RemoteAddr())
73
+			if err != nil {
74
+				// Not fatal, just debug log it
75
+				logrus.Debugf("Error hit during copy %s", err)
76
+			}
77
+		}()
78
+	} else {
79
+		defer outerrConn.Close()
80
+	}
81
+}
0 82
new file mode 100644
... ...
@@ -0,0 +1,17 @@
0
+// +build windows
1
+
2
+package windows
3
+
4
+import (
5
+	"fmt"
6
+
7
+	"github.com/docker/docker/daemon/execdriver"
8
+)
9
+
10
+func (d *driver) Pause(c *execdriver.Command) error {
11
+	return fmt.Errorf("Windows: Containers cannot be paused")
12
+}
13
+
14
+func (d *driver) Unpause(c *execdriver.Command) error {
15
+	return fmt.Errorf("Windows: Containers cannot be paused")
16
+}
0 17
new file mode 100644
... ...
@@ -0,0 +1,271 @@
0
+// +build windows
1
+
2
+package windows
3
+
4
+// Note this is alpha code for the bring up of containers on Windows.
5
+
6
+import (
7
+	"encoding/json"
8
+	"errors"
9
+	"fmt"
10
+	"strings"
11
+
12
+	"github.com/Sirupsen/logrus"
13
+	"github.com/docker/docker/daemon/execdriver"
14
+	"github.com/microsoft/hcsshim"
15
+	"github.com/natefinch/npipe"
16
+)
17
+
18
+type layer struct {
19
+	Id   string
20
+	Path string
21
+}
22
+
23
+type defConfig struct {
24
+	DefFile string
25
+}
26
+
27
+type networkConnection struct {
28
+	NetworkName string
29
+	EnableNat   bool
30
+}
31
+type networkSettings struct {
32
+	MacAddress string
33
+}
34
+
35
+type device struct {
36
+	DeviceType string
37
+	Connection interface{}
38
+	Settings   interface{}
39
+}
40
+
41
+type containerInit struct {
42
+	SystemType              string
43
+	Name                    string
44
+	IsDummy                 bool
45
+	VolumePath              string
46
+	Devices                 []device
47
+	IgnoreFlushesDuringBoot bool
48
+	LayerFolderPath         string
49
+	Layers                  []layer
50
+}
51
+
52
+func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (execdriver.ExitStatus, error) {
53
+
54
+	var (
55
+		term                           execdriver.Terminal
56
+		err                            error
57
+		inListen, outListen, errListen *npipe.PipeListener
58
+	)
59
+
60
+	// Make sure the client isn't asking for options which aren't supported
61
+	err = checkSupportedOptions(c)
62
+	if err != nil {
63
+		return execdriver.ExitStatus{ExitCode: -1}, err
64
+	}
65
+
66
+	cu := &containerInit{
67
+		SystemType:              "Container",
68
+		Name:                    c.ID,
69
+		IsDummy:                 dummyMode,
70
+		VolumePath:              c.Rootfs,
71
+		IgnoreFlushesDuringBoot: c.FirstStart,
72
+		LayerFolderPath:         c.LayerFolder,
73
+	}
74
+
75
+	for i := 0; i < len(c.LayerPaths); i++ {
76
+		cu.Layers = append(cu.Layers, layer{
77
+			Id:   hcsshim.NewGUID(c.LayerPaths[i]).ToString(),
78
+			Path: c.LayerPaths[i],
79
+		})
80
+	}
81
+
82
+	if c.Network.Interface != nil {
83
+
84
+		// TODO Windows: Temporary
85
+		c.Network.Interface.Bridge = "Virtual Switch"
86
+
87
+		dev := device{
88
+			DeviceType: "Network",
89
+			Connection: &networkConnection{
90
+				NetworkName: c.Network.Interface.Bridge,
91
+				EnableNat:   false,
92
+			},
93
+		}
94
+
95
+		if c.Network.Interface.MacAddress != "" {
96
+			windowsStyleMAC := strings.Replace(
97
+				c.Network.Interface.MacAddress, ":", "-", -1)
98
+			dev.Settings = networkSettings{
99
+				MacAddress: windowsStyleMAC,
100
+			}
101
+		}
102
+
103
+		cu.Devices = append(cu.Devices, dev)
104
+	}
105
+
106
+	configurationb, err := json.Marshal(cu)
107
+	if err != nil {
108
+		return execdriver.ExitStatus{ExitCode: -1}, err
109
+	}
110
+
111
+	configuration := string(configurationb)
112
+
113
+	err = hcsshim.CreateComputeSystem(c.ID, configuration)
114
+	if err != nil {
115
+		logrus.Debugln("Failed to create temporary container ", err)
116
+		return execdriver.ExitStatus{ExitCode: -1}, err
117
+	}
118
+
119
+	// Start the container
120
+	logrus.Debugln("Starting container ", c.ID)
121
+	err = hcsshim.StartComputeSystem(c.ID)
122
+	if err != nil {
123
+		logrus.Errorf("Failed to start compute system: %s", err)
124
+		return execdriver.ExitStatus{ExitCode: -1}, err
125
+	}
126
+	defer func() {
127
+		// Stop the container
128
+
129
+		if terminateMode {
130
+			logrus.Debugf("Terminating container %s", c.ID)
131
+			if err := hcsshim.TerminateComputeSystem(c.ID); err != nil {
132
+				// IMPORTANT: Don't fail if fails to change state. It could already
133
+				// have been stopped through kill().
134
+				// Otherwise, the docker daemon will hang in job wait()
135
+				logrus.Warnf("Ignoring error from TerminateComputeSystem %s", err)
136
+			}
137
+		} else {
138
+			logrus.Debugf("Shutting down container %s", c.ID)
139
+			if err := hcsshim.ShutdownComputeSystem(c.ID); err != nil {
140
+				// IMPORTANT: Don't fail if fails to change state. It could already
141
+				// have been stopped through kill().
142
+				// Otherwise, the docker daemon will hang in job wait()
143
+				logrus.Warnf("Ignoring error from ShutdownComputeSystem %s", err)
144
+			}
145
+		}
146
+	}()
147
+
148
+	// We use a different pipe name between real and dummy mode in the HCS
149
+	var serverPipeFormat, clientPipeFormat string
150
+	if dummyMode {
151
+		clientPipeFormat = `\\.\pipe\docker-run-%[1]s-%[2]s`
152
+		serverPipeFormat = clientPipeFormat
153
+	} else {
154
+		clientPipeFormat = `\\.\pipe\docker-run-%[2]s`
155
+		serverPipeFormat = `\\.\Containers\%[1]s\Device\NamedPipe\docker-run-%[2]s`
156
+	}
157
+
158
+	createProcessParms := hcsshim.CreateProcessParams{
159
+		EmulateConsole:   c.ProcessConfig.Tty,
160
+		WorkingDirectory: c.WorkingDir,
161
+		ConsoleSize:      c.ProcessConfig.ConsoleSize,
162
+	}
163
+
164
+	// Configure the environment for the process
165
+	createProcessParms.Environment = setupEnvironmentVariables(c.ProcessConfig.Env)
166
+
167
+	// Connect stdin
168
+	if pipes.Stdin != nil {
169
+		stdInPipe := fmt.Sprintf(serverPipeFormat, c.ID, "stdin")
170
+		createProcessParms.StdInPipe = fmt.Sprintf(clientPipeFormat, c.ID, "stdin")
171
+
172
+		// Listen on the named pipe
173
+		inListen, err = npipe.Listen(stdInPipe)
174
+		if err != nil {
175
+			logrus.Errorf("stdin failed to listen on %s err=%s", stdInPipe, err)
176
+			return execdriver.ExitStatus{ExitCode: -1}, err
177
+		}
178
+		defer inListen.Close()
179
+
180
+		// Launch a goroutine to do the accept. We do this so that we can
181
+		// cause an otherwise blocking goroutine to gracefully close when
182
+		// the caller (us) closes the listener
183
+		go stdinAccept(inListen, stdInPipe, pipes.Stdin)
184
+	}
185
+
186
+	// Connect stdout
187
+	stdOutPipe := fmt.Sprintf(serverPipeFormat, c.ID, "stdout")
188
+	createProcessParms.StdOutPipe = fmt.Sprintf(clientPipeFormat, c.ID, "stdout")
189
+
190
+	outListen, err = npipe.Listen(stdOutPipe)
191
+	if err != nil {
192
+		logrus.Errorf("stdout failed to listen on %s err=%s", stdOutPipe, err)
193
+		return execdriver.ExitStatus{ExitCode: -1}, err
194
+	}
195
+	defer outListen.Close()
196
+	go stdouterrAccept(outListen, stdOutPipe, pipes.Stdout)
197
+
198
+	// No stderr on TTY.
199
+	if !c.ProcessConfig.Tty {
200
+		// Connect stderr
201
+		stdErrPipe := fmt.Sprintf(serverPipeFormat, c.ID, "stderr")
202
+		createProcessParms.StdErrPipe = fmt.Sprintf(clientPipeFormat, c.ID, "stderr")
203
+		errListen, err = npipe.Listen(stdErrPipe)
204
+		if err != nil {
205
+			logrus.Errorf("stderr failed to listen on %s err=%s", stdErrPipe, err)
206
+			return execdriver.ExitStatus{ExitCode: -1}, err
207
+		}
208
+		defer errListen.Close()
209
+		go stdouterrAccept(errListen, stdErrPipe, pipes.Stderr)
210
+	}
211
+
212
+	// This should get caught earlier, but just in case - validate that we
213
+	// have something to run
214
+	if c.ProcessConfig.Entrypoint == "" {
215
+		err = errors.New("No entrypoint specified")
216
+		logrus.Error(err)
217
+		return execdriver.ExitStatus{ExitCode: -1}, err
218
+	}
219
+
220
+	// Build the command line of the process
221
+	createProcessParms.CommandLine = c.ProcessConfig.Entrypoint
222
+	for _, arg := range c.ProcessConfig.Arguments {
223
+		logrus.Debugln("appending ", arg)
224
+		createProcessParms.CommandLine += " " + arg
225
+	}
226
+	logrus.Debugf("CommandLine: %s", createProcessParms.CommandLine)
227
+
228
+	// Start the command running in the container.
229
+	var pid uint32
230
+	pid, err = hcsshim.CreateProcessInComputeSystem(c.ID, createProcessParms)
231
+
232
+	if err != nil {
233
+		logrus.Errorf("CreateProcessInComputeSystem() failed %s", err)
234
+		return execdriver.ExitStatus{ExitCode: -1}, err
235
+	}
236
+
237
+	//Save the PID as we'll need this in Kill()
238
+	logrus.Debugf("PID %d", pid)
239
+	c.ContainerPid = int(pid)
240
+
241
+	if c.ProcessConfig.Tty {
242
+		term = NewTtyConsole(c.ID, pid)
243
+	} else {
244
+		term = NewStdConsole()
245
+	}
246
+	c.ProcessConfig.Terminal = term
247
+
248
+	// Maintain our list of active containers. We'll need this later for exec
249
+	// and other commands.
250
+	d.Lock()
251
+	d.activeContainers[c.ID] = &activeContainer{
252
+		command: c,
253
+	}
254
+	d.Unlock()
255
+
256
+	// Invoke the start callback
257
+	if startCallback != nil {
258
+		startCallback(&c.ProcessConfig, int(pid))
259
+	}
260
+
261
+	var exitCode int32
262
+	exitCode, err = hcsshim.WaitForProcessInComputeSystem(c.ID, pid)
263
+	if err != nil {
264
+		logrus.Errorf("Failed to WaitForProcessInComputeSystem %s", err)
265
+		return execdriver.ExitStatus{ExitCode: -1}, err
266
+	}
267
+
268
+	logrus.Debugf("Exiting Run() exitCode %d id=%s", exitCode, c.ID)
269
+	return execdriver.ExitStatus{ExitCode: int(exitCode)}, nil
270
+}
0 271
new file mode 100644
... ...
@@ -0,0 +1,13 @@
0
+// +build windows
1
+
2
+package windows
3
+
4
+import (
5
+	"fmt"
6
+
7
+	"github.com/docker/docker/daemon/execdriver"
8
+)
9
+
10
+func (d *driver) Stats(id string) (*execdriver.ResourceStats, error) {
11
+	return nil, fmt.Errorf("Windows: Stats not implemented")
12
+}
0 13
new file mode 100644
... ...
@@ -0,0 +1,21 @@
0
+// +build windows
1
+
2
+package windows
3
+
4
+// StdConsole is for when using a container non-interactively
5
+type StdConsole struct {
6
+}
7
+
8
+func NewStdConsole() *StdConsole {
9
+	return &StdConsole{}
10
+}
11
+
12
+func (s *StdConsole) Resize(h, w int) error {
13
+	// we do not need to resize a non tty
14
+	return nil
15
+}
16
+
17
+func (s *StdConsole) Close() error {
18
+	// nothing to close here
19
+	return nil
20
+}
0 21
new file mode 100644
... ...
@@ -0,0 +1,45 @@
0
+// +build windows
1
+
2
+package windows
3
+
4
+import (
5
+	"github.com/Sirupsen/logrus"
6
+	"github.com/docker/docker/daemon/execdriver"
7
+	"github.com/microsoft/hcsshim"
8
+)
9
+
10
+func (d *driver) Terminate(p *execdriver.Command) error {
11
+	logrus.Debugf("WindowsExec: Terminate() id=%s", p.ID)
12
+	return kill(p.ID, p.ContainerPid)
13
+}
14
+
15
+func (d *driver) Kill(p *execdriver.Command, sig int) error {
16
+	logrus.Debugf("WindowsExec: Kill() id=%s sig=%d", p.ID, sig)
17
+	return kill(p.ID, p.ContainerPid)
18
+}
19
+
20
+func kill(id string, pid int) error {
21
+	logrus.Debugln("kill() ", id, pid)
22
+	var err error
23
+
24
+	// Terminate Process
25
+	if err = hcsshim.TerminateProcessInComputeSystem(id, uint32(pid)); err != nil {
26
+		logrus.Warnf("Failed to terminate pid %d in %s", id, pid, err)
27
+		// Ignore errors
28
+		err = nil
29
+	}
30
+
31
+	if terminateMode {
32
+		// Terminate the compute system
33
+		if err = hcsshim.TerminateComputeSystem(id); err != nil {
34
+			logrus.Errorf("Failed to terminate %s - %s", id, err)
35
+		}
36
+
37
+	} else {
38
+		// Shutdown the compute system
39
+		if err = hcsshim.TerminateComputeSystem(id); err != nil {
40
+			logrus.Errorf("Failed to shutdown %s - %s", id, err)
41
+		}
42
+	}
43
+	return err
44
+}
0 45
new file mode 100644
... ...
@@ -0,0 +1,31 @@
0
+// +build windows
1
+
2
+package windows
3
+
4
+import (
5
+	"github.com/microsoft/hcsshim"
6
+)
7
+
8
+// TtyConsole is for when using a container interactively
9
+type TtyConsole struct {
10
+	id        string
11
+	processid uint32
12
+}
13
+
14
+func NewTtyConsole(id string, processid uint32) *TtyConsole {
15
+	tty := &TtyConsole{
16
+		id:        id,
17
+		processid: processid,
18
+	}
19
+	return tty
20
+}
21
+
22
+func (t *TtyConsole) Resize(h, w int) error {
23
+	// TODO Windows: This is not implemented in HCS. Needs plumbing through
24
+	// along with mechanism for buffering
25
+	return hcsshim.ResizeConsoleInComputeSystem(t.id, t.processid, h, w)
26
+}
27
+
28
+func (t *TtyConsole) Close() error {
29
+	return nil
30
+}
... ...
@@ -1,23 +1,28 @@
1 1
 // +build windows
2 2
 
3
-/*
4
- This is the Windows driver for containers.
5
-
6
- TODO Windows: It is currently a placeholder to allow compilation of the
7
- daemon. Future PRs will have an implementation of this driver.
8
-*/
9
-
10 3
 package windows
11 4
 
12 5
 import (
13 6
 	"fmt"
7
+	"strings"
8
+	"sync"
14 9
 
10
+	"github.com/Sirupsen/logrus"
11
+	"github.com/docker/docker/autogen/dockerversion"
15 12
 	"github.com/docker/docker/daemon/execdriver"
13
+	"github.com/docker/docker/pkg/parsers"
16 14
 )
17 15
 
18
-const (
19
-	DriverName = "Windows"
20
-	Version    = "Placeholder"
16
+// This is a daemon development variable only and should not be
17
+// used for running production containers on Windows.
18
+var dummyMode bool
19
+
20
+// This allows the daemon to terminate containers rather than shutdown
21
+var terminateMode bool
22
+
23
+var (
24
+	DriverName = "Windows 1854"
25
+	Version    = dockerversion.VERSION + " " + dockerversion.GITCOMMIT
21 26
 )
22 27
 
23 28
 type activeContainer struct {
... ...
@@ -25,73 +30,61 @@ type activeContainer struct {
25 25
 }
26 26
 
27 27
 type driver struct {
28
-	root     string
29
-	initPath string
28
+	root             string
29
+	initPath         string
30
+	activeContainers map[string]*activeContainer
31
+	sync.Mutex
30 32
 }
31 33
 
32
-type info struct {
33
-	ID     string
34
-	driver *driver
35
-}
34
+func (d *driver) Name() string {
35
+	return fmt.Sprintf("%s %s", DriverName, Version)
36
+}
37
+
38
+func NewDriver(root, initPath string, options []string) (*driver, error) {
39
+
40
+	for _, option := range options {
41
+		key, val, err := parsers.ParseKeyValueOpt(option)
42
+		if err != nil {
43
+			return nil, err
44
+		}
45
+		key = strings.ToLower(key)
46
+		switch key {
47
+
48
+		case "dummy":
49
+			switch val {
50
+			case "1":
51
+				dummyMode = true
52
+				logrus.Warn("Using dummy mode in Windows exec driver. This is for development use only!")
53
+			}
54
+
55
+		case "terminate":
56
+			switch val {
57
+			case "1":
58
+				terminateMode = true
59
+				logrus.Warn("Using terminate mode in Windows exec driver. This is for testing purposes only.")
60
+			}
61
+
62
+		default:
63
+			return nil, fmt.Errorf("Unrecognised exec driver option %s\n", key)
64
+		}
65
+	}
36 66
 
37
-func NewDriver(root, initPath string) (*driver, error) {
38 67
 	return &driver{
39
-		root:     root,
40
-		initPath: initPath,
68
+		root:             root,
69
+		initPath:         initPath,
70
+		activeContainers: make(map[string]*activeContainer),
41 71
 	}, nil
42 72
 }
43 73
 
44
-func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (execdriver.ExitStatus, error) {
45
-	return execdriver.ExitStatus{ExitCode: 0}, nil
46
-}
47
-
48
-func (d *driver) Terminate(p *execdriver.Command) error {
49
-	return nil
50
-}
51
-
52
-func (d *driver) Kill(p *execdriver.Command, sig int) error {
53
-	return nil
54
-}
55
-
56
-func kill(ID string, PID int) error {
57
-	return nil
58
-}
59
-
60
-func (d *driver) Pause(c *execdriver.Command) error {
61
-	return fmt.Errorf("Windows: Containers cannot be paused")
62
-}
63
-
64
-func (d *driver) Unpause(c *execdriver.Command) error {
65
-	return fmt.Errorf("Windows: Containers cannot be paused")
66
-}
67
-
68
-func (i *info) IsRunning() bool {
69
-	return false
70
-}
71
-
72
-func (d *driver) Info(id string) execdriver.Info {
73
-	return &info{
74
-		ID:     id,
75
-		driver: d,
74
+// setupEnvironmentVariables convert a string array of environment variables
75
+// into a map as required by the HCS. Source array is in format [v1=k1] [v2=k2] etc.
76
+func setupEnvironmentVariables(a []string) map[string]string {
77
+	r := make(map[string]string)
78
+	for _, s := range a {
79
+		arr := strings.Split(s, "=")
80
+		if len(arr) == 2 {
81
+			r[arr[0]] = arr[1]
82
+		}
76 83
 	}
77
-}
78
-
79
-func (d *driver) Name() string {
80
-	return fmt.Sprintf("%s Date %s", DriverName, Version)
81
-}
82
-
83
-func (d *driver) GetPidsForContainer(id string) ([]int, error) {
84
-	return nil, fmt.Errorf("GetPidsForContainer: GetPidsForContainer() not implemented")
85
-}
86
-
87
-func (d *driver) Clean(id string) error {
88
-	return nil
89
-}
90
-
91
-func (d *driver) Stats(id string) (*execdriver.ResourceStats, error) {
92
-	return nil, fmt.Errorf("Windows: Stats not implemented")
93
-}
94
-
95
-func (d *driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
96
-	return 0, nil
84
+	return r
97 85
 }
... ...
@@ -12,7 +12,9 @@ clone git github.com/go-check/check 64131543e7896d5bcc6bd5a76287eb75ea96c673
12 12
 clone git github.com/gorilla/context 14f550f51a
13 13
 clone git github.com/gorilla/mux e444e69cbd
14 14
 clone git github.com/kr/pty 5cf931ef8f
15
+clone git github.com/microsoft/hcsshim 2f540b26beafc3d4aded4fc9799af261a1a91352
15 16
 clone git github.com/mistifyio/go-zfs v2.1.1
17
+clone git github.com/natefinch/npipe 0938d701e50e580f5925c773055eb6d6b32a0cbc
16 18
 clone git github.com/tchap/go-patricia v2.1.0
17 19
 clone git golang.org/x/net 3cffabab72adf04f8e3b01c5baf775361837b5fe https://github.com/golang/net.git
18 20
 clone hg code.google.com/p/gosqlite 74691fb6f837
... ...
@@ -254,6 +254,7 @@ type HostConfig struct {
254 254
 	Ulimits         []*ulimit.Ulimit
255 255
 	LogConfig       LogConfig
256 256
 	CgroupParent    string // Parent cgroup.
257
+	ConsoleSize     [2]int // Initial console size on Windows
257 258
 }
258 259
 
259 260
 func MergeConfigs(config *Config, hostConfig *HostConfig) *ContainerConfigWrapper {
260 261
new file mode 100644
... ...
@@ -0,0 +1,22 @@
0
+The MIT License (MIT)
1
+
2
+Copyright (c) 2015 Microsoft
3
+
4
+Permission is hereby granted, free of charge, to any person obtaining a copy
5
+of this software and associated documentation files (the "Software"), to deal
6
+in the Software without restriction, including without limitation the rights
7
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+copies of the Software, and to permit persons to whom the Software is
9
+furnished to do so, subject to the following conditions:
10
+
11
+The above copyright notice and this permission notice shall be included in all
12
+copies or substantial portions of the Software.
13
+
14
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+SOFTWARE.
21
+
0 22
new file mode 100644
... ...
@@ -0,0 +1,57 @@
0
+package hcsshim
1
+
2
+import (
3
+	"fmt"
4
+	"syscall"
5
+	"unsafe"
6
+
7
+	"github.com/Sirupsen/logrus"
8
+)
9
+
10
+func ActivateLayer(info DriverInfo, id string) error {
11
+	title := "hcsshim::ActivateLayer "
12
+	logrus.Debugf(title+"Flavour %s ID %s", info.Flavour, id)
13
+
14
+	// Load the DLL and get a handle to the procedure we need
15
+	dll, proc, err := loadAndFind(procActivateLayer)
16
+	if dll != nil {
17
+		defer dll.Release()
18
+	}
19
+	if err != nil {
20
+		return err
21
+	}
22
+
23
+	// Convert id to uint16 pointer for calling the procedure
24
+	idp, err := syscall.UTF16PtrFromString(id)
25
+	if err != nil {
26
+		err = fmt.Errorf(title+" - Failed conversion of id %s to pointer %s", id, err)
27
+		logrus.Error(err)
28
+		return err
29
+	}
30
+
31
+	// Convert info to API calling convention
32
+	infop, err := convertDriverInfo(info)
33
+	if err != nil {
34
+		err = fmt.Errorf(title+" - Failed conversion info struct %s", err)
35
+		logrus.Error(err)
36
+		return err
37
+	}
38
+
39
+	// Call the procedure itself.
40
+	r1, _, _ := proc.Call(
41
+		uintptr(unsafe.Pointer(&infop)),
42
+		uintptr(unsafe.Pointer(idp)))
43
+
44
+	use(unsafe.Pointer(&infop))
45
+	use(unsafe.Pointer(idp))
46
+
47
+	if r1 != 0 {
48
+		err = fmt.Errorf(title+" - Win32 API call returned error r1=%d err=%s id=%s flavour=%d",
49
+			r1, syscall.Errno(r1), id, info.Flavour)
50
+		logrus.Error(err)
51
+		return err
52
+	}
53
+
54
+	logrus.Debugf(title+" - succeeded id=%s flavour=%d", id, info.Flavour)
55
+	return nil
56
+}
0 57
new file mode 100644
... ...
@@ -0,0 +1,85 @@
0
+package hcsshim
1
+
2
+import (
3
+	"fmt"
4
+	"syscall"
5
+	"unsafe"
6
+
7
+	"github.com/Sirupsen/logrus"
8
+)
9
+
10
+func CopyLayer(info DriverInfo, srcId, dstId string, parentLayerPaths []string) error {
11
+	title := "hcsshim::CopyLayer "
12
+	logrus.Debugf(title+"srcId %s dstId", srcId, dstId)
13
+
14
+	// Load the DLL and get a handle to the procedure we need
15
+	dll, proc, err := loadAndFind(procCopyLayer)
16
+	if dll != nil {
17
+		defer dll.Release()
18
+	}
19
+	if err != nil {
20
+		return err
21
+	}
22
+
23
+	// Convert srcId to uint16 pointer for calling the procedure
24
+	srcIdp, err := syscall.UTF16PtrFromString(srcId)
25
+	if err != nil {
26
+		err = fmt.Errorf(title+" - Failed conversion of srcId %s to pointer %s", srcId, err)
27
+		logrus.Error(err)
28
+		return err
29
+	}
30
+
31
+	// Convert dstId to uint16 pointer for calling the procedure
32
+	dstIdp, err := syscall.UTF16PtrFromString(dstId)
33
+	if err != nil {
34
+		err = fmt.Errorf(title+" - Failed conversion of dstId %s to pointer %s", dstId, err)
35
+		logrus.Error(err)
36
+		return err
37
+	}
38
+
39
+	// Generate layer descriptors
40
+	layers, err := layerPathsToDescriptors(parentLayerPaths)
41
+	if err != nil {
42
+		err = fmt.Errorf(title+" - Failed to generate layer descriptors %s", err)
43
+		logrus.Error(err)
44
+		return err
45
+	}
46
+
47
+	// Convert info to API calling convention
48
+	infop, err := convertDriverInfo(info)
49
+	if err != nil {
50
+		err = fmt.Errorf(title+" - Failed conversion info struct %s", err)
51
+		logrus.Error(err)
52
+		return err
53
+	}
54
+
55
+	var layerDescriptorsp *WC_LAYER_DESCRIPTOR
56
+	if len(layers) > 0 {
57
+		layerDescriptorsp = &(layers[0])
58
+	} else {
59
+		layerDescriptorsp = nil
60
+	}
61
+
62
+	// Call the procedure itself.
63
+	r1, _, _ := proc.Call(
64
+		uintptr(unsafe.Pointer(&infop)),
65
+		uintptr(unsafe.Pointer(srcIdp)),
66
+		uintptr(unsafe.Pointer(dstIdp)),
67
+		uintptr(unsafe.Pointer(layerDescriptorsp)),
68
+		uintptr(len(layers)))
69
+
70
+	use(unsafe.Pointer(&infop))
71
+	use(unsafe.Pointer(srcIdp))
72
+	use(unsafe.Pointer(dstIdp))
73
+	use(unsafe.Pointer(layerDescriptorsp))
74
+
75
+	if r1 != 0 {
76
+		err = fmt.Errorf(title+" - Win32 API call returned error r1=%d err=%s srcId=%s dstId=%d",
77
+			r1, syscall.Errno(r1), srcId, dstId)
78
+		logrus.Error(err)
79
+		return err
80
+	}
81
+
82
+	logrus.Debugf(title+" - succeeded srcId=%s dstId=%s", srcId, dstId)
83
+	return nil
84
+}
0 85
new file mode 100644
... ...
@@ -0,0 +1,70 @@
0
+package hcsshim
1
+
2
+import (
3
+	"fmt"
4
+	"syscall"
5
+	"unsafe"
6
+
7
+	"github.com/Sirupsen/logrus"
8
+)
9
+
10
+// CreateComputeSystem creates a container
11
+func CreateComputeSystem(id string, configuration string) error {
12
+
13
+	title := "HCSShim::CreateComputeSystem"
14
+	logrus.Debugln(title+" id=%s, configuration=%s", id, configuration)
15
+
16
+	// Load the DLL and get a handle to the procedure we need
17
+	dll, proc, err := loadAndFind(procCreateComputeSystem)
18
+	if dll != nil {
19
+		defer dll.Release()
20
+	}
21
+	if err != nil {
22
+		return err
23
+	}
24
+
25
+	/*
26
+		Example configuration JSON below. RootDevicePath MUST use \\ not \ as path
27
+		separator. TODO Windows: Update this JSON sample with final API.
28
+
29
+		{
30
+		    "SystemType" : "Container",
31
+			"Name" : "ContainerName",
32
+		    "RootDevicePath" : "C:\\Containers\\test",
33
+			"IsDummy" : true
34
+		}
35
+
36
+	*/
37
+
38
+	// Convert id to uint16 pointers for calling the procedure
39
+	idp, err := syscall.UTF16PtrFromString(id)
40
+	if err != nil {
41
+		err = fmt.Errorf(title+"- Failed conversion of id %s to pointer %s", id, err)
42
+		logrus.Error(err)
43
+		return err
44
+	}
45
+
46
+	// Convert configuration to uint16 pointers for calling the procedure
47
+	configurationp, err := syscall.UTF16PtrFromString(configuration)
48
+	if err != nil {
49
+		err = fmt.Errorf(title+" - Failed conversion of configuration %s to pointer %s", configuration, err)
50
+		logrus.Error(err)
51
+		return err
52
+	}
53
+
54
+	// Call the procedure itself.
55
+	r1, _, _ := proc.Call(
56
+		uintptr(unsafe.Pointer(idp)), uintptr(unsafe.Pointer(configurationp)))
57
+
58
+	use(unsafe.Pointer(idp))
59
+	use(unsafe.Pointer(configurationp))
60
+
61
+	if r1 != 0 {
62
+		err = fmt.Errorf(title+" - Win32 API call returned error r1=%d err=%s id=%s configuration=%s", r1, syscall.Errno(r1), id, configuration)
63
+		logrus.Error(err)
64
+		return err
65
+	}
66
+
67
+	logrus.Debugf(title+"- succeeded %s", id)
68
+	return nil
69
+}
0 70
new file mode 100644
... ...
@@ -0,0 +1,67 @@
0
+package hcsshim
1
+
2
+import (
3
+	"fmt"
4
+	"syscall"
5
+	"unsafe"
6
+
7
+	"github.com/Sirupsen/logrus"
8
+)
9
+
10
+func CreateLayer(info DriverInfo, id, parent string) error {
11
+	title := "hcsshim::CreateLayer "
12
+	logrus.Debugf(title+"Flavour %s ID %s parent %s", info.Flavour, id, parent)
13
+
14
+	// Load the DLL and get a handle to the procedure we need
15
+	dll, proc, err := loadAndFind(procCreateLayer)
16
+	if dll != nil {
17
+		defer dll.Release()
18
+	}
19
+	if err != nil {
20
+		return err
21
+	}
22
+
23
+	// Convert id to uint16 pointer for calling the procedure
24
+	idp, err := syscall.UTF16PtrFromString(id)
25
+	if err != nil {
26
+		err = fmt.Errorf(title+" - Failed conversion of id %s to pointer %s", id, err)
27
+		logrus.Error(err)
28
+		return err
29
+	}
30
+
31
+	// Convert parent to uint16 pointer for calling the procedure
32
+	parentp, err := syscall.UTF16PtrFromString(parent)
33
+	if err != nil {
34
+		err = fmt.Errorf(title+" - Failed conversion of parent %s to pointer %s", parent, err)
35
+		logrus.Error(err)
36
+		return err
37
+	}
38
+
39
+	// Convert info to API calling convention
40
+	infop, err := convertDriverInfo(info)
41
+	if err != nil {
42
+		err = fmt.Errorf(title+" - Failed conversion info struct %s", parent, err)
43
+		logrus.Error(err)
44
+		return err
45
+	}
46
+
47
+	// Call the procedure itself.
48
+	r1, _, _ := proc.Call(
49
+		uintptr(unsafe.Pointer(&infop)),
50
+		uintptr(unsafe.Pointer(idp)),
51
+		uintptr(unsafe.Pointer(parentp)))
52
+
53
+	use(unsafe.Pointer(&infop))
54
+	use(unsafe.Pointer(idp))
55
+	use(unsafe.Pointer(parentp))
56
+
57
+	if r1 != 0 {
58
+		err = fmt.Errorf(title+" - Win32 API call returned error r1=%d err=%s id=%s parent=%s flavour=%d",
59
+			r1, syscall.Errno(r1), id, parent, info.Flavour)
60
+		logrus.Error(err)
61
+		return err
62
+	}
63
+
64
+	logrus.Debugf(title+" - succeeded id=%s parent=%s flavour=%d", id, parent, info.Flavour)
65
+	return nil
66
+}
0 67
new file mode 100644
... ...
@@ -0,0 +1,89 @@
0
+package hcsshim
1
+
2
+import (
3
+	"encoding/json"
4
+	"fmt"
5
+	"syscall"
6
+	"unsafe"
7
+
8
+	"github.com/Sirupsen/logrus"
9
+)
10
+
11
+// processParameters is use to both the input of CreateProcessInComputeSystem
12
+// and to convert the parameters to JSON for passing onto the HCS
13
+type CreateProcessParams struct {
14
+	ApplicationName                   string
15
+	CommandLine                       string
16
+	WorkingDirectory                  string
17
+	StdInPipe, StdOutPipe, StdErrPipe string
18
+	Environment                       map[string]string
19
+	EmulateConsole                    bool
20
+	ConsoleSize                       [2]int
21
+}
22
+
23
+// CreateProcessInComputeSystem starts a process in a container. This is invoked, for example,
24
+// as a result of docker run, docker exec, or RUN in Dockerfile. If successful,
25
+// it returns the PID of the process.
26
+func CreateProcessInComputeSystem(id string, params CreateProcessParams) (processid uint32, err error) {
27
+
28
+	title := "HCSShim::CreateProcessInComputeSystem"
29
+	logrus.Debugf(title+"id=%s params=%s", id, params)
30
+
31
+	// Load the DLL and get a handle to the procedure we need
32
+	dll, proc, err := loadAndFind(procCreateProcessInComputeSystem)
33
+	if dll != nil {
34
+		defer dll.Release()
35
+	}
36
+	if err != nil {
37
+		return 0, err
38
+	}
39
+
40
+	// Convert id to uint16 pointer for calling the procedure
41
+	idp, err := syscall.UTF16PtrFromString(id)
42
+	if err != nil {
43
+		err = fmt.Errorf(title+" - Failed conversion of id %s to pointer %s", id, err)
44
+		logrus.Error(err)
45
+		return 0, err
46
+	}
47
+
48
+	// If we are not emulating a console, ignore any console size passed to us
49
+	if !params.EmulateConsole {
50
+		params.ConsoleSize[0] = 0
51
+		params.ConsoleSize[1] = 0
52
+	}
53
+
54
+	paramsJson, err := json.Marshal(params)
55
+	if err != nil {
56
+		err = fmt.Errorf(title+" - Failed to marshall params %s %s", params, err)
57
+		return 0, err
58
+	}
59
+
60
+	// Convert paramsJson to uint16 pointer for calling the procedure
61
+	paramsJsonp, err := syscall.UTF16PtrFromString(string(paramsJson))
62
+	if err != nil {
63
+		return 0, err
64
+	}
65
+
66
+	// Get a POINTER to variable to take the pid outparm
67
+	pid := new(uint32)
68
+
69
+	logrus.Debugf(title+" - Calling the procedure itself %s %s", id, paramsJson)
70
+
71
+	// Call the procedure itself.
72
+	r1, _, _ := proc.Call(
73
+		uintptr(unsafe.Pointer(idp)),
74
+		uintptr(unsafe.Pointer(paramsJsonp)),
75
+		uintptr(unsafe.Pointer(pid)))
76
+
77
+	use(unsafe.Pointer(idp))
78
+	use(unsafe.Pointer(paramsJsonp))
79
+
80
+	if r1 != 0 {
81
+		err = fmt.Errorf(title+" - Win32 API call returned error r1=%d err=%s id=%s params=%s", r1, syscall.Errno(r1), id, params)
82
+		logrus.Error(err)
83
+		return 0, err
84
+	}
85
+
86
+	logrus.Debugf(title+" - succeeded id=%s params=%s pid=%d", id, paramsJson, *pid)
87
+	return *pid, nil
88
+}
0 89
new file mode 100644
... ...
@@ -0,0 +1,84 @@
0
+package hcsshim
1
+
2
+import (
3
+	"fmt"
4
+	"syscall"
5
+	"unsafe"
6
+
7
+	"github.com/Sirupsen/logrus"
8
+)
9
+
10
+func CreateSandboxLayer(info DriverInfo, layerId, parentId string, parentLayerPaths []string) error {
11
+	title := "hcsshim::CreateSandboxLayer "
12
+	logrus.Debugf(title+"layerId %s parentId %s", layerId, parentId)
13
+
14
+	// Load the DLL and get a handle to the procedure we need
15
+	dll, proc, err := loadAndFind(procCreateSandboxLayer)
16
+	if dll != nil {
17
+		defer dll.Release()
18
+	}
19
+	if err != nil {
20
+		return err
21
+	}
22
+
23
+	// Generate layer descriptors
24
+	layers, err := layerPathsToDescriptors(parentLayerPaths)
25
+	if err != nil {
26
+		err = fmt.Errorf(title+"- Failed to generate layer descriptors ", err)
27
+		return err
28
+	}
29
+
30
+	// Convert layerId to uint16 pointer for calling the procedure
31
+	layerIdp, err := syscall.UTF16PtrFromString(layerId)
32
+	if err != nil {
33
+		err = fmt.Errorf(title+"- Failed conversion of layerId %s to pointer %s", layerId, err)
34
+		logrus.Error(err)
35
+		return err
36
+	}
37
+
38
+	// Convert parentId to uint16 pointer for calling the procedure
39
+	parentIdp, err := syscall.UTF16PtrFromString(parentId)
40
+	if err != nil {
41
+		err = fmt.Errorf(title+"- Failed conversion of parentId %s to pointer %s", parentId, err)
42
+		logrus.Error(err)
43
+		return err
44
+	}
45
+
46
+	// Convert info to API calling convention
47
+	infop, err := convertDriverInfo(info)
48
+	if err != nil {
49
+		err = fmt.Errorf(title+"- Failed conversion info struct %s", err)
50
+		logrus.Error(err)
51
+		return err
52
+	}
53
+
54
+	var layerDescriptorsp *WC_LAYER_DESCRIPTOR
55
+	if len(layers) > 0 {
56
+		layerDescriptorsp = &(layers[0])
57
+	} else {
58
+		layerDescriptorsp = nil
59
+	}
60
+
61
+	// Call the procedure itself.
62
+	r1, _, _ := proc.Call(
63
+		uintptr(unsafe.Pointer(&infop)),
64
+		uintptr(unsafe.Pointer(layerIdp)),
65
+		uintptr(unsafe.Pointer(parentIdp)),
66
+		uintptr(unsafe.Pointer(layerDescriptorsp)),
67
+		uintptr(len(layers)))
68
+
69
+	use(unsafe.Pointer(&infop))
70
+	use(unsafe.Pointer(layerIdp))
71
+	use(unsafe.Pointer(parentIdp))
72
+	use(unsafe.Pointer(layerDescriptorsp))
73
+
74
+	if r1 != 0 {
75
+		err = fmt.Errorf(title+"- Win32 API call returned error r1=%d err=%s layerId=%s parentId=%s",
76
+			r1, syscall.Errno(r1), layerId, parentId)
77
+		logrus.Error(err)
78
+		return err
79
+	}
80
+
81
+	logrus.Debugf(title+"- succeeded layerId=%s parentId=%s", layerId, parentId)
82
+	return nil
83
+}
0 84
new file mode 100644
... ...
@@ -0,0 +1,57 @@
0
+package hcsshim
1
+
2
+import (
3
+	"fmt"
4
+	"syscall"
5
+	"unsafe"
6
+
7
+	"github.com/Sirupsen/logrus"
8
+)
9
+
10
+func DeactivateLayer(info DriverInfo, id string) error {
11
+	title := "hcsshim::DeactivateLayer "
12
+	logrus.Debugf(title+"Flavour %s ID %s", info.Flavour, id)
13
+
14
+	// Load the DLL and get a handle to the procedure we need
15
+	dll, proc, err := loadAndFind(procDeactivateLayer)
16
+	if dll != nil {
17
+		defer dll.Release()
18
+	}
19
+	if err != nil {
20
+		return err
21
+	}
22
+
23
+	// Convert id to uint16 pointer for calling the procedure
24
+	idp, err := syscall.UTF16PtrFromString(id)
25
+	if err != nil {
26
+		err = fmt.Errorf(title+" - Failed conversion of id %s to pointer %s", id, err)
27
+		logrus.Error(err)
28
+		return err
29
+	}
30
+
31
+	// Convert info to API calling convention
32
+	infop, err := convertDriverInfo(info)
33
+	if err != nil {
34
+		err = fmt.Errorf(title+" - Failed conversion info struct %s", err)
35
+		logrus.Error(err)
36
+		return err
37
+	}
38
+
39
+	// Call the procedure itself.
40
+	r1, _, _ := proc.Call(
41
+		uintptr(unsafe.Pointer(&infop)),
42
+		uintptr(unsafe.Pointer(idp)))
43
+
44
+	use(unsafe.Pointer(&infop))
45
+	use(unsafe.Pointer(idp))
46
+
47
+	if r1 != 0 {
48
+		err = fmt.Errorf(title+" - Win32 API call returned error r1=%d err=%s id=%s flavour=%d",
49
+			r1, syscall.Errno(r1), id, info.Flavour)
50
+		logrus.Error(err)
51
+		return err
52
+	}
53
+
54
+	logrus.Debugf(title+" - succeeded id=%s flavour=%d", id, info.Flavour)
55
+	return nil
56
+}
0 57
new file mode 100644
... ...
@@ -0,0 +1,57 @@
0
+package hcsshim
1
+
2
+import (
3
+	"fmt"
4
+	"syscall"
5
+	"unsafe"
6
+
7
+	"github.com/Sirupsen/logrus"
8
+)
9
+
10
+func DestroyLayer(info DriverInfo, id string) error {
11
+	title := "hcsshim::DestroyLayer "
12
+	logrus.Debugf(title+"Flavour %s ID %s", info.Flavour, id)
13
+
14
+	// Load the DLL and get a handle to the procedure we need
15
+	dll, proc, err := loadAndFind(procDestroyLayer)
16
+	if dll != nil {
17
+		defer dll.Release()
18
+	}
19
+	if err != nil {
20
+		return err
21
+	}
22
+
23
+	// Convert id to uint16 pointer for calling the procedure
24
+	idp, err := syscall.UTF16PtrFromString(id)
25
+	if err != nil {
26
+		err = fmt.Errorf(title+" - Failed conversion of id %s to pointer %s", id, err)
27
+		logrus.Error(err)
28
+		return err
29
+	}
30
+
31
+	// Convert info to API calling convention
32
+	infop, err := convertDriverInfo(info)
33
+	if err != nil {
34
+		err = fmt.Errorf(title+" - Failed conversion info struct %s", err)
35
+		logrus.Error(err)
36
+		return err
37
+	}
38
+
39
+	// Call the procedure itself.
40
+	r1, _, _ := proc.Call(
41
+		uintptr(unsafe.Pointer(&infop)),
42
+		uintptr(unsafe.Pointer(idp)))
43
+
44
+	use(unsafe.Pointer(&infop))
45
+	use(unsafe.Pointer(idp))
46
+
47
+	if r1 != 0 {
48
+		err = fmt.Errorf(title+" - Win32 API call returned error r1=%d err=%s id=%s flavour=%d",
49
+			r1, syscall.Errno(r1), id, info.Flavour)
50
+		logrus.Error(err)
51
+		return err
52
+	}
53
+
54
+	logrus.Debugf(title+" - succeeded id=%s flavour=%d", id, info.Flavour)
55
+	return nil
56
+}
0 57
new file mode 100644
... ...
@@ -0,0 +1,83 @@
0
+package hcsshim
1
+
2
+import (
3
+	"fmt"
4
+	"syscall"
5
+	"unsafe"
6
+
7
+	"github.com/Sirupsen/logrus"
8
+)
9
+
10
+func ExportLayer(info DriverInfo, layerId string, exportFolderPath string, parentLayerPaths []string) error {
11
+	title := "hcsshim::ExportLayer "
12
+	logrus.Debugf(title+"flavour %d layerId %s folder %s", info.Flavour, layerId, exportFolderPath)
13
+
14
+	// Load the DLL and get a handle to the procedure we need
15
+	dll, proc, err := loadAndFind(procExportLayer)
16
+	if dll != nil {
17
+		defer dll.Release()
18
+	}
19
+	if err != nil {
20
+		return err
21
+	}
22
+
23
+	// Generate layer descriptors
24
+	layers, err := layerPathsToDescriptors(parentLayerPaths)
25
+	if err != nil {
26
+		err = fmt.Errorf(title+"- Failed to generate layer descriptors ", err)
27
+		return err
28
+	}
29
+
30
+	// Convert layerId to uint16 pointer for calling the procedure
31
+	layerIdp, err := syscall.UTF16PtrFromString(layerId)
32
+	if err != nil {
33
+		err = fmt.Errorf(title+"- Failed conversion of layerId %s to pointer %s", layerId, err)
34
+		logrus.Error(err)
35
+		return err
36
+	}
37
+
38
+	// Convert exportFolderPath to uint16 pointer for calling the procedure
39
+	exportFolderPathp, err := syscall.UTF16PtrFromString(exportFolderPath)
40
+	if err != nil {
41
+		err = fmt.Errorf(title+"- Failed conversion of exportFolderPath %s to pointer %s", exportFolderPath, err)
42
+		logrus.Error(err)
43
+		return err
44
+	}
45
+
46
+	// Convert info to API calling convention
47
+	infop, err := convertDriverInfo(info)
48
+	if err != nil {
49
+		err = fmt.Errorf(title+"- Failed conversion info struct %s", err)
50
+		logrus.Error(err)
51
+		return err
52
+	}
53
+
54
+	var layerDescriptorsp *WC_LAYER_DESCRIPTOR
55
+	if len(layers) > 0 {
56
+		layerDescriptorsp = &(layers[0])
57
+	} else {
58
+		layerDescriptorsp = nil
59
+	}
60
+
61
+	// Call the procedure itself.
62
+	r1, _, _ := proc.Call(
63
+		uintptr(unsafe.Pointer(&infop)),
64
+		uintptr(unsafe.Pointer(layerIdp)),
65
+		uintptr(unsafe.Pointer(exportFolderPathp)),
66
+		uintptr(unsafe.Pointer(layerDescriptorsp)),
67
+		uintptr(len(layers)))
68
+	use(unsafe.Pointer(&infop))
69
+	use(unsafe.Pointer(layerIdp))
70
+	use(unsafe.Pointer(exportFolderPathp))
71
+	use(unsafe.Pointer(layerDescriptorsp))
72
+
73
+	if r1 != 0 {
74
+		err = fmt.Errorf(title+"- Win32 API call returned error r1=%d err=%s layerId=%s flavour=%d folder=%s",
75
+			r1, syscall.Errno(r1), layerId, info.Flavour, exportFolderPath)
76
+		logrus.Error(err)
77
+		return err
78
+	}
79
+
80
+	logrus.Debugf(title+"- succeeded layerId=%s flavour=%d folder=%s", layerId, info.Flavour, exportFolderPath)
81
+	return nil
82
+}
0 83
new file mode 100644
... ...
@@ -0,0 +1,87 @@
0
+package hcsshim
1
+
2
+import (
3
+	"fmt"
4
+	"syscall"
5
+	"unsafe"
6
+
7
+	"github.com/Sirupsen/logrus"
8
+)
9
+
10
+func GetLayerMountPath(info DriverInfo, id string) (string, error) {
11
+	title := "hcsshim::GetLayerMountPath "
12
+	logrus.Debugf(title+"Flavour %s ID %s", info.Flavour, id)
13
+
14
+	// Load the DLL and get a handle to the procedure we need
15
+	dll, proc, err := loadAndFind(procGetLayerMountPath)
16
+	if dll != nil {
17
+		defer dll.Release()
18
+	}
19
+	if err != nil {
20
+		return "", err
21
+	}
22
+
23
+	// Convert id to uint16 pointer for calling the procedure
24
+	idp, err := syscall.UTF16PtrFromString(id)
25
+	if err != nil {
26
+		err = fmt.Errorf(title+" - Failed conversion of id %s to pointer %s", id, err)
27
+		logrus.Error(err)
28
+		return "", err
29
+	}
30
+
31
+	// Convert info to API calling convention
32
+	infop, err := convertDriverInfo(info)
33
+	if err != nil {
34
+		err = fmt.Errorf(title+" - Failed conversion info struct %s", err)
35
+		logrus.Error(err)
36
+		return "", err
37
+	}
38
+
39
+	var mountPathLength uint64
40
+	mountPathLength = 0
41
+
42
+	// Call the procedure itself.
43
+	logrus.Debugf("Calling proc (1)")
44
+	r1, _, _ := proc.Call(
45
+		uintptr(unsafe.Pointer(&infop)),
46
+		uintptr(unsafe.Pointer(idp)),
47
+		uintptr(unsafe.Pointer(&mountPathLength)),
48
+		uintptr(unsafe.Pointer(nil)))
49
+
50
+	if r1 != 0 {
51
+		err = fmt.Errorf(title+" - First Win32 API call returned error r1=%d err=%s id=%s flavour=%d",
52
+			r1, syscall.Errno(r1), id, info.Flavour)
53
+		logrus.Error(err)
54
+		return "", err
55
+	}
56
+
57
+	// Allocate a mount path of the returned length.
58
+	if mountPathLength == 0 {
59
+		return "", nil
60
+	}
61
+	mountPathp := make([]uint16, mountPathLength)
62
+	mountPathp[0] = 0
63
+
64
+	// Call the procedure again
65
+	logrus.Debugf("Calling proc (2)")
66
+	r1, _, _ = proc.Call(
67
+		uintptr(unsafe.Pointer(&infop)),
68
+		uintptr(unsafe.Pointer(idp)),
69
+		uintptr(unsafe.Pointer(&mountPathLength)),
70
+		uintptr(unsafe.Pointer(&mountPathp[0])))
71
+
72
+	use(unsafe.Pointer(&mountPathLength))
73
+	use(unsafe.Pointer(&infop))
74
+	use(unsafe.Pointer(idp))
75
+
76
+	if r1 != 0 {
77
+		err = fmt.Errorf(title+" - Second Win32 API call returned error r1=%d errno=%d id=%s flavour=%d",
78
+			r1, syscall.Errno(r1), id, info.Flavour)
79
+		logrus.Error(err)
80
+		return "", err
81
+	}
82
+
83
+	path := syscall.UTF16ToString(mountPathp[0:])
84
+	logrus.Debugf(title+" - succeeded id=%s flavour=%d path=%s", id, info.Flavour, path)
85
+	return path, nil
86
+}
0 87
new file mode 100644
... ...
@@ -0,0 +1,19 @@
0
+package hcsshim
1
+
2
+import (
3
+	"crypto/sha1"
4
+	"fmt"
5
+)
6
+
7
+type GUID [16]byte
8
+
9
+func NewGUID(source string) *GUID {
10
+	h := sha1.Sum([]byte(source))
11
+	var g GUID
12
+	copy(g[0:], h[0:16])
13
+	return &g
14
+}
15
+
16
+func (g *GUID) ToString() string {
17
+	return fmt.Sprintf("%x-%x-%x-%x-%x", g[0:4], g[4:6], g[6:8], g[8:10], g[10:])
18
+}
0 19
new file mode 100644
... ...
@@ -0,0 +1,80 @@
0
+// Shim for the Host Compute Service (HSC) to manage Windows Server
1
+// containers and Hyper-V containers.
2
+
3
+package hcsshim
4
+
5
+import (
6
+	"fmt"
7
+	"syscall"
8
+	"unsafe"
9
+
10
+	"github.com/Sirupsen/logrus"
11
+)
12
+
13
+const (
14
+	// Name of the shim DLL for access to the HCS
15
+	shimDLLName = "vmcompute.dll"
16
+
17
+	// Container related functions in the shim DLL
18
+	procCreateComputeSystem             = "CreateComputeSystem"
19
+	procStartComputeSystem              = "StartComputeSystem"
20
+	procCreateProcessInComputeSystem    = "CreateProcessInComputeSystem"
21
+	procWaitForProcessInComputeSystem   = "WaitForProcessInComputeSystem"
22
+	procShutdownComputeSystem           = "ShutdownComputeSystem"
23
+	procTerminateComputeSystem          = "TerminateComputeSystem"
24
+	procTerminateProcessInComputeSystem = "TerminateProcessInComputeSystem"
25
+	procResizeConsoleInComputeSystem    = "ResizeConsoleInComputeSystem"
26
+
27
+	// Storage related functions in the shim DLL
28
+	procLayerExists        = "LayerExists"
29
+	procCreateLayer        = "CreateLayer"
30
+	procDestroyLayer       = "DestroyLayer"
31
+	procActivateLayer      = "ActivateLayer"
32
+	procDeactivateLayer    = "DeactivateLayer"
33
+	procGetLayerMountPath  = "GetLayerMountPath"
34
+	procCopyLayer          = "CopyLayer"
35
+	procCreateSandboxLayer = "CreateSandboxLayer"
36
+	procPrepareLayer       = "PrepareLayer"
37
+	procUnprepareLayer     = "UnprepareLayer"
38
+	procExportLayer        = "ExportLayer"
39
+	procImportLayer        = "ImportLayer"
40
+)
41
+
42
+// loadAndFind finds a procedure in the DLL. Note we do NOT do lazy loading as
43
+// go is particularly unfriendly in the case of a mismatch. By that - it panics
44
+// if a function can't be found. By explicitly loading, we can control error
45
+// handling gracefully without the daemon terminating.
46
+func loadAndFind(procedure string) (dll *syscall.DLL, proc *syscall.Proc, err error) {
47
+
48
+	logrus.Debugf("hcsshim::loadAndFind %s", procedure)
49
+
50
+	dll, err = syscall.LoadDLL(shimDLLName)
51
+	if err != nil {
52
+		err = fmt.Errorf("Failed to load %s - error %s", shimDLLName, err)
53
+		logrus.Error(err)
54
+		return nil, nil, err
55
+	}
56
+
57
+	proc, err = dll.FindProc(procedure)
58
+	if err != nil {
59
+		err = fmt.Errorf("Failed to find %s in %s", procedure, shimDLLName)
60
+		logrus.Error(err)
61
+		return nil, nil, err
62
+	}
63
+
64
+	return dll, proc, nil
65
+}
66
+
67
+// use is a no-op, but the compiler cannot see that it is.
68
+// Calling use(p) ensures that p is kept live until that point.
69
+/*
70
+//go:noescape
71
+func use(p unsafe.Pointer)
72
+*/
73
+
74
+// Alternate without using //go:noescape and asm.s
75
+var temp unsafe.Pointer
76
+
77
+func use(p unsafe.Pointer) {
78
+	temp = p
79
+}
0 80
new file mode 100644
... ...
@@ -0,0 +1,83 @@
0
+package hcsshim
1
+
2
+import (
3
+	"fmt"
4
+	"syscall"
5
+	"unsafe"
6
+
7
+	"github.com/Sirupsen/logrus"
8
+)
9
+
10
+func ImportLayer(info DriverInfo, layerId string, importFolderPath string, parentLayerPaths []string) error {
11
+	title := "hcsshim::ImportLayer "
12
+	logrus.Debugf(title+"flavour %d layerId %s folder %s", info.Flavour, layerId, importFolderPath)
13
+
14
+	// Load the DLL and get a handle to the procedure we need
15
+	dll, proc, err := loadAndFind(procImportLayer)
16
+	if dll != nil {
17
+		defer dll.Release()
18
+	}
19
+	if err != nil {
20
+		return err
21
+	}
22
+
23
+	// Generate layer descriptors
24
+	layers, err := layerPathsToDescriptors(parentLayerPaths)
25
+	if err != nil {
26
+		err = fmt.Errorf(title+"- Failed to generate layer descriptors ", err)
27
+		return err
28
+	}
29
+
30
+	// Convert layerId to uint16 pointer for calling the procedure
31
+	layerIdp, err := syscall.UTF16PtrFromString(layerId)
32
+	if err != nil {
33
+		err = fmt.Errorf(title+"- Failed conversion of layerId %s to pointer %s", layerId, err)
34
+		logrus.Error(err)
35
+		return err
36
+	}
37
+
38
+	// Convert importFolderPath to uint16 pointer for calling the procedure
39
+	importFolderPathp, err := syscall.UTF16PtrFromString(importFolderPath)
40
+	if err != nil {
41
+		err = fmt.Errorf(title+"- Failed conversion of importFolderPath %s to pointer %s", importFolderPath, err)
42
+		logrus.Error(err)
43
+		return err
44
+	}
45
+
46
+	// Convert info to API calling convention
47
+	infop, err := convertDriverInfo(info)
48
+	if err != nil {
49
+		err = fmt.Errorf(title+"- Failed conversion info struct %s", err)
50
+		logrus.Error(err)
51
+		return err
52
+	}
53
+
54
+	var layerDescriptorsp *WC_LAYER_DESCRIPTOR
55
+	if len(layers) > 0 {
56
+		layerDescriptorsp = &(layers[0])
57
+	} else {
58
+		layerDescriptorsp = nil
59
+	}
60
+
61
+	// Call the procedure itself.
62
+	r1, _, _ := proc.Call(
63
+		uintptr(unsafe.Pointer(&infop)),
64
+		uintptr(unsafe.Pointer(layerIdp)),
65
+		uintptr(unsafe.Pointer(importFolderPathp)),
66
+		uintptr(unsafe.Pointer(layerDescriptorsp)),
67
+		uintptr(len(layers)))
68
+	use(unsafe.Pointer(&infop))
69
+	use(unsafe.Pointer(layerIdp))
70
+	use(unsafe.Pointer(importFolderPathp))
71
+	use(unsafe.Pointer(layerDescriptorsp))
72
+
73
+	if r1 != 0 {
74
+		err = fmt.Errorf(title+"- Win32 API call returned error r1=%d err=%s layerId=%s flavour=%d folder=%s",
75
+			r1, syscall.Errno(r1), layerId, info.Flavour, importFolderPath)
76
+		logrus.Error(err)
77
+		return err
78
+	}
79
+
80
+	logrus.Debugf(title+"- succeeded layerId=%s flavour=%d folder=%s", layerId, info.Flavour, importFolderPath)
81
+	return nil
82
+}
0 83
new file mode 100644
... ...
@@ -0,0 +1,60 @@
0
+package hcsshim
1
+
2
+import (
3
+	"fmt"
4
+	"syscall"
5
+	"unsafe"
6
+
7
+	"github.com/Sirupsen/logrus"
8
+)
9
+
10
+func LayerExists(info DriverInfo, id string) (bool, error) {
11
+	title := "hcsshim::LayerExists "
12
+	logrus.Debugf(title+"Flavour %s ID %s", info.Flavour, id)
13
+
14
+	// Load the DLL and get a handle to the procedure we need
15
+	dll, proc, err := loadAndFind(procLayerExists)
16
+	if dll != nil {
17
+		defer dll.Release()
18
+	}
19
+	if err != nil {
20
+		return false, err
21
+	}
22
+
23
+	// Convert id to uint16 pointer for calling the procedure
24
+	idp, err := syscall.UTF16PtrFromString(id)
25
+	if err != nil {
26
+		err = fmt.Errorf(title+" - Failed conversion of id %s to pointer %s", id, err)
27
+		logrus.Error(err)
28
+		return false, err
29
+	}
30
+
31
+	// Convert info to API calling convention
32
+	infop, err := convertDriverInfo(info)
33
+	if err != nil {
34
+		err = fmt.Errorf(title+" - Failed conversion info struct %s", err)
35
+		logrus.Error(err)
36
+		return false, err
37
+	}
38
+
39
+	// Call the procedure itself.
40
+	var exists bool // Outparam from Win32
41
+
42
+	r1, _, _ := proc.Call(
43
+		uintptr(unsafe.Pointer(&infop)),
44
+		uintptr(unsafe.Pointer(idp)),
45
+		uintptr(unsafe.Pointer(&exists)))
46
+
47
+	use(unsafe.Pointer(&infop))
48
+	use(unsafe.Pointer(idp))
49
+
50
+	if r1 != 0 {
51
+		err = fmt.Errorf(title+" - Win32 API call returned error r1=%d err=%s id=%s flavour=%d",
52
+			r1, syscall.Errno(r1), id, info.Flavour)
53
+		logrus.Error(err)
54
+		return false, err
55
+	}
56
+
57
+	logrus.Debugf(title+" - succeeded id=%s flavour=%d exists=%d", id, info.Flavour, exists)
58
+	return exists, nil
59
+}
0 60
new file mode 100644
... ...
@@ -0,0 +1,105 @@
0
+package hcsshim
1
+
2
+// This file contains utility functions to support storage (graph) related
3
+// functionality.
4
+
5
+import (
6
+	"syscall"
7
+
8
+	"github.com/Sirupsen/logrus"
9
+)
10
+
11
+/* To pass into syscall, we need a struct matching the following:
12
+enum GraphDriverType
13
+{
14
+    DiffDriver,
15
+    FilterDriver
16
+};
17
+
18
+struct DriverInfo {
19
+    GraphDriverType Flavour;
20
+    LPCWSTR HomeDir;
21
+};
22
+*/
23
+type DriverInfo struct {
24
+	Flavour int
25
+	HomeDir string
26
+}
27
+
28
+type driverInfo struct {
29
+	Flavour  int
30
+	HomeDirp *uint16
31
+}
32
+
33
+func convertDriverInfo(info DriverInfo) (driverInfo, error) {
34
+	homedirp, err := syscall.UTF16PtrFromString(info.HomeDir)
35
+	if err != nil {
36
+		logrus.Debugf("Failed conversion of home to pointer for driver info: %s", err.Error())
37
+		return driverInfo{}, err
38
+	}
39
+
40
+	return driverInfo{
41
+		Flavour:  info.Flavour,
42
+		HomeDirp: homedirp,
43
+	}, nil
44
+}
45
+
46
+/* To pass into syscall, we need a struct matching the following:
47
+typedef struct _WC_LAYER_DESCRIPTOR {
48
+
49
+    //
50
+    // The ID of the layer
51
+    //
52
+
53
+    GUID LayerId;
54
+
55
+    //
56
+    // Additional flags
57
+    //
58
+
59
+    union {
60
+        struct {
61
+            ULONG Reserved : 31;
62
+            ULONG Dirty : 1;    // Created from sandbox as a result of snapshot
63
+        };
64
+        ULONG Value;
65
+    } Flags;
66
+
67
+    //
68
+    // Path to the layer root directory, null-terminated
69
+    //
70
+
71
+    PCWSTR Path;
72
+
73
+} WC_LAYER_DESCRIPTOR, *PWC_LAYER_DESCRIPTOR;
74
+*/
75
+type WC_LAYER_DESCRIPTOR struct {
76
+	LayerId GUID
77
+	Flags   uint32
78
+	Pathp   *uint16
79
+}
80
+
81
+func layerPathsToDescriptors(parentLayerPaths []string) ([]WC_LAYER_DESCRIPTOR, error) {
82
+	// Array of descriptors that gets constructed.
83
+	var layers []WC_LAYER_DESCRIPTOR
84
+
85
+	for i := 0; i < len(parentLayerPaths); i++ {
86
+		// Create a layer descriptor, using the folder path
87
+		// as the source for a GUID LayerId
88
+		g := NewGUID(parentLayerPaths[i])
89
+
90
+		p, err := syscall.UTF16PtrFromString(parentLayerPaths[i])
91
+		if err != nil {
92
+			logrus.Debugf("Failed conversion of parentLayerPath to pointer %s", err)
93
+			return nil, err
94
+		}
95
+
96
+		layers = append(layers, WC_LAYER_DESCRIPTOR{
97
+			LayerId: *g,
98
+			Flags:   0,
99
+			Pathp:   p,
100
+		})
101
+	}
102
+
103
+	return layers, nil
104
+}
0 105
new file mode 100644
... ...
@@ -0,0 +1,73 @@
0
+package hcsshim
1
+
2
+import (
3
+	"fmt"
4
+	"syscall"
5
+	"unsafe"
6
+
7
+	"github.com/Sirupsen/logrus"
8
+)
9
+
10
+func PrepareLayer(info DriverInfo, layerId string, parentLayerPaths []string) error {
11
+	title := "hcsshim::PrepareLayer "
12
+	logrus.Debugf(title+"flavour %d layerId %s", info.Flavour, layerId)
13
+
14
+	// Load the DLL and get a handle to the procedure we need
15
+	dll, proc, err := loadAndFind(procPrepareLayer)
16
+	if dll != nil {
17
+		defer dll.Release()
18
+	}
19
+	if err != nil {
20
+		return err
21
+	}
22
+
23
+	// Generate layer descriptors
24
+	layers, err := layerPathsToDescriptors(parentLayerPaths)
25
+	if err != nil {
26
+		err = fmt.Errorf(title+"- Failed to generate layer descriptors ", err)
27
+		return err
28
+	}
29
+
30
+	// Convert layerId to uint16 pointer for calling the procedure
31
+	layerIdp, err := syscall.UTF16PtrFromString(layerId)
32
+	if err != nil {
33
+		err = fmt.Errorf(title+"- Failed conversion of layerId %s to pointer %s", layerId, err)
34
+		logrus.Error(err)
35
+		return err
36
+	}
37
+
38
+	// Convert info to API calling convention
39
+	infop, err := convertDriverInfo(info)
40
+	if err != nil {
41
+		err = fmt.Errorf(title+"- Failed conversion info struct %s", err)
42
+		logrus.Error(err)
43
+		return err
44
+	}
45
+
46
+	var layerDescriptorsp *WC_LAYER_DESCRIPTOR
47
+	if len(layers) > 0 {
48
+		layerDescriptorsp = &(layers[0])
49
+	} else {
50
+		layerDescriptorsp = nil
51
+	}
52
+
53
+	// Call the procedure itself.
54
+	r1, _, _ := proc.Call(
55
+		uintptr(unsafe.Pointer(&infop)),
56
+		uintptr(unsafe.Pointer(layerIdp)),
57
+		uintptr(unsafe.Pointer(layerDescriptorsp)),
58
+		uintptr(len(layers)))
59
+	use(unsafe.Pointer(&infop))
60
+	use(unsafe.Pointer(layerIdp))
61
+	use(unsafe.Pointer(layerDescriptorsp))
62
+
63
+	if r1 != 0 {
64
+		err = fmt.Errorf(title+"- Win32 API call returned error r1=%d err=%s layerId=%s flavour=%d",
65
+			r1, syscall.Errno(r1), layerId, info.Flavour)
66
+		logrus.Error(err)
67
+		return err
68
+	}
69
+
70
+	logrus.Debugf(title+"- succeeded layerId=%s flavour=%d", layerId, info.Flavour)
71
+	return nil
72
+}
0 73
new file mode 100644
... ...
@@ -0,0 +1,46 @@
0
+package hcsshim
1
+
2
+import (
3
+	"fmt"
4
+	"syscall"
5
+	"unsafe"
6
+
7
+	"github.com/Sirupsen/logrus"
8
+)
9
+
10
+func ResizeConsoleInComputeSystem(id string, processid uint32, h, w int) error {
11
+
12
+	title := "HCSShim::ResizeConsoleInComputeSystem"
13
+	logrus.Debugf(title+" id=%s processid=%d (%d,%d)", id, processid, h, w)
14
+
15
+	// Load the DLL and get a handle to the procedure we need
16
+	dll, proc, err := loadAndFind(procResizeConsoleInComputeSystem)
17
+	if dll != nil {
18
+		defer dll.Release()
19
+	}
20
+	if err != nil {
21
+		return err
22
+	}
23
+
24
+	// Convert id to uint16 pointer for calling the procedure
25
+	idp, err := syscall.UTF16PtrFromString(id)
26
+	if err != nil {
27
+		err = fmt.Errorf(title+" - Failed conversion of id %s to pointer %s", id, err)
28
+		logrus.Error(err)
29
+		return err
30
+	}
31
+
32
+	h16 := uint16(h)
33
+	w16 := uint16(w)
34
+
35
+	r1, _, _ := proc.Call(uintptr(unsafe.Pointer(idp)), uintptr(processid), uintptr(h16), uintptr(w16), uintptr(0))
36
+	if r1 != 0 {
37
+		err = fmt.Errorf(title+" - Win32 API call returned error r1=%d err=%s, id=%s pid=%d", r1, syscall.Errno(r1), id, processid)
38
+		logrus.Error(err)
39
+		return err
40
+	}
41
+
42
+	logrus.Debugf(title+" - succeeded id=%s processid=%d (%d,%d)", id, processid, h, w)
43
+	return nil
44
+
45
+}
0 46
new file mode 100644
... ...
@@ -0,0 +1,49 @@
0
+package hcsshim
1
+
2
+import (
3
+	"fmt"
4
+	"syscall"
5
+	"unsafe"
6
+
7
+	"github.com/Sirupsen/logrus"
8
+)
9
+
10
+// ShutdownComputeSystem shuts down a container
11
+func ShutdownComputeSystem(id string) error {
12
+
13
+	var title = "HCSShim::ShutdownComputeSystem"
14
+	logrus.Debugf(title+" id=%s", id)
15
+
16
+	// Load the DLL and get a handle to the procedure we need
17
+	dll, proc, err := loadAndFind(procShutdownComputeSystem)
18
+	if dll != nil {
19
+		defer dll.Release()
20
+	}
21
+	if err != nil {
22
+		return err
23
+	}
24
+
25
+	// Convert id to uint16 pointers for calling the procedure
26
+	idp, err := syscall.UTF16PtrFromString(id)
27
+	if err != nil {
28
+		err = fmt.Errorf(title+" - Failed conversion of id %s to pointer %s", id, err)
29
+		logrus.Error(err)
30
+		return err
31
+	}
32
+
33
+	timeout := uint32(0xffffffff)
34
+
35
+	// Call the procedure itself.
36
+	r1, _, err := proc.Call(
37
+		uintptr(unsafe.Pointer(idp)), uintptr(timeout))
38
+
39
+	use(unsafe.Pointer(idp))
40
+
41
+	if r1 != 0 {
42
+		err = fmt.Errorf(title+" - Win32 API call returned error r1=%d err=%s id=%s", r1, syscall.Errno(r1), id)
43
+		return syscall.Errno(r1)
44
+	}
45
+
46
+	logrus.Debugf(title+" - succeeded id=%s", id)
47
+	return nil
48
+}
0 49
new file mode 100644
... ...
@@ -0,0 +1,47 @@
0
+package hcsshim
1
+
2
+import (
3
+	"fmt"
4
+	"syscall"
5
+	"unsafe"
6
+
7
+	"github.com/Sirupsen/logrus"
8
+)
9
+
10
+// StartComputeSystem starts a container
11
+func StartComputeSystem(id string) error {
12
+
13
+	title := "HCSShim::StartComputeSystem"
14
+	logrus.Debugf(title+" id=%s", id)
15
+
16
+	// Load the DLL and get a handle to the procedure we need
17
+	dll, proc, err := loadAndFind(procStartComputeSystem)
18
+	if dll != nil {
19
+		defer dll.Release()
20
+	}
21
+	if err != nil {
22
+		return err
23
+	}
24
+
25
+	// Convert ID to uint16 pointers for calling the procedure
26
+	idp, err := syscall.UTF16PtrFromString(id)
27
+	if err != nil {
28
+		err = fmt.Errorf(title+" - Failed conversion of id %s to pointer %s", id, err)
29
+		logrus.Error(err)
30
+		return err
31
+	}
32
+
33
+	// Call the procedure itself.
34
+	r1, _, _ := proc.Call(uintptr(unsafe.Pointer(idp)))
35
+
36
+	use(unsafe.Pointer(idp))
37
+
38
+	if r1 != 0 {
39
+		err = fmt.Errorf(title+" - Win32 API call returned error r1=%d err=%s id=%s", r1, syscall.Errno(r1), id)
40
+		logrus.Error(err)
41
+		return err
42
+	}
43
+
44
+	logrus.Debugf("HCSShim::StartComputeSystem - succeeded id=%s", id)
45
+	return nil
46
+}
0 47
new file mode 100644
... ...
@@ -0,0 +1,49 @@
0
+package hcsshim
1
+
2
+import (
3
+	"fmt"
4
+	"syscall"
5
+	"unsafe"
6
+
7
+	"github.com/Sirupsen/logrus"
8
+)
9
+
10
+// TerminateComputeSystem force terminates a container
11
+func TerminateComputeSystem(id string) error {
12
+
13
+	var title = "HCSShim::TerminateComputeSystem"
14
+	logrus.Debugf(title+" id=%s", id)
15
+
16
+	// Load the DLL and get a handle to the procedure we need
17
+	dll, proc, err := loadAndFind(procTerminateComputeSystem)
18
+	if dll != nil {
19
+		defer dll.Release()
20
+	}
21
+	if err != nil {
22
+		return err
23
+	}
24
+
25
+	// Convert id to uint16 pointers for calling the procedure
26
+	idp, err := syscall.UTF16PtrFromString(id)
27
+	if err != nil {
28
+		err = fmt.Errorf(title+" - Failed conversion of id %s to pointer %s", id, err)
29
+		logrus.Error(err)
30
+		return err
31
+	}
32
+
33
+	timeout := uint32(0xffffffff)
34
+
35
+	// Call the procedure itself.
36
+	r1, _, err := proc.Call(
37
+		uintptr(unsafe.Pointer(idp)), uintptr(timeout))
38
+
39
+	use(unsafe.Pointer(idp))
40
+
41
+	if r1 != 0 {
42
+		err = fmt.Errorf(title+" - Win32 API call returned error r1=%d err=%s id=%s", r1, syscall.Errno(r1), id)
43
+		return syscall.Errno(r1)
44
+	}
45
+
46
+	logrus.Debugf(title+" - succeeded id=%s", id)
47
+	return nil
48
+}
0 49
new file mode 100644
... ...
@@ -0,0 +1,49 @@
0
+package hcsshim
1
+
2
+import (
3
+	"fmt"
4
+	"syscall"
5
+	"unsafe"
6
+
7
+	"github.com/Sirupsen/logrus"
8
+)
9
+
10
+// TerminateProcessInComputeSystem kills a process in a running container
11
+func TerminateProcessInComputeSystem(id string, processid uint32) (err error) {
12
+
13
+	title := "HCSShim::TerminateProcessInComputeSystem"
14
+	logrus.Debugf(title+" id=%s processid=%d", id, processid)
15
+
16
+	// Load the DLL and get a handle to the procedure we need
17
+	dll, proc, err := loadAndFind(procTerminateProcessInComputeSystem)
18
+	if dll != nil {
19
+		defer dll.Release()
20
+	}
21
+	if err != nil {
22
+		return err
23
+	}
24
+
25
+	// Convert ID to uint16 pointer for calling the procedure
26
+	idp, err := syscall.UTF16PtrFromString(id)
27
+	if err != nil {
28
+		err = fmt.Errorf(title+" - Failed conversion of id %s to pointer %s", id, err)
29
+		logrus.Error(err)
30
+		return err
31
+	}
32
+
33
+	// Call the procedure itself.
34
+	r1, _, err := proc.Call(
35
+		uintptr(unsafe.Pointer(idp)),
36
+		uintptr(processid))
37
+
38
+	use(unsafe.Pointer(idp))
39
+
40
+	if r1 != 0 {
41
+		err = fmt.Errorf(title+" - Win32 API call returned error r1=%d err=%s id=%s", r1, syscall.Errno(r1), id)
42
+		logrus.Error(err)
43
+		return err
44
+	}
45
+
46
+	logrus.Debugf(title+" - succeeded id=%s", id)
47
+	return nil
48
+}
0 49
new file mode 100644
... ...
@@ -0,0 +1,57 @@
0
+package hcsshim
1
+
2
+import (
3
+	"fmt"
4
+	"syscall"
5
+	"unsafe"
6
+
7
+	"github.com/Sirupsen/logrus"
8
+)
9
+
10
+func UnprepareLayer(info DriverInfo, layerId string) error {
11
+	title := "hcsshim::UnprepareLayer "
12
+	logrus.Debugf(title+"flavour %d layerId %s", info.Flavour, layerId)
13
+
14
+	// Load the DLL and get a handle to the procedure we need
15
+	dll, proc, err := loadAndFind(procUnprepareLayer)
16
+	if dll != nil {
17
+		defer dll.Release()
18
+	}
19
+	if err != nil {
20
+		return err
21
+	}
22
+
23
+	// Convert layerId to uint16 pointer for calling the procedure
24
+	layerIdp, err := syscall.UTF16PtrFromString(layerId)
25
+	if err != nil {
26
+		err = fmt.Errorf(title+"- Failed conversion of layerId %s to pointer %s", layerId, err)
27
+		logrus.Error(err)
28
+		return err
29
+	}
30
+
31
+	// Convert info to API calling convention
32
+	infop, err := convertDriverInfo(info)
33
+	if err != nil {
34
+		err = fmt.Errorf(title+"- Failed conversion info struct %s", err)
35
+		logrus.Error(err)
36
+		return err
37
+	}
38
+
39
+	// Call the procedure itself.
40
+	r1, _, _ := proc.Call(
41
+		uintptr(unsafe.Pointer(&infop)),
42
+		uintptr(unsafe.Pointer(layerIdp)))
43
+
44
+	use(unsafe.Pointer(&infop))
45
+	use(unsafe.Pointer(layerIdp))
46
+
47
+	if r1 != 0 {
48
+		err = fmt.Errorf(title+"- Win32 API call returned error r1=%d err=%s layerId=%s flavour=%d",
49
+			r1, syscall.Errno(r1), layerId, info.Flavour)
50
+		logrus.Error(err)
51
+		return err
52
+	}
53
+
54
+	logrus.Debugf(title+"- succeeded layerId=%s flavour=%d", layerId, info.Flavour)
55
+	return nil
56
+}
0 57
new file mode 100644
... ...
@@ -0,0 +1,56 @@
0
+package hcsshim
1
+
2
+import (
3
+	"fmt"
4
+	"syscall"
5
+	"unsafe"
6
+
7
+	"github.com/Sirupsen/logrus"
8
+)
9
+
10
+// WaitForProcessInComputeSystem waits for a process ID to terminate and returns
11
+// the exit code.
12
+func WaitForProcessInComputeSystem(id string, processid uint32) (exitcode int32, err error) {
13
+
14
+	title := "HCSShim::WaitForProcessInComputeSystem"
15
+	logrus.Debugf(title+" id=%s processid=%d", id, processid)
16
+
17
+	var timeout uint32 = 0xFFFFFFFF // (-1/INFINITE)
18
+
19
+	// Load the DLL and get a handle to the procedure we need
20
+	dll, proc, err := loadAndFind(procWaitForProcessInComputeSystem)
21
+	if dll != nil {
22
+		defer dll.Release()
23
+	}
24
+	if err != nil {
25
+		return 0, err
26
+	}
27
+
28
+	// Convert id to uint16 pointer for calling the procedure
29
+	idp, err := syscall.UTF16PtrFromString(id)
30
+	if err != nil {
31
+		err = fmt.Errorf(title+" - Failed conversion of id %s to pointer %s", id, err)
32
+		logrus.Error(err)
33
+		return 0, err
34
+	}
35
+
36
+	// To get a POINTER to the ExitCode
37
+	ec := new(int32)
38
+
39
+	// Call the procedure itself.
40
+	r1, _, err := proc.Call(
41
+		uintptr(unsafe.Pointer(idp)),
42
+		uintptr(processid),
43
+		uintptr(timeout),
44
+		uintptr(unsafe.Pointer(ec)))
45
+
46
+	use(unsafe.Pointer(idp))
47
+
48
+	if r1 != 0 {
49
+		err = fmt.Errorf(title+" - Win32 API call returned error r1=%d err=%s id=%s", r1, syscall.Errno(r1), id)
50
+		return 0, err
51
+	}
52
+
53
+	logrus.Debugf(title+" - succeeded id=%s processid=%d exitcode=%d", id, processid, *ec)
54
+	return *ec, nil
55
+}
0 56
new file mode 100644
... ...
@@ -0,0 +1,22 @@
0
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
1
+*.o
2
+*.a
3
+*.so
4
+
5
+# Folders
6
+_obj
7
+_test
8
+
9
+# Architecture specific extensions/prefixes
10
+*.[568vq]
11
+[568vq].out
12
+
13
+*.cgo1.go
14
+*.cgo2.c
15
+_cgo_defun.c
16
+_cgo_gotypes.go
17
+_cgo_export.*
18
+
19
+_testmain.go
20
+
21
+*.exe
0 22
new file mode 100644
... ...
@@ -0,0 +1,8 @@
0
+The MIT License (MIT)
1
+Copyright (c) 2013 npipe authors
2
+
3
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
0 8
\ No newline at end of file
1 9
new file mode 100644
... ...
@@ -0,0 +1,308 @@
0
+npipe  [![Build status](https://ci.appveyor.com/api/projects/status/00vuepirsot29qwi)](https://ci.appveyor.com/project/natefinch/npipe) [![GoDoc](https://godoc.org/gopkg.in/natefinch/npipe.v2?status.svg)](https://godoc.org/gopkg.in/natefinch/npipe.v2)
1
+=====
2
+Package npipe provides a pure Go wrapper around Windows named pipes.
3
+
4
+Windows named pipe documentation: http://msdn.microsoft.com/en-us/library/windows/desktop/aa365780
5
+
6
+Note that the code lives at https://github.com/natefinch/npipe (v2 branch)
7
+but should be imported as gopkg.in/natefinch/npipe.v2 (the package name is
8
+still npipe).
9
+
10
+npipe provides an interface based on stdlib's net package, with Dial, Listen,
11
+and Accept functions, as well as associated implementations of net.Conn and
12
+net.Listener.  It supports rpc over the connection.
13
+
14
+### Notes
15
+* Deadlines for reading/writing to the connection are only functional in Windows Vista/Server 2008 and above, due to limitations with the Windows API.
16
+
17
+* The pipes support byte mode only (no support for message mode)
18
+
19
+### Examples
20
+The Dial function connects a client to a named pipe:
21
+
22
+
23
+	conn, err := npipe.Dial(`\\.\pipe\mypipename`)
24
+	if err != nil {
25
+		<handle error>
26
+	}
27
+	fmt.Fprintf(conn, "Hi server!\n")
28
+	msg, err := bufio.NewReader(conn).ReadString('\n')
29
+	...
30
+
31
+The Listen function creates servers:
32
+
33
+
34
+	ln, err := npipe.Listen(`\\.\pipe\mypipename`)
35
+	if err != nil {
36
+		// handle error
37
+	}
38
+	for {
39
+		conn, err := ln.Accept()
40
+		if err != nil {
41
+			// handle error
42
+			continue
43
+		}
44
+		go handleConnection(conn)
45
+	}
46
+
47
+
48
+
49
+
50
+
51
+## Variables
52
+``` go
53
+var ErrClosed = PipeError{"Pipe has been closed.", false}
54
+```
55
+ErrClosed is the error returned by PipeListener.Accept when Close is called
56
+on the PipeListener.
57
+
58
+
59
+
60
+## type PipeAddr
61
+``` go
62
+type PipeAddr string
63
+```
64
+PipeAddr represents the address of a named pipe.
65
+
66
+
67
+
68
+
69
+
70
+
71
+
72
+
73
+
74
+
75
+
76
+### func (PipeAddr) Network
77
+``` go
78
+func (a PipeAddr) Network() string
79
+```
80
+Network returns the address's network name, "pipe".
81
+
82
+
83
+
84
+### func (PipeAddr) String
85
+``` go
86
+func (a PipeAddr) String() string
87
+```
88
+String returns the address of the pipe
89
+
90
+
91
+
92
+## type PipeConn
93
+``` go
94
+type PipeConn struct {
95
+    // contains filtered or unexported fields
96
+}
97
+```
98
+PipeConn is the implementation of the net.Conn interface for named pipe connections.
99
+
100
+
101
+
102
+
103
+
104
+
105
+
106
+
107
+
108
+### func Dial
109
+``` go
110
+func Dial(address string) (*PipeConn, error)
111
+```
112
+Dial connects to a named pipe with the given address. If the specified pipe is not available,
113
+it will wait indefinitely for the pipe to become available.
114
+
115
+The address must be of the form \\.\\pipe\<name> for local pipes and \\<computer>\pipe\<name>
116
+for remote pipes.
117
+
118
+Dial will return a PipeError if you pass in a badly formatted pipe name.
119
+
120
+Examples:
121
+
122
+
123
+	// local pipe
124
+	conn, err := Dial(`\\.\pipe\mypipename`)
125
+	
126
+	// remote pipe
127
+	conn, err := Dial(`\\othercomp\pipe\mypipename`)
128
+
129
+
130
+### func DialTimeout
131
+``` go
132
+func DialTimeout(address string, timeout time.Duration) (*PipeConn, error)
133
+```
134
+DialTimeout acts like Dial, but will time out after the duration of timeout
135
+
136
+
137
+
138
+
139
+### func (\*PipeConn) Close
140
+``` go
141
+func (c *PipeConn) Close() error
142
+```
143
+Close closes the connection.
144
+
145
+
146
+
147
+### func (\*PipeConn) LocalAddr
148
+``` go
149
+func (c *PipeConn) LocalAddr() net.Addr
150
+```
151
+LocalAddr returns the local network address.
152
+
153
+
154
+
155
+### func (\*PipeConn) Read
156
+``` go
157
+func (c *PipeConn) Read(b []byte) (int, error)
158
+```
159
+Read implements the net.Conn Read method.
160
+
161
+
162
+
163
+### func (\*PipeConn) RemoteAddr
164
+``` go
165
+func (c *PipeConn) RemoteAddr() net.Addr
166
+```
167
+RemoteAddr returns the remote network address.
168
+
169
+
170
+
171
+### func (\*PipeConn) SetDeadline
172
+``` go
173
+func (c *PipeConn) SetDeadline(t time.Time) error
174
+```
175
+SetDeadline implements the net.Conn SetDeadline method.
176
+Note that timeouts are only supported on Windows Vista/Server 2008 and above
177
+
178
+
179
+
180
+### func (\*PipeConn) SetReadDeadline
181
+``` go
182
+func (c *PipeConn) SetReadDeadline(t time.Time) error
183
+```
184
+SetReadDeadline implements the net.Conn SetReadDeadline method.
185
+Note that timeouts are only supported on Windows Vista/Server 2008 and above
186
+
187
+
188
+
189
+### func (\*PipeConn) SetWriteDeadline
190
+``` go
191
+func (c *PipeConn) SetWriteDeadline(t time.Time) error
192
+```
193
+SetWriteDeadline implements the net.Conn SetWriteDeadline method.
194
+Note that timeouts are only supported on Windows Vista/Server 2008 and above
195
+
196
+
197
+
198
+### func (\*PipeConn) Write
199
+``` go
200
+func (c *PipeConn) Write(b []byte) (int, error)
201
+```
202
+Write implements the net.Conn Write method.
203
+
204
+
205
+
206
+## type PipeError
207
+``` go
208
+type PipeError struct {
209
+    // contains filtered or unexported fields
210
+}
211
+```
212
+PipeError is an error related to a call to a pipe
213
+
214
+
215
+
216
+
217
+
218
+
219
+
220
+
221
+
222
+
223
+
224
+### func (PipeError) Error
225
+``` go
226
+func (e PipeError) Error() string
227
+```
228
+Error implements the error interface
229
+
230
+
231
+
232
+### func (PipeError) Temporary
233
+``` go
234
+func (e PipeError) Temporary() bool
235
+```
236
+Temporary implements net.AddrError.Temporary()
237
+
238
+
239
+
240
+### func (PipeError) Timeout
241
+``` go
242
+func (e PipeError) Timeout() bool
243
+```
244
+Timeout implements net.AddrError.Timeout()
245
+
246
+
247
+
248
+## type PipeListener
249
+``` go
250
+type PipeListener struct {
251
+    // contains filtered or unexported fields
252
+}
253
+```
254
+PipeListener is a named pipe listener. Clients should typically
255
+use variables of type net.Listener instead of assuming named pipe.
256
+
257
+
258
+
259
+
260
+
261
+
262
+
263
+
264
+
265
+### func Listen
266
+``` go
267
+func Listen(address string) (*PipeListener, error)
268
+```
269
+Listen returns a new PipeListener that will listen on a pipe with the given
270
+address. The address must be of the form \\.\pipe\<name>
271
+
272
+Listen will return a PipeError for an incorrectly formatted pipe name.
273
+
274
+
275
+
276
+
277
+### func (\*PipeListener) Accept
278
+``` go
279
+func (l *PipeListener) Accept() (net.Conn, error)
280
+```
281
+Accept implements the Accept method in the net.Listener interface; it
282
+waits for the next call and returns a generic net.Conn.
283
+
284
+
285
+
286
+### func (\*PipeListener) AcceptPipe
287
+``` go
288
+func (l *PipeListener) AcceptPipe() (*PipeConn, error)
289
+```
290
+AcceptPipe accepts the next incoming call and returns the new connection.
291
+
292
+
293
+
294
+### func (\*PipeListener) Addr
295
+``` go
296
+func (l *PipeListener) Addr() net.Addr
297
+```
298
+Addr returns the listener's network address, a PipeAddr.
299
+
300
+
301
+
302
+### func (\*PipeListener) Close
303
+``` go
304
+func (l *PipeListener) Close() error
305
+```
306
+Close stops listening on the address.
307
+Already Accepted connections are not closed.
0 308
new file mode 100644
... ...
@@ -0,0 +1,50 @@
0
+// Copyright 2013 Nate Finch. All rights reserved.
1
+// Use of this source code is governed by an MIT-style
2
+// license that can be found in the LICENSE file.
3
+
4
+// Package npipe provides a pure Go wrapper around Windows named pipes.
5
+//
6
+// !! Note, this package is Windows-only.  There is no code to compile on linux.
7
+//
8
+// Windows named pipe documentation: http://msdn.microsoft.com/en-us/library/windows/desktop/aa365780
9
+//
10
+// Note that the code lives at https://github.com/natefinch/npipe (v2 branch)
11
+// but should be imported as gopkg.in/natefinch/npipe.v2 (the package name is
12
+// still npipe).
13
+//
14
+// npipe provides an interface based on stdlib's net package, with Dial, Listen,
15
+// and Accept functions, as well as associated implementations of net.Conn and
16
+// net.Listener.  It supports rpc over the connection.
17
+//
18
+// Notes
19
+//
20
+// * Deadlines for reading/writing to the connection are only functional in Windows Vista/Server 2008 and above, due to limitations with the Windows API.
21
+//
22
+// * The pipes support byte mode only (no support for message mode)
23
+//
24
+// Examples
25
+//
26
+// The Dial function connects a client to a named pipe:
27
+//   conn, err := npipe.Dial(`\\.\pipe\mypipename`)
28
+//   if err != nil {
29
+//   	<handle error>
30
+//   }
31
+//   fmt.Fprintf(conn, "Hi server!\n")
32
+//   msg, err := bufio.NewReader(conn).ReadString('\n')
33
+//   ...
34
+//
35
+// The Listen function creates servers:
36
+//
37
+//   ln, err := npipe.Listen(`\\.\pipe\mypipename`)
38
+//   if err != nil {
39
+//   	// handle error
40
+//   }
41
+//   for {
42
+//   	conn, err := ln.Accept()
43
+//   	if err != nil {
44
+//   		// handle error
45
+//   		continue
46
+//   	}
47
+//   	go handleConnection(conn)
48
+//   }
49
+package npipe
0 50
new file mode 100755
... ...
@@ -0,0 +1,518 @@
0
+package npipe
1
+
2
+//sys createNamedPipe(name *uint16, openMode uint32, pipeMode uint32, maxInstances uint32, outBufSize uint32, inBufSize uint32, defaultTimeout uint32, sa *syscall.SecurityAttributes) (handle syscall.Handle, err error)  [failretval==syscall.InvalidHandle] = CreateNamedPipeW
3
+//sys connectNamedPipe(handle syscall.Handle, overlapped *syscall.Overlapped) (err error) = ConnectNamedPipe
4
+//sys disconnectNamedPipe(handle syscall.Handle) (err error) = DisconnectNamedPipe
5
+//sys waitNamedPipe(name *uint16, timeout uint32) (err error) = WaitNamedPipeW
6
+//sys createEvent(sa *syscall.SecurityAttributes, manualReset bool, initialState bool, name *uint16) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateEventW
7
+//sys getOverlappedResult(handle syscall.Handle, overlapped *syscall.Overlapped, transferred *uint32, wait bool) (err error) = GetOverlappedResult
8
+//sys cancelIoEx(handle syscall.Handle, overlapped *syscall.Overlapped) (err error) = CancelIoEx
9
+
10
+import (
11
+	"fmt"
12
+	"io"
13
+	"net"
14
+	"sync"
15
+	"syscall"
16
+	"time"
17
+)
18
+
19
+const (
20
+	// openMode
21
+	pipe_access_duplex   = 0x3
22
+	pipe_access_inbound  = 0x1
23
+	pipe_access_outbound = 0x2
24
+
25
+	// openMode write flags
26
+	file_flag_first_pipe_instance = 0x00080000
27
+	file_flag_write_through       = 0x80000000
28
+	file_flag_overlapped          = 0x40000000
29
+
30
+	// openMode ACL flags
31
+	write_dac              = 0x00040000
32
+	write_owner            = 0x00080000
33
+	access_system_security = 0x01000000
34
+
35
+	// pipeMode
36
+	pipe_type_byte    = 0x0
37
+	pipe_type_message = 0x4
38
+
39
+	// pipeMode read mode flags
40
+	pipe_readmode_byte    = 0x0
41
+	pipe_readmode_message = 0x2
42
+
43
+	// pipeMode wait mode flags
44
+	pipe_wait   = 0x0
45
+	pipe_nowait = 0x1
46
+
47
+	// pipeMode remote-client mode flags
48
+	pipe_accept_remote_clients = 0x0
49
+	pipe_reject_remote_clients = 0x8
50
+
51
+	pipe_unlimited_instances = 255
52
+
53
+	nmpwait_wait_forever = 0xFFFFFFFF
54
+
55
+	// the two not-an-errors below occur if a client connects to the pipe between
56
+	// the server's CreateNamedPipe and ConnectNamedPipe calls.
57
+	error_no_data        syscall.Errno = 0xE8
58
+	error_pipe_connected syscall.Errno = 0x217
59
+	error_pipe_busy      syscall.Errno = 0xE7
60
+	error_sem_timeout    syscall.Errno = 0x79
61
+
62
+	error_bad_pathname syscall.Errno = 0xA1
63
+	error_invalid_name syscall.Errno = 0x7B
64
+
65
+	error_io_incomplete syscall.Errno = 0x3e4
66
+)
67
+
68
+var _ net.Conn = (*PipeConn)(nil)
69
+var _ net.Listener = (*PipeListener)(nil)
70
+
71
+// ErrClosed is the error returned by PipeListener.Accept when Close is called
72
+// on the PipeListener.
73
+var ErrClosed = PipeError{"Pipe has been closed.", false}
74
+
75
+// PipeError is an error related to a call to a pipe
76
+type PipeError struct {
77
+	msg     string
78
+	timeout bool
79
+}
80
+
81
+// Error implements the error interface
82
+func (e PipeError) Error() string {
83
+	return e.msg
84
+}
85
+
86
+// Timeout implements net.AddrError.Timeout()
87
+func (e PipeError) Timeout() bool {
88
+	return e.timeout
89
+}
90
+
91
+// Temporary implements net.AddrError.Temporary()
92
+func (e PipeError) Temporary() bool {
93
+	return false
94
+}
95
+
96
+// Dial connects to a named pipe with the given address. If the specified pipe is not available,
97
+// it will wait indefinitely for the pipe to become available.
98
+//
99
+// The address must be of the form \\.\\pipe\<name> for local pipes and \\<computer>\pipe\<name>
100
+// for remote pipes.
101
+//
102
+// Dial will return a PipeError if you pass in a badly formatted pipe name.
103
+//
104
+// Examples:
105
+//   // local pipe
106
+//   conn, err := Dial(`\\.\pipe\mypipename`)
107
+//
108
+//   // remote pipe
109
+//   conn, err := Dial(`\\othercomp\pipe\mypipename`)
110
+func Dial(address string) (*PipeConn, error) {
111
+	for {
112
+		conn, err := dial(address, nmpwait_wait_forever)
113
+		if err == nil {
114
+			return conn, nil
115
+		}
116
+		if isPipeNotReady(err) {
117
+			<-time.After(100 * time.Millisecond)
118
+			continue
119
+		}
120
+		return nil, err
121
+	}
122
+}
123
+
124
+// DialTimeout acts like Dial, but will time out after the duration of timeout
125
+func DialTimeout(address string, timeout time.Duration) (*PipeConn, error) {
126
+	deadline := time.Now().Add(timeout)
127
+
128
+	now := time.Now()
129
+	for now.Before(deadline) {
130
+		millis := uint32(deadline.Sub(now) / time.Millisecond)
131
+		conn, err := dial(address, millis)
132
+		if err == nil {
133
+			return conn, nil
134
+		}
135
+		if err == error_sem_timeout {
136
+			// This is WaitNamedPipe's timeout error, so we know we're done
137
+			return nil, PipeError{fmt.Sprintf(
138
+				"Timed out waiting for pipe '%s' to come available", address), true}
139
+		}
140
+		if isPipeNotReady(err) {
141
+			left := deadline.Sub(time.Now())
142
+			retry := 100 * time.Millisecond
143
+			if left > retry {
144
+				<-time.After(retry)
145
+			} else {
146
+				<-time.After(left - time.Millisecond)
147
+			}
148
+			now = time.Now()
149
+			continue
150
+		}
151
+		return nil, err
152
+	}
153
+	return nil, PipeError{fmt.Sprintf(
154
+		"Timed out waiting for pipe '%s' to come available", address), true}
155
+}
156
+
157
+// isPipeNotReady checks the error to see if it indicates the pipe is not ready
158
+func isPipeNotReady(err error) bool {
159
+	// Pipe Busy means another client just grabbed the open pipe end,
160
+	// and the server hasn't made a new one yet.
161
+	// File Not Found means the server hasn't created the pipe yet.
162
+	// Neither is a fatal error.
163
+
164
+	return err == syscall.ERROR_FILE_NOT_FOUND || err == error_pipe_busy
165
+}
166
+
167
+// newOverlapped creates a structure used to track asynchronous
168
+// I/O requests that have been issued.
169
+func newOverlapped() (*syscall.Overlapped, error) {
170
+	event, err := createEvent(nil, true, true, nil)
171
+	if err != nil {
172
+		return nil, err
173
+	}
174
+	return &syscall.Overlapped{HEvent: event}, nil
175
+}
176
+
177
+// waitForCompletion waits for an asynchronous I/O request referred to by overlapped to complete.
178
+// This function returns the number of bytes transferred by the operation and an error code if
179
+// applicable (nil otherwise).
180
+func waitForCompletion(handle syscall.Handle, overlapped *syscall.Overlapped) (uint32, error) {
181
+	_, err := syscall.WaitForSingleObject(overlapped.HEvent, syscall.INFINITE)
182
+	if err != nil {
183
+		return 0, err
184
+	}
185
+	var transferred uint32
186
+	err = getOverlappedResult(handle, overlapped, &transferred, true)
187
+	return transferred, err
188
+}
189
+
190
+// dial is a helper to initiate a connection to a named pipe that has been started by a server.
191
+// The timeout is only enforced if the pipe server has already created the pipe, otherwise
192
+// this function will return immediately.
193
+func dial(address string, timeout uint32) (*PipeConn, error) {
194
+	name, err := syscall.UTF16PtrFromString(string(address))
195
+	if err != nil {
196
+		return nil, err
197
+	}
198
+	// If at least one instance of the pipe has been created, this function
199
+	// will wait timeout milliseconds for it to become available.
200
+	// It will return immediately regardless of timeout, if no instances
201
+	// of the named pipe have been created yet.
202
+	// If this returns with no error, there is a pipe available.
203
+	if err := waitNamedPipe(name, timeout); err != nil {
204
+		if err == error_bad_pathname {
205
+			// badly formatted pipe name
206
+			return nil, badAddr(address)
207
+		}
208
+		return nil, err
209
+	}
210
+	pathp, err := syscall.UTF16PtrFromString(address)
211
+	if err != nil {
212
+		return nil, err
213
+	}
214
+	handle, err := syscall.CreateFile(pathp, syscall.GENERIC_READ|syscall.GENERIC_WRITE,
215
+		uint32(syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE), nil, syscall.OPEN_EXISTING,
216
+		syscall.FILE_FLAG_OVERLAPPED, 0)
217
+	if err != nil {
218
+		return nil, err
219
+	}
220
+	return &PipeConn{handle: handle, addr: PipeAddr(address)}, nil
221
+}
222
+
223
+// Listen returns a new PipeListener that will listen on a pipe with the given
224
+// address. The address must be of the form \\.\pipe\<name>
225
+//
226
+// Listen will return a PipeError for an incorrectly formatted pipe name.
227
+func Listen(address string) (*PipeListener, error) {
228
+	handle, err := createPipe(address, true)
229
+	if err == error_invalid_name {
230
+		return nil, badAddr(address)
231
+	}
232
+	if err != nil {
233
+		return nil, err
234
+	}
235
+	return &PipeListener{
236
+		addr:   PipeAddr(address),
237
+		handle: handle,
238
+	}, nil
239
+}
240
+
241
+// PipeListener is a named pipe listener. Clients should typically
242
+// use variables of type net.Listener instead of assuming named pipe.
243
+type PipeListener struct {
244
+	addr   PipeAddr
245
+	handle syscall.Handle
246
+	closed bool
247
+
248
+	// acceptHandle contains the current handle waiting for
249
+	// an incoming connection or nil.
250
+	acceptHandle syscall.Handle
251
+	// acceptOverlapped is set before waiting on a connection.
252
+	// If not waiting, it is nil.
253
+	acceptOverlapped *syscall.Overlapped
254
+	// acceptMutex protects the handle and overlapped structure.
255
+	acceptMutex sync.Mutex
256
+}
257
+
258
+// Accept implements the Accept method in the net.Listener interface; it
259
+// waits for the next call and returns a generic net.Conn.
260
+func (l *PipeListener) Accept() (net.Conn, error) {
261
+	c, err := l.AcceptPipe()
262
+	for err == error_no_data {
263
+		// Ignore clients that connect and immediately disconnect.
264
+		c, err = l.AcceptPipe()
265
+	}
266
+	if err != nil {
267
+		return nil, err
268
+	}
269
+	return c, nil
270
+}
271
+
272
+// AcceptPipe accepts the next incoming call and returns the new connection.
273
+// It might return an error if a client connected and immediately cancelled
274
+// the connection.
275
+func (l *PipeListener) AcceptPipe() (*PipeConn, error) {
276
+	if l == nil || l.addr == "" || l.closed {
277
+		return nil, syscall.EINVAL
278
+	}
279
+
280
+	// the first time we call accept, the handle will have been created by the Listen
281
+	// call. This is to prevent race conditions where the client thinks the server
282
+	// isn't listening because it hasn't actually called create yet. After the first time, we'll
283
+	// have to create a new handle each time
284
+	handle := l.handle
285
+	if handle == 0 {
286
+		var err error
287
+		handle, err = createPipe(string(l.addr), false)
288
+		if err != nil {
289
+			return nil, err
290
+		}
291
+	} else {
292
+		l.handle = 0
293
+	}
294
+
295
+	overlapped, err := newOverlapped()
296
+	if err != nil {
297
+		return nil, err
298
+	}
299
+	defer syscall.CloseHandle(overlapped.HEvent)
300
+	if err := connectNamedPipe(handle, overlapped); err != nil && err != error_pipe_connected {
301
+		if err == error_io_incomplete || err == syscall.ERROR_IO_PENDING {
302
+			l.acceptMutex.Lock()
303
+			l.acceptOverlapped = overlapped
304
+			l.acceptHandle = handle
305
+			l.acceptMutex.Unlock()
306
+			defer func() {
307
+				l.acceptMutex.Lock()
308
+				l.acceptOverlapped = nil
309
+				l.acceptHandle = 0
310
+				l.acceptMutex.Unlock()
311
+			}()
312
+
313
+			_, err = waitForCompletion(handle, overlapped)
314
+		}
315
+		if err == syscall.ERROR_OPERATION_ABORTED {
316
+			// Return error compatible to net.Listener.Accept() in case the
317
+			// listener was closed.
318
+			return nil, ErrClosed
319
+		}
320
+		if err != nil {
321
+			return nil, err
322
+		}
323
+	}
324
+	return &PipeConn{handle: handle, addr: l.addr}, nil
325
+}
326
+
327
+// Close stops listening on the address.
328
+// Already Accepted connections are not closed.
329
+func (l *PipeListener) Close() error {
330
+	if l.closed {
331
+		return nil
332
+	}
333
+	l.closed = true
334
+	if l.handle != 0 {
335
+		err := disconnectNamedPipe(l.handle)
336
+		if err != nil {
337
+			return err
338
+		}
339
+		err = syscall.CloseHandle(l.handle)
340
+		if err != nil {
341
+			return err
342
+		}
343
+		l.handle = 0
344
+	}
345
+	l.acceptMutex.Lock()
346
+	defer l.acceptMutex.Unlock()
347
+	if l.acceptOverlapped != nil && l.acceptHandle != 0 {
348
+		// Cancel the pending IO. This call does not block, so it is safe
349
+		// to hold onto the mutex above.
350
+		if err := cancelIoEx(l.acceptHandle, l.acceptOverlapped); err != nil {
351
+			return err
352
+		}
353
+		err := syscall.CloseHandle(l.acceptOverlapped.HEvent)
354
+		if err != nil {
355
+			return err
356
+		}
357
+		l.acceptOverlapped.HEvent = 0
358
+		err = syscall.CloseHandle(l.acceptHandle)
359
+		if err != nil {
360
+			return err
361
+		}
362
+		l.acceptHandle = 0
363
+	}
364
+	return nil
365
+}
366
+
367
+// Addr returns the listener's network address, a PipeAddr.
368
+func (l *PipeListener) Addr() net.Addr { return l.addr }
369
+
370
+// PipeConn is the implementation of the net.Conn interface for named pipe connections.
371
+type PipeConn struct {
372
+	handle syscall.Handle
373
+	addr   PipeAddr
374
+
375
+	// these aren't actually used yet
376
+	readDeadline  *time.Time
377
+	writeDeadline *time.Time
378
+}
379
+
380
+type iodata struct {
381
+	n   uint32
382
+	err error
383
+}
384
+
385
+// completeRequest looks at iodata to see if a request is pending. If so, it waits for it to either complete or to
386
+// abort due to hitting the specified deadline. Deadline may be set to nil to wait forever. If no request is pending,
387
+// the content of iodata is returned.
388
+func (c *PipeConn) completeRequest(data iodata, deadline *time.Time, overlapped *syscall.Overlapped) (int, error) {
389
+	if data.err == error_io_incomplete || data.err == syscall.ERROR_IO_PENDING {
390
+		var timer <-chan time.Time
391
+		if deadline != nil {
392
+			if timeDiff := deadline.Sub(time.Now()); timeDiff > 0 {
393
+				timer = time.After(timeDiff)
394
+			}
395
+		}
396
+		done := make(chan iodata)
397
+		go func() {
398
+			n, err := waitForCompletion(c.handle, overlapped)
399
+			done <- iodata{n, err}
400
+		}()
401
+		select {
402
+		case data = <-done:
403
+		case <-timer:
404
+			syscall.CancelIoEx(c.handle, overlapped)
405
+			data = iodata{0, timeout(c.addr.String())}
406
+		}
407
+	}
408
+	// Windows will produce ERROR_BROKEN_PIPE upon closing
409
+	// a handle on the other end of a connection. Go RPC
410
+	// expects an io.EOF error in this case.
411
+	if data.err == syscall.ERROR_BROKEN_PIPE {
412
+		data.err = io.EOF
413
+	}
414
+	return int(data.n), data.err
415
+}
416
+
417
+// Read implements the net.Conn Read method.
418
+func (c *PipeConn) Read(b []byte) (int, error) {
419
+	// Use ReadFile() rather than Read() because the latter
420
+	// contains a workaround that eats ERROR_BROKEN_PIPE.
421
+	overlapped, err := newOverlapped()
422
+	if err != nil {
423
+		return 0, err
424
+	}
425
+	defer syscall.CloseHandle(overlapped.HEvent)
426
+	var n uint32
427
+	err = syscall.ReadFile(c.handle, b, &n, overlapped)
428
+	return c.completeRequest(iodata{n, err}, c.readDeadline, overlapped)
429
+}
430
+
431
+// Write implements the net.Conn Write method.
432
+func (c *PipeConn) Write(b []byte) (int, error) {
433
+	overlapped, err := newOverlapped()
434
+	if err != nil {
435
+		return 0, err
436
+	}
437
+	defer syscall.CloseHandle(overlapped.HEvent)
438
+	var n uint32
439
+	err = syscall.WriteFile(c.handle, b, &n, overlapped)
440
+	return c.completeRequest(iodata{n, err}, c.writeDeadline, overlapped)
441
+}
442
+
443
+// Close closes the connection.
444
+func (c *PipeConn) Close() error {
445
+	return syscall.CloseHandle(c.handle)
446
+}
447
+
448
+// LocalAddr returns the local network address.
449
+func (c *PipeConn) LocalAddr() net.Addr {
450
+	return c.addr
451
+}
452
+
453
+// RemoteAddr returns the remote network address.
454
+func (c *PipeConn) RemoteAddr() net.Addr {
455
+	// not sure what to do here, we don't have remote addr....
456
+	return c.addr
457
+}
458
+
459
+// SetDeadline implements the net.Conn SetDeadline method.
460
+// Note that timeouts are only supported on Windows Vista/Server 2008 and above
461
+func (c *PipeConn) SetDeadline(t time.Time) error {
462
+	c.SetReadDeadline(t)
463
+	c.SetWriteDeadline(t)
464
+	return nil
465
+}
466
+
467
+// SetReadDeadline implements the net.Conn SetReadDeadline method.
468
+// Note that timeouts are only supported on Windows Vista/Server 2008 and above
469
+func (c *PipeConn) SetReadDeadline(t time.Time) error {
470
+	c.readDeadline = &t
471
+	return nil
472
+}
473
+
474
+// SetWriteDeadline implements the net.Conn SetWriteDeadline method.
475
+// Note that timeouts are only supported on Windows Vista/Server 2008 and above
476
+func (c *PipeConn) SetWriteDeadline(t time.Time) error {
477
+	c.writeDeadline = &t
478
+	return nil
479
+}
480
+
481
+// PipeAddr represents the address of a named pipe.
482
+type PipeAddr string
483
+
484
+// Network returns the address's network name, "pipe".
485
+func (a PipeAddr) Network() string { return "pipe" }
486
+
487
+// String returns the address of the pipe
488
+func (a PipeAddr) String() string {
489
+	return string(a)
490
+}
491
+
492
+// createPipe is a helper function to make sure we always create pipes
493
+// with the same arguments, since subsequent calls to create pipe need
494
+// to use the same arguments as the first one. If first is set, fail
495
+// if the pipe already exists.
496
+func createPipe(address string, first bool) (syscall.Handle, error) {
497
+	n, err := syscall.UTF16PtrFromString(address)
498
+	if err != nil {
499
+		return 0, err
500
+	}
501
+	mode := uint32(pipe_access_duplex | syscall.FILE_FLAG_OVERLAPPED)
502
+	if first {
503
+		mode |= file_flag_first_pipe_instance
504
+	}
505
+	return createNamedPipe(n,
506
+		mode,
507
+		pipe_type_byte,
508
+		pipe_unlimited_instances,
509
+		512, 512, 0, nil)
510
+}
511
+
512
+func badAddr(addr string) PipeError {
513
+	return PipeError{fmt.Sprintf("Invalid pipe address '%s'.", addr), false}
514
+}
515
+func timeout(addr string) PipeError {
516
+	return PipeError{fmt.Sprintf("Pipe IO timed out waiting for '%s'", addr), true}
517
+}
0 518
new file mode 100644
... ...
@@ -0,0 +1,124 @@
0
+// +build windows
1
+// go build mksyscall_windows.go && ./mksyscall_windows npipe_windows.go
2
+// MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
3
+
4
+package npipe
5
+
6
+import "unsafe"
7
+import "syscall"
8
+
9
+var (
10
+	modkernel32 = syscall.NewLazyDLL("kernel32.dll")
11
+
12
+	procCreateNamedPipeW    = modkernel32.NewProc("CreateNamedPipeW")
13
+	procConnectNamedPipe    = modkernel32.NewProc("ConnectNamedPipe")
14
+	procDisconnectNamedPipe = modkernel32.NewProc("DisconnectNamedPipe")
15
+	procWaitNamedPipeW      = modkernel32.NewProc("WaitNamedPipeW")
16
+	procCreateEventW        = modkernel32.NewProc("CreateEventW")
17
+	procGetOverlappedResult = modkernel32.NewProc("GetOverlappedResult")
18
+	procCancelIoEx          = modkernel32.NewProc("CancelIoEx")
19
+)
20
+
21
+func createNamedPipe(name *uint16, openMode uint32, pipeMode uint32, maxInstances uint32, outBufSize uint32, inBufSize uint32, defaultTimeout uint32, sa *syscall.SecurityAttributes) (handle syscall.Handle, err error) {
22
+	r0, _, e1 := syscall.Syscall9(procCreateNamedPipeW.Addr(), 8, uintptr(unsafe.Pointer(name)), uintptr(openMode), uintptr(pipeMode), uintptr(maxInstances), uintptr(outBufSize), uintptr(inBufSize), uintptr(defaultTimeout), uintptr(unsafe.Pointer(sa)), 0)
23
+	handle = syscall.Handle(r0)
24
+	if handle == syscall.InvalidHandle {
25
+		if e1 != 0 {
26
+			err = error(e1)
27
+		} else {
28
+			err = syscall.EINVAL
29
+		}
30
+	}
31
+	return
32
+}
33
+
34
+func cancelIoEx(handle syscall.Handle, overlapped *syscall.Overlapped) (err error) {
35
+	r1, _, e1 := syscall.Syscall(procCancelIoEx.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(overlapped)), 0)
36
+	if r1 == 0 {
37
+		if e1 != 0 {
38
+			err = error(e1)
39
+		} else {
40
+			err = syscall.EINVAL
41
+		}
42
+	}
43
+	return
44
+}
45
+
46
+func connectNamedPipe(handle syscall.Handle, overlapped *syscall.Overlapped) (err error) {
47
+	r1, _, e1 := syscall.Syscall(procConnectNamedPipe.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(overlapped)), 0)
48
+	if r1 == 0 {
49
+		if e1 != 0 {
50
+			err = error(e1)
51
+		} else {
52
+			err = syscall.EINVAL
53
+		}
54
+	}
55
+	return
56
+}
57
+
58
+func disconnectNamedPipe(handle syscall.Handle) (err error) {
59
+	r1, _, e1 := syscall.Syscall(procDisconnectNamedPipe.Addr(), 1, uintptr(handle), 0, 0)
60
+	if r1 == 0 {
61
+		if e1 != 0 {
62
+			err = error(e1)
63
+		} else {
64
+			err = syscall.EINVAL
65
+		}
66
+	}
67
+	return
68
+}
69
+
70
+func waitNamedPipe(name *uint16, timeout uint32) (err error) {
71
+	r1, _, e1 := syscall.Syscall(procWaitNamedPipeW.Addr(), 2, uintptr(unsafe.Pointer(name)), uintptr(timeout), 0)
72
+	if r1 == 0 {
73
+		if e1 != 0 {
74
+			err = error(e1)
75
+		} else {
76
+			err = syscall.EINVAL
77
+		}
78
+	}
79
+	return
80
+}
81
+
82
+func createEvent(sa *syscall.SecurityAttributes, manualReset bool, initialState bool, name *uint16) (handle syscall.Handle, err error) {
83
+	var _p0 uint32
84
+	if manualReset {
85
+		_p0 = 1
86
+	} else {
87
+		_p0 = 0
88
+	}
89
+	var _p1 uint32
90
+	if initialState {
91
+		_p1 = 1
92
+	} else {
93
+		_p1 = 0
94
+	}
95
+	r0, _, e1 := syscall.Syscall6(procCreateEventW.Addr(), 4, uintptr(unsafe.Pointer(sa)), uintptr(_p0), uintptr(_p1), uintptr(unsafe.Pointer(name)), 0, 0)
96
+	handle = syscall.Handle(r0)
97
+	if handle == syscall.InvalidHandle {
98
+		if e1 != 0 {
99
+			err = error(e1)
100
+		} else {
101
+			err = syscall.EINVAL
102
+		}
103
+	}
104
+	return
105
+}
106
+
107
+func getOverlappedResult(handle syscall.Handle, overlapped *syscall.Overlapped, transferred *uint32, wait bool) (err error) {
108
+	var _p0 uint32
109
+	if wait {
110
+		_p0 = 1
111
+	} else {
112
+		_p0 = 0
113
+	}
114
+	r1, _, e1 := syscall.Syscall6(procGetOverlappedResult.Addr(), 4, uintptr(handle), uintptr(unsafe.Pointer(overlapped)), uintptr(unsafe.Pointer(transferred)), uintptr(_p0), 0, 0)
115
+	if r1 == 0 {
116
+		if e1 != 0 {
117
+			err = error(e1)
118
+		} else {
119
+			err = syscall.EINVAL
120
+		}
121
+	}
122
+	return
123
+}
0 124
new file mode 100644
... ...
@@ -0,0 +1,124 @@
0
+// +build windows
1
+// go build mksyscall_windows.go && ./mksyscall_windows npipe_windows.go
2
+// MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
3
+
4
+package npipe
5
+
6
+import "unsafe"
7
+import "syscall"
8
+
9
+var (
10
+	modkernel32 = syscall.NewLazyDLL("kernel32.dll")
11
+
12
+	procCreateNamedPipeW    = modkernel32.NewProc("CreateNamedPipeW")
13
+	procConnectNamedPipe    = modkernel32.NewProc("ConnectNamedPipe")
14
+	procDisconnectNamedPipe = modkernel32.NewProc("DisconnectNamedPipe")
15
+	procWaitNamedPipeW      = modkernel32.NewProc("WaitNamedPipeW")
16
+	procCreateEventW        = modkernel32.NewProc("CreateEventW")
17
+	procGetOverlappedResult = modkernel32.NewProc("GetOverlappedResult")
18
+	procCancelIoEx          = modkernel32.NewProc("CancelIoEx")
19
+)
20
+
21
+func createNamedPipe(name *uint16, openMode uint32, pipeMode uint32, maxInstances uint32, outBufSize uint32, inBufSize uint32, defaultTimeout uint32, sa *syscall.SecurityAttributes) (handle syscall.Handle, err error) {
22
+	r0, _, e1 := syscall.Syscall9(procCreateNamedPipeW.Addr(), 8, uintptr(unsafe.Pointer(name)), uintptr(openMode), uintptr(pipeMode), uintptr(maxInstances), uintptr(outBufSize), uintptr(inBufSize), uintptr(defaultTimeout), uintptr(unsafe.Pointer(sa)), 0)
23
+	handle = syscall.Handle(r0)
24
+	if handle == syscall.InvalidHandle {
25
+		if e1 != 0 {
26
+			err = error(e1)
27
+		} else {
28
+			err = syscall.EINVAL
29
+		}
30
+	}
31
+	return
32
+}
33
+
34
+func cancelIoEx(handle syscall.Handle, overlapped *syscall.Overlapped) (err error) {
35
+	r1, _, e1 := syscall.Syscall(procCancelIoEx.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(overlapped)), 0)
36
+	if r1 == 0 {
37
+		if e1 != 0 {
38
+			err = error(e1)
39
+		} else {
40
+			err = syscall.EINVAL
41
+		}
42
+	}
43
+	return
44
+}
45
+
46
+func connectNamedPipe(handle syscall.Handle, overlapped *syscall.Overlapped) (err error) {
47
+	r1, _, e1 := syscall.Syscall(procConnectNamedPipe.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(overlapped)), 0)
48
+	if r1 == 0 {
49
+		if e1 != 0 {
50
+			err = error(e1)
51
+		} else {
52
+			err = syscall.EINVAL
53
+		}
54
+	}
55
+	return
56
+}
57
+
58
+func disconnectNamedPipe(handle syscall.Handle) (err error) {
59
+	r1, _, e1 := syscall.Syscall(procDisconnectNamedPipe.Addr(), 1, uintptr(handle), 0, 0)
60
+	if r1 == 0 {
61
+		if e1 != 0 {
62
+			err = error(e1)
63
+		} else {
64
+			err = syscall.EINVAL
65
+		}
66
+	}
67
+	return
68
+}
69
+
70
+func waitNamedPipe(name *uint16, timeout uint32) (err error) {
71
+	r1, _, e1 := syscall.Syscall(procWaitNamedPipeW.Addr(), 2, uintptr(unsafe.Pointer(name)), uintptr(timeout), 0)
72
+	if r1 == 0 {
73
+		if e1 != 0 {
74
+			err = error(e1)
75
+		} else {
76
+			err = syscall.EINVAL
77
+		}
78
+	}
79
+	return
80
+}
81
+
82
+func createEvent(sa *syscall.SecurityAttributes, manualReset bool, initialState bool, name *uint16) (handle syscall.Handle, err error) {
83
+	var _p0 uint32
84
+	if manualReset {
85
+		_p0 = 1
86
+	} else {
87
+		_p0 = 0
88
+	}
89
+	var _p1 uint32
90
+	if initialState {
91
+		_p1 = 1
92
+	} else {
93
+		_p1 = 0
94
+	}
95
+	r0, _, e1 := syscall.Syscall6(procCreateEventW.Addr(), 4, uintptr(unsafe.Pointer(sa)), uintptr(_p0), uintptr(_p1), uintptr(unsafe.Pointer(name)), 0, 0)
96
+	handle = syscall.Handle(r0)
97
+	if handle == syscall.InvalidHandle {
98
+		if e1 != 0 {
99
+			err = error(e1)
100
+		} else {
101
+			err = syscall.EINVAL
102
+		}
103
+	}
104
+	return
105
+}
106
+
107
+func getOverlappedResult(handle syscall.Handle, overlapped *syscall.Overlapped, transferred *uint32, wait bool) (err error) {
108
+	var _p0 uint32
109
+	if wait {
110
+		_p0 = 1
111
+	} else {
112
+		_p0 = 0
113
+	}
114
+	r1, _, e1 := syscall.Syscall6(procGetOverlappedResult.Addr(), 4, uintptr(handle), uintptr(unsafe.Pointer(overlapped)), uintptr(unsafe.Pointer(transferred)), uintptr(_p0), 0, 0)
115
+	if r1 == 0 {
116
+		if e1 != 0 {
117
+			err = error(e1)
118
+		} else {
119
+			err = syscall.EINVAL
120
+		}
121
+	}
122
+	return
123
+}