Bug 107649

Summary: ioctl not returning number of bytes in /dev/ttyS0
Product: Red Hat Enterprise Linux 3 Reporter: Scott Weathers <sweathers>
Component: kernelAssignee: Arjan van de Ven <arjanv>
Status: CLOSED NOTABUG QA Contact: Brian Brock <bbrock>
Severity: medium Docs Contact:
Priority: medium    
Version: 3.0CC: dwmw2, jlamb
Target Milestone: ---   
Target Release: ---   
Hardware: i386   
OS: Linux   
Whiteboard:
Fixed In Version: Doc Type: Bug Fix
Doc Text:
Story Points: ---
Clone Of: Environment:
Last Closed: 2003-10-23 12:28:44 UTC Type: ---
Regression: --- Mount Type: ---
Documentation: --- CRM:
Verified Versions: Category: ---
oVirt Team: --- RHEL 7.3 requirements from Atomic Host:
Cloudforms Team: --- Target Upstream Version:
Embargoed:

Description Scott Weathers 2003-10-21 18:20:55 UTC
Description of problem:
It is my understanding that the FIONREAD should return the number of bytes 
in /dev/ttyS0.  I am try to do an intelligent read for the number of bytes in 
serial port buffer so I don't have to wait for a time out to occur.  When I 
use ioctl( comm_fd, FIONREAD, &nchar ) it does not return me the number of 
bytes in the serial port buffer.

Any in site would be of great help.  If this is the wrong place to ask this 
question please direction to the correct place.


Example:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <time.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <sys/ioctl.h>


char    	comm_log_buf[5000];
int 		wcomm_fd,
			comm_fd;

extern unsigned old_mode;

int	caught_alrm = 0;

//-----------------------------------------------------------------------------
int tty_open (char *line, int flg)
{
	int fd;
	char devname[100];

	strcpy(devname, "/dev/");
	strcat(devname, line);

    fd = open (devname, flg);
    if (fd == -1)
    {
		printf("error (%d) opening %s - [%s]\n", errno, devname, 
strerror(errno));
		return(errno);
	}

	if(isatty(fd) == 0)
	{
		fprintf(stderr, "%s is not a tty\n", devname);
		return(-1);
	}
	
	return(fd);
}
//-----------------------------------------------------------------------------
static void sig_alrm(int signo)
{
	caught_alrm = 1;
	return;
}
//-----------------------------------------------------------------------------
size_t my_get_com_byte(int fd, char *buf)
{
	for ( ; ; )
	{
		if(caught_alrm)
		{
			fprintf(stderr, "\nAlarm Read Timeout...\n");
			return(-1);
		}
		if(read(fd, buf, 1) == 1)
		{
			//fprintf(stderr, "Read more than one byte...\n");
			return (1);
		}
		if(errno == EINTR && caught_alrm)
		{
			fprintf(stderr, "\nRead Timeout after read...\n");
			return(-1);
		}
	}
}
//-----------------------------------------------------------------------------
int dev_str(int fd, char *ptr)
{
	int nchar,
		i;
	

	if(signal(SIGALRM, sig_alrm) == SIG_ERR)
	{
		fprintf(stderr, "signal SIGLARM error in dev_str");
		return(-1);
	}
	caught_alrm = 0;
	alarm(1);

	if(	(i = ioctl( fd, FIONREAD, &nchar )) == -1)
		return(-1);
	fprintf(stdout, "Found %d %d bytes in port buffer\n",i, nchar);
	for(i = 1; i <= 80; i++)
	{
		if (my_get_com_byte(fd, &ptr[i]) == -1)
			break;
    	if(ptr[i]<32 || ptr[i]>126)
       		sprintf(comm_log_buf,"[%x]",ptr[i]);
    	else
      		sprintf(comm_log_buf,"%c",ptr[i]);
      	fprintf(stdout,"%s ",comm_log_buf);
	}	

	return(i);
}
//-----------------------------------------------------------------------------
int main (int argc, char **argv)
{
    long    n,
            c,
            len,
            done,
            bad,
            mismatch,
			cnt = 0,
            spurious;
    char    buf [500],
			test_pattern[256],
            line [500];
	int		err,
			i,
			nchar;

    strcpy (test_pattern, "0123456789");


    wcomm_fd = comm_fd = open ("/dev/ttyS0", O_RDWR | O_NONBLOCK);
    if (comm_fd == -1)
    {
		printf("error (%d) opening  [%s]\n", errno, strerror(errno));
		return(errno);
	}

   	/* send the data */
   	err = write(wcomm_fd,test_pattern,strlen(test_pattern));
	fprintf(stdout, "\nWrite %d bytes \n", err);
	
	if(	(err = ioctl( comm_fd, FIONREAD, &nchar )) == -1)
	{
		fprintf(stderr, "ioctl - error (%d) [%s]\n", errno, strerror
(errno));
		return(-1);
	}
	fprintf(stdout, "Found %d %d bytes in port buffer\n",err, nchar);
	dev_str(comm_fd,buf);


}


