#include "rubysocket.h"

#include <time.h>

static VALUE sym_wait_readable, sym_wait_writable;

#if defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL)
static VALUE rb_cAncillaryData;

static VALUE
constant_to_sym(int constant, ID (*intern_const)(int))
{
    ID name = intern_const(constant);
    if (name) {
        return ID2SYM(name);
    }

    return INT2NUM(constant);
}

static VALUE
ip_cmsg_type_to_sym(int level, int cmsg_type)
{
    switch (level) {
      case SOL_SOCKET:
        return constant_to_sym(cmsg_type, rsock_intern_scm_optname);
      case IPPROTO_IP:
        return constant_to_sym(cmsg_type, rsock_intern_ip_optname);
#ifdef IPPROTO_IPV6
      case IPPROTO_IPV6:
        return constant_to_sym(cmsg_type, rsock_intern_ipv6_optname);
#endif
      case IPPROTO_TCP:
        return constant_to_sym(cmsg_type, rsock_intern_tcp_optname);
      case IPPROTO_UDP:
        return constant_to_sym(cmsg_type, rsock_intern_udp_optname);
      default:
        return INT2NUM(cmsg_type);
    }
}

/*
 * call-seq:
 *   Socket::AncillaryData.new(family, cmsg_level, cmsg_type, cmsg_data) -> ancillarydata
 *
 * _family_ should be an integer, a string or a symbol.
 * - Socket::AF_INET, "AF_INET", "INET", :AF_INET, :INET
 * - Socket::AF_UNIX, "AF_UNIX", "UNIX", :AF_UNIX, :UNIX
 * - etc.
 *
 * _cmsg_level_ should be an integer, a string or a symbol.
 * - Socket::SOL_SOCKET, "SOL_SOCKET", "SOCKET", :SOL_SOCKET and :SOCKET
 * - Socket::IPPROTO_IP, "IP" and :IP
 * - Socket::IPPROTO_IPV6, "IPV6" and :IPV6
 * - Socket::IPPROTO_TCP, "TCP" and :TCP
 * - etc.
 *
 * _cmsg_type_ should be an integer, a string or a symbol.
 * If a string/symbol is specified, it is interpreted depend on _cmsg_level_.
 * - Socket::SCM_RIGHTS, "SCM_RIGHTS", "RIGHTS", :SCM_RIGHTS, :RIGHTS for SOL_SOCKET
 * - Socket::IP_RECVTTL, "RECVTTL" and :RECVTTL for IPPROTO_IP
 * - Socket::IPV6_PKTINFO, "PKTINFO" and :PKTINFO for IPPROTO_IPV6
 * - etc.
 *
 * _cmsg_data_ should be a string.
 *
 *   p Socket::AncillaryData.new(:INET, :TCP, :NODELAY, "")
 *   #=> #<Socket::AncillaryData: INET TCP NODELAY "">
 *
 *   p Socket::AncillaryData.new(:INET6, :IPV6, :PKTINFO, "")
 *   #=> #<Socket::AncillaryData: INET6 IPV6 PKTINFO "">
 *
 */
static VALUE
ancillary_initialize(VALUE self, VALUE vfamily, VALUE vlevel, VALUE vtype, VALUE data)
{
    int family = rsock_family_arg(vfamily);
    int level = rsock_level_arg(family, vlevel);
    int type = rsock_cmsg_type_arg(family, level, vtype);
    StringValue(data);
    rb_ivar_set(self, rb_intern("family"), INT2NUM(family));
    rb_ivar_set(self, rb_intern("level"), INT2NUM(level));
    rb_ivar_set(self, rb_intern("type"), INT2NUM(type));
    rb_ivar_set(self, rb_intern("data"), data);
    return self;
}

static VALUE
ancdata_new(int family, int level, int type, VALUE data)
{
    VALUE obj = rb_obj_alloc(rb_cAncillaryData);
    StringValue(data);
    ancillary_initialize(obj, INT2NUM(family), INT2NUM(level), INT2NUM(type), data);
    return (VALUE)obj;
}

static int
ancillary_family(VALUE self)
{
    VALUE v = rb_attr_get(self, rb_intern("family"));
    return NUM2INT(v);
}

/*
 * call-seq:
 *   ancillarydata.family => integer
 *
 * returns the socket family as an integer.
 *
 *   p Socket::AncillaryData.new(:INET6, :IPV6, :PKTINFO, "").family
 *   #=> 10
 */
static VALUE
ancillary_family_m(VALUE self)
{
    return INT2NUM(ancillary_family(self));
}

static int
ancillary_level(VALUE self)
{
    VALUE v = rb_attr_get(self, rb_intern("level"));
    return NUM2INT(v);
}

/*
 * call-seq:
 *   ancillarydata.level => integer
 *
 * returns the cmsg level as an integer.
 *
 *   p Socket::AncillaryData.new(:INET6, :IPV6, :PKTINFO, "").level
 *   #=> 41
 */
static VALUE
ancillary_level_m(VALUE self)
{
    return INT2NUM(ancillary_level(self));
}

static int
ancillary_type(VALUE self)
{
    VALUE v = rb_attr_get(self, rb_intern("type"));
    return NUM2INT(v);
}

/*
 * call-seq:
 *   ancillarydata.type => integer
 *
 * returns the cmsg type as an integer.
 *
 *   p Socket::AncillaryData.new(:INET6, :IPV6, :PKTINFO, "").type
 *   #=> 2
 */
static VALUE
ancillary_type_m(VALUE self)
{
    return INT2NUM(ancillary_type(self));
}

/*
 * call-seq:
 *   ancillarydata.data => string
 *
 * returns the cmsg data as a string.
 *
 *   p Socket::AncillaryData.new(:INET6, :IPV6, :PKTINFO, "").data
 *   #=> ""
 */
static VALUE
ancillary_data(VALUE self)
{
    VALUE v = rb_attr_get(self, rb_intern("data"));
    StringValue(v);
    return v;
}

#ifdef SCM_RIGHTS
/*
 * call-seq:
 *   Socket::AncillaryData.unix_rights(io1, io2, ...) => ancillarydata
 *
 * Creates a new Socket::AncillaryData object which contains file descriptors as data.
 *
 *   p Socket::AncillaryData.unix_rights(STDERR)
 *   #=> #<Socket::AncillaryData: UNIX SOCKET RIGHTS 2>
 */
static VALUE
ancillary_s_unix_rights(int argc, VALUE *argv, VALUE klass)
{
    VALUE result, str, ary;
    int i;

    ary = rb_ary_new();

    for (i = 0 ; i < argc; i++) {
        VALUE obj = argv[i];
        if (!RB_TYPE_P(obj, T_FILE)) {
            rb_raise(rb_eTypeError, "IO expected");
        }
        rb_ary_push(ary, obj);
    }

    str = rb_str_buf_new(sizeof(int) * argc);

    for (i = 0 ; i < argc; i++) {
        VALUE obj = RARRAY_AREF(ary, i);
        rb_io_t *fptr;
        int fd;
        GetOpenFile(obj, fptr);
        fd = fptr->fd;
        rb_str_buf_cat(str, (char *)&fd, sizeof(int));
    }

    result = ancdata_new(AF_UNIX, SOL_SOCKET, SCM_RIGHTS, str);
    rb_ivar_set(result, rb_intern("unix_rights"), ary);
    return result;
}
#else
#define ancillary_s_unix_rights rb_f_notimplement
#endif

#ifdef SCM_RIGHTS
/*
 * call-seq:
 *   ancillarydata.unix_rights => array-of-IOs or nil
 *
 * returns the array of IO objects for SCM_RIGHTS control message in UNIX domain socket.
 *
 * The class of the IO objects in the array is IO or Socket.
 *
 * The array is attached to _ancillarydata_ when it is instantiated.
 * For example, BasicSocket#recvmsg attach the array when
 * receives a SCM_RIGHTS control message and :scm_rights=>true option is given.
 *
 *   # recvmsg needs :scm_rights=>true for unix_rights
 *   s1, s2 = UNIXSocket.pair
 *   p s1                                         #=> #<UNIXSocket:fd 3>
 *   s1.sendmsg "stdin and a socket", 0, nil, Socket::AncillaryData.unix_rights(STDIN, s1)
 *   _, _, _, ctl = s2.recvmsg(:scm_rights=>true)
 *   p ctl                                        #=> #<Socket::AncillaryData: UNIX SOCKET RIGHTS 6 7>
 *   p ctl.unix_rights                            #=> [#<IO:fd 6>, #<Socket:fd 7>]
 *   p File.identical?(STDIN, ctl.unix_rights[0]) #=> true
 *   p File.identical?(s1, ctl.unix_rights[1])    #=> true
 *
 *   # If :scm_rights=>true is not given, unix_rights returns nil
 *   s1, s2 = UNIXSocket.pair
 *   s1.sendmsg "stdin and a socket", 0, nil, Socket::AncillaryData.unix_rights(STDIN, s1)
 *   _, _, _, ctl = s2.recvmsg
 *   p ctl #=> #<Socket::AncillaryData: UNIX SOCKET RIGHTS 6 7>
 *   p ctl.unix_rights #=> nil
 *
 */
