/*
 * Copyright (C) 2011
 * Author Roger Pau Monne <roger.pau@entel.upc.edu>
 *
 * This program 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; version 2.1 only. with the special
 * exception on linking described in file LICENSE.
 *
 * This program 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.
 */

#include "libxl_osdeps.h" /* must come before any other headers */

#include <sys/resource.h>
#include "libxl_internal.h"


/* Workarounds for Linux-specific lacks can go here: */

#ifndef CLONE_NEWIPC /* Available as of Linux 2.6.19 / glibc 2.8 */
# define CLONE_NEWIPC 0x08000000
#endif


int libxl__try_phy_backend(mode_t st_mode)
{
    if (S_ISBLK(st_mode) || S_ISREG(st_mode)) {
        return 1;
    }

    return 0;
}

char *libxl__devid_to_localdev(libxl__gc *gc, int devid)
{
    return libxl__devid_to_vdev(gc, devid);
}

/* Hotplug scripts helpers */

static char **get_hotplug_env(libxl__gc *gc,
                              char *script, libxl__device *dev)
{
    const char *type = libxl__device_kind_to_string(dev->backend_kind);
    char *be_path = libxl__device_backend_path(gc, dev);
    char **env;
    int nr = 0;

    const int arraysize = 15;
    GCNEW_ARRAY(env, arraysize);
    env[nr++] = "script";
    env[nr++] = script;
    env[nr++] = "XENBUS_TYPE";
    env[nr++] = (char *) type;
    env[nr++] = "XENBUS_PATH";
    env[nr++] = GCSPRINTF("backend/%s/%u/%d", type, dev->domid, dev->devid);
    env[nr++] = "XENBUS_BASE_PATH";
    env[nr++] = "backend";
    if (dev->backend_kind == LIBXL__DEVICE_KIND_VIF) {
        libxl_nic_type nictype;
        char *gatewaydev;

        gatewaydev = libxl__xs_read(gc, XBT_NULL,
                                    GCSPRINTF("%s/%s", be_path, "gatewaydev"));
        env[nr++] = "netdev";
        env[nr++] = gatewaydev ? : "";

        if (libxl__nic_type(gc, dev, &nictype)) {
            LOGD(ERROR, dev->domid, "unable to get nictype");
            return NULL;
        }
        switch (nictype) {
        case LIBXL_NIC_TYPE_VIF_IOEMU:
            env[nr++] = "INTERFACE";
            env[nr++] = (char *) libxl__device_nic_devname(gc, dev->domid,
                                                    dev->devid,
                                                    LIBXL_NIC_TYPE_VIF_IOEMU);
            /*
             * We need to fall through because for PV_IOEMU nic types we need
             * to execute both the vif and the tap hotplug script, and we
             * don't know which one we are executing in this call, so provide
             * both env variables.
             */
        case LIBXL_NIC_TYPE_VIF:
            env[nr++] = "vif";
            env[nr++] = (char *) libxl__device_nic_devname(gc, dev->domid,
                                                    dev->devid,
                                                    LIBXL_NIC_TYPE_VIF);
            break;
        default:
            return NULL;
        }
    }

    env[nr++] = NULL;
    assert(nr <= arraysize);

    return env;
}

/* Hotplug scripts caller functions */

static int libxl__hotplug_nic(libxl__gc *gc, libxl__device *dev,
                               char ***args, char ***env,
                               libxl__device_action action, int num_exec)
{
    char *be_path = libxl__device_backend_path(gc, dev);
    char *script;
    int nr = 0, rc = 0;
    libxl_nic_type nictype;

    script = libxl__xs_read(gc, XBT_NULL, GCSPRINTF("%s/%s", be_path,
                                                             "script"));
    if (!script) {
        LOGED(ERROR, dev->domid,
              "unable to read script from %s", be_path);
        rc = ERROR_FAIL;
        goto out;
    }

    rc = libxl__nic_type(gc, dev, &nictype);
    if (rc) {
        LOGD(ERROR, dev->domid, "error when fetching nic type");
        rc = ERROR_FAIL;
        goto out;
    }
    if (nictype == LIBXL_NIC_TYPE_VIF && num_exec != 0) {
        rc = 0;
        goto out;
    }

    *env = get_hotplug_env(gc, script, dev);
    if (!*env) {
        rc = ERROR_FAIL;
        goto out;
    }

    const int arraysize = 4;
    GCNEW_ARRAY(*args, arraysize);
    (*args)[nr++] = script;

    if (nictype == LIBXL_NIC_TYPE_VIF_IOEMU && num_exec) {
        (*args)[nr++] = (char *) libxl__device_action_to_string(action);
        (*args)[nr++] = "type_if=tap";
        (*args)[nr++] = NULL;
    } else {
        (*args)[nr++] = action == LIBXL__DEVICE_ACTION_ADD ? "online" :
                                                             "offline";
        (*args)[nr++] = "type_if=vif";
        (*args)[nr++] = NULL;
    }
    assert(nr == arraysize);
    rc = 1;

out:
    return rc;
}

static int libxl__hotplug_disk(libxl__gc *gc, libxl__device *dev,
                               char ***args, char ***env,
                               libxl__device_action action)
{
    char *be_path = libxl__device_backend_path(gc, dev);
    char *script;
    int nr = 0, rc = 0;

    script = libxl__xs_read(gc, XBT_NULL,
                            GCSPRINTF("%s/%s", be_path, "script"));
    if (!script) {
        LOGEVD(ERROR, errno, dev->domid,
               "unable to read script from %s", be_path);
        rc = ERROR_FAIL;
        goto error;
    }

    *env = get_hotplug_env(gc, script, dev);
    if (!*env) {
        LOGD(ERROR, dev->domid, "Failed to get hotplug environment");
        rc = ERROR_FAIL;
        goto error;
    }

    const int arraysize = 3;
    GCNEW_ARRAY(*args, arraysize);
    (*args)[nr++] = script;
    (*args)[nr++] = (char *) libxl__device_action_to_string(action);
    (*args)[nr++] = NULL;
    assert(nr == arraysize);

    LOGD(DEBUG, dev->domid, "Args and environment ready");
    rc = 1;

error:
    return rc;
}

