/* $Id: pkgconfig.c 1971 2007-04-15 14:13:18Z coudercd $ */

/*
 * Copyright (c) 2003-2005 Damien Couderc
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *    - Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *    - 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.
 *    - Neither the name of the copyright holder(s) nor the names of its
 *      contributors may be used to endorse or promote products derived
 *      from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "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
 * COPYRIGHT HOLDERS OR CONTRIBUTORS 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 it first as if it was <sys/types.h> - this will avoid errors */
#include "compat/pmk_sys_types.h"

#include <dirent.h>
#include <errno.h>
#include <stdlib.h>

#include "compat/pmk_ctype.h"
#include "compat/pmk_stdio.h"
#include "compat/pmk_stdbool.h"
#include "compat/pmk_string.h"
#include "common.h"
#include "hash.h"
#include "hash_tools.h"
#include "parse.h"
#include "pkgconfig.h"
#include "premake.h"


/*#define PKGCFG_DEBUG	1*/

/*
 * env PKG_CONFIG_PATH => list of colon separated path
 * env PKG_CONFIG_LIBDIR => replace default location (aka prefix/lib/pkgconfig)
 */

/**********
 constants
************************************************************************/

#define PKGCFG_HTABLE_SIZE	32
#define PKGCFG_TOK_ADDCT	1

#define PKGCFG_KW_NAME	0
#define PKGCFG_KW_DESCR	1
#define PKGCFG_KW_VERS	2
#define PKGCFG_KW_REQS	3
#define PKGCFG_KW_LIBS	4
#define PKGCFG_KW_CFLGS	5
#define PKGCFG_KW_CFLCT	6

/**********
 variables
************************************************************************/

/* pc file keywords */
pkgkw	kw_pkg[] = {
		{"Name",	PKGCFG_KW_NAME},
		{"Description",	PKGCFG_KW_DESCR},
		{"Version",	PKGCFG_KW_VERS},
		{"Requires",	PKGCFG_KW_REQS},
		{"Libs",	PKGCFG_KW_LIBS},
		{"Cflags",	PKGCFG_KW_CFLGS},
		{"CFlags",	PKGCFG_KW_CFLGS},
		{"Conflicts",	PKGCFG_KW_CFLCT}
};
size_t	nb_pkgkw = sizeof(kw_pkg) / sizeof(pkgkw);


/**********
 functions
************************************************************************/

/***************
 pkgcell_init()

 DESCR
	initialize pkgcell structure

 IN
	NONE

 OUT
	pkgcell structure or NULL
************************************************************************/

pkgcell *pkgcell_init(void) {
	pkgcell *ppc;

	ppc = (pkgcell *) malloc(sizeof(pkgcell));
	if (ppc == NULL) {
		errorf("cannot initialize pkgcell.");
		return(NULL);
	}

	ppc->variables = hash_init(PKGCFG_HTABLE_SIZE);
	if (ppc->variables == NULL) {
		free(ppc);
		errorf("cannot initialize pkgcell hash table.");
		return(NULL);
	}

	ppc->cflags = NULL;
	ppc->libs = NULL;
	ppc->requires = NULL;

	return(ppc);
}


/******************
 pkgcell_destroy()

 DESCR
	free pkgcell structure

 IN
	ppc : pkgcell structure

 OUT
	NONE
************************************************************************/

void pkgcell_destroy(pkgcell *ppc) {
	if (ppc->name != NULL)
		free(ppc->name);

	if (ppc->descr != NULL)
		free(ppc->descr);

	if (ppc->version != NULL)
		free(ppc->version);

	if (ppc->requires != NULL)
		free(ppc->requires);

	da_destroy(ppc->cflags);
	da_destroy(ppc->libs);
	hash_destroy(ppc->variables);

	free(ppc);
}


/***************
 pkgdata_init()

 DESCR
	initialize pkgdata structure

 IN
	NONE

 OUT
	pkgdata structure or NULL
************************************************************************/

