mirror of
https://github.com/asciinema/asciinema.git
synced 2025-12-16 19:58:03 +01:00
There's race condition happening (so far observed only on CentOS) at the moment when child process exits. Sometimes reading from master fd blocks forever even when the process is already reaped and there's no more data to be read. Not sure what's the reason, it may be some CentOS specific bug. Using timeout does the job and seems to be relatively harmless.
111 lines
2.1 KiB
Go
111 lines
2.1 KiB
Go
package terminal
|
|
|
|
import (
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"os/signal"
|
|
"syscall"
|
|
"time"
|
|
|
|
"code.google.com/p/go.crypto/ssh/terminal"
|
|
|
|
"github.com/asciinema/asciinema-cli/ptyx"
|
|
"github.com/asciinema/asciinema-cli/util"
|
|
"github.com/kr/pty"
|
|
)
|
|
|
|
type Terminal interface {
|
|
Size() (int, int, error)
|
|
Record(string, io.Writer) error
|
|
}
|
|
|
|
type Pty struct {
|
|
Stdin *os.File
|
|
Stdout *os.File
|
|
}
|
|
|
|
func New() *Pty {
|
|
return &Pty{Stdin: os.Stdin, Stdout: os.Stdout}
|
|
}
|
|
|
|
func (p *Pty) Size() (int, int, error) {
|
|
return pty.Getsize(p.Stdout)
|
|
}
|
|
|
|
func (p *Pty) Record(command string, stdoutCopy io.Writer) error {
|
|
// start command in pty
|
|
cmd := exec.Command("sh", "-c", command)
|
|
cmd.Env = append(os.Environ(), "ASCIINEMA_REC=1")
|
|
master, err := pty.Start(cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer master.Close()
|
|
|
|
// install WINCH signal handler
|
|
signals := make(chan os.Signal, 1)
|
|
signal.Notify(signals, syscall.SIGWINCH)
|
|
defer signal.Stop(signals)
|
|
go func() {
|
|
for _ = range signals {
|
|
p.resize(master)
|
|
}
|
|
}()
|
|
defer close(signals)
|
|
|
|
// put stdin in raw mode (if it's a tty)
|
|
fd := int(p.Stdin.Fd())
|
|
if terminal.IsTerminal(fd) {
|
|
oldState, err := terminal.MakeRaw(fd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer terminal.Restore(fd, oldState)
|
|
}
|
|
|
|
// do initial resize
|
|
p.resize(master)
|
|
|
|
// start stdin -> master copying
|
|
stop := util.Copy(master, p.Stdin)
|
|
|
|
// copy pty master -> p.stdout & stdoutCopy
|
|
stdout := io.MultiWriter(p.Stdout, stdoutCopy)
|
|
stdoutWaitChan := make(chan struct{})
|
|
go func() {
|
|
io.Copy(stdout, master)
|
|
stdoutWaitChan <- struct{}{}
|
|
}()
|
|
|
|
// wait for the process to exit and reap it
|
|
cmd.Wait()
|
|
|
|
// wait for master -> stdout copying to finish
|
|
//
|
|
// sometimes after process exits reading from master blocks forever (race condition?)
|
|
// we're using timeout here to overcome this problem
|
|
select {
|
|
case <-stdoutWaitChan:
|
|
case <-time.After(200 * time.Millisecond):
|
|
}
|
|
|
|
// stop stdin -> master copying
|
|
stop()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *Pty) resize(f *os.File) {
|
|
var rows, cols int
|
|
|
|
if terminal.IsTerminal(int(p.Stdout.Fd())) {
|
|
rows, cols, _ = p.Size()
|
|
} else {
|
|
rows = 24
|
|
cols = 80
|
|
}
|
|
|
|
ptyx.Setsize(f, rows, cols)
|
|
}
|