Version-Release number of selected component (if applicable):


How reproducible:


Steps to Reproduce:
1.compile
2.execute test code
3.
    
Actual results:
Write 10 bytes
Found 0 0 bytes in port buffer
Found 0 0 bytes in port buffer

Alarm Read Timeout...
0 1 2 3 4 5 6 7 8 9 

Expected results:
Write 10 bytes
Found 0 10 bytes in port buffer
Found 0 10 bytes in port buffer

Alarm Read Timeout...
0 1 2 3 4 5 6 7 8 9 

Additional info:

GCC version:
gcc -v
Reading specs from /usr/lib/gcc-lib/i386-redhat-linux/3.2.3/specs
Configured with: ../configure --prefix=/usr --mandir=/usr/share/man --
infodir=/usr/share/info --enable-shared --enable-threads=posix --disable-
checking --with-system-zlib --enable-__cxa_atexit --host=i386-redhat-linux
Thread model: posix
gcc version 3.2.3 20030502 (Red Hat Linux 3.2.3-20)

Comment 1 Arjan van de Ven 2003-10-22 12:58:44 UTC
Sounds it's cheaper to pass O_NONBLOCK as open flag for the file descriptor so
that the kernel will just return the pending bytes and doesnt' wait for more.
Saves you a syscall.....

Comment 2 David Woodhouse 2003-10-22 13:02:53 UTC
Means you have to buffer though. IF you have fixed-size packets it's easier to
wait till there's a whole packet, then read it.

Comment 3 Arjan van de Ven 2003-10-22 13:10:39 UTC
the example code reads the bytes one by one anyway....


Comment 4 David Woodhouse 2003-10-22 13:31:43 UTC
Well yes, but that's because it's test code designed to count bytes and show the
problem.

Yes, this does look like FIONREAD is buggy, although I can't actually find a
real definition of the expected behaviour of FIONREAD anywhere; SUSv2 doesn't
admit to its existence. 

I can't imagine there being a case for FIONREAD returning anything but either
the number of readable bytes or an error though.

In the meantime, I'd suggest using O_NONBLOCK and either poll() or select(). 

Comment 5 Scott Weathers 2003-10-22 13:39:54 UTC
Can a get a timeout of less than a 1 second with select? Waiting 1 second with 
the signal alarm is very expensive.  I need to be able to timeout in few mili 
seconds if there are no btyes in the ttySO buffer.

Comment 6 Arjan van de Ven 2003-10-22 13:46:55 UTC
select in RHEL3 has a 10 msec granularity (this does mean that if you ask for
10ms you get AT LEAST 10ms, so on average 15ms)

if you don't want to wait *at all* you can use O_NONBLOCK


Comment 7 David Woodhouse 2003-10-22 13:50:32 UTC
If you set the O_NONBLOCK flag with fcntl() as Arjan suggested, your read() call
will return _immediately_ if there are no data available.

At some point presumably your application is waiting for something to happen,
rather than continuously looping and attempting to read.... that's where you'd
be using select(), waiting for an event to happen on _any_ of the file
descriptors you're interested in; not just the serial port. 

According to the man page the select() call allows you to specify a timeout in
....what Arjan just said :)

Comment 8 Scott Weathers 2003-10-22 14:24:25 UTC
I am using the O_NONBLOCK in the open, and the read does not return 
_immediately_ unless I use a SIGALRM.

I have also added the fcntl call, with the same results.

    if(fcntl(fd, F_SETSIG, O_NONBLOCK) < 0)
    {
        fprintf(stderr, "error fcntl:%d - %s\n", errno, strerror(errno));
        return(-1);
    }





Comment 9 David Woodhouse 2003-10-22 14:31:37 UTC
Is that a typo for fcntl(fd, F_SETFL, O_NONBLOCK)?

Try this after open...

int fdflags = fcntl(fd, F_GETFL);
if (fdflags == -1) {
        perror("GETFL");
        exit(1);
}
if (!(fdflags & O_NONBLOCK)) {
        fprintf(stderr, "O_NONBLOCK not set even though we asked for it\n");
        exit(1);
}



