/* $Id: util.c,v 1.31 2003/05/18 21:58:26 aa5779 Exp $ */
/* util.c -- a collection of miscellaneous useful routines */
/*
	Copyright (C) 2001, 2002, 2003 Artem V. Andreev (artem@AA5779.spb.edu)

    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

*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <ctype.h>
#include <stdio.h>
#include <fcntl.h>
#include <setjmp.h>
#ifdef HAVE_FLOCK
#include <sys/file.h>
#endif
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#else
#include <stdio.h>
#endif
#ifdef HAVE_MMAP
#include <sys/mman.h>
#endif
#include <unistd.h>
#include <errno.h>
#include "lists.h"
#include "dstring.h"
#include "util.h"
/*** \file
This file contains various routines for file handling, string tokenizing,
memory management etc

\include[util.h]
*/

/*** \group[Miscellaneous functions] */
/*** \function
\returns the version of \em[libutils] e. g. \sample[0.7.15a]
*/
const char *libutils_version(void) { return VERSION; }

/*** \function
\returns the modification date of original source tree as \sample[YYYY/MM/DD]
*/
const char *libutils_srcdate(void) { return translate_cvs_date("$Date: 2003/05/18 21:58:26 $"); }

static int line_no;

/*** \function
Sets the current line number for parsing routines to \var[newl], if it is non-negative
\returns the previous line number
*/
int set_line_no(int newnl) 
{
	int old = line_no;
	if(newnl >= 0)
		line_no = newnl;
	return old;
}

#ifndef HAVE_STRUPR
/*** \function
Converts \var[str] to upper case in-place.
\note Incompatible with common DOS \code[strupr] -- the latter returns \type[char *]
*/
void strupr(char *str)
{
    while(*str)
    {
		*str = toupper(*str);
		str++;
    }
}
#endif

#ifndef HAVE_SETENV
/*** \function
Emulates \ref[setenv(3)] if it is not found in the standard library.
Not present otherwise.
*/
int setenv(const char *name, const char *value, int overwrite)
{
	char *new;
	if(!overwrite && getenv(name))
		return 0;
	new = malloc(strlen(name) + strlen(value) + 2);
	strcpy(new, name);
	strcat(new, "=");
	strcat(new, value);
	return putenv(new);
}
#endif

/*** \function
\returns the length of the file addressed by \var[fh] handle; 
-1 in case of an error
*/
off_t filelength(int fd)
{
#ifdef HAVE_FSTAT
	struct stat st;
	if(fstat(fd, &st) || !S_ISREG(st.st_mode))
		return -1;
	return st.st_size;
#else
	off_t pos = lseek(fd, 0, SEEK_CUR);
	off_t size = lseek(fd, 0, SEEK_END);
	lseek(fd, pos, SEEK_SET);
	return size;
#endif
}

int util_lock_max_tries = 3;
int util_lock_delay     = 1;

int lock_file(int fd, int writing)
{
#ifdef CAN_LOCK
	int tries = util_lock_max_tries;
#ifdef HAVE_FLOCK
	while(flock(fd, (writing ? LOCK_EX : LOCK_SH) | LOCK_NB))
	{
		if(errno != EWOULDBLOCK || !--tries)
			recover_error("can't acquire %s lock: %s", writing ? "write" : "read", strerror(errno));
		sleep(util_lock_delay);			
	}
#else
	struct flock lck;
	lck.l_type = writing ? F_WRLCK : F_RDLCK;
	lck.l_whence = SEEK_SET;
	lck.l_start = lck.l_len = 0;
	while(fcntl(fd, F_SETLK, &lck))
	{
		if((errno != EAGAIN && errno != EACCES) || !--tries)
			recover_error("can't acquire %s lock: %s", writing ? "write" : "read", strerror(errno));
		sleep(util_lock_delay);
	}
#endif
#endif
	return fd;
}

int unlock_file(int fd)
{
#ifdef CAN_LOCK
#ifdef HAVE_FLOCK
	if(flock(fd, LOCK_UN))
		recover_error("can't release lock: %s", strerror(errno));
#else
	struct flock lck;
	lck.l_type = F_UNLCK;
	lck.l_whence = SEEK_SET;
	lck.l_start = lck.l_len = 0;
	if(fcntl(fd, F_SETLK, &lck))
		recover_error("can't release lock: %s", strerror(errno));
#endif
#endif
	return fd;
}

