/* $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 #endif #ifdef HAVE_SYS_TYPES_H #include #endif #include #include #include #include #include #include #include #include #ifdef HAVE_FLOCK #include #endif #ifdef HAVE_SYS_STAT_H #include #else #include #endif #ifdef HAVE_MMAP #include #endif #include #include #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 */