/*
 * Copyright (c) 2004 Seth Kingsley.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgment:
 *    This product includes software developed by Seth Kingsley and
 *    contributors.
 * 4. Neither the author's name, nor any of the contributors names may be
 *    used to endorse or promote products derived from this software without
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include    <sys/types.h>
#include    <sys/errno.h>
#include    <sys/wait.h>
#include    <sys/queue.h>
#include    <netinet/in.h>
#include    <arpa/inet.h>

#ifdef MK_CONFIG
#include    <libutil.h>
#endif /* MK_CONFIG */

#include    <netdb.h>
#include    <unistd.h>
#include    <fcntl.h>
#include    <stdlib.h>
#include    <string.h>
#include    <stdio.h>
#include    <signal.h>
#include    <sysexits.h>
#include    <err.h>
#include    <assert.h>

#include    "mk.h"

__RCSID("$Id: mk.c,v 1.6 2004/06/24 04:40:39 sethk Exp $");

enum mk_spawn_error
{
    MK_SPAWNERR_NONE,
    MK_SPAWNERR_CHDIR,
    MK_SPAWNERR_EXEC,
    MK_SPAWNERR_LAST = MK_SPAWNERR_EXEC
};

struct mk_lines
{
    char    ml_buffer[1024 + 1];
    size_t  ml_length;
    char    *ml_next;
    bool    ml_flush;
};

#ifndef DMALLOC
void *(*mk_malloc)(size_t _n) = mk_malloc_or_die;
void *(*mk_realloc)(void *_p, size_t _n) = mk_realloc_or_die;
void (*mk_free)(void *_p) = free;
#endif /* !DMALLOC */

#ifdef MK_CONFIG
static STAILQ_HEAD(mk_config_types, mk_config_type) _mk_config_types =
STAILQ_HEAD_INITIALIZER(_mk_config_types);

static struct mk_config_type *
_mk_config_type_find(const char *tname)
{
    struct mk_config_type   *mctp;

    STAILQ_FOREACH(mctp, &_mk_config_types, mct_link)
	if (!strcmp(mctp->mct_name, tname))
	    return mctp;

    return NULL;
}

static bool
_mk_config_type_parse(struct mk_config_type mct,
	mk_config_errfunc_t efunc,
	void *udata,
	const char *name,
	void *valuep,
	const char *string)
{
    if (!mct.mct_typefunc(valuep, string))
    {
	char	act[128 + 1],
		desc[128 + 1];

	snprintf(act, sizeof(act), "parse %s", name);
	snprintf(desc, sizeof(desc), "could not convert \"%s\" to %s",
		string,
		mct.mct_name);
	efunc(act, desc, udata);

	return false;
    }

    return true;
}

static bool
_mk_config_parse_integer(int *ip, const char *string)
{
    char    *ae;
    int	    i;

    i = strtol(string, &ae, 10);
    if (ae - string < strlen(string))
	return false;

    *ip = i;

    return true;
}

static bool
_mk_config_parse_string(char **spp, const char *string)
{
    size_t  sl = strlen(string);

    if ((string[0] == '"' || string[0] == '\'') &&
	    string[sl - 1] == string[0])
    {
	++string;
	sl-= 2;
    }

    *spp = mk_malloc(sl + 1);
    strlcpy(*spp, string, sl + 1);

    return true;
}

static void
_mk_config_free_string(char **spp)
{
    mk_free(*spp);
}

static bool
_mk_config_parse_ipaddr(struct in_addr *ipp, const char *string)
{
    in_addr_t	ia;

    if ((ia = inet_addr(string)) != INADDR_NONE)
    {
	ipp->s_addr = ia;
	return true;
    }
    else
    {
	struct hostent	*hep;

	if ((hep = gethostbyname(string)))
	{
	    bcopy(hep->h_addr, ipp, sizeof(hep->h_addr));
	    return true;
	}
	else
	    return false;
    }
}

static void
_mk_config_cleanup(void)
{
    struct mk_config_type   *mctp;

    while ((mctp = STAILQ_FIRST(&(_mk_config_types))))
    {
	STAILQ_REMOVE(&(_mk_config_types), mctp, mk_config_type, mct_link);
	mk_free(mctp->mct_name);
	mk_free(mctp);
    }
}
#endif /* MK_CONFIG */

void *
mk_malloc_or_die(size_t n)
{
    void    *p;

    if (!(p = malloc(n)))
	err(EX_OSERR, "malloc size %u failed", n);

    return p;
}