/*** \function
\returns 1 if \var[ch] is a valid XML name character (letter, digit, hyphen,
underscore or colon); 0 otherwise
*/
int isnamesym(int ch)
{
    return isalnum(ch) || ch == '.' || ch == '-' || ch == '_' || ch == ':';
}

/*** \function
\returns 1 if \code[isnamesym(ch) != 0] and \var[ch] is not a colon
*/
int isplainnamesym(int ch)
{
    return isalnum(ch) || ch == '.' || ch == '-' || ch == '_';
}

/*** \ifdef[maintainer] */
/*** \group[Version comparison functions] */

/*** \enum
Enumeration of classes of version suffixes. The order of items determines the precedence
when comparing.
*/
enum special_version_suffixes { vss_alpha, vss_beta, vss_pre, vss_final, vss_empty };

/*** \function
Analyzes a version suffix into its class and possibly the following number
stored into \var[numpost]. The following suffix patterns are recognized:
\item[alpha]
\item[beta]
\item[pre[0-9]*]
\item[final]
*/
static int detect_special_suffix(const char *suffix, int *numpost)
{
	if(*suffix == '-')
		suffix++;
	*numpost = 0;
	if(!*suffix)
		return vss_empty;
	if(!strcmp(suffix, "alpha"))
		return vss_alpha;
	if(!strcmp(suffix, "beta"))
		return vss_beta;
	if(!strcmp(suffix, "final"))
		return vss_final;
	if(!strncmp(suffix, "pre", 3))
	{
		suffix += 3;
		if(*suffix == '-')
			suffix++;
		*numpost = strtol(suffix, NULL, 10);
		return vss_pre;
	}
	return vss_empty;
}

/*** \function
Compares version suffixes. If they are special suffixes, they are compared
by precedence (see above) and then by numeric postfixes if present.
Otherwise lexical ordering is used
\returns a negative value if \var[ver1] is less than \var[ver2];
zero if they are equal, a positive value otherwise
*/
static int compare_ver_suffixes(const char *ver1, const char *ver2)
{
	int vs1, vs2, vsn1, vsn2;
	vs1 = detect_special_suffix(ver1, &vsn1);
	vs2 = detect_special_suffix(ver2, &vsn2);
	if(vs1 != vs2)
		return vs1 - vs2;
	if(vs1 == vss_empty)
		return strcmp(ver1, ver2);
	return vsn1 - vsn2;
}
/*** \endif */

/**** \function
Compares two strings as version designators.
They should have the following structure: 
\sample[xx[\.yy]*(-?suffix)?].
Suffix may be a alphanumeric modifier or a special suffix.
\ifdef[maintainer]\else
Special suffixes are the following (in the order they're compared):
\item alpha
\item beta
\item pre[0-9]*
\item final
\endif
\returns negative value if \var[ver1] < \var[ver2], 
0 if they are equal, positive value otherwise
*/
int versioncmp(const char *ver1, const char *ver2)
{
	int vno1, vno2;
	while(*ver1 || *ver2)
	{
		vno1 = strtol(ver1, (char **)&ver1, 10);
		vno2 = strtol(ver2, (char **)&ver2, 10);
		if(vno1 != vno2)
			return vno1 - vno2;
		if(*ver1 == '.')
		{
			if(*ver2 == '.')
			{
				ver1++;
				ver2++;
			}
			else return 1;
		}
		else
		{
			if(*ver2 == '.')
				return -1;
			else return compare_ver_suffixes(ver1, ver2);
		}
	}
	return 0;
}

/**** \function
A very peculiar function used to translate CVS-supplied date
($Date: 2003/05/18 21:58:26 $ into YYYY/MM/DD. 
You're unlikely to use this function.
\note this function may cause buffer overflow
*/
const char *translate_cvs_date(const char *date)
{
	static char resdate[11];
	char *dptr = resdate;

	date++;
	while(*date && !isdigit(*date) && *date != '$')
		date++;
	while(isdigit(*date) || *date == '/')
		*dptr++ = *date++;
	*dptr = '\0';
	return resdate;
}

/*** \group[Error handling functions] */

/*** \ifdef[maintainer]
\struct[error_stack]
A stack of error handlers
\field[whereto] a point where control is to be transferred
\field[data] associated with an error handler
\endif
*/
DEFLIST(error_stack,
		jmp_buf whereto;
		void *data;
		);

static error_stack *ehandlers;

