diff options
Diffstat (limited to 'ext/sockets/conversions.c')
-rw-r--r-- | ext/sockets/conversions.c | 1528 |
1 files changed, 1528 insertions, 0 deletions
diff --git a/ext/sockets/conversions.c b/ext/sockets/conversions.c new file mode 100644 index 0000000000..e3ff271f84 --- /dev/null +++ b/ext/sockets/conversions.c @@ -0,0 +1,1528 @@ +#include "sockaddr_conv.h" +#include "conversions.h" +#include "sendrecvmsg.h" /* for ancillary registry */ +#ifdef PHP_WIN32 +# include "windows_common.h" +#endif + +#include <Zend/zend_llist.h> +#include <ext/standard/php_smart_str.h> + +#ifndef PHP_WIN32 +# include <sys/types.h> +# include <sys/socket.h> +# include <arpa/inet.h> +# include <netinet/in.h> +# include <sys/un.h> +# include <sys/ioctl.h> +# include <net/if.h> +#else +# include <win32/php_stdint.h> +#endif + +#include <limits.h> +#include <stdarg.h> +#include <stddef.h> + +#ifdef PHP_WIN32 +typedef unsigned short sa_family_t; +# define msghdr _WSAMSG +/* +struct _WSAMSG { + LPSOCKADDR name; //void *msg_name + INT namelen; //socklen_t msg_namelen + LPWSABUF lpBuffers; //struct iovec *msg_iov + ULONG dwBufferCount; //size_t msg_iovlen + WSABUF Control; //void *msg_control, size_t msg_controllen + DWORD dwFlags; //int msg_flags +} +struct __WSABUF { + u_long len; //size_t iov_len (2nd member) + char FAR *buf; //void *iov_base (1st member) +} +struct _WSACMSGHDR { + UINT cmsg_len; //socklen_t cmsg_len + INT cmsg_level; //int cmsg_level + INT cmsg_type; //int cmsg_type; + followed by UCHAR cmsg_data[] +} +*/ +# define msg_name name +# define msg_namelen namelen +# define msg_iov lpBuffers +# define msg_iovlen dwBufferCount +# define msg_control Control.buf +# define msg_controllen Control.len +# define msg_flags dwFlags +# define iov_base buf +# define iov_len len + +# define cmsghdr _WSACMSGHDR +# ifdef CMSG_DATA +# undef CMSG_DATA +# endif +# define CMSG_DATA WSA_CMSG_DATA +#endif + +#define MAX_USER_BUFF_SIZE ((size_t)(100*1024*1024)) +#define DEFAULT_BUFF_SIZE 8192 + +struct _ser_context { + HashTable params; /* stores pointers; has to be first */ + struct err_s err; + zend_llist keys, + /* common part to res_context ends here */ + allocations; + php_socket *sock; +}; +struct _res_context { + HashTable params; /* stores pointers; has to be first */ + struct err_s err; + zend_llist keys; +}; + +typedef struct { + /* zval info */ + const char *name; + unsigned name_size; + int required; + + /* structure info */ + size_t field_offset; /* 0 to pass full structure, e.g. when more than + one field is to be changed; in that case the + callbacks need to know the name of the fields */ + + /* callbacks */ + from_zval_write_field *from_zval; + to_zval_read_field *to_zval; +} field_descriptor; + +#define KEY_FILL_SOCKADDR "fill_sockaddr" +#define KEY_RECVMSG_RET "recvmsg_ret" +#define KEY_CMSG_LEN "cmsg_len" + +const struct key_value empty_key_value_list[] = {{0}}; + +/* PARAMETERS */ +static int param_get_bool(void *ctx, const char *key, int def) +{ + int **elem; + if (zend_hash_find(ctx, key, strlen(key) + 1, (void**)&elem) == SUCCESS) { + return **elem; + } else { + return def; + } +} + +/* MEMORY */ +static inline void *accounted_emalloc(size_t alloc_size, ser_context *ctx) +{ + void *ret = emalloc(alloc_size); + zend_llist_add_element(&ctx->allocations, &ret); + return ret; +} +static inline void *accounted_ecalloc(size_t nmemb, size_t alloc_size, ser_context *ctx) +{ + void *ret = ecalloc(nmemb, alloc_size); + zend_llist_add_element(&ctx->allocations, &ret); + return ret; +} +static inline void *accounted_safe_ecalloc(size_t nmemb, size_t alloc_size, size_t offset, ser_context *ctx) +{ + void *ret = safe_emalloc(nmemb, alloc_size, offset); + memset(ret, '\0', nmemb * alloc_size + offset); + zend_llist_add_element(&ctx->allocations, &ret); + return ret; +} + +/* ERRORS */ +static void do_from_to_zval_err(struct err_s *err, + zend_llist *keys, + const char *what_conv, + const char *fmt, + va_list ap) +{ + smart_str path = {0}; + const char **node; + char *user_msg; + int user_msg_size; + zend_llist_position pos; + + if (err->has_error) { + return; + } + + for (node = zend_llist_get_first_ex(keys, &pos); + node != NULL; + node = zend_llist_get_next_ex(keys, &pos)) { + smart_str_appends(&path, *node); + smart_str_appends(&path, " > "); + } + + if (path.len > 3) { + path.len -= 3; + } + smart_str_0(&path); + + user_msg_size = vspprintf(&user_msg, 0, fmt, ap); + + err->has_error = 1; + err->level = E_WARNING; + spprintf(&err->msg, 0, "error converting %s data (path: %s): %.*s", + what_conv, + path.c && path.c != '\0' ? path.c : "unavailable", + user_msg_size, user_msg); + err->should_free = 1; + + efree(user_msg); + smart_str_free_ex(&path, 0); +} +ZEND_ATTRIBUTE_FORMAT(printf, 2 ,3) +static void do_from_zval_err(ser_context *ctx, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + do_from_to_zval_err(&ctx->err, &ctx->keys, "user", fmt, ap); + va_end(ap); +} +ZEND_ATTRIBUTE_FORMAT(printf, 2 ,3) +static void do_to_zval_err(res_context *ctx, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + do_from_to_zval_err(&ctx->err, &ctx->keys, "native", fmt, ap); + va_end(ap); +} + +void err_msg_dispose(struct err_s *err TSRMLS_DC) +{ + if (err->msg != NULL) { + php_error_docref0(NULL TSRMLS_CC, err->level, "%s", err->msg); + if (err->should_free) { + efree(err->msg); + } + } +} +void allocations_dispose(zend_llist **allocations) +{ + zend_llist_destroy(*allocations); + efree(*allocations); + *allocations = NULL; +} + +static unsigned from_array_iterate(const zval *arr, + void (*func)(zval **elem, unsigned i, void **args, ser_context *ctx), + void **args, + ser_context *ctx) +{ + HashPosition pos; + unsigned i; + zval **elem; + char buf[sizeof("element #4294967295")]; + char *bufp = buf; + + for (zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(arr), &pos), i = 1; + !ctx->err.has_error + && zend_hash_get_current_data_ex(Z_ARRVAL_P(arr), (void **)&elem, &pos) == SUCCESS; + zend_hash_move_forward_ex(Z_ARRVAL_P(arr), &pos), i++) { + if (snprintf(buf, sizeof(buf), "element #%u", i) >= sizeof(buf)) { + memcpy(buf, "element", sizeof("element")); + } + zend_llist_add_element(&ctx->keys, &bufp); + + func(elem, i, args, ctx); + + zend_llist_remove_tail(&ctx->keys); + } + + return i -1; +} + +/* Generic Aggregated conversions */ +static void from_zval_write_aggregation(const zval *container, + char *structure, + const field_descriptor *descriptors, + ser_context *ctx) +{ + const field_descriptor *descr; + zval **elem; + + if (Z_TYPE_P(container) != IS_ARRAY) { + do_from_zval_err(ctx, "%s", "expected an array here"); + } + + for (descr = descriptors; descr->name != NULL && !ctx->err.has_error; descr++) { + if (zend_hash_find(Z_ARRVAL_P(container), + descr->name, descr->name_size, (void**)&elem) == SUCCESS) { + + if (descr->from_zval == NULL) { + do_from_zval_err(ctx, "No information on how to convert value " + "of key '%s'", descr->name); + break; + } + + zend_llist_add_element(&ctx->keys, (void*)&descr->name); + descr->from_zval(*elem, ((char*)structure) + descr->field_offset, ctx); + zend_llist_remove_tail(&ctx->keys); + + } else if (descr->required) { + do_from_zval_err(ctx, "The key '%s' is required", descr->name); + break; + } + } +} +static void to_zval_read_aggregation(const char *structure, + zval *zarr, /* initialized array */ + const field_descriptor *descriptors, + res_context *ctx) +{ + const field_descriptor *descr; + + assert(Z_TYPE_P(zarr) == IS_ARRAY); + assert(Z_ARRVAL_P(zarr) != NULL); + + for (descr = descriptors; descr->name != NULL && !ctx->err.has_error; descr++) { + zval *new_zv; + + if (descr->to_zval == NULL) { + do_to_zval_err(ctx, "No information on how to convert native " + "field into value for key '%s'", descr->name); + break; + } + + ALLOC_INIT_ZVAL(new_zv); + add_assoc_zval_ex(zarr, descr->name, descr->name_size, new_zv); + + zend_llist_add_element(&ctx->keys, (void*)&descr->name); + descr->to_zval(structure + descr->field_offset, new_zv, ctx); + zend_llist_remove_tail(&ctx->keys); + } +} + +/* CONVERSIONS for integers */ +static long from_zval_integer_common(const zval *arr_value, ser_context *ctx) +{ + long ret = 0; + zval lzval = zval_used_for_init; + + if (Z_TYPE_P(arr_value) != IS_LONG) { + ZVAL_COPY_VALUE(&lzval, arr_value); + zval_copy_ctor(&lzval); + arr_value = &lzval; + } + + switch (Z_TYPE_P(arr_value)) { + case IS_LONG: +long_case: + ret = Z_LVAL_P(arr_value); + break; + + /* if not long we're operating on lzval */ + case IS_DOUBLE: +double_case: + convert_to_long(&lzval); + goto long_case; + + case IS_OBJECT: + case IS_STRING: { + long lval; + double dval; + + convert_to_string(&lzval); + + switch (is_numeric_string(Z_STRVAL(lzval), Z_STRLEN(lzval), &lval, &dval, 0)) { + case IS_DOUBLE: + zval_dtor(&lzval); + Z_TYPE(lzval) = IS_DOUBLE; + Z_DVAL(lzval) = dval; + goto double_case; + + case IS_LONG: + zval_dtor(&lzval); + Z_TYPE(lzval) = IS_LONG; + Z_LVAL(lzval) = lval; + goto long_case; + } + + /* if we get here, we don't have a numeric string */ + do_from_zval_err(ctx, "expected an integer, but got a non numeric " + "string (possibly from a converted object): '%s'", Z_STRVAL_P(arr_value)); + break; + } + + default: + do_from_zval_err(ctx, "%s", "expected an integer, either of a PHP " + "integer type or of a convertible type"); + break; + } + + zval_dtor(&lzval); + + return ret; +} +void from_zval_write_int(const zval *arr_value, char *field, ser_context *ctx) +{ + long lval; + int ival; + + lval = from_zval_integer_common(arr_value, ctx); + if (ctx->err.has_error) { + return; + } + + if (lval > INT_MAX || lval < INT_MIN) { + do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " + "for a native int"); + return; + } + + ival = (int)lval; + memcpy(field, &ival, sizeof(ival)); +} +static void from_zval_write_uint32(const zval *arr_value, char *field, ser_context *ctx) +{ + long lval; + uint32_t ival; + + lval = from_zval_integer_common(arr_value, ctx); + if (ctx->err.has_error) { + return; + } + + if (sizeof(long) > sizeof(uint32_t) && (lval < 0 || lval > 0xFFFFFFFF)) { + do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " + "for an unsigned 32-bit integer"); + return; + } + + ival = (uint32_t)lval; + memcpy(field, &ival, sizeof(ival)); +} +static void from_zval_write_net_uint16(const zval *arr_value, char *field, ser_context *ctx) +{ + long lval; + uint16_t ival; + + lval = from_zval_integer_common(arr_value, ctx); + if (ctx->err.has_error) { + return; + } + + if (lval < 0 || lval > 0xFFFF) { + do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " + "for an unsigned 16-bit integer"); + return; + } + + ival = htons((uint16_t)lval); + memcpy(field, &ival, sizeof(ival)); +} +static void from_zval_write_sa_family(const zval *arr_value, char *field, ser_context *ctx) +{ + long lval; + sa_family_t ival; + + lval = from_zval_integer_common(arr_value, ctx); + if (ctx->err.has_error) { + return; + } + + if (lval < 0 || lval > (sa_family_t)-1) { /* sa_family_t is unsigned */ + do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " + "for a sa_family_t value"); + return; + } + + ival = (sa_family_t)lval; + memcpy(field, &ival, sizeof(ival)); +} +static void from_zval_write_pid_t(const zval *arr_value, char *field, ser_context *ctx) +{ + long lval; + pid_t ival; + + lval = from_zval_integer_common(arr_value, ctx); + if (ctx->err.has_error) { + return; + } + + if (lval < 0 || (pid_t)lval != lval) { /* pid_t is signed */ + do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " + "for a pid_t value"); + return; + } + + ival = (pid_t)lval; + memcpy(field, &ival, sizeof(ival)); +} +static void from_zval_write_uid_t(const zval *arr_value, char *field, ser_context *ctx) +{ + long lval; + uid_t ival; + + lval = from_zval_integer_common(arr_value, ctx); + if (ctx->err.has_error) { + return; + } + + /* uid_t can be signed or unsigned (generally unsigned) */ + if ((uid_t)-1 > (uid_t)0) { + if (sizeof(long) > sizeof(uid_t) && (lval < 0 || (uid_t)lval != lval)) { + do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " + "for a uid_t value"); + return; + } + } else { + if (sizeof(long) > sizeof(uid_t) && (uid_t)lval != lval) { + do_from_zval_err(ctx, "%s", "given PHP integer is out of bounds " + "for a uid_t value"); + return; + } + } + + ival = (uid_t)lval; + memcpy(field, &ival, sizeof(ival)); +} + +void to_zval_read_int(const char *data, zval *zv, res_context *ctx) +{ + int ival; + memcpy(&ival, data, sizeof(ival)); + + ZVAL_LONG(zv, (long)ival); +} +static void to_zval_read_unsigned(const char *data, zval *zv, res_context *ctx) +{ + unsigned ival; + memcpy(&ival, data, sizeof(ival)); + + ZVAL_LONG(zv, (long)ival); +} +static void to_zval_read_net_uint16(const char *data, zval *zv, res_context *ctx) +{ + uint16_t ival; + memcpy(&ival, data, sizeof(ival)); + + ZVAL_LONG(zv, (long)ntohs(ival)); +} +static void to_zval_read_uint32(const char *data, zval *zv, res_context *ctx) +{ + uint32_t ival; + memcpy(&ival, data, sizeof(ival)); + + ZVAL_LONG(zv, (long)ival); +} +static void to_zval_read_sa_family(const char *data, zval *zv, res_context *ctx) +{ + sa_family_t ival; + memcpy(&ival, data, sizeof(ival)); + + ZVAL_LONG(zv, (long)ival); +} +static void to_zval_read_pid_t(const char *data, zval *zv, res_context *ctx) +{ + pid_t ival; + memcpy(&ival, data, sizeof(ival)); + + ZVAL_LONG(zv, (long)ival); +} +static void to_zval_read_uid_t(const char *data, zval *zv, res_context *ctx) +{ + uid_t ival; + memcpy(&ival, data, sizeof(ival)); + + ZVAL_LONG(zv, (long)ival); +} + +/* CONVERSIONS for sockaddr */ +static void from_zval_write_sin_addr(const zval *zaddr_str, char *inaddr, ser_context *ctx) +{ + int res; + struct sockaddr_in saddr = {0}; + zval lzval = zval_used_for_init; + TSRMLS_FETCH(); + + if (Z_TYPE_P(zaddr_str) != IS_STRING) { + ZVAL_COPY_VALUE(&lzval, zaddr_str); + zval_copy_ctor(&lzval); + convert_to_string(&lzval); + zaddr_str = &lzval; + } + + res = php_set_inet_addr(&saddr, Z_STRVAL_P(zaddr_str), ctx->sock TSRMLS_CC); + if (res) { + memcpy(inaddr, &saddr.sin_addr, sizeof saddr.sin_addr); + } else { + /* error already emitted, but let's emit another more relevant */ + do_from_zval_err(ctx, "could not resolve address '%s' to get an AF_INET " + "address", Z_STRVAL_P(zaddr_str)); + } + + zval_dtor(&lzval); +} +static void to_zval_read_sin_addr(const char *data, zval *zv, res_context *ctx) +{ + const struct in_addr *addr = (const struct in_addr *)data; + socklen_t size = INET_ADDRSTRLEN; + + Z_TYPE_P(zv) = IS_STRING; + Z_STRVAL_P(zv) = ecalloc(1, size); + Z_STRLEN_P(zv) = 0; + + if (inet_ntop(AF_INET, addr, Z_STRVAL_P(zv), size) == NULL) { + do_to_zval_err(ctx, "could not convert IPv4 address to string " + "(errno %d)", errno); + return; + } + + Z_STRLEN_P(zv) = strlen(Z_STRVAL_P(zv)); +} +static const field_descriptor descriptors_sockaddr_in[] = { + {"family", sizeof("family"), 0, offsetof(struct sockaddr_in, sin_family), from_zval_write_sa_family, to_zval_read_sa_family}, + {"addr", sizeof("addr"), 0, offsetof(struct sockaddr_in, sin_addr), from_zval_write_sin_addr, to_zval_read_sin_addr}, + {"port", sizeof("port"), 0, offsetof(struct sockaddr_in, sin_port), from_zval_write_net_uint16, to_zval_read_net_uint16}, + {0} +}; +static void from_zval_write_sockaddr_in(const zval *container, char *sockaddr, ser_context *ctx) +{ + from_zval_write_aggregation(container, sockaddr, descriptors_sockaddr_in, ctx); +} +static void to_zval_read_sockaddr_in(const char *data, zval *zv, res_context *ctx) +{ + to_zval_read_aggregation(data, zv, descriptors_sockaddr_in, ctx); +} +static void from_zval_write_sin6_addr(const zval *zaddr_str, char *addr6, ser_context *ctx) +{ + int res; + struct sockaddr_in6 saddr6 = {0}; + zval lzval = zval_used_for_init; + TSRMLS_FETCH(); + + if (Z_TYPE_P(zaddr_str) != IS_STRING) { + ZVAL_COPY_VALUE(&lzval, zaddr_str); + zval_copy_ctor(&lzval); + convert_to_string(&lzval); + zaddr_str = &lzval; + } + + res = php_set_inet6_addr(&saddr6, + Z_STRVAL_P(zaddr_str), ctx->sock TSRMLS_CC); + if (res) { + memcpy(addr6, &saddr6.sin6_addr, sizeof saddr6.sin6_addr); + } else { + /* error already emitted, but let's emit another more relevant */ + do_from_zval_err(ctx, "could not resolve address '%s' to get an AF_INET6 " + "address", Z_STRVAL_P(zaddr_str)); + } + + zval_dtor(&lzval); +} +static void to_zval_read_sin6_addr(const char *data, zval *zv, res_context *ctx) +{ + const struct in6_addr *addr = (const struct in6_addr *)data; + socklen_t size = INET6_ADDRSTRLEN; + + Z_TYPE_P(zv) = IS_STRING; + Z_STRVAL_P(zv) = ecalloc(1, size); + Z_STRLEN_P(zv) = 0; + + if (inet_ntop(AF_INET6, addr, Z_STRVAL_P(zv), size) == NULL) { + do_to_zval_err(ctx, "could not convert IPv6 address to string " + "(errno %d)", errno); + return; + } + + Z_STRLEN_P(zv) = strlen(Z_STRVAL_P(zv)); +} +static const field_descriptor descriptors_sockaddr_in6[] = { + {"family", sizeof("family"), 0, offsetof(struct sockaddr_in6, sin6_family), from_zval_write_sa_family, to_zval_read_sa_family}, + {"addr", sizeof("addr"), 0, offsetof(struct sockaddr_in6, sin6_addr), from_zval_write_sin6_addr, to_zval_read_sin6_addr}, + {"port", sizeof("port"), 0, offsetof(struct sockaddr_in6, sin6_port), from_zval_write_net_uint16, to_zval_read_net_uint16}, + {"flowinfo", sizeof("flowinfo"), 0, offsetof(struct sockaddr_in6, sin6_flowinfo), from_zval_write_uint32, to_zval_read_uint32}, + {"scope_id", sizeof("scope_id"), 0, offsetof(struct sockaddr_in6, sin6_scope_id), from_zval_write_uint32, to_zval_read_uint32}, + {0} +}; +static void from_zval_write_sockaddr_in6(const zval *container, char *sockaddr6, ser_context *ctx) +{ + from_zval_write_aggregation(container, sockaddr6, descriptors_sockaddr_in6, ctx); +} +static void to_zval_read_sockaddr_in6(const char *data, zval *zv, res_context *ctx) +{ + to_zval_read_aggregation(data, zv, descriptors_sockaddr_in6, ctx); +} +static void from_zval_write_sun_path(const zval *path, char *sockaddr_un_c, ser_context *ctx) +{ + zval lzval = zval_used_for_init; + struct sockaddr_un *saddr = (struct sockaddr_un*)sockaddr_un_c; + + if (Z_TYPE_P(path) != IS_STRING) { + ZVAL_COPY_VALUE(&lzval, path); + zval_copy_ctor(&lzval); + convert_to_string(&lzval); + path = &lzval; + } + + if (Z_STRLEN_P(path) >= sizeof(saddr->sun_path)) { + do_from_zval_err(ctx, "the path is too long, the maximum permitted " + "length is %ld", sizeof(saddr->sun_path) - 1); + return; + } + + memcpy(&saddr->sun_path, Z_STRVAL_P(path), Z_STRLEN_P(path)); + saddr->sun_path[Z_STRLEN_P(path)] = '\0'; + + zval_dtor(&lzval); +} +static void to_zval_read_sun_path(const char *data, zval *zv, res_context *ctx) { + struct sockaddr_un *saddr = (struct sockaddr_un*)data; + char *nul_pos; + + nul_pos = memchr(&saddr->sun_path, '\0', sizeof(saddr->sun_path)); + if (nul_pos == NULL) { + do_to_zval_err(ctx, "could not find a NUL in the path"); + return; + } + + ZVAL_STRINGL(zv, saddr->sun_path, nul_pos - (char*)&saddr->sun_path, 1); +} +static const field_descriptor descriptors_sockaddr_un[] = { + {"family", sizeof("family"), 0, offsetof(struct sockaddr_un, sun_family), from_zval_write_sa_family, to_zval_read_sa_family}, + {"path", sizeof("path"), 0, 0, from_zval_write_sun_path, to_zval_read_sun_path}, + {0} +}; +static void from_zval_write_sockaddr_un(const zval *container, char *sockaddr, ser_context *ctx) +{ + from_zval_write_aggregation(container, sockaddr, descriptors_sockaddr_un, ctx); +} +static void to_zval_read_sockaddr_un(const char *data, zval *zv, res_context *ctx) +{ + to_zval_read_aggregation(data, zv, descriptors_sockaddr_un, ctx); +} +static void from_zval_write_sockaddr_aux(const zval *container, + struct sockaddr **sockaddr_ptr, + socklen_t *sockaddr_len, + ser_context *ctx) +{ + int family; + zval **elem; + int fill_sockaddr; + + if (Z_TYPE_P(container) != IS_ARRAY) { + do_from_zval_err(ctx, "%s", "expected an array here"); + return; + } + + fill_sockaddr = param_get_bool(ctx, KEY_FILL_SOCKADDR, 1); + + if (zend_hash_find(Z_ARRVAL_P(container), "family", sizeof("family"), (void**)&elem) == SUCCESS + && Z_TYPE_PP(elem) != IS_NULL) { + const char *node = "family"; + zend_llist_add_element(&ctx->keys, &node); + from_zval_write_int(*elem, (char*)&family, ctx); + zend_llist_remove_tail(&ctx->keys); + } else { + family = ctx->sock->type; + } + + switch (family) { + case AF_INET: + /* though not all OSes support sockaddr_in used in IPv6 sockets */ + if (ctx->sock->type != AF_INET && ctx->sock->type != AF_INET6) { + do_from_zval_err(ctx, "the specified family (number %d) is not " + "supported on this socket", family); + return; + } + *sockaddr_ptr = accounted_ecalloc(1, sizeof(struct sockaddr_in), ctx); + *sockaddr_len = sizeof(struct sockaddr_in); + if (fill_sockaddr) { + from_zval_write_sockaddr_in(container, (char*)*sockaddr_ptr, ctx); + (*sockaddr_ptr)->sa_family = AF_INET; + } + break; + + case AF_INET6: + if (ctx->sock->type != AF_INET6) { + do_from_zval_err(ctx, "the specified family (AF_INET6) is not " + "supported on this socket"); + return; + } + *sockaddr_ptr = accounted_ecalloc(1, sizeof(struct sockaddr_in6), ctx); + *sockaddr_len = sizeof(struct sockaddr_in6); + if (fill_sockaddr) { + from_zval_write_sockaddr_in6(container, (char*)*sockaddr_ptr, ctx); + (*sockaddr_ptr)->sa_family = AF_INET6; + } + break; + + case AF_UNIX: + if (ctx->sock->type != AF_UNIX) { + do_from_zval_err(ctx, "the specified family (AF_UNIX) is not " + "supported on this socket"); + return; + } + *sockaddr_ptr = accounted_ecalloc(1, sizeof(struct sockaddr_un), ctx); + *sockaddr_len = sizeof(struct sockaddr_un); + if (fill_sockaddr) { + from_zval_write_sockaddr_un(container, (char*)*sockaddr_ptr, ctx); + (*sockaddr_ptr)->sa_family = AF_UNIX; + } + break; + + default: + do_from_zval_err(ctx, "%s", "the only families currently supported are " + "AF_INET, AF_INET6 and AF_UNIX"); + break; + } +} +static void to_zval_read_sockaddr_aux(const char *sockaddr_c, zval *zv, res_context *ctx) +{ + const struct sockaddr *saddr = (struct sockaddr *)sockaddr_c; + + if (saddr->sa_family == 0) { + ZVAL_NULL(zv); + return; + } + + array_init(zv); + + switch (saddr->sa_family) { + case AF_INET: + to_zval_read_sockaddr_in(sockaddr_c, zv, ctx); + break; + + case AF_INET6: + to_zval_read_sockaddr_in6(sockaddr_c, zv, ctx); + break; + + case AF_UNIX: + to_zval_read_sockaddr_un(sockaddr_c, zv, ctx); + break; + + default: + do_to_zval_err(ctx, "cannot read struct sockaddr with family %d; " + "not supported", + (int)saddr->sa_family); + break; + } +} + +/* CONVERSIONS for cmsghdr */ +/* + * [ level => , type => , data => [],] + * struct cmsghdr { + * socklen_t cmsg_len; // data byte count, including header + * int cmsg_level; // originating protocol + * int cmsg_type; // protocol-specific type + * // followed by unsigned char cmsg_data[]; + * }; + */ +static void from_zval_write_control(const zval *arr, + void **control_buf, + zend_llist_element *alloc, + size_t *control_len, + size_t *offset, + ser_context *ctx) +{ + struct cmsghdr *cmsghdr; + int level, + type; + size_t data_len, + req_space, + space_left; + ancillary_reg_entry *entry; + + static const field_descriptor descriptor_level[] = { + {"level", sizeof("level"), 0, 0, from_zval_write_int, 0}, + {0} + }; + static const field_descriptor descriptor_type[] = { + {"type", sizeof("type"), 0, 0, from_zval_write_int, 0}, + {0} + }; + field_descriptor descriptor_data[] = { + {"data", sizeof("data"), 0, 0, 0, 0}, + {0} + }; + + from_zval_write_aggregation(arr, (char *)&level, descriptor_level, ctx); + if (ctx->err.has_error) { + return; + } + from_zval_write_aggregation(arr, (char *)&type, descriptor_type, ctx); + if (ctx->err.has_error) { + return; + } + + entry = get_ancillary_reg_entry(level, type); + if (entry == NULL) { + do_from_zval_err(ctx, "cmsghdr with level %d and type %d not supported", + level, type); + return; + } + + if (entry->calc_space) { + data_len = entry->calc_space(arr, ctx); + if (ctx->err.has_error) { + return; + } + } else { + data_len = entry->size; + } + req_space = CMSG_SPACE(data_len); + space_left = *control_len - *offset; + assert(*control_len >= *offset); + + if (space_left < req_space) { + *control_buf = safe_erealloc(*control_buf, 2, req_space, *control_len); + *control_len += 2 * req_space; + memset(*control_buf, '\0', *control_len - *offset); + memcpy(&alloc->data, *control_buf, sizeof *control_buf); + } + + cmsghdr = (struct cmsghdr*)(((char*)*control_buf) + *offset); + cmsghdr->cmsg_level = level; + cmsghdr->cmsg_type = type; + cmsghdr->cmsg_len = CMSG_LEN(data_len); + + descriptor_data[0].from_zval = entry->from_array; + from_zval_write_aggregation(arr, (char*)CMSG_DATA(cmsghdr), descriptor_data, ctx); + + *offset += req_space; +} +static void from_zval_write_control_array(const zval *arr, char *msghdr_c, ser_context *ctx) +{ + HashPosition pos; + char buf[sizeof("element #4294967295")]; + char *bufp = buf; + zval **elem; + uint32_t i; + int num_elems; + void *control_buf; + zend_llist_element *alloc; + size_t control_len, + cur_offset; + struct msghdr *msg = (struct msghdr*)msghdr_c; + + if (Z_TYPE_P(arr) != IS_ARRAY) { + do_from_zval_err(ctx, "%s", "expected an array here"); + return; + } + + num_elems = zend_hash_num_elements(Z_ARRVAL_P(arr)); + if (num_elems == 0) { + return; + } + + /* estimate each message at 20 bytes */ + control_buf = accounted_safe_ecalloc(num_elems, CMSG_SPACE(20), 0, ctx); + alloc = ctx->allocations.tail; + control_len = (size_t)num_elems * CMSG_SPACE(20); + cur_offset = 0; + + for (zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(arr), &pos), i = 0; + !ctx->err.has_error + && zend_hash_get_current_data_ex(Z_ARRVAL_P(arr), (void **)&elem, &pos) == SUCCESS; + zend_hash_move_forward_ex(Z_ARRVAL_P(arr), &pos)) { + + if (snprintf(buf, sizeof(buf), "element #%u", (unsigned)i++) >= sizeof(buf)) { + memcpy(buf, "element", sizeof("element")); + } + zend_llist_add_element(&ctx->keys, &bufp); + + from_zval_write_control(*elem, &control_buf, alloc, &control_len, + &cur_offset, ctx); + + zend_llist_remove_tail(&ctx->keys); + } + + msg->msg_control = control_buf; + msg->msg_controllen = cur_offset; /* not control_len, which may be larger */ +} +static void to_zval_read_cmsg_data(const char *cmsghdr_c, zval *zv, res_context *ctx) +{ + const struct cmsghdr *cmsg = (const struct cmsghdr *)cmsghdr_c; + ancillary_reg_entry *entry; + size_t len, + *len_p = &len; + + entry = get_ancillary_reg_entry(cmsg->cmsg_level, cmsg->cmsg_type); + if (entry == NULL) { + do_to_zval_err(ctx, "cmsghdr with level %d and type %d not supported", + cmsg->cmsg_level, cmsg->cmsg_type); + return; + } + if (CMSG_LEN(entry->size) > cmsg->cmsg_len) { + do_to_zval_err(ctx, "the cmsghdr structure is unexpectedly small; " + "expected a length of at least %ld, but got %ld", + (long)CMSG_LEN(entry->size), (long)cmsg->cmsg_len); + return; + } + + len = (size_t)cmsg->cmsg_len; /* use another var because type of cmsg_len varies */ + if (zend_hash_add(&ctx->params, KEY_CMSG_LEN, sizeof(KEY_CMSG_LEN), + &len_p, sizeof(len_p), NULL) == FAILURE) { + do_to_zval_err(ctx, "%s", "could not set parameter " KEY_CMSG_LEN); + return; + } + + entry->to_array((const char *)CMSG_DATA(cmsg), zv, ctx); + + zend_hash_del(&ctx->params, KEY_CMSG_LEN, sizeof(KEY_CMSG_LEN)); +} +static void to_zval_read_control(const char *cmsghdr_c, zval *zv, res_context *ctx) +{ + /* takes a cmsghdr, not a msghdr like from_zval_write_control */ + static const field_descriptor descriptors[] = { + {"level", sizeof("level"), 0, offsetof(struct cmsghdr, cmsg_level), 0, to_zval_read_int}, + {"type", sizeof("type"), 0, offsetof(struct cmsghdr, cmsg_type), 0, to_zval_read_int}, + {"data", sizeof("data"), 0, 0 /* cmsghdr passed */, 0, to_zval_read_cmsg_data}, + {0} + }; + + array_init_size(zv, 3); + to_zval_read_aggregation(cmsghdr_c, zv, descriptors, ctx); +} +static void to_zval_read_control_array(const char *msghdr_c, zval *zv, res_context *ctx) +{ + struct msghdr *msg = (struct msghdr *)msghdr_c; + struct cmsghdr *cmsg; + char buf[sizeof("element #4294967295")]; + char *bufp = buf; + uint32_t i = 1; + + /*if (msg->msg_flags & MSG_CTRUNC) { + php_error_docref0(NULL, E_WARNING, "The MSG_CTRUNC flag is present; will not " + "attempt to read control messages"); + ZVAL_FALSE(zv); + return; + }*/ + + array_init(zv); + + for (cmsg = CMSG_FIRSTHDR(msg); + cmsg != NULL && !ctx->err.has_error; + cmsg = CMSG_NXTHDR(msg, cmsg)) { + zval *elem; + + ALLOC_INIT_ZVAL(elem); + add_next_index_zval(zv, elem); + + if (snprintf(buf, sizeof(buf), "element #%u", (unsigned)i++) >= sizeof(buf)) { + memcpy(buf, "element", sizeof("element")); + } + zend_llist_add_element(&ctx->keys, &bufp); + + to_zval_read_control((const char *)cmsg, elem, ctx); + + zend_llist_remove_tail(&ctx->keys); + } +} + +/* CONVERSIONS for msghdr */ +static void from_zval_write_name(const zval *zname_arr, char *msghdr_c, ser_context *ctx) +{ + struct sockaddr *sockaddr; + socklen_t sockaddr_len; + struct msghdr *msghdr = (struct msghdr *)msghdr_c; + + from_zval_write_sockaddr_aux(zname_arr, &sockaddr, &sockaddr_len, ctx); + + msghdr->msg_name = sockaddr; + msghdr->msg_namelen = sockaddr_len; +} +static void to_zval_read_name(const char *sockaddr_p, zval *zv, res_context *ctx) +{ + void *name = (void*)*(void**)sockaddr_p; + if (name == NULL) { + ZVAL_NULL(zv); + } else { + to_zval_read_sockaddr_aux(name, zv, ctx); + } +} +static void from_zval_write_msghdr_buffer_size(const zval *elem, char *msghdr_c, ser_context *ctx) +{ + long lval; + struct msghdr *msghdr = (struct msghdr *)msghdr_c; + + lval = from_zval_integer_common(elem, ctx); + if (ctx->err.has_error) { + return; + } + + if (lval < 0 || lval > MAX_USER_BUFF_SIZE) { + do_from_zval_err(ctx, "the buffer size must be between 1 and %ld; " + "given %ld", (long)MAX_USER_BUFF_SIZE, lval); + return; + } + + msghdr->msg_iovlen = 1; + msghdr->msg_iov = accounted_emalloc(sizeof(*msghdr->msg_iov) * 1, ctx); + msghdr->msg_iov[0].iov_base = accounted_emalloc((size_t)lval, ctx); + msghdr->msg_iov[0].iov_len = (size_t)lval; +} +static void from_zval_write_iov_array_aux(zval **elem, unsigned i, void **args, ser_context *ctx) +{ + struct msghdr *msg = args[0]; + size_t len; + + zval_add_ref(elem); + convert_to_string_ex(elem); + + len = Z_STRLEN_PP(elem); + msg->msg_iov[i - 1].iov_base = accounted_emalloc(len, ctx); + msg->msg_iov[i - 1].iov_len = len; + memcpy(msg->msg_iov[i - 1].iov_base, Z_STRVAL_PP(elem), len); + + zval_ptr_dtor(elem); +} +static void from_zval_write_iov_array(const zval *arr, char *msghdr_c, ser_context *ctx) +{ + int num_elem; + struct msghdr *msg = (struct msghdr*)msghdr_c; + + if (Z_TYPE_P(arr) != IS_ARRAY) { + do_from_zval_err(ctx, "%s", "expected an array here"); + return; + } + + num_elem = zend_hash_num_elements(Z_ARRVAL_P(arr)); + if (num_elem == 0) { + return; + } + + msg->msg_iov = accounted_safe_ecalloc(num_elem, sizeof *msg->msg_iov, 0, ctx); + msg->msg_iovlen = (size_t)num_elem; + + from_array_iterate(arr, from_zval_write_iov_array_aux, (void**)&msg, ctx); +} +static void from_zval_write_controllen(const zval *elem, char *msghdr_c, ser_context *ctx) +{ + struct msghdr *msghdr = (struct msghdr *)msghdr_c; + uint32_t len; + + /* controllen should be an unsigned with at least 32-bit. Let's assume + * this least common denominator + */ + from_zval_write_uint32(elem, (char*)&len, ctx); + if (!ctx->err.has_error && len == 0) { + do_from_zval_err(ctx, "controllen cannot be 0"); + return; + } + msghdr->msg_control = accounted_emalloc(len, ctx); + msghdr->msg_controllen = len; +} +void from_zval_write_msghdr_send(const zval *container, char *msghdr_c, ser_context *ctx) +{ + static const field_descriptor descriptors[] = { + {"name", sizeof("name"), 0, 0, from_zval_write_name, 0}, + {"iov", sizeof("iov"), 0, 0, from_zval_write_iov_array, 0}, + {"control", sizeof("control"), 0, 0, from_zval_write_control_array, 0}, + {0} + }; + + from_zval_write_aggregation(container, msghdr_c, descriptors, ctx); +} +void from_zval_write_msghdr_recv(const zval *container, char *msghdr_c, ser_context *ctx) +{ + /* zval to struct msghdr, version for recvmsg(). It differs from the version + * for sendmsg() in that it: + * - has a buffer_size instead of an iov array; + * - has no control element; has a controllen element instead + * struct msghdr { + * void *msg_name; + * socklen_t msg_namelen; + * struct iovec *msg_iov; + * size_t msg_iovlen; + * void *msg_control; + * size_t msg_controllen; //can also be socklen_t + * int msg_flags; + * }; + */ + static const field_descriptor descriptors[] = { + {"name", sizeof("name"), 0, 0, from_zval_write_name, 0}, + {"buffer_size", sizeof("buffer_size"), 0, 0, from_zval_write_msghdr_buffer_size, 0}, + {"controllen", sizeof("controllen"), 1, 0, from_zval_write_controllen, 0}, + {0} + }; + struct msghdr *msghdr = (struct msghdr *)msghdr_c; + const int falsev = 0, + *falsevp = &falsev; + + if (zend_hash_add(&ctx->params, KEY_FILL_SOCKADDR, sizeof(KEY_FILL_SOCKADDR), + (void*)&falsevp, sizeof(falsevp), NULL) == FAILURE) { + do_from_zval_err(ctx, "could not add fill_sockaddr; this is a bug"); + return; + } + + from_zval_write_aggregation(container, msghdr_c, descriptors, ctx); + + zend_hash_del(&ctx->params, KEY_FILL_SOCKADDR, sizeof(KEY_FILL_SOCKADDR)); + if (ctx->err.has_error) { + return; + } + + if (msghdr->msg_iovlen == 0) { + msghdr->msg_iovlen = 1; + msghdr->msg_iov = accounted_emalloc(sizeof(*msghdr->msg_iov) * 1, ctx); + msghdr->msg_iov[0].iov_base = accounted_emalloc((size_t)DEFAULT_BUFF_SIZE, ctx); + msghdr->msg_iov[0].iov_len = (size_t)DEFAULT_BUFF_SIZE; + } +} + +static void to_zval_read_iov(const char *msghdr_c, zval *zv, res_context *ctx) +{ + const struct msghdr *msghdr = (const struct msghdr *)msghdr_c; + size_t iovlen = msghdr->msg_iovlen; + ssize_t **recvmsg_ret, + bytes_left; + uint i; + + if (iovlen > UINT_MAX) { + do_to_zval_err(ctx, "unexpectedly large value for iov_len: %lu", + (unsigned long)iovlen); + } + array_init_size(zv, (uint)iovlen); + + if (zend_hash_find(&ctx->params, KEY_RECVMSG_RET, sizeof(KEY_RECVMSG_RET), + (void**)&recvmsg_ret) == FAILURE) { + do_to_zval_err(ctx, "recvmsg_ret not found in params. This is a bug"); + return; + } + bytes_left = **recvmsg_ret; + + for (i = 0; bytes_left > 0 && i < (uint)iovlen; i++) { + zval *elem; + size_t len = MIN(msghdr->msg_iov[i].iov_len, (size_t)bytes_left); + char *buf = safe_emalloc(1, len, 1); + + MAKE_STD_ZVAL(elem); + memcpy(buf, msghdr->msg_iov[i].iov_base, len); + buf[len] = '\0'; + + ZVAL_STRINGL(elem, buf, len, 0); + add_next_index_zval(zv, elem); + bytes_left -= len; + } +} +void to_zval_read_msghdr(const char *msghdr_c, zval *zv, res_context *ctx) +{ + static const field_descriptor descriptors[] = { + {"name", sizeof("name"), 0, offsetof(struct msghdr, msg_name), 0, to_zval_read_name}, + {"control", sizeof("control"), 0, 0, 0, to_zval_read_control_array}, + {"iov", sizeof("iov"), 0, 0, 0, to_zval_read_iov}, + {"flags", sizeof("flags"), 0, offsetof(struct msghdr, msg_flags), 0, to_zval_read_int}, + {0} + }; + + array_init_size(zv, 4); + + to_zval_read_aggregation(msghdr_c, zv, descriptors, ctx); +} + +/* CONVERSIONS for if_index */ +static void from_zval_write_ifindex(const zval *zv, char *uinteger, ser_context *ctx) +{ + unsigned ret; + zval lzval = zval_used_for_init; + + if (Z_TYPE_P(zv) == IS_LONG) { + if (Z_LVAL_P(zv) < 0 || Z_LVAL_P(zv) > UINT_MAX) { /* allow 0 (unspecified interface) */ + do_from_zval_err(ctx, "the interface index cannot be negative or " + "larger than %u; given %ld", UINT_MAX, Z_LVAL_P(zv)); + } else { + ret = (unsigned)Z_LVAL_P(zv); + } + } else { + if (Z_TYPE_P(zv) != IS_STRING) { + ZVAL_COPY_VALUE(&lzval, zv); + zval_copy_ctor(&lzval); + convert_to_string(&lzval); + zv = &lzval; + } + +#if HAVE_IF_NAMETOINDEX + ret = if_nametoindex(Z_STRVAL_P(zv)); + if (ret == 0) { + do_from_zval_err(ctx, "no interface with name \"%s\" could be " + "found", Z_STRVAL_P(zv)); + } +#elif defined(SIOCGIFINDEX) + { + struct ifreq ifr; + if (strlcpy(ifr.ifr_name, Z_STRVAL_P(zv), sizeof(ifr.ifr_name)) + >= sizeof(ifr.ifr_name)) { + do_from_zval_err(ctx, "the interface name \"%s\" is too large ", + Z_STRVAL_P(zv)); + } else if (ioctl(ctx->sock->bsd_socket, SIOCGIFINDEX, &ifr) < 0) { + if (errno == ENODEV) { + do_from_zval_err(ctx, "no interface with name \"%s\" could be " + "found", Z_STRVAL_P(zv)); + } else { + do_from_zval_err(ctx, "error fetching interface index for " + "interface with name \"%s\" (errno %d)", + Z_STRVAL_P(zv), errno); + } + } else { + ret = (unsigned)ifr.ifr_ifindex; + } + } +#else + do_from_zval_err(ctx, + "this platform does not support looking up an interface by " + "name, an integer interface index must be supplied instead"); +#endif + } + + if (!ctx->err.has_error) { + memcpy(uinteger, &ret, sizeof(ret)); + } + + zval_dtor(&lzval); +} + +/* CONVERSIONS for struct in6_pktinfo */ +#ifdef IPV6_PKTINFO +static const field_descriptor descriptors_in6_pktinfo[] = { + {"addr", sizeof("addr"), 1, offsetof(struct in6_pktinfo, ipi6_addr), from_zval_write_sin6_addr, to_zval_read_sin6_addr}, + {"ifindex", sizeof("ifindex"), 1, offsetof(struct in6_pktinfo, ipi6_ifindex), from_zval_write_ifindex, to_zval_read_unsigned}, + {0} +}; +void from_zval_write_in6_pktinfo(const zval *container, char *in6_pktinfo_c, ser_context *ctx) +{ + from_zval_write_aggregation(container, in6_pktinfo_c, descriptors_in6_pktinfo, ctx); +} +void to_zval_read_in6_pktinfo(const char *data, zval *zv, res_context *ctx) +{ + array_init_size(zv, 2); + + to_zval_read_aggregation(data, zv, descriptors_in6_pktinfo, ctx); +} +#endif + +/* CONVERSIONS for struct ucred */ +#ifdef SO_PASSCRED +static const field_descriptor descriptors_ucred[] = { + {"pid", sizeof("pid"), 1, offsetof(struct ucred, pid), from_zval_write_pid_t, to_zval_read_pid_t}, + {"uid", sizeof("uid"), 1, offsetof(struct ucred, uid), from_zval_write_uid_t, to_zval_read_uid_t}, + /* assume the type gid_t is the same as uid_t: */ + {"gid", sizeof("gid"), 1, offsetof(struct ucred, gid), from_zval_write_uid_t, to_zval_read_uid_t}, + {0} +}; +void from_zval_write_ucred(const zval *container, char *ucred_c, ser_context *ctx) +{ + from_zval_write_aggregation(container, ucred_c, descriptors_ucred, ctx); +} +void to_zval_read_ucred(const char *data, zval *zv, res_context *ctx) +{ + array_init_size(zv, 3); + + to_zval_read_aggregation(data, zv, descriptors_ucred, ctx); +} +#endif + +/* CONVERSIONS for SCM_RIGHTS */ +#ifdef SCM_RIGHTS +size_t calculate_scm_rights_space(const zval *arr, ser_context *ctx) +{ + int num_elems; + + if (Z_TYPE_P(arr) != IS_ARRAY) { + do_from_zval_err(ctx, "%s", "expected an array here"); + return (size_t)-1; + } + + num_elems = zend_hash_num_elements(Z_ARRVAL_P(arr)); + if (num_elems == 0) { + do_from_zval_err(ctx, "%s", "expected at least one element in this array"); + return (size_t)-1; + } + + return zend_hash_num_elements(Z_ARRVAL_P(arr)) * sizeof(int); +} +static void from_zval_write_fd_array_aux(zval **elem, unsigned i, void **args, ser_context *ctx) +{ + int *iarr = args[0]; + TSRMLS_FETCH(); + + if (Z_TYPE_PP(elem) == IS_RESOURCE) { + php_stream *stream; + php_socket *sock; + + ZEND_FETCH_RESOURCE_NO_RETURN(sock, php_socket *, elem, -1, + NULL, php_sockets_le_socket()); + if (sock) { + iarr[i] = sock->bsd_socket; + return; + } + + ZEND_FETCH_RESOURCE2_NO_RETURN(stream, php_stream *, elem, -1, + NULL, php_file_le_stream(), php_file_le_pstream()); + if (stream == NULL) { + do_from_zval_err(ctx, "resource is not a stream or a socket"); + return; + } + + if (php_stream_cast(stream, PHP_STREAM_AS_FD, (void **)&iarr[i], + REPORT_ERRORS) == FAILURE) { + do_from_zval_err(ctx, "cast stream to file descriptor failed"); + return; + } + } else { + do_from_zval_err(ctx, "expected a resource variable"); + } +} +void from_zval_write_fd_array(const zval *arr, char *int_arr, ser_context *ctx) +{ + if (Z_TYPE_P(arr) != IS_ARRAY) { + do_from_zval_err(ctx, "%s", "expected an array here"); + return; + } + + from_array_iterate(arr, &from_zval_write_fd_array_aux, (void**)&int_arr, ctx); +} +void to_zval_read_fd_array(const char *data, zval *zv, res_context *ctx) +{ + size_t **cmsg_len; + int num_elems, + i; + struct cmsghdr *dummy_cmsg = 0; + size_t data_offset; + TSRMLS_FETCH(); + + data_offset = (unsigned char *)CMSG_DATA(dummy_cmsg) + - (unsigned char *)dummy_cmsg; + + if (zend_hash_find(&ctx->params, KEY_CMSG_LEN, sizeof(KEY_CMSG_LEN), + (void **)&cmsg_len) == FAILURE) { + do_to_zval_err(ctx, "could not get value of parameter " KEY_CMSG_LEN); + return; + } + + if (**cmsg_len < data_offset) { + do_to_zval_err(ctx, "length of cmsg is smaller than its data member " + "offset (%ld vs %ld)", (long)**cmsg_len, (long)data_offset); + return; + } + num_elems = (**cmsg_len - data_offset) / sizeof(int); + + array_init_size(zv, num_elems); + + for (i = 0; i < num_elems; i++) { + zval *elem; + int fd; + struct stat statbuf; + + MAKE_STD_ZVAL(elem); + + fd = *((int *)data + i); + + /* determine whether we have a socket */ + if (fstat(fd, &statbuf) == -1) { + do_to_zval_err(ctx, "error creating resource for received file " + "descriptor %d: fstat() call failed with errno %d", fd, errno); + efree(elem); + return; + } + if (S_ISSOCK(statbuf.st_mode)) { + php_socket *sock = socket_import_file_descriptor(fd TSRMLS_CC); + zend_register_resource(elem, sock, php_sockets_le_socket() TSRMLS_CC); + } else { + php_stream *stream = php_stream_fopen_from_fd(fd, "rw", NULL); + php_stream_to_zval(stream, elem); + } + + add_next_index_zval(zv, elem); + } +} +#endif + +/* ENTRY POINT for conversions */ +static void free_from_zval_allocation(void *alloc_ptr_ptr) +{ + efree(*(void**)alloc_ptr_ptr); +} +void *from_zval_run_conversions(const zval *container, + php_socket *sock, + from_zval_write_field *writer, + size_t struct_size, + const char *top_name, + zend_llist **allocations /* out */, + struct err_s *err /* in/out */) +{ + ser_context ctx = {{0}}; + char *structure = NULL; + + *allocations = NULL; + + if (err->has_error) { + return NULL; + } + + zend_hash_init(&ctx.params, 8, NULL, NULL, 0); + zend_llist_init(&ctx.keys, sizeof(const char *), NULL, 0); + zend_llist_init(&ctx.allocations, sizeof(void *), &free_from_zval_allocation, 0); + ctx.sock = sock; + + structure = ecalloc(1, struct_size); + + zend_llist_add_element(&ctx.keys, &top_name); + zend_llist_add_element(&ctx.allocations, &structure); + + /* main call */ + writer(container, structure, &ctx); + + if (ctx.err.has_error) { + zend_llist_destroy(&ctx.allocations); /* deallocates structure as well */ + structure = NULL; + *err = ctx.err; + } else { + *allocations = emalloc(sizeof **allocations); + **allocations = ctx.allocations; + } + + zend_llist_destroy(&ctx.keys); + zend_hash_destroy(&ctx.params); + + return structure; +} +zval *to_zval_run_conversions(const char *structure, + to_zval_read_field *reader, + const char *top_name, + const struct key_value *key_value_pairs, + struct err_s *err) +{ + res_context ctx = {{0}, {0}}; + const struct key_value *kv; + zval *zv = NULL; + + if (err->has_error) { + return NULL; + } + + ALLOC_INIT_ZVAL(zv); + + zend_llist_init(&ctx.keys, sizeof(const char *), NULL, 0); + zend_llist_add_element(&ctx.keys, &top_name); + + zend_hash_init(&ctx.params, 8, NULL, NULL, 0); + for (kv = key_value_pairs; kv->key != NULL; kv++) { + zend_hash_update(&ctx.params, kv->key, kv->key_size, + (void*)&kv->value, sizeof(kv->value), NULL); + } + + /* main call */ + reader(structure, zv, &ctx); + + if (ctx.err.has_error) { + zval_ptr_dtor(&zv); + zv = NULL; + *err = ctx.err; + } + + zend_llist_destroy(&ctx.keys); + zend_hash_destroy(&ctx.params); + + return zv; +} |