Illegal search error when working with socket streams with non-empty read buffers

I am currently writing a Linux x86_64 server application using <sys/socket.h> . After accepting the connection via accept() I use fdopen() to wrap the extracted socket into a FILE* stream.

Writing and reading from this FILE* stream usually works quite well, but the socket becomes inappropriate as soon as I write to it while it has a non-empty read buffer.

For demo purposes, I wrote code that listens for the connection, then reads line by line input into the read buffer using fgetc() . If the line is too long to fit into the buffer, it is not fully read, but instead is read during the next iteration.

 #include <unistd.h> #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> FILE* listen_on_port(unsigned short port) { int sock = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in name; name.sin_family = AF_INET; name.sin_port = htons(port); name.sin_addr.s_addr = htonl(INADDR_ANY); if(bind(sock, (struct sockaddr*) &name, sizeof(name)) < 0) perror("bind failed"); listen(sock, 5); int newsock = accept(sock, 0, 0); return fdopen(newsock, "r+"); } int main(int argc, char** argv) { int bufsize = 8; char buf[9]; buf[8] = 0; //ensure null termination int data; int size; //listen on the port specified in argv[1] FILE* sock = listen_on_port(atoi(argv[1])); puts("New connection incoming"); while(1) { //read a single line for(size = 0; size < bufsize; size++) { data = fgetc(sock); if(data == EOF) break; if(data == '\n') { buf[size] = 0; break; } buf[size] = (char) data; } //check if the read failed due to an EOF if(data == EOF) { perror("EOF: Connection reset by peer"); break; } else { printf("Input line: '%s'\n", buf); } //try to write ack if(fputs("ack\n", sock) == EOF) perror("sending 'ack' failed"); //try to flush if(fflush(sock) == EOF) perror("fflush failed"); } puts("Connection closed"); } 

The code should compile in gcc without any special parameters. Run it with the port number as an argument and use netcat to connect to it locally.

Now, if you try to send lines less than 8 characters long, this will work flawlessly. But if you send a string containing more than 10 characters, the program will end with an error. This input example:

 ab cd abcdefghij 

Generates this output:

 New connection incoming Input line: 'ab' Input line: 'cd' Input line: 'abcdefgh' fflush failed: Illegal seek EOF: Connection reset by peer: Illegal seek Connection closed 

As you can see, (by right) only the first 8 characters of abcdefgh are read, but when the program tries to send the string “ack” (which the client never receives), and then clears the output buffer, we get Illegal seek , and the next call to fgetc() returns EOF .

If the fflush() commented out, the same error still occurs, but

 fflush failed: Illegal seek 
There is no line at the server output

.

If the fputs(ack) commented out, everything seems to work as intended, but the perror (), called manually from gdb, still reports an Illegal Search error.

If both fputs(ack) and fflush() commented out, everything works as planned.

Unfortunately, I could not find any good documentation or any online discussions on this issue, so your help would be greatly appreciated.

change

The solution that I finally decided was not to use fdopen() and FILE* , because there seems to be no clean way to convert the fd socket to FILE* , which can be reliably used in r+ mode. Instead, I worked directly on the fd socket, writing my own replacement code for fputs and fprintf .

If someone needs this, here is the code .

+6
source share
4 answers

Obviously, the “r +” (read / write) mode does not work on sockets in this implementation, no doubt, because the base code assumes that it should strive to switch between reading and writing. This is a common case with stdio streams (that you have to perform some kind of synchronization operation) because in real time the actual stdio implementations had only one counter for the stream, and it was either a counter of the number of remaining characters to read from the stream buffer via getc macro "(in read mode) or" the number of characters that can be safely written to the stream buffer using the putc macro (in write mode). In addition to resetting this single counter, you had to perform an operation such as a search.

Distortion is not allowed for pipes and sockets (since the "file offset" does not make sense there).

One solution is not to completely pack the socket with stdio. The other is probably simpler / better for your purposes - to wrap it with not one but two stdio streams:

 FILE *in = fdopen(newsock, "r"); FILE *out = fdopen(newsock, "w"); 

There is another drawback here, because when you go to fclose one thread that closes another file descriptor. To get around this, you need a dup socket descriptor once (in any of the two calls above, it doesn't matter which one).

If you intend to use select or poll or similar in the socket at some point, you should usually go to the "do not wrap using stdio" solution, as there is no clean, clean portable way of tracking stdio buffering. (There are specific ways of implementation).

+4
source

Do not use fflush() on network sockets. These are unbuffered streams.

In addition, this code:

 //read a single line for(size = 0; size < bufsize; size++) { data = fgetc(sock); if(data == EOF) break; if(data == '\n') { buf[size] = 0; break; } buf[size] = (char) data; } 

does not read a single line. It reads only the size of the buffer that you defined as 8. sock will still have the data to receive, which you should get before by writing to the stream using fputs . BTW you can replace this whole block

 fgets(buf, bufsize, sock); 
+1
source

Try the following:

 #define BUFSIZE 88 FILE* listen_on_port(unsigned short port) { ... } int main(int argc, char** argv) { int bufsize = BUFSIZE; char buf[ BUFSIZE ]; 
0
source

Yes, you can use one file stream to work with your socket, at least on Linux. But you have to be careful with it: you should only use ferror () to check for errors. I have a code that uses this and works flawlessly on a large French site.

If you use errno or perror (), you will catch any internal error that the stream will encounter, even if it wants to hide it for you. And "Illegal Search" is one of them.

In addition, to check the real conditions of EOF, you should use feof (), since when returning true, it mutually excludes if ferror () returns a nonzero value. This is because when using the fgetc () function, you have no value to distinguish the error from the actual EOF conditions. Therefore, you are better off using fgets () as another user pointed out.

So your test:

 if(data == EOF) { perror("EOF: Connection reset by peer"); break; } else { printf("Input line: '%s'\n", buf); } 

Must be written as:

 int sock_error = ferror(sock); if (sock_error) { fprintf(stderr, "Error while reading: %s", strerror(sock_error)); } else { printf("Input line: '%s'\n", buf); } 
0
source

Source: https://habr.com/ru/post/913762/


All Articles