/*** \function
Registers a new error handler associated with data pointed by \var[arg] 
*/
void register_error_handler(void *arg)
{
	error_stack *ne = ALLOC(error_stack);
	ne->data = arg;
	ehandlers = list_add(ehandlers, ne);
}

/*** \function
Deletes the most recently registered error handler.
Makes the previous handler (if present) active.
*/
void unwind_error(void)
{
	ehandlers = list_pop(ehandlers, NULL, sizeof(error_stack));
}

/*** \function
\returns the point to pass control to of the current error handler
\error[No error handlers registered]
*/
jmp_buf *get_error_buf(void)
{
	if(!ehandlers)
		fatal_error("No error handlers registered!");
	return &ehandlers->whereto;
}

/*** \function
\returns the associated data of the current error handler
\error[No error handlers registered]
*/
void *get_eh_data(void)
{
	if(!ehandlers)
		fatal_error("No error handlers resistered!");
	return ehandlers->data;
}

static char error_buffer[256];
#ifdef USE_OBJC_API
static id objc_err_obj;
static int objc_err_code = -1;
#endif

/*** \function
\returns a pointer to static area holding the error description
produced by \see[functions][recover_error].
*/
char *get_error_desc(void)
{
	return error_buffer;
}

#ifdef USE_OBJC_API
int get_objc_error_info(id *err_id)
{
	if(err_id)
		*err_id = objc_err_obj;
	return objc_err_code;
}

void clear_objc_error_info(void)
{
	objc_err_obj = nil;
	objc_err_code = -1;
}
#endif

static char *cur_filename = "";

/*** \function
If \var[fname] is not NULL, sets the current filename for error messages to
\var[fname]
\returns the previous filename
*/
char *set_current_filename(char *fname)
{
	char *of = cur_filename;
	if(fname)
		cur_filename = fname;
	return of ? of : "";
}

static void report_error(const char *fmt, va_list args)
{
	if(line_no)
		fprintf(stderr, "%s:%d: ", cur_filename ? cur_filename : "", line_no);
#ifdef HAVE_VPRINTF	
	vfprintf(stderr, fmt, args);
#else
	fputs(fmt, stderr);
#endif
#ifdef USE_OBJC_API
	if(objc_err_code >= 0)
		fprintf(stderr, " [object id=%p, error code=%d]", objc_err_obj, objc_err_code);
#endif
	fputc('\n', stderr);
}

/*** 
\function
formats an error message a la \ref[printf(3)] and prints it to \em[stderr] followed
by a new line.
If the current line number and filename are defined they are printed in front
of a message.
\note If \ref[vfprintf(3)] is not present in the standard library,
the format string itself is printed.
\ifdef[maintainer]\note this function is just a wrapper for \em[report_error]\endif
*/
void print_error_msg(const char *fmt, ...)
{
	va_list args;
	va_start(args, fmt);
	report_error(fmt, args);
	va_end(args);
}

/**** \function
If there are registered error handlers, passes control where the active handler
tells us. An error message is formatted according to \em[fmt] and put into
\ifdef[maintainer] \var[error_buffer] \else a static area accessed by
\see[functions][get_error_desc]\endif.

Otherwise error message is printed as if by \see[functions][print_error_msg], 
and the program terminates
\note If the system has not \ref[vsnprintf(3)] but has \ref[vsprintf(3)],
buffer overflow may occur. If none are present, the format string itself is
copied.
*/
void recover_error(const char *fmt, ...)
{
	va_list args;
	va_start(args, fmt);
	recover_errorv(fmt, args);
}

void recover_errorv(const char *fmt, va_list args)
{
	if(ehandlers)
	{
#ifdef HAVE_VSNPRINTF	
		vsnprintf(error_buffer, sizeof(error_buffer), fmt, args);
#elif HAVE_VPRINTF
		vsprintf(error_buffer, fmt, args);
#else
		strncpy(error_buffer, fmt, sizeof(error_buffer));
#endif		
		va_end(args);
		longjmp(ehandlers->whereto, 1);
	}
	report_error(fmt, args);
	exit(EXIT_FAILURE);
}

#ifdef USE_OBJC_API
static BOOL recover_error_objc(id obj, int code, const char *fmt, va_list args)
{
	objc_err_obj = obj;
	objc_err_code = code;
	recover_errorv(fmt, args);
	return NO;
}

static void init_objc_error(void) __attribute__ ((constructor));
static void init_objc_error(void)
{
	objc_set_error_handler(recover_error_objc);
}
#endif

