/* NeoStats - IRC Statistical Services 
** Copyright (c) 1999-2004 Adam Rutter, Justin Hammond
** http://www.neostats.net/
**
**  This program is free software; you can redistribute it and/or modify
**  it under the terms of the GNU General Public License as published by
**  the Free Software Foundation; either version 2 of the License, or
**  (at your option) any later version.
**
**  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 General Public License for more details.
**
**  You should have received a copy of the GNU General Public License
**  along with this program; if not, write to the Free Software
**  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
**  USA
**
** NeoStats CVS Identification
** $Id: kp_util.c 1365 2004-01-31 06:26:56Z Fish $
*/
/*
 * KEEPER: A configuration reading and writing library
 *
 * Copyright (C) 1999-2000 Miklos Szeredi
 * Email: mszeredi@inf.bme.hu
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library 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
 */

#include "kp_util.h"

#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <sys/stat.h>

/* Initialization is done */
static volatile int kp_inited = 0;

/* Only the local database is initialized */
static volatile int kp_local_inited = 0;

/* Index for the different sections */
enum {
	KPDB_GLOBAL,
	KPDB_LOCAL,
	KPDB_USER,

	KPDB_NUMDBS
};

/* String containing the temporary file name (not the path) */
static char *kp_tmpname = NULL;

/* String array containing the base path of the different sections */
static char *kp_basedirs[KPDB_NUMDBS];

#define GLOBALDIR "kpconf"	/* Default global database path */
#define LOCALDIR  "kpdata"	/* Local database path */
#define USERSUBDIR "kplang"	/* Default user database dir */
#define LOCKFILE ":lock:"	/* Lock file name */

/* ------------------------------------------------------------------------- 
 * Initialize the kp_tmpname variable with a unique hostname/pid string
 * ------------------------------------------------------------------------- */
static void kp_init_tmpname()
{
	const char *hostname;
	unsigned int pid;

	hostname = getenv("HOST");
	if (hostname == NULL)
		hostname = "localhost";

	pid = getpid();

	kp_tmpname = (char *) malloc_check(strlen(hostname) + 64);
	sprintf(kp_tmpname, ":tmp.%s.%u:", hostname, pid);
}

/* ------------------------------------------------------------------------- 
 * Create the local database dir
 * ------------------------------------------------------------------------- */
static char *kp_init_localdb(void)
{
	char *basedir;

	basedir = (char *) malloc_check(strlen(LOCALDIR) + 2);
	sprintf(basedir, "%s/", LOCALDIR);
	mkdir(basedir, 0755);

	return basedir;
}

/* ------------------------------------------------------------------------- 
 * Get the location of the user database dir, and create it
 * ------------------------------------------------------------------------- */
static char *kp_init_userdb(void)
{
#if 0
	const char *homeval;
	char *userdir;
	char *basedir;
	userdir = getenv("KEEPER_USERDIR");
	if (userdir != 0) {
		basedir = (char *) malloc_check(strlen(userdir) + 2);
		sprintf(basedir, "%s/", userdir);
	} else {
		homeval = getenv("HOME");
		if (homeval == NULL)
			homeval = "";
		/* FIXME: where to find home, if $HOME is not set? */

		basedir = (char *) malloc_check(strlen(homeval) + 1 +
						strlen(USERSUBDIR) + 2);
		sprintf(basedir, "%s/%s/", homeval, USERSUBDIR);
	}
	mkdir(basedir, 0755);

	return basedir;
#endif
	return USERSUBDIR;
}

/* ------------------------------------------------------------------------- 
 * Get the location of the global database dir, and create it
 * ------------------------------------------------------------------------- */
static char *kp_init_globaldb(void)
{
	char *basedir;
	char *globaldir;

	/* Be careful. This is a recursion */
#if 0
	globaldir = NULL;
	kp_get_string("l/keeper/globaldir", &globaldir);
	if (globaldir == NULL)
#endif
		globaldir = strdup_check(GLOBALDIR);

	basedir = (char *) malloc_check(strlen(globaldir) + 2);
	sprintf(basedir, "%s/", globaldir);
	free(globaldir);

	mkdir(basedir, 0755);

	return basedir;
}

/* ------------------------------------------------------------------------- 
 * Initialize the database, if it has not yet been initialized
 * ------------------------------------------------------------------------- */
