source: branches/stable/ARBDB/adsocket.cxx

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