/***
\function
Prints an error message as if by \see[functions][print_error_msg] and terminates the program
*/
void fatal_error(const char *fmt, ...)
{
	va_list args;
	va_start(args, fmt);
	report_error(fmt, args);
	va_end(args);
	exit(EXIT_FAILURE);
}

/**** \function
Like \see[functions][print_error_msg] but issues \sample[warning:]
between line number and the message itself
*/
void warning(const char *fmt, ...)
{
	va_list args;
	va_start(args, fmt);
	if(line_no)
		fprintf(stderr, "%d: ", line_no);
	fputs("warning: ", stderr);
#ifdef HAVE_VFPRINTF
	vfprintf(stderr, fmt, args);
#else
	fputs(fmt, stderr);
#endif
	fputc('\n', stderr);
	va_end(args);
}

/*** \group[Safe memory allocation functions] 
The functions below are equivalent to corresponding standard functions,
but they call \see[functions][recover_error] in case of memory lack
and never return NULL
*/

#ifndef USE_OBJC_API
/*** \function */
void *xmalloc(size_t size)
{
	void *addr = malloc(size);
//	fprintf(stderr, "malloc: %d -> %p\n", size, addr);
	if(!addr)
		recover_error("malloc: no enough memory (%d requested)", size);
	return addr;
}

/*** \function */
void *xcalloc(size_t n, size_t size)
{
	void *addr = calloc(n, size);
//		fprintf(stderr, "calloc: %dx%d -> %p\n", n, size, addr);

	if(!addr)
		recover_error("calloc: no enough memory (%d x %d requested)", n, size);
	return addr;
}

/*** \function */
void *xrealloc(void *addr, size_t news)
{
	addr = realloc(addr, news);
	//	fprintf(stderr, "realloc: %d -> %p\n", news, addr);
	if(!addr)
		recover_error("realloc: no enough memory (%d requested)", news);
	return addr;
}
#endif

/*** \function
If \ref[strdup(3)] is not in the standard library, 
implements it.
*/
char *xstrdup(const char *src)
{
#if defined(HAVE_STRDUP) && !defined(USE_OBJC_API)
	char *dest = strdup(src);
//	fprintf(stderr, "strdup: '%s'\n", src);
	if(!dest)
		recover_error("strdup: no enough memory");
	return dest;
#else
	char *dest = xmalloc(strlen(src) + 1);
	strcpy(dest, src);
	return dest;
#endif		
}

/**** \group[Chunk-based memory allocation]
These functions maintain lists of \em[chunks] of a given size.
On request, a chunk from a list is reused. If there is no chunks
left, new one is allocated and zeroed. When freed, a chunk is
returned to a list. 
The maximal size of a chunk is \code[16385 * sizeof(void *) - 1]
which is 65539 on 32-bit machines.
Sizes of chunks less than \code[257 * sizeof(void *)] (1028) are 
rounded up to
\code[sizeof(void *)] boundary, others are rounded to 1K boundary
*/
#define CHUNK_FENCE	(257 * CHUNK_SIZE)
#define CHUNK_SIZE (sizeof(void *))
#define CHUNK_MASK (CHUNK_SIZE - 1)
#define PAGE_FENCE (16385 * CHUNK_SIZE)
#define PAGE_SIZE 1024
#define PAGE_MASK (PAGE_SIZE - 1)

static baselist_t *free_chunks[CHUNK_FENCE / CHUNK_SIZE];
static baselist_t *free_pages[PAGE_FENCE / PAGE_SIZE];

static baselist_t **getbase(int size)
{
	baselist_t **base;
	if(size >= PAGE_FENCE)
		fatal_error("requested chunk size %d is larger than %d", size, PAGE_FENCE);	
	base = size >= CHUNK_FENCE ? free_pages + size / PAGE_SIZE :
	  						     free_chunks  + size / CHUNK_SIZE;
	return base;
}

static int getalignsize(int size)
{
	if(size < CHUNK_FENCE)
		return (size + CHUNK_MASK) & ~CHUNK_MASK;
	return (size + PAGE_MASK) & ~PAGE_MASK;
}

#define FENCE_VAL 0x0FE9CE32