pkgdata *pkgdata_init(void) {
	pkgdata	*ppd;

	ppd = (pkgdata *) malloc(sizeof(pkgdata));
	if (ppd == NULL)
		return(NULL);

	/* init pc files hash table */
	ppd->files = hash_init(PKGCFG_HT_SIZE);
	if (ppd->files == NULL) {
		free(ppd);
		return(NULL);
	}

	/* init package cells hash table */
	ppd->cells = hash_init_adv(PKGCFG_HT_SIZE, NULL,
		(void (*)(void *))pkgcell_destroy, NULL);
	if (ppd->cells == NULL) {
		hash_destroy(ppd->files);
		free(ppd);
		return(NULL);
	}

	/* init recursed modules hash table */
	ppd->mods = da_init();
	if (ppd->mods == NULL) {
		hash_destroy(ppd->files);
		hash_destroy(ppd->cells);
		free(ppd);
		return(NULL);
	}

	return(ppd);
}


/******************
 pkgdata_destroy()

 DESCR
	free pkgdata structure

 IN
	ppd : pkgdata structure

 OUT
	NONE
************************************************************************/

void pkgdata_destroy(pkgdata *ppd) {
#ifdef PKGCFG_DEBUG
	debugf("clean pc files hash");
#endif
	hash_destroy(ppd->files);

#ifdef PKGCFG_DEBUG
	debugf("clean cells hash");
#endif
	hash_destroy(ppd->cells);

#ifdef PKGCFG_DEBUG
	debugf("clean modules list");
#endif
	da_destroy(ppd->mods);

#ifdef PKGCFG_DEBUG
	debugf("clean structure");
#endif
	free(ppd);
}


/*************
 skip_blank()

 DESCR
	skip blank character(s)

 IN
	pstr: current parsing cursor

 OUT
	new parsing cursor
***********************************************************************/

char *skip_blank(char *pstr) {
	while (isblank(*pstr) != 0) {
		pstr++;
	}
	return(pstr);
}


/***********
 scan_dir()

 DESCR
	scan given directory for pc files

 IN
	dir : directory to scan
	ppd : pkgdata structure

 OUT
	boolean
************************************************************************/

bool scan_dir(char *dir, pkgdata *ppd) {
	struct dirent	*pde;
	DIR				*pdir;
	char			*pstr,
					 buf[MAXPATHLEN],
					 fpath[MAXPATHLEN];
	size_t			 l;

	/* open directory */
	pdir = opendir(dir);
	if (pdir == NULL) {
		errorf("cannot open '%s' directory : %s.",
			dir, strerror(errno));
		return(false);
	}

	/* parse directory */
	do {
		pde = readdir(pdir);
		if (pde != NULL) {
			/* got an entry */
			pstr = pde->d_name;
			l = strlen(pstr);
			/* check if it's a .pc file */
			if ((pstr[l-3] == '.') && (pstr[l-2] == 'p')
						&& (pstr[l-1] == 'c')) {

				/* build pkg name */
				if (strlcpy_b(buf, pstr,
						sizeof(buf)) == false) {
					/* XXX err msg ? */
					closedir(pdir);
					return(false);
				}
				buf[l-3] = CHAR_EOS;

				/* build full path of pc file */
				strlcpy(fpath, dir, sizeof(fpath)); /* no check */
				strlcat(fpath, "/", sizeof(fpath)); /* no check */
				if (strlcat_b(fpath, pstr, sizeof(fpath)) == false) {
					/* XXX err msg ? */
					closedir(pdir);
					return(false);
				}

				hash_update_dup(ppd->files, buf, fpath);
				/* XXX detect if the package has already been detected ? */

#ifdef PKGCFG_DEBUG
				debugf("add module '%s' with file '%s'", buf, pstr);
#endif
			}
		}
	} while (pde != NULL);

	closedir(pdir);

	return(true);
}


/**************
 pkg_collect()

 DESCR
	collect packages data

 IN
	pkglibdir : default packages data directory
	ppd : pkgdata structure

 OUT
	return : boolean
************************************************************************/

