creack / pty

PTY interface for Go
https://pkg.go.dev/github.com/creack/pty?tab=doc
MIT License
1.68k stars 234 forks source link

[macOS] How to send large buffer through pty? #150

Closed lonnywong closed 2 years ago

lonnywong commented 2 years ago

import ( "io" "os" "os/exec" "strings"

"github.com/creack/pty"

)

func main() { c := exec.Command("python3", "-c", "import sys; s = sys.stdin.readline(); print(len(s))") f, err := pty.Start(c) if err != nil { panic(err) }

go func() {
    f.Write([]byte(strings.Repeat("A", 3000) + "\n"))
}()

io.Copy(os.Stdout, f)

}



What should I do to make the subprocess read all the buffer through pty?
creack commented 2 years ago

After testing, this behavior is the same when using the shell directly, it looks like it is more a question for python than for this library.

When piping, by default, the shell disables the pty for the process, you can use the command script to force a pty on the commandline to reproduce what the library is doing.

Note the script arguments differs between BSD (osx) and GNU (linux).

On linux, it works as expected:

$> python3 -c 'print("A" * 3000)' | script - -c 'python3 -c "import sys; s = sys.stdin.readline(); print(len(s))"'
Script started, output log file is '-'.
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
3001
Script done.

While on OSX, the same thing hangs.

$> python3 -c 'print("A" * 3000)' | script - python3 -c "import sys; s = sys.stdin.readline(); print(len(s))"
Script started, output file is -
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[hangs]

Unfortunately, I don't think there is much we can do here.

lonnywong commented 2 years ago

@creack Thanks for your help.

I found the issue: https://stackoverflow.com/a/11439517/7696611

It works with stty -icanon.

python3 -c 'print("A" * 3000)' | script - sh -c "stty -icanon; python3 -c 'import sys; s = sys.stdin.readline(); print(len(s))'"

It hangs with pty.

c := exec.Command("sh", "-c", "stty -icanon; python3 -c 'import sys; s = sys.stdin.readline(); print(len(s))'")

Could you do something in the pty?

lonnywong commented 2 years ago

I figured it out:

package main

import (
    "io"
    "os"
    "os/exec"
    "strings"
    "time"

    "github.com/creack/pty"
)

func main() {
    c := exec.Command("python3", "-c", "import sys; import tty; import termios; tty.setraw(sys.stdin.fileno(), termios.TCSANOW); s = sys.stdin.readline(); print(len(s))")
    f, err := pty.Start(c)
    if err != nil {
        panic(err)
    }

    go func() {
        time.Sleep(1 * time.Second)  // Have to wait for the python set raw tty first.
        f.Write([]byte(strings.Repeat("A", 5000) + "\n"))
    }()

    io.Copy(os.Stdout, f)
}
creack commented 2 years ago

Note that would can also easily set the pty in raw mode using the same method as shown in the readme for stdin.

import (
  "golang.org/x/term"
  "github.com/creack/pty"
)
[...]
        f, err := pty.Start(c)
        if err != nil {
                panic(err)
        }
        oldState, err := term.MakeRaw(int(f.Fd()))
        if err != nil {
                panic(err)
        }
        defer func() { _ = term.Restore(int(f.Fd()), oldState) }() // Best effort.
lonnywong commented 2 years ago

Note that would can also easily set the pty in raw mode using the same method as shown in the readme for stdin.

import (
  "golang.org/x/term"
  "github.com/creack/pty"
)
[...]
        f, err := pty.Start(c)
        if err != nil {
                panic(err)
        }
        oldState, err := term.MakeRaw(int(f.Fd()))
        if err != nil {
                panic(err)
        }
        defer func() { _ = term.Restore(int(f.Fd()), oldState) }() // Best effort.

We can do that in the child process. But we have to set the fd of /dev/ptmx to raw mode in the parent pty process.