4 * Select abstraction functions for the CUPS scheduler.
6 * Copyright 2007-2014 by Apple Inc.
7 * Copyright 2006-2007 by Easy Software Products.
9 * These coded instructions, statements, and computer programs are the
10 * property of Apple Inc. and are protected by Federal copyright
11 * law. Distribution and use rights are outlined in the file "LICENSE.txt"
12 * which should have been included with this file. If this file is
13 * file is missing or damaged, see the license at "http://www.cups.org/".
17 * Include necessary headers...
23 # include <sys/epoll.h>
25 #elif defined(HAVE_KQUEUE)
26 # include <sys/event.h>
27 # include <sys/time.h>
28 #elif defined(HAVE_POLL)
31 # include <sys/select.h>
32 #endif /* HAVE_EPOLL */
36 * Design Notes for Poll/Select API in CUPSD
37 * -----------------------------------------
41 * OS select poll epoll kqueue /dev/poll
42 * -------------- ------ ------ ------ ------ ---------
43 * AIX YES YES NO NO NO
44 * FreeBSD YES YES NO YES NO
45 * HP-UX YES YES NO NO NO
46 * Linux YES YES YES NO NO
47 * MacOS X YES YES NO YES NO
48 * NetBSD YES YES NO YES NO
49 * OpenBSD YES YES NO YES NO
50 * Solaris YES YES NO NO YES
51 * Tru64 YES YES NO NO NO
52 * Windows YES NO NO NO NO
57 * typedef void (*cupsd_selfunc_t)(void *data);
59 * void cupsdStartSelect(void);
60 * void cupsdStopSelect(void);
61 * void cupsdAddSelect(int fd, cupsd_selfunc_t read_cb,
62 * cupsd_selfunc_t write_cb, void *data);
63 * void cupsdRemoveSelect(int fd);
64 * int cupsdDoSelect(int timeout);
67 * IMPLEMENTATION STRATEGY
70 * a. CUPS array of file descriptor to callback functions
71 * and data + temporary array of removed fd's.
72 * b. cupsdStartSelect() creates the arrays
73 * c. cupsdStopSelect() destroys the arrays and all elements.
74 * d. cupsdAddSelect() adds to the array and allocates a
75 * new callback element.
76 * e. cupsdRemoveSelect() removes from the active array and
77 * adds to the inactive array.
78 * f. _cupsd_fd_t provides a reference-counted structure for
79 * tracking file descriptors that are monitored.
80 * g. cupsdDoSelect() frees all inactive FDs.
83 * a. Input/Output fd_set variables, copied to working
84 * copies and then used with select().
85 * b. Loop through CUPS array, using FD_ISSET and calling
86 * the read/write callbacks as needed.
87 * c. cupsdRemoveSelect() clears fd_set bit from main and
89 * d. cupsdStopSelect() frees all of the memory used by the
90 * CUPS array and fd_set's.
92 * 2. poll() - O(n log n)
93 * a. Regular array of pollfd, sorted the same as the CUPS
95 * b. Loop through pollfd array, call the corresponding
96 * read/write callbacks as needed.
97 * c. cupsdAddSelect() adds first to CUPS array and flags the
98 * pollfd array as invalid.
99 * d. cupsdDoSelect() rebuilds pollfd array as needed, calls
100 * poll(), then loops through the pollfd array looking up
102 * e. cupsdRemoveSelect() flags the pollfd array as invalid.
103 * f. cupsdStopSelect() frees all of the memory used by the
104 * CUPS array and pollfd array.
107 * a. cupsdStartSelect() creates epoll file descriptor using
108 * epoll_create() with the maximum fd count, and
109 * allocates an events buffer for the maximum fd count.
110 * b. cupsdAdd/RemoveSelect() uses epoll_ctl() to add
111 * (EPOLL_CTL_ADD) or remove (EPOLL_CTL_DEL) a single
112 * event using the level-triggered semantics. The event
113 * user data field is a pointer to the new callback array
115 * c. cupsdDoSelect() uses epoll_wait() with the global event
116 * buffer allocated in cupsdStartSelect() and then loops
117 * through the events, using the user data field to find
118 * the callback record.
119 * d. cupsdStopSelect() closes the epoll file descriptor and
120 * frees all of the memory used by the event buffer.
123 * b. cupsdStartSelect() creates kqueue file descriptor
124 * using kqueue() function and allocates a global event
126 * c. cupsdAdd/RemoveSelect() uses EV_SET and kevent() to
127 * register the changes. The event user data field is a
128 * pointer to the new callback array element.
129 * d. cupsdDoSelect() uses kevent() to poll for events and
130 * loops through the events, using the user data field to
131 * find the callback record.
132 * e. cupsdStopSelect() closes the kqueue() file descriptor
133 * and frees all of the memory used by the event buffer.
135 * 5. /dev/poll - O(n log n) - NOT YET IMPLEMENTED
136 * a. cupsdStartSelect() opens /dev/poll and allocates an
137 * array of pollfd structs; on failure to open /dev/poll,
138 * revert to poll() system call.
139 * b. cupsdAddSelect() writes a single pollfd struct to
140 * /dev/poll with the new file descriptor and the
141 * POLLIN/POLLOUT flags.
142 * c. cupsdRemoveSelect() writes a single pollfd struct to
143 * /dev/poll with the file descriptor and the POLLREMOVE
145 * d. cupsdDoSelect() uses the DP_POLL ioctl to retrieve
146 * events from /dev/poll and then loops through the
147 * returned pollfd array, looking up the file descriptors
149 * e. cupsdStopSelect() closes /dev/poll and frees the
154 * In tests using the "make test" target with option 0 (keep cupsd
155 * running) and the "testspeed" program with "-c 50 -r 1000", epoll()
156 * performed 5.5% slower than select(), followed by kqueue() at 16%
157 * slower than select() and poll() at 18% slower than select(). Similar
158 * results were seen with twice the number of client connections.
160 * The epoll() and kqueue() performance is likely limited by the
161 * number of system calls used to add/modify/remove file
162 * descriptors dynamically. Further optimizations may be possible
163 * in the area of limiting use of cupsdAddSelect() and
164 * cupsdRemoveSelect(), however extreme care will be needed to avoid
165 * excess CPU usage and deadlock conditions.
167 * We may be able to improve the poll() implementation simply by
168 * keeping the pollfd array sync'd with the _cupsd_fd_t array, as that
169 * will eliminate the rebuilding of the array whenever there is a
170 * change and eliminate the fd array lookups in the inner loop of
173 * Since /dev/poll will never be able to use a shadow array, it may
174 * not make sense to implement support for it. ioctl() overhead will
175 * impact performance as well, so my guess would be that, for CUPS,
176 * /dev/poll will yield a net performance loss.
180 * Local structures...
183 typedef struct _cupsd_fd_s
185 int fd
, /* File descriptor */
187 cupsd_selfunc_t read_cb
, /* Read callback */
188 write_cb
; /* Write callback */
189 void *data
; /* Data pointer for callbacks */
197 static cups_array_t
*cupsd_fds
= NULL
;
198 #if defined(HAVE_EPOLL) || defined(HAVE_KQUEUE)
199 static cups_array_t
*cupsd_inactive_fds
= NULL
;
200 static int cupsd_in_select
= 0;
201 #endif /* HAVE_EPOLL || HAVE_KQUEUE */
204 static int cupsd_kqueue_fd
= -1,
205 cupsd_kqueue_changes
= 0;
206 static struct kevent
*cupsd_kqueue_events
= NULL
;
207 #elif defined(HAVE_POLL)
208 static int cupsd_alloc_pollfds
= 0,
209 cupsd_update_pollfds
= 0;
210 static struct pollfd
*cupsd_pollfds
= NULL
;
212 static int cupsd_epoll_fd
= -1;
213 static struct epoll_event
*cupsd_epoll_events
= NULL
;
214 # endif /* HAVE_EPOLL */
216 static fd_set cupsd_global_input
,
219 cupsd_current_output
;
220 #endif /* HAVE_KQUEUE */
227 static int compare_fds(_cupsd_fd_t
*a
, _cupsd_fd_t
*b
);
228 static _cupsd_fd_t
*find_fd(int fd
);
229 #define release_fd(f) { \
231 if (!(f)->use) free((f));\
233 #define retain_fd(f) (f)->use++
237 * 'cupsdAddSelect()' - Add a file descriptor to the list.
240 int /* O - 1 on success, 0 on error */
241 cupsdAddSelect(int fd
, /* I - File descriptor */
242 cupsd_selfunc_t read_cb
, /* I - Read callback */
243 cupsd_selfunc_t write_cb
,/* I - Write callback */
244 void *data
) /* I - Data to pass to callback */
246 _cupsd_fd_t
*fdptr
; /* File descriptor record */
248 int added
; /* 1 if added, 0 if modified */
249 #endif /* HAVE_EPOLL */
253 * Range check input...
256 cupsdLogMessage(CUPSD_LOG_DEBUG2
,
257 "cupsdAddSelect(fd=%d, read_cb=%p, write_cb=%p, data=%p)",
258 fd
, read_cb
, write_cb
, data
);
264 * See if this FD has already been added...
267 if ((fdptr
= find_fd(fd
)) == NULL
)
270 * No, add a new entry...
273 if ((fdptr
= calloc(1, sizeof(_cupsd_fd_t
))) == NULL
)
279 if (!cupsArrayAdd(cupsd_fds
, fdptr
))
281 cupsdLogMessage(CUPSD_LOG_EMERG
, "Unable to add fd %d to array!", fd
);
293 #endif /* HAVE_EPOLL */
297 struct kevent event
; /* Event data */
298 struct timespec timeout
; /* Timeout value */
304 if (fdptr
->read_cb
!= read_cb
)
307 EV_SET(&event
, fd
, EVFILT_READ
, EV_ADD
, 0, 0, fdptr
);
309 EV_SET(&event
, fd
, EVFILT_READ
, EV_DELETE
, 0, 0, fdptr
);
311 if (kevent(cupsd_kqueue_fd
, &event
, 1, NULL
, 0, &timeout
))
313 cupsdLogMessage(CUPSD_LOG_EMERG
, "kevent() returned %s",
319 if (fdptr
->write_cb
!= write_cb
)
322 EV_SET(&event
, fd
, EVFILT_WRITE
, EV_ADD
, 0, 0, fdptr
);
324 EV_SET(&event
, fd
, EVFILT_WRITE
, EV_DELETE
, 0, 0, fdptr
);
326 if (kevent(cupsd_kqueue_fd
, &event
, 1, NULL
, 0, &timeout
))
328 cupsdLogMessage(CUPSD_LOG_EMERG
, "kevent() returned %s",
335 #elif defined(HAVE_POLL)
337 if (cupsd_epoll_fd
>= 0)
339 struct epoll_event event
; /* Event data */
345 event
.events
|= EPOLLIN
;
348 event
.events
|= EPOLLOUT
;
350 event
.data
.ptr
= fdptr
;
352 if (epoll_ctl(cupsd_epoll_fd
, added
? EPOLL_CTL_ADD
: EPOLL_CTL_MOD
, fd
,
355 close(cupsd_epoll_fd
);
357 cupsd_update_pollfds
= 1;
361 # endif /* HAVE_EPOLL */
363 cupsd_update_pollfds
= 1;
367 * Add or remove the file descriptor in the input and output sets
372 FD_SET(fd
, &cupsd_global_input
);
375 FD_CLR(fd
, &cupsd_global_input
);
376 FD_CLR(fd
, &cupsd_current_input
);
380 FD_SET(fd
, &cupsd_global_output
);
383 FD_CLR(fd
, &cupsd_global_output
);
384 FD_CLR(fd
, &cupsd_current_output
);
386 #endif /* HAVE_KQUEUE */
389 * Save the (new) read and write callbacks...
392 fdptr
->read_cb
= read_cb
;
393 fdptr
->write_cb
= write_cb
;
401 * 'cupsdDoSelect()' - Do a select-like operation.
404 int /* O - Number of files or -1 on error */
405 cupsdDoSelect(long timeout
) /* I - Timeout in seconds */
407 int nfds
; /* Number of file descriptors */
408 _cupsd_fd_t
*fdptr
; /* Current file descriptor */
410 int i
; /* Looping var */
411 struct kevent
*event
; /* Current event */
412 struct timespec ktimeout
; /* kevent() timeout */
417 if (timeout
>= 0 && timeout
< 86400)
419 ktimeout
.tv_sec
= timeout
;
420 ktimeout
.tv_nsec
= 0;
422 nfds
= kevent(cupsd_kqueue_fd
, NULL
, 0, cupsd_kqueue_events
, MaxFDs
,
426 nfds
= kevent(cupsd_kqueue_fd
, NULL
, 0, cupsd_kqueue_events
, MaxFDs
, NULL
);
428 cupsd_kqueue_changes
= 0;
430 for (i
= nfds
, event
= cupsd_kqueue_events
; i
> 0; i
--, event
++)
432 fdptr
= (_cupsd_fd_t
*)event
->udata
;
434 if (cupsArrayFind(cupsd_inactive_fds
, fdptr
))
439 if (fdptr
->read_cb
&& event
->filter
== EVFILT_READ
)
440 (*(fdptr
->read_cb
))(fdptr
->data
);
442 if (fdptr
->use
> 1 && fdptr
->write_cb
&& event
->filter
== EVFILT_WRITE
&&
443 !cupsArrayFind(cupsd_inactive_fds
, fdptr
))
444 (*(fdptr
->write_cb
))(fdptr
->data
);
449 #elif defined(HAVE_POLL)
450 struct pollfd
*pfd
; /* Current pollfd structure */
451 int count
; /* Number of file descriptors */
457 if (cupsd_epoll_fd
>= 0)
459 int i
; /* Looping var */
460 struct epoll_event
*event
; /* Current event */
463 if (timeout
>= 0 && timeout
< 86400)
464 nfds
= epoll_wait(cupsd_epoll_fd
, cupsd_epoll_events
, MaxFDs
,
467 nfds
= epoll_wait(cupsd_epoll_fd
, cupsd_epoll_events
, MaxFDs
, -1);
469 if (nfds
< 0 && errno
!= EINTR
)
471 close(cupsd_epoll_fd
);
476 for (i
= nfds
, event
= cupsd_epoll_events
; i
> 0; i
--, event
++)
478 fdptr
= (_cupsd_fd_t
*)event
->data
.ptr
;
480 if (cupsArrayFind(cupsd_inactive_fds
, fdptr
))
485 if (fdptr
->read_cb
&& (event
->events
& (EPOLLIN
| EPOLLERR
| EPOLLHUP
)))
486 (*(fdptr
->read_cb
))(fdptr
->data
);
488 if (fdptr
->use
> 1 && fdptr
->write_cb
&&
489 (event
->events
& (EPOLLOUT
| EPOLLERR
| EPOLLHUP
)) &&
490 !cupsArrayFind(cupsd_inactive_fds
, fdptr
))
491 (*(fdptr
->write_cb
))(fdptr
->data
);
496 goto release_inactive
;
499 # endif /* HAVE_EPOLL */
501 count
= cupsArrayCount(cupsd_fds
);
503 if (cupsd_update_pollfds
)
506 * Update the cupsd_pollfds array to match the current FD array...
509 cupsd_update_pollfds
= 0;
512 * (Re)allocate memory as needed...
515 if (count
> cupsd_alloc_pollfds
)
517 int allocfds
= count
+ 16;
521 pfd
= realloc(cupsd_pollfds
, allocfds
* sizeof(struct pollfd
));
523 pfd
= malloc(allocfds
* sizeof(struct pollfd
));
527 cupsdLogMessage(CUPSD_LOG_EMERG
,
528 "Unable to allocate %d bytes for polling!",
529 (int)(allocfds
* sizeof(struct pollfd
)));
535 cupsd_alloc_pollfds
= allocfds
;
539 * Rebuild the array...
542 for (fdptr
= (_cupsd_fd_t
*)cupsArrayFirst(cupsd_fds
), pfd
= cupsd_pollfds
;
544 fdptr
= (_cupsd_fd_t
*)cupsArrayNext(cupsd_fds
), pfd
++)
550 pfd
->events
|= POLLIN
;
553 pfd
->events
|= POLLOUT
;
557 if (timeout
>= 0 && timeout
< 86400)
558 nfds
= poll(cupsd_pollfds
, count
, timeout
* 1000);
560 nfds
= poll(cupsd_pollfds
, count
, -1);
565 * Do callbacks for each file descriptor...
568 for (pfd
= cupsd_pollfds
; count
> 0; pfd
++, count
--)
573 if ((fdptr
= find_fd(pfd
->fd
)) == NULL
)
578 if (fdptr
->read_cb
&& (pfd
->revents
& (POLLIN
| POLLERR
| POLLHUP
)))
579 (*(fdptr
->read_cb
))(fdptr
->data
);
581 if (fdptr
->use
> 1 && fdptr
->write_cb
&&
582 (pfd
->revents
& (POLLOUT
| POLLERR
| POLLHUP
)))
583 (*(fdptr
->write_cb
))(fdptr
->data
);
590 struct timeval stimeout
; /* Timeout for select() */
591 int maxfd
; /* Maximum file descriptor */
595 * Figure out the highest file descriptor number...
598 if ((fdptr
= (_cupsd_fd_t
*)cupsArrayLast(cupsd_fds
)) == NULL
)
601 maxfd
= fdptr
->fd
+ 1;
607 cupsd_current_input
= cupsd_global_input
;
608 cupsd_current_output
= cupsd_global_output
;
610 if (timeout
>= 0 && timeout
< 86400)
612 stimeout
.tv_sec
= timeout
;
613 stimeout
.tv_usec
= 0;
615 nfds
= select(maxfd
, &cupsd_current_input
, &cupsd_current_output
, NULL
,
619 nfds
= select(maxfd
, &cupsd_current_input
, &cupsd_current_output
, NULL
,
625 * Do callbacks for each file descriptor...
628 for (fdptr
= (_cupsd_fd_t
*)cupsArrayFirst(cupsd_fds
);
630 fdptr
= (_cupsd_fd_t
*)cupsArrayNext(cupsd_fds
))
634 if (fdptr
->read_cb
&& FD_ISSET(fdptr
->fd
, &cupsd_current_input
))
635 (*(fdptr
->read_cb
))(fdptr
->data
);
637 if (fdptr
->use
> 1 && fdptr
->write_cb
&&
638 FD_ISSET(fdptr
->fd
, &cupsd_current_output
))
639 (*(fdptr
->write_cb
))(fdptr
->data
);
645 #endif /* HAVE_KQUEUE */
647 #if defined(HAVE_EPOLL) || defined(HAVE_KQUEUE)
649 * Release all inactive file descriptors...
654 # endif /* !HAVE_KQUEUE */
658 for (fdptr
= (_cupsd_fd_t
*)cupsArrayFirst(cupsd_inactive_fds
);
660 fdptr
= (_cupsd_fd_t
*)cupsArrayNext(cupsd_inactive_fds
))
662 cupsArrayRemove(cupsd_inactive_fds
, fdptr
);
665 #endif /* HAVE_EPOLL || HAVE_KQUEUE */
668 * Return the number of file descriptors handled...
675 #ifdef CUPSD_IS_SELECTING
677 * 'cupsdIsSelecting()' - Determine whether we are monitoring a file
681 int /* O - 1 if selecting, 0 otherwise */
682 cupsdIsSelecting(int fd
) /* I - File descriptor */
684 return (find_fd(fd
) != NULL
);
686 #endif /* CUPSD_IS_SELECTING */
690 * 'cupsdRemoveSelect()' - Remove a file descriptor from the list.
694 cupsdRemoveSelect(int fd
) /* I - File descriptor */
696 _cupsd_fd_t
*fdptr
; /* File descriptor record */
698 struct epoll_event event
; /* Event data */
699 #elif defined(HAVE_KQUEUE)
700 struct kevent event
; /* Event data */
701 struct timespec timeout
; /* Timeout value */
702 #elif defined(HAVE_POLL)
703 /* No variables for poll() */
704 #endif /* HAVE_EPOLL */
708 * Range check input...
711 cupsdLogMessage(CUPSD_LOG_DEBUG2
, "cupsdRemoveSelect(fd=%d)", fd
);
717 * Find the file descriptor...
720 if ((fdptr
= find_fd(fd
)) == NULL
)
724 if (epoll_ctl(cupsd_epoll_fd
, EPOLL_CTL_DEL
, fd
, &event
))
726 close(cupsd_epoll_fd
);
728 cupsd_update_pollfds
= 1;
731 #elif defined(HAVE_KQUEUE)
737 EV_SET(&event
, fd
, EVFILT_READ
, EV_DELETE
, 0, 0, fdptr
);
739 if (kevent(cupsd_kqueue_fd
, &event
, 1, NULL
, 0, &timeout
))
741 cupsdLogMessage(CUPSD_LOG_EMERG
, "kevent() returned %s",
749 EV_SET(&event
, fd
, EVFILT_WRITE
, EV_DELETE
, 0, 0, fdptr
);
751 if (kevent(cupsd_kqueue_fd
, &event
, 1, NULL
, 0, &timeout
))
753 cupsdLogMessage(CUPSD_LOG_EMERG
, "kevent() returned %s",
759 #elif defined(HAVE_POLL)
761 * Update the pollfds array...
764 cupsd_update_pollfds
= 1;
767 FD_CLR(fd
, &cupsd_global_input
);
768 FD_CLR(fd
, &cupsd_global_output
);
769 FD_CLR(fd
, &cupsd_current_input
);
770 FD_CLR(fd
, &cupsd_current_output
);
771 #endif /* HAVE_EPOLL */
775 #endif /* HAVE_KQUEUE */
778 * Remove the file descriptor from the active array and add to the
779 * inactive array (or release, if we don't need the inactive array...)
782 cupsArrayRemove(cupsd_fds
, fdptr
);
784 #if defined(HAVE_EPOLL) || defined(HAVE_KQUEUE)
786 cupsArrayAdd(cupsd_inactive_fds
, fdptr
);
788 #endif /* HAVE_EPOLL || HAVE_KQUEUE */
795 * 'cupsdStartSelect()' - Initialize the file polling engine.
799 cupsdStartSelect(void)
801 cupsdLogMessage(CUPSD_LOG_DEBUG
, "cupsdStartSelect()");
803 cupsd_fds
= cupsArrayNew((cups_array_func_t
)compare_fds
, NULL
);
805 #if defined(HAVE_EPOLL) || defined(HAVE_KQUEUE)
806 cupsd_inactive_fds
= cupsArrayNew((cups_array_func_t
)compare_fds
, NULL
);
807 #endif /* HAVE_EPOLL || HAVE_KQUEUE */
810 cupsd_epoll_fd
= epoll_create(MaxFDs
);
811 cupsd_epoll_events
= calloc(MaxFDs
, sizeof(struct epoll_event
));
812 cupsd_update_pollfds
= 0;
814 #elif defined(HAVE_KQUEUE)
815 cupsd_kqueue_fd
= kqueue();
816 cupsd_kqueue_changes
= 0;
817 cupsd_kqueue_events
= calloc((size_t)MaxFDs
, sizeof(struct kevent
));
819 #elif defined(HAVE_POLL)
820 cupsd_update_pollfds
= 0;
823 FD_ZERO(&cupsd_global_input
);
824 FD_ZERO(&cupsd_global_output
);
825 #endif /* HAVE_EPOLL */
830 * 'cupsdStopSelect()' - Shutdown the file polling engine.
834 cupsdStopSelect(void)
836 _cupsd_fd_t
*fdptr
; /* Current file descriptor */
839 cupsdLogMessage(CUPSD_LOG_DEBUG
, "cupsdStopSelect()");
841 for (fdptr
= (_cupsd_fd_t
*)cupsArrayFirst(cupsd_fds
);
843 fdptr
= (_cupsd_fd_t
*)cupsArrayNext(cupsd_fds
))
846 cupsArrayDelete(cupsd_fds
);
849 #if defined(HAVE_EPOLL) || defined(HAVE_KQUEUE)
850 cupsArrayDelete(cupsd_inactive_fds
);
851 cupsd_inactive_fds
= NULL
;
852 #endif /* HAVE_EPOLL || HAVE_KQUEUE */
855 if (cupsd_kqueue_events
)
857 free(cupsd_kqueue_events
);
858 cupsd_kqueue_events
= NULL
;
861 if (cupsd_kqueue_fd
>= 0)
863 close(cupsd_kqueue_fd
);
864 cupsd_kqueue_fd
= -1;
867 cupsd_kqueue_changes
= 0;
869 #elif defined(HAVE_POLL)
871 if (cupsd_epoll_events
)
873 free(cupsd_epoll_events
);
874 cupsd_epoll_events
= NULL
;
877 if (cupsd_epoll_fd
>= 0)
879 close(cupsd_epoll_fd
);
882 # endif /* HAVE_EPOLL */
887 cupsd_pollfds
= NULL
;
888 cupsd_alloc_pollfds
= 0;
891 cupsd_update_pollfds
= 0;
894 FD_ZERO(&cupsd_global_input
);
895 FD_ZERO(&cupsd_global_output
);
896 #endif /* HAVE_EPOLL */
901 * 'compare_fds()' - Compare file descriptors.
904 static int /* O - Result of comparison */
905 compare_fds(_cupsd_fd_t
*a
, /* I - First file descriptor */
906 _cupsd_fd_t
*b
) /* I - Second file descriptor */
908 return (a
->fd
- b
->fd
);
913 * 'find_fd()' - Find an existing file descriptor record.
916 static _cupsd_fd_t
* /* O - FD record pointer or NULL */
917 find_fd(int fd
) /* I - File descriptor */
919 _cupsd_fd_t
*fdptr
, /* Matching record (if any) */
920 key
; /* Search key */
923 cupsArraySave(cupsd_fds
);
926 fdptr
= (_cupsd_fd_t
*)cupsArrayFind(cupsd_fds
, &key
);
928 cupsArrayRestore(cupsd_fds
);