basic ipc framework
This commit is contained in:
541
lib/ipc/client.c
Normal file
541
lib/ipc/client.c
Normal file
@@ -0,0 +1,541 @@
|
||||
/*
|
||||
* 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"
|
||||
|
||||
#ifdef __APPLE__
|
||||
|
||||
#include "heim_ipc.h"
|
||||
#include "heim_ipc_asyncServer.h"
|
||||
|
||||
#include <dispatch/dispatch.h>
|
||||
#include <mach/mach.h>
|
||||
|
||||
static dispatch_once_t jobqinited = 0;
|
||||
static dispatch_queue_t jobq = NULL;
|
||||
static dispatch_queue_t syncq;
|
||||
|
||||
struct mach_ctx {
|
||||
mach_port_t server;
|
||||
char *name;
|
||||
};
|
||||
|
||||
static int
|
||||
mach_release(void *ctx);
|
||||
|
||||
static int
|
||||
mach_init(const char *service, void **ctx)
|
||||
{
|
||||
struct mach_ctx *ipc;
|
||||
mach_port_t sport;
|
||||
int ret;
|
||||
|
||||
dispatch_once(&jobqinited, ^{
|
||||
jobq = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
|
||||
syncq = dispatch_queue_create("heim-ipc-syncq", NULL);
|
||||
});
|
||||
|
||||
ret = bootstrap_look_up(bootstrap_port, service, &sport);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ipc = malloc(sizeof(*ipc));
|
||||
if (ipc == NULL) {
|
||||
mach_port_destroy(mach_task_self(), sport);
|
||||
return ENOMEM;
|
||||
}
|
||||
|
||||
ipc->server = sport;
|
||||
ipc->name = strdup(service);
|
||||
if (ipc->name == NULL) {
|
||||
mach_release(ipc);
|
||||
return ENOMEM;
|
||||
}
|
||||
|
||||
*ctx = ipc;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
mach_ipc(void *ctx,
|
||||
const heim_idata *request, heim_idata *response,
|
||||
heim_icred *cred)
|
||||
{
|
||||
struct mach_ctx *ipc = ctx;
|
||||
heim_ipc_message_inband_t requestin;
|
||||
mach_msg_type_number_t requestin_length = 0;
|
||||
heim_ipc_message_outband_t requestout = NULL;
|
||||
mach_msg_type_number_t requestout_length = 0;
|
||||
heim_ipc_message_inband_t replyin;
|
||||
mach_msg_type_number_t replyin_length;
|
||||
heim_ipc_message_outband_t replyout;
|
||||
mach_msg_type_number_t replyout_length;
|
||||
int ret, errorcode, retries = 0;
|
||||
|
||||
memcpy(requestin, request->data, request->length);
|
||||
requestin_length = request->length;
|
||||
|
||||
while (retries < 2) {
|
||||
__block mach_port_t sport;
|
||||
|
||||
dispatch_sync(syncq, ^{ sport = ipc->server; });
|
||||
|
||||
ret = mheim_ipc_call(sport,
|
||||
requestin, requestin_length,
|
||||
requestout, requestout_length,
|
||||
&errorcode,
|
||||
replyin, &replyin_length,
|
||||
&replyout, &replyout_length);
|
||||
if (ret == MACH_SEND_INVALID_DEST) {
|
||||
mach_port_t nport;
|
||||
/* race other threads to get a new port */
|
||||
ret = bootstrap_look_up(bootstrap_port, ipc->name, &nport);
|
||||
if (ret)
|
||||
return ret;
|
||||
dispatch_sync(syncq, ^{
|
||||
/* check if we lost the race to lookup the port */
|
||||
if (sport != ipc->server) {
|
||||
mach_port_deallocate(mach_task_self(), nport);
|
||||
} else {
|
||||
mach_port_deallocate(mach_task_self(), ipc->server);
|
||||
ipc->server = nport;
|
||||
}
|
||||
});
|
||||
retries++;
|
||||
} else if (ret) {
|
||||
return ret;
|
||||
} else
|
||||
break;
|
||||
}
|
||||
if (retries >= 2)
|
||||
return EINVAL;
|
||||
|
||||
if (errorcode) {
|
||||
if (replyout_length)
|
||||
vm_deallocate (mach_task_self (), (vm_address_t) replyout,
|
||||
replyout_length);
|
||||
return errorcode;
|
||||
}
|
||||
|
||||
if (replyout_length) {
|
||||
response->data = malloc(replyout_length);
|
||||
if (response->data == NULL) {
|
||||
vm_deallocate (mach_task_self (), (vm_address_t) replyout,
|
||||
replyout_length);
|
||||
return ENOMEM;
|
||||
}
|
||||
memcpy(response->data, replyout, replyout_length);
|
||||
response->length = replyout_length;
|
||||
vm_deallocate (mach_task_self (), (vm_address_t) replyout,
|
||||
replyout_length);
|
||||
} else {
|
||||
response->data = malloc(replyin_length);
|
||||
if (response->data == NULL)
|
||||
return ENOMEM;
|
||||
memcpy(response->data, replyin, replyin_length);
|
||||
response->length = replyin_length;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct async_client {
|
||||
mach_port_t mp;
|
||||
dispatch_source_t source;
|
||||
dispatch_queue_t queue;
|
||||
void (*func)(void *, int, heim_idata *, heim_icred);
|
||||
void *userctx;
|
||||
};
|
||||
|
||||
kern_return_t
|
||||
mheim_ado_acall_reply(mach_port_t server_port,
|
||||
audit_token_t client_creds,
|
||||
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)
|
||||
{
|
||||
struct async_client *c = dispatch_get_context(dispatch_get_current_queue());
|
||||
heim_idata response;
|
||||
|
||||
if (returnvalue) {
|
||||
response.data = NULL;
|
||||
response.length = 0;
|
||||
} else if (replyoutCnt) {
|
||||
response.data = replyout;
|
||||
response.length = replyoutCnt;
|
||||
} else {
|
||||
response.data = replyin;
|
||||
response.length = replyinCnt;
|
||||
}
|
||||
|
||||
(*c->func)(c->userctx, returnvalue, &response, NULL);
|
||||
|
||||
if (replyoutCnt)
|
||||
vm_deallocate (mach_task_self (), (vm_address_t) replyout, replyoutCnt);
|
||||
|
||||
dispatch_source_cancel(c->source);
|
||||
|
||||
return 0;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
mach_async(void *ctx, const heim_idata *request, void *userctx,
|
||||
void (*func)(void *, int, heim_idata *, heim_icred))
|
||||
{
|
||||
struct mach_ctx *ipc = ctx;
|
||||
heim_ipc_message_inband_t requestin;
|
||||
mach_msg_type_number_t requestin_length = 0;
|
||||
heim_ipc_message_outband_t requestout = NULL;
|
||||
mach_msg_type_number_t requestout_length = 0;
|
||||
int ret, retries = 0;
|
||||
kern_return_t kr;
|
||||
struct async_client *c;
|
||||
|
||||
/* first create the service that will catch the reply from the server */
|
||||
/* XXX these object should be cached and reused */
|
||||
|
||||
c = malloc(sizeof(*c));
|
||||
if (c == NULL)
|
||||
return ENOMEM;
|
||||
|
||||
kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &c->mp);
|
||||
if (kr != KERN_SUCCESS)
|
||||
return EINVAL;
|
||||
|
||||
c->queue = dispatch_queue_create("heim-ipc-async-client", NULL);
|
||||
c->source = dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV, c->mp, 0, c->queue);
|
||||
dispatch_set_context(c->queue, c);
|
||||
|
||||
dispatch_source_set_event_handler(c->source, ^{
|
||||
dispatch_mig_server(c->source,
|
||||
sizeof(union __RequestUnion__mheim_ado_mheim_aipc_subsystem),
|
||||
mheim_aipc_server);
|
||||
});
|
||||
|
||||
dispatch_source_set_cancel_handler(c->source, ^{
|
||||
struct async_client *s = dispatch_get_context(dispatch_get_current_queue());
|
||||
mach_port_mod_refs(mach_task_self(), c->mp,
|
||||
MACH_PORT_RIGHT_RECEIVE, -1);
|
||||
dispatch_release(c->queue);
|
||||
dispatch_release(c->source);
|
||||
free(c);
|
||||
});
|
||||
|
||||
c->func = func;
|
||||
c->userctx = userctx;
|
||||
|
||||
dispatch_resume(c->source);
|
||||
|
||||
/* ok, send the message */
|
||||
|
||||
memcpy(requestin, request->data, request->length);
|
||||
requestin_length = request->length;
|
||||
|
||||
while (retries < 2) {
|
||||
__block mach_port_t sport;
|
||||
|
||||
dispatch_sync(syncq, ^{ sport = ipc->server; });
|
||||
|
||||
ret = mheim_ipc_call_request(sport, c->mp,
|
||||
requestin, requestin_length,
|
||||
requestout, requestout_length);
|
||||
if (ret == MACH_SEND_INVALID_DEST) {
|
||||
ret = bootstrap_look_up(bootstrap_port, ipc->name, &sport);
|
||||
if (ret) {
|
||||
dispatch_source_cancel(c->source);
|
||||
return ret;
|
||||
}
|
||||
mach_port_deallocate(mach_task_self(), ipc->server);
|
||||
ipc->server = sport;
|
||||
retries++;
|
||||
} else if (ret) {
|
||||
dispatch_source_cancel(c->source);
|
||||
return ret;
|
||||
} else
|
||||
break;
|
||||
}
|
||||
if (retries >= 2) {
|
||||
dispatch_source_cancel(c->source);
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
mach_release(void *ctx)
|
||||
{
|
||||
struct mach_ctx *ipc = ctx;
|
||||
if (ipc->server != MACH_PORT_NULL)
|
||||
mach_port_deallocate(mach_task_self(), ipc->server);
|
||||
free(ipc->name);
|
||||
free(ipc);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
struct path_ctx {
|
||||
char *path;
|
||||
int fd;
|
||||
};
|
||||
|
||||
static int common_release(void *);
|
||||
|
||||
static int
|
||||
connect_unix(struct path_ctx *s)
|
||||
{
|
||||
struct sockaddr_un addr;
|
||||
|
||||
addr.sun_family = AF_UNIX;
|
||||
strlcpy(addr.sun_path, s->path, sizeof(addr.sun_path));
|
||||
|
||||
s->fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (s->fd < 0)
|
||||
return errno;
|
||||
rk_cloexec(s->fd);
|
||||
|
||||
if (connect(s->fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
|
||||
close(s->fd);
|
||||
return errno;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
common_path_init(const char *service,
|
||||
const char *file,
|
||||
void **ctx)
|
||||
{
|
||||
struct path_ctx *s;
|
||||
const char *path;
|
||||
char *apath = NULL;
|
||||
int ret;
|
||||
int *fd;
|
||||
|
||||
s = malloc(sizeof(*s));
|
||||
if (s == NULL)
|
||||
return ENOMEM;
|
||||
s->fd = -1;
|
||||
|
||||
if (service[0] == '\0')
|
||||
asprintf(&s->path, "/var/run/.heim_%s", file);
|
||||
else
|
||||
s->path = strdup(service);
|
||||
|
||||
*ctx = s;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
unix_socket_init(const char *service,
|
||||
void **ctx)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = common_path_init(service, "socket", ctx);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = connect_unix(*ctx);
|
||||
if (ret)
|
||||
common_release(*ctx);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
unix_socket_ipc(void *ctx,
|
||||
const heim_idata *request, heim_idata *response,
|
||||
heim_icred *cred)
|
||||
{
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
int
|
||||
common_release(void *ctx)
|
||||
{
|
||||
struct path_ctx *s = ctx;
|
||||
if (s->fd >= 0)
|
||||
close(s->fd);
|
||||
free(s->path);
|
||||
free(s);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef HAVE_DOOR
|
||||
|
||||
static int
|
||||
door_init(const char *service,
|
||||
void **ctx)
|
||||
{
|
||||
ret = common_path_init(context, service, name, "door", ctx);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = connect_door(*ctx);
|
||||
if (ret)
|
||||
common_release(*ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
door_ipc(void *ctx,
|
||||
const heim_idata *request, heim_idata *response,
|
||||
heim_icred *cred)
|
||||
{
|
||||
door_arg_t arg;
|
||||
int ret;
|
||||
|
||||
arg.data_ptr = request->data;
|
||||
arg.data_size = request->length;
|
||||
arg.desc_ptr = NULL;
|
||||
arg.desc_num = 0;
|
||||
arg.rbuf = NULL;
|
||||
arg.rsize = 0;
|
||||
|
||||
ret = door_call(fd, &arg);
|
||||
close(fd);
|
||||
if (ret != 0)
|
||||
return errno;
|
||||
|
||||
response->data = malloc(arg.rsize);
|
||||
if (response->data == NULL) {
|
||||
munmap(arg.rbuf, arg.rsize);
|
||||
return ENOMEM;
|
||||
}
|
||||
memcpy(response->data, arg.rbuf, arg.rsize);
|
||||
response->length = arg.rsize;
|
||||
munmap(arg.rbuf, arg.rsize);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
struct hipc_ops {
|
||||
const char *prefix;
|
||||
int (*init)(const char *, void **);
|
||||
int (*release)(void *);
|
||||
int (*ipc)(void *,const heim_idata *, heim_idata *, heim_icred *);
|
||||
int (*async)(void *, const heim_idata *, void *,
|
||||
void (*)(void *, int, heim_idata *, heim_icred));
|
||||
};
|
||||
|
||||
struct hipc_ops ipcs[] = {
|
||||
#ifdef __APPLE__
|
||||
{ "MACH", mach_init, mach_release, mach_ipc, mach_async },
|
||||
#endif
|
||||
#ifdef HAVE_DOOR
|
||||
{ "DOOR", door_init, common_release, door_ipc }
|
||||
#endif
|
||||
{ "UNIX", unix_socket_init, common_release, unix_socket_ipc }
|
||||
};
|
||||
|
||||
struct heim_ipc {
|
||||
struct hipc_ops *ops;
|
||||
void *ctx;
|
||||
};
|
||||
|
||||
|
||||
int
|
||||
heim_ipc_init_context(const char *name, heim_ipc *ctx)
|
||||
{
|
||||
unsigned int i;
|
||||
int ret, any = 0;
|
||||
|
||||
for(i = 0; i < sizeof(ipcs)/sizeof(ipcs[0]); i++) {
|
||||
size_t prefix_len = strlen(ipcs[i].prefix);
|
||||
heim_ipc c;
|
||||
if(strncmp(ipcs[i].prefix, name, prefix_len) == 0
|
||||
&& name[prefix_len] == ':') {
|
||||
} else if (strncmp("ANY:", name, 4) == 0) {
|
||||
prefix_len = 3;
|
||||
any = 1;
|
||||
} else
|
||||
continue;
|
||||
|
||||
c = calloc(1, sizeof(*c));
|
||||
if (c == NULL)
|
||||
return ENOMEM;
|
||||
|
||||
c->ops = &ipcs[i];
|
||||
|
||||
ret = (c->ops->init)(name + prefix_len + 1, &c->ctx);
|
||||
if (ret) {
|
||||
free(c);
|
||||
if (any)
|
||||
continue;
|
||||
return ret;
|
||||
}
|
||||
|
||||
*ctx = c;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ENOENT;
|
||||
}
|
||||
|
||||
void
|
||||
heim_ipc_free_context(heim_ipc ctx)
|
||||
{
|
||||
(ctx->ops->release)(ctx->ctx);
|
||||
free(ctx);
|
||||
}
|
||||
|
||||
int
|
||||
heim_ipc_call(heim_ipc ctx, const heim_idata *send, heim_idata *recv,
|
||||
heim_icred *cred)
|
||||
{
|
||||
if (cred)
|
||||
*cred = NULL;
|
||||
return (ctx->ops->ipc)(ctx->ctx, send, recv, cred);
|
||||
}
|
||||
|
||||
int
|
||||
heim_ipc_async(heim_ipc ctx, const heim_idata *send, void *userctx,
|
||||
void (*func)(void *, int, heim_idata *, heim_icred))
|
||||
{
|
||||
if (ctx->ops->async == NULL)
|
||||
return EAGAIN; /* XXX */
|
||||
return (ctx->ops->async)(ctx->ctx, send, userctx, func);
|
||||
}
|
Reference in New Issue
Block a user