source: branches/profile/ARBDB/adsocket.cxx

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