3 ms·
select(2) does support fds >= 1024. At least, it does on Linux and many other systems. The issue is that <sys/select.h> defines the type fd_set, a struct contai
by wahern 5y ago
select(2) does support fds >= 1024. At least, it does on Linux and many other systems. The issue is that <sys/select.h> defines the type fd_set, a struct containing a fixed-size bit field as an array of integers. The maximum descriptor number (i.e. the bit field size) is reported by the constant FD_SETSIZE. Thus, conceptually fd_set is defined like
typedef struct {
int fds_bits[howmany(FD_SETSIZE, sizeof (int) * CHAR_BIT)]
} fd_set;
<sys/select.h> also provides a set of macros for initializing the structure (FD_ZERO), and testing (FD_ISSET) and setting (FD_SET, FD_CLR) descriptor numbers. But these macros have no way of reporting if a specified descriptor overflows the bit array. In all the implementations I'm familiar with, including Linux/glibc and Linux/musl, they don't even check, and will happily overwrite the array if given a descriptor greater than or equal to FD_SETSIZE.
But the maximum descriptor number in the bit field is reported to the kernel as an argument to select. And most kernels, including Linux, obey this parameter[1], and thus impose no intrinsic limit. If userspace provides a validly allocated and initialized buffer all will work just fine.
But how to allocate and use a properly sized buffer? Some systems, like OpenBSD, allow you to define FD_SETSIZE (or a similar constant) before including <sys/select.h>. On other systems, like modern Linux/glibc or Linux/musl, you have to get clever, like doing something like
#define MYFD_SETSIZE 100000
#define MYFD_SETSIZE_OVERFLOW (MYFD_SETSIZE - FD_SETSIZE)
union {
fd_set fdset;
char extra[sizeof (fd_set) + howmany(MYFD_SETSIZE_OVERFLOW, CHAR_BIT)];
} u;
but then be careful not to rely on FD_ZERO, which won't know to zero out the high bits.
[1] The same is true of (struct sockaddr_un).sun_path. On most systems you can pass Unix domain socket paths longer than 108 or whatever the local constant is, and the kernel will happily use the full path, though there may be another internal limit, like 256 or 1024. It can do this because syscalls like bind(2) and connect(2) take a separate addrlen parameter. One possibly shocking consequence to many people is that types like `struct sockaddr_storage` are not sufficient to represent every possible socket address. And unless you check the output of the addrlen pointer parameter to syscalls like accept(2), you may have received a truncated address without knowing it.