package winio import ( "errors" "net" "os" "syscall" "time" "unsafe" ) //sys connectNamedPipe(pipe syscall.Handle, o *syscall.Overlapped) (err error) = ConnectNamedPipe //sys createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *securityAttributes) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateNamedPipeW //sys createFile(name string, access uint32, mode uint32, sa *securityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateFileW //sys waitNamedPipe(name string, timeout uint32) (err error) = WaitNamedPipeW type securityAttributes struct { Length uint32 SecurityDescriptor *byte InheritHandle uint32 } const ( cERROR_PIPE_BUSY = syscall.Errno(231) cERROR_PIPE_CONNECTED = syscall.Errno(535) cERROR_SEM_TIMEOUT = syscall.Errno(121) cPIPE_ACCESS_DUPLEX = 0x3 cFILE_FLAG_FIRST_PIPE_INSTANCE = 0x80000 cSECURITY_SQOS_PRESENT = 0x100000 cSECURITY_ANONYMOUS = 0 cPIPE_REJECT_REMOTE_CLIENTS = 0x8 cPIPE_UNLIMITED_INSTANCES = 255 cNMPWAIT_USE_DEFAULT_WAIT = 0 cNMPWAIT_NOWAIT = 1 ) var ( // This error should match net.errClosing since docker takes a dependency on its text ErrPipeListenerClosed = errors.New("use of closed network connection") ) type win32Pipe struct { *win32File path string } type pipeAddress string func (f *win32Pipe) LocalAddr() net.Addr { return pipeAddress(f.path) } func (f *win32Pipe) RemoteAddr() net.Addr { return pipeAddress(f.path) } func (f *win32Pipe) SetDeadline(t time.Time) error { f.SetReadDeadline(t) f.SetWriteDeadline(t) return nil } func (s pipeAddress) Network() string { return "pipe" } func (s pipeAddress) String() string { return string(s) } func makeWin32Pipe(h syscall.Handle, path string) (*win32Pipe, error) { f, err := makeWin32File(h) if err != nil { return nil, err } return &win32Pipe{f, path}, nil } // DialPipe connects to a named pipe by path, timing out if the connection // takes longer than the specified duration. If timeout is nil, then the timeout // is the default timeout established by the pipe server. func DialPipe(path string, timeout *time.Duration) (net.Conn, error) { var absTimeout time.Time if timeout != nil { absTimeout = time.Now().Add(*timeout) } var err error var h syscall.Handle for { 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) if err != cERROR_PIPE_BUSY { break } now := time.Now() var ms uint32 if absTimeout.IsZero() { ms = cNMPWAIT_USE_DEFAULT_WAIT } else if now.After(absTimeout) { ms = cNMPWAIT_NOWAIT } else { ms = uint32(absTimeout.Sub(now).Nanoseconds() / 1000 / 1000) } err = waitNamedPipe(path, ms) if err != nil { if err == cERROR_SEM_TIMEOUT { return nil, ErrTimeout } break } } if err != nil { return nil, &os.PathError{"open", path, err} } p, err := makeWin32Pipe(h, path) if err != nil { syscall.Close(h) return nil, err } return p, nil } type acceptResponse struct { p *win32Pipe err error } type win32PipeListener struct { firstHandle syscall.Handle path string securityDescriptor []byte acceptCh chan (chan acceptResponse) closeCh chan int doneCh chan int } func makeServerPipeHandle(path string, securityDescriptor []byte, first bool) (syscall.Handle, error) { var flags uint32 = cPIPE_ACCESS_DUPLEX | syscall.FILE_FLAG_OVERLAPPED if first { flags |= cFILE_FLAG_FIRST_PIPE_INSTANCE } var sa securityAttributes sa.Length = uint32(unsafe.Sizeof(sa)) if securityDescriptor != nil { sa.SecurityDescriptor = &securityDescriptor[0] } h, err := createNamedPipe(path, flags, cPIPE_REJECT_REMOTE_CLIENTS, cPIPE_UNLIMITED_INSTANCES, 4096, 4096, 0, &sa) if err != nil { return 0, &os.PathError{"open", path, err} } return h, nil } func (l *win32PipeListener) makeServerPipe() (*win32Pipe, error) { h, err := makeServerPipeHandle(l.path, l.securityDescriptor, false) if err != nil { return nil, err } p, err := makeWin32Pipe(h, l.path) if err != nil { syscall.Close(h) return nil, err } return p, nil } func (l *win32PipeListener) listenerRoutine() { closed := false for !closed { select { case <-l.closeCh: closed = true case responseCh := <-l.acceptCh: p, err := l.makeServerPipe() if err == nil { // Wait for the client to connect. ch := make(chan error) go func() { ch <- connectPipe(p) }() select { case err = <-ch: if err != nil { p.Close() p = nil } case <-l.closeCh: // Abort the connect request by closing the handle. p.Close() p = nil err = <-ch if err == nil || err == ErrFileClosed { err = ErrPipeListenerClosed } closed = true } } responseCh <- acceptResponse{p, err} } } syscall.Close(l.firstHandle) l.firstHandle = 0 // Notify Close() and Accept() callers that the handle has been closed. close(l.doneCh) } func ListenPipe(path, sddl string) (net.Listener, error) { var ( sd []byte err error ) if sddl != "" { sd, err = SddlToSecurityDescriptor(sddl) if err != nil { return nil, err } } h, err := makeServerPipeHandle(path, sd, true) if err != nil { return nil, err } // Immediately open and then close a client handle so that the named pipe is // created but not currently accepting connections. h2, err := createFile(path, 0, 0, nil, syscall.OPEN_EXISTING, cSECURITY_SQOS_PRESENT|cSECURITY_ANONYMOUS, 0) if err != nil { syscall.Close(h) return nil, err } syscall.Close(h2) l := &win32PipeListener{ firstHandle: h, path: path, securityDescriptor: sd, acceptCh: make(chan (chan acceptResponse)), closeCh: make(chan int), doneCh: make(chan int), } go l.listenerRoutine() return l, nil } func connectPipe(p *win32Pipe) error { c, err := p.prepareIo() if err != nil { return err } err = connectNamedPipe(p.handle, &c.o) _, err = p.asyncIo(c, time.Time{}, 0, err) if err != nil && err != cERROR_PIPE_CONNECTED { return err } return nil } func (l *win32PipeListener) Accept() (net.Conn, error) { ch := make(chan acceptResponse) select { case l.acceptCh <- ch: response := <-ch return response.p, response.err case <-l.doneCh: return nil, ErrPipeListenerClosed } } func (l *win32PipeListener) Close() error { select { case l.closeCh <- 1: <-l.doneCh case <-l.doneCh: } return nil } func (l *win32PipeListener) Addr() net.Addr { return pipeAddress(l.path) }