/*
 * Copyright (C) 2010 Bernhard Reutner-Fischer <uclibc@uclibc.org>
 *
 * Licensed under LGPL v2.1 or later, see the file COPYING.LIB in this tarball.
 */

/* /etc/services
#   service-name   port/protocol   [aliases ...]
discard               9/udp        sink null

service-name: case sensitive friendly name of the service
port: decimal port number
protocol: protocols(5) compatible entry
aliases: case sensitive optional space or tab separated list of other names
*/

#include <features.h>
#include <netdb.h>
#include <string.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>
#include "internal/parse_config.h"

#include <bits/uClibc_mutex.h>
__UCLIBC_MUTEX_STATIC(mylock, PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP);

#define MINTOKENS	3
#define	MAXALIASES	8	/* we seldomly need more than 1 alias */
#define MAXTOKENS	(MINTOKENS + MAXALIASES + 1)
#define BUFSZ		(255)	/* one line */
#define SBUFSIZE	(BUFSZ + 1 + (sizeof(char *) * MAXTOKENS))

static parser_t *servp = NULL;
static struct servent serve;
static char *servbuf = NULL;
static size_t servbuf_sz = SBUFSIZE;
static smallint serv_stayopen;

void setservent(int stayopen)
{
	__UCLIBC_MUTEX_LOCK(mylock);
	if (servp)
		config_close(servp);
	servp = config_open(_PATH_SERVICES);
	if (stayopen)
		serv_stayopen = 1;
	__UCLIBC_MUTEX_UNLOCK(mylock);
}
libc_hidden_def(setservent)

void endservent(void)
{
	__UCLIBC_MUTEX_LOCK(mylock);
	if (servp) {
		config_close(servp);
		servp = NULL;
	}
	serv_stayopen = 0;
	__UCLIBC_MUTEX_UNLOCK(mylock);
}
libc_hidden_def(endservent)

int getservent_r(struct servent *result_buf,
				 char *buf, size_t buflen, struct servent **result)
{
	char **tok = NULL;
	const size_t aliaslen = sizeof(char *) * MAXTOKENS;
	int ret = ERANGE;

	*result = NULL;
	if (buflen < aliaslen
		|| (buflen - aliaslen) < BUFSZ + 1)
		goto DONE_NOUNLOCK;

	__UCLIBC_MUTEX_LOCK(mylock);
	ret = ENOENT;
	if (servp == NULL)
		setservent(serv_stayopen);
	if (servp == NULL)
		goto DONE;

	servp->data = buf;
	servp->data_len = aliaslen;
	servp->line_len = buflen - aliaslen;
	/* <name>[[:space:]]<port>/<proto>[[:space:]][<aliases>] */
	if (!config_read(servp, &tok, MAXTOKENS - 1, MINTOKENS, "# \t/", PARSE_NORMAL)) {
		goto DONE;
	}
	result_buf->s_name = *(tok++);
	result_buf->s_port = htons((u_short) atoi(*(tok++)));
	result_buf->s_proto = *(tok++);
	result_buf->s_aliases = tok;
	*result = result_buf;
	ret = 0;
 DONE:
	__UCLIBC_MUTEX_UNLOCK(mylock);
 DONE_NOUNLOCK:
	errno = ret;
	return errno;
}
libc_hidden_def(getservent_r)

static void __initbuf(void)
{
	if (!servbuf)
		servbuf = malloc(SBUFSIZE);
	if (!servbuf)
		abort();
}

struct servent *getservent(void)
{
	struct servent *result;

	__initbuf();
	getservent_r(&serve, servbuf, servbuf_sz, &result);
	return result;
}

int getservbyname_r(const char *name, const char *proto,
					struct servent *result_buf, char *buf, size_t buflen,
					struct servent **result)
{
	register char **cp;
	int ret;

	__UCLIBC_MUTEX_LOCK(mylock);
	setservent(serv_stayopen);
	while (!(ret = getservent_r(result_buf, buf, buflen, result))) {
		if (strcmp(name, result_buf->s_name) == 0)
			goto gotname;
		for (cp = result_buf->s_aliases; *cp; cp++)
			if (strcmp(name, *cp) == 0)
				goto gotname;
		continue;
 gotname:
		if (proto == 0 || strcmp(result_buf->s_proto, proto) == 0)
			break;
	}
	if (!serv_stayopen)
		endservent();
	__UCLIBC_MUTEX_UNLOCK(mylock);
	return *result ? 0 : ret;
}
libc_hidden_def(getservbyname_r)

struct servent *getservbyname(const char *name, const char *proto)
{
	struct servent *result;

	__initbuf();
	getservbyname_r(name, proto, &serve, servbuf, servbuf_sz, &result);
	return result;
}


int getservbyport_r(int port, const char *proto,
					struct servent *result_buf, char *buf,
					size_t buflen, struct servent **result)
{
	int ret;

	__UCLIBC_MUTEX_LOCK(mylock);
	setservent(serv_stayopen);
	while (!(ret = getservent_r(result_buf, buf, buflen, result))) {
		if (result_buf->s_port != port)
			continue;
		if (proto == 0 || strcmp(result_buf->s_proto, proto) == 0)
			break;
	}
	if (!serv_stayopen)
		endservent();
	__UCLIBC_MUTEX_UNLOCK(mylock);
	return *result ? 0 : ret;
}
libc_hidden_def(getservbyport_r)

struct servent *getservbyport(int port, const char *proto)
{
	struct servent *result;

	__initbuf();
	getservbyport_r(port, proto, &serve, servbuf, servbuf_sz, &result);
	return result;
}
libc_hidden_def(getservbyport)