2 * Select abstraction functions for the CUPS scheduler.
4 * Copyright 2007-2016 by Apple Inc.
5 * Copyright 2006-2007 by Easy Software Products.
7 * Licensed under Apache License v2.0. See the file "LICENSE" for more information.
11 * Include necessary headers...
17 # include <sys/epoll.h>
19 #elif defined(HAVE_KQUEUE)
20 # include <sys/event.h>
21 # include <sys/time.h>
22 #elif defined(HAVE_POLL)
25 # include <sys/select.h>
26 #endif /* HAVE_EPOLL */
30 * Design Notes for Poll/Select API in CUPSD
31 * -----------------------------------------
35 * OS select poll epoll kqueue /dev/poll
36 * -------------- ------ ------ ------ ------ ---------
37 * AIX YES YES NO NO NO
38 * FreeBSD YES YES NO YES NO
39 * HP-UX YES YES NO NO NO
40 * Linux YES YES YES NO NO
41 * macOS YES YES NO YES NO
42 * NetBSD YES YES NO YES NO
43 * OpenBSD YES YES NO YES NO
44 * Solaris YES YES NO NO YES
45 * Tru64 YES YES NO NO NO
46 * Windows YES NO NO NO NO
51 * typedef void (*cupsd_selfunc_t)(void *data);
53 * void cupsdStartSelect(void);
54 * void cupsdStopSelect(void);
55 * void cupsdAddSelect(int fd, cupsd_selfunc_t read_cb,
56 * cupsd_selfunc_t write_cb, void *data);
57 * void cupsdRemoveSelect(int fd);
58 * int cupsdDoSelect(int timeout);
61 * IMPLEMENTATION STRATEGY
64 * a. CUPS array of file descriptor to callback functions
65 * and data + temporary array of removed fd's.
66 * b. cupsdStartSelect() creates the arrays
67 * c. cupsdStopSelect() destroys the arrays and all elements.
68 * d. cupsdAddSelect() adds to the array and allocates a
69 * new callback element.
70 * e. cupsdRemoveSelect() removes from the active array and
71 * adds to the inactive array.
72 * f. _cupsd_fd_t provides a reference-counted structure for
73 * tracking file descriptors that are monitored.
74 * g. cupsdDoSelect() frees all inactive FDs.
77 * a. Input/Output fd_set variables, copied to working
78 * copies and then used with select().
79 * b. Loop through CUPS array, using FD_ISSET and calling
80 * the read/write callbacks as needed.
81 * c. cupsdRemoveSelect() clears fd_set bit from main and
83 * d. cupsdStopSelect() frees all of the memory used by the
84 * CUPS array and fd_set's.
86 * 2. poll() - O(n log n)
87 * a. Regular array of pollfd, sorted the same as the CUPS
89 * b. Loop through pollfd array, call the corresponding
90 * read/write callbacks as needed.
91 * c. cupsdAddSelect() adds first to CUPS array and flags the
92 * pollfd array as invalid.
93 * d. cupsdDoSelect() rebuilds pollfd array as needed, calls
94 * poll(), then loops through the pollfd array looking up
96 * e. cupsdRemoveSelect() flags the pollfd array as invalid.
97 * f. cupsdStopSelect() frees all of the memory used by the
98 * CUPS array and pollfd array.
101 * a. cupsdStartSelect() creates epoll file descriptor using
102 * epoll_create() with the maximum fd count, and
103 * allocates an events buffer for the maximum fd count.
104 * b. cupsdAdd/RemoveSelect() uses epoll_ctl() to add
105 * (EPOLL_CTL_ADD) or remove (EPOLL_CTL_DEL) a single
106 * event using the level-triggered semantics. The event
107 * user data field is a pointer to the new callback array
109 * c. cupsdDoSelect() uses epoll_wait() with the global event
110 * buffer allocated in cupsdStartSelect() and then loops
111 * through the events, using the user data field to find
112 * the callback record.
113 * d. cupsdStopSelect() closes the epoll file descriptor and
114 * frees all of the memory used by the event buffer.
117 * b. cupsdStartSelect() creates kqueue file descriptor
118 * using kqueue() function and allocates a global event
120 * c. cupsdAdd/RemoveSelect() uses EV_SET and kevent() to
121 * register the changes. The event user data field is a
122 * pointer to the new callback array element.
123 * d. cupsdDoSelect() uses kevent() to poll for events and
124 * loops through the events, using the user data field to
125 * find the callback record.
126 * e. cupsdStopSelect() closes the kqueue() file descriptor
127 * and frees all of the memory used by the event buffer.
129 * 5. /dev/poll - O(n log n) - NOT YET IMPLEMENTED
130 * a. cupsdStartSelect() opens /dev/poll and allocates an
131 * array of pollfd structs; on failure to open /dev/poll,
132 * revert to poll() system call.
133 * b. cupsdAddSelect() writes a single pollfd struct to
134 * /dev/poll with the new file descriptor and the
135 * POLLIN/POLLOUT flags.
136 * c. cupsdRemoveSelect() writes a single pollfd struct to
137 * /dev/poll with the file descriptor and the POLLREMOVE
139 * d. cupsdDoSelect() uses the DP_POLL ioctl to retrieve
140 * events from /dev/poll and then loops through the
141 * returned pollfd array, looking up the file descriptors
143 * e. cupsdStopSelect() closes /dev/poll and frees the
148 * In tests using the "make test" target with option 0 (keep cupsd
149 * running) and the "testspeed" program with "-c 50 -r 1000", epoll()
150 * performed 5.5% slower than select(), followed by kqueue() at 16%
151 * slower than select() and poll() at 18% slower than select(). Similar
152 * results were seen with twice the number of client connections.
154 * The epoll() and kqueue() performance is likely limited by the
155 * number of system calls used to add/modify/remove file
156 * descriptors dynamically. Further optimizations may be possible
157 * in the area of limiting use of cupsdAddSelect() and
158 * cupsdRemoveSelect(), however extreme care will be needed to avoid
159 * excess CPU usage and deadlock conditions.
161 * We may be able to improve the poll() implementation simply by
162 * keeping the pollfd array sync'd with the _cupsd_fd_t array, as that
163 * will eliminate the rebuilding of the array whenever there is a
164 * change and eliminate the fd array lookups in the inner loop of
167 * Since /dev/poll will never be able to use a shadow array, it may
168 * not make sense to implement support for it. ioctl() overhead will
169 * impact performance as well, so my guess would be that, for CUPS,
170 * /dev/poll will yield a net performance loss.
174 * Local structures...
177 typedef struct _cupsd_fd_s
179 int fd
, /* File descriptor */
181 cupsd_selfunc_t read_cb
, /* Read callback */
182 write_cb
; /* Write callback */
183 void *data
; /* Data pointer for callbacks */
191 static cups_array_t
*cupsd_fds
= NULL
;
192 #if defined(HAVE_EPOLL) || defined(HAVE_KQUEUE)
193 static cups_array_t
*cupsd_inactive_fds
= NULL
;
194 static int cupsd_in_select
= 0;
195 #endif /* HAVE_EPOLL || HAVE_KQUEUE */
198 static int cupsd_kqueue_fd
= -1,
199 cupsd_kqueue_changes
= 0;
200 static struct kevent
*cupsd_kqueue_events
= NULL
;
201 #elif defined(HAVE_POLL)
202 static int cupsd_alloc_pollfds
= 0,
203 cupsd_update_pollfds
= 0;
204 static struct pollfd
*cupsd_pollfds
= NULL
;
206 static int cupsd_epoll_fd
= -1;
207 static struct epoll_event
*cupsd_epoll_events
= NULL
;
208 # endif /* HAVE_EPOLL */
210 static fd_set cupsd_global_input
,
213 cupsd_current_output
;
214 #endif /* HAVE_KQUEUE */
221 static int compare_fds(_cupsd_fd_t
*a
, _cupsd_fd_t
*b
);
222 static _cupsd_fd_t
*find_fd(int fd
);
223 #define release_fd(f) { \
225 if (!(f)->use) free((f));\
227 #define retain_fd(f) (f)->use++
231 * 'cupsdAddSelect()' - Add a file descriptor to the list.
234 int /* O - 1 on success, 0 on error */
235 cupsdAddSelect(int fd
, /* I - File descriptor */
236 cupsd_selfunc_t read_cb
, /* I - Read callback */
237 cupsd_selfunc_t write_cb
,/* I - Write callback */
238 void *data
) /* I - Data to pass to callback */
240 _cupsd_fd_t
*fdptr
; /* File descriptor record */
242 int added
; /* 1 if added, 0 if modified */
243 #endif /* HAVE_EPOLL */
247 * Range check input...
250 cupsdLogMessage(CUPSD_LOG_DEBUG2
,
251 "cupsdAddSelect(fd=%d, read_cb=%p, write_cb=%p, data=%p)",
252 fd
, read_cb
, write_cb
, data
);
258 * See if this FD has already been added...
261 if ((fdptr
= find_fd(fd
)) == NULL
)
264 * No, add a new entry...
267 if ((fdptr
= calloc(1, sizeof(_cupsd_fd_t
))) == NULL
)
273 if (!cupsArrayAdd(cupsd_fds
, fdptr
))
275 cupsdLogMessage(CUPSD_LOG_EMERG
, "Unable to add fd %d to array!", fd
);
287 #endif /* HAVE_EPOLL */
291 struct kevent event
; /* Event data */
292 struct timespec timeout
; /* Timeout value */
298 if (fdptr
->read_cb
!= read_cb
)
301 EV_SET(&event
, fd
, EVFILT_READ
, EV_ADD
, 0, 0, fdptr
);
303 EV_SET(&event
, fd
, EVFILT_READ
, EV_DELETE
, 0, 0, fdptr
);
305 if (kevent(cupsd_kqueue_fd
, &event
, 1, NULL
, 0, &timeout
))
307 cupsdLogMessage(CUPSD_LOG_EMERG
, "kevent() returned %s",
313 if (fdptr
->write_cb
!= write_cb
)
316 EV_SET(&event
, fd
, EVFILT_WRITE
, EV_ADD
, 0, 0, fdptr
);
318 EV_SET(&event
, fd
, EVFILT_WRITE
, EV_DELETE
, 0, 0, fdptr
);
320 if (kevent(cupsd_kqueue_fd
, &event
, 1, NULL
, 0, &timeout
))
322 cupsdLogMessage(CUPSD_LOG_EMERG
, "kevent() returned %s",
329 #elif defined(HAVE_POLL)
331 if (cupsd_epoll_fd
>= 0)
333 struct epoll_event event
; /* Event data */
339 event
.events
|= EPOLLIN
;
342 event
.events
|= EPOLLOUT
;
344 event
.data
.ptr
= fdptr
;
346 if (epoll_ctl(cupsd_epoll_fd
, added
? EPOLL_CTL_ADD
: EPOLL_CTL_MOD
, fd
,
349 close(cupsd_epoll_fd
);
351 cupsd_update_pollfds
= 1;
355 # endif /* HAVE_EPOLL */
357 cupsd_update_pollfds
= 1;
361 * Add or remove the file descriptor in the input and output sets
366 FD_SET(fd
, &cupsd_global_input
);
369 FD_CLR(fd
, &cupsd_global_input
);
370 FD_CLR(fd
, &cupsd_current_input
);
374 FD_SET(fd
, &cupsd_global_output
);
377 FD_CLR(fd
, &cupsd_global_output
);
378 FD_CLR(fd
, &cupsd_current_output
);
380 #endif /* HAVE_KQUEUE */
383 * Save the (new) read and write callbacks...
386 fdptr
->read_cb
= read_cb
;
387 fdptr
->write_cb
= write_cb
;
395 * 'cupsdDoSelect()' - Do a select-like operation.
398 int /* O - Number of files or -1 on error */
399 cupsdDoSelect(long timeout
) /* I - Timeout in seconds */
401 int nfds
; /* Number of file descriptors */
402 _cupsd_fd_t
*fdptr
; /* Current file descriptor */
404 int i
; /* Looping var */
405 struct kevent
*event
; /* Current event */
406 struct timespec ktimeout
; /* kevent() timeout */
411 if (timeout
>= 0 && timeout
< 86400)
413 ktimeout
.tv_sec
= timeout
;
414 ktimeout
.tv_nsec
= 0;
416 nfds
= kevent(cupsd_kqueue_fd
, NULL
, 0, cupsd_kqueue_events
, MaxFDs
,
420 nfds
= kevent(cupsd_kqueue_fd
, NULL
, 0, cupsd_kqueue_events
, MaxFDs
, NULL
);
422 cupsd_kqueue_changes
= 0;
424 for (i
= nfds
, event
= cupsd_kqueue_events
; i
> 0; i
--, event
++)
426 fdptr
= (_cupsd_fd_t
*)event
->udata
;
428 if (cupsArrayFind(cupsd_inactive_fds
, fdptr
))
433 if (fdptr
->read_cb
&& event
->filter
== EVFILT_READ
)
434 (*(fdptr
->read_cb
))(fdptr
->data
);
436 if (fdptr
->use
> 1 && fdptr
->write_cb
&& event
->filter
== EVFILT_WRITE
&&
437 !cupsArrayFind(cupsd_inactive_fds
, fdptr
))
438 (*(fdptr
->write_cb
))(fdptr
->data
);
443 #elif defined(HAVE_POLL)
444 struct pollfd
*pfd
; /* Current pollfd structure */
445 int count
; /* Number of file descriptors */
451 if (cupsd_epoll_fd
>= 0)
453 int i
; /* Looping var */
454 struct epoll_event
*event
; /* Current event */
457 if (timeout
>= 0 && timeout
< 86400)
458 nfds
= epoll_wait(cupsd_epoll_fd
, cupsd_epoll_events
, MaxFDs
,
461 nfds
= epoll_wait(cupsd_epoll_fd
, cupsd_epoll_events
, MaxFDs
, -1);
463 if (nfds
< 0 && errno
!= EINTR
)
465 close(cupsd_epoll_fd
);
470 for (i
= nfds
, event
= cupsd_epoll_events
; i
> 0; i
--, event
++)
472 fdptr
= (_cupsd_fd_t
*)event
->data
.ptr
;
474 if (cupsArrayFind(cupsd_inactive_fds
, fdptr
))
479 if (fdptr
->read_cb
&& (event
->events
& (EPOLLIN
| EPOLLERR
| EPOLLHUP
)))
480 (*(fdptr
->read_cb
))(fdptr
->data
);
482 if (fdptr
->use
> 1 && fdptr
->write_cb
&&
483 (event
->events
& (EPOLLOUT
| EPOLLERR
| EPOLLHUP
)) &&
484 !cupsArrayFind(cupsd_inactive_fds
, fdptr
))
485 (*(fdptr
->write_cb
))(fdptr
->data
);
490 goto release_inactive
;
493 # endif /* HAVE_EPOLL */
495 count
= cupsArrayCount(cupsd_fds
);
497 if (cupsd_update_pollfds
)
500 * Update the cupsd_pollfds array to match the current FD array...
503 cupsd_update_pollfds
= 0;
506 * (Re)allocate memory as needed...
509 if (count
> cupsd_alloc_pollfds
)
511 int allocfds
= count
+ 16;
515 pfd
= realloc(cupsd_pollfds
, (size_t)allocfds
* sizeof(struct pollfd
));
517 pfd
= malloc((size_t)allocfds
* sizeof(struct pollfd
));
521 cupsdLogMessage(CUPSD_LOG_EMERG
, "Unable to allocate %d bytes for polling.", (int)((size_t)allocfds
* sizeof(struct pollfd
)));
527 cupsd_alloc_pollfds
= allocfds
;
531 * Rebuild the array...
534 for (fdptr
= (_cupsd_fd_t
*)cupsArrayFirst(cupsd_fds
), pfd
= cupsd_pollfds
;
536 fdptr
= (_cupsd_fd_t
*)cupsArrayNext(cupsd_fds
), pfd
++)
542 pfd
->events
|= POLLIN
;
545 pfd
->events
|= POLLOUT
;
549 if (timeout
>= 0 && timeout
< 86400)
550 nfds
= poll(cupsd_pollfds
, (nfds_t
)count
, timeout
* 1000);
552 nfds
= poll(cupsd_pollfds
, (nfds_t
)count
, -1);
557 * Do callbacks for each file descriptor...
560 for (pfd
= cupsd_pollfds
; count
> 0; pfd
++, count
--)
565 if ((fdptr
= find_fd(pfd
->fd
)) == NULL
)
570 if (fdptr
->read_cb
&& (pfd
->revents
& (POLLIN
| POLLERR
| POLLHUP
)))
571 (*(fdptr
->read_cb
))(fdptr
->data
);
573 if (fdptr
->use
> 1 && fdptr
->write_cb
&&
574 (pfd
->revents
& (POLLOUT
| POLLERR
| POLLHUP
)))
575 (*(fdptr
->write_cb
))(fdptr
->data
);
582 struct timeval stimeout
; /* Timeout for select() */
583 int maxfd
; /* Maximum file descriptor */
587 * Figure out the highest file descriptor number...
590 if ((fdptr
= (_cupsd_fd_t
*)cupsArrayLast(cupsd_fds
)) == NULL
)
593 maxfd
= fdptr
->fd
+ 1;
599 cupsd_current_input
= cupsd_global_input
;
600 cupsd_current_output
= cupsd_global_output
;
602 if (timeout
>= 0 && timeout
< 86400)
604 stimeout
.tv_sec
= timeout
;
605 stimeout
.tv_usec
= 0;
607 nfds
= select(maxfd
, &cupsd_current_input
, &cupsd_current_output
, NULL
,
611 nfds
= select(maxfd
, &cupsd_current_input
, &cupsd_current_output
, NULL
,
617 * Do callbacks for each file descriptor...
620 for (fdptr
= (_cupsd_fd_t
*)cupsArrayFirst(cupsd_fds
);
622 fdptr
= (_cupsd_fd_t
*)cupsArrayNext(cupsd_fds
))
626 if (fdptr
->read_cb
&& FD_ISSET(fdptr
->fd
, &cupsd_current_input
))
627 (*(fdptr
->read_cb
))(fdptr
->data
);
629 if (fdptr
->use
> 1 && fdptr
->write_cb
&&
630 FD_ISSET(fdptr
->fd
, &cupsd_current_output
))
631 (*(fdptr
->write_cb
))(fdptr
->data
);
637 #endif /* HAVE_KQUEUE */
639 #if defined(HAVE_EPOLL) || defined(HAVE_KQUEUE)
641 * Release all inactive file descriptors...
646 # endif /* !HAVE_KQUEUE */
650 for (fdptr
= (_cupsd_fd_t
*)cupsArrayFirst(cupsd_inactive_fds
);
652 fdptr
= (_cupsd_fd_t
*)cupsArrayNext(cupsd_inactive_fds
))
654 cupsArrayRemove(cupsd_inactive_fds
, fdptr
);
657 #endif /* HAVE_EPOLL || HAVE_KQUEUE */
660 * Return the number of file descriptors handled...
667 #ifdef CUPSD_IS_SELECTING
669 * 'cupsdIsSelecting()' - Determine whether we are monitoring a file
673 int /* O - 1 if selecting, 0 otherwise */
674 cupsdIsSelecting(int fd
) /* I - File descriptor */
676 return (find_fd(fd
) != NULL
);
678 #endif /* CUPSD_IS_SELECTING */
682 * 'cupsdRemoveSelect()' - Remove a file descriptor from the list.
686 cupsdRemoveSelect(int fd
) /* I - File descriptor */
688 _cupsd_fd_t
*fdptr
; /* File descriptor record */
690 struct epoll_event event
; /* Event data */
691 #elif defined(HAVE_KQUEUE)
692 struct kevent event
; /* Event data */
693 struct timespec timeout
; /* Timeout value */
694 #elif defined(HAVE_POLL)
695 /* No variables for poll() */
696 #endif /* HAVE_EPOLL */
700 * Range check input...
703 cupsdLogMessage(CUPSD_LOG_DEBUG2
, "cupsdRemoveSelect(fd=%d)", fd
);
709 * Find the file descriptor...
712 if ((fdptr
= find_fd(fd
)) == NULL
)
716 if (epoll_ctl(cupsd_epoll_fd
, EPOLL_CTL_DEL
, fd
, &event
))
718 close(cupsd_epoll_fd
);
720 cupsd_update_pollfds
= 1;
723 #elif defined(HAVE_KQUEUE)
729 EV_SET(&event
, fd
, EVFILT_READ
, EV_DELETE
, 0, 0, fdptr
);
731 if (kevent(cupsd_kqueue_fd
, &event
, 1, NULL
, 0, &timeout
))
733 cupsdLogMessage(CUPSD_LOG_EMERG
, "kevent() returned %s",
741 EV_SET(&event
, fd
, EVFILT_WRITE
, EV_DELETE
, 0, 0, fdptr
);
743 if (kevent(cupsd_kqueue_fd
, &event
, 1, NULL
, 0, &timeout
))
745 cupsdLogMessage(CUPSD_LOG_EMERG
, "kevent() returned %s",
751 #elif defined(HAVE_POLL)
753 * Update the pollfds array...
756 cupsd_update_pollfds
= 1;
759 FD_CLR(fd
, &cupsd_global_input
);
760 FD_CLR(fd
, &cupsd_global_output
);
761 FD_CLR(fd
, &cupsd_current_input
);
762 FD_CLR(fd
, &cupsd_current_output
);
763 #endif /* HAVE_EPOLL */
767 #endif /* HAVE_KQUEUE */
770 * Remove the file descriptor from the active array and add to the
771 * inactive array (or release, if we don't need the inactive array...)
774 cupsArrayRemove(cupsd_fds
, fdptr
);
776 #if defined(HAVE_EPOLL) || defined(HAVE_KQUEUE)
778 cupsArrayAdd(cupsd_inactive_fds
, fdptr
);
780 #endif /* HAVE_EPOLL || HAVE_KQUEUE */
787 * 'cupsdStartSelect()' - Initialize the file polling engine.
791 cupsdStartSelect(void)
793 cupsdLogMessage(CUPSD_LOG_DEBUG
, "cupsdStartSelect()");
795 cupsd_fds
= cupsArrayNew((cups_array_func_t
)compare_fds
, NULL
);
797 #if defined(HAVE_EPOLL) || defined(HAVE_KQUEUE)
798 cupsd_inactive_fds
= cupsArrayNew((cups_array_func_t
)compare_fds
, NULL
);
799 #endif /* HAVE_EPOLL || HAVE_KQUEUE */
802 cupsd_epoll_fd
= epoll_create(MaxFDs
);
803 cupsd_epoll_events
= calloc((size_t)MaxFDs
, sizeof(struct epoll_event
));
804 cupsd_update_pollfds
= 0;
806 #elif defined(HAVE_KQUEUE)
807 cupsd_kqueue_fd
= kqueue();
808 cupsd_kqueue_changes
= 0;
809 cupsd_kqueue_events
= calloc((size_t)MaxFDs
, sizeof(struct kevent
));
811 #elif defined(HAVE_POLL)
812 cupsd_update_pollfds
= 0;
815 FD_ZERO(&cupsd_global_input
);
816 FD_ZERO(&cupsd_global_output
);
817 #endif /* HAVE_EPOLL */
822 * 'cupsdStopSelect()' - Shutdown the file polling engine.
826 cupsdStopSelect(void)
828 _cupsd_fd_t
*fdptr
; /* Current file descriptor */
831 cupsdLogMessage(CUPSD_LOG_DEBUG
, "cupsdStopSelect()");
833 for (fdptr
= (_cupsd_fd_t
*)cupsArrayFirst(cupsd_fds
);
835 fdptr
= (_cupsd_fd_t
*)cupsArrayNext(cupsd_fds
))
838 cupsArrayDelete(cupsd_fds
);
841 #if defined(HAVE_EPOLL) || defined(HAVE_KQUEUE)
842 cupsArrayDelete(cupsd_inactive_fds
);
843 cupsd_inactive_fds
= NULL
;
844 #endif /* HAVE_EPOLL || HAVE_KQUEUE */
847 if (cupsd_kqueue_events
)
849 free(cupsd_kqueue_events
);
850 cupsd_kqueue_events
= NULL
;
853 if (cupsd_kqueue_fd
>= 0)
855 close(cupsd_kqueue_fd
);
856 cupsd_kqueue_fd
= -1;
859 cupsd_kqueue_changes
= 0;
861 #elif defined(HAVE_POLL)
863 if (cupsd_epoll_events
)
865 free(cupsd_epoll_events
);
866 cupsd_epoll_events
= NULL
;
869 if (cupsd_epoll_fd
>= 0)
871 close(cupsd_epoll_fd
);
874 # endif /* HAVE_EPOLL */
879 cupsd_pollfds
= NULL
;
880 cupsd_alloc_pollfds
= 0;
883 cupsd_update_pollfds
= 0;
886 FD_ZERO(&cupsd_global_input
);
887 FD_ZERO(&cupsd_global_output
);
888 #endif /* HAVE_EPOLL */
893 * 'compare_fds()' - Compare file descriptors.
896 static int /* O - Result of comparison */
897 compare_fds(_cupsd_fd_t
*a
, /* I - First file descriptor */
898 _cupsd_fd_t
*b
) /* I - Second file descriptor */
900 return (a
->fd
- b
->fd
);
905 * 'find_fd()' - Find an existing file descriptor record.
908 static _cupsd_fd_t
* /* O - FD record pointer or NULL */
909 find_fd(int fd
) /* I - File descriptor */
911 _cupsd_fd_t
*fdptr
, /* Matching record (if any) */
912 key
; /* Search key */
915 cupsArraySave(cupsd_fds
);
918 fdptr
= (_cupsd_fd_t
*)cupsArrayFind(cupsd_fds
, &key
);
920 cupsArrayRestore(cupsd_fds
);