diff --git a/npipe_windows.go b/npipe_windows.go index 48287e6..8bae0c3 100755 --- a/npipe_windows.go +++ b/npipe_windows.go @@ -1,3 +1,37 @@ +// Copyright 2013 Nate Finch. All rights reserved. +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +// Package npipe provides a pure Go wrapper around Windows named pipes. +// +// See http://msdn.microsoft.com/en-us/library/windows/desktop/aa365780 +// +// npipe provides an interface based on stdlib's net package, with Dial, Listen, and Accept +// functions, as well as associated implementations of net.Conn and net.Listener +// +// The Dial function connects a client to a named pipe: +// conn, err := npipe.Dial(`\\.\pipe\mypipename`) +// if err != nil { +// +// } +// fmt.Fprintf(conn, "Hi server!\n") +// msg, err := bufio.NewReader(conn).ReadString('\n') +// ... +// +// The Listen function creates servers: +// +// ln, err := npipe.Listen(`\\.\pipe\mypipename`) +// if err != nil { +// // handle error +// } +// for { +// conn, err := ln.Accept() +// if err != nil { +// // handle error +// continue +// } +// go handleConnection(conn) +// } package npipe //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 @@ -6,13 +40,12 @@ package npipe //sys waitNamedPipe(name *uint16, timeout uint32) (err error) = WaitNamedPipeW //sys createEvent(sa *syscall.SecurityAttributes, manualReset bool, initialState bool, name *uint16) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateEventW //sys getOverlappedResult(handle syscall.Handle, overlapped *syscall.Overlapped, transferred *uint32, wait bool) (err error) = GetOverlappedResult -//sys cancelIoEx(handle syscall.Handle, overlapped *syscall.Overlapped) (err error) = CancelIoEx import ( "fmt" "io" "net" - "sync" + "os" "syscall" "time" ) @@ -53,9 +86,8 @@ const ( nmpwait_wait_forever = 0xFFFFFFFF - // the two not-an-errors below occur if a client connects to the pipe between + // this not-an-error that occurs if a client connects to the pipe between // the server's CreateNamedPipe and ConnectNamedPipe calls. - error_no_data syscall.Errno = 0xE8 error_pipe_connected syscall.Errno = 0x217 error_pipe_busy syscall.Errno = 0xE7 error_sem_timeout syscall.Errno = 0x79 @@ -66,13 +98,6 @@ const ( error_io_incomplete syscall.Errno = 0x3e4 ) -var _ net.Conn = (*PipeConn)(nil) -var _ net.Listener = (*PipeListener)(nil) - -// ErrClosed is the error returned by PipeListener.Accept when Close is called -// on the PipeListener. -var ErrClosed = PipeError{"Pipe has been closed.", false} - // PipeError is an error related to a call to a pipe type PipeError struct { msg string @@ -162,7 +187,10 @@ func isPipeNotReady(err error) bool { // File Not Found means the server hasn't created the pipe yet. // Neither is a fatal error. - return err == syscall.ERROR_FILE_NOT_FOUND || err == error_pipe_busy + if err, ok := err.(*os.PathError); ok { + return err.Err == error_pipe_busy + } + return err == syscall.ERROR_FILE_NOT_FOUND } // newOverlapped creates a structure used to track asynchronous @@ -221,8 +249,8 @@ func dial(address string, timeout uint32) (*PipeConn, error) { return &PipeConn{handle: handle, addr: PipeAddr(address)}, nil } -// Listen returns a new PipeListener that will listen on a pipe with the given -// address. The address must be of the form \\.\pipe\ +// New returns a new PipeListener that will listen on a pipe with the given address. +// The address must be of the form \\.\pipe\ // // Listen will return a PipeError for an incorrectly formatted pipe name. func Listen(address string) (*PipeListener, error) { @@ -233,10 +261,7 @@ func Listen(address string) (*PipeListener, error) { if err != nil { return nil, err } - return &PipeListener{ - addr: PipeAddr(address), - handle: handle, - }, nil + return &PipeListener{PipeAddr(address), handle, false}, nil } // PipeListener is a named pipe listener. Clients should typically @@ -245,25 +270,12 @@ type PipeListener struct { addr PipeAddr handle syscall.Handle closed bool - - // acceptHandle contains the current handle waiting for - // an incoming connection or nil. - acceptHandle syscall.Handle - // acceptOverlapped is set before waiting on a connection. - // If not waiting, it is nil. - acceptOverlapped *syscall.Overlapped - // acceptMutex protects the handle and overlapped structure. - acceptMutex sync.Mutex } // Accept implements the Accept method in the net.Listener interface; it // waits for the next call and returns a generic net.Conn. func (l *PipeListener) Accept() (net.Conn, error) { c, err := l.AcceptPipe() - for err == error_no_data { - // Ignore clients that connect and immediately disconnect. - c, err = l.AcceptPipe() - } if err != nil { return nil, err } @@ -271,8 +283,6 @@ func (l *PipeListener) Accept() (net.Conn, error) { } // AcceptPipe accepts the next incoming call and returns the new connection. -// It might return an error if a client connected and immediately cancelled -// the connection. func (l *PipeListener) AcceptPipe() (*PipeConn, error) { if l == nil || l.addr == "" || l.closed { return nil, syscall.EINVAL @@ -300,24 +310,8 @@ func (l *PipeListener) AcceptPipe() (*PipeConn, error) { defer syscall.CloseHandle(overlapped.HEvent) if err := connectNamedPipe(handle, overlapped); err != nil && err != error_pipe_connected { if err == error_io_incomplete || err == syscall.ERROR_IO_PENDING { - l.acceptMutex.Lock() - l.acceptOverlapped = overlapped - l.acceptHandle = handle - l.acceptMutex.Unlock() - defer func() { - l.acceptMutex.Lock() - l.acceptOverlapped = nil - l.acceptHandle = 0 - l.acceptMutex.Unlock() - }() - _, err = waitForCompletion(handle, overlapped) } - if err == syscall.ERROR_OPERATION_ABORTED { - // Return error compatible to net.Listener.Accept() in case the - // listener was closed. - return nil, ErrClosed - } if err != nil { return nil, err } @@ -334,33 +328,8 @@ func (l *PipeListener) Close() error { l.closed = true if l.handle != 0 { err := disconnectNamedPipe(l.handle) - if err != nil { - return err - } - err = syscall.CloseHandle(l.handle) - if err != nil { - return err - } l.handle = 0 - } - l.acceptMutex.Lock() - defer l.acceptMutex.Unlock() - if l.acceptOverlapped != nil && l.acceptHandle != 0 { - // Cancel the pending IO. This call does not block, so it is safe - // to hold onto the mutex above. - if err := cancelIoEx(l.acceptHandle, l.acceptOverlapped); err != nil { - return err - } - err := syscall.CloseHandle(l.acceptOverlapped.HEvent) - if err != nil { - return err - } - l.acceptOverlapped.HEvent = 0 - err = syscall.CloseHandle(l.acceptHandle) - if err != nil { - return err - } - l.acceptHandle = 0 + return err } return nil } @@ -503,11 +472,17 @@ func createPipe(address string, first bool) (syscall.Handle, error) { if first { mode |= file_flag_first_pipe_instance } + + sa, err := initSecurityAttributes() + if err != nil { + return 0, err + } + return createNamedPipe(n, mode, pipe_type_byte, pipe_unlimited_instances, - 512, 512, 0, nil) + 512, 512, 0, sa) } func badAddr(addr string) PipeError { diff --git a/sec_attr.go b/sec_attr.go new file mode 100644 index 0000000..a2179fa --- /dev/null +++ b/sec_attr.go @@ -0,0 +1,46 @@ +package npipe + +import ( + "os" + "syscall" + "unsafe" +) + +const SECURITY_DESCRIPTOR_REVISION = 1 + +var ( + advapi32 = syscall.NewLazyDLL("advapi32.dll") + procInitializeSecurityDescriptor = advapi32.NewProc("InitializeSecurityDescriptor") + procSetSecurityDescriptorDacl = advapi32.NewProc("SetSecurityDescriptorDacl") +) + +func initSecurityAttributes() (*syscall.SecurityAttributes, error) { + + // create security descriptor + sd := make([]byte, 4096) + if res, _, err := procInitializeSecurityDescriptor.Call( + uintptr(unsafe.Pointer(&sd[0])), + SECURITY_DESCRIPTOR_REVISION); int(res) == 0 { + + return nil, os.NewSyscallError("InitializeSecurityDescriptor", err) + } + + // configure security descriptor + present := 1 + defaulted := 0 + if res, _, err := procSetSecurityDescriptorDacl.Call( + uintptr(unsafe.Pointer(&sd[0])), + uintptr(present), + uintptr(unsafe.Pointer(nil)), // acl + uintptr(defaulted)); int(res) == 0 { + + return nil, os.NewSyscallError("SetSecurityDescriptorDacl", err) + } + + var sa syscall.SecurityAttributes + sa.Length = uint32(unsafe.Sizeof(sa)) + sa.SecurityDescriptor = uintptr(unsafe.Pointer(&sd[0])) + + return &sa, nil + +}