source: tags/svn.1.5.4/ARBDB/adsocket.cxx

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