2015-09-11 14:36:03 +00:00
|
|
|
/*
|
|
|
|
* Copyright 2014-2015 iXsystems, Inc.
|
|
|
|
* All rights reserved
|
|
|
|
*
|
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
|
|
* modification, are permitted providing 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.
|
|
|
|
*
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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 <sys/types.h>
|
|
|
|
#include <sys/errno.h>
|
|
|
|
#include <sys/sbuf.h>
|
2017-02-15 12:15:02 +00:00
|
|
|
#include <libkern/OSAtomic.h>
|
2015-09-11 14:36:03 +00:00
|
|
|
#include <assert.h>
|
|
|
|
#include <syslog.h>
|
2015-09-12 22:47:17 +00:00
|
|
|
#include <pthread.h>
|
2015-09-11 14:36:03 +00:00
|
|
|
#include "xpc/xpc.h"
|
2017-04-02 18:24:19 +00:00
|
|
|
#include "xpc/launchd.h"
|
2015-09-11 14:36:03 +00:00
|
|
|
#include "xpc_internal.h"
|
|
|
|
|
2015-09-13 20:39:42 +00:00
|
|
|
#define RECV_BUFFER_SIZE 65536
|
2017-01-27 23:20:42 +00:00
|
|
|
#define sbuf_new_auto() sbuf_new(NULL, NULL, 0, SBUF_AUTOEXTEND)
|
2015-09-13 20:39:42 +00:00
|
|
|
|
2015-09-11 14:36:03 +00:00
|
|
|
static void xpc_copy_description_level(xpc_object_t obj, struct sbuf *sbuf,
|
|
|
|
int level);
|
|
|
|
|
2015-10-04 11:03:29 +00:00
|
|
|
extern struct xpc_transport unix_transport __attribute__((weak));
|
|
|
|
extern struct xpc_transport mach_transport __attribute__((weak));
|
|
|
|
static struct xpc_transport *selected_transport = NULL;
|
2015-09-11 14:36:03 +00:00
|
|
|
|
|
|
|
struct xpc_transport *
|
|
|
|
xpc_get_transport()
|
|
|
|
{
|
2015-10-04 11:03:29 +00:00
|
|
|
if (!selected_transport) {
|
|
|
|
char *env = getenv("XPC_TRANSPORT");
|
|
|
|
if (env) {
|
2017-01-27 23:20:42 +00:00
|
|
|
#if 0
|
2015-10-04 11:03:29 +00:00
|
|
|
if (!strcmp(env, "unix"))
|
|
|
|
selected_transport = &unix_transport;
|
2017-01-27 23:20:42 +00:00
|
|
|
#endif
|
2015-10-04 11:03:29 +00:00
|
|
|
|
|
|
|
if (!strcmp(env, "mach"))
|
|
|
|
selected_transport = &mach_transport;
|
|
|
|
} else {
|
|
|
|
#ifdef MACH
|
|
|
|
selected_transport = &mach_transport;
|
|
|
|
#else
|
|
|
|
selected_transport = &unix_transport;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-11 14:36:03 +00:00
|
|
|
return (selected_transport);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
xpc_dictionary_destroy(struct xpc_object *dict)
|
|
|
|
{
|
|
|
|
struct xpc_dict_head *head;
|
|
|
|
struct xpc_dict_pair *p, *ptmp;
|
|
|
|
|
|
|
|
head = &dict->xo_dict;
|
|
|
|
|
|
|
|
TAILQ_FOREACH_SAFE(p, head, xo_link, ptmp) {
|
|
|
|
TAILQ_REMOVE(head, p, xo_link);
|
|
|
|
xpc_object_destroy(p->value);
|
|
|
|
free(p);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
xpc_array_destroy(struct xpc_object *dict)
|
|
|
|
{
|
|
|
|
struct xpc_object *p, *ptmp;
|
|
|
|
struct xpc_array_head *head;
|
|
|
|
|
|
|
|
head = &dict->xo_array;
|
|
|
|
|
|
|
|
TAILQ_FOREACH_SAFE(p, head, xo_link, ptmp) {
|
|
|
|
TAILQ_REMOVE(head, p, xo_link);
|
|
|
|
xpc_object_destroy(p);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-15 12:17:09 +00:00
|
|
|
static void
|
|
|
|
xpc_activity_destroy(struct xpc_object *activity)
|
|
|
|
{
|
|
|
|
xpc_release(activity->xo_activity.criteria);
|
|
|
|
}
|
|
|
|
|
2015-09-11 14:36:03 +00:00
|
|
|
static int
|
2015-09-13 20:39:42 +00:00
|
|
|
xpc_pack(struct xpc_object *xo, void **buf, uint64_t id, size_t *size)
|
2015-09-11 14:36:03 +00:00
|
|
|
{
|
2015-09-13 20:39:42 +00:00
|
|
|
struct xpc_frame_header *header;
|
2015-09-11 14:36:03 +00:00
|
|
|
mpack_writer_t writer;
|
2015-09-13 20:39:42 +00:00
|
|
|
char *packed, *ret;
|
|
|
|
size_t packed_size;
|
2015-09-11 14:36:03 +00:00
|
|
|
|
2015-09-13 20:39:42 +00:00
|
|
|
mpack_writer_init_growable(&writer, &packed, &packed_size);
|
2015-09-11 14:36:03 +00:00
|
|
|
xpc2mpack(&writer, xo);
|
|
|
|
|
|
|
|
if (mpack_writer_destroy(&writer) != mpack_ok)
|
|
|
|
return (-1);
|
|
|
|
|
2015-09-13 20:39:42 +00:00
|
|
|
ret = malloc(packed_size + sizeof(*header));
|
|
|
|
memset(ret, 0, packed_size + sizeof(*header));
|
|
|
|
|
|
|
|
header = (struct xpc_frame_header *)ret;
|
|
|
|
header->length = packed_size;
|
|
|
|
header->id = id;
|
|
|
|
header->version = XPC_PROTOCOL_VERSION;
|
|
|
|
|
|
|
|
memcpy(ret + sizeof(*header), packed, packed_size);
|
|
|
|
*buf = ret;
|
|
|
|
*size = packed_size + sizeof(*header);
|
|
|
|
|
|
|
|
free(packed);
|
2015-09-11 14:36:03 +00:00
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct xpc_object *
|
|
|
|
xpc_unpack(void *buf, size_t size)
|
|
|
|
{
|
|
|
|
mpack_tree_t tree;
|
|
|
|
struct xpc_object *xo;
|
|
|
|
|
|
|
|
mpack_tree_init(&tree, (const char *)buf, size);
|
|
|
|
if (mpack_tree_error(&tree) != mpack_ok) {
|
|
|
|
debugf("unpack failed: %d", mpack_tree_error(&tree))
|
|
|
|
return (NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
xo = mpack2xpc(mpack_tree_root(&tree));
|
|
|
|
return (xo);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
xpc_object_destroy(struct xpc_object *xo)
|
|
|
|
{
|
|
|
|
if (xo->xo_xpc_type == _XPC_TYPE_DICTIONARY)
|
|
|
|
xpc_dictionary_destroy(xo);
|
|
|
|
|
|
|
|
if (xo->xo_xpc_type == _XPC_TYPE_ARRAY)
|
|
|
|
xpc_array_destroy(xo);
|
|
|
|
|
2017-02-15 12:17:09 +00:00
|
|
|
if (xo->xo_xpc_type == _XPC_TYPE_ACTIVITY)
|
|
|
|
xpc_activity_destroy(xo);
|
|
|
|
|
2015-09-11 14:36:03 +00:00
|
|
|
free(xo);
|
|
|
|
}
|
|
|
|
|
|
|
|
xpc_object_t
|
|
|
|
xpc_retain(xpc_object_t obj)
|
|
|
|
{
|
|
|
|
struct xpc_object *xo;
|
|
|
|
|
|
|
|
xo = obj;
|
2017-02-15 12:15:02 +00:00
|
|
|
OSAtomicIncrement32(&xo->xo_refcnt);
|
2015-09-11 14:36:03 +00:00
|
|
|
return (obj);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
xpc_release(xpc_object_t obj)
|
|
|
|
{
|
|
|
|
struct xpc_object *xo;
|
|
|
|
|
|
|
|
xo = obj;
|
2017-02-15 12:15:02 +00:00
|
|
|
if (OSAtomicDecrement32(&xo->xo_refcnt) > 1)
|
2015-09-11 14:36:03 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
xpc_object_destroy(xo);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const char *xpc_errors[] = {
|
|
|
|
"No Error Found",
|
|
|
|
"No Memory",
|
|
|
|
"Invalid Argument",
|
|
|
|
"No Such Process"
|
|
|
|
};
|
|
|
|
|
|
|
|
const char *
|
|
|
|
xpc_strerror(int error)
|
|
|
|
{
|
|
|
|
|
|
|
|
if (error > EXMAX || error < 0)
|
|
|
|
return "BAD ERROR";
|
|
|
|
return (xpc_errors[error]);
|
|
|
|
}
|
|
|
|
|
|
|
|
char *
|
|
|
|
xpc_copy_description(xpc_object_t obj)
|
|
|
|
{
|
|
|
|
char *result;
|
|
|
|
struct sbuf *sbuf;
|
|
|
|
|
|
|
|
sbuf = sbuf_new_auto();
|
|
|
|
xpc_copy_description_level(obj, sbuf, 0);
|
|
|
|
sbuf_finish(sbuf);
|
|
|
|
result = strdup(sbuf_data(sbuf));
|
|
|
|
sbuf_delete(sbuf);
|
|
|
|
|
|
|
|
return (result);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
xpc_copy_description_level(xpc_object_t obj, struct sbuf *sbuf, int level)
|
|
|
|
{
|
|
|
|
struct xpc_object *xo = obj;
|
2017-01-27 23:20:42 +00:00
|
|
|
#ifndef __APPLE__
|
2015-09-11 14:36:03 +00:00
|
|
|
struct uuid *id;
|
2017-01-27 23:20:42 +00:00
|
|
|
#else
|
|
|
|
uuid_t id;
|
|
|
|
#endif
|
2015-09-11 14:36:03 +00:00
|
|
|
char *uuid_str;
|
|
|
|
uint32_t uuid_status;
|
|
|
|
|
|
|
|
if (obj == NULL) {
|
|
|
|
sbuf_printf(sbuf, "<null value>\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
sbuf_printf(sbuf, "(%s) ", _xpc_get_type_name(obj));
|
|
|
|
|
|
|
|
switch (xo->xo_xpc_type) {
|
|
|
|
case _XPC_TYPE_DICTIONARY:
|
|
|
|
sbuf_printf(sbuf, "\n");
|
|
|
|
xpc_dictionary_apply(xo, ^(const char *k, xpc_object_t v) {
|
|
|
|
sbuf_printf(sbuf, "%*s\"%s\": ", level * 4, " ", k);
|
|
|
|
xpc_copy_description_level(v, sbuf, level + 1);
|
|
|
|
return ((bool)true);
|
|
|
|
});
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _XPC_TYPE_ARRAY:
|
|
|
|
sbuf_printf(sbuf, "\n");
|
|
|
|
xpc_array_apply(xo, ^(size_t idx, xpc_object_t v) {
|
|
|
|
sbuf_printf(sbuf, "%*s%ld: ", level * 4, " ", idx);
|
|
|
|
xpc_copy_description_level(v, sbuf, level + 1);
|
|
|
|
return ((bool)true);
|
|
|
|
});
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _XPC_TYPE_BOOL:
|
|
|
|
sbuf_printf(sbuf, "%s\n",
|
|
|
|
xpc_bool_get_value(obj) ? "true" : "false");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _XPC_TYPE_STRING:
|
|
|
|
sbuf_printf(sbuf, "\"%s\"\n",
|
|
|
|
xpc_string_get_string_ptr(obj));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _XPC_TYPE_INT64:
|
|
|
|
sbuf_printf(sbuf, "%ld\n",
|
|
|
|
xpc_int64_get_value(obj));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _XPC_TYPE_UINT64:
|
|
|
|
sbuf_printf(sbuf, "%lx\n",
|
|
|
|
xpc_uint64_get_value(obj));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _XPC_TYPE_DATE:
|
|
|
|
sbuf_printf(sbuf, "%lu\n",
|
|
|
|
xpc_date_get_value(obj));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _XPC_TYPE_UUID:
|
2017-01-27 23:20:42 +00:00
|
|
|
#ifdef __APPLE__
|
|
|
|
memcpy(id, xpc_uuid_get_bytes(obj), sizeof(id));
|
|
|
|
uuid_str = (char*) __builtin_alloca(40);
|
|
|
|
uuid_unparse(*id, uuid_str);
|
|
|
|
#else
|
2015-09-11 14:36:03 +00:00
|
|
|
id = (struct uuid *)xpc_uuid_get_bytes(obj);
|
|
|
|
uuid_to_string(id, &uuid_str, &uuid_status);
|
2017-01-27 23:20:42 +00:00
|
|
|
#endif
|
2015-09-11 14:36:03 +00:00
|
|
|
sbuf_printf(sbuf, "%s\n", uuid_str);
|
|
|
|
free(uuid_str);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _XPC_TYPE_ENDPOINT:
|
|
|
|
sbuf_printf(sbuf, "<%ld>\n", xo->xo_int);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case _XPC_TYPE_NULL:
|
|
|
|
sbuf_printf(sbuf, "<null>\n");
|
|
|
|
break;
|
2017-02-15 12:17:09 +00:00
|
|
|
|
|
|
|
case _XPC_TYPE_ACTIVITY:
|
|
|
|
sbuf_printf(sbuf, "<activity>\n");
|
|
|
|
break;
|
2015-09-11 14:36:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef MACH
|
|
|
|
struct _launch_data {
|
|
|
|
uint64_t type;
|
|
|
|
union {
|
|
|
|
struct {
|
|
|
|
union {
|
|
|
|
launch_data_t *_array;
|
|
|
|
char *string;
|
|
|
|
void *opaque;
|
|
|
|
int64_t __junk;
|
|
|
|
};
|
|
|
|
union {
|
|
|
|
uint64_t _array_cnt;
|
|
|
|
uint64_t string_len;
|
|
|
|
uint64_t opaque_size;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
int64_t fd;
|
|
|
|
uint64_t mp;
|
|
|
|
uint64_t err;
|
|
|
|
int64_t number;
|
|
|
|
uint64_t boolean; /* We'd use 'bool' but this struct needs to be used under Rosetta, and sizeof(bool) is different between PowerPC and Intel */
|
|
|
|
double float_num;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
static uint8_t ld_to_xpc_type[] = {
|
|
|
|
_XPC_TYPE_INVALID,
|
|
|
|
_XPC_TYPE_DICTIONARY,
|
|
|
|
_XPC_TYPE_ARRAY,
|
|
|
|
_XPC_TYPE_FD,
|
|
|
|
_XPC_TYPE_UINT64,
|
|
|
|
_XPC_TYPE_DOUBLE,
|
|
|
|
_XPC_TYPE_BOOL,
|
|
|
|
_XPC_TYPE_STRING,
|
|
|
|
_XPC_TYPE_DATA,
|
|
|
|
_XPC_TYPE_ERROR,
|
|
|
|
_XPC_TYPE_ENDPOINT
|
|
|
|
};
|
|
|
|
|
|
|
|
xpc_object_t
|
|
|
|
ld2xpc(launch_data_t ld)
|
|
|
|
{
|
|
|
|
struct xpc_object *xo;
|
|
|
|
xpc_u val;
|
|
|
|
|
|
|
|
|
|
|
|
if (ld->type > LAUNCH_DATA_MACHPORT)
|
|
|
|
return (NULL);
|
|
|
|
if (ld->type == LAUNCH_DATA_STRING || ld->type == LAUNCH_DATA_OPAQUE) {
|
|
|
|
val.str = malloc(ld->string_len);
|
|
|
|
memcpy(__DECONST(void *, val.str), ld->string, ld->string_len);
|
|
|
|
xo = _xpc_prim_create(ld_to_xpc_type[ld->type], val, ld->string_len);
|
|
|
|
} else if (ld->type == LAUNCH_DATA_BOOL) {
|
|
|
|
val.b = (bool)ld->boolean;
|
|
|
|
xo = _xpc_prim_create(ld_to_xpc_type[ld->type], val, 0);
|
|
|
|
} else if (ld->type == LAUNCH_DATA_ARRAY) {
|
|
|
|
xo = xpc_array_create(NULL, 0);
|
|
|
|
for (uint64_t i = 0; i < ld->_array_cnt; i++)
|
|
|
|
xpc_array_append_value(xo, ld2xpc(ld->_array[i]));
|
|
|
|
} else {
|
|
|
|
val.ui = ld->mp;
|
|
|
|
xo = _xpc_prim_create(ld_to_xpc_type[ld->type], val, ld->string_len);
|
|
|
|
}
|
|
|
|
return (xo);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
xpc_object_t
|
|
|
|
xpc_copy_entitlement_for_token(const char *key __unused, audit_token_t *token __unused)
|
|
|
|
{
|
|
|
|
xpc_u val;
|
|
|
|
|
|
|
|
val.b = true;
|
|
|
|
return (_xpc_prim_create(_XPC_TYPE_BOOL, val,0));
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
int
|
|
|
|
xpc_pipe_send(xpc_object_t xobj, uint64_t id, xpc_port_t local, xpc_port_t remote)
|
|
|
|
{
|
|
|
|
struct xpc_transport *transport = xpc_get_transport();
|
2015-09-13 20:39:42 +00:00
|
|
|
void *buf;
|
|
|
|
size_t size;
|
2015-09-11 14:36:03 +00:00
|
|
|
|
|
|
|
assert(xpc_get_type(xobj) == &_xpc_type_dictionary);
|
|
|
|
|
2015-09-13 20:39:42 +00:00
|
|
|
if (xpc_pack(xobj, &buf, id, &size) != 0) {
|
2015-09-11 14:36:03 +00:00
|
|
|
debugf("pack failed");
|
|
|
|
return (-1);
|
|
|
|
}
|
|
|
|
|
2015-09-13 20:39:42 +00:00
|
|
|
if (transport->xt_send(local, remote, buf, size, NULL, 0) != 0) {
|
2015-09-11 14:36:03 +00:00
|
|
|
debugf("transport send function failed: %s", strerror(errno));
|
|
|
|
return (-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
xpc_pipe_receive(xpc_port_t local, xpc_port_t *remote, xpc_object_t *result,
|
|
|
|
uint64_t *id, struct xpc_credentials *creds)
|
|
|
|
{
|
|
|
|
struct xpc_transport *transport = xpc_get_transport();
|
|
|
|
struct xpc_resource *resources;
|
2015-09-13 20:39:42 +00:00
|
|
|
struct xpc_frame_header *header;
|
|
|
|
void *buffer;
|
2015-09-11 14:36:03 +00:00
|
|
|
size_t nresources;
|
2015-09-12 22:47:17 +00:00
|
|
|
int ret;
|
2015-09-11 14:36:03 +00:00
|
|
|
|
2015-09-13 20:39:42 +00:00
|
|
|
buffer = malloc(RECV_BUFFER_SIZE);
|
2015-09-11 14:36:03 +00:00
|
|
|
|
2015-09-13 20:39:42 +00:00
|
|
|
ret = transport->xt_recv(local, remote, buffer, RECV_BUFFER_SIZE,
|
|
|
|
&resources, &nresources, creds);
|
2015-09-12 22:47:17 +00:00
|
|
|
if (ret < 0) {
|
2015-09-11 14:36:03 +00:00
|
|
|
debugf("transport receive function failed: %s", strerror(errno));
|
|
|
|
return (-1);
|
|
|
|
}
|
|
|
|
|
2015-09-12 22:47:17 +00:00
|
|
|
if (ret == 0) {
|
|
|
|
debugf("remote side closed connection, port=%s", transport->xt_port_to_string(local));
|
|
|
|
return (ret);
|
|
|
|
}
|
|
|
|
|
2015-09-13 20:39:42 +00:00
|
|
|
header = (struct xpc_frame_header *)buffer;
|
|
|
|
if (header->length > (ret - sizeof(*header))) {
|
|
|
|
debugf("invalid message length");
|
|
|
|
return (-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (header->version != XPC_PROTOCOL_VERSION) {
|
|
|
|
debugf("invalid protocol version")
|
|
|
|
return (-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
*id = header->id;
|
2015-10-04 11:03:29 +00:00
|
|
|
|
|
|
|
debugf("length=%ld", header->length);
|
|
|
|
|
2015-09-13 20:39:42 +00:00
|
|
|
*result = xpc_unpack(buffer + sizeof(*header), header->length);
|
2015-09-11 14:36:03 +00:00
|
|
|
|
|
|
|
if (*result == NULL)
|
|
|
|
return (-1);
|
|
|
|
|
2015-09-13 20:39:42 +00:00
|
|
|
free(buffer);
|
2015-09-12 22:47:17 +00:00
|
|
|
return (ret);
|
2015-09-11 14:36:03 +00:00
|
|
|
}
|