2016-02-01 23:09:35 +00:00
|
|
|
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 != "" {
|
2016-03-02 22:17:13 +00:00
|
|
|
sd, err = SddlToSecurityDescriptor(sddl)
|
2016-02-01 23:09:35 +00:00
|
|
|
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)
|
|
|
|
}
|