I've created a pseudo terminal (/dev/pts/N)
from a process A and I am writing random integers to that on a certain interval. I can open that pts from screen
and check its output.
But cat /dev/pts/N
fails: it infinitely blocks and doesn't return.
I am trying to read that from another process using open()/read()
functions and also there read()
never returns.
int main(){
int source_fd = open("/dev/pts/4", O_RDONLY);
while(1){
char buffer[READ_BUFFER_SIZE] = {0};
char* buff_ptr = buffer;
int r = read(source_fd, (void*)buff_ptr, 1);
// !!!! never comes here
while(r > 0){
++buff_ptr;
r = read(source_fd, (void*)buff_ptr, 1);
}
}
}
Short answer: You're not handling the pseudoterminal correctly. Observing strange or even random results by having an outside process read from the pseudoterminal is normal; you're not supposed to do that. It's like having two people write on the same keyboard at the same time. (Just because you can see that in some TV shows, does not mean it makes any sense whatsoever.)
Long answer: Alter your approach, and you'll have much better results.
Consider the following task you can do, to acquint yourself with pseudoterminal behaviour:
Create a pseudoterminal master, and allow slave access to it
(Use posix_openpt()
, grantpt()
, and unlockpt()
to create the pseudoterminal. Use ptsname()
to find out the device name of the slave end.)
Fork a child process.
(Use fork()
to fork the child process, then setsid()
to detach from the controlling terminal. It also creates a new process group, so your master process can send signals to all processes started by the child process by sending the signals to the entire group.)
In the child process, open standard input (STDIN_FILENO
) for reading from slave pseudoterminal, and standard output (STDOUT_FILENO
) and standard error (STDERR_FILENO
) for writing to the slave end of the pseudoterminal. Execute nano
.
(Use dup2()
to copy descriptors to their correct places, close()
to close the extra ones, and e.g. execlp("nano", "nano", NULL)
to execute nano
. Note that the first "nano"
is the file name of the nano command, and the second is the argv[0]
parameter the command sees. It does not supply any actual command-line parameters; it acts as if you ran nano
in your favourite shell.)
In the parent process, you can now read and write to the master end of the pseudoterminal.
Note that you may have to do so concurrently; there is no way to know when you can/need/must read (more), and when writing might block.
I cannot stress enough how important it is to be full-duplex or nonblocking here. If you never read from your pseudoterminal, do not expect it to work, either.
In the parent process, remove file foobar.txt
.
(Use remove()
or unlink()
.)
This is just so that nano
will not pop up a "File already exists" dialog.
In the parent process, while reading any output the slave process might write to the pseudoterminal,
Wait a fraction of a second (while nano draws the editor screen)
Write Some text
and a carriage return \r
,
Wait a fraction of a second,
Write Ctrl+O (\017
, often visualized as ^O
)
Write foobar.txt
and a carriage return \r
,
Wait a fraction of a second,
Write Ctrl+X (\030
, often visualized as ^X
),
Wait
and nano
should exit.
In the parent process, wait for the child (nano
) process to exit.
(Use a loop and waitpid()
for this.)
If you accomplish the above, your master terminal control program just emulated a local or remote "human" running a very short nano
session, writing just Some text
and a newline, saving it to foobar.txt
, and exiting. (The file should contain "Some text\n\n"
, because that's how nano
works.)
Step 6 is easiest to achieve, if you create a helper thread that does nothing but reads from the master pseudoterminal file descriptor. In a very clear sense, it acts like an automatic drain. After all, we're not really interested in what nano
outputs to the terminal here. After the step 7, you simply close that descriptor, causing the helper thread to error out (read()
returns -1 with errno == EBADF
) and return, so the main thread can use pthread_join()
to reap it.
You can implement step 6 using nonblocking I/O, of course. Any way you do it, it is imperative you always read()
from the master pseudoterminal, and do not get deadlocked by write()
ing to it while the slave process is also writing to the terminal. This is the situation the OP is struggling with, I bet.
A typical sequence of communications flowing through the pseudoterminal in the above scenario is:
Slave -> Master: "\e[?1049h\e[1;24r\e(B\e[m\e[4l\e[?7h\e[?12l\e[?25h"
Slave -> Master: "\e[?1h\e=\e[?1h\e=\e[?1h\e="
Slave -> Master: "\e[39;49m\e[39;49m\e(B\e[m\e[H\e[2J\e(B\e[0;7m"
" GNU nano 2.2.6 "
" New Buffer "
"\e[23;1H^G\e(B\e[m Get Help "
"\e(B\e[0;7m^O\e(B\e[m WriteOut "
"\e(B\e[0;7m^R\e(B\e[m Read File "
"\e(B\e[0;7m^Y\e(B\e[m Prev Page "
"\e(B\e[0;7m^K\e(B\e[m Cut Text "
"\e(B\e[0;7m^C\e(B\e[m Cur Pos"
"\015\e[24d\e(B\e[0;7m^X\e(B\e[m Exit"
"\e[14G\e(B\e[0;7m^J\e(B\e[m Justify "
"\e(B\e[0;7m^W\e(B\e[m Where Is "
"\e(B\e[0;7m^V\e(B\e[m Next Page "
"\e(B\e[0;7m^U\e(B\e[m UnCut Text"
"\e(B\e[0;7m^T\e(B\e[m To Spell\015\e[3d"
Master -> Slave: "Some text\015"
Slave -> Master: "\e[1;71H\e(B\e[0;7mModified\015\e[3d\e(B\e[mSome text\015\e[4d"
Master -> Slave: "\017"
Slave -> Master: "\e[22d\e(B\e[0;7mFile Name to Write: "
" "
" "
"\e[23;14H\e(B\e[m "
"\e(B\e[0;7mM-D\e(B\e[m DOS Format "
"\e(B\e[0;7mM-A\e(B\e[m Append "
"\e(B\e[0;7mM-B\e(B\e[m Backup File"
"\e[24;2H\e(B\e[0;7mC\e(B\e[m Cancel "
"\e(B\e[0;7mM-M\e(B\e[m Mac Format "
"\e(B\e[0;7mM-P\e(B\e[m Prefix\e[K\e[22;21H"
Master -> Slave: "foobar.txt\015"
Slave -> Master: "\e[1;31H\e[39;49m\e(B\e[0;7mFile: foobar.txt"
"\e[1;71H \e[22;31H\e(B\e[m\e[1K "
"\e(B\e[0;7m[ Wrote 2 lines ]"
"\e(B\e[m\e[K\e[23;14H\e(B\e[0;7m^O\e(B\e[m WriteOut "
"\e(B\e[0;7m^R\e(B\e[m Read File "
"\e(B\e[0;7m^Y\e(B\e[m Prev Page "
"\e(B\e[0;7m^K\e(B\e[m Cut Text "
"\e(B\e[0;7m^C\e(B\e[m Cur Pos"
"\e[24;2H\e(B\e[0;7mX\e(B\e[m Exit "
"\e(B\e[0;7m^J\e(B\e[m Justify "
"\e(B\e[0;7m^W\e(B\e[m Where Is "
"\e(B\e[0;7m^V\e(B\e[m Next Page "
"\e(B\e[0;7m^U\e(B\e[m UnCut Text"
"\e(B\e[0;7m^T\e(B\e[m To Spell\015\e[4d"
Master -> Slave: "\030"
Slave -> Master: "\e[23d\e[J\e[24;80H"
Slave -> Master: "\e[24;1H\e[?1049l\015\e[?1l\e>"
where \e
is shorthand for \033
or \x1B
, ie. the ASCII ESC character.
Especially note how the slave nano
process spews all kinds of output, just to draw a fancy editor screen. If there was a clock or some such that changed regularly, it would basically spew those updates every second.
The reason for Master->Slave using \r
instead of \n
as newline is the default termios settings.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With