<commit_before>/*
 * Copyright (C) 2018 SAP SE
 *
 * 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 *
 * Author: <NAME> <<EMAIL>>
 */

#include <config.h>

#include <errno.h>
#include <sys/un.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <search.h>
#include <dirent.h>
#include <pthread.h>
#include <libvirt/libvirt.h>

#include "util.h"
#include "virtio.h"


#define DEFAULT_VU_BUFFER_SIZE 512
#define SUN_PATH_LEN (sizeof(((struct sockaddr_un *) 0)->sun_path))
#define FREE -1


typedef struct {
    int fd;              /* UDS filehandle */
    int id;              /* domain id */
    time_t update_ts;    /* timestamp of last metrics update */
    char *name;          /* domain name */
    char *uds_name;      /* full UDS name */
    vu_buffer *metrics;  /* metrics buffer */
    vu_buffer *request;  /* virtio request buffer */
    vu_buffer *response; /* virtio response buffer */
} channel_t;

typedef struct {
    int id;              /* domain id */
    int index;           /* index of channel */
} id_map_t;

typedef enum {
    REQ_INCOMPLETE,
    REQ_INVALID,
    REQ_GET_XML
} REQUEST_T;

static channel_t *channel = NULL;
static id_map_t *id_map = NULL;
static time_t exp_period = 0;

static const char *channel_path = "/var/lib/libvirt/qemu/channel/target";
static const char *channel_name = "org.github.vhostmd.1";
static int channel_max = 0;
static volatile int channel_count = 0;
static volatile int connection_count = 0;

static int epoll_fd = -1;
static struct epoll_event *epoll_events = NULL;
static pthread_mutex_t channel_mtx;

static enum {
    VIRTIO_INIT,
    VIRTIO_ACTIVE,
    VIRTIO_STOP,
    VIRTIO_ERROR
} virtio_status = VIRTIO_INIT;


/*
 * Static functions
 */
static int vio_id_compare(const void * a, const void * b);
static channel_t *vio_channel_find(int id, const char * name, int insert);
static void vio_channel_free(channel_t * c);
static int vio_channel_open(channel_t * c);
static void vio_channel_close(channel_t * c);
static int vio_channel_update(channel_t * c);
static int vio_readdir(const char * path);
static void vio_recv(channel_t * c);
static void vio_send(channel_t * c, uint32_t ep_event);
static void vio_expire(void);
static REQUEST_T vio_check_request(channel_t * c);
static void vio_handle_io(unsigned epoll_wait_ms);

/*
 * Update response buffer of a channel.
 * Concat host and VM buffer into the response buffer.
 */
static int vio_channel_update(channel_t * c)
{
    static const char *metrics_start_str = "<metrics>\n";
    static const char *metrics_end_str = "</metrics>\n\n";

    int rc = 0;

    vu_buffer_erase(c->response);
    vu_buffer_add(c->response, metrics_start_str, -1);

    pthread_mutex_lock(&channel_mtx);

    /* Dom0/host */
    if (channel[0].metrics->content && channel[0].metrics->use)
        vu_buffer_add(c->response, channel[0].metrics->content, -1);
    else
        vu_buffer_add(c->response,
                      "<!-- host metrics not available -->", -1);

    /* VM */
    if (c->metrics->use)
        vu_buffer_add(c->response, c->metrics->content, -1);
    else {
        vu_buffer_add(c->response,
                      "<!-- VM metrics not available -->", -1);
        rc = -1;
    }

    pthread_mutex_unlock(&channel_mtx);

    vu_buffer_add(c->response, metrics_end_str, -1);

#ifdef ENABLE_DEBUG
    vu_log(VHOSTMD_DEBUG, "New response for '%d %s' (%u)\n>>>%s<<<\n",
           c->id, c->name, c->response->use,
           c->response->content);
#endif
    return rc;
}

/*
 * Free allocated buffers and init values.
 */
