2

How can terminal emulator read from ptm device while it missing read function? There is a PTY driver: https://github.com/torvalds/linux/blob/master/drivers/tty/pty.c. I see pty_write function, but can't see something like pty_read() function. As I understand, there's no need of read function for pty slave because it is a TTY device which has its own read buffer. So the corresponding method for reading in tty line discipline: https://github.com/torvalds/linux/blob/master/drivers/tty/n_tty.c#L2132.

But what about a master device? How can terminal emulator read from this device while it is not even a generic tty device?

TwITe
  • 151

1 Answers1

1

A read from a master pty will be dispatched to the same n_tty_read(), ie the same N_TTY line discipline is attached to the master as to the slave pty.

The whole trace of a read syscall is something like:

sys_read() -> ksys_read() -> vfs_read() -> __vfs_read()
  -> file->f_op->read = tty_fops.read -> tty_read()
    -> ld->ops->read = n_tty_ops.read -> n_tty_read()

and the line discipline is attached to a master pty via

ptmx_open()
  -> tty_init_dev()
     -> alloc_tty_struct()
        -> tty_ldisc_init()
           -> tty_ldisc_get(tty, N_TTY)

The situation may be confused by the fact that

a) the termios config used by a master pty is set to raw/passthrough, so it may appear like the line discipline is not even there ;-) (see the ptm_driver->init_termios... initializations in unix98_pty_init()).

