When Pty [Pseudo Terminal] Slave Fd Settings Are Changed by "Tcsetattr" , How Can the Master End Capture This Event Without Delay

Characters not making it from a master to a slave pseudo-terminal

0x04 is ^D, which is the end-of-file keypress. Has the pty been set in raw mode? Maybe the driver is eating it.

If you make it:

os.write(child_fd, b"'\x04hmm\x16\x04'\n")

you can see that indeed the driver is doing translation. \x16 is the same as ^V, which is how you quote things. It makes sense the translation would only be happening from the master (the pretend physical terminal) and the slave. The pretend physical terminal is where (on a normal terminal device) the person would be typing

I'm not sure how to get the driver to stop doing that. If the child sets its terminal to raw mode then that will likely do it.

Does terminal control function setting baud rate really effective for pseudo terminals?

The baud rate setting for a pseudo-terminal doesn't control anything, but advises the terminal driver (when it matters) how to synchronize with a physical device, as well as advising applications if they should send padding characters.

A pseudo-terminal might be connected to a physical device, but there's no way for an application to tell the difference. Physical devices are still used (perhaps not by you).

For example, xterm has an option, used for testing ncurses:

Patch #327 - 2016/10/07
add -baudrate option, for testing ncurses.

but you would not find it useful.

Why is ruby's PTY library failing to capture input when the shell has subprocesses?

The tty input mode defaults to line input, so you won't see anything until you output
a newline.

I suggest using strace to debug this kind of behaviour. This way you can see the system call, and for example see if you are blocked on a read waiting for more input and so on.

When you do not use '< /dev/tty0', it does work, right ?
Basically, what you want is echoing of character. If you do the following :

shell = PTY.spawn 'env TERM=ansi COLUMNS=63 LINES=21 sh -i'
s = shell[0].read(16)
puts s

And you strace the process using :

strace -ff -o test.log -e trace=read,write ./testr.rb

In the output you will see asdf twice.
But if you look at the strace code, you see that the cat subprocess only writes asdf once, and it's parent process, ie the shell, never writes asdf.

So why is there two 'asdf' output ? Because the tty layer is doing local echo. So that when you type something in shell it goes to the pty, and the pty driver :

  • Write it to the slave side of the pseudo tty
  • Echoes it to the master side.

So what happens when you do sh -i </dev/tty0 ? The character coming from the keyboard are echoed to /dev/tty0, not to the output of the shell.

The shell is not doing any echo, the tty layer is, so what you want to do is the folowing (take it as pseudo code, I am not competent in ruby) :

# Emulate terminal behavior with pty
shell = PTY.spawn 'env TERM=ansi COLUMNS=63 LINES=21 sh -i'
keyboard = open(/dev/tty0)

input = keyboard.read()
puts shell[0].read(...)

Now, if you want something interactive, you will need to configure /dev/tty0 in raw mode, and use select to know when you can read without blocking, and when there is data available for output.

To configure tty in raw mode, you can try to use

stty -F /dev/tty0 -cooked

Is stdout line buffered, unbuffered or indeterminate by default?

The C99 standard does not specify if the three standard streams are unbuffered or line buffered: It is up to the implementation. All UNIX implementations I know have a line buffered stdin. On Linux, stdout in line buffered and stderr unbuffered.

As far as I know, POSIX does not impose additional restrictions. POSIX's fflush page does note in the EXAMPLES section:

[...] The fflush() function is used because standard output is usually buffered and the prompt may not immediately be printed on the output or terminal.

So the remark that you add fflush(stdout); is correct.

An alternative could be to make stdout unbuffered:

setbuf(stdout, NULL);
/* or */
setvbuf(stdout, NULL, _IONBF, 0);

But as R. notes you can only do this once, and it must be before you write to stdout or perform any other operantion on it. (C99 2)

I just read a recent thread on comp.lang.c about the same thing. One of the remarks:

Unix convention is that stdin and stdout are line-buffered when associated with a terminal, and fully-buffered (aka block-buffered) otherwise. stderr is always unbuffered.

Serial driver in userspace

Yes, you can do this using a pty. The user mode driver opens the master end of the pty and the application that wants to use the serial port opens the slave end. Search for Linux pty.

Related Topics

Leave a reply