static void kp_init(int dbindex)
{
	if (kp_inited)
		return;

	/* This is because of the recursion caused by having to read the local
	   database, before the global can be initialized */
	if (kp_local_inited && dbindex == KPDB_LOCAL)
		return;

	if (!kp_inited) {
		kp_init_tmpname();
		kp_basedirs[KPDB_LOCAL] = kp_init_localdb();
		kp_local_inited = 1;
		kp_basedirs[KPDB_USER] = kp_init_userdb();
		kp_basedirs[KPDB_GLOBAL] = kp_init_globaldb();
		kp_inited = 1;
	}
}

/* ------------------------------------------------------------------------- 
 * Free everything used by the keeper library
 * ------------------------------------------------------------------------- */
void kp_exit()
{
	free(kp_tmpname);
	free(kp_basedirs[KPDB_LOCAL]);

	_kp_clear_cache();
}

#define READ_BUF_SIZE 1024

/* ------------------------------------------------------------------------- 
 * Read a line of arbitary length. On end of file return NULL
 * ------------------------------------------------------------------------- */
static char *kp_read_line(FILE * fp)
{
	char buf[READ_BUF_SIZE];
	char *line = NULL;
	int eol;
	unsigned int linelen, buflen;

	eol = 0;
	linelen = 0;
	do {
		if (fgets(buf, READ_BUF_SIZE, fp) == NULL)
			return line;

		buflen = strlen(buf);
		if (buflen > 0 && buf[buflen - 1] == '\n') {
			buf[buflen - 1] = '\0';
			buflen--;
			eol = 1;
		}

		line = (char *)
		    check_ptr(realloc(line, linelen + buflen + 1));

		strcpy(line + linelen, buf);
		linelen += buflen;
	} while (!eol);

	return line;
}

/* ------------------------------------------------------------------------- 
 * Get an entry in the database file, the key name is the return value,
 * and the value of the key  is returned in *valuep. On end of file it
 * returns NULL
 * ------------------------------------------------------------------------- */
char *_kp_get_line(FILE * fp, char **valuep)
{
	char *buf, *s;
	int found;

	found = 0;
	do {
		buf = kp_read_line(fp);
		if (buf == NULL)
			return NULL;

		for (s = buf; *s && *s != SEP1; s++);
		if (*s)
			found = 1;
		else
			free(buf);
	} while (!found);

	*s = '\0';
	*valuep = s + 1;

	return buf;
}

/* ------------------------------------------------------------------------- 
 * Split the key path into a section, a file path, and a key name.
 * If the path contained a colon, *iskeyfile is set to 1
 * ------------------------------------------------------------------------- */
int _kp_get_path(const char *keypath, kp_path * kpp, char **keynamep,
		 int *iskeyfile)
{
	char *path;
	char *basedir;
	int dbindex;
	int ibeg;
	int i;

	switch (keypath[0]) {
	case 'g':
		dbindex = KPDB_GLOBAL;
		break;

	case 'l':
		dbindex = KPDB_LOCAL;
		break;

	case 'u':
		dbindex = KPDB_USER;
		break;

	default:
		return KPERR_NOKEY;
	}

	/* Skip over the rest of the database selection chars */
	keypath++;
	while (*keypath && *keypath != '/')
		keypath++;

	if (*keypath)
		keypath++;

	kp_init(dbindex);

	basedir = kp_basedirs[dbindex];
	kpp->dbindex = dbindex;

	ibeg = strlen(basedir);

	path = (char *) malloc_check(ibeg + strlen(keypath) + 1);
	sprintf(path, "%s%s", basedir, keypath);

	i = ibeg;
	for (; path[i]; i++) {
		if (path[i] == ':') {
			if (path[i + 1] == '/')
				*keynamep = &path[i + 2];
			else
				*keynamep = &path[i + 1];

			path[i] = '\0';
			kpp->path = path;
			if (iskeyfile != NULL)
				*iskeyfile = 1;
			return 0;
		}
	}

	kpp->path = path;
	*keynamep = &path[i];
	if (iskeyfile != NULL)
		*iskeyfile = 0;

	return 0;
}

/* ------------------------------------------------------------------------- 
 * Lock a section of the database for reading or writing. While the given
 * section is locked for write no other process can modify or read the
 * database
 * ------------------------------------------------------------------------- */
