486 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			486 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * Copyright (c) 2009 Kungliga Tekniska Högskolan
 | 
						|
 * (Royal Institute of Technology, Stockholm, Sweden).
 | 
						|
 * All rights reserved.
 | 
						|
 *
 | 
						|
 * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
 | 
						|
 *
 | 
						|
 * Redistribution and use in source and binary forms, with or without
 | 
						|
 * modification, are permitted provided that the following conditions
 | 
						|
 * are met:
 | 
						|
 *
 | 
						|
 * 1. Redistributions of source code must retain the above copyright
 | 
						|
 *    notice, this list of conditions and the following disclaimer.
 | 
						|
 *
 | 
						|
 * 2. Redistributions in binary form must reproduce the above copyright
 | 
						|
 *    notice, this list of conditions and the following disclaimer in the
 | 
						|
 *    documentation and/or other materials provided with the distribution.
 | 
						|
 *
 | 
						|
 * 3. Neither the name of the Institute nor the names of its contributors
 | 
						|
 *    may be used to endorse or promote products derived from this software
 | 
						|
 *    without specific prior written permission.
 | 
						|
 *
 | 
						|
 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
 | 
						|
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 | 
						|
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 | 
						|
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
 | 
						|
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 | 
						|
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 | 
						|
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 | 
						|
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 | 
						|
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 | 
						|
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 | 
						|
 * SUCH DAMAGE.
 | 
						|
 */
 | 
						|
 | 
						|
#include "hi_locl.h"
 | 
						|
 | 
						|
struct heim_sipc {
 | 
						|
    int (*release)(heim_sipc ctx);
 | 
						|
    heim_ipc_callback callback;
 | 
						|
    void *userctx;
 | 
						|
    void *mech;
 | 
						|
};
 | 
						|
 | 
						|
#if defined(__APPLE__) && defined(HAVE_GCD)
 | 
						|
 | 
						|
#include "heim_ipcServer.h"
 | 
						|
#include "heim_ipc_reply.h"
 | 
						|
#include "heim_ipc_async.h"
 | 
						|
 | 
						|
static dispatch_source_t timer;
 | 
						|
static dispatch_queue_t timerq;
 | 
						|
static uint64_t timeoutvalue;
 | 
						|
 | 
						|
static dispatch_queue_t workq;
 | 
						|
 | 
						|
static void
 | 
						|
default_timer_ev(void)
 | 
						|
{
 | 
						|
    exit(0);
 | 
						|
}
 | 
						|
 | 
						|
static void (*timer_ev)(void) = default_timer_ev;
 | 
						|
 | 
						|
static void
 | 
						|
