mirror of
https://github.com/coder/coder.git
synced 2025-07-12 00:14:10 +00:00
* feat: Add workspace agent for SSH This adds the initial agent that supports TTY and execution over SSH. It functions across MacOS, Windows, and Linux. This does not handle the coderd interaction yet, but does setup a simple path forward. * Fix pty tests on Windows * Fix log race * Lock around dial error to fix log output * Fix context return early * fix: Leaking yamux session after HTTP handler is closed Closes #317. We depended on the context canceling the yamux connection, but this isn't a sync operation. Explicitly calling close ensures the handler waits for yamux to complete before exit. * Lock around close return * Force failure with log * Fix failed handler * Upgrade dep * Fix defer inside loops * Fix context cancel for HTTP requests * Fix resize
111 lines
2.5 KiB
Go
111 lines
2.5 KiB
Go
//go:build windows
|
|
// +build windows
|
|
|
|
package pty
|
|
|
|
import (
|
|
"io"
|
|
"os"
|
|
"sync"
|
|
"unsafe"
|
|
|
|
"golang.org/x/sys/windows"
|
|
|
|
"golang.org/x/xerrors"
|
|
)
|
|
|
|
var (
|
|
kernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
|
procResizePseudoConsole = kernel32.NewProc("ResizePseudoConsole")
|
|
procCreatePseudoConsole = kernel32.NewProc("CreatePseudoConsole")
|
|
procClosePseudoConsole = kernel32.NewProc("ClosePseudoConsole")
|
|
)
|
|
|
|
// See: https://docs.microsoft.com/en-us/windows/console/creating-a-pseudoconsole-session
|
|
func newPty() (PTY, error) {
|
|
// We use the CreatePseudoConsole API which was introduced in build 17763
|
|
vsn := windows.RtlGetVersion()
|
|
if vsn.MajorVersion < 10 ||
|
|
vsn.BuildNumber < 17763 {
|
|
// If the CreatePseudoConsole API is not available, we fall back to a simpler
|
|
// implementation that doesn't create an actual PTY - just uses os.Pipe
|
|
return nil, xerrors.Errorf("pty not supported")
|
|
}
|
|
|
|
ptyWindows := &ptyWindows{}
|
|
|
|
var err error
|
|
ptyWindows.inputRead, ptyWindows.inputWrite, err = os.Pipe()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ptyWindows.outputRead, ptyWindows.outputWrite, err = os.Pipe()
|
|
|
|
consoleSize := uintptr(80) + (uintptr(80) << 16)
|
|
ret, _, err := procCreatePseudoConsole.Call(
|
|
consoleSize,
|
|
uintptr(ptyWindows.inputRead.Fd()),
|
|
uintptr(ptyWindows.outputWrite.Fd()),
|
|
0,
|
|
uintptr(unsafe.Pointer(&ptyWindows.console)),
|
|
)
|
|
if int32(ret) < 0 {
|
|
return nil, xerrors.Errorf("create pseudo console (%d): %w", int32(ret), err)
|
|
}
|
|
return ptyWindows, nil
|
|
}
|
|
|
|
type ptyWindows struct {
|
|
console windows.Handle
|
|
|
|
outputWrite *os.File
|
|
outputRead *os.File
|
|
inputWrite *os.File
|
|
inputRead *os.File
|
|
|
|
closeMutex sync.Mutex
|
|
closed bool
|
|
}
|
|
|
|
func (p *ptyWindows) Output() io.ReadWriter {
|
|
return readWriter{
|
|
Reader: p.outputRead,
|
|
Writer: p.outputWrite,
|
|
}
|
|
}
|
|
|
|
func (p *ptyWindows) Input() io.ReadWriter {
|
|
return readWriter{
|
|
Reader: p.inputRead,
|
|
Writer: p.inputWrite,
|
|
}
|
|
}
|
|
|
|
func (p *ptyWindows) Resize(cols uint16, rows uint16) error {
|
|
ret, _, err := procResizePseudoConsole.Call(uintptr(p.console), uintptr(cols)+(uintptr(rows)<<16))
|
|
if ret != 0 {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (p *ptyWindows) Close() error {
|
|
p.closeMutex.Lock()
|
|
defer p.closeMutex.Unlock()
|
|
if p.closed {
|
|
return nil
|
|
}
|
|
p.closed = true
|
|
_ = p.outputWrite.Close()
|
|
_ = p.outputRead.Close()
|
|
_ = p.inputWrite.Close()
|
|
_ = p.inputRead.Close()
|
|
|
|
ret, _, err := procClosePseudoConsole.Call(uintptr(p.console))
|
|
if ret < 0 {
|
|
return xerrors.Errorf("close pseudo console: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|