bool pkg_collect(char *pkglibdir, pkgdata *ppd) {
	char			*pstr;
	dynary			*ppath,
					*ppcpath;
	unsigned int	 i;

	pstr = getenv(PMKCFG_ENV_PATH);
	if (pstr != NULL) {
		/* also scan path provided in env variable */
		ppath = str_to_dynary(pstr, PKGCFG_CHAR_PATH_SEP);

		for (i = 0 ; i < da_usize(ppath) ; i++) {
			pstr = da_idx(ppath, i);
			if (scan_dir(pstr, ppd) == false) {
				da_destroy(ppath);
				return(false);
			}
		}

		da_destroy(ppath);
	}

	/* check if pkg-config library directory has been set in env variable */
	pstr = getenv(PMKCFG_ENV_LIBDIR);
	if (pstr == NULL) {
		/* env variable is not set, getting pmk.conf variable */
		ppcpath = str_to_dynary(pkglibdir, PKGCFG_CHAR_PATH_SEP);
	} else {
		/* else use env variable */
		ppcpath = str_to_dynary(pstr, PKGCFG_CHAR_PATH_SEP);
	}

	/* scan all path for pc files */
	for (i = 0 ; i < da_usize(ppcpath) ; i++) {
		/* get directory */
		pstr = da_idx(ppcpath, i);

		/* scan directory */
		if (scan_dir(pstr, ppd) == false) {
			return(false);
		}
	}

	return(true);
}


/****************
 parse_keyword()

 DESCR
	parse keyword and set data

 IN
	ppc : pkgcell structure
	kword : keyword
	value : value to assign

 OUT
	boolean
************************************************************************/

bool parse_keyword(pkgcell *ppc, char *kword, char *value) {
	unsigned int	i;

	for (i = 0 ; i < nb_pkgkw ; i ++) {
		if (strncmp(kword, kw_pkg[i].kw_name, sizeof(kword)) == 0) {
			/* assgin keyword */
			switch (kw_pkg[i].kw_id) {
				case PKGCFG_KW_NAME :
					ppc->name = strdup(value);
					if (ppc->name == NULL) {
						errorf(ERRMSG_MEM);
						return(false);
					}
					break;

				case PKGCFG_KW_DESCR :
					ppc->descr = strdup(value);
					if (ppc->descr == NULL) {
						errorf(ERRMSG_MEM);
						return(false);
					}
					break;

				case PKGCFG_KW_VERS :
					ppc->version = strdup(value);
					if (ppc->version == NULL) {
						errorf(ERRMSG_MEM);
						return(false);
					}
					break;

				case PKGCFG_KW_REQS :
					if (*value != CHAR_EOS) {
						ppc->requires = strdup(value);
						if (ppc->requires == NULL) {
							errorf(ERRMSG_MEM);
							return(false);
						}
					}
					break;

				case PKGCFG_KW_CFLGS :
					ppc->cflags = str_to_dynary(value, ' ');
					break;

				case PKGCFG_KW_LIBS :
					ppc->libs = str_to_dynary(value, ' ');
					break;

				case PKGCFG_KW_CFLCT :
					/* unused */
					break;

				default :
					break;
			}

			return(true);
		}
	}

	return(true);
}


/********************
 process_variables()

 DESCR
	process string to substitute variables with their values

 IN
	pstr : string to process
	pht : hash table where variables are stored

 OUT
	new string or NULL
************************************************************************/

char *process_variables(char *pstr, htable *pht) {
	bool	 bs = false;
	char	 buf[OPT_VALUE_LEN],
			 var[OPT_NAME_LEN],
			*pvar,
			*pbuf;
	size_t	 size;

	size = sizeof(buf);
	pbuf = buf;

	while ((*pstr != CHAR_EOS) && (size > 0)) {
		switch(*pstr) {
			case '\\' :
				bs = true;
				pstr++;
				break;

			case '$' :
				if (bs == false) {
					/* found variable */
					pstr++;
					/* skip '{' */
					pstr++;
					pstr = parse_idtf(pstr, var, sizeof(var));
					if (pstr == NULL) {
						/* debugf("parse_idtf returned null."); */
						return(NULL);
					} else {
						/* check if identifier exists */
						pvar = hash_get(pht, var);
						if (pvar != NULL) {
							/* identifier found, append value */
							while ((*pvar != CHAR_EOS) && (size > 0)) {
								*pbuf = *pvar;
								pbuf++;
								pvar++;
								size--;
							}

							/* skip '}' */
							pstr++;
						}
					}
				} else {
					/* copy character */
					*pbuf = *pstr;
					pbuf++;
					pstr++;
					size--;
					bs = false;
				}
				break;

			default :
				if (bs == true) {
					*pbuf = '\\';
					pbuf++;
					pstr++;
					size--;
					if (size == 0) {
						/* debugf("overflow."); */
						return(NULL);
					}
					bs = false;
				}
				/* copy character */
				*pbuf = *pstr;
				pbuf++;
				pstr++;
				size--;
				break;
		}
	}

	if (size == 0) {
		/* debugf("overflow."); */
		return(NULL);
	}

	*pbuf = CHAR_EOS;
	return(strdup(buf));
}