static void vio_channel_free(channel_t * c)
{
    if (c->fd != FREE) {
        struct epoll_event evt;
        epoll_ctl(epoll_fd, EPOLL_CTL_DEL, c->fd, &evt);
        close(c->fd);
        c->fd = FREE;
    }
    if (c->name) {
        free(c->name);
        c->name = NULL;
    }
    if (c->uds_name) {
        free(c->uds_name);
        c->uds_name = NULL;
    }
    if (c->metrics) {
        vu_buffer_delete(c->metrics);
        c->metrics = NULL;
    }
    if (c->request) {
        vu_buffer_delete(c->request);
        c->request = NULL;
    }
    if (c->response) {
        vu_buffer_delete(c->response);
        c->response = NULL;
    }
    c->id = FREE;
}

/*
 * Connect channel and add the socket to the epoll desriptor.
 */
static int vio_channel_open(channel_t * c)
{
    struct sockaddr_un address;
    struct epoll_event evt;
    int flags;

    bzero(&address, sizeof(address));
    address.sun_family = AF_LOCAL;

    strncpy(address.sun_path, c->uds_name, SUN_PATH_LEN);

    if ((c->fd = socket(AF_LOCAL, SOCK_STREAM, 0)) == -1)
        goto error;

    flags = fcntl(c->fd, F_GETFL, 0);
    if (flags < 0)
        goto error;

    flags |= flags | O_NONBLOCK;
    if (fcntl(c->fd, F_SETFL, flags) == -1)
        goto error;

    if (connect(c->fd, (struct sockaddr *) &address,
                (socklen_t) sizeof(address)) < 0)
        goto error;

    evt.data.ptr = (void*) c;
    evt.events = EPOLLIN;

    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, c->fd, &evt) == -1)
        goto error;

    connection_count++;

    vu_log(VHOSTMD_INFO, "Opened channel '%d %s' (%d/%d/%d)",
           c->id, c->name, connection_count, channel_count, channel_max);

    return 0;

error:
    vu_log(VHOSTMD_ERR, "Could not add channel '%d %s' (%s)",
           c->id, c->name, strerror(errno));
    if (c->fd >= 0) {
        close(c->fd);
        c->fd = FREE;
    }

    return -1;
}

/*
 * Close channel.
 */
static void vio_channel_close(channel_t * c)
{
    int id = c->id;

    channel_count--;
    if (c->fd != FREE)
        connection_count--;

    vu_log(VHOSTMD_INFO, "Closed channel '%d %s' (%d/%d/%d)",
           c->id, c->name, connection_count, channel_count,  channel_max);
    vio_channel_free(c);

    if (id > 0) {
        id_map_t key, *result;

        key.id = id;
        result = (id_map_t *) bsearch(&key, id_map, channel_max, sizeof(id_map_t),
                                      vio_id_compare);

        if (result) {
            result->id = FREE;
            qsort (id_map, channel_max, sizeof(id_map_t), vio_id_compare);
        }
    }
}

/*
 * Lookup UDS sockets in the directory.
 * For valid type/name/channel connect to the socket.
 */
