views:

82

answers:

6

I am writing an application in C for linux, which uses 2 separate third-party libraries. Both libraries are asynchronous and use select(). They also provide an API which returns the file descriptors they wait on. My intention is to pass these into my own select() and then return control back to whichever library when their own fd values are set.

I think I've got most of it written, but I have trouble at the point where the select() parameters are concerned: Both libraries do not give individual file descriptors, but pointers to their read and write fd_sets. I need to combine the returned fd_sets from these libraries into one fd_set for read, one fd_set for write, etc.

Any suggestions on how I can combine 2 fd_sets into one resulting fd_set?

Addendum Sorry! I should have been clearer.. these libraries only return the fd_sets... I don't know the number of FDs in each set so that I can do a for loop and set each FD individually.. is there a simple way of determining this given just an fd_set?

A: 

The fd_set is actually a bitmap. Maybe you can combine them using binary or.

typedef long int __fd_mask;

typedef struct
{
    __fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS];
} fd_set;
Sjoerd
One might need to take special care about the size of the fd_set: it can be also dynamically allocated. Accessing bytes past the `(nfds+7)/8` (where `nfds` is the first select()'s argument) should be avoided. Unless of course one can confirm that the libraries are actually using the proper fd_set from sys/select.h.
Dummy00001
Except in the most performance-intensive applications, I would recommend strongly against this approach. Looking inside opaque data structures and writing code that depends on their implementation is a very bad practice.
R..
+1  A: 

Without relying on the internal implementation of fd_set, the best you can do is a simple pair of loops

fd_set combine_sets(fd_set* p_set1, int n1, fd_set* p_set2, int n2)
{
   fd_set combined;
   int i;

   FD_ZERO(combined);

   for (i = 0; i < n1; ++i) {
     if (FD_ISSET(i, p_set1)) {
       FD_SET(i, &combined);
     }
   }

   for (i = 0; i < n2; ++i) {
     if (FD_ISSET(i, p_set2)) {
       FD_SET(i, &combined);
     }
   }

   return combined;
}

Where the n parameters are the largest descriptor number potentially in the corresponding set, plus one (i.e. the nfds argument that you would pass to select if you were using just that set).

Edit: Changed to take pointers as arguments, since that's what you said you are getting from your libraries.

Tyler McHenry
Returning an fd_set seems like a really bad idea. It's been a while since I read POSIX but it may even allow fd_set to be an array type, in which case this code would fail. Better to pass a pointer to an fd_set and let the function write into that...
R..
+1  A: 

Iterate through one set and check each fd up to FD_SETSIZE with FD_ISSET() and if it is set, set the fd in the other set using FD_SET()

janm
A: 

I don't think you can do that portably, however if you rewrite your code to use poll() you can merge the sets it uses since they are just arrays of portably structs

Spudd86
+5  A: 

C code which doesn't depend on the implementation of fd_set:

void Fdset_Add(fd_set *Out, fd_set const *In, int InNfds)
{
    for(i = 0; i < InNfds; i++)
    {
        if(i < InNfds && FD_ISSET(i, In))
            FD_SET(i, Out);
    }
}

int Fdset_Merge(fd_set *Out, fd_set const *In1, int NFds1, fd_set const *In2, int NFds2)
{
    FD_ZERO(Out);
    Fdset_Add(Out, In1, Nfds1);
    Fdset_Add(Out, In2, Nfds2);
    return Nfds1 > Nfds2 ? Nfds1 : Nfds2;
}

int Fdset_Filter(fd_set const *Result, int ResultNfds, fd_set *ToFilter, int NfdsToFilter)
{
    int i;
    int Retval;

    Retval = 0;
    for(i = 0; i < ResultNfds; i++)
    {
        if(i < NfdsToFilter && FD_ISSET(i, ToFilter))
        {
            if(! FD_ISSET(i, Result))
                FD_CLR(i, ToFilter);
            else
                Retval++;
        }
    }
    return Retval;
}

void Fdset_Split(fd_set const *Result, int ResultNfds, fd_set *In1, int Nfds1, int *Count1, fd_set *In2, int Nfds2, int *Count2)
{
     *Count1 = Fdset_Filter(Result, ResultNfds, In1, Nfds1);
     *Count2 = Fdset_Filter(Result, ResultNfds, In1, Nfds2);
}
Aidan Cully
in FDSet_Merge() you only loop up to the minimum of NFds1 and NFds2. You need to loop up to the maximum of the two
JeremyP
@JeremyP - thanks, fixed.
Aidan Cully
Shouldn't that be "const fd_set *In", and so on?
unwind
@unwind - I try to put the `const` to the right of the type, as it will always qualify the type directly to its left. Maintaining this style reinforces that understanding, so that if I encounter something like `int * const * Something;`, it's easier to remember which part is being marked read-only.
Aidan Cully
@Aidan: Interesting, thanks.
unwind
+1  A: 

You can set up your own fd_set by looping over all possible file descriptors that each library could possibly have open and in each of their returned sets and set your's accordingly. It's a bit brute-force but the select interface is unfortunately quite primitive. For example

fd_set *lib1_read_fds, *lib2_read_fds;
int fd;
fdset my_fd_set;
FD_CLR(&my_fd_set);
for (fd = 0; fd < FD_SETSIZE; fd++) {
    if (FD_ISSET(fd, lib1_read_fds) || FD_ISSET(fd, lib2_read_fds)) {
        FD_SET(fd, &my_fd_set);
    }
}
bjg
You need to use FD_ZERO, not FD_CLR to zero the result set.
JeremyP
My bad. Should be FD_ZERO of course.
bjg