/****************
 parse_pc_file()

 DESCR
	parse pc file

 IN
	pcfile : file to parse

 OUT
	pkgcell structure or NULL
************************************************************************/

pkgcell *parse_pc_file(char *pcfile) {
	FILE	*fp;
	char	*pstr,
			*pps,
			 buf[TMP_BUF_LEN],
			 line[TMP_BUF_LEN];
	pkgcell	*ppc;

	fp = fopen(pcfile, "r");
	if (fp == NULL) {
		errorf("cannot open .pc file '%s' : %s.", pcfile, strerror(errno));
		return(NULL);
	}

	ppc = pkgcell_init();
	if (ppc == NULL) {
		errorf("cannot initialize pkgcell.");
		return(NULL);
	}

	/* main parsing */
	while (get_line(fp, line, sizeof(line)) == true) {
		/* collect identifier */
		pstr = parse_idtf(line, buf, sizeof(buf));

		switch (*pstr) {
			case ':' :
				/* keyword */
				pstr++;
				pstr = skip_blank(pstr);

				/* process variables */
				pps = process_variables(pstr, ppc->variables);
#ifdef PKGCFG_DEBUG
				debugf("keyword = '%s', value = '%s', string = '%s'", buf, pps, pstr);
#endif
				/* set variable with parsed data */
				if (parse_keyword(ppc, buf, pps) == false) {
					errorf("cannot fill pkgcell structure (keyword).");
					pkgcell_destroy(ppc);
					return(NULL);
				}
				break;

			case '=' :
				/* variable */
				pstr++;
				pstr = skip_blank(pstr);

				/* process variables */
				pps = process_variables(pstr, ppc->variables);
#ifdef PKGCFG_DEBUG
				debugf("variable = '%s', value = '%s', string = '%s'", buf, pps, pstr);
#endif
				/* store variable in hash */
				if (hash_update_dup(ppc->variables, buf, pps) == HASH_ADD_FAIL) {
					errorf("cannot fill pkgcell structure (variables).");
					pkgcell_destroy(ppc);
					return(NULL);
				}

				break;

			default :
				/* unknown or empty line */
#ifdef PKGCFG_DEBUG
				debugf("unknown = '%s'", line);
#endif
				break;
		}
	}

	fclose(fp);

	return(ppc);
}


/***************
 pkg_cell_add()

 DESCR
	insert a package cell and return a pointer on it

 IN
	mod : module name to process
	ppd : packages data structure

 OUT
	pkgcell structure or NULL
************************************************************************/

pkgcell *pkg_cell_add(pkgdata *ppd, char *mod) {
	char		*pcf;
	pkgcell		*ppc;

	/* get pc file name */
	pcf = hash_get(ppd->files, mod); /* XXX check if NULL ?? */

	/* parse pc file */
	ppc = parse_pc_file(pcf);
	if (ppc == NULL)
		return(NULL);

	/* store pkgcell in hash */
	if (hash_update(ppd->cells, mod, ppc) == HASH_ADD_FAIL) {
		return(NULL);
#ifdef PKGCFG_DEBUG
	} else {
		debugf("adding pkgcell for '%s'", mod);
#endif
	}

	/* add module in list */
	da_push(ppd->mods, strdup(mod));

	if (ppc->requires != NULL) {
#ifdef PKGCFG_DEBUG
		debugf("pkgcell requires = '%s'", ppc->requires);
#endif
		if (pkg_recurse(ppd, ppc->requires) == false) {
			pkgcell_destroy(ppc);
			return(NULL);
		}
	}

	return(ppc);
}


