/*
 * virsh-nwfilter.c: Commands to manage network filters
 *
 * Copyright (C) 2005, 2007-2016 Red Hat, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.  If not, see
 * <http://www.gnu.org/licenses/>.
 *
 *  Daniel Veillard <veillard@redhat.com>
 *  Karel Zak <kzak@redhat.com>
 *  Daniel P. Berrange <berrange@redhat.com>
 *
 */

#include <config.h>
#include "virsh-nwfilter.h"

#include "internal.h"
#include "virbuffer.h"
#include "viralloc.h"
#include "virfile.h"
#include "virutil.h"

virNWFilterPtr
virshCommandOptNWFilterBy(vshControl *ctl, const vshCmd *cmd,
                          const char **name, unsigned int flags)
{
    virNWFilterPtr nwfilter = NULL;
    const char *n = NULL;
    const char *optname = "nwfilter";
    virshControlPtr priv = ctl->privData;

    virCheckFlags(VIRSH_BYUUID | VIRSH_BYNAME, NULL);

    if (vshCommandOptStringReq(ctl, cmd, optname, &n) < 0)
        return NULL;

    vshDebug(ctl, VSH_ERR_INFO, "%s: found option <%s>: %s\n",
             cmd->def->name, optname, n);

    if (name)
        *name = n;

    /* try it by UUID */
    if ((flags & VIRSH_BYUUID) && strlen(n) == VIR_UUID_STRING_BUFLEN-1) {
        vshDebug(ctl, VSH_ERR_DEBUG, "%s: <%s> trying as nwfilter UUID\n",
                 cmd->def->name, optname);
        nwfilter = virNWFilterLookupByUUIDString(priv->conn, n);
    }
    /* try it by NAME */
    if (!nwfilter && (flags & VIRSH_BYNAME)) {
        vshDebug(ctl, VSH_ERR_DEBUG, "%s: <%s> trying as nwfilter NAME\n",
                 cmd->def->name, optname);
        nwfilter = virNWFilterLookupByName(priv->conn, n);
    }

    if (!nwfilter)
        vshError(ctl, _("failed to get nwfilter '%s'"), n);

    return nwfilter;
}

/*
 * "nwfilter-define" command
 */
static const vshCmdInfo info_nwfilter_define[] = {
    {.name = "help",
     .data = N_("define or update a network filter from an XML file")
    },
    {.name = "desc",
     .data = N_("Define a new network filter or update an existing one.")
    },
    {.name = NULL}
};

static const vshCmdOptDef opts_nwfilter_define[] = {
    VIRSH_COMMON_OPT_FILE(N_("file containing an XML network "
                             "filter description")),
    {.name = NULL}
};

static bool
cmdNWFilterDefine(vshControl *ctl, const vshCmd *cmd)
{
    virNWFilterPtr nwfilter;
    const char *from = NULL;
    bool ret = true;
    char *buffer;
    virshControlPtr priv = ctl->privData;

    if (vshCommandOptStringReq(ctl, cmd, "file", &from) < 0)
        return false;

    if (virFileReadAll(from, VSH_MAX_XML_FILE, &buffer) < 0)
        return false;

    nwfilter = virNWFilterDefineXML(priv->conn, buffer);
    VIR_FREE(buffer);

    if (nwfilter != NULL) {
        vshPrintExtra(ctl, _("Network filter %s defined from %s\n"),
                      virNWFilterGetName(nwfilter), from);
        virNWFilterFree(nwfilter);
    } else {
        vshError(ctl, _("Failed to define network filter from %s"), from);
        ret = false;
    }
    return ret;
}

/*
 * "nwfilter-undefine" command
 */
static const vshCmdInfo info_nwfilter_undefine[] = {
    {.name = "help",
     .data = N_("undefine a network filter")
    },
    {.name = "desc",
     .data = N_("Undefine a given network filter.")
    },
    {.name = NULL}
};

static const vshCmdOptDef opts_nwfilter_undefine[] = {
    {.name = "nwfilter",
     .type = VSH_OT_DATA,
     .flags = VSH_OFLAG_REQ,
     .help = N_("network filter name or uuid")
    },
    {.name = NULL}
};

static bool
cmdNWFilterUndefine(vshControl *ctl, const vshCmd *cmd)
{
    virNWFilterPtr nwfilter;
    bool ret = true;
    const char *name;

    if (!(nwfilter = virshCommandOptNWFilter(ctl, cmd, &name)))
        return false;

    if (virNWFilterUndefine(nwfilter) == 0) {
        vshPrintExtra(ctl, _("Network filter %s undefined\n"), name);
    } else {
        vshError(ctl, _("Failed to undefine network filter %s"), name);
        ret = false;
    }

    virNWFilterFree(nwfilter);
    return ret;
}

