/*!
* Copyright (c) 2009, Jay Loden, Dave Daeschler, Giampaolo Rodola.
 * Licensed under the BSD 3-Clause License.
 * See https://github.com/giampaolo/psutil/blob/master/LICENSE
 */
#if (defined(sun) || defined(__sun)) && (defined(__SVR4) || defined(__svr4__))

#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <net/if.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/sockio.h>

#include "ifaddrs_patch.h"

#define MAX(x, y) ((x) > (y)?(x):(y))
#define SIZE(p) MAX((p).ss_len, sizeof(p))


static struct sockaddr *
sa_dup(struct sockaddr_storage *sa1) {
    struct sockaddr *sa2;
    size_t sz = sizeof(struct sockaddr_storage);
    sa2 = (struct sockaddr *) calloc(1, sz);
    memcpy(sa2, sa1, sz);
    return(sa2);
}


void freeifaddrs(struct ifaddrs *ifp) {
    if (NULL == ifp) return;
    free(ifp->ifa_name);
    free(ifp->ifa_addr);
    free(ifp->ifa_netmask);
    free(ifp->ifa_dstaddr);
    freeifaddrs(ifp->ifa_next);
    free(ifp);
}


int getifaddrs(struct ifaddrs **ifap) {
    int sd = -1;
    char *ccp, *ecp;
    struct lifconf ifc;
    struct lifreq *ifr;
    struct lifnum lifn;
    struct ifaddrs *cifa = NULL; /* current */
    struct ifaddrs *pifa = NULL; /* previous */
    const size_t IFREQSZ = sizeof(struct lifreq);

    sd = socket(AF_INET, SOCK_STREAM, 0);
    if (sd < 0)
        goto error;

    ifc.lifc_buf = NULL;
    *ifap = NULL;
    /* find how much memory to allocate for the SIOCGLIFCONF call */
    lifn.lifn_family = AF_UNSPEC;
    lifn.lifn_flags = 0;
    if (ioctl(sd, SIOCGLIFNUM, &lifn) < 0)
        goto error;

    /* Sun and Apple code likes to pad the interface count here in case interfaces
     * are coming up between calls */
    lifn.lifn_count += 4;

    ifc.lifc_family = AF_UNSPEC;
    ifc.lifc_len = lifn.lifn_count * sizeof(struct lifreq);
    ifc.lifc_buf = static_cast<char*>(calloc(1, ifc.lifc_len));
    if (ioctl(sd, SIOCGLIFCONF, &ifc) < 0)
        goto error;

    ccp = reinterpret_cast<char *>(ifc.lifc_req);
    ecp = ccp + ifc.lifc_len;

    while (ccp < ecp) {
        ifr = (struct lifreq *) ccp;
        cifa = (struct ifaddrs *) calloc(1, sizeof(struct ifaddrs));
        cifa->ifa_next = NULL;
        cifa->ifa_name = strdup(ifr->lifr_name);

        if (pifa == NULL) {
            *ifap = cifa; /* first one */
        } else {
            pifa->ifa_next = cifa;
        }

        if (ioctl(sd, SIOCGLIFADDR, ifr, IFREQSZ) < 0)
            goto error;
        cifa->ifa_addr = sa_dup(&ifr->lifr_addr);

        if (ioctl(sd, SIOCGLIFNETMASK, ifr, IFREQSZ) < 0)
            goto error;
        cifa->ifa_netmask = sa_dup(&ifr->lifr_addr);

        cifa->ifa_flags = 0;
        cifa->ifa_dstaddr = NULL;

        if (0 == ioctl(sd, SIOCGLIFFLAGS, ifr)) /* optional */
            cifa->ifa_flags = ifr->lifr_flags;

        if (ioctl(sd, SIOCGLIFDSTADDR, ifr, IFREQSZ) < 0) {
            if (0 == ioctl(sd, SIOCGLIFBRDADDR, ifr, IFREQSZ))
                cifa->ifa_dstaddr = sa_dup(&ifr->lifr_addr);
        } else {
            cifa->ifa_dstaddr = sa_dup(&ifr->lifr_addr);
        }

        pifa = cifa;
        ccp += IFREQSZ;
    }
    free(ifc.lifc_buf);
    close(sd);
    return 0;
error:
    if (ifc.lifc_buf != NULL)
        free(ifc.lifc_buf);
    if (sd != -1)
        close(sd);
    freeifaddrs(*ifap);
    return (-1);
}

#endif  // (defined(sun) || defined(__sun)) && (defined(__SVR4) || defined(__svr4__))
