Search code examples
goterminalsystem-callsstty

Terminal in golang: syscall vs os/exec stty


This is how one can get a (POSIX) terminal size with a syscall in go:

func getTermDim() (width, height int, err error) {
    var termDim [4]uint16
    if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(0), uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&termDim)), 0, 0, 0); err != 0 {
        return -1, -1, err
    }
    return int(termDim[1]), int(termDim[0]), nil
}

Now, the same thing, calling stty with os/exec:

func getTermDim() (width, height int, err error) {
        cmd := exec.Command("stty", "size")
        cmd.Stdin = os.Stdin
        var termDim []byte
        if termDim, err = cmd.Output(); err != nil {
                return
        }
        fmt.Sscan(string(termDim), &height, &width)
        return
}

In practice, the first solution can get pretty heavy and unreadable, when one has to put a terminal in raw mode, set up options etc. When one is used to stty (e.g. in shell scripts), the second solution is so much easier!

So my question is: what are the benefits of using the first solution? It is speed? Is it that we cannot rely on the stty command to be installed on the host machine? Anything else I don't think of?

In a nutshell, what is the "risk" or the "cost" of using stty vs a syscall?


Solution

  • About the risk:

    • stty: your program will not work correctly if the stty command is not available in $PATH. Or if the stty command in $PATH is not the one you expect (it might be a security issue). Or if the program runs in a Docker container with a minimalist footprint: you'll have to put stty in the image.
    • syscall: your program is dependent on the OS. It is fine as long as you write that function in file protected with build tags to ensure the build will fail at compile time on unsupported OS.

    About the performance, just write a benchmark using package testing. But I can already tell you that exec.Command implies multiple syscalls much more costly than IOCTL/TIOCGWINSZ.