static int _hostresolver_getaddrinfo(
    oe_resolver_t* resolver,
    const char* node,
    const char* service,
    const struct oe_addrinfo* hints,
    struct oe_addrinfo** res)
{
    int ret = OE_EAI_FAIL;
    uint64_t handle = 0;
    struct oe_addrinfo* head = NULL;
    struct oe_addrinfo* tail = NULL;
    struct oe_addrinfo* p = NULL;

    OE_UNUSED(resolver);

    if (res)
        *res = NULL;

    if (!res)
    {
        ret = OE_EAI_SYSTEM;
        OE_RAISE_ERRNO(OE_EINVAL);
    }

    /* Get the handle for enumerating addrinfo structures. */
    {
        int retval = OE_EAI_FAIL;

        if (oe_syscall_getaddrinfo_open_ocall(
                &retval, node, service, hints, &handle) != OE_OK)
        {
            ret = OE_EAI_SYSTEM;
            OE_RAISE_ERRNO(OE_EINVAL);
        }

        if (!handle)
        {
            ret = retval;
            goto done;
        }
    }

    /* Enumerate addrinfo structures. */
    for (;;)
    {
        int retval = 0;
        size_t canonnamelen_in = 0;
        size_t canonnamelen_out = 0;
        struct oe_addrinfo p_out;

        memset(&p_out, 0, sizeof(struct oe_addrinfo));

        /* Determine required size ai_addr and ai_canonname buffers. */
        if (oe_syscall_getaddrinfo_read_ocall(
                &retval,
                handle,
                &p_out.ai_flags,
                &p_out.ai_family,
                &p_out.ai_socktype,
                &p_out.ai_protocol,
                p_out.ai_addrlen,
                &p_out.ai_addrlen,
                NULL,
                canonnamelen_in,
                &canonnamelen_out,
                NULL) != OE_OK)
        {
            ret = OE_EAI_SYSTEM;
            OE_RAISE_ERRNO(OE_EINVAL);
        }

        /* If this is the final element in the enumeration. */
        if (retval == 1)
            break;

        /* Expecting that addr and canonname buffers were too small. */
        if (retval != -1 || oe_errno != OE_ENAMETOOLONG)
        {
            ret = OE_EAI_SYSTEM;
            OE_RAISE_ERRNO(oe_errno);
        }

        /*
         * Guard the special case that a host sets an arbitrarily large value.
         * Based on the implementation of MUSL, the ai_addrlen can only be
         * sizeof(struct sockaddr_in) when the family is AF_INET or
         * sizeof(struct sockaddr_in6) when the family is AF_INET6.
         * When the family is AF_UNSPEC, OE checks the ai_addrlen against
         * sizeof(struct sockaddr_in6) as it should cover AF_INET and
         * AF_INET6 cases. Besides, OE errors out other family types.
         */
        switch (p_out.ai_family)
        {
            case OE_AF_INET:
                if (p_out.ai_addrlen != sizeof(struct oe_sockaddr))
                {
                    ret = OE_EAI_FAIL;
                    goto done;
                }
                break;
            case OE_AF_INET6:
            case OE_AF_INET6_WIN:
            case OE_AF_UNSPEC:
                if (p_out.ai_addrlen != sizeof(struct oe_sockaddr_in6))
                {
                    ret = OE_EAI_FAIL;
                    goto done;
                }
                break;
            default:
                ret = OE_EAI_FAIL;
                goto done;
        }

        if (!(p = oe_calloc(1, sizeof(struct oe_addrinfo))))
        {
            ret = OE_EAI_MEMORY;
            goto done;
        }

        if (p_out.ai_addrlen && !(p->ai_addr = oe_calloc(1, p_out.ai_addrlen)))
        {
            ret = OE_EAI_MEMORY;
            goto done;
        }

        if (canonnamelen_out &&
            !(p->ai_canonname = oe_calloc(1, canonnamelen_out)))
        {
            ret = OE_EAI_MEMORY;
            goto done;
        }

        /* Set canonnamelen_in to the expected length of p->ai_cannonname
         * returned by the host. */
        canonnamelen_in = canonnamelen_out;

        if (oe_syscall_getaddrinfo_read_ocall(
                &retval,
                handle,
                &p->ai_flags,
                &p->ai_family,
                &p->ai_socktype,
                &p->ai_protocol,
                p_out.ai_addrlen,
                &p->ai_addrlen,
                p->ai_addr,
                canonnamelen_in,
                &canonnamelen_out,
                p->ai_canonname) != OE_OK)
        {
            ret = OE_EAI_SYSTEM;
            OE_RAISE_ERRNO(OE_EINVAL);
        }

        /*
         * Lock down the out parameters, which are expected
         * to be the same as the first invocation. Also,
         * p->ai_cannonname is expected to be NULL-terminated.
         */
        if ((p->ai_flags != p_out.ai_flags) ||
            (p->ai_family != p_out.ai_family) ||
            (p->ai_socktype != p_out.ai_socktype) ||
            (p->ai_protocol != p_out.ai_protocol) ||
            (p->ai_addrlen != p_out.ai_addrlen) ||
            (canonnamelen_out != canonnamelen_in) ||
            (canonnamelen_out && p->ai_canonname[canonnamelen_out - 1] != '\0'))
        {
            ret = OE_EAI_FAIL;
            goto done;
        }

        /* Append to the list. */
        if (tail)
        {
            tail->ai_next = p;
            tail = p;
        }
        else
        {
            head = p;
            tail = p;
        }

        p = NULL;
    }

    /* Close the enumeration. */
    if (handle)
    {
        int retval = -1;

        if (oe_syscall_getaddrinfo_close_ocall(&retval, handle) != OE_OK)
        {
            ret = OE_EAI_SYSTEM;
            OE_RAISE_ERRNO(OE_EINVAL);
        }

        handle = 0;

        if (retval != 0)
        {
            ret = OE_EAI_SYSTEM;
            OE_RAISE_ERRNO(oe_errno);
        }
    }

    /* If the list is empty. */
    if (!head)
    {
        ret = OE_EAI_SYSTEM;
        OE_RAISE_ERRNO(OE_EINVAL);
    }

    *res = head;
    head = NULL;
    tail = NULL;
    ret = 0;

done:

    if (handle)
    {
        int retval;
        oe_syscall_getaddrinfo_close_ocall(&retval, handle);
    }

    if (head)
        oe_freeaddrinfo(head);

    if (p)
        oe_freeaddrinfo(p);

    return ret;
}