static VALUE
ancillary_unix_rights(VALUE self)
{
    int level, type;

    level = ancillary_level(self);
    type = ancillary_type(self);

    if (level != SOL_SOCKET || type != SCM_RIGHTS)
        rb_raise(rb_eTypeError, "SCM_RIGHTS ancillary data expected");

    return rb_attr_get(self, rb_intern("unix_rights"));
}
#else
#define ancillary_unix_rights rb_f_notimplement
#endif

#if defined(SCM_TIMESTAMP) || defined(SCM_TIMESTAMPNS) || defined(SCM_BINTIME)
/*
 * call-seq:
 *   ancillarydata.timestamp => time
 *
 * returns the timestamp as a time object.
 *
 * _ancillarydata_ should be one of following type:
 * - SOL_SOCKET/SCM_TIMESTAMP (microsecond) GNU/Linux, FreeBSD, NetBSD, OpenBSD, Solaris, MacOS X
 * - SOL_SOCKET/SCM_TIMESTAMPNS (nanosecond) GNU/Linux
 * - SOL_SOCKET/SCM_BINTIME (2**(-64) second) FreeBSD
 *
 *   Addrinfo.udp("127.0.0.1", 0).bind {|s1|
 *     Addrinfo.udp("127.0.0.1", 0).bind {|s2|
 *       s1.setsockopt(:SOCKET, :TIMESTAMP, true)
 *       s2.send "a", 0, s1.local_address
 *       ctl = s1.recvmsg.last
 *       p ctl    #=> #<Socket::AncillaryData: INET SOCKET TIMESTAMP 2009-02-24 17:35:46.775581>
 *       t = ctl.timestamp
 *       p t      #=> 2009-02-24 17:35:46 +0900
 *       p t.usec #=> 775581
 *       p t.nsec #=> 775581000
 *     }
 *   }
 *
 */
static VALUE
ancillary_timestamp(VALUE self)
{
    int level, type;
    VALUE data;
    VALUE result = Qnil;

    level = ancillary_level(self);
    type = ancillary_type(self);
    data = ancillary_data(self);

# ifdef SCM_TIMESTAMP
    if (level == SOL_SOCKET && type == SCM_TIMESTAMP &&
        RSTRING_LEN(data) == sizeof(struct timeval)) {
        struct timeval tv;
        memcpy((char*)&tv, RSTRING_PTR(data), sizeof(tv));
        result = rb_time_new(tv.tv_sec, tv.tv_usec);
    }
# endif

# ifdef SCM_TIMESTAMPNS
    if (level == SOL_SOCKET && type == SCM_TIMESTAMPNS &&
        RSTRING_LEN(data) == sizeof(struct timespec)) {
        struct timespec ts;
        memcpy((char*)&ts, RSTRING_PTR(data), sizeof(ts));
        result = rb_time_nano_new(ts.tv_sec, ts.tv_nsec);
    }
# endif

#define add(x,y) (rb_funcall((x), '+', 1, (y)))
#define mul(x,y) (rb_funcall((x), '*', 1, (y)))
#define quo(x,y) (rb_funcall((x), rb_intern("quo"), 1, (y)))

# ifdef SCM_BINTIME
    if (level == SOL_SOCKET && type == SCM_BINTIME &&
        RSTRING_LEN(data) == sizeof(struct bintime)) {
        struct bintime bt;
	VALUE d, timev;
        memcpy((char*)&bt, RSTRING_PTR(data), sizeof(bt));
	d = ULL2NUM(0x100000000ULL);
	d = mul(d,d);
	timev = add(TIMET2NUM(bt.sec), quo(ULL2NUM(bt.frac), d));
        result = rb_time_num_new(timev, Qnil);
    }
# endif

    if (result == Qnil)
        rb_raise(rb_eTypeError, "timestamp ancillary data expected");

    return result;
}
#else
#define ancillary_timestamp rb_f_notimplement
#endif

/*
 * call-seq:
 *   Socket::AncillaryData.int(family, cmsg_level, cmsg_type, integer) => ancillarydata
 *
 * Creates a new Socket::AncillaryData object which contains a int as data.
 *
 * The size and endian is dependent on the host.
 *
 *   require 'socket'
 *
 *   p Socket::AncillaryData.int(:UNIX, :SOCKET, :RIGHTS, STDERR.fileno)
 *   #=> #<Socket::AncillaryData: UNIX SOCKET RIGHTS 2>
 */
static VALUE
ancillary_s_int(VALUE klass, VALUE vfamily, VALUE vlevel, VALUE vtype, VALUE integer)
{
    int family = rsock_family_arg(vfamily);
    int level = rsock_level_arg(family, vlevel);
    int type = rsock_cmsg_type_arg(family, level, vtype);
    int i = NUM2INT(integer);
    return ancdata_new(family, level, type, rb_str_new((char*)&i, sizeof(i)));
}

/*
 * call-seq:
 *   ancillarydata.int => integer
 *
 * Returns the data in _ancillarydata_ as an int.
 *
 * The size and endian is dependent on the host.
 *
 *   ancdata = Socket::AncillaryData.int(:UNIX, :SOCKET, :RIGHTS, STDERR.fileno)
 *   p ancdata.int #=> 2
 */
static VALUE
ancillary_int(VALUE self)
{
    VALUE data;
    int i;
    data = ancillary_data(self);
    if (RSTRING_LEN(data) != sizeof(int))
        rb_raise(rb_eTypeError, "size differ.  expected as sizeof(int)=%d but %ld", (int)sizeof(int), (long)RSTRING_LEN(data));
    memcpy((char*)&i, RSTRING_PTR(data), sizeof(int));
    return INT2NUM(i);
}

#if defined(IPPROTO_IP) && defined(IP_PKTINFO) && defined(HAVE_STRUCT_IN_PKTINFO_IPI_SPEC_DST) /* GNU/Linux */
/*
 * call-seq:
 *   Socket::AncillaryData.ip_pktinfo(addr, ifindex) => ancdata
 *   Socket::AncillaryData.ip_pktinfo(addr, ifindex, spec_dst) => ancdata
 *
 * Returns new ancillary data for IP_PKTINFO.
 *
 * If spec_dst is not given, addr is used.
 *
 * IP_PKTINFO is not standard.
 *
 * Supported platform: GNU/Linux
 *
 *   addr = Addrinfo.ip("127.0.0.1")
 *   ifindex = 0
 *   spec_dst = Addrinfo.ip("127.0.0.1")
 *   p Socket::AncillaryData.ip_pktinfo(addr, ifindex, spec_dst)
 *   #=> #<Socket::AncillaryData: INET IP PKTINFO 127.0.0.1 ifindex:0 spec_dst:127.0.0.1>
 *
 */
static VALUE
ancillary_s_ip_pktinfo(int argc, VALUE *argv, VALUE self)
{
    VALUE v_addr, v_ifindex, v_spec_dst;
    unsigned int ifindex;
    struct sockaddr_in sa;
    struct in_pktinfo pktinfo;

    rb_scan_args(argc, argv, "21", &v_addr, &v_ifindex, &v_spec_dst);

    SockAddrStringValue(v_addr);
    ifindex = NUM2UINT(v_ifindex);
    if (NIL_P(v_spec_dst))
        v_spec_dst = v_addr;
    else
        SockAddrStringValue(v_spec_dst);

    memset(&pktinfo, 0, sizeof(pktinfo));

    memset(&sa, 0, sizeof(sa));
    if (RSTRING_LEN(v_addr) != sizeof(sa))
        rb_raise(rb_eArgError, "addr size different to AF_INET sockaddr");
    memcpy(&sa, RSTRING_PTR(v_addr), sizeof(sa));
    if (sa.sin_family != AF_INET)
        rb_raise(rb_eArgError, "addr is not AF_INET sockaddr");
    memcpy(&pktinfo.ipi_addr, &sa.sin_addr, sizeof(pktinfo.ipi_addr));

    pktinfo.ipi_ifindex = ifindex;

    memset(&sa, 0, sizeof(sa));
    if (RSTRING_LEN(v_spec_dst) != sizeof(sa))
        rb_raise(rb_eArgError, "spec_dat size different to AF_INET sockaddr");
    memcpy(&sa, RSTRING_PTR(v_spec_dst), sizeof(sa));
    if (sa.sin_family != AF_INET)
        rb_raise(rb_eArgError, "spec_dst is not AF_INET sockaddr");
    memcpy(&pktinfo.ipi_spec_dst, &sa.sin_addr, sizeof(pktinfo.ipi_spec_dst));

    return ancdata_new(AF_INET, IPPROTO_IP, IP_PKTINFO, rb_str_new((char *)&pktinfo, sizeof(pktinfo)));
}
#else
#define ancillary_s_ip_pktinfo rb_f_notimplement
#endif

