clinuxfile-handlingposix-select

Linux select() not blocking


I'm trying to understand the difference between select() and poll() better. For this I tried to implement a simple program that will open a file as write-only, add its file descriptor to the read set and than execute select in hopes that the function will block until the read permission is granted. As this didnt work (and as far as I understood, this is intended behaviour) I tried to block access to the file using flock before the select() executen. Still, the program did not block its execution.

My sample code is as follows:

#include <stdio.h>
#include <poll.h>
#include <sys/file.h>
#include <errno.h>
#include <sys/select.h>


int main(int argc, char **argv)
{
   printf("[+] Select minimal example\n");
   int max_number_fds = FOPEN_MAX;
   int select_return;
   int cnt_pollfds;
   struct pollfd pfds_array[max_number_fds];
   struct pollfd *pfds = pfds_array;

   fd_set fds;
   int fd_file = open("./poll_text.txt",  O_WRONLY);

   struct timeval tv;
   tv.tv_sec = 10;
   tv.tv_usec = 0;

   printf("\t[+] Textfile fd: %d\n", fd_file);

   //create and set fds set    
   FD_ZERO(&fds);
   FD_SET(fd_file, &fds);

   printf("[+] Locking file descriptor!\n");
   if(flock(fd_file,LOCK_EX) == -1)
   {
       int error_nr = errno;
       printf("\t[+] Errno: %d\n", error_nr);
   }  

   printf("[+] Executing select()\n");
   select_return = select(fd_file+1, &fds, NULL, NULL, &tv);
   if(select_return == -1){
       int error_nr = errno;
       printf("[+] Select Errno: %d\n", error_nr);
   }

   printf("[+] Select return: %d\n", select_return); 
 }

Can anybody see my error in this code? Also: I first tried to execute this code with two FDs added to the read list. When trying to lock them I had to use flock(fd_file,LOCK_SH) as I cannot exclusively lock two FDs with LOCK_EX. Is there a difference on how to lock two FDs of the same file (compared to only one fd)

I'm also not sure why select will not block when a file, that is added to the Read-set is opened as Write-Only. The program can never (without a permission change) read data from the fd, so in my understanding select should block the execution, right?

As a clarification: My "problem" I want to solve is that I have to check if I'm able to replace existing select() calls with poll() (existing in terms of: i will not re-write the select() call code, but will have access to the arguments of select.). To check this, I wanted to implement a test that will force select to block its execution, so I can later check if poll will act the same way (when given similar instructions, i.e. the same FDs to check).

So my "workflow" would be: write tests for different select behaviors (i.e. block and not block), write similar tests for poll (also block, not block) and check if/how poll can be forced do exactly what select is doing.


Solution

  • When select tells you that a file descriptor is ready for reading, this doesn't necessarily mean that you can read data. It only means that a read call will not block. A read call will also not block when it returns an EOF or error condition.

    In your case I expect that read will immediately return -1 and set errno to EBADF (fd is not a valid file descriptor or is not open for reading) or maybe EINVAL (fd is attached to an object which is unsuitable for reading...)

    Edit: Additional information as requested in a comment:

    A file can be in a blocking state if a physical operation is needed that will take some time, e.g. if the read buffer is empty and (new) data has to be read from the disk, if the file is connected to a terminal and the user has not yet entered any (more) data or if the file is a socket or a pipe and a read would have to wait for (new) data to arrive...

    The same applies for write: If the send buffer is full, a write will block. If the remaining space in the send buffer is smaller than your amount of data, it may write only the part that currently fits into the buffer.

    If you set a file to non-blocking mode, a read or write will not block but tell you that it would block.

    If you want to have a blocking situation for testing purposes, you need control over the process or hardware that provides or consumes the data. I suggest to use read from a terminal (stdin) when you don't enter any data or from a pipe where the writing process does not write any data. You can also fill the write buffer on a pipe when the reading process does not read from it.