/*
 * Copyright (c) 2004-2009, Luiz Otavio O Souza <loos.br@gmail.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

static const char rcsid[] = "$Id: ctl.c 96 2009-01-20 15:34:19Z loos-br $";

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>

#include <errno.h>
#include <string.h>
#include <unistd.h>

#include "io.h"
#include "ctl.h"
#include "xml.h"
#include "net-io.h"
#include "return.h"
#include "msn-proxy.h"

struct ctls	ctl_head;
unsigned int	ctl_clients = 0;

unsigned int
ctl_cmp(struct ctl_ *c1, struct ctl_ *c2) {
    return (c1->fd - c2->fd);
}

RB_GENERATE(ctls, ctl_, ctl__, ctl_cmp)

void
ctl_sched_read(struct ctl_ *ctl) {
    event_add(&ctl->read, &config.timeout_ctl_read);
}

void
ctl_init(void) {
    memset(&ctl_head, 0, sizeof(struct ctls));
}

struct ctl_ *
ctl_disconnect(struct ctl_ *ctl) {

    /* disconnect from client */
    if (EVENT_FD((&ctl->read)) != -1) {
	event_del(&ctl->read);
	ctl->read.ev_fd = -1;
    }
    if (ctl->fd != -1) {
	while (close(ctl->fd) != 0 && errno == EINTR);
	ctl->fd = -1;
    }

    /* remove ctl from tree */
    (void)RB_REMOVE(ctls, &ctl_head, ctl);
    ctl_clients--;

    str_free(&ctl->buf);
    memset(ctl, 0, sizeof(struct ctl_));
    free(ctl);

    /* return a null pointer */
    return((struct ctl_ *)NULL);
}

struct ctl_ *
ctl_alloc(int fd) {
 struct ctl_		*ctl;
 log_			*log = &config.log;

    /* alloc new ctl */
    ctl = (struct ctl_ *)malloc(sizeof(struct ctl_));
    if (ctl == NULL) exit(51);
    memset(ctl, 0, sizeof(struct ctl_));

    /* misc set */
    ctl->fd = fd;
    ctl->read.ev_fd = -1;

    /* add to rb list */
    if (RB_INSERT(ctls, &ctl_head, ctl)) {
        log->debug("panic ctl RB_INSERT fail (duplicate entry ?) !\n");
	ctl = ctl_disconnect(ctl);
        return(ctl);
    }

    /* return the new ctl */
    return(ctl);
}

void
ctl_read(const int evfd, short event, void *ctl__) {
 unsigned char		buf[MAX_BUF];
 unsigned char		*p;
 struct ctl_		*ctl = ctl__;
 log_			*log = &config.log;
 int			len;

    (void) evfd;
    if ((event & EV_TIMEOUT)) {
	log->debug("debug: ctl read timeout\n");
	ctl = ctl_disconnect(ctl);
	return;
    }

    len = io_read(ctl->fd, (char *)buf, sizeof(buf));
    if (len == -1) {
	if (errno == EINTR) {
	    /* AGAIN */
	    ctl_sched_read(ctl);
	    return;
	}
	log->debug("cannot read ctl [%d]\n", ctl->fd);
	ctl = ctl_disconnect(ctl);
	return;
    } else if (len == 0) {
	ctl = ctl_disconnect(ctl);
	return;
    }
    buf[len] = 0;

    p = buf;
    while (len > 0) {

	/* check for end of command */
	if (*p == 0)
	    ++ctl->end;
	else
	    ctl->end = 0;

	if (ctl->end == 2) {

	    /* parse received xml */
	    memset(&ctl->xml_head, 0, sizeof(struct xml_tags));
	    if (xml_parse(&ctl->xml_head, &ctl->buf) == RFAIL) {
		log->debug("cannot parse ctl data\n");
		xml_free(&ctl->xml_head);
		ctl = ctl_disconnect(ctl);
		return;
            }

            /* run xml instructions/commands */
            if (xml_run(&ctl->xml_head, ctl) == RFAIL) {
		log->debug("cannot execute ctl query\n");
		xml_free(&ctl->xml_head);
		ctl = ctl_disconnect(ctl);
		return;
	    }

	    /* free memory */
	    xml_free(&ctl->xml_head);
	    str_free(&ctl->buf);
	    ctl->end = 0;
	}
	if (*p && str_cat(&ctl->buf, p, 1) == 0) exit(51);
	len--; p++;
    }

    ctl_sched_read(ctl);
}

void
ctl_client(const int ctlfd, short event, void *p) {
 struct sockaddr_un	ctl_sun;
 struct ctl_		*ctl;
 socklen_t		ctl_sun_len;
 config_		*config = p;
 log_			*log = &config->log;
 int			fd;

    /* prepare client sockaddr */
    ctl_sun_len = sizeof(struct sockaddr_un);
    memset(&ctl_sun, 0, ctl_sun_len);
     
    /* accept connection */
    fd = accept(ctlfd, (struct sockaddr *)&ctl_sun, &ctl_sun_len);
    if (fd < 0) {
	log->debug("debug: unable to accept new ctl client connection\n");
	return;
    }

    /* set socket options */
    if (set_options(fd) == -1 || set_nonblock(fd) == -1) {
        log->debug("debug: unable to set ctl socket options\n");
        while (close(fd) != 0 && errno == EINTR);
        return;
    }
   
    /* max ctl connections */
    if (++ctl_clients > config->max_ctl_clients) {
        log->debug("debug: CTL Server full - too many clients\n");
        while (close(fd) != 0 && errno == EINTR);
	ctl_clients--;
        return;
    }

    ctl = ctl_alloc(fd);
    if (ctl == NULL) {
        log->debug("debug: fail to alloc ctl\n");
	return;
    }

    event_set(&ctl->read, ctl->fd, EV_READ, ctl_read, ctl);
    ctl_sched_read(ctl);
}