#if defined(IPPROTO_IP) && defined(IP_PKTINFO) && defined(HAVE_STRUCT_IN_PKTINFO_IPI_SPEC_DST) /* GNU/Linux */
/*
 * call-seq:
 *   ancdata.ip_pktinfo => [addr, ifindex, spec_dst]
 *
 * Extracts addr, ifindex and spec_dst from IP_PKTINFO ancillary data.
 *
 * IP_PKTINFO is not standard.
 *
 * Supported platform: GNU/Linux
 *
 *   addr = Addrinfo.ip("127.0.0.1")
 *   ifindex = 0
 *   spec_dest = Addrinfo.ip("127.0.0.1")
 *   ancdata = Socket::AncillaryData.ip_pktinfo(addr, ifindex, spec_dest)
 *   p ancdata.ip_pktinfo
 *   #=> [#<Addrinfo: 127.0.0.1>, 0, #<Addrinfo: 127.0.0.1>]
 *
 *
 */
static VALUE
ancillary_ip_pktinfo(VALUE self)
{
    int level, type;
    VALUE data;
    struct in_pktinfo pktinfo;
    struct sockaddr_in sa;
    VALUE v_spec_dst, v_addr;

    level = ancillary_level(self);
    type = ancillary_type(self);
    data = ancillary_data(self);

    if (level != IPPROTO_IP || type != IP_PKTINFO ||
        RSTRING_LEN(data) != sizeof(struct in_pktinfo)) {
        rb_raise(rb_eTypeError, "IP_PKTINFO ancillary data expected");
    }

    memcpy(&pktinfo, RSTRING_PTR(data), sizeof(struct in_pktinfo));
    memset(&sa, 0, sizeof(sa));

    sa.sin_family = AF_INET;
    memcpy(&sa.sin_addr, &pktinfo.ipi_addr, sizeof(sa.sin_addr));
    v_addr = rsock_addrinfo_new((struct sockaddr *)&sa, sizeof(sa), PF_INET, 0, 0, Qnil, Qnil);

    sa.sin_family = AF_INET;
    memcpy(&sa.sin_addr, &pktinfo.ipi_spec_dst, sizeof(sa.sin_addr));
    v_spec_dst = rsock_addrinfo_new((struct sockaddr *)&sa, sizeof(sa), PF_INET, 0, 0, Qnil, Qnil);

    return rb_ary_new3(3, v_addr, UINT2NUM(pktinfo.ipi_ifindex), v_spec_dst);
}
#else
#define ancillary_ip_pktinfo rb_f_notimplement
#endif

#if defined(IPPROTO_IPV6) && defined(IPV6_PKTINFO) /* IPv6 RFC3542 */
/*
 * call-seq:
 *   Socket::AncillaryData.ipv6_pktinfo(addr, ifindex) => ancdata
 *
 * Returns new ancillary data for IPV6_PKTINFO.
 *
 * IPV6_PKTINFO is defined by RFC 3542.
 *
 *   addr = Addrinfo.ip("::1")
 *   ifindex = 0
 *   p Socket::AncillaryData.ipv6_pktinfo(addr, ifindex)
 *   #=> #<Socket::AncillaryData: INET6 IPV6 PKTINFO ::1 ifindex:0>
 *
 */
static VALUE
ancillary_s_ipv6_pktinfo(VALUE self, VALUE v_addr, VALUE v_ifindex)
{
    unsigned int ifindex;
    struct sockaddr_in6 sa;
    struct in6_pktinfo pktinfo;

    SockAddrStringValue(v_addr);
    ifindex = NUM2UINT(v_ifindex);

    memset(&pktinfo, 0, sizeof(pktinfo));

    memset(&sa, 0, sizeof(sa));
    if (RSTRING_LEN(v_addr) != sizeof(sa))
        rb_raise(rb_eArgError, "addr size different to AF_INET6 sockaddr");
    memcpy(&sa, RSTRING_PTR(v_addr), sizeof(sa));
    if (sa.sin6_family != AF_INET6)
        rb_raise(rb_eArgError, "addr is not AF_INET6 sockaddr");
    memcpy(&pktinfo.ipi6_addr, &sa.sin6_addr, sizeof(pktinfo.ipi6_addr));

    pktinfo.ipi6_ifindex = ifindex;

    return ancdata_new(AF_INET6, IPPROTO_IPV6, IPV6_PKTINFO, rb_str_new((char *)&pktinfo, sizeof(pktinfo)));
}
#else
#define ancillary_s_ipv6_pktinfo rb_f_notimplement
#endif

#if defined(IPPROTO_IPV6) && defined(IPV6_PKTINFO) /* IPv6 RFC3542 */
static void
extract_ipv6_pktinfo(VALUE self, struct in6_pktinfo *pktinfo_ptr, struct sockaddr_in6 *sa_ptr)
{
    int level, type;
    VALUE data;

    level = ancillary_level(self);
    type = ancillary_type(self);
    data = ancillary_data(self);

    if (level != IPPROTO_IPV6 || type != IPV6_PKTINFO ||
        RSTRING_LEN(data) != sizeof(struct in6_pktinfo)) {
        rb_raise(rb_eTypeError, "IPV6_PKTINFO ancillary data expected");
    }

    memcpy(pktinfo_ptr, RSTRING_PTR(data), sizeof(*pktinfo_ptr));

    INIT_SOCKADDR((struct sockaddr *)sa_ptr, AF_INET6, sizeof(*sa_ptr));
    memcpy(&sa_ptr->sin6_addr, &pktinfo_ptr->ipi6_addr, sizeof(sa_ptr->sin6_addr));
    if (IN6_IS_ADDR_LINKLOCAL(&sa_ptr->sin6_addr))
        sa_ptr->sin6_scope_id = pktinfo_ptr->ipi6_ifindex;
}
#endif

#if defined(IPPROTO_IPV6) && defined(IPV6_PKTINFO) /* IPv6 RFC3542 */
/*
 * call-seq:
 *   ancdata.ipv6_pktinfo => [addr, ifindex]
 *
 * Extracts addr and ifindex from IPV6_PKTINFO ancillary data.
 *
 * IPV6_PKTINFO is defined by RFC 3542.
 *
 *   addr = Addrinfo.ip("::1")
 *   ifindex = 0
 *   ancdata = Socket::AncillaryData.ipv6_pktinfo(addr, ifindex)
 *   p ancdata.ipv6_pktinfo #=> [#<Addrinfo: ::1>, 0]
 *
 */
static VALUE
ancillary_ipv6_pktinfo(VALUE self)
{
    struct in6_pktinfo pktinfo;
    struct sockaddr_in6 sa;
    VALUE v_addr;

    extract_ipv6_pktinfo(self, &pktinfo, &sa);
    v_addr = rsock_addrinfo_new((struct sockaddr *)&sa, (socklen_t)sizeof(sa), PF_INET6, 0, 0, Qnil, Qnil);
    return rb_ary_new3(2, v_addr, UINT2NUM(pktinfo.ipi6_ifindex));
}
#else
#define ancillary_ipv6_pktinfo rb_f_notimplement
#endif

#if defined(IPPROTO_IPV6) && defined(IPV6_PKTINFO) /* IPv6 RFC3542 */
/*
 * call-seq:
 *   ancdata.ipv6_pktinfo_addr => addr
 *
 * Extracts addr from IPV6_PKTINFO ancillary data.
 *
 * IPV6_PKTINFO is defined by RFC 3542.
 *
 *   addr = Addrinfo.ip("::1")
 *   ifindex = 0
 *   ancdata = Socket::AncillaryData.ipv6_pktinfo(addr, ifindex)
 *   p ancdata.ipv6_pktinfo_addr #=> #<Addrinfo: ::1>
 *
 */