int libxl__get_hotplug_script_info(libxl__gc *gc, libxl__device *dev,
                                   char ***args, char ***env,
                                   libxl__device_action action,
                                   int num_exec)
{
    int rc;

    switch (dev->backend_kind) {
    case LIBXL__DEVICE_KIND_VBD:
        if (num_exec != 0) {
            LOGD(DEBUG, dev->domid,
                 "num_exec %d, not running hotplug scripts", num_exec);
            rc = 0;
            goto out;
        }
        rc = libxl__hotplug_disk(gc, dev, args, env, action);
        break;
    case LIBXL__DEVICE_KIND_VIF:
        /*
         * If domain has a stubdom we don't have to execute hotplug scripts
         * for emulated interfaces
         */
        if ((num_exec > 1) ||
            (libxl_get_stubdom_id(CTX, dev->domid) && num_exec)) {
            LOGD(DEBUG, dev->domid,
                 "num_exec %d, not running hotplug scripts", num_exec);
            rc = 0;
            goto out;
        }
        rc = libxl__hotplug_nic(gc, dev, args, env, action, num_exec);
        break;
    default:
        /* No need to execute any hotplug scripts */
        LOGD(DEBUG, dev->domid,
             "backend_kind %d, no need to execute scripts", dev->backend_kind);
        rc = 0;
        break;
    }

out:
    return rc;
}

libxl_device_model_version libxl__default_device_model(libxl__gc *gc)
{
    return LIBXL_DEVICE_MODEL_VERSION_QEMU_XEN;
}

int libxl__pci_numdevs(libxl__gc *gc)
{
    DIR *dir;
    struct dirent *entry;
    int num_devs = 0;

    dir = opendir("/sys/bus/pci/devices");
    if (!dir) {
        LOGE(ERROR, "Cannot open /sys/bus/pci/devices");
        return ERROR_FAIL;
    }

    while ((entry = readdir(dir))) {
        if (entry->d_name[0] == '.')
            continue;
        num_devs++;
    }
    closedir(dir);

    return num_devs;
}

int libxl__pci_topology_init(libxl__gc *gc,
                             physdev_pci_device_t *devs,
                             int num_devs)
{

    DIR *dir;
    struct dirent *entry;
    int i, err = 0;

    dir = opendir("/sys/bus/pci/devices");
    if (!dir) {
        LOGE(ERROR, "Cannot open /sys/bus/pci/devices");
        return ERROR_FAIL;
    }

    i = 0;
    while ((entry = readdir(dir))) {
        unsigned int dom, bus, dev, func;

        if (entry->d_name[0] == '.')
            continue;

        if (i == num_devs) {
            LOG(ERROR, "Too many devices");
            err = ERROR_FAIL;
            errno = -ENOSPC;
            goto out;
        }

        if (sscanf(entry->d_name, "%x:%x:%x.%d", &dom, &bus, &dev, &func) < 4) {
            LOGE(ERROR, "Error processing /sys/bus/pci/devices");
            err = ERROR_FAIL;
            goto out;
        }

        devs[i].seg = dom;
        devs[i].bus = bus;
        devs[i].devfn = ((dev & 0x1f) << 3) | (func & 7);

        i++;
    }

 out:
    closedir(dir);

    return err;
}

static struct {
    int resource;
    rlim_t limit;
} rlimits[] = {
#define RLIMIT_ENTRY(r, l) \
    { .resource = r, .limit = l }
    /* Big enough for log files, not big enough for a DoS */
    RLIMIT_ENTRY(RLIMIT_FSIZE,    256*1024),

    /* Shouldn't need any of these */
    RLIMIT_ENTRY(RLIMIT_CORE,     0),
    RLIMIT_ENTRY(RLIMIT_MSGQUEUE, 0),
    RLIMIT_ENTRY(RLIMIT_LOCKS,    0),
    RLIMIT_ENTRY(RLIMIT_MEMLOCK,  0),

    /* End-of-list marker */
    RLIMIT_ENTRY(RLIMIT_NLIMITS,  0),
#undef RLIMIT_ENTRY
};

int libxl__local_dm_preexec_restrict(libxl__gc *gc)
{
    int r;
    unsigned i;

    /* Unshare mount and IPC namespaces.  These are unused by QEMU. */
    r = unshare(CLONE_NEWNS | CLONE_NEWIPC);
    if (r) {
        LOGE(ERROR, "libxl: unshare Mount and IPC namespace failed");
        return ERROR_FAIL;
    }

    /* Set various "easy" rlimits */
    for (i = 0; rlimits[i].resource != RLIMIT_NLIMITS; i++) {
        struct rlimit rlim;

        rlim.rlim_cur = rlim.rlim_max = rlimits[i].limit;

        r = setrlimit(rlimits[i].resource, &rlim);
        if (r < 0) {
            LOGE(ERROR, "Setting rlimit %d to %llu failed\n",
                                  rlimits[i].resource,
                                  (unsigned long long)rlimits[i].limit);
            return ERROR_FAIL;
        }
    }

    return 0;
}

/*
 * Local variables:
 * mode: C
 * c-basic-offset: 4
 * indent-tabs-mode: nil
 * End:
 */
