2 * Select abstraction functions for the CUPS scheduler.
4 * Copyright 2007-2016 by Apple Inc.
5 * Copyright 2006-2007 by Easy Software Products.
7 * These coded instructions, statements, and computer programs are the
8 * property of Apple Inc. and are protected by Federal copyright
9 * law. Distribution and use rights are outlined in the file "LICENSE.txt"
10 * which should have been included with this file. If this file is
11 * file is missing or damaged, see the license at "http://www.cups.org/".
15 * Include necessary headers...
21 # include <sys/epoll.h>
23 #elif defined(HAVE_KQUEUE)
24 # include <sys/event.h>
25 # include <sys/time.h>
26 #elif defined(HAVE_POLL)
29 # include <sys/select.h>
30 #endif /* HAVE_EPOLL */
34 * Design Notes for Poll/Select API in CUPSD
35 * -----------------------------------------
39 * OS select poll epoll kqueue /dev/poll
40 * -------------- ------ ------ ------ ------ ---------
41 * AIX YES YES NO NO NO
42 * FreeBSD YES YES NO YES NO
43 * HP-UX YES YES NO NO NO
44 * Linux YES YES YES NO NO
45 * macOS YES YES NO YES NO
46 * NetBSD YES YES NO YES NO
47 * OpenBSD YES YES NO YES NO
48 * Solaris YES YES NO NO YES
49 * Tru64 YES YES NO NO NO
50 * Windows YES NO NO NO NO
55 * typedef void (*cupsd_selfunc_t)(void *data);
57 * void cupsdStartSelect(void);
58 * void cupsdStopSelect(void);
59 * void cupsdAddSelect(int fd, cupsd_selfunc_t read_cb,
60 * cupsd_selfunc_t write_cb, void *data);
61 * void cupsdRemoveSelect(int fd);
62 * int cupsdDoSelect(int timeout);
65 * IMPLEMENTATION STRATEGY
68 * a. CUPS array of file descriptor to callback functions
69 * and data + temporary array of removed fd's.
70 * b. cupsdStartSelect() creates the arrays
71 * c. cupsdStopSelect() destroys the arrays and all elements.
72 * d. cupsdAddSelect() adds to the array and allocates a
73 * new callback element.
74 * e. cupsdRemoveSelect() removes from the active array and
75 * adds to the inactive array.
76 * f. _cupsd_fd_t provides a reference-counted structure for
77 * tracking file descriptors that are monitored.
78 * g. cupsdDoSelect() frees all inactive FDs.
81 * a. Input/Output fd_set variables, copied to working
82 * copies and then used with select().
83 * b. Loop through CUPS array, using FD_ISSET and calling
84 * the read/write callbacks as needed.
85 * c. cupsdRemoveSelect() clears fd_set bit from main and
87 * d. cupsdStopSelect() frees all of the memory used by the
88 * CUPS array and fd_set's.
90 * 2. poll() - O(n log n)
91 * a. Regular array of pollfd, sorted the same as the CUPS
93 * b. Loop through pollfd array, call the corresponding
94 * read/write callbacks as needed.
95 * c. cupsdAddSelect() adds first to CUPS array and flags the
96 * pollfd array as invalid.
97 * d. cupsdDoSelect() rebuilds pollfd array as needed, calls
98 * poll(), then loops through the pollfd array looking up
100 * e. cupsdRemoveSelect() flags the pollfd array as invalid.
101 * f. cupsdStopSelect() frees all of the memory used by the
102 * CUPS array and pollfd array.
105 * a. cupsdStartSelect() creates epoll file descriptor using
106 * epoll_create() with the maximum fd count, and
107 * allocates an events buffer for the maximum fd count.
108 * b. cupsdAdd/RemoveSelect() uses epoll_ctl() to add
109 * (EPOLL_CTL_ADD) or remove (EPOLL_CTL_DEL) a single
110 * event using the level-triggered semantics. The event
111 * user data field is a pointer to the new callback array
113 * c. cupsdDoSelect() uses epoll_wait() with the global event
114 * buffer allocated in cupsdStartSelect() and then loops
115 * through the events, using the user data field to find
116 * the callback record.
117 * d. cupsdStopSelect() closes the epoll file descriptor and
118 * frees all of the memory used by the event buffer.
121 * b. cupsdStartSelect() creates kqueue file descriptor
122 * using kqueue() function and allocates a global event
124 * c. cupsdAdd/RemoveSelect() uses EV_SET and kevent() to
125 * register the changes. The event user data field is a
126 * pointer to the new callback array element.
127 * d. cupsdDoSelect() uses kevent() to poll for events and
128 * loops through the events, using the user data field to
129 * find the callback record.
130 * e. cupsdStopSelect() closes the kqueue() file descriptor
131 * and frees all of the memory used by the event buffer.
133 * 5. /dev/poll - O(n log n) - NOT YET IMPLEMENTED
134 * a. cupsdStartSelect() opens /dev/poll and allocates an
135 * array of pollfd structs; on failure to open /dev/poll,
136 * revert to poll() system call.
137 * b. cupsdAddSelect() writes a single pollfd struct to
138 * /dev/poll with the new file descriptor and the
139 * POLLIN/POLLOUT flags.
140 * c. cupsdRemoveSelect() writes a single pollfd struct to
141 * /dev/poll with the file descriptor and the POLLREMOVE
143 * d. cupsdDoSelect() uses the DP_POLL ioctl to retrieve
144 * events from /dev/poll and then loops through the
145 * returned pollfd array, looking up the file descriptors
147 * e. cupsdStopSelect() closes /dev/poll and frees the
152 * In tests using the "make test" target with option 0 (keep cupsd
153 * running) and the "testspeed" program with "-c 50 -r 1000", epoll()
154 * performed 5.5% slower than select(), followed by kqueue() at 16%
155 * slower than select() and poll() at 18% slower than select(). Similar
156 * results were seen with twice the number of client connections.
158 * The epoll() and kqueue() performance is likely limited by the
159 * number of system calls used to add/modify/remove file
160 * descriptors dynamically. Further optimizations may be possible
161 * in the area of limiting use of cupsdAddSelect() and
162 * cupsdRemoveSelect(), however extreme care will be needed to avoid
163 * excess CPU usage and deadlock conditions.
165 * We may be able to improve the poll() implementation simply by
166 * keeping the pollfd array sync'd with the _cupsd_fd_t array, as that
167 * will eliminate the rebuilding of the array whenever there is a
168 * change and eliminate the fd array lookups in the inner loop of
171 * Since /dev/poll will never be able to use a shadow array, it may
172 * not make sense to implement support for it. ioctl() overhead will
173 * impact performance as well, so my guess would be that, for CUPS,
174 * /dev/poll will yield a net performance loss.
178 * Local structures...
181 typedef struct _cupsd_fd_s
183 int fd
, /* File descriptor */
185 cupsd_selfunc_t read_cb
, /* Read callback */
186 write_cb
; /* Write callback */
187 void *data
; /* Data pointer for callbacks */
195 static cups_array_t
*cupsd_fds
= NULL
;
196 #if defined(HAVE_EPOLL) || defined(HAVE_KQUEUE)
197 static cups_array_t
*cupsd_inactive_fds
= NULL
;
198 static int cupsd_in_select
= 0;
199 #endif /* HAVE_EPOLL || HAVE_KQUEUE */
202 static int cupsd_kqueue_fd
= -1,
203 cupsd_kqueue_changes
= 0;
204 static struct kevent
*cupsd_kqueue_events
= NULL
;
205 #elif defined(HAVE_POLL)
206 static int cupsd_alloc_pollfds
= 0,
207 cupsd_update_pollfds
= 0;
208 static struct pollfd
*cupsd_pollfds
= NULL
;
210 static int cupsd_epoll_fd
= -1;
211 static struct epoll_event
*cupsd_epoll_events
= NULL
;
212 # endif /* HAVE_EPOLL */
214 static fd_set cupsd_global_input
,
217 cupsd_current_output
;
218 #endif /* HAVE_KQUEUE */
225 static int compare_fds(_cupsd_fd_t
*a
, _cupsd_fd_t
*b
);
226 static _cupsd_fd_t
*find_fd(int fd
);
227 #define release_fd(f) { \
229 if (!(f)->use) free((f));\
231 #define retain_fd(f) (f)->use++
235 * 'cupsdAddSelect()' - Add a file descriptor to the list.
238 int /* O - 1 on success, 0 on error */
239 cupsdAddSelect(int fd
, /* I - File descriptor */
240 cupsd_selfunc_t read_cb
, /* I - Read callback */
241 cupsd_selfunc_t write_cb
,/* I - Write callback */
242 void *data
) /* I - Data to pass to callback */
244 _cupsd_fd_t
*fdptr
; /* File descriptor record */
246 int added
; /* 1 if added, 0 if modified */
247 #endif /* HAVE_EPOLL */
251 * Range check input...
254 cupsdLogMessage(CUPSD_LOG_DEBUG2
,
255 "cupsdAddSelect(fd=%d, read_cb=%p, write_cb=%p, data=%p)",
256 fd
, read_cb
, write_cb
, data
);
262 * See if this FD has already been added...
265 if ((fdptr
= find_fd(fd
)) == NULL
)
268 * No, add a new entry...
271 if ((fdptr
= calloc(1, sizeof(_cupsd_fd_t
))) == NULL
)
277 if (!cupsArrayAdd(cupsd_fds
, fdptr
))
279 cupsdLogMessage(CUPSD_LOG_EMERG
, "Unable to add fd %d to array!", fd
);
291 #endif /* HAVE_EPOLL */
295 struct kevent event
; /* Event data */
296 struct timespec timeout
; /* Timeout value */
302 if (fdptr
->read_cb
!= read_cb
)
305 EV_SET(&event
, fd
, EVFILT_READ
, EV_ADD
, 0, 0, fdptr
);
307 EV_SET(&event
, fd
, EVFILT_READ
, EV_DELETE
, 0, 0, fdptr
);
309 if (kevent(cupsd_kqueue_fd
, &event
, 1, NULL
, 0, &timeout
))
311 cupsdLogMessage(CUPSD_LOG_EMERG
, "kevent() returned %s",
317 if (fdptr
->write_cb
!= write_cb
)
320 EV_SET(&event
, fd
, EVFILT_WRITE
, EV_ADD
, 0, 0, fdptr
);
322 EV_SET(&event
, fd
, EVFILT_WRITE
, EV_DELETE
, 0, 0, fdptr
);
324 if (kevent(cupsd_kqueue_fd
, &event
, 1, NULL
, 0, &timeout
))
326 cupsdLogMessage(CUPSD_LOG_EMERG
, "kevent() returned %s",
333 #elif defined(HAVE_POLL)
335 if (cupsd_epoll_fd
>= 0)
337 struct epoll_event event
; /* Event data */
343 event
.events
|= EPOLLIN
;
346 event
.events
|= EPOLLOUT
;
348 event
.data
.ptr
= fdptr
;
350 if (epoll_ctl(cupsd_epoll_fd
, added
? EPOLL_CTL_ADD
: EPOLL_CTL_MOD
, fd
,
353 close(cupsd_epoll_fd
);
355 cupsd_update_pollfds
= 1;
359 # endif /* HAVE_EPOLL */
361 cupsd_update_pollfds
= 1;
365 * Add or remove the file descriptor in the input and output sets
370 FD_SET(fd
, &cupsd_global_input
);
373 FD_CLR(fd
, &cupsd_global_input
);
374 FD_CLR(fd
, &cupsd_current_input
);
378 FD_SET(fd
, &cupsd_global_output
);
381 FD_CLR(fd
, &cupsd_global_output
);
382 FD_CLR(fd
, &cupsd_current_output
);
384 #endif /* HAVE_KQUEUE */
387 * Save the (new) read and write callbacks...
390 fdptr
->read_cb
= read_cb
;
391 fdptr
->write_cb
= write_cb
;
399 * 'cupsdDoSelect()' - Do a select-like operation.
402 int /* O - Number of files or -1 on error */
403 cupsdDoSelect(long timeout
) /* I - Timeout in seconds */
405 int nfds
; /* Number of file descriptors */
406 _cupsd_fd_t
*fdptr
; /* Current file descriptor */
408 int i
; /* Looping var */
409 struct kevent
*event
; /* Current event */
410 struct timespec ktimeout
; /* kevent() timeout */
415 if (timeout
>= 0 && timeout
< 86400)
417 ktimeout
.tv_sec
= timeout
;
418 ktimeout
.tv_nsec
= 0;
420 nfds
= kevent(cupsd_kqueue_fd
, NULL
, 0, cupsd_kqueue_events
, MaxFDs
,
424 nfds
= kevent(cupsd_kqueue_fd
, NULL
, 0, cupsd_kqueue_events
, MaxFDs
, NULL
);
426 cupsd_kqueue_changes
= 0;
428 for (i
= nfds
, event
= cupsd_kqueue_events
; i
> 0; i
--, event
++)
430 fdptr
= (_cupsd_fd_t
*)event
->udata
;
432 if (cupsArrayFind(cupsd_inactive_fds
, fdptr
))
437 if (fdptr
->read_cb
&& event
->filter
== EVFILT_READ
)
438 (*(fdptr
->read_cb
))(fdptr
->data
);
440 if (fdptr
->use
> 1 && fdptr
->write_cb
&& event
->filter
== EVFILT_WRITE
&&
441 !cupsArrayFind(cupsd_inactive_fds
, fdptr
))
442 (*(fdptr
->write_cb
))(fdptr
->data
);
447 #elif defined(HAVE_POLL)
448 struct pollfd
*pfd
; /* Current pollfd structure */
449 int count
; /* Number of file descriptors */
455 if (cupsd_epoll_fd
>= 0)
457 int i
; /* Looping var */
458 struct epoll_event
*event
; /* Current event */
461 if (timeout
>= 0 && timeout
< 86400)
462 nfds
= epoll_wait(cupsd_epoll_fd
, cupsd_epoll_events
, MaxFDs
,
465 nfds
= epoll_wait(cupsd_epoll_fd
, cupsd_epoll_events
, MaxFDs
, -1);
467 if (nfds
< 0 && errno
!= EINTR
)
469 close(cupsd_epoll_fd
);
474 for (i
= nfds
, event
= cupsd_epoll_events
; i
> 0; i
--, event
++)
476 fdptr
= (_cupsd_fd_t
*)event
->data
.ptr
;
478 if (cupsArrayFind(cupsd_inactive_fds
, fdptr
))
483 if (fdptr
->read_cb
&& (event
->events
& (EPOLLIN
| EPOLLERR
| EPOLLHUP
)))
484 (*(fdptr
->read_cb
))(fdptr
->data
);
486 if (fdptr
->use
> 1 && fdptr
->write_cb
&&
487 (event
->events
& (EPOLLOUT
| EPOLLERR
| EPOLLHUP
)) &&
488 !cupsArrayFind(cupsd_inactive_fds
, fdptr
))
489 (*(fdptr
->write_cb
))(fdptr
->data
);
494 goto release_inactive
;
497 # endif /* HAVE_EPOLL */
499 count
= cupsArrayCount(cupsd_fds
);
501 if (cupsd_update_pollfds
)
504 * Update the cupsd_pollfds array to match the current FD array...
507 cupsd_update_pollfds
= 0;
510 * (Re)allocate memory as needed...
513 if (count
> cupsd_alloc_pollfds
)
515 int allocfds
= count
+ 16;
519 pfd
= realloc(cupsd_pollfds
, (size_t)allocfds
* sizeof(struct pollfd
));
521 pfd
= malloc((size_t)allocfds
* sizeof(struct pollfd
));
525 cupsdLogMessage(CUPSD_LOG_EMERG
, "Unable to allocate %d bytes for polling.", (int)((size_t)allocfds
* sizeof(struct pollfd
)));
531 cupsd_alloc_pollfds
= allocfds
;
535 * Rebuild the array...
538 for (fdptr
= (_cupsd_fd_t
*)cupsArrayFirst(cupsd_fds
), pfd
= cupsd_pollfds
;
540 fdptr
= (_cupsd_fd_t
*)cupsArrayNext(cupsd_fds
), pfd
++)
546 pfd
->events
|= POLLIN
;
549 pfd
->events
|= POLLOUT
;
553 if (timeout
>= 0 && timeout
< 86400)
554 nfds
= poll(cupsd_pollfds
, (nfds_t
)count
, timeout
* 1000);
556 nfds
= poll(cupsd_pollfds
, (nfds_t
)count
, -1);
561 * Do callbacks for each file descriptor...
564 for (pfd
= cupsd_pollfds
; count
> 0; pfd
++, count
--)
569 if ((fdptr
= find_fd(pfd
->fd
)) == NULL
)
574 if (fdptr
->read_cb
&& (pfd
->revents
& (POLLIN
| POLLERR
| POLLHUP
)))
575 (*(fdptr
->read_cb
))(fdptr
->data
);
577 if (fdptr
->use
> 1 && fdptr
->write_cb
&&
578 (pfd
->revents
& (POLLOUT
| POLLERR
| POLLHUP
)))
579 (*(fdptr
->write_cb
))(fdptr
->data
);
586 struct timeval stimeout
; /* Timeout for select() */
587 int maxfd
; /* Maximum file descriptor */
591 * Figure out the highest file descriptor number...
594 if ((fdptr
= (_cupsd_fd_t
*)cupsArrayLast(cupsd_fds
)) == NULL
)
597 maxfd
= fdptr
->fd
+ 1;
603 cupsd_current_input
= cupsd_global_input
;
604 cupsd_current_output
= cupsd_global_output
;
606 if (timeout
>= 0 && timeout
< 86400)
608 stimeout
.tv_sec
= timeout
;
609 stimeout
.tv_usec
= 0;
611 nfds
= select(maxfd
, &cupsd_current_input
, &cupsd_current_output
, NULL
,
615 nfds
= select(maxfd
, &cupsd_current_input
, &cupsd_current_output
, NULL
,
621 * Do callbacks for each file descriptor...
624 for (fdptr
= (_cupsd_fd_t
*)cupsArrayFirst(cupsd_fds
);
626 fdptr
= (_cupsd_fd_t
*)cupsArrayNext(cupsd_fds
))
630 if (fdptr
->read_cb
&& FD_ISSET(fdptr
->fd
, &cupsd_current_input
))
631 (*(fdptr
->read_cb
))(fdptr
->data
);
633 if (fdptr
->use
> 1 && fdptr
->write_cb
&&
634 FD_ISSET(fdptr
->fd
, &cupsd_current_output
))
635 (*(fdptr
->write_cb
))(fdptr
->data
);
641 #endif /* HAVE_KQUEUE */
643 #if defined(HAVE_EPOLL) || defined(HAVE_KQUEUE)
645 * Release all inactive file descriptors...
650 # endif /* !HAVE_KQUEUE */
654 for (fdptr
= (_cupsd_fd_t
*)cupsArrayFirst(cupsd_inactive_fds
);
656 fdptr
= (_cupsd_fd_t
*)cupsArrayNext(cupsd_inactive_fds
))
658 cupsArrayRemove(cupsd_inactive_fds
, fdptr
);
661 #endif /* HAVE_EPOLL || HAVE_KQUEUE */
664 * Return the number of file descriptors handled...
671 #ifdef CUPSD_IS_SELECTING
673 * 'cupsdIsSelecting()' - Determine whether we are monitoring a file
677 int /* O - 1 if selecting, 0 otherwise */
678 cupsdIsSelecting(int fd
) /* I - File descriptor */
680 return (find_fd(fd
) != NULL
);
682 #endif /* CUPSD_IS_SELECTING */
686 * 'cupsdRemoveSelect()' - Remove a file descriptor from the list.
690 cupsdRemoveSelect(int fd
) /* I - File descriptor */
692 _cupsd_fd_t
*fdptr
; /* File descriptor record */
694 struct epoll_event event
; /* Event data */
695 #elif defined(HAVE_KQUEUE)
696 struct kevent event
; /* Event data */
697 struct timespec timeout
; /* Timeout value */
698 #elif defined(HAVE_POLL)
699 /* No variables for poll() */
700 #endif /* HAVE_EPOLL */
704 * Range check input...
707 cupsdLogMessage(CUPSD_LOG_DEBUG2
, "cupsdRemoveSelect(fd=%d)", fd
);
713 * Find the file descriptor...
716 if ((fdptr
= find_fd(fd
)) == NULL
)
720 if (epoll_ctl(cupsd_epoll_fd
, EPOLL_CTL_DEL
, fd
, &event
))
722 close(cupsd_epoll_fd
);
724 cupsd_update_pollfds
= 1;
727 #elif defined(HAVE_KQUEUE)
733 EV_SET(&event
, fd
, EVFILT_READ
, EV_DELETE
, 0, 0, fdptr
);
735 if (kevent(cupsd_kqueue_fd
, &event
, 1, NULL
, 0, &timeout
))
737 cupsdLogMessage(CUPSD_LOG_EMERG
, "kevent() returned %s",
745 EV_SET(&event
, fd
, EVFILT_WRITE
, EV_DELETE
, 0, 0, fdptr
);
747 if (kevent(cupsd_kqueue_fd
, &event
, 1, NULL
, 0, &timeout
))
749 cupsdLogMessage(CUPSD_LOG_EMERG
, "kevent() returned %s",
755 #elif defined(HAVE_POLL)
757 * Update the pollfds array...
760 cupsd_update_pollfds
= 1;
763 FD_CLR(fd
, &cupsd_global_input
);
764 FD_CLR(fd
, &cupsd_global_output
);
765 FD_CLR(fd
, &cupsd_current_input
);
766 FD_CLR(fd
, &cupsd_current_output
);
767 #endif /* HAVE_EPOLL */
771 #endif /* HAVE_KQUEUE */
774 * Remove the file descriptor from the active array and add to the
775 * inactive array (or release, if we don't need the inactive array...)
778 cupsArrayRemove(cupsd_fds
, fdptr
);
780 #if defined(HAVE_EPOLL) || defined(HAVE_KQUEUE)
782 cupsArrayAdd(cupsd_inactive_fds
, fdptr
);
784 #endif /* HAVE_EPOLL || HAVE_KQUEUE */
791 * 'cupsdStartSelect()' - Initialize the file polling engine.
795 cupsdStartSelect(void)
797 cupsdLogMessage(CUPSD_LOG_DEBUG
, "cupsdStartSelect()");
799 cupsd_fds
= cupsArrayNew((cups_array_func_t
)compare_fds
, NULL
);
801 #if defined(HAVE_EPOLL) || defined(HAVE_KQUEUE)
802 cupsd_inactive_fds
= cupsArrayNew((cups_array_func_t
)compare_fds
, NULL
);
803 #endif /* HAVE_EPOLL || HAVE_KQUEUE */
806 cupsd_epoll_fd
= epoll_create(MaxFDs
);
807 cupsd_epoll_events
= calloc((size_t)MaxFDs
, sizeof(struct epoll_event
));
808 cupsd_update_pollfds
= 0;
810 #elif defined(HAVE_KQUEUE)
811 cupsd_kqueue_fd
= kqueue();
812 cupsd_kqueue_changes
= 0;
813 cupsd_kqueue_events
= calloc((size_t)MaxFDs
, sizeof(struct kevent
));
815 #elif defined(HAVE_POLL)
816 cupsd_update_pollfds
= 0;
819 FD_ZERO(&cupsd_global_input
);
820 FD_ZERO(&cupsd_global_output
);
821 #endif /* HAVE_EPOLL */
826 * 'cupsdStopSelect()' - Shutdown the file polling engine.
830 cupsdStopSelect(void)
832 _cupsd_fd_t
*fdptr
; /* Current file descriptor */
835 cupsdLogMessage(CUPSD_LOG_DEBUG
, "cupsdStopSelect()");
837 for (fdptr
= (_cupsd_fd_t
*)cupsArrayFirst(cupsd_fds
);
839 fdptr
= (_cupsd_fd_t
*)cupsArrayNext(cupsd_fds
))
842 cupsArrayDelete(cupsd_fds
);
845 #if defined(HAVE_EPOLL) || defined(HAVE_KQUEUE)
846 cupsArrayDelete(cupsd_inactive_fds
);
847 cupsd_inactive_fds
= NULL
;
848 #endif /* HAVE_EPOLL || HAVE_KQUEUE */
851 if (cupsd_kqueue_events
)
853 free(cupsd_kqueue_events
);
854 cupsd_kqueue_events
= NULL
;
857 if (cupsd_kqueue_fd
>= 0)
859 close(cupsd_kqueue_fd
);
860 cupsd_kqueue_fd
= -1;
863 cupsd_kqueue_changes
= 0;
865 #elif defined(HAVE_POLL)
867 if (cupsd_epoll_events
)
869 free(cupsd_epoll_events
);
870 cupsd_epoll_events
= NULL
;
873 if (cupsd_epoll_fd
>= 0)
875 close(cupsd_epoll_fd
);
878 # endif /* HAVE_EPOLL */
883 cupsd_pollfds
= NULL
;
884 cupsd_alloc_pollfds
= 0;
887 cupsd_update_pollfds
= 0;
890 FD_ZERO(&cupsd_global_input
);
891 FD_ZERO(&cupsd_global_output
);
892 #endif /* HAVE_EPOLL */
897 * 'compare_fds()' - Compare file descriptors.
900 static int /* O - Result of comparison */
901 compare_fds(_cupsd_fd_t
*a
, /* I - First file descriptor */
902 _cupsd_fd_t
*b
) /* I - Second file descriptor */
904 return (a
->fd
- b
->fd
);
909 * 'find_fd()' - Find an existing file descriptor record.
912 static _cupsd_fd_t
* /* O - FD record pointer or NULL */
913 find_fd(int fd
) /* I - File descriptor */
915 _cupsd_fd_t
*fdptr
, /* Matching record (if any) */
916 key
; /* Search key */
919 cupsArraySave(cupsd_fds
);
922 fdptr
= (_cupsd_fd_t
*)cupsArrayFind(cupsd_fds
, &key
);
924 cupsArrayRestore(cupsd_fds
);