Nadav Har'El wrote:
On Fri, Oct 17, 2008, Shachar Shemesh wrote about "Kernel bug? Reading from master
PTY when no slaves are open":
If I try to read from a master PTY when no slaves are open I get an IO
error. This would be okay with me, except:
- Select keeps flaging the fd as having read data, resulting in 100% CPU
utilization
and
Hi Shachar,
I'm afraid I don't understand why you consider this to be a bug. select(),
according to its manual, is supposed to return "ready" when "it is possible to
perform the corresponding I/O operation (e.g., read(2)) without blocking".
In your case, you can indeed read() and it won't block - it will return an
error, but NOT block.
That's the bug. Not in select.
It is up to your application (as a sane application)
to notice that one the fds on its select() list has an error, and drop this
fd from the list.
Except I can't, because a master PTY, unlike other FDs, might be invalid
now but valid later on. That's the bug.
Only a broken application would keep this broken fd on the
list and cause an infinite loop.
It really does pay to read the whole mail before answering parts of it,
doesn't it? :-)
You bring up an interesting point. I am not aware of a solution for this
issue. But I wonder if there's a satisfactory solution. If there was, and if
closing the last open copy of a certain pty (slave) is not final, how can
you ever recycle the masters?
I think it is fair to say that so long as the PTY is the controlling TTY
for an application then it still has open references. Just like a file
that has all of its directory references erased, but is still open by an
application, is not considered erased and does not have its storage
reclaimed by the file system. THAT is where, I think, the kernel bug lies.
In other words, to really close the last copy, you should also detach
the application from the TTY.
Good question :-) I'm also hearing a better answer from someone.
In the mean while, I did some more tests. Select consistently reports
the master end non-blocking for read and writes, and does not report any
errors on it, whether there is any open slaves or not.
I'm attaching a small test program. The parent holds the master end of
the PTY, waits using select, and prints what it gets. It prints the
error if it receives one. The child prints something to the slave end,
waits one second, closes the slave end, waits one second, opens
"/dev/tty", and prints something else there, waits one second and exit.
When running the program, you get the initial message, a second of
silence, a whole lot of "IO error"s for one second, and then the second
message, a second of silence, and the program exit. Any suggestions how
to make this program work without the bunch of errors in the middle will
be welcome.
Shachar
#define _XOPEN_SOURCE
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
int main()
{
int masterpt=posix_openpt(O_RDWR);
if( masterpt==-1 ) {
perror("Failed to get a pseudo terminal");
return 2;
}
if( grantpt( masterpt )!=0 ) {
perror("Failed to change pseudo terminal's permission");
return 2;
}
if( unlockpt( masterpt )!=0 ) {
perror("Failed to unlock pseudo terminal");
return 2;
}
int childpid=fork();
if( childpid==0 ) {
// Child
// Detach us from the current TTY
setsid();
const char *name=ptsname(masterpt);
int slavept=open(name, O_RDWR ); // This line makes the ptty our controlling tty. We do not otherwise need it open
fprintf(stderr, "Opened %s with fd %d\n", name, slavept);
close( masterpt );
//close( slavept );
sleep(1);
close( slavept );
sleep(1);
slavept=open("/dev/tty", O_RDWR);
fprintf(stderr, "Opened /dev/tty with fd %d\n", slavept);
sleep(1);
int num=write( slavept, "Point1", 6 );
if( num<0 ) {
perror("write failed");
} else {
printf("Wrote 6 bytes, actually wrote %d\n", num );
}
return 0;
}
// We are the parent
int status=0;
int terminate=0;
pid_t wait_id;
do {
if( !terminate ) {
fd_set readfd, writefd, errfd;
FD_ZERO(&readfd);
FD_SET(masterpt, &readfd);
//writefd=readfd;
errfd=readfd;
int selret=select( masterpt+1, &readfd, NULL, &errfd, NULL );
if( selret>0 ) {
#if 0
if( FD_ISSET( masterpt, &writefd ) ) {
fprintf(stderr, "masterpt write ready\n");
}
#endif
if( FD_ISSET( masterpt, &errfd ) ) {
fprintf(stderr, "masterpt error flagged\n");
}
if( FD_ISSET( masterpt, &readfd ) ) {
char buffer[205];
int num=read( masterpt, buffer, 204 );
if( num<0 ) {
perror("read failed");
} else {
buffer[num]='\0';
printf("Slave sent \"%s\"\n", buffer );
}
}
}
wait_id=waitpid( childpid, &status, WNOHANG );
} else {
wait_id=waitpid( childpid, &status, 0 );
}
} while( wait_id==0 || (!WIFEXITED( status ) && !WIFSIGNALED( status )) );
if( terminate!=0 )
return terminate;
else if( WIFEXITED( status ) )
return WEXITSTATUS(status);
else
return 255;
}