void *
mk_realloc_or_die(void *p, size_t n)
{
    if (!(p = realloc(p, n)))
	err(EX_OSERR, "realloc size %u failed", n);

    return p;
}

#ifndef DMALLOC
char *
mk_strdup(const char *s)
{
    size_t  len = strlen(s) + 1;
    char    *ns = mk_malloc(len);

    bcopy(s, ns, len);

    return ns;
}
#endif /* !DMALLOC */

#ifdef MK_CONFIG
void
mk_config_type_add(const char *name,
	mk_config_typefunc_t tfunc,
	mk_config_freefunc_t ffunc)
{
    struct mk_config_type   *mctp = mk_malloc(sizeof(*mctp));

    mctp->mct_name = mk_strdup(name);
    mctp->mct_typefunc = tfunc;
    mctp->mct_freefunc = ffunc;
    STAILQ_INSERT_TAIL(&_mk_config_types, mctp, mct_link);
}

bool
mk_config_read(int fd,
	struct mk_config_var *mcvl,
	u_int nmcv,
	mk_config_errfunc_t efunc,
	void *udata)
{
    properties	p;
    u_int	i;

    if (STAILQ_EMPTY(&_mk_config_types))
    {
	mk_config_type_add(MK_CONF_INT,
		(mk_config_typefunc_t)_mk_config_parse_integer,
		NULL);
	mk_config_type_add(MK_CONF_STR,
		(mk_config_typefunc_t)_mk_config_parse_string,
		(mk_config_freefunc_t)_mk_config_free_string);
	mk_config_type_add(MK_CONF_IP,
		(mk_config_typefunc_t)_mk_config_parse_ipaddr,
		NULL);

	atexit(_mk_config_cleanup);
    }

    if (!(p = properties_read(fd)))
    {
	efunc("read properties", strerror(errno), udata);
	return false;
    }

    for (i = 0; i < nmcv; ++i)
    {
	char			*value;
	struct mk_config_type	*mctp;

	if (!(mctp = _mk_config_type_find(mcvl[i].mcv_typename)))
	{
	    char    act[64 + 1],
		    desc[64 + 1];

	    snprintf(act, sizeof(act), "parse %s", mcvl[i].mcv_name);
	    snprintf(desc, sizeof(desc),
		    "no type converter registered for \"%s\"",
		    mcvl[i].mcv_typename);
	    efunc(act, desc, udata);
	    return false;
	}

	if (!(value = property_find(p, mcvl[i].mcv_name)))
	{
	    if (!_mk_config_type_parse(*mctp,
			efunc,
			udata,
			mcvl[i].mcv_name,
			mcvl[i].mcv_valuep,
			mcvl[i].mcv_default))
		return false;
	}
	else if (!_mk_config_type_parse(*mctp,
		    efunc,
		    udata,
		    mcvl[i].mcv_name,
		    mcvl[i].mcv_valuep,
		    value))
	{
	    if (!_mk_config_type_parse(*mctp,
			efunc,
			udata,
			mcvl[i].mcv_name,
			mcvl[i].mcv_valuep,
			mcvl[i].mcv_default))
		return false;
	}
    }

    properties_free(p);

    return true;
}

void
mk_config_free(struct mk_config_var *mcvl, u_int nmcv)
{
    u_int   i;

    for (i = 0; i < nmcv; ++i)
    {
	struct mk_config_type	*mctp;

	if ((mctp = _mk_config_type_find(mcvl[i].mcv_typename)) &&
		mctp->mct_freefunc)
	    mctp->mct_freefunc(mcvl[i].mcv_valuep);
    }
}
#endif /* MK_CONFIG */