set_timer(void)
 | 
						|
{
 | 
						|
    dispatch_source_set_timer(timer,
 | 
						|
			      dispatch_time(DISPATCH_TIME_NOW,
 | 
						|
					    timeoutvalue * NSEC_PER_SEC),
 | 
						|
			      timeoutvalue * NSEC_PER_SEC, 1000000); 
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
init_globals(void)
 | 
						|
{
 | 
						|
    static dispatch_once_t once;
 | 
						|
    dispatch_once(&once, ^{ 
 | 
						|
	timerq = dispatch_queue_create("hiem-sipc-timer-q", NULL);
 | 
						|
        timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, timerq);
 | 
						|
	dispatch_source_set_event_handler(timer, ^{ timer_ev(); } );
 | 
						|
 | 
						|
	workq = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 | 
						|
    });
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
suspend_timer(void)
 | 
						|
{
 | 
						|
    dispatch_suspend(timer);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
restart_timer(void)
 | 
						|
{
 | 
						|
    dispatch_sync(timerq, ^{ set_timer(); });
 | 
						|
    dispatch_resume(timer);
 | 
						|
}
 | 
						|
 | 
						|
struct mach_service {
 | 
						|
    mach_port_t sport;
 | 
						|
    dispatch_source_t source;
 | 
						|
    dispatch_queue_t queue;
 | 
						|
};
 | 
						|
 | 
						|
struct mach_call_ctx {
 | 
						|
    mach_port_t reply_port;
 | 
						|
    heim_icred cred;
 | 
						|
    heim_idata req;
 | 
						|
};
 | 
						|
 | 
						|
 | 
						|
static void
 | 
						|
mach_complete_sync(heim_sipc_call ctx, int returnvalue, heim_idata *reply)
 | 
						|
{
 | 
						|
    struct mach_call_ctx *s = (struct mach_call_ctx *)ctx;
 | 
						|
    heim_ipc_message_inband_t replyin;
 | 
						|
    mach_msg_type_number_t replyinCnt;
 | 
						|
    heim_ipc_message_outband_t replyout;
 | 
						|
    mach_msg_type_number_t replyoutCnt;
 | 
						|
    kern_return_t kr;
 | 
						|
 | 
						|
    if (returnvalue) {
 | 
						|
	/* on error, no reply */
 | 
						|
	replyinCnt = 0;
 | 
						|
	replyout = 0; replyoutCnt = 0;
 | 
						|
	kr = KERN_SUCCESS;
 | 
						|
    } else if (reply->length < 2048) {
 | 
						|
	replyinCnt = reply->length;
 | 
						|
	memcpy(replyin, reply->data, replyinCnt);
 | 
						|
	replyout = 0; replyoutCnt = 0;
 | 
						|
	kr = KERN_SUCCESS;
 | 
						|
    } else {
 | 
						|
	replyinCnt = 0;
 | 
						|
	kr = vm_read(mach_task_self(), 
 | 
						|
		     (vm_address_t)reply->data, reply->length,
 | 
						|
		     (vm_address_t *)&replyout, &replyoutCnt);
 | 
						|
    }
 | 
						|
 | 
						|
    mheim_ripc_call_reply(s->reply_port, returnvalue,
 | 
						|
			  replyin, replyinCnt,
 | 
						|
			  replyout, replyoutCnt);
 | 
						|
 | 
						|
    heim_ipc_free_cred(s->cred);
 | 
						|
    free(s->req.data);
 | 
						|
    free(s);
 | 
						|
    restart_timer();
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
mach_complete_async(heim_sipc_call ctx, int returnvalue, heim_idata *reply)
 | 
						|
{
 | 
						|
    struct mach_call_ctx *s = (struct mach_call_ctx *)ctx;
 | 
						|
    heim_ipc_message_inband_t replyin;
 | 
						|
    mach_msg_type_number_t replyinCnt;
 | 
						|
    heim_ipc_message_outband_t replyout;
 | 
						|
    mach_msg_type_number_t replyoutCnt;
 | 
						|
    kern_return_t kr;
 | 
						|
 | 
						|
    if (returnvalue) {
 | 
						|
	/* on error, no reply */
 | 
						|
	replyinCnt = 0;
 | 
						|
	replyout = 0; replyoutCnt = 0;
 | 
						|
	kr = KERN_SUCCESS;
 | 
						|
    } else if (reply->length < 2048) {
 | 
						|
	replyinCnt = reply->length;
 | 
						|
	memcpy(replyin, reply->data, replyinCnt);
 | 
						|
	replyout = 0; replyoutCnt = 0;
 | 
						|
	kr = KERN_SUCCESS;
 | 
						|
    } else {
 | 
						|
	replyinCnt = 0;
 | 
						|
	kr = vm_read(mach_task_self(), 
 | 
						|
		     (vm_address_t)reply->data, reply->length,
 | 
						|
		     (vm_address_t *)&replyout, &replyoutCnt);
 | 
						|
    }
 | 
						|
 | 
						|
    kr = mheim_aipc_acall_reply(s->reply_port, returnvalue,
 | 
						|
				replyin, replyinCnt,
 | 
						|
				replyout, replyoutCnt);
 | 
						|
    heim_ipc_free_cred(s->cred);
 | 
						|
    free(s->req.data);
 | 
						|
    free(s);
 | 
						|
    restart_timer();
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
kern_return_t
 | 
						|
mheim_do_call(mach_port_t server_port,
 | 
						|
	      audit_token_t client_creds,
 | 
						|
	      mach_port_t reply_port,
 | 
						|
	      heim_ipc_message_inband_t requestin,
 | 
						|
	      mach_msg_type_number_t requestinCnt,
 | 
						|
	      heim_ipc_message_outband_t requestout,
 | 
						|
	      mach_msg_type_number_t requestoutCnt,
 | 
						|
	      int *returnvalue,
 | 
						|
	      heim_ipc_message_inband_t replyin,
 | 
						|
	      mach_msg_type_number_t *replyinCnt,
 | 
						|
	      heim_ipc_message_outband_t *replyout,
 | 
						|
	      mach_msg_type_number_t *replyoutCnt)
 | 
						|
{
 | 
						|
    heim_sipc ctx = dispatch_get_context(dispatch_get_current_queue());
 | 
						|
    struct mach_call_ctx *s;
 | 
						|
    kern_return_t kr;
 | 
						|
    uid_t uid;
 | 
						|
    gid_t gid;
 | 
						|
    pid_t pid;
 | 
						|
    au_asid_t session;
 | 
						|
 | 
						|
    *replyout = NULL;
 | 
						|
    *replyoutCnt = 0;
 | 
						|
    *replyinCnt = 0;
 | 
						|
 | 
						|
    s = malloc(sizeof(*s));
 | 
						|
    if (s == NULL)
 | 
						|
	return KERN_MEMORY_FAILURE; /* XXX */
 | 
						|
    
 | 
						|
    s->reply_port = reply_port;
 | 
						|
    
 | 
						|
    audit_token_to_au32(client_creds, NULL, &uid, &gid, NULL, NULL, &pid, &session, NULL);
 | 
						|
    
 | 
						|
    kr = _heim_ipc_create_cred(uid, gid, pid, session, &s->cred);
 | 
						|
    if (kr) {
 | 
						|
	free(s);
 | 
						|
	return kr;
 | 
						|
    }
 | 
						|
    
 | 
						|
    suspend_timer();
 | 
						|
    
 | 
						|
    if (requestinCnt) {
 | 
						|
	s->req.data = malloc(requestinCnt);
 | 
						|
	memcpy(s->req.data, requestin, requestinCnt);
 | 
						|
	s->req.length = requestinCnt;
 | 
						|
    } else {
 | 
						|
	s->req.data = malloc(requestoutCnt);
 | 
						|
	memcpy(s->req.data, requestout, requestoutCnt);
 | 
						|
	s->req.length = requestoutCnt;
 | 
						|
    }
 | 
						|
    
 | 
						|
    dispatch_async(workq, ^{
 | 
						|
	(ctx->callback)(ctx->userctx, &s->req, s->cred,
 | 
						|
			mach_complete_sync, (heim_sipc_call)s);
 | 
						|
    });
 | 
						|
 | 
						|
    return MIG_NO_REPLY;
 | 
						|
}
 | 
						|
 | 
						|
kern_return_t
 | 
						|
mheim_do_call_request(mach_port_t server_port,
 | 
						|
		      audit_token_t client_creds,
 | 
						|
		      mach_port_t reply_port,
 | 
						|
		      heim_ipc_message_inband_t requestin,
 | 
						|
		      mach_msg_type_number_t requestinCnt,
 | 
						|
		      heim_ipc_message_outband_t requestout,
 | 
						|
		      mach_msg_type_number_t requestoutCnt)
 | 
						|
{
 | 
						|
    heim_sipc ctx = dispatch_get_context(dispatch_get_current_queue());
 | 
						|
    struct mach_call_ctx *s;
 | 
						|
    kern_return_t kr;
 | 
						|
    uid_t uid;
 | 
						|
    gid_t gid;
 | 
						|
    pid_t pid;
 | 
						|
    au_asid_t session;
 | 
						|
    
 | 
						|
    s = malloc(sizeof(*s));
 | 
						|
    if (s == NULL)
 | 
						|
	return KERN_MEMORY_FAILURE; /* XXX */
 | 
						|
    
 | 
						|
    s->reply_port = reply_port;
 | 
						|
    
 | 
						|
    audit_token_to_au32(client_creds, NULL, &uid, &gid, NULL, NULL, &pid, &session, NULL);
 | 
						|
    
 | 
						|
    kr = _heim_ipc_create_cred(uid, gid, pid, session, &s->cred);
 | 
						|
    if (kr) {
 | 
						|
	free(s);
 | 
						|
	return kr;
 | 
						|
    }
 | 
						|
    
 | 
						|
    suspend_timer();
 | 
						|
    
 | 
						|
    if (requestinCnt) {
 | 
						|
	s->req.data = malloc(requestinCnt);
 | 
						|
	memcpy(s->req.data, requestin, requestinCnt);
 | 
						|
	s->req.length = requestinCnt;
 | 
						|
    } else {
 | 
						|
	s->req.data = malloc(requestoutCnt);
 | 
						|
	memcpy(s->req.data, requestout, requestoutCnt);
 | 
						|
	s->req.length = requestoutCnt;
 | 
						|
    }
 | 
						|
    
 | 
						|
    dispatch_async(workq, ^{
 | 
						|
	(ctx->callback)(ctx->userctx, &s->req, s->cred,
 | 
						|
			mach_complete_async, (heim_sipc_call)s);
 | 
						|
    });
 | 
						|
    
 | 
						|
    return KERN_SUCCESS;
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
mach_init(const char *service, mach_port_t sport, heim_sipc ctx)
 | 
						|
{
 | 
						|
    struct mach_service *s;
 | 
						|
    char *name;
 | 
						|
 | 
						|
    init_globals();
 | 
						|
 | 
						|
    s = calloc(1, sizeof(*s));
 | 
						|
    if (s == NULL)
 | 
						|
	return ENOMEM;
 | 
						|
 | 
						|
    asprintf(&name, "heim-ipc-mach-%s", service);
 | 
						|
 | 
						|
    s->queue = dispatch_queue_create(name, NULL);
 | 
						|
    free(name);
 | 
						|
    s->sport = sport;
 | 
						|
 | 
						|
    s->source = dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV, 
 | 
						|
				       s->sport, 0, s->queue);
 | 
						|
    if (s->source == NULL) {
 | 
						|
	dispatch_release(s->queue);
 | 
						|
	free(s);
 | 
						|
	return ENOMEM;
 | 
						|
    }
 | 
						|
    ctx->mech = s;
 | 
						|
 | 
						|
    dispatch_set_context(s->queue, ctx);
 | 
						|
    dispatch_set_context(s->source, s);
 | 
						|
 | 
						|
    dispatch_source_set_event_handler(s->source, ^{
 | 
						|
	    dispatch_mig_server(s->source, sizeof(union __RequestUnion__mheim_do_mheim_ipc_subsystem), mheim_ipc_server);
 | 
						|
	});
 | 
						|
 | 
						|
    dispatch_source_set_cancel_handler(s->source, ^{
 | 
						|
	    heim_sipc ctx = dispatch_get_context(dispatch_get_current_queue());
 | 
						|
	    struct mach_service *st = ctx->mech;
 | 
						|
	    mach_port_mod_refs(mach_task_self(), st->sport, 
 | 
						|
			       MACH_PORT_RIGHT_RECEIVE, -1);
 | 
						|
	    dispatch_release(st->queue);
 | 
						|
	    dispatch_release(st->source);
 | 
						|
	    free(st);
 | 
						|
	    free(ctx);
 | 
						|
	});
 | 
						|
 | 
						|
    dispatch_resume(s->source);
 | 
						|
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
mach_release(heim_sipc ctx)
 | 
						|
{
 | 
						|
    struct mach_service *s = ctx->mech;
 | 
						|
    dispatch_source_cancel(s->source);
 | 
						|
    dispatch_release(s->source);
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
static mach_port_t
 | 
						|
mach_checkin_or_register(const char *service)
 | 
						|
{
 | 
						|
    mach_port_t mp;
 | 
						|
    kern_return_t kr;
 | 
						|
 | 
						|
    kr = bootstrap_check_in(bootstrap_port, service, &mp);
 | 
						|
    if (kr == KERN_SUCCESS)
 | 
						|
	return mp;
 | 
						|
 | 
						|
    /* Pre SnowLeopard version */
 | 
						|
    kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &mp);
 | 
						|
    if (kr != KERN_SUCCESS)
 | 
						|
	return MACH_PORT_NULL;
 | 
						|
 | 
						|
    kr = mach_port_insert_right(mach_task_self(), mp, mp, 
 | 
						|
				MACH_MSG_TYPE_MAKE_SEND);
 | 
						|
    if (kr != KERN_SUCCESS) {
 | 
						|
	mach_port_destroy(mach_task_self(), mp);
 | 
						|
	return MACH_PORT_NULL;
 | 
						|
    }
 | 
						|
 | 
						|
    kr = bootstrap_register(bootstrap_port, rk_UNCONST(service), mp);
 | 
						|
    if (kr != KERN_SUCCESS) {
 | 
						|
	mach_port_destroy(mach_task_self(), mp);
 | 
						|
	return MACH_PORT_NULL;
 | 
						|
    }
 | 
						|
 | 
						|
    return mp;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
#endif /* __APPLE__ && HAVE_GCD */
 | 
						|
 | 
						|
 | 
						|
int
 | 
						|
heim_sipc_launchd_mach_init(const char *service,
 | 
						|
			    heim_ipc_callback callback,
 | 
						|
			    void *user, heim_sipc *ctx)
 | 
						|
{
 | 
						|
#if defined(__APPLE__) && defined(HAVE_GCD)
 | 
						|
    mach_port_t sport = MACH_PORT_NULL;
 | 
						|
    heim_sipc c = NULL;
 | 
						|
    int ret;
 | 
						|
 | 
						|
    *ctx = NULL;
 | 
						|
 | 
						|
    sport = mach_checkin_or_register(service);
 | 
						|
    if (sport == MACH_PORT_NULL) {
 | 
						|
	ret = ENOENT;
 | 
						|
	goto error;
 | 
						|
    }
 | 
						|
 | 
						|
    c = calloc(1, sizeof(*c));
 | 
						|
    if (c == NULL) {
 | 
						|
	ret = ENOMEM;
 | 
						|
	goto error;
 | 
						|
    }
 | 
						|
    c->release = mach_release;
 | 
						|
    c->userctx = user;
 | 
						|
    c->callback = callback;
 | 
						|
		 
 | 
						|
    ret = mach_init(service, sport, c);
 | 
						|
    if (ret)
 | 
						|
	goto error;
 | 
						|
 | 
						|
    *ctx = c;
 | 
						|
    return 0;
 | 
						|
 error:
 | 
						|
    if (c)
 | 
						|
	free(c);
 | 
						|
    if (sport != MACH_PORT_NULL)
 | 
						|
	mach_port_mod_refs(mach_task_self(), sport, 
 | 
						|
			   MACH_PORT_RIGHT_RECEIVE, -1);
 | 
						|
    return ret;
 | 
						|
#else /* !(__APPLE__ && HAVE_GCD) */
 | 
						|
    *ctx = NULL;
 | 
						|
    return EINVAL;
 | 
						|
#endif /* __APPLE__ && HAVE_GCD */
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Set the idle timeout value
 | 
						|
 | 
						|
 * The timeout event handler is triggered recurrently every idle
 | 
						|
 * period `t'. The default action is rather draconian and just calls
 | 
						|
 * exit(0), so you might want to change this to something more
 | 
						|
 * graceful using heim_sipc_set_timeout_handler().
 | 
						|
 */
 | 
						|
 | 
						|
void
 | 
						|
heim_sipc_timeout(time_t t)
 | 
						|
{
 | 
						|
#if defined(__APPLE__) && defined(HAVE_GCD)
 | 
						|
    static dispatch_once_t timeoutonce;
 | 
						|
    init_globals();
 | 
						|
    dispatch_sync(timerq, ^{
 | 
						|
	    timeoutvalue = t;
 | 
						|
	    set_timer();
 | 
						|
	});
 | 
						|
    dispatch_once(&timeoutonce, ^{  dispatch_resume(timer); });
 | 
						|
#else
 | 
						|
    abort();
 | 
						|
#endif
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Set the timeout event handler
 | 
						|
 *
 | 
						|
 * Replaces the default idle timeout action.
 | 
						|
 */
 | 
						|
 | 
						|
void
 | 
						|
heim_sipc_set_timeout_handler(void (*func)(void))
 | 
						|
{
 | 
						|
#if defined(__APPLE__) && defined(HAVE_GCD)
 | 
						|
    init_globals();
 | 
						|
    dispatch_sync(timerq, ^{ timer_ev = func; });
 | 
						|
#else
 | 
						|
    abort();
 | 
						|
#endif
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
void
 | 
						|
heim_sipc_free_context(heim_sipc ctx)
 | 
						|
{
 | 
						|
    (ctx->release)(ctx);
 | 
						|
}
 |