summaryrefslogtreecommitdiff
path: root/qdbm/cabin.c
diff options
context:
space:
mode:
Diffstat (limited to 'qdbm/cabin.c')
-rw-r--r--qdbm/cabin.c3529
1 files changed, 3529 insertions, 0 deletions
diff --git a/qdbm/cabin.c b/qdbm/cabin.c
new file mode 100644
index 00000000..262cb3eb
--- /dev/null
+++ b/qdbm/cabin.c
@@ -0,0 +1,3529 @@
+/*************************************************************************************************
+ * Implementation of Cabin
+ * Copyright (C) 2000-2007 Mikio Hirabayashi
+ * This file is part of QDBM, Quick Database Manager.
+ * QDBM is free software; you can redistribute it and/or modify it under the terms of the GNU
+ * Lesser General Public License as published by the Free Software Foundation; either version
+ * 2.1 of the License or any later version. QDBM 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 Lesser General Public License for more
+ * details.
+ * You should have received a copy of the GNU Lesser General Public License along with QDBM; if
+ * not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA.
+ *************************************************************************************************/
+
+
+#define QDBM_INTERNAL 1
+
+#include "cabin.h"
+#include "myconf.h"
+
+#define CB_GCUNIT 64 /* allocation unit size of a buffer in gc */
+#define CB_SPBUFSIZ 32 /* size of a buffer for sprintf */
+#define CB_SPMAXWIDTH 128 /* max width of a column for sprintf */
+#define CB_MAPPBNUM 251 /* bucket size of a petit map handle */
+#define CB_MAPCSUNIT 52 /* small allocation unit size of map concatenation */
+#define CB_MAPCBUNIT 252 /* big allocation unit size of map concatenation */
+#define CB_MSGBUFSIZ 256 /* size of a buffer for log message */
+#define CB_IOBUFSIZ 8192 /* size of an I/O buffer */
+#define CB_FILEMODE 00644 /* permission of a creating file */
+#define CB_NUMBUFSIZ 32 /* size of a buffer for a number */
+#define CB_ENCBUFSIZ 32 /* size of a buffer for encoding name */
+#define CB_DATEBUFSIZ 64 /* size of a buffer for date expression */
+#define CB_VNUMBUFSIZ 8 /* size of a buffer for variable length number */
+
+/* set a buffer for a variable length number */
+#define CB_SETVNUMBUF(CB_len, CB_buf, CB_num) \
+ do { \
+ int _CB_num; \
+ _CB_num = (CB_num); \
+ if(_CB_num == 0){ \
+ ((signed char *)(CB_buf))[0] = 0; \
+ (CB_len) = 1; \
+ } else { \
+ (CB_len) = 0; \
+ while(_CB_num > 0){ \
+ int _CB_rem = _CB_num & 0x7f; \
+ _CB_num >>= 7; \
+ if(_CB_num > 0){ \
+ ((signed char *)(CB_buf))[(CB_len)] = -_CB_rem - 1; \
+ } else { \
+ ((signed char *)(CB_buf))[(CB_len)] = _CB_rem; \
+ } \
+ (CB_len)++; \
+ } \
+ } \
+ } while(FALSE)
+
+/* read a variable length buffer */
+#define CB_READVNUMBUF(CB_buf, CB_size, CB_num, CB_step) \
+ do { \
+ int _CB_i, _CB_base; \
+ CB_num = 0; \
+ _CB_base = 1; \
+ if((size) < 2){ \
+ CB_num = ((signed char *)(CB_buf))[0]; \
+ (CB_step) = 1; \
+ } else { \
+ for(_CB_i = 0; _CB_i < (size); _CB_i++){ \
+ if(((signed char *)(CB_buf))[_CB_i] >= 0){ \
+ CB_num += ((signed char *)(CB_buf))[_CB_i] * _CB_base; \
+ break; \
+ } \
+ CB_num += _CB_base * (((signed char *)(CB_buf))[_CB_i] + 1) * -1; \
+ _CB_base *= 128; \
+ } \
+ (CB_step) = _CB_i + 1; \
+ } \
+ } while(FALSE)
+
+/* get the first hash value */
+#define CB_FIRSTHASH(CB_res, CB_kbuf, CB_ksiz) \
+ do { \
+ const unsigned char *_CB_p; \
+ int _CB_ksiz; \
+ _CB_p = (const unsigned char *)(CB_kbuf); \
+ _CB_ksiz = CB_ksiz; \
+ for((CB_res) = 19780211; _CB_ksiz--;){ \
+ (CB_res) = (CB_res) * 37 + *(_CB_p)++; \
+ } \
+ (CB_res) &= INT_MAX; \
+ } while(FALSE)
+
+/* get the second hash value */
+#define CB_SECONDHASH(CB_res, CB_kbuf, CB_ksiz) \
+ do { \
+ const unsigned char *_CB_p; \
+ int _CB_ksiz; \
+ _CB_p = (const unsigned char *)(CB_kbuf) + CB_ksiz - 1; \
+ _CB_ksiz = CB_ksiz; \
+ for((CB_res) = 0x13579bdf; _CB_ksiz--;){ \
+ (CB_res) = (CB_res) * 31 + *(_CB_p)--; \
+ } \
+ (CB_res) &= INT_MAX; \
+ } while(FALSE)
+
+
+/* private function prototypes */
+static void cbggchandler(void);
+static void cbggckeeper(void *ptr, void (*func)(void *));
+static void cbqsortsub(char *bp, int nmemb, int size, char *pswap, char *vswap,
+ int(*compar)(const void *, const void *));
+static int cblistelemcmp(const void *a, const void *b);
+static int cbkeycmp(const char *abuf, int asiz, const char *bbuf, int bsiz);
+
+
+
+/*************************************************************************************************
+ * public objects
+ *************************************************************************************************/
+
+
+/* Call back function for handling a fatal error. */
+void (*cbfatalfunc)(const char *message) = NULL;
+
+
+/* Allocate a region on memory. */
+void *cbmalloc(size_t size){
+ char *p;
+ assert(size > 0 && size < INT_MAX);
+ if(!(p = malloc(size))) cbmyfatal("out of memory");
+ return p;
+}
+
+
+/* Re-allocate a region on memory. */
+void *cbrealloc(void *ptr, size_t size){
+ char *p;
+ assert(size > 0);
+ if(!(p = realloc(ptr, size))) cbmyfatal("out of memory");
+ return p;
+}
+
+
+/* Duplicate a region on memory. */
+char *cbmemdup(const char *ptr, int size){
+ char *p;
+ assert(ptr);
+ if(size < 0) size = strlen(ptr);
+ CB_MALLOC(p, size + 1);
+ memcpy(p, ptr, size);
+ p[size] = '\0';
+ return p;
+}
+
+
+/* Free a region on memory. */
+void cbfree(void *ptr){
+ free(ptr);
+}
+
+
+/* Register the pointer or handle of an object to the global garbage collector. */
+void cbglobalgc(void *ptr, void (*func)(void *)){
+ assert(ptr && func);
+ cbggckeeper(ptr, func);
+}
+
+
+/* Exercise the global garbage collector explicitly. */
+void cbggcsweep(void){
+ cbggckeeper(NULL, NULL);
+}
+
+
+/* Check availability of allocation of the virtual memory. */
+int cbvmemavail(size_t size){
+ assert(size >= 0);
+ return _qdbm_vmemavail(size);
+}
+
+
+/* Sort an array using insert sort. */
+void cbisort(void *base, int nmemb, int size, int(*compar)(const void *, const void *)){
+ char *bp, *swap;
+ int i, j;
+ assert(base && nmemb >= 0 && size > 0 && compar);
+ bp = (char *)base;
+ CB_MALLOC(swap, size);
+ for(i = 1; i < nmemb; i++){
+ if(compar(bp + (i - 1) * size, bp + i * size) > 0){
+ memcpy(swap, bp + i * size, size);
+ for(j = i; j > 0; j--){
+ if(compar(bp + (j - 1) * size, swap) < 0) break;
+ memcpy(bp + j * size, bp + (j - 1) * size, size);
+ }
+ memcpy(bp + j * size, swap, size);
+ }
+ }
+ free(swap);
+}
+
+
+/* Sort an array using shell sort. */
+void cbssort(void *base, int nmemb, int size, int(*compar)(const void *, const void *)){
+ char *bp, *swap;
+ int step, bottom, i, j;
+ assert(base && nmemb >= 0 && size > 0 && compar);
+ bp = (char *)base;
+ CB_MALLOC(swap, size);
+ for(step = (nmemb - 1) / 3; step >= 0; step = (step - 1) / 3){
+ if(step < 5) step = 1;
+ for(bottom = 0; bottom < step; bottom++){
+ for(i = bottom + step; i < nmemb; i += step){
+ if(compar(bp + (i - step) * size, bp + i * size) > 0){
+ memcpy(swap, bp + i * size, size);
+ for(j = i; j > step - 1; j -= step){
+ if(compar(bp + (j - step) * size, swap) < 0) break;
+ memcpy(bp + j * size, bp + (j - step) * size, size);
+ }
+ memcpy(bp + j * size, swap, size);
+ }
+ }
+ }
+ if(step < 2) break;
+ }
+ free(swap);
+}
+
+
+/* Sort an array using heap sort. */
+void cbhsort(void *base, int nmemb, int size, int(*compar)(const void *, const void *)){
+ char *bp, *swap;
+ int top, bottom, mybot, i;
+ assert(base && nmemb >= 0 && size > 0 && compar);
+ bp = (char *)base;
+ nmemb--;
+ bottom = nmemb / 2 + 1;
+ top = nmemb;
+ CB_MALLOC(swap, size);
+ while(bottom > 0){
+ bottom--;
+ mybot = bottom;
+ i = 2 * mybot;
+ while(i <= top) {
+ if(i < top && compar(bp + (i + 1) * size, bp + i * size) > 0) i++;
+ if(compar(bp + mybot * size, bp + i * size) >= 0) break;
+ memcpy(swap, bp + mybot * size, size);
+ memcpy(bp + mybot * size, bp + i * size, size);
+ memcpy(bp + i * size, swap, size);
+ mybot = i;
+ i = 2 * mybot;
+ }
+ }
+ while(top > 0){
+ memcpy(swap, bp, size);
+ memcpy(bp, bp + top * size, size);
+ memcpy(bp + top * size, swap, size);
+ top--;
+ mybot = bottom;
+ i = 2 * mybot;
+ while(i <= top){
+ if(i < top && compar(bp + (i + 1) * size, bp + i * size) > 0) i++;
+ if(compar(bp + mybot * size, bp + i * size) >= 0) break;
+ memcpy(swap, bp + mybot * size, size);
+ memcpy(bp + mybot * size, bp + i * size, size);
+ memcpy(bp + i * size, swap, size);
+ mybot = i;
+ i = 2 * mybot;
+ }
+ }
+ free(swap);
+}
+
+
+/* Sort an array using quick sort. */
+void cbqsort(void *base, int nmemb, int size, int(*compar)(const void *, const void *)){
+ char *pswap, *vswap;
+ assert(base && nmemb >= 0 && size > 0 && compar);
+ CB_MALLOC(pswap, size);
+ CB_MALLOC(vswap, size);
+ cbqsortsub(base, nmemb, size, pswap, vswap, compar);
+ free(vswap);
+ free(pswap);
+}
+
+
+/* Compare two strings with case insensitive evaluation. */
+int cbstricmp(const char *astr, const char *bstr){
+ int ac, bc;
+ assert(astr && bstr);
+ while(*astr != '\0'){
+ if(*bstr == '\0') return 1;
+ ac = (*astr >= 'A' && *astr <= 'Z') ? *astr + ('a' - 'A') : *(unsigned char *)astr;
+ bc = (*bstr >= 'A' && *bstr <= 'Z') ? *bstr + ('a' - 'A') : *(unsigned char *)bstr;
+ if(ac != bc) return ac - bc;
+ astr++;
+ bstr++;
+ }
+ return *bstr == '\0' ? 0 : -1;
+}
+
+
+/* Check whether a string begins with a key. */
+int cbstrfwmatch(const char *str, const char *key){
+ assert(str && key);
+ while(*key != '\0'){
+ if(*str != *key || *str == '\0') return FALSE;
+ key++;
+ str++;
+ }
+ return TRUE;
+}
+
+
+/* Check whether a string begins with a key, with case insensitive evaluation. */
+int cbstrfwimatch(const char *str, const char *key){
+ int sc, kc;
+ assert(str && key);
+ while(*key != '\0'){
+ if(*str == '\0') return FALSE;
+ sc = *str;
+ if(sc >= 'A' && sc <= 'Z') sc += 'a' - 'A';
+ kc = *key;
+ if(kc >= 'A' && kc <= 'Z') kc += 'a' - 'A';
+ if(sc != kc) return FALSE;
+ key++;
+ str++;
+ }
+ return TRUE;
+}
+
+
+/* Check whether a string ends with a key. */
+int cbstrbwmatch(const char *str, const char *key){
+ int slen, klen, i;
+ assert(str && key);
+ slen = strlen(str);
+ klen = strlen(key);
+ for(i = 1; i <= klen; i++){
+ if(i > slen || str[slen-i] != key[klen-i]) return FALSE;
+ }
+ return TRUE;
+}
+
+
+/* Check whether a string ends with a key, with case insensitive evaluation. */
+int cbstrbwimatch(const char *str, const char *key){
+ int slen, klen, i, sc, kc;
+ assert(str && key);
+ slen = strlen(str);
+ klen = strlen(key);
+ for(i = 1; i <= klen; i++){
+ if(i > slen) return FALSE;
+ sc = str[slen-i];
+ if(sc >= 'A' && sc <= 'Z') sc += 'a' - 'A';
+ kc = key[klen-i];
+ if(kc >= 'A' && kc <= 'Z') kc += 'a' - 'A';
+ if(sc != kc) return FALSE;
+ }
+ return TRUE;
+}
+
+
+/* Locate a substring in a string using KMP method. */
+char *cbstrstrkmp(const char *haystack, const char *needle){
+ int i, j, hlen, nlen;
+ signed char tbl[0x100];
+ assert(haystack && needle);
+ nlen = strlen(needle);
+ if(nlen >= 0x100) return strstr(haystack, needle);
+ tbl[0] = -1;
+ i = 0;
+ j = -1;
+ while(i < nlen){
+ while((j >= 0) && (needle[i] != needle[j])){
+ j = tbl[j];
+ }
+ i++;
+ j++;
+ tbl[i] = j;
+ }
+ hlen = strlen(haystack);
+ i = 0;
+ j = 0;
+ while(i < hlen && j < nlen){
+ while((j >= 0) && (haystack[i] != needle[j])){
+ j = tbl[j];
+ }
+ i++;
+ j++;
+ }
+ if(j == nlen) return (char *)(haystack + i - nlen);
+ return NULL;
+}
+
+
+/* Locate a substring in a string using BM method. */
+char *cbstrstrbm(const char *haystack, const char *needle){
+ const unsigned char *rp;
+ const char *ep;
+ unsigned char tbl[0x100];
+ int i, j, nlen, len, idx;
+ assert(haystack && needle);
+ nlen = strlen(needle);
+ if(nlen < 3 || nlen >= 0x100) return strstr(haystack, needle);
+ for(i = 0; i < 0x100; i++){
+ tbl[i] = nlen;
+ }
+ len = nlen;
+ rp = (const unsigned char *)needle;
+ while(len > 0){
+ tbl[*rp++] = --len;
+ }
+ nlen--;
+ ep = haystack + strlen(haystack) - nlen;
+ while(haystack < ep){
+ for(i = nlen; haystack[i] == needle[i]; i--){
+ if(i == 0) return (char *)haystack;
+ }
+ idx = ((unsigned char *)haystack)[i];
+ j = tbl[idx] - nlen + i;
+ haystack += j > 0 ? j : 2;
+ }
+ return NULL;
+}
+
+
+/* Convert the letters of a string to upper case. */
+char *cbstrtoupper(char *str){
+ int i;
+ assert(str);
+ for(i = 0; str[i] != '\0'; i++){
+ if(str[i] >= 'a' && str[i] <= 'z') str[i] -= 'a' - 'A';
+ }
+ return str;
+}
+
+
+/* Convert the letters of a string to lower case. */
+char *cbstrtolower(char *str){
+ int i;
+ assert(str);
+ for(i = 0; str[i] != '\0'; i++){
+ if(str[i] >= 'A' && str[i] <= 'Z') str[i] += 'a' - 'A';
+ }
+ return str;
+}
+
+
+/* Cut space characters at head or tail of a string. */
+char *cbstrtrim(char *str){
+ char *wp;
+ int i, head;
+ assert(str);
+ wp = str;
+ head = TRUE;
+ for(i = 0; str[i] != '\0'; i++){
+ if((str[i] >= 0x07 && str[i] <= 0x0d) || str[i] == 0x20){
+ if(!head) *(wp++) = str[i];
+ } else {
+ *(wp++) = str[i];
+ head = FALSE;
+ }
+ }
+ *wp = '\0';
+ while(wp > str && ((wp[-1] >= 0x07 && wp[-1] <= 0x0d) || wp[-1] == 0x20)){
+ *(--wp) = '\0';
+ }
+ return str;
+}
+
+
+/* Squeeze space characters in a string and trim it. */
+char *cbstrsqzspc(char *str){
+ char *wp;
+ int i, spc;
+ assert(str);
+ wp = str;
+ spc = TRUE;
+ for(i = 0; str[i] != '\0'; i++){
+ if(str[i] > 0 && str[i] <= ' '){
+ if(!spc) *(wp++) = str[i];
+ spc = TRUE;
+ } else {
+ *(wp++) = str[i];
+ spc = FALSE;
+ }
+ }
+ *wp = '\0';
+ for(wp--; wp >= str; wp--){
+ if(*wp > 0 && *wp <= ' '){
+ *wp = '\0';
+ } else {
+ break;
+ }
+ }
+ return str;
+}
+
+
+/* Count the number of characters in a string of UTF-8. */
+int cbstrcountutf(const char *str){
+ const unsigned char *rp;
+ int cnt;
+ assert(str);
+ rp = (unsigned char *)str;
+ cnt = 0;
+ while(*rp != '\0'){
+ if((*rp & 0x80) == 0x00 || (*rp & 0xe0) == 0xc0 ||
+ (*rp & 0xf0) == 0xe0 || (*rp & 0xf8) == 0xf0) cnt++;
+ rp++;
+ }
+ return cnt;
+}
+
+
+/* Cut a string of UTF-8 at the specified number of characters. */
+char *cbstrcututf(char *str, int num){
+ unsigned char *wp;
+ int cnt;
+ assert(str && num >= 0);
+ wp = (unsigned char *)str;
+ cnt = 0;
+ while(*wp != '\0'){
+ if((*wp & 0x80) == 0x00 || (*wp & 0xe0) == 0xc0 ||
+ (*wp & 0xf0) == 0xe0 || (*wp & 0xf8) == 0xf0){
+ cnt++;
+ if(cnt > num){
+ *wp = '\0';
+ break;
+ }
+ }
+ wp++;
+ }
+ return str;
+}
+
+
+/* Get a datum handle. */
+CBDATUM *cbdatumopen(const char *ptr, int size){
+ CBDATUM *datum;
+ CB_MALLOC(datum, sizeof(*datum));
+ CB_MALLOC(datum->dptr, CB_DATUMUNIT);
+ datum->dptr[0] = '\0';
+ datum->dsize = 0;
+ datum->asize = CB_DATUMUNIT;
+ if(ptr) CB_DATUMCAT(datum, ptr, (size >= 0 ? size : strlen(ptr)));
+ return datum;
+}
+
+
+/* Copy a datum. */
+CBDATUM *cbdatumdup(const CBDATUM *datum){
+ assert(datum);
+ return cbdatumopen(datum->dptr, datum->dsize);
+}
+
+
+/* Free a datum handle. */
+void cbdatumclose(CBDATUM *datum){
+ assert(datum);
+ free(datum->dptr);
+ free(datum);
+}
+
+
+/* Concatenate a datum and a region. */
+void cbdatumcat(CBDATUM *datum, const char *ptr, int size){
+ assert(datum && ptr);
+ if(size < 0) size = strlen(ptr);
+ if(datum->dsize + size >= datum->asize){
+ datum->asize = datum->asize * 2 + size + 1;
+ CB_REALLOC(datum->dptr, datum->asize);
+ }
+ memcpy(datum->dptr + datum->dsize, ptr, size);
+ datum->dsize += size;
+ datum->dptr[datum->dsize] = '\0';
+}
+
+
+/* Get the pointer of the region of a datum. */
+const char *cbdatumptr(const CBDATUM *datum){
+ assert(datum);
+ return datum->dptr;
+}
+
+
+/* Get the size of the region of a datum. */
+int cbdatumsize(const CBDATUM *datum){
+ assert(datum);
+ return datum->dsize;
+}
+
+
+/* Set the size of the region of a datum. */
+void cbdatumsetsize(CBDATUM *datum, int size){
+ assert(datum && size >= 0);
+ if(size <= datum->dsize){
+ datum->dsize = size;
+ datum->dptr[size] = '\0';
+ } else {
+ if(size >= datum->asize){
+ datum->asize = datum->asize * 2 + size + 1;
+ CB_REALLOC(datum->dptr, datum->asize);
+ }
+ memset(datum->dptr + datum->dsize, 0, (size - datum->dsize) + 1);
+ datum->dsize = size;
+ }
+}
+
+
+/* Perform formatted output into a datum. */
+void cbdatumprintf(CBDATUM *datum, const char *format, ...){
+ va_list ap;
+ char *tmp, cbuf[CB_NUMBUFSIZ], tbuf[CB_NUMBUFSIZ*2];
+ unsigned char c;
+ int cblen, tlen;
+ assert(datum && format);
+ va_start(ap, format);
+ while(*format != '\0'){
+ if(*format == '%'){
+ cbuf[0] = '%';
+ cblen = 1;
+ format++;
+ while(strchr("0123456789 .+-", *format) && *format != '\0' && cblen < CB_NUMBUFSIZ - 1){
+ cbuf[cblen++] = *format;
+ format++;
+ }
+ cbuf[cblen++] = *format;
+ cbuf[cblen] = '\0';
+ switch(*format){
+ case 's':
+ tmp = va_arg(ap, char *);
+ if(!tmp) tmp = "(null)";
+ cbdatumcat(datum, tmp, -1);
+ break;
+ case 'd':
+ tlen = sprintf(tbuf, cbuf, va_arg(ap, int));
+ cbdatumcat(datum, tbuf, tlen);
+ break;
+ case 'o': case 'u': case 'x': case 'X': case 'c':
+ tlen = sprintf(tbuf, cbuf, va_arg(ap, unsigned int));
+ cbdatumcat(datum, tbuf, tlen);
+ break;
+ case 'e': case 'E': case 'f': case 'g': case 'G':
+ tlen = sprintf(tbuf, cbuf, va_arg(ap, double));
+ cbdatumcat(datum, tbuf, tlen);
+ break;
+ case '@':
+ tmp = va_arg(ap, char *);
+ if(!tmp) tmp = "(null)";
+ while(*tmp){
+ switch(*tmp){
+ case '&': cbdatumcat(datum, "&amp;", 5); break;
+ case '<': cbdatumcat(datum, "&lt;", 4); break;
+ case '>': cbdatumcat(datum, "&gt;", 4); break;
+ case '"': cbdatumcat(datum, "&quot;", 6); break;
+ default:
+ if(!((*tmp >= 0 && *tmp <= 0x8) || (*tmp >= 0x0e && *tmp <= 0x1f)))
+ cbdatumcat(datum, tmp, 1);
+ break;
+ }
+ tmp++;
+ }
+ break;
+ case '?':
+ tmp = va_arg(ap, char *);
+ if(!tmp) tmp = "(null)";
+ while(*tmp){
+ c = *(unsigned char *)tmp;
+ if((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
+ (c >= '0' && c <= '9') || (c != '\0' && strchr("_-.", c))){
+ cbdatumcat(datum, tmp, 1);
+ } else {
+ tlen = sprintf(tbuf, "%%%02X", c);
+ cbdatumcat(datum, tbuf, tlen);
+ }
+ tmp++;
+ }
+ break;
+ case ':':
+ tmp = va_arg(ap, char *);
+ if(!tmp) tmp = "";
+ tmp = cbmimeencode(tmp, "UTF-8", TRUE);
+ cbdatumcat(datum, tmp, -1);
+ free(tmp);
+ break;
+ case '%':
+ cbdatumcat(datum, "%", 1);
+ break;
+ }
+ } else {
+ cbdatumcat(datum, format, 1);
+ }
+ format++;
+ }
+ va_end(ap);
+}
+
+
+/* Convert a datum to an allocated region. */
+char *cbdatumtomalloc(CBDATUM *datum, int *sp){
+ char *ptr;
+ assert(datum);
+ ptr = datum->dptr;
+ if(sp) *sp = datum->dsize;
+ free(datum);
+ return ptr;
+}
+
+
+/* Get a list handle. */
+CBLIST *cblistopen(void){
+ CBLIST *list;
+ CB_MALLOC(list, sizeof(*list));
+ list->anum = CB_LISTUNIT;
+ CB_MALLOC(list->array, sizeof(list->array[0]) * list->anum);
+ list->start = 0;
+ list->num = 0;
+ return list;
+}
+
+
+/* Copy a list. */
+CBLIST *cblistdup(const CBLIST *list){
+ CBLIST *newlist;
+ int i, size;
+ const char *val;
+ assert(list);
+ CB_LISTOPEN2(newlist, CB_LISTNUM(list));
+ for(i = 0; i < CB_LISTNUM(list); i++){
+ val = CB_LISTVAL2(list, i, size);
+ CB_LISTPUSH(newlist, val, size);
+ }
+ return newlist;
+}
+
+
+/* Close a list handle. */
+void cblistclose(CBLIST *list){
+ int i, end;
+ assert(list);
+ end = list->start + list->num;
+ for(i = list->start; i < end; i++){
+ free(list->array[i].dptr);
+ }
+ free(list->array);
+ free(list);
+}
+
+
+/* Get the number of elements of a list. */
+int cblistnum(const CBLIST *list){
+ assert(list);
+ return list->num;
+}
+
+
+/* Get the pointer to the region of an element. */
+const char *cblistval(const CBLIST *list, int index, int *sp){
+ assert(list && index >= 0);
+ if(index >= list->num) return NULL;
+ index += list->start;
+ if(sp) *sp = list->array[index].dsize;
+ return list->array[index].dptr;
+}
+
+
+/* Add an element at the end of a list. */
+void cblistpush(CBLIST *list, const char *ptr, int size){
+ int index;
+ assert(list && ptr);
+ if(size < 0) size = strlen(ptr);
+ index = list->start + list->num;
+ if(index >= list->anum){
+ list->anum *= 2;
+ CB_REALLOC(list->array, list->anum * sizeof(list->array[0]));
+ }
+ CB_MALLOC(list->array[index].dptr, (size < CB_DATUMUNIT ? CB_DATUMUNIT : size) + 1);
+ memcpy(list->array[index].dptr, ptr, size);
+ list->array[index].dptr[size] = '\0';
+ list->array[index].dsize = size;
+ list->num++;
+}
+
+
+/* Remove an element of the end of a list. */
+char *cblistpop(CBLIST *list, int *sp){
+ int index;
+ assert(list);
+ if(list->num < 1) return NULL;
+ index = list->start + list->num - 1;
+ list->num--;
+ if(sp) *sp = list->array[index].dsize;
+ return list->array[index].dptr;
+}
+
+
+/* Add an element at the top of a list. */
+void cblistunshift(CBLIST *list, const char *ptr, int size){
+ int index;
+ assert(list && ptr);
+ if(size < 0) size = strlen(ptr);
+ if(list->start < 1){
+ if(list->start + list->num >= list->anum){
+ list->anum *= 2;
+ CB_REALLOC(list->array, list->anum * sizeof(list->array[0]));
+ }
+ list->start = list->anum - list->num;
+ memmove(list->array + list->start, list->array, list->num * sizeof(list->array[0]));
+ }
+ index = list->start - 1;
+ CB_MALLOC(list->array[index].dptr, (size < CB_DATUMUNIT ? CB_DATUMUNIT : size) + 1);
+ memcpy(list->array[index].dptr, ptr, size);
+ list->array[index].dptr[size] = '\0';
+ list->array[index].dsize = size;
+ list->start--;
+ list->num++;
+}
+
+
+/* Remove an element of the top of a list. */
+char *cblistshift(CBLIST *list, int *sp){
+ int index;
+ assert(list);
+ if(list->num < 1) return NULL;
+ index = list->start;
+ list->start++;
+ list->num--;
+ if(sp) *sp = list->array[index].dsize;
+ return list->array[index].dptr;
+}
+
+
+/* Add an element at the specified location of a list. */
+void cblistinsert(CBLIST *list, int index, const char *ptr, int size){
+ assert(list && index >= 0);
+ if(index > list->num) return;
+ if(size < 0) size = strlen(ptr);
+ index += list->start;
+ if(list->start + list->num >= list->anum){
+ list->anum *= 2;
+ CB_REALLOC(list->array, list->anum * sizeof(list->array[0]));
+ }
+ memmove(list->array + index + 1, list->array + index,
+ sizeof(list->array[0]) * (list->start + list->num - index));
+ CB_MEMDUP(list->array[index].dptr, ptr, size);
+ list->array[index].dsize = size;
+ list->num++;
+}
+
+
+/* Remove an element at the specified location of a list. */
+char *cblistremove(CBLIST *list, int index, int *sp){
+ char *dptr;
+ assert(list && index >= 0);
+ if(index >= list->num) return NULL;
+ index += list->start;
+ dptr = list->array[index].dptr;
+ if(sp) *sp = list->array[index].dsize;
+ list->num--;
+ memmove(list->array + index, list->array + index + 1,
+ sizeof(list->array[0]) * (list->start + list->num - index));
+ return dptr;
+}
+
+
+/* Overwrite an element at the specified location of a list. */
+void cblistover(CBLIST *list, int index, const char *ptr, int size){
+ assert(list && index >= 0);
+ if(index >= list->num) return;
+ if(size < 0) size = strlen(ptr);
+ index += list->start;
+ if(size > list->array[index].dsize)
+ CB_REALLOC(list->array[index].dptr, size + 1);
+ memcpy(list->array[index].dptr, ptr, size);
+ list->array[index].dsize = size;
+ list->array[index].dptr[size] = '\0';
+}
+
+
+/* Sort elements of a list in lexical order. */
+void cblistsort(CBLIST *list){
+ assert(list);
+ qsort(list->array + list->start, list->num, sizeof(list->array[0]), cblistelemcmp);
+}
+
+
+/* Search a list for an element using liner search. */
+int cblistlsearch(const CBLIST *list, const char *ptr, int size){
+ int i, end;
+ assert(list && ptr);
+ if(size < 0) size = strlen(ptr);
+ end = list->start + list->num;
+ for(i = list->start; i < end; i++){
+ if(list->array[i].dsize == size && !memcmp(list->array[i].dptr, ptr, size))
+ return i - list->start;
+ }
+ return -1;
+}
+
+
+/* Search a list for an element using binary search. */
+int cblistbsearch(const CBLIST *list, const char *ptr, int size){
+ CBLISTDATUM key, *res;
+ assert(list && ptr);
+ if(size < 0) size = strlen(ptr);
+ CB_MEMDUP(key.dptr, ptr, size);
+ key.dsize = size;
+ res = bsearch(&key, list->array + list->start, list->num, sizeof(list->array[0]), cblistelemcmp);
+ free(key.dptr);
+ return res ? (res - list->array - list->start) : -1;
+}
+
+
+/* Serialize a list into a byte array. */
+char *cblistdump(const CBLIST *list, int *sp){
+ char *buf, vnumbuf[CB_VNUMBUFSIZ];
+ const char *vbuf;
+ int i, bsiz, vnumsiz, ln, vsiz;
+ assert(list && sp);
+ ln = CB_LISTNUM(list);
+ CB_SETVNUMBUF(vnumsiz, vnumbuf, ln);
+ CB_MALLOC(buf, vnumsiz + 1);
+ memcpy(buf, vnumbuf, vnumsiz);
+ bsiz = vnumsiz;
+ for(i = 0; i < ln; i++){
+ vbuf = CB_LISTVAL2(list, i, vsiz);
+ CB_SETVNUMBUF(vnumsiz, vnumbuf, vsiz);
+ CB_REALLOC(buf, bsiz + vnumsiz + vsiz + 1);
+ memcpy(buf + bsiz, vnumbuf, vnumsiz);
+ bsiz += vnumsiz;
+ memcpy(buf + bsiz, vbuf, vsiz);
+ bsiz += vsiz;
+ }
+ *sp = bsiz;
+ return buf;
+}
+
+
+/* Redintegrate a serialized list. */
+CBLIST *cblistload(const char *ptr, int size){
+ CBLIST *list;
+ const char *rp;
+ int i, anum, step, ln, vsiz;
+ assert(ptr && size >= 0);
+ anum = size / (sizeof(CBLISTDATUM) + 1);
+ CB_LISTOPEN2(list, anum);
+ rp = ptr;
+ CB_READVNUMBUF(rp, size, ln, step);
+ rp += step;
+ size -= step;
+ if(ln > size) return list;
+ for(i = 0; i < ln; i++){
+ if(size < 1) break;
+ CB_READVNUMBUF(rp, size, vsiz, step);
+ rp += step;
+ size -= step;
+ if(vsiz > size) break;
+ CB_LISTPUSH(list, rp, vsiz);
+ rp += vsiz;
+ }
+ return list;
+}
+
+
+/* Get a map handle. */
+CBMAP *cbmapopen(void){
+ return cbmapopenex(CB_MAPBNUM);
+}
+
+
+/* Copy a map. */
+CBMAP *cbmapdup(CBMAP *map){
+ CBMAP *newmap;
+ const char *kbuf, *vbuf;
+ int ksiz, vsiz;
+ assert(map);
+ cbmapiterinit(map);
+ newmap = map->rnum > CB_MAPPBNUM ? cbmapopen() : cbmapopenex(CB_MAPPBNUM);
+ while((kbuf = cbmapiternext(map, &ksiz)) != NULL){
+ CB_MAPITERVAL(vbuf, kbuf, vsiz);
+ cbmapput(newmap, kbuf, ksiz, vbuf, vsiz, FALSE);
+ }
+ cbmapiterinit(map);
+ return newmap;
+}
+
+
+/* Close a map handle. */
+void cbmapclose(CBMAP *map){
+ CBMAPDATUM *datum, *next;
+ datum = map->first;
+ while(datum){
+ next = datum->next;
+ free(datum);
+ datum = next;
+ }
+ free(map->buckets);
+ free(map);
+}
+
+
+/* Store a record. */
+int cbmapput(CBMAP *map, const char *kbuf, int ksiz, const char *vbuf, int vsiz, int over){
+ CBMAPDATUM *datum, **entp, *old;
+ char *dbuf;
+ int bidx, hash, kcmp, psiz;
+ assert(map && kbuf && vbuf);
+ if(ksiz < 0) ksiz = strlen(kbuf);
+ if(vsiz < 0) vsiz = strlen(vbuf);
+ CB_FIRSTHASH(hash, kbuf, ksiz);
+ bidx = hash % map->bnum;
+ datum = map->buckets[bidx];
+ entp = map->buckets + bidx;
+ CB_SECONDHASH(hash, kbuf, ksiz);
+ while(datum){
+ if(hash > datum->hash){
+ entp = &(datum->left);
+ datum = datum->left;
+ } else if(hash < datum->hash){
+ entp = &(datum->right);
+ datum = datum->right;
+ } else {
+ dbuf = (char *)datum + sizeof(*datum);
+ kcmp = cbkeycmp(kbuf, ksiz, dbuf, datum->ksiz);
+ if(kcmp < 0){
+ entp = &(datum->left);
+ datum = datum->left;
+ } else if(kcmp > 0){
+ entp = &(datum->right);
+ datum = datum->right;
+ } else {
+ if(!over) return FALSE;
+ psiz = CB_ALIGNPAD(ksiz);
+ if(vsiz > datum->vsiz){
+ old = datum;
+ CB_REALLOC(datum, sizeof(*datum) + ksiz + psiz + vsiz + 1);
+ if(datum != old){
+ if(map->first == old) map->first = datum;
+ if(map->last == old) map->last = datum;
+ if(*entp == old) *entp = datum;
+ if(datum->prev) datum->prev->next = datum;
+ if(datum->next) datum->next->prev = datum;
+ dbuf = (char *)datum + sizeof(*datum);
+ }
+ }
+ memcpy(dbuf + ksiz + psiz, vbuf, vsiz);
+ dbuf[ksiz+psiz+vsiz] = '\0';
+ datum->vsiz = vsiz;
+ return TRUE;
+ }
+ }
+ }
+ psiz = CB_ALIGNPAD(ksiz);
+ CB_MALLOC(datum, sizeof(*datum) + ksiz + psiz + vsiz + 1);
+ dbuf = (char *)datum + sizeof(*datum);
+ memcpy(dbuf, kbuf, ksiz);
+ dbuf[ksiz] = '\0';
+ datum->ksiz = ksiz;
+ memcpy(dbuf + ksiz + psiz, vbuf, vsiz);
+ dbuf[ksiz+psiz+vsiz] = '\0';
+ datum->vsiz = vsiz;
+ datum->hash = hash;
+ datum->left = NULL;
+ datum->right = NULL;
+ datum->prev = map->last;
+ datum->next = NULL;
+ *entp = datum;
+ if(!map->first) map->first = datum;
+ if(map->last) map->last->next = datum;
+ map->last = datum;
+ map->rnum++;
+ return TRUE;
+}
+
+
+/* Concatenate a value at the end of the value of the existing record. */
+void cbmapputcat(CBMAP *map, const char *kbuf, int ksiz, const char *vbuf, int vsiz){
+ CBMAPDATUM *datum, **entp, *old;
+ char *dbuf;
+ int bidx, hash, kcmp, psiz, asiz, unit;
+ assert(map && kbuf && vbuf);
+ if(ksiz < 0) ksiz = strlen(kbuf);
+ if(vsiz < 0) vsiz = strlen(vbuf);
+ CB_FIRSTHASH(hash, kbuf, ksiz);
+ bidx = hash % map->bnum;
+ datum = map->buckets[bidx];
+ entp = map->buckets + bidx;
+ CB_SECONDHASH(hash, kbuf, ksiz);
+ while(datum){
+ if(hash > datum->hash){
+ entp = &(datum->left);
+ datum = datum->left;
+ } else if(hash < datum->hash){
+ entp = &(datum->right);
+ datum = datum->right;
+ } else {
+ dbuf = (char *)datum + sizeof(*datum);
+ kcmp = cbkeycmp(kbuf, ksiz, dbuf, datum->ksiz);
+ if(kcmp < 0){
+ entp = &(datum->left);
+ datum = datum->left;
+ } else if(kcmp > 0){
+ entp = &(datum->right);
+ datum = datum->right;
+ } else {
+ psiz = CB_ALIGNPAD(ksiz);
+ asiz = sizeof(*datum) + ksiz + psiz + datum->vsiz + vsiz + 1;
+ unit = asiz <= CB_MAPCSUNIT ? CB_MAPCSUNIT : CB_MAPCBUNIT;
+ asiz = (asiz - 1) + unit - (asiz - 1) % unit;
+ old = datum;
+ CB_REALLOC(datum, asiz);
+ if(datum != old){
+ if(map->first == old) map->first = datum;
+ if(map->last == old) map->last = datum;
+ if(*entp == old) *entp = datum;
+ if(datum->prev) datum->prev->next = datum;
+ if(datum->next) datum->next->prev = datum;
+ dbuf = (char *)datum + sizeof(*datum);
+ }
+ memcpy(dbuf + ksiz + psiz + datum->vsiz, vbuf, vsiz);
+ dbuf[ksiz+psiz+datum->vsiz+vsiz] = '\0';
+ datum->vsiz += vsiz;
+ return;
+ }
+ }
+ }
+ psiz = CB_ALIGNPAD(ksiz);
+ asiz = sizeof(*datum) + ksiz + psiz + vsiz + 1;
+ unit = asiz <= CB_MAPCSUNIT ? CB_MAPCSUNIT : CB_MAPCBUNIT;
+ asiz = (asiz - 1) + unit - (asiz - 1) % unit;
+ CB_MALLOC(datum, asiz);
+ dbuf = (char *)datum + sizeof(*datum);
+ memcpy(dbuf, kbuf, ksiz);
+ dbuf[ksiz] = '\0';
+ datum->ksiz = ksiz;
+ memcpy(dbuf + ksiz + psiz, vbuf, vsiz);
+ dbuf[ksiz+psiz+vsiz] = '\0';
+ datum->vsiz = vsiz;
+ datum->hash = hash;
+ datum->left = NULL;
+ datum->right = NULL;
+ datum->prev = map->last;
+ datum->next = NULL;
+ *entp = datum;
+ if(!map->first) map->first = datum;
+ if(map->last) map->last->next = datum;
+ map->last = datum;
+ map->rnum++;
+}
+
+
+/* Delete a record. */
+int cbmapout(CBMAP *map, const char *kbuf, int ksiz){
+ CBMAPDATUM *datum, **entp, *tmp;
+ char *dbuf;
+ int bidx, hash, kcmp;
+ assert(map && kbuf);
+ if(ksiz < 0) ksiz = strlen(kbuf);
+ CB_FIRSTHASH(hash, kbuf, ksiz);
+ bidx = hash % map->bnum;
+ datum = map->buckets[bidx];
+ entp = map->buckets + bidx;
+ CB_SECONDHASH(hash, kbuf, ksiz);
+ while(datum){
+ if(hash > datum->hash){
+ entp = &(datum->left);
+ datum = datum->left;
+ } else if(hash < datum->hash){
+ entp = &(datum->right);
+ datum = datum->right;
+ } else {
+ dbuf = (char *)datum + sizeof(*datum);
+ kcmp = cbkeycmp(kbuf, ksiz, dbuf, datum->ksiz);
+ if(kcmp < 0){
+ entp = &(datum->left);
+ datum = datum->left;
+ } else if(kcmp > 0){
+ entp = &(datum->right);
+ datum = datum->right;
+ } else {
+ if(datum->prev) datum->prev->next = datum->next;
+ if(datum->next) datum->next->prev = datum->prev;
+ if(datum == map->first) map->first = datum->next;
+ if(datum == map->last) map->last = datum->prev;
+ if(datum->left && !datum->right){
+ *entp = datum->left;
+ } else if(!datum->left && datum->right){
+ *entp = datum->right;
+ } else if(!datum->left && !datum->left){
+ *entp = NULL;
+ } else {
+ *entp = datum->left;
+ tmp = *entp;
+ while(TRUE){
+ if(hash > tmp->hash){
+ if(tmp->left){
+ tmp = tmp->left;
+ } else {
+ tmp->left = datum->right;
+ break;
+ }
+ } else if(hash < tmp->hash){
+ if(tmp->right){
+ tmp = tmp->right;
+ } else {
+ tmp->right = datum->right;
+ break;
+ }
+ } else {
+ kcmp = cbkeycmp(kbuf, ksiz, dbuf, datum->ksiz);
+ if(kcmp < 0){
+ if(tmp->left){
+ tmp = tmp->left;
+ } else {
+ tmp->left = datum->right;
+ break;
+ }
+ } else {
+ if(tmp->right){
+ tmp = tmp->right;
+ } else {
+ tmp->right = datum->right;
+ break;
+ }
+ }
+ }
+ }
+ }
+ free(datum);
+ map->rnum--;
+ return TRUE;
+ }
+ }
+ }
+ return FALSE;
+}
+
+
+/* Retrieve a record. */
+const char *cbmapget(const CBMAP *map, const char *kbuf, int ksiz, int *sp){
+ CBMAPDATUM *datum;
+ char *dbuf;
+ int hash, kcmp;
+ assert(map && kbuf);
+ if(ksiz < 0) ksiz = strlen(kbuf);
+ CB_FIRSTHASH(hash, kbuf, ksiz);
+ datum = map->buckets[hash%map->bnum];
+ CB_SECONDHASH(hash, kbuf, ksiz);
+ while(datum){
+ if(hash > datum->hash){
+ datum = datum->left;
+ } else if(hash < datum->hash){
+ datum = datum->right;
+ } else {
+ dbuf = (char *)datum + sizeof(*datum);
+ kcmp = cbkeycmp(kbuf, ksiz, dbuf, datum->ksiz);
+ if(kcmp < 0){
+ datum = datum->left;
+ } else if(kcmp > 0){
+ datum = datum->right;
+ } else {
+ if(sp) *sp = datum->vsiz;
+ return dbuf + datum->ksiz + CB_ALIGNPAD(datum->ksiz);
+ }
+ }
+ }
+ return NULL;
+}
+
+
+/* Move a record to the edge. */
+int cbmapmove(CBMAP *map, const char *kbuf, int ksiz, int head){
+ CBMAPDATUM *datum;
+ char *dbuf;
+ int hash, kcmp;
+ assert(map && kbuf);
+ if(ksiz < 0) ksiz = strlen(kbuf);
+ CB_FIRSTHASH(hash, kbuf, ksiz);
+ datum = map->buckets[hash%map->bnum];
+ CB_SECONDHASH(hash, kbuf, ksiz);
+ while(datum){
+ if(hash > datum->hash){
+ datum = datum->left;
+ } else if(hash < datum->hash){
+ datum = datum->right;
+ } else {
+ dbuf = (char *)datum + sizeof(*datum);
+ kcmp = cbkeycmp(kbuf, ksiz, dbuf, datum->ksiz);
+ if(kcmp < 0){
+ datum = datum->left;
+ } else if(kcmp > 0){
+ datum = datum->right;
+ } else {
+ if(head){
+ if(map->first == datum) return TRUE;
+ if(map->last == datum) map->last = datum->prev;
+ if(datum->prev) datum->prev->next = datum->next;
+ if(datum->next) datum->next->prev = datum->prev;
+ datum->prev = NULL;
+ datum->next = map->first;
+ map->first->prev = datum;
+ map->first = datum;
+ } else {
+ if(map->last == datum) return TRUE;
+ if(map->first == datum) map->first = datum->next;
+ if(datum->prev) datum->prev->next = datum->next;
+ if(datum->next) datum->next->prev = datum->prev;
+ datum->prev = map->last;
+ datum->next = NULL;
+ map->last->next = datum;
+ map->last = datum;
+ }
+ return TRUE;
+ }
+ }
+ }
+ return FALSE;
+}
+
+
+/* Initialize the iterator of a map handle. */
+void cbmapiterinit(CBMAP *map){
+ assert(map);
+ map->cur = map->first;
+}
+
+
+/* Get the next key of the iterator. */
+const char *cbmapiternext(CBMAP *map, int *sp){
+ CBMAPDATUM *datum;
+ assert(map);
+ if(!map->cur) return NULL;
+ datum = map->cur;
+ map->cur = datum->next;
+ if(sp) *sp = datum->ksiz;
+ return (char *)datum + sizeof(*datum);
+}
+
+
+/* Get the value binded to the key fetched from the iterator. */
+const char *cbmapiterval(const char *kbuf, int *sp){
+ CBMAPDATUM *datum;
+ assert(kbuf);
+ datum = (CBMAPDATUM *)(kbuf - sizeof(*datum));
+ if(sp) *sp = datum->vsiz;
+ return (char *)datum + sizeof(*datum) + datum->ksiz + CB_ALIGNPAD(datum->ksiz);
+}
+
+
+/* Get the number of the records stored in a map. */
+int cbmaprnum(const CBMAP *map){
+ assert(map);
+ return map->rnum;
+}
+
+
+/* Get the list handle contains all keys in a map. */
+CBLIST *cbmapkeys(CBMAP *map){
+ CBLIST *list;
+ const char *kbuf;
+ int anum, ksiz;
+ assert(map);
+ anum = cbmaprnum(map);
+ CB_LISTOPEN2(list, anum);
+ cbmapiterinit(map);
+ while((kbuf = cbmapiternext(map, &ksiz)) != NULL){
+ CB_LISTPUSH(list, kbuf, ksiz);
+ }
+ return list;
+}
+
+
+/* Get the list handle contains all values in a map. */
+CBLIST *cbmapvals(CBMAP *map){
+ CBLIST *list;
+ const char *kbuf, *vbuf;
+ int anum, ksiz, vsiz;
+ assert(map);
+ anum = cbmaprnum(map);
+ CB_LISTOPEN2(list, anum);
+ cbmapiterinit(map);
+ while((kbuf = cbmapiternext(map, &ksiz)) != NULL){
+ CB_MAPITERVAL(vbuf, kbuf, vsiz);
+ CB_LISTPUSH(list, vbuf, vsiz);
+ }
+ return list;
+}
+
+
+/* Serialize a map into a byte array. */
+char *cbmapdump(CBMAP *map, int *sp){
+ char *buf, vnumbuf[CB_VNUMBUFSIZ];
+ const char *kbuf, *vbuf;
+ int bsiz, vnumsiz, rn, ksiz, vsiz;
+ assert(map && sp);
+ rn = cbmaprnum(map);
+ CB_SETVNUMBUF(vnumsiz, vnumbuf, rn);
+ CB_MALLOC(buf, vnumsiz + 1);
+ memcpy(buf, vnumbuf, vnumsiz);
+ bsiz = vnumsiz;
+ cbmapiterinit(map);
+ while((kbuf = cbmapiternext(map, &ksiz)) != NULL){
+ CB_MAPITERVAL(vbuf, kbuf, vsiz);
+ CB_SETVNUMBUF(vnumsiz, vnumbuf, ksiz);
+ CB_REALLOC(buf, bsiz + vnumsiz + ksiz + 1);
+ memcpy(buf + bsiz, vnumbuf, vnumsiz);
+ bsiz += vnumsiz;
+ memcpy(buf + bsiz, kbuf, ksiz);
+ bsiz += ksiz;
+ CB_SETVNUMBUF(vnumsiz, vnumbuf, vsiz);
+ CB_REALLOC(buf, bsiz + vnumsiz + vsiz + 1);
+ memcpy(buf + bsiz, vnumbuf, vnumsiz);
+ bsiz += vnumsiz;
+ memcpy(buf + bsiz, vbuf, vsiz);
+ bsiz += vsiz;
+ }
+ *sp = bsiz;
+ return buf;
+}
+
+
+/* Redintegrate a serialized map. */
+CBMAP *cbmapload(const char *ptr, int size){
+ CBMAP *map;
+ const char *rp, *kbuf, *vbuf;
+ int i, step, rn, ksiz, vsiz;
+ assert(ptr && size >= 0);
+ map = cbmapopenex(CB_MAPPBNUM);
+ rp = ptr;
+ CB_READVNUMBUF(rp, size, rn, step);
+ rp += step;
+ size -= step;
+ if(rn > size) return map;
+ for(i = 0; i < rn; i++){
+ if(size < 1) break;
+ CB_READVNUMBUF(rp, size, ksiz, step);
+ rp += step;
+ size -= step;
+ if(ksiz > size) break;
+ kbuf = rp;
+ rp += ksiz;
+ if(size < 1) break;
+ CB_READVNUMBUF(rp, size, vsiz, step);
+ rp += step;
+ size -= step;
+ if(vsiz > size) break;
+ vbuf = rp;
+ rp += vsiz;
+ cbmapput(map, kbuf, ksiz, vbuf, vsiz, TRUE);
+ }
+ return map;
+}
+
+
+/* Redintegrate a serialized map and get one of the records. */
+char *cbmaploadone(const char *ptr, int size, const char *kbuf, int ksiz, int *sp){
+ const char *rp, *tkbuf, *vbuf;
+ char *rv;
+ int i, step, rn, tksiz, vsiz;
+ assert(ptr && size >= 0 && kbuf);
+ if(ksiz < 0) ksiz = strlen(kbuf);
+ rp = ptr;
+ CB_READVNUMBUF(rp, size, rn, step);
+ rp += step;
+ size -= step;
+ if(rn > size) return NULL;
+ for(i = 0; i < rn; i++){
+ if(size < 1) break;
+ CB_READVNUMBUF(rp, size, tksiz, step);
+ rp += step;
+ size -= step;
+ if(tksiz > size) break;
+ tkbuf = rp;
+ rp += tksiz;
+ if(size < 1) break;
+ CB_READVNUMBUF(rp, size, vsiz, step);
+ rp += step;
+ size -= step;
+ if(vsiz > size) break;
+ vbuf = rp;
+ rp += vsiz;
+ if(tksiz == ksiz && !memcmp(tkbuf, kbuf, ksiz)){
+ if(sp) *sp = vsiz;
+ CB_MEMDUP(rv, vbuf, vsiz);
+ return rv;
+ }
+ }
+ return NULL;
+}
+
+
+/* Get a heap handle. */
+CBHEAP *cbheapopen(int size, int max, int(*compar)(const void *, const void *)){
+ CBHEAP *heap;
+ assert(size > 0 && max >= 0 && compar);
+ CB_MALLOC(heap, sizeof(*heap));
+ CB_MALLOC(heap->base, size * max + 1);
+ CB_MALLOC(heap->swap, size);
+ heap->size = size;
+ heap->num = 0;
+ heap->max = max;
+ heap->compar = compar;
+ return heap;
+}
+
+
+/* Copy a heap. */
+CBHEAP *cbheapdup(CBHEAP *heap){
+ CBHEAP *newheap;
+ assert(heap);
+ CB_MALLOC(newheap, sizeof(*newheap));
+ CB_MEMDUP(newheap->base, heap->base, heap->size * heap->max);
+ CB_MALLOC(newheap->swap, heap->size);
+ newheap->size = heap->size;
+ newheap->num = heap->num;
+ newheap->max = heap->max;
+ newheap->compar = heap->compar;
+ return newheap;
+}
+
+
+/* Close a heap handle. */
+void cbheapclose(CBHEAP *heap){
+ assert(heap);
+ free(heap->swap);
+ free(heap->base);
+ free(heap);
+}
+
+
+/* Get the number of the records stored in a heap. */
+int cbheapnum(CBHEAP *heap){
+ assert(heap);
+ return heap->num;
+}
+
+
+/* Insert a record into a heap. */
+int cbheapinsert(CBHEAP *heap, const void *ptr){
+ char *base;
+ int size, pidx, cidx, bot;
+ assert(heap && ptr);
+ if(heap->max < 1) return FALSE;
+ base = heap->base;
+ size = heap->size;
+ if(heap->num >= heap->max){
+ if(heap->compar(ptr, base) > 0) return FALSE;
+ memcpy(base, ptr, size);
+ pidx = 0;
+ bot = heap->num / 2;
+ while(pidx < bot){
+ cidx = pidx * 2 + 1;
+ if(cidx < heap->num - 1 && heap->compar(base + cidx * size, base + (cidx + 1) * size) < 0)
+ cidx++;
+ if(heap->compar(base + pidx * size, base + cidx * size) > 0) break;
+ memcpy(heap->swap, base + pidx * size, size);
+ memcpy(base + pidx * size, base + cidx * size, size);
+ memcpy(base + cidx * size, heap->swap, size);
+ pidx = cidx;
+ }
+ } else {
+ memcpy(base + size * heap->num, ptr, size);
+ cidx = heap->num;
+ while(cidx > 0){
+ pidx = (cidx - 1) / 2;
+ if(heap->compar(base + cidx * size, base + pidx * size) <= 0) break;
+ memcpy(heap->swap, base + cidx * size, size);
+ memcpy(base + cidx * size, base + pidx * size, size);
+ memcpy(base + pidx * size, heap->swap, size);
+ cidx = pidx;
+ }
+ heap->num++;
+ }
+ return TRUE;
+}
+
+
+/* Get the pointer to the region of a record in a heap. */
+const void *cbheapval(CBHEAP *heap, int index){
+ assert(heap && index >= 0);
+ if(index >= heap->num) return NULL;
+ return heap->base + index * heap->size;
+}
+
+
+/* Convert a heap to an allocated region. */
+void *cbheaptomalloc(CBHEAP *heap, int *np){
+ char *ptr;
+ assert(heap);
+ qsort(heap->base, heap->num, heap->size, heap->compar);
+ ptr = heap->base;
+ if(np) *np = heap->num;
+ free(heap->swap);
+ free(heap);
+ return ptr;
+}
+
+
+/* Allocate a formatted string on memory. */
+char *cbsprintf(const char *format, ...){
+ va_list ap;
+ char *buf, cbuf[CB_SPBUFSIZ], *str;
+ int len, cblen, num, slen;
+ unsigned int unum;
+ double dnum;
+ va_start(ap, format);
+ assert(format);
+ CB_MALLOC(buf, 1);
+ len = 0;
+ while(*format != '\0'){
+ if(*format == '%'){
+ cbuf[0] = '%';
+ cblen = 1;
+ format++;
+ while(strchr("0123456789 .+-", *format) && *format != '\0' && cblen < CB_SPBUFSIZ - 1){
+ cbuf[cblen++] = *format;
+ format++;
+ }
+ cbuf[cblen] = '\0';
+ if(atoi(cbuf + 1) > CB_SPMAXWIDTH - 16){
+ sprintf(cbuf, "(err)");
+ } else {
+ cbuf[cblen++] = *format;
+ cbuf[cblen] = '\0';
+ }
+ switch(*format){
+ case 'd':
+ num = va_arg(ap, int);
+ CB_REALLOC(buf, len + CB_SPMAXWIDTH + 2);
+ len += sprintf(buf + len, cbuf, num);
+ break;
+ case 'o': case 'u': case 'x': case 'X': case 'c':
+ unum = va_arg(ap, unsigned int);
+ CB_REALLOC(buf, len + CB_SPMAXWIDTH + 2);
+ len += sprintf(buf + len, cbuf, unum);
+ break;
+ case 'e': case 'E': case 'f': case 'g': case 'G':
+ dnum = va_arg(ap, double);
+ CB_REALLOC(buf, len + CB_SPMAXWIDTH + 2);
+ len += sprintf(buf + len, cbuf, dnum);
+ break;
+ case 's':
+ str = va_arg(ap, char *);
+ slen = strlen(str);
+ CB_REALLOC(buf, len + slen + 2);
+ memcpy(buf + len, str, slen);
+ len += slen;
+ break;
+ case '%':
+ CB_REALLOC(buf, len + 2);
+ buf[len++] = '%';
+ break;
+ default:
+ break;
+ }
+ } else {
+ CB_REALLOC(buf, len + 2);
+ buf[len++] = *format;
+ }
+ format++;
+ }
+ buf[len] = '\0';
+ va_end(ap);
+ return buf;
+}
+
+
+/* Replace some patterns in a string. */
+char *cbreplace(const char *str, CBMAP *pairs){
+ int i, bsiz, wi, rep, ksiz, vsiz;
+ char *buf;
+ const char *key, *val;
+ assert(str && pairs);
+ bsiz = CB_DATUMUNIT;
+ CB_MALLOC(buf, bsiz);
+ wi = 0;
+ while(*str != '\0'){
+ rep = FALSE;
+ cbmapiterinit(pairs);
+ while((key = cbmapiternext(pairs, &ksiz)) != NULL){
+ for(i = 0; i < ksiz; i++){
+ if(str[i] == '\0' || str[i] != key[i]) break;
+ }
+ if(i == ksiz){
+ CB_MAPITERVAL(val, key, vsiz);
+ if(wi + vsiz >= bsiz){
+ bsiz = bsiz * 2 + vsiz;
+ CB_REALLOC(buf, bsiz);
+ }
+ memcpy(buf + wi, val, vsiz);
+ wi += vsiz;
+ str += ksiz;
+ rep = TRUE;
+ break;
+ }
+ }
+ if(!rep){
+ if(wi + 1 >= bsiz){
+ bsiz = bsiz * 2 + 1;
+ CB_REALLOC(buf, bsiz);
+ }
+ buf[wi++] = *str;
+ str++;
+ }
+ }
+ CB_REALLOC(buf, wi + 1);
+ buf[wi] = '\0';
+ return buf;
+}
+
+
+/* Make a list by split a serial datum. */
+CBLIST *cbsplit(const char *ptr, int size, const char *delim){
+ CBLIST *list;
+ int bi, step;
+ assert(ptr);
+ CB_LISTOPEN(list);
+ if(size < 0) size = strlen(ptr);
+ if(delim){
+ for(bi = 0; bi < size; bi += step){
+ step = 0;
+ while(bi + step < size && !strchr(delim, ptr[bi+step])){
+ step++;
+ }
+ cblistpush(list, ptr + bi, step);
+ step++;
+ }
+ if(size > 0 && strchr(delim, ptr[size-1])) cblistpush(list, "", 0);
+ } else {
+ for(bi = 0; bi < size; bi += step){
+ step = 0;
+ while(bi + step < size && ptr[bi+step]){
+ step++;
+ }
+ cblistpush(list, ptr + bi, step);
+ step++;
+ }
+ if(size > 0 && ptr[size-1] == 0) cblistpush(list, "", 0);
+ }
+ return list;
+}
+
+
+/* Read whole data of a file. */
+char *cbreadfile(const char *name, int *sp){
+ struct stat sbuf;
+ char iobuf[CB_IOBUFSIZ], *buf;
+ int fd, size, asiz, rv;
+ asiz = CB_IOBUFSIZ * 2;
+ if(name){
+ if((fd = open(name, O_RDONLY, 0)) == -1) return NULL;
+ if(fstat(fd, &sbuf) != -1) asiz = sbuf.st_size + 1;
+ } else {
+ fd = 0;
+ }
+ CB_MALLOC(buf, asiz + 1);
+ size = 0;
+ while((rv = read(fd, iobuf, CB_IOBUFSIZ)) > 0){
+ if(size + rv >= asiz){
+ asiz = asiz * 2 + size;
+ CB_REALLOC(buf, asiz + 1);
+ }
+ memcpy(buf + size, iobuf, rv);
+ size += rv;
+ }
+ buf[size] = '\0';
+ if(close(fd) == -1 || rv == -1){
+ free(buf);
+ return NULL;
+ }
+ if(sp) *sp = size;
+ return buf;
+}
+
+
+/* Write data of a region into a file. */
+int cbwritefile(const char *name, const char *ptr, int size){
+ int fd, err, wb;
+ assert(ptr);
+ if(size < 0) size = strlen(ptr);
+ if(name){
+ if((fd = open(name, O_WRONLY | O_CREAT | O_TRUNC, CB_FILEMODE)) == -1) return FALSE;
+ } else {
+ fd = 1;
+ }
+ err = FALSE;
+ wb = 0;
+ do {
+ wb = write(fd, ptr, size);
+ switch(wb){
+ case -1: err = errno != EINTR ? TRUE : FALSE; break;
+ case 0: break;
+ default:
+ ptr += wb;
+ size -= wb;
+ break;
+ }
+ } while(size > 0);
+ if(close(fd) == -1) err = TRUE;
+ return err ? FALSE : TRUE;
+}
+
+
+/* Read every line of a file. */
+CBLIST *cbreadlines(const char *name){
+ char *buf, *tmp;
+ int vsiz;
+ CBMAP *pairs;
+ CBLIST *list;
+ if(!(buf = cbreadfile(name, NULL))) return NULL;
+ pairs = cbmapopenex(3);
+ cbmapput(pairs, "\r\n", 2, "\n", 1, TRUE);
+ cbmapput(pairs, "\r", 1, "\n", 1, TRUE);
+ tmp = cbreplace(buf, pairs);
+ list = cbsplit(tmp, strlen(tmp), "\n");
+ free(tmp);
+ cbmapclose(pairs);
+ free(buf);
+ if(CB_LISTNUM(list) > 0){
+ cblistval(list, CB_LISTNUM(list) - 1, &vsiz);
+ if(vsiz < 1) CB_LISTDROP(list);
+ }
+ return list;
+}
+
+
+/* Read names of files in a directory. */
+CBLIST *cbdirlist(const char *name){
+ DIR *DD;
+ struct dirent *dp;
+ CBLIST *list;
+ assert(name);
+ if(!(DD = opendir(name))) return NULL;
+ CB_LISTOPEN(list);
+ while((dp = readdir(DD)) != NULL){
+ CB_LISTPUSH(list, dp->d_name, strlen(dp->d_name));
+ }
+ if(closedir(DD) == -1){
+ CB_LISTCLOSE(list);
+ return NULL;
+ }
+ return list;
+}
+
+
+/* Get the status of a file or a directory. */
+int cbfilestat(const char *name, int *isdirp, int *sizep, time_t *mtimep){
+ struct stat sbuf;
+ assert(name);
+ if(lstat(name, &sbuf) == -1) return FALSE;
+ if(isdirp) *isdirp = S_ISDIR(sbuf.st_mode);
+ if(sizep) *sizep = (int)sbuf.st_size;
+ if(mtimep) *mtimep = sbuf.st_mtime;
+ return TRUE;
+}
+
+
+/* Remove a file or a directory and its sub ones recursively. */
+int cbremove(const char *name){
+ CBLIST *list;
+ const char *elem;
+ char *path;
+ int i, err, tail;
+ struct stat sbuf;
+ assert(name);
+ if(lstat(name, &sbuf) == -1) return FALSE;
+ if(unlink(name) == 0) return TRUE;
+ if(!S_ISDIR(sbuf.st_mode) || !(list = cbdirlist(name))) return FALSE;
+ err = FALSE;
+ tail = name[0] != '\0' && name[strlen(name)-1] == MYPATHCHR;
+ for(i = 0; i < CB_LISTNUM(list); i++){
+ elem = CB_LISTVAL(list, i);
+ if(!strcmp(MYCDIRSTR, elem) || !strcmp(MYPDIRSTR, elem)) continue;
+ if(tail){
+ path = cbsprintf("%s%s", name, elem);
+ } else {
+ path = cbsprintf("%s%c%s", name, MYPATHCHR, elem);
+ }
+ if(!cbremove(path)) err = TRUE;
+ free(path);
+ }
+ CB_LISTCLOSE(list);
+ return rmdir(name) == 0 ? TRUE : FALSE;
+}
+
+
+/* Break up a URL into elements. */
+CBMAP *cburlbreak(const char *str){
+ CBMAP *map;
+ char *tmp, *ep;
+ const char *rp;
+ int serv;
+ assert(str);
+ map = cbmapopenex(CB_MAPPBNUM);
+ CB_MEMDUP(tmp, str, strlen(str));
+ rp = cbstrtrim(tmp);
+ cbmapput(map, "self", -1, rp, -1, TRUE);
+ serv = FALSE;
+ if(cbstrfwimatch(rp, "http://")){
+ cbmapput(map, "scheme", -1, "http", -1, TRUE);
+ rp += 7;
+ serv = TRUE;
+ } else if(cbstrfwimatch(rp, "https://")){
+ cbmapput(map, "scheme", -1, "https", -1, TRUE);
+ rp += 8;
+ serv = TRUE;
+ } else if(cbstrfwimatch(rp, "ftp://")){
+ cbmapput(map, "scheme", -1, "ftp", -1, TRUE);
+ rp += 6;
+ serv = TRUE;
+ } else if(cbstrfwimatch(rp, "sftp://")){
+ cbmapput(map, "scheme", -1, "sftp", -1, TRUE);
+ rp += 7;
+ serv = TRUE;
+ } else if(cbstrfwimatch(rp, "ftps://")){
+ cbmapput(map, "scheme", -1, "ftps", -1, TRUE);
+ rp += 7;
+ serv = TRUE;
+ } else if(cbstrfwimatch(rp, "tftp://")){
+ cbmapput(map, "scheme", -1, "tftp", -1, TRUE);
+ rp += 7;
+ serv = TRUE;
+ } else if(cbstrfwimatch(rp, "ldap://")){
+ cbmapput(map, "scheme", -1, "ldap", -1, TRUE);
+ rp += 7;
+ serv = TRUE;
+ } else if(cbstrfwimatch(rp, "ldaps://")){
+ cbmapput(map, "scheme", -1, "ldaps", -1, TRUE);
+ rp += 8;
+ serv = TRUE;
+ } else if(cbstrfwimatch(rp, "file://")){
+ cbmapput(map, "scheme", -1, "file", -1, TRUE);
+ rp += 7;
+ serv = TRUE;
+ }
+ if((ep = strchr(rp, '#')) != NULL){
+ cbmapput(map, "fragment", -1, ep + 1, -1, TRUE);
+ *ep = '\0';
+ }
+ if((ep = strchr(rp, '?')) != NULL){
+ cbmapput(map, "query", -1, ep + 1, -1, TRUE);
+ *ep = '\0';
+ }
+ if(serv){
+ if((ep = strchr(rp, '/')) != NULL){
+ cbmapput(map, "path", -1, ep, -1, TRUE);
+ *ep = '\0';
+ } else {
+ cbmapput(map, "path", -1, "/", -1, TRUE);
+ }
+ if((ep = strchr(rp, '@')) != NULL){
+ *ep = '\0';
+ if(rp[0] != '\0') cbmapput(map, "authority", -1, rp, -1, TRUE);
+ rp = ep + 1;
+ }
+ if((ep = strchr(rp, ':')) != NULL){
+ if(ep[1] != '\0') cbmapput(map, "port", -1, ep + 1, -1, TRUE);
+ *ep = '\0';
+ }
+ if(rp[0] != '\0') cbmapput(map, "host", -1, rp, -1, TRUE);
+ } else {
+ cbmapput(map, "path", -1, rp, -1, TRUE);
+ }
+ free(tmp);
+ if((rp = cbmapget(map, "path", -1, NULL)) != NULL){
+ if((ep = strrchr(rp, '/')) != NULL){
+ if(ep[1] != '\0') cbmapput(map, "file", -1, ep + 1, -1, TRUE);
+ } else {
+ cbmapput(map, "file", -1, rp, -1, TRUE);
+ }
+ }
+ if((rp = cbmapget(map, "file", -1, NULL)) != NULL && (!strcmp(rp, ".") || !strcmp(rp, "..")))
+ cbmapout(map, "file", -1);
+ return map;
+}
+
+
+/* Resolve a relative URL with another absolute URL. */
+char *cburlresolve(const char *base, const char *target){
+ CBMAP *telems, *belems;
+ CBLIST *bpaths, *opaths, *qelems;
+ CBDATUM *rbuf;
+ const char *vbuf, *path;
+ char *tmp, *wp, *enc, numbuf[CB_NUMBUFSIZ];
+ int i, vsiz, port, num;
+ assert(base && target);
+ while(*base > '\0' && *base <= ' '){
+ base++;
+ }
+ while(*target > '\0' && *target <= ' '){
+ target++;
+ }
+ if(*target == '\0') target = base;
+ CB_DATUMOPEN(rbuf);
+ telems = cburlbreak(target);
+ port = 80;
+ belems = cburlbreak(cbmapget(telems, "scheme", -1, &vsiz) ? target : base);
+ if((vbuf = cbmapget(belems, "scheme", -1, &vsiz)) != NULL){
+ CB_DATUMCAT(rbuf, vbuf, vsiz);
+ CB_DATUMCAT(rbuf, "://", 3);
+ if(!cbstricmp(vbuf, "https")){
+ port = 443;
+ } else if(!cbstricmp(vbuf, "ftp")){
+ port = 21;
+ } else if(!cbstricmp(vbuf, "sftp")){
+ port = 115;
+ } else if(!cbstricmp(vbuf, "ftps")){
+ port = 22;
+ } else if(!cbstricmp(vbuf, "tftp")){
+ port = 69;
+ } else if(!cbstricmp(vbuf, "ldap")){
+ port = 389;
+ } else if(!cbstricmp(vbuf, "ldaps")){
+ port = 636;
+ }
+ } else {
+ CB_DATUMCAT(rbuf, "http://", 7);
+ }
+ if((vbuf = cbmapget(belems, "authority", -1, &vsiz)) != NULL){
+ if((wp = strchr(vbuf, ':')) != NULL){
+ *wp = '\0';
+ tmp = cburldecode(vbuf, NULL);
+ enc = cburlencode(tmp, -1);
+ CB_DATUMCAT(rbuf, enc, strlen(enc));
+ free(enc);
+ free(tmp);
+ CB_DATUMCAT(rbuf, ":", 1);
+ wp++;
+ tmp = cburldecode(wp, NULL);
+ enc = cburlencode(tmp, -1);
+ CB_DATUMCAT(rbuf, enc, strlen(enc));
+ free(enc);
+ free(tmp);
+ } else {
+ tmp = cburldecode(vbuf, NULL);
+ enc = cburlencode(tmp, -1);
+ CB_DATUMCAT(rbuf, enc, strlen(enc));
+ free(enc);
+ free(tmp);
+ }
+ CB_DATUMCAT(rbuf, "@", 1);
+ }
+ if((vbuf = cbmapget(belems, "host", -1, &vsiz)) != NULL){
+ tmp = cburldecode(vbuf, NULL);
+ cbstrtolower(tmp);
+ enc = cburlencode(tmp, -1);
+ CB_DATUMCAT(rbuf, enc, strlen(enc));
+ free(enc);
+ free(tmp);
+ } else {
+ CB_DATUMCAT(rbuf, "localhost", 9);
+ }
+ if((vbuf = cbmapget(belems, "port", -1, &vsiz)) != NULL &&
+ (num = atoi(vbuf)) != port && num > 1){
+ sprintf(numbuf, ":%d", num);
+ CB_DATUMCAT(rbuf, numbuf, strlen(numbuf));
+ }
+ if(!(path = cbmapget(telems, "path", -1, NULL))) path = "/";
+ if(path[0] == '\0' && (vbuf = cbmapget(belems, "path", -1, NULL)) != NULL) path = vbuf;
+ if(path[0] == '\0') path = "/";
+ CB_LISTOPEN(bpaths);
+ if(path[0] != '/' && (vbuf = cbmapget(belems, "path", -1, &vsiz)) != NULL){
+ opaths = cbsplit(vbuf, vsiz, "/");
+ } else {
+ opaths = cbsplit("/", 1, "/");
+ }
+ CB_LISTDROP(opaths);
+ for(i = 0; i < CB_LISTNUM(opaths); i++){
+ vbuf = CB_LISTVAL2(opaths, i, vsiz);
+ if(vsiz < 1 || !strcmp(vbuf, ".")) continue;
+ if(!strcmp(vbuf, "..")){
+ CB_LISTDROP(bpaths);
+ } else {
+ CB_LISTPUSH(bpaths, vbuf, vsiz);
+ }
+ }
+ CB_LISTCLOSE(opaths);
+ opaths = cbsplit(path, -1, "/");
+ for(i = 0; i < CB_LISTNUM(opaths); i++){
+ vbuf = CB_LISTVAL2(opaths, i, vsiz);
+ if(vsiz < 1 || !strcmp(vbuf, ".")) continue;
+ if(!strcmp(vbuf, "..")){
+ CB_LISTDROP(bpaths);
+ } else {
+ CB_LISTPUSH(bpaths, vbuf, vsiz);
+ }
+ }
+ CB_LISTCLOSE(opaths);
+ for(i = 0; i < CB_LISTNUM(bpaths); i++){
+ vbuf = CB_LISTVAL(bpaths, i);
+ if(strchr(vbuf, '%')){
+ tmp = cburldecode(vbuf, NULL);
+ } else {
+ CB_MEMDUP(tmp, vbuf, strlen(vbuf));
+ }
+ enc = cburlencode(tmp, -1);
+ CB_DATUMCAT(rbuf, "/", 1);
+ CB_DATUMCAT(rbuf, enc, strlen(enc));
+ free(enc);
+ free(tmp);
+ }
+ if(cbstrbwmatch(path, "/")) CB_DATUMCAT(rbuf, "/", 1);
+ CB_LISTCLOSE(bpaths);
+ if((vbuf = cbmapget(telems, "query", -1, &vsiz)) != NULL){
+ CB_DATUMCAT(rbuf, "?", 1);
+ qelems = cbsplit(vbuf, vsiz, "&;");
+ for(i = 0; i < CB_LISTNUM(qelems); i++){
+ vbuf = CB_LISTVAL(qelems, i);
+ if(i > 0) CB_DATUMCAT(rbuf, "&", 1);
+ if((wp = strchr(vbuf, '=')) != NULL){
+ *wp = '\0';
+ tmp = cburldecode(vbuf, NULL);
+ enc = cburlencode(tmp, -1);
+ CB_DATUMCAT(rbuf, enc, strlen(enc));
+ free(enc);
+ free(tmp);
+ CB_DATUMCAT(rbuf, "=", 1);
+ wp++;
+ tmp = cburldecode(wp, NULL);
+ enc = cburlencode(tmp, -1);
+ CB_DATUMCAT(rbuf, enc, strlen(enc));
+ free(enc);
+ free(tmp);
+ } else {
+ tmp = cburldecode(vbuf, NULL);
+ enc = cburlencode(tmp, -1);
+ CB_DATUMCAT(rbuf, enc, strlen(enc));
+ free(enc);
+ free(tmp);
+ }
+ }
+ CB_LISTCLOSE(qelems);
+ }
+ if((vbuf = cbmapget(telems, "fragment", -1, &vsiz)) != NULL){
+ tmp = cburldecode(vbuf, NULL);
+ enc = cburlencode(tmp, -1);
+ CB_DATUMCAT(rbuf, "#", 1);
+ CB_DATUMCAT(rbuf, enc, strlen(enc));
+ free(enc);
+ free(tmp);
+ }
+ cbmapclose(belems);
+ cbmapclose(telems);
+ return cbdatumtomalloc(rbuf, NULL);
+}
+
+
+/* Encode a serial object with URL encoding. */
+char *cburlencode(const char *ptr, int size){
+ char *buf, *wp;
+ int i, c;
+ assert(ptr);
+ if(size < 0) size = strlen(ptr);
+ CB_MALLOC(buf, size * 3 + 1);
+ wp = buf;
+ for(i = 0; i < size; i++){
+ c = ((unsigned char *)ptr)[i];
+ if((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
+ (c >= '0' && c <= '9') || (c != '\0' && strchr("_-.!~*'()", c))){
+ *(wp++) = c;
+ } else {
+ wp += sprintf(wp, "%%%02X", c);
+ }
+ }
+ *wp = '\0';
+ return buf;
+}
+
+
+/* Decode a string encoded with URL encoding. */
+char *cburldecode(const char *str, int *sp){
+ char *buf, *wp;
+ unsigned char c;
+ CB_MEMDUP(buf, str, strlen(str));
+ wp = buf;
+ while(*str != '\0'){
+ if(*str == '%'){
+ str++;
+ if(((str[0] >= '0' && str[0] <= '9') || (str[0] >= 'A' && str[0] <= 'F') ||
+ (str[0] >= 'a' && str[0] <= 'f')) &&
+ ((str[1] >= '0' && str[1] <= '9') || (str[1] >= 'A' && str[1] <= 'F') ||
+ (str[1] >= 'a' && str[1] <= 'f'))){
+ c = *str;
+ if(c >= 'A' && c <= 'Z') c += 'a' - 'A';
+ if(c >= 'a' && c <= 'z'){
+ *wp = c - 'a' + 10;
+ } else {
+ *wp = c - '0';
+ }
+ *wp *= 0x10;
+ str++;
+ c = *str;
+ if(c >= 'A' && c <= 'Z') c += 'a' - 'A';
+ if(c >= 'a' && c <= 'z'){
+ *wp += c - 'a' + 10;
+ } else {
+ *wp += c - '0';
+ }
+ str++;
+ wp++;
+ } else {
+ break;
+ }
+ } else if(*str == '+'){
+ *wp = ' ';
+ str++;
+ wp++;
+ } else {
+ *wp = *str;
+ str++;
+ wp++;
+ }
+ }
+ *wp = '\0';
+ if(sp) *sp = wp - buf;
+ return buf;
+}
+
+
+/* Encode a serial object with Base64 encoding. */
+char *cbbaseencode(const char *ptr, int size){
+ char *tbl = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+ char *buf, *wp;
+ const unsigned char *obj;
+ int i;
+ assert(ptr);
+ if(size < 0) size = strlen(ptr);
+ CB_MALLOC(buf, 4 * (size + 2) / 3 + 1);
+ obj = (const unsigned char *)ptr;
+ wp = buf;
+ for(i = 0; i < size; i += 3){
+ switch(size - i){
+ case 1:
+ *wp++ = tbl[obj[0] >> 2];
+ *wp++ = tbl[(obj[0] & 3) << 4];
+ *wp++ = '=';
+ *wp++ = '=';
+ break;
+ case 2:
+ *wp++ = tbl[obj[0] >> 2];
+ *wp++ = tbl[((obj[0] & 3) << 4) + (obj[1] >> 4)];
+ *wp++ = tbl[(obj[1] & 0xf) << 2];
+ *wp++ = '=';
+ break;
+ default:
+ *wp++ = tbl[obj[0] >> 2];
+ *wp++ = tbl[((obj[0] & 3) << 4) + (obj[1] >> 4)];
+ *wp++ = tbl[((obj[1] & 0xf) << 2) + (obj[2] >> 6)];
+ *wp++ = tbl[obj[2] & 0x3f];
+ break;
+ }
+ obj += 3;
+ }
+ *wp = '\0';
+ return buf;
+}
+
+
+/* Decode a string encoded with Base64 encoding. */
+char *cbbasedecode(const char *str, int *sp){
+ unsigned char *obj, *wp;
+ int len, cnt, bpos, i, bits, eqcnt;
+ assert(str);
+ cnt = 0;
+ bpos = 0;
+ eqcnt = 0;
+ len = strlen(str);
+ CB_MALLOC(obj, len + 4);
+ wp = obj;
+ while(bpos < len && eqcnt == 0){
+ bits = 0;
+ for(i = 0; bpos < len && i < 4; bpos++){
+ if(str[bpos] >= 'A' && str[bpos] <= 'Z'){
+ bits = (bits << 6) | (str[bpos] - 'A');
+ i++;
+ } else if(str[bpos] >= 'a' && str[bpos] <= 'z'){
+ bits = (bits << 6) | (str[bpos] - 'a' + 26);
+ i++;
+ } else if(str[bpos] >= '0' && str[bpos] <= '9'){
+ bits = (bits << 6) | (str[bpos] - '0' + 52);
+ i++;
+ } else if(str[bpos] == '+'){
+ bits = (bits << 6) | 62;
+ i++;
+ } else if(str[bpos] == '/'){
+ bits = (bits << 6) | 63;
+ i++;
+ } else if(str[bpos] == '='){
+ bits <<= 6;
+ i++;
+ eqcnt++;
+ }
+ }
+ if(i == 0 && bpos >= len) continue;
+ switch(eqcnt){
+ case 0:
+ *wp++ = (bits >> 16) & 0xff;
+ *wp++ = (bits >> 8) & 0xff;
+ *wp++ = bits & 0xff;
+ cnt += 3;
+ break;
+ case 1:
+ *wp++ = (bits >> 16) & 0xff;
+ *wp++ = (bits >> 8) & 0xff;
+ cnt += 2;
+ break;
+ case 2:
+ *wp++ = (bits >> 16) & 0xff;
+ cnt += 1;
+ break;
+ }
+ }
+ obj[cnt] = '\0';
+ if(sp) *sp = cnt;
+ return (char *)obj;
+}
+
+
+/* Encode a serial object with quoted-printable encoding. */
+char *cbquoteencode(const char *ptr, int size){
+ const unsigned char *rp;
+ char *buf, *wp;
+ int i, cols;
+ assert(ptr);
+ if(size < 0) size = strlen(ptr);
+ rp = (const unsigned char *)ptr;
+ CB_MALLOC(buf, size * 3 + 1);
+ wp = buf;
+ cols = 0;
+ for(i = 0; i < size; i++){
+ if(rp[i] == '=' || (rp[i] < 0x20 && rp[i] != '\r' && rp[i] != '\n' && rp[i] != '\t') ||
+ rp[i] > 0x7e){
+ wp += sprintf(wp, "=%02X", rp[i]);
+ cols += 3;
+ } else {
+ *(wp++) = rp[i];
+ cols++;
+ }
+ }
+ *wp = '\0';
+ return buf;
+}
+
+
+/* Decode a string encoded with quoted-printable encoding. */
+char *cbquotedecode(const char *str, int *sp){
+ char *buf, *wp;
+ assert(str);
+ CB_MALLOC(buf, strlen(str) + 1);
+ wp = buf;
+ for(; *str != '\0'; str++){
+ if(*str == '='){
+ str++;
+ if(*str == '\0'){
+ break;
+ } else if(str[0] == '\r' && str[1] == '\n'){
+ str++;
+ } else if(str[0] != '\n' && str[0] != '\r'){
+ if(*str >= 'A' && *str <= 'Z'){
+ *wp = (*str - 'A' + 10) * 16;
+ } else if(*str >= 'a' && *str <= 'z'){
+ *wp = (*str - 'a' + 10) * 16;
+ } else {
+ *wp = (*str - '0') * 16;
+ }
+ str++;
+ if(*str == '\0') break;
+ if(*str >= 'A' && *str <= 'Z'){
+ *wp += *str - 'A' + 10;
+ } else if(*str >= 'a' && *str <= 'z'){
+ *wp += *str - 'a' + 10;
+ } else {
+ *wp += *str - '0';
+ }
+ wp++;
+ }
+ } else {
+ *wp = *str;
+ wp++;
+ }
+ }
+ *wp = '\0';
+ if(sp) *sp = wp - buf;
+ return buf;
+}
+
+
+/* Split a string of MIME into headers and the body. */
+char *cbmimebreak(const char *ptr, int size, CBMAP *attrs, int *sp){
+ CBLIST *list;
+ const char *head, *line, *pv, *ep;
+ char *hbuf, *name, *rv;
+ int i, j, wi, hlen;
+ assert(ptr);
+ if(size < 0) size = strlen(ptr);
+ head = NULL;
+ hlen = 0;
+ for(i = 0; i < size; i++){
+ if(i < size - 4 && ptr[i] == '\r' && ptr[i+1] == '\n' &&
+ ptr[i+2] == '\r' && ptr[i+3] == '\n'){
+ head = ptr;
+ hlen = i;
+ ptr += i + 4;
+ size -= i + 4;
+ break;
+ } else if(i < size - 2 && ptr[i] == '\n' && ptr[i+1] == '\n'){
+ head = ptr;
+ hlen = i;
+ ptr += i + 2;
+ size -= i + 2;
+ break;
+ }
+ }
+ if(head && attrs){
+ CB_MALLOC(hbuf, hlen + 1);
+ wi = 0;
+ for(i = 0; i < hlen; i++){
+ if(head[i] == '\r') continue;
+ if(i < hlen - 1 && head[i] == '\n' && (head[i+1] == ' ' || head[i+1] == '\t')){
+ hbuf[wi++] = ' ';
+ i++;
+ } else {
+ hbuf[wi++] = head[i];
+ }
+ }
+ list = cbsplit(hbuf, wi, "\n");
+ for(i = 0; i < CB_LISTNUM(list); i++){
+ line = CB_LISTVAL(list, i);
+ if((pv = strchr(line, ':')) != NULL){
+ CB_MEMDUP(name, line, pv - line);
+ for(j = 0; name[j] != '\0'; j++){
+ if(name[j] >= 'A' && name[j] <= 'Z') name[j] -= 'A' - 'a';
+ }
+ pv++;
+ while(*pv == ' ' || *pv == '\t'){
+ pv++;
+ }
+ cbmapput(attrs, name, -1, pv, -1, TRUE);
+ free(name);
+ }
+
+ }
+ CB_LISTCLOSE(list);
+ free(hbuf);
+ if((pv = cbmapget(attrs, "content-type", -1, NULL)) != NULL){
+ if((ep = strchr(pv, ';')) != NULL){
+ cbmapput(attrs, "TYPE", -1, pv, ep - pv, TRUE);
+ do {
+ ep++;
+ while(ep[0] == ' '){
+ ep++;
+ }
+ if(cbstrfwimatch(ep, "charset=")){
+ ep += 8;
+ while(*ep > '\0' && *ep <= ' '){
+ ep++;
+ }
+ if(ep[0] == '"') ep++;
+ pv = ep;
+ while(ep[0] != '\0' && ep[0] != ' ' && ep[0] != '"' && ep[0] != ';'){
+ ep++;
+ }
+ cbmapput(attrs, "CHARSET", -1, pv, ep - pv, TRUE);
+ } else if(cbstrfwimatch(ep, "boundary=")){
+ ep += 9;
+ while(*ep > '\0' && *ep <= ' '){
+ ep++;
+ }
+ if(ep[0] == '"'){
+ ep++;
+ pv = ep;
+ while(ep[0] != '\0' && ep[0] != '"'){
+ ep++;
+ }
+ } else {
+ pv = ep;
+ while(ep[0] != '\0' && ep[0] != ' ' && ep[0] != '"' && ep[0] != ';'){
+ ep++;
+ }
+ }
+ cbmapput(attrs, "BOUNDARY", -1, pv, ep - pv, TRUE);
+ }
+ } while((ep = strchr(ep, ';')) != NULL);
+ } else {
+ cbmapput(attrs, "TYPE", -1, pv, -1, TRUE);
+ }
+ }
+ if((pv = cbmapget(attrs, "content-disposition", -1, NULL)) != NULL){
+ if((ep = strchr(pv, ';')) != NULL){
+ cbmapput(attrs, "DISPOSITION", -1, pv, ep - pv, TRUE);
+ do {
+ ep++;
+ while(ep[0] == ' '){
+ ep++;
+ }
+ if(cbstrfwimatch(ep, "filename=")){
+ ep += 9;
+ if(ep[0] == '"') ep++;
+ pv = ep;
+ while(ep[0] != '\0' && ep[0] != '"'){
+ ep++;
+ }
+ cbmapput(attrs, "FILENAME", -1, pv, ep - pv, TRUE);
+ } else if(cbstrfwimatch(ep, "name=")){
+ ep += 5;
+ if(ep[0] == '"') ep++;
+ pv = ep;
+ while(ep[0] != '\0' && ep[0] != '"'){
+ ep++;
+ }
+ cbmapput(attrs, "NAME", -1, pv, ep - pv, TRUE);
+ }
+ } while((ep = strchr(ep, ';')) != NULL);
+ } else {
+ cbmapput(attrs, "DISPOSITION", -1, pv, -1, TRUE);
+ }
+ }
+ }
+ if(sp) *sp = size;
+ CB_MEMDUP(rv, ptr, size);
+ return rv;
+}
+
+
+/* Split multipart data in MIME into its parts. */
+CBLIST *cbmimeparts(const char *ptr, int size, const char *boundary){
+ CBLIST *list;
+ const char *pv, *ep;
+ int i, blen;
+ assert(ptr && boundary);
+ if(size < 0) size = strlen(ptr);
+ CB_LISTOPEN(list);
+ if((blen = strlen(boundary)) < 1) return list;
+ pv = NULL;
+ for(i = 0; i < size; i++){
+ if(ptr[i] == '-' && ptr[i+1] == '-' && i + 2 + blen < size &&
+ cbstrfwmatch(ptr + i + 2, boundary) && strchr("\t\n\v\f\r ", ptr[i+2+blen])){
+ pv = ptr + i + 2 + blen;
+ if(*pv == '\r') pv++;
+ if(*pv == '\n') pv++;
+ size -= pv - ptr;
+ ptr = pv;
+ break;
+ }
+ }
+ if(!pv) return list;
+ for(i = 0; i < size; i++){
+ if(ptr[i] == '-' && ptr[i+1] == '-' && i + 2 + blen < size &&
+ cbstrfwmatch(ptr + i + 2, boundary) && strchr("\t\n\v\f\r -", ptr[i+2+blen])){
+ ep = ptr + i;
+ if(ep > ptr && ep[-1] == '\n') ep--;
+ if(ep > ptr && ep[-1] == '\r') ep--;
+ if(ep > pv) CB_LISTPUSH(list, pv, ep - pv);
+ pv = ptr + i + 2 + blen;
+ if(*pv == '\r') pv++;
+ if(*pv == '\n') pv++;
+ }
+ }
+ return list;
+}
+
+
+/* Encode a string with MIME encoding. */
+char *cbmimeencode(const char *str, const char *encname, int base){
+ char *buf, *wp, *enc;
+ int len;
+ assert(str && encname);
+ len = strlen(str);
+ CB_MALLOC(buf, len * 3 + strlen(encname) + 16);
+ wp = buf;
+ wp += sprintf(wp, "=?%s?%c?", encname, base ? 'B' : 'Q');
+ enc = base ? cbbaseencode(str, len) : cbquoteencode(str, len);
+ wp += sprintf(wp, "%s?=", enc);
+ free(enc);
+ return buf;
+}
+
+
+/* Decode a string encoded with MIME encoding. */
+char *cbmimedecode(const char *str, char *enp){
+ char *buf, *wp, *tmp, *dec;
+ const char *pv, *ep;
+ int quoted;
+ assert(str);
+ if(enp) sprintf(enp, "US-ASCII");
+ CB_MALLOC(buf, strlen(str) + 1);
+ wp = buf;
+ while(*str != '\0'){
+ if(cbstrfwmatch(str, "=?")){
+ str += 2;
+ pv = str;
+ if(!(ep = strchr(str, '?'))) continue;
+ if(enp && ep - pv < CB_ENCBUFSIZ){
+ memcpy(enp, pv, ep - pv);
+ enp[ep-pv] = '\0';
+ }
+ pv = ep + 1;
+ quoted = (*pv == 'Q' || *pv == 'q');
+ if(*pv != '\0') pv++;
+ if(*pv != '\0') pv++;
+ if(!(ep = strchr(pv, '?'))) continue;
+ CB_MEMDUP(tmp, pv, ep - pv);
+ dec = quoted ? cbquotedecode(tmp, NULL) : cbbasedecode(tmp, NULL);
+ wp += sprintf(wp, "%s", dec);
+ free(dec);
+ free(tmp);
+ str = ep + 1;
+ if(*str != '\0') str++;
+ } else {
+ *(wp++) = *str;
+ str++;
+ }
+ }
+ *wp = '\0';
+ return buf;
+}
+
+
+/* Split a string of CSV into rows. */
+CBLIST *cbcsvrows(const char *str){
+ CBLIST *list;
+ const char *pv;
+ int quoted;
+ assert(str);
+ CB_LISTOPEN(list);
+ pv = str;
+ quoted = FALSE;
+ while(TRUE){
+ if(*str == '"') quoted = !quoted;
+ if(!quoted && (*str == '\r' || *str == '\n')){
+ CB_LISTPUSH(list, pv, str - pv);
+ if(str[0] == '\r' && str[1] == '\n') str++;
+ str++;
+ pv = str;
+ } else if(*str == '\0'){
+ if(str > pv) CB_LISTPUSH(list, pv, str - pv);
+ break;
+ } else {
+ str++;
+ }
+ }
+ return list;
+}
+
+
+/* Split a string of a row of CSV into cells. */
+CBLIST *cbcsvcells(const char *str){
+ CBLIST *list, *uelist;
+ const char *pv;
+ char *tmp;
+ int i, quoted;
+ assert(str);
+ CB_LISTOPEN(list);
+ pv = str;
+ quoted = FALSE;
+ while(TRUE){
+ if(*str == '"') quoted = !quoted;
+ if(!quoted && *str == ','){
+ CB_LISTPUSH(list, pv, str - pv);
+ str++;
+ pv = str;
+ } else if(*str == '\0'){
+ CB_LISTPUSH(list, pv, str - pv);
+ break;
+ } else {
+ str++;
+ }
+ }
+ CB_LISTOPEN(uelist);
+ for(i = 0; i < CB_LISTNUM(list); i++){
+ tmp = cbcsvunescape(CB_LISTVAL(list, i));
+ CB_LISTPUSH(uelist, tmp, strlen(tmp));
+ free(tmp);
+ }
+ CB_LISTCLOSE(list);
+ return uelist;
+}
+
+
+/* Escape a string with the meta characters of CSV. */
+char *cbcsvescape(const char *str){
+ char *buf, *wp;
+ int i;
+ assert(str);
+ CB_MALLOC(buf, strlen(str) * 2 + 3);
+ wp = buf;
+ *(wp++) = '"';
+ for(i = 0; str[i] != '\0'; i++){
+ if(str[i] == '"') *(wp++) = '"';
+ *(wp++) = str[i];
+ }
+ *(wp++) = '"';
+ *wp = '\0';
+ return buf;
+}
+
+
+/* Unescape a string with the escaped meta characters of CSV. */
+char *cbcsvunescape(const char *str){
+ char *buf, *wp;
+ int i, len;
+ assert(str);
+ len = strlen(str);
+ if(str[0] == '"'){
+ str++;
+ len--;
+ if(str[len-1] == '"') len--;
+ }
+ CB_MALLOC(buf, len + 1);
+ wp = buf;
+ for(i = 0; i < len; i++){
+ if(str[i] == '"'){
+ if(str[i+1] == '"') *(wp++) = str[i++];
+ } else {
+ *(wp++) = str[i];
+ }
+ }
+ *wp = '\0';
+ return buf;
+}
+
+
+/* Split a string of XML into tags and text sections. */
+CBLIST *cbxmlbreak(const char *str, int cr){
+ CBLIST *list;
+ CBDATUM *datum;
+ int i, pv, tag;
+ char *ep;
+ assert(str);
+ CB_LISTOPEN(list);
+ i = 0;
+ pv = 0;
+ tag = FALSE;
+ while(TRUE){
+ if(str[i] == '\0'){
+ if(i > pv) CB_LISTPUSH(list, str + pv, i - pv);
+ break;
+ } else if(!tag && str[i] == '<'){
+ if(str[i+1] == '!' && str[i+2] == '-' && str[i+3] == '-'){
+ if(i > pv) CB_LISTPUSH(list, str + pv, i - pv);
+ if((ep = strstr(str + i, "-->")) != NULL){
+ if(!cr) CB_LISTPUSH(list, str + i, ep - str - i + 3);
+ i = ep - str + 2;
+ pv = i + 1;
+ }
+ } else if(str[i+1] == '!' && str[i+2] == '[' && cbstrfwimatch(str + i, "<![CDATA[")){
+ if(i > pv) CB_LISTPUSH(list, str + pv, i - pv);
+ if((ep = strstr(str + i, "]]>")) != NULL){
+ i += 9;
+ CB_DATUMOPEN(datum);
+ while(str + i < ep){
+ if(str[i] == '&'){
+ CB_DATUMCAT(datum, "&amp;", 5);
+ } else if(str[i] == '<'){
+ CB_DATUMCAT(datum, "&lt;", 4);
+ } else if(str[i] == '>'){
+ CB_DATUMCAT(datum, "&gt;", 4);
+ } else {
+ CB_DATUMCAT(datum, str + i, 1);
+ }
+ i++;
+ }
+ if(CB_DATUMSIZE(datum) > 0) CB_LISTPUSH(list, CB_DATUMPTR(datum), CB_DATUMSIZE(datum));
+ CB_DATUMCLOSE(datum);
+ i = ep - str + 2;
+ pv = i + 1;
+ }
+ } else {
+ if(i > pv) CB_LISTPUSH(list, str + pv, i - pv);
+ tag = TRUE;
+ pv = i;
+ }
+ } else if(tag && str[i] == '>'){
+ if(i > pv) CB_LISTPUSH(list, str + pv, i - pv + 1);
+ tag = FALSE;
+ pv = i + 1;
+ }
+ i++;
+ }
+ return list;
+}
+
+
+/* Get the map of attributes of a XML tag. */
+CBMAP *cbxmlattrs(const char *str){
+ CBMAP *map;
+ const unsigned char *rp, *key, *val;
+ char *copy, *raw;
+ int ksiz, vsiz;
+ assert(str);
+ map = cbmapopenex(CB_MAPPBNUM);
+ rp = (unsigned char *)str;
+ while(*rp == '<' || *rp == '/' || *rp == '?' || *rp == '!' || *rp == ' '){
+ rp++;
+ }
+ key = rp;
+ while(*rp > 0x20 && *rp != '/' && *rp != '>'){
+ rp++;
+ }
+ cbmapput(map, "", -1, (char *)key, rp - key, FALSE);
+ while(*rp != '\0'){
+ while(*rp != '\0' && (*rp <= 0x20 || *rp == '/' || *rp == '?' || *rp == '>')){
+ rp++;
+ }
+ key = rp;
+ while(*rp > 0x20 && *rp != '/' && *rp != '>' && *rp != '='){
+ rp++;
+ }
+ ksiz = rp - key;
+ while(*rp != '\0' && (*rp == '=' || *rp <= 0x20)){
+ rp++;
+ }
+ if(*rp == '"'){
+ rp++;
+ val = rp;
+ while(*rp != '\0' && *rp != '"'){
+ rp++;
+ }
+ vsiz = rp - val;
+ } else if(*rp == '\''){
+ rp++;
+ val = rp;
+ while(*rp != '\0' && *rp != '\''){
+ rp++;
+ }
+ vsiz = rp - val;
+ } else {
+ val = rp;
+ while(*rp > 0x20 && *rp != '"' && *rp != '\'' && *rp != '>'){
+ rp++;
+ }
+ vsiz = rp - val;
+ }
+ if(*rp != '\0') rp++;
+ if(ksiz > 0){
+ CB_MEMDUP(copy, (char *)val, vsiz);
+ raw = cbxmlunescape(copy);
+ cbmapput(map, (char *)key, ksiz, raw, -1, FALSE);
+ free(raw);
+ free(copy);
+ }
+ }
+ return map;
+}
+
+
+/* Escape a string with the meta characters of XML. */
+char *cbxmlescape(const char *str){
+ CBDATUM *datum;
+ assert(str);
+ CB_DATUMOPEN(datum);
+ while(*str != '\0'){
+ switch(*str){
+ case '&':
+ CB_DATUMCAT(datum, "&amp;", 5);
+ break;
+ case '<':
+ CB_DATUMCAT(datum, "&lt;", 4);
+ break;
+ case '>':
+ CB_DATUMCAT(datum, "&gt;", 4);
+ break;
+ case '"':
+ CB_DATUMCAT(datum, "&quot;", 6);
+ break;
+ case '\'':
+ CB_DATUMCAT(datum, "&apos;", 6);
+ break;
+ default:
+ CB_DATUMCAT(datum, str, 1);
+ break;
+ }
+ str++;
+ }
+ return cbdatumtomalloc(datum, NULL);
+}
+
+
+/* Unescape a string with the entity references of XML. */
+char *cbxmlunescape(const char *str){
+ CBDATUM *datum;
+ assert(str);
+ CB_DATUMOPEN(datum);
+ while(*str != '\0'){
+ if(*str == '&'){
+ if(cbstrfwmatch(str, "&amp;")){
+ CB_DATUMCAT(datum, "&", 1);
+ str += 5;
+ } else if(cbstrfwmatch(str, "&lt;")){
+ CB_DATUMCAT(datum, "<", 1);
+ str += 4;
+ } else if(cbstrfwmatch(str, "&gt;")){
+ CB_DATUMCAT(datum, ">", 1);
+ str += 4;
+ } else if(cbstrfwmatch(str, "&quot;")){
+ CB_DATUMCAT(datum, "\"", 1);
+ str += 6;
+ } else if(cbstrfwmatch(str, "&apos;")){
+ CB_DATUMCAT(datum, "'", 1);
+ str += 6;
+ } else {
+ CB_DATUMCAT(datum, str, 1);
+ str++;
+ }
+ } else {
+ CB_DATUMCAT(datum, str, 1);
+ str++;
+ }
+ }
+ return cbdatumtomalloc(datum, NULL);
+}
+
+
+/* Compress a serial object with ZLIB. */
+char *cbdeflate(const char *ptr, int size, int *sp){
+ assert(ptr && sp);
+ if(!_qdbm_deflate) return NULL;
+ return _qdbm_deflate(ptr, size, sp, _QDBM_ZMZLIB);
+}
+
+
+/* Decompress a serial object compressed with ZLIB. */
+char *cbinflate(const char *ptr, int size, int *sp){
+ assert(ptr && size >= 0);
+ if(!_qdbm_inflate) return NULL;
+ return _qdbm_inflate(ptr, size, sp, _QDBM_ZMZLIB);
+}
+
+
+/* Compress a serial object with GZIP. */
+char *cbgzencode(const char *ptr, int size, int *sp){
+ assert(ptr && sp);
+ if(!_qdbm_deflate) return NULL;
+ return _qdbm_deflate(ptr, size, sp, _QDBM_ZMGZIP);
+}
+
+
+/* Decompress a serial object compressed with GZIP. */
+char *cbgzdecode(const char *ptr, int size, int *sp){
+ assert(ptr && size >= 0);
+ if(!_qdbm_inflate) return NULL;
+ return _qdbm_inflate(ptr, size, sp, _QDBM_ZMGZIP);
+}
+
+
+/* Get the CRC32 checksum of a serial object. */
+unsigned int cbgetcrc(const char *ptr, int size){
+ assert(ptr);
+ if(!_qdbm_inflate) return 0;
+ return _qdbm_getcrc(ptr, size);
+}
+
+
+/* Compress a serial object with LZO. */
+char *cblzoencode(const char *ptr, int size, int *sp){
+ assert(ptr && sp);
+ if(!_qdbm_lzoencode) return NULL;
+ return _qdbm_lzoencode(ptr, size, sp);
+}
+
+
+/* Decompress a serial object compressed with LZO. */
+char *cblzodecode(const char *ptr, int size, int *sp){
+ assert(ptr && size >= 0);
+ if(!_qdbm_lzodecode) return NULL;
+ return _qdbm_lzodecode(ptr, size, sp);
+}
+
+
+/* Compress a serial object with BZIP2. */
+char *cbbzencode(const char *ptr, int size, int *sp){
+ assert(ptr && sp);
+ if(!_qdbm_bzencode) return NULL;
+ return _qdbm_bzencode(ptr, size, sp);
+}
+
+
+/* Decompress a serial object compressed with BZIP2. */
+char *cbbzdecode(const char *ptr, int size, int *sp){
+ assert(ptr && size >= 0);
+ if(!_qdbm_bzdecode) return NULL;
+ return _qdbm_bzdecode(ptr, size, sp);
+}
+
+
+/* Convert the character encoding of a string. */
+char *cbiconv(const char *ptr, int size, const char *icode, const char *ocode, int *sp, int *mp){
+ char *res;
+ assert(ptr && icode && ocode);
+ if(!_qdbm_iconv) return NULL;
+ if((res = _qdbm_iconv(ptr, size, icode, ocode, sp, mp)) != NULL) return res;
+ if(!cbstricmp(icode, ocode)){
+ if(sp) *sp = size;
+ if(mp) *mp = 0;
+ CB_MEMDUP(res, ptr, size < 0 ? strlen(ptr) : size);
+ return res;
+ }
+ return NULL;
+}
+
+
+/* Detect the encoding of a string automatically. */
+const char *cbencname(const char *ptr, int size){
+ assert(ptr);
+ if(!_qdbm_encname) return "ISO-8859-1";
+ return _qdbm_encname(ptr, size);
+}
+
+
+/* Get the jet lag of the local time in seconds. */
+int cbjetlag(void){
+ struct tm ts, *tp;
+ time_t t, gt, lt;
+ if((t = time(NULL)) < 0) return 0;
+ if(!(tp = _qdbm_gmtime(&t, &ts))) return 0;
+ if((gt = mktime(tp)) < 0) return 0;
+ if(!(tp = _qdbm_localtime(&t, &ts))) return 0;
+ if((lt = mktime(tp)) < 0) return 0;
+ return lt - gt;
+}
+
+
+/* Get the Gregorian calendar of a time. */
+void cbcalendar(time_t t, int jl, int *yearp, int *monp, int *dayp,
+ int *hourp, int *minp, int *secp){
+ struct tm ts, *tp;
+ if(t < 0) t = time(NULL);
+ t += jl;
+ if(!(tp = _qdbm_gmtime(&t, &ts))) return;
+ if(yearp) *yearp = tp->tm_year + 1900;
+ if(monp) *monp = tp->tm_mon + 1;
+ if(dayp) *dayp = tp->tm_mday;
+ if(hourp) *hourp = tp->tm_hour;
+ if(minp) *minp = tp->tm_min;
+ if(secp) *secp = tp->tm_sec;
+}
+
+
+/* Get the day of week of a date. */
+int cbdayofweek(int year, int mon, int day){
+ if(mon < 3){
+ year--;
+ mon += 12;
+ }
+ return (day + ((8 + (13 * mon)) / 5) + (year + (year / 4) - (year / 100) + (year / 400))) % 7;
+}
+
+
+/* Get the string for a date in W3CDTF. */
+char *cbdatestrwww(time_t t, int jl){
+ char date[CB_DATEBUFSIZ], tzone[CB_DATEBUFSIZ], *rv;
+ int year, mon, day, hour, min, sec;
+ cbcalendar(t, jl, &year, &mon, &day, &hour, &min, &sec);
+ jl /= 60;
+ if(jl == 0){
+ sprintf(tzone, "Z");
+ } else if(jl < 0){
+ jl *= -1;
+ sprintf(tzone, "-%02d:%02d", jl / 60, jl % 60);
+ } else {
+ sprintf(tzone, "+%02d:%02d", jl / 60, jl % 60);
+ }
+ sprintf(date, "%04d-%02d-%02dT%02d:%02d:%02d%s", year, mon, day, hour, min, sec, tzone);
+ CB_MEMDUP(rv, date, strlen(date));
+ return rv;
+}
+
+
+/* Get the string for a date in RFC 1123 format. */
+char *cbdatestrhttp(time_t t, int jl){
+ char date[CB_DATEBUFSIZ], *wp, *rv;
+ int year, mon, day, hour, min, sec;
+ cbcalendar(t, jl, &year, &mon, &day, &hour, &min, &sec);
+ jl /= 60;
+ wp = date;
+ switch(cbdayofweek(year, mon, day)){
+ case 0: wp += sprintf(wp, "Sun, "); break;
+ case 1: wp += sprintf(wp, "Mon, "); break;
+ case 2: wp += sprintf(wp, "Tue, "); break;
+ case 3: wp += sprintf(wp, "Wed, "); break;
+ case 4: wp += sprintf(wp, "Thu, "); break;
+ case 5: wp += sprintf(wp, "Fri, "); break;
+ case 6: wp += sprintf(wp, "Sat, "); break;
+ }
+ wp += sprintf(wp, "%02d ", day);
+ switch(mon){
+ case 1: wp += sprintf(wp, "Jan "); break;
+ case 2: wp += sprintf(wp, "Feb "); break;
+ case 3: wp += sprintf(wp, "Mar "); break;
+ case 4: wp += sprintf(wp, "Apr "); break;
+ case 5: wp += sprintf(wp, "May "); break;
+ case 6: wp += sprintf(wp, "Jun "); break;
+ case 7: wp += sprintf(wp, "Jul "); break;
+ case 8: wp += sprintf(wp, "Aug "); break;
+ case 9: wp += sprintf(wp, "Sep "); break;
+ case 10: wp += sprintf(wp, "Oct "); break;
+ case 11: wp += sprintf(wp, "Nov "); break;
+ case 12: wp += sprintf(wp, "Dec "); break;
+ }
+ wp += sprintf(wp, "%04d %02d:%02d:%02d ", year, hour, min, sec);
+ if(jl == 0){
+ wp += sprintf(wp, "GMT");
+ } else if(jl < 0){
+ jl *= -1;
+ wp += sprintf(wp, "-%02d%02d", jl / 60, jl % 60);
+ } else {
+ wp += sprintf(wp, "+%02d%02d", jl / 60, jl % 60);
+ }
+ CB_MEMDUP(rv, date, strlen(date));
+ return rv;
+}
+
+
+/* Get the time value of a date string in decimal, W3CDTF, or RFC 1123. */
+time_t cbstrmktime(const char *str){
+ const char *crp;
+ char *pv, *rp;
+ int len, clen;
+ time_t t;
+ struct tm ts;
+ assert(str);
+ while(*str > '\0' && *str <= ' '){
+ str++;
+ }
+ if(*str == '\0') return -1;
+ if(str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) return (time_t)strtol(str + 2, NULL, 16);
+ memset(&ts, 0, sizeof(struct tm));
+ ts.tm_year = 70;
+ ts.tm_mon = 0;
+ ts.tm_mday = 1;
+ ts.tm_hour = 0;
+ ts.tm_min = 0;
+ ts.tm_sec = 0;
+ ts.tm_isdst = 0;
+ len = strlen(str);
+ t = (time_t)strtol(str, &pv, 10);
+ if(*(signed char *)pv >= '\0' && *pv <= ' '){
+ while(*pv > '\0' && *pv <= ' '){
+ pv++;
+ }
+ if(*pv == '\0') return t;
+ }
+ if((pv[0] == 's' || pv[0] == 'S') && ((signed char *)pv)[1] >= '\0' && pv[1] <= ' ')
+ return t;
+ if((pv[0] == 'm' || pv[0] == 'M') && ((signed char *)pv)[1] >= '\0' && pv[1] <= ' ')
+ return t * 60;
+ if((pv[0] == 'h' || pv[0] == 'H') && ((signed char *)pv)[1] >= '\0' && pv[1] <= ' ')
+ return t * 60 * 60;
+ if((pv[0] == 'd' || pv[0] == 'D') && ((signed char *)pv)[1] >= '\0' && pv[1] <= ' ')
+ return t * 60 * 60 * 24;
+ if(len > 4 && str[4] == '-'){
+ ts.tm_year = atoi(str) - 1900;
+ if((pv = strchr(str, '-')) != NULL && pv - str == 4){
+ rp = pv + 1;
+ ts.tm_mon = atoi(rp) - 1;
+ if((pv = strchr(rp, '-')) != NULL && pv - str == 7){
+ rp = pv + 1;
+ ts.tm_mday = atoi(rp);
+ if((pv = strchr(rp, 'T')) != NULL && pv - str == 10){
+ rp = pv + 1;
+ ts.tm_hour = atoi(rp);
+ if((pv = strchr(rp, ':')) != NULL && pv - str == 13){
+ rp = pv + 1;
+ ts.tm_min = atoi(rp);
+ }
+ if((pv = strchr(rp, ':')) != NULL && pv - str == 16){
+ rp = pv + 1;
+ ts.tm_sec = atoi(rp);
+ }
+ if((pv = strchr(rp, '.')) != NULL && pv - str >= 19) rp = pv + 1;
+ strtol(rp, &pv, 10);
+ if((*pv == '+' || *pv == '-') && strlen(pv) >= 6 && pv[3] == ':')
+ ts.tm_sec -= (atoi(pv + 1) * 3600 + atoi(pv + 4) * 60) * (pv[0] == '+' ? 1 : -1);
+ }
+ }
+ }
+ ts.tm_sec += cbjetlag();
+ return mktime(&ts);
+ }
+ if(len > 4 && str[4] == '/'){
+ ts.tm_year = atoi(str) - 1900;
+ if((pv = strchr(str, '/')) != NULL && pv - str == 4){
+ rp = pv + 1;
+ ts.tm_mon = atoi(rp) - 1;
+ if((pv = strchr(rp, '/')) != NULL && pv - str == 7){
+ rp = pv + 1;
+ ts.tm_mday = atoi(rp);
+ if((pv = strchr(rp, ' ')) != NULL && pv - str == 10){
+ rp = pv + 1;
+ ts.tm_hour = atoi(rp);
+ if((pv = strchr(rp, ':')) != NULL && pv - str == 13){
+ rp = pv + 1;
+ ts.tm_min = atoi(rp);
+ }
+ if((pv = strchr(rp, ':')) != NULL && pv - str == 16){
+ rp = pv + 1;
+ ts.tm_sec = atoi(rp);
+ }
+ if((pv = strchr(rp, '.')) != NULL && pv - str >= 19) rp = pv + 1;
+ strtol(rp, &pv, 10);
+ if((*pv == '+' || *pv == '-') && strlen(pv) >= 6 && pv[3] == ':')
+ ts.tm_sec -= (atoi(pv + 1) * 3600 + atoi(pv + 4) * 60) * (pv[0] == '+' ? 1 : -1);
+ }
+ }
+ }
+ ts.tm_sec += cbjetlag();
+ return mktime(&ts);
+ }
+ crp = str;
+ if(len >= 4 && str[3] == ',') crp = str + 4;
+ while(*crp == ' '){
+ crp++;
+ }
+ ts.tm_mday = atoi(crp);
+ while((*crp >= '0' && *crp <= '9') || *crp == ' '){
+ crp++;
+ }
+ if(cbstrfwimatch(crp, "Jan")){
+ ts.tm_mon = 0;
+ } else if(cbstrfwimatch(crp, "Feb")){
+ ts.tm_mon = 1;
+ } else if(cbstrfwimatch(crp, "Mar")){
+ ts.tm_mon = 2;
+ } else if(cbstrfwimatch(crp, "Apr")){
+ ts.tm_mon = 3;
+ } else if(cbstrfwimatch(crp, "May")){
+ ts.tm_mon = 4;
+ } else if(cbstrfwimatch(crp, "Jun")){
+ ts.tm_mon = 5;
+ } else if(cbstrfwimatch(crp, "Jul")){
+ ts.tm_mon = 6;
+ } else if(cbstrfwimatch(crp, "Aug")){
+ ts.tm_mon = 7;
+ } else if(cbstrfwimatch(crp, "Sep")){
+ ts.tm_mon = 8;
+ } else if(cbstrfwimatch(crp, "Oct")){
+ ts.tm_mon = 9;
+ } else if(cbstrfwimatch(crp, "Nov")){
+ ts.tm_mon = 10;
+ } else if(cbstrfwimatch(crp, "Dec")){
+ ts.tm_mon = 11;
+ } else {
+ ts.tm_mon = -1;
+ }
+ if(ts.tm_mon >= 0) crp += 3;
+ while(*crp == ' '){
+ crp++;
+ }
+ ts.tm_year = atoi(crp);
+ if(ts.tm_year >= 1969) ts.tm_year -= 1900;
+ while(*crp >= '0' && *crp <= '9'){
+ crp++;
+ }
+ while(*crp == ' '){
+ crp++;
+ }
+ if(ts.tm_mday > 0 && ts.tm_mon >= 0 && ts.tm_year >= 0){
+ clen = strlen(crp);
+ if(clen >= 8 && crp[2] == ':' && crp[5] == ':'){
+ ts.tm_hour = atoi(crp + 0);
+ ts.tm_min = atoi(crp + 3);
+ ts.tm_sec = atoi(crp + 6);
+ if(clen >= 14 && crp[8] == ' ' && (crp[9] == '+' || crp[9] == '-')){
+ ts.tm_sec -= ((crp[10] - '0') * 36000 + (crp[11] - '0') * 3600 +
+ (crp[12] - '0') * 600 + (crp[13] - '0') * 60) * (crp[9] == '+' ? 1 : -1);
+ } else if(clen > 9){
+ if(!strcmp(crp + 9, "JST")){
+ ts.tm_sec -= 9 * 3600;
+ } else if(!strcmp(crp + 9, "CCT")){
+ ts.tm_sec -= 8 * 3600;
+ } else if(!strcmp(crp + 9, "KST")){
+ ts.tm_sec -= 9 * 3600;
+ } else if(!strcmp(crp + 9, "EDT")){
+ ts.tm_sec -= -4 * 3600;
+ } else if(!strcmp(crp + 9, "EST")){
+ ts.tm_sec -= -5 * 3600;
+ } else if(!strcmp(crp + 9, "CDT")){
+ ts.tm_sec -= -5 * 3600;
+ } else if(!strcmp(crp + 9, "CST")){
+ ts.tm_sec -= -6 * 3600;
+ } else if(!strcmp(crp + 9, "MDT")){
+ ts.tm_sec -= -6 * 3600;
+ } else if(!strcmp(crp + 9, "MST")){
+ ts.tm_sec -= -7 * 3600;
+ } else if(!strcmp(crp + 9, "PDT")){
+ ts.tm_sec -= -7 * 3600;
+ } else if(!strcmp(crp + 9, "PST")){
+ ts.tm_sec -= -8 * 3600;
+ } else if(!strcmp(crp + 9, "HDT")){
+ ts.tm_sec -= -9 * 3600;
+ } else if(!strcmp(crp + 9, "HST")){
+ ts.tm_sec -= -10 * 3600;
+ }
+ }
+ }
+ ts.tm_sec += cbjetlag();
+ return mktime(&ts);
+ }
+ return -1;
+}
+
+
+/* Get user and system processing times. */
+void cbproctime(double *usrp, double *sysp){
+ struct tms buf;
+ times(&buf);
+ if(usrp) *usrp = (double)buf.tms_utime / sysconf(_SC_CLK_TCK);
+ if(sysp) *sysp = (double)buf.tms_stime / sysconf(_SC_CLK_TCK);
+}
+
+
+/* Ensure that the standard I/O is binary mode. */
+void cbstdiobin(void){
+ if(setmode(0, O_BINARY) == -1 || setmode(1, O_BINARY) == -1 || setmode(2, O_BINARY) == -1)
+ cbmyfatal("setmode failed");
+}
+
+
+
+/*************************************************************************************************
+ * features for experts
+ *************************************************************************************************/
+
+
+/* Show error message on the standard error output and exit. */
+void *cbmyfatal(const char *message){
+ char buf[CB_MSGBUFSIZ];
+ assert(message);
+ if(cbfatalfunc){
+ cbfatalfunc(message);
+ } else {
+ sprintf(buf, "fatal error: %s\n", message);
+ write(2, buf, strlen(buf));
+ }
+ exit(1);
+ return NULL;
+}
+
+
+/* Create a datum handle from an allocated region. */
+CBDATUM *cbdatumopenbuf(char *ptr, int size){
+ CBDATUM *datum;
+ assert(ptr && size >= 0);
+ CB_REALLOC(ptr, size + 1);
+ CB_MALLOC(datum, sizeof(*datum));
+ datum->dptr = ptr;
+ datum->dptr[size] = '\0';
+ datum->dsize = size;
+ datum->asize = size;
+ return datum;
+}
+
+
+/* Set a buffer to a datum handle. */
+void cbdatumsetbuf(CBDATUM *datum, char *ptr, int size){
+ assert(datum && ptr && size >= 0);
+ free(datum->dptr);
+ CB_REALLOC(ptr, size + 1);
+ datum->dptr = ptr;
+ datum->dptr[size] = '\0';
+ datum->dsize = size;
+ datum->asize = size;
+}
+
+
+/* Add an allocated element at the end of a list. */
+void cblistpushbuf(CBLIST *list, char *ptr, int size){
+ int index;
+ assert(list && ptr && size >= 0);
+ index = list->start + list->num;
+ if(index >= list->anum){
+ list->anum *= 2;
+ CB_REALLOC(list->array, list->anum * sizeof(list->array[0]));
+ }
+ list->array[index].dptr = ptr;
+ list->array[index].dsize = size;
+ list->num++;
+}
+
+
+/* Get a map handle with specifying the number of buckets. */
+CBMAP *cbmapopenex(int bnum){
+ CBMAP *map;
+ int i;
+ assert(bnum > 0);
+ CB_MALLOC(map, sizeof(*map));
+ CB_MALLOC(map->buckets, sizeof(map->buckets[0]) * bnum);
+ for(i = 0; i < bnum; i++){
+ map->buckets[i] = NULL;
+ }
+ map->first = NULL;
+ map->last = NULL;
+ map->cur = NULL;
+ map->bnum = bnum;
+ map->rnum = 0;
+ return map;
+}
+
+
+
+/*************************************************************************************************
+ * private objects
+ *************************************************************************************************/
+
+
+/* Handler to invoke the global garbage collector. */
+static void cbggchandler(void){
+ cbggckeeper(NULL, NULL);
+}
+
+
+/* Manage resources of the global garbage collector.
+ `ptr' specifies the pointer to add to the collection. If it is `NULL', all resources are
+ released.
+ `func' specifies the pointer to the function to release the resources. */
+static void cbggckeeper(void *ptr, void (*func)(void *)){
+ static void **parray = NULL;
+ static void (**farray)(void *) = NULL;
+ static int onum = 0;
+ static int asiz = CB_GCUNIT;
+ int i;
+ if(!ptr){
+ if(!parray) return;
+ for(i = onum - 1; i >= 0; i--){
+ farray[i](parray[i]);
+ }
+ free(parray);
+ free(farray);
+ parray = NULL;
+ farray = NULL;
+ onum = 0;
+ asiz = CB_GCUNIT;
+ return;
+ }
+ if(!parray){
+ CB_MALLOC(parray, sizeof(void *) * asiz);
+ CB_MALLOC(farray, sizeof(void *) * asiz);
+ if(atexit(cbggchandler) != 0) cbmyfatal("gc failed");
+ }
+ if(onum >= asiz){
+ asiz *= 2;
+ CB_REALLOC(parray, sizeof(void *) * asiz);
+ CB_REALLOC(farray, sizeof(void *) * asiz);
+ }
+ parray[onum] = ptr;
+ farray[onum] = func;
+ onum++;
+}
+
+
+/* Utility function for quick sort.
+ `bp' specifies the pointer to the pointer to an array.
+ `nmemb' specifies the number of elements of the array.
+ `size' specifies the size of each element.
+ `pswap' specifies the pointer to the swap region for a pivot.
+ `vswap' specifies the pointer to the swap region for elements.
+ `compar' specifies the pointer to comparing function. */
+static void cbqsortsub(char *bp, int nmemb, int size, char *pswap, char *vswap,
+ int(*compar)(const void *, const void *)){
+ int top, bottom;
+ assert(bp && nmemb >= 0 && size > 0 && pswap && vswap && compar);
+ if(nmemb < 10){
+ if(nmemb > 1) cbisort(bp, nmemb, size, compar);
+ return;
+ }
+ top = 0;
+ bottom = nmemb - 1;
+ memcpy(pswap, bp + (nmemb / 2) * size, size);
+ while(top - 1 < bottom){
+ if(compar(bp + top * size, pswap) < 0){
+ top++;
+ } else if(compar(bp + bottom * size, pswap) > 0){
+ bottom--;
+ } else {
+ if(top != bottom){
+ memcpy(vswap, bp + top * size, size);
+ memcpy(bp + top * size, bp + bottom * size, size);
+ memcpy(bp + bottom * size, vswap, size);
+ }
+ top++;
+ bottom--;
+ }
+ }
+ cbqsortsub(bp, top, size, pswap, vswap, compar);
+ cbqsortsub(bp + (bottom + 1) * size, nmemb - bottom - 1, size, pswap, vswap, compar);
+}
+
+
+/* Compare two list elements.
+ `a' specifies the pointer to one element.
+ `b' specifies the pointer to the other element.
+ The return value is positive if a is big, negative if b is big, else, it is 0. */
+static int cblistelemcmp(const void *a, const void *b){
+ int i, size;
+ CBLISTDATUM *ap, *bp;
+ char *ao, *bo;
+ assert(a && b);
+ ap = (CBLISTDATUM *)a;
+ bp = (CBLISTDATUM *)b;
+ ao = ap->dptr;
+ bo = bp->dptr;
+ size = ap->dsize < bp->dsize ? ap->dsize : bp->dsize;
+ for(i = 0; i < size; i++){
+ if(ao[i] > bo[i]) return 1;
+ if(ao[i] < bo[i]) return -1;
+ }
+ return ap->dsize - bp->dsize;
+}
+
+
+/* Compare two keys.
+ `abuf' specifies the pointer to the region of the former.
+ `asiz' specifies the size of the region.
+ `bbuf' specifies the pointer to the region of the latter.
+ `bsiz' specifies the size of the region.
+ The return value is 0 if two equals, positive if the formar is big, else, negative. */
+static int cbkeycmp(const char *abuf, int asiz, const char *bbuf, int bsiz){
+ assert(abuf && asiz >= 0 && bbuf && bsiz >= 0);
+ if(asiz > bsiz) return 1;
+ if(asiz < bsiz) return -1;
+ return memcmp(abuf, bbuf, asiz);
+}
+
+
+
+/* END OF FILE */