/* $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