2 * Copyright (c) 2009 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
6 * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
19 * 3. Neither the name of the Institute nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
39 #define MAX_PACKET_SIZE (128 * 1024)
42 int (*release
)(heim_sipc ctx
);
43 heim_ipc_callback callback
;
48 #if defined(__APPLE__) && defined(HAVE_GCD)
50 #include "heim_ipcServer.h"
51 #include "heim_ipc_reply.h"
52 #include "heim_ipc_async.h"
54 static dispatch_source_t timer
;
55 static dispatch_queue_t timerq
;
56 static uint64_t timeoutvalue
;
58 static dispatch_queue_t eventq
;
60 static dispatch_queue_t workq
;
63 default_timer_ev(void)
68 static void (*timer_ev
)(void) = default_timer_ev
;
73 dispatch_source_set_timer(timer
,
74 dispatch_time(DISPATCH_TIME_NOW
,
75 timeoutvalue
* NSEC_PER_SEC
),
76 timeoutvalue
* NSEC_PER_SEC
, 1000000);
82 static dispatch_once_t once
;
83 dispatch_once(&once
, ^{
84 timerq
= dispatch_queue_create("hiem-sipc-timer-q", NULL
);
85 timer
= dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER
, 0, 0, timerq
);
86 dispatch_source_set_event_handler(timer
, ^{ timer_ev(); } );
88 workq
= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT
, 0);
89 eventq
= dispatch_queue_create("heim-ipc.event-queue", NULL
);
96 dispatch_suspend(timer
);
102 dispatch_sync(timerq
, ^{ set_timer(); });
103 dispatch_resume(timer
);
106 struct mach_service
{
108 dispatch_source_t source
;
109 dispatch_queue_t queue
;
112 struct mach_call_ctx
{
113 mach_port_t reply_port
;
120 mach_complete_sync(heim_sipc_call ctx
, int returnvalue
, heim_idata
*reply
)
122 struct mach_call_ctx
*s
= (struct mach_call_ctx
*)ctx
;
123 heim_ipc_message_inband_t replyin
;
124 mach_msg_type_number_t replyinCnt
;
125 heim_ipc_message_outband_t replyout
;
126 mach_msg_type_number_t replyoutCnt
;
130 /* on error, no reply */
132 replyout
= 0; replyoutCnt
= 0;
134 } else if (reply
->length
< 2048) {
135 replyinCnt
= reply
->length
;
136 memcpy(replyin
, reply
->data
, replyinCnt
);
137 replyout
= 0; replyoutCnt
= 0;
141 kr
= vm_read(mach_task_self(),
142 (vm_address_t
)reply
->data
, reply
->length
,
143 (vm_address_t
*)&replyout
, &replyoutCnt
);
146 mheim_ripc_call_reply(s
->reply_port
, returnvalue
,
148 replyout
, replyoutCnt
);
150 heim_ipc_free_cred(s
->cred
);
157 mach_complete_async(heim_sipc_call ctx
, int returnvalue
, heim_idata
*reply
)
159 struct mach_call_ctx
*s
= (struct mach_call_ctx
*)ctx
;
160 heim_ipc_message_inband_t replyin
;
161 mach_msg_type_number_t replyinCnt
;
162 heim_ipc_message_outband_t replyout
;
163 mach_msg_type_number_t replyoutCnt
;
167 /* on error, no reply */
169 replyout
= 0; replyoutCnt
= 0;
171 } else if (reply
->length
< 2048) {
172 replyinCnt
= reply
->length
;
173 memcpy(replyin
, reply
->data
, replyinCnt
);
174 replyout
= 0; replyoutCnt
= 0;
178 kr
= vm_read(mach_task_self(),
179 (vm_address_t
)reply
->data
, reply
->length
,
180 (vm_address_t
*)&replyout
, &replyoutCnt
);
183 kr
= mheim_aipc_acall_reply(s
->reply_port
, returnvalue
,
185 replyout
, replyoutCnt
);
186 heim_ipc_free_cred(s
->cred
);
194 mheim_do_call(mach_port_t server_port
,
195 audit_token_t client_creds
,
196 mach_port_t reply_port
,
197 heim_ipc_message_inband_t requestin
,
198 mach_msg_type_number_t requestinCnt
,
199 heim_ipc_message_outband_t requestout
,
200 mach_msg_type_number_t requestoutCnt
,
202 heim_ipc_message_inband_t replyin
,
203 mach_msg_type_number_t
*replyinCnt
,
204 heim_ipc_message_outband_t
*replyout
,
205 mach_msg_type_number_t
*replyoutCnt
)
207 heim_sipc ctx
= dispatch_get_context(dispatch_get_current_queue());
208 struct mach_call_ctx
*s
;
219 s
= malloc(sizeof(*s
));
221 return KERN_MEMORY_FAILURE
; /* XXX */
223 s
->reply_port
= reply_port
;
225 audit_token_to_au32(client_creds
, NULL
, &uid
, &gid
, NULL
, NULL
, &pid
, &session
, NULL
);
227 kr
= _heim_ipc_create_cred(uid
, gid
, pid
, session
, &s
->cred
);
236 s
->req
.data
= malloc(requestinCnt
);
237 memcpy(s
->req
.data
, requestin
, requestinCnt
);
238 s
->req
.length
= requestinCnt
;
240 s
->req
.data
= malloc(requestoutCnt
);
241 memcpy(s
->req
.data
, requestout
, requestoutCnt
);
242 s
->req
.length
= requestoutCnt
;
245 dispatch_async(workq
, ^{
246 (ctx
->callback
)(ctx
->userctx
, &s
->req
, s
->cred
,
247 mach_complete_sync
, (heim_sipc_call
)s
);
254 mheim_do_call_request(mach_port_t server_port
,
255 audit_token_t client_creds
,
256 mach_port_t reply_port
,
257 heim_ipc_message_inband_t requestin
,
258 mach_msg_type_number_t requestinCnt
,
259 heim_ipc_message_outband_t requestout
,
260 mach_msg_type_number_t requestoutCnt
)
262 heim_sipc ctx
= dispatch_get_context(dispatch_get_current_queue());
263 struct mach_call_ctx
*s
;
270 s
= malloc(sizeof(*s
));
272 return KERN_MEMORY_FAILURE
; /* XXX */
274 s
->reply_port
= reply_port
;
276 audit_token_to_au32(client_creds
, NULL
, &uid
, &gid
, NULL
, NULL
, &pid
, &session
, NULL
);
278 kr
= _heim_ipc_create_cred(uid
, gid
, pid
, session
, &s
->cred
);
287 s
->req
.data
= malloc(requestinCnt
);
288 memcpy(s
->req
.data
, requestin
, requestinCnt
);
289 s
->req
.length
= requestinCnt
;
291 s
->req
.data
= malloc(requestoutCnt
);
292 memcpy(s
->req
.data
, requestout
, requestoutCnt
);
293 s
->req
.length
= requestoutCnt
;
296 dispatch_async(workq
, ^{
297 (ctx
->callback
)(ctx
->userctx
, &s
->req
, s
->cred
,
298 mach_complete_async
, (heim_sipc_call
)s
);
305 mach_init(const char *service
, mach_port_t sport
, heim_sipc ctx
)
307 struct mach_service
*s
;
312 s
= calloc(1, sizeof(*s
));
316 asprintf(&name
, "heim-ipc-mach-%s", service
);
318 s
->queue
= dispatch_queue_create(name
, NULL
);
322 s
->source
= dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV
,
323 s
->sport
, 0, s
->queue
);
324 if (s
->source
== NULL
) {
325 dispatch_release(s
->queue
);
331 dispatch_set_context(s
->queue
, ctx
);
332 dispatch_set_context(s
->source
, s
);
334 dispatch_source_set_event_handler(s
->source
, ^{
335 dispatch_mig_server(s
->source
, sizeof(union __RequestUnion__mheim_do_mheim_ipc_subsystem
), mheim_ipc_server
);
338 dispatch_source_set_cancel_handler(s
->source
, ^{
339 heim_sipc ctx
= dispatch_get_context(dispatch_get_current_queue());
340 struct mach_service
*st
= ctx
->mech
;
341 mach_port_mod_refs(mach_task_self(), st
->sport
,
342 MACH_PORT_RIGHT_RECEIVE
, -1);
343 dispatch_release(st
->queue
);
344 dispatch_release(st
->source
);
349 dispatch_resume(s
->source
);
355 mach_release(heim_sipc ctx
)
357 struct mach_service
*s
= ctx
->mech
;
358 dispatch_source_cancel(s
->source
);
359 dispatch_release(s
->source
);
364 mach_checkin_or_register(const char *service
)
369 kr
= bootstrap_check_in(bootstrap_port
, service
, &mp
);
370 if (kr
== KERN_SUCCESS
)
373 /* Pre SnowLeopard version */
374 kr
= mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE
, &mp
);
375 if (kr
!= KERN_SUCCESS
)
376 return MACH_PORT_NULL
;
378 kr
= mach_port_insert_right(mach_task_self(), mp
, mp
,
379 MACH_MSG_TYPE_MAKE_SEND
);
380 if (kr
!= KERN_SUCCESS
) {
381 mach_port_destroy(mach_task_self(), mp
);
382 return MACH_PORT_NULL
;
385 kr
= bootstrap_register(bootstrap_port
, rk_UNCONST(service
), mp
);
386 if (kr
!= KERN_SUCCESS
) {
387 mach_port_destroy(mach_task_self(), mp
);
388 return MACH_PORT_NULL
;
395 #endif /* __APPLE__ && HAVE_GCD */
399 heim_sipc_launchd_mach_init(const char *service
,
400 heim_ipc_callback callback
,
401 void *user
, heim_sipc
*ctx
)
403 #if defined(__APPLE__) && defined(HAVE_GCD)
404 mach_port_t sport
= MACH_PORT_NULL
;
410 sport
= mach_checkin_or_register(service
);
411 if (sport
== MACH_PORT_NULL
) {
416 c
= calloc(1, sizeof(*c
));
421 c
->release
= mach_release
;
423 c
->callback
= callback
;
425 ret
= mach_init(service
, sport
, c
);
434 if (sport
!= MACH_PORT_NULL
)
435 mach_port_mod_refs(mach_task_self(), sport
,
436 MACH_PORT_RIGHT_RECEIVE
, -1);
438 #else /* !(__APPLE__ && HAVE_GCD) */
441 #endif /* __APPLE__ && HAVE_GCD */
446 heim_ipc_callback callback
;
449 #define LISTEN_SOCKET 1
450 #define WAITING_READ 2
451 #define WAITING_WRITE 4
452 #define WAITING_CLOSE 8
454 #define HTTP_REPLY 16
456 #define INHERIT_MASK 0xffff0000
457 #define INCLUDE_ERROR_CODE (1 << 16)
458 #define ALLOW_HTTP (1<<17)
465 dispatch_source_t in
;
466 dispatch_source_t out
;
471 static unsigned num_clients
= 0;
472 static struct client
**clients
= NULL
;
475 static void handle_read(struct client
*);
476 static void handle_write(struct client
*);
477 static int maybe_close(struct client
*);
479 static struct client
*
480 add_new_socket(int fd
,
482 heim_ipc_callback callback
,
488 c
= calloc(1, sizeof(*c
));
492 if (flags
& LISTEN_SOCKET
) {
495 c
->fd
= accept(fd
, NULL
, NULL
);
503 c
->callback
= callback
;
504 c
->userctx
= userctx
;
506 fileflags
= fcntl(c
->fd
, F_GETFL
, 0);
507 fcntl(c
->fd
, F_SETFL
, fileflags
| O_NONBLOCK
);
512 c
->in
= dispatch_source_create(DISPATCH_SOURCE_TYPE_READ
,
514 c
->out
= dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE
,
517 dispatch_source_set_event_handler(c
->in
, ^{
518 int rw
= (c
->flags
& WAITING_WRITE
);
520 if (rw
== 0 && (c
->flags
& WAITING_WRITE
))
521 dispatch_resume(c
->out
);
522 if ((c
->flags
& WAITING_READ
) == 0)
523 dispatch_suspend(c
->in
);
526 dispatch_source_set_event_handler(c
->out
, ^{
528 if ((c
->flags
& WAITING_WRITE
) == 0) {
529 dispatch_suspend(c
->out
);
534 dispatch_resume(c
->in
);
536 clients
= erealloc(clients
, sizeof(clients
[0]) * (num_clients
+ 1));
537 clients
[num_clients
] = c
;
545 maybe_close(struct client
*c
)
549 if (c
->flags
& (WAITING_READ
|WAITING_WRITE
))
553 dispatch_cancel(c
->in
);
554 if ((c
->flags
& WAITING_READ
) == 0)
555 dispatch_resume(c
->in
);
556 dispatch_release(c
->in
);
558 dispatch_cancel(c
->out
);
559 if ((c
->flags
& WAITING_WRITE
) == 0)
560 dispatch_resume(c
->out
);
561 dispatch_release(c
->out
);
563 close(c
->fd
); /* ref count fd close */
575 output_data(struct client
*c
, const void *data
, size_t len
)
577 if (c
->olen
+ len
< c
->olen
)
579 c
->outmsg
= erealloc(c
->outmsg
, c
->olen
+ len
);
580 memcpy(&c
->outmsg
[c
->olen
], data
, len
);
582 c
->flags
|= WAITING_WRITE
;
586 socket_complete(heim_sipc_call ctx
, int returnvalue
, heim_idata
*reply
)
588 struct socket_call
*sc
= (struct socket_call
*)ctx
;
589 struct client
*c
= sc
->c
;
591 /* double complete ? */
595 if ((c
->flags
& WAITING_CLOSE
) == 0) {
599 u32
= htonl(reply
->length
);
600 output_data(c
, &u32
, sizeof(u32
));
603 if (c
->flags
& INCLUDE_ERROR_CODE
) {
604 u32
= htonl(returnvalue
);
605 output_data(c
, &u32
, sizeof(u32
));
609 output_data(c
, reply
->data
, reply
->length
);
611 /* if HTTP, close connection */
612 if (c
->flags
& HTTP_REPLY
) {
613 c
->flags
|= WAITING_CLOSE
;
614 c
->flags
&= ~WAITING_READ
;
621 sc
->c
= NULL
; /* so we can catch double complete */
627 /* remove HTTP %-quoting from buf */
631 unsigned char *p
, *q
;
632 for(p
= q
= (unsigned char *)buf
; *p
; p
++, q
++) {
633 if(*p
== '%' && isxdigit(p
[1]) && isxdigit(p
[2])) {
635 if(sscanf((char *)p
+ 1, "%2x", &x
) != 1)
646 static struct socket_call
*
647 handle_http_tcp(struct client
*c
)
649 struct socket_call
*cs
;
655 s
= (char *)c
->inmsg
;
657 p
= strstr(s
, "\r\n");
664 t
= strtok_r(s
, " \t", &p
);
668 t
= strtok_r(NULL
, " \t", &p
);
672 data
= malloc(strlen(t
));
678 if(de_http(t
) != 0) {
682 proto
= strtok_r(NULL
, " \t", &p
);
687 len
= base64_decode(t
, data
);
691 "Server: Heimdal/" VERSION
"\r\n"
692 "Cache-Control: no-cache\r\n"
693 "Pragma: no-cache\r\n"
694 "Content-type: text/html\r\n"
695 "Content-transfer-encoding: 8bit\r\n\r\n"
696 "<TITLE>404 Not found</TITLE>\r\n"
697 "<H1>404 Not found</H1>\r\n"
698 "That page doesn't exist, maybe you are looking for "
699 "<A HREF=\"http://www.h5l.org/\">Heimdal</A>?\r\n";
701 output_data(c
, proto
, strlen(proto
));
702 output_data(c
, msg
, strlen(msg
));
706 cs
= emalloc(sizeof(*cs
));
715 "Server: Heimdal/" VERSION
"\r\n"
716 "Cache-Control: no-cache\r\n"
717 "Pragma: no-cache\r\n"
718 "Content-type: application/octet-stream\r\n"
719 "Content-transfer-encoding: binary\r\n\r\n";
720 output_data(c
, proto
, strlen(proto
));
721 output_data(c
, msg
, strlen(msg
));
729 handle_read(struct client
*c
)
734 if (c
->flags
& LISTEN_SOCKET
) {
735 add_new_socket(c
->fd
,
736 WAITING_READ
| (c
->flags
& INHERIT_MASK
),
742 if (c
->ptr
- c
->len
< 1024) {
743 c
->inmsg
= erealloc(c
->inmsg
,
748 len
= read(c
->fd
, c
->inmsg
+ c
->ptr
, c
->len
- c
->ptr
);
750 c
->flags
|= WAITING_CLOSE
;
751 c
->flags
&= ~WAITING_READ
;
758 while (c
->ptr
>= sizeof(dlen
)) {
759 struct socket_call
*cs
;
761 if((c
->flags
& ALLOW_HTTP
) && c
->ptr
>= 4 &&
762 strncmp((char *)c
->inmsg
, "GET ", 4) == 0 &&
763 strncmp((char *)c
->inmsg
+ c
->ptr
- 4, "\r\n\r\n", 4) == 0) {
765 /* remove the trailing \r\n\r\n so the string is NUL terminated */
766 c
->inmsg
[c
->ptr
- 4] = '\0';
768 c
->flags
|= HTTP_REPLY
;
770 cs
= handle_http_tcp(c
);
772 c
->flags
|= WAITING_CLOSE
;
773 c
->flags
&= ~WAITING_READ
;
777 memcpy(&dlen
, c
->inmsg
, sizeof(dlen
));
780 if (dlen
> MAX_PACKET_SIZE
) {
781 c
->flags
|= WAITING_CLOSE
;
782 c
->flags
&= ~WAITING_READ
;
785 if (dlen
< c
->ptr
- sizeof(dlen
)) {
789 cs
= emalloc(sizeof(*cs
));
791 cs
->in
.data
= emalloc(dlen
);
792 memcpy(cs
->in
.data
, c
->inmsg
+ sizeof(dlen
), dlen
);
793 cs
->in
.length
= dlen
;
795 c
->ptr
-= sizeof(dlen
) + dlen
;
797 c
->inmsg
+ sizeof(dlen
) + dlen
,
802 c
->callback(c
->userctx
, &cs
->in
,
803 NULL
, socket_complete
,
809 handle_write(struct client
*c
)
813 len
= write(c
->fd
, c
->outmsg
, c
->olen
);
815 c
->flags
|= WAITING_CLOSE
;
816 c
->flags
&= ~(WAITING_WRITE
);
817 } else if (c
->olen
!= len
) {
818 memmove(&c
->outmsg
[0], &c
->outmsg
[len
], c
->olen
- len
);
824 c
->flags
&= ~(WAITING_WRITE
);
838 while(num_clients
> 0) {
840 fds
= malloc(num_clients
* sizeof(fds
[0]));
844 num_fds
= num_clients
;
846 for (n
= 0 ; n
< num_fds
; n
++) {
847 fds
[n
].fd
= clients
[n
]->fd
;
849 if (clients
[n
]->flags
& WAITING_READ
)
850 fds
[n
].events
|= POLLIN
;
851 if (clients
[n
]->flags
& WAITING_WRITE
)
852 fds
[n
].events
|= POLLOUT
;
857 poll(fds
, num_fds
, -1);
859 for (n
= 0 ; n
< num_fds
; n
++) {
860 if (clients
[n
] == NULL
)
862 if (fds
[n
].revents
& POLLERR
) {
863 clients
[n
]->flags
|= WAITING_CLOSE
;
867 if (fds
[n
].revents
& POLLIN
)
868 handle_read(clients
[n
]);
869 if (fds
[n
].revents
& POLLOUT
)
870 handle_write(clients
[n
]);
874 while (n
< num_clients
) {
875 struct client
*c
= clients
[n
];
876 if (maybe_close(c
)) {
877 if (n
< num_clients
- 1)
878 clients
[n
] = clients
[num_clients
- 1];
891 socket_release(heim_sipc ctx
)
893 struct client
*c
= ctx
->mech
;
894 c
->flags
|= WAITING_CLOSE
;
899 heim_sipc_stream_listener(int fd
, int type
,
900 heim_ipc_callback callback
,
901 void *user
, heim_sipc
*ctx
)
903 heim_sipc ct
= calloc(1, sizeof(*ct
));
906 if ((type
& HEIM_SIPC_TYPE_IPC
) && (type
& (HEIM_SIPC_TYPE_UINT32
|HEIM_SIPC_TYPE_HTTP
)))
910 case HEIM_SIPC_TYPE_IPC
:
911 c
= add_new_socket(fd
, LISTEN_SOCKET
|WAITING_READ
|INCLUDE_ERROR_CODE
, callback
, user
);
913 case HEIM_SIPC_TYPE_UINT32
:
914 c
= add_new_socket(fd
, LISTEN_SOCKET
|WAITING_READ
, callback
, user
);
916 case HEIM_SIPC_TYPE_HTTP
:
917 case HEIM_SIPC_TYPE_UINT32
|HEIM_SIPC_TYPE_HTTP
:
918 c
= add_new_socket(fd
, LISTEN_SOCKET
|WAITING_READ
|ALLOW_HTTP
, callback
, user
);
926 ct
->release
= socket_release
;
932 heim_sipc_service_unix(const char *service
,
933 heim_ipc_callback callback
,
934 void *user
, heim_sipc
*ctx
)
936 struct sockaddr_un un
;
939 un
.sun_family
= AF_UNIX
;
941 snprintf(un
.sun_path
, sizeof(un
.sun_path
),
942 "/var/run/.heim_%s-socket", service
);
943 fd
= socket(AF_UNIX
, SOCK_STREAM
, 0);
947 socket_set_reuseaddr(fd
, 1);
951 setsockopt(fd
, 0, LOCAL_CREDS
, (void *)&one
, sizeof(one
));
957 if (bind(fd
, (struct sockaddr
*)&un
, sizeof(un
)) < 0) {
962 if (listen(fd
, SOMAXCONN
) < 0) {
967 chmod(un
.sun_path
, 0666);
969 return heim_sipc_stream_listener(fd
, HEIM_SIPC_TYPE_IPC
,
970 callback
, user
, ctx
);
974 * Set the idle timeout value
976 * The timeout event handler is triggered recurrently every idle
977 * period `t'. The default action is rather draconian and just calls
978 * exit(0), so you might want to change this to something more
979 * graceful using heim_sipc_set_timeout_handler().
983 heim_sipc_timeout(time_t t
)
986 static dispatch_once_t timeoutonce
;
988 dispatch_sync(timerq
, ^{
992 dispatch_once(&timeoutonce
, ^{ dispatch_resume(timer
); });
999 * Set the timeout event handler
1001 * Replaces the default idle timeout action.
1005 heim_sipc_set_timeout_handler(void (*func
)(void))
1009 dispatch_sync(timerq
, ^{ timer_ev
= func
; });
1017 heim_sipc_free_context(heim_sipc ctx
)
1019 (ctx
->release
)(ctx
);