b) all the termios ioctl (tcsetattr() -> ioctl(TCSETS), etc) will act on the slave's termios config even when called on the master pty (see tty_mode_ioctl() in drivers/tty/tty_ioctl.c).

  • If master pty do has associated ldisc, then can we set its termios from userspace? If no, then it would be completely the same as "it doesn't even exist". – 炸鱼薯条德里克 Jul 13 '19 at 02:17
  • Have you read the point b) of my answer? All the termios ioctls on a master pty fd will operate on the slave. –  Jul 13 '19 at 02:19
  • Yes, I am just asking if there's another possibility to set that from userland. Seems that it's a on-purpose design of linux kernel. – 炸鱼薯条德里克 Jul 13 '19 at 02:23
  • No, there isn't. All the line discipline / termios ioctls act on the slave, even when called on the master. –  Jul 13 '19 at 02:35
  • Thanks for answer, @mosvy. But I can't see how ldics is attached to the slave device. Where can I find corresponding code? – TwITe Jul 13 '19 at 09:22
  • Also, If ldisc attached to the master, does that mean that when a process writes to the slave, chars will be processed too (the same as when terminal emulator writes to the master). – TwITe Jul 13 '19 at 10:25
  • So this image isn't correct? https://i.stack.imgur.com/gMXOF.png – TwITe Jul 13 '19 at 11:19
  • @TwiTe I've already mentioned that in the answer. In tty_ldisc_init() -- which is really trivial to see for anyone looking it up in the source. If you don't believe it, trace a Linux kernel with a remote debugger. Also, it would be pretty obvious that the "line discipline" abstraction is handling both reading and writing -- have you read the comment above the pty_write() function you mention in your Q? –  Jul 13 '19 at 12:32
  • @mosvy i've seen this function (tty_ldisc_init()), what I don't understand is that this function saying: ldisc setup for new tty. What is meant here by tty? Pty slave or Pty master? – TwITe Jul 13 '19 at 12:41
  • That code is really not rocket science -- they're two struct tty_struct each pointing to another via their ->link field. The hack is pretty easy to follow -- unless you come with pre-conceived notions about the abstractions used. Linux was never based on SysV, never used STREAMS and modular/stackable line disciplines. The "line discipline" in linux is simply a struct with callbacks, referenced from the struct tty_ldisc abstraction. –  Jul 13 '19 at 12:43
  • The alloc_tty_struct() -> tty_ldisc_init() will be called separately for both the slave and the master. Each one will have their separate line discipline, both using the same callbacks ("ops"). –  Jul 13 '19 at 12:48
  • @mosvy I've made some research and found out that both pts and ptm drivers call pty_unix98_install function on startup. Latter calls pty_common_install which will call o_tty = alloc_tty_struct(driver->other, idx) and this really makes sense. But what I don't understand is why we need ptmx_open function then? Why it calls alloc_tty_struct() again? – TwITe Jul 13 '19 at 15:54
  • Have you looked at what will happen if pty_common_install() is called from the slave pty? There's even a comment about it ;-) As to ptmx_open(), it's the open callback from the /dev/pts/ptmx or /dev/ptmx file ops table. There should be a way to open the master pty, right? FreeBSD has posix_openpt() as a separate system call, Linux is not already there. There is still a device multiplexer living in the filesystem. –  Jul 13 '19 at 16:24
  • @mosvy can we please continue discussion in chat? – TwITe Jul 13 '19 at 17:49
  • I don't do chat. –  Jul 13 '19 at 17:52
  • Okay, so I will ask here :)
    1. What is the point of calling tty_init_dev() with ptm_driver and an index of pts file? It is in ptmx_open() function. First we get pts pts device index: index = devpts_new_index(fsi) here: https://github.com/torvalds/linux/blob/master/drivers/tty/pty.c#L836 and then pass it to tty = tty_init_dev(ptm_driver, index)
    – TwITe Jul 13 '19 at 18:05
  • About tty_init_dev() function. Here: https://github.com/torvalds/linux/blob/master/drivers/tty/tty_io.c#L1333 we get tty_struct for ptm device. And then method calls tty_driver_install_tty(), which calls install() method of current driver (ptm driver). The install() method in fops maps to the pty_unix98_install, which will alloc tty_struct for the other part of tty/pty device.
  • So that means that install() method of pts device never be called, but install() method of ptm device will be called? Am I get it right?

    – TwITe Jul 13 '19 at 18:08
  • I have another question, and this is the last one I think :) Who will call receive_buf() to process data? I have looked at the pty_write() function and this function just calls tty_insert_flip_string_fixed_flag() method, which will just save data into the flip buffer structure. But I can't see any processing of this data? So, where's processing by ldisc is performing? – TwITe Jul 13 '19 at 19:39
  • I've found some docs about this method:

    receive_buf() (optional) Called by the low-level driver to handa buffer of received bytes to the ldisc for processing.

    It looks weird because driver doesn't know anything about ldisc. Btw is here meant pty master/pty slave driver by low-level driver or something else?

    – TwITe Jul 13 '19 at 19:42
  • Update: I found out who will process data :) The chain is following: pty_write() -> tty_flip_buffer_push() -> tty_schedule_flip() -> flush_to_ldisc() -> receive_buf() (it is the method in tty_buffer.c file) -> port->client_ops->receive_buf. This method was set while ptmx_open was called in the method tty_init_dev() -> tty_driver_install_tty() -> driver->ops->install which maps to the method pty_unix98_install() -> pty_common_install(). This method does the following:
    tty_port_init(ports[0]);
    tty_port_init(ports[1]);
    
    – TwITe Jul 14 '19 at 08:53
  • And the tty_port_init(): port->client_ops = &default_client_ops;

    default_client_ops is the following const structure: static const struct tty_port_client_operations default_client_ops = { .receive_buf = tty_port_default_receive_buf, .write_wakeup = tty_port_default_wakeup, };

    So we return to the port->client_ops->receive_buf call and we know that tty_port_default_receive_buf will be called. Now this method calls tty_ldisc_receive_buf. That's all :)

    – TwITe Jul 14 '19 at 08:53
  • Pls correct me if I'm wrong – TwITe Jul 14 '19 at 08:59
  • I think that you should set up a kernel with remote debugging support in a virtual machine, and simply set breakpoints and do backtraces as you do with a regular userland program instead of trying to guess the paths inside the maze. Good luck! –  Jul 14 '19 at 19:35