//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) }