static VALUE
ancillary_ipv6_pktinfo_addr(VALUE self)
{
    struct in6_pktinfo pktinfo;
    struct sockaddr_in6 sa;
    extract_ipv6_pktinfo(self, &pktinfo, &sa);
    return rsock_addrinfo_new((struct sockaddr *)&sa, (socklen_t)sizeof(sa), PF_INET6, 0, 0, Qnil, Qnil);
}
#else
#define ancillary_ipv6_pktinfo_addr rb_f_notimplement
#endif

#if defined(IPPROTO_IPV6) && defined(IPV6_PKTINFO) /* IPv6 RFC3542 */
/*
 * call-seq:
 *   ancdata.ipv6_pktinfo_ifindex => addr
 *
 * Extracts ifindex from IPV6_PKTINFO ancillary data.
 *
 * IPV6_PKTINFO is defined by RFC 3542.
 *
 *   addr = Addrinfo.ip("::1")
 *   ifindex = 0
 *   ancdata = Socket::AncillaryData.ipv6_pktinfo(addr, ifindex)
 *   p ancdata.ipv6_pktinfo_ifindex #=> 0
 *
 */
static VALUE
ancillary_ipv6_pktinfo_ifindex(VALUE self)
{
    struct in6_pktinfo pktinfo;
    struct sockaddr_in6 sa;
    extract_ipv6_pktinfo(self, &pktinfo, &sa);
    return UINT2NUM(pktinfo.ipi6_ifindex);
}
#else
#define ancillary_ipv6_pktinfo_ifindex rb_f_notimplement
#endif

#if defined(SOL_SOCKET) && defined(SCM_RIGHTS) /* 4.4BSD */
static int
anc_inspect_socket_rights(int level, int type, VALUE data, VALUE ret)
{
    if (level == SOL_SOCKET && type == SCM_RIGHTS &&
        0 < RSTRING_LEN(data) && (RSTRING_LEN(data) % sizeof(int) == 0)) {
        long off;
        for (off = 0; off < RSTRING_LEN(data); off += sizeof(int)) {
            int fd;
            memcpy((char*)&fd, RSTRING_PTR(data)+off, sizeof(int));
            rb_str_catf(ret, " %d", fd);
        }
        return 1;
    }
    else {
        return 0;
    }
}
#endif

#if defined(SCM_CREDENTIALS) /* GNU/Linux */
static int
anc_inspect_passcred_credentials(int level, int type, VALUE data, VALUE ret)
{
    if (level == SOL_SOCKET && type == SCM_CREDENTIALS &&
        RSTRING_LEN(data) == sizeof(struct ucred)) {
        struct ucred cred;
        memcpy(&cred, RSTRING_PTR(data), sizeof(struct ucred));
        rb_str_catf(ret, " pid=%u uid=%u gid=%u", cred.pid, cred.uid, cred.gid);
	rb_str_cat2(ret, " (ucred)");
        return 1;
    }
    else {
        return 0;
    }
}
#endif

#if defined(SCM_CREDS)
#define INSPECT_SCM_CREDS
static int
anc_inspect_socket_creds(int level, int type, VALUE data, VALUE ret)
{
    if (level != SOL_SOCKET && type != SCM_CREDS)
	return 0;

    /*
     * FreeBSD has struct cmsgcred and struct sockcred.
     * They use both SOL_SOCKET/SCM_CREDS in the ancillary message.
     * They are not ambiguous from the view of the caller
     * because struct sockcred is sent if and only if the caller sets LOCAL_CREDS socket option.
     * But inspect method doesn't know it.
     * So they are ambiguous from the view of inspect.
     * This function distinguish them by the size of the ancillary message.
     * This heuristics works well except when sc_ngroups == CMGROUP_MAX.
     */

#if defined(HAVE_TYPE_STRUCT_CMSGCRED) /* FreeBSD */
    if (RSTRING_LEN(data) == sizeof(struct cmsgcred)) {
	struct cmsgcred cred;
        memcpy(&cred, RSTRING_PTR(data), sizeof(struct cmsgcred));
        rb_str_catf(ret, " pid=%u", cred.cmcred_pid);
        rb_str_catf(ret, " uid=%u", cred.cmcred_uid);
        rb_str_catf(ret, " euid=%u", cred.cmcred_euid);
        rb_str_catf(ret, " gid=%u", cred.cmcred_gid);
	if (cred.cmcred_ngroups) {
	    int i;
	    const char *sep = " groups=";
	    for (i = 0; i < cred.cmcred_ngroups; i++) {
		rb_str_catf(ret, "%s%u", sep, cred.cmcred_groups[i]);
		sep = ",";
	    }
	}
	rb_str_cat2(ret, " (cmsgcred)");
        return 1;
    }
#endif
#if defined(HAVE_TYPE_STRUCT_SOCKCRED) /* FreeBSD, NetBSD */
    if ((size_t)RSTRING_LEN(data) >= SOCKCREDSIZE(0)) {
	struct sockcred cred0, *cred;
        memcpy(&cred0, RSTRING_PTR(data), SOCKCREDSIZE(0));
	if ((size_t)RSTRING_LEN(data) == SOCKCREDSIZE(cred0.sc_ngroups)) {
	    cred = (struct sockcred *)ALLOCA_N(char, SOCKCREDSIZE(cred0.sc_ngroups));
	    memcpy(cred, RSTRING_PTR(data), SOCKCREDSIZE(cred0.sc_ngroups));
	    rb_str_catf(ret, " uid=%u", cred->sc_uid);
	    rb_str_catf(ret, " euid=%u", cred->sc_euid);
	    rb_str_catf(ret, " gid=%u", cred->sc_gid);
	    rb_str_catf(ret, " egid=%u", cred->sc_egid);
	    if (cred0.sc_ngroups) {
		int i;
		const char *sep = " groups=";
		for (i = 0; i < cred0.sc_ngroups; i++) {
		    rb_str_catf(ret, "%s%u", sep, cred->sc_groups[i]);
		    sep = ",";
		}
	    }
	    rb_str_cat2(ret, " (sockcred)");
	    return 1;
	}
    }
#endif
    return 0;
}
#endif

#if defined(IPPROTO_IP) && defined(IP_RECVDSTADDR) /* 4.4BSD */
static int
anc_inspect_ip_recvdstaddr(int level, int type, VALUE data, VALUE ret)
{
    if (level == IPPROTO_IP && type == IP_RECVDSTADDR &&
        RSTRING_LEN(data) == sizeof(struct in_addr)) {
        struct in_addr addr;
        char addrbuf[INET_ADDRSTRLEN];
        memcpy(&addr, RSTRING_PTR(data), sizeof(addr));
        if (inet_ntop(AF_INET, &addr, addrbuf, (socklen_t)sizeof(addrbuf)) == NULL)
            rb_str_cat2(ret, " invalid-address");
        else
            rb_str_catf(ret, " %s", addrbuf);
        return 1;
    }
    else {
        return 0;
    }
}
#endif

#if defined(IPPROTO_IP) && defined(IP_PKTINFO) && defined(HAVE_STRUCT_IN_PKTINFO_IPI_SPEC_DST) /* GNU/Linux */
static int
anc_inspect_ip_pktinfo(int level, int type, VALUE data, VALUE ret)
{
    if (level == IPPROTO_IP && type == IP_PKTINFO &&
        RSTRING_LEN(data) == sizeof(struct in_pktinfo)) {
        struct in_pktinfo pktinfo;
        char buf[INET_ADDRSTRLEN > IFNAMSIZ ? INET_ADDRSTRLEN : IFNAMSIZ];
        memcpy(&pktinfo, RSTRING_PTR(data), sizeof(pktinfo));
        if (inet_ntop(AF_INET, &pktinfo.ipi_addr, buf, sizeof(buf)) == NULL)
            rb_str_cat2(ret, " invalid-address");
        else
            rb_str_catf(ret, " %s", buf);
        if (if_indextoname(pktinfo.ipi_ifindex, buf) == NULL)
            rb_str_catf(ret, " ifindex:%d", pktinfo.ipi_ifindex);
        else
            rb_str_catf(ret, " %s", buf);
        if (inet_ntop(AF_INET, &pktinfo.ipi_spec_dst, buf, sizeof(buf)) == NULL)
            rb_str_cat2(ret, " spec_dst:invalid-address");
        else
            rb_str_catf(ret, " spec_dst:%s", buf);
        return 1;
    }
    else {
        return 0;
    }
}
#endif

