Back to index

tetex-bin  3.0
util.c
Go to the documentation of this file.
00001 /*========================================================================*\
00002 
00003 Copyright (c) 1990-2004  Paul Vojta and others
00004 
00005 Permission is hereby granted, free of charge, to any person obtaining a copy
00006 of this software and associated documentation files (the "Software"), to
00007 deal in the Software without restriction, including without limitation the
00008 rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
00009 sell copies of the Software, and to permit persons to whom the Software is
00010 furnished to do so, subject to the following conditions:
00011 
00012 The above copyright notice and this permission notice shall be included in
00013 all copies or substantial portions of the Software.
00014 
00015 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
00016 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
00017 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
00018 PAUL VOJTA OR ANY OTHER AUTHOR OF THIS SOFTWARE BE LIABLE FOR ANY CLAIM,
00019 DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
00020 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
00021 OTHER DEALINGS IN THE SOFTWARE.
00022 
00023 NOTE:
00024        xdvi is based on prior work, as noted in the modification history
00025        in xdvi.c.
00026 
00027 \*========================================================================*/
00028 
00029 #include "xdvi-config.h"
00030 
00031 #include <sys/types.h> /* ZLB: must be before sys/socket.h for IRIX 5.3 */
00032 #include <sys/socket.h>
00033 #include <sys/file.h>       /* this defines FASYNC */
00034 #include <sys/ioctl.h>      /* this defines SIOCSPGRP and FIOASYNC */
00035 #include <sys/wait.h>       /* this defines WIFEXITED and WEXITSTATUS */
00036 
00037 #include "xdvi.h"
00038 #include "hypertex.h"
00039 #include "dvi-init.h"
00040 #include "special.h"
00041 #include "string-utils.h"
00042 
00043 #include "kpathsea/tex-file.h"
00044 
00045 #include "events.h"
00046 #include "util.h"
00047 #include "x_util.h"
00048 #include "message-window.h"
00049 #include "search-internal.h"
00050 #include "encodings.h"
00051 #include "filehist.h"
00052 #include "xm_prefs.h" /* for preferences_changed() */
00053 
00054 #include <errno.h>
00055 
00056 #ifndef HAVE_MEMICMP
00057 #include <ctype.h>
00058 #endif
00059 
00060 #ifdef VMS
00061 #include <rmsdef.h>
00062 #endif /* VMS */
00063 
00064 #ifdef X_NOT_STDC_ENV
00065 extern int errno;
00066 extern void *malloc();
00067 extern void *realloc();
00068 #endif
00069 
00070 
00071 #ifdef DOPRNT /* define this if vfprintf gives you trouble */
00072 #define       vfprintf(stream, message, args)    _doprnt(message, args, stream)
00073 #endif
00074 
00075 #ifdef VMS
00076 #include <rmsdef.h>
00077 #endif /* VMS */
00078 
00079 #ifdef X_NOT_STDC_ENV
00080 extern int errno;
00081 extern void *malloc();
00082 extern void *realloc();
00083 #endif
00084 
00085 
00086 /* if POSIX O_NONBLOCK is not available, use O_NDELAY */
00087 #if !defined O_NONBLOCK && defined O_NDELAY
00088 # define O_NONBLOCK O_NDELAY
00089 #endif
00090 
00091 /* Linux prefers O_ASYNC over FASYNC; SGI IRIX does the opposite.  */
00092 #if !defined(FASYNC) && defined(O_ASYNC)
00093 # define FASYNC O_ASYNC
00094 #endif
00095 
00096 #if !defined(FLAKY_SIGPOLL) && !HAVE_STREAMS && !defined(FASYNC)
00097 # if !defined(SIOCSPGRP) || !defined(FIOASYNC)
00098 #  define FLAKY_SIGPOLL     1
00099 # endif
00100 #endif
00101 
00102 #if !FLAKY_SIGPOLL && HAVE_STREAMS
00103 # include <stropts.h>
00104 
00105 # ifndef S_RDNORM
00106 #  define S_RDNORM S_INPUT
00107 # endif
00108 
00109 # ifndef S_RDBAND
00110 #  define S_RDBAND 0
00111 # endif
00112 
00113 # ifndef S_HANGUP
00114 #  define S_HANGUP 0
00115 # endif
00116 
00117 # ifndef S_WRNORM
00118 #  define S_WRNORM S_OUTPUT
00119 # endif
00120 
00121 #endif /* not FLAKY_SIGPOLL && HAVE_STREAMS */
00122 
00123 #include <sys/param.h>
00124 #include <sys/stat.h>
00125 #include <pwd.h>
00126 #include <errno.h>
00127 
00128 #ifndef MAXSYMLINKS         /* Workaround for Linux libc 4.x/5.x */
00129 #define MAXSYMLINKS 5
00130 #endif
00131 
00132 #if HAVE_ICONV_H
00133 #include <iconv.h>
00134 #endif
00135 
00136 #include <locale.h>
00137 
00138 #if USE_LANGINFO
00139 #include <langinfo.h>
00140 #endif
00141 
00142 #define BUF_SIZE 1024
00143 
00144 /*
00145   Insert item to the list and return the result.
00146 */
00147 struct dl_list *
00148 dl_list_insert(struct dl_list *list, void *item)
00149 {
00150     struct dl_list *new_elem = xmalloc(sizeof *new_elem);
00151     new_elem->item = item;
00152     new_elem->next = NULL;
00153     new_elem->prev = NULL;
00154     
00155     if (list == NULL) {
00156        list = new_elem;
00157     }
00158     else {
00159        /* append after current position */
00160        struct dl_list *ptr = list;
00161        
00162        new_elem->next = ptr->next;
00163        new_elem->prev = ptr;
00164        
00165        if (ptr->next != NULL)
00166            ptr->next->prev = new_elem;
00167        
00168        ptr->next = new_elem;
00169     }
00170     return new_elem;
00171 }
00172 
00173 /*
00174   Return head of the list.
00175 */
00176 struct dl_list *
00177 dl_list_head(struct dl_list *list)
00178 {
00179     for (; list != NULL && list->prev != NULL; list = list->prev) { ; }
00180     return list;
00181 }
00182 
00183 /*
00184   Put a new item at the front of the list (current front is passed in first argument)
00185   and return its position.
00186 */
00187 struct dl_list *
00188 dl_list_push_front(struct dl_list *list, void *item)
00189 {
00190     struct dl_list *new_elem = xmalloc(sizeof *new_elem);
00191     new_elem->item = item;
00192     new_elem->next = NULL;
00193     new_elem->prev = NULL;
00194     
00195     if (list != NULL) { /* prepend to current position */
00196        new_elem->next = list;
00197        list->prev = new_elem;
00198     }
00199     return new_elem;
00200 }
00201 
00202 /*
00203   Truncate list so that current pointer is the last element.
00204 */
00205 struct dl_list *
00206 dl_list_truncate(struct dl_list *list)
00207 {
00208     struct dl_list *ptr = list->next;
00209     struct dl_list *save;
00210 
00211     list->next = NULL;
00212     
00213     while (ptr != NULL) {
00214        save = ptr->next;
00215        free(ptr);
00216        ptr = save;
00217     }
00218     return list;
00219 }
00220 
00221 /*
00222   Truncate list at the head (i.e. remove the first element from it - head must be passed to this list),
00223   and return the result.
00224 */
00225 struct dl_list *
00226 dl_list_truncate_head(struct dl_list *list)
00227 {
00228     struct dl_list *ptr = list->next;
00229     if (list->next != NULL)
00230        list->next->prev = NULL;
00231     free(list);
00232     return ptr;
00233 }
00234 
00235 /*
00236   If the item pointed to by *list isn't the head of the list, remove it,
00237   set *list to the previous item, and return True. Else return False.
00238 */
00239 Boolean
00240 dl_list_remove_item(struct dl_list **list)
00241 {
00242     struct dl_list *ptr = *list; /* item to remove */
00243     if (ptr->prev == NULL)
00244        return False;
00245     ptr->prev->next = ptr->next;
00246     if (ptr->next != NULL)
00247        ptr->next->prev = ptr->prev;
00248     /* update list */
00249     *list = (*list)->prev;
00250     /* remove item */
00251     free(ptr);
00252     
00253     return True;
00254 }
00255 
00256 /*
00257   Remove all items matching compare_func() from list. Must be called
00258   with a pointer to the head of the list, which is also returned.
00259   Returns the number of removed items in `count'.
00260 */
00261 struct dl_list *
00262 dl_list_remove(struct dl_list *list, const void *item,
00263               int *count,
00264               void **removed_item,
00265               Boolean (*compare_func)(const void *item1, const void *item2))
00266 {
00267     struct dl_list *ptr = list;
00268     while (ptr != NULL) {
00269        struct dl_list *next = ptr->next;
00270        if (compare_func(ptr->item, item)) { /* match */
00271            *removed_item = ptr->item;
00272            (*count)++;
00273            if (ptr->prev != NULL) {
00274               ptr->prev->next = ptr->next;
00275            }
00276            else { /* removed first element */
00277               list = list->next;
00278            }
00279 
00280            if (ptr->next != NULL)
00281               ptr->next->prev = ptr->prev;
00282            free(ptr);
00283            ptr = NULL;
00284        }
00285        ptr = next;
00286     }
00287     return list;
00288 }
00289 
00290 
00291 /* NOTE: keep this table in sync with the #defines in xdvi-debug.h! */
00292 struct debug_string_options debug_options[] = {
00293     {  DBG_BITMAP,   "bitmap",     ", " },
00294     {  DBG_DVI,             "dvi",        ", " },
00295     {  DBG_PK,              "pk",         ", " },
00296     {  DBG_BATCH,    "batch",      ", " },
00297     {  DBG_EVENT,    "event",      ", " },
00298     {  DBG_PS,              "ps",         ",\n"},
00299     {  DBG_STAT,     "stat",              ", " },
00300     {  DBG_HASH,     "hash",              ", " },
00301     {  DBG_OPEN,     "open",              ", " },
00302     {  DBG_PATHS,    "paths",      ", " },
00303     {  DBG_EXPAND,   "expand",     ", " },
00304     {  DBG_SEARCH,   "search",     ", " },
00305     {  DBG_KPATHSEA, "kpathsea",   ",\n"},
00306     {  DBG_HTEX,     "htex",              ", " },
00307     {  DBG_SRC_SPECIALS,"src",            ", " },
00308     {  DBG_CLIENT,   "client",     ", " },
00309     {  DBG_T1,              "t1",         ", " },
00310     {  DBG_T1_VERBOSE,      "t1_verbose", ",\n"},
00311     {  DBG_GUI,             "gui",        ", " },
00312     {  DBG_FIND,     "find",              ", " },
00313     {  DBG_FILES,    "files",      ", " },
00314     {  DBG_ALL,             "all",        "\n" },
00315     /* end marker */
00316     {  0,            NULL,         NULL }
00317 };
00318 
00319 /*
00320  *     General utility routines.
00321  */
00322 
00323 /*
00324  * 2-step fopen using close_a_file() if first opening attempt fails with
00325  * `too many open files'.
00326  */
00327 FILE *
00328 try_fopen(const char *fname, const char *mode)
00329 {
00330     FILE *fp = fopen(fname, mode);
00331     if (fp == NULL && (errno == EMFILE || errno == ENFILE)) {
00332        close_a_file();
00333        fp = fopen(fname, mode);
00334     }
00335     return fp;
00336 }
00337 
00338 /*
00339  * Like try_fopen(), for fdopen().
00340  */
00341 FILE *
00342 try_fdopen(int fd, const char *mode)
00343 {
00344     FILE *fp = fdopen(fd, mode);
00345     if (fp == NULL && (errno == EMFILE || errno == ENFILE)) {
00346        close_a_file();
00347        fp = fdopen(fd, mode);
00348     }
00349     return fp;
00350 }
00351 
00352 
00353 /*
00354  * Like try_fopen(), for open().
00355  */
00356 int
00357 try_open(const char *fname, int flags)
00358 {
00359     int fd = open(fname, flags);
00360     if (fd < 0 && (errno == EMFILE || errno == ENFILE)) {
00361        close_a_file();
00362        fd = open(fname, flags);
00363     }
00364     return fd;
00365 }
00366 
00367 /*
00368  * Like try_fopen(), for open() with 3 arguments.
00369  */
00370 int
00371 try_open_mode(const char *fname, int flags, mode_t mode)
00372 {
00373     int fd = open(fname, flags, mode);
00374     if (fd < 0 && (errno == EMFILE || errno == ENFILE)) {
00375        close_a_file();
00376        fd = open(fname, flags);
00377     }
00378     return fd;
00379 }
00380 
00381 
00382 /*
00383  * This routine should be used for all exits. (SU: This is different
00384  * from non-k xdvi, where it's only used for `non-early' exits; all
00385  * routines called here should be aware of their own state and either
00386  * perform cleanup or just return, unless xdvi_exit() itself checks for
00387  * the status).
00388  */
00389 
00390 void
00391 xdvi_exit(int status)
00392 {
00393     /* do the following only if the window has been opened: */
00394     if (globals.widgets.top_level != 0 && XtIsRealized(globals.widgets.top_level)) {
00395        char *filehist;
00396        file_history_set_page(current_page);
00397        filehist = file_history_get_list();
00398        store_preference(NULL, "fileHistory", "%s", filehist);
00399        free(filehist);
00400 
00401 #if MOTIF
00402        if (preferences_changed()) {
00403            return;
00404        }
00405 /*      else { */
00406 /*     fprintf(stderr, "Preferences not changed.\n"); */
00407 /*      } */
00408 #endif
00409        /* try to save user preferences, unless we're exiting with an error */
00410        if (status == 0 && !save_user_preferences(True))
00411            return;
00412     
00413        /* Clean up the "xdvi windows" property in the root window.  */
00414        update_window_property(XtWindow(globals.widgets.top_level), False);
00415     }
00416 
00417 #if PS
00418     ps_destroy();
00419 #endif
00420     remove_tmp_dvi_file();
00421 
00422     close_iconv();
00423 
00424     exit(status);
00425 }
00426 
00427 /*
00428  *     invoked on SIGSEGV: try to stop gs before aborting, to prevent gs
00429  *     running on with 100% CPU consumption - this can be annoying during
00430  *     testing. We'd also like to clean up the "xdvi windows" property in
00431  *     the root window, but it might be already too late to talk to the
00432  *     X server here (the code can also be triggered by X errors).
00433  */
00434 void
00435 do_abort()
00436 {
00437 #if PS
00438     ps_destroy();
00439 #endif
00440     /*     if (globals.widgets.top_level) */
00441     /*        exit_clean_windows(); */
00442     abort();
00443 }
00444 
00445 /*
00446   Expand leading ~ or ~user in path to the actual homedirectory of the user.
00447   Returns either NULL if unsuccessful, or result in freshly allocated string.
00448   Bugs: ~user doesn't work with NIS/yellow pages.
00449 */
00450 char *
00451 expand_homedir(const char *path)
00452 {
00453     char *resolved = NULL;
00454     const char *orig_path = path;
00455     UNUSED(orig_path); /* if HAVE_GETPWNAM or HAVE_GETPWUID && HAVE_GETUID */
00456 
00457     if (path == NULL)
00458        return NULL;
00459     
00460     /* if path doesn't start with ~, just return a copy of path */
00461     if (*path != '~')
00462        return xstrdup(path);
00463 
00464     /* expand ~/ or ~user */
00465     path++;
00466     if (*path == '/') { /* expand `~/' to `$HOME/' */
00467        char *homedir = getenv("HOME");
00468        if (homedir != NULL) {
00469            resolved = xstrdup(homedir);
00470            resolved = xstrcat(resolved, path);
00471        }
00472        else {
00473 #if defined(HAVE_GETPWUID) && defined(HAVE_GETUID)
00474            /* homedir not set */
00475            struct passwd *entry = getpwuid(getuid());
00476            if (entry != NULL) {
00477               homedir = entry->pw_dir;
00478               if (homedir != NULL) {
00479                   resolved = xstrdup(homedir);
00480                   resolved = xstrcat(resolved, path);
00481               }
00482               else {
00483                   XDVI_ERROR((stderr, "getpwnam returned NULL for entry->pw_dir: %s", strerror(errno)));
00484                   return NULL;
00485               }
00486            }
00487            else {
00488               XDVI_ERROR((stderr, "getpwnam failed: %s", strerror(errno)));
00489               return NULL;
00490            }
00491 #else
00492            popup_message(globals.widgets.top_level,
00493                        MSG_WARN,
00494                        NULL,
00495                        "$HOME not set, and getpwuid() or getuid() not supported - could not expand \"%s\".",
00496                        orig_path);
00497            return NULL;
00498 #endif
00499        }
00500        TRACE_GUI((stderr, "resolved: |%s|", resolved));
00501        return resolved;
00502     }
00503     else { /* ~user -> try getpwnam() to get homedir */
00504 #ifdef HAVE_GETPWNAM
00505        struct passwd *entry;
00506        char *separator = strchr(path, '/');
00507        char *homedir;
00508 
00509        TRACE_GUI((stderr, "separator is: |%s|, len of username: %d",
00510                  separator, (int)(separator - path)));
00511        if (separator == NULL)
00512            return NULL;
00513 
00514        resolved = xmalloc(separator - path + 1);
00515        memcpy(resolved, path, separator - path);
00516        resolved[separator - path] = '\0';
00517 
00518        TRACE_GUI((stderr, "username is: |%s|", resolved));
00519        entry = getpwnam(resolved);
00520        if (entry == NULL) {
00521            XDVI_ERROR((stderr, "getpwnam failed: %s", strerror(errno)));
00522            return NULL;
00523        }
00524        TRACE_GUI((stderr, "homedir of user is: |%s|", entry->pw_dir));
00525        homedir = entry->pw_dir;
00526            
00527        free(resolved);
00528        resolved = xstrdup(homedir);
00529        resolved = xstrcat(resolved, path + (separator - path));
00530        TRACE_GUI((stderr, "resolved: |%s|", resolved));
00531        return resolved;
00532 #else
00533        popup_message(globals.widgets.top_level,
00534                     MSG_WARN,
00535                     NULL,
00536                     "Expanding \"%s\" failed: This platform doesn't support getpwnam().",
00537                     orig_path);
00538        return NULL;
00539 #endif
00540     }
00541 
00542 }
00543 
00544 /*
00545   realpath implementation if no implementation available.
00546   Try to canonicalize path, removing `~', `.' and `..' and expanding symlinks.
00547   
00548   Adopted from wu-ftpd's fb_realpath (in realpath.c), but without the
00549   seteuid(0) stuff (which we don't need, since we never change into
00550   directories or read files the user has no permissions for), and
00551   without the ugly goto(). Special care has been taken to avoid buffer
00552   overflows (e.g. this version is not affected by
00553   http://isec.pl/vulnerabilities/isec-0011-wu-ftpd.txt).
00554   
00555   `resolved' should be a buffer of size MAXPATHLEN.  */
00556 
00557 char *
00558 my_realpath(const char *path, char *resolved)
00559 {
00560     struct stat sb;
00561     int n;
00562     /*     char *p, *q, *tmp; */
00563     char *base;
00564     char tmpbuf[MAXPATHLEN];
00565     int symlinks = 0;
00566 #ifdef HAVE_FCHDIR
00567     int fd;
00568 #else
00569     char cwd[MAXPATHLEN];
00570 #endif
00571 
00572     /* Save cwd for going back later */
00573 #ifdef HAVE_FCHDIR
00574     if ((fd = try_open(".", O_RDONLY)) < 0)
00575        return NULL;
00576 #else /* HAVE_FCHDIR */
00577     if (
00578 # ifdef HAVE_GETCWD
00579        getcwd(cwd, MAXPATHLEN)
00580 # else
00581        getwd(cwd)
00582 # endif
00583        == NULL)
00584        return NULL;
00585 #endif /* HAVE_FCHDIR */
00586 
00587     if (strlen(path) + 1 > MAXPATHLEN) {
00588        errno = ENAMETOOLONG;
00589        return NULL;
00590     }
00591     
00592     strcpy(resolved, path);
00593 
00594     for (;;) { /* loop for resolving symlinks in base name */
00595        /* get base name and dir name components */
00596        char *p = strrchr(resolved, '/');
00597        if (p != NULL) {
00598            base = p + 1;
00599            if (p == resolved) {
00600               /* resolved is in root dir; this must be treated as a special case,
00601                  since we can't chop off at `/'; instead, just use the `/':
00602               */
00603               p = "/";
00604            }
00605            else {
00606               /* not in root dir; chop off path name at slash */
00607               while (p > resolved && *p == '/') /* for multiple trailing slashes */
00608                   p--;
00609               *(p + 1) = '\0';
00610               p = resolved;
00611            }
00612 
00613            /* change into that dir */
00614            if (chdir(p) != 0)
00615               break;
00616        }
00617        else /* no directory component */
00618            base = resolved;
00619 
00620        /* resolve symlinks or directory names (not used in our case) in basename */
00621        if (*base != '\0') {
00622            if (
00623 #ifdef HAVE_LSTAT
00624               lstat(base, &sb)
00625 #else
00626               stat(base, &sb)
00627 #endif
00628               == 0) {
00629 #ifdef HAVE_LSTAT
00630               if (S_ISLNK(sb.st_mode)) { /* if it's a symlink, iterate for what it links to */
00631                   if (++symlinks > MAXSYMLINKS) {
00632                      errno = ELOOP;
00633                      break;
00634                   }
00635 
00636                   if ((n = readlink(base, resolved, MAXPATHLEN)) < 0)
00637                      break;
00638 
00639                   resolved[n] = '\0';
00640                   continue;
00641               }
00642 #endif /* HAVE_LSTAT */
00643               if (S_ISDIR(sb.st_mode)) { /* if it's a directory, go there */
00644                   if (chdir(base) != 0)
00645                      break;
00646 
00647                   base = "";
00648               }
00649            }
00650        }
00651 
00652        /* Now get full pathname of current directory and concatenate it with saved copy of base name */
00653        strcpy(tmpbuf, base); /* cannot overrun, since strlen(base) <= strlen(path) < MAXPATHLEN */
00654        if (
00655 #ifdef HAVE_GETCWD
00656            getcwd(resolved, MAXPATHLEN)
00657 #else
00658            getwd(resolved)
00659 #endif
00660            == NULL)
00661            break;
00662 
00663        /* need to append a slash if resolved is not the root dir */
00664        if (!(resolved[0] == '/' && resolved[1] == '\0')) {
00665            if (strlen(resolved) + 2 > MAXPATHLEN) {
00666               errno = ENAMETOOLONG;
00667               break;
00668            }
00669            strcat(resolved, "/");
00670        }
00671 
00672        if (*tmpbuf) {
00673            if (strlen(resolved) + strlen(tmpbuf) + 1 > MAXPATHLEN) {
00674               errno = ENAMETOOLONG;
00675               break;
00676            }
00677            strcat(resolved, tmpbuf);
00678        }
00679 
00680        /* go back to where we came from */
00681 #ifdef HAVE_FCHDIR
00682        fchdir(fd);
00683        close(fd);
00684 #else
00685        chdir(cwd);
00686 #endif
00687        return resolved;
00688     }
00689 
00690     /* arrive here in case of error: go back to where we came from, and return NULL */
00691 #ifdef HAVE_FCHDIR
00692     fchdir(fd);
00693     close(fd);
00694 #else
00695     chdir(cwd);
00696 #endif
00697     return NULL;
00698 }
00699 
00700 
00701 #ifndef KPATHSEA
00702 
00703 /*
00704  *     Either (re)allocate storage or fail with explanation.
00705  */
00706 
00707 void *
00708 xmalloc(unsigned size)
00709 {
00710     void *mem = malloc(size);
00711 
00712     if (mem == NULL)
00713        XDVI_FATAL((stderr, "Out of memory (allocating %u bytes).", size));
00714     return mem;
00715 }
00716 
00717 void *
00718 xrealloc(void *where, unsigned size)
00719 {
00720     void *mem = realloc(where, size);
00721 
00722     if (mem == NULL)
00723        XDVI_FATAL((stderr, "Out of memory (reallocating %u bytes).", size));
00724     return mem;
00725 }
00726 
00727 
00728 /*
00729  *     Allocate a new string.
00730  */
00731 
00732 char *
00733 xstrdup(const char *str)
00734 {
00735     size_t len;
00736     char *new;
00737 
00738     ASSERT(fprintf(stderr, "Test assertion!\n") && 1 == 0);
00739     ASSERT(str != NULL, "");
00740     len = strlen(str) + 1;
00741     new = xmalloc(len);
00742     memcpy(new, str, len);
00743     return new;
00744 }
00745 
00746 
00747 
00748 /*
00749  *     Allocate a new string.  The second argument is the length.
00750  */
00751 
00752 char *
00753 xmemdup(const char *str, size_t len)
00754 {
00755     char *new;
00756 
00757     new = xmalloc(len);
00758     memcpy(new, str, len);
00759     return new;
00760 }
00761 
00762 #endif /* not KPATHSEA */
00763 
00764 
00765 /*
00766  * Append str2 to str1, reallocating str1 as neccessary.
00767  * Note that this modifies str1, so if you want a clean
00768  * new copy, use xstrdup() first, then xstrcat().
00769  * str1 should be either NULL, or a valid char * that has
00770  * previously been malloc()ed.
00771  */
00772 
00773 char *
00774 xstrcat(char *str1, const char *str2)
00775 {
00776     size_t len1 = (str1 == NULL) ? 0 : strlen(str1);
00777     size_t len2 = strlen(str2) + 1;
00778 
00779     str1 = xrealloc(str1, len1 + len2);
00780     memcpy(str1 + len1, str2, len2);
00781     return str1;
00782 }
00783 
00784 
00785 /*
00786  *     Allocate bitmap for given font and character
00787  */
00788 
00789 void
00790 alloc_bitmap(struct bitmap *bitmap)
00791 {
00792     unsigned int size;
00793 
00794     /* fprintf(stderr, "allocating bitmap of size %u x %u\n", bitmap->w, bitmap->h); */
00795     /* width must be multiple of <arch-defined> bits for raster_op */
00796     bitmap->bytes_wide = ROUNDUP((int)bitmap->w, BMBITS) * BMBYTES;
00797     size = bitmap->bytes_wide * bitmap->h;
00798     bitmap->bits = xmalloc(size != 0 ? size : 1);
00799 }
00800 
00801 
00802 #ifndef HAVE_MEMICMP
00803 /*
00804  * Case-insensitive version of memcmp().  This code assumes that the second
00805  * argument (i.e. what is being compared with) is lower case.
00806  */
00807 
00808 int
00809 memicmp(const char *s1, const char *s2, size_t n)
00810 {
00811     while (n > 0) {
00812        int i = tolower((int)*s1) - *s2;
00813        if (i != 0)
00814            return i;
00815        ++s1;
00816        ++s2;
00817        --n;
00818     }
00819     return 0;
00820 }
00821 #endif /* HAVE_MEMICMP */
00822 
00823 /*
00824  * Try to close the pixel file for the least recently used font.
00825  * Invoked when we've run out of file descriptors.
00826  */
00827 void
00828 close_a_file(void)
00829 {
00830     struct font *fontp;
00831     unsigned short oldest = USHRT_MAX;
00832     struct font *f = NULL;
00833 
00834     if (globals.debug & DBG_OPEN)
00835        puts("Calling close_a_file().");
00836 
00837     for (fontp = font_head; fontp != NULL; fontp = fontp->next) {
00838        if (fontp->file != NULL && fontp->timestamp <= oldest) {
00839            f = fontp;
00840            oldest = fontp->timestamp;
00841        }
00842     }
00843     /* fprintf(stderr, "oldest = %u\n", oldest); */
00844     if (f == NULL)
00845        XDVI_FATAL((stderr, "Can't find an open pixel file to close"));
00846     fclose(f->file);
00847     f->file = NULL;
00848 }
00849 
00850 /*
00851  * Open a file in the given mode. We use XFOPEN since xfopen is already
00852  * usurpated by kpathsea's xfopen.c, which just exits rather ungracefully
00853  * if it detects a NULL return value; most certainly NOT what we want here.
00854  */
00855 FILE *
00856 XFOPEN(const char *path, const char *mode)
00857 {
00858     FILE *fp = NULL;
00859 #ifdef TESTING_OPEN_FILES
00860     fprintf(stderr, "trying to open |%s|\n", path);
00861 #endif
00862     if ((fp = try_fopen(path, mode)) == NULL && (errno == EMFILE || errno == ENFILE)) {
00863        XDVI_FATAL((stderr, "too many open files"));
00864     }
00865     return fp;
00866 }
00867 
00868 /*
00869  *     Create a pipe, closing a file if necessary.
00870  *     We use socketpair() instead of pipe() because several operating
00871  *     systems don't support SIGPOLL/SIGIO on pipes:
00872  *            SGI IRIX 6.5  F_SETOWN not implemented
00873  *            Linux 2.4.2   Not supported
00874  */
00875 
00876 int
00877 xpipe(int *fd)
00878 {
00879     int       retval;
00880     
00881     for (;;) {
00882        retval = socketpair(AF_UNIX, SOCK_STREAM, 0, fd);
00883        if (retval == 0) { /* success */
00884            break;
00885        }
00886        if ((errno != EMFILE && errno != ENFILE)) {
00887            /* failed, but not because of too many files */
00888            break;
00889        }
00890        close_a_file();
00891     }
00892     return retval;
00893 }
00894 
00895 
00896 
00897 /*
00898  *
00899  *      Read size bytes from the FILE fp, constructing them into a
00900  *      signed/unsigned integer.
00901  *
00902  */
00903 
00904 unsigned long
00905 get_bytes(FILE *fp, int size)
00906 {
00907     long x = 0;
00908 
00909     while (size--)
00910        x = (x << 8) | get_byte(fp);
00911     return x;
00912 }
00913 
00914 long
00915 get_lbytes(FILE *fp, int size)
00916 {
00917     long x;
00918 
00919 #if    __STDC__
00920     x = (signed char)getc(fp);
00921 #else
00922     x = (unsigned char)getc(fp);
00923     if (x & 0x80)
00924        x -= 0x100;
00925 #endif
00926     while (--size)
00927        x = (x << 8) | get_byte(fp);
00928     return x;
00929 }
00930 
00931 
00932 
00933 /*
00934  *     Create a temporary file and return its fd.  Also put its filename
00935  *     in str.  Create str if it's NULL.
00936  */
00937 
00938 #ifndef P_tmpdir
00939 #define       P_tmpdir      "/tmp"
00940 #endif
00941 
00942 static const char tmp_suffix[] = "/xdvi-XXXXXX";
00943 
00944 int
00945 xdvi_temp_fd(char **str)
00946 {
00947     int fd;
00948     char *p;
00949     size_t len;
00950     static const char *template = NULL;
00951 #if !HAVE_MKSTEMP
00952     static unsigned long seed;
00953     static char letters[] =
00954        "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789._";
00955     char *p1;
00956 #endif
00957 
00958     if (*str != NULL) {
00959        p = *str;
00960 
00961        /* O_EXCL is there for security (if root runs xdvi) */
00962        if (!((fd = try_open_mode(p, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR)) == -1
00963              && errno == EEXIST))
00964            return fd;
00965 #if HAVE_MKSTEMP
00966        memcpy(p + strlen(p) - 6, "XXXXXX", 6);
00967 #endif
00968     }
00969     else {
00970        if (template == NULL) {
00971            const char *ourdir;
00972 
00973            ourdir = getenv("TMPDIR");
00974            if (ourdir == NULL || access(ourdir, W_OK) < 0) {
00975               ourdir = P_tmpdir;
00976               if (access(ourdir, W_OK) < 0)
00977                   ourdir = ".";
00978            }
00979            len = strlen(ourdir);
00980            if (len > 0 && ourdir[len - 1] == '/')
00981               --len;
00982            template = p = xmalloc(len + sizeof tmp_suffix);
00983            memcpy(p, ourdir, len);
00984            memcpy(p + len, tmp_suffix, sizeof tmp_suffix);
00985 #if !HAVE_MKSTEMP
00986            seed = 123456789 * time(NULL) + 987654321 * getpid();
00987 #endif
00988        }
00989        *str = p = xstrdup(template);
00990     }
00991 
00992 #if HAVE_MKSTEMP
00993     fd = mkstemp(p);
00994     if (fd == -1 && (errno == EMFILE || errno == ENFILE)) {
00995        close_a_file();
00996        memcpy(p + strlen(p) - 6, "XXXXXX", 6);
00997        fd = mkstemp(p);
00998     }
00999 #else
01000     p1 = p + strlen(p) - 6;
01001     for (;;) {
01002        unsigned long s = ++seed;
01003        char *p2;
01004 
01005        for (p2 = p1 + 5; p2 >= p1; --p2) {
01006            *p2 = letters[s & 63];
01007            s >>= 6;
01008        }
01009        if (!((fd = try_open_mode(p, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR)) == -1
01010              && errno == EEXIST))
01011            break;
01012     }
01013 #endif
01014     return fd;
01015 }
01016 
01017 
01018 /* print a GUI error message for childs that exited with an errorcode */
01019 void
01020 handle_child_exit(int status, struct xchild *this)
01021 {
01022     char *err_msg = NULL;
01023     
01024     /* if child exited with error and xio struct is available for child,
01025        print error text */
01026     if (this->io != NULL
01027        && (WIFEXITED(status) != 0)
01028        && (WEXITSTATUS(status) != 0)
01029        && (err_msg = (this->io->read_proc)(this->io->fd)) != NULL) {
01030 
01031        if (this->name == NULL) {
01032            popup_message(globals.widgets.top_level,
01033                        MSG_WARN,
01034                        NULL,
01035                        err_msg[0] == '\0' ? "An unknown error occurred" : err_msg);
01036        }
01037        else {
01038            popup_message(globals.widgets.top_level,
01039                        MSG_WARN,
01040                        "Xdvi tries to collect all messages from STDERR. "
01041                        "When no useful error message is available "
01042                        "(e.g. because the program has written to STDOUT instead), "
01043                        "try to run the command again from the command line "
01044                        "to find out what the problem was.",
01045                        "Command \"%s\" exited with error code %d\n%s\n",
01046                        this->name, WEXITSTATUS(status), err_msg);
01047        }
01048        free(err_msg);
01049     }
01050     free(this->name);
01051     free(this->io);
01052     free(this);
01053 }
01054 
01055 static void
01056 dummy_write_proc(int fd)
01057 {
01058     UNUSED(fd);
01059     fprintf(stderr, "============== dummy_write_proc called for fd %d\n", fd);
01060 }
01061 
01062 /*
01063  * read what child printed to fd (should be set up to be stderr).
01064  * Allocates and returns error text; caller is responsible for free()ing it
01065  * afterwards.
01066  */
01067 char *
01068 read_child_error(int fd)
01069 {
01070     int bytes = 0, buf_old_size = 0, buf_new_size = 0;
01071     char tmp_buf[BUF_SIZE];
01072     char *err_buf = xstrdup("");
01073     char *ret;
01074     /* collect stderr messages into err_buf */
01075     while ((bytes = read(fd, tmp_buf, BUF_SIZE - 1)) > 0) {
01076        buf_old_size = buf_new_size;
01077        buf_new_size += bytes;
01078        err_buf = xrealloc(err_buf, buf_new_size + 1);
01079        memcpy(err_buf + buf_old_size, tmp_buf, buf_new_size - buf_old_size);
01080        err_buf[buf_new_size] = '\0';
01081     }
01082 
01083     close(fd);
01084     ret = escape_format_arg(err_buf); /* this allocates ret */
01085     free(err_buf);
01086     return ret;
01087 }
01088 
01089 /*
01090  * Fork a child process. If exit_proc is NULL, the process' error messages
01091  * will be collected and a window popped up for GUI display. If exit_proc
01092  * is non-NULL, it should be a function that does something reasonable
01093  * with the error messages itself. It should also free the xchild struct.
01094  *
01095  * If dirname != NULL, the child will chdir into dirname before running the
01096  * command.
01097  */
01098 Boolean
01099 fork_process(const char *proc_name, Boolean redirect_stdout,
01100             const char *dirname,
01101             childProcT exit_proc, void *data,
01102             char *const argv[])
01103 {
01104     int i, pid;
01105     struct xchild *my_child = xmalloc(sizeof *my_child);
01106     struct xio *my_io = xmalloc(sizeof *my_io);
01107     int err_pipe[2];
01108     char *volatile buf = xstrdup("");
01109 
01110     for (i = 0; argv[i] != NULL; i++) {
01111        TRACE_GUI((stderr, "argv: |%s|", argv[i]));
01112        buf = xstrcat(buf, argv[i]);
01113        buf = xstrcat(buf, " ");
01114     }
01115     
01116     if (i > 0)
01117        buf[strlen(buf) - 1] = '\0'; /* chop off trailing space */
01118 
01119     TRACE_GUI((stderr, "forking: |%s|", buf));
01120     
01121     /* flush output buffers to avoid double buffering (i.e. data
01122        waiting in the output buffer being written twice, by the parent
01123        and the child) */
01124     fflush(stdout);
01125     fflush(stderr);
01126 
01127     if (pipe(err_pipe) < 0) {
01128        XDVI_FATAL((stderr, "pipe error"));
01129     }
01130 
01131     switch (pid = vfork()) {
01132     case -1:  /* forking error */
01133        perror("vfork");
01134        close(err_pipe[0]);
01135        close(err_pipe[1]);
01136        return False;
01137     case 0:   /* child */
01138        if (dirname != NULL)
01139            chdir(dirname);
01140        if (globals.debug & DBG_FILES) {
01141            char path[MAXPATHLEN];
01142            getcwd(path, MAXPATHLEN);
01143            fprintf(stderr, "Directory of running `%s': `%s'\n",
01144                   proc_name, path);
01145        }
01146        /* FIXME: There's a bug which prevents this from working
01147           with xdvi as child: Whenever xdvi tries to print to stderr,
01148           this will hang the child forever. Closing all other file
01149           descriptors, as in the #if TRY_FIX regions, seems to fix
01150           this, but it also loses all output ...
01151         */
01152 #if TRY_FIX
01153        close(0);
01154        close(1);
01155 #endif /* TRY_FIX */
01156        close(err_pipe[0]);  /* no reading from stderr */
01157 
01158        /* redirect writing to stderr */
01159        if (dup2(err_pipe[1], STDERR_FILENO) != STDERR_FILENO) {
01160            perror("dup2 for stderr");
01161            _exit(EXIT_FAILURE);
01162            return False;    /* make compiler happy */
01163        }
01164        if (redirect_stdout) {
01165            /* also redirect writing to stdout */
01166            if (dup2(err_pipe[1], STDOUT_FILENO) != STDOUT_FILENO) {
01167               perror("dup2 for stdout");
01168               _exit(EXIT_FAILURE);
01169               return False; /* make compiler happy */
01170            }
01171        }
01172 
01173 #if TRY_FIX
01174        /* close all remaining descriptors */
01175        i = 2;
01176        while (i < 256 /* TODO: better handling of OPEN_MAX; see Stevens p. 43 */) {
01177            close(i++);
01178        }
01179 #endif /* TRY_FIX */
01180        execvp(proc_name, argv);
01181 
01182        /* arrive here only if execvp failed */
01183        fprintf(stderr, "%s: Execution of %s failed.\n", globals.program_name, proc_name);
01184        fflush(stderr);
01185        close(err_pipe[1]);
01186        _exit(EXIT_FAILURE);
01187        return False;        /* make compiler happy */
01188     default:  /* parent */
01189        close(err_pipe[1]);  /* no writing to stderr */
01190 
01191        my_io->next = NULL;
01192        my_io->fd = err_pipe[0];
01193        my_io->xio_events = XIO_IN;
01194 #if HAVE_POLL
01195        my_io->pfd = NULL;
01196 #endif
01197        my_io->read_proc = read_child_error;
01198        my_io->write_proc = dummy_write_proc;
01199        
01200        my_child->next = NULL;
01201        my_child->pid = pid;
01202        my_child->name = buf;
01203        my_child->data = data;
01204        if (exit_proc == NULL) { /* use default exit procedure */
01205            my_child->proc = handle_child_exit;
01206        }
01207        else {
01208            my_child->proc = exit_proc;
01209        }
01210        my_child->io = my_io;
01211        
01212        set_chld(my_child);
01213        
01214        return True;
01215     }
01216 }
01217 
01218 
01219 
01220 /*
01221  *     Prepare the file descriptor to generate SIGPOLL/SIGIO events.
01222  *     If called with a True argument, set it up for non-blocking I/O.
01223  */
01224 
01225 void
01226 prep_fd(int fd, wide_bool noblock)
01227 {
01228     /* Set file descriptor for non-blocking I/O */
01229     if (noblock)
01230        (void) fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);
01231     
01232 #if !FLAKY_SIGPOLL
01233 # if HAVE_STREAMS
01234     if (isastream(fd) > 0) {
01235        if (ioctl(fd, I_SETSIG,
01236                 S_RDNORM | S_RDBAND | S_HANGUP | S_WRNORM) == -1)
01237            perror("xdvi: ioctl I_SETSIG");
01238     }
01239     else
01240 # endif
01241        {
01242 # ifdef FASYNC
01243            if (fcntl(fd, F_SETOWN, getpid()) == -1)
01244               perror("xdvi: fcntl F_SETOWN");
01245            if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | FASYNC) == -1)
01246               perror("xdvi: fcntl F_SETFL");
01247 # elif defined SIOCSPGRP && defined FIOASYNC
01248            /* For HP-UX B.10.10 and maybe others.  See "man 7 socket".  */
01249            int arg;
01250            
01251            arg = getpid();
01252            if (ioctl(fd, SIOCSPGRP, &arg) == -1)
01253               perror("xdvi: ioctl SIOCSPGRP");
01254            arg = 1;
01255            if (ioctl(fd, FIOASYNC, &arg) == -1)
01256               perror("xdvi: ioctl FIOASYNC");
01257 # endif
01258        }
01259 #endif /* not FLAKY_SIGPOLL */
01260 }
01261 
01262 
01263 /* APS Pointer locator: */
01264 /* Return screen positions */
01265 Boolean
01266 pointerlocate(int *xpos, int *ypos)
01267 {
01268     Window root, child;
01269     int root_x, root_y;
01270     unsigned int keys_buttons;
01271 
01272     if (!XtIsRealized(globals.widgets.top_level))
01273        return False;
01274     
01275     return XQueryPointer(DISP, mane.win, &root, &child,
01276                       &root_x, &root_y, xpos, ypos, &keys_buttons);
01277 }
01278 
01279 unsigned long
01280 parse_debugging_string(const char *arg)
01281 {
01282     int retval = 0;
01283     const char *curr, *last;
01284     size_t i;
01285     
01286     curr = last = arg;
01287 
01288     while (curr != '\0') {
01289        Boolean matched = False;
01290 
01291        while (isspace((int)*curr))
01292            curr++;
01293        for (i = 0; debug_options[i].description != NULL; i++) {
01294            /* match on length of passed argument, to allow for abbreviations */
01295            if (memicmp(curr,
01296                      debug_options[i].description,
01297                      strlen(debug_options[i].description)) == 0
01298               && (curr[strlen(debug_options[i].description)] == '\0'
01299                   || curr[strlen(debug_options[i].description)] == ','
01300                   || isspace((int)curr[strlen(debug_options[i].description)]))) {
01301               matched = True;
01302               retval |= debug_options[i].bitmap;
01303               fprintf(stderr, "Debugging option: \"%s\" = \"%s\", debug: %d\n",
01304                      curr, debug_options[i].description, retval);
01305            }
01306        }
01307        if (!matched) {
01308            char *tempstr = xstrdup(curr);
01309            char *test;
01310            if ((test = strchr(curr, ',')) != NULL) {
01311               *test = '\0';
01312            }
01313            XDVI_WARNING((stderr, "Ignoring unknown debugging option \"%s\". Valid options are:\n", tempstr));
01314            for (i = 0; debug_options[i].description != NULL; i++) {
01315               fprintf(stderr, "`%s'%s",
01316                      debug_options[i].description,
01317                      debug_options[i].help_formatting);
01318            }
01319            fprintf(stderr, "\n");
01320            free(tempstr);
01321        }
01322        curr = strchr(curr, ',');
01323        if (curr != NULL)
01324            curr++;
01325     }
01326 
01327     return retval;
01328 }
01329 
01330 unsigned long
01331 parse_debugging_option(const char *ptr)
01332 {
01333     if (ptr == NULL)
01334        return 0L;
01335     else if (isdigit((int)*ptr)) {
01336        if (resource.debug_arg == NULL)
01337            return DBG_ALL; /* per default debug everything */
01338        else
01339            return strtol(resource.debug_arg, (char **)NULL, 10);
01340     } else if (*ptr == '-')
01341        return DBG_ALL;
01342     else return parse_debugging_string(ptr);
01343 }
01344 
01345 /* determine average width of a font */
01346 int
01347 get_avg_font_width(XFontStruct *font)
01348 {
01349     int width;
01350 
01351     assert(font != NULL);
01352     width = font->max_bounds.width + font->min_bounds.width / 2;
01353     if (width == 0) {
01354        /* if min_bounds.width = -max_bounds.width, we probably
01355           have a scalable TT font; try to determine its actual
01356           width by measuring the letter `x':
01357        */
01358        width = XTextWidth(font, "x", 1);
01359     }
01360     if (width == 0) { /* last resort */
01361        width = font->max_bounds.width / 2;
01362     }
01363     return width;
01364     
01365 }
01366 
01367 /*
01368   Splits LINE from BEGIN to END (not neccessarily null-terminated)
01369   into items separated by SEP, removes leading or trailing whitespace,
01370   and saves the items as newly allocated char*s into the return array.
01371   Returns the number of items that have been saved as RET_ITEMS.
01372   Empty entries are returned as such, i.e. both `abc:' and `abc: '
01373   return RET_ITEMS = 2 and "" as second entry.
01374 */
01375 char **
01376 split_line(const char *line, char sep, size_t begin, size_t end, size_t *ret_items)
01377 {
01378     const char *c_ptr = line + begin;
01379     const char *e_ptr = line + end;
01380     const char *test_end;
01381     
01382     size_t result_cnt = 0;
01383     size_t alloc_len = 0;
01384     size_t len;
01385     const size_t ALLOC_STEP = 8;
01386     char **result_arr = NULL;
01387     
01388     /* create new result item, resizing result_arr as needed
01389      * (an empty string will coun1 as 1 item: "") */
01390     while (result_cnt + 1 >= alloc_len) {
01391        alloc_len += ALLOC_STEP;
01392        result_arr = xrealloc(result_arr, alloc_len * sizeof *result_arr);
01393     }
01394 
01395     while (c_ptr <= e_ptr) {
01396        /* skip leading whitespace */
01397        while (c_ptr < e_ptr && isspace((int)*c_ptr)) {
01398            c_ptr++;
01399        }
01400 
01401        /* find end of current elem, which is either the separator or out of range */
01402        test_end = strchr(c_ptr, sep);
01403        /* skip escaped separators */
01404        while (test_end != NULL && test_end <= e_ptr) {
01405            if (test_end > c_ptr && *(test_end - 1) == '\\') {
01406               test_end = strchr(test_end + 1, sep);
01407            }
01408            else
01409               break;
01410        }
01411        /* if nothing found, use e_ptr */
01412        if (test_end == NULL || test_end > e_ptr) {
01413            test_end = e_ptr;
01414        }
01415 
01416        len = test_end - c_ptr;
01417 
01418        /* skip trailing whitespace */
01419        while (len > 0 && isspace((int)c_ptr[len - 1])) {
01420            len--;
01421        }
01422 
01423        result_arr[result_cnt] = xmalloc(len + 1);
01424        /* copy into result item, skipping the escape '\\' characters */
01425        {
01426            size_t i = 0, j = 0;
01427            while (i < len) {
01428               if (c_ptr[i] == '\\' && c_ptr[i + 1] == sep) /* i + 1 is safe since (i < len) */
01429                   i++;
01430               result_arr[result_cnt][j++] = c_ptr[i++];
01431            }
01432            result_arr[result_cnt][j] = '\0';
01433        }
01434        result_cnt++;
01435        
01436        /* skip to next item */
01437        c_ptr = test_end + 1;
01438     }
01439     result_arr[result_cnt] = NULL; /* null-terminate return array, just to be safe */
01440     *ret_items = result_cnt;
01441     return result_arr;
01442 }
01443 
01444 
01445 /*------------------------------------------------------------
01446  *  find_file
01447  *
01448  *  Arguments:
01449  *   filename - absolute or relative file name
01450  *   statbuf  - buffer to stat filename
01451  *   pathinfo - kpse_file_format_type, only used if a kpathsearch for the
01452  *             file is performed.
01453  *            See  kpathsea/tex-file.h for a list of possible values.
01454  *
01455  *  Returns:
01456  *      expanded filename
01457  *
01458  *  Purpose:
01459  *     Find a file name corresponding to <filename>, possibly
01460  *     expanding it to a full path name; checks if the file
01461  *     exists by stat()ing it; returns NULL if nothing has
01462  *     been found, else the expanded filename in fresh memory.
01463  *
01464 
01465  *------------------------------------------------------------*/
01466 
01467 char *
01468 find_file(const char *filename, struct stat *statbuf, kpse_file_format_type pathinfo)
01469 {
01470     char *tmp;
01471     char *pathname;
01472 
01473     TRACE_SRC((stderr, "checking filename \"%s\"", filename));
01474 
01475     /*
01476      * case 1:
01477      * try absolute filename
01478      */
01479     if (filename[0] == '/') {
01480        if (stat(filename, statbuf) == 0) {
01481            TRACE_SRC((stderr, "Found absolute filename \"%s\"", filename));
01482            return xstrdup(filename);
01483        }
01484        else {
01485            TRACE_SRC((stderr, "Can't stat absolute filename \"%s\"\n", filename));
01486            return NULL;
01487        }
01488     }
01489 
01490     /*
01491      * case 2:
01492      * prepend filename with dir component from the `main' xdvi file (globals.dvi_file.dirname).
01493      * This works for both
01494      * /absolute/path/ + filename
01495      * and
01496      * /absolute/path/ + relative/path/filename
01497      */
01498     ASSERT(globals.dvi_file.dirname != NULL, "globals.dvi_file.dirname should have been initialized");
01499     {
01500        pathname = xstrdup(globals.dvi_file.dirname);
01501        pathname = xstrcat(pathname, filename);
01502 
01503        TRACE_SRC((stderr, "Trying globals.dvi_file.dirname: \"%s\"", pathname));
01504        if (stat(pathname, statbuf) == 0) {
01505            return pathname;
01506        }
01507     }
01508 
01509     /*
01510      * case 3:
01511      * try current directory; if it's a match, expand to full (but uncanonicalized) path name.
01512      */
01513     if (stat(filename, statbuf) == 0) {
01514        TRACE_SRC((stderr, "Found file \"%s\" in current dir", filename));
01515        free(pathname);
01516        return expand_filename(filename, USE_CWD_PATH);
01517     }
01518 
01519     /*
01520      * case 4:
01521      * try a kpathsea search for filename
01522      */
01523     TRACE_SRC((stderr,
01524               "trying kpathsearch for filename \"%s\"",
01525               filename));
01526     tmp = kpse_find_file(filename, pathinfo, True);
01527 
01528     if (tmp != NULL && stat(tmp, statbuf) == 0) {
01529        TRACE_SRC((stderr, "Found file: `%s'", tmp));
01530        free(pathname);
01531        return tmp;
01532     }
01533 
01534     /*
01535      * case 5:
01536      * try a kpathsea search for pathname
01537      */
01538     TRACE_SRC((stderr,
01539               "trying kpathsearch for pathname \"%s\"",
01540               pathname));
01541     tmp = kpse_find_file(pathname, pathinfo, True);
01542 
01543     if (tmp != NULL && stat(tmp, statbuf) == 0) {
01544        TRACE_SRC((stderr, "Found file: `%s'", tmp));
01545        free(pathname);
01546        return tmp;
01547     }
01548 
01549     /* not found */
01550     free(pathname);
01551     free(tmp);
01552     errno = 0;
01553     return NULL;
01554 }
01555 
01556 /*
01557   Hashtable functions
01558   
01559   The purpose of these is to wrap kpathsea's rather strange hash
01560   functions that can be used to store 2 type of values: char *, and
01561   long, where the latter is interpreted as char *. (See kpathsea/dir.c
01562   for an example of where this is used).
01563   We can't use a different approach (like using a void *) since
01564   the debugging ouput of kpathsea relies on printing the value
01565   either as char * or long (depending on the value of the global flag
01566   `kpse_debug_hash_lookup_int').
01567 */
01568 /*
01569   If key is in hashtable, return True and the integer value in val;
01570   else return False (and leave val untouched).
01571 */
01572 Boolean
01573 find_str_int_hash(hashTableT *hashtable, const char *key, size_t *val)
01574 {
01575     string *ret;
01576 #ifdef KPSE_DEBUG
01577     if (KPSE_DEBUG_P (KPSE_DEBUG_HASH))
01578        kpse_debug_hash_lookup_int = True;
01579 #endif
01580     ret = hash_lookup(*hashtable, key);
01581 #ifdef KPSE_DEBUG
01582     if (KPSE_DEBUG_P (KPSE_DEBUG_HASH))
01583        kpse_debug_hash_lookup_int = False;
01584 #endif
01585 
01586     if (ret != NULL) {
01587        long l = (long)*ret;
01588        *val = (size_t)l;
01589        return True;
01590     }
01591     return False;
01592 }
01593 
01594 /*
01595   Insert key-value pair into hashtable. Note that the key is
01596   *not* copied (i.e. it is expected that is had been allocated
01597   somewhere else, and persists throughout the program).
01598  */
01599 void
01600 put_str_int_hash(hashTableT *hashtable, const char *key, size_t val)
01601 {
01602     long ptr = (long)val;
01603     hash_insert(hashtable, key, (const string)ptr);
01604 }
01605 
01606 /* set globals.dvi_name, globals.dvi_file.dirname and globals.dvi_file.dirlen */
01607 void
01608 set_dvi_name_expand(const char *new_filename)
01609 {
01610     ASSERT(new_filename != NULL, "");
01611     free(globals.dvi_name);
01612     globals.dvi_name = expand_filename_append_dvi(new_filename, USE_CWD_PATH, True);
01613 
01614     free(globals.dvi_file.dirname);
01615     globals.dvi_file.dirname = get_dir_component(globals.dvi_name);
01616 
01617     ASSERT(globals.dvi_file.dirname != NULL, "dvi_name should be a path with dir component");
01618     globals.dvi_file.dirlen = strlen(globals.dvi_file.dirname);
01619 }   
01620 
01621 /* set globals.dvi_name, globals.dvi_file.dirname and globals.dvi_file.dirlen
01622    In contrast to previous function, input filename is not copied.
01623  */
01624 void
01625 set_dvi_name(char *new_filename)
01626 {
01627     ASSERT(new_filename != NULL, "");
01628     free(globals.dvi_name);
01629     globals.dvi_name = new_filename;
01630 
01631     free(globals.dvi_file.dirname);
01632     globals.dvi_file.dirname = get_dir_component(globals.dvi_name);
01633 
01634     ASSERT(globals.dvi_file.dirname != NULL, "dvi_name should be a path with dir component");
01635     globals.dvi_file.dirlen = strlen(globals.dvi_file.dirname);
01636 }   
01637 
01638 /*
01639  * Copy the file pointer `in' to the file pointer `out'.  Return True
01640  * if successful, False else (in which case caller should examine
01641  * errno to find the error).
01642  * The caller is responsible for closing the files.
01643  */
01644 Boolean
01645 copy_fp(FILE *in, FILE *out)
01646 {
01647 #define TMP_BUF_SIZE 4 * 1024
01648     char buf[TMP_BUF_SIZE];
01649     
01650     while (feof(in) == 0) {
01651        size_t bytes_in, bytes_out;
01652        
01653        bytes_in = fread(buf, 1, TMP_BUF_SIZE, in);
01654        if (bytes_in < TMP_BUF_SIZE && !feof(in))
01655            return False;
01656        bytes_out = fwrite(buf, 1, bytes_in, out);
01657 /*     fprintf(stderr, "read %d, wrote %d bytes\n", bytes_in, bytes_out); */
01658        if (bytes_out < bytes_in)
01659            return False;
01660     }
01661     return True;
01662 
01663 #undef TMP_BUF_SIZE
01664 }
01665 
01666 /*
01667  * Copy a file from `from_path' to `to'. Return True if successful, False else
01668  * (in which case caller should examine errno to find the error).
01669  */
01670 Boolean
01671 copy_file(const char *from_path, const char *to_path) {
01672     FILE *from_fp;
01673     FILE *to_fp;
01674 
01675     Boolean retval;
01676 
01677     if ((from_fp = try_fopen(from_path, "rb")) == NULL) {
01678        XDVI_ERROR((stderr, "opening %s for reading failed: %s", from_path, strerror(errno)));
01679        return False;
01680     }
01681 
01682     if ((to_fp = try_fopen(to_path, "wb")) == NULL) {
01683        XDVI_ERROR((stderr, "opening %s for writing failed: %s", to_path, strerror(errno)));
01684        return False;
01685     }
01686 
01687     retval = copy_fp(from_fp, to_fp);
01688 
01689     fclose(from_fp);
01690     fclose(to_fp);
01691 
01692     return retval;
01693 }
01694 
01695 const char *
01696 get_text_encoding(void)
01697 {
01698     const char *text_encoding = NULL;
01699     
01700     /* if resource.text_encoding isn't set, use nl_langinfo() if langinfo is available */
01701     if (resource.text_encoding == NULL) {
01702 #if USE_LANGINFO
01703        if (globals.orig_locale == NULL) {
01704            XDVI_ERROR((stderr, "Call to setlocale() returned NULL; assuming ISO-8859-1 charset."));
01705            text_encoding = "ISO-8859-1";
01706        }
01707        else {
01708            if (strcmp(globals.orig_locale, "C") == 0 || strcmp(globals.orig_locale, "POSIX") == 0) {
01709               /* nl_langinfo returns rather strange values for these ... */
01710               text_encoding = "ISO-8859-1";
01711               TRACE_FIND((stderr, "Assuming |%s| for locale |%s|",
01712                          text_encoding, globals.orig_locale));
01713            }
01714            else {
01715               text_encoding = nl_langinfo(CODESET);
01716               TRACE_FIND((stderr, "nl_langinfo returned: |%s| for locale |%s|",
01717                          text_encoding, globals.orig_locale));
01718            }
01719        }
01720 #else
01721        XDVI_WARNING((stderr,
01722                     "nl_langinfo() not available on this platform, "
01723                     "and XDvi.textEncoding resource not set; using default "
01724                     "encoding ISO-8859-1."));
01725        text_encoding = "ISO-8859-1";
01726 #endif
01727     }
01728     else {
01729        text_encoding = resource.text_encoding;
01730     }
01731     return text_encoding;
01732 }
01733 
01734 char *
01735 iconv_convert_string(const char *from_enc, const char *to_enc, const char *str)
01736 {
01737     static Boolean have_warned = False;
01738 #if HAVE_ICONV_H
01739     size_t input_len = strlen(str);
01740     size_t conv_len = input_len * 4 + 1; /* worst case ... */
01741     int conv_len_save = conv_len;
01742     char *conv_buf = xmalloc(conv_len);
01743     const char *in_ptr = str;
01744     const char *out_ptr = conv_buf;
01745 
01746     iconv_t conv_desc = iconv_open(to_enc, from_enc);
01747 
01748     if (conv_desc == (iconv_t)(-1)) {
01749        if (!have_warned) {
01750            popup_message(XtNameToWidget(globals.widgets.top_level, "*find_popup"),
01751                        MSG_ERR,
01752                        NULL,
01753                        "iconv_open() error: Encoding \"%s\" is not supported by this version of iconv.\n"
01754                        "Please check the output of \"iconv -l\" and set the X resource\n"
01755                        "\"XDvi.textEncoding\" to an appropriate value.", from_enc);
01756            have_warned = True;
01757        }
01758        free(conv_buf);
01759        return NULL;
01760     }
01761 
01762     TRACE_FIND((stderr, "iconv_convert_string: from `%s', to `%s'", from_enc, to_enc));
01763     if (iconv(conv_desc, (iconv_char_pptrT)&in_ptr, &input_len, (char **)&out_ptr, &conv_len) == (size_t)(-1)) {
01764        popup_message(XtNameToWidget(globals.widgets.top_level, "*find_popup"),
01765                     MSG_ERR,
01766                     NULL,
01767                     "iconv_convert_string(): Could not convert %s to %s: %s.",
01768                     from_enc, to_enc, strerror(errno));
01769        iconv_close(conv_desc);
01770        free(conv_buf);
01771        return NULL;
01772     }
01773 
01774     iconv_close(conv_desc);
01775     conv_len = conv_len_save - conv_len;
01776     conv_buf[conv_len] = '\0';
01777 
01778     TRACE_FIND((stderr, "after iconv conversion: |%s| %lu bytes\n",
01779               conv_buf, (unsigned long)conv_len));
01780 
01781     
01782     return conv_buf;
01783     
01784 #else /* HAVE_ICONV_H */
01785 
01786     UNUSED(from_enc);
01787     UNUSED(to_enc);
01788     UNUSED(str);
01789     
01790     /* no iconv available */
01791     if (!have_warned) {
01792        popup_message(XtNameToWidget(globals.widgets.top_level, "*find_popup"),
01793                     MSG_ERR,
01794                     "You can either set the \"LANG\" environment variable or the X resource "
01795                     "\"XDvi.textEncoding\" to make xdvi use a different language/encoding setting.\n"
01796                     "Without iconv, only the encodings ISO-8859-1 and UTF-8 are supported. "
01797                     "For real iconv support, you will need to install the iconv library "
01798                     "and recompile xdvik.",
01799                     "Cannot convert from %s to UTF-8 without iconv support compiled in.");
01800        have_warned = True;
01801     }
01802     return NULL;
01803     
01804 #endif /* HAVE_ICONV_H */
01805 }
01806 
01807 #if 0
01808 /*
01809  * Search integer array <arr> of length <arr_len> for for <item>.
01810  * Return the index of the item, or the index of the next smaller item
01811  * if there's no exact match. (That we want the latter is the
01812  * reason why we can't use bsearch()).
01813  */
01814 int
01815 binary_search(int *arr, int arr_len, int item)
01816 {
01817     int lower = -1;
01818     int upper = arr_len;
01819     int mid;
01820 
01821     ASSERT(arr_len >= 1, "binary_search expects arrays of length >= 1");
01822     
01823     do {
01824        mid = (lower + upper) / 2;
01825        if (item > arr[mid])
01826            lower = mid;
01827        else if (item < arr[mid])
01828            upper = mid;
01829        else /* exact match */
01830            return mid;
01831     } while (upper - lower > 1);
01832 
01833     /* no exact match, return next lower item */
01834     if (mid > 0 && arr[mid] > item)
01835        return mid - 1;
01836     else
01837        return mid;
01838 }
01839 #endif /* 0 */