static int vio_readdir(const char * path)
{
    struct dirent *ent;
    DIR *dir = NULL;

    if ((dir = opendir(path)) == NULL) {
        vu_log(VHOSTMD_ERR, "opendir(%s) failed (%s)", path, strerror(errno));
        return -1;
    }

    while ((ent = readdir(dir)) != NULL) {
        int rc, id;

        if (ent->d_type == DT_DIR &&
            sscanf(ent->d_name, "domain-%d-", &id) == 1) {

            char tmp[SUN_PATH_LEN + 8];
            struct stat st;

            rc = snprintf(tmp, sizeof(tmp), "%s/%s/%s", path, ent->d_name, channel_name);

            if (rc > 0 && rc < (int) sizeof(tmp) &&
                strlen(tmp) < SUN_PATH_LEN &&
                stat(tmp, &st) == 0 &&
                S_ISSOCK(st.st_mode)) {
<commit_msg>Remove d_type check for channel directory.<commit_after>/*
 * Copyright (C) 2018 SAP SE
 *
 * 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 *
 * Author: <NAME> <<EMAIL>>
 */

#include <config.h>

#include <errno.h>
#include <sys/un.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <search.h>
#include <dirent.h>
#include <pthread.h>
#include <libvirt/libvirt.h>

#include "util.h"
#include "virtio.h"


#define DEFAULT_VU_BUFFER_SIZE 512
#define SUN_PATH_LEN (sizeof(((struct sockaddr_un *) 0)->sun_path))
#define FREE -1


typedef struct {
    int fd;              /* UDS filehandle */
    int id;              /* domain id */
    time_t update_ts;    /* timestamp of last metrics update */
    char *name;          /* domain name */
    char *uds_name;      /* full UDS name */
    vu_buffer *metrics;  /* metrics buffer */
    vu_buffer *request;  /* virtio request buffer */
    vu_buffer *response; /* virtio response buffer */
} channel_t;

typedef struct {
    int id;              /* domain id */
    int index;           /* index of channel */
} id_map_t;

typedef enum {
    REQ_INCOMPLETE,
    REQ_INVALID,
    REQ_GET_XML
} REQUEST_T;

static channel_t *channel = NULL;
static id_map_t *id_map = NULL;
static time_t exp_period = 0;

static const char *channel_path = "/var/lib/libvirt/qemu/channel/target";
static const char *channel_name = "org.github.vhostmd.1";
static int channel_max = 0;
static volatile int channel_count = 0;
static volatile int connection_count = 0;

static int epoll_fd = -1;
static struct epoll_event *epoll_events = NULL;
static pthread_mutex_t channel_mtx;

static enum {
    VIRTIO_INIT,
    VIRTIO_ACTIVE,
    VIRTIO_STOP,
    VIRTIO_ERROR
} virtio_status = VIRTIO_INIT;


/*
 * Static functions
 */
static int vio_id_compare(const void * a, const void * b);
static channel_t *vio_channel_find(int id, const char * name, int insert);
static void vio_channel_free(channel_t * c);
static int vio_channel_open(channel_t * c);
static void vio_channel_close(channel_t * c);
static int vio_channel_update(channel_t * c);
static int vio_readdir(const char * path);
static void vio_recv(channel_t * c);
static void vio_send(channel_t * c, uint32_t ep_event);
static void vio_expire(void);
static REQUEST_T vio_check_request(channel_t * c);
static void vio_handle_io(unsigned epoll_wait_ms);

/*
 * Update response buffer of a channel.
 * Concat host and VM buffer into the response buffer.
 */
static int vio_channel_update(channel_t * c)
{
    static const char *metrics_start_str = "<metrics>\n";
    static const char *metrics_end_str = "</metrics>\n\n";

    int rc = 0;

    vu_buffer_erase(c->response);
    vu_buffer_add(c->response, metrics_start_str, -1);

    pthread_mutex_lock(&channel_mtx);

    /* Dom0/host */
    if (channel[0].metrics->content && channel[0].metrics->use)
        vu_buffer_add(c->response, channel[0].metrics->content, -1);
    else
        vu_buffer_add(c->response,
                      "<!-- host metrics not available -->", -1);

    /* VM */
    if (c->metrics->use)
        vu_buffer_add(c->response, c->metrics->content, -1);
    else {
        vu_buffer_add(c->response,
                      "<!-- VM metrics not available -->", -1);
        rc = -1;
    }

    pthread_mutex_unlock(&channel_mtx);

    vu_buffer_add(c->response, metrics_end_str, -1);

#ifdef ENABLE_DEBUG
    vu_log(VHOSTMD_DEBUG, "New response for '%d %s' (%u)\n>>>%s<<<\n",
           c->id, c->name, c->response->use,
           c->response->content);
#endif
    return rc;
}

/*
 * Free allocated buffers and init values.
 */
static void vio_channel_free(channel_t * c)
{
    if (c->fd != FREE) {
        struct epoll_event evt;
        epoll_ctl(epoll_fd, EPOLL_CTL_DEL, c->fd, &evt);
        close(c->fd);
        c->fd = FREE;
    }
    if (c->name) {
        free(c->name);
        c->name = NULL;
    }
    if (c->uds_name) {
        free(c->uds_name);
        c->uds_name = NULL;
    }
    if (c->metrics) {
        vu_buffer_delete(c->metrics);
        c->metrics = NULL;
    }
    if (c->request) {
        vu_buffer_delete(c->request);
        c->request = NULL;
    }
    if (c->response) {
        vu_buffer_delete(c->response);
        c->response = NULL;
    }
    c->id = FREE;
}

/*
 * Connect channel and add the socket to the epoll desriptor.
 */
static int vio_channel_open(channel_t * c)
{
    struct sockaddr_un address;
    struct epoll_event evt;
    int flags;

    bzero(&address, sizeof(address));
    address.sun_family = AF_LOCAL;

    strncpy(address.sun_path, c->uds_name, SUN_PATH_LEN);

    if ((c->fd = socket(AF_LOCAL, SOCK_STREAM, 0)) == -1)
        goto error;

    flags = fcntl(c->fd, F_GETFL, 0);
    if (flags < 0)
        goto error;

    flags |= flags | O_NONBLOCK;
    if (fcntl(c->fd, F_SETFL, flags) == -1)
        goto error;

    if (connect(c->fd, (struct sockaddr *) &address,
                (socklen_t) sizeof(address)) < 0)
        goto error;

    evt.data.ptr = (void*) c;
    evt.events = EPOLLIN;

    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, c->fd, &evt) == -1)
        goto error;

    connection_count++;

    vu_log(VHOSTMD_INFO, "Opened channel '%d %s' (%d/%d/%d)",
           c->id, c->name, connection_count, channel_count, channel_max);

    return 0;

error:
    vu_log(VHOSTMD_ERR, "Could not add channel '%d %s' (%s)",
           c->id, c->name, strerror(errno));
    if (c->fd >= 0) {
        close(c->fd);
        c->fd = FREE;
    }

    return -1;
}

/*
 * Close channel.
 */
static void vio_channel_close(channel_t * c)
{
    int id = c->id;

    channel_count--;
    if (c->fd != FREE)
        connection_count--;

    vu_log(VHOSTMD_INFO, "Closed channel '%d %s' (%d/%d/%d)",
           c->id, c->name, connection_count, channel_count,  channel_max);
    vio_channel_free(c);

    if (id > 0) {
        id_map_t key, *result;

        key.id = id;
        result = (id_map_t *) bsearch(&key, id_map, channel_max, sizeof(id_map_t),
                                      vio_id_compare);

        if (result) {
            result->id = FREE;
            qsort (id_map, channel_max, sizeof(id_map_t), vio_id_compare);
        }
    }
}

/*
 * Lookup UDS sockets in the directory.
 * For valid type/name/channel connect to the socket.
 */
static int vio_readdir(const char * path)
{
    struct dirent *ent;
    DIR *dir = NULL;

    if ((dir = opendir(path)) == NULL) {
        vu_log(VHOSTMD_ERR, "opendir(%s) failed (%s)", path, strerror(errno));
        return -1;
    }

    while ((ent = readdir(dir)) != NULL) {
        int rc, id;

        if (sscanf(ent->d_name, "domain-%d-", &id) == 1) {

            char tmp[SUN_PATH_LEN + 8];
            struct stat st;

            rc = snprintf(tmp, sizeof(tmp), "%s/%s/%s", path, ent->d_name, channel_name);

            if (rc > 0 && rc < (int) sizeof(tmp) &&
                strlen(tmp) < SUN_PATH_LEN &&
                stat(tmp, &st) == 0 &&
                S_ISSOCK(st.st_mode)) {

{'fraction_non_alphanumeric': 0.1339424572317263, 'fraction_numerical': 0.007905650596163815, 'mean_word_length': 2.5974358974358975, 'pattern_counts': {'":': 0, '<': 55, '<?xml version=': 0, '>': 176, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3576181', 'n_tokens_mistral': 5468, 'n_tokens_neox': 5271, 'n_words': 1431}