source: branches/lib/ARBDB/adsocket.cxx

Last change on this file was 19549, checked in by westram, 5 weeks ago
  • reintegrates 'sync' into 'trunk'
    • fixes #869
      • introduces synchronisation of external commands via DB
      • macros now wait for these external commands to finish ⇒ macro can continue with results
  • adds: log:branches/sync@19535:19548
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 53.9 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#include "arbdbt.h"
41
42#include <SigHandler.h>
43
44#include <algorithm>
45#include <arb_misc.h>
46#include <arb_defs.h>
47
48// ------------------------------------------------
49//      private read and write socket functions
50
51void gbcm_read_flush() {
52    gb_local->write_ptr  = gb_local->write_buffer;
53    gb_local->write_free = gb_local->write_bufsize;
54}
55
56static long gbcm_read_buffered(int socket, char *ptr, long size) {
57    /* write_ptr ptr to not read data
58       write_free   = write_bufsize-size of non read data;
59    */
60    long holding;
61    holding = gb_local->write_bufsize - gb_local->write_free;
62    if (holding <= 0) {
63        holding = read(socket, gb_local->write_buffer, (size_t)gb_local->write_bufsize);
64
65        if (holding < 0) {
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 0
157// never used code
158// (similar is done in GBCMS_shutdown, called e.g. from nt_disconnect_from_db)
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#endif
170
171gbcmc_comm *gbcmc_open(const char *path) {
172    gbcmc_comm *link = ARB_calloc<gbcmc_comm>(1);
173    GB_ERROR    err  = gbcm_open_socket(path, true, &link->socket, &link->unix_name);
174
175    if (err) {
176        if (link->unix_name) free(link->unix_name); // @@@
177        free(link);
178        if (*err) {
179            GB_internal_errorf("ARB_DB_CLIENT_OPEN\n(Reason: %s)", err);
180        }
181        return NULp;
182    }
183    gb_local->iamclient = true;
184    return link;
185}
186
187long gbcm_write_two(int socket, long a, long c) {
188    long    ia[3];
189    ia[0] = a;
190    ia[1] = 3;
191    ia[2] = c;
192    if (!socket) return 1;
193    return  gbcm_write(socket, (const char *)ia, sizeof(long)*3);
194}
195
196
197GBCM_ServerResult gbcm_read_two(int socket, long a, long *b, long *c) {
198    /*! read two values: length and any user long
199     *
200     *  if data is send by gbcm_write_two() then @param b should be zero
201     *  and is not used!
202     */
203
204    long    ia[3];
205    long    size;
206    size = gbcm_read(socket, (char *)&(ia[0]), sizeof(long)*3);
207    if (size != sizeof(long) * 3) {
208        GB_internal_errorf("receive failed: %zu bytes expected, %li got, keyword %lX",
209                           sizeof(long) * 3, size, a);
210        return GBCM_SERVER_FAULT;
211    }
212    if (ia[0] != a) {
213        GB_internal_errorf("received keyword failed %lx != %lx\n", ia[0], a);
214        return GBCM_SERVER_FAULT;
215    }
216    if (b) {
217        *b = ia[1];
218    }
219    else {
220        if (ia[1]!=3) {
221            GB_internal_error("receive failed: size not 3\n");
222            return GBCM_SERVER_FAULT;
223        }
224    }
225    *c = ia[2];
226    return GBCM_SERVER_OK;
227}
228
229GBCM_ServerResult gbcm_write_string(int socket, const char *key) {
230    if (key) {
231        size_t len = strlen(key);
232        gbcm_write_long(socket, len);
233        if (len) gbcm_write(socket, key, len);
234    }
235    else {
236        gbcm_write_long(socket, -1);
237    }
238    return GBCM_SERVER_OK;
239}
240
241char *gbcm_read_string(int socket) {
242    char *key;
243    long  len = gbcm_read_long(socket);
244
245    if (len) {
246        if (len>0) {
247            ARB_calloc(key, len+1);
248            gbcm_read(socket, key, len);
249        }
250        else {
251            key = NULp;
252        }
253    }
254    else {
255        key = ARB_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(4096);
280
281    int c;
282    while (EOF != (c = getc(in))) {
283        buf.put(c);
284    }
285    return buf.release_memfriendly();
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 NULp in case of error (use GB_await_error() to get the message)
294     */
295    char *result = NULp;
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 = ARB_alloc<char>(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 NULp;
331    }
332    if (writeable) {
333        buffer = (char*)mmap(NULp, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fi, 0);
334    }
335    else {
336        buffer = (char*)mmap(NULp, 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 NULp;
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 NULp;
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, NULp)) 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    char       *quoted_fpath = GBK_singlequote(fpath);
368    const char *command      = GBS_global_string("arb_textprint %s &", quoted_fpath);
369    GB_ERROR    error        = GBK_system(command);
370    error                    = GB_failedTo_error("print textfile", fpath, error);
371    free(quoted_fpath);
372    free(fpath);
373    return error;
374}
375
376// --------------------------------------------------------------------------------
377
378static GB_CSTR GB_getenvPATH() {
379    static const char *path = NULp;
380    if (!path) {
381        path = ARB_getenv_ignore_empty("PATH");
382        if (!path) {
383            path = GBS_eval_env("/bin:/usr/bin:$(ARBHOME)/bin");
384            GB_informationf("Your PATH variable is empty - using '%s' as search path.", path);
385        }
386        else {
387            char *arbbin = GBS_eval_env("$(ARBHOME)/bin");
388            if (!strstr(path, arbbin)) {
389                GB_warningf("Your PATH variable does not contain '%s'. Things may not work as expected.", arbbin);
390            }
391            free(arbbin);
392        }
393    }
394    return path;
395}
396
397// --------------------------------------------------------------------------------
398// Functions to find an executable
399
400static char *GB_find_executable(GB_CSTR description_of_executable, ...) {
401    // goes to header: __ATTR__SENTINEL
402    /* search the path for an executable with any of the given names (...)
403     * if any is found, it's full path is returned
404     * if none is found, a warning call is returned (which can be executed without harm)
405    */
406
407    GB_CSTR  name;
408    char    *found = NULp;
409    va_list  args;
410
411    va_start(args, description_of_executable);
412    while (!found && (name = va_arg(args, GB_CSTR))) found = ARB_executable(name, GB_getenvPATH());
413    va_end(args);
414
415    if (!found) { // none of the executables has been found
416        char *looked_for;
417        char *msg;
418        {
419            GBS_strstruct buf(100);
420            bool          first = true;
421
422            va_start(args, description_of_executable);
423            while ((name = va_arg(args, GB_CSTR))) {
424                if (!first) buf.cat(", ");
425                first = false;
426                buf.cat(name);
427            }
428            va_end(args);
429            looked_for = buf.release();
430        }
431
432        msg   = GBS_global_string_copy("Could not find a %s (looked for: %s)", description_of_executable, looked_for);
433        GB_warning(msg);
434        found = GBS_global_string_copy("echo \"%s\" ; arb_ign Parameters", msg);
435        free(msg);
436        free(looked_for);
437    }
438    else {
439        if (ARB_path_contains_unwanted_chars(found)) {
440            char *description = GBS_global_string_copy("the %s derived from environment variable PATH", description_of_executable);
441            ARB_warn_about_unwanted_chars(found, description);
442            free(description);
443        }
444        GB_informationf("Using %s '%s' ('%s')", description_of_executable, name, found);
445    }
446    return found;
447}
448
449// --------------------------------------------------------------------------------
450// Functions to access the environment variables used by ARB:
451
452static char *getenv_executable(GB_CSTR envvar) {
453    // get full path of executable defined by 'envvar'
454    // returns 0 if
455    //  - envvar not defined or
456    //  - not defining an executable (warns about that)
457
458    char       *result   = NULp;
459    const char *exe_name = ARB_getenv_ignore_empty(envvar);
460
461    if (exe_name) {
462#if defined(DARWIN)
463        // On MacOS, the open command can be used to open files with MacOS
464        // applications. The open command fails the ARB executable test. If no
465        // application is passed to the open command, the default application
466        // for the file type is used or the user is presented with a dialog to
467        // chose an application if no default application is used. This is very
468        // hard to test from within ARB. Therefore, commands that start with
469        // open are considered to be valid applications and the executable test
470        // is not run.
471        if (!strncasecmp(exe_name, "open", 4)) {
472            return ARB_strdup(exe_name);
473        }
474#endif
475
476        result = ARB_executable(exe_name, GB_getenvPATH());
477        if (!result) {
478            GB_warningf("Environment variable '%s' contains '%s' (which is not an executable)", envvar, exe_name);
479        }
480
481        if (ARB_path_contains_unwanted_chars(result)) {
482            char *description = GBS_global_string_copy("the executable derived from environment variables '%s' and PATH", envvar);
483            ARB_warn_about_unwanted_chars(result, description);
484            free(description);
485        }
486    }
487
488    return result;
489}
490
491static char *getenv_existing_directory(GB_CSTR envvar) {
492    // get full path of directory defined by 'envvar'
493    // return 0 if
494    // - envvar is not defined or
495    // - does not point to a directory (warns about that)
496
497    char       *result   = NULp;
498    const char *dir_name = ARB_getenv_ignore_empty(envvar);
499
500    if (dir_name) {
501        if (GB_is_directory(dir_name)) {
502            result = ARB_strdup(dir_name);
503            if (ARB_path_contains_unwanted_chars(result)) {
504                char *description = GBS_global_string_copy("the directory derived from environment variable '%s'", envvar);
505                ARB_warn_about_unwanted_chars(result, description);
506                free(description);
507            }
508        }
509        else {
510            GB_warningf("Environment variable '%s' should contain the path of an existing directory.\n"
511                        "(current content '%s' has been ignored.)", envvar, dir_name);
512        }
513    }
514    return result;
515}
516
517void GB_setenv(const char *var, const char *value) {
518    if (setenv(var, value, 1) != 0) {
519        GB_warningf("Could not set environment variable '%s'. This might cause problems in subprocesses.\n"
520                    "(Reason: %s)", var, strerror(errno));
521    }
522}
523
524GB_CSTR GB_getenvARB_XTERM() {
525    static const char *xterm = NULp;
526    if (!xterm) {
527        xterm = ARB_getenv_ignore_empty("ARB_XTERM"); // doc in ../HELP_SOURCE/source/arb_envar.hlp@ARB_XTERM
528        if (!xterm) xterm = "xterm -sl 1000 -sb -geometry 150x60";
529    }
530    return xterm;
531}
532
533GB_CSTR GB_getenvARB_XCMD() {
534    static const char *xcmd = NULp;
535    if (!xcmd) {
536        xcmd = ARB_getenv_ignore_empty("ARB_XCMD"); // doc in ../HELP_SOURCE/source/arb_envar.hlp@ARB_XCMD
537        if (!xcmd) {
538            const char *xterm = GB_getenvARB_XTERM();
539            gb_assert(xterm);
540            xcmd = GBS_global_string_copy("%s -e", xterm);
541        }
542    }
543    return xcmd;
544}
545
546GB_CSTR GB_getenvUSER() {
547    static const char *user = NULp;
548    if (!user) {
549        user = ARB_getenv_ignore_empty("USER");
550        if (!user) user = ARB_getenv_ignore_empty("LOGNAME");
551        if (!user) {
552            user = ARB_getenv_ignore_empty("HOME");
553            if (user && strrchr(user, '/')) user = strrchr(user, '/')+1;
554        }
555        if (!user) {
556            fprintf(stderr, "WARNING: Cannot identify user: environment variables USER, LOGNAME and HOME not set\n");
557            user = "UnknownUser";
558        }
559    }
560    return user;
561}
562
563
564static GB_CSTR GB_getenvHOME() {
565    static SmartCharPtr Home;
566    if (Home.isNull()) {
567        char *home = getenv_existing_directory("HOME");
568        if (!home) {
569            home = nulldup(GB_getcwd());
570            if (!home) home = ARB_strdup(".");
571            fprintf(stderr, "WARNING: Cannot identify user's home directory: environment variable HOME not set\n"
572                    "Using current directory (%s) as home.\n", home);
573        }
574        gb_assert(home);
575        Home = home;
576    }
577    return &*Home;
578}
579
580GB_CSTR GB_getenvARBHOME() {
581    static SmartCharPtr Arbhome;
582    if (Arbhome.isNull()) {
583        char *arbhome = getenv_existing_directory("ARBHOME"); // doc in ../HELP_SOURCE/source/arb_envar.hlp@ARBHOME
584        if (!arbhome) {
585            fprintf(stderr, "Fatal ERROR: Environment Variable ARBHOME not found !!!\n"
586                    "   Please set 'ARBHOME' to the installation path of ARB\n");
587            exit(EXIT_FAILURE);
588        }
589        Arbhome = arbhome;
590    }
591    return &*Arbhome;
592}
593
594GB_CSTR GB_getenvARBMACRO() {
595    static const char *am = NULp;
596    if (!am) {
597        am          = getenv_existing_directory("ARBMACRO"); // doc in ../HELP_SOURCE/source/arb_envar.hlp@ARBMACRO
598        if (!am) am = ARB_strdup(GB_path_in_ARBLIB("macros"));
599    }
600    return am;
601}
602
603static char *getenv_autodirectory(const char *envvar, const char *defaultDirectory) {
604    // if environment variable 'envvar' contains an existing directory -> use that
605    // otherwise fallback to 'defaultDirectory' (create if not existing)
606    // return heap-copy of full directory name
607    char *dir = getenv_existing_directory(envvar);
608    if (!dir) {
609        dir = GBS_eval_env(defaultDirectory);
610        if (ARB_path_contains_unwanted_chars(dir)) {
611            char *description = GBS_global_string_copy("the default directory expanded from expression '%s'", defaultDirectory);
612            ARB_warn_about_unwanted_chars(dir, description);
613            free(description);
614        }
615        if (!GB_is_directory(dir)) {
616            GB_ERROR error = GB_create_directory(dir);
617            if (error) GB_warning(error);
618        }
619    }
620    return dir;
621}
622
623static GB_CSTR GB_getenvARB_PROP() {
624    static SmartCharPtr ArbProps;
625    if (ArbProps.isNull()) ArbProps = getenv_autodirectory("ARB_PROP", GB_path_in_HOME(".arb_prop")); // doc in ../HELP_SOURCE/source/arb_envar.hlp@ARB_PROP
626    return &*ArbProps;
627}
628
629GB_CSTR GB_getenvARBMACROHOME() {
630    static SmartCharPtr ArbMacroHome;
631    if (ArbMacroHome.isNull()) ArbMacroHome = getenv_autodirectory("ARBMACROHOME", GB_path_in_arbprop("macros"));  // doc in ../HELP_SOURCE/source/arb_envar.hlp@ARBMACROHOME
632    return &*ArbMacroHome;
633}
634
635static GB_CSTR GB_getenvARBCONFIG() {
636    static SmartCharPtr ArbConfig;
637    if (ArbConfig.isNull()) ArbConfig = getenv_autodirectory("ARBCONFIG", GB_path_in_arbprop("cfgSave")); // doc in ../HELP_SOURCE/source/arb_envar.hlp@ARBCONFIG
638    return &*ArbConfig;
639}
640
641GB_CSTR GB_getenvARB_GS() {
642    static const char *gs = NULp;
643    if (!gs) {
644        gs = getenv_executable("ARB_GS"); // doc in ../HELP_SOURCE/source/arb_envar.hlp@ARB_GS
645        if (!gs) {
646#if defined(DARWIN)
647            // use default application for PS files on MacOS if envvar is not
648            // set - defaults to Apple Preview application
649            // -W waits until the user has deleted the application this is
650            // required
651            gs = ARB_strdup("open -W");
652#else
653            gs = GB_find_executable("Postscript viewer", "xreader", "gv", "ghostview", NULp);
654#endif
655        }
656    }
657    return gs;
658}
659
660GB_CSTR GB_getenvARB_PDFVIEW() {
661    static const char *pdfview = NULp;
662    if (!pdfview) {
663        pdfview = getenv_executable("ARB_PDFVIEW"); // doc in ../HELP_SOURCE/source/arb_envar.hlp@ARB_PDFVIEW
664        if (!pdfview) {
665#if defined(DARWIN)
666            // use default application for PDF files on MacOS if envvar is not
667            // set - defaults to Apple Preview application
668            pdfview = ARB_strdup("open -W");
669#else
670            pdfview = GB_find_executable("PDF viewer", "xreader", "epdfview", "xpdf", "kpdf", "acroread", "gv", NULp);
671#endif
672        }
673    }
674    return pdfview;
675}
676
677GB_CSTR GB_getenvDOCPATH() {
678    static const char *dp = NULp;
679    if (!dp) {
680        char *res = getenv_existing_directory("ARB_DOC"); // doc in ../HELP_SOURCE/source/arb_envar.hlp@ARB_DOC
681        if (res) dp = res;
682        else     dp = ARB_strdup(GB_path_in_ARBLIB("help"));
683    }
684    return dp;
685}
686
687GB_CSTR GB_getenvHTMLDOCPATH() {
688    static const char *dp = NULp;
689    if (!dp) {
690        char *res = getenv_existing_directory("ARB_HTMLDOC"); // doc in ../HELP_SOURCE/source/arb_envar.hlp@ARB_HTMLDOC
691        if (res) dp = res;
692        else     dp = ARB_strdup(GB_path_in_ARBLIB("help_html"));
693    }
694    return dp;
695
696}
697
698static gb_getenv_hook getenv_hook = NULp;
699
700NOT4PERL gb_getenv_hook GB_install_getenv_hook(gb_getenv_hook hook) {
701    // Install 'hook' to be called by GB_getenv().
702    // If the 'hook' returns NULp, normal expansion takes place.
703    // Otherwise GB_getenv() returns result from 'hook'
704
705    gb_getenv_hook oldHook = getenv_hook;
706    getenv_hook            = hook;
707    return oldHook;
708}
709
710GB_CSTR GB_getenv(const char *env) {
711    if (getenv_hook) {
712        const char *result = getenv_hook(env);
713        if (result) return result;
714    }
715    if (strncmp(env, "ARB", 3) == 0) {
716        // doc in ../HELP_SOURCE/source/arb_envar.hlp
717
718        if (strcmp(env, "ARBHOME")      == 0) return GB_getenvARBHOME();
719        if (strcmp(env, "ARB_PROP")     == 0) return GB_getenvARB_PROP();
720        if (strcmp(env, "ARBCONFIG")    == 0) return GB_getenvARBCONFIG();
721        if (strcmp(env, "ARBMACROHOME") == 0) return GB_getenvARBMACROHOME();
722        if (strcmp(env, "ARBMACRO")     == 0) return GB_getenvARBMACRO();
723
724        if (strcmp(env, "ARB_GS")       == 0) return GB_getenvARB_GS();
725        if (strcmp(env, "ARB_PDFVIEW")  == 0) return GB_getenvARB_PDFVIEW();
726        if (strcmp(env, "ARB_DOC")      == 0) return GB_getenvDOCPATH();
727        if (strcmp(env, "ARB_XTERM")    == 0) return GB_getenvARB_XTERM();
728        if (strcmp(env, "ARB_XCMD")     == 0) return GB_getenvARB_XCMD();
729    }
730    else {
731        if (strcmp(env, "HOME") == 0) return GB_getenvHOME();
732        if (strcmp(env, "USER") == 0) return GB_getenvUSER();
733    }
734
735    return ARB_getenv_ignore_empty(env);
736}
737
738struct export_environment {
739    export_environment() {
740        // set all variables needed in ARB subprocesses
741        GB_setenv("ARB_XCMD", GB_getenvARB_XCMD());
742    }
743};
744
745static export_environment expenv;
746
747bool GB_host_is_local(const char *hostname) {
748    // returns true if host is local
749
750    arb_assert(hostname);
751    arb_assert(hostname[0]);
752
753    return
754        ARB_stricmp(hostname, "localhost")       == 0 ||
755        ARB_strBeginsWith(hostname, "127.0.0.")       ||
756        ARB_stricmp(hostname, arb_gethostname()) == 0;
757}
758
759static GB_ULONG get_physical_memory() {
760    // Returns the physical available memory size in k available for one process
761    static GB_ULONG physical_memsize = 0;
762    if (!physical_memsize) {
763        GB_ULONG memsize; // real existing memory in k
764#if defined(LINUX)
765        {
766            long pagesize = sysconf(_SC_PAGESIZE);
767            long pages    = sysconf(_SC_PHYS_PAGES);
768
769            memsize = (pagesize/1024) * pages;
770        }
771#elif defined(DARWIN)
772#warning memsize detection needs to be tested for Darwin
773        {
774            int      mib[2];
775            uint64_t bytes;
776            size_t   len;
777
778            mib[0] = CTL_HW;
779            mib[1] = HW_MEMSIZE; // uint64_t: physical ram size
780            len = sizeof(bytes);
781            sysctl(mib, 2, &bytes, &len, NULp, 0);
782
783            memsize = bytes/1024;
784        }
785#else
786        memsize = 1024*1024; // assume 1 Gb
787        printf("\n"
788               "Warning: ARB is not prepared to detect the memory size on your system!\n"
789               "         (it assumes you have %ul Mb,  but does not use more)\n\n", memsize/1024);
790#endif
791
792        GB_ULONG net_memsize = memsize - 10240;         // reduce by 10Mb
793
794        // detect max allocateable memory by ... allocating
795        GB_ULONG max_malloc_try = net_memsize*1024;
796        GB_ULONG max_malloc     = 0;
797        {
798            GB_ULONG  step_size = 4096;
799            void     *head      = NULp;
800
801            do {
802                void **tmp;
803                while ((tmp=(void**)malloc(step_size))) { // do NOT use ARB_alloc here!
804                    *tmp        = head;
805                    head        = tmp;
806                    max_malloc += step_size;
807                    if (max_malloc >= max_malloc_try) break;
808                    step_size *= 2;
809                }
810            } while ((step_size=step_size/2) > sizeof(void*));
811
812            while (head) freeset(head, *(void**)head);
813            max_malloc /= 1024;
814        }
815
816        physical_memsize = std::min(net_memsize, max_malloc);
817
818#if defined(DEBUG) && 0
819        printf("- memsize(real)        = %20lu k\n", memsize);
820        printf("- memsize(net)         = %20lu k\n", net_memsize);
821        printf("- memsize(max_malloc)  = %20lu k\n", max_malloc);
822#endif // DEBUG
823
824        GB_informationf("Visible memory: %s", GBS_readable_size(physical_memsize*1024, "b"));
825    }
826
827    arb_assert(physical_memsize>0);
828    return physical_memsize;
829}
830
831static GB_ULONG parse_env_mem_definition(const char *env_override, GB_ERROR& error) {
832    const char *end;
833    GB_ULONG    num = strtoul(env_override, const_cast<char**>(&end), 10);
834
835    error = NULp;
836
837    bool valid = num>0 || env_override[0] == '0';
838    if (valid) {
839        const char *formatSpec = end;
840        double      factor     = 1;
841
842        switch (tolower(formatSpec[0])) {
843            case 0:
844                num = GB_ULONG(num/1024.0+0.5); // byte->kb
845                break; // no format given
846
847            case 'g': factor *= 1024; FALLTHROUGH;
848            case 'm': factor *= 1024; FALLTHROUGH;
849            case 'k': break;
850
851            case '%':
852                factor = num/100.0;
853                num    = get_physical_memory();
854                break;
855
856            default: valid = false; break;
857        }
858
859        if (valid) return GB_ULONG(num*factor+0.5);
860    }
861
862    error = "expected digits (optionally followed by k, M, G or %)";
863    return 0;
864}
865
866GB_ULONG GB_get_usable_memory() {
867    // memory allowed to be used by a single ARB process (in kbyte)
868
869    static GB_ULONG useable_memory = 0;
870    if (!useable_memory) {
871        bool        allow_fallback = true;
872        const char *env_override   = GB_getenv("ARB_MEMORY");
873        const char *via_whom;
874        if (env_override) {
875            via_whom = "via envar ARB_MEMORY";
876        }
877        else {
878          FALLBACK:
879            env_override   = "90%"; // ARB processes do not use more than 90% of physical memory
880            via_whom       = "by internal default";
881            allow_fallback = false;
882        }
883
884        gb_assert(env_override);
885
886        GB_ERROR env_error;
887        GB_ULONG env_memory = parse_env_mem_definition(env_override, env_error);
888        if (env_error) {
889            GB_warningf("Ignoring invalid setting '%s' %s (%s)", env_override, via_whom, env_error);
890            if (allow_fallback) goto FALLBACK;
891            GBK_terminate("failed to detect usable memory");
892        }
893
894        GB_informationf("Restricting used memory (%s '%s') to %s", via_whom, env_override, GBS_readable_size(env_memory*1024, "b"));
895        if (!allow_fallback) {
896            GB_informationf("Note: Setting envar ARB_MEMORY will override that restriction (percentage or absolute memsize)");
897        }
898        useable_memory = env_memory;
899        gb_assert(useable_memory>0 && useable_memory<get_physical_memory());
900    }
901    return useable_memory;
902}
903
904// ---------------------------------
905//      process synchronisation
906
907static GBDATA *find_or_create_sync_data(GBDATA *gb_main) {
908    gb_assert(!GB_have_error()); // illegal to enter this function when an error is exported!
909
910    GBDATA *gb_sync_data = GB_entry(gb_main, "sync_data");
911    if (!gb_sync_data) {
912        gb_sync_data   = GBT_create(gb_main, "sync_data", 0); // has to use security level 0 (otherwise GB_set_temporary does fail)
913        GB_ERROR error = GB_set_temporary(gb_sync_data);
914        if (error) {
915            GB_export_error(error);
916            gb_sync_data = NULp;
917        }
918    }
919
920    gb_assert(contradicted(gb_sync_data, GB_have_error())); // either result or error!
921    return gb_sync_data;
922}
923static GBDATA *find_or_create_sync_value(GBDATA *gb_main, const char *id) {
924    gb_assert(!GB_have_error()); // illegal to enter this function when an error is exported!
925
926    GBDATA *gb_value     = NULp;
927    GBDATA *gb_sync_data = find_or_create_sync_data(gb_main);
928    if (gb_sync_data) {
929        GBDATA *gb_sync = GBT_find_or_create_item_rel_item_data(gb_sync_data, "sync", "id", id, false);
930        if (gb_sync) {
931            gb_value = GB_searchOrCreate_string(gb_sync, "value", "");
932        }
933    }
934
935    gb_assert(contradicted(gb_value, GB_have_error())); // either result or error!
936    return gb_value;
937}
938
939GB_ERROR GB_write_sync_value(GBDATA *gb_main, const char *id, const char *value) {
940    GB_ERROR  error        = NULp;
941    GBDATA   *gb_syncValue = find_or_create_sync_value(gb_main, id);
942    if (gb_syncValue) {
943        error = GB_write_string(gb_syncValue, value);
944    }
945    else {
946        error = GB_await_error();
947    }
948    return error;
949}
950
951GB_CSTR GB_read_sync_value(GBDATA *gb_main, const char *id) {
952    GB_CSTR  value        = NULp;
953    GBDATA  *gb_syncValue = find_or_create_sync_value(gb_main, id);
954
955    if (gb_syncValue) {
956        value = GB_read_char_pntr(gb_syncValue);
957    }
958
959    gb_assert(contradicted(value, GB_have_error())); // either result or error!
960    return value;
961}
962
963// ---------------------------------------------
964// path completion (parts former located in AWT)
965// @@@ whole section (+ corresponding tests) should move to adfile.cxx
966
967static int  path_toggle = 0;
968static char path_buf[2][ARB_PATH_MAX];
969
970static char *use_other_path_buf() {
971    path_toggle = 1-path_toggle;
972    return path_buf[path_toggle];
973}
974
975GB_CSTR GB_append_suffix(const char *name, const char *suffix) {
976    // if suffix isnt NULp -> append .suffix
977    // (automatically removes duplicated '.'s)
978    //
979    // Note: see also GB_split_full_path
980
981    GB_CSTR result = name;
982    if (suffix) {
983        while (suffix[0] == '.') suffix++;
984        if (suffix[0]) {
985            result = GBS_global_string_to_buffer(use_other_path_buf(), ARB_PATH_MAX, "%s.%s", name, suffix);
986        }
987    }
988    return result;
989}
990
991GB_CSTR GB_canonical_path(const char *anypath) {
992    // expands '~' '..' symbolic links etc in 'anypath'.
993    //
994    // Never returns NULp (if called correctly)
995    // Instead might return non-canonical path (when a directory
996    // in 'anypath' does not exist)
997
998    GB_CSTR result = NULp;
999    if (!anypath) {
1000        GB_export_error("NULp path (internal error)");
1001    }
1002    else if (!anypath[0]) {
1003        result = "/";
1004    }
1005    else if (strlen(anypath) >= ARB_PATH_MAX) {
1006        GB_export_errorf("Path too long (> %i chars)", ARB_PATH_MAX-1);
1007    }
1008    else {
1009        if (anypath[0] == '~' && (!anypath[1] || anypath[1] == '/')) {
1010            GB_CSTR home    = GB_getenvHOME();
1011            GB_CSTR homeexp = GBS_global_string("%s%s", home, anypath+1);
1012            result          = GB_canonical_path(homeexp);
1013            GBS_reuse_buffer(homeexp);
1014        }
1015        else {
1016            result = realpath(anypath, path_buf[1-path_toggle]);
1017            if (result) {
1018                path_toggle = 1-path_toggle;
1019            }
1020            else { // realpath failed (happens e.g. when using a non-existing path, e.g. if user entered the name of a new file)
1021                   // => content of path_buf[path_toggle] is UNDEFINED!
1022                char *dir, *fullname;
1023                GB_split_full_path(anypath, &dir, &fullname, NULp, NULp);
1024
1025                const char *canonical_dir = NULp;
1026                if (!dir) {
1027                    gb_assert(!strchr(anypath, '/'));
1028                    canonical_dir = GB_canonical_path("."); // use working directory
1029                }
1030                else {
1031                    gb_assert(strcmp(dir, anypath) != 0); // avoid deadlock
1032                    canonical_dir = GB_canonical_path(dir);
1033                }
1034                gb_assert(canonical_dir);
1035
1036                // manually resolve '.' and '..' in non-existing parent directories
1037                if (strcmp(fullname, "..") == 0) {
1038                    char *parent;
1039                    GB_split_full_path(canonical_dir, &parent, NULp, NULp, NULp);
1040                    if (parent) {
1041                        result = strcpy(use_other_path_buf(), parent);
1042                        free(parent);
1043                    }
1044                }
1045                else if (strcmp(fullname, ".") == 0) {
1046                    result = canonical_dir;
1047                }
1048
1049                if (!result) result = GB_concat_path(canonical_dir, fullname);
1050
1051                free(dir);
1052                free(fullname);
1053            }
1054        }
1055        gb_assert(result);
1056    }
1057    return result;
1058}
1059
1060GB_CSTR GB_concat_path(GB_CSTR anypath_left, GB_CSTR anypath_right) {
1061    // concats left and right part of a path.
1062    // '/' is inserted in-between
1063    //
1064    // if one of the arguments is NULp => returns the other argument
1065    // if both arguments are NULp      => return NULp (@@@ maybe forbid?)
1066    //
1067    // Note: see also GB_split_full_path
1068
1069    GB_CSTR result = NULp;
1070
1071    if (anypath_right) {
1072        if (anypath_right[0] == '/') {
1073            result = GB_concat_path(anypath_left, anypath_right+1);
1074        }
1075        else if (anypath_left && anypath_left[0]) {
1076            if (anypath_left[strlen(anypath_left)-1] == '/') {
1077                result = GBS_global_string_to_buffer(use_other_path_buf(), sizeof(path_buf[0]), "%s%s", anypath_left, anypath_right);
1078            }
1079            else {
1080                result = GBS_global_string_to_buffer(use_other_path_buf(), sizeof(path_buf[0]), "%s/%s", anypath_left, anypath_right);
1081            }
1082        }
1083        else {
1084            result = anypath_right;
1085        }
1086    }
1087    else {
1088        result = anypath_left;
1089    }
1090
1091    return result;
1092}
1093
1094GB_CSTR GB_concat_full_path(const char *anypath_left, const char *anypath_right) {
1095    // like GB_concat_path(), but returns the canonical path
1096    GB_CSTR result = GB_concat_path(anypath_left, anypath_right);
1097
1098    gb_assert(result != anypath_left); // consider using GB_canonical_path() directly
1099    gb_assert(result != anypath_right);
1100
1101    if (result) result = GB_canonical_path(result);
1102    return result;
1103}
1104
1105inline bool is_absolute_path(const char *path) { return path[0] == '/' || path[0] == '~'; }
1106inline bool is_name_of_envvar(const char *name) {
1107    for (int i = 0; name[i]; ++i) {
1108        if (isalnum(name[i]) || name[i] == '_') continue;
1109        return false;
1110    }
1111    return true;
1112}
1113
1114GB_CSTR GB_unfold_in_directory(const char *relative_directory, const char *path) {
1115    // If 'path' is an absolute path, return canonical path.
1116    //
1117    // Otherwise unfolds relative 'path' using 'relative_directory' as start directory.
1118
1119    if (is_absolute_path(path)) return GB_canonical_path(path);
1120    return GB_concat_full_path(relative_directory, path);
1121}
1122
1123GB_CSTR GB_unfold_path(const char *pwd_envar, const char *path) {
1124    // If 'path' is an absolute path, return canonical path.
1125    //
1126    // Otherwise unfolds relative 'path' using content of environment
1127    // variable 'pwd_envar' as start directory.
1128    // If environment variable is not defined, fall-back to current directory
1129
1130    gb_assert(is_name_of_envvar(pwd_envar));
1131    if (is_absolute_path(path)) {
1132        return GB_canonical_path(path);
1133    }
1134
1135    const char *pwd = GB_getenv(pwd_envar);
1136    if (!pwd) pwd = GB_getcwd(); // @@@ really wanted ?
1137    return GB_concat_full_path(pwd, path);
1138}
1139
1140GB_CSTR GB_path_in_ARBHOME(const char *relative_path) {
1141    return GB_unfold_path("ARBHOME", relative_path);
1142}
1143static GB_CSTR GB_concat_path_in_ARBHOME(const char *relative_path_left, const char *anypath_right) {
1144    return GB_path_in_ARBHOME(GB_concat_path(relative_path_left, anypath_right));
1145}
1146
1147GB_CSTR GB_path_in_ARBLIB(const char *relative_path) {
1148    return GB_concat_path_in_ARBHOME("lib", relative_path);
1149}
1150GB_CSTR GB_concat_path_in_ARBLIB(const char *relative_path_left, const char *anypath_right) {
1151    return GB_path_in_ARBLIB(GB_concat_path(relative_path_left, anypath_right));
1152}
1153
1154GB_CSTR GB_path_in_HOME(const char *relative_path) {
1155    return GB_unfold_path("HOME", relative_path);
1156}
1157GB_CSTR GB_path_in_arbprop(const char *relative_path) {
1158    return GB_unfold_path("ARB_PROP", relative_path);
1159}
1160static GB_CSTR GB_path_in_arb_temp(const char *relative_path) {
1161    return GB_path_in_HOME(GB_concat_path(".arb_tmp", relative_path));
1162}
1163
1164
1165#define GB_PATH_TMP GB_path_in_arb_temp("tmp") // = "~/.arb_tmp/tmp" (used wherever '/tmp' was used in the past)
1166
1167FILE *GB_fopen_tempfile(const char *filename, const char *fmode, char **res_fullname) {
1168    // fopens a tempfile
1169    //
1170    // Returns
1171    // - NULp in case of error (which is exported then)
1172    // - otherwise returns open filehandle
1173    //
1174    // Always sets
1175    // - heap-copy of used filename in 'res_fullname' (if res_fullname isnt NULp)
1176    // (even if fopen failed)
1177
1178    char     *file  = ARB_strdup(GB_concat_path(GB_PATH_TMP, filename));
1179    GB_ERROR  error = GB_create_parent_directory(file);
1180    FILE     *fp    = NULp;
1181
1182    if (!error) {
1183        bool write = strpbrk(fmode, "wa");
1184
1185        fp = fopen(file, fmode);
1186        if (fp) {
1187            // make file private
1188            if (fchmod(fileno(fp), S_IRUSR|S_IWUSR) != 0) {
1189                error = GB_IO_error("changing permissions of", file);
1190            }
1191        }
1192        else {
1193            error = GB_IO_error(GBS_global_string("opening(%s) tempfile", write ? "write" : "read"), file);
1194        }
1195
1196        if (res_fullname) {
1197            *res_fullname = file ? ARB_strdup(file) : NULp;
1198        }
1199    }
1200
1201    if (error) {
1202        // don't care if anything fails here..
1203        if (fp) { fclose(fp); fp = NULp; }
1204        if (file) unlink(file);
1205        GB_export_error(error);
1206    }
1207
1208    free(file);
1209
1210    return fp;
1211}
1212
1213char *GB_create_tempfile(const char *name) {
1214    // creates a tempfile and returns full name of created file
1215    // returns NULp in case of error (which is exported then)
1216
1217    char *fullname;
1218    FILE *out = GB_fopen_tempfile(name, "wt", &fullname);
1219
1220    if (out) fclose(out);
1221    return fullname;
1222}
1223
1224char *GB_unique_filename(const char *name_prefix, const char *suffix) {
1225    // generates a unique (enough) filename
1226    //
1227    // scheme: name_prefix_USER_PID_COUNT.suffix
1228
1229    static int counter = 0;
1230    return GBS_global_string_copy("%s_%s_%i_%i.%s",
1231                                  name_prefix,
1232                                  GB_getenvUSER(), getpid(), counter++,
1233                                  suffix);
1234}
1235
1236static GB_HASH *files_to_remove_on_exit = NULp;
1237static void exit_remove_file(const char *file, long, void *) {
1238    if (unlink(file) != 0) {
1239        fprintf(stderr, "Warning: %s\n", GB_IO_error("removing", file));
1240    }
1241}
1242static void exit_removal() {
1243    if (files_to_remove_on_exit) {
1244        GBS_hash_do_const_loop(files_to_remove_on_exit, exit_remove_file, NULp);
1245        GBS_free_hash(files_to_remove_on_exit);
1246        files_to_remove_on_exit = NULp;
1247    }
1248}
1249void GB_remove_on_exit(const char *filename) {
1250    // mark a file for removal on exit
1251
1252    if (!files_to_remove_on_exit) {
1253        files_to_remove_on_exit = GBS_create_hash(20, GB_MIND_CASE);
1254        GB_atexit(exit_removal);
1255    }
1256    GBS_write_hash(files_to_remove_on_exit, filename, 1);
1257}
1258
1259void GB_split_full_path(const char *fullpath, char **res_dir, char **res_fullname, char **res_name_only, char **res_suffix) {
1260    // Takes a file (or directory) name and splits it into "path/name.suffix".
1261    // If result pointers (res_*) are non-NULp, they'll get assigned heap-copies of the splitted parts:
1262    //     "path" -> res_dir
1263    //     "name.suffix" -> res_fullname
1264    //     "name" -> res_name_only
1265    //     "suffix" -> res_suffix
1266    //
1267    // If parts are not valid (e.g. cause 'fullpath' doesn't have a .suffix) the corresponding result pointer
1268    // is set to NULp.
1269    //
1270    // The '/' and '.' characters at the split-positions will be removed (not included in the results-strings).
1271    // Exceptions:
1272    // - the '.' in 'res_fullname'
1273    // - the '/' if directory part is the rootdir
1274    //
1275    // Note:
1276    // - if the filename starts with '.' (and that is the only '.' in the filename, an empty filename is returned: "")
1277    //
1278    // Reverse functionality is provided by GB_concat_path and GB_append_suffix.
1279
1280    if (fullpath && fullpath[0]) {
1281        const char *lslash     = strrchr(fullpath, '/');
1282        const char *name_start = lslash ? lslash+1 : fullpath;
1283        const char *ldot       = strrchr(lslash ? lslash : fullpath, '.');
1284        const char *terminal   = strchr(name_start, 0);
1285
1286        gb_assert(terminal);
1287        gb_assert(name_start);
1288        gb_assert(terminal > fullpath); // ensure (terminal-1) is a valid character position in path
1289
1290        if (!lslash && fullpath[0] == '.' && (fullpath[1] == 0 || (fullpath[1] == '.' && fullpath[2] == 0))) { // '.' and '..'
1291            if (res_dir)       *res_dir       = ARB_strdup(fullpath);
1292            if (res_fullname)  *res_fullname  = NULp;
1293            if (res_name_only) *res_name_only = NULp;
1294            if (res_suffix)    *res_suffix    = NULp;
1295        }
1296        else {
1297            if (res_dir)       *res_dir       = lslash ? ARB_strpartdup(fullpath, lslash == fullpath ? lslash : lslash-1) : NULp;
1298            if (res_fullname)  *res_fullname  = ARB_strpartdup(name_start, terminal-1);
1299            if (res_name_only) *res_name_only = ARB_strpartdup(name_start, ldot ? ldot-1 : terminal-1);
1300            if (res_suffix)    *res_suffix    = ldot ? ARB_strpartdup(ldot+1, terminal-1) : NULp;
1301        }
1302    }
1303    else {
1304        if (res_dir)       *res_dir       = NULp;
1305        if (res_fullname)  *res_fullname  = NULp;
1306        if (res_name_only) *res_name_only = NULp;
1307        if (res_suffix)    *res_suffix    = NULp;
1308    }
1309}
1310
1311
1312// --------------------------------------------------------------------------------
1313
1314#ifdef UNIT_TESTS
1315
1316#include <test_unit.h>
1317
1318#define TEST_EXPECT_IS_CANONICAL(file)                  \
1319    do {                                                \
1320        char *dup = ARB_strdup(file);                   \
1321        TEST_EXPECT_EQUAL(GB_canonical_path(dup), dup); \
1322        free(dup);                                      \
1323    } while(0)
1324
1325#define TEST_EXPECT_CANONICAL_TO(not_cano,cano)                                 \
1326    do {                                                                        \
1327        char *arb_not_cano = ARB_strdup(GB_concat_path(arbhome, not_cano));     \
1328        char *arb_cano     = ARB_strdup(GB_concat_path(arbhome, cano));         \
1329        TEST_EXPECT_EQUAL(GB_canonical_path(arb_not_cano), arb_cano);           \
1330        free(arb_cano);                                                         \
1331        free(arb_not_cano);                                                     \
1332    } while (0)
1333
1334static arb_test::match_expectation path_splits_into(const char *path, const char *Edir, const char *Enameext, const char *Ename, const char *Eext) {
1335    using namespace arb_test;
1336    expectation_group expected;
1337
1338    char *Sdir,*Snameext,*Sname,*Sext;
1339    GB_split_full_path(path, &Sdir, &Snameext, &Sname, &Sext);
1340
1341    expected.add(that(Sdir).is_equal_to(Edir));
1342    expected.add(that(Snameext).is_equal_to(Enameext));
1343    expected.add(that(Sname).is_equal_to(Ename));
1344    expected.add(that(Sext).is_equal_to(Eext));
1345
1346    free(Sdir);
1347    free(Snameext);
1348    free(Sname);
1349    free(Sext);
1350
1351    return all().ofgroup(expected);
1352}
1353
1354#define TEST_EXPECT_PATH_SPLITS_INTO(path,dir,nameext,name,ext)         TEST_EXPECTATION(path_splits_into(path,dir,nameext,name,ext))
1355#define TEST_EXPECT_PATH_SPLITS_INTO__BROKEN(path,dir,nameext,name,ext) TEST_EXPECTATION__BROKEN(path_splits_into(path,dir,nameext,name,ext))
1356
1357static arb_test::match_expectation path_splits_reversible(const char *path) {
1358    using namespace arb_test;
1359    expectation_group expected;
1360
1361    char *Sdir,*Snameext,*Sname,*Sext;
1362    GB_split_full_path(path, &Sdir, &Snameext, &Sname, &Sext);
1363
1364    expected.add(that(GB_append_suffix(Sname, Sext)).is_equal_to(Snameext)); // GB_append_suffix should reverse name.ext-split
1365    expected.add(that(GB_concat_path(Sdir, Snameext)).is_equal_to(path));    // GB_concat_path should reverse dir/file-split
1366
1367    free(Sdir);
1368    free(Snameext);
1369    free(Sname);
1370    free(Sext);
1371
1372    return all().ofgroup(expected);
1373}
1374
1375#define TEST_SPLIT_REVERSIBILITY(path)         TEST_EXPECTATION(path_splits_reversible(path))
1376#define TEST_SPLIT_REVERSIBILITY__BROKEN(path) TEST_EXPECTATION__BROKEN(path_splits_reversible(path))
1377
1378void TEST_paths() {
1379    // test GB_concat_path
1380    TEST_EXPECT_EQUAL(GB_concat_path("a", NULp), "a");
1381    TEST_EXPECT_EQUAL(GB_concat_path(NULp, "b"), "b");
1382    TEST_EXPECT_EQUAL(GB_concat_path("a", "b"), "a/b");
1383
1384    TEST_EXPECT_EQUAL(GB_concat_path("/", "test.fig"), "/test.fig");
1385
1386    // test GB_split_full_path
1387    TEST_EXPECT_PATH_SPLITS_INTO("dir/sub/.ext",              "dir/sub",   ".ext",            "",            "ext");
1388    TEST_EXPECT_PATH_SPLITS_INTO("/root/sub/file.notext.ext", "/root/sub", "file.notext.ext", "file.notext", "ext");
1389
1390    TEST_EXPECT_PATH_SPLITS_INTO("./file.ext", ".", "file.ext", "file", "ext");
1391    TEST_EXPECT_PATH_SPLITS_INTO("/file",      "/", "file",     "file", NULp);
1392    TEST_EXPECT_PATH_SPLITS_INTO(".",          ".", NULp,       NULp,   NULp);
1393
1394    // test reversibility of GB_split_full_path and GB_concat_path/GB_append_suffix
1395    {
1396        const char *prefix[] = {
1397            "",
1398            "dir/",
1399            "dir/sub/",
1400            "/dir/",
1401            "/dir/sub/",
1402            "/",
1403            "./",
1404            "../",
1405        };
1406
1407        for (size_t d = 0; d<ARRAY_ELEMS(prefix); ++d) {
1408            TEST_ANNOTATE(GBS_global_string("prefix='%s'", prefix[d]));
1409
1410            TEST_SPLIT_REVERSIBILITY(GBS_global_string("%sfile.ext", prefix[d]));
1411            TEST_SPLIT_REVERSIBILITY(GBS_global_string("%sfile", prefix[d]));
1412            TEST_SPLIT_REVERSIBILITY(GBS_global_string("%s.ext", prefix[d]));
1413            if (prefix[d][0]) { // empty string "" reverts to NULp
1414                TEST_SPLIT_REVERSIBILITY(prefix[d]);
1415            }
1416        }
1417    }
1418
1419    // GB_canonical_path basics
1420    TEST_EXPECT_CONTAINS(GB_canonical_path("./bla"), "UNIT_TESTER/run/bla");
1421    TEST_EXPECT_CONTAINS(GB_canonical_path("bla"),   "UNIT_TESTER/run/bla");
1422
1423    {
1424        char        *arbhome    = ARB_strdup(GB_getenvARBHOME());
1425        const char*  nosuchfile = "nosuchfile";
1426        const char*  somefile   = "arb_README.txt";
1427
1428        char *somefile_in_arbhome   = ARB_strdup(GB_concat_path(arbhome, somefile));
1429        char *nosuchfile_in_arbhome = ARB_strdup(GB_concat_path(arbhome, nosuchfile));
1430        char *nosuchpath_in_arbhome = ARB_strdup(GB_concat_path(arbhome, "nosuchpath"));
1431        char *somepath_in_arbhome   = ARB_strdup(GB_concat_path(arbhome, "lib"));
1432        char *file_in_nosuchpath    = ARB_strdup(GB_concat_path(nosuchpath_in_arbhome, "whatever"));
1433
1434        TEST_REJECT(GB_is_directory(nosuchpath_in_arbhome));
1435
1436        // test GB_get_full_path
1437        TEST_EXPECT_IS_CANONICAL(somefile_in_arbhome);
1438        TEST_EXPECT_IS_CANONICAL(nosuchpath_in_arbhome);
1439        TEST_EXPECT_IS_CANONICAL(file_in_nosuchpath);
1440
1441        TEST_EXPECT_IS_CANONICAL("/usr"); // existing (most likely)
1442#if !defined(DARWIN)
1443        // TEST_DISABLED_OSX: fails for darwin on jenkins (/tmp seems to be a symbolic link there)
1444        TEST_EXPECT_IS_CANONICAL("/tmp/arbtest.fig");
1445#endif
1446        TEST_EXPECT_IS_CANONICAL("/arbtest.fig"); // not existing (most likely)
1447
1448        TEST_EXPECT_CANONICAL_TO("./PARSIMONY/./../ARBDB/./arbdb.h",     "ARBDB/arbdb.h"); // test parent-path
1449        TEST_EXPECT_CANONICAL_TO("INCLUDE/arbdb.h",                   "ARBDB/arbdb.h"); // test symbolic link to file
1450        TEST_EXPECT_CANONICAL_TO("NAMES_COM/AISC/aisc.pa",            "AISC_COM/AISC/aisc.pa"); // test symbolic link to directory
1451        TEST_EXPECT_CANONICAL_TO("./NAMES_COM/AISC/..",               "AISC_COM");              // test parent-path through links
1452
1453        TEST_EXPECT_CANONICAL_TO("./PARSIMONY/./../ARBDB/../nosuchpath", "nosuchpath"); // nosuchpath does not exist, but involved parent dirs do
1454        // test resolving of non-existent parent dirs:
1455        TEST_EXPECT_CANONICAL_TO("./PARSIMONY/./../nosuchpath/../ARBDB", "ARBDB");
1456        TEST_EXPECT_CANONICAL_TO("./nosuchpath/./../ARBDB", "ARBDB");
1457
1458        // test GB_unfold_path
1459        TEST_EXPECT_EQUAL(GB_unfold_path("ARBHOME", somefile), somefile_in_arbhome);
1460        TEST_EXPECT_EQUAL(GB_unfold_path("ARBHOME", nosuchfile), nosuchfile_in_arbhome);
1461
1462        char *inhome = ARB_strdup(GB_unfold_path("HOME", "whatever"));
1463        TEST_EXPECT_EQUAL(inhome, GB_canonical_path("~/whatever"));
1464        free(inhome);
1465
1466        // test GB_unfold_in_directory
1467        TEST_EXPECT_EQUAL(GB_unfold_in_directory(arbhome, somefile), somefile_in_arbhome);
1468        TEST_EXPECT_EQUAL(GB_unfold_in_directory(nosuchpath_in_arbhome, somefile_in_arbhome), somefile_in_arbhome);
1469        TEST_EXPECT_EQUAL(GB_unfold_in_directory(arbhome, nosuchfile), nosuchfile_in_arbhome);
1470        TEST_EXPECT_EQUAL(GB_unfold_in_directory(nosuchpath_in_arbhome, "whatever"), file_in_nosuchpath);
1471        TEST_EXPECT_EQUAL(GB_unfold_in_directory(somepath_in_arbhome, "../nosuchfile"), nosuchfile_in_arbhome);
1472
1473        // test unfolding absolute paths (HOME is ignored)
1474        TEST_EXPECT_EQUAL(GB_unfold_path("HOME", arbhome), arbhome);
1475        TEST_EXPECT_EQUAL(GB_unfold_path("HOME", somefile_in_arbhome), somefile_in_arbhome);
1476        TEST_EXPECT_EQUAL(GB_unfold_path("HOME", nosuchfile_in_arbhome), nosuchfile_in_arbhome);
1477
1478        // test GB_path_in_ARBHOME
1479        TEST_EXPECT_EQUAL(GB_path_in_ARBHOME(somefile), somefile_in_arbhome);
1480        TEST_EXPECT_EQUAL(GB_path_in_ARBHOME(nosuchfile), nosuchfile_in_arbhome);
1481
1482        free(file_in_nosuchpath);
1483        free(somepath_in_arbhome);
1484        free(nosuchpath_in_arbhome);
1485        free(nosuchfile_in_arbhome);
1486        free(somefile_in_arbhome);
1487        free(arbhome);
1488    }
1489
1490    TEST_EXPECT_EQUAL(GB_path_in_ARBLIB("help"), GB_concat_path_in_ARBHOME("lib", "help"));
1491
1492}
1493
1494// ----------------------------------------
1495
1496class TestFile : virtual Noncopyable {
1497    const char *name;
1498    bool open(const char *mode) {
1499        FILE *out = fopen(name, mode);
1500        if (out) fclose(out);
1501        return out;
1502    }
1503    void create() { ASSERT_RESULT(bool, true, open("w")); }
1504    void unlink() { ::unlink(name); }
1505public:
1506    TestFile(const char *name_) : name(name_) { create(); }
1507    ~TestFile() { if (exists()) unlink(); }
1508    const char *get_name() const { return name; }
1509    bool exists() { return open("r"); }
1510};
1511
1512void TEST_GB_remove_on_exit() {
1513    {
1514        // first test class TestFile
1515        TestFile file("test1");
1516        TEST_EXPECT(file.exists());
1517        TEST_EXPECT(TestFile(file.get_name()).exists()); // removes the file
1518        TEST_REJECT(file.exists());
1519    }
1520
1521    TestFile t("test1");
1522    {
1523        GB_shell shell;
1524        GBDATA *gb_main = GB_open("no.arb", "c");
1525
1526        GB_remove_on_exit(t.get_name());
1527        GB_close(gb_main);
1528    }
1529    TEST_REJECT(t.exists());
1530}
1531
1532void TEST_some_paths() {
1533    gb_getenv_hook old = GB_install_getenv_hook(arb_test::fakeenv);
1534    {
1535        // ../UNIT_TESTER/run/homefake
1536
1537        TEST_EXPECT_CONTAINS__BROKEN(GB_getenvHOME(), "/UNIT_TESTER/run/homefake"); // GB_getenvHOME() ignores the hook
1538        // @@@ this is a general problem - unit tested code cannot use GB_getenvHOME() w/o problems
1539
1540        TEST_EXPECT_CONTAINS(GB_getenvARB_PROP(), "/UNIT_TESTER/run/homefake/.arb_prop");
1541        TEST_EXPECT_CONTAINS(GB_getenvARBMACRO(), "/lib/macros");
1542
1543        TEST_EXPECT_CONTAINS(GB_getenvARBCONFIG(),    "/UNIT_TESTER/run/homefake/.arb_prop/cfgSave");
1544        TEST_EXPECT_CONTAINS(GB_getenvARBMACROHOME(), "/UNIT_TESTER/run/homefake/.arb_prop/macros");  // works in [11068]
1545    }
1546    TEST_EXPECT_EQUAL((void*)arb_test::fakeenv, (void*)GB_install_getenv_hook(old));
1547}
1548
1549void TEST_sync() {
1550    const char *db_save_name = "saved_after_sync.arb";
1551
1552    GB_shell shell;
1553    {
1554        GBDATA *gb_main = GB_open("nosuch.arb", "cw");
1555
1556        {
1557            GB_transaction ta(gb_main);
1558            GB_CSTR value;
1559
1560            TEST_EXPECT_RESULT__NOERROREXPORTED(value = GB_read_sync_value(gb_main, "neverUsedId"));
1561            TEST_EXPECT_EQUAL(value, ""); // unused IDs have empty string as (default) value
1562
1563#define SYNCID "myId"
1564
1565            // write new value:
1566            TEST_EXPECT_NO_ERROR(GB_write_sync_value(gb_main, SYNCID, "eins"));
1567            // read written value:
1568            TEST_EXPECT_RESULT__NOERROREXPORTED(value = GB_read_sync_value(gb_main, SYNCID));
1569            TEST_EXPECT_EQUAL(value, "eins");
1570
1571            // change value:
1572            TEST_EXPECT_NO_ERROR(GB_write_sync_value(gb_main, SYNCID, "zwei"));
1573            // read written value:
1574            TEST_EXPECT_RESULT__NOERROREXPORTED(value = GB_read_sync_value(gb_main, SYNCID));
1575            TEST_EXPECT_EQUAL(value, "zwei");
1576        }
1577
1578        // save database (for test below)
1579        TEST_EXPECT_NO_ERROR(GB_save_as(gb_main, db_save_name, "b"));
1580        GB_close(gb_main);
1581    }
1582
1583    {
1584        // reload database
1585        GBDATA *gb_main = GB_open(db_save_name, "r");
1586
1587        {
1588            GB_transaction ta(gb_main);
1589            GB_CSTR value;
1590
1591            TEST_EXPECT_RESULT__NOERROREXPORTED(value = GB_read_sync_value(gb_main, SYNCID));
1592            TEST_EXPECT_EQUAL(value, ""); // test value of used sync entry has vanished
1593        }
1594
1595        GB_close(gb_main);
1596    }
1597
1598    // delete saved database
1599    TEST_EXPECT_NO_ERROR(GB_delete_database(db_save_name));
1600}
1601
1602#endif // UNIT_TESTS
1603
Note: See TracBrowser for help on using the repository browser.