1 /**
2  * @file large_fd_set.c
3  *
4  * @brief Macro's and functions for manipulation of large file descriptor sets.
5  *
6  * Portions of this file are copyrighted by:
7  * Copyright (c) 2016 VMware, Inc. All rights reserved.
8  * Use is subject to license terms specified in the COPYING file
9  * distributed with the Net-SNMP package.
10  */
11 
12 #include <net-snmp/net-snmp-config.h>
13 
14 #ifdef HAVE_STDLIB_H
15 #include <stdlib.h>
16 #else
17 #error broken
18 #endif
19 
20 #include <stdio.h>
21 #include <string.h> /* memset(), which is invoked by FD_ZERO() */
22 
23 #include <stddef.h>
24 
25 #include <net-snmp/net-snmp-includes.h>
26 #include <net-snmp/library/snmp_assert.h>
27 #include <net-snmp/library/large_fd_set.h>
28 
29 
30 #if !defined(cygwin) && defined(HAVE_WINSOCK_H)
31 
32 #define LFD_SET(n, p)    FD_SET(n, p)
33 #define LFD_CLR(n, p)    FD_CLR(n, p)
34 #define LFD_ISSET(n, p)  FD_ISSET(n, p)
35 
36 void
netsnmp_large_fd_setfd(SOCKET fd,netsnmp_large_fd_set * fdset)37 netsnmp_large_fd_setfd(SOCKET fd, netsnmp_large_fd_set * fdset)
38 {
39     unsigned        i;
40 
41     netsnmp_assert(fd != INVALID_SOCKET);
42 
43     if (fdset->lfs_setptr->fd_count == fdset->lfs_setsize)
44         netsnmp_large_fd_set_resize(fdset, 2 * (fdset->lfs_setsize + 1));
45 
46     for (i = 0; i < fdset->lfs_setptr->fd_count; i++) {
47         if (fdset->lfs_setptr->fd_array[i] == fd)
48             break;
49     }
50 
51     if (i == fdset->lfs_setptr->fd_count &&
52         fdset->lfs_setptr->fd_count < fdset->lfs_setsize) {
53         fdset->lfs_setptr->fd_count++;
54         fdset->lfs_setptr->fd_array[i] = fd;
55     }
56 }
57 
58 void
netsnmp_large_fd_clr(SOCKET fd,netsnmp_large_fd_set * fdset)59 netsnmp_large_fd_clr(SOCKET fd, netsnmp_large_fd_set * fdset)
60 {
61     unsigned        i;
62 
63     netsnmp_assert(fd != INVALID_SOCKET);
64 
65     for (i = 0; i < fdset->lfs_setptr->fd_count; i++) {
66         if (fdset->lfs_setptr->fd_array[i] == fd) {
67             while (i < fdset->lfs_setptr->fd_count - 1) {
68                 fdset->lfs_setptr->fd_array[i] =
69                     fdset->lfs_setptr->fd_array[i + 1];
70                 i++;
71             }
72             fdset->lfs_setptr->fd_count--;
73             break;
74         }
75     }
76 }
77 
78 int
netsnmp_large_fd_is_set(SOCKET fd,netsnmp_large_fd_set * fdset)79 netsnmp_large_fd_is_set(SOCKET fd, netsnmp_large_fd_set * fdset)
80 {
81     unsigned int    i;
82 
83     netsnmp_assert(fd != INVALID_SOCKET);
84 
85     for (i = 0; i < fdset->lfs_setptr->fd_count; i++) {
86         if (fdset->lfs_setptr->fd_array[i] == fd)
87             return 1;
88     }
89     return 0;
90 }
91 
92 #else
93 
94 /*
95  * Recent versions of glibc trigger abort() if FD_SET(), FD_CLR() or
96  * FD_ISSET() is invoked with n >= FD_SETSIZE. Hence these replacement
97  * functions. However, since NFDBITS != 8 * sizeof(fd_set.fds_bits[0]) for at
98  * least HP-UX on ia64 and since that combination uses big endian, use the
99  * macros from <sys/select.h> on such systems.
100  */
LFD_SET(unsigned n,fd_set * p)101 NETSNMP_STATIC_INLINE void LFD_SET(unsigned n, fd_set *p)
102 {
103     enum { nfdbits = 8 * sizeof(p->fds_bits[0]) };
104 
105     if (nfdbits == NFDBITS)
106         p->fds_bits[n / nfdbits] |= (1ULL << (n % nfdbits));
107     else
108         FD_SET(n, p);
109 }
110 
LFD_CLR(unsigned n,fd_set * p)111 NETSNMP_STATIC_INLINE void LFD_CLR(unsigned n, fd_set *p)
112 {
113     enum { nfdbits = 8 * sizeof(p->fds_bits[0]) };
114 
115     if (nfdbits == NFDBITS)
116         p->fds_bits[n / nfdbits] &= ~(1ULL << (n % nfdbits));
117     else
118         FD_CLR(n, p);
119 }
120 
LFD_ISSET(unsigned n,const fd_set * p)121 NETSNMP_STATIC_INLINE unsigned LFD_ISSET(unsigned n, const fd_set *p)
122 {
123     enum { nfdbits = 8 * sizeof(p->fds_bits[0]) };
124 
125     if (nfdbits == NFDBITS)
126         return (p->fds_bits[n / nfdbits] & (1ULL << (n % nfdbits))) != 0;
127     else
128         return FD_ISSET(n, p) != 0;
129 }
130 
131 void
netsnmp_large_fd_setfd(int fd,netsnmp_large_fd_set * fdset)132 netsnmp_large_fd_setfd(int fd, netsnmp_large_fd_set * fdset)
133 {
134     netsnmp_assert(fd >= 0);
135 
136     while (fd >= (int)fdset->lfs_setsize)
137         netsnmp_large_fd_set_resize(fdset, 2 * (fdset->lfs_setsize + 1));
138 
139     LFD_SET(fd, fdset->lfs_setptr);
140 }
141 
142 void
netsnmp_large_fd_clr(int fd,netsnmp_large_fd_set * fdset)143 netsnmp_large_fd_clr(int fd, netsnmp_large_fd_set * fdset)
144 {
145     netsnmp_assert(fd >= 0);
146 
147     if ((unsigned)fd < fdset->lfs_setsize)
148         LFD_CLR(fd, fdset->lfs_setptr);
149 }
150 
151 int
netsnmp_large_fd_is_set(int fd,netsnmp_large_fd_set * fdset)152 netsnmp_large_fd_is_set(int fd, netsnmp_large_fd_set * fdset)
153 {
154     netsnmp_assert(fd >= 0);
155 
156     return ((unsigned)fd < fdset->lfs_setsize &&
157             LFD_ISSET(fd, fdset->lfs_setptr));
158 }
159 
160 #endif
161 
162 void
netsnmp_large_fd_set_init(netsnmp_large_fd_set * fdset,int setsize)163 netsnmp_large_fd_set_init(netsnmp_large_fd_set * fdset, int setsize)
164 {
165     fdset->lfs_setsize = 0;
166     fdset->lfs_setptr  = NULL;
167 #if !defined(cygwin) && defined(HAVE_WINSOCK_H)
168     fdset->lfs_set.fd_count = 0;
169 #endif
170     netsnmp_large_fd_set_resize(fdset, setsize);
171 }
172 
173 int
netsnmp_large_fd_set_select(int numfds,netsnmp_large_fd_set * readfds,netsnmp_large_fd_set * writefds,netsnmp_large_fd_set * exceptfds,struct timeval * timeout)174 netsnmp_large_fd_set_select(int numfds, netsnmp_large_fd_set *readfds,
175                      netsnmp_large_fd_set *writefds,
176                      netsnmp_large_fd_set *exceptfds,
177                      struct timeval *timeout)
178 {
179     NETSNMP_SELECT_TIMEVAL tmo;
180 
181     if (timeout) {
182         tmo.tv_sec  = timeout->tv_sec;
183         tmo.tv_usec = timeout->tv_usec;
184     }
185 #if defined(cygwin) || !defined(HAVE_WINSOCK_H)
186     /* Bit-set representation: make sure all fds have at least size 'numfds'. */
187     if (readfds && readfds->lfs_setsize < numfds)
188         netsnmp_large_fd_set_resize(readfds, numfds);
189     if (writefds && writefds->lfs_setsize < numfds)
190         netsnmp_large_fd_set_resize(writefds, numfds);
191     if (exceptfds && exceptfds->lfs_setsize < numfds)
192         netsnmp_large_fd_set_resize(exceptfds, numfds);
193 #else
194     /* Array representation: no resizing is necessary. */
195 #endif
196 
197     return select(numfds, (readfds) ? readfds->lfs_setptr : NULL,
198                   (writefds) ? writefds->lfs_setptr : NULL,
199                   (exceptfds) ? exceptfds->lfs_setptr : NULL,
200                   timeout ? &tmo : NULL);
201 }
202 
203 int
netsnmp_large_fd_set_resize(netsnmp_large_fd_set * fdset,int setsize)204 netsnmp_large_fd_set_resize(netsnmp_large_fd_set * fdset, int setsize)
205 {
206     int             fd_set_bytes;
207 
208     if (fdset->lfs_setsize == setsize)
209         goto success;
210 
211     if (setsize > FD_SETSIZE) {
212         fd_set_bytes = NETSNMP_FD_SET_BYTES(setsize);
213         if (fdset->lfs_setsize > FD_SETSIZE) {
214             fdset->lfs_setptr = realloc(fdset->lfs_setptr, fd_set_bytes);
215             if (!fdset->lfs_setptr)
216                 goto out_of_mem;
217         } else {
218             fdset->lfs_setptr = malloc(fd_set_bytes);
219             if (!fdset->lfs_setptr)
220                 goto out_of_mem;
221             *fdset->lfs_setptr = fdset->lfs_set;
222         }
223     } else {
224         if (fdset->lfs_setsize > FD_SETSIZE) {
225             fdset->lfs_set = *fdset->lfs_setptr;
226             free(fdset->lfs_setptr);
227         }
228         fdset->lfs_setptr = &fdset->lfs_set;
229     }
230 
231 #if defined(cygwin) || !defined(HAVE_WINSOCK_H)
232     /*
233      * Unix: when enlarging, clear the file descriptors defined in the
234      * resized *fdset but that were not defined in the original *fdset.
235      */
236     if ( fdset->lfs_setsize == 0 && setsize == FD_SETSIZE ) {
237         /* In this case we can use the OS's FD_ZERO */
238         FD_ZERO(fdset->lfs_setptr);
239     } else {
240         int             i;
241 
242         for (i = fdset->lfs_setsize; i < setsize; i++)
243             LFD_CLR(i, fdset->lfs_setptr);
244     }
245 #endif
246 
247     fdset->lfs_setsize = setsize;
248 #if !defined(cygwin) && defined(HAVE_WINSOCK_H)
249     if (setsize < fdset->lfs_setptr->fd_count)
250         fdset->lfs_setptr->fd_count = setsize;
251 #endif
252 success:
253     return 1;
254 
255 out_of_mem:
256     fdset->lfs_setsize = 0;
257 #if !defined(cygwin) && defined(HAVE_WINSOCK_H)
258     fdset->lfs_setptr->fd_count = 0;
259 #endif
260     return 0;
261 }
262 
263 void
netsnmp_large_fd_set_cleanup(netsnmp_large_fd_set * fdset)264 netsnmp_large_fd_set_cleanup(netsnmp_large_fd_set * fdset)
265 {
266     netsnmp_large_fd_set_resize(fdset, 0);
267     fdset->lfs_setsize = 0;
268     fdset->lfs_setptr  = NULL;
269 }
270 
271 void
netsnmp_copy_fd_set_to_large_fd_set(netsnmp_large_fd_set * dst,const fd_set * src)272 netsnmp_copy_fd_set_to_large_fd_set(netsnmp_large_fd_set * dst,
273                                     const fd_set * src)
274 {
275     netsnmp_large_fd_set_resize(dst, FD_SETSIZE);
276     *dst->lfs_setptr = *src;
277 }
278 
279 int
netsnmp_copy_large_fd_set_to_fd_set(fd_set * dst,const netsnmp_large_fd_set * src)280 netsnmp_copy_large_fd_set_to_fd_set(fd_set * dst,
281                                     const netsnmp_large_fd_set * src)
282 {
283     /* Report failure if *src is larger than FD_SETSIZE. */
284     if (src->lfs_setsize > FD_SETSIZE) {
285         FD_ZERO(dst);
286         return -1;
287     }
288 
289     *dst = *src->lfs_setptr;
290 
291 #if !(!defined(cygwin) && defined(HAVE_WINSOCK_H))
292     {
293         int             i;
294 
295         /* Unix: clear any file descriptors defined in *dst but not in *src. */
296         for (i = src->lfs_setsize; i < FD_SETSIZE; ++i)
297             FD_CLR(i, dst);
298     }
299 #endif
300 
301     return 0;
302 }
303