From efda81cbbb2d3926bb6a2b9b219d0e49a9c4df82 Mon Sep 17 00:00:00 2001 From: Danielle Lancashire Date: Tue, 18 Jun 2019 14:56:24 +0200 Subject: [PATCH 1/7] logmon: Refactor fifo access for windows safety On unix platforms, it is safe to re-open fifo's for reading after the first creation if the file is already a fifo, however this is not possible on windows where this triggers a permissions error on the socket path, as you cannot recreate it. We can't transparently handle this in the CreateAndRead handle, because the Access Is Denied error is too generic to reliably be an IO error. Instead, we add an explict API for opening a reader to an existing FIFO, and check to see if the fifo already exists inside the calling package (e.g logmon) --- client/lib/fifo/fifo_unix.go | 8 +++++--- client/lib/fifo/fifo_windows.go | 11 +++++++++-- client/logmon/logmon.go | 25 ++++++++++++++++++------- 3 files changed, 32 insertions(+), 12 deletions(-) diff --git a/client/lib/fifo/fifo_unix.go b/client/lib/fifo/fifo_unix.go index 4d902fb03..1533b0bee 100644 --- a/client/lib/fifo/fifo_unix.go +++ b/client/lib/fifo/fifo_unix.go @@ -21,11 +21,13 @@ func CreateAndRead(path string) (func() (io.ReadCloser, error), error) { return nil, fmt.Errorf("error creating fifo %v: %v", path, err) } - openFn := func() (io.ReadCloser, error) { + return func() (io.ReadCloser, error) { return os.OpenFile(path, unix.O_RDONLY, os.ModeNamedPipe) - } + }, nil +} - return openFn, nil +func OpenReader(path string) (io.ReadCloser, error) { + return os.OpenFile(path, unix.O_RDONLY, os.ModeNamedPipe) } // OpenWriter opens a fifo file for writer, assuming it already exists, returns io.WriteCloser diff --git a/client/lib/fifo/fifo_windows.go b/client/lib/fifo/fifo_windows.go index 24d9c1e42..0c3a88ba0 100644 --- a/client/lib/fifo/fifo_windows.go +++ b/client/lib/fifo/fifo_windows.go @@ -78,13 +78,20 @@ func CreateAndRead(path string) (func() (io.ReadCloser, error), error) { return nil, err } - openFn := func() (io.ReadCloser, error) { + return func() (io.ReadCloser, error) { return &winFIFO{ listener: l, }, nil + }, nil +} + +func OpenReader(path string) (io.ReadCloser, error) { + conn, err := winio.DialPipe(path, nil) + if err != nil { + return nil, err } - return openFn, nil + return &winFIFO{conn: conn}, nil } // OpenWriter opens a fifo that already exists and returns an io.WriteCloser for it diff --git a/client/logmon/logmon.go b/client/logmon/logmon.go index 74a13631d..a92257649 100644 --- a/client/logmon/logmon.go +++ b/client/logmon/logmon.go @@ -3,6 +3,7 @@ package logmon import ( "fmt" "io" + "os" "strings" "sync" "time" @@ -199,7 +200,18 @@ func (l *logRotatorWrapper) isRunning() bool { // processOutWriter to attach to the stdout or stderr of a process. func newLogRotatorWrapper(path string, logger hclog.Logger, rotator *logging.FileRotator) (*logRotatorWrapper, error) { logger.Info("opening fifo", "path", path) - fifoOpenFn, err := fifo.CreateAndRead(path) + + var openFn func() (io.ReadCloser, error) + var err error + + if _, ferr := os.Stat(path); os.IsNotExist(ferr) { + openFn, err = fifo.CreateAndRead(path) + } else { + openFn = func() (io.ReadCloser, error) { + return fifo.OpenReader(path) + } + } + if err != nil { return nil, fmt.Errorf("failed to create fifo for extracting logs: %v", err) } @@ -211,20 +223,20 @@ func newLogRotatorWrapper(path string, logger hclog.Logger, rotator *logging.Fil openCompleted: make(chan struct{}), logger: logger, } - wrap.start(fifoOpenFn) + + wrap.start(openFn) return wrap, nil } // start starts a goroutine that copies from the pipe into the rotator. This is // called by the constructor and not the user of the wrapper. -func (l *logRotatorWrapper) start(readerOpenFn func() (io.ReadCloser, error)) { +func (l *logRotatorWrapper) start(openFn func() (io.ReadCloser, error)) { go func() { defer close(l.hasFinishedCopied) - reader, err := readerOpenFn() + reader, err := openFn() if err != nil { - close(l.openCompleted) - l.logger.Warn("failed to open log fifo", "error", err) + l.logger.Warn("failed to open fifo", "error", err) return } l.processOutReader = reader @@ -284,5 +296,4 @@ func (l *logRotatorWrapper) Close() { } l.rotatorWriter.Close() - return } From 76f72fe4bd40fd1bb0f153589da6c93df373fd43 Mon Sep 17 00:00:00 2001 From: Danielle Lancashire Date: Thu, 20 Jun 2019 19:13:05 +0200 Subject: [PATCH 2/7] vendor: Use dani fork of go-winio --- client/lib/fifo/fifo_windows.go | 4 +- vendor/github.com/Microsoft/go-winio/file.go | 10 + vendor/github.com/Microsoft/go-winio/go.mod | 9 + vendor/github.com/Microsoft/go-winio/go.sum | 16 + .../github.com/Microsoft/go-winio/hvsock.go | 305 ++++++++++++++++++ vendor/github.com/Microsoft/go-winio/pipe.go | 269 ++++++++++----- .../Microsoft/go-winio/pkg/guid/guid.go | 187 +++++++++++ .../github.com/Microsoft/go-winio/syscall.go | 2 +- .../Microsoft/go-winio/zsyscall_windows.go | 88 +++-- vendor/vendor.json | 3 +- 10 files changed, 787 insertions(+), 106 deletions(-) create mode 100644 vendor/github.com/Microsoft/go-winio/go.mod create mode 100644 vendor/github.com/Microsoft/go-winio/go.sum create mode 100644 vendor/github.com/Microsoft/go-winio/hvsock.go create mode 100644 vendor/github.com/Microsoft/go-winio/pkg/guid/guid.go diff --git a/client/lib/fifo/fifo_windows.go b/client/lib/fifo/fifo_windows.go index 0c3a88ba0..4feaa63da 100644 --- a/client/lib/fifo/fifo_windows.go +++ b/client/lib/fifo/fifo_windows.go @@ -86,12 +86,12 @@ func CreateAndRead(path string) (func() (io.ReadCloser, error), error) { } func OpenReader(path string) (io.ReadCloser, error) { - conn, err := winio.DialPipe(path, nil) + l, err := winio.ListenOnlyPipe(path, nil) if err != nil { return nil, err } - return &winFIFO{conn: conn}, nil + return &winFIFO{listener: l}, nil } // OpenWriter opens a fifo that already exists and returns an io.WriteCloser for it diff --git a/vendor/github.com/Microsoft/go-winio/file.go b/vendor/github.com/Microsoft/go-winio/file.go index 4334ff1cb..ea2d27934 100644 --- a/vendor/github.com/Microsoft/go-winio/file.go +++ b/vendor/github.com/Microsoft/go-winio/file.go @@ -16,6 +16,7 @@ import ( //sys createIoCompletionPort(file syscall.Handle, port syscall.Handle, key uintptr, threadCount uint32) (newport syscall.Handle, err error) = CreateIoCompletionPort //sys getQueuedCompletionStatus(port syscall.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) = GetQueuedCompletionStatus //sys setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err error) = SetFileCompletionNotificationModes +//sys wsaGetOverlappedResult(h syscall.Handle, o *syscall.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) = ws2_32.WSAGetOverlappedResult type atomicBool int32 @@ -79,6 +80,7 @@ type win32File struct { wg sync.WaitGroup wgLock sync.RWMutex closing atomicBool + socket bool readDeadline deadlineHandler writeDeadline deadlineHandler } @@ -190,6 +192,10 @@ func (f *win32File) asyncIo(c *ioOperation, d *deadlineHandler, bytes uint32, er if f.closing.isSet() { err = ErrFileClosed } + } else if err != nil && f.socket { + // err is from Win32. Query the overlapped structure to get the winsock error. + var bytes, flags uint32 + err = wsaGetOverlappedResult(f.handle, &c.o, &bytes, false, &flags) } case <-timeout: cancelIoEx(f.handle, &c.o) @@ -265,6 +271,10 @@ func (f *win32File) Flush() error { return syscall.FlushFileBuffers(f.handle) } +func (f *win32File) Fd() uintptr { + return uintptr(f.handle) +} + func (d *deadlineHandler) set(deadline time.Time) error { d.setLock.Lock() defer d.setLock.Unlock() diff --git a/vendor/github.com/Microsoft/go-winio/go.mod b/vendor/github.com/Microsoft/go-winio/go.mod new file mode 100644 index 000000000..b3846826b --- /dev/null +++ b/vendor/github.com/Microsoft/go-winio/go.mod @@ -0,0 +1,9 @@ +module github.com/Microsoft/go-winio + +go 1.12 + +require ( + github.com/pkg/errors v0.8.1 + github.com/sirupsen/logrus v1.4.1 + golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b +) diff --git a/vendor/github.com/Microsoft/go-winio/go.sum b/vendor/github.com/Microsoft/go-winio/go.sum new file mode 100644 index 000000000..babb4a70d --- /dev/null +++ b/vendor/github.com/Microsoft/go-winio/go.sum @@ -0,0 +1,16 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b h1:ag/x1USPSsqHud38I9BAC88qdNLDHHtQ4mlgQIZPPNA= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/vendor/github.com/Microsoft/go-winio/hvsock.go b/vendor/github.com/Microsoft/go-winio/hvsock.go new file mode 100644 index 000000000..dbfe790ee --- /dev/null +++ b/vendor/github.com/Microsoft/go-winio/hvsock.go @@ -0,0 +1,305 @@ +package winio + +import ( + "fmt" + "io" + "net" + "os" + "syscall" + "time" + "unsafe" + + "github.com/Microsoft/go-winio/pkg/guid" +) + +//sys bind(s syscall.Handle, name unsafe.Pointer, namelen int32) (err error) [failretval==socketError] = ws2_32.bind + +const ( + afHvSock = 34 // AF_HYPERV + + socketError = ^uintptr(0) +) + +// An HvsockAddr is an address for a AF_HYPERV socket. +type HvsockAddr struct { + VMID guid.GUID + ServiceID guid.GUID +} + +type rawHvsockAddr struct { + Family uint16 + _ uint16 + VMID guid.GUID + ServiceID guid.GUID +} + +// Network returns the address's network name, "hvsock". +func (addr *HvsockAddr) Network() string { + return "hvsock" +} + +func (addr *HvsockAddr) String() string { + return fmt.Sprintf("%s:%s", &addr.VMID, &addr.ServiceID) +} + +// VsockServiceID returns an hvsock service ID corresponding to the specified AF_VSOCK port. +func VsockServiceID(port uint32) guid.GUID { + g, _ := guid.FromString("00000000-facb-11e6-bd58-64006a7986d3") + g.Data1 = port + return g +} + +func (addr *HvsockAddr) raw() rawHvsockAddr { + return rawHvsockAddr{ + Family: afHvSock, + VMID: addr.VMID, + ServiceID: addr.ServiceID, + } +} + +func (addr *HvsockAddr) fromRaw(raw *rawHvsockAddr) { + addr.VMID = raw.VMID + addr.ServiceID = raw.ServiceID +} + +// HvsockListener is a socket listener for the AF_HYPERV address family. +type HvsockListener struct { + sock *win32File + addr HvsockAddr +} + +// HvsockConn is a connected socket of the AF_HYPERV address family. +type HvsockConn struct { + sock *win32File + local, remote HvsockAddr +} + +func newHvSocket() (*win32File, error) { + fd, err := syscall.Socket(afHvSock, syscall.SOCK_STREAM, 1) + if err != nil { + return nil, os.NewSyscallError("socket", err) + } + f, err := makeWin32File(fd) + if err != nil { + syscall.Close(fd) + return nil, err + } + f.socket = true + return f, nil +} + +// ListenHvsock listens for connections on the specified hvsock address. +func ListenHvsock(addr *HvsockAddr) (_ *HvsockListener, err error) { + l := &HvsockListener{addr: *addr} + sock, err := newHvSocket() + if err != nil { + return nil, l.opErr("listen", err) + } + sa := addr.raw() + err = bind(sock.handle, unsafe.Pointer(&sa), int32(unsafe.Sizeof(sa))) + if err != nil { + return nil, l.opErr("listen", os.NewSyscallError("socket", err)) + } + err = syscall.Listen(sock.handle, 16) + if err != nil { + return nil, l.opErr("listen", os.NewSyscallError("listen", err)) + } + return &HvsockListener{sock: sock, addr: *addr}, nil +} + +func (l *HvsockListener) opErr(op string, err error) error { + return &net.OpError{Op: op, Net: "hvsock", Addr: &l.addr, Err: err} +} + +// Addr returns the listener's network address. +func (l *HvsockListener) Addr() net.Addr { + return &l.addr +} + +// Accept waits for the next connection and returns it. +func (l *HvsockListener) Accept() (_ net.Conn, err error) { + sock, err := newHvSocket() + if err != nil { + return nil, l.opErr("accept", err) + } + defer func() { + if sock != nil { + sock.Close() + } + }() + c, err := l.sock.prepareIo() + if err != nil { + return nil, l.opErr("accept", err) + } + defer l.sock.wg.Done() + + // AcceptEx, per documentation, requires an extra 16 bytes per address. + const addrlen = uint32(16 + unsafe.Sizeof(rawHvsockAddr{})) + var addrbuf [addrlen * 2]byte + + var bytes uint32 + err = syscall.AcceptEx(l.sock.handle, sock.handle, &addrbuf[0], 0, addrlen, addrlen, &bytes, &c.o) + _, err = l.sock.asyncIo(c, nil, bytes, err) + if err != nil { + return nil, l.opErr("accept", os.NewSyscallError("acceptex", err)) + } + conn := &HvsockConn{ + sock: sock, + } + conn.local.fromRaw((*rawHvsockAddr)(unsafe.Pointer(&addrbuf[0]))) + conn.remote.fromRaw((*rawHvsockAddr)(unsafe.Pointer(&addrbuf[addrlen]))) + sock = nil + return conn, nil +} + +// Close closes the listener, causing any pending Accept calls to fail. +func (l *HvsockListener) Close() error { + return l.sock.Close() +} + +/* Need to finish ConnectEx handling +func DialHvsock(ctx context.Context, addr *HvsockAddr) (*HvsockConn, error) { + sock, err := newHvSocket() + if err != nil { + return nil, err + } + defer func() { + if sock != nil { + sock.Close() + } + }() + c, err := sock.prepareIo() + if err != nil { + return nil, err + } + defer sock.wg.Done() + var bytes uint32 + err = windows.ConnectEx(windows.Handle(sock.handle), sa, nil, 0, &bytes, &c.o) + _, err = sock.asyncIo(ctx, c, nil, bytes, err) + if err != nil { + return nil, err + } + conn := &HvsockConn{ + sock: sock, + remote: *addr, + } + sock = nil + return conn, nil +} +*/ + +func (conn *HvsockConn) opErr(op string, err error) error { + return &net.OpError{Op: op, Net: "hvsock", Source: &conn.local, Addr: &conn.remote, Err: err} +} + +func (conn *HvsockConn) Read(b []byte) (int, error) { + c, err := conn.sock.prepareIo() + if err != nil { + return 0, conn.opErr("read", err) + } + defer conn.sock.wg.Done() + buf := syscall.WSABuf{Buf: &b[0], Len: uint32(len(b))} + var flags, bytes uint32 + err = syscall.WSARecv(conn.sock.handle, &buf, 1, &bytes, &flags, &c.o, nil) + n, err := conn.sock.asyncIo(c, &conn.sock.readDeadline, bytes, err) + if err != nil { + if _, ok := err.(syscall.Errno); ok { + err = os.NewSyscallError("wsarecv", err) + } + return 0, conn.opErr("read", err) + } else if n == 0 { + err = io.EOF + } + return n, err +} + +func (conn *HvsockConn) Write(b []byte) (int, error) { + t := 0 + for len(b) != 0 { + n, err := conn.write(b) + if err != nil { + return t + n, err + } + t += n + b = b[n:] + } + return t, nil +} + +func (conn *HvsockConn) write(b []byte) (int, error) { + c, err := conn.sock.prepareIo() + if err != nil { + return 0, conn.opErr("write", err) + } + defer conn.sock.wg.Done() + buf := syscall.WSABuf{Buf: &b[0], Len: uint32(len(b))} + var bytes uint32 + err = syscall.WSASend(conn.sock.handle, &buf, 1, &bytes, 0, &c.o, nil) + n, err := conn.sock.asyncIo(c, &conn.sock.writeDeadline, bytes, err) + if err != nil { + if _, ok := err.(syscall.Errno); ok { + err = os.NewSyscallError("wsasend", err) + } + return 0, conn.opErr("write", err) + } + return n, err +} + +// Close closes the socket connection, failing any pending read or write calls. +func (conn *HvsockConn) Close() error { + return conn.sock.Close() +} + +func (conn *HvsockConn) shutdown(how int) error { + err := syscall.Shutdown(conn.sock.handle, syscall.SHUT_RD) + if err != nil { + return os.NewSyscallError("shutdown", err) + } + return nil +} + +// CloseRead shuts down the read end of the socket. +func (conn *HvsockConn) CloseRead() error { + err := conn.shutdown(syscall.SHUT_RD) + if err != nil { + return conn.opErr("close", err) + } + return nil +} + +// CloseWrite shuts down the write end of the socket, notifying the other endpoint that +// no more data will be written. +func (conn *HvsockConn) CloseWrite() error { + err := conn.shutdown(syscall.SHUT_WR) + if err != nil { + return conn.opErr("close", err) + } + return nil +} + +// LocalAddr returns the local address of the connection. +func (conn *HvsockConn) LocalAddr() net.Addr { + return &conn.local +} + +// RemoteAddr returns the remote address of the connection. +func (conn *HvsockConn) RemoteAddr() net.Addr { + return &conn.remote +} + +// SetDeadline implements the net.Conn SetDeadline method. +func (conn *HvsockConn) SetDeadline(t time.Time) error { + conn.SetReadDeadline(t) + conn.SetWriteDeadline(t) + return nil +} + +// SetReadDeadline implements the net.Conn SetReadDeadline method. +func (conn *HvsockConn) SetReadDeadline(t time.Time) error { + return conn.sock.SetReadDeadline(t) +} + +// SetWriteDeadline implements the net.Conn SetWriteDeadline method. +func (conn *HvsockConn) SetWriteDeadline(t time.Time) error { + return conn.sock.SetWriteDeadline(t) +} diff --git a/vendor/github.com/Microsoft/go-winio/pipe.go b/vendor/github.com/Microsoft/go-winio/pipe.go index d99eedb64..c645cf483 100644 --- a/vendor/github.com/Microsoft/go-winio/pipe.go +++ b/vendor/github.com/Microsoft/go-winio/pipe.go @@ -3,10 +3,13 @@ package winio import ( + "context" "errors" + "fmt" "io" "net" "os" + "runtime" "syscall" "time" "unsafe" @@ -18,6 +21,48 @@ import ( //sys getNamedPipeInfo(pipe syscall.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) = GetNamedPipeInfo //sys getNamedPipeHandleState(pipe syscall.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) = GetNamedPipeHandleStateW //sys localAlloc(uFlags uint32, length uint32) (ptr uintptr) = LocalAlloc +//sys ntCreateNamedPipeFile(pipe *syscall.Handle, access uint32, oa *objectAttributes, iosb *ioStatusBlock, share uint32, disposition uint32, options uint32, typ uint32, readMode uint32, completionMode uint32, maxInstances uint32, inboundQuota uint32, outputQuota uint32, timeout *int64) (status ntstatus) = ntdll.NtCreateNamedPipeFile +//sys rtlNtStatusToDosError(status ntstatus) (winerr error) = ntdll.RtlNtStatusToDosErrorNoTeb +//sys rtlDosPathNameToNtPathName(name *uint16, ntName *unicodeString, filePart uintptr, reserved uintptr) (status ntstatus) = ntdll.RtlDosPathNameToNtPathName_U +//sys rtlDefaultNpAcl(dacl *uintptr) (status ntstatus) = ntdll.RtlDefaultNpAcl + +type ioStatusBlock struct { + Status, Information uintptr +} + +type objectAttributes struct { + Length uintptr + RootDirectory uintptr + ObjectName *unicodeString + Attributes uintptr + SecurityDescriptor *securityDescriptor + SecurityQoS uintptr +} + +type unicodeString struct { + Length uint16 + MaximumLength uint16 + Buffer uintptr +} + +type securityDescriptor struct { + Revision byte + Sbz1 byte + Control uint16 + Owner uintptr + Group uintptr + Sacl uintptr + Dacl uintptr +} + +type ntstatus int32 + +func (status ntstatus) Err() error { + if status >= 0 { + return nil + } + return rtlNtStatusToDosError(status) +} const ( cERROR_PIPE_BUSY = syscall.Errno(231) @@ -25,21 +70,20 @@ const ( 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 + cSECURITY_SQOS_PRESENT = 0x100000 + cSECURITY_ANONYMOUS = 0 cPIPE_TYPE_MESSAGE = 4 cPIPE_READMODE_MESSAGE = 2 + + cFILE_OPEN = 1 + cFILE_CREATE = 2 + + cFILE_PIPE_MESSAGE_TYPE = 1 + cFILE_PIPE_REJECT_REMOTE_CLIENTS = 2 + + cSE_DACL_PRESENT = 4 ) var ( @@ -137,9 +181,30 @@ func (s pipeAddress) String() string { return string(s) } +// tryDialPipe attempts to dial the pipe at `path` until `ctx` cancellation or timeout. +func tryDialPipe(ctx context.Context, path *string) (syscall.Handle, error) { + for { + select { + case <-ctx.Done(): + return syscall.Handle(0), ctx.Err() + default: + 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 == nil { + return h, nil + } + if err != cERROR_PIPE_BUSY { + return h, &os.PathError{Err: err, Op: "open", Path: *path} + } + // Wait 10 msec and try again. This is a rather simplistic + // view, as we always try each 10 milliseconds. + time.Sleep(time.Millisecond * 10) + } + } +} + // DialPipe connects to a named pipe by path, timing out if the connection // takes longer than the specified duration. If timeout is nil, then we use -// a default timeout of 5 seconds. (We do not use WaitNamedPipe.) +// a default timeout of 2 seconds. (We do not use WaitNamedPipe.) func DialPipe(path string, timeout *time.Duration) (net.Conn, error) { var absTimeout time.Time if timeout != nil { @@ -147,23 +212,22 @@ func DialPipe(path string, timeout *time.Duration) (net.Conn, error) { } else { absTimeout = time.Now().Add(time.Second * 2) } + ctx, _ := context.WithDeadline(context.Background(), absTimeout) + conn, err := DialPipeContext(ctx, path) + if err == context.DeadlineExceeded { + return nil, ErrTimeout + } + return conn, err +} + +// DialPipeContext attempts to connect to a named pipe by `path` until `ctx` +// cancellation or timeout. +func DialPipeContext(ctx context.Context, path string) (net.Conn, error) { 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 - } - if time.Now().After(absTimeout) { - return nil, ErrTimeout - } - - // Wait 10 msec and try again. This is a rather simplistic - // view, as we always try each 10 milliseconds. - time.Sleep(time.Millisecond * 10) - } + h, err = tryDialPipe(ctx, &path) if err != nil { - return nil, &os.PathError{Op: "open", Path: path, Err: err} + return nil, err } var flags uint32 @@ -194,43 +258,87 @@ type acceptResponse struct { } type win32PipeListener struct { - firstHandle syscall.Handle - path string - securityDescriptor []byte - config PipeConfig - acceptCh chan (chan acceptResponse) - closeCh chan int - doneCh chan int + firstHandle syscall.Handle + path string + config PipeConfig + acceptCh chan (chan acceptResponse) + closeCh chan int + doneCh chan int } -func makeServerPipeHandle(path string, securityDescriptor []byte, c *PipeConfig, first bool) (syscall.Handle, error) { - var flags uint32 = cPIPE_ACCESS_DUPLEX | syscall.FILE_FLAG_OVERLAPPED - if first { - flags |= cFILE_FLAG_FIRST_PIPE_INSTANCE - } - - var mode uint32 = cPIPE_REJECT_REMOTE_CLIENTS - if c.MessageMode { - mode |= cPIPE_TYPE_MESSAGE - } - - sa := &syscall.SecurityAttributes{} - sa.Length = uint32(unsafe.Sizeof(*sa)) - if securityDescriptor != nil { - len := uint32(len(securityDescriptor)) - sa.SecurityDescriptor = localAlloc(0, len) - defer localFree(sa.SecurityDescriptor) - copy((*[0xffff]byte)(unsafe.Pointer(sa.SecurityDescriptor))[:], securityDescriptor) - } - h, err := createNamedPipe(path, flags, mode, cPIPE_UNLIMITED_INSTANCES, uint32(c.OutputBufferSize), uint32(c.InputBufferSize), 0, sa) +func makeServerPipeHandle(path string, sd []byte, c *PipeConfig, first bool) (syscall.Handle, error) { + path16, err := syscall.UTF16FromString(path) if err != nil { return 0, &os.PathError{Op: "open", Path: path, Err: err} } + + var oa objectAttributes + oa.Length = unsafe.Sizeof(oa) + + var ntPath unicodeString + if err := rtlDosPathNameToNtPathName(&path16[0], &ntPath, 0, 0).Err(); err != nil { + return 0, &os.PathError{Op: "open", Path: path, Err: err} + } + defer localFree(ntPath.Buffer) + oa.ObjectName = &ntPath + + // The security descriptor is only needed for the first pipe. + if first { + if sd != nil { + len := uint32(len(sd)) + sdb := localAlloc(0, len) + defer localFree(sdb) + copy((*[0xffff]byte)(unsafe.Pointer(sdb))[:], sd) + oa.SecurityDescriptor = (*securityDescriptor)(unsafe.Pointer(sdb)) + } else { + // Construct the default named pipe security descriptor. + var dacl uintptr + if err := rtlDefaultNpAcl(&dacl).Err(); err != nil { + return 0, fmt.Errorf("getting default named pipe ACL: %s", err) + } + defer localFree(dacl) + + sdb := &securityDescriptor{ + Revision: 1, + Control: cSE_DACL_PRESENT, + Dacl: dacl, + } + oa.SecurityDescriptor = sdb + } + } + + typ := uint32(cFILE_PIPE_REJECT_REMOTE_CLIENTS) + if c.MessageMode { + typ |= cFILE_PIPE_MESSAGE_TYPE + } + + disposition := uint32(cFILE_OPEN) + access := uint32(syscall.GENERIC_READ | syscall.GENERIC_WRITE | syscall.SYNCHRONIZE) + if first { + disposition = cFILE_CREATE + // By not asking for read or write access, the named pipe file system + // will put this pipe into an initially disconnected state, blocking + // client connections until the next call with first == false. + access = syscall.SYNCHRONIZE + } + + timeout := int64(-50 * 10000) // 50ms + + var ( + h syscall.Handle + iosb ioStatusBlock + ) + err = ntCreateNamedPipeFile(&h, access, &oa, &iosb, syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE, disposition, 0, typ, 0, 0, 0xffffffff, uint32(c.InputBufferSize), uint32(c.OutputBufferSize), &timeout).Err() + if err != nil { + return 0, &os.PathError{Op: "open", Path: path, Err: err} + } + + runtime.KeepAlive(ntPath) return h, nil } func (l *win32PipeListener) makeServerPipe() (*win32File, error) { - h, err := makeServerPipeHandle(l.path, l.securityDescriptor, &l.config, false) + h, err := makeServerPipeHandle(l.path, nil, &l.config, false) if err != nil { return nil, err } @@ -321,6 +429,28 @@ type PipeConfig struct { OutputBufferSize int32 } +// ListenOnlyPipe creates a listener on a Windows named pipe path, e.g. \\.\pipe\mypipe. +// The pipe must already exist. +func ListenOnlyPipe(path string, c *PipeConfig) (net.Listener, error) { + if c == nil { + c = &PipeConfig{} + } + h, err := makeServerPipeHandle(path, nil, c, false) + if err != nil { + return nil, err + } + l := &win32PipeListener{ + firstHandle: h, + path: path, + config: *c, + acceptCh: make(chan (chan acceptResponse)), + closeCh: make(chan int), + doneCh: make(chan int), + } + go l.listenerRoutine() + return l, nil +} + // ListenPipe creates a listener on a Windows named pipe path, e.g. \\.\pipe\mypipe. // The pipe must not already exist. func ListenPipe(path string, c *PipeConfig) (net.Listener, error) { @@ -341,32 +471,13 @@ func ListenPipe(path string, c *PipeConfig) (net.Listener, error) { if err != nil { return nil, err } - // Create a client handle and connect it. This results in the pipe - // instance always existing, so that clients see ERROR_PIPE_BUSY - // rather than ERROR_FILE_NOT_FOUND. This ties the first instance - // up so that no other instances can be used. This would have been - // cleaner if the Win32 API matched CreateFile with ConnectNamedPipe - // instead of CreateNamedPipe. (Apparently created named pipes are - // considered to be in listening state regardless of whether any - // active calls to ConnectNamedPipe are outstanding.) - 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 - } - // Close the client handle. The server side of the instance will - // still be busy, leading to ERROR_PIPE_BUSY instead of - // ERROR_NOT_FOUND, as long as we don't close the server handle, - // or disconnect the client with DisconnectNamedPipe. - syscall.Close(h2) l := &win32PipeListener{ - firstHandle: h, - path: path, - securityDescriptor: sd, - config: *c, - acceptCh: make(chan (chan acceptResponse)), - closeCh: make(chan int), - doneCh: make(chan int), + firstHandle: h, + path: path, + config: *c, + acceptCh: make(chan (chan acceptResponse)), + closeCh: make(chan int), + doneCh: make(chan int), } go l.listenerRoutine() return l, nil diff --git a/vendor/github.com/Microsoft/go-winio/pkg/guid/guid.go b/vendor/github.com/Microsoft/go-winio/pkg/guid/guid.go new file mode 100644 index 000000000..d0595f667 --- /dev/null +++ b/vendor/github.com/Microsoft/go-winio/pkg/guid/guid.go @@ -0,0 +1,187 @@ +// Package guid provides a GUID type. The backing structure for a GUID is +// identical to that used by the golang.org/x/sys/windows GUID type. +// There are two main binary encodings used for a GUID, the big-endian encoding, +// and the Windows (mixed-endian) encoding. See here for details: +// https://en.wikipedia.org/wiki/Universally_unique_identifier#Encoding +package guid + +import ( + "crypto/rand" + "encoding" + "encoding/binary" + "fmt" + "strconv" + + "golang.org/x/sys/windows" +) + +// Variant specifies which GUID variant (or "type") of the GUID. It determines +// how the entirety of the rest of the GUID is interpreted. +type Variant uint8 + +// The variants specified by RFC 4122. +const ( + // VariantUnknown specifies a GUID variant which does not conform to one of + // the variant encodings specified in RFC 4122. + VariantUnknown Variant = iota + VariantNCS + VariantRFC4122 + VariantMicrosoft + VariantFuture +) + +// Version specifies how the bits in the GUID were generated. For instance, a +// version 4 GUID is randomly generated, and a version 5 is generated from the +// hash of an input string. +type Version uint8 + +var _ = (encoding.TextMarshaler)(GUID{}) +var _ = (encoding.TextUnmarshaler)(&GUID{}) + +// GUID represents a GUID/UUID. It has the same structure as +// golang.org/x/sys/windows.GUID so that it can be used with functions expecting +// that type. It is defined as its own type so that stringification and +// marshaling can be supported. The representation matches that used by native +// Windows code. +type GUID windows.GUID + +// NewV4 returns a new version 4 (pseudorandom) GUID, as defined by RFC 4122. +func NewV4() (GUID, error) { + var b [16]byte + if _, err := rand.Read(b[:]); err != nil { + return GUID{}, err + } + + b[6] = (b[6] & 0x0f) | 0x40 // Version 4 (randomly generated) + b[8] = (b[8] & 0x3f) | 0x80 // RFC4122 variant + + return FromArray(b), nil +} + +func fromArray(b [16]byte, order binary.ByteOrder) GUID { + var g GUID + g.Data1 = order.Uint32(b[0:4]) + g.Data2 = order.Uint16(b[4:6]) + g.Data3 = order.Uint16(b[6:8]) + copy(g.Data4[:], b[8:16]) + return g +} + +func (g GUID) toArray(order binary.ByteOrder) [16]byte { + b := [16]byte{} + order.PutUint32(b[0:4], g.Data1) + order.PutUint16(b[4:6], g.Data2) + order.PutUint16(b[6:8], g.Data3) + copy(b[8:16], g.Data4[:]) + return b +} + +// FromArray constructs a GUID from a big-endian encoding array of 16 bytes. +func FromArray(b [16]byte) GUID { + return fromArray(b, binary.BigEndian) +} + +// ToArray returns an array of 16 bytes representing the GUID in big-endian +// encoding. +func (g GUID) ToArray() [16]byte { + return g.toArray(binary.BigEndian) +} + +// FromWindowsArray constructs a GUID from a Windows encoding array of bytes. +func FromWindowsArray(b [16]byte) GUID { + return fromArray(b, binary.LittleEndian) +} + +// ToWindowsArray returns an array of 16 bytes representing the GUID in Windows +// encoding. +func (g GUID) ToWindowsArray() [16]byte { + return g.toArray(binary.LittleEndian) +} + +func (g GUID) String() string { + return fmt.Sprintf( + "%08x-%04x-%04x-%04x-%012x", + g.Data1, + g.Data2, + g.Data3, + g.Data4[:2], + g.Data4[2:]) +} + +// FromString parses a string containing a GUID and returns the GUID. The only +// format currently supported is the `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` +// format. +func FromString(s string) (GUID, error) { + if len(s) != 36 { + return GUID{}, fmt.Errorf("invalid GUID %q", s) + } + if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' { + return GUID{}, fmt.Errorf("invalid GUID %q", s) + } + + var g GUID + + data1, err := strconv.ParseUint(s[0:8], 16, 32) + if err != nil { + return GUID{}, fmt.Errorf("invalid GUID %q", s) + } + g.Data1 = uint32(data1) + + data2, err := strconv.ParseUint(s[9:13], 16, 16) + if err != nil { + return GUID{}, fmt.Errorf("invalid GUID %q", s) + } + g.Data2 = uint16(data2) + + data3, err := strconv.ParseUint(s[14:18], 16, 16) + if err != nil { + return GUID{}, fmt.Errorf("invalid GUID %q", s) + } + g.Data3 = uint16(data3) + + for i, x := range []int{19, 21, 24, 26, 28, 30, 32, 34} { + v, err := strconv.ParseUint(s[x:x+2], 16, 8) + if err != nil { + return GUID{}, fmt.Errorf("invalid GUID %q", s) + } + g.Data4[i] = uint8(v) + } + + return g, nil +} + +// Variant returns the GUID variant, as defined in RFC 4122. +func (g GUID) Variant() Variant { + b := g.Data4[0] + if b&0x80 == 0 { + return VariantNCS + } else if b&0xc0 == 0x80 { + return VariantRFC4122 + } else if b&0xe0 == 0xc0 { + return VariantMicrosoft + } else if b&0xe0 == 0xe0 { + return VariantFuture + } + return VariantUnknown +} + +// Version returns the GUID version, as defined in RFC 4122. +func (g GUID) Version() Version { + return Version((g.Data3 & 0xF000) >> 12) +} + +// MarshalText returns the textual representation of the GUID. +func (g GUID) MarshalText() ([]byte, error) { + return []byte(g.String()), nil +} + +// UnmarshalText takes the textual representation of a GUID, and unmarhals it +// into this GUID. +func (g *GUID) UnmarshalText(text []byte) error { + g2, err := FromString(string(text)) + if err != nil { + return err + } + *g = g2 + return nil +} diff --git a/vendor/github.com/Microsoft/go-winio/syscall.go b/vendor/github.com/Microsoft/go-winio/syscall.go index 20d64cf41..5cb52bc74 100644 --- a/vendor/github.com/Microsoft/go-winio/syscall.go +++ b/vendor/github.com/Microsoft/go-winio/syscall.go @@ -1,3 +1,3 @@ package winio -//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go file.go pipe.go sd.go fileinfo.go privilege.go backup.go +//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go file.go pipe.go sd.go fileinfo.go privilege.go backup.go hvsock.go diff --git a/vendor/github.com/Microsoft/go-winio/zsyscall_windows.go b/vendor/github.com/Microsoft/go-winio/zsyscall_windows.go index 3f527639a..e26b01faf 100644 --- a/vendor/github.com/Microsoft/go-winio/zsyscall_windows.go +++ b/vendor/github.com/Microsoft/go-winio/zsyscall_windows.go @@ -1,4 +1,4 @@ -// MACHINE GENERATED BY 'go generate' COMMAND; DO NOT EDIT +// Code generated by 'go generate'; DO NOT EDIT. package winio @@ -38,19 +38,25 @@ func errnoErr(e syscall.Errno) error { var ( modkernel32 = windows.NewLazySystemDLL("kernel32.dll") + modws2_32 = windows.NewLazySystemDLL("ws2_32.dll") + modntdll = windows.NewLazySystemDLL("ntdll.dll") modadvapi32 = windows.NewLazySystemDLL("advapi32.dll") procCancelIoEx = modkernel32.NewProc("CancelIoEx") procCreateIoCompletionPort = modkernel32.NewProc("CreateIoCompletionPort") procGetQueuedCompletionStatus = modkernel32.NewProc("GetQueuedCompletionStatus") procSetFileCompletionNotificationModes = modkernel32.NewProc("SetFileCompletionNotificationModes") + procWSAGetOverlappedResult = modws2_32.NewProc("WSAGetOverlappedResult") procConnectNamedPipe = modkernel32.NewProc("ConnectNamedPipe") procCreateNamedPipeW = modkernel32.NewProc("CreateNamedPipeW") procCreateFileW = modkernel32.NewProc("CreateFileW") - procWaitNamedPipeW = modkernel32.NewProc("WaitNamedPipeW") procGetNamedPipeInfo = modkernel32.NewProc("GetNamedPipeInfo") procGetNamedPipeHandleStateW = modkernel32.NewProc("GetNamedPipeHandleStateW") procLocalAlloc = modkernel32.NewProc("LocalAlloc") + procNtCreateNamedPipeFile = modntdll.NewProc("NtCreateNamedPipeFile") + procRtlNtStatusToDosErrorNoTeb = modntdll.NewProc("RtlNtStatusToDosErrorNoTeb") + procRtlDosPathNameToNtPathName_U = modntdll.NewProc("RtlDosPathNameToNtPathName_U") + procRtlDefaultNpAcl = modntdll.NewProc("RtlDefaultNpAcl") procLookupAccountNameW = modadvapi32.NewProc("LookupAccountNameW") procConvertSidToStringSidW = modadvapi32.NewProc("ConvertSidToStringSidW") procConvertStringSecurityDescriptorToSecurityDescriptorW = modadvapi32.NewProc("ConvertStringSecurityDescriptorToSecurityDescriptorW") @@ -69,6 +75,7 @@ var ( procLookupPrivilegeDisplayNameW = modadvapi32.NewProc("LookupPrivilegeDisplayNameW") procBackupRead = modkernel32.NewProc("BackupRead") procBackupWrite = modkernel32.NewProc("BackupWrite") + procbind = modws2_32.NewProc("bind") ) func cancelIoEx(file syscall.Handle, o *syscall.Overlapped) (err error) { @@ -120,6 +127,24 @@ func setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err erro return } +func wsaGetOverlappedResult(h syscall.Handle, o *syscall.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) { + var _p0 uint32 + if wait { + _p0 = 1 + } else { + _p0 = 0 + } + r1, _, e1 := syscall.Syscall6(procWSAGetOverlappedResult.Addr(), 5, uintptr(h), uintptr(unsafe.Pointer(o)), uintptr(unsafe.Pointer(bytes)), uintptr(_p0), uintptr(unsafe.Pointer(flags)), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + func connectNamedPipe(pipe syscall.Handle, o *syscall.Overlapped) (err error) { r1, _, e1 := syscall.Syscall(procConnectNamedPipe.Addr(), 2, uintptr(pipe), uintptr(unsafe.Pointer(o)), 0) if r1 == 0 { @@ -176,27 +201,6 @@ func _createFile(name *uint16, access uint32, mode uint32, sa *syscall.SecurityA return } -func waitNamedPipe(name string, timeout uint32) (err error) { - var _p0 *uint16 - _p0, err = syscall.UTF16PtrFromString(name) - if err != nil { - return - } - return _waitNamedPipe(_p0, timeout) -} - -func _waitNamedPipe(name *uint16, timeout uint32) (err error) { - r1, _, e1 := syscall.Syscall(procWaitNamedPipeW.Addr(), 2, uintptr(unsafe.Pointer(name)), uintptr(timeout), 0) - if r1 == 0 { - if e1 != 0 { - err = errnoErr(e1) - } else { - err = syscall.EINVAL - } - } - return -} - func getNamedPipeInfo(pipe syscall.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) { r1, _, e1 := syscall.Syscall6(procGetNamedPipeInfo.Addr(), 5, uintptr(pipe), uintptr(unsafe.Pointer(flags)), uintptr(unsafe.Pointer(outSize)), uintptr(unsafe.Pointer(inSize)), uintptr(unsafe.Pointer(maxInstances)), 0) if r1 == 0 { @@ -227,6 +231,32 @@ func localAlloc(uFlags uint32, length uint32) (ptr uintptr) { return } +func ntCreateNamedPipeFile(pipe *syscall.Handle, access uint32, oa *objectAttributes, iosb *ioStatusBlock, share uint32, disposition uint32, options uint32, typ uint32, readMode uint32, completionMode uint32, maxInstances uint32, inboundQuota uint32, outputQuota uint32, timeout *int64) (status ntstatus) { + r0, _, _ := syscall.Syscall15(procNtCreateNamedPipeFile.Addr(), 14, uintptr(unsafe.Pointer(pipe)), uintptr(access), uintptr(unsafe.Pointer(oa)), uintptr(unsafe.Pointer(iosb)), uintptr(share), uintptr(disposition), uintptr(options), uintptr(typ), uintptr(readMode), uintptr(completionMode), uintptr(maxInstances), uintptr(inboundQuota), uintptr(outputQuota), uintptr(unsafe.Pointer(timeout)), 0) + status = ntstatus(r0) + return +} + +func rtlNtStatusToDosError(status ntstatus) (winerr error) { + r0, _, _ := syscall.Syscall(procRtlNtStatusToDosErrorNoTeb.Addr(), 1, uintptr(status), 0, 0) + if r0 != 0 { + winerr = syscall.Errno(r0) + } + return +} + +func rtlDosPathNameToNtPathName(name *uint16, ntName *unicodeString, filePart uintptr, reserved uintptr) (status ntstatus) { + r0, _, _ := syscall.Syscall6(procRtlDosPathNameToNtPathName_U.Addr(), 4, uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(ntName)), uintptr(filePart), uintptr(reserved), 0, 0) + status = ntstatus(r0) + return +} + +func rtlDefaultNpAcl(dacl *uintptr) (status ntstatus) { + r0, _, _ := syscall.Syscall(procRtlDefaultNpAcl.Addr(), 1, uintptr(unsafe.Pointer(dacl)), 0, 0) + status = ntstatus(r0) + return +} + func lookupAccountName(systemName *uint16, accountName string, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) { var _p0 *uint16 _p0, err = syscall.UTF16PtrFromString(accountName) @@ -518,3 +548,15 @@ func backupWrite(h syscall.Handle, b []byte, bytesWritten *uint32, abort bool, p } return } + +func bind(s syscall.Handle, name unsafe.Pointer, namelen int32) (err error) { + r1, _, e1 := syscall.Syscall(procbind.Addr(), 3, uintptr(s), uintptr(name), uintptr(namelen)) + if r1 == socketError { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 69d9a59b4..b3a756fd1 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -8,7 +8,8 @@ {"path":"github.com/Azure/go-ansiterm/winterm","checksumSHA1":"3/UphB+6Hbx5otA4PjFjvObT+L4=","revision":"d6e3b3328b783f23731bc4d058875b0371ff8109","revisionTime":"2017-09-29T23:40:23Z","version":"master","versionExact":"master"}, {"path":"github.com/DataDog/datadog-go/statsd","checksumSHA1":"WvApwvvSe3i/3KO8300dyeFmkbI=","revision":"b10af4b12965a1ad08d164f57d14195b4140d8de","revisionTime":"2017-08-09T10:47:06Z"}, {"path":"github.com/LK4D4/joincontext","checksumSHA1":"Jmf4AnrptgBdQ5TPBJ2M89nooIQ=","revision":"1724345da6d5bcc8b66fefb843b607ab918e175c","revisionTime":"2017-10-26T17:01:39Z"}, - {"path":"github.com/Microsoft/go-winio","checksumSHA1":"PbR6ZKoLeSZl8aXxDQqXih0wSgE=","revision":"97e4973ce50b2ff5f09635a57e2b88a037aae829","revisionTime":"2018-08-23T22:24:21Z"}, + {"path":"github.com/Microsoft/go-winio","checksumSHA1":"nEVw+80Junfo7iEY7ThP7Ci9Pyk=","origin":"github.com/endocrimes/go-winio","revision":"fb47a8b419480a700368c176bc1d5d7e3393b98d","revisionTime":"2019-06-20T17:03:19Z","version":"dani/safe-relisten","versionExact":"dani/safe-relisten"}, + {"path":"github.com/Microsoft/go-winio/pkg/guid","checksumSHA1":"/ykkyb7gmtZC68n7T24xwbmlCBc=","origin":"github.com/endocrimes/go-winio/pkg/guid","revision":"fb47a8b419480a700368c176bc1d5d7e3393b98d","revisionTime":"2019-06-20T17:03:19Z","version":"dani/safe-relisten","versionExact":"dani/safe-relisten"}, {"path":"github.com/NVIDIA/gpu-monitoring-tools","checksumSHA1":"kF1vk+8Xvb3nGBiw9+qbUc0SZ4M=","revision":"86f2a9fac6c5b597dc494420005144b8ef7ec9fb","revisionTime":"2018-08-29T22:20:09Z"}, {"path":"github.com/NVIDIA/gpu-monitoring-tools/bindings/go/nvml","checksumSHA1":"P8FATSSgpe5A17FyPrGpsX95Xw8=","revision":"86f2a9fac6c5b597dc494420005144b8ef7ec9fb","revisionTime":"2018-08-29T22:20:09Z"}, {"path":"github.com/NYTimes/gziphandler","checksumSHA1":"jktW57+vJsziNVPeXMCoujTzdW4=","revision":"97ae7fbaf81620fe97840685304a78a306a39c64","revisionTime":"2017-09-16T00:36:49Z"}, From e6daf3b5bd581ccdeaa848b0ff7a1a55672a55fe Mon Sep 17 00:00:00 2001 From: Danielle Lancashire Date: Fri, 28 Jun 2019 13:35:41 +0200 Subject: [PATCH 3/7] fifo: Require that fifos do not exist for create Although this operation is safe on linux, it is not safe on Windows when using the named pipe interface. To provide a ~reasonable common api abstraction, here we switch to returning File exists errors on the unix api. --- client/lib/fifo/fifo_unix.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/lib/fifo/fifo_unix.go b/client/lib/fifo/fifo_unix.go index 1533b0bee..c1d90a3ad 100644 --- a/client/lib/fifo/fifo_unix.go +++ b/client/lib/fifo/fifo_unix.go @@ -11,18 +11,18 @@ import ( ) // CreateAndRead creates a fifo at the given path, and returns an open function for reading. -// The fifo must not exist already, or that it's already a fifo file +// For compatibility with windows, the fifo must not exist already. // // It returns a reader open function that may block until a writer opens // so it's advised to run it in a goroutine different from reader goroutine func CreateAndRead(path string) (func() (io.ReadCloser, error), error) { // create first - if err := mkfifo(path, 0600); err != nil && !os.IsExist(err) { + if err := mkfifo(path, 0600); err != nil { return nil, fmt.Errorf("error creating fifo %v: %v", path, err) } return func() (io.ReadCloser, error) { - return os.OpenFile(path, unix.O_RDONLY, os.ModeNamedPipe) + return OpenReader(path) }, nil } From aff554deec35470ac33c6168960938932de6d329 Mon Sep 17 00:00:00 2001 From: Danielle Lancashire Date: Fri, 28 Jun 2019 13:49:07 +0200 Subject: [PATCH 4/7] appveyor: Run logmon tests --- appveyor.yml | 1 + client/lib/fifo/fifo_windows.go | 5 ++-- client/logmon/logmon_test.go | 50 ++++++++++++++++++++++----------- 3 files changed, 38 insertions(+), 18 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 576c6f80c..18906d917 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -46,6 +46,7 @@ test_script: gotestsum --junitfile results.xml github.com/hashicorp/nomad/drivers/docker github.com/hashicorp/nomad/client/lib/fifo + github.com/hashicorp/nomad/client/logmon # on_finish: # - ps: | # Push-AppveyorArtifact (Resolve-Path .\results.xml) diff --git a/client/lib/fifo/fifo_windows.go b/client/lib/fifo/fifo_windows.go index 4feaa63da..9963570bb 100644 --- a/client/lib/fifo/fifo_windows.go +++ b/client/lib/fifo/fifo_windows.go @@ -1,6 +1,7 @@ package fifo import ( + "fmt" "io" "net" "os" @@ -75,7 +76,7 @@ func CreateAndRead(path string) (func() (io.ReadCloser, error), error) { OutputBufferSize: PipeBufferSize, }) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to create fifo: %v", err) } return func() (io.ReadCloser, error) { @@ -88,7 +89,7 @@ func CreateAndRead(path string) (func() (io.ReadCloser, error), error) { func OpenReader(path string) (io.ReadCloser, error) { l, err := winio.ListenOnlyPipe(path, nil) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to open fifo listener: %v", err) } return &winFIFO{listener: l}, nil diff --git a/client/logmon/logmon_test.go b/client/logmon/logmon_test.go index 609b3db6e..c0de5fb47 100644 --- a/client/logmon/logmon_test.go +++ b/client/logmon/logmon_test.go @@ -6,6 +6,7 @@ import ( "io/ioutil" "os" "path/filepath" + "runtime" "testing" "github.com/hashicorp/nomad/client/lib/fifo" @@ -16,13 +17,23 @@ import ( func TestLogmon_Start_rotate(t *testing.T) { require := require.New(t) + + stdoutLog := "stdout" + stderrLog := "stderr" + + var stdoutFifoPath, stderrFifoPath string + dir, err := ioutil.TempDir("", "nomadtest") require.NoError(err) defer os.RemoveAll(dir) - stdoutLog := "stdout" - stdoutFifoPath := filepath.Join(dir, "stdout.fifo") - stderrLog := "stderr" - stderrFifoPath := filepath.Join(dir, "stderr.fifo") + + if runtime.GOOS == "windows" { + stdoutFifoPath = "//./pipe/test-rotate.stdout" + stderrFifoPath = "//./pipe/test-rotate.stderr" + } else { + stdoutFifoPath = filepath.Join(dir, "stdout.fifo") + stderrFifoPath = filepath.Join(dir, "stderr.fifo") + } cfg := &LogConfig{ LogDir: dir, @@ -69,13 +80,23 @@ func TestLogmon_Start_rotate(t *testing.T) { // asserts that calling Start twice restarts the log rotator func TestLogmon_Start_restart(t *testing.T) { require := require.New(t) + + stdoutLog := "stdout" + stderrLog := "stderr" + + var stdoutFifoPath, stderrFifoPath string + dir, err := ioutil.TempDir("", "nomadtest") require.NoError(err) defer os.RemoveAll(dir) - stdoutLog := "stdout" - stdoutFifoPath := filepath.Join(dir, "stdout.fifo") - stderrLog := "stderr" - stderrFifoPath := filepath.Join(dir, "stderr.fifo") + + if runtime.GOOS == "windows" { + stdoutFifoPath = "//./pipe/test-restart.stdout" + stderrFifoPath = "//./pipe/test-restart.stderr" + } else { + stdoutFifoPath = filepath.Join(dir, "stdout.fifo") + stderrFifoPath = filepath.Join(dir, "stderr.fifo") + } cfg := &LogConfig{ LogDir: dir, @@ -122,6 +143,11 @@ func TestLogmon_Start_restart(t *testing.T) { require.NoError(err) }) + require.NoError(lm.Stop()) + + // Start logmon again and assert that it appended to the file + require.NoError(lm.Start(cfg)) + stdout, err = fifo.OpenWriter(stdoutFifoPath) require.NoError(err) stderr, err = fifo.OpenWriter(stderrFifoPath) @@ -140,14 +166,6 @@ func TestLogmon_Start_restart(t *testing.T) { require.NoError(err) }) - // Start logmon again and assert that it appended to the file - require.NoError(lm.Start(cfg)) - - stdout, err = fifo.OpenWriter(stdoutFifoPath) - require.NoError(err) - stderr, err = fifo.OpenWriter(stderrFifoPath) - require.NoError(err) - _, err = stdout.Write([]byte("st\n")) require.NoError(err) testutil.WaitForResult(func() (bool, error) { From 2e5aba918864b9fb50fbac885971d0388f246ab7 Mon Sep 17 00:00:00 2001 From: Danielle Lancashire Date: Fri, 28 Jun 2019 16:35:20 +0200 Subject: [PATCH 5/7] logmon: Add windows compatibility test --- client/logmon/logmon_test.go | 98 +++++++++++++++++++++++++++++++++++- 1 file changed, 96 insertions(+), 2 deletions(-) diff --git a/client/logmon/logmon_test.go b/client/logmon/logmon_test.go index c0de5fb47..d3105f7b3 100644 --- a/client/logmon/logmon_test.go +++ b/client/logmon/logmon_test.go @@ -77,8 +77,13 @@ func TestLogmon_Start_rotate(t *testing.T) { require.NoError(lm.Stop()) } -// asserts that calling Start twice restarts the log rotator -func TestLogmon_Start_restart(t *testing.T) { +// asserts that calling Start twice restarts the log rotator and that any logs +// published while the listener was unavailable are recieved. +func TestLogmon_Start_restart_flusheslogs(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("windows does not support pushing data to a pipe with no servers") + } + require := require.New(t) stdoutLog := "stdout" @@ -180,3 +185,92 @@ func TestLogmon_Start_restart(t *testing.T) { require.NoError(err) }) } + +// asserts that calling Start twice restarts the log rotator +func TestLogmon_Start_restart(t *testing.T) { + require := require.New(t) + + stdoutLog := "stdout" + stderrLog := "stderr" + + var stdoutFifoPath, stderrFifoPath string + + dir, err := ioutil.TempDir("", "nomadtest") + require.NoError(err) + defer os.RemoveAll(dir) + + if runtime.GOOS == "windows" { + stdoutFifoPath = "//./pipe/test-restart.stdout" + stderrFifoPath = "//./pipe/test-restart.stderr" + } else { + stdoutFifoPath = filepath.Join(dir, "stdout.fifo") + stderrFifoPath = filepath.Join(dir, "stderr.fifo") + } + + cfg := &LogConfig{ + LogDir: dir, + StdoutLogFile: stdoutLog, + StdoutFifo: stdoutFifoPath, + StderrLogFile: stderrLog, + StderrFifo: stderrFifoPath, + MaxFiles: 2, + MaxFileSizeMB: 1, + } + + lm := NewLogMon(testlog.HCLogger(t)) + impl, ok := lm.(*logmonImpl) + require.True(ok) + require.NoError(lm.Start(cfg)) + + stdout, err := fifo.OpenWriter(stdoutFifoPath) + require.NoError(err) + stderr, err := fifo.OpenWriter(stderrFifoPath) + require.NoError(err) + + // Write a string and assert it was written to the file + _, err = stdout.Write([]byte("test\n")) + require.NoError(err) + + testutil.WaitForResult(func() (bool, error) { + raw, err := ioutil.ReadFile(filepath.Join(dir, "stdout.0")) + if err != nil { + return false, err + } + return "test\n" == string(raw), fmt.Errorf("unexpected stdout %q", string(raw)) + }, func(err error) { + require.NoError(err) + }) + require.True(impl.tl.IsRunning()) + + // Close stdout and assert that logmon no longer writes to the file + require.NoError(stdout.Close()) + require.NoError(stderr.Close()) + + testutil.WaitForResult(func() (bool, error) { + return !impl.tl.IsRunning(), fmt.Errorf("logmon is still running") + }, func(err error) { + require.NoError(err) + }) + + // Start logmon again and assert that it can recieve logs again + require.NoError(lm.Start(cfg)) + + stdout, err = fifo.OpenWriter(stdoutFifoPath) + require.NoError(err) + stderr, err = fifo.OpenWriter(stderrFifoPath) + require.NoError(err) + + _, err = stdout.Write([]byte("test\n")) + require.NoError(err) + testutil.WaitForResult(func() (bool, error) { + raw, err := ioutil.ReadFile(filepath.Join(dir, "stdout.0")) + if err != nil { + return false, err + } + + expected := "test\ntest\n" == string(raw) + return expected, fmt.Errorf("unexpected stdout %q", string(raw)) + }, func(err error) { + require.NoError(err) + }) +} From 8148466da63e0724cf54174841ee3b6a2690a4a0 Mon Sep 17 00:00:00 2001 From: Danielle Lancashire Date: Fri, 28 Jun 2019 16:27:10 +0200 Subject: [PATCH 6/7] fifo: Close connections and cleanup lock handling --- client/lib/fifo/fifo_windows.go | 10 ++++++-- client/logmon/logmon_test.go | 41 +++++++++++++-------------------- 2 files changed, 24 insertions(+), 27 deletions(-) diff --git a/client/lib/fifo/fifo_windows.go b/client/lib/fifo/fifo_windows.go index 9963570bb..08396c50a 100644 --- a/client/lib/fifo/fifo_windows.go +++ b/client/lib/fifo/fifo_windows.go @@ -23,7 +23,6 @@ type winFIFO struct { func (f *winFIFO) Read(p []byte) (n int, err error) { f.connLock.Lock() - defer f.connLock.Unlock() if f.conn == nil { c, err := f.listener.Accept() if err != nil { @@ -32,6 +31,7 @@ func (f *winFIFO) Read(p []byte) (n int, err error) { f.conn = c } + f.connLock.Unlock() // If the connection is closed then we need to close the listener // to emulate unix fifo behavior @@ -44,7 +44,6 @@ func (f *winFIFO) Read(p []byte) (n int, err error) { func (f *winFIFO) Write(p []byte) (n int, err error) { f.connLock.Lock() - defer f.connLock.Unlock() if f.conn == nil { c, err := f.listener.Accept() if err != nil { @@ -53,11 +52,13 @@ func (f *winFIFO) Write(p []byte) (n int, err error) { f.conn = c } + f.connLock.Unlock() // If the connection is closed then we need to close the listener // to emulate unix fifo behavior n, err = f.conn.Write(p) if err == io.EOF { + f.conn.Close() f.listener.Close() } return n, err @@ -65,6 +66,11 @@ func (f *winFIFO) Write(p []byte) (n int, err error) { } func (f *winFIFO) Close() error { + f.connLock.Lock() + if f.conn != nil { + f.conn.Close() + } + f.connLock.Unlock() return f.listener.Close() } diff --git a/client/logmon/logmon_test.go b/client/logmon/logmon_test.go index d3105f7b3..82b71d451 100644 --- a/client/logmon/logmon_test.go +++ b/client/logmon/logmon_test.go @@ -17,10 +17,6 @@ import ( func TestLogmon_Start_rotate(t *testing.T) { require := require.New(t) - - stdoutLog := "stdout" - stderrLog := "stderr" - var stdoutFifoPath, stderrFifoPath string dir, err := ioutil.TempDir("", "nomadtest") @@ -37,9 +33,9 @@ func TestLogmon_Start_rotate(t *testing.T) { cfg := &LogConfig{ LogDir: dir, - StdoutLogFile: stdoutLog, + StdoutLogFile: "stdout", StdoutFifo: stdoutFifoPath, - StderrLogFile: stderrLog, + StderrLogFile: "stderr", StderrFifo: stderrFifoPath, MaxFiles: 2, MaxFileSizeMB: 1, @@ -78,17 +74,13 @@ func TestLogmon_Start_rotate(t *testing.T) { } // asserts that calling Start twice restarts the log rotator and that any logs -// published while the listener was unavailable are recieved. +// published while the listener was unavailable are received. func TestLogmon_Start_restart_flusheslogs(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("windows does not support pushing data to a pipe with no servers") } require := require.New(t) - - stdoutLog := "stdout" - stderrLog := "stderr" - var stdoutFifoPath, stderrFifoPath string dir, err := ioutil.TempDir("", "nomadtest") @@ -105,9 +97,9 @@ func TestLogmon_Start_restart_flusheslogs(t *testing.T) { cfg := &LogConfig{ LogDir: dir, - StdoutLogFile: stdoutLog, + StdoutLogFile: "stdout", StdoutFifo: stdoutFifoPath, - StderrLogFile: stderrLog, + StderrLogFile: "stderr", StderrFifo: stderrFifoPath, MaxFiles: 2, MaxFileSizeMB: 1, @@ -148,11 +140,6 @@ func TestLogmon_Start_restart_flusheslogs(t *testing.T) { require.NoError(err) }) - require.NoError(lm.Stop()) - - // Start logmon again and assert that it appended to the file - require.NoError(lm.Start(cfg)) - stdout, err = fifo.OpenWriter(stdoutFifoPath) require.NoError(err) stderr, err = fifo.OpenWriter(stderrFifoPath) @@ -171,6 +158,14 @@ func TestLogmon_Start_restart_flusheslogs(t *testing.T) { require.NoError(err) }) + // Start logmon again and assert that it appended to the file + require.NoError(lm.Start(cfg)) + + stdout, err = fifo.OpenWriter(stdoutFifoPath) + require.NoError(err) + stderr, err = fifo.OpenWriter(stderrFifoPath) + require.NoError(err) + _, err = stdout.Write([]byte("st\n")) require.NoError(err) testutil.WaitForResult(func() (bool, error) { @@ -189,10 +184,6 @@ func TestLogmon_Start_restart_flusheslogs(t *testing.T) { // asserts that calling Start twice restarts the log rotator func TestLogmon_Start_restart(t *testing.T) { require := require.New(t) - - stdoutLog := "stdout" - stderrLog := "stderr" - var stdoutFifoPath, stderrFifoPath string dir, err := ioutil.TempDir("", "nomadtest") @@ -209,9 +200,9 @@ func TestLogmon_Start_restart(t *testing.T) { cfg := &LogConfig{ LogDir: dir, - StdoutLogFile: stdoutLog, + StdoutLogFile: "stdout", StdoutFifo: stdoutFifoPath, - StderrLogFile: stderrLog, + StderrLogFile: "stderr", StderrFifo: stderrFifoPath, MaxFiles: 2, MaxFileSizeMB: 1, @@ -252,7 +243,7 @@ func TestLogmon_Start_restart(t *testing.T) { require.NoError(err) }) - // Start logmon again and assert that it can recieve logs again + // Start logmon again and assert that it can receive logs again require.NoError(lm.Start(cfg)) stdout, err = fifo.OpenWriter(stdoutFifoPath) From c712fdcbd9a187c21cbc7c2291cb696d526a0df8 Mon Sep 17 00:00:00 2001 From: Danielle Lancashire Date: Tue, 2 Jul 2019 13:12:54 +0200 Subject: [PATCH 7/7] fifo: Safer access to Conn --- client/lib/fifo/fifo_windows.go | 34 +++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/client/lib/fifo/fifo_windows.go b/client/lib/fifo/fifo_windows.go index 08396c50a..b8ea6b4b9 100644 --- a/client/lib/fifo/fifo_windows.go +++ b/client/lib/fifo/fifo_windows.go @@ -21,21 +21,29 @@ type winFIFO struct { connLock sync.Mutex } -func (f *winFIFO) Read(p []byte) (n int, err error) { +func (f *winFIFO) ensureConn() (net.Conn, error) { f.connLock.Lock() + defer f.connLock.Unlock() if f.conn == nil { c, err := f.listener.Accept() if err != nil { - return 0, err + return nil, err } - f.conn = c } - f.connLock.Unlock() + + return f.conn, nil +} + +func (f *winFIFO) Read(p []byte) (n int, err error) { + conn, err := f.ensureConn() + if err != nil { + return 0, err + } // If the connection is closed then we need to close the listener // to emulate unix fifo behavior - n, err = f.conn.Read(p) + n, err = conn.Read(p) if err == io.EOF { f.listener.Close() } @@ -43,22 +51,16 @@ func (f *winFIFO) Read(p []byte) (n int, err error) { } func (f *winFIFO) Write(p []byte) (n int, err error) { - f.connLock.Lock() - if f.conn == nil { - c, err := f.listener.Accept() - if err != nil { - return 0, err - } - - f.conn = c + conn, err := f.ensureConn() + if err != nil { + return 0, err } - f.connLock.Unlock() // If the connection is closed then we need to close the listener // to emulate unix fifo behavior - n, err = f.conn.Write(p) + n, err = conn.Write(p) if err == io.EOF { - f.conn.Close() + conn.Close() f.listener.Close() } return n, err