Repository navigation
Expand file tree
/
Copy pathpty.go
More file actions
108 lines (92 loc) · 2.87 KB
/
Copy pathpty.go
File metadata and controls
108 lines (92 loc) · 2.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
package ssh
import (
"bytes"
"errors"
"io"
"os/exec"
)
// ErrUnsupported is returned when the platform does not support PTY.
var ErrUnsupported = errors.New("pty unsupported")
// NewPtyWriter creates a writer that handles when the session has a active
// PTY, replacing the \n with \r\n.
func NewPtyWriter(w io.Writer) io.Writer {
return ptyWriter{
w: w,
}
}
var _ io.Writer = ptyWriter{}
type ptyWriter struct {
w io.Writer
}
func (w ptyWriter) Write(p []byte) (int, error) {
m := len(p)
// normalize \n to \r\n when pty is accepted.
// this is a hardcoded shortcut since we don't support terminal modes.
p = bytes.ReplaceAll(p, []byte{'\n'}, []byte{'\r', '\n'})
p = bytes.ReplaceAll(p, []byte{'\r', '\r', '\n'}, []byte{'\r', '\n'})
n, err := w.w.Write(p)
if n > m {
n = m
}
return n, err
}
// NewPtyReadWriter return an io.ReadWriter that delegates the read to the
// given io.ReadWriter, and the writes to a ptyWriter.
func NewPtyReadWriter(rw io.ReadWriter) io.ReadWriter {
return readWriterDelegate{
w: NewPtyWriter(rw),
r: rw,
}
}
var _ io.ReadWriter = readWriterDelegate{}
type readWriterDelegate struct {
w io.Writer
r io.Reader
}
func (rw readWriterDelegate) Read(p []byte) (n int, err error) {
return rw.r.Read(p)
}
func (rw readWriterDelegate) Write(p []byte) (n int, err error) {
return rw.w.Write(p)
}
// PtyStartOption configures how Pty.Start runs a command.
type PtyStartOption func(*ptyStartConfig)
type ptyStartConfig struct {
jobControl bool
owner *int
}
// WithOwner hands the PTY slave to the given uid before the command starts.
//
// A server that switches the command to another user needs this: the slave is
// created owned by whoever runs the server, so without it the command cannot
// write to its own terminal. The group is left alone.
func WithOwner(uid int) PtyStartOption {
return func(c *ptyStartConfig) {
c.owner = &uid
}
}
// WithJobControl enables job control when starting the command: on Unix,
// the command is put in its own session with the PTY slave as its
// controlling terminal, so interactive shells get working signal handling
// (e.g. ^C, ^Z).
//
// Leave it disabled (the default) when running multiple commands
// sequentially against the same PTY, or when managing the command's process
// attributes yourself via exec.Cmd.SysProcAttr.
func WithJobControl() PtyStartOption {
return func(c *ptyStartConfig) {
c.jobControl = true
}
}
// Start starts a *exec.Cmd attached to the Session. If a PTY is allocated,
// it will use that for I/O.
// On Windows, the process execution lifecycle is not managed by Go and has to
// be managed manually. This means that c.Wait() won't work.
// See https://github.com/charmbracelet/x/blob/main/exp/term/conpty/conpty_windows.go
func (p *Pty) Start(c *exec.Cmd, opts ...PtyStartOption) error {
cfg := ptyStartConfig{}
for _, opt := range opts {
opt(&cfg)
}
return p.start(c, cfg)
}