#if defined(IPPROTO_IPV6) && defined(IPV6_PKTINFO) && defined(HAVE_TYPE_STRUCT_IN6_PKTINFO) /* IPv6 RFC3542 */
static int
anc_inspect_ipv6_pktinfo(int level, int type, VALUE data, VALUE ret)
{
    if (level == IPPROTO_IPV6 && type == IPV6_PKTINFO &&
        RSTRING_LEN(data) == sizeof(struct in6_pktinfo)) {
        struct in6_pktinfo *pktinfo = (struct in6_pktinfo *)RSTRING_PTR(data);
        struct in6_addr addr;
        unsigned int ifindex;
        char addrbuf[INET6_ADDRSTRLEN], ifbuf[IFNAMSIZ];
        memcpy(&addr, &pktinfo->ipi6_addr, sizeof(addr));
        memcpy(&ifindex, &pktinfo->ipi6_ifindex, sizeof(ifindex));
        if (inet_ntop(AF_INET6, &addr, addrbuf, (socklen_t)sizeof(addrbuf)) == NULL)
            rb_str_cat2(ret, " invalid-address");
        else
            rb_str_catf(ret, " %s", addrbuf);
        if (if_indextoname(ifindex, ifbuf) == NULL)
            rb_str_catf(ret, " ifindex:%d", ifindex);
        else
            rb_str_catf(ret, " %s", ifbuf);
        return 1;
    }
    else {
        return 0;
    }
}
#endif

#if defined(SCM_TIMESTAMP) /* GNU/Linux, FreeBSD, NetBSD, OpenBSD, MacOS X, Solaris */
static int
inspect_timeval_as_abstime(int level, int optname, VALUE data, VALUE ret)
{
    if (RSTRING_LEN(data) == sizeof(struct timeval)) {
        struct timeval tv;
        time_t time;
        struct tm tm;
        char buf[32];
        memcpy((char*)&tv, RSTRING_PTR(data), sizeof(tv));
        time = tv.tv_sec;
        tm = *localtime(&time);
        strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &tm);
        rb_str_catf(ret, " %s.%06ld", buf, (long)tv.tv_usec);
        return 1;
    }
    else {
        return 0;
    }
}
#endif

#if defined(SCM_TIMESTAMPNS) /* GNU/Linux */
static int
inspect_timespec_as_abstime(int level, int optname, VALUE data, VALUE ret)
{
    if (RSTRING_LEN(data) == sizeof(struct timespec)) {
        struct timespec ts;
        struct tm tm;
        char buf[32];
        memcpy((char*)&ts, RSTRING_PTR(data), sizeof(ts));
        tm = *localtime(&ts.tv_sec);
        strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &tm);
        rb_str_catf(ret, " %s.%09ld", buf, (long)ts.tv_nsec);
        return 1;
    }
    else {
        return 0;
    }
}
#endif

#if defined(SCM_BINTIME) /* FreeBSD */
static int
inspect_bintime_as_abstime(int level, int optname, VALUE data, VALUE ret)
{
    if (RSTRING_LEN(data) == sizeof(struct bintime)) {
        struct bintime bt;
        struct tm tm;
	uint64_t frac_h, frac_l;
	uint64_t scale_h, scale_l;
	uint64_t tmp1, tmp2;
	uint64_t res_h, res_l;
        char buf[32];
        memcpy((char*)&bt, RSTRING_PTR(data), sizeof(bt));
        tm = *localtime(&bt.sec);
        strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &tm);

	/* res_h = frac * 10**19 / 2**64 */

	frac_h = bt.frac >> 32;
	frac_l = bt.frac & 0xffffffff;

	scale_h = 0x8ac72304; /* 0x8ac7230489e80000 == 10**19 */
	scale_l = 0x89e80000;

	res_h = frac_h * scale_h;
	res_l = frac_l * scale_l;

	tmp1 = frac_h * scale_l;
	res_h += tmp1 >> 32;
	tmp2 = res_l;
	res_l += tmp1 & 0xffffffff;
	if (res_l < tmp2) res_h++;

	tmp1 = frac_l * scale_h;
	res_h += tmp1 >> 32;
	tmp2 = res_l;
	res_l += tmp1 & 0xffffffff;
	if (res_l < tmp2) res_h++;

        rb_str_catf(ret, " %s.%019"PRIu64, buf, res_h);
        return 1;
    }
    else {
        return 0;
    }
}
#endif

/*
 * call-seq:
 *   ancillarydata.inspect => string
 *
 * returns a string which shows ancillarydata in human-readable form.
 *
 *   p Socket::AncillaryData.new(:INET6, :IPV6, :PKTINFO, "").inspect
 *   #=> "#<Socket::AncillaryData: INET6 IPV6 PKTINFO \"\">"
 */
static VALUE
ancillary_inspect(VALUE self)
{
    VALUE ret;
    int family, level, type;
    VALUE data;
    ID family_id, level_id, type_id;
    VALUE vtype;
    int inspected;

    family = ancillary_family(self);
    level = ancillary_level(self);
    type = ancillary_type(self);
    data = ancillary_data(self);

    ret = rb_sprintf("#<%s:", rb_obj_classname(self));

    family_id = rsock_intern_family_noprefix(family);
    if (family_id)
        rb_str_catf(ret, " %s", rb_id2name(family_id));
    else
        rb_str_catf(ret, " family:%d", family);

    if (level == SOL_SOCKET) {
        rb_str_cat2(ret, " SOCKET");

        type_id = rsock_intern_scm_optname(type);
        if (type_id)
            rb_str_catf(ret, " %s", rb_id2name(type_id));
        else
            rb_str_catf(ret, " cmsg_type:%d", type);
    }
    else if (IS_IP_FAMILY(family)) {
        level_id = rsock_intern_iplevel(level);
        if (level_id)
            rb_str_catf(ret, " %s", rb_id2name(level_id));
        else
            rb_str_catf(ret, " cmsg_level:%d", level);

        vtype = ip_cmsg_type_to_sym(level, type);
        if (SYMBOL_P(vtype))
            rb_str_catf(ret, " %"PRIsVALUE, rb_sym2str(vtype));
        else
            rb_str_catf(ret, " cmsg_type:%d", type);
    }
    else {
        rb_str_catf(ret, " cmsg_level:%d", level);
        rb_str_catf(ret, " cmsg_type:%d", type);
    }

    inspected = 0;

    if (level == SOL_SOCKET)
        family = AF_UNSPEC;

    switch (family) {
      case AF_UNSPEC:
        switch (level) {
#        if defined(SOL_SOCKET)
          case SOL_SOCKET:
            switch (type) {
#            if defined(SCM_TIMESTAMP) /* GNU/Linux, FreeBSD, NetBSD, OpenBSD, MacOS X, Solaris */
              case SCM_TIMESTAMP: inspected = inspect_timeval_as_abstime(level, type, data, ret); break;
#            endif
#            if defined(SCM_TIMESTAMPNS) /* GNU/Linux */
              case SCM_TIMESTAMPNS: inspected = inspect_timespec_as_abstime(level, type, data, ret); break;
#            endif
#            if defined(SCM_BINTIME) /* FreeBSD */
              case SCM_BINTIME: inspected = inspect_bintime_as_abstime(level, type, data, ret); break;
#            endif
#            if defined(SCM_RIGHTS) /* 4.4BSD */
              case SCM_RIGHTS: inspected = anc_inspect_socket_rights(level, type, data, ret); break;
#            endif
#            if defined(SCM_CREDENTIALS) /* GNU/Linux */
              case SCM_CREDENTIALS: inspected = anc_inspect_passcred_credentials(level, type, data, ret); break;
#            endif
#            if defined(INSPECT_SCM_CREDS) /* NetBSD */
              case SCM_CREDS: inspected = anc_inspect_socket_creds(level, type, data, ret); break;
#            endif
            }
            break;
#        endif
        }
        break;

      case AF_INET:
#ifdef INET6
      case AF_INET6:
#endif
        switch (level) {
#        if defined(IPPROTO_IP)
          case IPPROTO_IP:
            switch (type) {
#            if defined(IP_RECVDSTADDR) /* 4.4BSD */
              case IP_RECVDSTADDR: inspected = anc_inspect_ip_recvdstaddr(level, type, data, ret); break;
#            endif
#            if defined(IP_PKTINFO) && defined(HAVE_STRUCT_IN_PKTINFO_IPI_SPEC_DST) /* GNU/Linux */
              case IP_PKTINFO: inspected = anc_inspect_ip_pktinfo(level, type, data, ret); break;
#            endif
            }
            break;
#        endif

#        if defined(IPPROTO_IPV6)
          case IPPROTO_IPV6:
            switch (type) {
#            if defined(IPV6_PKTINFO) && defined(HAVE_TYPE_STRUCT_IN6_PKTINFO) /* RFC 3542 */
              case IPV6_PKTINFO: inspected = anc_inspect_ipv6_pktinfo(level, type, data, ret); break;
#            endif
            }
            break;
#        endif
        }
        break;
    }

    if (!inspected) {
        rb_str_cat2(ret, " ");
        rb_str_append(ret, rb_str_dump(data));
    }

    rb_str_cat2(ret, ">");

    return ret;
}