/*
 * "nwfilter-dumpxml" command
 */
static const vshCmdInfo info_nwfilter_dumpxml[] = {
    {.name = "help",
     .data = N_("network filter information in XML")
    },
    {.name = "desc",
     .data = N_("Output the network filter information as an XML dump to stdout.")
    },
    {.name = NULL}
};

static const vshCmdOptDef opts_nwfilter_dumpxml[] = {
    {.name = "nwfilter",
     .type = VSH_OT_DATA,
     .flags = VSH_OFLAG_REQ,
     .help = N_("network filter name or uuid")
    },
    {.name = NULL}
};

static bool
cmdNWFilterDumpXML(vshControl *ctl, const vshCmd *cmd)
{
    virNWFilterPtr nwfilter;
    bool ret = true;
    char *dump;

    if (!(nwfilter = virshCommandOptNWFilter(ctl, cmd, NULL)))
        return false;

    dump = virNWFilterGetXMLDesc(nwfilter, 0);
    if (dump != NULL) {
        vshPrint(ctl, "%s", dump);
        VIR_FREE(dump);
    } else {
        ret = false;
    }

    virNWFilterFree(nwfilter);
    return ret;
}

static int
virshNWFilterSorter(const void *a, const void *b)
{
    virNWFilterPtr *fa = (virNWFilterPtr *) a;
    virNWFilterPtr *fb = (virNWFilterPtr *) b;

    if (*fa && !*fb)
        return -1;

    if (!*fa)
        return *fb != NULL;

    return vshStrcasecmp(virNWFilterGetName(*fa),
                         virNWFilterGetName(*fb));
}

struct virshNWFilterList {
    virNWFilterPtr *filters;
    size_t nfilters;
};
typedef struct virshNWFilterList *virshNWFilterListPtr;

static void
virshNWFilterListFree(virshNWFilterListPtr list)
{
    size_t i;

    if (list && list->filters) {
        for (i = 0; i < list->nfilters; i++) {
            if (list->filters[i])
                virNWFilterFree(list->filters[i]);
        }
        VIR_FREE(list->filters);
    }
    VIR_FREE(list);
}

static virshNWFilterListPtr
virshNWFilterListCollect(vshControl *ctl,
                         unsigned int flags)
{
    virshNWFilterListPtr list = vshMalloc(ctl, sizeof(*list));
    size_t i;
    int ret;
    virNWFilterPtr filter;
    bool success = false;
    size_t deleted = 0;
    int nfilters = 0;
    char **names = NULL;
    virshControlPtr priv = ctl->privData;

    /* try the list with flags support (0.10.2 and later) */
    if ((ret = virConnectListAllNWFilters(priv->conn,
                                          &list->filters,
                                          flags)) >= 0) {
        list->nfilters = ret;
        goto finished;
    }

    /* check if the command is actually supported */
    if (last_error && last_error->code == VIR_ERR_NO_SUPPORT) {
        vshResetLibvirtError();
        goto fallback;
    }

    /* there was an error during the call */
    vshError(ctl, "%s", _("Failed to list node filters"));
    goto cleanup;


 fallback:
    /* fall back to old method (0.9.13 and older) */
    vshResetLibvirtError();

    nfilters = virConnectNumOfNWFilters(priv->conn);
    if (nfilters < 0) {
        vshError(ctl, "%s", _("Failed to count network filters"));
        goto cleanup;
    }

    if (nfilters == 0)
        return list;

    names = vshMalloc(ctl, sizeof(char *) * nfilters);

    nfilters = virConnectListNWFilters(priv->conn, names, nfilters);
    if (nfilters < 0) {
        vshError(ctl, "%s", _("Failed to list network filters"));
        goto cleanup;
    }

    list->filters = vshMalloc(ctl, sizeof(virNWFilterPtr) * nfilters);
    list->nfilters = 0;

    /* get the network filters */
    for (i = 0; i < nfilters; i++) {
        if (!(filter = virNWFilterLookupByName(priv->conn, names[i])))
            continue;
        list->filters[list->nfilters++] = filter;
    }

    /* truncate network filters that weren't found */
    deleted = nfilters - list->nfilters;

 finished:
    /* sort the list */
    if (list->filters && list->nfilters)
        qsort(list->filters, list->nfilters,
              sizeof(*list->filters), virshNWFilterSorter);

    /* truncate the list for not found filter objects */
    if (deleted)
        VIR_SHRINK_N(list->filters, list->nfilters, deleted);

    success = true;

 cleanup:
    for (i = 0; nfilters != -1 && i < nfilters; i++)
        VIR_FREE(names[i]);
    VIR_FREE(names);

    if (!success) {
        virshNWFilterListFree(list);
        list = NULL;
    }

    return list;
}