/*** \function */
void *alloc_chunk(int size)
{	
#ifdef ENABLE_CHECKSIZE
	baselist_t **wherefrom = getbase(size + sizeof(size));
	int eod = 0;
#else
	baselist_t **wherefrom = getbase(size);
#endif
	baselist_t *result;

	if(!*wherefrom)
#ifdef ENABLE_CHECKSIZE
	{
		result = xcalloc(1, eod = getalignsize(size + sizeof(size)));
		((int *)result)[eod / sizeof(size) - 1] = FENCE_VAL;
	}
#else
		result = xcalloc(1, getalignsize(size));
#endif
	else
	{
		result = *wherefrom;
		*wherefrom = result->next;
		memset(result, 0, size);
	}
	return result;
}

/*** \function 
\note explicit \var[size], since the function has no other means to 
detect it
*/
void free_chunk(void *chunk, int size)
{
#ifndef ENABLE_CHECKSIZE
	baselist_t **whereto = getbase(size);
#else
	baselist_t **whereto = getbase(size + sizeof(size));
	int eod = getalignsize(size + sizeof(size));

	if(((int *)chunk)[eod / sizeof(size) - 1] != FENCE_VAL)
		fatal_error("chunk size (%d) mismatch or writing past chunk end", size);
#endif
	((baselist_t *)chunk)->next = *whereto;
	*whereto = chunk;
}	

/*** \function
Reclaims all the memory kept in chunks.
You will rarely need to call this function
*/
void reset_free_chunks(void)
{
	baselist_t **iter;
	baselist_t *iter1, *temp;
	
	for(iter = free_chunks; iter < ENDOF(free_chunks); iter++) 
	{
		for(iter1 = *iter; iter1; iter1 = temp)
		{
			temp = iter1->next;
			free(iter1);
		}
		*iter = NULL;	
	}
	for(iter = free_pages; iter < ENDOF(free_pages); iter++) 
	{
		for(iter1 = *iter; iter1; iter1 = temp)
		{
			temp = iter1->next;
			free(iter1);
		}
		*iter = NULL;	
	}
}


/*** \group[Safe file management functions] 
They are equvalents of corresponding standard functions, but
call \see[functions][recover_error] and never return invalid
values.
*/
/*** \function */
FILE *xfopen(const char *name, const char *mode)
{
	FILE *f = fopen(name, mode);
	if(!f)
		recover_error("can't open %s: %s", name, strerror(errno));
	return f;
}

/**** \function */
int xopen(const char *name, int mode, int rights)
{
	int f = open(name, mode, rights);
	if(f < 0)
		recover_error("can't open %s: %s", name, strerror(errno));
	return f;
}

/*** \group[File loading functions] */

/*** \ifdef[maintainer]
\function
A callback for \see[functions][find_filename] which
reads the whole file into memory, allocating a buffer of the necessary size.
\returns the address of the buffer or NULL if a file is not loadable (i. e.
*/
static void *iload_file(const char *name)
{
	int f = xopen(name, O_RDONLY, 0);
	off_t len;
	char *buf;
	len = filelength(f);
	if(len < 0)
		return NULL;
	buf = xcalloc(len + 1, 1);
	lock_file(f, 0);
	len = read(f, buf, len);
	close(f);
	return buf;
}

/*** \function
\returns 1 if \var[fname] has an extension; 0 otherwise
*/
static int has_extension(const char *name)
{
	const char *point = strrchr(name, '.');
	const char *slash = strrchr(name, '/');
	if(!point || point == name)
		return 0;
	return !slash || slash < point - 1;
}

/*** \function
\returns 1 if \var[fname] need not be searched in a path (i. e. is either an 
absolute pathname or starts with a period
*/
static int is_selfish(const char *name)
{
	return (*name == '/' || (*name == '.' && (name[1] == '/' || (name[1] == '.' && name[2] == '/'))));
}

/*** \function
checks whether a file \em[name] does exist and is a regular file
\returns non-zero if success
*/
static int check_file(const char *name)
{
#ifdef HAVE_STAT
	struct stat st;
	if(stat(name, &st))
		return 0;
	return S_ISREG(st.st_mode);
#else
	int fd = open(fname, O_RDONLY);
	int res = fd > 0;
	if(fd > 0)
		close(fd);
	return res;
#endif
}


/*** \function
Auxiliary function for \see[functions][find_filename].
It has the same interface but \var[path] and \var[defext] are single values,
not lists
*/
static void *i_find_filename(const char *name, const char *path, const char *defext, 
							void *(*ioproc)(const char *name),
							void *(crproc)(const char *name)
					)		
{
	char temp[PATH_MAX + 1] = "";
	if(path && !is_selfish(name))
	{
		int l = strlen(path);
		strcpy(temp, path);
		if(temp[l - 1] != '/')
			temp[l] = '/';
	}
	strcat(temp, name);
	if(defext && *defext && !has_extension(temp))
	{
		if(*defext != '.')
			strcat(temp, ".");
		strcat(temp, defext);
	}
	if(!check_file(temp))
		return crproc ? crproc(temp) : NULL;
	return ioproc ? ioproc(temp) : temp;
}

