source: tags/arb_5.3/ARBDB/adstring.c

Last change on this file was 6102, checked in by westram, 16 years ago
  • fix warning "format not a string literal and no format arguments"
    • GB_information → GB_information/GB_informationf
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 62.0 KB
Line 
1/* ============================================================= */
2/*                                                               */
3/*   File      : adstring.c                                      */
4/*   Purpose   : various string functions                        */
5/*                                                               */
6/*   Institute of Microbiology (Technical University Munich)     */
7/*   http://www.arb-home.de/                                     */
8/*                                                               */
9/* ============================================================= */
10#include "adlocal.h"
11
12#include <stdlib.h>
13#include <string.h>
14#include <ctype.h>
15#include <errno.h>
16#include <stdarg.h>
17#include <dirent.h>
18#include <sys/stat.h>
19#include <execinfo.h>
20#include <signal.h>
21
22
23
24/********************************************************************************************
25                    directory handling
26********************************************************************************************/
27
28GB_ERROR gb_scan_directory(char *basename, struct gb_scandir *sd) { /* goes to header: __ATTR__USERESULT */
29    /* look for quick saves (basename = yyy/xxx no arb ending !!!!) */
30    char *path = strdup(basename);
31    const char *fulldir = ".";
32    char *file = strrchr(path,'/');
33    DIR *dirp;
34    int curindex;
35    char *suffix;
36    struct dirent *dp;
37    struct stat st;
38    const char *oldstyle = ".arb.quick";
39    char    buffer[GB_PATH_MAX];
40    int oldstylelen = strlen(oldstyle);
41    int filelen;
42
43    if (file) {
44        *(file++) = 0;
45        fulldir = path;
46    } else {
47        file = path;
48    }
49
50    memset((char*)sd,0,sizeof(*sd));
51    sd->type = GB_SCAN_NO_QUICK;
52    sd->highest_quick_index = -1;
53    sd->newest_quick_index = -1;
54    sd->date_of_quick_file = 0;
55
56    dirp = opendir(fulldir);
57    if (!dirp){
58        GB_ERROR error = GB_export_errorf("Directory %s of file %s.arb not readable",fulldir,file);
59        free(path);
60        return error;
61    }
62    filelen = strlen(file);
63    for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)){
64        if (strncmp(dp->d_name,file,filelen)) continue;
65        suffix = dp->d_name + filelen;
66        if (suffix[0] != '.') continue;
67        if (!strncmp(suffix,oldstyle,oldstylelen)){
68            if (sd->type == GB_SCAN_NEW_QUICK){
69                printf("Warning: Found new and old changes files, using new\n");
70                continue;
71            }
72            sd->type = GB_SCAN_OLD_QUICK;
73            curindex = atoi(suffix+oldstylelen);
74            goto check_time_and_date;
75        }
76        if (strlen(suffix) == 4 &&
77            suffix[0] == '.' &&
78            suffix[1] == 'a' &&
79            isdigit(suffix[2]) &&
80            isdigit(suffix[3])){
81            if (sd->type == GB_SCAN_OLD_QUICK){
82                printf("Warning: Found new and old changes files, using new\n");
83            }
84            sd->type = GB_SCAN_NEW_QUICK;
85            curindex = atoi(suffix+2);
86            goto check_time_and_date;
87        }
88        continue;
89    check_time_and_date:
90        if (curindex > sd->highest_quick_index) sd->highest_quick_index = curindex;
91        sprintf(buffer,"%s/%s",fulldir,dp->d_name);
92        stat(buffer,&st);
93        if ((unsigned long)st.st_mtime > sd->date_of_quick_file){
94            sd->date_of_quick_file = st.st_mtime;
95            sd->newest_quick_index = curindex;
96        }
97        continue;
98    }
99    closedir(dirp);
100    free(path);
101    return 0;
102}
103
104
105char *GB_find_all_files(const char *dir,const char *mask, GB_BOOL filename_only) {
106    /* Returns a string containing the filenames of all files matching mask.
107       The single filenames are seperated by '*'.
108       if 'filename_only' is true -> string contains only filenames w/o path
109       
110       returns 0 if no files found (or directory not found).
111       in this case an error may be exported 
112
113       'mask' may contain wildcards (*?) or
114       it may be a regular expression ('/regexp/')
115    */
116
117    DIR           *dirp;
118    struct dirent *dp;
119    struct stat    st;
120    char          *result = 0;
121    char           buffer[GB_PATH_MAX];
122
123    dirp = opendir(dir);
124    if (dirp) {
125        GBS_MATCHER *matcher = GBS_compile_matcher(mask, GB_IGNORE_CASE);
126        if (matcher) {
127            for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
128                if (GBS_string_matches_regexp(dp->d_name, matcher)) {
129                    sprintf(buffer,"%s/%s",dir,dp->d_name);
130                    if (stat(buffer,&st) == 0  && S_ISREG(st.st_mode)) { // regular file ?
131                        if (filename_only) strcpy(buffer, dp->d_name);
132                        if (result) {
133                            freeset(result, GBS_global_string_copy("%s*%s", result, buffer));
134                        }
135                        else {
136                            result = strdup(buffer);
137                        }
138                    }
139                }
140            }
141            GBS_free_matcher(matcher);
142        }
143        closedir(dirp);
144    }
145
146    return result;
147}
148
149char *GB_find_latest_file(const char *dir, const char *mask) {
150    /* returns the name of the newest file in dir 'dir' matching 'mask'
151     * or NULL (in this case an error may be exported)
152     *
153     * 'mask' may contain wildcards (*?) or
154     * it may be a regular expression ('/regexp/')
155     */
156   
157    DIR           *dirp;
158    struct dirent *dp;
159    char           buffer[GB_PATH_MAX];
160    struct stat    st;
161    GB_ULONG       newest = 0;
162    char          *result = 0;
163
164    dirp = opendir(dir);
165    if (dirp) {
166        GBS_MATCHER *matcher = GBS_compile_matcher(mask, GB_IGNORE_CASE);
167        if (matcher) {
168            for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
169                if (GBS_string_matches_regexp(dp->d_name, matcher)) {
170                    sprintf(buffer,"%s/%s",dir,dp->d_name);
171                    if (stat(buffer,&st) == 0) {
172                        if ((GB_ULONG)st.st_mtime > newest) {
173                            newest = st.st_mtime;
174                            freedup(result, dp->d_name);
175                        }
176                    }
177                }
178            }
179            GBS_free_matcher(matcher);
180        }
181        closedir(dirp);
182    }
183    return result;
184}
185
186gb_warning_func_type gb_warning_func;
187gb_information_func_type gb_information_func;
188gb_status_func_type gb_status_func;
189gb_status_func2_type gb_status_func2;
190
191/********************************************************************************************
192                    error handling
193********************************************************************************************/
194
195void GB_raise_critical_error(const char *msg) {
196    fprintf(stderr, "------------------------------------------------------------\n");
197    fprintf(stderr, "A critical error occurred in ARB\nError-Message: %s\n", msg);
198#if defined(DEBUG)
199    fprintf(stderr, "Run the debugger to find the location where the error was raised.\n");
200#endif /* DEBUG */
201    fprintf(stderr, "------------------------------------------------------------\n");
202    gb_assert(0);
203    exit(-1);
204}
205
206#if defined(DEVEL_RALF)
207#warning redesign GB_export_error et al
208/* To clearly distinguish between the two ways of error handling
209 * (which are: return GB_ERROR
210 *  and:       export the error)
211 *
212 * GB_export_error() shall only export, not return the error message.
213 * if only used for formatting GBS_global_string shall be used
214 * (most cases where GB_export_errorf is used are candidates for this.
215 *  GB_export_error was generally misused for this, before
216 *  GBS_global_string was added!)
217 *
218 * GB_export_IO_error() shall not export and be renamed into GB_IO_error()
219 *
220 * GB_export_error() shall fail if there is already an exported error
221 * (maybe always remember a stack trace of last error export (try whether copy of backtrace-array works))
222 *
223 * use GB_get_error() to import AND clear the error
224 */
225#endif /* DEVEL_RALF */
226
227static char *GB_error_buffer = 0;
228
229GB_ERROR GB_export_error(const char *error) { // just a temp hack around format-warnings
230    return GB_export_errorf("%s", error);
231}
232
233GB_ERROR GB_export_errorf(const char *templat, ...) {
234    /* goes to header: __ATTR__FORMAT(1) __ATTR__DEPRECATED */
235
236    char     buffer[GBS_GLOBAL_STRING_SIZE];
237    char    *p = buffer;
238    va_list  parg;
239   
240    memset(buffer,0,1000);
241
242#if defined(DEVEL_RALF)
243#warning dont prepend error here
244#endif /* DEVEL_RALF */
245
246    p += sprintf(buffer,"ARB ERROR: ");
247    va_start(parg,templat);
248
249    vsprintf(p,templat,parg);
250
251    freedup(GB_error_buffer, buffer);
252    return GB_error_buffer;
253}
254
255GB_ERROR GB_export_IO_error(const char *action, const char *filename) {
256    /* like GB_export_error() - creates error message from current 'errno'
257       action may be NULL (otherwise it should contain sth like "writing" or "deleting")
258       filename may be NULL (otherwise it should contain the filename, the IO-Error occurred for)
259    */
260
261    const char *error_message;
262    if (errno) {
263        error_message = strerror(errno);
264    }
265    else {
266        ad_assert(0);           // unhandled error (which is NOT an IO-Error)
267        error_message =
268            "Some unhandled error occurred, but it was not an IO-Error. "
269            "Please send detailed information about how the error occurred to devel@arb-home.de "
270            "or ignore this error (if possible).";
271    }
272
273    {
274        char buffer[GBS_GLOBAL_STRING_SIZE];
275
276        if (action) {
277            if (filename) sprintf(buffer, "ARB ERROR: While %s '%s': %s", action, filename, error_message);
278            else sprintf(buffer, "ARB ERROR: While %s <unknown file>: %s", action, error_message);
279        }
280        else {
281            if (filename) sprintf(buffer, "ARB ERROR: Concerning '%s': %s", filename, error_message);
282            else sprintf(buffer, "ARB ERROR: %s", error_message);
283        }
284
285        freedup(GB_error_buffer, buffer);
286    }
287    return GB_error_buffer;
288}
289
290GB_ERROR GB_print_error() {
291    if (GB_error_buffer){
292        fflush(stdout);
293        fprintf(stderr,"%s\n",GB_error_buffer);
294    }
295    return GB_error_buffer;
296}
297
298GB_ERROR GB_get_error() {
299    /* goes to header: __ATTR__DEPRECATED  */
300
301    /* This function is deprecated.
302     * Instead use either
303     * - GB_have_error() or
304     * - GB_await_error()
305     */
306   
307    return GB_error_buffer;
308}
309
310GB_BOOL GB_have_error() {
311    return GB_error_buffer != 0;
312}
313
314GB_ERROR GB_await_error() {
315    if (GB_error_buffer) {
316        static char *err = 0;
317        reassign(err, GB_error_buffer);
318        return err;
319    }
320    ad_assert(0); // please correct error handling
321    return "Program logic error: Something went wrong, but reason is unknown";
322}
323
324void GB_clear_error() {         /* clears the error buffer */
325    freeset(GB_error_buffer, 0);
326}
327
328#if defined(DEVEL_RALF)
329#warning search for 'GBS_global_string.*error' and replace with GB_failedTo_error
330#endif /* DEVEL_RALF */
331GB_ERROR GB_failedTo_error(const char *do_something, const char *special, GB_ERROR error) {
332    if (error) {
333        if (special) {
334            error = GBS_global_string("Failed to %s '%s'.\n(Reason: %s)", do_something, special, error);
335        }
336        else {
337            error = GBS_global_string("Failed to %s.\n(Reason: %s)", do_something, error);
338        }
339    }
340    return error;
341}
342
343/* -------------------------------------------------------------------------------- */
344
345#ifdef LINUX
346# define HAVE_VSNPRINTF
347#endif
348
349#ifdef HAVE_VSNPRINTF
350# define PRINT2BUFFER(buffer, bufsize, templat, parg) vsnprintf(buffer, bufsize, templat, parg);
351#else
352# define PRINT2BUFFER(buffer, bufsize, templat, parg) vsprintf(buffer, templat, parg);
353#endif
354
355#define PRINT2BUFFER_CHECKED(printed, buffer, bufsize, templat, parg)   \
356    (printed) = PRINT2BUFFER(buffer, bufsize, templat, parg);           \
357    if ((printed) < 0 || (size_t)(printed) >= (bufsize)) {              \
358        GBK_terminatef("Internal buffer overflow (size=%zu, used=%i)\n", \
359                       (bufsize), (printed));                           \
360    }
361
362/* -------------------------------------------------------------------------------- */
363
364#if defined(DEBUG)
365#if defined(DEVEL_RALF)
366/* #define TRACE_BUFFER_USAGE */
367#endif /* DEBUG */
368#endif /* DEVEL_RALF */
369
370#define GLOBAL_STRING_BUFFERS 4
371
372static size_t last_global_string_size = 0;
373
374static GB_CSTR gbs_vglobal_string(const char *templat, va_list parg, int allow_reuse)
375{
376    static char buffer[GLOBAL_STRING_BUFFERS][GBS_GLOBAL_STRING_SIZE+2]; // serveral buffers - used alternately
377    static int  idx                             = 0;
378    static char lifetime[GLOBAL_STRING_BUFFERS] = { };
379    static char nextIdx[GLOBAL_STRING_BUFFERS] = { };
380
381    int my_idx;
382    int psize;
383
384    if (nextIdx[0] == 0) { // initialize nextIdx
385        for (my_idx = 0; my_idx<GLOBAL_STRING_BUFFERS; my_idx++) {
386            nextIdx[my_idx] = (my_idx+1)%GLOBAL_STRING_BUFFERS;
387        }
388    }
389
390    if (allow_reuse == -1) { /* called from GBS_reuse_buffer */
391        /* buffer to reuse is passed in 'templat' */
392
393        for (my_idx = 0; my_idx<GLOBAL_STRING_BUFFERS; my_idx++) {
394            if (buffer[my_idx] == templat) {
395                lifetime[my_idx] = 0;
396#if defined(TRACE_BUFFER_USAGE)
397                printf("Reusing buffer #%i\n", my_idx);
398#endif /* TRACE_BUFFER_USAGE */
399                if (nextIdx[my_idx] == idx) idx = my_idx;
400                return 0;
401            }
402#if defined(TRACE_BUFFER_USAGE)
403            else {
404                printf("(buffer to reuse is not buffer #%i (%p))\n", my_idx, buffer[my_idx]);
405            }
406#endif /* TRACE_BUFFER_USAGE */
407        }
408        for (my_idx = 0; my_idx<GLOBAL_STRING_BUFFERS; my_idx++) {
409            printf("buffer[%i]=%p\n", my_idx, buffer[my_idx]);
410        }
411        ad_assert(0);       /* GBS_reuse_buffer called with illegal buffer */
412        return 0;
413    }
414
415    if (lifetime[idx] == 0) {
416        my_idx = idx;
417    }
418    else {
419        for (my_idx = nextIdx[idx]; lifetime[my_idx]>0; my_idx = nextIdx[my_idx]) {
420#if defined(TRACE_BUFFER_USAGE)
421            printf("decreasing lifetime[%i] (%i->%i)\n", my_idx, lifetime[my_idx], lifetime[my_idx]-1);
422#endif /* TRACE_BUFFER_USAGE */
423            lifetime[my_idx]--;
424        }
425    }
426
427    PRINT2BUFFER_CHECKED(psize, buffer[my_idx], (size_t)GBS_GLOBAL_STRING_SIZE, templat, parg);
428
429#if defined(TRACE_BUFFER_USAGE)
430    printf("Printed into global buffer #%i ('%s')\n", my_idx, buffer[my_idx]);
431#endif /* TRACE_BUFFER_USAGE */
432
433    last_global_string_size = psize;
434
435    if (!allow_reuse) {
436        idx           = my_idx;
437        lifetime[idx] = 1;
438    }
439#if defined(TRACE_BUFFER_USAGE)
440    else {
441        printf("Allow reuse of buffer #%i\n", my_idx);
442    }
443#endif /* TRACE_BUFFER_USAGE */
444
445    return buffer[my_idx];
446}
447
448static char *gbs_vglobal_string_copy(const char *templat, va_list parg) {
449    GB_CSTR gstr = gbs_vglobal_string(templat, parg, 1);
450    return GB_strduplen(gstr, last_global_string_size);
451}
452
453void GBS_reuse_buffer(GB_CSTR global_buffer) {
454    /* If you've just shortely used a buffer, you can put it back here */
455    gbs_vglobal_string(global_buffer, 0, -1);
456}
457
458GB_CSTR GBS_global_string(const char *templat, ...) {
459    /* goes to header: __ATTR__FORMAT(1)  */
460
461    va_list parg;
462    GB_CSTR result;
463
464    va_start(parg,templat);
465    result = gbs_vglobal_string(templat, parg, 0);
466    va_end(parg);
467
468    return result;
469}
470
471char *GBS_global_string_copy(const char *templat, ...) {
472    /* goes to header: __ATTR__FORMAT(1)  */
473
474    va_list parg;
475    char *result;
476
477    va_start(parg,templat);
478    result = gbs_vglobal_string_copy(templat, parg);
479    va_end(parg);
480
481    return result;
482}
483
484#if defined(DEVEL_RALF)
485#warning search for '\b(sprintf)\b\s*\(' and replace by GBS_global_string_to_buffer
486#endif /* DEVEL_RALF */
487
488const char *GBS_global_string_to_buffer(char *buffer, size_t bufsize, const char *templat, ...) {
489    /* goes to header: __ATTR__FORMAT(3)  */
490
491    va_list parg;
492    int     psize;
493
494    gb_assert(buffer);
495    va_start(parg,templat);
496    PRINT2BUFFER_CHECKED(psize, buffer, bufsize, templat, parg);
497    va_end(parg);
498
499    return buffer;
500}
501
502size_t GBS_last_global_string_size() {
503    return last_global_string_size;
504}
505
506char *GBS_string_2_key_with_exclusions(const char *str, const char *additional)
507/* converts any string to a valid key (all chars in 'additional' are additionally allowed) */
508{
509    char buf[GB_KEY_LEN_MAX+1];
510    int i;
511    int c;
512    for (i=0;i<GB_KEY_LEN_MAX;) {
513        c = *(str++);
514        if (!c) break;
515
516        if (c==' ' || c == '_' ) {
517            buf[i++]  = '_';
518        }
519        else if (isalnum(c) || strchr(additional, c) != 0) {
520            buf[i++]  = c;
521        }
522    }
523    for (;i<GB_KEY_LEN_MIN;i++) buf[i] = '_';
524    buf[i] = 0;
525    return strdup(buf);
526}
527
528char *GBS_string_2_key(const char *str) /* converts any string to a valid key */
529{
530    return GBS_string_2_key_with_exclusions(str, "");
531}
532
533void gbs_uppercase(char *str)
534{
535    char c;
536    while( (c=*str) )   {
537        if ( (c<='z') && (c>='a')) *str = c - 'a' + 'A';
538        str++;
539    }
540}
541
542void gbs_memcopy(char *dest, const char *source, long len)
543{
544    long        i;
545    const char *s;
546    char       *d;
547
548    i = len;
549    s = source;
550    d = dest;
551    if (s < d) {
552        s += i;
553        d += i;
554        while (i--) {
555            *(--d) = *(--s);
556        }
557    } else {
558        while (i--) {
559            *(d++) = *(s++);
560        }
561    }
562}
563
564char *gbs_malloc_copy(const char *source, long len)
565{
566    char *dest;
567    dest = (char *)malloc((size_t)len);
568    memcpy(dest,source,(int)len);
569    return dest;
570}
571
572GB_ERROR GB_check_key(const char *key) { /* goes to header: __ATTR__USERESULT */
573    /* test whether all characters are letters, numbers or _ */
574    int  i;
575    long len;
576
577    if (!key || key[0] == 0) return GB_export_error("Empty key is not allowed");
578    len = strlen(key);
579    if (len>GB_KEY_LEN_MAX) return GB_export_errorf("Invalid key '%s': too long",key);
580    if (len < GB_KEY_LEN_MIN) return GB_export_errorf("Invalid key '%s': too short",key);
581
582    for (i = 0; key[i]; ++i) {
583        char c = key[i];
584        if ( (c>='a') && (c<='z')) continue;
585        if ( (c>='A') && (c<='Z')) continue;
586        if ( (c>='0') && (c<='9')) continue;
587        if ( (c=='_') ) continue;
588        return GB_export_errorf("Invalid character '%c' in '%s'; allowed: a-z A-Z 0-9 '_' ", c, key);
589    }
590
591    return 0;
592}
593GB_ERROR GB_check_link_name(const char *key) { /* goes to header: __ATTR__USERESULT */
594    /* test whether all characters are letters, numbers or _ */
595    int  i;
596    long len;
597
598    if (!key || key[0] == 0) return GB_export_error("Empty key is not allowed");
599    len = strlen(key);
600    if (len>GB_KEY_LEN_MAX) return GB_export_errorf("Invalid key '%s': too long",key);
601    if (len < 1) return GB_export_errorf("Invalid key '%s': too short",key); // here it differs from GB_check_key
602
603    for (i = 0; key[i]; ++i) {
604        char c = key[i];
605        if ( (c>='a') && (c<='z')) continue;
606        if ( (c>='A') && (c<='Z')) continue;
607        if ( (c>='0') && (c<='9')) continue;
608        if ( (c=='_') ) continue;
609        return GB_export_errorf("Invalid character '%c' in '%s'; allowed: a-z A-Z 0-9 '_' ", c, key);
610    }
611
612    return 0;
613}
614GB_ERROR GB_check_hkey(const char *key) { /* goes to header: __ATTR__USERESULT */
615    /* test whether all characters are letters, numbers or _ */
616    /* additionally allow '/' and '->' for hierarchical keys */
617    GB_ERROR err = 0;
618
619    if (!key || key[0] == 0) {
620        err = GB_export_error("Empty key is not allowed");
621    }
622    else if (!strpbrk(key, "/-")) {
623        err = GB_check_key(key);
624    }
625    else {
626        char *key_copy = strdup(key);
627        char *start    = key_copy;
628
629        if (start[0] == '/') ++start;
630
631        while (start && !err) {
632            char *key_end = strpbrk(start, "/-");
633
634            if (key_end) {
635                char c   = *key_end;
636                *key_end = 0;
637                err      = GB_check_key(start);
638                *key_end = c;
639
640                if (c == '-') {
641                    if (key_end[1] != '>') {
642                        err = GB_export_errorf("'>' expected after '-' in '%s'", key);
643                    }
644                    start = key_end+2;
645                }
646                else  {
647                    ad_assert(c == '/');
648                    start = key_end+1;
649                }
650            }
651            else {
652                err   = GB_check_key(start);
653                start = 0;
654            }
655        }
656
657        free(key_copy);
658    }
659
660    return err;
661}
662
663char *gbs_add_path(char *path,char *name)
664{
665    long i,len,found;
666    char *erg;
667    if (!name) return name;
668    if (!path) {
669        return 0;
670    }
671    if (*name == '/') return name;
672    found =0;
673    len = strlen(path);
674    for (i=0;i<len;i++) {
675        if (path[i] == '/') found = i+1;
676    }
677    len = found + strlen(name);
678    erg = (char *)GB_calloc(sizeof(char),(size_t)(len +1));
679    for (i=0;i<found;i++) {
680        erg[i] = path[i];
681    }
682    for (i=found;i<len;i++){
683        erg[i] = name[i-found];
684    }
685    return erg;
686}
687
688/* --------------------------- */
689/*      escape characters      */
690
691char *GBS_remove_escape(char *com)  /* \ is the escape charakter
692                                     */
693{
694    char *result,*s,*d;
695    int   ch;
696
697    s = d = result = strdup(com);
698    while ( (ch = *(s++)) ){
699        switch (ch) {
700            case '\\':
701                ch = *(s++); if (!ch) { s--; break; };
702                switch (ch) {
703                    case 'n':   *(d++) = '\n';break;
704                    case 't':   *(d++) = '\t';break;
705                    case '0':   *(d++) = '\0';break;
706                    default:    *(d++) = ch;break;
707                }
708                break;
709            default:
710                *(d++) = ch;
711        }
712    }
713    *d = 0;
714    return result;
715}
716
717/********************************************************************************************
718                    escape/unescape characters in strings
719********************************************************************************************/
720
721char *GBS_escape_string(const char *str, const char *chars_to_escape, char escape_char) {
722    // uses a special escape-method, which eliminates all 'chars_to_escape' completely
723    // from str (this makes further processing of the string more easy)
724    //
725    // escape_char is the character used for escaping. For performance reasons it
726    // should be a character rarely used in 'str'.
727    //
728    // 'chars_to_escape' may not contain 'A'-'Z' (these are used for escaping)
729    // and it may not be longer than 26
730    //
731    // RETURN VALUE : heap copy of escaped string
732
733    int   len    = strlen(str);
734    char *buffer = (char*)malloc(2*len+1);
735    int   j      = 0;
736    int   i;
737
738    ad_assert(strlen(chars_to_escape)              <= 26);
739    ad_assert(strchr(chars_to_escape, escape_char) == 0); // escape_char may not be included in chars_to_escape
740
741    for (i = 0; str[i]; ++i) {
742        if (str[i] == escape_char) {
743            buffer[j++] = escape_char;
744            buffer[j++] = escape_char;
745        }
746        else {
747            const char *found = strchr(chars_to_escape, str[i]);
748            if (found) {
749                buffer[j++] = escape_char;
750                buffer[j++] = (found-chars_to_escape+'A');
751
752                ad_assert(found[0]<'A' || found[0]>'Z'); // illegal character in chars_to_escape
753            }
754            else {
755
756                buffer[j++] = str[i];
757            }
758        }
759    }
760    buffer[j] = 0;
761
762    return buffer;
763}
764
765char *GBS_unescape_string(const char *str, const char *escaped_chars, char escape_char) {
766    // undoes GB_escape_string
767    int   len    = strlen(str);
768    char *buffer = (char*)malloc(len+1);
769    int   j      = 0;
770    int   i;
771
772#if defined(DEBUG)
773    int escaped_chars_len = strlen(escaped_chars);
774#endif // DEBUG
775
776    ad_assert(strlen(escaped_chars)              <= 26);
777    ad_assert(strchr(escaped_chars, escape_char) == 0); // escape_char may not be included in chars_to_escape
778
779    for (i = 0; str[i]; ++i) {
780        if (str[i] == escape_char) {
781            if (str[i+1] == escape_char) {
782                buffer[j++] = escape_char;
783            }
784            else {
785                int idx = str[i+1]-'A';
786
787                ad_assert(idx >= 0 && idx<escaped_chars_len);
788                buffer[j++] = escaped_chars[idx];
789            }
790            ++i;
791        }
792        else {
793            buffer[j++] = str[i];
794        }
795    }
796    buffer[j] = 0;
797
798    return buffer;
799}
800
801
802
803/********************************************************************************************
804                    String streams
805********************************************************************************************/
806
807#if defined(DEBUG)
808// # define DUMP_STRSTRUCT_MEMUSE
809#endif /* DEBUG */
810
811
812struct GBS_strstruct {
813    char *GBS_strcat_data;
814    long  GBS_strcat_data_size;
815    long  GBS_strcat_pos;
816};
817
818static struct GBS_strstruct *last_used = 0;
819
820struct GBS_strstruct *GBS_stropen(long init_size)   { /* opens a memory file */
821    struct GBS_strstruct *strstr;
822
823    if (last_used && last_used->GBS_strcat_data_size >= init_size) {
824        strstr    = last_used;
825        last_used = 0;
826    }
827    else {
828#if defined(DUMP_STRSTRUCT_MEMUSE)
829        printf("allocating new GBS_strstruct (size = %li)\n", init_size);
830#endif /* DUMP_STRSTRUCT_MEMUSE */
831        strstr                       = (struct GBS_strstruct *)malloc(sizeof(struct GBS_strstruct));
832        strstr->GBS_strcat_data_size = init_size;
833        strstr->GBS_strcat_data      = (char *)malloc((size_t)strstr->GBS_strcat_data_size);
834    }
835
836    strstr->GBS_strcat_pos     = 0;
837    strstr->GBS_strcat_data[0] = 0;
838
839    return strstr;
840}
841
842char *GBS_strclose(struct GBS_strstruct *strstr) {
843    /* returns a char* copy of the memory file */
844   
845    long  length = strstr->GBS_strcat_pos;
846    char *str    = (char*)malloc(length+1);
847
848    gb_assert(str);
849
850    memcpy(str, strstr->GBS_strcat_data, length+1); /* copy with 0 */
851    GBS_strforget(strstr);
852
853    return str;
854}
855
856void GBS_strforget(struct GBS_strstruct *strstr) {
857    if (last_used) {
858        if (last_used->GBS_strcat_data_size < strstr->GBS_strcat_data_size) { /* last_used is smaller -> keep this */
859            struct GBS_strstruct *tmp = last_used;
860            last_used                 = strstr;
861            strstr                    = tmp;
862        }
863    }
864    else {
865        static short oversized_counter = 0;
866
867        if (strstr->GBS_strcat_pos*10 < strstr->GBS_strcat_data_size) oversized_counter++;
868        else oversized_counter = 0;
869
870        if (oversized_counter<10) {
871            // keep strstruct for next call
872            last_used = strstr;
873            strstr    = 0;
874        }
875        // otherwise the current strstruct was oversized 10 times -> free it
876    }
877
878    if (strstr) {
879#if defined(DUMP_STRSTRUCT_MEMUSE)
880        printf("freeing GBS_strstruct (size = %li)\n", strstr->GBS_strcat_data_size);
881#endif /* DUMP_STRSTRUCT_MEMUSE */
882        free(strstr->GBS_strcat_data);
883        free(strstr);
884    }
885}
886
887GB_BUFFER GBS_mempntr(struct GBS_strstruct *strstr) {
888    /* returns the memory file */
889    return strstr->GBS_strcat_data;
890}
891
892long GBS_memoffset(struct GBS_strstruct *strstr) {
893    /* returns the offset into the memory file */
894    return strstr->GBS_strcat_pos;
895}
896
897void GBS_str_cut_tail(struct GBS_strstruct *strstr, int byte_count){
898    /* Removes byte_count characters at the tail of a memfile */
899    strstr->GBS_strcat_pos -= byte_count;
900    if (strstr->GBS_strcat_pos < 0) strstr->GBS_strcat_pos = 0;
901    strstr->GBS_strcat_data[strstr->GBS_strcat_pos] = 0;
902}
903
904static void gbs_strensure_mem(struct GBS_strstruct *strstr,long len) {
905    if (strstr->GBS_strcat_pos + len + 2 >= strstr->GBS_strcat_data_size) {
906        strstr->GBS_strcat_data_size = (strstr->GBS_strcat_pos+len+2)*3/2;
907        strstr->GBS_strcat_data      = (char *)realloc((MALLOC_T)strstr->GBS_strcat_data,(size_t)strstr->GBS_strcat_data_size);
908#if defined(DUMP_STRSTRUCT_MEMUSE)
909        printf("re-allocated GBS_strstruct to size = %li\n", strstr->GBS_strcat_data_size);
910#endif /* DUMP_STRSTRUCT_MEMUSE */
911    }
912}
913
914void GBS_strncat(struct GBS_strstruct *strstr, const char *ptr, size_t len) {
915    /* append some bytes string to strstruct
916     * (caution : copies zero byte and things behind!)
917     */
918    if (len>0) {
919        gbs_strensure_mem(strstr,len+2);
920        memcpy(strstr->GBS_strcat_data+strstr->GBS_strcat_pos,ptr, len);
921        strstr->GBS_strcat_pos += len;
922        strstr->GBS_strcat_data[strstr->GBS_strcat_pos] = 0;
923    }
924}
925
926void GBS_strcat(struct GBS_strstruct *strstr, const char *ptr) {
927    /* append string to strstruct */
928    GBS_strncat(strstr, ptr, strlen(ptr));
929}
930
931
932
933void GBS_strnprintf(struct GBS_strstruct *strstr, long len, const char *templat, ...) {
934    /* goes to header: __ATTR__FORMAT(3)  */
935    char    *buffer;
936    int      psize;
937    va_list  parg;
938   
939    va_start(parg,templat);
940    gbs_strensure_mem(strstr,len+2);
941
942    buffer = strstr->GBS_strcat_data+strstr->GBS_strcat_pos;
943
944#ifdef LINUX
945    psize = vsnprintf(buffer,len,templat,parg);
946#else
947    psize = vsprintf(buffer,templat,parg);
948#endif
949
950    assert_or_exit(psize >= 0 && psize <= len);
951    strstr->GBS_strcat_pos += psize;
952}
953
954void GBS_chrcat(struct GBS_strstruct *strstr,char ch) {
955    gbs_strensure_mem(strstr, 1);
956    strstr->GBS_strcat_data[strstr->GBS_strcat_pos++] = ch;
957    strstr->GBS_strcat_data[strstr->GBS_strcat_pos] = 0;
958}
959
960void GBS_intcat(struct GBS_strstruct *strstr,long val) {
961    char buffer[200];
962    long len = sprintf(buffer,"%li",val);
963    GBS_strncat(strstr, buffer, len);
964}
965
966void GBS_floatcat(struct GBS_strstruct *strstr,double val) {
967    char buffer[200];
968    long len = sprintf(buffer,"%f",val);
969    GBS_strncat(strstr, buffer, len);
970}
971
972char *GBS_eval_env(GB_CSTR p){
973    GB_ERROR  error = 0;
974    GB_CSTR   ka;
975    void     *out   = GBS_stropen(1000);
976
977    while ((ka = GBS_find_string(p,"$(",0))) {
978        GB_CSTR kz = strchr(ka,')');
979        if (!kz) {
980            error = GBS_global_string("missing ')' for envvar '%s'", p);
981            break;
982        }
983        else {
984            char *envvar = GB_strpartdup(ka+2, kz-1);
985            int   len    = ka-p;
986
987            if (len) GBS_strncat(out, p, len);
988
989            GB_CSTR genv = GB_getenv(envvar);
990            if (genv) GBS_strcat(out, genv);
991
992            p = kz+1;
993            free(envvar);
994        }
995    }
996
997    if (error) {
998        GB_export_error(error);
999        free(GBS_strclose(out));
1000        return 0;
1001    }
1002
1003    GBS_strcat(out, p);         // copy rest
1004    return GBS_strclose(out);
1005}
1006
1007char *GBS_find_lib_file(const char *filename, const char *libprefix, int warn_when_not_found) {
1008    /* Searches files in current dir, $HOME, $ARBHOME/lib/libprefix */
1009
1010    char *result = 0;
1011
1012    if (GB_is_readablefile(filename)) {
1013        result = strdup(filename);
1014    }
1015    else {
1016        const char *slash = strrchr(filename, '/'); // look for last slash
1017
1018        if (slash && filename[0] != '.') { // have absolute path
1019            filename = slash+1; // only use filename part
1020            slash    = 0;
1021        }
1022
1023        const char *fileInHome = GB_concat_full_path(GB_getenvHOME(), filename);
1024
1025        if (fileInHome && GB_is_readablefile(fileInHome)) {
1026            result = strdup(fileInHome);
1027        }
1028        else {
1029            if (slash) filename = slash+1; // now use filename only, even if path starts with '.'
1030
1031            const char *fileInLib = GB_path_in_ARBLIB(libprefix, filename);
1032
1033            if (fileInLib && GB_is_readablefile(fileInLib)) {
1034                result = strdup(fileInLib);
1035            }
1036            else {
1037                if (warn_when_not_found) {
1038                    GB_warningf("Don't know where to find '%s'\n"
1039                                "  searched in '.'\n"
1040                                "  searched in $(HOME) (for '%s')\n"
1041                                "  searched in $(ARBHOME)/lib/%s (for '%s')\n", 
1042                                filename, fileInHome, libprefix, fileInLib);
1043                }
1044            }
1045        }
1046    }
1047
1048    return result;
1049}
1050
1051
1052
1053/* *******************************************************************************************
1054   some simple find procedures
1055********************************************************************************************/
1056
1057char **GBS_read_dir(const char *dir, const char *mask) {
1058    /* Return names of files in directory 'dir'.
1059     * Filter through 'mask':
1060     * - mask == NULL -> return all files
1061     * -      in format '/expr/' -> use regular expression (case sensitive)
1062     * - else it does a simple string match with wildcards ("?*")
1063     *
1064     * Result is a NULL terminated array of char* (sorted alphanumerically)
1065     * Use GBT_free_names() to free the result.
1066     *
1067     * In case of error, result is NULL and error is exported
1068     *
1069     * Special case: If 'dir' is the name of a file, return an array with file as only element
1070     */
1071
1072    gb_assert(dir);             // dir == NULL was allowed before 12/2008, forbidden now!
1073
1074    char  *fulldir   = nulldup(GB_get_full_path(dir));
1075    DIR   *dirstream = opendir(fulldir);
1076    char **names     = NULL;
1077
1078    if (!dirstream) {
1079        if (GB_is_readablefile(fulldir)) {
1080            names    = malloc(2*sizeof(*names));
1081            names[0] = strdup(fulldir);
1082            names[1] = NULL;
1083        }
1084        else {
1085            char *lslash = strrchr(fulldir, '/');
1086
1087            if (lslash) {
1088                char *name;
1089                lslash[0] = 0;
1090                name      = lslash+1;
1091                if (GB_is_directory(fulldir)) {
1092                    names = GBS_read_dir(fulldir, name);
1093                }
1094                lslash[0] = '/';
1095            }
1096
1097            if (!names) GB_export_errorf("can't read directory '%s'", fulldir);
1098        }
1099    }
1100    else {
1101        if (mask == NULL) mask = "*";
1102
1103        GBS_MATCHER *matcher = GBS_compile_matcher(mask, GB_MIND_CASE);
1104        if (matcher) {
1105            int allocated = 100;
1106            int entries   = 0;
1107            names         = malloc(100*sizeof(*names));
1108
1109            struct dirent *entry;
1110            while ((entry = readdir(dirstream)) != 0) {
1111                const char *name = entry->d_name;
1112
1113                if (name[0] == '.' && (name[1] == 0 || (name[1] == '.' && name[2] == 0))) {
1114                    ; // skip '.' and '..'
1115                }
1116                else {
1117                    if (GBS_string_matches_regexp(name, matcher)) {
1118                        const char *full = GB_concat_path(fulldir, name);
1119                        if (!GB_is_directory(full)) { // skip directories
1120                            if (entries == allocated) {
1121                                allocated += allocated>>1; // * 1.5
1122                                names      = realloc(names, allocated*sizeof(*names));
1123                            }
1124                            names[entries++] = strdup(full);
1125                        }
1126                    }
1127                }
1128            }
1129
1130            names          = realloc(names, (entries+1)*sizeof(*names));
1131            names[entries] = NULL;
1132
1133            GB_sort((void**)names, 0, entries, GB_string_comparator, 0);
1134
1135            GBS_free_matcher(matcher);
1136        }
1137
1138        closedir(dirstream);
1139    }
1140
1141    free(fulldir);
1142
1143    return names; 
1144}
1145
1146long GBS_gcgchecksum( const char *seq )
1147/* GCGchecksum */
1148{
1149    long i;
1150    long check  = 0;
1151    long count  = 0;
1152    long seqlen = strlen(seq);
1153
1154    for (i = 0; i < seqlen; i++) {
1155        count++;
1156        check += count * toupper(seq[i]);
1157        if (count == 57) count = 0;
1158    }
1159    check %= 10000;
1160
1161    return check;
1162}
1163
1164/* Table of CRC-32's of all single byte values (made by makecrc.c of ZIP source) */
1165const uint32_t crctab[] = {
1166    0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L,
1167    0x706af48fL, 0xe963a535L, 0x9e6495a3L, 0x0edb8832L, 0x79dcb8a4L,
1168    0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L,
1169    0x90bf1d91L, 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL,
1170    0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, 0x136c9856L,
1171    0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L,
1172    0xfa0f3d63L, 0x8d080df5L, 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L,
1173    0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL,
1174    0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L,
1175    0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, 0x26d930acL, 0x51de003aL,
1176    0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L,
1177    0xb8bda50fL, 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L,
1178    0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, 0x76dc4190L,
1179    0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL,
1180    0x9fbfe4a5L, 0xe8b8d433L, 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL,
1181    0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L,
1182    0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL,
1183    0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, 0x65b0d9c6L, 0x12b7e950L,
1184    0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L,
1185    0xfbd44c65L, 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L,
1186    0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, 0x4369e96aL,
1187    0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L,
1188    0xaa0a4c5fL, 0xdd0d7cc9L, 0x5005713cL, 0x270241aaL, 0xbe0b1010L,
1189    0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL,
1190    0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L,
1191    0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, 0xedb88320L, 0x9abfb3b6L,
1192    0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L,
1193    0x73dc1683L, 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L,
1194    0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, 0xf00f9344L,
1195    0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL,
1196    0x196c3671L, 0x6e6b06e7L, 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL,
1197    0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L,
1198    0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L,
1199    0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, 0xd80d2bdaL, 0xaf0a1b4cL,
1200    0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL,
1201    0x4669be79L, 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L,
1202    0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, 0xc5ba3bbeL,
1203    0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L,
1204    0x2cd99e8bL, 0x5bdeae1dL, 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL,
1205    0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L,
1206    0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL,
1207    0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, 0x86d3d2d4L, 0xf1d4e242L,
1208    0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L,
1209    0x18b74777L, 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL,
1210    0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, 0xa00ae278L,
1211    0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L,
1212    0x4969474dL, 0x3e6e77dbL, 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L,
1213    0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L,
1214    0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L,
1215    0xcdd70693L, 0x54de5729L, 0x23d967bfL, 0xb3667a2eL, 0xc4614ab8L,
1216    0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL,
1217    0x2d02ef8dL
1218};
1219
1220uint32_t GB_checksum(const char *seq, long length, int ignore_case , const char *exclude) /* RALF: 02-12-96 */
1221     /*
1222      * CRC32checksum: modified from CRC-32 algorithm found in ZIP compression source
1223      * if ignore_case == true -> treat all characters as uppercase-chars (applies to exclude too)
1224      */
1225{
1226    unsigned long c = 0xffffffffL;
1227    long   n = length;
1228    int    i;
1229    int tab[256];
1230
1231    for (i=0;i<256;i++) {
1232        tab[i] = ignore_case ? toupper(i) : i;
1233    }
1234
1235    if (exclude) {
1236        while (1) {
1237            int k  = *(unsigned char *)exclude++;
1238            if (!k) break;
1239            tab[k] = 0;
1240            if (ignore_case) tab[toupper(k)] = tab[tolower(k)] = 0;
1241        }
1242    }
1243
1244    while (n--) {
1245        i = tab[*(const unsigned char *)seq++];
1246        if (i) {
1247            c = crctab[((int) c ^ i) & 0xff] ^ (c >> 8);
1248        }
1249    }
1250    c = c ^ 0xffffffffL;
1251    return c;
1252}
1253
1254uint32_t GBS_checksum(const char *seq, int ignore_case, const char *exclude)
1255     /* if 'ignore_case' == true -> treat all characters as uppercase-chars (applies to 'exclude' too) */
1256{
1257    return GB_checksum(seq, strlen(seq), ignore_case, exclude);
1258}
1259
1260/* extract all words in a text that:
1261    1. minlen < 1.0  contain more than minlen*len_of_text characters that also exists in chars
1262    2. minlen > 1.0  contain more than minlen characters that also exists in chars
1263*/
1264
1265char *GBS_extract_words( const char *source,const char *chars, float minlen, GB_BOOL sort_output ) {
1266    char  *s         = strdup(source);
1267    char **ps        = (char **)GB_calloc(sizeof(char *), (strlen(source)>>1) + 1);
1268    void  *strstruct = GBS_stropen(1000);
1269    char  *f         = s;
1270    int    count     = 0;
1271    char  *p;
1272    char  *h;
1273    int    cnt;
1274    int    len;
1275    int    iminlen   = (int) (minlen+.5);
1276   
1277    while ( (p = strtok(f," \t,;:|")) ) {
1278        f = 0;
1279        cnt = 0;
1280        len = strlen(p);
1281        for (h=p;*h;h++) {
1282            if (strchr(chars,*h)) cnt++;
1283        }
1284
1285        if (minlen == 1.0) {
1286            if (cnt != len) continue;
1287        }else  if (minlen > 1.0) {
1288            if (cnt < iminlen) continue;
1289        }else{
1290            if (len < 3 || cnt < minlen*len) continue;
1291        }
1292        ps[count] = p;
1293        count ++;
1294    }
1295    if (sort_output) {
1296        GB_sort((void **)ps, 0, count, GB_string_comparator, 0);
1297    }
1298    for (cnt = 0;cnt<count;cnt++) {
1299        if (cnt) {
1300            GBS_chrcat(strstruct,' ');
1301        }
1302        GBS_strcat(strstruct,ps[cnt]);
1303    }
1304
1305    free((char *)ps);
1306    free(s);
1307    return GBS_strclose(strstruct);
1308}
1309
1310
1311size_t GBS_shorten_repeated_data(char *data) {
1312    // shortens repeats in 'data'
1313    // This function modifies 'data'!!
1314    // e.g. "..............................ACGT....................TGCA"
1315    // ->   ".{30}ACGT.{20}TGCA"
1316
1317#if defined(DEBUG)
1318    size_t  orgLen    = strlen(data);
1319#endif // DEBUG
1320    char   *dataStart = data;
1321    char   *dest      = data;
1322    size_t  repeat    = 1;
1323    char    last      = *data++;
1324
1325    do {
1326        char curr = *data++;
1327        if (curr == last) {
1328            repeat++;
1329        }
1330        else {
1331            if (repeat >= 10) {
1332                dest += sprintf(dest, "%c{%zu}", last, repeat); // insert repeat count
1333            }
1334            else {
1335                size_t r;
1336                for (r = 0; r<repeat; r++) *dest++ = last; // insert plain
1337            }
1338            last   = curr;
1339            repeat = 1;
1340        }
1341    }
1342    while (last);
1343
1344    *dest = 0;
1345
1346#if defined(DEBUG)
1347
1348    ad_assert(strlen(dataStart) <= orgLen);
1349#endif // DEBUG
1350    return dest-dataStart;
1351}
1352
1353/* ----------------------- */
1354/*      Error handler      */
1355
1356static void gb_error_to_stderr(const char *msg) {
1357    fprintf(stderr, "%s\n", msg);
1358}
1359
1360static gb_error_handler_type gb_error_handler = gb_error_to_stderr;
1361
1362NOT4PERL void GB_install_error_handler(gb_error_handler_type aw_message_handler){
1363    gb_error_handler = aw_message_handler;
1364}
1365
1366/* --------------------- */
1367/*      Backtracing      */
1368
1369#define MAX_BACKTRACE 66
1370
1371void GBK_dump_backtrace(FILE *out, GB_ERROR error) {
1372    void   *array[MAX_BACKTRACE];
1373    size_t  size = backtrace(array, MAX_BACKTRACE); // get void*'s for all entries on the stack
1374
1375    if (!out) out = stderr;
1376
1377    // print out all the frames to out
1378    fprintf(out, "\n-------------------- ARB-backtrace for '%s':\n", error);
1379    backtrace_symbols_fd(array, size, fileno(out));
1380    if (size == MAX_BACKTRACE) fputs("[stack truncated to avoid deadlock]\n", out);
1381    fputs("-------------------- End of backtrace\n", out);
1382    fflush(out);
1383}
1384
1385/* ----------------------- */
1386/*      catch SIGSEGV      */
1387
1388static void sigsegv_handler_exit(int sig) {
1389    fprintf(stderr, "[Terminating with signal %i]\n", sig);
1390    exit(EXIT_FAILURE);
1391}
1392void sigsegv_handler_dump(int sig) {
1393    /* sigsegv_handler is intentionally global, to show it in backtrace! */
1394    GBK_dump_backtrace(stderr, GBS_global_string("received signal %i", sig));
1395    sigsegv_handler_exit(sig);
1396}
1397
1398void GBK_install_SIGSEGV_handler(GB_BOOL install) {
1399    signal(SIGSEGV, install ? sigsegv_handler_dump : sigsegv_handler_exit);
1400}
1401
1402/* ------------------------------------------- */
1403/*      Error/notification functions           */
1404
1405void GB_internal_error(const char *message)  {
1406    /* Use GB_internal_error, when something goes badly wrong
1407     * but you want to give the user a chance to save his database
1408     *
1409     * Note: it's NOT recommended to use this function!
1410     */
1411
1412    char *full_message = GBS_global_string_copy("Internal ARB Error: %s", message);
1413    gb_error_handler(full_message);
1414    gb_error_handler("ARB is most likely unstable now (due to this error).\n"
1415                     "If you've made changes to the database, consider to save it using a different name.\n"
1416                     "Try to fix the cause of the error and restart ARB.");
1417
1418#ifdef ASSERTION_USED
1419    fputs(full_message, stderr);
1420    gb_assert(0);               // internal errors shall not happen, go fix it
1421#else
1422    GBK_dump_backtrace(stderr, full_message);
1423#endif
1424
1425    free(full_message);
1426}
1427
1428void GB_internal_errorf(const char *templat, ...) {
1429    /* goes to header: __ATTR__FORMAT(1)  */
1430    va_list parg;
1431
1432    va_start(parg, templat);
1433    GB_CSTR message = gbs_vglobal_string(templat, parg, 0);
1434    va_end(parg);
1435
1436    GB_internal_error(message);
1437}
1438
1439void GBK_terminate(const char *error) {
1440
1441    /* GBK_terminate is the emergency exit!
1442     * only used if no other way to recover
1443     */
1444   
1445    fprintf(stderr, "Error: '%s'\n", error);
1446    fputs("Can't continue - terminating..\n", stderr);
1447    GBK_dump_backtrace(stderr, "GBK_terminate");
1448
1449    fflush(stderr);
1450    ARB_SIGSEGV(0); // GBK_terminate shall not be called, fix reason for call (this will crash in RELEASE version)
1451    exit(EXIT_FAILURE);
1452}
1453
1454void GBK_terminatef(const char *templat, ...) {
1455    /* goes to header: __ATTR__FORMAT(1)  */
1456    va_list parg;
1457
1458    va_start(parg,templat);
1459    GB_CSTR error = gbs_vglobal_string(templat, parg, 0);
1460    va_end(parg);
1461
1462    GBK_terminate(error);
1463}
1464
1465GB_ERROR GBK_assert_msg(const char *assertion, const char *file, int linenr) {
1466#define BUFSIZE 1000
1467    static char *buffer   = 0;
1468    const char  *result   = 0;
1469    int          old_size = last_global_string_size;
1470
1471    if (!buffer) buffer = (char *)malloc(BUFSIZE);
1472    result              = GBS_global_string_to_buffer(buffer, BUFSIZE, "assertion '%s' failed in %s #%i", assertion, file, linenr);
1473
1474    last_global_string_size = old_size;
1475
1476    return result;
1477#undef BUFSIZE
1478}
1479
1480void GB_warning(const char *message) {
1481    /* If program uses GUI, the message is printed via aw_message, otherwise it goes to stdout
1482     * see also : GB_information
1483     */
1484
1485    if (gb_warning_func) {
1486        gb_warning_func(message);
1487    }
1488    else {
1489        fputs(message, stdout);
1490        fputc('\n', stdout);
1491    }
1492}
1493void GB_warningf(const char *templat, ...) {
1494    /* goes to header: __ATTR__FORMAT(1)  */
1495
1496    va_list parg;
1497
1498    va_start(parg,templat);
1499    char *message = gbs_vglobal_string_copy(templat, parg);
1500    va_end(parg);
1501
1502    GB_warning(message);
1503    free(message);
1504}
1505
1506NOT4PERL void GB_install_warning(gb_warning_func_type warn){
1507    gb_warning_func = warn;
1508}
1509
1510void GB_information(const char *message) {
1511    if (gb_information_func) {
1512        gb_information_func(message);
1513    }
1514    else {
1515        fputs(message, stdout);
1516        fputc('\n', stdout);
1517    }
1518}
1519void GB_informationf(const char *templat, ...) {   
1520    /* goes to header: __ATTR__FORMAT(1)  */
1521
1522    /* this message is always printed to stdout (regardless whether program uses GUI or not)
1523     * see also : GB_warning
1524     */
1525
1526    va_list parg;
1527
1528    va_start(parg,templat);
1529    char *message = gbs_vglobal_string_copy(templat, parg);
1530    va_end(parg);
1531
1532    GB_information(message);
1533    free(message);
1534}
1535
1536NOT4PERL void GB_install_information(gb_information_func_type info){
1537    gb_information_func = info;
1538}
1539
1540
1541int GB_status( double val) {
1542    int result = 0;
1543
1544    /* if program uses GUI this uses aw_status(),
1545     * otherwise it uses simple status to stdout
1546     *
1547     * return value : 0 = ok, 1 = userAbort
1548     */
1549
1550    if ( gb_status_func ) {
1551        result = gb_status_func(val);
1552    }
1553    else {
1554        char buffer[100];
1555        int i;
1556        static int lastv = 0;
1557        int v = (int)(val*80);
1558        if (v == lastv) return 0;
1559        lastv = v;
1560        for (i=0;i<v;i++) buffer[i] = '+';
1561        for (;i<80;i++) buffer[i] = '-';
1562        buffer[i] = 0;
1563        fprintf(stdout,"%s\n",buffer);
1564    }
1565    return result;
1566}
1567
1568NOT4PERL void GB_install_status(gb_status_func_type func){
1569    gb_status_func = func;
1570}
1571
1572
1573int GB_status2( const char *templat, ... ) {
1574    /* goes to header: __ATTR__FORMAT(1)  */
1575   
1576    /* return value : 0 = ok, 1 = userAbort */
1577
1578    va_list parg;
1579
1580    if ( gb_status_func2 ) {
1581        char    buffer[4000];memset(&buffer[0],0,4000);
1582        va_start(parg,templat);
1583        vsprintf(buffer,templat,parg);
1584        return gb_status_func2(buffer);
1585    }else{
1586        va_start(parg,templat);
1587        vfprintf(stdout,templat,parg);
1588        fprintf(stdout,"\n");
1589        return 0;
1590    }
1591}
1592
1593NOT4PERL void GB_install_status2(gb_status_func2_type func2){
1594    gb_status_func2 = func2;
1595}
1596
1597/* ------------------------------------------- */
1598/*      helper function for tagged fields      */
1599
1600static GB_ERROR g_bs_add_value_tag_to_hash(GBDATA *gb_main, GB_HASH *hash, char *tag, char *value,const char *rtag, const char *srt, const char *aci, GBDATA *gbd) {
1601    char    *p;
1602    GB_HASH *sh;
1603    char    *to_free = 0;
1604    if (rtag && strcmp(tag,rtag) == 0){ 
1605        if (srt) {
1606            value = to_free = GBS_string_eval(value,srt,gbd);
1607        }
1608        else if (aci) {
1609            value = to_free = GB_command_interpreter(gb_main,value,aci,gbd, 0);
1610        }
1611        if (!value) return GB_await_error();
1612    }
1613
1614    p=value; while ( (p = strchr(p,'[')) ) *p =  '{'; /* replace all '[' by '{' */
1615    p=value; while ( (p = strchr(p,']')) ) *p =  '}'; /* replace all ']' by '}' */
1616
1617    sh = (GB_HASH *)GBS_read_hash(hash,value);
1618    if (!sh){
1619        sh = GBS_create_hash(10, GB_IGNORE_CASE); /* Tags are case independent */
1620        GBS_write_hash(hash,value,(long)sh);
1621    }
1622
1623    GBS_write_hash(sh,tag,1);
1624    if (to_free) free(to_free);
1625    return 0;
1626}
1627
1628
1629static GB_ERROR g_bs_convert_string_to_tagged_hash(GB_HASH *hash, char *s,char *default_tag,const char *del,
1630                                                   GBDATA *gb_main, const char *rtag,const char *srt, const char *aci, GBDATA *gbd){
1631    char *se;           /* string end */
1632    char *sa;           /* string anfang and tag end */
1633    char *ts;           /* tag start */
1634    char *t;
1635    GB_ERROR error = 0;
1636    while (s && s[0]) {
1637        ts = strchr(s,'[');
1638        if (!ts){
1639            error = g_bs_add_value_tag_to_hash(gb_main,hash,default_tag,s,rtag,srt,aci,gbd); /* no tag found, use default tag */
1640            if (error) break;
1641            break;
1642        }else{
1643            *(ts++) = 0;
1644        }
1645        sa = strchr(ts,']');
1646        if (sa){
1647            *sa++ = 0;
1648            while (*sa == ' ') sa++;
1649        }else{
1650            error = g_bs_add_value_tag_to_hash(gb_main,hash,default_tag,s,rtag,srt,aci,gbd); /* no tag found, use default tag */
1651            if (error) break;
1652            break;
1653        }
1654        se = strchr(sa,'[');
1655        if (se) {
1656            while (se>sa && se[-1] == ' ') se--;
1657            *(se++) = 0;
1658        }
1659        for (t = strtok(ts,","); t; t = strtok(0,",")){
1660            if (del && strcmp(t,del) == 0) continue; /* test, whether to delete */
1661            if (sa[0] == 0) continue;
1662            error = g_bs_add_value_tag_to_hash(gb_main,hash,t,sa,rtag,srt,aci,gbd); /* tag found, use  tag */
1663            if (error) break;
1664        }
1665        s = se;
1666    }
1667    return error;
1668}
1669
1670static long g_bs_merge_tags(const char *tag, long val, void *cd_sub_result) {
1671    struct GBS_strstruct *sub_result = (struct GBS_strstruct*)cd_sub_result;
1672
1673    GBS_strcat(sub_result, tag);
1674    GBS_strcat(sub_result, ",");
1675
1676    return val;
1677}
1678
1679static long g_bs_read_tagged_hash(const char *value, long subhash, void *cd_g_bs_collect_tags_hash) {
1680    char                 *str;
1681    static int            counter    = 0;
1682    struct GBS_strstruct *sub_result = GBS_stropen(100);
1683       
1684    GBS_hash_do_sorted_loop((GB_HASH *)subhash, g_bs_merge_tags, GBS_HCF_sortedByKey, sub_result);
1685    GBS_intcat(sub_result, counter++); /* create a unique number */
1686
1687    str = GBS_strclose(sub_result);
1688
1689    GB_HASH *g_bs_collect_tags_hash = (GB_HASH*)cd_g_bs_collect_tags_hash;
1690    GBS_write_hash(g_bs_collect_tags_hash, str,(long)strdup(value)); /* send output to new hash for sorting */
1691
1692    free(str);
1693    return 0;
1694}
1695
1696static long g_bs_read_final_hash(const char *tag, long value, void *cd_merge_result) {
1697    struct GBS_strstruct *merge_result = (struct GBS_strstruct*)cd_merge_result;
1698       
1699    char *lk = strrchr(tag,',');
1700    if (lk) {           /* remove number at end */
1701        *lk = 0;
1702        GBS_strcat(merge_result, " [");
1703        GBS_strcat(merge_result, tag);
1704        GBS_strcat(merge_result, "] ");
1705    }
1706    GBS_strcat(merge_result,(char *)value);
1707    return value;
1708}
1709
1710static char *g_bs_get_string_of_tag_hash(GB_HASH *tag_hash){
1711    struct GBS_strstruct *merge_result      = GBS_stropen(256);
1712    GB_HASH              *collect_tags_hash = GBS_create_dynaval_hash(1024, GB_IGNORE_CASE, GBS_dynaval_free);
1713
1714    GBS_hash_do_sorted_loop(tag_hash, g_bs_read_tagged_hash, GBS_HCF_sortedByKey, collect_tags_hash);     /* move everything into collect_tags_hash */
1715    GBS_hash_do_sorted_loop(collect_tags_hash, g_bs_read_final_hash, GBS_HCF_sortedByKey, merge_result);
1716
1717    GBS_free_hash(collect_tags_hash);
1718    return GBS_strclose(merge_result);
1719}
1720
1721long g_bs_free_hash_of_hashes_elem(const char *key, long val, void *dummy) {
1722    GB_HASH *hash = (GB_HASH*)val;
1723    GBUSE(key);
1724    GBUSE(dummy);
1725    if (hash) GBS_free_hash(hash);
1726    return 0;
1727}
1728static void g_bs_free_hash_of_hashes(GB_HASH *hash) {
1729    GBS_hash_do_loop(hash, g_bs_free_hash_of_hashes_elem, NULL);
1730    GBS_free_hash(hash);
1731}
1732
1733char *GBS_merge_tagged_strings(const char *s1, const char *tag1, const char *replace1, const char *s2, const char *tag2, const char *replace2){
1734    /* Create a tagged string from two tagged strings:
1735     * a tagged string is somthing like '[tag,tag,tag] string [tag] string [tag,tag] string'
1736     *
1737     * if 's2' is not empty, then delete tag 'replace1' in 's1'
1738     * if 's1' is not empty, then delete tag 'replace2' in 's2'
1739     *
1740     * if result is NULL, an error has been exported.
1741     */
1742
1743    char     *str1   = strdup(s1);
1744    char     *str2   = strdup(s2);
1745    char     *t1     = GBS_string_2_key(tag1);
1746    char     *t2     = GBS_string_2_key(tag2);
1747    char     *result = 0;
1748    GB_ERROR  error  = 0;
1749    GB_HASH  *hash   = GBS_create_hash(16, GB_MIND_CASE);
1750
1751    if (!strlen(s1)) replace2 = 0;
1752    if (!strlen(s2)) replace1 = 0;
1753
1754    if (replace1 && replace1[0] == 0) replace1 = 0;
1755    if (replace2 && replace2[0] == 0) replace2 = 0;
1756
1757    error              = g_bs_convert_string_to_tagged_hash(hash,str1,t1,replace1,0,0,0,0,0);
1758    if (!error) error  = g_bs_convert_string_to_tagged_hash(hash,str2,t2,replace2,0,0,0,0,0);
1759
1760    if (!error) {
1761        result = g_bs_get_string_of_tag_hash(hash);
1762    }
1763    else {
1764        GB_export_error(error);
1765    }
1766
1767    g_bs_free_hash_of_hashes(hash);
1768
1769    free(t2);
1770    free(t1);
1771    free(str2);
1772    free(str1);
1773
1774    return result;
1775}
1776
1777char *GBS_string_eval_tagged_string(GBDATA *gb_main, const char *s, const char *dt, const char *tag, const char *srt, const char *aci, GBDATA *gbd) {
1778    /* if 's' is untagged, tag it with default tag 'dt'.
1779     * if 'tag' is != NULL -> apply 'srt' or 'aci' to that part of the  content of 's', which is tagged with 'tag'
1780     *
1781     * if result is NULL, an error has been exported.
1782     */
1783   
1784    char     *str         = strdup(s);
1785    char     *default_tag = GBS_string_2_key(dt);
1786    GB_HASH  *hash        = GBS_create_hash(16, GB_MIND_CASE);
1787    char     *result      = 0;
1788    GB_ERROR  error       = g_bs_convert_string_to_tagged_hash(hash,str,default_tag,0,gb_main,tag,srt,aci,gbd);
1789
1790    if (!error) {
1791        result = g_bs_get_string_of_tag_hash(hash);
1792    }
1793    else {
1794        GB_export_error(error);
1795    }
1796
1797    g_bs_free_hash_of_hashes(hash);
1798    free(default_tag);
1799    free(str);
1800   
1801    return result;
1802}
1803
1804
1805char *GB_read_as_tagged_string(GBDATA *gbd, const char *tagi){
1806    char *s;
1807    char *tag;
1808    char *buf;
1809    char *se;           /* string end */
1810    char *sa;           /* string anfang and tag end */
1811    char *ts;           /* tag start */
1812    char *t;
1813
1814    buf = s = GB_read_as_string(gbd);
1815    if (!s) return s;
1816    if (!tagi) return s;
1817    if (!strlen(tagi)) return s;
1818
1819    tag = GBS_string_2_key(tagi);
1820
1821    while(s){
1822        ts = strchr(s,'[');
1823        if (!ts)    goto notfound;      /* no tag */
1824
1825        *(ts++) = 0;
1826
1827        sa = strchr(ts,']');
1828        if (!sa) goto notfound;
1829
1830        *sa++ = 0;
1831        while (*sa == ' ') sa++;
1832
1833        se = strchr(sa,'[');
1834        if (se) {
1835            while (se>sa && se[-1] == ' ') se--;
1836            *(se++) = 0;
1837        }
1838        for (t = strtok(ts,","); t; t = strtok(0,",")){
1839            if (strcmp(t,tag) == 0) {
1840                s = strdup(sa);
1841                free(buf);
1842                goto found;
1843            }
1844        }
1845        s = se;
1846    }
1847 notfound:
1848    /* Nothing found */
1849    free(buf);
1850    s = 0;
1851 found:
1852    free(tag);
1853    return s;
1854}
1855
1856
1857/* be CAREFUL : this function is used to save ARB ASCII database (i.e. properties)
1858 * used as well to save perl macros
1859 *
1860 * when changing GBS_fwrite_string -> GBS_fread_string needs to be fixed as well
1861 *
1862 * always keep in mind, that many users have databases/macros written with older
1863 * versions of this function. They MUST load proper!!!
1864 */
1865void GBS_fwrite_string(const char *strngi,FILE *out){
1866    unsigned char *strng = (unsigned char *)strngi;
1867    int            c;
1868   
1869    putc('"',out);
1870
1871    while ( (c= *strng++) ) {
1872        if (c < 32) {
1873            putc('\\',out);
1874            if (c == '\n')
1875                putc('n',out);
1876            else if (c == '\t')
1877                putc('t',out);
1878            else if ( c<25 ) {
1879                putc(c+'@',out); /* characters ASCII 0..24 encoded as \@..\X    (\n and \t are done above) */
1880            }else{
1881                putc(c+('0'-25),out);/* characters ASCII 25..31 encoded as \0..\6 */
1882            }
1883        }else if (c == '"'){
1884            putc('\\',out);
1885            putc('"',out);
1886        }else if (c == '\\'){
1887            putc('\\',out);
1888            putc('\\',out);
1889        }else{
1890            putc(c,out);
1891        }
1892    }
1893    putc('"',out);
1894}
1895
1896/*  Read a string from a file written by GBS_fwrite_string,
1897 *  Searches first '"'
1898 *
1899 *  WARNING : changing this function affects perl-macro execution (read warnings for GBS_fwrite_string)
1900 *  any changes should be done in GBS_fconvert_string too.
1901 */
1902
1903char *GBS_fread_string(FILE *in) {
1904    void *strstr = GBS_stropen(1024);
1905    int   x;
1906
1907    while ((x = getc(in)) != '"' ) if (x == EOF) break; /* Search first '"' */
1908
1909    if (x != EOF) {
1910        while ((x = getc(in)) != '"' ){
1911            if (x == EOF) break;
1912            if (x == '\\'){
1913                x = getc(in); if (x==EOF) break;
1914                if (x == 'n') {
1915                    GBS_chrcat(strstr,'\n');
1916                    continue;
1917                }
1918                if (x == 't') {
1919                    GBS_chrcat(strstr,'\t');
1920                    continue;
1921                }
1922                if (x>='@' && x <='@'+ 25) {
1923                    GBS_chrcat(strstr,x-'@');
1924                    continue;
1925                }
1926                if (x>='0' && x <='9') {
1927                    GBS_chrcat(strstr,x-('0'-25));
1928                    continue;
1929                }
1930                /* all other backslashes are simply skipped */
1931            }
1932            GBS_chrcat(strstr,x);
1933        }
1934    }
1935    return GBS_strclose(strstr);
1936}
1937
1938/* does similiar decoding as GBS_fread_string but works directly on an existing buffer
1939 * (WARNING : GBS_fconvert_string is used by gb_read_file which reads ARB ASCII databases!!)
1940 *
1941 * inserts \0 behind decoded string (removes the closing '"')
1942 * returns a pointer behind the end (") of the _encoded_ string
1943 * returns NULL if a 0-character is found
1944 */
1945char *GBS_fconvert_string(char *buffer) {
1946    char *t = buffer;
1947    char *f = buffer;
1948    int   x;
1949
1950    ad_assert(f[-1] == '"');
1951    /* the opening " has already been read */
1952
1953    while ((x = *f++) != '"') {
1954        if (!x) break;
1955
1956        if (x == '\\') {
1957            x = *f++;
1958            if (!x) break;
1959
1960            if (x == 'n') {
1961                *t++ = '\n';
1962                continue;
1963            }
1964            if (x == 't') {
1965                *t++ = '\t';
1966                continue;
1967            }
1968            if (x>='@' && x <='@'+ 25) {
1969                *t++ = x-'@';
1970                continue;
1971            }
1972            if (x>='0' && x <='9') {
1973                *t++ = x-('0'-25);
1974                continue;
1975            }
1976            /* all other backslashes are simply skipped */
1977        }
1978        *t++ = x;
1979    }
1980
1981    if (!x) return 0;           // error (string should not contain 0-character)
1982    ad_assert(x == '"');
1983
1984    t[0] = 0;
1985    return f;
1986}
1987
1988char *GBS_replace_tabs_by_spaces(const char *text){
1989    int tlen = strlen(text);
1990    void *mfile = GBS_stropen(tlen * 3/2);
1991    int tabpos = 0;
1992    int c;
1993    while ((c=*(text++))) {
1994        if (c == '\t'){
1995            int ntab = (tabpos + 8) & 0xfffff8;
1996            while(tabpos < ntab){
1997                GBS_chrcat(mfile,' ');
1998                tabpos++;
1999            }
2000            continue;
2001        }
2002        tabpos ++;
2003        if (c== '\n'){
2004            tabpos = 0;
2005        }
2006        GBS_chrcat(mfile,c);
2007    }
2008    return GBS_strclose(mfile);
2009}
2010
2011int GBS_strscmp(const char *s1, const char *s2) {
2012    int    cmp = 0;
2013    size_t idx = 0;
2014    while (!cmp) {
2015        if (!s1[idx] || !s2[idx]) break;
2016        cmp = s1[idx] - s2[idx];
2017        ++idx;
2018    }
2019    return cmp;
2020}
2021
2022const char *GBS_readable_size(unsigned long long size) {
2023    /* return human readable size information */
2024    /* returned string is maximal 7 characters long */
2025
2026    if (size<1000) return GBS_global_string("%llu b", size);
2027
2028    const char *units = "kMGTPEZY"; // kilo, Mega, Giga, Tera, ... should be enough forever
2029    int i;
2030
2031    for (i = 0; units[i]; ++i) {
2032        char unit = units[i];
2033        if (size<1000*1024) {
2034            double amount = size/(double)1024;
2035            if (amount<10.0)  return GBS_global_string("%4.2f %cb", amount+0.005, unit);
2036            if (amount<100.0) return GBS_global_string("%4.1f %cb", amount+0.05, unit);
2037            return GBS_global_string("%i %cb", (int)(amount+0.5), unit);
2038        }
2039        size /= 1024; // next unit
2040    }
2041    ad_assert(0);
2042    return "<much>";
2043}
2044
2045char *GBS_trim(const char *str) {
2046    // trim whitespace at beginning and end of 'str'
2047    const char *whitespace = " \t\n";
2048    while (str[0] && strchr(whitespace, str[0])) str++;
2049
2050    const char *end = strchr(str, 0)-1;
2051    while (end >= str && strchr(whitespace, end[0])) end--;
2052
2053    return GB_strpartdup(str, end);
2054}
Note: See TracBrowser for help on using the repository browser.