source: tags/arb_5.2/ARBDB/adsocket.c

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