/*** \function
Auxiliary function for \see[functions][find_filename].
It has the same interface but \var[defext] is a single value,
not a list
*/
static void *i_find_filename2(const char *name, const char *path, const char *defext, 
							void *(*ioproc)(const char *name),
							void *(crproc)(const char *name)
					)
{
	char cur_path[PATH_MAX + 1];
	const char *next = path;
	void *result = NULL;
	if(!path || is_selfish(name)) 
		return i_find_filename(name, NULL, defext, ioproc, crproc);
	while(next)
	{
		next = strchr(path, PATH_SEP);
		if(!next)
			strcpy(cur_path, path);
		else
		{
			memcpy(cur_path, path, next - path);
			cur_path[next - path] = '\0';
			path = next + 1;
		}
		if((result = i_find_filename(name, cur_path, defext, ioproc, 
					next ? NULL : crproc)))
			break;
	}
	return result;
}
/***\endif */

/*** \function
Tries to find \var[name].
\arg[name] base filename
\arg[path] colon-separated list of directories where \arg[name] is searched
if it does not contain an explicit directory reference
\arg[defext] slash-separated list of extensions which are tried if \em[name]
has no extension. Period is inserted if necessary.
\arg[ioproc] routine called when a file is found. Its value is returned.
If it is NULL, the resulting filename is returned
\arg[crproc] routine called when a file is not found. Its value is returned,
or NULL if it is NULL
*/
void *find_filename(const char *name, const char *path, const char *defext, 
							void *(*ioproc)(const char *name),
							void *(crproc)(const char *name)
					)
{
	static char cur_ext[PATH_MAX + 1];
	void *result = NULL;
	const char *next = defext;

	if(!defext)
		return i_find_filename2(name, path, defext, ioproc, crproc);
	while(next)
	{
		next = strchr(defext, '/');
		if(!next)
			strcpy(cur_ext, defext);
		else
		{
			memcpy(cur_ext, defext, next - defext);
			cur_ext[next - defext] = '\0';
			defext = next + 1;
		}
		if((result = i_find_filename2(name, path, cur_ext, ioproc, 
					next ? NULL : crproc)))
			break;
	}
	return result;
}

/*** \function
Finds a file via \see[functions][find_filename] and loads it into memory.
\error[can't locate name]
\returns a pointer to allocated buffer
*/
char *load_file(const char *name, const char *path, const char *defext)
{
	char *data = find_filename(name, path, defext, iload_file, NULL);
	if(!data)
		recover_error("can't locate %s", name);
	return data;
}


/*** \function
Tries to map a file into memory \em[for reading]. 
If mapping fails or is not implemented on the system,
loads the file
\returns pointer to \see[structs][mapping]
\note In general, you'd better call \see[functions][map_file]
*/
void *imap_file(const char *name)
{
	int tries = util_lock_max_tries;
	mapping *nm = ALLOC(mapping);
	nm->hfile = xopen(name, O_RDONLY, 0);
	nm->len = filelength(nm->hfile);
	nm->really_mapped = 1;
#ifdef HAVE_MMAP
	while(tries--)
	{
		errno = 0;
		nm->data = mmap(NULL, nm->len, PROT_READ, MAP_PRIVATE, nm->hfile, 0);
		if(errno != EAGAIN)
			break;
		sleep(util_lock_delay);
	}
	if(nm->data == MAP_FAILED)
#endif		
	{
		lock_file(nm->hfile, 0);
		nm->data = xmalloc(nm->len);
		if(read(nm->hfile, nm->data, nm->len) < 0)
			recover_error("cannot read from file %s: %s", name, strerror(errno));
		nm->really_mapped  = 0;
	}
	return nm;
}

/*** \function
Like \see[functions][load_file], but first tries to map it, loads if mapping fails 
*/
mapping *map_file(const char *name, const char *path, const char *defext)
{
	mapping *m = find_filename(name, path, defext, imap_file, NULL);
	if(!m)
		recover_error("can't locate %s", name);
	return m;
}