/*
 * call-seq:
 *   ancillarydata.cmsg_is?(level, type) => true or false
 *
 * tests the level and type of _ancillarydata_.
 *
 *   ancdata = Socket::AncillaryData.new(:INET6, :IPV6, :PKTINFO, "")
 *   ancdata.cmsg_is?(Socket::IPPROTO_IPV6, Socket::IPV6_PKTINFO) #=> true
 *   ancdata.cmsg_is?(:IPV6, :PKTINFO)       #=> true
 *   ancdata.cmsg_is?(:IP, :PKTINFO)         #=> false
 *   ancdata.cmsg_is?(:SOCKET, :RIGHTS)      #=> false
 */
static VALUE
ancillary_cmsg_is_p(VALUE self, VALUE vlevel, VALUE vtype)
{
    int family = ancillary_family(self);
    int level = rsock_level_arg(family, vlevel);
    int type = rsock_cmsg_type_arg(family, level, vtype);

    if (ancillary_level(self) == level &&
        ancillary_type(self) == type)
        return Qtrue;
    else
        return Qfalse;
}

#endif

#if defined(HAVE_SENDMSG)
struct sendmsg_args_struct {
    int fd;
    int flags;
    const struct msghdr *msg;
};

static void *
nogvl_sendmsg_func(void *ptr)
{
    struct sendmsg_args_struct *args = ptr;
    return (void *)(VALUE)sendmsg(args->fd, args->msg, args->flags);
}

static ssize_t
rb_sendmsg(int fd, const struct msghdr *msg, int flags)
{
    struct sendmsg_args_struct args;
    args.fd = fd;
    args.msg = msg;
    args.flags = flags;
    return (ssize_t)rb_thread_call_without_gvl(nogvl_sendmsg_func, &args, RUBY_UBF_IO, 0);
}

static VALUE
bsock_sendmsg_internal(VALUE sock, VALUE data, VALUE vflags,
		       VALUE dest_sockaddr, VALUE controls, VALUE ex,
		       int nonblock)
{
    rb_io_t *fptr;
    struct msghdr mh;
    struct iovec iov;
    VALUE tmp;
    int controls_num;
#if defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL)
    VALUE controls_str = 0;
    int family;
#endif
    int flags;
    ssize_t ss;

    GetOpenFile(sock, fptr);
#if defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL)
    family = rsock_getfamily(fptr);
#endif

    StringValue(data);
    tmp = rb_str_tmp_frozen_acquire(data);

    if (!RB_TYPE_P(controls, T_ARRAY)) {
	controls = rb_ary_new();
    }
    controls_num = RARRAY_LENINT(controls);

    if (controls_num) {
#if defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL)
	int i;
	size_t last_pad = 0;
	const VALUE *controls_ptr = RARRAY_CONST_PTR(controls);
#if defined(__NetBSD__)
        int last_level = 0;
        int last_type = 0;
#endif
        controls_str = rb_str_tmp_new(0);
        for (i = 0; i < controls_num; i++) {
            VALUE elt = controls_ptr[i], v;
            VALUE vlevel, vtype;
            int level, type;
            VALUE cdata;
            long oldlen;
            struct cmsghdr cmh;
            char *cmsg;
            size_t cspace;
            v = rb_check_convert_type(elt, T_ARRAY, "Array", "to_ary");
            if (!NIL_P(v)) {
                elt = v;
                if (RARRAY_LEN(elt) != 3)
                    rb_raise(rb_eArgError, "an element of controls should be 3-elements array");
                vlevel = rb_ary_entry(elt, 0);
                vtype = rb_ary_entry(elt, 1);
                cdata = rb_ary_entry(elt, 2);
            }
            else {
                vlevel = rb_funcall(elt, rb_intern("level"), 0);
                vtype = rb_funcall(elt, rb_intern("type"), 0);
                cdata = rb_funcall(elt, rb_intern("data"), 0);
            }
            level = rsock_level_arg(family, vlevel);
            type = rsock_cmsg_type_arg(family, level, vtype);
            StringValue(cdata);
            oldlen = RSTRING_LEN(controls_str);
            cspace = CMSG_SPACE(RSTRING_LEN(cdata));
            rb_str_resize(controls_str, oldlen + cspace);
            cmsg = RSTRING_PTR(controls_str)+oldlen;
            memset((char *)cmsg, 0, cspace);
            memset((char *)&cmh, 0, sizeof(cmh));
            cmh.cmsg_level = level;
            cmh.cmsg_type = type;
            cmh.cmsg_len = (socklen_t)CMSG_LEN(RSTRING_LEN(cdata));
            MEMCPY(cmsg, &cmh, char, sizeof(cmh));
            MEMCPY(cmsg+((char*)CMSG_DATA(&cmh)-(char*)&cmh), RSTRING_PTR(cdata), char, RSTRING_LEN(cdata));
#if defined(__NetBSD__)
            last_level = cmh.cmsg_level;
            last_type = cmh.cmsg_type;
#endif
	    last_pad = cspace - cmh.cmsg_len;
        }
	if (last_pad) {
            /*
             * This code removes the last padding from msg_controllen.
             *
             * 4.3BSD-Reno reject the padding for SCM_RIGHTS. (There was no 64bit environments in those days?)
             * RFC 2292 require the padding.
             * RFC 3542 relaxes the condition - implementation must accept both as valid.
             *
             * Actual problems:
             *
             * - NetBSD 4.0.1
             *   SCM_RIGHTS with padding causes EINVAL
             *   IPV6_PKTINFO without padding causes "page fault trap"
             *     http://www.netbsd.org/cgi-bin/query-pr-single.pl?number=40661
             *
             * - OpenBSD 4.4
             *   IPV6_PKTINFO without padding causes EINVAL
             *
             * Basically, msg_controllen should contains the padding.
             * So the padding is removed only if a problem really exists.
             */
#if defined(__NetBSD__)
            if (last_level == SOL_SOCKET && last_type == SCM_RIGHTS)
                rb_str_set_len(controls_str, RSTRING_LEN(controls_str)-last_pad);
#endif
	}
	RB_GC_GUARD(controls);
#else
	rb_raise(rb_eNotImpError, "control message for sendmsg is unimplemented");
#endif
    }

    flags = NIL_P(vflags) ? 0 : NUM2INT(vflags);
#ifdef MSG_DONTWAIT
    if (nonblock)
        flags |= MSG_DONTWAIT;
#endif

    if (!NIL_P(dest_sockaddr))
	SockAddrStringValue(dest_sockaddr);

    rb_io_check_closed(fptr);

  retry:
    memset(&mh, 0, sizeof(mh));
    if (!NIL_P(dest_sockaddr)) {
        mh.msg_name = RSTRING_PTR(dest_sockaddr);
        mh.msg_namelen = RSTRING_SOCKLEN(dest_sockaddr);
    }
    mh.msg_iovlen = 1;
    mh.msg_iov = &iov;
    iov.iov_base = RSTRING_PTR(tmp);
    iov.iov_len = RSTRING_LEN(tmp);
#if defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL)
    if (controls_str) {
        mh.msg_control = RSTRING_PTR(controls_str);
        mh.msg_controllen = RSTRING_SOCKLEN(controls_str);
    }
#endif

    rb_io_check_closed(fptr);
    if (nonblock && !MSG_DONTWAIT_RELIABLE)
        rb_io_set_nonblock(fptr);

    ss = rb_sendmsg(fptr->fd, &mh, flags);

    if (ss == -1) {
	int e;
        if (!nonblock && rb_io_wait_writable(fptr->fd)) {
            rb_io_check_closed(fptr);
            goto retry;
        }
	e = errno;
	if (nonblock && (e == EWOULDBLOCK || e == EAGAIN)) {
	    if (ex == Qfalse) {
		return sym_wait_writable;
	    }
	    rb_readwrite_syserr_fail(RB_IO_WAIT_WRITABLE, e,
				     "sendmsg(2) would block");
	}
	rb_syserr_fail(e, "sendmsg(2)");
    }
#if defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL)
    RB_GC_GUARD(controls_str);
#endif
    rb_str_tmp_frozen_release(data, tmp);

    return SSIZET2NUM(ss);
}
#endif