pid_t
mk_spawn(const char *dir, int *pfds, char * const av[], char **descp)
{
    pid_t   pid;
    int	    efds[2];

    if (pfds)
    {
	if (pipe(pfds) == -1)
	{
	    *descp = "create pipe";
	    return -1;
	}
    }

    if (pipe(efds) == -1)
    {
	*descp = "create error pipe";
	return -1;
    }

#ifdef DEBUG
    fprintf(stderr, "%s: %s(): exec '%s'", __func__, getprogname(), av[0]);
    {
	u_int	_i;

	for (_i = 1; av[_i]; ++_i)
	    fprintf(stderr, " '%s'", av[_i]);
    }
    fputc('\n', stderr);
#endif /* DEBUG */

    switch ((pid = vfork()))
    {
	ssize_t	br;
	int	errn;

	case -1:
	    if (pfds)
	    {
		close(pfds[0]);
		close(pfds[1]);
	    }

	    close(efds[0]);
	    close(efds[1]);

	    *descp = "create process";

	    return -1;

	case 0:
	    close(efds[0]);
	    fcntl(efds[1], F_SETFD, FD_CLOEXEC); 

	    if (pfds)
	    {
		close(pfds[0]);
		dup2(pfds[1], STDOUT_FILENO);
		dup2(STDOUT_FILENO, STDERR_FILENO);
		close(pfds[1]);
	    }

	    if (dir)
	    {
		if (chdir(dir) == -1)
		{
		    write(efds[1], &errno, sizeof(errno));
		    _exit(MK_SPAWNERR_CHDIR);
		}
	    }

	    if (execvp(av[0], av) == -1)
	    {
		write(efds[1], &errno, sizeof(errno));
		_exit(MK_SPAWNERR_EXEC);
	    }

	    /* NOTREACHED */
	    return -1;

	default:
	    close(efds[1]);

	    if (pfds)
		close(pfds[1]);

	    /* Check for pre-exec error. */
	    br = read(efds[0], &errn, sizeof(errn));
	    close(efds[0]);
	    if (br == sizeof(errn))
	    {
		int	    st;
		static char desc[1024 + 1];

		if (waitpid(pid, &st, WNOHANG) == -1 ||
			!WIFEXITED(st) ||
			WEXITSTATUS(st) < 1 ||
			WEXITSTATUS(st) > MK_SPAWNERR_LAST)
		    *descp = "(unknown error)";
		else
		{
		    switch (WEXITSTATUS(st))
		    {
			case MK_SPAWNERR_CHDIR:
			    snprintf(desc, sizeof(desc), "chdir %s", dir);
			    break;

			case MK_SPAWNERR_EXEC:
			    snprintf(desc, sizeof(desc), "exec %s", av[0]);
			    break;
		    }

		    *descp = desc;
		}

		errno = errn;

		return -1;
	    }

	    return pid;
    }
}

struct mk_lines *
mk_lines_init(void)
{
    struct mk_lines *mlp;

    if ((mlp = mk_malloc(sizeof(*mlp))))
    {
	mlp->ml_length = 0;
	*(mlp->ml_buffer) = '\0';
	mlp->ml_next = (mlp)->ml_buffer;
	mlp->ml_flush = false;
    }

    return mlp;
}

void
mk_lines_fini(struct mk_lines *mlp)
{
    mk_free(mlp);
}

ssize_t
mk_lines_read(int fd, struct mk_lines *mlp)
{
    ssize_t br;

    /* Read a line segment. */
    if ((br = read(fd,
		    mlp->ml_buffer + mlp->ml_length,
		    (sizeof(mlp->ml_buffer) - 1) - mlp->ml_length)) <= 0)
	/* End of input, flush unterminated line. */
	mlp->ml_flush = true;
    else
    {
	/* Concatenante this segment. */
	mlp->ml_length+= br;
	mlp->ml_buffer[mlp->ml_length] = '\0';
    }

    return br;
}

char *
mk_lines_next(struct mk_lines *mlp)
{
    char    *save_next = mlp->ml_next,
	    *nlp;

    /* Find the first terminated line. */
    if ((nlp = strchr(mlp->ml_next, '\n')))
    {
	mlp->ml_next = nlp + 1;

	/* Chomp the EOL. */
	while (nlp > mlp->ml_buffer && (*nlp == '\n' || *nlp == '\r'))
	    *nlp-- = '\0';
    }
    else
    {
	size_t	tl = strlen(mlp->ml_next);

	if (!tl)    /* No more lines in buffer. */
	{
	    if (mlp->ml_length)
	    {
		*(mlp->ml_buffer) = '\0';
		mlp->ml_length = 0;
		mlp->ml_next = mlp->ml_buffer;
	    }

	    return NULL;
	}
	else if (mlp->ml_flush)
	    /* Return */
	    mlp->ml_next+= tl;
	else
	{
	    /* Shift the unterminated segment. */
	    bcopy(mlp->ml_next, mlp->ml_buffer, tl + 1);
	    mlp->ml_length = tl;
	    mlp->ml_next = mlp->ml_buffer;

	    /* No more terminated lines. */
	    return NULL;
	}
    }

    return save_next;
}