/*** \function
Unmaps a file and frees all the resources allocated by \see[functions][imap_file]
*/
void delete_mapping(mapping *map)
{
	if(!map->really_mapped)
		free(map->data);
#ifdef HAVE_MMAP	
	else munmap(map->data, map->len);
#endif	
	close(map->hfile);
	FREE(map);
}

/*** \function
\returns 1 if \var[pos] points to the end of mapped area
*/
int is_map_eof(mapping *map, const char *pos)
{
	return pos == (const char *)map->data + map->len;
}

/*** \group[Common parsing functions] */

int util_comment_character = '#';
/*** \function
Skips all the space characters and comments in an input string \var[start].
A comment starts with \em[#], ends with a newline.
A pointer to the first non-space character is stored into \var[end].
Line number is incremented as necessary.
\note Comment-introducing character may be changed by assigning
to \var[util_comment_character] variable
*/
void skip_spaces(const char *start, char **end)
{
	if(!util_comment_character)
	{
		skip_spaces_nc(start, end);
		return;
	}
	for(;;)
	{
		skip_spaces_nc(start, (char **)&start);
		if(*start != util_comment_character)
			break;
		while(*start && *start != '\n')
			start++;
	}
	(*end) = (char *)start;
}

/*** \function
Like \see[functions][skip_spaces], but doesn't recognize comments
*/
void skip_spaces_nc(const char *start, char **end)
{
	while(*start && isspace(*start))
	{
		if(line_no && *start == '\n')
			line_no++;
		start++;
	}
	(*end) = (char *)start;
}

/*** \function
Gets the first character of \var[start] recognizing C-style escape sequences.
In addition, \em[\\e] denotes ESC (code 27).
A pointer to the next character is stored into \em[end]
\returns a character code
\note \var[end] may be NULL
*/
int parse_single_char(const char *start, char **end)
{
	static char escapes[] = "abefnrtvsz";
	static char esc_out[] = "\a\b\x1B\f\n\r\t\v \0";

	switch(*start)
	{
		case '\0':
		case '\n':
		case '\r':
			recover_error("unfinished character constant");
		case '\\':
		{
			char *e = strchr(escapes, *++start);
			if(e)
			{
				if(end)
					*end = (char *)(start + 1);
				return esc_out[e - escapes];
			}	
			if(*start == 'x' || *start == 'X')
				return strtol(start + 1, end, 16);
			else if(isdigit(*start) && *start != '8' && *start != '9')
				return strtol(start, end, 8);
		}
		default:
			if(end)
				*end = (char *)(start + 1);
			return *start;
	}
}

/*** \function
Parses a character constant like in C. The character pointed at by \var[start]
is taken as a delimiter. Multicharacter constants are treated in a bigendian
manner, i. e. the first character will occupy the most significant byte.
A pointer to the character following the constant will be stored into \var[end]
\returns the value of a constant 
\note \var[end] may be NULL
*/
int parse_char(const char *start, char **end)
{
	int delim = *start++;
	int val = 0;
	while(*start != delim)
		val = (val << 8) | (parse_single_char(start, (char **)&start) & 0xFF);
	if(end)
		*end = (char *)(start + 1);
	return val;
}

/**** \function
Parses a C-like string. The character pointed at by \var[start] is taken as a delimiter.
A pointer to the character following the string is be stored into \var[end]
\returns a parsed string
\note The returned value is overwritten in a sequential call to \code[parse_string] and
you shouldn't try to free it
\ifdef[maintainer] Actually it is stored in a dstring \endif
\note \var[end] may be NULL
*/
char *parse_string(const char *start, char **end)
{
//	static char buffer[1024];
	static dstring *buffer = NULL;
	int delim = *start++;

	if(!buffer)
		buffer = ds_fix(ds_create(NULL));
	else
		ds_assign(buffer, NULL);
	while(*start != delim)
		ds_appendch(buffer, parse_single_char(start, (char **)&start));
	if(end)
		*end = (char *)(start + 1);
	return DS_BODY(buffer);
}

/*** \function
Parses an \em[identifier] -- a token consisting of characters for which
\see[functions][isnamesym] is true. Starting point is at \var[start],
the first non-id character is stored into \var[end].
\returns a parsed identifier
\note See the note of \see[functions][parse_string]
\note \var[end] may be NULL
*/
char *parse_id(const char *start, char **end)
{
	static dstring *buffer = NULL;
	
	if(!buffer)
		buffer = ds_fix(ds_create(NULL));
	else
		ds_assign(buffer, NULL);
	while(isnamesym(*start))
		ds_appendch(buffer, *start++);
	if(end)
		*end = (char *)start;
	return DS_BODY(buffer);
}