Then strace your test program and show me the output of strace.


Comment 10 David Woodhouse 2003-10-22 14:38:52 UTC
You do mean the _read_ doesn't return immediately, don't you? Not just that your
my_get_com_byte() function doesn't return immediately? What changes did you make
to your function? As shown above, it'd just sit in an tight loop calling read()
over and over again until the alarm happens...

Comment 11 Scott Weathers 2003-10-22 14:50:43 UTC
Yes, that is why I need to know how many bytes are on the port. So I don't 
have to wait for the 1 second time out and I can do an intelligent read for 
nchar.

The code would then look like this.

if((i = ioctl( fd, FIONREAD, &nchar )) == -1)
  return(-1);

for(i = 1; i <= nchar; i++)
{
....	}	




Comment 12 Arjan van de Ven 2003-10-22 14:53:51 UTC
if you have O_NONBLOCK set you can just do

ret = read(fd, buf, BUFFERSPACELEFT);

that will return 0 if there are no characters pending, and the amount read if
there's > 1.
no need to loop no need for the IOCTL

Comment 13 David Woodhouse 2003-10-22 15:00:53 UTC
To clarify.... where you seem to want a function which reads all
currently-available characters (up to the buffer size 'max') from the serial
port, which would look like the following:

int read_serial_port(int fd, char *buf, int max)
{
	int nchar;

	if (ioctl(fd, FIONREAD, &nchar) == -1)
		return -1;

	if (nchar > max)
		nchar = max;

	return read(fd, buf, nchar);
}


.... it can in fact be done with O_NONBLOCK as follows:

int read_serial_port(int fd, char *buf, int max)
{
	return read(fd, buf, max);
}


Comment 14 Scott Weathers 2003-10-22 15:16:08 UTC
Here is the strace output.  Do you want see all the strace output or just the 
open?

open("/dev/ttyS0", O_RDWR|O_NONBLOCK)   = 3
fcntl64(3, F_GETFL)                     = 0x802 (flags O_RDWR|O_NONBLOCK)
write(3, "0123456789", 10)              = 10
fstat64(1, {st_mode=S_IFCHR|0600, st_rdev=makedev(136, 1), ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 
0xb75ea000
write(1, "\n", 1)                       = 1
write(1, "Write 10 bytes \n", 16)       = 16
ioctl(3, FIONREAD, [0])                 = 0
write(1, "Found 0 0 bytes in port buffer\n", 31) = 31
rt_sigaction(SIGALRM, {0x8048ab2, [ALRM], SA_RESTORER|SA_RESTART, 0xb72b0b78}, 
{SIG_DFL}, 8) = 0
alarm(1)                                = 0
ioctl(3, FIONREAD, [0])                 = 0
write(1, "Found 0 0 bytes in port buffer\n", 31) = 31
r

Comment 15 David Woodhouse 2003-10-22 15:19:46 UTC
All of it please. You haven't shown read() blocking. I don't believe read()
blocks when the O_NONBLOCK flag is set.... I believe your function is calling
read() over and over again in a tight loop.

Comment 16 Scott Weathers 2003-10-22 19:37:33 UTC
Thanks for the help, it looks like the select and O_NONBLOCK is going to give 
us what we need for now for our serail io processing.  Is someone could look 
into why FIONREAD does not return the number of bytes on the port it would be 
helpful to us in the future.

Comment 17 David Woodhouse 2003-10-23 12:28:44 UTC
This test program shows FIONREAD (which is also known as TIOCINQ) working
correctly; run it and see its output when there are data available on the port...

FIONREAD says 0 chars available
FIONREAD says 0 chars available
FIONREAD says 0 chars available
FIONREAD says 7 chars available
FIONREAD says 7 chars available
FIONREAD says 7 chars available

I suspect your calls to FIONREAD in your test program were happening before the
kernel had registered that there were data available. 

#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
 
 
int main(void)
{
        int fd, nchar;
 
        fd = open("/dev/ttyS0", O_RDWR);
        if (fd == -1) {
                perror("open");
                exit(1);
        }
 
        while(1) {
                if (ioctl(fd, FIONREAD, &nchar)) {
                        perror("ioctl(FIONREAD)");
                        exit(1);
                }
                printf("FIONREAD says %d chars available\n", nchar);
                sleep(1);
        }
        return 0;
}


Comment 18 David Woodhouse 2003-10-23 12:37:29 UTC
Indeed.... if you put the FIONREAD ioctl into the loop in my_get_com_byte() in
your original test case, you'll see it returning a non-zero number of characters.