5

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?

lyderic
  • 382
  • 3
  • 7
  • IMO the syscall version is much clearer with the only improvement is that I'd wrap it to a generic function which would model `ioctl` interface (in Go-style, of course), and then `getTermDim()` would call that one. – kostix Jun 22 '18 at 13:38
  • 2
    Another approach is to not reinvent the wheel and grab https://golang.org/x/crypto/ssh/terminal or https://github.com/nsf/termbox-go to leverage their ability to work with terminals. – kostix Jun 22 '18 at 13:39
  • Our disagreement on what's clearer is supposedly comes from the fact I sort-of know what `stty` does and hence calling it instead or doing what it's supposed to do *directly* is just cruft. For those used to shell scripting calling `stty` is supposedly more logical :-) – kostix Jun 22 '18 at 13:41
  • Thanks for your inputs, kostix. It looks like it's eventually a matter of personal preference, then. – lyderic Jun 23 '18 at 14:17
  • I've got another case pro syscall: if your program's stdout is not connected to a TTY, the syscall allows for way more sensible error handling, as otherwise you'd have to parse the `stty`'s output and rely on it to be stable across different possible platforms (which I doubt). – kostix Jun 23 '18 at 15:00
  • And by the way, notice that terminals may change dimensions at runtime. In this case, the process attached to it is typically sent a `SIGWINCH` signal. It might make sense to handle it as well. – kostix Jun 23 '18 at 15:02
  • SIGWINCH: yes that'd need another stty fork, which is not the end of the world, however, you're right about the case when one is not connected to the TTY. Parsing stty's output is somewhat messy anyway. – lyderic Jun 24 '18 at 16:05

2 Answers2

1

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.

dolmen
  • 8,126
  • 5
  • 40
  • 42
1

In all likelihood, if stty exists on the box, the syscall is also present. It's seems that it, and the argument TIOCGWINSZ, are a part of the SVR4 standard, which dates back about 20 years at this point and are widely implemented by Unixes everywhere, so it's unlikely you would need variant code for different OSes and an ioctl would be portable.

Running stty is more readable, certainly, but you're running a separate process, which comes with a cost.

It's slower and considerably more expensive: the OS is starting a new process context and loading the binary; not only does your code make multiple syscalls, so does the process you run.

It also comes with a set of security concerns. Perhaps the stty you found on your path is one the user put there, as just one example; and if you embed a path, perhaps it's wrong for this specific OS).

ijw
  • 4,376
  • 3
  • 25
  • 29