/**************
 pkg_recurse()

 DESCR
	recurse packages

 IN
	ppd : packages data structure
	reqs : requires string

 OUT
	boolean
************************************************************************/

bool pkg_recurse(pkgdata *ppd, char *reqs) {
	char			*mod;
	dynary			*pda;
	unsigned int	 i;

#ifdef PKGCFG_DEBUG
debugf("recursing '%s'", reqs);
#endif

	pda = str_to_dynary_adv(reqs, " ,");
	if (pda == NULL) {
		errorf("failed to process requirements in pkg_recurse().");
		return(false);
	}

	for (i = 0 ; i < da_usize(pda) ; i++) {
		/* get module name */
		mod = da_idx(pda, i);

		/*  check if module has been already processed */
		if (hash_get(ppd->cells, mod) == NULL) {
			if (pkg_cell_add(ppd, mod) == NULL) {
				errorf("failed to add a cell in pkg_recurse().");
				da_destroy(pda);
				return(false);
			}
		}
	}

	da_destroy(pda);

	return(true);
}


/********************
 pkg_single_append()

 DESCR
	append module name if it does not already exists

 IN
	ostr : modules list string
	astr : module to append

 OUT
	return : new list of modules or NULL
************************************************************************/

char *pkg_single_append(char *ostr, char *astr) {
	char	*pstr,
			*buf;
	size_t	 s;

#ifdef PKGCFG_DEBUG
	debugf("single_append '%s', '%s'", ostr, astr);
#endif

	if (*astr == CHAR_EOS)
		return(ostr);

	if ((*ostr != CHAR_EOS) && (ostr != NULL)) {
		pstr = strstr(ostr, astr);
		while (pstr != NULL) {
			pstr = pstr + strlen (astr);
			if ((*pstr == ' ') || (*pstr == CHAR_EOS)) {
				/* found existing value */
				return(ostr);
			}
			pstr = strstr(pstr, astr);
		}

		/* 2 is for the separator and the end of file char */
		s = strlen(ostr) + strlen(astr) + 2;
		buf = (char *) malloc(s);
		if (buf == NULL)
			return(NULL);

		if (snprintf_b(buf, s, "%s %s", ostr, astr) == false) {
			return(NULL);
		}

		free(ostr);

	} else {
		buf = strdup(astr);
	}

	return(buf);
}


/*****************
 pkg_get_cflags()

 DESCR
	get recursed list of cflags

 IN
	ppd : packages data structure

 OUT
	cflags string
************************************************************************/

char *pkg_get_cflags(pkgdata *ppd) {
	return(pkg_get_cflags_adv(ppd, PKGCFG_CFLAGS_ALL));
}


/*********************
 pkg_get_cflags_adv()

 DESCR
	get recursed list of cflags with output filtering

 IN
	ppd : packages data structure
	opts : output filtering options

 OUT
	cflags string
************************************************************************/

char *pkg_get_cflags_adv(pkgdata *ppd, unsigned int opts) {
	char			*cflags = "",
					*pstr;
	dynary			*pda;
	pkgcell			*ppc;
	unsigned int	 i,
					 j,
					 o;

	pda = ppd->mods;

	for (i = 0 ; i < da_usize(pda) ; i++) {
		/* get module name */
		ppc = hash_get(ppd->cells, da_idx(pda, i));

		/* append each element but avoid duplicate*/
		for (j = 0 ; j < da_usize(ppc->cflags) ; j++) {
			/* get element */
			pstr = da_idx(ppc->cflags, j);

			/* check type of element */
			if (pstr[0] == '-' && pstr[1] == 'I') {
				/* include path */
				o = PKGCFG_CFLAGS_I;
			} else {
				/* other */
				o = PKGCFG_CFLAGS_o;
			}

			/* append if not filtered */
			if ((opts & o) != 0)
				cflags = pkg_single_append(cflags, pstr);
		}
	}

	return(cflags);
}


