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
|
//go:build linux || darwin
package terminal
import (
"os"
"os/exec"
"path/filepath"
)
// unixChild is a shell behind a Unix pseudo-terminal: the master end this side
// holds, and the process that has the slave as its controlling terminal.
type unixChild struct {
master *os.File
command *exec.Cmd
}
// startChild opens a pseudo-terminal and starts the shell in it.
func startChild(options Options) (child, error) {
master, slave, err := openPTY()
if err != nil {
return nil, err
}
// The child keeps the slave as its own standard input, output and error;
// this process has no further use for it once the child has started.
defer slave.Close()
command := exec.Command(shellOrDefault(options.Shell), options.Args...)
command.Dir = options.Dir
command.Env = environment(options.Env)
command.Stdin, command.Stdout, command.Stderr = slave, slave, slave
command.SysProcAttr = childAttributes()
if err := command.Start(); err != nil {
master.Close()
return nil, err
}
c := &unixChild{master: master, command: command}
if err := c.Resize(max(options.Width, 1), max(options.Height, 1)); err != nil {
c.Close()
return nil, err
}
return c, nil
}
// defaultShell returns the user's own shell, or a last-resort fallback that
// exists on every Unix.
func defaultShell() string {
if fromEnvironment := os.Getenv("SHELL"); fromEnvironment != "" {
return fromEnvironment
}
return "/bin/sh"
}
func (c *unixChild) Read(b []byte) (int, error) { return c.master.Read(b) }
func (c *unixChild) Write(b []byte) (int, error) { return c.master.Write(b) }
// Resize sets the pseudo-terminal's size, which the kernel passes on to the
// shell as SIGWINCH.
func (c *unixChild) Resize(width, height int) error {
return setWinsize(c.master, width, height)
}
// Close closes the master — the shell sees its input end — then kills and
// reaps the process, so that closing cannot hang on a shell that ignores the
// hint.
func (c *unixChild) Close() error {
closeErr := c.master.Close()
if c.command.Process != nil {
_ = c.command.Process.Kill()
_, _ = c.command.Process.Wait()
}
return closeErr
}
// Command returns the shell's name without its path.
func (c *unixChild) Command() string { return filepath.Base(c.command.Path) }
|