#include "netcfg.h"

#include <net/if.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>

#include <debian-installer.h>

static char *get_vlan_command(const char *parentif, const char *vlanif, int vlanid)
{
#if defined(__linux__)
    const char *vlan_command = "ip link add link %s name %s type vlan id %d";
    int len = strlen(vlan_command) + strlen(parentif) + strlen(vlanif) + 4 + 1;
    char *buf = malloc(len);
    snprintf(buf, len, vlan_command, parentif, vlanif, vlanid);
#elif defined(__FreeBSD_kernel__)
    const char *vlan_command = "ifconfig %s vlan %d vlandev %s";
    int len = strlen(vlan_command) + strlen(parentif) + strlen(vlanif) + 4 + 1;
    char *buf = malloc(len);
    snprintf(buf, len, vlan_command, vlanif, vlanid, parentif);
#endif
    return buf;
}

/* Create a new VLAN interface attached to the currently selected
 * network interface.
 */
int netcfg_set_vlan(struct debconfclient *client, struct netcfg_interface *interface)
{
#if defined(__linux__) || defined(__FreeBSD_kernel__)
    int vlanid;

    debconf_get(client, "netcfg/vlan_id");
    /* Empty string: no VLAN preseeded, ask if we should configure VLAN */
    if (strlen(client->value) == 0) {
        debconf_input (client, "medium", "netcfg/use_vlan");
        if (debconf_go(client) == GO_BACK) {
            return GO_BACK;
        }

        debconf_get(client, "netcfg/use_vlan");

        if (!strcmp(client->value, "false")) {
            return 0;
        }

        debconf_input(client, "critical", "netcfg/vlan_id");
        if (debconf_go(client) == GO_BACK) {
            return GO_BACK;
        }
        debconf_get(client, "netcfg/vlan_id");
    }

    for (;;) {
        vlanid = strtol(client->value, NULL, 10);
        /* Valid range: 1-4094 (0 and 4095 are reserved.)
         * 0 will be returned by strtol if the value cannot be parsed.
         */
        if (vlanid < 1 || vlanid > 4094) {
            di_error("VLAN ID \"%s\" is invalid.", client->value);
            debconf_subst(client, "netcfg/vlan_id_invalid", "vlan_id", client->value);
            debconf_input(client, "critical", "netcfg/invalid_vlan");
            debconf_capb(client);
            debconf_go(client);

            debconf_capb(client, "backup");
            debconf_input(client, "critical", "netcfg/vlan_id");
            if (debconf_go(client) == GO_BACK) {
                return GO_BACK;
            }
        }
        else {
            break;
        }
    }

    int vlaniflen = strlen(interface->name) + 1 + 4 + 1;
    char *vlanif = malloc(vlaniflen);
    snprintf(vlanif, vlaniflen, "%s.%d", interface->name, vlanid);

    /* Check if VLAN interface already exists; skip setting it up if so */
    struct ifreq ifr;
    strncpy(ifr.ifr_name, vlanif, IFNAMSIZ-1);
    ifr.ifr_name[IFNAMSIZ-1] = '\0';

    di_info("Check if VLAN interface %s already exists...", vlanif);

    if (skfd && ioctl(skfd, SIOCGIFFLAGS, &ifr) >= 0) {
        di_info("VLAN interface %s already exists, skipping creation", vlanif);
    }
    else {
        char *vlan_command = get_vlan_command(interface->name, vlanif, vlanid);
        int rc = di_exec_shell_log(vlan_command);
        if (rc != 0) {
            di_error("\"%s\" failed to create VLAN interface; return code = %d", vlan_command, rc);
            free(vlan_command);
            debconf_input(client, "critical", "netcfg/vlan_failed");
            debconf_go(client);
            return GO_BACK;
        }
        free(vlan_command);
    }

    interface->parentif = interface->name;
    interface->name = vlanif;
    interface->vlanid = vlanid;
#else
    /* This platform does not support VLANs. */
    debconf_get(client, "netcfg/vlan_id");
    if (strlen(client->value) > 0) {
        di_error("netcfg/vlan_id specified, yet VLAN is not supported on this platform");
    }
#endif
    return 0;
}
