diff options
author | Karl Berry <karl@freefriends.org> | 2006-01-17 21:41:51 +0000 |
---|---|---|
committer | Karl Berry <karl@freefriends.org> | 2006-01-17 21:41:51 +0000 |
commit | 487ca4806cc046076293cf6cc5fbba0db282bac7 (patch) | |
tree | 847b412ab5158dd7bdd7ed7e5a4cc3fbca94be32 /Build/source/texk/xdvik/util.c | |
parent | a3d3111bfe26b8e5f5bc6049dfb2a4ca2edc7881 (diff) |
texk 1
git-svn-id: svn://tug.org/texlive/trunk@1485 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Build/source/texk/xdvik/util.c')
-rw-r--r-- | Build/source/texk/xdvik/util.c | 1839 |
1 files changed, 1839 insertions, 0 deletions
diff --git a/Build/source/texk/xdvik/util.c b/Build/source/texk/xdvik/util.c new file mode 100644 index 00000000000..d34ef137387 --- /dev/null +++ b/Build/source/texk/xdvik/util.c @@ -0,0 +1,1839 @@ +/*========================================================================*\ + +Copyright (c) 1990-2004 Paul Vojta and others + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +PAUL VOJTA OR ANY OTHER AUTHOR OF THIS SOFTWARE BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +NOTE: + xdvi is based on prior work, as noted in the modification history + in xdvi.c. + +\*========================================================================*/ + +#include "xdvi-config.h" + +#include <sys/types.h> /* ZLB: must be before sys/socket.h for IRIX 5.3 */ +#include <sys/socket.h> +#include <sys/file.h> /* this defines FASYNC */ +#include <sys/ioctl.h> /* this defines SIOCSPGRP and FIOASYNC */ +#include <sys/wait.h> /* this defines WIFEXITED and WEXITSTATUS */ + +#include "xdvi.h" +#include "hypertex.h" +#include "dvi-init.h" +#include "special.h" +#include "string-utils.h" + +#include "kpathsea/tex-file.h" + +#include "events.h" +#include "util.h" +#include "x_util.h" +#include "message-window.h" +#include "search-internal.h" +#include "encodings.h" +#include "filehist.h" +#include "xm_prefs.h" /* for preferences_changed() */ + +#include <errno.h> + +#ifndef HAVE_MEMICMP +#include <ctype.h> +#endif + +#ifdef VMS +#include <rmsdef.h> +#endif /* VMS */ + +#ifdef X_NOT_STDC_ENV +extern int errno; +extern void *malloc(); +extern void *realloc(); +#endif + + +#ifdef DOPRNT /* define this if vfprintf gives you trouble */ +#define vfprintf(stream, message, args) _doprnt(message, args, stream) +#endif + +#ifdef VMS +#include <rmsdef.h> +#endif /* VMS */ + +#ifdef X_NOT_STDC_ENV +extern int errno; +extern void *malloc(); +extern void *realloc(); +#endif + + +/* if POSIX O_NONBLOCK is not available, use O_NDELAY */ +#if !defined O_NONBLOCK && defined O_NDELAY +# define O_NONBLOCK O_NDELAY +#endif + +/* Linux prefers O_ASYNC over FASYNC; SGI IRIX does the opposite. */ +#if !defined(FASYNC) && defined(O_ASYNC) +# define FASYNC O_ASYNC +#endif + +#if !defined(FLAKY_SIGPOLL) && !HAVE_STREAMS && !defined(FASYNC) +# if !defined(SIOCSPGRP) || !defined(FIOASYNC) +# define FLAKY_SIGPOLL 1 +# endif +#endif + +#if !FLAKY_SIGPOLL && HAVE_STREAMS +# include <stropts.h> + +# ifndef S_RDNORM +# define S_RDNORM S_INPUT +# endif + +# ifndef S_RDBAND +# define S_RDBAND 0 +# endif + +# ifndef S_HANGUP +# define S_HANGUP 0 +# endif + +# ifndef S_WRNORM +# define S_WRNORM S_OUTPUT +# endif + +#endif /* not FLAKY_SIGPOLL && HAVE_STREAMS */ + +#include <sys/param.h> +#include <sys/stat.h> +#include <pwd.h> +#include <errno.h> + +#ifndef MAXSYMLINKS /* Workaround for Linux libc 4.x/5.x */ +#define MAXSYMLINKS 5 +#endif + +#if HAVE_ICONV_H +#include <iconv.h> +#endif + +#include <locale.h> + +#if USE_LANGINFO +#include <langinfo.h> +#endif + +#define BUF_SIZE 1024 + +/* + Insert item to the list and return the result. +*/ +struct dl_list * +dl_list_insert(struct dl_list *list, void *item) +{ + struct dl_list *new_elem = xmalloc(sizeof *new_elem); + new_elem->item = item; + new_elem->next = NULL; + new_elem->prev = NULL; + + if (list == NULL) { + list = new_elem; + } + else { + /* append after current position */ + struct dl_list *ptr = list; + + new_elem->next = ptr->next; + new_elem->prev = ptr; + + if (ptr->next != NULL) + ptr->next->prev = new_elem; + + ptr->next = new_elem; + } + return new_elem; +} + +/* + Return head of the list. +*/ +struct dl_list * +dl_list_head(struct dl_list *list) +{ + for (; list != NULL && list->prev != NULL; list = list->prev) { ; } + return list; +} + +/* + Put a new item at the front of the list (current front is passed in first argument) + and return its position. +*/ +struct dl_list * +dl_list_push_front(struct dl_list *list, void *item) +{ + struct dl_list *new_elem = xmalloc(sizeof *new_elem); + new_elem->item = item; + new_elem->next = NULL; + new_elem->prev = NULL; + + if (list != NULL) { /* prepend to current position */ + new_elem->next = list; + list->prev = new_elem; + } + return new_elem; +} + +/* + Truncate list so that current pointer is the last element. +*/ +struct dl_list * +dl_list_truncate(struct dl_list *list) +{ + struct dl_list *ptr = list->next; + struct dl_list *save; + + list->next = NULL; + + while (ptr != NULL) { + save = ptr->next; + free(ptr); + ptr = save; + } + return list; +} + +/* + Truncate list at the head (i.e. remove the first element from it - head must be passed to this list), + and return the result. +*/ +struct dl_list * +dl_list_truncate_head(struct dl_list *list) +{ + struct dl_list *ptr = list->next; + if (list->next != NULL) + list->next->prev = NULL; + free(list); + return ptr; +} + +/* + If the item pointed to by *list isn't the head of the list, remove it, + set *list to the previous item, and return True. Else return False. +*/ +Boolean +dl_list_remove_item(struct dl_list **list) +{ + struct dl_list *ptr = *list; /* item to remove */ + if (ptr->prev == NULL) + return False; + ptr->prev->next = ptr->next; + if (ptr->next != NULL) + ptr->next->prev = ptr->prev; + /* update list */ + *list = (*list)->prev; + /* remove item */ + free(ptr); + + return True; +} + +/* + Remove all items matching compare_func() from list. Must be called + with a pointer to the head of the list, which is also returned. + Returns the number of removed items in `count'. +*/ +struct dl_list * +dl_list_remove(struct dl_list *list, const void *item, + int *count, + void **removed_item, + Boolean (*compare_func)(const void *item1, const void *item2)) +{ + struct dl_list *ptr = list; + while (ptr != NULL) { + struct dl_list *next = ptr->next; + if (compare_func(ptr->item, item)) { /* match */ + *removed_item = ptr->item; + (*count)++; + if (ptr->prev != NULL) { + ptr->prev->next = ptr->next; + } + else { /* removed first element */ + list = list->next; + } + + if (ptr->next != NULL) + ptr->next->prev = ptr->prev; + free(ptr); + ptr = NULL; + } + ptr = next; + } + return list; +} + + +/* NOTE: keep this table in sync with the #defines in xdvi-debug.h! */ +struct debug_string_options debug_options[] = { + { DBG_BITMAP, "bitmap", ", " }, + { DBG_DVI, "dvi", ", " }, + { DBG_PK, "pk", ", " }, + { DBG_BATCH, "batch", ", " }, + { DBG_EVENT, "event", ", " }, + { DBG_PS, "ps", ",\n"}, + { DBG_STAT, "stat", ", " }, + { DBG_HASH, "hash", ", " }, + { DBG_OPEN, "open", ", " }, + { DBG_PATHS, "paths", ", " }, + { DBG_EXPAND, "expand", ", " }, + { DBG_SEARCH, "search", ", " }, + { DBG_KPATHSEA, "kpathsea", ",\n"}, + { DBG_HTEX, "htex", ", " }, + { DBG_SRC_SPECIALS,"src", ", " }, + { DBG_CLIENT, "client", ", " }, + { DBG_T1, "t1", ", " }, + { DBG_T1_VERBOSE, "t1_verbose", ",\n"}, + { DBG_GUI, "gui", ", " }, + { DBG_FIND, "find", ", " }, + { DBG_FILES, "files", ", " }, + { DBG_ALL, "all", "\n" }, + /* end marker */ + { 0, NULL, NULL } +}; + +/* + * General utility routines. + */ + +/* + * 2-step fopen using close_a_file() if first opening attempt fails with + * `too many open files'. + */ +FILE * +try_fopen(const char *fname, const char *mode) +{ + FILE *fp = fopen(fname, mode); + if (fp == NULL && (errno == EMFILE || errno == ENFILE)) { + close_a_file(); + fp = fopen(fname, mode); + } + return fp; +} + +/* + * Like try_fopen(), for fdopen(). + */ +FILE * +try_fdopen(int fd, const char *mode) +{ + FILE *fp = fdopen(fd, mode); + if (fp == NULL && (errno == EMFILE || errno == ENFILE)) { + close_a_file(); + fp = fdopen(fd, mode); + } + return fp; +} + + +/* + * Like try_fopen(), for open(). + */ +int +try_open(const char *fname, int flags) +{ + int fd = open(fname, flags); + if (fd < 0 && (errno == EMFILE || errno == ENFILE)) { + close_a_file(); + fd = open(fname, flags); + } + return fd; +} + +/* + * Like try_fopen(), for open() with 3 arguments. + */ +int +try_open_mode(const char *fname, int flags, mode_t mode) +{ + int fd = open(fname, flags, mode); + if (fd < 0 && (errno == EMFILE || errno == ENFILE)) { + close_a_file(); + fd = open(fname, flags); + } + return fd; +} + + +/* + * This routine should be used for all exits. (SU: This is different + * from non-k xdvi, where it's only used for `non-early' exits; all + * routines called here should be aware of their own state and either + * perform cleanup or just return, unless xdvi_exit() itself checks for + * the status). + */ + +void +xdvi_exit(int status) +{ + /* do the following only if the window has been opened: */ + if (globals.widgets.top_level != 0 && XtIsRealized(globals.widgets.top_level)) { + char *filehist; + file_history_set_page(current_page); + filehist = file_history_get_list(); + store_preference(NULL, "fileHistory", "%s", filehist); + free(filehist); + +#if MOTIF + if (preferences_changed()) { + return; + } +/* else { */ +/* fprintf(stderr, "Preferences not changed.\n"); */ +/* } */ +#endif + /* try to save user preferences, unless we're exiting with an error */ + if (status == 0 && !save_user_preferences(True)) + return; + + /* Clean up the "xdvi windows" property in the root window. */ + update_window_property(XtWindow(globals.widgets.top_level), False); + } + +#if PS + ps_destroy(); +#endif + remove_tmp_dvi_file(); + + close_iconv(); + + exit(status); +} + +/* + * invoked on SIGSEGV: try to stop gs before aborting, to prevent gs + * running on with 100% CPU consumption - this can be annoying during + * testing. We'd also like to clean up the "xdvi windows" property in + * the root window, but it might be already too late to talk to the + * X server here (the code can also be triggered by X errors). + */ +void +do_abort() +{ +#if PS + ps_destroy(); +#endif + /* if (globals.widgets.top_level) */ + /* exit_clean_windows(); */ + abort(); +} + +/* + Expand leading ~ or ~user in path to the actual homedirectory of the user. + Returns either NULL if unsuccessful, or result in freshly allocated string. + Bugs: ~user doesn't work with NIS/yellow pages. +*/ +char * +expand_homedir(const char *path) +{ + char *resolved = NULL; + const char *orig_path = path; + UNUSED(orig_path); /* if HAVE_GETPWNAM or HAVE_GETPWUID && HAVE_GETUID */ + + if (path == NULL) + return NULL; + + /* if path doesn't start with ~, just return a copy of path */ + if (*path != '~') + return xstrdup(path); + + /* expand ~/ or ~user */ + path++; + if (*path == '/') { /* expand `~/' to `$HOME/' */ + char *homedir = getenv("HOME"); + if (homedir != NULL) { + resolved = xstrdup(homedir); + resolved = xstrcat(resolved, path); + } + else { +#if defined(HAVE_GETPWUID) && defined(HAVE_GETUID) + /* homedir not set */ + struct passwd *entry = getpwuid(getuid()); + if (entry != NULL) { + homedir = entry->pw_dir; + if (homedir != NULL) { + resolved = xstrdup(homedir); + resolved = xstrcat(resolved, path); + } + else { + XDVI_ERROR((stderr, "getpwnam returned NULL for entry->pw_dir: %s", strerror(errno))); + return NULL; + } + } + else { + XDVI_ERROR((stderr, "getpwnam failed: %s", strerror(errno))); + return NULL; + } +#else + popup_message(globals.widgets.top_level, + MSG_WARN, + NULL, + "$HOME not set, and getpwuid() or getuid() not supported - could not expand \"%s\".", + orig_path); + return NULL; +#endif + } + TRACE_GUI((stderr, "resolved: |%s|", resolved)); + return resolved; + } + else { /* ~user -> try getpwnam() to get homedir */ +#ifdef HAVE_GETPWNAM + struct passwd *entry; + char *separator = strchr(path, '/'); + char *homedir; + + TRACE_GUI((stderr, "separator is: |%s|, len of username: %d", + separator, (int)(separator - path))); + if (separator == NULL) + return NULL; + + resolved = xmalloc(separator - path + 1); + memcpy(resolved, path, separator - path); + resolved[separator - path] = '\0'; + + TRACE_GUI((stderr, "username is: |%s|", resolved)); + entry = getpwnam(resolved); + if (entry == NULL) { + XDVI_ERROR((stderr, "getpwnam failed: %s", strerror(errno))); + return NULL; + } + TRACE_GUI((stderr, "homedir of user is: |%s|", entry->pw_dir)); + homedir = entry->pw_dir; + + free(resolved); + resolved = xstrdup(homedir); + resolved = xstrcat(resolved, path + (separator - path)); + TRACE_GUI((stderr, "resolved: |%s|", resolved)); + return resolved; +#else + popup_message(globals.widgets.top_level, + MSG_WARN, + NULL, + "Expanding \"%s\" failed: This platform doesn't support getpwnam().", + orig_path); + return NULL; +#endif + } + +} + +/* + realpath implementation if no implementation available. + Try to canonicalize path, removing `~', `.' and `..' and expanding symlinks. + + Adopted from wu-ftpd's fb_realpath (in realpath.c), but without the + seteuid(0) stuff (which we don't need, since we never change into + directories or read files the user has no permissions for), and + without the ugly goto(). Special care has been taken to avoid buffer + overflows (e.g. this version is not affected by + http://isec.pl/vulnerabilities/isec-0011-wu-ftpd.txt). + + `resolved' should be a buffer of size MAXPATHLEN. */ + +char * +my_realpath(const char *path, char *resolved) +{ + struct stat sb; + int n; + /* char *p, *q, *tmp; */ + char *base; + char tmpbuf[MAXPATHLEN]; + int symlinks = 0; +#ifdef HAVE_FCHDIR + int fd; +#else + char cwd[MAXPATHLEN]; +#endif + + /* Save cwd for going back later */ +#ifdef HAVE_FCHDIR + if ((fd = try_open(".", O_RDONLY)) < 0) + return NULL; +#else /* HAVE_FCHDIR */ + if ( +# ifdef HAVE_GETCWD + getcwd(cwd, MAXPATHLEN) +# else + getwd(cwd) +# endif + == NULL) + return NULL; +#endif /* HAVE_FCHDIR */ + + if (strlen(path) + 1 > MAXPATHLEN) { + errno = ENAMETOOLONG; + return NULL; + } + + strcpy(resolved, path); + + for (;;) { /* loop for resolving symlinks in base name */ + /* get base name and dir name components */ + char *p = strrchr(resolved, '/'); + if (p != NULL) { + base = p + 1; + if (p == resolved) { + /* resolved is in root dir; this must be treated as a special case, + since we can't chop off at `/'; instead, just use the `/': + */ + p = "/"; + } + else { + /* not in root dir; chop off path name at slash */ + while (p > resolved && *p == '/') /* for multiple trailing slashes */ + p--; + *(p + 1) = '\0'; + p = resolved; + } + + /* change into that dir */ + if (chdir(p) != 0) + break; + } + else /* no directory component */ + base = resolved; + + /* resolve symlinks or directory names (not used in our case) in basename */ + if (*base != '\0') { + if ( +#ifdef HAVE_LSTAT + lstat(base, &sb) +#else + stat(base, &sb) +#endif + == 0) { +#ifdef HAVE_LSTAT + if (S_ISLNK(sb.st_mode)) { /* if it's a symlink, iterate for what it links to */ + if (++symlinks > MAXSYMLINKS) { + errno = ELOOP; + break; + } + + if ((n = readlink(base, resolved, MAXPATHLEN)) < 0) + break; + + resolved[n] = '\0'; + continue; + } +#endif /* HAVE_LSTAT */ + if (S_ISDIR(sb.st_mode)) { /* if it's a directory, go there */ + if (chdir(base) != 0) + break; + + base = ""; + } + } + } + + /* Now get full pathname of current directory and concatenate it with saved copy of base name */ + strcpy(tmpbuf, base); /* cannot overrun, since strlen(base) <= strlen(path) < MAXPATHLEN */ + if ( +#ifdef HAVE_GETCWD + getcwd(resolved, MAXPATHLEN) +#else + getwd(resolved) +#endif + == NULL) + break; + + /* need to append a slash if resolved is not the root dir */ + if (!(resolved[0] == '/' && resolved[1] == '\0')) { + if (strlen(resolved) + 2 > MAXPATHLEN) { + errno = ENAMETOOLONG; + break; + } + strcat(resolved, "/"); + } + + if (*tmpbuf) { + if (strlen(resolved) + strlen(tmpbuf) + 1 > MAXPATHLEN) { + errno = ENAMETOOLONG; + break; + } + strcat(resolved, tmpbuf); + } + + /* go back to where we came from */ +#ifdef HAVE_FCHDIR + fchdir(fd); + close(fd); +#else + chdir(cwd); +#endif + return resolved; + } + + /* arrive here in case of error: go back to where we came from, and return NULL */ +#ifdef HAVE_FCHDIR + fchdir(fd); + close(fd); +#else + chdir(cwd); +#endif + return NULL; +} + + +#ifndef KPATHSEA + +/* + * Either (re)allocate storage or fail with explanation. + */ + +void * +xmalloc(unsigned size) +{ + void *mem = malloc(size); + + if (mem == NULL) + XDVI_FATAL((stderr, "Out of memory (allocating %u bytes).", size)); + return mem; +} + +void * +xrealloc(void *where, unsigned size) +{ + void *mem = realloc(where, size); + + if (mem == NULL) + XDVI_FATAL((stderr, "Out of memory (reallocating %u bytes).", size)); + return mem; +} + + +/* + * Allocate a new string. + */ + +char * +xstrdup(const char *str) +{ + size_t len; + char *new; + + ASSERT(fprintf(stderr, "Test assertion!\n") && 1 == 0); + ASSERT(str != NULL, ""); + len = strlen(str) + 1; + new = xmalloc(len); + memcpy(new, str, len); + return new; +} + + + +/* + * Allocate a new string. The second argument is the length. + */ + +char * +xmemdup(const char *str, size_t len) +{ + char *new; + + new = xmalloc(len); + memcpy(new, str, len); + return new; +} + +#endif /* not KPATHSEA */ + + +/* + * Append str2 to str1, reallocating str1 as neccessary. + * Note that this modifies str1, so if you want a clean + * new copy, use xstrdup() first, then xstrcat(). + * str1 should be either NULL, or a valid char * that has + * previously been malloc()ed. + */ + +char * +xstrcat(char *str1, const char *str2) +{ + size_t len1 = (str1 == NULL) ? 0 : strlen(str1); + size_t len2 = strlen(str2) + 1; + + str1 = xrealloc(str1, len1 + len2); + memcpy(str1 + len1, str2, len2); + return str1; +} + + +/* + * Allocate bitmap for given font and character + */ + +void +alloc_bitmap(struct bitmap *bitmap) +{ + unsigned int size; + + /* fprintf(stderr, "allocating bitmap of size %u x %u\n", bitmap->w, bitmap->h); */ + /* width must be multiple of <arch-defined> bits for raster_op */ + bitmap->bytes_wide = ROUNDUP((int)bitmap->w, BMBITS) * BMBYTES; + size = bitmap->bytes_wide * bitmap->h; + bitmap->bits = xmalloc(size != 0 ? size : 1); +} + + +#ifndef HAVE_MEMICMP +/* + * Case-insensitive version of memcmp(). This code assumes that the second + * argument (i.e. what is being compared with) is lower case. + */ + +int +memicmp(const char *s1, const char *s2, size_t n) +{ + while (n > 0) { + int i = tolower((int)*s1) - *s2; + if (i != 0) + return i; + ++s1; + ++s2; + --n; + } + return 0; +} +#endif /* HAVE_MEMICMP */ + +/* + * Try to close the pixel file for the least recently used font. + * Invoked when we've run out of file descriptors. + */ +void +close_a_file(void) +{ + struct font *fontp; + unsigned short oldest = USHRT_MAX; + struct font *f = NULL; + + if (globals.debug & DBG_OPEN) + puts("Calling close_a_file()."); + + for (fontp = font_head; fontp != NULL; fontp = fontp->next) { + if (fontp->file != NULL && fontp->timestamp <= oldest) { + f = fontp; + oldest = fontp->timestamp; + } + } + /* fprintf(stderr, "oldest = %u\n", oldest); */ + if (f == NULL) + XDVI_FATAL((stderr, "Can't find an open pixel file to close")); + fclose(f->file); + f->file = NULL; +} + +/* + * Open a file in the given mode. We use XFOPEN since xfopen is already + * usurpated by kpathsea's xfopen.c, which just exits rather ungracefully + * if it detects a NULL return value; most certainly NOT what we want here. + */ +FILE * +XFOPEN(const char *path, const char *mode) +{ + FILE *fp = NULL; +#ifdef TESTING_OPEN_FILES + fprintf(stderr, "trying to open |%s|\n", path); +#endif + if ((fp = try_fopen(path, mode)) == NULL && (errno == EMFILE || errno == ENFILE)) { + XDVI_FATAL((stderr, "too many open files")); + } + return fp; +} + +/* + * Create a pipe, closing a file if necessary. + * We use socketpair() instead of pipe() because several operating + * systems don't support SIGPOLL/SIGIO on pipes: + * SGI IRIX 6.5 F_SETOWN not implemented + * Linux 2.4.2 Not supported + */ + +int +xpipe(int *fd) +{ + int retval; + + for (;;) { + retval = socketpair(AF_UNIX, SOCK_STREAM, 0, fd); + if (retval == 0) { /* success */ + break; + } + if ((errno != EMFILE && errno != ENFILE)) { + /* failed, but not because of too many files */ + break; + } + close_a_file(); + } + return retval; +} + + + +/* + * + * Read size bytes from the FILE fp, constructing them into a + * signed/unsigned integer. + * + */ + +unsigned long +get_bytes(FILE *fp, int size) +{ + long x = 0; + + while (size--) + x = (x << 8) | get_byte(fp); + return x; +} + +long +get_lbytes(FILE *fp, int size) +{ + long x; + +#if __STDC__ + x = (signed char)getc(fp); +#else + x = (unsigned char)getc(fp); + if (x & 0x80) + x -= 0x100; +#endif + while (--size) + x = (x << 8) | get_byte(fp); + return x; +} + + + +/* + * Create a temporary file and return its fd. Also put its filename + * in str. Create str if it's NULL. + */ + +#ifndef P_tmpdir +#define P_tmpdir "/tmp" +#endif + +static const char tmp_suffix[] = "/xdvi-XXXXXX"; + +int +xdvi_temp_fd(char **str) +{ + int fd; + char *p; + size_t len; + static const char *template = NULL; +#if !HAVE_MKSTEMP + static unsigned long seed; + static char letters[] = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789._"; + char *p1; +#endif + + if (*str != NULL) { + p = *str; + + /* O_EXCL is there for security (if root runs xdvi) */ + if (!((fd = try_open_mode(p, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR)) == -1 + && errno == EEXIST)) + return fd; +#if HAVE_MKSTEMP + memcpy(p + strlen(p) - 6, "XXXXXX", 6); +#endif + } + else { + if (template == NULL) { + const char *ourdir; + + ourdir = getenv("TMPDIR"); + if (ourdir == NULL || access(ourdir, W_OK) < 0) { + ourdir = P_tmpdir; + if (access(ourdir, W_OK) < 0) + ourdir = "."; + } + len = strlen(ourdir); + if (len > 0 && ourdir[len - 1] == '/') + --len; + template = p = xmalloc(len + sizeof tmp_suffix); + memcpy(p, ourdir, len); + memcpy(p + len, tmp_suffix, sizeof tmp_suffix); +#if !HAVE_MKSTEMP + seed = 123456789 * time(NULL) + 987654321 * getpid(); +#endif + } + *str = p = xstrdup(template); + } + +#if HAVE_MKSTEMP + fd = mkstemp(p); + if (fd == -1 && (errno == EMFILE || errno == ENFILE)) { + close_a_file(); + memcpy(p + strlen(p) - 6, "XXXXXX", 6); + fd = mkstemp(p); + } +#else + p1 = p + strlen(p) - 6; + for (;;) { + unsigned long s = ++seed; + char *p2; + + for (p2 = p1 + 5; p2 >= p1; --p2) { + *p2 = letters[s & 63]; + s >>= 6; + } + if (!((fd = try_open_mode(p, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR)) == -1 + && errno == EEXIST)) + break; + } +#endif + return fd; +} + + +/* print a GUI error message for childs that exited with an errorcode */ +void +handle_child_exit(int status, struct xchild *this) +{ + char *err_msg = NULL; + + /* if child exited with error and xio struct is available for child, + print error text */ + if (this->io != NULL + && (WIFEXITED(status) != 0) + && (WEXITSTATUS(status) != 0) + && (err_msg = (this->io->read_proc)(this->io->fd)) != NULL) { + + if (this->name == NULL) { + popup_message(globals.widgets.top_level, + MSG_WARN, + NULL, + err_msg[0] == '\0' ? "An unknown error occurred" : err_msg); + } + else { + popup_message(globals.widgets.top_level, + MSG_WARN, + "Xdvi tries to collect all messages from STDERR. " + "When no useful error message is available " + "(e.g. because the program has written to STDOUT instead), " + "try to run the command again from the command line " + "to find out what the problem was.", + "Command \"%s\" exited with error code %d\n%s\n", + this->name, WEXITSTATUS(status), err_msg); + } + free(err_msg); + } + free(this->name); + free(this->io); + free(this); +} + +static void +dummy_write_proc(int fd) +{ + UNUSED(fd); + fprintf(stderr, "============== dummy_write_proc called for fd %d\n", fd); +} + +/* + * read what child printed to fd (should be set up to be stderr). + * Allocates and returns error text; caller is responsible for free()ing it + * afterwards. + */ +char * +read_child_error(int fd) +{ + int bytes = 0, buf_old_size = 0, buf_new_size = 0; + char tmp_buf[BUF_SIZE]; + char *err_buf = xstrdup(""); + char *ret; + /* collect stderr messages into err_buf */ + while ((bytes = read(fd, tmp_buf, BUF_SIZE - 1)) > 0) { + buf_old_size = buf_new_size; + buf_new_size += bytes; + err_buf = xrealloc(err_buf, buf_new_size + 1); + memcpy(err_buf + buf_old_size, tmp_buf, buf_new_size - buf_old_size); + err_buf[buf_new_size] = '\0'; + } + + close(fd); + ret = escape_format_arg(err_buf); /* this allocates ret */ + free(err_buf); + return ret; +} + +/* + * Fork a child process. If exit_proc is NULL, the process' error messages + * will be collected and a window popped up for GUI display. If exit_proc + * is non-NULL, it should be a function that does something reasonable + * with the error messages itself. It should also free the xchild struct. + * + * If dirname != NULL, the child will chdir into dirname before running the + * command. + */ +Boolean +fork_process(const char *proc_name, Boolean redirect_stdout, + const char *dirname, + childProcT exit_proc, void *data, + char *const argv[]) +{ + int i, pid; + struct xchild *my_child = xmalloc(sizeof *my_child); + struct xio *my_io = xmalloc(sizeof *my_io); + int err_pipe[2]; + char *volatile buf = xstrdup(""); + + for (i = 0; argv[i] != NULL; i++) { + TRACE_GUI((stderr, "argv: |%s|", argv[i])); + buf = xstrcat(buf, argv[i]); + buf = xstrcat(buf, " "); + } + + if (i > 0) + buf[strlen(buf) - 1] = '\0'; /* chop off trailing space */ + + TRACE_GUI((stderr, "forking: |%s|", buf)); + + /* flush output buffers to avoid double buffering (i.e. data + waiting in the output buffer being written twice, by the parent + and the child) */ + fflush(stdout); + fflush(stderr); + + if (pipe(err_pipe) < 0) { + XDVI_FATAL((stderr, "pipe error")); + } + + switch (pid = vfork()) { + case -1: /* forking error */ + perror("vfork"); + close(err_pipe[0]); + close(err_pipe[1]); + return False; + case 0: /* child */ + if (dirname != NULL) + chdir(dirname); + if (globals.debug & DBG_FILES) { + char path[MAXPATHLEN]; + getcwd(path, MAXPATHLEN); + fprintf(stderr, "Directory of running `%s': `%s'\n", + proc_name, path); + } + /* FIXME: There's a bug which prevents this from working + with xdvi as child: Whenever xdvi tries to print to stderr, + this will hang the child forever. Closing all other file + descriptors, as in the #if TRY_FIX regions, seems to fix + this, but it also loses all output ... + */ +#if TRY_FIX + close(0); + close(1); +#endif /* TRY_FIX */ + close(err_pipe[0]); /* no reading from stderr */ + + /* redirect writing to stderr */ + if (dup2(err_pipe[1], STDERR_FILENO) != STDERR_FILENO) { + perror("dup2 for stderr"); + _exit(EXIT_FAILURE); + return False; /* make compiler happy */ + } + if (redirect_stdout) { + /* also redirect writing to stdout */ + if (dup2(err_pipe[1], STDOUT_FILENO) != STDOUT_FILENO) { + perror("dup2 for stdout"); + _exit(EXIT_FAILURE); + return False; /* make compiler happy */ + } + } + +#if TRY_FIX + /* close all remaining descriptors */ + i = 2; + while (i < 256 /* TODO: better handling of OPEN_MAX; see Stevens p. 43 */) { + close(i++); + } +#endif /* TRY_FIX */ + execvp(proc_name, argv); + + /* arrive here only if execvp failed */ + fprintf(stderr, "%s: Execution of %s failed.\n", globals.program_name, proc_name); + fflush(stderr); + close(err_pipe[1]); + _exit(EXIT_FAILURE); + return False; /* make compiler happy */ + default: /* parent */ + close(err_pipe[1]); /* no writing to stderr */ + + my_io->next = NULL; + my_io->fd = err_pipe[0]; + my_io->xio_events = XIO_IN; +#if HAVE_POLL + my_io->pfd = NULL; +#endif + my_io->read_proc = read_child_error; + my_io->write_proc = dummy_write_proc; + + my_child->next = NULL; + my_child->pid = pid; + my_child->name = buf; + my_child->data = data; + if (exit_proc == NULL) { /* use default exit procedure */ + my_child->proc = handle_child_exit; + } + else { + my_child->proc = exit_proc; + } + my_child->io = my_io; + + set_chld(my_child); + + return True; + } +} + + + +/* + * Prepare the file descriptor to generate SIGPOLL/SIGIO events. + * If called with a True argument, set it up for non-blocking I/O. + */ + +void +prep_fd(int fd, wide_bool noblock) +{ + /* Set file descriptor for non-blocking I/O */ + if (noblock) + (void) fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); + +#if !FLAKY_SIGPOLL +# if HAVE_STREAMS + if (isastream(fd) > 0) { + if (ioctl(fd, I_SETSIG, + S_RDNORM | S_RDBAND | S_HANGUP | S_WRNORM) == -1) + perror("xdvi: ioctl I_SETSIG"); + } + else +# endif + { +# ifdef FASYNC + if (fcntl(fd, F_SETOWN, getpid()) == -1) + perror("xdvi: fcntl F_SETOWN"); + if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | FASYNC) == -1) + perror("xdvi: fcntl F_SETFL"); +# elif defined SIOCSPGRP && defined FIOASYNC + /* For HP-UX B.10.10 and maybe others. See "man 7 socket". */ + int arg; + + arg = getpid(); + if (ioctl(fd, SIOCSPGRP, &arg) == -1) + perror("xdvi: ioctl SIOCSPGRP"); + arg = 1; + if (ioctl(fd, FIOASYNC, &arg) == -1) + perror("xdvi: ioctl FIOASYNC"); +# endif + } +#endif /* not FLAKY_SIGPOLL */ +} + + +/* APS Pointer locator: */ +/* Return screen positions */ +Boolean +pointerlocate(int *xpos, int *ypos) +{ + Window root, child; + int root_x, root_y; + unsigned int keys_buttons; + + if (!XtIsRealized(globals.widgets.top_level)) + return False; + + return XQueryPointer(DISP, mane.win, &root, &child, + &root_x, &root_y, xpos, ypos, &keys_buttons); +} + +unsigned long +parse_debugging_string(const char *arg) +{ + int retval = 0; + const char *curr, *last; + size_t i; + + curr = last = arg; + + while (curr != '\0') { + Boolean matched = False; + + while (isspace((int)*curr)) + curr++; + for (i = 0; debug_options[i].description != NULL; i++) { + /* match on length of passed argument, to allow for abbreviations */ + if (memicmp(curr, + debug_options[i].description, + strlen(debug_options[i].description)) == 0 + && (curr[strlen(debug_options[i].description)] == '\0' + || curr[strlen(debug_options[i].description)] == ',' + || isspace((int)curr[strlen(debug_options[i].description)]))) { + matched = True; + retval |= debug_options[i].bitmap; + fprintf(stderr, "Debugging option: \"%s\" = \"%s\", debug: %d\n", + curr, debug_options[i].description, retval); + } + } + if (!matched) { + char *tempstr = xstrdup(curr); + char *test; + if ((test = strchr(curr, ',')) != NULL) { + *test = '\0'; + } + XDVI_WARNING((stderr, "Ignoring unknown debugging option \"%s\". Valid options are:\n", tempstr)); + for (i = 0; debug_options[i].description != NULL; i++) { + fprintf(stderr, "`%s'%s", + debug_options[i].description, + debug_options[i].help_formatting); + } + fprintf(stderr, "\n"); + free(tempstr); + } + curr = strchr(curr, ','); + if (curr != NULL) + curr++; + } + + return retval; +} + +unsigned long +parse_debugging_option(const char *ptr) +{ + if (ptr == NULL) + return 0L; + else if (isdigit((int)*ptr)) { + if (resource.debug_arg == NULL) + return DBG_ALL; /* per default debug everything */ + else + return strtol(resource.debug_arg, (char **)NULL, 10); + } else if (*ptr == '-') + return DBG_ALL; + else return parse_debugging_string(ptr); +} + +/* determine average width of a font */ +int +get_avg_font_width(XFontStruct *font) +{ + int width; + + assert(font != NULL); + width = font->max_bounds.width + font->min_bounds.width / 2; + if (width == 0) { + /* if min_bounds.width = -max_bounds.width, we probably + have a scalable TT font; try to determine its actual + width by measuring the letter `x': + */ + width = XTextWidth(font, "x", 1); + } + if (width == 0) { /* last resort */ + width = font->max_bounds.width / 2; + } + return width; + +} + +/* + Splits LINE from BEGIN to END (not neccessarily null-terminated) + into items separated by SEP, removes leading or trailing whitespace, + and saves the items as newly allocated char*s into the return array. + Returns the number of items that have been saved as RET_ITEMS. + Empty entries are returned as such, i.e. both `abc:' and `abc: ' + return RET_ITEMS = 2 and "" as second entry. +*/ +char ** +split_line(const char *line, char sep, size_t begin, size_t end, size_t *ret_items) +{ + const char *c_ptr = line + begin; + const char *e_ptr = line + end; + const char *test_end; + + size_t result_cnt = 0; + size_t alloc_len = 0; + size_t len; + const size_t ALLOC_STEP = 8; + char **result_arr = NULL; + + /* create new result item, resizing result_arr as needed + * (an empty string will coun1 as 1 item: "") */ + while (result_cnt + 1 >= alloc_len) { + alloc_len += ALLOC_STEP; + result_arr = xrealloc(result_arr, alloc_len * sizeof *result_arr); + } + + while (c_ptr <= e_ptr) { + /* skip leading whitespace */ + while (c_ptr < e_ptr && isspace((int)*c_ptr)) { + c_ptr++; + } + + /* find end of current elem, which is either the separator or out of range */ + test_end = strchr(c_ptr, sep); + /* skip escaped separators */ + while (test_end != NULL && test_end <= e_ptr) { + if (test_end > c_ptr && *(test_end - 1) == '\\') { + test_end = strchr(test_end + 1, sep); + } + else + break; + } + /* if nothing found, use e_ptr */ + if (test_end == NULL || test_end > e_ptr) { + test_end = e_ptr; + } + + len = test_end - c_ptr; + + /* skip trailing whitespace */ + while (len > 0 && isspace((int)c_ptr[len - 1])) { + len--; + } + + result_arr[result_cnt] = xmalloc(len + 1); + /* copy into result item, skipping the escape '\\' characters */ + { + size_t i = 0, j = 0; + while (i < len) { + if (c_ptr[i] == '\\' && c_ptr[i + 1] == sep) /* i + 1 is safe since (i < len) */ + i++; + result_arr[result_cnt][j++] = c_ptr[i++]; + } + result_arr[result_cnt][j] = '\0'; + } + result_cnt++; + + /* skip to next item */ + c_ptr = test_end + 1; + } + result_arr[result_cnt] = NULL; /* null-terminate return array, just to be safe */ + *ret_items = result_cnt; + return result_arr; +} + + +/*------------------------------------------------------------ + * find_file + * + * Arguments: + * filename - absolute or relative file name + * statbuf - buffer to stat filename + * pathinfo - kpse_file_format_type, only used if a kpathsearch for the + * file is performed. + * See kpathsea/tex-file.h for a list of possible values. + * + * Returns: + * expanded filename + * + * Purpose: + * Find a file name corresponding to <filename>, possibly + * expanding it to a full path name; checks if the file + * exists by stat()ing it; returns NULL if nothing has + * been found, else the expanded filename in fresh memory. + * + + *------------------------------------------------------------*/ + +char * +find_file(const char *filename, struct stat *statbuf, kpse_file_format_type pathinfo) +{ + char *tmp; + char *pathname; + + TRACE_SRC((stderr, "checking filename \"%s\"", filename)); + + /* + * case 1: + * try absolute filename + */ + if (filename[0] == '/') { + if (stat(filename, statbuf) == 0) { + TRACE_SRC((stderr, "Found absolute filename \"%s\"", filename)); + return xstrdup(filename); + } + else { + TRACE_SRC((stderr, "Can't stat absolute filename \"%s\"\n", filename)); + return NULL; + } + } + + /* + * case 2: + * prepend filename with dir component from the `main' xdvi file (globals.dvi_file.dirname). + * This works for both + * /absolute/path/ + filename + * and + * /absolute/path/ + relative/path/filename + */ + ASSERT(globals.dvi_file.dirname != NULL, "globals.dvi_file.dirname should have been initialized"); + { + pathname = xstrdup(globals.dvi_file.dirname); + pathname = xstrcat(pathname, filename); + + TRACE_SRC((stderr, "Trying globals.dvi_file.dirname: \"%s\"", pathname)); + if (stat(pathname, statbuf) == 0) { + return pathname; + } + } + + /* + * case 3: + * try current directory; if it's a match, expand to full (but uncanonicalized) path name. + */ + if (stat(filename, statbuf) == 0) { + TRACE_SRC((stderr, "Found file \"%s\" in current dir", filename)); + free(pathname); + return expand_filename(filename, USE_CWD_PATH); + } + + /* + * case 4: + * try a kpathsea search for filename + */ + TRACE_SRC((stderr, + "trying kpathsearch for filename \"%s\"", + filename)); + tmp = kpse_find_file(filename, pathinfo, True); + + if (tmp != NULL && stat(tmp, statbuf) == 0) { + TRACE_SRC((stderr, "Found file: `%s'", tmp)); + free(pathname); + return tmp; + } + + /* + * case 5: + * try a kpathsea search for pathname + */ + TRACE_SRC((stderr, + "trying kpathsearch for pathname \"%s\"", + pathname)); + tmp = kpse_find_file(pathname, pathinfo, True); + + if (tmp != NULL && stat(tmp, statbuf) == 0) { + TRACE_SRC((stderr, "Found file: `%s'", tmp)); + free(pathname); + return tmp; + } + + /* not found */ + free(pathname); + free(tmp); + errno = 0; + return NULL; +} + +/* + Hashtable functions + + The purpose of these is to wrap kpathsea's rather strange hash + functions that can be used to store 2 type of values: char *, and + long, where the latter is interpreted as char *. (See kpathsea/dir.c + for an example of where this is used). + We can't use a different approach (like using a void *) since + the debugging ouput of kpathsea relies on printing the value + either as char * or long (depending on the value of the global flag + `kpse_debug_hash_lookup_int'). +*/ +/* + If key is in hashtable, return True and the integer value in val; + else return False (and leave val untouched). +*/ +Boolean +find_str_int_hash(hashTableT *hashtable, const char *key, size_t *val) +{ + string *ret; +#ifdef KPSE_DEBUG + if (KPSE_DEBUG_P (KPSE_DEBUG_HASH)) + kpse_debug_hash_lookup_int = True; +#endif + ret = hash_lookup(*hashtable, key); +#ifdef KPSE_DEBUG + if (KPSE_DEBUG_P (KPSE_DEBUG_HASH)) + kpse_debug_hash_lookup_int = False; +#endif + + if (ret != NULL) { + long l = (long)*ret; + *val = (size_t)l; + return True; + } + return False; +} + +/* + Insert key-value pair into hashtable. Note that the key is + *not* copied (i.e. it is expected that is had been allocated + somewhere else, and persists throughout the program). + */ +void +put_str_int_hash(hashTableT *hashtable, const char *key, size_t val) +{ + long ptr = (long)val; + hash_insert(hashtable, key, (const string)ptr); +} + +/* set globals.dvi_name, globals.dvi_file.dirname and globals.dvi_file.dirlen */ +void +set_dvi_name_expand(const char *new_filename) +{ + ASSERT(new_filename != NULL, ""); + free(globals.dvi_name); + globals.dvi_name = expand_filename_append_dvi(new_filename, USE_CWD_PATH, True); + + free(globals.dvi_file.dirname); + globals.dvi_file.dirname = get_dir_component(globals.dvi_name); + + ASSERT(globals.dvi_file.dirname != NULL, "dvi_name should be a path with dir component"); + globals.dvi_file.dirlen = strlen(globals.dvi_file.dirname); +} + +/* set globals.dvi_name, globals.dvi_file.dirname and globals.dvi_file.dirlen + In contrast to previous function, input filename is not copied. + */ +void +set_dvi_name(char *new_filename) +{ + ASSERT(new_filename != NULL, ""); + free(globals.dvi_name); + globals.dvi_name = new_filename; + + free(globals.dvi_file.dirname); + globals.dvi_file.dirname = get_dir_component(globals.dvi_name); + + ASSERT(globals.dvi_file.dirname != NULL, "dvi_name should be a path with dir component"); + globals.dvi_file.dirlen = strlen(globals.dvi_file.dirname); +} + +/* + * Copy the file pointer `in' to the file pointer `out'. Return True + * if successful, False else (in which case caller should examine + * errno to find the error). + * The caller is responsible for closing the files. + */ +Boolean +copy_fp(FILE *in, FILE *out) +{ +#define TMP_BUF_SIZE 4 * 1024 + char buf[TMP_BUF_SIZE]; + + while (feof(in) == 0) { + size_t bytes_in, bytes_out; + + bytes_in = fread(buf, 1, TMP_BUF_SIZE, in); + if (bytes_in < TMP_BUF_SIZE && !feof(in)) + return False; + bytes_out = fwrite(buf, 1, bytes_in, out); +/* fprintf(stderr, "read %d, wrote %d bytes\n", bytes_in, bytes_out); */ + if (bytes_out < bytes_in) + return False; + } + return True; + +#undef TMP_BUF_SIZE +} + +/* + * Copy a file from `from_path' to `to'. Return True if successful, False else + * (in which case caller should examine errno to find the error). + */ +Boolean +copy_file(const char *from_path, const char *to_path) { + FILE *from_fp; + FILE *to_fp; + + Boolean retval; + + if ((from_fp = try_fopen(from_path, "rb")) == NULL) { + XDVI_ERROR((stderr, "opening %s for reading failed: %s", from_path, strerror(errno))); + return False; + } + + if ((to_fp = try_fopen(to_path, "wb")) == NULL) { + XDVI_ERROR((stderr, "opening %s for writing failed: %s", to_path, strerror(errno))); + return False; + } + + retval = copy_fp(from_fp, to_fp); + + fclose(from_fp); + fclose(to_fp); + + return retval; +} + +const char * +get_text_encoding(void) +{ + const char *text_encoding = NULL; + + /* if resource.text_encoding isn't set, use nl_langinfo() if langinfo is available */ + if (resource.text_encoding == NULL) { +#if USE_LANGINFO + if (globals.orig_locale == NULL) { + XDVI_ERROR((stderr, "Call to setlocale() returned NULL; assuming ISO-8859-1 charset.")); + text_encoding = "ISO-8859-1"; + } + else { + if (strcmp(globals.orig_locale, "C") == 0 || strcmp(globals.orig_locale, "POSIX") == 0) { + /* nl_langinfo returns rather strange values for these ... */ + text_encoding = "ISO-8859-1"; + TRACE_FIND((stderr, "Assuming |%s| for locale |%s|", + text_encoding, globals.orig_locale)); + } + else { + text_encoding = nl_langinfo(CODESET); + TRACE_FIND((stderr, "nl_langinfo returned: |%s| for locale |%s|", + text_encoding, globals.orig_locale)); + } + } +#else + XDVI_WARNING((stderr, + "nl_langinfo() not available on this platform, " + "and XDvi.textEncoding resource not set; using default " + "encoding ISO-8859-1.")); + text_encoding = "ISO-8859-1"; +#endif + } + else { + text_encoding = resource.text_encoding; + } + return text_encoding; +} + +char * +iconv_convert_string(const char *from_enc, const char *to_enc, const char *str) +{ + static Boolean have_warned = False; +#if HAVE_ICONV_H + size_t input_len = strlen(str); + size_t conv_len = input_len * 4 + 1; /* worst case ... */ + int conv_len_save = conv_len; + char *conv_buf = xmalloc(conv_len); + const char *in_ptr = str; + const char *out_ptr = conv_buf; + + iconv_t conv_desc = iconv_open(to_enc, from_enc); + + if (conv_desc == (iconv_t)(-1)) { + if (!have_warned) { + popup_message(XtNameToWidget(globals.widgets.top_level, "*find_popup"), + MSG_ERR, + NULL, + "iconv_open() error: Encoding \"%s\" is not supported by this version of iconv.\n" + "Please check the output of \"iconv -l\" and set the X resource\n" + "\"XDvi.textEncoding\" to an appropriate value.", from_enc); + have_warned = True; + } + free(conv_buf); + return NULL; + } + + TRACE_FIND((stderr, "iconv_convert_string: from `%s', to `%s'", from_enc, to_enc)); + if (iconv(conv_desc, (iconv_char_pptrT)&in_ptr, &input_len, (char **)&out_ptr, &conv_len) == (size_t)(-1)) { + popup_message(XtNameToWidget(globals.widgets.top_level, "*find_popup"), + MSG_ERR, + NULL, + "iconv_convert_string(): Could not convert %s to %s: %s.", + from_enc, to_enc, strerror(errno)); + iconv_close(conv_desc); + free(conv_buf); + return NULL; + } + + iconv_close(conv_desc); + conv_len = conv_len_save - conv_len; + conv_buf[conv_len] = '\0'; + + TRACE_FIND((stderr, "after iconv conversion: |%s| %lu bytes\n", + conv_buf, (unsigned long)conv_len)); + + + return conv_buf; + +#else /* HAVE_ICONV_H */ + + UNUSED(from_enc); + UNUSED(to_enc); + UNUSED(str); + + /* no iconv available */ + if (!have_warned) { + popup_message(XtNameToWidget(globals.widgets.top_level, "*find_popup"), + MSG_ERR, + "You can either set the \"LANG\" environment variable or the X resource " + "\"XDvi.textEncoding\" to make xdvi use a different language/encoding setting.\n" + "Without iconv, only the encodings ISO-8859-1 and UTF-8 are supported. " + "For real iconv support, you will need to install the iconv library " + "and recompile xdvik.", + "Cannot convert from %s to UTF-8 without iconv support compiled in."); + have_warned = True; + } + return NULL; + +#endif /* HAVE_ICONV_H */ +} + +#if 0 +/* + * Search integer array <arr> of length <arr_len> for for <item>. + * Return the index of the item, or the index of the next smaller item + * if there's no exact match. (That we want the latter is the + * reason why we can't use bsearch()). + */ +int +binary_search(int *arr, int arr_len, int item) +{ + int lower = -1; + int upper = arr_len; + int mid; + + ASSERT(arr_len >= 1, "binary_search expects arrays of length >= 1"); + + do { + mid = (lower + upper) / 2; + if (item > arr[mid]) + lower = mid; + else if (item < arr[mid]) + upper = mid; + else /* exact match */ + return mid; + } while (upper - lower > 1); + + /* no exact match, return next lower item */ + if (mid > 0 && arr[mid] > item) + return mid - 1; + else + return mid; +} +#endif /* 0 */ |