DEV Community

Cover image for Pipes Won't Let Me Go
Kostya Malinovskiy
Kostya Malinovskiy

Posted on

Pipes Won't Let Me Go

Some time ago when I was working on codecrafters shell challenge I hit a problem.
The problem was that behaviour of my shell if use it via terminal was different from when use it in test environment.
When use it via terminal emulator the prompt was shown, however in tests it wasn't there. It appeared that readline works a bit different if communicating with it throug Stdin Stdout programatically.
I managed to overcome the problem, i wrote about it in one of my previous posts, but i think it is quite interesting and important to understand how this 2 ways work.

What is tty?

In unix system everything is a file including any device, tty is not an exception. It is a special character device. It means that it is a device that it streaming data as it receives it in real time byte by byte.

As it is a file it can be open for read or read with cat, or it can be written to with echo >. There is something confusing though. Suppose we use /dev/ttyp0 we want to write some data to it so it would be picked up somwhere, also we want to receive feedback from the device. But there seems to be a problem, if it is a file, how what is written on our hand is not mixing up with what is sent from the other hand?

At this point I found that instead of saying "in unix everything is a file" it would be better to say: "in unix everything looks like a file". What it means that file and tty device have same interface: read, write, close, but for diferent things it behaves differently differently. You see file descriptor no only a pointer to a file, it includes some additional info, and among other things it includes info on what drivers to use. For File read and write will use same destination, for tty read and write would use different buffers.

The Two-Way Pipe

TTY it is a 2 way-pipe (full-duplex). When any tty device would be represented by master end and slave end - two different entries at /dev/ptmx and /dev/pts/4.

  • Master end is held by usef facing app, when app receives typed charecters it writes them to master.
  • Slave end is held by a shell application and used slave as a stdin, and stdout.

When something is written to master, it appears on slave reader, and directed to a shell app stdin. When a shell app writes something to its stdout, whidh is slaves write, it is transfered to masters read.

TTY vs PTY

  • TTY is actual hardware(e.g /dev/ttys0), so the other end of the pipe is actual device
  • PTY is a pseudo-terminal, it is pair of files used to build TTY like interaction between software apps.

There is also such thing as line discipline, this is a kernel layer that echoes typed characters back to master, buffers input until Enter is hit. It also intercepts special symbols and key sequences like ^c or ^z and act on them accordingly. While this is an important piece, I will not focus on it at this post.

how to find out if app interacts with tty?

it can be easialy done with IsTerminal(fd int) from "golang.org/x/term", but how does it work under the hood?

// x/term/term.go
func IsTerminal(fd int) bool {
    return isTerminal(fd)
}

// x/term/unix_term.go
func isTerminal(fd int) bool {
    _, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
    return err == nil
}

// x/term/term_unix_bsd.go
    const ioctlReadTermios = unix.TIOCGETA
// x/sys/unix/ioctl_unsigned.go

func IoctlGetTermios(fd int, req uint) (*Termios, error) {
    var value Termios
    err := ioctlPtr(fd, req, unsafe.Pointer(&value))
    return &value, err
}

Enter fullscreen mode Exit fullscreen mode

ioctlPtr makes a syscall with a file descriptor and unix.TIOCGETA request, and writes result to value, it errors if file under fd is not tty. Presnse of error in the upstream code is interpreted as current file descriptor does not point to a tty device.

unix.TIOCGETA is read:
TIO - terminal Input/Output
C - control
GET - read/retrieve operation
A - attributes

Actual unix.TIOGETA value of course is not an arbitrary integer. But as I think it would be to deep in the rabbit hole for now, and I'll get to it somewhere in the future.

Top comments (0)