/*
 * "nwfilter-list" command
 */
static const vshCmdInfo info_nwfilter_list[] = {
    {.name = "help",
     .data = N_("list network filters")
    },
    {.name = "desc",
     .data = N_("Returns list of network filters.")
    },
    {.name = NULL}
};

static const vshCmdOptDef opts_nwfilter_list[] = {
    {.name = NULL}
};

static bool
cmdNWFilterList(vshControl *ctl, const vshCmd *cmd ATTRIBUTE_UNUSED)
{
    size_t i;
    char uuid[VIR_UUID_STRING_BUFLEN];
    virshNWFilterListPtr list = NULL;

    if (!(list = virshNWFilterListCollect(ctl, 0)))
        return false;

    vshPrintExtra(ctl, " %-36s  %-20s \n", _("UUID"), _("Name"));
    vshPrintExtra(ctl, "---------------------------------"
                       "---------------------------------\n");

    for (i = 0; i < list->nfilters; i++) {
        virNWFilterPtr nwfilter = list->filters[i];

        virNWFilterGetUUIDString(nwfilter, uuid);
        vshPrint(ctl, " %-36s  %-20s\n",
                 uuid,
                 virNWFilterGetName(nwfilter));
    }

    virshNWFilterListFree(list);
    return true;
}

/*
 * "nwfilter-edit" command
 */
static const vshCmdInfo info_nwfilter_edit[] = {
    {.name = "help",
     .data = N_("edit XML configuration for a network filter")
    },
    {.name = "desc",
     .data = N_("Edit the XML configuration for a network filter.")
    },
    {.name = NULL}
};

static const vshCmdOptDef opts_nwfilter_edit[] = {
    {.name = "nwfilter",
     .type = VSH_OT_DATA,
     .flags = VSH_OFLAG_REQ,
     .help = N_("network filter name or uuid")
    },
    {.name = NULL}
};

static bool
cmdNWFilterEdit(vshControl *ctl, const vshCmd *cmd)
{
    bool ret = false;
    virNWFilterPtr nwfilter = NULL;
    virNWFilterPtr nwfilter_edited = NULL;
    virshControlPtr priv = ctl->privData;

    nwfilter = virshCommandOptNWFilter(ctl, cmd, NULL);
    if (nwfilter == NULL)
        goto cleanup;

#define EDIT_GET_XML virNWFilterGetXMLDesc(nwfilter, 0)
#define EDIT_NOT_CHANGED                                        \
    do {                                                        \
        vshPrint(ctl, _("Network filter %s XML "                \
                        "configuration not changed.\n"),        \
                 virNWFilterGetName(nwfilter));                 \
        ret = true;                                             \
        goto edit_cleanup;                                      \
    } while (0)
#define EDIT_DEFINE \
    (nwfilter_edited = virNWFilterDefineXML(priv->conn, doc_edited))
#include "virsh-edit.c"

    vshPrint(ctl, _("Network filter %s XML configuration edited.\n"),
             virNWFilterGetName(nwfilter_edited));

    ret = true;

 cleanup:
    if (nwfilter)
        virNWFilterFree(nwfilter);
    if (nwfilter_edited)
        virNWFilterFree(nwfilter_edited);

    return ret;
}

const vshCmdDef nwfilterCmds[] = {
    {.name = "nwfilter-define",
     .handler = cmdNWFilterDefine,
     .opts = opts_nwfilter_define,
     .info = info_nwfilter_define,
     .flags = 0
    },
    {.name = "nwfilter-dumpxml",
     .handler = cmdNWFilterDumpXML,
     .opts = opts_nwfilter_dumpxml,
     .info = info_nwfilter_dumpxml,
     .flags = 0
    },
    {.name = "nwfilter-edit",
     .handler = cmdNWFilterEdit,
     .opts = opts_nwfilter_edit,
     .info = info_nwfilter_edit,
     .flags = 0
    },
    {.name = "nwfilter-list",
     .handler = cmdNWFilterList,
     .opts = opts_nwfilter_list,
     .info = info_nwfilter_list,
     .flags = 0
    },
    {.name = "nwfilter-undefine",
     .handler = cmdNWFilterUndefine,
     .opts = opts_nwfilter_undefine,
     .info = info_nwfilter_undefine,
     .flags = 0
    },
    {.name = NULL}
};
