David K. Bainbridge | 215e024 | 2017-09-05 23:18:24 -0700 | [diff] [blame] | 1 | // +build windows |
| 2 | |
| 3 | package winio |
| 4 | |
| 5 | import ( |
| 6 | "errors" |
| 7 | "io" |
| 8 | "net" |
| 9 | "os" |
| 10 | "syscall" |
| 11 | "time" |
| 12 | "unsafe" |
| 13 | ) |
| 14 | |
| 15 | //sys connectNamedPipe(pipe syscall.Handle, o *syscall.Overlapped) (err error) = ConnectNamedPipe |
| 16 | //sys createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *syscall.SecurityAttributes) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateNamedPipeW |
| 17 | //sys createFile(name string, access uint32, mode uint32, sa *syscall.SecurityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateFileW |
| 18 | //sys waitNamedPipe(name string, timeout uint32) (err error) = WaitNamedPipeW |
| 19 | //sys getNamedPipeInfo(pipe syscall.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) = GetNamedPipeInfo |
| 20 | //sys getNamedPipeHandleState(pipe syscall.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) = GetNamedPipeHandleStateW |
| 21 | //sys localAlloc(uFlags uint32, length uint32) (ptr uintptr) = LocalAlloc |
| 22 | |
| 23 | const ( |
| 24 | cERROR_PIPE_BUSY = syscall.Errno(231) |
| 25 | cERROR_PIPE_CONNECTED = syscall.Errno(535) |
| 26 | cERROR_SEM_TIMEOUT = syscall.Errno(121) |
| 27 | |
| 28 | cPIPE_ACCESS_DUPLEX = 0x3 |
| 29 | cFILE_FLAG_FIRST_PIPE_INSTANCE = 0x80000 |
| 30 | cSECURITY_SQOS_PRESENT = 0x100000 |
| 31 | cSECURITY_ANONYMOUS = 0 |
| 32 | |
| 33 | cPIPE_REJECT_REMOTE_CLIENTS = 0x8 |
| 34 | |
| 35 | cPIPE_UNLIMITED_INSTANCES = 255 |
| 36 | |
| 37 | cNMPWAIT_USE_DEFAULT_WAIT = 0 |
| 38 | cNMPWAIT_NOWAIT = 1 |
| 39 | |
| 40 | cPIPE_TYPE_MESSAGE = 4 |
| 41 | |
| 42 | cPIPE_READMODE_MESSAGE = 2 |
| 43 | ) |
| 44 | |
| 45 | var ( |
| 46 | // ErrPipeListenerClosed is returned for pipe operations on listeners that have been closed. |
| 47 | // This error should match net.errClosing since docker takes a dependency on its text. |
| 48 | ErrPipeListenerClosed = errors.New("use of closed network connection") |
| 49 | |
| 50 | errPipeWriteClosed = errors.New("pipe has been closed for write") |
| 51 | ) |
| 52 | |
| 53 | type win32Pipe struct { |
| 54 | *win32File |
| 55 | path string |
| 56 | } |
| 57 | |
| 58 | type win32MessageBytePipe struct { |
| 59 | win32Pipe |
| 60 | writeClosed bool |
| 61 | readEOF bool |
| 62 | } |
| 63 | |
| 64 | type pipeAddress string |
| 65 | |
| 66 | func (f *win32Pipe) LocalAddr() net.Addr { |
| 67 | return pipeAddress(f.path) |
| 68 | } |
| 69 | |
| 70 | func (f *win32Pipe) RemoteAddr() net.Addr { |
| 71 | return pipeAddress(f.path) |
| 72 | } |
| 73 | |
| 74 | func (f *win32Pipe) SetDeadline(t time.Time) error { |
| 75 | f.SetReadDeadline(t) |
| 76 | f.SetWriteDeadline(t) |
| 77 | return nil |
| 78 | } |
| 79 | |
| 80 | // CloseWrite closes the write side of a message pipe in byte mode. |
| 81 | func (f *win32MessageBytePipe) CloseWrite() error { |
| 82 | if f.writeClosed { |
| 83 | return errPipeWriteClosed |
| 84 | } |
| 85 | err := f.win32File.Flush() |
| 86 | if err != nil { |
| 87 | return err |
| 88 | } |
| 89 | _, err = f.win32File.Write(nil) |
| 90 | if err != nil { |
| 91 | return err |
| 92 | } |
| 93 | f.writeClosed = true |
| 94 | return nil |
| 95 | } |
| 96 | |
| 97 | // Write writes bytes to a message pipe in byte mode. Zero-byte writes are ignored, since |
| 98 | // they are used to implement CloseWrite(). |
| 99 | func (f *win32MessageBytePipe) Write(b []byte) (int, error) { |
| 100 | if f.writeClosed { |
| 101 | return 0, errPipeWriteClosed |
| 102 | } |
| 103 | if len(b) == 0 { |
| 104 | return 0, nil |
| 105 | } |
| 106 | return f.win32File.Write(b) |
| 107 | } |
| 108 | |
| 109 | // Read reads bytes from a message pipe in byte mode. A read of a zero-byte message on a message |
| 110 | // mode pipe will return io.EOF, as will all subsequent reads. |
| 111 | func (f *win32MessageBytePipe) Read(b []byte) (int, error) { |
| 112 | if f.readEOF { |
| 113 | return 0, io.EOF |
| 114 | } |
| 115 | n, err := f.win32File.Read(b) |
| 116 | if err == io.EOF { |
| 117 | // If this was the result of a zero-byte read, then |
| 118 | // it is possible that the read was due to a zero-size |
| 119 | // message. Since we are simulating CloseWrite with a |
| 120 | // zero-byte message, ensure that all future Read() calls |
| 121 | // also return EOF. |
| 122 | f.readEOF = true |
| 123 | } |
| 124 | return n, err |
| 125 | } |
| 126 | |
| 127 | func (s pipeAddress) Network() string { |
| 128 | return "pipe" |
| 129 | } |
| 130 | |
| 131 | func (s pipeAddress) String() string { |
| 132 | return string(s) |
| 133 | } |
| 134 | |
| 135 | // DialPipe connects to a named pipe by path, timing out if the connection |
| 136 | // takes longer than the specified duration. If timeout is nil, then the timeout |
| 137 | // is the default timeout established by the pipe server. |
| 138 | func DialPipe(path string, timeout *time.Duration) (net.Conn, error) { |
| 139 | var absTimeout time.Time |
| 140 | if timeout != nil { |
| 141 | absTimeout = time.Now().Add(*timeout) |
| 142 | } |
| 143 | var err error |
| 144 | var h syscall.Handle |
| 145 | for { |
| 146 | h, err = createFile(path, syscall.GENERIC_READ|syscall.GENERIC_WRITE, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_OVERLAPPED|cSECURITY_SQOS_PRESENT|cSECURITY_ANONYMOUS, 0) |
| 147 | if err != cERROR_PIPE_BUSY { |
| 148 | break |
| 149 | } |
| 150 | now := time.Now() |
| 151 | var ms uint32 |
| 152 | if absTimeout.IsZero() { |
| 153 | ms = cNMPWAIT_USE_DEFAULT_WAIT |
| 154 | } else if now.After(absTimeout) { |
| 155 | ms = cNMPWAIT_NOWAIT |
| 156 | } else { |
| 157 | ms = uint32(absTimeout.Sub(now).Nanoseconds() / 1000 / 1000) |
| 158 | } |
| 159 | err = waitNamedPipe(path, ms) |
| 160 | if err != nil { |
| 161 | if err == cERROR_SEM_TIMEOUT { |
| 162 | return nil, ErrTimeout |
| 163 | } |
| 164 | break |
| 165 | } |
| 166 | } |
| 167 | if err != nil { |
| 168 | return nil, &os.PathError{Op: "open", Path: path, Err: err} |
| 169 | } |
| 170 | |
| 171 | var flags uint32 |
| 172 | err = getNamedPipeInfo(h, &flags, nil, nil, nil) |
| 173 | if err != nil { |
| 174 | return nil, err |
| 175 | } |
| 176 | |
| 177 | var state uint32 |
| 178 | err = getNamedPipeHandleState(h, &state, nil, nil, nil, nil, 0) |
| 179 | if err != nil { |
| 180 | return nil, err |
| 181 | } |
| 182 | |
| 183 | if state&cPIPE_READMODE_MESSAGE != 0 { |
| 184 | return nil, &os.PathError{Op: "open", Path: path, Err: errors.New("message readmode pipes not supported")} |
| 185 | } |
| 186 | |
| 187 | f, err := makeWin32File(h) |
| 188 | if err != nil { |
| 189 | syscall.Close(h) |
| 190 | return nil, err |
| 191 | } |
| 192 | |
| 193 | // If the pipe is in message mode, return a message byte pipe, which |
| 194 | // supports CloseWrite(). |
| 195 | if flags&cPIPE_TYPE_MESSAGE != 0 { |
| 196 | return &win32MessageBytePipe{ |
| 197 | win32Pipe: win32Pipe{win32File: f, path: path}, |
| 198 | }, nil |
| 199 | } |
| 200 | return &win32Pipe{win32File: f, path: path}, nil |
| 201 | } |
| 202 | |
| 203 | type acceptResponse struct { |
| 204 | f *win32File |
| 205 | err error |
| 206 | } |
| 207 | |
| 208 | type win32PipeListener struct { |
| 209 | firstHandle syscall.Handle |
| 210 | path string |
| 211 | securityDescriptor []byte |
| 212 | config PipeConfig |
| 213 | acceptCh chan (chan acceptResponse) |
| 214 | closeCh chan int |
| 215 | doneCh chan int |
| 216 | } |
| 217 | |
| 218 | func makeServerPipeHandle(path string, securityDescriptor []byte, c *PipeConfig, first bool) (syscall.Handle, error) { |
| 219 | var flags uint32 = cPIPE_ACCESS_DUPLEX | syscall.FILE_FLAG_OVERLAPPED |
| 220 | if first { |
| 221 | flags |= cFILE_FLAG_FIRST_PIPE_INSTANCE |
| 222 | } |
| 223 | |
| 224 | var mode uint32 = cPIPE_REJECT_REMOTE_CLIENTS |
| 225 | if c.MessageMode { |
| 226 | mode |= cPIPE_TYPE_MESSAGE |
| 227 | } |
| 228 | |
| 229 | sa := &syscall.SecurityAttributes{} |
| 230 | sa.Length = uint32(unsafe.Sizeof(*sa)) |
| 231 | if securityDescriptor != nil { |
| 232 | len := uint32(len(securityDescriptor)) |
| 233 | sa.SecurityDescriptor = localAlloc(0, len) |
| 234 | defer localFree(sa.SecurityDescriptor) |
| 235 | copy((*[0xffff]byte)(unsafe.Pointer(sa.SecurityDescriptor))[:], securityDescriptor) |
| 236 | } |
| 237 | h, err := createNamedPipe(path, flags, mode, cPIPE_UNLIMITED_INSTANCES, uint32(c.OutputBufferSize), uint32(c.InputBufferSize), 0, sa) |
| 238 | if err != nil { |
| 239 | return 0, &os.PathError{Op: "open", Path: path, Err: err} |
| 240 | } |
| 241 | return h, nil |
| 242 | } |
| 243 | |
| 244 | func (l *win32PipeListener) makeServerPipe() (*win32File, error) { |
| 245 | h, err := makeServerPipeHandle(l.path, l.securityDescriptor, &l.config, false) |
| 246 | if err != nil { |
| 247 | return nil, err |
| 248 | } |
| 249 | f, err := makeWin32File(h) |
| 250 | if err != nil { |
| 251 | syscall.Close(h) |
| 252 | return nil, err |
| 253 | } |
| 254 | return f, nil |
| 255 | } |
| 256 | |
| 257 | func (l *win32PipeListener) listenerRoutine() { |
| 258 | closed := false |
| 259 | for !closed { |
| 260 | select { |
| 261 | case <-l.closeCh: |
| 262 | closed = true |
| 263 | case responseCh := <-l.acceptCh: |
| 264 | p, err := l.makeServerPipe() |
| 265 | if err == nil { |
| 266 | // Wait for the client to connect. |
| 267 | ch := make(chan error) |
| 268 | go func(p *win32File) { |
| 269 | ch <- connectPipe(p) |
| 270 | }(p) |
| 271 | select { |
| 272 | case err = <-ch: |
| 273 | if err != nil { |
| 274 | p.Close() |
| 275 | p = nil |
| 276 | } |
| 277 | case <-l.closeCh: |
| 278 | // Abort the connect request by closing the handle. |
| 279 | p.Close() |
| 280 | p = nil |
| 281 | err = <-ch |
| 282 | if err == nil || err == ErrFileClosed { |
| 283 | err = ErrPipeListenerClosed |
| 284 | } |
| 285 | closed = true |
| 286 | } |
| 287 | } |
| 288 | responseCh <- acceptResponse{p, err} |
| 289 | } |
| 290 | } |
| 291 | syscall.Close(l.firstHandle) |
| 292 | l.firstHandle = 0 |
| 293 | // Notify Close() and Accept() callers that the handle has been closed. |
| 294 | close(l.doneCh) |
| 295 | } |
| 296 | |
| 297 | // PipeConfig contain configuration for the pipe listener. |
| 298 | type PipeConfig struct { |
| 299 | // SecurityDescriptor contains a Windows security descriptor in SDDL format. |
| 300 | SecurityDescriptor string |
| 301 | |
| 302 | // MessageMode determines whether the pipe is in byte or message mode. In either |
| 303 | // case the pipe is read in byte mode by default. The only practical difference in |
| 304 | // this implementation is that CloseWrite() is only supported for message mode pipes; |
| 305 | // CloseWrite() is implemented as a zero-byte write, but zero-byte writes are only |
| 306 | // transferred to the reader (and returned as io.EOF in this implementation) |
| 307 | // when the pipe is in message mode. |
| 308 | MessageMode bool |
| 309 | |
| 310 | // InputBufferSize specifies the size the input buffer, in bytes. |
| 311 | InputBufferSize int32 |
| 312 | |
| 313 | // OutputBufferSize specifies the size the input buffer, in bytes. |
| 314 | OutputBufferSize int32 |
| 315 | } |
| 316 | |
| 317 | // ListenPipe creates a listener on a Windows named pipe path, e.g. \\.\pipe\mypipe. |
| 318 | // The pipe must not already exist. |
| 319 | func ListenPipe(path string, c *PipeConfig) (net.Listener, error) { |
| 320 | var ( |
| 321 | sd []byte |
| 322 | err error |
| 323 | ) |
| 324 | if c == nil { |
| 325 | c = &PipeConfig{} |
| 326 | } |
| 327 | if c.SecurityDescriptor != "" { |
| 328 | sd, err = SddlToSecurityDescriptor(c.SecurityDescriptor) |
| 329 | if err != nil { |
| 330 | return nil, err |
| 331 | } |
| 332 | } |
| 333 | h, err := makeServerPipeHandle(path, sd, c, true) |
| 334 | if err != nil { |
| 335 | return nil, err |
| 336 | } |
| 337 | // Immediately open and then close a client handle so that the named pipe is |
| 338 | // created but not currently accepting connections. |
| 339 | h2, err := createFile(path, 0, 0, nil, syscall.OPEN_EXISTING, cSECURITY_SQOS_PRESENT|cSECURITY_ANONYMOUS, 0) |
| 340 | if err != nil { |
| 341 | syscall.Close(h) |
| 342 | return nil, err |
| 343 | } |
| 344 | syscall.Close(h2) |
| 345 | l := &win32PipeListener{ |
| 346 | firstHandle: h, |
| 347 | path: path, |
| 348 | securityDescriptor: sd, |
| 349 | config: *c, |
| 350 | acceptCh: make(chan (chan acceptResponse)), |
| 351 | closeCh: make(chan int), |
| 352 | doneCh: make(chan int), |
| 353 | } |
| 354 | go l.listenerRoutine() |
| 355 | return l, nil |
| 356 | } |
| 357 | |
| 358 | func connectPipe(p *win32File) error { |
| 359 | c, err := p.prepareIo() |
| 360 | if err != nil { |
| 361 | return err |
| 362 | } |
| 363 | defer p.wg.Done() |
| 364 | |
| 365 | err = connectNamedPipe(p.handle, &c.o) |
| 366 | _, err = p.asyncIo(c, nil, 0, err) |
| 367 | if err != nil && err != cERROR_PIPE_CONNECTED { |
| 368 | return err |
| 369 | } |
| 370 | return nil |
| 371 | } |
| 372 | |
| 373 | func (l *win32PipeListener) Accept() (net.Conn, error) { |
| 374 | ch := make(chan acceptResponse) |
| 375 | select { |
| 376 | case l.acceptCh <- ch: |
| 377 | response := <-ch |
| 378 | err := response.err |
| 379 | if err != nil { |
| 380 | return nil, err |
| 381 | } |
| 382 | if l.config.MessageMode { |
| 383 | return &win32MessageBytePipe{ |
| 384 | win32Pipe: win32Pipe{win32File: response.f, path: l.path}, |
| 385 | }, nil |
| 386 | } |
| 387 | return &win32Pipe{win32File: response.f, path: l.path}, nil |
| 388 | case <-l.doneCh: |
| 389 | return nil, ErrPipeListenerClosed |
| 390 | } |
| 391 | } |
| 392 | |
| 393 | func (l *win32PipeListener) Close() error { |
| 394 | select { |
| 395 | case l.closeCh <- 1: |
| 396 | <-l.doneCh |
| 397 | case <-l.doneCh: |
| 398 | } |
| 399 | return nil |
| 400 | } |
| 401 | |
| 402 | func (l *win32PipeListener) Addr() net.Addr { |
| 403 | return pipeAddress(l.path) |
| 404 | } |