int _kp_lock_file(int dbindex, int iswrite)
{
#if 0				/* not needed for Neo? */
	struct flock flock;
	int res;
	char *basedir;
	char *lockfile;
	int lockfd;
	int mode;

	basedir = kp_basedirs[dbindex];
	lockfile =
	    (char *) malloc_check(strlen(basedir) + strlen(LOCKFILE) + 1);
	sprintf(lockfile, "%s%s", basedir, LOCKFILE);

	if (iswrite)
		mode = O_RDWR;
	else
		mode = O_RDONLY;

	lockfd = open(lockfile, O_CREAT | mode, 0644);
	free(lockfile);
	if (lockfd == -1) {
		/* Silently return if lockfile creation fails */
		return -1;
	}
	fcntl(lockfd, F_SETFD, FD_CLOEXEC);

	memset(&flock, 0, sizeof(flock));

	if (iswrite)
		flock.l_type = F_WRLCK;
	else
		flock.l_type = F_RDLCK;

	flock.l_whence = 0;
	flock.l_start = 0;
	flock.l_len = 0;

	do {
		res = fcntl(lockfd, F_SETLKW, &flock);
	}
	while (res == -1 && errno == EINTR);

	if (res == -1 && errno == EDEADLK) {
		fprintf(stderr, "keeper: Internal Error: Deadlock\n");
		abort();
	}

	return lockfd;
#endif
	return -1;
}

/* ------------------------------------------------------------------------- 
 * Unlock a section of the database
 * ------------------------------------------------------------------------- */
void _kp_unlock_file(int lockfd)
{
#if 0				/* not needed for Neo? */
	struct flock flock;

	memset(&flock, 0, sizeof(flock));

	flock.l_type = F_UNLCK;
	flock.l_whence = 0;
	flock.l_start = 0;
	flock.l_len = 0;

	fcntl(lockfd, F_SETLK, &flock);
	close(lockfd);
#endif
}

/* ------------------------------------------------------------------------- 
 * Return the length of the base directory for a given section
 * ------------------------------------------------------------------------- */
int _kp_get_ibeg(int dbindex)
{
	return strlen(kp_basedirs[dbindex]);
}

/* ------------------------------------------------------------------------- 
 * Return a temporary file path for a given section
 * ------------------------------------------------------------------------- */
char *_kp_get_tmpfile(int dbindex)
{
	char *basedir;
	char *filename;

	basedir = kp_basedirs[dbindex];
	filename =
	    (char *) malloc_check(strlen(basedir) + strlen(kp_tmpname) +
				  1);
	sprintf(filename, "%s%s", basedir, kp_tmpname);

	return filename;
}

/* ------------------------------------------------------------------------- 
 * Convert an errno value to a kperr_t value
 * ------------------------------------------------------------------------- */
int _kp_errno_to_kperr(int en)
{
	switch (en) {
	case ENAMETOOLONG:
	case EISDIR:
	case ENOTDIR:
	case ENOTEMPTY:
		return KPERR_BADKEY;

	case EACCES:
	case EROFS:
	case EPERM:
		return KPERR_NOACCES;

	case ENOENT:
		return KPERR_NOKEY;

	case EMFILE:
	case ENFILE:
	case ENOMEM:
	case ENOSPC:
		return KPERR_NOSPACE;

	case EFAULT:
		fprintf(stderr,
			"keeper: Internal Error: Function retured EFAULT\n");
		abort();

	default:
		return KPERR_BADDB;
	}
}

/* ------------------------------------------------------------------------- 
 * Add a key name to the key array, the array allocated to a size that will
 * hold the array and all the key names. But for now the key names are
 * allocated separate space.
 * ------------------------------------------------------------------------- */
static void kp_add_subkey(struct key_array *keys, char *name)
{
	char *namedup;
	int strsize;

	strsize = strlen(name) + 1;
	strsize = ROUND_TO(strsize, 8);
	keys->strsize += strsize;

	keys->num++;
	keys->array = (char **)
	    check_ptr(realloc(keys->array,
			      (keys->num + 1) * sizeof(char *) +
			      keys->strsize));

	namedup = strdup_check(name);

	keys->array[keys->num - 1] = namedup;
	keys->array[keys->num] = NULL;
}

/* ------------------------------------------------------------------------- 
 * Check if the key name already exists in the array, and if not add it
 * ------------------------------------------------------------------------- */
void _kp_add_subkey_check(struct key_array *keys, char *name)
{
	char **kp;
	unsigned int namelen;
	char *s;

	namelen = strlen(name);
	s = strchr(name, ':');
	if (s != NULL) {
		if (s[1] != '\0')
			return;
		namelen--;
	}

	for (kp = keys->array; *kp != NULL; kp++) {
		if (strncmp(*kp, name, namelen) == 0 &&
		    ((*kp)[namelen] == '\0' || (*kp)[namelen] == ':'))
			return;
	}
	kp_add_subkey(keys, name);
}

/* End of kp_util.c */
