root/ext/sockets/conversions.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. param_get_bool
  2. accounted_emalloc
  3. accounted_ecalloc
  4. accounted_safe_ecalloc
  5. do_from_to_zval_err
  6. ZEND_ATTRIBUTE_FORMAT
  7. ZEND_ATTRIBUTE_FORMAT
  8. err_msg_dispose
  9. allocations_dispose
  10. from_array_iterate
  11. from_zval_write_aggregation
  12. to_zval_read_aggregation
  13. from_zval_integer_common
  14. from_zval_write_int
  15. from_zval_write_uint32
  16. from_zval_write_net_uint16
  17. from_zval_write_sa_family
  18. from_zval_write_pid_t
  19. from_zval_write_uid_t
  20. to_zval_read_int
  21. to_zval_read_unsigned
  22. to_zval_read_net_uint16
  23. to_zval_read_uint32
  24. to_zval_read_sa_family
  25. to_zval_read_pid_t
  26. to_zval_read_uid_t
  27. from_zval_write_sin_addr
  28. to_zval_read_sin_addr
  29. from_zval_write_sockaddr_in
  30. to_zval_read_sockaddr_in
  31. from_zval_write_sin6_addr
  32. to_zval_read_sin6_addr
  33. from_zval_write_sockaddr_in6
  34. to_zval_read_sockaddr_in6
  35. from_zval_write_sun_path
  36. to_zval_read_sun_path
  37. from_zval_write_sockaddr_un
  38. to_zval_read_sockaddr_un
  39. from_zval_write_sockaddr_aux
  40. to_zval_read_sockaddr_aux
  41. from_zval_write_control
  42. from_zval_write_control_array
  43. to_zval_read_cmsg_data
  44. to_zval_read_control
  45. to_zval_read_control_array
  46. from_zval_write_name
  47. to_zval_read_name
  48. from_zval_write_msghdr_buffer_size
  49. from_zval_write_iov_array_aux
  50. from_zval_write_iov_array
  51. from_zval_write_controllen
  52. from_zval_write_msghdr_send
  53. from_zval_write_msghdr_recv
  54. to_zval_read_iov
  55. to_zval_read_msghdr
  56. from_zval_write_ifindex
  57. from_zval_write_in6_pktinfo
  58. to_zval_read_in6_pktinfo
  59. from_zval_write_ucred
  60. to_zval_read_ucred
  61. calculate_scm_rights_space
  62. from_zval_write_fd_array_aux
  63. from_zval_write_fd_array
  64. to_zval_read_fd_array
  65. free_from_zval_allocation
  66. from_zval_run_conversions
  67. to_zval_run_conversions

#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;

        /* Note i starts at 1, not 0! */
    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);
}
#if HAVE_IPV6
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);
}
#endif /* HAVE_IPV6 */
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;
        }

        /* code in this file relies on the path being nul terminated, even though
         * this is not required, at least on linux for abstract paths. It also
         * assumes that the path is not empty */
        if (Z_STRLEN_P(path) == 0) {
                do_from_zval_err(ctx, "%s", "the path is cannot be empty");
                return;
        }
        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;

#if HAVE_IPV6
        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;
#endif /* HAVE_IPV6 */

        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);
                if (fill_sockaddr) {
                        struct sockaddr_un *sock_un = (struct sockaddr_un*)*sockaddr_ptr;

                        from_zval_write_sockaddr_un(container, (char*)*sockaddr_ptr, ctx);
                        (*sockaddr_ptr)->sa_family = AF_UNIX;

                        /* calculating length is more complicated here. Giving the size of
                         * struct sockaddr_un here and relying on the nul termination of
                         * sun_path does not work for paths in the abstract namespace. Note
                         * that we always assume the path is not empty and nul terminated */
                        *sockaddr_len = offsetof(struct sockaddr_un, sun_path) +
                                        (sock_un->sun_path[0] == '\0'
                                        ? (1 + strlen(&sock_un->sun_path[1]))
                                        : strlen(sock_un->sun_path));
                } else {
                        *sockaddr_len = sizeof(struct sockaddr_un);
                }
                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;

#if HAVE_IPV6
        case AF_INET6:
                to_zval_read_sockaddr_in6(sockaddr_c, zv, ctx);
                break;
#endif /* HAVE_IPV6 */

        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) {
                zval **data_elem;
                /* arr must be an array at this point */
                if (zend_hash_find(Z_ARRVAL_P(arr), "data", sizeof("data"),
                                (void**)&data_elem) == FAILURE) {
                        do_from_zval_err(ctx, "cmsghdr should have a 'data' element here");
                        return;
                }
                data_len = entry->calc_space(*data_elem, 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((char *)*control_buf + *offset, '\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 = 0;
        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 */
#if defined(IPV6_PKTINFO) && HAVE_IPV6
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 - 1],
                                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;
}

/* [<][>][^][v][top][bottom][index][help] */