#if defined(HAVE_SENDMSG)
VALUE
rsock_bsock_sendmsg(VALUE sock, VALUE data, VALUE flags, VALUE dest_sockaddr,
		    VALUE controls)
{
    return bsock_sendmsg_internal(sock, data, flags, dest_sockaddr, controls,
				  Qtrue, 0);
}
#endif

#if defined(HAVE_SENDMSG)
VALUE
rsock_bsock_sendmsg_nonblock(VALUE sock, VALUE data, VALUE flags,
			     VALUE dest_sockaddr, VALUE controls, VALUE ex)
{
    return bsock_sendmsg_internal(sock, data, flags, dest_sockaddr,
				  controls, ex, 1);
}
#endif

#if defined(HAVE_RECVMSG)
struct recvmsg_args_struct {
    int fd;
    int flags;
    struct msghdr *msg;
};

ssize_t
rsock_recvmsg(int socket, struct msghdr *message, int flags)
{
    ssize_t ret;
    socklen_t len0;
#ifdef MSG_CMSG_CLOEXEC
    /* MSG_CMSG_CLOEXEC is available since Linux 2.6.23.  Linux 2.6.18 silently ignore it. */
    flags |= MSG_CMSG_CLOEXEC;
#endif
    len0 = message->msg_namelen;
    ret = recvmsg(socket, message, flags);
    if (ret != -1 && len0 < message->msg_namelen)
        message->msg_namelen = len0;
    return ret;
}

static void *
nogvl_recvmsg_func(void *ptr)
{
    struct recvmsg_args_struct *args = ptr;
    int flags = args->flags;
    return (void *)rsock_recvmsg(args->fd, args->msg, flags);
}

static ssize_t
rb_recvmsg(int fd, struct msghdr *msg, int flags)
{
    struct recvmsg_args_struct args;
    args.fd = fd;
    args.msg = msg;
    args.flags = flags;
    return (ssize_t)rb_thread_call_without_gvl(nogvl_recvmsg_func, &args, RUBY_UBF_IO, 0);
}

#if defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL)
static void
discard_cmsg(struct cmsghdr *cmh, char *msg_end, int msg_peek_p)
{
# if !defined(FD_PASSING_WORK_WITH_RECVMSG_MSG_PEEK)
    /*
     * FreeBSD 8.2.0, NetBSD 5 and MacOS X Snow Leopard doesn't
     * allocate fds by recvmsg with MSG_PEEK.
     * [ruby-dev:44189]
     * http://bugs.ruby-lang.org/issues/5075
     *
     * Linux 2.6.38 allocate fds by recvmsg with MSG_PEEK.
     */
    if (msg_peek_p)
        return;
# endif
    if (cmh->cmsg_level == SOL_SOCKET && cmh->cmsg_type == SCM_RIGHTS) {
        int *fdp = (int *)CMSG_DATA(cmh);
        int *end = (int *)((char *)cmh + cmh->cmsg_len);
        while ((char *)fdp + sizeof(int) <= (char *)end &&
               (char *)fdp + sizeof(int) <= msg_end) {
            rb_update_max_fd(*fdp);
            close(*fdp);
            fdp++;
        }
    }
}
#endif

void
rsock_discard_cmsg_resource(struct msghdr *mh, int msg_peek_p)
{
#if defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL)
    struct cmsghdr *cmh;
    char *msg_end;

    if (mh->msg_controllen == 0)
        return;

    msg_end = (char *)mh->msg_control + mh->msg_controllen;

    for (cmh = CMSG_FIRSTHDR(mh); cmh != NULL; cmh = CMSG_NXTHDR(mh, cmh)) {
        discard_cmsg(cmh, msg_end, msg_peek_p);
    }
#endif
}

#if defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL)
static void
make_io_for_unix_rights(VALUE ctl, struct cmsghdr *cmh, char *msg_end)
{
    if (cmh->cmsg_level == SOL_SOCKET && cmh->cmsg_type == SCM_RIGHTS) {
        int *fdp, *end;
	VALUE ary = rb_ary_new();
	rb_ivar_set(ctl, rb_intern("unix_rights"), ary);
        fdp = (int *)CMSG_DATA(cmh);
        end = (int *)((char *)cmh + cmh->cmsg_len);
        while ((char *)fdp + sizeof(int) <= (char *)end &&
	       (char *)fdp + sizeof(int) <= msg_end) {
            int fd = *fdp;
            struct stat stbuf;
            VALUE io;
            if (fstat(fd, &stbuf) == -1)
                rb_raise(rb_eSocket, "invalid fd in SCM_RIGHTS");
            rb_update_max_fd(fd);
            rb_maygvl_fd_fix_cloexec(fd);
            if (S_ISSOCK(stbuf.st_mode))
                io = rsock_init_sock(rb_obj_alloc(rb_cSocket), fd);
            else
                io = rb_io_fdopen(fd, O_RDWR, NULL);
            ary = rb_attr_get(ctl, rb_intern("unix_rights"));
            rb_ary_push(ary, io);
            fdp++;
        }
	OBJ_FREEZE(ary);
    }
}
#endif