/*** \function
Like \see[functions][parse_id], but parses a \em[plain identifer], i. e.
which doesn't contain colons
*/
char *parse_plain_id(const char *start, char **end)
{
	static dstring *buffer = NULL;
	
	if(!buffer)
		buffer = ds_fix(ds_create(NULL));
	else
		ds_assign(buffer, NULL);
	while(isplainnamesym(*start))
		ds_appendch(buffer, *start++);
	if(end)
		*end = (char *)start;
	return DS_BODY(buffer);
}


/*** \function
Parses a \em[numerical token] which is either a number in a form
acceptable by \ref[strtol(3)] or a character constant acceptable
by \see[functions][parse_char] delimited by an apostrophe.
Parsing starts at \var[start], a pointer to the following character is stored
into \var[end]
\returns numerical value of a token
\note \var[end] may be NULL
*/
int parse_num_token(const char *start, char **end)
{
	if(*start == '\'')
		return parse_char(start, end);
	return strtol(start, end, 0);
}

int util_directive_character = '.';
/*** \function
Used to skip a block of text starting with \var[start] until a matching \em[.endif].
A pointer to the text following \em[.endif] is stored into \var[end].
Nested \em[.if*] are properly handled.
\arg[else_flag] if non-zero, \em[.else] behaves like \em[.endif] 
\note \var[end] must be non-NULL
\note A directive may start with two dots as well
\note A period may be replaced by another character by assigning to 
\var[util_directive_character]
*/
void skip_ifs(const char *start, char **end, int else_flag)
{
	int depth = 1;
	const char *tok;

	while(depth)
	{
//		start = strchr(start, util_directive_character);
		for(; *start && *start != util_directive_character; start++)
		{
			if(line_no && *start == '\n')
				line_no++;
		}
		if(!*start)
			recover_error("'%cif' without '%cendif'", util_directive_character, 
						  util_directive_character);
		if(*start == util_directive_character)
		{
			if(start[1] == util_directive_character)
				start++;
			start++;
			tok = parse_id(start, (char **)&start);
			if(!strcmp(tok, "endif"))
				depth--;
			else if(*tok == 'i' && tok[1] == 'f')
				depth++;
			else if(else_flag && depth == 1 && !strcmp(tok, "else"))
				depth--;
		}
	}
	*end = (char *)start;
}


/*** \group[Generic table lookup] */

/*** \function
Looks up for a \var[key] in \var[table] (\see[structs][table_t])
\note \var[table] must be a NULL-terminated array
\returns \see[fields][table_t.data] field if an item is found, NULL otherwise
*/
void *lookup_table(table_t *table, const char *key)
{
	for(; table->key; table++)
	{
		if(!strcmp(table->key, key))
			return table->data;
	}
	return NULL;
}

/*** \function
Looks up for a \var[key] in a table of \var[names]
\note \var[names] must be a NULL-terminated array
\returns zero-base index of \var[key] within \var[names], 
a negative value if not found
*/
int lookup_name(char **names, const char *key)
{
	char **base = names;
	for(; *names; names++)
	{
		if(!strcmp(*names, key))
			return names - base; 
	}
	return -1;
}

/*** \group[atexit enhancement] */

static int is_atexit_installed;
/*** \ifdef[maintainer] \struct[atexit_list]
The type of \code[atexit] handler list
\field[func] a pointer to handler
*/
DEFLIST(atexit_list,
		void (*func)(void);
	);
static atexit_list *atexits;

/*** \function
Handler installed by \ref[atexit(3)] to process other handlers
\endif
*/
static void enh_atexit_handler(void)
{
	atexit_list *iter = atexits;
	for(; iter; iter = list_next(iter))
		iter->func();
}

/*** \function
Registers a new atexit handler \var[func].
Unlike standard \ref[atexit(3)], there is no limit on the number of
handlers. 

Registered handlers are executed in the order of registration.
\returns 0 if success, negative value if error
*/
int enh_atexit(void (*func)(void))
{
	atexit_list *na;
	if(!func)
		return -1;
	if(!is_atexit_installed)
	{
		if(atexit(enh_atexit_handler))
			return -1;
	}
	na = ALLOC(atexit_list);
	na->func = func;
	atexits = list_append(atexits, na);
	return 0;
}
/*** \endgroup */


syntax highlighted by Code2HTML, v. 0.9.1