/***************
 pkg_get_libs()

 DESCR
	get recursed list of libs

 IN
	ppd : packages data structure

 OUT
	libs string
************************************************************************/

char *pkg_get_libs(pkgdata *ppd) {
	return(pkg_get_libs_adv(ppd, PKGCFG_LIBS_ALL));
}


/*******************
 pkg_get_libs_adv()

 DESCR
	get recursed list of libs with output filtering

 IN
	ppd : packages data structure
	opts : output filtering options

 OUT
	libs string
************************************************************************/

char *pkg_get_libs_adv(pkgdata *ppd, unsigned int opts) {
	char			*libs = "",
					*pstr;
	dynary			*pda;
	pkgcell			*ppc;
	unsigned int	 i,
					 j,
					 o;

	pda = ppd->mods;

	for (i = 0 ; i < da_usize(pda) ; i++) {
		/* get module name */
		ppc = hash_get(ppd->cells, da_idx(pda, i));

		/* append each element but avoid duplicate*/
		for (j = 0 ; j < da_usize(ppc->libs) ; j++) {
			/* get element */
			pstr = da_idx(ppc->libs, j);

			/* check type of element */
			if (pstr[0] == '-') {
				switch (pstr[1]) {
					case 'L':
						/* lib path */
						o = PKGCFG_LIBS_L;
						break;

					case 'l':
						/* lib name */
						o = PKGCFG_LIBS_l;
						break;

					default:
						/* other */
						o = PKGCFG_LIBS_o;
						break;
				}
			} else {
				/* other */
				o = PKGCFG_LIBS_o;
			}

			/* append if not filtered */
			if ((opts & o) != 0)
				libs = pkg_single_append(libs, pstr);
		}
	}

	return(libs);
}


/*****************
 pkg_mod_exists()

 DESCR
	check if the given module exists

 IN
	mod : module to check

 OUT
	boolean
************************************************************************/

bool pkg_mod_exists(pkgdata *ppd, char *mod) {
	if (hash_get(ppd->files, mod) == NULL) {
		return(false);
	} else {
		return(true);
	}
}


/******************
 compare_version()

 DESCR
	compare two version strings

 IN
	vref : reference version
	vcomp : version to check

 OUT
	<0 if vref is greater than vcomp
	=0 if vref is equal to vcomp
	>0 if vref is smaller than vcomp
************************************************************************/

int compare_version(char *vref, char *vcomp) {
	bool		 bexit = false;
	char		*sr,
			*sc;
	dynary		*vr,
			*vc;
	int		 delta,
			 ref,
			 cmp;
	unsigned int	 i = 0;
	unsigned long	 tl;

	/* need to check da_* returns */
	vr = str_to_dynary(vref, VERSION_CHAR_SEP);
	if (vr == NULL) {
		errorf("cannot parse reference version '%s'.", vref);
		return(false);
	}
	vc = str_to_dynary(vcomp, VERSION_CHAR_SEP);
	if (vc == NULL) {
		errorf("cannot parse comparison version '%s'.", vcomp);
		return(false);
	}

	while (bexit == false) {
		/* process reference version */
		sr = da_idx(vr, i);
		if (sr != NULL) {
			/* convert string */
			if (str_to_ulong(sr, 10, &tl) == false) {
				errorf("cannot get numerical value of '%s'.", sr);
				return(false);
			}
			ref = (int) tl;
		} else {
			/* end of version string */
			ref = 0;
			bexit = true;
		}

		/* process compared version */
		sc = da_idx(vc, i);
		if (sc != NULL) {
			if (str_to_ulong(sc, 10, &tl) == false) {
				errorf("cannot get numerical value of '%s'.", sc);
				return(false);
			}
			cmp = (int) tl;
		} else {
			/* end of version string */
			cmp = 0;
			bexit = true;
		}

		/* compare versions */
		delta = cmp - ref;

		if (delta != 0) {
			/* not equal end of comparison */
			bexit = true;
		} else {
			i++;
		}
	}

	/* destroy dynaries */
	da_destroy(vr);
	da_destroy(vc);

	return(delta);
}


syntax highlighted by Code2HTML, v. 0.9.1