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
  • 5

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
  • 2. 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