source: tags/arb-6.0-rc1/ARBDB/adsocket.cxx

Last change on this file was 11889, checked in by westram, 11 years ago
  • add missing comment to closing #endif of test-sections
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 56.4 KB
Line 
1// =============================================================== //
2//                                                                 //
3//   File      : adsocket.cxx                                      //
4//   Purpose   :                                                   //
5//                                                                 //
6//   Institute of Microbiology (Technical University Munich)       //
7//   http://www.arb-home.de/                                       //
8//                                                                 //
9// =============================================================== //
10
11#include <unistd.h>
12
13#include <climits>
14#include <cstdarg>
15#include <cctype>
16
17#include <netdb.h>
18#include <netinet/tcp.h>
19#include <signal.h>
20#include <sys/mman.h>
21#include <sys/socket.h>
22#include <sys/stat.h>
23#include <sys/time.h>
24#include <sys/un.h>
25
26#if defined(DARWIN)
27# include <sys/sysctl.h>
28#endif // DARWIN
29
30#include <arb_cs.h>
31#include <arb_str.h>
32#include <arb_strbuf.h>
33#include <arb_file.h>
34#include <arb_sleep.h>
35#include <arb_pathlen.h>
36
37#include "gb_comm.h"
38#include "gb_data.h"
39#include "gb_localdata.h"
40
41#include <SigHandler.h>
42
43#include <algorithm>
44#include <arb_misc.h>
45#include <arb_defs.h>
46
47static int gbcm_pipe_violation_flag = 0;
48void gbcms_sigpipe(int) {
49    gbcm_pipe_violation_flag = 1;
50}
51
52// ------------------------------------------------
53//      private read and write socket functions
54
55void gbcm_read_flush() {
56    gb_local->write_ptr  = gb_local->write_buffer;
57    gb_local->write_free = gb_local->write_bufsize;
58}
59
60static long gbcm_read_buffered(int socket, char *ptr, long size) {
61    /* write_ptr ptr to not read data
62       write_free   = write_bufsize-size of non read data;
63    */
64    long holding;
65    holding = gb_local->write_bufsize - gb_local->write_free;
66    if (holding <= 0) {
67        holding = read(socket, gb_local->write_buffer, (size_t)gb_local->write_bufsize);
68
69        if (holding < 0)
70        {
71            fprintf(stderr, "Cannot read data from client: len=%li (%s, errno %i)\n",
72                    holding, strerror(errno), errno);
73            return 0;
74        }
75        gbcm_read_flush();
76        gb_local->write_free-=holding;
77    }
78    if (size>holding) size = holding;
79    memcpy(ptr, gb_local->write_ptr, (int)size);
80    gb_local->write_ptr += size;
81    gb_local->write_free += size;
82    return size;
83}
84
85long gbcm_read(int socket, char *ptr, long size) {
86    long leftsize = size;
87    while (leftsize) {
88        long readsize = gbcm_read_buffered(socket, ptr, leftsize);
89        if (readsize<=0) return 0;
90        ptr += readsize;
91        leftsize -= readsize;
92    }
93
94    return size;
95}
96
97GBCM_ServerResult gbcm_write_flush(int socket) {
98    char *ptr      = gb_local->write_buffer;
99    long  leftsize = gb_local->write_ptr - ptr;
100
101    gb_local->write_free = gb_local->write_bufsize;
102
103    if (leftsize) {
104        gb_local->write_ptr = ptr;
105        gbcm_pipe_violation_flag = 0;
106
107        long writesize = write(socket, ptr, (size_t)leftsize);
108
109        if (gbcm_pipe_violation_flag || writesize<0) {
110            if (gb_local->iamclient) {
111                fprintf(stderr, "DB_Server is killed, Now I kill myself\n");
112                exit(-1);
113            }
114            fprintf(stderr, "writesize: %li ppid %i\n", writesize, getppid());
115            return GBCM_SERVER_FAULT;
116        }
117        ptr += writesize;
118        leftsize = leftsize - writesize;
119
120        while (leftsize) {
121            GB_sleep(10, MS);
122
123            writesize = write(socket, ptr, (size_t)leftsize);
124            if (gbcm_pipe_violation_flag || writesize<0) {
125                if ((int)getppid() <= 1) {
126                    fprintf(stderr, "DB_Server is killed, Now I kill myself\n");
127                    exit(-1);
128                }
129                fprintf(stderr, "write error\n");
130                return GBCM_SERVER_FAULT;
131            }
132            ptr += writesize;
133            leftsize -= writesize;
134        }
135    }
136    return GBCM_SERVER_OK;
137}
138
139GBCM_ServerResult gbcm_write(int socket, const char *ptr, long size) {
140    while (size >= gb_local->write_free) {
141        memcpy(gb_local->write_ptr, ptr, (int)gb_local->write_free);
142        gb_local->write_ptr += gb_local->write_free;
143        size -= gb_local->write_free;
144        ptr += gb_local->write_free;
145
146        gb_local->write_free = 0;
147        if (gbcm_write_flush(socket)) return GBCM_SERVER_FAULT;
148    }
149    memcpy(gb_local->write_ptr, ptr, (int)size);
150    gb_local->write_ptr += size;
151    gb_local->write_free -= size;
152    return GBCM_SERVER_OK;
153}
154
155static GB_ERROR gbcm_get_m_id(const char *path, char **m_name, long *id) {
156    // find the mach name and id
157    GB_ERROR error = 0;
158
159    if (!path) error = "missing hostname:socketid";
160    else {
161        if (strcmp(path, ":") == 0) {
162            path             = GBS_read_arb_tcp("ARB_DB_SERVER");
163            if (!path) error = GB_await_error();
164        }
165
166        if (!error) {
167            const char *p = strchr(path, ':');
168
169            if (!p) error = GBS_global_string("missing ':' in '%s'", path);
170            else {
171                if (path[0] == '*' || path[0] == ':') { // UNIX MODE
172                    *m_name = strdup(p+1);
173                    *id     = -1;
174                }
175                else {
176                    char *mn = GB_strpartdup(path, p-1);
177
178                    int i = atoi(p + 1);
179                    if ((i < 1) || (i > 4096)) {
180                        error = GBS_global_string("socketnumber %i not in [1..4096]", i);
181                    }
182
183                    if (!error) {
184                        *m_name = mn;
185                        *id     = i;
186                    }
187                    else {
188                        free(mn);
189                    }
190                }
191            }
192        }
193    }
194
195    if (error) error = GBS_global_string("OPEN_ARB_DB_CLIENT ERROR: %s", error);
196    return error;
197}
198
199GB_ERROR gbcm_open_socket(const char *path, long delay2, long do_connect, int *psocket, char **unix_name) {
200    char     *machName = 0;
201    long      socketID;
202    GB_ERROR  err      = gbcm_get_m_id(path, &machName, &socketID);
203    if (err) {
204        free(machName);
205        return err;
206    }
207
208    if (socketID >= 0) {    // TCP
209        sockaddr_in so_ad;
210        memset((char *)&so_ad, 0, sizeof(sockaddr_in));
211        *psocket = socket(PF_INET, SOCK_STREAM, 0);
212        if (*psocket <= 0) {
213            return "CANNOT CREATE SOCKET";
214        }
215
216        hostent *he;
217        arb_gethostbyname(machName, he, err);
218        if (err) return err;
219
220        in_addr addr;                                   // union -> u_long
221       
222        // simply take first address
223        addr.s_addr      = *(long *) (he->h_addr);
224        so_ad.sin_addr   = addr;
225        so_ad.sin_family = AF_INET;
226        so_ad.sin_port   = htons((unsigned short)socketID); // @@@ = pb_socket
227
228        if (do_connect) {
229            if (connect(*psocket, (sockaddr *)(&so_ad), sizeof(so_ad))) {
230                GB_warningf("Cannot connect to %s:%li   errno %i", machName, socketID, errno);
231                return "";
232            }
233        }
234        else {
235            int one = 1;
236            setsockopt(*psocket, SOL_SOCKET, SO_REUSEADDR, (const char *)&one, sizeof(one));
237            if (bind(*psocket, (sockaddr *)(&so_ad), sizeof(so_ad))) {
238                return "Could not open socket on Server";
239            }
240        }
241        free(machName);
242        if (delay2 == TCP_NODELAY) {
243            GB_UINT4      optval;
244            optval = 1;
245            setsockopt(*psocket, IPPROTO_TCP, TCP_NODELAY, (char *)&optval, sizeof (GB_UINT4));
246        }
247        *unix_name = 0;
248        return 0;
249    }
250    else {        // UNIX
251        sockaddr_un so_ad;
252        if (strlen(machName) >= sizeof(so_ad.sun_path)) {
253            return "Could not open socket on Server (socket name too long)";
254        }
255        memset((char *)&so_ad, 0, sizeof(so_ad));
256        *psocket = socket(PF_UNIX, SOCK_STREAM, 0);
257        if (*psocket <= 0) {
258            return "CANNOT CREATE SOCKET";
259        }
260        so_ad.sun_family = AF_UNIX;
261        strcpy(so_ad.sun_path, machName);
262
263        if (do_connect) {
264            if (connect(*psocket, (sockaddr*)(&so_ad), strlen(so_ad.sun_path)+2)) {
265                free(machName);
266                return "";
267            }
268        }
269        else {
270            if (unlink(machName) == 0) printf("old socket found\n");
271            if (bind(*psocket, (sockaddr*)(&so_ad), strlen(machName)+2)) {
272                free(machName);
273                return "Could not open socket on Server";
274            }
275            if (chmod(machName, 0777)) return GB_export_errorf("Cannot change mode of socket '%s'", machName);
276        }
277        *unix_name = machName;
278        return 0;
279    }
280}
281
282#if defined(WARN_TODO)
283#warning gbcms_close is unused
284#endif
285long gbcms_close(gbcmc_comm *link) {
286    if (link->socket) {
287        close(link->socket);
288        link->socket = 0;
289        if (link->unix_name) {
290            unlink(link->unix_name);
291        }
292    }
293    return 0;
294}
295
296static void gbcmc_suppress_sigpipe(int) {
297}
298
299inline bool is_default_or_ignore_or_own_sighandler(SigHandler sh) {
300    return sh == gbcmc_suppress_sigpipe || is_default_or_ignore_sighandler(sh);
301}
302
303gbcmc_comm *gbcmc_open(const char *path) {
304    gbcmc_comm *link = (gbcmc_comm *)GB_calloc(sizeof(gbcmc_comm), 1);
305    GB_ERROR    err  = gbcm_open_socket(path, TCP_NODELAY, 1, &link->socket, &link->unix_name);
306
307    if (err) {
308        if (link->unix_name) free(link->unix_name); // @@@
309        free(link);
310        if (*err) {
311            GB_internal_errorf("ARB_DB_CLIENT_OPEN\n(Reason: %s)", err);
312        }
313        return 0;
314    }
315    ASSERT_RESULT_PREDICATE(is_default_or_ignore_or_own_sighandler, INSTALL_SIGHANDLER(SIGPIPE, gbcmc_suppress_sigpipe, "gbcmc_open"));
316    gb_local->iamclient = true;
317    return link;
318}
319
320long gbcm_write_two(int socket, long a, long c)
321{
322    long    ia[3];
323    ia[0] = a;
324    ia[1] = 3;
325    ia[2] = c;
326    if (!socket) return 1;
327    return  gbcm_write(socket, (const char *)ia, sizeof(long)*3);
328}
329
330
331GBCM_ServerResult gbcm_read_two(int socket, long a, long *b, long *c) {
332    /*! read two values: length and any user long
333     *
334     *  if data is send by gbcm_write_two() then @param b should be zero
335     *  and is not used!
336     */
337
338    long    ia[3];
339    long    size;
340    size = gbcm_read(socket, (char *)&(ia[0]), sizeof(long)*3);
341    if (size != sizeof(long) * 3) {
342        GB_internal_errorf("receive failed: %zu bytes expected, %li got, keyword %lX",
343                           sizeof(long) * 3, size, a);
344        return GBCM_SERVER_FAULT;
345    }
346    if (ia[0] != a) {
347        GB_internal_errorf("received keyword failed %lx != %lx\n", ia[0], a);
348        return GBCM_SERVER_FAULT;
349    }
350    if (b) {
351        *b = ia[1];
352    }
353    else {
354        if (ia[1]!=3) {
355            GB_internal_error("receive failed: size not 3\n");
356            return GBCM_SERVER_FAULT;
357        }
358    }
359    *c = ia[2];
360    return GBCM_SERVER_OK;
361}
362
363GBCM_ServerResult gbcm_write_string(int socket, const char *key) {
364    if (key) {
365        size_t len = strlen(key);
366        gbcm_write_long(socket, len);
367        if (len) gbcm_write(socket, key, len);
368    }
369    else {
370        gbcm_write_long(socket, -1);
371    }
372    return GBCM_SERVER_OK;
373}
374
375char *gbcm_read_string(int socket)
376{
377    char *key;
378    long  len = gbcm_read_long(socket);
379
380    if (len) {
381        if (len>0) {
382            key = (char *)GB_calloc(sizeof(char), (size_t)len+1);
383            gbcm_read(socket, key, len);
384        }
385        else {
386            key = 0;
387        }
388    }
389    else {
390        key = strdup("");
391    }
392
393    return key;
394}
395
396GBCM_ServerResult gbcm_write_long(int socket, long data) {
397    gbcm_write(socket, (char*)&data, sizeof(data));
398    return GBCM_SERVER_OK;
399}
400
401long gbcm_read_long(int socket) {
402    long data;
403    gbcm_read(socket, (char*)&data, sizeof(data));
404    return data;
405}
406
407char *GB_read_fp(FILE *in) {
408    /*! like GB_read_file(), but works on already open file
409     * (useful together with GB_fopen_tempfile())
410     *
411     * Note: File should be opened in text-mode (e.g. "rt")
412     */
413
414    GBS_strstruct *buf = GBS_stropen(4096);
415    int            c;
416
417    while (EOF != (c = getc(in))) {
418        GBS_chrcat(buf, c);
419    }
420    return GBS_strclose(buf);
421}
422
423char *GB_read_file(const char *path) { // consider using class FileContent instead
424    /*! read content of file 'path' into string (heap-copy)
425     *
426     * if path is '-', read from STDIN
427     *
428     * @return NULL in case of error (use GB_await_error() to get the message)
429     */
430    char *result = 0;
431
432    if (strcmp(path, "-") == 0) {
433        result = GB_read_fp(stdin);
434    }
435    else {
436        char *epath = GBS_eval_env(path);
437
438        if (epath) {
439            FILE *in = fopen(epath, "rt");
440
441            if (!in) GB_export_error(GB_IO_error("reading", epath));
442            else {
443                long data_size = GB_size_of_file(epath);
444
445                if (data_size >= 0) {
446                    result = (char*)malloc(data_size+1);
447
448                    data_size         = fread(result, 1, data_size, in);
449                    result[data_size] = 0;
450                }
451                fclose(in);
452            }
453        }
454        free(epath);
455    }
456    return result;
457}
458
459char *GB_map_FILE(FILE *in, int writeable) {
460    int fi = fileno(in);
461    size_t size = GB_size_of_FILE(in);
462    char *buffer;
463    if (size<=0) {
464        GB_export_error("GB_map_file: sorry file not found");
465        return NULL;
466    }
467    if (writeable) {
468        buffer = (char*)mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fi, 0);
469    }
470    else {
471        buffer = (char*)mmap(NULL, size, PROT_READ, MAP_SHARED, fi, 0);
472    }
473    if (buffer == MAP_FAILED) {
474        GB_export_errorf("GB_map_file: Error: Out of Memory: mmap failed (errno: %i)", errno);
475        return NULL;
476    }
477    return buffer;
478}
479
480char *GB_map_file(const char *path, int writeable) {
481    FILE *in;
482    char *buffer;
483    in = fopen(path, "r");
484    if (!in) {
485        GB_export_errorf("GB_map_file: sorry file '%s' not readable", path);
486        return NULL;
487    }
488    buffer = GB_map_FILE(in, writeable);
489    fclose(in);
490    return buffer;
491}
492
493GB_ULONG GB_time_of_day() {
494    timeval tp;
495    if (gettimeofday(&tp, 0)) return 0;
496    return tp.tv_sec;
497}
498
499GB_ERROR GB_textprint(const char *path) {
500    // goes to header: __ATTR__USERESULT
501    char       *fpath   = GBS_eval_env(path);
502    const char *command = GBS_global_string("arb_textprint '%s' &", fpath);
503    GB_ERROR    error   = GBK_system(command);
504    error               = GB_failedTo_error("print textfile", fpath, error);
505    free(fpath);
506    return error;
507}
508
509// --------------------------------------------------------------------------------
510
511static GB_CSTR getenv_ignore_empty(GB_CSTR envvar) {
512    GB_CSTR result = getenv(envvar);
513    return (result && result[0]) ? result : 0;
514}
515
516static GB_CSTR GB_getenvPATH() {
517    static const char *path = 0;
518    if (!path) {
519        path = getenv_ignore_empty("PATH");
520        if (!path) {
521            path = GBS_eval_env("/bin:/usr/bin:$(ARBHOME)/bin");
522            GB_informationf("Your PATH variable is empty - using '%s' as search path.", path);
523        }
524        else {
525            char *arbbin = GBS_eval_env("$(ARBHOME)/bin");
526            if (strstr(path, arbbin) == 0) {
527                GB_warningf("Your PATH variable does not contain '%s'. Things may not work as expected.", arbbin);
528            }
529            free(arbbin);
530        }
531    }
532    return path;
533}
534
535// --------------------------------------------------------------------------------
536// Functions to find an executable
537
538char *GB_executable(GB_CSTR exe_name) {
539    GB_CSTR     path   = GB_getenvPATH();
540    char       *buffer = GB_give_buffer(strlen(path)+1+strlen(exe_name)+1);
541    const char *start  = path;
542    int         found  = 0;
543
544    while (!found && start) {
545        const char *colon = strchr(start, ':');
546        int         len   = colon ? (colon-start) : (int)strlen(start);
547
548        memcpy(buffer, start, len);
549        buffer[len] = '/';
550        strcpy(buffer+len+1, exe_name);
551
552        found = GB_is_executablefile(buffer);
553        start = colon ? colon+1 : 0;
554    }
555
556    return found ? strdup(buffer) : 0;
557}
558
559static char *GB_find_executable(GB_CSTR description_of_executable, ...) {
560    // goes to header: __ATTR__SENTINEL
561    /* search the path for an executable with any of the given names (...)
562     * if any is found, it's full path is returned
563     * if none is found, a warning call is returned (which can be executed without harm)
564    */
565
566    GB_CSTR  name;
567    char    *found = 0;
568    va_list  args;
569
570    va_start(args, description_of_executable);
571    while (!found && (name = va_arg(args, GB_CSTR)) != 0) found = GB_executable(name);
572    va_end(args);
573
574    if (!found) { // none of the executables has been found
575        char *looked_for;
576        char *msg;
577        {
578            GBS_strstruct *buf   = GBS_stropen(100);
579            int            first = 1;
580
581            va_start(args, description_of_executable);
582            while ((name = va_arg(args, GB_CSTR)) != 0) {
583                if (!first) GBS_strcat(buf, ", ");
584                first = 0;
585                GBS_strcat(buf, name);
586            }
587            va_end(args);
588            looked_for = GBS_strclose(buf);
589        }
590
591        msg   = GBS_global_string_copy("Could not find a %s (looked for: %s)", description_of_executable, looked_for);
592        GB_warning(msg);
593        found = GBS_global_string_copy("echo \"%s\" ; arb_ign Parameters", msg);
594        free(msg);
595        free(looked_for);
596    }
597    else {
598        GB_informationf("Using %s '%s' ('%s')", description_of_executable, name, found);
599    }
600    return found;
601}
602
603// --------------------------------------------------------------------------------
604// Functions to access the environment variables used by ARB:
605
606static char *getenv_executable(GB_CSTR envvar) {
607    // get full path of executable defined by 'envvar'
608    // returns 0 if
609    //  - envvar not defined or
610    //  - not defining an executable (warns about that)
611
612    char       *result   = 0;
613    const char *exe_name = getenv_ignore_empty(envvar);
614
615    if (exe_name) {
616        result = GB_executable(exe_name);
617        if (!result) {
618            GB_warningf("Environment variable '%s' contains '%s' (which is not an executable)", envvar, exe_name);
619        }
620    }
621
622    return result;
623}
624
625static char *getenv_existing_directory(GB_CSTR envvar) {
626    // get full path of directory defined by 'envvar'
627    // return 0 if
628    // - envvar is not defined or
629    // - does not point to a directory (warns about that)
630
631    char       *result   = 0;
632    const char *dir_name = getenv_ignore_empty(envvar);
633
634    if (dir_name) {
635        if (GB_is_directory(dir_name)) {
636            result = strdup(dir_name);
637        }
638        else {
639            GB_warningf("Environment variable '%s' should contain the path of an existing directory.\n"
640                        "(current content '%s' has been ignored.)", envvar, dir_name);
641        }
642    }
643    return result;
644}
645
646static void GB_setenv(const char *var, const char *value) {
647    if (setenv(var, value, 1) != 0) {
648        GB_warningf("Could not set environment variable '%s'. This might cause problems in subprocesses.\n"
649                    "(Reason: %s)", var, strerror(errno));
650    }
651}
652
653static GB_CSTR GB_getenvARB_XTERM() {
654    static const char *xterm = 0;
655    if (!xterm) {
656        xterm = getenv_ignore_empty("ARB_XTERM"); // doc in ../HELP_SOURCE/oldhelp/arb_envar.hlp@ARB_XTERM
657        if (!xterm) xterm = "xterm -sl 1000 -sb -geometry 120x50";
658    }
659    return xterm;
660}
661
662static GB_CSTR GB_getenvARB_XCMD() {
663    static const char *xcmd = 0;
664    if (!xcmd) {
665        xcmd = getenv_ignore_empty("ARB_XCMD"); // doc in ../HELP_SOURCE/oldhelp/arb_envar.hlp@ARB_XCMD
666        if (!xcmd) {
667            const char *xterm = GB_getenvARB_XTERM();
668            gb_assert(xterm);
669            xcmd = GBS_global_string_copy("%s -e", xterm);
670        }
671    }
672    return xcmd;
673}
674
675GB_CSTR GB_getenvUSER() {
676    static const char *user = 0;
677    if (!user) {
678        user = getenv_ignore_empty("USER");
679        if (!user) user = getenv_ignore_empty("LOGNAME");
680        if (!user) {
681            user = getenv_ignore_empty("HOME");
682            if (user && strrchr(user, '/')) user = strrchr(user, '/')+1;
683        }
684        if (!user) {
685            fprintf(stderr, "WARNING: Cannot identify user: environment variables USER, LOGNAME and HOME not set\n");
686            user = "UnknownUser";
687        }
688    }
689    return user;
690}
691
692
693static GB_CSTR GB_getenvHOME() {
694    static SmartCharPtr Home;
695    if (Home.isNull()) {
696        char *home = getenv_existing_directory("HOME");
697        if (!home) {
698            home = nulldup(GB_getcwd());
699            if (!home) home = strdup(".");
700            fprintf(stderr, "WARNING: Cannot identify user's home directory: environment variable HOME not set\n"
701                    "Using current directory (%s) as home.\n", home);
702        }
703        gb_assert(home);
704        Home = home;
705    }
706    return &*Home;
707}
708
709GB_CSTR GB_getenvARBHOME() {
710    static SmartCharPtr Arbhome;
711    if (Arbhome.isNull()) {
712        char *arbhome = getenv_existing_directory("ARBHOME"); // doc in ../HELP_SOURCE/oldhelp/arb_envar.hlp@ARBHOME
713        if (!arbhome) {
714            fprintf(stderr, "Fatal ERROR: Environment Variable ARBHOME not found !!!\n"
715                    "   Please set 'ARBHOME' to the installation path of ARB\n");
716            exit(-1);
717        }
718        Arbhome = arbhome;
719    }
720    return &*Arbhome;
721}
722
723GB_CSTR GB_getenvARBMACRO() {
724    static const char *am = 0;
725    if (!am) {
726        am          = getenv_existing_directory("ARBMACRO"); // doc in ../HELP_SOURCE/oldhelp/arb_envar.hlp@ARBMACRO
727        if (!am) am = strdup(GB_path_in_ARBLIB("macros"));
728    }
729    return am;
730}
731
732static char *getenv_autodirectory(const char *envvar, const char *defaultDirectory) {
733    // if environment variable 'envvar' contains an existing directory -> use that
734    // otherwise fallback to 'defaultDirectory' (create if not existing)
735    // return heap-copy of full directory name
736    char *dir = getenv_existing_directory(envvar);
737    if (!dir) {
738        dir = GBS_eval_env(defaultDirectory);
739        if (!GB_is_directory(dir)) {
740            GB_ERROR error = GB_create_directory(dir);
741            if (error) GB_warning(error);
742        }
743    }
744    return dir;
745}
746
747GB_CSTR GB_getenvARB_PROP() {
748    static SmartCharPtr ArbProps;
749    if (ArbProps.isNull()) ArbProps = getenv_autodirectory("ARB_PROP", GB_path_in_HOME(".arb_prop")); // doc in ../HELP_SOURCE/oldhelp/arb_envar.hlp@ARB_PROP
750    return &*ArbProps;
751}
752
753GB_CSTR GB_getenvARBMACROHOME() {
754    static SmartCharPtr ArbMacroHome;
755    if (ArbMacroHome.isNull()) ArbMacroHome = getenv_autodirectory("ARBMACROHOME", GB_path_in_arbprop("macros"));  // doc in ../HELP_SOURCE/oldhelp/arb_envar.hlp@ARBMACROHOME
756    return &*ArbMacroHome;
757}
758
759GB_CSTR GB_getenvARBCONFIG() {
760    static SmartCharPtr ArbConfig;
761    if (ArbConfig.isNull()) ArbConfig = getenv_autodirectory("ARBCONFIG", GB_path_in_arbprop("cfgSave")); // doc in ../HELP_SOURCE/oldhelp/arb_envar.hlp@ARBCONFIG
762    return &*ArbConfig;
763}
764
765GB_CSTR GB_getenvARB_GS() {
766    static const char *gs = 0;
767    if (!gs) {
768        gs = getenv_executable("ARB_GS"); // doc in ../HELP_SOURCE/oldhelp/arb_envar.hlp@ARB_GS
769        if (!gs) gs = GB_find_executable("Postscript viewer", "gv", "ghostview", NULL);
770    }
771    return gs;
772}
773
774GB_CSTR GB_getenvARB_PDFVIEW() {
775    static const char *pdfview = 0;
776    if (!pdfview) {
777        pdfview = getenv_executable("ARB_PDFVIEW"); // doc in ../HELP_SOURCE/oldhelp/arb_envar.hlp@ARB_PDFVIEW
778        if (!pdfview) pdfview = GB_find_executable("PDF viewer", "epdfview", "xpdf", "kpdf", "acroread", "gv", NULL);
779    }
780    return pdfview;
781}
782
783GB_CSTR GB_getenvARB_TEXTEDIT() {
784    static const char *editor = 0;
785    if (!editor) {
786        editor = getenv_executable("ARB_TEXTEDIT"); // doc in ../HELP_SOURCE/oldhelp/arb_envar.hlp@ARB_TEXTEDIT
787        if (!editor) editor = "arb_textedit"; // a smart editor shell script
788    }
789    return editor;
790}
791
792GB_CSTR GB_getenvDOCPATH() {
793    static const char *dp = 0;
794    if (!dp) {
795        char *res = getenv_existing_directory("ARB_DOC"); // doc in ../HELP_SOURCE/oldhelp/arb_envar.hlp@ARB_DOC
796        if (res) dp = res;
797        else     dp = strdup(GB_path_in_ARBLIB("help"));
798    }
799    return dp;
800}
801
802GB_CSTR GB_getenvHTMLDOCPATH() {
803    static const char *dp = 0;
804    if (!dp) {
805        char *res = getenv_existing_directory("ARB_HTMLDOC"); // doc in ../HELP_SOURCE/oldhelp/arb_envar.hlp@ARB_HTMLDOC
806        if (res) dp = res;
807        else     dp = strdup(GB_path_in_ARBLIB("help_html"));
808    }
809    return dp;
810
811}
812
813static gb_getenv_hook getenv_hook = NULL;
814
815NOT4PERL gb_getenv_hook GB_install_getenv_hook(gb_getenv_hook hook) {
816    // Install 'hook' to be called by GB_getenv().
817    // If the 'hook' returns NULL, normal expansion takes place.
818    // Otherwise GB_getenv() returns result from 'hook'
819
820    gb_getenv_hook oldHook = getenv_hook;
821    getenv_hook            = hook;
822    return oldHook;
823}
824
825GB_CSTR GB_getenv(const char *env) {
826    if (getenv_hook) {
827        const char *result = getenv_hook(env);
828        if (result) return result;
829    }
830    if (strncmp(env, "ARB", 3) == 0) {
831        // doc in ../HELP_SOURCE/oldhelp/arb_envar.hlp
832
833        if (strcmp(env, "ARBHOME")      == 0) return GB_getenvARBHOME();
834        if (strcmp(env, "ARB_PROP")     == 0) return GB_getenvARB_PROP();
835        if (strcmp(env, "ARBCONFIG")    == 0) return GB_getenvARBCONFIG();
836        if (strcmp(env, "ARBMACROHOME") == 0) return GB_getenvARBMACROHOME();
837        if (strcmp(env, "ARBMACRO")     == 0) return GB_getenvARBMACRO();
838
839        if (strcmp(env, "ARB_GS")       == 0) return GB_getenvARB_GS();
840        if (strcmp(env, "ARB_PDFVIEW")  == 0) return GB_getenvARB_PDFVIEW();
841        if (strcmp(env, "ARB_DOC")      == 0) return GB_getenvDOCPATH();
842        if (strcmp(env, "ARB_TEXTEDIT") == 0) return GB_getenvARB_TEXTEDIT();
843        if (strcmp(env, "ARB_XTERM")    == 0) return GB_getenvARB_XTERM();
844        if (strcmp(env, "ARB_XCMD")     == 0) return GB_getenvARB_XCMD();
845    }
846    else {
847        if (strcmp(env, "HOME") == 0) return GB_getenvHOME();
848        if (strcmp(env, "USER") == 0) return GB_getenvUSER();
849    }
850
851    return getenv_ignore_empty(env);
852}
853
854struct export_environment {
855    export_environment() {
856        // set all variables needed in ARB subprocesses
857        GB_setenv("ARB_XCMD", GB_getenvARB_XCMD());
858    }
859};
860
861static export_environment expenv;
862
863bool GB_host_is_local(const char *hostname) {
864    // returns true if host is local
865    return
866        ARB_stricmp(hostname, "localhost")       == 0 ||
867        ARB_strBeginsWith(hostname, "127.0.0.")       ||
868        ARB_stricmp(hostname, arb_gethostname()) == 0;
869}
870
871static GB_ULONG get_physical_memory() {
872    // Returns the physical available memory size in k available for one process
873    static GB_ULONG physical_memsize = 0;
874    if (!physical_memsize) {
875        GB_ULONG memsize; // real existing memory in k
876#if defined(LINUX)
877        {
878            long pagesize = sysconf(_SC_PAGESIZE);
879            long pages    = sysconf(_SC_PHYS_PAGES);
880
881            memsize = (pagesize/1024) * pages;
882        }
883#elif defined(DARWIN)
884#warning memsize detection needs to be tested for Darwin
885        {
886            int      mib[2];
887            uint64_t bytes;
888            size_t   len;
889
890            mib[0] = CTL_HW;
891            mib[1] = HW_MEMSIZE; // uint64_t: physical ram size
892            len = sizeof(bytes);
893            sysctl(mib, 2, &bytes, &len, NULL, 0);
894
895            memsize = bytes/1024;
896        }
897#else
898        memsize = 1024*1024; // assume 1 Gb
899        printf("\n"
900               "Warning: ARB is not prepared to detect the memory size on your system!\n"
901               "         (it assumes you have %ul Mb,  but does not use more)\n\n", memsize/1024);
902#endif
903
904        GB_ULONG net_memsize = memsize - 10240;         // reduce by 10Mb
905
906        // detect max allocateable memory by ... allocating
907        GB_ULONG max_malloc_try = net_memsize*1024;
908        GB_ULONG max_malloc     = 0;
909        {
910            GB_ULONG step_size  = 4096;
911            void *head = 0;
912
913            do {
914                void **tmp;
915                while ((tmp=(void**)malloc(step_size))) {
916                    *tmp        = head;
917                    head        = tmp;
918                    max_malloc += step_size;
919                    if (max_malloc >= max_malloc_try) break;
920                    step_size *= 2;
921                }
922            } while ((step_size=step_size/2) > sizeof(void*));
923
924            while (head) freeset(head, *(void**)head);
925            max_malloc /= 1024;
926        }
927
928        physical_memsize = std::min(net_memsize, max_malloc);
929
930#if defined(DEBUG) && 0
931        printf("- memsize(real)        = %20lu k\n", memsize);
932        printf("- memsize(net)         = %20lu k\n", net_memsize);
933        printf("- memsize(max_malloc)  = %20lu k\n", max_malloc);
934#endif // DEBUG
935
936        GB_informationf("Visible memory: %s", GBS_readable_size(physical_memsize*1024, "b"));
937    }
938
939    arb_assert(physical_memsize>0);
940    return physical_memsize;
941}
942
943static GB_ULONG parse_env_mem_definition(const char *env_override, GB_ERROR& error) {
944    const char *end;
945    GB_ULONG    num = strtoul(env_override, const_cast<char**>(&end), 10);
946
947    error = NULL;
948
949    bool valid = num>0 || env_override[0] == '0';
950    if (valid) {
951        const char *formatSpec = end;
952        double      factor     = 1;
953
954        switch (tolower(formatSpec[0])) {
955            case 0:
956                num = GB_ULONG(num/1024.0+0.5); // byte->kb
957                break; // no format given
958
959            case 'g': factor *= 1024;
960            case 'm': factor *= 1024;
961            case 'k': break;
962
963            case '%':
964                factor = num/100.0;
965                num    = get_physical_memory();
966                break;
967
968            default: valid = false; break;
969        }
970
971        if (valid) return GB_ULONG(num*factor+0.5);
972    }
973
974    error = "expected digits (optionally followed by k, M, G or %)";
975    return 0;
976}
977
978GB_ULONG GB_get_usable_memory() {
979    // memory allowed to be used by a single ARB process (in kbyte)
980
981    static GB_ULONG useable_memory = 0;
982    if (!useable_memory) {
983        bool        allow_fallback = true;
984        const char *env_override   = GB_getenv("ARB_MEMORY");
985        const char *via_whom;
986        if (env_override) {
987            via_whom = "via envar ARB_MEMORY";
988        }
989        else {
990          FALLBACK:
991            env_override   = "90%"; // ARB processes do not use more than 90% of physical memory
992            via_whom       = "by internal default";
993            allow_fallback = false;
994        }
995
996        gb_assert(env_override);
997
998        GB_ERROR env_error;
999        GB_ULONG env_memory = parse_env_mem_definition(env_override, env_error);
1000        if (env_error) {
1001            GB_warningf("Ignoring invalid setting '%s' %s (%s)", env_override, via_whom, env_error);
1002            if (allow_fallback) goto FALLBACK;
1003            GBK_terminate("failed to detect usable memory");
1004        }
1005
1006        GB_informationf("Restricting used memory (%s '%s') to %s", via_whom, env_override, GBS_readable_size(env_memory*1024, "b"));
1007        if (!allow_fallback) {
1008            GB_informationf("Note: Setting envar ARB_MEMORY will override that restriction (percentage or absolute memsize)");
1009        }
1010        useable_memory = env_memory;
1011        gb_assert(useable_memory>0 && useable_memory<get_physical_memory());
1012    }
1013    return useable_memory;
1014}
1015
1016// ---------------------------
1017//      external commands
1018
1019GB_ERROR GB_xterm() {
1020    // goes to header: __ATTR__USERESULT
1021    const char *xt      = GB_getenvARB_XTERM();
1022    const char *command = GBS_global_string("%s &", xt);
1023    return GBK_system(command);
1024}
1025
1026GB_ERROR GB_xcmd(const char *cmd, bool background, bool wait_only_if_error) {
1027    // goes to header: __ATTR__USERESULT_TODO
1028
1029    // runs a command in an xterm
1030    // if 'background' is true -> run asynchronous
1031    // if 'wait_only_if_error' is true -> asynchronous does wait for keypress only if cmd fails
1032
1033    GBS_strstruct *strstruct = GBS_stropen(1024);
1034    const char    *xcmd      = GB_getenvARB_XCMD();
1035
1036    GBS_strcat(strstruct, "(");
1037    GBS_strcat(strstruct, xcmd);
1038    GBS_strcat(strstruct, " bash -c 'LD_LIBRARY_PATH=\"");
1039    GBS_strcat(strstruct, GB_getenv("LD_LIBRARY_PATH"));
1040    GBS_strcat(strstruct, "\";export LD_LIBRARY_PATH; (");
1041    GBS_strcat(strstruct, cmd);
1042
1043    if (background) {
1044        if (wait_only_if_error) {
1045            GBS_strcat(strstruct, ") || (echo; echo Press RETURN to close Window; read a)' ) &");
1046        }
1047        else {
1048            GBS_strcat(strstruct, "; echo; echo Press RETURN to close Window; read a)' ) &");
1049        }
1050    }
1051    else {
1052        if (wait_only_if_error) {
1053            GBS_strcat(strstruct, ") || (echo; echo Press RETURN to close Window; read a)' )");
1054        }
1055        else { // no wait
1056            GBS_strcat(strstruct, " )' ) ");
1057        }
1058    }
1059
1060    GB_ERROR error = GBK_system(GBS_mempntr(strstruct));
1061    GBS_strforget(strstruct);
1062
1063    return error;
1064}
1065
1066// ---------------------------------------------
1067// path completion (parts former located in AWT)
1068// @@@ whole section (+ corresponding tests) should move to adfile.cxx
1069
1070static int  path_toggle = 0;
1071static char path_buf[2][ARB_PATH_MAX];
1072
1073static char *use_other_path_buf() {
1074    path_toggle = 1-path_toggle;
1075    return path_buf[path_toggle];
1076}
1077
1078GB_CSTR GB_append_suffix(const char *name, const char *suffix) {
1079    // if suffix != NULL -> append .suffix
1080    // (automatically removes duplicated '.'s)
1081
1082    GB_CSTR result = name;
1083    if (suffix) {
1084        while (suffix[0] == '.') suffix++;
1085        if (suffix[0]) {
1086            result = GBS_global_string_to_buffer(use_other_path_buf(), ARB_PATH_MAX, "%s.%s", name, suffix);
1087        }
1088    }
1089    return result;
1090}
1091
1092GB_CSTR GB_canonical_path(const char *anypath) {
1093    // expands '~' '..' symbolic links etc in 'anypath'.
1094    //
1095    // Never returns NULL (if called correctly)
1096    // Instead might return non-canonical path (when a directory
1097    // in 'anypath' does not exist)
1098
1099    GB_CSTR result = NULL;
1100    if (!anypath) {
1101        GB_export_error("NULL path (internal error)");
1102    }
1103    else if (!anypath[0]) {
1104        result = "/";
1105    }
1106    else if (strlen(anypath) >= ARB_PATH_MAX) {
1107        GB_export_errorf("Path too long (> %i chars)", ARB_PATH_MAX-1);
1108    }
1109    else {
1110        if (anypath[0] == '~' && (!anypath[1] || anypath[1] == '/')) {
1111            GB_CSTR home    = GB_getenvHOME();
1112            GB_CSTR homeexp = GBS_global_string("%s%s", home, anypath+1);
1113            result          = GB_canonical_path(homeexp);
1114            GBS_reuse_buffer(homeexp);
1115        }
1116        else {
1117            result = realpath(anypath, path_buf[1-path_toggle]);
1118            if (result) {
1119                path_toggle = 1-path_toggle;
1120            }
1121            else { // realpath failed (happens e.g. when using a non-existing path, e.g. if user entered the name of a new file)
1122                   // => content of path_buf[path_toggle] is UNDEFINED!
1123                char *dir, *fullname;
1124                GB_split_full_path(anypath, &dir, &fullname, NULL, NULL);
1125
1126                const char *canonical_dir = NULL;
1127                if (!dir) {
1128                    gb_assert(!strchr(anypath, '/'));
1129                    canonical_dir = GB_canonical_path("."); // use working directory
1130                }
1131                else {
1132                    gb_assert(strcmp(dir, anypath) != 0); // avoid deadlock
1133                    canonical_dir = GB_canonical_path(dir);
1134                }
1135                gb_assert(canonical_dir);
1136
1137                // manually resolve '.' and '..' in non-existing parent directories
1138                if (strcmp(fullname, "..") == 0) {
1139                    char *parent;
1140                    GB_split_full_path(canonical_dir, &parent, NULL, NULL, NULL);
1141                    if (parent) {
1142                        result = strcpy(use_other_path_buf(), parent);
1143                        free(parent);
1144                    }
1145                }
1146                else if (strcmp(fullname, ".") == 0) {
1147                    result = canonical_dir;
1148                }
1149
1150                if (!result) result = GB_concat_path(canonical_dir, fullname);
1151
1152                free(dir);
1153                free(fullname);
1154            }
1155        }
1156        gb_assert(result);
1157    }
1158    return result;
1159}
1160
1161GB_CSTR GB_concat_path(GB_CSTR anypath_left, GB_CSTR anypath_right) {
1162    // concats left and right part of a path.
1163    // '/' is inserted in-between
1164    //
1165    // if one of the arguments is NULL => returns the other argument
1166    // if both arguments are NULL      => return NULL (@@@ maybe forbid?)
1167
1168    GB_CSTR result = NULL;
1169
1170    if (anypath_right) {
1171        if (anypath_right[0] == '/') {
1172            result = GB_concat_path(anypath_left, anypath_right+1);
1173        }
1174        else if (anypath_left) {
1175            if (anypath_left[strlen(anypath_left)-1] == '/') {
1176                result = GBS_global_string_to_buffer(use_other_path_buf(), sizeof(path_buf[0]), "%s%s", anypath_left, anypath_right);
1177            }
1178            else {
1179                result = GBS_global_string_to_buffer(use_other_path_buf(), sizeof(path_buf[0]), "%s/%s", anypath_left, anypath_right);
1180            }
1181        }
1182        else {
1183            result = anypath_right;
1184        }
1185    }
1186    else {
1187        result = anypath_left;
1188    }
1189
1190    return result;
1191}
1192
1193GB_CSTR GB_concat_full_path(const char *anypath_left, const char *anypath_right) {
1194    // like GB_concat_path(), but returns the canonical path
1195    GB_CSTR result = GB_concat_path(anypath_left, anypath_right);
1196
1197    gb_assert(result != anypath_left); // consider using GB_canonical_path() directly
1198    gb_assert(result != anypath_right);
1199
1200    if (result) result = GB_canonical_path(result);
1201    return result;
1202}
1203
1204inline bool is_absolute_path(const char *path) { return path[0] == '/' || path[0] == '~'; }
1205inline bool is_name_of_envvar(const char *name) {
1206    for (int i = 0; name[i]; ++i) {
1207        if (isalnum(name[i]) || name[i] == '_') continue;
1208        return false;
1209    }
1210    return true;
1211}
1212
1213GB_CSTR GB_unfold_in_directory(const char *relative_directory, const char *path) {
1214    // If 'path' is an absolute path, return canonical path.
1215    //
1216    // Otherwise unfolds relative 'path' using 'relative_directory' as start directory.
1217
1218    if (is_absolute_path(path)) return GB_canonical_path(path);
1219    return GB_concat_full_path(relative_directory, path);
1220}
1221
1222GB_CSTR GB_unfold_path(const char *pwd_envar, const char *path) {
1223    // If 'path' is an absolute path, return canonical path.
1224    //
1225    // Otherwise unfolds relative 'path' using content of environment
1226    // variable 'pwd_envar' as start directory.
1227    // If environment variable is not defined, fall-back to current directory
1228
1229    gb_assert(is_name_of_envvar(pwd_envar));
1230    if (is_absolute_path(path)) {
1231        return GB_canonical_path(path);
1232    }
1233
1234    const char *pwd = GB_getenv(pwd_envar);
1235    if (!pwd) pwd = GB_getcwd(); // @@@ really wanted ?
1236    return GB_concat_full_path(pwd, path);
1237}
1238
1239static GB_CSTR GB_path_in_ARBHOME(const char *relative_path_left, const char *anypath_right) {
1240    return GB_path_in_ARBHOME(GB_concat_path(relative_path_left, anypath_right));
1241}
1242
1243GB_CSTR GB_path_in_ARBHOME(const char *relative_path) {
1244    return GB_unfold_path("ARBHOME", relative_path);
1245}
1246GB_CSTR GB_path_in_ARBLIB(const char *relative_path) {
1247    return GB_path_in_ARBHOME("lib", relative_path);
1248}
1249GB_CSTR GB_path_in_HOME(const char *relative_path) {
1250    return GB_unfold_path("HOME", relative_path);
1251}
1252GB_CSTR GB_path_in_arbprop(const char *relative_path) {
1253    return GB_unfold_path("ARB_PROP", relative_path);
1254}
1255GB_CSTR GB_path_in_ARBLIB(const char *relative_path_left, const char *anypath_right) {
1256    return GB_path_in_ARBLIB(GB_concat_path(relative_path_left, anypath_right));
1257}
1258GB_CSTR GB_path_in_arb_temp(const char *relative_path) {
1259    return GB_path_in_HOME(GB_concat_path(".arb_tmp", relative_path));
1260}
1261
1262#define GB_PATH_TMP GB_path_in_arb_temp("tmp") // = "~/.arb_tmp/tmp" (used wherever '/tmp' was used in the past)
1263
1264FILE *GB_fopen_tempfile(const char *filename, const char *fmode, char **res_fullname) {
1265    // fopens a tempfile
1266    //
1267    // Returns
1268    // - NULL in case of error (which is exported then)
1269    // - otherwise returns open filehandle
1270    //
1271    // Always sets
1272    // - heap-copy of used filename in 'res_fullname' (if res_fullname != NULL)
1273    // (even if fopen failed)
1274
1275    char     *file  = strdup(GB_concat_path(GB_PATH_TMP, filename));
1276    GB_ERROR  error = GB_create_parent_directory(file);
1277    FILE     *fp    = NULL;
1278
1279    if (!error) {
1280        bool write = strpbrk(fmode, "wa") != 0;
1281
1282        fp = fopen(file, fmode);
1283        if (fp) {
1284            // make file private
1285            if (fchmod(fileno(fp), S_IRUSR|S_IWUSR) != 0) {
1286                error = GB_IO_error("changing permissions of", file);
1287            }
1288        }
1289        else {
1290            error = GB_IO_error(GBS_global_string("opening(%s) tempfile", write ? "write" : "read"), file);
1291        }
1292
1293        if (res_fullname) {
1294            *res_fullname = file ? strdup(file) : 0;
1295        }
1296    }
1297
1298    if (error) {
1299        // don't care if anything fails here..
1300        if (fp) { fclose(fp); fp = 0; }
1301        if (file) unlink(file);
1302        GB_export_error(error);
1303    }
1304
1305    free(file);
1306
1307    return fp;
1308}
1309
1310char *GB_create_tempfile(const char *name) {
1311    // creates a tempfile and returns full name of created file
1312    // returns NULL in case of error (which is exported then)
1313
1314    char *fullname;
1315    FILE *out = GB_fopen_tempfile(name, "wt", &fullname);
1316
1317    if (out) fclose(out);
1318    return fullname;
1319}
1320
1321char *GB_unique_filename(const char *name_prefix, const char *suffix) {
1322    // generates a unique (enough) filename
1323    //
1324    // scheme: name_prefix_USER_PID_COUNT.suffix
1325
1326    static int counter = 0;
1327    return GBS_global_string_copy("%s_%s_%i_%i.%s",
1328                                  name_prefix,
1329                                  GB_getenvUSER(), getpid(), counter++,
1330                                  suffix);
1331}
1332
1333static GB_HASH *files_to_remove_on_exit = 0;
1334static long exit_remove_file(const char *file, long, void *) {
1335    if (unlink(file) != 0) {
1336        fprintf(stderr, "Warning: %s\n", GB_IO_error("removing", file));
1337    }
1338    return 0;
1339}
1340static void exit_removal() {
1341    if (files_to_remove_on_exit) {
1342        GBS_hash_do_loop(files_to_remove_on_exit, exit_remove_file, NULL);
1343        GBS_free_hash(files_to_remove_on_exit);
1344        files_to_remove_on_exit = NULL;
1345    }
1346}
1347void GB_remove_on_exit(const char *filename) {
1348    // mark a file for removal on exit
1349
1350    if (!files_to_remove_on_exit) {
1351        files_to_remove_on_exit = GBS_create_hash(20, GB_MIND_CASE);
1352        GB_atexit(exit_removal);
1353    }
1354    GBS_write_hash(files_to_remove_on_exit, filename, 1);
1355}
1356
1357void GB_split_full_path(const char *fullpath, char **res_dir, char **res_fullname, char **res_name_only, char **res_suffix) {
1358    // Takes a file (or directory) name and splits it into "path/name.suffix".
1359    // If result pointers (res_*) are non-NULL, they are assigned heap-copies of the splitted parts.
1360    // If parts are not valid (e.g. cause 'fullpath' doesn't have a .suffix) the corresponding result pointer
1361    // is set to NULL.
1362    //
1363    // The '/' and '.' characters at the split-positions will be removed (not included in the results-strings).
1364    // Exceptions:
1365    // - the '.' in 'res_fullname'
1366    // - the '/' if directory part is the rootdir
1367    //
1368    // Note:
1369    // - if the filename starts with '.' (and that is the only '.' in the filename, an empty filename is returned: "")
1370
1371    if (fullpath && fullpath[0]) {
1372        const char *lslash     = strrchr(fullpath, '/');
1373        const char *name_start = lslash ? lslash+1 : fullpath;
1374        const char *ldot       = strrchr(lslash ? lslash : fullpath, '.');
1375        const char *terminal   = strchr(name_start, 0);
1376
1377        gb_assert(terminal);
1378        gb_assert(name_start);
1379        gb_assert(terminal > fullpath); // ensure (terminal-1) is a valid character position in path
1380
1381        if (!lslash && fullpath[0] == '.' && (fullpath[1] == 0 || (fullpath[1] == '.' && fullpath[2] == 0))) { // '.' and '..'
1382            if (res_dir)       *res_dir       = strdup(fullpath);
1383            if (res_fullname)  *res_fullname  = NULL;
1384            if (res_name_only) *res_name_only = NULL;
1385            if (res_suffix)    *res_suffix    = NULL;
1386        }
1387        else {
1388            if (res_dir)       *res_dir       = lslash ? GB_strpartdup(fullpath, lslash == fullpath ? lslash : lslash-1) : NULL;
1389            if (res_fullname)  *res_fullname  = GB_strpartdup(name_start, terminal-1);
1390            if (res_name_only) *res_name_only = GB_strpartdup(name_start, ldot ? ldot-1 : terminal-1);
1391            if (res_suffix)    *res_suffix    = ldot ? GB_strpartdup(ldot+1, terminal-1) : NULL;
1392        }
1393    }
1394    else {
1395        if (res_dir)       *res_dir       = NULL;
1396        if (res_fullname)  *res_fullname  = NULL;
1397        if (res_name_only) *res_name_only = NULL;
1398        if (res_suffix)    *res_suffix    = NULL;
1399    }
1400}
1401
1402
1403// --------------------------------------------------------------------------------
1404
1405#ifdef UNIT_TESTS
1406
1407#include <test_unit.h>
1408
1409static const char *ANY_NAME = "ANY_NAME";
1410
1411struct gbcm_get_m_id_TESTER : virtual Noncopyable {
1412    const char *path;
1413    GB_ERROR    error;
1414    char       *name;
1415    long        id;
1416
1417    gbcm_get_m_id_TESTER(const char *path_)
1418        : path(path_)
1419        , name(NULL)
1420    {
1421        error = gbcm_get_m_id(path, &name, &id);
1422    }
1423    ~gbcm_get_m_id_TESTER() {
1424        TEST_EXPECT(!error || !name);               // if error occurs no name should exist!
1425        TEST_EXPECT(error || name);                 // either error or result
1426        free(name);
1427    }
1428
1429    bool no_error() {
1430        if (error) {
1431            fprintf(stderr, "Unexpected error for path '%s': %s\n", path, error);
1432            TEST_REJECT(name);
1433        }
1434        else if (!name) fprintf(stderr, "Neither error nor name for path '%s'\n", path);
1435        return !error;
1436    }
1437    bool parsed_name(const char *expected_name) {
1438        bool ok = no_error();
1439        if (ok && strcmp(expected_name, name) != 0) {
1440            if (expected_name != ANY_NAME) {
1441                fprintf(stderr, "path '%s' produces unexpected name '%s' (expected '%s')\n", path, name, expected_name);
1442                ok = false;
1443            }
1444        }
1445        return ok;
1446    }
1447    bool parsed_id(long expected_id) {
1448        bool ok = no_error();
1449        if (ok && id != expected_id) {
1450            fprintf(stderr, "path '%s' produces unexpected id '%li' (expected '%li')\n", path, id, expected_id);
1451            ok = false;
1452        }
1453        return ok;
1454    }
1455
1456    bool parsed(const char *expected_name, long expected_id) { return parsed_name(expected_name) && parsed_id(expected_id); }
1457};
1458
1459void TEST_gbcm_get_m_id() {
1460    TEST_REJECT_NULL(gbcm_get_m_id_TESTER(NULL).error);
1461    TEST_REJECT_NULL(gbcm_get_m_id_TESTER("").error);
1462    TEST_EXPECT(gbcm_get_m_id_TESTER(":").parsed(ANY_NAME, -1));
1463
1464    TEST_EXPECT(gbcm_get_m_id_TESTER("localhost:71").parsed("localhost", 71)); // fixed with [6486]
1465    TEST_REJECT_NULL(gbcm_get_m_id_TESTER("localhost:0").error);
1466    TEST_EXPECT_NULL(gbcm_get_m_id_TESTER("localhost:4096").error);
1467    TEST_REJECT_NULL(gbcm_get_m_id_TESTER("localhost:4097").error);
1468
1469    TEST_EXPECT(gbcm_get_m_id_TESTER(":/tmp/arb_pt_ralf1").parsed("/tmp/arb_pt_ralf1", -1));
1470
1471    // @@@ no idea what "*" is used for, so the following tests are only descriptive!
1472    TEST_EXPECT(gbcm_get_m_id_TESTER("*whatever:bar").parsed("bar", -1));
1473    TEST_EXPECT(gbcm_get_m_id_TESTER("*:bar").parsed("bar", -1));
1474}
1475
1476#define TEST_EXPECT_IS_CANONICAL(file)                  \
1477    do {                                                \
1478        char *dup = strdup(file);                       \
1479        TEST_EXPECT_EQUAL(GB_canonical_path(dup), dup); \
1480        free(dup);                                      \
1481    } while(0)
1482
1483#define TEST_EXPECT_CANONICAL_TO(not_cano,cano)                         \
1484    do {                                                                \
1485        char *arb_not_cano = strdup(GB_concat_path(arbhome, not_cano)); \
1486        char *arb_cano     = strdup(GB_concat_path(arbhome, cano));     \
1487        TEST_EXPECT_EQUAL(GB_canonical_path(arb_not_cano), arb_cano);   \
1488        free(arb_cano);                                                 \
1489        free(arb_not_cano);                                             \
1490    } while (0)
1491
1492static arb_test::match_expectation path_splits_into(const char *path, const char *Edir, const char *Enameext, const char *Ename, const char *Eext) {
1493    using namespace arb_test;
1494    expectation_group expected;
1495
1496    char *Sdir,*Snameext,*Sname,*Sext;
1497    GB_split_full_path(path, &Sdir, &Snameext, &Sname, &Sext);
1498
1499    expected.add(that(Sdir).is_equal_to(Edir));
1500    expected.add(that(Snameext).is_equal_to(Enameext));
1501    expected.add(that(Sname).is_equal_to(Ename));
1502    expected.add(that(Sext).is_equal_to(Eext));
1503
1504    free(Sdir);
1505    free(Snameext);
1506    free(Sname);
1507    free(Sext);
1508
1509    return all().ofgroup(expected);
1510}
1511
1512#define TEST_EXPECT_PATH_SPLITS_INTO(path,dir,nameext,name,ext)         TEST_EXPECTATION(path_splits_into(path,dir,nameext,name,ext))
1513#define TEST_EXPECT_PATH_SPLITS_INTO__BROKEN(path,dir,nameext,name,ext) TEST_EXPECTATION__BROKEN(path_splits_into(path,dir,nameext,name,ext))
1514
1515static arb_test::match_expectation path_splits_reversible(const char *path) {
1516    using namespace arb_test;
1517    expectation_group expected;
1518
1519    char *Sdir,*Snameext,*Sname,*Sext;
1520    GB_split_full_path(path, &Sdir, &Snameext, &Sname, &Sext);
1521
1522    expected.add(that(GB_append_suffix(Sname, Sext)).is_equal_to(Snameext)); // GB_append_suffix should reverse name.ext-split
1523    expected.add(that(GB_concat_path(Sdir, Snameext)).is_equal_to(path));    // GB_concat_path should reverse dir/file-split
1524
1525    free(Sdir);
1526    free(Snameext);
1527    free(Sname);
1528    free(Sext);
1529
1530    return all().ofgroup(expected);
1531}
1532
1533#define TEST_SPLIT_REVERSIBILITY(path)         TEST_EXPECTATION(path_splits_reversible(path))
1534#define TEST_SPLIT_REVERSIBILITY__BROKEN(path) TEST_EXPECTATION__BROKEN(path_splits_reversible(path))
1535
1536void TEST_paths() {
1537    // test GB_concat_path
1538    TEST_EXPECT_EQUAL(GB_concat_path("a", NULL), "a");
1539    TEST_EXPECT_EQUAL(GB_concat_path(NULL, "b"), "b");
1540    TEST_EXPECT_EQUAL(GB_concat_path("a", "b"), "a/b");
1541
1542    TEST_EXPECT_EQUAL(GB_concat_path("/", "test.fig"), "/test.fig");
1543
1544    // test GB_split_full_path
1545    TEST_EXPECT_PATH_SPLITS_INTO("dir/sub/.ext",              "dir/sub",   ".ext",            "",            "ext");
1546    TEST_EXPECT_PATH_SPLITS_INTO("/root/sub/file.notext.ext", "/root/sub", "file.notext.ext", "file.notext", "ext");
1547
1548    TEST_EXPECT_PATH_SPLITS_INTO("./file.ext", ".", "file.ext", "file", "ext");
1549    TEST_EXPECT_PATH_SPLITS_INTO("/file",      "/", "file",     "file", NULL);
1550    TEST_EXPECT_PATH_SPLITS_INTO(".",          ".", NULL,       NULL,   NULL);
1551
1552    // test reversibility of GB_split_full_path and GB_concat_path/GB_append_suffix
1553    {
1554        const char *prefix[] = {
1555            "",
1556            "dir/",
1557            "dir/sub/",
1558            "/dir/",
1559            "/dir/sub/",
1560            "/",
1561            "./",
1562            "../",
1563        };
1564
1565        for (size_t d = 0; d<ARRAY_ELEMS(prefix); ++d) {
1566            TEST_ANNOTATE(GBS_global_string("prefix='%s'", prefix[d]));
1567
1568            TEST_SPLIT_REVERSIBILITY(GBS_global_string("%sfile.ext", prefix[d]));
1569            TEST_SPLIT_REVERSIBILITY(GBS_global_string("%sfile", prefix[d]));
1570            TEST_SPLIT_REVERSIBILITY(GBS_global_string("%s.ext", prefix[d]));
1571            if (prefix[d][0]) { // empty string "" reverts to NULL
1572                TEST_SPLIT_REVERSIBILITY(prefix[d]);
1573            }
1574        }
1575    }
1576
1577    // GB_canonical_path basics
1578    TEST_EXPECT_CONTAINS(GB_canonical_path("./bla"), "UNIT_TESTER/run/bla");
1579    TEST_EXPECT_CONTAINS(GB_canonical_path("bla"),   "UNIT_TESTER/run/bla");
1580
1581    {
1582        char        *arbhome    = strdup(GB_getenvARBHOME());
1583        const char*  nosuchfile = "nosuchfile";
1584        const char*  somefile   = "arb_README.txt";
1585
1586        char *somefile_in_arbhome   = strdup(GB_concat_path(arbhome, somefile));
1587        char *nosuchfile_in_arbhome = strdup(GB_concat_path(arbhome, nosuchfile));
1588        char *nosuchpath_in_arbhome = strdup(GB_concat_path(arbhome, "nosuchpath"));
1589        char *somepath_in_arbhome   = strdup(GB_concat_path(arbhome, "lib"));
1590        char *file_in_nosuchpath    = strdup(GB_concat_path(nosuchpath_in_arbhome, "whatever"));
1591
1592        TEST_REJECT(GB_is_directory(nosuchpath_in_arbhome));
1593
1594        // test GB_get_full_path
1595        TEST_EXPECT_IS_CANONICAL(somefile_in_arbhome);
1596        TEST_EXPECT_IS_CANONICAL(nosuchpath_in_arbhome);
1597        TEST_EXPECT_IS_CANONICAL(file_in_nosuchpath);
1598
1599        TEST_EXPECT_IS_CANONICAL("/tmp"); // existing (most likely)
1600        TEST_EXPECT_IS_CANONICAL("/tmp/arbtest.fig");
1601        TEST_EXPECT_IS_CANONICAL("/arbtest.fig"); // not existing (most likely)
1602
1603        TEST_EXPECT_CANONICAL_TO("./PARSIMONY/./../ARBDB/./arbdb.h",     "ARBDB/arbdb.h"); // test parent-path
1604        TEST_EXPECT_CANONICAL_TO("INCLUDE/arbdb.h",                   "ARBDB/arbdb.h"); // test symbolic link to file
1605        TEST_EXPECT_CANONICAL_TO("NAMES_COM/AISC/aisc.pa",            "AISC_COM/AISC/aisc.pa"); // test symbolic link to directory
1606        TEST_EXPECT_CANONICAL_TO("./NAMES_COM/AISC/..",               "AISC_COM");              // test parent-path through links
1607
1608        TEST_EXPECT_CANONICAL_TO("./PARSIMONY/./../ARBDB/../nosuchpath", "nosuchpath"); // nosuchpath does not exist, but involved parent dirs do
1609        // test resolving of non-existent parent dirs:
1610        TEST_EXPECT_CANONICAL_TO("./PARSIMONY/./../nosuchpath/../ARBDB", "ARBDB");
1611        TEST_EXPECT_CANONICAL_TO("./nosuchpath/./../ARBDB", "ARBDB");
1612
1613        // test GB_unfold_path
1614        TEST_EXPECT_EQUAL(GB_unfold_path("ARBHOME", somefile), somefile_in_arbhome);
1615        TEST_EXPECT_EQUAL(GB_unfold_path("ARBHOME", nosuchfile), nosuchfile_in_arbhome);
1616
1617        char *inhome = strdup(GB_unfold_path("HOME", "whatever"));
1618        TEST_EXPECT_EQUAL(inhome, GB_canonical_path("~/whatever"));
1619        free(inhome);
1620
1621        // test GB_unfold_in_directory
1622        TEST_EXPECT_EQUAL(GB_unfold_in_directory(arbhome, somefile), somefile_in_arbhome);
1623        TEST_EXPECT_EQUAL(GB_unfold_in_directory(nosuchpath_in_arbhome, somefile_in_arbhome), somefile_in_arbhome);
1624        TEST_EXPECT_EQUAL(GB_unfold_in_directory(arbhome, nosuchfile), nosuchfile_in_arbhome);
1625        TEST_EXPECT_EQUAL(GB_unfold_in_directory(nosuchpath_in_arbhome, "whatever"), file_in_nosuchpath);
1626        TEST_EXPECT_EQUAL(GB_unfold_in_directory(somepath_in_arbhome, "../nosuchfile"), nosuchfile_in_arbhome);
1627
1628        // test unfolding absolute paths (HOME is ignored)
1629        TEST_EXPECT_EQUAL(GB_unfold_path("HOME", arbhome), arbhome);
1630        TEST_EXPECT_EQUAL(GB_unfold_path("HOME", somefile_in_arbhome), somefile_in_arbhome);
1631        TEST_EXPECT_EQUAL(GB_unfold_path("HOME", nosuchfile_in_arbhome), nosuchfile_in_arbhome);
1632
1633        // test GB_path_in_ARBHOME
1634        TEST_EXPECT_EQUAL(GB_path_in_ARBHOME(somefile), somefile_in_arbhome);
1635        TEST_EXPECT_EQUAL(GB_path_in_ARBHOME(nosuchfile), nosuchfile_in_arbhome);
1636
1637        free(file_in_nosuchpath);
1638        free(somepath_in_arbhome);
1639        free(nosuchpath_in_arbhome);
1640        free(nosuchfile_in_arbhome);
1641        free(somefile_in_arbhome);
1642        free(arbhome);
1643    }
1644
1645    TEST_EXPECT_EQUAL(GB_path_in_ARBLIB("help"), GB_path_in_ARBHOME("lib", "help"));
1646
1647}
1648
1649// ----------------------------------------
1650
1651class TestFile : virtual Noncopyable {
1652    const char *name;
1653    bool open(const char *mode) {
1654        FILE *out = fopen(name, mode);
1655        if (out) fclose(out);
1656        return out;
1657    }
1658    void create() { ASSERT_RESULT(bool, true, open("w")); }
1659    void unlink() { ::unlink(name); }
1660public:
1661    TestFile(const char *name_) : name(name_) { create(); }
1662    ~TestFile() { if (exists()) unlink(); }
1663    const char *get_name() const { return name; }
1664    bool exists() { return open("r"); }
1665};
1666
1667void TEST_GB_remove_on_exit() {
1668    {
1669        // first test class TestFile
1670        TestFile file("test1");
1671        TEST_EXPECT(file.exists());
1672        TEST_EXPECT(TestFile(file.get_name()).exists()); // removes the file
1673        TEST_REJECT(file.exists());
1674    }
1675
1676    TestFile t("test1");
1677    {
1678        GB_shell shell;
1679        GBDATA *gb_main = GB_open("no.arb", "c");
1680
1681        GB_remove_on_exit(t.get_name());
1682        GB_close(gb_main);
1683    }
1684    TEST_REJECT(t.exists());
1685}
1686
1687void TEST_some_paths() {
1688    gb_getenv_hook old = GB_install_getenv_hook(arb_test::fakeenv);
1689    {
1690        // ../UNIT_TESTER/run/homefake
1691
1692        TEST_EXPECT_CONTAINS__BROKEN(GB_getenvHOME(), "/UNIT_TESTER/run/homefake"); // GB_getenvHOME() ignores the hook
1693        // @@@ this is a general problem - unit tested code cannot use GB_getenvHOME() w/o problems
1694
1695        TEST_EXPECT_CONTAINS(GB_getenvARB_PROP(), "/UNIT_TESTER/run/homefake/.arb_prop");
1696        TEST_EXPECT_CONTAINS(GB_getenvARBMACRO(), "/lib/macros");
1697
1698        TEST_EXPECT_CONTAINS(GB_getenvARBCONFIG(),    "/UNIT_TESTER/run/homefake/.arb_prop/cfgSave");
1699        TEST_EXPECT_CONTAINS(GB_getenvARBMACROHOME(), "/UNIT_TESTER/run/homefake/.arb_prop/macros");  // works in [11068]
1700    }
1701    TEST_EXPECT_EQUAL((void*)arb_test::fakeenv, (void*)GB_install_getenv_hook(old));
1702}
1703
1704#endif // UNIT_TESTS
1705
Note: See TracBrowser for help on using the repository browser.