Skip to content

Commit bda13a2

Browse files
deansheathercoadler
authored andcommitted
fix: make terminal raw in ssh command on windows (coder#12990)
(cherry picked from commit d426569)
1 parent 353888a commit bda13a2

File tree

4 files changed

+146
-8
lines changed

4 files changed

+146
-8
lines changed

cli/ssh.go

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,18 @@ import (
2525
"golang.org/x/xerrors"
2626
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
2727

28-
"github.com/coder/retry"
29-
"github.com/coder/serpent"
30-
3128
"cdr.dev/slog"
3229
"cdr.dev/slog/sloggers/sloghuman"
33-
3430
"github.com/coder/coder/v2/cli/cliui"
3531
"github.com/coder/coder/v2/cli/cliutil"
3632
"github.com/coder/coder/v2/coderd/autobuild/notify"
3733
"github.com/coder/coder/v2/coderd/util/ptr"
3834
"github.com/coder/coder/v2/codersdk"
3935
"github.com/coder/coder/v2/codersdk/workspacesdk"
4036
"github.com/coder/coder/v2/cryptorand"
37+
"github.com/coder/coder/v2/pty"
38+
"github.com/coder/retry"
39+
"github.com/coder/serpent"
4140
)
4241

4342
var (
@@ -341,15 +340,22 @@ func (r *RootCmd) ssh() *serpent.Command {
341340
}
342341
}
343342

344-
stdoutFile, validOut := inv.Stdout.(*os.File)
345343
stdinFile, validIn := inv.Stdin.(*os.File)
346-
if validOut && validIn && isatty.IsTerminal(stdoutFile.Fd()) {
347-
state, err := term.MakeRaw(int(stdinFile.Fd()))
344+
stdoutFile, validOut := inv.Stdout.(*os.File)
345+
if validIn && validOut && isatty.IsTerminal(stdinFile.Fd()) && isatty.IsTerminal(stdoutFile.Fd()) {
346+
inState, err := pty.MakeInputRaw(stdinFile.Fd())
347+
if err != nil {
348+
return err
349+
}
350+
defer func() {
351+
_ = pty.RestoreTerminal(stdinFile.Fd(), inState)
352+
}()
353+
outState, err := pty.MakeOutputRaw(stdoutFile.Fd())
348354
if err != nil {
349355
return err
350356
}
351357
defer func() {
352-
_ = term.Restore(int(stdinFile.Fd()), state)
358+
_ = pty.RestoreTerminal(stdoutFile.Fd(), outState)
353359
}()
354360

355361
windowChange := listenWindowSize(ctx)

pty/terminal.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package pty
2+
3+
// TerminalState differs per-platform.
4+
type TerminalState struct {
5+
state terminalState
6+
}
7+
8+
// MakeInputRaw calls term.MakeRaw on non-Windows platforms. On Windows it sets
9+
// special terminal modes that enable VT100 emulation as well as setting the
10+
// same modes that term.MakeRaw sets.
11+
//
12+
//nolint:revive
13+
func MakeInputRaw(fd uintptr) (*TerminalState, error) {
14+
return makeInputRaw(fd)
15+
}
16+
17+
// MakeOutputRaw does nothing on non-Windows platforms. On Windows it sets
18+
// special terminal modes that enable VT100 emulation as well as setting the
19+
// same modes that term.MakeRaw sets.
20+
//
21+
//nolint:revive
22+
func MakeOutputRaw(fd uintptr) (*TerminalState, error) {
23+
return makeOutputRaw(fd)
24+
}
25+
26+
// RestoreTerminal restores the terminal back to its original state.
27+
//
28+
//nolint:revive
29+
func RestoreTerminal(fd uintptr, state *TerminalState) error {
30+
return restoreTerminal(fd, state)
31+
}

pty/terminal_other.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//go:build !windows
2+
// +build !windows
3+
4+
package pty
5+
6+
import "golang.org/x/term"
7+
8+
type terminalState *term.State
9+
10+
//nolint:revive
11+
func makeInputRaw(fd uintptr) (*TerminalState, error) {
12+
s, err := term.MakeRaw(int(fd))
13+
if err != nil {
14+
return nil, err
15+
}
16+
return &TerminalState{
17+
state: s,
18+
}, nil
19+
}
20+
21+
//nolint:revive
22+
func makeOutputRaw(_ uintptr) (*TerminalState, error) {
23+
// Does nothing. makeInputRaw does enough for both input and output.
24+
return &TerminalState{
25+
state: nil,
26+
}, nil
27+
}
28+
29+
//nolint:revive
30+
func restoreTerminal(fd uintptr, state *TerminalState) error {
31+
if state == nil || state.state == nil {
32+
return nil
33+
}
34+
35+
return term.Restore(int(fd), state.state)
36+
}

pty/terminal_windows.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
//go:build windows
2+
// +build windows
3+
4+
package pty
5+
6+
import "golang.org/x/sys/windows"
7+
8+
type terminalState uint32
9+
10+
// This is adapted from term.MakeRaw, but adds
11+
// ENABLE_VIRTUAL_TERMINAL_PROCESSING to the output mode and
12+
// ENABLE_VIRTUAL_TERMINAL_INPUT to the input mode.
13+
//
14+
// See: https://github.com/golang/term/blob/5b15d269ba1f54e8da86c8aa5574253aea0c2198/term_windows.go#L23
15+
//
16+
// Copyright 2019 The Go Authors. BSD-3-Clause license. See:
17+
// https://github.com/golang/term/blob/master/LICENSE
18+
func makeRaw(handle windows.Handle, input bool) (uint32, error) {
19+
var prevState uint32
20+
if err := windows.GetConsoleMode(handle, &prevState); err != nil {
21+
return 0, err
22+
}
23+
24+
var raw uint32
25+
if input {
26+
raw = prevState &^ (windows.ENABLE_ECHO_INPUT | windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_LINE_INPUT | windows.ENABLE_PROCESSED_OUTPUT)
27+
raw |= windows.ENABLE_VIRTUAL_TERMINAL_INPUT
28+
} else {
29+
raw = prevState | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING
30+
}
31+
32+
if err := windows.SetConsoleMode(handle, raw); err != nil {
33+
return 0, err
34+
}
35+
return prevState, nil
36+
}
37+
38+
//nolint:revive
39+
func makeInputRaw(handle uintptr) (*TerminalState, error) {
40+
prevState, err := makeRaw(windows.Handle(handle), true)
41+
if err != nil {
42+
return nil, err
43+
}
44+
45+
return &TerminalState{
46+
state: terminalState(prevState),
47+
}, nil
48+
}
49+
50+
//nolint:revive
51+
func makeOutputRaw(handle uintptr) (*TerminalState, error) {
52+
prevState, err := makeRaw(windows.Handle(handle), false)
53+
if err != nil {
54+
return nil, err
55+
}
56+
57+
return &TerminalState{
58+
state: terminalState(prevState),
59+
}, nil
60+
}
61+
62+
//nolint:revive
63+
func restoreTerminal(handle uintptr, state *TerminalState) error {
64+
return windows.SetConsoleMode(windows.Handle(handle), uint32(state.state))
65+
}

0 commit comments

Comments
 (0)