static VALUE
bsock_recvmsg_internal(VALUE sock,
		VALUE vmaxdatlen, VALUE vflags, VALUE vmaxctllen,
		VALUE scm_rights, VALUE ex, int nonblock)
{
    rb_io_t *fptr;
    int grow_buffer;
    size_t maxdatlen;
    int flags, orig_flags;
    struct msghdr mh;
    struct iovec iov;
    union_sockaddr namebuf;
    char *datbuf;
    VALUE dat_str = Qnil;
    VALUE ret;
    ssize_t ss;
    int request_scm_rights;
#if defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL)
    struct cmsghdr *cmh;
    size_t maxctllen;
    char *ctlbuf;
    VALUE ctl_str = Qnil;
    int family;
    int gc_done = 0;
#endif

    maxdatlen = NIL_P(vmaxdatlen) ? 4096 : NUM2SIZET(vmaxdatlen);
#if defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL)
    maxctllen = NIL_P(vmaxctllen) ? 4096 : NUM2SIZET(vmaxctllen);
#else
    if (!NIL_P(vmaxctllen))
        rb_raise(rb_eArgError, "control message not supported");
#endif
    flags = NUM2INT(vflags);
#ifdef MSG_DONTWAIT
    if (nonblock)
        flags |= MSG_DONTWAIT;
#endif
    orig_flags = flags;

    grow_buffer = NIL_P(vmaxdatlen) || NIL_P(vmaxctllen);

    request_scm_rights = 0;
    if (RTEST(scm_rights))
        request_scm_rights = 1;
#if !defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL)
    if (request_scm_rights)
        rb_raise(rb_eNotImpError, "control message for recvmsg is unimplemented");
#endif

    GetOpenFile(sock, fptr);
    if (rb_io_read_pending(fptr)) {
        rb_raise(rb_eIOError, "recvmsg for buffered IO");
    }

#if !defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL)
    if (grow_buffer) {
	int socktype;
	socklen_t optlen = (socklen_t)sizeof(socktype);
        if (getsockopt(fptr->fd, SOL_SOCKET, SO_TYPE, (void*)&socktype, &optlen) == -1) {
	    rb_sys_fail("getsockopt(SO_TYPE)");
	}
	if (socktype == SOCK_STREAM)
	    grow_buffer = 0;
    }
#endif

  retry:
    if (NIL_P(dat_str))
	dat_str = rb_str_tmp_new(maxdatlen);
    else
	rb_str_resize(dat_str, maxdatlen);
    datbuf = RSTRING_PTR(dat_str);

#if defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL)
    if (NIL_P(ctl_str))
	ctl_str = rb_str_tmp_new(maxctllen);
    else
	rb_str_resize(ctl_str, maxctllen);
    ctlbuf = RSTRING_PTR(ctl_str);
#endif

    memset(&mh, 0, sizeof(mh));

    memset(&namebuf, 0, sizeof(namebuf));
    mh.msg_name = &namebuf.addr;
    mh.msg_namelen = (socklen_t)sizeof(namebuf);

    mh.msg_iov = &iov;
    mh.msg_iovlen = 1;
    iov.iov_base = datbuf;
    iov.iov_len = maxdatlen;

#if defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL)
    mh.msg_control = ctlbuf;
    mh.msg_controllen = (socklen_t)maxctllen;
#endif

    if (grow_buffer)
        flags |= MSG_PEEK;

    rb_io_check_closed(fptr);
    if (nonblock && !MSG_DONTWAIT_RELIABLE)
        rb_io_set_nonblock(fptr);

    ss = rb_recvmsg(fptr->fd, &mh, flags);

    if (ss == -1) {
	int e;
        if (!nonblock && rb_io_wait_readable(fptr->fd)) {
            rb_io_check_closed(fptr);
            goto retry;
        }
	e = errno;
	if (nonblock && (e == EWOULDBLOCK || e == EAGAIN)) {
            if (ex == Qfalse) {
                return sym_wait_readable;
            }
	    rb_readwrite_syserr_fail(RB_IO_WAIT_READABLE, e, "recvmsg(2) would block");
        }
#if defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL)
	if (!gc_done && (e == EMFILE || e == EMSGSIZE)) {
          /*
           * When SCM_RIGHTS hit the file descriptors limit:
           * - Linux 2.6.18 causes success with MSG_CTRUNC
           * - MacOS X 10.4 causes EMSGSIZE (and lost file descriptors?)
           * - Solaris 11 causes EMFILE
           */
          gc_and_retry:
            rb_gc();
            gc_done = 1;
	    goto retry;
        }
#else
	if (NIL_P(vmaxdatlen) && grow_buffer && e == EMSGSIZE)
	    ss = (ssize_t)iov.iov_len;
	else
#endif
	rb_syserr_fail(e, "recvmsg(2)");
    }

    if (grow_buffer) {
	int grown = 0;
	if (NIL_P(vmaxdatlen) && ss != -1 && ss == (ssize_t)iov.iov_len) {
            if (SIZE_MAX/2 < maxdatlen)
                rb_raise(rb_eArgError, "max data length too big");
	    maxdatlen *= 2;
	    grown = 1;
	}
#if defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL)
        if (NIL_P(vmaxctllen) && (mh.msg_flags & MSG_CTRUNC)) {
#define BIG_ENOUGH_SPACE 65536
            if (BIG_ENOUGH_SPACE < maxctllen &&
                (socklen_t)mh.msg_controllen < (socklen_t)(maxctllen - BIG_ENOUGH_SPACE)) {
                /* there are big space bug truncated.
                 * file descriptors limit? */
                if (!gc_done) {
		    rsock_discard_cmsg_resource(&mh, (flags & MSG_PEEK) != 0);
                    goto gc_and_retry;
		}
            }
            else {
                if (SIZE_MAX/2 < maxctllen)
                    rb_raise(rb_eArgError, "max control message length too big");
                maxctllen *= 2;
                grown = 1;
            }
#undef BIG_ENOUGH_SPACE
	}
#endif
	if (grown) {
            rsock_discard_cmsg_resource(&mh, (flags & MSG_PEEK) != 0);
	    goto retry;
	}
	else {
            grow_buffer = 0;
            if (flags != orig_flags) {
                rsock_discard_cmsg_resource(&mh, (flags & MSG_PEEK) != 0);
                flags = orig_flags;
                goto retry;
            }
        }
    }

    if (NIL_P(dat_str))
        dat_str = rb_str_new(datbuf, ss);
    else {
        rb_str_resize(dat_str, ss);
	rb_obj_reveal(dat_str, rb_cString);
    }

    ret = rb_ary_new3(3, dat_str,
                         rsock_io_socket_addrinfo(sock, mh.msg_name, mh.msg_namelen),
#if defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL)
			 INT2NUM(mh.msg_flags)
#else
			 Qnil
#endif
			 );

#if defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL)
    family = rsock_getfamily(fptr);
    if (mh.msg_controllen) {
	char *msg_end = (char *)mh.msg_control + mh.msg_controllen;
        for (cmh = CMSG_FIRSTHDR(&mh); cmh != NULL; cmh = CMSG_NXTHDR(&mh, cmh)) {
            VALUE ctl;
	    char *ctl_end;
            size_t clen;
            if (cmh->cmsg_len == 0) {
                rb_raise(rb_eTypeError, "invalid control message (cmsg_len == 0)");
            }
            ctl_end = (char*)cmh + cmh->cmsg_len;
	    clen = (ctl_end <= msg_end ? ctl_end : msg_end) - (char*)CMSG_DATA(cmh);
            ctl = ancdata_new(family, cmh->cmsg_level, cmh->cmsg_type, rb_str_new((char*)CMSG_DATA(cmh), clen));
            if (request_scm_rights)
                make_io_for_unix_rights(ctl, cmh, msg_end);
            else
                discard_cmsg(cmh, msg_end, (flags & MSG_PEEK) != 0);
            rb_ary_push(ret, ctl);
        }
        RB_GC_GUARD(ctl_str);
    }
#endif

    return ret;
}
#endif

#if defined(HAVE_RECVMSG)
VALUE
rsock_bsock_recvmsg(VALUE sock, VALUE dlen, VALUE flags, VALUE clen,
		    VALUE scm_rights)
{
    VALUE ex = Qtrue;
    return bsock_recvmsg_internal(sock, dlen, flags, clen, scm_rights, ex, 0);
}
#endif

#if defined(HAVE_RECVMSG)
VALUE
rsock_bsock_recvmsg_nonblock(VALUE sock, VALUE dlen, VALUE flags, VALUE clen,
			     VALUE scm_rights, VALUE ex)
{
    return bsock_recvmsg_internal(sock, dlen, flags, clen, scm_rights, ex, 1);
}
#endif

void
rsock_init_ancdata(void)
{
#if defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL)
    /*
     * Document-class: Socket::AncillaryData
     *
     * Socket::AncillaryData represents the ancillary data (control information)
     * used by sendmsg and recvmsg system call.  It contains socket #family,
     * control message (cmsg) #level, cmsg #type and cmsg #data.
     */
    rb_cAncillaryData = rb_define_class_under(rb_cSocket, "AncillaryData", rb_cObject);
    rb_define_method(rb_cAncillaryData, "initialize", ancillary_initialize, 4);
    rb_define_method(rb_cAncillaryData, "inspect", ancillary_inspect, 0);
    rb_define_method(rb_cAncillaryData, "family", ancillary_family_m, 0);
    rb_define_method(rb_cAncillaryData, "level", ancillary_level_m, 0);
    rb_define_method(rb_cAncillaryData, "type", ancillary_type_m, 0);
    rb_define_method(rb_cAncillaryData, "data", ancillary_data, 0);

    rb_define_method(rb_cAncillaryData, "cmsg_is?", ancillary_cmsg_is_p, 2);

    rb_define_singleton_method(rb_cAncillaryData, "int", ancillary_s_int, 4);
    rb_define_method(rb_cAncillaryData, "int", ancillary_int, 0);

    rb_define_singleton_method(rb_cAncillaryData, "unix_rights", ancillary_s_unix_rights, -1);
    rb_define_method(rb_cAncillaryData, "unix_rights", ancillary_unix_rights, 0);

    rb_define_method(rb_cAncillaryData, "timestamp", ancillary_timestamp, 0);

    rb_define_singleton_method(rb_cAncillaryData, "ip_pktinfo", ancillary_s_ip_pktinfo, -1);
    rb_define_method(rb_cAncillaryData, "ip_pktinfo", ancillary_ip_pktinfo, 0);

    rb_define_singleton_method(rb_cAncillaryData, "ipv6_pktinfo", ancillary_s_ipv6_pktinfo, 2);
    rb_define_method(rb_cAncillaryData, "ipv6_pktinfo", ancillary_ipv6_pktinfo, 0);
    rb_define_method(rb_cAncillaryData, "ipv6_pktinfo_addr", ancillary_ipv6_pktinfo_addr, 0);
    rb_define_method(rb_cAncillaryData, "ipv6_pktinfo_ifindex", ancillary_ipv6_pktinfo_ifindex, 0);
#endif
#undef rb_intern
    sym_wait_readable = ID2SYM(rb_intern("wait_readable"));
    sym_wait_writable = ID2SYM(rb_intern("wait_writable"));
}
